- 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
Seguimiento de los cambios en el entorno de trading
En la sección anterior relacionada con el evento OnTrade hemos mencionado que algunos enfoques de programación de estrategias de trading pueden requerir que se tomen instantáneas del entorno y se comparen entre sí a lo largo del tiempo. Esta es una práctica común cuando se utiliza OnTrade pero también puede activarse en un horario, en cada barra, o incluso tick. Nuestras clases de monitor que pueden leer las propiedades de órdenes, transacciones y posiciones carecían de la capacidad de guardar el estado. En esta sección presentaremos una de las opciones de almacenamiento en caché del entorno de trading.
Las propiedades de todos los objetos de trading se dividen por tipos en tres grupos: entero, real y cadena. Cada clase de objeto tiene sus propios grupos (por ejemplo, para las órdenes, las propiedades de enteros se describen en la enumeración ENUM_ORDER_PROPERTY_INTEGER, y para las posiciones se describen en ENUM_POSITION_PROPERTY_INTEGER), pero la esencia de la división es la misma. Por lo tanto, introduciremos la enumeración PROP_TYPE, con cuya ayuda será posible describir a qué tipo pertenece una propiedad de objeto. Esta generalización surge de forma natural, ya que los mecanismos para almacenar y procesar propiedades del mismo tipo deben ser los mismos, independientemente de si la propiedad pertenece a una orden, posición o transacción.
enum PROP_TYPE
|
Los arrays son la forma más sencilla de almacenar valores de propiedades. Obviamente, debido a la presencia de tres tipos de base, necesitaremos tres arrays diferentes. Vamos a describirlos dentro de una nueva clase TradeState anidada en MonitorInterface (TradeBaseMonitor.mqh).
La plantilla básica MonitorInterface<I,D,S> constituye la base de todas las clases de monitor aplicadas (OrderMonitor, DealMonitor, PositionMonitor). Los tipos I, D y S corresponden aquí a enumeraciones concretas de propiedades de enteros, reales y cadenas.
Es bastante lógico incluir el mecanismo de almacenamiento en el monitor base, sobre todo porque la caché de propiedades creada se llenará de datos mediante la lectura de propiedades del objeto monitor.
template<typename I,typename D,typename S>
|
Toda la clase TradeState se ha hecho pública porque sería necesario acceder a sus campos desde el objeto monitor padre (que se pasa como puntero al constructor), y además TradeState sólo se utilizará en la parte protegida del monitor (no se puede acceder a ellos desde el exterior).
Para rellenar tres arrays con valores de propiedades de tres tipos diferentes, primero debe averiguar la distribución de propiedades por tipo e índices en cada array concreto.
Para cada tipo de objeto de trading (órdenes, transacciones y posiciones), los identificadores de las tres enumeraciones correspondientes con propiedades de distintos tipos no se cruzan y forman una numeración continua. Vamos a demostrarlo.
En el capítulo Enumeraciones hemos visto el script ConversionEnum.mq5 que implementa la función process para registrar todos los elementos de una enumeración determinada. Ese script examinó el enum ENUM_APPLIED_PRICE. Ahora podemos crear una copia del script y analizar las otras tres enumeraciones. Por ejemplo, así:
void OnStart()
|
Como resultado de su ejecución, obtenemos el siguiente registro. La columna de la izquierda contiene la numeración dentro de las enumeraciones, y los valores de la derecha (después del signo '=') son las constantes integradas (identificadores) de los elementos.
ENUM_POSITION_PROPERTY_INTEGER Count=9
|
Por ejemplo, la propiedad con constante 0 es una cadena POSITION_SYMBOL, las propiedades con constantes 1 y 2 son enteros POSITION_TIME y POSITION_TYPE, la propiedad con constante 3 es un POSITION_VOLUME real, etc.
Así, las constantes son un sistema de índices de extremo a extremo sobre propiedades de todos los tipos, y podemos utilizar el mismo algoritmo (basado en EnumToArray.mqh) para obtenerlas.
Para cada propiedad, es necesario recordar su tipo (que determina cuál de los tres arrays almacenará el valor) y el número de serie entre las propiedades del mismo tipo (este será el índice del elemento en el array correspondiente). Por ejemplo, vemos que las posiciones sólo tienen tres propiedades de cadena, por lo que el array strings en la instantánea de una posición tendrá que tener el mismo tamaño, y POSITION_SYMBOL (0), POSITION_COMMENT (11) y POSITION_EXTERNAL_ID (19) se escribirán en sus índices 0, 1 y 2.
La conversión de los índices de extremo a extremo de las propiedades en su tipo (uno de PROP_TYPE) y en un número ordinal en un array del tipo correspondiente se puede hacer una vez al inicio del programa, ya que las enumeraciones con propiedades son constantes (integradas en el sistema). Escribimos la tabla de direccionamiento indirecto resultante en un array estático bidimensional indices. Su tamaño en la primera dimensión se determinará dinámicamente como el número total de propiedades (de los 3 tipos). Escribiremos el tamaño en la variable estática limit. Se asignan un par de celdas para la segunda dimensión: indices[i][0] - tipo PROP_TYPE, indices[i][1] - índice en uno de los arrays ulongs, doubles o strings (dependiendo de indices[i][0]).
class TradeState
|
Las variables j, d y s se utilizarán para indexar secuencialmente las propiedades dentro de cada uno de los tres tipos diferentes. Así es como se hace en el método estático calcIndices.
static int calcIndices()
|
El método boundary devuelve la constante máxima entre todos los elementos de la enumeración E dada.
template<typename E>
|
El mayor valor de los tres tipos de enumeraciones determina el rango de enteros que deben ordenarse de acuerdo con el tipo de propiedad al que pertenecen.
Aquí utilizamos el método detect que devuelve true si el entero es un elemento de una enumeración.
template<typename E>
|
La última pregunta es cómo ejecutar este cálculo cuando se inicia el programa. Esto se consigue utilizando la naturaleza estática de las variables y el método.
template<typename I,typename D,typename S>
|
Observe que limit se inicializa con el resultado de llamar a nuestra función calcIndices.
Teniendo una tabla con índices, implementamos el llenado de arrays con valores de propiedades en el método cache.
class TradeState
|
Hacemos un bucle a través de todo el rango de propiedades de 0 a limit y, dependiendo del tipo de propiedad en indices[i][0], escribimos su valor en el elemento del array ulongs, doubles o strings bajo el número indices[i][1] (el elemento correspondiente del array se pasa por referencia al método _get).
Una llamada a owner.get(e, value) remite a uno de los métodos estándar de la clase monitor (aquí es visible como puntero abstracto MonitorInterface). En concreto, para las posiciones de la clase PositionMonitor, esto dará lugar a las llamadas PositionGetInteger, PositionGetDouble, o PositionGetString. El compilador elegirá el tipo correcto. Los monitores de órdenes y transacciones tienen sus propias implementaciones similares, que este código base incluye automáticamente.
Es lógico heredar la descripción de una instantánea de un objeto de trading de la clase monitor. Dado que tenemos que almacenar en caché órdenes, transacciones y posiciones, tiene sentido hacer de la nueva clase una plantilla y recopilar en ella todos los algoritmos comunes adecuados para todos los objetos. Llamémosla TradeBaseState (archivoTradeState.mqh).
template<typename M,typename I,typename D,typename S>
|
Una de las clases de monitor específicas descritas anteriormente se oculta bajo la letra M (OrderMonitor.mqh, PositionMonitor.mqh, DealMonitor.mqh). La base es el objeto de caché state de la recién introducida clase M::TradeState. Dependiendo de M, se formará en su interior una tabla de índices específica (una para la clase M) y se distribuirán arrays de propiedades (propias para cada instancia de M, es decir, para cada orden, transacción, posición).
La variable cached contiene una señal de si los arrays de state están llenos de valores de propiedades, y si se deben consultar las propiedades de un objeto para devolver valores de la caché. Esto será necesario más adelante para comparar los estados guardados y actuales.
En otras palabras: cuando cached se establece en false, el objeto se comportará como un monitor normal, leyendo propiedades del entorno de trading. Cuando cached es igual a true, el objeto devolverá los valores almacenados previamente de los arrays internos.
virtual long get(const I property) const override
|
Por defecto, el almacenamiento en caché está, por supuesto, activado.
También debemos proporcionar un método que realice directamente el almacenamiento en caché (rellenado de arrays). Para ello, basta con llamar al método cache para el objeto state.
bool update()
|
¿Qué es el método refresh?
Hasta ahora hemos estado utilizando objetos monitor en modo simple: creándolos, leyendo propiedades y borrándolos. Al mismo tiempo, la lectura de propiedades supone que la orden, transacción o posición correspondiente se ha seleccionado en el contexto de trading (dentro del constructor). Como ahora estamos mejorando los monitores para admitir el estado interno, es necesario asegurarse de que el elemento deseado se reasigna para poder leer las propiedades incluso después de un tiempo indefinido (por supuesto, con una comprobación de que el elemento todavía existe). Para implementarlo, hemos añadido el método virtual refresh a la plantilla de la clase MonitorInterface.
// TradeBaseMonitor.mqh
|
Debe devolver true al asignar con éxito una orden, transacción o posición. Si el resultado es false, la variable integrada _LastError debe contener uno de los siguientes errores:
- 4753 ERR_TRADE_POSITION_NOT_FOUND;
- 4754 ERR_TRADE_ORDER_NOT_FOUND;
- 4755 ERR_TRADE_DEAL_NOT_FOUND;
En este caso, la variable miembro ready, que señala la disponibilidad del objeto, debe restablecerse a false en las implementaciones de este método en las clases derivadas.
Por ejemplo, en el constructor PositionMonitor, teníamos y seguimos teniendo una inicialización de este tipo. La situación es similar a la de los monitores de órdenes y transacciones.
// PositionMonitor.mqh
|
Ahora añadiremos el método refresh a todas las clases específicas de este tipo (véase el ejemplo PositionMonitor):
// PositionMonitor.mqh
|
No obstante, rellenar arrays de caché con los valores de las propiedades es sólo la mitad de la batalla; la segunda parte consiste en comparar estos valores con el estado real de la orden, transacción o posición.
Para identificar las diferencias y escribir los índices de las propiedades modificadas en el array changes, la clase generada TradeBaseState proporciona el método getChanges. El método devuelve true cuando se detectan cambios.
template<typename M,typename I,typename D,typename S>
|
Como puede ver, el trabajo principal se confía a un determinado método diff de la clase M. Se trata de un nuevo método: tenemos que escribirlo. Afortunadamente, gracias a la programación orientada a objetos (POO), puede hacer esto una vez en la plantilla base MonitorInterface y el método aparecerá inmediatamente para órdenes, transacciones y posiciones.
// TradeBaseMonitor.mqh
|
Así pues, todo está listo para formar clases de caché específicas para órdenes, transacciones y posiciones. Por ejemplo, las posiciones se almacenarán en el monitor ampliado PositionState en la base de PositionMonitor.
class PositionState: public TradeBaseState<PositionMonitor,
|
Del mismo modo, en el archivo TradeState.mqh se define una clase de caché para las transacciones.
class DealState: public TradeBaseState<DealMonitor,
|
Con las órdenes, las cosas son un poco más complicadas, porque éstas pueden ser activas e históricas. Hasta ahora hemos tenido una clase de monitor genérica para órdenes, OrderMonitor, que intenta encontrar el ticket de orden presentado tanto entre las órdenes activas como en el historial. Este enfoque no es adecuado para el almacenamiento en caché, ya que los Asesores Expertos necesitan hacer un seguimiento de la transición de una orden de un estado a otro.
Por esta razón, añadimos 2 clases específicas más al archivo OrderMonitor.mqh: ActiveOrderMonitor y HistoryOrderMonitor.
// OrderMonitor.mqh
|
Cada una de ellas busca un ticket sólo en su zona. Basándose en estos monitores, ya puede crear clases de caché.
// TradeState.mqh
|
El toque final que añadiremos a la clase TradeBaseState por comodidad es un método especial para convertir un valor de propiedad en una cadena. Aunque existen varias versiones de los métodos stringify en el monitor, todos ellos «imprimirán» valores de la caché (si la variable miembro cached es igual a true) o valores del objeto original del entorno de trading (si cached es igual a false). Para visualizar las diferencias entre la caché y el objeto modificado (cuando dichas diferencias se encuentran), necesitamos leer simultáneamente el valor de la caché y pasar por alto la caché. En este sentido, añadimos el método stringifyRaw que siempre trabaja con la propiedad directamente (debido a que la variable cached se restablece y reinstala temporalmente).
// get the string representation of the property 'i' bypassing the cache
|
Vamos a comprobar el rendimiento del monitor de caché utilizando un ejemplo sencillo de un Asesor Experto que monitoriza el estado de una orden activa (OrderSnapshot.mq5). Más adelante desarrollaremos esta idea para almacenar en caché cualquier conjunto de órdenes, transacciones o posiciones, es decir, crearemos una caché completa.
El Asesor Experto intentará encontrar la última en la lista de órdenes activas y creará el objeto OrderState para ella. Si no hay órdenes, se pedirá al usuario que cree una orden o abra una posición (esto último se asocia a la colocación y ejecución de una orden en el mercado). En cuanto se encuentra una orden se comprueba si el estado de la misma ha cambiado. Esta comprobación se realiza en el manejador OnTrade. El Asesor Experto continuará supervisando esta orden hasta que se descargue.
int OnInit()
|
Además de mostrar un array de propiedades modificadas, estaría bien mostrar los cambios en sí. Por lo tanto, en lugar de una elipsis, añadiremos un fragmento de este tipo (nos será útil en futuras clases de cachés completas).
for(int k = 0; k < ArraySize(changes); ++k)
|
Aquí utilizamos el nuevo método stringifyRaw. Después de visualizar los cambios, no olvide actualizar el estado de la caché.
state.update(); |
Si ejecuta el Asesor Experto en una cuenta sin órdenes activas y coloca una nueva, verá las siguientes entradas en el registro (aquí buy limit para EURUSD se crea por debajo del precio de mercado actual).
Alert: Please, create a pending order or open/close a position >>> OnTrade(0) Order picked up: 1311736135 true MonitorInterface<ENUM_ORDER_PROPERTY_INTEGER,ENUM_ORDER_PROPERTY_DOUBLE,ENUM_ORDER_PROPERTY_STRING> ENUM_ORDER_PROPERTY_INTEGER Count=14 0 ORDER_TIME_SETUP=2022.04.11 11:42:39 1 ORDER_TIME_EXPIRATION=1970.01.01 00:00:00 2 ORDER_TIME_DONE=1970.01.01 00:00:00 3 ORDER_TYPE=ORDER_TYPE_BUY_LIMIT 4 ORDER_TYPE_FILLING=ORDER_FILLING_RETURN 5 ORDER_TYPE_TIME=ORDER_TIME_GTC 6 ORDER_STATE=ORDER_STATE_STARTED 7 ORDER_MAGIC=0 8 ORDER_POSITION_ID=0 9 ORDER_TIME_SETUP_MSC=2022.04.11 11:42:39'729 10 ORDER_TIME_DONE_MSC=1970.01.01 00:00:00'000 11 ORDER_POSITION_BY_ID=0 12 ORDER_TICKET=1311736135 13 ORDER_REASON=ORDER_REASON_CLIENT ENUM_ORDER_PROPERTY_DOUBLE Count=7 0 ORDER_VOLUME_INITIAL=0.01 1 ORDER_VOLUME_CURRENT=0.01 2 ORDER_PRICE_OPEN=1.087 3 ORDER_PRICE_CURRENT=1.087 4 ORDER_PRICE_STOPLIMIT=0.0 5 ORDER_SL=0.0 6 ORDER_TP=0.0 ENUM_ORDER_PROPERTY_STRING Count=3 0 ORDER_SYMBOL=EURUSD 1 ORDER_COMMENT= 2 ORDER_EXTERNAL_ID= >>> OnTrade(1) Order properties changed: 10 14 ORDER_PRICE_CURRENT: 1.087 -> 1.09073 ORDER_STATE: ORDER_STATE_STARTED -> ORDER_STATE_PLACED >>> OnTrade(2) >>> OnTrade(3) >>> OnTrade(4) |
Aquí puede ver cómo el estado de la orden ha cambiado de INICIADA a CURSADA. Si, en lugar de una orden pendiente, hemos abierto en el mercado con un volumen pequeño, es posible que no tengamos tiempo de recibir estos cambios, porque tales órdenes, por regla general, se establecen muy rápidamente, y su estado observado cambia de INICIADO inmediatamente a RELLENADO. Y esto último ya significa que la orden ha pasado al historial. Por lo tanto, es necesario un seguimiento paralelo de historias para rastrearlas. Lo mostraremos en el siguiente ejemplo.
Tenga en cuenta que puede haber muchos eventos OnTrade pero no todos están relacionados con nuestra orden.
Intentemos establecer el nivel Take Profit y comprobemos el registro.
>>> OnTrade(5)
|
A continuación, cambie la fecha de vencimiento: de GTC a un día.
>>> OnTrade(8)
|
Aquí, en el proceso de cambio de nuestra orden, el precio tuvo tiempo suficiente de cambiar, y por lo tanto hemos «enganchado» una notificación intermedia sobre el nuevo valor en ORDER_PRICE_CURRENT. Y sólo después de eso, los cambios esperados en ORDER_TYPE_TIME y ORDER_TIME_EXPIRATION entraron en el registro.
A continuación, eliminamos la orden.
>>> OnTrade(12)
|
Ahora, para cualquier acción con la cuenta que lleve a eventos OnTrade, nuestro Asesor Experto mostrará TRADE_ORDER_NOT_FOUND, ya que está diseñado para seguir una sola orden. Si el Asesor Experto se reinicia, «atrapará» otra orden si la hay. Pero dejaremos el Asesor Experto y empezaremos a prepararnos para una tarea más urgente.
Por regla general, el almacenamiento en caché y el control de los cambios no son necesarios para una sola orden o posición, sino para todas o un conjunto de ellas, seleccionadas en función de determinadas condiciones. Para ello, desarrollaremos una clase de plantilla base TradeCache (TradeCache.mqh) y, a partir de ella, crearemos clases aplicadas para listas de órdenes, transacciones y posiciones.
template<typename T,typename F,typename E>
|
En esta plantilla, la letra T indica una de las clases de la familia TradeState. Como puede ver, se reserva un array de tales objetos en forma de punteros automáticos con el nombre data.
La letra F describe el tipo de una de las clases de filtro (OrderFilter.mqh, incluyendo HistoryOrderFilter, DealFilter.mqh, PositionFilter.mqh) utilizado para seleccionar elementos en caché. En el caso más sencillo, cuando el filtro no contiene condiciones let, todos los elementos se almacenarán en caché (con respecto al historial de muestreo para objetos del historial).
La letra E corresponde a la enumeración en la que se encuentra la property que identifica los objetos. Dado que esta propiedad suele ser SOME_TICKET, se supone que la enumeración es un entero ENUM_SOMETHING_PROPERTY_INTEGER.
La variable NOT_FOUND_ERROR está destinada al código de error que se produce al intentar asignar un objeto inexistente para su lectura, por ejemplo, ERR_TRADE_POSITION_NOT_FOUND para posiciones.
En parámetros, el método de la clase principal scan recibe una referencia al filtro configurado (debe ser configurado por el código llamante).
void scan(F &f)
|
Al principio del método recopilamos los identificadores de los objetos ya almacenados en caché en el array tickets. Obviamente, en la primera ejecución, éste estará vacío.
A continuación, rellenamos el array objects con entradas de objetos relevantes utilizando un filtro. Para cada nuevo ticket, creamos un objeto monitor de caché T y lo añadimos al array data. Para los objetos antiguos, analizamos la presencia de cambios llamando a data[j][].getChanges(changes) y luego actualizamos la caché llamando a data[j][].update().
ulong objects[];
|
Como puede ver, en cada fase del cambio, es decir, cuando se añade un objeto o después de modificarlo, se llama a los métodos onAdded y onUpdated. Se trata de métodos stub virtuales que el escáner puede utilizar para notificar al programa los eventos apropiados. Se espera que el código de la aplicación implemente una clase derivada con versiones anuladas de estos métodos. Abordaremos esta cuestión un poco más adelante, pero por ahora seguiremos considerando el método scan.
En el bucle anterior, todos los tickets encontrados en el array tickets se ponen a cero y, por lo tanto, los elementos restantes corresponden a los objetos que faltan del entorno de trading. A continuación, se comprueban llamando a getChanges y comparando el código de error con NOT_FOUND_ERROR. Si esto es cierto, se llama al método virtual onRemoved. Devuelve una bandera booleana (proporcionada por el código de la aplicación) que indica si el elemento debe eliminarse de la caché.
for(int j = 0; j < existedBefore; ++j)
|
Al final del método scan, el array data se despeja de elementos nulos, pero este fragmento se omite aquí por brevedad.
La clase base proporciona implementaciones estándar de los métodos onAdded, onRemoved y onUpdated que muestran la esencia de los eventos en el registro. Definiendo la macro PRINT_DETAILS en su código antes de incluir el archivo de encabezado TradeCache.mqh puede ordenar la impresión de todas las propiedades de cada objeto nuevo.
virtual void onAdded(const T &state)
|
No presentaremos el método onUpdated, ya que prácticamente repite el código para la salida de cambios del Asesor Experto OrderSnapshot.mq5 mostrado anteriormente.
Por supuesto, la clase base tiene facilidades para obtener el tamaño de la caché y acceder a un objeto específico por número.
int size() const
|
A partir de la clase base TradeCache podemos crear fácilmente ciertas clases para almacenar en caché listas de posiciones, órdenes activas y órdenes del historial. El almacenamiento en caché de las transacciones se deja como tarea independiente.
class PositionCache: public TradeCache<PositionState,PositionFilter,
|
Para resumir el proceso de desarrollo de la funcionalidad presentada, ofrecemos un diagrama de las clases principales. Se trata de una versión simplificada de los diagramas de UML que puede resultar útil a la hora de diseñar programas complejos en MQL5.

