在 MQL5 中构建自定义市场状态检测系统(第二部分):智能交易系统(EA)(基础篇)
📘

在 MQL5 中构建自定义市场状态检测系统(第二部分):智能交易系统(EA)(基础篇)

第 1/3 篇

◍ 先看清这套EA要解决的问题

原文给出的系列背景是:在 MQL5 里搭建自定义市场状态检测系统,本篇是第二部分,落点是把状态判断写成一个可跑的 EA。作者 Sahil Bagdi 于 2026 年 1 月 7 日发布,原文标注阅读量 902、评论 1,说明这套内容偏冷门实操,不是大众向指标科普。 从目录结构看,这一篇会依次覆盖:EA 创建、实践考量与优化、多时间周期市场状态指标、回测结果评估。也就是说,读者拿到手的不只是代码片段,而是一个能直接挂 MT5 跑、且带多周期状态过滤的自动化框架雏形。 外汇与贵金属市场高杠杆、高波动,任何 EA 在历史表现都只代表过去概率,实盘前必须自己用策略测试器重跑一遍同周期回测。

从识别市况到自动换挡

上一节我们已经把市场状态检测的地基打好了:CMarketRegimeDetector 类能客观区分趋势、震荡和高波动,MarketRegimeIndicator 也能在图表上直接画出来。但只把状态识别出来,等于任务做了一半——检测器再准,不落到下单动作上,就只是个分析玩具。 真正有价值的是让 EA 根据状态自动切换打法:趋势强时跑跟踪,横盘切均值回归,波动飙升就改风控参数。这一节要填的空,就是把「识别」接上「执行」。 我们要写的 MarketRegimeEA 会直接挂上之前的检测器,按状态选策略、调手数和止损止盈,还会处理状态切换时的平滑过渡。外汇和贵金属杠杆高、跳空频繁,这种自动换挡逻辑尤其要小心过渡期假信号,实盘前务必在 MT5 策略测试器里按品种和周期分开优化参数。

「把状态检测塞进一个会自己换打法的EA」

这套 EA 的核心思路是:让市场状态检测器在每根新 K 线后重新判定环境,再切换对应的交易逻辑。趋势市走趋势跟踪,震荡市做 RSI 均值回归,高波动市用布林带突破但把手数压到最小——三种状态的手数分别是 0.1 / 0.05 / 0.02,止损点数 100 / 50 / 150,止盈点数 200 / 80 / 300,参数全开放给用户输入。 OnInit() 里只干一件事:按 LookbackPeriod=100、SmoothingPeriod=10 把检测器 new 出来,设好 TrendThreshold=0.2 和 VolatilityThreshold=1.5,失败就返回 INIT_FAILED。OnTick() 先判新 K 线、攒够样本再调 Detector 处理收盘价数组;这里 ArraySetAsSeries(close, true) 不能省,它保证索引 0 是最新价,是 MQL5 时间序列的硬约定。 ExecuteRegimeBasedStrategy() 按状态分派:上升趋势买、下降趋势卖;RSI 超卖买超买卖;布林带突破跟进但仓位缩到 VolatileLotSize。每种分支都现算止损止盈再 OrderSend()。把波动率直接挂钩仓位,是高波动状态控风险的关键一步。 别把参数当摆设 TrendThreshold 在 0.1–0.5 之间、VolatilityThreshold 在 1.0–3.0 之间,这两个值直接决定 EA 多久切一次打法。拿 EURUSD 或 XAUUSD 在 MT5 里先跑历史样本,看切状态频率再反调阈值,比盲设更靠谱。外汇和贵金属杠杆高,实盘前务必用策略测试器验证。

