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  }