English Русский 中文 Deutsch 日本語
preview
Pruebas retrospectivas manuales simplificadas: herramientas personalizadas en MQL5 para el Probador de Estrategias

Pruebas retrospectivas manuales simplificadas: herramientas personalizadas en MQL5 para el Probador de Estrategias

MetaTrader 5Probador |
87 6
Allan Munene Mutiiria
Allan Munene Mutiiria

Introducción

La prueba retrospectiva de estrategias de trading es un pilar del trading exitoso, pero automatizar cada idea puede resultar limitante, mientras que las pruebas manuales suelen carecer de estructura y precisión. ¿Y si pudieras combinar el control del trading manual con la potencia del Probador de Estrategias de MetaTrader 5? En este artículo presentamos un Asesor Experto (Expert Advisor, EA) personalizado MetaQuotes Language 5 (MQL5) que transforma el backtesting manual en un proceso intuitivo y eficiente, proporcionándole un conjunto de herramientas para probar estrategias según sus propios criterios. Cubriremos estos pasos en este orden:

  1. El plan: Diseñar un kit de herramientas para el backtesting manual
  2. Implementación en MQL5: dando vida al kit de herramientas
  3. Backtesting en acción: uso del kit de herramientas
  4. Conclusión

Al final, dispondrá de una solución práctica para realizar backtests y perfeccionar sus ideas de trading de forma rápida y segura en el Probador de Estrategias.


El plan: Diseñar un kit de herramientas para el backtesting manual

Nuestro objetivo es crear un conjunto de herramientas que combine el control manual con la rápida velocidad de backtesting del Probador de Estrategias en MetaTrader 5, evitando los lentos ticks en tiempo real de las pruebas manuales tradicionales. Diseñaremos el programa con botones en el gráfico para activar operaciones de compra o venta, ajustar el tamaño de los lotes, establecer los niveles de Stop Loss (SL) y Take Profit (TP), y cerrar todas las posiciones mediante un botón de pánico, totalmente integrable con cualquier estrategia, desde indicadores y patrones de velas japonesas hasta la acción del precio, todo ello funcionando al ritmo acelerado del Probador. Esta configuración flexible nos permitirá probar cualquier enfoque comercial de forma interactiva, con rapidez y precisión, optimizando el perfeccionamiento de la estrategia en un entorno simulado. En pocas palabras, aquí hay una visualización de lo que pretendemos:

PLAN


Implementación en MQL5: dando vida al kit de herramientas

Para crear el programa en MQL5, tendremos que definir los metadatos del programa y, a continuación, definir algunos parámetros de entrada del usuario y, por último, incluir algunos archivos de biblioteca que nos permitirán realizar la actividad de trading.

//+------------------------------------------------------------------+
//|                       Manual backtest toolkit in Strategy Tester |
//|                        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 Enables manual backtest in the strategy tester"
#property strict //--- Enforce strict coding rules to catch errors early

#define BTN_BUY "BTN BUY" //--- Define the name for the Buy button
#define BTN_SELL "BTN SELL" //--- Define the name for the Sell button
#define BTN_P "BTN P" //--- Define the name for the button that increase lot size
#define BTN_M "BTN M" //--- Define the name for the button that decrease lot size
#define BTN_LOT "BTN LOT" //--- Define the name for the lot size display button
#define BTN_CLOSE "BTN CLOSE" //--- Define the name for the button that close all positions
#define BTN_SL "BTN SL" //--- Define the name for the Stop Loss display button
#define BTN_SL1M "BTN SL1M" //--- Define the name for the button that slightly lower Stop Loss
#define BTN_SL2M "BTN SL2M" //--- Define the name for the button that greatly lower Stop Loss
#define BTN_SL1P "BTN SL1P" //--- Define the name for the button that slightly raise Stop Loss
#define BTN_SL2P "BTN SL2P" //--- Define the name for the button that greatly raise Stop Loss
#define BTN_TP "BTN TP" //--- Define the name for the Take Profit display button
#define BTN_TP1M "BTN TP1M" //--- Define the name for the button that slightly lower Take Profit
#define BTN_TP2M "BTN TP2M" //--- Define the name for the button that greatly lower Take Profit
#define BTN_TP1P "BTN TP1P" //--- Define the name for the button that slightly raise Take Profit
#define BTN_TP2P "BTN TP2P" //--- Define the name for the button that greatly raise Take Profit
#define BTN_YES "BTN YES" //--- Define the name for the button that confirm a trade
#define BTN_NO "BTN NO" //--- Define the name for the button that cancel a trade
#define BTN_IDLE "BTN IDLE" //--- Define the name for the idle button between Yes and No
#define HL_SL "HL SL" //--- Define the name for the Stop Loss horizontal line
#define HL_TP "HL TP" //--- Define the name for the Take Profit horizontal line

#include <Trade/Trade.mqh> //--- Bring in the Trade library needed for trading functions
CTrade obj_Trade; //--- Create a trading object to handle trade operations

bool tradeInAction = false; //--- Track whether a trade setup is currently active
bool isHaveTradeLevels = false; //--- Track whether Stop Loss and Take Profit levels are shown

input double init_lot = 0.03;
input int slow_pts = 10;
input int fast_pts = 100;

