mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
Merge pull request #1433 from c9s/kbearXD/dca2/open-maker-orders
FEATURE: prepare open maker orders function
This commit is contained in:
commit
9101d7ad4a
17
pkg/exchange/retry/ticker.go
Normal file
17
pkg/exchange/retry/ticker.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package retry
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
func QueryTickerUntilSuccessful(ctx context.Context, ex types.Exchange, symbol string) (ticker *types.Ticker, err error) {
|
||||
var op = func() (err2 error) {
|
||||
ticker, err2 = ex.QueryTicker(ctx, symbol)
|
||||
return err2
|
||||
}
|
||||
|
||||
err = GeneralBackoff(ctx, op)
|
||||
return ticker, err
|
||||
}
|
113
pkg/strategy/dca2/openPosition.go
Normal file
113
pkg/strategy/dca2/openPosition.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
package dca2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/exchange/retry"
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
func (s *Strategy) placeOpenPositionOrders(ctx context.Context) error {
|
||||
s.logger.Infof("[DCA] start placing open position orders")
|
||||
price, err := getBestPriceUntilSuccess(ctx, s.Session.Exchange, s.Symbol, s.Short)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
orders, err := s.generateOpenPositionOrders(s.Short, s.Budget, price, s.PriceDeviation, s.MaxOrderNum)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
createdOrders, err := s.OrderExecutor.SubmitOrders(ctx, orders...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.debugOrders(createdOrders)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getBestPriceUntilSuccess(ctx context.Context, ex types.Exchange, symbol string, short bool) (fixedpoint.Value, error) {
|
||||
ticker, err := retry.QueryTickerUntilSuccessful(ctx, ex, symbol)
|
||||
if err != nil {
|
||||
return fixedpoint.Zero, err
|
||||
}
|
||||
|
||||
if short {
|
||||
return ticker.Buy, nil
|
||||
} else {
|
||||
return ticker.Sell, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Strategy) generateOpenPositionOrders(short bool, budget, price, priceDeviation fixedpoint.Value, maxOrderNum int64) ([]types.SubmitOrder, error) {
|
||||
factor := fixedpoint.One.Sub(priceDeviation)
|
||||
if short {
|
||||
factor = fixedpoint.One.Add(priceDeviation)
|
||||
}
|
||||
|
||||
// calculate all valid prices
|
||||
var prices []fixedpoint.Value
|
||||
for i := 0; i < int(maxOrderNum); i++ {
|
||||
if i > 0 {
|
||||
price = price.Mul(factor)
|
||||
}
|
||||
price = s.Market.TruncatePrice(price)
|
||||
if price.Compare(s.Market.MinPrice) < 0 {
|
||||
break
|
||||
}
|
||||
|
||||
prices = append(prices, price)
|
||||
}
|
||||
|
||||
notional, orderNum := calculateNotionalAndNum(s.Market, short, budget, prices)
|
||||
if orderNum == 0 {
|
||||
return nil, fmt.Errorf("failed to calculate notional and num of open position orders, price: %s, budget: %s", price, budget)
|
||||
}
|
||||
|
||||
var submitOrders []types.SubmitOrder
|
||||
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,
|
||||
Type: types.OrderTypeLimit,
|
||||
Price: prices[i],
|
||||
Side: s.makerSide,
|
||||
TimeInForce: types.TimeInForceGTC,
|
||||
Quantity: quantity,
|
||||
Tag: orderTag,
|
||||
GroupID: s.OrderGroupID,
|
||||
})
|
||||
}
|
||||
|
||||
return submitOrders, nil
|
||||
}
|
||||
|
||||
// calculateNotionalAndNum calculates the notional and num of open position orders
|
||||
// DCA2 is notional-based, every order has the same notional
|
||||
func calculateNotionalAndNum(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
|
||||
}
|
75
pkg/strategy/dca2/openPosition_test.go
Normal file
75
pkg/strategy/dca2/openPosition_test.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package dca2
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
. "github.com/c9s/bbgo/pkg/testing/testhelper"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func newTestMarket() types.Market {
|
||||
return types.Market{
|
||||
BaseCurrency: "BTC",
|
||||
QuoteCurrency: "USDT",
|
||||
TickSize: Number(0.01),
|
||||
StepSize: Number(0.000001),
|
||||
PricePrecision: 2,
|
||||
VolumePrecision: 8,
|
||||
MinNotional: Number(8.0),
|
||||
MinQuantity: Number(0.0003),
|
||||
}
|
||||
}
|
||||
|
||||
func newTestStrategy(va ...string) *Strategy {
|
||||
symbol := "BTCUSDT"
|
||||
|
||||
if len(va) > 0 {
|
||||
symbol = va[0]
|
||||
}
|
||||
|
||||
market := newTestMarket()
|
||||
s := &Strategy{
|
||||
logger: logrus.NewEntry(logrus.New()),
|
||||
Symbol: symbol,
|
||||
Market: market,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func TestGenerateOpenPositionOrders(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
strategy := newTestStrategy()
|
||||
|
||||
t.Run("case 1: all config is valid and we can place enough orders", func(t *testing.T) {
|
||||
budget := Number("10500")
|
||||
askPrice := Number("30000")
|
||||
margin := Number("0.05")
|
||||
submitOrders, err := strategy.generateOpenPositionOrders(false, budget, askPrice, margin, 4)
|
||||
if !assert.NoError(err) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Len(submitOrders, 4)
|
||||
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, so we should not create such order", func(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("case 3: notional is too small, so we should decrease num of orders", func(t *testing.T) {
|
||||
})
|
||||
|
||||
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