在 MQL5 中利用 ARIMA 模型进行预测(基础篇)
📘

在 MQL5 中利用 ARIMA 模型进行预测(基础篇)

第 1/3 篇

◍ 在 MT5 里亲手跑一个 ARIMA 预测

MQL5 原生没有 ARIMA 函数,想做时间序列外推,得自己把差分、自回归、移动平均三块拼起来。下面这段示例用一阶差分加简单 AR(2) 近似,在 EA 的 OnTick 里刷新预测值,适合拿历史收盘价做短线均值回归参考。 外汇与贵金属杠杆高、跳空频繁,ARIMA 外推只是概率倾向,不是方向保证;实盘前务必用策略测试器跑至少 3 个月 Tick 数据。 代码里 diff[i] = close[i] - close[i+1] 做一阶差分,forecast = phi1*diff[0] + phi2*diff[1] 用两个滞后项线性外推,最后加回最新价得到下一根候选价。把 phi1、phi2 从 0.5/0.3 起调,看残差是否平稳。

MQL5 / C++
class="type">class="kw">double close[class="num">3];
CopyClose(_Symbol, PERIOD_M15, class="num">0, class="num">3, close);
class="type">class="kw">double diff[class="num">2];
diff[class="num">0] = close[class="num">0] - close[class="num">1];
diff[class="num">1] = close[class="num">1] - close[class="num">2];
class="type">class="kw">double phi1 = class="num">0.5, phi2 = class="num">0.3;
class="type">class="kw">double forecast = close[class="num">0] + (phi1*diff[class="num">0] + phi2*diff[class="num">1]);
Print("ARIMA近似下一价: ", forecast);

「把 CArima 从能用到好用」

前一篇讲过的 CArima 类,从技术角度确实能在 MQL5 里把 ARIMA 模型跑起来并出预测值,但调用链路不直观,实盘里要手动拼差分阶数和参数,容易在 EA 里写出一堆冗余代码。 本文的目标很具体:扩展这个类,把预测相关的复杂度封装进方法内部,让交易者开 MT5 后只要传价格和阶数就能拿到下一根 K 线的估计值。 我们会逐个拆实现预测时的坑——比如平稳性判断、残差检验怎么接进类里,以及新加的辅助函数如何减少外部依赖。最终产出的完整类,可直接塞进智能系统或指标,对外汇和贵金属价格做概率性前瞻;这两类品种杠杆高、跳空频繁,模型输出仅作参考,不代表方向必现。

喂给 ARIMA 的输入序列到底要多长

ARIMA 靠的是时间序列里的依赖关系,要往外推一步或几步,必须先塞进去一段够长的输入。模型规格里写的最小输入尺度,只是底线;不同 AR 模型对输入长度的要求,并不只由阶数决定。 纯 AR 最好办,输入长度只要等于最大滞后就行。一旦混进 MA 项,麻烦就来了——我们手头根本没有真实的误差序列。解决办法是先忽略所有 MA 项、把初始 MA 当成 0,拿已知价格循环跑一批“冗余预测”反推出初始误差,这批冗余步数会额外吃掉输入长度。 CArima 还支持非连续滞后。比如 price(t)=c+AR*price(t-4),只挂一个滞后 4 的 AR 项,看上去只需 1 个历史值,但为了保住时间位置关系,实际至少得备 4 个输入。若模型还带差分,输入尺度还要再叠加。 外汇与贵金属波动剧烈、跳空频繁,用这类模型前务必在 MT5 用历史数据核对最小输入长度,否则预测结果可能完全不反映模型本身。

◍ 差分会吃掉一个数据点

做一阶差分时,序列长度必然比原始序列少 1。比如 100 根收盘价做差分后只剩 99 个值,这 99 个值才进模型输入。少掉的那个位置不是小事,它意味着模型能看到的窗口边缘信息被截断了。 为了不让边界效应拖累拟合,实践中常按差分的阶数补输入:d 阶差分就多塞 d 个原始域观测值当补偿。MT5 里如果你用自定义 EA 喂 RNN,得自己算好这个偏移,否则训练集和推理集的 bar 对齐会错位。 另一个坑在预测输出端。模型吐出来的是差分域里的增量,必须做逆差分(积分)并回原始价格域,否则你拿到的“预测”只是相邻 bar 的价差,不能直接挂单。外汇和贵金属杠杆高、跳空频繁,逆差分若没处理周末缺口,还原价可能偏离真实开盘价数个点到数十点。

「远程多步预测的退化陷阱」

想用单一输入集一路外推未来多个时序槽位,技术上能跑,但每多推一步,就要把上一步的预测值塞回模型当自回归输入。一旦越过已知初始序列的边界,真实值缺失,误差项无从计算,移动平均部分只能强制设为零。 这时模型事实上塌缩成纯自回归过程(若没设自回归项,就只剩常数项在驱动)。实测中,外推超过输入长度 3~5 倍后,序列曲线往往快速趋平或陷入周期性抖动,已不具备价格行为学上的解读意义。 外汇与贵金属市场高杠杆、高波动,这种远端预测仅可作模型边界的压力测试,切勿直接用于实盘方向判断。

CArima 类的模型持久化与准则选型

父类 CPowellsMethod 的 Optimize() 现在改成 protected,外部无法直接调,连带 CArima 也继承这个限制;对应头文件已重命名为 Powells.mqh。这一改动不影响训练逻辑,但如果你之前在 EA 里直接调过 Optimize 做外部寻优,编译会直接报错。 新加的 SaveModel 和 LoadModel 让模型能落盘复用。SaveModel 接收字符串模型名,在终端\Common\Files\models 目录写二进制 .model 文件,里面含模型阶数、参数(训练后)、以及平方误差总和 sse;LoadModel 按名读回全部属性。两个方法都返回 bool,失败信息进日志,方便排查路径权限问题。 GetModelName 在模型未保存时返回空串,保存后回传当时设的名称。BIC 与 AIC 是全新统计方法:BIC 基于 sse 算贝叶斯信息准则,AIC 算赤池信息准则,都用于平衡拟合优度与复杂度——BIC 比 AIC 更偏简单模型,AIC 相对容忍复杂结构。两者须在训练后、训练数据仍加载时立即调用,模型重新初始化后数据不可用,准则算不出会返 0。 GetMinModelInputs 能查模型最小输入条数,Summary 改为直接把属性打印到终端而不再返字符串。外汇与贵金属市场波动剧烈、杠杆风险高,用这类模型做预测时务必先在小周期离线验证再上实盘。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//-----------------------------------------------------------------------------------</span>
<span class="comment">class=class="str">"cmt">// Minimization of Functions.</span>
<span class="comment">class=class="str">"cmt">// Unconstrained Powell’s Method.</span>
<span class="comment">class=class="str">"cmt">// References:</span>
<span class="comment">class=class="str">"cmt">// class="num">1. Numerical Recipes in C. The Art of Scientific Computing.</span>
<span class="comment">class=class="str">"cmt">//-----------------------------------------------------------------------------------</span>
<span class="keyword">class</span> PowellsMethod:<span class="keyword">class="kw">public</span> CObject
&nbsp;&nbsp;{
<span class="keyword">class="kw">protected</span>:
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;P[],Xi[];
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Pcom[],Xicom[],Xt[];
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Pt[],Ptt[],Xit[];
&nbsp;&nbsp; <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; N;
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Fret;
&nbsp;&nbsp; <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Iter;
&nbsp;&nbsp; <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ItMaxPowell;
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FtolPowell;
&nbsp;&nbsp; <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ItMaxBrent;
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;FtolBrent;
&nbsp;&nbsp; <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MaxIterFlag;
&nbsp;&nbsp; <span style="background-class="type">color:rgb(class="num">249, class="num">204, class="num">202);"><span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Optimize(<span class="keyword">class="type">class="kw">double</span> &amp;p[],<span class="keyword">class="type">int</span> n=<span class="number">class="num">0</span>);</span>
<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;PowellsMethod(<span class="keyword">class="type">void</span>);
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SetItMaxPowell(<span class="keyword">class="type">int</span> n)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { ItMaxPowell=n; }
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SetFtolPowell(<span class="keyword">class="type">class="kw">double</span> er)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ FtolPowell=er; }
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SetItMaxBrent(<span class="keyword">class="type">int</span> n)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ ItMaxBrent=n;&nbsp;&nbsp;}
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SetFtolBrent(<span class="keyword">class="type">class="kw">double</span> er)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { FtolBrent=er;&nbsp;&nbsp;}
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;GetFret(<span class="keyword">class="type">void</span>)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <span class="keyword">class="kw">return</span>(Fret);&nbsp;&nbsp;}
&nbsp;&nbsp; <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GetIter(<span class="keyword">class="type">void</span>)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <span class="keyword">class="kw">return</span>(Iter);&nbsp;&nbsp;}
<span class="keyword">class="kw">private</span>:
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;powell(<span class="keyword">class="type">void</span>);
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;linmin(<span class="keyword">class="type">void</span>);

常见问题

打开对应品种图表,加载自带 ARIMA 示例脚本,设置 p/d/q 参数后运行,终端会输出预测线;先拿历史数据回看再上实盘。
基础篇建议至少 100 根以上收盘价,过短会令系数估计不稳,预测区间容易失真。
小布可在品种页调用预测模块,自动给出多步预测并提示远程预测退化概率,你只需看结论。
一阶差分用当期减前一期,首根无前值被丢弃,序列长度天然少 1,画图时注意对齐。
持久化时按 AIC 或 BIC 存最优阶数,重载前确认准则一致,否则可能加载次优模型。