package emacross import ( "context" "fmt" "sync" log "github.com/sirupsen/logrus" "git.qtrade.icu/lychiyu/qbtrade/pkg/fixedpoint" indicatorv2 "git.qtrade.icu/lychiyu/qbtrade/pkg/indicator/v2" "git.qtrade.icu/lychiyu/qbtrade/pkg/qbtrade" "git.qtrade.icu/lychiyu/qbtrade/pkg/strategy/common" "git.qtrade.icu/lychiyu/qbtrade/pkg/types" "git.qtrade.icu/lychiyu/qbtrade/pkg/util" ) const ID = "emacross" func init() { qbtrade.RegisterStrategy(ID, &Strategy{}) } type Strategy struct { *common.Strategy Environment *qbtrade.Environment Market types.Market Symbol string `json:"symbol"` Interval types.Interval `json:"interval"` SlowWindow int `json:"slowWindow"` FastWindow int `json:"fastWindow"` OpenBelow fixedpoint.Value `json:"openBelow"` CloseAbove fixedpoint.Value `json:"closeAbove"` lastKLine types.KLine qbtrade.OpenPositionOptions } func (s *Strategy) ID() string { return ID } func (s *Strategy) InstanceID() string { return fmt.Sprintf("%s:%s:%s:%d-%d", ID, s.Symbol, s.Interval, s.FastWindow, s.SlowWindow) } func (s *Strategy) Initialize() error { if s.Strategy == nil { s.Strategy = &common.Strategy{} } return nil } func (s *Strategy) Subscribe(session *qbtrade.ExchangeSession) { session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval}) } func (s *Strategy) Run(ctx context.Context, _ qbtrade.OrderExecutor, session *qbtrade.ExchangeSession) error { s.Strategy.Initialize(ctx, s.Environment, session, s.Market, ID, s.InstanceID()) session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, types.Interval5m, func(k types.KLine) { s.lastKLine = k })) fastEMA := session.Indicators(s.Symbol).EWMA(types.IntervalWindow{Interval: s.Interval, Window: s.FastWindow}) slowEMA := session.Indicators(s.Symbol).EWMA(types.IntervalWindow{Interval: s.Interval, Window: s.SlowWindow}) cross := indicatorv2.Cross(fastEMA, slowEMA) cross.OnUpdate(func(v float64) { switch indicatorv2.CrossType(v) { case indicatorv2.CrossOver: if err := s.Strategy.OrderExecutor.GracefulCancel(ctx); err != nil { log.WithError(err).Errorf("unable to cancel order") } opts := s.OpenPositionOptions opts.Long = true if price, ok := session.LastPrice(s.Symbol); ok { opts.Price = price } opts.Tags = []string{"emaCrossOver"} _, err := s.Strategy.OrderExecutor.OpenPosition(ctx, opts) util.LogErr(err, "unable to open position") case indicatorv2.CrossUnder: err := s.Strategy.OrderExecutor.ClosePosition(ctx, fixedpoint.One) util.LogErr(err, "unable to submit close position order") } }) qbtrade.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) { defer wg.Done() qbtrade.Sync(ctx, s) }) return nil }