- 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
Seleccionar órdenes por propiedades
En una de las secciones sobre propiedades de los símbolos hemos introducido la clase SymbolFilter para seleccionar instrumentos financieros con características específicas. Ahora aplicaremos el mismo enfoque a las órdenes.
Dado que no sólo tenemos que analizar órdenes, sino también transacciones y posiciones de forma similar, separaremos la parte general del algoritmo de filtrado en la clase base TradeFilter (TradeFilter.mqh). Repite casi exactamente el código fuente de SymbolFilter. Por lo tanto, no volveremos a explicarlo aquí.
Quienes lo deseen pueden realizar una comparación contextual de los archivos SymbolFilter.mqh y TradeFilter.mqh para comprobar su similitud y localizar pequeñas modificaciones.
La principal diferencia es que la clase TradeFilter es una plantilla, ya que tiene que manejar las propiedades de diferentes objetos: órdenes, transacciones y posiciones.
enum IS // supported comparison conditions in filters
|
Los parámetros de plantilla I, D y S son enumeraciones para grupos de propiedades de tres tipos principales (entero, real y cadena): para las órdenes, se describieron en secciones anteriores, por lo que, para mayor claridad, puede imaginar que I=ENUM_ORDER_PROPERTY_INTEGER, D=ENUM_ORDER_PROPERTY_DOUBLE, S=ENUM_ORDER_PROPERTY_STRING.
El tipo T ha sido diseñado para especificar una clase de monitor. Por el momento sólo tenemos listo un monitor, OrderMonitor. Más adelante implementaremos DealMonitor y PositionMonitor.
Anteriormente, en la clase SymbolFilter, no utilizamos parámetros de plantilla porque para los símbolos, todos los tipos de enumeraciones de propiedades son invariablemente conocidos, y existe una única clase SymbolMonitor.
Recordemos la estructura de la clase filtro. Un grupo de métodos let permite registrar una combinación de pares «propiedad=valor» en el filtro, que luego se utilizará para seleccionar objetos en los métodos select. La propiedad ID se especifica en el parámetro property, y el valor está en el parámetro value.
También existen varios métodos select. Permiten al código de llamada rellenar un array con los tickets seleccionados, así como, si es necesario, arrays adicionales con los valores de las propiedades de los objetos solicitados. Los identificadores específicos de las propiedades solicitadas se establecen en el primer parámetro del método select; puede ser una propiedad o varias. En función de esto, el array receptor debe ser unidimensional o bidimensional.
La combinación de propiedad y valor puede comprobarse no sólo para la igualdad (EQUAL), sino también para las operaciones mayor/menor (GREATER/LESS). Para las propiedades de cadena, es aceptable especificar un patrón de búsqueda con el carácter «*» que indique cualquier secuencia de caracteres (por ejemplo, «*[tp]*» para la propiedad ORDER_COMMENT coincidirá con todos los comentarios en los que aparezca «[tp]» en cualquier lugar, aunque esto es sólo una demostración de la posibilidad -mientras que para buscar órdenes resultantes de Take Profit activado debe analizar ORDER_REASON).
Como el algoritmo requiere la implementación de un bucle a través de todos los objetos y los objetos pueden ser de diferentes tipos (hasta ahora son órdenes, pero luego aparecerá la compatibilidad para transacciones y posiciones), necesitamos describir dos métodos abstractos en la clase TradeFilter, total y get:
virtual int total() const = 0;
|
El primero devuelve el número de objetos y el segundo devuelve el ticket de orden por su número. Esto debería recordarle el par de funciones OrdersTotal y OrderGetTicket. De hecho, se utilizan en implementaciones específicas de métodos para filtrar órdenes.
A continuación se muestra la clase OrderFilter (OrderFilter.mqh) en su totalidad.
#include <MQL5Book/OrderMonitor.mqh>
|
Esta simplicidad es especialmente importante dado que se crearán filtros similares sin esfuerzo para operaciones y posiciones.
Con la ayuda de la nueva clase, podemos comprobar mucho más fácilmente la presencia de órdenes pertenecientes a nuestro Asesor Experto, es decir, reemplazar cualquier versión autoescrita de la función GetMyOrder utilizada en el ejemplo PendingOrderModify.mq5.
OrderFilter filter;
|
Con «cualquier versión» nos referimos a que, gracias a la clase de filtro, podemos crear condiciones arbitrarias para seleccionar órdenes y modificarlas «sobre la marcha» (por ejemplo, por indicación del usuario, no del programador).
Como ejemplo de cómo utilizar el filtro, utilicemos un Asesor Experto que crea una cuadrícula de órdenes pendientes para operar en un rebote desde niveles dentro de un determinado rango de precios, es decir, diseñado para un mercado fluctuante. A partir de esta sección y durante las siguientes, modificaremos el Asesor Experto en el contexto del material que se está estudiando.
La primera versión del Asesor Experto PendingOrderGrid1.mq5 construye una cuadrícula de un tamaño determinado a partir de órdenes Limit y Stop Limit. Los parámetros serán el número de niveles de precios y el paso en puntos entre ellos. El esquema de funcionamiento se ilustra en el siguiente gráfico:
Cuadrícula de órdenes pendientes en 4 niveles con un paso de 200 puntos
A una hora inicial determinada, que puede venir determinada por el horario intradiario y corresponder, por ejemplo, a la «zona plana de la noche», el precio actual se redondea al tamaño del paso de la cuadrícula, y a partir de este nivel se establece un número determinado de niveles hacia arriba y hacia abajo.
En cada nivel superior colocamos una orden límite de venta y una orden límite de compra stoplimit con el precio de la orden Limit futura un nivel por debajo. En cada nivel inferior, colocamos una orden límite de compra y una orden de venta stoplimit con el precio de la orden Limit futura un nivel por encima.
Cuando el precio toca uno de los niveles, la orden Limit que se encuentra allí se convierte en compra o venta (posición). Al mismo tiempo, una orden Stop Limit del mismo nivel es convertida automáticamente por el sistema en una orden Limit de sentido opuesto en el nivel siguiente.
Por ejemplo, si el precio traspasa el nivel mientras se mueve al alza, obtendremos una posición corta, y se creará una orden Limit de compra a la distancia de paso por debajo del mismo.
El Asesor Experto controlará que en cada nivel haya una orden Stop Limit emparejada con una orden Limit. Por lo tanto, tras detectar una nueva orden límite de compra, el programa le añadirá una orden Stop Limit de venta al mismo nivel, y el precio objetivo de la futura orden Limit será el nivel situado junto al superior, es decir, aquel en el que se abre la posición.
Digamos que el precio baja y activa una orden Limit al nivel inferior: obtendremos una posición larga. Al mismo tiempo, la orden Stop Limit se convierte en una orden Limit para vender al nivel inmediatamente superior. Ahora el Asesor Experto detectará de nuevo una orden Limit «básica» y creará una orden Stop Limit para comprar como par a ella, al mismo nivel que el precio de la futura orden Limit un nivel por debajo.
Si hay posiciones opuestas, las cerraremos. También se establecerá un periodo intradiario en el que el sistema de trading estará habilitado, y durante el resto del tiempo se eliminarán todas las órdenes y posiciones. Esto, en particular, es útil para la «zona plana de la noche», cuando las fluctuaciones de rentabilidad del mercado son especialmente pronunciadas.
Por supuesto, ésta es sólo una de las muchas posibles implementaciones de la estrategia de cuadrícula que carece de muchas de las personalizaciones de las cuadrículas, pero no complicaremos demasiado el ejemplo.
El Asesor Experto analizará la situación en cada barra (presumiblemente marco temporal H1 o menos). En teoría, la lógica de funcionamiento de este Asesor Experto debe mejorarse respondiendo rápidamente a eventos de trading pero aún no las hemos explorado. Por lo tanto, en lugar de realizar un seguimiento constante y un restablecimiento «manual» instantáneo de las órdenes Limit en los niveles de cuadrícula vacantes, confiamos este trabajo al servidor mediante el uso de órdenes Stop Limit. Sin embargo, aquí hay un matiz.
El hecho es que las órdenes Limit y Stop Limit en cada nivel son de tipos opuestos (buy/sell) y por lo tanto son activadas por diferentes tipos de precios.
Resulta que si el mercado subiera al siguiente nivel de la mitad superior de la cuadrícula, el precio Ask podría tocar el nivel y activar una orden de compra Stop Limit, pero el precio Bid no alcanzaría el nivel, y la orden límite de venta se mantendría tal cual (no se convertiría en una posición). En la mitad inferior de la cuadrícula, cuando el mercado se mueve a la baja, la situación se repite. Cualquier nivel es tocado en primer lugar por el precio Bid, y activa una orden Stop Limit de venta, y sólo con un nuevo descenso el nivel es alcanzado también por el precio Ask. Si no hay movimiento, la orden Limit de compra permanecerá como está.
Este problema se vuelve crítico a medida que aumenta el diferencial. Por lo tanto, el Asesor Experto requerirá un control adicional sobre las órdenes Limit «extra». En otras palabras: el Asesor Experto no generará una orden Stop Limit que falte en el nivel si ya existe una orden Limit a su supuesto precio objetivo (nivel adyacente).
El código fuente se incluye en el archivo PendingOrderGrid1.mq5. En los parámetros de entrada, puede establecer el Volume de cada operación (por defecto, si se deja igual a 0, se toma el lote mínimo del símbolo del gráfico), el número de niveles de la cuadrícula GridSize (debe ser par), y el paso GridStep entre niveles en puntos. Las horas de inicio y fin del segmento intradiario sobre el que se permite trabajar a la estrategia se especifican en los parámetros StartTime y StopTime: en ambos, sólo importa la hora.
#include <MQL5Book/MqlTradeSync.mqh>
|
El segmento de tiempo de trabajo puede estar dentro de un día (StartTime < StopTime) o cruzar el límite del día (StartTime > StopTime); por ejemplo, de 22:00 a 09:00. Si las dos horas son iguales, se entiende que las operaciones se realizan las veinticuatro horas del día.
Antes de proceder a la aplicación de la idea de trading, vamos a simplificar la tarea de configurar las consultas y la salida de información de diagnóstico en el registro. Para ello, describimos nuestra propia estructura MqlTradeRequestSyncLog, la derivada de MqlTradeRequestSync.
const ulong DAYLONG = 60 * 60 * 24; // length of the day in seconds
|
En el constructor, rellenamos todos los campos con valores invariables. En el destructor, registramos campos de consulta y resultado significativos. Obviamente, el destructor de objetos automáticos siempre será llamado en el momento de salida del bloque de código donde se formó y envió la orden, es decir, se imprimirán los datos enviados y recibidos.
En OnInit vamos a realizar algunas comprobaciones de la corrección de las variables de entrada; en concreto, para un tamaño de cuadrícula uniforme.
int OnInit()
|
El principal punto de entrada del algoritmo es el manejador OnTick. En él, por brevedad, omitiremos el mismo mecanismo de gestión de errores basado en TRADE_RETCODE_SEVERITY que en el ejemplo PendingOrderModify.mq5.
Para trabajar barra a barra, la función dispone de una variable estática lastBar, en la que almacenamos la hora de la última barra procesada con éxito. Se omiten todos los ticks posteriores de la misma barra.
void OnTick()
|
En lugar de una elipsis, seguirá el algoritmo principal, dividido en varias funciones auxiliares con fines de sistematización. En primer lugar, determinemos si el periodo de trabajo del día está fijado y, en caso afirmativo, si la estrategia está activada en ese momento. Este atributo se almacena en la variable tradeScheduled.
...
|
Con el trading activado, compruebe en primer lugar si ya existe una red de órdenes mediante la función CheckGrid. Si no hay red, la función devolverá la constante GRID_EMPTY y deberemos crear la red llamando a Setup Grid. Si la red ya se ha construido, tiene sentido comprobar si hay posiciones opuestas que cerrar: de ello se encarga la función CompactPositions.
if(tradeScheduled)
|
Tan pronto como finalice el periodo de trading es necesario eliminar las órdenes y cerrar todas las posiciones (si las hubiera). Esto se hace, respectivamente, mediante las funciones RemoveOrders y CompactPositions function, pero con una bandera booleana (true): este único argumento opcional ordena aplicar un cierre simple para las posiciones restantes después del cierre opuesto.
else
|
Todas las funciones devuelven un código de servidor, cuyo éxito o fracaso se analiza con TradeCodeSeverity. Los códigos de aplicación especial GRID_EMPTY y GRID_OK también se consideran estándar según TRADE_RETCODE_SEVERITY.
#define GRID_OK +1
|
Veamos ahora las funciones una por una.
La función CheckGrid utiliza la clase OrderFilter presentada al principio de esta sección. El filtro solicita todas las órdenes pendientes para el símbolo actual y con «nuestro» número de identificación, y los tickets de las órdenes encontradas se almacenan en el array.
uint CheckGrid()
|
La integridad de la cuadrícula se analiza utilizando la ya conocida clase MapArray, que almacena pares «clave=valor». En este caso, la clave es el nivel (precio convertido en puntos), y el valor es la máscara de bits (superposición) de tipos de orden en el nivel dado. Además, las órdenes Limit y Stop Limit se contabilizan en las variables limits y stops, respectivamente.
// price levels => masks of types of orders existing there
|
Si el número de órdenes de cada tipo coincide y es igual al tamaño de cuadrícula especificado, entonces todo está en orden.
if(limits == stops)
|
La situación en la que el número de órdenes Limit es mayor que las Stop Limit es normal: significa que, debido al movimiento del precio, una o más órdenes Stop Limit se han convertido en Limit. A continuación, el programa debe añadir órdenes Stop Limit en los niveles en los que no haya suficientes. La función RepairGridLevel permite colocar una orden independiente de un tipo específico para un nivel concreto.
if(limits > stops)
|
La situación cuando el número de órdenes Stop Limit es mayor que las Limit se trata como un error (probablemente el servidor omitió el precio por alguna razón).
Alert("Error: Orphaned Stop-Limit orders found");
|
La función RepairGridLevel realiza las siguientes acciones:
uint RepairGridLevel(const ulong level, const double point, const bool buyLimit)
|
Tenga en cuenta que no necesitamos rellenar realmente la estructura (excepto un comentario que puede hacerse más informativo si es necesario) ya que algunos de los campos son rellenados automáticamente por el constructor, y pasamos el volumen y el precio directamente al método sellStopLimit o buyStopLimit.
Un enfoque similar se utiliza en la función SetupGrid, que crea una nueva red completa de órdenes. Al principio de la función preparamos las variables para los cálculos y describimos el array de estructuras MqlTradeRequestSyncLog.
uint SetupGrid()
|
A continuación, generamos órdenes para la mitad inferior y superior de la cuadrícula, divergiendo desde el centro hacia los lados.
for(int i = 0; i < (int)GridSize / 2; ++i)
|
Luego comprobamos si está listo.
for(int i = 0; i < (int)GridSize; ++i)
|
Aunque la comprobación (llamada de completed) se espacia con el envío de órdenes, nuestra estructura sigue utilizando internamente la forma síncrona OrderSend. De hecho, para acelerar el envío de un lote de órdenes (como en nuestro Asesor Experto de cuadrícula), es mejor utilizar la versión asíncrona OrderSendAsync. Pero entonces el estado de ejecución de la orden debe iniciarse desde el manejador de eventos OnTradeTransaction, que estudiaremos más adelante.
Un error en el envío de cualquier orden provoca una salida anticipada del bucle y la devolución del código desde el servidor. Este Asesor Experto de simulación simplemente detendrá su trabajo en caso de error. Para un robot real es deseable proporcionar un análisis intelectual del significado del error y, si es necesario, eliminar todas las órdenes y cerrar posiciones.
Las posiciones generadas por órdenes pendientes se cierran mediante la función CompactPositions.
uint CompactPositions(const bool cleanup = false) |
El parámetro cleanup igual a false por defecto significa una «limpieza» regular de las posiciones dentro del periodo de trading, es decir, el cierre de las posiciones opuestas (si las hay). El valor cleanup=true se utiliza para forzar el cierre de todas las posiciones al final del periodo de trading.
La función rellena los arrays ticketsLong y ticketsShort con tickets de posiciones largas y cortas utilizando una función de ayuda GetMyPositions. Ya hemos utilizado esta última en el ejemplo TradeCloseBy.mq5 de la sección Cierre de posiciones opuestas: total y parcial, La función CloseByPosition de ese ejemplo ha sufrido cambios mínimos en el nuevo Asesor Experto: devuelve un código del servidor en lugar de un indicador lógico de éxito o error.
uint CompactPositions(const bool cleanup = false)
|
La segunda parte de CompactPositions sólo funciona cuando cleanup=true, lo cual está lejos de ser perfecto y se reescribirá en breve.
if(cleanup)
|
Para todas las posiciones restantes encontradas se realiza el cierre habitual llamando a CloseAllPositions.
uint CloseAllPositions(const ulong &tickets[], const int start = 0)
|
Ahora sólo tenemos que considerar la función RemoveOrders. También utiliza el filtro de orden para obtener una lista de las mismas, y luego llama al método remove en un bucle.
uint RemoveOrders()
|
Vamos a comprobar cómo funciona el Asesor Experto en el probador con la configuración por defecto (período de trading de 00:00 a 09:00). A continuación se muestra una captura de pantalla para el lanzamiento en EURUSD, H1:
Estrategia de cuadrícula PendingOrderGrid1.mq5 en el probador
En el registro, además de las entradas periódicas sobre la creación por lotes de varias órdenes (al principio del día) y su eliminación por la mañana, veremos regularmente el restablecimiento de la red (añadiendo órdenes en lugar de las activadas) y el cierre de posiciones.
buy stop limit 0.01 EURUSD at 1.14200 (1.14000) (1.13923 / 1.13923)
|
Ahora es el momento de analizar las funciones de MQL5 para trabajar con posiciones y mejorar su selección y análisis en nuestro Asesor Experto. Ello se abordará en las siguientes secciones.