orderbook: consolidate slice array types to orderbook package (#1992)

* orderbook: consolidate slice array types to orderbook package

* Update exchanges/bybit/bybit_types.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* linter: fix and add test

* cranktakular: nits

* cranktakular: nits

* Update exchanges/orderbook/orderbook_types.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 consolidation

* gk: rm unifySpotOrderbook func

* gk: nit but different

* linter: fix

* gk: nits

* glorious: nits

* Update exchanges/binance/binance.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update exchanges/binance/binance_cfutures.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* Update exchanges/binanceus/binanceus.go

Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>

* thrasher-:nits

* thrasher-: more nit

---------

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Gareth Kirwan <gbjkirwan@gmail.com>
Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
This commit is contained in:
Ryan O'Hara-Reid
2025-10-02 14:22:20 +10:00
committed by GitHub
parent eb60a3c40e
commit ac91fabcd5
43 changed files with 413 additions and 854 deletions

View File

@@ -24,7 +24,6 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
"github.com/thrasher-corp/gocryptotrader/types"
)
@@ -99,33 +98,6 @@ func (e *Exchange) GetMarketList(ctx context.Context) ([]string, error) {
return resp, e.SendHTTPRequest(ctx, exchange.RestSpot, marketListEPL, "/v1/markets", &resp)
}
// processOB constructs an orderbook.Level instances from slice of numbers.
func processOB(ob [][2]types.Number) []orderbook.Level {
o := make([]orderbook.Level, len(ob))
for x := range ob {
o[x].Amount = ob[x][1].Float64()
o[x].Price = ob[x][0].Float64()
}
return o
}
// constructOrderbook parse checks and constructs an *Orderbook instance from *orderbookResponse.
func constructOrderbook(o *orderbookResponse) (*Orderbook, error) {
s := Orderbook{
Bids: processOB(o.Bids),
Asks: processOB(o.Asks),
Time: o.Time.Time(),
}
if o.Sequence != "" {
var err error
s.Sequence, err = strconv.ParseInt(o.Sequence, 10, 64)
if err != nil {
return nil, err
}
}
return &s, nil
}
// GetPartOrderbook20 gets orderbook for a specified pair with depth 20
func (e *Exchange) GetPartOrderbook20(ctx context.Context, symbol string) (*Orderbook, error) {
if symbol == "" {
@@ -134,11 +106,10 @@ func (e *Exchange) GetPartOrderbook20(ctx context.Context, symbol string) (*Orde
params := url.Values{}
params.Set("symbol", symbol)
var o *orderbookResponse
err := e.SendHTTPRequest(ctx, exchange.RestSpot, partOrderbook20EPL, common.EncodeURLValues("/v1/market/orderbook/level2_20", params), &o)
if err != nil {
if err := e.SendHTTPRequest(ctx, exchange.RestSpot, partOrderbook20EPL, common.EncodeURLValues("/v1/market/orderbook/level2_20", params), &o); err != nil {
return nil, err
}
return constructOrderbook(o)
return &Orderbook{Asks: o.Asks, Bids: o.Bids, Time: o.Time.Time(), Sequence: o.Sequence.Int64()}, nil
}
// GetPartOrderbook100 gets orderbook for a specified pair with depth 100
@@ -149,11 +120,10 @@ func (e *Exchange) GetPartOrderbook100(ctx context.Context, symbol string) (*Ord
params := url.Values{}
params.Set("symbol", symbol)
var o *orderbookResponse
err := e.SendHTTPRequest(ctx, exchange.RestSpot, partOrderbook100EPL, common.EncodeURLValues("/v1/market/orderbook/level2_100", params), &o)
if err != nil {
if err := e.SendHTTPRequest(ctx, exchange.RestSpot, partOrderbook100EPL, common.EncodeURLValues("/v1/market/orderbook/level2_100", params), &o); err != nil {
return nil, err
}
return constructOrderbook(o)
return &Orderbook{Asks: o.Asks, Bids: o.Bids, Time: o.Time.Time(), Sequence: o.Sequence.Int64()}, nil
}
// GetOrderbook gets full orderbook for a specified pair
@@ -164,11 +134,10 @@ func (e *Exchange) GetOrderbook(ctx context.Context, symbol string) (*Orderbook,
params := url.Values{}
params.Set("symbol", symbol)
var o *orderbookResponse
err := e.SendAuthHTTPRequest(ctx, exchange.RestSpot, fullOrderbookEPL, http.MethodGet, common.EncodeURLValues("/v3/market/orderbook/level2", params), nil, &o)
if err != nil {
if err := e.SendAuthHTTPRequest(ctx, exchange.RestSpot, fullOrderbookEPL, http.MethodGet, common.EncodeURLValues("/v3/market/orderbook/level2", params), nil, &o); err != nil {
return nil, err
}
return constructOrderbook(o)
return &Orderbook{Asks: o.Asks, Bids: o.Bids, Time: o.Time.Time(), Sequence: o.Sequence.Int64()}, nil
}
// GetTradeHistory gets trade history of the specified pair

View File

@@ -18,7 +18,6 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/types"
)
@@ -117,12 +116,11 @@ func (e *Exchange) GetFuturesOrderbook(ctx context.Context, symbol string) (*Ord
}
params := url.Values{}
params.Set("symbol", symbol)
var o futuresOrderbookResponse
err := e.SendHTTPRequest(ctx, exchange.RestFutures, futuresOrderbookEPL, common.EncodeURLValues("/v1/level2/snapshot", params), &o)
if err != nil {
var o *futuresOrderbookResponse
if err := e.SendHTTPRequest(ctx, exchange.RestFutures, futuresOrderbookEPL, common.EncodeURLValues("/v1/level2/snapshot", params), &o); err != nil {
return nil, err
}
return constructFuturesOrderbook(&o), nil
return unifyFuturesOrderbook(o), nil
}
// GetFuturesPartOrderbook20 gets orderbook for a specified symbol with depth 20
@@ -132,12 +130,11 @@ func (e *Exchange) GetFuturesPartOrderbook20(ctx context.Context, symbol string)
}
params := url.Values{}
params.Set("symbol", symbol)
var o futuresOrderbookResponse
err := e.SendHTTPRequest(ctx, exchange.RestFutures, futuresPartOrderbookDepth20EPL, common.EncodeURLValues("/v1/level2/depth20", params), &o)
if err != nil {
var o *futuresOrderbookResponse
if err := e.SendHTTPRequest(ctx, exchange.RestFutures, futuresPartOrderbookDepth20EPL, common.EncodeURLValues("/v1/level2/depth20", params), &o); err != nil {
return nil, err
}
return constructFuturesOrderbook(&o), nil
return unifyFuturesOrderbook(o), nil
}
// GetFuturesPartOrderbook100 gets orderbook for a specified symbol with depth 100
@@ -147,12 +144,15 @@ func (e *Exchange) GetFuturesPartOrderbook100(ctx context.Context, symbol string
}
params := url.Values{}
params.Set("symbol", symbol)
var o futuresOrderbookResponse
err := e.SendHTTPRequest(ctx, exchange.RestFutures, futuresPartOrderbookDepth100EPL, common.EncodeURLValues("/v1/level2/depth100", params), &o)
if err != nil {
var o *futuresOrderbookResponse
if err := e.SendHTTPRequest(ctx, exchange.RestFutures, futuresPartOrderbookDepth100EPL, common.EncodeURLValues("/v1/level2/depth100", params), &o); err != nil {
return nil, err
}
return constructFuturesOrderbook(&o), nil
return unifyFuturesOrderbook(o), nil
}
func unifyFuturesOrderbook(o *futuresOrderbookResponse) *Orderbook {
return &Orderbook{Bids: o.Bids.Levels(), Asks: o.Asks.Levels(), Sequence: o.Sequence, Time: o.Time.Time()}
}
// GetFuturesTradeHistory get last 100 trades for symbol
@@ -822,26 +822,6 @@ func (e *Exchange) GetFuturesTransferOutList(ctx context.Context, ccy currency.C
return resp, e.SendAuthHTTPRequest(ctx, exchange.RestFutures, futuresTransferOutListEPL, http.MethodGet, common.EncodeURLValues("/v1/transfer-list", params), nil, &resp)
}
func processFuturesOB(ob [][2]float64) []orderbook.Level {
o := make([]orderbook.Level, len(ob))
for x := range ob {
o[x] = orderbook.Level{
Price: ob[x][0],
Amount: ob[x][1],
}
}
return o
}
func constructFuturesOrderbook(o *futuresOrderbookResponse) *Orderbook {
return &Orderbook{
Bids: processFuturesOB(o.Bids),
Asks: processFuturesOB(o.Asks),
Sequence: o.Sequence,
Time: o.Time.Time(),
}
}
// GetFuturesTradingPairsActualFees retrieves the actual fee rate of the trading pair. The fee rate of your sub-account is the same as that of the master account
func (e *Exchange) GetFuturesTradingPairsActualFees(ctx context.Context, symbol string) (*TradingPairFee, error) {
if symbol == "" {

View File

@@ -3,6 +3,7 @@ package kucoin
import (
"github.com/thrasher-corp/gocryptotrader/encoding/json"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/types"
)
@@ -85,11 +86,11 @@ type FuturesTicker struct {
}
type futuresOrderbookResponse struct {
Asks [][2]float64 `json:"asks"`
Bids [][2]float64 `json:"bids"`
Time types.Time `json:"ts"`
Sequence int64 `json:"sequence"`
Symbol string `json:"symbol"`
Asks orderbook.LevelsArrayPriceAmount `json:"asks"`
Bids orderbook.LevelsArrayPriceAmount `json:"bids"`
Time types.Time `json:"ts"`
Sequence int64 `json:"sequence"`
Symbol string `json:"symbol"`
}
// FuturesTrade stores trade data

View File

@@ -162,10 +162,10 @@ type Orderbook struct {
}
type orderbookResponse struct {
Asks [][2]types.Number `json:"asks"`
Bids [][2]types.Number `json:"bids"`
Time types.Time `json:"time"`
Sequence string `json:"sequence"`
Asks orderbook.LevelsArrayPriceAmount `json:"asks"`
Bids orderbook.LevelsArrayPriceAmount `json:"bids"`
Time types.Time `json:"time"`
Sequence types.Number `json:"sequence"`
}
// Trade stores trade data
@@ -1537,35 +1537,11 @@ type WsOrderbookLevel5 struct {
// WsOrderbookLevel5Response represents a response data for an orderbook push data with depth level 5
type WsOrderbookLevel5Response struct {
Sequence int64 `json:"sequence"`
Bids [][2]types.Number `json:"bids"`
Asks [][2]types.Number `json:"asks"`
PushTimestamp types.Time `json:"ts"`
Timestamp types.Time `json:"timestamp"`
}
// ExtractOrderbookItems returns WsOrderbookLevel5 instance from WsOrderbookLevel5Response
func (a *WsOrderbookLevel5Response) ExtractOrderbookItems() *WsOrderbookLevel5 {
resp := WsOrderbookLevel5{
Timestamp: a.Timestamp,
Sequence: a.Sequence,
PushTimestamp: a.PushTimestamp,
}
resp.Asks = make([]orderbook.Level, len(a.Asks))
for x := range a.Asks {
resp.Asks[x] = orderbook.Level{
Price: a.Asks[x][0].Float64(),
Amount: a.Asks[x][1].Float64(),
}
}
resp.Bids = make([]orderbook.Level, len(a.Bids))
for x := range a.Bids {
resp.Bids[x] = orderbook.Level{
Price: a.Bids[x][0].Float64(),
Amount: a.Bids[x][1].Float64(),
}
}
return &resp
Sequence int64 `json:"sequence"`
Bids orderbook.LevelsArrayPriceAmount `json:"bids"`
Asks orderbook.LevelsArrayPriceAmount `json:"asks"`
PushTimestamp types.Time `json:"ts"`
Timestamp types.Time `json:"timestamp"`
}
// WsFundingRate represents the funding rate push data information through the websocket channel
@@ -2095,9 +2071,9 @@ type HFMarginOrderTransaction struct {
// Level2Depth5Or20 stores the orderbook data for the level 5 or level 20
// orderbook
type Level2Depth5Or20 struct {
Asks [][2]types.Number `json:"asks"`
Bids [][2]types.Number `json:"bids"`
Timestamp types.Time `json:"timestamp"`
Asks orderbook.LevelsArrayPriceAmount `json:"asks"`
Bids orderbook.LevelsArrayPriceAmount `json:"bids"`
Timestamp types.Time `json:"timestamp"`
}
// TradingPairFee represents actual fee information of a trading fee

View File

@@ -533,16 +533,11 @@ func (e *Exchange) ensureFuturesOrderbookSnapshotLoaded(ctx context.Context, sym
// processFuturesOrderbookSnapshot processes a futures account orderbook websocket update.
func (e *Exchange) processFuturesOrderbookSnapshot(respData []byte, instrument string) error {
response := WsOrderbookLevel5Response{}
if err := json.Unmarshal(respData, &response); err != nil {
var resp WsOrderbookLevel5Response
if err := json.Unmarshal(respData, &resp); err != nil {
return err
}
resp := response.ExtractOrderbookItems()
enabledPairs, err := e.GetEnabledPairs(asset.Futures)
if err != nil {
return err
}
cp, err := enabledPairs.DeriveFrom(instrument)
pair, err := e.MatchSymbolWithAvailablePairs(instrument, asset.Futures, false)
if err != nil {
return err
}
@@ -550,9 +545,9 @@ func (e *Exchange) processFuturesOrderbookSnapshot(respData []byte, instrument s
UpdateID: resp.Sequence,
UpdateTime: resp.Timestamp.Time(),
Asset: asset.Futures,
Bids: resp.Bids,
Asks: resp.Asks,
Pair: cp,
Bids: resp.Bids.Levels(),
Asks: resp.Asks.Levels(),
Pair: pair,
SkipOutOfOrderLastUpdateID: true,
})
}
@@ -942,9 +937,8 @@ func (e *Exchange) updateLocalBuffer(wsdp *WsOrderbook, assetType asset.Item) (b
// processOrderbook processes orderbook data for a specific symbol.
func (e *Exchange) processOrderbook(respData []byte, symbol, topic string) error {
var response Level2Depth5Or20
err := json.Unmarshal(respData, &response)
if err != nil {
var resp Level2Depth5Or20
if err := json.Unmarshal(respData, &resp); err != nil {
return err
}
@@ -953,32 +947,20 @@ func (e *Exchange) processOrderbook(respData []byte, symbol, topic string) error
return err
}
asks := make([]orderbook.Level, len(response.Asks))
for x := range response.Asks {
asks[x].Price = response.Asks[x][0].Float64()
asks[x].Amount = response.Asks[x][1].Float64()
}
bids := make([]orderbook.Level, len(response.Bids))
for x := range response.Bids {
bids[x].Price = response.Bids[x][0].Float64()
bids[x].Amount = response.Bids[x][1].Float64()
}
assets, err := e.CalculateAssets(topic, pair)
if err != nil {
return err
}
lastUpdatedTime := response.Timestamp.Time()
if response.Timestamp.Time().IsZero() {
lastUpdatedTime := resp.Timestamp.Time()
if lastUpdatedTime.IsZero() {
lastUpdatedTime = time.Now()
}
for x := range assets {
err = e.Websocket.Orderbook.LoadSnapshot(&orderbook.Book{
Exchange: e.Name,
Asks: asks,
Bids: bids,
Asks: resp.Asks.Levels(),
Bids: resp.Bids.Levels(),
Pair: pair,
Asset: assets[x],
LastUpdated: lastUpdatedTime,
@@ -1270,9 +1252,7 @@ func (e *Exchange) SynchroniseWebsocketOrderbook(ctx context.Context) {
// SeedLocalCache seeds depth data
func (e *Exchange) SeedLocalCache(ctx context.Context, p currency.Pair, assetType asset.Item) error {
var ob *Orderbook
var err error
ob, err = e.GetPartOrderbook100(ctx, p.String())
ob, err := e.GetPartOrderbook100(ctx, p.String())
if err != nil {
return err
}
@@ -1284,29 +1264,16 @@ func (e *Exchange) SeedLocalCache(ctx context.Context, p currency.Pair, assetTyp
// SeedLocalCacheWithBook seeds the local orderbook cache
func (e *Exchange) SeedLocalCacheWithBook(p currency.Pair, orderbookNew *Orderbook, assetType asset.Item) error {
newOrderBook := orderbook.Book{
return e.Websocket.Orderbook.LoadSnapshot(&orderbook.Book{
Pair: p,
Asset: assetType,
Exchange: e.Name,
LastUpdated: time.Now(),
LastUpdated: orderbookNew.Time,
LastUpdateID: orderbookNew.Sequence,
ValidateOrderbook: e.ValidateOrderbook,
Bids: make(orderbook.Levels, len(orderbookNew.Bids)),
Asks: make(orderbook.Levels, len(orderbookNew.Asks)),
}
for i := range orderbookNew.Bids {
newOrderBook.Bids[i] = orderbook.Level{
Amount: orderbookNew.Bids[i].Amount,
Price: orderbookNew.Bids[i].Price,
}
}
for i := range orderbookNew.Asks {
newOrderBook.Asks[i] = orderbook.Level{
Amount: orderbookNew.Asks[i].Amount,
Price: orderbookNew.Asks[i].Price,
}
}
return e.Websocket.Orderbook.LoadSnapshot(&newOrderBook)
Bids: orderbookNew.Bids,
Asks: orderbookNew.Asks,
})
}
// processJob fetches and processes orderbook updates

View File

@@ -368,41 +368,42 @@ func (e *Exchange) UpdateTickers(ctx context.Context, assetType asset.Item) erro
}
// UpdateOrderbook updates and returns the orderbook for a currency pair
func (e *Exchange) UpdateOrderbook(ctx context.Context, pair currency.Pair, assetType asset.Item) (*orderbook.Book, error) {
err := e.CurrencyPairs.IsAssetEnabled(assetType)
func (e *Exchange) UpdateOrderbook(ctx context.Context, p currency.Pair, a asset.Item) (*orderbook.Book, error) {
err := e.CurrencyPairs.IsAssetEnabled(a)
if err != nil {
return nil, err
}
pair, err = e.FormatExchangeCurrency(pair, assetType)
p, err = e.FormatExchangeCurrency(p, a)
if err != nil {
return nil, err
}
var ordBook *Orderbook
switch assetType {
switch a {
case asset.Futures:
ordBook, err = e.GetFuturesOrderbook(ctx, pair.String())
ordBook, err = e.GetFuturesOrderbook(ctx, p.String())
case asset.Spot, asset.Margin:
ordBook, err = e.GetPartOrderbook100(ctx, pair.String())
ordBook, err = e.GetPartOrderbook100(ctx, p.String())
default:
return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, assetType)
return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, a)
}
if err != nil {
return nil, err
}
book := &orderbook.Book{
ob := &orderbook.Book{
Exchange: e.Name,
Pair: pair,
Asset: assetType,
Pair: p,
Asset: a,
ValidateOrderbook: e.ValidateOrderbook,
Asks: ordBook.Asks,
Bids: ordBook.Bids,
}
err = book.Process()
if err != nil {
return book, err
if err := ob.Process(); err != nil {
return nil, err
}
return orderbook.Get(e.Name, pair, assetType)
return orderbook.Get(e.Name, p, a)
}
// UpdateAccountInfo retrieves balances for all enabled currencies