Optimización de color de estrategias comerciales
Introducción
Después de la optimización, tenemos que seleccionar sólo un conjunto de parámetros de una gran variedad de conjuntos diferentes. Sin embargo, todavía no existe una respuesta clara qué criterio se usa para seleccionar este conjunto: rentabilidad, reducción (drawdown), factor de recuperación, o alguna combinación de estos u otros parámetros. Es más, ¿cómo evaluar la combinación de estos parámetros?
En este artículo, vamos a realizar un experimento del coloreo de los resultados de la optimización. Como se sabe, el color se determina por tres parámetros: los niveles del color rojo, verde y azul (RGB en inglés, Red — rojo, Green — verde, Blue — azul). Hay otros métodos de codificar el color, pero igualmente se usan tres parámetros. Así, tres parámetros de la simulación pueden ser convertidos en un color que el trader percibe visualmente. Al final de este artículo descubriremos si esta representación va a ser útil.
Datos iniciales:
En el artículo Analizando resultados comerciales con ayuda de informes HTML, fue creada la biblioteca de las funciones para analizar los archivos con informes, HTMLReport.mqh. Esta biblioteca contiene la función OptimizerXMLReportToStruct() diseñada para trabajar con informes de la optimización. Vamos a usar esta función. La función recibe dos parámetros:
- string aFileName — nombre del archivo con el informe de la optimización. Este archivo debe ubicarse en la carpeta MQL5/Files del directorio de datos del terminal.
- SOptimization & aOptimization se pasa por referencia. Después de la ejecución de la función, los datos extraídos del informe van a ubicarse en esta estructura.
Estructura SOptimisation:
struct SOptimization{ string ParameterName[]; SPass Pass[]; };
La estructura incluye dos arrays: string ParameterName[] y SPass Pass[]. Los nombres de los parámetros optimizados se encuentran en array ParameterName[]. Principalmente, nos va a interesar el segundo array SPass[]. Uno de los elementos de este array contiene datos sobre un paso de la optimización.
Estructura 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[]; };
Los campos de la estructura:
- Pass — número del paso de la optimización;
- Result — balance final tras la optimización;
- Profit — beneficio;
- ExpectedPayoff — valor esperado del beneficio;
- ProfitFactor — factor de beneficio;
- RecoveryFactor — factor de recuperación;
- SharpeRatio — ratio de Sharpe;
- Custom — parámetro personalizado;
- EquityDD_perc — reducción (drawdown) en por cientos;
- Trades — número de transacciones;
- Parameters[] — array con valores de parámetros optimizados.
Estos son los parámetros más populares para analizar los resultados del trading:
- Factor de beneficio — beneficio medio por transacción;
- Reducción — disminución de equidad respecto a su valor máximo;
- Factor de recuperación — relación entre el beneficio absoluto y la reducción máxima.
En primer lugar, vamos a usar precisamente estos parámetros. Sin embargo, el informe incluye otros valores, por tanto, estaría bien asegurar la posibilidad de usarlos también.
Para asegurar la posibilidad de una selección aleatoria de parámetros, crearemos una estructura adicional en vez de la estructura SPass. Los parámetros de esta estructura que pueden ser necesarios van a ubicarse en el array tipo double. No vamos a reescribir por completo la estructura, ya que usaremos las posibilidades de la herencia Procedemos a la implementación:
1. Cree el archivo ColorOptimization.mqh. Este archivo va a contener todas las funciones para crear los informes de color.
2. Incluimos el archivo HTMLReport.mqh en el principio del archivo ColorOptimization.mqh:
#include <HTMLReport.mqh>
3. Creamos una nueva estructura que hereda todos los campos de la estructura SPass, y añadimos los arrays factor[] y dParameters[] en ella:
struct SPass2:SPass{ double factor[9]; double dParameters[]; };
Ambos arrays tienen el tipo double. El array factor[] va a incluir 9 valores resultantes, es decir, todos los valores a excepción del Pass (número del paso), y salvo los parámetros optimizados. Los valores de los parámetros optimizados estarán en el array sParameters[]. A pesar de que todos los datos ya están disponibles en la estructura, ellos están representados en formato string, por tanto, al usarlos, cada vez habría que convertirlos en números. Mientras que en nuestro caso, podemos tener los datos en un formato conveniente para el uso.
4. Creamos una estructura final para los datos de la optimización.
struct SOptimization2{ string ParameterName[]; SPass2 Pass[]; };
5. Creamos una función para convertir los datos de la estructura SOptimization en SOptimization2:
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]); } } } }
La función recibe la estructura de datos como primer parámetro; una nueva estructura se devuelve por referencia como segundo parámetro. El ciclo se ejecuta en la función por todos los pasos de la optimización, algunos campos de la estructura se copian, para algunos se ejecuta la conversión del tipo. En general, el proceso no es complicado y puede ser entendido al analizar el código de la función.
Será conveniente usar la enumeración para acceder a los elementos del array factor[]
enum EOptimizatrionFactor{ Result=0, Profit=1, ExpectedPayoff=2, ProfitFactor=3, RecoveryFactor=4, SharpeRatio=5, Custom=6, EquityDD_perc=7, Trades=8 };
Los valores de las variantes de la enumeración se empiezan con cero y se incrementan a 1, por tanto, no es necesario indicar los valores al describir la enumeración. No obstante, los valores están indicados, porque es necesario tener una correspondencia con el array factor[] para esta enumeración. Eso ayudará a evitar posibles errores al realizar algunas mejoras en el futuro.
6. Creamos una función para cargar el archivo del informe en la estructura SOptimization2, que será parecida a la función OptimizerXMLReportToStruct() desde el archivo HTMLReport.mqh:
bool OptimizerXMLReportToStruct2(string aFileName,SOptimization2 & aOptimization){ SOptimization tmp; if(!OptimizerXMLReportToStruct(aFileName,tmp)){ return(false); } ConvertOptimizationStruct(tmp,aOptimization); return(true); }
El nombre del archivo con el informe se pasa a la función como el primer parámetro; la estructura llenada SOptimization2 se devuelve como el segundo parámetro.
Ahora, todo está listo para resolver el problema principal del artículo. .
Creando un informe de color
Las funciones para crear un informe de color van a ubicarse en el archivo ColorOptimization.mqh. La llamada de estas funciones van a realizarse desde el script.
1. Creamos un script con el nombre ColorOptimization.mq5.
2. El archivo ColorOptimization.mqh será incluido en el script ColorOptimization.mq5.
#include <ColorOptimization.mqh>
3. Añadimos los parámetros externos al script. Primero, añadimos una propiedad que indica en la existencia de la ventana de propiedades, luego, las variables externas.
Propiedad:
#property script_show_inputs
Variables externas:
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;
Asignación de variables:
- ReportName — nombre del archivo del informe inicial de la optimización;
- OutputName — nombre del archivo del informe creado por el script;
- Factor1 — el primer factor a base del cual se determina el color del informe;
- Factor2 — el segundo factor a base del cual se determina el color del informe;
- Factor3 — el tercer factor a base del cual se determina el color del informe;
- Factor1Invert — inversión del primer factor;
- Factor2Invert — inversión del segundo factor;
- Factor3Invert — inversión del tercer factor;
- Sort — activar el ordenamiento del informe final de acuerdo con la indicación de color;
4. En la función OnStart() del script, declaramos una variable del tipo SOptimisation2 y recibimos los datos del informe inicial en ella:
SOptimization2 opt; if(!OptimizerXMLReportToStruct2(ReportName,opt)){ Alert("Error OptimizerXMLReportToStruct2"); return; }
5. Como el modelo RGB es uno de varios modelos de color diferentes, intentaremos garantizar la posibilidad de nuevas modificaciones de la biblioteca, en particular, la adición de otros modelos de color. Por eso, empezaremos con el cálculo de indicaciones abstractas en el rango de 0 a 1, en vez de calcular los valores de color de los componentes RGB. Luego, convertimos estas indicaciones en los componentes RGB en el rango de 0 a 255. Cada paso de la optimización tiene su indicación de color, entonces, hay que añadir tres campos para los componentes de color a la estructura SPass2, pero en vez de añadir tres campos, vamos a añadir un array de tres elementos:
double ColorComponent[3];
6. Para calcular los componentes de color, escribiremos la función SolveColorComponents() en el archivo ColorOptimization.mqh. Hay que pasar los siguientes parámetros a la función:
- SOptimization2 & aOpt — datos del informe final de la optimización;
- int i1, int i2, int i3 — índices de los valores del informe inicial de la optimización (del array factor[9] de la estructura SPass);
- bool r1=false, bool r2=false, bool r3=false — variables para invertir los valores.
Después de ejecutar la función, el array ColorComponents[3] en el array de estructuras SPass será llenado de valores.
Para calcular los componentes de color, es necesario encontrar los valores máximos y mínimos de cada indicación, luego, calcular el valor de la indicación en el rango de 0 a 1 para cada paso. Abajo se muestra el código entero de la función 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; } }
Llamamos esta función desde el script:
SolveColorComponents(opt,Factor1,Factor2,Factor3,Factor1Invert,Factor2Invert,Factor3Invert);
7. Si el ordenamiento está activada en los parámetros externos del script, es necesario calcular el factor para el ordenamiento a realizar y ejecutarla. El mejor paso de la optimización es aquél durante el cual todos los parámetros combinados tienen el valor máximo. Si estos parámetros corresponden a los componentes RGB, la mejor opción será el color blanco. Así, el factor del ordenamiento se calcula como la media aritmética de tres componentes.
Añadimos un campos más a la estructura SPass2:
double SortFactor;
Escribiremos las funciones en el archivo ColorOptimization.mqh para calcular el factor del ordenamiento.
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; } }
Escribiremos la función del ordenamiento (se usa el método del ordenamiento de burbuja):
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; } } } }
Llamaremos a estas funciones desde el script. El factor del ordenamiento será necesario no sólo para ordenar la tabla, por eso, la función SolveSortFactor() va a invocarse independientemente del alor de la variable Sort:
SolveSortFactor(opt);
if(Sort){
SortFactorSort(opt);
}
Ahora, todo está listo para crear el informe. El informe va a componerse de dos partes. La primera parte prácticamente representará una copia de la tabla con los datos de la optimización, con un botón adicional con indicación de color (fig. 1). La segunda parte representará varios planos de color (tablas) para cada par de parámetros optimizados, cada celda de estas tablas va a incluir un gradiente que muestra cómo se cambian los resultados de la simulación para cada par de parámetros optimizados (fig. 2).
Tabla con indicaciones de color
La creación de la tabla con una indicación de color adicional se ejecuta en la función TableContent(). Esta función se ubica en el archivo ColorOptimization.mqh, y devuelve el código HTML de la tabla.
La creación de la tabla HTML es una tarea bastante simple. El coloreo de una celda con indicación de color se realiza especificando el estilo de la celda, el atributo background-color. Los componentes de color con el rango de variación de 0 a 1 se convierten fácilmente en los componentes que varían de 0 a 255, multiplicando los valores. Sin embargo, nos gustaría hacer la tabla con información más visual. Para eso, en la celda superior de la columna de la indicación de color, hay que especificar qué parámetro de optimización corresponde a un color, y colorear las celdas superiores de estas indicaciones con un color correspondiente (fig. 1).
Fig. 1. Fragmento del informe con indicación de color
Abajo se muestra el código entero de la función 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); }
Consideraremos esta función en detalle. Recibimos el número de pasos de la optimización en la variable size, el número de parámetros optimizados se pasa en la variable pc, el tamaño del array con los nombres de de las indicaciones (está declarado a nivel global):
int size=ArraySize(aOpt.Pass); int pc=ArraySize(aOpt.ParameterName); int nc=ArraySize(co_names);
Array global co_names[]:
string co_names[]={"Result","Profit","Expected Payoff", "Profit Factor","Recovery Factor", "Sharpe Ratio","Custom","Equity DD","Trades"};
Vamos a añadir el código HTML de la tabla a la variable string s a la medida de su formación, por eso, después de declarar la variable, le asignamos la etiqueta del inicio de la tabla:
string s="<table>";
Luego, se añade la etiqueta del inicio de la fila y la primera celda del encabezado con el texto «Pass».
s=s+"<tr>"; s=s+"<th>Pass</th>";
La columna «Pass» es seguida por las columnas con indicaciones, cualquiera de las cuales puede usarse para la formación de una indicación de color. Es necesario marcar tres columnas que se usan en este momento. Formamos el código HTML de las celdas:
for(int i=0;i<nc;i++){ s=s+"<th"+HStyle(i,i1,i2,i3)+">"+co_names[i]+"</th>"; }
En caso de necesidad, la función HStyle() forma el código que cambia el color del fondo de la celda:
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(""); }
Formamos el texto para las celdas con el encabezado de la indicación de color:
s=s+"<th>"+ColorCollHeader(i1,i2,i3)+"</th>";
Código de la función ColorCollHeader():
string ColorCollHeader(int i1,int i2,int i3){ return(co_names[i1]+"-R,<br>"+co_names[i2]+"-G,<br>"+co_names[i3]+"-B"); }
Luego, formamos el código HTML para las celdas con los nombres de los parámetros optimizados y terminamos la fila de la tabla:
for(int j=0;j<pc;j++){ s=s+"<th>"+aOpt.ParameterName[j]+"</th>"; } s=s+"</tr>";
Después de eso, se declaran tres variables auxiliares: r, g, b, y se empieza el ciclo en el que se realiza la formación del código HTML de todas filas del informe. Al principio de cada ciclo, se calcula el valor RGB de los componentes:
ComponentsToRGB(aOpt.Pass[i].ColorComponent[0], aOpt.Pass[i].ColorComponent[1], aOpt.Pass[i].ColorComponent[2], r,g,b);
Código de la función 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); }
Luego, se forma el código HTML de la fila con la celda que contiene los resultados de la simulación.
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>";
Después de eso, sigue la celda con indicación de color: Primero, el componente RGB se convierten en una cadena usando la función RGBToStr(), después de eso, se forma el código de la celda:
string cs=RGBToStr(r,g,b); s=s+"<td title='"+cs+"' style='background-color: "+cs+"'> </td>";
Código de la función RGBToStr():
string RGBToStr(int r,int g,int b){ return("rgb("+(string)r+","+(string)g+","+(string)b+")"); }
Las celdas con los valores de los parámetros optimizados se muestran al final de la fila:
for(int j=0;j<pc;j++){ s=s+"<td>"+aOpt.Pass[i].Parameters[j]+"</td>"; } s=s+"</tr>";
Al final de la función, la tabla se cierra y el contenido de la variable s se cierra:
s=s+"</table>"; return(s);
Planos con parámetros optimizados
El dibujado de este plano es posible cuando hay dos o más los parámetros optimizados. El plano se muestra en la fig. 2.
Fig. 2. plano de parámetros optimizados
Al principio del plano, se muestra la correspondencia de los parámetros por los ejes. En particular, por el eje X (por la horizontal) se encuentran los valores del parámetro Inp_Signal_MACD_PeriodSlow, y por el eje Y (por la vertical) se encuentran los valores del parámetro Inp_Signal_MACD_PeriodFast. El gradiente en las celdas muestra cómo se cambian los resultados de la simulación para este par de los valores del parámetro X y Y, cuando se cambian otros parámetros optimizados. El color del peor valor se encuentra a la izquierda, y el mejor está a la derecha. Las mejores y las peores variantes se determinan a base del factor del ordenamiento mencionado antes que se calcula como la media aritmética de los componentes abstractos del color.
La formación del código HTML de los planos se realiza en la función Color2DPlanes(). En esta función, se realiza el repaso de todos los posibles combinaciones de dos parámetros optimizados, y el código HTML se forma para cada par. Código de la función 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); }
El código HTML de un plano se forma en la función 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); }
Vamos a considerar en detalle la función Color2DPlane(). En la función se pasa la estructura SOptimization2 con todos los datos del informe de la optimización y dos variables tipo int: xi y yi, que son los índices del par de los parámetros optimizados para los cuales es necesario construir el plano. Primero, reunimos todas las variantes de los valores de cada par de parámetros en el array. Para eso, declaramos dos arrays, modificamos sus tamaños de acuerdo con el número de los pasos de la optimización y los llenamos con todas los valores posibles.
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]; }
Es necesario dejar sólo los valores únicos de los parámetros. Para eso, ordenamos los arrays y movemos los valores exclusivos al principio de los arrays:
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++; } }
Después de eso, la variable xc contiene el número de valores exclusivos de un parámetro, y la variable yc contiene el número de valores de otro parámetro. Vamos a añadir el código HTML del plano a la variable string s a la medida de su formación. Al declarar la variable s, insertamos en ella la información sobre los nombres de las variables y la etiqueta de apertura de la tabla:
string s="<hr><h3>X - "+aOpt.ParameterName[xi]+", Y - "+aOpt.ParameterName[yi]+"</h3><table>";
Formamos la primera fila de la tabla con los valores del parámetro 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>";
Luego, sigue el ciclo por todas las variantes del parámetro y:
for(int y=0;y<yc;y++){
En este ciclo, iniciamos una fila en cada paso y añadimos una celda con el valor del parámetro y.
s=s+"<tr>"; s=s+"<td>"+(string)ya[y]+"</td>";
Luego, añadimos las celdas con el gradiente (ellas se añaden ciclicamente por todas las variantes del parámetro x):
for(int x=0;x<xc;x++){
Para crear el gradiente, es necesario encontrar el mejor y el peor repaso de la optimización:
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; } } }
Después de ejecutar esta parte del código, los índices del mejor y el peor paso de la optimización se encuentran en las variables mxi y mni .
Convertimos los componentes abstractos del color en 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);
Para analizar los planos de forma más eficiente, añadimos las descripciones emergentes (se hacen a través del atributo 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"; }
Las descripciones emergentes se muestran en la fig. 3.
Fig. 3. Descripción emergente para una de las celdas del plano
En esta descripción emergente hay todos los datos sobre el peor y el mejor paso de la optimización (primero, el mejor, tras, el signo "/" el mejor). Los valores del componente RGB del gradiente se indican en la primera línea de la descripción emergente.
Ahora, lo más importante es el gradiente:
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>";
La visualización del gradiente fue verificada en los navegadores: Opera, Google Chrome, Yandex y Microsoft-Edge. Funciona bien en todos ellos.
Al final de cada fila, añadimos la etiqueta del fin de la fila.
s=s+"</tr>";
Al final de la tabla, añadimos la etiqueta del fin de la tabla y devolvemos el código HTML formado:
s=s+"<table>"; return(s);
Nos queda llamar a las funciones desde el script:
string report=HTMLStart("Color Optimization","style2.css")+ TableContent(opt,Factor1,Factor2,Factor3)+ Color2DPlanes(opt)+HTMLEnd();
Las funciones HTMLStart() и HTMLEnd() fueron tomadas desde el artículo Analizando resultados comerciales con la ayuda de informes HTML. El archivo de estilos del mismo artículo fue un poco modificado y renombrado en style2.css.
Los archivos hechos ColorOptimization.mqh, скрипт ColorOptimization.mq5 se adjuntan al artículo.
Modificación del modelo de color
El código en el archivo ColorOptimization.mqh está estructurizado de tal manera para que se pueda modificarlo fácilmente para otro modelo de color. Añadimos la variante con el modelo de color CMY. Primero, hagamos algunos pasos previos.
1. Hagamos las copias de los archivos ColorOptimization.mqh y ColorOptimization.mq5 con los nombres ColorOptimization2.mqh y ColorOptimization2.mq5.
2. Añadimos al archivo ColorOptimization2.mqh dos constantes para dos tipos de modelos de color y una variable global que va a determinar el modelo de color usado:
#define MODEL_RGB 0 #define MODEL_CMY 1 int co_ColorModel;
3. Añadimos al script una enumeración y una variable externa usando la cual el usuario va a seleccionar el modelo de color:
enum EColorModel{ RGB=MODEL_RGB, CMY=MODEL_CMY }; input EColorModel ColorModel = RGB;
Al principio de la función OnStart() del script, asignamos el valor seleccionado en la ventana de propiedades usando la variable co_ColorModel:
co_ColorModel=ColorModel;
Ahora, la principal modificación se realiza en las funciones del archivo ColorOptimization2.mqh. Principalmente, hay que modificar la función ComponentsToRGB(). Los valores de los componentes del modelo CMY varían de 0 a 1, por tanto, los valores de los componentes de la estructura de datos del informe corresponden a los componentes CMY, queda sólo recalcularlos en RGB. Obtenemos la siguiente función 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); } }
La transformación del modelo CMY en RGB está implementada en una función separada:
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); }
Las demás modificaciones conciernen sólo a los elementos auxiliares del informe. Es la modificación de la función HStyle() para colorear correctamente las celdas de la fila de encabezado de la tabla:
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(""); }
La modificación de la función ColorCollHeader() para un correcto encabezado de la columna de la indicación de color:
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 ""; }
Nos queda modificar el texto de las descripciones emergentes de la tabla principal y los planos de color. Para la tabla principal, hay que modificar el valor del atributo title en la función TableContent(). Las dos siguientes líneas:
string cs=RGBToStr(r,g,b); s=s+"<td title='"+cs+"' style='background-color: "+cs+"'> </td>";
deben ser modificadas de la siguiente forma:
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>";
Para los planos de color, la modificación se ejecuta en la función Color2DPlane(). Aquí, también hay que modificar el valor del atributo title. La siguiente línea:
string title=RGBToStr(mnr,mng,mnb)+"/"+RGBToStr(mxr,mxg,mxb)+"\n";
debe ser alterada de la siguiente forma:
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"; }
Ahora, al iniciar el script, se puede seleccionar el tipo del modelo de color. La diferencia entre CMY y RGB es que en CMY los mejores valores serán negros y los demás matices serán otros (fig. 4, 5).
Fig. 4. Fragmento del informe creado con el uso del modelo CMY
Fig. 5. Plano de color obtenido con el uso del modelo CMY
Interpretación de las indicaciones de color
Está claro que durante el uso de RGB, las mejores opciones se aproximan al blanco, y durante el uso de CMY, al negro. Para una interpretación correcta de los matices, es necesario comprender cómo se combinan los componentes individuales del modelo de color, y cómo se forma el color resultante.
Vamos a considerar en detalle el modelo RGB. Cuando los valores de todos los componentes son iguales a 0, obtenemos el color negro. Cuando todos los componentes son iguales al valor máximo, el color es blanco, cualquier otra combinación de valores dan matices diferentes. Si uno de los componentes tiene el valor máximo y dos componentes son iguales a 0, es obvio que obtenemos el color puro del componente correspondiente: rojo, verde o azul. Si dos componentes tienen los valores máximos y el tercero es igual a cero, también tiene colores puros. El rojo y el verde dan el amarillo, el verde y el azul dan el ciano, el rojo y el azul dan el magenta. La figura 6 muestra algunas combinaciones de los componentes RGB.
Fig. 7. Combinaciones principales de los componentes RGB
A base del matiz se puede juzgar sobre la indicación que aporta más influencia positiva en el resultado de la simulación. El matiz rojo es el primer parámetro; el amarillo es el primero y el segundo; el verde es el tercer parámetro, etc.
Los colores en el modelo RGB se suman de forma semejante a las luces coloreadas. En el modelo CMY, cuando se aumentan los valores de los componentes, estos valores se restan del color blanco. Así, el color negro corresponde al valor máximo de todos los componentes. El modelo CMY es semejante a la mezcla de los tintes, es decir, si no hay tintes, tenemos una hoja en blanco. Si mezclamos muchos tintes diferentes, obtenemos el color negro (o más bien, el color sucio si los tintes son naturales, ya que es necesario saber mezclarlos). Las principales combinaciones de los componentes CMY se muestran en la fig. 8.
Fig. 8. Combinaciones principales de los componentes CMY
Los colores en CMY son los mismos que en RGB, pero están descolocados. Su interpretación será la siguiente: el matiz ciano es el primer parámetro; el azul es el primero y el segundo; el magenta representa el segundo; el rojo es el segundo y el tercero; el amarillo es el tercero; el verde es el primero y el tercero.
Como podemos observar, no hay diferencia fundamental en el uso del modelo RGB o CMY.
Conclusiones
La percepción de los colores es un proceso subjetivo, por tanto, es difícil hacer una conclusión clara sobre la conveniencia y la utilidad de la representación de color. Por lo menos, utilizando una indicación visual, el grado de luz (que significa la proximidad al blanco en RGB), se puede sacar conclusiones sobre la combinación de tres parámetros. Eso simplifica considerablemente el proceso del análisis del informe. Cuando la selección es automatizada, como en este artículo, durante el ordenamiento de la tabla, todo se reduce a la decisión a base de la media aritmética de tres valores. En realidad, aquí hacemos el primer paso en el área de la lógica difusa, usando la cual se puede calcular el valor final no como una media aritmética simple, sino de una manera más compleja. No obstante, solamente los experimentos prácticos pueden evaluar la eficacia de este método.
Archivos adjuntos
- HTMLReport.mqh — archivo con funciones para analizar los informes (véase el artículo Analizando resultados comerciales con la ayuda de informes HTML);
- ColorOptimization.mqh — archivo con las funciones para crear los informes de color usando el modelo RGB;
- ColorOptimization.mq5 — script con el ejemplo del uso de las funciones ColorOptimization.mqh;
- ColorOptimization2.mqh — archivo con las funciones para crear los informes de color usando el modelo RGB o CMY;
- ColorOptimization2.mq5 — script con el ejemplo del uso de las funciones ColorOptimization2.mqh;
- ReportOptimizer-555849.xml — archivo con el informe de la optimización, utilizado en el artículo;
- ColorOptimization.htm — archivo con el informe obtenido por el script ColorOptimization.mq5;
- ColorOptimization2.htm — archivo con el informe obtenido por el script ColorOptimization2.mq5 (modelo CMY);
- Style2.css — archivo de los estilos para los informes creados por los scripts ColorOptimization.mq5 y ColorOptimization2.mq5.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/5437
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso