View on GitHub

AH HM MFI Strategy

Summary

The AH HM MFI strategy trades hammer and hanging man candlestick patterns that are confirmed by the Money Flow Index (MFI). When a bullish hammer appears in a short-term downtrend and the MFI stays below an oversold threshold, the strategy opens a long position. When a bearish hanging man forms in an uptrend while the MFI is above an overbought threshold, it opens a short position. Protective exits are triggered when the MFI crosses predefined upper or lower boundaries.

Core Logic

  1. Subscribe to the configured time-frame candles and calculate two indicators:
    • Money Flow Index with a configurable period (default: 47).
    • Simple Moving Average of closing prices to approximate the trend filter from the original MQL strategy (default length: 5).
  2. Detect hammer and hanging man patterns:
    • Candle body located in the upper third of the range.
    • Long lower shadow relative to the real body.
    • Gap in the direction of the trend compared with the previous candle.
    • Trend confirmation using the midpoint of the previous candle versus the moving average.
  3. Confirm entries with MFI thresholds:
    • Enter long if a hammer is detected and the MFI is at or below the configured oversold level (default: 40).
    • Enter short if a hanging man is detected and the MFI is at or above the configured overbought level (default: 60).
  4. Manage exits using MFI crossings:
    • Close short positions when the MFI crosses upward above the lower or upper exit levels (defaults: 30 and 70).
    • Close long positions when the MFI crosses upward above the upper exit level or downward below the lower exit level.
  5. Start the built-in risk protection module to handle emergency stops.

Parameters

Name Description Default
CandleType Candle data type and timeframe used for pattern detection. 30-minute time frame
MfiPeriod Lookback period for the MFI calculation. 47
MaPeriod Length of the SMA applied to closing prices for trend confirmation. 5
HammerEntryThreshold Maximum MFI value allowed before entering on a hammer signal. 40
HangingEntryThreshold Minimum MFI value required before entering on a hanging man signal. 60
MfiUpperExitLevel Upper MFI boundary; crossing above it closes any open position. 70
MfiLowerExitLevel Lower MFI boundary; crossing below it closes long positions, while crossing above it closes shorts. 30

Notes

  • The strategy evaluates only finished candles to avoid acting on incomplete information.
  • Hammer and hanging man detection is conservative: both a long lower shadow and a body located near the candle high are required.
  • The moving average replaces the MetaTrader 5 CloseAvg filter from the original expert advisor, ensuring that entries align with the broader trend.
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Hammer/Hanging Man + MFI strategy.
/// Buys on hammer with low MFI (oversold), sells on hanging man with high MFI (overbought).
/// </summary>
public class AhHmMfiStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _mfiPeriod;
	private readonly StrategyParam<decimal> _mfiLow;
	private readonly StrategyParam<decimal> _mfiHigh;
	private readonly StrategyParam<int> _signalCooldownCandles;
	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 MfiLow { get => _mfiLow.Value; set => _mfiLow.Value = value; }
	public decimal MfiHigh { get => _mfiHigh.Value; set => _mfiHigh.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public AhHmMfiStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_mfiPeriod = Param(nameof(MfiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("MFI Period", "MFI period", "Indicators");
		_mfiLow = Param(nameof(MfiLow), 35m)
			.SetDisplay("MFI Low", "MFI oversold threshold for buy", "Signals");
		_mfiHigh = Param(nameof(MfiHigh), 65m)
			.SetDisplay("MFI High", "MFI overbought threshold for sell", "Signals");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 8)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_candlesSinceTrade = SignalCooldownCandles;
		var rsi = new RelativeStrengthIndex { Length = MfiPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(rsi, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal mfiValue)
	{
		if (candle.State != CandleStates.Finished) return;

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		var body = Math.Abs(candle.ClosePrice - candle.OpenPrice);
		var range = candle.HighPrice - candle.LowPrice;
		if (range <= 0 || body <= 0) return;

		var upperShadow = candle.HighPrice - Math.Max(candle.OpenPrice, candle.ClosePrice);
		var lowerShadow = Math.Min(candle.OpenPrice, candle.ClosePrice) - candle.LowPrice;

		var isHammer = lowerShadow > body * 2.5m && upperShadow < body * 0.5m;
		var isHangingMan = upperShadow > body * 2.5m && lowerShadow < body * 0.5m;

		if (isHammer && mfiValue < MfiLow && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
		{
			BuyMarket();
			_candlesSinceTrade = 0;
		}
		else if (isHangingMan && mfiValue > MfiHigh && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
		{
			SellMarket();
			_candlesSinceTrade = 0;
		}
	}
}