Merge pull request #1473 from c9s/kbearXD/dca2/remove-short

FEATURE: remove Short
This commit is contained in:
kbearXD 2023-12-28 23:13:20 +08:00 committed by GitHub
commit d08aebabad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 30 additions and 62 deletions

View File

@ -15,12 +15,12 @@ type cancelOrdersByGroupIDApi interface {
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)
price, err := getBestPriceUntilSuccess(ctx, s.Session.Exchange, s.Symbol)
if err != nil {
return err
}
orders, err := generateOpenPositionOrders(s.Market, s.Short, s.Budget, price, s.PriceDeviation, s.MaxOrderNum, s.OrderGroupID)
orders, err := generateOpenPositionOrders(s.Market, s.Budget, price, s.PriceDeviation, s.MaxOrderNum, s.OrderGroupID)
if err != nil {
return err
}
@ -35,24 +35,17 @@ func (s *Strategy) placeOpenPositionOrders(ctx context.Context) error {
return nil
}
func getBestPriceUntilSuccess(ctx context.Context, ex types.Exchange, symbol string, short bool) (fixedpoint.Value, error) {
func getBestPriceUntilSuccess(ctx context.Context, ex types.Exchange, symbol string) (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
}
return ticker.Sell, nil
}
func generateOpenPositionOrders(market types.Market, short bool, budget, price, priceDeviation fixedpoint.Value, maxOrderNum int64, orderGroupID uint32) ([]types.SubmitOrder, error) {
func generateOpenPositionOrders(market types.Market, budget, price, priceDeviation fixedpoint.Value, maxOrderNum int64, orderGroupID uint32) ([]types.SubmitOrder, error) {
factor := fixedpoint.One.Sub(priceDeviation)
if short {
factor = fixedpoint.One.Add(priceDeviation)
}
// calculate all valid prices
var prices []fixedpoint.Value
@ -68,15 +61,12 @@ func generateOpenPositionOrders(market types.Market, short bool, budget, price,
prices = append(prices, price)
}
notional, orderNum := calculateNotionalAndNum(market, short, budget, prices)
notional, orderNum := calculateNotionalAndNum(market, 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)
}
side := types.SideTypeBuy
if short {
side = types.SideTypeSell
}
var submitOrders []types.SubmitOrder
for i := 0; i < orderNum; i++ {
@ -99,7 +89,7 @@ func generateOpenPositionOrders(market types.Market, short bool, budget, price,
// 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) {
func calculateNotionalAndNum(market types.Market, 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 {
@ -107,9 +97,6 @@ func calculateNotionalAndNum(market types.Market, short bool, budget fixedpoint.
}
maxPriceIdx := 0
if short {
maxPriceIdx = num - 1
}
quantity := market.TruncateQuantity(notional.Div(prices[maxPriceIdx]))
if quantity.Compare(market.MinQuantity) < 0 {
continue

View File

@ -36,7 +36,6 @@ func newTestStrategy(va ...string) *Strategy {
logger: logrus.NewEntry(logrus.New()),
Symbol: symbol,
Market: market,
Short: false,
TakeProfitRatio: Number("10%"),
}
return s
@ -51,7 +50,7 @@ func TestGenerateOpenPositionOrders(t *testing.T) {
budget := Number("10500")
askPrice := Number("30000")
margin := Number("0.05")
submitOrders, err := generateOpenPositionOrders(strategy.Market, false, budget, askPrice, margin, 4, strategy.OrderGroupID)
submitOrders, err := generateOpenPositionOrders(strategy.Market, budget, askPrice, margin, 4, strategy.OrderGroupID)
if !assert.NoError(err) {
return
}

View File

@ -39,14 +39,14 @@ func (s *Strategy) recover(ctx context.Context) error {
return err
}
currentRound, err := getCurrentRoundOrders(s.Short, openOrders, closedOrders, s.OrderGroupID)
currentRound, err := getCurrentRoundOrders(openOrders, closedOrders, s.OrderGroupID)
if err != nil {
return err
}
debugRoundOrders(s.logger, "current", currentRound)
// recover state
state, err := recoverState(ctx, s.Symbol, s.Short, int(s.MaxOrderNum), openOrders, currentRound, s.OrderExecutor.ActiveMakerOrders(), s.OrderExecutor.OrderStore(), s.OrderGroupID)
state, err := recoverState(ctx, s.Symbol, int(s.MaxOrderNum), openOrders, currentRound, s.OrderExecutor.ActiveMakerOrders(), s.OrderExecutor.OrderStore(), s.OrderGroupID)
if err != nil {
return err
}
@ -72,7 +72,7 @@ func (s *Strategy) recover(ctx context.Context) error {
}
// recover state
func recoverState(ctx context.Context, symbol string, short bool, maxOrderNum int, openOrders []types.Order, currentRound Round, activeOrderBook *bbgo.ActiveOrderBook, orderStore *core.OrderStore, groupID uint32) (State, error) {
func recoverState(ctx context.Context, symbol string, maxOrderNum int, openOrders []types.Order, currentRound Round, activeOrderBook *bbgo.ActiveOrderBook, orderStore *core.OrderStore, groupID uint32) (State, error) {
if len(currentRound.OpenPositionOrders) == 0 {
// new strategy
return WaitToOpenPosition, nil
@ -225,15 +225,10 @@ type Round struct {
TakeProfitOrder types.Order
}
func getCurrentRoundOrders(short bool, openOrders, closedOrders []types.Order, groupID uint32) (Round, error) {
func getCurrentRoundOrders(openOrders, closedOrders []types.Order, groupID uint32) (Round, error) {
openPositionSide := types.SideTypeBuy
takeProfitSide := types.SideTypeSell
if short {
openPositionSide = types.SideTypeSell
takeProfitSide = types.SideTypeBuy
}
var allOrders []types.Order
allOrders = append(allOrders, openOrders...)
allOrders = append(allOrders, closedOrders...)

View File

@ -39,7 +39,7 @@ func Test_GetCurrenctAndLastRoundOrders(t *testing.T) {
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-5*time.Second)),
}
currentRound, err := getCurrentRoundOrders(false, openOrders, closedOrders, 0)
currentRound, err := getCurrentRoundOrders(openOrders, closedOrders, 0)
assert.NoError(t, err)
assert.NotEqual(t, 0, currentRound.TakeProfitOrder.OrderID)
@ -72,7 +72,7 @@ func Test_GetCurrenctAndLastRoundOrders(t *testing.T) {
generateTestOrder(types.SideTypeBuy, types.OrderStatusFilled, now.Add(-17*time.Second)),
}
currentRound, err := getCurrentRoundOrders(false, openOrders, closedOrders, 0)
currentRound, err := getCurrentRoundOrders(openOrders, closedOrders, 0)
assert.NoError(t, err)
assert.NotEqual(t, 0, currentRound.TakeProfitOrder.OrderID)
@ -101,7 +101,7 @@ func Test_RecoverState(t *testing.T) {
currentRound := Round{}
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
orderStore := core.NewOrderStore(symbol)
state, err := recoverState(context.Background(), symbol, false, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
state, err := recoverState(context.Background(), symbol, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
assert.NoError(t, err)
assert.Equal(t, WaitToOpenPosition, state)
})
@ -120,7 +120,7 @@ func Test_RecoverState(t *testing.T) {
}
orderStore := core.NewOrderStore(symbol)
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
state, err := recoverState(context.Background(), symbol, false, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
state, err := recoverState(context.Background(), symbol, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
assert.NoError(t, err)
assert.Equal(t, OpenPositionReady, state)
})
@ -144,7 +144,7 @@ func Test_RecoverState(t *testing.T) {
}
orderStore := core.NewOrderStore(symbol)
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
state, err := recoverState(context.Background(), symbol, false, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
state, err := recoverState(context.Background(), symbol, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
assert.NoError(t, err)
assert.Equal(t, OpenPositionOrderFilled, state)
})
@ -165,7 +165,7 @@ func Test_RecoverState(t *testing.T) {
}
orderStore := core.NewOrderStore(symbol)
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
state, err := recoverState(context.Background(), symbol, false, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
state, err := recoverState(context.Background(), symbol, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
assert.NoError(t, err)
assert.Equal(t, OpenPositionOrdersCancelling, state)
})
@ -184,7 +184,7 @@ func Test_RecoverState(t *testing.T) {
}
orderStore := core.NewOrderStore(symbol)
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
state, err := recoverState(context.Background(), symbol, false, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
state, err := recoverState(context.Background(), symbol, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
assert.NoError(t, err)
assert.Equal(t, OpenPositionOrdersCancelled, state)
})
@ -206,7 +206,7 @@ func Test_RecoverState(t *testing.T) {
}
orderStore := core.NewOrderStore(symbol)
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
state, err := recoverState(context.Background(), symbol, false, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
state, err := recoverState(context.Background(), symbol, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
assert.NoError(t, err)
assert.Equal(t, TakeProfitReady, state)
})
@ -226,7 +226,7 @@ func Test_RecoverState(t *testing.T) {
}
orderStore := core.NewOrderStore(symbol)
activeOrderBook := bbgo.NewActiveOrderBook(symbol)
state, err := recoverState(context.Background(), symbol, false, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
state, err := recoverState(context.Background(), symbol, 5, openOrders, currentRound, activeOrderBook, orderStore, 0)
assert.NoError(t, err)
assert.Equal(t, WaitToOpenPosition, state)
})

View File

@ -178,12 +178,11 @@ func (s *Strategy) runOpenPositionOrdersCancelled(ctx context.Context, next Stat
}
func (s *Strategy) runTakeProfitReady(_ context.Context, next State) {
// wait 3 seconds to avoid position not update
time.Sleep(3 * time.Second)
s.logger.Info("[State] TakeProfitReady - start reseting position and calculate budget for next round")
if s.Short {
s.Budget = s.Budget.Add(s.Position.Base)
} else {
s.Budget = s.Budget.Add(s.Position.Quote)
}
s.Budget = s.Budget.Add(s.Position.Quote)
// reset position
s.Position.Reset()

View File

@ -34,7 +34,6 @@ type Strategy struct {
Symbol string `json:"symbol"`
// setting
Short bool `json:"short"`
Budget fixedpoint.Value `json:"budget"`
MaxOrderNum int64 `json:"maxOrderNum"`
PriceDeviation fixedpoint.Value `json:"priceDeviation"`
@ -122,10 +121,6 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
s.logger.Infof("[DCA] FILLED ORDER: %s", o.String())
openPositionSide := types.SideTypeBuy
takeProfitSide := types.SideTypeSell
if s.Short {
openPositionSide = types.SideTypeSell
takeProfitSide = types.SideTypeBuy
}
switch o.Side {
case openPositionSide:
@ -145,7 +140,7 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
compRes := kline.Close.Compare(s.takeProfitPrice)
// price doesn't hit the take profit price
if (s.Short && compRes > 0) || (!s.Short && compRes < 0) {
if compRes < 0 {
return
}
@ -193,9 +188,6 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
func (s *Strategy) updateTakeProfitPrice() {
takeProfitRatio := s.TakeProfitRatio
if s.Short {
takeProfitRatio = takeProfitRatio.Neg()
}
s.takeProfitPrice = s.Market.TruncatePrice(s.Position.AverageCost.Mul(fixedpoint.One.Add(takeProfitRatio)))
s.logger.Infof("[DCA] cost: %s, ratio: %s, price: %s", s.Position.AverageCost, takeProfitRatio, s.takeProfitPrice)
}

View File

@ -9,7 +9,7 @@ import (
func (s *Strategy) placeTakeProfitOrders(ctx context.Context) error {
s.logger.Info("[DCA] start placing take profit orders")
order := generateTakeProfitOrder(s.Market, s.Short, s.TakeProfitRatio, s.Position, s.OrderGroupID)
order := generateTakeProfitOrder(s.Market, s.TakeProfitRatio, s.Position, s.OrderGroupID)
createdOrders, err := s.OrderExecutor.SubmitOrders(ctx, order)
if err != nil {
return err
@ -22,12 +22,8 @@ func (s *Strategy) placeTakeProfitOrders(ctx context.Context) error {
return nil
}
func generateTakeProfitOrder(market types.Market, short bool, takeProfitRatio fixedpoint.Value, position *types.Position, orderGroupID uint32) types.SubmitOrder {
func generateTakeProfitOrder(market types.Market, takeProfitRatio fixedpoint.Value, position *types.Position, orderGroupID uint32) types.SubmitOrder {
side := types.SideTypeSell
if short {
takeProfitRatio = takeProfitRatio.Neg()
side = types.SideTypeBuy
}
takeProfitPrice := market.TruncatePrice(position.AverageCost.Mul(fixedpoint.One.Add(takeProfitRatio)))
return types.SubmitOrder{
Symbol: market.Symbol,

View File

@ -24,7 +24,7 @@ func TestGenerateTakeProfitOrder(t *testing.T) {
FeeCurrency: strategy.Market.BaseCurrency,
})
o := generateTakeProfitOrder(strategy.Market, false, strategy.TakeProfitRatio, position, strategy.OrderGroupID)
o := generateTakeProfitOrder(strategy.Market, strategy.TakeProfitRatio, position, strategy.OrderGroupID)
assert.Equal(Number("31397.09"), o.Price)
assert.Equal(Number("0.9985"), o.Quantity)
assert.Equal(types.SideTypeSell, o.Side)
@ -38,7 +38,7 @@ func TestGenerateTakeProfitOrder(t *testing.T) {
Fee: Number("0.00075"),
FeeCurrency: strategy.Market.BaseCurrency,
})
o = generateTakeProfitOrder(strategy.Market, false, strategy.TakeProfitRatio, position, strategy.OrderGroupID)
o = generateTakeProfitOrder(strategy.Market, strategy.TakeProfitRatio, position, strategy.OrderGroupID)
assert.Equal(Number("30846.26"), o.Price)
assert.Equal(Number("1.49775"), o.Quantity)
assert.Equal(types.SideTypeSell, o.Side)