Aquí comenzamos definiendo un conjunto de botones interactivos como «BTN_BUY» y «BTN_SELL» utilizando la palabra clave #define para iniciar operaciones cuando queramos, lo que nos permite controlar directamente los puntos de entrada, mientras que «BTN_P» y «BTN_M» nos permiten ajustar el tamaño de «init_lot» (establecido inicialmente en 0,03) al alza o a la baja para adaptarlo a nuestra tolerancia de riesgo. También incluimos «BTN_CLOSE» como nuestra salida de emergencia, una forma rápida de cerrar todas las posiciones en un instante, y confiamos en «tradeInAction» para controlar si estamos en medio de la configuración de una operación y «isHaveTradeLevels» para indicar cuándo están activos los indicadores visuales de Stop Loss y Take Profit.

A continuación, accedemos a la clase «CTrade» desde «<Trade/Trade.mqh>» para crear un objeto «obj_Trade» que gestione la ejecución de las operaciones de forma fluida y eficiente. Para tener aún más flexibilidad, añadimos entradas ajustables como «slow_pts» a 10 y «fast_pts» a 100, de modo que podemos ajustar nuestros niveles de Stop Loss y Take Profit sobre la marcha, asegurándonos de que nuestro kit de herramientas se adapta a cualquier estrategia que estemos probando. Ahora, dado que tendremos que crear los botones del panel, creemos una función con todas las entradas posibles para poder reutilizarla y personalizarla.

//+------------------------------------------------------------------+
//| Create button function                                           |
//+------------------------------------------------------------------+
void CreateBtn(string objName,int xD,int yD,int xS,int yS,string txt,
               int fs=13,color clrTxt=clrWhite,color clrBg=clrBlack,
               color clrBd=clrBlack,string font="Calibri"){
   ObjectCreate(0,objName,OBJ_BUTTON,0,0,0); //--- Create a new button object on the chart
   ObjectSetInteger(0,objName,OBJPROP_XDISTANCE, xD); //--- Set the button's horizontal position
   ObjectSetInteger(0,objName,OBJPROP_YDISTANCE, yD); //--- Set the button's vertical position
   ObjectSetInteger(0,objName,OBJPROP_XSIZE, xS); //--- Set the button's width
   ObjectSetInteger(0,objName,OBJPROP_YSIZE, yS); //--- Set the button's height
   ObjectSetInteger(0,objName,OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Position the button from the top-left corner
   ObjectSetString(0,objName,OBJPROP_TEXT, txt); //--- Set the text displayed on the button
   ObjectSetInteger(0,objName,OBJPROP_FONTSIZE, fs); //--- Set the font size of the button text
   ObjectSetInteger(0,objName,OBJPROP_COLOR, clrTxt); //--- Set the color of the button text
   ObjectSetInteger(0,objName,OBJPROP_BGCOLOR, clrBg); //--- Set the background color of the button
   ObjectSetInteger(0,objName,OBJPROP_BORDER_COLOR,clrBd); //--- Set the border color of the button
   ObjectSetString(0,objName,OBJPROP_FONT,font); //--- Set the font style of the button text

   ChartRedraw(0); //--- Refresh the chart to show the new button
}

Aquí definimos la función «CreateBtn» para crear todos los botones (como «BTN_BUY» o «BTN_SELL») del gráfico, tomando entradas como «objName» para la identidad del botón, «xD» e «yD» para sus posiciones horizontal y vertical, «xS» e «yS» para su anchura y altura, y «txt» para la etiqueta que queremos mostrar, como «BUY» o «SELL». Para ello, utilizamos la función ObjectCreate para colocar un nuevo objeto OBJ_BUTTON en el gráfico, estableciendo su base en las coordenadas (0,0,0) para simplificar. A continuación, lo posicionamos con precisión con ObjectSetInteger para ajustar «OBJPROP_XDISTANCE» a «xD» y «OBJPROP_YDISTANCE» a «yD», asegurándonos de que quede exactamente donde lo necesitamos, y le damos tamaño utilizando «OBJPROP_XSIZE» para «xS» y «OBJPROP_YSIZE» para «yS» para que se ajuste a nuestro diseño.

Lo anclamos a la esquina superior izquierda con «OBJPROP_CORNER» establecido en CORNER_LEFT_UPPER, lo que hace que el diseño sea coherente, y utilizamos ObjectSetString para asignar «OBJPROP_TEXT» como «txt», de modo que el botón muestre claramente su finalidad. Para el estilo, ajustamos «OBJPROP_FONTSIZE» a «fs» (por defecto 13), «OBJPROP_COLOR» a «clrTxt» (por defecto, blanco) para el texto, OBJPROP_BGCOLOR a «clrBg» (por defecto, negro) para el fondo y «OBJPROP_BORDER_COLOR» a «clrBd» (por defecto, negro) para el contorno, mientras que «OBJPROP_FONT» obtiene «font» (por defecto, «Calibri») para un aspecto limpio. Por último, utilizamos la función ChartRedraw para actualizar el gráfico con «0» como ID de ventana, mostrando al instante nuestro nuevo botón para que podamos interactuar con él en el Probador de Estrategias. Ahora podemos llamar a la función cada vez que queramos crear un botón, y comenzaremos llamándola en el controlador de eventos OnInit.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
   CreateBtn(BTN_P,150,45,40,25,CharToString(217),15,clrBlack,clrWhite,clrBlack,"Wingdings"); //--- Make the button to increase lot size with an up arrow
   CreateBtn(BTN_LOT,190,45,60,25,string(init_lot),12,clrWhite,clrGray,clrBlack); //--- Make the button showing the current lot size
   CreateBtn(BTN_M,250,45,40,25,CharToString(218),15,clrBlack,clrWhite,clrBlack,"Wingdings"); //--- Make the button to decrease lot size with a down arrow
   CreateBtn(BTN_BUY,110,70,110,30,"BUY",15,clrWhite,clrGreen,clrBlack); //--- Make the Buy button with a green background
   CreateBtn(BTN_SELL,220,70,110,30,"SELL",15,clrWhite,clrRed,clrBlack); //--- Make the Sell button with a red background
   CreateBtn(BTN_CLOSE,110,100,220,30,"PANIC BUTTON (X)",15,clrWhite,clrBlack,clrBlack); //--- Make the emergency button to close all trades
         
   return(INIT_SUCCEEDED); //--- Tell the system the EA start up successfully
}

