El reproductor de trading basado en el historial de las transacciones

Mykola Demko | 4 abril, 2014


Más vale ver una sola vez que oír cien veces

El análisis gráfico del historial de las operaciones es una parte muy importante de la labor analítica de un trader. Si no fuera así, no habría ningún análisis técnico que convierta el mundo de los números a un mundo de imágenes. Esto es obvio, ya que el 80 % de la percepción humana se hace con los ojos. Las estadísticas, que generalizan las informaciones, no pueden transmitir muchos matices. Y solo la visualización con una percepción intuitiva puede poner los puntos sobre las íes. Como dice el refrán, más vale ver una sola vez que oír cien veces

En este artículo, no vamos a ver la forma de escribir un Asesor Experto específico para la automatización de la representación gráfica del historial de las operaciones de trading. Sino que vamos a tratar los temas relacionados con el intercambio de datos entre los objetos, la planificación de aplicaciones a gran escala, la gestión de los gráficos, la sincronización de los datos de distintos símbolos, etc.

Como de costumbre, me gustaría primero comentar las ventajas de la aplicación del reproductor y el script asociado, y luego pasaremos al análisis del código.


Ejecución de transacciones en el probador de estrategias de MetaTrader 5

Se basa el funcionamiento del reproductor en el informe HTML de MetaTrader 5. De este modo se puede obtener el historial del Campeonato de Trading Automatizado 2010 (Automated Trading Championship 2010) iniciando una sesión con la cuenta correspondiente de ATC-2010 y guardando el historial de las operaciones como un informe HTML.

Puesto que el servidor del Campeonato de Trading Automatizado 2008 está desactivado, no podemos proceder del mismo modo. Este sitio contiene el informe general de todos los participantes en un solo archivo zip. Automated_Trading_Championship_2008_All_Trades.zip

Hay que descomprimir el archivo "Automated Trading Championship 2008 All Trades.zip" en la carpeta \Files del directorio de instalación de MetaTrader 5.

Para analizar el historial del Campeonato de Trading Automatizado 2008, tiene que ejecutar el script Report Parser MT4 que analizará el historial, seleccionará la sesión correspondiente y la guardará en un archivo binario. El Asesor Experto Player Report leerá este archivo binario.

Se tiene que ejecutar el Asesor Experto Player Report en el probador de estrategias con la sesión correspondiente. Una vez finalizada la prueba, guardamos el informe en formato HTML. El inicio de la sesión especificada no afecta el resultado de la prueba, pero se mostrará en el informe como el parámetro de entrada "login". Permite distinguir los resultados posteriores. Puesto que los informes se generan mediante el mismo Asesor Experto, se recomienda asignar nombres distintos a los que vienen por defecto.

El script Report Parser MT4 también tiene el parámetro interno "login" en el cual debemos indicar el inicio de sesión del concursante, cuyo historial queremos consultar. Si no conoce el login del participante, pero sí que conoce su nombre de usuario, empiece el script con el valor de login cero (por defecto). En este caso, el script no hará una selección por login; solo creará un archivo csv donde se muestran todos los logins por orden alfabético. El nombre del archivo es "Automated Trading Championship 2008 All Trades_plus". Nada más encontrar el participante requerido en este archivo, ejecute el script una vez más con el login correspondiente.

Por lo tanto, el conjunto del script Report Parser MT4 y el Asesor Experto Player Report generan el informe html estándar del probador de estrategias de MetaTrader 5 a partir del historial de operaciones en el formato MetaTrader 4.

El Asesor Experto Player Report no ejecuta las operaciones exactamente como se habían ejecutado en realidad, lo hace de manera aproximada. Esto se debe a la diferencia de cotizaciones, al redondeo a minutos del tiempo del informe y a las desviaciones durante la ejecución. En la mayoría de los casos, la diferencia es de algunos puntos, ya que ocurre con el 10 % de las operaciones. Pero es suficiente para disminuir el beneficio en el probador de estrategias, por ejemplo, de 170 000 a 160 000. Todo depende del volumen de operaciones con desviaciones.


El funcionamiento del reproductor

Como he mencionado anteriormente, se puede utilizar el reproductor para ver el historial del trading del Campeonato de Trading Automatizado 2008 por medio de aplicaciones adicionales, y se puede ver el Campeonato de Trading Automatizado 2010 directamente.

Además, el reproductor es compatible con todos los informes de MetaTrader 5, de modo que podemos observar el historial de operaciones de cualquier Asesor Experto ejecutándose en el probador de estrategias o el historial del trading manual que no ha sido probado en el probador de estrategias, pero ha sido guardado como un informe a partir de la pestaña "History" (Historial) de la ventana "Toolbox" (Caja de herramientas).

Los parámetros del Asesor Experto Player History Trades exp v5 son:

Se usa el informe del probador de estrategias de MetaTrader 5 Strategy como archivo de entrada para el reproductor del historial de transacciones. Es el nombre del informe que hay que especificar como parámetro de entrada del Asesor Experto Player History Trades exp v5 "name of html file of the strategy tester report" (nombre del archivo HTML del informe del probador de estrategias). Al iniciar el reproductor, el usuario puede indicar el período de la reproducción en las variables de entrada "start of history" (inicio del historial) y "end of history" (final del historial).

Si no se establecen estas variables, las tomará el reproductor a partir del inicio del historial de trading con la primera transacción y del final con la última transacción. La cantidad de símbolos que se usan para el trading no es relevante. Solo se tienen en cuenta los tiempos de la primera y última transacción.

Además, el usuario puede establecer los nombres de los símbolos cuyos gráficos hay que analizar. Se deben especificar los nombres como enumeraciones en la variable "list of required charts" (lista de los gráficos requeridos). El análisis de esta variable no es sensible a las mayúsculas ni al tipo de separador. Si no se establece la variable, se abren todos los símbolos de las operaciones de la cuenta. Y a veces hay muchos.

Por ejemplo, Manov usa 12 pares de divisas en sus operaciones de trading. Recomiendo no usar más de cuatro símbolos al mismo tiempo. Primero, es conveniente ordenarlos y además, demasiados gráficos reducen la velocidad de reproducción. Puesto que cada símbolo se procesa en un bucle general, el incremento del número de símbolos tiende a ralentizar la generación de ticks.

Aunque hayamos especificado un símbolo que no se va a utilizar en las operaciones, el reproductor funcionará también. En este caso, el gráfico no mostrará ninguna transacción y será igual a los otros gráficos. Además, tendrá el indicador de balance adjunto; sin embargo, solo mostrará el historial de balance general en cualquiera de sus variantes.

He omitido a propósito la descripción del parámetro "Delete chart when deleting the EA" (eliminar los gráficos al eliminar el EA). Tiene que ver con el comportamiento del Asesor Experto, no con su gestión. La cuestión es que el Asesor Experto analiza muchos datos durante su funcionamiento. He decidido que algunos de los datos del Asesor Experto serán muy útiles para el análisis en forma de archivos. El Asesor Experto crea archivos csv que contienen operaciones de trading para cada símbolo y el archivo con balances sincronizados de todos los símbolos, que pueden ser muy útiles a la hora de detectar un símbolo en una cesta multidivisa.

Esta misma variable se usa también para eliminar los gráficos abiertos automáticamente por el Asesor Experto. Por lo general, el Asesor Experto debe limpiar su espacio de trabajo al finalizar su funcionamiento. Pero si el usuario quiere analizar un gráfico de cerca y sin el control del Asesor Experto, deberá iniciar el Asesor Experto con el valor del parámetro "delete charts when deleting the EA" igual a "false".

Los siguientes parámetros no son tan relevantes.

El período del generador establece el parámetro inicial del generador de ticks. Aquí, el término "tick" no se usa en su sentido clásico; se trata de la variación del nivel del precio. En el Asesor Experto, se generan los ticks en función de cuatro puntos de las barras. El parámetro "Period of the generator" (período del generador) configura el estado general del generador. Además, podemos cambiar este parámetro en el reproductor durante su funcionamiento.

¿Por qué no hemos generado todos los períodos comenzando por M1? ¿Por qué necesitamos cambiar el período del generador? El problema es que las barras en los períodos de tiempo mayores contienen un lote de barras M1, de modo que puede que tengamos que aumentar la velocidad para acelerar el proceso de generación. Es por ello que se ha implementado la posibilidad de cambiar el período. Solo se implementan algunos de los períodos de tiempo en el generador, no todos. Más adelante, describiré de qué manera podemos cambiarlos en el código.

El parámetro "font of the comments to deals" (fuente de los comentarios de las transacciones) puede resultar útil, por ejemplo, cuando los comentarios impiden la visualización de las propias transacciones. Si le asigna el tamaño "1", el comentario se mostrará con una línea muy fina y no obstaculizará la visualización. En este comentario podrá ver el volumen de la transacción y la posición en la pestaña "List of objects" (lista de objetos) y podrá averiguar el nombre del objeto a partir de la herramienta.

Se dibuja el historial del trading con transacciones independientes, pero la línea que se dibuja depende del tipo de posición.

Mediante "color of buy operations" y "color of sell operations" (color de las operaciones de compra y venta) puede establecer los colores que quiera.

Representación gráfico del historial del trading

En la captura de pantalla anterior, puede apreciar que a menudo el nivel de la posición es distinto del nivel de la transacción.

Pero el beneficio se calcula en función del nivel de la posición. Así que he decidido mostrar la posición con la línea de tendencia y conectar los niveles de la posición y la transacción mediante una línea vertical. El comentario al lado del nivel de la posición muestra la siguiente información:

[deal volume|position volume]

Si el tipo de posición no corresponde al tipo de transacción (por ejemplo, hay un cierre parcial), se mostrarán los volúmenes con unos signos adicionales:

[<deal volume>|position volume]

Primero, se puede ver el volumen de la transacción tal y como se muestra en el informe de las operaciones de trading; se calcula el volumen de la posición en función del estado anterior de la posición y los cambios resultantes de la transacción.

El parámetro "number of speeds" (número de velocidades) regula el número de pasos para bajar la velocidad de reproducción. El reproductor se inicia con la velocidad máxima. Asimismo, se puede subir y bajar la velocidad con el valor del parámetro "number of speeds". De modo que el botón y el período del generador componen una serie completa de herramientas para controlar la velocidad de reproducción del historial de operaciones.

Y por último, tenemos el parámetro "vert. size of the progress button" (tamaño vertical del botón de la barra de progreso). Lo hice para los usuarios que prefieren botones grandes. En general, mi objetivo era evitar que los controles oculten el gráfico. Es por ello que se asigna el valor 8 al parámetro "vert. size of the progress button".

Pasemos ahora a los controles del reproductor.

Controles del reproductor

Se controla la velocidad mediante las flechas derecha e izquierda. El modo de control depende del estado del botón del medio (cuadrado). En el estado "sin pulsar" cambia la velocidad, en el estado "pulsado" cambia el período del generador.

El objeto de control del indicador del balance está representado por una forma oval, pero que en realidad son dos botones grandes que superan de sobra los límites de su tamaño visible. El botón izquierdo se usa para añadir y eliminar el indicador de balance del gráfico, y el botón derecho controla el contenido de los datos.

En el estado "All" (todo), se muestra la información acerca del balance total de la cuenta; el estado "Sum" (resumen) se utiliza para mostrar una muestra del balance del símbolo del gráfico en el cual se está ejecutando el indicador. El control del indicador es asíncrono, lo que quiere decir que solo se puede ejecutar el indicador en un gráfico a la vez. 

El indicador del balance

El objeto de control del indicador de balance es la única excepción en la sincronización de los gráficos; todos los demás objetos de control están sincronizados. En otras palabras, los cambios aplicados a un símbolo se aplican automáticamente a los otros símbolos.

El botón plays/stop (reproducción/pausa) muestra qué operación se va a llevar a cabo nada más pulsarlo. Durante la reproducción, se muestran dos pequeñas líneas que indican que al pulsar el botón, el funcionamiento se pone en pausa. Y viceversa, se muestra un triangulo en el estado de pausa. De modo que si se pulsa, empieza a funcionar el reproductor.

La línea de progreso se compone de 100 botones de control en forma de disparadores (triggers); si se pulsa un botón, todos los demás botones se desactivan (dejan de estar pulsados). Puesto que hay 100 botones, se divide el período de reproducción en 100 partes. Si no se puede dividir el número de barras entre 100, se añade el resto a la última parte. Es por ello que los ajustes incluyen los parámetros "start of history" y "end of history" (inicio y final del historial). Cambiando estos parámetros, se puede desplazar al período deseado del historial.

Pulsando el botón, el usuario puede cambiar la fecha del generador interno de ticks y navegar a la barra cero. Si no está pulsado pero el tiempo del generador interno ya se ha movido fuera del botón activo, el reproductor llevará a cabo por sí mismo la conmutación correspondiente.

Por lo tanto, el objeto "línea de progreso" es a la vez el indicador de progreso y el control activo de la navegación. Se ocultan los controles del reproductor automáticamente y se expanden hasta el centro del gráfico; de modo que si tiene que pulsar algún botón de la línea de progreso, maximice el gráfico a pantalla completa.

Vamos a comentar ahora el comportamiento de los gráficos que gestiona el reproductor. El reproductor lleva a cabo la sincronización de todos los gráficos, pero sin llevar a cabo ningún cambio de escala, de tipo de gráfico, de esquema de color, de desplazamiento a la barra cero, etc. del gráfico principal a los demás gráficos.

Los cambios incluyen un cambio del período de tiempo. En este sentido, hay que señalar que el reproductor considera como gráfico principal aquel donde se muestran los controles, no el que tiene la línea de actividad azul. Por lo general, es el mismo gráfico, pero no es siempre así. Para activar el gráfico, haga clic en el área del gráfico.

El uso del reproductor tiene una característica. Si dos objetos están en el mismo campo, los botones dejan de funcionar. Es por eso que a veces cuando la línea cruza el área del reproductor, hay que cambiar a otro gráfico o cambiar la escala vertical del gráfico para poder pulsar un botón.


En el vídeo se muestra la reproducción del trading de Manov, uno de los participantes en ATC 2010. Para hacerlo, me he conectado a su cuenta en el terminal de cliente mediante los parámetros login=630165 y password=MetaTrader. Se ha guardado el informe de trading con el nombre ReportHistory-630165.html en la carpeta terminal_data_folder\MQL5\Files. Puede descargarlo como un archivo comprimido y extraerlo en la carpeta correspondiente.


Antes de empezar

  1. Para que todo funcione correctamente, descargue player_history_trades.zip y descomprímalo en la carpeta terminal_data_folder/MQL5/Indicators.
  2. Abra la carpeta Player History Trades que ha copiado y compile cuatro archivos en su repertorio raíz en MetaEditor. El orden de compilación de los archivos no importa.
  3. Asegúrese de que el período del historial de todos los símbolos en el informe de trading está disponible en el período de tiempo М1. Para ello, abra manualmente el gráfico necesario con el período de tiempo M1, coloque una línea vertical y abra la lista de objetos mediante la combinación Ctrl+B del menú contextual. Luego, cambie la fecha de la línea vertical a la fecha de inicio de las operaciones de trading.
  4. A continuación, pulse el botón "Show" (mostrar). Si no hay ninguna cotización, se puede deber a dos razones. O bien no se han descargado, o el parámetro "Max bars in chart" (número máximo de barras en el gráfico) es demasiado pequeño. Para verlo, hay que ir a Herramientas->Opciones->Gráficos.

Ahora todo debería funcionar correctamente.


Primeros pasos del desarrollo

