Analizando resultados comerciales con la ayuda de informes HTML

8 abril 2019, 15:48
Dmitry Fedoseev
0
889

Introducción

Si un tráder tiene la intención de atraer inversores, es altamente probable que estos se interesen por sus resultados comerciales. Y esto significa que deberá mostrarlos, es decir, enseñar su historia comercial completa. El terminal MetaTrader 5 permite guardar la historia comercial en un archivo (ventana "Instrumentos" — pestaña "Comercio" — menú contextual — Informe). El informe se puede guardar en archivos XLSX (para el programa Microsoft Excel) y en archivos HTML, que se pueden visualizar en cualquier navegador. Resulta obvio que la segunda opción es más popular, puesto que el programa Excel a veces no se puede instalar en todas las computadoras, mientras que todas disponen de un navegador. Así que lo más seguro es que un inversor potencial se encuentre con un informe HTML del trabajo del tráder, antes que con cualquier otro.

Tras familiarizarse con los indicadores estándar mostrados en el informe, alguien podría necesitar calcular sus propios valores usando los datos de las transacciones, y para ello necesitará extraer dichos datos del informe. La tarea de la extracción de datos de los informes HTML nos ocupará en el siguiente artículo. Primero investigaremos la estructura de los informes, y después escribiremos una función para analizarlos. A la función se le transmitirá el nombre del archivo con el informe, después la función retornará la estructura con todos los datos. Gracias a esta estructura, se posibilitará el acceso directo a cualquier valor del informe. Usando esta estructura, será posible formar rápida y fácilmente nuestros propios informes con las métricas deseadas.

Aparte de los informes sobre el comercio, MetaTrader 5 permite guardar informes sobre la simulación y la optimización de expertos. El informe de simulación, al igual que la historia comercial, puede guardarse en dos formatos: XLSX y HTML, mientras que el informe de optimización se guarda en el formato XML.

En este artículo, vamos a analizar con detalle el informe HTML tras la simulación, el informe XML de optimización y el informe HTML con la historia comercial.

Archivos HTML y XML

El archivo HTML, en esencia, es un archivo de texto dentro del cual se encuentra un texto (datos representados) y tags que indican de qué forma se deben representar estos datos de texto (fig. 1). Cualquier tag comienza con el símbolo "<", y termina con el símbolo ">". Por ejemplo, el tag <br> indica que el tag que le sigue debe comenzar con una nueva línea, y el tag <p>, con un nuevo párrafo (no solo con una nueva línea, sino también hacer un espacio en forma de línea vacía). Dentro de los tags, pueden ubicarse diversos atributos, por ejemplo, <p color="red"> significa que el texto que va tras él debe ubicarse desde el nuevo párrafo y tiene que ser de color rojo. 

Archivo HTML en el bloc de notas
Fig. 1. Fragmento de un archivo HTML abierto en el bloc de notas

Para cancelar la acción de un tag, se usa el tag de cierre, que se distingue del tag de apertura por la presencia del símbolo "/" al inicio, por ejemplo, </p> es el tag de cierre de un párrafo. Algunos tags no presuponen el uso de tags de cierre, en concreto, <br>. En algunos tags, el tag de cierre puede usarse, pero no es obligatorio. Podemos comenzar un nuevo párrafo sin cerrar el anterior. Si, como se muestra en el ejemplo anterior con el tag <p>, se usa el atributo de color, debemos utilizar el tag de cierre, para marcar el coloreado del texto con color rojo. También existen tags que deben cerrarse obligatoriamente, por ejemplo, el tag de recuadro <table>. La finalización de un recuadro siempre debe terminarse con un tag de cierre </table>. Dentro del recuadro se ubican líneas designadas por un tag de apertura <tr> y otro de cierre </tr>, y dentro de las línas se ubican las celdas designadas por los tags <td> y </td>. En ocasiones, las celdas se designan con el tag <th> (celda de encabezado), en un recuadro pueden existir celdas designadas tanto por el tag <td>, como por el tag <th>. Los tags de cierre de líneas y celdas son obligatorios.

En la actualidad, la mayoría de atributos html prácticamente no se utilizan, en lugar de ello, se usa un atributo, "style", y en él se describe el aspecto externo del elemento, por ejemplo, <p style="color: red">, un párrafo con texto de color rojo, pero este tampoco es un método muy extendido. Con mayor frecuencia, se usa el atributo "class", en el que se indica solo el nombre de la clase del estilo, por ejemplo, <p class="small">, y la propia clase (descripción del estilo) se ubica al inicio del documento html, o incluso en un archivo CSS aparte (Cascading Style Sheet — recuadro de estilos en cascada). 

Los archivos XML (fig. 2) se asemejan mucho a los archivos HTML, la principal diferencia es que en HTML existe un conjunto de tags rigurosamente limitado, mientras que en XML, el conjunto de tags puede ampliarse añadiendo tags propios.

Archivo XML en el bloc de notas
Fig. 2. Fragmento de un archivo XML abierto en el bloc de notas

La principal misión de HTML es la representación de datos, por eso el conjunto de tags es estándar, y los docuementos HTML se representan de una forma relativamente igual en los navegadores. La principal misión de XML es guardar y transmitir datos, por eso, en él existe la posibilidad de estructurar aleatoriamente los datos; ya el propio usuario que utiliza este archivo debe comprender para qué lo hace y qué datos desea extraer.

Funciones de línea o expresiones regulares

Normalmente, para extraer cualquier dato del texto, se usan las expresiones regulares. En CodeBase, es posible encontrar la biblioteca RegularExpressions para trabajar con ellas, además, existe un artículo sobre su uso: "Expresiones regulares para los traders". No cabe duda de que las expresiones regulares son un recurso potente y cómodo para el análisis de datos de texto. Si resolvemos con mucha frecuencia tareas de análisis de datos, y estas son siempre distintas, resulta extraño imaginar esta actividad sin utilizar expresiones regulares.

Sin embargo, las expresiones regulares tienen un par de defectos: antes de utilizarlas, es necesario estudiarlas en profundidad. El propio tema de las expresiones regulares es bastante amplio, en lo que respecta a ellas, no resulta tan sencillo "echar un vistazo a la guía cuando hace falta": debemos estudiar de principio a fin toda la teoría y poseer habilidades prácticas. La propia forma de pensamiento necesaria para usar las expresiones regulares se diferencia significativamente de la forma de pensar necesaria para resolver, digamos, las tareas de programación habituales. Incluso si sabemos usar las expresiones regulares, pueden surgir pequeñas dificultades al pasar al trabajo con expresiones regulares y viceversa. 

Si las tareas de análisis de datos no son complejas y no surgen con frecuencia, podemos usar las funciones de línea estándar. Además, en las guías de uso de expresiones regulares, se encuentra con frecuencia la opinión de que el uso de las funciones de línea funciona más rápido. Claro que se trata de una afirmación un tanto dudosa, pues la velocidad dependerá del tipo de la tarea a realizar y las condiciones de uso de las expresiones regulares; no obstante, las funciones de línea son un método totalmente legítimo para solucionar tareas de análisis de datos.

