GitHub で見る

CDC PL MFI Strategy

Overview

The CDC PL MFI Strategy reproduces the MetaTrader expert advisor Expert_ADC_PL_MFI (MQL/299) in StockSharp. It searches for the Dark Cloud Cover and Piercing Line two-candle reversal patterns and validates each signal with the Money Flow Index (MFI) oscillator. The strategy uses the same indicator periods and level thresholds as the original expert, adds optional stop-loss and take-profit protection in pip units, and closes positions when the MFI crosses configurable reversal levels.

Trading Logic

  1. Subscribe to the configured candle type (one-hour candles by default) and calculate a Money Flow Index with the specified period. Maintain simple moving averages of candle body size and closing prices to replicate the original trend and volatility filters.
  2. When a bullish Piercing Line pattern forms (gap below the previous low, bullish close above the midpoint of the prior bearish candle, both candles larger than the average body, and the prior close below the trend average) and the current MFI value is below the LongEntryLevel (default 40), enter or flip to a long position.
  3. When a bearish Dark Cloud Cover pattern forms (gap above the previous high, bearish close below the midpoint of the prior bullish candle, both candles larger than the average body, and the prior close above the trend average) and the current MFI value is above the ShortEntryLevel (default 60), enter or flip to a short position.
  4. Monitor the MFI to close positions proactively:
    • Close short positions when the MFI crosses above ExitLowerLevel (30) or ExitUpperLevel (70).
    • Close long positions when the MFI crosses below ExitUpperLevel (70) or ExitLowerLevel (30).
  5. Protective orders are optional. When TakeProfitPips or StopLossPips are greater than zero, the strategy calls StartProtection with the corresponding price offsets (pip distance multiplied by the security price step).

Parameters

Parameter Description Default
CandleType Candle data type used for pattern detection. 1 hour time frame
MfiPeriod Length of the Money Flow Index oscillator. 49
BodyAveragePeriod Period of the candle body moving average used to qualify “long” candles. 11
LongEntryLevel MFI threshold that confirms bullish Piercing Line setups. 40
ShortEntryLevel MFI threshold that confirms bearish Dark Cloud Cover setups. 60
ExitLowerLevel Lower MFI level that triggers covering short positions. 30
ExitUpperLevel Upper MFI level that triggers closing long positions. 70
StopLossPips Optional stop-loss distance in pips (0 disables protection). 50
TakeProfitPips Optional take-profit distance in pips (0 disables protection). 50

Notes

  • Volume defaults to 1 lot. When the strategy flips direction it sends a single market order sized to close the existing position and open the new one, matching the MQL behavior.
  • Pattern detection mirrors the MetaTrader logic: only completed candles are evaluated, gaps must occur beyond the previous high/low, and a simple moving average enforces the prevailing trend condition.
  • The Money Flow Index values come directly from the bound indicator. No manual buffering of indicator history is required; the strategy stores only the most recent values to detect threshold crossings.
  • No Python port is provided; only the C# implementation is included in this directory.
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>
/// CDC PL MFI strategy: Dark Cloud Cover and Piercing Line candlestick patterns
/// confirmed by Money Flow Index levels.
/// </summary>
public class CdcPlMfiStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _mfiPeriod;
	private readonly StrategyParam<decimal> _longLevel;
	private readonly StrategyParam<decimal> _shortLevel;
	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 LongLevel { get => _longLevel.Value; set => _longLevel.Value = value; }
	public decimal ShortLevel { get => _shortLevel.Value; set => _shortLevel.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public CdcPlMfiStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_mfiPeriod = Param(nameof(MfiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("MFI Period", "Money Flow Index period", "Indicators");
		_longLevel = Param(nameof(LongLevel), 40m)
			.SetDisplay("Long Level", "MFI below this for long entry", "Signals");
		_shortLevel = Param(nameof(ShortLevel), 60m)
			.SetDisplay("Short Level", "MFI above this for short entry", "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();

		StartProtection(
			takeProfit: new Unit(2, UnitTypes.Percent),
			stopLoss: new Unit(1, UnitTypes.Percent)
		);
	}

	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 && _hasPrevMfi)
		{
			var curr = _candles[^1];
			var prev = _candles[^2];

			// Piercing Line: prev bearish, curr bullish, opens below prev low, closes above midpoint
			var isPiercing = prev.OpenPrice > prev.ClosePrice
				&& curr.ClosePrice > curr.OpenPrice
				&& curr.OpenPrice < prev.LowPrice
				&& curr.ClosePrice > (prev.OpenPrice + prev.ClosePrice) / 2m;

			// Dark Cloud Cover: prev bullish, curr bearish, opens above prev high, closes below midpoint
			var isDarkCloud = prev.ClosePrice > prev.OpenPrice
				&& curr.OpenPrice > curr.ClosePrice
				&& curr.OpenPrice > prev.HighPrice
				&& curr.ClosePrice < (prev.OpenPrice + prev.ClosePrice) / 2m;

			if (isPiercing && mfiValue < LongLevel && Position == 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (isDarkCloud && mfiValue > ShortLevel && Position == 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevMfi = mfiValue;
		_hasPrevMfi = true;
	}
}