English Русский 中文 Deutsch 日本語
preview
Automatización de estrategias de trading en MQL5 (Parte 7): Creación de un EA para el comercio en cuadrícula con escalado dinámico de lotes

Automatización de estrategias de trading en MQL5 (Parte 7): Creación de un EA para el comercio en cuadrícula con escalado dinámico de lotes

MetaTrader 5Trading |
151 12
Allan Munene Mutiiria
Allan Munene Mutiiria

Introducción

En el artículo anterior (Parte 6), desarrollamos un sistema automatizado de detección de bloques de órdenes en MetaQuotes Language 5 (MQL5). Ahora, en la Parte 7, nos centramos en el trading de cuadrícula, una estrategia que coloca operaciones a intervalos de precios fijos, combinada con un escalado dinámico de lotes para optimizar el riesgo y la recompensa. Este enfoque adapta el tamaño de las posiciones en función de las condiciones del mercado, con el objetivo de mejorar la rentabilidad y la gestión del riesgo. Cubriremos:

  1. Plan de la estrategia
  2. Implementación en MQL5
  3. Pruebas retrospectivas
  4. Conclusión

Al final, tendrás un programa de negociación por cuadrícula totalmente funcional con escalado dinámico de lotes, listo para probar y optimizar. ¡Comencemos!


Plan de la estrategia

El trading en cuadrícula es un enfoque sistemático que coloca órdenes de compra y venta en intervalos de precios predeterminados, lo que permite a los operadores capitalizar las fluctuaciones del mercado sin necesidad de realizar predicciones precisas de tendencias. Esta estrategia se beneficia de la volatilidad del mercado abriendo y cerrando continuamente operaciones dentro de un rango de precios definido. Para mejorar su rendimiento, integraremos un escalado de lotes dinámico, que ajustará el tamaño de las posiciones en función de condiciones predefinidas, como el saldo de la cuenta, la volatilidad o los resultados comerciales anteriores. Nuestro sistema de negociación en cuadrícula funcionará con los siguientes componentes clave:

  • Estructura de cuadrícula: Definiremos el espaciado entre los pedidos.
  • Reglas de entrada y ejecución: Determinaremos cuándo abrir operaciones en cuadrícula basándonos en distancias fijas utilizando una estrategia de indicador de media móvil.
  • Escalado dinámico de lotes: Implementaremos un mecanismo adaptativo de dimensionamiento de lotes que ajusta el tamaño de las posiciones en función de las condiciones del mercado o de parámetros de riesgo predefinidos.
  • Gestión de operaciones: Incorporaremos mecanismos de stop-loss, take-profit y breakeven opcionales para gestionar el riesgo de forma eficaz.
  • Estrategia de salida: Desarrollaremos una lógica para cerrar posiciones basada en objetivos de ganancias, límites de riesgo o cambios de tendencia.

En pocas palabras, aquí está la visualización del plan de estrategia completo para facilitar su comprensión.

DISEÑO DE CUADRÍCULA

Al combinar un sistema de cuadrícula estructurado con un tamaño de lote adaptable, crearemos un EA que maximice los retornos y al mismo tiempo gestione el riesgo de manera eficaz. A continuación, implementaremos estos conceptos en MQL5.


Implementación en MQL5

Para crear el programa en MQL5, abra el MetaEditor, vaya al Navegador, localice la carpeta Indicadores, haga clic en la pestaña "Nuevo" y siga las instrucciones para crear el archivo. Una vez realizado, en el entorno de codificación, necesitaremos declarar algunas variables globales que utilizaremos a lo largo del programa.

//+------------------------------------------------------------------+
//|                        Copyright 2025, Forex Algo-Trader, Allan. |
//|                                 "https://t.me/Forex_Algo_Trader" |
//+------------------------------------------------------------------+
#property copyright "Forex Algo-Trader, Allan"
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property description "This EA trades based on Grid Strategy"
#property strict

#include <Trade/Trade.mqh>                     //--- Include trading library
CTrade obj_Trade;                            //--- Trading object instance

