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 }