View on GitHub

Harami CCI Confirmation

Overview

Harami CCI Confirmation is a high-level StockSharp port of the MetaTrader 5 expert advisor Expert_ABH_BH_CCI. The original EA trades the two-candle Bullish Harami and Bearish Harami reversal patterns. Before entering a trade, it demands confirmation from a Commodity Channel Index (CCI) oscillator and measures candle body size against a moving average to ensure that the larger candle truly dominates the range. The StockSharp conversion keeps the same confirmation logic, processes only completed candles, and uses the platform's built-in protection module for order safety.

Strategy logic

Pattern detection

  • Average body calculation – maintains a moving average of absolute candle bodies over the last N bars (default 5). This mirrors the MetaTrader helper class that smooths the candle size and trend reference.
  • Bullish Harami – requires the previous candle to be bullish, the prior candle to be bearish with a body longer than the average, and the bullish body to remain inside the bearish range. The midpoint of the earlier candle must also sit below the moving average of closes, confirming a downtrend.
  • Bearish Harami – mirrored conditions: the previous candle must be bearish, the earlier candle bullish and long, the bearish body must be contained inside the bullish range, and the midpoint needs to be above the close moving average to confirm an uptrend.

CCI confirmation

  • Entry filter – the strategy checks the CCI value from the most recently completed candle (shift 1). Long trades require the CCI to be below -EntryThreshold (default 50), while short trades demand a value above +EntryThreshold.
  • Exit band – the CCI history is monitored for crossings of ±ExitBand (default 80). When the indicator rises through -ExitBand, any open short position is closed. When it drops below +ExitBand, existing long exposure is closed. This reproduces the "votes" used by the MetaTrader expert to flatten positions.

Trade management

  • Reversals – if the opposite Harami setup is confirmed while the strategy already holds a position, it will trade enough volume to both close the existing exposure and open the new direction.
  • ProtectionStartProtection() is activated so that users can attach stop-loss or take-profit settings through the StockSharp UI if desired. No fixed stops are enforced by default to stay aligned with the source EA, which relied on manual money management settings.

Parameters

  • Order Volume – base volume sent with every market entry. Extra volume is automatically added to close the opposite position when a reversal occurs.
  • CCI Period – length of the Commodity Channel Index oscillator.
  • Body Average – number of historical candles used when averaging body sizes and close prices.
  • CCI Entry – minimum absolute CCI value needed to accept a Harami signal.
  • CCI Exit Band – band magnitude that defines the CCI crossover exit rules.
  • Candle Type – timeframe used for candles (default: 1-hour time frame).

Additional notes

  • All calculations run on completed candles supplied by SubscribeCandles. Intrabar signals are intentionally ignored to match the MetaTrader execution model.
  • The strategy keeps a short sliding history of candles and CCI values to evaluate the Harami rules without recreating full indicator buffers.
  • Only the C# implementation is provided in this folder; there is no Python version for this conversion.
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 CCI Confirmation strategy: Harami pattern with CCI confirmation.
/// </summary>
public class HaramiCciConfirmationStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<decimal> _entryLevel;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private readonly List<ICandleMessage> _candles = new();
	private int _candlesSinceTrade;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int CciPeriod { get => _cciPeriod.Value; set => _cciPeriod.Value = value; }
	public decimal EntryLevel { get => _entryLevel.Value; set => _entryLevel.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public HaramiCciConfirmationStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_cciPeriod = Param(nameof(CciPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("CCI Period", "CCI period", "Indicators");
		_entryLevel = Param(nameof(EntryLevel), 0m)
			.SetDisplay("Entry Level", "CCI threshold", "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();
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_candles.Clear();
		_candlesSinceTrade = SignalCooldownCandles;
		var cci = new CommodityChannelIndex { Length = CciPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(cci, ProcessCandle).Start();
	}

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

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

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

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

			var bullishHarami = prev.OpenPrice > prev.ClosePrice
				&& curr.ClosePrice > curr.OpenPrice
				&& curr.OpenPrice > prev.ClosePrice
				&& curr.ClosePrice < prev.OpenPrice;

			var bearishHarami = prev.ClosePrice > prev.OpenPrice
				&& curr.OpenPrice > curr.ClosePrice
				&& curr.ClosePrice > prev.OpenPrice
				&& curr.OpenPrice < prev.ClosePrice;

			if (bullishHarami && cciValue < -EntryLevel && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (bearishHarami && cciValue > EntryLevel && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}
	}
}