The ABH_BH_MFI Strategy is a StockSharp high-level port of the MetaTrader expert advisor "Expert_ABH_BH_MFI". The algorithm combines bullish and bearish Harami candlestick patterns with confirmation from the Money Flow Index (MFI). Long trades are triggered when a bullish Harami forms inside a falling market while the MFI remains depressed. Short trades require a bearish Harami inside a rising market and an elevated MFI. The original MQL implementation relied on MetaTrader's signal infrastructure; this conversion keeps the decision logic but expresses it with StockSharp's candle subscriptions, indicator binding, and position management helpers.
Trading Logic
1. Harami pattern detection
The strategy stores the two most recent completed candles.
A bullish Harami requires:
Two candles ago was a long black (bearish) candle whose body is larger than the average body length.
The most recent candle is bullish and its open/close are engulfed by the body of the previous bearish candle.
The midpoint of the older candle lies below the simple moving average of closes, signalling a prevailing downtrend.
A bearish Harami mirrors these requirements with inverted colours and the midpoint above the moving average to confirm an uptrend.
2. Money Flow Index confirmation
The MFI uses the configurable MfiPeriod (default 37) to replicate the original oscillator settings.
Long entries demand that the latest completed MFI value stays below BullishThreshold (default 40) to ensure capital inflow exhaustion.
Short entries require the MFI to remain above BearishThreshold (default 60) to show buying pressure exhaustion.
3. Exit rules through MFI crossovers
Active long positions are closed when the MFI crosses above either ExitLowerLevel (default 30) or ExitUpperLevel (default 70), matching the MetaTrader conditions MFI(1) > level && MFI(2) < level.
Active short positions are closed when the MFI crosses down from the overbought zone or spikes below the oversold level, mirroring the original short exit clauses.
4. Risk management
The strategy optionally applies StartProtection with stop-loss and take-profit offsets expressed in price steps. Setting the corresponding parameter to zero disables the protective distance, reproducing the MetaTrader defaults.
Position sizing uses the base Volume property; reversing positions automatically adds enough contracts to flatten and reopen in the new direction, just like the source expert.
Parameters
Name
Default
Description
CandleType
1-hour time frame
Primary candle series analysed for patterns and MFI.
MfiPeriod
37
Lookback for the Money Flow Index indicator.
BodyAveragePeriod
11
Length of the simple moving averages that measure body size and closing trend.
BullishThreshold
40
Maximum MFI value allowed before opening long trades.
BearishThreshold
60
Minimum MFI value required before opening short trades.
ExitLowerLevel
30
Lower MFI crossover level for position exits.
ExitUpperLevel
70
Upper MFI crossover level for position exits.
StopLossPoints
0
Optional stop-loss distance in price steps (0 disables).
TakeProfitPoints
0
Optional take-profit distance in price steps (0 disables).
Implementation Notes
Candle data are received via SubscribeCandles(CandleType) and processed only when the candle state is Finished, ensuring alignment with closed-bar logic of the MQL expert.
The MFI indicator is bound directly with .Bind(_mfi, ProcessCandle) so that the handler receives ready-to-use decimal values without calling GetValue.
Two auxiliary simple moving averages replicate the AvgBody and CloseAvg helper functions from the MetaTrader code. Their results are cached to avoid historical indicator queries.
Exit and entry decisions call IsFormedAndOnlineAndAllowTrading() before sending orders, staying consistent with StockSharp's recommended trading safety checks.
Differences from the MetaTrader Expert
Money management is simplified to the base strategy volume. The original "fixed lot" module translated to StockSharp's position sizing helper, which covers the same functionality without separate classes.
The MetaTrader trailing stop component (TrailingNone) had no logic; the StockSharp version therefore omits any trailing actions but keeps optional fixed risk targets.
Logging is minimal by default; you may extend it with LogInfo calls if you need verbose trade diagnostics.
Usage Tips
Configure the desired security and assign the CandleType before starting the strategy.
Optionally adjust the MFI and exit thresholds to suit different volatility regimes.
Provide non-zero StopLossPoints/TakeProfitPoints when the broker requires explicit protective orders; otherwise leave them at zero to trade without hard targets.
Monitor the chart panes created by the strategy to visualise candles, the MFI indicator, and executed trades.
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>
/// ABH BH MFI strategy: Harami pattern with MFI confirmation.
/// </summary>
public class AbhBhMfiStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _mfiPeriod;
private readonly StrategyParam<decimal> _oversold;
private readonly StrategyParam<decimal> _overbought;
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 MfiPeriod { get => _mfiPeriod.Value; set => _mfiPeriod.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 AbhBhMfiStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_mfiPeriod = Param(nameof(MfiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("MFI Period", "Money Flow Index period", "Indicators");
_oversold = Param(nameof(Oversold), 40m)
.SetDisplay("Oversold", "MFI oversold level", "Signals");
_overbought = Param(nameof(Overbought), 60m)
.SetDisplay("Overbought", "MFI 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();
_candlesSinceTrade = SignalCooldownCandles;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_candles.Clear();
_candlesSinceTrade = SignalCooldownCandles;
var mfi = new MoneyFlowIndex { Length = MfiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(mfi, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal mfiValue)
{
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 && mfiValue < Oversold && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
else if (bearishHarami && mfiValue > Overbought && 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 MoneyFlowIndex
from StockSharp.Algo.Strategies import Strategy
class abh_bh_mfi_strategy(Strategy):
def __init__(self):
super(abh_bh_mfi_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._mfi_period = self.Param("MfiPeriod", 14)
self._oversold = self.Param("Oversold", 40.0)
self._overbought = self.Param("Overbought", 60.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 MfiPeriod(self):
return self._mfi_period.Value
@MfiPeriod.setter
def MfiPeriod(self, value):
self._mfi_period.Value = value
@property
def Oversold(self):
return self._oversold.Value
@Oversold.setter
def Oversold(self, value):
self._oversold.Value = value
@property
def Overbought(self):
return self._overbought.Value
@Overbought.setter
def Overbought(self, value):
self._overbought.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(abh_bh_mfi_strategy, self).OnReseted()
self._candles.clear()
self._candles_since_trade = self.SignalCooldownCandles
def OnStarted2(self, time):
super(abh_bh_mfi_strategy, self).OnStarted2(time)
self._candles.clear()
self._candles_since_trade = self.SignalCooldownCandles
mfi = MoneyFlowIndex()
mfi.Length = self.MfiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(mfi, self._process_candle).Start()
def _process_candle(self, candle, mfi_value):
if candle.State != CandleStates.Finished:
return
if self._candles_since_trade < self.SignalCooldownCandles:
self._candles_since_trade += 1
mfi_val = float(mfi_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 mfi_val < self.Oversold and self.Position <= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
elif bearish_harami and mfi_val > self.Overbought and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
def CreateClone(self):
return abh_bh_mfi_strategy()