//--- Closure Mode Enumeration and Inputs
enum ClosureMode {
   CLOSE_BY_PROFIT,      //--- Use total profit (in currency) to close positions
   CLOSE_BY_POINTS       //--- Use a points threshold from breakeven to close positions
};

input group "General EA Inputs"
input ClosureMode closureMode = CLOSE_BY_POINTS;    //Select closure mode

double breakevenPoints = 50 * _Point;                 //--- Points offset to add/subtract to/from breakeven

//--- Global Variables
double TakeProfit;                           //--- Current take profit level
double initialLotsize      = 0.1;            //--- Initial lot size for the first trade
double takeProfitPts       = 200 * _Point;    //--- Take profit distance in points
double profitTotal_inCurrency = 100;          //--- Profit target (in currency) to close positions

double gridSize;                             //--- Price level at which grid orders are triggered
double gridSize_Spacing   = 500 * _Point;      //--- Grid spacing in points
double LotSize;                              //--- Current lot size (increased with grid orders)

bool isTradeAllowed      = true;              //--- Flag to allow trade on a new bar
int totalBars            = 0;                 //--- Count of bars seen so far
int handle;                                  //--- Handle for the Moving Average indicator
double maData[];                             //--- Array for Moving Average data

Aquí, incluimos la biblioteca «Trade/Trade.mqh» utilizando #include e instanciamos el objeto «obj_Trade» para gestionar nuestras operaciones. Definimos una enumeración «ClosureMode» con opciones para cerrar posiciones y configuramos entradas de usuario como «closureMode» y «breakevenPoints». A continuación, declaramos variables para gestionar nuestros niveles de take profit, tamaño inicial del lote, espaciado de la cuadrícula y tamaño dinámico del lote, junto con indicadores y contadores para el control de las operaciones y los datos del indicador de media móvil. A continuación, debemos declarar los prototipos de nuestras funciones clave que estructurarán el programa de la siguiente manera.

//--- Function Prototypes
void   CheckAndCloseProfitTargets();         //--- Closes all positions if total profit meets target
void   ExecuteInitialTrade(double ask, double bid); //--- Executes the initial BUY/SELL trade (initial positions)
void   ManageGridPositions(double ask, double bid); //--- Adds grid orders when market moves to grid level (grid positions)
void   UpdateMovingAverage();                //--- Updates MA indicator data from its buffer
bool   IsNewBar();                           //--- Checks if a new bar has formed
double CalculateWeightedBreakevenPrice();    //--- Calculates the weighted average entry price for positions
void   CheckBreakevenClose(double ask, double bid); //--- Closes positions if price meets breakeven+/- threshold
void   CloseAllPositions();                  //--- Closes all open positions

En cuanto a las funciones, implementaremos «CheckAndCloseProfitTargets» para supervisar la rentabilidad general y cerrar posiciones una vez alcanzado nuestro objetivo, y «ExecuteInitialTrade» para poner en marcha la estrategia con la orden inicial de COMPRA o VENTA. «ManageGridPositions» añadirá órdenes adicionales a intervalos fijos a medida que el mercado se mueva, mientras que «UpdateMovingAverage» garantiza que los datos de nuestro indicador estén actualizados para la toma de decisiones. «IsNewBar» detecta nuevas barras para evitar múltiples operaciones en la misma vela, «CalculateWeightedBreakevenPrice» calcula el precio medio de entrada en todas las posiciones y «CheckBreakevenClose» utiliza esa información para salir de las operaciones cuando se dan las condiciones favorables. Por último, «CloseAllPositions» cerrará metódicamente todas las operaciones abiertas cuando sea necesario.

Después de configurar todo esto en el «ámbito global», estamos listos para continuar con la inicialización del programa, que se encuentra en el controlador de eventos «OnInit».

//+------------------------------------------------------------------+
//--- Expert initialization function
//+------------------------------------------------------------------+
int OnInit(){
   //--- Initialize the Moving Average indicator (Period: 21, SMA, Price: Close)
   handle = iMA(_Symbol, _Period, 21, 0, MODE_SMA, PRICE_CLOSE);
   if (handle == INVALID_HANDLE){
      Print("ERROR: UNABLE TO INITIALIZE THE INDICATOR. REVERTING NOW!");
      return (INIT_FAILED);
   }
   ArraySetAsSeries(maData, true);            //--- Ensure MA data array is in series order
   return(INIT_SUCCEEDED);
}

