Ver no GitHub

Bullish & Bearish Harami Stochastic Strategy

The Bullish & Bearish Harami Stochastic Strategy is the StockSharp port of the MetaTrader Expert Advisor expert_abh_bh_stoch.mq5 from folder MQL/310. The original expert uses candlestick pattern recognition for Bullish Harami and Bearish Harami setups and requires a stochastic oscillator confirmation. The C# version keeps the same logic using the high-level StockSharp API and adds detailed logging and chart output for easier monitoring.

Core Ideas

  • Detect Bullish Harami and Bearish Harami candlestick patterns using the previous two completed candles.
  • Confirm bullish setups with the stochastic %D line below an oversold threshold and bearish setups with %D above an overbought threshold.
  • Close short positions when the stochastic %D line rebounds above either the lower or upper exit thresholds, and close long positions when %D drops below those thresholds.

Parameters

Parameter Description Default
CandleType Timeframe of the candle series used for pattern recognition. 1 Hour
StochasticKPeriod Lookback period for the stochastic %K calculation. 47
StochasticDPeriod Smoothing period for the %D line. 9
StochasticSlowing Additional smoothing applied to %K (MT5 “slowing”). 13
MovingAveragePeriod Number of candles used to average body size for pattern validation. 5
OversoldLevel Stochastic %D threshold to confirm bullish signals. 30
OverboughtLevel Stochastic %D threshold to confirm bearish signals. 70
ExitLowerLevel Lower stochastic level that triggers exits. 20
ExitUpperLevel Upper stochastic level that triggers exits. 80

Trading Rules

Long Entry

  1. A Bullish Harami pattern is detected on the two most recent completed candles (a small bullish candle engulfed by a longer bearish candle in a downtrend).
  2. The stochastic %D line of the confirmation candle is at or below OversoldLevel.
  3. No long position is currently open (Position <= 0).
  4. The strategy buys at market for the configured Volume, adding any short exposure to flip the position if necessary.

Short Entry

  1. A Bearish Harami pattern is detected (small bearish candle inside a long bullish candle during an uptrend).
  2. The stochastic %D value is at or above OverboughtLevel.
  3. No short exposure exists (Position >= 0).
  4. The strategy sells at market, covering any long position first if required.

Exits

  • Cover Shorts: When the stochastic %D crosses upward through either ExitLowerLevel or ExitUpperLevel, the algorithm covers the entire short position.
  • Close Longs: When the stochastic %D crosses downward through ExitUpperLevel or ExitLowerLevel, the long position is closed.

Files

  • CS/BullishBearishHaramiStochasticStrategy.cs — high-level StockSharp implementation of the strategy.
  • README.md — English documentation (this file).
  • README_ru.md — Russian documentation.
  • README_zh.md — Chinese documentation.

Note: The Python version is not included per the conversion instructions.

namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Harami + Stochastic strategy: Bullish/bearish harami patterns with stochastic confirmation.
/// </summary>
public class BullishBearishHaramiStochasticStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _stochPeriod;
	private readonly StrategyParam<decimal> _oversold;
	private readonly StrategyParam<decimal> _overbought;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private readonly List<ICandleMessage> _candles = new();
	private decimal _prevK;
	private bool _hasPrevK;
	private int _candlesSinceTrade;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int StochPeriod { get => _stochPeriod.Value; set => _stochPeriod.Value = value; }
	public decimal Oversold { get => _oversold.Value; set => _oversold.Value = value; }
	public decimal Overbought { get => _overbought.Value; set => _overbought.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public BullishBearishHaramiStochasticStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_stochPeriod = Param(nameof(StochPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("Stoch Period", "Stochastic K period", "Indicators");
		_oversold = Param(nameof(Oversold), 30m)
			.SetDisplay("Oversold", "Stochastic oversold level", "Signals");
		_overbought = Param(nameof(Overbought), 70m)
			.SetDisplay("Overbought", "Stochastic overbought level", "Signals");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 6)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_candles.Clear();
		_prevK = 0m;
		_hasPrevK = false;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_candles.Clear();
		_hasPrevK = false;
		_candlesSinceTrade = SignalCooldownCandles;
		var stoch = new StochasticOscillator { K = { Length = StochPeriod }, D = { Length = 3 } };
		var subscription = SubscribeCandles(CandleType);
		subscription.BindEx(stoch, ProcessCandle).Start();
	}

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

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		var stochTyped = stochValue as StochasticOscillatorValue;
		if (stochTyped?.K is not decimal kValue) return;

		_candles.Add(candle);
		if (_candles.Count > 5) _candles.RemoveAt(0);

		if (_candles.Count >= 2)
		{
			var curr = _candles[^1];
			var prev = _candles[^2];

			// Bullish harami: prev bearish, curr bullish, curr body inside prev body
			var bullishHarami = prev.OpenPrice > prev.ClosePrice
				&& curr.ClosePrice > curr.OpenPrice
				&& curr.OpenPrice > prev.ClosePrice
				&& curr.ClosePrice < prev.OpenPrice;

			// Bearish harami: prev bullish, curr bearish, curr body inside prev body
			var bearishHarami = prev.ClosePrice > prev.OpenPrice
				&& curr.OpenPrice > curr.ClosePrice
				&& curr.ClosePrice > prev.OpenPrice
				&& curr.OpenPrice < prev.ClosePrice;

			if (bullishHarami && kValue < Oversold && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (bearishHarami && kValue > Overbought && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		if (_hasPrevK)
		{
			if (Position > 0 && _prevK >= Overbought && kValue < Overbought && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
			else if (Position < 0 && _prevK <= Oversold && kValue > Oversold && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevK = kValue;
		_hasPrevK = true;
	}
}