mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
Merge pull request #1302 from c9s/feature/grid2/use-quote-quantity
FEATURE: use quote quantity if there is QuoteQuantity in trade
This commit is contained in:
commit
6d0c266513
|
@ -302,7 +302,7 @@ func convertWebSocketTrade(t max.TradeUpdate) (*types.Trade, error) {
|
||||||
Fee: t.Fee,
|
Fee: t.Fee,
|
||||||
FeeCurrency: toGlobalCurrency(t.FeeCurrency),
|
FeeCurrency: toGlobalCurrency(t.FeeCurrency),
|
||||||
FeeDiscounted: t.FeeDiscounted,
|
FeeDiscounted: t.FeeDiscounted,
|
||||||
QuoteQuantity: t.Price.Mul(t.Volume),
|
QuoteQuantity: t.Funds,
|
||||||
Time: types.Time(t.Timestamp.Time()),
|
Time: types.Time(t.Timestamp.Time()),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,6 +98,7 @@ type TradeUpdate struct {
|
||||||
Side string `json:"sd"`
|
Side string `json:"sd"`
|
||||||
Price fixedpoint.Value `json:"p"`
|
Price fixedpoint.Value `json:"p"`
|
||||||
Volume fixedpoint.Value `json:"v"`
|
Volume fixedpoint.Value `json:"v"`
|
||||||
|
Funds fixedpoint.Value `json:"fn"`
|
||||||
Market string `json:"M"`
|
Market string `json:"M"`
|
||||||
|
|
||||||
Fee fixedpoint.Value `json:"f"`
|
Fee fixedpoint.Value `json:"f"`
|
||||||
|
|
|
@ -378,9 +378,9 @@ func (s *Strategy) verifyOrderTrades(o types.Order, trades []types.Trade) bool {
|
||||||
return true
|
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.
|
// 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)
|
// try to get the received trades (websocket trades)
|
||||||
orderTrades := s.historicalTrades.GetOrderTrades(o)
|
orderTrades := s.historicalTrades.GetOrderTrades(o)
|
||||||
if len(orderTrades) > 0 {
|
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 one of the trades is missing, we need to query the trades from the RESTful API
|
||||||
if s.verifyOrderTrades(o, orderTrades) {
|
if s.verifyOrderTrades(o, orderTrades) {
|
||||||
// if trades are verified
|
// if trades are verified
|
||||||
|
quoteAmount := aggregateTradesQuoteQuantity(orderTrades)
|
||||||
fees := collectTradeFee(orderTrades)
|
fees := collectTradeFee(orderTrades)
|
||||||
if fee, ok := fees[feeCurrency]; ok {
|
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 we don't support orderQueryService, then we should just skip
|
||||||
if s.orderQueryService == nil {
|
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)
|
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:
|
// still try to aggregate the trades quantity if we can:
|
||||||
fees := collectTradeFee(orderTrades)
|
fees := collectTradeFee(orderTrades)
|
||||||
if fee, ok := fees[feeCurrency]; ok {
|
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) {
|
func (s *Strategy) processFilledOrder(o types.Order) {
|
||||||
|
@ -446,7 +448,6 @@ func (s *Strategy) processFilledOrder(o types.Order) {
|
||||||
}
|
}
|
||||||
|
|
||||||
newQuantity := executedQuantity
|
newQuantity := executedQuantity
|
||||||
executedPrice := o.Price
|
|
||||||
|
|
||||||
if o.ExecutedQuantity.Compare(o.Quantity) != 0 {
|
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)
|
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
|
// collect trades for fee
|
||||||
// fee calculation is used to reduce the order quantity
|
// 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
|
// 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
|
// 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",
|
s.logger.Infof("GRID ORDER #%d %s FEE: %s %s",
|
||||||
o.OrderID, o.Side,
|
o.OrderID, o.Side,
|
||||||
fee.String(), feeCurrency)
|
fee.String(), feeCurrency)
|
||||||
|
|
|
@ -651,7 +651,7 @@ func TestStrategy_calculateProfit(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStrategy_aggregateOrderBaseFee(t *testing.T) {
|
func TestStrategy_aggregateOrderQuoteAmountAndFee(t *testing.T) {
|
||||||
s := newTestStrategy()
|
s := newTestStrategy()
|
||||||
|
|
||||||
mockCtrl := gomock.NewController(t)
|
mockCtrl := gomock.NewController(t)
|
||||||
|
@ -666,32 +666,34 @@ func TestStrategy_aggregateOrderBaseFee(t *testing.T) {
|
||||||
OrderID: "3",
|
OrderID: "3",
|
||||||
}).Return([]types.Trade{
|
}).Return([]types.Trade{
|
||||||
{
|
{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
OrderID: 3,
|
OrderID: 3,
|
||||||
Exchange: "binance",
|
Exchange: "binance",
|
||||||
Price: number(20000.0),
|
Price: number(20000.0),
|
||||||
Quantity: number(0.2),
|
Quantity: number(0.2),
|
||||||
Symbol: "BTCUSDT",
|
QuoteQuantity: number(4000),
|
||||||
Side: types.SideTypeBuy,
|
Symbol: "BTCUSDT",
|
||||||
IsBuyer: true,
|
Side: types.SideTypeBuy,
|
||||||
FeeCurrency: "BTC",
|
IsBuyer: true,
|
||||||
Fee: number(0.2 * 0.01),
|
FeeCurrency: "BTC",
|
||||||
|
Fee: number(0.2 * 0.01),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
OrderID: 3,
|
OrderID: 3,
|
||||||
Exchange: "binance",
|
Exchange: "binance",
|
||||||
Price: number(20000.0),
|
Price: number(20000.0),
|
||||||
Quantity: number(0.8),
|
Quantity: number(0.8),
|
||||||
Symbol: "BTCUSDT",
|
QuoteQuantity: number(16000),
|
||||||
Side: types.SideTypeBuy,
|
Symbol: "BTCUSDT",
|
||||||
IsBuyer: true,
|
Side: types.SideTypeBuy,
|
||||||
FeeCurrency: "BTC",
|
IsBuyer: true,
|
||||||
Fee: number(0.8 * 0.01),
|
FeeCurrency: "BTC",
|
||||||
|
Fee: number(0.8 * 0.01),
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
baseFee, _ := s.aggregateOrderFee(types.Order{
|
quoteAmount, fee, _ := s.aggregateOrderQuoteAmountAndFee(types.Order{
|
||||||
SubmitOrder: types.SubmitOrder{
|
SubmitOrder: types.SubmitOrder{
|
||||||
Symbol: "BTCUSDT",
|
Symbol: "BTCUSDT",
|
||||||
Side: types.SideTypeBuy,
|
Side: types.SideTypeBuy,
|
||||||
|
@ -710,7 +712,8 @@ func TestStrategy_aggregateOrderBaseFee(t *testing.T) {
|
||||||
ExecutedQuantity: number(1.0),
|
ExecutedQuantity: number(1.0),
|
||||||
IsWorking: false,
|
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) {
|
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()
|
s := newTestStrategy()
|
||||||
|
|
||||||
mockCtrl := gomock.NewController(t)
|
mockCtrl := gomock.NewController(t)
|
||||||
|
@ -1161,7 +1164,7 @@ func TestStrategy_aggregateOrderBaseFeeRetry(t *testing.T) {
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
baseFee, _ := s.aggregateOrderFee(types.Order{
|
quoteAmount, fee, _ := s.aggregateOrderQuoteAmountAndFee(types.Order{
|
||||||
SubmitOrder: types.SubmitOrder{
|
SubmitOrder: types.SubmitOrder{
|
||||||
Symbol: "BTCUSDT",
|
Symbol: "BTCUSDT",
|
||||||
Side: types.SideTypeBuy,
|
Side: types.SideTypeBuy,
|
||||||
|
@ -1180,7 +1183,8 @@ func TestStrategy_aggregateOrderBaseFeeRetry(t *testing.T) {
|
||||||
ExecutedQuantity: number(1.0),
|
ExecutedQuantity: number(1.0),
|
||||||
IsWorking: false,
|
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) {
|
func TestStrategy_checkMinimalQuoteInvestment(t *testing.T) {
|
||||||
|
|
|
@ -29,3 +29,17 @@ func aggregateTradesQuantity(trades []types.Trade) fixedpoint.Value {
|
||||||
}
|
}
|
||||||
return tq
|
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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user