Entre la enorme abundancia de funciones de línea, nosotros vamos a necesitar solo algunas de ellas: StringFind(), StringSubstr(), StringReplace(). Y también, puede ser, algunas otras muy sencillas: StringLen(), StringTrimLeft(), StringTrimRight().

Informe del simulador

El mayor volumen de información lo ocupa el informe del simulador, por eso comenzaremos por él, para familiarizarnos con la tarea en general y prever todas las complicaciones que puedan surgir al resolverla. Al crear este artículo, se ha usado el informe de simulación del experto ExpertMACD con los parámetros por defecto (el informe se adjunta al artículo). Abrimos el informe en el navegador, para ver qué tenemos entre manos (fig. 3).

Apartados del informe HTML
Fig. 3. Informe HTML del simulador en el navegador Los principales apartados del informe se muestran con líneas rojas y marcas azules

En el informe podemos ver que todos los datos se dividen en varias apartados: nombre del informe, parámetros, resultados, gráficos, órdenes, transacciones, total, etc.  

En cada navegador existe un comando para visualizar el código de la página. Si clicamos con el botón derecho del ratón en la página, se abrirá un menú contextual en el que se encontrará el comando "Texto fuente de la página", "Código fuente de la página" o algo similar. 

Como resultado de la investigación visual del código fuente del informe, resultará que todos los datos del informe se ubican en recuadros (tag <table>). En total, en el informe hay dos recuadros. En el primer recuadro se encuentran los datos generales: desde el nombre del informe hasta el indicador "Tiempo medio de mantenimiento de la posición", es decir, todo lo que se encuentra hasta la sección "Órdenes". En el segundo recuadro, están los datos sobre las órdenes y transacciones, así como los datos sobre el estado total del depósito. La ubicación de datos heterogéneos en un solo recuadro se logra gracias al uso del atributo "colspan", que une varias celdas. Los nombres de los parámetros comunes y sus valores se ubican en diferentes celdas del recuadro, a veces estas celdas se encuentran en una misma línea, a veces en distintas.

Un aspecto esencial al que debemos prestar atención es la presencia de los atributos "id", los identificadores únicos de los tags. Usando estos valores de los identificadores, podríamos encontrar las celdas con los datos necesarios sin lugar a error. Sin embargo, los atributos "id" no se usan, por eso, buscaremos todos los parámetros contando las líneas y celdas. Por ejemplo, el nombre del informe "Informe del Simulador de Estrategias" se encuentra en el primer recuadro, primera línea, primera celda. 

El número de órdenes y transacciones que se encuentra en el segundo recuadro será diferente en todos los informes, por eso debemos encontrar el rasgo del inicio de las órdenes y el final de las transacciones. Después de las transacciones se encuentra una línea que solo dispone de una celda, y después una línea con los encabezados, es decir, un rasgo indicativo. Por el momento, no necesitamos estudiar al detalle el número de líneas y celdas, un poco más tarde dispondremos de un modo más cómodo de hacerlo.

Todos los datos se encuentran en las celdas del recuadro, por eso, lo único que necesitamos en la etapa inicial es extraer los recuadros, sus líneas y sus celdas.

Echemos un vistazo a los informes de la historia comercial y la optimización. El informe con la historia es semejante al informe del simulador, solo que el apartado con los resultados se ubica abajo, y además ha aparecido otro apartado con el estado total del depósito (fig. 4).

Informe HTML con la historia en el navegador

Fig. 4. Informe HTML con la historia en el navegador. Los principales apartados del informe se muestran con líneas rojas y marcas azules

Al estudiar el código HTML de la historia comercial, resulta que en el mismo, para  designar las celdas, se usa no solo el tag <td>, sino también el tag <th>, una celda encabezado, mientras que todos los datos se ubican en un solo recuadro.

El informe de optimización es muy sencillo, en él solo hay un apartado de datos con un solo recuadro. El estudio del código muestra que para crear recuadros en él se usa el tag <Table>, las líneas se definen con el tag <Row>, y las celdas con el tag <Cell> (todos los tags con los tags de cierre correspondiente).

Cargando el archivo

Antes de hacer cualquier cosa con los datos del informe, debemos leerlos desde el archivo. Vamos a usar la siguiente función, que retorna el contenido completo del archivo en una variable de línea:

bool FileGetContent(string aFileName,string & aContent){
   int h=FileOpen(aFileName,FILE_READ|FILE_TXT);
   if(h==-1)return(false);
   aContent="";
   while(!FileIsEnding(h)){
      aContent=aContent+FileReadString(h);
   }
   FileClose(h);
   return(true);
}

Con el primer parámetro en la función se transmite el nombre del archivo con el informe; con el segundo parámetro, se transmite por enlace la variable en la que se encontrará el contenido del archivo, mientras que la propia función retorna true/false dependiendo del éxito de su funcionamiento.

Extrayendo los recuadros

Para ubicar los datos del recuadro, usaremos dos estructuras. Una estructura que contiene una línea del recuadro:

struct Str{
   string td[];
};

En cada elemento de la matriz td[] se ubicará el contenido de una celda.

Y una estructura que contiene el recuadro completo (todas las líneas):

struct STable{
   Str tr[];
};

El proceso de extracción de los datos del informe tendrá lugar de la forma siguiente: primero encontramos el inicio del recuadro por el tag de apertura. Puesto que los tags pueden tener atributos, se ejecutará solo la búsqueda del inicio del tag: "<table". Tras encontrar el inicio del tag de apertura, hallamos su final, la cuña ">". Después de ello, buscamos el final del recuadro, es decir, el tag de cierre: "</table>". Esta tarea se ve simplificada por el hecho de que los informes no tienen recuadros incorporados, es decir, después de cada tag de apertura viene solo un tag de cierre.

Tras ubicar el contenido de los recuadros, hacemos lo mismo para cada uno de ellos, pero con las líneas buscaremos el inicio de ellas "<tr" y su finalización "</tr", a continuación, buscaremos para cada línea el inicio de las celdas "<td" y su final </td>. Al extraer las líneas, la tarea se complica un poco, ya que la celda puede designarse tanto con el tag <td> como con el tag <th>, por eso, en lugar de la función StringFind(), usaremos nuestra propia función TagFind():

int TagFind(string aContent,string & aTags[],int aStart,string & aTag){
   int rp=-1;
   for(int i=0;i<ArraySize(aTags);i++){
      int p=StringFind(aContent,"<"+aTags[i],aStart);
      if(p!=-1){
         if(rp==-1){
            rp=p;
            aTag=aTags[i];
         }
         else{
            if(p<rp){
               rp=p;
               aTag=aTags[i];
            }
         }      
      }
   }
   return(rp);
}

Parámetros de la función:

  • string aContent — línea en la que se ejecuta la búsqueda;
  • string & aTags[] — matriz con tags;
  • int aStart — posición desde la que se ejecuta la búsqueda;
  • string & aTag — el tag encontrado se retorna mediante enlace.

A diferencia de la función StringFind(), a la función TagFind() se le transmite, no la línea a buscar, sino la matriz. Además, a las líneas buscadas en la función se les añaden las cuñas de apertura "<". La función retorna la posición del tag más cercano.

Usando la función TagFind(), buscaremos secuencialmente los tags de apertura y cierre, y reuniremos en una matriz todo lo que se encuentre entre ellos:

