概述
在金融市场工作时, 我们一直在寻找一个能够帮助我们赢取利润的系统。 当然, 我们希望这个系统稳定且保持最低风险。 出于寻找这种策略的目的, 已开发了不同种类的寻找最佳入场和离场的交易系统。 这些系统包括技术指标和交易信号, 它们可建议何时买入和卖出。 技术分析有一个完整的价格形态体系。 与此同时, 拉尔夫·文斯在他的著作 "资金管理中的数学" 中阐明, 用于执行交易的资金额度同样重要。 为了优化盈利并节省本金, 有必要确定交易的手数规模。
并且, 文斯驳斥了流行的 "虚假概念"。 例如, 其中一个概念如下: "风险越高, 利润越高":
潜在利润是潜在风险的线性函数。 这不是真的!
下一个 "虚假概念" 是 "多元化能减少亏损"。 这也是错误的。 文斯说到:
多元化可以减少亏损, 但仅在一定程度上, 远低于大多数交易者的看法。
基本原理
为了明晰起见, 基础思路会通过示例来解释。 假设我们有一个条件系统进行两笔交易。 首笔交易盈利 50%, 而第二笔亏损 40%。如果我们不用利润投资, 我们将赚取 10%。 如果我们用复利再投资, 同样的交易顺序将导致 10% 的亏损。 (P&L=盈利或亏损)。
交易编号 | P&L 无复利投资 | 总资产 | P&L 复利投资 | 总资产 | |
---|---|---|---|---|---|
100 | 100 | ||||
1 | +50 | 150 | +50 | 150 | |
2 | -40 | 110 | -60 | 90 |
复利投资导致盈利系统亏损。 在此, 交易顺序无所谓。 这个例子表明复利投资期间的策略与交易固定手数的策略必须有所不同。 因此, 在 复利投资期间寻找最佳手数规模 是文斯资金管理方法的基础。
我们从简单的开始, 循序渐进到复杂情况。 所以, 我们从旋转投币开始。 假设, 如果赢了, 我们得到 2 美元, 如果输了, 则赔 1 美元。 失败或获胜的概率是 1/2。 假设我们有 100 美元。 那么如果我们下注 100 美元, 我们的潜在利润就是 200 美元。 但在失败情况下, 我们会失去所有的钱财, 并且无法继续比赛。 在无止境的游戏中, 这是优化的目标, 我们肯定会输。
如果我们每次下注时不会用到所有的钱, 而是使用其中的一部分, 例如 20 美元, 那么我们就有钱继续游戏。 我们研究一下可能的交易顺序, 每笔交易的资本份额不同。 最初资本是 100 美元。
交易 | P&L 若 K=0.1 | 资本 | P&L 若 K=0.2 | 资本 | P&L 若 K=0.5 | 资本 | P&L 若 K=0.7 | 资本 | P&L 若 K=1 | 资本 | ||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
100 | 100 | 100 | 100 | 100 | ||||||||||
+2 | 20 | 120 | 40 | 140 | 100 | 200 | 140 | 240 | 200 | 300 | ||||
-1 | -12 | 108 | -28 | 112 | -100 | 100 | -168 | 72 | -300 | 0 | ||||
+2 | 21.6 | 129.6 | 44.8 | 156.8 | 100 | 200 | 100.8 | 172.8 | 0 | 0 | ||||
-1 | -12.96 | 116.64 | -31.36 | 125.44 | -100 | 100 | -120.96 | 51.84 | 0 | 0 | ||||
+2 | 23.33 | 139.97 | 50.18 | 175.62 | 100 | 200 | 72.58 | 124.42 | 0 | 0 | ||||
-1 | -14 | 125.97 | -35.12 | 140.5 | -100 | 100 | -87.09 | 37.32 | 0 | 0 | ||||
总计 | 126 | 141 | 100 | 37 | 0 |
如上所述, 盈利/亏损不取决于交易顺序。 所以交易的盈利和亏损交替是正确的。
必须有一个最优系数 (除数), 其利润最大。 对于简单的情况, 当获胜概率和利润/损失比率不变时, 该系数可以通过凯利 (Kelly) 公式得出:
f=((B+1)*P-1)/B
f 是我们要搜索的最佳固定份额
P 是获胜的概率
B 是赢/亏比率
为了方便起见, 我们称之为 f 系数。
在实践中, 获胜的规模和概率不断变化, 所以凯利公式不适用。 因此, 经验数据的 f 系数是通过数值方法找到的。 系统盈利能力将针对任意的交易经验流程进行优化。 为了获得交易盈利, 文斯使用 HPR (持有期回报) 条款。 如果交易获利 10%, 那么 HPR =1+0.1=1.1。 所以, 每笔交易的计算为: HPR =1+f*回报/(最大可能亏损), 此处回报的符号可能为正或为负, 这要取决于其盈亏。 其实 f 是最大可能回撤的系数。 若要找到最佳的 f 值, 我们需要找到所有交易最大值(HPR1 * HPR2 * ... *HPRn)。
我们来编写一个用于为任意数据数组寻找 f 的程序。
程序 1. 寻找最佳 f。
double PL[]={9,18,7,1,10,-5,-3,-17,-7}; // 来自本书的利润/损失数组 double Arr[]={2,-1}; void OnStart() { SearchMaxFactor(Arr); //盈亏或任何其它数组 } void SearchMaxFactor(double &arr[]) { double MaxProfit=0,K=0; // 最大盈利 // 和利润对应的比率 for(int i=1;i<=100;i++) { double k,profit,min; min =MathAbs(arr[ArrayMinimum(arr)]); // 查找数组中的最大亏损 k =i*0.01; profit =1; // 用设定的系数查找回报 for(int j=0;j<ArraySize(arr);j++) { profit =profit*(1+k*arr[j]/min); } // 比较最大利润 if(profit>MaxProfit) { MaxProfit =profit; K=k; } } Print("优化 K ",K," 盈利 ",NormalizeDouble(MaxProfit,2)); }
我们可以验证案例 +2, -1, +2, -1 等等, f 将等于使用凯利公式获得的那个。
请注意, 优化只对盈利系统有意义, 即具有正数的数学期望 (平均利润) 的系统。 对于亏损系统, 优化 f=0。 手数规模管理无助于系统的扭亏为盈。 相反, 如果流程中没有亏损, 即如果所有 P&L>0, 优化也没有意义: f= 1, 我们应该用最大手数交易。
使用 MQL5 的图形可能性, 我们可以找到 f 的最大值并根据 f 查看利润分布的整个曲线。 下面的程序根据 f 系数绘制利润图。
程序 2. 依据 f 的利润图
//+------------------------------------------------------------------+ //| Graphic.mq5 | //| Orangetree | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Orangetree" #property link "https://www.mql5.com" #property version "1.00" #include<Graphics\Graphic.mqh> //+------------------------------------------------------------------+ //| 脚本程序 start 函数 | //+------------------------------------------------------------------+ //double PL[]={9,18,7,1,10,-5,-3,-17,-7}; // 来自本书的利润/损失数组 double PL[]={2,-1}; void OnStart() { double X[100]={0}; for(int i=1;i<=100;i++) X[i-1]=i*0.01; double Y[100]; double min =PL[ArrayMinimum(PL)]; if(min>=0){Comment("f=1");return;} min =MathAbs(min); int n = ArraySize(X); double maxX[1]= {0}; double maxY[1] ={0}; for(int j=0;j<n;j++) { double k =X[j]; double profit =1; for(int i=0;i<ArraySize(PL);i++) { profit =profit*(1+k*PL[i]/min); } Y[j] =profit; if(maxY[0]<profit) { maxY[0] =profit; maxX[0] =k; } } CGraphic Graphic; Graphic.Create(0,"Graphic",0,30,30,630,330); CCurve *Curve=Graphic.CurveAdd(X,Y,ColorToARGB(clrBlue,255),CURVE_LINES,"Profit"); Curve.LinesStyle(STYLE_DOT); //如果需要,图形可以平滑 /*Curve.LinesSmooth(true); Curve.LinesSmoothTension(0.8); Curve.LinesSmoothStep(0.2);*/CCurve *MAX =Graphic.CurveAdd(maxX,maxY,ColorToARGB(clrBlue,255),CURVE_POINTS,"Maximum"); MAX.PointsSize(8); MAX.PointsFill(true); MAX.PointsColor(ColorToARGB(clrRed,255)); Graphic.XAxis().MaxLabels(100); Graphic.TextAdd(30,30,"Text",255); Graphic.CurvePlotAll(); Graphic.Update(); Print("最大因子 f = ", maxX[0]); }
{+2,-1} 的图形看上去像这样:
该图示意以下规则是错误的: "风险越高, 利润越高"。 在所有情况下, 如果曲线低于 1 (f> 0.5), 我们最终会亏损, 并且在无止境的游戏中, 我们的账户上会出现 0。
这里有一个有趣的矛盾。 利润的数学期望越高, 系统越稳定, 系数越大。 例如, 对于流 {-1,1,1,1,1,1,1,1,1,1} 系数等于 0.8。 它看起来像一个梦幻系统。 但系数为 0.8 意味着最大允许亏损等于 80%, 您可能会一次损失账户的 80%! 从数理统计的角度来看, 这是最大化余额的最佳手数规模, 但您是否准备好了承受这种损失?
关于多元化的几句话
假设我们有两种交易策略: A 和 B, 具有相同的利润/损失分布, 例如 (+2, -1)。 它们的优化 f 等于 0.25。 我们研究系统在 1.0 和 -1 时的相关性情况。 账户余额将在这些系统之间平均分配。
关联 1, f=0.25
系统 A | 交易盈亏 | 系统 B | 交易盈亏 | 合并帐户 | ||
---|---|---|---|---|---|---|
50 | 50 | 100 | ||||
2 | 25 | 2 | 25 | 150 | ||
-1 | -18.75 | -1 | -18.75 | 112.5 | ||
2 | 28.13 | 2 | 28.13 | 168.75 | ||
-1 | -21.09 | -1 | -21.09 | 126.56 | ||
盈利 26.56 |
这种变体与基于使用整个资本的策略进行交易的情况没有区别。 现在让我们看看相关性等于 0。
关联 0, f=0.25
系统 A | 交易盈亏 | 系统 B | 交易盈亏 | 合并帐户 |
---|---|---|---|---|
50 | 50 | 100 | ||
2 | 25 | 2 | 25 | 150 |
2 | 37.5 | -1 | -18.75 | 168.75 |
-1 | -21.1 | 2 | 42.19 | 189.85 |
-1 | -23.73 | -1 | -23.73 | 142.39 |
盈利 42.39 |
利润要高得多。 最后, 相关性等于 -1。
关联 -1, f=0.25
系统 A | 交易盈亏 | 系统 B | 交易盈亏 | 合并帐户 |
---|---|---|---|---|
50 | 50 | 100 | ||
2 | 25 | -1 | -12.5 | 112.5 |
-1 | -14.08 | 2 | 28.12 | 126.56 |
2 | 31.64 | -1 | -15 | 142.38 |
-1 | 17.8 | 2 | 35.59 | 160.18 |
盈利 60.18 |
在这种情况下, 利润最高。 这些示例以及类似的例子表明, 在复利投资多样化的情况下会产生更好的结果。 但也很清楚, 它不能消除最糟糕的情况 (在我们的情况下, 余额的最大亏损 f=0.25), 除了系统相关系数为 -1 时的变体。 在实际中, 正好具有 -1 相关性的系统不存在。 这类似于在不同方向上打开相同品种的仓位。 基于这样的论点, 文斯得出以下结论。 这是他的书中的一句话:
多元化, 如果做得好, 是一种提高回报的技巧。 这并不一定会减少最坏情况下的回撤。 这与流行的观念完全相反。
相关性和其它统计
在我们开始寻找 f 系数的参数方法之前, 我们来研究一下利润和损失流的更多特征。 我们可能会得到一系列相互关联的结果。 可盈利交易接着一笔可盈利交易, 而亏损交易之后跟着一笔亏损交易。 为了识别这种依赖关系, 我们考虑以下两种方法: 查找一系列的自相关和系列测试。
系列测试是计算称为 "Z 分数" 的值。 就内容而言, Z 分数表示数据远离正态分布平均值的标准差。 负 Z 分值表示连续盈利/亏损系列的条纹较正常分布较少, 因此盈利之后很可能出现亏损, 反之亦然。 计算 Z 分数的公式:
Z=(N(R-0.5)-Х)/((Х(Х-N))/(N-1))^(1/2)
或
其中:
- N 是交易总数
- R 是系列的总数
- X=2*W*L, 其中
- W = 序列中获胜交易的总数
- L = 序列中亏损交易的总数
程序 3. Z 分数。
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; }
Z 分数由策略测试器计算, 在回测报告中称为 "Z 分数"。
序列相关性是与一个序列一起使用的一系列序列值之间的静态关系。 对于序列 {1,2,3,4,5,6,7,8,9,10}, 它是 {1,2,3,4,5,6,7,8,9} 和 {2,3,4,5,6,7,8,9,10} 之间的相关性。 以下是查找序列关联的程序。
程序 4. 序列关联。
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==0||D1==0) return 1; double k=sum/(D0*D1); return k; }
如果交易结果是相互关联的, 那么可以调整交易策略。 如果我们使用两个不同的系数 f1 和 f2 来获得更好的结果, 我们将获得更好的结果。 对于这种情况, 我们将在 MQL5 中编写一个单独的资金管理模块。
参数方法
在优化系统参数时, 我们可以使用两种方法。 第一种方法是经验性的, 它直接基于实验数据。 在这种情况下, 我们优化某个结果的参数。 第二种方法是参数化。 它基于功能或静态依赖关系。 参数化方法的一个例子是从凯利公式中找出最佳系数。
文斯建议使用所获回报的分布来寻找最佳系数。 首先文斯认为正态分布是最好研究和最受欢迎的分布。 然后他构造了一个广义分布。
问题表述如下。 假设我们的利润/亏损按正态 (或其它) 分布。 我们找到这个分布的最佳 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> 中。
我已为实验创建了两个版本: 一个是 Vince, 另一个是上面描述的。 库函数 MathCumulativeDistributionNormal(PL,mean,stand,ProbCum) 用于查找关联的概率。
程序 5. 在正态分布(Vince) 中查找最优 f。
//+------------------------------------------------------------------+ //| Vince.mq5 | //| 版权所有 2017, MetaQuotes 软件公司 | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "版权所有 2017, MetaQuotes 软件公司" #property link "https://www.mql5.com" #property version "1.00" #include<Math\Stat\Math.mqh> #include<Math\Stat\Normal.mqh> input double N=3; // 标准偏差的截止间隔 input int M=60; // 分段数量 //+------------------------------------------------------------------+ //| 脚本程序 start 函数 | //+------------------------------------------------------------------+ void OnStart() { double arr[10000]; bool ch =MathRandomNormal(1,8,10000,arr); double mean =MathMean(arr); double stand =MathStandardDeviation(arr); double PL[]; // "相关利润" 数组 ArrayResize(PL,M+1); // 填充数组 for(int i=0;i<M+1;i++) { double nn =-N+2.0*i*N/M; PL[i] =stand*nn+mean; } //............................. "相关概率" 数组 double ProbCum[]; ArrayResize(ProbCum,M+1); //............................. 填充数组.................. ch =MathCumulativeDistributionNormal(PL,mean,stand,ProbCum); //F'(x)= 1-F(x) при х>0 for(int i=0,j=0;i<M+1;i++) { if(i<=M/2)continue; else j=M-i; ProbCum[i] =ProbCum[j]; } double SumProb=0; for(int i=0;i<M+1;i++) { SumProb =SumProb+ProbCum[i]; } Print("SumProb ",SumProb); double MinPL =PL[ArrayMinimum(PL)]; double min =arr[ArrayMinimum(arr)]; double f=0.01,HPR=1,profit=1; double MaxProfit=1,MaxF=0; for(int k=0;k<1000;k++) { f=k*0.001; profit =1; for(int i=0;i<M+1;i++) { HPR=pow((1-PL[i]/MinPL*f),ProbCum[i]); profit =HPR*profit; } if(MaxProfit<profit) { MaxF =f; MaxProfit =profit; } } Print("Profit Vince"); Print(MaxF," ",pow(MaxProfit,1/SumProb)," ",Profit(MaxF,min,arr)); //... 为了比较, 我们使用实际数据找到最大利润 MaxF =0; MaxProfit =1; for(int k=0;k<1000;k++) { f=k*0.001; profit =Profit(f,min,arr); if(MaxProfit<profit) { MaxF =f; MaxProfit =profit; } } Print("------MaxProfit-------"); Print(MaxF," ",MaxProfit); } // 使用arr[] 数组的实际数据 // 以最小值 "min" // 和指定的f值查找利润的程序 double Profit(double f,double min, double &arr[]) { if(min>=0) { return 1.0; Alert("min>=0"); } double profit =1; int n =ArraySize(arr); for(int i=0;i<n;i++) { profit =profit*(1-arr[i]*f/min); } return profit; }
程序代码在 Vince.mq5 文件中可用。
这个程序有一个正态分布的系数, 然后是实际的数据。 第二个变体仅在 "关联" 概率和 PL 的数组中有所不同。
程序 6.
............................................. double ProbDiff[]; ArrayResize(ProbDiff,M+2); double PLMean[]; ArrayResize(PLMean,M+2); ProbDiff[0]=ProbCum[0]; ProbDiff[M+1]=ProbCum[M]; PLMean[0]=PL[0]; PLMean[M+1]=PL[M]; for(int i=1;i<M+1;i++) { ProbDiff[i] =MathAbs(ProbCum[i]-ProbCum[i-1]); PLMean[i] =(PL[i]+PL[i-1])/2; } ..............................................
程序代码在 Vince_2.mq5 文件中可用。
此处 PLMean[i] =(PL[i]+PL[i-1])/2; 是 PL 的平均值, ProbDiff[] 是给定时间间隔内找到该值的概率。 边界处的值被截断 (可能是由于止损或止盈), 因此边界的概率等于累积概率。
这两个程序的工作方式大致相同, 并产生大致相同的结果。 事实证明, 响应在很大程度上取决于截止宽度 N (所谓的置信间隔)。 最令人失望的事实是, 当 N 增加时, 从正态分布获得的 f 系数趋于 1。 理论上, 置信间隔越宽, 得到的结果越准确。 然而这在实践中不会发生。
这可能是由累积错误引起的。 指数函数快速下降, 我们需要处理较小的值: HPR=pow((1-PL[i]/MinPL*f),ProbCum[i]). 该方法本身也可能包含错误。但它对于实际应用并不重要。 无论如何, 我们需要 "调整" N 参数, 其强烈影响结果。
当然, 任何 PL 流都与正态分布不同。 这就是为什么文斯用参数创建一个广义分布, 它模拟任意随机分布的特征。 添加不同时刻的分布参数 (平均值, 峰度, 宽度, 偏度)。 然后使用数值方法为经验数据找到这些参数, 并创建 PL 流分布函数。
由于我不喜欢正态分布实验的结果, 因此我决定不使用广义分布进行数值计算。 这是另一个说法, 解释我的疑惑。
文斯声称参数方法更强大。 随着实验次数的增加, 数据趋于理论结果, 因为由于有限的采样, 从样本获得的系数不准确。 然而, 正态分布的参数 (平均值和标准偏差) 是 从相同的有限样本 中获得的。 分布特性计算中的不准确性完全相同。 由于进一步计算中的累计误差, 精度也不断降低。 然后事实证明, 实际执行的结果也取决于截止宽度。 由于分布在实践中并不正常, 因此我们增加一个元素—搜索分布函数, 这也是基于相同的经验最终数据。 额外的元素导致额外的误差。
这是我的愚见。 参数化方法说明了这样一个事实: 理论上看起来不错的想法在实践中并不总是那么好。
文斯的书籍概述
MQL5 向导模块
该模块的实现与可用的标准模块 MoneyFixedRisk 类似, 手数大小根据配置的止损值进行计算。 出于演示目的, 我们保持独立的止损, 并通过输入参数明确设定系数和最大亏损。
首先, 我们在 Include/Expert 目录中为我们的模块创建一个新文件夹 — 例如, MyMoney。 然后我们在这个文件夹中创建 MoneyF1.mql 文件。
所有交易模块由一组标准部件组成: 交易模块类别及其特殊描述。
该类通常包含以下元素:
- 构造函数
- 析构函数
- 输入参数设置函数
- 参数验证函数 ValidationSettings(void)
- 用于确定持仓量的方法 CheckOpenLong(double price,double sl) 和 CheckOpenShort(double price,double sl)
我们称这个类为 CMoneyFactor
class CMoneyFactor : public CExpertMoney { protected: //--- 输入参数 double m_factor; // 最大亏损系数 f double m_max_loss; // 最大亏损点数 public: CMoneyFactor(void); ~CMoneyFactor(void); //--- void Factor(double f) { m_factor=f;} void MaxLoss(double point) { m_max_loss=point;} virtual bool ValidationSettings(void); //--- virtual double CheckOpenLong(double price,double sl); virtual double CheckOpenShort(double price,double sl); }; //+------------------------------------------------------------------+ //| 构造函数 | //+------------------------------------------------------------------+ void CMoneyFactor::CMoneyFactor(void) : m_factor(0.1), m_max_loss(100) { } //+------------------------------------------------------------------+ //| 析构函数 | //+------------------------------------------------------------------+ void CMoneyFactor::~CMoneyFactor(void) { }
最大亏损点数设置为 double 类型以便适应标准模块。 在分布式软件包中可用的其它标准模块中, 止损和止盈级别按 ExpertBase.mqh 基类中定义的点数设置。
ExpertBase.mqh int digits_adjust=(m_symbol.Digits()==3 || m_symbol.Digits()==5) ? 10 : 1; m_adjusted_point=m_symbol.Point()*digits_adjust;
这意味着对于带有 3 和 5小数位的报价, 一个点数等于 10*Point()。 在标准 MQL5 模块中 105 点对于 Point() 来说等于 10.5。
Factor(double f) 和 MaxLoss(double point) 函数设置输入参数并以同样方式命名, 因为它们将在模块描述符中进行描述。
验证输入参数的函数:
bool CMoneyFactor::ValidationSettings(void) { if(!CExpertMoney::ValidationSettings()) return(false); //--- 初始化数据检查 if(m_factor<0||m_factor>1) { Print(__FUNCTION__+"系数值必须介于 0 和 1 之间"); return false; } return true; }
检查系数值是否在 0 和 1 之间。
最后, 此为判断持仓量的函数。 开多头仓位:
double CMoneyFactor::CheckOpenLong(double price,double sl) { if(m_symbol==NULL) return(0.0); //--- 判断手数 double lot; /* ExpertBase.mqh int digits_adjust=(m_symbol.Digits()==3 || m_symbol.Digits()==5) ? 10 : 1; m_adjusted_point=m_symbol.Point()*digits_adjust; */ double loss; if(price==0.0)price =m_symbol.Ask(); loss=-m_account.OrderProfitCheck(m_symbol.Name(),ORDER_TYPE_BUY,1.0,price,price - m_max_loss*m_adjusted_point); double stepvol=m_symbol.LotsStep(); lot=MathFloor(m_account.Balance()*m_factor/loss/stepvol)*stepvol; double minvol=m_symbol.LotsMin(); //---检查最小手数 if(lot<minvol) lot=minvol; //---检查最大手数 double maxvol=m_symbol.LotsMax(); if(lot>maxvol) lot=maxvol; //--- 返回交易量 return(lot); }
这里, 通过使用函数库中 CAccountInf 类的 OrderProfitCheck() 方法可以找到最大手数。 然后检查手数是否符合最小值和最大值的限制。
每个模块都以描述符开始, 编译器需要这些描述符来识别模块。
// wizard description start //+------------------------------------------------------------------+ //| Description of the class | //| Title=Trading with the optimal f coefficient | //| Type=Money | //| Name=FixedPart | //| Class=CMoneyFactor | //| Page= ? | //| Parameter=Factor,double,0.1,Optimum fixed share | //| Parameter=MaxLoss,double,50,Maximum loss in points | //+------------------------------------------------------------------+ // wizard description end
出于测试目的, 您可以使用任何现有的信号模块编译此模块。 所选模块的交易信号也可以用基于固定手数的资金管理模块进行编制。 所获得的结果用于找出最大亏损和 PL 流。 然后我们将程序 1 应用到这些结果中, 以便找到最佳 f 系数。 因此, 实验数据可以用来找到最佳的 f 值。 另一种方法是通过优化直接从智能交易系统结果中找到最佳 f。 我的结果差异只有 +/- 0.01。 这种差异是出于计算误差, 可能与四舍五入有关。
模块代码在 MoneyF1.mqh 文件中可用。
我们的盈利/亏损可能会产生显著的序列相关性。 这可以通过使用上述程序来计算 Z 分数和序列相关性。 然后可以指定两个系数: f1 和 f2。 第一种是在盈利交易之后应用, 第二种是在亏损交易后使用。 我们来编写这个策略的第二个资金管理模块。 然后可以使用优化或直接从固定手数的相同策略的盈利/亏损流数据中找到系数。
程序 7. 基于 PL 流确定最佳 f1 和 f2。
void OptimumF1F2(double &arr[]) { double f1,f2; double profit=1; double MaxProfit =0; double MaxF1 =0,MaxF2 =0; double min =MathAbs(arr[ArrayMinimum(arr)]); for(int i=1;i<=100;i++) { f1 =i*0.01; for(int j=1;j<=100;i++) { f2 =j*0.01; profit =profit*(1+f1*arr[0]/min); for(int n=1;n<ArraySize(arr);n++) { if(arr[n-1]>0){profit =profit*(1+f1*arr[n]/min);} else{profit =profit*(1+f2*arr[n]/min);} } if(MaxProfit<profit) { MaxProfit=profit; MaxF1 =i;MaxF2 =j; } } }
我们还需要调整 MQL5 向导的资金管理模块的基本功能。 首先, 我们添加另一个 f2 参数并检查该参数。 其次, 我们修改 CheckOpenLong() 和 CheckOpenShort() 函数。 我们还添加了 CheckLoss() 来确定上次交易的财务结果。
//+------------------------------------------------------------------+ //| 检查之前交易的结果 | //+------------------------------------------------------------------+ double CMoneyTwoFact:: CheckLoss() { double lot=0.0; HistorySelect(0,TimeCurrent()); int deals=HistoryDealsTotal(); // 历史记录中的成交数量 CDealInfo deal; //--- 搜索以前的交易 if(deals==1) return 1; for(int i=deals-1;i>=0;i--) { if(!deal.SelectByIndex(i)) { printf(__FUNCTION__+": 选择指定索引的交易失败"); break; } //--- 根据品种或其它参数选择交易 if(deal.Symbol()!=m_symbol.Name()) continue; //--- 返回交易结果 lot=deal.Profit(); break; } return(lot); }
函数 CheckOpenLong() 和 CheckOpenShort():
double CMoneyTwoFact::CheckOpenLong(double price,double sl) { double lot=0.0; double p=CheckLoss(); /* ExpertBase.mqh int digits_adjust=(m_symbol.Digits()==3 || m_symbol.Digits()==5) ? 10 : 1; m_adjusted_point=m_symbol.Point()*digits_adjust; */ double loss; if(price==0.0)price =m_symbol.Ask(); if(p>0) { loss=-m_account.OrderProfitCheck(m_symbol.Name(),ORDER_TYPE_BUY,1.0,price,price - m_max_loss*m_adjusted_point); double stepvol=m_symbol.LotsStep(); lot=MathFloor(m_account.Balance()*m_factor1/loss/stepvol)*stepvol; } if(p<0) { loss=-m_account.OrderProfitCheck(m_symbol.Name(),ORDER_TYPE_BUY,1.0,price,price - m_max_loss*m_adjusted_point); double stepvol=m_symbol.LotsStep(); lot=MathFloor(m_account.Balance()*m_factor2/loss/stepvol)*stepvol; } return(lot); } //+------------------------------------------------------------------+ double CMoneyTwoFact::CheckOpenShort(double price,double sl) { double lot=0.0; double p=CheckLoss(); /* int digits_adjust=(m_symbol.Digits()==3 || m_symbol.Digits()==5) ? 10 : 1; m_adjusted_point=m_symbol.Point()*digits_adjust;*/ double loss; if(price==0.0)price =m_symbol.Ask(); if(p>0) { loss=-m_account.OrderProfitCheck(m_symbol.Name(),ORDER_TYPE_SELL,1.0,price,price+m_max_loss*m_adjusted_point); double stepvol=m_symbol.LotsStep(); lot=MathFloor(m_account.Balance()*m_factor1/loss/stepvol)*stepvol; } if(p<0) { loss=-m_account.OrderProfitCheck(m_symbol.Name(),ORDER_TYPE_SELL,1.0,price,price+m_max_loss*m_adjusted_point); double stepvol=m_symbol.LotsStep(); lot=MathFloor(m_account.Balance()*m_factor2/loss/stepvol)*stepvol; } return(lot); }
该模块的完整代码在 MoneyF1F2.mqh 中可用。
如上所述, 文斯的货币管理概念与最优 f 系数基本相关。 因此这个示例中有两个模块已经足够了。 不过, 您可以实现其它变体。 例如, 您可以添加马丁格尔元素。
附件
Programs.mq5 文件包含文章中使用的程序代码。 从文件 void ReadFile(string file, double&arr[]) 读取数据的程序也附在下面。 该程序允许基于来自策略测试器的 PL 流查找 f 系数。 有人可能想要编写一个完整的类来解析报告, 就像在文章 将入场解析到指标 中所做的那样。 但是, 这将是自己类中的一个单独程序。
我推荐一个更简单的方法。 在策略测试器中使用固定手数运行策略。 将测试报告保存为 Open XML (MS Office Excel)。 将隔夜费和佣金添加到 Profit 列以便获取 PL 流。 将此列保存到文本或 csv 文件。 因此, 我们获得一组由每笔交易单独结果组成的行。 ReadFile() 函数将这些结果读取到 arr[] 数组。 因此, 我们可以基于固定手数的任何策略的数据查找最优 f 系数。
文件 Vince.mq5 和 Vince_2.mq5 包含用于查找文章中描述的最佳系数的参数化方法的源代码。
MoneyF1.mqh 和 MoneyF1F2.mqh 包含资金管理模块的源代码。
附加 zip 文件夹中的文件已按照 MetaEditor 目录排列。