在MQL5中构建自定义市场状态检测系统(第一部分):指标·综合运用
🧩

在MQL5中构建自定义市场状态检测系统(第一部分):指标·综合运用

(3/3)·把前三篇的统计基础、检测器与可视化指标收口成可运行的完整自适应交易框架

实战向 第 3/3 篇
很多交易者把市场状态检测写成孤立指标就停手,结果图表上看得见、EA里用不上。本篇把检测器类、自定义指标与智能交易系统拼成一条可部署的闭环,少一步都只是半成品。

读数接口与缓冲区导出的边界处理

市场状态检测类把趋势强度、波动率、状态标签都存进内部数组,对外只暴露两个层面的取数方式:单值读取和整段缓冲导出。单值接口在回看窗口不足 2 根时直接返回 0.0,避免越界访问,这是 MT5 自定义类里常见的防御式写法。 GetRegimeBuffer 要求目标数组长度不小于 m_lookbackPeriod,不够就 ArrayResize 撑到该长度,再逐根把 m_regimeBuffer 拷出去;而趋势强度和波动率缓冲的实际有效长度是 m_lookbackPeriod - 1,因为这两者在计算时少了一根基准。 在 MT5 里接这段逻辑时,调用方若用固定大小数组,要确认自己声明的 size 覆盖了 -1 的偏移,否则最后一根会被静默截断。外汇与贵金属行情跳空频繁,这类截断可能让状态切换晚一根 bar 才被指标画出来,实战中属于偏高风险的延迟。

MQL5 / C++
      class="kw">return class="num">0.0;
      
   class="kw">return m_trendStrength[m_lookbackPeriod - class="num">2];
}
class="type">class="kw">double CMarketRegimeDetector::GetVolatility() class="kw">const
{
   if(m_lookbackPeriod <= class="num">2)
      class="kw">return class="num">0.0;
      
   class="kw">return m_volatility[m_lookbackPeriod - class="num">2];
}
class="type">bool CMarketRegimeDetector::GetRegimeBuffer(class="type">class="kw">double &buffer[]) class="kw">const
{
   if(ArraySize(buffer) < m_lookbackPeriod)
      ArrayResize(buffer, m_lookbackPeriod);
      
   for(class="type">int i = class="num">0; i < m_lookbackPeriod; i++)
      buffer[i] = m_regimeBuffer[i];
      
   class="kw">return true;
}
class="type">bool CMarketRegimeDetector::GetTrendStrengthBuffer(class="type">class="kw">double &buffer[]) class="kw">const
{
   class="type">int size = m_lookbackPeriod - class="num">1;
   
   if(ArraySize(buffer) < size)
      ArrayResize(buffer, size);
      
   for(class="type">int i = class="num">0; i < size; i++)
      buffer[i] = m_trendStrength[i];
      
   class="kw">return true;
}
class="type">bool CMarketRegimeDetector::GetVolatilityBuffer(class="type">class="kw">double &buffer[]) class="kw">const
{
   class="type">int size = m_lookbackPeriod - class="num">1;
   
   if(ArraySize(buffer) < size)
      ArrayResize(buffer, size);
      
   for(class="type">int i = class="num">0; i < size; i++)
      buffer[i] = m_volatility[i];
      
   class="kw">return true;
}

「把市场状态检测器画到图表上」

有了状态检测类之后,下一步是在 MT5 主图直接渲染当前市场状态、趋势强度和波动率,省去手动翻数据的麻烦。下面这套自定义指标用三个缓冲区承接不同维度:RegimeBuffer 存状态编码,TrendStrengthBuffer 存趋势强度,VolatilityBuffer 存波动率。 初始化阶段用 SetIndexBuffer 把数组绑到指标槽位,再用 PlotIndexSetString / PlotIndexSetInteger 配置标签、线型与颜色。默认参数里 LookbackPeriod=100、SmoothingPeriod=10、TrendThreshold=0.2、VolatilityThreshold=1.5,都是可在输入栏改的。 OnCalculate 每次新 K 线或图表滚动都会被平台调用:先校验数据量,再喂给检测器,取出三路数值写进缓冲区,并在左上角注释当前读数。状态线编码很直接——0 上升趋势、1 下跌趋势、2 盘整、3 波动、4 未定义。 蓝线为正表示多头强,为负表示空头强,贴近 0 就是弱趋势;红线峰值往往领先状态切换,外汇与贵金属属高风险品种,这种拐点可能暗示机会也可能放大回撤,仓位要留余地。 别把状态线当买卖指令 它只告诉你「现在像什么市」,趋势市用跟随、震荡市用均值回归更顺手,但红线冲高时减仓或暂离场是更稳的应对,而不是反向赌突破。