int TagsToArray(string aContent,string & aTags[],string & aArray[]){
   ArrayResize(aArray,0);
   int e,s=0;
   string tag;
   while((s=TagFind(aContent,aTags,s,tag))!=-1 && !IsStopped()){  
      s=StringFind(aContent,">",s);
      if(s==-1)break;
      s++; 
      e=StringFind(aContent,"</"+tag,s);   
      if(e==-1)break;  
      ArrayResize(aArray,ArraySize(aArray)+1);
      aArray[ArraySize(aArray)-1]=StringSubstr(aContent,s,e-s);  
   }
   return(ArraySize(aArray));
}

Parámetros de la función:

  • string aContent — línea en la que se ejecuta la búsqueda;
  • string & aTags[] — matriz con tags;
  • string aArray[] — mediante un enlace se retorna la matriz con el contenido de todos los tags encontrados.

A la función TagsToArray() se le transmite la matriz de tags buscados solo al extraer las celdas, por eso, para mayor comodidad, al usarla en el resto de los casos con las posibilidades de la sobrecarga, escribiremos un análogo suyo, pero con un parámetro de línea:

int TagsToArray(string aContent,string aTag,string & aArray[]){
   string Tags[1];
   Tags[0]=aTag;
   return(TagsToArray(aContent,Tags,aArray));
}

Parámetros de la función:

  • string aContent — línea en la que se ejecuta la búsqueda;
  • string & aTag — tag buscado;
  • string aArray[] — mediante un enlace se retorna la matriz con el contenido de todos los tags encontrados.

Ahora vamos a ocuparnos propiamente de la función para la extracción del contenido de los recuadros. A la función se le transmite el parámetro de línea aFileName con el nombre del archivo analizado. En la función se usará la variable de línea local para el contenido del archivo, así como la matriz local de estructuras STable:

STable tables[];
string FileContent;

Usando la función FileGetContent, obtenemos el contenido completo del informe:

if(!FileGetContent(aFileName,FileContent)){
   return(true);
}

Ahora, las variables auxiliares:

string tags[]={"td","th"};
string ttmp[],trtmp[],tdtmp[];
int tcnt,trcnt,tdcnt;

En la matriz tags se han preparado las posibles variantes de tag de las celdas. En las matrices de línea ttmp[], trtmp[], tdtmp[] se ubicará temporalmente el contenido de los recuadros, líneas y celdas, respectivamente. 

Extrayendo los recuadros:

tcnt=TagsToArray(FileContent,"table",ttmp);
ArrayResize(tables,tcnt);

Pasando en un ciclo por todos los recuadros, extraemos las líneas; iterando por las líneas, extraemos las celdas:

for(int i=0;i<tcnt;i++){
   trcnt=TagsToArray(ttmp[i],"tr",trtmp);
   ArrayResize(tables[i].tr,trcnt);      
   for(int j=0;j<trcnt;j++){         
      tdcnt=TagsToArray(trtmp[j],tags,tdtmp);
      ArrayResize(tables[i].tr[j].td,tdcnt);
      for(int k=0;k<tdcnt;k++){  
         tables[i].tr[j].td[k]=RemoveTags(tdtmp[k]);
      }
   }
} 

Los recuadros, líneas y celdas se extraen a las matrices temporales, después, la matriz de celdas se copia a la estructura. Al copiar una celda, se ejecuta su filtrado con la función RemoveTags(). En las celdas de los recuadros pueden encontrarse tags. La función RemoveTags() los elimina y deja solo los datos útiles.

Función RemoveTags():

string RemoveTags(string aStr){
   string rstr="";
   int e,s=0;
   while((e=StringFind(aStr,"<",s))!=-1){
      if(e>s){

         rstr=rstr+StringSubstr(aStr,s,e-s);
      }
      s=StringFind(aStr,">",e);
      if(s==-1)break;
      s++;
   }
   if(s!=-1){
      rstr=rstr+StringSubstr(aStr,s,StringLen(aStr)-s);
   }
   StringReplace(rstr,"&nbsp;"," ");
   while(StringReplace(rstr,"  "," ")>0);
   StringTrimLeft(rstr);
   StringTrimRight(rstr);
   return(rstr);
}

Vamos a analizar la función RemoveTags(). La variable "s" se usa para la posición de inicio de los datos útiles: primero su valor es igual a 0, porque los datos pueden comenzar desde el propio inicio de la línea. En el ciclo while se ejecuta la búsqueda de la cuña de apertura "<", que indica el inicio del tag. Cuando el inicio del tag ha sido localizado, todos los datos de la posición de la variable "s" indicada y hasta la posición encontrada se copian a la variable rstr, y luego se ejecuta la búsqueda del final del tag, es decir, el comienzo de los nuevos datos útiles. Después del ciclo, si el valor de la variable "s" no es igual a -1 (esto significa que se acaban los datos útiles, pero estos no han sido copiados), se ejecuta su copiado a la varibale rstr. Al final de la función, se sustituye el símbolo especial de espacio &nbsp; por un espacio simple, también se eliminan los espacios repetidos y aquellos que se encuentran a inicio y final de línea.

En esta etapa, tenemos la matriz de estructuras tables, rellenada con datos puros del recuadro. Guardamos esta matriz en un archivo de texto. Al guardarlo, numeramos los recuadros, líneas y celdas (el guardado se realiza en el archivo con el nombre 1.txt):

int h=FileOpen("1.txt",FILE_TXT|FILE_WRITE);

for(int i=0;i<ArraySize(tables);i++){
   FileWriteString(h,"table "+(string)i+"\n");
   for(int j=0;j<ArraySize(tables[i].tr);j++){      
      FileWriteString(h,"   tr "+(string)j+"\n");         
      for(int k=0;k<ArraySize(tables[i].tr[j].td);k++){
         FileWriteString(h,"      td "+(string)k+": "+tables[i].tr[j].td[k]+"\n");
      }
   }
}

FileClose(h);

Al analizar este archivo, resultará mucho más sencillo entender qué datos se encuentran en qué celda. Aquí mostramos un fragmento del archivo:

table 0
   tr 0
      td 0: Informe del Simulador de Estrategias
   tr 1
      td 0: IMPACT-Demo (Build 1940)
   tr 2
      td 0: 
   tr 3
      td 0: Ajustes
   tr 4
      td 0: Asesor:
      td 1: ExpertMACD
   tr 5
      td 0: Símbolo:
      td 1: USDCHF
   tr 6
      td 0: Periodo:
      td 1: H1 (2018.11.01 - 2018.12.01)
   tr 7
      td 0: Parámetros:
      td 1: Inp_Expert_Title=ExpertMACD
   tr 8
      td 0: 
      td 1: Inp_Signal_MACD_PeriodFast=12
   tr 9
      td 0: 
      td 1: Inp_Signal_MACD_PeriodSlow=24
   tr 10
      td 0: 
      td 1: Inp_Signal_MACD_PeriodSignal=9
   tr 11

Estructura de los datos del informe

Solo queda un poco de trabajo rutinario y aburrido: escribir una estructura que se adecue a los datos del informe y rellenarla con los datos de la matriz tables. El informe se divide en varios apartados, por lo tanto, se usarán varias estructuras reunidas en una común.

