diff --git a/pkg/strategy/pivotshort/exit.go b/pkg/strategy/pivotshort/exit.go index 34b808704..735814995 100644 --- a/pkg/strategy/pivotshort/exit.go +++ b/pkg/strategy/pivotshort/exit.go @@ -5,10 +5,8 @@ import "github.com/c9s/bbgo/pkg/bbgo" type ExitMethod struct { RoiStopLoss *RoiStopLoss `json:"roiStopLoss"` ProtectionStopLoss *ProtectionStopLoss `json:"protectionStopLoss"` - RoiTakeProfit *RoiTakeProfit `json:"roiTakeProfit"` LowerShadowTakeProfit *LowerShadowTakeProfit `json:"lowerShadowTakeProfit"` - CumulatedVolumeTakeProfit *CumulatedVolumeTakeProfit `json:"cumulatedVolumeTakeProfit"` } diff --git a/pkg/strategy/pivotshort/protection_stop.go b/pkg/strategy/pivotshort/protection_stop.go index 3831ac2c0..02a47fac7 100644 --- a/pkg/strategy/pivotshort/protection_stop.go +++ b/pkg/strategy/pivotshort/protection_stop.go @@ -110,6 +110,20 @@ func (s *ProtectionStopLoss) Bind(session *bbgo.ExchangeSession, orderExecutor * s.handleChange(context.Background(), position, kline.Close, s.orderExecutor) } }) + + if !bbgo.IsBackTesting { + session.MarketDataStream.OnMarketTrade(func(trade types.Trade) { + if trade.Symbol != position.Symbol { + return + } + + if s.stopLossPrice.IsZero() || s.PlaceStopOrder { + return + } + + s.checkStopPrice(trade.Price, position) + }) + } } func (s *ProtectionStopLoss) handleChange(ctx context.Context, position *types.Position, closePrice fixedpoint.Value, orderExecutor *bbgo.GeneralOrderExecutor) { @@ -136,19 +150,27 @@ func (s *ProtectionStopLoss) handleChange(ctx context.Context, position *types.P log.Infof("[ProtectionStopLoss] %s protection stop loss activated, current price = %f, average cost = %f, stop loss price = %f", position.Symbol, closePrice.Float64(), position.AverageCost.Float64(), s.stopLossPrice.Float64()) + + if s.PlaceStopOrder { + if err := s.placeStopOrder(ctx, position, orderExecutor); err != nil { + log.WithError(err).Errorf("failed to place stop limit order") + } + return + } } else { // not activated, skip setup stop order return } } - if s.PlaceStopOrder { - if err := s.placeStopOrder(ctx, position, orderExecutor); err != nil { - log.WithError(err).Errorf("failed to place stop limit order") - } - } else if s.shouldStop(closePrice) { + // check stop price + s.checkStopPrice(closePrice, position) +} + +func (s *ProtectionStopLoss) checkStopPrice(closePrice fixedpoint.Value, position *types.Position) { + if s.shouldStop(closePrice) { log.Infof("[ProtectionStopLoss] protection stop order is triggered at price %f, position = %+v", closePrice.Float64(), position) - if err := orderExecutor.ClosePosition(ctx, one); err != nil { + if err := s.orderExecutor.ClosePosition(context.Background(), one); err != nil { log.WithError(err).Errorf("failed to close position") } } diff --git a/pkg/strategy/pivotshort/roi_stop.go b/pkg/strategy/pivotshort/roi_stop.go index ab9f3766a..34cc7d5d0 100644 --- a/pkg/strategy/pivotshort/roi_stop.go +++ b/pkg/strategy/pivotshort/roi_stop.go @@ -25,17 +25,30 @@ func (s *RoiStopLoss) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Ge return } - closePrice := kline.Close - if position.IsClosed() || position.IsDust(closePrice) { - return - } - - roi := position.ROI(closePrice) - if roi.Compare(s.Percentage.Neg()) < 0 { - // stop loss - bbgo.Notify("[RoiStopLoss] %s stop loss triggered by ROI %s/%s, price: %f", position.Symbol, roi.Percentage(), s.Percentage.Neg().Percentage(), kline.Close.Float64()) - _ = orderExecutor.ClosePosition(context.Background(), fixedpoint.One) - return - } + s.checkStopPrice(kline.Close, position) }) + + if !bbgo.IsBackTesting { + session.MarketDataStream.OnMarketTrade(func(trade types.Trade) { + if trade.Symbol != position.Symbol { + return + } + + s.checkStopPrice(trade.Price, position) + }) + } +} + +func (s *RoiStopLoss) checkStopPrice(closePrice fixedpoint.Value, position *types.Position) { + if position.IsClosed() || position.IsDust(closePrice) { + return + } + + roi := position.ROI(closePrice) + if roi.Compare(s.Percentage.Neg()) < 0 { + // stop loss + bbgo.Notify("[RoiStopLoss] %s stop loss triggered by ROI %s/%s, price: %f", position.Symbol, roi.Percentage(), s.Percentage.Neg().Percentage(), closePrice.Float64()) + _ = s.orderExecutor.ClosePosition(context.Background(), fixedpoint.One) + return + } } diff --git a/pkg/strategy/pivotshort/strategy.go b/pkg/strategy/pivotshort/strategy.go index de77a983d..94699edc9 100644 --- a/pkg/strategy/pivotshort/strategy.go +++ b/pkg/strategy/pivotshort/strategy.go @@ -123,6 +123,10 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { if s.BounceShort != nil && s.BounceShort.Enabled { session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.BounceShort.Interval}) } + + if !bbgo.IsBackTesting { + session.Subscribe(types.MarketTradeChannel, s.Symbol, types.SubscribeOptions{}) + } } func (s *Strategy) useQuantityOrBaseBalance(quantity fixedpoint.Value) fixedpoint.Value { @@ -189,6 +193,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.TradeStats = &types.TradeStats{} } + s.lastLow = fixedpoint.Zero + // StrategyController s.Status = types.StrategyStatusRunning @@ -230,7 +236,13 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.stopEWMA = standardIndicator.EWMA(*s.BreakLow.StopEMA) } - s.lastLow = fixedpoint.Zero + for _, method := range s.ExitMethods { + method.Bind(session, s.orderExecutor) + } + + session.MarketDataStream.OnMarketTrade(func(trade types.Trade) { + log.Info(trade) + }) session.UserDataStream.OnStart(func() { lastKLine := s.preloadPivot(s.pivot, store) @@ -264,10 +276,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se } }) - for _, method := range s.ExitMethods { - method.Bind(session, s.orderExecutor) - } - // Always check whether you can open a short position or not session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { if s.Status != types.StrategyStatusRunning {