Aquí, inicializamos el programa configurando nuestro indicador de media móvil utilizando la función iMA con un período de 21, tipo SMA y PRICE_CLOSE para capturar los precios de cierre. Comprobamos si el identificador del indicador es válido; si no lo es (INVALID_HANDLE), mostramos un mensaje de error y devolvemos INIT_FAILED para detener la ejecución del programa. Por último, llamamos a la función ArraySetAsSeries en la matriz «maData» para asegurarnos de que los datos de la media móvil estén ordenados correctamente antes de devolver INIT_SUCCEEDED para confirmar que la inicialización se ha realizado correctamente. Una vez inicializado correctamente, podemos pasar al controlador de eventos OnTick para crear la lógica para abrir y gestionar las posiciones.

//+------------------------------------------------------------------+
//--- Expert tick function
//+------------------------------------------------------------------+
void OnTick(){
   
   //--- Allow new trade signals on a new bar
   if(IsNewBar())
      isTradeAllowed = true;
   
   //--- Update the Moving Average data
   UpdateMovingAverage();
   
}

Como no queremos comprobar las operaciones en cada tick, sino en cada barra, llamamos a la función «IsNewBar» y la utilizamos para establecer la variable «isTradeAllowed» en verdadero si se forma una nueva barra. A continuación, llamamos a la función responsable de obtener los valores de la media móvil. Las definiciones de la función son las siguientes.

//+-------------------------------------------------------------------+
//--- Function: UpdateMovingAverage
//--- Description: Copies the latest data from the MA indicator buffer.
//+-------------------------------------------------------------------+
void UpdateMovingAverage(){
   if(CopyBuffer(handle, 0, 1, 3, maData) < 0)
      Print("Error: Unable to update Moving Average data.");
}
  
//+-------------------------------------------------------------------+
//--- Function: IsNewBar
//--- Description: Checks if a new bar has been formed.
//+-------------------------------------------------------------------+
bool IsNewBar(){
   int bars = iBars(_Symbol, _Period);
   if(bars > totalBars){
      totalBars = bars;
      return true;
   }
   return false;
}

Aquí, implementamos «UpdateMovingAverage» para actualizar los datos de nuestro indicador copiando los últimos valores del búfer de la media móvil utilizando la función CopyBuffer. Si esta llamada a la función falla, imprimimos un mensaje de error para alertarnos de que la actualización no se ha realizado correctamente. En la función «IsNewBar», comprobamos si se ha formado una nueva barra comparando el número actual de barras, obtenido mediante la función iBars, con nuestro recuento «totalBars» almacenado; si el número ha aumentado, actualizamos «totalBars» y devolvemos «true», lo que indica que hay una nueva barra disponible para tomar decisiones de trading. A continuación, continuamos con la función tick para ejecutar operaciones basadas en los valores de los indicadores recuperados.

//--- Reset lot size if no positions are open
if(PositionsTotal() == 0)
   LotSize = initialLotsize;

//--- Retrieve recent bar prices for trade signal logic
double low1  = iLow(_Symbol, _Period, 1);
double low2  = iLow(_Symbol, _Period, 2);
double high1 = iHigh(_Symbol, _Period, 1);
double high2 = iHigh(_Symbol, _Period, 2);

//--- Get current Ask and Bid prices (normalized)
double ask = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits);
double bid = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits);

//--- If no positions are open and trading is allowed, check for an initial trade signal
if(PositionsTotal() == 0 && isTradeAllowed){
   ExecuteInitialTrade(ask, bid);
}

