mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-26 08:45:16 +00:00
Merge pull request #1172 from c9s/c9s/grid2/base-quote
FEATURE: [grid2] truncate base quantity for quote+base mode
This commit is contained in:
commit
14849afe4e
|
@ -777,20 +777,30 @@ func (s *Strategy) calculateBaseQuoteInvestmentQuantity(quoteInvestment, baseInv
|
||||||
numberOfSellOrders++
|
numberOfSellOrders++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// avoid placing a sell order above the last price
|
||||||
|
if numberOfSellOrders > 0 {
|
||||||
|
numberOfSellOrders--
|
||||||
|
}
|
||||||
|
|
||||||
// if the maxBaseQuantity is less than minQuantity, then we need to reduce the number of the sell orders
|
// if the maxBaseQuantity is less than minQuantity, then we need to reduce the number of the sell orders
|
||||||
// so that the quantity can be increased.
|
// so that the quantity can be increased.
|
||||||
maxNumberOfSellOrders := numberOfSellOrders + 1
|
baseQuantity := s.Market.TruncateQuantity(
|
||||||
minBaseQuantity := fixedpoint.Max(s.Market.MinNotional.Div(lastPrice), s.Market.MinQuantity)
|
baseInvestment.Div(
|
||||||
maxBaseQuantity := fixedpoint.Zero
|
fixedpoint.NewFromInt(
|
||||||
for maxBaseQuantity.Compare(s.Market.MinQuantity) <= 0 || maxBaseQuantity.Compare(minBaseQuantity) <= 0 {
|
int64(numberOfSellOrders))))
|
||||||
maxNumberOfSellOrders--
|
|
||||||
maxBaseQuantity = baseInvestment.Div(fixedpoint.NewFromInt(int64(maxNumberOfSellOrders)))
|
minBaseQuantity := fixedpoint.Max(
|
||||||
}
|
s.Market.MinNotional.Div(s.UpperPrice),
|
||||||
s.logger.Infof("grid base investment sell orders: %d", maxNumberOfSellOrders)
|
s.Market.MinQuantity)
|
||||||
if maxNumberOfSellOrders > 0 {
|
|
||||||
s.logger.Infof("grid base investment quantity: %f (base investment) / %d (number of sell orders) = %f (base quantity per order)", baseInvestment.Float64(), maxNumberOfSellOrders, maxBaseQuantity.Float64())
|
if baseQuantity.Compare(minBaseQuantity) <= 0 {
|
||||||
|
baseQuantity = s.Market.RoundUpQuantityByPrecision(minBaseQuantity)
|
||||||
|
numberOfSellOrders = int(math.Floor(baseInvestment.Div(baseQuantity).Float64()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.logger.Infof("grid base investment sell orders: %d", numberOfSellOrders)
|
||||||
|
s.logger.Infof("grid base investment quantity: %f (base investment) / %d (number of sell orders) = %f (base quantity per order)", baseInvestment.Float64(), numberOfSellOrders, baseQuantity.Float64())
|
||||||
|
|
||||||
// calculate quantity with quote investment
|
// calculate quantity with quote investment
|
||||||
totalQuotePrice := fixedpoint.Zero
|
totalQuotePrice := fixedpoint.Zero
|
||||||
// quoteInvestment = (p1 * q) + (p2 * q) + (p3 * q) + ....
|
// quoteInvestment = (p1 * q) + (p2 * q) + (p3 * q) + ....
|
||||||
|
@ -798,7 +808,7 @@ func (s *Strategy) calculateBaseQuoteInvestmentQuantity(quoteInvestment, baseInv
|
||||||
// quoteInvestment = (p1 + p2 + p3) * q
|
// quoteInvestment = (p1 + p2 + p3) * q
|
||||||
// maxBuyQuantity = quoteInvestment / (p1 + p2 + p3)
|
// maxBuyQuantity = quoteInvestment / (p1 + p2 + p3)
|
||||||
si := -1
|
si := -1
|
||||||
for i := len(pins) - 1 - maxNumberOfSellOrders; i >= 0; i-- {
|
for i := len(pins) - 1 - numberOfSellOrders; i >= 0; i-- {
|
||||||
pin := pins[i]
|
pin := pins[i]
|
||||||
price := fixedpoint.Value(pin)
|
price := fixedpoint.Value(pin)
|
||||||
|
|
||||||
|
@ -834,8 +844,8 @@ func (s *Strategy) calculateBaseQuoteInvestmentQuantity(quoteInvestment, baseInv
|
||||||
}
|
}
|
||||||
|
|
||||||
quoteSideQuantity := quoteInvestment.Div(totalQuotePrice)
|
quoteSideQuantity := quoteInvestment.Div(totalQuotePrice)
|
||||||
if maxNumberOfSellOrders > 0 {
|
if numberOfSellOrders > 0 {
|
||||||
return fixedpoint.Min(quoteSideQuantity, maxBaseQuantity), nil
|
return fixedpoint.Min(quoteSideQuantity, baseQuantity), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return quoteSideQuantity, nil
|
return quoteSideQuantity, nil
|
||||||
|
@ -1323,7 +1333,7 @@ func (s *Strategy) generateGridOrders(totalQuote, totalBase, lastPrice fixedpoin
|
||||||
if price.Compare(lastPrice) >= 0 {
|
if price.Compare(lastPrice) >= 0 {
|
||||||
si = i
|
si = i
|
||||||
|
|
||||||
// do not place sell order when i == 0
|
// do not place sell order when i == 0 (the bottom of grid)
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -285,8 +285,58 @@ func TestStrategy_checkRequiredInvestmentByAmount(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStrategy_calculateQuoteInvestmentQuantity(t *testing.T) {
|
func TestStrategy_calculateBaseQuoteInvestmentQuantity(t *testing.T) {
|
||||||
|
t.Run("1 sell", func(t *testing.T) {
|
||||||
|
s := newTestStrategy()
|
||||||
|
s.Market = newTestMarket("ETHUSDT")
|
||||||
|
s.UpperPrice = number(200.0)
|
||||||
|
s.LowerPrice = number(100.0)
|
||||||
|
s.GridNum = 7
|
||||||
|
s.Compound = true
|
||||||
|
|
||||||
|
lastPrice := number(180.0)
|
||||||
|
quoteInvestment := number(334.0) // 333.33
|
||||||
|
baseInvestment := number(0.5)
|
||||||
|
quantity, err := s.calculateBaseQuoteInvestmentQuantity(quoteInvestment, baseInvestment, lastPrice, []Pin{
|
||||||
|
Pin(number(100.00)),
|
||||||
|
Pin(number(116.67)),
|
||||||
|
Pin(number(133.33)),
|
||||||
|
Pin(number(150.00)),
|
||||||
|
Pin(number(166.67)),
|
||||||
|
Pin(number(183.33)),
|
||||||
|
Pin(number(200.00)),
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.InDelta(t, 0.5, quantity.Float64(), 0.0001)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("6 sell", func(t *testing.T) {
|
||||||
|
s := newTestStrategy()
|
||||||
|
s.Market = newTestMarket("ETHUSDT")
|
||||||
|
s.UpperPrice = number(200.0)
|
||||||
|
s.LowerPrice = number(100.0)
|
||||||
|
s.GridNum = 7
|
||||||
|
s.Compound = true
|
||||||
|
|
||||||
|
lastPrice := number(95.0)
|
||||||
|
quoteInvestment := number(334.0) // 333.33
|
||||||
|
baseInvestment := number(0.5)
|
||||||
|
quantity, err := s.calculateBaseQuoteInvestmentQuantity(quoteInvestment, baseInvestment, lastPrice, []Pin{
|
||||||
|
Pin(number(100.00)),
|
||||||
|
Pin(number(116.67)),
|
||||||
|
Pin(number(133.33)),
|
||||||
|
Pin(number(150.00)),
|
||||||
|
Pin(number(166.67)),
|
||||||
|
Pin(number(183.33)),
|
||||||
|
Pin(number(200.00)),
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.InDelta(t, 0.08333, quantity.Float64(), 0.0001)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStrategy_calculateQuoteInvestmentQuantity(t *testing.T) {
|
||||||
t.Run("quote quantity", func(t *testing.T) {
|
t.Run("quote quantity", func(t *testing.T) {
|
||||||
// quoteInvestment = (10,000 + 11,000 + 12,000 + 13,000 + 14,000) * q
|
// quoteInvestment = (10,000 + 11,000 + 12,000 + 13,000 + 14,000) * q
|
||||||
// q = quoteInvestment / (10,000 + 11,000 + 12,000 + 13,000 + 14,000)
|
// q = quoteInvestment / (10,000 + 11,000 + 12,000 + 13,000 + 14,000)
|
||||||
|
@ -375,11 +425,38 @@ func TestStrategy_calculateQuoteInvestmentQuantity(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestMarket() types.Market {
|
func newTestMarket(symbol string) types.Market {
|
||||||
|
switch symbol {
|
||||||
|
case "BTCUSDT":
|
||||||
|
return types.Market{
|
||||||
|
BaseCurrency: "BTC",
|
||||||
|
QuoteCurrency: "USDT",
|
||||||
|
TickSize: number(0.01),
|
||||||
|
StepSize: number(0.00001),
|
||||||
|
PricePrecision: 2,
|
||||||
|
VolumePrecision: 8,
|
||||||
|
MinNotional: number(10.0),
|
||||||
|
MinQuantity: number(0.001),
|
||||||
|
}
|
||||||
|
case "ETHUSDT":
|
||||||
|
return types.Market{
|
||||||
|
BaseCurrency: "ETH",
|
||||||
|
QuoteCurrency: "USDT",
|
||||||
|
TickSize: number(0.01),
|
||||||
|
StepSize: number(0.00001),
|
||||||
|
PricePrecision: 2,
|
||||||
|
VolumePrecision: 6,
|
||||||
|
MinNotional: number(8.000),
|
||||||
|
MinQuantity: number(0.00030),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// default
|
||||||
return types.Market{
|
return types.Market{
|
||||||
BaseCurrency: "BTC",
|
BaseCurrency: "BTC",
|
||||||
QuoteCurrency: "USDT",
|
QuoteCurrency: "USDT",
|
||||||
TickSize: number(0.01),
|
TickSize: number(0.01),
|
||||||
|
StepSize: number(0.00001),
|
||||||
PricePrecision: 2,
|
PricePrecision: 2,
|
||||||
VolumePrecision: 8,
|
VolumePrecision: 8,
|
||||||
MinNotional: number(10.0),
|
MinNotional: number(10.0),
|
||||||
|
@ -390,7 +467,7 @@ func newTestMarket() types.Market {
|
||||||
var testOrderID = uint64(0)
|
var testOrderID = uint64(0)
|
||||||
|
|
||||||
func newTestOrder(price, quantity fixedpoint.Value, side types.SideType) types.Order {
|
func newTestOrder(price, quantity fixedpoint.Value, side types.SideType) types.Order {
|
||||||
market := newTestMarket()
|
market := newTestMarket("BTCUSDT")
|
||||||
testOrderID++
|
testOrderID++
|
||||||
return types.Order{
|
return types.Order{
|
||||||
SubmitOrder: types.SubmitOrder{
|
SubmitOrder: types.SubmitOrder{
|
||||||
|
@ -414,7 +491,7 @@ func newTestOrder(price, quantity fixedpoint.Value, side types.SideType) types.O
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestStrategy() *Strategy {
|
func newTestStrategy() *Strategy {
|
||||||
market := newTestMarket()
|
market := newTestMarket("BTCUSDT")
|
||||||
|
|
||||||
s := &Strategy{
|
s := &Strategy{
|
||||||
logger: logrus.NewEntry(logrus.New()),
|
logger: logrus.NewEntry(logrus.New()),
|
||||||
|
|
|
@ -70,6 +70,17 @@ func (m Market) TruncateQuantity(quantity fixedpoint.Value) fixedpoint.Value {
|
||||||
return fixedpoint.MustNewFromString(qs)
|
return fixedpoint.MustNewFromString(qs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RoundDownQuantityByPrecision uses the volume precision to round down the quantity
|
||||||
|
// This is different from the TruncateQuantity, which uses StepSize (it uses fewer fractions to truncate)
|
||||||
|
func (m Market) RoundDownQuantityByPrecision(quantity fixedpoint.Value) fixedpoint.Value {
|
||||||
|
return quantity.Round(m.VolumePrecision, fixedpoint.Down)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundUpQuantityByPrecision uses the volume precision to round up the quantity
|
||||||
|
func (m Market) RoundUpQuantityByPrecision(quantity fixedpoint.Value) fixedpoint.Value {
|
||||||
|
return quantity.Round(m.VolumePrecision, fixedpoint.Up)
|
||||||
|
}
|
||||||
|
|
||||||
func (m Market) TruncatePrice(price fixedpoint.Value) fixedpoint.Value {
|
func (m Market) TruncatePrice(price fixedpoint.Value) fixedpoint.Value {
|
||||||
return fixedpoint.MustNewFromString(m.FormatPrice(price))
|
return fixedpoint.MustNewFromString(m.FormatPrice(price))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user