- Evento principal de Asesores Expertos: OnTick
- Principios y conceptos básicos: orden, transacción y posición
- Tipos de operaciones de trading
- Tipos de órdenes
- Modos de ejecución de órdenes por precio y volumen
- Fechas de vencimiento de órdenes pendientes
- Cálculo del margen para una orden futura: OrderCalcMargin
- Estimación del beneficio de una operación de trading: OrderCalcProfit
- Estructura MqlTradeRequest
- Estructura MqlTradeCheckResult
- Solicitar validación: OrderCheck
- Solicitar resultado del envío: estructura MqlTradeResult
- Enviar una solicitud de trading: OrderSend y OrderSendAsync
- Operaciones de compraventa
- Modificar los niveles de Stop Loss y/o Take Profit de una posición
- Trailing stop
- Cierre de una posición: total y parcial
- Cierre de posiciones opuestas: total y parcial
- Colocar una orden pendiente
- Modificar una orden pendiente
- Borrar una orden pendiente
- Obtener una lista de órdenes activas
- Propiedades de una orden (activas e históricas)
- Funciones para leer las propiedades de órdenes activas
- Seleccionar órdenes por propiedades
- Obtener la lista de posiciones
- Propiedades de posiciones
- Funciones de lectura de propiedades de posición
- Propiedades de transacción
- Seleccionar órdenes y transacciones del historial
- Funciones para leer propiedades de órdenes del historial
- Funciones para leer propiedades de transacciones del historial
- Tipos de transacciones de trading
- Evento OnTradeTransaction
- Peticiones síncronas y asíncronas
- Evento OnTrade
- Seguimiento de los cambios en el entorno de trading
- Crear Asesores Expertos multisímbolo
- Limitaciones y ventajas de los Asesores Expertos
- Crear Asesores Expertos en el Asistente MQL
Crear Asesores Expertos multisímbolo
Hasta ahora, en el marco del libro, hemos analizado principalmente ejemplos de Asesores Expertos que operan en el símbolo de trabajo actual del gráfico. Sin embargo, MQL5 permite generar órdenes de trading para cualquier símbolo de Observación de Mercado, independientemente del símbolo de trabajo del gráfico.
De hecho, muchos de los ejemplos de las secciones anteriores tenían un parámetro de entrada symbol, en el que se puede especificar un símbolo arbitrario. Por defecto, hay una cadena vacía, que se trata como el símbolo actual del gráfico. Así, ya hemos visto los siguientes ejemplos:
- CustomOrderSend.mq5 en Enviar una solicitud de trading
- MarketOrderSend.mq5 en Operaciones de compraventa
- MarketOrderSendMonitor.mq5 en Funciones para leer las propiedades de órdenes activas
- PendingOrderSend.mq5 en Configurar una orden pendiente
- PendingOrderModify.mq5 en Modificar una orden pendiente
- PendingOrderDelete.mq5 en Borrar una orden pendiente
Puede intentar ejecutar estos ejemplos con un símbolo diferente y asegurarse de que las operaciones de trading se realizan exactamente igual que con el nativo.
Además, como vimos en la descripción de OnBookEven y OnTradeTransaction, estos eventos son universales e informan de los cambios en el entorno de trading relativos a símbolos arbitrarios. Pero esto no es cierto para el evento OnTick que sólo se genera cuando se produce un cambio en los nuevos precios del símbolo actual. Normalmente, esto no es un problema, pero el trading multidivisa de alta frecuencia requiere que se tomen algunas medidas técnicas adicionales, como suscribirse a eventos OnBookEvent para otros símbolos o establecer un temporizador de alta frecuencia. Otra opción para eludir esta limitación en forma de indicador espía EventTickSpy.mq5 se presentó en la sección Generación de eventos personalizados.
En el contexto de hablar sobre la compatibilidad del trading multisímbolo, hay que señalar que un concepto similar de Asesores Expertos de marco temporal múltiple no es del todo correcto. Operar en las nuevas horas de apertura de las barras es sólo un caso especial de agrupación de ticks por periodos arbitrarios, no necesariamente estándar. Por supuesto, el análisis de la aparición de una nueva barra en un marco temporal específico se simplifica por el núcleo del sistema debido a funciones como iTime(_Symbol, PERIOD_XX, 0), pero este análisis se basa en ticks de todos modos.
Puede construir barras virtuales dentro de su Asesor Experto por el número de ticks (equivolume), por el rango de precios (renko, rango) y así sucesivamente. En algunos casos, incluso para mayor claridad, tiene sentido generar tales «marcos temporales» explícitamente fuera del Asesor Experto, en forma de símbolos personalizados. Pero este enfoque tiene sus limitaciones: hablaremos de ellas en la siguiente parte del libro.
No obstante, si el sistema de trading sigue requiriendo el análisis de las cotizaciones basada en la apertura de barras o utiliza un indicador multidivisa, habrá que esperar de algún modo a la sincronización de las barras de todos los instrumentos implicados. Proporcionamos un ejemplo de una clase que realiza esta tarea en la sección Seguimiento de formación de barras.
Al desarrollar un Asesor Experto multisímbolo, la tarea imperativa consiste en segregar un algoritmo de trading universal en bloques distintos. Estos bloques pueden aplicarse posteriormente a varios símbolos con distintos ajustes. El enfoque más lógico para lograrlo es articular una o varias clases en el marco del concepto de programación orientada a objetos (POO).
Ilustremos esta metodología con un ejemplo de Asesor Experto que emplea la conocida estrategia de martingala. Como se suele entender, la estrategia de martingala es intrínsecamente arriesgada, dada su práctica de duplicar los lotes después de cada operación perdedora en previsión de recuperar las pérdidas anteriores. Mitigar este riesgo es esencial, y un enfoque eficaz consiste en operar simultáneamente con varios símbolos, preferiblemente aquellos con correlaciones débiles. De este modo, las pérdidas temporales en un instrumento pueden compensarse, en potencia, con ganancias en otros.
La incorporación de una variedad de instrumentos (o diversas configuraciones dentro de un único sistema de trading, o incluso distintos sistemas de trading) dentro del Asesor Experto sirve para disminuir el impacto global de los fallos de componentes individuales. En esencia, cuanto mayor sea la diversidad de instrumentos o sistemas, menos dependerá el resultado final de los contratiempos aislados de sus partes constituyentes.
Llamemos a un nuevo Asesor Experto MultiMartingale.mq5. Los ajustes del algoritmo de trading incluyen:
- UseTime - bandera lógica para activar/desactivar el trading programado
- HourStart y Hour End - el intervalo de horas dentro del cual se permite el trading, si UseTime es igual a true
- Lots - el volumen de la primera transacción de la serie
- Factor - coeficiente de aumento del volumen de las transacciones posteriores a una pérdida
- Limit - el número máximo de operaciones en una serie perdedora con multiplicación de volúmenes (tras ella, vuelta al lote inicial)
- Stop Loss y Take Profit - distancia a los niveles de protección en puntos
- StartType - tipo de la primera transacción (compra o venta)
- Trailing - indicación de stop loss trailing
En el código fuente se describen de esta manera:
input bool UseTime = true; // UseTime (hourStart and hourEnd)
|
En teoría, es lógico establecer niveles de protección no en puntos, sino en términos de acciones del indicador rango medio verdadero (Average True Range, ATR). Sin embargo, en la actualidad no es una tarea primordial.
Además, el Asesor Experto incorpora un mecanismo para detener temporalmente las operaciones de trading durante un tiempo especificado por el usuario (controlado por el parámetro SkipTimeOnError) en caso de errores. Omitiremos aquí una discusión detallada de este aspecto, ya que puede consultarse en los códigos fuente.
Para consolidar todo el conjunto de configuraciones en una entidad unificada, se define una estructura denominada Settings. Esta estructura tiene campos que reflejan variables de entrada. Además, la estructura incluye el campo symbol, que aborda la naturaleza multidivisa de la estrategia. En otras palabras: el símbolo puede ser arbitrario y difiere del símbolo de trabajo del gráfico.
struct Settings
|
En la fase inicial de desarrollo, rellenamos la estructura con variables de entrada. Sin embargo, esto sólo es suficiente para operar con un único símbolo. Posteriormente, a medida que ampliemos el algoritmo para abarcar múltiples símbolos, tendremos que leer varios conjuntos de configuraciones (utilizando un enfoque diferente) y añadirlos a un array de estructuras.
La estructura también engloba varios métodos beneficiosos. En concreto, el método validate verifica la corrección de la configuración, confirmando la existencia del símbolo especificado, y devuelve un indicador de éxito (true).
struct Settings
|
La llamada a CopyClose no sólo comprueba si el símbolo está en línea en Observación de Mercado, sino que también inicia la carga de sus cotizaciones (del marco temporal deseado) y ticks en el probador. Si no se hace esto, sólo las cotizaciones y los ticks (en el modo de ticks reales) del instrumento y marco temporal actualmente seleccionados estarán disponibles en el probador por defecto. Como estamos escribiendo un Asesor Experto multidivisa, necesitaremos cotizaciones y ticks de terceros.
struct Settings
|
El método print envía todos los campos al registro de forma abreviada en una sola línea. Por ejemplo:
EURUSD+0.01*2.0^5(500,1000)[2,22]
|
Necesitaremos otros métodos en la estructura Settings cuando pasemos a la multidivisa. Por ahora, imaginemos una versión simplificada de lo que podría ser el manejador OnInit del Asesor Experto que opera con un símbolo.
int OnInit()
|
De acuerdo con la programación orientada a objetos (POO), el sistema de trading debe describirse de forma generalizada como una interfaz de software. De nuevo, para simplificar el ejemplo, sólo utilizaremos un método en esta interfaz: trade.
interface TradingStrategy
|
La tarea principal del algoritmo es operar, y ni siquiera importa desde dónde decidamos llamar a este método: en cada tick desde OnTick, en la apertura de la barra, o posiblemente en el temporizador.
Lo más probable es que sus Asesores Expertos en funcionamiento necesiten métodos de interfaz adicionales para configurar y admitir varios modos. Pero en este ejemplo no son necesarios.
Vamos a empezar a crear una clase de un sistema de trading específico basado en la interfaz. En nuestro caso, todas las instancias serán de la clase SimpleMartingale. Sin embargo, también es posible implementar muchas clases diferentes que hereden la interfaz dentro de un Asesor Experto y luego utilizarlas de manera uniforme en una combinación arbitraria. Una cartera de estrategias (preferiblemente de naturaleza muy diferente) suele caracterizarse por una mayor estabilidad del comportamiento financiero.
class SimpleMartingale: public TradingStrategy
|
Dentro de la clase, vemos una estructura Settings familiar y el monitor del símbolo de trabajo SymbolMonitor. Además, necesitaremos controlar la presencia de posiciones y seguir el nivel de stop-loss de las mismas, para lo que hemos introducido variables con punteros automáticos a objetos PositionState y TrailingStop. Los punteros automáticos nos permiten en nuestro código no preocuparnos por el borrado explícito de objetos ya que esto se hará automáticamente cuando el control salga del ámbito, o cuando un nuevo puntero sea asignado al puntero automático.
La clase TrailingStop es una clase base, con la implementación más sencilla de seguimiento de precios, de la que se puede heredar una gran cantidad de algoritmos más complejos, un ejemplo de los cuales consideramos como un derivado TrailingStopByMA. Por lo tanto, para dar flexibilidad al programa en el futuro, es conveniente garantizar que el código de llamada pueda pasar su propio objeto de seguimiento específico y personalizado, derivado de TrailingStop. Esto se puede hacer, por ejemplo, pasando un puntero al constructor o convirtiendo SimpleMartingale en una clase de plantilla (entonces la clase de trailing será establecida por el parámetro de plantilla).
Este principio de la programación orientada a objetos (POO) se denomina dependency injection y se utiliza ampliamente junto con muchos otros que hemos mencionado brevemente en la sección Fundamentos teóricos de la programación orientada a objetos: composición.
La configuración se pasa a la clase de estrategia como parámetro del constructor. A partir de ella, asignamos todas las variables internas.
class SimpleMartingale: public TradingStrategy
|
A continuación, utilizamos el objeto PositionFilter para buscar las posiciones «propias» existentes (por el símbolo y número mágico). Si se encuentra una posición de este tipo, creamos para ella el objeto PositionState y, si es necesario, el objeto TrailingStop.
PositionFilter positions;
|
Las operaciones de programación se dejarán por ahora «entre bastidores» en el método trade (campos de parámetros useTime, hourStart y hourEnd). Pasemos directamente al algoritmo de trading.
Si todavía no hay ni ha habido ninguna posición, el puntero PositionState será cero, y tendremos que abrir una posición larga o corta de acuerdo con la dirección seleccionada startType.
virtual bool trade() override
|
Aquí se utilizan los métodos de ayuda openBuy y openSell. Hablaremos de ellos en un par de párrafos. Por ahora, sólo necesitamos saber que devuelven el número de ticket en caso de éxito o 0 en caso de fallo.
Si el objeto position ya contiene información sobre la posición rastreada, comprobamos si está activa llamando a refresh. En caso de éxito (true), actualice la información de la posición llamando a update y rastree también el stop loss, si fue solicitado por los ajustes.
else // position[] != NULL
|
Si se cierra la posición, refresh devolverá false, y estaremos en otra rama if para abrir una nueva posición: en la misma dirección, si se fijó un beneficio, o en dirección opuesta, si se produjo una pérdida. Tenga en cuenta que todavía tenemos una instantánea de la posición anterior en la caché.
else // the position is closed - you need to open a new one
|
La presencia de un ticket distinto de cero en esta etapa final significa que debemos empezar a controlarlo con los nuevos objetos PositionState y TrailingStop.
if(ticket > 0)
|
A continuación presentamos, con algunas abreviaturas, el método openBuy (openSell es lo mismo), que consta de tres pasos:
- Preparación de la estructura MqlTradeRequestSync usando el método prepare (no se muestra aquí, rellena deviation y magic);
- Envío de una orden mediante una llamada al método request.buy;
- Comprobación del resultado con el método postprocess (no se muestra aquí, llama a request.completed y, en caso de error, comienza el periodo de suspensión del trading a la espera de mejores condiciones).
ulong openBuy(double lots)
|
Normalmente, las posiciones se cierran mediante stop loss o take profit. No obstante, admitimos las operaciones programadas que puedan provocar cierres. Volvamos al principio del método trade para programar el trabajo.
virtual bool trade() override
|
El método de trabajo close es muy similar al de openBuy, por lo que no lo consideraremos aquí. Otro método, scheduled, sólo devuelve true o false, dependiendo de si la hora actual cae dentro del rango de horas de trabajo especificado (hourStart, hourEnd).
Así pues, la clase de trading está lista. No obstante, para trabajar con varias divisas, tendrá que crear varias copias de la misma, que gestionará la clase TradingStrategyPool, en la que describimos un array de punteros a TradingStrategy y métodos para reponerla: constructor paramétrico y push.
class TradingStrategyPool: public TradingStrategy
|
No es necesario hacer el pool derivado de la interfaz TradingStrategy, pero si lo hacemos, esto permite el futuro empaquetamiento de pools de estrategias en otros pools de estrategias más grandes, y así sucesivamente. El método trade simplemente llama al mismo método en todos los objetos de array.
En el contexto global, vamos a añadir un puntero automático al pool de trading, y en el manejador OnInit nos aseguraremos de su llenado. Podemos empezar con una única estrategia (abordaremos la multidivisa un poco más adelante).
AutoPtr<TradingStrategyPool> pool;
|
Para empezar a operar, sólo tenemos que escribir el pequeño manejador OnTick siguiente.
void OnTick()
|
Pero, ¿qué hay de la compatibilidad multidivisa?
El conjunto actual de parámetros de entrada está diseñado para un solo instrumento. Podemos utilizar esto para probar y optimizar el Asesor Experto en un único símbolo, pero después de que los ajustes óptimos se encuentran para todos los símbolos, tienen que combinarse y pasarse al algoritmo de alguna manera.
En este caso, aplicamos la solución más sencilla. El código anterior contenía una línea con los ajustes formados por el método print generado por las estructuras Settings. Implementamos el método en la estructura parse que realiza la operación inversa: restaura el estado de los campos por la descripción de la línea. Además, como necesitamos concatenar varias configuraciones para distintos caracteres, acordaremos que se concatenen en una sola cadena larga mediante un carácter delimitador especial, por ejemplo ';'. Entonces es fácil escribir el método estático parseAll para leer el conjunto fusionado de configuraciones, que llamará a parse para llenar el array de estructuras Settings pasadas por referencia. El código fuente completo de los métodos se encuentra en el archivo adjunto.
struct Settings
|
Por ejemplo, la siguiente cadena concatenada contiene ajustes para tres símbolos:
EURUSD+0.01*2.0^7(500,500)[2,22];AUDJPY+0.01*2.0^8(300,500)[2,22];GBPCHF+0.01*1.7^8(1000,2000)[2,22] |
El método parseAll puede analizar este tipo de líneas. Para introducir una cadena de este tipo en el Asesor Experto, describimos la variable de entrada WorkSymbols.
input string WorkSymbols = ""; // WorkSymbols (name±lots*factor^limit(sl,tp)[start,stop];...) |
Si está vacía, el Asesor Experto trabajará con los ajustes de las variables de entrada individuales presentadas anteriormente. Si se especifica la cadena, el manejador de OnInit rellenará el pool de sistemas de trading basándose en los resultados del análisis de esta línea.
int OnInit()
|
Es importante tener en cuenta que en MQL5, la longitud de la cadena de entrada está restringida a 250 caracteres. Además, durante la optimización en el probador, las cadenas se truncan aún más hasta un máximo de 63 caracteres. En consecuencia, para optimizar el trading concurrente a través de numerosos símbolos, se hace imperativo idear un método alternativo para cargar las configuraciones, como recuperarlas de un archivo de texto. Esto puede lograrse fácilmente utilizando la misma variable de entrada, siempre que se designe con un nombre de archivo en lugar de una cadena que contenga ajustes.
Este enfoque se aplica en el método Settings::parseAll mencionado. El nombre del archivo de texto en el que una cadena de entrada se pasará al Asesor Experto sin limitación de longitud se establece de acuerdo con el principio universal adecuado para todos los casos similares: el nombre del archivo comienza con el nombre del Asesor Experto, y luego, después del guion, debe haber el nombre de la variable cuyos datos contiene el archivo. Por ejemplo, en nuestro caso, en la variable de entrada WorkSymbols, puede especificar opcionalmente el nombre de archivo «MultiMartingale-WorkSymbols.txt». A continuación, el método parseAll intentará leer el texto del archivo (debe estar en la «sandbox» MQL5/Files estándar).
El paso de nombres de archivos en los parámetros de entrada requiere que se tomen medidas adicionales para las simulaciones posteriores y la optimización de dicho Asesor Experto: la directiva #property tester_file "MultiMartingale-WorkSymbols.txt" debe añadirse al código fuente. Esto se abordará en detalle en la sección Directivas del preprocesador para el probador. Cuando se añada esta directiva, el Asesor Experto requerirá la presencia del archivo y no se iniciará sin él en el probador.
El Asesor Experto está listo. Podemos probarlo en diferentes símbolos por separado, elegir la mejor configuración para cada uno y construir una cartera de trading. En el próximo capítulo, estudiaremos la API del probador, incluida la optimización, y este Asesor Experto nos resultará muy útil. Mientras tanto, vamos a comprobar su funcionamiento multidivisa.
WorkSymbols=EURUSD+0.01*1.2^4(300,600)[9,11];GBPCHF+0.01*2.0^7(300,400)[14,16];AUDJPY+0.01*2.0^6(500,800)[18,16] |
En el primer trimestre de 2022, recibiremos el siguiente informe (los informes de MetaTrader 5 no proporcionan estadísticas desglosadas por símbolos, por lo que es posible distinguir un informe de una sola divisa de uno de varias divisas sólo por la tabla de transacciones/órdenes/posiciones).