Aquí, primero comprobamos si no hay posiciones abiertas utilizando la función PositionsTotal y, si es así, restablecemos «LotSize» a «initialLotsize». A continuación, recuperamos los precios recientes de las barras llamando a iLow e iHigh para capturar los máximos y mínimos de las dos barras anteriores, lo que nos ayudará a formar nuestras señales de trading. A continuación, obtenemos los precios actuales «ask» y «bid» utilizando SymbolInfoDouble y los normalizamos con NormalizeDouble para garantizar su precisión. Por último, si se permite operar (tal y como indica «isTradeAllowed») y no hay posiciones abiertas actualmente, llamamos a la función «ExecuteInitialTrade» con los precios «ask» y «bid» para iniciar nuestra primera operación. La definición de la función es la siguiente.

//+---------------------------------------------------------------------------+
//--- Function: ExecuteInitialTrade
//--- Description: Executes the initial BUY or SELL trade based on MA criteria.
//---              (These are considered "initial positions.")
//+---------------------------------------------------------------------------+
void ExecuteInitialTrade(double ask, double bid){
   //--- BUY Signal: previous bar's low above MA and bar before that below MA
   if(iLow(_Symbol, _Period, 1) > maData[1] && iLow(_Symbol, _Period, 2) < maData[1]){
      gridSize = ask - gridSize_Spacing;     //--- Set grid trigger below current ask
      TakeProfit = ask + takeProfitPts;      //--- Set TP for BUY
      if(obj_Trade.Buy(LotSize, _Symbol, ask, 0, TakeProfit,"Initial Buy"))
         Print("Initial BUY order executed at ", ask, " with LotSize: ", LotSize);
      else
         Print("Initial BUY order failed at ", ask);
      isTradeAllowed = false;
   }
   //--- SELL Signal: previous bar's high below MA and bar before that above MA
   else if(iHigh(_Symbol, _Period, 1) < maData[1] && iHigh(_Symbol, _Period, 2) > maData[1]){
      gridSize = bid + gridSize_Spacing;     //--- Set grid trigger above current bid
      TakeProfit = bid - takeProfitPts;      //--- Set TP for SELL
      if(obj_Trade.Sell(LotSize, _Symbol, bid, 0, TakeProfit,"Initial Sell"))
         Print("Initial SELL order executed at ", bid, " with LotSize: ", LotSize);
      else
         Print("Initial SELL order failed at ", bid);
      isTradeAllowed = false;
   }
}

Aquí, implementamos la función «ExecuteInitialTrade» para abrir una operación inicial basada en los valores «maData». Recuperamos los precios bajos de las dos barras anteriores utilizando la función iLow y los precios altos utilizando la función iHigh. Para una señal de COMPRA, comprobamos si el mínimo de la barra anterior está por encima de «maData», mientras que la barra anterior estaba por debajo. Si se cumple esta condición, establecemos «gridSize» por debajo del «ask» actual utilizando «gridSize_Spacing» para determinar el siguiente nivel de la cuadrícula, calculamos «TakeProfit» añadiendo «takeProfitPts» al «ask» y ejecutamos una operación de COMPRA utilizando el método «obj_Trade.Buy».

Para una señal de VENTA, comprobamos si el máximo de la barra anterior está por debajo de «maData», mientras que la barra anterior estaba por encima. Si es cierto, establecemos «gridSize» por encima de la «oferta», determinamos «TakeProfit» restando «takeProfitPts» de la «oferta» e intentamos ejecutar una operación de VENTA utilizando «obj_Trade.Sell». Una vez ejecutada una operación, establecemos «isTradeAllowed» en falso para evitar entradas adicionales hasta que se cumplan otras condiciones. Aquí está el resultado.

EJECUCIÓN COMERCIAL

Desde la imagen, podemos ver que tenemos las operaciones confirmadas siendo ejecutadas. Ahora debemos pasar a gestionar las operaciones abriendo las posiciones de la cuadrícula.

//--- If positions exist, manage grid orders
if(PositionsTotal() > 0){
   ManageGridPositions(ask, bid);
}

Comprobamos si hay posiciones abiertas utilizando la función PositionsTotal. Si el número de posiciones es mayor que cero, llamamos a la función «ManageGridPositions» para gestionar las operaciones adicionales de la cuadrícula. La función toma «ask» y «bid» como parámetros para determinar los niveles de precios adecuados para colocar nuevas órdenes en la red en función de los movimientos del mercado. La implementación del fragmento de código de la función es la siguiente.

