Merge pull request #1629 from c9s/kbearXD/dca2/refactor

REFACTOR: [dca2] refactor dca2 strategy to make it can back testing
This commit is contained in:
kbearXD 2024-05-13 17:22:20 +08:00 committed by GitHub
commit e86decea6e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 26 additions and 57 deletions

View File

@ -1,7 +0,0 @@
package dca2
// DevMode, if Enabled is true. it means it will check the running field
type DevMode struct {
Enabled bool `json:"enabled"`
IsNewAccount bool `json:"isNewAccount"`
}

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/exchange/retry" "github.com/c9s/bbgo/pkg/exchange/retry"
"github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/types"
@ -33,21 +32,6 @@ func (s *Strategy) placeOpenPositionOrders(ctx context.Context) error {
s.debugOrders(createdOrders) s.debugOrders(createdOrders)
if s.DevMode != nil && s.DevMode.Enabled && s.DevMode.IsNewAccount {
if len(createdOrders) > 0 {
s.ProfitStats.FromOrderID = createdOrders[0].OrderID
}
for _, createdOrder := range createdOrders {
if s.ProfitStats.FromOrderID > createdOrder.OrderID {
s.ProfitStats.FromOrderID = createdOrder.OrderID
}
}
s.DevMode.IsNewAccount = false
bbgo.Sync(ctx, s)
}
return nil return nil
} }

View File

