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  }