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.
Protection – StartProtection() 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;
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
class harami_cci_confirmation_strategy(Strategy):
def __init__(self):
super(harami_cci_confirmation_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._cci_period = self.Param("CciPeriod", 14)
self._entry_level = self.Param("EntryLevel", 0.0)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 6)
self._candles = []
self._candles_since_trade = 6
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def CciPeriod(self):
return self._cci_period.Value
@CciPeriod.setter
def CciPeriod(self, value):
self._cci_period.Value = value
@property
def EntryLevel(self):
return self._entry_level.Value
@EntryLevel.setter
def EntryLevel(self, value):
self._entry_level.Value = value
@property
def SignalCooldownCandles(self):
return self._signal_cooldown_candles.Value
@SignalCooldownCandles.setter
def SignalCooldownCandles(self, value):
self._signal_cooldown_candles.Value = value
def OnReseted(self):
super(harami_cci_confirmation_strategy, self).OnReseted()
self._candles.clear()
self._candles_since_trade = self.SignalCooldownCandles
def OnStarted2(self, time):
super(harami_cci_confirmation_strategy, self).OnStarted2(time)
self._candles.clear()
self._candles_since_trade = self.SignalCooldownCandles
cci = CommodityChannelIndex()
cci.Length = self.CciPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(cci, self._process_candle).Start()
def _process_candle(self, candle, cci_value):
if candle.State != CandleStates.Finished:
return
if self._candles_since_trade < self.SignalCooldownCandles:
self._candles_since_trade += 1
cci_val = float(cci_value)
self._candles.append(candle)
if len(self._candles) > 5:
self._candles.pop(0)
if len(self._candles) >= 2:
curr = self._candles[-1]
prev = self._candles[-2]
bullish_harami = (float(prev.OpenPrice) > float(prev.ClosePrice)
and float(curr.ClosePrice) > float(curr.OpenPrice)
and float(curr.OpenPrice) > float(prev.ClosePrice)
and float(curr.ClosePrice) < float(prev.OpenPrice))
bearish_harami = (float(prev.ClosePrice) > float(prev.OpenPrice)
and float(curr.OpenPrice) > float(curr.ClosePrice)
and float(curr.ClosePrice) > float(prev.OpenPrice)
and float(curr.OpenPrice) < float(prev.ClosePrice))
if bullish_harami and cci_val < -self.EntryLevel and self.Position <= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
elif bearish_harami and cci_val > self.EntryLevel and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
def CreateClone(self):
return harami_cci_confirmation_strategy()