mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-25 16:25:16 +00:00
FEATURE: use notional based to crease dca maker orders
This commit is contained in:
parent
60003fc472
commit
4aa6ea3a46
|
@ -9,14 +9,14 @@ import (
|
|||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
func (s *Strategy) placeMakerOrders(ctx context.Context) error {
|
||||
func (s *Strategy) placeDCAOrders(ctx context.Context) error {
|
||||
s.logger.Infof("[DCA] start placing maker orders")
|
||||
price, err := s.getBestPriceUntilSuccess(ctx, s.Short)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
orders, err := s.generateMakerOrder(s.Short, s.Budget, price, s.PriceDeviation, s.MaxOrderNum)
|
||||
orders, err := s.generateDCAOrders(s.Short, s.Budget, price, s.PriceDeviation, s.MaxOrderNum)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -50,47 +50,35 @@ func (s *Strategy) getBestPriceUntilSuccess(ctx context.Context, short bool) (fi
|
|||
return fixedpoint.Zero, err
|
||||
}
|
||||
|
||||
func (s *Strategy) generateMakerOrder(short bool, budget, price, margin fixedpoint.Value, orderNum int64) ([]types.SubmitOrder, error) {
|
||||
marginPrice := price.Mul(margin)
|
||||
if !short {
|
||||
marginPrice = marginPrice.Neg()
|
||||
}
|
||||
|
||||
func (s *Strategy) generateDCAOrders(short bool, budget, price, priceDeviation fixedpoint.Value, maxOrderNum int64) ([]types.SubmitOrder, error) {
|
||||
// TODO: not implement short part yet
|
||||
var prices []fixedpoint.Value
|
||||
var total fixedpoint.Value
|
||||
for i := 0; i < int(orderNum); i++ {
|
||||
price = price.Add(marginPrice)
|
||||
truncatePrice := s.Market.TruncatePrice(price)
|
||||
|
||||
// need to avoid the price is below 0
|
||||
if truncatePrice.Compare(fixedpoint.Zero) <= 0 {
|
||||
break
|
||||
}
|
||||
|
||||
prices = append(prices, truncatePrice)
|
||||
total = total.Add(truncatePrice)
|
||||
factor := fixedpoint.One.Sub(priceDeviation)
|
||||
if short {
|
||||
factor = fixedpoint.One.Add(priceDeviation)
|
||||
}
|
||||
|
||||
quantity := fixedpoint.Zero
|
||||
l := len(prices) - 1
|
||||
for ; l >= 0; l-- {
|
||||
if total.IsZero() {
|
||||
return nil, fmt.Errorf("total is zero, please check it")
|
||||
// calculate all valid prices
|
||||
var prices []fixedpoint.Value
|
||||
for i := 0; i < int(maxOrderNum); i++ {
|
||||
if i > 0 {
|
||||
price = price.Mul(factor)
|
||||
}
|
||||
|
||||
quantity = budget.Div(total)
|
||||
quantity = s.Market.TruncateQuantity(quantity)
|
||||
if prices[l].Mul(quantity).Compare(s.Market.MinNotional) > 0 {
|
||||
price = s.Market.TruncatePrice(price)
|
||||
if price.Compare(s.Market.MinPrice) < 0 {
|
||||
break
|
||||
}
|
||||
|
||||
total = total.Sub(prices[l])
|
||||
prices = append(prices, price)
|
||||
}
|
||||
|
||||
notional, orderNum := calculateDCAMakerOrderNotionalAndNum(s.Market, short, budget, prices)
|
||||
if orderNum == 0 {
|
||||
return nil, fmt.Errorf("failed to calculate DCA maker order notional and num, price: %s, budget: %s", price, budget)
|
||||
}
|
||||
|
||||
var submitOrders []types.SubmitOrder
|
||||
|
||||
for i := 0; i <= l; i++ {
|
||||
for i := 0; i < orderNum; i++ {
|
||||
quantity := s.Market.TruncateQuantity(notional.Div(prices[i]))
|
||||
submitOrders = append(submitOrders, types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
Market: s.Market,
|
||||
|
@ -106,3 +94,27 @@ func (s *Strategy) generateMakerOrder(short bool, budget, price, margin fixedpoi
|
|||
|
||||
return submitOrders, nil
|
||||
}
|
||||
|
||||
// calculateDCAMakerNotionalAndNum will calculate the notional and num of DCA orders
|
||||
// DCA2 is notional-based, every order has the same notional
|
||||
func calculateDCAMakerOrderNotionalAndNum(market types.Market, short bool, budget fixedpoint.Value, prices []fixedpoint.Value) (fixedpoint.Value, int) {
|
||||
for num := len(prices); num > 0; num-- {
|
||||
notional := budget.Div(fixedpoint.NewFromInt(int64(num)))
|
||||
if notional.Compare(market.MinNotional) < 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
maxPriceIdx := 0
|
||||
if short {
|
||||
maxPriceIdx = num - 1
|
||||
}
|
||||
quantity := market.TruncateQuantity(notional.Div(prices[maxPriceIdx]))
|
||||
if quantity.Compare(market.MinQuantity) < 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
return notional, num
|
||||
}
|
||||
|
||||
return fixedpoint.Zero, 0
|
||||
}
|
||||
|
|
|
@ -45,58 +45,31 @@ func TestGenerateMakerOrder(t *testing.T) {
|
|||
strategy := newTestStrategy()
|
||||
|
||||
t.Run("case 1: all config is valid and we can place enough orders", func(t *testing.T) {
|
||||
budget := Number("105000")
|
||||
budget := Number("10500")
|
||||
askPrice := Number("30000")
|
||||
margin := Number("0.05")
|
||||
submitOrders, err := strategy.generateMakerOrder(false, budget, askPrice, margin, 4)
|
||||
submitOrders, err := strategy.generateDCAOrders(false, budget, askPrice, margin, 4)
|
||||
if !assert.NoError(err) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Len(submitOrders, 4)
|
||||
assert.Equal(submitOrders[0].Price, Number("28500"))
|
||||
assert.Equal(submitOrders[1].Price, Number("27000"))
|
||||
assert.Equal(submitOrders[2].Price, Number("25500"))
|
||||
assert.Equal(submitOrders[3].Price, Number("24000"))
|
||||
assert.Equal(submitOrders[0].Quantity, Number("1"))
|
||||
assert.Equal(submitOrders[1].Quantity, Number("1"))
|
||||
assert.Equal(submitOrders[2].Quantity, Number("1"))
|
||||
assert.Equal(submitOrders[3].Quantity, Number("1"))
|
||||
assert.Equal(Number("30000"), submitOrders[0].Price)
|
||||
assert.Equal(Number("0.0875"), submitOrders[0].Quantity)
|
||||
assert.Equal(Number("28500"), submitOrders[1].Price)
|
||||
assert.Equal(Number("0.092105"), submitOrders[1].Quantity)
|
||||
assert.Equal(Number("27075"), submitOrders[2].Price)
|
||||
assert.Equal(Number("0.096952"), submitOrders[2].Quantity)
|
||||
assert.Equal(Number("25721.25"), submitOrders[3].Price)
|
||||
assert.Equal(Number("0.102055"), submitOrders[3].Quantity)
|
||||
})
|
||||
|
||||
t.Run("case 2: some orders' price will below 0 and we should not create such order", func(t *testing.T) {
|
||||
budget := Number("100000")
|
||||
askPrice := Number("30000")
|
||||
margin := Number("0.2")
|
||||
submitOrders, err := strategy.generateMakerOrder(false, budget, askPrice, margin, 5)
|
||||
if !assert.NoError(err) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Len(submitOrders, 4)
|
||||
assert.Equal(submitOrders[0].Price, Number("24000"))
|
||||
assert.Equal(submitOrders[1].Price, Number("18000"))
|
||||
assert.Equal(submitOrders[2].Price, Number("12000"))
|
||||
assert.Equal(submitOrders[3].Price, Number("6000"))
|
||||
assert.Equal(submitOrders[0].Quantity, Number("1.666666"))
|
||||
assert.Equal(submitOrders[1].Quantity, Number("1.666666"))
|
||||
assert.Equal(submitOrders[2].Quantity, Number("1.666666"))
|
||||
assert.Equal(submitOrders[3].Quantity, Number("1.666666"))
|
||||
t.Run("case 2: some orders' price will below 0, so we should not create such order", func(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("case 3: some orders' notional is too small and we should not create such order", func(t *testing.T) {
|
||||
budget := Number("30")
|
||||
askPrice := Number("30000")
|
||||
margin := Number("0.2")
|
||||
submitOrders, err := strategy.generateMakerOrder(false, budget, askPrice, margin, 5)
|
||||
if !assert.NoError(err) {
|
||||
return
|
||||
}
|
||||
t.Run("case 3: notional is too small, so we should decrease num of orders", func(t *testing.T) {
|
||||
})
|
||||
|
||||
assert.Len(submitOrders, 2)
|
||||
assert.Equal(submitOrders[0].Price, Number("24000"))
|
||||
assert.Equal(submitOrders[1].Price, Number("18000"))
|
||||
assert.Equal(submitOrders[0].Quantity, Number("0.000714"))
|
||||
assert.Equal(submitOrders[1].Quantity, Number("0.000714"))
|
||||
t.Run("case 4: quantity is too small, so we should decrease num of orders", func(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user