在MQL5中构建自定义市场状态检测系统(第一部分):指标(基础篇)
📘

在MQL5中构建自定义市场状态检测系统(第一部分):指标(基础篇)

第 1/3 篇

用 MQL5 搭一个市场状态探针

在 MT5 里做自定义分析,第一步往往是先把「市场现在处于什么状态」量化出来,而不是凭肉眼猜震荡还是趋势。原文作者 Sahil Bagdi 在 2026 年 1 月 7 日发布的系列第一部分,就是围绕这个需求从指标层落地。 统计基础通常来自价格序列的均值与离散度。用一段滑动窗口算收益率标准差,就能给当前波动水平打个分;再叠加均值回归或趋势偏离判定,市场状态就从模糊感觉变成可回看的数字。 该节给出的落地路径分四步:理解状态定义、构建统计基础、实现检测器逻辑、最后封装成自定义指标做可视化。读者打开 MT5 的 MQL5 编辑器,新建一个 Custom Indicator,就能照这个顺序把骨架搭起来。外汇与贵金属波动受杠杆与消息面影响大,这类状态评分只描述概率倾向,实盘仍需自担高风险。

「为什么死守单一策略会吃瘪」

金融市场在强趋势、横向盘整和混乱波动三种状态间反复切换,这是肉眼可见的常态。算法交易者最头疼的地方在于:趋势里赚钱的策略,一盘整就亏;为低波动设计的仓位,遇到波动骤增可能直接爆仓。 大多数交易系统构建时偷偷假设了一件事——市场行为长期不变。这个假设和真实市场根本对不上,于是策略性能退化成了家常便饭:回测漂亮、上线初期也行,行情一变就开始失灵。 这时候交易者一般只有两条路:砍掉策略重来,或者硬扛回撤赌行情回转。两条都不舒服。 如果系统能客观认出当下是趋势、盘整还是波动,并自动换打法呢?本系列要做的就是一个 MQL5 市场状态检测框架,把行情分类,给自适应交易留接口。到第二部分结束,你会拿到统计分类基础、状态检测类、图表可视化指标和随状态切策略的 EA。 外汇和贵金属杠杆高、状态切换快,用固定策略硬刚属于高风险行为;先想清楚自己系统在哪类行情会死,比急着上线重要。

◍ 先认清市场处在哪种状态

市场不会一直用同一种方式走路。它会在几种行为模式之间切换,每种模式对应不同的统计特征,也直接决定你手里的策略能不能赚钱。 所谓市场状态,就是一段具有稳定统计特性的价格行为。对交易系统开发最有用的只有三类:趋势型、盘整型、波动型。趋势型里收益呈正自相关,价格往一个方向走、回撤浅;盘整型收益呈负自相关,涨完容易跌、跌完容易涨,价格在边界内晃;波动型标准差高、方向乱,常出现在消息或压力期。 传统指标大多只认形态不认状态。MA、MACD 能看趋势却分不出趋势和暴涨暴跌的波动;RSI、随机指标在盘整好用,趋势里净给假信号;布林带跟得上波动率,但说不清状态什么时候切了。不清状态就上策略,等于盲赌环境碰巧合你的假设。 状态检测得靠客观统计量:自相关性(正=趋势、负=均值回归)、波动性(标准差突变常是切换前兆)、趋势强度(可用自相关绝对值、回归斜率或 ADX 量化)。把这三者组合,就能搭一个不靠肉眼判断的状态分类框架,下一节直接用 MQL5 落地。

给状态检测打地基的统计类

