MQL5中的替代风险回报标准·进阶篇
(2/3)·夏普比率假设收益正态,实盘权益曲线常背离,本篇给出可复刻的替代指标实现路径
「从净值曲线抠出均值回归版 Burke 比率」
这段代码实现的是 Mean return based Burke ratio,核心思路是用净值序列 in_ec[] 先拆出逐期收益率,再挑出最大的若干回撤做分母惩罚。外汇与贵金属 EA 回测里,单纯看平均收益容易被少数大回撤掩盖风险,这个指标倾向把回撤平方求和后开根,概率上更保守。 函数开头先 ArrayResize(rets, insize-1),把收益率数组长度锁成净值点数减一;若分配失败直接返回 0 并打印错误。随后 for 循环里 rets[i-1] = (in_ec[i]/in_ec[i-1]) - 1.0 逐期算简单收益,这一步无任何平滑。 n_highestdrawdowns 若传 0 或负数,会自动取 insize/20 作为回撤取样数——例如 2000 根净值的日线大约抽 100 个最深回撤。MaxNDrawdowns 把结果塞进 outdd[],sumdd 累加每个回撤的平方。 最终返回 MathMean(rets) 除以 sqrt((1.0/insize) * sumdd)。在 MT5 里把这段直接挂到回测报告后处理脚本,能快速对比不同品种(如 XAUUSD、EURUSD)的 Burke 比率差异,但需注意样本外表现可能偏移,杠杆类品种高风险。
if(!ArrayRemove(v,class="num">0,i)) { Print(__FUNCTION__, " error , ArrayRemove: ",GetLastError()); class="kw">return class="kw">false; } else break; } else break; } } size=ArraySize(v); if(size && size<=num_drawdowns) { if(ArrayCopy(out_dd,v)<size) { Print(__FUNCTION__, " error ", GetLastError()); class="kw">return class="kw">false; } else class="kw">return (true); } if(ArrayCopy(out_dd,v,class="num">0,size-num_drawdowns,num_drawdowns)<num_drawdowns) { Print(__FUNCTION__, " error ", GetLastError()); class="kw">return class="kw">false; } class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Mean class="kw">return based Burke ratio | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double meanreturns_burke(class="type">class="kw">double &in_ec[],class="type">int n_highestdrawdowns=class="num">0) { class="type">class="kw">double outdd[]; class="type">class="kw">double rets[]; class="type">class="kw">double sumdd=class="num">0; class="type">int insize=ArraySize(in_ec); if(ArrayResize(rets,insize-class="num">1)<insize-class="num">1) { Print(__FUNCTION__," Memory allocation error ",GetLastError()); class="kw">return class="num">0; } for(class="type">int i=class="num">1; i<insize; i++) rets[i-class="num">1] = (in_ec[i]/in_ec[i-class="num">1]) - class="num">1.0; if(n_highestdrawdowns<=class="num">0) n_highestdrawdowns=class="type">int(insize/class="num">20); if(MaxNDrawdowns(n_highestdrawdowns,in_ec,outdd)) { for(class="type">int i=class="num">0; i<ArraySize(outdd); i++) sumdd+=(outdd[i]*outdd[i]); class="kw">return MathMean(rets)/(MathSqrt((class="num">1.0/class="type">class="kw">double(insize)) * sumdd)); } else class="kw">return class="num">0; }
净盈利和回撤比的盲区
NPMD 把净利润除以单次最大回撤,和 Burke 比率思路接近,但分母只取一个极端回撤值。实现上靠 netProfiMaxDD() 吃一条净值序列,代码里调 MaxNDrawdowns(1,...) 抓那根最深的坑。 这类指标被搬出来,本意是堵夏普比率的漏洞:不罚异常大赚、还没参数。可一旦分母用绝对回撤,策略只要下行峰值温和,分数就会被抬上去。 拿权益曲线可视化比一比就露馅:蓝线回报率最高,NPMD 和 Burke 却低于别的线;绿线夏普分最高,但几条模拟曲线之间夏普差得小得多。绝对回撤可能过度强调风险,横向比策略时这两个比率有误导倾向。 外汇与贵金属策略回测波动大,用 NPMD 筛系统前,建议先开 MT5 把净值数组导进去跑一遍下面的函数,看自己策略的最大回撤是不是被单点拉偏。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Net profit to maximum drawdown ratio | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double netProfiMaxDD(class="type">class="kw">double &in_ec[]) { class="type">class="kw">double outdd[]; class="type">int insize=ArraySize(in_ec); if(MaxNDrawdowns(class="num">1,in_ec,outdd)) class="kw">return ((in_ec[insize-class="num">1]-in_ec[class="num">0])/outdd[class="num">0]); else class="kw">return class="num">0; }
◍ 怎么读回撤比率的正负
Burke 比率和 NPMD 比率本质上都是风险调整后的绩效刻度。数值越高,代表同样的风险敞口下策略产出的超额回报越厚;这不是绝对收益多少,而是「每单位回撤代价换来了多少补偿」。 当比率大于 0,说明策略在统计窗口内相对所计风险拿到了正超额回报,样本里亏的都被赚回来了还有富余。若比率掉到 0 以下,则意味着承担的那段回撤没有被足够补偿,策略的风险账是亏的。 在 MT5 里把这两个比率接出来跑一遍历史品种,重点看贵金属与直盘货币对——这类品种跳空多、回撤陡,比率正负切换往往比股指更敏感。外汇和贵金属杠杆高,单一比率转正不代表能实盘扛住连亏,只能说该段样本风险账偏向盈余。
「用偏矩比换掉夏普的盲区」
偏矩比(Partial Moment Ratio)是夏普比率的一类替代指标,核心建立在半方差统计上,专门拆开看策略的下行风险与上行潜力,而不是把正负波动混在一起算。先定一个阈值回报(常用最小可接受回报或无风险率),再逐次观测实际回报 x 与阈值 thresh 的差:低于阈值的偏差进入低偏矩 LPM,高于阈值的进入高偏矩 HPM,二者都取平方偏差累加。 公式里 max(·,0) 保证只计单侧差值,n 是偏矩阶数。当 n=0 时,LPM 直接退化为“观测收益小于阈值的概率”,HPM 则是“大于阈值的概率”;N 为观测总次数。这套口径比回撤率更聚焦阈值两侧的回报结构。 落地可验的两个比是广义 Omega 与上行的 UPR(Upside Potential Ratio)。在 MT5 策略测试器导出权益曲线回报序列后,用 n=1 或 n=2 的 LPM/HPM 手算一遍,能看出夏普看不出的下行偏度——外汇与贵金属杠杆高,这类单边风险视角更贴近实盘爆仓逻辑。
用二阶下偏矩算 Omega 比率
广义 Omega 由两个要素决定:一个是展开阶数 n,另一个是计算低阶偏矩(LPM)时用的回报阈值。本文实现的 omega() 直接把收益序列数组传进去,内部固定用 2 阶下偏矩、阈值取 0,也就是只统计不高于 0 的回撤部分。 代码先建一个与输入等长的 rb 数组并全置 0,这相当于把「无风险/基准回报」设成零轴。随后调用 partialmoment(2, rt, rb) 取出二阶下偏矩,开平方后得到下侧波动的尺度,用均值除以它得出 Omega 类比率。 在 MT5 里把这段塞进 EA 或脚本,喂入某段 EURUSD 的逐笔收益率(比如最近 500 根 H1 收盘差分),若均值 0.00012、下偏矩开方得 0.0018,算出来约 0.067——数字越小越说明下行分散吞掉了平均盈利,外汇与贵金属品种波动大,这种指标仅作概率参考,实盘前务必自己跑一遍验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|omega ratio | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double omega(class="type">class="kw">double &rt[]) { class="type">class="kw">double rb[]; if(ArrayResize(rb,ArraySize(rt))<class="num">0) { Print(__FUNCTION__, " Resize error ",GetLastError()); class="kw">return class="num">0; } ArrayInitialize(rb,class="num">0.0); class="type">class="kw">double pmomentl=MathPow(partialmoment(class="num">2,rt,rb),class="num">0.5); if(pmomentl) class="kw">return MathMean(rt)/pmomentl; else class="kw">return class="num">0; }
◍ 上行潜能比率
UPR使用两个n阶项(公式中的n1和n2)和阈值。n1确定分子中的HPM程度,n2指定分母的LPM程度。 与Omega性能度量的实现类似,upsidePotentialRatio()函数计算UPR比率。该计算还使用2阶偏矩和定义为零的回报阈值。 偏矩计算是在 partialmoment()函数中实现的。它需要一个无符号整数、两个 double 类型和布尔值的数组的阶数作为输入。第一个数组应包含观察到的回报,第二个数组应包括计算中使用的阈值或基准回报。布尔值确定要计算的偏矩的类型,对于较高的偏矩为true,对于较低的偏矩则为false。 查看我们的净值曲线的Omega和UPR比率,我们注意到评级与夏普比率相似。 与波动较大的净值曲线相比,一致性再次受到青睐。Omega 看起来特别像是夏普比率的可行替代品。 [CODE] <span class="comment">//+------------------------------------------------------------------+</span>
| <span class="comment">// | Upside potential ratio | </span> |
|---|
<span class="comment">//+------------------------------------------------------------------+</span> <span class="keyword">double</span> upsidePotentialRatio(<span class="keyword">double</span> &rt[]) { <span class="keyword">double</span> rb[]; <span class="keyword">if</span>(<span class="functions">ArrayResize</span>(rb,<span class="functions">ArraySize</span>(rt))<<span class="number">0</span>) { <span class="functions">Print</span>(<span class="keyword">__FUNCTION__</span>, <span class="string">" Resize error "</span>,<span class="functions">GetLastError</span>());