统计分布在交易者工作中的作用(基础篇)
📘

统计分布在交易者工作中的作用(基础篇)

第 1/2 篇

◍ 随机性里也有油水可榨

Denis Kirichenko 在 2013-12-31 10:09 于 MT5 社区发布了一篇关于统计分布的小文,原文累计获得 2786 次查看、11 条讨论。对交易者来说,这不算爆款,但说明基础统计话题在外汇/贵金属圈长期有刚性需求。 作者借 Georgiy Aleksandrov 的话点出一个反直觉事实:规律性让交易系统容易搭,但从随机波动里抠概率优势同样关键。外汇和贵金属杠杆高、跳空频繁,把行情当纯随机序列去度量,反而能避开「过度拟合形态」的坑。 开 MT5 随便拉一品种日线,用 StdDev 看近年波动率分布,你会注意到极端值出现频率比教科书正态假设高得多——这本身就是可验证的现象。

「为什么先写分布类再碰实盘数据」

在 MT5 里直接拿 tick 或收盘序列去套统计模型,往往会在第三次重写时崩在类型转换上。先把正态、对数正态这类理论分布封装成类,后续调用只需传样本数组,不用每次重算矩估计。 上一篇已经把分布类的接口铺完,这一篇的落点是把真实行情序列喂进去,看理论曲线和样本直方图差多少。过程中会顺带暴露一个老问题:样本量低于 200 根 K 线时,偏度和峰度估计的方差大到没法信。 外汇与贵金属属高风险品种,小样本统计结论只作概率参考,别拿来当仓位依据。打开 MT5 的「数据窗口」拉一段 EURUSD 的 H1 收盘,数一下是不是够 200 根再跑下面的类。

在 MT5 里搭一个可指定分布的随机数发生器

