
交易策略的色彩优化
简介
经过优化后,我们只需要从各种各样的参数集中选择一个。在选择这样的一个集合时,没有明确的答案可以使用什么标准:盈利能力、回撤、恢复系数或这些或其他参数的某种组合。但是如何评价参数组合呢?
在本文中,我们将进行一个实验:我们将使用颜色优化结果。颜色由三个参数决定:红色、绿色和蓝色(RGB)的级别。还有其他的颜色编码方法,它们也使用三个参数。因此,可以将三个测试参数转换为一种颜色,以直观方式表示这些值。在本文的最后,我们将了解这种表示是否有用。
初始数据
在文章使用 HTML 报告分析交易结果中, 我们为分析报告文件创建了一个函数库, HTMLReport.mqh. 库中包含 OptimizerXMLReportToStruct()函数,该函数是为具有优化结果的操作而设计的。我们将使用此函数向函数传递两个参数:
- string aFileName 是包含优化报告的文件名,该文件必须在终端数据目录的 MQL5/Files文件夹中可用。
- SOptimization & aOptimization 是通过引用传递的,函数执行后,从报告中提取的数据将位于该结构中。
SOptimisation 结构:
struct SOptimization{ string ParameterName[]; SPass Pass[]; };
该结构包括两个数组:string ParameterName[] 和 SPAS Pass[],要优化的参数名称位于 ParameterName[] 中,我们主要感兴趣的是第二个数组 SPass[]: 这个数组中的一个元素就包含着一个优化通过的有关数据。
SPass 结构:
struct SPass{ string Pass; string Result; string Profit; string ExpectedPayoff; string ProfitFactor; string RecoveryFactor; string SharpeRatio; string Custom; string EquityDD_perc; string Trades; string Parameters[]; };
结构的栏位:
- Pass — 优化过程的编号;
- Result — 优化后的最终余额;
- Profit — 结果利润值;
- ExpectedPayoff — 期望收入值;
- ProfitFactor — 利润因子值;
- RecoveryFactor — 采收系数;
- SharpeRatio — 夏普比率;
- Custom — 一个自定义参数;
- EquityDD_perc — 回撤百分比;
- Trades — 交易数量;
- Parameters[] — 优化的参数数值数组.
以下是分析交易结果最常用的参数:
- Profitability(盈利能力),每笔交易的平均利润
- Drawdown(回撤), 相对最大值的净值下降值
- Recovery factor(采收系数),绝对利润与最大回撤之比
首先,我们将使用这些参数。但是,报告包含其他值,因此我们需要提供使用这些值的可能性。
为了启用任意参数选择,我们将创建一个额外的结构来替换SPass。优化参数将位于该结构中的双精度数组中。我们不会完全重写结构,而是使用继承可能性。让我们继续实现部分:
1. 创建 ColorOptimization.mqh 文件,创建彩色报告的所有函数都将位于此文件中。
2. 在 ColorOptimization.mqh 文件的开头关联 HtmlReport.mqh 文件:
#include <HTMLReport.mqh>
3. 创建一个新的结构,它继承 SPass 结构的所有栏位,并且在其中加上 factor[] 和 dParameters[] 数组:
struct SPass2:SPass{ double factor[9]; double dParameters[]; };
两个数组都是双精度类型的,在 factor[] 数组中将有九个结果值,也就是除了 Pass (测试编号) 的所有将要优化的参数。优化参数的值位于sParameters[]数组中。虽然结构中已经有了所有的数据,但是它们是以字符串格式呈现的,所以每次使用数据时,我们都需要将它们转换为数字。数组允许以方便的格式保存数据。
4. 为优化数据创建最终结构:
struct SOptimization2{ string ParameterName[]; SPass2 Pass[]; };
5. 创建一个将数据从sOptimeization结构转换为sOptimeization2的函数:
void ConvertOptimizationStruct(SOptimization & src,SOptimization2 & dst){ ArrayCopy(dst.ParameterName,src.ParameterName); int cnt=ArraySize(src.Pass); ArrayResize(dst.Pass,cnt); for(int i=0;i<cnt;i++){ ArrayCopy(dst.Pass[i].Parameters,src.Pass[i].Parameters); dst.Pass[i].Pass=src.Pass[i].Pass; dst.Pass[i].Result=src.Pass[i].Result; dst.Pass[i].Profit=src.Pass[i].Profit; dst.Pass[i].ExpectedPayoff=src.Pass[i].ExpectedPayoff; dst.Pass[i].ProfitFactor=src.Pass[i].ProfitFactor; dst.Pass[i].RecoveryFactor=src.Pass[i].RecoveryFactor; dst.Pass[i].SharpeRatio=src.Pass[i].SharpeRatio; dst.Pass[i].Custom=src.Pass[i].Custom; dst.Pass[i].EquityDD_perc=src.Pass[i].EquityDD_perc; dst.Pass[i].Trades=src.Pass[i].Trades; dst.Pass[i].factor[0]=StringToDouble(src.Pass[i].Result); dst.Pass[i].factor[1]=StringToDouble(src.Pass[i].Profit); dst.Pass[i].factor[2]=StringToDouble(src.Pass[i].ExpectedPayoff); dst.Pass[i].factor[3]=StringToDouble(src.Pass[i].ProfitFactor); dst.Pass[i].factor[4]=StringToDouble(src.Pass[i].RecoveryFactor); dst.Pass[i].factor[5]=StringToDouble(src.Pass[i].SharpeRatio); dst.Pass[i].factor[6]=StringToDouble(src.Pass[i].Custom); dst.Pass[i].factor[7]=StringToDouble(src.Pass[i].EquityDD_perc); dst.Pass[i].factor[8]=StringToDouble(src.Pass[i].Trades); int pc=ArraySize(src.Pass[i].Parameters); ArrayResize(dst.Pass[i].dParameters,pc); for(int j=0;j<pc;j++){ if(src.Pass[i].Parameters[j]=="true"){ dst.Pass[i].dParameters[j]=1; } else if(src.Pass[i].Parameters[j]=="false"){ dst.Pass[i].dParameters[j]=0; } else{ dst.Pass[i].dParameters[j]=StringToDouble(src.Pass[i].Parameters[j]); } } } }
带有数据的数据结构作为第一个参数传递给函数,新结构作为第二个参数通过引用返回。在函数中执行所有优化过程的循环;同时复制结构的某些字段,并对某些字段执行类型转换。一般的过程并不复杂,可以从函数代码中理解。
将使用枚举访问factor[]数组元素:
enum EOptimizatrionFactor{ Result=0, Profit=1, ExpectedPayoff=2, ProfitFactor=3, RecoveryFactor=4, SharpeRatio=5, Custom=6, EquityDD_perc=7, Trades=8 };
枚举选项值以零开始,并增加1,因此可能不需要指定值。但是,这些值还是指定了,因为提供与factor[]数组的匹配是很重要的。这将有助于避免进一步修改和添加程序时可能出现的错误。
6. 创建一个函数,用于将报告文件加载到sOptimeization2结构中,该结构与 HtmlReport.mqh 中的 OptimizerXMLReportToStruct()类似:
bool OptimizerXMLReportToStruct2(string aFileName,SOptimization2 & aOptimization){ SOptimization tmp; if(!OptimizerXMLReportToStruct(aFileName,tmp)){ return(false); } ConvertOptimizationStruct(tmp,aOptimization); return(true); }
报告文件名作为第一个参数传递给函数,填充的 sOptimeration2 结构作为第二个参数返回。
现在一切就绪,可以解决本文的主要任务了。
创建彩色报告
用于创建彩色报告的函数将位于ColorOptimization.mqh中。调用这些函数将从脚本执行。
1. 让我们创建一个脚本 ColorOptimization.mq5.
2. 把 ColorOptimization.mqh 与 ColorOptimization.mq5 关联.
#include <ColorOptimization.mqh>
3. 在脚本中另外增加外部参数。首先,我们将添加一个指示属性窗口存在的属性,然后我们将添加变量。
属性:
#property script_show_inputs
外部变量:
input string ReportName = "*.xml"; input string OutputName = "ColorOptimization1-1.htm"; input EOptimizatrionFactor Factor1 = Profit; input EOptimizatrionFactor Factor2 = EquityDD_perc; input EOptimizatrionFactor Factor3 = RecoveryFactor; input bool Factor1Invert = false; input bool Factor2Invert = true; input bool Factor3Invert = false; input bool Sort = true;
变量的描述:
- ReportName — 源优化报告文件的名称;
- OutputName — 由脚本所创建的报告文件的名称;
- Factor1 — 第一个因子,根据该因子确定报告颜色;
- Factor2 — 第二个因子,根据该因子确定报告颜色;
- Factor3 — 第三个因子,根据该因子确定报告颜色;
- Factor1Invert — 反转第一个因子;
- Factor2Invert — 反转第二个因子;
- Factor3Invert — 反转第三个因子;
- Sort — 根据颜色指示对最终报告进行排序;
4. 在脚本的 OnStart()函数中,我们声明一个sOptimisation2 类型的变量,并接收源报告数据:
SOptimization2 opt; if(!OptimizerXMLReportToStruct2(ReportName,opt)){ Alert("错误的 OptimizerXMLReportToStruct2"); return; }
5. 由于RGB只是许多不同颜色模型中的一种,因此让我们提供进一步修改库的可能性,特别是添加其他颜色模型。这就是为什么我们将从0到1的抽象值计算开始,而不是计算RGB组件的值。然后我们将这些值转换为从0到255的RGB组件。每个优化过程都使用单独的颜色指示,因此我们需要为SPass2添加三个颜色组件字段。我们不添加三个字段,而是添加一个三元素数组:
double ColorComponent[3];
6. ColorOptimization.mqh 中的 SolveColorComponents() 函数将计算颜色组成部分,应该向函数中传入以下参数:
- SOptimization2 & aOpt — 源优化报告中的数据
- int i1, int i2, int i3 — 源优化报告中数值的索引 (SPass 结构中的 factor[9] 数组)
- bool r1=false, bool r2=false, bool r3=false — 用于反转数值
函数执行后,SPass 结构数组中的 ColorComponents[3] 数组将填充值。
对于颜色分量的计算,我们需要找到每个参数的最小值和最大值,然后计算0到1之间的值。以下显示 SolveColorComponents() 函数的完整代码:
void SolveColorComponents( SOptimization2 & aOpt, int i1,int i2,int i3, bool r1=false,bool r2=false,bool r3=false){ double mx[3]={0,0,0}; double mn[3]={DBL_MAX,DBL_MAX,DBL_MAX}; int size=ArraySize(aOpt.Pass); for(int i=0;i<size;i++){ mx[0]=MathMax(mx[0],aOpt.Pass[i].factor[i1]); mx[1]=MathMax(mx[1],aOpt.Pass[i].factor[i2]); mx[2]=MathMax(mx[2],aOpt.Pass[i].factor[i3]); mn[0]=MathMin(mn[0],aOpt.Pass[i].factor[i1]); mn[1]=MathMin(mn[1],aOpt.Pass[i].factor[i2]); mn[2]=MathMin(mn[2],aOpt.Pass[i].factor[i3]); } double c1,c2,c3,d; for(int i=0;i<size;i++){ c1=0; c2=0; c3=0; d=mx[0]-mn[0]; if(d!=0){ c1=(aOpt.Pass[i].factor[i1]-mn[0])/d; } d=mx[1]-mn[1]; if(d!=0){ c2=(aOpt.Pass[i].factor[i2]-mn[1])/d; } d=mx[2]-mn[2]; if(d!=0){ c3=(aOpt.Pass[i].factor[i3]-mn[2])/d; } if(r1)c1=1.0-c1; if(r2)c2=1.0-c2; if(r3)c3=1.0-c3; aOpt.Pass[i].ColorComponent[0]=c1; aOpt.Pass[i].ColorComponent[1]=c2; aOpt.Pass[i].ColorComponent[2]=c3; } }
如何从脚本中调用这个函数:
SolveColorComponents(opt,Factor1,Factor2,Factor3,Factor1Invert,Factor2Invert,Factor3Invert);
7. 如果在外部脚本参数中启用排序,则需要计算排序的系数并执行该排序。最佳的优化过程是一个,在此过程中所有参数组合都具有最大值。如果这些参数对应于RGB,则最佳选项是白色。因此,应将排序因子计算为三个分量的算术平均值。
让我们再向 SPass2 结构添加一个字段:
double SortFactor;
计算排序因子的函数应添加到 ColorOptimization.mqh文件中:
void SolveSortFactor(SOptimization2 & aOpt){ int size=ArraySize(aOpt.Pass); for(int i=0;i<size;i++){ aOpt.Pass[i].SortFactor=0; for(int j=0;j<3;j++){ aOpt.Pass[i].SortFactor+=aOpt.Pass[i].ColorComponent[j]; } aOpt.Pass[i].SortFactor/=3; } }
下面是排序函数(使用气泡排序方法):
void SortFactorSort(SOptimization2 & aOpt){ int size=ArraySize(aOpt.Pass); for(int i=size-1;i>0;i--){ for(int j=0;j<i;j++){ if(aOpt.Pass[j].SortFactor<aOpt.Pass[j+1].SortFactor){ SPass2 tmp=aOpt.Pass[j]; aOpt.Pass[j]=aOpt.Pass[j+1]; aOpt.Pass[j+1]=tmp; } } } }
从脚本调用这些函数。排序因子不仅用于对表进行排序,因此无论 Sort 变量的值如何,都将调用 SolveSortFactor():
SolveSortFactor(opt);
if(Sort){
SortFactorSort(opt);
}
现在一切就绪,可以创建报告了。报告由两部分组成,第一个是带有附加颜色按钮的优化数据表的副本(图1)。第二部分由多个彩色平面(表)组成,每对优化参数,每个单元将显示给定一对优化参数测试结果中反映变化的梯度(图2)。
带颜色指示的表格
在 TableContent()函数中创建带有附加颜色指示的表。此函数位于ColorOptimization.mqh文件中,并返回表的HTML代码。
创建HTML表是一项简单的任务。颜色指示单元格的颜色是通过指定单元格样式“background-color”属性来设置的。范围在0到1之间的颜色分量可以通过乘以值很容易地转换为范围在1到255之间的分量。为了在表中提供更多的可视信息,让我们添加有关与此颜色或该颜色对应的优化参数的详细信息。该数据将在颜色指示器列的上单元格中指定,参数的上单元格将具有适当的颜色(图1)。
图 1. 带颜色指示的报告片段
TableContent() 函数的完整代码如下:
string TableContent(SOptimization2 & aOpt,int i1,int i2,int i3){ int size=ArraySize(aOpt.Pass); int pc=ArraySize(aOpt.ParameterName); int nc=ArraySize(co_names); string s="<table>"; s=s+"<tr>"; s=s+"<th>Pass</td>"; for(int i=0;i<nc;i++){ s=s+"<th"+HStyle(i,i1,i2,i3)+">"+co_names[i]+"</th>"; } s=s+"<th>"+ColorCollHeader(i1,i2,i3)+"</th>"; for(int j=0;j<pc;j++){ s=s+"<th>"+aOpt.ParameterName[j]+"</th>"; } s=s+"</tr>"; int r,g,b; for(int i=0;i<size;i++){ ComponentsToRGB(aOpt.Pass[i].ColorComponent[0], aOpt.Pass[i].ColorComponent[1], aOpt.Pass[i].ColorComponent[2], r,g,b); s=s+"<tr>"; s=s+"<td>"+aOpt.Pass[i].Pass+"</td>"; s=s+"<td>"+aOpt.Pass[i].Result+"</td>"; s=s+"<td>"+aOpt.Pass[i].Profit+"</td>"; s=s+"<td>"+aOpt.Pass[i].ExpectedPayoff+"</td>"; s=s+"<td>"+aOpt.Pass[i].ProfitFactor+"</td>"; s=s+"<td>"+aOpt.Pass[i].RecoveryFactor+"</td>"; s=s+"<td>"+aOpt.Pass[i].SharpeRatio+"</td>"; s=s+"<td>"+aOpt.Pass[i].Custom+"</td>"; s=s+"<td>"+aOpt.Pass[i].EquityDD_perc+"</td>"; s=s+"<td>"+aOpt.Pass[i].Trades+"</td>"; string cs=RGBToStr(r,g,b); s=s+"<td title='"+cs+"' style='background-color: "+cs+"'> </td>"; for(int j=0;j<pc;j++){ s=s+"<td>"+aOpt.Pass[i].Parameters[j]+"</td>"; } s=s+"</tr>"; } s=s+"</table>"; return(s); }
让我们更加详细地探讨这个函数。我们接收到传递到“size”变量的优化数,接收到的优化参数数为pc变量,接收到的数组大小和参数名(在全局级别声明)为nc变量:
int size=ArraySize(aOpt.Pass); int pc=ArraySize(aOpt.ParameterName); int nc=ArraySize(co_names);
全局数组 co_names[]:
string co_names[]={"Result","Profit","Expected Payoff", "Profit Factor","Recovery Factor", "Sharpe Ratio","Custom","Equity DD","Trades"};
表的HTML代码将在其形成过程中添加到变量s中,因此在变量声明过程中,我们将添加表开始标记:
string s="<table>";
然后添加行开始标记和标题的第一个单元格,其中包含“Pass”文本:
s=s+"<tr>"; s=s+"<th>Pass</th>";
“Pass”列后面是带参数的列,其中任何一个都可以用来形成颜色指示。确定单元格的HTML代码:
for(int i=0;i<nc;i++){ s=s+"<th"+HStyle(i,i1,i2,i3)+">"+co_names[i]+"</th>"; }
如果需要,hstyle()函数将形成一个代码,用于更改单元格背景色:
string HStyle(int i,int i1,int i2,int i3){ if(i==i1)return(" style='background-color: rgb(255,0,0);'"); if(i==i2)return(" style='background-color: rgb(0,255,0);'"); if(i==i3)return(" style='background-color: rgb(0,0,255);'"); return(""); }
使用颜色指示标题为单元格形成文本:
s=s+"<th>"+ColorCollHeader(i1,i2,i3)+"</th>";
ColorCollHeader() 函数代码:
string ColorCollHeader(int i1,int i2,int i3){ return(co_names[i1]+"-R,<br>"+co_names[i2]+"-G,<br>"+co_names[i3]+"-B"); }
然后,我们为包含优化参数名称的单元格生成HTML代码,并结束表格行:
for(int j=0;j<pc;j++){ s=s+"<th>"+aOpt.ParameterName[j]+"</th>"; } s=s+"</tr>";
然后声明三个辅助变量: r, g, b. 然后是一个循环,在该循环中生成所有报表行的HTML代码,在每个循环开始时计算RGB分量值:
ComponentsToRGB(aOpt.Pass[i].ColorComponent[0], aOpt.Pass[i].ColorComponent[1], aOpt.Pass[i].ColorComponent[2], r,g,b);
ComponentsToRGB() 函数代码:
void ComponentsToRGB(double c1,double c2,double c3,int & r,int & g,int & b){ r=(int)(c1*255.0); g=(int)(c2*255.0); b=(int)(c3*255.0); }
然后用包含测试结果的单元格生成行的HTML代码:
s=s+"<tr>"; s=s+"<td>"+aOpt.Pass[i].Pass+"</td>"; s=s+"<td>"+aOpt.Pass[i].Result+"</td>"; s=s+"<td>"+aOpt.Pass[i].Profit+"</td>"; s=s+"<td>"+aOpt.Pass[i].ExpectedPayoff+"</td>"; s=s+"<td>"+aOpt.Pass[i].ProfitFactor+"</td>"; s=s+"<td>"+aOpt.Pass[i].RecoveryFactor+"</td>"; s=s+"<td>"+aOpt.Pass[i].SharpeRatio+"</td>"; s=s+"<td>"+aOpt.Pass[i].Custom+"</td>"; s=s+"<td>"+aOpt.Pass[i].EquityDD_perc+"</td>"; s=s+"<td>"+aOpt.Pass[i].Trades+"</td>";
然后是颜色指示单元,首先使用 RGBToStr()函数将 RGB 组分转换为字符串;然后生成单元代码:
string cs=RGBToStr(r,g,b); s=s+"<td title='"+cs+"' style='background-color: "+cs+"'> </td>";
RGBToStr() 函数代码:
string RGBToStr(int r,int g,int b){ return("rgb("+(string)r+","+(string)g+","+(string)b+")"); }
行末显示参数值处于优化状态的单元格:
for(int j=0;j<pc;j++){ s=s+"<td>"+aOpt.Pass[i].Parameters[j]+"</td>"; } s=s+"</tr>";
表格关闭,函数结束时返回s变量内容:
s=s+"</table>"; return(s);
优化过参数的平面
当有两个或多个优化参数时,可以绘制平面。平面在图2中显示
图 2. 优化参数平面
第一行显示对应于平面轴的参数:沿X轴(水平)显示 Inp_Signal_MACD_PeriodSlow的值,沿Y轴显示 Inp_Signal_MACD_PeriodSlow的值。单元中的梯度显示了当其他参数发生变化时,这对x和y参数的测试结果是如何变化的。最差值的颜色显示在左侧,最好值的颜色显示在右侧。最佳和最差的变体是根据前面提到的排序因子确定的,排序因子是作为抽象颜色组件的算术平均值计算的。
平面的HTML代码在 Color2DPlanes()函数中生成。在这个函数中可以找到两个优化参数的所有可能组合,并为每对生成HTML平面代码。Color2DPlanes() 函数的代码:
string Color2DPlanes(SOptimization2 & aOpt){ string s=""; int pc=ArraySize(aOpt.ParameterName); for(int y=0;y<pc;y++){ for(int x=y+1;x<pc;x++){ s=s+Color2DPlane(aOpt,x,y); } } return(s); }
一个平面的HTML代码在Color2DPlane()函数中生成:
string Color2DPlane(SOptimization2 & aOpt,int xi,int yi){ double xa[]; double ya[]; int cnt=ArraySize(aOpt.Pass); ArrayResize(xa,cnt); ArrayResize(ya,cnt); for(int i=0;i<cnt;i++){ xa[i]=aOpt.Pass[i].dParameters[xi]; ya[i]=aOpt.Pass[i].dParameters[yi]; } ArraySort(xa); ArraySort(ya); int xc=1; int yc=1; for(int i=1;i<cnt;i++){ if(xa[i]!=xa[i-1]){ xa[xc]=xa[i]; xc++; } if(ya[i]!=ya[i-1]){ ya[xc]=ya[i]; yc++; } } string s="<hr><h3>X - "+aOpt.ParameterName[xi]+", Y - "+aOpt.ParameterName[yi]+"</h3><table>"; s=s+"<tr>"; s=s+"<td> </td>"; for(int x=0;x<xc;x++){ s=s+"<td>"+(string)xa[x]+"</td>"; } s=s+"</tr>"; for(int y=0;y<yc;y++){ s=s+"<tr>"; s=s+"<td>"+(string)ya[y]+"</td>"; for(int x=0;x<xc;x++){ double mx=0; double mn=DBL_MAX; int mxi=0; int mni=0; for(int i=0;i<cnt;i++){ if(aOpt.Pass[i].dParameters[yi]==ya[y] && aOpt.Pass[i].dParameters[xi]==xa[x] ){ if(aOpt.Pass[i].SortFactor>mx){ mx=aOpt.Pass[i].SortFactor; mxi=i; } if(aOpt.Pass[i].SortFactor<mn){ mn=aOpt.Pass[i].SortFactor; mni=i; } } } int mnr,mng,mnb; int mxr,mxg,mxb; ComponentsToRGB(aOpt.Pass[mni].ColorComponent[0], aOpt.Pass[mni].ColorComponent[1], aOpt.Pass[mni].ColorComponent[2], mnr,mng,mnb); ComponentsToRGB(aOpt.Pass[mxi].ColorComponent[0], aOpt.Pass[mxi].ColorComponent[1], aOpt.Pass[mxi].ColorComponent[2], mxr,mxg,mxb); string title=RGBToStr(mnr,mng,mnb)+"/"+RGBToStr(mxr,mxg,mxb)+"\n"; int digits[]={2,2,6,6,6,6,6,4,0}; for(int k=0;k<ArraySize(co_names);k++){ title=title+co_names[k]+": "+DoubleToString(aOpt.Pass[mni].factor[k],digits[k])+ "/"+DoubleToString(aOpt.Pass[mxi].factor[k],digits[k])+"\n"; } s=s+"<td title='"+title+"' style='background: linear-gradient(to right, rgb("+ (string)mnr+","+(string)mng+","+(string)mnb+"), rgb("+ (string)mxr+","+(string)mxg+","+(string)mxb+"));'> "+ (string)mni+"-"+(string)mxi+"</td>"; } s=s+"</tr>"; } s=s+"<table>"; return(s); }
让我们详细探讨一下 Color2DPlane() 函数。下面是输入函数:由优化报告中的所有数据和两个 int 变量 xi和 yi这两个参数组成的平面。首先,我们将把每对参数的所有可能值收集到数组中。为此,我们声明了两个数组,根据优化过程的数量更改它们的大小,并用所有可能的值填充它们:
double xa[]; double ya[]; int cnt=ArraySize(aOpt.Pass); ArrayResize(xa,cnt); ArrayResize(ya,cnt); for(int i=0;i<cnt;i++){ xa[i]=aOpt.Pass[i].dParameters[xi]; ya[i]=aOpt.Pass[i].dParameters[yi]; }
只应使用参数的唯一值,以便我们对数组进行排序并将唯一值移到数组的开头:
ArraySort(xa); ArraySort(ya); int xc=1; int yc=1; for(int i=1;i<cnt;i++){ if(xa[i]!=xa[i-1]){ xa[xc]=xa[i]; xc++; } if(ya[i]!=ya[i-1]){ ya[xc]=ya[i]; yc++; } }
之后,xc变量包含一个参数的唯一值的数目,并且yc变量包含其他参数的唯一值。平面的HTML代码将在其形成过程中添加到变量s。在s变量声明期间,让我们立即添加变量名称和表格开启标记的信息:
string s="<hr><h3>X - "+aOpt.ParameterName[xi]+", Y - "+aOpt.ParameterName[yi]+"</h3><table>";
让我们创建包含x参数值的第一个表行:
s=s+"<tr>"; s=s+"<td> </td>"; for(int x=0;x<xc;x++){ s=s+"<td>"+(string)xa[x]+"</td>"; } s=s+"</tr>";
之后,循环通过所有变量的y参数:
for(int y=0;y<yc;y++){
在此循环中,在每次传递时开始一行,并添加一个单元格,其y参数值为:
s=s+"<tr>"; s=s+"<td>"+(string)ya[y]+"</td>";
然后使用渐变添加单元格(它们通过所有x参数变量添加到循环中):
for(int x=0;x<xc;x++){
要创建渐变,必须找到最佳和最差的优化过程:
double mx=0; double mn=DBL_MAX; int mxi=0; int mni=0; for(int i=0;i<cnt;i++){ if(aOpt.Pass[i].dParameters[yi]==ya[y] && aOpt.Pass[i].dParameters[xi]==xa[x] ){ if(aOpt.Pass[i].SortFactor>mx){ mx=aOpt.Pass[i].SortFactor; mxi=i; } if(aOpt.Pass[i].SortFactor<mn){ mn=aOpt.Pass[i].SortFactor; mni=i; } } }
执行此代码部分后,mxi和mni变量将包含最佳和最差优化过程的索引。
然后需要将抽象颜色组件转换为RGB:
ComponentsToRGB(aOpt.Pass[mni].ColorComponent[0], aOpt.Pass[mni].ColorComponent[1], aOpt.Pass[mni].ColorComponent[2], mnr,mng,mnb); ComponentsToRGB(aOpt.Pass[mxi].ColorComponent[0], aOpt.Pass[mxi].ColorComponent[1], aOpt.Pass[mxi].ColorComponent[2], mxr,mxg,mxb);
为了更有效地分析平面,让我们添加工具提示(可以使用HTML属性'title'添加):
string title=RGBToStr(mnr,mng,mnb)+"/"+RGBToStr(mxr,mxg,mxb)+"\n"; int digits[]={2,2,6,6,6,6,6,4,0}; for(int k=0;k<ArraySize(co_names);k++){ title=title+co_names[k]+": "+DoubleToString(aOpt.Pass[mni].factor[k],digits[k])+ "/"+DoubleToString(aOpt.Pass[mxi].factor[k],digits[k])+"\n"; }
标题如图3所示。
图 3. 一个普通单元格的工具提示
工具提示包含有关最差和最佳优化过程(最差/最好)的所有数据。RGB渐变的组件值显示在工具提示的第一行中。
现在,进入最重要的部分,到梯度:
s=s+"<td title='"+title+"' style='background: linear-gradient(to right, rgb("+ (string)mnr+","+(string)mng+","+(string)mnb+"), rgb("+ (string)mxr+","+(string)mxg+","+(string)mxb+"));'> "+ (string)mni+"-"+(string)mxi+"</td>";
在以下Web浏览器中检查了渐变显示:Opera、Google Chrome、Yandex浏览器和Microsoft Edge。可以在所有这些浏览器中正常工作。
在每行末尾添加行结束标记:
s=s+"</tr>";
在表的末尾,添加表格结束标记并返回形成的HTML代码:
s=s+"<table>"; return(s);
现在让我们从脚本调用函数:
string report=HTMLStart("Color Optimization","style2.css")+ TableContent(opt,Factor1,Factor2,Factor3)+ Color2DPlanes(opt)+HTMLEnd();
我使用了来自文章使用 HTML 报告分析交易结果的 HTMLStart()和 HTMLEnd()函数,来自同一篇文章的样式文件被稍微更改并重命名为Style2.css。
准备好的文件附在下面:ColorOptimization.mqh 和 ColorOptimization.mq5脚本。
颜色模型的修改
在 ColorOptimization.mqh中的代码是结构化的,因此您可以很容易地针对不同的颜色模型对其进行修改。让我们尝试添加CMY颜色模型。为此,我们需要执行一些初步步骤。
1. 复制 ColorOptimization.mqh 和 ColorOptimization.mq5,并将其保存为ColorOptimization2.mqh 和 ColorOptimization2.mq5。
2. 将两种颜色模型类型的两个常量和一个全局变量添加到ColorOptimization2.mqh中,它将确定颜色模型:
#define MODEL_RGB 0 #define MODEL_CMY 1 int co_ColorModel;
3. 添加枚举和外部变量,用户将使用该变量选择颜色模型:
enum EColorModel{ RGB=MODEL_RGB, CMY=MODEL_CMY }; input EColorModel ColorModel = RGB;
在脚本的 OnStart()函数的开头,将“属性”窗口中选择的值赋给 co_ColorModel 变量:
co_ColorModel=ColorModel;
主要修改在 ColorOptimization2.mqh 文件函数中执行。首先,我们需要更改ComponentsToRGB()。CMY模型中组件的值在0到1之间,因此报表数据结构中组件的值对应于CMY组件,可以重新计算为RGB。这是 ComponentsToRGB() 的结构:
void ComponentsToRGB(double c1,double c2,double c3,int & r,int & g,int & b){ if(co_ColorModel==MODEL_RGB){ r=(int)(c1*255.0); g=(int)(c2*255.0); b=(int)(c3*255.0); } else if(co_ColorModel==MODEL_CMY){ CMYtoRGB(c1,c2,c3,r,g,b); } }
CMY模型到RGB的转换在单独的函数中实现:
void CMYtoRGB(double C,double M,double Y,int & R,int & G,int & B){ R=(int)((1.0-C)*255.0); G=(int)((1.0-M)*255.0); B=(int)((1.0-Y)*255.0); }
其他修改只涉及辅助报告元素。修改 HStyle()函数以正确着色表格的标题行单元格:
string HStyle(int i,int i1,int i2,int i3){ if(co_ColorModel==MODEL_RGB){ if(i==i1)return(" style='background-color: rgb(255,0,0);'"); if(i==i2)return(" style='background-color: rgb(0,255,0);'"); if(i==i3)return(" style='background-color: rgb(0,0,255);'"); } else if(co_ColorModel==MODEL_CMY){ if(i==i1)return(" style='background-color: rgb(0,255,255);'"); if(i==i2)return(" style='background-color: rgb(255,0,255);'"); if(i==i3)return(" style='background-color: rgb(255,255,0);'"); } return(""); }
ColorCollHeader()函数的修改是为了正确使用彩色显示列标题:
string ColorCollHeader(int i1,int i2,int i3){ if(co_ColorModel==MODEL_RGB){ return(co_names[i1]+"-R,<br>"+co_names[i2]+"-G,<br>"+co_names[i3]+"-B"); } else if(co_ColorModel==MODEL_CMY){ return(co_names[i1]+"-C,<br>"+co_names[i2]+"-M,<br>"+co_names[i3]+"-Y"); } return ""; }
然后,需要对主表和颜色平面的工具提示进行一些修改。对于主表,我们需要更改 TableContent()中“title”属性的值。以下代码行:
string cs=RGBToStr(r,g,b); s=s+"<td title='"+cs+"' style='background-color: "+cs+"'> </td>";
应当修改如下:
string ts="",cs=RGBToStr(r,g,b); if(co_ColorModel==MODEL_RGB){ ts=cs; } else if(co_ColorModel==MODEL_CMY){ ts=CMYToStr(aOpt.Pass[i].ColorComponent[0], aOpt.Pass[i].ColorComponent[1], aOpt.Pass[i].ColorComponent[2]); } s=s+"<td title='"+ts+"' style='background-color: "+cs+"'> </td>";
应更改 Color2DPlane()函数中的“title”属性,为平面设置适当的标题。代码行:
string title=RGBToStr(mnr,mng,mnb)+"/"+RGBToStr(mxr,mxg,mxb)+"\n";
应当如下修改:
string title=""; if(co_ColorModel==MODEL_RGB){ title=RGBToStr(mnr,mng,mnb)+"/"+RGBToStr(mxr,mxg,mxb)+"\n"; } else if(co_ColorModel==MODEL_CMY){ title=CMYToStr(aOpt.Pass[mni].ColorComponent[0], aOpt.Pass[mni].ColorComponent[1], aOpt.Pass[mni].ColorComponent[2])+"/"+ CMYToStr(aOpt.Pass[mxi].ColorComponent[0], aOpt.Pass[mxi].ColorComponent[1], aOpt.Pass[mxi].ColorComponent[2])+"\n"; }
现在,可以在脚本启动期间选择颜色模型类型。CMY和RGB之间的差异是,最佳值以黑色显示,其他颜色也将不同(图4、5)。
图 4. 使用CMY颜色模型创建的报告片段
图 5. CMY颜色模型中的颜色平面
如何解释颜色指示
RGB中的最佳选项接近白色,而CMY中的最佳选项接近黑色。为了正确解释其他颜色,我们需要了解颜色模型中的各个组件是如何组合的,以及结果颜色是如何形成的。
让我们更详细地查看RGB模型。当所有分量的值等于0时,我们得到黑色。当所有组件都等于最大值时,颜色为白色。所有其他组合提供不同的颜色。如果其中一个组件的值最高,而另外两个值等于0,则得到相应组件的清晰颜色:红色、绿色或蓝色。如果两个分量有最大值,而第三个分量为零,则结果颜色也很清楚。红色和绿色的结果是黄色,绿色和蓝色提供青色,红色和蓝色显示为洋红。图6显示了几种RGB组分的组合。
图 7. RGB组分的基本组合
基于阴影,我们可以了解哪些参数指标对测试结果的贡献更为积极。如果为红色,则为第一个参数;如果颜色为黄色,则为第一个和第二个参数;绿色表示第三个参数等。
RGB模型中的颜色与彩色灯光的添加类似。在CMY模型中,值从白色中减去,因此所有组件的最大值对应于黑色。CMY模型类似于混合颜料:如果没有颜料,我们就有一张白纸;如果混合了太多不同的颜料,你就会得到黑色(或者更确切地说,在处理真正的颜料时是一种肮脏的颜色)。图8. 显示CMY组分的基本组合。
图 7. CMY组分的基本组合
与RGB相比,CMY中的颜色发生了转换。解释如下:青色为第一参数,蓝色为第一和第二参数,洋红为第二参数,红色为第二和第三值,黄色为第三参数,绿色为第一和第三参数。
如您所见,在使用 RGB 或 CMY 模型方面没有根本区别。
结论
颜色的感知是一个主观的过程,因此很难对颜色表示的便利性和益处作出明确的结论。至少有一个视觉指示,即亮度(即与RGB中的白色接近),允许评估三个参数的组合。这可以简化报表分析。当选择是自动化的(如本文中所述)时,基于排序表的决策是根据三个值的算术平均值做出的。这可以看作是进入模糊逻辑领域的第一步,利用模糊逻辑,最终值的计算不是简单的算术平均值,而是更复杂的方法。然而,我们需要更多的实际实验来评估这种方法的有效性。
附件
- HTMLReport.mqh — 报告分析函数的文件 (参见文章 使用 HTML 报告分析交易结果);
- ColorOptimization.mqh — 具有使用RGB模型创建颜色报告的功能的文件;
- ColorOptimization.mq5 — 使用 ColorOptimization.mqh 函数编写脚本,使用示例;
- ColorOptimization2.mqh — 具有创建彩色报告功能的文件,可在RGB和CMY之间进行选择;
- ColorOptimization2.mq5 — 使用 ColorOptimization2.mqh函数编写脚本,使用示例;
- ReportOptimizer-555849.xml — 与文章中使用的优化报告一起归档;
- ColorOptimization.htm — 将通过 ColorOptimization.mq5 脚本获得的报告归档;
- ColorOptimization2.htm — 文件中包含通过 ColorOptimization2.mq5脚本(CMY颜色模型)获得的报告.
- Style2.css — 文件中包含由ColorOptimization.mq5 和 ColorOptimization2.mq5创建的报告的样式。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/5437
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.



可视化实验非常有趣,我马上想到,这三个值对其范围的依赖关系是线性的,如果我们使用非线性的,那就更正确了。第二个想法是--也有 4 参数的颜色名称。
什么是四参数?
我不认为给桌子上的元素 上色是拉希德的想法。
德米特里,你很少关注问题的陈述,却一下子把所有东西都涂上了颜色。
让我们打个比方--建造一条绕过防空系统的路线。
在地形图(当然是二维的)上,考虑到地形和其他因素,对每个点的防空效果指标进行三维标注。然后根据这些指标的数值对地图进行着色。也可以不进行一维标注,那么灰色阴影就足够了。
使用这种方法的经验表明,操作员几乎可以立即建立一条航线,而在这条航线上损失飞机的可能性微乎其微。只要看看彩色地图就足够了。
现在--关于在优化中的应用。漂亮,但效率低。除非你有一台超级计算机。您可以尝试使用它对参数进行动态优化(调整),但您需要非常高的计算能力,而且您很难优化两个以上的参数。不过,看到彩色图表,你就能实时将优化参数的数值集引导到所需区域。手工操作。
一切都在我的掌控之中。
我认为给桌子上的元素上色并不是拉希德的想法。
德米特里,你很少关注问题的陈述,却一下子把所有东西都涂上了颜色。
让我们打个比方--建造一条绕过防空系统的路线。
在地形图(当然是二维的)上,考虑到地形和其他因素,对每个点的防空效果指标进行三维标注。然后根据这些指标的数值对地图进行着色。也可以不进行一维标注,那么灰色阴影就足够了。
使用这种方法的经验表明,操作员几乎可以立即建立一条航线,而在这条航线上损失飞机的可能性微乎其微。只需查看彩色地图即可。
现在--关于优化中的应用。漂亮,但效率低。除非你有一台超级计算机。您可以尝试用它来动态优化(调整)参数,但您需要非常高的计算能力,而且您很难优化两个以上的参数。不过,看到彩色图表,你就能实时将优化参数的数值集引导到所需区域。手动
这是我的权力。
是的,拉希德指的是彩色平面,而不是表格元素。但在说 "B "之前,应该先说 "A",这就是为什么在将优化结果 放置在平面上之前,要在通常的优化结果表中添加一列新内容的原因。
在文章的后半部分,我们将绘制平面图,只不过像素是大像素、矩形像素和双色渐变像素。)
为什么需要超级计算机并不清楚。如果优化两个参数,只需在循环中通过一次,就能找到三个参数的最大值和最小值。如果需要优化的参数不止两个,那么对于每个单元格,您仍然需要一个循环来找出该单元格的最大/最小值。这需要一点时间。此外,整个优化过程只需创建一次报告,即使创建报告需要三分钟,也没有问题(与优化过程中花费的时间相比)。
还有一点是,以某种方式将所有优化结果排列在一个平面上,而不仅仅是一对参数。但这是不切实际的幻想。