在 GitHub 上查看

ABH_BH_MFI 策略

概述

ABH_BH_MFI 策略 是 MetaTrader 专家顾问 “Expert_ABH_BH_MFI” 的 StockSharp 高级 API 版本。策略通过识别孕线形态并结合资金流量指数(MFI)确认来捕捉趋势反转:

  • 当下跌行情中出现看涨孕线,且 MFI 仍处于低位时做多;
  • 当上涨行情中出现看跌孕线,且 MFI 处于高位时做空。 原始 MQL 版本依赖 MetaTrader 的信号、资金管理和跟踪模块,本移植保留了相同的交易逻辑,并用 StockSharp 的烛图订阅、指标绑定与保护系统重新实现。

交易逻辑

1. 孕线识别

  • 处理函数会缓存最近两根完成的 K 线。
  • 看涨孕线 条件:
    • 前两根蜡烛为实体大于平均值的长阴线;
    • 最近一根蜡烛为阳线,其开盘价/收盘价被前一根阴线实体完全包裹;
    • 更早那根蜡烛的高低点中值低于收盘价均线,确认下行趋势。
  • 看跌孕线 条件与之相反,需要上行趋势且阳线实体被新阴线包裹。

2. MFI 确认

  • MfiPeriod(默认 37)用于计算资金流量指数,与 MQL 参数一致。
  • 做多必须满足上一根完成蜡烛的 MFI 低于 BullishThreshold(默认 40),表明资金流已经衰竭。
  • 做空则要求 MFI 高于 BearishThreshold(默认 60),显示买盘动能减弱。

3. MFI 离场规则

  • 当多头仓位持有且 MFI 从下方穿越 ExitLowerLevel(默认 30)或 ExitUpperLevel(默认 70)时立即平仓。
  • 当空头仓位持有且 MFI 从上方跌破 ExitUpperLevel 或从下方穿越 ExitLowerLevel 时平仓,与 MQL 原版的条件完全一致。

4. 风险控制

  • StartProtection 会根据 StopLossPointsTakeProfitPoints(以价格步长计)可选地设置止损/止盈,参数为零时关闭保护,与原始专家顾问相同。
  • 头寸规模使用基础 Volume 属性,当需要反向时会自动加仓以先平旧仓再开新仓。

参数一览

参数 默认值 说明
CandleType 1 小时时间框架 用于形态识别与 MFI 计算的主图周期。
MfiPeriod 37 资金流量指数的回溯长度。
BodyAveragePeriod 11 计算平均实体和收盘均线的 SMA 长度。
BullishThreshold 40 看涨孕线需要的 MFI 上限。
BearishThreshold 60 看跌孕线需要的 MFI 下限。
ExitLowerLevel 30 MFI 穿越该值时触发离场。
ExitUpperLevel 70 MFI 从该值回落或穿越时触发离场。
StopLossPoints 0 以价格步长计的可选止损距离,0 表示关闭。
TakeProfitPoints 0 以价格步长计的可选止盈距离,0 表示关闭。

实现细节

  • 通过 SubscribeCandles(CandleType) 订阅蜡烛,并仅在 Finished 状态时处理,确保全部基于收盘价计算。
  • 使用 .Bind(_mfi, ProcessCandle) 直接获取 MFI 的十进制数值,无需调用 GetValue
  • 两个简单移动平均分别模拟 MQL 中的 AvgBodyCloseAvg,结果被缓存避免重复读取历史数据。
  • 每次交易前调用 IsFormedAndOnlineAndAllowTrading(),符合 StockSharp 的风控建议。

与 MQL 版本的差异

  • 资金管理模块改为使用 Volume 属性,不再单独实现固定手数类,但对功能没有影响。
  • 原版的 TrailingNone 没有实际逻辑,因此移植版本保持无追踪止损,仅提供固定止损/止盈参数。
  • 可以根据需要添加 LogInfo 输出,以便更细致地记录信号。

使用建议

  1. 启动前设置交易品种并指定合适的 CandleType
  2. 根据市场波动调整 MFI 阈值与离场参数。
  3. 若经纪商要求硬性止损/止盈,则将 StopLossPoints/TakeProfitPoints 设为大于零的数值。
  4. 运行时策略会创建图表区域展示 K 线、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>
/// ABH BH MFI strategy: Harami pattern with MFI confirmation.
/// </summary>
public class AbhBhMfiStrategy : 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 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 AbhBhMfiStrategy()
	{
		_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), 40m)
			.SetDisplay("Oversold", "MFI oversold level", "Signals");
		_overbought = Param(nameof(Overbought), 60m)
			.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();
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_candles.Clear();
		_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];

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

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

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