//+------------------------------------------------------------------------+
//--- Function: ManageGridPositions
//--- Description: When an initial position exists, grid orders are added 
//---              if the market moves to the grid level. (These orders are 
//---              considered "grid positions.") The lot size is doubled 
//---              with each grid order.
//+------------------------------------------------------------------------+
void ManageGridPositions(double ask, double bid){
   for(int i = PositionsTotal()-1; i >= 0; i--){
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket)){
         int positionType = (int)PositionGetInteger(POSITION_TYPE);
         //--- Grid management for BUY positions
         if(positionType == POSITION_TYPE_BUY){
            if(ask <= gridSize){
               LotSize *= 2;              //--- Increase lot size for grid order
               if(obj_Trade.Buy(LotSize, _Symbol, ask, 0, TakeProfit,"Grid Position BUY"))
                  Print("Grid BUY order executed at ", ask, " with LotSize: ", LotSize);
               else
                  Print("Grid BUY order failed at ", ask);
               gridSize = ask - gridSize_Spacing; //--- Update grid trigger
            }
         }
         //--- Grid management for SELL positions
         else if(positionType == POSITION_TYPE_SELL){
            if(bid >= gridSize){
               LotSize *= 2;              //--- Increase lot size for grid order
               if(obj_Trade.Sell(LotSize, _Symbol, bid, 0, TakeProfit,"Grid Position SELL"))
                  Print("Grid SELL order executed at ", bid, " with LotSize: ", LotSize);
               else
                  Print("Grid SELL order failed at ", bid);
               gridSize = bid + gridSize_Spacing; //--- Update grid trigger
            }
         }
      }
   }
}

Implementamos la función «ManageGridPositions» para gestionar los pedidos de la red. Iteramos a través de todas las posiciones abiertas en orden inverso utilizando un bucle for y recuperamos el ticket de cada posición con la función PositionGetTicket. A continuación, seleccionamos la posición utilizando PositionSelectByTicket y determinamos si se trata de una operación de COMPRA o VENTA utilizando PositionGetInteger con el parámetro POSITION_TYPE. Si la posición es una COMPRA, comprobamos si el precio de mercado «ask» ha alcanzado o caído por debajo de «gridSize». Si es cierto, duplicamos «LotSize» y ejecutamos una nueva orden de COMPRA en la cuadrícula utilizando la función «obj_Trade.Buy». Si el pedido se realiza correctamente, imprimimos un mensaje de confirmación; de lo contrario, imprimimos un mensaje de error. A continuación, actualizamos «gridSize» al siguiente nivel de cuadrícula inferior.

Del mismo modo, si la posición es una VENTA, comprobamos si la «oferta» ha alcanzado o superado el «tamaño de la cuadrícula». Si es cierto, duplicamos «LotSize» y colocamos una nueva orden de VENTA en la cuadrícula utilizando «obj_Trade.Sell». A continuación, el activador de cuadrícula «gridSize» se actualiza al siguiente nivel superior. Después de abrir las posiciones de la cuadrícula, debemos realizar un seguimiento y gestionar las posiciones cerrándolas una vez que alcancemos los valores definidos a continuación.

//--- Check if total profit meets the target (only used if closureMode == CLOSE_BY_PROFIT)
if(closureMode == CLOSE_BY_PROFIT)
   CheckAndCloseProfitTargets();

Si «closureMode» se establece en «CLOSE_BY_PROFIT», llamamos a la función «CheckAndCloseProfitTargets» para comprobar si el beneficio total ha alcanzado el objetivo predefinido y cerrar todas las posiciones en consecuencia. La declaración de la función es la siguiente.

//+----------------------------------------------------------------------------+
//--- Function: CheckAndCloseProfitTargets
//--- Description: Closes all positions if the combined profit meets or exceeds
//---              the user-defined profit target.
//+----------------------------------------------------------------------------+
void CheckAndCloseProfitTargets(){
   if(PositionsTotal() > 1){
      double totalProfit = 0;
      for(int i = PositionsTotal()-1; i >= 0; i--){
         ulong tkt = PositionGetTicket(i);
         if(PositionSelectByTicket(tkt))
            totalProfit += PositionGetDouble(POSITION_PROFIT);
      }
      if(totalProfit >= profitTotal_inCurrency){
         Print("Profit target reached (", totalProfit, "). Closing all positions.");
         CloseAllPositions();
      }
   }
}

