单一资产交易顺序中的风险评估·进阶篇
📉

单一资产交易顺序中的风险评估·进阶篇

(2/3)·直接套用凯利类公式常把账户拖进深回撤,这篇把不确定性拆进每笔交易的收益与风险里

实战向进阶 第 2/3 篇
不少交易者把最优f当固定仓位系数直接上实盘,结果遇到连续止损就濒临爆仓。文斯的理论依赖已发生的成交历史,而真实入场时那些值根本未知。把历史峰值当未来边界,是这类仓位法最隐蔽的坑。

◍ 这套模型后续要补哪些拼图

上面只是把主题撕开了一道口子。后续文章会沿着几条实线往下推,你可以先记在 MT5 测试计划里。 要把交易模型当成独立、可分步执行的序列来用,得先对资产价格和交易算法做明确假设——这不是哲学讨论,是回测前必须落地的约束条件。 离场规则必须被指定成具体形态,比如固定止损/止盈、固定尾随停止,否则概率推导没有边界。 后面还会展示怎么用概率论搭缺口系统,并触及机器学习辅助。开 MT5 把尾随停止和固定止盈分两组先跑一遍 EURUSD H1,能直观看到假设差异带来的权益曲线分化。

「脚本跑出来的风险值对照」

把 30 笔历史交易收益率塞进 r_intro.mq5 脚本,扫 100 个风险比例 r,得到一组可直接在 MT5 复现的数值:rc=0.984、rg=0.118、rd=0.0394、ra=0.0394、rmax=0.315、ropt=0.0394。其中 rmax 对应的复合净值 cn=4.018,几何收益 g=0.151,最大回撤 d=0.393;ropt 对应的 cn=1.417,g=0.297,d=0.904。 用 rmax 当仓位风险,账面利润倾向冲到 300%(cn 翻 4 倍),但回撤会大于 60%;换 ropt 则利润约 40%、回撤压在 10% 以内。两者差距说明:盲目追高 r 换来的高收益,是用深回撤和平均收益波动买单的,外汇与贵金属杠杆品种里这种回撤足以触发强平,属高风险操作。 结论很直接——这套样本里该拿 ropt=0.0394 做系统风险值;rmax 与 ropt 之间释放出的风险预算,可挪去挂新系统做分散,而不是堆单系统仓位。

MQL5 / C++
class="macro">#include <Graphics\Graphic.mqh>
class="macro">#define N class="num">30
class="macro">#define NR class="num">100
class="macro">#class="kw">property script_show_inputs
input class="type">int ngr=class="num">0;    class=class="str">"cmt">// 显示的图表数量
class="type">class="kw">double G0=class="num">0.25;     class=class="str">"cmt">// 最低平均收益
class="type">class="kw">double D0=class="num">0.9;      class=class="str">"cmt">// 最低最小增益
                    class=class="str">"cmt">// 初始资本 c0 被假定为等于 class="num">1
