MFI Histogram Strategy
The MFI Histogram Strategy uses the Money Flow Index (MFI) to detect overbought and oversold conditions via configurable thresholds. The MFI combines price and volume to measure the intensity of capital inflow and outflow. When the indicator crosses above the high level from below, the strategy interprets this as rising buying pressure and enters a long position while closing any existing short. Conversely, a cross below the low level triggers a short entry and closes existing longs. Stop-loss and take-profit values are managed in ticks through the built-in protection mechanism.
The strategy operates on a user-defined candle timeframe (4 hours by default) and relies on a single indicator without additional filters. Parameters allow optimization of the MFI period, threshold levels, and risk limits, making the system adaptable to various markets and volatility regimes.
Details
- Entry Criteria:
- Long:
MFIcrosses aboveHighLevelfrom below. - Short:
MFIcrosses belowLowLevelfrom above.
- Long:
- Long/Short: Both.
- Exit Criteria:
- Opposite signal generates a reversal.
- Stop-loss or take-profit is reached.
- Stops:
StopLossandTakeProfitin ticks. - Default Values:
MFI Period= 14HighLevel= 60LowLevel= 40Candle Type= 4-hourStopLoss= 1000 ticksTakeProfit= 2000 ticks
- Filters:
- Category: Oscillator
- Direction: Both
- Indicators: Single
- Stops: Yes
- Complexity: Basic
- Timeframe: Mid-term
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// MFI level-crossing reversal with stop loss/take profit distances in instrument price steps.
/// A crossover signal is acted on only after a completed, formed indicator bar.
/// </summary>
public class MfiHistogramStrategy : Strategy
{
private readonly StrategyParam<int> _mfiPeriod;
private readonly StrategyParam<decimal> _highLevel;
private readonly StrategyParam<decimal> _lowLevel;
private readonly StrategyParam<Unit> _stopLoss;
private readonly StrategyParam<Unit> _takeProfit;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevMfi;
private Order _pendingEntry;
public int MfiPeriod { get => _mfiPeriod.Value; set => _mfiPeriod.Value = value; }
public decimal HighLevel { get => _highLevel.Value; set => _highLevel.Value = value; }
public decimal LowLevel { get => _lowLevel.Value; set => _lowLevel.Value = value; }
/// <summary>Number of price steps to the protective stop.</summary>
public Unit StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
/// <summary>Number of price steps to the profit target.</summary>
public Unit TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public MfiHistogramStrategy()
{
_mfiPeriod = Param(nameof(MfiPeriod), 14).SetGreaterThanZero()
.SetDisplay("MFI Period", "Period for Money Flow Index", "MFI");
_highLevel = Param(nameof(HighLevel), 60m)
.SetDisplay("High Level", "Upper MFI crossing threshold", "MFI");
_lowLevel = Param(nameof(LowLevel), 40m)
.SetDisplay("Low Level", "Lower MFI crossing threshold", "MFI");
// Preserve the existing public Unit parameter API; its numeric value denotes PRICE STEPS.
_stopLoss = Param(nameof(StopLoss), new Unit(1000m, UnitTypes.Absolute))
.SetDisplay("Stop Loss", "Distance measured in instrument price steps", "Risk");
_takeProfit = Param(nameof(TakeProfit), new Unit(2000m, UnitTypes.Absolute))
.SetDisplay("Take Profit", "Distance measured in instrument price steps", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe of candles", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_prevMfi = null;
_pendingEntry = null;
}
private Unit ConvertTicksToPrice(Unit ticks, decimal step, string name)
{
if (ticks is null || ticks.Type != UnitTypes.Absolute || ticks.Value <= 0)
throw new InvalidOperationException($"{name} must be a positive number of price steps (absolute numeric unit).");
return new Unit(ticks.Value * step, UnitTypes.Absolute);
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevMfi = null;
_pendingEntry = null;
var step = Security?.PriceStep;
if (step is null || step <= 0m)
throw new InvalidOperationException("MFI Histogram requires a positive Security.PriceStep for tick-based protection.");
StartProtection(
takeProfit: ConvertTicksToPrice(TakeProfit, step.Value, nameof(TakeProfit)),
stopLoss: ConvertTicksToPrice(StopLoss, step.Value, nameof(StopLoss)),
useMarketOrders: true, isLocalStop: true);
var mfi = new MoneyFlowIndex { Length = MfiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(mfi, ProcessCandle).Start();
var area = CreateChartArea();
if (area is not null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var indicatorArea = CreateChartArea();
if (indicatorArea is not null)
DrawIndicator(indicatorArea, mfi);
}
}
private void ProcessCandle(ICandleMessage candle, decimal mfiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevMfi is decimal previous && IsFormedAndOnlineAndAllowTrading()
&& (_pendingEntry is null || _pendingEntry.State is OrderStates.Done or OrderStates.Failed))
{
if (previous <= HighLevel && mfiValue > HighLevel && Position <= 0m)
_pendingEntry = BuyMarket(Volume + Math.Abs(Position));
else if (previous >= LowLevel && mfiValue < LowLevel && Position >= 0m)
_pendingEntry = SellMarket(Volume + Math.Abs(Position));
}
// Always advance the baseline, even while trading is disabled or an order is pending.
_prevMfi = mfiValue;
}
}
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, Unit, UnitTypes, OrderStates
from StockSharp.Algo.Indicators import MoneyFlowIndex
from StockSharp.Algo.Strategies import Strategy
class mfi_histogram_strategy(Strategy):
"""MFI threshold crossings; risk distances are instrument price steps, not raw prices."""
def __init__(self):
super(mfi_histogram_strategy, self).__init__()
self._mfi_period = self.Param("MfiPeriod", 14).SetDisplay("MFI Period", "Period for Money Flow Index", "MFI")
self._high_level = self.Param("HighLevel", 60.0).SetDisplay("High Level", "Upper MFI crossing threshold", "MFI")
self._low_level = self.Param("LowLevel", 40.0).SetDisplay("Low Level", "Lower MFI crossing threshold", "MFI")
# As in C#, keep Unit parameters. The numeric value is the count of price steps.
self._stop_loss = self.Param("StopLoss", Unit(1000, UnitTypes.Absolute)).SetDisplay("Stop Loss", "Distance in instrument price steps", "Risk")
self._take_profit = self.Param("TakeProfit", Unit(2000, UnitTypes.Absolute)).SetDisplay("Take Profit", "Distance in instrument price steps", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Timeframe of candles", "General")
self._prev_mfi = None
self._pending_entry = None
@property
def mfi_period(self): return self._mfi_period.Value
@property
def high_level(self): return self._high_level.Value
@property
def low_level(self): return self._low_level.Value
@property
def stop_loss(self): return self._stop_loss.Value
@property
def take_profit(self): return self._take_profit.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(mfi_histogram_strategy, self).OnReseted()
self._prev_mfi = None
self._pending_entry = None
def _absolute_price_distance(self, count, step, name):
if count is None or count.Type != UnitTypes.Absolute or float(count.Value) <= 0:
raise ValueError(name + " must be a positive number of price steps")
return Unit(float(count.Value) * float(step), UnitTypes.Absolute)
def OnStarted2(self, time):
super(mfi_histogram_strategy, self).OnStarted2(time)
self._prev_mfi = None
self._pending_entry = None
step = self.Security.PriceStep if self.Security is not None else None
if step is None or float(step) <= 0:
raise ValueError("MFI Histogram requires a positive Security.PriceStep")
self.StartProtection(
takeProfit=self._absolute_price_distance(self.take_profit, step, "TakeProfit"),
stopLoss=self._absolute_price_distance(self.stop_loss, step, "StopLoss"),
useMarketOrders=True, isLocalStop=True)
mfi = MoneyFlowIndex()
mfi.Length = self.mfi_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(mfi, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
indicator_area = self.CreateChartArea()
if indicator_area is not None:
self.DrawIndicator(indicator_area, mfi)
def process_candle(self, candle, mfi_value):
if candle.State != CandleStates.Finished:
return
mfi = float(mfi_value)
previous = self._prev_mfi
self._prev_mfi = mfi
if previous is None or not self.IsFormedAndOnlineAndAllowTrading():
return
if self._pending_entry is not None and self._pending_entry.State not in (OrderStates.Done, OrderStates.Failed):
return
if previous <= float(self.high_level) and mfi > float(self.high_level) and self.Position <= 0:
self._pending_entry = self.BuyMarket(self.Volume + abs(self.Position))
elif previous >= float(self.low_level) and mfi < float(self.low_level) and self.Position >= 0:
self._pending_entry = self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return mfi_histogram_strategy()