Estructura para el apartado "Ajustes":

struct SSettings{
   string Expert;
   string Symbol;
   string Period;
   string Inputs;
   string Broker;
   string Currency;
   string InitialDeposit;
   string Leverage;
};

El objetivo de los campos de la estructura resulta obvio si miramos a su nombre. Todos los campos de la estructura contendrán los datos con exactamente el mismo aspecto del informe, mientras que la lista de parámetros del experto se encontrará en otra variable de línea Inputs.

Estructura de datos para la sección "Resultados":

struct SResults{
   string HistoryQuality;
   string Bars;
   string Ticks;
   string Symbols;
   string TotalNetProfit;
   string BalanceDrawdownAbsolute;
   string EquityDrawdownAbsolute;
   string GrossProfit;
   string BalanceDrawdownMaximal;
   string EquityDrawdownMaximal;
   string GrossLoss;
   string BalanceDrawdownRelative;
   string EquityDrawdownRelative;
   string ProfitFactor;
   string ExpectedPayoff;
   string MarginLevel;
   string RecoveryFactor;
   string SharpeRatio;
   string ZScore;
   string AHPR;
   string LRCorrelation;
   string OnTesterResult;
   string GHPR;
   string LRStandardError;
   string TotalTrades;
   string ShortTrades_won_pers;
   string LongTrades_won_perc;
   string TotalDeals;
   string ProfitTrades_perc_of_total;
   string LossTrades_perc_of_total;
   string LargestProfitTrade;
   string LargestLossTrade;
   string AverageProfitTrade;
   string AverageLossTrade;
   string MaximumConsecutiveWins_cur;
   string MaximumConsecutiveLosses_cur;
   string MaximalConsecutiveProfit_count;
   string MaximalConsecutiveLoss_count;
   string AverageConsecutiveWins;
   string AverageConsecutiveLosses;
   string Correlation_Profits_MFE;
   string Correlation_Profits_MAE;
   string Correlation_MFE_MAE;      
   string MinimalPositionHoldingTime;
   string MaximalPositionHoldingTime;
   string AveragePositionHoldingTime;
};

Estructura para los datos de una orden:

struct SOrder{
   string OpenTime;
   string Order;
   string Symbol;
   string Type;
   string Volume;
   string Price;
   string SL;
   string TP;
   string Time;
   string State;
   string Comment;
};

Estructura para los datos de una transacción:

struct SDeal{
   string Time;
   string Deal;
   string Symbol;
   string Type;
   string Direction;
   string Volume;
   string Price;
   string Order;
   string Commission;
   string Swap;
   string Profit;
   string Balance;
   string Comment;
};

Estructura para el estado total del depósito:

struct SSummary{
   string Commission;
   string Swap;
   string Profit;
   string Balance;
};

Estructura general:

struct STestingReport{
   SSettings Settings;
   SResults Results;
   SOrder Orders[];
   SDeal Deals[];
   SSummary Summary;
};

Para las órdenes y transacciones se usan las matrices de estructuras SOrder y SDeals.

Rellenando la estructura con datos

Ya solo queda un poco de trabajo rutinario, pero este requiere todavía nuestra atención. Vamos a echar un vistazo al archivo de texto obtenido anteriormente - que contiene los datos del recuadro - y a rellenar la estructura:

aTestingReport.Settings.Expert=tables[0].tr[4].td[1];
aTestingReport.Settings.Symbol=tables[0].tr[5].td[1];
aTestingReport.Settings.Period=tables[0].tr[6].td[1];
aTestingReport.Settings.Inputs=tables[0].tr[7].td[1];

Preste atención a que en la última línea del ejemplo de código mostrado más arriba, se asigna un valor al campo Inputs, pero después de esto, en el campo solo se encontrarán los datos sobre un parámetro. A continuación, se reúnen los demás parámetros, estos se ubican desde la línea 8; la primera celda de cada línea con el parámetro está vacía, por eso el ciclo se ejecuta hasta que la primera celda en la línea esté vacía:

int i=8;
while(i<ArraySize(tables[0].tr) && tables[0].tr[i].td[0]==""){
   aTestingReport.Settings.Inputs=aTestingReport.Settings.Inputs+", "+tables[0].tr[i].td[1];
   i++;
}

Abajo mostramos la función completa de análisis del informe de simulación:

