经过优化后,我们只需要从各种各样的参数集中选择一个。在选择这样的一个集合时,没有明确的答案可以使用什么标准:盈利能力、回撤、恢复系数或这些或其他参数的某种组合。但是如何评价参数组合呢?
在本文中,我们将进行一个实验:我们将使用颜色优化结果。颜色由三个参数决定:红色、绿色和蓝色(RGB)的级别。还有其他的颜色编码方法,它们也使用三个参数。因此,可以将三个测试参数转换为一种颜色,以直观方式表示这些值。在本文的最后,我们将了解这种表示是否有用。
在文章使用 HTML 报告分析交易结果中, 我们为分析报告文件创建了一个函数库, HTMLReport.mqh. 库中包含 OptimizerXMLReportToStruct()函数,该函数是为具有优化结果的操作而设计的。我们将使用此函数向函数传递两个参数:
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[]; };
结构的栏位:
以下是分析交易结果最常用的参数:
首先,我们将使用这些参数。但是,报告包含其他值,因此我们需要提供使用这些值的可能性。
为了启用任意参数选择,我们将创建一个额外的结构来替换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;
变量的描述:
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() 函数将计算颜色组成部分,应该向函数中传入以下参数:
函数执行后,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中的白色接近),允许评估三个参数的组合。这可以简化报表分析。当选择是自动化的(如本文中所述)时,基于排序表的决策是根据三个值的算术平均值做出的。这可以看作是进入模糊逻辑领域的第一步,利用模糊逻辑,最终值的计算不是简单的算术平均值,而是更复杂的方法。然而,我们需要更多的实际实验来评估这种方法的有效性。
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程