class="type">void OnStart()
  {
   class="type">class="kw">double A0,A,rc,rg,rd,ra,rmax,ropt,r[NR],g[NR],d[NR],cn[NR],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
     };
   A0=a[ArrayMinimum(a)];
   A=class="num">0; for(class="type">int i=class="num">0; i<N;++i) A+=a[i]; A/=N;
   rc=class="num">1; if(A0<-class="num">1) rc=-class="num">1/A0;
   class="type">class="kw">double c[N];
   r[class="num">0]=class="num">0; cn[class="num">0]=class="num">1; g[class="num">0]=A; d[class="num">0]=class="num">1;
   for(class="type">int i=class="num">1; i<NR;++i)
     {
      r[i]=i*rc/NR;
      cn[i]=class="num">1; for(class="type">int j=class="num">0; j<N;++j) cn[i]*=class="num">1+r[i]*a[j];
      g[i]=(MathPow(cn[i],class="num">1.0/N)-class="num">1)/r[i];
      c[class="num">0]=class="num">1+r[i]*a[class="num">0]; for(class="type">int j=class="num">1; j<N;++j) c[j]=c[j-class="num">1]*(class="num">1+r[i]*a[j]); d[i]=dcalc(c);
     }
   class="type">int nrg,nrd,nra,nrmax,nropt;
   class="type">class="kw">double b[NR];
   for(class="type">int i=class="num">0; i<NR;++i) b[i]=MathAbs(g[i]-G0);
   nrg=ArrayMinimum(b); rg=r[nrg];
   for(class="type">int i=class="num">0; i<NR;++i) b[i]=MathAbs(d[i]-D0);
   nrd=ArrayMinimum(b); rd=r[nrd];
   nra=MathMin(nrg,nrd); ra=r[nra];
   nrmax=ArrayMaximum(cn); rmax=r[nrmax];
   nropt=MathMin(nra,nrmax); ropt=r[nropt];
   Print("rc = ",rc,"\nrg = ",rg,"\nrd = ",rd,"\nra = ",ra,"\nrmax = ",rmax,"\nropt = ",ropt,
         "\ng(rmax) = ",g[nrmax],", g(ropt) = ",g[nropt],

用 CGraphic 把收益曲线画出来

这段逻辑负责把前面算出的 r、g、d、cn 等数组直接渲染成图。先判断 ngr 不在 1~5 就直接 return,意味着只支持五种图形模式,超范围不画图也不报错,调试时容易以为挂了其实只是参数越界。 ChartSetInteger(0,CHART_SHOW,false) 会关掉当前图表显示,给 750×350 的离屏绘图腾地方;CGraphic 对象建好之后,按 ngr 分支加曲线:模式1画 g=g(r) 并标出 g=G0 和 r=rg 两条参考线,模式3则在 cn(r) 上同时标出 rmax 与 ropt 对应的水平和竖直线,方便肉眼比对极值点和最优点。 模式4和5最实用——用 ropt 或 rmax 作为固定仓位系数,把每笔收益 a[j] 复利累乘成净值 c[j]:c[0]=1+ropt*a[0],之后 c[j]=c[j-1]*(1+ropt*a[j])。外汇和贵金属波动大,这种净值曲线对仓位系数的敏感度极高,实盘前务必在 MT5 策略测试器里跑一遍。 最后 graphic.CurvePlotAll() 和 Update() 出图,Sleep(30000) 让窗口停留30秒。想长期看就把 Sleep 去掉或改成交互关闭,否则脚本卡半分钟才退出。

MQL5 / C++
  "\nd(rmax) = ",d[nrmax],", d(ropt) = ",d[nropt],
  "\ncn(rmax) = ",cn[nrmax],", cn(ropt) = ",cn[nropt]);
  if(ngr<class="num">1 || ngr>class="num">5) class="kw">return;
  ChartSetInteger(class="num">0,CHART_SHOW,class="kw">false);
  CGraphic graphic;
  graphic.Create(class="num">0,"G",class="num">0,class="num">0,class="num">0,class="num">750,class="num">350);
  class="type">class="kw">double x[class="num">2],y[class="num">2];
  class="kw">switch(ngr)
    {
     case class="num">1: graphic.CurveAdd(r,g,CURVE_LINES,"g=g(r)");
     x[class="num">0]=class="num">0; x[class="num">1]=r[NR-class="num">1]; y[class="num">0]=G0; y[class="num">1]=G0;
     graphic.CurveAdd(x,y,CURVE_LINES,"g=G0");
     x[class="num">0]=rg; x[class="num">1]=rg; y[class="num">0]=g[class="num">0]; y[class="num">1]=g[NR-class="num">1];
     graphic.CurveAdd(x,y,CURVE_LINES,"r=rg");
     class="kw">break;
     case class="num">2: graphic.CurveAdd(r,d,CURVE_LINES,"d=d(r)");
     x[class="num">0]=class="num">0; x[class="num">1]=r[NR-class="num">1]; y[class="num">0]=D0; y[class="num">1]=D0;
     graphic.CurveAdd(x,y,CURVE_LINES,"d=D0");
     x[class="num">0]=rd; x[class="num">1]=rd; y[class="num">0]=d[class="num">0]; y[class="num">1]=d[NR-class="num">1];
     graphic.CurveAdd(x,y,CURVE_LINES,"r=rd");
     class="kw">break;
     case class="num">3: graphic.CurveAdd(r,cn,CURVE_LINES,"cn=cn(r)");
     x[class="num">0]=class="num">0; x[class="num">1]=rmax; y[class="num">0]=cn[nrmax]; y[class="num">1]=cn[nrmax];
     graphic.CurveAdd(x,y,CURVE_LINES,"cn=cn(rmax)");
     x[class="num">0]=rmax; x[class="num">1]=rmax; y[class="num">0]=cn[NR-class="num">1]; y[class="num">1]=cn[nrmax];
     graphic.CurveAdd(x,y,CURVE_LINES,"r=rmax");
     x[class="num">0]=class="num">0; x[class="num">1]=ropt; y[class="num">0]=cn[nropt]; y[class="num">1]=cn[nropt];
     graphic.CurveAdd(x,y,CURVE_LINES,"cn=cn(ropt)");
     x[class="num">0]=ropt; x[class="num">1]=ropt; y[class="num">0]=cn[NR-class="num">1]; y[class="num">1]=cn[nropt];
     graphic.CurveAdd(x,y,CURVE_LINES,"r=ropt");
     class="kw">break;
     case class="num">4: c[class="num">0]=class="num">1+ropt*a[class="num">0]; for(class="type">int j=class="num">1; j<N;++j) c[j]=c[j-class="num">1]*(class="num">1+ropt*a[j]);
     graphic.CurveAdd(c,CURVE_LINES,"Equity, ropt");
     class="kw">break;
     case class="num">5: c[class="num">0]=class="num">1+rmax*a[class="num">0]; for(class="type">int j=class="num">1; j<N;++j) c[j]=c[j-class="num">1]*(class="num">1+rmax*a[j]);
     graphic.CurveAdd(c,CURVE_LINES,"Equity, rmax");
     class="kw">break;
    }
  graphic.CurvePlotAll();
  graphic.Update();
  Sleep(class="num">30000);

