Automatización de estrategias de trading en MQL5 (Parte 4): Creación de un sistema de recuperación de zonas multinivel
Introducción
En el artículo anterior (Parte 3 de la serie), exploramos el sistema RSI de recuperación de zona y demostramos cómo integrar la generación de señales basada en el RSI con un mecanismo dinámico de recuperación de zona para gestionar las operaciones y recuperarse de movimientos desfavorables del mercado utilizando MetaQuotes Language 5 (MQL5). En este artículo (Parte 4), ampliamos esos fundamentos presentando un Sistema de Recuperación de Zona Multinivel, un enfoque sofisticado de gestión de operaciones capaz de manejar múltiples señales independientes simultáneamente.
Este sistema aprovecha el indicador Relative Strength Indicator (RSI) para generar señales de trading e incorpora dinámicamente cada señal en una estructura de matriz, lo que permite una integración perfecta con la lógica de la recuperación de zonas. El objetivo principal es ampliar el mecanismo de recuperación para gestionar múltiples configuraciones comerciales de manera eficiente, reduciendo las pérdidas generales y mejorando los resultados comerciales.
Le guiamos a través del diseño del plan estratégico, la codificación del sistema en MQL5 y la comprobación retrospectiva de su rendimiento. Para facilitar la comprensión y la organización, hemos dividido los pasos en temas de la siguiente manera:
- Plan estratégico
- Implementación en MQL5
- Pruebas retrospectivas
- Conclusión
Al finalizar, comprenderás de forma práctica cómo construir y optimizar un sistema de recuperación de zonas multinivel para una gestión comercial dinámica y robusta.
Plan estratégico
El sistema de recuperación de zonas multinivel utilizará una estructura bien organizada para gestionar eficazmente múltiples señales de negociación. Para lograr esto, definiremos una estructura (struct) que sirve como modelo para la creación de cestas de intercambio individuales. Cada señal de negociación generada por el indicador RSI corresponderá a su cesta única, almacenada como un elemento dentro de una matriz. Por ejemplo, cuando el sistema genera la Señal 1, crearemos la Cesta 1, que no solo almacenará los detalles de la operación inicial, sino que también gestionará todas las posiciones de recuperación asociadas a esa señal. De manera similar, la Señal 2 iniciará la Cesta 2, y esta cesta realizará un seguimiento independiente y ejecutará todas las operaciones de recuperación en función de los parámetros de la Señal 2. Aquí se muestra una visualización de las propiedades de la cesta y la señal.

Cada cesta incluirá datos esenciales como la dirección de la señal (compra o venta), el precio de entrada, los niveles de recuperación, los tamaños de lote calculados dinámicamente y otros parámetros específicos de la operación. A medida que se identifican nuevas señales por parte del RSI, las añadiremos al array, asegurando así que el sistema pueda manejar múltiples señales simultáneamente. Las operaciones de recuperación se calcularán y ejecutarán dinámicamente dentro de sus respectivas cestas, garantizando que cada configuración se gestione de forma independiente y sin interferencias de otras. Aquí tenéis un ejemplo de cómo se manejan las señales por separado.

Al estructurar el sistema de esta manera, garantizaremos un alto grado de escalabilidad y flexibilidad. Cada cesta actuará como una unidad autónoma, lo que permitirá al sistema responder dinámicamente a las condiciones del mercado para cada señal. Este diseño simplificará el seguimiento y la gestión de configuraciones comerciales complejas, ya que cada señal y sus operaciones de recuperación asociadas estarán perfectamente organizadas. El sistema de cestas basado en matrices servirá como base para construir un sistema de recuperación de zona multinivel robusto y adaptable, capaz de gestionar diversos escenarios de negociación manteniendo la eficiencia y la claridad. ¡Comencemos!
Implementación en MQL5
Tras aprender todas las teorías sobre la estrategia de trading de recuperación de zona Multinivel, automatizaremos la teoría y crearemos un asesor experto (EA) en MetaQuotes Language 5 (MQL5) para MetaTrader 5.
Para crear un asesor experto (EA), en su terminal MetaTrader 5, haga clic en la pestaña Herramientas y marque Editor de lenguaje MetaQuotes, o simplemente presione F4 en su teclado. Como alternativa, puede hacer clic en el icono IDE (Integrated Development Environment) en la barra de herramientas. Esto abrirá el entorno MetaQuotes Language Editor (MetaEditor), que permite escribir robots de trading, indicadores técnicos, scripts y bibliotecas de funciones. Una vez abierto MetaEditor, en la barra de herramientas, vaya a la pestaña Archivo y marque Nuevo archivo, o simplemente presione CTRL + N para crear un nuevo documento. Como alternativa, puede hacer clic en el icono Nuevo de la pestaña Herramientas. Esto dará como resultado una ventana emergente del Asistente para MQL.
En el asistente que aparece, marque Asesor experto (plantilla) y haga clic en Siguiente. En las propiedades generales del Asesor Experto, en la sección de nombre, proporcione el nombre del archivo de su experto. Tenga en cuenta que para especificar o crear una carpeta si no existe, debe usar la barra invertida antes del nombre del EA. Por ejemplo, aquí tenemos "Experts" por defecto. Eso significa que nuestro EA se creará en la carpeta de Expertos y podremos encontrarlo allí. Las demás secciones son bastante sencillas, pero puedes seguir el enlace que aparece al final del asistente para saber cómo realizar el proceso con precisión.

Después de proporcionar el nombre de archivo del Asesor Experto deseado, haga clic en Siguiente, haga clic en Siguiente y, a continuación, haga clic en Finalizar. Después de hacer todo eso, ya estamos listos para codificar y programar nuestra estrategia.
En primer lugar, comenzamos definiendo algunos metadatos sobre el Asesor Experto (EA). Esto incluye el nombre del agente de bolsa, la información de derechos de autor y un enlace al sitio web de MetaQuotes. También especificamos la versión del EA, que está configurada en "1.00".
//+------------------------------------------------------------------+ //| 1. Zone Recovery RSI EA Multi-Zone.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00"
Esto mostrará los metadatos del sistema al cargar el programa. A continuación, podemos agregar algunas variables de entrada que se mostrarán en la interfaz de usuario de la siguiente manera.
sinput group "General EA Settings" input double inputlot = 0.01; input double inputzonesizepts = 200; input double inputzonetragetpts = 400; input double inputlotmultiplier = 2.0; input double inputTrailingStopPts = 50; // Trailing stop distance in points input double inputMinimumProfitPts = 50; // Minimum profit points before trailing stop starts input bool inputTrailingStopEnabled = true; // Enable or disable trailing stop
Definimos un grupo de parámetros de entrada bajo la categoría "General EA Settings", lo que permite a los usuarios configurar los ajustes esenciales para el Asesor Experto (EA) antes de ejecutarlo. Estas entradas se declaran utilizando el tipo de datos input en MQL5, lo que permite ajustarlas directamente desde el panel de configuración de entradas del EA sin modificar el código. Cada parámetro de entrada es específico para controlar el comportamiento del EA y la gestión de riesgos.
El parámetro «inputlot» define el tamaño inicial del lote para abrir operaciones, con un valor predeterminado de 0.01, lo que permite un control preciso sobre el volumen de las operaciones. El parámetro «inputzonesizepts» especifica el tamaño de la zona de recuperación en puntos, establecido en 200 de forma predeterminada, que determina la distancia entre las operaciones de recuperación. El parámetro «inputzonetragetpts», con un valor predeterminado de 400, establece la distancia objetivo de beneficio en puntos, lo que guía al EA sobre cuándo cerrar posiciones de forma rentable.
Para gestionar las operaciones de recuperación, utilizamos el parámetro «inputlotmultiplier», establecido en 2.0 de forma predeterminada, lo que permite al EA calcular dinámicamente el aumento del tamaño de los lotes para las operaciones de recuperación en función del multiplicador. Además, se introduce la funcionalidad de trailing stop a través de tres parámetros. El «inputTrailingStopPts» define la distancia de trailing stop en puntos, establecida en 50, que ajusta el stop loss a medida que el mercado se mueve a favor de la operación. El «inputTrailingStopPts» define la distancia de trailing stop en puntos, establecida en 50, que ajusta el stop loss a medida que el mercado se mueve a favor de la operación.
Por último, el parámetro «inputTrailingStopEnabled», de tipo de datos bool, permite a los usuarios habilitar o deshabilitar la función de stop dinámico según sea necesario. Esta flexibilidad garantiza que el EA pueda adaptarse a diferentes estrategias de negociación, perfiles de riesgo y condiciones del mercado, proporcionando un marco personalizable para una gestión eficiente de las operaciones y los riesgos. A continuación, dado que vamos a abrir operaciones, necesitamos incluir algunos archivos adicionales para poder incluir una instancia de operación utilizando #include. Esto nos da acceso a la clase «CTrade», que utilizaremos para crear un objeto de operación. Esto es crucial ya que lo necesitamos para abrir operaciones.
#include <Trade/Trade.mqh> //--- Includes the MQL5 Trade library for handling trading operations.
El preprocesador sustituirá la línea #include <Trade/Trade.mqh> por el contenido del archivo Trade.mqh. Los corchetes angulares indican que el archivo Trade.mqh se tomará del directorio estándar (normalmente es directorio_de_instalación_del_terminal\MQL5\Include). El directorio actual no se incluye en la búsqueda. La línea se puede colocar en cualquier parte del programa, pero normalmente todas las inclusiones se colocan al principio del código fuente, para mejorar la estructura del código y facilitar la consulta. Aquí está el archivo específico, tal como aparece en la sección del navegador.