做市场状态识别,第一步不是画通道或算均线,而是先把价格序列的统计特征抽象成一个可复用的计算核心。这里用 CStatistics 类把均值、标准差、极差、自相关和百分位数全收进一个对象,后面接状态机时直接调方法,不用每次重算。 类里有个细节值得抄:m_isSorted 这个布尔旗。只要 SetData 或 AddData 动了原始数组,就把它置 false;只有真要算 Percentile 或 Median 时才排序,排完存进 m_sortedData 并置 true。连续算多个分位时只排一次,在 MT5 实盘逐根 tick 更新时能少掉不少重复开销。 自相关是区分趋势和盘整的主轴。Autocorrelation(1) 滞后一阶为正,价格倾向延续前一根方向,趋势概率偏高;为负则倾向来回蹭,均值回归色彩更重。TrendStrength 直接返回滞后1阶自相关值,MeanReversionStrength 返回它的负值——正数越大,对应倾向越强。外汇与贵金属波动受消息驱动,这类统计只描述已发生分布,实战须警惕滑点与跳空高风险。 边界处理上,空数组或只有一个点时 Mean、StdDev 全返回 0,避免除零崩脚本。下面这段是类声明骨架,private 里挂了原数组、长度、排序旗和排序副本,public 铺开所有接口,复制进 MT5 的 .mqh 就能编译。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class for statistical calculations                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CStatistics
{
class="kw">private:
    class="type">class="kw">double      m_data[];         class=class="str">"cmt">// Data array for calculations
    class="type">int         m_dataSize;       class=class="str">"cmt">// Size of the data array
    class="type">bool        m_isSorted;       class=class="str">"cmt">// Flag indicating if data is sorted
    class="type">class="kw">double      m_sortedData[];   class=class="str">"cmt">// Sorted copy of data for percentile calculations
    
class="kw">public:
    class=class="str">"cmt">// Constructor and destructor
    CStatistics();
    ~CStatistics();
    
    class=class="str">"cmt">// Data management methods
    class="type">bool        SetData(class="kw">const class="type">class="kw">double &data[], class="type">int size);
    class="type">bool        AddData(class="type">class="kw">double value);
    class="type">void        Clear();
    
    class=class="str">"cmt">// Basic statistical methods
    class="type">class="kw">double      Mean() class="kw">const;
    class="type">class="kw">double      StandardDeviation() class="kw">const;
    class="type">class="kw">double      Variance() class="kw">const;
    
    class=class="str">"cmt">// Range and extremes
    class="type">class="kw">double      Min() class="kw">const;
    class="type">class="kw">double      Max() class="kw">const;
    class="type">class="kw">double      Range() class="kw">const;
    
    class=class="str">"cmt">// Time series specific methods
    class="type">class="kw">double      Autocorrelation(class="type">int lag) class="kw">const;
    class="type">class="kw">double      TrendStrength() class="kw">const;
    class="type">class="kw">double      MeanReversionStrength() class="kw">const;
    
    class=class="str">"cmt">// Percentile calculations
    class="type">class="kw">double      Percentile(class="type">class="kw">double percentile);
    class="type">class="kw">double      Median();
};

「统计类的构造与均值方差实现」

做价格分布分析时,先得有个能装样本、算基础统计量的容器。下面这段 CStatistics 类的构造与核心方法,把样本数组的初始化、追加、清零,以及均值、标准差、方差都包好了,直接挂到 EA 里就能跑。 构造器 CStatistics() 把 m_dataSize 置 0、m_isSorted 标 false,并用 ArrayResize 把 m_data 与 m_sortedData 都缩成 0 长度;析构器 ~CStatistics() 只调 Clear() 释放。SetData() 要求 size>0,否则返回 false,它会按 size 重设 m_data 并把传入数组逐元素拷贝进去,同时把排序标记复位。AddData() 则是每次 m_dataSize 自增 1、数组扩容、把新值塞到末位,同样清掉排序标记。Clear() 把尺寸归零、两数组缩容、排序标记复位。 均值 Mean() 在 m_dataSize<=0 时返回 0.0,否则累加全部元素除以样本量。标准差 StandardDeviation() 在样本量<=1 时返回 0.0(自由度不足),否则用样本方差公式 sum((x-mean)^2)/(n-1) 再开根号;注意这里是除以 m_dataSize-1,属无偏估计。Variance() 直接调标准差结果自乘,样本量<=1 同样返 0.0。 别把正态当圣经 外汇与贵金属收益率常带厚尾,n=30 以下时标准差置信区间会明显偏宽,实盘前建议先用历史 tick 跑一遍看离散度。

MQL5 / C++
CStatistics::CStatistics()
{
   m_dataSize = class="num">0;
   m_isSorted = false;
   ArrayResize(m_data, class="num">0);
   ArrayResize(m_sortedData, class="num">0);
}
CStatistics::~CStatistics()
{
   Clear();
}
class="type">bool CStatistics::SetData(class="kw">const class="type">class="kw">double &data[], class="type">int size)
{
   if(size <= class="num">0)
      class="kw">return false;
      
   m_dataSize = size;
   ArrayResize(m_data, size);
   
   for(class="type">int i = class="num">0; i < size; i++)
      m_data[i] = data[i];
      
   m_isSorted = false;
   class="kw">return true;
}
class="type">bool CStatistics::AddData(class="type">class="kw">double value)
{
   m_dataSize++;
   ArrayResize(m_data, m_dataSize);
   m_data[m_dataSize - class="num">1] = value;
   m_isSorted = false;
   class="kw">return true;
}
class="type">void CStatistics::Clear()
{
   m_dataSize = class="num">0;
   ArrayResize(m_data, class="num">0);
   ArrayResize(m_sortedData, class="num">0);
   m_isSorted = false;
}

class="type">class="kw">double CStatistics::Mean() class="kw">const
{
   if(m_dataSize <= class="num">0)
      class="kw">return class="num">0.0;
      
   class="type">class="kw">double sum = class="num">0.0;
   for(class="type">int i = class="num">0; i < m_dataSize; i++)
      sum += m_data[i];
      
   class="kw">return sum / m_dataSize;
}
class="type">class="kw">double CStatistics::StandardDeviation() class="kw">const
{
   if(m_dataSize <= class="num">1)
      class="kw">return class="num">0.0;
      
   class="type">class="kw">double mean = Mean();
   class="type">class="kw">double sum = class="num">0.0;
   
