Ver no GitHub

Bullish & Bearish Engulfing Strategy

Overview

This strategy replicates the classic bullish and bearish engulfing candlestick setup that was originally implemented in MetaTrader for the "Bullish and Bearish Engulfing" expert advisor. The StockSharp port evaluates completed candles on a configurable timeframe, optionally skips a number of recent bars, and reacts when an engulfing pattern meets a minimum distance filter. The logic is designed for discretionary traders who want to automate an established price action pattern while keeping control over direction, volume, and how existing positions are handled.

Pattern definition

An engulfing signal is confirmed when two consecutive completed candles satisfy the following rules (after applying the configured shift):

  • Bullish engulfing
    • The most recent evaluated candle closes above its open (bullish body).
    • The preceding candle closes below its open (bearish body).
    • The bullish candle makes a higher high and lower low than the previous candle by at least the distance filter.
    • The bullish close finishes above the previous open and its open is below the prior close, again respecting the distance filter.
  • Bearish engulfing
    • The evaluated candle closes below its open (bearish body).
    • The preceding candle closes above its open (bullish body).
    • The bearish candle still prints a higher high but closes well below the previous open, and its open exceeds the prior close, each by the distance filter.
    • The low of the bearish bar is below the previous low by the distance filter.

These conditions reproduce the original MetaTrader implementation, which demanded that the engulfing candle fully covers the previous body and extends beyond both extremes. The distance filter is measured in pips and converted to price by using the instrument's price step and decimals (5-digit and 3-digit forex quotes are automatically scaled to 10-point pips).

