量化交易软件:赫兹量化中使用指数平滑法进行时间序列预测
为了编译指标,我们需要用到同一目录下的 IndicatorES.mq5、CIndicatorES.mqh 和 PowellsMethod.mqh。这些文件都可以在本文末尾的 files2.zip 档案中找到。
我们来刷新定义了该指标制定过程中所用指数平滑模型的方程,即呈线性衰减的增长模型。
该指标唯一的输入参数就是用于确定区间长度的值,将根据该值来优化模型参数以及选择初始值(研究区间)。确定某给定区间模型参数的最优值及所需的计算后,就会生成预测、置信区间以及与提前一步预测相对应的线条。每个新柱处的参数都会被优化,并会做出相应预测。
由于要更新所述指标,所以我们会利用本文末尾处 Files2.zip 档案中的测试序列来评估变化结果。档案目录 \Dataset2 中的文件包含已保存的 EURUSD、USDCHF、USDJPY 报价及美元指数 DXY。其中每一个都针对 M1、H1 与 D1 三种时间框架予以提供。在文件中保存 "open" 值时,应让最新值位于文件末尾。每个文件都包含 1200 个元素。
预测误差将通过计算“平均绝对百分比误差” (MAPE) 系数进行评估

我们将 12 个测试序列中的每个序列都划分为 50 个重叠区间,每个区间都包含 80 个元素,并计算其中每个元素的 MAPE 值。通过这种方式获取的评估平均值,将会被用作与对比指标相关的一个预测误差指数。提前两步和三步预测误差的 MAPE 值,也以同样的方式进行计算。此类平均估算值还会进一步表示如下:
MAPE1 – 提前一步预测误差的平均估算值;
MAPE2 – 提前两步预测误差的平均估算值;
MAPE3 – 提前三步预测误差的平均估算值;
MAPE1-3 – 平均值 (MAPE1+MAPE2+MAPE3)/3。
计算 MAPE 值时,每一步都会用绝对预测误差值除以序列的当前值。为在此过程中避免被零除或得到负值,要求输入序列仅取非零正值,就像在本例中一样。
初始指标的估算值如表 1 所示。
MAPE1 MAPE2 MAPE3 MAPE1-3 IndicatorES 0.2099 0.2925 0.3564 0.2863
表 1. 初始指标预测误差估算值
表 1 中所示数据是利用 Errors_IndicatorES.mq5 脚本(来自本文末尾的 files2.zip 档案)获取的。要编译并运行此脚本,则 CIndicatorES.mqh 和 PowellsMethod.mqh 必须与 Errors_IndicatorES.mq5 同处一个目录下,且输入序列位于 Files\Dataset2\ 目录下。
获取预测误差的初始估算值后,现在就可以继续升级研究中的指标了。
2. 优化准则
《使用指数平滑法进行时间序列预测》文中所述的初始指标中的模型参数,均通过提前一步对预测误差平方和进行最小化的方法进行确定。针对提前一步预测进行优化的模型参数可能不会产生提前多步预测的最小误差,这似乎比较符合逻辑。当然,最好能够将提前 10 到 12 步预测的误差降至最低,但要在给定的研究序列范围内获得满意的预测结果,却是不可能完成的任务。
从现实来看,在优化模型参数时,我们会使用提前一、二、三步预测误差的平方和,将其用于指标的第一次升级。误差的平均数量可能有望在预测前三步的范围内实现某种程度上的降低。
显而易见,初始指标的此类升级并不涉及其主体结构原理,而只是更改参数优化准则而已。因此,我们不能指望预测精确度能提高数倍,尽管提前两步和三步预测误差的数量应当下降一点。
为了对比预测结果,我们创建了 CMod1 类,其类似于前文提到的带有修改目标函数 func 的 CIndicatorES 类。
初始 CIndicatorES 类的 func 函数:
double CIndicatorES::func(const double &p[]) { int i; double s,t,alp,gam,phi,k1,k2,k3,e,sse,ae,pt; s=p[0]; t=p[1]; alp=p[2]; gam=p[3]; phi=p[4]; k1=1; k2=1; k3=1; if (alp>0.95){k1+=(alp-0.95)*200; alp=0.95;} // Alpha > 0.95 else if(alp<0.05){k1+=(0.05-alp)*200; alp=0.05;} // Alpha < 0.05 if (gam>0.95){k2+=(gam-0.95)*200; gam=0.95;} // Gamma > 0.95 else if(gam<0.05){k2+=(0.05-gam)*200; gam=0.05;} // Gamma < 0.05 if (phi>1.0 ){k3+=(phi-1.0 )*200; phi=1.0; } // Phi > 1.0 else if(phi<0.05){k3+=(0.05-phi)*200; phi=0.05;} // Phi < 0.05 sse=0; for(i=0;i<Dlen;i++) { e=Dat[i]-(s+phi*t); sse+=e*e; ae=alp*e; pt=phi*t; s=s+pt+ae; t=pt+gam*ae; } return(Dlen*MathLog(k1*k2*k3*sse)); }
经过一些修改之后,func 函数的现状如下:
double CMod1::func(const double &p[]) { int i; double s,t,alp,gam,phi,k1,k2,k3,e,err,ae,pt,phi2,phi3,a; s=p[0]; t=p[1]; alp=p[2]; gam=p[3]; phi=p[4]; k1=1; k2=1; k3=1; if (alp>0.95){k1+=(alp-0.95)*200; alp=0.95; // Alpha > 0.95 else if(alp<0.05){k1+=(0.05-alp)*200; alp=0.05;} // Alpha < 0.05 if (gam>0.95){k2+=(gam-0.95)*200; gam=0.95;} // Gamma > 0.95 else if(gam<0.05){k2+=(0.05-gam)*200; gam=0.05;} // Gamma < 0.05 if (phi>1.0 ){k3+=(phi-1.0 )*200; phi=1.0; } // Phi > 1.0 else if(phi<0.05){k3+=(0.05-phi)*200; phi=0.05;} // Phi < 0.05 phi2=phi+phi*phi; phi3=phi2+phi*phi*phi; err=0; for(i=0;i<Dlen-2;i++) { e=Dat[i]-(s+phi*t); err+=e*e; a=Dat[i+1]-(s+phi2*t); err+=a*a; a=Dat[i+2]-(s+phi3*t); err+=a*a; ae=alp*e; pt=phi*t; s=s+pt+ae; t=pt+gam*ae; } e=Dat[Dlen-2]-(s+phi*t); err+=e*e; a=Dat[Dlen-1]-(s+phi2*t); err+=a*a; ae=alp*e; pt=phi*t; s=s+pt+ae; t=pt+gam*ae; a=Dat[Dlen-1]-(s+phi*t); err+=a*a; return(k1*k2*k3*err); }
现在,计算该对象函数时就会用到提前一、二、三步预测误差的平方和。
而且,基于此类开发的 Errors_Mod1.mq5 脚本允许估算预测误差,这类似于曾经提到过的 Errors_IndicatorES.mq5 脚本功能。CMod1.mqh 与 Errors_Mod1.mq5 均位于本文末尾的 files2.zip 档案中。
表 2 显示了初始及升级版的预测误差估算值。
MAPE1 MAPE2 MAPE3 MAPE1-3 IndicatorES 0.2099 0.2925 0.3564 0.2863 Mod1 0.2144 0.2898 0.3486 0.2842
表 2. 预测误差估算值的对比
可以看出,误差系数 MAPE2 和 MAPE3 以及平均值 MAPE1-3 确实要比研究中的序列略低一些。所以,我们保存了这一版本,并继续进一步修改我们的指标。
3. 平滑过程中的参数调整
根据输入序列的当前值更改平滑参数,这一想法并不新颖,也并非原创,其目的是希望能调整平滑系数,以便其在给定的输入序列性质发生变化时仍保持最佳状态。调整平滑系数的一些方式会在参考文献 [2]、[3] 中进行说明。
为了进一步升级该指标,我们会使用平滑系数呈动态变化的模型,希望使用自适应指数平滑模型来实现指标预测精确度的提升。
遗憾的是,如果在预测算法中使用该模型,则大多数自适应方法都无法始终获得理想结果。选取适当的自适应方法可能过于繁琐且耗时,因此在本例中我们会利用参考文献 [4] 中提供的研究结果,并采用文章 [5] 中讲到的“平滑转换的指数平滑” (STES)。
由于指定文章中已明确说明该方法的实质内容,所以我们暂时无需理会,而只需直接转到模型的方程(请参阅指定文章的开头),同时考虑到自适应平滑系数的使用即可。