用 MQL5 表示统计概率分布·进阶篇
📊

用 MQL5 表示统计概率分布·进阶篇

(2/3)· 从连续离散分类到 MQL5 类缺失多继承的绕行方案,交易者常卡在理论到代码的断层

实战向进阶 第 2/3 篇
很多交易者把正态分布当万能模板直接套行情,却忽略离散与连续分布的根本分野。MQL5 里没有多继承,硬抄 C++ 层次结构只会编译报错。先厘清分布本质再写代码,比盲目调参更省时间。

互补误差函数的切比雪夫实现与反函数

在 MT5 里做统计套利或波动率通道,常需要误差函数及其反函数。下面这段把互补误差函数 erfc 拆成切比雪夫逼近,相对误差控制在 1.2e-7,足够日内价位区间计算用。 erfc 先按正负分流:x>=0 直接走 erfccheb,x<0 用 2.0 减对称值。erfccheb 内部把自变量映射到 [-2,2] 的 ty,再用 cof 系数做递推,最后乘 exp(-z*z+...) 收口;若传入负数会弹 Alert 提醒。 反函数 inverfc 用初值 t=sqrt(-2*log(pp/2)) 再牛顿迭代两次,p>=2 返回 -100、p<=0 返回 100 作边界保护。inverf 只是包了一层 1.0-p。 开 MT5 把 ncof 和 cof[] 补成具体系数就能编译,外汇与贵金属杠杆高,用这类函数算置信带时须自行校验样本分布,实盘误用可能放大回撤。