Para garantizar que todas las posiciones se cierren si el beneficio acumulado total alcanza o supera el objetivo de beneficio predefinido, primero comprobamos si hay más de una posición abierta utilizando PositionsTotal. Inicializamos «totalProfit» para realizar un seguimiento de los beneficios combinados de todas las posiciones. A continuación, recorremos todas las posiciones abiertas, recuperando el ticket de cada posición mediante PositionGetTicket y seleccionándolo con PositionSelectByTicket. Para cada posición seleccionada, recuperamos su beneficio utilizando PositionGetDouble con el parámetro POSITION_PROFIT y lo añadimos a «totalProfit». Si «totalProfit» es igual o superior a «profitTotal_inCurrency», imprimimos un mensaje indicando que se ha alcanzado el objetivo de beneficio y llamamos a la función «CloseAllPositions», cuya definición es la siguiente, para cerrar todas las operaciones abiertas.

//+------------------------------------------------------------------+
//--- Function: CloseAllPositions
//--- Description: Iterates through and closes all open positions.
//+------------------------------------------------------------------+
void CloseAllPositions(){
   for(int i = PositionsTotal()-1; i >= 0; i--){
      ulong posTkt = PositionGetTicket(i);
      if(PositionSelectByTicket(posTkt)){
         if(obj_Trade.PositionClose(posTkt))
            Print("Closed position ticket: ", posTkt);
         else
            Print("Failed to close position ticket: ", posTkt);
      }
   }
}

La función simplemente recorre todas las posiciones abiertas y, para cada posición seleccionada, se cierra utilizando el método «obj_Trade.PositionClose». Por último, definimos la lógica para cerrar las posiciones en el punto de equilibrio.

//--- If using CLOSE_BY_POINTS and more than one position exists (i.e. grid), check breakeven closure
if(closureMode == CLOSE_BY_POINTS && PositionsTotal() > 1)
   CheckBreakevenClose(ask, bid);

Si «closureMode» se establece en «CLOSE_BY_POINTS» y hay más de una posición abierta, llamamos a la función «CheckBreakevenClose» con los parámetros «ask» y «bid» para determinar si el precio ha alcanzado el umbral de equilibrio, lo que permite cerrar posiciones en función de puntos predefinidos a partir del equilibrio. A continuación se muestra la definición de la función.

//+----------------------------------------------------------------------------+
//--- Function: CalculateWeightedBreakevenPrice
//--- Description: Calculates the weighted average entry price (breakeven)
//---              of all open positions (assumed to be in the same direction).
//+----------------------------------------------------------------------------+
double CalculateWeightedBreakevenPrice(){
   double totalCost = 0;
   double totalVolume = 0;
   int posType = -1;
   //--- Determine the type from the first position
   for(int i = 0; i < PositionsTotal(); i++){
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket)){
         posType = (int)PositionGetInteger(POSITION_TYPE);
         break;
      }
   }
   //--- Sum the cost and volume for positions matching the type
   for(int i = 0; i < PositionsTotal(); i++){
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket)){
         if(PositionGetInteger(POSITION_TYPE) == posType){
            double price = PositionGetDouble(POSITION_PRICE_OPEN);
            double volume = PositionGetDouble(POSITION_VOLUME);
            totalCost += price * volume;
            totalVolume += volume;
         }
      }
   }
   if(totalVolume > 0)
      return(totalCost / totalVolume);
   else
      return(0);
}
  
