mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-25 16:25:16 +00:00
Merge pull request #1391 from c9s/c9s/grid2-base-quote-fix
This commit is contained in:
commit
e773bb0e52
|
@ -796,6 +796,8 @@ func (s *Strategy) calculateBaseQuoteInvestmentQuantity(
|
|||
if numberOfSellOrders > 0 {
|
||||
numberOfSellOrders--
|
||||
}
|
||||
|
||||
s.logger.Infof("calculated number of sell orders: %d", numberOfSellOrders)
|
||||
}
|
||||
|
||||
// if the maxBaseQuantity is less than minQuantity, then we need to reduce the number of the sell orders
|
||||
|
@ -810,8 +812,12 @@ func (s *Strategy) calculateBaseQuoteInvestmentQuantity(
|
|||
s.Market.MinQuantity)
|
||||
|
||||
if baseQuantity.Compare(minBaseQuantity) <= 0 {
|
||||
s.logger.Infof("base quantity %s is less than min base quantity: %s, adjusting...", baseQuantity.String(), minBaseQuantity.String())
|
||||
|
||||
baseQuantity = s.Market.RoundUpQuantityByPrecision(minBaseQuantity)
|
||||
numberOfSellOrders = int(math.Floor(baseInvestment.Div(baseQuantity).Float64()))
|
||||
|
||||
s.logger.Infof("adjusted base quantity to %s", baseQuantity.String())
|
||||
}
|
||||
|
||||
s.logger.Infof("grid base investment sell orders: %d", numberOfSellOrders)
|
||||
|
@ -824,7 +830,8 @@ func (s *Strategy) calculateBaseQuoteInvestmentQuantity(
|
|||
// quoteInvestment = (p1 + p2 + p3) * q
|
||||
// maxBuyQuantity = quoteInvestment / (p1 + p2 + p3)
|
||||
si := -1
|
||||
for i := len(pins) - 1 - numberOfSellOrders; i >= 0; i-- {
|
||||
end := len(pins) - 1
|
||||
for i := end - numberOfSellOrders - 1; i >= 0; i-- {
|
||||
pin := pins[i]
|
||||
price := fixedpoint.Value(pin)
|
||||
|
||||
|
@ -844,6 +851,7 @@ func (s *Strategy) calculateBaseQuoteInvestmentQuantity(
|
|||
// requiredQuote = requiredQuote.Add(quantity.Mul(nextLowerPrice))
|
||||
totalQuotePrice = totalQuotePrice.Add(nextLowerPrice)
|
||||
}
|
||||
|
||||
} else {
|
||||
// for orders that buy
|
||||
if s.ProfitSpread.IsZero() && i+1 == si {
|
||||
|
@ -851,7 +859,7 @@ func (s *Strategy) calculateBaseQuoteInvestmentQuantity(
|
|||
}
|
||||
|
||||
// should never place a buy order at the upper price
|
||||
if i == len(pins)-1 {
|
||||
if i == end {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -859,8 +867,11 @@ func (s *Strategy) calculateBaseQuoteInvestmentQuantity(
|
|||
}
|
||||
}
|
||||
|
||||
s.logger.Infof("total quote price: %f", totalQuotePrice.Float64())
|
||||
if totalQuotePrice.Sign() > 0 && quoteInvestment.Sign() > 0 {
|
||||
quoteSideQuantity := quoteInvestment.Div(totalQuotePrice)
|
||||
|
||||
s.logger.Infof("quote side quantity: %f = %f / %f", quoteSideQuantity.Float64(), quoteInvestment.Float64(), totalQuotePrice.Float64())
|
||||
if numberOfSellOrders > 0 {
|
||||
return fixedpoint.Min(quoteSideQuantity, baseQuantity), nil
|
||||
}
|
||||
|
@ -1058,6 +1069,11 @@ func (s *Strategy) openGrid(ctx context.Context, session *bbgo.ExchangeSession)
|
|||
return err2
|
||||
}
|
||||
|
||||
if s.BaseGridNum > 0 {
|
||||
sell1 := fixedpoint.Value(s.grid.Pins[len(s.grid.Pins)-1-s.BaseGridNum])
|
||||
lastPrice = sell1.Sub(s.Market.TickSize)
|
||||
}
|
||||
|
||||
// check if base and quote are enough
|
||||
var totalBase = fixedpoint.Zero
|
||||
var totalQuote = fixedpoint.Zero
|
||||
|
@ -1432,6 +1448,8 @@ func calculateMinimalQuoteInvestment(market types.Market, grid *Grid) fixedpoint
|
|||
for i := len(pins) - 2; i >= 0; i-- {
|
||||
pin := pins[i]
|
||||
price := fixedpoint.Value(pin)
|
||||
|
||||
// TODO: should we round the quote here before adding?
|
||||
totalQuote = totalQuote.Add(price.Mul(minQuantity))
|
||||
}
|
||||
|
||||
|
|
|
@ -204,6 +204,123 @@ func TestStrategy_generateGridOrders(t *testing.T) {
|
|||
}, orders)
|
||||
})
|
||||
|
||||
t.Run("base and quote with predefined base grid num", func(t *testing.T) {
|
||||
gridNum := int64(22)
|
||||
upperPrice := number(35500.000000)
|
||||
lowerPrice := number(34450.000000)
|
||||
quoteInvestment := number(18.47)
|
||||
baseInvestment := number(0.010700)
|
||||
lastPrice := number(34522.930000)
|
||||
baseGridNum := int(20)
|
||||
|
||||
s := newTestStrategy()
|
||||
s.GridNum = gridNum
|
||||
s.BaseGridNum = baseGridNum
|
||||
s.LowerPrice = lowerPrice
|
||||
s.UpperPrice = upperPrice
|
||||
s.grid = NewGrid(lowerPrice, upperPrice, fixedpoint.NewFromInt(s.GridNum), s.Market.TickSize)
|
||||
s.grid.CalculateArithmeticPins()
|
||||
assert.Equal(t, 22, len(s.grid.Pins))
|
||||
|
||||
quantity, err := s.calculateBaseQuoteInvestmentQuantity(quoteInvestment, baseInvestment, lastPrice, s.grid.Pins)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "0.000535", quantity.String())
|
||||
|
||||
s.QuantityOrAmount.Quantity = quantity
|
||||
|
||||
orders, err := s.generateGridOrders(quoteInvestment, baseInvestment, lastPrice)
|
||||
assert.NoError(t, err)
|
||||
if !assert.Equal(t, 21, len(orders)) {
|
||||
for _, o := range orders {
|
||||
t.Logf("- %s %s", o.Price.String(), o.Side)
|
||||
}
|
||||
}
|
||||
|
||||
assertPriceSide(t, []PriceSideAssert{
|
||||
{number(35500.0), types.SideTypeSell},
|
||||
{number(35450.0), types.SideTypeSell},
|
||||
{number(35400.0), types.SideTypeSell},
|
||||
{number(35350.0), types.SideTypeSell},
|
||||
{number(35300.0), types.SideTypeSell},
|
||||
{number(35250.0), types.SideTypeSell},
|
||||
{number(35200.0), types.SideTypeSell},
|
||||
{number(35150.0), types.SideTypeSell},
|
||||
{number(35100.0), types.SideTypeSell},
|
||||
{number(35050.0), types.SideTypeSell},
|
||||
{number(35000.0), types.SideTypeSell},
|
||||
{number(34950.0), types.SideTypeSell},
|
||||
{number(34900.0), types.SideTypeSell},
|
||||
{number(34850.0), types.SideTypeSell},
|
||||
{number(34800.0), types.SideTypeSell},
|
||||
{number(34750.0), types.SideTypeSell},
|
||||
{number(34700.0), types.SideTypeSell},
|
||||
{number(34650.0), types.SideTypeSell},
|
||||
{number(34600.0), types.SideTypeSell},
|
||||
{number(34550.0), types.SideTypeSell},
|
||||
// -- fake trade price at 34549.9
|
||||
// -- 34500 should be empty
|
||||
{number(34450.0), types.SideTypeBuy},
|
||||
}, orders)
|
||||
})
|
||||
|
||||
t.Run("base and quote", func(t *testing.T) {
|
||||
gridNum := int64(22)
|
||||
upperPrice := number(35500.000000)
|
||||
lowerPrice := number(34450.000000)
|
||||
quoteInvestment := number(20.0)
|
||||
baseInvestment := number(0.010700)
|
||||
lastPrice := number(34522.930000)
|
||||
baseGridNum := int(0)
|
||||
|
||||
s := newTestStrategy()
|
||||
s.GridNum = gridNum
|
||||
s.BaseGridNum = baseGridNum
|
||||
s.LowerPrice = lowerPrice
|
||||
s.UpperPrice = upperPrice
|
||||
s.grid = NewGrid(lowerPrice, upperPrice, fixedpoint.NewFromInt(s.GridNum), s.Market.TickSize)
|
||||
s.grid.CalculateArithmeticPins()
|
||||
assert.Equal(t, 22, len(s.grid.Pins))
|
||||
|
||||
quantity, err := s.calculateBaseQuoteInvestmentQuantity(quoteInvestment, baseInvestment, lastPrice, s.grid.Pins)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "0.00029006", quantity.String())
|
||||
|
||||
s.QuantityOrAmount.Quantity = quantity
|
||||
|
||||
orders, err := s.generateGridOrders(quoteInvestment, baseInvestment, lastPrice)
|
||||
assert.NoError(t, err)
|
||||
if !assert.Equal(t, 21, len(orders)) {
|
||||
for _, o := range orders {
|
||||
t.Logf("- %s %s", o.Price.String(), o.Side)
|
||||
}
|
||||
}
|
||||
|
||||
assertPriceSide(t, []PriceSideAssert{
|
||||
{number(35500.0), types.SideTypeSell},
|
||||
{number(35450.0), types.SideTypeSell},
|
||||
{number(35400.0), types.SideTypeSell},
|
||||
{number(35350.0), types.SideTypeSell},
|
||||
{number(35300.0), types.SideTypeSell},
|
||||
{number(35250.0), types.SideTypeSell},
|
||||
{number(35200.0), types.SideTypeSell},
|
||||
{number(35150.0), types.SideTypeSell},
|
||||
{number(35100.0), types.SideTypeSell},
|
||||
{number(35050.0), types.SideTypeSell},
|
||||
{number(35000.0), types.SideTypeSell},
|
||||
{number(34950.0), types.SideTypeSell},
|
||||
{number(34900.0), types.SideTypeSell},
|
||||
{number(34850.0), types.SideTypeSell},
|
||||
{number(34800.0), types.SideTypeSell},
|
||||
{number(34750.0), types.SideTypeSell},
|
||||
{number(34700.0), types.SideTypeSell},
|
||||
{number(34650.0), types.SideTypeSell},
|
||||
{number(34600.0), types.SideTypeSell},
|
||||
{number(34550.0), types.SideTypeSell},
|
||||
// -- 34500 should be empty
|
||||
{number(34450.0), types.SideTypeBuy},
|
||||
}, orders)
|
||||
})
|
||||
|
||||
t.Run("base and quote with pre-calculated baseGridNumber", func(t *testing.T) {
|
||||
s := newTestStrategy()
|
||||
s.grid = NewGrid(s.LowerPrice, s.UpperPrice, fixedpoint.NewFromInt(s.GridNum), s.Market.TickSize)
|
||||
|
@ -519,11 +636,11 @@ func newTestMarket(symbol string) types.Market {
|
|||
BaseCurrency: "BTC",
|
||||
QuoteCurrency: "USDT",
|
||||
TickSize: number(0.01),
|
||||
StepSize: number(0.00001),
|
||||
StepSize: number(0.000001),
|
||||
PricePrecision: 2,
|
||||
VolumePrecision: 8,
|
||||
MinNotional: number(10.0),
|
||||
MinQuantity: number(0.001),
|
||||
MinNotional: number(8.0),
|
||||
MinQuantity: number(0.0003),
|
||||
}
|
||||
case "ETHUSDT":
|
||||
return types.Market{
|
||||
|
@ -534,7 +651,7 @@ func newTestMarket(symbol string) types.Market {
|
|||
PricePrecision: 2,
|
||||
VolumePrecision: 6,
|
||||
MinNotional: number(8.000),
|
||||
MinQuantity: number(0.00030),
|
||||
MinQuantity: number(0.0046),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -577,12 +694,17 @@ func newTestOrder(price, quantity fixedpoint.Value, side types.SideType) types.O
|
|||
}
|
||||
}
|
||||
|
||||
func newTestStrategy() *Strategy {
|
||||
market := newTestMarket("BTCUSDT")
|
||||
func newTestStrategy(va ...string) *Strategy {
|
||||
symbol := "BTCUSDT"
|
||||
|
||||
if len(va) > 0 {
|
||||
symbol = va[0]
|
||||
}
|
||||
|
||||
market := newTestMarket(symbol)
|
||||
s := &Strategy{
|
||||
logger: logrus.NewEntry(logrus.New()),
|
||||
Symbol: "BTCUSDT",
|
||||
Symbol: symbol,
|
||||
Market: market,
|
||||
GridProfitStats: newGridProfitStats(market),
|
||||
UpperPrice: number(20_000),
|
||||
|
@ -790,7 +912,9 @@ func TestStrategy_handleOrderFilled(t *testing.T) {
|
|||
}
|
||||
|
||||
orderExecutor := gridmocks.NewMockOrderExecutor(mockCtrl)
|
||||
orderExecutor.EXPECT().SubmitOrders(ctx, gomock.Any()).DoAndReturn(func(ctx context.Context, order types.SubmitOrder) (types.OrderSlice, error) {
|
||||
orderExecutor.EXPECT().SubmitOrders(ctx, gomock.Any()).DoAndReturn(func(
|
||||
ctx context.Context, order types.SubmitOrder,
|
||||
) (types.OrderSlice, error) {
|
||||
assert.True(t, equalOrdersIgnoreClientOrderID(expectedSubmitOrder, order), "%+v is not equal to %+v", order, expectedSubmitOrder)
|
||||
return []types.Order{
|
||||
{SubmitOrder: expectedSubmitOrder},
|
||||
|
@ -858,7 +982,9 @@ func TestStrategy_handleOrderFilled(t *testing.T) {
|
|||
}
|
||||
|
||||
orderExecutor := gridmocks.NewMockOrderExecutor(mockCtrl)
|
||||
orderExecutor.EXPECT().SubmitOrders(ctx, gomock.Any()).DoAndReturn(func(ctx context.Context, order types.SubmitOrder) (types.OrderSlice, error) {
|
||||
orderExecutor.EXPECT().SubmitOrders(ctx, gomock.Any()).DoAndReturn(func(
|
||||
ctx context.Context, order types.SubmitOrder,
|
||||
) (types.OrderSlice, error) {
|
||||
assert.True(t, equalOrdersIgnoreClientOrderID(expectedSubmitOrder, order), "%+v is not equal to %+v", order, expectedSubmitOrder)
|
||||
return []types.Order{
|
||||
{SubmitOrder: expectedSubmitOrder},
|
||||
|
@ -946,7 +1072,9 @@ func TestStrategy_handleOrderFilled(t *testing.T) {
|
|||
Market: s.Market,
|
||||
Tag: orderTag,
|
||||
}
|
||||
orderExecutor.EXPECT().SubmitOrders(ctx, gomock.Any()).DoAndReturn(func(ctx context.Context, order types.SubmitOrder) (types.OrderSlice, error) {
|
||||
orderExecutor.EXPECT().SubmitOrders(ctx, gomock.Any()).DoAndReturn(func(
|
||||
ctx context.Context, order types.SubmitOrder,
|
||||
) (types.OrderSlice, error) {
|
||||
assert.True(t, equalOrdersIgnoreClientOrderID(expectedSubmitOrder, order), "%+v is not equal to %+v", order, expectedSubmitOrder)
|
||||
return []types.Order{
|
||||
{SubmitOrder: expectedSubmitOrder},
|
||||
|
@ -963,7 +1091,9 @@ func TestStrategy_handleOrderFilled(t *testing.T) {
|
|||
Market: s.Market,
|
||||
Tag: orderTag,
|
||||
}
|
||||
orderExecutor.EXPECT().SubmitOrders(ctx, gomock.Any()).DoAndReturn(func(ctx context.Context, order types.SubmitOrder) (types.OrderSlice, error) {
|
||||
orderExecutor.EXPECT().SubmitOrders(ctx, gomock.Any()).DoAndReturn(func(
|
||||
ctx context.Context, order types.SubmitOrder,
|
||||
) (types.OrderSlice, error) {
|
||||
assert.True(t, equalOrdersIgnoreClientOrderID(expectedSubmitOrder2, order), "%+v is not equal to %+v", order, expectedSubmitOrder2)
|
||||
return []types.Order{
|
||||
{SubmitOrder: expectedSubmitOrder2},
|
||||
|
@ -1060,7 +1190,9 @@ func TestStrategy_handleOrderFilled(t *testing.T) {
|
|||
}
|
||||
|
||||
orderExecutor := gridmocks.NewMockOrderExecutor(mockCtrl)
|
||||
orderExecutor.EXPECT().SubmitOrders(ctx, gomock.Any()).DoAndReturn(func(ctx context.Context, order types.SubmitOrder) (types.OrderSlice, error) {
|
||||
orderExecutor.EXPECT().SubmitOrders(ctx, gomock.Any()).DoAndReturn(func(
|
||||
ctx context.Context, order types.SubmitOrder,
|
||||
) (types.OrderSlice, error) {
|
||||
assert.True(t, equalOrdersIgnoreClientOrderID(expectedSubmitOrder, order), "%+v is not equal to %+v", order, expectedSubmitOrder)
|
||||
return []types.Order{
|
||||
{SubmitOrder: expectedSubmitOrder},
|
||||
|
@ -1078,7 +1210,9 @@ func TestStrategy_handleOrderFilled(t *testing.T) {
|
|||
Tag: orderTag,
|
||||
}
|
||||
|
||||
orderExecutor.EXPECT().SubmitOrders(ctx, gomock.Any()).DoAndReturn(func(ctx context.Context, order types.SubmitOrder) (types.OrderSlice, error) {
|
||||
orderExecutor.EXPECT().SubmitOrders(ctx, gomock.Any()).DoAndReturn(func(
|
||||
ctx context.Context, order types.SubmitOrder,
|
||||
) (types.OrderSlice, error) {
|
||||
assert.True(t, equalOrdersIgnoreClientOrderID(expectedSubmitOrder2, order), "%+v is not equal to %+v", order, expectedSubmitOrder2)
|
||||
return []types.Order{
|
||||
{SubmitOrder: expectedSubmitOrder2},
|
||||
|
@ -1190,14 +1324,14 @@ func TestStrategy_aggregateOrderQuoteAmountAndFeeRetry(t *testing.T) {
|
|||
func TestStrategy_checkMinimalQuoteInvestment(t *testing.T) {
|
||||
|
||||
t.Run("7 grids", func(t *testing.T) {
|
||||
s := newTestStrategy()
|
||||
s := newTestStrategy("ETHUSDT")
|
||||
s.UpperPrice = number(1660)
|
||||
s.LowerPrice = number(1630)
|
||||
s.QuoteInvestment = number(61)
|
||||
s.GridNum = 7
|
||||
grid := s.newGrid()
|
||||
minQuoteInvestment := calculateMinimalQuoteInvestment(s.Market, grid)
|
||||
assert.InDelta(t, 60.46, minQuoteInvestment.Float64(), 0.01)
|
||||
assert.InDelta(t, 48.36, minQuoteInvestment.Float64(), 0.01)
|
||||
|
||||
err := s.checkMinimalQuoteInvestment(grid)
|
||||
assert.NoError(t, err)
|
||||
|
@ -1207,12 +1341,11 @@ func TestStrategy_checkMinimalQuoteInvestment(t *testing.T) {
|
|||
s := newTestStrategy()
|
||||
// 10_000 * 0.001 = 10USDT
|
||||
// 20_000 * 0.001 = 20USDT
|
||||
// hence we should have at least: 20USDT * 10 grids
|
||||
s.QuoteInvestment = number(10_000)
|
||||
s.GridNum = 10
|
||||
grid := s.newGrid()
|
||||
minQuoteInvestment := calculateMinimalQuoteInvestment(s.Market, grid)
|
||||
assert.InDelta(t, 129.9999, minQuoteInvestment.Float64(), 0.01)
|
||||
assert.InDelta(t, 103.999, minQuoteInvestment.Float64(), 0.01)
|
||||
|
||||
err := s.checkMinimalQuoteInvestment(grid)
|
||||
assert.NoError(t, err)
|
||||
|
@ -1225,11 +1358,11 @@ func TestStrategy_checkMinimalQuoteInvestment(t *testing.T) {
|
|||
|
||||
grid := s.newGrid()
|
||||
minQuoteInvestment := calculateMinimalQuoteInvestment(s.Market, grid)
|
||||
assert.InDelta(t, 14979.995499, minQuoteInvestment.Float64(), 0.001)
|
||||
assert.InDelta(t, 11983.996400, minQuoteInvestment.Float64(), 0.001)
|
||||
|
||||
err := s.checkMinimalQuoteInvestment(grid)
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, err, "need at least 14979.995500 USDT for quote investment, 10000.000000 USDT given")
|
||||
assert.EqualError(t, err, "need at least 11983.996400 USDT for quote investment, 10000.000000 USDT given")
|
||||
})
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user