MQL5 / C++
class="type">class="kw">double erfc(class="type">class="kw">double x)
  {
  if(x>=class="num">0.0) class="kw">return erfccheb(x);
  else class="kw">return class="num">2.0-erfccheb(-x);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Chebyshev approximations for the error function                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double erfccheb(class="type">class="kw">double z)
  {
  class="type">int j;
  class="type">class="kw">double t,ty,tmp,d=class="num">0.0,dd=class="num">0.0;
  if(z<class="num">0.) Alert("erfccheb requires nonnegative argument!");
  t=class="num">2.0/(class="num">2.0+z);
  ty=class="num">4.0*t-class="num">2.0;
  for(j=ncof-class="num">1;j>class="num">0;j--)
    {
     tmp=d;
     d=ty*d-dd+cof[j];
     dd=tmp;
    }
  class="kw">return t*exp(-z*z+class="num">0.5*(cof[class="num">0]+ty*d)-dd);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Inverse complementary error function                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double inverfc(class="type">class="kw">double p)
  {
  class="type">class="kw">double x,err,t,pp;
  if(p >= class="num">2.0) class="kw">return -class="num">100.0;
  if(p <= class="num">0.0) class="kw">return class="num">100.0;
  pp=(p<class="num">1.0)? p : class="num">2.0-p;
  t = sqrt(-class="num">2.*log(pp/class="num">2.0));
  x = -class="num">0.70711*((class="num">2.30753+t*class="num">0.27061)/(class="num">1.0+t*(class="num">0.99229+t*class="num">0.04481)) - t);
  for(class="type">int j=class="num">0;j<class="num">2;j++)
    {
     err=erfc(x)-pp;
     x+=err/(M_2_SQRTPI*exp(-pow(x,class="num">2))-x*err);
    }
  class="kw">return(p<class="num">1.0? x : -x);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Inverse error function                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double inverf(class="type">class="kw">double p)
  {class="kw">return inverfc(class="num">1.0-p);}
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double erfcc(const class="type">class="kw">double x)
class=class="str">"cmt">/*
complementary error function erfc(x) with
a relative error of class="num">1.2 * class="num">10^(-class="num">7)            
*/
  {
  class="type">class="kw">double t,z=fabs(x),ans;

◍ 对数正态分布类的底层实现

在价格行为建模里,把收益率当成正态往往失真,而贵金属与外汇的价位波动更适合用对数正态来描述——它保证 x 永远大于 0,不会算出负金价。下面这段 MQL5 类直接挂了误差函数基类,把 μ 和 σ 作为公开参数,构造时默认 μ=0、σ=1,若 σ≤0 会弹窗报警。 pdf 里用了常数 0.398942280401432678,其实就是 1/√(2π);当 x=0 直接返回 0,避免 log(0) 爆掉。cdf 则借 erfc 实现,系数 -0.707106781186547524 是 -1/√2,把 log(x) 标准化后喂进互补误差函数。 别把正态当圣经 做 XAUUSD 周线级别止损间距测算时,用这类对数正态 cdf 估尾概率,比直接套正态更贴近真实跳空分布,但外汇/贵金属杠杆高,任何分布都只是概率参考,实盘仍可能极端穿仓。

MQL5 / C++
class CLognormaldist : CErf class=class="str">"cmt">// Erf class inheritance
  {
class="kw">public:
  class="type">class="kw">double            mu, class=class="str">"cmt">//location parameter(μ)
  sig;               class=class="str">"cmt">//scale parameter(σ)
  class="type">void  CLognormaldist()
     {
       mu=class="num">0.0;sig=class="num">1.0; class=class="str">"cmt">//class="kw">default parameters μ and σ
       if(sig<=class="num">0.) Alert("bad sig in Lognormal Distribution!");
     }
  class="type">class="kw">double pdf(class="type">class="kw">double x)
     {
       if(x<class="num">0.) Alert("bad x in Lognormal Distribution!");
       if(x==class="num">0.) class="kw">return class="num">0.;
       class="kw">return(class="num">0.398942280401432678/(sig*x))*exp(-class="num">0.5*pow((log(x)-mu)/sig,class="num">2));
     }
  class="type">class="kw">double cdf(class="type">class="kw">double x)
     {
       if(x<class="num">0.) Alert("bad x in Lognormal Distribution!");
       if(x==class="num">0.) class="kw">return class="num">0.;
       class="kw">return class="num">0.5*erfc(-class="num">0.707106781186547524*(log(x)-mu)/sig);
     }
  class="type">class="kw">double invcdf(class="type">class="kw">double p)
     {

「柯西分布类与生存函数的代码落地」

对数正态分布里顺手补了个生存函数 sf(x),直接 return 1-cdf(x),本质是右尾概率,用来评估价格突破某阈值后继续走的概率倾向。 紧接着定义了柯西分布类 CCauchydist,位置参数 mu 默认 0.0、尺度参数 sig 默认 1.0;若 sig<=0 会弹 Alert("bad sig in Cauchy Distribution!"),因为尺度非正无意义。 pdf 用常数 0.318309886183790671(即 1/π)除以 sig*(1+((x-mu)/sig)^2),峰比正态更尖、尾更厚,对贵金属跳空行情的极端值建模可能更贴合。 cdf 写成了 0.5 + 0.318309886183790671*atan2(x-mu,sig),invcdf 则 return mu+sig*tan(π*(p-0.5)),p 不在 (0,1) 同样报警。外汇与贵金属杠杆高,厚尾分布下的回撤可能远超预期,开 MT5 把这组类贴进脚本跑一遍分位数就能感受差异。

MQL5 / C++
class="type">class="kw">double sf(class="type">class="kw">double x)
 {
  class="kw">return class="num">1-cdf(x);
 }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                     Cauchy Distribution class definition          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CCauchydist class=class="str">"cmt">//
  {
class="kw">public:
  class="type">class="kw">double           mu,class=class="str">"cmt">//location parameter(μ)
  sig;               class=class="str">"cmt">//scale parameter(σ)
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| CCauchydist class constructor                                    |
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class="type">void  CCauchydist() 
  {
   mu=class="num">0.0;sig=class="num">1.0; class=class="str">"cmt">//class="kw">default parameters μ and σ
   if(sig<=class="num">0.) Alert("bad sig in Cauchy Distribution!");
  }
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| Probability density function(pdf)                               |
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class="type">class="kw">double pdf(class="type">class="kw">double x)
  {
   class="kw">return class="num">0.318309886183790671/(sig*(class="num">1.+pow((x-mu)/sig,class="num">2)));
  }
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| Cumulative distribution function(cdf)                           |
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class="type">class="kw">double cdf(class="type">class="kw">double x)
  {
   class="kw">return class="num">0.5+class="num">0.318309886183790671*atan2(x-mu,sig); class=class="str">"cmt">//todo
  }
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| Inverse cumulative distribution function(invcdf) (quantile func)|
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class="type">class="kw">double invcdf(class="type">class="kw">double p)
  {
   if(!(p>class="num">0. && p<class="num">1.))
     Alert("bad p in Cauchy Distribution!");
   class="kw">return mu+sig*tan(M_PI*(p-class="num">0.5));
  }

双曲正割分布与反正切的实现细节

在自定义统计类里,生存函数 sf(x) 直接由 1 减去累积分布 cdf(x) 得到,这意味着只要 cdf 写对,尾部存活概率就自动闭合,不用单独再推公式。

下面这段 atan2 是手写版四象限反正切,避免某些环境里内置函数行为不一致。它先比较xy大小,走不同分支把结果收进 (-π, π]:当x>y用 atan(y/x),否则用 atan(x/y) 再做 π/2 的补角变换,最后按 x 负半轴加減 π 修正象限。

双曲正割分布类 CHypersecdist 默认 μ=0、σ=1;构造时若 σ≤0 会弹 Alert 报警。其概率密度 pdf(x) = sech(π(x-μ)/(2σ)) / (2σ),比正态更厚尾,用于刻画外汇或贵金属收益率跳跃时,可能比高斯假设更贴近实际,但高频交易仍属高风险,参数别盲调。 开 MT5 把这段粘进 EA 头文件,改 sig 从 1.0 到 0.5 看 pdf 峰值变化,能直观感受尺度参数对尾部的压缩。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Reliability(survival) function(sf)                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double sf(class="type">class="kw">double x)
  {
   class="kw">return class="num">1-cdf(x);
  }
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double atan2(class="type">class="kw">double y,class="type">class="kw">double x)
class=class="str">"cmt">/*
Returns the principal value of the arc tangent of y/x,
expressed in radians. To compute the value, the function
uses the sign of both arguments to determine the quadrant.
y - class="type">class="kw">double value representing an y-coordinate.
x - class="type">class="kw">double value representing an x-coordinate.
*/
 {
  class="type">class="kw">double a;
  if(fabs(x)>fabs(y))
    a=atan(y/x);
  else
    {
     a=atan(x/y); class=class="str">"cmt">// pi/class="num">4 <= a <= pi/class="num">4
     if(a<class="num">0.)
       a=-class="num">1.*M_PI_2-a; class=class="str">"cmt">//a is negative, so we&class="macro">#x27;re adding
     else
       a=M_PI_2-a;
    }
  if(x<class="num">0.)
    {
     if(y<class="num">0.)
       a=a-M_PI;
     else
       a=a+M_PI;
    }
  class="kw">return a;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|       Hyperbolic Secant Distribution class definition             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CHypersecdist class=class="str">"cmt">//
 {
class="kw">public:
  class="type">class="kw">double            mu,class=class="str">"cmt">// location parameter(μ)
  sig;                class=class="str">"cmt">//scale parameter(σ)
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| CHypersecdist class constructor                                 |
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class="type">void  CHypersecdist()
    {
     mu=class="num">0.0;sig=class="num">1.0; class=class="str">"cmt">//class="kw">default parameters μ and σ
     if(sig<=class="num">0.) Alert("bad sig in Hyperbolic Secant Distribution!");
    }
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| Probability density function(pdf)                              |
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class="type">class="kw">double pdf(class="type">class="kw">double x)
    {
     class="kw">return sech((M_PI*(x-mu))/(class="num">2*sig))/class="num">2*sig;
    }
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| Cumulative distribution function(cdf)                          |
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class="type">class="kw">double cdf(class="type">class="kw">double x)

◍ 双曲正割与 Student-t 的分布函数落地

双曲正割分布(Hyperbolic Secant)的累计分布用了 2/π·arctan(exp(π(x-μ)/(2σ))) 的闭式表达,逆累积分布则靠 tan(π/2·p) 的对数反解,p 不在 (0,1) 区间会直接弹 Alert 报警,实盘前先把边界判断跑通。 生存函数 sf(x) 就是 1-cdf(x),代码里没单独算积分,直接复用 cdf,省掉重复数值开销;双曲正割函数 sech(x) 写成 2/(e^x+e^-x),在 MT5 里用 pow(M_E,x) 拼即可,注意 M_E 是编译器常量不必自己定义。 Student-t 类继承了 CBeta,构造默认 ν=1、μ=0、σ=1,自由度只有 1 时尾厚到柯西级别,外汇 1 分钟线上容易把止损扫掉;调 ν 到 5 以上尾部才接近正态,但仍是高风险品种,验证时建议在 EURUSD 的 H1 跑分布拟合看残差。

MQL5 / C++
  {
      class="kw">return class="num">2/M_PI*atan(exp((M_PI*(x-mu)/(class="num">2*sig))));
   }
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class=class="str">"cmt">//| Inverse cumulative distribution function(invcdf) (quantile func)|
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class="type">class="kw">double invcdf(class="type">class="kw">double p)
   {
      if(!(p>class="num">0. && p<class="num">1.))
         Alert("bad p in Hyperbolic Secant Distribution!");
      class="kw">return(mu+(class="num">2.0*sig/M_PI*log(tan(M_PI/class="num">2.0*p))));
   }
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class=class="str">"cmt">//| Reliability(survival) function(sf)                             |
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class="type">class="kw">double sf(class="type">class="kw">double x)
   {
      class="kw">return class="num">1-cdf(x);
   }
   };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|              Hyperbolic Secant Function                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double sech(class="type">class="kw">double x)
class=class="str">"cmt">// Hyperbolic Secant Function
  {
   class="kw">return class="num">2/(pow(M_E,x)+pow(M_E,-x));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|              Student&class="macro">#x27;s t-distribution class definition              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class CStudenttdist : CBeta class=class="str">"cmt">// CBeta class inheritance
  {
class="kw">public:
   class="type">int               nu;     class=class="str">"cmt">// shape parameter(ν)
   class="type">class="kw">double            mu,     class=class="str">"cmt">// location parameter(μ)
   sig,                      class=class="str">"cmt">// scale parameter(σ)
   np,                       class=class="str">"cmt">// class="num">1/class="num">2*(ν+class="num">1)
   fac;                      class=class="str">"cmt">// Г(class="num">1/class="num">2*(ν+class="num">1))-Г(class="num">1/class="num">2*ν)
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class=class="str">"cmt">//| CStudenttdist class constructor                                  |
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class="type">void   CStudenttdist()
   {
      class="type">int Nu=class="num">1;class="type">class="kw">double Mu=class="num">0.0,Sig=class="num">1.0; class=class="str">"cmt">//class="kw">default parameters ν, μ and σ
      setCStudenttdist(Nu,Mu,Sig);
   }
   class="type">void setCStudenttdist(class="type">int Nu,class="type">class="kw">double Mu,class="type">class="kw">double Sig)
   {
      nu=Nu;
      mu=Mu;
      sig=Sig;

「把学生氏 t 分布塞进 MT5 类里」

上面这组方法把学生氏 t 分布完整封装成了一个可复用的计算单元:从密度、累积到分位和反函数全齐了。外汇与贵金属行情的尾部风险往往比正态假设更肥,用这套分布估极端_move概率时,自由度 nu 越小尾部越厚,实战里 EURUSD 日线回测常落在 nu=3~5 区间。 pdf 直接返回某价格偏离 mu 的相对概率密度;cdf 用 betai 算双侧积分后按 t 与 mu 的大小翻面,t>=mu 时返回 1-p。invcdf 里先用 fmin(p,1-p) 夹住输入,再经 invbetai 与开方变换还原分位点,p>=0.5 取 mu+x 否则 mu-x。

sf 只是 1-cdf 的薄封装,aa 算双尾累积且强制 t>=0 否则报警,对应假设检验里t的 p 值。把 nu、mu、sig 接上实时波动率,就能在 MT5 里实时吐出『当前价处于历史分布百分之几分位』,贵金属跳空夜尤其值得盯。
MQL5 / C++
if(sig<=class="num">0. || nu<=class="num">0.) Alert("bad sig,nu in Student-t Distribution!");
np=class="num">0.5*(nu+class="num">1.);
fac=gammln(np)-gammln(class="num">0.5*nu);
 }
 class=class="str">"cmt">//+------------------------------------------------------------------+
 class=class="str">"cmt">//| Probability density function(pdf)                               |
 class=class="str">"cmt">//+------------------------------------------------------------------+
 class="type">class="kw">double pdf(class="type">class="kw">double x)
  {
   class="kw">return exp(-np*log(class="num">1.+pow((x-mu)/sig,class="num">2.)/nu)+fac)/(sqrt(M_PI*nu)*sig);
  }
 class=class="str">"cmt">//+------------------------------------------------------------------+
 class=class="str">"cmt">//| Cumulative distribution function(cdf)                           |
 class=class="str">"cmt">//+------------------------------------------------------------------+
 class="type">class="kw">double cdf(class="type">class="kw">double t)
  {
   class="type">class="kw">double p=class="num">0.5*betai(class="num">0.5*nu,class="num">0.5,nu/(nu+pow((t-mu)/sig,class="num">2)));
   if(t>=mu) class="kw">return class="num">1.-p;
   else class="kw">return p;
  }
 class=class="str">"cmt">//+------------------------------------------------------------------+
 class=class="str">"cmt">//| Inverse cumulative distribution function(invcdf) (quantile func)|
 class=class="str">"cmt">//+------------------------------------------------------------------+
 class="type">class="kw">double invcdf(class="type">class="kw">double p)
  {
   if(p<=class="num">0. || p>=class="num">1.) Alert("bad p in Student-t Distribution!");
   class="type">class="kw">double x=invbetai(class="num">2.*fmin(p,class="num">1.-p),class="num">0.5*nu,class="num">0.5);
   x=sig*sqrt(nu*(class="num">1.-x)/x);
   class="kw">return(p>=class="num">0.5? mu+x : mu-x);
  }
 class=class="str">"cmt">//+------------------------------------------------------------------+
 class=class="str">"cmt">//| Reliability(survival) function(sf)                             |
 class=class="str">"cmt">//+------------------------------------------------------------------+
 class="type">class="kw">double sf(class="type">class="kw">double x)
  {
   class="kw">return class="num">1-cdf(x);
  }
 class=class="str">"cmt">//+------------------------------------------------------------------+
 class=class="str">"cmt">//| Two-tailed cumulative distribution function(aa) A(t|ν)          |
 class=class="str">"cmt">//+------------------------------------------------------------------+
 class="type">class="kw">double aa(class="type">class="kw">double t)
  {
   if(t < class="num">0.) Alert("bad t in Student-t Distribution!");
   class="kw">return class="num">1.-betai(class="num">0.5*nu,class="num">0.5,nu/(nu+pow(t,class="num">2.)));
  }
 class=class="str">"cmt">//+------------------------------------------------------------------+
 class=class="str">"cmt">//| Inverse two-tailed cumulative distribution function(invaa)      |
把分布诊断交给小布盯盘
这些分布形态与参数偏移的诊断,小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时拟合结果,你只需判断当前是随机还是规律主导。

常见问题

连续分布描述可取无穷多值的变量,离散分布对应 countable 样本,二者概率密度与质量函数不同,代码封装时采样逻辑不能混用。
可借用 Numerical Recipes 的扁平函数组,用结构体内聚参数而非深层继承;以组合代替子类化,降低编译耦合。
可以,小布盯盘品种页内置了分布拟合模块,会给出均值方差及偏离度,便于快速判断分布异动。
多用于分位数止损或信号阈值反推,例如由置信水平求价格边界,概率倾向随样本量增大而稳定。
可靠性函数是生存概率 1-F(x),分布函数是累积概率 F(x),二者互补,看尾部风险时可靠性函数更直观。