
Automatización de estrategias de trading en MQL5 (Parte 2): El sistema Kumo Breakout con Ichimoku y Awesome Oscillator
Introducción
En el artículo anterior (parte 1 de la serie) mostramos cómo automatizar el sistema Profitunity (Trading Chaos de Bill Williams). En este artículo (parte 2), mostramos cómo transformar la estrategia Kumo Breakout en un asesor experto (Expert Advisor, EA) totalmente funcional en MetaQuotes Language 5 (MQL5). La estrategia Kumo Breakout utiliza el indicador Ichimoku Kinko Hyo para identificar posibles reversiones del mercado y continuaciones de tendencias, con movimientos de precios relativos al Kumo (nube), una zona dinámica de soporte y resistencia formada por las líneas Senkou Span A y Senkou Span B. Al incorporar el indicador Awesome Oscillator como herramienta de confirmación de tendencias, podemos filtrar las señales falsas y aumentar la precisión de las entradas y salidas de las operaciones. Esta estrategia es muy utilizada por los operadores que buscan sacar provecho de los fuertes movimientos del mercado motivados por el impulso.
Recorremos el proceso de codificación de la lógica de la estrategia, gestión de operaciones y mejora del control de riesgos con trailing stops. Al final de este artículo, comprenderá claramente cómo automatizar la estrategia, probar su rendimiento utilizando el probador de estrategias MQL5 y perfeccionarla para obtener resultados óptimos. Hemos dividido el proceso en secciones para facilitar su comprensión.
- Descripción general de la estrategia Kumo Breakout
- Implementación de la estrategia Kumo Breakout en MQL5
- Prueba y optimización de la estrategia
- Conclusión
Descripción general de la estrategia Kumo Breakout
La estrategia Kumo Breakout es un enfoque que sigue las tendencias y busca sacar provecho de los movimientos de precios más allá de los límites de la nube Kumo. El Kumo, también llamado nube Kumo, es un área sombreada entre las líneas Senkou Span A y Senkou Span B del indicador Ichimoku Kinko Hyo, que actúa como niveles dinámicos de soporte y resistencia. Cuando el precio supera el Kumo, indica una posible tendencia alcista, mientras que una ruptura por debajo indica una posible tendencia bajista. En cuanto al indicador, los parámetros utilizados para su configuración son Tenkan-sen = 8, Kijun-sen = 29 y Senkou-span B = 34. Aquí están los ajustes:
Para filtrar las señales falsas, la estrategia también integra el indicador Awesome Oscillator para proporcionar una confirmación adicional de las entradas en las operaciones. El Awesome Oscillator identifica los cambios de impulso midiendo la diferencia entre una media móvil simple de 34 períodos y otra de 5 períodos, representadas en el precio medio. Las señales de compra se validan cuando el oscilador pasa de negativo a positivo, y las señales de venta se confirman cuando pasa de positivo a negativo. Al combinar las rupturas de Kumo con la confirmación del impulso del oscilador Awesome, la estrategia tiene como objetivo reducir las señales falsas y aumentar la probabilidad de operaciones exitosas.
Cuando se combina por completo, se muestra lo que se ve a continuación en el gráfico.
Para salir de las posiciones, utilizamos la lógica de los cambios de impulso. Cuando el oscilador pasa de positivo a negativo, indica un cambio en el impulso alcista y cerramos las posiciones de compra existentes. Del mismo modo, cuando el oscilador pasa de negativo a positivo, cerramos las posiciones de venta existentes. Aquí hay una ilustración.
Este enfoque es especialmente eficaz en mercados con tendencia alcista en los que el impulso es fuerte. Sin embargo, durante los periodos de consolidación, la estrategia puede generar señales falsas debido a la naturaleza irregular de la acción del precio dentro del Kumo y el oscilador. Como resultado, podemos aplicar filtros adicionales o técnicas de gestión de riesgos, como los trailing stops, para mitigar posibles caídas. Comprender estos principios básicos es esencial para implementar con éxito la estrategia como un Asesor Experto automatizado.
Implementación de la estrategia Kumo Breakout en MQL5
Después de aprender todas las teorías sobre la estrategia de trading Kumo breakout, automatizamos la teoría y creamos un asesor experto (Expert Advisor, EA) en MetaQuotes Language 5 (MQL5) para MetaTrader 5.
Para crear un asesor experto (Expert Advisor, EA) en su terminal MetaTrader 5, haga clic en la pestaña Herramientas y marque MetaQuotes Language Editor, o simplemente pulse F4 en su teclado. También puede hacer clic en el icono IDE (Integrated Development Environment) de la barra de herramientas. Esto abrirá el entorno MetaQuotes Language Editor, que permite escribir robots de trading, indicadores técnicos, scripts y bibliotecas de funciones. Una vez abierto MetaEditor, en la barra de herramientas, vaya a la pestaña Archivo y seleccione Nuevo archivo, o simplemente pulse CTRL + N, para crear un nuevo documento. También puede hacer clic en el icono Nuevo de la pestaña Herramientas. Esto hará que aparezca una ventana emergente del Asistente MQL (MQL Wizard).
En el asistente que aparece, marque Asesor experto (plantilla) y haga clic en Siguiente. En las propiedades generales del Asesor experto, en la sección «Nombre», introduzca el nombre del archivo de su experto. Tenga en cuenta que para especificar o crear una carpeta si no existe, debe utilizar la barra invertida antes del nombre del EA. Por ejemplo, aquí tenemos «Experts\» por defecto. Esto significa que nuestro EA se creará en la carpeta Experts y lo podremos encontrar allí. Las demás secciones son bastante sencillas, pero puede seguir el enlace que aparece en la parte inferior del Asistente para saber cómo llevar a cabo el proceso con precisión.
Después de proporcionar el nombre de archivo del Asesor Experto que desee, haga clic en Siguiente, haga clic en Siguiente y, a continuación, haga clic en Finalizar. Después de hacer todo eso, ya estamos listos para codificar y programar nuestra estrategia.
En primer lugar, comenzamos definiendo algunos metadatos sobre el Asesor Experto (Expert Advisor, EA). Esto incluye el nombre del EA, la información sobre derechos de autor y un enlace al sitio web de MetaQuotes. También especificamos la versión del EA, que está configurada en «1.00».
//+------------------------------------------------------------------+ //| 1. Kumo Breakout 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"
Esto mostrará los metadatos del sistema al cargar el programa. A continuación, podemos pasar a añadir algunas variables globales que utilizaremos dentro del programa. En primer lugar, incluimos una instancia comercial utilizando #include al principio del código fuente. Esto nos da acceso a la «clase CTrade», que utilizaremos para crear un objeto comercial. Esto es crucial, ya que lo necesitamos para abrir operaciones.
#include <Trade/Trade.mqh>
CTrade obj_Trade;
El preprocesador sustituirá la línea #include <Trade/Trade.mqh> por el contenido del archivo Trade.mqh. Los corchetes angulares indican que el archivo Trade.mqh se tomará del directorio estándar (normalmente es directorio_de_instalación_del_terminal\MQL5\Include). El directorio actual no se incluye en la búsqueda. La línea se puede colocar en cualquier parte del programa, pero normalmente todas las inclusiones se colocan al principio del código fuente, para mejorar la estructura del código y facilitar la consulta. La declaración del objeto obj_Trade de la clase CTrade nos dará acceso a los métodos contenidos en esa clase fácilmente, gracias a los desarrolladores de MQL5.
Después de eso, debemos declarar varios indicadores importantes que utilizaremos en el sistema de trading.
int handle_Kumo = INVALID_HANDLE; //--- Initialize the Kumo indicator handle to an invalid state int handle_AO = INVALID_HANDLE; //--- Initialize the Awesome Oscillator handle to an invalid state
Aquí declaramos dos variables integer (int), «handle_Kumo» y «handle_AO», que utilizamos para almacenar los identificadores del indicador Kumo (Ichimoku) y del indicador Awesome Oscillator (AO), respectivamente. Inicializamos ambas variables con el valor INVALID_HANDLE, una constante predefinida en MQL5 que representa un identificador no válido o no inicializado. Esto es importante porque cuando creamos un indicador, el sistema devuelve un identificador que nos permite interactuar con el indicador. Si el identificador es «INVALID_HANDLE», la creación del indicador ha fallado o no se ha inicializado correctamente. Al establecer inicialmente los manejadores en INVALID_HANDLE, nos aseguramos de que más adelante podamos comprobar si hay problemas de inicialización y gestionar los errores de forma adecuada.
A continuación, debemos inicializar las matrices donde almacenaremos los valores recuperados.
double senkouSpan_A[]; //--- Array to store Senkou Span A values double senkouSpan_B[]; //--- Array to store Senkou Span B values double awesome_Oscillator[]; //--- Array to store Awesome Oscillator values
De nuevo en el ámbito global, declaramos tres matrices: «senkouSpan_A», «senkouSpan_B» y «awesome_Oscillator», que utilizamos para almacenar los valores de Senkou Span A, Senkou Span B y Awesome Oscillator, respectivamente. Definimos estas matrices como tipos dobles (double), lo que significa que contendrán valores de punto flotante, lo cual es adecuado para almacenar los resultados de los cálculos de los indicadores. Utilizamos las matrices «senkouSpan_A» y «senkouSpan_B» para almacenar los valores de los componentes Senkou Span A y B del indicador Ichimoku. Por el contrario, la matriz «awesome_Oscillator» almacena los valores calculados por el oscilador Awesome. Al declarar estas matrices, nos preparamos para almacenar los valores indicadores de modo que más adelante podamos acceder a ellos y utilizarlos en nuestra lógica de negociación.
Esas son todas las variables que necesitamos en el ámbito global. Ahora podemos inicializar los controladores del indicador en el controlador de eventos OnInit, que es una función que gestiona el bucle de inicialización.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- return(INIT_SUCCEEDED); //--- Return successful initialization }
Este es un controlador de eventos que se activa cada vez que se inicializa el indicador, independientemente del motivo. Dentro de él, inicializamos los manejadores del indicador. Empezamos con el manejador Kumo.
//--- Initialize the Ichimoku Kumo indicator handle_Kumo = iIchimoku(_Symbol,_Period,8,29,34); if (handle_Kumo == INVALID_HANDLE){ //--- Check if Kumo indicator initialization failed Print("ERROR: UNABLE TO INITIALIZE THE KUMO INDICATOR HANDLE. REVERTING NOW!"); //--- Log error return (INIT_FAILED); //--- Return initialization failure }
Aquí, inicializamos «handle_Kumo» llamando a la función iIchimoku, que crea una instancia del indicador Ichimoku Kumo para el símbolo actual (_Symbol) y el período (_Period). Utilizamos los parámetros específicos para el indicador Ichimoku: los periodos 8, 29 y 34 para Tenkan-sen, Kijun-sen y Senkou Span B, respectivamente, como se ha ilustrado anteriormente.
Después de llamar a iIchimoku, la función devuelve un identificador, que almacenamos en «handle_Kumo». A continuación, comprobamos si «handle_Kumo» es igual a INVALID_HANDLE, lo que indicaría que la inicialización del indicador ha fallado. Si el identificador no es válido, registramos un mensaje de error con la función «Print» que especifica el motivo del fallo y devolvemos la constante INIT_FAILED, lo que indica que el proceso de inicialización no se ha realizado correctamente. Del mismo modo, inicializamos el indicador del oscilador.
//--- Initialize the Awesome Oscillator handle_AO = iAO(_Symbol,_Period); if (handle_AO == INVALID_HANDLE){ //--- Check if AO indicator initialization failed Print("ERROR: UNABLE TO INITIALIZE THE AO INDICATOR HANDLE. REVERTING NOW!"); //--- Log error return (INIT_FAILED); //--- Return initialization failure }
Para inicializar el oscilador, llamamos a la función iAO y pasamos solo el símbolo y el periodo como parámetros predeterminados. A continuación, continuamos con el resto de la lógica de inicialización utilizando el mismo formato que el identificador Kumo. Una vez completada la inicialización, podemos pasar a configurar las matrices de almacenamiento como series temporales.
ArraySetAsSeries(senkouSpan_A,true); //--- Set Senkou Span A array as a time series ArraySetAsSeries(senkouSpan_B,true); //--- Set Senkou Span B array as a time series ArraySetAsSeries(awesome_Oscillator,true); //--- Set Awesome Oscillator array as a time series
Utilizamos la función ArraySetAsSeries para establecer las matrices «senkouSpan_A», «senkouSpan_B» y «awesome_Oscillator» como matrices de series temporales. Al configurar estas matrices como series temporales, nos aseguramos de que los valores más recientes se almacenen al principio de la matriz, y los valores más antiguos se desplacen hacia el final. Esto es importante porque, en MQL5, los datos de series temporales suelen organizarse de tal manera que se accede primero a los valores más recientes (en el índice 0), lo que facilita la recuperación de los datos más recientes para tomar decisiones de trading.
Llamamos a ArraySetAsSeries en cada matriz, pasando true como segundo argumento para habilitar este comportamiento de serie temporal. Esto nos permite trabajar con los datos de una manera que se ajusta a las estrategias comerciales típicas, en las que a menudo necesitamos acceder primero a los valores más recientes. Por último, cuando toda la inicialización haya finalizado, podemos imprimir un mensaje en el diario para indicar que todo está listo.
Print("SUCCESS. ",__FILE__," HAS BEEN INITIALIZED."); //--- Log successful initialization
Tras una inicialización satisfactoria, utilizamos la función Print para registrar un mensaje que indica que el proceso de inicialización se ha realizado correctamente. El mensaje incluye la cadena «SUCCESS.», seguida de la variable especial predefinida __FILE__, que representa el nombre del archivo de código fuente actual. Al utilizar __FILE__, podemos insertar dinámicamente el nombre del archivo en el mensaje de registro, lo que puede ayudar a depurar o rastrear el proceso de inicialización en proyectos más grandes con múltiples archivos. El mensaje se imprimirá en el terminal o en el archivo de registro, confirmando que la inicialización se ha completado correctamente. Este paso ayuda a garantizar que obtengamos información adecuada sobre el estado del proceso de inicialización, lo que facilita la identificación de posibles problemas en el código.
El fragmento de código de inicialización completo es el siguiente:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ //--- Initialize the Ichimoku Kumo indicator handle_Kumo = iIchimoku(_Symbol,_Period,8,29,34); if (handle_Kumo == INVALID_HANDLE){ //--- Check if Kumo indicator initialization failed Print("ERROR: UNABLE TO INITIALIZE THE KUMO INDICATOR HANDLE. REVERTING NOW!"); //--- Log error return (INIT_FAILED); //--- Return initialization failure } //--- Initialize the Awesome Oscillator handle_AO = iAO(_Symbol,_Period); if (handle_AO == INVALID_HANDLE){ //--- Check if AO indicator initialization failed Print("ERROR: UNABLE TO INITIALIZE THE AO INDICATOR HANDLE. REVERTING NOW!"); //--- Log error return (INIT_FAILED); //--- Return initialization failure } ArraySetAsSeries(senkouSpan_A,true); //--- Set Senkou Span A array as a time series ArraySetAsSeries(senkouSpan_B,true); //--- Set Senkou Span B array as a time series ArraySetAsSeries(awesome_Oscillator,true); //--- Set Awesome Oscillator array as a time series Print("SUCCESS. ",__FILE__," HAS BEEN INITIALIZED."); //--- Log successful initialization //--- return(INIT_SUCCEEDED); //--- Return successful initialization }
Esto da el siguiente resultado.
Dado que hemos inicializado las matrices y los manejadores de almacenamiento de datos, no queremos conservarlos una vez que desinicialicemos el programa, ya que habremos ocupado recursos innecesarios. Nos ocupamos de esto en el controlador de eventos OnDeinit, que se invoca cada vez que se desinicializa el programa, sea cual sea el motivo.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ //--- Free memory allocated for Senkou Span A and B arrays ArrayFree(senkouSpan_A); ArrayFree(senkouSpan_B); //--- Free memory allocated for the Awesome Oscillator array ArrayFree(awesome_Oscillator); }
Dentro de la función OnDeinit, realizamos tareas de limpieza para liberar cualquier memoria que se haya asignado durante el proceso de inicialización. En concreto, utilizamos la función ArrayFree para liberar la memoria de las matrices «senkouSpan_A», «senkouSpan_B» y «awesome_Oscillator». Estas matrices se utilizaban anteriormente para almacenar los valores del indicador Ichimoku Kumo y del Awesome Oscillator, y ahora que ya no son necesarias, liberamos la memoria para evitar fugas de recursos. De esta manera, nos aseguramos de que el programa gestione de forma eficiente los recursos del sistema y evite un uso innecesario de memoria una vez que el asesor experto ya no está activo.
Ahora solo queda gestionar la lógica de negociación, donde recuperamos los valores del indicador y los analizamos para tomar decisiones de negociación. Nos ocupamos de esto en el controlador de eventos OnTick, que se activa cada vez que hay un nuevo tick o simplemente cambia el precio. El primer paso que debemos dar es recuperar los puntos de datos de los indicadores y almacenarlos para su posterior análisis.
//--- Copy data for Senkou Span A from the Kumo indicator if (CopyBuffer(handle_Kumo,SENKOUSPANA_LINE,0,2,senkouSpan_A) < 2){ Print("ERROR: UNABLE TO COPY REQUESTED DATA FROM SENKOUSPAN A LINE. REVERTING NOW!"); //--- Log error return; //--- Exit if data copy fails } //--- Copy data for Senkou Span B from the Kumo indicator if (CopyBuffer(handle_Kumo,SENKOUSPANB_LINE,0,2,senkouSpan_B) < 2){ Print("ERROR: UNABLE TO COPY REQUESTED DATA FROM SENKOUSPAN B LINE. REVERTING NOW!"); //--- Log error return; //--- Exit if data copy fails }
Aquí utilizamos la función CopyBuffer para copiar datos de las líneas Senkou Span A y Senkou Span B del indicador Kumo (Ichimoku) en las matrices «senkouSpan_A» y «senkouSpan_B», respectivamente. El primer argumento pasado a CopyBuffer es el identificador del indicador «handle_Kumo», que hace referencia al indicador Kumo inicializado. El segundo argumento especifica qué línea de datos copiar: «SENKOUSPANA_LINE» para Senkou Span A y «SENKOUSPANB_LINE» para Senkou Span B. El tercer argumento es el índice inicial desde el que comenzar a copiar, que se establece en 0 para comenzar desde los datos más recientes. El cuarto argumento especifica el número de puntos de datos que se van a copiar, que en este caso es 2. El último argumento es la matriz donde se almacenarán los datos, «senkouSpan_A» o «senkouSpan_B».
Después de llamar a CopyBuffer, comprobamos si la función devuelve un valor inferior a 2, lo que indica que los datos solicitados no se han copiado correctamente. Si esto ocurre, registramos un mensaje de error con la función Print, especificando que no se han podido copiar los datos de la línea Senkou Span correspondiente, y luego salimos de la función utilizando return. Esto garantiza que, si falla la copia de datos, gestionemos el error correctamente registrando el problema y deteniendo la ejecución posterior de la función.
Utilizamos la misma lógica para recuperar los valores del oscilador.
//--- Copy data from the Awesome Oscillator if (CopyBuffer(handle_AO,0,0,3,awesome_Oscillator) < 3){ Print("ERROR: UNABLE TO COPY REQUESTED DATA FROM AWESOME OSCILLATOR. REVERTING NOW!"); //--- Log error return; //--- Exit if data copy fails }
Utilizamos la función CopyBuffer para copiar datos del indicador Awesome Oscillator (AO) en la matriz «awesome_Oscillator». El primer argumento pasado a la función CopyBuffer es el indicador «handle_AO», que hace referencia al oscilador Awesome inicializado. El segundo argumento especifica la línea de datos o el índice del búfer que se va a copiar, que en este caso es 0, ya que el Awesome Oscillator tiene un único búfer de datos. El tercer argumento es el índice inicial, establecido en 0 para comenzar a copiar desde los datos más recientes. El cuarto argumento especifica el número de puntos de datos que se van a copiar, que en este caso se establece en 3, lo que significa que queremos copiar los tres valores más recientes. El último argumento es la matriz «awesome_Oscillator», donde se almacenarán los datos copiados. Si los datos recuperados son inferiores a los solicitados, registramos un mensaje de error y lo devolvemos.
Si disponemos de todos los datos necesarios, podemos continuar con su procesamiento. Lo primero que debemos hacer es definir una lógica que utilizaremos para asegurarnos de analizar los datos una vez cada vez que se genere una nueva barra completa y no en cada tick. Incorporamos esa lógica en una función.
//+------------------------------------------------------------------+ //| IS NEW BAR FUNCTION | //+------------------------------------------------------------------+ bool isNewBar(){ static int prevBars = 0; //--- Store previous bar count int currBars = iBars(_Symbol,_Period); //--- Get current bar count for the symbol and period if (prevBars == currBars) return (false); //--- If bars haven't changed, return false prevBars = currBars; //--- Update previous bar count return (true); //--- Return true if new bar is detected }
Definimos una función booleana «isNewBar», que se utiliza para detectar si ha aparecido una nueva barra en el gráfico para el símbolo y el período especificados. Dentro de esta función, declaramos una variable estática «prevBars», que almacena el recuento de barras de la comprobación anterior. La palabra clave static garantiza que la variable conserve su valor entre llamadas a funciones.
A continuación, utilizamos la función iBars para obtener el número actual de barras en el gráfico para el símbolo dado (_Symbol) y el período (_Period). El resultado se almacena en la variable «currBars». Si el número de barras no ha cambiado (es decir, «prevBars» es igual a «currBars»), devolvemos falso, lo que indica que no ha aparecido ninguna barra nueva. Si el número de barras ha cambiado, actualizamos «prevBars» con el recuento actual de barras y devolvemos verdadero, lo que indica que se ha detectado una nueva barra. Con esta función, podemos llamarla dentro del controlador de eventos tick y analizarla.
//--- Check if a new bar has formed if (isNewBar()){ //--- Determine if the AO has crossed above or below zero bool isAO_Above = awesome_Oscillator[1] > 0 && awesome_Oscillator[2] < 0; bool isAO_Below = awesome_Oscillator[1] < 0 && awesome_Oscillator[2] > 0; //--- }
Aquí, comprobamos si se ha formado una nueva barra llamando a la función «isNewBar». Si se detecta una nueva barra (es decir, «isNewBar» devuelve verdadero), procedemos a determinar el comportamiento del Awesome Oscillator (AO).
Definimos dos variables booleanas: «isAO_Above» e «isAO_Below». La variable «isAO_Above» se establece en verdadero si el valor anterior del Awesome Oscillator (awesome_Oscillator[1]) es mayor que cero y el valor anterior a ese (awesome_Oscillator[2]) es menor que cero. Esta condición comprueba si el AO ha cruzado por encima de cero, lo que indica una posible señal alcista. Del mismo modo, «isAO_Below» se establece en verdadero si el valor AO anterior (awesome_Oscillator[1]) es inferior a cero y el valor anterior a ese (awesome_Oscillator[2]) es superior a cero, lo que indica que el AO ha cruzado por debajo de cero, lo que podría indicar un movimiento bajista. A continuación, podemos utilizar el mismo método para establecer la otra lógica.
//--- Determine if the Kumo is bullish or bearish bool isKumo_Above = senkouSpan_A[1] > senkouSpan_B[1]; bool isKumo_Below = senkouSpan_A[1] < senkouSpan_B[1]; //--- Determine buy and sell signals based on conditions bool isBuy_Signal = isAO_Above && isKumo_Below && getClosePrice(1) > senkouSpan_A[1] && getClosePrice(1) > senkouSpan_B[1]; bool isSell_Signal = isAO_Below && isKumo_Above && getClosePrice(1) < senkouSpan_A[1] && getClosePrice(1) < senkouSpan_B[1];
Aquí determinamos las condiciones para una configuración alcista o bajista de Kumo (Ichimoku). En primer lugar, definimos dos variables booleanas: «isKumo_Above» e «isKumo_Below». La variable «isKumo_Above» se establece en verdadero si el valor anterior de Senkou Span A (senkouSpan_A[1]) es mayor que el valor anterior de Senkou Span B (senkouSpan_B[1]), lo que indica un Kumo alcista (sentimiento alcista del mercado). Por otro lado, «isKumo_Below» se establece en verdadero si Senkou Span A es menor que Senkou Span B, lo que indica un Kumo bajista (sentimiento bajista del mercado).
A continuación, definimos las condiciones para las posibles señales de compra y venta. La señal de compra («isBuy_Signal») se establece en verdadero si se cumplen las siguientes condiciones: el oscilador Awesome ha cruzado por encima de cero (isAO_Above), el Kumo es bajista (isKumo_Below) y el precio de cierre de la barra anterior está por encima tanto de Senkou Span A como de Senkou Span B. Esto sugiere un posible movimiento alcista del precio a pesar del Kumo bajista. La señal de venta («isSell_Signal») se establece en verdadero si el oscilador Awesome ha cruzado por debajo de cero (isAO_Below), el Kumo es alcista (isKumo_Above) y el precio de cierre de la barra anterior está por debajo tanto de Senkou Span A como de Senkou Span B. Esto indica un posible movimiento bajista del precio a pesar del Kumo alcista.
Quizás hayas notado que hemos utilizado una nueva función para obtener los precios de cierre. Aquí está la lógica de todas las funciones que necesitaremos.
//+------------------------------------------------------------------+ //| FUNCTION TO GET CLOSE PRICES | //+------------------------------------------------------------------+ double getClosePrice(int bar_index){ return (iClose(_Symbol, _Period, bar_index)); //--- Retrieve the close price of the specified bar } //+------------------------------------------------------------------+ //| FUNCTION TO GET ASK PRICES | //+------------------------------------------------------------------+ double getAsk(){ return (NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits)); //--- Get and normalize the Ask price } //+------------------------------------------------------------------+ //| FUNCTION TO GET BID PRICES | //+------------------------------------------------------------------+ double getBid(){ return (NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits)); //--- Get and normalize the Bid price }
Aquí definimos tres funciones para recuperar diferentes tipos de datos sobre precios:
- Función «getClosePrice»: esta función recupera el precio de cierre de una barra específica. Toma un parámetro «bar_index», que representa el índice de la barra para la que queremos obtener el precio de cierre. La función llama a la función integrada iClose, pasando el símbolo (_Symbol), el período (_Period) y el índice de la barra para obtener el precio de cierre de la barra especificada. El precio recuperado se devuelve como un doble (double).
- Función «getAsk»: esta función recupera el precio de venta actual para el símbolo dado. Utiliza la función SymbolInfoDouble con la constante SYMBOL_ASK para obtener el precio de venta. A continuación, el resultado se normaliza utilizando la función NormalizeDouble para garantizar que el precio se redondee al número correcto de decimales según la propiedad _Digits del símbolo. Esta función devuelve el precio de venta normalizado como un doble (double).
- Función «getBid»: esta función recupera el precio de oferta actual para el símbolo dado. De forma similar a la función «getAsk», utiliza SymbolInfoDouble con la constante SYMBOL_BID para obtener el precio de compra y, a continuación, lo normaliza mediante la función NormalizeDouble para garantizar que coincida con la precisión correcta definida por la propiedad _Digits del símbolo. Esta función devuelve el precio de compra normalizado como un doble (double).
Estas funciones proporcionan una forma sencilla de recuperar y normalizar los precios relevantes para las decisiones de negociación dentro del programa. A continuación, podemos utilizar las señales comerciales calculadas y abrir las posiciones correspondientes para las señales existentes.
if (isBuy_Signal){ //--- If buy signal is generated Print("BUY SIGNAL GENERATED @ ",iTime(_Symbol,_Period,1),", PRICE: ",getAsk()); //--- Log buy signal obj_Trade.Buy(0.01,_Symbol,getAsk()); //--- Execute a buy trade } else if (isSell_Signal){ //--- If sell signal is generated Print("SELL SIGNAL GENERATED @ ",iTime(_Symbol,_Period,1),", PRICE: ",getBid()); //--- Log sell signal obj_Trade.Sell(0.01,_Symbol,getBid()); //--- Execute a sell trade }
Comprobamos si se ha generado una señal de compra o venta y ejecutamos la operación correspondiente. Si «isBuy_Signal» es verdadero, lo que indica que se ha producido una señal de compra, primero registramos el evento utilizando la función Print. Incluimos la marca de tiempo de la barra anterior, que se recupera con la función iTime, y el precio de venta actual, obtenido de la función «getAsk». Este registro proporciona un historial de la señal de compra y el precio al que se produjo. Después de iniciar sesión, ejecutamos la operación de compra llamando a «obj_Trade.Buy(0.01, _Symbol, getAsk())», lo que coloca una orden de compra de 0,01 lotes al precio de venta actual.
Del mismo modo, si «isSell_Signal» es verdadero, lo que indica una señal de venta, registramos el evento con la función Print, que incluye la marca de tiempo de la barra anterior y el precio de compra actual de la función «getBid». Después de iniciar sesión, colocamos una orden de venta utilizando «obj_Trade.Sell(0.01, _Symbol, getBid())», que ejecuta una orden de venta por 0,01 lotes al precio de compra actual. Esto garantiza que las operaciones se realicen siempre que se cumplan las condiciones para las señales de compra o venta, y mantenemos un registro claro de esas acciones.
Por último, solo tenemos que comprobar los cambios de impulso y cerrar las posiciones correspondientes. Esta es la lógica:
if (isAO_Above || isAO_Below){ //--- If AO crossover occurs if (PositionsTotal() > 0){ //--- If there are open positions for (int i=PositionsTotal()-1; i>=0; i--){ //--- Loop through open positions ulong posTicket = PositionGetTicket(i); //--- Get the position ticket if (posTicket > 0){ //--- If ticket is valid if (PositionSelectByTicket(posTicket)){ //--- Select position by ticket ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get position type if (posType == POSITION_TYPE_BUY){ //--- If position is a buy if (isAO_Below){ //--- If AO indicates bearish crossover Print("CLOSING THE BUY POSITION WITH #",posTicket); //--- Log position closure obj_Trade.PositionClose(posTicket); //--- Close the buy position } } else if (posType == POSITION_TYPE_SELL){ //--- If position is a sell if (isAO_Above){ //--- If AO indicates bullish crossover Print("CLOSING THE SELL POSITION WITH #",posTicket); //--- Log position closure obj_Trade.PositionClose(posTicket); //--- Close the sell position } } } } } } }
Aquí, comprobamos si se produce un cruce del oscilador Awesome (Awesome Oscillator, AO) (por encima o por debajo de cero) y gestionamos las posiciones abiertas en consecuencia. Si «isAO_Above» o «isAO_Below» es verdadero, lo que indica que se ha producido un cruce de AO, procedemos a comprobar si hay posiciones abiertas llamando a la función PositionsTotal. Si hay posiciones abiertas (es decir, «PositionsTotal» devuelve un valor superior a 0), recorremos todas las posiciones abiertas, comenzando por la más reciente (PositionsTotal()-1) y retrocediendo.
Dentro del bucle, recuperamos el ticket de posición utilizando la función PositionGetTicket. Si el ticket de posición es válido (es decir, mayor que 0), seleccionamos la posición utilizando la función PositionSelectByTicket. A continuación, determinamos el tipo de posición llamando a PositionGetInteger. Si la posición es una compra (POSITION_TYPE_BUY), comprobamos si «isAO_Below» es verdadero, lo que indica un cruce bajista. Si es cierto, registramos el cierre de la posición de compra utilizando la función Print y cerramos la posición con «obj_Trade.PositionClose(posTicket)».
Del mismo modo, si la posición es una venta (POSITION_TYPE_SELL), comprobamos si «isAO_Above» es verdadero, lo que indica un cruce alcista. Si es cierto, registramos el cierre de la posición de venta y la cerramos utilizando «obj_Trade.PositionClose(posTicket)». Esto garantiza que gestionemos las posiciones abiertas de manera eficaz, cerrándolas cuando las condiciones para un cruce de AO indiquen un cambio en el impulso del mercado. Al ejecutar el programa, obtenemos el siguiente resultado.
Confirmación de posición de venta.
Confirmación de salida de la posición de venta ante un cambio en el impulso del mercado:
A partir de las ilustraciones anteriores, podemos estar seguros de que hemos logrado nuestros objetivos deseados. Ahora podemos proceder a probar y optimizar el programa. Esto se aborda en la siguiente sección.
Prueba y optimización de la estrategia
En esta sección, probamos la estrategia y la optimizamos para que funcione mejor en diversas condiciones del mercado. El cambio que haremos es en el sector de gestión de riesgos, donde podremos añadir un trailing stop para fijar las ganancias cuando ya estemos en ganancias en lugar de esperar a que el mercado tome una decisión completa sobre el cambio de impulso del mercado. Para manejar esto de manera eficiente, construiremos una función dinámica para manejar la lógica del trailing stop.
//+------------------------------------------------------------------+ //| FUNCTION TO APPLY TRAILING STOP | //+------------------------------------------------------------------+ void applyTrailingSTOP(double slPoints, CTrade &trade_object,int magicNo=0){ double buySL = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID)-slPoints,_Digits); //--- Calculate SL for buy positions double sellSL = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK)+slPoints,_Digits); //--- Calculate SL for sell positions for (int i = PositionsTotal() - 1; i >= 0; i--){ //--- Iterate through all open positions ulong ticket = PositionGetTicket(i); //--- Get position ticket if (ticket > 0){ //--- If ticket is valid if (PositionGetString(POSITION_SYMBOL) == _Symbol && (magicNo == 0 || PositionGetInteger(POSITION_MAGIC) == magicNo)){ //--- Check symbol and magic number if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && buySL > PositionGetDouble(POSITION_PRICE_OPEN) && (buySL > PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0)){ //--- Modify SL for buy position if conditions are met trade_object.PositionModify(ticket,buySL,PositionGetDouble(POSITION_TP)); } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && sellSL < PositionGetDouble(POSITION_PRICE_OPEN) && (sellSL < PositionGetDouble(POSITION_SL) || PositionGetDouble(POSITION_SL) == 0)){ //--- Modify SL for sell position if conditions are met trade_object.PositionModify(ticket,sellSL,PositionGetDouble(POSITION_TP)); } } } } }
Aquí, implementamos una función para aplicar un trailing stop a las posiciones abiertas. La función se llama «applyTrailingSTOP» y toma tres parámetros: «slPoints», que representa el número de puntos que se deben establecer para el stop loss; «trade_object», que es una referencia al objeto comercial utilizado para modificar posiciones; y un «magicNo» opcional, que se utiliza para identificar posiciones específicas por su número mágico. En primer lugar, calculamos los niveles de stop loss (SL) para las posiciones de compra y venta. Para las posiciones de compra, el stop loss se establece en el precio de compra menos los «slPoints» especificados, y para las posiciones de venta, el stop loss se establece en el precio de venta más los «slPoints» especificados. Ambos valores SL se normalizan utilizando la función NormalizeDouble para que coincidan con la precisión decimal del símbolo, que se define mediante la variable _Digits.
A continuación, recorremos todas las posiciones abiertas utilizando la función PositionsTotal, iterando desde la posición más reciente hasta la más antigua. Para cada posición, recuperamos el ticket de posición utilizando la función PositionGetTicket y nos aseguramos de que sea válido. A continuación, comprobamos si el símbolo de la posición coincide con el símbolo actual (_Symbol) y si el número mágico de la posición coincide con el «magicNo» proporcionado, a menos que el número mágico esté establecido en 0, en cuyo caso se tienen en cuenta todas las posiciones.
Si la posición es una posición de compra (POSITION_TYPE_BUY), comprobamos si el stop loss de compra calculado («buySL») está por encima del precio de apertura de la posición (POSITION_PRICE_OPEN) y si es mayor que el stop loss actual (POSITION_SL) o si el stop loss actual no está establecido («POSITION_SL» == 0). Si se cumplen estas condiciones, actualizamos el stop loss de la posición llamando a «trade_object.PositionModify(ticket, buySL, PositionGetDouble(POSITION_TP))», que modifica el stop loss de la posición sin cambiar el take profit («POSITION_TP»).
Si la posición es una posición de venta (POSITION_TYPE_SELL), aplicamos una lógica similar. Comprobamos si el stop loss de venta calculado («sellSL») está por debajo del precio de apertura de la posición (POSITION_PRICE_OPEN) y si es inferior al stop loss actual (POSITION_SL) o si el stop loss actual no está establecido. Si se cumplen estas condiciones, actualizamos el stop loss de la posición utilizando «trade_object.PositionModify(ticket, sellSL, PositionGetDouble(POSITION_TP))».
Después de definir la función, solo tenemos que llamarla en la función tick para que se pueda ejecutar. Lo conseguimos llamándolo y pasando los parámetros correspondientes de la siguiente manera.
if (PositionsTotal() > 0){ //--- If there are open positions applyTrailingSTOP(3000*_Point,obj_Trade,0); //--- Apply a trailing stop }
Si hay posiciones abiertas, llamamos a la función «applyTrailingSTOP» para aplicar un trailing stop a estas posiciones abiertas. La función se invoca con tres argumentos:
- Puntos de stop dinámico (Trailing Stop): La distancia del stop loss se calcula como «3000 * _Point», donde _Point representa el menor movimiento de precio posible para el símbolo actual. Esto significa que el stop loss se establece a 3000 puntos del precio actual de mercado.
- Objeto de operación: Pasamos «obj_Trade», que es una instancia del objeto de operación utilizado para modificar los niveles de stop loss y take profit de la posición.
- Número mágico: El tercer argumento se establece en 0, lo que significa que la función aplicará el stop dinámico a todas las posiciones abiertas, independientemente de su número mágico.
Tras la aplicación del trailing stop, obtenemos el siguiente resultado.
A partir de la visualización, podemos ver que, en lugar de esperar a que cambie el impulso del mercado, aseguramos nuestras ganancias y las maximizamos moviendo el nivel de stop loss cada vez que el mercado avanza en nuestra dirección. Los resultados finales de la prueba de estrategia son los siguientes.
Gráfico del probador:
Conclusión
En conclusión, este artículo ha mostrado cómo crear un Asesor Experto (Expert Advisor, EA) MQL5 utilizando el sistema Kumo Breakout. Al integrar el indicador Ichimoku Kumo y el Awesome Oscillator (AO), creamos un marco para detectar cambios en el impulso del mercado y señales de ruptura. Los pasos clave incluyeron la configuración de indicadores, la extracción de valores clave y la automatización de la ejecución de operaciones con stops dinámicos y gestión de posiciones, lo que dio como resultado un EA basado en estrategias con una lógica de trading sólida.
Descargo de responsabilidad: Este artículo es una guía educativa para desarrollar EA MQL5 basados en señales de trading impulsadas por indicadores. Aunque el sistema Kumo Breakout es una estrategia popular, su eficacia no está garantizada en todas las condiciones del mercado. El trading conlleva riesgos financieros, y los resultados pasados no garantizan resultados futuros. Es fundamental realizar pruebas exhaustivas y gestionar adecuadamente los riesgos antes de operar con dinero real.
Siguiendo esta guía, podrá mejorar sus habilidades de desarrollo en MQL5 y crear sistemas de trading más sofisticados. Los conceptos de integración de indicadores, lógica de señales y automatización de operaciones que se muestran aquí pueden aplicarse a otras estrategias, lo que fomenta una mayor exploración e innovación en el comercio algorítmico. ¡Feliz programación y éxito en tus operaciones!
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/16657
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
¡Muy buena, tío!
Muchas gracias. Aprecio mucho sus amables comentarios.