Skip to content

Commit 3981970

Browse files
authored
Merge pull request #1580 from c9s/kbearXD/dca2/profit
FIX: [dca2] must calculate and emit profit at the end of the round
2 parents 3a98660 + 2b52211 commit 3981970

File tree

4 files changed

+54
-11
lines changed

4 files changed

+54
-11
lines changed

pkg/exchange/max/maxapi/order.go

+4
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ const (
4646
OrderStateFailed = OrderState("failed")
4747
)
4848

49+
func IsFilledOrderState(state OrderState) bool {
50+
return state == OrderStateDone || state == OrderStateFinalizing
51+
}
52+
4953
type OrderType string
5054

5155
// Order types that the API can return.

pkg/strategy/dca2/recover.go

+13-6
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,14 @@ func (s *Strategy) recover(ctx context.Context) error {
4545
}
4646
debugRoundOrders(s.logger, "current", currentRound)
4747

48+
// TODO: use flag
4849
// recover profit stats
49-
if err := recoverProfitStats(ctx, s); err != nil {
50-
return err
51-
}
52-
s.logger.Info("recover profit stats DONE")
50+
/*
51+
if err := recoverProfitStats(ctx, s); err != nil {
52+
return err
53+
}
54+
s.logger.Info("recover profit stats DONE")
55+
*/
5356

5457
// recover position
5558
if err := recoverPosition(ctx, s.Position, queryService, currentRound); err != nil {
@@ -79,8 +82,9 @@ func recoverState(ctx context.Context, maxOrderCount int, currentRound Round, or
7982

8083
// dca stop at take-profit order stage
8184
if currentRound.TakeProfitOrder.OrderID != 0 {
82-
if len(currentRound.OpenPositionOrders) != maxOrderCount {
83-
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)
85+
// the number of open-positions orders may not be equal to maxOrderCount, because the notional may not enough to open maxOrderCount orders
86+
if len(currentRound.OpenPositionOrders) > maxOrderCount {
87+
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)
8488
}
8589

8690
takeProfitOrder := currentRound.TakeProfitOrder
@@ -202,13 +206,16 @@ func recoverPosition(ctx context.Context, position *types.Position, queryService
202206
return nil
203207
}
204208

209+
// TODO: use flag to decide which to recover
210+
/*
205211
func recoverProfitStats(ctx context.Context, strategy *Strategy) error {
206212
if strategy.ProfitStats == nil {
207213
return fmt.Errorf("profit stats is nil, please check it")
208214
}
209215
210216
return strategy.CalculateAndEmitProfit(ctx)
211217
}
218+
*/
212219

213220
func recoverStartTimeOfNextRound(ctx context.Context, currentRound Round, coolDownInterval types.Duration) time.Time {
214221
if currentRound.TakeProfitOrder.OrderID != 0 && currentRound.TakeProfitOrder.Status == types.OrderStatusFilled {

pkg/strategy/dca2/state.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ func (s *Strategy) runTakeProfitReady(ctx context.Context, next State) {
201201
// reset position
202202

203203
// calculate profit stats
204-
if err := s.CalculateAndEmitProfit(ctx); err != nil {
204+
if err := s.CalculateAndEmitProfitUntilSuccessful(ctx); err != nil {
205205
s.logger.WithError(err).Warn("failed to calculate and emit profit")
206206
}
207207

pkg/strategy/dca2/strategy.go

+36-4
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ import (
88
"sync"
99
"time"
1010

11+
"github.com/pkg/errors"
1112
"github.com/prometheus/client_golang/prometheus"
1213
"github.com/sirupsen/logrus"
1314
"go.uber.org/multierr"
1415

1516
"github.com/c9s/bbgo/pkg/bbgo"
17+
"github.com/c9s/bbgo/pkg/exchange"
18+
maxapi "github.com/c9s/bbgo/pkg/exchange/max/maxapi"
1619
"github.com/c9s/bbgo/pkg/exchange/retry"
1720
"github.com/c9s/bbgo/pkg/fixedpoint"
1821
"github.com/c9s/bbgo/pkg/strategy/common"
@@ -370,7 +373,9 @@ func (s *Strategy) CleanUp(ctx context.Context) error {
370373
return werr
371374
}
372375

373-
func (s *Strategy) CalculateAndEmitProfit(ctx context.Context) error {
376+
func (s *Strategy) CalculateAndEmitProfitUntilSuccessful(ctx context.Context) error {
377+
fromOrderID := s.ProfitStats.FromOrderID
378+
374379
historyService, ok := s.ExchangeSession.Exchange.(types.ExchangeTradeHistoryService)
375380
if !ok {
376381
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 {
381386
return fmt.Errorf("exchange %s doesn't support ExchangeOrderQueryService", s.ExchangeSession.Exchange.Name())
382387
}
383388

389+
var op = func() error {
390+
if err := s.CalculateAndEmitProfit(ctx, historyService, queryService); err != nil {
391+
return errors.Wrapf(err, "failed to calculate and emit profit, please check it")
392+
}
393+
394+
if s.ProfitStats.FromOrderID == fromOrderID {
395+
return fmt.Errorf("FromOrderID (%d) is not updated, retry it", s.ProfitStats.FromOrderID)
396+
}
397+
398+
return nil
399+
}
400+
401+
return retry.GeneralLiteBackoff(ctx, op)
402+
}
403+
404+
func (s *Strategy) CalculateAndEmitProfit(ctx context.Context, historyService types.ExchangeTradeHistoryService, queryService types.ExchangeOrderQueryService) error {
384405
// TODO: pagination for it
385406
// query the orders
386407
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 {
390411
}
391412
s.logger.Infof("there are %d closed orders from order id #%d", len(orders), s.ProfitStats.FromOrderID)
392413

414+
isMax := exchange.IsMaxExchange(s.ExchangeSession.Exchange)
415+
393416
var rounds []Round
394417
var round Round
395418
for _, order := range orders {
@@ -402,9 +425,18 @@ func (s *Strategy) CalculateAndEmitProfit(ctx context.Context) error {
402425
case types.SideTypeBuy:
403426
round.OpenPositionOrders = append(round.OpenPositionOrders, order)
404427
case types.SideTypeSell:
405-
if order.Status != types.OrderStatusFilled {
406-
continue
428+
if !isMax {
429+
if order.Status != types.OrderStatusFilled {
430+
s.logger.Infof("take-profit order is %s not filled, so this round is not finished. Skip it", order.Status)
431+
continue
432+
}
433+
} else {
434+
if !maxapi.IsFilledOrderState(maxapi.OrderState(order.OriginalStatus)) {
435+
s.logger.Infof("isMax and take-profit order is %s not done or finalizing, so this round is not finished. Skip it", order.OriginalStatus)
436+
continue
437+
}
407438
}
439+
408440
round.TakeProfitOrder = order
409441
rounds = append(rounds, round)
410442
round = Round{}
@@ -415,7 +447,7 @@ func (s *Strategy) CalculateAndEmitProfit(ctx context.Context) error {
415447

416448
s.logger.Infof("there are %d rounds from order id #%d", len(rounds), s.ProfitStats.FromOrderID)
417449
for _, round := range rounds {
418-
debugRoundOrders(s.logger, "calculate", round)
450+
debugRoundOrders(s.logger, strconv.FormatInt(s.ProfitStats.Round, 10), round)
419451
var roundOrders []types.Order = round.OpenPositionOrders
420452
roundOrders = append(roundOrders, round.TakeProfitOrder)
421453
for _, order := range roundOrders {

0 commit comments

Comments
 (0)