以 MQL5 实现 ARIMA 训练算法·进阶篇
📉

以 MQL5 实现 ARIMA 训练算法·进阶篇

(2/3)· 从 ARIMA 数学框架到 MQL5 类封装,多数人在差分阶数和系数初始化上栽跟头

含代码示例偏理论 第 2/3 篇
不少交易者直接把原始价格丢进 ARIMA 拟合,忽略稳态检验,得到的系数只是噪声里的幻觉。差分阶数 d 拍脑袋定,模型要么欠差分留趋势、要么过差分造出假平稳。外汇贵金属杠杆高,这类误用会放大错误信号的概率。

◍ 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 怎么判数据长度与做差分:

MQL5 / C++
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,确认系数显著再上实盘。

MQL5 / C++
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 模型类的公开方法声明,负责把内部训练结果暴露给外部调用。注意 GetMaxArLagGetMinArLag 都先判断 m_ar_order 是否为 0,若未训练直接返回 0,避免越界读 m_arlags 数组。 GetSSE 返回 m_sse,这是拟合残差平方和,数值越低代表模型对历史序列的还原度可能越高,但外汇与贵金属行情受突发消息驱动,低 SSE 不代表样本外预测可靠,属高风险应用。 Fit 有两个重载:一个自动选阶,一个手动传 p,d,q 并可选常数项,后者在调试 EURUSD 小时线时更可控。GetModelParametersGetModelInnovationArrayCopy 把内部数组浅拷出去,调用方拿到的是独立副本,不会污染原模型。 打开 MT5 新建 EA,把这段声明贴进你的 CARIMA 类头文件,先跑 Fit(series, 1, 1, 1)Summary() 输出的阶数和 SSE,再决定是否换手动 lag 数组。

MQL5 / C++
  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); }
};
把系数诊断交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 ARIMA 残差稳态与差分阶数建议,你只需判断信号是否值得跟。

常见问题

常用做法是看自相关图衰减速度,或用 ADF 检验循环增加 d 直到序列稳态;MQL5 里可先写差分函数再接检验,d 一般不过 2。
ARIMA 似然面常非凸且不可导,Powell 方向集搜索不依赖梯度,更容易绕开局部极小,但迭代次数要设上限防卡死。
小布盯盘内置了时间序列诊断模块,品种页会给出稳态判定和阶数参考,不必自己从零写 CArima 类也能对照验证。
偏移量尺度与系数差很多,混进同一最小化易数值病态;通常先去均值再拟合,或单独归一化步长。