//+-----------------------------------------------------------------------------+
//--- Function: CheckBreakevenClose
//--- Description: When using CLOSE_BY_POINTS and multiple positions exist,
//---              calculates the weighted breakeven price and checks if the
//---              current price has moved the specified points in a profitable
//---              direction relative to breakeven. If so, closes all positions.
//+-----------------------------------------------------------------------------+
void CheckBreakevenClose(double ask, double bid){
   //--- Ensure we have more than one position (grid positions)
   if(PositionsTotal() <= 1)
      return;
      
   double weightedBreakeven = CalculateWeightedBreakevenPrice();
   int posType = -1;
   //--- Determine the trade type from one of the positions
   for(int i = 0; i < PositionsTotal(); i++){
      ulong ticket = PositionGetTicket(i);
      if(PositionSelectByTicket(ticket)){
         posType = (int)PositionGetInteger(POSITION_TYPE);
         break;
      }
   }
   if(posType == -1)
      return;
      
   //--- For BUY positions, profit when Bid >= breakeven + threshold
   if(posType == POSITION_TYPE_BUY){
      if(bid >= weightedBreakeven + breakevenPoints){
         Print("Closing BUY positions: Bid (", bid, ") >= Breakeven (", weightedBreakeven, ") + ", breakevenPoints);
         CloseAllPositions();
      }
   }
   //--- For SELL positions, profit when Ask <= breakeven - threshold
   else if(posType == POSITION_TYPE_SELL){
      if(ask <= weightedBreakeven - breakevenPoints){
         Print("Closing SELL positions: Ask (", ask, ") <= Breakeven (", weightedBreakeven, ") - ", breakevenPoints);
         CloseAllPositions();
      }
   }
}

Aquí calculamos el precio de equilibrio para todas las posiciones abiertas y determinamos si el precio de mercado se ha movido una distancia específica más allá de él para cerrar posiciones con fines lucrativos. En «CalculateWeightedBreakevenPrice», calculamos el precio de equilibrio ponderado sumando el coste total de todas las posiciones abiertas utilizando POSITION_PRICE_OPEN y ponderándolo por «POSITION_VOLUME». Primero determinamos el tipo de posición (COMPRA o VENTA) a partir de la primera posición abierta utilizando POSITION_TYPE. A continuación, recorremos todas las posiciones, sumando el coste total y el volumen de las posiciones que coinciden con el tipo identificado. Si el volumen total es mayor que cero, devolvemos el precio de equilibrio ponderado dividiendo el coste total por el volumen total. De lo contrario, devolvemos cero.

En «CheckBreakevenClose», primero confirmamos que hay varias posiciones abiertas utilizando la función PositionsTotal. A continuación, recuperamos el precio de equilibrio ponderado llamando a «CalculateWeightedBreakevenPrice». Determinamos el tipo de posición seleccionando una posición y recuperando POSITION_TYPE. Si el tipo no es válido, salimos de la función. Para las posiciones de COMPRA, comprobamos si el precio de «oferta» ha alcanzado o superado el «punto de equilibrio ponderado» más los «puntos de equilibrio». Si es así, imprimimos un mensaje y llamamos a «CloseAllPositions». Para las posiciones de VENTA, comprobamos si el precio de «venta» ha caído por debajo del «punto de equilibrio ponderado» menos los «puntos de equilibrio». Si se cumple esta condición, también imprimimos un mensaje y llamamos a la función «CloseAllPositions» para asegurar las ganancias. Tras compilar y ejecutar el programa, obtenemos el siguiente resultado.

GIF CUADRÍCULA

A partir de la visualización, podemos ver que las posiciones se abren y gestionan a través del sistema de cuadrícula y se cierran cuando se alcanzan los niveles de cierre definidos, logrando así nuestro objetivo de crear un sistema de cuadrícula con un tamaño de lote dinámico. Lo que queda por hacer es realizar pruebas retrospectivas del programa, lo cual se aborda en la siguiente sección.


Pruebas retrospectivas

Tras realizar exhaustivas pruebas retrospectivas, hemos obtenido los siguientes resultados.

Gráfico de prueba retrospectiva:

GRÁFICO

Informe de prueba retrospectiva:

INFORME

Aquí también hay un vídeo que muestra toda la estrategia de backtest en un periodo de 1 año, 2024.



Conclusión