MQL5 / C++
class="macro">#class="kw">property indicator_chart_window
class="macro">#class="kw">property indicator_buffers class="num">3
class="macro">#class="kw">property indicator_plots   class="num">3
class=class="str">"cmt">// Include the Market Regime Detector
class="macro">#include "MarketRegimeEnum.mqh"
class="macro">#include "MarketRegimeDetector.mqh"
class=class="str">"cmt">// Indicator 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">// Indicator buffers
class="type">class="kw">double RegimeBuffer[];          class=class="str">"cmt">// Buffer for regime classification
class="type">class="kw">double TrendStrengthBuffer[];  class=class="str">"cmt">// Buffer for trend strength
class="type">class="kw">double VolatilityBuffer[];     class=class="str">"cmt">// Buffer for volatility
class=class="str">"cmt">// Global variables
CMarketRegimeDetector *Detector = NULL;
class="type">int OnInit()
{
   class=class="str">"cmt">// Set indicator buffers
   SetIndexBuffer(class="num">0, RegimeBuffer, INDICATOR_DATA);
   SetIndexBuffer(class="num">1, TrendStrengthBuffer, INDICATOR_DATA);
   SetIndexBuffer(class="num">2, VolatilityBuffer, INDICATOR_DATA);
   
   class=class="str">"cmt">// Set indicator labels
   PlotIndexSetString(class="num">0, PLOT_LABEL, "Market Regime");
   PlotIndexSetString(class="num">1, PLOT_LABEL, "Trend Strength");
   PlotIndexSetString(class="num">2, PLOT_LABEL, "Volatility");
   
   class=class="str">"cmt">// Set indicator styles
   PlotIndexSetInteger(class="num">0, PLOT_DRAW_TYPE, DRAW_LINE);
   PlotIndexSetInteger(class="num">1, PLOT_DRAW_TYPE, DRAW_LINE);
   PlotIndexSetInteger(class="num">2, PLOT_DRAW_TYPE, DRAW_LINE);
   
   class=class="str">"cmt">// Set line colors
   PlotIndexSetInteger(class="num">1, PLOT_LINE_COLOR, clrBlue);
   PlotIndexSetInteger(class="num">2, PLOT_LINE_COLOR, clrRed);
   
   class=class="str">"cmt">// Set line styles
   PlotIndexSetInteger(class="num">1, PLOT_LINE_STYLE, STYLE_SOLID);

◍ 把市场状态探测器的初始化和计算接进指标

在 OnInit 里先给第 2 条绘图线设实线、两条线宽都设为 1,这是为了让状态线和强度线在 MT5 副图里不抢视觉。接着用 LookbackPeriod 和 SmoothingPeriod 实例化 CMarketRegimeDetector,若返回 NULL 就 Print 报错并返回 INIT_FAILED,否则用 SetTrendThreshold / SetVolatilityThreshold 灌入阈值后调 Initialize,最后把指标短名写成 Market Regime Detector。 OnCalculate 第一道闸门是 rates_total < LookbackPeriod 直接 return 0,意味着历史 K 线不足回看窗口时不出值,避免前端缓冲区算穿。之后调 Detector.ProcessData(close, rates_total),失败同样 return 0,成功才把 RegimeBuffer、TrendStrengthBuffer、VolatilityBuffer 三个数组从探测器拉出来。 图表左上角文本是用 GetRegimeDescription 拼的字符串,趋势强度和波动率都 DoubleToString 保留 4 位小数——你开 MT5 挂上这个指标,能在角落实时看到例如 Volatility: 0.0042 这类数值,据此判断黄金或欧美盘面处于低波还是扩波区间,外汇贵金属杠杆高,波动切换时止损易被扫,仅作概率参考。

MQL5 / C++
PlotIndexSetInteger(class="num">2, PLOT_LINE_STYLE, STYLE_SOLID);

class=class="str">"cmt">// Set line widths
PlotIndexSetInteger(class="num">1, PLOT_LINE_WIDTH, class="num">1);
PlotIndexSetInteger(class="num">2, PLOT_LINE_WIDTH, class="num">1);

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);
Detector.SetVolatilityThreshold(VolatilityThreshold);
Detector.Initialize();

class=class="str">"cmt">// Set indicator name
IndicatorSetString(INDICATOR_SHORTNAME, "Market Regime Detector");

class="kw">return INIT_SUCCEEDED;
}
class="type">int OnCalculate(class="kw">const class="type">int rates_total,
                class="kw">const class="type">int prev_calculated,
                class="kw">const class="type">class="kw">datetime &time[],
                class="kw">const class="type">class="kw">double &open[],
                class="kw">const class="type">class="kw">double &high[],
                class="kw">const class="type">class="kw">double &low[],
                class="kw">const class="type">class="kw">double &close[],
                class="kw">const class="type">long &tick_volume[],
                class="kw">const class="type">long &volume[],
                class="kw">const class="type">int &spread[])
{
   class=class="str">"cmt">// Check if there&class="macro">#x27;s enough data
   if(rates_total < LookbackPeriod)
      class="kw">return class="num">0;

