利用指数平滑法进行时间序列预测·进阶篇
(2/3)· 跳过预处理直接用货币报价测试 Holt-Brown 模型,看误差到底藏在哪里
用 RelMAE 看穿预测到底比随机游走强多少
给序列建模做一步预测时,先选了参数和初值,接下来就必须回答一个问题:这预测到底准不准。常见做法是看 MSE(均方误差),公式是把每一步误差平方后取平均;但它对偶发大误差特别敏感,因为平方会放大离群值。 改用 MAE(平均绝对误差)能稳一些,不平方只取绝对值。不过 MSE 和 MAE 都只是绝对数,比如算出 MAE=0.03,你根本没法说这算好还是坏,更不能拿去横向比不同品种。 真正能跨序列比的是相对评估:RelMSE 和 RelMAE。思路是用朴素法(假设下一根 K 线等于当前值)当基准,把你的误差估算除以朴素法的误差。比值等于 1 说明跟瞎猜差不多;小于 1 才算真有点用;大于 1 就是还不如直接拿现价填坑。 拿线性增长单参数模型跑了 8 个品种的最近 200 根测试,alpha 取 0.3/0.4/0.5 下 RelMAE 全部大于 1:EURUSD M1 在 alpha=0.5 时为 1.10,XAUUSD M30 在 alpha=0.4 时为 1.12。外汇与贵金属杠杆高、滑点跳空频繁,这类回测只说明该模型在此样本内未战胜朴素法,实盘风险依旧很高。 把提前一步误差换成两步或更多步的误差,同一套 Rel 算法也能估多步预测质量。开 MT5 自己拽一段 EURUSD M1 历史,写个朴素法基准对比,就能复算表 1 的数。
「加法指数平滑的六种展开」
前文提到的过程级别加线性趋势加随机变量的结构,只是指数平滑的起点。把周期和季节分量也作为总和叠进去,就得到了加法模型这一族;与之并列的还有乘法模型,但本篇只啃加法。 简单指数平滑对应 ARIMA(0,1,1);线性增长加法模型用了两个独立平滑参数,分别管级别和趋势,等效 ARIMA(0,2,2);线性衰减增长模型则多一个衰减系数,趋势斜率按该系数在每个预测步递减,文献图 4 显示衰减系数越小趋势失效越快。 把季节分量以总和形式加进上述三个模型,再得三个加法季节模型:简单季节、线性增长季节、衰减增长加线性增长季节。季节模型虽也有 ARIMA 等价式,但实用价值低,此处不展开。 公式标记里四个平滑参数 α、β、γ、φ 都落在 [0:1];p 为季节周期数,m 为提前步数。末个模型公式把六个变量全用上了。若在衰减增长加线性增长季节模型里令 γ=0,季节被忽略;β=0 退化为线性增长,β=φ=0 则成衰减增长;简单指数平滑即 α 外其余为 0。 概率预测区间用文献 [3] 的派生分析法:以全样本大小 n 的提前一步误差平方和均值作方差估计,提前 m 步估计方差按模 p 取 1 或 0 的权重累加,95% 区间限制值据此算出,可称置信区间。短区间测试序列未见稳定循环,故季节初始化示例从略。 下面这段 MQL5 类声明骨架,就是要把上述表达式落到代码里,方便在 MT5 里直接接自己的报价序列跑。
◍ AdditiveES 类的方法与简单模型反而更稳
AdditiveES 类以线性衰减增长模型加线性增长加法季节模型为底子,是所有派生模型里参数最杂的一个。它的 Init 方法必须第一个调用,负责塞入平滑级别、趋势、alpha、gamma、phi、delta 以及季节周期数 nses;注意 Init 本身不支持非零季节指数,只能把季节项置零,要非零得随后用 IniIs(m, is) 单独设。 跑数时每次来新值就调 NewY(y),返回提前一步预测;之后再调 Fcast(m) 拿提前 m 步的预测,且不影响内部平滑状态。VarCoefficient(m) 给出的是预测方差相对一步方差的放大系数,能直接看出远端预测的不确定性膨胀速度。 GetS、GetT、GetF、GetIs 只是受保护变量的读接口,方便外部拿平滑级别、趋势、一步预测和指定序号的季节指数。 复杂模型参数多,直觉上该更准,但实际常相反:参数少的简单模型在多数样本里预测误差方差更小、运行更平。并行跑全部模型后按最低误差加参数惩罚选优,阿凯克信息论准则(AIC)就是这类标准之一,倾向挑出最稳的那个而不是最花哨的。 为验证,原文附带 AdditiveES_Test.mq5 指标,所有平滑参数手设、季节初值恒为零。随着新值流入,季节影响从零慢慢爬到稳态,所需柱数取决于 delta——delta 越小,爬得越慢。指标还画了过去值的一步预测线及 95% 置信带,带宽基于近期 nTest 根柱的一步误差方差,避开初始值不精确的干扰。外汇与贵金属行情跳变频繁,这类指数平滑在外盘高风险环境下仅作概率参考,实盘前务必在 MT5 用历史数据复跑。
class=class="str">"cmt">//----------------------------------------------------------------------------------- class=class="str">"cmt">// AdditiveES.mqh class=class="str">"cmt">// class="num">2011, victorg class=class="str">"cmt">// [MQL5官方文档] class=class="str">"cmt">//----------------------------------------------------------------------------------- class="macro">#class="kw">property copyright "class="num">2011, victorg" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#include <Object.mqh> class=class="str">"cmt">//----------------------------------------------------------------------------------- class=class="str">"cmt">// Forecasting. Exponential smoothing. Additive models. class=class="str">"cmt">// References: class=class="str">"cmt">// class="num">1. Everette S. Gardner Jr. Exponential smoothing: The state of the art – Part II. class=class="str">"cmt">// June class="num">3, class="num">2005. class=class="str">"cmt">// class="num">2. Rob J Hyndman. Forecasting based on state space models for exponential class=class="str">"cmt">// smoothing. class="num">29 August class="num">2002. class=class="str">"cmt">// class="num">3. Rob J Hyndman et al. Prediction intervals for exponential smoothing class=class="str">"cmt">// class="kw">using two new classes of state space models. class="num">30 January class="num">2003. class=class="str">"cmt">//----------------------------------------------------------------------------------- class AdditiveES:class="kw">public CObject { class="kw">protected: class="type">class="kw">double Alpha; class=class="str">"cmt">// Smoothed parameter for the level of the series class="type">class="kw">double Gamma; class=class="str">"cmt">// Smoothed parameter for the trend class="type">class="kw">double Phi; class=class="str">"cmt">// Autoregressive or damping parameter class="type">class="kw">double Delta; class=class="str">"cmt">// Smoothed parameter for seasonal indices class="type">int nSes; class=class="str">"cmt">// Number of periods in the seasonal cycle class="type">class="kw">double S; class=class="str">"cmt">// Smoothed level of the series, computed after last Y is observed class="type">class="kw">double T; class=class="str">"cmt">// Smoothed additive trend
加法型指数平滑的类骨架与初始化逻辑
把季节性拆成加法项的指数平滑模型,在 MT5 里通常以一个 C++ 风格类承载。下面这段声明给出了核心成员:Ises[] 存平滑后的季节指数,p_Ises 是它的移位寄存器指针,F 是原点 t 向后一期的预测值。 构造函数 AdditiveES() 把默认参数定死了:Alpha=0.5、Gamma=0、Delta=0、Phi=1、nSes=1,并把 Ises 数组 resize 后清零。也就是说,不传参时它先当一个无季节项、无阻尼的简单平滑器跑起来。 Init() 才是真正注入状态的地方。它接收水平 s、趋势 t,以及 alpha/gamma/phi/delta/nses 全套系数,且对每个系数做了硬截断——Alpha、Gamma、Phi、Delta 超出 [0,1] 会被夹回边界,nSes 小于 1 强制为 1。最后 F 按 S+Phi*T 算出来返回,这一步决定了首期预测的基准。 别把默认 Alpha 当最优 构造里 Alpha 写死 0.5 只是占位,黄金小时图这类噪声大的品种,直接 0.5 可能让水平线过度追随杂波;进 Init() 前最好先按样本回测调,例如从 0.2 起扫到 0.8 看残差方差。外汇与贵金属杠杆高,模型误差会被放大,任何预测都只是概率倾向,不是方向保证。
class="type">class="kw">double Ises[]; class=class="str">"cmt">// Smoothed seasonal indices class="type">int p_Ises; class=class="str">"cmt">// Pointer for Ises[] shift register class="type">class="kw">double F; class=class="str">"cmt">// Forecast for class="num">1 period ahead from origin t class="kw">public: AdditiveES(); class="type">class="kw">double Init(class="type">class="kw">double s,class="type">class="kw">double t,class="type">class="kw">double alpha=class="num">1,class="type">class="kw">double gamma=class="num">0, class="type">class="kw">double phi=class="num">1,class="type">class="kw">double delta=class="num">0,class="type">int nses=class="num">1); class="type">class="kw">double GetS() { class="kw">return(S); } class="type">class="kw">double GetT() { class="kw">return(T); } class="type">class="kw">double GetF() { class="kw">return(F); } class="type">class="kw">double GetIs(class="type">int m); class="type">void IniIs(class="type">int m,class="type">class="kw">double is); class=class="str">"cmt">// Initialization of smoothed seasonal indices class="type">class="kw">double NewY(class="type">class="kw">double y); class=class="str">"cmt">// Next calculating step class="type">class="kw">double Fcast(class="type">int m); class=class="str">"cmt">// m-step ahead forecast class="type">class="kw">double VarCoefficient(class="type">int m); class=class="str">"cmt">// Coefficient for calculating prediction intervals }; class=class="str">"cmt">//----------------------------------------------------------------------------------- class=class="str">"cmt">// Constructor class=class="str">"cmt">//----------------------------------------------------------------------------------- class="type">void AdditiveES::AdditiveES() { Alpha=class="num">0.5; Gamma=class="num">0; Delta=class="num">0; Phi=class="num">1; nSes=class="num">1; ArrayResize(Ises,nSes); ArrayInitialize(Ises,class="num">0); p_Ises=class="num">0; S=class="num">0; T=class="num">0; } class=class="str">"cmt">//----------------------------------------------------------------------------------- class=class="str">"cmt">// Initialization class=class="str">"cmt">//----------------------------------------------------------------------------------- class="type">class="kw">double AdditiveES::Init(class="type">class="kw">double s,class="type">class="kw">double t,class="type">class="kw">double alpha=class="num">1,class="type">class="kw">double gamma=class="num">0, class="type">class="kw">double phi=class="num">1,class="type">class="kw">double delta=class="num">0,class="type">int nses=class="num">1) { S=s; T=t; Alpha=alpha; if(Alpha<class="num">0)Alpha=class="num">0; if(Alpha>class="num">1)Alpha=class="num">1; Gamma=gamma; if(Gamma<class="num">0)Gamma=class="num">0; if(Gamma>class="num">1)Gamma=class="num">1; Phi=phi; if(Phi<class="num">0)Phi=class="num">0; if(Phi>class="num">1)Phi=class="num">1; Delta=delta; if(Delta<class="num">0)Delta=class="num">0; if(Delta>class="num">1)Delta=class="num">1; nSes=nses; if(nSes<class="num">1)nSes=class="num">1; ArrayResize(Ises,nSes); ArrayInitialize(Ises,class="num">0); p_Ises=class="num">0; F=S+Phi*T; class="kw">return(F); } class=class="str">"cmt">//----------------------------------------------------------------------------------- class=class="str">"cmt">// Calculations for the new Y
「加性指数平滑的季节性更新与预测内核」
加性指数平滑(Additive ES)把水平、趋势和季节三项拆开迭代。NewY 函数在每次来一个新观测 y 时,先算误差 e=y-F,再用 Alpha 更新水平 S、用 Gamma 约束趋势 T 的阻尼,同时把季节偏差按 Delta*(1-Alpha)*e 推进环形缓冲 Ises。 GetIs 和 IniIs 负责季节指数的环形存取:用 MathMod(m+p_Ises,nSes) 把任意超前步数 m 映射到当前季节窗口内的下标,初始化时只允许写前 nSes 个槽位。这样多周期(比如日内的 24 小时或周内的 5 个交易日)季节项不会越界。 Fcast 做 m 步向前预测:h 步的水平贡献是 S 加一个等比数列求和 v*T(公比 Phi),再加 GetIs(h) 对应的季节项。当 Phi=1 时 v 正好等于 h,退化为无阻尼线性外推。 VarCoefficient 给出预测区间的系数,不是简单累加。它在 i=1..h-1 上累加 a²,其中 a 同时含趋势项 Alpha*(1+v*Gamma) 和季节项 k*Delta*(1-Alpha);仅当 MathMod(i,nSes)==0 时 k=1,说明跨过一个完整季节周期才计入季节不确定度。h=1 时直接返回 1,即单步方差不放大。 外汇与贵金属价格具备高波动、高杠杆风险,这套平滑内核仅用于辅助刻画序列结构,实盘信号须结合风控与多周期验证。
class="type">class="kw">double AdditiveES::NewY(class="type">class="kw">double y) { class="type">class="kw">double e; e=y-F; S=S+Phi*T+Alpha*e; T=Phi*T+Alpha*Gamma*e; Ises[p_Ises]=Ises[p_Ises]+Delta*(class="num">1-Alpha)*e; p_Ises++; if(p_Ises>=nSes)p_Ises=class="num">0; F=S+Phi*T+GetIs(class="num">0); class="kw">return(F); } class=class="str">"cmt">// Return smoothed seasonal index class="type">class="kw">double AdditiveES::GetIs(class="type">int m) { if(m<class="num">0)m=class="num">0; class="type">int i=(class="type">int)MathMod(m+p_Ises,nSes); class="kw">return(Ises[i]); } class=class="str">"cmt">// Initialization of smoothed seasonal indices class="type">void AdditiveES::IniIs(class="type">int m,class="type">class="kw">double is) { if(m<class="num">0)m=class="num">0; if(m<nSes) { class="type">int i=(class="type">int)MathMod(m+p_Ises,nSes); Ises[i]=is; } } class=class="str">"cmt">// m-step-ahead forecast class="type">class="kw">double AdditiveES::Fcast(class="type">int m) { class="type">int i,h; class="type">class="kw">double v,v1; if(m<class="num">1)h=class="num">1; else h=m; v1=class="num">1; v=class="num">0; for(i=class="num">0;i<h;i++){v1=v1*Phi; v+=v1;} class="kw">return(S+v*T+GetIs(h)); } class=class="str">"cmt">// Coefficient for calculating prediction intervals class="type">class="kw">double AdditiveES::VarCoefficient(class="type">int m) { class="type">int i,h; class="type">class="kw">double v,v1,a,sum,k; if(m<class="num">1)h=class="num">1; else h=m; if(h==class="num">1)class="kw">return(class="num">1); v=class="num">0; v1=class="num">1; sum=class="num">0; for(i=class="num">1;i<h;i++) { v1=v1*Phi; v+=v1; if((class="type">int)MathMod(i,nSes)==class="num">0)k=class="num">1; else k=class="num">0; a=Alpha*(class="num">1+v*Gamma)+k*Delta*(class="num">1-Alpha); sum+=a*a; } class="kw">return(class="num">1+sum); }
◍ 把阻尼指数平滑接进主图
前面把加性指数平滑的算法拆开了,这一节直接看指标壳子怎么把模型挂上 MT5 主图。它声明了 4 个绘图缓冲:历史拟合线、预测线、以及上下两条预测区间线,颜色分别用了 DodgerBlue、DarkOrange 和 CadetBlue,视觉上能一眼区分已发生与未发生的部分。 核心参数里 Alpha=0.2 管水平平滑、Gamma=0.2 管趋势平滑、Phi=0.8 是阻尼系数,意味着趋势外推会被逐步收敛而不是直线延长。nHist=250 要求历史柱不少于 100,nTest=150 落在 50 到 nHist 之间做样本内检验,nFore=12 给出至少 2 根的未来 horizon。 代码通过 #include "AdditiveES.mqh" 把前面说的类拉进来,实例化一个 fc 对象,再由 NHist、NFore、NTest 三个整型变量接管输入值。你把这个 mqh 和 mq5 放同一目录,改 Phi 到 0.9 或 0.7,能在 EURUSD 的 H1 上直观看到预测线弯曲程度的差异,外汇品种杠杆高、信号失效也快,仅作概率参考。
class="macro">#class="kw">property copyright "class="num">2011, victorg" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property indicator_chart_window class="macro">#class="kw">property indicator_buffers class="num">4 class="macro">#class="kw">property indicator_plots class="num">4 class="macro">#class="kw">property indicator_label1 "History" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 clrDodgerBlue class="macro">#class="kw">property indicator_style1 STYLE_SOLID class="macro">#class="kw">property indicator_width1 class="num">1 class="macro">#class="kw">property indicator_label2 "Forecast" class=class="str">"cmt">// Forecast class="macro">#class="kw">property indicator_type2 DRAW_LINE class="macro">#class="kw">property indicator_color2 clrDarkOrange class="macro">#class="kw">property indicator_style2 STYLE_SOLID class="macro">#class="kw">property indicator_width2 class="num">1 class="macro">#class="kw">property indicator_label3 "PInterval+" class=class="str">"cmt">// Prediction interval class="macro">#class="kw">property indicator_type3 DRAW_LINE class="macro">#class="kw">property indicator_color3 clrCadetBlue class="macro">#class="kw">property indicator_style3 STYLE_SOLID class="macro">#class="kw">property indicator_width3 class="num">1 class="macro">#class="kw">property indicator_label4 "PInterval-" class=class="str">"cmt">// Prediction interval class="macro">#class="kw">property indicator_type4 DRAW_LINE class="macro">#class="kw">property indicator_color4 clrCadetBlue class="macro">#class="kw">property indicator_style4 STYLE_SOLID class="macro">#class="kw">property indicator_width4 class="num">1 class="type">class="kw">double HIST[]; class="type">class="kw">double FORE[]; class="type">class="kw">double PINT1[]; class="type">class="kw">double PINT2[]; class="kw">input class="type">class="kw">double Alpha=class="num">0.2; class=class="str">"cmt">// Smoothed parameter for the level class="kw">input class="type">class="kw">double Gamma=class="num">0.2; class=class="str">"cmt">// Smoothed parameter for the trend class="kw">input class="type">class="kw">double Phi=class="num">0.8; class=class="str">"cmt">// Damping parameter class="kw">input class="type">class="kw">double Delta=class="num">0; class=class="str">"cmt">// Smoothed parameter for seasonal indices class="kw">input class="type">int nSes=class="num">1; class=class="str">"cmt">// Number of periods in the seasonal cycle class="kw">input class="type">int nHist=class="num">250; class=class="str">"cmt">// History bars, nHist>=class="num">100 class="kw">input class="type">int nTest=class="num">150; class=class="str">"cmt">// Test interval, class="num">50<=nTest<nHist class="kw">input class="type">int nFore=class="num">12; class=class="str">"cmt">// Forecasting horizon, nFore>=class="num">2 class="macro">#include "AdditiveES.mqh" AdditiveES fc; class="type">int NHist; class=class="str">"cmt">// history bars class="type">int NFore; class=class="str">"cmt">// forecasting horizon class="type">int NTest; class=class="str">"cmt">// test interval class="type">class="kw">double ALPH; class=class="str">"cmt">// alpha