Diagrama de clases de monitores, filtros y cachés de objetos de trading
Las plantillas se marcan en amarillo, las clases abstractas se dejan en blanco y ciertas implementaciones se muestran en color. Las flechas sólidas con puntas rellenas indican herencia, y las flechas punteadas con puntas huecas indican clasificación de plantillas. Las flechas punteadas con puntas abiertas indican el uso de los métodos especificados entre sí por las clases. Las conexiones con diamantes son una composición (inclusión de unos objetos en otros).
Como ejemplo de uso de la caché, vamos a crear un Asesor Experto TradeSnapshot.mq5, que responderá a cualquier cambio en el entorno de trading desde el manejador OnTrade. Para el filtrado y el almacenamiento en caché, el código describe 6 objetos, 2 (filtro y caché) para cada tipo de elemento: posiciones, órdenes activas y órdenes históricas.
PositionFilter filter0;
|
No se establecen condiciones para los filtros a través de las llamadas al método let para que todos los objetos en línea descubiertos entren en la caché. Existe un ajuste adicional para las órdenes del historial.
Opcionalmente, al inicio, puede cargar en la caché órdenes anteriores con una profundidad de historial determinada. Esto puede hacerse a través de la variable de entrada HistoryLookup. En esta variable, puede seleccionar el último día, la última semana (por duración, no por calendario), el mes (30 días) o el año (360 días). Por defecto, el historial pasado no se carga (más concretamente, sólo se carga en 1 segundo). Dado que la macro PRINT_DETAILS está definida en el Asesor Experto, tenga cuidado con las cuentas con un gran historial: pueden generar un gran registro si no se limita el periodo.
enum ENUM_HISTORY_LOOKUP
|
En el manejador OnInit, reiniciamos las cachés (en caso de que el Asesor Experto se reinicie con nuevos parámetros), calculamos la fecha de inicio del historial en la variable origin, y llamamos a OnTrade por primera vez.
int OnInit()
|
El manejador OnTrade es minimalista, ya que todas las complejidades están ahora ocultas dentro de las clases.
void OnTrade()
|
Inmediatamente después de lanzar el Asesor Experto en una cuenta limpia, veremos el siguiente mensaje:
>>> OnTrade(0)
|
Vamos a intentar ejecutar el caso de prueba más sencillo: vamos a comprar o vender en una cuenta «vacía» que no tiene posiciones abiertas ni órdenes pendientes. El registro incluirá los siguientes eventos (que ocurren casi instantáneamente).
En primer lugar, se detectará una orden activa.
>>> OnTrade(1) OrderCache added: 1311792104 MonitorInterface<ENUM_ORDER_PROPERTY_INTEGER,ENUM_ORDER_PROPERTY_DOUBLE,ENUM_ORDER_PROPERTY_STRING> ENUM_ORDER_PROPERTY_INTEGER Count=14 0 ORDER_TIME_SETUP=2022.04.11 12:34:51 1 ORDER_TIME_EXPIRATION=1970.01.01 00:00:00 2 ORDER_TIME_DONE=1970.01.01 00:00:00 3 ORDER_TYPE=ORDER_TYPE_BUY 4 ORDER_TYPE_FILLING=ORDER_FILLING_FOK 5 ORDER_TYPE_TIME=ORDER_TIME_GTC 6 ORDER_STATE=ORDER_STATE_STARTED 7 ORDER_MAGIC=0 8 ORDER_POSITION_ID=0 9 ORDER_TIME_SETUP_MSC=2022.04.11 12:34:51'096 10 ORDER_TIME_DONE_MSC=1970.01.01 00:00:00'000 11 ORDER_POSITION_BY_ID=0 12 ORDER_TICKET=1311792104 13 ORDER_REASON=ORDER_REASON_CLIENT ENUM_ORDER_PROPERTY_DOUBLE Count=7 0 ORDER_VOLUME_INITIAL=0.01 1 ORDER_VOLUME_CURRENT=0.01 2 ORDER_PRICE_OPEN=1.09218 3 ORDER_PRICE_CURRENT=1.09218 4 ORDER_PRICE_STOPLIMIT=0.0 5 ORDER_SL=0.0 6 ORDER_TP=0.0 ENUM_ORDER_PROPERTY_STRING Count=3 0 ORDER_SYMBOL=EURUSD 1 ORDER_COMMENT= 2 ORDER_EXTERNAL_ID= |
A continuación, esta orden pasará al historial (al mismo tiempo, cambiarán al menos el estado, el tiempo de ejecución y el ID de posición).
HistoryOrderCache added: 1311792104 MonitorInterface<ENUM_ORDER_PROPERTY_INTEGER,ENUM_ORDER_PROPERTY_DOUBLE,ENUM_ORDER_PROPERTY_STRING> ENUM_ORDER_PROPERTY_INTEGER Count=14 0 ORDER_TIME_SETUP=2022.04.11 12:34:51 1 ORDER_TIME_EXPIRATION=1970.01.01 00:00:00 2 ORDER_TIME_DONE=2022.04.11 12:34:51 3 ORDER_TYPE=ORDER_TYPE_BUY 4 ORDER_TYPE_FILLING=ORDER_FILLING_FOK 5 ORDER_TYPE_TIME=ORDER_TIME_GTC 6 ORDER_STATE=ORDER_STATE_FILLED 7 ORDER_MAGIC=0 8 ORDER_POSITION_ID=1311792104 9 ORDER_TIME_SETUP_MSC=2022.04.11 12:34:51'096 10 ORDER_TIME_DONE_MSC=2022.04.11 12:34:51'097 11 ORDER_POSITION_BY_ID=0 12 ORDER_TICKET=1311792104 13 ORDER_REASON=ORDER_REASON_CLIENT ENUM_ORDER_PROPERTY_DOUBLE Count=7 0 ORDER_VOLUME_INITIAL=0.01 1 ORDER_VOLUME_CURRENT=0.0 2 ORDER_PRICE_OPEN=1.09218 3 ORDER_PRICE_CURRENT=1.09218 4 ORDER_PRICE_STOPLIMIT=0.0 5 ORDER_SL=0.0 6 ORDER_TP=0.0 ENUM_ORDER_PROPERTY_STRING Count=3 0 ORDER_SYMBOL=EURUSD 1 ORDER_COMMENT= 2 ORDER_EXTERNAL_ID= >>> positions: 0, orders: 1, history: 1 |
Obsérvese que estas modificaciones se han producido dentro de la misma llamada a OnTrade. En otras palabras: mientras nuestro programa analizaba las propiedades de la nueva orden (llamando a orders.scan), la orden era procesada por el terminal en paralelo, y para cuando se comprobó el historial (llamando a history.scan), ya había descendido en el historial. Por eso aparece tanto aquí como allí de acuerdo con la última línea de este fragmento de registro. Este comportamiento es normal en los programas multihilo y debe tenerse en cuenta a la hora de diseñarlos, pero no siempre tiene por qué ser así. Aquí simplemente llamamos la atención sobre ello. Cuando se ejecuta un programa MQL rápidamente, esta situación no suele ocurrir.
Si comprobáramos primero el historial y luego las órdenes en línea, en la primera fase podríamos encontrarnos con que la orden aún no está en el historial, y en la segunda fase con que la orden ya no está en línea. Es decir, teóricamente podría perderse por un momento. Una situación más realista es saltarse una orden en su fase activa debido a la sincronización del historial, es decir, fijarla inmediatamente por primera vez en el historial.
Recuerde que MQL5 no permite sincronizar el entorno de trading en su conjunto, sino sólo por partes:
- Entre las órdenes activas, la información es relevante para la orden para la que se acaba de llamar a la función OrderSelect o OrderGetTicket.
- Entre las posiciones, la información es relevante para la posición para la que se acaba de llamar a la función PositionSelect, PositionSelectByTicket o PositionGetTicket.
- Para las órdenes y transacciones del historial, la información está disponible en el contexto de la última llamada de HistorySelect, HistorySelectByPosition HistoryOrderSelect. HistoryDealSelect
Además, recordemos que los eventos de trading (como cualquier evento MQL5) son mensajes sobre cambios que se han producido, puestos en cola, y recuperado de la cola de forma diferida, y no inmediatamente en el momento de los cambios. Además, el evento OnTrade se produce después de los eventos OnTradeTransaction relevantes.
Pruebe diferentes configuraciones del programa, depure y genere registros detallados para elegir el algoritmo más fiable para su sistema de trading.
Volvamos a nuestro registro. En la siguiente activación de OnTrade, la situación ya se ha arreglado: la caché de órdenes activas ha detectado la eliminación de la orden. Por el camino, la caché de posiciones vio una posición abierta.
>>> OnTrade(2) PositionCache added: 1311792104 MonitorInterface<ENUM_POSITION_PROPERTY_INTEGER,ENUM_POSITION_PROPERTY_DOUBLE,ENUM_POSITION_PROPERTY_STRING> ENUM_POSITION_PROPERTY_INTEGER Count=9 0 POSITION_TIME=2022.04.11 12:34:51 1 POSITION_TYPE=POSITION_TYPE_BUY 2 POSITION_MAGIC=0 3 POSITION_IDENTIFIER=1311792104 4 POSITION_TIME_MSC=2022.04.11 12:34:51'097 5 POSITION_TIME_UPDATE=2022.04.11 12:34:51 6 POSITION_TIME_UPDATE_MSC=2022.04.11 12:34:51'097 7 POSITION_TICKET=1311792104 8 POSITION_REASON=POSITION_REASON_CLIENT ENUM_POSITION_PROPERTY_DOUBLE Count=8 0 POSITION_VOLUME=0.01 1 POSITION_PRICE_OPEN=1.09218 2 POSITION_PRICE_CURRENT=1.09214 3 POSITION_SL=0.00000 4 POSITION_TP=0.00000 5 POSITION_COMMISSION=0.0 6 POSITION_SWAP=0.00 7 POSITION_PROFIT=-0.04 ENUM_POSITION_PROPERTY_STRING Count=3 0 POSITION_SYMBOL=EURUSD 1 POSITION_COMMENT= 2 POSITION_EXTERNAL_ID= OrderCache removed: 1311792104 >>> positions: 1, orders: 0, history: 1 |
Al cabo de un tiempo, cerramos la posición. Como en nuestro código la caché de posición se comprueba primero (positions.scan), los cambios en la posición cerrada se incluyen en el registro.
>>> OnTrade(8)
|
Más adelante, en la misma llamada de OnTrade, detectamos la aparición de una orden de cierre y su transferencia instantánea al historial (de nuevo, debido a su rápido procesamiento paralelo por parte del terminal).
OrderCache added: 1311796883 MonitorInterface<ENUM_ORDER_PROPERTY_INTEGER,ENUM_ORDER_PROPERTY_DOUBLE,ENUM_ORDER_PROPERTY_STRING> ENUM_ORDER_PROPERTY_INTEGER Count=14 0 ORDER_TIME_SETUP=2022.04.11 12:39:55 1 ORDER_TIME_EXPIRATION=1970.01.01 00:00:00 2 ORDER_TIME_DONE=1970.01.01 00:00:00 3 ORDER_TYPE=ORDER_TYPE_SELL 4 ORDER_TYPE_FILLING=ORDER_FILLING_FOK 5 ORDER_TYPE_TIME=ORDER_TIME_GTC 6 ORDER_STATE=ORDER_STATE_STARTED 7 ORDER_MAGIC=0 8 ORDER_POSITION_ID=1311792104 9 ORDER_TIME_SETUP_MSC=2022.04.11 12:39:55'710 10 ORDER_TIME_DONE_MSC=1970.01.01 00:00:00'000 11 ORDER_POSITION_BY_ID=0 12 ORDER_TICKET=1311796883 13 ORDER_REASON=ORDER_REASON_CLIENT ENUM_ORDER_PROPERTY_DOUBLE Count=7 0 ORDER_VOLUME_INITIAL=0.01 1 ORDER_VOLUME_CURRENT=0.01 2 ORDER_PRICE_OPEN=1.09222 3 ORDER_PRICE_CURRENT=1.09222 4 ORDER_PRICE_STOPLIMIT=0.0 5 ORDER_SL=0.0 6 ORDER_TP=0.0 ENUM_ORDER_PROPERTY_STRING Count=3 0 ORDER_SYMBOL=EURUSD 1 ORDER_COMMENT= 2 ORDER_EXTERNAL_ID= HistoryOrderCache added: 1311796883 MonitorInterface<ENUM_ORDER_PROPERTY_INTEGER,ENUM_ORDER_PROPERTY_DOUBLE,ENUM_ORDER_PROPERTY_STRING> ENUM_ORDER_PROPERTY_INTEGER Count=14 0 ORDER_TIME_SETUP=2022.04.11 12:39:55 1 ORDER_TIME_EXPIRATION=1970.01.01 00:00:00 2 ORDER_TIME_DONE=2022.04.11 12:39:55 3 ORDER_TYPE=ORDER_TYPE_SELL 4 ORDER_TYPE_FILLING=ORDER_FILLING_FOK 5 ORDER_TYPE_TIME=ORDER_TIME_GTC 6 ORDER_STATE=ORDER_STATE_FILLED 7 ORDER_MAGIC=0 8 ORDER_POSITION_ID=1311792104 9 ORDER_TIME_SETUP_MSC=2022.04.11 12:39:55'710 10 ORDER_TIME_DONE_MSC=2022.04.11 12:39:55'711 11 ORDER_POSITION_BY_ID=0 12 ORDER_TICKET=1311796883 13 ORDER_REASON=ORDER_REASON_CLIENT ENUM_ORDER_PROPERTY_DOUBLE Count=7 0 ORDER_VOLUME_INITIAL=0.01 1 ORDER_VOLUME_CURRENT=0.0 2 ORDER_PRICE_OPEN=1.09222 3 ORDER_PRICE_CURRENT=1.09222 4 ORDER_PRICE_STOPLIMIT=0.0 5 ORDER_SL=0.0 6 ORDER_TP=0.0 ENUM_ORDER_PROPERTY_STRING Count=3 0 ORDER_SYMBOL=EURUSD 1 ORDER_COMMENT= 2 ORDER_EXTERNAL_ID= >>> positions: 1, orders: 1, history: 2 |
Ya hay dos órdenes en la caché del historial, pero las cachés de posición y de órdenes activas que se analizaron antes de la caché del historial aún no han aplicado estos cambios.
No obstante, en el siguiente evento OnTrade, vemos que la posición está cerrada, y la orden de mercado ha desaparecido.
>>> OnTrade(9)
|
Si monitorizamos las cachés en cada tick (o una vez por segundo, pero no sólo para eventos OnTrade), veremos cambios en las propiedades ORDER_PRICE_CURRENT y POSITION_PRICE_CURRENT sobre la marcha. POSITION_PROFIT también cambiará.
Nuestras clases no tienen persistence, es decir, viven sólo en RAM y no saben cómo guardar y restaurar su estado en ningún almacenamiento a largo plazo, como archivos. Esto significa que el programa puede pasar por alto un cambio ocurrido entre sesiones de terminal. Si necesita esa funcionalidad, deberá implementarla usted mismo. En el futuro, en la Parte 7 del libro, veremos el soporte de base de datos SQLite integrado en MQL5, que proporciona la forma más eficiente y conveniente para almacenar la caché del entorno de trading y datos tabulares similares.