Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE: [dca2] when all open-position orders are filled, place the t… #1599

Merged
merged 2 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions pkg/strategy/dca2/active_order_recover.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ func (s *Strategy) recoverPeriodically(ctx context.Context) {
}

func (s *Strategy) recoverActiveOrders(ctx context.Context) error {
s.logger.Info("recover active orders...")
openOrders, err := retry.QueryOpenOrdersUntilSuccessfulLite(ctx, s.ExchangeSession.Exchange, s.Symbol)
if err != nil {
s.logger.WithError(err).Warn("failed to query open orders")
return err
}

activeOrders := s.OrderExecutor.ActiveMakerOrders().Orders()
activeOrders := s.OrderExecutor.ActiveMakerOrders()

// update num of open orders metrics
if metricsNumOfOpenOrders != nil {
Expand All @@ -43,13 +44,17 @@ func (s *Strategy) recoverActiveOrders(ctx context.Context) error {

// update num of active orders metrics
if metricsNumOfActiveOrders != nil {
metricsNumOfActiveOrders.With(baseLabels).Set(float64(len(activeOrders)))
metricsNumOfActiveOrders.With(baseLabels).Set(float64(activeOrders.NumOfOrders()))
}

if len(openOrders) != activeOrders.NumOfOrders() {
s.logger.Warnf("num of open orders (%d) and active orders (%d) is different before active orders recovery, please check it.", len(openOrders), activeOrders.NumOfOrders())
}

opts := common.SyncActiveOrdersOpts{
Logger: s.logger,
Exchange: s.ExchangeSession.Exchange,
ActiveOrderBook: s.OrderExecutor.ActiveMakerOrders(),
ActiveOrderBook: activeOrders,
OpenOrders: openOrders,
}

Expand Down
35 changes: 35 additions & 0 deletions pkg/strategy/dca2/background_runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package dca2

import (
"context"
"time"

"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/util"
)

func (s *Strategy) runBackgroundTask(ctx context.Context) {
s.logger.Info("run background task")

// recover active orders
recoverActiveOrdersInterval := util.MillisecondsJitter(10*time.Minute, 5*60*1000)
recoverActiveOrdersTicker := time.NewTicker(recoverActiveOrdersInterval)
defer recoverActiveOrdersTicker.Stop()

// sync strategy
syncPersistenceTicker := time.NewTicker(1 * time.Hour)
defer syncPersistenceTicker.Stop()

for {
select {
case <-ctx.Done():
return
case <-syncPersistenceTicker.C:
bbgo.Sync(ctx, s)
case <-recoverActiveOrdersTicker.C:
if err := s.recoverActiveOrders(ctx); err != nil {
s.logger.WithError(err).Warn(err, "failed to recover active orders")
}
}
}
}
39 changes: 13 additions & 26 deletions pkg/strategy/dca2/recover.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,40 +135,27 @@ func recoverState(ctx context.Context, maxOrderCount int, currentRound Round, or
}
}

// the number of open-position orders is the same as maxOrderCount -> place open-position orders successfully
if numOpenPositionOrders == maxOrderCount {
// all open-position orders are still not filled -> OpenPositionReady
if filledCnt == 0 && cancelledCnt == 0 {
return OpenPositionReady, nil
}
// all open-position orders are still not filled -> OpenPositionReady
if filledCnt == 0 && cancelledCnt == 0 {
return OpenPositionReady, nil
}

// there are at least one open-position orders filled -> OpenPositionOrderFilled
if filledCnt > 0 && cancelledCnt == 0 {
// there are at least one open-position orders filled
if filledCnt > 0 && cancelledCnt == 0 {
if openedCnt > 0 {
return OpenPositionOrderFilled, nil
}

// there are at last one open-position orders cancelled ->
if cancelledCnt > 0 {
} else {
// all open-position orders filled, change to cancelling and place the take-profit order
return OpenPositionOrdersCancelling, nil
}

return None, fmt.Errorf("unexpected order status combination when numOpenPositionOrders(%d) == maxOrderCount(%d) (opened, filled, cancelled) = (%d, %d, %d)", numOpenPositionOrders, maxOrderCount, openedCnt, filledCnt, cancelledCnt)
}

// the number of open-position orders is less than maxOrderCount -> failed to place open-position orders
// 1. This strategy is at position opening, so it may not place all orders we want successfully
// 2. There are some errors when placing open-position orders. e.g. cannot lock fund.....
if filledCnt == 0 && cancelledCnt == 0 {
// TODO: place the remaining open-position orders
return OpenPositionReady, nil
}

if filledCnt > 0 && cancelledCnt == 0 {
// TODO: place the remaing open-position orders and change state to OpenPositionOrderFilled
return OpenPositionOrderFilled, nil
// there are at last one open-position orders cancelled ->
if cancelledCnt > 0 {
return OpenPositionOrdersCancelling, nil
}

return None, fmt.Errorf("unexpected order status combination when numOpenPositionOrders(%d) < maxOrderCount(%d) (opened, filled, cancelled) = (%d, %d, %d)", numOpenPositionOrders, maxOrderCount, openedCnt, filledCnt, cancelledCnt)
return None, fmt.Errorf("unexpected order status combination (opened, filled, cancelled) = (%d, %d, %d)", openedCnt, filledCnt, cancelledCnt)
}

func recoverPosition(ctx context.Context, position *types.Position, queryService RecoverApiQueryService, currentRound Round) error {
Expand Down
5 changes: 0 additions & 5 deletions pkg/strategy/dca2/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,6 @@ func (s *Strategy) runState(ctx context.Context) {
stateTriggerTicker := time.NewTicker(5 * time.Second)
defer stateTriggerTicker.Stop()

monitorTicker := time.NewTicker(10 * time.Minute)
defer monitorTicker.Stop()

for {
select {
case <-ctx.Done():
Expand All @@ -84,8 +81,6 @@ func (s *Strategy) runState(ctx context.Context) {
case <-stateTriggerTicker.C:
// s.logger.Infof("[DCA] triggerNextState current state: %d", s.state)
s.triggerNextState()
case <-monitorTicker.C:
s.updateNumOfOrdersMetrics(ctx)
case nextState := <-s.nextStateC:
// s.logger.Infof("[DCA] currenct state: %d, next state: %d", s.state, nextState)
// check the next state is valid
Expand Down
39 changes: 21 additions & 18 deletions pkg/strategy/dca2/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,25 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
s.logger.Infof("unsupported side (%s) of order: %s", o.Side, o)
}

// update metrics when filled
s.updateNumOfOrdersMetrics(ctx)
openOrders, err := retry.QueryOpenOrdersUntilSuccessful(ctx, s.ExchangeSession.Exchange, s.Symbol)
if err != nil {
s.logger.WithError(err).Warn("failed to query open orders when order filled")
} else {
// update open orders metrics
metricsNumOfOpenOrders.With(baseLabels).Set(float64(len(openOrders)))
}

// update active orders metrics
numActiveMakerOrders := s.OrderExecutor.ActiveMakerOrders().NumOfOrders()
metricsNumOfActiveOrders.With(baseLabels).Set(float64(numActiveMakerOrders))

if len(openOrders) != numActiveMakerOrders {
s.logger.Warnf("num of open orders (%d) and active orders (%d) is different when order filled, please check it.", len(openOrders), numActiveMakerOrders)
}

if err == nil && o.Side == openPositionSide && numActiveMakerOrders == 0 && len(openOrders) == 0 {
s.emitNextState(OpenPositionOrdersCancelling)
}
})

session.MarketDataStream.OnKLine(func(kline types.KLine) {
Expand Down Expand Up @@ -311,6 +328,8 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
})
})

go s.runBackgroundTask(ctx)

bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()

Expand Down Expand Up @@ -461,19 +480,3 @@ func (s *Strategy) UpdateProfitStats(ctx context.Context) (bool, error) {

return updated, nil
}

func (s *Strategy) updateNumOfOrdersMetrics(ctx context.Context) {
// update open orders metrics
openOrders, err := s.ExchangeSession.Exchange.QueryOpenOrders(ctx, s.Symbol)
if err != nil {
s.logger.WithError(err).Warn("failed to query open orders to update num of the orders metrics")
} else {
metricsNumOfOpenOrders.With(baseLabels).Set(float64(len(openOrders)))
}

// update active orders metrics
metricsNumOfActiveOrders.With(baseLabels).Set(float64(s.OrderExecutor.ActiveMakerOrders().NumOfOrders()))

// set persistence
bbgo.Sync(ctx, s)
}
Loading