Después de eso, necesitamos declarar varias variables globales importantes que utilizaremos en el sistema de negociación.
//--- Global variables for RSI logic int rsiPeriod = 14; //--- The period used for calculating the RSI indicator. int rsiHandle; //--- Handle for the RSI indicator, used to retrieve RSI values. double rsiBuffer[]; //--- Array to store the RSI values retrieved from the indicator. datetime lastBarTime = 0; //--- Holds the time of the last processed bar to prevent duplicate signals.
Aquí definimos un conjunto de variables globales para gestionar la lógica del indicador RSI, que impulsará las señales de trading del EA. Estas variables están diseñadas para gestionar de forma eficiente el cálculo, la recuperación y el procesamiento de los valores del RSI. Al declararlas como globales, nos aseguramos de que sean accesibles en toda la EA, lo que permite una generación de señales coherente y eficaz. Establecemos la variable «rsiPeriod», un tipo de datos int, en 14, especificando el período de referencia para calcular el indicador RSI. Este valor determina el número de barras que el EA analizará para calcular los valores del RSI, lo que nos permite controlar la sensibilidad del indicador. A continuación, declaramos «rsiHandle», otra variable int, que utilizaremos para almacenar el identificador del indicador RSI. Este identificador se obtiene cuando inicializamos el RSI utilizando la función iRSI, lo que nos permite recuperar los valores del RSI directamente desde el búfer de indicadores del terminal.
Para almacenar estos valores RSI, creamos «rsiBuffer[]», una matriz dinámica del tipo de datos double. Esta matriz contendrá los valores RSI calculados para cada barra, que utilizaremos para identificar condiciones de sobrecompra o sobreventa en el mercado. Además, definimos la variable «lastBarTime», de tipo «datetime», para almacenar la hora de la última barra procesada. Al realizar un seguimiento de este valor, nos aseguramos de que el EA solo procese una nueva señal cuando aparezca una barra nueva, evitando así señales duplicadas para la misma barra. Ahora podemos definir los parámetros comunes de la «Cesta», en una estructura que vincularemos a cada señal generada. Para lograrlo, utilizamos la lógica struct, cuya sintaxis general es la siguiente:
//--- Struct to track individual position recovery states struct PositionRecovery { //--- Member 1 //--- Member 2 //--- Member 3 //--- Method 1 //... };
Para agrupar variables de datos relacionadas, necesitamos una estructura, y aquí está el prototipo general de la misma. Definimos una struct denominada «PositionRecovery», que sirve como modelo para organizar y gestionar los datos relacionados con los estados de recuperación de posiciones individuales dentro del EA. La struct actúa como un tipo de datos personalizado, lo que nos permite agrupar variables relacionadas (miembros) y funciones (métodos) en una sola entidad.
Explicación de la sintaxis:
"struct" "PositionRecovery { ... };"
Esto declara una estructura denominada «PositionRecovery». La palabra clave struct se utiliza para definir la estructura, y las llaves { ... } encierran los miembros y métodos de la estructura. El punto y coma (;) al final de la definición es obligatorio en MQL5.
- Miembros
Los miembros son variables definidas dentro de la estructura que almacenan datos específicos de cada instancia de «PositionRecovery».
"//--- Member 1": Espacio reservado para una variable, como el tamaño inicial del lote de la operación o el precio de entrada.
"//--- Member 2": Podría representar parámetros como el tamaño de la zona de recuperación o el estado actual de la operación.
"//--- Member 3": Datos adicionales como el número de operaciones de recuperación ejecutadas o un indicador que señale la finalización de la recuperación.
Estos miembros nos permiten recopilar toda la información necesaria para realizar un seguimiento y gestionar el proceso de recuperación individual.
- Métodos
Los métodos son funciones definidas dentro de la estructura que operan sobre sus miembros.
"//--- Method 1": Un marcador de posición para un método, como calcular el siguiente tamaño del lote de recuperación o comprobar si se ha alcanzado el objetivo de recuperación.
Al combinar tanto los datos (miembros) como la lógica (métodos), la estructura se vuelve más versátil y autónoma. Una vez comprendido esto, podemos comenzar a definir los miembros de la estructura.
//--- Struct to track individual position recovery states struct PositionRecovery { CTrade trade; //--- Object to handle trading operations. double initialLotSize; //--- Initial lot size for this position. double currentLotSize; //--- Current lot size in the recovery sequence. double zoneSize; //--- Distance in points defining the recovery zone size. double targetSize; //--- Distance in points defining the profit target range. double multiplier; //--- Lot size multiplier for recovery trades. string symbol; //--- Trading symbol. ENUM_ORDER_TYPE lastOrderType; //--- Type of the last order (BUY or SELL). double lastOrderPrice; //--- Price of the last executed order. double zoneHigh; //--- Upper boundary of the recovery zone. double zoneLow; //--- Lower boundary of the recovery zone. double zoneTargetHigh; //--- Upper boundary of the target range. double zoneTargetLow; //--- Lower boundary of the target range. bool isRecovery; //--- Whether the recovery is active. ulong tickets[]; //--- Array to store tickets of positions associated with this recovery. double trailingStop; //--- Trailing stop level double initialEntryPrice; //--- Initial entry price for trailing stop calculation //--- };
Aquí, creamos una struct denominada «PositionRecovery» para organizar y gestionar todos los datos necesarios para realizar un seguimiento de los estados de recuperación de posición individuales. Al utilizar esta estructura, nos aseguramos de que cada proceso de recuperación se gestione de forma independiente, lo que nos permite gestionar múltiples señales de forma eficaz.
Definimos el objeto «CTrade trade», que utilizaremos para ejecutar operaciones bursátiles como abrir, modificar y cerrar órdenes relacionadas con esta recuperación. Configuramos «initialLotSize» para almacenar el tamaño de la primera operación de la secuencia, mientras que «currentLotSize» nos ayuda a realizar un seguimiento del tamaño del lote de la operación más reciente en el proceso de recuperación. Para controlar la estrategia de recuperación, especificamos la distancia de la zona de recuperación en puntos utilizando «zoneSize» y definimos el rango objetivo de ganancias con «targetSize».
Para gestionar el tamaño dinámico de los lotes, incluimos un «multiplicador», que utilizaremos para calcular el tamaño del lote para cada operación de recuperación posterior. Añadimos «symbol» para identificar el instrumento de negociación para esta recuperación, asegurándonos de que el EA ejecute las operaciones con el símbolo correcto. Utilizamos un tipo de datos de enumeración ENUM_ORDER_TYPE para declarar una variable «lastOrderType» que almacena el tipo de la última orden ejecutada (por ejemplo, COMPRA o VENTA) y «lastOrderPrice» para registrar su precio de ejecución, lo que nos ayuda a realizar un seguimiento del estado actual de la recuperación. Para supervisar las zonas de recuperación, definimos «zoneHigh» y «zoneLow» como los límites superior e inferior de la zona de recuperación, mientras que «zoneTargetHigh» y «zoneTargetLow» marcan el rango objetivo de beneficios.
Para determinar si una recuperación está activa, utilizamos «isRecovery», un indicador que establecemos como verdadero o falso según sea necesario. También incluimos «tickets[]», una matriz en la que almacenamos los números de ticket de todas las operaciones de la secuencia de recuperación, lo que nos permite realizar un seguimiento y gestionarlas individualmente. Por último, incluimos «trailingStop» para especificar la distancia del trailing stop y «initialEntryPrice» para registrar el precio de entrada de la primera operación, que utilizaremos para calcular el trailing stop. Estos componentes nos permiten proteger dinámicamente los beneficios durante la recuperación.
Después de definir las variables miembro, debemos inicializarlas en cada instancia que se cree, o más bien en cada cesta. Para ello, podemos crear un método que gestione la inicialización sin problemas.
//--- Initialize position recovery void Initialize(double lot, double zonePts, double targetPts, double lotMultiplier, string _symbol, ENUM_ORDER_TYPE type, double price) { initialLotSize = lot; //--- Assign initial lot size. currentLotSize = lot; //--- Set current lot size equal to initial lot size. zoneSize = zonePts * _Point; //--- Calculate zone size in points. targetSize = targetPts * _Point; //--- Calculate target size in points. multiplier = lotMultiplier; //--- Assign lot size multiplier. symbol = _symbol; //--- Assign the trading symbol. lastOrderType = type; //--- Set the type of the last order. lastOrderPrice = price; //--- Record the price of the last executed order. isRecovery = false; //--- Set recovery as inactive initially. ArrayResize(tickets, 0); //--- Initialize the tickets array. trailingStop = 0; //--- Initialize trailing stop initialEntryPrice = price; //--- Set initial entry price CalculateZones(); //--- Calculate recovery and target zones. }
Definimos el método «Initialize», que se encarga de configurar todos los parámetros necesarios e inicializar el estado para la recuperación de una posición individual. Este método garantiza que cada instancia de recuperación esté configurada correctamente y lista para gestionar las operaciones de forma dinámica en función de los valores de entrada proporcionados. Comenzamos asignando el valor «lot» a «initialLotSize», que especifica el tamaño de la primera operación en la secuencia de recuperación. Al mismo tiempo, establecemos «currentLotSize» igual a «initialLotSize», ya que la primera operación utiliza el mismo tamaño de lote. A continuación, calculamos el tamaño de la zona de recuperación y el rango objetivo de ganancias en puntos utilizando las entradas «zonePts» y «targetPts», respectivamente, multiplicándolas por la constante «_Point» para tener en cuenta el valor en puntos del símbolo. Estos cálculos definen los umbrales de distancia para gestionar las operaciones de recuperación y sus objetivos.
Asignamos el «lotMultiplier» a la variable «multiplier», que determina cómo aumentará el tamaño del lote en las operaciones de recuperación posteriores. El símbolo de negociación se asigna a «symbol» para garantizar que todas las operaciones de esta instancia de recuperación se ejecuten en el instrumento de mercado correcto. Establecemos «lastOrderType» en el parámetro «type» proporcionado y «lastOrderPrice» en «price», registrando los detalles del pedido más reciente. Estos valores nos ayudan a realizar un seguimiento del estado de la recuperación actual. Además, inicializamos «isRecovery» como falso, lo que indica que el proceso de recuperación no está activo cuando se crea por primera vez.
Cambiamos el tamaño de la matriz «tickets» a cero utilizando la función «ArrayResize», borrando cualquier dato existente y preparándola para almacenar los números de ticket de las operaciones asociadas a esta instancia de recuperación. Para mayor flexibilidad, inicializamos «trailingStop» en 0 y establecemos «initialEntryPrice» en «price», lo que proporciona una base de referencia para los cálculos del trailing stop. Por último, llamamos al método «CalculateZones», que calcula los límites superior e inferior de la zona de recuperación y el rango objetivo. Este paso garantiza que el EA disponga de todos los datos necesarios para gestionar las operaciones de forma eficiente. Mediante el método «Initialize», estableceremos un punto de partida completo y bien definido para cada proceso de recuperación, garantizando que todos los parámetros relevantes estén configurados correctamente para una gestión eficaz de las operaciones. A continuación, podemos definir la función «CalculateZones», que se encarga de calcular los niveles del rango de recuperación.
//--- Calculate dynamic zones and targets void CalculateZones() { if (lastOrderType == ORDER_TYPE_BUY) { //--- If the last order was a BUY... zoneHigh = lastOrderPrice; //--- Set upper boundary at the last order price. zoneLow = zoneHigh - zoneSize; //--- Set lower boundary below the last order price. zoneTargetHigh = zoneHigh + targetSize; //--- Define target range above recovery zone. zoneTargetLow = zoneLow - targetSize; //--- Define target range below recovery zone. } else if (lastOrderType == ORDER_TYPE_SELL) { //--- If the last order was a SELL... zoneLow = lastOrderPrice; //--- Set lower boundary at the last order price. zoneHigh = zoneLow + zoneSize; //--- Set upper boundary above the last order price. zoneTargetLow = zoneLow - targetSize; //--- Define target range below recovery zone. zoneTargetHigh = zoneHigh + targetSize; //--- Define target range above recovery zone. } }
Aquí definimos el método «CalculateZones», que calcula dinámicamente los límites de la zona de recuperación y los rangos de beneficio objetivo en función del tipo de la última orden ejecutada y su precio. Este método garantiza que cada proceso de recuperación tenga niveles claramente definidos para orientar las decisiones comerciales posteriores, lo que permite al sistema reaccionar adecuadamente a los movimientos del mercado.
Comenzamos comprobando el «lastOrderType» para determinar si la orden más reciente fue una COMPRA o una VENTA. Si «lastOrderType» es ORDER_TYPE_BUY, asignamos «zoneHigh» a «lastOrderPrice», estableciendo el límite superior de la zona de recuperación en el precio de entrada de la última orden de COMPRA. El límite inferior, «zoneLow», se calcula restando «zoneSize» (convertido a puntos) de «zoneHigh». Además, definimos el rango objetivo de ganancias: «zoneTargetHigh» se calcula sumando «targetSize» a «zoneHigh», mientras que «zoneTargetLow» se calcula restando «targetSize» de «zoneLow». Estos cálculos garantizan que la zona de recuperación y el rango objetivo de ganancias se posicionen en relación con el precio de la orden de COMPRA.
Si «lastOrderType» es ORDER_TYPE_SELL, invertimos la lógica. En este caso, asignamos «zoneLow» al «lastOrderPrice», estableciendo el límite inferior de la zona de recuperación en el precio de entrada de la última orden de VENTA. El límite superior, «zoneHigh», se calcula sumando «zoneSize» a «zoneLow». Para el rango objetivo de ganancias, calculamos «zoneTargetLow» restando «targetSize» de «zoneLow» y «zoneTargetHigh» sumando «targetSize» a «zoneHigh». Estos límites se establecen en relación con el precio de la orden de VENTA. Estas definiciones de nivel describirían la imagen que se muestra a continuación:

