在 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 会根据 StopLossPoints 与 TakeProfitPoints(以价格步长计)可选地设置止损/止盈,参数为零时关闭保护,与原始专家顾问相同。
- 头寸规模使用基础
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 中的
AvgBody 与 CloseAvg,结果被缓存避免重复读取历史数据。
- 每次交易前调用
IsFormedAndOnlineAndAllowTrading(),符合 StockSharp 的风控建议。
与 MQL 版本的差异
- 资金管理模块改为使用
Volume 属性,不再单独实现固定手数类,但对功能没有影响。
- 原版的
TrailingNone 没有实际逻辑,因此移植版本保持无追踪止损,仅提供固定止损/止盈参数。
- 可以根据需要添加
LogInfo 输出,以便更细致地记录信号。
使用建议
- 启动前设置交易品种并指定合适的
CandleType。
- 根据市场波动调整 MFI 阈值与离场参数。
- 若经纪商要求硬性止损/止盈,则将
StopLossPoints/TakeProfitPoints 设为大于零的数值。
- 运行时策略会创建图表区域展示 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;
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import MoneyFlowIndex
from StockSharp.Algo.Strategies import Strategy
class abh_bh_mfi_strategy(Strategy):
def __init__(self):
super(abh_bh_mfi_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._mfi_period = self.Param("MfiPeriod", 14)
self._oversold = self.Param("Oversold", 40.0)
self._overbought = self.Param("Overbought", 60.0)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 6)
self._candles = []
self._candles_since_trade = 6
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def MfiPeriod(self):
return self._mfi_period.Value
@MfiPeriod.setter
def MfiPeriod(self, value):
self._mfi_period.Value = value
@property
def Oversold(self):
return self._oversold.Value
@Oversold.setter
def Oversold(self, value):
self._oversold.Value = value
@property
def Overbought(self):
return self._overbought.Value
@Overbought.setter
def Overbought(self, value):
self._overbought.Value = value
@property
def SignalCooldownCandles(self):
return self._signal_cooldown_candles.Value
@SignalCooldownCandles.setter
def SignalCooldownCandles(self, value):
self._signal_cooldown_candles.Value = value
def OnReseted(self):
super(abh_bh_mfi_strategy, self).OnReseted()
self._candles.clear()
self._candles_since_trade = self.SignalCooldownCandles
def OnStarted2(self, time):
super(abh_bh_mfi_strategy, self).OnStarted2(time)
self._candles.clear()
self._candles_since_trade = self.SignalCooldownCandles
mfi = MoneyFlowIndex()
mfi.Length = self.MfiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(mfi, self._process_candle).Start()
def _process_candle(self, candle, mfi_value):
if candle.State != CandleStates.Finished:
return
if self._candles_since_trade < self.SignalCooldownCandles:
self._candles_since_trade += 1
mfi_val = float(mfi_value)
self._candles.append(candle)
if len(self._candles) > 5:
self._candles.pop(0)
if len(self._candles) >= 2:
curr = self._candles[-1]
prev = self._candles[-2]
bullish_harami = (float(prev.OpenPrice) > float(prev.ClosePrice)
and float(curr.ClosePrice) > float(curr.OpenPrice)
and float(curr.OpenPrice) > float(prev.ClosePrice)
and float(curr.ClosePrice) < float(prev.OpenPrice))
bearish_harami = (float(prev.ClosePrice) > float(prev.OpenPrice)
and float(curr.OpenPrice) > float(curr.ClosePrice)
and float(curr.ClosePrice) > float(prev.OpenPrice)
and float(curr.OpenPrice) < float(prev.ClosePrice))
if bullish_harami and mfi_val < self.Oversold and self.Position <= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
elif bearish_harami and mfi_val > self.Overbought and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
def CreateClone(self):
return abh_bh_mfi_strategy()