dca2: must calculate and emit profit at the end of the round

This commit is contained in:
kbearXD 2024-03-14 11:40:03 +08:00
parent 3a98660e0c
commit 91123edbd6
3 changed files with 54 additions and 8 deletions

View File

@ -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 {

View File

@ -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")
}

View File

@ -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{}