利用文斯 (Vince) 进行资金管理。 作为 MQL5 向导模块实现(基础篇)
📘

利用文斯 (Vince) 进行资金管理。 作为 MQL5 向导模块实现(基础篇)

第 1/3 篇

「把文斯仓位法做成 MT5 向导模块」

文斯(Vince)资金管理核心是用固定分数模型控制每笔风险,避免在连亏期把本金打穿。2018 年 4 月发布的这套实现把它做成了 MQL5 向导模块,交易者建 EA 时可直接勾选,不用手写道仓逻辑。 该模块在原始发布页累计被查看 10961 次、收到 8 条讨论,说明手动派和量化派都常卡在仓位系数落地这一步。外汇与贵金属杠杆高,连亏放大时爆仓概率陡增,任何仓位法都只是降低风险而非消除风险。 想验证就开 MT5:调出 EA 向导 → 选信号类 → 在资金管理里找 Vince 实现 → 把风险分数先设 0.01 跑历史。看净值曲线回撤是否比你原先的固定手数更平,再决定实盘分数。

手数比指标更先决定生死

很多交易者把精力全花在找买卖信号上,却忽略了一个更底层的变量:每笔交易下多少手。拉尔夫·文斯在《资金管理中的数学》里讲得很直白——执行交易的资金额度,和入场信号本身一样关键,甚至更先决定账户能否活下去。 两个被大众奉为常识的说法,文斯直接戳破。其一是“风险越高利润越高”,认为潜在利润是潜在风险的线性函数,这不成立;杠杆堆出来的暴露,在外汇和贵金属这种高波动品种里更可能一次性吞掉本金。 其二是“多元化能减少亏损”。文斯的原话是:多元化能减亏,但只在很有限的程度内,远低于大多数交易者的想象。把欧元、黄金、原油都开一遍,并不等于把风险摊薄了。 打开 MT5 的“交易”面板,把每笔 0.1 手和 0.5 手的回撤曲线拉出来对比,你会看到手数缩放对净值曲线的塑形效果,往往比换一个指标更猛。

◍ 复利下注为何会反噬盈利系统

用条件系统跑两笔交易就能看清问题:第一笔赚 50%,第二笔亏 40%。若不动用利润再投,总资产从 100 到 110,净赚 10%;若全程复利再投,第二笔按 150 的 40% 亏掉 60,账户只剩 90,净亏 10%。交易顺序不影响这个结果,说明复利模式下的手数逻辑必须和固定手数策略彻底分开。 文斯资金管理的核心,就是在复利框架里找最优手数系数。先从最简单的旋转硬币模型入手:赢拿 2 美元、输赔 1 美元,胜负概率各 1/2,本金 100。若一把梭 100,赢则 200,输则归零且无法续局;在无限游戏里重仓必死。把下注比例压到 0.1、0.2、0.5、0.7、1.0 分别跑 +2、-1 交替序列,期末资本依次是 126、141、100、37、0——存在某个中间系数使利润最大化。 不变赢率和赔率时,凯利公式直接给解:f=((B+1)*P-1)/B,B 为赢亏比,P 为胜率。实战中盈亏幅度乱跳,凯利失效,只能对历史成交流做数值搜索。文斯引入 HPR(持有期回报),单笔 HPR=1+f*回报/最大可能亏损,全局目标变为最大化连乘 HPR1*HPR2*…*HPRn。 下面这段 MQL5 用 {+2,-1} 数组扫 f,从 0.01 到 1.00 步进,抓出令终值最大的 K。可丢进 MT5 脚本跑,验证凯利结果。 别把高期望当护身符 流 {-1,1,1,1,1,1,1,1,1,1} 的数学期望极漂亮,算出来 f=0.8。意思是按统计最优应允许单次回撤 80% 本金——真撞上那根阴线,账户直接残废。外汇和贵金属自带高杠杆高风险,f 越大越可能一夜回到解放前,心理承受力得先于公式入场。 优化只对正期望系统有意义:亏损系统最优 f=0,手数管理救不活负期望;若历史全盈利无回撤,f=1 满仓即可。下方代码即搜索逻辑,改 arr[] 就能测你自己的成交序列。