MQL5 / C++
class=class="str">"cmt">// Include the Market Regime Detector
class="macro">#include <MarketRegimeEnum.mqh>
class="macro">#include <MarketRegimeDetector.mqh>
class=class="str">"cmt">// EA input parameters
input class="type">int      LookbackPeriod = class="num">100;      class=class="str">"cmt">// Lookback period for calculations
input class="type">int      SmoothingPeriod = class="num">10;      class=class="str">"cmt">// Smoothing period for regime transitions
input class="type">class="kw">double   TrendThreshold = class="num">0.2;      class=class="str">"cmt">// Threshold for trend detection(class="num">0.1-class="num">0.5) 
input class="type">class="kw">double   VolatilityThreshold = class="num">1.5; class=class="str">"cmt">// Threshold for volatility detection(class="num">1.0-class="num">3.0)
class=class="str">"cmt">// Trading parameters
input class="type">class="kw">double   TrendingLotSize = class="num">0.1;     class=class="str">"cmt">// Lot size for trending regimes
input class="type">class="kw">double   RangingLotSize = class="num">0.05;     class=class="str">"cmt">// Lot size for ranging regimes
input class="type">class="kw">double   VolatileLotSize = class="num">0.02;    class=class="str">"cmt">// Lot size for volatile regimes
input class="type">int      TrendingStopLoss = class="num">100;    class=class="str">"cmt">// Stop loss in points for trending regimes
input class="type">int      RangingStopLoss = class="num">50;      class=class="str">"cmt">// Stop loss in points for ranging regimes
input class="type">int      VolatileStopLoss = class="num">150;    class=class="str">"cmt">// Stop loss in points for volatile regimes
input class="type">int      TrendingTakeProfit = class="num">200;  class=class="str">"cmt">// Take profit in points for trending regimes
input class="type">int      RangingTakeProfit = class="num">80;    class=class="str">"cmt">// Take profit in points for ranging regimes
input class="type">int      VolatileTakeProfit = class="num">300;  class=class="str">"cmt">// Take profit in points for volatile regimes
class=class="str">"cmt">// Global variables
CMarketRegimeDetector *Detector = NULL;
class="type">int OnBarCount = class="num">0;
class="type">class="kw">datetime LastBarTime = class="num">0;
class="type">int OnInit()
{
   class=class="str">"cmt">// Create and initialize the Market Regime Detector
   Detector = new CMarketRegimeDetector(LookbackPeriod, SmoothingPeriod);
   if(Detector == NULL)
   {
      Print("Failed to create Market Regime Detector");
      class="kw">return INIT_FAILED;
   }
   
   class=class="str">"cmt">// Configure the detector
   Detector.SetTrendThreshold(TrendThreshold);

◍ 用 OnTick 驱动市场状态识别与下单闸门

这段逻辑把「新K线判定」做成了事件过滤器的核心:用 iTime 取当前周期第 0 根 Bar 的时间,和上次记录的 LastBarTime 比对,相等就直接 return,保证一段行情里只在每根 Bar 闭合后跑一次分析。 数据积累阶段用 OnBarCount 和 LookbackPeriod 做计数,不足时在图上提示「Accumulating data: X of Y bars」,例如回测 EURUSD M15 时若 LookbackPeriod=100,前 100 根 Bar 都不会触发策略,避免样本过少导致波动率估计失真。外汇与贵金属杠杆高,样本窗太短容易误判 regime,实盘前建议在策略测试器里先跑满这个预热长度。 CopyClose 取近 LookbackPeriod 根收盘价,ArraySetAsSeries(close, true) 让索引 0 对应最新 Bar;若 copied != LookbackPeriod 就打印失败条数并退出,这是 MT5 常见坑——切换品种或离线图时常取不满。 Detector.ProcessData 跑完后,用 GetCurrentRegime / GetTrendStrength / GetVolatility 把状态、趋势强度、波动率刷到 Comment,趋势强度与波动率都保留 4 位小数便于肉眼比对阈值。ExecuteRegimeBasedStrategy 开头用 PositionsTotal()>0 直接 return,等于给账户上了「同屏只持一单」的硬闸门,降低多 regime 信号叠加带来的敞口风险。 开仓参数那段先抓 ask/bid/point 三个报价原子量,再按 regime 分支决定 lotSize,后续你只要把 lotSize 赋值和订单发送补上,就能在 MT5 里跑一套 regime 过滤的轻量策略。

MQL5 / C++
Detector.SetVolatilityThreshold(VolatilityThreshold);
Detector.Initialize();

class=class="str">"cmt">// Initialize variables
OnBarCount = class="num">0;
LastBarTime = class="num">0;

class="kw">return INIT_SUCCEEDED;
}
class="type">void OnTick()
{
   class=class="str">"cmt">// Check for new bar
   class="type">class="kw">datetime currentBarTime = iTime(Symbol(), PERIOD_CURRENT, class="num">0);
   if(currentBarTime == LastBarTime)
      class="kw">return; class=class="str">"cmt">// No new bar
      
   LastBarTime = currentBarTime;
   OnBarCount++;
   
   class=class="str">"cmt">// Wait for enough bars to accumulate
   if(OnBarCount < LookbackPeriod)
   {
      Comment("Accumulating data: ", OnBarCount, " of ", LookbackPeriod, " bars");
      class="kw">return;
   }
   
