This strategy replicates the MetaTrader expert "Expert_ABE_BE_MFI" by combining Japanese candlestick engulfing patterns with confirmation from the Money Flow Index (MFI) oscillator. A long position is opened when a bullish engulfing candle appears while money flow stays in an oversold zone. A short position is opened when a bearish engulfing candle forms under overbought money-flow conditions. Positions are closed when MFI crosses dynamic exit thresholds, signalling momentum reversals.
Core Idea
Pattern detection – the body of the current finished candle must fully engulf the previous candle in the direction of the trade.
Volume confirmation – the MFI indicator (length configurable, default 37) must be below the oversold level (40) for long entries or above the overbought level (60) for short entries.
Momentum exits – open positions are closed when MFI crosses key reversal levels (30 and 70) in the opposite direction, mimicking the original voting logic of the MQL expert.
Indicators
Money Flow Index (MFI) – calculates volume-adjusted momentum. The strategy stores the last two MFI readings to detect level crossings.
Candlestick Body Analysis – no additional indicator is registered; engulfing detection uses the latest two completed candles.
Trading Rules
Long Entry
Previous candle is bearish and current candle is bullish.
Current candle body opens below or equal to the previous close and closes above or equal to the previous open (strict engulfing).
Latest MFI value is below the configurable OversoldLevel (default 40).
Short Entry
Previous candle is bullish and current candle is bearish.
Current candle body opens above or equal to the previous close and closes below or equal to the previous open.
Latest MFI value is above the configurable OverboughtLevel (default 60).
Exit Conditions
Close Short when MFI crosses above ExitLongLevel (30) or ExitShortLevel (70) from below.
Close Long when MFI crosses below ExitShortLevel (70) or ExitLongLevel (30) from above.
These exit thresholds recreate the double voting logic of the original expert, ensuring that extended moves in money flow trigger timely liquidation of positions.
Trade Management
Market orders (BuyMarket / SellMarket) are used for entries and exits.
No explicit stop-loss or take-profit is used; risk management relies on the MFI reversal signals.
Parameters
Name
Description
Default
Range / Notes
CandleType
Candle timeframe used for analysis.
1 minute
Any supported candle type.
MfiPeriod
Length of the Money Flow Index.
37
Must be > 0; matches original EA default.
OversoldLevel
MFI level that confirms bullish engulfing setups.
40
Enable optimization if needed.
OverboughtLevel
MFI level that confirms bearish engulfing setups.
60
Enable optimization if needed.
ExitLongLevel
Lower MFI boundary for detecting reversals.
30
Used for both long exits and short confirmations.
ExitShortLevel
Upper MFI boundary for detecting reversals.
70
Used for both short exits and long confirmations.
Notes on Conversion
The original MQL expert aggregated “votes” from engulfing patterns and MFI filters. The C# strategy reproduces the same decision flow by directly converting the voting rules into discrete entry and exit conditions.
Money management and trailing modules from the MQL version are omitted; StockSharp position sizing is controlled by the strategy volume.
All indicator bindings leverage the high-level API (SubscribeCandles().Bind(...)) as required.
Usage Tips
Optimise MfiPeriod, OversoldLevel, and OverboughtLevel to adapt the strategy to specific markets.
Combine with risk controls (protective stops) via StartProtection in the host application if additional safety is required.
Ensure sufficient historical data so that the Money Flow Index is fully formed before enabling trading.
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>
/// Engulfing MFI Confirmation strategy: Engulfing pattern with MFI filter.
/// Bullish engulfing + oversold MFI for long, bearish engulfing + overbought MFI for short.
/// </summary>
public class EngulfingMfiConfirmationStrategy : 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 decimal _prevMfi;
private bool _hasPrevMfi;
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 EngulfingMfiConfirmationStrategy()
{
_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), 30m)
.SetDisplay("Oversold", "MFI oversold level", "Signals");
_overbought = Param(nameof(Overbought), 70m)
.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();
_prevMfi = 0m;
_hasPrevMfi = false;
_candlesSinceTrade = SignalCooldownCandles;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_candles.Clear();
_hasPrevMfi = false;
_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];
if (curr is null || prev is null)
return;
var bullishEngulfing = prev.OpenPrice > prev.ClosePrice
&& curr.ClosePrice > curr.OpenPrice
&& curr.OpenPrice <= prev.ClosePrice
&& curr.ClosePrice >= prev.OpenPrice;
var bearishEngulfing = prev.ClosePrice > prev.OpenPrice
&& curr.OpenPrice > curr.ClosePrice
&& curr.OpenPrice >= prev.ClosePrice
&& curr.ClosePrice <= prev.OpenPrice;
if (bullishEngulfing && mfiValue < Oversold && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
else if (bearishEngulfing && mfiValue > Overbought && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
{
SellMarket();
_candlesSinceTrade = 0;
}
}
// Exit on MFI crossing
if (_hasPrevMfi)
{
if (Position > 0 && _prevMfi >= Overbought && mfiValue < Overbought && _candlesSinceTrade >= SignalCooldownCandles)
{
SellMarket();
_candlesSinceTrade = 0;
}
else if (Position < 0 && _prevMfi <= Oversold && mfiValue > Oversold && _candlesSinceTrade >= SignalCooldownCandles)
{
BuyMarket();
_candlesSinceTrade = 0;
}
}
_prevMfi = mfiValue;
_hasPrevMfi = true;
}
}
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 engulfing_mfi_confirmation_strategy(Strategy):
def __init__(self):
super(engulfing_mfi_confirmation_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", 30.0)
self._overbought = self.Param("Overbought", 70.0)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 6)
self._candles = []
self._prev_mfi = 0.0
self._has_prev_mfi = False
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(engulfing_mfi_confirmation_strategy, self).OnReseted()
self._candles.clear()
self._prev_mfi = 0.0
self._has_prev_mfi = False
self._candles_since_trade = self.SignalCooldownCandles
def OnStarted2(self, time):
super(engulfing_mfi_confirmation_strategy, self).OnStarted2(time)
self._candles.clear()
self._has_prev_mfi = False
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]
if curr is None or prev is None:
return
bullish_engulfing = (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_engulfing = (float(prev.ClosePrice) > float(prev.OpenPrice)
and float(curr.OpenPrice) > float(curr.ClosePrice)
and float(curr.OpenPrice) >= float(prev.ClosePrice)
and float(curr.ClosePrice) <= float(prev.OpenPrice))
if bullish_engulfing 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_engulfing and mfi_val > self.Overbought and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
if self._has_prev_mfi:
if self.Position > 0 and self._prev_mfi >= self.Overbought and mfi_val < self.Overbought and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
elif self.Position < 0 and self._prev_mfi <= self.Oversold and mfi_val > self.Oversold and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
self._prev_mfi = mfi_val
self._has_prev_mfi = True
def CreateClone(self):
return engulfing_mfi_confirmation_strategy()