   for(class="type">int i = class="num">0; i < m_dataSize; i++)
      sum += MathPow(m_data[i] - mean, class="num">2);
      
   class="kw">return MathSqrt(sum / (m_dataSize - class="num">1));
}
class="type">class="kw">double CStatistics::Variance() class="kw">const
{
   if(m_dataSize <= class="num">1)
      class="kw">return class="num">0.0;
      
   class="type">class="kw">double stdDev = StandardDeviation();
   class="kw">return stdDev * stdDev;
}

◍ 极值、极差与一阶自相关判趋势

统计类里最底层的几个量:最小值、最大值和极差,都是线性扫描一遍数组就能拿到。空数据或长度为 0 时直接返回 0.0,避免越界;非空时以首元素为初值,逐个比较替换,时间复杂度 O(n),在 MT5 的 EA 初始化阶段跑几百根 K 线数据几乎无感。 Range() 直接复用 Max() 减 Min(),没有单独存字段,好处是数据更新后不用维护冗余状态,代价是调一次 Range 会扫两遍数组——样本量上了十万级别才需要考虑合并循环。 真正用来区分行情性质的是 Autocorrelation(lag)。lag=1 时,若返回值明显大于 0(例如 0.3 以上),价格序列倾向有趋势惯性;若落为负值(如 -0.2),则更可能出现均值回归。分母用全部样本的去均值平方和,lag 段不够或分母为 0 时返回 0.0,代表无法判断。 TrendStrength() 与 MeanReversionStrength() 目前都只取一阶自相关:前者原样返回 ac1,后者本意取负值表征回归强度,但给出的代码片段在 return 前截断,实际落地时应当写 return -ac1; 才能在调用层直接读到正数强度的回归指标。外汇与贵金属波动受杠杆与事件驱动,自相关随时变号,高风险的品种上这套读数只能做概率参考。

MQL5 / C++
class="type">class="kw">double CStatistics::Min() class="kw">const
{
   if(m_dataSize <= class="num">0)
      class="kw">return class="num">0.0;

   class="type">class="kw">double min = m_data[class="num">0];
   for(class="type">int i = class="num">1; i < m_dataSize; i++)
      if(m_data[i] < min)
         min = m_data[i];

   class="kw">return min;
}
class="type">class="kw">double CStatistics::Max() class="kw">const
{
   if(m_dataSize <= class="num">0)
      class="kw">return class="num">0.0;

   class="type">class="kw">double max = m_data[class="num">0];
   for(class="type">int i = class="num">1; i < m_dataSize; i++)
      if(m_data[i] > max)
         max = m_data[i];

   class="kw">return max;
}
class="type">class="kw">double CStatistics::Range() class="kw">const
{
   class="kw">return Max() - Min();
}

class="type">class="kw">double CStatistics::Autocorrelation(class="type">int lag) class="kw">const
{
   if(m_dataSize <= lag || lag <= class="num">0)
      class="kw">return class="num">0.0;

   class="type">class="kw">double mean = Mean();
   class="type">class="kw">double numerator = class="num">0.0;
   class="type">class="kw">double denominator = class="num">0.0;

   for(class="type">int i = class="num">0; i < m_dataSize - lag; i++)
   {
      numerator += (m_data[i] - mean) * (m_data[i + lag] - mean);
   }

   for(class="type">int i = class="num">0; i < m_dataSize; i++)
   {
      denominator += MathPow(m_data[i] - mean, class="num">2);
   }

   if(denominator == class="num">0.0)
      class="kw">return class="num">0.0;

   class="kw">return numerator / denominator;
}
class="type">class="kw">double CStatistics::TrendStrength() class="kw">const
{
   class=class="str">"cmt">// Use lag-class="num">1 autocorrelation as a measure of trend strength
   class="type">class="kw">double ac1 = Autocorrelation(class="num">1);

   class=class="str">"cmt">// Positive autocorrelation indicates trending behavior
   class="kw">return ac1;
}
class="type">class="kw">double CStatistics::MeanReversionStrength() class="kw">const
{
   class=class="str">"cmt">// Negative autocorrelation indicates mean-reverting behavior
   class="type">class="kw">double ac1 = Autocorrelation(class="num">1);

常见问题

先用统计类算出收益率的均值、方差和一阶自相关:自相关明显为正且极差放大,倾向趋势;自相关近零且方差收敛,多为震荡。把这套计算写成探针每天跑一遍即可。
更可能是市场状态切换了:趋势策略在震荡市会反复挨耳光。先检测市场状态再决定用不用该策略,比死调参数更治本。
可以。小布盯盘的 AIGC 已内置状态诊断,打开对应品种页就能直接看到当前偏趋势还是偏震荡的判定,不用自己写代码。
不够。方差只看离散度,极值和极差能暴露价格是否走出单边大波;三者结合才能区分「窄幅乱震」和「宽幅单向」。
负号代表收益倾向反转,价格大概率走来回。此时做突破趋势单胜率可能偏低,更适合逢高卖逢低买的均值回归思路。