github.com/nntaoli-project/goex@v1.3.3/binance/SpotWs.go (about)

     1  package binance
     2  
     3  import (
     4  	json2 "encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"sort"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/nntaoli-project/goex"
    13  	"github.com/nntaoli-project/goex/internal/logger"
    14  )
    15  
    16  type req struct {
    17  	Method string   `json:"method"`
    18  	Params []string `json:"params"`
    19  	Id     int      `json:"id"`
    20  }
    21  
    22  type resp struct {
    23  	Stream string           `json:"stream"`
    24  	Data   json2.RawMessage `json:"data"`
    25  }
    26  
    27  type depthResp struct {
    28  	LastUpdateId int             `json:"lastUpdateId"`
    29  	Bids         [][]interface{} `json:"bids"`
    30  	Asks         [][]interface{} `json:"asks"`
    31  }
    32  
    33  type SpotWs struct {
    34  	c         *goex.WsConn
    35  	once      sync.Once
    36  	wsBuilder *goex.WsBuilder
    37  
    38  	reqId int
    39  
    40  	depthCallFn  func(depth *goex.Depth)
    41  	tickerCallFn func(ticker *goex.Ticker)
    42  	tradeCallFn  func(trade *goex.Trade)
    43  }
    44  
    45  func NewSpotWs() *SpotWs {
    46  	spotWs := &SpotWs{}
    47  	logger.Debugf("proxy url: %s", os.Getenv("HTTPS_PROXY"))
    48  
    49  	spotWs.wsBuilder = goex.NewWsBuilder().
    50  		WsUrl(TESTNET_SPOT_STREAM_BASE_URL + "?streams=depth/miniTicker/ticker/trade").
    51  		ProxyUrl(os.Getenv("HTTPS_PROXY")).
    52  		ProtoHandleFunc(spotWs.handle).AutoReconnect()
    53  
    54  	spotWs.reqId = 1
    55  
    56  	return spotWs
    57  }
    58  
    59  func (s *SpotWs) connect() {
    60  	s.once.Do(func() {
    61  		s.c = s.wsBuilder.Build()
    62  	})
    63  }
    64  
    65  func (s *SpotWs) DepthCallback(f func(depth *goex.Depth)) {
    66  	s.depthCallFn = f
    67  }
    68  
    69  func (s *SpotWs) TickerCallback(f func(ticker *goex.Ticker)) {
    70  	s.tickerCallFn = f
    71  }
    72  
    73  func (s *SpotWs) TradeCallback(f func(trade *goex.Trade)) {
    74  	s.tradeCallFn = f
    75  }
    76  
    77  func (s *SpotWs) SubscribeDepth(pair goex.CurrencyPair) error {
    78  	defer func() {
    79  		s.reqId++
    80  	}()
    81  
    82  	s.connect()
    83  
    84  	return s.c.Subscribe(req{
    85  		Method: "SUBSCRIBE",
    86  		Params: []string{
    87  			fmt.Sprintf("%s@depth10@100ms", pair.ToLower().ToSymbol("")),
    88  		},
    89  		Id: s.reqId,
    90  	})
    91  }
    92  
    93  func (s *SpotWs) SubscribeTicker(pair goex.CurrencyPair) error {
    94  	defer func() {
    95  		s.reqId++
    96  	}()
    97  
    98  	s.connect()
    99  
   100  	return s.c.Subscribe(req{
   101  		Method: "SUBSCRIBE",
   102  		Params: []string{pair.ToLower().ToSymbol("") + "@ticker"},
   103  		Id:     s.reqId,
   104  	})
   105  }
   106  
   107  // TODO: test
   108  func (s *SpotWs) SubscribeTrade(pair goex.CurrencyPair) error {
   109  
   110  	defer func() {
   111  		s.reqId++
   112  	}()
   113  
   114  	s.connect()
   115  
   116  	return s.c.Subscribe(req{
   117  		Method: "SUBSCRIBE",
   118  		Params: []string{pair.ToLower().ToSymbol("") + "@aggTrade"},
   119  		Id:     s.reqId,
   120  	})
   121  }
   122  
   123  func (s *SpotWs) handle(data []byte) error {
   124  	var r resp
   125  	err := json2.Unmarshal(data, &r)
   126  	if err != nil {
   127  		logger.Errorf("json unmarshal ws response error [%s] , response data = %s", err, string(data))
   128  		return err
   129  	}
   130  
   131  	if strings.HasSuffix(r.Stream, "@depth10@100ms") {
   132  		return s.depthHandle(r.Data, adaptStreamToCurrencyPair(r.Stream))
   133  	}
   134  
   135  	if strings.HasSuffix(r.Stream, "@ticker") {
   136  		return s.tickerHandle(r.Data, adaptStreamToCurrencyPair(r.Stream))
   137  	}
   138  
   139  	if strings.HasSuffix(r.Stream, "@aggTrade") {
   140  		return s.tradeHandle(r.Data, adaptStreamToCurrencyPair(r.Stream))
   141  	}
   142  
   143  	logger.Warn("unknown ws response:", string(data))
   144  
   145  	return nil
   146  }
   147  
   148  func (s *SpotWs) depthHandle(data json2.RawMessage, pair goex.CurrencyPair) error {
   149  	var (
   150  		depthR depthResp
   151  		dep    goex.Depth
   152  		err    error
   153  	)
   154  
   155  	err = json2.Unmarshal(data, &depthR)
   156  	if err != nil {
   157  		logger.Errorf("unmarshal depth response error %s[] , response data = %s", err, string(data))
   158  		return err
   159  	}
   160  
   161  	dep.UTime = time.Now()
   162  	dep.Pair = pair
   163  
   164  	for _, bid := range depthR.Bids {
   165  		dep.BidList = append(dep.BidList, goex.DepthRecord{
   166  			Price:  goex.ToFloat64(bid[0]),
   167  			Amount: goex.ToFloat64(bid[1]),
   168  		})
   169  	}
   170  
   171  	for _, ask := range depthR.Asks {
   172  		dep.AskList = append(dep.AskList, goex.DepthRecord{
   173  			Price:  goex.ToFloat64(ask[0]),
   174  			Amount: goex.ToFloat64(ask[1]),
   175  		})
   176  	}
   177  
   178  	sort.Sort(sort.Reverse(dep.AskList))
   179  
   180  	s.depthCallFn(&dep)
   181  
   182  	return nil
   183  }
   184  
   185  func (s *SpotWs) tickerHandle(data json2.RawMessage, pair goex.CurrencyPair) error {
   186  	var (
   187  		tickerData = make(map[string]interface{}, 4)
   188  		ticker     goex.Ticker
   189  	)
   190  
   191  	err := json2.Unmarshal(data, &tickerData)
   192  	if err != nil {
   193  		logger.Errorf("unmarshal ticker response data error [%s] , data = %s", err, string(data))
   194  		return err
   195  	}
   196  
   197  	ticker.Pair = pair
   198  	ticker.Vol = goex.ToFloat64(tickerData["v"])
   199  	ticker.Last = goex.ToFloat64(tickerData["c"])
   200  	ticker.Sell = goex.ToFloat64(tickerData["a"])
   201  	ticker.Buy = goex.ToFloat64(tickerData["b"])
   202  	ticker.High = goex.ToFloat64(tickerData["h"])
   203  	ticker.Low = goex.ToFloat64(tickerData["l"])
   204  	ticker.Date = goex.ToUint64(tickerData["E"])
   205  
   206  	s.tickerCallFn(&ticker)
   207  
   208  	return nil
   209  }
   210  
   211  func (s *SpotWs) tradeHandle(data json2.RawMessage, pair goex.CurrencyPair) error {
   212  
   213  	var (
   214  		tradeData = make(map[string]interface{}, 4)
   215  		trade     goex.Trade
   216  	)
   217  
   218  	err := json2.Unmarshal(data, &tradeData)
   219  	if err != nil {
   220  		logger.Errorf("unmarshal ticker response data error [%s] , data = %s", err, string(data))
   221  		return err
   222  	}
   223  
   224  	trade.Pair = pair                             //Symbol
   225  	trade.Tid = goex.ToInt64(tradeData["a"])      // Aggregate trade ID
   226  	trade.Date = goex.ToInt64(tradeData["E"])     // Event time
   227  	trade.Amount = goex.ToFloat64(tradeData["q"]) // Quantity
   228  	trade.Price = goex.ToFloat64(tradeData["p"])  // Price
   229  
   230  	if tradeData["m"].(bool) {
   231  		trade.Type = goex.BUY_MARKET
   232  	} else {
   233  		trade.Type = goex.SELL_MARKET
   234  	}
   235  
   236  	return nil
   237  }