diff --git a/exchanges/hbdm/orderbook.go b/exchanges/hbdm/orderbook.go new file mode 100644 index 0000000..fa3ea85 --- /dev/null +++ b/exchanges/hbdm/orderbook.go @@ -0,0 +1,154 @@ +package hbdm + +import ( + "fmt" + "github.com/MauriceGit/skiplist" + . "github.com/coinrust/crex" + "github.com/frankrap/huobi-api/hbdm" +) + +type DobItem struct { + Price float64 + Amount float64 +} + +func (e DobItem) ExtractKey() float64 { + return e.Price +} + +func (e DobItem) String() string { + return fmt.Sprintf("%.2f", e.Price) +} + +type DepthOrderBook struct { + symbol string + asks skiplist.SkipList + bids skiplist.SkipList +} + +func (d *DepthOrderBook) GetSymbol() string { + return d.symbol +} + +func (d *DepthOrderBook) Update(data *hbdm.WSDepthHF) { + if data.Tick.Event == "snapshot" { + d.asks = skiplist.New() + d.bids = skiplist.New() + for _, ask := range data.Tick.Asks { + d.asks.Insert(DobItem{ + Price: ask[0], + Amount: ask[1], + }) + } + for _, bid := range data.Tick.Bids { + d.bids.Insert(DobItem{ + Price: bid[0], + Amount: bid[1], + }) + } + return + } + + if data.Tick.Event == "update" { + for _, ask := range data.Tick.Asks { + price := ask[0] + amount := ask[1] + if amount == 0 { + d.asks.Delete(DobItem{ + Price: price, + Amount: amount, + }) + } else { + item := DobItem{ + Price: price, + Amount: amount, + } + elem, ok := d.asks.Find(item) + if ok { + d.asks.ChangeValue(elem, item) + } else { + d.asks.Insert(item) + } + } + } + for _, bid := range data.Tick.Bids { + price := bid[0] + amount := bid[1] + if amount == 0 { + d.bids.Delete(DobItem{ + Price: price, + Amount: amount, + }) + } else { + item := DobItem{ + Price: price, + Amount: amount, + } + elem, ok := d.bids.Find(item) + if ok { + d.bids.ChangeValue(elem, item) + } else { + d.bids.Insert(item) + } + } + } + } +} + +func (d *DepthOrderBook) GetOrderBook(depth int) (result OrderBook) { + result.Symbol = d.symbol + smallest := d.asks.GetSmallestNode() + if smallest != nil { + item := smallest.GetValue().(DobItem) + result.Asks = append(result.Asks, Item{ + Price: item.Price, + Amount: item.Amount, + }) + count := 1 + node := smallest + for count < depth { + node = d.asks.Next(node) + if node == nil { + break + } + item := node.GetValue().(DobItem) + result.Asks = append(result.Asks, Item{ + Price: item.Price, + Amount: item.Amount, + }) + count++ + } + } + + largest := d.bids.GetLargestNode() + if largest != nil { + item := largest.GetValue().(DobItem) + result.Bids = append(result.Bids, Item{ + Price: item.Price, + Amount: item.Amount, + }) + count := 1 + node := largest + for count < depth { + node = d.bids.Prev(node) + if node == nil { + break + } + item := node.GetValue().(DobItem) + result.Bids = append(result.Bids, Item{ + Price: item.Price, + Amount: item.Amount, + }) + count++ + } + } + return +} + +func NewDepthOrderBook(symbol string) *DepthOrderBook { + return &DepthOrderBook{ + symbol: symbol, + asks: skiplist.New(), + bids: skiplist.New(), + } +} diff --git a/exchanges/hbdm/websocket.go b/exchanges/hbdm/websocket.go index 3e89280..93d6c0c 100644 --- a/exchanges/hbdm/websocket.go +++ b/exchanges/hbdm/websocket.go @@ -12,6 +12,7 @@ import ( type HbdmWebSocket struct { ws *hbdm.WS nws *hbdm.NWS + dobMap map[string]*DepthOrderBook emitter *emission.Emitter } @@ -24,8 +25,9 @@ func (s *HbdmWebSocket) SubscribeTrades(symbol string, contractType string, call func (s *HbdmWebSocket) SubscribeLevel2Snapshots(symbol string, contractType string, callback func(ob *OrderBook)) error { s.emitter.On(WSEventL2Snapshot, callback) - s.ws.SubscribeDepth("depth_1", - s.convertToSymbol(symbol, contractType)) + //s.ws.SubscribeDepth("depth_1", + // s.convertToSymbol(symbol, contractType)) + s.ws.SubscribeDepthHF("depth_1", s.convertToSymbol(symbol, contractType), 20, "incremental") return nil } @@ -84,6 +86,22 @@ func (s *HbdmWebSocket) depthCallback(depth *hbdm.WSDepth) { s.emitter.Emit(WSEventL2Snapshot, ob) } +func (s *HbdmWebSocket) depthHFCallback(depth *hbdm.WSDepthHF) { + // ch: market.BTC_CQ.depth.size_20.high_freq + symbol := depth.Ch + if v, ok := s.dobMap[symbol]; ok { + v.Update(depth) + ob := v.GetOrderBook(20) + s.emitter.Emit(WSEventL2Snapshot, &ob) + } else { + dob := NewDepthOrderBook(symbol) + dob.Update(depth) + s.dobMap[symbol] = dob + ob := dob.GetOrderBook(20) + s.emitter.Emit(WSEventL2Snapshot, &ob) + } +} + func (s *HbdmWebSocket) tradeCallback(trade *hbdm.WSTrade) { // log.Printf("tradeCallback") var trades []Trade @@ -184,14 +202,19 @@ func (s *HbdmWebSocket) positionsCallback(positions *hbdm.WSPositions) { func NewHbdmWebSocket(params *Parameters) *HbdmWebSocket { wsURL := "wss://api.hbdm.com/ws" + if params.WsURL != "" { + wsURL = params.WsURL + } s := &HbdmWebSocket{ + dobMap: make(map[string]*DepthOrderBook), emitter: emission.NewEmitter(), } - ws := hbdm.NewWS(wsURL, "", "") + ws := hbdm.NewWS(wsURL, "", "", params.DebugMode) if params.ProxyURL != "" { ws.SetProxy(params.ProxyURL) } ws.SetDepthCallback(s.depthCallback) + ws.SetDepthHFCallback(s.depthHFCallback) ws.SetTradeCallback(s.tradeCallback) ws.Start() s.ws = ws diff --git a/exchanges/hbdm/websocket_test.go b/exchanges/hbdm/websocket_test.go index 2422e1a..d6f5fdd 100644 --- a/exchanges/hbdm/websocket_test.go +++ b/exchanges/hbdm/websocket_test.go @@ -15,6 +15,7 @@ func testWebSocket() *HbdmWebSocket { params.SecretKey = testConfig.SecretKey params.ProxyURL = testConfig.ProxyURL params.Testnet = testConfig.Testnet + params.WsURL = "wss://api.btcgateway.pro/ws" ws := NewHbdmWebSocket(params) return ws } diff --git a/exchanges/hbdmswap/orderbook.go b/exchanges/hbdmswap/orderbook.go new file mode 100644 index 0000000..f89d4aa --- /dev/null +++ b/exchanges/hbdmswap/orderbook.go @@ -0,0 +1,154 @@ +package hbdmswap + +import ( + "fmt" + "github.com/MauriceGit/skiplist" + . "github.com/coinrust/crex" + "github.com/frankrap/huobi-api/hbdmswap" +) + +type DobItem struct { + Price float64 + Amount float64 +} + +func (e DobItem) ExtractKey() float64 { + return e.Price +} + +func (e DobItem) String() string { + return fmt.Sprintf("%.2f", e.Price) +} + +type DepthOrderBook struct { + symbol string + asks skiplist.SkipList + bids skiplist.SkipList +} + +func (d *DepthOrderBook) GetSymbol() string { + return d.symbol +} + +func (d *DepthOrderBook) Update(data *hbdmswap.WSDepthHF) { + if data.Tick.Event == "snapshot" { + d.asks = skiplist.New() + d.bids = skiplist.New() + for _, ask := range data.Tick.Asks { + d.asks.Insert(DobItem{ + Price: ask[0], + Amount: ask[1], + }) + } + for _, bid := range data.Tick.Bids { + d.bids.Insert(DobItem{ + Price: bid[0], + Amount: bid[1], + }) + } + return + } + + if data.Tick.Event == "update" { + for _, ask := range data.Tick.Asks { + price := ask[0] + amount := ask[1] + if amount == 0 { + d.asks.Delete(DobItem{ + Price: price, + Amount: amount, + }) + } else { + item := DobItem{ + Price: price, + Amount: amount, + } + elem, ok := d.asks.Find(item) + if ok { + d.asks.ChangeValue(elem, item) + } else { + d.asks.Insert(item) + } + } + } + for _, bid := range data.Tick.Bids { + price := bid[0] + amount := bid[1] + if amount == 0 { + d.bids.Delete(DobItem{ + Price: price, + Amount: amount, + }) + } else { + item := DobItem{ + Price: price, + Amount: amount, + } + elem, ok := d.bids.Find(item) + if ok { + d.bids.ChangeValue(elem, item) + } else { + d.bids.Insert(item) + } + } + } + } +} + +func (d *DepthOrderBook) GetOrderBook(depth int) (result OrderBook) { + result.Symbol = d.symbol + smallest := d.asks.GetSmallestNode() + if smallest != nil { + item := smallest.GetValue().(DobItem) + result.Asks = append(result.Asks, Item{ + Price: item.Price, + Amount: item.Amount, + }) + count := 1 + node := smallest + for count < depth { + node = d.asks.Next(node) + if node == nil { + break + } + item := node.GetValue().(DobItem) + result.Asks = append(result.Asks, Item{ + Price: item.Price, + Amount: item.Amount, + }) + count++ + } + } + + largest := d.bids.GetLargestNode() + if largest != nil { + item := largest.GetValue().(DobItem) + result.Bids = append(result.Bids, Item{ + Price: item.Price, + Amount: item.Amount, + }) + count := 1 + node := largest + for count < depth { + node = d.bids.Prev(node) + if node == nil { + break + } + item := node.GetValue().(DobItem) + result.Bids = append(result.Bids, Item{ + Price: item.Price, + Amount: item.Amount, + }) + count++ + } + } + return +} + +func NewDepthOrderBook(symbol string) *DepthOrderBook { + return &DepthOrderBook{ + symbol: symbol, + asks: skiplist.New(), + bids: skiplist.New(), + } +} diff --git a/exchanges/hbdmswap/websocket.go b/exchanges/hbdmswap/websocket.go index 30376ef..82ab085 100644 --- a/exchanges/hbdmswap/websocket.go +++ b/exchanges/hbdmswap/websocket.go @@ -12,6 +12,7 @@ import ( type SwapWebSocket struct { ws *hbdmswap.WS nws *hbdmswap.NWS + dobMap map[string]*DepthOrderBook emitter *emission.Emitter } @@ -23,7 +24,8 @@ func (s *SwapWebSocket) SubscribeTrades(market Market, callback func(trades []Tr func (s *SwapWebSocket) SubscribeLevel2Snapshots(market Market, callback func(ob *OrderBook)) error { s.emitter.On(WSEventL2Snapshot, callback) - s.ws.SubscribeDepth("depth_1", market.Symbol) + //s.ws.SubscribeDepth("depth_1", market.Symbol) + s.ws.SubscribeDepthHF("depth_1", market.Symbol, 20, "incremental") return nil } @@ -69,6 +71,22 @@ func (s *SwapWebSocket) depthCallback(depth *hbdmswap.WSDepth) { s.emitter.Emit(WSEventL2Snapshot, ob) } +func (s *SwapWebSocket) depthHFCallback(depth *hbdmswap.WSDepthHF) { + // ch: market.BTC_USD.depth.size_20.high_freq + symbol := depth.Ch + if v, ok := s.dobMap[symbol]; ok { + v.Update(depth) + ob := v.GetOrderBook(20) + s.emitter.Emit(WSEventL2Snapshot, &ob) + } else { + dob := NewDepthOrderBook(symbol) + dob.Update(depth) + s.dobMap[symbol] = dob + ob := dob.GetOrderBook(20) + s.emitter.Emit(WSEventL2Snapshot, &ob) + } +} + func (s *SwapWebSocket) tradeCallback(trade *hbdmswap.WSTrade) { var trades []Trade for _, v := range trade.Tick.Data { @@ -168,14 +186,19 @@ func (s *SwapWebSocket) positionsCallback(positions *hbdmswap.WSPositions) { func NewSwapWebSocket(params *Parameters) *SwapWebSocket { wsURL := "wss://api.hbdm.com/swap-ws" + if params.WsURL != "" { + wsURL = params.WsURL + } s := &SwapWebSocket{ + dobMap: make(map[string]*DepthOrderBook), emitter: emission.NewEmitter(), } - ws := hbdmswap.NewWS(wsURL, params.AccessKey, params.SecretKey) + ws := hbdmswap.NewWS(wsURL, params.AccessKey, params.SecretKey, params.DebugMode) if params.ProxyURL != "" { ws.SetProxy(params.ProxyURL) } ws.SetDepthCallback(s.depthCallback) + ws.SetDepthHFCallback(s.depthHFCallback) ws.SetTradeCallback(s.tradeCallback) ws.Start() s.ws = ws diff --git a/go.mod b/go.mod index 302374d..665b308 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/frankrap/bitmex-api v1.0.0 github.com/frankrap/bybit-api v1.0.1-0.20200423094337-80be56d868a1 github.com/frankrap/deribit-api v1.0.3 - github.com/frankrap/huobi-api v0.0.0-20200420011222-6e786f2797a5 + github.com/frankrap/huobi-api v1.0.0 github.com/frankrap/okex-api v0.0.0-20200422112408-a276dd807813 github.com/micro/go-micro v1.18.0 // indirect github.com/sony/sonyflake v1.0.0 diff --git a/go.sum b/go.sum index 8536232..e1177b3 100644 --- a/go.sum +++ b/go.sum @@ -117,8 +117,8 @@ github.com/frankrap/bybit-api v1.0.1-0.20200423094337-80be56d868a1 h1:wQzZBVOVBT github.com/frankrap/bybit-api v1.0.1-0.20200423094337-80be56d868a1/go.mod h1:7miMJxc0+7yEWbUk+iug6J0P4RCoE/pYpQNesCfkPKo= github.com/frankrap/deribit-api v1.0.3 h1:dr99UrQ4nT4T7B2voPKPRyQwcihBjIg8VDZrImUGF20= github.com/frankrap/deribit-api v1.0.3/go.mod h1:CxElDUkDJbDOPBJhnKFh28RM+0P7LwmFYnLnBuK1Xsw= -github.com/frankrap/huobi-api v0.0.0-20200420011222-6e786f2797a5 h1:5xpEsWvHEk7y6NBa9iNzCIgjQ1swSkYkE4Q+YGT8TjI= -github.com/frankrap/huobi-api v0.0.0-20200420011222-6e786f2797a5/go.mod h1:qVwcC8rD0J/YJblSPQqFD1TEMumT8Up9B6pUPnAws0g= +github.com/frankrap/huobi-api v1.0.0 h1:xJ/p7/gZLXAZhG+pI2VCr/nxJrkhzCJwWgJBKNVGorE= +github.com/frankrap/huobi-api v1.0.0/go.mod h1:qVwcC8rD0J/YJblSPQqFD1TEMumT8Up9B6pUPnAws0g= github.com/frankrap/okex-api v0.0.0-20200422112408-a276dd807813 h1:sntKgQSgbmkKO68ZNjqQfplstf1C2i0XTBKJFWu6LrU= github.com/frankrap/okex-api v0.0.0-20200422112408-a276dd807813/go.mod h1:RLK/99FeDBeDANLmZG/BEzqg2RjwVNk5935XjUT2hUs= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=