在 GitHub 上查看

Engulfing MFI Confirmation 策略

该策略复刻 MetaTrader 专家顾问 "Expert_ABE_BE_MFI" 的交易逻辑。通过检测日式蜡烛图的吞没形态,并辅以资金流量指标(Money Flow Index,MFI)确认信号。当出现看涨吞没且 MFI 位于超卖区域时开多;当出现看跌吞没且 MFI 处于超买区域时开空。MFI 穿越关键阈值时表示动能反转,用于平仓。

核心思路

  1. 形态检测:当前完成的蜡烛实体必须完全吞没前一根蜡烛实体,并与交易方向一致。
  2. 成交量确认:MFI 指标(默认周期 37)低于超卖水平(40)触发多头信号,高于超买水平(60)触发空头信号。
  3. 动能出场:MFI 向相反方向突破 30 或 70 时,表示趋势动能转弱,立即平掉对应头寸。

指标

  • Money Flow Index (MFI):衡量价格与成交量共同作用的动能。策略保留最近两个 MFI 数值以检测阈值穿越。
  • 蜡烛实体分析:不另外注册指标,通过最后两根完成蜡烛直接计算吞没形态。

交易规则

多头入场

  • 前一根蜡烛收阴,当前蜡烛收阳。
  • 当前蜡烛开盘价不高于前一根的收盘价,收盘价不低于前一根的开盘价(严格吞没)。
  • 最新 MFI 数值低于参数 OversoldLevel(默认 40)。

空头入场

  • 前一根蜡烛收阳,当前蜡烛收阴。
  • 当前蜡烛开盘价不低于前一根收盘价,收盘价不高于前一根开盘价。
  • 最新 MFI 数值高于参数 OverboughtLevel(默认 60)。

平仓条件

  • 平空:MFI 从下向上突破 ExitLongLevel(30)或 ExitShortLevel(70)。
  • 平多:MFI 从上向下跌破 ExitShortLevel(70)或 ExitLongLevel(30)。

这些阈值还原了原始专家顾问中的“投票”逻辑,确保资金流向改变时及时退出。

交易管理

  • 使用市价单 (BuyMarket / SellMarket) 开仓和平仓。
  • 不设置固定止损/止盈,风险控制完全依赖 MFI 反转信号。

参数

名称 说明 默认值 备注
CandleType 分析所用的蜡烛类型/周期。 1 分钟 可替换为任意支持的蜡烛类型。
MfiPeriod MFI 指标周期。 37 必须大于 0,与原始 EA 相同。
OversoldLevel 触发多头的 MFI 超卖阈值。 40 可根据市场进行优化。
OverboughtLevel 触发空头的 MFI 超买阈值。 60 可根据市场进行优化。
ExitLongLevel MFI 反转的下轨阈值。 30 同时用于多头出场与空头确认。
ExitShortLevel MFI 反转的上轨阈值。 70 同时用于空头出场与多头确认。

转换说明

  • 原 MQL 专家通过“加权投票”汇总信号。C# 实现将其转化为明确的入场和平仓条件,保留相同的逻辑顺序。
  • MQL 版本中的资金管理和跟踪止损模块未迁移;头寸规模由 StockSharp 策略的 Volume 参数控制。
  • 使用高层 API SubscribeCandles().Bind(...) 绑定指标,符合仓库要求。

使用建议

  • 可对 MfiPeriodOversoldLevelOverboughtLevel 等参数执行优化以适应不同品种。
  • 若需要额外保护,可在宿主程序中调用 StartProtection 添加止损/止盈。
  • 启动策略前,请确保历史数据足够,以便 MFI 指标完成初始化。
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>
/// Engulfing MFI Confirmation strategy: Engulfing pattern with MFI filter.
/// Bullish engulfing + oversold MFI for long, bearish engulfing + overbought MFI for short.
/// </summary>
public class EngulfingMfiConfirmationStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _mfiPeriod;
	private readonly StrategyParam<decimal> _oversold;
	private readonly StrategyParam<decimal> _overbought;
	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 Oversold { get => _oversold.Value; set => _oversold.Value = value; }
	public decimal Overbought { get => _overbought.Value; set => _overbought.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public EngulfingMfiConfirmationStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_mfiPeriod = Param(nameof(MfiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("MFI Period", "Money Flow Index period", "Indicators");
		_oversold = Param(nameof(Oversold), 30m)
			.SetDisplay("Oversold", "MFI oversold level", "Signals");
		_overbought = Param(nameof(Overbought), 70m)
			.SetDisplay("Overbought", "MFI overbought level", "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();
	}

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

			if (curr is null || prev is null)
				return;

			var bullishEngulfing = prev.OpenPrice > prev.ClosePrice
				&& curr.ClosePrice > curr.OpenPrice
				&& curr.OpenPrice <= prev.ClosePrice
				&& curr.ClosePrice >= prev.OpenPrice;

			var bearishEngulfing = prev.ClosePrice > prev.OpenPrice
				&& curr.OpenPrice > curr.ClosePrice
				&& curr.OpenPrice >= prev.ClosePrice
				&& curr.ClosePrice <= prev.OpenPrice;

			if (bullishEngulfing && mfiValue < Oversold && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (bearishEngulfing && mfiValue > Overbought && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		// Exit on MFI crossing
		if (_hasPrevMfi)
		{
			if (Position > 0 && _prevMfi >= Overbought && mfiValue < Overbought && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
			else if (Position < 0 && _prevMfi <= Oversold && mfiValue > Oversold && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevMfi = mfiValue;
		_hasPrevMfi = true;
	}
}