From f9f634646831891aa4b86f86dc8681574fcf1a59 Mon Sep 17 00:00:00 2001 From: chiahung Date: Wed, 8 Mar 2023 17:18:18 +0800 Subject: [PATCH] FEATURE: split self trades when use MAX RESTful API to query trades --- pkg/exchange/max/convert.go | 48 +++++++- pkg/exchange/max/convert_test.go | 116 ++++++++++++++++++ pkg/exchange/max/exchange.go | 8 +- .../v3/get_order_trades_request_requestgen.go | 5 +- .../get_wallet_trades_request_requestgen.go | 4 +- pkg/exchange/max/maxapi/v3/order.go | 1 - pkg/exchange/max/maxapi/v3/trade.go | 33 +++++ 7 files changed, 203 insertions(+), 12 deletions(-) create mode 100644 pkg/exchange/max/convert_test.go create mode 100644 pkg/exchange/max/maxapi/v3/trade.go diff --git a/pkg/exchange/max/convert.go b/pkg/exchange/max/convert.go index 80e01bab8..c8c406b4a 100644 --- a/pkg/exchange/max/convert.go +++ b/pkg/exchange/max/convert.go @@ -5,7 +5,8 @@ import ( "strings" "time" - "github.com/c9s/bbgo/pkg/exchange/max/maxapi" + max "github.com/c9s/bbgo/pkg/exchange/max/maxapi" + v3 "github.com/c9s/bbgo/pkg/exchange/max/maxapi/v3" "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/types" ) @@ -193,7 +194,50 @@ func toGlobalOrder(maxOrder max.Order) (*types.Order, error) { }, nil } -func toGlobalTrade(t max.Trade) (*types.Trade, error) { +func toGlobalTradeV3(t v3.Trade) ([]types.Trade, error) { + var trades []types.Trade + isMargin := t.WalletType == max.WalletTypeMargin + side := toGlobalSideType(t.Side) + + trade := types.Trade{ + ID: t.ID, + OrderID: t.OrderID, + Price: t.Price, + Symbol: toGlobalSymbol(t.Market), + Exchange: types.ExchangeMax, + Quantity: t.Volume, + Side: side, + IsBuyer: t.IsBuyer(), + IsMaker: t.IsMaker(), + Fee: t.Fee, + FeeCurrency: toGlobalCurrency(t.FeeCurrency), + QuoteQuantity: t.Funds, + Time: types.Time(t.CreatedAt), + IsMargin: isMargin, + IsIsolated: false, + IsFutures: false, + } + + if t.Side == "self-trade" { + trade.Side = types.SideTypeSell + + // create trade for bid + bidTrade := trade + bidTrade.Side = types.SideTypeBuy + bidTrade.OrderID = t.SelfTradeBidOrderID + bidTrade.Fee = t.SelfTradeBidFee + bidTrade.FeeCurrency = t.SelfTradeBidFeeCurrency + bidTrade.IsBuyer = !trade.IsBuyer + bidTrade.IsMaker = !trade.IsMaker + trades = append(trades, bidTrade) + } + + trades = append(trades, trade) + + return trades, nil +} + +func toGlobalTradeV2(t max.Trade) (*types.Trade, error) { isMargin := t.WalletType == max.WalletTypeMargin side := toGlobalSideType(t.Side) return &types.Trade{ diff --git a/pkg/exchange/max/convert_test.go b/pkg/exchange/max/convert_test.go new file mode 100644 index 000000000..d6a3b3c4b --- /dev/null +++ b/pkg/exchange/max/convert_test.go @@ -0,0 +1,116 @@ +package max + +import ( + "encoding/json" + "testing" + + v3 "github.com/c9s/bbgo/pkg/exchange/max/maxapi/v3" + "github.com/c9s/bbgo/pkg/types" + "github.com/stretchr/testify/assert" +) + +func Test_toGlobalTradeV3(t *testing.T) { + assert := assert.New(t) + + t.Run("ask trade", func(t *testing.T) { + str := ` + { + "id": 68444, + "order_id": 87, + "wallet_type": "spot", + "price": "21499.0", + "volume": "0.2658", + "funds": "5714.4", + "market": "ethtwd", + "market_name": "ETH/TWD", + "side": "bid", + "fee": "0.00001", + "fee_currency": "usdt", + "self_trade_bid_fee": "0.00001", + "self_trade_bid_fee_currency": "eth", + "self_trade_bid_order_id": 86, + "liquidity": "maker", + "created_at": 1521726960357 + } + ` + + var trade v3.Trade + assert.NoError(json.Unmarshal([]byte(str), &trade)) + + trades, err := toGlobalTradeV3(trade) + assert.NoError(err) + assert.Len(trades, 1) + + assert.Equal(uint64(87), trades[0].OrderID) + assert.Equal(types.SideTypeBuy, trades[0].Side) + }) + + t.Run("bid trade", func(t *testing.T) { + str := ` + { + "id": 68444, + "order_id": 87, + "wallet_type": "spot", + "price": "21499.0", + "volume": "0.2658", + "funds": "5714.4", + "market": "ethtwd", + "market_name": "ETH/TWD", + "side": "ask", + "fee": "0.00001", + "fee_currency": "usdt", + "self_trade_bid_fee": "0.00001", + "self_trade_bid_fee_currency": "eth", + "self_trade_bid_order_id": 86, + "liquidity": "maker", + "created_at": 1521726960357 + } + ` + + var trade v3.Trade + assert.NoError(json.Unmarshal([]byte(str), &trade)) + + trades, err := toGlobalTradeV3(trade) + assert.NoError(err) + assert.Len(trades, 1) + + assert.Equal(uint64(87), trades[0].OrderID) + assert.Equal(types.SideTypeSell, trades[0].Side) + }) + + t.Run("self trade", func(t *testing.T) { + str := ` + { + "id": 68444, + "order_id": 87, + "wallet_type": "spot", + "price": "21499.0", + "volume": "0.2658", + "funds": "5714.4", + "market": "ethtwd", + "market_name": "ETH/TWD", + "side": "self-trade", + "fee": "0.00001", + "fee_currency": "usdt", + "self_trade_bid_fee": "0.00001", + "self_trade_bid_fee_currency": "eth", + "self_trade_bid_order_id": 86, + "liquidity": "maker", + "created_at": 1521726960357 + } + ` + + var trade v3.Trade + assert.NoError(json.Unmarshal([]byte(str), &trade)) + + trades, err := toGlobalTradeV3(trade) + assert.NoError(err) + assert.Len(trades, 2) + + assert.Equal(uint64(86), trades[0].OrderID) + assert.Equal(types.SideTypeBuy, trades[0].Side) + + assert.Equal(uint64(87), trades[1].OrderID) + assert.Equal(types.SideTypeSell, trades[1].Side) + }) +} diff --git a/pkg/exchange/max/exchange.go b/pkg/exchange/max/exchange.go index abdcab608..41a7b0201 100644 --- a/pkg/exchange/max/exchange.go +++ b/pkg/exchange/max/exchange.go @@ -186,13 +186,13 @@ func (e *Exchange) QueryOrderTrades(ctx context.Context, q types.OrderQuery) ([] var trades []types.Trade for _, t := range maxTrades { - localTrade, err := toGlobalTrade(t) + localTrades, err := toGlobalTradeV3(t) if err != nil { log.WithError(err).Errorf("can not convert trade: %+v", t) continue } - trades = append(trades, *localTrade) + trades = append(trades, localTrades...) } // ensure everything is sorted ascending @@ -806,13 +806,13 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type } for _, t := range maxTrades { - localTrade, err := toGlobalTrade(t) + localTrades, err := toGlobalTradeV3(t) if err != nil { log.WithError(err).Errorf("can not convert trade: %+v", t) continue } - trades = append(trades, *localTrade) + trades = append(trades, localTrades...) } // ensure everything is sorted ascending diff --git a/pkg/exchange/max/maxapi/v3/get_order_trades_request_requestgen.go b/pkg/exchange/max/maxapi/v3/get_order_trades_request_requestgen.go index 10bd1cd44..e739f3396 100644 --- a/pkg/exchange/max/maxapi/v3/get_order_trades_request_requestgen.go +++ b/pkg/exchange/max/maxapi/v3/get_order_trades_request_requestgen.go @@ -6,7 +6,6 @@ import ( "context" "encoding/json" "fmt" - "github.com/c9s/bbgo/pkg/exchange/max/maxapi" "net/url" "reflect" "regexp" @@ -136,7 +135,7 @@ func (g *GetOrderTradesRequest) GetSlugsMap() (map[string]string, error) { return slugs, nil } -func (g *GetOrderTradesRequest) Do(ctx context.Context) ([]max.Trade, error) { +func (g *GetOrderTradesRequest) Do(ctx context.Context) ([]Trade, error) { // empty params for GET operation var params interface{} @@ -157,7 +156,7 @@ func (g *GetOrderTradesRequest) Do(ctx context.Context) ([]max.Trade, error) { return nil, err } - var apiResponse []max.Trade + var apiResponse []Trade if err := response.DecodeJSON(&apiResponse); err != nil { return nil, err } diff --git a/pkg/exchange/max/maxapi/v3/get_wallet_trades_request_requestgen.go b/pkg/exchange/max/maxapi/v3/get_wallet_trades_request_requestgen.go index 2fdf94c40..647916103 100644 --- a/pkg/exchange/max/maxapi/v3/get_wallet_trades_request_requestgen.go +++ b/pkg/exchange/max/maxapi/v3/get_wallet_trades_request_requestgen.go @@ -198,7 +198,7 @@ func (g *GetWalletTradesRequest) GetSlugsMap() (map[string]string, error) { return slugs, nil } -func (g *GetWalletTradesRequest) Do(ctx context.Context) ([]max.Trade, error) { +func (g *GetWalletTradesRequest) Do(ctx context.Context) ([]Trade, error) { // empty params for GET operation var params interface{} @@ -225,7 +225,7 @@ func (g *GetWalletTradesRequest) Do(ctx context.Context) ([]max.Trade, error) { return nil, err } - var apiResponse []max.Trade + var apiResponse []Trade if err := response.DecodeJSON(&apiResponse); err != nil { return nil, err } diff --git a/pkg/exchange/max/maxapi/v3/order.go b/pkg/exchange/max/maxapi/v3/order.go index df3a63a60..22486c112 100644 --- a/pkg/exchange/max/maxapi/v3/order.go +++ b/pkg/exchange/max/maxapi/v3/order.go @@ -15,7 +15,6 @@ type WalletType = maxapi.WalletType type OrderType = maxapi.OrderType type Order = maxapi.Order -type Trade = maxapi.Trade type Account = maxapi.Account // OrderService manages the Order endpoint. diff --git a/pkg/exchange/max/maxapi/v3/trade.go b/pkg/exchange/max/maxapi/v3/trade.go new file mode 100644 index 000000000..c9e975bb0 --- /dev/null +++ b/pkg/exchange/max/maxapi/v3/trade.go @@ -0,0 +1,33 @@ +package v3 + +import ( + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" +) + +type Trade struct { + ID uint64 `json:"id" db:"exchange_id"` + WalletType WalletType `json:"wallet_type,omitempty"` + Price fixedpoint.Value `json:"price"` + Volume fixedpoint.Value `json:"volume"` + Funds fixedpoint.Value `json:"funds"` + Market string `json:"market"` + MarketName string `json:"market_name"` + CreatedAt types.MillisecondTimestamp `json:"created_at"` + Side string `json:"side"` + OrderID uint64 `json:"order_id"` + Fee fixedpoint.Value `json:"fee"` // float number as string + FeeCurrency string `json:"fee_currency"` + Liquidity string `json:"liquidity"` + SelfTradeBidFee fixedpoint.Value `json:"self_trade_bid_fee"` + SelfTradeBidFeeCurrency string `json:"self_trade_bid_fee_currency"` + SelfTradeBidOrderID uint64 `json:"self_trade_bid_order_id"` +} + +func (t Trade) IsBuyer() bool { + return t.Side == "bid" +} + +func (t Trade) IsMaker() bool { + return t.Liquidity == "maker" +}