bool TestingHTMLReportToStruct(string aFileName,STestingReport & aTestingReport){

   STable tables[];

   string FileContent;
   
   if(!FileGetContent(aFileName,FileContent)){
      return(true);
   }

   string tags[]={"td","th"};
   string ttmp[],trtmp[],tdtmp[];
   int tcnt,trcnt,tdcnt;
   
   tcnt=TagsToArray(FileContent,"table",ttmp);

   ArrayResize(tables,tcnt);
   
   for(int i=0;i<tcnt;i++){
      trcnt=TagsToArray(ttmp[i],"tr",trtmp);
      ArrayResize(tables[i].tr,trcnt);      
      for(int j=0;j<trcnt;j++){         
         tdcnt=TagsToArray(trtmp[j],tags,tdtmp);
         ArrayResize(tables[i].tr[j].td,tdcnt);
         for(int k=0;k<tdcnt;k++){  
            tables[i].tr[j].td[k]=RemoveTags(tdtmp[k]);
         }
      }
   }   
   
   // settings section
   
   aTestingReport.Settings.Expert=tables[0].tr[4].td[1];
   aTestingReport.Settings.Symbol=tables[0].tr[5].td[1];
   aTestingReport.Settings.Period=tables[0].tr[6].td[1];
   aTestingReport.Settings.Inputs=tables[0].tr[7].td[1];
   int i=8;
   while(i<ArraySize(tables[0].tr) && tables[0].tr[i].td[0]==""){
      aTestingReport.Settings.Inputs=aTestingReport.Settings.Inputs+", "+tables[0].tr[i].td[1];
      i++;
   }
   aTestingReport.Settings.Broker=tables[0].tr[i++].td[1];
   aTestingReport.Settings.Currency=tables[0].tr[i++].td[1];  
   aTestingReport.Settings.InitialDeposit=tables[0].tr[i++].td[1];
   aTestingReport.Settings.Leverage=tables[0].tr[i++].td[1];   
   
   // results section
   
   i+=2;
   aTestingReport.Results.HistoryQuality=tables[0].tr[i++].td[1];
   aTestingReport.Results.Bars=tables[0].tr[i].td[1];
   aTestingReport.Results.Ticks=tables[0].tr[i].td[3];
   aTestingReport.Results.Symbols=tables[0].tr[i].td[5];
   i++;
   aTestingReport.Results.TotalNetProfit=tables[0].tr[i].td[1];
   aTestingReport.Results.BalanceDrawdownAbsolute=tables[0].tr[i].td[3];
   aTestingReport.Results.EquityDrawdownAbsolute=tables[0].tr[i].td[5];
   i++;
   aTestingReport.Results.GrossProfit=tables[0].tr[i].td[1];
   aTestingReport.Results.BalanceDrawdownMaximal=tables[0].tr[i].td[3];
   aTestingReport.Results.EquityDrawdownMaximal=tables[0].tr[i].td[5];
   i++;
   aTestingReport.Results.GrossLoss=tables[0].tr[i].td[1];
   aTestingReport.Results.BalanceDrawdownRelative=tables[0].tr[i].td[3];
   aTestingReport.Results.EquityDrawdownRelative=tables[0].tr[i].td[5];
   i+=2;
   aTestingReport.Results.ProfitFactor=tables[0].tr[i].td[1];
   aTestingReport.Results.ExpectedPayoff=tables[0].tr[i].td[3];
   aTestingReport.Results.MarginLevel=tables[0].tr[i].td[5];
   i++;
   aTestingReport.Results.RecoveryFactor=tables[0].tr[i].td[1];
   aTestingReport.Results.SharpeRatio=tables[0].tr[i].td[3];
   aTestingReport.Results.ZScore=tables[0].tr[i].td[5];
   i++;
   aTestingReport.Results.AHPR=tables[0].tr[i].td[1];
   aTestingReport.Results.LRCorrelation=tables[0].tr[i].td[3];
   aTestingReport.Results.tables[0].tr[i].td[5];
   i++;
   aTestingReport.Results.GHPR=tables[0].tr[i].td[1];
   aTestingReport.Results.LRStandardError=tables[0].tr[i].td[3];
   i+=2;
   aTestingReport.Results.TotalTrades=tables[0].tr[i].td[1];
   aTestingReport.Results.ShortTrades_won_pers=tables[0].tr[i].td[3];
   aTestingReport.Results.LongTrades_won_perc=tables[0].tr[i].td[5];
   i++;
   aTestingReport.Results.TotalDeals=tables[0].tr[i].td[1];
   aTestingReport.Results.ProfitTrades_perc_of_total=tables[0].tr[i].td[3];
   aTestingReport.Results.LossTrades_perc_of_total=tables[0].tr[i].td[5];
   i++;
   aTestingReport.Results.LargestProfitTrade=tables[0].tr[i].td[2];
   aTestingReport.Results.LargestLossTrade=tables[0].tr[i].td[4];
   i++;
   aTestingReport.Results.AverageProfitTrade=tables[0].tr[i].td[2];
   aTestingReport.Results.AverageLossTrade=tables[0].tr[i].td[4];
   i++;
   aTestingReport.Results.MaximumConsecutiveWins_cur=tables[0].tr[i].td[2];
   aTestingReport.Results.MaximumConsecutiveLosses_cur=tables[0].tr[i].td[4];
   i++;
   aTestingReport.Results.MaximalConsecutiveProfit_count=tables[0].tr[i].td[2];
   aTestingReport.Results.MaximalConsecutiveLoss_count=tables[0].tr[i].td[4];
   i++;
   aTestingReport.Results.AverageConsecutiveWins=tables[0].tr[i].td[2];
   aTestingReport.Results.AverageConsecutiveLosses=tables[0].tr[i].td[4];    
   i+=6;
   aTestingReport.Results.Correlation_Profits_MFE=tables[0].tr[i].td[1];
   aTestingReport.Results.Correlation_Profits_MAE=tables[0].tr[i].td[3];
   aTestingReport.Results.Correlation_MFE_MAE=tables[0].tr[i].td[5];    
   i+=3;
   aTestingReport.Results.MinimalPositionHoldingTime=tables[0].tr[i].td[1];
   aTestingReport.Results.MaximalPositionHoldingTime=tables[0].tr[i].td[3];
   aTestingReport.Results.AveragePositionHoldingTime=tables[0].tr[i].td[5];   
   
   // orders

   ArrayFree(aTestingReport.Orders);
   int ocnt=0;
   for(i=3;i<ArraySize(tables[1].tr);i++){
      if(ArraySize(tables[1].tr[i].td)==1){
         break;
      }   
      ArrayResize(aTestingReport.Orders,ocnt+1);
      aTestingReport.Orders[ocnt].OpenTime=tables[1].tr[i].td[0];
      aTestingReport.Orders[ocnt].Order=tables[1].tr[i].td[1];
      aTestingReport.Orders[ocnt].Symbol=tables[1].tr[i].td[2];
      aTestingReport.Orders[ocnt].Type=tables[1].tr[i].td[3];
      aTestingReport.Orders[ocnt].Volume=tables[1].tr[i].td[4];
      aTestingReport.Orders[ocnt].Price=tables[1].tr[i].td[5];
      aTestingReport.Orders[ocnt].SL=tables[1].tr[i].td[6];
      aTestingReport.Orders[ocnt].TP=tables[1].tr[i].td[7];
      aTestingReport.Orders[ocnt].Time=tables[1].tr[i].td[8];
      aTestingReport.Orders[ocnt].State=tables[1].tr[i].td[9];
      aTestingReport.Orders[ocnt].Comment=tables[1].tr[i].td[10];      
      ocnt++;
   }
   
   // deals
   
   i+=3;
   ArrayFree(aTestingReport.Deals);
   int dcnt=0;
   for(;i<ArraySize(tables[1].tr);i++){
      if(ArraySize(tables[1].tr[i].td)!=13){
         if(ArraySize(tables[1].tr[i].td)==6){
            aTestingReport.Summary.Commission=tables[1].tr[i].td[1];
            aTestingReport.Summary.Swap=tables[1].tr[i].td[2];
            aTestingReport.Summary.Profit=tables[1].tr[i].td[3];
            aTestingReport.Summary.Balance=tables[1].tr[i].td[4];            
         }
         break;
      }   
      ArrayResize(aTestingReport.Deals,dcnt+1);   
      aTestingReport.Deals[dcnt].Time=tables[1].tr[i].td[0];
      aTestingReport.Deals[dcnt].Deal=tables[1].tr[i].td[1];
      aTestingReport.Deals[dcnt].Symbol=tables[1].tr[i].td[2];
      aTestingReport.Deals[dcnt].Type=tables[1].tr[i].td[3];
      aTestingReport.Deals[dcnt].Direction=tables[1].tr[i].td[4];
      aTestingReport.Deals[dcnt].Volume=tables[1].tr[i].td[5];
      aTestingReport.Deals[dcnt].Price=tables[1].tr[i].td[6];
      aTestingReport.Deals[dcnt].Order=tables[1].tr[i].td[7];
      aTestingReport.Deals[dcnt].Commission=tables[1].tr[i].td[8];
      aTestingReport.Deals[dcnt].Swap=tables[1].tr[i].td[9];
      aTestingReport.Deals[dcnt].Profit=tables[1].tr[i].td[10];
      aTestingReport.Deals[dcnt].Balance=tables[1].tr[i].td[11];
      aTestingReport.Deals[dcnt].Comment=tables[1].tr[i].td[12];
      dcnt++;
   }
   return(true);
}

A la función se transmite el nombre del archivo y mediante un enlace la estructura STestingReport, que se rellenará en la función.

Preste atención a los segmentos de código que comienzan con los comentarios Orders y Deals. El número de línea con el comienzo de la lista de órdenes se determina de forma inequívoca, pero el final de la lista de órdenes se determina según la línea con una celda:

if(ArraySize(tables[1].tr[i].td)==1){
   break;
}  

