
Desarrollando la estrategia martingala Zone Recovery en MQL5
Introducción
En este artículo, crearemos paso a paso el asesor experto (EA) de estrategia de trading de divisas martingala de recuperación de zona en lenguaje MetaQuotes 5 (MQL5) para MetaTrader 5 (MT5). La estrategia martingala de recuperación de zona es una estrategia común que tiene como objetivo contrarrestar posiciones perdedoras abriendo posiciones opuestas con un volumen de negociación ligeramente mayor que cancela las posiciones perdedoras. Se trata básicamente de una estrategia de seguimiento de tendencias que no se preocupa por la dirección del mercado, con la esperanza de que en algún momento el mercado tenga una tendencia hacia abajo o hacia arriba y, eventualmente, se alcancen objetivos particulares. Únase a nosotros mientras no solo discutimos sino que también automatizamos este sistema en MQL5.
Este viaje cubrirá los siguientes temas:
- Definición de la estrategia de recuperación de zona
- Descripción de la estrategia de recuperación de zona
- Implementación en MQL5
- Resultados de pruebas retrospectivas
- Conclusión
Definición de la estrategia de recuperación de zona
La estrategia comercial de recuperación de zona es un método sofisticado que se utiliza principalmente en el comercio de divisas para controlar y reducir las pérdidas. La idea central de este método es determinar los rangos de precios precisos dentro de los cuales un comerciante anticipa que fluctúa el mercado. El comerciante lanza una serie de contraoperaciones para construir una zona de recuperación cuando una operación se mueve desfavorablemente y alcanza una zona de pérdida predeterminada, en lugar de cerrar la posición con pérdidas. Lo ideal es que estas operaciones se realicen de manera que un movimiento de regreso a la zona de recuperación permita cerrar la posición en su conjunto en el punto de equilibrio o incluso con ganancias.
Esta técnica se centra principalmente en la cobertura y en promediar a la baja. Cuando el mercado va en contra de la primera operación, el operador crea una posición opuesta de igual tamaño para que actúe como cobertura. Si el mercado continúa moviéndose a la baja, se abren nuevas operaciones a intervalos cruciales para promediar el precio de entrada. Los acuerdos están diseñados para que el beneficio agregado de las operaciones de recuperación pueda igualar las pérdidas de la operación inicial cuando el mercado regrese a su media. Este enfoque requiere una estrategia de gestión de riesgos bien definida, ya que la posibilidad de acumular inversiones podría generar requisitos de margen elevados y exposición a circunstancias de mercado volátiles.
La capacidad de la estrategia de recuperación de zona de convertir una transacción perdedora en una exitosa sin requerir una predicción precisa de la dirección del mercado es una de sus principales ventajas. Permite a los traders sacar provecho de las turbulencias y reversiones del mercado, convirtiendo las tendencias desfavorables en oportunidades de rebote.
El método de negociación de recuperación de zona es el más apropiado para operadores experimentados con un conocimiento profundo de las estrategias de gestión de riesgos y la dinámica del mercado. Es un instrumento poderoso en la caja de herramientas de un comerciante que es especialmente útil en mercados erráticos con frecuentes oscilaciones de precios. Si bien tiene la capacidad de recuperarse de operaciones fallidas y obtener ganancias, su complejidad y los riesgos asociados requieren una planificación cuidadosa y una ejecución calculada.
Descripción de la estrategia de recuperación de zona
Basado en un análisis de mercado, el método de negociación de recuperación de zona comienza con la apertura de una posición inicial. Imaginemos a un comerciante que abre una posición de compra porque cree que el mercado subirá. Ganar dinero con esta tendencia creciente es el primer objetivo. El comerciante cierra la posición y asegura las ganancias si el mercado se mueve positivamente y el precio sube hasta un objetivo de ganancia predeterminado. El comerciante puede beneficiarse de los movimientos favorables del mercado con esta sencilla estrategia sin tener que utilizar otras más complejas.
Por otro lado, el método de recuperación de zona inicia su mecanismo de mitigación de pérdidas si el mercado se mueve en contra de la posición larga inicial y alcanza un punto de pérdida predeterminado. En este punto, el comerciante abre una posición de venta con un tamaño de lote más grande, generalmente el doble del tamaño de la posición larga anterior, en lugar de cerrar la posición de compra con pérdidas. Al compensar las pérdidas de la primera operación con las posibles ganancias de la nueva posición, esta operación contraria busca establecer una cobertura. Utilizando la oscilación inherente del mercado, la estrategia anticipa una reversión o al menos una estabilización dentro de un rango determinado.
El comerciante vigila la nueva posición de venta a medida que el mercado avanza. El impacto combinado de la compra inicial y posiciones de venta más grandes resulta idealmente en una situación de equilibrio o ganancia si el mercado continúa disminuyendo y alcanza otro punto predeterminado. Después de eso, el comerciante puede cerrar ambas posiciones y usar las ganancias de la siguiente operación más grande para compensar las pérdidas de la primera operación. Para garantizar que toda la posición pueda cerrarse de forma rentable dentro de la zona de recuperación, esta estrategia requiere un cálculo y una sincronización exactos.
Implementación en MQL5
Para crear un asesor experto en MQL5 que esté orientado a visualizar los niveles de recuperación de la zona, normalmente cuatro, primero necesitaremos definir los niveles. Lo hacemos definiéndolos lo antes posible, ya que serán cruciales para visualizar el sistema comercial. Esto se logra utilizando la palabra clave "#define", que es una directiva incorporada en MQL5 que se puede utilizar para asignar nombres mnemotécnicos a constantes. Esto es lo que aparece a continuación:
#define ZONE_H "ZH" // Define a constant for the high zone line name #define ZONE_L "ZL" // Define a constant for the low zone line name #define ZONE_T_H "ZTH" // Define a constant for the target high zone line name #define ZONE_T_L "ZTL" // Define a constant for the target low zone line name
Aquí, definimos las constantes de nuestros límites de zona: ZONE_H como 'ZH' (Zona Alta), ZONE_L como 'ZL' (Zona Baja), ZONE_T_H como 'ZTH' (Zona Objetivo Alta) y ZONE_T_L como 'ZTL' (Zona Objetivo Baja). Estas constantes representan los niveles respectivos en nuestro sistema.
Después de nuestras definiciones, necesitaremos abrir posiciones comerciales. La forma más sencilla de abrir posiciones es incluir una instancia comercial, lo que generalmente se logra mediante la inclusión de otro archivo dedicado a abrir posiciones. Utilizamos la directiva include para incluir la biblioteca comercial, que contiene funciones para operaciones comerciales.
#include <Trade/Trade.mqh>
CTrade obj_trade;
Primero, usamos los corchetes angulares para indicar que el archivo que queremos incluir está contenido en la carpeta de inclusión y proporcionamos la carpeta Trade, seguida de una barra o barra invertida normal y luego el nombre del archivo de destino, en este caso, "Trade.mqh". CTrade es una clase para gestionar operaciones comerciales, y obj_trade es una instancia de esta clase, normalmente un objeto puntero creado a partir de la clase CTrade para proporcionar acceso a las variables miembro de la clase.
Después necesitamos cierta lógica de control para generar señales para abrir las posiciones. En nuestro caso utilizamos el RSI (Índice de Fuerza Relativa), pero puedes utilizar cualquiera que consideres oportuno.
int rsi_handle; // Handle for the RSI indicator double rsiData[]; // Array to store RSI data int totalBars = 0; // Variable to keep track of the total number of bars
El rsi_handle almacena la referencia del indicador RSI (Índice de Fuerza Relativa), que se inicializa en la función OnInit, lo que permite que el EA recupere valores RSI. La matriz rsiData almacena estos valores RSI, obtenidos mediante CopyBuffer, y se utiliza para determinar señales comerciales basadas en umbrales RSI. totalBars: La variable realiza un seguimiento del número total de barras en el gráfico, lo que garantiza que la lógica comercial se ejecute solo una vez por cada nueva barra, evitando ejecuciones múltiples dentro de una sola barra. En conjunto, estas variables permiten que el EA genere señales comerciales basadas en valores RSI mientras mantiene el tiempo de ejecución adecuado.
Finalmente, después de definir los valores del indicador, definimos los niveles de generación de señal del indicador y los niveles de zona.
double overBoughtLevel = 70.0; // Overbought level for RSI double overSoldLevel = 30.0; // Oversold level for RSI double zoneHigh = 0; // Variable to store the high zone price double zoneLow = 0; // Variable to store the low zone price double zoneTargetHigh = 0; // Variable to store the target high zone price double zoneTargetLow = 0; // Variable to store the target low zone price
Nuevamente, definimos dos variables dobles, overBoughtLevel y overSoldLevel, y las inicializamos en 70 y 30, respectivamente. Estos sirven como nuestros niveles extremos para la producción de señales. Además, definimos cuatro variables de tipo de datos dobles adicionales, zoneHigh, zoneLow, zoneTradegetHigh y zoneTargetLow, y las inicializamos a cero. Mantendrán nuestros niveles de configuración de recuperación más adelante en el código.
Hasta este punto, hemos definido todas las variables globales, que son cruciales para el sistema. Ahora podemos pasar libremente al controlador de eventos OnInit, que se llama siempre que se inicializa el asesor experto. Es en este caso que necesitamos inicializar el controlador del indicador desde el cual luego copiaremos datos para su posterior análisis. Para inicializar el indicador, utilizamos la función incorporada para devolver su identificador proporcionando los parámetros correctos.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize the RSI indicator rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); //--- Return initialization result return(INIT_SUCCEEDED); }
En el controlador de eventos de desinicialización, necesitamos liberar los datos del indicador y también liberar los datos almacenados. Hacemos esto para liberar típicamente el indicador de la memoria de la computadora para ahorrar recursos ya que no será usado nuevamente, y si lo es, el indicador maneja y los datos se crearán en la inicialización del asesor experto.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Remove RSI indicator from memory IndicatorRelease(rsi_handle); ArrayFree(rsiData); // Free the RSI data array }
Luego pasamos al controlador de eventos OnTick, que es una función que se llama en cada tick, que es un cambio de precio de cotización. Esta es nuevamente nuestra función o sección principal, ya que contiene todos los fragmentos de código cruciales para una implementación exitosa de la estrategia comercial. Como abriremos posiciones, necesitamos definir nuestros precios de venta y oferta para que podamos usar sus valores más actuales para fines de análisis.
double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
Definir los precios de oferta y demanda mediante el uso de información de símbolos como un tipo de datos doble nos ayuda a obtener las últimas cotizaciones de precios para un análisis más detallado. La función que utilizamos es una función de sobrecarga que contiene dos variantes, pero usamos la primera que toma solo dos parámetros. El primer parámetro es el símbolo; utilizamos _Symbol para recuperar automáticamente el símbolo en el gráfico actual y SYMBOL_ASK para obtener la enumeración del tipo doble.
Después de obtener las últimas cotizaciones de precios, generadas técnicamente en cada tick, estamos listos. Simplemente continuamos definiendo el rango de zona y la zona objetivo para el asesor experto. Los definimos como variables de tipo de datos doble y los multiplicamos por la variable _Point, que contiene el tamaño en puntos del símbolo actual en la moneda de cotización.
double zoneRange = 200*_Point; double zoneTarget = 400*_Point;
Por último, necesitamos variables de utilidad que garanticen que abramos posiciones de recuperación correctamente. En este caso definimos cuatro de ellos. LastDirection mantendrá el tipo de la última posición abierta para garantizar que alternemos entre órdenes de compra y venta. Pretendemos que la venta sea -1 y la compra +1, solo un valor arbitrario que se puede cambiar o implementar según la elección de cada uno. Luego se utiliza el lote de recuperación y se inicializa a cero, cuya función será contener y almacenar nuestros puntos de recuperación calculados para que no perdamos de vista los próximos volúmenes comerciales, siempre que todavía esté en juego un sistema de recuperación de zona. Aún así, tenemos dos variables booleanas, isBuyDone y isSellDone, para almacenar indicadores que nos ayuden a no abrir varias posiciones a la vez. Tenga en cuenta que todas nuestras variables son estáticas para garantizar que no se actualicen a menos que las actualicemos individualmente nosotros mismos. Esto se debe a que las variables locales declaradas con la palabra clave static conservan sus valores durante toda la vida de la función. De esta forma, con cada próxima llamada a la función OnTick, nuestras variables locales contendrán los valores que tenían durante la llamada anterior.
static int lastDirection = 0; //-1 = sell, 1 = buy static double recovery_lot = 0.0; static bool isBuyDone = false, isSellDone = false;
Luego de definir todas las variables de utilidad, procedemos a abrir posiciones, desde las cuales posteriormente implementaremos la lógica de recuperación de la zona. Pretendemos lograr esto mediante el uso del indicador RSI, que es totalmente modificable según las preferencias de los usuarios, por lo que puede seguir adelante y aplicar su técnica de entrada. Bien, ahora, para ahorrar recursos, queremos obtener los datos del indicador en cada vela y no en cada tick. Esto se consigue de la siguiente manera:
int bars = iBars(_Symbol,PERIOD_CURRENT); if (totalBars == bars) return; totalBars = bars;
Aquí, definimos la variable entera barras y las inicializamos con el número de barras actuales en el gráfico, lo que se logra mediante el uso de la función incorporada iBars, que toma dos argumentos: un símbolo y un punto. Luego procedemos a verificar si el número de barras definidas previamente es igual a las barras actuales, y si es así entonces significa que seguimos en la barra actual, y por lo tanto regresamos, es decir rompemos la operación y devolvemos el control al programa que la llamó. De lo contrario, si las dos variables no coinciden, significa que hemos pasado a una nueva vela y podemos continuar. Así que actualizamos el valor de totalBars a las barras actuales para que en el siguiente tick tengamos un valor actualizado de totalBars variable.
Para que la recuperación de la zona tenga efecto y abra solo una posición y la gestione, necesitamos abrir una posición por instancia. Por lo tanto, si el número de posiciones es mayor que uno, no necesitamos agregar ninguna otra posición y simplemente regresamos anticipadamente. Esto es como se muestra a continuación nuevamente.
Si no volvemos a este punto significa que aún no tenemos ninguna posición y podemos proceder a abrir una. De esta forma, copiamos los datos del controlador del indicador y los almacenamos en la matriz de datos del indicador para su posterior análisis. Esto se logra mediante la función de búfer de copia.
if (PositionsTotal() > 0) return;
if (!CopyBuffer(rsi_handle,0,1,2,rsiData)) return;
Como se visualiza, la función de búfer de copia es una función de sobrecarga que devuelve un entero. Por razones de seguridad, utilizamos la declaración if para verificar si devuelve los datos solicitados y, si no, no tenemos suficientes datos para devolver ya que no se puede realizar un análisis más detallado. Sin embargo, veamos qué hace la función. Contiene cinco argumentos. Primero está el controlador del indicador desde el cual se copiarán los datos, y segundo está el número de búfer del indicador; en este caso, es 0, pero puede variar completamente según el indicador que esté utilizando. En tercer lugar está la posición inicial, o el índice de la barra desde donde se copian los datos. Aquí usamos 1 para indicar que comenzamos en la barra anterior a la barra actual en el gráfico. En cuarto lugar está el recuento: la cantidad de datos que se van a almacenar. Aquí nos bastan dos, ya que no hacemos análisis detallados. Finalmente, proporcionamos la matriz de destino del almacenamiento de datos recuperados.
Después de recuperar los datos del controlador del indicador, procedemos a utilizarlos para fines comerciales o, mejor dicho, para la generación de señales. Primero, buscamos señales de compra. Logramos esto utilizando una declaración if y abriendo una posición de compra.
if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel){ obj_trade.Buy(0.01);
Si los datos en el índice 1 de la matriz almacenada son menores que el nivel de sobreventa definido y los datos en el índice 0 son mayores que el nivel de sobreventa, significa que tuvimos un cruce entre la línea RSI y el nivel de sobreventa, lo que significa que tenemos una señal de compra. Luego utilizamos el objeto comercial y el operador de punto para obtener acceso al método de compra contenido en la clase CTrade. En este caso, abrimos una posición de compra de volumen 0.01 e ignoramos el resto de parámetros como stop loss y take profit ya que no los utilizaremos, ya que harían que nuestro sistema no funcione como está previsto porque implementamos la estrategia de recuperación x=zona que no necesita cerrar las posiciones en los niveles de stop loss.
Sin embargo, para establecer los niveles de recuperación de la zona, necesitaremos el ticket de la posición para poder acceder a sus propiedades. Para obtener el ticket, utilizamos el orden de resultados de la posición abierta anteriormente. Luego de obtener el ticket, queremos verificar que sea mayor a 0, lo que significa que fue un éxito la apertura de la posición, y luego seleccionar la posición por ticket. Si podemos seleccionarlo por ticket, entonces podemos obtener las propiedades de la posición, pero solo nos interesa el precio de apertura. A partir del precio de apertura, establecemos las posiciones de la siguiente manera: El precio de apertura de la posición de compra es nuestro máximo de zona, y para obtener el mínimo de zona, simplemente restamos el rango de zona del máximo de zona. Para los objetivos de zona altos y bajos, simplemente sumamos el objetivo de zona a la zona alta y restamos el objetivo de zona de la zona baja, respectivamente. Por último, solo nos falta normalizar los valores a los dígitos del símbolo para mayor precisión y habremos terminado.
ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0){ if (PositionSelectByTicket(pos_ticket)){ double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneHigh = NormalizeDouble(openPrice,_Digits); zoneLow = NormalizeDouble(zoneHigh - zoneRange,_Digits); zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget,_Digits); zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget,_Digits);
Hasta este punto podemos establecer los niveles, pero no son visibles en el gráfico. Para lograr esto, creamos una función que utilizaremos para visualizar los cuatro niveles definidos en el gráfico.
drawZoneLevel(ZONE_H,zoneHigh,clrGreen,2); drawZoneLevel(ZONE_L,zoneLow,clrRed,2); drawZoneLevel(ZONE_T_H,zoneTargetHigh,clrBlue,3); drawZoneLevel(ZONE_T_L,zoneTargetLow,clrBlue,3);
Creamos simplemente una función void simple que toma cuatro parámetros de entrada o argumentos, a saber, levelName, precio, CLR y ancho. Utilizamos la función incorporada ObjectCreate para crear una línea horizontal que se extiende a lo largo del gráfico y la vincula al tiempo y precio proporcionados. Por último, utilizamos ObjectSetInteger para establecer el color del objeto para que sea único y el ancho para facilitar el ajuste de la visibilidad.
void drawZoneLevel(string levelName, double price, color clr, int width) { ObjectCreate(0, levelName, OBJ_HLINE, 0, TimeCurrent(), price); // Create a horizontal line object ObjectSetInteger(0, levelName, OBJPROP_COLOR, clr); // Set the line color ObjectSetInteger(0, levelName, OBJPROP_WIDTH, width); // Set the line width }
Finalmente, establecemos la última dirección en el valor 1 para mostrar que abrimos una posición de compra, establecemos el próximo volumen de recuperación como el volumen inicial multiplicado por una constante multiplicadora, en este caso, 2, lo que significa que duplicamos el volumen y, por último, establecemos el indicador isBuyDone en verdadero y isSellDone en falso.
lastDirection = 1; recovery_lot = 0.01*2; isBuyDone = true; isSellDone = false;
El código completo para abrir la posición y configurar los niveles de recuperación de la zona es el siguiente.
//--- Check for oversold condition and open a buy position if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel) { obj_trade.Buy(0.01); // Open a buy trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneHigh = NormalizeDouble(openPrice, _Digits); // Set the high zone price zoneLow = NormalizeDouble(zoneHigh - zoneRange, _Digits); // Set the low zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = 1; // Set the last direction to buy recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } }
Para abrir la posición de venta y configurar los niveles de recuperación de la zona, la lógica de control permanece, pero con condiciones inversas como se muestra a continuación.
else if (rsiData[1] > overBoughtLevel && rsiData[0] < overBoughtLevel) { obj_trade.Sell(0.01); // Open a sell trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneLow = NormalizeDouble(openPrice, _Digits); // Set the low zone price zoneHigh = NormalizeDouble(zoneLow + zoneRange, _Digits); // Set the high zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = -1; // Set the last direction to sell recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } }
Aquí, verificamos si se cumplen las condiciones de la señal de venta y, si es así, abrimos una posición de venta instantáneamente. Luego, obtenemos su ticket y lo usamos para recuperar el precio de apertura de la posición, que usamos para establecer los niveles de recuperación de la zona. Dado que es una posición de venta, su precio se convierte en el mínimo de la zona y, para obtener el máximo de la zona, simplemente sumamos el rango de la zona al máximo de la zona. De manera similar, sumamos la zona objetivo a la zona alta para obtener la zona objetivo alta y restamos la zona objetivo de la zona baja para obtener la zona objetivo baja. Para la visualización, dibujamos nuevamente los cuatro niveles utilizando las funciones. Por último, simplemente configuramos nuestras variables de utilidad.
Hasta este punto, logramos abrir las posiciones en base a la señal presentada y configurar el sistema de recuperación de zonas. Aquí está el código completo que lo permite.
void OnTick() { int bars = iBars(_Symbol,PERIOD_CURRENT); if (totalBars == bars) return; totalBars = bars; if (PositionsTotal() > 0) return; if (!CopyBuffer(rsi_handle,0,1,2,rsiData)) return; //--- Check for oversold condition and open a buy position if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel) { obj_trade.Buy(0.01); // Open a buy trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneHigh = NormalizeDouble(openPrice, _Digits); // Set the high zone price zoneLow = NormalizeDouble(zoneHigh - zoneRange, _Digits); // Set the low zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = 1; // Set the last direction to buy recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } } //--- Check for overbought condition and open a sell position else if (rsiData[1] > overBoughtLevel && rsiData[0] < overBoughtLevel) { obj_trade.Sell(0.01); // Open a sell trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneLow = NormalizeDouble(openPrice, _Digits); // Set the low zone price zoneHigh = NormalizeDouble(zoneLow + zoneRange, _Digits); // Set the high zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = -1; // Set the last direction to sell recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } } }
Podemos configurar el sistema de recuperación de zona, pero debemos monitorearlo y asegurarnos de escapar de él una vez que alcancemos los niveles objetivo o abramos las posiciones respectivas una vez que alcancemos los niveles predefinidos. Tendremos que hacer esto en cada tic y, por lo tanto, la lógica debe implementarse antes de la restricción del bucle de velas. Primero, verifiquemos la condición en la que los precios alcanzan los niveles objetivo y, posteriormente, verifiquemos la condición en la que los precios alcanzan los niveles de zona. Entonces, bailemos.
if (zoneTargetHigh > 0 && zoneTargetLow > 0){ if (bid > zoneTargetHigh || bid < zoneTargetLow){ obj_trade.PositionClose(_Symbol); deleteZoneLevels(); ... } }
Aquí, verificamos que el sistema de recuperación de zona esté configurado teniendo la lógica de que los niveles objetivo estén por encima de cero, lo que significa que ya tenemos un sistema en juego. Entonces, si ese es el caso, verificamos si el precio de oferta está por encima del objetivo máximo o por debajo del objetivo mínimo, una indicación de que podemos destruir cómodamente la recuperación de la zona ya que su objetivo se ha alcanzado. De esta forma, eliminamos los niveles de zona a través de la función deleteZoneLevels. La función que utilizamos es de tipo void ya que no necesitamos devolver nada, y la función ObjectDelete incorporada está implementada para eliminar los niveles tomando dos argumentos, el índice del gráfico y el nombre del objeto.
void deleteZoneLevels(){ ObjectDelete(0,ZONE_H); ObjectDelete(0,ZONE_L); ObjectDelete(0,ZONE_T_H); ObjectDelete(0,ZONE_T_L); }
Para cerrar las posiciones, ya que en este punto podrían ser varias, utilizamos un bucle que las considera todas y luego las elimina individualmente. Esto se logra mediante el siguiente código.
for (int i = PositionsTotal()-1; i >= 0; i--){ ulong ticket = PositionGetTicket(i); if (ticket > 0){ if (PositionSelectByTicket(ticket)){ obj_trade.PositionClose(ticket); } } }
Luego de cerrar todas las posiciones y eliminar los niveles, reiniciamos el sistema a valor predeterminado, que no tiene ningún sistema de recuperación de zonas.
//closed all, reset all zoneHigh=0;zoneLow=0;zoneTargetHigh=0;zoneTargetLow=0; lastDirection=0; recovery_lot = 0;
Esto se consigue poniendo a cero los niveles de zona y los objetivos, además de la última dirección y el lote de recuperación. Estas son variables estáticas, por lo que debemos restablecerlas manualmente. Para las variables dinámicas no es necesario, ya que a menudo se actualizan automáticamente.
El código completo responsable de destruir el sistema de recuperación una vez cumplidos sus objetivos es el siguiente:
//--- Close all positions if the bid price is outside target zones if (zoneTargetHigh > 0 && zoneTargetLow > 0) { if (bid > zoneTargetHigh || bid < zoneTargetLow) { obj_trade.PositionClose(_Symbol); // Close the current position deleteZoneLevels(); // Delete all drawn zone levels for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if (ticket > 0) { if (PositionSelectByTicket(ticket)) { obj_trade.PositionClose(ticket); // Close positions by ticket } } } //--- Reset all zone and direction variables zoneHigh = 0; zoneLow = 0; zoneTargetHigh = 0; zoneTargetLow = 0; lastDirection = 0; recovery_lot = 0; } }
Pasando ahora a abrir posiciones de recuperación, tendremos que comprobar si el sistema sigue en juego, como lo demuestra el momento en que los niveles de la zona están por encima de cero. Si es así, lo configuramos declarando una variable lots_rec, que simplemente usamos para almacenar nuestros lotes de recuperación. Luego lo normalizamos a 3 decimales para mayor precisión, ya que la cuenta comercial que estamos usando es una cuenta de microlotes. Este valor puede cambiar según el tipo de cuenta que esté utilizando. Por ejemplo, si estás utilizando una cuenta estándar, su lote mínimo es 1, y por lo tanto su valor será 0, para eliminar los decimales. La mayoría tiene 2 decimales, pero podría tener un tipo de cuenta 0.001 y, por lo tanto, su valor es 3 para redondear los lotes a los 3 decimales más cercanos.
if (zoneHigh > 0 && zoneLow > 0){ double lots_Rec = 0; lots_Rec = NormalizeDouble(recovery_lot,2); ... }
Luego, verificamos si el precio de oferta está por encima del máximo de la zona y si el indicador isBuyDone anterior es falso o el último valor de dirección es menor que cero, abrimos una posición de recuperación de compra. Después de abrir la posición, establecemos lastDirection en 1, lo que significa que la posición abierta anteriormente es una compra, calculamos los lotes de recuperación y los almacenamos en la variable recovery_lot para usarlos en la próxima llamada de posición de recuperación, y luego establecemos el indicador isBuyDone en verdadero y isSellDone en falso, lo que indica que ya se ha abierto una posición de compra.
if (bid > zoneHigh) { if (isBuyDone == false || lastDirection < 0) { obj_trade.Buy(lots_Rec); // Open a buy trade lastDirection = 1; // Set the last direction to buy recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } }
De lo contrario, si el precio de oferta está por debajo del mínimo de la zona, abrimos la posición de recuperación de venta, respectivamente, como se muestra.
else if (bid < zoneLow) { if (isSellDone == false || lastDirection > 0) { obj_trade.Sell(lots_Rec); // Open a sell trade lastDirection = -1; // Set the last direction to sell recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } }
El código completo ahora responsable de abrir las posiciones de recuperación es el siguiente:
//--- Check if price is within defined zones and take action if (zoneHigh > 0 && zoneLow > 0) { double lots_Rec = NormalizeDouble(recovery_lot, 2); // Normalize the recovery lot size to 2 decimal places if (bid > zoneHigh) { if (isBuyDone == false || lastDirection < 0) { obj_trade.Buy(lots_Rec); // Open a buy trade lastDirection = 1; // Set the last direction to buy recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } else if (bid < zoneLow) { if (isSellDone == false || lastDirection > 0) { obj_trade.Sell(lots_Rec); // Open a sell trade lastDirection = -1; // Set the last direction to sell recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } }
Este es el hito que hemos alcanzado hasta el momento.
El código completo necesario para automatizar el sistema de recuperación de zona es el siguiente:
//+------------------------------------------------------------------+ //| MARTINGALE EA.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- Define utility variables for later use #define ZONE_H "ZH" // Define a constant for the high zone line name #define ZONE_L "ZL" // Define a constant for the low zone line name #define ZONE_T_H "ZTH" // Define a constant for the target high zone line name #define ZONE_T_L "ZTL" // Define a constant for the target low zone line name //--- Include trade instance class #include <Trade/Trade.mqh> // Include the trade class for trading functions CTrade obj_trade; // Create an instance of the CTrade class for trading operations //--- Declare variables to hold indicator data int rsi_handle; // Handle for the RSI indicator double rsiData[]; // Array to store RSI data int totalBars = 0; // Variable to keep track of the total number of bars double overBoughtLevel = 70.0; // Overbought level for RSI double overSoldLevel = 30.0; // Oversold level for RSI double zoneHigh = 0; // Variable to store the high zone price double zoneLow = 0; // Variable to store the low zone price double zoneTargetHigh = 0; // Variable to store the target high zone price double zoneTargetLow = 0; // Variable to store the target low zone price //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize the RSI indicator rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE); //--- Return initialization result return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Remove RSI indicator from memory IndicatorRelease(rsi_handle); ArrayFree(rsiData); // Free the RSI data array } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Retrieve the current Ask and Bid prices double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double zoneRange = 200 * _Point; // Define the range for the zones double zoneTarget = 400 * _Point; // Define the target range for the zones //--- Variables to track trading status static int lastDirection = 0; // -1 = sell, 1 = buy static double recovery_lot = 0.0; // Lot size for recovery trades static bool isBuyDone = false, isSellDone = false; // Flags to track trade completion //--- Close all positions if the bid price is outside target zones if (zoneTargetHigh > 0 && zoneTargetLow > 0) { if (bid > zoneTargetHigh || bid < zoneTargetLow) { obj_trade.PositionClose(_Symbol); // Close the current position deleteZoneLevels(); // Delete all drawn zone levels for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if (ticket > 0) { if (PositionSelectByTicket(ticket)) { obj_trade.PositionClose(ticket); // Close positions by ticket } } } //--- Reset all zone and direction variables zoneHigh = 0; zoneLow = 0; zoneTargetHigh = 0; zoneTargetLow = 0; lastDirection = 0; recovery_lot = 0; } } //--- Check if price is within defined zones and take action if (zoneHigh > 0 && zoneLow > 0) { double lots_Rec = NormalizeDouble(recovery_lot, 2); // Normalize the recovery lot size to 2 decimal places if (bid > zoneHigh) { if (isBuyDone == false || lastDirection < 0) { obj_trade.Buy(lots_Rec); // Open a buy trade lastDirection = 1; // Set the last direction to buy recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } else if (bid < zoneLow) { if (isSellDone == false || lastDirection > 0) { obj_trade.Sell(lots_Rec); // Open a sell trade lastDirection = -1; // Set the last direction to sell recovery_lot = recovery_lot * 2; // Double the recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } } //--- Update bars and check for new bars int bars = iBars(_Symbol, PERIOD_CURRENT); if (totalBars == bars) return; // Exit if no new bars totalBars = bars; // Update the total number of bars //--- Exit if there are open positions if (PositionsTotal() > 0) return; //--- Copy RSI data and check for oversold/overbought conditions if (!CopyBuffer(rsi_handle, 0, 1, 2, rsiData)) return; //--- Check for oversold condition and open a buy position if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel) { obj_trade.Buy(0.01); // Open a buy trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneHigh = NormalizeDouble(openPrice, _Digits); // Set the high zone price zoneLow = NormalizeDouble(zoneHigh - zoneRange, _Digits); // Set the low zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = 1; // Set the last direction to buy recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = true; // Mark buy trade as done isSellDone = false; // Reset sell trade flag } } } //--- Check for overbought condition and open a sell position else if (rsiData[1] > overBoughtLevel && rsiData[0] < overBoughtLevel) { obj_trade.Sell(0.01); // Open a sell trade with 0.01 lots ulong pos_ticket = obj_trade.ResultOrder(); if (pos_ticket > 0) { if (PositionSelectByTicket(pos_ticket)) { double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); zoneLow = NormalizeDouble(openPrice, _Digits); // Set the low zone price zoneHigh = NormalizeDouble(zoneLow + zoneRange, _Digits); // Set the high zone price zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line lastDirection = -1; // Set the last direction to sell recovery_lot = 0.01 * 2; // Set the initial recovery lot size isBuyDone = false; // Reset buy trade flag isSellDone = true; // Mark sell trade as done } } } } //+------------------------------------------------------------------+ //| FUNCTION TO DRAW HORIZONTAL ZONE LINES | //+------------------------------------------------------------------+ void drawZoneLevel(string levelName, double price, color clr, int width) { ObjectCreate(0, levelName, OBJ_HLINE, 0, TimeCurrent(), price); // Create a horizontal line object ObjectSetInteger(0, levelName, OBJPROP_COLOR, clr); // Set the line color ObjectSetInteger(0, levelName, OBJPROP_WIDTH, width); // Set the line width } //+------------------------------------------------------------------+ //| FUNCTION TO DELETE DRAWN ZONE LINES | //+------------------------------------------------------------------+ void deleteZoneLevels() { ObjectDelete(0, ZONE_H); // Delete the high zone line ObjectDelete(0, ZONE_L); // Delete the low zone line ObjectDelete(0, ZONE_T_H); // Delete the target high zone line ObjectDelete(0, ZONE_T_L); // Delete the target low zone line }
Hasta este punto, hemos automatizado con éxito el sistema de comercio de divisas de recuperación de zona según lo previsto, y procederemos a probar y ver su rendimiento, como se muestra a continuación, para determinar si cumple con sus objetivos designados.
Resultados de pruebas retrospectivas
Luego de la prueba en el probador de estrategias, a continuación se muestran sus resultados.
Gráfico:
Resultados:
Conclusión
En este artículo, analizamos los pasos básicos que deben implementarse para automatizar la famosa estrategia martingala de recuperación de zona en MQL5. Hemos proporcionado la definición y descripción básicas de la estrategia y mostramos cómo se puede implementar en MQL5. Los comerciantes ahora pueden utilizar el conocimiento demostrado para desarrollar sistemas de recuperación de zonas más complejos que, más adelante, pueden optimizarse para producir mejores resultados al final.
Descargo de responsabilidad: Este código solo está destinado a ayudarlo a obtener los conceptos básicos para crear un sistema de comercio de divisas de recuperación de zona, y los resultados demostrados no garantizan el rendimiento futuro. Por lo tanto, implemente cuidadosamente el conocimiento para crear y optimizar sus sistemas para que se ajusten a sus estilos comerciales.
El artículo contiene todos los pasos de manera periódica hacia la creación del sistema. Esperamos que lo encuentre útil y un paso adelante hacia la creación de un sistema de recuperación de zonas mejor y totalmente optimizado. Se adjuntan los archivos necesarios para proporcionar los ejemplos que se utilizaron para demostrar estos ejemplos. Debería poder estudiar el código y aplicarlo a su estrategia específica para lograr resultados óptimos.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/15067
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.






- 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