MQL5 / C++
class="type">class="kw">double PL[]={class="num">9,class="num">18,class="num">7,class="num">1,class="num">10,-class="num">5,-class="num">3,-class="num">17,-class="num">7};   class=class="str">"cmt">// 来自本书的利润/损失数组
class="type">class="kw">double Arr[]={class="num">2,-class="num">1};
class="type">void OnStart()
{
SearchMaxFactor(Arr);                   class=class="str">"cmt">//盈亏或任何其它数组
}
class="type">void SearchMaxFactor(class="type">class="kw">double &arr[])
{
class="type">class="kw">double MaxProfit=class="num">0,K=class="num">0;                 class=class="str">"cmt">// 最大盈利
                                    class=class="str">"cmt">// 和利润对应的比率
for(class="type">int i=class="num">1;i<=class="num">100;i++)
{
   class="type">class="kw">double k,profit,min;
   min =MathAbs(arr[ArrayMinimum(arr)]); class=class="str">"cmt">// 查找数组中的最大亏损
   k =i*class="num">0.01;                           class=class="str">"cmt">// 系数从0.01步进到1.class="num">00
   profit =class="num">1;
class=class="str">"cmt">// 用设定的系数查找回报
      for(class="type">int j=class="num">0;j<ArraySize(arr);j++)
   {
       profit =profit*(class="num">1+k*arr[j]/min);
   }
class=class="str">"cmt">// 比较最大利润 
   if(profit>MaxProfit)
   {
   MaxProfit =profit;
   K=k;
   }
}
Print("优化 K  ",K," 盈利  ",NormalizeDouble(MaxProfit,class="num">2));
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 Graphic.mq5 |
class=class="str">"cmt">//|                                                                 Orangetree |
class=class="str">"cmt">//|                                                    [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Orangetree"

「用凯利变体给两笔盈亏找最优仓位系数」

这段脚本把「固定分数仓位」的优化思路直接落到 MT5 脚本里:给定一组盈亏序列 PL,遍历 0.01~1.00 的仓位系数 k,算出每个 k 下账户的累计增长倍数,再挑出使倍数最大的那个 k。 原文示例只放了 double PL[]={2,-1}; 两笔数据,即先赚 2 个单位、再亏 1 个单位。代码里先把 PL 的最小值取绝对值当成归一化分母 min,避免除零;若最小盈亏 ≥0 则直接判定 f=1 满仓并退出。 核心双层循环在 X 上扫 100 个 k 值,内层把 profit 初始化为 1,再对每个 PL 元素做 profit*=(1+k*PL[i]/min)。最终 Y 数组存每个 k 的终值,maxX/maxY 记录峰值。注意外汇与贵金属杠杆高,这种回测只反映历史序列特征,实盘可能严重偏离。 最后用 CGraphic 把 X-Y 曲线画成蓝点线,峰值标红点,并在日志 Print 出「最大因子 f」。你把这串代码贴进 MT5 脚本跑一遍,改 PL 数组就能看自己交易记录的最优 f 落在哪。

MQL5 / C++
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#include<Graphics\Graphic.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 脚本程序 start 函数                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//class="type">class="kw">double PL[]={class="num">9,class="num">18,class="num">7,class="num">1,class="num">10,-class="num">5,-class="num">3,-class="num">17,-class="num">7};      // 来自本书的利润/损失数组
class="type">class="kw">double PL[]={class="num">2,-class="num">1};
class="type">void OnStart()
  {
   class="type">class="kw">double X[class="num">100]={class="num">0};
   for(class="type">int i=class="num">1;i<=class="num">100;i++)
      X[i-class="num">1]=i*class="num">0.01;
   class="type">class="kw">double Y[class="num">100];

