Fractal MFI Strategy
This strategy is a translation of the Exp_Fractal_MFI.mq5 expert advisor. It uses the Money Flow Index (MFI) indicator to generate trading signals when the oscillator crosses predefined upper and lower levels.
How It Works
- Calculates MFI over a configurable period.
- When the previous MFI value was above the Low Level and the current value falls below it, a signal is generated.
- In Direct mode this opens a long position and optionally closes shorts.
- In Against mode this opens a short position and optionally closes longs.
- When the previous MFI value was below the High Level and the current value rises above it, another signal is generated.
- In Direct mode this opens a short position and optionally closes longs.
- In Against mode this opens a long position and optionally closes shorts.
Only completed candles are processed. The strategy can be configured to enable or disable opening and closing of long or short positions separately.
Parameters
MfiPeriod– period of the Money Flow Index calculation.HighLevel– upper threshold for the MFI.LowLevel– lower threshold for the MFI.CandleType– candle timeframe used in calculations.Trend– chooseDirectto trade with the indicator direction orAgainstto invert signals.BuyPosOpen/SellPosOpen– allow opening long or short positions.BuyPosClose/SellPosClose– allow closing existing positions on opposite signals.
Notes
This C# version focuses on high level API usage and does not implement the original money management rules or stop levels from the MQL code.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Money Flow Index crossover strategy using upper and lower thresholds.
/// Opens or closes positions depending on MFI crossing defined levels.
/// The trading direction can follow the trend or work against it.
/// </summary>
public class FractalMfiStrategy : Strategy
{
private readonly StrategyParam<int> _mfiPeriod;
private readonly StrategyParam<decimal> _highLevel;
private readonly StrategyParam<decimal> _lowLevel;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<TrendModes> _trend;
private readonly StrategyParam<bool> _buyPosOpen;
private readonly StrategyParam<bool> _sellPosOpen;
private readonly StrategyParam<bool> _buyPosClose;
private readonly StrategyParam<bool> _sellPosClose;
private decimal _prevMfi;
private bool _isPrevSet;
/// <summary>
/// MFI calculation period.
/// </summary>
public int MfiPeriod { get => _mfiPeriod.Value; set => _mfiPeriod.Value = value; }
/// <summary>
/// Upper threshold for MFI.
/// </summary>
public decimal HighLevel { get => _highLevel.Value; set => _highLevel.Value = value; }
/// <summary>
/// Lower threshold for MFI.
/// </summary>
public decimal LowLevel { get => _lowLevel.Value; set => _lowLevel.Value = value; }
/// <summary>
/// Type of candles used by the strategy.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Trading direction mode.
/// </summary>
public TrendModes Trend { get => _trend.Value; set => _trend.Value = value; }
/// <summary>
/// Allow opening long positions.
/// </summary>
public bool BuyPosOpen { get => _buyPosOpen.Value; set => _buyPosOpen.Value = value; }
/// <summary>
/// Allow opening short positions.
/// </summary>
public bool SellPosOpen { get => _sellPosOpen.Value; set => _sellPosOpen.Value = value; }
/// <summary>
/// Allow closing long positions on signals.
/// </summary>
public bool BuyPosClose { get => _buyPosClose.Value; set => _buyPosClose.Value = value; }
/// <summary>
/// Allow closing short positions on signals.
/// </summary>
public bool SellPosClose { get => _sellPosClose.Value; set => _sellPosClose.Value = value; }
/// <summary>
/// Constructor.
/// </summary>
public FractalMfiStrategy()
{
_mfiPeriod = Param(nameof(MfiPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("MFI Period", "Length of MFI indicator", "Indicator")
;
_highLevel = Param<decimal>(nameof(HighLevel), 70m)
.SetDisplay("High Level", "Upper MFI threshold", "Levels")
;
_lowLevel = Param<decimal>(nameof(LowLevel), 30m)
.SetDisplay("Low Level", "Lower MFI threshold", "Levels")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
_trend = Param(nameof(Trend), TrendModes.Direct)
.SetDisplay("Trend Mode", "Follow or trade against the trend", "General")
;
_buyPosOpen = Param(nameof(BuyPosOpen), true)
.SetDisplay("Buy Open", "Enable long entries", "Signals");
_sellPosOpen = Param(nameof(SellPosOpen), true)
.SetDisplay("Sell Open", "Enable short entries", "Signals");
_buyPosClose = Param(nameof(BuyPosClose), true)
.SetDisplay("Buy Close", "Enable closing longs", "Signals");
_sellPosClose = Param(nameof(SellPosClose), true)
.SetDisplay("Sell Close", "Enable closing shorts", "Signals");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevMfi = 0m;
_isPrevSet = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var mfi = new MoneyFlowIndex { Length = MfiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(mfi, (candle, currentMfi) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!mfi.IsFormed)
{
_prevMfi = currentMfi;
return;
}
if (!_isPrevSet)
{
_prevMfi = currentMfi;
_isPrevSet = true;
return;
}
ProcessSignal(candle.ClosePrice, _prevMfi, currentMfi);
_prevMfi = currentMfi;
})
.Start();
StartProtection(null, null);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, mfi);
DrawOwnTrades(area);
}
}
private void ProcessSignal(decimal price, decimal prev, decimal current)
{
if (Trend == TrendModes.Direct)
{
if (prev > LowLevel && current <= LowLevel)
{
if (SellPosClose && Position < 0)
BuyMarket(Math.Abs(Position));
if (BuyPosOpen && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
}
if (prev < HighLevel && current >= HighLevel)
{
if (BuyPosClose && Position > 0)
SellMarket(Math.Abs(Position));
if (SellPosOpen && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
}
else
{
if (prev > LowLevel && current <= LowLevel)
{
if (BuyPosClose && Position > 0)
SellMarket(Math.Abs(Position));
if (SellPosOpen && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
if (prev < HighLevel && current >= HighLevel)
{
if (SellPosClose && Position < 0)
BuyMarket(Math.Abs(Position));
if (BuyPosOpen && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
}
}
}
/// <summary>
/// Trend mode enumeration.
/// </summary>
public enum TrendModes
{
/// <summary>
/// Trade in direction of indicator.
/// </summary>
Direct,
/// <summary>
/// Trade against indicator direction.
/// </summary>
Against
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import MoneyFlowIndex
from StockSharp.Algo.Strategies import Strategy
DIRECT = 0
AGAINST = 1
class fractal_mfi_strategy(Strategy):
def __init__(self):
super(fractal_mfi_strategy, self).__init__()
self._mfi_period = self.Param("MfiPeriod", 30)
self._high_level = self.Param("HighLevel", 70.0)
self._low_level = self.Param("LowLevel", 30.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._trend = self.Param("Trend", DIRECT)
self._buy_pos_open = self.Param("BuyPosOpen", True)
self._sell_pos_open = self.Param("SellPosOpen", True)
self._buy_pos_close = self.Param("BuyPosClose", True)
self._sell_pos_close = self.Param("SellPosClose", True)
self._prev_mfi = 0.0
self._is_prev_set = False
@property
def MfiPeriod(self):
return self._mfi_period.Value
@MfiPeriod.setter
def MfiPeriod(self, value):
self._mfi_period.Value = value
@property
def HighLevel(self):
return self._high_level.Value
@HighLevel.setter
def HighLevel(self, value):
self._high_level.Value = value
@property
def LowLevel(self):
return self._low_level.Value
@LowLevel.setter
def LowLevel(self, value):
self._low_level.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def Trend(self):
return self._trend.Value
@Trend.setter
def Trend(self, value):
self._trend.Value = value
@property
def BuyPosOpen(self):
return self._buy_pos_open.Value
@BuyPosOpen.setter
def BuyPosOpen(self, value):
self._buy_pos_open.Value = value
@property
def SellPosOpen(self):
return self._sell_pos_open.Value
@SellPosOpen.setter
def SellPosOpen(self, value):
self._sell_pos_open.Value = value
@property
def BuyPosClose(self):
return self._buy_pos_close.Value
@BuyPosClose.setter
def BuyPosClose(self, value):
self._buy_pos_close.Value = value
@property
def SellPosClose(self):
return self._sell_pos_close.Value
@SellPosClose.setter
def SellPosClose(self, value):
self._sell_pos_close.Value = value
def OnStarted2(self, time):
super(fractal_mfi_strategy, self).OnStarted2(time)
self._prev_mfi = 0.0
self._is_prev_set = False
mfi = MoneyFlowIndex()
mfi.Length = self.MfiPeriod
self._mfi = mfi
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(mfi, self.ProcessCandle).Start()
self.StartProtection(None, None)
def ProcessCandle(self, candle, current_mfi):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
mfi_val = float(current_mfi)
if not self._mfi.IsFormed:
self._prev_mfi = mfi_val
return
if not self._is_prev_set:
self._prev_mfi = mfi_val
self._is_prev_set = True
return
self._process_signal(float(candle.ClosePrice), self._prev_mfi, mfi_val)
self._prev_mfi = mfi_val
def _process_signal(self, price, prev, current):
high = float(self.HighLevel)
low = float(self.LowLevel)
vol = float(self.Volume)
if int(self.Trend) == DIRECT:
if prev > low and current <= low:
pos = float(self.Position)
if self.SellPosClose and pos < 0:
self.BuyMarket(abs(pos))
pos = float(self.Position)
if self.BuyPosOpen and pos <= 0:
self.BuyMarket(vol + abs(pos))
if prev < high and current >= high:
pos = float(self.Position)
if self.BuyPosClose and pos > 0:
self.SellMarket(abs(pos))
pos = float(self.Position)
if self.SellPosOpen and pos >= 0:
self.SellMarket(vol + abs(pos))
else:
if prev > low and current <= low:
pos = float(self.Position)
if self.BuyPosClose and pos > 0:
self.SellMarket(abs(pos))
pos = float(self.Position)
if self.SellPosOpen and pos >= 0:
self.SellMarket(vol + abs(pos))
if prev < high and current >= high:
pos = float(self.Position)
if self.SellPosClose and pos < 0:
self.BuyMarket(abs(pos))
pos = float(self.Position)
if self.BuyPosOpen and pos <= 0:
self.BuyMarket(vol + abs(pos))
def OnReseted(self):
super(fractal_mfi_strategy, self).OnReseted()
self._prev_mfi = 0.0
self._is_prev_set = False
def CreateClone(self):
return fractal_mfi_strategy()