◍ 用 dcalc 榨出多周期协同的最小增益

上面这段逻辑藏在指标重绘收尾之后,核心是一个叫 dcalc 的函数:把若干周期数组 c1…cN 喂进去,返回它们之间的最小增益 d。它默认 c0 恒为 1,相当于把基准周期归一化,其他周期都相对它说话。 函数里先卡两道防线——只要 c[0] 或任一 c[i] 小于等于 0 直接返回 0,意味着数据缺失或无效周期不参与协同计算。随后用 mx、mn 双游标滚动:遇到更小值就更新 mn 并拿 mn/mx 去逼紧 d;遇到更大值则把 mx 和 mn 同时抬到该值,相当于重启一段上升区间的度量。 实战里你可以把不同时间帧的动量比值塞进 c[],d 越接近 1 说明多周期方向越一致,越接近 0 则分化越严重。外汇与贵金属杠杆高、滑点跳变频繁,d 只描述概率性协同,不能当成进场保证,开 MT5 把 N 改成你常看的周期数跑一遍最直观。

MQL5 / C++
ChartSetInteger(class="num">0,CHART_SHOW,true);
   graphic.Destroy();
   }
class=class="str">"cmt">// 函数 dcalc() 接收 c1, c2, ... cN 数值数组, 并
class=class="str">"cmt">// 返回最小增益 d。Assume that c0==class="num">1
class="type">class="kw">double dcalc(class="type">class="kw">double &c[])
  {
  if(c[class="num">0]<=class="num">0) class="kw">return class="num">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;
  }

「回撤样本跑出来的两个关键数」

下面这段脚本把 30 笔交易的收益序列拿去做置换重采样,输出两个能直接用的数。样本 0.05 分位上的 rd[] 算出来是 0.021647,也就是说在 5% 显著水平下,风险敞口参数得压到约 0.022 才够安全;比起引言里拿到的 0.039,等于要把风险砍掉将近一半。 如果实际回撤超过目标值 10%,按这个阈值系统大概率有缺陷,不该直接上实盘。此时把合格系统误判 reject 的错误概率固定在 δ=0.05,也就是 5%。第二行给出 rd=0.039358 时 pd=0.584,说明初始那串交易的回撤幅度偏小,处在平均水准附近。 不过 30 笔样本实在太薄,结论的置信度有限。真要评估一个贵金属或外汇 EA,建议把 NT 从 500 拉到几千甚至上万,看大样本下 rd 分位和 pd 怎么漂。外汇和贵金属杠杆高,回测温和不代表 live 不爆,重采样结果只给概率倾向,不当买卖依据。 代码里 N=30、NR=NT=500 是写死的,想扩样本直接改宏就行;a[] 数组就是那 30 笔盈亏率,换成你自己的实盘导出数据就能复跑。

