Files
gocryptotrader/exchanges/gateio/ws_ob_update_manager_test.go
Ryan O'Hara-Reid b281759573 gateio: Fix websocket orderbook incremental updates (#1863)
* gateio: Add websocket orderbook update manager

* RM println

* glorious: nit

* Adds delivery futures update processing as well

* Change to const value for delivery

* Drop check out of order, can reinstate if required.

* Adds in validation methods to ensure config changes are correct when expanding templates and return errors with correct info if not.

* fix some things and add in todo when this gets updated

* fix spelling

* linter: fix

* gk: initial nits

* gk: nits shift to template only verification with funcmap, rm interface for single sub checking.

* rm unused error

* linter: fix

* update to const frequency

* gk: wrap with panic and single invocation in template, change name

* gk: nits to check across stored subs with incoming subs

* linter: fix

* updates names, makes things slightly more efficient and adds tests

* linter: fix

* gk: sexc patch v2

* glorious: nits

* gk: nits

* Update exchanges/subscription/template.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* gk: nits

* linter: make peace with linter regulations

* glorious: Add TODO for future template integration

* glorious: commentary nits

* fix name

* give me a break, have a kit kat

* revert whoops

* update wording on comment

* revert secondary call to expand templates and update tests

* misc lint: fix

* Add spot orderbook update interval for 20ms, expand tests, piggy back limit/level off loaded subscription. Thanks to @thrasher-

* linter/spell: fix

* ai nits: drop go routine on mtx RUnlock

* Update exchanges/gateio/ws_ob_update_manager.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* gk: revert to 100ms from 20ms waiting for config upgrade patch

* test: fix

* cranktakular: nits

* strings quoted in fmt call

* thrasher-: nits

* Update exchanges/gateio/gateio_test.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* Update exchanges/gateio/gateio_test.go

Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>

* gk: nits

---------

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>
2025-05-23 17:29:39 +10:00

206 lines
5.9 KiB
Go

package gateio
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchange/websocket/buffer"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/subscription"
testexch "github.com/thrasher-corp/gocryptotrader/internal/testing/exchange"
)
func TestProcessUpdate(t *testing.T) {
t.Parallel()
m := newWsOBUpdateManager(0)
err := m.ProcessUpdate(t.Context(), g, 1337, &orderbook.Update{})
assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty)
pair := currency.NewPair(currency.BABY, currency.BABYDOGE)
err = g.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{
Exchange: g.Name,
Pair: pair,
Asset: asset.USDTMarginedFutures,
Bids: []orderbook.Tranche{{Price: 1, Amount: 1}},
Asks: []orderbook.Tranche{{Price: 1, Amount: 1}},
LastUpdated: time.Now(),
UpdatePushedAt: time.Now(),
LastUpdateID: 1336,
})
require.NoError(t, err)
err = m.ProcessUpdate(t.Context(), g, 1337, &orderbook.Update{
UpdateID: 1338,
Pair: pair,
Asset: asset.USDTMarginedFutures,
AllowEmpty: true,
UpdateTime: time.Now(),
})
require.NoError(t, err)
// Test orderbook snapshot is behind update
err = m.ProcessUpdate(t.Context(), g, 1340, &orderbook.Update{
UpdateID: 1341,
Pair: pair,
Asset: asset.USDTMarginedFutures,
AllowEmpty: true,
UpdateTime: time.Now(),
})
require.NoError(t, err)
cache := m.LoadCache(pair, asset.USDTMarginedFutures)
cache.mtx.Lock()
assert.Len(t, cache.updates, 1)
assert.True(t, cache.updating)
cache.mtx.Unlock()
// Test orderbook snapshot is behind update
err = m.ProcessUpdate(t.Context(), g, 1342, &orderbook.Update{
UpdateID: 1343,
Pair: pair,
Asset: asset.USDTMarginedFutures,
AllowEmpty: true,
UpdateTime: time.Now(),
})
require.NoError(t, err)
cache.mtx.Lock()
assert.Len(t, cache.updates, 2)
assert.True(t, cache.updating)
cache.mtx.Unlock()
time.Sleep(time.Millisecond * 2) // Allow sync delay to pass
cache.mtx.Lock()
assert.Empty(t, cache.updates)
assert.False(t, cache.updating)
cache.mtx.Unlock()
}
func TestLoadCache(t *testing.T) {
t.Parallel()
m := newWsOBUpdateManager(0)
pair := currency.NewPair(currency.BABY, currency.BABYDOGE)
cache := m.LoadCache(pair, asset.USDTMarginedFutures)
assert.NotNil(t, cache)
assert.Len(t, m.lookup, 1)
// Test cache is reused
cache2 := m.LoadCache(pair, asset.USDTMarginedFutures)
assert.Equal(t, cache, cache2)
}
func TestSyncOrderbook(t *testing.T) {
t.Parallel()
g := new(Gateio)
require.NoError(t, testexch.Setup(g), "Setup must not error")
require.NoError(t, g.UpdateTradablePairs(t.Context(), false))
// Add dummy subscription so that it can be matched and a limit/level can be extracted for initial orderbook sync spot.
err := g.Websocket.AddSubscriptions(nil, &subscription.Subscription{Channel: subscription.OrderbookChannel, Interval: kline.HundredMilliseconds})
require.NoError(t, err)
m := newWsOBUpdateManager(defaultWSSnapshotSyncDelay)
for _, a := range []asset.Item{asset.Spot, asset.USDTMarginedFutures} {
pair := currency.NewPair(currency.ETH, currency.USDT)
err := g.CurrencyPairs.EnablePair(a, pair)
require.NoError(t, err)
cache := m.LoadCache(pair, a)
cache.updates = []pendingUpdate{{update: &orderbook.Update{Pair: pair, Asset: a}}}
cache.updating = true
err = cache.SyncOrderbook(t.Context(), g, pair, a)
require.NoError(t, err)
require.False(t, cache.updating)
require.Empty(t, cache.updates)
expectedLimit := 20
if a == asset.Spot {
expectedLimit = 100
}
b, err := g.Websocket.Orderbook.GetOrderbook(pair, a)
require.NoError(t, err)
require.Len(t, b.Bids, expectedLimit)
require.Len(t, b.Asks, expectedLimit)
}
}
func TestApplyPendingUpdates(t *testing.T) {
t.Parallel()
g := new(Gateio)
require.NoError(t, testexch.Setup(g), "Setup must not error")
require.NoError(t, g.UpdateTradablePairs(t.Context(), false))
m := newWsOBUpdateManager(defaultWSSnapshotSyncDelay)
pair := currency.NewPair(currency.LTC, currency.USDT)
err := g.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{
Exchange: g.Name,
Pair: pair,
Asset: asset.USDTMarginedFutures,
Bids: []orderbook.Tranche{{Price: 1, Amount: 1}},
Asks: []orderbook.Tranche{{Price: 1, Amount: 1}},
LastUpdated: time.Now(),
UpdatePushedAt: time.Now(),
LastUpdateID: 1335,
})
require.NoError(t, err)
cache := m.LoadCache(pair, asset.USDTMarginedFutures)
update := &orderbook.Update{
UpdateID: 1339,
Pair: pair,
Asset: asset.USDTMarginedFutures,
AllowEmpty: true,
UpdateTime: time.Now(),
}
cache.updates = []pendingUpdate{{update: update, firstUpdateID: 1337}}
err = cache.applyPendingUpdates(g, asset.USDTMarginedFutures)
require.ErrorIs(t, err, errOrderbookSnapshotOutdated)
cache.updates[0].firstUpdateID = 1336
err = cache.applyPendingUpdates(g, asset.USDTMarginedFutures)
require.NoError(t, err)
}
func TestApplyOrderbookUpdate(t *testing.T) {
t.Parallel()
g := new(Gateio)
require.NoError(t, testexch.Setup(g), "Setup must not error")
require.NoError(t, g.UpdateTradablePairs(t.Context(), false))
pair := currency.NewBTCUSDT()
update := &orderbook.Update{
Pair: pair,
Asset: asset.USDTMarginedFutures,
AllowEmpty: true,
UpdateTime: time.Now(),
}
err := applyOrderbookUpdate(g, update)
require.ErrorIs(t, err, buffer.ErrDepthNotFound)
update.Asset = asset.Spot
err = applyOrderbookUpdate(g, update)
require.ErrorIs(t, err, buffer.ErrDepthNotFound)
update.Pair = currency.NewPair(currency.BABY, currency.BABYDOGE)
err = applyOrderbookUpdate(g, update)
require.NoError(t, err)
}