Tras las órdenes, se omiten tres líneas:

// deals
   
i+=3;

A continuación, se recopilan los datos sobre las transacciones. El final de la lista de transacciones se determina según la línea con un número de celdas igual a 6, esta misma línea contiene los datos sobre el estado total del depósito. Antes de salir del ciclo, se rellenan las estructuras del estado total del depósito:

if(ArraySize(tables[1].tr[i].td)!=13){
   if(ArraySize(tables[1].tr[i].td)==6){
      aTestingReport.Summary.Commission=tables[1].tr[i].td[1];
      aTestingReport.Summary.Swap=tables[1].tr[i].td[2];
      aTestingReport.Summary.Profit=tables[1].tr[i].td[3];
      aTestingReport.Summary.Balance=tables[1].tr[i].td[4];            
   }
   break;
} 

La estructura con los datos del informe está completamente lista.

Informe de la historia comercial

El análisis de la historia comercial casi no se diferencia del análisis del informe del simulador, excepto algunas diferencias en la estructura final de los datos y las estructuras que la componen:

struct SHistory{
   SHistoryInfo Info;
   SOrder Orders[];
   SDeal Deals[];
   SSummary Summary;  
   SDeposit Deposit;
   SHistoryResults Results;
};

En la estructura SHistory entran las siguientes estructuras: SHistoryInfo — información general de la cuenta, matrices de las estructuras con los datos sobre las órdenes y transacciones, SSummary — resultados comerciales, SDeposit — estado total del depósito, SHistoryResults — estructura con los valores generales (beneficio, número de transacciones, reducción, etc.).

El código completo de la función para el análisis de los informes comerciales se puede encontrar en los anexos, el nombre de la función es HistoryHTMLReportToStruct(). A la función se transmiten dos parámetros:

  • string FileName — nombre del archivo con la función;
  • SHistory History — estructura de datos a rellenar en el informe comercial.

Informe de optimización

La primera diferencia de funcionamiento con respecto al informe de optimización es el tipo de archivo; el archivo está guardado en un archivo ANSI, por eso, para leer su contenido, se usa otra función:

bool FileGetContentAnsi(string aFileName,string & aContent){
   int h=FileOpen(aFileName,FILE_READ|FILE_TXT|FILE_ANSI);
   if(h==-1)return(false);
   aContent="";
   while(!FileIsEnding(h)){
      aContent=aContent+FileReadString(h);
   }
   FileClose(h);
   return(true);
}

La segunda diferencia reside en los tags. En lugar de los tags <table>, <tr> y <td>, se usan los tags <Table>, <Row> y <Cell>. La última y principal diferencia es la estructura de los datos:

struct SOptimization{
   string ParameterName[];
   SPass Pass[];
};

En la estructura entra una matriz de línea con los nombres de los parámetros optimizados (la columna del extremo derecho del informe) y la matriz de estructuras 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[];
};

En la estructura entran los mismos campos que se ubican en las columnas del informe. El último campo es una matriz de línea, en ella se encuentran los valores de los parámetros optimizados, de acuerdo con sus deonominaciones en la matriz ParameterName().

El código completo de la función para el análisis de los informes de optimización se puede encontrar en los anexos: el nombre de la función es OptimizerXMLReportToStruct(). A la función se le transmiten dos parámetros:

  • string FileName — nombre del archivo con la función;
  • SOptimization Optimization — estructura de datos a rellenar de la optimización.

Funciones auxiliares

Si colocamos todas las estructuras y funciones creadas en este artículo en un archivo de inclusión, el trabajo completo de análisis de informes constará de tres líneas de código. 

1. Incluir el archivo:

#include <HTMLReport.mqh>

2. Actualizar la estructura:

SHistory History;

3. Llamar la función:

HistoryHTMLReportToStruct("ReportHistory-555849.html",History);

Después de ello, todos los datos del informe se ubicarán de acuerdo con los campos de estructura correspondientes, exactamente de la misma forma que en el informe. Aunque todos los campos tienen tipo de línea, su uso para los cálculos no supondrá dificultad, solo habrá que transformar las líneas en cifras. Sin embargo, en algunos campos del informe, y por consiguiente de la estructura, se encuentran valores dobles, por ejemplo, los datos sobre el volumen de la orden se representan con dos valores del tipo "0.1 / 0.1", donde el primer valor es el volumen en la solicitud, y el segundo, el volumen ejecutado. Asimismo, algunos valores finales son de datos dobles, un valor principal y otro adicional entre paréntesis, por ejemplo, los datos sobre el número de transacciones: "11 (54.55%)", se indica el número real y entre paréntesis el número relativo en tanto por ciento. Por eso, escribiremos varias funciones auxiliares para facilitar el uso de estos valores.

Funciones para extraer valores de volumen aparte:

string Volume1(string aStr){
   int p=StringFind(aStr,"/",0);
   if(p!=-1){
      aStr=StringSubstr(aStr,0,p);
   }
   StringTrimLeft(aStr);
   StringTrimRight(aStr);
   return(aStr);
}

string Volume2(string aStr){
   int p=StringFind(aStr,"/",0);
   if(p!=-1){
      aStr=StringSubstr(aStr,p+1);
   }
   StringTrimLeft(aStr);
   StringTrimRight(aStr);
   return(aStr);
}

La función Volume1() extrae el primer valor del volumen de una línea del tipo "0.1 / 0.1", la función Volume2(), extrae el segundo.

Función para extraer los valores de los datos dobles:

string Value1(string aStr){
   int p=StringFind(aStr,"(",0);
   if(p!=-1){
      aStr=StringSubstr(aStr,0,p);
   }
   StringTrimLeft(aStr);
   StringTrimRight(aStr);
   return(aStr);
}

string Value2(string aStr){
   int p=StringFind(aStr,"(",0);
   if(p!=-1){
      aStr=StringSubstr(aStr,p+1);
   }
   StringReplace(aStr,")","");
   StringReplace(aStr,"%","");
   StringTrimLeft(aStr);
   StringTrimRight(aStr);
   return(aStr);
}

La función Value1() extrae el primer valor de la línea del tipo "8.02 (0.08%)", la función Value2(), el segundo, es decir, elimina el paréntesis de cierre y el símbolo de porcentaje, si este existe.

También puede ser necesaria la función para transformar los datos sobre los parámetros de la estructura del informe de simulación. Para convertirlos en una forma más cómoda, necesitaremos la estructura:

struct SInput{
   string Name,
   string Value;
}

El campo Name se usa para el nombre del parámetro, Value, para el valor. El tipo de valor no se conoce de antemano, por eso el campo Value tiene el tipo string.

Función para la transformación de una línea con parámetros en una matriz de estructuras SInputs:

void InputsToStruct(string aStr,SInput & aInputs[]){
   string tmp[];
   string tmp2[];
   StringSplit(aStr,',',tmp);
   int sz=ArraySize(tmp);
   ArrayResize(aInputs,sz);
   for(int i=0;i<sz;i++){
      StringSplit(tmp[i],'=',tmp2);
      StringTrimLeft(tmp2[0]);
      StringTrimRight(tmp2[0]);      
      aInputs[i].Name=tmp2[0];
      if(ArraySize(tmp2)>1){
         StringTrimLeft(tmp2[1]);
         StringTrimRight(tmp2[1]);       
         aInputs[i].Value=tmp2[1];
      }
      else{
         aInputs[i].Value="";
      }
   }
}

