在 GitHub 上查看

CDC PL MFI 策略

概述

CDC PL MFI 策略 将 MetaTrader 专家顾问 Expert_ADC_PL_MFI(MQL/299)移植到 StockSharp 平台。策略识别两根 K 线组成的 Dark Cloud CoverPiercing Line 形态,并使用 资金流量指数(MFI) 对信号进行确认。实现沿用了原策略的指标周期与阈值,并提供以点数表示的可选止损/止盈,同时在 MFI 穿越设定的反转水平时平仓。

交易逻辑

  1. 订阅配置的蜡烛类型(默认 1 小时)并计算指定周期的 MFI,同时维护蜡烛实体长度和收盘价的简单移动平均,用于复现原策略的趋势与波动过滤条件。
  2. 当出现满足以下条件的看涨 Piercing Line 形态且当前 MFI 低于 LongEntryLevel(默认 40)时,建立或反转为多头:
    • 当前蜡烛向上收盘并高于前一根蜡烛实体中点;
    • 开盘价向下跳空低于前一根蜡烛最低价;
    • 当前与上一根蜡烛的实体长度均大于平均实体;
    • 前一根蜡烛的收盘价低于趋势均线。
  3. 当出现满足以下条件的看跌 Dark Cloud Cover 形态且当前 MFI 高于 ShortEntryLevel(默认 60)时,建立或反转为空头:
    • 当前蜡烛向下收盘并低于前一根蜡烛实体中点;
    • 开盘价向上跳空高于前一根蜡烛最高价;
    • 当前与上一根蜡烛的实体长度均大于平均实体;
    • 前一根蜡烛的收盘价高于趋势均线。
  4. 根据 MFI 水平主动离场:
    • 当 MFI 向上穿越 ExitLowerLevel30)或 ExitUpperLevel70)时平掉空头;
    • 当 MFI 向下穿越 ExitUpperLevel70)或 ExitLowerLevel30)时平掉多头。
  5. 止损与止盈可选。如果 TakeProfitPipsStopLossPips 大于零,则调用 StartProtection,并把点数距离换算为价格距离(点数乘以合约的最小报价单位)。

参数

参数 说明 默认值
CandleType 用于形态识别的蜡烛类型。 1 小时
MfiPeriod MFI 指标周期。 49
BodyAveragePeriod 计算平均蜡烛实体长度的周期。 11
LongEntryLevel 确认看涨信号的 MFI 阈值。 40
ShortEntryLevel 确认看跌信号的 MFI 阈值。 60
ExitLowerLevel MFI 向上突破该值时平空。 30
ExitUpperLevel MFI 向下跌破该值时平多。 70
StopLossPips 止损距离(点),0 表示关闭。 50
TakeProfitPips 止盈距离(点),0 表示关闭。 50

说明

  • 默认下单量为 1 手;当策略需要反向时,会发送一笔足够大的市价单同时平掉原仓位并建立新仓,与原版 EA 行为一致。
  • 形态识别完全基于已完成的蜡烛,跳空必须突破上一根蜡烛的高/低点,并通过简单移动平均确认趋势方向。
  • MFI 数值直接来自绑定的指标,只缓存最近几次的结果以判断阈值突破,无需额外数据结构。
  • 本目录仅包含 C# 实现,暂不提供 Python 版本。
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;
	}
}