Una vez definidos los niveles de la zona, podemos proceder a abrir las posiciones. Encapsularemos la lógica de apertura de posiciones en un método para facilitar su uso dentro de la estructura del código.
//--- Open a trade with comments for position type bool OpenTrade(ENUM_ORDER_TYPE type, string comment) { if (type == ORDER_TYPE_BUY) { //--- For a BUY order... if (trade.Buy(currentLotSize, symbol, 0, 0, 0, comment)) { //--- Attempt to place a BUY trade. lastOrderType = ORDER_TYPE_BUY; //--- Update the last order type. lastOrderPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Record the current price. ArrayResize(tickets, ArraySize(tickets) + 1); //--- Resize the tickets array. tickets[ArraySize(tickets) - 1] = trade.ResultOrder(); //--- Store the new ticket. CalculateZones(); //--- Recalculate zones. isRecovery = false; //--- Ensure recovery is inactive for initial trade. Print("Opened BUY Position, Ticket: ", tickets[ArraySize(tickets) - 1]); return true; //--- Return success. } } else if (type == ORDER_TYPE_SELL) { //--- For a SELL order... if (trade.Sell(currentLotSize, symbol, 0, 0, 0, comment)) { //--- Attempt to place a SELL trade. lastOrderType = ORDER_TYPE_SELL; //--- Update the last order type. lastOrderPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Record the current price. ArrayResize(tickets, ArraySize(tickets) + 1); //--- Resize the tickets array. tickets[ArraySize(tickets) - 1] = trade.ResultOrder(); //--- Store the new ticket. CalculateZones(); //--- Recalculate zones. isRecovery = false; //--- Ensure recovery is inactive for initial trade. Print("Opened SELL Position, Ticket: ", tickets[ArraySize(tickets) - 1]); return true; //--- Return success. } } return false; //--- If the trade was not placed, return false. }
En esta función booleana «OpenTrade», gestionamos la lógica para abrir una nueva operación de un tipo específico (COMPRA o VENTA) y gestionamos las actualizaciones necesarias en el sistema de recuperación. Esta función garantiza que las operaciones se abran correctamente y que todos los datos relacionados se actualicen para mantener la sincronización con el proceso de recuperación. Cuando el parámetro «type» es ORDER_TYPE_BUY, intentamos abrir una operación de COMPRA utilizando el método «trade.Buy». El método utiliza los parámetros «currentLotSize», «symbol» y «comment» para ejecutar la operación, dejando los niveles de stop loss y take profit en cero (sin especificar), ya que los definimos dinámicamente según los rangos objetivo de la zona. Si la operación de COMPRA se realiza correctamente, actualizamos «lastOrderType» a ORDER_TYPE_BUY, indicando el tipo de la última operación, y «lastOrderPrice» se establece en el precio de mercado actual obtenido mediante la función SymbolInfoDouble con el parámetro «SYMBOL_BID».
A continuación, cambiamos el tamaño de la matriz «tickets» utilizando la función ArrayResize para hacer espacio para la nueva operación, y almacenamos el número de ticket de la operación realizada con éxito utilizando «trade.ResultOrder()». Esto garantiza que todas las operaciones relacionadas con esta instancia de recuperación se rastreen y almacenen de manera eficiente. A continuación, llamamos a la función «CalculateZones» para volver a calcular las zonas de recuperación y objetivo basándonos en la última operación. Por último, establecemos «isRecovery» en falso, ya que se trata de la operación inicial y no forma parte de un proceso de recuperación. Se imprime un mensaje de éxito en el registro y la función devuelve verdadero para indicar que la operación se ha abierto correctamente.
Si el parámetro «type» es «ORDER_TYPE_SELL», seguimos una lógica similar. Se llama al método «trade.Sell» para realizar una operación de VENTA con los parámetros especificados. Si se realiza correctamente, actualizamos «lastOrderType» a ORDER_TYPE_SELL y registramos «lastOrderPrice» como el precio actual de mercado. Se cambia el tamaño de la matriz «tickets» y se almacena el nuevo ticket, tal y como hicimos con una orden de COMPRA. Las zonas se recalculan utilizando «CalculateZones» y «isRecovery» se establece en falso. Se imprime un mensaje de éxito y la función devuelve verdadero.
Si la operación falla para cualquiera de los dos tipos de orden, la función devuelve falso, lo que indica que la operación no se ha realizado correctamente. Esta estructura garantiza que las operaciones se gestionen de forma sistemática y que todos los datos relacionados con la recuperación se actualicen correctamente para una gestión fluida de las operaciones. Una vez abiertas las posiciones y calculados los niveles de zona, podemos continuar gestionando esas zonas en cada tick para abrir las posiciones de recuperación cuando se alcance cualquiera de los niveles.
//--- Manage zone recovery void ManageZones() { double currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Get the current price. if (lastOrderType == ORDER_TYPE_BUY && currentPrice <= zoneLow) { //--- If price drops below the recovery zone for a BUY... double previousLotSize = currentLotSize; //--- Store the current lot size temporarily. currentLotSize *= multiplier; //--- Tentatively increase lot size. if (OpenTrade(ORDER_TYPE_SELL, "Recovery Position")) { //--- Attempt to open a SELL recovery trade. isRecovery = true; //--- Mark recovery as active if trade is successful. } else { currentLotSize = previousLotSize; //--- Revert the lot size if the trade fails. } } else if (lastOrderType == ORDER_TYPE_SELL && currentPrice >= zoneHigh) { //--- If price rises above the recovery zone for a SELL... double previousLotSize = currentLotSize; //--- Store the current lot size temporarily. currentLotSize *= multiplier; //--- Tentatively increase lot size. if (OpenTrade(ORDER_TYPE_BUY, "Recovery Position")) { //--- Attempt to open a BUY recovery trade. isRecovery = true; //--- Mark recovery as active if trade is successful. } else { currentLotSize = previousLotSize; //--- Revert the lot size if the trade fails. } } }
Declaramos una función «ManageZones» para supervisar el precio de mercado de las zonas de recuperación y tomar medidas si el precio se mueve en contra de la operación inicial. En primer lugar, recuperamos el precio actual de mercado utilizando la función SymbolInfoDouble para obtener el último precio de compra. A continuación, comprobamos si el precio se ha movido fuera de los límites de la zona de recuperación, que se define mediante «zoneLow» para una orden de COMPRA y «zoneHigh» para una orden de VENTA.
Si la última orden fue una COMPRA (indicada por «lastOrderType» == ORDER_TYPE_BUY) y el precio actual cae por debajo de «zoneLow», aumentamos el tamaño del lote para la operación de recuperación. Almacenamos el tamaño actual del lote en «previousLotSize» y, a continuación, multiplicamos «currentLotSize» por el «multiplicador» para aumentarlo. Después de eso, intentamos abrir una operación de recuperación de VENTA utilizando la función «OpenTrade». Si la operación de recuperación se realiza correctamente, establecemos «isRecovery» en verdadero para indicar que la recuperación está activa. Si la operación falla, revertimos el tamaño del lote al valor original almacenado en «previousLotSize».
Del mismo modo, si la última orden fue una VENTA (indicada por «lastOrderType» == ORDER_TYPE_SELL) y el precio sube por encima de «zoneHigh», aplicamos la misma lógica para abrir una operación de recuperación de COMPRA. El tamaño del lote ha aumentado y estamos intentando abrir la operación de COMPRA. Si tiene éxito, «isRecovery» se establece en verdadero, pero si la operación falla, el tamaño del lote se revierte. Esto garantiza que el sistema gestione eficazmente las operaciones de recuperación, ajustando el tamaño de las posiciones y tomando medidas correctivas en función de las condiciones del mercado. Por último, debemos cerrar las posiciones cuando el precio alcance los niveles objetivo definidos, por lo que necesitaremos una función que gestione esa lógica.
//--- Check and close trades at targets void CheckCloseAtTargets() { double currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); //--- Get the current price. if (lastOrderType == ORDER_TYPE_BUY && currentPrice >= zoneTargetHigh) { //--- If price reaches the target for a BUY... ClosePositionsAtTarget(); //--- Close positions that meet the target criteria. } else if (lastOrderType == ORDER_TYPE_SELL && currentPrice <= zoneTargetLow) { //--- If price reaches the target for a SELL... ClosePositionsAtTarget(); //--- Close positions that meet the target criteria. } }
Aquí definimos una función nula «CheckCloseAtTargets» para comprobar si el precio de mercado ha alcanzado los niveles objetivo predefinidos y cerrar las posiciones que cumplen los criterios objetivo. En primer lugar, recuperamos el precio de compra actual del mercado utilizando «SymbolInfoDouble (symbol, SYMBOL_BID)». A continuación, comparamos este precio con los niveles objetivo definidos por «zoneTargetHigh» para una orden de COMPRA y «zoneTargetLow» para una orden de VENTA.
Si la última operación fue una COMPRA (indicada por «lastOrderType» == ORDER_TYPE_BUY) y el precio actual sube hasta o por encima de «zoneTargetHigh», consideramos que la posición ha alcanzado el objetivo de beneficio deseado. En este caso, llamamos a la función «ClosePositionsAtTarget» para cerrar cualquier posición que cumpla los criterios establecidos. Del mismo modo, si la última orden fue una VENTA (indicada por «lastOrderType» == ORDER_TYPE_SELL) y el precio cae hasta o por debajo de «zoneTargetLow», el sistema vuelve a llamar a «ClosePositionsAtTarget» para cerrar las posiciones. Esta función garantiza que las operaciones se cierren cuando el mercado alcance el objetivo de beneficio designado, asegurando las ganancias y finalizando el proceso de recuperación.
Para cerrar las posiciones, utilizamos una función llamada «ClosePositionsAtTarget» para poder reutilizarla. Aquí está el fragmento de función.
//--- Close positions that have reached the target void ClosePositionsAtTarget() { for (int i = ArraySize(tickets) - 1; i >= 0; i--) { //--- Iterate through all tickets. ulong ticket = tickets[i]; //--- Get the position ticket. int retries = 10; //--- Set retry count. while (retries > 0) { //--- Retry until successful or retries exhausted. if (trade.PositionClose(ticket)) { //--- Attempt to close the position. Print("CLOSED # ", ticket, " Trailed and closed: ", (trailingStop != 0)); ArrayRemove(tickets, i); //--- Remove the ticket from the array on success. retries = 0; //--- Exit the loop on success. } else { retries--; //--- Decrement retries on failure. Sleep(100); //--- Wait before retrying. } } } if (ArraySize(tickets) == 0) { //--- If all tickets are closed... Reset(); //--- Reset recovery state after closing the target positions. } }
En la función «ClosePositionsAtTarget», iteramos a través de todas las posiciones abiertas almacenadas en la matriz «tickets» e intentamos cerrar aquellas que han alcanzado los niveles objetivo. Comenzamos recorriendo la matriz «tickets» en orden inverso para asegurarnos de no omitir ninguna posición al eliminarlas después del cierre. Para cada ticket, establecemos un recuento de reintentos de «retries» para garantizar que, si una posición no se cierra en el primer intento, el sistema lo vuelva a intentar.
Para cada posición, intentamos cerrarla utilizando la función «trade.PositionClose(ticket)». Si la posición se cierra con éxito, imprimimos un mensaje indicando que el ticket se ha cerrado y si era trailing o no, utilizando «trailingStop != 0» para comprobar si se aplicó un trailing stop. Una vez cerrada la posición, eliminamos el ticket de la matriz «tickets» utilizando la función ArrayRemove y salimos del bucle de reintentos estableciendo «retries» en 0. Si la posición no se cierra, disminuimos el contador de «reintentos», esperamos un breve periodo de tiempo utilizando la función Sleep y, a continuación, intentamos cerrar la posición de nuevo, asegurándonos de no sobrecargar la función.
Después de intentar cerrar todas las posiciones, comprobamos si la matriz «tickets» está vacía utilizando la función ArraySize. Si todas las posiciones están cerradas, llamamos a la función «Reset» para restablecer el estado de recuperación, borrando cualquier dato restante relacionado con la recuperación y preparándonos para futuras operaciones. Eso es todo. Sin embargo, dado que en la mayoría de los casos no estamos seguros de que el mercado alcance nuestros niveles objetivo, podemos mejorar el sistema siguiendo las posiciones que alcanzan nuestro beneficio mínimo en lugar de tener que esperar hasta que se alcancen dichos niveles. Esta lógica se encuentra nuevamente en un método.
//--- Apply trailing stop logic to initial positions void ApplyTrailingStop() { if (inputTrailingStopEnabled && ArraySize(tickets) == 1) { // Ensure trailing stop is enabled and there is only one position (initial position) ulong ticket = tickets[0]; // Get the ticket of the initial position double entryPrice = GetPositionEntryPrice(ticket); // Get the entry price of the position by ticket double currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); // Get the current price double newTrailingStop; if (lastOrderType == ORDER_TYPE_BUY) { if (currentPrice > entryPrice + (inputMinimumProfitPts + inputTrailingStopPts) * _Point) { newTrailingStop = currentPrice - inputTrailingStopPts * _Point; // Calculate new trailing stop for BUY if (newTrailingStop > trailingStop) { trailingStop = newTrailingStop; // Update trailing stop if the new one is higher Print("Trailing BUY Position, Ticket: ", ticket, " New Trailing Stop: ", trailingStop); } } if (trailingStop != 0 && currentPrice <= trailingStop) { Print("Trailed and closing BUY Position, Ticket: ", ticket); ClosePositionsAtTarget(); // Close position if the price falls below the trailing stop } } else if (lastOrderType == ORDER_TYPE_SELL) { if (currentPrice < entryPrice - (inputMinimumProfitPts + inputTrailingStopPts) * _Point) { newTrailingStop = currentPrice + inputTrailingStopPts * _Point; // Calculate new trailing stop for SELL if (newTrailingStop < trailingStop) { trailingStop = newTrailingStop; // Update trailing stop if the new one is lower Print("Trailing SELL Position, Ticket: ", ticket, " New Trailing Stop: ", trailingStop); } } if (trailingStop != 0 && currentPrice >= trailingStop) { Print("Trailed and closing SELL Position, Ticket: ", ticket); ClosePositionsAtTarget(); // Close position if the price rises above the trailing stop } } } }
En el método vacío «ApplyTrailingStop» que definimos, implementamos la lógica de trailing stop para la posición inicial basándonos en si los trailing stops están habilitados y si solo hay una posición activa. En primer lugar, comprobamos si la función de stop dinámico está habilitada utilizando «inputTrailingStopEnabled» y si solo hay una posición abierta (lo cual se garantiza mediante «ArraySize(tickets) == 1»). A continuación, recuperamos el ticket de la posición inicial y lo utilizamos para obtener el precio de entrada mediante la función «GetPositionEntryPrice». También obtenemos el precio actual de mercado utilizando la función SymbolInfoDouble.
Para una posición de COMPRA, comprobamos si el precio actual se ha movido por encima del precio de entrada en una cantidad específica (teniendo en cuenta tanto el beneficio mínimo como la distancia del trailing stop, calculada con «inputMinimumProfitPts + inputTrailingStopPts») y establecemos el nuevo trailing stop en consecuencia. Si el trailing stop calculado es superior al «trailingStop» actual, actualizamos el valor del trailing stop e imprimimos un mensaje indicando el nuevo nivel de trailing stop. Si el precio actual cae hasta el nivel de stop dinámico o por debajo de él, cerramos la posición utilizando la función «ClosePositionsAtTarget».
Para una posición de VENTA, seguimos un proceso similar, pero a la inversa. Comprobamos si el precio actual está por debajo del precio de entrada en una determinada cantidad y, si es necesario, ajustamos el trailing stop a la baja. Si el trailing stop calculado es inferior al «trailingStop» actual, actualizamos el trailing stop e imprimimos un mensaje indicando el nuevo nivel. Si el precio actual sube hasta alcanzar o superar el trailing stop, la posición se cierra. Esta función garantiza que el trailing stop se aplique de forma dinámica en función de las condiciones del mercado, lo que permite asegurar las ganancias y proteger la posición frente a pérdidas significativas. Si el precio se mueve favorablemente, se ajusta el trailing stop; si el precio se invierte y alcanza el trailing stop, se cierra la posición.
Quizás hayas notado que utilizamos una función personalizada para obtener los precios de entrada. Esta es la lógica de la función.
//--- Get the entry price of a position by ticket double GetPositionEntryPrice(ulong ticket) { if (PositionSelectByTicket(ticket)) { return PositionGetDouble(POSITION_PRICE_OPEN); } else { Print("Failed to select position by ticket: ", ticket); return 0.0; } }
Aquí definimos la función «GetPositionEntryPrice», que recupera el precio de entrada de una posición utilizando el número de ticket proporcionado. En primer lugar, intentamos seleccionar la posición asociada al ticket dado utilizando la función PositionSelectByTicket. Si la posición se selecciona correctamente, recuperamos el precio de entrada de la posición llamando a «PositionGetDouble (POSITION_PRICE_OPEN)», lo que nos da el precio al que se abrió la posición. Si no se puede seleccionar la posición (por ejemplo, si el ticket no es válido o la posición ya no existe), imprimimos un mensaje de error indicando el fallo y devolvemos un valor de 0.0 para indicar que no se ha podido recuperar el precio de entrada.
Ahora, tras abrir y cerrar las posiciones, debemos restablecer el sistema y eliminar la cesta de operaciones asociada como método de limpieza. Así es como gestionamos esa lógica de limpieza, en una función «Reset».
//--- Reset recovery state void Reset() { currentLotSize = inputlot; //--- Reset lot size to initial value. lastOrderType = -1; //--- Clear the last order type. lastOrderPrice = 0.0; //--- Reset the last order price. isRecovery = false; //--- Mark recovery as inactive. ArrayResize(tickets, 0); //--- Clear the tickets array. trailingStop = 0; //--- Reset trailing stop initialEntryPrice = 0.0; //--- Reset initial entry price Print("Strategy BASKET reset after closing trades."); }
En la función «Reset», restablecemos el estado de recuperación para prepararnos para un nuevo ciclo comercial. En primer lugar, restablecemos «currentLotSize» al valor inicial definido por «inputlot», asegurándonos de que el tamaño del lote se restablece al importe inicial definido por el usuario. También borramos los detalles del último pedido estableciendo «lastOrderType» en -1 (lo que indica que no hay ningún tipo de pedido activo) y restablecemos «lastOrderPrice» a 0.0, eliminando así cualquier información sobre el precio de pedidos anteriores.
A continuación, marcamos la recuperación como inactiva estableciendo «isRecovery» en falso, lo que garantiza que no se aplicará ninguna lógica de recuperación cuando se produzca el restablecimiento. A continuación, borramos la matriz «tickets» utilizando la función ArrayResize, eliminando todos los tickets de posición almacenados que formaban parte del proceso de recuperación anterior. Además, restablecemos el «trailingStop» a 0 y el «initialEntryPrice» a 0,0, borrando cualquier configuración de trailing stop y valores de precio de entrada de operaciones anteriores. Por último, imprimimos el mensaje «Strategy BASKET reset after closing trades» para notificar que el restablecimiento se ha completado y que el estado de recuperación se ha borrado. Esta función garantiza que el sistema se encuentre en un estado limpio y listo para el siguiente ciclo comercial.
Después de definir las propiedades de la estructura, ahora estamos listos para generar señales y añadirlas a la estructura definida. Sin embargo, dado que tendremos que gestionar muchas señales dinámicas, necesitaremos definir una estructura de matriz, que actuará como una cesta global en la que definiremos las subcestas para cada señal generada. Así es como lo conseguimos.
//--- Dynamic list to track multiple positions PositionRecovery recoveryArray[]; //--- Dynamic array for recovery instances.
Aquí declaramos una matriz dinámica denominada «recoveryArray», diseñada para realizar un seguimiento y gestionar múltiples instancias de recuperación de posiciones. La matriz se basa en la estructura «PositionRecovery», lo que le permite almacenar estados de recuperación individuales para múltiples operaciones de forma independiente. Cada elemento de la matriz representa una configuración de recuperación distinta, completa con todos los atributos relevantes, como el tamaño del lote, los límites de la zona y los tickets comerciales asociados.
Al hacer que la matriz sea dinámica, podemos ampliarla o reducirla según sea necesario durante el tiempo de ejecución utilizando funciones como ArrayResize. Esto nos permite añadir dinámicamente nuevas instancias de recuperación para nuevas señales de trading o eliminar recuperaciones completadas, lo que garantiza un uso eficiente de la memoria y la adaptabilidad a diferentes escenarios de trading. Este enfoque es esencial para gestionar múltiples operaciones simultáneamente, ya que permite que la lógica de recuperación de cada operación funcione de forma independiente dentro de su propia «cesta» de datos.
Después de definir la matriz, ahora podemos comenzar con la lógica de generación de señales. Tendremos que inicializar el identificador del indicador en el controlador de eventos OnInit, que se llama cada vez que se inicializa el programa.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { rsiHandle = iRSI(_Symbol, PERIOD_CURRENT, rsiPeriod, PRICE_CLOSE); //--- Create RSI indicator handle. if (rsiHandle == INVALID_HANDLE) { //--- Check if handle creation failed. Print("Failed to create RSI handle. Error: ", GetLastError()); //--- Print error message. return(INIT_FAILED); //--- Return initialization failure. } ArraySetAsSeries(rsiBuffer, true); //--- Set RSI buffer as a time series. Print("Multi-Zone Recovery Strategy initialized."); //--- Log initialization success. return(INIT_SUCCEEDED); //--- Return initialization success. }
En la función del controlador de eventos OnInit, inicializamos los componentes esenciales necesarios para que funcione el Asesor Experto (EA). Comenzamos creando un indicador RSI utilizando la función iRSI, que calcula el índice de fuerza relativa para el símbolo y el período actuales. Este controlador permite al EA acceder a los valores RSI de forma dinámica. Si falla la creación del identificador, lo que se indica con el valor INVALID_HANDLE, registramos un mensaje de error con detalles utilizando la función Print y devolvemos INIT_FAILED para finalizar el proceso de inicialización. A continuación, configuramos la matriz «rsiBuffer» como una serie temporal utilizando ArraySetAsSeries, asegurándonos de que los datos estén organizados cronológicamente para un procesamiento preciso. Una vez completada la inicialización, imprimimos un mensaje de confirmación indicando que la estrategia está lista y devolvemos INIT_SUCCEEDED para indicar que el EA está listo para funcionar. A continuación, en el controlador de eventos OnDeinit, destruimos el identificador para ahorrar recursos.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if (rsiHandle != INVALID_HANDLE) //--- Check if RSI handle is valid. IndicatorRelease(rsiHandle); //--- Release the RSI handle. Print("Multi-Zone Recovery Strategy deinitialized."); //--- Log deinitialization. }
Aquí nos encargamos de la limpieza y la gestión de recursos del Asesor Experto (EA) cuando se elimina o desactiva. Primero comprobamos si el «rsiHandle» del indicador RSI es válido asegurándonos de que no sea igual a «INVALID_HANDLE». Si el identificador es válido, lo liberamos utilizando la función IndicatorRelease para liberar recursos y evitar fugas de memoria. Por último, registramos un mensaje utilizando Print para indicar que la estrategia de recuperación multizona se ha desinicializado correctamente. Esta función garantiza un cierre limpio y ordenado del programa, sin dejar recursos ni procesos pendientes.
A continuación, podemos pasar al controlador de eventos OnTick, que gestionará toda la lógica principal del sistema utilizando la estructura definida anteriormente. Primero tendremos que recuperar los datos del indicador para poder utilizarlos en análisis posteriores.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if (CopyBuffer(rsiHandle, 0, 1, 2, rsiBuffer) <= 0) { //--- Copy the RSI buffer values. Print("Failed to copy RSI buffer. Error: ", GetLastError()); //--- Print error on failure. return; //--- Exit on failure. } //--- }
Aquí, en la función OnTick, gestionamos la lógica que se ejecuta en cada nuevo tick, que representa una actualización del precio del símbolo de negociación. El primer paso consiste en copiar los valores del indicador RSI en la matriz «rsiBuffer» utilizando la función CopyBuffer. Especificamos «rsiHandle» para identificar el indicador RSI, establecemos el índice del búfer en 0 y solicitamos dos valores a partir de la barra más reciente. Si la operación falla (es decir, el valor devuelto es menor o igual a 0), imprimimos un mensaje de error utilizando Print para notificar al usuario el problema e incluimos los detalles del error obtenidos de la función GetLastError. Después de registrar el error, salimos inmediatamente de la función utilizando return. Esto garantiza que el resto de la lógica no se ejecute si falla la recuperación de datos del RSI, lo que mantiene la integridad y la estabilidad del Asesor Experto.
Si recuperamos los datos correctamente, podemos utilizarlos para la lógica de generación de señales de la siguiente manera.
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0); //--- Get the time of the current bar. if (currentBarTime != lastBarTime) { //--- Check if a new bar has formed. lastBarTime = currentBarTime; //--- Update the last processed bar time. if (rsiBuffer[1] > 30 && rsiBuffer[0] <= 30) { //--- Check for oversold RSI crossing up. Print("BUY SIGNAL"); PositionRecovery newRecovery; //--- Create a new recovery instance. newRecovery.Initialize(inputlot, inputzonesizepts, inputzonetragetpts, inputlotmultiplier, _Symbol, ORDER_TYPE_BUY, SymbolInfoDouble(_Symbol, SYMBOL_BID)); //--- Initialize the recovery. newRecovery.OpenTrade(ORDER_TYPE_BUY, "Initial Position"); //--- Open an initial BUY position. ArrayResize(recoveryArray, ArraySize(recoveryArray) + 1); //--- Resize the recovery array. recoveryArray[ArraySize(recoveryArray) - 1] = newRecovery; //--- Add the new recovery to the array. } else if (rsiBuffer[1] < 70 && rsiBuffer[0] >= 70) { //--- Check for overbought RSI crossing down. Print("SELL SIGNAL"); PositionRecovery newRecovery; //--- Create a new recovery instance. newRecovery.Initialize(inputlot, inputzonesizepts, inputzonetragetpts, inputlotmultiplier, _Symbol, ORDER_TYPE_SELL, SymbolInfoDouble(_Symbol, SYMBOL_BID)); //--- Initialize the recovery. newRecovery.OpenTrade(ORDER_TYPE_SELL, "Initial Position"); //--- Open an initial SELL position. ArrayResize(recoveryArray, ArraySize(recoveryArray) + 1); //--- Resize the recovery array. recoveryArray[ArraySize(recoveryArray) - 1] = newRecovery; //--- Add the new recovery to the array. } }
Aquí, nos centramos en detectar nuevas barras y generar señales de negociación basadas en el cruce del indicador por ciertos niveles. En primer lugar, recuperamos la hora de la barra actual utilizando la función iTime, que se almacena en la variable «currentBarTime». A continuación, comparamos «currentBarTime» con «lastBarTime» para comprobar si se ha formado una nueva barra. Si los dos valores difieren, significa que se ha formado una nueva barra, así que actualizamos «lastBarTime» al valor de «currentBarTime» para evitar procesar la misma barra varias veces.
A continuación, evaluamos las condiciones para las señales basadas en el RSI. Si el valor del RSI en «rsiBuffer[1]» (barra anterior) es superior a 30 y el valor actual en «rsiBuffer[0]» (barra actual) es inferior o igual a 30, significa que hay una condición de sobreventa con un cruce al alza. En este caso, imprimimos un mensaje «BUY SIGNAL» (SEÑAL DE COMPRA) e iniciamos una nueva instancia «PositionRecovery» denominada «newRecovery». A continuación, llamamos al método «Initialize» de «newRecovery» para configurar los parámetros de recuperación, incluidos «inputlot», «inputzonesizepts», «inputzonetragetpts», «inputlotmultiplier», el símbolo y el tipo de orden como ORDER_TYPE_BUY y el precio de compra actual de la función «SymbolInfoDouble». Tras la inicialización, abrimos una posición inicial «BUY» utilizando el método «OpenTrade», pasando «ORDER_TYPE_BUY» y un comentario descriptivo.
Del mismo modo, si el valor «RSI» en «rsiBuffer[1]» es inferior a 70 y el valor actual en «rsiBuffer[0]» es superior o igual a 70, indica una condición de sobrecompra con un cruce a la baja. En este escenario, imprimimos un mensaje «SIGNAL DE VENTA» y creamos una nueva instancia «PositionRecovery». Después de inicializarlo con los mismos parámetros, pero estableciendo el tipo de orden en ORDER_TYPE_SELL, abrimos una posición inicial «SELL» utilizando el método «OpenTrade».
Por último, tanto para las señales «BUY» como «SELL», añadimos la instancia «PositionRecovery» inicializada a «recoveryArray». El tamaño de la matriz se modifica mediante la función ArrayResize, y la nueva instancia se asigna a la última posición de la matriz, lo que garantiza que cada recuperación se rastree de forma independiente. Esta lógica es ahora responsable de iniciar las cestas de posiciones con posiciones y condiciones iniciales. Para gestionar la gestión de posiciones, tendremos que recorrer las cestas de la cesta principal y aplicar la lógica de gestión como en la estructura principal en cada tick. Esta es la lógica.
for (int i = 0; i < ArraySize(recoveryArray); i++) { //--- Iterate through all recovery instances. recoveryArray[i].ManageZones(); //--- Manage zones for each recovery instance. recoveryArray[i].CheckCloseAtTargets(); //--- Check and close positions at targets. recoveryArray[i].ApplyTrailingStop(); //--- Apply trailing stop logic to initial positions. }
Para gestionar las posiciones de forma independiente, utilizamos un bucle for para iterar a través de todas las instancias de recuperación almacenadas en «recoveryArray». Este bucle garantiza que cada instancia de recuperación se gestione por separado, lo que permite al sistema mantener un control independiente sobre múltiples escenarios de recuperación. El bucle comienza con el índice «i» establecido en 0 y continúa hasta que se hayan procesado todos los elementos de «recoveryArray», según lo determinado por la función ArraySize.
Dentro del bucle, se llaman tres métodos esenciales en cada instancia de recuperación. En primer lugar, el método «ManageZones» se invoca utilizando el operador de punto, que supervisa los movimientos de precios en relación con las zonas de recuperación definidas. Si el precio sale de los límites de la zona, este método actúa intentando abrir una posición de recuperación, ajustando dinámicamente el tamaño del lote según el multiplicador especificado.
A continuación, se ejecuta el método «CheckCloseAtTargets» para evaluar si el precio ha alcanzado los niveles objetivo para la instancia de recuperación. Si se cumplen las condiciones objetivo, este método cierra todas las posiciones asociadas y restablece la instancia de recuperación, lo que garantiza que se aseguren las ganancias y que la instancia esté lista para un nuevo ciclo.
Por último, se aplica el método «ApplyTrailingStop», que impone la lógica de trailing stop para la posición inicial de la instancia de recuperación. Este método ajusta dinámicamente el nivel del stop dinámico a medida que el precio se mueve favorablemente, asegurando las ganancias. Si el precio se invierte y alcanza el trailing stop, el método garantiza el cierre de la posición, protegiendo así contra posibles pérdidas.
Al procesar cada instancia de recuperación de esta manera, el sistema gestiona eficazmente múltiples posiciones independientes, lo que garantiza que todos los escenarios de recuperación se gestionen de forma dinámica y en consonancia con las estrategias predefinidas. Para asegurarnos de que el programa funciona correctamente, lo ejecutamos y este es el resultado.

En la imagen podemos ver que la estrategia se restableció tras cerrar una instancia de recuperación, lo cual es uno de los principales objetivos del sistema. Sin embargo, el cierre no interfiere con las demás instancias en ejecución, lo que significa que la instancia se gestiona de forma independiente al resto de instancias de la matriz. Para confirmarlo, pasamos a la pestaña «Trade» y vemos que hay instancias activas.

En la imagen podemos ver que todavía hay instancias de recuperación activas y que dos de ellas ya se encuentran en modo de recuperación. Se distinguen perfectamente por los comentarios añadidos en la sección derecha, que indican si se trata de posiciones iniciales o de recuperación. Esto confirma que hemos logrado nuestro objetivo con éxito, y lo que queda por hacer es realizar una prueba retrospectiva del programa y analizar su rendimiento. Esto se trata en la siguiente sección.
Pruebas retrospectivas
Para evaluar el rendimiento y la solidez del programa, primero debemos simular las condiciones históricas del mercado. De este modo, podemos determinar cómo gestiona el programa los escenarios de recuperación, se ajusta a los movimientos de los precios y gestiona las operaciones. El backtest nos proporciona información importante sobre la rentabilidad de la estrategia, los niveles de drawdown y la gestión del riesgo. El programa genera señales de compra y venta basadas en los umbrales establecidos (por ejemplo, sobrevendido en 30 y sobrecomprado en 70, según prefiera el usuario) mediante el procesamiento de datos históricos de precios tick por tick para replicar las condiciones reales del mercado. Cuando se genera una señal, el EA inicializa una nueva instancia de recuperación, ejecuta la primera operación y realiza un seguimiento de los movimientos de precios dentro de las zonas de recuperación designadas.
Probamos rigurosamente el mecanismo de recuperación dinámica del sistema, que ajusta el tamaño de los lotes utilizando el multiplicador y abre posiciones de cobertura cuando es necesario, en diversas condiciones de mercado. El programa evalúa los escenarios de recuperación de forma independiente para cada señal, lo que garantiza que todas las operaciones se gestionen de forma aislada, tal y como se refleja en el manejo de «recoveryArray». Esto garantiza que, incluso con múltiples instancias de recuperación activas, la estrategia siga siendo organizada y adaptable. Probamos el programa durante los últimos 5 meses utilizando la siguiente configuración:

Una vez finalizado, hemos obtenido los siguientes resultados:
Gráfico del Probador de estrategias:

Informe del Probador de estrategias:

A partir de las imágenes anteriores, podemos ver que los gráficos son suaves, aunque presentan irregularidades cuando existe una correlación entre el saldo y el patrimonio, causada por el continuo aumento en el número de niveles de recuperación que el programa ejecuta por instancia y el número de señales generadas. De este modo, podemos limitar el número de posiciones de recuperación habilitando la función de parada de seguimiento. Estos son los resultados que obtenemos.

Como se puede observar en la imagen, el número de operaciones se reduce y la tasa de aciertos aumenta cuando se activa la función de trailing stop. Podemos limitar aún más el número de posiciones incorporando una lógica de restricción del número de operaciones, de modo que cuando ya existan varias posiciones abiertas, no se considere la apertura de más órdenes basadas en las señales generadas. Para lograr esto, definimos variables de entrada adicionales de la siguiente manera:
input bool inputEnablePositionsRestriction = true; // Enable Maximum positions restriction input int inputMaximumPositions = 11; // Maximum number of positions
Estas variables de entrada contienen un indicador para habilitar o deshabilitar la opción de restricción y la segunda contiene el número máximo de posiciones que se pueden iniciar en el sistema cuando la restricción está habilitada. Luego adoptamos la lógica en elEnTick Se activa un controlador de eventos cuando se confirma una señal, añadiendo una capa adicional de restricción en las operaciones.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if (CopyBuffer(rsiHandle, 0, 1, 2, rsiBuffer) <= 0) { //--- Copy the RSI buffer values. Print("Failed to copy RSI buffer. Error: ", GetLastError()); //--- Print error on failure. return; //--- Exit on failure. } datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0); //--- Get the time of the current bar. if (currentBarTime != lastBarTime) { //--- Check if a new bar has formed. lastBarTime = currentBarTime; //--- Update the last processed bar time. if (rsiBuffer[1] > 30 && rsiBuffer[0] <= 30) { //--- Check for oversold RSI crossing up. Print("BUY SIGNAL"); if (inputEnablePositionsRestriction == false || inputMaximumPositions > PositionsTotal()){ PositionRecovery newRecovery; //--- Create a new recovery instance. newRecovery.Initialize(inputlot, inputzonesizepts, inputzonetragetpts, inputlotmultiplier, _Symbol, ORDER_TYPE_BUY, SymbolInfoDouble(_Symbol, SYMBOL_BID)); //--- Initialize the recovery. newRecovery.OpenTrade(ORDER_TYPE_BUY, "Initial Position"); //--- Open an initial BUY position. ArrayResize(recoveryArray, ArraySize(recoveryArray) + 1); //--- Resize the recovery array. recoveryArray[ArraySize(recoveryArray) - 1] = newRecovery; //--- Add the new recovery to the array. } else { Print("FAILED: Maximum positions threshold hit!"); } } else if (rsiBuffer[1] < 70 && rsiBuffer[0] >= 70) { //--- Check for overbought RSI crossing down. Print("SELL SIGNAL"); if (inputEnablePositionsRestriction == false || inputMaximumPositions > PositionsTotal()){ PositionRecovery newRecovery; //--- Create a new recovery instance. newRecovery.Initialize(inputlot, inputzonesizepts, inputzonetragetpts, inputlotmultiplier, _Symbol, ORDER_TYPE_SELL, SymbolInfoDouble(_Symbol, SYMBOL_BID)); //--- Initialize the recovery. newRecovery.OpenTrade(ORDER_TYPE_SELL, "Initial Position"); //--- Open an initial SELL position. ArrayResize(recoveryArray, ArraySize(recoveryArray) + 1); //--- Resize the recovery array. recoveryArray[ArraySize(recoveryArray) - 1] = newRecovery; //--- Add the new recovery to the array. } else { Print("FAILED: Maximum positions threshold hit!"); } } } for (int i = 0; i < ArraySize(recoveryArray); i++) { //--- Iterate through all recovery instances. recoveryArray[i].ManageZones(); //--- Manage zones for each recovery instance. recoveryArray[i].CheckCloseAtTargets(); //--- Check and close positions at targets. recoveryArray[i].ApplyTrailingStop(); //--- Apply trailing stop logic to initial positions. } }
Aquí implementamos un mecanismo para gestionar las restricciones de posición y controlar el número de operaciones que el Asesor Experto (EA) puede abrir en un momento dado. La lógica comienza evaluando si las restricciones de posición están desactivadas («inputEnablePositionsRestriction» == false) o si el número total de posiciones abiertas actualmente (PositionsTotal) es inferior al máximo definido por el usuario («inputMaximumPositions»). Si se cumple cualquiera de las dos condiciones, el EA procede a abrir una nueva operación, asegurándose de que se ajusta a las preferencias del usuario en cuanto a operaciones sin restricciones o limitadas..
Sin embargo, si ambas condiciones fallan, lo que indica que las restricciones de posición están habilitadas y se ha alcanzado el número máximo permitido de posiciones, el EA no abrirá una nueva operación. En su lugar, registra un mensaje de error en el terminal: «FAILED: Maximum positions threshold hit!». Este mensaje sirve como mecanismo informativo de retroalimentación, ayudando al usuario a comprender por qué no se ejecutaron operaciones adicionales. Hemos resaltado los cambios en color amarillo claro para mayor claridad. Tras realizar las pruebas, obtenemos los siguientes resultados.

En la imagen podemos ver que el número de operaciones se reduce aún más y que la tasa de ganancias aumenta aún más. Esto confirma que hemos logrado nuestro objetivo de crear un sistema de recuperación multizona. En una visualización en formato Graphic Interchange Format (GIF), tenemos la siguiente simulación, que confirma el logro de nuestro objetivo.

Conclusión
En conclusión, este artículo ha ilustrado el proceso de construcción de un robusto Asesor Experto MQL5 basado en una estrategia de recuperación de zona multinivel. Aprovechando conceptos básicos como la detección automática de señales, la gestión dinámica de la recuperación y mecanismos de protección de beneficios como los trailing stops, hemos creado un sistema flexible capaz de gestionar múltiples instancias de recuperación independientes. Los componentes clave de esta implementación incluyen la generación de señales comerciales, la lógica de restricción de posiciones y el manejo eficiente de las estrategias de recuperación y salida.
Descargo de responsabilidad: Este artículo está pensado como recurso educativo para la programación MQL5. Si bien el sistema de recuperación por zonas multinivel presentado proporciona un marco estructurado para la gestión comercial, el comportamiento del mercado es intrínsecamente incierto. El trading conlleva riesgos financieros, y el éxito histórico no garantiza resultados futuros. Es imprescindible realizar pruebas exhaustivas y gestionar eficazmente los riesgos antes de implementar cualquier estrategia en los mercados reales.
Siguiendo las metodologías descritas en esta guía, podrá ampliar sus conocimientos sobre el trading algorítmico y aplicar estos principios para crear sistemas de trading aún más sofisticados. ¡Feliz programación y que tus operaciones bursátiles sean exitosas!
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/17001
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Gestión de capital en el trading y programa de contabilidad doméstica del tráder con base de datos
Simulación de mercado (Parte 16): Sockets (X)
Indicador de estimación de fuerza y debilidad de pares de divisas en MQL5 puro
Desarrollamos un asesor experto multidivisas (Parte 23): Ordenando la cadena de etapas de optimización automática de proyectos (II)
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso