以 MQL5 实现 ARIMA 训练算法·进阶篇
(2/3)· 从 ARIMA 数学框架到 MQL5 类封装,多数人在差分阶数和系数初始化上栽跟头
◍ ARMA 系数拟合从哪里开始算
带 p+q 个系数的 ARMA 模型不能凭空定参,得拿已知序列跑预测、再和真实值比对,用误差平方和最小那条作为最优系数。这一步本质是最小二乘思路,但预测窗口不能无限往前推。 只要模型里有 AR 项,就必须先丢掉前面等于最大 AR 滞后阶数的样本。拿一个含 AR 与 MA 项的示例说,最大 AR 滞后到 6,那就只能从时隙 7 起做预测,更早的位置会引用序列开始前的未知值,纯属无效。 第 7 个时隙的首个预测先用人工初始 AR 系数算,预测减真实得到该时隙创新值;之后若模型含 MA 项,等对应滞后创新值齐了就并进预测,没齐则 MA 项暂置零。纯 MA 模型类似,但若要恒定偏移量,就初始化成序列均值。 已知序列长度必须不少于模型按顺序铺开所需的样本量,项越多、滞后越大,要的历史值越多。系数最终用 Powell 法做全局最小化来优化,该算法在时间序列预测文章里已有实现细节,外汇与贵金属行情受杠杆与跳空影响大,拟合结果仅代表历史样本下的概率倾向,实盘前请在 MT5 用策略测试器复跑。
「CArima 怎么把 ARIMA 模型装进类里」
在 Arima.mqh 里,ARIMA 的训练逻辑被收进 CArima 类。它提供两个构造函数:默认版直接建一个带恒定偏移的纯 AR(1);参数化版则让你用 p、d、q、use_const_term 四个量精确控模型——p 是 AR 项数,d 是差分阶数,q 是 MA 项数,布尔值决定要不要常数项。 光靠构造还不够灵活,类里重载了两个 Fit()。第一个只吃数据序列,第二个再追加 p/d/q/use_const_term 四个参数,调用后会覆盖之前指定的模型,并默认各项滞后相邻。两者都假定传入序列未差分,差分按模型参数内部处理,返回布尔值表示训练成败。 想打破「相邻滞后」限制,用 SetARLags() 和 SetMALags() 传数组即可,元素值 ≥1,数组长度须匹配对应阶数。比如要复刻 y(t)=AR1*y(t-2)+AR2*y(t-5)+MA1*E(t-1)+MA2*E(t-3)+E(t) 这个 ARMA(2,2) 非相邻滞后结构,就给 AR 传 [2,5]、MA 传 [1,3]。 训练成功后,GetModelParameters() 把最优系数写进数组,顺序是 AR 项→常数项→MA 项;GetModelInnovation() 取拟合残差,GetSSE() 给误差平方和,IsTrained() 查状态。类继承自 PowellsMethod,所以 Powell 优化器的参数也能调。 下面拆一段构造函数与单参数 Fit() 的源码,看清默认 AR(1) 如何初始化、以及 Fit 怎么判数据长度与做差分:
CArima::CArima(class="type">void) { m_ar_order=class="num">1; class=class="str">"cmt">// 默认自回归阶数设为1 class=class="str">"cmt">//--- ArrayResize(m_arlags,m_ar_order); class=class="str">"cmt">// 给AR滞后数组分配1个位置 for(class="type">uint i=class="num">0; i<m_ar_order; i++) m_arlags[i]=i+class="num">1; class=class="str">"cmt">// 滞后填为1,即相邻 class=class="str">"cmt">//--- m_ma_order=m_diff_order=class="num">0; class=class="str">"cmt">// 无MA项、无差分 m_istrained=false; class=class="str">"cmt">// 未训练标记 m_const=true; class=class="str">"cmt">// 启用常数项 ArrayResize(m_model,m_ar_order+m_ma_order+m_const); class=class="str">"cmt">// 模型参数数组大小=class="num">1+class="num">0+class="num">1 ArrayInitialize(m_model,class="num">0); class=class="str">"cmt">// 参数初值全0 } CArima::CArima(class="kw">const class="type">uint p,class="kw">const class="type">uint d,class="kw">const class="type">uint q,class="type">bool use_const_term=true) { m_ar_order=m_ma_order=m_diff_order=class="num">0; class=class="str">"cmt">// 先清零 if(d) m_diff_order=d; class=class="str">"cmt">// 设定差分阶数 if(p) { m_ar_order=p; class=class="str">"cmt">// 设定AR阶数 ArrayResize(m_arlags,p); class=class="str">"cmt">// 分配AR滞后数组 for(class="type">uint i=class="num">0; i<m_ar_order; i++) m_arlags[i]=i+class="num">1; class=class="str">"cmt">// 默认相邻滞后 } if(q) { m_ma_order=q; class=class="str">"cmt">// 设定MA阶数 ArrayResize(m_malags,q); class=class="str">"cmt">// 分配MA滞后数组 for(class="type">uint i=class="num">0; i<m_ma_order; i++) m_malags[i]=i+class="num">1; class=class="str">"cmt">// 默认相邻滞后 } m_istrained=false; class=class="str">"cmt">// 未训练 m_const=use_const_term; class=class="str">"cmt">// 是否常数项由入参定 ArrayResize(m_model,m_ar_order+m_ma_order+m_const); class=class="str">"cmt">// 模型数组尺寸 ArrayInitialize(m_model,class="num">0); class=class="str">"cmt">// 初值0 } class="type">bool CArima::Fit(class="type">class="kw">double &input_series[]) { class="type">uint input_size=ArraySize(input_series); class=class="str">"cmt">// 序列长度 class="type">uint in = m_ar_order+ (m_ma_order*class="num">2); class=class="str">"cmt">// 所需最小长度经验值 if(input_size<=class="num">0 || input_size<in) class=class="str">"cmt">// 长度不足直接失败 class="kw">return false; if(m_diff_order) difference(m_diff_order,input_series,m_differenced,m_leads); class=class="str">"cmt">// 按阶差分 else ArrayCopy(m_differenced,input_series); class=class="str">"cmt">// 不差分则拷贝 ArrayResize(m_innovation,ArraySize(m_differenced)); class=class="str">"cmt">// 残差数组 class="type">class="kw">double parameters[]; ArrayResize(parameters,(m_const)?m_ar_order+m_ma_order+class="num">1:m_ar_order+m_ma_order); class=class="str">"cmt">// 待优化参数数 ArrayInitialize(parameters,class="num">0.0); class=class="str">"cmt">// 初值0 class="type">int iterations = Optimize(parameters); class=class="str">"cmt">// Powell优化 if(iterations>class="num">0) m_istrained=true; class=class="str">"cmt">// 迭代成功标记已训练 else class="kw">return false; class=class="str">"cmt">// 否则失败 m_sse=PowellsMethod::GetFret(); class=class="str">"cmt">// 记录误差平方和 ArrayCopy(m_model,parameters); class=class="str">"cmt">// 存最优参数 class="kw">return true; }
自定义滞后阶数打破等距假设
标准 ARIMA 在 Fit 里默认把 AR 和 MA 的滞后阶数按 1,2,3… 等距排布:当 p=2 时 m_arlags 被填成 {1,2},q=2 时 m_malags 填成 {1,2}。这套逻辑在大部分平稳序列上够用,但贵金属 1 小时线常出现跳空导致的非等距记忆,硬套等距滞后会吃掉真实周期。 CArima 类留了 SetARLags / SetMALags 两个口子,允许你显式指定滞后位置。下面这段实例化后强行把 AR 滞后钉在 {2,5}、MA 滞后钉在 {1,3},再打印模型摘要,等于告诉优化器「别自动排,按我的节奏来」。 [CODE]…[/CODE] 注意 GetMaxArLag 的实现:它返回 m_arlags 数组里的最大值而非 m_ar_order 本身。当你塞了 {2,5} 这类非连续滞后,最大滞后就是 5,差分和残差缓冲区的长度得按 5 而不是按阶数 2 来分配,否则 integrate 回推时会越界。外汇与贵金属杠杆高、跳空频繁,自定义滞后前先在 MT5 用历史样本跑一遍 Summary,确认系数显著再上实盘。
class="type">bool CArima::Fit(class="type">class="kw">double&input_series[],class="kw">const class="type">uint p,class="kw">const class="type">uint d,class="kw">const class="type">uint q,class="type">bool use_const_term=true) { m_ar_order=m_ma_order=m_diff_order=class="num">0; if(d) m_diff_order=d; if(p) { m_ar_order=p; ArrayResize(m_arlags,p); for(class="type">uint i=class="num">0; i<m_ar_order; i++) m_arlags[i]=i+class="num">1; } if(q) { m_ma_order=q; ArrayResize(m_malags,q); for(class="type">uint i=class="num">0; i<m_ma_order; i++) m_malags[i]=i+class="num">1; } m_istrained=false; m_const=use_const_term; ZeroMemory(m_innovation); ZeroMemory(m_model); ZeroMemory(m_differenced); ArrayResize(m_model,m_ar_order+m_ma_order+m_const); ArrayInitialize(m_model,class="num">0); class="kw">return Fit(input_series); } CArima arima(class="num">2,class="num">0,class="num">2); class="type">uint alags [class="num">2]= {class="num">2,class="num">5}; class="type">uint mlags [class="num">2]= {class="num">1,class="num">3}; if(arima.SetARLags(alags) && arima.SetMALags(mlags)) Print(arima.Summary()); class CArima:class="kw">public PowellsMethod { class="kw">private: class="type">bool m_const,m_istrained; class="type">uint m_diff_order,m_ar_order,m_ma_order; class="type">uint m_arlags[],m_malags[]; class="type">class="kw">double m_model[],m_sse; class="type">class="kw">double m_differenced[],m_innovation[],m_leads[]; class="type">void difference(class="kw">const class="type">uint difference_degree, class="type">class="kw">double &data[], class="type">class="kw">double &differenced[], class="type">class="kw">double &leads[]); class="type">void integrate(class="type">class="kw">double &differenced[], class="type">class="kw">double &leads[], class="type">class="kw">double &integrated[]); class="kw">virtual class="type">class="kw">double func(class="kw">const class="type">class="kw">double &p[]); class="kw">public : CArima(class="type">void); CArima(class="kw">const class="type">uint p,class="kw">const class="type">uint d, class="kw">const class="type">uint q,class="type">bool use_const_term=true); ~CArima(class="type">void); class="type">uint GetMaxArLag(class="type">void) { if(m_ar_order) class="kw">return m_arlags[ArrayMaximum(m_arlags)]; else class="kw">return class="num">0;}
◍ ARIMA 封装类的存取接口
上面这段是 ARIMA 模型类的公开方法声明,负责把内部训练结果暴露给外部调用。注意 GetMaxArLag 和 GetMinArLag 都先判断 m_ar_order 是否为 0,若未训练直接返回 0,避免越界读 m_arlags 数组。
GetSSE 返回 m_sse,这是拟合残差平方和,数值越低代表模型对历史序列的还原度可能越高,但外汇与贵金属行情受突发消息驱动,低 SSE 不代表样本外预测可靠,属高风险应用。
Fit 有两个重载:一个自动选阶,一个手动传 p,d,q 并可选常数项,后者在调试 EURUSD 小时线时更可控。GetModelParameters 与 GetModelInnovation 用 ArrayCopy 把内部数组浅拷出去,调用方拿到的是独立副本,不会污染原模型。
打开 MT5 新建 EA,把这段声明贴进你的 CARIMA 类头文件,先跑 Fit(series, 1, 1, 1) 看 Summary() 输出的阶数和 SSE,再决定是否换手动 lag 数组。
class="type">uint GetMinArLag(class="type">void) { if(m_ar_order) class="kw">return m_arlags[ArrayMinimum(m_arlags)]; else class="kw">return class="num">0;} class="type">uint GetMaxMaLag(class="type">void) { if(m_ma_order) class="kw">return m_malags[ArrayMaximum(m_malags)]; else class="kw">return class="num">0;} class="type">uint GetMinMaLag(class="type">void) { if(m_ma_order) class="kw">return m_malags[ArrayMinimum(m_malags)]; else class="kw">return class="num">0;} class="type">uint GetArOrder(class="type">void) { class="kw">return m_ar_order; } class="type">uint GetMaOrder(class="type">void) { class="kw">return m_ma_order; } class="type">uint GetDiffOrder(class="type">void) { class="kw">return m_diff_order;} class="type">bool IsTrained(class="type">void) { class="kw">return m_istrained; } class="type">class="kw">double GetSSE(class="type">void) { class="kw">return m_sse; } class="type">uint GetArLagAt(class="kw">const class="type">uint shift); class="type">uint GetMaLagAt(class="kw">const class="type">uint shift); class="type">bool SetARLags(class="type">uint &ar_lags[]); class="type">bool SetMALags(class="type">uint &ma_lags[]); class="type">bool Fit(class="type">class="kw">double &input_series[]); class="type">bool Fit(class="type">class="kw">double &input_series[],class="kw">const class="type">uint p,class="kw">const class="type">uint d, class="kw">const class="type">uint q,class="type">bool use_const_term=true); class="type">class="kw">string Summary(class="type">void); class="type">void GetModelParameters(class="type">class="kw">double &out_array[]) { ArrayCopy(out_array,m_model); } class="type">void GetModelInnovation(class="type">class="kw">double &out_array[]) { ArrayCopy(out_array,m_innovation); } };