La línea con los parámetros se divide en una matriz con el símbolo ",", a continuación, cada elemento de la matriz obtenida se divide en el nombre y el valor con el símbolo "=".

Con ello, todo queda listo para ejecutar el análisis deseado de los datos extraídos y formar nuestros propios informes.

Formando nuestro propio informe

Ahora, una vez que tenemos acceso a los datos del informe, todo depende de lo que tengamos que ver en estos datos. Hay muchas opciones. Según los resultados de las transacciones, podemos calcular algunos valores generales como el ratio de Sharpe, el factor de recuperación, etc. Al formar nuestros informes HTML, obtenemos acceso a todas las posibilidades de HTML, CSS y JavaScript. Usando las capacidades de HTML, podemos, por ejemplo, crear un recuadro común de órdenes y transacciones, o recuadros aparte para las transacciones de compra y venta, etc.

Con la ayuda de CSS, dando a los datos diferentes colores, podemos hacer el informe más visual y expresivo. Usando JavaScript, podemos hacer el informe interactivo, por ejemplo, colocando el ratón en una línea con una transacción, destacar la línea de la orden correspondiente en el recuadro de órdenes, y mucho más. Podemos formar imágenes y añadirlas a nuestro informe HTML o, usando las posibilidades de JavaScript, dibujar directamente en el elemento canvas incluido en html5. En general, todo depende de las necesidades que usted tenga, de su fantasía y su manejo de estas herramientas.

Vamos a intentar crear nuestro propio informe html sobre el comercio. Combinaremos en un recuadro órdenes y transacciones. Las órdenes pendientes canceladas se colorearán de gris, puesto que no suponen interés. También de gris se colorearán las órdenes de mercado, puesto que estas duplican significativamente la información sobre las transacciones colocadas tras ellas. Las transacciones tendrán colores llamativos: azul o rojo. Las órdenes pendientes ejecutadas también deben tener colores llamativos, por ejemplo, rosa o celeste. 

El recuadro del informe generado contendrá los encabezados del recuadro de órdenes y del recuadro de transacciones, vamos a preparar una matriz con ellos:

   string TableHeader[]={  "Time",
                           "Order",
                           "Deal",
                           "Symbol",
                           "Type",
                           "Direction",
                           "Volume",
                           "Price",
                           "Order",
                           "S/L",
                           "T/P",
                           "Time",
                           "State",
                           "Commission",
                           "Swap",
                           "Profit",
                           "Balance",
                           "Comment"};

Obteniendo la estructura con los datos del informe:

SHistory History;
HistoryHTMLReportToStruct("ReportHistory-555849.html",History);

Abrimos el archivo para el informe a crear y anotamos en el mismo el inicio de la página HTML estándar, formado por la función HTMLStart():

   int h=FileOpen("Report.htm",FILE_WRITE);
   if(h==-1)return;

   FileWriteString(h,HTMLStart("Report"));
   FileWriteString(h,"<table>\n");
   
   FileWriteString(h,"\t<tr>\n");   
   for(int i=0;i<ArraySize(TableHeader);i++){
      FileWriteString(h,"\t\t<th>"+TableHeader[i]+"</th>\n"); 
   }
   FileWriteString(h,"\t</tr>\n");     

Abajo se muestra el código de la función HTMLStart():

string HTMLStart(string aTitle,string aCSSFile="style.css"){
   string str="<!DOCTYPE html>\n";
   str=str+"<html>\n";
   str=str+"<head>\n";
   str=str+"<link href=\""+aCSSFile+"\" rel=\"stylesheet\" type=\"text/css\">\n";
   str=str+"<title>"+aTitle+"</title>\n";
   str=str+"</head>\n";  
   str=str+"<body>\n";     
   return str;
}

A la función se transmite la línea con el encabezado de la página para el tag title y el nombre del archivo con los estilos.

Iterando en el ciclo por todas las órdenes, definimos el estilo de representación de la línea y lo formamos:

int j=0;
for(int i=0;i<ArraySize(History.Orders);i++){
   
   string sc="";
   
   if(History.Orders[i].State=="canceled"){
      sc="PendingCancelled";
   }
   else if(History.Orders[i].State=="filled"){
      if(History.Orders[i].Type=="buy"){
         sc="OrderMarketBuy";
      }
      else if(History.Orders[i].Type=="sell"){
         sc="OrderMarketSell";
      }
      if(History.Orders[i].Type=="buy limit" || History.Orders[i].Type=="buy stop"){
         sc="OrderPendingBuy";
      }
      else if(History.Orders[i].Type=="sell limit" || History.Orders[i].Type=="sell stop"){
         sc="OrderMarketSell";
      }         
   }

   FileWriteString(h,"\t<tr class=\""+sc+"\">\n");   
   FileWriteString(h,"\t\t<td>"+History.Orders[i].OpenTime+"</td>\n");  // Time
   FileWriteString(h,"\t\t<td>"+History.Orders[i].Order+"</td>\n");     // Order 
   FileWriteString(h,"\t\t<td>"+"&nbsp;"+"</td>\n");                    // Deal 
   FileWriteString(h,"\t\t<td>"+History.Orders[i].Symbol+"</td>\n");    // Symbol 
   FileWriteString(h,"\t\t<td>"+History.Orders[i].Type+"</td>\n");      // Type 
   FileWriteString(h,"\t\t<td>"+"&nbsp;"+"</td>\n");                    // Direction       
   FileWriteString(h,"\t\t<td>"+History.Orders[i].Volume+"</td>\n");    // Volume    
   FileWriteString(h,"\t\t<td>"+History.Orders[i].Price+"</td>\n");     // Price  
   FileWriteString(h,"\t\t<td>"+"&nbsp;"+"</td>\n");                    // Order        
   FileWriteString(h,"\t\t<td>"+History.Orders[i].SL+"</td>\n");        // SL
   FileWriteString(h,"\t\t<td>"+History.Orders[i].TP+"</td>\n");        // TP
   FileWriteString(h,"\t\t<td>"+History.Orders[i].Time+"</td>\n");      // Time    
   FileWriteString(h,"\t\t<td>"+History.Orders[i].State+"</td>\n");     // State
   FileWriteString(h,"\t\t<td>"+"&nbsp;"+"</td>\n");                    // Comission
   FileWriteString(h,"\t\t<td>"+"&nbsp;"+"</td>\n");                    // Swap
   FileWriteString(h,"\t\t<td>"+"&nbsp;"+"</td>\n");                    // Profit     
   FileWriteString(h,"\t\t<td>"+"&nbsp;"+"</td>\n");                    // Ballance    
   FileWriteString(h,"\t\t<td>"+History.Orders[i].Comment+"</td>\n");   // Comment     
   FileWriteString(h,"\t</tr>\n");   
   

Buscamos la transacción para cada orden, si está ejecutada; asimismo, definimos su estilo de representación y formamos su código HTML:  

// encontrar transacción

if(History.Orders[i].State=="filled"){
   for(;j<ArraySize(History.Deals);j++){
      if(History.Deals[j].Order==History.Orders[i].Order){
         
         sc="";
         
         if(History.Deals[j].Type=="buy"){
            sc="DealBuy";
         }
         else if(History.Deals[j].Type=="sell"){
            sc="DealSell";
         }
      
         FileWriteString(h,"\t<tr class=\""+sc+"\">\n");   
         FileWriteString(h,"\t\t<td>"+History.Deals[j].Time+"</td>\n");     // Time
         FileWriteString(h,"\t\t<td>"+"&nbsp;"+"</td>\n");     // Order 
         FileWriteString(h,"\t\t<td>"+History.Deals[j].Deal+"</td>\n");     // Deal 
         FileWriteString(h,"\t\t<td>"+History.Deals[j].Symbol+"</td>\n");     // Symbol 
         FileWriteString(h,"\t\t<td>"+History.Deals[j].Type+"</td>\n");     // Type 
         FileWriteString(h,"\t\t<td>"+History.Deals[j].Direction+"</td>\n");     // Direction       
         FileWriteString(h,"\t\t<td>"+History.Deals[j].Volume+"</td>\n");     // Volume    
         FileWriteString(h,"\t\t<td>"+History.Deals[j].Price+"</td>\n");     // Price  
         FileWriteString(h,"\t\t<td>"+History.Deals[j].Order+"</td>\n");     // Order        
         FileWriteString(h,"\t\t<td>"+"&nbsp;"+"</td>\n");     // SL
         FileWriteString(h,"\t\t<td>"+"&nbsp;"+"</td>\n");     // TP
         FileWriteString(h,"\t\t<td>"+"&nbsp;"+"</td>\n");     // Time    
         FileWriteString(h,"\t\t<td>"+"&nbsp;"+"</td>\n");     // State
         FileWriteString(h,"\t\t<td>"+History.Deals[j].Commission+"</td>\n");                    // Comission
         FileWriteString(h,"\t\t<td>"+History.Deals[j].Swap+"</td>\n");     // Swap
         FileWriteString(h,"\t\t<td>"+History.Deals[j].Profit+"</td>\n");     // Profit     
         FileWriteString(h,"\t\t<td>"+History.Deals[j].Balance+"</td>\n");     // Ballance    
         FileWriteString(h,"\t\t<td>"+History.Deals[j].Comment+"</td>\n");     // Comment     
         FileWriteString(h,"\t</tr>\n"); 
         break;
      }
   }
}

Al final, cerramos el recuadro y añadimos el final estándar de la página HTML, formado por la función HTMLEnd():

FileWriteString(h,"</table>\n");   
FileWriteString(h,HTMLEnd("Report"));  

Código de la función HTMLEnd():

string HTMLEnd(){
   string str="</body>\n";
   str=str+"</html>\n";
   return str;
}

Solo queda escribir el archivo de estilos — style.css. El estudio de css no es el objetivo de este artículo y no se analizará con detalle. Quien así lo desee, puede estudiar el archivo correspondiente, que se encuentra en los anexos al artículo. En los mismos anexos se encuentra el script que crea el informe, su nombre es HTMLReportCreate.mq5.

Este es el aspecto del informe preparado:

Informe HTML propio
Fig. 5. Fragmento del informe HTML propio

Conclusión

Es posible que el lector se haga la pregunta: ¿hasta qué punto sería más sencillo implementar todo esto con la ayuda de expresiones regulares? La estructura y el principio general se quedarían igual. También se ejecutaría de la misma forma la obtención de la matriz con el contenido de los recuadros, líneas y celdas, solo que en lugar de la función  TagsToArray(), se usaría la llamada de la función con la expresión regular: el resto, con bastante probabilidad, sería igual que en el presente artículo.

El ejemplo de formación de un informe propio que hemos visto en este artículo es solo uno entre la multitud de opciones que tenemos para la creación de informes, y se describe solo como ejemplo. Cada cual debe decidir qué es lo que resulta más cómodo y comprensible para él. Lo importante es que ahora disponemos de un acceso muy sencillo a todos los datos de los informes.

Archivos de la aplicación

  • Include/HTMLReport.mqh — archivo de inclusión con las funciones para el análisis de informes.
  • Scripts/HTMLReportTest.mq5 — ejemplo del uso de HTMLReport.mqh para el análisis de los informes de simulación, optimización e historia.
  • Scripts/HTMLReportCreate.mq5 — ejemplo de creación de un informe HTML personalizado.
  • Files/ReportTester-555849.html — informe del simulador.
  • Files/ReportOptimizer-555849.xml — informe del optimizador.
  • Files/ReportHistory-555849.html — informe con la historia.
  • Files/Report.htm — archivo del informe creado con el script HTMLReportCreate.
  • Files/style.css — archivo de estilos para el archivo Report.htm.


Traducción del ruso hecha por MetaQuotes Software Corp.
Artículo original: https://www.mql5.com/ru/articles/5436

Archivos adjuntos |
MQL5.zip (24.2 KB)
Utilidad para la selección y navegación en MQL5 y MQL4: añadiendo las pestañas de "recordatorios" y guardando objetos gráficos Utilidad para la selección y navegación en MQL5 y MQL4: añadiendo las pestañas de "recordatorios" y guardando objetos gráficos

En este artículo, ampliaremos las posibilidades de la utilidad creada anteriormente, añadiéndole pestañas para seleccionar los instrumentos que necesitemos. Asimismo, aprenderemos a guardar los objetos gráficos creados en el gráfico de un instrumento determinado, para no crearlos de nuevo constantemente. E incluso aprenderemos a trabajar solo con los instrumentos que han sido elegidos preliminarmente con la ayuda del sitio web necesario.

Uso práctico de las redes neuronales de Kohonen en el trading algorítmico (Parte I) Instrumental Uso práctico de las redes neuronales de Kohonen en el trading algorítmico (Parte I) Instrumental

El presente artículo desarrolla la idea del uso de redes de Kohonen en MetaTrader 5 que fue abordada en algunas publicaciones anteriores. Las clases corregidas y mejoradas proporcionan el instrumental para solucionar las tareas prácticas.

Aplicación práctica de las correlaciones en el trading Aplicación práctica de las correlaciones en el trading

En este artículo hablaremos sobre el concepto de correlación de magnitudes, y también analizaremos los métodos de cálculo de los coeficientes de correlación y su aplicación práctica en el comercio. La correlación es la interacción estadística de dos o más magnitudes aleatorias (o bien magnitudes que se pueden considerar tales con cierto nivel de precisión permisible). En este caso, además, el cambio de los valores de una o varias de estas magnitudes acompaña al cambio sistemático de los valores de otra u otras magnitudes.

Martingale como base de una estrategia comercial a largo plazo Martingale como base de una estrategia comercial a largo plazo

En este artículo, vamos a analizar con detalle el sistema martingale. Estudiaremos la posibilidad de aplicarlo, y cómo hacerlo de tal forma que reduzcamos los riesgos al mínimo. La principal desventaja de este sencillo sistema es la probabilidad de perder todo el depósito. Y usted debe tener este hecho en cuenta a la hora de comerciar, si es que finalmente decide usar dicho sistema de trading.