   class=class="str">"cmt">// Get price data
   class="type">class="kw">double close[];
   ArraySetAsSeries(close, true);
   class="type">int copied = CopyClose(Symbol(), PERIOD_CURRENT, class="num">0, LookbackPeriod, close);
   
   if(copied != LookbackPeriod)
   {
      Print("Failed to copy price data: copied = ", copied, " of ", LookbackPeriod);
      class="kw">return;
   }
   
   class=class="str">"cmt">// Process data with the detector
   if(!Detector.ProcessData(close, LookbackPeriod))
   {
      Print("Failed to process data with Market Regime Detector");
      class="kw">return;
   }
   
   class=class="str">"cmt">// Get current market regime
   ENUM_MARKET_REGIME currentRegime = Detector.GetCurrentRegime();
   
   class=class="str">"cmt">// Display current regime information
   class="type">class="kw">string regimeText = "Current Market Regime: " + Detector.GetRegimeDescription();
   class="type">class="kw">string trendText = "Trend Strength: " + DoubleToString(Detector.GetTrendStrength(), class="num">4);
   class="type">class="kw">string volatilityText = "Volatility: " + DoubleToString(Detector.GetVolatility(), class="num">4);
   
   Comment(regimeText + "\n" + trendText + "\n" + volatilityText);
   
   class=class="str">"cmt">// Execute trading strategy based on market regime
   ExecuteRegimeBasedStrategy(currentRegime);
}
class="type">void ExecuteRegimeBasedStrategy(ENUM_MARKET_REGIME regime)
{
   class=class="str">"cmt">// Check if we already have open positions
   if(PositionsTotal() > class="num">0)
      class="kw">return; class=class="str">"cmt">// Don&class="macro">#x27;t open new positions if we already have one
   
   class=class="str">"cmt">// Get current market information
   class="type">class="kw">double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
   class="type">class="kw">double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
   class="type">class="kw">double point = SymbolInfoDouble(Symbol(), SYMBOL_POINT);
   
   class=class="str">"cmt">// Determine trading parameters based on regime
   class="type">class="kw">double lotSize = class="num">0.0;

按市况切换下单参数的代码骨架

这段逻辑把「趋势 / 震荡 / 波动」三种市况映射到不同的手数、止损、止盈与买卖方向上。趋势段直接跟方向:上涨吃多、下跌吃空,参数套用 Trending 系列变量;震荡段则借 RSI(14) 做均值回归,低于 30 考虑多、高于 70 考虑空,中间地带直接 return 不出手。 波动段原文只开了头,用布林带做突破的思路已经点出,但 upper[]、lower[] 数组刚设了序列方向就被截断。你可以把 iBands 的缓冲拷进来,价格刺穿上轨倾向跟多、下轨倾向跟空,参数接 Volatile 系列。 外汇与贵金属杠杆高,市况误判会让止损形同虚设,任何方向判断都只是概率倾向,开 MT5 把这段补完跑回测再上实盘。

MQL5 / C++
class="type">int stopLoss = class="num">0;
class="type">int takeProfit = class="num">0;
ENUM_ORDER_TYPE orderType = ORDER_TYPE_BUY;

class="kw">switch(regime)
{
   case REGIME_TRENDING_UP: {
      lotSize = TrendingLotSize;
      stopLoss = TrendingStopLoss;
      takeProfit = TrendingTakeProfit;
      orderType = ORDER_TYPE_BUY; class=class="str">"cmt">// Buy in uptrend
      break;
   }
   case REGIME_TRENDING_DOWN: {
      lotSize = TrendingLotSize;
      stopLoss = TrendingStopLoss;
      takeProfit = TrendingTakeProfit;
      orderType = ORDER_TYPE_SELL; class=class="str">"cmt">// Sell in downtrend
      break;
}
   
