From 91123edbd6f80a4fe3317fe695b4620c3f1c9bb7 Mon Sep 17 00:00:00 2001 From: kbearXD Date: Thu, 14 Mar 2024 11:40:03 +0800 Subject: [PATCH] dca2: must calculate and emit profit at the end of the round --- pkg/strategy/dca2/recover.go | 14 ++++++++--- pkg/strategy/dca2/state.go | 2 +- pkg/strategy/dca2/strategy.go | 46 ++++++++++++++++++++++++++++++++--- 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/pkg/strategy/dca2/recover.go b/pkg/strategy/dca2/recover.go index c875eed46e..147dd3a840 100644 --- a/pkg/strategy/dca2/recover.go +++ b/pkg/strategy/dca2/recover.go @@ -45,11 +45,14 @@ func (s *Strategy) recover(ctx context.Context) error { } debugRoundOrders(s.logger, "current", currentRound) + // TODO: use flag // recover profit stats - if err := recoverProfitStats(ctx, s); err != nil { - return err - } - s.logger.Info("recover profit stats DONE") + /* + if err := recoverProfitStats(ctx, s); err != nil { + return err + } + s.logger.Info("recover profit stats DONE") + */ // recover position if err := recoverPosition(ctx, s.Position, queryService, currentRound); err != nil { @@ -202,6 +205,8 @@ func recoverPosition(ctx context.Context, position *types.Position, queryService return nil } +// TODO: use flag to decide which to recover +/* func recoverProfitStats(ctx context.Context, strategy *Strategy) error { if strategy.ProfitStats == nil { return fmt.Errorf("profit stats is nil, please check it") @@ -209,6 +214,7 @@ func recoverProfitStats(ctx context.Context, strategy *Strategy) error { return strategy.CalculateAndEmitProfit(ctx) } +*/ func recoverStartTimeOfNextRound(ctx context.Context, currentRound Round, coolDownInterval types.Duration) time.Time { if currentRound.TakeProfitOrder.OrderID != 0 && currentRound.TakeProfitOrder.Status == types.OrderStatusFilled { diff --git a/pkg/strategy/dca2/state.go b/pkg/strategy/dca2/state.go index 44c56fa7e6..7416dbdb01 100644 --- a/pkg/strategy/dca2/state.go +++ b/pkg/strategy/dca2/state.go @@ -201,7 +201,7 @@ func (s *Strategy) runTakeProfitReady(ctx context.Context, next State) { // reset position // calculate profit stats - if err := s.CalculateAndEmitProfit(ctx); err != nil { + if err := s.mustCalculateAndEmitProfit(ctx); err != nil { s.logger.WithError(err).Warn("failed to calculate and emit profit") } diff --git a/pkg/strategy/dca2/strategy.go b/pkg/strategy/dca2/strategy.go index 512dd34868..cedf2a32f6 100644 --- a/pkg/strategy/dca2/strategy.go +++ b/pkg/strategy/dca2/strategy.go @@ -13,6 +13,8 @@ import ( "go.uber.org/multierr" "github.com/c9s/bbgo/pkg/bbgo" + "github.com/c9s/bbgo/pkg/exchange" + maxapi "github.com/c9s/bbgo/pkg/exchange/max/maxapi" "github.com/c9s/bbgo/pkg/exchange/retry" "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/strategy/common" @@ -370,7 +372,9 @@ func (s *Strategy) CleanUp(ctx context.Context) error { return werr } -func (s *Strategy) CalculateAndEmitProfit(ctx context.Context) error { +func (s *Strategy) mustCalculateAndEmitProfit(ctx context.Context) error { + fromOrderID := s.ProfitStats.FromOrderID + historyService, ok := s.ExchangeSession.Exchange.(types.ExchangeTradeHistoryService) if !ok { return fmt.Errorf("exchange %s doesn't support ExchangeTradeHistoryService", s.ExchangeSession.Exchange.Name()) @@ -381,6 +385,26 @@ func (s *Strategy) CalculateAndEmitProfit(ctx context.Context) error { return fmt.Errorf("exchange %s doesn't support ExchangeOrderQueryService", s.ExchangeSession.Exchange.Name()) } + maxTry := 10 + for try := 1; try < maxTry; try++ { + if err := s.CalculateAndEmitProfit(ctx, historyService, queryService); err != nil { + s.logger.WithError(err).Warnf("failed to calculate and emit profit at #%d try, please check it", try) + continue + } + + if s.ProfitStats.FromOrderID > fromOrderID { + break + } + } + + if s.ProfitStats.FromOrderID == fromOrderID { + return fmt.Errorf("after trying %d times, we still can't calculate and emit profit, please check it", maxTry) + } + + return nil +} + +func (s *Strategy) CalculateAndEmitProfit(ctx context.Context, historyService types.ExchangeTradeHistoryService, queryService types.ExchangeOrderQueryService) error { // TODO: pagination for it // query the orders s.logger.Infof("query %s closed orders from order id #%d", s.Symbol, s.ProfitStats.FromOrderID) @@ -390,6 +414,8 @@ func (s *Strategy) CalculateAndEmitProfit(ctx context.Context) error { } s.logger.Infof("there are %d closed orders from order id #%d", len(orders), s.ProfitStats.FromOrderID) + isMax := exchange.IsMaxExchange(s.ExchangeSession.Exchange) + var rounds []Round var round Round for _, order := range orders { @@ -402,9 +428,23 @@ func (s *Strategy) CalculateAndEmitProfit(ctx context.Context) error { case types.SideTypeBuy: round.OpenPositionOrders = append(round.OpenPositionOrders, order) case types.SideTypeSell: - if order.Status != types.OrderStatusFilled { - continue + if !isMax { + if order.Status != types.OrderStatusFilled { + s.logger.Infof("take-profit order is %s not filled, so this round is not finished. Skip it", order.Status) + continue + } + } else { + switch maxapi.OrderState(order.OriginalStatus) { + case maxapi.OrderStateDone: + // the same as filled + case maxapi.OrderStateFinalizing: + // the same as filled + default: + s.logger.Infof("isMax and take-profit order is %s not done or finalizing, so this round is not finished. Skip it", order.OriginalStatus) + continue + } } + round.TakeProfitOrder = order rounds = append(rounds, round) round = Round{}