   class=class="str">"cmt">// Process data with the detector
   if(!Detector.ProcessData(close, rates_total))
   {
      Print("Failed to process data with Market Regime Detector");
      class="kw">return class="num">0;
   }

   class=class="str">"cmt">// Get the regime buffer
   Detector.GetRegimeBuffer(RegimeBuffer);

   class=class="str">"cmt">// Get the trend strength buffer
   Detector.GetTrendStrengthBuffer(TrendStrengthBuffer);

   class=class="str">"cmt">// Get the volatility buffer
   Detector.GetVolatilityBuffer(VolatilityBuffer);

   class=class="str">"cmt">// Display current regime in the chart corner
   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);

收尾的资源释放与状态清屏

指标退出时若不手动释放 C++ 风格对象,MT5 会在日志里堆积内存警告,长期挂多周期图表可能拖慢终端。上面这段在 OnDeinit 里先判空再 delete Detector,并把指针置 NULL,是标准防野指针写法。 最后一行 Comment("") 把左下角之前打印的 regime / trend / volatility 文本整块擦掉,避免切周期或移除指标后残留旧字符。实盘里你可以故意不写这句,切到别的品种会看到上一品种的旧状态停留几秒,就能验证它的必要性。 跑完一次完整加载-卸载循环后,打开 MT5 的「专家」标签页,若没有 'object already deleted' 类报错,说明这套清理逻辑是干净的。

MQL5 / C++
  Comment(regimeText + "\n" + trendText + "\n" + volatilityText);
  
  class=class="str">"cmt">// Return the number of calculated bars
  class="kw">return rates_total;
}
class="type">void OnDeinit(class="kw">const class="type">int reason)
{
  class=class="str">"cmt">// Clean up
  if(Detector != NULL)
  {
    class="kw">delete Detector;
    Detector = NULL;
  }
  
  class=class="str">"cmt">// Clear the comment
  Comment("");
}

「先手动跑一遍状态指标」

整套市场状态检测系统的落地路径已经清晰:自相关性 + 波动率做统计基底,封装成类去判别趋势、震荡、波动三种状态,再借自定义指标把状态切换画在价格图上。逻辑上不复杂,但建议你先在 MT5 手动加载该指标观察两周,而不是立刻接 EA。 手动阶段重点看一件事——指标给出的状态色块和裸 K 结构是否吻合。比如波动率突增伴随长影线扫单,若指标仍标“震荡”,说明阈值要调。 下一部分会拆参数优化与状态切换延迟处理,在那之前你手头应已积累至少 20 组不同品种的状态切换样本,这是后续调参的底。

◍ 别急着下结论

这套状态检测方案的代码以四个文件落地:MarketRegimeEnum.mqh 仅 0.79 KB 定义枚举,CStatistics.mqh 约 9.28 KB 做统计计算,MarketRegimeDetector.mqh 约 16.5 KB 是核心逻辑,MarketRegimeIndicator.mq5 约 5.15 KB 负责图表可视化。 从社区反馈看,编译报错多集中在 include 路径:有位用户在 MQL5/Include 目录缺失前文头文件时,指标直接报出 24 个错误加 1 个警告,本质就是检测器头文件没放进终端对应文件夹。 下载 ZIP 后先把三个 .mqh 丢进 Terminal 数据目录的 MQL5/Include,再编译 .mq5,大概率能避开绝大多数『undeclared identifier』。外汇与贵金属波动随机性高,这类工具只帮你识别状态概率,不替你过滤爆仓风险。

MQL5 / C++
class="macro">#class="kw">property copyright "Sahil Bagdi"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property indicator_chart_window
class="macro">#class="kw">property indicator_buffers class="num">3
class="macro">#class="kw">property indicator_plots   class="num">3
class=class="str">"cmt">// 包括市场机制检测器
class="macro">#include <MarketRegimeEnum.mqh>
class="macro">#include <MarketRegimeDetector.mqh>
让小布替你跑这套状态巡检
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到当前趋势、盘整或波动标签,你只需把EA逻辑对接好状态枚举,重复劳动交给小布。

常见问题

倾向用收益率自相关与滚动波动率的分位数动态切分,比固定点数更抗品种差异,具体实现见本篇检测器类小节。
可用全局变量或iCustom缓冲区共享枚举值,EA每根K读一次即可,避免指标内重算拖慢终端。
可以,小布盯盘品种页已内置趋势/盘整/波动的AIGC判定,可作为你EA外部参考或人工复核。
市场状态切换频率随时段变化,若EA未对状态持续时间做最小确认,可能被噪音反复横跳洗掉,建议加状态稳定滤波。
状态检测器类、可视化指标、EA三者头文件依赖同一结构体定义,缺任一个会报未声明,按本篇文件树摆放即可。