在 GitHub 上查看

MFI 确认的晨星/暮星策略

概述

该策略复刻 MetaTrader 专家顾问 Expert_AMS_ES_MFI 的核心思想,将多根 K 线反转形态与资金流量指数(MFI)动量确认结合在一起。策略在选定周期上监控三根蜡烛组成的晨星与暮星形态,并使用 MFI 阈值过滤信号,确认当前波段衰竭后再进场。MFI 的穿越信号也用于提前离场。

交易逻辑

  • 数据来源:所配置周期的收盘 K 线以及对应的 MFI 数值。
  • 指标
    • 资金流量指数(MFI),周期可配置(默认 49)。
  • 入场规则
    • 做多:检测到晨星形态(强烈的第一根看跌蜡烛、第二根小实体蜡烛、第三根强烈看涨蜡烛且收盘价高于第一根实体中点),并且上一根 K 线的 MFI 低于做多确认阈值(默认 40)。
    • 做空:检测到暮星形态(强烈的第一根看涨蜡烛、第二根小实体蜡烛、第三根强烈看跌蜡烛且收盘价低于第一根实体中点),并且上一根 K 线的 MFI 高于做空确认阈值(默认 60)。
    • 当需要反向开仓时,策略会先平掉旧方向的仓位,再开立新的方向。
  • 离场规则
    • 多单离场:当 MFI 上穿超买阈值(默认 70)或下穿超卖阈值(默认 30)时,视为动量过热或反转失败,立即平仓。
    • 空单离场:当 MFI 上穿超卖阈值(默认 30)或继续上穿超买阈值(默认 70)时,视为多头动量增强,立即平仓。
  • 委托类型:使用 StockSharp 中配置的策略默认手数以市价单执行。

参数

名称 说明 默认值
CandleType 用于分析的 K 线周期。 1 小时 K 线
MfiPeriod MFI 指标周期。 49
BullishMfiThreshold 晨星信号的 MFI 确认阈值。 40
BearishMfiThreshold 暮星信号的 MFI 确认阈值。 60
UpperExitLevel 用于识别超买的 MFI 离场阈值。 70
LowerExitLevel 用于识别超卖的 MFI 离场阈值。 30

所有参数都可以在 StockSharp Designer/Optimizer 中进行优化。

使用说明

  1. 将策略附加到目标品种,并将 CandleType 设置为与原始 MQL 专家顾问相同的时间周期。
  2. 在 StockSharp 平台中配置风险参数,如策略手数或经纪商要求的下单数量。
  3. 启动策略后,它会自动订阅 K 线、计算 MFI,并按照上述规则管理仓位。

来源

该策略直接移植自 MQL/323 目录中的 MQL5 专家顾问,保留了形态与 MFI 组合的决策逻辑,并改写为适配 StockSharp 高级 API。

namespace StockSharp.Samples.Strategies;

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

/// <summary>
/// Morning/Evening Star + MFI strategy.
/// Buys on morning star with low MFI, sells on evening star with high MFI.
/// </summary>
public class MorningEveningMfiStrategy : 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 ICandleMessage _prevCandle;
	private ICandleMessage _prevPrevCandle;
	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 MorningEveningMfiStrategy()
	{
		_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), 40m)
			.SetDisplay("MFI Low", "MFI oversold threshold", "Signals");
		_mfiHigh = Param(nameof(MfiHigh), 60m)
			.SetDisplay("MFI High", "MFI overbought threshold", "Signals");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 6)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

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

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevCandle = null;
		_prevPrevCandle = null;
		_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++;

		if (_prevCandle != null && _prevPrevCandle != null)
		{
			var prevBody = Math.Abs(_prevCandle.ClosePrice - _prevCandle.OpenPrice);
			var prevRange = _prevCandle.HighPrice - _prevCandle.LowPrice;
			var isSmallBody = prevRange > 0 && prevBody < prevRange * 0.3m;
			var firstMidpoint = (_prevPrevCandle.OpenPrice + _prevPrevCandle.ClosePrice) / 2m;

			var firstBearish = _prevPrevCandle.OpenPrice > _prevPrevCandle.ClosePrice;
			var currBullish = candle.ClosePrice > candle.OpenPrice;
			var isMorningStar = firstBearish && isSmallBody && currBullish && candle.ClosePrice > firstMidpoint;

			var firstBullish = _prevPrevCandle.ClosePrice > _prevPrevCandle.OpenPrice;
			var currBearish = candle.OpenPrice > candle.ClosePrice;
			var isEveningStar = firstBullish && isSmallBody && currBearish && candle.ClosePrice < firstMidpoint;

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

		_prevPrevCandle = _prevCandle;
		_prevCandle = candle;
	}
}