Aquí, iniciamos nuestro kit de herramientas de backtesting manual con el controlador de eventos OnInit, configurando su interfaz en el Probador de Estrategias. Utilizamos la función «CreateBtn» para colocar «BTN_P» en «xD» 150, «yD» 45 con una flecha hacia arriba desde CharToString(217) en «Wingdings», «BTN_LOT» en «xD» 190 mostrando «init_lot» y «BTN_M» en «xD» 250 con una flecha hacia abajo de «CharToString(218)», todo ello con estilo para el control del tamaño del lote. A continuación, añadimos «BTN_BUY» en «xD» 110, «yD» 70 con «BUY» en «clrGreen», «BTN_SELL» en «xD» 220 con «SELL» en «clrRed» y «BTN_CLOSE» en «xD» 110, «yD» 100 como «PANIC BUTTON (X)» en «clrBlack», antes de indicar el éxito con «return» e INIT_SUCCEEDED. La fuente Wingdings que utilizamos para los iconos de la tabla de caracteres ya definida de MQL5 es la siguiente.

WINGDINGS

Cuando ejecutamos el programa, obtenemos el siguiente resultado.

INTERFAZ DE BOTONES

Una vez establecidos los fundamentos, debemos leer los estados de los botones y los valores para poder utilizarlos con fines comerciales. Por lo tanto, también necesitamos algunas funciones para eso.

int GetState(string Name){return (int)ObjectGetInteger(0,Name,OBJPROP_STATE);} //--- Get whether a button is pressed or not
string GetValue(string Name){return ObjectGetString(0,Name,OBJPROP_TEXT);} //--- Get the text shown on an object
double GetValueHL(string Name){return ObjectGetDouble(0,Name,OBJPROP_PRICE);} //--- Get the price level of a horizontal line

Aquí definimos la función «GetState» para comprobar los clics en los botones, donde utilizamos la función ObjectGetInteger con «OBJPROP_STATE» para devolver si se pulsa «Name», «GetValue» para obtener el texto de «Name» utilizando «ObjectGetString» con «OBJPROP_TEXT», y «GetValueHL» para obtener los niveles de precio de «Name» con ObjectGetDouble utilizando OBJPROP_PRICE para un control preciso de las operaciones. Ahora podemos utilizar las funciones para obtener los estados de los botones en el controlador de eventos OnTick, ya que no podemos utilizar directamente el controlador de eventos OnChartEvent en el Probador de Estrategias. Así es como lo conseguimos.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get the current Ask price and adjust it to the right decimal places
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get the current Bid price and adjust it to the right decimal places

   if (GetState(BTN_BUY)==true || GetState(BTN_SELL)){ //--- Check if either the Buy or Sell button is clicked
      tradeInAction = true; //--- Set trade setup to active
   }
}

Aquí, utilizamos el controlador de eventos OnTick para impulsar las acciones en tiempo real de nuestro kit de herramientas en el Probador de Estrategias, donde utilizamos la función NormalizeDouble con SymbolInfoDouble para establecer «Ask» en el precio actual «SYMBOL_ASK» y «Bid» al precio SYMBOL_BID, ambos ajustados a _Digits para mayor precisión, y si «GetState» muestra «BTN_BUY» o «BTN_SELL» como verdadero, establecemos «tradeInAction» como verdadero para iniciar nuestra configuración de operaciones. Este es el punto en el que necesitamos niveles comerciales adicionales que nos permitan establecer los niveles y ajustarlos dinámicamente. Hagamos una función para eso.

//+------------------------------------------------------------------+
//| Create high low function                                         |
//+------------------------------------------------------------------+
void createHL(string objName,datetime time1,double price1,color clr){
   if (ObjectFind(0,objName) < 0){ //--- Check if the horizontal line doesn’t already exist
      ObjectCreate(0,objName,OBJ_HLINE,0,time1,price1); //--- Create a new horizontal line at the specified price
      ObjectSetInteger(0,objName,OBJPROP_TIME,time1); //--- Set the time property (though not critical for HLINE)
      ObjectSetDouble(0,objName,OBJPROP_PRICE,price1); //--- Set the price level of the horizontal line
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr); //--- Set the color of the line (red for SL, green for TP)
      ObjectSetInteger(0,objName,OBJPROP_STYLE,STYLE_DASHDOTDOT); //--- Set the line style to dash-dot-dot
      ChartRedraw(0); //--- Refresh the chart to display the new line
   }
}