做统计检验或蒙特卡洛式的价格行为模拟前,先要有能按理论分布批量吐数的底层发生器。理想状态是用户只填分布参数和样本量,类结构自己把样本算出来,而不是每次手写循环。 我沿用《Numerical Recipes》里的高质均匀发生器,它的周期约 3.138×10^57,碰撞概率极低,直接把原 C 逻辑搬进 MQL5 不会损失随机性。下面这段 Random 基类就是搬过来的核心,负责产出 64 位整型与 [0,1] 双精度。 CNormaldev 这类派生类会持有一个 CNormaldist 成员,用 Leva 均匀比例法从正态里取点——原 C 版本没把「取值器」和「分布」绑在一起,我在 MQL5 封装时特意加了这层逻辑连接,后面换分布只改成员类即可。所有分布类都收在 Random_class.mqh,实盘前你可用 doub() 拉一万个数跑偏度峰度,看是不是贴合设定。 外汇与贵金属杠杆高、跳空频繁,这类合成样本只用于方法验证,不能直接当行情预测依据,实盘前务必用历史 tick 复核。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//|&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Random class definition&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class</span> Random
&nbsp;&nbsp;{
<span class="keyword">class="kw">private</span>:
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">ulong</span> u, <span class="comment">class=class="str">"cmt">//unsigned class="num">64-bit integers </span>
&nbsp;&nbsp; v,
&nbsp;&nbsp; w;
<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//| The Random class constructor&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
&nbsp;&nbsp; <span class="keyword">class="type">void</span> Random()
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;randomSet(<span class="number">class="num">184467440737095516</span>);
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//| The Random&nbsp;class set-method&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|</span>
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
&nbsp;&nbsp; <span class="keyword">class="type">void</span> randomSet(<span class="keyword">class="type">class="kw">ulong</span> j)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;v=<span class="number">class="num">4101842887655102017</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;w=<span class="number">class="num">1</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;u=<span class="number">class="num">14757395258967641292</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;u=j^v;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int64();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;v = u; int64();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;w = v; int64();
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//| Return class="num">64-bit random integer&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">ulong</span> int64()
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">uint</span> k=<span class="number">class="num">4294957665</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;u=u*<span class="number">class="num">2862933555777941757</span>+<span class="number">class="num">7046029254386353087</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;v^= v&gt;&gt; <span class="number">class="num">17</span>; v ^= v&lt;&lt; <span class="number">class="num">31</span>; v ^= v&gt;&gt; <span class="number">class="num">8</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;w = k*(w &amp; <span class="number">0xffffffff</span>) +(w&gt;&gt; <span class="number">class="num">32</span>);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">ulong</span> x=u^(u&lt;&lt;<span class="number">class="num">21</span>); x^=x&gt;&gt;<span class="number">class="num">35</span>; x^=x&lt;&lt;<span class="number">class="num">4</span>;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span>(x+v)^w;
&nbsp;&nbsp;&nbsp;&nbsp; };
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//| Return random class="type">class="kw">double-precision value in the range class="num">0. to class="num">1.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
&nbsp;&nbsp; <span class="keyword">class="type">class="kw">double</span> doub()
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span> <span class="number">class="num">5.42101086242752217</span>e-<span class="number">class="num">20</span>*int64();
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//| Return class="num">32-bit random integer&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
&nbsp;&nbsp; <span class="keyword">class="type">uint</span> int32()
&nbsp;&nbsp;&nbsp;&nbsp; {

◍ 正态偏差类的采样实现与参数钩子

CNormaldev 继承自 Random 基类,内部挂了一个 CNormaldist 实例 N 来保存均值 mu 与标准差 sig。构造函数里写死把种子设为 18446744073709(约 1.84e13),相当于给 MT5 的伪随机流一个固定起手式,回测时可复现。 setNormaldev 方法把外部传入的分布参数拷进 N,再调用 randomSet(j) 重设随机种子;这意味着你在 EA 初始化时换一个 j,就能让同一套正态采样走出不同路径,做蒙特卡洛压力测试很顺手。 dev() 用的是经典 Ratio-of-Uniforms 近似:先取 u=doub()、v=1.7156*(doub()-0.5),再用 x、y、q 做拒绝判定,阈值 0.27597 与 0.27846 直接写在循环里。最终 return N.mu+N.sig*v/u,把均匀随机量扭成服从 N(mu,sig²) 的正态偏差。外汇与贵金属价格波动用这类采样做情景生成时,务必记住杠杆放大下的爆仓风险偏高。 把这段代码直接塞进你的 include 里,把 18446744073709 改成 TimeCurrent() 相关的动态种子,就能在每根 K 线拿到一组新的正态扰动,用来给止损带宽做概率化抖动。

MQL5 / C++
class CNormaldev : class="kw">public Random
  {
class="kw">public:
  CNormaldist      N; class=class="str">"cmt">//Normal Distribution instance
  class="type">void CNormaldev()
    {
    CNormaldist Nn;
    setNormaldev(Nn,class="num">18446744073709);
    }
  class="type">void setNormaldev(CNormaldist &Nn,class="type">class="kw">ulong j)
    {
    N.mu=Nn.mu;
    N.sig=Nn.sig;
    randomSet(j);
    }
  class="type">class="kw">double dev()
    {
    class="type">class="kw">double u,v,x,y,q;
    do
      {
      u = doub();
      v = class="num">1.7156*(doub()-class="num">0.5);
      x = u - class="num">0.449871;
      y = fabs(v) + class="num">0.386595;
      q = pow(x,class="num">2) + y*(class="num">0.19600*y-class="num">0.25472*x);
      }
    while(q>class="num">0.27597
    && (q>class="num">0.27846 || pow(v,class="num">2)>-class="num">4.*log(u)*pow(u,class="num">2)));
    class="kw">return N.mu+N.sig*v/u;
    }
  };

常见问题

先看生成数据的直方图和理论曲线贴合度,样本量低于一万时偏差会很明显,建议至少跑五万次再下结论。
先掌握可控的随机源才能给实盘噪声定基准,不然容易把偶发波动误读成规律,建议先在模拟环境验证分布逻辑。
可以,小布能读取你的分布参数并跑偏差诊断,直接在品种页标出采样异常区间,省去手算直方图。
用历史品种的实测均值和波动做初值,再按风险偏好缩放标准差,别超过实测值的1.5倍否则尾部失真。
是的,必须先有可指定分布的发生器才能控变量,否则蒙特卡洛结果不可信,优先搭好分布钩子再谈压力测试。