mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
Merge pull request #1580 from c9s/kbearXD/dca2/profit
FIX: [dca2] must calculate and emit profit at the end of the round
This commit is contained in:
commit
3981970667
|
@ -46,6 +46,10 @@ const (
|
|||
OrderStateFailed = OrderState("failed")
|
||||
)
|
||||
|
||||
func IsFilledOrderState(state OrderState) bool {
|
||||
return state == OrderStateDone || state == OrderStateFinalizing
|
||||
}
|
||||
|
||||
type OrderType string
|
||||
|
||||
// Order types that the API can return.
|
||||
|
|
|
@ -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 {
|
||||
|
@ -79,8 +82,9 @@ func recoverState(ctx context.Context, maxOrderCount int, currentRound Round, or
|
|||
|
||||
// dca stop at take-profit order stage
|
||||
if currentRound.TakeProfitOrder.OrderID != 0 {
|
||||
if len(currentRound.OpenPositionOrders) != maxOrderCount {
|
||||
return None, fmt.Errorf("there is take-profit order but the number of open-position orders (%d) is not the same as maxOrderCount(%d). Please check it", len(currentRound.OpenPositionOrders), maxOrderCount)
|
||||
// the number of open-positions orders may not be equal to maxOrderCount, because the notional may not enough to open maxOrderCount orders
|
||||
if len(currentRound.OpenPositionOrders) > maxOrderCount {
|
||||
return None, fmt.Errorf("there is take-profit order but the number of open-position orders (%d) is greater than maxOrderCount(%d). Please check it", len(currentRound.OpenPositionOrders), maxOrderCount)
|
||||
}
|
||||
|
||||
takeProfitOrder := currentRound.TakeProfitOrder
|
||||
|
@ -202,6 +206,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 +215,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 {
|
||||
|
|
|
@ -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.CalculateAndEmitProfitUntilSuccessful(ctx); err != nil {
|
||||
s.logger.WithError(err).Warn("failed to calculate and emit profit")
|
||||
}
|
||||
|
||||
|
|
|
@ -8,11 +8,14 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/sirupsen/logrus"
|
||||
"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 +373,9 @@ func (s *Strategy) CleanUp(ctx context.Context) error {
|
|||
return werr
|
||||
}
|
||||
|
||||
func (s *Strategy) CalculateAndEmitProfit(ctx context.Context) error {
|
||||
func (s *Strategy) CalculateAndEmitProfitUntilSuccessful(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 +386,22 @@ func (s *Strategy) CalculateAndEmitProfit(ctx context.Context) error {
|
|||
return fmt.Errorf("exchange %s doesn't support ExchangeOrderQueryService", s.ExchangeSession.Exchange.Name())
|
||||
}
|
||||
|
||||
var op = func() error {
|
||||
if err := s.CalculateAndEmitProfit(ctx, historyService, queryService); err != nil {
|
||||
return errors.Wrapf(err, "failed to calculate and emit profit, please check it")
|
||||
}
|
||||
|
||||
if s.ProfitStats.FromOrderID == fromOrderID {
|
||||
return fmt.Errorf("FromOrderID (%d) is not updated, retry it", s.ProfitStats.FromOrderID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return retry.GeneralLiteBackoff(ctx, op)
|
||||
}
|
||||
|
||||
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 +411,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 +425,18 @@ 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 {
|
||||
if !maxapi.IsFilledOrderState(maxapi.OrderState(order.OriginalStatus)) {
|
||||
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{}
|
||||
|
@ -415,7 +447,7 @@ func (s *Strategy) CalculateAndEmitProfit(ctx context.Context) error {
|
|||
|
||||
s.logger.Infof("there are %d rounds from order id #%d", len(rounds), s.ProfitStats.FromOrderID)
|
||||
for _, round := range rounds {
|
||||
debugRoundOrders(s.logger, "calculate", round)
|
||||
debugRoundOrders(s.logger, strconv.FormatInt(s.ProfitStats.Round, 10), round)
|
||||
var roundOrders []types.Order = round.OpenPositionOrders
|
||||
roundOrders = append(roundOrders, round.TakeProfitOrder)
|
||||
for _, order := range roundOrders {
|
||||
|
|
Loading…
Reference in New Issue
Block a user