En primer lugar, definimos la función «createHL» para dibujar líneas horizontales para nuestro kit de herramientas en el Probador de Estrategias, donde utilizamos la función ObjectFind para comprobar si «objName» existe y, si es menor que 0, utilizamos la función ObjectCreate para crear un «OBJ_HLINE» en «time1» y «price1». utilizamos la función ObjectSetInteger para establecer «OBJPROP_TIME» en «time1», «OBJPROP_COLOR» en «clr» y «OBJPROP_STYLE» en «STYLE_DASHDOTDOT», utilizamos la función ObjectSetDouble para establecer «OBJPROP_PRICE» en «price1», y utilizamos la función ChartRedraw para actualizar el gráfico con «0» y mostrarlo. A continuación, integramos esta función en otra función para crear los niveles comerciales de forma fluida, como se muestra a continuación.

//+------------------------------------------------------------------+
//| Create trade levels function                                     |
//+------------------------------------------------------------------+
void CreateTradeLevels(){
   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get unnoticed the current Ask price, adjusted for digits
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get the current Bid price, adjusted for digits
   
   string level_SL,level_TP; //--- Declare variables to hold SL and TP levels as strings
   if (GetState(BTN_BUY)==true){ //--- Check if the Buy button is active
      level_SL = string(Bid-100*_Point); //--- Set initial Stop Loss 100 points below Bid for Buy
      level_TP = string(Bid+100*_Point); //--- Set initial Take Profit 100 points above Bid for Buy
   }
   else if (GetState(BTN_SELL)==true){ //--- Check if the Sell button is active
      level_SL = string(Ask+100*_Point); //--- Set initial Stop Loss 100 points above Ask for Sell
      level_TP = string(Ask-100*_Point); //--- Set initial Take Profit 100 points below Ask for Sell
   }
   
   createHL(HL_SL,0,double(level_SL),clrRed); //--- Create a red Stop Loss line at the calculated level
   createHL(HL_TP,0,double(level_TP),clrGreen); //--- Create a green Take Profit line at the calculated level

   CreateBtn(BTN_SL,110,135,110,23,"SL: "+GetValue(HL_SL),13,clrRed,clrWhite,clrRed); //--- Make a button showing the Stop Loss level
   CreateBtn(BTN_TP,220,135,110,23,"TP: "+GetValue(HL_TP),13,clrGreen,clrWhite,clrGreen); //--- Make a button showing the Take Profit level
   
   CreateBtn(BTN_SL1M,110,158,27,20,"-",17,clrBlack,clrWhite,clrGray); //--- Make a button to slightly lower Stop Loss
   CreateBtn(BTN_SL2M,137,158,27,20,"--",17,clrBlack,clrWhite,clrGray); //--- Make a button to greatly lower Stop Loss
   CreateBtn(BTN_SL2P,164,158,27,20,"++",17,clrBlack,clrWhite,clrGray); //--- Make a button to greatly raise Stop Loss
   CreateBtn(BTN_SL1P,191,158,27,20,"+",17,clrBlack,clrWhite,clrGray); //--- Make a button to slightly raise Stop Loss
   
   CreateBtn(BTN_TP1P,222,158,27,20,"+",17,clrBlack,clrWhite,clrGray); //--- Make a button to slightly raise Take Profit
   CreateBtn(BTN_TP2P,249,158,27,20,"++",17,clrBlack,clrWhite,clrGray); //--- Make a button to greatly raise Take Profit
   CreateBtn(BTN_TP2M,276,158,27,20,"--",17,clrBlack,clrWhite,clrGray); //--- Make a button to greatly lower Take Profit
   CreateBtn(BTN_TP1M,303,158,27,20,"-",17,clrBlack,clrWhite,clrGray); //--- Make a button to slightly lower Take Profit
   
   CreateBtn(BTN_YES,110,178,70,30,CharToString(254),20,clrWhite,clrDarkGreen,clrWhite,"Wingdings"); //--- Make a green checkmark button to confirm the trade
   CreateBtn(BTN_NO,260,178,70,30,CharToString(253),20,clrWhite,clrDarkRed,clrWhite,"Wingdings"); //--- Make a red X button to cancel the trade
   CreateBtn(BTN_IDLE,180,183,80,25,CharToString(40),20,clrWhite,clrBlack,clrWhite,"Wingdings"); //--- Make a neutral button between Yes and No
}

Aquí definimos la función «CreateTradeLevels» para configurar nuestros niveles de negociación, donde utilizamos la función NormalizeDouble con SymbolInfoDouble para establecer «Ask» en «SYMBOL_ASK» y «Bid» en «SYMBOL_BID», ajustado por _Digits, y declaramos «level_SL» y «level_TP» como cadenas. Si «GetState» muestra «BTN_BUY» como verdadero, establecemos «level_SL» en «Bid-100_Point» y «level_TP» en «Bid+100_Point», pero si «BTN_SELL» es verdadero, establecemos «level_SL» en «Ask+100_Point» y «level_TP» en «Ask-100_Point».