Para desarrollar una aplicación, debe tener un plan que se convierte más adelante en un diagrama de bloques al estudiarlo y luego se convierte en código. Pero el proyecto en sí, comienza antes. El punto de partida de cualquier proyecto son las propiedades de la aplicación requeridas por el usuario. Entonces, ¿cuáles son las propiedades que debe tener el historial del trading? 

  1. Soportar el modo multidivisa.
  2. Apertura automática de los gráficos requeridos.
  3. Una Interfaz de navegación fácil y la posibilidad de desplazar el historial en ambas direcciones.
  4. Visualización simultánea de todos los gráficos.
  5. Iniciar/Pausar la reproducción.
  6. La posibilidad de elegir (y el modo por defecto) la cantidad y los símbolos de a mostrar en el gráfico.
  7. La posibilidad de elegir (y el modo por defecto) el período de funcionamiento del reproductor.
  8. Representación del historial de transacciones en un gráfico.
  9. Representación del historial de balance y de patrimonio.
  10. Representación por separado del balance (patrimonio) de un símbolo el balance (patrimonio) total de una cuenta.

Los cuatro primero elementos definen el concepto general. Las otras propiedades determinan la dirección de la implementación de los métodos.

El plan general del funcionamiento del reproductor:

  1. Cargar el informe HTML;
  2. Analizar las transacciones y recuperar el historial de las posiciones;
  3. Preparar las transacciones como una cola de órdenes de apertura/cierre;
  4. Cuando el usuario da la orden, iniciar la representación de la dinámica del historial de transacciones con el cálculo de los tipos necesarios en forma de indicadores (gráficos de patrimonio, retiradas, etc.);
  5. Organizar la visualización de un panel de informaciones en el gráfico con los otros tipos.

Además, se requiere un Asesor Experto especial para operar en el probador de estrategias en función del informe de MetaTrader 4:

  1. Se deben escribir las transacciones analizadas en forma de un archivo de datos binarios para el Asesor Experto.
  2. Crear el informe del probador de estrategias de MetaTrader 5.

Este es el esquema general para iniciar el desarrollo, se trata de una especificación de requisitos. Si lo tiene, puede planificar la escritura del código desde arriba hacia abajo, desde el concepto hasta la implementación de la funcionalidad.

Sin querer extenderme en el artículo, voy a describir más adelante las partes más significativas del código. No encontrará ningún problema durante la lectura del código, ya que está bien comentado.


Órdenes y transacciones

Actualmente, hay dos conceptos de trading. El antiguo concepto que usa MetaTrader 4 y el nuevo que se usa en las ofertas reales y en MetaTrader 5, y que se llama el concepto de "compensación (netting). En el artículo Órdenes, Posiciones y Transacciones en MetaTrader 5 se describen las diferencias que hay en detalle.

Solo voy a describir las diferencias relevantes. En MetaTrader 4, se puede representar una orden como un contenedor que almacena las informaciones acerca del tiempo de apertura, del precio de apertura y el volumen de la operación. Y mientras esté abierta la puerta, está en el estado de operación activa. Nada más se cierra la puerta, se mueven todos los datos al historial.

En MetaTrader 5, se usan las posiciones como contenedores. Pero la diferencia más importante, es la ausencia del historial de posiciones. Solo está el historial general de órdenes y transacciones. Aunque el historial contenga todos los datos necesarios para recuperar el historial de posiciones, tiene que dedicar algún tiempo a la reorganización de los conceptos.

Puede averiguar a qué posición pertenece la orden o transacción seleccionada mediante los identificadores ORDER_POSITION_ID y DEAL_POSITION_ID respectivamente. Por tanto, para convertir el historial a un formato compatible con MetaTrader 5, voy a dividir el historial de las órdenes de MetaTrader 4 en dos operaciones de trading separadas; operaciones de apertura y de cierre.


El analizador sintáctico HTML

Para aquellos que no están familiarizados con la jerga informática, voy a describir el significado del término "análisis sintáctico" (parse en inglés). Se trata del análisis sintáctico (gramatical y léxico) de un texto o cualquier secuencia de lexemas (símbolos, palabras, bytes, etc.), que comprueba si el texto de entrada corresponde a la gramática especificad y constituye un árbol de análisis, en función del cual podemos llevar a cabo los cálculos y transformaciones posteriores.

El analizador usa las dos categorías de clases CTable y CHTML. Se describe en detalle el uso de la clase CTable en el artículo Tablas electrónicas en MQL5, por lo tanto no la voy a describir de nuevo.

Para analizar contenidos HTML, he desarrollado la clase CHTML. En mi opinión, ya se habrá escrito un artículo describiéndola. Pero la clase es demasiado sencilla para que se escriba un artículo, con lo cual, voy a proporcionar un descripción concisa de la misma.

Se puede describir el concepto general de la clase mediante el término "tag" (etiqueta). Se puede representar una etiqueta como una función delimitada. Por ejemplo, Tag(header,casket), donde "header" es el título de la etiqueta (en general, se especifican aquí las variables de la etiqueta que controla el aspecto de la página) y "casket" es el contenido del contenedor de la etiqueta. Todo el lenguaje HTML se compone de estas etiquetas.

Se puede representar la estructura general de la clase como una llamada de objetos en tres etapas. La instancia de la clase CHTML crea en su cuerpo los objetos de todas las etiquetas posibles. Las funciones de las etiquetas se crean mediante una plantilla, y se diferencian entre sí por sus nombres y por la configuración de las dos etiquetas.

