Engulfing MFI Confirmation 策略
该策略复刻 MetaTrader 专家顾问 "Expert_ABE_BE_MFI" 的交易逻辑。通过检测日式蜡烛图的吞没形态,并辅以资金流量指标(Money Flow Index,MFI)确认信号。当出现看涨吞没且 MFI 位于超卖区域时开多;当出现看跌吞没且 MFI 处于超买区域时开空。MFI 穿越关键阈值时表示动能反转,用于平仓。
核心思路
- 形态检测:当前完成的蜡烛实体必须完全吞没前一根蜡烛实体,并与交易方向一致。
- 成交量确认:MFI 指标(默认周期 37)低于超卖水平(40)触发多头信号,高于超买水平(60)触发空头信号。
- 动能出场: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(...)绑定指标,符合仓库要求。
使用建议
- 可对
MfiPeriod、OversoldLevel、OverboughtLevel等参数执行优化以适应不同品种。 - 若需要额外保护,可在宿主程序中调用
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;
}
}
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 engulfing_mfi_confirmation_strategy(Strategy):
def __init__(self):
super(engulfing_mfi_confirmation_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", 30.0)
self._overbought = self.Param("Overbought", 70.0)
self._signal_cooldown_candles = self.Param("SignalCooldownCandles", 6)
self._candles = []
self._prev_mfi = 0.0
self._has_prev_mfi = False
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(engulfing_mfi_confirmation_strategy, self).OnReseted()
self._candles.clear()
self._prev_mfi = 0.0
self._has_prev_mfi = False
self._candles_since_trade = self.SignalCooldownCandles
def OnStarted2(self, time):
super(engulfing_mfi_confirmation_strategy, self).OnStarted2(time)
self._candles.clear()
self._has_prev_mfi = False
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]
if curr is None or prev is None:
return
bullish_engulfing = (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_engulfing = (float(prev.ClosePrice) > float(prev.OpenPrice)
and float(curr.OpenPrice) > float(curr.ClosePrice)
and float(curr.OpenPrice) >= float(prev.ClosePrice)
and float(curr.ClosePrice) <= float(prev.OpenPrice))
if bullish_engulfing 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_engulfing and mfi_val > self.Overbought and self.Position >= 0 and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
if self._has_prev_mfi:
if self.Position > 0 and self._prev_mfi >= self.Overbought and mfi_val < self.Overbought and self._candles_since_trade >= self.SignalCooldownCandles:
self.SellMarket()
self._candles_since_trade = 0
elif self.Position < 0 and self._prev_mfi <= self.Oversold and mfi_val > self.Oversold and self._candles_since_trade >= self.SignalCooldownCandles:
self.BuyMarket()
self._candles_since_trade = 0
self._prev_mfi = mfi_val
self._has_prev_mfi = True
def CreateClone(self):
return engulfing_mfi_confirmation_strategy()