github.com/frankrap/okex-api@v1.0.4/swap_ws.go (about) 1 package okex 2 3 import ( 4 "context" 5 "fmt" 6 "github.com/recws-org/recws" 7 "github.com/tidwall/gjson" 8 "log" 9 "net/http" 10 "net/url" 11 "sync" 12 "time" 13 ) 14 15 const ( 16 TableSwapTicker = "swap/ticker" // 公共-Ticker频道 17 TableSwapTrade = "swap/trade" // 公共-交易频道 18 TableSwapDepthL2Tbt = "swap/depth_l2_tbt" // 公共-400档增量数据频道 19 TableSwapPosition = "swap/position" // 用户持仓频道 20 TableSwapAccount = "swap/account" // 用户账户频道 21 TableSwapOrder = "swap/order" // 用户交易频道 22 ) 23 24 type SwapWS struct { 25 sync.RWMutex 26 27 wsURL string 28 accessKey string 29 secretKey string 30 passphrase string 31 debugMode bool 32 33 ctx context.Context 34 cancel context.CancelFunc 35 conn recws.RecConn 36 37 subscriptions map[string]interface{} 38 39 tickersCallback func(tickers []WSTicker) 40 tradesCallback func(trades []WSTrade) 41 depthL2TbtCallback func(action string, data []WSDepthL2Tbt) 42 depth20SnapshotCallback func(ob *OrderBook) // 20档盘口 43 accountCallback func(accounts []WSAccount) 44 positionCallback func(positions []WSSwapPositionData) 45 orderCallback func(orders []WSOrder) 46 47 dobMap map[string]*DepthOrderBook 48 } 49 50 // SetProxy 设置代理地址 51 // porxyURL: 52 // socks5://127.0.0.1:1080 53 // https://127.0.0.1:1080 54 func (ws *SwapWS) SetProxy(proxyURL string) (err error) { 55 var purl *url.URL 56 purl, err = url.Parse(proxyURL) 57 if err != nil { 58 return 59 } 60 log.Printf("[ws][%s] proxy url:%s", proxyURL, purl) 61 ws.conn.Proxy = http.ProxyURL(purl) 62 return 63 } 64 65 func (ws *SwapWS) SetTickerCallback(callback func(tickers []WSTicker)) { 66 ws.tickersCallback = callback 67 } 68 69 func (ws *SwapWS) SetTradeCallback(callback func(trades []WSTrade)) { 70 ws.tradesCallback = callback 71 } 72 73 func (ws *SwapWS) SetDepthL2TbtCallback(callback func(action string, data []WSDepthL2Tbt)) { 74 ws.depthL2TbtCallback = callback 75 } 76 77 func (ws *SwapWS) SetDepth20SnapshotCallback(callback func(ob *OrderBook)) { 78 ws.depth20SnapshotCallback = callback 79 } 80 81 func (ws *SwapWS) SetAccountCallback(callback func(accounts []WSAccount)) { 82 ws.accountCallback = callback 83 } 84 85 func (ws *SwapWS) SetPositionCallback(callback func(position []WSSwapPositionData)) { 86 ws.positionCallback = callback 87 } 88 89 func (ws *SwapWS) SetOrderCallback(callback func(orders []WSOrder)) { 90 ws.orderCallback = callback 91 } 92 93 func (ws *SwapWS) SubscribeTicker(id string, symbol string) error { 94 ch := fmt.Sprintf("%v:%v", TableSwapTicker, symbol) 95 return ws.Subscribe(id, []string{ch}) 96 } 97 98 func (ws *SwapWS) SubscribeTrade(id string, symbol string) error { 99 ch := fmt.Sprintf("%v:%v", TableSwapTrade, symbol) 100 return ws.Subscribe(id, []string{ch}) 101 } 102 103 // SubscribeDepthL2Tbt 公共-400档增量数据频道 104 // 订阅后首次返回市场订单簿的400档深度数据并推送;后续只要订单簿深度有变化就推送有更改的数据。 105 func (ws *SwapWS) SubscribeDepthL2Tbt(id string, symbol string) error { 106 ch := fmt.Sprintf("%v:%v", TableSwapDepthL2Tbt, symbol) 107 return ws.Subscribe(id, []string{ch}) 108 } 109 110 func (ws *SwapWS) SubscribePosition(id string, symbol string) error { 111 ch := fmt.Sprintf("%v:%v", TableSwapPosition, symbol) 112 return ws.Subscribe(id, []string{ch}) 113 } 114 115 func (ws *SwapWS) SubscribeAccount(id string, symbol string) error { 116 ch := fmt.Sprintf("%v:%v", TableSwapAccount, symbol) 117 return ws.Subscribe(id, []string{ch}) 118 } 119 120 func (ws *SwapWS) SubscribeOrder(id string, symbol string) error { 121 ch := fmt.Sprintf("%v:%v", TableSwapOrder, symbol) 122 return ws.Subscribe(id, []string{ch}) 123 } 124 125 // Subscribe 订阅 126 func (ws *SwapWS) Subscribe(id string, args []string) error { 127 ws.Lock() 128 defer ws.Unlock() 129 130 type Op struct { 131 Op string `json:"op"` 132 Args []string `json:"args"` 133 } 134 135 op := Op{ 136 Op: "subscribe", 137 Args: args, 138 } 139 ws.subscriptions[id] = op 140 return ws.sendWSMessage(op) 141 } 142 143 // Unsubscribe 取消订阅 144 func (ws *SwapWS) Unsubscribe(id string) error { 145 ws.Lock() 146 defer ws.Unlock() 147 148 if _, ok := ws.subscriptions[id]; ok { 149 delete(ws.subscriptions, id) 150 } 151 return nil 152 } 153 154 func (ws *SwapWS) Login() error { 155 if ws.accessKey == "" || ws.secretKey == "" || ws.passphrase == "" { 156 return fmt.Errorf("missing key") 157 } 158 timestamp := EpochTime() 159 160 preHash := PreHashString(timestamp, GET, "/users/self/verify", "") 161 if sign, err := HmacSha256Base64Signer(preHash, ws.secretKey); err != nil { 162 return err 163 } else { 164 op, err := loginOp(ws.accessKey, ws.passphrase, timestamp, sign) 165 if err != nil { 166 return err 167 } 168 //data, err := Struct2JsonString(op) 169 log.Printf("Send Msg: %#v", *op) 170 //err = a.conn.WriteMessage(websocket.TextMessage, []byte(data)) 171 err = ws.sendWSMessage(op) 172 if err != nil { 173 return err 174 } 175 time.Sleep(time.Millisecond * 100) 176 } 177 return nil 178 } 179 180 func (ws *SwapWS) subscribeHandler() error { 181 //log.Printf("subscribeHandler") 182 ws.Lock() 183 defer ws.Unlock() 184 185 err := ws.Login() 186 if err != nil { 187 log.Printf("login error: %v", err) 188 } 189 190 for _, v := range ws.subscriptions { 191 //log.Printf("sub: %#v", v) 192 err := ws.sendWSMessage(v) 193 if err != nil { 194 log.Printf("%v", err) 195 } 196 } 197 return nil 198 } 199 200 func (ws *SwapWS) sendWSMessage(msg interface{}) error { 201 return ws.conn.WriteJSON(msg) 202 } 203 204 func (ws *SwapWS) Start() { 205 log.Printf("wsURL: %v", ws.wsURL) 206 ws.conn.Dial(ws.wsURL, nil) 207 go ws.run() 208 } 209 210 func (ws *SwapWS) run() { 211 ctx := context.Background() 212 for { 213 select { 214 case <-ctx.Done(): 215 go ws.conn.Close() 216 log.Printf("Websocket closed %s", ws.conn.GetURL()) 217 return 218 default: 219 messageType, msg, err := ws.conn.ReadMessage() 220 if err != nil { 221 log.Printf("Read error: %v", err) 222 time.Sleep(100 * time.Millisecond) 223 continue 224 } 225 226 msg, err = FlateUnCompress(msg) 227 if err != nil { 228 log.Printf("%v", err) 229 continue 230 } 231 232 ws.handleMsg(messageType, msg) 233 } 234 } 235 } 236 237 func (ws *SwapWS) handleMsg(messageType int, msg []byte) { 238 ret := gjson.ParseBytes(msg) 239 // 登录成功 240 // {"event":"login","success":true} 241 242 if tableValue := ret.Get("table"); tableValue.Exists() { 243 table := tableValue.String() 244 if table == TableSwapDepthL2Tbt { // 优先判断最高频数据 245 var depthL2 WSDepthL2TbtResult 246 err := json.Unmarshal(msg, &depthL2) 247 if err != nil { 248 log.Printf("%v", err) 249 return 250 } 251 252 if ws.depthL2TbtCallback != nil { 253 ws.depthL2TbtCallback(depthL2.Action, depthL2.Data) 254 } 255 256 if ws.depth20SnapshotCallback != nil { 257 for _, v := range depthL2.Data { 258 var ob OrderBook 259 if v1, ok := ws.dobMap[v.InstrumentID]; ok { 260 v1.Update(depthL2.Action, &v) 261 ob = v1.GetOrderBook(20) 262 } else { 263 dob := NewDepthOrderBook(v.InstrumentID) 264 dob.Update(depthL2.Action, &v) 265 ws.dobMap[v.InstrumentID] = dob 266 ob = dob.GetOrderBook(20) 267 } 268 ws.depth20SnapshotCallback(&ob) 269 } 270 } 271 return 272 } else if table == TableSwapTicker { 273 var tickerResult WSTickerResult 274 err := json.Unmarshal(msg, &tickerResult) 275 if err != nil { 276 log.Printf("%v", err) 277 return 278 } 279 280 if ws.tickersCallback != nil { 281 ws.tickersCallback(tickerResult.Data) 282 } 283 return 284 } else if table == TableSwapTrade { 285 var tradeResult WSTradeResult 286 err := json.Unmarshal(msg, &tradeResult) 287 if err != nil { 288 log.Printf("%v", err) 289 return 290 } 291 292 if ws.tradesCallback != nil { 293 ws.tradesCallback(tradeResult.Data) 294 } 295 return 296 } else if table == TableSwapAccount { 297 var accountResult WSAccountResult 298 err := json.Unmarshal(msg, &accountResult) 299 if err != nil { 300 log.Printf("%v", err) 301 return 302 } 303 304 if ws.accountCallback != nil { 305 var accounts []WSAccount 306 for _, v := range accountResult.Data { 307 if v.BTC != nil { 308 accounts = append(accounts, *v.BTC) 309 continue 310 } 311 if v.ETH != nil { 312 accounts = append(accounts, *v.ETH) 313 continue 314 } 315 if v.ETC != nil { 316 accounts = append(accounts, *v.ETC) 317 continue 318 } 319 if v.XRP != nil { 320 accounts = append(accounts, *v.XRP) 321 continue 322 } 323 if v.EOS != nil { 324 accounts = append(accounts, *v.EOS) 325 continue 326 } 327 if v.BCH != nil { 328 accounts = append(accounts, *v.BCH) 329 continue 330 } 331 if v.BSV != nil { 332 accounts = append(accounts, *v.BSV) 333 continue 334 } 335 if v.TRX != nil { 336 accounts = append(accounts, *v.TRX) 337 continue 338 } 339 } 340 ws.accountCallback(accounts) 341 } 342 return 343 } else if table == TableSwapPosition { 344 var positionResult WSSwapPositionResult 345 err := json.Unmarshal(msg, &positionResult) 346 if err != nil { 347 log.Printf("%v", err) 348 return 349 } 350 351 if ws.positionCallback != nil { 352 ws.positionCallback(positionResult.Data) 353 } 354 return 355 } else if table == TableSwapOrder { 356 var orderResult WSOrderResult 357 err := json.Unmarshal(msg, &orderResult) 358 if err != nil { 359 log.Printf("%v", err) 360 return 361 } 362 363 if ws.orderCallback != nil { 364 ws.orderCallback(orderResult.Data) 365 } 366 return 367 } 368 log.Printf("%v", string(msg)) 369 return 370 } 371 372 if eventValue := ret.Get("event"); eventValue.Exists() { 373 event := eventValue.String() 374 if event == "error" { 375 log.Printf("error: %v", string(msg)) 376 return 377 } 378 log.Printf("%v", string(msg)) 379 return 380 } 381 382 log.Printf("%v", string(msg)) 383 } 384 385 // NewSwapWS 创建永续合约WS 386 // wsURL: 387 // wss://real.okex.com:8443/ws/v3 388 func NewSwapWS(wsURL string, accessKey string, secretKey string, passphrase string, debugMode bool) *SwapWS { 389 ws := &SwapWS{ 390 wsURL: wsURL, 391 accessKey: accessKey, 392 secretKey: secretKey, 393 passphrase: passphrase, 394 debugMode: debugMode, 395 subscriptions: make(map[string]interface{}), 396 dobMap: make(map[string]*DepthOrderBook), 397 } 398 ws.ctx, ws.cancel = context.WithCancel(context.Background()) 399 ws.conn = recws.RecConn{ 400 KeepAliveTimeout: 10 * time.Second, 401 } 402 ws.conn.SubscribeHandler = ws.subscribeHandler 403 return ws 404 }