Una etiqueta determina la presencia de "header" y la otra determina la presencia de "casket". Estas dos etiquetas permiten expresar todas las etiquetas mediante una estructura común. Cada instancia de etiqueta crea una instancia de la clase Ctegs en su cuerpo. Esta clase contiene los métodos comunes para todas las etiquetas, y lleva a cabo las operaciones principales para buscar la etiqueta necesaria en el cuerpo del documento.

Por lo tanto, la llamada de tres etapas es la siguiente:

h.td.base.casket

Esta expresión significa que el objeto "h" llama al valor de la variable "casket" mediante el objeto "td" (que es una instancia de la etiqueta <td header >casket</td>) a través del objeto anidad "base" (que es un objeto de la clase CTegs).

La clase incluye también métodos de búsqueda de etiquetas, que se combinan en el método público:

h.td.Search(text,start);

que devuelve el punto de búsqueda del final de la etiqueta y rellena las variables "header" y "casket de la misma.

Las otras funciones de la clase no se usan, así que no las voy a describir, hay muchas cosas interesantes que comentar.

Al final de esta descripción de cómo trabajar con documentos HTML, quiero mencionar que se han utilizado dos analizadores en este artículo; solo se diferencian por el tipo de almacenamiento de los datos obtenidos a partir del archivo. El primer tipo almacena todo el documento en una sola variable de tipo "string", que se usa en el reproductor. El segundo tipo analiza el informe línea por línea. Se usa en el script de preparación del historial del Campeonato 2008.

¿Por qué he usado dos métodos? El tema está en que para un funcionamiento correcto de las funciones de la clase CTegs, se debe colocar la etiqueta entera en la cadena analizada. Y esto no es siempre así. Por ejemplo, en el caso de etiquetas como table, html, body (son de varias líneas). Una variable de tipo "string" permite almacenar (según mis cálculos) 32 750 símbolos sin los símbolos de tabulación. Y con "\r" (después de cada símbolo 32 748) he conseguido guardar con éxito hasta 2 000 000 de símbolos; después de alcanzar este valor, me paré. Es muy probable que se puedan almacenar más símbolos.

Entonces, ¿por qué hemos usado dos métodos? Lo que pasa es que para un analizador genérico, es necesario encontrar la tabla apropiada. Las tablas necesarias para el informe del probador y para el informe del historial de transacciones se encuentran en distintos sitios. Para mantener la flexibilidad (para que el analizador entienda ambos informes), he usado el esquema de búsqueda de la tabla con la etiqueta "td" que contiene "deals".

La estructura del informe del Campeonato 2008 es conocida y no hace falta buscar la tabla necesaria. Sin embargo, el tamaño del documento del informe es muy grande (35 MB), y la colocación de todo el informe en una sola variable llevaría mucho tiempo. Esta situación requiere el uso del segundo método de análisis.


El reproductor

Se describen los 10 requisitos del reproductor en la sección "Primeros pasos del desarrollo". Puesto que en primer lugar tenemos la multidivisa, el Asesor Experto debe gestionar los gráficos. Sería lógico si se procesara cada gráfico por un objeto independiente, que disponga de todas las características que requiere el reproductor para funcionar.

Dado que trabajamos con el historial, necesitamos una muestra separada del historial para una operación ininterrumpida, y no esperar a que podamos obtener el historial siempre que queramos. Además, la obtención repetida del mismo historial sería ineficiente en comparación con mantenerlo en el reproductor. Al final, obtenemos el siguiente esquema:

Esquema general del reproductor del historial del trading

La programación orientada a objetos (POO) permite escribir aplicaciones muy grandes mediante unos sistemas de bloques. Se puede escribir previamente la parte desarrollada del código del Asesor Experto en un script, depurado y luego conectado al Asesor Experto con una mínima adaptación.

Este esquema de desarrollo es muy práctico, ya que le garantiza que el código conectado no contiene errores (puesto que funciona en el script sin errores), y cualquier fallo se debe a los errores de adaptación. Esta ventaja no existe cuando se escribe el código de abajo hacia arriba, cuando se describe todo en un solo lugar como un procedimiento. Y puede aparecer un nuevo fallo en cualquier parte de la aplicación.

Por lo tanto, la programación de arriba hacia abajo tiene ventajas en cuanto a la simplicidad y velocidad de escritura de la aplicación. Puede preguntarse, "¿qué hay de sencillo aquí?", me gustaría responder con una alegoría: es difícil aprender a montar en bicicleta, pero una vez aprende, ni siquiera se dará cuenta del proceso. Solo disfrutará de un paseo rápido. Una vez haya aprendido la sintaxis de la programación orientada a objetos, obtendrá una gran ventaja.


Antes de seguir, tengo que describir tres términos de la programación orientada a objetos: asociación, agregación y composición.

Dado que la asociación incluye la agregación y la composición, durante un análisis detallado, se llama asociación a todos los casos que no se pueden describir como agregación o composición. Por lo general, las tres expresiones se llaman asociación.

class Base
  {
public:
                     Base(void){};
                    ~Base(void){};
   int               a;
  };
//+------------------------------------------------------------------+

