diff --git a/config/pivotshort-ETHUSDT.yaml b/config/pivotshort-ETHUSDT.yaml index 42bdf70f9..33d6f9723 100644 --- a/config/pivotshort-ETHUSDT.yaml +++ b/config/pivotshort-ETHUSDT.yaml @@ -16,9 +16,26 @@ exchangeStrategies: # breakLow settings are used for shorting when the current price break the previous low breakLow: + # ratio is how much the price breaks the previous low to trigger the short. ratio: 0.1% + + # quantity is used for submitting the sell order + # if quantity is not set, all base balance will be used for selling the short. quantity: 10.0 - stopEMARange: 5% + + # marketOrder submits the market sell order when the closed price is lower than the previous pivot low. + marketOrder: true + + # bounceRatio is used for calculating the price of the limit sell order. + # it's ratio of pivot low bounce when a new pivot low is detected. + # Sometimes when the price breaks the previous low, the price might be pulled back to a higher price. + # The bounceRatio is useful for such case, however, you might also miss the chance to short at the price if there is no pull back. + # Notice: When marketOrder is set, bounceRatio will not be used. + # bounceRatio: 0.1% + + # stopEMARange is the price range we allow short. + # Short-allowed price range = [current price] > [EMA] * (1 - [stopEMARange]) + stopEMARange: 0% stopEMA: interval: 1h window: 99 @@ -36,7 +53,7 @@ exchangeStrategies: # roiTakeProfitPercentage is used to force taking profit by percentage of the position ROI (currently the price change) # force to take the profit ROI exceeded the percentage. - roiTakeProfitPercentage: 25% + roiTakeProfitPercentage: 10% # roiMinTakeProfitPercentage applies to lowerShadowRatio and cumulatedVolume exit options roiMinTakeProfitPercentage: 10% diff --git a/pkg/strategy/pivotshort/strategy.go b/pkg/strategy/pivotshort/strategy.go index 187896a04..736a2ae9d 100644 --- a/pkg/strategy/pivotshort/strategy.go +++ b/pkg/strategy/pivotshort/strategy.go @@ -67,6 +67,8 @@ type IntervalWindowSetting struct { // BreakLow -- when price breaks the previous pivot low, we set a trade entry type BreakLow struct { Ratio fixedpoint.Value `json:"ratio"` + MarketOrder bool `json:"marketOrder"` + BounceRatio fixedpoint.Value `json:"bounceRatio"` Quantity fixedpoint.Value `json:"quantity"` StopEMARange fixedpoint.Value `json:"stopEMARange"` StopEMA *types.IntervalWindow `json:"stopEMA"` @@ -156,7 +158,7 @@ func (s *Strategy) submitOrders(ctx context.Context, orderExecutor bbgo.OrderExe s.tradeCollector.Process() } -func (s *Strategy) placeMarketSell(ctx context.Context, orderExecutor bbgo.OrderExecutor, quantity fixedpoint.Value) { +func (s *Strategy) useQuantityOrBaseBalance(quantity fixedpoint.Value) fixedpoint.Value { if quantity.IsZero() { if balance, ok := s.session.Account.Balance(s.Market.BaseCurrency); ok { s.Notify("sell quantity is not set, submitting sell with all base balance: %s", balance.Available.String()) @@ -166,18 +168,30 @@ func (s *Strategy) placeMarketSell(ctx context.Context, orderExecutor bbgo.Order if quantity.IsZero() { log.Errorf("quantity is zero, can not submit sell order, please check settings") - return } - submitOrder := types.SubmitOrder{ + return quantity +} + +func (s *Strategy) placeLimitSell(ctx context.Context, orderExecutor bbgo.OrderExecutor, price, quantity fixedpoint.Value) { + s.submitOrders(ctx, orderExecutor, types.SubmitOrder{ + Symbol: s.Symbol, + Price: price, + Side: types.SideTypeSell, + Type: types.OrderTypeLimit, + Quantity: quantity, + MarginSideEffect: types.SideEffectTypeMarginBuy, + }) +} + +func (s *Strategy) placeMarketSell(ctx context.Context, orderExecutor bbgo.OrderExecutor, quantity fixedpoint.Value) { + s.submitOrders(ctx, orderExecutor, types.SubmitOrder{ Symbol: s.Symbol, Side: types.SideTypeSell, Type: types.OrderTypeMarket, Quantity: quantity, MarginSideEffect: types.SideEffectTypeMarginBuy, - } - - s.submitOrders(ctx, orderExecutor, submitOrder) + }) } func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Value) error { @@ -379,13 +393,18 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se return } - s.Notify("%s price %f breaks the previous low %f with ratio %f, submitting market sell to open a short position", s.Symbol, kline.Close.Float64(), previousLow.Float64(), s.BreakLow.Ratio.Float64()) - if err := s.activeMakerOrders.GracefulCancel(ctx, s.session.Exchange); err != nil { log.WithError(err).Errorf("graceful cancel order error") } - s.placeMarketSell(ctx, orderExecutor, s.BreakLow.Quantity) + quantity := s.useQuantityOrBaseBalance(s.BreakLow.Quantity) + if s.BreakLow.MarketOrder { + s.Notify("%s price %f breaks the previous low %f with ratio %f, submitting market sell to open a short position", s.Symbol, kline.Close.Float64(), previousLow.Float64(), s.BreakLow.Ratio.Float64()) + s.placeMarketSell(ctx, orderExecutor, quantity) + } else { + sellPrice := kline.Close.Mul(fixedpoint.One.Add(s.BreakLow.BounceRatio)) + s.placeLimitSell(ctx, orderExecutor, sellPrice, quantity) + } }) session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { @@ -394,12 +413,10 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se } if s.pivot.LastLow() > 0.0 { - log.Infof("pivot low detected: %f %s", s.pivot.LastLow(), kline.EndTime.Time()) + log.Debugf("pivot low detected: %f %s", s.pivot.LastLow(), kline.EndTime.Time()) lastLow := fixedpoint.NewFromFloat(s.pivot.LastLow()) - if lastLow.Compare(s.lastLow) != 0 { - s.lastLow = lastLow - s.pivotLowPrices = append(s.pivotLowPrices, s.lastLow) - } + s.lastLow = lastLow + s.pivotLowPrices = append(s.pivotLowPrices, s.lastLow) } })