MQL5 / C++
class="macro">#include <Math\Stat\Uniform.mqh>
class="macro">#define N class="num">30      class=class="str">"cmt">// 交易系列长度
class="macro">#define NR class="num">500    class=class="str">"cmt">// 按间隔分割的段落进入  [class="num">0,rc]
class="macro">#define NT class="num">500    class=class="str">"cmt">// 产生的排列数量
class="type">class="kw">double D0=class="num">0.9;    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 A0,rc,r[NR],d[NR],rd[NT],a[N]=class=class="str">"cmt">//A,rg,ra,rmax,ropt,g[NR],cn[NR],
     {
      -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
     };
   A0=a[ArrayMinimum(a)];
   rc=class="num">1; if(A0<-class="num">1) rc=-class="num">1/A0;
   for(class="type">int i=class="num">0; i<NR;++i) r[i]=i*rc/NR;
   class="type">class="kw">double b[NR],c[N];
   class="type">int nrd;
   MathSrand(GetTickCount());
   for(class="type">int j=class="num">0; j<NT;++j)
     {
      trps(a,N);
      for(class="type">int i=class="num">1; i<NR;++i)
        {
         c[class="num">0]=class="num">1+r[i]*a[class="num">0];
         for(class="type">int k=class="num">1; k<N;++k) c[k]=c[k-class="num">1]*(class="num">1+r[i]*a[k]);
         d[i]=dcalc(c);
        }
      for(class="type">int i=class="num">0; i<NR;++i) b[i]=MathAbs(d[i]-D0);
      nrd=ArrayMinimum(b); rd[j]=r[nrd];
     }
   class="type">class="kw">double p[class="num">1],q[class="num">1]; p[class="num">0]=dlt;
   if(!MathQuantile(rd,p,q)) {Print("MathQuantile() 错误"); class="kw">return;}
   PrintFormat("样本 %f-quantile rd[] 等于 %f",p[class="num">0],q[class="num">0]);
   class="type">class="kw">double rd0=class="num">0.03935845714847978; class=class="str">"cmt">// 引言中获得的 rd 值
   class="type">class="kw">double pd0=class="num">0;                  class=class="str">"cmt">// pd 的对应值
   for(class="type">int j=class="num">0; j<NT;++j) if(rd[j]<rd0) ++pd0; pd0/=NT;
   PrintFormat("对于 rd = %f 值 pd = %f",rd0,pd0);
  }
class=class="str">"cmt">// 函数 dcalc() 接收 c1, c2, ... cN 数值数组, 并
class=class="str">"cmt">// 返回最小增益 d。Assume that c0==class="num">1
class="type">class="kw">double dcalc(class="type">class="kw">double &c[])
  {
   if(c[class="num">0]<=class="num">0) class="kw">return class="num">0;
把重复劳动交给小布
这些针对不同交易顺序的回撤模拟与风险分布诊断,小布盯盘的AIGC已内置,打开对应品种页即可看到,你只需判断当前波动区是否匹配模型假设。

常见问题

最优f基于历史成交最大化收益,但真实序列的止损频率和收益比在入场时未知,过度依赖历史峰值会放大连续亏损下的资本侵蚀,回撤可能超出承受区间。
经过对入场、止损、离场价格的重排,买卖两类交易均可化为C1=C0(1+ra),其中r为止损份额、a为收益倍数,模型对称性使得公式统一。
小布可自动跑多组交易顺序的蒙特卡洛式回撤推演,标出r取值敏感区,省去手动套用公式的重复计算,你专注确认品种波动结构是否偏离假设。
它针对价格按单位资产近似、最小交易量固定等简化假设之外的现实噪声,说明已知量如何限制r的选取,避免把确定性模型直用于随机市况。
示例仅用于印证理论推导,未嵌入滑点、流动性与品种特异性,直接实盘可能因结构错配放大外汇贵金属的高风险暴露。