class A_Association
  {
public:
                     A_Association(void){};
                    ~A_Association(void){};
   void              Association(Base *a_){};
   // At association, data of the bound object 
   // will be available through the object pointer only in the method, 
   // where the pointer is passed.
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class A_Aggregation
  {
   Base             *a;
public:
                     A_Aggregation(void){};
                    ~A_Aggregation(void){};
   void              Aggregation(Base *a_){a=a_;};
   // At aggregation, data of the bound object 
   // will be available through the object pointer in any method of the class.
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class A_Composition
  {
   Base             *a;

public:
                     A_Composition(void){ a=new Base;};
                    ~A_Composition(void){delete a;};
   // At composition, the object becomes the class member.
  };

Existe una función en MQL5 para pasar un puntero por un parámetro: 

GetPointer(pointer)

Su parámetro es el puntero del objeto.

Por ejemplo:

void OnStart()
  {
   Base a; 
   A_Association b;
   b.Association(GetPointer(a));
  }


Las funciones que llama OnInit() en mi código suelen usar a menudo la asociación. Se aplica la composición en la clase CHTML. Estoy utilizando la agregación y la composición juntas para vincular los objetos con la clase CPlayer. Por ejemplo, mediante la agregación, los objetos de las clases CChartData y SBase crean un campo común de datos para todos los objetos creados mediante la composición en el reproductor.

A continuación, tenemos una representación gráfica:

Vinculación de los datos

Las clases cuyos objetos han sido creados mediante la composición en la clase CPlayer, tienen una plantilla de estructura con mayor extensión de sus características. El uso de las plantillas se describe en el artículo El uso de las pseudoplantillas como alternativa a las plantillas de C++, así que no voy a proporcionar más detalles de su descripción.

La estructura de una plantilla de la clase es la siguiente:

//this_is_the_start_point
//+******************************************************************+
class _XXX_
  {
private:
   long              chart_id;
   string            name;
   SBase            *s;
   CChartData       *d;
public:
   bool              state;
                     _XXX_(){state=0;};
                    ~_XXX_(){};
   void              Create(long Chart_id, SBase *base, CChartData *data);
   void              Update();
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void _XXX_::Create(long Chart_id, SBase *base, CChartData *data)
  {
   chart_id=Chart_id;
   s=base; // binding data to the player structure
   d=data; // binding data to the chart structure
   name=" "+ChartSymbol(chart_id);

   if(ObjectFind(chart_id,name)<0)// if there is no object yet
     {//--- try to create the object         
      if(ObjectCreate(chart_id,name,OBJ_TREND,0,0,0,0,0))
        {//---
        }
      else
        {//--- failed to create the object, tell about it
         Print("Failed to create the object"+name+". Error code ",GetLastError());
         ResetLastError();
        }
     }       
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void _XXX_::Update()
  {
  };
//+******************************************************************+
//this_is_the_end_point

De modo que he creado clases vacías mediante la plantilla, las he conectado, he comprobado si procesan todas las peticiones correctamente, y solo después, he empezado a rellenar las clases compuestas con las funcionalidades necesarias. Esto se conoce como la programación de arriba hacia abajo. Si hay algún error, sabemos donde buscar la causa.

Como ya está claro el concepto general de la implementación, podemos proceder ahora con los detalles.

En primer lugar, echemos un vistazo al funcionamiento de las funciones declaradas en el Asesor Experto Player History Trades exp v5.

Se usa la función OnInit() como de costumbre para preparar la información. Crea un objeto de la clase CParse que analiza el informe del probador de estrategias, obtiene las lista de todos los instrumentos financieros usados en el trading, procesa las transacciones, calcula volúmenes y niveles de posiciones y luego dibuja el historial en el gráfico. El último elemento describe la razón por la cual no se ha eliminado el objeto justo después de enviar los datos. En realidad la información se prepara antes de la apertura de los gráficos. Y los objetos gráficos requieren un ID (identificador) de gráfico para dibujar. Este es el motivo por el cual se borra el objeto de la clase CParser_Tester class más tarde.

Además, como ya tenemos los nombres de los símbolos utilizados en el trading, se llama a la función Balance_Process(), que calcula el balance y patrimonio de todos los símbolos que recibe, así como el balance y patrimonio total en base al historial de M1.

En esta parte, la aplicación es muy especialmente sensible a la falta de información; es por ello que he implementado la interrupción de la ejecución del Asesor Experto en el caso de que no se haya descargado la información de algún símbolo. Cuando la aplicación se detiene, muestra un aviso con la información del símbolo que hay que descargar.

El resultado del funcionamiento de la función Balance_Process() son archivos binarios del historial de balance y patrimonio en M1, que se dividen aún más en los períodos necesarios mediante el indicador de balance. Bueno, me estoy adelantando un poco, se describirá el funcionamiento del indicador de balance más adelante.

La siguiente etapa del inicio del Asesor Experto es la selección de los símbolos. En este punto, analizamos el parámetro de entrada "list of required charts" (lista de los gráficos requeridos); si el símbolo requerido está en la lista de "Observación del mercado", se añade a la matriz de los símbolos. De esta forma, nos protegemos de los "despistados", ya que el usuario puede introducir fácilmente cualquier cosa en lugar del nombre del símbolo y lo puede escribir de forma errónea.

Ya que disponemos de la lista verificada de los símbolos que requiere el usuario para la apertura, podemos abrir los gráficos. Esto se hace mediante la función:

ChartOpen(symbol,period)

Esta función abre un gráfico con el símbolo y el período que ha recibido en los parámetros. En teoría, esta función devuelve el identificador ID del gráfico abierto, pero esto no ocurre siempre.

Como resultado de la pérdida de ID, se producen errores de funcionamiento de la aplicación. Para evitarlo, he creado dos funciones:

ChartTotal(arrayID);   // get the list of charts before opening additional charts
CurrentChart(arrayID); // get the list of chart for operation

Se ejecuta una función antes de la apertura del gráfico y la otra se ejecuta después. La función ChartTotal() obtiene la lista de gráficos abiertos antes del inicio de Asesor Experto (incluyendo el gráfico en el cual se ejecuta el EA) y guarda sus ID en una matriz de entrada.

La función CurrentChart() obtiene esta información, crea un nueva lista teniendo en cuenta los gráficos ya abiertos; en función de la diferencia entre las listas, envía los ID de los gráficos creados por el Asesor Experto a la matriz de parámetros. Este procedimiento es fiable, ya que trabaja en función de la apertura de gráficos.

Ahora que ya disponemos de los ID de los gráficos necesarios, podemos controlarlos. Para ello, recorra todos los gráficos en un bucle y mediante el objeto de CParser_Tester (como recordará, comenté antes que nos haría falta) dibuje el historial de las transacciones y cree los objetos para la gestión del gráfico.

La última acción en OnInit() - crea el temporizador y lo llama para funcionar. Todas las demás acciones se llevan a cabo en OnTimer().

El primer problema con la creación del reproductor surge en la etapa inicial del desarrollo. Es el problema de la creación del temporizador. La función EventSetTimer(timer) permite crear un temporizador con una frecuencia inferior a 1 segundo. Con esta variante, se van a generar los ticks una vez por segundo. Aún teniendo en cuenta las limitaciones de la visión humana, un segundo es demasiado tiempo. Necesito por lo menos 100 milisegundos.

Esta es la razón por la cual he implementado un bucle dentro del temporizador; se sale varios milisegundos antes de la llegada de un nuevo evento del temporizador. Pero esta implementación hace que se puedan poner en práctica muchas soluciones técnicas. Por ejemplo, la desaparición de la posibilidad de recibir eventos, ya que están esperando continuamente la salida del temporizador del bucle. Y la imposibilidad de recibir eventos hace desaparecer la posibilidad de colocar los objetos de los reproductores en el indicador y llevar a cabo cálculos paralelos de todos los gráficos al mismo tiempo. Pero incluso con el consiguiente procesamiento de los gráficos, el Asesor Experto funciona bastante rápido.

Se sustituye el evento de activación del gráfico por la clase compuesta CClick, cuyos objetos crean una señal de cambio del gráfico activo que está siendo procesado en el bucle de la función Click(n). La función Click() es un disparador que hace un seguimiento de los cambios del botón de activación del gráfico. Si detecta que el botón está pulsado, pondrá todos los otros objetos en el modo desactivado. El botón de activación del gráfico está siempre cerca del usuario, pero no es visible, porque su tamaño es de todo el gráfico, tiene el mismo color que el fondo y está en el fondo. Cuando se activa un gráfico, se desplaza detrás de los límites visibles del gráfico, lo que permite ver los objetos gráficos del control del reproductor, que están ocultos por el botón de activación del gráfico en el modo pasivo.

Además, como hemos detectado el gráfico principal mediante la función Click(), pasamos al cálculo del movimiento en el tiempo y llamamos a la función Progress(Time) del reproductor activo. Esta función lleva a cabo los siguientes cálculos: comprueba si el usuario ha llevado a cabo acciones de navegación, de los contrario, comprueba si es el momento de pasar a la siguiente barra; de ser así, comprueba si hay que mover el progreso a la siguiente sección.

Al final, cuando salimos de la función Progress(Time), el bucle dispone de los datos correspondientes al tiempo actual, que se usan en los cálculos anteriores. A continuación, se copia la configuración del parámetro actual a los gráficos esclavos. Esto se hace mediante la función CopyPlayer(n). Después de eso, en la función Play(Time) se pasa a la ejecución de todos los cambios que se deben aplicar al gráfico para hacer que el usuario piense que el tiempo se está moviendo, las cotizaciones llegan y el trading se está llevando a cabo.


Clases compuestas del reproductor

  1. CArrayRuler* -  almacena y busca informaciones para un movimiento rápido entre las barras del período de tiempo actual.
  2. CRuler*         -  almacena y busca las informaciones del historial M1 para la generación de ticks.
  3. CSpeed          -  controla los ajustes de seguridad y el período del generador de ticks.
  4. CProgress      -  combina todos los botones de progreso en un solo objeto, marca solo el botón pulsado, cambia los colores de los botones.
  5. CPlay             -  se encarga de iniciar y parar el reproductor, también controla el indicador de balance.
  6. CClick            -  se encarga de las señales de activación del gráfico.
  7. CBackGround  -  el objeto oculta la barra cero a los usuarios, también oculta las barras futuras cuando está activado el desplazamiento del gráfico desde el borde derecho.
  8. CBarDraw      -  dibuja la barra cero en función de la escala y el tipo de gráfico (barras, velas o líneas).
  9. CHistoryDraw -  genera un efecto que da la impresión al usuario que la última transacción ha cambiado en tiempo real.

* - las clases no incluyen objetos gráficos.

Como ya lo he mencionado, los objetos de las clases CCahrtData y SBase crean un campo común de datos para todos los objetos en el reproductor usando la agregación. Se usa el objeto de la clase CChartData para almacenar y actualizar la información relacionada con el gráfico, así como gestionarla. Con la gestión del gráfico nos referimos a cambiar la configuración del mismo mediante la copia de la configuración del gráfico principal. De esta forma se lleva a cabo la sincronización de los gráficos. El usuario solo envía la señal inicial modificando la configuración del gráfico activo, y luego varias funciones del reproductor se encargan del resto de las operaciones de sincronización.

Esto se hace de la siguiente manera:

La función CopyPlayer(n), descrita en el Asesor Experto, llama a la función CPlayer::Copy(CPlayer *base) en un bucle, enviando mediante asociación el puntero al reproductor del gráfico actual. Dentro de CPlayer::Copy(CPlayer *base) y a partir del puntero del reproductor, se envía el puntero del objeto CChartData del reproductor activo mediante asociación. Por lo tanto, se coloca la información relativa al estado del gráfico actual en el objeto de la clase CCartData del gráfico esclavo para la copia. A continuación, se actualiza la función CPlayer::Update(), en la cual se llevan a cabo todas las comprobaciones necesarias y se conmutan todos los objetos a los estados correspondientes.

Como había prometido antes, voy a explicar cómo se pueden añadir períodos a la lista de los períodos disponibles del generador. Para hacerlo, abra el archivo de inclusión "Player5_1.mqh". Se declara la matriz estática TFarray[] al principio del archivo. Hay que añadir el período a su posición correspondiente en la enumeración que rellena la matriz, y no olvide cambiar el tamaño de la matriz y la variable CountTF. A continuación, compile el Asesor Experto Player History Trades exp v5.


Los gráficos del balance y de retirada

El indicador de balance se gestiona desde el objeto de la clase CPlay. Contiene los métodos y botones de control.

Los métodos de control del indicador son los siguientes:

   Ind_Balance_Create();                 // add the indicator
   IndicatorDelete(int ind_total);     // delete the indicator
   EventIndicators(bool &prev_state);   // send an event to the indicator
   StateIndicators();                  // state of the indicator, state checks

Los métodos de añadir y eliminar trabajan en función del estado del botón name_on_balance. Usan las funciones IndicatorCreate() and ChartIndicatorDelete() de MQL5.

El indicador recibe un evento y lleva a cabo los cálculos en la función OnChartEvent() del indicador, en función del código del evento. Se dividen los eventos en tres tipos. 

Son "actualizar el indicador", "calcular el balance total" y "calcular el balance para un símbolo". Por lo tanto, al enviar eventos, dependiendo del estado del botón name_all_balance, el usuario controla el tipo de cálculos. Pero el código del indicador en sí, no contiene ningún análisis del historial de trading, ni del cálculo de posición o del cálculo de beneficio. No le hace falta al indicador.

El indicador de balance se usa para mostrar los datos del historial, así que no tiene sentido volver a hacer todo de nuevo cada vez que se cambie el tipo o se añada / elimine el indicador. El indicador lee el archivo binario de los datos calculados para el período de tiempo M1, y después, en función del período de tiempo actual, divide los datos.

La función Balance_Process(), a la que se llama en OnInit(), se encarga de preparar el archivo binario. Si el usuario añade un símbolo que no ha sido utilizado para el trading y que no exista el archivo binario correspondiente, el indicador mostrará el balance total en ambas variantes.

Vamos a hablar ahora del formato de datos enviados al indicador. Para dividir la información correctamente, no basta con conocer cuatro puntos de la barra (Apertura, Máximo, Mínimo y Cierre).

Además de esto, le hace falta saber cuál es el primero; máximo o mínimo. Para recuperar la información, la función Balance_Process() usa el mismo principio que el modo "1 minuto OHLC" del probador de estrategias; si el precio de cierre de una barra es inferior al precio de apertura, el segundo punto es el máximo, de lo contrario, es el mínimo.

Se usa el mismo principio para el tercer punto. Como resultado, obtenemos el formato de datos (apertura, 2o punto, 3er punto, cierre) en el cual todo está coherente y definido. Se usa este formato para dividir el historial de cotizaciones M1. Y dependiendo del historial de trading analizado, se usa el resultado para el cálculo del historial de balance y patrimonio (en el mismo formato).


Conclusión

Para concluir, me gustaría comentar que esta aplicación no pretende ser un simulador gráfico, aunque se puede usar para ello. Aunque, me alegraré mucho si los conceptos implementados en ella resultasen útiles en un simulador real. El desarrollo del reproductor pretende ayudar a los traders y a los creadores de Asesores Expertos con la preparación del próximo campeonato y con el duro trabajo de análisis de estrategias de trading.

Además, me gustaría decir que el lenguaje MQL5 es una herramienta de programación muy potente que permite implementar proyectos muy grandes. Si todavía está leyendo este artículo, se habrá dado cuenta de que el proyecto del "reproductor" contiene cerca de 8 000 líneas. No puedo ni imaginar escribir semejante código en MQL4, y la dificultad no radica en la descripción de todo ello con procedimientos. Si ya existe algo desarrollado, se puede rehacer en el estilo del procedimiento. Pero desarrollar tales proyectos desde cero es una tarea realmente ardua.

¡Buena suerte!