From ad9de19d4730d551c531ec5607ca45b90f761927 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Thu, 7 Sep 2023 11:00:16 +1000 Subject: [PATCH] orderbook: Check assignment of time values and reject if not set (#1318) * orderbook: Check assignment of time values and reject if not set. * linter: fix * buffer: additional linter winter fixter * Implement through pending exchanges * finished push * linty: minty * gomod: tidy * thrasher: nits * glorious: nits * orderbook: purge type now in favour of external call allocation * orderbook: push last param * orderbook: only 1 unlock call is needed --------- Co-authored-by: Ryan O'Hara-Reid --- engine/rpcserver_test.go | 15 +- exchanges/binance/binance_test.go | 2 +- exchanges/binance/binance_websocket.go | 1 + exchanges/binanceus/binanceus_test.go | 2 +- exchanges/binanceus/binanceus_websocket.go | 1 + exchanges/bitfinex/bitfinex_websocket.go | 4 +- exchanges/bitmex/bitmex_test.go | 8 +- exchanges/bitmex/bitmex_types.go | 11 +- exchanges/bitmex/bitmex_websocket.go | 12 +- exchanges/btse/btse_websocket.go | 11 +- exchanges/coinbasepro/coinbasepro_test.go | 5 +- exchanges/coinbasepro/coinbasepro_types.go | 3 +- .../coinbasepro/coinbasepro_websocket.go | 36 +- exchanges/coinut/coinut_websocket.go | 8 +- exchanges/gemini/gemini_websocket.go | 10 +- exchanges/hitbtc/hitbtc_types.go | 5 +- exchanges/hitbtc/hitbtc_websocket.go | 13 +- exchanges/huobi/huobi_websocket.go | 1 + exchanges/orderbook/depth.go | 75 +++- exchanges/orderbook/depth_test.go | 399 +++++++++++++----- exchanges/orderbook/linked_list.go | 21 +- exchanges/orderbook/linked_list_test.go | 155 ++++--- exchanges/orderbook/node.go | 12 +- exchanges/orderbook/node_test.go | 8 +- exchanges/orderbook/orderbook.go | 5 +- exchanges/orderbook/unsafe_test.go | 70 ++- exchanges/poloniex/poloniex_test.go | 4 +- exchanges/poloniex/poloniex_websocket.go | 49 ++- exchanges/stream/buffer/buffer.go | 15 +- exchanges/stream/buffer/buffer_test.go | 91 ++-- exchanges/zb/zb_websocket.go | 1 + 31 files changed, 726 insertions(+), 327 deletions(-) diff --git a/engine/rpcserver_test.go b/engine/rpcserver_test.go index 919d3c1ed88..b0ea29f120a 100644 --- a/engine/rpcserver_test.go +++ b/engine/rpcserver_test.go @@ -3461,7 +3461,10 @@ func TestGetOrderbookMovement(t *testing.T) { {Price: 13, Amount: 1}, {Price: 14, Amount: 1}, } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } _, err = s.GetOrderbookMovement(context.Background(), req) if err.Error() != "quote amount invalid" { @@ -3571,7 +3574,10 @@ func TestGetOrderbookAmountByNominal(t *testing.T) { {Price: 13, Amount: 1}, {Price: 14, Amount: 1}, } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } nominal, err := s.GetOrderbookAmountByNominal(context.Background(), req) if !errors.Is(err, nil) { @@ -3674,7 +3680,10 @@ func TestGetOrderbookAmountByImpact(t *testing.T) { {Price: 13, Amount: 1}, {Price: 14, Amount: 1}, } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } req.ImpactPercentage = 9.090909090909092 impact, err := s.GetOrderbookAmountByImpact(context.Background(), req) diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index f096f969a06..8240c01a208 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -2123,7 +2123,7 @@ func TestWsDepthUpdate(t *testing.T) { p := currency.NewPairWithDelimiter("BTC", "USDT", "-") if err := b.SeedLocalCacheWithBook(p, &book); err != nil { - t.Error(err) + t.Fatal(err) } if err := b.wsHandleData(update1); err != nil { diff --git a/exchanges/binance/binance_websocket.go b/exchanges/binance/binance_websocket.go index c6949b06b7f..85efe593265 100644 --- a/exchanges/binance/binance_websocket.go +++ b/exchanges/binance/binance_websocket.go @@ -494,6 +494,7 @@ func (b *Binance) SeedLocalCacheWithBook(p currency.Pair, orderbookNew *OrderBoo VerifyOrderbook: b.CanVerifyOrderbook, Bids: make(orderbook.Items, len(orderbookNew.Bids)), Asks: make(orderbook.Items, len(orderbookNew.Asks)), + LastUpdated: time.Now(), // Time not provided in REST book. } for i := range orderbookNew.Bids { newOrderBook.Bids[i] = orderbook.Item{ diff --git a/exchanges/binanceus/binanceus_test.go b/exchanges/binanceus/binanceus_test.go index 3e704a74c3f..b42a4fc239f 100644 --- a/exchanges/binanceus/binanceus_test.go +++ b/exchanges/binanceus/binanceus_test.go @@ -1450,7 +1450,7 @@ func TestWebsocketOrderBookDepthDiffStream(t *testing.T) { p := currency.NewPairWithDelimiter("BTC", "USDT", "-") if err := bi.SeedLocalCacheWithBook(p, &book); err != nil { - t.Error(err) + t.Fatal(err) } if err := bi.wsHandleData(update1); err != nil { t.Error(err) diff --git a/exchanges/binanceus/binanceus_websocket.go b/exchanges/binanceus/binanceus_websocket.go index bfd75426f4a..ae627c19e80 100644 --- a/exchanges/binanceus/binanceus_websocket.go +++ b/exchanges/binanceus/binanceus_websocket.go @@ -845,6 +845,7 @@ func (bi *Binanceus) SeedLocalCacheWithBook(p currency.Pair, orderbookNew *Order VerifyOrderbook: bi.CanVerifyOrderbook, Bids: make(orderbook.Items, len(orderbookNew.Bids)), Asks: make(orderbook.Items, len(orderbookNew.Asks)), + LastUpdated: time.Now(), // Time not provided in REST book. } for i := range orderbookNew.Bids { newOrderBook.Bids[i] = orderbook.Item{ diff --git a/exchanges/bitfinex/bitfinex_websocket.go b/exchanges/bitfinex/bitfinex_websocket.go index 611f89cf815..f06b35688f3 100644 --- a/exchanges/bitfinex/bitfinex_websocket.go +++ b/exchanges/bitfinex/bitfinex_websocket.go @@ -1439,7 +1439,7 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType asset.Item, books book.PriceDuplication = true book.IsFundingRate = fundingRate book.VerifyOrderbook = b.CanVerifyOrderbook - book.LastUpdated = time.Now() + book.LastUpdated = time.Now() // Not included in snapshot return b.Websocket.Orderbook.LoadSnapshot(&book) } @@ -1451,7 +1451,7 @@ func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book Pair: p, Bids: make([]orderbook.Item, 0, len(book)), Asks: make([]orderbook.Item, 0, len(book)), - UpdateTime: time.Now(), + UpdateTime: time.Now(), // Not included in update } for i := range book { diff --git a/exchanges/bitmex/bitmex_test.go b/exchanges/bitmex/bitmex_test.go index 27fd6b42496..f9915d520b1 100644 --- a/exchanges/bitmex/bitmex_test.go +++ b/exchanges/bitmex/bitmex_test.go @@ -941,7 +941,7 @@ func TestWSOrderbookHandling(t *testing.T) { "attributes":{"id":"sorted","symbol":"grouped"}, "action":"partial", "data":[ - {"symbol":"ETHUSD","id":17999992000,"side":"Sell","size":100,"price":80}, + {"symbol":"ETHUSD","id":17999992000,"side":"Sell","size":100,"price":80,"timestamp":"2017-04-04T22:16:38.461Z"}, {"symbol":"ETHUSD","id":17999993000,"side":"Sell","size":20,"price":70}, {"symbol":"ETHUSD","id":17999994000,"side":"Sell","size":10,"price":60}, {"symbol":"ETHUSD","id":17999995000,"side":"Buy","size":10,"price":50}, @@ -958,7 +958,7 @@ func TestWSOrderbookHandling(t *testing.T) { "table":"orderBookL2_25", "action":"update", "data":[ - {"symbol":"ETHUSD","id":17999995000,"side":"Buy","size":5} + {"symbol":"ETHUSD","id":17999995000,"side":"Buy","size":5,"timestamp":"2017-04-04T22:16:38.461Z"} ] }`) err = b.wsHandleData(pressXToJSON) @@ -981,7 +981,7 @@ func TestWSOrderbookHandling(t *testing.T) { "table":"orderBookL2_25", "action":"delete", "data":[ - {"symbol":"ETHUSD","id":17999995000,"side":"Buy"} + {"symbol":"ETHUSD","id":17999995000,"side":"Buy","timestamp":"2017-04-04T22:16:38.461Z"} ] }`) err = b.wsHandleData(pressXToJSON) @@ -993,7 +993,7 @@ func TestWSOrderbookHandling(t *testing.T) { "table":"orderBookL2_25", "action":"delete", "data":[ - {"symbol":"ETHUSD","id":17999995000,"side":"Buy"} + {"symbol":"ETHUSD","id":17999995000,"side":"Buy","timestamp":"2017-04-04T22:16:38.461Z"} ] }`) err = b.wsHandleData(pressXToJSON) diff --git a/exchanges/bitmex/bitmex_types.go b/exchanges/bitmex/bitmex_types.go index 91dd62a6c9b..96ba9724c9d 100644 --- a/exchanges/bitmex/bitmex_types.go +++ b/exchanges/bitmex/bitmex_types.go @@ -322,11 +322,12 @@ type Order struct { // OrderBookL2 contains order book l2 type OrderBookL2 struct { - ID int64 `json:"id"` - Price float64 `json:"price"` - Side string `json:"side"` - Size int64 `json:"size"` - Symbol string `json:"symbol"` + ID int64 `json:"id"` + Price float64 `json:"price"` + Side string `json:"side"` + Size int64 `json:"size"` + Symbol string `json:"symbol"` + Timestamp time.Time `json:"timestamp"` } // Position Summary of Open and Closed Positions diff --git a/exchanges/bitmex/bitmex_websocket.go b/exchanges/bitmex/bitmex_websocket.go index d7e70190f12..b245c8aca3f 100644 --- a/exchanges/bitmex/bitmex_websocket.go +++ b/exchanges/bitmex/bitmex_websocket.go @@ -500,6 +500,7 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, p currency. book.Pair = p book.Exchange = b.Name book.VerifyOrderbook = b.CanVerifyOrderbook + book.LastUpdated = data[0].Timestamp err := b.Websocket.Orderbook.LoadSnapshot(&book) if err != nil { @@ -528,11 +529,12 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, p currency. } err = b.Websocket.Orderbook.Update(&orderbook.Update{ - Bids: bids, - Asks: asks, - Pair: p, - Asset: a, - Action: updateAction, + Bids: bids, + Asks: asks, + Pair: p, + Asset: a, + Action: updateAction, + UpdateTime: data[0].Timestamp, }) if err != nil { return err diff --git a/exchanges/btse/btse_websocket.go b/exchanges/btse/btse_websocket.go index 1e2f86fbf2b..8a1b0daed86 100644 --- a/exchanges/btse/btse_websocket.go +++ b/exchanges/btse/btse_websocket.go @@ -143,7 +143,9 @@ func (b *BTSE) wsHandleData(respRaw []byte) error { if err != nil { return err } - log.Infof(log.WebsocketMgr, "%v subscribed to %v", b.Name, strings.Join(subscribe.Channel, ", ")) + if b.Verbose { + log.Infof(log.WebsocketMgr, "%v subscribed to %v", b.Name, strings.Join(subscribe.Channel, ", ")) + } case "login": var login WsLoginAcknowledgement err = json.Unmarshal(respRaw, &login) @@ -151,7 +153,9 @@ func (b *BTSE) wsHandleData(respRaw []byte) error { return err } b.Websocket.SetCanUseAuthenticatedEndpoints(login.Success) - log.Infof(log.WebsocketMgr, "%v websocket authenticated: %v", b.Name, login.Success) + if b.Verbose { + log.Infof(log.WebsocketMgr, "%v websocket authenticated: %v", b.Name, login.Success) + } default: return errors.New(b.Name + stream.UnhandledMessage + string(respRaw)) } @@ -265,7 +269,7 @@ func (b *BTSE) wsHandleData(respRaw []byte) error { }) } return trade.AddTradesToBuffer(b.Name, trades...) - case strings.Contains(topic, "orderBookL2Api"): + case strings.Contains(topic, "orderBookL2Api"): // TODO: Fix orderbook updates. var t wsOrderBook err = json.Unmarshal(respRaw, &t) if err != nil { @@ -328,6 +332,7 @@ func (b *BTSE) wsHandleData(respRaw []byte) error { newOB.Exchange = b.Name newOB.Asks.Reverse() // Reverse asks for correct alignment newOB.VerifyOrderbook = b.CanVerifyOrderbook + newOB.LastUpdated = time.Now() // NOTE: Temp to fix test. err = b.Websocket.Orderbook.LoadSnapshot(&newOB) if err != nil { return err diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 0120eceabe2..af5c2ab37ef 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -852,7 +852,8 @@ func TestWsOrderbook(t *testing.T) { "type": "snapshot", "product_id": "BTC-USD", "bids": [["10101.10", "0.45054140"]], - "asks": [["10102.55", "0.57753524"]] + "asks": [["10102.55", "0.57753524"]], + "time":"2023-08-15T06:46:55.376250Z" }`) err := c.wsHandleData(pressXToJSON) if err != nil { @@ -862,7 +863,7 @@ func TestWsOrderbook(t *testing.T) { pressXToJSON = []byte(`{ "type": "l2update", "product_id": "BTC-USD", - "time": "2019-08-14T20:42:27.265Z", + "time": "2023-08-15T06:46:57.933713Z", "changes": [ [ "buy", diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 451ea5b0e53..2a8765edba7 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -441,13 +441,14 @@ type WebsocketOrderbookSnapshot struct { Type string `json:"type"` Bids [][2]string `json:"bids"` Asks [][2]string `json:"asks"` + Time time.Time `json:"time"` } // WebsocketL2Update defines an update on the L2 orderbooks type WebsocketL2Update struct { Type string `json:"type"` ProductID string `json:"product_id"` - Time string `json:"time"` + Time time.Time `json:"time"` Changes [][3]string `json:"changes"` } diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 586eeba482b..286af6f18b9 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -102,7 +102,7 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte) error { } case "snapshot": - snapshot := WebsocketOrderbookSnapshot{} + var snapshot WebsocketOrderbookSnapshot err := json.Unmarshal(respRaw, &snapshot) if err != nil { return err @@ -112,9 +112,8 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte) error { if err != nil { return err } - case "l2update": - update := WebsocketL2Update{} + var update WebsocketL2Update err := json.Unmarshal(respRaw, &update) if err != nil { return err @@ -291,30 +290,30 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro } for i := range snapshot.Bids { - price, err := strconv.ParseFloat(snapshot.Bids[i][0], 64) + var price float64 + price, err = strconv.ParseFloat(snapshot.Bids[i][0], 64) if err != nil { return err } - - amount, err := strconv.ParseFloat(snapshot.Bids[i][1], 64) + var amount float64 + amount, err = strconv.ParseFloat(snapshot.Bids[i][1], 64) if err != nil { return err } - base.Bids[i] = orderbook.Item{Price: price, Amount: amount} } for i := range snapshot.Asks { - price, err := strconv.ParseFloat(snapshot.Asks[i][0], 64) + var price float64 + price, err = strconv.ParseFloat(snapshot.Asks[i][0], 64) if err != nil { return err } - - amount, err := strconv.ParseFloat(snapshot.Asks[i][1], 64) + var amount float64 + amount, err = strconv.ParseFloat(snapshot.Asks[i][1], 64) if err != nil { return err } - base.Asks[i] = orderbook.Item{Price: price, Amount: amount} } @@ -322,7 +321,7 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro base.Pair = pair base.Exchange = c.Name base.VerifyOrderbook = c.CanVerifyOrderbook - + base.LastUpdated = snapshot.Time return c.Websocket.Orderbook.LoadSnapshot(&base) } @@ -337,11 +336,6 @@ func (c *CoinbasePro) ProcessUpdate(update *WebsocketL2Update) error { return err } - timestamp, err := time.Parse(time.RFC3339, update.Time) - if err != nil { - return err - } - asks := make(orderbook.Items, 0, len(update.Changes)) bids := make(orderbook.Items, 0, len(update.Changes)) @@ -365,14 +359,18 @@ func (c *CoinbasePro) ProcessUpdate(update *WebsocketL2Update) error { Bids: bids, Asks: asks, Pair: p, - UpdateTime: timestamp, + UpdateTime: update.Time, Asset: asset.Spot, }) } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (c *CoinbasePro) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { - var channels = []string{"heartbeat", "level2", "ticker", "user", "matches"} + var channels = []string{"heartbeat", + "level2_batch", /*Other orderbook feeds require authentication. This is batched in 50ms lots.*/ + "ticker", + "user", + "matches"} enabledCurrencies, err := c.GetEnabledPairs(asset.Spot) if err != nil { return nil, err diff --git a/exchanges/coinut/coinut_websocket.go b/exchanges/coinut/coinut_websocket.go index 64df18a076f..2a373c9e79c 100644 --- a/exchanges/coinut/coinut_websocket.go +++ b/exchanges/coinut/coinut_websocket.go @@ -557,6 +557,7 @@ func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error { newOrderBook.Asset = asset.Spot newOrderBook.Exchange = c.Name + newOrderBook.LastUpdated = time.Now() // No time sent return c.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } @@ -582,9 +583,10 @@ func (c *COINUT) WsProcessOrderbookUpdate(update *WsOrderbookUpdate) error { } bufferUpdate := &orderbook.Update{ - Pair: p, - UpdateID: update.TransID, - Asset: asset.Spot, + Pair: p, + UpdateID: update.TransID, + Asset: asset.Spot, + UpdateTime: time.Now(), // No time sent } if strings.EqualFold(update.Side, order.Buy.Lower()) { bufferUpdate.Bids = []orderbook.Item{{Price: update.Price, Amount: update.Volume}} diff --git a/exchanges/gemini/gemini_websocket.go b/exchanges/gemini/gemini_websocket.go index 77c3ca5c802..1ea3f5cdbaf 100644 --- a/exchanges/gemini/gemini_websocket.go +++ b/exchanges/gemini/gemini_websocket.go @@ -560,6 +560,7 @@ func (g *Gemini) wsProcessUpdate(result *wsL2MarketData) error { newOrderBook.Pair = pair newOrderBook.Exchange = g.Name newOrderBook.VerifyOrderbook = g.CanVerifyOrderbook + newOrderBook.LastUpdated = time.Now() // No time is sent err := g.Websocket.Orderbook.LoadSnapshot(&newOrderBook) if err != nil { return err @@ -569,10 +570,11 @@ func (g *Gemini) wsProcessUpdate(result *wsL2MarketData) error { return nil } err := g.Websocket.Orderbook.Update(&orderbook.Update{ - Asks: asks, - Bids: bids, - Pair: pair, - Asset: asset.Spot, + Asks: asks, + Bids: bids, + Pair: pair, + Asset: asset.Spot, + UpdateTime: time.Now(), // No time is sent }) if err != nil { return err diff --git a/exchanges/hitbtc/hitbtc_types.go b/exchanges/hitbtc/hitbtc_types.go index 74c32430e67..4f55f206822 100644 --- a/exchanges/hitbtc/hitbtc_types.go +++ b/exchanges/hitbtc/hitbtc_types.go @@ -343,8 +343,9 @@ type WsOrderbook struct { Price float64 `json:"price,string"` Size float64 `json:"size,string"` } `json:"bid"` - Symbol string `json:"symbol"` - Sequence int64 `json:"sequence"` + Symbol string `json:"symbol"` + Sequence int64 `json:"sequence"` + Timestamp time.Time `json:"timestamp"` } `json:"params"` } diff --git a/exchanges/hitbtc/hitbtc_websocket.go b/exchanges/hitbtc/hitbtc_websocket.go index ef03f405968..accc7cf13be 100644 --- a/exchanges/hitbtc/hitbtc_websocket.go +++ b/exchanges/hitbtc/hitbtc_websocket.go @@ -332,10 +332,12 @@ func (h *HitBTC) WsProcessOrderbookSnapshot(ob *WsOrderbook) error { h.Websocket.DataHandler <- err return err } + newOrderBook.Asset = asset.Spot newOrderBook.Pair = p newOrderBook.Exchange = h.Name newOrderBook.VerifyOrderbook = h.CanVerifyOrderbook + newOrderBook.LastUpdated = ob.Params.Timestamp return h.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } @@ -453,11 +455,12 @@ func (h *HitBTC) WsProcessOrderbookUpdate(update *WsOrderbook) error { } return h.Websocket.Orderbook.Update(&orderbook.Update{ - Asks: asks, - Bids: bids, - Pair: p, - UpdateID: update.Params.Sequence, - Asset: asset.Spot, + Asks: asks, + Bids: bids, + Pair: p, + UpdateID: update.Params.Sequence, + Asset: asset.Spot, + UpdateTime: update.Params.Timestamp, }) } diff --git a/exchanges/huobi/huobi_websocket.go b/exchanges/huobi/huobi_websocket.go index 095c553fe4c..3f9fcb7f49e 100644 --- a/exchanges/huobi/huobi_websocket.go +++ b/exchanges/huobi/huobi_websocket.go @@ -508,6 +508,7 @@ func (h *HUOBI) WsProcessOrderbook(update *WsDepth, symbol string) error { newOrderBook.Asset = asset.Spot newOrderBook.Exchange = h.Name newOrderBook.VerifyOrderbook = h.CanVerifyOrderbook + newOrderBook.LastUpdated = time.UnixMilli(update.Timestamp) return h.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } diff --git a/exchanges/orderbook/depth.go b/exchanges/orderbook/depth.go index b4aa6fc8cc8..1b21352e751 100644 --- a/exchanges/orderbook/depth.go +++ b/exchanges/orderbook/depth.go @@ -7,6 +7,7 @@ import ( "time" "github.com/gofrs/uuid" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/dispatch" "github.com/thrasher-corp/gocryptotrader/exchanges/alert" @@ -20,7 +21,8 @@ var ( // ErrInvalidAction defines and error when an action is invalid ErrInvalidAction = errors.New("invalid action") - errInvalidBookDepth = errors.New("invalid book depth") + errLastUpdatedNotSet = errors.New("last updated not set") + errInvalidBookDepth = errors.New("invalid book depth") ) // Outbound restricts outbound usage of depth. NOTE: Type assert to @@ -91,16 +93,24 @@ func (d *Depth) Retrieve() (*Base, error) { } // LoadSnapshot flushes the bids and asks with a snapshot -func (d *Depth) LoadSnapshot(bids, asks []Item, lastUpdateID int64, lastUpdated time.Time, updateByREST bool) { +func (d *Depth) LoadSnapshot(bids, asks []Item, lastUpdateID int64, lastUpdated time.Time, updateByREST bool) error { + if lastUpdated.IsZero() { + return fmt.Errorf("%s %s %s %w", + d.exchange, + d.pair, + d.asset, + errLastUpdatedNotSet) + } d.m.Lock() d.lastUpdateID = lastUpdateID d.lastUpdated = lastUpdated d.restSnapshot = updateByREST - d.bids.load(bids, d.stack) - d.asks.load(asks, d.stack) + d.bids.load(bids, d.stack, lastUpdated) + d.asks.load(asks, d.stack, lastUpdated) d.validationError = nil d.Alert() d.m.Unlock() + return nil } // invalidate flushes all values back to zero so as to not allow strategy @@ -108,14 +118,14 @@ func (d *Depth) LoadSnapshot(bids, asks []Item, lastUpdateID int64, lastUpdated func (d *Depth) invalidate(withReason error) error { d.lastUpdateID = 0 d.lastUpdated = time.Time{} - d.bids.load(nil, d.stack) - d.asks.load(nil, d.stack) - d.validationError = fmt.Errorf("%s %s %s %w Reason: [%v]", + tn := time.Now() + d.bids.load(nil, d.stack, tn) + d.asks.load(nil, d.stack, tn) + d.validationError = fmt.Errorf("%s %s %s Reason: [%w]", d.exchange, d.pair, d.asset, - ErrOrderbookInvalid, - withReason) + common.AppendError(ErrOrderbookInvalid, withReason)) d.Alert() return d.validationError } @@ -138,21 +148,35 @@ func (d *Depth) IsValid() bool { // UpdateBidAskByPrice updates the bid and ask spread by supplied updates, this // will trim total length of depth level to a specified supplied number -func (d *Depth) UpdateBidAskByPrice(update *Update) { - tn := getNow() +func (d *Depth) UpdateBidAskByPrice(update *Update) error { + if update.UpdateTime.IsZero() { + return fmt.Errorf("%s %s %s %w", + d.exchange, + d.pair, + d.asset, + errLastUpdatedNotSet) + } d.m.Lock() if len(update.Bids) != 0 { - d.bids.updateInsertByPrice(update.Bids, d.stack, d.options.maxDepth, tn) + d.bids.updateInsertByPrice(update.Bids, d.stack, d.options.maxDepth, update.UpdateTime) } if len(update.Asks) != 0 { - d.asks.updateInsertByPrice(update.Asks, d.stack, d.options.maxDepth, tn) + d.asks.updateInsertByPrice(update.Asks, d.stack, d.options.maxDepth, update.UpdateTime) } d.updateAndAlert(update) d.m.Unlock() + return nil } // UpdateBidAskByID amends details by ID func (d *Depth) UpdateBidAskByID(update *Update) error { + if update.UpdateTime.IsZero() { + return fmt.Errorf("%s %s %s %w", + d.exchange, + d.pair, + d.asset, + errLastUpdatedNotSet) + } d.m.Lock() defer d.m.Unlock() if len(update.Bids) != 0 { @@ -173,16 +197,23 @@ func (d *Depth) UpdateBidAskByID(update *Update) error { // DeleteBidAskByID deletes a price level by ID func (d *Depth) DeleteBidAskByID(update *Update, bypassErr bool) error { + if update.UpdateTime.IsZero() { + return fmt.Errorf("%s %s %s %w", + d.exchange, + d.pair, + d.asset, + errLastUpdatedNotSet) + } d.m.Lock() defer d.m.Unlock() if len(update.Bids) != 0 { - err := d.bids.deleteByID(update.Bids, d.stack, bypassErr) + err := d.bids.deleteByID(update.Bids, d.stack, bypassErr, update.UpdateTime) if err != nil { return d.invalidate(err) } } if len(update.Asks) != 0 { - err := d.asks.deleteByID(update.Asks, d.stack, bypassErr) + err := d.asks.deleteByID(update.Asks, d.stack, bypassErr, update.UpdateTime) if err != nil { return d.invalidate(err) } @@ -193,6 +224,13 @@ func (d *Depth) DeleteBidAskByID(update *Update, bypassErr bool) error { // InsertBidAskByID inserts new updates func (d *Depth) InsertBidAskByID(update *Update) error { + if update.UpdateTime.IsZero() { + return fmt.Errorf("%s %s %s %w", + d.exchange, + d.pair, + d.asset, + errLastUpdatedNotSet) + } d.m.Lock() defer d.m.Unlock() if len(update.Bids) != 0 { @@ -213,6 +251,13 @@ func (d *Depth) InsertBidAskByID(update *Update) error { // UpdateInsertByID updates or inserts by ID at current price level. func (d *Depth) UpdateInsertByID(update *Update) error { + if update.UpdateTime.IsZero() { + return fmt.Errorf("%s %s %s %w", + d.exchange, + d.pair, + d.asset, + errLastUpdatedNotSet) + } d.m.Lock() defer d.m.Unlock() if len(update.Bids) != 0 { diff --git a/exchanges/orderbook/depth_test.go b/exchanges/orderbook/depth_test.go index e2c0bec36d0..d3ac0c324d6 100644 --- a/exchanges/orderbook/depth_test.go +++ b/exchanges/orderbook/depth_test.go @@ -3,7 +3,6 @@ package orderbook import ( "errors" "reflect" - "strings" "testing" "time" @@ -26,7 +25,10 @@ func TestGetLength(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid) } - d.LoadSnapshot([]Item{{Price: 1337}}, nil, 0, time.Time{}, true) + err = d.LoadSnapshot([]Item{{Price: 1337}}, nil, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } askLen, err := d.GetAskLength() if !errors.Is(err, nil) { @@ -37,7 +39,7 @@ func TestGetLength(t *testing.T) { t.Errorf("expected len %v, but received %v", 0, askLen) } - d.asks.load([]Item{{Price: 1337}}, d.stack) + d.asks.load([]Item{{Price: 1337}}, d.stack, time.Now()) askLen, err = d.GetAskLength() if !errors.Is(err, nil) { @@ -58,7 +60,10 @@ func TestGetLength(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid) } - d.LoadSnapshot(nil, []Item{{Price: 1337}}, 0, time.Time{}, true) + err = d.LoadSnapshot(nil, []Item{{Price: 1337}}, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } bidLen, err := d.GetBidLength() if !errors.Is(err, nil) { @@ -69,7 +74,7 @@ func TestGetLength(t *testing.T) { t.Errorf("expected len %v, but received %v", 0, bidLen) } - d.bids.load([]Item{{Price: 1337}}, d.stack) + d.bids.load([]Item{{Price: 1337}}, d.stack, time.Now()) bidLen, err = d.GetBidLength() if !errors.Is(err, nil) { @@ -84,8 +89,8 @@ func TestGetLength(t *testing.T) { func TestRetrieve(t *testing.T) { t.Parallel() d := NewDepth(id) - d.asks.load([]Item{{Price: 1337}}, d.stack) - d.bids.load([]Item{{Price: 1337}}, d.stack) + d.asks.load([]Item{{Price: 1337}}, d.stack, time.Now()) + d.bids.load([]Item{{Price: 1337}}, d.stack, time.Now()) d.options = options{ exchange: "THE BIG ONE!!!!!!", pair: currency.NewPair(currency.THETA, currency.USD), @@ -181,8 +186,8 @@ func TestTotalAmounts(t *testing.T) { value) } - d.asks.load([]Item{{Price: 1337, Amount: 1}}, d.stack) - d.bids.load([]Item{{Price: 1337, Amount: 10}}, d.stack) + d.asks.load([]Item{{Price: 1337, Amount: 1}}, d.stack, time.Now()) + d.bids.load([]Item{{Price: 1337, Amount: 10}}, d.stack, time.Now()) liquidity, value, err = d.TotalBidAmounts() if !errors.Is(err, nil) { @@ -214,7 +219,15 @@ func TestTotalAmounts(t *testing.T) { func TestLoadSnapshot(t *testing.T) { t.Parallel() d := NewDepth(id) - d.LoadSnapshot(Items{{Price: 1337, Amount: 1}}, Items{{Price: 1337, Amount: 10}}, 0, time.Time{}, false) + err := d.LoadSnapshot(Items{{Price: 1337, Amount: 1}}, Items{{Price: 1337, Amount: 10}}, 0, time.Time{}, false) + if !errors.Is(err, errLastUpdatedNotSet) { + t.Fatalf("received: '%v' but expected: '%v'", err, errLastUpdatedNotSet) + } + + err = d.LoadSnapshot(Items{{Price: 1337, Amount: 1}}, Items{{Price: 1337, Amount: 10}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } ob, err := d.Retrieve() if !errors.Is(err, nil) { @@ -232,7 +245,10 @@ func TestInvalidate(t *testing.T) { d.exchange = "testexchange" d.pair = currency.NewPair(currency.BTC, currency.WABI) d.asset = asset.Spot - d.LoadSnapshot(Items{{Price: 1337, Amount: 1}}, Items{{Price: 1337, Amount: 10}}, 0, time.Time{}, false) + err := d.LoadSnapshot(Items{{Price: 1337, Amount: 1}}, Items{{Price: 1337, Amount: 10}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } ob, err := d.Retrieve() if !errors.Is(err, nil) { @@ -243,18 +259,16 @@ func TestInvalidate(t *testing.T) { t.Fatalf("unexpected value") } - err = d.Invalidate(errors.New("random reason")) - if !errors.Is(err, ErrOrderbookInvalid) { - t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid) - } + testReason := errors.New("random reason") - _, err = d.Retrieve() + err = d.Invalidate(testReason) if !errors.Is(err, ErrOrderbookInvalid) { t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid) } - if err.Error() != "testexchange BTCWABI spot orderbook data integrity compromised Reason: [random reason]" { - t.Fatal("unexpected string return") + _, err = d.Retrieve() + if !errors.Is(err, ErrOrderbookInvalid) && !errors.Is(err, testReason) { + t.Fatalf("received: '%v' but expected: '%v' && '%v'", err, ErrOrderbookInvalid, testReason) } d.validationError = nil @@ -272,17 +286,31 @@ func TestInvalidate(t *testing.T) { func TestUpdateBidAskByPrice(t *testing.T) { t.Parallel() d := NewDepth(id) - d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false) + err := d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } + err = d.UpdateBidAskByPrice(&Update{}) + if !errors.Is(err, errLastUpdatedNotSet) { + t.Fatalf("received: '%v' but expected: '%v'", err, errLastUpdatedNotSet) + } // empty - d.UpdateBidAskByPrice(&Update{}) + err = d.UpdateBidAskByPrice(&Update{UpdateTime: time.Now()}) + if err != nil { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } updates := &Update{ - Bids: Items{{Price: 1337, Amount: 2, ID: 1}}, - Asks: Items{{Price: 1337, Amount: 2, ID: 2}}, - UpdateID: 1, + Bids: Items{{Price: 1337, Amount: 2, ID: 1}}, + Asks: Items{{Price: 1337, Amount: 2, ID: 2}}, + UpdateID: 1, + UpdateTime: time.Now(), + } + err = d.UpdateBidAskByPrice(updates) + if err != nil { + t.Fatal(err) } - d.UpdateBidAskByPrice(updates) ob, err := d.Retrieve() if !errors.Is(err, nil) { @@ -294,11 +322,15 @@ func TestUpdateBidAskByPrice(t *testing.T) { } updates = &Update{ - Bids: Items{{Price: 1337, Amount: 0, ID: 1}}, - Asks: Items{{Price: 1337, Amount: 0, ID: 2}}, - UpdateID: 2, + Bids: Items{{Price: 1337, Amount: 0, ID: 1}}, + Asks: Items{{Price: 1337, Amount: 0, ID: 2}}, + UpdateID: 2, + UpdateTime: time.Now(), + } + err = d.UpdateBidAskByPrice(updates) + if err != nil { + t.Fatal(err) } - d.UpdateBidAskByPrice(updates) askLen, err := d.GetAskLength() if !errors.Is(err, nil) { @@ -318,13 +350,23 @@ func TestUpdateBidAskByPrice(t *testing.T) { func TestDeleteBidAskByID(t *testing.T) { t.Parallel() d := NewDepth(id) - d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false) + err := d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } updates := &Update{ Bids: Items{{Price: 1337, Amount: 2, ID: 1}}, Asks: Items{{Price: 1337, Amount: 2, ID: 2}}, } - err := d.DeleteBidAskByID(updates, false) + + err = d.DeleteBidAskByID(updates, false) + if !errors.Is(err, errLastUpdatedNotSet) { + t.Fatalf("received: '%v' but expected: '%v'", err, errLastUpdatedNotSet) + } + + updates.UpdateTime = time.Now() + err = d.DeleteBidAskByID(updates, false) if err != nil { t.Fatal(err) } @@ -339,23 +381,26 @@ func TestDeleteBidAskByID(t *testing.T) { } updates = &Update{ - Bids: Items{{Price: 1337, Amount: 2, ID: 1}}, + Bids: Items{{Price: 1337, Amount: 2, ID: 1}}, + UpdateTime: time.Now(), } err = d.DeleteBidAskByID(updates, false) - if !strings.Contains(err.Error(), errIDCannotBeMatched.Error()) { + if !errors.Is(err, errIDCannotBeMatched) { t.Fatalf("error expected %v received %v", errIDCannotBeMatched, err) } updates = &Update{ - Asks: Items{{Price: 1337, Amount: 2, ID: 2}}, + Asks: Items{{Price: 1337, Amount: 2, ID: 2}}, + UpdateTime: time.Now(), } err = d.DeleteBidAskByID(updates, false) - if !strings.Contains(err.Error(), errIDCannotBeMatched.Error()) { + if !errors.Is(err, errIDCannotBeMatched) { t.Fatalf("error expected %v received %v", errIDCannotBeMatched, err) } updates = &Update{ - Asks: Items{{Price: 1337, Amount: 2, ID: 2}}, + Asks: Items{{Price: 1337, Amount: 2, ID: 2}}, + UpdateTime: time.Now(), } err = d.DeleteBidAskByID(updates, true) if !errors.Is(err, nil) { @@ -366,13 +411,23 @@ func TestDeleteBidAskByID(t *testing.T) { func TestUpdateBidAskByID(t *testing.T) { t.Parallel() d := NewDepth(id) - d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false) + err := d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } updates := &Update{ Bids: Items{{Price: 1337, Amount: 2, ID: 1}}, Asks: Items{{Price: 1337, Amount: 2, ID: 2}}, } - err := d.UpdateBidAskByID(updates) + + err = d.UpdateBidAskByID(updates) + if !errors.Is(err, errLastUpdatedNotSet) { + t.Fatalf("received: '%v' but expected: '%v'", err, errLastUpdatedNotSet) + } + + updates.UpdateTime = time.Now() + err = d.UpdateBidAskByID(updates) if err != nil { t.Fatal(err) } @@ -387,19 +442,21 @@ func TestUpdateBidAskByID(t *testing.T) { } updates = &Update{ - Bids: Items{{Price: 1337, Amount: 2, ID: 666}}, + Bids: Items{{Price: 1337, Amount: 2, ID: 666}}, + UpdateTime: time.Now(), } // random unmatching IDs err = d.UpdateBidAskByID(updates) - if !strings.Contains(err.Error(), errIDCannotBeMatched.Error()) { + if !errors.Is(err, errIDCannotBeMatched) { t.Fatalf("error expected %v received %v", errIDCannotBeMatched, err) } updates = &Update{ - Asks: Items{{Price: 1337, Amount: 2, ID: 69}}, + Asks: Items{{Price: 1337, Amount: 2, ID: 69}}, + UpdateTime: time.Now(), } err = d.UpdateBidAskByID(updates) - if !strings.Contains(err.Error(), errIDCannotBeMatched.Error()) { + if !errors.Is(err, errIDCannotBeMatched) { t.Fatalf("error expected %v received %v", errIDCannotBeMatched, err) } } @@ -407,32 +464,50 @@ func TestUpdateBidAskByID(t *testing.T) { func TestInsertBidAskByID(t *testing.T) { t.Parallel() d := NewDepth(id) - d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false) + err := d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } updates := &Update{ Asks: Items{{Price: 1337, Amount: 2, ID: 3}}, } + err = d.InsertBidAskByID(updates) + if !errors.Is(err, errLastUpdatedNotSet) { + t.Fatalf("received: '%v' but expected: '%v'", err, errLastUpdatedNotSet) + } + + updates.UpdateTime = time.Now() - err := d.InsertBidAskByID(updates) - if !strings.Contains(err.Error(), errCollisionDetected.Error()) { + err = d.InsertBidAskByID(updates) + if !errors.Is(err, errCollisionDetected) { t.Fatalf("received: '%v' but expected: '%v'", err, errCollisionDetected) } - d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false) + err = d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } updates = &Update{ - Bids: Items{{Price: 1337, Amount: 2, ID: 3}}, + Bids: Items{{Price: 1337, Amount: 2, ID: 3}}, + UpdateTime: time.Now(), } err = d.InsertBidAskByID(updates) - if !strings.Contains(err.Error(), errCollisionDetected.Error()) { + if !errors.Is(err, errCollisionDetected) { t.Fatalf("received: '%v' but expected: '%v'", err, errCollisionDetected) } - d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false) + err = d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } + updates = &Update{ - Bids: Items{{Price: 1338, Amount: 2, ID: 3}}, - Asks: Items{{Price: 1336, Amount: 2, ID: 4}}, + Bids: Items{{Price: 1338, Amount: 2, ID: 3}}, + Asks: Items{{Price: 1336, Amount: 2, ID: 4}}, + UpdateTime: time.Now(), } err = d.InsertBidAskByID(updates) if err != nil { @@ -452,14 +527,23 @@ func TestInsertBidAskByID(t *testing.T) { func TestUpdateInsertByID(t *testing.T) { t.Parallel() d := NewDepth(id) - d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false) + err := d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } updates := &Update{ Bids: Items{{Price: 1338, Amount: 0, ID: 3}}, Asks: Items{{Price: 1336, Amount: 2, ID: 4}}, } - err := d.UpdateInsertByID(updates) - if !strings.Contains(err.Error(), errAmountCannotBeLessOrEqualToZero.Error()) { + err = d.UpdateInsertByID(updates) + if !errors.Is(err, errLastUpdatedNotSet) { + t.Fatalf("expected: %v but received: %v", errLastUpdatedNotSet, err) + } + + updates.UpdateTime = time.Now() + err = d.UpdateInsertByID(updates) + if !errors.Is(err, errAmountCannotBeLessOrEqualToZero) { t.Fatalf("expected: %v but received: %v", errAmountCannotBeLessOrEqualToZero, err) } @@ -469,14 +553,18 @@ func TestUpdateInsertByID(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid) } - d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false) + err = d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } updates = &Update{ - Bids: Items{{Price: 1338, Amount: 2, ID: 3}}, - Asks: Items{{Price: 1336, Amount: 0, ID: 4}}, + Bids: Items{{Price: 1338, Amount: 2, ID: 3}}, + Asks: Items{{Price: 1336, Amount: 0, ID: 4}}, + UpdateTime: time.Now(), } err = d.UpdateInsertByID(updates) - if !strings.Contains(err.Error(), errAmountCannotBeLessOrEqualToZero.Error()) { + if !errors.Is(err, errAmountCannotBeLessOrEqualToZero) { t.Fatalf("expected: %v but received: %v", errAmountCannotBeLessOrEqualToZero, err) } @@ -486,11 +574,15 @@ func TestUpdateInsertByID(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid) } - d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Time{}, false) + err = d.LoadSnapshot(Items{{Price: 1337, Amount: 1, ID: 1}}, Items{{Price: 1337, Amount: 10, ID: 2}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } updates = &Update{ - Bids: Items{{Price: 1338, Amount: 2, ID: 3}}, - Asks: Items{{Price: 1336, Amount: 2, ID: 4}}, + Bids: Items{{Price: 1338, Amount: 2, ID: 3}}, + Asks: Items{{Price: 1336, Amount: 2, ID: 4}}, + UpdateTime: time.Now(), } err = d.UpdateInsertByID(updates) if err != nil { @@ -643,7 +735,10 @@ func TestHitTheBidsByNominalSlippage(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } // First tranche amt, err := depth.HitTheBidsByNominalSlippage(0, 1336) @@ -765,7 +860,11 @@ func TestHitTheBidsByNominalSlippageFromMid(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } + // First price from mid point amt, err := depth.HitTheBidsByNominalSlippageFromMid(0.03741114852226) if !errors.Is(err, nil) { @@ -802,7 +901,11 @@ func TestHitTheBidsByNominalSlippageFromBest(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } + // First and second price from best bid amt, err := depth.HitTheBidsByNominalSlippageFromBest(0.037425149700599) if !errors.Is(err, nil) { @@ -839,7 +942,10 @@ func TestLiftTheAsksByNominalSlippage(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } // First and second price amt, err := depth.LiftTheAsksByNominalSlippage(0.037397157816006, 1337) @@ -876,7 +982,11 @@ func TestLiftTheAsksByNominalSlippageFromMid(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } + // First price from mid point amt, err := depth.LiftTheAsksByNominalSlippageFromMid(0.074822297044519) if !errors.Is(err, nil) { @@ -913,7 +1023,11 @@ func TestLiftTheAsksByNominalSlippageFromBest(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } + // First and second price from best bid amt, err := depth.LiftTheAsksByNominalSlippageFromBest(0.037397157816006) if !errors.Is(err, nil) { @@ -944,7 +1058,10 @@ func TestHitTheBidsByImpactSlippage(t *testing.T) { } depth := NewDepth(id) - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } // First and second price from best bid - price level target 1326 (which should be kept) amt, err := depth.HitTheBidsByImpactSlippage(0.7485029940119761, 1336) @@ -981,7 +1098,10 @@ func TestHitTheBidsByImpactSlippageFromMid(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } // First and second price from mid - price level target 1326 (which should be kept) amt, err := depth.HitTheBidsByImpactSlippageFromMid(0.7485029940119761) @@ -1016,7 +1136,10 @@ func TestHitTheBidsByImpactSlippageFromBest(t *testing.T) { if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } // First and second price from mid - price level target 1326 (which should be kept) amt, err := depth.HitTheBidsByImpactSlippageFromBest(0.7485029940119761) @@ -1047,7 +1170,10 @@ func TestLiftTheAsksByImpactSlippage(t *testing.T) { } depth := NewDepth(id) - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } // First and second price from best bid - price level target 1326 (which should be kept) amt, err := depth.LiftTheAsksByImpactSlippage(0.7479431563201197, 1337) @@ -1082,8 +1208,10 @@ func TestLiftTheAsksByImpactSlippageFromMid(t *testing.T) { if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) - + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } // First and second price from mid - price level target 1326 (which should be kept) amt, err := depth.LiftTheAsksByImpactSlippageFromMid(0.7485029940119761) if !errors.Is(err, nil) { @@ -1117,8 +1245,10 @@ func TestLiftTheAsksByImpactSlippageFromBest(t *testing.T) { if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) - + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } // First and second price from mid - price level target 1326 (which should be kept) amt, err := depth.LiftTheAsksByImpactSlippageFromBest(0.7479431563201197) if !errors.Is(err, nil) { @@ -1145,7 +1275,10 @@ func TestLiftTheAsksByImpactSlippageFromBest(t *testing.T) { func TestHitTheBids(t *testing.T) { t.Parallel() depth := NewDepth(id) - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err := depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } mov, err := depth.HitTheBids(20.1, 1336, false) if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -1211,7 +1344,10 @@ func TestHitTheBids_QuotationRequired(t *testing.T) { } depth := NewDepth(id) - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } mov, err := depth.HitTheBids(26531, 1336, true) if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -1281,7 +1417,10 @@ func TestHitTheBidsFromMid(t *testing.T) { if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } mov, err := depth.HitTheBidsFromMid(20.1, false) if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -1346,7 +1485,10 @@ func TestHitTheBidsFromMid_QuotationRequired(t *testing.T) { if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } mov, err := depth.HitTheBidsFromMid(26531, true) if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -1411,7 +1553,10 @@ func TestHitTheBidsFromBest(t *testing.T) { if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } mov, err := depth.HitTheBidsFromBest(20.1, false) if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -1480,7 +1625,10 @@ func TestHitTheBidsFromBest_QuotationRequired(t *testing.T) { if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } mov, err := depth.HitTheBidsFromBest(26531, true) if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -1540,7 +1688,10 @@ func TestHitTheBidsFromBest_QuotationRequired(t *testing.T) { func TestLiftTheAsks(t *testing.T) { t.Parallel() depth := NewDepth(id) - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err := depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } mov, err := depth.LiftTheAsks(26931, 1337, false) if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -1605,7 +1756,10 @@ func TestLiftTheAsks_BaseRequired(t *testing.T) { } depth := NewDepth(id) - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } mov, err := depth.LiftTheAsks(21, 1337, true) if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -1674,7 +1828,10 @@ func TestLiftTheAsksFromMid(t *testing.T) { if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } mov, err := depth.LiftTheAsksFromMid(26931, false) if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -1743,7 +1900,10 @@ func TestLiftTheAsksFromMid_BaseRequired(t *testing.T) { if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } mov, err := depth.LiftTheAsksFromMid(21, true) if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -1812,7 +1972,10 @@ func TestLiftTheAsksFromBest(t *testing.T) { if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } mov, err := depth.LiftTheAsksFromBest(26931, false) if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -1881,7 +2044,10 @@ func TestLiftTheAsksFromBest_BaseRequired(t *testing.T) { if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } mov, err := depth.LiftTheAsksFromBest(21, true) if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -1949,7 +2115,10 @@ func TestGetMidPrice_Depth(t *testing.T) { if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } mid, err := depth.GetMidPrice() if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -1967,13 +2136,19 @@ func TestGetMidPriceNoLock_Depth(t *testing.T) { if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, nil, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, nil, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } _, err = depth.getMidPriceNoLock() if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } mid, err := depth.getMidPriceNoLock() if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -2005,7 +2180,10 @@ func TestGetBestBidASk_Depth(t *testing.T) { if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } mid, err := depth.GetBestBid() if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -2036,15 +2214,19 @@ func TestGetSpreadAmount(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(nil, ask, 0, time.Time{}, true) - + err = depth.LoadSnapshot(nil, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } _, err = depth.GetSpreadAmount() if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) - + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } spread, err := depth.GetSpreadAmount() if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -2069,15 +2251,19 @@ func TestGetSpreadPercentage(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(nil, ask, 0, time.Time{}, true) - + err = depth.LoadSnapshot(nil, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } _, err = depth.GetSpreadPercentage() if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) - + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } spread, err := depth.GetSpreadPercentage() if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -2102,15 +2288,19 @@ func TestGetImbalance_Depth(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(nil, ask, 0, time.Time{}, true) - + err = depth.LoadSnapshot(nil, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } _, err = depth.GetImbalance() if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) - + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } imbalance, err := depth.GetImbalance() if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -2148,7 +2338,10 @@ func TestGetTranches(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", len(bidT), 0) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err = depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } askT, bidT, err = depth.GetTranches(0) if !errors.Is(err, nil) { diff --git a/exchanges/orderbook/linked_list.go b/exchanges/orderbook/linked_list.go index becaa40a1cd..e08c36081d0 100644 --- a/exchanges/orderbook/linked_list.go +++ b/exchanges/orderbook/linked_list.go @@ -3,6 +3,7 @@ package orderbook import ( "errors" "fmt" + "time" "github.com/thrasher-corp/gocryptotrader/common/math" ) @@ -40,7 +41,7 @@ type comparison func(float64, float64) bool // load iterates across new items and refreshes linked list. It creates a linked // list exactly the same as the item slice that is supplied, if items is of nil // value it will flush entire list. -func (ll *linkedList) load(items Items, stack *stack) { +func (ll *linkedList) load(items Items, stack *stack, tn time.Time) { // Tip sets up a pointer to a struct field variable pointer. This is used // so when a node is popped from the stack we can reference that current // nodes' struct 'next' field and set on next iteration without utilising @@ -81,7 +82,7 @@ func (ll *linkedList) load(items Items, stack *stack) { // Push unused pointers back on stack for push != nil { pending := push.Next - stack.Push(push, getNow()) + stack.Push(push, tn) ll.length-- push = pending } @@ -111,14 +112,14 @@ updates: } // deleteByID deletes reference by ID -func (ll *linkedList) deleteByID(updts Items, stack *stack, bypassErr bool) error { +func (ll *linkedList) deleteByID(updts Items, stack *stack, bypassErr bool, tn time.Time) error { updates: for x := range updts { for tip := &ll.head; *tip != nil; tip = &(*tip).Next { if updts[x].ID != (*tip).Value.ID { continue } - stack.Push(deleteAtTip(ll, tip), getNow()) + stack.Push(deleteAtTip(ll, tip), tn) continue updates } if !bypassErr { @@ -133,7 +134,7 @@ updates: // cleanup reduces the max size of the depth length if exceeded. Is used after // updates have been applied instead of adhoc, reason being its easier to prune // at the end. (can't inline) -func (ll *linkedList) cleanup(maxChainLength int, stack *stack) { +func (ll *linkedList) cleanup(maxChainLength int, stack *stack, tn time.Time) { // Reduces the max length of total linked list chain, occurs after updates // have been implemented as updates can push length out of bounds, if // cleaved after that update, new update might not applied correctly. @@ -156,7 +157,7 @@ func (ll *linkedList) cleanup(maxChainLength int, stack *stack) { for n != nil { pruned++ pending := n.Next - stack.Push(n, getNow()) + stack.Push(n, tn) n = pending } ll.length -= pruned @@ -185,7 +186,7 @@ func (ll *linkedList) retrieve(count int) Items { // updateInsertByPrice amends, inserts, moves and cleaves length of depth by // updates -func (ll *linkedList) updateInsertByPrice(updts Items, stack *stack, maxChainLength int, compare func(float64, float64) bool, tn now) { +func (ll *linkedList) updateInsertByPrice(updts Items, stack *stack, maxChainLength int, compare func(float64, float64) bool, tn time.Time) { for x := range updts { for tip := &ll.head; ; tip = &(*tip).Next { if *tip == nil { @@ -224,7 +225,7 @@ func (ll *linkedList) updateInsertByPrice(updts Items, stack *stack, maxChainLen } // Reduces length of total linked list chain to a maxChainLength value if maxChainLength != 0 && ll.length > maxChainLength { - ll.cleanup(maxChainLength, stack) + ll.cleanup(maxChainLength, stack, tn) } } @@ -482,7 +483,7 @@ func bidCompare(left, right float64) bool { // updateInsertByPrice amends, inserts, moves and cleaves length of depth by // updates -func (ll *bids) updateInsertByPrice(updts Items, stack *stack, maxChainLength int, tn now) { +func (ll *bids) updateInsertByPrice(updts Items, stack *stack, maxChainLength int, tn time.Time) { ll.linkedList.updateInsertByPrice(updts, stack, maxChainLength, bidCompare, tn) } @@ -618,7 +619,7 @@ func askCompare(left, right float64) bool { // updateInsertByPrice amends, inserts, moves and cleaves length of depth by // updates -func (ll *asks) updateInsertByPrice(updts Items, stack *stack, maxChainLength int, tn now) { +func (ll *asks) updateInsertByPrice(updts Items, stack *stack, maxChainLength int, tn time.Time) { ll.linkedList.updateInsertByPrice(updts, stack, maxChainLength, askCompare, tn) } diff --git a/exchanges/orderbook/linked_list_test.go b/exchanges/orderbook/linked_list_test.go index 2b6a8283990..d5804429e97 100644 --- a/exchanges/orderbook/linked_list_test.go +++ b/exchanges/orderbook/linked_list_test.go @@ -73,7 +73,7 @@ func TestLoad(t *testing.T) { {Price: 7, Amount: 1}, {Price: 9, Amount: 1}, {Price: 11, Amount: 1}, - }, stack) + }, stack, time.Now()) if stack.getCount() != 0 { t.Fatalf("incorrect stack count expected: %v received: %v", 0, stack.getCount()) @@ -85,7 +85,7 @@ func TestLoad(t *testing.T) { {Price: 1, Amount: 1}, {Price: 3, Amount: 1}, {Price: 5, Amount: 1}, - }, stack) + }, stack, time.Now()) if stack.getCount() != 3 { t.Fatalf("incorrect stack count expected: %v received: %v", 3, stack.getCount()) @@ -98,7 +98,7 @@ func TestLoad(t *testing.T) { {Price: 3, Amount: 1}, {Price: 5, Amount: 1}, {Price: 7, Amount: 1}, - }, stack) + }, stack, time.Now()) if stack.getCount() != 2 { t.Fatalf("incorrect stack count expected: %v received: %v", 2, stack.getCount()) @@ -107,7 +107,7 @@ func TestLoad(t *testing.T) { Check(t, list, 4, 16, 4) // purge entire list - list.load(nil, stack) + list.load(nil, stack, time.Now()) if stack.getCount() != 6 { t.Fatalf("incorrect stack count expected: %v received: %v", 6, stack.getCount()) @@ -122,7 +122,7 @@ func BenchmarkLoad(b *testing.B) { ll := linkedList{} s := newStack() for i := 0; i < b.N; i++ { - ll.load(ask, s) + ll.load(ask, s, time.Now()) } } @@ -137,12 +137,12 @@ func TestUpdateInsertByPrice(t *testing.T) { {Price: 9, Amount: 1}, {Price: 11, Amount: 1}, } - a.load(asksSnapshot, stack) + a.load(asksSnapshot, stack, time.Now()) // Update one instance with matching price a.updateInsertByPrice(Items{ {Price: 1, Amount: 2}, - }, stack, 0, getNow()) + }, stack, 0, time.Now()) Check(t, a, 7, 37, 6) @@ -153,7 +153,7 @@ func TestUpdateInsertByPrice(t *testing.T) { // Insert at head a.updateInsertByPrice(Items{ {Price: 0.5, Amount: 2}, - }, stack, 0, getNow()) + }, stack, 0, time.Now()) Check(t, a, 9, 38, 7) @@ -164,7 +164,7 @@ func TestUpdateInsertByPrice(t *testing.T) { // Insert at tail a.updateInsertByPrice(Items{ {Price: 12, Amount: 2}, - }, stack, 0, getNow()) + }, stack, 0, time.Now()) Check(t, a, 11, 62, 8) @@ -177,7 +177,7 @@ func TestUpdateInsertByPrice(t *testing.T) { {Price: 11.5, Amount: 2}, {Price: 10.5, Amount: 2}, {Price: 13, Amount: 2}, - }, stack, 10, getNow()) + }, stack, 10, time.Now()) Check(t, a, 15, 106, 10) @@ -188,7 +188,7 @@ func TestUpdateInsertByPrice(t *testing.T) { // delete at tail a.updateInsertByPrice(Items{ {Price: 12, Amount: 0}, - }, stack, 0, getNow()) + }, stack, 0, time.Now()) Check(t, a, 13, 82, 9) @@ -199,7 +199,7 @@ func TestUpdateInsertByPrice(t *testing.T) { // delete at mid a.updateInsertByPrice(Items{ {Price: 7, Amount: 0}, - }, stack, 0, getNow()) + }, stack, 0, time.Now()) Check(t, a, 12, 75, 8) @@ -210,7 +210,7 @@ func TestUpdateInsertByPrice(t *testing.T) { // delete at head a.updateInsertByPrice(Items{ {Price: 0.5, Amount: 0}, - }, stack, 0, getNow()) + }, stack, 0, time.Now()) Check(t, a, 10, 74, 7) @@ -219,7 +219,7 @@ func TestUpdateInsertByPrice(t *testing.T) { } // purge if liquidity plunges to zero - a.load(nil, stack) + a.load(nil, stack, time.Now()) // rebuild everything again a.updateInsertByPrice(Items{ @@ -229,7 +229,7 @@ func TestUpdateInsertByPrice(t *testing.T) { {Price: 7, Amount: 1}, {Price: 9, Amount: 1}, {Price: 11, Amount: 1}, - }, stack, 0, getNow()) + }, stack, 0, time.Now()) Check(t, a, 6, 36, 6) @@ -246,12 +246,12 @@ func TestUpdateInsertByPrice(t *testing.T) { {Price: 3, Amount: 1}, {Price: 1, Amount: 1}, } - b.load(bidsSnapshot, stack) + b.load(bidsSnapshot, stack, time.Now()) // Update one instance with matching price b.updateInsertByPrice(Items{ {Price: 11, Amount: 2}, - }, stack, 0, getNow()) + }, stack, 0, time.Now()) Check(t, b, 7, 47, 6) @@ -262,7 +262,7 @@ func TestUpdateInsertByPrice(t *testing.T) { // Insert at head b.updateInsertByPrice(Items{ {Price: 12, Amount: 2}, - }, stack, 0, getNow()) + }, stack, 0, time.Now()) Check(t, b, 9, 71, 7) @@ -273,7 +273,7 @@ func TestUpdateInsertByPrice(t *testing.T) { // Insert at tail b.updateInsertByPrice(Items{ {Price: 0.5, Amount: 2}, - }, stack, 0, getNow()) + }, stack, 0, time.Now()) Check(t, b, 11, 72, 8) @@ -286,7 +286,7 @@ func TestUpdateInsertByPrice(t *testing.T) { {Price: 11.5, Amount: 2}, {Price: 10.5, Amount: 2}, {Price: 13, Amount: 2}, - }, stack, 10, getNow()) + }, stack, 10, time.Now()) Check(t, b, 15, 141, 10) @@ -297,7 +297,7 @@ func TestUpdateInsertByPrice(t *testing.T) { // Insert between price and up to and beyond max allowable depth level b.updateInsertByPrice(Items{ {Price: 1, Amount: 0}, - }, stack, 0, getNow()) + }, stack, 0, time.Now()) Check(t, b, 14, 140, 9) @@ -308,7 +308,7 @@ func TestUpdateInsertByPrice(t *testing.T) { // delete at mid b.updateInsertByPrice(Items{ {Price: 10.5, Amount: 0}, - }, stack, 0, getNow()) + }, stack, 0, time.Now()) Check(t, b, 12, 119, 8) @@ -319,7 +319,7 @@ func TestUpdateInsertByPrice(t *testing.T) { // delete at head b.updateInsertByPrice(Items{ {Price: 13, Amount: 0}, - }, stack, 0, getNow()) + }, stack, 0, time.Now()) Check(t, b, 10, 93, 7) @@ -328,7 +328,7 @@ func TestUpdateInsertByPrice(t *testing.T) { } // purge if liquidity plunges to zero - b.load(nil, stack) + b.load(nil, stack, time.Now()) // rebuild everything again b.updateInsertByPrice(Items{ @@ -338,7 +338,7 @@ func TestUpdateInsertByPrice(t *testing.T) { {Price: 7, Amount: 1}, {Price: 9, Amount: 1}, {Price: 11, Amount: 1}, - }, stack, 0, getNow()) + }, stack, 0, time.Now()) Check(t, b, 6, 36, 6) @@ -358,17 +358,17 @@ func TestCleanup(t *testing.T) { {Price: 9, Amount: 1}, {Price: 11, Amount: 1}, } - a.load(asksSnapshot, stack) + a.load(asksSnapshot, stack, time.Now()) - a.cleanup(6, stack) + a.cleanup(6, stack, time.Now()) Check(t, a, 6, 36, 6) - a.cleanup(5, stack) + a.cleanup(5, stack, time.Now()) Check(t, a, 5, 25, 5) - a.cleanup(1, stack) + a.cleanup(1, stack, time.Now()) Check(t, a, 1, 1, 1) - a.cleanup(10, stack) + a.cleanup(10, stack, time.Now()) Check(t, a, 1, 1, 1) - a.cleanup(0, stack) // will purge, underlying checks are done elseware to prevent this + a.cleanup(0, stack, time.Now()) // will purge, underlying checks are done elseware to prevent this Check(t, a, 0, 0, 0) } @@ -378,7 +378,7 @@ func BenchmarkUpdateInsertByPrice_Amend(b *testing.B) { a := asks{} stack := newStack() - a.load(ask, stack) + a.load(ask, stack, time.Now()) updates := Items{ { @@ -392,7 +392,7 @@ func BenchmarkUpdateInsertByPrice_Amend(b *testing.B) { } for i := 0; i < b.N; i++ { - a.updateInsertByPrice(updates, stack, 0, getNow()) + a.updateInsertByPrice(updates, stack, 0, time.Now()) } } @@ -401,7 +401,7 @@ func BenchmarkUpdateInsertByPrice_Insert_Delete(b *testing.B) { a := asks{} stack := newStack() - a.load(ask, stack) + a.load(ask, stack, time.Now()) updates := Items{ { @@ -415,7 +415,7 @@ func BenchmarkUpdateInsertByPrice_Insert_Delete(b *testing.B) { } for i := 0; i < b.N; i++ { - a.updateInsertByPrice(updates, stack, 0, getNow()) + a.updateInsertByPrice(updates, stack, 0, time.Now()) } } @@ -430,7 +430,7 @@ func TestUpdateByID(t *testing.T) { {Price: 9, Amount: 1, ID: 9}, {Price: 11, Amount: 1, ID: 11}, } - a.load(asksSnapshot, s) + a.load(asksSnapshot, s, time.Now()) err := a.updateByID(Items{ {Price: 1, Amount: 1, ID: 1}, @@ -485,7 +485,7 @@ func BenchmarkUpdateByID(b *testing.B) { {Price: 9, Amount: 1, ID: 9}, {Price: 11, Amount: 1, ID: 11}, } - asks.load(asksSnapshot, s) + asks.load(asksSnapshot, s, time.Now()) for i := 0; i < b.N; i++ { err := asks.updateByID(Items{ @@ -513,12 +513,12 @@ func TestDeleteByID(t *testing.T) { {Price: 9, Amount: 1, ID: 9}, {Price: 11, Amount: 1, ID: 11}, } - a.load(asksSnapshot, s) + a.load(asksSnapshot, s, time.Now()) // Delete at head err := a.deleteByID(Items{ {Price: 1, Amount: 1, ID: 1}, - }, s, false) + }, s, false, time.Now()) if err != nil { t.Fatal(err) } @@ -528,7 +528,7 @@ func TestDeleteByID(t *testing.T) { // Delete at tail err = a.deleteByID(Items{ {Price: 1, Amount: 1, ID: 11}, - }, s, false) + }, s, false, time.Now()) if err != nil { t.Fatal(err) } @@ -538,7 +538,7 @@ func TestDeleteByID(t *testing.T) { // Delete in middle err = a.deleteByID(Items{ {Price: 1, Amount: 1, ID: 5}, - }, s, false) + }, s, false, time.Now()) if err != nil { t.Fatal(err) } @@ -548,7 +548,7 @@ func TestDeleteByID(t *testing.T) { // Intentional error err = a.deleteByID(Items{ {Price: 11, Amount: 1, ID: 1337}, - }, s, false) + }, s, false, time.Now()) if !errors.Is(err, errIDCannotBeMatched) { t.Fatalf("expecting %s but received %v", errIDCannotBeMatched, err) } @@ -556,7 +556,7 @@ func TestDeleteByID(t *testing.T) { // Error bypass err = a.deleteByID(Items{ {Price: 11, Amount: 1, ID: 1337}, - }, s, true) + }, s, true, time.Now()) if err != nil { t.Fatal(err) } @@ -573,7 +573,7 @@ func TestUpdateInsertByIDAsk(t *testing.T) { {Price: 9, Amount: 1, ID: 9}, {Price: 11, Amount: 1, ID: 11}, } - a.load(asksSnapshot, s) + a.load(asksSnapshot, s, time.Now()) // Update one instance with matching ID err := a.updateInsertByID(Items{ @@ -586,7 +586,7 @@ func TestUpdateInsertByIDAsk(t *testing.T) { Check(t, a, 7, 37, 6) // Reset - a.load(asksSnapshot, s) + a.load(asksSnapshot, s, time.Now()) // Update all instances with matching ID in order err = a.updateInsertByID(Items{ @@ -664,7 +664,7 @@ func TestUpdateInsertByIDAsk(t *testing.T) { Check(t, a, 12, 63, 6) // Reset - a.load(asksSnapshot, s) + a.load(asksSnapshot, s, time.Now()) // Update all instances move one after ID err = a.updateInsertByID(Items{ @@ -682,7 +682,7 @@ func TestUpdateInsertByIDAsk(t *testing.T) { Check(t, a, 12, 78, 6) // Reset - a.load(asksSnapshot, s) + a.load(asksSnapshot, s, time.Now()) // Update all instances move one after ID to tail err = a.updateInsertByID(Items{ @@ -716,7 +716,7 @@ func TestUpdateInsertByIDAsk(t *testing.T) { Check(t, a, 14, 106, 7) // Reset - a.load(asksSnapshot, s) + a.load(asksSnapshot, s, time.Now()) // Update all instances pop at head err = a.updateInsertByID(Items{ @@ -815,7 +815,7 @@ func TestUpdateInsertByIDAsk(t *testing.T) { Check(t, a, 19, 213, 9) // purge - a.load(nil, s) + a.load(nil, s, time.Now()) // insert with no liquidity and jumbled err = a.updateInsertByID(Items{ @@ -845,7 +845,7 @@ func TestUpdateInsertByIDBids(t *testing.T) { {Price: 3, Amount: 1, ID: 3}, {Price: 1, Amount: 1, ID: 1}, } - b.load(bidsSnapshot, s) + b.load(bidsSnapshot, s, time.Now()) // Update one instance with matching ID err := b.updateInsertByID(Items{ @@ -858,7 +858,7 @@ func TestUpdateInsertByIDBids(t *testing.T) { Check(t, b, 7, 37, 6) // Reset - b.load(bidsSnapshot, s) + b.load(bidsSnapshot, s, time.Now()) // Update all instances with matching ID in order err = b.updateInsertByID(Items{ @@ -936,7 +936,7 @@ func TestUpdateInsertByIDBids(t *testing.T) { Check(t, b, 12, 63, 6) // Reset - b.load(bidsSnapshot, s) + b.load(bidsSnapshot, s, time.Now()) // Update all instances move one after ID err = b.updateInsertByID(Items{ @@ -954,7 +954,7 @@ func TestUpdateInsertByIDBids(t *testing.T) { Check(t, b, 12, 78, 6) // Reset - b.load(bidsSnapshot, s) + b.load(bidsSnapshot, s, time.Now()) // Update all instances move one after ID to tail err = b.updateInsertByID(Items{ @@ -988,7 +988,7 @@ func TestUpdateInsertByIDBids(t *testing.T) { Check(t, b, 14, 106, 7) // Reset - b.load(bidsSnapshot, s) + b.load(bidsSnapshot, s, time.Now()) // Update all instances pop at tail err = b.updateInsertByID(Items{ @@ -1084,7 +1084,7 @@ func TestUpdateInsertByIDBids(t *testing.T) { Check(t, b, 19, 157.7, 9) // purge - b.load(nil, s) + b.load(nil, s, time.Now()) // insert with no liquidity and jumbled err = b.updateInsertByID(Items{ @@ -1114,7 +1114,7 @@ func TestInsertUpdatesBid(t *testing.T) { {Price: 3, Amount: 1, ID: 3}, {Price: 1, Amount: 1, ID: 1}, } - b.load(bidsSnapshot, s) + b.load(bidsSnapshot, s, time.Now()) err := b.insertUpdates(Items{ {Price: 11, Amount: 1, ID: 11}, @@ -1161,7 +1161,7 @@ func TestInsertUpdatesBid(t *testing.T) { Check(t, b, 9, 54, 9) // purge - b.load(nil, s) + b.load(nil, s, time.Now()) // Add one at head err = b.insertUpdates(Items{ @@ -1185,7 +1185,7 @@ func TestInsertUpdatesAsk(t *testing.T) { {Price: 9, Amount: 1, ID: 9}, {Price: 11, Amount: 1, ID: 11}, } - a.load(askSnapshot, s) + a.load(askSnapshot, s, time.Now()) err := a.insertUpdates(Items{ {Price: 11, Amount: 1, ID: 11}, @@ -1232,7 +1232,7 @@ func TestInsertUpdatesAsk(t *testing.T) { Check(t, a, 9, 54, 9) // purge - a.load(nil, s) + a.load(nil, s, time.Now()) // Add one at head err = a.insertUpdates(Items{ @@ -1353,7 +1353,7 @@ func TestAmount(t *testing.T) { {Price: 9, Amount: 1, ID: 9}, {Price: 11, Amount: 1, ID: 11}, } - a.load(askSnapshot, s) + a.load(askSnapshot, s, time.Now()) liquidity, value := a.amount() if liquidity != 6 { @@ -1545,7 +1545,10 @@ func TestGetMovementByBaseAmount(t *testing.T) { t.Run(tt.Name, func(t *testing.T) { t.Parallel() depth := NewDepth(id) - depth.LoadSnapshot(tt.BidLiquidity, nil, 0, time.Time{}, true) + err := depth.LoadSnapshot(tt.BidLiquidity, nil, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } movement, err := depth.bids.getMovementByBase(tt.BaseAmount, tt.ReferencePrice, false) if !errors.Is(err, tt.ExpectedError) { t.Fatalf("received: '%v' but expected: '%v'", err, tt.ExpectedError) @@ -1666,7 +1669,10 @@ func TestGetBaseAmountFromNominalSlippage(t *testing.T) { t.Run(tt.Name, func(t *testing.T) { t.Parallel() depth := NewDepth(id) - depth.LoadSnapshot(tt.BidLiquidity, nil, 0, time.Time{}, true) + err := depth.LoadSnapshot(tt.BidLiquidity, nil, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } base, err := depth.bids.hitBidsByNominalSlippage(tt.NominalSlippage, tt.ReferencePrice) if !errors.Is(err, tt.ExpectedError) { t.Fatalf("%s received: '%v' but expected: '%v'", @@ -1775,7 +1781,10 @@ func TestGetBaseAmountFromImpact(t *testing.T) { t.Run(tt.Name, func(t *testing.T) { t.Parallel() depth := NewDepth(id) - depth.LoadSnapshot(tt.BidLiquidity, nil, 0, time.Time{}, true) + err := depth.LoadSnapshot(tt.BidLiquidity, nil, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } base, err := depth.bids.hitBidsByImpactSlippage(tt.ImpactSlippage, tt.ReferencePrice) if !errors.Is(err, tt.ExpectedError) { t.Fatalf("%s received: '%v' but expected: '%v'", tt.Name, err, tt.ExpectedError) @@ -1858,7 +1867,10 @@ func TestGetMovementByQuoteAmount(t *testing.T) { t.Run(tt.Name, func(t *testing.T) { t.Parallel() depth := NewDepth(id) - depth.LoadSnapshot(nil, tt.AskLiquidity, 0, time.Time{}, true) + err := depth.LoadSnapshot(nil, tt.AskLiquidity, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } movement, err := depth.asks.getMovementByQuotation(tt.QuoteAmount, tt.ReferencePrice, false) if !errors.Is(err, tt.ExpectedError) { t.Fatalf("received: '%v' but expected: '%v'", err, tt.ExpectedError) @@ -1988,7 +2000,10 @@ func TestGetQuoteAmountFromNominalSlippage(t *testing.T) { t.Run(tt.Name, func(t *testing.T) { t.Parallel() depth := NewDepth(id) - depth.LoadSnapshot(nil, tt.AskLiquidity, 0, time.Time{}, true) + err := depth.LoadSnapshot(nil, tt.AskLiquidity, 0, time.Now(), true) + if err != nil { + t.Fatalf("failed to load snapshot: %s", err) + } quote, err := depth.asks.liftAsksByNominalSlippage(tt.NominalSlippage, tt.ReferencePrice) if !errors.Is(err, tt.ExpectedError) { t.Fatalf("%s received: '%v' but expected: '%v'", tt.Name, err, tt.ExpectedError) @@ -2077,7 +2092,10 @@ func TestGetQuoteAmountFromImpact(t *testing.T) { t.Run(tt.Name, func(t *testing.T) { t.Parallel() depth := NewDepth(id) - depth.LoadSnapshot(nil, tt.AskLiquidity, 0, time.Time{}, true) + err := depth.LoadSnapshot(nil, tt.AskLiquidity, 0, time.Now(), true) + if err != nil { + t.Fatalf("failed to load snapshot: %s", err) + } quote, err := depth.asks.liftAsksByImpactSlippage(tt.ImpactSlippage, tt.ReferencePrice) if !errors.Is(err, tt.ExpectedError) { t.Fatalf("received: '%v' but expected: '%v'", err, tt.ExpectedError) @@ -2099,7 +2117,10 @@ func TestGetHeadPrice(t *testing.T) { if _, err := depth.asks.getHeadPriceNoLock(); !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - depth.LoadSnapshot(bid, ask, 0, time.Time{}, true) + err := depth.LoadSnapshot(bid, ask, 0, time.Now(), true) + if err != nil { + t.Fatalf("failed to load snapshot: %s", err) + } val, err := depth.bids.getHeadPriceNoLock() if !errors.Is(err, nil) { diff --git a/exchanges/orderbook/node.go b/exchanges/orderbook/node.go index f52aec672c3..5e428d5df5d 100644 --- a/exchanges/orderbook/node.go +++ b/exchanges/orderbook/node.go @@ -40,24 +40,16 @@ func newStack() *stack { return s } -// now defines a time which is now to ensure no other values get passed in -type now time.Time - -// getNow returns the time at which it is called -func getNow() now { - return now(time.Now()) -} - // Push pushes a node pointer into the stack to be reused the time is passed in // to allow for inlining which sets the time at which the node is theoretically // pushed to a stack. -func (s *stack) Push(n *Node, tn now) { +func (s *stack) Push(n *Node, tn time.Time) { if !atomic.CompareAndSwapUint32(&s.sema, neutral, active) { // Stack is in use, for now we can dereference pointer return } // Adds a time when its placed back on to stack. - n.shelved = time.Time(tn) + n.shelved = tn n.Next = nil n.Prev = nil n.Value = Item{} diff --git a/exchanges/orderbook/node_test.go b/exchanges/orderbook/node_test.go index a58519151b7..6c31145f9c9 100644 --- a/exchanges/orderbook/node_test.go +++ b/exchanges/orderbook/node_test.go @@ -19,7 +19,7 @@ func TestPushPop(t *testing.T) { } for i := 0; i < 100; i++ { - s.Push(nSlice[i], getNow()) + s.Push(nSlice[i], time.Now()) } if s.getCount() != 100 { @@ -34,13 +34,13 @@ func TestCleaner(t *testing.T) { nSlice[i] = s.Pop() } - tn := getNow() + tn := time.Now() for i := 0; i < 50; i++ { s.Push(nSlice[i], tn) } // Makes all the 50 pushed nodes invalid time.Sleep(time.Millisecond * 260) - tn = getNow() + tn = time.Now() for i := 50; i < 100; i++ { s.Push(nSlice[i], tn) } @@ -81,7 +81,7 @@ func BenchmarkWithStack(b *testing.B) { stack := newStack() b.ReportAllocs() b.ResetTimer() - tn := getNow() + tn := time.Now() for i := 0; i < b.N; i++ { for j := 0; j < 100000; j++ { n = stack.Pop() diff --git a/exchanges/orderbook/orderbook.go b/exchanges/orderbook/orderbook.go index f7e024869ee..d4c3c2a34bf 100644 --- a/exchanges/orderbook/orderbook.go +++ b/exchanges/orderbook/orderbook.go @@ -77,8 +77,11 @@ func (s *Service) Update(b *Base) error { book.AssignOptions(b) m3[b.Pair.Quote.Item] = book } - book.LoadSnapshot(b.Bids, b.Asks, b.LastUpdateID, b.LastUpdated, true) + err := book.LoadSnapshot(b.Bids, b.Asks, b.LastUpdateID, b.LastUpdated, true) s.mu.Unlock() + if err != nil { + return err + } return s.Mux.Publish(book, m1.ID) } diff --git a/exchanges/orderbook/unsafe_test.go b/exchanges/orderbook/unsafe_test.go index 552e5549479..e14f4d21ebb 100644 --- a/exchanges/orderbook/unsafe_test.go +++ b/exchanges/orderbook/unsafe_test.go @@ -39,13 +39,19 @@ func TestGetLiquidity(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - d.LoadSnapshot([]Item{{Price: 2}}, nil, 0, time.Time{}, false) + err = d.LoadSnapshot([]Item{{Price: 2}}, nil, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } _, _, err = unsafe.GetLiquidity() if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - d.LoadSnapshot([]Item{{Price: 2}}, []Item{{Price: 2}}, 0, time.Time{}, false) + err = d.LoadSnapshot([]Item{{Price: 2}}, []Item{{Price: 2}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } aN, bN, err := unsafe.GetLiquidity() if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -69,7 +75,10 @@ func TestCheckBidLiquidity(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - d.LoadSnapshot([]Item{{Price: 2}}, nil, 0, time.Time{}, false) + err = d.LoadSnapshot([]Item{{Price: 2}}, nil, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } err = unsafe.CheckBidLiquidity() if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -85,7 +94,10 @@ func TestCheckAskLiquidity(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - d.LoadSnapshot(nil, []Item{{Price: 2}}, 0, time.Time{}, false) + err = d.LoadSnapshot(nil, []Item{{Price: 2}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } err = unsafe.CheckAskLiquidity() if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -100,7 +112,10 @@ func TestGetBestBid(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - d.LoadSnapshot([]Item{{Price: 2}}, nil, 0, time.Time{}, false) + err := d.LoadSnapshot([]Item{{Price: 2}}, nil, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } bestBid, err := unsafe.GetBestBid() if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -119,7 +134,10 @@ func TestGetBestAsk(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - d.LoadSnapshot(nil, []Item{{Price: 2}}, 0, time.Time{}, false) + err := d.LoadSnapshot(nil, []Item{{Price: 2}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } bestAsk, err := unsafe.GetBestAsk() if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -138,7 +156,10 @@ func TestGetMidPrice(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - d.LoadSnapshot([]Item{{Price: 1}}, []Item{{Price: 2}}, 0, time.Time{}, false) + err := d.LoadSnapshot([]Item{{Price: 1}}, []Item{{Price: 2}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } mid, err := unsafe.GetMidPrice() if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -157,7 +178,10 @@ func TestGetSpread(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } - d.LoadSnapshot([]Item{{Price: 1}}, []Item{{Price: 2}}, 0, time.Time{}, false) + err := d.LoadSnapshot([]Item{{Price: 1}}, []Item{{Price: 2}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } spread, err := unsafe.GetSpread() if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -178,14 +202,20 @@ func TestGetImbalance(t *testing.T) { } // unlikely event zero amounts - d.LoadSnapshot([]Item{{Price: 1, Amount: 0}}, []Item{{Price: 2, Amount: 0}}, 0, time.Time{}, false) + err = d.LoadSnapshot([]Item{{Price: 1, Amount: 0}}, []Item{{Price: 2, Amount: 0}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } _, err = unsafe.GetImbalance() if !errors.Is(err, errNoLiquidity) { t.Fatalf("received: '%v' but expected: '%v'", err, errNoLiquidity) } // balance skewed to asks - d.LoadSnapshot([]Item{{Price: 1, Amount: 1}}, []Item{{Price: 2, Amount: 1000}}, 0, time.Time{}, false) + err = d.LoadSnapshot([]Item{{Price: 1, Amount: 1}}, []Item{{Price: 2, Amount: 1000}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } imbalance, err := unsafe.GetImbalance() if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -196,7 +226,10 @@ func TestGetImbalance(t *testing.T) { } // balance skewed to bids - d.LoadSnapshot([]Item{{Price: 1, Amount: 1000}}, []Item{{Price: 2, Amount: 1}}, 0, time.Time{}, false) + err = d.LoadSnapshot([]Item{{Price: 1, Amount: 1000}}, []Item{{Price: 2, Amount: 1}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } imbalance, err = unsafe.GetImbalance() if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -207,7 +240,10 @@ func TestGetImbalance(t *testing.T) { } // in balance - d.LoadSnapshot([]Item{{Price: 1, Amount: 1}}, []Item{{Price: 2, Amount: 1}}, 0, time.Time{}, false) + err = d.LoadSnapshot([]Item{{Price: 1, Amount: 1}}, []Item{{Price: 2, Amount: 1}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } imbalance, err = unsafe.GetImbalance() if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -225,12 +261,18 @@ func TestIsStreaming(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", unsafe.IsStreaming(), true) } - d.LoadSnapshot([]Item{{Price: 1, Amount: 1}}, []Item{{Price: 2, Amount: 1}}, 0, time.Time{}, true) + err := d.LoadSnapshot([]Item{{Price: 1, Amount: 1}}, []Item{{Price: 2, Amount: 1}}, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } if unsafe.IsStreaming() { t.Fatalf("received: '%v' but expected: '%v'", unsafe.IsStreaming(), false) } - d.LoadSnapshot([]Item{{Price: 1, Amount: 1}}, []Item{{Price: 2, Amount: 1}}, 0, time.Time{}, false) + err = d.LoadSnapshot([]Item{{Price: 1, Amount: 1}}, []Item{{Price: 2, Amount: 1}}, 0, time.Now(), false) + if err != nil { + t.Fatal(err) + } if !unsafe.IsStreaming() { t.Fatalf("received: '%v' but expected: '%v'", unsafe.IsStreaming(), true) } diff --git a/exchanges/poloniex/poloniex_test.go b/exchanges/poloniex/poloniex_test.go index 01241014e21..f42bc6772ad 100644 --- a/exchanges/poloniex/poloniex_test.go +++ b/exchanges/poloniex/poloniex_test.go @@ -638,13 +638,13 @@ func TestWsPriceAggregateOrderbook(t *testing.T) { if err != nil { t.Error(err) } - pressXToJSON := []byte(`[148,827987828,[["i",{"currencyPair":"BTC_ETH","orderBook":[{"0.02311264":"2.20557811","1000.02022945":"1.00000000","1000.17618025":"0.00100000","1148.00000000":"0.04594689","1997.00000000":"2.00000000","2000.00000000":"0.00000206","3000.00000000":"0.00000137","3772.00000000":"0.65977073","4000.00000000":"0.00000103","5000.00000000":"0.10284089"},{"0.02310611":"21.20361406","0.00010000":"2052.10260000","0.00009726":"17.85554185","0.00009170":"10.00000000","0.00008800":"8.00000000","0.00008000":"2.02050000","0.00007186":"6.95811300","0.00006060":"130.00000000","0.00005126":"1070.00000000","0.00005120":"195.31250000","0.00005000":"2120.00000000","0.00004295":"202.34435389","0.00004168":"95.96928983","0.00004000":"200.00000000","0.00003638":"137.43815283","0.00003500":"114.28657143","0.00003492":"6.90074951","0.00003101":"500.00000000","0.00003100":"1000.00000000","0.00002560":"390.62500000","0.00002500":"20000.00000000","0.00002000":"55.00000000","0.00001280":"781.25000000","0.00001010":"50.00000000","0.00001005":"146.26965174","0.00001000":"12109.99999999","0.00000640":"1562.50000000","0.00000550":"800.00000000","0.00000500":"200.00000000","0.00000331":"1000.00000000","0.00000330":"11479.02727273","0.00000320":"3125.00000000","0.00000200":"1000.00000001","0.00000178":"65.00000000","0.00000170":"100.00000000","0.00000164":"210.17073171","0.00000160":"6250.00000000","0.00000100":"1999.00000000","0.00000095":"1612.31578947","0.00000090":"1111.11111111","0.00000080":"12500.00000000","0.00000054":"557.96296296","0.00000040":"25000.00000000","0.00000020":"50000.00000000","0.00000010":"200000.00000000","0.00000005":"200000.00000000","0.00000004":"2500.00000000","0.00000002":"556100.00000000","0.00000001":"1182263.00000000"}]}]]]`) + pressXToJSON := []byte(`[50,141160924,[["i",{"currencyPair":"BTC_LTC","orderBook":[{"0.002784":"17.55","0.002786":"1.47","0.002792":"13.25","0.0028":"0.21","0.002804":"0.02","0.00281":"1.5","0.002811":"258.82","0.002812":"3.81","0.002817":"0.06","0.002824":"3","0.002825":"0.02","0.002836":"18.01","0.002837":"0.03","0.00284":"0.03","0.002842":"12.7","0.00285":"0.02","0.002852":"0.02","0.002855":"1.3","0.002857":"15.64","0.002864":"0.01"},{"0.002782":"45.93","0.002781":"1.46","0.002774":"13.34","0.002773":"0.04","0.002771":"0.05","0.002765":"6.21","0.002764":"3","0.00276":"10.77","0.002758":"3.11","0.002754":"0.02","0.002751":"288.94","0.00275":"24.06","0.002745":"187.27","0.002743":"0.04","0.002742":"0.96","0.002731":"0.06","0.00273":"12.13","0.002727":"0.02","0.002725":"0.03","0.002719":"1.09"}]}, "1692080077892"]]]`) err = p.wsHandleData(pressXToJSON) if err != nil { t.Error(err) } - pressXToJSON = []byte(`[148,827984670,[["o",0,"0.02328500","0.00000000"],["o",0,"0.02328498","0.04303557"]]]`) + pressXToJSON = []byte(`[50,141160925,[["o",1,"0.002742","0", "1692080078806"],["o",1,"0.002718","0.02", "1692080078806"]]]`) err = p.wsHandleData(pressXToJSON) if err != nil { t.Error(err) diff --git a/exchanges/poloniex/poloniex_websocket.go b/exchanges/poloniex/poloniex_websocket.go index 7fb0660ae94..718507949ae 100644 --- a/exchanges/poloniex/poloniex_websocket.go +++ b/exchanges/poloniex/poloniex_websocket.go @@ -391,6 +391,20 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(data []interface{}) error { errTypeAssertionFailure) } + if len(data) < 3 { + return fmt.Errorf("%w for pair %v", errNotEnoughData, pair) + } + + ts, ok := data[2].(string) + if !ok { + return common.GetTypeAssertError("string", data[2], "timestamp string") + } + + tsMilli, err := strconv.ParseInt(ts, 10, 64) + if err != nil { + return err + } + oMap, ok := subDataMap["orderBook"] if !ok { return errors.New("could not find orderbook data in map") @@ -421,7 +435,8 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(data []interface{}) error { var book orderbook.Base book.Asks = make(orderbook.Items, 0, len(askData)) for price, volume := range askData { - p, err := strconv.ParseFloat(price, 64) + var p float64 + p, err = strconv.ParseFloat(price, 64) if err != nil { return err } @@ -430,7 +445,8 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(data []interface{}) error { return fmt.Errorf("%w ask volume data not string", errTypeAssertionFailure) } - a, err := strconv.ParseFloat(v, 64) + var a float64 + a, err = strconv.ParseFloat(v, 64) if err != nil { return err } @@ -439,7 +455,8 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(data []interface{}) error { book.Bids = make(orderbook.Items, 0, len(bidData)) for price, volume := range bidData { - p, err := strconv.ParseFloat(price, 64) + var p float64 + p, err = strconv.ParseFloat(price, 64) if err != nil { return err } @@ -448,7 +465,8 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(data []interface{}) error { return fmt.Errorf("%w bid volume data not string", errTypeAssertionFailure) } - a, err := strconv.ParseFloat(v, 64) + var a float64 + a, err = strconv.ParseFloat(v, 64) if err != nil { return err } @@ -460,8 +478,7 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(data []interface{}) error { book.Bids.SortBids() book.Asset = asset.Spot book.VerifyOrderbook = p.CanVerifyOrderbook - - var err error + book.LastUpdated = time.UnixMilli(tsMilli) book.Pair, err = currency.NewPairFromString(pair) if err != nil { return err @@ -473,7 +490,7 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(data []interface{}) error { // WsProcessOrderbookUpdate processes new orderbook updates func (p *Poloniex) WsProcessOrderbookUpdate(sequenceNumber float64, data []interface{}, pair currency.Pair) error { - if len(data) < 4 { + if len(data) < 5 { return errNotEnoughData } @@ -497,10 +514,22 @@ func (p *Poloniex) WsProcessOrderbookUpdate(sequenceNumber float64, data []inter if !ok { return fmt.Errorf("%w buysell not float64", errTypeAssertionFailure) } + + ts, ok := data[4].(string) + if !ok { + return common.GetTypeAssertError("string", data[2], "timestamp string") + } + + tsMilli, err := strconv.ParseInt(ts, 10, 64) + if err != nil { + return err + } + update := &orderbook.Update{ - Pair: pair, - Asset: asset.Spot, - UpdateID: int64(sequenceNumber), + Pair: pair, + Asset: asset.Spot, + UpdateID: int64(sequenceNumber), + UpdateTime: time.UnixMilli(tsMilli), } if bs == 1 { update.Bids = []orderbook.Item{{Price: price, Amount: volume}} diff --git a/exchanges/stream/buffer/buffer.go b/exchanges/stream/buffer/buffer.go index dd9c044ec14..f2ef36e90c5 100644 --- a/exchanges/stream/buffer/buffer.go +++ b/exchanges/stream/buffer/buffer.go @@ -245,7 +245,10 @@ func (w *Orderbook) processObUpdate(o *orderbookHolder, u *orderbook.Update) err if w.updateEntriesByID { return o.updateByIDAndAction(u) } - o.updateByPrice(u) + err := o.updateByPrice(u) + if err != nil { + return err + } if w.checksum != nil { compare, err := o.ob.Retrieve() if err != nil { @@ -262,8 +265,8 @@ func (w *Orderbook) processObUpdate(o *orderbookHolder, u *orderbook.Update) err // updateByPrice amends amount if match occurs by price, deletes if amount is // zero or less and inserts if not found. -func (o *orderbookHolder) updateByPrice(updts *orderbook.Update) { - o.ob.UpdateBidAskByPrice(updts) +func (o *orderbookHolder) updateByPrice(updts *orderbook.Update) error { + return o.ob.UpdateBidAskByPrice(updts) } // updateByIDAndAction will receive an action to execute against the orderbook @@ -328,11 +331,15 @@ func (w *Orderbook) LoadSnapshot(book *orderbook.Base) error { } holder.updateID = book.LastUpdateID - holder.ob.LoadSnapshot(book.Bids, + + err = holder.ob.LoadSnapshot(book.Bids, book.Asks, book.LastUpdateID, book.LastUpdated, false) + if err != nil { + return err + } if holder.ob.VerifyOrderbook { // This is used here so as to not retrieve book if verification is off. diff --git a/exchanges/stream/buffer/buffer_test.go b/exchanges/stream/buffer/buffer_test.go index 582a51dc485..7f2ecfb3988 100644 --- a/exchanges/stream/buffer/buffer_test.go +++ b/exchanges/stream/buffer/buffer_test.go @@ -41,6 +41,7 @@ func createSnapshot() (holder *Orderbook, asks, bids orderbook.Items, err error) Asset: asset.Spot, Pair: cp, PriceDuplication: true, + LastUpdated: time.Now(), } newBook := make(map[Key]*orderbookHolder) @@ -93,7 +94,10 @@ func BenchmarkUpdateBidsByPrice(b *testing.B) { Asset: asset.Spot, } holder := ob.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}] - holder.updateByPrice(update) + err = holder.updateByPrice(update) + if err != nil { + b.Fatal(err) + } } } @@ -113,7 +117,10 @@ func BenchmarkUpdateAsksByPrice(b *testing.B) { Asset: asset.Spot, } holder := ob.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}] - holder.updateByPrice(update) + err = holder.updateByPrice(update) + if err != nil { + b.Fatal(err) + } } } @@ -240,7 +247,7 @@ func TestUpdates(t *testing.T) { } book := holder.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}] - book.updateByPrice(&orderbook.Update{ + err = book.updateByPrice(&orderbook.Update{ Bids: itemArray[5], Asks: itemArray[5], Pair: cp, @@ -251,7 +258,7 @@ func TestUpdates(t *testing.T) { t.Error(err) } - book.updateByPrice(&orderbook.Update{ + err = book.updateByPrice(&orderbook.Update{ Bids: itemArray[0], Asks: itemArray[0], Pair: cp, @@ -375,11 +382,12 @@ func TestSortIDs(t *testing.T) { asks := itemArray[i] bids := itemArray[i] err = holder.Update(&orderbook.Update{ - Bids: bids, - Asks: asks, - Pair: cp, - UpdateID: int64(i), - Asset: asset.Spot, + Bids: bids, + Asks: asks, + Pair: cp, + UpdateID: int64(i), + Asset: asset.Spot, + UpdateTime: time.Now(), }) if err != nil { t.Fatal(err) @@ -420,10 +428,11 @@ func TestOutOfOrderIDs(t *testing.T) { for i := range itemArray { asks := itemArray[i] err = holder.Update(&orderbook.Update{ - Asks: asks, - Pair: cp, - UpdateID: outOFOrderIDs[i], - Asset: asset.Spot, + Asks: asks, + Pair: cp, + UpdateID: outOFOrderIDs[i], + Asset: asset.Spot, + UpdateTime: time.Now(), }) if err != nil { t.Fatal(err) @@ -454,10 +463,11 @@ func TestOrderbookLastUpdateID(t *testing.T) { // this update invalidates the book err = holder.Update(&orderbook.Update{ - Asks: []orderbook.Item{{Price: 999999}}, - Pair: cp, - UpdateID: -1, - Asset: asset.Spot, + Asks: []orderbook.Item{{Price: 999999}}, + Pair: cp, + UpdateID: -1, + Asset: asset.Spot, + UpdateTime: time.Now(), }) if !errors.Is(err, orderbook.ErrOrderbookInvalid) { t.Fatalf("received: %v but expected: %v", err, orderbook.ErrOrderbookInvalid) @@ -474,10 +484,11 @@ func TestOrderbookLastUpdateID(t *testing.T) { for i := range itemArray { asks := itemArray[i] err = holder.Update(&orderbook.Update{ - Asks: asks, - Pair: cp, - UpdateID: int64(i) + 1, - Asset: asset.Spot, + Asks: asks, + Pair: cp, + UpdateID: int64(i) + 1, + Asset: asset.Spot, + UpdateTime: time.Now(), }) if err != nil { t.Fatal(err) @@ -566,6 +577,7 @@ func TestRunSnapshotWithNoData(t *testing.T) { snapShot1.Pair = cp snapShot1.Exchange = "test" obl.exchangeName = "test" + snapShot1.LastUpdated = time.Now() err := obl.LoadSnapshot(&snapShot1) if err != nil { t.Fatal(err) @@ -590,6 +602,7 @@ func TestLoadSnapshot(t *testing.T) { snapShot1.Bids = bids snapShot1.Asset = asset.Spot snapShot1.Pair = cp + snapShot1.LastUpdated = time.Now() err := obl.LoadSnapshot(&snapShot1) if err != nil { t.Error(err) @@ -651,6 +664,7 @@ func TestInsertingSnapShots(t *testing.T) { snapShot1.Bids = bids snapShot1.Asset = asset.Spot snapShot1.Pair = cp + snapShot1.LastUpdated = time.Now() err := holder.LoadSnapshot(&snapShot1) if err != nil { t.Fatal(err) @@ -694,6 +708,7 @@ func TestInsertingSnapShots(t *testing.T) { if err != nil { t.Fatal(err) } + snapShot2.LastUpdated = time.Now() err = holder.LoadSnapshot(&snapShot2) if err != nil { t.Fatal(err) @@ -737,6 +752,7 @@ func TestInsertingSnapShots(t *testing.T) { if err != nil { t.Fatal(err) } + snapShot3.LastUpdated = time.Now() err = holder.LoadSnapshot(&snapShot3) if err != nil { t.Fatal(err) @@ -873,7 +889,7 @@ func TestEnsureMultipleUpdatesViaPrice(t *testing.T) { asks := bidAskGenerator() book := holder.ob[Key{Base: cp.Base.Item, Quote: cp.Quote.Item, Asset: asset.Spot}] - book.updateByPrice(&orderbook.Update{ + err = book.updateByPrice(&orderbook.Update{ Bids: asks, Asks: asks, Pair: cp, @@ -916,7 +932,10 @@ func TestUpdateByIDAndAction(t *testing.T) { t.Fatal(err) } - book.LoadSnapshot(append(bids[:0:0], bids...), append(asks[:0:0], asks...), 0, time.Time{}, true) + err = book.LoadSnapshot(append(bids[:0:0], bids...), append(asks[:0:0], asks...), 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } ob, err := book.Retrieve() if !errors.Is(err, nil) { @@ -948,7 +967,10 @@ func TestUpdateByIDAndAction(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errAmendFailure) } - book.LoadSnapshot(append(bids[:0:0], bids...), append(asks[:0:0], asks...), 0, time.Time{}, true) + err = book.LoadSnapshot(append(bids[:0:0], bids...), append(asks[:0:0], asks...), 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } // append to slice err = holder.updateByIDAndAction(&orderbook.Update{ Action: orderbook.UpdateInsert, @@ -966,6 +988,7 @@ func TestUpdateByIDAndAction(t *testing.T) { Amount: 1, }, }, + UpdateTime: time.Now(), }) if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -1000,6 +1023,7 @@ func TestUpdateByIDAndAction(t *testing.T) { Amount: 100, }, }, + UpdateTime: time.Now(), }) if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -1035,6 +1059,7 @@ func TestUpdateByIDAndAction(t *testing.T) { Amount: 99, }, }, + UpdateTime: time.Now(), }) if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -1053,7 +1078,10 @@ func TestUpdateByIDAndAction(t *testing.T) { t.Fatal("did not adjust ask item placement and details") } - book.LoadSnapshot(append(bids[:0:0], bids...), append(asks[:0:0], asks...), 0, time.Time{}, true) //nolint:gocritic + err = book.LoadSnapshot(append(bids[:0:0], bids...), append(asks[:0:0], asks...), 0, time.Now(), true) //nolint:gocritic + if err != nil { + t.Fatal(err) + } // Delete - not found err = holder.updateByIDAndAction(&orderbook.Update{ Action: orderbook.Delete, @@ -1069,13 +1097,17 @@ func TestUpdateByIDAndAction(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errDeleteFailure) } - book.LoadSnapshot(append(bids[:0:0], bids...), append(asks[:0:0], asks...), 0, time.Time{}, true) //nolint:gocritic + err = book.LoadSnapshot(append(bids[:0:0], bids...), append(asks[:0:0], asks...), 0, time.Now(), true) //nolint:gocritic + if err != nil { + t.Fatal(err) + } // Delete - found err = holder.updateByIDAndAction(&orderbook.Update{ Action: orderbook.Delete, Asks: []orderbook.Item{ asks[0], }, + UpdateTime: time.Now(), }) if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) @@ -1101,7 +1133,10 @@ func TestUpdateByIDAndAction(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, errAmendFailure) } - book.LoadSnapshot(bids, bids, 0, time.Time{}, true) + err = book.LoadSnapshot(bids, bids, 0, time.Now(), true) + if err != nil { + t.Fatal(err) + } ob, err = book.Retrieve() if !errors.Is(err, nil) { @@ -1119,6 +1154,7 @@ func TestUpdateByIDAndAction(t *testing.T) { Asks: []orderbook.Item{ update, }, + UpdateTime: time.Now(), }) if err != nil { t.Fatal(err) @@ -1154,6 +1190,7 @@ func TestFlushOrderbook(t *testing.T) { snapShot1.Bids = bids snapShot1.Asset = asset.Spot snapShot1.Pair = cp + snapShot1.LastUpdated = time.Now() err = w.FlushOrderbook(cp, asset.Spot) if err == nil { diff --git a/exchanges/zb/zb_websocket.go b/exchanges/zb/zb_websocket.go index 325f2a641c1..0ffe0228f49 100644 --- a/exchanges/zb/zb_websocket.go +++ b/exchanges/zb/zb_websocket.go @@ -133,6 +133,7 @@ func (z *ZB) wsHandleData(respRaw []byte) error { Pair: cPair, Exchange: z.Name, VerifyOrderbook: z.CanVerifyOrderbook, + LastUpdated: time.Now(), // This is temp to pass test as the API is broken. } for i := range depth.Asks {