first add
This commit is contained in:
		
						commit
						bf3c65e488
					
				
							
								
								
									
										
											BIN
										
									
								
								.README.md.swp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								.README.md.swp
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
vendor/
 | 
			
		||||
							
								
								
									
										24
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
 | 
			
		||||
## Init
 | 
			
		||||
 | 
			
		||||
cd submodule 
 | 
			
		||||
git submodule add baidu:/root/repos/go/okexV5Api okex
 | 
			
		||||
cd ../
 | 
			
		||||
git pull
 | 
			
		||||
git submodule init
 | 
			
		||||
git submodule update --force --recursive --init --remote
 | 
			
		||||
go mod tidy
 | 
			
		||||
go mod vendor
 | 
			
		||||
 | 
			
		||||
## How TO RUN
 | 
			
		||||
 | 
			
		||||
go run main.go
 | 
			
		||||
 | 
			
		||||
环境分成两个:test和production,相关配置都在basicConfig.json中。
 | 
			
		||||
 | 
			
		||||
## 架构
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### 欧易文档首页:
 | 
			
		||||
 | 
			
		||||
https://www.okx.com/docs-v5/zh/#overview
 | 
			
		||||
							
								
								
									
										85
									
								
								configs/basicConfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								configs/basicConfig.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,85 @@
 | 
			
		||||
{
 | 
			
		||||
	"test": {
 | 
			
		||||
		"redis": {
 | 
			
		||||
			"url": "localhost:6379",
 | 
			
		||||
			"password": "",
 | 
			
		||||
			"index": 3,
 | 
			
		||||
      "description": ""
 | 
			
		||||
		},
 | 
			
		||||
		"credentialReadOnly": {
 | 
			
		||||
			"secretKey": "D6D74DF9DD60A25BE2B27CA71D8F814D",
 | 
			
		||||
			"baseUrl": "https://aws.okx.com",
 | 
			
		||||
			"okAccessKey": "fe468418-5e40-433f-8d04-04951286d417",
 | 
			
		||||
			"okAccessPassphrase": "M4pw71Id",
 | 
			
		||||
      "env": "realPlate",
 | 
			
		||||
      "ctype": "readOnly"
 | 
			
		||||
		},
 | 
			
		||||
		"connect": {
 | 
			
		||||
			"loginSubUrl": "/users/self/verify",
 | 
			
		||||
			"wsPrivateBaseUrl": "wsaws.okx.com:8443/ws/v5/private",
 | 
			
		||||
			"wsPublicBaseUrl": "wsaws.okx.com:8443/ws/v5/public",
 | 
			
		||||
			"restBaseUrl": "https://aws.okx.com"
 | 
			
		||||
		},
 | 
			
		||||
    "threads ":{
 | 
			
		||||
      "maxLenTickerStream": 512,
 | 
			
		||||
      "maxLenCandleStream": 1280,
 | 
			
		||||
      "maxCandles":7,
 | 
			
		||||
      "asyncChannels":40,
 | 
			
		||||
      "maxTickers":3,
 | 
			
		||||
      "restPeriod": 180,
 | 
			
		||||
      "waitWs": 120
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
	"stage": {
 | 
			
		||||
		"redis": {
 | 
			
		||||
			"url": "localhost:6379",
 | 
			
		||||
			"password": "",
 | 
			
		||||
			"index": 4,
 | 
			
		||||
      "description": ""
 | 
			
		||||
		},
 | 
			
		||||
		"credentialReadOnly": {
 | 
			
		||||
			"secretKey": "D6D74DF9DD60A25BE2B27CA71D8F814D",
 | 
			
		||||
			"baseUrl": "https://aws.okx.com",
 | 
			
		||||
			"okAccessKey": "fe468418-5e40-433f-8d04-04951286d417",
 | 
			
		||||
			"okAccessPassphrase": "M4pw71Id",
 | 
			
		||||
      "env": "realPlate",
 | 
			
		||||
      "ctype": "readOnly"
 | 
			
		||||
		},
 | 
			
		||||
		"connect": {
 | 
			
		||||
			"loginSubUrl": "/users/self/verify",
 | 
			
		||||
			"wsPrivateBaseUrl": "wsaws.okx.com:8443/ws/v5/private",
 | 
			
		||||
			"wsPublicBaseUrl": "wsaws.okx.com:8443/ws/v5/public",
 | 
			
		||||
			"restBaseUrl": "https://aws.okx.com"
 | 
			
		||||
		}
 | 
			
		||||
  },
 | 
			
		||||
	"production": {
 | 
			
		||||
		"redis": {
 | 
			
		||||
			"url": "localhost:6379",
 | 
			
		||||
			"password": "",
 | 
			
		||||
			"index": 5
 | 
			
		||||
		},
 | 
			
		||||
		"credentialReadOnly": {
 | 
			
		||||
			"secretKey": "D6D74DF9DD60A25BE2B27CA71D8F814D",
 | 
			
		||||
			"baseUrl": "https://aws.okx.com",
 | 
			
		||||
			"okAccessKey": "fe468418-5e40-433f-8d04-04951286d417",
 | 
			
		||||
      "env": "realPlate",
 | 
			
		||||
			"okAccessPassphrase": "M4pw71Id",
 | 
			
		||||
      "ctype": "readOnly"
 | 
			
		||||
		},
 | 
			
		||||
		"credentialMutable": {
 | 
			
		||||
			"secretKey": "49F354BBEEA3D917FF190F94525ACEB7",
 | 
			
		||||
			"baseUrl": "https://aws.okx.com",
 | 
			
		||||
			"okAccessKey": "98afba4e-531b-4ec7-af2e-16e270b6b576",
 | 
			
		||||
      "env": "realPlate",
 | 
			
		||||
			"okAccessPassphrase": "jitbyw-kArrac-zydva2",
 | 
			
		||||
      "ctype": "Mutable"
 | 
			
		||||
		},
 | 
			
		||||
		"connect": {
 | 
			
		||||
			"userId": "169408628405739520",
 | 
			
		||||
			"loginSubUrl": "/users/self/verify",
 | 
			
		||||
			"wsPrivateBaseUrl": "wsaws.okx.com:8443/ws/v5/private",
 | 
			
		||||
			"wsPublicBaseUrl": "wsaws.okx.com:8443/ws/v5/public",
 | 
			
		||||
			"restBaseUrl": "https://aws.okx.com"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										450
									
								
								core/candle.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										450
									
								
								core/candle.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,450 @@
 | 
			
		||||
package core
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"math/rand"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
	"v5sdk_go/rest"
 | 
			
		||||
 | 
			
		||||
	simple "github.com/bitly/go-simplejson"
 | 
			
		||||
	"github.com/go-redis/redis"
 | 
			
		||||
	"phyer.click/tunas/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Candle struct {
 | 
			
		||||
	core   *Core
 | 
			
		||||
	InstId string
 | 
			
		||||
	Period string
 | 
			
		||||
	Data   []interface{}
 | 
			
		||||
	From   string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MaX struct {
 | 
			
		||||
	Core    *Core
 | 
			
		||||
	InstId  string
 | 
			
		||||
	Period  string
 | 
			
		||||
	KeyName string
 | 
			
		||||
	Count   int
 | 
			
		||||
	Ts      int64
 | 
			
		||||
	Value   float64
 | 
			
		||||
	Data    []interface{}
 | 
			
		||||
	From    string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MatchCheck struct {
 | 
			
		||||
	Minutes int64
 | 
			
		||||
	Matched bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mc *MatchCheck) SetMatched(value bool) {
 | 
			
		||||
	mc.Matched = value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) GetCandlesWithRest(instId string, kidx int, dura time.Duration, maxCandles int) error {
 | 
			
		||||
	ary := []string{}
 | 
			
		||||
 | 
			
		||||
	wsary := core.Cfg.CandleDimentions
 | 
			
		||||
	for k, v := range wsary {
 | 
			
		||||
		matched := false
 | 
			
		||||
		// 这个算法的目的是:越靠后的candles维度,被命中的概率越低,第一个百分之百命中,后面开始越来越低, 每分钟都会发生这样的计算,
 | 
			
		||||
		// 因为维度多了的话,照顾不过来
 | 
			
		||||
		rand.New(rand.NewSource(time.Now().UnixNano()))
 | 
			
		||||
		rand.Seed(time.Now().UnixNano())
 | 
			
		||||
		n := (k*2 + 2) * 3
 | 
			
		||||
		if n < 1 {
 | 
			
		||||
			n = 1
 | 
			
		||||
		}
 | 
			
		||||
		b := rand.Intn(n)
 | 
			
		||||
		if b < 8 {
 | 
			
		||||
			matched = true
 | 
			
		||||
		}
 | 
			
		||||
		if matched {
 | 
			
		||||
			ary = append(ary, v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mdura := dura/(time.Duration(len(ary)+1)) - 50*time.Millisecond
 | 
			
		||||
	// fmt.Println("loop4 Ticker Start instId, dura: ", instId, dura, dura/10, mdura, len(ary), " idx: ", kidx)
 | 
			
		||||
	// time.Duration(len(ary)+1)
 | 
			
		||||
	ticker := time.NewTicker(mdura)
 | 
			
		||||
	done := make(chan bool)
 | 
			
		||||
	idx := 0
 | 
			
		||||
	go func(i int) {
 | 
			
		||||
		for {
 | 
			
		||||
			select {
 | 
			
		||||
			case <-ticker.C:
 | 
			
		||||
				if i >= (len(ary)) {
 | 
			
		||||
					done <- true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				rand.Seed(time.Now().UnixNano())
 | 
			
		||||
				b := rand.Intn(2)
 | 
			
		||||
				maxCandles = maxCandles * (i + b) * 2
 | 
			
		||||
 | 
			
		||||
				if maxCandles < 3 {
 | 
			
		||||
					maxCandles = 3
 | 
			
		||||
				}
 | 
			
		||||
				if maxCandles > 30 {
 | 
			
		||||
					maxCandles = 30
 | 
			
		||||
				}
 | 
			
		||||
				mx := strconv.Itoa(maxCandles)
 | 
			
		||||
				// fmt.Println("loop4 getCandlesWithRest, instId, period,limit,dura, t: ", instId, ary[i], mx, mdura)
 | 
			
		||||
				go func(ii int) {
 | 
			
		||||
					restQ := RestQueue{
 | 
			
		||||
						InstId:   instId,
 | 
			
		||||
						Bar:      ary[ii],
 | 
			
		||||
						Limit:    mx,
 | 
			
		||||
						Duration: mdura,
 | 
			
		||||
						WithWs:   true,
 | 
			
		||||
					}
 | 
			
		||||
					js, _ := json.Marshal(restQ)
 | 
			
		||||
					core.RedisCli.LPush("restQueue", js)
 | 
			
		||||
				}(i)
 | 
			
		||||
				i++
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}(idx)
 | 
			
		||||
	time.Sleep(dura - 10*time.Millisecond)
 | 
			
		||||
	ticker.Stop()
 | 
			
		||||
	// fmt.Println("loop4 Ticker stopped instId, dura: ", instId, dura, mdura)
 | 
			
		||||
	done <- true
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 当前的时间毫秒数 对于某个时间段,比如3分钟,10分钟,是否可以被整除,
 | 
			
		||||
func IsModOf(curInt int64, duration time.Duration) bool {
 | 
			
		||||
	vol := int64(0)
 | 
			
		||||
	if duration < 24*time.Hour {
 | 
			
		||||
		// 小于1天
 | 
			
		||||
		vol = (curInt + 28800000)
 | 
			
		||||
	} else if duration >= 24*time.Hour && duration < 48*time.Hour {
 | 
			
		||||
		// 1天
 | 
			
		||||
		vol = curInt - 1633881600000
 | 
			
		||||
	} else if duration >= 48*time.Hour && duration < 72*time.Hour {
 | 
			
		||||
		// 2天
 | 
			
		||||
		vol = curInt - 1633795200000
 | 
			
		||||
	} else if duration >= 72*time.Hour && duration < 120*time.Hour {
 | 
			
		||||
		// 3天
 | 
			
		||||
		vol = curInt - 1633708800000
 | 
			
		||||
	} else if duration >= 120*time.Hour {
 | 
			
		||||
		// 5天
 | 
			
		||||
		vol = curInt - 1633795200000
 | 
			
		||||
	} else {
 | 
			
		||||
		// fmt.Println("noMatched:", curInt)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mody := vol % duration.Milliseconds()
 | 
			
		||||
	if mody == 0 {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) SaveCandle(instId string, period string, rsp *rest.RESTAPIResult, dura time.Duration, withWs bool) {
 | 
			
		||||
	js, err := simple.NewJson([]byte(rsp.Body))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("restTicker err: ", err, rsp.Body)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if len(rsp.Body) == 0 {
 | 
			
		||||
		fmt.Println("rsp body is null")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	itemList := js.Get("data").MustArray()
 | 
			
		||||
	Daoxu(itemList)
 | 
			
		||||
 | 
			
		||||
	leng := len(itemList)
 | 
			
		||||
	for _, v := range itemList {
 | 
			
		||||
		candle := Candle{
 | 
			
		||||
			InstId: instId,
 | 
			
		||||
			Period: period,
 | 
			
		||||
			Data:   v.([]interface{}),
 | 
			
		||||
			From:   "rest",
 | 
			
		||||
		}
 | 
			
		||||
		//保存rest得到的candle
 | 
			
		||||
		saveCandle := os.Getenv("TUNAS_SAVECANDLE")
 | 
			
		||||
		if saveCandle == "true" {
 | 
			
		||||
			candle.SetToKey(core)
 | 
			
		||||
		}
 | 
			
		||||
		// 发布到allCandles|publish, 给外部订阅者用于setToKey,和给其他协程用于订阅ws
 | 
			
		||||
		arys := []string{ALLCANDLES_PUBLISH}
 | 
			
		||||
		if withWs {
 | 
			
		||||
			arys = append(arys, ALLCANDLES_INNER_PUBLISH)
 | 
			
		||||
		}
 | 
			
		||||
		core.AddToGeneralCandleChnl(&candle, arys)
 | 
			
		||||
		time.Sleep(dura / time.Duration(leng))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Daoxu(arr []interface{}) {
 | 
			
		||||
	var temp interface{}
 | 
			
		||||
	length := len(arr)
 | 
			
		||||
	for i := 0; i < length/2; i++ {
 | 
			
		||||
		temp = arr[i]
 | 
			
		||||
		arr[i] = arr[length-1-i]
 | 
			
		||||
		arr[length-1-i] = temp
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cl *Candle) SetToKey(core *Core) ([]interface{}, error) {
 | 
			
		||||
	data := cl.Data
 | 
			
		||||
	tsi, err := strconv.ParseInt(data[0].(string), 10, 64)
 | 
			
		||||
	tss := strconv.FormatInt(tsi, 10)
 | 
			
		||||
	keyName := "candle" + cl.Period + "|" + cl.InstId + "|ts:" + tss
 | 
			
		||||
	//过期时间:根号(当前candle的周期/1分钟)*10000
 | 
			
		||||
 | 
			
		||||
	dt, err := json.Marshal(cl.Data)
 | 
			
		||||
	exp := core.PeriodToMinutes(cl.Period)
 | 
			
		||||
	// expf := float64(exp) * 60
 | 
			
		||||
	expf := utils.Sqrt(float64(exp)) * 100
 | 
			
		||||
	extt := time.Duration(expf) * time.Minute
 | 
			
		||||
	curVolstr, _ := data[5].(string)
 | 
			
		||||
	curVol, err := strconv.ParseFloat(curVolstr, 64)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("err of convert ts:", err)
 | 
			
		||||
	}
 | 
			
		||||
	curVolCcystr, _ := data[6].(string)
 | 
			
		||||
	curVolCcy, err := strconv.ParseFloat(curVolCcystr, 64)
 | 
			
		||||
	curPrice := curVolCcy / curVol
 | 
			
		||||
	if curPrice <= 0 {
 | 
			
		||||
		fmt.Println("price有问题", curPrice, "dt: ", string(dt), "from:", cl.From)
 | 
			
		||||
		err = errors.New("price有问题")
 | 
			
		||||
		return cl.Data, err
 | 
			
		||||
	}
 | 
			
		||||
	redisCli := core.RedisCli
 | 
			
		||||
	// tm := time.UnixMilli(tsi).Format("2006-01-02 15:04")
 | 
			
		||||
	// fmt.Println("setToKey:", keyName, "ts: ", tm, "price: ", curPrice, "from:", cl.From)
 | 
			
		||||
	redisCli.Set(keyName, dt, extt).Result()
 | 
			
		||||
	core.SaveUniKey(cl.Period, keyName, extt, tsi)
 | 
			
		||||
	return cl.Data, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (mx *MaX) SetToKey() ([]interface{}, error) {
 | 
			
		||||
	cstr := strconv.Itoa(mx.Count)
 | 
			
		||||
	tss := strconv.FormatInt(mx.Ts, 10)
 | 
			
		||||
	keyName := "ma" + cstr + "|candle" + mx.Period + "|" + mx.InstId + "|ts:" + tss
 | 
			
		||||
	//过期时间:根号(当前candle的周期/1分钟)*10000
 | 
			
		||||
	dt := []interface{}{}
 | 
			
		||||
	dt = append(dt, mx.Ts)
 | 
			
		||||
	dt = append(dt, mx.Value)
 | 
			
		||||
	dj, _ := json.Marshal(dt)
 | 
			
		||||
	exp := mx.Core.PeriodToMinutes(mx.Period)
 | 
			
		||||
	expf := utils.Sqrt(float64(exp)) * 100
 | 
			
		||||
	extt := time.Duration(expf) * time.Minute
 | 
			
		||||
	// loc, _ := time.LoadLocation("Asia/Shanghai")
 | 
			
		||||
	// tm := time.UnixMilli(mx.Ts).In(loc).Format("2006-01-02 15:04")
 | 
			
		||||
	// fmt.Println("setToKey:", keyName, "ts:", tm, string(dj), "from: ", mx.From)
 | 
			
		||||
	_, err := mx.Core.RedisCli.GetSet(keyName, dj).Result()
 | 
			
		||||
	mx.Core.RedisCli.Expire(keyName, extt)
 | 
			
		||||
	return dt, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) SaveUniKey(period string, keyName string, extt time.Duration, tsi int64) {
 | 
			
		||||
	refName := keyName + "|refer"
 | 
			
		||||
	refRes, _ := core.RedisCli.GetSet(refName, 1).Result()
 | 
			
		||||
	core.RedisCli.Expire(refName, extt)
 | 
			
		||||
	if len(refRes) == 0 {
 | 
			
		||||
		core.SaveToSortSet(period, keyName, extt, tsi)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// tsi: 上报时间timeStamp millinSecond
 | 
			
		||||
func (core *Core) SaveToSortSet(period string, keyName string, extt time.Duration, tsi int64) {
 | 
			
		||||
	ary := strings.Split(keyName, "ts:")
 | 
			
		||||
	setName := ary[0] + "sortedSet"
 | 
			
		||||
	z := redis.Z{
 | 
			
		||||
		Score:  float64(tsi),
 | 
			
		||||
		Member: keyName,
 | 
			
		||||
	}
 | 
			
		||||
	rs, err := core.RedisCli.ZAdd(setName, z).Result()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("err of ma7|ma30 add to redis:", err)
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("sortedSet add to redis:", rs, keyName)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cr *Core) PeriodToMinutes(period string) int64 {
 | 
			
		||||
	ary := strings.Split(period, "")
 | 
			
		||||
	beiStr := "1"
 | 
			
		||||
	danwei := ""
 | 
			
		||||
	if len(ary) == 3 {
 | 
			
		||||
		beiStr = ary[0] + ary[1]
 | 
			
		||||
		danwei = ary[2]
 | 
			
		||||
	} else {
 | 
			
		||||
		beiStr = ary[0]
 | 
			
		||||
		danwei = ary[1]
 | 
			
		||||
	}
 | 
			
		||||
	cheng := 1
 | 
			
		||||
	bei, _ := strconv.Atoi(beiStr)
 | 
			
		||||
	switch danwei {
 | 
			
		||||
	case "m":
 | 
			
		||||
		{
 | 
			
		||||
			cheng = bei
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	case "H":
 | 
			
		||||
		{
 | 
			
		||||
			cheng = bei * 60
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	case "D":
 | 
			
		||||
		{
 | 
			
		||||
			cheng = bei * 60 * 24
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	case "W":
 | 
			
		||||
		{
 | 
			
		||||
			cheng = bei * 60 * 24 * 7
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	case "M":
 | 
			
		||||
		{
 | 
			
		||||
			cheng = bei * 60 * 24 * 30
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	case "Y":
 | 
			
		||||
		{
 | 
			
		||||
			cheng = bei * 60 * 24 * 365
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		{
 | 
			
		||||
			fmt.Println("notmatch:", danwei)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return int64(cheng)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// type ScanCmd struct {
 | 
			
		||||
// baseCmd
 | 
			
		||||
//
 | 
			
		||||
// page   []string
 | 
			
		||||
// cursor uint64
 | 
			
		||||
//
 | 
			
		||||
// process func(cmd Cmder) error
 | 
			
		||||
// }
 | 
			
		||||
func (core *Core) GetRangeKeyList(pattern string, from time.Time) ([]*simple.Json, error) {
 | 
			
		||||
	// 比如,用来计算ma30或ma7,倒推多少时间范围,
 | 
			
		||||
	redisCli := core.RedisCli
 | 
			
		||||
	cursor := uint64(0)
 | 
			
		||||
	n := 0
 | 
			
		||||
	allTs := []int64{}
 | 
			
		||||
	var keys []string
 | 
			
		||||
	for {
 | 
			
		||||
		var err error
 | 
			
		||||
		keys, cursor, _ = redisCli.Scan(cursor, pattern+"*", 2000).Result()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			panic(err)
 | 
			
		||||
		}
 | 
			
		||||
		n += len(keys)
 | 
			
		||||
		if n == 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// keys, _ := redisCli.Keys(pattern + "*").Result()
 | 
			
		||||
	for _, key := range keys {
 | 
			
		||||
		keyAry := strings.Split(key, ":")
 | 
			
		||||
		key = keyAry[1]
 | 
			
		||||
		keyi64, _ := strconv.ParseInt(key, 10, 64)
 | 
			
		||||
		allTs = append(allTs, keyi64)
 | 
			
		||||
	}
 | 
			
		||||
	nary := utils.RecursiveBubble(allTs, len(allTs))
 | 
			
		||||
	tt := from.UnixMilli()
 | 
			
		||||
	ff := tt - tt%60000
 | 
			
		||||
	fi := int64(ff)
 | 
			
		||||
	mary := []int64{}
 | 
			
		||||
	for _, v := range nary {
 | 
			
		||||
		if v < fi {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		mary = append(mary, v)
 | 
			
		||||
	}
 | 
			
		||||
	res := []*simple.Json{}
 | 
			
		||||
	for _, v := range mary {
 | 
			
		||||
		// if k > 1 {
 | 
			
		||||
		// break
 | 
			
		||||
		// }
 | 
			
		||||
		nv := pattern + strconv.FormatInt(v, 10)
 | 
			
		||||
		str, err := redisCli.Get(nv).Result()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("err of redis get key:", nv, err)
 | 
			
		||||
		}
 | 
			
		||||
		cur, err := simple.NewJson([]byte(str))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("err of create newJson:", str, err)
 | 
			
		||||
		}
 | 
			
		||||
		res = append(res, cur)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return res, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) InitStreamGroups() {
 | 
			
		||||
	redisCli := core.RedisCli
 | 
			
		||||
	streamNames := []string{
 | 
			
		||||
		"candles|stream",
 | 
			
		||||
		"maXs|stream",
 | 
			
		||||
	}
 | 
			
		||||
	grpNames := []string{
 | 
			
		||||
		"sardine1",
 | 
			
		||||
		"sardine2",
 | 
			
		||||
		"sardine3",
 | 
			
		||||
	}
 | 
			
		||||
	for _, v := range streamNames {
 | 
			
		||||
		for _, vv := range grpNames {
 | 
			
		||||
			redisCli.XGroupCreate(v, vv, "0-0").Result()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
func (cr *Core) AddToGeneralCandleChnl(candle *Candle, channels []string) {
 | 
			
		||||
	//只让特定概率的事件发生
 | 
			
		||||
	// r := rand.New(rand.NewSource(time.Now().UnixNano()))
 | 
			
		||||
	// val := r.Float64()
 | 
			
		||||
	// rand.Seed(time.Now().UnixNano())
 | 
			
		||||
	// if from == "ws" && val > 0.90 {
 | 
			
		||||
	// fmt.Println("drop ", from)
 | 
			
		||||
	// return
 | 
			
		||||
	// }
 | 
			
		||||
	// fmt.Println("not drop ", from)
 | 
			
		||||
	// TODO map
 | 
			
		||||
	redisCli := cr.RedisCli
 | 
			
		||||
 | 
			
		||||
	ab, _ := json.Marshal(candle)
 | 
			
		||||
	//特定的币只发送给特定的下游接收者
 | 
			
		||||
	// 这么设计不太好,业务逻辑和程序架构紧耦合了,
 | 
			
		||||
	prename := cr.DispatchDownstreamNodes(candle.InstId)
 | 
			
		||||
 | 
			
		||||
	for _, v := range channels {
 | 
			
		||||
		suffix := ""
 | 
			
		||||
		env := os.Getenv("GO_ENV")
 | 
			
		||||
		if env == "demoEnv" {
 | 
			
		||||
			suffix = "-demoEnv"
 | 
			
		||||
		}
 | 
			
		||||
		vd := v + suffix
 | 
			
		||||
 | 
			
		||||
		// TODO FIXME cli2
 | 
			
		||||
		_, err := redisCli.Publish(vd, string(ab)).Result()
 | 
			
		||||
		if len(prename) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		nodeAdd := prename + "|" + v + suffix
 | 
			
		||||
		// fmt.Println("nodeAdd: ", nodeAdd)
 | 
			
		||||
 | 
			
		||||
		// TODO FIXME cli2
 | 
			
		||||
		_, err = redisCli.Publish(nodeAdd, string(ab)).Result()
 | 
			
		||||
		// fmt.Println("publish, channel,res,err:", nodeAdd, res, err, "candle:", string(ab))
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("err of ma7|ma30 add to redis2:", err, candle.From)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// TODO 下面这个先屏蔽,不订阅ws信息,否则系统压力会太大,等有更灵活的机制再说
 | 
			
		||||
	// redisCli.Publish("allCandlesInner|publish"+suffix, string(ab)).Result()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										122
									
								
								core/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								core/config.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,122 @@
 | 
			
		||||
package core
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"log"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	simple "github.com/bitly/go-simplejson"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type MyConfig struct {
 | 
			
		||||
	Env                    string `json:"env", string`
 | 
			
		||||
	Config                 *simple.Json
 | 
			
		||||
	CandleDimentions       []string          `json:"candleDimentions"`
 | 
			
		||||
	RedisConf              *RedisConfig      `json:"redis"`
 | 
			
		||||
	CredentialReadOnlyConf *CredentialConfig `json:"credential"`
 | 
			
		||||
	CredentialMutableConf  *CredentialConfig `json:"credential"`
 | 
			
		||||
	ConnectConf            *ConnectConfig    `json:"connect"`
 | 
			
		||||
	// ThreadsConf    *ThreadsConfig    `json:"threads"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RedisConfig struct {
 | 
			
		||||
	Url      string `json:"url", string`
 | 
			
		||||
	Password string `json:"password", string`
 | 
			
		||||
	Index    int    `json:"index", string`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CredentialConfig struct {
 | 
			
		||||
	SecretKey          string `json:"secretKey", string`
 | 
			
		||||
	BaseUrl            string `json:"baseUrl", string`
 | 
			
		||||
	OkAccessKey        string `json:"okAccessKey", string`
 | 
			
		||||
	OkAccessPassphrase string `json:"okAccessPassphrase", string`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ConnectConfig struct {
 | 
			
		||||
	LoginSubUrl      string `json:"loginSubUrl", string`
 | 
			
		||||
	WsPrivateBaseUrl string `json:"wsPrivateBaseUrl", string`
 | 
			
		||||
	WsPublicBaseUrl  string `json:"wsPublicBaseUrl", string`
 | 
			
		||||
	RestBaseUrl      string `json:"restBaseUrl", string`
 | 
			
		||||
}
 | 
			
		||||
type ThreadsConfig struct {
 | 
			
		||||
	MaxLenTickerStream int `json:"maxLenTickerStream", int`
 | 
			
		||||
	MaxCandles         int `json:"maxCandles", string`
 | 
			
		||||
	AsyncChannels      int `json:"asyncChannels", int`
 | 
			
		||||
	MaxTickers         int `json:"maxTickers", int`
 | 
			
		||||
	RestPeriod         int `json:"restPeriod", int`
 | 
			
		||||
	WaitWs             int `json:"waitWs", int`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cfg MyConfig) Init() (MyConfig, error) {
 | 
			
		||||
	env := os.Getenv("GO_ENV")
 | 
			
		||||
	arystr := os.Getenv("TUNAS_CANDLESDIMENTIONS")
 | 
			
		||||
	ary := strings.Split(arystr, "|")
 | 
			
		||||
	cfg.CandleDimentions = ary
 | 
			
		||||
	jsonStr, err := ioutil.ReadFile("/go/json/basicConfig.json")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		jsonStr, err = ioutil.ReadFile("configs/basicConfig.json")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("err2:", err.Error())
 | 
			
		||||
			return cfg, err
 | 
			
		||||
		}
 | 
			
		||||
		cfg.Config, err = simple.NewJson([]byte(jsonStr))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("err2:", err.Error())
 | 
			
		||||
			return cfg, err
 | 
			
		||||
		}
 | 
			
		||||
		cfg.Env = env
 | 
			
		||||
	}
 | 
			
		||||
	cfg.Config = cfg.Config.Get(env)
 | 
			
		||||
 | 
			
		||||
	ru, err := cfg.Config.Get("redis").Get("url").String()
 | 
			
		||||
	rp, _ := cfg.Config.Get("redis").Get("password").String()
 | 
			
		||||
	ri, _ := cfg.Config.Get("redis").Get("index").Int()
 | 
			
		||||
	redisConf := RedisConfig{
 | 
			
		||||
		Url:      ru,
 | 
			
		||||
		Password: rp,
 | 
			
		||||
		Index:    ri,
 | 
			
		||||
	}
 | 
			
		||||
	// fmt.Println("cfg: ", cfg)
 | 
			
		||||
	cfg.RedisConf = &redisConf
 | 
			
		||||
	ls, _ := cfg.Config.Get("connect").Get("loginSubUrl").String()
 | 
			
		||||
	wsPub, _ := cfg.Config.Get("connect").Get("wsPrivateBaseUrl").String()
 | 
			
		||||
	wsPri, _ := cfg.Config.Get("connect").Get("wsPublicBaseUrl").String()
 | 
			
		||||
	restBu, _ := cfg.Config.Get("connect").Get("restBaseUrl").String()
 | 
			
		||||
	connectConfig := ConnectConfig{
 | 
			
		||||
		LoginSubUrl:      ls,
 | 
			
		||||
		WsPublicBaseUrl:  wsPub,
 | 
			
		||||
		WsPrivateBaseUrl: wsPri,
 | 
			
		||||
		RestBaseUrl:      restBu,
 | 
			
		||||
	}
 | 
			
		||||
	cfg.ConnectConf = &connectConfig
 | 
			
		||||
	return cfg, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cfg *MyConfig) GetConfigJson(arr []string) *simple.Json {
 | 
			
		||||
	env := os.Getenv("GO_ENV")
 | 
			
		||||
	fmt.Println("env: ", env)
 | 
			
		||||
	cfg.Env = env
 | 
			
		||||
 | 
			
		||||
	json, err := ioutil.ReadFile("/go/json/basicConfig.json")
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		json, err = ioutil.ReadFile("configs/basicConfig.json")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Panic("read config error: ", err.Error())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("read file err: ", err)
 | 
			
		||||
	}
 | 
			
		||||
	rjson, err := simple.NewJson(json)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("newJson err: ", err)
 | 
			
		||||
	}
 | 
			
		||||
	for _, s := range arr {
 | 
			
		||||
		rjson = rjson.Get(s)
 | 
			
		||||
		// fmt.Println(s, ": ", rjson)
 | 
			
		||||
	}
 | 
			
		||||
	return rjson
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								core/const.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								core/const.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
package core
 | 
			
		||||
 | 
			
		||||
const MAIN_ALLCOINS_PERIOD_MINUTES = 1
 | 
			
		||||
const MAIN_ALLCOINS_ONCE_COUNTS = 3
 | 
			
		||||
const MAIN_ALLCOINS_BAR_PERIOD = "3m"
 | 
			
		||||
const ALLCANDLES_PUBLISH = "allCandles|publish"
 | 
			
		||||
const ALLCANDLES_INNER_PUBLISH = "allCandlesiInner|publish"
 | 
			
		||||
const ORDER_PUBLISH = "private|order|publish"
 | 
			
		||||
const TICKERINFO_PUBLISH = "tickerInfo|publish"
 | 
			
		||||
const CCYPOSISTIONS_PUBLISH = "ccyPositions|publish"
 | 
			
		||||
const SUBACTIONS_PUBLISH = "subActions|publish"
 | 
			
		||||
const ORDER_RESP_PUBLISH = "private|actionResp|publish"
 | 
			
		||||
							
								
								
									
										725
									
								
								core/core.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										725
									
								
								core/core.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,725 @@
 | 
			
		||||
package core
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"math/rand"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
	"v5sdk_go/rest"
 | 
			
		||||
	"v5sdk_go/ws"
 | 
			
		||||
	"v5sdk_go/ws/wImpl"
 | 
			
		||||
 | 
			
		||||
	simple "github.com/bitly/go-simplejson"
 | 
			
		||||
	"github.com/go-redis/redis"
 | 
			
		||||
	"phyer.click/tunas/private"
 | 
			
		||||
	"phyer.click/tunas/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Core struct {
 | 
			
		||||
	Env           string
 | 
			
		||||
	Cfg           *MyConfig
 | 
			
		||||
	RedisCli      *redis.Client
 | 
			
		||||
	RedisCli2     *redis.Client
 | 
			
		||||
	Wg            sync.WaitGroup
 | 
			
		||||
	RestQueueChan chan *RestQueue
 | 
			
		||||
	OrderChan     chan *private.Order
 | 
			
		||||
}
 | 
			
		||||
type RestQueue struct {
 | 
			
		||||
	InstId   string
 | 
			
		||||
	Bar      string
 | 
			
		||||
	After    int64
 | 
			
		||||
	Before   int64
 | 
			
		||||
	Limit    string
 | 
			
		||||
	Duration time.Duration
 | 
			
		||||
	WithWs   bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SubAction struct {
 | 
			
		||||
	ActionName string
 | 
			
		||||
	ForAll     bool
 | 
			
		||||
	MetaInfo   map[string]interface{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (rst *RestQueue) Show(cr *Core) {
 | 
			
		||||
	fmt.Println("restQueue:", rst.InstId, rst.Bar, rst.Limit)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (rst *RestQueue) Save(cr *Core) {
 | 
			
		||||
	afterSec := ""
 | 
			
		||||
	if rst.After > 0 {
 | 
			
		||||
		afterSec = fmt.Sprint("&after=", rst.After)
 | 
			
		||||
	}
 | 
			
		||||
	beforeSec := ""
 | 
			
		||||
	if rst.Before > 0 {
 | 
			
		||||
		beforeSec = fmt.Sprint("&before=", rst.Before)
 | 
			
		||||
	}
 | 
			
		||||
	limitSec := ""
 | 
			
		||||
	if len(rst.Limit) > 0 {
 | 
			
		||||
		limitSec = fmt.Sprint("&limit=", rst.Limit)
 | 
			
		||||
	}
 | 
			
		||||
	link := "/api/v5/market/candles?instId=" + rst.InstId + "&bar=" + rst.Bar + limitSec + afterSec + beforeSec
 | 
			
		||||
 | 
			
		||||
	fmt.Println("restLink: ", link)
 | 
			
		||||
	rsp, _ := cr.RestInvoke(link, rest.GET)
 | 
			
		||||
	cr.SaveCandle(rst.InstId, rst.Bar, rsp, rst.Duration, rst.WithWs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cr *Core) ShowSysTime() {
 | 
			
		||||
	rsp, _ := cr.RestInvoke("/api/v5/public/time", rest.GET)
 | 
			
		||||
	fmt.Println("serverSystem time:", rsp)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) Init() {
 | 
			
		||||
	core.Env = os.Getenv("GO_ENV")
 | 
			
		||||
	gitBranch := os.Getenv("gitBranchName")
 | 
			
		||||
	commitID := os.Getenv("gitCommitID")
 | 
			
		||||
 | 
			
		||||
	fmt.Println("当前环境: ", core.Env)
 | 
			
		||||
	fmt.Println("gitBranch: ", gitBranch)
 | 
			
		||||
	fmt.Println("gitCommitID: ", commitID)
 | 
			
		||||
	cfg := MyConfig{}
 | 
			
		||||
	cfg, _ = cfg.Init()
 | 
			
		||||
	core.Cfg = &cfg
 | 
			
		||||
	cli, err := core.GetRedisCli()
 | 
			
		||||
	core.RedisCli = cli
 | 
			
		||||
	core.RestQueueChan = make(chan *RestQueue)
 | 
			
		||||
	core.OrderChan = make(chan *private.Order)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("init redis client err: ", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) GetWsCli() (*ws.WsClient, error) {
 | 
			
		||||
	url, err := core.Cfg.Config.Get("connect").Get("wsPublicBaseUrl").String()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("err of json decode: ", err)
 | 
			
		||||
	}
 | 
			
		||||
	pubCli, err := ws.NewWsClient("wss://" + url)
 | 
			
		||||
	pubCli.AddBookMsgHook(core.PubMsgDispatcher)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("err of create ublic ws cli:", err)
 | 
			
		||||
	}
 | 
			
		||||
	return pubCli, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) DispatchDownstreamNodes(originName string) string {
 | 
			
		||||
	nodesStr := os.Getenv("TUNAS_DOWNSTREAM_NODES")
 | 
			
		||||
	if len(nodesStr) == 0 {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	nodes := strings.Split(nodesStr, "|")
 | 
			
		||||
	count := len(nodes)
 | 
			
		||||
	idx := utils.HashDispatch(originName, uint8(count))
 | 
			
		||||
	return nodes[idx]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) Dispatch(channel string, ctype string, instId string, data interface{}) error {
 | 
			
		||||
	// fmt.Println("start to SaveToRedis:", channel, ctype, instId, data)
 | 
			
		||||
	b, err := json.Marshal(data)
 | 
			
		||||
	js, err := simple.NewJson(b)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("err of unMarshalJson1:", js)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	isUsdt := strings.Contains(instId, "-USDT")
 | 
			
		||||
	instType, err := js.Get("instType").String()
 | 
			
		||||
	if !isUsdt {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// fmt.Println("instId: ", instId)
 | 
			
		||||
	redisCli := core.RedisCli
 | 
			
		||||
	channelType := ""
 | 
			
		||||
	if channel == "instruments" {
 | 
			
		||||
		channelType = "instruments"
 | 
			
		||||
	} else if strings.Contains(channel, "candle") {
 | 
			
		||||
		channelType = "candle"
 | 
			
		||||
	}
 | 
			
		||||
	switch channelType {
 | 
			
		||||
	case "instruments":
 | 
			
		||||
		{
 | 
			
		||||
			// fmt.Println("isInstrument:", instId)
 | 
			
		||||
			if instType != "SPOT" {
 | 
			
		||||
				return errors.New("instType is not SPOT")
 | 
			
		||||
			}
 | 
			
		||||
			_, err = redisCli.HSet("instruments|"+ctype+"|hash", instId, b).Result()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				fmt.Println("err of hset to redis:", err)
 | 
			
		||||
			}
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	case "candle":
 | 
			
		||||
		{
 | 
			
		||||
			data := data.([]interface{})
 | 
			
		||||
			ary := strings.Split(channel, "candle")
 | 
			
		||||
			// fmt.Println("dispatch candle:", ary[1], instId)
 | 
			
		||||
			candle := Candle{
 | 
			
		||||
				InstId: instId,
 | 
			
		||||
				Period: ary[1],
 | 
			
		||||
				Data:   data,
 | 
			
		||||
				From:   "ws",
 | 
			
		||||
			}
 | 
			
		||||
			core.WsSubscribe(&candle)
 | 
			
		||||
			saveCandle := os.Getenv("TUNAS_SAVECANDLE")
 | 
			
		||||
			if saveCandle == "true" {
 | 
			
		||||
				candle.SetToKey(core)
 | 
			
		||||
			}
 | 
			
		||||
			// TODO mxLen要放到core.Cfg里
 | 
			
		||||
			arys := []string{ALLCANDLES_PUBLISH}
 | 
			
		||||
			core.AddToGeneralCandleChnl(&candle, arys)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		{
 | 
			
		||||
			// data := data.([]interface{})
 | 
			
		||||
			// bj, _ := json.Marshal(data)
 | 
			
		||||
			// fmt.Println("private data:", string(bj))
 | 
			
		||||
			return errors.New("channel type not catched")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) PubMsgDispatcher(ts time.Time, data wImpl.MsgData) error {
 | 
			
		||||
	instList := data.Data
 | 
			
		||||
	for _, v := range instList {
 | 
			
		||||
		core.Dispatch(data.Arg["channel"], data.Arg["instType"], data.Arg["instId"], v)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) GetRedisCli() (*redis.Client, error) {
 | 
			
		||||
	ru := core.Cfg.RedisConf.Url
 | 
			
		||||
	rp := core.Cfg.RedisConf.Password
 | 
			
		||||
	ri := core.Cfg.RedisConf.Index
 | 
			
		||||
	re := os.Getenv("REDIS_URL")
 | 
			
		||||
	if len(re) > 0 {
 | 
			
		||||
		ru = re
 | 
			
		||||
	}
 | 
			
		||||
	client := redis.NewClient(&redis.Options{
 | 
			
		||||
		Addr:     ru,
 | 
			
		||||
		Password: rp, //默认空密码
 | 
			
		||||
		DB:       ri, //使用默认数据库
 | 
			
		||||
	})
 | 
			
		||||
	pong, err := client.Ping().Result()
 | 
			
		||||
	if pong == "PONG" && err == nil {
 | 
			
		||||
		return client, err
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("redis状态不可用:", ru, rp, ri, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return client, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) GetAllTickerInfo() (*rest.RESTAPIResult, error) {
 | 
			
		||||
	// GET / 获取所有产品行情信息
 | 
			
		||||
	rsp, err := core.RestInvoke("/api/v5/market/tickers?instType=SPOT", rest.GET)
 | 
			
		||||
	return rsp, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) GetBalances() (*rest.RESTAPIResult, error) {
 | 
			
		||||
	// TODO 临时用了两个实现,restInvoke,复用原来的会有bug,不知道是谁的bug
 | 
			
		||||
	rsp, err := core.RestInvoke2("/api/v5/account/balance", rest.GET, nil)
 | 
			
		||||
	return rsp, err
 | 
			
		||||
}
 | 
			
		||||
func (core *Core) GetLivingOrderList() ([]*private.Order, error) {
 | 
			
		||||
	// TODO 临时用了两个实现,restInvoke,复用原来的会有bug,不知道是谁的bug
 | 
			
		||||
	params := make(map[string]interface{})
 | 
			
		||||
	data, err := core.RestInvoke2("/api/v5/trade/orders-pending", rest.GET, ¶ms)
 | 
			
		||||
	odrsp := private.OrderResp{}
 | 
			
		||||
	err = json.Unmarshal([]byte(data.Body), &odrsp)
 | 
			
		||||
	str, _ := json.Marshal(odrsp)
 | 
			
		||||
	fmt.Println("convert: err:", err, " body: ", data.Body, odrsp, " string:", string(str))
 | 
			
		||||
	list, err := odrsp.Convert()
 | 
			
		||||
	fmt.Println("loopLivingOrder response data:", str)
 | 
			
		||||
	fmt.Println(utils.GetFuncName(), " 当前数据是 ", data.V5Response.Code, " list len:", len(list))
 | 
			
		||||
	return list, err
 | 
			
		||||
}
 | 
			
		||||
func (core *Core) LoopInstrumentList() error {
 | 
			
		||||
	for {
 | 
			
		||||
		time.Sleep(3 * time.Second)
 | 
			
		||||
		ctype := ws.SPOT
 | 
			
		||||
 | 
			
		||||
		redisCli := core.RedisCli
 | 
			
		||||
		counts, err := redisCli.HLen("instruments|" + ctype + "|hash").Result()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("err of hset to redis:", err)
 | 
			
		||||
		}
 | 
			
		||||
		if counts == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		break
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
func (core *Core) SubscribeTicker(op string) error {
 | 
			
		||||
	mp := make(map[string]string)
 | 
			
		||||
 | 
			
		||||
	redisCli := core.RedisCli
 | 
			
		||||
	ctype := ws.SPOT
 | 
			
		||||
	mp, err := redisCli.HGetAll("instruments|" + ctype + "|hash").Result()
 | 
			
		||||
	b, err := json.Marshal(mp)
 | 
			
		||||
	js, err := simple.NewJson(b)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("err of unMarshalJson3:", js)
 | 
			
		||||
	}
 | 
			
		||||
	// fmt.Println("ticker js: ", js)
 | 
			
		||||
	instAry := js.MustMap()
 | 
			
		||||
	for k, v := range instAry {
 | 
			
		||||
		b = []byte(v.(string))
 | 
			
		||||
		_, err := simple.NewJson(b)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("err of unMarshalJson4:", js)
 | 
			
		||||
		}
 | 
			
		||||
		time.Sleep(5 * time.Second)
 | 
			
		||||
		go func(instId string, op string) {
 | 
			
		||||
 | 
			
		||||
			redisCli := core.RedisCli
 | 
			
		||||
			_, err = redisCli.SAdd("tickers|"+op+"|set", instId).Result()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				fmt.Println("err of unMarshalJson5:", js)
 | 
			
		||||
			}
 | 
			
		||||
		}(k, op)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) InnerSubscribeTicker(name string, op string, retry bool) error {
 | 
			
		||||
	// 在这里 args1 初始化tickerList的列表
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instId"] = name
 | 
			
		||||
	arg["instType"] = ws.SPOT
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	if retry {
 | 
			
		||||
		go func(op string, args []map[string]string) {
 | 
			
		||||
			core.retrySubscribe(op, args)
 | 
			
		||||
		}(op, args)
 | 
			
		||||
	} else {
 | 
			
		||||
		go func(op string, args []map[string]string) {
 | 
			
		||||
			core.OnceSubscribe(op, args)
 | 
			
		||||
		}(op, args)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) OnceSubscribe(op string, args []map[string]string) error {
 | 
			
		||||
	wsCli, _ := core.GetWsCli()
 | 
			
		||||
	res, _, err := wsCli.PubTickers(op, args)
 | 
			
		||||
	// defer wsCli.Stop()
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("pubTickers err:", err)
 | 
			
		||||
	}
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) retrySubscribe(op string, args []map[string]string) error {
 | 
			
		||||
	wsCli, _ := core.GetWsCli()
 | 
			
		||||
	res, _, err := wsCli.PubTickers(op, args)
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		r := rand.New(rand.NewSource(time.Now().UnixNano()))
 | 
			
		||||
		val := r.Int() / 20000000000000000
 | 
			
		||||
		fmt.Println("pubTickers err:", err, val, "秒后重试")
 | 
			
		||||
		time.Sleep(time.Duration(val) * time.Second)
 | 
			
		||||
 | 
			
		||||
		return core.retrySubscribe(op, args)
 | 
			
		||||
	}
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) TickerScore(score float64, name string) error {
 | 
			
		||||
 | 
			
		||||
	redisCli := core.RedisCli
 | 
			
		||||
	mb := redis.Z{
 | 
			
		||||
		Score:  score,
 | 
			
		||||
		Member: name,
 | 
			
		||||
	}
 | 
			
		||||
	_, err := redisCli.ZAdd("tradingRank", mb).Result()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("zadd err:", err)
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) RestInvoke(subUrl string, method string) (*rest.RESTAPIResult, error) {
 | 
			
		||||
	restUrl, _ := core.Cfg.Config.Get("connect").Get("restBaseUrl").String()
 | 
			
		||||
	//ep, method, uri string, param *map[string]interface{}
 | 
			
		||||
	rest := rest.NewRESTAPI(restUrl, method, subUrl, nil)
 | 
			
		||||
	key, _ := core.Cfg.Config.Get("credentialReadOnly").Get("okAccessKey").String()
 | 
			
		||||
	secure, _ := core.Cfg.Config.Get("credentialReadOnly").Get("secretKey").String()
 | 
			
		||||
	pass, _ := core.Cfg.Config.Get("credentialReadOnly").Get("okAccessPassphrase").String()
 | 
			
		||||
	isDemo := false
 | 
			
		||||
	if core.Env == "demoEnv" {
 | 
			
		||||
		isDemo = true
 | 
			
		||||
	}
 | 
			
		||||
	rest.SetSimulate(isDemo).SetAPIKey(key, secure, pass)
 | 
			
		||||
	response, err := rest.Run(context.Background())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("restInvoke1 err:", subUrl, err)
 | 
			
		||||
	}
 | 
			
		||||
	return response, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) RestInvoke2(subUrl string, method string, param *map[string]interface{}) (*rest.RESTAPIResult, error) {
 | 
			
		||||
	key, err1 := core.Cfg.Config.Get("credentialReadOnly").Get("okAccessKey").String()
 | 
			
		||||
	secret, err2 := core.Cfg.Config.Get("credentialReadOnly").Get("secretKey").String()
 | 
			
		||||
	pass, err3 := core.Cfg.Config.Get("credentialReadOnly").Get("okAccessPassphrase").String()
 | 
			
		||||
	userId, err4 := core.Cfg.Config.Get("connect").Get("userId").String()
 | 
			
		||||
	restUrl, err5 := core.Cfg.Config.Get("connect").Get("restBaseUrl").String()
 | 
			
		||||
	if err1 != nil || err2 != nil || err3 != nil || err4 != nil || err5 != nil {
 | 
			
		||||
		fmt.Println(err1, err2, err3, err4, err5)
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("key:", key, secret, pass, "userId:", userId, "restUrl: ", restUrl)
 | 
			
		||||
	}
 | 
			
		||||
	// reqParam := make(map[string]interface{})
 | 
			
		||||
	// if param != nil {
 | 
			
		||||
	// reqParam = *param
 | 
			
		||||
	// }
 | 
			
		||||
	// rs := rest.NewRESTAPI(restUrl, method, subUrl, &reqParam)
 | 
			
		||||
	isDemo := false
 | 
			
		||||
	if core.Env == "demoEnv" {
 | 
			
		||||
		isDemo = true
 | 
			
		||||
	}
 | 
			
		||||
	// rs.SetSimulate(isDemo).SetAPIKey(key, secret, pass).SetUserId(userId)
 | 
			
		||||
	// response, err := rs.Run(context.Background())
 | 
			
		||||
	// if err != nil {
 | 
			
		||||
	// fmt.Println("restInvoke2 err:", subUrl, err)
 | 
			
		||||
	// }
 | 
			
		||||
	apikey := rest.APIKeyInfo{
 | 
			
		||||
		ApiKey:     key,
 | 
			
		||||
		SecKey:     secret,
 | 
			
		||||
		PassPhrase: pass,
 | 
			
		||||
	}
 | 
			
		||||
	cli := rest.NewRESTClient(restUrl, &apikey, isDemo)
 | 
			
		||||
	rsp, err := cli.Get(context.Background(), subUrl, param)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return rsp, err
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println("response:", rsp, err)
 | 
			
		||||
	return rsp, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) RestPost(subUrl string, param *map[string]interface{}) (*rest.RESTAPIResult, error) {
 | 
			
		||||
	key, err1 := core.Cfg.Config.Get("credentialMutable").Get("okAccessKey").String()
 | 
			
		||||
	secret, err2 := core.Cfg.Config.Get("credentialMutable").Get("secretKey").String()
 | 
			
		||||
	pass, err3 := core.Cfg.Config.Get("credentialMutable").Get("okAccessPassphrase").String()
 | 
			
		||||
	userId, err4 := core.Cfg.Config.Get("connect").Get("userId").String()
 | 
			
		||||
	restUrl, err5 := core.Cfg.Config.Get("connect").Get("restBaseUrl").String()
 | 
			
		||||
	if err1 != nil || err2 != nil || err3 != nil || err4 != nil || err5 != nil {
 | 
			
		||||
		fmt.Println(err1, err2, err3, err4, err5)
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("key:", key, secret, pass, "userId:", userId, "restUrl: ", restUrl)
 | 
			
		||||
	}
 | 
			
		||||
	// 请求的另一种方式
 | 
			
		||||
	apikey := rest.APIKeyInfo{
 | 
			
		||||
		ApiKey:     key,
 | 
			
		||||
		SecKey:     secret,
 | 
			
		||||
		PassPhrase: pass,
 | 
			
		||||
	}
 | 
			
		||||
	isDemo := false
 | 
			
		||||
	if core.Env == "demoEnv" {
 | 
			
		||||
		isDemo = true
 | 
			
		||||
	}
 | 
			
		||||
	cli := rest.NewRESTClient(restUrl, &apikey, isDemo)
 | 
			
		||||
	rsp, err := cli.Post(context.Background(), subUrl, param)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return rsp, err
 | 
			
		||||
	}
 | 
			
		||||
	return rsp, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 我当前持有的币,每分钟刷新
 | 
			
		||||
func (core *Core) GetMyFavorList() []string {
 | 
			
		||||
	redisCli := core.RedisCli
 | 
			
		||||
	opt := redis.ZRangeBy{
 | 
			
		||||
		Min: "10",
 | 
			
		||||
		Max: "100000000000",
 | 
			
		||||
	}
 | 
			
		||||
	cary, _ := redisCli.ZRevRangeByScore("private|positions|sortedSet", opt).Result()
 | 
			
		||||
	cl := []string{}
 | 
			
		||||
	for _, v := range cary {
 | 
			
		||||
		cry := strings.Split(v, "|")[0] + "-USDT"
 | 
			
		||||
		cl = append(cl, cry)
 | 
			
		||||
	}
 | 
			
		||||
	return cary
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 得到交易量排行榜,订阅其中前N名的各维度k线,并merge进来我已经购买的币列表,这个列表是动态更新的
 | 
			
		||||
// 改了,不需要交易排行榜,我手动指定一个排行即可, tickersVol|sortedSet 改成 tickersList|sortedSet
 | 
			
		||||
func (core *Core) GetScoreList(count int) []string {
 | 
			
		||||
 | 
			
		||||
	redisCli := core.RedisCli
 | 
			
		||||
 | 
			
		||||
	curList, err := redisCli.ZRevRange("tickersList|sortedSet", 0, int64(count-1)).Result()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("zrevrange err:", err)
 | 
			
		||||
	}
 | 
			
		||||
	return curList
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (core *Core) SubscribeCandleWithDimention(op string, instIdList []string, dimension string) {
 | 
			
		||||
	wsCli, err := core.GetWsCli()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("ws client err", err)
 | 
			
		||||
	}
 | 
			
		||||
	err = wsCli.Start()
 | 
			
		||||
	// 创建ws客户端
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("ws client start err", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	for _, vs := range instIdList {
 | 
			
		||||
		arg := make(map[string]string)
 | 
			
		||||
		arg["instId"] = vs
 | 
			
		||||
		args = append(args, arg)
 | 
			
		||||
	}
 | 
			
		||||
	_, _, err = wsCli.PubKLine(op, wImpl.Period(dimension), args)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("pubTickers err:", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 订阅某币,先要和配置比对,是否允许订阅此币此周期,
 | 
			
		||||
func (core *Core) WsSubscribe(candle *Candle) error {
 | 
			
		||||
	wsPeriods := []string{}
 | 
			
		||||
	wsary := core.Cfg.Config.Get("wsDimentions").MustArray()
 | 
			
		||||
	for _, v := range wsary {
 | 
			
		||||
		wsPeriods = append(wsPeriods, v.(string))
 | 
			
		||||
	}
 | 
			
		||||
	redisCli := core.RedisCli
 | 
			
		||||
	period := candle.Period
 | 
			
		||||
	instId := candle.InstId
 | 
			
		||||
	from := candle.From
 | 
			
		||||
	sname := instId + "|" + period + "ts|Subscribed|key"
 | 
			
		||||
 | 
			
		||||
	exists, _ := redisCli.Exists(sname).Result()
 | 
			
		||||
	ttl, _ := redisCli.TTL(sname).Result()
 | 
			
		||||
	inAry, _ := utils.In_Array(period, wsPeriods)
 | 
			
		||||
	if !inAry {
 | 
			
		||||
		estr := "subscribe 在配置中此period未被订阅: " + "," + period
 | 
			
		||||
		// fmt.Println(estr)
 | 
			
		||||
		err := errors.New(estr)
 | 
			
		||||
		return err
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("subscribe 已经订阅: ", period)
 | 
			
		||||
	}
 | 
			
		||||
	waitWs, _ := core.Cfg.Config.Get("threads").Get("waitWs").Int64()
 | 
			
		||||
	willSub := false
 | 
			
		||||
	if exists > 0 {
 | 
			
		||||
		if ttl > 0 {
 | 
			
		||||
			if from == "ws" {
 | 
			
		||||
				redisCli.Expire(sname, time.Duration(waitWs)*time.Second).Result()
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			willSub = true
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		willSub = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if willSub {
 | 
			
		||||
		// 需要订阅
 | 
			
		||||
 | 
			
		||||
		instIdList := []string{}
 | 
			
		||||
		instIdList = append(instIdList, instId)
 | 
			
		||||
 | 
			
		||||
		core.SubscribeCandleWithDimention(ws.OP_SUBSCRIBE, instIdList, period)
 | 
			
		||||
		// 如果距离上次检查此candle此维度订阅状态已经过去超过2分钟还没有发现有ws消息上报,执行订阅
 | 
			
		||||
		dr := 1 * time.Duration(waitWs) * time.Second
 | 
			
		||||
		redisCli.Set(sname, 1, dr).Result()
 | 
			
		||||
	} else {
 | 
			
		||||
		// fmt.Println("拒绝订阅candles:", keyName, "tm: ", tm, "otsi:", otsi)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func LoopBalances(cr *Core, mdura time.Duration) {
 | 
			
		||||
	//协程:动态维护topScore
 | 
			
		||||
	ticker := time.NewTicker(mdura)
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ticker.C:
 | 
			
		||||
			//协程:循环执行rest请求candle
 | 
			
		||||
			fmt.Println("loopBalance: receive ccyChannel start")
 | 
			
		||||
			RestBalances(cr)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
func LoopLivingOrders(cr *Core, mdura time.Duration) {
 | 
			
		||||
	//协程:动态维护topScore
 | 
			
		||||
	ticker := time.NewTicker(mdura)
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ticker.C:
 | 
			
		||||
			//协程:循环执行rest请求candle
 | 
			
		||||
			fmt.Println("loopLivingOrder: receive ccyChannel start")
 | 
			
		||||
			RestLivingOrder(cr)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func RestBalances(cr *Core) ([]*private.Ccy, error) {
 | 
			
		||||
	// fmt.Println("restBalance loopBalance loop start")
 | 
			
		||||
	ccyList := []*private.Ccy{}
 | 
			
		||||
	rsp, err := cr.GetBalances()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("loopBalance err00: ", err)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println("loopBalance balance rsp: ", rsp)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("loopBalance err01: ", err)
 | 
			
		||||
		return ccyList, err
 | 
			
		||||
	}
 | 
			
		||||
	if len(rsp.Body) == 0 {
 | 
			
		||||
		fmt.Println("loopBalance err03: rsp body is null")
 | 
			
		||||
		return ccyList, err
 | 
			
		||||
	}
 | 
			
		||||
	js1, err := simple.NewJson([]byte(rsp.Body))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("loopBalance err1: ", err)
 | 
			
		||||
	}
 | 
			
		||||
	itemList := js1.Get("data").GetIndex(0).Get("details").MustArray()
 | 
			
		||||
	// maxTickers是重点关注的topScore的coins的数量
 | 
			
		||||
	cli := cr.RedisCli
 | 
			
		||||
	for _, v := range itemList {
 | 
			
		||||
		js, _ := json.Marshal(v)
 | 
			
		||||
		ccyResp := private.CcyResp{}
 | 
			
		||||
		err := json.Unmarshal(js, &ccyResp)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("loopBalance err2: ", err)
 | 
			
		||||
		}
 | 
			
		||||
		ccy, err := ccyResp.Convert()
 | 
			
		||||
		ccyList = append(ccyList, ccy)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("loopBalance err2: ", err)
 | 
			
		||||
		}
 | 
			
		||||
		z := redis.Z{
 | 
			
		||||
			Score:  ccy.EqUsd,
 | 
			
		||||
			Member: ccy.Ccy + "|position|key",
 | 
			
		||||
		}
 | 
			
		||||
		res, err := cli.ZAdd("private|positions|sortedSet", z).Result()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("loopBalance err3: ", res, err)
 | 
			
		||||
		}
 | 
			
		||||
		res1, err := cli.Set(ccy.Ccy+"|position|key", js, 0).Result()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("loopBalance err4: ", res1, err)
 | 
			
		||||
		}
 | 
			
		||||
		bjs, _ := json.Marshal(ccy)
 | 
			
		||||
		tsi := time.Now().Unix()
 | 
			
		||||
		tsii := tsi - tsi%600
 | 
			
		||||
		tss := strconv.FormatInt(tsii, 10)
 | 
			
		||||
		cli.Set(CCYPOSISTIONS_PUBLISH+"|ts:"+tss, 1, 10*time.Minute).Result()
 | 
			
		||||
		fmt.Println("ccy published: ", string(bjs))
 | 
			
		||||
		//TODO FIXME 50毫秒,每分钟上限是1200个订单,超过就无法遍历完成
 | 
			
		||||
		time.Sleep(50 * time.Millisecond)
 | 
			
		||||
		suffix := ""
 | 
			
		||||
		if cr.Env == "demoEnv" {
 | 
			
		||||
			suffix = "-demoEnv"
 | 
			
		||||
		}
 | 
			
		||||
		// TODO FIXME cli2
 | 
			
		||||
		cli.Publish(CCYPOSISTIONS_PUBLISH+suffix, string(bjs)).Result()
 | 
			
		||||
	}
 | 
			
		||||
	return ccyList, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func RestLivingOrder(cr *Core) ([]*private.Order, error) {
 | 
			
		||||
	// fmt.Println("restOrder loopOrder loop start")
 | 
			
		||||
	orderList := []*private.Order{}
 | 
			
		||||
	list, err := cr.GetLivingOrderList()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("loopLivingOrder err00: ", err)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println("loopLivingOrder response:", list)
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, v := range list {
 | 
			
		||||
			fmt.Println("order orderV:", v)
 | 
			
		||||
			time.Sleep(30 * time.Millisecond)
 | 
			
		||||
			cr.OrderChan <- v
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	return orderList, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cr *Core) ProcessOrder(od *private.Order) error {
 | 
			
		||||
	// publish
 | 
			
		||||
	go func() {
 | 
			
		||||
		suffix := ""
 | 
			
		||||
		if cr.Env == "demoEnv" {
 | 
			
		||||
			suffix = "-demoEnv"
 | 
			
		||||
		}
 | 
			
		||||
		cn := ORDER_PUBLISH + suffix
 | 
			
		||||
		bj, _ := json.Marshal(od)
 | 
			
		||||
 | 
			
		||||
		// TODO FIXME cli2
 | 
			
		||||
		res, _ := cr.RedisCli.Publish(cn, string(bj)).Result()
 | 
			
		||||
		fmt.Println("order publish res: ", res, " content: ", string(bj))
 | 
			
		||||
		rsch := ORDER_RESP_PUBLISH + suffix
 | 
			
		||||
		bj1, _ := json.Marshal(res)
 | 
			
		||||
 | 
			
		||||
		// TODO FIXME cli2
 | 
			
		||||
		res, _ = cr.RedisCli.Publish(rsch, string(bj1)).Result()
 | 
			
		||||
	}()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cr *Core) DispatchSubAction(action *SubAction) error {
 | 
			
		||||
	go func() {
 | 
			
		||||
		suffix := ""
 | 
			
		||||
		if cr.Env == "demoEnv" {
 | 
			
		||||
			suffix = "-demoEnv"
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Println("action: ", action.ActionName, action.MetaInfo)
 | 
			
		||||
		res, err := cr.RestPostWrapper("/api/v5/trade/"+action.ActionName, action.MetaInfo)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println(utils.GetFuncName(), " actionRes 1:", err)
 | 
			
		||||
		}
 | 
			
		||||
		rsch := ORDER_RESP_PUBLISH + suffix
 | 
			
		||||
		bj1, _ := json.Marshal(res)
 | 
			
		||||
 | 
			
		||||
		// TODO FIXME cli2
 | 
			
		||||
		rs, _ := cr.RedisCli.Publish(rsch, string(bj1)).Result()
 | 
			
		||||
		fmt.Println("action rs: ", rs)
 | 
			
		||||
	}()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cr *Core) RestPostWrapper(url string, param map[string]interface{}) (rest.Okexv5APIResponse, error) {
 | 
			
		||||
	suffix := ""
 | 
			
		||||
	if cr.Env == "demoEnv" {
 | 
			
		||||
		suffix = "-demoEnv"
 | 
			
		||||
	}
 | 
			
		||||
	res, err := cr.RestPost(url, ¶m)
 | 
			
		||||
	fmt.Println("actionRes 2:", res.V5Response.Msg, res.V5Response.Data, err)
 | 
			
		||||
	bj, _ := json.Marshal(res)
 | 
			
		||||
 | 
			
		||||
	// TODO FIXME cli2
 | 
			
		||||
	cr.RedisCli.Publish(ORDER_RESP_PUBLISH+suffix, string(bj))
 | 
			
		||||
	return res.V5Response, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								core/ticker.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								core/ticker.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,68 @@
 | 
			
		||||
package core
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type TickerInfo struct {
 | 
			
		||||
	InstId    string  `json:"instId"`
 | 
			
		||||
	Last      float64 `json:"last"`
 | 
			
		||||
	InstType  string  `json:"instType"`
 | 
			
		||||
	VolCcy24h float64 `json:"volCcy24h"`
 | 
			
		||||
	Ts        int64   `json:"ts"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TickerInfoResp struct {
 | 
			
		||||
	InstId    string `json:"instId"`
 | 
			
		||||
	Last      string `json:"last"`
 | 
			
		||||
	InstType  string `json:"instType"`
 | 
			
		||||
	VolCcy24h string `json:"volCcy24h"`
 | 
			
		||||
	Ts        string `json:"ts"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (tir *TickerInfoResp) Convert() TickerInfo {
 | 
			
		||||
	ti := TickerInfo{
 | 
			
		||||
		InstId:    tir.InstId,
 | 
			
		||||
		InstType:  tir.InstType,
 | 
			
		||||
		Last:      ToFloat64(tir.Last),
 | 
			
		||||
		VolCcy24h: ToFloat64(tir.VolCcy24h),
 | 
			
		||||
		Ts:        ToInt64(tir.Ts),
 | 
			
		||||
	}
 | 
			
		||||
	return ti
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ToString(val interface{}) string {
 | 
			
		||||
	valstr := ""
 | 
			
		||||
	if reflect.TypeOf(val).Name() == "string" {
 | 
			
		||||
		valstr = val.(string)
 | 
			
		||||
	} else if reflect.TypeOf(val).Name() == "float64" {
 | 
			
		||||
		valstr = fmt.Sprintf("%f", val)
 | 
			
		||||
	} else if reflect.TypeOf(val).Name() == "int64" {
 | 
			
		||||
		valstr = strconv.FormatInt(val.(int64), 16)
 | 
			
		||||
	}
 | 
			
		||||
	return valstr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ToInt64(val interface{}) int64 {
 | 
			
		||||
	vali := int64(0)
 | 
			
		||||
	if reflect.TypeOf(val).Name() == "string" {
 | 
			
		||||
		vali, _ = strconv.ParseInt(val.(string), 10, 64)
 | 
			
		||||
	} else if reflect.TypeOf(val).Name() == "float64" {
 | 
			
		||||
		vali = int64(val.(float64))
 | 
			
		||||
	}
 | 
			
		||||
	return vali
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ToFloat64(val interface{}) float64 {
 | 
			
		||||
	valf := float64(0)
 | 
			
		||||
	if reflect.TypeOf(val).Name() == "string" {
 | 
			
		||||
		valf, _ = strconv.ParseFloat(val.(string), 64)
 | 
			
		||||
	} else if reflect.TypeOf(val).Name() == "float64" {
 | 
			
		||||
		valf = val.(float64)
 | 
			
		||||
	} else if reflect.TypeOf(val).Name() == "int64" {
 | 
			
		||||
		valf = float64(val.(int64))
 | 
			
		||||
	}
 | 
			
		||||
	return valf
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
module phyer.click/tunas
 | 
			
		||||
 | 
			
		||||
replace (
 | 
			
		||||
	v5sdk_go/config => ./submodules/okex/config
 | 
			
		||||
	v5sdk_go/rest => ./submodules/okex/rest
 | 
			
		||||
	v5sdk_go/utils => ./submodules/okex/utils
 | 
			
		||||
	v5sdk_go/ws => ./submodules/okex/ws
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
go 1.14
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/bitly/go-simplejson v0.5.0
 | 
			
		||||
	github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
 | 
			
		||||
	github.com/go-redis/redis v6.15.9+incompatible
 | 
			
		||||
	github.com/gorilla/websocket v1.5.1 // indirect
 | 
			
		||||
	github.com/kr/pretty v0.3.0 // indirect
 | 
			
		||||
	github.com/onsi/gomega v1.16.0 // indirect
 | 
			
		||||
	v5sdk_go/config v0.0.0-00010101000000-000000000000 // indirect
 | 
			
		||||
	v5sdk_go/rest v0.0.0-00010101000000-000000000000
 | 
			
		||||
	v5sdk_go/utils v0.0.0-00010101000000-000000000000 // indirect
 | 
			
		||||
	v5sdk_go/ws v0.0.0-00010101000000-000000000000
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										146
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,146 @@
 | 
			
		||||
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
 | 
			
		||||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
 | 
			
		||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
 | 
			
		||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
 | 
			
		||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 | 
			
		||||
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
 | 
			
		||||
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
 | 
			
		||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 | 
			
		||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
			
		||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 | 
			
		||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
 | 
			
		||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
 | 
			
		||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
 | 
			
		||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
 | 
			
		||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 | 
			
		||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 | 
			
		||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 | 
			
		||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 | 
			
		||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 | 
			
		||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 | 
			
		||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
			
		||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 | 
			
		||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
			
		||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
 | 
			
		||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
 | 
			
		||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 | 
			
		||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 | 
			
		||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
 | 
			
		||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 | 
			
		||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
			
		||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
			
		||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 | 
			
		||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 | 
			
		||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 | 
			
		||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 | 
			
		||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
 | 
			
		||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
			
		||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
 | 
			
		||||
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
 | 
			
		||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
 | 
			
		||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 | 
			
		||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 | 
			
		||||
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
 | 
			
		||||
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
 | 
			
		||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 | 
			
		||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 | 
			
		||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
			
		||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
 | 
			
		||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
			
		||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 | 
			
		||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 | 
			
		||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
			
		||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 | 
			
		||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 | 
			
		||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 | 
			
		||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 | 
			
		||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 | 
			
		||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
 | 
			
		||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 | 
			
		||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
 | 
			
		||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
			
		||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
			
		||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 | 
			
		||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 | 
			
		||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 | 
			
		||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
			
		||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 | 
			
		||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
 | 
			
		||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 | 
			
		||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 | 
			
		||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 | 
			
		||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 | 
			
		||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
 | 
			
		||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
 | 
			
		||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
 | 
			
		||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 | 
			
		||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 | 
			
		||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
 | 
			
		||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 | 
			
		||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 | 
			
		||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 | 
			
		||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 | 
			
		||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 | 
			
		||||
							
								
								
									
										123
									
								
								jsonResp/balanceAndPositions.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								jsonResp/balanceAndPositions.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,123 @@
 | 
			
		||||
{
 | 
			
		||||
	"arg": {
 | 
			
		||||
		"channel": "balance_and_position"
 | 
			
		||||
	},
 | 
			
		||||
	"data": [{
 | 
			
		||||
		"balData": [{
 | 
			
		||||
			"cashBal": "0.000000009594",
 | 
			
		||||
			"ccy": "BTC",
 | 
			
		||||
			"uTime": "1631116773307"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "0.89910025",
 | 
			
		||||
			"ccy": "ETH",
 | 
			
		||||
			"uTime": "1631025341572"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "79586.878594366",
 | 
			
		||||
			"ccy": "TOPC",
 | 
			
		||||
			"uTime": "1629961501428"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "0.0000001816",
 | 
			
		||||
			"ccy": "LUNA",
 | 
			
		||||
			"uTime": "1630861153696"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "0.0000001186",
 | 
			
		||||
			"ccy": "MASK",
 | 
			
		||||
			"uTime": "1631028059063"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "0.0000149286384406",
 | 
			
		||||
			"ccy": "USDT",
 | 
			
		||||
			"uTime": "1631286518384"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "0.0000155492",
 | 
			
		||||
			"ccy": "NU",
 | 
			
		||||
			"uTime": "1630951420333"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "14.654412074",
 | 
			
		||||
			"ccy": "ZEC",
 | 
			
		||||
			"uTime": "1631025233669"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "81.537378431",
 | 
			
		||||
			"ccy": "YGG",
 | 
			
		||||
			"uTime": "1631117745754"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "0.0000000464",
 | 
			
		||||
			"ccy": "PICKLE",
 | 
			
		||||
			"uTime": "1630684891608"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "87198.5544112",
 | 
			
		||||
			"ccy": "WXT",
 | 
			
		||||
			"uTime": "1629907247424"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "0.0000316",
 | 
			
		||||
			"ccy": "EC",
 | 
			
		||||
			"uTime": "1630847969947"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "149.6670084176",
 | 
			
		||||
			"ccy": "EFI",
 | 
			
		||||
			"uTime": "1630401172180"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "0.3634",
 | 
			
		||||
			"ccy": "BABYDOGE",
 | 
			
		||||
			"uTime": "1629900682276"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "0.000030236",
 | 
			
		||||
			"ccy": "AE",
 | 
			
		||||
			"uTime": "1630688579353"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "0.4276632956",
 | 
			
		||||
			"ccy": "YFII",
 | 
			
		||||
			"uTime": "1631175274947"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "2.9723385262",
 | 
			
		||||
			"ccy": "USDC",
 | 
			
		||||
			"uTime": "1631116798568"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "0.0000006224",
 | 
			
		||||
			"ccy": "DOGE",
 | 
			
		||||
			"uTime": "1630868728486"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "0.037677167462",
 | 
			
		||||
			"ccy": "USDK",
 | 
			
		||||
			"uTime": "1629992951524"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "0.00000046",
 | 
			
		||||
			"ccy": "AERGO",
 | 
			
		||||
			"uTime": "1630855658607"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "0.0000331484",
 | 
			
		||||
			"ccy": "REVV",
 | 
			
		||||
			"uTime": "1630254401576"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "23.644622275",
 | 
			
		||||
			"ccy": "SOL",
 | 
			
		||||
			"uTime": "1631286518384"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "121422.162699056",
 | 
			
		||||
			"ccy": "VIB",
 | 
			
		||||
			"uTime": "1631027980427"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "0.0000004088",
 | 
			
		||||
			"ccy": "XCH",
 | 
			
		||||
			"uTime": "1630859983222"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "0.0000587688",
 | 
			
		||||
			"ccy": "CVC",
 | 
			
		||||
			"uTime": "1630861363361"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "0.0000368",
 | 
			
		||||
			"ccy": "CNTM",
 | 
			
		||||
			"uTime": "1630993584362"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "0.0000007918",
 | 
			
		||||
			"ccy": "AVAX",
 | 
			
		||||
			"uTime": "1631164302027"
 | 
			
		||||
		}, {
 | 
			
		||||
			"cashBal": "0.0000007332",
 | 
			
		||||
			"ccy": "FIL",
 | 
			
		||||
			"uTime": "1630859413545"
 | 
			
		||||
		}],
 | 
			
		||||
		"eventType": "snapshot",
 | 
			
		||||
		"pTime": "1631593669419",
 | 
			
		||||
		"posData": []
 | 
			
		||||
	}]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								jsonResp/order.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								jsonResp/order.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
			
		||||
{
 | 
			
		||||
    "arg": {
 | 
			
		||||
        "channel": "orders",
 | 
			
		||||
        "instType": "FUTURES",
 | 
			
		||||
        "instId": "BTC-USD-200329"
 | 
			
		||||
    },
 | 
			
		||||
    "data": [{
 | 
			
		||||
        "instType": "FUTURES",
 | 
			
		||||
        "instId": "BTC-USD-200329",
 | 
			
		||||
        "ordId": "312269865356374016",
 | 
			
		||||
        "clOrdId": "b1",
 | 
			
		||||
        "tag": "",
 | 
			
		||||
        "px": "999",
 | 
			
		||||
        "sz": "333",
 | 
			
		||||
        "notionalUsd": "",
 | 
			
		||||
        "ordType": "limit",
 | 
			
		||||
        "side": "buy",
 | 
			
		||||
        "posSide": "long",
 | 
			
		||||
        "tdMode": "cross",
 | 
			
		||||
        "tgtCcy": "",
 | 
			
		||||
        "fillSz": "0",
 | 
			
		||||
        "fillPx": "long",
 | 
			
		||||
        "tradeId": "0",
 | 
			
		||||
        "accFillSz": "323",
 | 
			
		||||
        "fillNotionalUsd": "",
 | 
			
		||||
        "fillTime": "0",
 | 
			
		||||
        "fillFee": "0.0001",
 | 
			
		||||
        "fillFeeCcy": "BTC",
 | 
			
		||||
        "execType": "T",
 | 
			
		||||
        "source": "",
 | 
			
		||||
        "state": "canceled",
 | 
			
		||||
        "avgPx": "0",
 | 
			
		||||
        "lever": "20",
 | 
			
		||||
        "tpTriggerPx": "0",
 | 
			
		||||
        "tpTriggerPxType": "last",
 | 
			
		||||
        "tpOrdPx": "20",
 | 
			
		||||
        "slTriggerPx": "0",
 | 
			
		||||
        "slTriggerPxType": "last",
 | 
			
		||||
        "slOrdPx": "20",
 | 
			
		||||
        "feeCcy": "",
 | 
			
		||||
        "fee": "",
 | 
			
		||||
        "rebateCcy": "",
 | 
			
		||||
        "rebate": "",
 | 
			
		||||
        "tgtCcy":"",
 | 
			
		||||
        "pnl": "",
 | 
			
		||||
        "category": "",
 | 
			
		||||
        "uTime": "1597026383085",
 | 
			
		||||
        "cTime": "1597026383085",
 | 
			
		||||
        "reqId": "",
 | 
			
		||||
        "amendResult": "",
 | 
			
		||||
        "code": "0",
 | 
			
		||||
        "msg": ""
 | 
			
		||||
    }]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										55
									
								
								jsonResp/order2.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								jsonResp/order2.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,55 @@
 | 
			
		||||
{
 | 
			
		||||
	"arg": {
 | 
			
		||||
		"channel": "orders",
 | 
			
		||||
		"instType": "ANY",
 | 
			
		||||
		"uid": "169408628405739520"
 | 
			
		||||
	},
 | 
			
		||||
	"data": [{
 | 
			
		||||
		"accFillSz": "0",
 | 
			
		||||
		"amendResult": "0",
 | 
			
		||||
		"avgPx": "0",
 | 
			
		||||
		"cTime": "1644507606496",
 | 
			
		||||
		"category": "normal",
 | 
			
		||||
		"ccy": "",
 | 
			
		||||
		"clOrdId": "",
 | 
			
		||||
		"code": "0",
 | 
			
		||||
		"execType": "",
 | 
			
		||||
		"fee": "0",
 | 
			
		||||
		"feeCcy": "USDT",
 | 
			
		||||
		"fillFee": "0",
 | 
			
		||||
		"fillFeeCcy": "",
 | 
			
		||||
		"fillNotionalUsd": "",
 | 
			
		||||
		"fillPx": "",
 | 
			
		||||
		"fillSz": "0",
 | 
			
		||||
		"fillTime": "",
 | 
			
		||||
		"instId": "ACA-USDT",
 | 
			
		||||
		"instType": "SPOT",
 | 
			
		||||
		"lever": "0",
 | 
			
		||||
		"msg": "",
 | 
			
		||||
		"notionalUsd": "1760.8526031423098",
 | 
			
		||||
		"ordId": "412029997271109634",
 | 
			
		||||
		"ordType": "limit",
 | 
			
		||||
		"pnl": "0",
 | 
			
		||||
		"posSide": "",
 | 
			
		||||
		"px": "1.98",
 | 
			
		||||
		"rebate": "0",
 | 
			
		||||
		"rebateCcy": "ACA",
 | 
			
		||||
		"reduceOnly": "false",
 | 
			
		||||
		"reqId": "",
 | 
			
		||||
		"side": "sell",
 | 
			
		||||
		"slOrdPx": "",
 | 
			
		||||
		"slTriggerPx": "",
 | 
			
		||||
		"slTriggerPxType": "last",
 | 
			
		||||
		"source": "",
 | 
			
		||||
		"state": "live",
 | 
			
		||||
		"sz": "888.644127",
 | 
			
		||||
		"tag": "",
 | 
			
		||||
		"tdMode": "cash",
 | 
			
		||||
		"tgtCcy": "",
 | 
			
		||||
		"tpOrdPx": "",
 | 
			
		||||
		"tpTriggerPx": "",
 | 
			
		||||
		"tpTriggerPxType": "last",
 | 
			
		||||
		"tradeId": "",
 | 
			
		||||
		"uTime": "1644539201208"
 | 
			
		||||
	}]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2248
									
								
								jsonResp/position.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2248
									
								
								jsonResp/position.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										639
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										639
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,639 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"math/rand"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
	"v5sdk_go/rest"
 | 
			
		||||
	"v5sdk_go/ws"
 | 
			
		||||
	"v5sdk_go/ws/wImpl"
 | 
			
		||||
 | 
			
		||||
	simple "github.com/bitly/go-simplejson"
 | 
			
		||||
	"github.com/go-redis/redis"
 | 
			
		||||
	"phyer.click/tunas/core"
 | 
			
		||||
	"phyer.click/tunas/private"
 | 
			
		||||
	"phyer.click/tunas/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	// //fmt.Println("inited")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 通过rest接口,获取所有ticker信息,存入redis的stream和成交量排行榜
 | 
			
		||||
func RestTicker(cr *core.Core, dura time.Duration) {
 | 
			
		||||
 | 
			
		||||
	rsp := rest.RESTAPIResult{}
 | 
			
		||||
	js := simple.Json{}
 | 
			
		||||
	itemList := []interface{}{}
 | 
			
		||||
	fmt.Println("getAllTickerInfo err: ")
 | 
			
		||||
	rsp1, err := cr.GetAllTickerInfo()
 | 
			
		||||
	rsp = *rsp1
 | 
			
		||||
	js1, err := simple.NewJson([]byte(rsp.Body))
 | 
			
		||||
	js = *js1
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("restTicker err: ", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if len(rsp.Body) == 0 {
 | 
			
		||||
		fmt.Println("rsp body is null")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	itemList = js.Get("data").MustArray()
 | 
			
		||||
	// maxTickers是重点关注的topScore的coins的数量
 | 
			
		||||
	length, _ := cr.Cfg.Config.Get("threads").Get("maxTickers").Int()
 | 
			
		||||
	fmt.Println("itemList length:", len(itemList))
 | 
			
		||||
	if len(itemList) < length {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// 关注多少个币,在这里设置, 只需要5个币
 | 
			
		||||
	allTicker := cr.GetScoreList(5)
 | 
			
		||||
	redisCli := cr.RedisCli
 | 
			
		||||
	// 全部币种列表,跟特定币种列表进行比对,匹配后push到redis
 | 
			
		||||
	for _, v := range itemList {
 | 
			
		||||
		tir := core.TickerInfoResp{}
 | 
			
		||||
		bs, err := json.Marshal(v)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("restTicker marshal err: ", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		err = json.Unmarshal(bs, &tir)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("restTicker unmarshal err: ", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		ti := tir.Convert()
 | 
			
		||||
		isUsdt := strings.Contains(ti.InstId, "-USDT")
 | 
			
		||||
		if !isUsdt {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if ti.InstType != "SPOT" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		// 把单个ticker信息小时交易量存到交易量排行榜
 | 
			
		||||
		// fmt.Println("ticker item: ", item)
 | 
			
		||||
		// 不需要排行榜了
 | 
			
		||||
		// _, err = redisCli.ZAdd("tickersVol|sortedSet", redis.Z{ti.VolCcy24h, ti.InstId}).Result()
 | 
			
		||||
		// if err != nil {
 | 
			
		||||
		// 	fmt.Println("restTicker redis err: ", err)
 | 
			
		||||
		// }
 | 
			
		||||
 | 
			
		||||
		ab, _ := json.Marshal(ti)
 | 
			
		||||
		suffix := ""
 | 
			
		||||
		env := os.Getenv("GO_ENV")
 | 
			
		||||
		if env == "demoEnv" {
 | 
			
		||||
			suffix = "-demoEnv"
 | 
			
		||||
		}
 | 
			
		||||
		for _, v := range allTicker {
 | 
			
		||||
			if v == ti.InstId {
 | 
			
		||||
				redisCli.Publish(core.TICKERINFO_PUBLISH+suffix, string(ab)).Result()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 私有订阅: 订阅订单频道
 | 
			
		||||
func wsPriv(core *core.Core) error {
 | 
			
		||||
	cfg := core.Cfg
 | 
			
		||||
	url, _ := cfg.Config.Get("connect").Get("wsPrivateBaseUrl").String()
 | 
			
		||||
	priCli, err := ws.NewWsClient("wss://" + url)
 | 
			
		||||
	err = priCli.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	priCli.SetDailTimeout(time.Second * 10)
 | 
			
		||||
	defer func() {
 | 
			
		||||
		priCli.Stop()
 | 
			
		||||
	}()
 | 
			
		||||
	var res bool
 | 
			
		||||
	key, _ := core.Cfg.Config.Get("credentialReadOnly").Get("okAccessKey").String()
 | 
			
		||||
	secure, _ := core.Cfg.Config.Get("credentialReadOnly").Get("secretKey").String()
 | 
			
		||||
	pass, _ := core.Cfg.Config.Get("credentialReadOnly").Get("okAccessPassphrase").String()
 | 
			
		||||
	// TODO 这里订阅收听订单频道结果,但是ws请求不可靠,这个所谓冗余机制存在,有比没有强,restPost函数 是保底方案
 | 
			
		||||
	priCli.AddBookMsgHook(func(ts time.Time, data wImpl.MsgData) error {
 | 
			
		||||
		// 添加你的方法
 | 
			
		||||
		fmt.Println("这是自定义AddBookMsgHook")
 | 
			
		||||
		bj, _ := json.Marshal(data)
 | 
			
		||||
		fmt.Println("当前数据是", string(bj))
 | 
			
		||||
		resp := private.OrderResp{}
 | 
			
		||||
		// TODO FIXME 这里默认所有的数据都是OrderResp类型,但是ws请求有其他类型的,这个地方将来肯定得改
 | 
			
		||||
		err := json.Unmarshal(bj, &resp)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("Canvert MsgData to OrderResp err:", err)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Println("order resp: ", resp)
 | 
			
		||||
		list, _ := resp.Convert()
 | 
			
		||||
		fmt.Println("order list: ", list)
 | 
			
		||||
		go func() {
 | 
			
		||||
			for _, v := range list {
 | 
			
		||||
				fmt.Println("order orderV:", v)
 | 
			
		||||
				core.OrderChan <- v
 | 
			
		||||
			}
 | 
			
		||||
		}()
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	fmt.Println("key, secure, pass:", key, secure, pass)
 | 
			
		||||
	res, _, err = priCli.Login(key, secure, pass)
 | 
			
		||||
 | 
			
		||||
	if res {
 | 
			
		||||
		fmt.Println("私有订阅登录成功!")
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("私有订阅登录失败!", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instType"] = ws.ANY
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
	// 订阅订单频道动作执行
 | 
			
		||||
	res, msg, err := priCli.PrivBookOrder("subscribe", args)
 | 
			
		||||
	bj, _ := json.Marshal(msg)
 | 
			
		||||
	fmt.Println("PrivBookOrder res:", res, " msg:", string(bj), " err:", err)
 | 
			
		||||
	for {
 | 
			
		||||
		fmt.Println(utils.GetFuncName(), url+" is in subscribing")
 | 
			
		||||
		time.Sleep(10 * time.Minute)
 | 
			
		||||
		priCli.Stop()
 | 
			
		||||
		wsPriv(core)
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 统一受理发起rest请求的请求
 | 
			
		||||
func LoopSaveCandle(cr *core.Core) {
 | 
			
		||||
	for {
 | 
			
		||||
		ary, err := cr.RedisCli.BRPop(0, "restQueue").Result()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("brpop err:", err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		restQ := core.RestQueue{}
 | 
			
		||||
		json.Unmarshal([]byte(ary[1]), &restQ)
 | 
			
		||||
		// fmt.Println("before: ", restQ.InstId)
 | 
			
		||||
		// before:  USDT|position|key
 | 
			
		||||
		ary1 := strings.Split(restQ.InstId, "|")
 | 
			
		||||
		if ary1[0] == "USDT" {
 | 
			
		||||
			// "USDT-USDT" 这个没有意义,忽略
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if len(ary1) > 1 && ary1[1] == "position" {
 | 
			
		||||
			restQ.InstId = ary1[0] + "-USDT"
 | 
			
		||||
		}
 | 
			
		||||
		// fmt.Println("after: ", restQ.InstId)
 | 
			
		||||
		// after:  restQueue-USDT
 | 
			
		||||
		go func() {
 | 
			
		||||
			restQ.Show(cr)
 | 
			
		||||
			restQ.Save(cr)
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 这个函数被废弃了, 根据现有逻辑,redisCli.Scan(cursor, "*"+pattern+"*", 2000) 得不到任何内容
 | 
			
		||||
func LoopRestQ(cr *core.Core) {
 | 
			
		||||
	redisCli := cr.RedisCli
 | 
			
		||||
	cursor := uint64(0)
 | 
			
		||||
	n := 0
 | 
			
		||||
	// allTs := []int64{}
 | 
			
		||||
	rstr := []string{}
 | 
			
		||||
	pattern := "restQueue"
 | 
			
		||||
	fmt.Println("LoopRestQ start")
 | 
			
		||||
	for {
 | 
			
		||||
		var err error
 | 
			
		||||
		rstr, cursor, err = redisCli.Scan(cursor, "*"+pattern+"*", 2000).Result()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			panic(err)
 | 
			
		||||
		}
 | 
			
		||||
		n += len(rstr)
 | 
			
		||||
		if n == 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if n > 200000 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if n > 0 {
 | 
			
		||||
			fmt.Println("LoopRestQ rstr: ", rstr)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// fmt.Println("LoopRestQ rstr: ", rstr)
 | 
			
		||||
	fmt.Println("LoopRestQ rstr len: ", len(rstr))
 | 
			
		||||
	for _, keyN := range rstr {
 | 
			
		||||
		val, err := redisCli.Get(keyN).Result()
 | 
			
		||||
		fmt.Println("LoopRestQ val:", val)
 | 
			
		||||
		restQ := core.RestQueue{}
 | 
			
		||||
		if err != nil || len(val) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		err = json.Unmarshal([]byte(val), &restQ)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			fmt.Println("RestQueue Unmarshal err: ", err)
 | 
			
		||||
		}
 | 
			
		||||
		// res, err := redisCli.LPush("restQueue", val).Result()
 | 
			
		||||
		fmt.Println("restQueue will LPush: ", val)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			redisCli.Del(keyN)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	time.Sleep(10 * time.Second)
 | 
			
		||||
	LoopRestQ(cr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func LoopSubscribe(cr *core.Core) {
 | 
			
		||||
	redisCli := cr.RedisCli
 | 
			
		||||
	suffix := ""
 | 
			
		||||
	if cr.Env == "demoEnv" {
 | 
			
		||||
		suffix = "-demoEnv"
 | 
			
		||||
	}
 | 
			
		||||
	pubsub := redisCli.Subscribe(core.ALLCANDLES_INNER_PUBLISH + suffix)
 | 
			
		||||
	_, err := pubsub.Receive()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	// 用管道来接收消息
 | 
			
		||||
	ch := pubsub.Channel()
 | 
			
		||||
	// 处理消息
 | 
			
		||||
 | 
			
		||||
	for msg := range ch {
 | 
			
		||||
		cd := core.Candle{}
 | 
			
		||||
		json.Unmarshal([]byte(msg.Payload), &cd)
 | 
			
		||||
		fmt.Println("msg.PayLoad:", msg.Payload, "candle:", cd)
 | 
			
		||||
		go func() {
 | 
			
		||||
			cr.WsSubscribe(&cd)
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 订阅并执行sardine端传来的订单相关的动作
 | 
			
		||||
func LoopSubscribeSubAction(cr *core.Core) {
 | 
			
		||||
	redisCli := cr.RedisCli
 | 
			
		||||
	suffix := ""
 | 
			
		||||
	if cr.Env == "demoEnv" {
 | 
			
		||||
		suffix = "-demoEnv"
 | 
			
		||||
	}
 | 
			
		||||
	// TODO FIXME cli2
 | 
			
		||||
	prisub := redisCli.Subscribe(core.SUBACTIONS_PUBLISH + suffix)
 | 
			
		||||
	_, err := prisub.Receive()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	// 用管道来接收消息
 | 
			
		||||
	ch := prisub.Channel()
 | 
			
		||||
	// 处理消息
 | 
			
		||||
 | 
			
		||||
	for msg := range ch {
 | 
			
		||||
		action := core.SubAction{}
 | 
			
		||||
		json.Unmarshal([]byte(msg.Payload), &action)
 | 
			
		||||
		fmt.Println("actionMsg.PayLoad:", msg.Payload, "action:", action)
 | 
			
		||||
		go func() {
 | 
			
		||||
			cr.DispatchSubAction(&action)
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// period: 每个循环开始的时间点,单位:秒
 | 
			
		||||
// delay:延时多少秒后去取此值, 单位:秒
 | 
			
		||||
// mdura:多少个分钟之内,遍历完获取到的goins列表, 单位:秒
 | 
			
		||||
// onceCount:每次获取这个coin几个当前周期的candle数据
 | 
			
		||||
// range: 随机的范围,从0开始到range个周期,作为查询的after值,也就是随机n个周期,去取之前的记录,对于2D,5D等数据,可以用来补全数据, range值越大,随机散点的范围越大, 越失焦
 | 
			
		||||
 | 
			
		||||
func LoopAllCoinsList(period int64, delay int64, mdura int, barPeriod string, onceCount int, rge int) {
 | 
			
		||||
	cr := core.Core{}
 | 
			
		||||
	cr.Init()
 | 
			
		||||
	allScoreChan := make(chan []string)
 | 
			
		||||
	// fmt.Println("allCoins1")
 | 
			
		||||
	per1 := 1 * time.Minute
 | 
			
		||||
	ticker := time.NewTicker(per1)
 | 
			
		||||
	go func() {
 | 
			
		||||
		for {
 | 
			
		||||
			tsi := time.Now().Unix()
 | 
			
		||||
			// fmt.Println("tsi, period, delay, tsi%(period): ", tsi, period, delay, tsi%(period))
 | 
			
		||||
			if tsi%(period) != delay {
 | 
			
		||||
				time.Sleep(1 * time.Second)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			select {
 | 
			
		||||
			case <-ticker.C:
 | 
			
		||||
				go func() {
 | 
			
		||||
					// -1 是获取全部coin列表
 | 
			
		||||
					list := cr.GetScoreList(5)
 | 
			
		||||
					// fmt.Println("allCoins3", list)
 | 
			
		||||
					allScoreChan <- list
 | 
			
		||||
				}()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	for {
 | 
			
		||||
		allScore, _ := <-allScoreChan
 | 
			
		||||
		// fmt.Println("allCoins allScore", allScore)
 | 
			
		||||
		if len(allScore) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		utils.TickerWrapper(time.Duration(mdura)*time.Second, allScore, func(i int, ary []string) error {
 | 
			
		||||
			nw := time.Now()
 | 
			
		||||
			rand.Seed(nw.UnixNano())
 | 
			
		||||
			ct := rand.Intn(rge)
 | 
			
		||||
			minutes := cr.PeriodToMinutes(barPeriod)
 | 
			
		||||
			tmi := nw.UnixMilli()
 | 
			
		||||
			tmi = tmi - tmi%60000
 | 
			
		||||
			tmi = tmi - (int64(ct) * minutes * 60000)
 | 
			
		||||
			fmt.Println("instId: ", ary[i])
 | 
			
		||||
			restQ := core.RestQueue{
 | 
			
		||||
				InstId: ary[i],
 | 
			
		||||
				Bar:    barPeriod,
 | 
			
		||||
 | 
			
		||||
				WithWs: false,
 | 
			
		||||
				After:  tmi,
 | 
			
		||||
			}
 | 
			
		||||
			js, err := json.Marshal(restQ)
 | 
			
		||||
			// fmt.Println("allCoins lpush js:", string(js))
 | 
			
		||||
			cr.RedisCli.LPush("restQueue", js)
 | 
			
		||||
			return err
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func LoopCheckMyFavorList(core *core.Core) {
 | 
			
		||||
	maxTickers, _ := core.Cfg.Config.Get("threads").Get("maxTickers").Int()
 | 
			
		||||
	myFavorChan := make(chan []string)
 | 
			
		||||
	//协程:动态维护topScore
 | 
			
		||||
	go func(mx int) {
 | 
			
		||||
		myFavor := []string{}
 | 
			
		||||
		for {
 | 
			
		||||
			myFavor = core.GetMyFavorList()
 | 
			
		||||
			if len(myFavor) < mx {
 | 
			
		||||
				fmt.Println("topScore 长度不符合预期")
 | 
			
		||||
				break
 | 
			
		||||
			} else {
 | 
			
		||||
				fmt.Println("topScore 长度符合预期")
 | 
			
		||||
			}
 | 
			
		||||
			myFavorChan <- myFavor
 | 
			
		||||
			time.Sleep(12 * time.Minute)
 | 
			
		||||
		}
 | 
			
		||||
	}(maxTickers)
 | 
			
		||||
	//协程:循环执行rest请求candle
 | 
			
		||||
 | 
			
		||||
	for {
 | 
			
		||||
		myFavor, _ := <-myFavorChan
 | 
			
		||||
		go func() {
 | 
			
		||||
			loop2(core, myFavor, maxTickers)
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func loop2(core *core.Core, topScore []string, maxTickers int) {
 | 
			
		||||
	restPeriod, _ := core.Cfg.Config.Get("threads").Get("restPeriod").Int()
 | 
			
		||||
	maxCandles, _ := core.Cfg.Config.Get("threads").Get("maxCandles").Int()
 | 
			
		||||
	if len(topScore) < maxTickers {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// fmt.Println("loop1 ", 12*time.Minute, " topScore2: ", topScore)
 | 
			
		||||
	// fmt.Println("topScore: ", topScore, len(topScore), mx, ok)
 | 
			
		||||
	//每隔Period1,重新发起一次rest请求的大循环。里面还有60秒一次的小循环. 作为ws请求的备份(保底)机制,实效性差了一点,但是稳定性高于ws
 | 
			
		||||
	dura := time.Duration(restPeriod) * time.Second
 | 
			
		||||
 | 
			
		||||
	mdura := dura/time.Duration(len(topScore)) - 20*time.Millisecond
 | 
			
		||||
	ticker := time.NewTicker(mdura)
 | 
			
		||||
	done := make(chan bool)
 | 
			
		||||
	idx := 0
 | 
			
		||||
	go func(i int) {
 | 
			
		||||
		for {
 | 
			
		||||
			select {
 | 
			
		||||
			case <-ticker.C:
 | 
			
		||||
				if i >= 4 { //: 12分钟 / 3分钟 = 4
 | 
			
		||||
					done <- true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				for {
 | 
			
		||||
					//内层循环3分钟一圈,3分钟内遍历完topScore限定的candle列表, 12分钟能够跑4圈
 | 
			
		||||
					if i >= 4 { //: 12分钟 / 3分钟 = 4
 | 
			
		||||
						done <- true
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
					go func() {
 | 
			
		||||
						// fmt.Println("loop2 :", "dura:", dura, "i:", i)
 | 
			
		||||
						// core.InVokeCandle(topScore, per1, maxCandles)
 | 
			
		||||
						mdura := dura / time.Duration(len(topScore)+1)
 | 
			
		||||
						for k, v := range topScore {
 | 
			
		||||
							go func(k int, v string) {
 | 
			
		||||
								time.Sleep(mdura*time.Duration(k) - 10*time.Millisecond)
 | 
			
		||||
								core.GetCandlesWithRest(v, k, mdura, maxCandles)
 | 
			
		||||
							}(k, v)
 | 
			
		||||
						}
 | 
			
		||||
						i++
 | 
			
		||||
					}()
 | 
			
		||||
					time.Sleep(dura)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}(idx)
 | 
			
		||||
	time.Sleep(dura - 100*time.Millisecond)
 | 
			
		||||
	done <- true
 | 
			
		||||
	ticker.Stop()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 订阅公共频道
 | 
			
		||||
func wsPub(core *core.Core) {
 | 
			
		||||
	wsCli, _ := core.GetWsCli()
 | 
			
		||||
	err := wsCli.Start()
 | 
			
		||||
	// 创建ws客户端
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		//fmt.Println("ws client err", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 设置连接超时
 | 
			
		||||
 | 
			
		||||
	wsCli.SetDailTimeout(time.Second * 20)
 | 
			
		||||
 | 
			
		||||
	defer wsCli.Stop()
 | 
			
		||||
	// 订阅产品频道
 | 
			
		||||
	// 在这里初始化instrument列表
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instType"] = ws.SPOT
 | 
			
		||||
	//arg["instType"] = OPTION
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	// start := time.Now()
 | 
			
		||||
	//订阅
 | 
			
		||||
 | 
			
		||||
	//设置订阅事件的event handler
 | 
			
		||||
	wsCli.AddBookMsgHook(core.PubMsgDispatcher)
 | 
			
		||||
	res, _, err := wsCli.PubInstruemnts(ws.OP_SUBSCRIBE, args)
 | 
			
		||||
	//fmt.Println("args:", args)
 | 
			
		||||
	if res {
 | 
			
		||||
		// usedTime := time.Since(start)
 | 
			
		||||
		//fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		//fmt.Println("订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
	core.LoopInstrumentList()
 | 
			
		||||
 | 
			
		||||
	// start = time.Now()
 | 
			
		||||
	// res, _, err = r.PubInstruemnts(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	// if res {
 | 
			
		||||
	// usedTime := time.Since(start)
 | 
			
		||||
	// //fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	// } else {
 | 
			
		||||
	// //fmt.Println("取消订阅失败!", err)
 | 
			
		||||
	// }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 处理 ws 订单请求相关订阅回调
 | 
			
		||||
func subscribeOrder(cr *core.Core) {
 | 
			
		||||
	fmt.Println("subscribeOrder order:")
 | 
			
		||||
	for {
 | 
			
		||||
		order := <-cr.OrderChan
 | 
			
		||||
		go func() {
 | 
			
		||||
			bj, _ := json.Marshal(order)
 | 
			
		||||
			fmt.Println("process order:", string(bj))
 | 
			
		||||
			cr.ProcessOrder(order)
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
func main() {
 | 
			
		||||
	cr := core.Core{}
 | 
			
		||||
	cr.Init()
 | 
			
		||||
	cr.ShowSysTime()
 | 
			
		||||
	// 从rest接口获取的ticker记录种的交量计入排行榜,指定周期刷新一次
 | 
			
		||||
	go func() {
 | 
			
		||||
		per1 := 1 * time.Minute
 | 
			
		||||
		RestTicker(&cr, per1)
 | 
			
		||||
		limiter := time.Tick(per1)
 | 
			
		||||
		for {
 | 
			
		||||
			<-limiter
 | 
			
		||||
			go func() {
 | 
			
		||||
				RestTicker(&cr, per1)
 | 
			
		||||
			}()
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	//全员1分钟candle & maX
 | 
			
		||||
	// period: 每个循环开始的时间点,单位:秒
 | 
			
		||||
	// delay:延时多少秒后去取此值
 | 
			
		||||
	// mdura:多少秒之内,遍历完获取到的goins列表
 | 
			
		||||
	// onceCount:每次获取这个coin几个当前周期的candle数据
 | 
			
		||||
	// 全员3m
 | 
			
		||||
	// go func() {
 | 
			
		||||
	// fmt.Println("LoopAllCoinsList1")
 | 
			
		||||
	// LoopAllCoinsList(180, 0, 180, "3m", 5, 6)
 | 
			
		||||
	// }()
 | 
			
		||||
 | 
			
		||||
	// 全员15m candle
 | 
			
		||||
	//	go func() {
 | 
			
		||||
	//		fmt.Println("LoopAllCoinsList2")
 | 
			
		||||
	//		LoopAllCoinsList(380, 90, 380, "15m", 4, 7)
 | 
			
		||||
	//	}()
 | 
			
		||||
	// 全员30m candle
 | 
			
		||||
	// go func() {
 | 
			
		||||
	// fmt.Println("LoopAllCoinsList2")
 | 
			
		||||
	// LoopAllCoinsList(510, 90, 500, "30m", 5, 8)
 | 
			
		||||
	// }()
 | 
			
		||||
	// 全员1H candle
 | 
			
		||||
	//	go func() {
 | 
			
		||||
	//		fmt.Println("LoopAllCoinsList2")
 | 
			
		||||
	//		LoopAllCoinsList(770, 0, 760, "1H", 9, 12)
 | 
			
		||||
	//	}()
 | 
			
		||||
	// 全员2H candle
 | 
			
		||||
	go func() {
 | 
			
		||||
		fmt.Println("LoopAllCoinsList2")
 | 
			
		||||
		LoopAllCoinsList(820, 0, 820, "2H", 12, 15)
 | 
			
		||||
	}()
 | 
			
		||||
	// 全员4小时candle
 | 
			
		||||
	//	go func() {
 | 
			
		||||
	//		fmt.Println("LoopAllCoinsList1")
 | 
			
		||||
	//		LoopAllCoinsList(1280, 150, 1280, "4H", 15, 19)
 | 
			
		||||
	//	}()
 | 
			
		||||
	// 全员6小时candle
 | 
			
		||||
	//go func() {
 | 
			
		||||
	//	fmt.Println("LoopAllCoinsList1")
 | 
			
		||||
	//	LoopAllCoinsList(1440, 180, 1440, "6H", 17, 21)
 | 
			
		||||
	//}()
 | 
			
		||||
	// 全员12小时candle
 | 
			
		||||
	go func() {
 | 
			
		||||
		fmt.Println("LoopAllCoinsList1")
 | 
			
		||||
		LoopAllCoinsList(1680, 180, 1680, "12H", 19, 23)
 | 
			
		||||
	}()
 | 
			
		||||
	// 全员1Day candle & maX
 | 
			
		||||
	//go func() {
 | 
			
		||||
	//		fmt.Println("LoopAllCoinsList1")
 | 
			
		||||
	//		LoopAllCoinsList(1920, 4, 1920, "1D", 25, 30)
 | 
			
		||||
	//	}()
 | 
			
		||||
	// 全员2Day candle & maX
 | 
			
		||||
	go func() {
 | 
			
		||||
		fmt.Println("LoopAllCoinsList1")
 | 
			
		||||
		LoopAllCoinsList(3840, 220, 3840, "2D", 26, 31)
 | 
			
		||||
	}()
 | 
			
		||||
	// 全员5Day candle & maX
 | 
			
		||||
	go func() {
 | 
			
		||||
		fmt.Println("LoopAllCoinsList1")
 | 
			
		||||
		LoopAllCoinsList(6400, 4, 6400, "5D", 28, 35)
 | 
			
		||||
	}()
 | 
			
		||||
	// 循环检查tickersVol|sortedSet,并执行订阅candles
 | 
			
		||||
	go func() {
 | 
			
		||||
		LoopCheckMyFavorList(&cr)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		LoopSaveCandle(&cr)
 | 
			
		||||
	}()
 | 
			
		||||
	go func() {
 | 
			
		||||
		LoopSubscribe(&cr)
 | 
			
		||||
	}()
 | 
			
		||||
	// 订阅下游sardine发过来的要执行的动作
 | 
			
		||||
	go func() {
 | 
			
		||||
		LoopSubscribeSubAction(&cr)
 | 
			
		||||
	}()
 | 
			
		||||
	// 废弃
 | 
			
		||||
	// go func() {
 | 
			
		||||
	// 	LoopRestQ(&cr)
 | 
			
		||||
	// }()
 | 
			
		||||
 | 
			
		||||
	//-----------
 | 
			
		||||
	//私有部分
 | 
			
		||||
	go func() {
 | 
			
		||||
		core.LoopLivingOrders(&cr, 1*time.Minute)
 | 
			
		||||
	}()
 | 
			
		||||
	go func() {
 | 
			
		||||
		core.LoopBalances(&cr, 1*time.Minute)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// 公共订阅
 | 
			
		||||
	// wsPub(&cr)
 | 
			
		||||
 | 
			
		||||
	// 停止私有订阅
 | 
			
		||||
 | 
			
		||||
	//	go func() {
 | 
			
		||||
	//		//正常情况下 wsPrive不会主动退出,如果不慎退出了,自动重新运行
 | 
			
		||||
	//		for {
 | 
			
		||||
	//			wsPriv(&cr)
 | 
			
		||||
	//		}
 | 
			
		||||
	//}()
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		subscribeOrder(&cr)
 | 
			
		||||
	}()
 | 
			
		||||
	// gcl := map[string]models.GlobalCoin{}
 | 
			
		||||
	// msgr := cr.Messager{}
 | 
			
		||||
	// msgr.Init()
 | 
			
		||||
	// msgr.Login()
 | 
			
		||||
	// msgr.Alive()
 | 
			
		||||
	// msgr.GlobalSubscribe() // 订阅全局该订阅的公共和私有内容
 | 
			
		||||
	// msgr.Dispatcher(gcl)
 | 
			
		||||
	// msgr.Pop()
 | 
			
		||||
 | 
			
		||||
	// //fmt.Println("listenning ... ")
 | 
			
		||||
	// 这个地方为了让main不退出, 将来可以改成一个http的listener
 | 
			
		||||
	// ip := "0.0.0.0:6066"
 | 
			
		||||
	// if err := http.ListenAndServe(ip, nil); err != nil {
 | 
			
		||||
	// fmt.Printf("start pprof failed on %s\n", ip)
 | 
			
		||||
	// }
 | 
			
		||||
	// 永久阻塞
 | 
			
		||||
	select {}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								models/account.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								models/account.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,44 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
type Account struct {
 | 
			
		||||
	UTime int64 `json:"uTime"` //币种余额信息的更新时间
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// {
 | 
			
		||||
// "uTime": "1597026383085",
 | 
			
		||||
// "totalEq": "41624.32",
 | 
			
		||||
// "isoEq": "3624.32",
 | 
			
		||||
// "adjEq": "41624.32",
 | 
			
		||||
// "ordFroz": "0",
 | 
			
		||||
// "imr": "4162.33",
 | 
			
		||||
// "mmr": "4",
 | 
			
		||||
// "notionalUsd": "",
 | 
			
		||||
// "mgnRatio": "41624.32",
 | 
			
		||||
// "details": [
 | 
			
		||||
// {
 | 
			
		||||
// "availBal": "",
 | 
			
		||||
// "availEq": "1",
 | 
			
		||||
// "ccy": "BTC",
 | 
			
		||||
// "cashBal": "1",
 | 
			
		||||
// "uTime": "1617279471503",
 | 
			
		||||
// "disEq": "50559.01",
 | 
			
		||||
// "eq": "1",
 | 
			
		||||
// "eqUsd": "45078.3790756226851775",
 | 
			
		||||
// "frozenBal": "0",
 | 
			
		||||
// "interest": "0",
 | 
			
		||||
// "isoEq": "0",
 | 
			
		||||
// "liab": "0",
 | 
			
		||||
// "maxLoan": "",
 | 
			
		||||
// "mgnRatio": "",
 | 
			
		||||
// "notionalLever": "0.0022195262185864",
 | 
			
		||||
// "ordFrozen": "0",
 | 
			
		||||
// "upl": "0",
 | 
			
		||||
// "uplLiab": "0",
 | 
			
		||||
// "crossLiab": "0",
 | 
			
		||||
// "isoLiab": "0",
 | 
			
		||||
// "coinUsdPrice": "60000",
 | 
			
		||||
// "stgyEq":"0",
 | 
			
		||||
// "isoUpl":""
 | 
			
		||||
// }
 | 
			
		||||
// ]
 | 
			
		||||
// }
 | 
			
		||||
							
								
								
									
										10
									
								
								models/candle.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								models/candle.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
type Candle struct {
 | 
			
		||||
	InstId string  `json:"instId"` //开始时间
 | 
			
		||||
	Ts     int64   `json:"ts"`     // 最高价格
 | 
			
		||||
	O      float64 `json:"o"`      //最低价格
 | 
			
		||||
	C      string  `json:"c"`      //收盘价格
 | 
			
		||||
	Vol    string  `json:"vol"`    // 交易量,以张为单位
 | 
			
		||||
	VolCcy string  `json:"volCcy"` // 交易量,以币为单位
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								models/globalCoin.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								models/globalCoin.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"phyer.click/tunas/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type GlobalCoin struct {
 | 
			
		||||
	CoinName      string                                `json:"coinName"`
 | 
			
		||||
	Instrument    *Instrument                           `json:"instrument"`
 | 
			
		||||
	Ticker        *Ticker                               `json:"ticker"`
 | 
			
		||||
	CandleMapList map[string](map[string]utils.MyStack) `json:"candleMapList"` // map["BTC"]["oneMintue"]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (coin *GlobalCoin) GetInstrument() *Instrument {
 | 
			
		||||
	return coin.Instrument
 | 
			
		||||
}
 | 
			
		||||
func (coin *GlobalCoin) SetInstrument(instr *Instrument) {
 | 
			
		||||
	coin.Instrument = instr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// gcl := map[string]GlobalGoin{}
 | 
			
		||||
							
								
								
									
										40
									
								
								models/instrument.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								models/instrument.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
type Instrument struct {
 | 
			
		||||
	Alias    string  `json:"alias" mapstructure:"alias"`       //合约日期别名, 目前只关注: “this_week”
 | 
			
		||||
	BaseCcy  string  `json:"baseCcy" mapstructure:"baseCcy"`   //交易货币币种,如 BTC-USDT 中BTC
 | 
			
		||||
	Category int32   `json:"category" mapstructure:"category"` // 手续费档位,每个交易产品属于哪个档位手续费,币币交易, 第1类和第3类:卖0.080%	买0.100%,第2类:0.060%	0.060%
 | 
			
		||||
	CtVal    float64 `json:"ctVal" mapstructure:"ctVal"`       //合约面值
 | 
			
		||||
	CtValCcy string  `json:"ctValCcy" mapstructure:"ctValCcy"` //合约面值计价币种
 | 
			
		||||
	InstId   string  `json:"instId" mapstructure:"instId"`     // 产品ID
 | 
			
		||||
	InstType string  `json:"instType" mapstructure:"instType"` // 产品类型 只关注SPOT
 | 
			
		||||
	LotSz    float64 `json:"lotSz" mapstructure:"lotSz"`       //下单数量精度
 | 
			
		||||
	MinSz    int32   `json:"minSz" mapstructure:"minSz"`       //最小下单数量
 | 
			
		||||
	QuoteCcy string  `json:"quoteCcy" mapstructure:"quoteCcy"` // 计价货币币种,如 BTC-USDT 中 USDT ,仅适用于币币
 | 
			
		||||
	State    string  `json:"state" mapstructure:"state"`       //产品状态:live, suspend,expired,preopen
 | 
			
		||||
	TickSz   float64 `json:"tickSz" mapstructure:"tickSz"`     //下单价格精度,如0.00001
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// {
 | 
			
		||||
// alias: ,
 | 
			
		||||
// baseCcy: AE,
 | 
			
		||||
// category: 2,
 | 
			
		||||
// ctMult: ,
 | 
			
		||||
// ctType: ,
 | 
			
		||||
// ctVal: ,
 | 
			
		||||
// ctValCcy: ,
 | 
			
		||||
// expTime: ,
 | 
			
		||||
// instId: AE-BTC,
 | 
			
		||||
// instType: SPOT,
 | 
			
		||||
// lever: ,
 | 
			
		||||
// listTime: ,
 | 
			
		||||
// lotSz: 0.00000001,
 | 
			
		||||
// minSz: 10,
 | 
			
		||||
// optType: ,
 | 
			
		||||
// quoteCcy: BTC,
 | 
			
		||||
// settleCcy: ,
 | 
			
		||||
// state: live,
 | 
			
		||||
// stk: ,
 | 
			
		||||
// tickSz: 0.00000001,
 | 
			
		||||
// uly:
 | 
			
		||||
// }
 | 
			
		||||
							
								
								
									
										20
									
								
								models/ticker.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								models/ticker.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
type Ticker struct {
 | 
			
		||||
	InstType  string  `json:"instType,omitempty"`  //SWAP,
 | 
			
		||||
	InstId    string  `json:"instId,omitempty"`    //LTC-USD-SWAP,
 | 
			
		||||
	Last      float64 `json:"last,omitempty"`      //9999.99,
 | 
			
		||||
	LastSz    float64 `json:"lastSz,omitempty"`    //0.1,
 | 
			
		||||
	AskPx     float64 `json:"askPx,omitempty"`     //9999.99,
 | 
			
		||||
	AskSz     int32   `json:"askSz,omitempty"`     //11,
 | 
			
		||||
	bidPx     float64 `json:"bidPx,omitempty"`     //8888.88,
 | 
			
		||||
	BidSz     int32   `json:"bidSz,omitempty"`     //5,
 | 
			
		||||
	Open24h   int32   `json:"open24h,omitempty"`   //9000,
 | 
			
		||||
	High24h   int32   `json:"high24h,omitempty"`   //10000,
 | 
			
		||||
	Low24h    float64 `json:"low24h,omitempty"`    //8888.88,
 | 
			
		||||
	VolCcy24h int32   `json:"volCcy24h,omitempty"` //2222,
 | 
			
		||||
	Vol24h    int32   `json:"vol24h,omitempty"`    //2222,
 | 
			
		||||
	SodUtc0   int32   `json:"sodUtc0,omitempty"`   //2222,
 | 
			
		||||
	SodUtc8   int32   `json:"sodUtc8,omitempty"`   //2222,
 | 
			
		||||
	Ts        int64   `json:"ts,omitempty"`        //1597026383085
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								okex/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								okex/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
ws/vendor/
 | 
			
		||||
							
								
								
									
										21
									
								
								okex/LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								okex/LICENSE
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
MIT License
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2021 wang
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
SOFTWARE.
 | 
			
		||||
							
								
								
									
										32
									
								
								okex/config/conf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								okex/config/conf.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import "fmt"
 | 
			
		||||
 | 
			
		||||
type Env struct {
 | 
			
		||||
	RestEndpoint string `yaml:"RestEndpoint"`
 | 
			
		||||
	WsEndpoint   string `yaml:"WsEndpoint"`
 | 
			
		||||
	IsSimulation bool   `yaml:"IsSimulation"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ApiInfo struct {
 | 
			
		||||
	ApiKey     string `yaml:"ApiKey"`
 | 
			
		||||
	SecretKey  string `yaml:"SecretKey"`
 | 
			
		||||
	Passphrase string `yaml:"Passphrase"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MetaData struct {
 | 
			
		||||
	Description string `yaml:"Description"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Config struct {
 | 
			
		||||
	MetaData `yaml:"MetaData"`
 | 
			
		||||
	Env      `yaml:"Env"`
 | 
			
		||||
	ApiInfo  `yaml:"ApiInfo"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *ApiInfo) String() string {
 | 
			
		||||
	res := "ApiInfo{"
 | 
			
		||||
	res += fmt.Sprintf("ApiKey:%v,SecretKey:%v,Passphrase:%v", s.ApiKey, s.SecretKey, s.Passphrase)
 | 
			
		||||
	res += "}"
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								okex/config/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								okex/config/go.mod
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
module v5sdk_go/config
 | 
			
		||||
 | 
			
		||||
go 1.14
 | 
			
		||||
							
								
								
									
										8
									
								
								okex/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								okex/go.mod
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
module v5sdk_go
 | 
			
		||||
 | 
			
		||||
go 1.15
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/gorilla/websocket v1.4.2
 | 
			
		||||
	github.com/stretchr/testify v1.7.0
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										12
									
								
								okex/go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								okex/go.sum
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 | 
			
		||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 | 
			
		||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
							
								
								
									
										237
									
								
								okex/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								okex/main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,237 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"time"
 | 
			
		||||
	. "v5sdk_go/rest"
 | 
			
		||||
	. "v5sdk_go/ws"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	rest API请求
 | 
			
		||||
	更多示例请查看 rest/rest_test.go
 | 
			
		||||
*/
 | 
			
		||||
func REST() {
 | 
			
		||||
	// 设置您的APIKey
 | 
			
		||||
	apikey := APIKeyInfo{
 | 
			
		||||
		ApiKey:     "eca767d4-fda5-4a1b-bb28-49ae18093307",
 | 
			
		||||
		SecKey:     "8CA3628A556ED137977DB298D37BC7F3",
 | 
			
		||||
		PassPhrase: "Op3Druaron",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 第三个参数代表是否为模拟环境,更多信息查看接口说明
 | 
			
		||||
	cli := NewRESTClient("https://www.okex.win", &apikey, false)
 | 
			
		||||
	rsp, err := cli.Get(context.Background(), "/api/v5/account/balance", nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Response:")
 | 
			
		||||
	fmt.Println("\thttp code: ", rsp.Code)
 | 
			
		||||
	fmt.Println("\t总耗时: ", rsp.TotalUsedTime)
 | 
			
		||||
	fmt.Println("\t请求耗时: ", rsp.ReqUsedTime)
 | 
			
		||||
	fmt.Println("\t返回消息: ", rsp.Body)
 | 
			
		||||
	fmt.Println("\terrCode: ", rsp.V5Response.Code)
 | 
			
		||||
	fmt.Println("\terrMsg: ", rsp.V5Response.Msg)
 | 
			
		||||
	fmt.Println("\tdata: ", rsp.V5Response.Data)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 订阅私有频道
 | 
			
		||||
func wsPriv() {
 | 
			
		||||
	ep := "wss://ws.okex.com:8443/ws/v5/private?brokerId=9999"
 | 
			
		||||
 | 
			
		||||
	// 填写您自己的APIKey信息
 | 
			
		||||
	apikey := "xxxx"
 | 
			
		||||
	secretKey := "xxxxx"
 | 
			
		||||
	passphrase := "xxxxx"
 | 
			
		||||
 | 
			
		||||
	// 创建ws客户端
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 设置连接超时
 | 
			
		||||
	r.SetDailTimeout(time.Second * 2)
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer r.Stop()
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	res, _, err = r.Login(apikey, secretKey, passphrase)
 | 
			
		||||
	if res {
 | 
			
		||||
		fmt.Println("登录成功!")
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("登录失败!", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 订阅账户频道
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["ccy"] = "BTC"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PrivAccout(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!耗时:", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(100 * time.Second)
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PrivAccout(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 订阅公共频道
 | 
			
		||||
func wsPub() {
 | 
			
		||||
	ep := "wss://wsaws.okex.com:8443/ws/v5/public?brokerId=9999"
 | 
			
		||||
 | 
			
		||||
	// 创建ws客户端
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 设置连接超时
 | 
			
		||||
	r.SetDailTimeout(time.Second * 2)
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer r.Stop()
 | 
			
		||||
	// 订阅产品频道
 | 
			
		||||
	// 在这里初始化instrument列表
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instType"] = FUTURES
 | 
			
		||||
	//arg["instType"] = OPTION
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	//订阅
 | 
			
		||||
	res, _, err := r.PubInstruemnts(OP_SUBSCRIBE, args)
 | 
			
		||||
	fmt.Println("args:", args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 在这里 args1 初始化tickerList的列表
 | 
			
		||||
	var args1 []map[string]string
 | 
			
		||||
	arg1 := make(map[string]string)
 | 
			
		||||
	arg1["instId"] = "ETH-USDT"
 | 
			
		||||
	//arg["instType"] = OPTION
 | 
			
		||||
	args1 = append(args1, arg1)
 | 
			
		||||
	//------------------------------------------------------
 | 
			
		||||
	start1 := time.Now()
 | 
			
		||||
	res, _, err = r.PubTickers(OP_SUBSCRIBE, args1)
 | 
			
		||||
	fmt.Println("args:", args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start1)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
	time.Sleep(300 * time.Second)
 | 
			
		||||
	//
 | 
			
		||||
	// start = time.Now()
 | 
			
		||||
	// res, _, err = r.PubInstruemnts(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	// if res {
 | 
			
		||||
	// usedTime := time.Since(start)
 | 
			
		||||
	// fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	// } else {
 | 
			
		||||
	// fmt.Println("取消订阅失败!", err)
 | 
			
		||||
	// }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// websocket交易
 | 
			
		||||
func wsJrpc() {
 | 
			
		||||
	ep := "wss://ws.okex.com:8443/ws/v5/private?brokerId=9999"
 | 
			
		||||
 | 
			
		||||
	// 填写您自己的APIKey信息
 | 
			
		||||
	apikey := "xxxx"
 | 
			
		||||
	secretKey := "xxxxx"
 | 
			
		||||
	passphrase := "xxxxx"
 | 
			
		||||
 | 
			
		||||
	var res bool
 | 
			
		||||
	var req_id string
 | 
			
		||||
 | 
			
		||||
	// 创建ws客户端
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 设置连接超时
 | 
			
		||||
	r.SetDailTimeout(time.Second * 2)
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer r.Stop()
 | 
			
		||||
 | 
			
		||||
	res, _, err = r.Login(apikey, secretKey, passphrase)
 | 
			
		||||
	if res {
 | 
			
		||||
		fmt.Println("登录成功!")
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("登录失败!", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	param := map[string]interface{}{}
 | 
			
		||||
	param["instId"] = "BTC-USDT"
 | 
			
		||||
	param["tdMode"] = "cash"
 | 
			
		||||
	param["side"] = "buy"
 | 
			
		||||
	param["ordType"] = "market"
 | 
			
		||||
	param["sz"] = "200"
 | 
			
		||||
	req_id = "00001"
 | 
			
		||||
 | 
			
		||||
	res, _, err = r.PlaceOrder(req_id, param)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("下单成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("下单失败!", usedTime.String(), err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	// 公共订阅
 | 
			
		||||
	wsPub()
 | 
			
		||||
 | 
			
		||||
	// 私有订阅
 | 
			
		||||
	// wsPriv()
 | 
			
		||||
 | 
			
		||||
	// websocket交易
 | 
			
		||||
	// wsJrpc()
 | 
			
		||||
 | 
			
		||||
	// rest请求
 | 
			
		||||
	// REST()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										283
									
								
								okex/readme.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								okex/readme.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,283 @@
 | 
			
		||||
# 简介
 | 
			
		||||
OKEX go版本的v5sdk,仅供学习交流使用。
 | 
			
		||||
(文档持续完善中)
 | 
			
		||||
# 项目说明
 | 
			
		||||
 | 
			
		||||
## REST调用
 | 
			
		||||
``` go
 | 
			
		||||
    // 设置您的APIKey
 | 
			
		||||
	apikey := APIKeyInfo{
 | 
			
		||||
		ApiKey:     "xxxx",
 | 
			
		||||
		SecKey:     "xxxx",
 | 
			
		||||
		PassPhrase: "xxxx",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 第三个参数代表是否为模拟环境,更多信息查看接口说明
 | 
			
		||||
	cli := NewRESTClient("https://www.okex.win", &apikey, true)
 | 
			
		||||
	rsp, err := cli.Get(context.Background(), "/api/v5/account/balance", nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Response:")
 | 
			
		||||
	fmt.Println("\thttp code: ", rsp.Code)
 | 
			
		||||
	fmt.Println("\t总耗时: ", rsp.TotalUsedTime)
 | 
			
		||||
	fmt.Println("\t请求耗时: ", rsp.ReqUsedTime)
 | 
			
		||||
	fmt.Println("\t返回消息: ", rsp.Body)
 | 
			
		||||
	fmt.Println("\terrCode: ", rsp.V5Response.Code)
 | 
			
		||||
	fmt.Println("\terrMsg: ", rsp.V5Response.Msg)
 | 
			
		||||
	fmt.Println("\tdata: ", rsp.V5Response.Data)
 | 
			
		||||
 ```
 | 
			
		||||
更多示例请查看rest/rest_test.go  
 | 
			
		||||
 | 
			
		||||
## websocket订阅
 | 
			
		||||
 | 
			
		||||
### 私有频道
 | 
			
		||||
```go
 | 
			
		||||
    ep := "wss://ws.okex.com:8443/ws/v5/private?brokerId=9999"
 | 
			
		||||
 | 
			
		||||
	// 填写您自己的APIKey信息
 | 
			
		||||
	apikey := "xxxx"
 | 
			
		||||
	secretKey := "xxxxx"
 | 
			
		||||
	passphrase := "xxxxx"
 | 
			
		||||
 | 
			
		||||
	// 创建ws客户端
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 设置连接超时
 | 
			
		||||
	r.SetDailTimeout(time.Second * 2)
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer r.Stop()
 | 
			
		||||
 | 
			
		||||
	var res bool
 | 
			
		||||
	// 私有频道需要登录
 | 
			
		||||
	res, _, err = r.Login(apikey, secretKey, passphrase)
 | 
			
		||||
	if res {
 | 
			
		||||
		fmt.Println("登录成功!")
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("登录失败!", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["ccy"] = "BTC"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	// 订阅账户频道
 | 
			
		||||
	res, _, err = r.PrivAccout(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!耗时:", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(100 * time.Second)
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	// 取消订阅账户频道
 | 
			
		||||
	res, _, err = r.PrivAccout(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
```
 | 
			
		||||
更多示例请查看ws/ws_priv_channel_test.go  
 | 
			
		||||
 | 
			
		||||
### 公有频道
 | 
			
		||||
```go
 | 
			
		||||
    ep := "wss://ws.okex.com:8443/ws/v5/public?brokerId=9999"
 | 
			
		||||
 | 
			
		||||
	// 创建ws客户端
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	// 设置连接超时
 | 
			
		||||
	r.SetDailTimeout(time.Second * 2)
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer r.Stop()
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instType"] = FUTURES
 | 
			
		||||
	//arg["instType"] = OPTION
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
 | 
			
		||||
	// 订阅产品频道
 | 
			
		||||
	res, _, err := r.PubInstruemnts(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(30 * time.Second)
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
 | 
			
		||||
	// 取消订阅产品频道
 | 
			
		||||
	res, _, err = r.PubInstruemnts(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
```
 | 
			
		||||
更多示例请查看ws/ws_pub_channel_test.go  
 | 
			
		||||
 | 
			
		||||
## websocket交易
 | 
			
		||||
```go
 | 
			
		||||
    ep := "wss://ws.okex.com:8443/ws/v5/private?brokerId=9999"
 | 
			
		||||
 | 
			
		||||
	// 填写您自己的APIKey信息
 | 
			
		||||
	apikey := "xxxx"
 | 
			
		||||
	secretKey := "xxxxx"
 | 
			
		||||
	passphrase := "xxxxx"
 | 
			
		||||
 | 
			
		||||
	var res bool
 | 
			
		||||
	var req_id string
 | 
			
		||||
 | 
			
		||||
	// 创建ws客户端
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 设置连接超时
 | 
			
		||||
	r.SetDailTimeout(time.Second * 2)
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer r.Stop()
 | 
			
		||||
 | 
			
		||||
	res, _, err = r.Login(apikey, secretKey, passphrase)
 | 
			
		||||
	if res {
 | 
			
		||||
		fmt.Println("登录成功!")
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("登录失败!", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	param := map[string]interface{}{}
 | 
			
		||||
	param["instId"] = "BTC-USDT"
 | 
			
		||||
	param["tdMode"] = "cash"
 | 
			
		||||
	param["side"] = "buy"
 | 
			
		||||
	param["ordType"] = "market"
 | 
			
		||||
	param["sz"] = "200"
 | 
			
		||||
	req_id = "00001"
 | 
			
		||||
 | 
			
		||||
	// 单个下单
 | 
			
		||||
	res, _, err = r.PlaceOrder(req_id, param)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("下单成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("下单失败!", usedTime.String(), err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
更多示例请查看ws/ws_jrpc_test.go  
 | 
			
		||||
 | 
			
		||||
## wesocket推送
 | 
			
		||||
websocket推送数据分为两种类型数据:`普通推送数据`和`深度类型数据`。  
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
ws/wImpl/BookData.go
 | 
			
		||||
 | 
			
		||||
// 普通推送
 | 
			
		||||
type MsgData struct {
 | 
			
		||||
	Arg  map[string]string `json:"arg"`
 | 
			
		||||
	Data []interface{}     `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 深度数据
 | 
			
		||||
type DepthData struct {
 | 
			
		||||
	Arg    map[string]string `json:"arg"`
 | 
			
		||||
	Action string            `json:"action"`
 | 
			
		||||
	Data   []DepthDetail     `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
如果需要对推送数据做处理用户可以自定义回调函数:
 | 
			
		||||
1. 全局消息处理的回调函数  
 | 
			
		||||
该回调函数会处理所有从服务端接受到的数据。
 | 
			
		||||
```go
 | 
			
		||||
/*
 | 
			
		||||
	添加全局消息处理的回调函数
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) AddMessageHook(fn ReceivedDataCallback) error {
 | 
			
		||||
	a.onMessageHook = fn
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
使用方法参见 ws/ws_test.go中测试用例TestAddMessageHook。
 | 
			
		||||
 | 
			
		||||
2. 订阅消息处理回调函数  
 | 
			
		||||
可以处理所有非深度类型的数据,包括 订阅/取消订阅,普通推送数据。
 | 
			
		||||
```go
 | 
			
		||||
/*
 | 
			
		||||
	添加订阅消息处理的回调函数
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) AddBookMsgHook(fn ReceivedMsgDataCallback) error {
 | 
			
		||||
	a.onBookMsgHook = fn
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
使用方法参见 ws/ws_test.go中测试用例TestAddBookedDataHook。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
3. 深度消息处理的回调函数  
 | 
			
		||||
这里需要说明的是,Wsclient提供了深度数据管理和自动checksum的功能,用户如果需要关闭此功能,只需要调用EnableAutoDepthMgr方法。
 | 
			
		||||
```go
 | 
			
		||||
/*
 | 
			
		||||
	添加深度消息处理的回调函数
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) AddDepthHook(fn ReceivedDepthDataCallback) error {
 | 
			
		||||
	a.onDepthHook = fn
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
使用方法参见 ws/ws_pub_channel_test.go中测试用例TestOrderBooks。
 | 
			
		||||
 | 
			
		||||
4. 错误消息类型回调函数  
 | 
			
		||||
```go
 | 
			
		||||
func (a *WsClient) AddErrMsgHook(fn ReceivedDataCallback) error {
 | 
			
		||||
	a.OnErrorHook = fn
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# 联系方式
 | 
			
		||||
邮箱:caron_co@163.com  
 | 
			
		||||
微信:caron_co
 | 
			
		||||
							
								
								
									
										35
									
								
								okex/rest/contants.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								okex/rest/contants.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
			
		||||
package rest
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	  http headers
 | 
			
		||||
	*/
 | 
			
		||||
	OK_ACCESS_KEY        = "OK-ACCESS-KEY"
 | 
			
		||||
	OK_ACCESS_SIGN       = "OK-ACCESS-SIGN"
 | 
			
		||||
	OK_ACCESS_TIMESTAMP  = "OK-ACCESS-TIMESTAMP"
 | 
			
		||||
	OK_ACCESS_PASSPHRASE = "OK-ACCESS-PASSPHRASE"
 | 
			
		||||
	X_SIMULATE_TRADING   = "x-simulated-trading"
 | 
			
		||||
 | 
			
		||||
	CONTENT_TYPE = "Content-Type"
 | 
			
		||||
	ACCEPT       = "Accept"
 | 
			
		||||
	COOKIE       = "Cookie"
 | 
			
		||||
	LOCALE       = "locale="
 | 
			
		||||
 | 
			
		||||
	APPLICATION_JSON      = "application/json"
 | 
			
		||||
	APPLICATION_JSON_UTF8 = "application/json; charset=UTF-8"
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	  i18n: internationalization
 | 
			
		||||
	*/
 | 
			
		||||
	ENGLISH            = "en_US"
 | 
			
		||||
	SIMPLIFIED_CHINESE = "zh_CN"
 | 
			
		||||
	//zh_TW || zh_HK
 | 
			
		||||
	TRADITIONAL_CHINESE = "zh_HK"
 | 
			
		||||
 | 
			
		||||
	GET = "GET"
 | 
			
		||||
	POST = "POST"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
							
								
								
									
										3
									
								
								okex/rest/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								okex/rest/go.mod
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
module v5sdk_go/rest
 | 
			
		||||
 | 
			
		||||
go 1.14
 | 
			
		||||
							
								
								
									
										21
									
								
								okex/rest/go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								okex/rest/go.sum
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
 | 
			
		||||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
 | 
			
		||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
 | 
			
		||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 | 
			
		||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 | 
			
		||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 | 
			
		||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
			
		||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
			
		||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 | 
			
		||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
							
								
								
									
										331
									
								
								okex/rest/rest.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										331
									
								
								okex/rest/rest.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,331 @@
 | 
			
		||||
package rest
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
	. "v5sdk_go/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type RESTAPI struct {
 | 
			
		||||
	EndPoint string `json:"endPoint"`
 | 
			
		||||
	// GET/POST
 | 
			
		||||
	Method     string                 `json:"method"`
 | 
			
		||||
	Uri        string                 `json:"uri"`
 | 
			
		||||
	Param      map[string]interface{} `json:"param"`
 | 
			
		||||
	Timeout    time.Duration
 | 
			
		||||
	ApiKeyInfo *APIKeyInfo
 | 
			
		||||
	isSimulate bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type APIKeyInfo struct {
 | 
			
		||||
	ApiKey     string
 | 
			
		||||
	PassPhrase string
 | 
			
		||||
	SecKey     string
 | 
			
		||||
	UserId     string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RESTAPIResult struct {
 | 
			
		||||
	Url    string `json:"url"`
 | 
			
		||||
	Param  string `json:"param"`
 | 
			
		||||
	Header string `json:"header"`
 | 
			
		||||
	Code   int    `json:"code"`
 | 
			
		||||
	// 原始返回信息
 | 
			
		||||
	Body string `json:"body"`
 | 
			
		||||
	// okexV5返回的数据
 | 
			
		||||
	V5Response    Okexv5APIResponse `json:"v5Response"`
 | 
			
		||||
	ReqUsedTime   time.Duration     `json:"reqUsedTime"`
 | 
			
		||||
	TotalUsedTime time.Duration     `json:"totalUsedTime"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Okexv5APIResponse struct {
 | 
			
		||||
	Code string                   `json:"code"`
 | 
			
		||||
	Msg  string                   `json:"msg"`
 | 
			
		||||
	Data []map[string]interface{} `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	endPoint:请求地址
 | 
			
		||||
	apiKey
 | 
			
		||||
	isSimulate: 是否为模拟环境
 | 
			
		||||
*/
 | 
			
		||||
func NewRESTClient(endPoint string, apiKey *APIKeyInfo, isSimulate bool) *RESTAPI {
 | 
			
		||||
 | 
			
		||||
	res := &RESTAPI{
 | 
			
		||||
		EndPoint:   endPoint,
 | 
			
		||||
		ApiKeyInfo: apiKey,
 | 
			
		||||
		isSimulate: isSimulate,
 | 
			
		||||
		Timeout:    5 * time.Second,
 | 
			
		||||
	}
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewRESTAPI(ep, method, uri string, param *map[string]interface{}) *RESTAPI {
 | 
			
		||||
	//TODO:参数校验
 | 
			
		||||
	reqParam := make(map[string]interface{})
 | 
			
		||||
 | 
			
		||||
	if param != nil {
 | 
			
		||||
		reqParam = *param
 | 
			
		||||
	}
 | 
			
		||||
	res := &RESTAPI{
 | 
			
		||||
		EndPoint: ep,
 | 
			
		||||
		Method:   method,
 | 
			
		||||
		Uri:      uri,
 | 
			
		||||
		Param:    reqParam,
 | 
			
		||||
		Timeout:  150 * time.Second,
 | 
			
		||||
	}
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *RESTAPI) SetSimulate(b bool) *RESTAPI {
 | 
			
		||||
	this.isSimulate = b
 | 
			
		||||
	return this
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *RESTAPI) SetAPIKey(apiKey, secKey, passPhrase string) *RESTAPI {
 | 
			
		||||
	if this.ApiKeyInfo == nil {
 | 
			
		||||
		this.ApiKeyInfo = &APIKeyInfo{
 | 
			
		||||
			ApiKey:     apiKey,
 | 
			
		||||
			PassPhrase: passPhrase,
 | 
			
		||||
			SecKey:     secKey,
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		this.ApiKeyInfo.ApiKey = apiKey
 | 
			
		||||
		this.ApiKeyInfo.PassPhrase = passPhrase
 | 
			
		||||
		this.ApiKeyInfo.SecKey = secKey
 | 
			
		||||
	}
 | 
			
		||||
	return this
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *RESTAPI) SetUserId(userId string) *RESTAPI {
 | 
			
		||||
	if this.ApiKeyInfo == nil {
 | 
			
		||||
		fmt.Println("ApiKey为空")
 | 
			
		||||
		return this
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.ApiKeyInfo.UserId = userId
 | 
			
		||||
	return this
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *RESTAPI) SetTimeOut(timeout time.Duration) *RESTAPI {
 | 
			
		||||
	this.Timeout = timeout
 | 
			
		||||
	return this
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GET请求
 | 
			
		||||
func (this *RESTAPI) Get(ctx context.Context, uri string, param *map[string]interface{}) (res *RESTAPIResult, err error) {
 | 
			
		||||
	this.Method = GET
 | 
			
		||||
	this.Uri = uri
 | 
			
		||||
 | 
			
		||||
	reqParam := make(map[string]interface{})
 | 
			
		||||
 | 
			
		||||
	if param != nil {
 | 
			
		||||
		reqParam = *param
 | 
			
		||||
	}
 | 
			
		||||
	this.Param = reqParam
 | 
			
		||||
	return this.Run(ctx)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// POST请求
 | 
			
		||||
func (this *RESTAPI) Post(ctx context.Context, uri string, param *map[string]interface{}) (res *RESTAPIResult, err error) {
 | 
			
		||||
	this.Method = POST
 | 
			
		||||
	this.Uri = uri
 | 
			
		||||
 | 
			
		||||
	reqParam := make(map[string]interface{})
 | 
			
		||||
 | 
			
		||||
	if param != nil {
 | 
			
		||||
		reqParam = *param
 | 
			
		||||
	}
 | 
			
		||||
	this.Param = reqParam
 | 
			
		||||
 | 
			
		||||
	return this.Run(ctx)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *RESTAPI) Run(ctx context.Context) (res *RESTAPIResult, err error) {
 | 
			
		||||
 | 
			
		||||
	if this.ApiKeyInfo == nil {
 | 
			
		||||
		err = errors.New("APIKey不可为空")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	procStart := time.Now()
 | 
			
		||||
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if res != nil {
 | 
			
		||||
			res.TotalUsedTime = time.Since(procStart)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	client := &http.Client{
 | 
			
		||||
		Timeout: this.Timeout,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uri, body, err := this.GenReqInfo()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := this.EndPoint + uri
 | 
			
		||||
	bodyBuf := new(bytes.Buffer)
 | 
			
		||||
	bodyBuf.ReadFrom(strings.NewReader(body))
 | 
			
		||||
 | 
			
		||||
	req, err := http.NewRequest(this.Method, url, bodyBuf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res = &RESTAPIResult{
 | 
			
		||||
		Url:   url,
 | 
			
		||||
		Param: body,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Sign and set request headers
 | 
			
		||||
	timestamp := IsoTime()
 | 
			
		||||
	preHash := PreHashString(timestamp, this.Method, uri, body)
 | 
			
		||||
	//log.Println("preHash:", preHash)
 | 
			
		||||
	sign, err := HmacSha256Base64Signer(preHash, this.ApiKeyInfo.SecKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	//log.Println("sign:", sign)
 | 
			
		||||
	headStr := this.SetHeaders(req, timestamp, sign)
 | 
			
		||||
	res.Header = headStr
 | 
			
		||||
 | 
			
		||||
	this.PrintRequest(req, body, preHash)
 | 
			
		||||
	resp, err := client.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("请求失败!", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	res.ReqUsedTime = time.Since(procStart)
 | 
			
		||||
 | 
			
		||||
	resBuff, err := ioutil.ReadAll(resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("获取请求结果失败!", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res.Body = string(resBuff)
 | 
			
		||||
	res.Code = resp.StatusCode
 | 
			
		||||
 | 
			
		||||
	// 解析结果
 | 
			
		||||
	var v5rsp Okexv5APIResponse
 | 
			
		||||
	err = json.Unmarshal(resBuff, &v5rsp)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("解析v5返回失败!", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res.V5Response = v5rsp
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	生成请求对应的参数
 | 
			
		||||
*/
 | 
			
		||||
func (this *RESTAPI) GenReqInfo() (uri string, body string, err error) {
 | 
			
		||||
	uri = this.Uri
 | 
			
		||||
 | 
			
		||||
	switch this.Method {
 | 
			
		||||
	case GET:
 | 
			
		||||
		getParam := []string{}
 | 
			
		||||
 | 
			
		||||
		if len(this.Param) == 0 {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for k, v := range this.Param {
 | 
			
		||||
			getParam = append(getParam, fmt.Sprintf("%v=%v", k, v))
 | 
			
		||||
		}
 | 
			
		||||
		uri = uri + "?" + strings.Join(getParam, "&")
 | 
			
		||||
 | 
			
		||||
	case POST:
 | 
			
		||||
 | 
			
		||||
		var rawBody []byte
 | 
			
		||||
		rawBody, err = json.Marshal(this.Param)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		body = string(rawBody)
 | 
			
		||||
	default:
 | 
			
		||||
		err = errors.New("request type unknown!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
   Set http request headers:
 | 
			
		||||
   Accept: application/json
 | 
			
		||||
   Content-Type: application/json; charset=UTF-8  (default)
 | 
			
		||||
   Cookie: locale=en_US        (English)
 | 
			
		||||
   OK-ACCESS-KEY: (Your setting)
 | 
			
		||||
   OK-ACCESS-SIGN: (Use your setting, auto sign and add)
 | 
			
		||||
   OK-ACCESS-TIMESTAMP: (Auto add)
 | 
			
		||||
   OK-ACCESS-PASSPHRASE: Your setting
 | 
			
		||||
*/
 | 
			
		||||
func (this *RESTAPI) SetHeaders(request *http.Request, timestamp string, sign string) (header string) {
 | 
			
		||||
 | 
			
		||||
	request.Header.Add(ACCEPT, APPLICATION_JSON)
 | 
			
		||||
	header += ACCEPT + ":" + APPLICATION_JSON + "\n"
 | 
			
		||||
 | 
			
		||||
	request.Header.Add(CONTENT_TYPE, APPLICATION_JSON_UTF8)
 | 
			
		||||
	header += CONTENT_TYPE + ":" + APPLICATION_JSON_UTF8 + "\n"
 | 
			
		||||
 | 
			
		||||
	request.Header.Add(COOKIE, LOCALE+ENGLISH)
 | 
			
		||||
	header += COOKIE + ":" + LOCALE + ENGLISH + "\n"
 | 
			
		||||
 | 
			
		||||
	request.Header.Add(OK_ACCESS_KEY, this.ApiKeyInfo.ApiKey)
 | 
			
		||||
	header += OK_ACCESS_KEY + ":" + this.ApiKeyInfo.ApiKey + "\n"
 | 
			
		||||
 | 
			
		||||
	request.Header.Add(OK_ACCESS_SIGN, sign)
 | 
			
		||||
	header += OK_ACCESS_SIGN + ":" + sign + "\n"
 | 
			
		||||
 | 
			
		||||
	request.Header.Add(OK_ACCESS_TIMESTAMP, timestamp)
 | 
			
		||||
	header += OK_ACCESS_TIMESTAMP + ":" + timestamp + "\n"
 | 
			
		||||
 | 
			
		||||
	request.Header.Add(OK_ACCESS_PASSPHRASE, this.ApiKeyInfo.PassPhrase)
 | 
			
		||||
	header += OK_ACCESS_PASSPHRASE + ":" + this.ApiKeyInfo.PassPhrase + "\n"
 | 
			
		||||
 | 
			
		||||
	//模拟盘交易标记
 | 
			
		||||
	if this.isSimulate {
 | 
			
		||||
		request.Header.Add(X_SIMULATE_TRADING, "1")
 | 
			
		||||
		header += X_SIMULATE_TRADING + ":1" + "\n"
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	打印header信息
 | 
			
		||||
*/
 | 
			
		||||
func (this *RESTAPI) PrintRequest(request *http.Request, body string, preHash string) {
 | 
			
		||||
	if this.ApiKeyInfo.SecKey != "" {
 | 
			
		||||
		fmt.Println("  Secret-Key: " + this.ApiKeyInfo.SecKey)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println("  Request(" + IsoTime() + "):")
 | 
			
		||||
	fmt.Println("\tUrl: " + request.URL.String())
 | 
			
		||||
	fmt.Println("\tMethod: " + strings.ToUpper(request.Method))
 | 
			
		||||
	if len(request.Header) > 0 {
 | 
			
		||||
		fmt.Println("\tHeaders: ")
 | 
			
		||||
		for k, v := range request.Header {
 | 
			
		||||
			if strings.Contains(k, "Ok-") {
 | 
			
		||||
				k = strings.ToUpper(k)
 | 
			
		||||
			}
 | 
			
		||||
			fmt.Println("\t\t" + k + ": " + v[0])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println("\tBody: " + body)
 | 
			
		||||
	if preHash != "" {
 | 
			
		||||
		fmt.Println("  PreHash: " + preHash)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										100
									
								
								okex/rest/rest_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								okex/rest/rest_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,100 @@
 | 
			
		||||
package rest
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	GET请求
 | 
			
		||||
*/
 | 
			
		||||
func TestRESTAPIGet(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	rest := NewRESTAPI("https://www.okex.win", GET, "/api/v5/account/balance", nil)
 | 
			
		||||
	rest.SetSimulate(true).SetAPIKey("xxxx", "xxxx", "xxxx")
 | 
			
		||||
	rest.SetUserId("xxxxx")
 | 
			
		||||
	response, err := rest.Run(context.Background())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Response:")
 | 
			
		||||
	fmt.Println("\thttp code: ", response.Code)
 | 
			
		||||
	fmt.Println("\t总耗时: ", response.TotalUsedTime)
 | 
			
		||||
	fmt.Println("\t请求耗时: ", response.ReqUsedTime)
 | 
			
		||||
	fmt.Println("\t返回消息: ", response.Body)
 | 
			
		||||
	fmt.Println("\terrCode: ", response.V5Response.Code)
 | 
			
		||||
	fmt.Println("\terrMsg: ", response.V5Response.Msg)
 | 
			
		||||
	fmt.Println("\tdata: ", response.V5Response.Data)
 | 
			
		||||
 | 
			
		||||
	// 请求的另一种方式
 | 
			
		||||
	apikey := APIKeyInfo{
 | 
			
		||||
		ApiKey:     "xxxxx",
 | 
			
		||||
		SecKey:     "xxxxx",
 | 
			
		||||
		PassPhrase: "xxx",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cli := NewRESTClient("https://www.okex.win", &apikey, true)
 | 
			
		||||
	rsp, err := cli.Get(context.Background(), "/api/v5/account/balance", nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Response:")
 | 
			
		||||
	fmt.Println("\thttp code: ", rsp.Code)
 | 
			
		||||
	fmt.Println("\t总耗时: ", rsp.TotalUsedTime)
 | 
			
		||||
	fmt.Println("\t请求耗时: ", rsp.ReqUsedTime)
 | 
			
		||||
	fmt.Println("\t返回消息: ", rsp.Body)
 | 
			
		||||
	fmt.Println("\terrCode: ", rsp.V5Response.Code)
 | 
			
		||||
	fmt.Println("\terrMsg: ", rsp.V5Response.Msg)
 | 
			
		||||
	fmt.Println("\tdata: ", rsp.V5Response.Data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	POST请求
 | 
			
		||||
*/
 | 
			
		||||
func TestRESTAPIPost(t *testing.T) {
 | 
			
		||||
	param := make(map[string]interface{})
 | 
			
		||||
	param["greeksType"] = "PA"
 | 
			
		||||
 | 
			
		||||
	rest := NewRESTAPI("https://www.okex.win", POST, "/api/v5/account/set-greeks", ¶m)
 | 
			
		||||
	rest.SetSimulate(true).SetAPIKey("xxxx", "xxxx", "xxxx")
 | 
			
		||||
	response, err := rest.Run(context.Background())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Response:")
 | 
			
		||||
	fmt.Println("\thttp code: ", response.Code)
 | 
			
		||||
	fmt.Println("\t总耗时: ", response.TotalUsedTime)
 | 
			
		||||
	fmt.Println("\t请求耗时: ", response.ReqUsedTime)
 | 
			
		||||
	fmt.Println("\t返回消息: ", response.Body)
 | 
			
		||||
	fmt.Println("\terrCode: ", response.V5Response.Code)
 | 
			
		||||
	fmt.Println("\terrMsg: ", response.V5Response.Msg)
 | 
			
		||||
	fmt.Println("\tdata: ", response.V5Response.Data)
 | 
			
		||||
 | 
			
		||||
	// 请求的另一种方式
 | 
			
		||||
	apikey := APIKeyInfo{
 | 
			
		||||
		ApiKey:     "xxxx",
 | 
			
		||||
		SecKey:     "xxxxx",
 | 
			
		||||
		PassPhrase: "xxxx",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cli := NewRESTClient("https://www.okex.win", &apikey, true)
 | 
			
		||||
	rsp, err := cli.Post(context.Background(), "/api/v5/account/set-greeks", ¶m)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Response:")
 | 
			
		||||
	fmt.Println("\thttp code: ", rsp.Code)
 | 
			
		||||
	fmt.Println("\t总耗时: ", rsp.TotalUsedTime)
 | 
			
		||||
	fmt.Println("\t请求耗时: ", rsp.ReqUsedTime)
 | 
			
		||||
	fmt.Println("\t返回消息: ", rsp.Body)
 | 
			
		||||
	fmt.Println("\terrCode: ", rsp.V5Response.Code)
 | 
			
		||||
	fmt.Println("\terrMsg: ", rsp.V5Response.Msg)
 | 
			
		||||
	fmt.Println("\tdata: ", rsp.V5Response.Data)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								okex/utils/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								okex/utils/go.mod
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
module v5sdk_go/utils
 | 
			
		||||
 | 
			
		||||
go 1.15
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/gorilla/websocket v1.4.2
 | 
			
		||||
	github.com/stretchr/testify v1.7.0
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										7
									
								
								okex/utils/go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								okex/utils/go.sum
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
							
								
								
									
										102
									
								
								okex/utils/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								okex/utils/utils.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,102 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"compress/flate"
 | 
			
		||||
	"crypto/hmac"
 | 
			
		||||
	"crypto/sha256"
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"log"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
	//"net/http"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 Get a epoch time
 | 
			
		||||
  eg: 1521221737
 | 
			
		||||
*/
 | 
			
		||||
func EpochTime() string {
 | 
			
		||||
	millisecond := time.Now().UnixNano() / 1000000
 | 
			
		||||
	epoch := strconv.Itoa(int(millisecond))
 | 
			
		||||
	epochBytes := []byte(epoch)
 | 
			
		||||
	epoch = string(epochBytes[:10])
 | 
			
		||||
	return epoch
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 signing a message
 | 
			
		||||
 using: hmac sha256 + base64
 | 
			
		||||
  eg:
 | 
			
		||||
    message = Pre_hash function comment
 | 
			
		||||
    secretKey = E65791902180E9EF4510DB6A77F6EBAE
 | 
			
		||||
 | 
			
		||||
  return signed string = TO6uwdqz+31SIPkd4I+9NiZGmVH74dXi+Fd5X0EzzSQ=
 | 
			
		||||
*/
 | 
			
		||||
func HmacSha256Base64Signer(message string, secretKey string) (string, error) {
 | 
			
		||||
	mac := hmac.New(sha256.New, []byte(secretKey))
 | 
			
		||||
	_, err := mac.Write([]byte(message))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return base64.StdEncoding.EncodeToString(mac.Sum(nil)), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 the pre hash string
 | 
			
		||||
  eg:
 | 
			
		||||
    timestamp = 2018-03-08T10:59:25.789Z
 | 
			
		||||
    method  = POST
 | 
			
		||||
    request_path = /orders?before=2&limit=30
 | 
			
		||||
    body = {"product_id":"BTC-USD-0309","order_id":"377454671037440"}
 | 
			
		||||
 | 
			
		||||
  return pre hash string = 2018-03-08T10:59:25.789ZPOST/orders?before=2&limit=30{"product_id":"BTC-USD-0309","order_id":"377454671037440"}
 | 
			
		||||
*/
 | 
			
		||||
func PreHashString(timestamp string, method string, requestPath string, body string) string {
 | 
			
		||||
	return timestamp + strings.ToUpper(method) + requestPath + body
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 struct convert json string
 | 
			
		||||
*/
 | 
			
		||||
func Struct2JsonString(raw interface{}) (jsonString string, err error) {
 | 
			
		||||
	//fmt.Println("转化json,", raw)
 | 
			
		||||
	data, err := json.Marshal(raw)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println("convert json failed!", err)
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	//log.Println(string(data))
 | 
			
		||||
	return string(data), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 解压缩消息
 | 
			
		||||
func GzipDecode(in []byte) ([]byte, error) {
 | 
			
		||||
	reader := flate.NewReader(bytes.NewReader(in))
 | 
			
		||||
	defer reader.Close()
 | 
			
		||||
 | 
			
		||||
	return ioutil.ReadAll(reader)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 Get a iso time
 | 
			
		||||
  eg: 2018-03-16T18:02:48.284Z
 | 
			
		||||
*/
 | 
			
		||||
func IsoTime() string {
 | 
			
		||||
	utcTime := time.Now().UTC()
 | 
			
		||||
	iso := utcTime.String()
 | 
			
		||||
	isoBytes := []byte(iso)
 | 
			
		||||
	iso = string(isoBytes[:10]) + "T" + string(isoBytes[11:23]) + "Z"
 | 
			
		||||
	return iso
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								okex/utils/utils_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								okex/utils/utils_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestHmacSha256Base64Signer(t *testing.T) {
 | 
			
		||||
	raw := `2021-04-06T03:33:21.681ZPOST/api/v5/trade/order{"instId":"ETH-USDT-SWAP","ordType":"limit","px":"2300","side":"sell","sz":"1","tdMode":"cross"}`
 | 
			
		||||
	key := "1A9E86759F2D2AA16E389FD3B7F8273E"
 | 
			
		||||
	res, err := HmacSha256Base64Signer(raw, key)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(res)
 | 
			
		||||
	t.Log(res)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								okex/ws/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								okex/ws/go.mod
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
module v5sdk_go/ws
 | 
			
		||||
 | 
			
		||||
go 1.15
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/gorilla/websocket v1.4.2
 | 
			
		||||
	github.com/stretchr/testify v1.7.0
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										28
									
								
								okex/ws/go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								okex/ws/go.sum
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
			
		||||
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
 | 
			
		||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
 | 
			
		||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 | 
			
		||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 | 
			
		||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 | 
			
		||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
 | 
			
		||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 | 
			
		||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
			
		||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
			
		||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 | 
			
		||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 | 
			
		||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
							
								
								
									
										75
									
								
								okex/ws/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								okex/ws/utils.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"log"
 | 
			
		||||
	"runtime/debug"
 | 
			
		||||
	. "v5sdk_go/ws/wImpl"
 | 
			
		||||
	. "v5sdk_go/ws/wInterface"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 判断返回结果成功失败
 | 
			
		||||
func checkResult(wsReq WSReqData, wsRsps []*Msg) (res bool, err error) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		a := recover()
 | 
			
		||||
		if a != nil {
 | 
			
		||||
			log.Printf("Receive End. Recover msg: %+v", a)
 | 
			
		||||
			debug.PrintStack()
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	res = false
 | 
			
		||||
	if len(wsRsps) == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, v := range wsRsps {
 | 
			
		||||
		switch v.Info.(type) {
 | 
			
		||||
		case ErrData:
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if wsReq.GetType() != v.Info.(WSRspData).MsgType() {
 | 
			
		||||
			err = errors.New("消息类型不一致")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//检查所有频道是否都更新成功
 | 
			
		||||
	if wsReq.GetType() == MSG_NORMAL {
 | 
			
		||||
		req, ok := wsReq.(ReqData)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			log.Println("类型转化失败", req)
 | 
			
		||||
			err = errors.New("类型转化失败")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for idx, _ := range req.Args {
 | 
			
		||||
			ok := false
 | 
			
		||||
			i_req := req.Args[idx]
 | 
			
		||||
			//fmt.Println("检查",i_req)
 | 
			
		||||
			for i, _ := range wsRsps {
 | 
			
		||||
				info, _ := wsRsps[i].Info.(RspData)
 | 
			
		||||
				//fmt.Println("<<",info)
 | 
			
		||||
				if info.Event == req.Op && info.Arg["channel"] == i_req["channel"] && info.Arg["instType"] == i_req["instType"] {
 | 
			
		||||
					ok = true
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !ok {
 | 
			
		||||
				err = errors.New("未得到所有的期望的返回结果")
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		for i, _ := range wsRsps {
 | 
			
		||||
			info, _ := wsRsps[i].Info.(JRPCRsp)
 | 
			
		||||
			if info.Code != "0" {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res = true
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										226
									
								
								okex/ws/wImpl/BookData.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								okex/ws/wImpl/BookData.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,226 @@
 | 
			
		||||
/*
 | 
			
		||||
	订阅频道后收到的推送数据
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package wImpl
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"hash/crc32"
 | 
			
		||||
	"log"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 普通推送
 | 
			
		||||
type MsgData struct {
 | 
			
		||||
	Arg  map[string]string `json:"arg"`
 | 
			
		||||
	Data []interface{}     `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 深度数据
 | 
			
		||||
type DepthData struct {
 | 
			
		||||
	Arg    map[string]string `json:"arg"`
 | 
			
		||||
	Action string            `json:"action"`
 | 
			
		||||
	Data   []DepthDetail     `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DepthDetail struct {
 | 
			
		||||
	Asks     [][]string `json:"asks"`
 | 
			
		||||
	Bids     [][]string `json:"bids"`
 | 
			
		||||
	Ts       string     `json:"ts"`
 | 
			
		||||
	Checksum int32      `json:"checksum"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	深度数据校验
 | 
			
		||||
*/
 | 
			
		||||
func (this *DepthData) CheckSum(snap *DepthDetail) (pDepData *DepthDetail, err error) {
 | 
			
		||||
 | 
			
		||||
	if len(this.Data) != 1 {
 | 
			
		||||
		err = errors.New("深度数据错误!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if this.Action == DEPTH_SNAPSHOT {
 | 
			
		||||
		_, cs := CalCrc32(this.Data[0].Asks, this.Data[0].Bids)
 | 
			
		||||
 | 
			
		||||
		if cs != this.Data[0].Checksum {
 | 
			
		||||
			err = errors.New("校验失败!")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		pDepData = &this.Data[0]
 | 
			
		||||
		log.Println("snapshot校验成功", this.Data[0].Checksum)
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if this.Action == DEPTH_UPDATE {
 | 
			
		||||
		if snap == nil {
 | 
			
		||||
			err = errors.New("深度快照数据不可为空!")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pDepData, err = MergDepthData(*snap, this.Data[0], this.Data[0].Checksum)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		log.Println("update校验成功", this.Data[0].Checksum)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CalCrc32(askDepths [][]string, bidDepths [][]string) (bytes.Buffer, int32) {
 | 
			
		||||
 | 
			
		||||
	crc32BaseBuffer := bytes.Buffer{}
 | 
			
		||||
	crcAskDepth, crcBidDepth := 25, 25
 | 
			
		||||
 | 
			
		||||
	if len(askDepths) < 25 {
 | 
			
		||||
		crcAskDepth = len(askDepths)
 | 
			
		||||
	}
 | 
			
		||||
	if len(bidDepths) < 25 {
 | 
			
		||||
		crcBidDepth = len(bidDepths)
 | 
			
		||||
	}
 | 
			
		||||
	if crcAskDepth == crcBidDepth {
 | 
			
		||||
		for i := 0; i < crcAskDepth; i++ {
 | 
			
		||||
			if crc32BaseBuffer.Len() > 0 {
 | 
			
		||||
				crc32BaseBuffer.WriteString(":")
 | 
			
		||||
			}
 | 
			
		||||
			crc32BaseBuffer.WriteString(
 | 
			
		||||
				fmt.Sprintf("%v:%v:%v:%v",
 | 
			
		||||
					(bidDepths)[i][0], (bidDepths)[i][1],
 | 
			
		||||
					(askDepths)[i][0], (askDepths)[i][1]))
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
 | 
			
		||||
		var crcArr []string
 | 
			
		||||
		for i, j := 0, 0; i < crcBidDepth || j < crcAskDepth; {
 | 
			
		||||
 | 
			
		||||
			if i < crcBidDepth {
 | 
			
		||||
				crcArr = append(crcArr, fmt.Sprintf("%v:%v", (bidDepths)[i][0], (bidDepths)[i][1]))
 | 
			
		||||
				i++
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if j < crcAskDepth {
 | 
			
		||||
				crcArr = append(crcArr, fmt.Sprintf("%v:%v", (askDepths)[j][0], (askDepths)[j][1]))
 | 
			
		||||
				j++
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		crc32BaseBuffer.WriteString(strings.Join(crcArr, ":"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	expectCrc32 := int32(crc32.ChecksumIEEE(crc32BaseBuffer.Bytes()))
 | 
			
		||||
 | 
			
		||||
	return crc32BaseBuffer, expectCrc32
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	深度合并的内部方法
 | 
			
		||||
	返回结果:
 | 
			
		||||
	res:合并后的深度
 | 
			
		||||
	index: 最新的 ask1/bids1 的索引
 | 
			
		||||
*/
 | 
			
		||||
func mergeDepth(oldDepths [][]string, newDepths [][]string, method string) (res [][]string, err error) {
 | 
			
		||||
 | 
			
		||||
	oldIdx, newIdx := 0, 0
 | 
			
		||||
 | 
			
		||||
	for oldIdx < len(oldDepths) && newIdx < len(newDepths) {
 | 
			
		||||
 | 
			
		||||
		oldItem := oldDepths[oldIdx]
 | 
			
		||||
		newItem := newDepths[newIdx]
 | 
			
		||||
		var oldPrice, newPrice float64
 | 
			
		||||
		oldPrice, err = strconv.ParseFloat(oldItem[0], 10)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		newPrice, err = strconv.ParseFloat(newItem[0], 10)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if oldPrice == newPrice {
 | 
			
		||||
			if newItem[1] != "0" {
 | 
			
		||||
				res = append(res, newItem)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			oldIdx++
 | 
			
		||||
			newIdx++
 | 
			
		||||
		} else {
 | 
			
		||||
			switch method {
 | 
			
		||||
			// 降序
 | 
			
		||||
			case "bids":
 | 
			
		||||
				if oldPrice < newPrice {
 | 
			
		||||
					res = append(res, newItem)
 | 
			
		||||
					newIdx++
 | 
			
		||||
				} else {
 | 
			
		||||
 | 
			
		||||
					res = append(res, oldItem)
 | 
			
		||||
					oldIdx++
 | 
			
		||||
				}
 | 
			
		||||
			// 升序
 | 
			
		||||
			case "asks":
 | 
			
		||||
				if oldPrice > newPrice {
 | 
			
		||||
					res = append(res, newItem)
 | 
			
		||||
					newIdx++
 | 
			
		||||
				} else {
 | 
			
		||||
 | 
			
		||||
					res = append(res, oldItem)
 | 
			
		||||
					oldIdx++
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if oldIdx < len(oldDepths) {
 | 
			
		||||
		res = append(res, oldDepths[oldIdx:]...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if newIdx < len(newDepths) {
 | 
			
		||||
		res = append(res, newDepths[newIdx:]...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	深度合并,并校验
 | 
			
		||||
*/
 | 
			
		||||
func MergDepthData(snap DepthDetail, update DepthDetail, expChecksum int32) (res *DepthDetail, err error) {
 | 
			
		||||
 | 
			
		||||
	newAskDepths, err1 := mergeDepth(snap.Asks, update.Asks, "asks")
 | 
			
		||||
	if err1 != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// log.Println("old Ask - ", snap.Asks)
 | 
			
		||||
	// log.Println("update Ask - ", update.Asks)
 | 
			
		||||
	// log.Println("new Ask - ", newAskDepths)
 | 
			
		||||
	newBidDepths, err2 := mergeDepth(snap.Bids, update.Bids, "bids")
 | 
			
		||||
	if err2 != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// log.Println("old Bids - ", snap.Bids)
 | 
			
		||||
	// log.Println("update Bids - ", update.Bids)
 | 
			
		||||
	// log.Println("new Bids - ", newBidDepths)
 | 
			
		||||
 | 
			
		||||
	cBuf, checksum := CalCrc32(newAskDepths, newBidDepths)
 | 
			
		||||
	if checksum != expChecksum {
 | 
			
		||||
		err = errors.New("校验失败!")
 | 
			
		||||
		log.Println("buffer:", cBuf.String())
 | 
			
		||||
		log.Fatal(checksum, expChecksum)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res = &DepthDetail{
 | 
			
		||||
		Asks:     newAskDepths,
 | 
			
		||||
		Bids:     newBidDepths,
 | 
			
		||||
		Ts:       update.Ts,
 | 
			
		||||
		Checksum: update.Checksum,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								okex/ws/wImpl/ErrData.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								okex/ws/wImpl/ErrData.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
/*
 | 
			
		||||
	错误数据
 | 
			
		||||
*/
 | 
			
		||||
package wImpl
 | 
			
		||||
 | 
			
		||||
// 服务端请求错误返回消息格式
 | 
			
		||||
type ErrData struct {
 | 
			
		||||
	Event string `json:"event"`
 | 
			
		||||
	Code  string `json:"code"`
 | 
			
		||||
	Msg   string `json:"msg"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										50
									
								
								okex/ws/wImpl/JRPCData.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								okex/ws/wImpl/JRPCData.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
			
		||||
/*
 | 
			
		||||
	JRPC请求/响应数据
 | 
			
		||||
*/
 | 
			
		||||
package wImpl
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	. "v5sdk_go/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// jrpc请求结构体
 | 
			
		||||
type JRPCReq struct {
 | 
			
		||||
	Id   string                   `json:"id"`
 | 
			
		||||
	Op   string                   `json:"op"`
 | 
			
		||||
	Args []map[string]interface{} `json:"args"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r JRPCReq) GetType() int {
 | 
			
		||||
	return MSG_JRPC
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r JRPCReq) ToString() string {
 | 
			
		||||
	data, err := Struct2JsonString(r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	return data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r JRPCReq) Len() int {
 | 
			
		||||
	return 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// jrpc响应结构体
 | 
			
		||||
type JRPCRsp struct {
 | 
			
		||||
	Id   string                   `json:"id"`
 | 
			
		||||
	Op   string                   `json:"op"`
 | 
			
		||||
	Data []map[string]interface{} `json:"data"`
 | 
			
		||||
	Code string                   `json:"code"`
 | 
			
		||||
	Msg  string                   `json:"msg"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r JRPCRsp) MsgType() int {
 | 
			
		||||
	return MSG_JRPC
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r JRPCRsp) String() string {
 | 
			
		||||
	raw, _ := json.Marshal(r)
 | 
			
		||||
	return string(raw)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								okex/ws/wImpl/ReqData.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								okex/ws/wImpl/ReqData.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,47 @@
 | 
			
		||||
/*
 | 
			
		||||
	普通订阅请求和响应的数据格式
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package wImpl
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	. "v5sdk_go/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 客户端请求消息格式
 | 
			
		||||
type ReqData struct {
 | 
			
		||||
	Op   string              `json:"op"`
 | 
			
		||||
	Args []map[string]string `json:"args"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r ReqData) GetType() int {
 | 
			
		||||
	return MSG_NORMAL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r ReqData) ToString() string {
 | 
			
		||||
	data, err := Struct2JsonString(r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	return data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r ReqData) Len() int {
 | 
			
		||||
	return len(r.Args)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 服务端请求响应消息格式
 | 
			
		||||
type RspData struct {
 | 
			
		||||
	Event string            `json:"event"`
 | 
			
		||||
	Arg   map[string]string `json:"arg"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r RspData) MsgType() int {
 | 
			
		||||
	return MSG_NORMAL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r RspData) String() string {
 | 
			
		||||
	raw, _ := json.Marshal(r)
 | 
			
		||||
	return string(raw)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										241
									
								
								okex/ws/wImpl/contants.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								okex/ws/wImpl/contants.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,241 @@
 | 
			
		||||
package wImpl
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"regexp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	MSG_NORMAL = iota
 | 
			
		||||
	MSG_JRPC
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//事件
 | 
			
		||||
type Event int
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	EventID
 | 
			
		||||
*/
 | 
			
		||||
const (
 | 
			
		||||
	EVENT_UNKNOWN Event = iota
 | 
			
		||||
	EVENT_ERROR
 | 
			
		||||
	EVENT_PING
 | 
			
		||||
	EVENT_LOGIN
 | 
			
		||||
 | 
			
		||||
	//订阅公共频道
 | 
			
		||||
	EVENT_BOOK_INSTRUMENTS
 | 
			
		||||
	EVENT_STATUS
 | 
			
		||||
	EVENT_BOOK_TICKERS
 | 
			
		||||
	EVENT_BOOK_OPEN_INTEREST
 | 
			
		||||
	EVENT_BOOK_KLINE
 | 
			
		||||
	EVENT_BOOK_TRADE
 | 
			
		||||
	EVENT_BOOK_ESTIMATE_PRICE
 | 
			
		||||
	EVENT_BOOK_MARK_PRICE
 | 
			
		||||
	EVENT_BOOK_MARK_PRICE_CANDLE_CHART
 | 
			
		||||
	EVENT_BOOK_LIMIT_PRICE
 | 
			
		||||
	EVENT_BOOK_ORDER_BOOK
 | 
			
		||||
	EVENT_BOOK_ORDER_BOOK5
 | 
			
		||||
	EVENT_BOOK_ORDER_BOOK_TBT
 | 
			
		||||
	EVENT_BOOK_ORDER_BOOK50_TBT
 | 
			
		||||
	EVENT_BOOK_OPTION_SUMMARY
 | 
			
		||||
	EVENT_BOOK_FUND_RATE
 | 
			
		||||
	EVENT_BOOK_KLINE_INDEX
 | 
			
		||||
	EVENT_BOOK_INDEX_TICKERS
 | 
			
		||||
 | 
			
		||||
	//订阅私有频道
 | 
			
		||||
	EVENT_BOOK_ACCOUNT
 | 
			
		||||
	EVENT_BOOK_POSTION
 | 
			
		||||
	EVENT_BOOK_ORDER
 | 
			
		||||
	EVENT_BOOK_ALG_ORDER
 | 
			
		||||
	EVENT_BOOK_B_AND_P
 | 
			
		||||
 | 
			
		||||
	// JRPC
 | 
			
		||||
	EVENT_PLACE_ORDER
 | 
			
		||||
	EVENT_PLACE_BATCH_ORDERS
 | 
			
		||||
	EVENT_CANCEL_ORDER
 | 
			
		||||
	EVENT_CANCEL_BATCH_ORDERS
 | 
			
		||||
	EVENT_AMEND_ORDER
 | 
			
		||||
	EVENT_AMEND_BATCH_ORDERS
 | 
			
		||||
 | 
			
		||||
	//订阅返回数据
 | 
			
		||||
	EVENT_BOOKED_DATA
 | 
			
		||||
	EVENT_DEPTH_DATA
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	EventID,事件名称,channel
 | 
			
		||||
	注: 带有周期参数的频道 如 行情频道 ,需要将channel写为 正则表达模式方便 类型匹配,如 "^candle*"
 | 
			
		||||
*/
 | 
			
		||||
var EVENT_TABLE = [][]interface{}{
 | 
			
		||||
	// 未知的消息
 | 
			
		||||
	{EVENT_UNKNOWN, "未知", ""},
 | 
			
		||||
	// 错误的消息
 | 
			
		||||
	{EVENT_ERROR, "错误", ""},
 | 
			
		||||
	// Ping
 | 
			
		||||
	{EVENT_PING, "ping", ""},
 | 
			
		||||
	// 登陆
 | 
			
		||||
	{EVENT_LOGIN, "登录", ""},
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
		订阅公共频道
 | 
			
		||||
	*/
 | 
			
		||||
 | 
			
		||||
	{EVENT_BOOK_INSTRUMENTS, "产品", "instruments"},
 | 
			
		||||
	{EVENT_STATUS, "status", "status"},
 | 
			
		||||
	{EVENT_BOOK_TICKERS, "行情", "tickers"},
 | 
			
		||||
	{EVENT_BOOK_OPEN_INTEREST, "持仓总量", "open-interest"},
 | 
			
		||||
	{EVENT_BOOK_KLINE, "K线", "candle"},
 | 
			
		||||
	{EVENT_BOOK_TRADE, "交易", "trades"},
 | 
			
		||||
	{EVENT_BOOK_ESTIMATE_PRICE, "预估交割/行权价格", "estimated-price"},
 | 
			
		||||
	{EVENT_BOOK_MARK_PRICE, "标记价格", "mark-price"},
 | 
			
		||||
	{EVENT_BOOK_MARK_PRICE_CANDLE_CHART, "标记价格K线", "mark-price-candle"},
 | 
			
		||||
	{EVENT_BOOK_LIMIT_PRICE, "限价", "price-limit"},
 | 
			
		||||
	{EVENT_BOOK_ORDER_BOOK, "400档深度", "books"},
 | 
			
		||||
	{EVENT_BOOK_ORDER_BOOK5, "5档深度", "books5"},
 | 
			
		||||
	{EVENT_BOOK_ORDER_BOOK_TBT, "tbt深度", "books-l2-tbt"},
 | 
			
		||||
	{EVENT_BOOK_ORDER_BOOK50_TBT, "tbt50深度", "books50-l2-tbt"},
 | 
			
		||||
	{EVENT_BOOK_OPTION_SUMMARY, "期权定价", "opt-summary"},
 | 
			
		||||
	{EVENT_BOOK_FUND_RATE, "资金费率", "funding-rate"},
 | 
			
		||||
	{EVENT_BOOK_KLINE_INDEX, "指数K线", "index-candle"},
 | 
			
		||||
	{EVENT_BOOK_INDEX_TICKERS, "指数行情", "index-tickers"},
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
		订阅私有频道
 | 
			
		||||
	*/
 | 
			
		||||
	{EVENT_BOOK_ACCOUNT, "账户", "account"},
 | 
			
		||||
	{EVENT_BOOK_POSTION, "持仓", "positions"},
 | 
			
		||||
	{EVENT_BOOK_ORDER, "订单", "orders"},
 | 
			
		||||
	{EVENT_BOOK_ALG_ORDER, "策略委托订单", "orders-algo"},
 | 
			
		||||
	{EVENT_BOOK_B_AND_P, "账户余额和持仓", "balance_and_position"},
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
		JRPC
 | 
			
		||||
	*/
 | 
			
		||||
	{EVENT_PLACE_ORDER, "下单", "order"},
 | 
			
		||||
	{EVENT_PLACE_BATCH_ORDERS, "批量下单", "batch-orders"},
 | 
			
		||||
	{EVENT_CANCEL_ORDER, "撤单", "cancel-order"},
 | 
			
		||||
	{EVENT_CANCEL_BATCH_ORDERS, "批量撤单", "batch-cancel-orders"},
 | 
			
		||||
	{EVENT_AMEND_ORDER, "改单", "amend-order"},
 | 
			
		||||
	{EVENT_AMEND_BATCH_ORDERS, "批量改单", "batch-amend-orders"},
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
		订阅返回数据
 | 
			
		||||
		注意:推送数据channle统一为""
 | 
			
		||||
	*/
 | 
			
		||||
	{EVENT_BOOKED_DATA, "普通推送", ""},
 | 
			
		||||
	{EVENT_DEPTH_DATA, "深度推送", ""},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	获取事件名称
 | 
			
		||||
*/
 | 
			
		||||
func (e Event) String() string {
 | 
			
		||||
	for _, v := range EVENT_TABLE {
 | 
			
		||||
		eventId := v[0].(Event)
 | 
			
		||||
		if e == eventId {
 | 
			
		||||
			return v[1].(string)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	通过事件获取对应的channel信息
 | 
			
		||||
	对于频道名称有时间周期的 通过参数 pd 传入,拼接后返回完整channel信息
 | 
			
		||||
*/
 | 
			
		||||
func (e Event) GetChannel(pd Period) string {
 | 
			
		||||
 | 
			
		||||
	channel := ""
 | 
			
		||||
 | 
			
		||||
	for _, v := range EVENT_TABLE {
 | 
			
		||||
		eventId := v[0].(Event)
 | 
			
		||||
		if e == eventId {
 | 
			
		||||
			channel = v[2].(string)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if channel == "" {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return channel + string(pd)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	通过channel信息匹配获取事件类型
 | 
			
		||||
*/
 | 
			
		||||
func GetEventId(raw string) Event {
 | 
			
		||||
	evt := EVENT_UNKNOWN
 | 
			
		||||
 | 
			
		||||
	for _, v := range EVENT_TABLE {
 | 
			
		||||
		channel := v[2].(string)
 | 
			
		||||
		if raw == channel {
 | 
			
		||||
			evt = v[0].(Event)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		regexp := regexp.MustCompile(`^(.*)([1-9][0-9]?[\w])$`)
 | 
			
		||||
		//regexp := regexp.MustCompile(`^http://www.flysnow.org/([\d]{4})/([\d]{2})/([\d]{2})/([\w-]+).html$`)
 | 
			
		||||
 | 
			
		||||
		substr := regexp.FindStringSubmatch(raw)
 | 
			
		||||
		//fmt.Println(substr)
 | 
			
		||||
		if len(substr) >= 2 {
 | 
			
		||||
			if substr[1] == channel {
 | 
			
		||||
				evt = v[0].(Event)
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return evt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 时间维度
 | 
			
		||||
type Period string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// 年
 | 
			
		||||
	PERIOD_1YEAR Period = "1Y"
 | 
			
		||||
 | 
			
		||||
	// 月
 | 
			
		||||
	PERIOD_6Mon Period = "6M"
 | 
			
		||||
	PERIOD_3Mon Period = "3M"
 | 
			
		||||
	PERIOD_1Mon Period = "1M"
 | 
			
		||||
 | 
			
		||||
	// 周
 | 
			
		||||
	PERIOD_1WEEK Period = "1W"
 | 
			
		||||
 | 
			
		||||
	// 天
 | 
			
		||||
	PERIOD_5DAY Period = "5D"
 | 
			
		||||
	PERIOD_3DAY Period = "3D"
 | 
			
		||||
	PERIOD_2DAY Period = "2D"
 | 
			
		||||
	PERIOD_1DAY Period = "1D"
 | 
			
		||||
 | 
			
		||||
	// 小时
 | 
			
		||||
	PERIOD_12HOUR Period = "12H"
 | 
			
		||||
	PERIOD_6HOUR  Period = "6H"
 | 
			
		||||
	PERIOD_4HOUR  Period = "4H"
 | 
			
		||||
	PERIOD_2HOUR  Period = "2H"
 | 
			
		||||
	PERIOD_1HOUR  Period = "1H"
 | 
			
		||||
 | 
			
		||||
	// 分钟
 | 
			
		||||
	PERIOD_30MIN Period = "30m"
 | 
			
		||||
	PERIOD_15MIN Period = "15m"
 | 
			
		||||
	PERIOD_5MIN  Period = "5m"
 | 
			
		||||
	PERIOD_3MIN  Period = "3m"
 | 
			
		||||
	PERIOD_1MIN  Period = "1m"
 | 
			
		||||
 | 
			
		||||
	// 缺省
 | 
			
		||||
	PERIOD_NONE Period = ""
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 深度枚举
 | 
			
		||||
const (
 | 
			
		||||
	DEPTH_SNAPSHOT = "snapshot"
 | 
			
		||||
	DEPTH_UPDATE   = "update"
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										28
									
								
								okex/ws/wImpl/contants_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								okex/ws/wImpl/contants_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
			
		||||
package wImpl
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGetEventId(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	id1 := GetEventId("index-candle30m")
 | 
			
		||||
 | 
			
		||||
	assert.True(t, id1 == EVENT_BOOK_KLINE_INDEX)
 | 
			
		||||
 | 
			
		||||
	id2 := GetEventId("candle1Y")
 | 
			
		||||
 | 
			
		||||
	assert.True(t, id2 == EVENT_BOOK_KLINE)
 | 
			
		||||
 | 
			
		||||
	id3 := GetEventId("orders-algo")
 | 
			
		||||
	assert.True(t, id3 == EVENT_BOOK_ALG_ORDER)
 | 
			
		||||
 | 
			
		||||
	id4 := GetEventId("balance_and_position")
 | 
			
		||||
	assert.True(t, id4 == EVENT_BOOK_B_AND_P)
 | 
			
		||||
 | 
			
		||||
	id5 := GetEventId("index-candle1m")
 | 
			
		||||
	assert.True(t, id5 == EVENT_BOOK_KLINE_INDEX)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								okex/ws/wInterface/IParam.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								okex/ws/wInterface/IParam.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
package wInterface
 | 
			
		||||
 | 
			
		||||
import . "v5sdk_go/ws/wImpl"
 | 
			
		||||
 | 
			
		||||
// 请求数据
 | 
			
		||||
type WSParam interface {
 | 
			
		||||
	EventType() Event
 | 
			
		||||
	ToMap() *map[string]string
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								okex/ws/wInterface/IReqData.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								okex/ws/wInterface/IReqData.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
package wInterface
 | 
			
		||||
 | 
			
		||||
// 请求数据
 | 
			
		||||
type WSReqData interface {
 | 
			
		||||
	GetType() int
 | 
			
		||||
	Len() int
 | 
			
		||||
	ToString() string
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								okex/ws/wInterface/IRspData.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								okex/ws/wInterface/IRspData.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
package wInterface
 | 
			
		||||
 | 
			
		||||
// 返回数据
 | 
			
		||||
type WSRspData interface {
 | 
			
		||||
	MsgType() int
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										147
									
								
								okex/ws/ws_AddBookedDataHook_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								okex/ws/ws_AddBookedDataHook_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,147 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
// HOW TO RUN
 | 
			
		||||
// go test ws_cli.go ws_op.go ws_contants.go utils.go ws_pub_channel.go  ws_pub_channel_test.go ws_priv_channel.go  ws_priv_channel_test.go ws_jrpc.go  ws_jrpc_test.go  ws_test_AddBookedDataHook.go -v
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
	. "v5sdk_go/ws/wImpl"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	TRADE_ACCOUNT = iota
 | 
			
		||||
	ISOLATE_ACCOUNT
 | 
			
		||||
	CROSS_ACCOUNT
 | 
			
		||||
	CROSS_ACCOUNT_B
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	log.SetFlags(log.LstdFlags | log.Llongfile)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func prework() *WsClient {
 | 
			
		||||
	ep := "wss://wsaws.okex.com:8443/ws/v5/private"
 | 
			
		||||
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err, ep)
 | 
			
		||||
	}
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
func prework_pri(t int) *WsClient {
 | 
			
		||||
	// 模拟环境
 | 
			
		||||
	ep := "wss://wsaws.okex.com:8443/ws/v5/private"
 | 
			
		||||
	var apikey, passphrase, secretKey string
 | 
			
		||||
	// 把账号密码写这里
 | 
			
		||||
	switch t {
 | 
			
		||||
	case TRADE_ACCOUNT:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	case ISOLATE_ACCOUNT:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	case CROSS_ACCOUNT:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	case CROSS_ACCOUNT_B:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var res bool
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.Login(apikey, secretKey, passphrase)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("登录成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		log.Fatal("登录失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(apikey, secretKey, passphrase)
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAddBookedDataHook(t *testing.T) {
 | 
			
		||||
	var r *WsClient
 | 
			
		||||
 | 
			
		||||
	/*订阅私有频道*/
 | 
			
		||||
	{
 | 
			
		||||
		r = prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
		var res bool
 | 
			
		||||
		var err error
 | 
			
		||||
 | 
			
		||||
		r.AddBookMsgHook(func(ts time.Time, data MsgData) error {
 | 
			
		||||
			// 添加你的方法
 | 
			
		||||
			fmt.Println("这是自定义AddBookMsgHook")
 | 
			
		||||
			fmt.Println("当前数据是", data)
 | 
			
		||||
			return nil
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		param := map[string]string{}
 | 
			
		||||
		param["channel"] = "account"
 | 
			
		||||
		param["ccy"] = "BTC"
 | 
			
		||||
 | 
			
		||||
		res, _, err = r.Subscribe(param)
 | 
			
		||||
		if res {
 | 
			
		||||
			fmt.Println("订阅成功!")
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Println("订阅失败!", err)
 | 
			
		||||
			t.Fatal("订阅失败!", err)
 | 
			
		||||
			//return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		time.Sleep(100 * time.Second)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//订阅公共频道
 | 
			
		||||
	{
 | 
			
		||||
		r = prework()
 | 
			
		||||
		var res bool
 | 
			
		||||
		var err error
 | 
			
		||||
 | 
			
		||||
		// r.AddBookMsgHook(func(ts time.Time, data MsgData) error {
 | 
			
		||||
		// 添加你的方法
 | 
			
		||||
		// fmt.Println("这是公共自定义AddBookMsgHook")
 | 
			
		||||
		// fmt.Println("当前数据是", data)
 | 
			
		||||
		// return nil
 | 
			
		||||
		// })
 | 
			
		||||
 | 
			
		||||
		param := map[string]string{}
 | 
			
		||||
		param["channel"] = "instruments"
 | 
			
		||||
		param["instType"] = "FUTURES"
 | 
			
		||||
 | 
			
		||||
		res, _, err = r.Subscribe(param)
 | 
			
		||||
		if res {
 | 
			
		||||
			fmt.Println("订阅成功!")
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Println("订阅失败!", err)
 | 
			
		||||
			t.Fatal("订阅失败!", err)
 | 
			
		||||
			//return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		select {}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										725
									
								
								okex/ws/ws_cli.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										725
									
								
								okex/ws/ws_cli.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,725 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"runtime/debug"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
	. "v5sdk_go/config"
 | 
			
		||||
	. "v5sdk_go/utils"
 | 
			
		||||
	. "v5sdk_go/ws/wImpl"
 | 
			
		||||
 | 
			
		||||
	"github.com/gorilla/websocket"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 全局回调函数
 | 
			
		||||
type ReceivedDataCallback func(*Msg) error
 | 
			
		||||
 | 
			
		||||
// 普通订阅推送数据回调函数
 | 
			
		||||
type ReceivedMsgDataCallback func(time.Time, MsgData) error
 | 
			
		||||
 | 
			
		||||
// 深度订阅推送数据回调函数
 | 
			
		||||
type ReceivedDepthDataCallback func(time.Time, DepthData) error
 | 
			
		||||
 | 
			
		||||
// websocket
 | 
			
		||||
type WsClient struct {
 | 
			
		||||
	WsEndPoint string
 | 
			
		||||
	WsApi      *ApiInfo
 | 
			
		||||
	conn       *websocket.Conn
 | 
			
		||||
	sendCh     chan string //发消息队列
 | 
			
		||||
	resCh      chan *Msg   //收消息队列
 | 
			
		||||
 | 
			
		||||
	errCh chan *Msg
 | 
			
		||||
	regCh map[Event]chan *Msg //请求响应队列
 | 
			
		||||
 | 
			
		||||
	quitCh chan struct{}
 | 
			
		||||
	lock   sync.RWMutex
 | 
			
		||||
 | 
			
		||||
	onMessageHook ReceivedDataCallback      //全局消息回调函数
 | 
			
		||||
	onBookMsgHook ReceivedMsgDataCallback   //普通订阅消息回调函数
 | 
			
		||||
	onDepthHook   ReceivedDepthDataCallback //深度订阅消息回调函数
 | 
			
		||||
	OnErrorHook   ReceivedDataCallback      //错误处理回调函数
 | 
			
		||||
 | 
			
		||||
	// 记录深度信息
 | 
			
		||||
	DepthDataList map[string]DepthDetail
 | 
			
		||||
	autoDepthMgr  bool // 深度数据管理(checksum等)
 | 
			
		||||
	DepthDataLock sync.RWMutex
 | 
			
		||||
 | 
			
		||||
	isStarted   bool //防止重复启动和关闭
 | 
			
		||||
	dailTimeout time.Duration
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	服务端响应详细信息
 | 
			
		||||
	Timestamp: 接受到消息的时间
 | 
			
		||||
	Info: 接受到的消息字符串
 | 
			
		||||
*/
 | 
			
		||||
type Msg struct {
 | 
			
		||||
	Timestamp time.Time   `json:"timestamp"`
 | 
			
		||||
	Info      interface{} `json:"info"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *Msg) Print() {
 | 
			
		||||
	fmt.Println("【消息时间】", this.Timestamp.Format("2006-01-02 15:04:05.000"))
 | 
			
		||||
	str, _ := json.Marshal(this.Info)
 | 
			
		||||
	fmt.Println("【消息内容】", string(str))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	订阅结果封装后的消息结构体
 | 
			
		||||
*/
 | 
			
		||||
type ProcessDetail struct {
 | 
			
		||||
	EndPoint string        `json:"endPoint"`
 | 
			
		||||
	ReqInfo  string        `json:"ReqInfo"`  //订阅请求
 | 
			
		||||
	SendTime time.Time     `json:"sendTime"` //发送订阅请求的时间
 | 
			
		||||
	RecvTime time.Time     `json:"recvTime"` //接受到订阅结果的时间
 | 
			
		||||
	UsedTime time.Duration `json:"UsedTime"` //耗时
 | 
			
		||||
	Data     []*Msg        `json:"data"`     //订阅结果数据
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *ProcessDetail) String() string {
 | 
			
		||||
	data, _ := json.Marshal(p)
 | 
			
		||||
	return string(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 创建ws对象
 | 
			
		||||
func NewWsClient(ep string) (r *WsClient, err error) {
 | 
			
		||||
	if ep == "" {
 | 
			
		||||
		err = errors.New("websocket endpoint cannot be null")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r = &WsClient{
 | 
			
		||||
		WsEndPoint: ep,
 | 
			
		||||
		sendCh:     make(chan string),
 | 
			
		||||
		resCh:      make(chan *Msg),
 | 
			
		||||
		errCh:      make(chan *Msg),
 | 
			
		||||
		regCh:      make(map[Event]chan *Msg),
 | 
			
		||||
		//cbs:        make(map[Event]ReceivedDataCallback),
 | 
			
		||||
		quitCh:        make(chan struct{}),
 | 
			
		||||
		DepthDataList: make(map[string]DepthDetail),
 | 
			
		||||
		dailTimeout:   time.Second * 10,
 | 
			
		||||
		// 自动深度校验默认开启
 | 
			
		||||
		autoDepthMgr: true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	新增记录深度信息
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) addDepthDataList(key string, dd DepthDetail) error {
 | 
			
		||||
	a.DepthDataLock.Lock()
 | 
			
		||||
	defer a.DepthDataLock.Unlock()
 | 
			
		||||
	a.DepthDataList[key] = dd
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	更新记录深度信息(如果没有记录不会更新成功)
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) updateDepthDataList(key string, dd DepthDetail) error {
 | 
			
		||||
	a.DepthDataLock.Lock()
 | 
			
		||||
	defer a.DepthDataLock.Unlock()
 | 
			
		||||
	if _, ok := a.DepthDataList[key]; !ok {
 | 
			
		||||
		return errors.New("更新失败!未发现记录" + key)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a.DepthDataList[key] = dd
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	删除记录深度信息
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) deleteDepthDataList(key string) error {
 | 
			
		||||
	a.DepthDataLock.Lock()
 | 
			
		||||
	defer a.DepthDataLock.Unlock()
 | 
			
		||||
	delete(a.DepthDataList, key)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	设置是否自动深度管理,开启 true,关闭 false
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) EnableAutoDepthMgr(b bool) error {
 | 
			
		||||
	a.DepthDataLock.Lock()
 | 
			
		||||
	defer a.DepthDataLock.Unlock()
 | 
			
		||||
 | 
			
		||||
	if len(a.DepthDataList) != 0 {
 | 
			
		||||
		err := errors.New("当前有深度数据处于订阅中")
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a.autoDepthMgr = b
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	获取当前的深度快照信息(合并后的)
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) GetSnapshotByChannel(data DepthData) (snapshot *DepthDetail, err error) {
 | 
			
		||||
	key, err := json.Marshal(data.Arg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	a.DepthDataLock.Lock()
 | 
			
		||||
	defer a.DepthDataLock.Unlock()
 | 
			
		||||
	val, ok := a.DepthDataList[string(key)]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	snapshot = new(DepthDetail)
 | 
			
		||||
	raw, err := json.Marshal(val)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	err = json.Unmarshal(raw, &snapshot)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 设置dial超时时间
 | 
			
		||||
func (a *WsClient) SetDailTimeout(tm time.Duration) {
 | 
			
		||||
	a.dailTimeout = tm
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 非阻塞启动
 | 
			
		||||
func (a *WsClient) Start() error {
 | 
			
		||||
	a.lock.RLock()
 | 
			
		||||
	if a.isStarted {
 | 
			
		||||
		a.lock.RUnlock()
 | 
			
		||||
		fmt.Println("ws已经启动")
 | 
			
		||||
		return nil
 | 
			
		||||
	} else {
 | 
			
		||||
		a.lock.RUnlock()
 | 
			
		||||
		a.lock.Lock()
 | 
			
		||||
		defer a.lock.Unlock()
 | 
			
		||||
		// 增加超时处理
 | 
			
		||||
		done := make(chan struct{})
 | 
			
		||||
		ctx, cancel := context.WithTimeout(context.Background(), a.dailTimeout)
 | 
			
		||||
		defer cancel()
 | 
			
		||||
		go func() {
 | 
			
		||||
			defer func() {
 | 
			
		||||
				close(done)
 | 
			
		||||
			}()
 | 
			
		||||
			var c *websocket.Conn
 | 
			
		||||
			fmt.Println("a.WsEndPoint: ", a.WsEndPoint)
 | 
			
		||||
			c, _, err := websocket.DefaultDialer.Dial(a.WsEndPoint, nil)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				err = errors.New("dial error:" + err.Error())
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			a.conn = c
 | 
			
		||||
 | 
			
		||||
		}()
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ctx.Done():
 | 
			
		||||
			err := errors.New("连接超时退出!")
 | 
			
		||||
			return err
 | 
			
		||||
		case <-done:
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
		//TODO 自定义的推送消息回调回来试试放在这里
 | 
			
		||||
		go a.receive()
 | 
			
		||||
		go a.work()
 | 
			
		||||
		a.isStarted = true
 | 
			
		||||
		log.Println("客户端已启动!", a.WsEndPoint)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 客户端退出消息channel
 | 
			
		||||
func (a *WsClient) IsQuit() <-chan struct{} {
 | 
			
		||||
	return a.quitCh
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *WsClient) work() {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		a.Stop()
 | 
			
		||||
		err := recover()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Printf("work End. Recover msg: %+v", a)
 | 
			
		||||
			debug.PrintStack()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	ticker := time.NewTicker(10 * time.Second)
 | 
			
		||||
	defer ticker.Stop()
 | 
			
		||||
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ticker.C: // 保持心跳
 | 
			
		||||
			// go a.Ping(1000)
 | 
			
		||||
			go func() {
 | 
			
		||||
				_, _, err := a.Ping(1000)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					fmt.Println("心跳检测失败!", err)
 | 
			
		||||
					a.Stop()
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			}()
 | 
			
		||||
 | 
			
		||||
		case <-a.quitCh: // 保持心跳
 | 
			
		||||
			return
 | 
			
		||||
		case data, ok := <-a.resCh: //接收到服务端发来的消息
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			//log.Println("接收到来自resCh的消息:", data)
 | 
			
		||||
			if a.onMessageHook != nil {
 | 
			
		||||
				err := a.onMessageHook(data)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Println("执行onMessageHook函数错误!", err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		case errMsg, ok := <-a.errCh: //错误处理
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if a.OnErrorHook != nil {
 | 
			
		||||
				err := a.OnErrorHook(errMsg)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Println("执行OnErrorHook函数错误!", err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		case req, ok := <-a.sendCh: //从发送队列中取出数据发送到服务端
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			//log.Println("接收到来自req的消息:", req)
 | 
			
		||||
			err := a.conn.WriteMessage(websocket.TextMessage, []byte(req))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Printf("发送请求失败: %s\n", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			log.Printf("[发送请求] %v\n", req)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	处理接受到的消息
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) receive() {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		a.Stop()
 | 
			
		||||
		err := recover()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Printf("Receive End. Recover msg: %+v", a)
 | 
			
		||||
			debug.PrintStack()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	for {
 | 
			
		||||
		messageType, message, err := a.conn.ReadMessage()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if a.isStarted {
 | 
			
		||||
				log.Println("receive message error!" + err.Error())
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		txtMsg := message
 | 
			
		||||
		switch messageType {
 | 
			
		||||
		case websocket.TextMessage:
 | 
			
		||||
		case websocket.BinaryMessage:
 | 
			
		||||
			txtMsg, err = GzipDecode(message)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Println("解压失败!")
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		log.Println("[收到消息]", string(txtMsg))
 | 
			
		||||
 | 
			
		||||
		//发送结果到默认消息处理通道
 | 
			
		||||
 | 
			
		||||
		timestamp := time.Now()
 | 
			
		||||
		msg := &Msg{Timestamp: timestamp, Info: string(txtMsg)}
 | 
			
		||||
 | 
			
		||||
		a.resCh <- msg
 | 
			
		||||
 | 
			
		||||
		evt, data, err := a.parseMessage(txtMsg)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Println("解析消息失败!", err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//log.Println("解析消息成功!消息类型 =", evt)
 | 
			
		||||
 | 
			
		||||
		a.lock.RLock()
 | 
			
		||||
		ch, ok := a.regCh[evt]
 | 
			
		||||
		a.lock.RUnlock()
 | 
			
		||||
		if !ok {
 | 
			
		||||
			//只有推送消息才会主动创建通道和消费队列
 | 
			
		||||
			if evt == EVENT_BOOKED_DATA || evt == EVENT_DEPTH_DATA {
 | 
			
		||||
				//log.Println("channel不存在!event:", evt)
 | 
			
		||||
				//a.lock.RUnlock()
 | 
			
		||||
				a.lock.Lock()
 | 
			
		||||
				a.regCh[evt] = make(chan *Msg)
 | 
			
		||||
				ch = a.regCh[evt]
 | 
			
		||||
				a.lock.Unlock()
 | 
			
		||||
 | 
			
		||||
				//log.Println("创建", evt, "通道")
 | 
			
		||||
 | 
			
		||||
				// 创建消费队列
 | 
			
		||||
				go func(evt Event) {
 | 
			
		||||
					//log.Println("创建goroutine  evt:", evt)
 | 
			
		||||
 | 
			
		||||
					for msg := range a.regCh[evt] {
 | 
			
		||||
						//log.Println(msg)
 | 
			
		||||
						// msg.Print()
 | 
			
		||||
						switch evt {
 | 
			
		||||
						// 处理普通推送数据
 | 
			
		||||
						case EVENT_BOOKED_DATA:
 | 
			
		||||
							fn := a.onBookMsgHook
 | 
			
		||||
							if fn != nil {
 | 
			
		||||
								err = fn(msg.Timestamp, msg.Info.(MsgData))
 | 
			
		||||
								if err != nil {
 | 
			
		||||
									log.Println("订阅数据回调函数执行失败!", err)
 | 
			
		||||
								}
 | 
			
		||||
								//log.Println("函数执行成功!", err)
 | 
			
		||||
							}
 | 
			
		||||
						// 处理深度推送数据
 | 
			
		||||
						case EVENT_DEPTH_DATA:
 | 
			
		||||
							fn := a.onDepthHook
 | 
			
		||||
 | 
			
		||||
							depData := msg.Info.(DepthData)
 | 
			
		||||
 | 
			
		||||
							// 开启深度数据管理功能的,会合并深度数据
 | 
			
		||||
							if a.autoDepthMgr {
 | 
			
		||||
								a.MergeDepth(depData)
 | 
			
		||||
							}
 | 
			
		||||
 | 
			
		||||
							// 运行用户定义回调函数
 | 
			
		||||
							if fn != nil {
 | 
			
		||||
								err = fn(msg.Timestamp, msg.Info.(DepthData))
 | 
			
		||||
								if err != nil {
 | 
			
		||||
									log.Println("深度回调函数执行失败!", err)
 | 
			
		||||
								}
 | 
			
		||||
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
					}
 | 
			
		||||
					//log.Println("退出goroutine  evt:", evt)
 | 
			
		||||
				}(evt)
 | 
			
		||||
 | 
			
		||||
				//continue
 | 
			
		||||
			} else {
 | 
			
		||||
				//log.Println("程序异常!通道已关闭", evt)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//log.Println(evt,"事件已注册",ch)
 | 
			
		||||
 | 
			
		||||
		ctx := context.Background()
 | 
			
		||||
		ctx, cancel := context.WithTimeout(ctx, time.Millisecond*1000)
 | 
			
		||||
		defer cancel()
 | 
			
		||||
		select {
 | 
			
		||||
		/*
 | 
			
		||||
			丢弃消息容易引发数据处理处理错误
 | 
			
		||||
		*/
 | 
			
		||||
		// case <-ctx.Done():
 | 
			
		||||
		// 	log.Println("等待超时,消息丢弃 - ", data)
 | 
			
		||||
		case ch <- &Msg{Timestamp: timestamp, Info: data}:
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	开启了深度数据管理功能后,系统会自动合并深度信息
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) MergeDepth(depData DepthData) (err error) {
 | 
			
		||||
	if !a.autoDepthMgr {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	key, err := json.Marshal(depData.Arg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = errors.New("数据错误")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// books5 不需要做checksum
 | 
			
		||||
	if depData.Arg["channel"] == "books5" {
 | 
			
		||||
		a.addDepthDataList(string(key), depData.Data[0])
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if depData.Action == "snapshot" {
 | 
			
		||||
 | 
			
		||||
		_, err = depData.CheckSum(nil)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Println("校验失败", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		a.addDepthDataList(string(key), depData.Data[0])
 | 
			
		||||
 | 
			
		||||
	} else {
 | 
			
		||||
 | 
			
		||||
		var newSnapshot *DepthDetail
 | 
			
		||||
		a.DepthDataLock.RLock()
 | 
			
		||||
		oldSnapshot, ok := a.DepthDataList[string(key)]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			log.Println("深度数据错误,全量数据未发现!")
 | 
			
		||||
			err = errors.New("数据错误")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		a.DepthDataLock.RUnlock()
 | 
			
		||||
		newSnapshot, err = depData.CheckSum(&oldSnapshot)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Println("深度校验失败", err)
 | 
			
		||||
			err = errors.New("校验失败")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		a.updateDepthDataList(string(key), *newSnapshot)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	通过ErrorCode判断事件类型
 | 
			
		||||
*/
 | 
			
		||||
func GetInfoFromErrCode(data ErrData) Event {
 | 
			
		||||
	switch data.Code {
 | 
			
		||||
	case "60001":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60002":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60003":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60004":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60005":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60006":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60007":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60008":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60009":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60010":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60011":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return EVENT_UNKNOWN
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
   从error返回中解析出对应的channel
 | 
			
		||||
   error信息样例
 | 
			
		||||
 {"event":"error","msg":"channel:index-tickers,instId:BTC-USDT1 doesn't exist","code":"60018"}
 | 
			
		||||
*/
 | 
			
		||||
func GetInfoFromErrMsg(raw string) (channel string) {
 | 
			
		||||
	reg := regexp.MustCompile(`channel:(.*?),`)
 | 
			
		||||
	if reg == nil {
 | 
			
		||||
		fmt.Println("MustCompile err")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	//提取关键信息
 | 
			
		||||
	result := reg.FindAllStringSubmatch(raw, -1)
 | 
			
		||||
	for _, text := range result {
 | 
			
		||||
		channel = text[1]
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	解析消息类型
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) parseMessage(raw []byte) (evt Event, data interface{}, err error) {
 | 
			
		||||
	evt = EVENT_UNKNOWN
 | 
			
		||||
	//log.Println("解析消息")
 | 
			
		||||
	//log.Println("消息内容:", string(raw))
 | 
			
		||||
	if string(raw) == "pong" {
 | 
			
		||||
		evt = EVENT_PING
 | 
			
		||||
		data = raw
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	//log.Println(0, evt)
 | 
			
		||||
	var rspData = RspData{}
 | 
			
		||||
	err = json.Unmarshal(raw, &rspData)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		op := rspData.Event
 | 
			
		||||
		if op == OP_SUBSCRIBE || op == OP_UNSUBSCRIBE {
 | 
			
		||||
			channel := rspData.Arg["channel"]
 | 
			
		||||
			evt = GetEventId(channel)
 | 
			
		||||
			data = rspData
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//log.Println("ErrData")
 | 
			
		||||
	var errData = ErrData{}
 | 
			
		||||
	err = json.Unmarshal(raw, &errData)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		op := errData.Event
 | 
			
		||||
		switch op {
 | 
			
		||||
		case OP_LOGIN:
 | 
			
		||||
			evt = EVENT_LOGIN
 | 
			
		||||
			data = errData
 | 
			
		||||
			//log.Println(3, evt)
 | 
			
		||||
			return
 | 
			
		||||
		case OP_ERROR:
 | 
			
		||||
			data = errData
 | 
			
		||||
			// TODO:细化报错对应的事件判断
 | 
			
		||||
 | 
			
		||||
			//尝试从msg字段中解析对应的事件类型
 | 
			
		||||
			evt = GetInfoFromErrCode(errData)
 | 
			
		||||
			if evt != EVENT_UNKNOWN {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			evt = GetEventId(GetInfoFromErrMsg(errData.Msg))
 | 
			
		||||
			if evt == EVENT_UNKNOWN {
 | 
			
		||||
				evt = EVENT_ERROR
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		//log.Println(5, evt)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//log.Println("JRPCRsp")
 | 
			
		||||
	var jRPCRsp = JRPCRsp{}
 | 
			
		||||
	err = json.Unmarshal(raw, &jRPCRsp)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		data = jRPCRsp
 | 
			
		||||
		evt = GetEventId(jRPCRsp.Op)
 | 
			
		||||
		if evt != EVENT_UNKNOWN {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var depthData = DepthData{}
 | 
			
		||||
	err = json.Unmarshal(raw, &depthData)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		evt = EVENT_DEPTH_DATA
 | 
			
		||||
		data = depthData
 | 
			
		||||
		//log.Println("-->>EVENT_DEPTH_DATA", evt)
 | 
			
		||||
		//log.Println(evt, data)
 | 
			
		||||
		//log.Println(6)
 | 
			
		||||
		switch depthData.Arg["channel"] {
 | 
			
		||||
		case "books":
 | 
			
		||||
			return
 | 
			
		||||
		case "books-l2-tbt":
 | 
			
		||||
			return
 | 
			
		||||
		case "books50-l2-tbt":
 | 
			
		||||
			return
 | 
			
		||||
		case "books5":
 | 
			
		||||
			return
 | 
			
		||||
		default:
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//log.Println("MsgData")
 | 
			
		||||
	var msgData = MsgData{}
 | 
			
		||||
	err = json.Unmarshal(raw, &msgData)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		evt = EVENT_BOOKED_DATA
 | 
			
		||||
		data = msgData
 | 
			
		||||
		//log.Println("-->>EVENT_BOOK_DATA", evt)
 | 
			
		||||
		//log.Println(evt, data)
 | 
			
		||||
		//log.Println(6)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	evt = EVENT_UNKNOWN
 | 
			
		||||
	err = errors.New("message unknown")
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *WsClient) Stop() error {
 | 
			
		||||
 | 
			
		||||
	a.lock.Lock()
 | 
			
		||||
	defer a.lock.Unlock()
 | 
			
		||||
	if !a.isStarted {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a.isStarted = false
 | 
			
		||||
 | 
			
		||||
	if a.conn != nil {
 | 
			
		||||
		a.conn.Close()
 | 
			
		||||
	}
 | 
			
		||||
	close(a.errCh)
 | 
			
		||||
	close(a.sendCh)
 | 
			
		||||
	close(a.resCh)
 | 
			
		||||
	close(a.quitCh)
 | 
			
		||||
 | 
			
		||||
	for _, ch := range a.regCh {
 | 
			
		||||
		close(ch)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Println("ws客户端退出!")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	添加全局消息处理的回调函数
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) AddMessageHook(fn ReceivedDataCallback) error {
 | 
			
		||||
	a.onMessageHook = fn
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	添加订阅消息处理的回调函数
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) AddBookMsgHook(fn ReceivedMsgDataCallback) error {
 | 
			
		||||
	a.onBookMsgHook = fn
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	添加深度消息处理的回调函数
 | 
			
		||||
	例如:
 | 
			
		||||
	cli.AddDepthHook(func(ts time.Time, data DepthData) error { return nil })
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) AddDepthHook(fn ReceivedDepthDataCallback) error {
 | 
			
		||||
	a.onDepthHook = fn
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	添加错误类型消息处理的回调函数
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) AddErrMsgHook(fn ReceivedDataCallback) error {
 | 
			
		||||
	a.OnErrorHook = fn
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	判断连接是否存活
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) IsAlive() bool {
 | 
			
		||||
	res := false
 | 
			
		||||
	if a.conn == nil {
 | 
			
		||||
		return res
 | 
			
		||||
	}
 | 
			
		||||
	res, _, _ = a.Ping(500)
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								okex/ws/ws_contants.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								okex/ws/ws_contants.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
//操作符
 | 
			
		||||
const (
 | 
			
		||||
	OP_LOGIN       = "login"
 | 
			
		||||
	OP_ERROR       = "error"
 | 
			
		||||
	OP_SUBSCRIBE   = "subscribe"
 | 
			
		||||
	OP_UNSUBSCRIBE = "unsubscribe"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// instrument Type
 | 
			
		||||
const (
 | 
			
		||||
	SPOT    = "SPOT"
 | 
			
		||||
	SWAP    = "SWAP"
 | 
			
		||||
	FUTURES = "FUTURES"
 | 
			
		||||
	OPTION  = "OPTION"
 | 
			
		||||
	ANY     = "ANY"
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										157
									
								
								okex/ws/ws_jrpc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								okex/ws/ws_jrpc.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,157 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"log"
 | 
			
		||||
	"time"
 | 
			
		||||
	. "v5sdk_go/ws/wImpl"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	websocket交易 通用请求
 | 
			
		||||
	参数说明:
 | 
			
		||||
		evtId:封装的事件类型
 | 
			
		||||
		id: 请求ID
 | 
			
		||||
		op: 请求参数op
 | 
			
		||||
		params: 请求参数
 | 
			
		||||
		timeOut: 超时时间
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) jrpcReq(evtId Event, op string, id string, params []map[string]interface{}, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
	res = true
 | 
			
		||||
	tm := 5000
 | 
			
		||||
	if len(timeOut) != 0 {
 | 
			
		||||
		tm = timeOut[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req := &JRPCReq{
 | 
			
		||||
		Id:   id,
 | 
			
		||||
		Op:   op,
 | 
			
		||||
		Args: params,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	detail = &ProcessDetail{
 | 
			
		||||
		EndPoint: a.WsEndPoint,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	ctx, cancel := context.WithTimeout(ctx, time.Duration(tm)*time.Millisecond)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	ctx = context.WithValue(ctx, "detail", detail)
 | 
			
		||||
 | 
			
		||||
	msg, err := a.process(ctx, evtId, req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		log.Println("处理请求失败!", req, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	detail.Data = msg
 | 
			
		||||
 | 
			
		||||
	res, err = checkResult(req, msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	单个下单
 | 
			
		||||
	参数说明:
 | 
			
		||||
		id: 请求ID
 | 
			
		||||
		params: 请求参数
 | 
			
		||||
		timeOut: 超时时间
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PlaceOrder(id string, param map[string]interface{}, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
	op := "order"
 | 
			
		||||
	evtId := EVENT_PLACE_ORDER
 | 
			
		||||
 | 
			
		||||
	var args []map[string]interface{}
 | 
			
		||||
	args = append(args, param)
 | 
			
		||||
 | 
			
		||||
	return a.jrpcReq(evtId, op, id, args, timeOut...)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	批量下单
 | 
			
		||||
	参数说明:
 | 
			
		||||
		id: 请求ID
 | 
			
		||||
		params: 请求参数
 | 
			
		||||
		timeOut: 超时时间
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) BatchPlaceOrders(id string, params []map[string]interface{}, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
 | 
			
		||||
	op := "batch-orders"
 | 
			
		||||
	evtId := EVENT_PLACE_BATCH_ORDERS
 | 
			
		||||
	return a.jrpcReq(evtId, op, id, params, timeOut...)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	单个撤单
 | 
			
		||||
	参数说明:
 | 
			
		||||
		id: 请求ID
 | 
			
		||||
		params: 请求参数
 | 
			
		||||
		timeOut: 超时时间
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) CancelOrder(id string, param map[string]interface{}, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
 | 
			
		||||
	op := "cancel-order"
 | 
			
		||||
	evtId := EVENT_CANCEL_ORDER
 | 
			
		||||
 | 
			
		||||
	var args []map[string]interface{}
 | 
			
		||||
	args = append(args, param)
 | 
			
		||||
 | 
			
		||||
	return a.jrpcReq(evtId, op, id, args, timeOut...)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	批量撤单
 | 
			
		||||
	参数说明:
 | 
			
		||||
		id: 请求ID
 | 
			
		||||
		params: 请求参数
 | 
			
		||||
		timeOut: 超时时间
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) BatchCancelOrders(id string, params []map[string]interface{}, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
 | 
			
		||||
	op := "batch-cancel-orders"
 | 
			
		||||
	evtId := EVENT_CANCEL_BATCH_ORDERS
 | 
			
		||||
	return a.jrpcReq(evtId, op, id, params, timeOut...)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	单个改单
 | 
			
		||||
	参数说明:
 | 
			
		||||
		id: 请求ID
 | 
			
		||||
		params: 请求参数
 | 
			
		||||
		timeOut: 超时时间
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) AmendOrder(id string, param map[string]interface{}, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
 | 
			
		||||
	op := "amend-order"
 | 
			
		||||
	evtId := EVENT_AMEND_ORDER
 | 
			
		||||
 | 
			
		||||
	var args []map[string]interface{}
 | 
			
		||||
	args = append(args, param)
 | 
			
		||||
 | 
			
		||||
	return a.jrpcReq(evtId, op, id, args, timeOut...)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	批量改单
 | 
			
		||||
	参数说明:
 | 
			
		||||
		id: 请求ID
 | 
			
		||||
		params: 请求参数
 | 
			
		||||
		timeOut: 超时时间
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) BatchAmendOrders(id string, params []map[string]interface{}, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
 | 
			
		||||
	op := "batch-amend-orders"
 | 
			
		||||
	evtId := EVENT_AMEND_BATCH_ORDERS
 | 
			
		||||
	return a.jrpcReq(evtId, op, id, params, timeOut...)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										186
									
								
								okex/ws/ws_jrpc_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								okex/ws/ws_jrpc_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,186 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
	. "v5sdk_go/ws/wImpl"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func PrintDetail(d *ProcessDetail) {
 | 
			
		||||
	fmt.Println("[详细信息]")
 | 
			
		||||
	fmt.Println("请求地址:", d.EndPoint)
 | 
			
		||||
	fmt.Println("请求内容:", d.ReqInfo)
 | 
			
		||||
	fmt.Println("发送时间:", d.SendTime.Format("2006-01-02 15:04:05.000"))
 | 
			
		||||
	fmt.Println("响应时间:", d.RecvTime.Format("2006-01-02 15:04:05.000"))
 | 
			
		||||
	fmt.Println("耗时:", d.UsedTime.String())
 | 
			
		||||
	fmt.Printf("接受到 %v 条消息:\n", len(d.Data))
 | 
			
		||||
	for _, v := range d.Data {
 | 
			
		||||
		fmt.Printf("[%v] %v\n", v.Timestamp.Format("2006-01-02 15:04:05.000"), v.Info)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *WsClient) makeOrder(instId string, tdMode string, side string, ordType string, px string, sz string) (orderId string, err error) {
 | 
			
		||||
 | 
			
		||||
	var res bool
 | 
			
		||||
	var data *ProcessDetail
 | 
			
		||||
 | 
			
		||||
	param := map[string]interface{}{}
 | 
			
		||||
	param["instId"] = instId
 | 
			
		||||
	param["tdMode"] = tdMode
 | 
			
		||||
	param["side"] = side
 | 
			
		||||
	param["ordType"] = ordType
 | 
			
		||||
	if px != "" {
 | 
			
		||||
		param["px"] = px
 | 
			
		||||
	}
 | 
			
		||||
	param["sz"] = sz
 | 
			
		||||
 | 
			
		||||
	res, data, err = r.PlaceOrder("0011", param)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if res && len(data.Data) == 1 {
 | 
			
		||||
		rsp := data.Data[0].Info.(JRPCRsp)
 | 
			
		||||
		if len(rsp.Data) == 1 {
 | 
			
		||||
			val, ok := rsp.Data[0]["ordId"]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			orderId = val.(string)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	单个下单
 | 
			
		||||
*/
 | 
			
		||||
// func TestPlaceOrder(t *testing.T) {
 | 
			
		||||
// r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
// r := prework_pri(TRADE_ACCOUNT)
 | 
			
		||||
// var res bool
 | 
			
		||||
// var err error
 | 
			
		||||
// var data *ProcessDetail
 | 
			
		||||
//
 | 
			
		||||
// start := time.Now()
 | 
			
		||||
// param := map[string]interface{}{}
 | 
			
		||||
// param["instId"] = "BTC-USDT"
 | 
			
		||||
// param["tdMode"] = "cash"
 | 
			
		||||
// param["side"] = "buy"
 | 
			
		||||
// param["ordType"] = "market"
 | 
			
		||||
// param["px"] = "1"
 | 
			
		||||
// param["sz"] = "200"
 | 
			
		||||
//
 | 
			
		||||
// res, data, err = r.PlaceOrder("0011", param)
 | 
			
		||||
// if res {
 | 
			
		||||
// usedTime := time.Since(start)
 | 
			
		||||
// fmt.Println("下单成功!", usedTime.String())
 | 
			
		||||
// PrintDetail(data)
 | 
			
		||||
// } else {
 | 
			
		||||
// usedTime := time.Since(start)
 | 
			
		||||
// fmt.Println("下单失败!", usedTime.String(), err)
 | 
			
		||||
// }
 | 
			
		||||
//
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	批量下单
 | 
			
		||||
*/
 | 
			
		||||
// func TestPlaceBatchOrder(t *testing.T) {
 | 
			
		||||
// r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
// var res bool
 | 
			
		||||
// var err error
 | 
			
		||||
// var data *ProcessDetail
 | 
			
		||||
//
 | 
			
		||||
// start := time.Now()
 | 
			
		||||
// var params []map[string]interface{}
 | 
			
		||||
// param := map[string]interface{}{}
 | 
			
		||||
// param["instId"] = "BTC-USDT"
 | 
			
		||||
// param["tdMode"] = "cash"
 | 
			
		||||
// param["side"] = "sell"
 | 
			
		||||
// param["ordType"] = "market"
 | 
			
		||||
// param["sz"] = "0.001"
 | 
			
		||||
// params = append(params, param)
 | 
			
		||||
// param = map[string]interface{}{}
 | 
			
		||||
// param["instId"] = "BTC-USDT"
 | 
			
		||||
// param["tdMode"] = "cash"
 | 
			
		||||
// param["side"] = "buy"
 | 
			
		||||
// param["ordType"] = "market"
 | 
			
		||||
// param["sz"] = "100"
 | 
			
		||||
// params = append(params, param)
 | 
			
		||||
// res, data, err = r.BatchPlaceOrders("001", params)
 | 
			
		||||
// usedTime := time.Since(start)
 | 
			
		||||
// if err != nil {
 | 
			
		||||
// fmt.Println("下单失败!", err, usedTime.String())
 | 
			
		||||
// t.Fail()
 | 
			
		||||
// }
 | 
			
		||||
// if res {
 | 
			
		||||
// fmt.Println("下单成功!", usedTime.String())
 | 
			
		||||
// PrintDetail(data)
 | 
			
		||||
// } else {
 | 
			
		||||
//
 | 
			
		||||
// fmt.Println("下单失败!", usedTime.String())
 | 
			
		||||
// t.Fail()
 | 
			
		||||
// }
 | 
			
		||||
//
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	撤销订单
 | 
			
		||||
*/
 | 
			
		||||
// func TestCancelOrder(t *testing.T) {
 | 
			
		||||
// r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
//
 | 
			
		||||
// 用户自定义limit限价价格
 | 
			
		||||
// ordId, _ := r.makeOrder("BTC-USDT", "cash", "sell", "limit", "57000", "0.01")
 | 
			
		||||
// if ordId == "" {
 | 
			
		||||
// t.Fatal()
 | 
			
		||||
// }
 | 
			
		||||
//
 | 
			
		||||
// t.Log("生成挂单:orderId=", ordId)
 | 
			
		||||
//
 | 
			
		||||
// param := map[string]interface{}{}
 | 
			
		||||
// param["instId"] = "BTC-USDT"
 | 
			
		||||
// param["ordId"] = ordId
 | 
			
		||||
// start := time.Now()
 | 
			
		||||
// res, _, _ := r.CancelOrder("1", param)
 | 
			
		||||
// if res {
 | 
			
		||||
// usedTime := time.Since(start)
 | 
			
		||||
// fmt.Println("撤单成功!", usedTime.String())
 | 
			
		||||
// } else {
 | 
			
		||||
// t.Fatal("撤单失败!")
 | 
			
		||||
// }
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	修改订单
 | 
			
		||||
*/
 | 
			
		||||
func TestAmendlOrder(t *testing.T) {
 | 
			
		||||
	r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
 | 
			
		||||
	// 用户自定义limit限价价格
 | 
			
		||||
	ordId, _ := r.makeOrder("BTC-USDT", "cash", "sell", "limit", "57000", "0.01")
 | 
			
		||||
	if ordId == "" {
 | 
			
		||||
		t.Fatal()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.Log("生成挂单:orderId=", ordId)
 | 
			
		||||
 | 
			
		||||
	param := map[string]interface{}{}
 | 
			
		||||
	param["instId"] = "BTC-USDT"
 | 
			
		||||
	param["ordId"] = ordId
 | 
			
		||||
	// 调整修改订单的参数
 | 
			
		||||
	//param["newSz"] = "0.02"
 | 
			
		||||
	param["newPx"] = "57001"
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, _ := r.AmendOrder("1", param)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("修改订单成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		t.Fatal("修改订单失败!")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								okex/ws/ws_middleware.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								okex/ws/ws_middleware.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import "fmt"
 | 
			
		||||
 | 
			
		||||
type ReqFunc func(...interface{}) (res bool, msg *Msg, err error)
 | 
			
		||||
type Decorator func(ReqFunc) ReqFunc
 | 
			
		||||
 | 
			
		||||
func handler(h ReqFunc, decors ...Decorator) ReqFunc {
 | 
			
		||||
	for i := range decors {
 | 
			
		||||
		d := decors[len(decors)-1-i]
 | 
			
		||||
		h = d(h)
 | 
			
		||||
	}
 | 
			
		||||
	return h
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func preprocess() (res bool, msg *Msg, err error) {
 | 
			
		||||
	fmt.Println("preprocess")
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										532
									
								
								okex/ws/ws_op.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										532
									
								
								okex/ws/ws_op.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,532 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"log"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
	. "v5sdk_go/config"
 | 
			
		||||
	"v5sdk_go/rest"
 | 
			
		||||
	. "v5sdk_go/utils"
 | 
			
		||||
	. "v5sdk_go/ws/wImpl"
 | 
			
		||||
	. "v5sdk_go/ws/wInterface"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	Ping服务端保持心跳。
 | 
			
		||||
	timeOut:超时时间(毫秒),如果不填默认为5000ms
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) Ping(timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
	tm := 5000
 | 
			
		||||
	if len(timeOut) != 0 {
 | 
			
		||||
		tm = timeOut[0]
 | 
			
		||||
	}
 | 
			
		||||
	res = true
 | 
			
		||||
 | 
			
		||||
	detail = &ProcessDetail{
 | 
			
		||||
		EndPoint: a.WsEndPoint,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	ctx, _ = context.WithTimeout(ctx, time.Duration(tm)*time.Millisecond)
 | 
			
		||||
	ctx = context.WithValue(ctx, "detail", detail)
 | 
			
		||||
	msg, err := a.process(ctx, EVENT_PING, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		log.Println("处理请求失败!", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	detail.Data = msg
 | 
			
		||||
 | 
			
		||||
	if len(msg) == 0 {
 | 
			
		||||
		res = false
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	str := string(msg[0].Info.([]byte))
 | 
			
		||||
	if str != "pong" {
 | 
			
		||||
		res = false
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	登录私有频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) Login(apiKey, secKey, passPhrase string, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
 | 
			
		||||
	if apiKey == "" {
 | 
			
		||||
		err = errors.New("ApiKey cannot be null")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if secKey == "" {
 | 
			
		||||
		err = errors.New("SecretKey cannot be null")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if passPhrase == "" {
 | 
			
		||||
		err = errors.New("Passphrase cannot be null")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a.WsApi = &ApiInfo{
 | 
			
		||||
		ApiKey:     apiKey,
 | 
			
		||||
		SecretKey:  secKey,
 | 
			
		||||
		Passphrase: passPhrase,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tm := 5000
 | 
			
		||||
	if len(timeOut) != 0 {
 | 
			
		||||
		tm = timeOut[0]
 | 
			
		||||
	}
 | 
			
		||||
	res = true
 | 
			
		||||
 | 
			
		||||
	timestamp := EpochTime()
 | 
			
		||||
 | 
			
		||||
	preHash := PreHashString(timestamp, rest.GET, "/users/self/verify", "")
 | 
			
		||||
	//fmt.Println("preHash:", preHash)
 | 
			
		||||
	var sign string
 | 
			
		||||
	if sign, err = HmacSha256Base64Signer(preHash, secKey); err != nil {
 | 
			
		||||
		log.Println("处理签名失败!", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	args := map[string]string{}
 | 
			
		||||
	args["apiKey"] = apiKey
 | 
			
		||||
	args["passphrase"] = passPhrase
 | 
			
		||||
	args["timestamp"] = timestamp
 | 
			
		||||
	args["sign"] = sign
 | 
			
		||||
	req := &ReqData{
 | 
			
		||||
		Op:   OP_LOGIN,
 | 
			
		||||
		Args: []map[string]string{args},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	detail = &ProcessDetail{
 | 
			
		||||
		EndPoint: a.WsEndPoint,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	ctx, _ = context.WithTimeout(ctx, time.Duration(tm)*time.Millisecond)
 | 
			
		||||
	ctx = context.WithValue(ctx, "detail", detail)
 | 
			
		||||
 | 
			
		||||
	msg, err := a.process(ctx, EVENT_LOGIN, req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		log.Println("处理请求失败!", req, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	detail.Data = msg
 | 
			
		||||
 | 
			
		||||
	if len(msg) == 0 {
 | 
			
		||||
		res = false
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	info, _ := msg[0].Info.(ErrData)
 | 
			
		||||
 | 
			
		||||
	if info.Code == "0" && info.Event == OP_LOGIN {
 | 
			
		||||
		log.Println("登录成功!")
 | 
			
		||||
	} else {
 | 
			
		||||
		log.Println("登录失败!")
 | 
			
		||||
		res = false
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	等待结果响应
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) waitForResult(e Event, timeOut int) (data interface{}, err error) {
 | 
			
		||||
 | 
			
		||||
	if _, ok := a.regCh[e]; !ok {
 | 
			
		||||
		a.lock.Lock()
 | 
			
		||||
		a.regCh[e] = make(chan *Msg)
 | 
			
		||||
		a.lock.Unlock()
 | 
			
		||||
		//log.Println("注册", e, "事件成功")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a.lock.RLock()
 | 
			
		||||
	defer a.lock.RUnlock()
 | 
			
		||||
	ch := a.regCh[e]
 | 
			
		||||
	//log.Println(e, "等待响应!")
 | 
			
		||||
	select {
 | 
			
		||||
	case <-time.After(time.Duration(timeOut) * time.Millisecond):
 | 
			
		||||
		log.Println(e, "超时未响应!")
 | 
			
		||||
		err = errors.New(e.String() + "超时未响应!")
 | 
			
		||||
		return
 | 
			
		||||
	case data = <-ch:
 | 
			
		||||
		//log.Println(data)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	发送消息到服务端
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) Send(ctx context.Context, op WSReqData) (err error) {
 | 
			
		||||
	select {
 | 
			
		||||
	case <-ctx.Done():
 | 
			
		||||
		log.Println("发生失败退出!")
 | 
			
		||||
		err = errors.New("发送超时退出!")
 | 
			
		||||
	case a.sendCh <- op.ToString():
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *WsClient) process(ctx context.Context, e Event, op WSReqData) (data []*Msg, err error) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		_ = recover()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	var detail *ProcessDetail
 | 
			
		||||
	if val := ctx.Value("detail"); val != nil {
 | 
			
		||||
		detail = val.(*ProcessDetail)
 | 
			
		||||
	} else {
 | 
			
		||||
		detail = &ProcessDetail{
 | 
			
		||||
			EndPoint: a.WsEndPoint,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		//fmt.Println("处理完成,", e.String())
 | 
			
		||||
		detail.UsedTime = detail.RecvTime.Sub(detail.SendTime)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	//查看事件是否被注册
 | 
			
		||||
	if _, ok := a.regCh[e]; !ok {
 | 
			
		||||
		a.lock.Lock()
 | 
			
		||||
		a.regCh[e] = make(chan *Msg)
 | 
			
		||||
		a.lock.Unlock()
 | 
			
		||||
		//log.Println("注册", e, "事件成功")
 | 
			
		||||
	} else {
 | 
			
		||||
		//log.Println("事件", e, "已注册!")
 | 
			
		||||
		err = errors.New("事件" + e.String() + "尚未处理完毕")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//预期请求响应的条数
 | 
			
		||||
	expectCnt := 1
 | 
			
		||||
	if op != nil {
 | 
			
		||||
		expectCnt = op.Len()
 | 
			
		||||
	}
 | 
			
		||||
	recvCnt := 0
 | 
			
		||||
 | 
			
		||||
	//等待完成通知
 | 
			
		||||
	wg := sync.WaitGroup{}
 | 
			
		||||
	wg.Add(1)
 | 
			
		||||
	// 这里要先定义go routine func(){} 是为了在里面订阅channel的内容, 我们知道一个队列要想往里塞东西,必先给他安排一个订阅它的协程
 | 
			
		||||
	go func(ctx context.Context) {
 | 
			
		||||
		defer func() {
 | 
			
		||||
			a.lock.Lock()
 | 
			
		||||
			delete(a.regCh, e)
 | 
			
		||||
			//log.Println("事件已注销!",e)
 | 
			
		||||
			a.lock.Unlock()
 | 
			
		||||
			wg.Done()
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		a.lock.RLock()
 | 
			
		||||
		ch := a.regCh[e] //请求响应队列
 | 
			
		||||
		a.lock.RUnlock()
 | 
			
		||||
 | 
			
		||||
		//log.Println(e, "等待响应!")
 | 
			
		||||
		done := false
 | 
			
		||||
		ok := true
 | 
			
		||||
		for {
 | 
			
		||||
			var item *Msg
 | 
			
		||||
			select {
 | 
			
		||||
			case <-ctx.Done():
 | 
			
		||||
				log.Println(e, "超时未响应!")
 | 
			
		||||
				err = errors.New(e.String() + "超时未响应!")
 | 
			
		||||
				return
 | 
			
		||||
			case item, ok = <-ch:
 | 
			
		||||
				if !ok {
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				detail.RecvTime = time.Now()
 | 
			
		||||
				//log.Println(e, "接受到数据", item)
 | 
			
		||||
				// 这里只是把推送的数据显示出来,并没有做更近一步的处理,后续可以二次开发,在这个位置上进行处理
 | 
			
		||||
				data = append(data, item)
 | 
			
		||||
				recvCnt++
 | 
			
		||||
				//log.Println(data)
 | 
			
		||||
				if recvCnt == expectCnt {
 | 
			
		||||
					done = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if done {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if ok {
 | 
			
		||||
			close(ch)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}(ctx)
 | 
			
		||||
 | 
			
		||||
	//
 | 
			
		||||
	switch e {
 | 
			
		||||
	case EVENT_PING:
 | 
			
		||||
		msg := "ping"
 | 
			
		||||
		detail.ReqInfo = msg
 | 
			
		||||
		a.sendCh <- msg
 | 
			
		||||
		detail.SendTime = time.Now()
 | 
			
		||||
	default:
 | 
			
		||||
		detail.ReqInfo = op.ToString()
 | 
			
		||||
		//这个时候ctx中已经提供了meta信息,用于发送ws请求
 | 
			
		||||
		err = a.Send(ctx, op)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Println("发送[", e, "]消息失败!", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		detail.SendTime = time.Now()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	wg.Wait()
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	根据args请求参数判断请求类型
 | 
			
		||||
	如:{"channel": "account","ccy": "BTC"} 类型为 EVENT_BOOK_ACCOUNT
 | 
			
		||||
*/
 | 
			
		||||
func GetEventByParam(param map[string]string) (evtId Event) {
 | 
			
		||||
	evtId = EVENT_UNKNOWN
 | 
			
		||||
	channel, ok := param["channel"]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	evtId = GetEventId(channel)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	订阅频道。
 | 
			
		||||
	req:请求json字符串
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) Subscribe(param map[string]string, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
	res = true
 | 
			
		||||
	tm := 5000
 | 
			
		||||
	if len(timeOut) != 0 {
 | 
			
		||||
		tm = timeOut[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	evtid := GetEventByParam(param)
 | 
			
		||||
	if evtid == EVENT_UNKNOWN {
 | 
			
		||||
		err = errors.New("非法的请求参数!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	args = append(args, param)
 | 
			
		||||
 | 
			
		||||
	req := ReqData{
 | 
			
		||||
		Op:   OP_SUBSCRIBE,
 | 
			
		||||
		Args: args,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	detail = &ProcessDetail{
 | 
			
		||||
		EndPoint: a.WsEndPoint,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	ctx, _ = context.WithTimeout(ctx, time.Duration(tm)*time.Millisecond)
 | 
			
		||||
	ctx = context.WithValue(ctx, "detail", detail)
 | 
			
		||||
 | 
			
		||||
	msg, err := a.process(ctx, evtid, req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		log.Println("处理请求失败!", req, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	detail.Data = msg
 | 
			
		||||
 | 
			
		||||
	//检查所有频道是否都更新成功
 | 
			
		||||
	res, err = checkResult(req, msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	取消订阅频道。
 | 
			
		||||
	req:请求json字符串
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) UnSubscribe(param map[string]string, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
	res = true
 | 
			
		||||
	tm := 5000
 | 
			
		||||
	if len(timeOut) != 0 {
 | 
			
		||||
		tm = timeOut[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	evtid := GetEventByParam(param)
 | 
			
		||||
	if evtid == EVENT_UNKNOWN {
 | 
			
		||||
		err = errors.New("非法的请求参数!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	args = append(args, param)
 | 
			
		||||
 | 
			
		||||
	req := ReqData{
 | 
			
		||||
		Op:   OP_UNSUBSCRIBE,
 | 
			
		||||
		Args: args,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	detail = &ProcessDetail{
 | 
			
		||||
		EndPoint: a.WsEndPoint,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	ctx, _ = context.WithTimeout(ctx, time.Duration(tm)*time.Millisecond)
 | 
			
		||||
	ctx = context.WithValue(ctx, "detail", detail)
 | 
			
		||||
	msg, err := a.process(ctx, evtid, req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		log.Println("处理请求失败!", req, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	detail.Data = msg
 | 
			
		||||
	//检查所有频道是否都更新成功
 | 
			
		||||
	res, err = checkResult(req, msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	jrpc请求
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) Jrpc(id, op string, params []map[string]interface{}, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
	res = true
 | 
			
		||||
	tm := 5000
 | 
			
		||||
	if len(timeOut) != 0 {
 | 
			
		||||
		tm = timeOut[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	evtid := GetEventId(op)
 | 
			
		||||
	if evtid == EVENT_UNKNOWN {
 | 
			
		||||
		err = errors.New("非法的请求参数!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req := JRPCReq{
 | 
			
		||||
		Id:   id,
 | 
			
		||||
		Op:   op,
 | 
			
		||||
		Args: params,
 | 
			
		||||
	}
 | 
			
		||||
	detail = &ProcessDetail{
 | 
			
		||||
		EndPoint: a.WsEndPoint,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	ctx, _ = context.WithTimeout(ctx, time.Duration(tm)*time.Millisecond)
 | 
			
		||||
	ctx = context.WithValue(ctx, "detail", detail)
 | 
			
		||||
	msg, err := a.process(ctx, evtid, req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		log.Println("处理请求失败!", req, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	detail.Data = msg
 | 
			
		||||
 | 
			
		||||
	//检查所有频道是否都更新成功
 | 
			
		||||
	res, err = checkResult(req, msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *WsClient) PubChannel(evtId Event, op string, params []map[string]string, pd Period, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
 | 
			
		||||
	// 参数校验
 | 
			
		||||
	pa, err := checkParams(evtId, params, pd)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res = true
 | 
			
		||||
	tm := 5000
 | 
			
		||||
	if len(timeOut) != 0 {
 | 
			
		||||
		tm = timeOut[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req := ReqData{
 | 
			
		||||
		Op:   op,
 | 
			
		||||
		Args: pa,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	ctx, _ = context.WithTimeout(ctx, time.Duration(tm)*time.Millisecond)
 | 
			
		||||
	msg, err = a.process(ctx, evtId, req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		log.Println("处理请求失败!", req, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//检查所有频道是否都更新成功
 | 
			
		||||
 | 
			
		||||
	res, err = checkResult(req, msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 参数校验
 | 
			
		||||
func checkParams(evtId Event, params []map[string]string, pd Period) (res []map[string]string, err error) {
 | 
			
		||||
 | 
			
		||||
	channel := evtId.GetChannel(pd)
 | 
			
		||||
	if channel == "" {
 | 
			
		||||
		err = errors.New("参数校验失败!未知的类型:" + evtId.String())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	log.Println(channel)
 | 
			
		||||
	if params == nil {
 | 
			
		||||
		tmp := make(map[string]string)
 | 
			
		||||
		tmp["channel"] = channel
 | 
			
		||||
		res = append(res, tmp)
 | 
			
		||||
	} else {
 | 
			
		||||
		//log.Println(params)
 | 
			
		||||
		for _, param := range params {
 | 
			
		||||
 | 
			
		||||
			tmp := make(map[string]string)
 | 
			
		||||
			for k, v := range param {
 | 
			
		||||
				tmp[k] = v
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			val, ok := tmp["channel"]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				tmp["channel"] = channel
 | 
			
		||||
			} else {
 | 
			
		||||
				if val != channel {
 | 
			
		||||
					err = errors.New("参数校验失败!channel应为" + channel + val)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			res = append(res, tmp)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								okex/ws/ws_priv_channel.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								okex/ws/ws_priv_channel.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	. "v5sdk_go/ws/wImpl"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	订阅账户频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PrivAccout(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_ACCOUNT, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	订阅持仓频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PrivPostion(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_POSTION, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	订阅订单频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PrivBookOrder(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_ORDER, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	订阅策略委托订单频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PrivBookAlgoOrder(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_ALG_ORDER, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	订阅账户余额和持仓频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PrivBalAndPos(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_B_AND_P, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										99
									
								
								okex/ws/ws_priv_channel_Accout_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								okex/ws/ws_priv_channel_Accout_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,99 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
// HOW TO RUN
 | 
			
		||||
// go test ws_cli.go ws_op.go ws_contants.go utils.go ws_priv_channel.go ws_priv_channel_Accout_test.go -v
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	TRADE_ACCOUNT = iota
 | 
			
		||||
	ISOLATE_ACCOUNT
 | 
			
		||||
	CROSS_ACCOUNT
 | 
			
		||||
	CROSS_ACCOUNT_B
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func prework_pri(t int) *WsClient {
 | 
			
		||||
	// 模拟环境
 | 
			
		||||
	ep := "wss://wsaws.okex.com:8443/ws/v5/private"
 | 
			
		||||
	var apikey, passphrase, secretKey string
 | 
			
		||||
	// 把账号密码写这里
 | 
			
		||||
	switch t {
 | 
			
		||||
	case TRADE_ACCOUNT:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	case ISOLATE_ACCOUNT:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	case CROSS_ACCOUNT:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	case CROSS_ACCOUNT_B:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var res bool
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.Login(apikey, secretKey, passphrase)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("登录成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		log.Fatal("登录失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(apikey, secretKey, passphrase)
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 账户频道 测试
 | 
			
		||||
func TestAccout(t *testing.T) {
 | 
			
		||||
	r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
	var res bool
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["ccy"] = "BTC"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
	fmt.Println("args: ", args)
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PrivAccout(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅所有成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅所有成功!", err)
 | 
			
		||||
		t.Fatal("订阅所有成功!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(100 * time.Second)
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	// res, _, err = r.PrivAccout(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	// if res {
 | 
			
		||||
	// usedTime := time.Since(start)
 | 
			
		||||
	// fmt.Println("取消订阅所有成功!", usedTime.String())
 | 
			
		||||
	// } else {
 | 
			
		||||
	// fmt.Println("取消订阅所有失败!", err)
 | 
			
		||||
	// t.Fatal("取消订阅所有失败!", err)
 | 
			
		||||
	// }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										247
									
								
								okex/ws/ws_priv_channel_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										247
									
								
								okex/ws/ws_priv_channel_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,247 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	TRADE_ACCOUNT = iota
 | 
			
		||||
	ISOLATE_ACCOUNT
 | 
			
		||||
	CROSS_ACCOUNT
 | 
			
		||||
	CROSS_ACCOUNT_B
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func prework_pri(t int) *WsClient {
 | 
			
		||||
	ep := "wss://wsaws.okex.com:8443/ws/v5/private"
 | 
			
		||||
	var apikey, passphrase, secretKey string
 | 
			
		||||
	// 把账号密码写这里
 | 
			
		||||
	switch t {
 | 
			
		||||
	case TRADE_ACCOUNT:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	case ISOLATE_ACCOUNT:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	case CROSS_ACCOUNT:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	case CROSS_ACCOUNT_B:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var res bool
 | 
			
		||||
	//start := time.Now()
 | 
			
		||||
	res, _, err = r.Login(apikey, secretKey, passphrase)
 | 
			
		||||
	if res {
 | 
			
		||||
		//usedTime := time.Since(start)
 | 
			
		||||
		//fmt.Println("登录成功!",usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		log.Fatal("登录失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(apikey, secretKey, passphrase)
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 账户频道 测试
 | 
			
		||||
func TestAccout(t *testing.T) {
 | 
			
		||||
	r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
	var res bool
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	//arg["ccy"] = "BTC"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PrivAccout(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅所有成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅所有成功!", err)
 | 
			
		||||
		t.Fatal("订阅所有成功!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(100 * time.Second)
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PrivAccout(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅所有成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅所有失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅所有失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 持仓频道 测试
 | 
			
		||||
func TestPositon(t *testing.T) {
 | 
			
		||||
	r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instType"] = FUTURES
 | 
			
		||||
	arg["uly"] = "BTC-USD"
 | 
			
		||||
	//arg["instId"] = "BTC-USD-210319"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PrivPostion(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(60000 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PrivPostion(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 订单频道 测试
 | 
			
		||||
func TestBookOrder(t *testing.T) {
 | 
			
		||||
	r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instId"] = "BTC-USDT"
 | 
			
		||||
	arg["instType"] = "ANY"
 | 
			
		||||
	//arg["instType"] = "SWAP"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PrivBookOrder(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(6000 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PrivBookOrder(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 策略委托订单频道 测试
 | 
			
		||||
func TestAlgoOrder(t *testing.T) {
 | 
			
		||||
	r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instType"] = "SPOT"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PrivBookAlgoOrder(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(60 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PrivBookAlgoOrder(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 账户余额和持仓频道 测试
 | 
			
		||||
func TestPrivBalAndPos(t *testing.T) {
 | 
			
		||||
	r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PrivBalAndPos(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(600 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PrivBalAndPos(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										141
									
								
								okex/ws/ws_pub_channel.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								okex/ws/ws_pub_channel.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,141 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	. "v5sdk_go/ws/wImpl"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	产品频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PubInstruemnts(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_INSTRUMENTS, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取系统维护的状态,当系统维护状态改变时推送
 | 
			
		||||
func (a *WsClient) PubStatus(op string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
	return a.PubChannel(EVENT_STATUS, op, nil, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	行情频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PubTickers(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_TICKERS, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	持仓总量频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PubOpenInsterest(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_OPEN_INTEREST, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	K线频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PubKLine(op string, period Period, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_KLINE, op, params, period, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	交易频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PubTrade(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_TRADE, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	预估交割/行权价格频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PubEstDePrice(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_ESTIMATE_PRICE, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	标记价格频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PubMarkPrice(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_MARK_PRICE, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	标记价格K线频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PubMarkPriceCandle(op string, pd Period, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_MARK_PRICE_CANDLE_CHART, op, params, pd, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	限价频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PubLimitPrice(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_LIMIT_PRICE, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	深度频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PubOrderBooks(op string, channel string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
 | 
			
		||||
	switch channel {
 | 
			
		||||
	// 400档快照
 | 
			
		||||
	case "books":
 | 
			
		||||
		return a.PubChannel(EVENT_BOOK_ORDER_BOOK, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
	// 5档快照
 | 
			
		||||
	case "books5":
 | 
			
		||||
		return a.PubChannel(EVENT_BOOK_ORDER_BOOK5, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
	// 400 tbt
 | 
			
		||||
	case "books-l2-tbt":
 | 
			
		||||
		return a.PubChannel(EVENT_BOOK_ORDER_BOOK_TBT, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
	// 50 tbt
 | 
			
		||||
	case "books50-l2-tbt":
 | 
			
		||||
		return a.PubChannel(EVENT_BOOK_ORDER_BOOK50_TBT, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		err = errors.New("未知的channel")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	期权定价频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PubOptionSummary(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_OPTION_SUMMARY, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	资金费率频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PubFundRate(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_FUND_RATE, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	指数K线频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PubKLineIndex(op string, pd Period, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_KLINE_INDEX, op, params, pd, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	指数行情频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PubIndexTickers(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_INDEX_TICKERS, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										669
									
								
								okex/ws/ws_pub_channel_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										669
									
								
								okex/ws/ws_pub_channel_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,669 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
	. "v5sdk_go/ws/wImpl"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func prework() *WsClient {
 | 
			
		||||
	ep := "wss://wsaws.okex.com:8443/ws/v5/private"
 | 
			
		||||
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err, ep)
 | 
			
		||||
	}
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 产品频道测试
 | 
			
		||||
func TestInstruemnts(t *testing.T) {
 | 
			
		||||
	r := prework()
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instType"] = SPOT
 | 
			
		||||
	//arg["instType"] = OPTION
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PubInstruemnts(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(3 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PubInstruemnts(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// status频道测试
 | 
			
		||||
func TestStatus(t *testing.T) {
 | 
			
		||||
	r := prework()
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PubStatus(OP_SUBSCRIBE)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(10000 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PubStatus(OP_UNSUBSCRIBE)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 行情频道测试
 | 
			
		||||
func TestTickers(t *testing.T) {
 | 
			
		||||
	r := prework()
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instId"] = "BTC-USDT"
 | 
			
		||||
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PubTickers(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(600 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PubTickers(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 持仓总量频道 测试
 | 
			
		||||
func TestOpenInsterest(t *testing.T) {
 | 
			
		||||
	r := prework()
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instId"] = "LTC-USD-SWAP"
 | 
			
		||||
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PubOpenInsterest(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(60 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PubOpenInsterest(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// K线频道测试
 | 
			
		||||
func TestKLine(t *testing.T) {
 | 
			
		||||
	r := prework()
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instId"] = "BTC-USDT"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	// 1分钟K
 | 
			
		||||
	period := PERIOD_1MIN
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PubKLine(OP_SUBSCRIBE, period, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(60 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PubKLine(OP_UNSUBSCRIBE, period, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 交易频道测试
 | 
			
		||||
func TestTrade(t *testing.T) {
 | 
			
		||||
	r := prework()
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instId"] = "BTC-USDT"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PubTrade(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(60 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PubTrade(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 预估交割/行权价格频道 测试
 | 
			
		||||
func TestEstDePrice(t *testing.T) {
 | 
			
		||||
	r := prework()
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instType"] = FUTURES
 | 
			
		||||
	arg["uly"] = "BTC-USD"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PubEstDePrice(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(60 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PubEstDePrice(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 标记价格频道 测试
 | 
			
		||||
func TestMarkPrice(t *testing.T) {
 | 
			
		||||
	r := prework()
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instId"] = "BTC-USDT"
 | 
			
		||||
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PubMarkPrice(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(60 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PubMarkPrice(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 标记价格K线频道 测试s
 | 
			
		||||
func TestMarkPriceCandle(t *testing.T) {
 | 
			
		||||
	r := prework()
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instId"] = "BTC-USDT"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	period := PERIOD_1MIN
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PubMarkPriceCandle(OP_SUBSCRIBE, period, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(60 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PubMarkPriceCandle(OP_UNSUBSCRIBE, period, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 限价频道 测试
 | 
			
		||||
func TestLimitPrice(t *testing.T) {
 | 
			
		||||
	r := prework()
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instId"] = "BTC-USDT"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PubLimitPrice(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(60 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PubLimitPrice(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 深度频道 测试
 | 
			
		||||
func TestOrderBooks(t *testing.T) {
 | 
			
		||||
	r := prework()
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
		设置关闭深度数据管理
 | 
			
		||||
	*/
 | 
			
		||||
	// err = r.EnableAutoDepthMgr(false)
 | 
			
		||||
	// if err != nil {
 | 
			
		||||
	// 	fmt.Println("关闭自动校验失败!")
 | 
			
		||||
	// }
 | 
			
		||||
 | 
			
		||||
	end := make(chan struct{})
 | 
			
		||||
 | 
			
		||||
	r.AddDepthHook(func(ts time.Time, data DepthData) error {
 | 
			
		||||
		// 对于深度类型数据处理的用户可以自定义
 | 
			
		||||
 | 
			
		||||
		// 检测深度数据是否正常
 | 
			
		||||
		key, _ := json.Marshal(data.Arg)
 | 
			
		||||
		fmt.Println("个数:", len(data.Data[0].Asks))
 | 
			
		||||
		checksum := data.Data[0].Checksum
 | 
			
		||||
		fmt.Println("[自定义方法] ", string(key), ", checksum = ", checksum)
 | 
			
		||||
 | 
			
		||||
		for _, ask := range data.Data[0].Asks {
 | 
			
		||||
 | 
			
		||||
			arr := strings.Split(ask[0], ".")
 | 
			
		||||
			//fmt.Println(arr)
 | 
			
		||||
			if len(arr) > 1 && len(arr[1]) > 2 {
 | 
			
		||||
				fmt.Println("ask数据异常,", checksum, "ask:", ask)
 | 
			
		||||
				t.Fatal()
 | 
			
		||||
				end <- struct{}{}
 | 
			
		||||
				return nil
 | 
			
		||||
			} else {
 | 
			
		||||
				fmt.Println("bid数据正常,", checksum, "ask:", ask)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, bid := range data.Data[0].Bids {
 | 
			
		||||
 | 
			
		||||
			arr := strings.Split(bid[0], ".")
 | 
			
		||||
			//fmt.Println(arr)
 | 
			
		||||
			if len(arr) > 1 && len(arr[1]) > 2 {
 | 
			
		||||
				fmt.Println("bid数据异常,", checksum, "bid:", bid)
 | 
			
		||||
				t.Fatal()
 | 
			
		||||
				end <- struct{}{}
 | 
			
		||||
				return nil
 | 
			
		||||
			} else {
 | 
			
		||||
				fmt.Println("ask数据正常,", checksum, "bid:", bid)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// // 查看当前合并后的全量深度数据
 | 
			
		||||
		// snapshot, err := r.GetSnapshotByChannel(data)
 | 
			
		||||
		// if err != nil {
 | 
			
		||||
		// 	t.Fatal("深度数据不存在!")
 | 
			
		||||
		// }
 | 
			
		||||
		// // 展示ask/bid 前5档数据
 | 
			
		||||
		// fmt.Println(" Ask 5 档数据 >> ")
 | 
			
		||||
		// for _, v := range snapshot.Asks[:5] {
 | 
			
		||||
		// 	fmt.Println(" price:", v[0], " amount:", v[1])
 | 
			
		||||
		// }
 | 
			
		||||
		// fmt.Println(" Bid 5 档数据 >> ")
 | 
			
		||||
		// for _, v := range snapshot.Bids[:5] {
 | 
			
		||||
		// 	fmt.Println(" price:", v[0], " amount:", v[1])
 | 
			
		||||
		// }
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// 可选类型:books books5 books-l2-tbt
 | 
			
		||||
	channel := "books50-l2-tbt"
 | 
			
		||||
 | 
			
		||||
	instIds := []string{"BTC-USDT"}
 | 
			
		||||
	for _, instId := range instIds {
 | 
			
		||||
		var args []map[string]string
 | 
			
		||||
		arg := make(map[string]string)
 | 
			
		||||
		arg["instId"] = instId
 | 
			
		||||
		args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
		start := time.Now()
 | 
			
		||||
		res, _, err = r.PubOrderBooks(OP_SUBSCRIBE, channel, args)
 | 
			
		||||
		if res {
 | 
			
		||||
			usedTime := time.Since(start)
 | 
			
		||||
			fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Println("订阅失败!", err)
 | 
			
		||||
			t.Fatal("订阅失败!", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	select {
 | 
			
		||||
	case <-end:
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	//等待推送
 | 
			
		||||
	for _, instId := range instIds {
 | 
			
		||||
		var args []map[string]string
 | 
			
		||||
		arg := make(map[string]string)
 | 
			
		||||
		arg["instId"] = instId
 | 
			
		||||
		args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
		start := time.Now()
 | 
			
		||||
		res, _, err = r.PubOrderBooks(OP_UNSUBSCRIBE, channel, args)
 | 
			
		||||
		if res {
 | 
			
		||||
			usedTime := time.Since(start)
 | 
			
		||||
			fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Println("取消订阅失败!", err)
 | 
			
		||||
			t.Fatal("取消订阅失败!", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 期权定价频道 测试
 | 
			
		||||
func TestOptionSummary(t *testing.T) {
 | 
			
		||||
	r := prework()
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["uly"] = "BTC-USD"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PubOptionSummary(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(60 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PubOptionSummary(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 资金费率 测试
 | 
			
		||||
func TestFundRate(t *testing.T) {
 | 
			
		||||
	r := prework()
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instId"] = "BTC-USD-SWAP"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PubFundRate(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(600 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PubFundRate(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 指数K线频道 测试
 | 
			
		||||
func TestKLineIndex(t *testing.T) {
 | 
			
		||||
	r := prework()
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
 | 
			
		||||
	arg["instId"] = "BTC-USDT"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
	period := PERIOD_1MIN
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PubKLineIndex(OP_SUBSCRIBE, period, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(60 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PubKLineIndex(OP_UNSUBSCRIBE, period, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 指数行情频道 测试
 | 
			
		||||
func TestIndexMarket(t *testing.T) {
 | 
			
		||||
	r := prework()
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instId"] = "BTC-USDT"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PubIndexTickers(OP_SUBSCRIBE, args)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
	usedTime := time.Since(start)
 | 
			
		||||
	if res {
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", usedTime.String())
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(600 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PubIndexTickers(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										386
									
								
								okex/ws/ws_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										386
									
								
								okex/ws/ws_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,386 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
	. "v5sdk_go/ws/wImpl"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	log.SetFlags(log.LstdFlags | log.Llongfile)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPing(t *testing.T) {
 | 
			
		||||
	r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
 | 
			
		||||
	res, _, _ := r.Ping()
 | 
			
		||||
	assert.True(t, res, true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWsClient_SubscribeAndUnSubscribe(t *testing.T) {
 | 
			
		||||
	r := prework()
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	param := map[string]string{}
 | 
			
		||||
	param["channel"] = "opt-summary"
 | 
			
		||||
	param["uly"] = "BTC-USD"
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.Subscribe(param)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(60 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.UnSubscribe(param)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWsClient_SubscribeAndUnSubscribe_priv(t *testing.T) {
 | 
			
		||||
	r := prework_pri(ISOLATE_ACCOUNT)
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var params []map[string]string
 | 
			
		||||
	params = append(params, map[string]string{"channel": "orders", "instType": SPOT, "instId": "BTC-USDT"})
 | 
			
		||||
	//一个失败的订阅用例
 | 
			
		||||
	params = append(params, map[string]string{"channel": "positions", "instType": "any"})
 | 
			
		||||
 | 
			
		||||
	for _, v := range params {
 | 
			
		||||
		start := time.Now()
 | 
			
		||||
		var data *ProcessDetail
 | 
			
		||||
		res, data, err = r.Subscribe(v)
 | 
			
		||||
		if res {
 | 
			
		||||
			usedTime := time.Since(start)
 | 
			
		||||
			fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
			PrintDetail(data)
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Println("订阅失败!", err)
 | 
			
		||||
			//return
 | 
			
		||||
		}
 | 
			
		||||
		time.Sleep(60 * time.Second)
 | 
			
		||||
		//等待推送
 | 
			
		||||
 | 
			
		||||
		start = time.Now()
 | 
			
		||||
		res, _, err = r.UnSubscribe(v)
 | 
			
		||||
		if res {
 | 
			
		||||
			usedTime := time.Since(start)
 | 
			
		||||
			fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWsClient_Jrpc(t *testing.T) {
 | 
			
		||||
	//r := prework_pri(ISOLATE_ACCOUNT)
 | 
			
		||||
	r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
	var res bool
 | 
			
		||||
	var err error
 | 
			
		||||
	var data *ProcessDetail
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	var args []map[string]interface{}
 | 
			
		||||
 | 
			
		||||
	param := map[string]interface{}{}
 | 
			
		||||
	param["instId"] = "BTC-USDT"
 | 
			
		||||
	param["clOrdId"] = "SIM0dcopy16069997808063455"
 | 
			
		||||
	param["tdMode"] = "cross"
 | 
			
		||||
	param["side"] = "sell"
 | 
			
		||||
	param["ordType"] = "limit"
 | 
			
		||||
	param["px"] = "19333.3"
 | 
			
		||||
	param["sz"] = "0.18605445"
 | 
			
		||||
 | 
			
		||||
	param1 := map[string]interface{}{}
 | 
			
		||||
	param1["instId"] = "BTC-USDT"
 | 
			
		||||
	param1["clOrdId"] = "SIM0dcopy16069997808063456"
 | 
			
		||||
	param1["tdMode"] = "cross"
 | 
			
		||||
	param1["side"] = "sell"
 | 
			
		||||
	param1["ordType"] = "limit"
 | 
			
		||||
	param1["px"] = "19334.2"
 | 
			
		||||
	param1["sz"] = "0.03508913"
 | 
			
		||||
 | 
			
		||||
	param2 := map[string]interface{}{}
 | 
			
		||||
	param2["instId"] = "BTC-USDT"
 | 
			
		||||
	param2["clOrdId"] = "SIM0dcopy16069997808063457"
 | 
			
		||||
	param2["tdMode"] = "cross"
 | 
			
		||||
	param2["side"] = "sell"
 | 
			
		||||
	param2["ordType"] = "limit"
 | 
			
		||||
	param2["px"] = "19334.8"
 | 
			
		||||
	param2["sz"] = "0.03658186"
 | 
			
		||||
 | 
			
		||||
	param3 := map[string]interface{}{}
 | 
			
		||||
	param3["instId"] = "BTC-USDT"
 | 
			
		||||
	param3["clOrdId"] = "SIM0dcopy16069997808063458"
 | 
			
		||||
	param3["tdMode"] = "cross"
 | 
			
		||||
	param3["side"] = "sell"
 | 
			
		||||
	param3["ordType"] = "limit"
 | 
			
		||||
	param3["px"] = "19334.9"
 | 
			
		||||
	param3["sz"] = "0.5"
 | 
			
		||||
 | 
			
		||||
	param4 := map[string]interface{}{}
 | 
			
		||||
	param4["instId"] = "BTC-USDT"
 | 
			
		||||
	param4["clOrdId"] = "SIM0dcopy16069997808063459"
 | 
			
		||||
	param4["tdMode"] = "cross"
 | 
			
		||||
	param4["side"] = "sell"
 | 
			
		||||
	param4["ordType"] = "limit"
 | 
			
		||||
	param4["px"] = "19335.2"
 | 
			
		||||
	param4["sz"] = "0.3"
 | 
			
		||||
 | 
			
		||||
	param5 := map[string]interface{}{}
 | 
			
		||||
	param5["instId"] = "BTC-USDT"
 | 
			
		||||
	param5["clOrdId"] = "SIM0dcopy16069997808063460"
 | 
			
		||||
	param5["tdMode"] = "cross"
 | 
			
		||||
	param5["side"] = "sell"
 | 
			
		||||
	param5["ordType"] = "limit"
 | 
			
		||||
	param5["px"] = "19335.9"
 | 
			
		||||
	param5["sz"] = "0.051"
 | 
			
		||||
 | 
			
		||||
	param6 := map[string]interface{}{}
 | 
			
		||||
	param6["instId"] = "BTC-USDT"
 | 
			
		||||
	param6["clOrdId"] = "SIM0dcopy16069997808063461"
 | 
			
		||||
	param6["tdMode"] = "cross"
 | 
			
		||||
	param6["side"] = "sell"
 | 
			
		||||
	param6["ordType"] = "limit"
 | 
			
		||||
	param6["px"] = "19336.4"
 | 
			
		||||
	param6["sz"] = "1"
 | 
			
		||||
 | 
			
		||||
	param7 := map[string]interface{}{}
 | 
			
		||||
	param7["instId"] = "BTC-USDT"
 | 
			
		||||
	param7["clOrdId"] = "SIM0dcopy16069997808063462"
 | 
			
		||||
	param7["tdMode"] = "cross"
 | 
			
		||||
	param7["side"] = "sell"
 | 
			
		||||
	param7["ordType"] = "limit"
 | 
			
		||||
	param7["px"] = "19336.8"
 | 
			
		||||
	param7["sz"] = "0.475"
 | 
			
		||||
 | 
			
		||||
	param8 := map[string]interface{}{}
 | 
			
		||||
	param8["instId"] = "BTC-USDT"
 | 
			
		||||
	param8["clOrdId"] = "SIM0dcopy16069997808063463"
 | 
			
		||||
	param8["tdMode"] = "cross"
 | 
			
		||||
	param8["side"] = "sell"
 | 
			
		||||
	param8["ordType"] = "limit"
 | 
			
		||||
	param8["px"] = "19337.3"
 | 
			
		||||
	param8["sz"] = "0.21299357"
 | 
			
		||||
 | 
			
		||||
	param9 := map[string]interface{}{}
 | 
			
		||||
	param9["instId"] = "BTC-USDT"
 | 
			
		||||
	param9["clOrdId"] = "SIM0dcopy16069997808063464"
 | 
			
		||||
	param9["tdMode"] = "cross"
 | 
			
		||||
	param9["side"] = "sell"
 | 
			
		||||
	param9["ordType"] = "limit"
 | 
			
		||||
	param9["px"] = "19337.5"
 | 
			
		||||
	param9["sz"] = "0.5"
 | 
			
		||||
 | 
			
		||||
	args = append(args, param)
 | 
			
		||||
	args = append(args, param1)
 | 
			
		||||
	args = append(args, param2)
 | 
			
		||||
	args = append(args, param3)
 | 
			
		||||
	args = append(args, param4)
 | 
			
		||||
	args = append(args, param5)
 | 
			
		||||
	args = append(args, param6)
 | 
			
		||||
	args = append(args, param7)
 | 
			
		||||
	args = append(args, param8)
 | 
			
		||||
	args = append(args, param9)
 | 
			
		||||
 | 
			
		||||
	res, data, err = r.Jrpc("okexv5wsapi001", "order", args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("下单成功!", usedTime.String())
 | 
			
		||||
		PrintDetail(data)
 | 
			
		||||
	} else {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("下单失败!", usedTime.String(), err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	测试 添加全局消息回调函数
 | 
			
		||||
*/
 | 
			
		||||
func TestAddMessageHook(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
 | 
			
		||||
	r.AddMessageHook(func(msg *Msg) error {
 | 
			
		||||
		// 添加你的方法
 | 
			
		||||
		fmt.Println("这是全局消息自定义MessageHook")
 | 
			
		||||
		fmt.Println("当前数据是", msg)
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	select {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	普通推送数据回调函数
 | 
			
		||||
*/
 | 
			
		||||
func TestAddBookedDataHook(t *testing.T) {
 | 
			
		||||
	var r *WsClient
 | 
			
		||||
 | 
			
		||||
	/*订阅私有频道*/
 | 
			
		||||
	{
 | 
			
		||||
		r = prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
		var res bool
 | 
			
		||||
		var err error
 | 
			
		||||
 | 
			
		||||
		r.AddBookMsgHook(func(ts time.Time, data MsgData) error {
 | 
			
		||||
			// 添加你的方法
 | 
			
		||||
			fmt.Println("这是私有自定义AddBookMsgHook")
 | 
			
		||||
			fmt.Println("当前数据是", data)
 | 
			
		||||
			return nil
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		param := map[string]string{}
 | 
			
		||||
		param["channel"] = "account"
 | 
			
		||||
		param["ccy"] = "BTC"
 | 
			
		||||
 | 
			
		||||
		res, _, err = r.Subscribe(param)
 | 
			
		||||
		if res {
 | 
			
		||||
			fmt.Println("订阅成功!")
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Println("订阅失败!", err)
 | 
			
		||||
			t.Fatal("订阅失败!", err)
 | 
			
		||||
			//return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		time.Sleep(100 * time.Second)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//订阅公共频道
 | 
			
		||||
	{
 | 
			
		||||
		r = prework()
 | 
			
		||||
		var res bool
 | 
			
		||||
		var err error
 | 
			
		||||
 | 
			
		||||
		r.AddBookMsgHook(func(ts time.Time, data MsgData) error {
 | 
			
		||||
			// 添加你的方法
 | 
			
		||||
			fmt.Println("这是公共自定义AddBookMsgHook")
 | 
			
		||||
			fmt.Println("当前数据是", data)
 | 
			
		||||
			return nil
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		param := map[string]string{}
 | 
			
		||||
		param["channel"] = "instruments"
 | 
			
		||||
		param["instType"] = "FUTURES"
 | 
			
		||||
 | 
			
		||||
		res, _, err = r.Subscribe(param)
 | 
			
		||||
		if res {
 | 
			
		||||
			fmt.Println("订阅成功!")
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Println("订阅失败!", err)
 | 
			
		||||
			t.Fatal("订阅失败!", err)
 | 
			
		||||
			//return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		select {}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetInfoFromErrMsg(t *testing.T) {
 | 
			
		||||
	a := assert.New(t)
 | 
			
		||||
	buf := `
 | 
			
		||||
"channel:index-tickers,instId:BTC-USDT1 doesn't exist"
 | 
			
		||||
    `
 | 
			
		||||
	ch := GetInfoFromErrMsg(buf)
 | 
			
		||||
	//t.Log(ch)
 | 
			
		||||
	a.Equal("index-tickers", ch)
 | 
			
		||||
 | 
			
		||||
	//assert.True(t,ch == "index-tickers")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
 */
 | 
			
		||||
func TestParseMessage(t *testing.T) {
 | 
			
		||||
	r := prework()
 | 
			
		||||
	var evt Event
 | 
			
		||||
	msg := `{"event":"error","msg":"Contract does not exist.","code":"51001"}`
 | 
			
		||||
 | 
			
		||||
	evt, _, _ = r.parseMessage([]byte(msg))
 | 
			
		||||
	assert.True(t, EVENT_ERROR == evt)
 | 
			
		||||
 | 
			
		||||
	msg = `{"event":"error","msg":"channel:positions,ccy:BTC doesn't exist","code":"60018"}`
 | 
			
		||||
	evt, _, _ = r.parseMessage([]byte(msg))
 | 
			
		||||
	assert.True(t, EVENT_BOOK_POSTION == evt)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	原始方式 深度订阅 测试
 | 
			
		||||
*/
 | 
			
		||||
func TestSubscribeTBT(t *testing.T) {
 | 
			
		||||
	r := prework()
 | 
			
		||||
	var res bool
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	// 添加你的方法
 | 
			
		||||
	r.AddDepthHook(func(ts time.Time, data DepthData) error {
 | 
			
		||||
		//fmt.Println("这是自定义AddBookMsgHook")
 | 
			
		||||
		fmt.Println("当前数据是:", data)
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	param := map[string]string{}
 | 
			
		||||
	param["channel"] = "books-l2-tbt"
 | 
			
		||||
	//param["channel"] = "books"
 | 
			
		||||
	param["instId"] = "BTC-USD-SWAP"
 | 
			
		||||
 | 
			
		||||
	res, _, err = r.Subscribe(param)
 | 
			
		||||
	if res {
 | 
			
		||||
		fmt.Println("订阅成功!")
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(60 * time.Second)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
 */
 | 
			
		||||
func TestSubscribeBalAndPos(t *testing.T) {
 | 
			
		||||
	r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
	var res bool
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	param := map[string]string{}
 | 
			
		||||
 | 
			
		||||
	// 产品信息
 | 
			
		||||
	param["channel"] = "balance_and_position"
 | 
			
		||||
 | 
			
		||||
	res, _, err = r.Subscribe(param)
 | 
			
		||||
	if res {
 | 
			
		||||
		fmt.Println("订阅成功!")
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(60 * time.Second)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										73
									
								
								private/balance.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								private/balance.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,73 @@
 | 
			
		||||
package private
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// {"availEq":0,"cashBal":2397.6,"ccy":"FODL","disEq":0,"eq":2397.6,"eqUsd":340.3419189984,"frozenBal":2397.6,"ordFrozen":2397.6,"uTime":1649902953671}
 | 
			
		||||
// {"availEq":0,"cashBal":12987,"ccy":"ANW","disEq":0,"eq":12987,"eqUsd":358.555901184,"frozenBal":12987,"ordFrozen":12987,"uTime":1649596232202}
 | 
			
		||||
// {"availEq":0,"cashBal":6995.8,"ccy":"ABT","disEq":0,"eq":6995.8,"eqUsd":1082.5316450676,"frozenBal":6995.8,"ordFrozen":6995.8,"uTime":1648835098048}
 | 
			
		||||
 | 
			
		||||
// "availBal string ",
 | 
			
		||||
// "crossLiab string`json:""`
 | 
			
		||||
// "interest string`json:""`
 | 
			
		||||
// "isoEq string`json:""`
 | 
			
		||||
// "isoLiab string`json:""`
 | 
			
		||||
// "isoUpl":"0",
 | 
			
		||||
// "liab string`json:""`
 | 
			
		||||
// "maxLoan string 1453.92289531493594",
 | 
			
		||||
// "mgnRatio string ",
 | 
			
		||||
// "notionalLever string ",
 | 
			
		||||
// "ordFrozen string`json:""`
 | 
			
		||||
// "twap string`json:""`
 | 
			
		||||
// "upl string 0.570822125136023",
 | 
			
		||||
// "uplLiab string`json:""`
 | 
			
		||||
// "stgyEq":"0"
 | 
			
		||||
 | 
			
		||||
type CcyResp struct {
 | 
			
		||||
	AvailEq   string `json:"availEq"`
 | 
			
		||||
	CashBal   string `json:"cashBal"`
 | 
			
		||||
	Ccy       string `json:"ccy"`
 | 
			
		||||
	DisEq     string `json:"disEq"`
 | 
			
		||||
	Eq        string `json:"eq"`
 | 
			
		||||
	EqUsd     string `json:"eqUsd"`
 | 
			
		||||
	FrozenBal string `json:"frozenBal"`
 | 
			
		||||
	OrdFrozen string `json:"ordFrozen"`
 | 
			
		||||
	UTime     string `json:"uTime"`
 | 
			
		||||
	AvailBal  string `json:"availBal"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Ccy struct {
 | 
			
		||||
	AvailEq   float64 `json:"availEq"`
 | 
			
		||||
	CashBal   float64 `json:"cashBal"`
 | 
			
		||||
	Ccy       string  `json:"ccy"`
 | 
			
		||||
	DisEq     float64 `json:"disEq"`
 | 
			
		||||
	Eq        float64 `json:"eq":wa`
 | 
			
		||||
	EqUsd     float64 `json:"eqUsd"`
 | 
			
		||||
	FrozenBal float64 `json:"frozenBal"`
 | 
			
		||||
	OrdFrozen float64 `json:"ordFrozen"`
 | 
			
		||||
	UTime     int64   `json:"uTime"`
 | 
			
		||||
	AvailBal  float64 `json:"availBal"`
 | 
			
		||||
}
 | 
			
		||||
type BalanceResp struct {
 | 
			
		||||
}
 | 
			
		||||
type Balance struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ccyResp *CcyResp) Convert() (*Ccy, error) {
 | 
			
		||||
	ccy := Ccy{}
 | 
			
		||||
	ccy.Ccy = ccyResp.Ccy
 | 
			
		||||
	ccy.AvailEq, _ = strconv.ParseFloat(ccyResp.AvailEq, 64)
 | 
			
		||||
	ccy.CashBal, _ = strconv.ParseFloat(ccyResp.CashBal, 64)
 | 
			
		||||
	ccy.DisEq, _ = strconv.ParseFloat(ccyResp.DisEq, 64)
 | 
			
		||||
	ccy.Eq, _ = strconv.ParseFloat(ccyResp.Eq, 64)
 | 
			
		||||
	ccy.EqUsd, _ = strconv.ParseFloat(ccyResp.EqUsd, 64)
 | 
			
		||||
	ccy.FrozenBal, _ = strconv.ParseFloat(ccyResp.FrozenBal, 64)
 | 
			
		||||
	ccy.OrdFrozen, _ = strconv.ParseFloat(ccyResp.OrdFrozen, 64)
 | 
			
		||||
	ccy.UTime, _ = strconv.ParseInt(ccyResp.UTime, 10, 64)
 | 
			
		||||
 | 
			
		||||
	return &ccy, nil
 | 
			
		||||
}
 | 
			
		||||
func (balanceResp *BalanceResp) Convert() (*Balance, error) {
 | 
			
		||||
	return nil, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										107
									
								
								private/order.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								private/order.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,107 @@
 | 
			
		||||
package private
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"phyer.click/tunas/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type OrderResp struct {
 | 
			
		||||
	Arg  ArgResp          `json:"arg"`
 | 
			
		||||
	Data []*OrderDataResp `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ArgResp struct {
 | 
			
		||||
	Channel  string `json:"channel"`
 | 
			
		||||
	InstType string `json:"instType"`
 | 
			
		||||
	InstId   string `json:"instId"`
 | 
			
		||||
}
 | 
			
		||||
type OrderDataResp struct {
 | 
			
		||||
	InstType          string `json:"instType"`
 | 
			
		||||
	InstId            string `json:"instId"`
 | 
			
		||||
	OrdId             string `json:"ordId"`
 | 
			
		||||
	ClOrdId           string `json:"clOrdId"`
 | 
			
		||||
	Tag               string `json:"tag"`
 | 
			
		||||
	Px                string `json:"Px"`
 | 
			
		||||
	Sz                string `json:"sz"`
 | 
			
		||||
	NotionalUsd       string `json:"notionalUsd"`
 | 
			
		||||
	OrdType           string `json:"ordType"`
 | 
			
		||||
	Side              string `json:"side"`
 | 
			
		||||
	PosSide           string `json:"posSide"`
 | 
			
		||||
	TdMode            string `json:"tdMode"`
 | 
			
		||||
	TgtCcy            string `json:"tgtCcy"`
 | 
			
		||||
	FillSz            string `json:"fillSz"`
 | 
			
		||||
	FillPx            string `json:"fillPx"`
 | 
			
		||||
	TradeId           string `json:"tradeId"`
 | 
			
		||||
	AccFillSz         string `json:"accFillSz"`
 | 
			
		||||
	FillNotionalUsd   string `json:"FillNotionalUsd"`
 | 
			
		||||
	FillTime          string `json:"fillTime"`
 | 
			
		||||
	FillFee           string `json:"fillFee"`
 | 
			
		||||
	FillFeeCcy        string `json:"fillFeeCcy"`
 | 
			
		||||
	ExecType          string `json:"execType"`
 | 
			
		||||
	Source            string `json:"source"`
 | 
			
		||||
	State             string `json:"state"`
 | 
			
		||||
	AvgPx             string `json:"avgPx"`
 | 
			
		||||
	Lever             string `json:"lever"`
 | 
			
		||||
	TpTriggerPxstring string `json:"tpTriggerPxstring"`
 | 
			
		||||
	TpTriggerPxType   string `json:"tpTriggerPxType"`
 | 
			
		||||
	TpOrdPx           string `json:"tpOrdPx"`
 | 
			
		||||
	SlTriggerPx       string `json:"slTriggerPx"`
 | 
			
		||||
	SlTriggerPxType   string `json:"slTriggerPxType"`
 | 
			
		||||
	SlOrdPx           string `json:"slOrdPx"`
 | 
			
		||||
	FeeCcy            string `json:"feeCcy"`
 | 
			
		||||
	Fee               string `json:"fee"`
 | 
			
		||||
	RebateCcy         string `json:"rebateCcy"`
 | 
			
		||||
	Rebate            string `json:"rebate"`
 | 
			
		||||
	TgtCcystring      string `json:"tgtCcystring"`
 | 
			
		||||
	Pnl               string `json:"pnl"`
 | 
			
		||||
	Category          string `json:"category"`
 | 
			
		||||
	UTime             string `json:"uTime"`
 | 
			
		||||
	CTime             string `json:"cTime"`
 | 
			
		||||
	ReqId             string `json:"reqId"`
 | 
			
		||||
	AmendResult       string `json:"amendResult"`
 | 
			
		||||
	Code              string `json:"code"`
 | 
			
		||||
	Msg               string `json:"msg"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Order struct {
 | 
			
		||||
	CTime     int64   `json:"cTime,number"` // 订单创建时间
 | 
			
		||||
	UTime     int64   `json:"uTime,number"` // 订单状态更新时间
 | 
			
		||||
	InstId    string  `json:"instId"`
 | 
			
		||||
	OrdId     string  `json:"ordId"`
 | 
			
		||||
	ClOrdId   string  `json:"clOrdId"`          // 自定义订单ID
 | 
			
		||||
	Px        float64 `json:"px,number"`        // 委托价格
 | 
			
		||||
	Side      string  `json:"side"`             //交易方向 sell, buy
 | 
			
		||||
	Sz        float64 `json:"sz,number"`        // 委托总量
 | 
			
		||||
	AccFillSz float64 `json:"accFillSz,number"` //累计成交数量
 | 
			
		||||
	AvgPx     float64 `json:"avgPx,number"`     //成交均价
 | 
			
		||||
	State     string  `json:"state"`            //订单状态
 | 
			
		||||
	TgtCcy    string  `json:"tgtCcy"`           // 限定了size的单位,两个选项:base_ccy: 交易货币 ;quote_ccy:计价货币(美元)
 | 
			
		||||
	// canceled:撤单成功
 | 
			
		||||
	// live:等待成交
 | 
			
		||||
	// partially_filled:部分成交
 | 
			
		||||
	// filled:完全成交
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (orderResp *OrderResp) Convert() ([]*Order, error) {
 | 
			
		||||
	orderList := []*Order{}
 | 
			
		||||
	for _, v := range orderResp.Data {
 | 
			
		||||
		// fmt.Println("v.Sz:", v.Sz, reflect.TypeOf(v.Sz).Name())
 | 
			
		||||
		curOrder := Order{}
 | 
			
		||||
		curOrder.CTime = utils.ToInt64(v.CTime)
 | 
			
		||||
		curOrder.UTime = utils.ToInt64(v.UTime)
 | 
			
		||||
		curOrder.InstId = v.InstId
 | 
			
		||||
		curOrder.OrdId = v.OrdId
 | 
			
		||||
		curOrder.ClOrdId = v.ClOrdId
 | 
			
		||||
		curOrder.Side = v.Side
 | 
			
		||||
		curOrder.Px = utils.ToFloat64(v.Px)
 | 
			
		||||
		curOrder.Sz = utils.ToFloat64(v.Sz)
 | 
			
		||||
		curOrder.AccFillSz = utils.ToFloat64(v.AccFillSz)
 | 
			
		||||
		curOrder.AvgPx = utils.ToFloat64(v.AvgPx)
 | 
			
		||||
		curOrder.State = v.State
 | 
			
		||||
		orderList = append(orderList, &curOrder)
 | 
			
		||||
	}
 | 
			
		||||
	return orderList, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// func (order *Order) Process(cr *core.Core) error {
 | 
			
		||||
// return nil
 | 
			
		||||
// }
 | 
			
		||||
							
								
								
									
										21
									
								
								submodules/okex/LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								submodules/okex/LICENSE
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
MIT License
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2021 wang
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
in the Software without restriction, including without limitation the rights
 | 
			
		||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
The above copyright notice and this permission notice shall be included in all
 | 
			
		||||
copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
			
		||||
SOFTWARE.
 | 
			
		||||
							
								
								
									
										32
									
								
								submodules/okex/config/conf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								submodules/okex/config/conf.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import "fmt"
 | 
			
		||||
 | 
			
		||||
type Env struct {
 | 
			
		||||
	RestEndpoint string `yaml:"RestEndpoint"`
 | 
			
		||||
	WsEndpoint   string `yaml:"WsEndpoint"`
 | 
			
		||||
	IsSimulation bool   `yaml:"IsSimulation"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ApiInfo struct {
 | 
			
		||||
	ApiKey     string `yaml:"ApiKey"`
 | 
			
		||||
	SecretKey  string `yaml:"SecretKey"`
 | 
			
		||||
	Passphrase string `yaml:"Passphrase"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MetaData struct {
 | 
			
		||||
	Description string `yaml:"Description"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Config struct {
 | 
			
		||||
	MetaData `yaml:"MetaData"`
 | 
			
		||||
	Env      `yaml:"Env"`
 | 
			
		||||
	ApiInfo  `yaml:"ApiInfo"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *ApiInfo) String() string {
 | 
			
		||||
	res := "ApiInfo{"
 | 
			
		||||
	res += fmt.Sprintf("ApiKey:%v,SecretKey:%v,Passphrase:%v", s.ApiKey, s.SecretKey, s.Passphrase)
 | 
			
		||||
	res += "}"
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								submodules/okex/config/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								submodules/okex/config/go.mod
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
module v5sdk_go/config
 | 
			
		||||
 | 
			
		||||
go 1.14
 | 
			
		||||
							
								
								
									
										8
									
								
								submodules/okex/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								submodules/okex/go.mod
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
module v5sdk_go
 | 
			
		||||
 | 
			
		||||
go 1.15
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/gorilla/websocket v1.4.2
 | 
			
		||||
	github.com/stretchr/testify v1.7.0
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										12
									
								
								submodules/okex/go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								submodules/okex/go.sum
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 | 
			
		||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 | 
			
		||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
							
								
								
									
										237
									
								
								submodules/okex/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								submodules/okex/main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,237 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"time"
 | 
			
		||||
	. "v5sdk_go/rest"
 | 
			
		||||
	. "v5sdk_go/ws"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	rest API请求
 | 
			
		||||
	更多示例请查看 rest/rest_test.go
 | 
			
		||||
*/
 | 
			
		||||
func REST() {
 | 
			
		||||
	// 设置您的APIKey
 | 
			
		||||
	apikey := APIKeyInfo{
 | 
			
		||||
		ApiKey:     "eca767d4-fda5-4a1b-bb28-49ae18093307",
 | 
			
		||||
		SecKey:     "8CA3628A556ED137977DB298D37BC7F3",
 | 
			
		||||
		PassPhrase: "Op3Druaron",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 第三个参数代表是否为模拟环境,更多信息查看接口说明
 | 
			
		||||
	cli := NewRESTClient("https://www.okex.win", &apikey, false)
 | 
			
		||||
	rsp, err := cli.Get(context.Background(), "/api/v5/account/balance", nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Response:")
 | 
			
		||||
	fmt.Println("\thttp code: ", rsp.Code)
 | 
			
		||||
	fmt.Println("\t总耗时: ", rsp.TotalUsedTime)
 | 
			
		||||
	fmt.Println("\t请求耗时: ", rsp.ReqUsedTime)
 | 
			
		||||
	fmt.Println("\t返回消息: ", rsp.Body)
 | 
			
		||||
	fmt.Println("\terrCode: ", rsp.V5Response.Code)
 | 
			
		||||
	fmt.Println("\terrMsg: ", rsp.V5Response.Msg)
 | 
			
		||||
	fmt.Println("\tdata: ", rsp.V5Response.Data)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 订阅私有频道
 | 
			
		||||
func wsPriv() {
 | 
			
		||||
	ep := "wss://ws.okex.com:8443/ws/v5/private?brokerId=9999"
 | 
			
		||||
 | 
			
		||||
	// 填写您自己的APIKey信息
 | 
			
		||||
	apikey := "xxxx"
 | 
			
		||||
	secretKey := "xxxxx"
 | 
			
		||||
	passphrase := "xxxxx"
 | 
			
		||||
 | 
			
		||||
	// 创建ws客户端
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 设置连接超时
 | 
			
		||||
	r.SetDailTimeout(time.Second * 2)
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer r.Stop()
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	res, _, err = r.Login(apikey, secretKey, passphrase)
 | 
			
		||||
	if res {
 | 
			
		||||
		fmt.Println("登录成功!")
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("登录失败!", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 订阅账户频道
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["ccy"] = "BTC"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PrivAccout(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!耗时:", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(100 * time.Second)
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PrivAccout(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 订阅公共频道
 | 
			
		||||
func wsPub() {
 | 
			
		||||
	ep := "wss://wsaws.okex.com:8443/ws/v5/public?brokerId=9999"
 | 
			
		||||
 | 
			
		||||
	// 创建ws客户端
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 设置连接超时
 | 
			
		||||
	r.SetDailTimeout(time.Second * 2)
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer r.Stop()
 | 
			
		||||
	// 订阅产品频道
 | 
			
		||||
	// 在这里初始化instrument列表
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instType"] = FUTURES
 | 
			
		||||
	//arg["instType"] = OPTION
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	//订阅
 | 
			
		||||
	res, _, err := r.PubInstruemnts(OP_SUBSCRIBE, args)
 | 
			
		||||
	fmt.Println("args:", args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 在这里 args1 初始化tickerList的列表
 | 
			
		||||
	var args1 []map[string]string
 | 
			
		||||
	arg1 := make(map[string]string)
 | 
			
		||||
	arg1["instId"] = "ETH-USDT"
 | 
			
		||||
	//arg["instType"] = OPTION
 | 
			
		||||
	args1 = append(args1, arg1)
 | 
			
		||||
	//------------------------------------------------------
 | 
			
		||||
	start1 := time.Now()
 | 
			
		||||
	res, _, err = r.PubTickers(OP_SUBSCRIBE, args1)
 | 
			
		||||
	fmt.Println("args:", args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start1)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
	time.Sleep(300 * time.Second)
 | 
			
		||||
	//
 | 
			
		||||
	// start = time.Now()
 | 
			
		||||
	// res, _, err = r.PubInstruemnts(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	// if res {
 | 
			
		||||
	// usedTime := time.Since(start)
 | 
			
		||||
	// fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	// } else {
 | 
			
		||||
	// fmt.Println("取消订阅失败!", err)
 | 
			
		||||
	// }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// websocket交易
 | 
			
		||||
func wsJrpc() {
 | 
			
		||||
	ep := "wss://ws.okex.com:8443/ws/v5/private?brokerId=9999"
 | 
			
		||||
 | 
			
		||||
	// 填写您自己的APIKey信息
 | 
			
		||||
	apikey := "xxxx"
 | 
			
		||||
	secretKey := "xxxxx"
 | 
			
		||||
	passphrase := "xxxxx"
 | 
			
		||||
 | 
			
		||||
	var res bool
 | 
			
		||||
	var req_id string
 | 
			
		||||
 | 
			
		||||
	// 创建ws客户端
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 设置连接超时
 | 
			
		||||
	r.SetDailTimeout(time.Second * 2)
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer r.Stop()
 | 
			
		||||
 | 
			
		||||
	res, _, err = r.Login(apikey, secretKey, passphrase)
 | 
			
		||||
	if res {
 | 
			
		||||
		fmt.Println("登录成功!")
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("登录失败!", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	param := map[string]interface{}{}
 | 
			
		||||
	param["instId"] = "BTC-USDT"
 | 
			
		||||
	param["tdMode"] = "cash"
 | 
			
		||||
	param["side"] = "buy"
 | 
			
		||||
	param["ordType"] = "market"
 | 
			
		||||
	param["sz"] = "200"
 | 
			
		||||
	req_id = "00001"
 | 
			
		||||
 | 
			
		||||
	res, _, err = r.PlaceOrder(req_id, param)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("下单成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("下单失败!", usedTime.String(), err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	// 公共订阅
 | 
			
		||||
	wsPub()
 | 
			
		||||
 | 
			
		||||
	// 私有订阅
 | 
			
		||||
	// wsPriv()
 | 
			
		||||
 | 
			
		||||
	// websocket交易
 | 
			
		||||
	// wsJrpc()
 | 
			
		||||
 | 
			
		||||
	// rest请求
 | 
			
		||||
	// REST()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										283
									
								
								submodules/okex/readme.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								submodules/okex/readme.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,283 @@
 | 
			
		||||
# 简介
 | 
			
		||||
OKEX go版本的v5sdk,仅供学习交流使用。
 | 
			
		||||
(文档持续完善中)
 | 
			
		||||
# 项目说明
 | 
			
		||||
 | 
			
		||||
## REST调用
 | 
			
		||||
``` go
 | 
			
		||||
    // 设置您的APIKey
 | 
			
		||||
	apikey := APIKeyInfo{
 | 
			
		||||
		ApiKey:     "xxxx",
 | 
			
		||||
		SecKey:     "xxxx",
 | 
			
		||||
		PassPhrase: "xxxx",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 第三个参数代表是否为模拟环境,更多信息查看接口说明
 | 
			
		||||
	cli := NewRESTClient("https://www.okex.win", &apikey, true)
 | 
			
		||||
	rsp, err := cli.Get(context.Background(), "/api/v5/account/balance", nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Response:")
 | 
			
		||||
	fmt.Println("\thttp code: ", rsp.Code)
 | 
			
		||||
	fmt.Println("\t总耗时: ", rsp.TotalUsedTime)
 | 
			
		||||
	fmt.Println("\t请求耗时: ", rsp.ReqUsedTime)
 | 
			
		||||
	fmt.Println("\t返回消息: ", rsp.Body)
 | 
			
		||||
	fmt.Println("\terrCode: ", rsp.V5Response.Code)
 | 
			
		||||
	fmt.Println("\terrMsg: ", rsp.V5Response.Msg)
 | 
			
		||||
	fmt.Println("\tdata: ", rsp.V5Response.Data)
 | 
			
		||||
 ```
 | 
			
		||||
更多示例请查看rest/rest_test.go  
 | 
			
		||||
 | 
			
		||||
## websocket订阅
 | 
			
		||||
 | 
			
		||||
### 私有频道
 | 
			
		||||
```go
 | 
			
		||||
    ep := "wss://ws.okex.com:8443/ws/v5/private?brokerId=9999"
 | 
			
		||||
 | 
			
		||||
	// 填写您自己的APIKey信息
 | 
			
		||||
	apikey := "xxxx"
 | 
			
		||||
	secretKey := "xxxxx"
 | 
			
		||||
	passphrase := "xxxxx"
 | 
			
		||||
 | 
			
		||||
	// 创建ws客户端
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 设置连接超时
 | 
			
		||||
	r.SetDailTimeout(time.Second * 2)
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer r.Stop()
 | 
			
		||||
 | 
			
		||||
	var res bool
 | 
			
		||||
	// 私有频道需要登录
 | 
			
		||||
	res, _, err = r.Login(apikey, secretKey, passphrase)
 | 
			
		||||
	if res {
 | 
			
		||||
		fmt.Println("登录成功!")
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("登录失败!", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["ccy"] = "BTC"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	// 订阅账户频道
 | 
			
		||||
	res, _, err = r.PrivAccout(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!耗时:", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(100 * time.Second)
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	// 取消订阅账户频道
 | 
			
		||||
	res, _, err = r.PrivAccout(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
```
 | 
			
		||||
更多示例请查看ws/ws_priv_channel_test.go  
 | 
			
		||||
 | 
			
		||||
### 公有频道
 | 
			
		||||
```go
 | 
			
		||||
    ep := "wss://ws.okex.com:8443/ws/v5/public?brokerId=9999"
 | 
			
		||||
 | 
			
		||||
	// 创建ws客户端
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	// 设置连接超时
 | 
			
		||||
	r.SetDailTimeout(time.Second * 2)
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer r.Stop()
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instType"] = FUTURES
 | 
			
		||||
	//arg["instType"] = OPTION
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
 | 
			
		||||
	// 订阅产品频道
 | 
			
		||||
	res, _, err := r.PubInstruemnts(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(30 * time.Second)
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
 | 
			
		||||
	// 取消订阅产品频道
 | 
			
		||||
	res, _, err = r.PubInstruemnts(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
```
 | 
			
		||||
更多示例请查看ws/ws_pub_channel_test.go  
 | 
			
		||||
 | 
			
		||||
## websocket交易
 | 
			
		||||
```go
 | 
			
		||||
    ep := "wss://ws.okex.com:8443/ws/v5/private?brokerId=9999"
 | 
			
		||||
 | 
			
		||||
	// 填写您自己的APIKey信息
 | 
			
		||||
	apikey := "xxxx"
 | 
			
		||||
	secretKey := "xxxxx"
 | 
			
		||||
	passphrase := "xxxxx"
 | 
			
		||||
 | 
			
		||||
	var res bool
 | 
			
		||||
	var req_id string
 | 
			
		||||
 | 
			
		||||
	// 创建ws客户端
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 设置连接超时
 | 
			
		||||
	r.SetDailTimeout(time.Second * 2)
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer r.Stop()
 | 
			
		||||
 | 
			
		||||
	res, _, err = r.Login(apikey, secretKey, passphrase)
 | 
			
		||||
	if res {
 | 
			
		||||
		fmt.Println("登录成功!")
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("登录失败!", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	param := map[string]interface{}{}
 | 
			
		||||
	param["instId"] = "BTC-USDT"
 | 
			
		||||
	param["tdMode"] = "cash"
 | 
			
		||||
	param["side"] = "buy"
 | 
			
		||||
	param["ordType"] = "market"
 | 
			
		||||
	param["sz"] = "200"
 | 
			
		||||
	req_id = "00001"
 | 
			
		||||
 | 
			
		||||
	// 单个下单
 | 
			
		||||
	res, _, err = r.PlaceOrder(req_id, param)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("下单成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("下单失败!", usedTime.String(), err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
更多示例请查看ws/ws_jrpc_test.go  
 | 
			
		||||
 | 
			
		||||
## wesocket推送
 | 
			
		||||
websocket推送数据分为两种类型数据:`普通推送数据`和`深度类型数据`。  
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
ws/wImpl/BookData.go
 | 
			
		||||
 | 
			
		||||
// 普通推送
 | 
			
		||||
type MsgData struct {
 | 
			
		||||
	Arg  map[string]string `json:"arg"`
 | 
			
		||||
	Data []interface{}     `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 深度数据
 | 
			
		||||
type DepthData struct {
 | 
			
		||||
	Arg    map[string]string `json:"arg"`
 | 
			
		||||
	Action string            `json:"action"`
 | 
			
		||||
	Data   []DepthDetail     `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
如果需要对推送数据做处理用户可以自定义回调函数:
 | 
			
		||||
1. 全局消息处理的回调函数  
 | 
			
		||||
该回调函数会处理所有从服务端接受到的数据。
 | 
			
		||||
```go
 | 
			
		||||
/*
 | 
			
		||||
	添加全局消息处理的回调函数
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) AddMessageHook(fn ReceivedDataCallback) error {
 | 
			
		||||
	a.onMessageHook = fn
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
使用方法参见 ws/ws_test.go中测试用例TestAddMessageHook。
 | 
			
		||||
 | 
			
		||||
2. 订阅消息处理回调函数  
 | 
			
		||||
可以处理所有非深度类型的数据,包括 订阅/取消订阅,普通推送数据。
 | 
			
		||||
```go
 | 
			
		||||
/*
 | 
			
		||||
	添加订阅消息处理的回调函数
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) AddBookMsgHook(fn ReceivedMsgDataCallback) error {
 | 
			
		||||
	a.onBookMsgHook = fn
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
使用方法参见 ws/ws_test.go中测试用例TestAddBookedDataHook。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
3. 深度消息处理的回调函数  
 | 
			
		||||
这里需要说明的是,Wsclient提供了深度数据管理和自动checksum的功能,用户如果需要关闭此功能,只需要调用EnableAutoDepthMgr方法。
 | 
			
		||||
```go
 | 
			
		||||
/*
 | 
			
		||||
	添加深度消息处理的回调函数
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) AddDepthHook(fn ReceivedDepthDataCallback) error {
 | 
			
		||||
	a.onDepthHook = fn
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
使用方法参见 ws/ws_pub_channel_test.go中测试用例TestOrderBooks。
 | 
			
		||||
 | 
			
		||||
4. 错误消息类型回调函数  
 | 
			
		||||
```go
 | 
			
		||||
func (a *WsClient) AddErrMsgHook(fn ReceivedDataCallback) error {
 | 
			
		||||
	a.OnErrorHook = fn
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# 联系方式
 | 
			
		||||
邮箱:caron_co@163.com  
 | 
			
		||||
微信:caron_co
 | 
			
		||||
							
								
								
									
										35
									
								
								submodules/okex/rest/contants.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								submodules/okex/rest/contants.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
			
		||||
package rest
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	  http headers
 | 
			
		||||
	*/
 | 
			
		||||
	OK_ACCESS_KEY        = "OK-ACCESS-KEY"
 | 
			
		||||
	OK_ACCESS_SIGN       = "OK-ACCESS-SIGN"
 | 
			
		||||
	OK_ACCESS_TIMESTAMP  = "OK-ACCESS-TIMESTAMP"
 | 
			
		||||
	OK_ACCESS_PASSPHRASE = "OK-ACCESS-PASSPHRASE"
 | 
			
		||||
	X_SIMULATE_TRADING   = "x-simulated-trading"
 | 
			
		||||
 | 
			
		||||
	CONTENT_TYPE = "Content-Type"
 | 
			
		||||
	ACCEPT       = "Accept"
 | 
			
		||||
	COOKIE       = "Cookie"
 | 
			
		||||
	LOCALE       = "locale="
 | 
			
		||||
 | 
			
		||||
	APPLICATION_JSON      = "application/json"
 | 
			
		||||
	APPLICATION_JSON_UTF8 = "application/json; charset=UTF-8"
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	  i18n: internationalization
 | 
			
		||||
	*/
 | 
			
		||||
	ENGLISH            = "en_US"
 | 
			
		||||
	SIMPLIFIED_CHINESE = "zh_CN"
 | 
			
		||||
	//zh_TW || zh_HK
 | 
			
		||||
	TRADITIONAL_CHINESE = "zh_HK"
 | 
			
		||||
 | 
			
		||||
	GET = "GET"
 | 
			
		||||
	POST = "POST"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	
 | 
			
		||||
							
								
								
									
										0
									
								
								submodules/okex/rest/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								submodules/okex/rest/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										331
									
								
								submodules/okex/rest/rest.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										331
									
								
								submodules/okex/rest/rest.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,331 @@
 | 
			
		||||
package rest
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
	. "v5sdk_go/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type RESTAPI struct {
 | 
			
		||||
	EndPoint string `json:"endPoint"`
 | 
			
		||||
	// GET/POST
 | 
			
		||||
	Method     string                 `json:"method"`
 | 
			
		||||
	Uri        string                 `json:"uri"`
 | 
			
		||||
	Param      map[string]interface{} `json:"param"`
 | 
			
		||||
	Timeout    time.Duration
 | 
			
		||||
	ApiKeyInfo *APIKeyInfo
 | 
			
		||||
	isSimulate bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type APIKeyInfo struct {
 | 
			
		||||
	ApiKey     string
 | 
			
		||||
	PassPhrase string
 | 
			
		||||
	SecKey     string
 | 
			
		||||
	UserId     string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type RESTAPIResult struct {
 | 
			
		||||
	Url    string `json:"url"`
 | 
			
		||||
	Param  string `json:"param"`
 | 
			
		||||
	Header string `json:"header"`
 | 
			
		||||
	Code   int    `json:"code"`
 | 
			
		||||
	// 原始返回信息
 | 
			
		||||
	Body string `json:"body"`
 | 
			
		||||
	// okexV5返回的数据
 | 
			
		||||
	V5Response    Okexv5APIResponse `json:"v5Response"`
 | 
			
		||||
	ReqUsedTime   time.Duration     `json:"reqUsedTime"`
 | 
			
		||||
	TotalUsedTime time.Duration     `json:"totalUsedTime"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Okexv5APIResponse struct {
 | 
			
		||||
	Code string                   `json:"code"`
 | 
			
		||||
	Msg  string                   `json:"msg"`
 | 
			
		||||
	Data []map[string]interface{} `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	endPoint:请求地址
 | 
			
		||||
	apiKey
 | 
			
		||||
	isSimulate: 是否为模拟环境
 | 
			
		||||
*/
 | 
			
		||||
func NewRESTClient(endPoint string, apiKey *APIKeyInfo, isSimulate bool) *RESTAPI {
 | 
			
		||||
 | 
			
		||||
	res := &RESTAPI{
 | 
			
		||||
		EndPoint:   endPoint,
 | 
			
		||||
		ApiKeyInfo: apiKey,
 | 
			
		||||
		isSimulate: isSimulate,
 | 
			
		||||
		Timeout:    5 * time.Second,
 | 
			
		||||
	}
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewRESTAPI(ep, method, uri string, param *map[string]interface{}) *RESTAPI {
 | 
			
		||||
	//TODO:参数校验
 | 
			
		||||
	reqParam := make(map[string]interface{})
 | 
			
		||||
 | 
			
		||||
	if param != nil {
 | 
			
		||||
		reqParam = *param
 | 
			
		||||
	}
 | 
			
		||||
	res := &RESTAPI{
 | 
			
		||||
		EndPoint: ep,
 | 
			
		||||
		Method:   method,
 | 
			
		||||
		Uri:      uri,
 | 
			
		||||
		Param:    reqParam,
 | 
			
		||||
		Timeout:  150 * time.Second,
 | 
			
		||||
	}
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *RESTAPI) SetSimulate(b bool) *RESTAPI {
 | 
			
		||||
	this.isSimulate = b
 | 
			
		||||
	return this
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *RESTAPI) SetAPIKey(apiKey, secKey, passPhrase string) *RESTAPI {
 | 
			
		||||
	if this.ApiKeyInfo == nil {
 | 
			
		||||
		this.ApiKeyInfo = &APIKeyInfo{
 | 
			
		||||
			ApiKey:     apiKey,
 | 
			
		||||
			PassPhrase: passPhrase,
 | 
			
		||||
			SecKey:     secKey,
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		this.ApiKeyInfo.ApiKey = apiKey
 | 
			
		||||
		this.ApiKeyInfo.PassPhrase = passPhrase
 | 
			
		||||
		this.ApiKeyInfo.SecKey = secKey
 | 
			
		||||
	}
 | 
			
		||||
	return this
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *RESTAPI) SetUserId(userId string) *RESTAPI {
 | 
			
		||||
	if this.ApiKeyInfo == nil {
 | 
			
		||||
		fmt.Println("ApiKey为空")
 | 
			
		||||
		return this
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.ApiKeyInfo.UserId = userId
 | 
			
		||||
	return this
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *RESTAPI) SetTimeOut(timeout time.Duration) *RESTAPI {
 | 
			
		||||
	this.Timeout = timeout
 | 
			
		||||
	return this
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GET请求
 | 
			
		||||
func (this *RESTAPI) Get(ctx context.Context, uri string, param *map[string]interface{}) (res *RESTAPIResult, err error) {
 | 
			
		||||
	this.Method = GET
 | 
			
		||||
	this.Uri = uri
 | 
			
		||||
 | 
			
		||||
	reqParam := make(map[string]interface{})
 | 
			
		||||
 | 
			
		||||
	if param != nil {
 | 
			
		||||
		reqParam = *param
 | 
			
		||||
	}
 | 
			
		||||
	this.Param = reqParam
 | 
			
		||||
	return this.Run(ctx)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// POST请求
 | 
			
		||||
func (this *RESTAPI) Post(ctx context.Context, uri string, param *map[string]interface{}) (res *RESTAPIResult, err error) {
 | 
			
		||||
	this.Method = POST
 | 
			
		||||
	this.Uri = uri
 | 
			
		||||
 | 
			
		||||
	reqParam := make(map[string]interface{})
 | 
			
		||||
 | 
			
		||||
	if param != nil {
 | 
			
		||||
		reqParam = *param
 | 
			
		||||
	}
 | 
			
		||||
	this.Param = reqParam
 | 
			
		||||
 | 
			
		||||
	return this.Run(ctx)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *RESTAPI) Run(ctx context.Context) (res *RESTAPIResult, err error) {
 | 
			
		||||
 | 
			
		||||
	if this.ApiKeyInfo == nil {
 | 
			
		||||
		err = errors.New("APIKey不可为空")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	procStart := time.Now()
 | 
			
		||||
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if res != nil {
 | 
			
		||||
			res.TotalUsedTime = time.Since(procStart)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	client := &http.Client{
 | 
			
		||||
		Timeout: this.Timeout,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uri, body, err := this.GenReqInfo()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := this.EndPoint + uri
 | 
			
		||||
	bodyBuf := new(bytes.Buffer)
 | 
			
		||||
	bodyBuf.ReadFrom(strings.NewReader(body))
 | 
			
		||||
 | 
			
		||||
	req, err := http.NewRequest(this.Method, url, bodyBuf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res = &RESTAPIResult{
 | 
			
		||||
		Url:   url,
 | 
			
		||||
		Param: body,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Sign and set request headers
 | 
			
		||||
	timestamp := IsoTime()
 | 
			
		||||
	preHash := PreHashString(timestamp, this.Method, uri, body)
 | 
			
		||||
	//log.Println("preHash:", preHash)
 | 
			
		||||
	sign, err := HmacSha256Base64Signer(preHash, this.ApiKeyInfo.SecKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	//log.Println("sign:", sign)
 | 
			
		||||
	headStr := this.SetHeaders(req, timestamp, sign)
 | 
			
		||||
	res.Header = headStr
 | 
			
		||||
 | 
			
		||||
	this.PrintRequest(req, body, preHash)
 | 
			
		||||
	resp, err := client.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("请求失败!", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	res.ReqUsedTime = time.Since(procStart)
 | 
			
		||||
 | 
			
		||||
	resBuff, err := ioutil.ReadAll(resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("获取请求结果失败!", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res.Body = string(resBuff)
 | 
			
		||||
	res.Code = resp.StatusCode
 | 
			
		||||
 | 
			
		||||
	// 解析结果
 | 
			
		||||
	var v5rsp Okexv5APIResponse
 | 
			
		||||
	err = json.Unmarshal(resBuff, &v5rsp)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("解析v5返回失败!", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res.V5Response = v5rsp
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	生成请求对应的参数
 | 
			
		||||
*/
 | 
			
		||||
func (this *RESTAPI) GenReqInfo() (uri string, body string, err error) {
 | 
			
		||||
	uri = this.Uri
 | 
			
		||||
 | 
			
		||||
	switch this.Method {
 | 
			
		||||
	case GET:
 | 
			
		||||
		getParam := []string{}
 | 
			
		||||
 | 
			
		||||
		if len(this.Param) == 0 {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for k, v := range this.Param {
 | 
			
		||||
			getParam = append(getParam, fmt.Sprintf("%v=%v", k, v))
 | 
			
		||||
		}
 | 
			
		||||
		uri = uri + "?" + strings.Join(getParam, "&")
 | 
			
		||||
 | 
			
		||||
	case POST:
 | 
			
		||||
 | 
			
		||||
		var rawBody []byte
 | 
			
		||||
		rawBody, err = json.Marshal(this.Param)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		body = string(rawBody)
 | 
			
		||||
	default:
 | 
			
		||||
		err = errors.New("request type unknown!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
   Set http request headers:
 | 
			
		||||
   Accept: application/json
 | 
			
		||||
   Content-Type: application/json; charset=UTF-8  (default)
 | 
			
		||||
   Cookie: locale=en_US        (English)
 | 
			
		||||
   OK-ACCESS-KEY: (Your setting)
 | 
			
		||||
   OK-ACCESS-SIGN: (Use your setting, auto sign and add)
 | 
			
		||||
   OK-ACCESS-TIMESTAMP: (Auto add)
 | 
			
		||||
   OK-ACCESS-PASSPHRASE: Your setting
 | 
			
		||||
*/
 | 
			
		||||
func (this *RESTAPI) SetHeaders(request *http.Request, timestamp string, sign string) (header string) {
 | 
			
		||||
 | 
			
		||||
	request.Header.Add(ACCEPT, APPLICATION_JSON)
 | 
			
		||||
	header += ACCEPT + ":" + APPLICATION_JSON + "\n"
 | 
			
		||||
 | 
			
		||||
	request.Header.Add(CONTENT_TYPE, APPLICATION_JSON_UTF8)
 | 
			
		||||
	header += CONTENT_TYPE + ":" + APPLICATION_JSON_UTF8 + "\n"
 | 
			
		||||
 | 
			
		||||
	request.Header.Add(COOKIE, LOCALE+ENGLISH)
 | 
			
		||||
	header += COOKIE + ":" + LOCALE + ENGLISH + "\n"
 | 
			
		||||
 | 
			
		||||
	request.Header.Add(OK_ACCESS_KEY, this.ApiKeyInfo.ApiKey)
 | 
			
		||||
	header += OK_ACCESS_KEY + ":" + this.ApiKeyInfo.ApiKey + "\n"
 | 
			
		||||
 | 
			
		||||
	request.Header.Add(OK_ACCESS_SIGN, sign)
 | 
			
		||||
	header += OK_ACCESS_SIGN + ":" + sign + "\n"
 | 
			
		||||
 | 
			
		||||
	request.Header.Add(OK_ACCESS_TIMESTAMP, timestamp)
 | 
			
		||||
	header += OK_ACCESS_TIMESTAMP + ":" + timestamp + "\n"
 | 
			
		||||
 | 
			
		||||
	request.Header.Add(OK_ACCESS_PASSPHRASE, this.ApiKeyInfo.PassPhrase)
 | 
			
		||||
	header += OK_ACCESS_PASSPHRASE + ":" + this.ApiKeyInfo.PassPhrase + "\n"
 | 
			
		||||
 | 
			
		||||
	//模拟盘交易标记
 | 
			
		||||
	if this.isSimulate {
 | 
			
		||||
		request.Header.Add(X_SIMULATE_TRADING, "1")
 | 
			
		||||
		header += X_SIMULATE_TRADING + ":1" + "\n"
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	打印header信息
 | 
			
		||||
*/
 | 
			
		||||
func (this *RESTAPI) PrintRequest(request *http.Request, body string, preHash string) {
 | 
			
		||||
	if this.ApiKeyInfo.SecKey != "" {
 | 
			
		||||
		fmt.Println("  Secret-Key: " + this.ApiKeyInfo.SecKey)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println("  Request(" + IsoTime() + "):")
 | 
			
		||||
	fmt.Println("\tUrl: " + request.URL.String())
 | 
			
		||||
	fmt.Println("\tMethod: " + strings.ToUpper(request.Method))
 | 
			
		||||
	if len(request.Header) > 0 {
 | 
			
		||||
		fmt.Println("\tHeaders: ")
 | 
			
		||||
		for k, v := range request.Header {
 | 
			
		||||
			if strings.Contains(k, "Ok-") {
 | 
			
		||||
				k = strings.ToUpper(k)
 | 
			
		||||
			}
 | 
			
		||||
			fmt.Println("\t\t" + k + ": " + v[0])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println("\tBody: " + body)
 | 
			
		||||
	if preHash != "" {
 | 
			
		||||
		fmt.Println("  PreHash: " + preHash)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										100
									
								
								submodules/okex/rest/rest_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								submodules/okex/rest/rest_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,100 @@
 | 
			
		||||
package rest
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	GET请求
 | 
			
		||||
*/
 | 
			
		||||
func TestRESTAPIGet(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	rest := NewRESTAPI("https://www.okex.win", GET, "/api/v5/account/balance", nil)
 | 
			
		||||
	rest.SetSimulate(true).SetAPIKey("xxxx", "xxxx", "xxxx")
 | 
			
		||||
	rest.SetUserId("xxxxx")
 | 
			
		||||
	response, err := rest.Run(context.Background())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Response:")
 | 
			
		||||
	fmt.Println("\thttp code: ", response.Code)
 | 
			
		||||
	fmt.Println("\t总耗时: ", response.TotalUsedTime)
 | 
			
		||||
	fmt.Println("\t请求耗时: ", response.ReqUsedTime)
 | 
			
		||||
	fmt.Println("\t返回消息: ", response.Body)
 | 
			
		||||
	fmt.Println("\terrCode: ", response.V5Response.Code)
 | 
			
		||||
	fmt.Println("\terrMsg: ", response.V5Response.Msg)
 | 
			
		||||
	fmt.Println("\tdata: ", response.V5Response.Data)
 | 
			
		||||
 | 
			
		||||
	// 请求的另一种方式
 | 
			
		||||
	apikey := APIKeyInfo{
 | 
			
		||||
		ApiKey:     "xxxxx",
 | 
			
		||||
		SecKey:     "xxxxx",
 | 
			
		||||
		PassPhrase: "xxx",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cli := NewRESTClient("https://www.okex.win", &apikey, true)
 | 
			
		||||
	rsp, err := cli.Get(context.Background(), "/api/v5/account/balance", nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Response:")
 | 
			
		||||
	fmt.Println("\thttp code: ", rsp.Code)
 | 
			
		||||
	fmt.Println("\t总耗时: ", rsp.TotalUsedTime)
 | 
			
		||||
	fmt.Println("\t请求耗时: ", rsp.ReqUsedTime)
 | 
			
		||||
	fmt.Println("\t返回消息: ", rsp.Body)
 | 
			
		||||
	fmt.Println("\terrCode: ", rsp.V5Response.Code)
 | 
			
		||||
	fmt.Println("\terrMsg: ", rsp.V5Response.Msg)
 | 
			
		||||
	fmt.Println("\tdata: ", rsp.V5Response.Data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	POST请求
 | 
			
		||||
*/
 | 
			
		||||
func TestRESTAPIPost(t *testing.T) {
 | 
			
		||||
	param := make(map[string]interface{})
 | 
			
		||||
	param["greeksType"] = "PA"
 | 
			
		||||
 | 
			
		||||
	rest := NewRESTAPI("https://www.okex.win", POST, "/api/v5/account/set-greeks", ¶m)
 | 
			
		||||
	rest.SetSimulate(true).SetAPIKey("xxxx", "xxxx", "xxxx")
 | 
			
		||||
	response, err := rest.Run(context.Background())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Response:")
 | 
			
		||||
	fmt.Println("\thttp code: ", response.Code)
 | 
			
		||||
	fmt.Println("\t总耗时: ", response.TotalUsedTime)
 | 
			
		||||
	fmt.Println("\t请求耗时: ", response.ReqUsedTime)
 | 
			
		||||
	fmt.Println("\t返回消息: ", response.Body)
 | 
			
		||||
	fmt.Println("\terrCode: ", response.V5Response.Code)
 | 
			
		||||
	fmt.Println("\terrMsg: ", response.V5Response.Msg)
 | 
			
		||||
	fmt.Println("\tdata: ", response.V5Response.Data)
 | 
			
		||||
 | 
			
		||||
	// 请求的另一种方式
 | 
			
		||||
	apikey := APIKeyInfo{
 | 
			
		||||
		ApiKey:     "xxxx",
 | 
			
		||||
		SecKey:     "xxxxx",
 | 
			
		||||
		PassPhrase: "xxxx",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cli := NewRESTClient("https://www.okex.win", &apikey, true)
 | 
			
		||||
	rsp, err := cli.Post(context.Background(), "/api/v5/account/set-greeks", ¶m)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Println("Response:")
 | 
			
		||||
	fmt.Println("\thttp code: ", rsp.Code)
 | 
			
		||||
	fmt.Println("\t总耗时: ", rsp.TotalUsedTime)
 | 
			
		||||
	fmt.Println("\t请求耗时: ", rsp.ReqUsedTime)
 | 
			
		||||
	fmt.Println("\t返回消息: ", rsp.Body)
 | 
			
		||||
	fmt.Println("\terrCode: ", rsp.V5Response.Code)
 | 
			
		||||
	fmt.Println("\terrMsg: ", rsp.V5Response.Msg)
 | 
			
		||||
	fmt.Println("\tdata: ", rsp.V5Response.Data)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										0
									
								
								submodules/okex/utils/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								submodules/okex/utils/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										102
									
								
								submodules/okex/utils/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								submodules/okex/utils/utils.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,102 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"compress/flate"
 | 
			
		||||
	"crypto/hmac"
 | 
			
		||||
	"crypto/sha256"
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"log"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
	//"net/http"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 Get a epoch time
 | 
			
		||||
  eg: 1521221737
 | 
			
		||||
*/
 | 
			
		||||
func EpochTime() string {
 | 
			
		||||
	millisecond := time.Now().UnixNano() / 1000000
 | 
			
		||||
	epoch := strconv.Itoa(int(millisecond))
 | 
			
		||||
	epochBytes := []byte(epoch)
 | 
			
		||||
	epoch = string(epochBytes[:10])
 | 
			
		||||
	return epoch
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 signing a message
 | 
			
		||||
 using: hmac sha256 + base64
 | 
			
		||||
  eg:
 | 
			
		||||
    message = Pre_hash function comment
 | 
			
		||||
    secretKey = E65791902180E9EF4510DB6A77F6EBAE
 | 
			
		||||
 | 
			
		||||
  return signed string = TO6uwdqz+31SIPkd4I+9NiZGmVH74dXi+Fd5X0EzzSQ=
 | 
			
		||||
*/
 | 
			
		||||
func HmacSha256Base64Signer(message string, secretKey string) (string, error) {
 | 
			
		||||
	mac := hmac.New(sha256.New, []byte(secretKey))
 | 
			
		||||
	_, err := mac.Write([]byte(message))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return base64.StdEncoding.EncodeToString(mac.Sum(nil)), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 the pre hash string
 | 
			
		||||
  eg:
 | 
			
		||||
    timestamp = 2018-03-08T10:59:25.789Z
 | 
			
		||||
    method  = POST
 | 
			
		||||
    request_path = /orders?before=2&limit=30
 | 
			
		||||
    body = {"product_id":"BTC-USD-0309","order_id":"377454671037440"}
 | 
			
		||||
 | 
			
		||||
  return pre hash string = 2018-03-08T10:59:25.789ZPOST/orders?before=2&limit=30{"product_id":"BTC-USD-0309","order_id":"377454671037440"}
 | 
			
		||||
*/
 | 
			
		||||
func PreHashString(timestamp string, method string, requestPath string, body string) string {
 | 
			
		||||
	return timestamp + strings.ToUpper(method) + requestPath + body
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 struct convert json string
 | 
			
		||||
*/
 | 
			
		||||
func Struct2JsonString(raw interface{}) (jsonString string, err error) {
 | 
			
		||||
	//fmt.Println("转化json,", raw)
 | 
			
		||||
	data, err := json.Marshal(raw)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Println("convert json failed!", err)
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	//log.Println(string(data))
 | 
			
		||||
	return string(data), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 解压缩消息
 | 
			
		||||
func GzipDecode(in []byte) ([]byte, error) {
 | 
			
		||||
	reader := flate.NewReader(bytes.NewReader(in))
 | 
			
		||||
	defer reader.Close()
 | 
			
		||||
 | 
			
		||||
	return ioutil.ReadAll(reader)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 Get a iso time
 | 
			
		||||
  eg: 2018-03-16T18:02:48.284Z
 | 
			
		||||
*/
 | 
			
		||||
func IsoTime() string {
 | 
			
		||||
	utcTime := time.Now().UTC()
 | 
			
		||||
	iso := utcTime.String()
 | 
			
		||||
	isoBytes := []byte(iso)
 | 
			
		||||
	iso = string(isoBytes[:10]) + "T" + string(isoBytes[11:23]) + "Z"
 | 
			
		||||
	return iso
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								submodules/okex/utils/utils_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								submodules/okex/utils/utils_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestHmacSha256Base64Signer(t *testing.T) {
 | 
			
		||||
	raw := `2021-04-06T03:33:21.681ZPOST/api/v5/trade/order{"instId":"ETH-USDT-SWAP","ordType":"limit","px":"2300","side":"sell","sz":"1","tdMode":"cross"}`
 | 
			
		||||
	key := "1A9E86759F2D2AA16E389FD3B7F8273E"
 | 
			
		||||
	res, err := HmacSha256Base64Signer(raw, key)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(res)
 | 
			
		||||
	t.Log(res)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										0
									
								
								submodules/okex/ws/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								submodules/okex/ws/go.mod
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										75
									
								
								submodules/okex/ws/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								submodules/okex/ws/utils.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"log"
 | 
			
		||||
	"runtime/debug"
 | 
			
		||||
	. "v5sdk_go/ws/wImpl"
 | 
			
		||||
	. "v5sdk_go/ws/wInterface"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 判断返回结果成功失败
 | 
			
		||||
func checkResult(wsReq WSReqData, wsRsps []*Msg) (res bool, err error) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		a := recover()
 | 
			
		||||
		if a != nil {
 | 
			
		||||
			log.Printf("Receive End. Recover msg: %+v", a)
 | 
			
		||||
			debug.PrintStack()
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	res = false
 | 
			
		||||
	if len(wsRsps) == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, v := range wsRsps {
 | 
			
		||||
		switch v.Info.(type) {
 | 
			
		||||
		case ErrData:
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if wsReq.GetType() != v.Info.(WSRspData).MsgType() {
 | 
			
		||||
			err = errors.New("消息类型不一致")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//检查所有频道是否都更新成功
 | 
			
		||||
	if wsReq.GetType() == MSG_NORMAL {
 | 
			
		||||
		req, ok := wsReq.(ReqData)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			log.Println("类型转化失败", req)
 | 
			
		||||
			err = errors.New("类型转化失败")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for idx, _ := range req.Args {
 | 
			
		||||
			ok := false
 | 
			
		||||
			i_req := req.Args[idx]
 | 
			
		||||
			//fmt.Println("检查",i_req)
 | 
			
		||||
			for i, _ := range wsRsps {
 | 
			
		||||
				info, _ := wsRsps[i].Info.(RspData)
 | 
			
		||||
				//fmt.Println("<<",info)
 | 
			
		||||
				if info.Event == req.Op && info.Arg["channel"] == i_req["channel"] && info.Arg["instType"] == i_req["instType"] {
 | 
			
		||||
					ok = true
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !ok {
 | 
			
		||||
				err = errors.New("未得到所有的期望的返回结果")
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		for i, _ := range wsRsps {
 | 
			
		||||
			info, _ := wsRsps[i].Info.(JRPCRsp)
 | 
			
		||||
			if info.Code != "0" {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res = true
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										226
									
								
								submodules/okex/ws/wImpl/BookData.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										226
									
								
								submodules/okex/ws/wImpl/BookData.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,226 @@
 | 
			
		||||
/*
 | 
			
		||||
	订阅频道后收到的推送数据
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package wImpl
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"hash/crc32"
 | 
			
		||||
	"log"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 普通推送
 | 
			
		||||
type MsgData struct {
 | 
			
		||||
	Arg  map[string]string `json:"arg"`
 | 
			
		||||
	Data []interface{}     `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 深度数据
 | 
			
		||||
type DepthData struct {
 | 
			
		||||
	Arg    map[string]string `json:"arg"`
 | 
			
		||||
	Action string            `json:"action"`
 | 
			
		||||
	Data   []DepthDetail     `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type DepthDetail struct {
 | 
			
		||||
	Asks     [][]string `json:"asks"`
 | 
			
		||||
	Bids     [][]string `json:"bids"`
 | 
			
		||||
	Ts       string     `json:"ts"`
 | 
			
		||||
	Checksum int32      `json:"checksum"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	深度数据校验
 | 
			
		||||
*/
 | 
			
		||||
func (this *DepthData) CheckSum(snap *DepthDetail) (pDepData *DepthDetail, err error) {
 | 
			
		||||
 | 
			
		||||
	if len(this.Data) != 1 {
 | 
			
		||||
		err = errors.New("深度数据错误!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if this.Action == DEPTH_SNAPSHOT {
 | 
			
		||||
		_, cs := CalCrc32(this.Data[0].Asks, this.Data[0].Bids)
 | 
			
		||||
 | 
			
		||||
		if cs != this.Data[0].Checksum {
 | 
			
		||||
			err = errors.New("校验失败!")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		pDepData = &this.Data[0]
 | 
			
		||||
		log.Println("snapshot校验成功", this.Data[0].Checksum)
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if this.Action == DEPTH_UPDATE {
 | 
			
		||||
		if snap == nil {
 | 
			
		||||
			err = errors.New("深度快照数据不可为空!")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pDepData, err = MergDepthData(*snap, this.Data[0], this.Data[0].Checksum)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		log.Println("update校验成功", this.Data[0].Checksum)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CalCrc32(askDepths [][]string, bidDepths [][]string) (bytes.Buffer, int32) {
 | 
			
		||||
 | 
			
		||||
	crc32BaseBuffer := bytes.Buffer{}
 | 
			
		||||
	crcAskDepth, crcBidDepth := 25, 25
 | 
			
		||||
 | 
			
		||||
	if len(askDepths) < 25 {
 | 
			
		||||
		crcAskDepth = len(askDepths)
 | 
			
		||||
	}
 | 
			
		||||
	if len(bidDepths) < 25 {
 | 
			
		||||
		crcBidDepth = len(bidDepths)
 | 
			
		||||
	}
 | 
			
		||||
	if crcAskDepth == crcBidDepth {
 | 
			
		||||
		for i := 0; i < crcAskDepth; i++ {
 | 
			
		||||
			if crc32BaseBuffer.Len() > 0 {
 | 
			
		||||
				crc32BaseBuffer.WriteString(":")
 | 
			
		||||
			}
 | 
			
		||||
			crc32BaseBuffer.WriteString(
 | 
			
		||||
				fmt.Sprintf("%v:%v:%v:%v",
 | 
			
		||||
					(bidDepths)[i][0], (bidDepths)[i][1],
 | 
			
		||||
					(askDepths)[i][0], (askDepths)[i][1]))
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
 | 
			
		||||
		var crcArr []string
 | 
			
		||||
		for i, j := 0, 0; i < crcBidDepth || j < crcAskDepth; {
 | 
			
		||||
 | 
			
		||||
			if i < crcBidDepth {
 | 
			
		||||
				crcArr = append(crcArr, fmt.Sprintf("%v:%v", (bidDepths)[i][0], (bidDepths)[i][1]))
 | 
			
		||||
				i++
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if j < crcAskDepth {
 | 
			
		||||
				crcArr = append(crcArr, fmt.Sprintf("%v:%v", (askDepths)[j][0], (askDepths)[j][1]))
 | 
			
		||||
				j++
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		crc32BaseBuffer.WriteString(strings.Join(crcArr, ":"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	expectCrc32 := int32(crc32.ChecksumIEEE(crc32BaseBuffer.Bytes()))
 | 
			
		||||
 | 
			
		||||
	return crc32BaseBuffer, expectCrc32
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	深度合并的内部方法
 | 
			
		||||
	返回结果:
 | 
			
		||||
	res:合并后的深度
 | 
			
		||||
	index: 最新的 ask1/bids1 的索引
 | 
			
		||||
*/
 | 
			
		||||
func mergeDepth(oldDepths [][]string, newDepths [][]string, method string) (res [][]string, err error) {
 | 
			
		||||
 | 
			
		||||
	oldIdx, newIdx := 0, 0
 | 
			
		||||
 | 
			
		||||
	for oldIdx < len(oldDepths) && newIdx < len(newDepths) {
 | 
			
		||||
 | 
			
		||||
		oldItem := oldDepths[oldIdx]
 | 
			
		||||
		newItem := newDepths[newIdx]
 | 
			
		||||
		var oldPrice, newPrice float64
 | 
			
		||||
		oldPrice, err = strconv.ParseFloat(oldItem[0], 10)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		newPrice, err = strconv.ParseFloat(newItem[0], 10)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if oldPrice == newPrice {
 | 
			
		||||
			if newItem[1] != "0" {
 | 
			
		||||
				res = append(res, newItem)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			oldIdx++
 | 
			
		||||
			newIdx++
 | 
			
		||||
		} else {
 | 
			
		||||
			switch method {
 | 
			
		||||
			// 降序
 | 
			
		||||
			case "bids":
 | 
			
		||||
				if oldPrice < newPrice {
 | 
			
		||||
					res = append(res, newItem)
 | 
			
		||||
					newIdx++
 | 
			
		||||
				} else {
 | 
			
		||||
 | 
			
		||||
					res = append(res, oldItem)
 | 
			
		||||
					oldIdx++
 | 
			
		||||
				}
 | 
			
		||||
			// 升序
 | 
			
		||||
			case "asks":
 | 
			
		||||
				if oldPrice > newPrice {
 | 
			
		||||
					res = append(res, newItem)
 | 
			
		||||
					newIdx++
 | 
			
		||||
				} else {
 | 
			
		||||
 | 
			
		||||
					res = append(res, oldItem)
 | 
			
		||||
					oldIdx++
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if oldIdx < len(oldDepths) {
 | 
			
		||||
		res = append(res, oldDepths[oldIdx:]...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if newIdx < len(newDepths) {
 | 
			
		||||
		res = append(res, newDepths[newIdx:]...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	深度合并,并校验
 | 
			
		||||
*/
 | 
			
		||||
func MergDepthData(snap DepthDetail, update DepthDetail, expChecksum int32) (res *DepthDetail, err error) {
 | 
			
		||||
 | 
			
		||||
	newAskDepths, err1 := mergeDepth(snap.Asks, update.Asks, "asks")
 | 
			
		||||
	if err1 != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// log.Println("old Ask - ", snap.Asks)
 | 
			
		||||
	// log.Println("update Ask - ", update.Asks)
 | 
			
		||||
	// log.Println("new Ask - ", newAskDepths)
 | 
			
		||||
	newBidDepths, err2 := mergeDepth(snap.Bids, update.Bids, "bids")
 | 
			
		||||
	if err2 != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// log.Println("old Bids - ", snap.Bids)
 | 
			
		||||
	// log.Println("update Bids - ", update.Bids)
 | 
			
		||||
	// log.Println("new Bids - ", newBidDepths)
 | 
			
		||||
 | 
			
		||||
	cBuf, checksum := CalCrc32(newAskDepths, newBidDepths)
 | 
			
		||||
	if checksum != expChecksum {
 | 
			
		||||
		err = errors.New("校验失败!")
 | 
			
		||||
		log.Println("buffer:", cBuf.String())
 | 
			
		||||
		log.Fatal(checksum, expChecksum)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res = &DepthDetail{
 | 
			
		||||
		Asks:     newAskDepths,
 | 
			
		||||
		Bids:     newBidDepths,
 | 
			
		||||
		Ts:       update.Ts,
 | 
			
		||||
		Checksum: update.Checksum,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								submodules/okex/ws/wImpl/ErrData.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								submodules/okex/ws/wImpl/ErrData.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
/*
 | 
			
		||||
	错误数据
 | 
			
		||||
*/
 | 
			
		||||
package wImpl
 | 
			
		||||
 | 
			
		||||
// 服务端请求错误返回消息格式
 | 
			
		||||
type ErrData struct {
 | 
			
		||||
	Event string `json:"event"`
 | 
			
		||||
	Code  string `json:"code"`
 | 
			
		||||
	Msg   string `json:"msg"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										50
									
								
								submodules/okex/ws/wImpl/JRPCData.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								submodules/okex/ws/wImpl/JRPCData.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
			
		||||
/*
 | 
			
		||||
	JRPC请求/响应数据
 | 
			
		||||
*/
 | 
			
		||||
package wImpl
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	. "v5sdk_go/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// jrpc请求结构体
 | 
			
		||||
type JRPCReq struct {
 | 
			
		||||
	Id   string                   `json:"id"`
 | 
			
		||||
	Op   string                   `json:"op"`
 | 
			
		||||
	Args []map[string]interface{} `json:"args"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r JRPCReq) GetType() int {
 | 
			
		||||
	return MSG_JRPC
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r JRPCReq) ToString() string {
 | 
			
		||||
	data, err := Struct2JsonString(r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	return data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r JRPCReq) Len() int {
 | 
			
		||||
	return 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// jrpc响应结构体
 | 
			
		||||
type JRPCRsp struct {
 | 
			
		||||
	Id   string                   `json:"id"`
 | 
			
		||||
	Op   string                   `json:"op"`
 | 
			
		||||
	Data []map[string]interface{} `json:"data"`
 | 
			
		||||
	Code string                   `json:"code"`
 | 
			
		||||
	Msg  string                   `json:"msg"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r JRPCRsp) MsgType() int {
 | 
			
		||||
	return MSG_JRPC
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r JRPCRsp) String() string {
 | 
			
		||||
	raw, _ := json.Marshal(r)
 | 
			
		||||
	return string(raw)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								submodules/okex/ws/wImpl/ReqData.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								submodules/okex/ws/wImpl/ReqData.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,47 @@
 | 
			
		||||
/*
 | 
			
		||||
	普通订阅请求和响应的数据格式
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package wImpl
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	. "v5sdk_go/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 客户端请求消息格式
 | 
			
		||||
type ReqData struct {
 | 
			
		||||
	Op   string              `json:"op"`
 | 
			
		||||
	Args []map[string]string `json:"args"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r ReqData) GetType() int {
 | 
			
		||||
	return MSG_NORMAL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r ReqData) ToString() string {
 | 
			
		||||
	data, err := Struct2JsonString(r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	return data
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r ReqData) Len() int {
 | 
			
		||||
	return len(r.Args)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 服务端请求响应消息格式
 | 
			
		||||
type RspData struct {
 | 
			
		||||
	Event string            `json:"event"`
 | 
			
		||||
	Arg   map[string]string `json:"arg"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r RspData) MsgType() int {
 | 
			
		||||
	return MSG_NORMAL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r RspData) String() string {
 | 
			
		||||
	raw, _ := json.Marshal(r)
 | 
			
		||||
	return string(raw)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										241
									
								
								submodules/okex/ws/wImpl/contants.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								submodules/okex/ws/wImpl/contants.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,241 @@
 | 
			
		||||
package wImpl
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"regexp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	MSG_NORMAL = iota
 | 
			
		||||
	MSG_JRPC
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//事件
 | 
			
		||||
type Event int
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	EventID
 | 
			
		||||
*/
 | 
			
		||||
const (
 | 
			
		||||
	EVENT_UNKNOWN Event = iota
 | 
			
		||||
	EVENT_ERROR
 | 
			
		||||
	EVENT_PING
 | 
			
		||||
	EVENT_LOGIN
 | 
			
		||||
 | 
			
		||||
	//订阅公共频道
 | 
			
		||||
	EVENT_BOOK_INSTRUMENTS
 | 
			
		||||
	EVENT_STATUS
 | 
			
		||||
	EVENT_BOOK_TICKERS
 | 
			
		||||
	EVENT_BOOK_OPEN_INTEREST
 | 
			
		||||
	EVENT_BOOK_KLINE
 | 
			
		||||
	EVENT_BOOK_TRADE
 | 
			
		||||
	EVENT_BOOK_ESTIMATE_PRICE
 | 
			
		||||
	EVENT_BOOK_MARK_PRICE
 | 
			
		||||
	EVENT_BOOK_MARK_PRICE_CANDLE_CHART
 | 
			
		||||
	EVENT_BOOK_LIMIT_PRICE
 | 
			
		||||
	EVENT_BOOK_ORDER_BOOK
 | 
			
		||||
	EVENT_BOOK_ORDER_BOOK5
 | 
			
		||||
	EVENT_BOOK_ORDER_BOOK_TBT
 | 
			
		||||
	EVENT_BOOK_ORDER_BOOK50_TBT
 | 
			
		||||
	EVENT_BOOK_OPTION_SUMMARY
 | 
			
		||||
	EVENT_BOOK_FUND_RATE
 | 
			
		||||
	EVENT_BOOK_KLINE_INDEX
 | 
			
		||||
	EVENT_BOOK_INDEX_TICKERS
 | 
			
		||||
 | 
			
		||||
	//订阅私有频道
 | 
			
		||||
	EVENT_BOOK_ACCOUNT
 | 
			
		||||
	EVENT_BOOK_POSTION
 | 
			
		||||
	EVENT_BOOK_ORDER
 | 
			
		||||
	EVENT_BOOK_ALG_ORDER
 | 
			
		||||
	EVENT_BOOK_B_AND_P
 | 
			
		||||
 | 
			
		||||
	// JRPC
 | 
			
		||||
	EVENT_PLACE_ORDER
 | 
			
		||||
	EVENT_PLACE_BATCH_ORDERS
 | 
			
		||||
	EVENT_CANCEL_ORDER
 | 
			
		||||
	EVENT_CANCEL_BATCH_ORDERS
 | 
			
		||||
	EVENT_AMEND_ORDER
 | 
			
		||||
	EVENT_AMEND_BATCH_ORDERS
 | 
			
		||||
 | 
			
		||||
	//订阅返回数据
 | 
			
		||||
	EVENT_BOOKED_DATA
 | 
			
		||||
	EVENT_DEPTH_DATA
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	EventID,事件名称,channel
 | 
			
		||||
	注: 带有周期参数的频道 如 行情频道 ,需要将channel写为 正则表达模式方便 类型匹配,如 "^candle*"
 | 
			
		||||
*/
 | 
			
		||||
var EVENT_TABLE = [][]interface{}{
 | 
			
		||||
	// 未知的消息
 | 
			
		||||
	{EVENT_UNKNOWN, "未知", ""},
 | 
			
		||||
	// 错误的消息
 | 
			
		||||
	{EVENT_ERROR, "错误", ""},
 | 
			
		||||
	// Ping
 | 
			
		||||
	{EVENT_PING, "ping", ""},
 | 
			
		||||
	// 登陆
 | 
			
		||||
	{EVENT_LOGIN, "登录", ""},
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
		订阅公共频道
 | 
			
		||||
	*/
 | 
			
		||||
 | 
			
		||||
	{EVENT_BOOK_INSTRUMENTS, "产品", "instruments"},
 | 
			
		||||
	{EVENT_STATUS, "status", "status"},
 | 
			
		||||
	{EVENT_BOOK_TICKERS, "行情", "tickers"},
 | 
			
		||||
	{EVENT_BOOK_OPEN_INTEREST, "持仓总量", "open-interest"},
 | 
			
		||||
	{EVENT_BOOK_KLINE, "K线", "candle"},
 | 
			
		||||
	{EVENT_BOOK_TRADE, "交易", "trades"},
 | 
			
		||||
	{EVENT_BOOK_ESTIMATE_PRICE, "预估交割/行权价格", "estimated-price"},
 | 
			
		||||
	{EVENT_BOOK_MARK_PRICE, "标记价格", "mark-price"},
 | 
			
		||||
	{EVENT_BOOK_MARK_PRICE_CANDLE_CHART, "标记价格K线", "mark-price-candle"},
 | 
			
		||||
	{EVENT_BOOK_LIMIT_PRICE, "限价", "price-limit"},
 | 
			
		||||
	{EVENT_BOOK_ORDER_BOOK, "400档深度", "books"},
 | 
			
		||||
	{EVENT_BOOK_ORDER_BOOK5, "5档深度", "books5"},
 | 
			
		||||
	{EVENT_BOOK_ORDER_BOOK_TBT, "tbt深度", "books-l2-tbt"},
 | 
			
		||||
	{EVENT_BOOK_ORDER_BOOK50_TBT, "tbt50深度", "books50-l2-tbt"},
 | 
			
		||||
	{EVENT_BOOK_OPTION_SUMMARY, "期权定价", "opt-summary"},
 | 
			
		||||
	{EVENT_BOOK_FUND_RATE, "资金费率", "funding-rate"},
 | 
			
		||||
	{EVENT_BOOK_KLINE_INDEX, "指数K线", "index-candle"},
 | 
			
		||||
	{EVENT_BOOK_INDEX_TICKERS, "指数行情", "index-tickers"},
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
		订阅私有频道
 | 
			
		||||
	*/
 | 
			
		||||
	{EVENT_BOOK_ACCOUNT, "账户", "account"},
 | 
			
		||||
	{EVENT_BOOK_POSTION, "持仓", "positions"},
 | 
			
		||||
	{EVENT_BOOK_ORDER, "订单", "orders"},
 | 
			
		||||
	{EVENT_BOOK_ALG_ORDER, "策略委托订单", "orders-algo"},
 | 
			
		||||
	{EVENT_BOOK_B_AND_P, "账户余额和持仓", "balance_and_position"},
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
		JRPC
 | 
			
		||||
	*/
 | 
			
		||||
	{EVENT_PLACE_ORDER, "下单", "order"},
 | 
			
		||||
	{EVENT_PLACE_BATCH_ORDERS, "批量下单", "batch-orders"},
 | 
			
		||||
	{EVENT_CANCEL_ORDER, "撤单", "cancel-order"},
 | 
			
		||||
	{EVENT_CANCEL_BATCH_ORDERS, "批量撤单", "batch-cancel-orders"},
 | 
			
		||||
	{EVENT_AMEND_ORDER, "改单", "amend-order"},
 | 
			
		||||
	{EVENT_AMEND_BATCH_ORDERS, "批量改单", "batch-amend-orders"},
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
		订阅返回数据
 | 
			
		||||
		注意:推送数据channle统一为""
 | 
			
		||||
	*/
 | 
			
		||||
	{EVENT_BOOKED_DATA, "普通推送", ""},
 | 
			
		||||
	{EVENT_DEPTH_DATA, "深度推送", ""},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	获取事件名称
 | 
			
		||||
*/
 | 
			
		||||
func (e Event) String() string {
 | 
			
		||||
	for _, v := range EVENT_TABLE {
 | 
			
		||||
		eventId := v[0].(Event)
 | 
			
		||||
		if e == eventId {
 | 
			
		||||
			return v[1].(string)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	通过事件获取对应的channel信息
 | 
			
		||||
	对于频道名称有时间周期的 通过参数 pd 传入,拼接后返回完整channel信息
 | 
			
		||||
*/
 | 
			
		||||
func (e Event) GetChannel(pd Period) string {
 | 
			
		||||
 | 
			
		||||
	channel := ""
 | 
			
		||||
 | 
			
		||||
	for _, v := range EVENT_TABLE {
 | 
			
		||||
		eventId := v[0].(Event)
 | 
			
		||||
		if e == eventId {
 | 
			
		||||
			channel = v[2].(string)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if channel == "" {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return channel + string(pd)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	通过channel信息匹配获取事件类型
 | 
			
		||||
*/
 | 
			
		||||
func GetEventId(raw string) Event {
 | 
			
		||||
	evt := EVENT_UNKNOWN
 | 
			
		||||
 | 
			
		||||
	for _, v := range EVENT_TABLE {
 | 
			
		||||
		channel := v[2].(string)
 | 
			
		||||
		if raw == channel {
 | 
			
		||||
			evt = v[0].(Event)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		regexp := regexp.MustCompile(`^(.*)([1-9][0-9]?[\w])$`)
 | 
			
		||||
		//regexp := regexp.MustCompile(`^http://www.flysnow.org/([\d]{4})/([\d]{2})/([\d]{2})/([\w-]+).html$`)
 | 
			
		||||
 | 
			
		||||
		substr := regexp.FindStringSubmatch(raw)
 | 
			
		||||
		//fmt.Println(substr)
 | 
			
		||||
		if len(substr) >= 2 {
 | 
			
		||||
			if substr[1] == channel {
 | 
			
		||||
				evt = v[0].(Event)
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return evt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 时间维度
 | 
			
		||||
type Period string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// 年
 | 
			
		||||
	PERIOD_1YEAR Period = "1Y"
 | 
			
		||||
 | 
			
		||||
	// 月
 | 
			
		||||
	PERIOD_6Mon Period = "6M"
 | 
			
		||||
	PERIOD_3Mon Period = "3M"
 | 
			
		||||
	PERIOD_1Mon Period = "1M"
 | 
			
		||||
 | 
			
		||||
	// 周
 | 
			
		||||
	PERIOD_1WEEK Period = "1W"
 | 
			
		||||
 | 
			
		||||
	// 天
 | 
			
		||||
	PERIOD_5DAY Period = "5D"
 | 
			
		||||
	PERIOD_3DAY Period = "3D"
 | 
			
		||||
	PERIOD_2DAY Period = "2D"
 | 
			
		||||
	PERIOD_1DAY Period = "1D"
 | 
			
		||||
 | 
			
		||||
	// 小时
 | 
			
		||||
	PERIOD_12HOUR Period = "12H"
 | 
			
		||||
	PERIOD_6HOUR  Period = "6H"
 | 
			
		||||
	PERIOD_4HOUR  Period = "4H"
 | 
			
		||||
	PERIOD_2HOUR  Period = "2H"
 | 
			
		||||
	PERIOD_1HOUR  Period = "1H"
 | 
			
		||||
 | 
			
		||||
	// 分钟
 | 
			
		||||
	PERIOD_30MIN Period = "30m"
 | 
			
		||||
	PERIOD_15MIN Period = "15m"
 | 
			
		||||
	PERIOD_5MIN  Period = "5m"
 | 
			
		||||
	PERIOD_3MIN  Period = "3m"
 | 
			
		||||
	PERIOD_1MIN  Period = "1m"
 | 
			
		||||
 | 
			
		||||
	// 缺省
 | 
			
		||||
	PERIOD_NONE Period = ""
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 深度枚举
 | 
			
		||||
const (
 | 
			
		||||
	DEPTH_SNAPSHOT = "snapshot"
 | 
			
		||||
	DEPTH_UPDATE   = "update"
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										28
									
								
								submodules/okex/ws/wImpl/contants_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								submodules/okex/ws/wImpl/contants_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
			
		||||
package wImpl
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGetEventId(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	id1 := GetEventId("index-candle30m")
 | 
			
		||||
 | 
			
		||||
	assert.True(t, id1 == EVENT_BOOK_KLINE_INDEX)
 | 
			
		||||
 | 
			
		||||
	id2 := GetEventId("candle1Y")
 | 
			
		||||
 | 
			
		||||
	assert.True(t, id2 == EVENT_BOOK_KLINE)
 | 
			
		||||
 | 
			
		||||
	id3 := GetEventId("orders-algo")
 | 
			
		||||
	assert.True(t, id3 == EVENT_BOOK_ALG_ORDER)
 | 
			
		||||
 | 
			
		||||
	id4 := GetEventId("balance_and_position")
 | 
			
		||||
	assert.True(t, id4 == EVENT_BOOK_B_AND_P)
 | 
			
		||||
 | 
			
		||||
	id5 := GetEventId("index-candle1m")
 | 
			
		||||
	assert.True(t, id5 == EVENT_BOOK_KLINE_INDEX)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								submodules/okex/ws/wInterface/IParam.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								submodules/okex/ws/wInterface/IParam.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
package wInterface
 | 
			
		||||
 | 
			
		||||
import . "v5sdk_go/ws/wImpl"
 | 
			
		||||
 | 
			
		||||
// 请求数据
 | 
			
		||||
type WSParam interface {
 | 
			
		||||
	EventType() Event
 | 
			
		||||
	ToMap() *map[string]string
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								submodules/okex/ws/wInterface/IReqData.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								submodules/okex/ws/wInterface/IReqData.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
package wInterface
 | 
			
		||||
 | 
			
		||||
// 请求数据
 | 
			
		||||
type WSReqData interface {
 | 
			
		||||
	GetType() int
 | 
			
		||||
	Len() int
 | 
			
		||||
	ToString() string
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								submodules/okex/ws/wInterface/IRspData.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								submodules/okex/ws/wInterface/IRspData.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
package wInterface
 | 
			
		||||
 | 
			
		||||
// 返回数据
 | 
			
		||||
type WSRspData interface {
 | 
			
		||||
	MsgType() int
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										147
									
								
								submodules/okex/ws/ws_AddBookedDataHook_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								submodules/okex/ws/ws_AddBookedDataHook_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,147 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
// HOW TO RUN
 | 
			
		||||
// go test ws_cli.go ws_op.go ws_contants.go utils.go ws_pub_channel.go  ws_pub_channel_test.go ws_priv_channel.go  ws_priv_channel_test.go ws_jrpc.go  ws_jrpc_test.go  ws_test_AddBookedDataHook.go -v
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
	. "v5sdk_go/ws/wImpl"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	TRADE_ACCOUNT = iota
 | 
			
		||||
	ISOLATE_ACCOUNT
 | 
			
		||||
	CROSS_ACCOUNT
 | 
			
		||||
	CROSS_ACCOUNT_B
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	log.SetFlags(log.LstdFlags | log.Llongfile)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func prework() *WsClient {
 | 
			
		||||
	ep := "wss://wsaws.okex.com:8443/ws/v5/private"
 | 
			
		||||
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err, ep)
 | 
			
		||||
	}
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
func prework_pri(t int) *WsClient {
 | 
			
		||||
	// 模拟环境
 | 
			
		||||
	ep := "wss://wsaws.okex.com:8443/ws/v5/private"
 | 
			
		||||
	var apikey, passphrase, secretKey string
 | 
			
		||||
	// 把账号密码写这里
 | 
			
		||||
	switch t {
 | 
			
		||||
	case TRADE_ACCOUNT:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	case ISOLATE_ACCOUNT:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	case CROSS_ACCOUNT:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	case CROSS_ACCOUNT_B:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var res bool
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.Login(apikey, secretKey, passphrase)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("登录成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		log.Fatal("登录失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(apikey, secretKey, passphrase)
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAddBookedDataHook(t *testing.T) {
 | 
			
		||||
	var r *WsClient
 | 
			
		||||
 | 
			
		||||
	/*订阅私有频道*/
 | 
			
		||||
	{
 | 
			
		||||
		r = prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
		var res bool
 | 
			
		||||
		var err error
 | 
			
		||||
 | 
			
		||||
		r.AddBookMsgHook(func(ts time.Time, data MsgData) error {
 | 
			
		||||
			// 添加你的方法
 | 
			
		||||
			fmt.Println("这是自定义AddBookMsgHook")
 | 
			
		||||
			fmt.Println("当前数据是", data)
 | 
			
		||||
			return nil
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		param := map[string]string{}
 | 
			
		||||
		param["channel"] = "account"
 | 
			
		||||
		param["ccy"] = "BTC"
 | 
			
		||||
 | 
			
		||||
		res, _, err = r.Subscribe(param)
 | 
			
		||||
		if res {
 | 
			
		||||
			fmt.Println("订阅成功!")
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Println("订阅失败!", err)
 | 
			
		||||
			t.Fatal("订阅失败!", err)
 | 
			
		||||
			//return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		time.Sleep(100 * time.Second)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//订阅公共频道
 | 
			
		||||
	{
 | 
			
		||||
		r = prework()
 | 
			
		||||
		var res bool
 | 
			
		||||
		var err error
 | 
			
		||||
 | 
			
		||||
		// r.AddBookMsgHook(func(ts time.Time, data MsgData) error {
 | 
			
		||||
		// 添加你的方法
 | 
			
		||||
		// fmt.Println("这是公共自定义AddBookMsgHook")
 | 
			
		||||
		// fmt.Println("当前数据是", data)
 | 
			
		||||
		// return nil
 | 
			
		||||
		// })
 | 
			
		||||
 | 
			
		||||
		param := map[string]string{}
 | 
			
		||||
		param["channel"] = "instruments"
 | 
			
		||||
		param["instType"] = "FUTURES"
 | 
			
		||||
 | 
			
		||||
		res, _, err = r.Subscribe(param)
 | 
			
		||||
		if res {
 | 
			
		||||
			fmt.Println("订阅成功!")
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Println("订阅失败!", err)
 | 
			
		||||
			t.Fatal("订阅失败!", err)
 | 
			
		||||
			//return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		select {}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										725
									
								
								submodules/okex/ws/ws_cli.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										725
									
								
								submodules/okex/ws/ws_cli.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,725 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"runtime/debug"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
	. "v5sdk_go/config"
 | 
			
		||||
	. "v5sdk_go/utils"
 | 
			
		||||
	. "v5sdk_go/ws/wImpl"
 | 
			
		||||
 | 
			
		||||
	"github.com/gorilla/websocket"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 全局回调函数
 | 
			
		||||
type ReceivedDataCallback func(*Msg) error
 | 
			
		||||
 | 
			
		||||
// 普通订阅推送数据回调函数
 | 
			
		||||
type ReceivedMsgDataCallback func(time.Time, MsgData) error
 | 
			
		||||
 | 
			
		||||
// 深度订阅推送数据回调函数
 | 
			
		||||
type ReceivedDepthDataCallback func(time.Time, DepthData) error
 | 
			
		||||
 | 
			
		||||
// websocket
 | 
			
		||||
type WsClient struct {
 | 
			
		||||
	WsEndPoint string
 | 
			
		||||
	WsApi      *ApiInfo
 | 
			
		||||
	conn       *websocket.Conn
 | 
			
		||||
	sendCh     chan string //发消息队列
 | 
			
		||||
	resCh      chan *Msg   //收消息队列
 | 
			
		||||
 | 
			
		||||
	errCh chan *Msg
 | 
			
		||||
	regCh map[Event]chan *Msg //请求响应队列
 | 
			
		||||
 | 
			
		||||
	quitCh chan struct{}
 | 
			
		||||
	lock   sync.RWMutex
 | 
			
		||||
 | 
			
		||||
	onMessageHook ReceivedDataCallback      //全局消息回调函数
 | 
			
		||||
	onBookMsgHook ReceivedMsgDataCallback   //普通订阅消息回调函数
 | 
			
		||||
	onDepthHook   ReceivedDepthDataCallback //深度订阅消息回调函数
 | 
			
		||||
	OnErrorHook   ReceivedDataCallback      //错误处理回调函数
 | 
			
		||||
 | 
			
		||||
	// 记录深度信息
 | 
			
		||||
	DepthDataList map[string]DepthDetail
 | 
			
		||||
	autoDepthMgr  bool // 深度数据管理(checksum等)
 | 
			
		||||
	DepthDataLock sync.RWMutex
 | 
			
		||||
 | 
			
		||||
	isStarted   bool //防止重复启动和关闭
 | 
			
		||||
	dailTimeout time.Duration
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	服务端响应详细信息
 | 
			
		||||
	Timestamp: 接受到消息的时间
 | 
			
		||||
	Info: 接受到的消息字符串
 | 
			
		||||
*/
 | 
			
		||||
type Msg struct {
 | 
			
		||||
	Timestamp time.Time   `json:"timestamp"`
 | 
			
		||||
	Info      interface{} `json:"info"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *Msg) Print() {
 | 
			
		||||
	fmt.Println("【消息时间】", this.Timestamp.Format("2006-01-02 15:04:05.000"))
 | 
			
		||||
	str, _ := json.Marshal(this.Info)
 | 
			
		||||
	fmt.Println("【消息内容】", string(str))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	订阅结果封装后的消息结构体
 | 
			
		||||
*/
 | 
			
		||||
type ProcessDetail struct {
 | 
			
		||||
	EndPoint string        `json:"endPoint"`
 | 
			
		||||
	ReqInfo  string        `json:"ReqInfo"`  //订阅请求
 | 
			
		||||
	SendTime time.Time     `json:"sendTime"` //发送订阅请求的时间
 | 
			
		||||
	RecvTime time.Time     `json:"recvTime"` //接受到订阅结果的时间
 | 
			
		||||
	UsedTime time.Duration `json:"UsedTime"` //耗时
 | 
			
		||||
	Data     []*Msg        `json:"data"`     //订阅结果数据
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *ProcessDetail) String() string {
 | 
			
		||||
	data, _ := json.Marshal(p)
 | 
			
		||||
	return string(data)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 创建ws对象
 | 
			
		||||
func NewWsClient(ep string) (r *WsClient, err error) {
 | 
			
		||||
	if ep == "" {
 | 
			
		||||
		err = errors.New("websocket endpoint cannot be null")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r = &WsClient{
 | 
			
		||||
		WsEndPoint: ep,
 | 
			
		||||
		sendCh:     make(chan string),
 | 
			
		||||
		resCh:      make(chan *Msg),
 | 
			
		||||
		errCh:      make(chan *Msg),
 | 
			
		||||
		regCh:      make(map[Event]chan *Msg),
 | 
			
		||||
		//cbs:        make(map[Event]ReceivedDataCallback),
 | 
			
		||||
		quitCh:        make(chan struct{}),
 | 
			
		||||
		DepthDataList: make(map[string]DepthDetail),
 | 
			
		||||
		dailTimeout:   time.Second * 10,
 | 
			
		||||
		// 自动深度校验默认开启
 | 
			
		||||
		autoDepthMgr: true,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	新增记录深度信息
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) addDepthDataList(key string, dd DepthDetail) error {
 | 
			
		||||
	a.DepthDataLock.Lock()
 | 
			
		||||
	defer a.DepthDataLock.Unlock()
 | 
			
		||||
	a.DepthDataList[key] = dd
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	更新记录深度信息(如果没有记录不会更新成功)
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) updateDepthDataList(key string, dd DepthDetail) error {
 | 
			
		||||
	a.DepthDataLock.Lock()
 | 
			
		||||
	defer a.DepthDataLock.Unlock()
 | 
			
		||||
	if _, ok := a.DepthDataList[key]; !ok {
 | 
			
		||||
		return errors.New("更新失败!未发现记录" + key)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a.DepthDataList[key] = dd
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	删除记录深度信息
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) deleteDepthDataList(key string) error {
 | 
			
		||||
	a.DepthDataLock.Lock()
 | 
			
		||||
	defer a.DepthDataLock.Unlock()
 | 
			
		||||
	delete(a.DepthDataList, key)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	设置是否自动深度管理,开启 true,关闭 false
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) EnableAutoDepthMgr(b bool) error {
 | 
			
		||||
	a.DepthDataLock.Lock()
 | 
			
		||||
	defer a.DepthDataLock.Unlock()
 | 
			
		||||
 | 
			
		||||
	if len(a.DepthDataList) != 0 {
 | 
			
		||||
		err := errors.New("当前有深度数据处于订阅中")
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a.autoDepthMgr = b
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	获取当前的深度快照信息(合并后的)
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) GetSnapshotByChannel(data DepthData) (snapshot *DepthDetail, err error) {
 | 
			
		||||
	key, err := json.Marshal(data.Arg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	a.DepthDataLock.Lock()
 | 
			
		||||
	defer a.DepthDataLock.Unlock()
 | 
			
		||||
	val, ok := a.DepthDataList[string(key)]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	snapshot = new(DepthDetail)
 | 
			
		||||
	raw, err := json.Marshal(val)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	err = json.Unmarshal(raw, &snapshot)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 设置dial超时时间
 | 
			
		||||
func (a *WsClient) SetDailTimeout(tm time.Duration) {
 | 
			
		||||
	a.dailTimeout = tm
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 非阻塞启动
 | 
			
		||||
func (a *WsClient) Start() error {
 | 
			
		||||
	a.lock.RLock()
 | 
			
		||||
	if a.isStarted {
 | 
			
		||||
		a.lock.RUnlock()
 | 
			
		||||
		fmt.Println("ws已经启动")
 | 
			
		||||
		return nil
 | 
			
		||||
	} else {
 | 
			
		||||
		a.lock.RUnlock()
 | 
			
		||||
		a.lock.Lock()
 | 
			
		||||
		defer a.lock.Unlock()
 | 
			
		||||
		// 增加超时处理
 | 
			
		||||
		done := make(chan struct{})
 | 
			
		||||
		ctx, cancel := context.WithTimeout(context.Background(), a.dailTimeout)
 | 
			
		||||
		defer cancel()
 | 
			
		||||
		go func() {
 | 
			
		||||
			defer func() {
 | 
			
		||||
				close(done)
 | 
			
		||||
			}()
 | 
			
		||||
			var c *websocket.Conn
 | 
			
		||||
			fmt.Println("a.WsEndPoint: ", a.WsEndPoint)
 | 
			
		||||
			c, _, err := websocket.DefaultDialer.Dial(a.WsEndPoint, nil)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				err = errors.New("dial error:" + err.Error())
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			a.conn = c
 | 
			
		||||
 | 
			
		||||
		}()
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ctx.Done():
 | 
			
		||||
			err := errors.New("连接超时退出!")
 | 
			
		||||
			return err
 | 
			
		||||
		case <-done:
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
		//TODO 自定义的推送消息回调回来试试放在这里
 | 
			
		||||
		go a.receive()
 | 
			
		||||
		go a.work()
 | 
			
		||||
		a.isStarted = true
 | 
			
		||||
		log.Println("客户端已启动!", a.WsEndPoint)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 客户端退出消息channel
 | 
			
		||||
func (a *WsClient) IsQuit() <-chan struct{} {
 | 
			
		||||
	return a.quitCh
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *WsClient) work() {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		a.Stop()
 | 
			
		||||
		err := recover()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Printf("work End. Recover msg: %+v", a)
 | 
			
		||||
			debug.PrintStack()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	ticker := time.NewTicker(10 * time.Second)
 | 
			
		||||
	defer ticker.Stop()
 | 
			
		||||
 | 
			
		||||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-ticker.C: // 保持心跳
 | 
			
		||||
			// go a.Ping(1000)
 | 
			
		||||
			go func() {
 | 
			
		||||
				_, _, err := a.Ping(1000)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					fmt.Println("心跳检测失败!", err)
 | 
			
		||||
					a.Stop()
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			}()
 | 
			
		||||
 | 
			
		||||
		case <-a.quitCh: // 保持心跳
 | 
			
		||||
			return
 | 
			
		||||
		case data, ok := <-a.resCh: //接收到服务端发来的消息
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			//log.Println("接收到来自resCh的消息:", data)
 | 
			
		||||
			if a.onMessageHook != nil {
 | 
			
		||||
				err := a.onMessageHook(data)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Println("执行onMessageHook函数错误!", err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		case errMsg, ok := <-a.errCh: //错误处理
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if a.OnErrorHook != nil {
 | 
			
		||||
				err := a.OnErrorHook(errMsg)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					log.Println("执行OnErrorHook函数错误!", err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		case req, ok := <-a.sendCh: //从发送队列中取出数据发送到服务端
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			//log.Println("接收到来自req的消息:", req)
 | 
			
		||||
			err := a.conn.WriteMessage(websocket.TextMessage, []byte(req))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Printf("发送请求失败: %s\n", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			log.Printf("[发送请求] %v\n", req)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	处理接受到的消息
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) receive() {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		a.Stop()
 | 
			
		||||
		err := recover()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Printf("Receive End. Recover msg: %+v", a)
 | 
			
		||||
			debug.PrintStack()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	for {
 | 
			
		||||
		messageType, message, err := a.conn.ReadMessage()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if a.isStarted {
 | 
			
		||||
				log.Println("receive message error!" + err.Error())
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		txtMsg := message
 | 
			
		||||
		switch messageType {
 | 
			
		||||
		case websocket.TextMessage:
 | 
			
		||||
		case websocket.BinaryMessage:
 | 
			
		||||
			txtMsg, err = GzipDecode(message)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Println("解压失败!")
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		log.Println("[收到消息]", string(txtMsg))
 | 
			
		||||
 | 
			
		||||
		//发送结果到默认消息处理通道
 | 
			
		||||
 | 
			
		||||
		timestamp := time.Now()
 | 
			
		||||
		msg := &Msg{Timestamp: timestamp, Info: string(txtMsg)}
 | 
			
		||||
 | 
			
		||||
		a.resCh <- msg
 | 
			
		||||
 | 
			
		||||
		evt, data, err := a.parseMessage(txtMsg)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Println("解析消息失败!", err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//log.Println("解析消息成功!消息类型 =", evt)
 | 
			
		||||
 | 
			
		||||
		a.lock.RLock()
 | 
			
		||||
		ch, ok := a.regCh[evt]
 | 
			
		||||
		a.lock.RUnlock()
 | 
			
		||||
		if !ok {
 | 
			
		||||
			//只有推送消息才会主动创建通道和消费队列
 | 
			
		||||
			if evt == EVENT_BOOKED_DATA || evt == EVENT_DEPTH_DATA {
 | 
			
		||||
				//log.Println("channel不存在!event:", evt)
 | 
			
		||||
				//a.lock.RUnlock()
 | 
			
		||||
				a.lock.Lock()
 | 
			
		||||
				a.regCh[evt] = make(chan *Msg)
 | 
			
		||||
				ch = a.regCh[evt]
 | 
			
		||||
				a.lock.Unlock()
 | 
			
		||||
 | 
			
		||||
				//log.Println("创建", evt, "通道")
 | 
			
		||||
 | 
			
		||||
				// 创建消费队列
 | 
			
		||||
				go func(evt Event) {
 | 
			
		||||
					//log.Println("创建goroutine  evt:", evt)
 | 
			
		||||
 | 
			
		||||
					for msg := range a.regCh[evt] {
 | 
			
		||||
						//log.Println(msg)
 | 
			
		||||
						// msg.Print()
 | 
			
		||||
						switch evt {
 | 
			
		||||
						// 处理普通推送数据
 | 
			
		||||
						case EVENT_BOOKED_DATA:
 | 
			
		||||
							fn := a.onBookMsgHook
 | 
			
		||||
							if fn != nil {
 | 
			
		||||
								err = fn(msg.Timestamp, msg.Info.(MsgData))
 | 
			
		||||
								if err != nil {
 | 
			
		||||
									log.Println("订阅数据回调函数执行失败!", err)
 | 
			
		||||
								}
 | 
			
		||||
								//log.Println("函数执行成功!", err)
 | 
			
		||||
							}
 | 
			
		||||
						// 处理深度推送数据
 | 
			
		||||
						case EVENT_DEPTH_DATA:
 | 
			
		||||
							fn := a.onDepthHook
 | 
			
		||||
 | 
			
		||||
							depData := msg.Info.(DepthData)
 | 
			
		||||
 | 
			
		||||
							// 开启深度数据管理功能的,会合并深度数据
 | 
			
		||||
							if a.autoDepthMgr {
 | 
			
		||||
								a.MergeDepth(depData)
 | 
			
		||||
							}
 | 
			
		||||
 | 
			
		||||
							// 运行用户定义回调函数
 | 
			
		||||
							if fn != nil {
 | 
			
		||||
								err = fn(msg.Timestamp, msg.Info.(DepthData))
 | 
			
		||||
								if err != nil {
 | 
			
		||||
									log.Println("深度回调函数执行失败!", err)
 | 
			
		||||
								}
 | 
			
		||||
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
					}
 | 
			
		||||
					//log.Println("退出goroutine  evt:", evt)
 | 
			
		||||
				}(evt)
 | 
			
		||||
 | 
			
		||||
				//continue
 | 
			
		||||
			} else {
 | 
			
		||||
				//log.Println("程序异常!通道已关闭", evt)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//log.Println(evt,"事件已注册",ch)
 | 
			
		||||
 | 
			
		||||
		ctx := context.Background()
 | 
			
		||||
		ctx, cancel := context.WithTimeout(ctx, time.Millisecond*1000)
 | 
			
		||||
		select {
 | 
			
		||||
		/*
 | 
			
		||||
			丢弃消息容易引发数据处理处理错误
 | 
			
		||||
		*/
 | 
			
		||||
		// case <-ctx.Done():
 | 
			
		||||
		// 	log.Println("等待超时,消息丢弃 - ", data)
 | 
			
		||||
		case ch <- &Msg{Timestamp: timestamp, Info: data}:
 | 
			
		||||
		}
 | 
			
		||||
		cancel()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	开启了深度数据管理功能后,系统会自动合并深度信息
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) MergeDepth(depData DepthData) (err error) {
 | 
			
		||||
	if !a.autoDepthMgr {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	key, err := json.Marshal(depData.Arg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = errors.New("数据错误")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// books5 不需要做checksum
 | 
			
		||||
	if depData.Arg["channel"] == "books5" {
 | 
			
		||||
		a.addDepthDataList(string(key), depData.Data[0])
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if depData.Action == "snapshot" {
 | 
			
		||||
 | 
			
		||||
		_, err = depData.CheckSum(nil)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Println("校验失败", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		a.addDepthDataList(string(key), depData.Data[0])
 | 
			
		||||
 | 
			
		||||
	} else {
 | 
			
		||||
 | 
			
		||||
		var newSnapshot *DepthDetail
 | 
			
		||||
		a.DepthDataLock.RLock()
 | 
			
		||||
		oldSnapshot, ok := a.DepthDataList[string(key)]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			log.Println("深度数据错误,全量数据未发现!")
 | 
			
		||||
			err = errors.New("数据错误")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		a.DepthDataLock.RUnlock()
 | 
			
		||||
		newSnapshot, err = depData.CheckSum(&oldSnapshot)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Println("深度校验失败", err)
 | 
			
		||||
			err = errors.New("校验失败")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		a.updateDepthDataList(string(key), *newSnapshot)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	通过ErrorCode判断事件类型
 | 
			
		||||
*/
 | 
			
		||||
func GetInfoFromErrCode(data ErrData) Event {
 | 
			
		||||
	switch data.Code {
 | 
			
		||||
	case "60001":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60002":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60003":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60004":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60005":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60006":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60007":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60008":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60009":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60010":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	case "60011":
 | 
			
		||||
		return EVENT_LOGIN
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return EVENT_UNKNOWN
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
   从error返回中解析出对应的channel
 | 
			
		||||
   error信息样例
 | 
			
		||||
 {"event":"error","msg":"channel:index-tickers,instId:BTC-USDT1 doesn't exist","code":"60018"}
 | 
			
		||||
*/
 | 
			
		||||
func GetInfoFromErrMsg(raw string) (channel string) {
 | 
			
		||||
	reg := regexp.MustCompile(`channel:(.*?),`)
 | 
			
		||||
	if reg == nil {
 | 
			
		||||
		fmt.Println("MustCompile err")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	//提取关键信息
 | 
			
		||||
	result := reg.FindAllStringSubmatch(raw, -1)
 | 
			
		||||
	for _, text := range result {
 | 
			
		||||
		channel = text[1]
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	解析消息类型
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) parseMessage(raw []byte) (evt Event, data interface{}, err error) {
 | 
			
		||||
	evt = EVENT_UNKNOWN
 | 
			
		||||
	//log.Println("解析消息")
 | 
			
		||||
	//log.Println("消息内容:", string(raw))
 | 
			
		||||
	if string(raw) == "pong" {
 | 
			
		||||
		evt = EVENT_PING
 | 
			
		||||
		data = raw
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	//log.Println(0, evt)
 | 
			
		||||
	var rspData = RspData{}
 | 
			
		||||
	err = json.Unmarshal(raw, &rspData)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		op := rspData.Event
 | 
			
		||||
		if op == OP_SUBSCRIBE || op == OP_UNSUBSCRIBE {
 | 
			
		||||
			channel := rspData.Arg["channel"]
 | 
			
		||||
			evt = GetEventId(channel)
 | 
			
		||||
			data = rspData
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//log.Println("ErrData")
 | 
			
		||||
	var errData = ErrData{}
 | 
			
		||||
	err = json.Unmarshal(raw, &errData)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		op := errData.Event
 | 
			
		||||
		switch op {
 | 
			
		||||
		case OP_LOGIN:
 | 
			
		||||
			evt = EVENT_LOGIN
 | 
			
		||||
			data = errData
 | 
			
		||||
			//log.Println(3, evt)
 | 
			
		||||
			return
 | 
			
		||||
		case OP_ERROR:
 | 
			
		||||
			data = errData
 | 
			
		||||
			// TODO:细化报错对应的事件判断
 | 
			
		||||
 | 
			
		||||
			//尝试从msg字段中解析对应的事件类型
 | 
			
		||||
			evt = GetInfoFromErrCode(errData)
 | 
			
		||||
			if evt != EVENT_UNKNOWN {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			evt = GetEventId(GetInfoFromErrMsg(errData.Msg))
 | 
			
		||||
			if evt == EVENT_UNKNOWN {
 | 
			
		||||
				evt = EVENT_ERROR
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		//log.Println(5, evt)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//log.Println("JRPCRsp")
 | 
			
		||||
	var jRPCRsp = JRPCRsp{}
 | 
			
		||||
	err = json.Unmarshal(raw, &jRPCRsp)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		data = jRPCRsp
 | 
			
		||||
		evt = GetEventId(jRPCRsp.Op)
 | 
			
		||||
		if evt != EVENT_UNKNOWN {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var depthData = DepthData{}
 | 
			
		||||
	err = json.Unmarshal(raw, &depthData)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		evt = EVENT_DEPTH_DATA
 | 
			
		||||
		data = depthData
 | 
			
		||||
		//log.Println("-->>EVENT_DEPTH_DATA", evt)
 | 
			
		||||
		//log.Println(evt, data)
 | 
			
		||||
		//log.Println(6)
 | 
			
		||||
		switch depthData.Arg["channel"] {
 | 
			
		||||
		case "books":
 | 
			
		||||
			return
 | 
			
		||||
		case "books-l2-tbt":
 | 
			
		||||
			return
 | 
			
		||||
		case "books50-l2-tbt":
 | 
			
		||||
			return
 | 
			
		||||
		case "books5":
 | 
			
		||||
			return
 | 
			
		||||
		default:
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//log.Println("MsgData")
 | 
			
		||||
	var msgData = MsgData{}
 | 
			
		||||
	err = json.Unmarshal(raw, &msgData)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		evt = EVENT_BOOKED_DATA
 | 
			
		||||
		data = msgData
 | 
			
		||||
		//log.Println("-->>EVENT_BOOK_DATA", evt)
 | 
			
		||||
		//log.Println(evt, data)
 | 
			
		||||
		//log.Println(6)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	evt = EVENT_UNKNOWN
 | 
			
		||||
	err = errors.New("message unknown")
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *WsClient) Stop() error {
 | 
			
		||||
 | 
			
		||||
	a.lock.Lock()
 | 
			
		||||
	defer a.lock.Unlock()
 | 
			
		||||
	if !a.isStarted {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a.isStarted = false
 | 
			
		||||
 | 
			
		||||
	if a.conn != nil {
 | 
			
		||||
		a.conn.Close()
 | 
			
		||||
	}
 | 
			
		||||
	close(a.errCh)
 | 
			
		||||
	close(a.sendCh)
 | 
			
		||||
	close(a.resCh)
 | 
			
		||||
	close(a.quitCh)
 | 
			
		||||
 | 
			
		||||
	for _, ch := range a.regCh {
 | 
			
		||||
		close(ch)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Println("ws客户端退出!")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	添加全局消息处理的回调函数
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) AddMessageHook(fn ReceivedDataCallback) error {
 | 
			
		||||
	a.onMessageHook = fn
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	添加订阅消息处理的回调函数
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) AddBookMsgHook(fn ReceivedMsgDataCallback) error {
 | 
			
		||||
	a.onBookMsgHook = fn
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	添加深度消息处理的回调函数
 | 
			
		||||
	例如:
 | 
			
		||||
	cli.AddDepthHook(func(ts time.Time, data DepthData) error { return nil })
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) AddDepthHook(fn ReceivedDepthDataCallback) error {
 | 
			
		||||
	a.onDepthHook = fn
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	添加错误类型消息处理的回调函数
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) AddErrMsgHook(fn ReceivedDataCallback) error {
 | 
			
		||||
	a.OnErrorHook = fn
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	判断连接是否存活
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) IsAlive() bool {
 | 
			
		||||
	res := false
 | 
			
		||||
	if a.conn == nil {
 | 
			
		||||
		return res
 | 
			
		||||
	}
 | 
			
		||||
	res, _, _ = a.Ping(500)
 | 
			
		||||
	return res
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								submodules/okex/ws/ws_contants.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								submodules/okex/ws/ws_contants.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
//操作符
 | 
			
		||||
const (
 | 
			
		||||
	OP_LOGIN       = "login"
 | 
			
		||||
	OP_ERROR       = "error"
 | 
			
		||||
	OP_SUBSCRIBE   = "subscribe"
 | 
			
		||||
	OP_UNSUBSCRIBE = "unsubscribe"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// instrument Type
 | 
			
		||||
const (
 | 
			
		||||
	SPOT    = "SPOT"
 | 
			
		||||
	SWAP    = "SWAP"
 | 
			
		||||
	FUTURES = "FUTURES"
 | 
			
		||||
	OPTION  = "OPTION"
 | 
			
		||||
	ANY     = "ANY"
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										157
									
								
								submodules/okex/ws/ws_jrpc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								submodules/okex/ws/ws_jrpc.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,157 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"log"
 | 
			
		||||
	"time"
 | 
			
		||||
	. "v5sdk_go/ws/wImpl"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	websocket交易 通用请求
 | 
			
		||||
	参数说明:
 | 
			
		||||
		evtId:封装的事件类型
 | 
			
		||||
		id: 请求ID
 | 
			
		||||
		op: 请求参数op
 | 
			
		||||
		params: 请求参数
 | 
			
		||||
		timeOut: 超时时间
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) jrpcReq(evtId Event, op string, id string, params []map[string]interface{}, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
	res = true
 | 
			
		||||
	tm := 5000
 | 
			
		||||
	if len(timeOut) != 0 {
 | 
			
		||||
		tm = timeOut[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req := &JRPCReq{
 | 
			
		||||
		Id:   id,
 | 
			
		||||
		Op:   op,
 | 
			
		||||
		Args: params,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	detail = &ProcessDetail{
 | 
			
		||||
		EndPoint: a.WsEndPoint,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	ctx, cancel := context.WithTimeout(ctx, time.Duration(tm)*time.Millisecond)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	ctx = context.WithValue(ctx, "detail", detail)
 | 
			
		||||
 | 
			
		||||
	msg, err := a.process(ctx, evtId, req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		log.Println("处理请求失败!", req, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	detail.Data = msg
 | 
			
		||||
 | 
			
		||||
	res, err = checkResult(req, msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	单个下单
 | 
			
		||||
	参数说明:
 | 
			
		||||
		id: 请求ID
 | 
			
		||||
		params: 请求参数
 | 
			
		||||
		timeOut: 超时时间
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PlaceOrder(id string, param map[string]interface{}, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
	op := "order"
 | 
			
		||||
	evtId := EVENT_PLACE_ORDER
 | 
			
		||||
 | 
			
		||||
	var args []map[string]interface{}
 | 
			
		||||
	args = append(args, param)
 | 
			
		||||
 | 
			
		||||
	return a.jrpcReq(evtId, op, id, args, timeOut...)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	批量下单
 | 
			
		||||
	参数说明:
 | 
			
		||||
		id: 请求ID
 | 
			
		||||
		params: 请求参数
 | 
			
		||||
		timeOut: 超时时间
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) BatchPlaceOrders(id string, params []map[string]interface{}, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
 | 
			
		||||
	op := "batch-orders"
 | 
			
		||||
	evtId := EVENT_PLACE_BATCH_ORDERS
 | 
			
		||||
	return a.jrpcReq(evtId, op, id, params, timeOut...)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	单个撤单
 | 
			
		||||
	参数说明:
 | 
			
		||||
		id: 请求ID
 | 
			
		||||
		params: 请求参数
 | 
			
		||||
		timeOut: 超时时间
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) CancelOrder(id string, param map[string]interface{}, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
 | 
			
		||||
	op := "cancel-order"
 | 
			
		||||
	evtId := EVENT_CANCEL_ORDER
 | 
			
		||||
 | 
			
		||||
	var args []map[string]interface{}
 | 
			
		||||
	args = append(args, param)
 | 
			
		||||
 | 
			
		||||
	return a.jrpcReq(evtId, op, id, args, timeOut...)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	批量撤单
 | 
			
		||||
	参数说明:
 | 
			
		||||
		id: 请求ID
 | 
			
		||||
		params: 请求参数
 | 
			
		||||
		timeOut: 超时时间
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) BatchCancelOrders(id string, params []map[string]interface{}, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
 | 
			
		||||
	op := "batch-cancel-orders"
 | 
			
		||||
	evtId := EVENT_CANCEL_BATCH_ORDERS
 | 
			
		||||
	return a.jrpcReq(evtId, op, id, params, timeOut...)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	单个改单
 | 
			
		||||
	参数说明:
 | 
			
		||||
		id: 请求ID
 | 
			
		||||
		params: 请求参数
 | 
			
		||||
		timeOut: 超时时间
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) AmendOrder(id string, param map[string]interface{}, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
 | 
			
		||||
	op := "amend-order"
 | 
			
		||||
	evtId := EVENT_AMEND_ORDER
 | 
			
		||||
 | 
			
		||||
	var args []map[string]interface{}
 | 
			
		||||
	args = append(args, param)
 | 
			
		||||
 | 
			
		||||
	return a.jrpcReq(evtId, op, id, args, timeOut...)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	批量改单
 | 
			
		||||
	参数说明:
 | 
			
		||||
		id: 请求ID
 | 
			
		||||
		params: 请求参数
 | 
			
		||||
		timeOut: 超时时间
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) BatchAmendOrders(id string, params []map[string]interface{}, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
 | 
			
		||||
	op := "batch-amend-orders"
 | 
			
		||||
	evtId := EVENT_AMEND_BATCH_ORDERS
 | 
			
		||||
	return a.jrpcReq(evtId, op, id, params, timeOut...)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										186
									
								
								submodules/okex/ws/ws_jrpc_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								submodules/okex/ws/ws_jrpc_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,186 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
	. "v5sdk_go/ws/wImpl"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func PrintDetail(d *ProcessDetail) {
 | 
			
		||||
	fmt.Println("[详细信息]")
 | 
			
		||||
	fmt.Println("请求地址:", d.EndPoint)
 | 
			
		||||
	fmt.Println("请求内容:", d.ReqInfo)
 | 
			
		||||
	fmt.Println("发送时间:", d.SendTime.Format("2006-01-02 15:04:05.000"))
 | 
			
		||||
	fmt.Println("响应时间:", d.RecvTime.Format("2006-01-02 15:04:05.000"))
 | 
			
		||||
	fmt.Println("耗时:", d.UsedTime.String())
 | 
			
		||||
	fmt.Printf("接受到 %v 条消息:\n", len(d.Data))
 | 
			
		||||
	for _, v := range d.Data {
 | 
			
		||||
		fmt.Printf("[%v] %v\n", v.Timestamp.Format("2006-01-02 15:04:05.000"), v.Info)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *WsClient) makeOrder(instId string, tdMode string, side string, ordType string, px string, sz string) (orderId string, err error) {
 | 
			
		||||
 | 
			
		||||
	var res bool
 | 
			
		||||
	var data *ProcessDetail
 | 
			
		||||
 | 
			
		||||
	param := map[string]interface{}{}
 | 
			
		||||
	param["instId"] = instId
 | 
			
		||||
	param["tdMode"] = tdMode
 | 
			
		||||
	param["side"] = side
 | 
			
		||||
	param["ordType"] = ordType
 | 
			
		||||
	if px != "" {
 | 
			
		||||
		param["px"] = px
 | 
			
		||||
	}
 | 
			
		||||
	param["sz"] = sz
 | 
			
		||||
 | 
			
		||||
	res, data, err = r.PlaceOrder("0011", param)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if res && len(data.Data) == 1 {
 | 
			
		||||
		rsp := data.Data[0].Info.(JRPCRsp)
 | 
			
		||||
		if len(rsp.Data) == 1 {
 | 
			
		||||
			val, ok := rsp.Data[0]["ordId"]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			orderId = val.(string)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	单个下单
 | 
			
		||||
*/
 | 
			
		||||
// func TestPlaceOrder(t *testing.T) {
 | 
			
		||||
// r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
// r := prework_pri(TRADE_ACCOUNT)
 | 
			
		||||
// var res bool
 | 
			
		||||
// var err error
 | 
			
		||||
// var data *ProcessDetail
 | 
			
		||||
//
 | 
			
		||||
// start := time.Now()
 | 
			
		||||
// param := map[string]interface{}{}
 | 
			
		||||
// param["instId"] = "BTC-USDT"
 | 
			
		||||
// param["tdMode"] = "cash"
 | 
			
		||||
// param["side"] = "buy"
 | 
			
		||||
// param["ordType"] = "market"
 | 
			
		||||
// param["px"] = "1"
 | 
			
		||||
// param["sz"] = "200"
 | 
			
		||||
//
 | 
			
		||||
// res, data, err = r.PlaceOrder("0011", param)
 | 
			
		||||
// if res {
 | 
			
		||||
// usedTime := time.Since(start)
 | 
			
		||||
// fmt.Println("下单成功!", usedTime.String())
 | 
			
		||||
// PrintDetail(data)
 | 
			
		||||
// } else {
 | 
			
		||||
// usedTime := time.Since(start)
 | 
			
		||||
// fmt.Println("下单失败!", usedTime.String(), err)
 | 
			
		||||
// }
 | 
			
		||||
//
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	批量下单
 | 
			
		||||
*/
 | 
			
		||||
// func TestPlaceBatchOrder(t *testing.T) {
 | 
			
		||||
// r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
// var res bool
 | 
			
		||||
// var err error
 | 
			
		||||
// var data *ProcessDetail
 | 
			
		||||
//
 | 
			
		||||
// start := time.Now()
 | 
			
		||||
// var params []map[string]interface{}
 | 
			
		||||
// param := map[string]interface{}{}
 | 
			
		||||
// param["instId"] = "BTC-USDT"
 | 
			
		||||
// param["tdMode"] = "cash"
 | 
			
		||||
// param["side"] = "sell"
 | 
			
		||||
// param["ordType"] = "market"
 | 
			
		||||
// param["sz"] = "0.001"
 | 
			
		||||
// params = append(params, param)
 | 
			
		||||
// param = map[string]interface{}{}
 | 
			
		||||
// param["instId"] = "BTC-USDT"
 | 
			
		||||
// param["tdMode"] = "cash"
 | 
			
		||||
// param["side"] = "buy"
 | 
			
		||||
// param["ordType"] = "market"
 | 
			
		||||
// param["sz"] = "100"
 | 
			
		||||
// params = append(params, param)
 | 
			
		||||
// res, data, err = r.BatchPlaceOrders("001", params)
 | 
			
		||||
// usedTime := time.Since(start)
 | 
			
		||||
// if err != nil {
 | 
			
		||||
// fmt.Println("下单失败!", err, usedTime.String())
 | 
			
		||||
// t.Fail()
 | 
			
		||||
// }
 | 
			
		||||
// if res {
 | 
			
		||||
// fmt.Println("下单成功!", usedTime.String())
 | 
			
		||||
// PrintDetail(data)
 | 
			
		||||
// } else {
 | 
			
		||||
//
 | 
			
		||||
// fmt.Println("下单失败!", usedTime.String())
 | 
			
		||||
// t.Fail()
 | 
			
		||||
// }
 | 
			
		||||
//
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	撤销订单
 | 
			
		||||
*/
 | 
			
		||||
// func TestCancelOrder(t *testing.T) {
 | 
			
		||||
// r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
//
 | 
			
		||||
// 用户自定义limit限价价格
 | 
			
		||||
// ordId, _ := r.makeOrder("BTC-USDT", "cash", "sell", "limit", "57000", "0.01")
 | 
			
		||||
// if ordId == "" {
 | 
			
		||||
// t.Fatal()
 | 
			
		||||
// }
 | 
			
		||||
//
 | 
			
		||||
// t.Log("生成挂单:orderId=", ordId)
 | 
			
		||||
//
 | 
			
		||||
// param := map[string]interface{}{}
 | 
			
		||||
// param["instId"] = "BTC-USDT"
 | 
			
		||||
// param["ordId"] = ordId
 | 
			
		||||
// start := time.Now()
 | 
			
		||||
// res, _, _ := r.CancelOrder("1", param)
 | 
			
		||||
// if res {
 | 
			
		||||
// usedTime := time.Since(start)
 | 
			
		||||
// fmt.Println("撤单成功!", usedTime.String())
 | 
			
		||||
// } else {
 | 
			
		||||
// t.Fatal("撤单失败!")
 | 
			
		||||
// }
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	修改订单
 | 
			
		||||
*/
 | 
			
		||||
func TestAmendlOrder(t *testing.T) {
 | 
			
		||||
	r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
 | 
			
		||||
	// 用户自定义limit限价价格
 | 
			
		||||
	ordId, _ := r.makeOrder("BTC-USDT", "cash", "sell", "limit", "57000", "0.01")
 | 
			
		||||
	if ordId == "" {
 | 
			
		||||
		t.Fatal()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.Log("生成挂单:orderId=", ordId)
 | 
			
		||||
 | 
			
		||||
	param := map[string]interface{}{}
 | 
			
		||||
	param["instId"] = "BTC-USDT"
 | 
			
		||||
	param["ordId"] = ordId
 | 
			
		||||
	// 调整修改订单的参数
 | 
			
		||||
	//param["newSz"] = "0.02"
 | 
			
		||||
	param["newPx"] = "57001"
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, _ := r.AmendOrder("1", param)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("修改订单成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		t.Fatal("修改订单失败!")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								submodules/okex/ws/ws_middleware.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								submodules/okex/ws/ws_middleware.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import "fmt"
 | 
			
		||||
 | 
			
		||||
type ReqFunc func(...interface{}) (res bool, msg *Msg, err error)
 | 
			
		||||
type Decorator func(ReqFunc) ReqFunc
 | 
			
		||||
 | 
			
		||||
func handler(h ReqFunc, decors ...Decorator) ReqFunc {
 | 
			
		||||
	for i := range decors {
 | 
			
		||||
		d := decors[len(decors)-1-i]
 | 
			
		||||
		h = d(h)
 | 
			
		||||
	}
 | 
			
		||||
	return h
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func preprocess() (res bool, msg *Msg, err error) {
 | 
			
		||||
	fmt.Println("preprocess")
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										532
									
								
								submodules/okex/ws/ws_op.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										532
									
								
								submodules/okex/ws/ws_op.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,532 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"log"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
	. "v5sdk_go/config"
 | 
			
		||||
	"v5sdk_go/rest"
 | 
			
		||||
	. "v5sdk_go/utils"
 | 
			
		||||
	. "v5sdk_go/ws/wImpl"
 | 
			
		||||
	. "v5sdk_go/ws/wInterface"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	Ping服务端保持心跳。
 | 
			
		||||
	timeOut:超时时间(毫秒),如果不填默认为5000ms
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) Ping(timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
	tm := 5000
 | 
			
		||||
	if len(timeOut) != 0 {
 | 
			
		||||
		tm = timeOut[0]
 | 
			
		||||
	}
 | 
			
		||||
	res = true
 | 
			
		||||
 | 
			
		||||
	detail = &ProcessDetail{
 | 
			
		||||
		EndPoint: a.WsEndPoint,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	ctx, _ = context.WithTimeout(ctx, time.Duration(tm)*time.Millisecond)
 | 
			
		||||
	ctx = context.WithValue(ctx, "detail", detail)
 | 
			
		||||
	msg, err := a.process(ctx, EVENT_PING, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		log.Println("处理请求失败!", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	detail.Data = msg
 | 
			
		||||
 | 
			
		||||
	if len(msg) == 0 {
 | 
			
		||||
		res = false
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	str := string(msg[0].Info.([]byte))
 | 
			
		||||
	if str != "pong" {
 | 
			
		||||
		res = false
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	登录私有频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) Login(apiKey, secKey, passPhrase string, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
 | 
			
		||||
	if apiKey == "" {
 | 
			
		||||
		err = errors.New("ApiKey cannot be null")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if secKey == "" {
 | 
			
		||||
		err = errors.New("SecretKey cannot be null")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if passPhrase == "" {
 | 
			
		||||
		err = errors.New("Passphrase cannot be null")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a.WsApi = &ApiInfo{
 | 
			
		||||
		ApiKey:     apiKey,
 | 
			
		||||
		SecretKey:  secKey,
 | 
			
		||||
		Passphrase: passPhrase,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tm := 5000
 | 
			
		||||
	if len(timeOut) != 0 {
 | 
			
		||||
		tm = timeOut[0]
 | 
			
		||||
	}
 | 
			
		||||
	res = true
 | 
			
		||||
 | 
			
		||||
	timestamp := EpochTime()
 | 
			
		||||
 | 
			
		||||
	preHash := PreHashString(timestamp, rest.GET, "/users/self/verify", "")
 | 
			
		||||
	//fmt.Println("preHash:", preHash)
 | 
			
		||||
	var sign string
 | 
			
		||||
	if sign, err = HmacSha256Base64Signer(preHash, secKey); err != nil {
 | 
			
		||||
		log.Println("处理签名失败!", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	args := map[string]string{}
 | 
			
		||||
	args["apiKey"] = apiKey
 | 
			
		||||
	args["passphrase"] = passPhrase
 | 
			
		||||
	args["timestamp"] = timestamp
 | 
			
		||||
	args["sign"] = sign
 | 
			
		||||
	req := &ReqData{
 | 
			
		||||
		Op:   OP_LOGIN,
 | 
			
		||||
		Args: []map[string]string{args},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	detail = &ProcessDetail{
 | 
			
		||||
		EndPoint: a.WsEndPoint,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	ctx, _ = context.WithTimeout(ctx, time.Duration(tm)*time.Millisecond)
 | 
			
		||||
	ctx = context.WithValue(ctx, "detail", detail)
 | 
			
		||||
 | 
			
		||||
	msg, err := a.process(ctx, EVENT_LOGIN, req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		log.Println("处理请求失败!", req, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	detail.Data = msg
 | 
			
		||||
 | 
			
		||||
	if len(msg) == 0 {
 | 
			
		||||
		res = false
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	info, _ := msg[0].Info.(ErrData)
 | 
			
		||||
 | 
			
		||||
	if info.Code == "0" && info.Event == OP_LOGIN {
 | 
			
		||||
		log.Println("登录成功!")
 | 
			
		||||
	} else {
 | 
			
		||||
		log.Println("登录失败!")
 | 
			
		||||
		res = false
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	等待结果响应
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) waitForResult(e Event, timeOut int) (data interface{}, err error) {
 | 
			
		||||
 | 
			
		||||
	if _, ok := a.regCh[e]; !ok {
 | 
			
		||||
		a.lock.Lock()
 | 
			
		||||
		a.regCh[e] = make(chan *Msg)
 | 
			
		||||
		a.lock.Unlock()
 | 
			
		||||
		//log.Println("注册", e, "事件成功")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	a.lock.RLock()
 | 
			
		||||
	defer a.lock.RUnlock()
 | 
			
		||||
	ch := a.regCh[e]
 | 
			
		||||
	//log.Println(e, "等待响应!")
 | 
			
		||||
	select {
 | 
			
		||||
	case <-time.After(time.Duration(timeOut) * time.Millisecond):
 | 
			
		||||
		log.Println(e, "超时未响应!")
 | 
			
		||||
		err = errors.New(e.String() + "超时未响应!")
 | 
			
		||||
		return
 | 
			
		||||
	case data = <-ch:
 | 
			
		||||
		//log.Println(data)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	发送消息到服务端
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) Send(ctx context.Context, op WSReqData) (err error) {
 | 
			
		||||
	select {
 | 
			
		||||
	case <-ctx.Done():
 | 
			
		||||
		log.Println("发生失败退出!")
 | 
			
		||||
		err = errors.New("发送超时退出!")
 | 
			
		||||
	case a.sendCh <- op.ToString():
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *WsClient) process(ctx context.Context, e Event, op WSReqData) (data []*Msg, err error) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		_ = recover()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	var detail *ProcessDetail
 | 
			
		||||
	if val := ctx.Value("detail"); val != nil {
 | 
			
		||||
		detail = val.(*ProcessDetail)
 | 
			
		||||
	} else {
 | 
			
		||||
		detail = &ProcessDetail{
 | 
			
		||||
			EndPoint: a.WsEndPoint,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		//fmt.Println("处理完成,", e.String())
 | 
			
		||||
		detail.UsedTime = detail.RecvTime.Sub(detail.SendTime)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	//查看事件是否被注册
 | 
			
		||||
	if _, ok := a.regCh[e]; !ok {
 | 
			
		||||
		a.lock.Lock()
 | 
			
		||||
		a.regCh[e] = make(chan *Msg)
 | 
			
		||||
		a.lock.Unlock()
 | 
			
		||||
		//log.Println("注册", e, "事件成功")
 | 
			
		||||
	} else {
 | 
			
		||||
		//log.Println("事件", e, "已注册!")
 | 
			
		||||
		err = errors.New("事件" + e.String() + "尚未处理完毕")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//预期请求响应的条数
 | 
			
		||||
	expectCnt := 1
 | 
			
		||||
	if op != nil {
 | 
			
		||||
		expectCnt = op.Len()
 | 
			
		||||
	}
 | 
			
		||||
	recvCnt := 0
 | 
			
		||||
 | 
			
		||||
	//等待完成通知
 | 
			
		||||
	wg := sync.WaitGroup{}
 | 
			
		||||
	wg.Add(1)
 | 
			
		||||
	// 这里要先定义go routine func(){} 是为了在里面订阅channel的内容, 我们知道一个队列要想往里塞东西,必先给他安排一个订阅它的协程
 | 
			
		||||
	go func(ctx context.Context) {
 | 
			
		||||
		defer func() {
 | 
			
		||||
			a.lock.Lock()
 | 
			
		||||
			delete(a.regCh, e)
 | 
			
		||||
			//log.Println("事件已注销!",e)
 | 
			
		||||
			a.lock.Unlock()
 | 
			
		||||
			wg.Done()
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		a.lock.RLock()
 | 
			
		||||
		ch := a.regCh[e] //请求响应队列
 | 
			
		||||
		a.lock.RUnlock()
 | 
			
		||||
 | 
			
		||||
		//log.Println(e, "等待响应!")
 | 
			
		||||
		done := false
 | 
			
		||||
		ok := true
 | 
			
		||||
		for {
 | 
			
		||||
			var item *Msg
 | 
			
		||||
			select {
 | 
			
		||||
			case <-ctx.Done():
 | 
			
		||||
				log.Println(e, "超时未响应!")
 | 
			
		||||
				err = errors.New(e.String() + "超时未响应!")
 | 
			
		||||
				return
 | 
			
		||||
			case item, ok = <-ch:
 | 
			
		||||
				if !ok {
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				detail.RecvTime = time.Now()
 | 
			
		||||
				//log.Println(e, "接受到数据", item)
 | 
			
		||||
				// 这里只是把推送的数据显示出来,并没有做更近一步的处理,后续可以二次开发,在这个位置上进行处理
 | 
			
		||||
				data = append(data, item)
 | 
			
		||||
				recvCnt++
 | 
			
		||||
				//log.Println(data)
 | 
			
		||||
				if recvCnt == expectCnt {
 | 
			
		||||
					done = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if done {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if ok {
 | 
			
		||||
			close(ch)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	}(ctx)
 | 
			
		||||
 | 
			
		||||
	//
 | 
			
		||||
	switch e {
 | 
			
		||||
	case EVENT_PING:
 | 
			
		||||
		msg := "ping"
 | 
			
		||||
		detail.ReqInfo = msg
 | 
			
		||||
		a.sendCh <- msg
 | 
			
		||||
		detail.SendTime = time.Now()
 | 
			
		||||
	default:
 | 
			
		||||
		detail.ReqInfo = op.ToString()
 | 
			
		||||
		//这个时候ctx中已经提供了meta信息,用于发送ws请求
 | 
			
		||||
		err = a.Send(ctx, op)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Println("发送[", e, "]消息失败!", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		detail.SendTime = time.Now()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	wg.Wait()
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	根据args请求参数判断请求类型
 | 
			
		||||
	如:{"channel": "account","ccy": "BTC"} 类型为 EVENT_BOOK_ACCOUNT
 | 
			
		||||
*/
 | 
			
		||||
func GetEventByParam(param map[string]string) (evtId Event) {
 | 
			
		||||
	evtId = EVENT_UNKNOWN
 | 
			
		||||
	channel, ok := param["channel"]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	evtId = GetEventId(channel)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	订阅频道。
 | 
			
		||||
	req:请求json字符串
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) Subscribe(param map[string]string, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
	res = true
 | 
			
		||||
	tm := 5000
 | 
			
		||||
	if len(timeOut) != 0 {
 | 
			
		||||
		tm = timeOut[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	evtid := GetEventByParam(param)
 | 
			
		||||
	if evtid == EVENT_UNKNOWN {
 | 
			
		||||
		err = errors.New("非法的请求参数!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	args = append(args, param)
 | 
			
		||||
 | 
			
		||||
	req := ReqData{
 | 
			
		||||
		Op:   OP_SUBSCRIBE,
 | 
			
		||||
		Args: args,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	detail = &ProcessDetail{
 | 
			
		||||
		EndPoint: a.WsEndPoint,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	ctx, _ = context.WithTimeout(ctx, time.Duration(tm)*time.Millisecond)
 | 
			
		||||
	ctx = context.WithValue(ctx, "detail", detail)
 | 
			
		||||
 | 
			
		||||
	msg, err := a.process(ctx, evtid, req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		log.Println("处理请求失败!", req, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	detail.Data = msg
 | 
			
		||||
 | 
			
		||||
	//检查所有频道是否都更新成功
 | 
			
		||||
	res, err = checkResult(req, msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	取消订阅频道。
 | 
			
		||||
	req:请求json字符串
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) UnSubscribe(param map[string]string, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
	res = true
 | 
			
		||||
	tm := 5000
 | 
			
		||||
	if len(timeOut) != 0 {
 | 
			
		||||
		tm = timeOut[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	evtid := GetEventByParam(param)
 | 
			
		||||
	if evtid == EVENT_UNKNOWN {
 | 
			
		||||
		err = errors.New("非法的请求参数!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	args = append(args, param)
 | 
			
		||||
 | 
			
		||||
	req := ReqData{
 | 
			
		||||
		Op:   OP_UNSUBSCRIBE,
 | 
			
		||||
		Args: args,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	detail = &ProcessDetail{
 | 
			
		||||
		EndPoint: a.WsEndPoint,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	ctx, _ = context.WithTimeout(ctx, time.Duration(tm)*time.Millisecond)
 | 
			
		||||
	ctx = context.WithValue(ctx, "detail", detail)
 | 
			
		||||
	msg, err := a.process(ctx, evtid, req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		log.Println("处理请求失败!", req, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	detail.Data = msg
 | 
			
		||||
	//检查所有频道是否都更新成功
 | 
			
		||||
	res, err = checkResult(req, msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	jrpc请求
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) Jrpc(id, op string, params []map[string]interface{}, timeOut ...int) (res bool, detail *ProcessDetail, err error) {
 | 
			
		||||
	res = true
 | 
			
		||||
	tm := 5000
 | 
			
		||||
	if len(timeOut) != 0 {
 | 
			
		||||
		tm = timeOut[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	evtid := GetEventId(op)
 | 
			
		||||
	if evtid == EVENT_UNKNOWN {
 | 
			
		||||
		err = errors.New("非法的请求参数!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req := JRPCReq{
 | 
			
		||||
		Id:   id,
 | 
			
		||||
		Op:   op,
 | 
			
		||||
		Args: params,
 | 
			
		||||
	}
 | 
			
		||||
	detail = &ProcessDetail{
 | 
			
		||||
		EndPoint: a.WsEndPoint,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	ctx, _ = context.WithTimeout(ctx, time.Duration(tm)*time.Millisecond)
 | 
			
		||||
	ctx = context.WithValue(ctx, "detail", detail)
 | 
			
		||||
	msg, err := a.process(ctx, evtid, req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		log.Println("处理请求失败!", req, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	detail.Data = msg
 | 
			
		||||
 | 
			
		||||
	//检查所有频道是否都更新成功
 | 
			
		||||
	res, err = checkResult(req, msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *WsClient) PubChannel(evtId Event, op string, params []map[string]string, pd Period, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
 | 
			
		||||
	// 参数校验
 | 
			
		||||
	pa, err := checkParams(evtId, params, pd)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	res = true
 | 
			
		||||
	tm := 5000
 | 
			
		||||
	if len(timeOut) != 0 {
 | 
			
		||||
		tm = timeOut[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req := ReqData{
 | 
			
		||||
		Op:   op,
 | 
			
		||||
		Args: pa,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	ctx, _ = context.WithTimeout(ctx, time.Duration(tm)*time.Millisecond)
 | 
			
		||||
	msg, err = a.process(ctx, evtId, req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		log.Println("处理请求失败!", req, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//检查所有频道是否都更新成功
 | 
			
		||||
 | 
			
		||||
	res, err = checkResult(req, msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		res = false
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 参数校验
 | 
			
		||||
func checkParams(evtId Event, params []map[string]string, pd Period) (res []map[string]string, err error) {
 | 
			
		||||
 | 
			
		||||
	channel := evtId.GetChannel(pd)
 | 
			
		||||
	if channel == "" {
 | 
			
		||||
		err = errors.New("参数校验失败!未知的类型:" + evtId.String())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	log.Println(channel)
 | 
			
		||||
	if params == nil {
 | 
			
		||||
		tmp := make(map[string]string)
 | 
			
		||||
		tmp["channel"] = channel
 | 
			
		||||
		res = append(res, tmp)
 | 
			
		||||
	} else {
 | 
			
		||||
		//log.Println(params)
 | 
			
		||||
		for _, param := range params {
 | 
			
		||||
 | 
			
		||||
			tmp := make(map[string]string)
 | 
			
		||||
			for k, v := range param {
 | 
			
		||||
				tmp[k] = v
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			val, ok := tmp["channel"]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				tmp["channel"] = channel
 | 
			
		||||
			} else {
 | 
			
		||||
				if val != channel {
 | 
			
		||||
					err = errors.New("参数校验失败!channel应为" + channel + val)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			res = append(res, tmp)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								submodules/okex/ws/ws_priv_channel.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								submodules/okex/ws/ws_priv_channel.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	. "v5sdk_go/ws/wImpl"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	订阅账户频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PrivAccout(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_ACCOUNT, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	订阅持仓频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PrivPostion(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_POSTION, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	订阅订单频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PrivBookOrder(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_ORDER, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	订阅策略委托订单频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PrivBookAlgoOrder(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_ALG_ORDER, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	订阅账户余额和持仓频道
 | 
			
		||||
*/
 | 
			
		||||
func (a *WsClient) PrivBalAndPos(op string, params []map[string]string, timeOut ...int) (res bool, msg []*Msg, err error) {
 | 
			
		||||
	return a.PubChannel(EVENT_BOOK_B_AND_P, op, params, PERIOD_NONE, timeOut...)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										99
									
								
								submodules/okex/ws/ws_priv_channel_Accout_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								submodules/okex/ws/ws_priv_channel_Accout_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,99 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
// HOW TO RUN
 | 
			
		||||
// go test ws_cli.go ws_op.go ws_contants.go utils.go ws_priv_channel.go ws_priv_channel_Accout_test.go -v
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	TRADE_ACCOUNT = iota
 | 
			
		||||
	ISOLATE_ACCOUNT
 | 
			
		||||
	CROSS_ACCOUNT
 | 
			
		||||
	CROSS_ACCOUNT_B
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func prework_pri(t int) *WsClient {
 | 
			
		||||
	// 模拟环境
 | 
			
		||||
	ep := "wss://wsaws.okex.com:8443/ws/v5/private"
 | 
			
		||||
	var apikey, passphrase, secretKey string
 | 
			
		||||
	// 把账号密码写这里
 | 
			
		||||
	switch t {
 | 
			
		||||
	case TRADE_ACCOUNT:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	case ISOLATE_ACCOUNT:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	case CROSS_ACCOUNT:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	case CROSS_ACCOUNT_B:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var res bool
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.Login(apikey, secretKey, passphrase)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("登录成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		log.Fatal("登录失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(apikey, secretKey, passphrase)
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 账户频道 测试
 | 
			
		||||
func TestAccout(t *testing.T) {
 | 
			
		||||
	r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
	var res bool
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["ccy"] = "BTC"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
	fmt.Println("args: ", args)
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PrivAccout(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅所有成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅所有成功!", err)
 | 
			
		||||
		t.Fatal("订阅所有成功!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(100 * time.Second)
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	// res, _, err = r.PrivAccout(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	// if res {
 | 
			
		||||
	// usedTime := time.Since(start)
 | 
			
		||||
	// fmt.Println("取消订阅所有成功!", usedTime.String())
 | 
			
		||||
	// } else {
 | 
			
		||||
	// fmt.Println("取消订阅所有失败!", err)
 | 
			
		||||
	// t.Fatal("取消订阅所有失败!", err)
 | 
			
		||||
	// }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										247
									
								
								submodules/okex/ws/ws_priv_channel_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										247
									
								
								submodules/okex/ws/ws_priv_channel_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,247 @@
 | 
			
		||||
package ws
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	TRADE_ACCOUNT = iota
 | 
			
		||||
	ISOLATE_ACCOUNT
 | 
			
		||||
	CROSS_ACCOUNT
 | 
			
		||||
	CROSS_ACCOUNT_B
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func prework_pri(t int) *WsClient {
 | 
			
		||||
	ep := "wss://wsaws.okex.com:8443/ws/v5/private"
 | 
			
		||||
	var apikey, passphrase, secretKey string
 | 
			
		||||
	// 把账号密码写这里
 | 
			
		||||
	switch t {
 | 
			
		||||
	case TRADE_ACCOUNT:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	case ISOLATE_ACCOUNT:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	case CROSS_ACCOUNT:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	case CROSS_ACCOUNT_B:
 | 
			
		||||
		apikey = "fe468418-5e40-433f-8d04-04951286d417"
 | 
			
		||||
		passphrase = "M4pw71Id"
 | 
			
		||||
		secretKey = "D6D74DF9DD60A25BE2B27CA71D8F814D"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r, err := NewWsClient(ep)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = r.Start()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var res bool
 | 
			
		||||
	//start := time.Now()
 | 
			
		||||
	res, _, err = r.Login(apikey, secretKey, passphrase)
 | 
			
		||||
	if res {
 | 
			
		||||
		//usedTime := time.Since(start)
 | 
			
		||||
		//fmt.Println("登录成功!",usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		log.Fatal("登录失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Println(apikey, secretKey, passphrase)
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 账户频道 测试
 | 
			
		||||
func TestAccout(t *testing.T) {
 | 
			
		||||
	r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
	var res bool
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	//arg["ccy"] = "BTC"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PrivAccout(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅所有成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅所有成功!", err)
 | 
			
		||||
		t.Fatal("订阅所有成功!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(100 * time.Second)
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PrivAccout(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅所有成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅所有失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅所有失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 持仓频道 测试
 | 
			
		||||
func TestPositon(t *testing.T) {
 | 
			
		||||
	r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instType"] = FUTURES
 | 
			
		||||
	arg["uly"] = "BTC-USD"
 | 
			
		||||
	//arg["instId"] = "BTC-USD-210319"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PrivPostion(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(60000 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PrivPostion(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 订单频道 测试
 | 
			
		||||
func TestBookOrder(t *testing.T) {
 | 
			
		||||
	r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instId"] = "BTC-USDT"
 | 
			
		||||
	arg["instType"] = "ANY"
 | 
			
		||||
	//arg["instType"] = "SWAP"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PrivBookOrder(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(6000 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PrivBookOrder(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 策略委托订单频道 测试
 | 
			
		||||
func TestAlgoOrder(t *testing.T) {
 | 
			
		||||
	r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	arg["instType"] = "SPOT"
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PrivBookAlgoOrder(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(60 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PrivBookAlgoOrder(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 账户余额和持仓频道 测试
 | 
			
		||||
func TestPrivBalAndPos(t *testing.T) {
 | 
			
		||||
	r := prework_pri(CROSS_ACCOUNT)
 | 
			
		||||
	var err error
 | 
			
		||||
	var res bool
 | 
			
		||||
 | 
			
		||||
	var args []map[string]string
 | 
			
		||||
	arg := make(map[string]string)
 | 
			
		||||
	args = append(args, arg)
 | 
			
		||||
 | 
			
		||||
	start := time.Now()
 | 
			
		||||
	res, _, err = r.PrivBalAndPos(OP_SUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("订阅失败!", err)
 | 
			
		||||
		t.Fatal("订阅失败!", err)
 | 
			
		||||
		//return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(600 * time.Second)
 | 
			
		||||
	//等待推送
 | 
			
		||||
 | 
			
		||||
	start = time.Now()
 | 
			
		||||
	res, _, err = r.PrivBalAndPos(OP_UNSUBSCRIBE, args)
 | 
			
		||||
	if res {
 | 
			
		||||
		usedTime := time.Since(start)
 | 
			
		||||
		fmt.Println("取消订阅成功!", usedTime.String())
 | 
			
		||||
	} else {
 | 
			
		||||
		fmt.Println("取消订阅失败!", err)
 | 
			
		||||
		t.Fatal("取消订阅失败!", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user