@ -74,7 +74,7 @@ func (s *Strategy) emitNextState(nextState State) {
// TakeProfitReady -> the takeProfit order filled -> // TakeProfitReady -> the takeProfit order filled ->
func (s *Strategy) runState(ctx context.Context) { func (s *Strategy) runState(ctx context.Context) {
s.logger.Info("[DCA] runState") s.logger.Info("[DCA] runState")
stateTriggerTicker := time.NewTicker(5 * time.Second) stateTriggerTicker := time.NewTicker(1 * time.Minute)
defer stateTriggerTicker.Stop() defer stateTriggerTicker.Stop()
for { for {
@ -83,7 +83,7 @@ func (s *Strategy) runState(ctx context.Context) {
s.logger.Info("[DCA] runState DONE") s.logger.Info("[DCA] runState DONE")
return return
case <-stateTriggerTicker.C: case <-stateTriggerTicker.C:
// s.logger.Infof("[DCA] triggerNextState current state: %d", s.state) // move triggerNextState to the end of next state handler, this ticker is used to avoid the state is stopped unexpectedly
s.triggerNextState() s.triggerNextState()
case nextState := <-s.nextStateC: case nextState := <-s.nextStateC:
// next state == current state -> skip // next state == current state -> skip
@ -120,6 +120,8 @@ func (s *Strategy) runState(ctx context.Context) {
case TakeProfitReady: case TakeProfitReady:
s.runTakeProfitReady(ctx, nextState) s.runTakeProfitReady(ctx, nextState)
} }
s.triggerNextState()
} }
} }
} }
@ -178,9 +180,6 @@ func (s *Strategy) runOpenPositionReady(_ context.Context, next State) {
func (s *Strategy) runOpenPositionOrderFilled(_ context.Context, next State) { func (s *Strategy) runOpenPositionOrderFilled(_ context.Context, next State) {
s.updateState(OpenPositionOrdersCancelling) s.updateState(OpenPositionOrdersCancelling)
s.logger.Info("[State] OpenPositionOrderFilled -> OpenPositionOrdersCancelling") s.logger.Info("[State] OpenPositionOrderFilled -> OpenPositionOrdersCancelling")
// after open position cancelling, immediately trigger open position cancelled to cancel the other orders
s.emitNextState(OpenPositionOrdersCancelled)
} }
func (s *Strategy) runOpenPositionOrdersCancelling(ctx context.Context, next State) { func (s *Strategy) runOpenPositionOrdersCancelling(ctx context.Context, next State) {
@ -191,9 +190,6 @@ func (s *Strategy) runOpenPositionOrdersCancelling(ctx context.Context, next Sta
} }
s.updateState(OpenPositionOrdersCancelled) s.updateState(OpenPositionOrdersCancelled)
s.logger.Info("[State] OpenPositionOrdersCancelling -> OpenPositionOrdersCancelled") s.logger.Info("[State] OpenPositionOrdersCancelling -> OpenPositionOrdersCancelled")
// after open position cancelled, immediately trigger take profit ready to open take-profit order
s.emitNextState(TakeProfitReady)
} }
func (s *Strategy) runOpenPositionOrdersCancelled(ctx context.Context, next State) { func (s *Strategy) runOpenPositionOrdersCancelled(ctx context.Context, next State) {

View File

@ -79,9 +79,6 @@ type Strategy struct {
// UseCancelAllOrdersApiWhenClose uses a different API to cancel all the orders on the market when closing a grid // UseCancelAllOrdersApiWhenClose uses a different API to cancel all the orders on the market when closing a grid
UseCancelAllOrdersApiWhenClose bool `json:"useCancelAllOrdersApiWhenClose"` UseCancelAllOrdersApiWhenClose bool `json:"useCancelAllOrdersApiWhenClose"`
// dev mode
DevMode *DevMode `json:"devMode"`
// log // log
logger *logrus.Entry logger *logrus.Entry
LogFields logrus.Fields `json:"logFields"` LogFields logrus.Fields `json:"logFields"`
@ -179,12 +176,6 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
s.Position = types.NewPositionFromMarket(s.Market) s.Position = types.NewPositionFromMarket(s.Market)
} }
// if dev mode is on and it's not a new strategy
if s.DevMode != nil && s.DevMode.Enabled && !s.DevMode.IsNewAccount {
s.ProfitStats = newProfitStats(s.Market, s.QuoteInvestment)
s.Position = types.NewPositionFromMarket(s.Market)
}
// set ttl for persistence // set ttl for persistence
s.Position.SetTTL(s.PersistenceTTL.Duration()) s.Position.SetTTL(s.PersistenceTTL.Duration())
s.ProfitStats.SetTTL(s.PersistenceTTL.Duration()) s.ProfitStats.SetTTL(s.PersistenceTTL.Duration())
@ -219,7 +210,7 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
} }
s.OrderExecutor = bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position) s.OrderExecutor = bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position)
s.OrderExecutor.SetMaxRetries(10) s.OrderExecutor.SetMaxRetries(50)
s.OrderExecutor.BindEnvironment(s.Environment) s.OrderExecutor.BindEnvironment(s.Environment)
s.OrderExecutor.Bind() s.OrderExecutor.Bind()
@ -271,23 +262,23 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
}) })
session.MarketDataStream.OnKLine(func(kline types.KLine) { session.MarketDataStream.OnKLine(func(kline types.KLine) {
// check price here switch s.state {
if s.state != OpenPositionOrderFilled { case OpenPositionOrderFilled:
if s.takeProfitPrice.IsZero() {
s.logger.Warn("take profit price should not be 0 when there is at least one open-position order filled, please check it")
return
}
compRes := kline.Close.Compare(s.takeProfitPrice)
// price doesn't hit the take profit price
if compRes < 0 {
return
}
s.emitNextState(OpenPositionOrdersCancelling)
default:
return return
} }
if s.takeProfitPrice.IsZero() {
s.logger.Warn("take profit price should not be 0 when there is at least one open-position order filled, please check it")
return
}
compRes := kline.Close.Compare(s.takeProfitPrice)
// price doesn't hit the take profit price
if compRes < 0 {
return
}
s.emitNextState(OpenPositionOrdersCancelling)
}) })
session.UserDataStream.OnAuth(func() { session.UserDataStream.OnAuth(func() {
@ -298,7 +289,7 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
// no need to recover when two situation // no need to recover when two situation
// 1. recoverWhenStart is false // 1. recoverWhenStart is false
// 2. dev mode is on and it's not new strategy // 2. dev mode is on and it's not new strategy
if !s.RecoverWhenStart || (s.DevMode != nil && s.DevMode.Enabled && !s.DevMode.IsNewAccount) { if !s.RecoverWhenStart {
s.updateState(WaitToOpenPosition) s.updateState(WaitToOpenPosition)
} else { } else {
// recover // recover
@ -343,6 +334,11 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
// start to sync periodically // start to sync periodically
go s.syncPeriodically(ctx) go s.syncPeriodically(ctx)
// try to trigger position opening immediately
if s.state == WaitToOpenPosition {
s.emitNextState(PositionOpening)
}
// start running state machine // start running state machine
s.runState(ctx) s.runState(ctx)
} }