   case REGIME_RANGING: {
      class=class="str">"cmt">// In ranging markets, we can use mean-reversion strategies
      class=class="str">"cmt">// For simplicity, we&class="macro">#x27;ll use RSI to determine overbought/oversold
      class="type">class="kw">double rsi[];
      ArraySetAsSeries(rsi, true);
      class="type">int rsiCopied = CopyBuffer(iRSI(Symbol(), PERIOD_CURRENT, class="num">14, PRICE_CLOSE), class="num">0, class="num">0, class="num">2, rsi);
      
      if(rsiCopied != class="num">2)
         class="kw">return;
         
      lotSize = RangingLotSize;
      stopLoss = RangingStopLoss;
      takeProfit = RangingTakeProfit;
      
      if(rsi[class="num">0] < class="num">30) class=class="str">"cmt">// Oversold
         orderType = ORDER_TYPE_BUY;
      else if(rsi[class="num">0] > class="num">70) class=class="str">"cmt">// Overbought
         orderType = ORDER_TYPE_SELL;
      else
         class="kw">return; class=class="str">"cmt">// No signal
         
      break;
   }
   case REGIME_VOLATILE: {
      class=class="str">"cmt">// In volatile markets, we can use breakout strategies
      class=class="str">"cmt">// For simplicity, we&class="macro">#x27;ll use Bollinger Bands
      class="type">class="kw">double upper[], lower[];
      ArraySetAsSeries(upper, true);

「布林带突破信号的代码落地」

把波动率区制切到「动荡」后,EA 要从布林带上下轨抓取突破。下面这段直接取当前品种、当前周期、参数 20/2/0 的收盘价轨道,并把上下轨数组设成时间序列方便按索引读最新值。 代码里先各拉 2 根 K 线的 upper、lower 缓冲,若 CopyBuffer 返回值不是 2 就直接 return,避免半截数据触发错单。随后用 close[1] 与 close[0] 对照上一根和当前根是否站上 upper 或跌破 lower,满足前者挂 BUY,满足后者挂 SELL,否则无信号退出。 止损止盈按 VolatileLotSize / VolatileStopLoss / VolatileTakeProfit 套用,BUY 单 slLevel = ask - stopLoss*point、tpLevel = ask + takeProfit*point;SELL 反向。外汇与贵金属杠杆高,布林突破在消息面跳空时可能直接越过轨道,实盘前请在 MT5 策略测试器用 2020—2023 年数据跑一遍验证触发频率。

MQL5 / C++
ArraySetAsSeries(lower, true);

class="type">int bbCopied1 = CopyBuffer(iBands(Symbol(), PERIOD_CURRENT, class="num">20, class="num">2, class="num">0, PRICE_CLOSE), class="num">1, class="num">0, class="num">2, upper);
class="type">int bbCopied2 = CopyBuffer(iBands(Symbol(), PERIOD_CURRENT, class="num">20, class="num">2, class="num">0, PRICE_CLOSE), class="num">2, class="num">0, class="num">2, lower);

if(bbCopied1 != class="num">2 || bbCopied2 != class="num">2)
   class="kw">return;

lotSize = VolatileLotSize;
stopLoss = VolatileStopLoss;
takeProfit = VolatileTakeProfit;

class="type">class="kw">double close[];
ArraySetAsSeries(close, true);
class="type">int copied = CopyClose(Symbol(), PERIOD_CURRENT, class="num">0, class="num">2, close);

if(copied != class="num">2)
   class="kw">return;

if(close[class="num">1] < upper[class="num">1] && close[class="num">0] > upper[class="num">0]) class=class="str">"cmt">// Breakout above upper band
   orderType = ORDER_TYPE_BUY;
else if(close[class="num">1] > lower[class="num">1] && close[class="num">0] < lower[class="num">0]) class=class="str">"cmt">// Breakout below lower band
   orderType = ORDER_TYPE_SELL;
else
   class="kw">return; class=class="str">"cmt">// No signal

   break;
   }
   class="kw">default:
      class="kw">return; class=class="str">"cmt">// No trading in undefined regime
   }

class=class="str">"cmt">// Calculate stop loss and take profit levels
class="type">class="kw">double slLevel = class="num">0.0;
class="type">class="kw">double tpLevel = class="num">0.0;

if(orderType == ORDER_TYPE_BUY)
{
   slLevel = ask - stopLoss * point;
   tpLevel = ask + takeProfit * point;
}
else if(orderType == ORDER_TYPE_SELL)
{
   slLevel = bid + stopLoss * point;
   tpLevel = bid - takeProfit * point;

常见问题

可用布林带带宽结合波动率阈值做状态识别,带宽收窄倾向震荡、放大倾向趋势,写个EA在OnTick里自动算就行。
能。在状态检测模块输出市况标签后,用if分支给止损、手数、挂单距离赋不同值,EA就会自己换打法。
可以。小布盯盘的AIGC已内置市况诊断,打开对应品种页就能直接看震荡或趋势倾向,不用手搓EA。
突破价穿带宽且收盘确认再触发,配合状态检测只在趋势市开突破单,能过滤掉大部分震荡市毛刺。
大概率没做市况门控。加一层波动率突变判定,数据夜波动率爆表时直接关闸门,比调参数更管用。