Trading logic

  1. Subscribe to the selected candle type through the high-level API and process only finished candles.
  2. Maintain a short rolling buffer that stores the OHLC values required for the current shift value.
  3. When at least two historical candles are available for evaluation, test the bullish and bearish engulfing conditions described above.
  4. Upon a bullish signal, send a market order on the side defined by BullishSide. Upon a bearish signal, use the side configured via BearishSide.
  5. If CloseOppositePositions is enabled and an opposite exposure exists, the strategy increases the order volume by the absolute current position so that the resulting trade both closes the opposite leg and opens a new one in the desired direction. When the flag is disabled, signals are ignored while an opposite position is open.
  6. Position sizing is controlled by the strategy Volume parameter (default 1 contract/lot). No automatic stop-loss or take-profit is attached by default; risk management is left to the end user or to protective modules (you can combine it with StockSharp's built-in protections).

Parameters

Parameter Description Default Notes
CandleType Timeframe (StockSharp DataType) used for signal detection. 1-hour time frame Adjustable to any supported candle type.
Shift Number of completed candles to skip before evaluating the pattern. 1 Setting 1 analyses the latest closed bar, higher values look further back.
DistanceInPips Minimum pip distance that the engulfing candle must exceed relative to the previous one. 0 Converted to price using the instrument price step; useful to filter small-bodied candles.
CloseOppositePositions Whether to close an existing opposite position when a new signal fires. true Disabling skips the trade if the current exposure conflicts with the signal.
BullishSide Order side executed on a bullish engulfing signal. Buy Can be flipped to Sell for contrarian behaviour.
BearishSide Order side executed on a bearish engulfing signal. Sell Can be flipped to Buy to trade counter-trend setups.
Volume Base order size. 1 The order volume is increased by abs(Position) when closing the opposite side.

Position management and risk

  • Because orders are sent at market without protective stops, combine the strategy with additional modules (e.g., StartProtection) or configure external risk controls.
  • The original MetaTrader code sized trades via a risk-based money manager. In this port the sizing is simplified to a direct volume parameter so that the behaviour is deterministic inside StockSharp; integrate a custom money management block if dynamic sizing is required.
  • When CloseOppositePositions is true, reversals are immediate: the trade volume equals the base volume plus the absolute open position, guaranteeing a flat-to-new-direction transition.

Files

  • CS/BullishBearishEngulfingStrategy.cs – main C# implementation built on the high-level StockSharp strategy API.

Note: No Python implementation is provided for this ID; only the C# version is included as requested.

using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Engulfing pattern strategy that reacts to bullish and bearish engulfing candles.
/// </summary>
public class BullishBearishEngulfingStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _shift;
	private readonly StrategyParam<decimal> _distanceInPips;
	private readonly StrategyParam<bool> _closeOpposite;
	private readonly StrategyParam<Sides> _bullishSide;
	private readonly StrategyParam<Sides> _bearishSide;
	private readonly List<CandleSnapshot> _candles = new();

	/// <summary>
	/// Initializes a new instance of <see cref="BullishBearishEngulfingStrategy"/>.
	/// </summary>
	public BullishBearishEngulfingStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Time frame for analysis", "General");

		_shift = Param(nameof(Shift), 1)
			.SetGreaterThanZero()
			.SetDisplay("Shift", "Number of completed candles to skip", "Pattern")
			
			.SetOptimize(1, 5, 1);

		_distanceInPips = Param(nameof(DistanceInPips), 0m)
			.SetNotNegative()
			.SetDisplay("Distance (pips)", "Additional filter expressed in pips", "Pattern")
			
			.SetOptimize(0m, 10m, 1m);

		_closeOpposite = Param(nameof(CloseOppositePositions), true)
			.SetDisplay("Close Opposite", "Close opposite position before entering", "Risk");

		_bullishSide = Param(nameof(BullishSide), Sides.Buy)
			.SetDisplay("Bullish Action", "Order side for bullish engulfing", "Pattern");

		_bearishSide = Param(nameof(BearishSide), Sides.Sell)
			.SetDisplay("Bearish Action", "Order side for bearish engulfing", "Pattern");
	}

	/// <summary>
	/// Candle type used for pattern detection.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Number of fully completed candles to skip before pattern evaluation.
	/// </summary>
	public int Shift
	{
		get => _shift.Value;
		set => _shift.Value = value;
	}

	/// <summary>
	/// Additional price filter expressed in pips.
	/// </summary>
	public decimal DistanceInPips
	{
		get => _distanceInPips.Value;
		set => _distanceInPips.Value = value;
	}

	/// <summary>
	/// Indicates whether opposite positions should be closed before entering a new trade.
	/// </summary>
	public bool CloseOppositePositions
	{
		get => _closeOpposite.Value;
		set => _closeOpposite.Value = value;
	}

	/// <summary>
	/// Side used when a bullish engulfing pattern appears.
	/// </summary>
	public Sides BullishSide
	{
		get => _bullishSide.Value;
		set => _bullishSide.Value = value;
	}

	/// <summary>
	/// Side used when a bearish engulfing pattern appears.
	/// </summary>
	public Sides BearishSide
	{
		get => _bearishSide.Value;
		set => _bearishSide.Value = value;
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_candles.Clear();
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_candles.Clear();

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();

		// no protection needed
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var snapshot = new CandleSnapshot
		{
			Open = candle.OpenPrice,
			High = candle.HighPrice,
			Low = candle.LowPrice,
			Close = candle.ClosePrice
		};

		_candles.Add(snapshot);

		var maxCount = Math.Max(Shift + 2, 3);
		while (_candles.Count > maxCount)
			try { _candles.RemoveAt(0); } catch { break; }

		if (_candles.Count < Shift + 1)
			return;

		var candles = _candles.ToArray();
		var currentIndex = candles.Length - Shift;
		if (currentIndex <= 0)
			return;

		var previousIndex = currentIndex - 1;
		if (previousIndex < 0)
			return;

		var current = candles[currentIndex];
		var previous = candles[previousIndex];
		var distance = CalculateDistanceInPrice();

		var isBullishEngulfing = current.Close > current.Open && previous.Open > previous.Close &&
			current.High > previous.High + distance &&
			current.Close > previous.Open + distance &&
			current.Open < previous.Close - distance &&
			current.Low < previous.Low - distance;

		if (isBullishEngulfing)
		{
			HandleSignal(BullishSide);
			return;
		}

		var isBearishEngulfing = current.Open > current.Close && previous.Open < previous.Close &&
			current.High > previous.High + distance &&
			current.Open > previous.Close + distance &&
			current.Close < previous.Open - distance &&
			current.Low < previous.Low - distance;

		if (isBearishEngulfing)
			HandleSignal(BearishSide);
	}

	private void HandleSignal(Sides side)
	{
		switch (side)
		{
			case Sides.Buy:
				EnterLong();
				break;
			case Sides.Sell:
				EnterShort();
				break;
		}
	}

	private void EnterLong()
	{
		if (Position > 0)
			return;

		if (Position < 0)
		{
			if (!CloseOppositePositions)
				return;

			// Close short first
			BuyMarket();
		}

		BuyMarket();
	}

	private void EnterShort()
	{
		if (Position < 0)
			return;

		if (Position > 0)
		{
			if (!CloseOppositePositions)
				return;

			// Close long first
			SellMarket();
		}

		SellMarket();
	}

	private decimal CalculateDistanceInPrice()
	{
		var priceStep = Security?.PriceStep;
		if (priceStep == null)
			return 0m;

		var decimals = Security?.Decimals ?? 0;
		var multiplier = decimals is 3 or 5 ? 10m : 1m;
		return DistanceInPips * priceStep.Value * multiplier;
	}

	private struct CandleSnapshot
	{
		public decimal Open;
		public decimal High;
		public decimal Low;
		public decimal Close;
	}
}