- 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
Cierre de una posición: total y parcial
Técnicamente, el cierre de una posición puede considerarse una operación de trading opuesta a la utilizada para abrirla. Por ejemplo, para salir de una compra, es necesario realizar una operación de venta (ORDER_TYPE_SELL en el campo type) y para salir de la de venta es necesario comprar (ORDER_TYPE_BUY en el campo type).
El tipo de operación de trading en el campo action de la estructura MqlTradeTransaction sigue siendo el mismo: TRADE_ACTION_DEAL.
En una cuenta de cobertura, la posición que se desea cerrar debe especificarse mediante un ticket en el campo position. Para las cuentas de compensación, sólo puede especificar el nombre del símbolo en el campo symbol, ya que en ellas sólo es posible una posición de símbolo. Sin embargo, también puede cerrar posiciones aquí mediante un ticket.
Para unificar el código, tiene sentido rellenar los campos position y symbol independientemente del tipo de cuenta.
Asegúrese también de ajustar el volumen en el campo volume. Si es igual al volumen de posición, se cerrará completamente. Sin embargo, si se especifica un valor inferior, es posible cerrar sólo una parte de la posición.
En la siguiente tabla, todos los campos obligatorios de la estructura están marcados con un asterisco y los campos opcionales están marcados con un signo más.
Campo |
Compensación |
Cobertura |
|---|---|---|
action |
* |
* |
symbol |
* |
+ |
posición |
+ |
* |
type |
* |
* |
type_filling |
* |
* |
volume |
* |
* |
price |
*' |
*' |
deviation |
± |
± |
magic |
+ |
+ |
comment |
+ |
+ |
El campo price marcado está con un asterisco con un tick porque se requiere sólo para los símbolos con los modos de ejecución Request y Instant), mientras que para la ejecución Exchange y Market, el precio en la estructura no se tiene en cuenta.
Por una razón similar, el campo deviation está marcado con «±». Sólo tiene efecto para los modos Instant y Request.
Para simplificar la implementación programática del cierre de una posición, volvamos a nuestra estructura extendida MqlTradeRequestSync en el archivo MqlTradeSync.mqh. El método para cerrar una posición por ticket tiene el siguiente código:
struct MqlTradeRequestSync: public MqlTradeRequest
|
Aquí comprobamos primero la existencia de una posición llamando a la función PositionSelectByTicket. Además, esta llamada hace que la posición sea seleccionada en el entorno de trading del terminal, lo que permite leer sus propiedades mediante las funciones posteriores. En concreto, averiguamos el símbolo de una posición a partir de la propiedad POSITION_SYMBOL e «invertimos» su tipo de POSITION_TYPE al opuesto para obtener el tipo de orden requerido.
Los tipos de posición de a enumeración ENUM_POSITION_TYPE son POSITION_TYPE_BUY (valor 0) y POSITION_TYPE_SELL (valor 1). En la enumeración de tipos de órdenes ENUM_ORDER_TYPE, las operaciones de mercado ocupan exactamente los mismos valores: ORDER_TYPE_BUY y ORDER_TYPE_SELL. Por eso podemos llevar la primera enumeración a la segunda, y para obtener el sentido opuesto de la operación, basta con conmutar el bit cero mediante la operación OR exclusiva ('^'): obtenemos 1 a partir de 0, y 0 a partir de 1.
Poner a cero el campo price significa seleccionar automáticamente el precio actual correcto (Ask o Bid) antes de enviar la solicitud: esto se hace un poco más tarde, dentro del método de ayuda setVolumePrices, que se llama más adelante en el algoritmo, desde el método market.
La llamada al método _market se produce un par de líneas más abajo. El método _market genera una orden de mercado para todo el volumen o una parte, teniendo en cuenta todos los campos completos de la estructura.
const double total = lot == 0 ? PositionGetDouble(POSITION_VOLUME) : lot;
|
Este fragmento está ligeramente simplificado en comparación con el código fuente actual. El código completo contiene el manejo de una situación rara pero posible cuando el volumen de la posición excede el volumen máximo permitido en una orden por símbolo (propiedad SYMBOL_VOLUME_MAX). En este caso, la posición debe cerrarse por partes, mediante varias órdenes.
También hay que tener en cuenta que como la posición se puede cerrar parcialmente, hemos tenido que añadir un campo a la estructura partial, donde se coloca el saldo previsto del volumen después de la operación. Por supuesto, para un cierre completo, esto será 0. Esta información será necesaria para seguir verificando la realización de la operación.
Para las cuentas de compensación, existe una versión del método close que identifica la posición por el nombre del símbolo: selecciona una posición por símbolo, obtiene su ticket y, a continuación, remite a la versión anterior de close.
bool close(const string name, const double lot = 0)
|
En la estructura MqlTradeRequestSync, tenemos el método completed que proporciona una espera sincrónica para la finalización de la operación, si es necesario. Ahora necesitamos complementarlo para cerrar posiciones, en la rama donde action es igual a TRADE_ACTION_DEAL. Distinguiremos entre abrir una posición y cerrarla por un valor cero en el campo position: no tiene ticket cuando se abre una posición, y tiene uno cuando se cierra.
bool completed()
|
Para comprobar el cierre real de una posición, hemos añadido el método closed en la estructura MqlTradeResultSync. Antes de llamarlo, escribimos el ticket de la posición en el campo result.position para que la estructura de resultados pueda realizar un seguimiento del momento en que el ticket correspondiente desaparece del entorno de trading del terminal, o cuando el volumen es igual a result.partial en caso de cierre parcial.
He aquí el método closed. Se basa en un principio bien conocido: comprobar primero el éxito del código de retorno del servidor y luego esperar con el método wait a que se cumpla alguna condición.
struct MqlTradeResultSync: public MqlTradeResult
|
En este caso, para comprobar la condición para que la posición desaparezca, hemos tenido que implementar una nueva función positionRemoved.
static bool positionRemoved(MqlTradeResultSync &ref)
|
Probaremos el funcionamiento del cierre de posiciones utilizando el Asesor Experto TradeClose.mq5, que implementa una sencilla estrategia de trading: entrar en el mercado si hay dos barras consecutivas en la misma dirección, y en cuanto la siguiente barra cierre en sentido opuesto a la tendencia anterior, salir del mercado. Las señales repetitivas durante tendencias continuas serán ignoradas, es decir, habrá un máximo de una posición (lote mínimo) o ninguna en el mercado.
El Asesor Experto no tendrá ningún parámetro ajustable, a excepción de (Deviation) y un número único (Magic). Los parámetros implícitos son el marco temporal y el símbolo de trabajo del gráfico.
Para rastrear la presencia de una posición ya abierta, utilizamos la función GetMyPosition del ejemplo anterior TradeTrailing.mq5: busca entre las posiciones por símbolo y número de Asesor Experto y devuelve un true lógico si se encuentra una posición adecuada.
También tomamos la función casi sin cambios OpenPosition: abre una posición según el tipo de orden de mercado pasada en el parámetro único. Aquí, este parámetro provendrá del algoritmo de detección de tendencias, y anteriormente (en TrailingStop.mq5) el tipo de orden era establecido por el usuario a través de una variable de entrada.
Una nueva función que implementa el cierre de una posición es ClosePosition. Dado que el archivo de encabezado MqlTradeSync.mqh se hizo cargo de toda la rutina, sólo tenemos que llamar al método request.close(ticket) para el ticket de posición enviado y esperar a que se complete el borrado mediante request.completed().
En teoría, esto último puede evitarse si el Asesor Experto analiza la situación en cada tick. En este caso, un posible problema con la eliminación de la posición se revelará rápidamente en el siguiente tick, y el Asesor Experto puede intentar eliminarla de nuevo. Sin embargo, este Asesor Experto tiene una lógica de trading basada en barras, y por lo tanto no tiene sentido analizar cada tick. A continuación, implementamos un mecanismo especial para trabajar barra por barra y, en este sentido, controlamos de forma sincrónica la eliminación, ya que, de lo contrario, la posición se quedaría «colgada» durante toda una barra.
ulong LastErrorCode = 0;
|
Podríamos obligar a las funciones de ClosePosition a devolver 0 en caso de borrado correcto de la posición, y un código de error en caso contrario. Este enfoque aparentemente eficaz haría que el comportamiento de las dos funciones OpenPosition y ClosePosition fuera diferente: en el código de llamada, sería necesario anidar las llamadas de estas funciones en expresiones lógicas de significado opuesto, lo que introduciría confusión. Además, necesitaríamos la variable global LastErrorCode en cualquier caso, a fin de añadir información sobre el error dentro de la función OpenPosition. Además, la comprobación if(condition) se interpreta más orgánicamente como un éxito que if(!condition).
La función que genera señales de trading según la estrategia anterior se denomina GetTradeDirection.
ENUM_ORDER_TYPE GetTradeDirection()
|
La función devuelve un valor del tipo ENUM_ORDER_TYPE con dos elementos estándar (ORDER_TYPE_BUY y ORDER_TYPE_SELL) que activan compras y ventas, respectivamente. El valor especial -1 (no en la enumeración) se utilizará como señal de cierre.
Para activar el Asesor Experto basado en el algoritmo de trading utilizamos el manejador OnTick. Como recordamos, otras opciones son adecuadas para otras estrategias; por ejemplo, un temporizador para operar en las noticias o eventos de Profundidad de Mercado para trading de volumen.
En primer lugar, analicemos la función de forma simplificada, sin manejar posibles errores. Al principio, hay un bloque que garantiza que el algoritmo posterior sólo se active cuando se abra una nueva barra.
void OnTick()
|
A continuación obtenemos la señal actual de la función GetTradeDirection.
const ENUM_ORDER_TYPE type = GetTradeDirection(); |
Si hay una posición, comprobamos si se ha recibido una señal para cerrarla y llamamos a ClosePosition si es necesario. Si todavía no hay ninguna posición y hay una señal para entrar en el mercado, llamamos a OpenPosition.
if(GetMyPosition(_Symbol, Magic))
|
Para analizar errores, necesitará encerrar las llamadas a OpenPosition y ClosePosition en sentencias condicionales y tomar alguna acción para restaurar el estado de funcionamiento del programa. En el caso más sencillo, basta con repetir la solicitud en el siguiente tick, pero es deseable hacerlo un número limitado de veces. Por lo tanto, crearemos variables estáticas con un contador y un límite de error.
void OnTick()
|
El mecanismo barra a barra se desactiva temporalmente si aparecen errores, ya que conviene superarlos lo antes posible.
Los errores se cuentan en las sentencias condicionales en torno a ClosePosition y OpenPosition.
const ENUM_ORDER_TYPE type = GetTradeDirection();
|
Si se establece la variable errors en 0, se activa de nuevo el mecanismo barra a barra y se detienen los intentos de repetir la solicitud hasta la siguiente barra.
La macro IS_TANGIBLE se define en TradeRetcode.mqh como:
#define IS_TANGIBLE(T) ((T) >= TRADE_RETCODE_ERROR) |
Los errores con códigos más pequeños son operativos, es decir, normales en cierto sentido. Los códigos grandes requieren análisis y diferentes acciones, dependiendo de la causa del problema: parámetros de solicitud incorrectos, prohibiciones permanentes o temporales en el entorno de trading, falta de fondos, etc. Presentaremos un clasificador de errores mejorado en la sección Modificación de orden pendiente.
Vamos a ejecutar el Asesor Experto en el probador en XAUUSD, H1 desde principios de 2022, simulando ticks reales. En el siguiente collage se muestra un fragmento de un gráfico con transacciones, así como la curva de balance.

Resultados de las pruebas TradeClose en XAUUSD, H1
Basándonos en el informe y el registro, podemos ver que la combinación de nuestra sencilla lógica de trading y las dos operaciones de apertura y cierre de posiciones funciona correctamente.
Además de simplemente cerrar una posición, la plataforma admite la posibilidad de un mutuo cierre de dos posiciones opuestas en cuentas de cobertura.