   class="type">class="kw">double min =PL[ArrayMinimum(PL)];
   if(min>=class="num">0){Comment("f=class="num">1");class="kw">return;}
   min =MathAbs(min);
   class="type">int n = ArraySize(X);
   class="type">class="kw">double maxX[class="num">1]= {class="num">0};
   class="type">class="kw">double maxY[class="num">1] ={class="num">0};
   for(class="type">int j=class="num">0;j<n;j++)
   {
      class="type">class="kw">double k =X[j];
      class="type">class="kw">double profit =class="num">1;
      for(class="type">int i=class="num">0;i<ArraySize(PL);i++)
      {
         profit =profit*(class="num">1+k*PL[i]/min);
      }
      Y[j] =profit;
      if(maxY[class="num">0]<profit)
      {
         maxY[class="num">0] =profit;
         maxX[class="num">0] =k;
      }
   }  
   CGraphic Graphic;
   Graphic.Create(class="num">0,"Graphic",class="num">0,class="num">30,class="num">30,class="num">630,class="num">330);
   CCurve *Curve=Graphic.CurveAdd(X,Y,ColorToARGB(clrBlue,class="num">255),CURVE_LINES,"Profit");
   Curve.LinesStyle(STYLE_DOT);
   class=class="str">"cmt">//如果需要,图形可以平滑
   class=class="str">"cmt">/*Curve.LinesSmooth(true);
   Curve.LinesSmoothTension(class="num">0.8);
   Curve.LinesSmoothStep(class="num">0.2);*/
   CCurve *MAX =Graphic.CurveAdd(maxX,maxY,ColorToARGB(clrBlue,class="num">255),CURVE_POINTS,"Maximum");
   MAX.PointsSize(class="num">8);
   MAX.PointsFill(true);
   MAX.PointsColor(ColorToARGB(clrRed,class="num">255));
   Graphic.XAxis().MaxLabels(class="num">100);
   Graphic.TextAdd(class="num">30,class="num">30,"Text",class="num">255);  
   Graphic.CurvePlotAll();
   Graphic.Update();
   Print("最大因子 f =  ", maxX[class="num">0]);  
  }

相关性怎么吃掉复利多元化的收益

拿两个参数完全相同的策略 A、B 来拆:盈亏分布都是 (+2, -1),单系统优化仓位 f=0.25,账户本金对半分。先把两系统相关性拉到 1.0——四笔交替交易后合并账户从 100 走到 126.56,盈利 26.56,和拿全部资本跑单个策略没区别。 把相关性降到 0,同样四笔交易里 A、B 盈亏错开,合并账户终值 142.39,盈利 42.39,比完全同步走高出约 15.8 个点。再极端一点,相关性 -1:A 赚时 B 必亏、反之亦然,四笔跑完账户到 160.18,盈利 60.18,是三种情形里最高的。 这组数说清一件事:复利下做多元化,分散的是相关性而不是亏损本身。f=0.25 时最大单笔回撤依旧在,只有相关系数恰好 -1 的那种互斥组合能绕开——而实盘里严格 -1 相关的系统基本不存在,近似情况等同于同一品种双向锁仓,外汇和贵金属市场里这种结构极脆弱、高风险。 文斯在原著里的判断很直:多元化若做得好,是抬升回报的手法,却不必然压缩最坏情形的回撤,这和圈内「分散必降风险」的流行认知正好相反。

◍ 用 Z 分数和序列相关扒开连盈亏的底牌

在动手拟合 f 系数之前,先看清盈亏流本身有没有记忆性。连盈之后还连盈、连亏之后还连亏,这种依赖关系用肉眼看回测曲线容易误判,得靠两个统计量说话。 Z 分数衡量的是实际连串长度偏离正态假设多少个标准差。MT5 策略测试器的回测报告里直接给了这个字段:负值意味着连续盈利或连续亏损的 streak 比随机分布少,也就是盈后易亏、亏后易盈的概率偏高;正值则相反,趋势性 streak 更密集。 序列相关算的是错位对齐后的相关系数。把 {1,2,3,4,5,6,7,8,9,10} 错一位变成 {1..9} 对 {2..10},两者相关系数就是一阶序列相关。若结果显著不为 0,说明上笔盈亏对下笔有线性牵引,单一 f 值资金管理可能不够用,得考虑双系数模块。 下面这段 MQL5 把两个指标都实现了,arr 里塞每笔盈亏(正为盈、负为亏)即可直接跑。Z 函数先数胜负笔数与连串数 R,再套公式;AutoCorr 则算错位协方差除以各自离差。 double Z(double &arr[]) { int n =ArraySize(arr); int W,L,X,R=1; if(arr[0]>0) { W=1; L=0; } else { W=0; L=1; } for(int i=1;i<n;i++) { if(arr[i]>0) { W++; if(arr[i-1]<=0){R++;} } else { L++; if(arr[i-1]>0){R++;} } } X =2*W*L; double x=(n*(R-0.5)-X); double y =X*(X-n); y=y/(n-1); double Z=(n*(R-0.5)-X)/pow(y,0.5); Print(Z); return Z; } double AutoCorr(double &arr[]) { int n =ArraySize(arr); double avr0 =0; for(int i=0;i<n-1;i++) { avr0=avr0+arr[i]; } avr0=avr0/(n-1); double avr1 =0; for(int i=1;i<n;i++) { avr1=avr1+arr[i]; } avr1=avr1/(n-1); double D0 =0; double sum =0.0; for(int i=0;i<n-1;i++) { sum =sum+(arr[i]-avr0)*(arr[i]-avr0); } D0 =MathSqrt(sum); double D1 =0; sum =0.0; for(int i=1;i<n;i++) { sum =sum+(arr[i]-avr1)*(arr[i]-avr1); } D1 =MathSqrt(sum); sum =0.0; for(int i=0;i<n-1;i++) { sum =sum +(arr[i]-avr0)*(arr[i+1]-avr1); }

if(D0==0D1==0) return 1;

double k=sum/(D0*D1); return k; } 实盘前先把历史 tick 跑出来的 arr 喂进去,Z 接近 0 且 AutoCorr 贴近 0,才谈得上近似独立。外汇和贵金属杠杆高,序列相关一旦被证实,原仓位公式可能放大回撤,调参前务必用 MT5 重测。

MQL5 / C++
Z=(N(R-class="num">0.5)-Х)/((Х(Х-N))/(N-class="num">1))^(class="num">1/class="num">2)

class="type">class="kw">double Z(class="type">class="kw">double &arr[])
{
class="type">int n =ArraySize(arr);
class="type">int W,L,X,R=class="num">1;
  if(arr[class="num">0]>class="num">0)
  {
    W=class="num">1;
    L=class="num">0;
  }
  else
  {
    W=class="num">0;
    L=class="num">1;
  }
  for(class="type">int i=class="num">1;i<n;i++)
  {
    if(arr[i]>class="num">0)
    {
      W++;
      if(arr[i-class="num">1]<=class="num">0){R++;}
    }
    else
    {
      L++;
      if(arr[i-class="num">1]>class="num">0){R++;}
    }    
  }
 X =class="num">2*W*L;
 class="type">class="kw">double x=(n*(R-class="num">0.5)-X);
 class="type">class="kw">double y =X*(X-n);
 y=y/(n-class="num">1);
 class="type">class="kw">double Z=(n*(R-class="num">0.5)-X)/pow(y,class="num">0.5);  
Print(Z);
class="kw">return Z;
}
class="type">class="kw">double AutoCorr(class="type">class="kw">double &arr[])
{
  class="type">int n =ArraySize(arr);
  class="type">class="kw">double avr0 =class="num">0;
  for(class="type">int i=class="num">0;i<n-class="num">1;i++)
  {
  avr0=avr0+arr[i];
  }
  avr0=avr0/(n-class="num">1);
  class="type">class="kw">double avr1 =class="num">0;
  for(class="type">int i=class="num">1;i<n;i++)
  {
  avr1=avr1+arr[i];
  }
  avr1=avr1/(n-class="num">1);
  class="type">class="kw">double D0 =class="num">0;
  class="type">class="kw">double sum =class="num">0.0;
  for(class="type">int i=class="num">0;i<n-class="num">1;i++)
  {
  sum =sum+(arr[i]-avr0)*(arr[i]-avr0);
  }
  D0 =MathSqrt(sum);
  class="type">class="kw">double D1 =class="num">0;
  sum =class="num">0.0;
  for(class="type">int i=class="num">1;i<n;i++)
  {
  sum =sum+(arr[i]-avr1)*(arr[i]-avr1);
  }
  D1 =MathSqrt(sum);
  sum =class="num">0.0;
  for(class="type">int i=class="num">0;i<n-class="num">1;i++)
  {
  sum =sum +(arr[i]-avr0)*(arr[i+class="num">1]-avr1);
  }
  if(D0==class="num">0||D1==class="num">0) class="kw">return class="num">1;
  class="type">class="kw">double k=sum/(D0*D1);
class="kw">return k;
}

常见问题

先把近期盈亏序列算成最优f系数,再用账户净值乘系数除以单笔风险额得出基础手数,避免拍脑袋下重仓。
复利下注在连亏段会快速放大仓位,回撤恢复所需涨幅大于亏损比例,倾向反噬盈利,建议封顶加仓倍数。
小布可读取你的成交记录自动算f值和序列相关,并提示当前手数是否超出安全线,省去手动回测。
两品种同向下跌时亏损叠加,真实分散度远低于表面,需用相关系数打折后的净值算仓位系数。
Z分数为负且显著时,历史盈亏存在负序列相关,连亏后倾向反弹,但仍要控仓,外汇贵金属高风险勿梭哈。