Utilizamos la función «createHL» para dibujar «HL_SL» en «double(level_SL)» en «clrRed» y «HL_TP» en «double(level_TP)» en «clrGreen», y luego utilizamos la función «CreateBtn» para crear botones como «BTN_SL» con el texto «GetValue(HL_SL)», «BTN_TP» con «GetValue(HL_TP)» y botones de ajuste «BTN_SL1M», «BTN_SL2M», «BTN_SL2P», «BTN_SL1P», «BTN_TP1P», «BTN_TP2P», «BTN_TP2M» y «BTN_TP1M» con símbolos como «-» y «+», además de «BTN_YES», «BTN_NO» y «BTN_IDLE» utilizando CharToString para las opciones de confirmar, cancelar y neutral en «Wingdings». Con esta función, podemos activarla cuando se pulsa el botón de compra o venta para inicializar la configuración del nivel de negociación.

if (!isHaveTradeLevels){ //--- Check if trade levels aren't already on the chart
   CreateTradeLevels(); //--- Add Stop Loss and Take Profit levels and controls to the chart
   isHaveTradeLevels = true; //--- Mark that trade levels are now present
}

Aquí configuramos una comprobación en la que verificamos si «isHaveTradeLevels» es falso con «!isHaveTradeLevels» y, cuando lo es, utilizamos la función «CreateTradeLevels» para colocar controles Stop Loss y Take Profit en el gráfico, y luego actualizamos «isHaveTradeLevels» a verdadero para mostrar que están activos. Tras la compilación, obtenemos el siguiente resultado.

NIVELES DE COMERCIO

A continuación debemos dar vida a los botones de los niveles comerciales haciéndolos interactivos y asegurándonos de que funcionen correctamente. Así es como lo conseguimos.

