package pivotshort import ( "context" "fmt" "os" "sync" "github.com/sirupsen/logrus" "git.qtrade.icu/lychiyu/qbtrade/pkg/dynamic" "git.qtrade.icu/lychiyu/qbtrade/pkg/fixedpoint" "git.qtrade.icu/lychiyu/qbtrade/pkg/qbtrade" "git.qtrade.icu/lychiyu/qbtrade/pkg/types" ) const ID = "pivotshort" var one = fixedpoint.One var log = logrus.WithField("strategy", ID) func init() { qbtrade.RegisterStrategy(ID, &Strategy{}) } type Strategy struct { Environment *qbtrade.Environment Symbol string `json:"symbol"` Market types.Market // pivot interval and window types.IntervalWindow Leverage fixedpoint.Value `json:"leverage"` Quantity fixedpoint.Value `json:"quantity"` // persistence fields Position *types.Position `persistence:"position"` ProfitStats *types.ProfitStats `persistence:"profit_stats"` TradeStats *types.TradeStats `persistence:"trade_stats"` // BreakLow is one of the entry method BreakLow *BreakLow `json:"breakLow"` FailedBreakHigh *FailedBreakHigh `json:"failedBreakHigh"` // ResistanceShort is one of the entry method ResistanceShort *ResistanceShort `json:"resistanceShort"` ExitMethods qbtrade.ExitMethodSet `json:"exits"` session *qbtrade.ExchangeSession orderExecutor *qbtrade.GeneralOrderExecutor // StrategyController qbtrade.StrategyController } func (s *Strategy) ID() string { return ID } func (s *Strategy) InstanceID() string { return fmt.Sprintf("%s:%s", ID, s.Symbol) } func (s *Strategy) Subscribe(session *qbtrade.ExchangeSession) { session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval}) session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: types.Interval1m}) if s.ResistanceShort != nil && s.ResistanceShort.Enabled { dynamic.InheritStructValues(s.ResistanceShort, s) s.ResistanceShort.Subscribe(session) } if s.BreakLow != nil { dynamic.InheritStructValues(s.BreakLow, s) s.BreakLow.Subscribe(session) } if s.FailedBreakHigh != nil { dynamic.InheritStructValues(s.FailedBreakHigh, s) s.FailedBreakHigh.Subscribe(session) } if !qbtrade.IsBackTesting { session.Subscribe(types.MarketTradeChannel, s.Symbol, types.SubscribeOptions{}) } s.ExitMethods.SetAndSubscribe(session, s) } func (s *Strategy) CurrentPosition() *types.Position { return s.Position } func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Value) error { return s.orderExecutor.ClosePosition(ctx, percentage) } func (s *Strategy) Run(ctx context.Context, orderExecutor qbtrade.OrderExecutor, session *qbtrade.ExchangeSession) error { var instanceID = s.InstanceID() if s.Position == nil { s.Position = types.NewPositionFromMarket(s.Market) } if s.ProfitStats == nil { s.ProfitStats = types.NewProfitStats(s.Market) } if s.TradeStats == nil { s.TradeStats = types.NewTradeStats(s.Symbol) } if s.Leverage.IsZero() { // the default leverage is 3x s.Leverage = fixedpoint.NewFromInt(3) } // StrategyController s.Status = types.StrategyStatusRunning s.OnSuspend(func() { // Cancel active orders _ = s.orderExecutor.GracefulCancel(ctx) if s.BreakLow != nil { s.BreakLow.Suspend() } if s.ResistanceShort != nil { s.ResistanceShort.Suspend() } if s.FailedBreakHigh != nil { s.FailedBreakHigh.Suspend() } }) s.OnEmergencyStop(func() { // Cancel active orders _ = s.orderExecutor.GracefulCancel(ctx) // Close 100% position _ = s.ClosePosition(ctx, fixedpoint.One) if s.BreakLow != nil { s.BreakLow.EmergencyStop() } if s.ResistanceShort != nil { s.ResistanceShort.EmergencyStop() } if s.FailedBreakHigh != nil { s.FailedBreakHigh.EmergencyStop() } }) // initial required information s.session = session s.orderExecutor = qbtrade.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position) s.orderExecutor.BindEnvironment(s.Environment) s.orderExecutor.BindProfitStats(s.ProfitStats) s.orderExecutor.BindTradeStats(s.TradeStats) s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) { qbtrade.Sync(ctx, s) }) s.orderExecutor.Bind() s.ExitMethods.Bind(session, s.orderExecutor) if s.ResistanceShort != nil && s.ResistanceShort.Enabled { s.ResistanceShort.Bind(session, s.orderExecutor) } if s.BreakLow != nil { s.BreakLow.Bind(session, s.orderExecutor) } if s.FailedBreakHigh != nil { s.FailedBreakHigh.Bind(session, s.orderExecutor) } qbtrade.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) { defer wg.Done() _, _ = fmt.Fprintln(os.Stderr, s.TradeStats.String()) _ = s.orderExecutor.GracefulCancel(ctx) }) return nil }