From db376f8483a1504cd36d1bc051c2994b16cda342 Mon Sep 17 00:00:00 2001 From: chiahung Date: Tue, 5 Sep 2023 18:28:10 +0800 Subject: [PATCH] FEATURE: use quote quantity if there is QuoteQuantity in trade --- pkg/exchange/max/convert.go | 2 +- pkg/exchange/max/maxapi/userdata.go | 1 + pkg/strategy/grid2/strategy.go | 24 ++++++------- pkg/strategy/grid2/strategy_test.go | 56 +++++++++++++++-------------- pkg/strategy/grid2/trade.go | 14 ++++++++ 5 files changed, 56 insertions(+), 41 deletions(-) diff --git a/pkg/exchange/max/convert.go b/pkg/exchange/max/convert.go index 0fb6c7462..ef7b8a117 100644 --- a/pkg/exchange/max/convert.go +++ b/pkg/exchange/max/convert.go @@ -300,7 +300,7 @@ func convertWebSocketTrade(t max.TradeUpdate) (*types.Trade, error) { Fee: t.Fee, FeeCurrency: toGlobalCurrency(t.FeeCurrency), FeeDiscounted: t.FeeDiscounted, - QuoteQuantity: t.Price.Mul(t.Volume), + QuoteQuantity: t.Funds, Time: types.Time(t.Timestamp.Time()), }, nil } diff --git a/pkg/exchange/max/maxapi/userdata.go b/pkg/exchange/max/maxapi/userdata.go index 551d3d685..fe7ae8441 100644 --- a/pkg/exchange/max/maxapi/userdata.go +++ b/pkg/exchange/max/maxapi/userdata.go @@ -98,6 +98,7 @@ type TradeUpdate struct { Side string `json:"sd"` Price fixedpoint.Value `json:"p"` Volume fixedpoint.Value `json:"v"` + Funds fixedpoint.Value `json:"fn"` Market string `json:"M"` Fee fixedpoint.Value `json:"f"` diff --git a/pkg/strategy/grid2/strategy.go b/pkg/strategy/grid2/strategy.go index c532ed1ad..82751f4b4 100644 --- a/pkg/strategy/grid2/strategy.go +++ b/pkg/strategy/grid2/strategy.go @@ -378,9 +378,9 @@ func (s *Strategy) verifyOrderTrades(o types.Order, trades []types.Trade) bool { return true } -// aggregateOrderFee collects the base fee quantity from the given order +// aggregateOrderQuoteAmountAndBaseFee collects the base fee quantity from the given order // it falls back to query the trades via the RESTful API when the websocket trades are not all received. -func (s *Strategy) aggregateOrderFee(o types.Order) (fixedpoint.Value, string) { +func (s *Strategy) aggregateOrderQuoteAmountAndFee(o types.Order) (fixedpoint.Value, fixedpoint.Value, string) { // try to get the received trades (websocket trades) orderTrades := s.historicalTrades.GetOrderTrades(o) if len(orderTrades) > 0 { @@ -396,16 +396,17 @@ func (s *Strategy) aggregateOrderFee(o types.Order) (fixedpoint.Value, string) { // if one of the trades is missing, we need to query the trades from the RESTful API if s.verifyOrderTrades(o, orderTrades) { // if trades are verified + quoteAmount := aggregateTradesQuoteQuantity(orderTrades) fees := collectTradeFee(orderTrades) if fee, ok := fees[feeCurrency]; ok { - return fee, feeCurrency + return quoteAmount, fee, feeCurrency } - return fixedpoint.Zero, feeCurrency + return quoteAmount, fixedpoint.Zero, feeCurrency } // if we don't support orderQueryService, then we should just skip if s.orderQueryService == nil { - return fixedpoint.Zero, feeCurrency + return fixedpoint.Zero, fixedpoint.Zero, feeCurrency } s.logger.Warnf("GRID: missing #%d order trades or missing trade fee, pulling order trades from API", o.OrderID) @@ -423,13 +424,14 @@ func (s *Strategy) aggregateOrderFee(o types.Order) (fixedpoint.Value, string) { } } + quoteAmount := aggregateTradesQuoteQuantity(orderTrades) // still try to aggregate the trades quantity if we can: fees := collectTradeFee(orderTrades) if fee, ok := fees[feeCurrency]; ok { - return fee, feeCurrency + return quoteAmount, fee, feeCurrency } - return fixedpoint.Zero, feeCurrency + return quoteAmount, fixedpoint.Zero, feeCurrency } func (s *Strategy) processFilledOrder(o types.Order) { @@ -446,7 +448,6 @@ func (s *Strategy) processFilledOrder(o types.Order) { } newQuantity := executedQuantity - executedPrice := o.Price if o.ExecutedQuantity.Compare(o.Quantity) != 0 { s.logger.Warnf("order #%d is filled, but order executed quantity %s != order quantity %s, something is wrong", o.OrderID, o.ExecutedQuantity, o.Quantity) @@ -458,16 +459,11 @@ func (s *Strategy) processFilledOrder(o types.Order) { } */ - // will be used for calculating quantity - orderExecutedQuoteAmount := executedQuantity.Mul(executedPrice) - // round down order executed quote amount to avoid insufficient balance - orderExecutedQuoteAmount = orderExecutedQuoteAmount.Round(s.Market.PricePrecision, fixedpoint.Down) - // collect trades for fee // fee calculation is used to reduce the order quantity // because when 1.0 BTC buy order is filled without FEE token, then we will actually get 1.0 * (1 - feeRate) BTC // if we don't reduce the sell quantity, than we might fail to place the sell order - fee, feeCurrency := s.aggregateOrderFee(o) + orderExecutedQuoteAmount, fee, feeCurrency := s.aggregateOrderQuoteAmountAndFee(o) s.logger.Infof("GRID ORDER #%d %s FEE: %s %s", o.OrderID, o.Side, fee.String(), feeCurrency) diff --git a/pkg/strategy/grid2/strategy_test.go b/pkg/strategy/grid2/strategy_test.go index a96a9ab61..9f592959f 100644 --- a/pkg/strategy/grid2/strategy_test.go +++ b/pkg/strategy/grid2/strategy_test.go @@ -651,7 +651,7 @@ func TestStrategy_calculateProfit(t *testing.T) { }) } -func TestStrategy_aggregateOrderBaseFee(t *testing.T) { +func TestStrategy_aggregateOrderQuoteAmountAndFee(t *testing.T) { s := newTestStrategy() mockCtrl := gomock.NewController(t) @@ -666,32 +666,34 @@ func TestStrategy_aggregateOrderBaseFee(t *testing.T) { OrderID: "3", }).Return([]types.Trade{ { - ID: 1, - OrderID: 3, - Exchange: "binance", - Price: number(20000.0), - Quantity: number(0.2), - Symbol: "BTCUSDT", - Side: types.SideTypeBuy, - IsBuyer: true, - FeeCurrency: "BTC", - Fee: number(0.2 * 0.01), + ID: 1, + OrderID: 3, + Exchange: "binance", + Price: number(20000.0), + Quantity: number(0.2), + QuoteQuantity: number(4000), + Symbol: "BTCUSDT", + Side: types.SideTypeBuy, + IsBuyer: true, + FeeCurrency: "BTC", + Fee: number(0.2 * 0.01), }, { - ID: 1, - OrderID: 3, - Exchange: "binance", - Price: number(20000.0), - Quantity: number(0.8), - Symbol: "BTCUSDT", - Side: types.SideTypeBuy, - IsBuyer: true, - FeeCurrency: "BTC", - Fee: number(0.8 * 0.01), + ID: 1, + OrderID: 3, + Exchange: "binance", + Price: number(20000.0), + Quantity: number(0.8), + QuoteQuantity: number(16000), + Symbol: "BTCUSDT", + Side: types.SideTypeBuy, + IsBuyer: true, + FeeCurrency: "BTC", + Fee: number(0.8 * 0.01), }, }, nil) - baseFee, _ := s.aggregateOrderFee(types.Order{ + quoteAmount, fee, _ := s.aggregateOrderQuoteAmountAndFee(types.Order{ SubmitOrder: types.SubmitOrder{ Symbol: "BTCUSDT", Side: types.SideTypeBuy, @@ -710,7 +712,8 @@ func TestStrategy_aggregateOrderBaseFee(t *testing.T) { ExecutedQuantity: number(1.0), IsWorking: false, }) - assert.Equal(t, "0.01", baseFee.String()) + assert.Equal(t, "0.01", fee.String()) + assert.Equal(t, "20000", quoteAmount.String()) } func TestStrategy_findDuplicatedPriceOpenOrders(t *testing.T) { @@ -1116,7 +1119,7 @@ func TestStrategy_handleOrderFilled(t *testing.T) { }) } -func TestStrategy_aggregateOrderBaseFeeRetry(t *testing.T) { +func TestStrategy_aggregateOrderQuoteAmountAndFeeRetry(t *testing.T) { s := newTestStrategy() mockCtrl := gomock.NewController(t) @@ -1161,7 +1164,7 @@ func TestStrategy_aggregateOrderBaseFeeRetry(t *testing.T) { }, }, nil) - baseFee, _ := s.aggregateOrderFee(types.Order{ + quoteAmount, fee, _ := s.aggregateOrderQuoteAmountAndFee(types.Order{ SubmitOrder: types.SubmitOrder{ Symbol: "BTCUSDT", Side: types.SideTypeBuy, @@ -1180,7 +1183,8 @@ func TestStrategy_aggregateOrderBaseFeeRetry(t *testing.T) { ExecutedQuantity: number(1.0), IsWorking: false, }) - assert.Equal(t, "0.01", baseFee.String()) + assert.Equal(t, "0.01", fee.String()) + assert.Equal(t, "20000", quoteAmount.String()) } func TestStrategy_checkMinimalQuoteInvestment(t *testing.T) { diff --git a/pkg/strategy/grid2/trade.go b/pkg/strategy/grid2/trade.go index c2a4d8eb6..dbb11bf34 100644 --- a/pkg/strategy/grid2/trade.go +++ b/pkg/strategy/grid2/trade.go @@ -29,3 +29,17 @@ func aggregateTradesQuantity(trades []types.Trade) fixedpoint.Value { } return tq } + +// aggregateTradesQuoteQuantity aggregates the quote quantity from the given trade slice +func aggregateTradesQuoteQuantity(trades []types.Trade) fixedpoint.Value { + quoteQuantity := fixedpoint.Zero + for _, t := range trades { + if t.QuoteQuantity.IsZero() { + quoteQuantity = quoteQuantity.Add(t.Price.Mul(t.Quantity)) + } else { + quoteQuantity = quoteQuantity.Add(t.QuoteQuantity) + } + } + + return quoteQuantity +}