if (tradeInAction){ //--- Continue if a trade setup is active

   // SL SLOW/FAST BUTTONS
   if (GetState(BTN_SL1M)){ //--- Check if the small Stop Loss decrease button is clicked
      ObjectSetDouble(0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)-slow_pts*_Point); //--- Move the Stop Loss down by a small amount
      ObjectSetInteger(0,BTN_SL1M,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_SL2M)){ //--- Check if the large Stop Loss decrease button is clicked
      ObjectSetDouble(0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)-fast_pts*_Point); //--- Move the Stop Loss down by a large amount
      ObjectSetInteger(0,BTN_SL2M,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_SL1P)){ //--- Check if the small Stop Loss increase button is clicked
      ObjectSetDouble(0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)+slow_pts*_Point); //--- Move the Stop Loss up by a small amount
      ObjectSetInteger(0,BTN_SL1P,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_SL2P)){ //--- Check if the large Stop Loss increase button is clicked
      ObjectSetDouble(0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)+fast_pts*_Point); //--- Move the Stop Loss up by a large amount
      ObjectSetInteger(0,BTN_SL2P,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   
   // TP SLOW/FAST BUTTONS
   if (GetState(BTN_TP1M)){ //--- Check if the small Take Profit decrease button is clicked
      ObjectSetDouble(0,HL_TP,OBJPROP_PRICE,GetValueHL(HL_TP)-slow_pts*_Point); //--- Move the Take Profit down by a small amount
      ObjectSetInteger(0,BTN_TP1M,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_TP2M)){ //--- Check if the large Take Profit decrease button is clicked
      ObjectSetDouble(0,HL_TP,OBJPROP_PRICE,GetValueHL(HL_TP)-fast_pts*_Point); //--- Move the Take Profit down by a large amount
      ObjectSetInteger(0,BTN_TP2M,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_TP1P)){ //--- Check if the small Take Profit increase button is clicked
      ObjectSetDouble(0,HL_TP,OBJPROP_PRICE,GetValueHL(HL_TP)+slow_pts*_Point); //--- Move the Take Profit up by a small amount
      ObjectSetInteger(0,BTN_TP1P,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_TP2P)){ //--- Check if the large Take Profit increase button is clicked
      ObjectSetDouble(0,HL_TP,OBJPROP_PRICE,GetValueHL(HL_TP)+fast_pts*_Point); //--- Move the Take Profit up by a large amount
      ObjectSetInteger(0,BTN_TP2P,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }

}

Aquí, gestionamos los ajustes de Stop Loss y Take Profit en nuestro kit de herramientas cuando «tradeInAction» es verdadero, donde utilizamos la función «GetState» para comprobar si se han pulsado botones como «BTN_SL1M», «BTN_SL2M», «BTN_SL1P» o «BTN_SL2P» y ajustamos «HL_SL» mediante «slow_pts_Point» o «fast_pts_Point» utilizando la función ObjectSetDouble con «OBJPROP_PRICE» y «GetValueHL», luego utiliza la función ObjectSetInteger para restablecer OBJPROP_STATE a falso y la función ChartRedraw para actualizar el gráfico, y de manera similar maneja «BTN_TP1M», «BTN_TP2M», «BTN_TP1P» o «BTN_TP2P» para los ajustes «HL_TP». Por último, una vez establecidos los niveles, podemos confirmar la colocación y abrir las posiciones respectivas, y luego limpiar la configuración del nivel de negociación, pero primero necesitaremos una función para eliminar el panel de configuración de niveles.

//+------------------------------------------------------------------+
//| Delete objects function                                          |
//+------------------------------------------------------------------+
void DeleteObjects_SLTP(){
   ObjectDelete(0,HL_SL); //--- Remove the Stop Loss line from the chart
   ObjectDelete(0,HL_TP); //--- Remove the Take Profit line from the chart
   ObjectDelete(0,BTN_SL); //--- Remove the Stop Loss display button
   ObjectDelete(0,BTN_TP); //--- Remove the Take Profit display button
   ObjectDelete(0,BTN_SL1M); //--- Remove the small Stop Loss decrease button
   ObjectDelete(0,BTN_SL2M); //--- Remove the large Stop Loss decrease button
   ObjectDelete(0,BTN_SL1P); //--- Remove the small Stop Loss increase button
   ObjectDelete(0,BTN_SL2P); //--- Remove the large Stop Loss increase button
   ObjectDelete(0,BTN_TP1P); //--- Remove the small Take Profit increase button
   ObjectDelete(0,BTN_TP2P); //--- Remove the large Take Profit increase button
   ObjectDelete(0,BTN_TP2M); //--- Remove the large Take Profit decrease button
   ObjectDelete(0,BTN_TP1M); //--- Remove the small Take Profit decrease button
   ObjectDelete(0,BTN_YES); //--- Remove the confirm trade button
   ObjectDelete(0,BTN_NO); //--- Remove the cancel trade button
   ObjectDelete(0,BTN_IDLE); //--- Remove the idle button
   
   ChartRedraw(0); //--- Refresh the chart to show all objects removed
}

Aquí, gestionamos la limpieza en nuestro kit de herramientas con la función «DeleteObjects_SLTP», donde utilizamos la función ObjectDelete para eliminar «HL_SL», «HL_TP», «BTN_SL», «BTN_TP», «BTN_SL1M», «BTN_SL2M», «BTN_SL1P», «BTN_SL2P», «BTN_TP1P», «BTN_TP2P», «BTN_TP2M», «BTN_TP1M», «BTN_YES», «BTN_NO» y «BTN_IDLE» del gráfico, y luego utilizamos la función ChartRedraw con «0» para actualizar y mostrar todo borrado. Ahora podemos utilizar esta función en nuestra lógica de colocación de órdenes.

// BUY ORDER PLACEMENT
if (GetState(BTN_BUY) && GetState(BTN_YES)){ //--- Check if both Buy and Yes buttons are clicked
   obj_Trade.Buy(double(GetValue(BTN_LOT)),_Symbol,Ask,GetValueHL(HL_SL),GetValueHL(HL_TP)); //--- Place a Buy order with set lot size, SL, and TP
   DeleteObjects_SLTP(); //--- Remove all trade level objects from the chart
   isHaveTradeLevels = false; //--- Mark that trade levels are no longer present
   ObjectSetInteger(0,BTN_YES,OBJPROP_STATE,false); //--- Turn off the Yes button press state
   ObjectSetInteger(0,BTN_BUY,OBJPROP_STATE,false); //--- Turn off the Buy button press state
   tradeInAction = false; //--- Mark the trade setup as complete
   ChartRedraw(0); //--- Refresh the chart to reflect changes
}
// SELL ORDER PLACEMENT
else if (GetState(BTN_SELL) && GetState(BTN_YES)){ //--- Check if both Sell and Yes buttons are clicked
   obj_Trade.Sell(double(GetValue(BTN_LOT)),_Symbol,Bid,GetValueHL(HL_SL),GetValueHL(HL_TP)); //--- Place a Sell order with set lot size, SL, and TP
   DeleteObjects_SLTP(); //--- Remove all trade level objects from the chart
   isHaveTradeLevels = false; //--- Mark that trade levels are no longer present
   ObjectSetInteger(0,BTN_YES,OBJPROP_STATE,false); //--- Turn off the Yes button press state
   ObjectSetInteger(0,BTN_SELL,OBJPROP_STATE,false); //--- Turn off the Sell button press state
   tradeInAction = false; //--- Mark the trade setup as complete
   ChartRedraw(0); //--- Refresh the chart to reflect changes
}
else if (GetState(BTN_NO)){ //--- Check if the No button is clicked to cancel
   DeleteObjects_SLTP(); //--- Remove all trade level objects from the chart
   isHaveTradeLevels = false; //--- Mark that trade levels are no longer present
   ObjectSetInteger(0,BTN_NO,OBJPROP_STATE,false); //--- Turn off the No button press state
   ObjectSetInteger(0,BTN_BUY,OBJPROP_STATE,false); //--- Turn off the Buy button press state
   ObjectSetInteger(0,BTN_SELL,OBJPROP_STATE,false); //--- Turn off the Sell button press state
   tradeInAction = false; //--- Mark the trade setup as canceled
   ChartRedraw(0); //--- Refresh the chart to reflect changes
}

Ejecutamos operaciones en nuestro kit de herramientas dentro del Probador de Estrategias, donde utilizamos la función «GetState» para comprobar si «BTN_BUY» y «BTN_YES» son verdaderos y, a continuación, utilizamos el método «obj_Trade. Buy» con «double(GetValue(BTN_LOT))», _Symbol, «Ask», «GetValueHL(HL_SL)» y «GetValueHL(HL_TP)» para colocar una orden de compra, o si «BTN_SELL» y «BTN_YES» son verdaderos, utilizamos «obj_Trade. Sell» con «Bid» en su lugar, y en cualquier caso, utilizamos la función «DeleteObjects_SLTP» para borrar objetos, establecemos «isHaveTradeLevels» y «tradeInAction» en falso, utilizamos la función ObjectSetInteger para restablecer OBJPROP_STATE en «BTN_YES», «BTN_BUY» o «BTN_SELL» en falso, y utilizamos la función ChartRedraw para actualizar el gráfico, pero si «BTN_NO» es verdadero, cancelamos borrando los objetos y restableciendo los estados de forma similar. Del mismo modo, gestionamos el aumento o la disminución de los botones de volumen de negociación de la siguiente manera.

if (GetState(BTN_P)==true){ //--- Check if the lot size increase button is clicked
   double newLot = (double)GetValue(BTN_LOT); //--- Get the current lot size as a number
   double lotStep = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); //--- Get the minimum lot size change allowed
   newLot += lotStep; //--- Increase the lot size by one step
   newLot = NormalizeDouble(newLot,2); //--- Round the new lot size to 2 decimal places
   newLot = newLot > 0.1 ? lotStep : newLot; //--- Ensure lot size doesn't exceed 0.1, otherwise reset to step
   ObjectSetString(0,BTN_LOT,OBJPROP_TEXT,string(newLot)); //--- Update the lot size display with the new value
   ObjectSetInteger(0,BTN_P,OBJPROP_STATE,false); //--- Turn off the increase button press state
   ChartRedraw(0); //--- Refresh the chart to show the new lot size
}
if (GetState(BTN_M)==true){ //--- Check if the lot size decrease button is clicked
   double newLot = (double)GetValue(BTN_LOT); //--- Get the current lot size as a number
   double lotStep = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); //--- Get the minimum lot size change allowed
   newLot -= lotStep; //--- Decrease the lot size by one step
   newLot = NormalizeDouble(newLot,2); //--- Round the new lot size to 2 decimal places
   newLot = newLot < lotStep ? lotStep : newLot; //--- Ensure lot size doesn't go below minimum, otherwise set to step
   ObjectSetString(0,BTN_LOT,OBJPROP_TEXT,string(newLot)); //--- Update the lot size display with the new value
   ObjectSetInteger(0,BTN_M,OBJPROP_STATE,false); //--- Turn off the decrease button press state
   ChartRedraw(0); //--- Refresh the chart to show the new lot size
}

