Bearish Engulfing Pattern Strategy
This pattern aims to capture the start of a bearish swing after a rally. A bearish engulfing occurs when a red candle completely swallows the prior bullish body. Counting a few consecutive up bars before the pattern ensures the market was previously rising.
Testing indicates an average annual return of about 79%. It performs best in the stocks market.
The algorithm stores each candle in sequence. If the new bar closes lower than it opens and its body engulfs the previous bullish bar, a short sale is executed. The stop-loss is positioned above the pattern high to limit exposure.
Positions are typically managed using the protective stop, although the trader may exit manually if conditions change. Requiring an uptrend helps avoid false signals during choppy markets.
Details
- Entry Criteria: Bearish candle engulfs prior bullish bar, optional uptrend present.
- Long/Short: Short only.
- Exit Criteria: Stop-loss or discretionary.
- Stops: Yes, above pattern high.
- Default Values:
CandleType= 15 minuteStopLossPercent= 1RequireUptrend= trueUptrendBars= 3
- Filters:
- Category: Pattern
- Direction: Short
- Indicators: Candlestick
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Short-only bearish body engulfing after optional consecutive bullish candles.
/// Protection sits above the higher of the two pattern highs.
/// </summary>
public class EngulfingBearishStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<bool> _requireUptrend;
private readonly StrategyParam<int> _uptrendBars;
private readonly List<ICandleMessage> _recent = new();
private decimal? _patternStop;
private Order _entryOrder;
private Order _exitOrder;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public bool RequireUptrend { get => _requireUptrend.Value; set => _requireUptrend.Value = value; }
public int UptrendBars { get => _uptrendBars.Value; set => _uptrendBars.Value = value; }
public EngulfingBearishStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Engulfing timeframe", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 1m).SetRange(0m, 99m)
.SetDisplay("Stop above pattern high (%)", "Buffer above the higher of the two pattern highs; zero places the stop at the high.", "Protection");
_requireUptrend = Param(nameof(RequireUptrend), true)
.SetDisplay("Require Uptrend", "Require consecutive bullish candles immediately before the engulfing candle.", "Pattern");
_uptrendBars = Param(nameof(UptrendBars), 3).SetRange(1, 100)
.SetDisplay("Uptrend Bars", "Number of prior consecutive bullish candles, including the engulfed candle.", "Pattern");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
protected override void OnReseted()
{
base.OnReseted();
ClearState();
}
private void ClearState()
{
_recent.Clear();
_patternStop = null;
_entryOrder = _exitOrder = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ClearState();
var asks = new Subscription(DataType.Level1, Security);
asks.MarketData.BuildField = Level1Fields.BestAskPrice;
SubscribeLevel1(asks).Bind(ProcessAsk).Start();
var candles = SubscribeCandles(CandleType);
candles.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, candles);
DrawOwnTrades(area);
}
}
private static bool IsPending(Order order)
=> order is not null && order.State is not (OrderStates.Done or OrderStates.Failed);
private void ProcessAsk(Level1ChangeMessage message)
{
if (message.TryGetDecimal(Level1Fields.BestAskPrice) is decimal ask && ask > 0m)
CheckStop(ask);
}
private void CheckStop(decimal executableAsk)
{
if (Position < 0m && _patternStop is decimal stop && executableAsk >= stop && !IsPending(_exitOrder))
{
_exitOrder = BuyMarket(Math.Abs(Position));
_recent.Clear();
_patternStop = null;
}
}
private void Append(ICandleMessage candle)
{
_recent.Add(candle);
if (_recent.Count > UptrendBars)
_recent.RemoveAt(0);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (Position < 0m)
{
// Bar-high fallback covers gaps or missing executable quote updates.
CheckStop(candle.HighPrice);
if (Position < 0m && !IsPending(_exitOrder))
Append(candle);
return;
}
if (IsPending(_entryOrder) || IsPending(_exitOrder))
{
Append(candle);
return;
}
var previous = _recent.LastOrDefault();
var uptrend = !RequireUptrend ||
_recent.Count >= UptrendBars &&
_recent.TakeLast(UptrendBars).All(bar => bar.ClosePrice > bar.OpenPrice);
var engulfing = previous is not null && previous.ClosePrice > previous.OpenPrice &&
candle.ClosePrice < candle.OpenPrice &&
candle.OpenPrice >= previous.ClosePrice &&
candle.ClosePrice <= previous.OpenPrice;
if (engulfing && uptrend && IsFormedAndOnlineAndAllowTrading())
{
_patternStop = Math.Max(previous.HighPrice, candle.HighPrice) * (1m + StopLossPercent / 100m);
_entryOrder = SellMarket(Volume);
}
Append(candle);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal, Math
from StockSharp.Messages import DataType, CandleStates, Level1Fields, OrderStates
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
class engulfing_bearish_strategy(Strategy):
"""
Short-only bearish body engulfing after optional consecutive bullish candles.
Protection sits above the higher of the two pattern highs.
"""
def __init__(self):
super(engulfing_bearish_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).SetDisplay("Candle Type", "Engulfing timeframe", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 1.0).SetRange(0.0, 99.0).SetDisplay("Stop above pattern high (%)", "Buffer above the higher of the two pattern highs; zero places the stop at the high.", "Protection")
self._require_uptrend = self.Param("RequireUptrend", True).SetDisplay("Require Uptrend", "Require consecutive bullish candles immediately before the engulfing candle.", "Pattern")
self._uptrend_bars = self.Param("UptrendBars", 3).SetRange(1, 100).SetDisplay("Uptrend Bars", "Number of prior consecutive bullish candles, including the engulfed candle.", "Pattern")
self._clear_state()
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, DataType.Level1)]
def _clear_state(self):
self._recent = []
self._pattern_stop = None
self._entry_order = None
self._exit_order = None
def OnReseted(self):
super(engulfing_bearish_strategy, self).OnReseted()
self._clear_state()
def OnStarted2(self, time):
super(engulfing_bearish_strategy, self).OnStarted2(time)
self._clear_state()
asks = Subscription(DataType.Level1, self.Security)
asks.MarketData.BuildField = Level1Fields.BestAskPrice
self.SubscribeLevel1(asks).Bind(self._process_ask).Start()
candles = self.SubscribeCandles(self.candle_type)
candles.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, candles)
self.DrawOwnTrades(area)
def _is_pending(self, order):
return order is not None and order.State not in (OrderStates.Done, OrderStates.Failed)
def _process_ask(self, message):
if message.Changes.ContainsKey(Level1Fields.BestAskPrice):
ask = message.Changes[Level1Fields.BestAskPrice]
if ask is not None and ask > Decimal(0):
self._check_stop(ask)
def _check_stop(self, executable_ask):
if (self.Position < 0 and self._pattern_stop is not None and
executable_ask >= self._pattern_stop and not self._is_pending(self._exit_order)):
self._exit_order = self.BuyMarket(Math.Abs(self.Position))
self._recent = []
self._pattern_stop = None
def _append(self, candle):
self._recent.append(candle)
if len(self._recent) > self._uptrend_bars.Value:
self._recent.pop(0)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if self.Position < 0:
# Bar-high fallback covers gaps or missing executable quote updates.
self._check_stop(candle.HighPrice)
if self.Position < 0 and not self._is_pending(self._exit_order):
self._append(candle)
return
if self._is_pending(self._entry_order) or self._is_pending(self._exit_order):
self._append(candle)
return
previous = self._recent[-1] if self._recent else None
uptrend = (not self._require_uptrend.Value or
len(self._recent) >= self._uptrend_bars.Value and
all(bar.ClosePrice > bar.OpenPrice for bar in self._recent[-self._uptrend_bars.Value:]))
engulfing = (previous is not None and previous.ClosePrice > previous.OpenPrice and
candle.ClosePrice < candle.OpenPrice and
candle.OpenPrice >= previous.ClosePrice and
candle.ClosePrice <= previous.OpenPrice)
if engulfing and uptrend and self.IsFormedAndOnlineAndAllowTrading():
pattern_high = Math.Max(previous.HighPrice, candle.HighPrice)
self._pattern_stop = pattern_high * (Decimal(1) + Decimal(self._stop_loss_percent.Value) / Decimal(100))
self._entry_order = self.SellMarket(self.Volume)
self._append(candle)
def CreateClone(self):
return engulfing_bearish_strategy()