diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index a6b137db..59a7354b 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -118,40 +118,17 @@ func (e *Exchange) GetExchangeInfo(ctx context.Context) (ExchangeInfo, error) { } // GetOrderBook returns full orderbook information -// -// OrderBookDataRequestParams contains the following members -// symbol: string of currency pair -// limit: returned limit amount -func (e *Exchange) GetOrderBook(ctx context.Context, obd OrderBookDataRequestParams) (*OrderBook, error) { - params := url.Values{} - symbol, err := e.FormatSymbol(obd.Symbol, asset.Spot) +func (e *Exchange) GetOrderBook(ctx context.Context, pair currency.Pair, limit uint64) (*OrderBookResponse, error) { + symbol, err := e.FormatSymbol(pair, asset.Spot) if err != nil { return nil, err } + params := url.Values{} params.Set("symbol", symbol) - params.Set("limit", strconv.Itoa(obd.Limit)) + params.Set("limit", strconv.FormatUint(limit, 10)) - var resp *OrderBookData - if err := e.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, common.EncodeURLValues(orderBookDepth, params), orderbookLimit(obd.Limit), &resp); err != nil { - return nil, err - } - - ob := &OrderBook{ - Bids: make([]OrderbookItem, len(resp.Bids)), - Asks: make([]OrderbookItem, len(resp.Asks)), - LastUpdateID: resp.LastUpdateID, - } - for x := range resp.Bids { - ob.Bids[x].Price = resp.Bids[x][0].Float64() - ob.Bids[x].Quantity = resp.Bids[x][1].Float64() - } - - for x := range resp.Asks { - ob.Asks[x].Price = resp.Asks[x][0].Float64() - ob.Asks[x].Quantity = resp.Asks[x][1].Float64() - } - - return ob, nil + var resp *OrderBookResponse + return resp, e.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, common.EncodeURLValues(orderBookDepth, params), orderbookLimit(limit), &resp) } // GetMostRecentTrades returns recent trade activity diff --git a/exchanges/binance/binance_cfutures.go b/exchanges/binance/binance_cfutures.go index a38dcf9e..2de456d5 100644 --- a/exchanges/binance/binance_cfutures.go +++ b/exchanges/binance/binance_cfutures.go @@ -84,7 +84,7 @@ func (e *Exchange) FuturesExchangeInfo(ctx context.Context) (CExchangeInfo, erro } // GetFuturesOrderbook gets orderbook data for CoinMarginedFutures, -func (e *Exchange) GetFuturesOrderbook(ctx context.Context, symbol currency.Pair, limit int64) (*OrderBook, error) { +func (e *Exchange) GetFuturesOrderbook(ctx context.Context, symbol currency.Pair, limit uint64) (*OrderBookResponse, error) { symbolValue, err := e.FormatSymbol(symbol, asset.CoinMarginedFutures) if err != nil { return nil, err @@ -93,7 +93,7 @@ func (e *Exchange) GetFuturesOrderbook(ctx context.Context, symbol currency.Pair params := url.Values{} params.Set("symbol", symbolValue) if limit > 0 { - params.Set("limit", strconv.FormatInt(limit, 10)) + params.Set("limit", strconv.FormatUint(limit, 10)) } rateBudget := cFuturesOrderbook1000Rate @@ -106,27 +106,8 @@ func (e *Exchange) GetFuturesOrderbook(ctx context.Context, symbol currency.Pair rateBudget = cFuturesOrderbook500Rate } - var data *OrderbookData - if err := e.SendHTTPRequest(ctx, exchange.RestCoinMargined, cfuturesOrderbook+params.Encode(), rateBudget, &data); err != nil { - return nil, err - } - - ob := &OrderBook{ - Bids: make([]OrderbookItem, len(data.Bids)), - Asks: make([]OrderbookItem, len(data.Asks)), - } - - for x := range data.Bids { - ob.Bids[x].Price = data.Bids[x][0].Float64() - ob.Bids[x].Quantity = data.Bids[x][1].Float64() - } - - for x := range data.Asks { - ob.Asks[x].Price = data.Asks[x][0].Float64() - ob.Asks[x].Quantity = data.Asks[x][1].Float64() - } - - return ob, nil + var resp *OrderBookResponse + return resp, e.SendHTTPRequest(ctx, exchange.RestCoinMargined, cfuturesOrderbook+params.Encode(), rateBudget, &resp) } // GetFuturesPublicTrades gets recent public trades for CoinMarginedFutures, diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index 75053a81..64a06277 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -25,6 +25,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/margin" "github.com/thrasher-corp/gocryptotrader/exchanges/order" + "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/subscription" testexch "github.com/thrasher-corp/gocryptotrader/internal/testing/exchange" @@ -1120,14 +1121,8 @@ func TestFetchTradablePairs(t *testing.T) { func TestGetOrderBook(t *testing.T) { t.Parallel() - _, err := e.GetOrderBook(t.Context(), - OrderBookDataRequestParams{ - Symbol: currency.NewBTCUSDT(), - Limit: 1000, - }) - if err != nil { - t.Error("Binance GetOrderBook() error", err) - } + _, err := e.GetOrderBook(t.Context(), currency.NewBTCUSDT(), 1000) + assert.NoError(t, err) } func TestGetMostRecentTrades(t *testing.T) { @@ -2102,30 +2097,30 @@ func TestWsDepthUpdate(t *testing.T) { require.NoError(t, testexch.Setup(e), "Test instance Setup must not error") e.setupOrderbookManager(t.Context()) seedLastUpdateID := int64(161) - book := OrderBook{ - Asks: []OrderbookItem{ - {Price: 6621.80000000, Quantity: 0.00198100}, - {Price: 6622.14000000, Quantity: 4.00000000}, - {Price: 6622.46000000, Quantity: 2.30000000}, - {Price: 6622.47000000, Quantity: 1.18633300}, - {Price: 6622.64000000, Quantity: 4.00000000}, - {Price: 6622.73000000, Quantity: 0.02900000}, - {Price: 6622.76000000, Quantity: 0.12557700}, - {Price: 6622.81000000, Quantity: 2.08994200}, - {Price: 6622.82000000, Quantity: 0.01500000}, - {Price: 6623.17000000, Quantity: 0.16831300}, + book := OrderBookResponse{ + Asks: []orderbook.Level{ + {Price: 6621.80000000, Amount: 0.00198100}, + {Price: 6622.14000000, Amount: 4.00000000}, + {Price: 6622.46000000, Amount: 2.30000000}, + {Price: 6622.47000000, Amount: 1.18633300}, + {Price: 6622.64000000, Amount: 4.00000000}, + {Price: 6622.73000000, Amount: 0.02900000}, + {Price: 6622.76000000, Amount: 0.12557700}, + {Price: 6622.81000000, Amount: 2.08994200}, + {Price: 6622.82000000, Amount: 0.01500000}, + {Price: 6623.17000000, Amount: 0.16831300}, }, - Bids: []OrderbookItem{ - {Price: 6621.55000000, Quantity: 0.16356700}, - {Price: 6621.45000000, Quantity: 0.16352600}, - {Price: 6621.41000000, Quantity: 0.86091200}, - {Price: 6621.25000000, Quantity: 0.16914100}, - {Price: 6621.23000000, Quantity: 0.09193600}, - {Price: 6621.22000000, Quantity: 0.00755100}, - {Price: 6621.13000000, Quantity: 0.08432000}, - {Price: 6621.03000000, Quantity: 0.00172000}, - {Price: 6620.94000000, Quantity: 0.30506700}, - {Price: 6620.93000000, Quantity: 0.00200000}, + Bids: []orderbook.Level{ + {Price: 6621.55000000, Amount: 0.16356700}, + {Price: 6621.45000000, Amount: 0.16352600}, + {Price: 6621.41000000, Amount: 0.86091200}, + {Price: 6621.25000000, Amount: 0.16914100}, + {Price: 6621.23000000, Amount: 0.09193600}, + {Price: 6621.22000000, Amount: 0.00755100}, + {Price: 6621.13000000, Amount: 0.08432000}, + {Price: 6621.03000000, Amount: 0.00172000}, + {Price: 6620.94000000, Amount: 0.30506700}, + {Price: 6620.93000000, Amount: 0.00200000}, }, LastUpdateID: seedLastUpdateID, } diff --git a/exchanges/binance/binance_types.go b/exchanges/binance/binance_types.go index e4bd1bb2..8e3ef17b 100644 --- a/exchanges/binance/binance_types.go +++ b/exchanges/binance/binance_types.go @@ -8,6 +8,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/encoding/json" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/types" ) @@ -123,35 +124,14 @@ type CoinInfo struct { Withdrawing float64 `json:"withdrawing,string"` } -// OrderBookDataRequestParams represents Klines request data. -type OrderBookDataRequestParams struct { - Symbol currency.Pair `json:"symbol"` // Required field; example LTCBTC,BTCUSDT - Limit int `json:"limit"` // Default 100; max 5000. If limit > 5000, then the response will truncate to 5000 -} - -// OrderbookItem stores an individual orderbook item -type OrderbookItem struct { - Price float64 - Quantity float64 -} - -// OrderBookData is resp data from orderbook endpoint -type OrderBookData struct { - Code int `json:"code"` - Msg string `json:"msg"` - LastUpdateID int64 `json:"lastUpdateId"` - Bids [][2]types.Number `json:"bids"` - Asks [][2]types.Number `json:"asks"` -} - -// OrderBook actual structured data that can be used for orderbook -type OrderBook struct { - Symbol string - LastUpdateID int64 - Code int - Msg string - Bids []OrderbookItem - Asks []OrderbookItem +// OrderBookResponse is resp data from orderbook endpoint +type OrderBookResponse struct { + Code int64 `json:"code"` + Msg string `json:"msg"` + LastUpdateID int64 `json:"lastUpdateId"` + Timestamp types.Time `json:"T"` + Bids orderbook.LevelsArrayPriceAmount `json:"bids"` + Asks orderbook.LevelsArrayPriceAmount `json:"asks"` } // DepthUpdateParams is used as an embedded type for WebsocketDepthStream @@ -163,13 +143,13 @@ type DepthUpdateParams []struct { // WebsocketDepthStream is the difference for the update depth stream type WebsocketDepthStream struct { - Event string `json:"e"` - Timestamp types.Time `json:"E"` - Pair string `json:"s"` - FirstUpdateID int64 `json:"U"` - LastUpdateID int64 `json:"u"` - UpdateBids [][2]types.Number `json:"b"` - UpdateAsks [][2]types.Number `json:"a"` + Event string `json:"e"` + Timestamp types.Time `json:"E"` + Pair string `json:"s"` + FirstUpdateID int64 `json:"U"` + LastUpdateID int64 `json:"u"` + UpdateBids orderbook.LevelsArrayPriceAmount `json:"b"` + UpdateAsks orderbook.LevelsArrayPriceAmount `json:"a"` } // RecentTradeRequestParams represents Klines request data. diff --git a/exchanges/binance/binance_ufutures.go b/exchanges/binance/binance_ufutures.go index 6401a393..52f68cc1 100644 --- a/exchanges/binance/binance_ufutures.go +++ b/exchanges/binance/binance_ufutures.go @@ -88,7 +88,7 @@ func (e *Exchange) UExchangeInfo(ctx context.Context) (UFuturesExchangeInfo, err } // UFuturesOrderbook gets orderbook data for usdt margined futures -func (e *Exchange) UFuturesOrderbook(ctx context.Context, symbol currency.Pair, limit int64) (*OrderBook, error) { +func (e *Exchange) UFuturesOrderbook(ctx context.Context, symbol currency.Pair, limit int64) (*OrderBookResponse, error) { symbolValue, err := e.FormatSymbol(symbol, asset.USDTMarginedFutures) if err != nil { return nil, err @@ -114,27 +114,8 @@ func (e *Exchange) UFuturesOrderbook(ctx context.Context, symbol currency.Pair, rateBudget = uFuturesOrderbook500Rate } - var data *OrderbookData - if err := e.SendHTTPRequest(ctx, exchange.RestUSDTMargined, ufuturesOrderbook+params.Encode(), rateBudget, &data); err != nil { - return nil, err - } - - ob := &OrderBook{ - Symbol: symbolValue, - LastUpdateID: data.LastUpdateID, - Bids: make([]OrderbookItem, len(data.Bids)), - Asks: make([]OrderbookItem, len(data.Asks)), - } - - for x := range data.Asks { - ob.Asks[x].Price = data.Asks[x][0].Float64() - ob.Asks[x].Quantity = data.Asks[x][1].Float64() - } - for x := range data.Bids { - ob.Bids[x].Price = data.Bids[x][0].Float64() - ob.Bids[x].Quantity = data.Bids[x][1].Float64() - } - return ob, nil + var resp *OrderBookResponse + return resp, e.SendHTTPRequest(ctx, exchange.RestUSDTMargined, ufuturesOrderbook+params.Encode(), rateBudget, &resp) } // URecentTrades gets recent trades for usdt margined futures diff --git a/exchanges/binance/binance_websocket.go b/exchanges/binance/binance_websocket.go index ffa4af28..2a47a9ba 100644 --- a/exchanges/binance/binance_websocket.go +++ b/exchanges/binance/binance_websocket.go @@ -449,11 +449,7 @@ func stringToOrderStatus(status string) (order.Status, error) { // SeedLocalCache seeds depth data func (e *Exchange) SeedLocalCache(ctx context.Context, p currency.Pair) error { - ob, err := e.GetOrderBook(ctx, - OrderBookDataRequestParams{ - Symbol: p, - Limit: 1000, - }) + ob, err := e.GetOrderBook(ctx, p, 1000) if err != nil { return err } @@ -461,28 +457,20 @@ func (e *Exchange) SeedLocalCache(ctx context.Context, p currency.Pair) error { } // SeedLocalCacheWithBook seeds the local orderbook cache -func (e *Exchange) SeedLocalCacheWithBook(p currency.Pair, orderbookNew *OrderBook) error { +func (e *Exchange) SeedLocalCacheWithBook(p currency.Pair, orderbookNew *OrderBookResponse) error { + t := orderbookNew.Timestamp.Time() + if t.IsZero() { + t = time.Now() // Time not provided for this REST book. + } newOrderBook := orderbook.Book{ Pair: p, Asset: asset.Spot, Exchange: e.Name, LastUpdateID: orderbookNew.LastUpdateID, ValidateOrderbook: e.ValidateOrderbook, - Bids: make(orderbook.Levels, len(orderbookNew.Bids)), - Asks: make(orderbook.Levels, len(orderbookNew.Asks)), - LastUpdated: time.Now(), // Time not provided in REST book. - } - for i := range orderbookNew.Bids { - newOrderBook.Bids[i] = orderbook.Level{ - Amount: orderbookNew.Bids[i].Quantity, - Price: orderbookNew.Bids[i].Price, - } - } - for i := range orderbookNew.Asks { - newOrderBook.Asks[i] = orderbook.Level{ - Amount: orderbookNew.Asks[i].Quantity, - Price: orderbookNew.Asks[i].Price, - } + Bids: orderbookNew.Bids.Levels(), + Asks: orderbookNew.Asks.Levels(), + LastUpdated: t, } return e.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } @@ -618,23 +606,9 @@ func (e *Exchange) manageSubs(ctx context.Context, op string, subs subscription. // ProcessOrderbookUpdate processes the websocket orderbook update func (e *Exchange) ProcessOrderbookUpdate(cp currency.Pair, a asset.Item, ws *WebsocketDepthStream) error { - updateBid := make([]orderbook.Level, len(ws.UpdateBids)) - for i := range ws.UpdateBids { - updateBid[i] = orderbook.Level{ - Price: ws.UpdateBids[i][0].Float64(), - Amount: ws.UpdateBids[i][1].Float64(), - } - } - updateAsk := make([]orderbook.Level, len(ws.UpdateAsks)) - for i := range ws.UpdateAsks { - updateAsk[i] = orderbook.Level{ - Price: ws.UpdateAsks[i][0].Float64(), - Amount: ws.UpdateAsks[i][1].Float64(), - } - } return e.Websocket.Orderbook.Update(&orderbook.Update{ - Bids: updateBid, - Asks: updateAsk, + Bids: ws.UpdateBids.Levels(), + Asks: ws.UpdateAsks.Levels(), Pair: cp, UpdateID: ws.LastUpdateID, UpdateTime: ws.Timestamp.Time(), diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index 0e4a5cf0..02c8936a 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -512,60 +512,44 @@ func (e *Exchange) UpdateTicker(ctx context.Context, p currency.Pair, a asset.It } // UpdateOrderbook updates and returns the orderbook for a currency pair -func (e *Exchange) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType asset.Item) (*orderbook.Book, error) { +func (e *Exchange) UpdateOrderbook(ctx context.Context, p currency.Pair, a asset.Item) (*orderbook.Book, error) { if p.IsEmpty() { return nil, currency.ErrCurrencyPairEmpty } - if err := e.CurrencyPairs.IsAssetEnabled(assetType); err != nil { + if err := e.CurrencyPairs.IsAssetEnabled(a); err != nil { return nil, err } - book := &orderbook.Book{ - Exchange: e.Name, - Pair: p, - Asset: assetType, - ValidateOrderbook: e.ValidateOrderbook, - } - var orderbookNew *OrderBook - var err error - switch assetType { + var orderbookNew *OrderBookResponse + var err error + switch a { case asset.Spot, asset.Margin: - orderbookNew, err = e.GetOrderBook(ctx, - OrderBookDataRequestParams{ - Symbol: p, - Limit: 1000, - }) + orderbookNew, err = e.GetOrderBook(ctx, p, 1000) case asset.USDTMarginedFutures: orderbookNew, err = e.UFuturesOrderbook(ctx, p, 1000) case asset.CoinMarginedFutures: orderbookNew, err = e.GetFuturesOrderbook(ctx, p, 1000) default: - return nil, fmt.Errorf("[%s] %w", assetType, asset.ErrNotSupported) + return nil, fmt.Errorf("[%s] %w", a, asset.ErrNotSupported) } if err != nil { - return book, err + return nil, err } - book.Bids = make(orderbook.Levels, len(orderbookNew.Bids)) - for x := range orderbookNew.Bids { - book.Bids[x] = orderbook.Level{ - Amount: orderbookNew.Bids[x].Quantity, - Price: orderbookNew.Bids[x].Price, - } - } - book.Asks = make(orderbook.Levels, len(orderbookNew.Asks)) - for x := range orderbookNew.Asks { - book.Asks[x] = orderbook.Level{ - Amount: orderbookNew.Asks[x].Quantity, - Price: orderbookNew.Asks[x].Price, - } + ob := &orderbook.Book{ + Exchange: e.Name, + Pair: p, + Asset: a, + ValidateOrderbook: e.ValidateOrderbook, + Bids: orderbookNew.Bids.Levels(), + Asks: orderbookNew.Asks.Levels(), } - err = book.Process() - if err != nil { - return book, err + if err := ob.Process(); err != nil { + return nil, err } - return orderbook.Get(e.Name, p, assetType) + + return orderbook.Get(e.Name, p, a) } // UpdateAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/binance/ratelimit.go b/exchanges/binance/ratelimit.go index 332075ac..c98a7601 100644 --- a/exchanges/binance/ratelimit.go +++ b/exchanges/binance/ratelimit.go @@ -181,7 +181,7 @@ func openOrdersLimit(symbol string) request.EndpointLimit { return spotOpenOrdersSpecificRate } -func orderbookLimit(depth int) request.EndpointLimit { +func orderbookLimit(depth uint64) request.EndpointLimit { switch { case depth <= 100: return spotOrderbookDepth100Rate diff --git a/exchanges/binance/ufutures_types.go b/exchanges/binance/ufutures_types.go index 59cf5b57..469f7b31 100644 --- a/exchanges/binance/ufutures_types.go +++ b/exchanges/binance/ufutures_types.go @@ -38,16 +38,6 @@ var ( uValidPeriods = []string{"5m", "15m", "30m", "1h", "2h", "4h", "6h", "12h", "1d"} ) -// USDT Margined Futures - -// OrderbookData stores ob data for umargined and cmargined futures -type OrderbookData struct { - LastUpdateID int64 `json:"lastUpdateID"` - Timestamp types.Time `json:"T"` - Bids [][2]types.Number `json:"bids"` - Asks [][2]types.Number `json:"asks"` -} - // UPublicTradesData stores trade data type UPublicTradesData struct { ID int64 `json:"id"` diff --git a/exchanges/binanceus/binanceus.go b/exchanges/binanceus/binanceus.go index 5da78607..06f516d8 100644 --- a/exchanges/binanceus/binanceus.go +++ b/exchanges/binanceus/binanceus.go @@ -318,34 +318,25 @@ outer: } // GetOrderBookDepth to get the order book depth. Please note the limits in the table below. -func (e *Exchange) GetOrderBookDepth(ctx context.Context, arg *OrderBookDataRequestParams) (*OrderBook, error) { +func (e *Exchange) GetOrderBookDepth(ctx context.Context, pair currency.Pair, limit uint64) (*OrderBook, error) { params := url.Values{} - symbol, err := e.FormatSymbol(arg.Symbol, asset.Spot) + symbol, err := e.FormatSymbol(pair, asset.Spot) if err != nil { return nil, err } params.Set("symbol", symbol) - params.Set("limit", strconv.FormatInt(arg.Limit, 10)) + params.Set("limit", strconv.FormatUint(limit, 10)) var resp *OrderBookData - if err := e.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, common.EncodeURLValues(orderBookDepth, params), orderbookLimit(arg.Limit), &resp); err != nil { + if err := e.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, common.EncodeURLValues(orderBookDepth, params), orderbookLimit(limit), &resp); err != nil { return nil, err } - ob := &OrderBook{ - Bids: make([]OrderbookItem, len(resp.Bids)), - Asks: make([]OrderbookItem, len(resp.Asks)), + return &OrderBook{ + Bids: resp.Bids.Levels(), + Asks: resp.Asks.Levels(), LastUpdateID: resp.LastUpdateID, - } - for x := range resp.Bids { - ob.Bids[x].Price = resp.Bids[x][0].Float64() - ob.Bids[x].Quantity = resp.Bids[x][1].Float64() - } - for x := range resp.Asks { - ob.Asks[x].Price = resp.Asks[x][0].Float64() - ob.Asks[x].Quantity = resp.Asks[x][1].Float64() - } - return ob, nil + }, nil } // GetIntervalEnum allowed interval params by Binanceus diff --git a/exchanges/binanceus/binanceus_test.go b/exchanges/binanceus/binanceus_test.go index 89baa09c..ce7f6b3c 100644 --- a/exchanges/binanceus/binanceus_test.go +++ b/exchanges/binanceus/binanceus_test.go @@ -18,6 +18,7 @@ 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/sharedtestvalues" testexch "github.com/thrasher-corp/gocryptotrader/internal/testing/exchange" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" @@ -448,13 +449,8 @@ func TestGetAggregateTrades(t *testing.T) { func TestGetOrderBookDepth(t *testing.T) { t.Parallel() - _, er := e.GetOrderBookDepth(t.Context(), &OrderBookDataRequestParams{ - Symbol: currency.NewBTCUSDT(), - Limit: 1000, - }) - if er != nil { - t.Error("Binanceus GetOrderBook() error", er) - } + _, err := e.GetOrderBookDepth(t.Context(), currency.NewBTCUSDT(), 1000) + assert.NoError(t, err) } func TestGetCandlestickData(t *testing.T) { @@ -1338,29 +1334,29 @@ func TestWebsocketOrderBookDepthDiffStream(t *testing.T) { e.setupOrderbookManager(t.Context()) seedLastUpdateID := int64(161) book := OrderBook{ - Asks: []OrderbookItem{ - {Price: 6621.80000000, Quantity: 0.00198100}, - {Price: 6622.14000000, Quantity: 4.00000000}, - {Price: 6622.46000000, Quantity: 2.30000000}, - {Price: 6622.47000000, Quantity: 1.18633300}, - {Price: 6622.64000000, Quantity: 4.00000000}, - {Price: 6622.73000000, Quantity: 0.02900000}, - {Price: 6622.76000000, Quantity: 0.12557700}, - {Price: 6622.81000000, Quantity: 2.08994200}, - {Price: 6622.82000000, Quantity: 0.01500000}, - {Price: 6623.17000000, Quantity: 0.16831300}, + Asks: []orderbook.Level{ + {Price: 6621.80000000, Amount: 0.00198100}, + {Price: 6622.14000000, Amount: 4.00000000}, + {Price: 6622.46000000, Amount: 2.30000000}, + {Price: 6622.47000000, Amount: 1.18633300}, + {Price: 6622.64000000, Amount: 4.00000000}, + {Price: 6622.73000000, Amount: 0.02900000}, + {Price: 6622.76000000, Amount: 0.12557700}, + {Price: 6622.81000000, Amount: 2.08994200}, + {Price: 6622.82000000, Amount: 0.01500000}, + {Price: 6623.17000000, Amount: 0.16831300}, }, - Bids: []OrderbookItem{ - {Price: 6621.55000000, Quantity: 0.16356700}, - {Price: 6621.45000000, Quantity: 0.16352600}, - {Price: 6621.41000000, Quantity: 0.86091200}, - {Price: 6621.25000000, Quantity: 0.16914100}, - {Price: 6621.23000000, Quantity: 0.09193600}, - {Price: 6621.22000000, Quantity: 0.00755100}, - {Price: 6621.13000000, Quantity: 0.08432000}, - {Price: 6621.03000000, Quantity: 0.00172000}, - {Price: 6620.94000000, Quantity: 0.30506700}, - {Price: 6620.93000000, Quantity: 0.00200000}, + Bids: []orderbook.Level{ + {Price: 6621.55000000, Amount: 0.16356700}, + {Price: 6621.45000000, Amount: 0.16352600}, + {Price: 6621.41000000, Amount: 0.86091200}, + {Price: 6621.25000000, Amount: 0.16914100}, + {Price: 6621.23000000, Amount: 0.09193600}, + {Price: 6621.22000000, Amount: 0.00755100}, + {Price: 6621.13000000, Amount: 0.08432000}, + {Price: 6621.03000000, Amount: 0.00172000}, + {Price: 6620.94000000, Amount: 0.30506700}, + {Price: 6620.93000000, Amount: 0.00200000}, }, LastUpdateID: seedLastUpdateID, } diff --git a/exchanges/binanceus/binanceus_types.go b/exchanges/binanceus/binanceus_types.go index a8743f4e..57e4a993 100644 --- a/exchanges/binanceus/binanceus_types.go +++ b/exchanges/binanceus/binanceus_types.go @@ -10,6 +10,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/encoding/json" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" + "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/trade" "github.com/thrasher-corp/gocryptotrader/types" ) @@ -161,23 +162,11 @@ func (a *AggregatedTrade) toTradeData(p currency.Pair, exchange string, aType as } } -// OrderBookDataRequestParams represents Klines request data. -type OrderBookDataRequestParams struct { - Symbol currency.Pair `json:"symbol"` // Required field; example LTCBTC,BTCUSDT - Limit int64 `json:"limit"` // Default 100; max 5000. If limit > 5000, then the response will truncate to 5000 -} - -// OrderbookItem stores an individual orderbook item -type OrderbookItem struct { - Price float64 - Quantity float64 -} - // OrderBookData is resp data from orderbook endpoint type OrderBookData struct { - LastUpdateID int64 `json:"lastUpdateId"` - Bids [][2]types.Number `json:"bids"` - Asks [][2]types.Number `json:"asks"` + LastUpdateID int64 `json:"lastUpdateId"` + Bids orderbook.LevelsArrayPriceAmount `json:"bids"` + Asks orderbook.LevelsArrayPriceAmount `json:"asks"` } // OrderBook actual structured data that can be used for orderbook @@ -186,8 +175,8 @@ type OrderBook struct { LastUpdateID int64 Code int Msg string - Bids []OrderbookItem - Asks []OrderbookItem + Bids []orderbook.Level + Asks []orderbook.Level } // KlinesRequestParams represents Klines request data. @@ -833,20 +822,20 @@ type update struct { // WebsocketDepthStream is the difference for the update depth stream type WebsocketDepthStream struct { - Event string `json:"e"` - Timestamp types.Time `json:"E"` - Pair string `json:"s"` - FirstUpdateID int64 `json:"U"` - LastUpdateID int64 `json:"u"` - UpdateBids [][2]types.Number `json:"b"` - UpdateAsks [][2]types.Number `json:"a"` + Event string `json:"e"` + Timestamp types.Time `json:"E"` + Pair string `json:"s"` + FirstUpdateID int64 `json:"U"` + LastUpdateID int64 `json:"u"` + UpdateBids orderbook.LevelsArrayPriceAmount `json:"b"` + UpdateAsks orderbook.LevelsArrayPriceAmount `json:"a"` } // WebsocketDepthDiffStream websocket response of depth diff stream type WebsocketDepthDiffStream struct { - LastUpdateID int64 `json:"lastUpdateId"` - Bids [][2]types.Number `json:"bids"` - Asks [][2]types.Number `json:"asks"` + LastUpdateID int64 `json:"lastUpdateId"` + Bids orderbook.LevelsArrayPriceAmount `json:"bids"` + Asks orderbook.LevelsArrayPriceAmount `json:"asks"` } // WsAccountInfoData defines websocket account info data diff --git a/exchanges/binanceus/binanceus_websocket.go b/exchanges/binanceus/binanceus_websocket.go index 1f28e571..f050d1ac 100644 --- a/exchanges/binanceus/binanceus_websocket.go +++ b/exchanges/binanceus/binanceus_websocket.go @@ -654,21 +654,9 @@ func (e *Exchange) SynchroniseWebsocketOrderbook(ctx context.Context) { // ProcessOrderbookUpdate processes the websocket orderbook update func (e *Exchange) ProcessOrderbookUpdate(cp currency.Pair, a asset.Item, wsDSUpdate *WebsocketDepthStream) error { - updateBid := make([]orderbook.Level, len(wsDSUpdate.UpdateBids)) - for i := range wsDSUpdate.UpdateBids { - updateBid[i].Price = wsDSUpdate.UpdateBids[i][0].Float64() - updateBid[i].Amount = wsDSUpdate.UpdateBids[i][1].Float64() - } - - updateAsk := make([]orderbook.Level, len(wsDSUpdate.UpdateAsks)) - for i := range wsDSUpdate.UpdateAsks { - updateAsk[i].Price = wsDSUpdate.UpdateAsks[i][0].Float64() - updateAsk[i].Amount = wsDSUpdate.UpdateAsks[i][1].Float64() - } - return e.Websocket.Orderbook.Update(&orderbook.Update{ - Bids: updateBid, - Asks: updateAsk, + Bids: wsDSUpdate.UpdateBids.Levels(), + Asks: wsDSUpdate.UpdateAsks.Levels(), Pair: cp, UpdateID: wsDSUpdate.LastUpdateID, UpdateTime: wsDSUpdate.Timestamp.Time(), @@ -789,11 +777,7 @@ func (e *Exchange) processJob(ctx context.Context, p currency.Pair) error { // SeedLocalCache seeds depth data func (e *Exchange) SeedLocalCache(ctx context.Context, p currency.Pair) error { - ob, err := e.GetOrderBookDepth(ctx, - &OrderBookDataRequestParams{ - Symbol: p, - Limit: 1000, - }) + ob, err := e.GetOrderBookDepth(ctx, p, 1000) if err != nil { return err } @@ -802,29 +786,16 @@ func (e *Exchange) SeedLocalCache(ctx context.Context, p currency.Pair) error { // SeedLocalCacheWithBook seeds the local orderbook cache func (e *Exchange) SeedLocalCacheWithBook(p currency.Pair, orderbookNew *OrderBook) error { - newOrderBook := orderbook.Book{ + return e.Websocket.Orderbook.LoadSnapshot(&orderbook.Book{ Pair: p, Asset: asset.Spot, Exchange: e.Name, LastUpdateID: orderbookNew.LastUpdateID, ValidateOrderbook: e.ValidateOrderbook, - Bids: make(orderbook.Levels, len(orderbookNew.Bids)), - Asks: make(orderbook.Levels, len(orderbookNew.Asks)), + Bids: orderbookNew.Bids, + Asks: orderbookNew.Asks, LastUpdated: time.Now(), // Time not provided in REST book. - } - for i := range orderbookNew.Bids { - newOrderBook.Bids[i] = orderbook.Level{ - Amount: orderbookNew.Bids[i].Quantity, - Price: orderbookNew.Bids[i].Price, - } - } - for i := range orderbookNew.Asks { - newOrderBook.Asks[i] = orderbook.Level{ - Amount: orderbookNew.Asks[i].Quantity, - Price: orderbookNew.Asks[i].Price, - } - } - return e.Websocket.Orderbook.LoadSnapshot(&newOrderBook) + }) } // handleFetchingBook checks if a full book is being fetched or needs to be diff --git a/exchanges/binanceus/binanceus_wrapper.go b/exchanges/binanceus/binanceus_wrapper.go index eab0bcb5..5db52241 100644 --- a/exchanges/binanceus/binanceus_wrapper.go +++ b/exchanges/binanceus/binanceus_wrapper.go @@ -317,37 +317,20 @@ func (e *Exchange) UpdateOrderbook(ctx context.Context, pair currency.Pair, asse if err := e.CurrencyPairs.IsAssetEnabled(assetType); err != nil { return nil, err } - book := &orderbook.Book{ + orderbookNew, err := e.GetOrderBookDepth(ctx, pair, 1000) + if err != nil { + return nil, err + } + ob := &orderbook.Book{ Exchange: e.Name, Pair: pair, Asset: assetType, ValidateOrderbook: e.ValidateOrderbook, + Bids: orderbookNew.Bids, + Asks: orderbookNew.Asks, } - - orderbookNew, err := e.GetOrderBookDepth(ctx, &OrderBookDataRequestParams{ - Symbol: pair, - Limit: 1000, - }) - if err != nil { - return book, err - } - book.Bids = make([]orderbook.Level, len(orderbookNew.Bids)) - for x := range orderbookNew.Bids { - book.Bids[x] = orderbook.Level{ - Amount: orderbookNew.Bids[x].Quantity, - Price: orderbookNew.Bids[x].Price, - } - } - book.Asks = make([]orderbook.Level, len(orderbookNew.Asks)) - for x := range orderbookNew.Asks { - book.Asks[x] = orderbook.Level{ - Amount: orderbookNew.Asks[x].Quantity, - Price: orderbookNew.Asks[x].Price, - } - } - 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) } diff --git a/exchanges/binanceus/ratelimit.go b/exchanges/binanceus/ratelimit.go index 34816a3a..03a4f92a 100644 --- a/exchanges/binanceus/ratelimit.go +++ b/exchanges/binanceus/ratelimit.go @@ -66,7 +66,7 @@ func GetRateLimit() request.RateLimitDefinitions { } // orderbookLimit returns the endpoint rate limit representing enum given order depth -func orderbookLimit(depth int64) request.EndpointLimit { +func orderbookLimit(depth uint64) request.EndpointLimit { switch { case depth <= 100: return spotDefaultRate diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index 1af154a7..1f52335f 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -170,35 +170,18 @@ func (e *Exchange) GetTicker(ctx context.Context, symbol string, hourly bool) (* // the amount. func (e *Exchange) GetOrderbook(ctx context.Context, symbol string) (*Orderbook, error) { type response struct { - Timestamp types.Time `json:"timestamp"` - Bids [][2]types.Number `json:"bids"` - Asks [][2]types.Number `json:"asks"` + Timestamp types.Time `json:"timestamp"` + Bids orderbook.LevelsArrayPriceAmount `json:"bids"` + Asks orderbook.LevelsArrayPriceAmount `json:"asks"` } path := "/v" + bitstampAPIVersion + "/" + bitstampAPIOrderbook + "/" + strings.ToLower(symbol) + "/" var resp response - err := e.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) - if err != nil { + if err := e.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp); err != nil { return nil, err } - ob := &Orderbook{ - Timestamp: resp.Timestamp.Time(), - Bids: make([]OrderbookBase, len(resp.Bids)), - Asks: make([]OrderbookBase, len(resp.Asks)), - } - - for x := range resp.Bids { - ob.Bids[x].Price = resp.Bids[x][0].Float64() - ob.Bids[x].Amount = resp.Bids[x][1].Float64() - } - - for x := range resp.Asks { - ob.Asks[x].Price = resp.Asks[x][0].Float64() - ob.Asks[x].Amount = resp.Asks[x][1].Float64() - } - - return ob, nil + return &Orderbook{Timestamp: resp.Timestamp.Time(), Bids: resp.Bids.Levels(), Asks: resp.Asks.Levels()}, nil } // GetTradingPairs returns a list of trading pairs which Bitstamp diff --git a/exchanges/bitstamp/bitstamp_test.go b/exchanges/bitstamp/bitstamp_test.go index 9669d854..4efdb572 100644 --- a/exchanges/bitstamp/bitstamp_test.go +++ b/exchanges/bitstamp/bitstamp_test.go @@ -197,7 +197,7 @@ func TestGetOrderbook(t *testing.T) { ob, err := e.GetOrderbook(t.Context(), currency.BTC.String()+currency.USD.String()) require.NoError(t, err, "GetOrderbook must not error") assert.NotEmpty(t, ob.Timestamp, "Timestamp should not be empty") - for i, o := range [][]OrderbookBase{ob.Asks, ob.Bids} { + for i, o := range [][]orderbook.Level{ob.Asks, ob.Bids} { s := []string{"Ask", "Bid"}[i] if assert.NotEmptyf(t, o, "Should have items in %ss", s) { a := o[0] diff --git a/exchanges/bitstamp/bitstamp_types.go b/exchanges/bitstamp/bitstamp_types.go index e0634d89..baa3dfcb 100644 --- a/exchanges/bitstamp/bitstamp_types.go +++ b/exchanges/bitstamp/bitstamp_types.go @@ -4,6 +4,7 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/types" ) @@ -37,17 +38,11 @@ type Ticker struct { PercentChange24 float64 `json:"percent_change_24,string"` } -// OrderbookBase holds singular price information -type OrderbookBase struct { - Price float64 - Amount float64 -} - // Orderbook holds orderbook information type Orderbook struct { Timestamp time.Time - Bids []OrderbookBase - Asks []OrderbookBase + Bids []orderbook.Level + Asks []orderbook.Level } // TradingPair holds trading pair information @@ -276,10 +271,10 @@ type websocketOrderBookResponse struct { } type websocketOrderBook struct { - Asks [][2]types.Number `json:"asks"` - Bids [][2]types.Number `json:"bids"` - Timestamp types.Time `json:"timestamp"` - Microtimestamp types.Time `json:"microtimestamp"` + Asks orderbook.LevelsArrayPriceAmount `json:"asks"` + Bids orderbook.LevelsArrayPriceAmount `json:"bids"` + Timestamp types.Time `json:"timestamp"` + Microtimestamp types.Time `json:"microtimestamp"` } // OHLCResponse holds returned candle data diff --git a/exchanges/bitstamp/bitstamp_websocket.go b/exchanges/bitstamp/bitstamp_websocket.go index 6401eb73..175352b5 100644 --- a/exchanges/bitstamp/bitstamp_websocket.go +++ b/exchanges/bitstamp/bitstamp_websocket.go @@ -300,23 +300,14 @@ func (e *Exchange) handleWSOrderbook(msg []byte) error { } obUpdate := &orderbook.Book{ - Bids: make(orderbook.Levels, len(wsOrderBookResp.Data.Bids)), - Asks: make(orderbook.Levels, len(wsOrderBookResp.Data.Asks)), + Bids: wsOrderBookResp.Data.Bids.Levels(), + Asks: wsOrderBookResp.Data.Asks.Levels(), Pair: p, LastUpdated: wsOrderBookResp.Data.Microtimestamp.Time(), Asset: asset.Spot, Exchange: e.Name, ValidateOrderbook: e.ValidateOrderbook, } - - for i := range wsOrderBookResp.Data.Asks { - obUpdate.Asks[i].Price = wsOrderBookResp.Data.Asks[i][0].Float64() - obUpdate.Asks[i].Amount = wsOrderBookResp.Data.Asks[i][1].Float64() - } - for i := range wsOrderBookResp.Data.Bids { - obUpdate.Bids[i].Price = wsOrderBookResp.Data.Bids[i][0].Float64() - obUpdate.Bids[i].Amount = wsOrderBookResp.Data.Bids[i][1].Float64() - } filterOrderbookZeroBidPrice(obUpdate) return e.Websocket.Orderbook.LoadSnapshot(obUpdate) } diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index f1e29d00..81fa8862 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -148,22 +148,7 @@ func (e *Exchange) GetOrderbook(ctx context.Context, marketID string, level int6 return nil, err } - ob := &Orderbook{ - MarketID: resp.MarketID, - SnapshotID: resp.SnapshotID, - Bids: make([]OBData, len(resp.Bids)), - Asks: make([]OBData, len(resp.Asks)), - } - - for x := range resp.Asks { - ob.Asks[x].Price = resp.Asks[x][0].Float64() - ob.Asks[x].Volume = resp.Asks[x][1].Float64() - } - for x := range resp.Bids { - ob.Bids[x].Price = resp.Bids[x][0].Float64() - ob.Bids[x].Volume = resp.Bids[x][1].Float64() - } - return ob, nil + return &Orderbook{MarketID: resp.MarketID, SnapshotID: resp.SnapshotID, Bids: resp.Bids.Levels(), Asks: resp.Asks.Levels()}, nil } // GetMarketCandles gets candles for specified currency pair @@ -227,16 +212,8 @@ func (e *Exchange) GetMultipleOrderbooks(ctx context.Context, marketIDs []string orderbooks[i] = Orderbook{ MarketID: resp[i].MarketID, SnapshotID: resp[i].SnapshotID, - Asks: make([]OBData, len(resp[i].Asks)), - Bids: make([]OBData, len(resp[i].Bids)), - } - for j := range resp[i].Asks { - orderbooks[i].Asks[j].Price = resp[i].Asks[j][0].Float64() - orderbooks[i].Asks[j].Volume = resp[i].Asks[j][1].Float64() - } - for j := range resp[i].Bids { - orderbooks[i].Bids[j].Price = resp[i].Bids[j][0].Float64() - orderbooks[i].Bids[j].Volume = resp[i].Bids[j][1].Float64() + Asks: resp[i].Asks.Levels(), + Bids: resp[i].Bids.Levels(), } } return orderbooks, nil diff --git a/exchanges/btcmarkets/btcmarkets_types.go b/exchanges/btcmarkets/btcmarkets_types.go index fc9c8f34..045799b8 100644 --- a/exchanges/btcmarkets/btcmarkets_types.go +++ b/exchanges/btcmarkets/btcmarkets_types.go @@ -46,24 +46,18 @@ type Trade struct { // tempOrderbook stores orderbook data type tempOrderbook struct { - MarketID currency.Pair `json:"marketId"` - SnapshotID int64 `json:"snapshotId"` - Asks [][2]types.Number `json:"asks"` - Bids [][2]types.Number `json:"bids"` -} - -// OBData stores orderbook data -type OBData struct { - Price float64 - Volume float64 + MarketID currency.Pair `json:"marketId"` + SnapshotID int64 `json:"snapshotId"` + Asks orderbook.LevelsArrayPriceAmount `json:"asks"` + Bids orderbook.LevelsArrayPriceAmount `json:"bids"` } // Orderbook holds current orderbook information returned from the exchange type Orderbook struct { MarketID currency.Pair SnapshotID int64 - Asks []OBData - Bids []OBData + Asks []orderbook.Level + Bids []orderbook.Level } // MarketCandle stores candle data for a given pair diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 8abc7bf5..38b22d1e 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -257,43 +257,28 @@ func (e *Exchange) UpdateOrderbook(ctx context.Context, p currency.Pair, assetTy return nil, err } - book := &orderbook.Book{ + fPair, err := e.FormatExchangeCurrency(p, assetType) + if err != nil { + return nil, err + } + // Retrieve level one book which is the top 50 ask and bids, this is not + // cached. + resp, err := e.GetOrderbook(ctx, fPair.String(), 1) + if err != nil { + return nil, err + } + + ob := &orderbook.Book{ Exchange: e.Name, Pair: p, Asset: assetType, PriceDuplication: true, ValidateOrderbook: e.ValidateOrderbook, + Asks: resp.Asks, + Bids: resp.Bids, } - - fPair, err := e.FormatExchangeCurrency(p, assetType) - if err != nil { - return book, err - } - // Retrieve level one book which is the top 50 ask and bids, this is not - // cached. - tempResp, err := e.GetOrderbook(ctx, fPair.String(), 1) - if err != nil { - return book, err - } - - book.Bids = make(orderbook.Levels, len(tempResp.Bids)) - for x := range tempResp.Bids { - book.Bids[x] = orderbook.Level{ - Amount: tempResp.Bids[x].Volume, - Price: tempResp.Bids[x].Price, - } - } - - book.Asks = make(orderbook.Levels, len(tempResp.Asks)) - for y := range tempResp.Asks { - book.Asks[y] = orderbook.Level{ - Amount: tempResp.Asks[y].Volume, - Price: tempResp.Asks[y].Price, - } - } - err = book.Process() - if err != nil { - return book, err + if err := ob.Process(); err != nil { + return nil, err } return orderbook.Get(e.Name, p, assetType) } diff --git a/exchanges/bybit/bybit.go b/exchanges/bybit/bybit.go index 8463c24d..475a95cd 100644 --- a/exchanges/bybit/bybit.go +++ b/exchanges/bybit/bybit.go @@ -22,9 +22,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "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" ) // Exchange implements exchange.IBotExchange and contains additional specific api methods for interacting with Bybit @@ -247,29 +245,18 @@ func (e *Exchange) GetOrderBook(ctx context.Context, category, symbol string, li params.Set("limit", strconv.FormatInt(limit, 10)) } var resp orderbookResponse - err = e.SendHTTPRequest(ctx, exchange.RestSpot, common.EncodeURLValues("market/orderbook", params), defaultEPL, &resp) - if err != nil { + if err := e.SendHTTPRequest(ctx, exchange.RestSpot, common.EncodeURLValues("market/orderbook", params), defaultEPL, &resp); err != nil { return nil, err } - return &Orderbook{ - Symbol: resp.Symbol, UpdateID: resp.UpdateID, + Symbol: resp.Symbol, GenerationTime: resp.Timestamp.Time(), - Bids: processOB(resp.Bids), - Asks: processOB(resp.Asks), + Bids: resp.Bids.Levels(), + Asks: resp.Asks.Levels(), }, nil } -func processOB(ob [][2]types.Number) []orderbook.Level { - o := make([]orderbook.Level, len(ob)) - for x := range ob { - o[x].Price = ob[x][0].Float64() - o[x].Amount = ob[x][1].Float64() - } - return o -} - func fillCategoryAndSymbol(category, symbol string, optionalSymbol ...bool) (url.Values, error) { if category == "" { return nil, errCategoryNotSet diff --git a/exchanges/bybit/bybit_convert.go b/exchanges/bybit/bybit_convert.go deleted file mode 100644 index 45fc1841..00000000 --- a/exchanges/bybit/bybit_convert.go +++ /dev/null @@ -1,22 +0,0 @@ -package bybit - -import "github.com/thrasher-corp/gocryptotrader/encoding/json" - -// UnmarshalJSON deserializes incoming data into orderbookResponse instance. -func (a *orderbookResponse) UnmarshalJSON(data []byte) error { - type Alias orderbookResponse - child := &struct { - *Alias - }{ - Alias: (*Alias)(a), - } - err := json.Unmarshal(data, child) - if err != nil { - var resp []any - err = json.Unmarshal(data, &resp) - if err != nil { - return err - } - } - return nil -} diff --git a/exchanges/bybit/bybit_test.go b/exchanges/bybit/bybit_test.go index d106ede8..7eb2dab4 100644 --- a/exchanges/bybit/bybit_test.go +++ b/exchanges/bybit/bybit_test.go @@ -47,7 +47,6 @@ const ( canManipulateRealOrders = false skipAuthenticatedFunctionsForMockTesting = "skipping authenticated function for mock testing" - skippingWebsocketFunctionsForMockTesting = "skipping websocket function for mock testing" ) var ( diff --git a/exchanges/bybit/bybit_types.go b/exchanges/bybit/bybit_types.go index b080137a..65d6be5c 100644 --- a/exchanges/bybit/bybit_types.go +++ b/exchanges/bybit/bybit_types.go @@ -16,11 +16,11 @@ import ( var supportedOptionsTypes = []string{"BTC", "ETH", "SOL"} type orderbookResponse struct { - Symbol string `json:"s"` - Asks [][2]types.Number `json:"a"` - Bids [][2]types.Number `json:"b"` - Timestamp types.Time `json:"ts"` - UpdateID int64 `json:"u"` + Symbol string `json:"s"` + Asks orderbook.LevelsArrayPriceAmount `json:"a"` + Bids orderbook.LevelsArrayPriceAmount `json:"b"` + Timestamp types.Time `json:"ts"` + UpdateID int64 `json:"u"` } // Authenticate stores authentication variables required @@ -1749,11 +1749,11 @@ type Orderbook struct { // WsOrderbookDetail represents an orderbook detail information. type WsOrderbookDetail struct { - Symbol string `json:"s"` - Bids [][2]types.Number `json:"b"` - Asks [][2]types.Number `json:"a"` - UpdateID int64 `json:"u"` - Sequence int64 `json:"seq"` + Symbol string `json:"s"` + Bids orderbook.LevelsArrayPriceAmount `json:"b"` + Asks orderbook.LevelsArrayPriceAmount `json:"a"` + UpdateID int64 `json:"u"` + Sequence int64 `json:"seq"` } // SubscriptionResponse represents a subscription response. diff --git a/exchanges/bybit/bybit_websocket.go b/exchanges/bybit/bybit_websocket.go index 26d08130..ba48085b 100644 --- a/exchanges/bybit/bybit_websocket.go +++ b/exchanges/bybit/bybit_websocket.go @@ -653,16 +653,6 @@ func (e *Exchange) wsProcessOrderbook(assetType asset.Item, resp *WebsocketRespo if err != nil { return err } - asks := make([]orderbook.Level, len(result.Asks)) - for i := range result.Asks { - asks[i].Price = result.Asks[i][0].Float64() - asks[i].Amount = result.Asks[i][1].Float64() - } - bids := make([]orderbook.Level, len(result.Bids)) - for i := range result.Bids { - bids[i].Price = result.Bids[i][0].Float64() - bids[i].Amount = result.Bids[i][1].Float64() - } if resp.Type == "snapshot" { return e.Websocket.Orderbook.LoadSnapshot(&orderbook.Book{ @@ -672,14 +662,14 @@ func (e *Exchange) wsProcessOrderbook(assetType asset.Item, resp *WebsocketRespo LastUpdated: resp.OrderbookLastUpdated.Time(), LastUpdateID: result.UpdateID, LastPushed: resp.PushTimestamp.Time(), - Asks: asks, - Bids: bids, + Asks: result.Asks.Levels(), + Bids: result.Bids.Levels(), }) } return e.Websocket.Orderbook.Update(&orderbook.Update{ Pair: cp, - Asks: asks, - Bids: bids, + Asks: result.Asks.Levels(), + Bids: result.Bids.Levels(), Asset: assetType, UpdateID: result.UpdateID, UpdateTime: resp.OrderbookLastUpdated.Time(), diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index 905bcb56..c561dc10 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -3676,3 +3676,13 @@ func TestMarshalJSONNumber(t *testing.T) { assert.Equal(t, tc.expected, string(payload), "MarshalJSON should return expected value") } } + +func TestUnmarshalJSONOrderbookLevels(t *testing.T) { + t.Parallel() + var ob OrderbookLevels + require.NoError(t, ob.UnmarshalJSON([]byte(`[{"p":"123.45","s":"0.001"}]`))) + assert.Equal(t, 123.45, ob[0].Price, "Price should be correct") + assert.Equal(t, 0.001, ob[0].Amount, "Amount should be correct") + + require.Error(t, ob.UnmarshalJSON([]byte(`["p":"123.45","s":"0.001"]`))) +} diff --git a/exchanges/gateio/gateio_types.go b/exchanges/gateio/gateio_types.go index 5e240753..c9edf65b 100644 --- a/exchanges/gateio/gateio_types.go +++ b/exchanges/gateio/gateio_types.go @@ -7,6 +7,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/encoding/json" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/types" ) @@ -519,26 +520,41 @@ type Ticker struct { // OrderbookData holds orderbook ask and bid datas. type OrderbookData struct { - ID int64 `json:"id"` - Current types.Time `json:"current"` // The timestamp of the response data being generated (in milliseconds) - Update types.Time `json:"update"` // The timestamp of when the orderbook last changed (in milliseconds) - Asks [][2]types.Number `json:"asks"` - Bids [][2]types.Number `json:"bids"` + ID int64 `json:"id"` + Current types.Time `json:"current"` // The timestamp of the response data being generated (in milliseconds) + Update types.Time `json:"update"` // The timestamp of when the orderbook last changed (in milliseconds) + Asks orderbook.LevelsArrayPriceAmount `json:"asks"` + Bids orderbook.LevelsArrayPriceAmount `json:"bids"` } // MakeOrderbook converts OrderbookData into an Orderbook func (a *OrderbookData) MakeOrderbook() *Orderbook { - asks := make([]OrderbookItem, len(a.Asks)) - for x := range a.Asks { - asks[x].Price = a.Asks[x][0] - asks[x].Amount = a.Asks[x][1] + return &Orderbook{ID: a.ID, Current: a.Current, Update: a.Update, Asks: OrderbookLevels(a.Asks.Levels()), Bids: OrderbookLevels(a.Bids.Levels())} +} + +// OrderbookLevels represents a slice of orderbook levels. +type OrderbookLevels orderbook.Levels + +// UnmarshalJSON implements the json.Unmarshaler interface for OrderbookLevels. +func (o *OrderbookLevels) UnmarshalJSON(data []byte) error { + var levels []OrderbookItem + if err := json.Unmarshal(data, &levels); err != nil { + return err } - bids := make([]OrderbookItem, len(a.Bids)) - for x := range a.Bids { - bids[x].Price = a.Bids[x][0] - bids[x].Amount = a.Bids[x][1] + + *o = make(OrderbookLevels, len(levels)) + for x := range levels { + (*o)[x] = orderbook.Level{ + Price: levels[x].Price.Float64(), + Amount: levels[x].Amount.Float64(), + } } - return &Orderbook{ID: a.ID, Current: a.Current, Update: a.Update, Asks: asks, Bids: bids} + return nil +} + +// Levels converts OrderbookLevels to orderbook.Levels. +func (o *OrderbookLevels) Levels() orderbook.Levels { + return orderbook.Levels(*o) } // OrderbookItem stores an orderbook item @@ -552,8 +568,8 @@ type Orderbook struct { ID int64 `json:"id"` Current types.Time `json:"current"` // The timestamp of the response data being generated (in milliseconds) Update types.Time `json:"update"` // The timestamp of when the orderbook last changed (in milliseconds) - Bids []OrderbookItem `json:"bids"` - Asks []OrderbookItem `json:"asks"` + Bids OrderbookLevels `json:"bids"` + Asks OrderbookLevels `json:"asks"` } // Trade represents market trade. @@ -2053,21 +2069,21 @@ type WsOrderbookTickerData struct { // WsOrderbookUpdate represents websocket orderbook update push data type WsOrderbookUpdate struct { - UpdateTime types.Time `json:"t"` - Pair currency.Pair `json:"s"` - FirstUpdateID int64 `json:"U"` // First update order book id in this event since last update - LastUpdateID int64 `json:"u"` - Bids [][2]types.Number `json:"b"` - Asks [][2]types.Number `json:"a"` + UpdateTime types.Time `json:"t"` + Pair currency.Pair `json:"s"` + FirstUpdateID int64 `json:"U"` // First update order book id in this event since last update + LastUpdateID int64 `json:"u"` + Bids orderbook.LevelsArrayPriceAmount `json:"b"` + Asks orderbook.LevelsArrayPriceAmount `json:"a"` } // WsOrderbookSnapshot represents a websocket orderbook snapshot push data type WsOrderbookSnapshot struct { - UpdateTime types.Time `json:"t"` - LastUpdateID int64 `json:"lastUpdateId"` - CurrencyPair currency.Pair `json:"s"` - Bids [][2]types.Number `json:"bids"` - Asks [][2]types.Number `json:"asks"` + UpdateTime types.Time `json:"t"` + LastUpdateID int64 `json:"lastUpdateId"` + CurrencyPair currency.Pair `json:"s"` + Bids orderbook.LevelsArrayPriceAmount `json:"bids"` + Asks orderbook.LevelsArrayPriceAmount `json:"asks"` } // WsSpotOrder represents an order push data through the websocket channel. diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index f5ed4a33..3a449bf6 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -373,24 +373,14 @@ func (e *Exchange) processOrderbookUpdate(ctx context.Context, incoming []byte, if err := json.Unmarshal(incoming, &data); err != nil { return err } - asks := make([]orderbook.Level, len(data.Asks)) - for x := range data.Asks { - asks[x].Price = data.Asks[x][0].Float64() - asks[x].Amount = data.Asks[x][1].Float64() - } - bids := make([]orderbook.Level, len(data.Bids)) - for x := range data.Bids { - bids[x].Price = data.Bids[x][0].Float64() - bids[x].Amount = data.Bids[x][1].Float64() - } return e.wsOBUpdateMgr.ProcessOrderbookUpdate(ctx, e, data.FirstUpdateID, &orderbook.Update{ UpdateID: data.LastUpdateID, UpdateTime: data.UpdateTime.Time(), LastPushed: lastPushed, Pair: data.Pair, Asset: asset.Spot, - Asks: asks, - Bids: bids, + Asks: data.Asks.Levels(), + Bids: data.Bids.Levels(), AllowEmpty: true, }) } @@ -401,17 +391,6 @@ func (e *Exchange) processOrderbookSnapshot(incoming []byte, lastPushed time.Tim return err } - asks := make([]orderbook.Level, len(data.Asks)) - for x := range data.Asks { - asks[x].Price = data.Asks[x][0].Float64() - asks[x].Amount = data.Asks[x][1].Float64() - } - bids := make([]orderbook.Level, len(data.Bids)) - for x := range data.Bids { - bids[x].Price = data.Bids[x][0].Float64() - bids[x].Amount = data.Bids[x][1].Float64() - } - for _, a := range standardMarginAssetTypes { if enabled, _ := e.CurrencyPairs.IsPairEnabled(data.CurrencyPair, a); enabled { if err := e.Websocket.Orderbook.LoadSnapshot(&orderbook.Book{ @@ -420,8 +399,8 @@ func (e *Exchange) processOrderbookSnapshot(incoming []byte, lastPushed time.Tim Asset: a, LastUpdated: data.UpdateTime.Time(), LastPushed: lastPushed, - Bids: bids, - Asks: asks, + Bids: data.Bids.Levels(), + Asks: data.Asks.Levels(), }); err != nil { return err } diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index 4b5242db..e6732191 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -670,34 +670,24 @@ func (e *Exchange) UpdateOrderbookWithLimit(ctx context.Context, p currency.Pair if err != nil { return nil, err } - book := &orderbook.Book{ + + ob := &orderbook.Book{ Exchange: e.Name, Asset: a, ValidateOrderbook: e.ValidateOrderbook, - Pair: p.Upper(), + Pair: p, LastUpdateID: o.ID, LastUpdated: o.Update.Time(), LastPushed: o.Current.Time(), + Bids: o.Bids.Levels(), + Asks: o.Asks.Levels(), } - book.Bids = make(orderbook.Levels, len(o.Bids)) - for x := range o.Bids { - book.Bids[x] = orderbook.Level{ - Amount: o.Bids[x].Amount.Float64(), - Price: o.Bids[x].Price.Float64(), - } + + if err := ob.Process(); err != nil { + return nil, err } - book.Asks = make(orderbook.Levels, len(o.Asks)) - for x := range o.Asks { - book.Asks[x] = orderbook.Level{ - Amount: o.Asks[x].Amount.Float64(), - Price: o.Asks[x].Price.Float64(), - } - } - err = book.Process() - if err != nil { - return book, err - } - return orderbook.Get(e.Name, book.Pair, a) + + return orderbook.Get(e.Name, p, a) } // UpdateAccountInfo retrieves balances for all enabled currencies for the diff --git a/exchanges/kucoin/kucoin.go b/exchanges/kucoin/kucoin.go index 3b4176e8..c482c6ba 100644 --- a/exchanges/kucoin/kucoin.go +++ b/exchanges/kucoin/kucoin.go @@ -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 diff --git a/exchanges/kucoin/kucoin_futures.go b/exchanges/kucoin/kucoin_futures.go index 8287483a..8cfa1a24 100644 --- a/exchanges/kucoin/kucoin_futures.go +++ b/exchanges/kucoin/kucoin_futures.go @@ -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 == "" { diff --git a/exchanges/kucoin/kucoin_futures_types.go b/exchanges/kucoin/kucoin_futures_types.go index e2f60774..324f2e3b 100644 --- a/exchanges/kucoin/kucoin_futures_types.go +++ b/exchanges/kucoin/kucoin_futures_types.go @@ -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 diff --git a/exchanges/kucoin/kucoin_types.go b/exchanges/kucoin/kucoin_types.go index 5da468d6..59e5d1c5 100644 --- a/exchanges/kucoin/kucoin_types.go +++ b/exchanges/kucoin/kucoin_types.go @@ -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 diff --git a/exchanges/kucoin/kucoin_websocket.go b/exchanges/kucoin/kucoin_websocket.go index cd252016..f89c7819 100644 --- a/exchanges/kucoin/kucoin_websocket.go +++ b/exchanges/kucoin/kucoin_websocket.go @@ -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 diff --git a/exchanges/kucoin/kucoin_wrapper.go b/exchanges/kucoin/kucoin_wrapper.go index 40d7fd38..e57e359e 100644 --- a/exchanges/kucoin/kucoin_wrapper.go +++ b/exchanges/kucoin/kucoin_wrapper.go @@ -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 diff --git a/exchanges/lbank/lbank_types.go b/exchanges/lbank/lbank_types.go index 85a3c782..7c91d32f 100644 --- a/exchanges/lbank/lbank_types.go +++ b/exchanges/lbank/lbank_types.go @@ -5,6 +5,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/encoding/json" + "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/types" ) @@ -29,9 +30,9 @@ type TickerResponse struct { type MarketDepthResponse struct { ErrCapture Data 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"` } `json:"data"` } diff --git a/exchanges/lbank/lbank_wrapper.go b/exchanges/lbank/lbank_wrapper.go index 3a7e2cab..d5e4d15e 100644 --- a/exchanges/lbank/lbank_wrapper.go +++ b/exchanges/lbank/lbank_wrapper.go @@ -210,28 +210,17 @@ func (e *Exchange) UpdateOrderbook(ctx context.Context, p currency.Pair, assetTy return nil, err } - book := &orderbook.Book{ + ob := &orderbook.Book{ Exchange: e.Name, Pair: p, Asset: assetType, ValidateOrderbook: e.ValidateOrderbook, - Asks: make(orderbook.Levels, len(d.Data.Asks)), - Bids: make(orderbook.Levels, len(d.Data.Bids)), + Asks: d.Data.Asks.Levels(), + Bids: d.Data.Bids.Levels(), } - - for i := range d.Data.Asks { - book.Asks[i].Price = d.Data.Asks[i][0].Float64() - book.Asks[i].Amount = d.Data.Asks[i][1].Float64() - } - for i := range d.Data.Bids { - book.Bids[i].Price = d.Data.Bids[i][0].Float64() - book.Bids[i].Amount = d.Data.Bids[i][1].Float64() - } - - if err := book.Process(); err != nil { + if err := ob.Process(); err != nil { return nil, err } - return orderbook.Get(e.Name, p, assetType) } diff --git a/exchanges/orderbook/orderbook_test.go b/exchanges/orderbook/orderbook_test.go index 4a079caa..0b374f15 100644 --- a/exchanges/orderbook/orderbook_test.go +++ b/exchanges/orderbook/orderbook_test.go @@ -562,3 +562,20 @@ func BenchmarkProcess(b *testing.B) { } } } + +func TestLevelsArrayPriceAmountUnmarshalJSON(t *testing.T) { + t.Parallel() + + var asks LevelsArrayPriceAmount + err := asks.UnmarshalJSON([]byte(`[[1,2],["3","4"]]`)) + require.NoError(t, err) + assert.Len(t, asks, 2) + assert.Equal(t, 1.0, asks[0].Price) + assert.Equal(t, 2.0, asks[0].Amount) + assert.Equal(t, 3.0, asks[1].Price) + assert.Equal(t, 4.0, asks[1].Amount) + assert.Equal(t, 2, len(asks.Levels())) + + err = asks.UnmarshalJSON([]byte(`invalid`)) + assert.Error(t, err) +} diff --git a/exchanges/orderbook/orderbook_types.go b/exchanges/orderbook/orderbook_types.go index b74de9d3..1a90d756 100644 --- a/exchanges/orderbook/orderbook_types.go +++ b/exchanges/orderbook/orderbook_types.go @@ -9,7 +9,9 @@ import ( "github.com/thrasher-corp/gocryptotrader/common/key" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/dispatch" + "github.com/thrasher-corp/gocryptotrader/encoding/json" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/types" ) const ( @@ -184,3 +186,26 @@ type SideAmounts struct { QuoteValue float64 BaseAmount float64 } + +// LevelsArrayPriceAmount used to unmarshal orderbook levels from JSON slice of arrays +// e.g. [[price, amount], [price, amount]] or [][2]types.Number type declaration +type LevelsArrayPriceAmount Levels + +// UnmarshalJSON implements json.Unmarshaler +func (l *LevelsArrayPriceAmount) UnmarshalJSON(data []byte) error { + var v [][2]types.Number + if err := json.Unmarshal(data, &v); err != nil { + return err + } + *l = make(LevelsArrayPriceAmount, len(v)) + for x := range v { + (*l)[x].Price = v[x][0].Float64() + (*l)[x].Amount = v[x][1].Float64() + } + return nil +} + +// Levels converts the LevelsArrayPriceAmount to a orderbook.Levels type +func (l *LevelsArrayPriceAmount) Levels() Levels { + return Levels(*l) +} diff --git a/exchanges/poloniex/poloniex.go b/exchanges/poloniex/poloniex.go index 35db710e..ca113576 100644 --- a/exchanges/poloniex/poloniex.go +++ b/exchanges/poloniex/poloniex.go @@ -101,56 +101,28 @@ func (e *Exchange) GetOrderbook(ctx context.Context, currencyPair string, depth vals.Set("currencyPair", currencyPair) resp := OrderbookResponse{} path := "/public?command=returnOrderBook&" + vals.Encode() - err := e.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) - if err != nil { + if err := e.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp); err != nil { return oba, err } if resp.Error != "" { return oba, fmt.Errorf("%s GetOrderbook() error: %s", e.Name, resp.Error) } - ob := Orderbook{ - Bids: make([]OrderbookItem, len(resp.Bids)), - Asks: make([]OrderbookItem, len(resp.Asks)), + oba.Data[currencyPair] = Orderbook{ + Bids: resp.Bids.Levels(), + Asks: resp.Asks.Levels(), } - for x := range resp.Asks { - ob.Asks[x] = OrderbookItem{ - Price: resp.Asks[x][0].Float64(), - Amount: resp.Asks[x][1].Float64(), - } - } - for x := range resp.Bids { - ob.Bids[x] = OrderbookItem{ - Price: resp.Bids[x][0].Float64(), - Amount: resp.Bids[x][1].Float64(), - } - } - oba.Data[currencyPair] = ob } else { vals.Set("currencyPair", "all") resp := OrderbookResponseAll{} path := "/public?command=returnOrderBook&" + vals.Encode() - err := e.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp.Data) - if err != nil { + if err := e.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp.Data); err != nil { return oba, err } for currency, orderbook := range resp.Data { - ob := Orderbook{ - Bids: make([]OrderbookItem, len(orderbook.Bids)), - Asks: make([]OrderbookItem, len(orderbook.Asks)), + oba.Data[currency] = Orderbook{ + Bids: orderbook.Bids.Levels(), + Asks: orderbook.Asks.Levels(), } - for x := range orderbook.Asks { - ob.Asks[x] = OrderbookItem{ - Price: orderbook.Asks[x][0].Float64(), - Amount: orderbook.Asks[x][1].Float64(), - } - } - for x := range orderbook.Bids { - ob.Bids[x] = OrderbookItem{ - Price: orderbook.Bids[x][0].Float64(), - Amount: orderbook.Bids[x][1].Float64(), - } - } - oba.Data[currency] = ob } } return oba, nil diff --git a/exchanges/poloniex/poloniex_types.go b/exchanges/poloniex/poloniex_types.go index 7e23cc37..24cd16c2 100644 --- a/exchanges/poloniex/poloniex_types.go +++ b/exchanges/poloniex/poloniex_types.go @@ -5,6 +5,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/encoding/json" + "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/types" ) @@ -33,17 +34,11 @@ type CompleteBalances map[string]CompleteBalance // OrderbookResponse is a sub-type for orderbooks type OrderbookResponse struct { - Asks [][2]types.Number `json:"asks"` - Bids [][2]types.Number `json:"bids"` - IsFrozen string `json:"isFrozen"` - Error string `json:"error"` - Seq int64 `json:"seq"` -} - -// OrderbookItem holds data on an individual item -type OrderbookItem struct { - Price float64 - Amount float64 + Asks orderbook.LevelsArrayPriceAmount `json:"asks"` + Bids orderbook.LevelsArrayPriceAmount `json:"bids"` + IsFrozen string `json:"isFrozen"` + Error string `json:"error"` + Seq int64 `json:"seq"` } // OrderbookAll contains the full range of orderbooks @@ -53,8 +48,8 @@ type OrderbookAll struct { // Orderbook is a generic type golding orderbook information type Orderbook struct { - Asks []OrderbookItem `json:"asks"` - Bids []OrderbookItem `json:"bids"` + Asks []orderbook.Level `json:"asks"` + Bids []orderbook.Level `json:"bids"` } // TradeHistory holds trade history data