利用文斯 (Vince) 进行资金管理。 作为 MQL5 向导模块实现·进阶篇
(2/3)· 从旋转投币到向导实装,看清复利下注为何让盈利系统反亏 10%
「参数方法」
在优化系统参数时, 我们可以使用两种方法。 第一种方法是经验性的, 它直接基于实验数据。 在这种情况下, 我们优化某个结果的参数。 第二种方法是参数化。 它基于功能或静态依赖关系。 参数化方法的一个例子是从凯利公式中找出最佳系数。 文斯建议使用所获回报的分布来寻找最佳系数。 首先文斯认为正态分布是最好研究和最受欢迎的分布。 然后他构造了一个广义分布。 问题表述如下。 假设我们的利润/亏损按正态 (或其它) 分布。 我们找到这个分布的最佳 f 系数。 在正态分布的情况下, 我们可以使用实验数据来找到 PL (利润/亏损) 流的平均值和标准偏差。 这两个参数完全表征正态分布。 此为正态分布密度的公式: 其中 σ 是标准 偏差 m 是数学期望 (均值)。 我喜欢这个主意。 盈利/亏损分布的性质可以使用经验数据找到。 然后我们可以使用这个函数来找出 f 参数, 从而避免随机值的影响。 不幸地是, 这在实践中并不那么简单。 我们来从头开始。 首先, 我们讨论这个方法。 正态分布密度图在图表中以蓝色绘制。 平均值等于零, 标准偏差等于 1。 红色显示此函数的组成部分。 这是一个累积概率, 即该值小于或等于给定 X 的概率。它通常表示为 F(x) 。 橙色图表示意当 x<0 时数值小于或等于 x 的概率, 而当 x>0 ( F(x)'=1-F(x) 时该数值大于或等于 x。 所有这些函数都是众所周知的, 它们的数值很容易获得。 我们需要根据这个定律找到交易分布的最大几何平均数。 在此, 文斯建议采取以下行动。 首先我们找到分布的特征, 即均值和标准差。 然后我们选择 "置信区间" 或以标准偏差表示的截止宽度。 通常选择 3σ 间隔。 大于 3σ 的值被截断。 在此之后, 划分间隔, 然后就能找到相关的盈利/亏损值 ( PL )。 例如, 对于 σ=1 和 m=0, 在区间边界处关联的 PLs 值为 m +- 3σ = +3 和 -3。 如果我们将区间分成长度为 0.1σ 的间隔, 则相关的 PLs 将会是 -3, -2.9, -2.8 ... 0 ... 2.8, 2,9, 3。 对于这个 PL 流, 我们可以找到最佳的 f 。 由于 PL 的不同值具有不同的概率, 因此能为每个值找到单一 "关联概率" P 。 之后, 找到最大的产品: HPR=(1+ PL *f/maxLoss)^P , 其中 maxLoss 是最大损失 (模)。 文斯建议使用累积概率作为关联概率。 在我们的图表 F'(x) 中, 累积概率以橙色显示。 逻辑上, 累积概率应该仅用于极端值, 而对于其它值 P=F'(x)-F'(y), 其中 x 和 y 是 F(x) 在间隔的边界处的值。 那么因子 HPR=(1+ PL *f/maxLoss)^P 将是一种概率加权值。 正如预期的那样, 这些值的总概率将等于 1。 文斯在他的书中承认, 用这种方式获得的结果与实际数据得到的结果并不一致。 他将这样的结果与抽样的有限性以及实际分布与正态分布的差异结合在一起。 据推测, 根据正态定律, 随着元素数量的增加和分布的增加, 最佳 f 系数的参数值和实际值将匹配。 在文斯方法分析的示例中, 总概率等于 7.9。 文斯简单地通过获得结果的第 7.9 个根来找到几何平均值。 显然, 这种方法必须有严格的数学证明。 使用 MQL5 工具, 我们可以轻松地检查上述内容。 为此目的, 我们将使用 Normal.mqh 函数库, 它位于 <Math\Stat\Normal.mqh> 中。 我已为实验创建了两个版
◍ 用正态假设和实际样本分别搜最优 f
这段代码把前面算出的价格水平数组 PL[] 和正态累计概率 ProbCum[] 直接拿来做仓位优化。核心思路是:先用正态分布假设下的几何均值找一遍最大 f,再用 arr[] 里的真实盈亏样本找一遍,两相对照看假设偏离多大。外汇与贵金属杠杆高,这种回测只是历史样本拟合,实盘可能严重漂移。 正态那段里,循环从 i=M/2 之后把 ProbCum[i] 翻成对称值 ProbCum[M-i],相当于只取右尾再镜像,SumProb 就是这些概率之和。随后用 1000 步网格(k 从 0 到 999,f=k*0.001)搜 HPR 连乘最大的 MaxF,打印出几何均值 pow(MaxProfit,1/SumProb)。 实际数据那段调用 Profit(f,min,arr):若最小样本 min>=0 直接返回 1 并报警,否则 profit 连乘 (1-arr[i]*f/min)。同样 1000 步搜出 MaxProfit。下面这段是代码原文,注意 Profit() 里 min 为亏损幅度的绝对值分母。 末尾又建了 ProbDiff[] 和 PLMean[],把相邻累计概率差和价格中值填进去,给后面算期望用。跑完你可以在 MT5 策略测试器里把 M 调到 200、N 调到 3,看 MaxF 打印值是否落在 0.1~0.4 区间。
class=class="str">"cmt">// 填充数组 for(class="type">int i=class="num">0;i<M+class="num">1;i++) { class="type">class="kw">double nn =-N+class="num">2.0*i*N/M; PL[i] =stand*nn+mean; } class=class="str">"cmt">//............................. "相关概率" 数组 class="type">class="kw">double ProbCum[]; ArrayResize(ProbCum,M+class="num">1); class=class="str">"cmt">//............................. 填充数组.................. ch =MathCumulativeDistributionNormal(PL,mean,stand,ProbCum); class=class="str">"cmt">//F&class="macro">#x27;(x)= class="num">1-F(x) при х>class="num">0 for(class="type">int i=class="num">0,j=class="num">0;i<M+class="num">1;i++) { if(i<=M/class="num">2)class="kw">continue; else j=M-i; ProbCum[i] =ProbCum[j]; } class="type">class="kw">double SumProb=class="num">0; for(class="type">int i=class="num">0;i<M+class="num">1;i++) { SumProb =SumProb+ProbCum[i]; } Print("SumProb ",SumProb); class="type">class="kw">double MinPL =PL[ArrayMinimum(PL)]; class="type">class="kw">double min =arr[ArrayMinimum(arr)]; class="type">class="kw">double f=class="num">0.01,HPR=class="num">1,profit=class="num">1; class="type">class="kw">double MaxProfit=class="num">1,MaxF=class="num">0; for(class="type">int k=class="num">0;k<class="num">1000;k++) { f=k*class="num">0.001; profit =class="num">1; for(class="type">int i=class="num">0;i<M+class="num">1;i++) { HPR=pow((class="num">1-PL[i]/MinPL*f),ProbCum[i]); profit =HPR*profit; } if(MaxProfit<profit) { MaxF =f; MaxProfit =profit; } } Print("Profit Vince"); Print(MaxF," ",pow(MaxProfit,class="num">1/SumProb)," ",Profit(MaxF,min,arr)); class=class="str">"cmt">//... 为了比较, 我们使用实际数据找到最大利润 MaxF =class="num">0; MaxProfit =class="num">1; for(class="type">int k=class="num">0;k<class="num">1000;k++) { f=k*class="num">0.001; profit =Profit(f,min,arr); if(MaxProfit<profit) { MaxF =f; MaxProfit =profit; } } Print("------MaxProfit-------"); Print(MaxF," ",MaxProfit); } class=class="str">"cmt">// 使用arr[] 数组的实际数据 class=class="str">"cmt">// 以最小值 "min" class=class="str">"cmt">// 和指定的f值查找利润的程序 class="type">class="kw">double Profit(class="type">class="kw">double f,class="type">class="kw">double min, class="type">class="kw">double &arr[]) { if(min>=class="num">0) { class="kw">return class="num">1.0; Alert("min>=class="num">0"); } class="type">class="kw">double profit =class="num">1; class="type">int n =ArraySize(arr); for(class="type">int i=class="num">0;i<n;i++) { profit =profit*(class="num">1-arr[i]*f/min); } class="kw">return profit; } ............................................. class="type">class="kw">double ProbDiff[]; ArrayResize(ProbDiff,M+class="num">2); class="type">class="kw">double PLMean[]; ArrayResize(PLMean,M+class="num">2); ProbDiff[class="num">0]=ProbCum[class="num">0]; ProbDiff[M+class="num">1]=ProbCum[M]; PLMean[class="num">0]=PL[class="num">0]; PLMean[M+class="num">1]=PL[M]; for(class="type">int i=class="num">1;i<M+class="num">1;i++) { ProbDiff[i] =MathAbs(ProbCum[i]-ProbCum[i-class="num">1]); PLMean[i] =(PL[i]+PL[i-class="num">1])/class="num">2; }
空小节无内容可析
本小节原文未提供任何技术文字或代码,仅见截断标记,无法提取可验证数据点。 若你手头有对应 MQL5 片段,贴出后我可补全逐行拆解与落地参数。
「文斯那本书到底覆盖了什么」
文斯在《资金管理中的数学》里把统计方法和找最优 f 系数的路子全捋了一遍。书里跨度很猛:从 Markovitz 投资组合模型、Kolmogorov-Smirnov 分布检验,到 Black-Scholes 期权定价,再到方程组求解,基本把量化仓位能用的数学工具都铺开了。 这种广度显然不是一篇短文能拆完的,而且所有方法最终都指向同一件事——确定最佳 f。所以这里不展开推导,直接奔实盘可用的部分。 接下来的落点很明确:上面那些方法会做成 MQL5 向导模块,你在 MT5 里调向导就能挂上,不用自己从头写数学。外汇和贵金属杠杆高,最优 f 只是概率层面的仓位参考,真跑起来得用历史样本先验一遍。
◍ 在 MT5 向导里手搓最优 f 资金模块
想用文斯最优 f 思路管仓位,不必重写整套 EA。在 MT5 的 Include/Expert 目录下新建一个 MyMoney 文件夹,丢入 MoneyF1.mql,照着标准 MoneyFixedRisk 的骨架改即可:类里必须挂构造函数、析构函数、参数设置函数、ValidationSettings(void),以及多空开仓手数判定 CheckOpenLong / CheckOpenShort。 最大亏损点数用 double 存,是因为标准模块按 ExpertBase.mqh 的基点规则走:报价小数位为 3 或 5 时,1 个『点』等于 10*Point()。所以标准模块里写的 105 点,落到 Point() 上实际是 10.5,这个换算直接决定你手数算得准不准。 验证函数只卡一条:系数 f 必须落在 0 到 1 之间。手数本身则调用库的 CAccountInf::OrderProfitCheck() 取账户允许最大手数,再夹在最小/最大值限制里。 两个模块描述符开头的函数 Factor(double f) 和 MaxLoss(double point) 名字刻意和输入参数一致,方便向导识别。我拿固定手数跑出的 PL 流反推最优 f,和 EA 直接优化出来的结果差只有 +/-0.01,基本是四舍五入的误差。 如果策略盈亏序列相关明显,可以照程序 7 拆出 f1(盈后)和 f2(亏后)两个系数,另写 MoneyF1F2 模块,在里面加 CheckLoss() 读上笔财务结果并改两个开仓函数。外汇和贵金属杠杆高,这类仓位算法只管概率倾向,不承诺回撤上限,实盘前务必在策略测试器跑穿。
class CMoneyFactor : class="kw">public CExpertMoney { class="kw">protected: class=class="str">"cmt">//--- 输入参数 class="type">class="kw">double m_factor; class=class="str">"cmt">// 最大亏损系数 f class="type">class="kw">double m_max_loss; class=class="str">"cmt">// 最大亏损点数 class="kw">public: CMoneyFactor(class="type">void); ~CMoneyFactor(class="type">void); class=class="str">"cmt">//--- class="type">void Factor(class="type">class="kw">double f) { m_factor=f;} class="type">void MaxLoss(class="type">class="kw">double point) { m_max_loss=point;} class="kw">virtual class="type">bool ValidationSettings(class="type">void); class=class="str">"cmt">//--- class="kw">virtual class="type">class="kw">double CheckOpenLong(class="type">class="kw">double price,class="type">class="kw">double sl); class="kw">virtual class="type">class="kw">double CheckOpenShort(class="type">class="kw">double price,class="type">class="kw">double sl); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 构造函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CMoneyFactor::CMoneyFactor(class="type">void) : m_factor(class="num">0.1), m_max_loss(class="num">100) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 析构函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CMoneyFactor::~CMoneyFactor(class="type">void) { } ExpertBase.mqh class="type">int digits_adjust=(m_symbol.Digits()==class="num">3 || m_symbol.Digits()==class="num">5) ? class="num">10 : class="num">1; m_adjusted_point=m_symbol.Point()*digits_adjust; class="type">bool CMoneyFactor::ValidationSettings(class="type">void) {
手数推导与最优 f 扫描的实现落点
这段实现把「按账户权益比例下注」落到了可编译的 MQL5 函数里。CheckOpenLong 先拿 OrderProfitCheck 用 1 手模拟一笔多单在 price - m_max_loss*m_adjusted_point 处的亏损额,再用 Balance()*m_factor/loss 反推手数,最后用 LotsStep 向下取整并夹在 LotsMin 与 LotsMax 之间。 系数 m_factor 被硬性约束在 0 到 1 之间,ValidationSettings 里若越界直接 Print 报错并返回 false,这是防止过度杠杆的最基础护栏。外汇与贵金属波动剧烈,这类按权益比例开仓的方法在连亏序列里可能快速侵蚀本金,属高风险用法。 OptimumF1F2 则是一个暴力网格搜索:f1 从 0.01 以步长 0.01 扫到 1.00,共 100 次循环,配合 arr 中的历史盈亏分布去追 MaxProfit 对应的 MaxF1 / MaxF2。实盘前你应当把这段直接丢进 MT5 策略测试器,用自己品种的 tick 数据重跑,观察 100 次扫描里峰值 f 落在哪一段。 让小布替你跑这套 把 MaxLoss 默认 50 点调到对应品种 ATR(14) 的 1.5 倍再扫一遍,EURUSD 与 XAUUSD 得出的优选 f 往往不在同一区间,硬套参数会偏薄或爆仓。
if(!CExpertMoney::ValidationSettings()) class="kw">return(false); class=class="str">"cmt">//--- 初始化数据检查 if(m_factor<class="num">0||m_factor>class="num">1) { Print(__FUNCTION__+"系数值必须介于 class="num">0 和 class="num">1 之间"); class="kw">return false; } class="kw">return true; } class="type">class="kw">double CMoneyFactor::CheckOpenLong(class="type">class="kw">double price,class="type">class="kw">double sl) { if(m_symbol==NULL) class="kw">return(class="num">0.0); class=class="str">"cmt">//--- 判断手数 class="type">class="kw">double lot; class=class="str">"cmt">/* ExpertBase.mqh class="type">int digits_adjust=(m_symbol.Digits()==class="num">3 || m_symbol.Digits()==class="num">5) ? class="num">10 : class="num">1; m_adjusted_point=m_symbol.Point()*digits_adjust; */ class="type">class="kw">double loss; if(price==class="num">0.0)price =m_symbol.Ask(); loss=-m_account.OrderProfitCheck(m_symbol.Name(),ORDER_TYPE_BUY,class="num">1.0,price,price - m_max_loss*m_adjusted_point); class="type">class="kw">double stepvol=m_symbol.LotsStep(); lot=MathFloor(m_account.Balance()*m_factor/loss/stepvol)*stepvol; class="type">class="kw">double minvol=m_symbol.LotsMin(); class=class="str">"cmt">//---检查最小手数 if(lot<minvol) lot=minvol; class=class="str">"cmt">//---检查最大手数 class="type">class="kw">double maxvol=m_symbol.LotsMax(); if(lot>maxvol) lot=maxvol; class=class="str">"cmt">//--- 返回交易量 class="kw">return(lot); } class=class="str">"cmt">// wizard description start class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Description of the class | class=class="str">"cmt">//| Title=Trading with the optimal f coefficient | class=class="str">"cmt">//| Type=Money | class=class="str">"cmt">//| Name=FixedPart | class=class="str">"cmt">//| Class=CMoneyFactor | class=class="str">"cmt">//| Page= ? | class=class="str">"cmt">//| Parameter=Factor,class="type">class="kw">double,class="num">0.1,Optimum fixed share | class=class="str">"cmt">//| Parameter=MaxLoss,class="type">class="kw">double,class="num">50,Maximum loss in points | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">// wizard description end class="type">void OptimumF1F2(class="type">class="kw">double &arr[]) { class="type">class="kw">double f1,f2; class="type">class="kw">double profit=class="num">1; class="type">class="kw">double MaxProfit =class="num">0; class="type">class="kw">double MaxF1 =class="num">0,MaxF2 =class="num">0; class="type">class="kw">double min =MathAbs(arr[ArrayMinimum(arr)]); for(class="type">int i=class="num">1;i<=class="num">100;i++) { f1 =i*class="num">0.01;