github.com/nntaoli-project/goex@v1.3.3/okex/OKExSwapWs.go (about) 1 package okex 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 . "github.com/nntaoli-project/goex" 8 "github.com/nntaoli-project/goex/internal/logger" 9 "sort" 10 "strconv" 11 "strings" 12 "time" 13 ) 14 15 type OKExV3SwapWs struct { 16 base *OKEx 17 v3Ws *OKExV3Ws 18 tickerCallback func(*FutureTicker) 19 depthCallback func(*Depth) 20 tradeCallback func(*Trade, string) 21 klineCallback func(*FutureKline, int) 22 } 23 24 func NewOKExV3SwapWs(base *OKEx) *OKExV3SwapWs { 25 okV3Ws := &OKExV3SwapWs{ 26 base: base, 27 } 28 okV3Ws.v3Ws = NewOKExV3Ws(base, okV3Ws.handle) 29 return okV3Ws 30 } 31 32 func (okV3Ws *OKExV3SwapWs) TickerCallback(tickerCallback func(*FutureTicker)) { 33 okV3Ws.tickerCallback = tickerCallback 34 } 35 36 func (okV3Ws *OKExV3SwapWs) DepthCallback(depthCallback func(*Depth)) { 37 okV3Ws.depthCallback = depthCallback 38 } 39 40 func (okV3Ws *OKExV3SwapWs) TradeCallback(tradeCallback func(*Trade, string)) { 41 okV3Ws.tradeCallback = tradeCallback 42 } 43 44 func (okV3Ws *OKExV3SwapWs) KlineCallback(klineCallback func(*FutureKline, int)) { 45 okV3Ws.klineCallback = klineCallback 46 } 47 48 func (okV3Ws *OKExV3SwapWs) SetCallbacks(tickerCallback func(*FutureTicker), 49 depthCallback func(*Depth), 50 tradeCallback func(*Trade, string), 51 klineCallback func(*FutureKline, int)) { 52 okV3Ws.tickerCallback = tickerCallback 53 okV3Ws.depthCallback = depthCallback 54 okV3Ws.tradeCallback = tradeCallback 55 okV3Ws.klineCallback = klineCallback 56 } 57 58 func (okV3Ws *OKExV3SwapWs) getChannelName(currencyPair CurrencyPair, contractType string) string { 59 var ( 60 prefix string 61 contractId string 62 channelName string 63 ) 64 65 if contractType == SWAP_CONTRACT { 66 prefix = "swap" 67 contractId = fmt.Sprintf("%s-SWAP", currencyPair.ToSymbol("-")) 68 } else { 69 prefix = "futures" 70 contractId = okV3Ws.base.OKExFuture.GetFutureContractId(currencyPair, contractType) 71 // logger.Info("contractid=", contractId) 72 } 73 74 if contractId == "" { 75 return "" 76 } 77 78 channelName = prefix + "/%s:" + contractId 79 80 return channelName 81 } 82 83 func (okV3Ws *OKExV3SwapWs) SubscribeDepth(currencyPair CurrencyPair, contractType string) error { 84 if okV3Ws.depthCallback == nil { 85 return errors.New("please set depth callback func") 86 } 87 88 chName := okV3Ws.getChannelName(currencyPair, contractType) 89 if chName == "" { 90 return errors.New("subscribe error, get channel name fail") 91 } 92 93 return okV3Ws.v3Ws.Subscribe(map[string]interface{}{ 94 "op": "subscribe", 95 "args": []string{fmt.Sprintf(chName, "depth5")}}) 96 } 97 98 func (okV3Ws *OKExV3SwapWs) SubscribeTicker(currencyPair CurrencyPair, contractType string) error { 99 if okV3Ws.tickerCallback == nil { 100 return errors.New("please set ticker callback func") 101 } 102 103 chName := okV3Ws.getChannelName(currencyPair, contractType) 104 if chName == "" { 105 return errors.New("subscribe error, get channel name fail") 106 } 107 108 return okV3Ws.v3Ws.Subscribe(map[string]interface{}{ 109 "op": "subscribe", 110 "args": []string{fmt.Sprintf(chName, "ticker")}}) 111 } 112 113 func (okV3Ws *OKExV3SwapWs) SubscribeTrade(currencyPair CurrencyPair, contractType string) error { 114 if okV3Ws.tradeCallback == nil { 115 return errors.New("please set trade callback func") 116 } 117 118 chName := okV3Ws.getChannelName(currencyPair, contractType) 119 if chName == "" { 120 return errors.New("subscribe error, get channel name fail") 121 } 122 123 return okV3Ws.v3Ws.Subscribe(map[string]interface{}{ 124 "op": "subscribe", 125 "args": []string{fmt.Sprintf(chName, "trade")}}) 126 } 127 128 func (okV3Ws *OKExV3SwapWs) SubscribeKline(currencyPair CurrencyPair, contractType string, period int) error { 129 if okV3Ws.klineCallback == nil { 130 return errors.New("place set kline callback func") 131 } 132 133 seconds := adaptKLinePeriod(KlinePeriod(period)) 134 if seconds == -1 { 135 return fmt.Errorf("unsupported kline period %d in okex", period) 136 } 137 138 chName := okV3Ws.getChannelName(currencyPair, contractType) 139 if chName == "" { 140 return errors.New("subscribe error, get channel name fail") 141 } 142 143 return okV3Ws.v3Ws.Subscribe(map[string]interface{}{ 144 "op": "subscribe", 145 "args": []string{fmt.Sprintf(chName, fmt.Sprintf("candle%ds", seconds))}}) 146 } 147 148 func (okV3Ws *OKExV3SwapWs) getContractAliasAndCurrencyPairFromInstrumentId(instrumentId string) (alias string, pair CurrencyPair) { 149 if strings.HasSuffix(instrumentId, "SWAP") { 150 ar := strings.Split(instrumentId, "-") 151 return instrumentId, NewCurrencyPair2(fmt.Sprintf("%s_%s", ar[0], ar[1])) 152 } else { 153 contractInfo, err := okV3Ws.base.OKExFuture.GetContractInfo(instrumentId) 154 if err != nil { 155 logger.Error("instrument id invalid:", err) 156 return "", UNKNOWN_PAIR 157 } 158 alias = contractInfo.Alias 159 pair = NewCurrencyPair2(fmt.Sprintf("%s_%s", contractInfo.UnderlyingIndex, contractInfo.QuoteCurrency)) 160 return alias, pair 161 } 162 } 163 164 func (okV3Ws *OKExV3SwapWs) handle(channel string, data json.RawMessage) error { 165 var ( 166 err error 167 ch string 168 tickers []tickerResponse 169 depthResp []depthResponse 170 dep Depth 171 tradeResponse []struct { 172 Side string `json:"side"` 173 TradeId int64 `json:"trade_id,string"` 174 Price float64 `json:"price,string"` 175 Qty float64 `json:"qty,string"` 176 InstrumentId string `json:"instrument_id"` 177 Timestamp string `json:"timestamp"` 178 } 179 klineResponse []struct { 180 Candle []string `json:"candle"` 181 InstrumentId string `json:"instrument_id"` 182 } 183 ) 184 185 if strings.Contains(channel, "futures/candle") || 186 strings.Contains(channel, "swap/candle") { 187 ch = "candle" 188 } else { 189 ch, err = okV3Ws.v3Ws.parseChannel(channel) 190 if err != nil { 191 logger.Errorf("[%s] parse channel err=%s , originChannel=%s", okV3Ws.base.GetExchangeName(), err, ch) 192 return nil 193 } 194 } 195 196 switch ch { 197 case "ticker": 198 err = json.Unmarshal(data, &tickers) 199 if err != nil { 200 return err 201 } 202 203 for _, t := range tickers { 204 alias, pair := okV3Ws.getContractAliasAndCurrencyPairFromInstrumentId(t.InstrumentId) 205 date, _ := time.Parse(time.RFC3339, t.Timestamp) 206 okV3Ws.tickerCallback(&FutureTicker{ 207 Ticker: &Ticker{ 208 Pair: pair, 209 Last: t.Last, 210 Buy: t.BestBid, 211 Sell: t.BestAsk, 212 High: t.High24h, 213 Low: t.Low24h, 214 Vol: t.Volume24h, 215 Date: uint64(date.UnixNano() / int64(time.Millisecond)), 216 }, 217 ContractId: t.InstrumentId, 218 ContractType: alias, 219 }) 220 } 221 return nil 222 case "candle": 223 err = json.Unmarshal(data, &klineResponse) 224 if err != nil { 225 return err 226 } 227 228 for _, t := range klineResponse { 229 _, pair := okV3Ws.getContractAliasAndCurrencyPairFromInstrumentId(t.InstrumentId) 230 ts, _ := time.Parse(time.RFC3339, t.Candle[0]) 231 //granularity := adaptKLinePeriod(KlinePeriod(period)) 232 okV3Ws.klineCallback(&FutureKline{ 233 Kline: &Kline{ 234 Pair: pair, 235 High: ToFloat64(t.Candle[2]), 236 Low: ToFloat64(t.Candle[3]), 237 Timestamp: ts.Unix(), 238 Open: ToFloat64(t.Candle[1]), 239 Close: ToFloat64(t.Candle[4]), 240 Vol: ToFloat64(t.Candle[5]), 241 }, 242 Vol2: ToFloat64(t.Candle[6]), 243 }, 1) 244 } 245 return nil 246 case "depth5": 247 err := json.Unmarshal(data, &depthResp) 248 if err != nil { 249 logger.Error(err) 250 return err 251 } 252 if len(depthResp) == 0 { 253 return nil 254 } 255 alias, pair := okV3Ws.getContractAliasAndCurrencyPairFromInstrumentId(depthResp[0].InstrumentId) 256 dep.Pair = pair 257 dep.ContractType = alias 258 dep.ContractId = depthResp[0].InstrumentId 259 dep.UTime, _ = time.Parse(time.RFC3339, depthResp[0].Timestamp) 260 for _, itm := range depthResp[0].Asks { 261 dep.AskList = append(dep.AskList, DepthRecord{ 262 Price: ToFloat64(itm[0]), 263 Amount: ToFloat64(itm[1])}) 264 } 265 for _, itm := range depthResp[0].Bids { 266 dep.BidList = append(dep.BidList, DepthRecord{ 267 Price: ToFloat64(itm[0]), 268 Amount: ToFloat64(itm[1])}) 269 } 270 sort.Sort(sort.Reverse(dep.AskList)) 271 //call back func 272 okV3Ws.depthCallback(&dep) 273 return nil 274 case "trade": 275 err := json.Unmarshal(data, &tradeResponse) 276 if err != nil { 277 logger.Error("unmarshal error :", err) 278 return err 279 } 280 281 for _, resp := range tradeResponse { 282 alias, pair := okV3Ws.getContractAliasAndCurrencyPairFromInstrumentId(resp.InstrumentId) 283 284 tradeSide := SELL 285 switch resp.Side { 286 case "buy": 287 tradeSide = BUY 288 } 289 290 t, err := time.Parse(time.RFC3339, resp.Timestamp) 291 if err != nil { 292 logger.Warn("parse timestamp error:", err) 293 } 294 295 okV3Ws.tradeCallback(&Trade{ 296 Tid: resp.TradeId, 297 Type: tradeSide, 298 Amount: resp.Qty, 299 Price: resp.Price, 300 Date: t.Unix(), 301 Pair: pair, 302 }, alias) 303 } 304 return nil 305 } 306 307 return fmt.Errorf("[%s] unknown websocket message: %s", ch, string(data)) 308 } 309 310 func (okV3Ws *OKExV3SwapWs) getKlinePeriodFormChannel(channel string) int { 311 metas := strings.Split(channel, ":") 312 if len(metas) != 2 { 313 return 0 314 } 315 i, _ := strconv.ParseInt(metas[1], 10, 64) 316 return int(i) 317 }