单一资产交易顺序中的风险评估·综合运用
(3/3)· 从凯利衍生的最优 f 到一般情形附录,把账户回撤与未知 f 两座大山一次拆穿
下降因子与随机抽样的底层实现
这段逻辑干两件事:先算一串价格的‘下降因子’ d,再对数组做原地随机抽样。下降因子衡量的是在窗口 N 内,每一个新低点相对此前最高点的回撤极小值,外汇与贵金属波动无序,该值仅反映历史形态,不预示方向,杠杆品种高风险。 核心循环从 i=1 扫到 N-1:若某根收盘价 c[i]≤0 直接返回 0(防脏数据);c[i] 比当前 mn 还低时,更新 mn 并用 MathMin 累积 d=mn/mx 的最小比值;否则一旦 c[i] 创新高,就把 mx 和 mn 同时重置为 c[i],相当于一段下跌结构结束、重新起算。 [CODE] double d=c[0], mx=c[0], mn=c[0]; for(int i=1; i<N; ++i) { if(c[i]<=0) return 0; if(c[i]<mn) {mn=c[i]; d=MathMin(d,mn/mx);} else {if(c[i]>mx) mx=mn=c[i];} } return d; } // 随机排列的第一个不大于 min(n,ArraySize(b)) // b[] 数组的元素 // 可以使用函数库 MathSample() 代替 trps() void trps(double &b[],int n) { if(n<=1) return; int sz=ArraySize(b); if(sz<=1) return; if(sz<n) n=sz; int ner; double dnc=MathRandomUniform(0,n,ner); if(!MathIsValidNumber(dnc)) {Print("错误 ",ner); ExpertRemove();} int nc=(int)dnc; if(nc>=0 && nc<n-1) { double tmp=b[n-1]; b[n-1]=b[nc]; b[nc]=tmp;} trps(b,n-1); } [/CODE] trps 是递归版 Fisher–Yates 抽样:每次从 [0,n-1] 用 MathRandomUniform 取一个索引 nc,和末位交换,再把规模缩到 n-1 递归。当数组长度小于 n 时自动把 n 截断为 sz,避免越界。 MathRandomUniform 返回非有限值时会 Print 错误码并 ExpertRemove 终止 EA,实盘前应在 MT5 策略测试器里跑一遍确认 ner 恒为 0,否则抽样逻辑会直接崩掉。
class="type">class="kw">double d=c[class="num">0], mx=c[class="num">0], mn=c[class="num">0]; for(class="type">int i=class="num">1; i<N; ++i) { if(c[i]<=class="num">0) class="kw">return class="num">0; if(c[i]<mn) {mn=c[i]; d=MathMin(d,mn/mx);} else {if(c[i]>mx) mx=mn=c[i];} } class="kw">return d; } class=class="str">"cmt">// 随机排列的第一个不大于 min(n,ArraySize(b)) class=class="str">"cmt">// b[] 数组的元素 class=class="str">"cmt">// 可以使用函数库 MathSample() 代替 trps() class="type">void trps(class="type">class="kw">double &b[],class="type">int n) { if(n<=class="num">1) class="kw">return; class="type">int sz=ArraySize(b); if(sz<=class="num">1) class="kw">return; if(sz<n) n=sz; class="type">int ner; class="type">class="kw">double dnc=MathRandomUniform(class="num">0,n,ner); if(!MathIsValidNumber(dnc)) {Print("错误 ",ner); ExpertRemove();} class="type">int nc=(class="type">int)dnc; if(nc>=class="num">0 && nc<n-class="num">1) { class="type">class="kw">double tmp=b[n-class="num">1]; b[n-class="num">1]=b[nc]; b[nc]=tmp;} trps(b,n-class="num">1); }
◍ 样本跑出来为何判系统不可用
把一段 30 笔交易的收益序列丢进 r_cmn.mq5 脚本,得到的样本分布参数为:平均 0.322655、分散 1.419552、非对称 0.990362、中位数 0.023030。dlt 设 0.05、G0 设 0.25,正态近似下 dlt 四分位是 -0.0351,G0 显著等级 0.369;自举 1 万次的结果几乎一致(四分位 -0.0136,等级 0.3727)。 按准则算出的 r_opt(δ)=0,意味着这套系统在当前数据下被直接禁用——要么样本不够,要么得拉更长的交易历史。即便前面附录的图看着漂亮,统计口径不买账。 根子在用的标准太“通用”。它覆盖了我们研究过的所有系统,而那些系统多是左截尾分布、理论期望可能无限,平均收益向对称正态收敛极慢。本例系统的偏度 0.99、中位数远小于均值,正是“砍亏损、让利润跑”的典型右偏。 想落地得更准,得切到参数化方法:利用具体系统和资产价格序列的信息做推测,但不扭曲真实分布,也就是处在“一般情况”里说的第二种变体。计算更重,但特定品种上更贴。 亏损不单来自趋势反转这类意外,更多时候是噪音和波动把系统拖垮。若系统分不清这两类原因、压不住它们的干扰,外汇和贵金属这种高杠杆品种上就别用。 下面这段是上述脚本的核心片段,可直接在 MT5 里建 r_cmn.mq5 跑通: #include <Math\Stat\Normal.mqh> // 引入正态分布统计库 #include <Math\Stat\Uniform.mqh> // 引入均匀分布库(自举备用) #include <Graphics\Graphic.mqh> // 图形库,绘图用 #define N 30 // 交易系列长度:只取30笔 #define NB 10000 // 为自举生成的样本数:1万次重采样 double G0=0.25; // 最低平均收益阈值 double dlt=0.05; // 显著等级 alpha void OnStart() // 脚本入口 { double a[N]= // 30笔交易收益数组 { -0.7615,0.2139,0.0003,0.04576,-0.9081,0.2969, // 前6笔
- 6360,-0.3689,-0.6934,0.8549,1.8484,-0.9745,
-0.0325,-0.5037,-1.0163,3.4825,-0.1873,0.4850,
- 9643,0.3734,0.8480,2.6887,-0.8462,0.5375,
-0.9141,0.9065,1.9506,-0.2472,-0.9218,-0.0775 }; double A=MathMean(a); // 算均值 double S2=MathVariance(a); // 算方差 double Sk=MathSkewness(a); // 算偏度 double Md=MathMedian(a); // 算中位数 Print("样本分布参数:"); // 输出标题 PrintFormat("平均: %f, 分散: %f, 非对称: %f, 中位数: %f",A,S2,Sk,Md); PrintFormat("交易数量: %d, dlt 值: %f, G0 值: %f",N,dlt,G0); // 通过正态分布近似 Print("通过正态分布近似:"); double q0, p0; // 四分位与显著等级变量 int ner; // 错误码 q0=MathQuantileNormal(dlt,A,MathSqrt(S2/N),ner); // 正态下dlt分位 if(!MathIsValidNumber(q0)) {Print("错误 ",ner); return;} // 校验失败退出 Print("dlt 四分位: ",q0); p0=MathCumulativeDistributionNormal(G0,A,MathSqrt(S2/N),ner); // G0累积概率
class="macro">#include <Math\Stat\Normal.mqh> class="macro">#include <Math\Stat\Uniform.mqh> class="macro">#include <Graphics\Graphic.mqh> class="macro">#define N class="num">30 class=class="str">"cmt">// 交易系列长度 class="macro">#define NB class="num">10000 class=class="str">"cmt">// 为自举生成的样本数 class="type">class="kw">double G0=class="num">0.25; class=class="str">"cmt">// 最低平均收益 class="type">class="kw">double dlt=class="num">0.05; class=class="str">"cmt">// 显著等级 class="type">void OnStart() { class="type">class="kw">double a[N]= { -class="num">0.7615,class="num">0.2139,class="num">0.0003,class="num">0.04576,-class="num">0.9081,class="num">0.2969, class=class="str">"cmt">// 交易收益 class="num">2.6360,-class="num">0.3689,-class="num">0.6934,class="num">0.8549,class="num">1.8484,-class="num">0.9745, -class="num">0.0325,-class="num">0.5037,-class="num">1.0163,class="num">3.4825,-class="num">0.1873,class="num">0.4850, class="num">0.9643,class="num">0.3734,class="num">0.8480,class="num">2.6887,-class="num">0.8462,class="num">0.5375, -class="num">0.9141,class="num">0.9065,class="num">1.9506,-class="num">0.2472,-class="num">0.9218,-class="num">0.0775 }; class="type">class="kw">double A=MathMean(a); class="type">class="kw">double S2=MathVariance(a); class="type">class="kw">double Sk=MathSkewness(a); class="type">class="kw">double Md=MathMedian(a); Print("样本分布参数:"); PrintFormat("平均: %f, 分散: %f, 非对称: %f, 中位数: %f",A,S2,Sk,Md); PrintFormat("交易数量: %d, dlt 值: %f, G0 值: %f",N,dlt,G0); class=class="str">"cmt">// 通过正态分布近似 Print("通过正态分布近似:"); class="type">class="kw">double q0, p0; class="type">int ner; q0=MathQuantileNormal(dlt,A,MathSqrt(S2/N),ner); if(!MathIsValidNumber(q0)) {Print("错误 ",ner); class="kw">return;} Print("dlt 四分位: ",q0); p0=MathCumulativeDistributionNormal(G0,A,MathSqrt(S2/N),ner);
「自举与正态近似算出最小样本量」
这段脚本把前面算出的 G0 显著等级先做了合法性校验,不合法直接退出,避免后续统计全建立在脏数据上。 自举部分用 GetTickCount 做随机种子,从原样本 a 里重采样 NB 次,每次算均值塞进 s 数组;统计小于 G0 的次数占比,得到经验显著等级 p0,同时用 MathQuantile 取 dlt 对应的分位值 q[0]。 正态近似那段从 nmin=1 循环到 999,用 MathQuantileNormal 以 S2/nmin 为方差近似,一旦 q0 超过 G0 就 break,输出「最小交易数 nmin」。在 MT5 里跑这段代码,你能直接看到自己策略需要多少笔成交,才让观测优势脱离随机噪声——外汇和贵金属杠杆高,样本不够时任何「优势」都可能是运气,务必用这套自检。 sample 函数是自举的底层,用 MathRandomUniform 在 [0,N) 均匀抽索引,nc==N 时强制拉回 N-1 防越界;抽到的原值写进 b 完成一次重采样。把 N、NB、dlt、G0、S2 接进你自己的成交序列,就能复算专属的 nmin。
if(!MathIsValidNumber(p0)) {Print("MathIsValidNumber(p0) 错误 ",ner); class="kw">return;} Print("G0 的显著等级: ",p0); class=class="str">"cmt">// 自举 MathSrand(GetTickCount()); class="type">class="kw">double b[N],s[NB]; p0=class="num">0; for(class="type">int i=class="num">0;i<NB;++i) { sample(a,b); s[i]=MathMean(b); if(s[i]<G0) ++p0; } p0/=NB; class="type">class="kw">double p[class="num">1],q[class="num">1]; p[class="num">0]=dlt; if(!MathQuantile(s,p,q)) {Print("MathQuantile() 错误"); class="kw">return;} Print("自举近似"); Print("dlt 四分位: ",q[class="num">0]); Print("G0 的显著等级: ",p0); class=class="str">"cmt">// 近似值 nmin(通过正态分布近似) class="type">int nmin; for(nmin=class="num">1; nmin<class="num">1000;++nmin) { q0=MathQuantileNormal(dlt,A,MathSqrt(S2/nmin),ner); if(!MathIsValidNumber(q0)) {Print("错误 ",ner); class="kw">return;} if(q0>G0) class="kw">break; } Print("最小交易数 nmin(通过正态分布近似)):"); PrintFormat("不小于 %d, dlt 四分位的值: %f",nmin,q0); } class="type">void sample(class="type">class="kw">double &a[],class="type">class="kw">double &b[]) { class="type">int ner; class="type">class="kw">double dnc; for(class="type">int i=class="num">0; i<N;++i) { dnc=MathRandomUniform(class="num">0,N,ner); if(!MathIsValidNumber(dnc)) {Print("MathIsValidNumber(dnc) 错误 ",ner); ExpertRemove();} class="type">int nc=(class="type">int)dnc; if(nc==N) nc=N-class="num">1; b[i]=a[nc]; } }