用 MQL5 表示统计概率分布·进阶篇
(2/3)· 从连续离散分类到 MQL5 类缺失多继承的绕行方案,交易者常卡在理论到代码的断层
互补误差函数的切比雪夫实现与反函数
在 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[] 补成具体系数就能编译,外汇与贵金属杠杆高,用这类函数算置信带时须自行校验样本分布,实盘误用可能放大回撤。
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 估尾概率,比直接套正态更贴近真实跳空分布,但外汇/贵金属杠杆高,任何分布都只是概率参考,实盘仍可能极端穿仓。
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 把这组类贴进脚本跑一遍分位数就能感受差异。
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 是手写版四象限反正切,避免某些环境里内置函数行为不一致。它先比较 | x | 与 | y | 大小,走不同分支把结果收进 (-π, π]:当 | 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 峰值变化,能直观感受尺度参数对尾部的压缩。
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 跑分布拟合看残差。
{
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 里实时吐出『当前价处于历史分布百分之几分位』,贵金属跳空夜尤其值得盯。 |
|---|
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) |