Aquí ajustamos los tamaños de lote comenzando con el aumento, donde utilizamos la función «GetState» para comprobar si «BTN_P» es verdadero, luego utilizamos «GetValue» para establecer «newLot» desde «BTN_LOT», utilizamos SymbolInfoDouble para obtener «lotStep» desde SYMBOL_VOLUME_STEP, añadimos «lotStep» a «newLot» y utilizamos NormalizeDouble para redondearlo a 2 decimales, limitándolo a «lotStep» si es superior a 0,1 antes de utilizar ObjectSetString para actualizar «OBJPROP_TEXT» de «BTN_LOT» y «ObjectSetInteger» para restablecer «OBJPROP_STATE» de «BTN_P» a falso, seguido de ChartRedraw para actualizar.

Para la disminución, utilizamos «GetState» para comprobar «BTN_M», restamos «lotStep» de «newLot» después de obtenerlo de la misma manera, lo mantenemos al menos en «lotStep» y aplicamos los mismos pasos de función ObjectSetString, «ObjectSetInteger» y «ChartRedraw» para actualizar «BTN_LOT» y restablecer «BTN_M». En cuanto al botón de pánico, tendremos que definir una función para cerrar todas las posiciones abiertas cuando se pulse.

//+------------------------------------------------------------------+
//| Close all positions function                                     |
//+------------------------------------------------------------------+
void closeAllPositions(){
   for (int i=PositionsTotal()-1; i>=0; i--){ //--- Loop through all open positions, starting from the last one
      ulong ticket = PositionGetTicket(i); //--- Get the ticket number of the current position
      if (ticket > 0){ //--- Check if the ticket is valid
         if (PositionSelectByTicket(ticket)){ //--- Select the position by its ticket number
            if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position is for the current chart symbol
               obj_Trade.PositionClose(ticket); //--- Close the selected position
            }
         }
      }
   }
}

Nos encargamos de cerrar todas las posiciones con la función «closeAllPositions», donde utilizamos la función PositionsTotal para realizar un bucle desde «i» como la última posición menos 1 hasta 0, utilizamos la función PositionGetTicket para obtener el «ticket» de cada índice «i» y, si el «ticket» es válido, utilizamos PositionSelectByTicket para seleccionarlo, luego utilizamos PositionGetString para comprobar si POSITION_SYMBOL coincide con _Symbol antes de utilizar el método «obj_Trade.PositionClose» para cerrar la posición con «ticket». Entonces podemos llamar a esta función cuando se pulsa el botón de pánico para cerrar todas las posiciones.

if (GetState(BTN_CLOSE)==true){ //--- Check if the close all positions button is clicked
   closeAllPositions(); //--- Close all open trades
   ObjectSetInteger(0,BTN_CLOSE,OBJPROP_STATE,false); //--- Turn off the close button press state
   ChartRedraw(0); //--- Refresh the chart to reflect closed positions
}

