统计分布在交易者工作中的作用(基础篇)
◍ 随机性里也有油水可榨
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 复核。
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| Random class definition |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class</span> Random { <span class="keyword">class="kw">private</span>: <span class="keyword">class="type">class="kw">ulong</span> u, <span class="comment">class=class="str">"cmt">//unsigned class="num">64-bit integers </span> v, w; <span class="keyword">class="kw">public</span>: <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| The Random class constructor |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">void</span> Random() { randomSet(<span class="number">class="num">184467440737095516</span>); } <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| The Random class set-method |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">void</span> randomSet(<span class="keyword">class="type">class="kw">ulong</span> j) { v=<span class="number">class="num">4101842887655102017</span>; w=<span class="number">class="num">1</span>; u=<span class="number">class="num">14757395258967641292</span>; u=j^v; int64(); v = u; int64(); w = v; int64(); } <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| Return class="num">64-bit random integer |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">class="kw">ulong</span> int64() { <span class="keyword">class="type">uint</span> k=<span class="number">class="num">4294957665</span>; u=u*<span class="number">class="num">2862933555777941757</span>+<span class="number">class="num">7046029254386353087</span>; v^= v>> <span class="number">class="num">17</span>; v ^= v<< <span class="number">class="num">31</span>; v ^= v>> <span class="number">class="num">8</span>; w = k*(w & <span class="number">0xffffffff</span>) +(w>> <span class="number">class="num">32</span>); <span class="keyword">class="type">class="kw">ulong</span> x=u^(u<<<span class="number">class="num">21</span>); x^=x>><span class="number">class="num">35</span>; x^=x<<<span class="number">class="num">4</span>; <span class="keyword">class="kw">return</span>(x+v)^w; }; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <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. |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">class="kw">double</span> doub() { <span class="keyword">class="kw">return</span> <span class="number">class="num">5.42101086242752217</span>e-<span class="number">class="num">20</span>*int64(); } <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| Return class="num">32-bit random integer |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">uint</span> int32() {
◍ 正态偏差类的采样实现与参数钩子
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 线拿到一组新的正态扰动,用来给止损带宽做概率化抖动。
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; } };