Informe del probador para un Asesor Experto de estrategia de martingala multidivisa
Debe tenerse en cuenta que, debido al hecho de que la estrategia se lanza desde el manejador OnTick, las ejecuciones en diferentes símbolos principales (es decir, los seleccionados en la lista desplegable de ajustes del probador) darán resultados ligeramente diferentes. En nuestra prueba, nos limitamos a utilizar EURUSD como el instrumento más líquido y con mayor frecuencia de ticks, lo que es suficiente para la mayoría de las aplicaciones. No obstante, si desea reaccionar a los ticks de todos los instrumentos, puede utilizar un indicador como EventTickSpy.mq5. Opcionalmente, puede ejecutar la lógica de trading en un temporizador sin estar vinculada a los ticks de un instrumento específico.
Y este es el aspecto de la estrategia de trading para un solo símbolo, en este caso AUDJPY.

Gráfico con una prueba de un Asesor Experto en estrategia de martingala multidivisa
Por cierto, para todos los Asesores Expertos multidivisa, hay otra cuestión importante que queda desatendida aquí. Nos referimos al método de selección del tamaño del lote, por ejemplo, en función de la carga del depósito o del riesgo. Anteriormente, mostramos ejemplos de este tipo de cálculos en un Asesor Experto no de trading LotMarginExposureTable.mq5. En MultiMartingale.mq5 hemos simplificado la tarea eligiendo un lote fijo y mostrándolo en los ajustes de cada símbolo. Sin embargo, en los Asesores Expertos operacionales multidivisa, tiene sentido elegir lotes en proporción al valor de los instrumentos (por margen o volatilidad).
Para concluir, me gustaría señalar que las estrategias multidivisa pueden requerir diferentes principios de optimización. La estrategia considerada permite encontrar por separado los parámetros de los símbolos y luego combinarlos. No obstante, algunas estrategias de arbitraje y agrupación (por ejemplo, el trading de pares) se basan en el análisis simultáneo de todas las herramientas para tomar decisiones de trading. En este caso, los ajustes asociados a todos los símbolos deben incluirse por separado en los parámetros de entrada.