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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -47,7 +47,6 @@ const (
canManipulateRealOrders = false
skipAuthenticatedFunctionsForMockTesting = "skipping authenticated function for mock testing"
skippingWebsocketFunctionsForMockTesting = "skipping websocket function for mock testing"
)
var (

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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