diff --git a/config/xnav.yaml b/config/xnav.yaml index 2e59eef84..7b1078d3a 100644 --- a/config/xnav.yaml +++ b/config/xnav.yaml @@ -30,6 +30,7 @@ crossExchangeStrategies: - xnav: interval: 1h + # schedule: "0 * * * *" # every hour reportOnStart: true ignoreDusts: true diff --git a/pkg/strategy/xnav/strategy.go b/pkg/strategy/xnav/strategy.go index 91e3bc22f..a517dbfca 100644 --- a/pkg/strategy/xnav/strategy.go +++ b/pkg/strategy/xnav/strategy.go @@ -2,19 +2,19 @@ package xnav import ( "context" + "fmt" "sync" "time" - "github.com/c9s/bbgo/pkg/fixedpoint" - "github.com/c9s/bbgo/pkg/util/templateutil" - - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "github.com/slack-go/slack" - "github.com/c9s/bbgo/pkg/bbgo" + "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/util" + "github.com/c9s/bbgo/pkg/util/templateutil" + + "github.com/robfig/cron/v3" + "github.com/sirupsen/logrus" + "github.com/slack-go/slack" ) const ID = "xnav" @@ -59,16 +59,30 @@ type Strategy struct { *bbgo.Environment Interval types.Interval `json:"interval"` + Schedule string `json:"schedule"` ReportOnStart bool `json:"reportOnStart"` IgnoreDusts bool `json:"ignoreDusts"` State *State `persistence:"state"` + + cron *cron.Cron } func (s *Strategy) ID() string { return ID } +func (s *Strategy) Initialize() error { + return nil +} + +func (s *Strategy) Validate() error { + if s.Interval == "" && s.Schedule == "" { + return fmt.Errorf("interval or schedule is required") + } + return nil +} + var Ten = fixedpoint.NewFromInt(10) func (s *Strategy) CrossSubscribe(sessions map[string]*bbgo.ExchangeSession) {} @@ -138,10 +152,6 @@ func (s *Strategy) recordNetAssetValue(ctx context.Context, sessions map[string] } func (s *Strategy) CrossRun(ctx context.Context, _ bbgo.OrderExecutionRouter, sessions map[string]*bbgo.ExchangeSession) error { - if s.Interval == "" { - return errors.New("interval can not be empty") - } - if s.State == nil { s.State = &State{} s.State.Reset() @@ -161,25 +171,32 @@ func (s *Strategy) CrossRun(ctx context.Context, _ bbgo.OrderExecutionRouter, se log.Warnf("xnav does not support backtesting") } - // TODO: if interval is supported, we can use kline as the ticker - if _, ok := types.SupportedIntervals[s.Interval]; ok { + if s.Interval != "" { + go func() { + ticker := time.NewTicker(util.MillisecondsJitter(s.Interval.Duration(), 1000)) + defer ticker.Stop() - } + for { + select { + case <-ctx.Done(): + return - go func() { - ticker := time.NewTicker(util.MillisecondsJitter(s.Interval.Duration(), 1000)) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return - - case <-ticker.C: - s.recordNetAssetValue(ctx, sessions) + case <-ticker.C: + s.recordNetAssetValue(ctx, sessions) + } } + }() + + } else if s.Schedule != "" { + s.cron = cron.New() + _, err := s.cron.AddFunc(s.Schedule, func() { + s.recordNetAssetValue(ctx, sessions) + }) + if err != nil { + return err } - }() + s.cron.Start() + } return nil }