使用计量经济学方法分析图表·进阶篇
📉

使用计量经济学方法分析图表·进阶篇

(2/3)· 价格序列的肥尾与杠杆效应常被肉眼误读,本篇用非线性模型给出可量化的诊断路径

实战向 第 2/3 篇
把高波动当成随机噪声、用肉眼判断拐点,是多数交易者误判风险的根源。外汇与贵金属杠杆高,波动集群一旦被忽略,仓位就容易撞上肥尾。本文从时域方法切入,先补模型认知再谈落地。

「用对数回报替代价格做序列研究」

做时序建模时,直接预测价格序列往往不如预测回报序列。价格变化的对数(即连续复合回报)定义为 ln(P_t / P_{t-1}),其中 P_t 为 t 时刻价格、P_{t-1} 为前一时刻价格。回报序列相比原始价格具备更平稳的统计特性,外盘贵金属与外汇的高杠杆品种尤其明显,用回报能削弱异方差带来的干扰。 回报数组天然比主序列少一个元素,所以指标从第 1 个索引(第二个柱)才开始计算,第 0 位固定为 0。下面这段 ReturnsIndicator 的 OnCalculate 就是按这个逻辑写的,新手可对照自定义指标文档在 MT5 里建一个 mq5 验证。 别把首元素当有效信号 首根 K 线的回报缓冲写 0 只是占位,回测或采样时若从 i=0 起算,会把无效 0 带进均值/方差,扭曲分布估计。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Custom indicator iteration function                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(const class="type">int rates_total,      class=class="str">"cmt">// size of the array price[]
                const class="type">int prev_calculated,  class=class="str">"cmt">// number of bars available at the previous call
                const class="type">int begin,            class=class="str">"cmt">// index of the array price[] the reliable data starts from
                const class="type">class="kw">double& price[])      class=class="str">"cmt">// array for the calculation itself
  {
class=class="str">"cmt">//---
   class="type">int start;
   if(prev_calculated<class="num">2) start=class="num">1;   class=class="str">"cmt">// start filling ReturnsBuffer[] from the class="num">1-st index, not class="num">0.
   else start=prev_calculated-class="num">1;    class=class="str">"cmt">// set &class="macro">#x27;start&class="macro">#x27; equal to the last index in the arrays
   for(class="type">int i=start;i<rates_total;i++)
     {
      ReturnsBuffer[i]=MathLog(price[i]/price[i-class="num">1]);
     }
class=class="str">"cmt">//--- class="kw">return value of prev_calculated for next call
   class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

用 Ljung-Box Q 检验挑出序列自相关

判定一段价格回报序列该不该上 GARCH 类模型,先得过统计检验这关。Ljung-Box-Pierce Q 检验干的事很直接:看序列自相关是随机噪声还是真有结构关联。同一序列在 t1、t2 的取值概率相连,相邻时刻错开一个单位去比,这种偏移就叫滞后,滞后值可以是任意正数。 MQL5 和标准 C++ 一样,没有现成的统计计算库。作者没借 Matlab 或 STATISTICA 偷懒,而是手搓了一个复数类 CComplex 来支持 Q 检验里的复数运算——因为 MQL5 不能重载算术运算符,所以 a+b 写法编译器会报错,只能调 c.opPlus(a,b) 这类方法。类外还挂了三个引用传参的辅助函数:getComplexArr、setComplexArr、setComplexArr2,专门在头文件 Complex_class.mqh 里兜底数组转换。 傅里叶变换走的是 CFFT 类(FFT_class.mqh),硬性要求是数组长度凑成 2^N。比如原始长度 n=73585,nextpow2 返回 17,因为 2^17=131072 才盖住;nFFT 再翻一倍取 2^(17+1)=262144,多出来的元素全填 0。自相关函数算完指定滞后的 ACF,就能喂进 Q 检验。 检验函数吐出的是逻辑数组:某滞后位为 false,代表零假设成立、无自相关;为 true 则拒绝零假设、存在自相关。常见做法是把第 10、15、20 个滞后打包看。若原始回报序列没显著自相关,就改测回报平方——相当于人为放大非线性成分,在原有置信限内再确认一遍。 实盘验证路径:MT5 里开 USDJPY 线图加载 ReturnsIndicator,跑 GarchTest 脚本(图解像素按屏设,例 700*250)。H4 上原始回报 ACF 蓝柱零星越红限,平方序列蓝柱压倒性占优,说明平方序列自相关明确。窗口底部给检验耗时,true 标志越多,该品种越像非线性 GARCH 的适用对象;全 false 就退回到更简模型。外汇与贵金属波动剧烈,检验结果只提示概率倾向,实盘仍需自担高风险。

MQL5 / C++
class Complex
  {
class="kw">public:
   class="type">class="kw">double               re,im; class=class="str">"cmt">//re -real component of the complex number, im - imaginary
class="kw">public:
   class="type">void                Complex(){};                                        class=class="str">"cmt">//class="kw">default constructor
   class="type">void                setComplex(class="type">class="kw">double rE,class="type">class="kw">double iM){re=rE; im=iM;}; class=class="str">"cmt">//set method(class="num">1-st variant)
   class="type">void                setComplex(class="type">class="kw">double rE){re=rE; im=class="num">0;};           class=class="str">"cmt">//set method(class="num">2-nd variant)
   class="type">void               ~Complex(){};                                        class=class="str">"cmt">//destructor
   class="type">void                opEqual(const Complex &y){re=y.re;im=y.im;};   class=class="str">"cmt">//class="kw">operator=

◍ 复数运算与FFT类的接口骨架

在 MT5 里做频域分析,先得把复数这一层封装利索。上面这段声明把加减乘、共轭和模长归一都拆成了独立方法,其中 opMultEq 还给了两个重载:一个吃复数、一个吃 double 标量,实盘里对振幅做整体缩放会直接调第二个。 CFFT 类才是真正干活的地方。Input[] 和 Output[] 都是 Complex 数组,长度必须自己用 uint N 传进去;Forward 跑正变换,InverseT 带加权还原(Scale 默认 true),InverseF 则不加权(Scale 默认 false),三种路径对应不同的信号重建需求。 setCFFT 同样有两个版本,一种显式给 N,一种靠数组本身长度推断。你在 EA 里初始化时若数组大小固定,用第二版更省事;若每根 K 线重算窗长,第一版更不容易越界。外汇与贵金属波动剧烈,高频 FFT 重算有滑点风险,参数先上模拟盘验证。

MQL5 / C++
class="type">void                opPlus(const Complex &x,const Complex &y);   class=class="str">"cmt">//class="kw">operator+
class="type">void                opPlusEq(const Complex &y);                 class=class="str">"cmt">//class="kw">operator+=
class="type">void                opMinus(const Complex &x,const Complex &y); class=class="str">"cmt">//class="kw">operator-
class="type">void                opMult(const Complex &x,const Complex &y);  class=class="str">"cmt">//class="kw">operator*
class="type">void                opMultEq(const Complex &y);                 class=class="str">"cmt">//class="kw">operator*= (class="num">1-st variant)
class="type">void                opMultEq(const class="type">class="kw">double y);                   class=class="str">"cmt">//class="kw">operator*= (class="num">2-nd variant)
class="type">void                conjugate(const Complex &y);                class=class="str">"cmt">//conjugation of complex numbers
class="type">class="kw">double              norm();                                     class=class="str">"cmt">//normalization 
};
class CFFT
  {
class="kw">public:
  Complex           Input[];  class=class="str">"cmt">//class="kw">input array of complex numbers
  Complex           Output[]; class=class="str">"cmt">//output array of complex numbers
class="kw">public:
  class="type">bool              Forward(const class="type">uint N);                                            class=class="str">"cmt">//direct Fourier transformation
  class="type">bool              InverseT(const class="type">uint N,const class="type">bool Scale=true);                     class=class="str">"cmt">//weighted reverse Fourier transformation
  class="type">bool              InverseF(const class="type">uint N,const class="type">bool Scale=class="kw">false);                    class=class="str">"cmt">//non-weighted reverse Fourier transformation
  class="type">void              setCFFT(Complex &data1[],Complex &data2[],const class="type">uint N); class=class="str">"cmt">//set method(class="num">1-st variant)
  class="type">void              setCFFT(Complex &data1[],Complex &data2[]);              class=class="str">"cmt">//set method(class="num">2-nd variant)
class="kw">protected:

「用 FFT 算序列自相关的一段实现」

这段 MT5 代码给出了一维随机时间序列选择性自相关函数(ACF)的第一种写法,核心思路是先对去均值后的观测序列做复化,再借快速傅里叶变换绕开直接套公式的 O(N²) 开销。 nFFT 取的是 2 的 nextpow2(ArraySize(res))+1 次幂,例如 res 长度为 500 时 nextpow2 得 9,nFFT 即为 2 的 10 次方 = 1024,所有中间数组都按这个尺寸重设。 代码里先把 res 拷进 rets1 并逐点减掉算术平均 m,再用 setComplexArr 封装成复数输入 Data1;随后声明 CFFT 实例 F、F1,通过 F.setCFFT(Data1,Data2) 绑定输入输出,后续由类内部 Rearrange / Perform / Scale 三步完成变换与加权。 对外汇或贵金属这类高波动品种跑这套,序列非平稳会让 ACF 滞后阶估计偏误,结果仅作概率层面的周期参考,实盘前务必在 MT5 策略测试器用真实 tick 复核。

MQL5 / C++
class="type">void      Rearrange(const class="type">uint N);              class=class="str">"cmt">// regrouping
class="type">void      Perform(const class="type">uint N,const class="type">bool Inverse); class=class="str">"cmt">// implementation of transformation
class="type">void      Scale(const class="type">uint N);                     class=class="str">"cmt">// weighting
};
class="type">int nFFT=pow(class="num">2,nextpow2(ArraySize(res))+class="num">1);      class=class="str">"cmt">//power rate of two
class="type">void autocorr(class="type">class="kw">double &ACF[],class="type">class="kw">double &res[],class="type">int nLags) class=class="str">"cmt">//class="num">1-st variant of function
class=class="str">"cmt">/* selective autocorrelation function(ACF)
   for unidimensional stochastic time series
   ACF - output array of calculated values of the autocorrelation function;
   res - array of observation of stochastic time series;
   nLags - maximum number of lags the ACF is calculated for.
*/
  {
   Complex Data1[],Data21[], class=class="str">"cmt">//class="kw">input arrays of complex numbers
   Data2[],Data22[],         class=class="str">"cmt">//output arrays of complex numbers
   cData[];                  class=class="str">"cmt">//array of conjugated complex numbers
   class="type">class="kw">double rA[][class="num">2];           class=class="str">"cmt">//auxiliary two-dimensional array of real numbers
   class="type">int nFFT=pow(class="num">2,nextpow2(ArraySize(res))+class="num">1);      class=class="str">"cmt">//power rate of two
   ArrayResize(rA,nFFT);ArrayResize(Data1,nFFT);    class=class="str">"cmt">//correction of array sizes
   ArrayResize(Data2,nFFT);ArrayResize(Data21,nFFT);
   ArrayResize(Data22,nFFT);ArrayResize(cData,nFFT);
   class="type">class="kw">double rets1[];                   class=class="str">"cmt">//an auxiliary array for observing the series
   class="type">class="kw">double m=mean(res);               class=class="str">"cmt">//arithmetical mean of the array res
   ArrayResize(rets1,nFFT);          class=class="str">"cmt">//correction of array size
   for(class="type">int t=class="num">0;t<ArraySize(res);t++) class=class="str">"cmt">//copy the initial array of observation
   class=class="str">"cmt">// to the auxiliary one with correction by average
     rets1[t]=res[t]-m;
   setComplexArr(Data1,rets1); class=class="str">"cmt">//set class="kw">input array of complex numbers
   CFFT F,F1;                  class=class="str">"cmt">//initialize instances of the CFFT class
   F.setCFFT(Data1,Data2);     class=class="str">"cmt">//initialize data-members for the instance F
交给小布盯盘看波动惯性
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到自相关和条件方差的实时估计,把重复劳动交给小布,你专注决策。

常见问题

看峰度(峭度),小于正态分布峭度的分布出现肥尾的概率更大,需用概率密度算序列取值可能。
杠杆效应多见于股市下跌,外汇市场该特性不显著,建模时可不必单独纳入。
非线性模型更能描述价格波动集群与肥尾,GARCH 类模型可量化条件方差的惯性。
可以,对应品种页已内置自相关与条件方差估计,无需自己写 MQL5 脚本即可观察。
高波动周期倾向延续,止损距离若按低波动期设定,被扫概率可能偏高,应随估计方差调整。