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 <ryan.oharareid@thrasher.io>
This commit is contained in:
Ryan O'Hara-Reid
2023-09-07 11:00:16 +10:00
committed by GitHub
parent 20143886ca
commit ad9de19d47
31 changed files with 726 additions and 327 deletions

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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{

View File

@@ -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)

View File

@@ -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{

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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",

View File

@@ -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"`
}

View File

@@ -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

View File

@@ -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}}

View File

@@ -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

View File

@@ -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"`
}

View File

@@ -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,
})
}

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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"))
testReason := errors.New("random reason")
err = d.Invalidate(testReason)
if !errors.Is(err, ErrOrderbookInvalid) {
t.Fatalf("received: '%v' but expected: '%v'", err, ErrOrderbookInvalid)
}
_, err = d.Retrieve()
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")
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)
}
err := d.InsertBidAskByID(updates)
if !strings.Contains(err.Error(), errCollisionDetected.Error()) {
updates.UpdateTime = time.Now()
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) {

View File

@@ -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)
}

View File

@@ -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) {

View File

@@ -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{}

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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}}

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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 {