在MQL5中构建自定义市场状态检测系统(第一部分):指标(基础篇)
用 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 就能编译。
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 跑一遍看离散度。
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; 才能在调用层直接读到正数强度的回归指标。外汇与贵金属波动受杠杆与事件驱动,自相关随时变号,高风险的品种上这套读数只能做概率参考。
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);