En conclusión, hemos demostrado el proceso de desarrollo de un Asesor Experto (EA) MQL5 utilizando una estrategia dinámica de trading con cuadrículas. Al combinar elementos clave como la colocación de órdenes en la red, el escalado dinámico de lotes y la gestión específica de beneficios y umbrales de rentabilidad, hemos creado un sistema que se adapta a las fluctuaciones del mercado con el objetivo de optimizar la relación riesgo-recompensa y recuperarse de movimientos adversos de los precios.

Descargo de responsabilidad: Este artículo tiene fines exclusivamente educativos. El trading conlleva un riesgo financiero significativo y el comportamiento del mercado puede ser muy impredecible. Si bien las estrategias descritas ofrecen un enfoque estructurado para el comercio en red, no garantizan la rentabilidad futura. Es esencial realizar rigurosas pruebas retrospectivas y gestionar los riesgos antes de operar en vivo.

Al implementar estas técnicas, podrá perfeccionar sus sistemas de trading con cuadrículas, mejorar su análisis de mercado y elevar sus estrategias de trading algorítmico. ¡Mucha suerte en tu aventura en el mundo del trading!

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/17190

testtestmio71
testtestmio71 | 4 abr 2025 en 15:19

está bien.....best EA .

Las 4 líneas son confusas para un novato

Allan Munene Mutiiria
Allan Munene Mutiiria | 4 abr 2025 en 16:35
testtestmio71 #:

está bien.....best EA .

Las 4 líneas son confusas para un novato

Ok

Roman Shiredchenko
Roman Shiredchenko | 20 oct 2025 en 02:54

gran artículo - muchas gracias... ¡Estoy estudiando el enfoque comercial que voy a poner con ediciones en mis propios oficios en símbolos hashedge personalizados!

comprobación....

Allan Munene Mutiiria
Allan Munene Mutiiria | 20 oct 2025 en 14:12
Roman Shiredchenko símbolos hashedge personalizados!

comprobación....

Bien. Bienvenido.
Allan Munene Mutiiria
Allan Munene Mutiiria | 30 oct 2025 en 11:32
Roman Shiredchenko símbolos hash personalizados en mi propio comercio!

examinar....

Gracias y bienvenido.
Redes neuronales en el trading: Clusterización doble de series temporales (DUET) Redes neuronales en el trading: Clusterización doble de series temporales (DUET)
El framework DUET ofrece un enfoque innovador del análisis de series temporales, combinando la clusterización temporal y por canales para revelar patrones ocultos en los datos analizados. Esto permite a los modelos adaptarse a los cambios a lo largo del tiempo y mejorar la calidad de las previsiones eliminando el ruido.
Operaciones de arbitraje en Forex: Panel de evaluación de correlaciones Operaciones de arbitraje en Forex: Panel de evaluación de correlaciones
Hoy analizaremos la creación de un panel de arbitraje en el lenguaje MQL5. ¿Cómo obtener tipos de cambio justos en Forex de formas diferentes? En esta ocasión, crearemos un indicador para obtener las desviaciones de los precios de mercado respecto a los tipos justos, y para estimar el beneficio de las vías de arbitraje para cambiar una divisa por otra (como en el arbitraje triangular).
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 14): Herramienta Parabolic SAR (Stop and Reverse) Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 14): Herramienta Parabolic SAR (Stop and Reverse)
Incorporar indicadores técnicos en el análisis de la acción del precio es un enfoque muy eficaz. Estos indicadores suelen resaltar niveles clave de reversiones y retrocesos, lo que ofrece información valiosa sobre la dinámica del mercado. En este artículo, mostramos cómo desarrollamos una herramienta automatizada que genera señales utilizando el indicador Parabolic SAR.
Cierres parciales condicionales (Parte 1): Creación de la clase base Cierres parciales condicionales (Parte 1): Creación de la clase base
En este artículo implementaremos un nuevo método para la gestión de posiciones, parecido a los cierres parciales "simples" que implementamos anteriormente, pero con una diferencia importante. En lugar de basarse en niveles de takeprofit fijos, este enfoque aplica los cierres parciales al momento de cumplirse cierta condición específica. De ahí su nombre: "Cierres parciales condicionales". En esta primera parte de la implementación en MQL5 veremos cómo funciona esta técnica de gestión de posiciones.