Para gestionar el cierre de todas las operaciones, utilizamos la función «GetState» para comprobar si «BTN_CLOSE» es verdadero y, y, si es así, utilizamos la función «closeAllPositions» para cerrar todas las posiciones abiertas, y luego utilizamos la función ObjectSetInteger para establecer OBJPROP_STATE de «BTN_CLOSE» en falso y la función ChartRedraw con «0» para actualizar el gráfico. Tras compilar y ejecutar el programa, obtenemos el siguiente resultado.

RESULTADO FINAL

En la imagen podemos ver que hemos establecido los niveles de negociación y que podemos abrir posiciones de forma dinámica, logrando así nuestro objetivo. Ahora solo queda probar el programa a fondo, lo cual se aborda en el siguiente tema.


Backtesting en acción: uso del kit de herramientas

Probamos nuestro kit de herramientas en el Probador de Estrategias de MetaTrader 5 cargando el programa, seleccionando nuestra configuración e iniciándolo. Vea el Graphics Interchange Format (GIF) a continuación para ver los botones Comprar, Vender y Ajustar en acción a la velocidad del rayo. Haga clic en Comprar o Vender, ajuste el Stop Loss, el Take Profit y el tamaño del lote, luego confirme con Sí o cancele con No, con el botón de pánico listo para cerrar todas las operaciones rápidamente. Aquí lo tienes.

GIF DE PRUEBA RETROSPECTIVA DEL PROBADOR


Conclusión

En conclusión, hemos creado un kit de herramientas de backtesting manual que combina el control práctico con la velocidad del Probador de Estrategias en MQL5, simplificando la forma en que probamos las ideas de trading. Hemos mostrado cómo diseñarlo, programarlo y utilizarlo para ajustar las operaciones con botones, todo ello adaptado para realizar simulaciones rápidas y precisas. Puede adaptar este kit de herramientas a sus necesidades y mejorar su experiencia de backtesting con él.

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

Allan Munene Mutiiria
Allan Munene Mutiiria | 16 abr 2025 en 14:31
Mogulh Chilyalya Kiti #:
¿Qué pasa con los distintos plazos?

Hola. Actualmente sólo un marco de tiempo. Tal vez tratar de que en un futuro próximo.

Blessing Dumbura
Blessing Dumbura | 16 abr 2025 en 15:47
Gracias es una herramienta útil
Allan Munene Mutiiria
Allan Munene Mutiiria | 16 abr 2025 en 18:26
Blessing Dumbura #:
Gracias, es una herramienta útil.

Claro. Bienvenido y gracias también por el comentario.

Dontrace
Dontrace | 7 may 2025 en 11:08

me preguntaba como convertir un indicador mq4 a mq5

zigooo
zigooo | 17 jun 2025 en 17:16
Intenté modificarlo para que pueda abrir órdenes rápidamente con lot size y tp/sl, de acuerdo a los parámetros de entrada del EA. Funciona en el mercado real, pero no funciona en modo backtest. ¿Cual es la solución?
Redes neuronales en el trading: Actor—Director—Crítico (Actor—Director—Critic) Redes neuronales en el trading: Actor—Director—Crítico (Actor—Director—Critic)
Hoy le presentamos el framework Actor-Director-Critic, que combina el aprendizaje jerárquico y la arquitectura multicomponente para crear estrategias comerciales adaptativas. En este artículo, detallaremos cómo el uso del Director para clasificar las acciones del Actor ayuda a optimizar eficazmente las decisiones comerciales y a aumentar la solidez de los modelos en el entorno de los mercados financieros.
Optimización de arrecifes de coral — Coral Reefs Optimization (CRO) Optimización de arrecifes de coral — Coral Reefs Optimization (CRO)
Este artículo presenta un análisis exhaustivo del algoritmo de optimización de arrecifes de coral (CRO), un método metaheurístico inspirado en los procesos biológicos de formación y desarrollo de los arrecifes de coral. El algoritmo modela aspectos clave de la evolución de los corales: la reproducción externa e interna, el asentamiento de larvas, la reproducción asexual y la competencia por un espacio limitado en el arrecife. El artículo se centra en una versión mejorada del algoritmo.
Formulación de un Asesor Experto Multipar Dinámico (Parte 2): Diversificación y optimización de carteras Formulación de un Asesor Experto Multipar Dinámico (Parte 2): Diversificación y optimización de carteras
La diversificación y optimización de la cartera distribuye estratégicamente las inversiones entre múltiples activos para minimizar el riesgo, al tiempo que selecciona la combinación ideal de activos para maximizar la rentabilidad basándose en métricas de rendimiento ajustadas al riesgo.
Trading con algoritmos: La IA y su camino hacia las alturas doradas Trading con algoritmos: La IA y su camino hacia las alturas doradas
En este artículo veremos un método para crear estrategias comerciales para el oro utilizando el aprendizaje automático. Considerando el enfoque propuesto para el análisis y la previsión de series temporales desde distintos ángulos, podemos determinar sus ventajas e inconvenientes en comparación con otras formas de crear sistemas comerciales basados únicamente en el análisis y la previsión de series temporales financieras.