English Русский 中文 Deutsch 日本語
preview
Automatización de estrategias de trading en MQL5 (Parte 1): El sistema Profitunity (Trading Chaos de Bill Williams)

Automatización de estrategias de trading en MQL5 (Parte 1): El sistema Profitunity (Trading Chaos de Bill Williams)

MetaTrader 5Trading |
124 4
Allan Munene Mutiiria
Allan Munene Mutiiria

Introducción

En este artículo, exploramos el sistema Profitunity, una estrategia de trading desarrollada por Bill Williams que tiene como objetivo obtener beneficios del «caos» del mercado utilizando un conjunto de indicadores clave y mostramos cómo automatizarla en MetaQuotes Language 5 (MQL5). Comenzamos con una descripción general de la estrategia y sus principios clave. Luego, recorremos el proceso de implementación en MQL5, centrándonos en codificar los indicadores esenciales y automatizar las señales de entrada y salida. A continuación, probamos y optimizamos el sistema para garantizar el rendimiento en diversas condiciones del mercado. Finalmente, concluimos discutiendo el potencial y la eficacia del sistema Profitunity en el trading automatizado. Las secciones que cubrimos en este artículo incluyen:

  1. Descripción general del sistema Profitunity
  2. Implementación de la estrategia en MQL5
  3. Prueba y optimización de estrategias
  4. Conclusión

Al final de este artículo, comprenderá claramente cómo automatizar el sistema Profitunity utilizando MQL5, desde la implementación de sus indicadores clave hasta la optimización de su rendimiento. Esto le proporcionará las herramientas necesarias para mejorar su estrategia de trading y aprovechar el «caos» del mercado para obtener un mayor rendimiento en sus operaciones. Vamos a ello.


Descripción general del sistema Profitunity

El sistema Profitunity, creado por Bill Williams, utiliza un conjunto de indicadores especializados que nos permiten comprender y actuar ante los movimientos caóticos del mercado. La estrategia combina el poder de los indicadores de seguimiento de tendencias y de impulso para crear una metodología de negociación dinámica y altamente receptiva. El sistema identifica los cambios de tendencia y la aceleración del mercado, lo que nos ayuda a encontrar configuraciones de operaciones con alta probabilidad de éxito. Los indicadores clave utilizados en la estrategia son:

  • Fractales
  • Alligator
  • Awesome Oscillator (AO)
  • Accelerator Oscillator (AC)

Cada uno de estos indicadores funciona en conjunto, proporcionando información crítica sobre las condiciones del mercado y ofreciendo señales de entrada y salida. Analicemos más detenidamente los ajustes individuales de los indicadores que se aplican a la estrategia.

Configuración de indicadores

Indicador fractal. Identifica puntos de reversión en el mercado. Los fractales se forman cuando hay cinco barras consecutivas y la barra del medio es la más alta o la más baja. Indican el posible inicio de una nueva tendencia o un cambio de tendencia en los precios, lo que ayuda a marcar máximos o mínimos locales, proporcionando una indicación temprana de un posible cambio de tendencia. En cuanto a la configuración, el período predeterminado para los fractales es 2 o 5. Esto significa que comprueba patrones en los que una sola barra está rodeada por dos barras a cada lado que son más bajas (para descensos fractales) o más altas (para ascensos fractales). Así es como se ve en el gráfico.

FRACTALES

Indicador Alligator. El indicador es una combinación de tres medias móviles suavizadas, conocidas como «mandíbula», «dientes» y «labios», que funcionan conjuntamente para determinar la tendencia del mercado. La interacción entre estas líneas nos ayuda a reconocer si el mercado está en tendencia o en consolidación. Cuando las líneas comienzan a separarse, esto indica una tendencia; cuando convergen, sugiere que el mercado se encuentra en una fase de consolidación.

Configuración:

  • Mandíbula (línea azul): 13 períodos, suavizada por 8 barras.
  • Dientes (línea roja): 8 períodos, suavizados por 5 barras.
  • Labios (línea verde): 5 períodos, suavizados por 3 barras.

El indicador nos ayudará a identificar la dirección y el momento de la tendencia, así como las entradas y salidas del mercado. Aquí está su configuración en el gráfico.

ALLIGATOR

Indicador Awesome Oscillator (AO). Este indicador es un indicador de impulso que calcula la diferencia entre una media móvil simple de 34 períodos y una de 5 períodos del precio medio. Nos ayuda a medir la fuerza y la dirección de una tendencia trazando la diferencia entre estas dos medias móviles en forma de histograma. La configuración de este indicador es la predeterminada.

El AO se utiliza normalmente para identificar el impulso alcista o bajista del mercado y para detectar cambios de tendencia. Un histograma por encima de la línea cero indica un impulso alcista, mientras que un histograma por debajo de la línea cero indica un impulso bajista.

Indicador Accelerator Oscillator (AC). Derivado del indicador AO, mide la aceleración del impulso del mercado. Proporciona información sobre si el impulso del mercado se está acelerando o desacelerando, lo cual es fundamental para detectar cambios de tendencia antes de que se desarrollen por completo. El AC oscila alrededor de una línea cero, moviéndose en zonas verde (positiva) o roja (negativa). Su configuración también es la predeterminada.

El indicador AC se utiliza junto con el indicador AO para proporcionar confirmación de la fortaleza del mercado y los cambios de impulso, lo que garantiza que el mercado se esté moviendo fuertemente en una dirección antes de ingresar a una operación. Ahora echemos un vistazo a las condiciones de entrada y salida utilizadas por el sistema. Los indicadores AO y AC se parecerían a la siguiente ilustración.

AO Y AC

Condiciones de entrada y salida

El sistema utiliza un conjunto de condiciones específicas para entrar y salir de operaciones, que se basan en las señales secuenciales de los indicadores Fractal, Alligator, Accelerator Oscillator (AC) y Awesome Oscillator (AO). Las señales funcionan juntas para garantizar que las operaciones se inicien solo cuando el mercado proporcione una fuerte confirmación de la dirección, lo que reduce el riesgo de señales falsas.

Condiciones de compra:

  1. Señal fractal: Una señal fractal bajista se produce cuando la acción del precio forma una serie de máximos cada vez más bajos, lo que sugiere una posible reversión alcista del precio.
  2. Desglose de la línea del Alligator: La línea azul del Alligator (mandíbula) se rompe de abajo hacia arriba, lo que indica el inicio de una tendencia alcista.
  3. Confirmación del Accelerator Oscillator (AC): El AC se encuentra en la zona verde, lo que indica un impulso alcista y respalda la fortaleza de la tendencia.
  4. Confirmación del Awesome Oscillator (AO): El histograma del AO cruza por encima de la línea cero de abajo hacia arriba, lo que confirma aún más el impulso alcista.

Condición de compra:

Una entrada de compra se activa después de que el histograma del indicador AO cruza por encima de la línea cero desde abajo, lo que confirma un aumento en el impulso alcista. Esto es una indicación de que se está desarrollando una fuerte tendencia alcista y es el momento en el que abrimos una posición de compra en el mercado. A continuación se muestra un ejemplo de una señal de compra en el gráfico de MetaTrader 5.

SEÑAL DE COMPRA EN MT5

Condiciones de venta:

  1. Señal fractal: Una señal fractal alcista se produce cuando la acción del precio forma una serie de mínimos más altos, lo que sugiere una posible reversión a la baja del precio.
  2. Desglose de la línea del Alligator: La línea azul del Alligator (mandíbula) se rompe de arriba abajo, lo que indica el inicio de una tendencia bajista.
  3. Confirmación del Accelerator Oscillator (AC): El AC se encuentra en la zona roja, lo que confirma un fuerte impulso bajista e indica una alta probabilidad de que continúe el movimiento a la baja.
  4. Confirmación del Awesome Oscillator (AO): El histograma del AO cruza por debajo de la línea cero de arriba abajo, lo que indica una tendencia bajista.

Condición de venta:

Una entrada de venta se activa después de que el histograma AO cruza por debajo de la línea cero desde arriba, lo que confirma el impulso bajista. Esto indica que es probable que el mercado continúe moviéndose a la baja, y es el punto en el que abrimos una posición de venta en el mercado.

Condiciones de salida o reversión:

  1. Reversión de la línea del Alligator: Se produce una reversión de la línea verde del Alligator (labios), lo que sugiere el final de la tendencia actual. El cambio de dirección de los labios indica que el precio podría estar revirtiéndose o consolidándose.
  2. Reversión del Accelerator Oscillator (AC): El AC cruza de la zona verde a la zona roja (o viceversa), lo que indica un posible cambio en el impulso. Este es un indicador temprano de que el impulso del mercado está cambiando y que la tendencia actual podría estar llegando a su fin.
  3. Reversión del Awesome Oscillator (AO): El histograma AO cruza la línea cero en la dirección opuesta, lo que confirma aún más que es probable que se produzca una reversión de la tendencia.

Condición de salida:

Se puede utilizar cualquiera o todas las condiciones de salida anteriores, pero en nuestro caso elegiremos salir de las posiciones cuando los histogramas del indicador AO crucen a la zona opuesta, lo que indica un cambio en el impulso del mercado.

Al utilizar la combinación de los indicadores mencionados anteriormente, el sistema Profitunity ofrece un enfoque poderoso para identificar reversiones del mercado y oportunidades de tendencias fuertes. En la siguiente sección, analizaremos cómo implementar estas condiciones de entrada y salida en MQL5, lo que permitirá la automatización completa de esta estrategia.


Implementación de la estrategia en MQL5

Después de aprender todas las teorías sobre la estrategia de trading Profitunity de Bill William, automatizamos la teoría y creamos un Asesor Experto (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. Como alternativa, puede hacer clic en el icono IDE (Integrated Development Environment) de la barra de herramientas. Esto abrirá el entorno MetaEditor, que permite escribir robots de trading, indicadores técnicos, scripts y bibliotecas de funciones.

METAEDITOR ABIERTO

Una vez abierto el MetaEditor, en la barra de herramientas, vaya a la pestaña Archivo y marque Nuevo archivo, o simplemente presione CTRL + N, para crear un nuevo documento. Alternativamente, puede hacer clic en el icono Nuevo en la pestaña de herramientas. Esto generará una ventana emergente del Asistente MQL (MQL Wizard).

CREAR NUEVO EA

En el asistente que aparece, marque Asesor experto (plantilla) y haga clic en Siguiente.

ASISTENTE MQL

En las propiedades generales del Asesor Experto, en la sección «Nombre», introduzca el nombre del archivo de su asesor. 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.

NUEVO NOMBRE DEL EA

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, ahora estamos listos para codificar y programar nuestra estrategia.

En primer lugar, comenzamos definiendo algunos metadatos sobre el Asesor Experto (EA). Esto incluye el nombre del 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. PROFITUNITY (TRADING CHAOS BY BILL WILLIAMS).mq5 |
//|      Copyright 2024, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader. |
//|                                     https://forexalgo-trader.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader"
#property link      "https://forexalgo-trader.com"
#property description "1. PROFITUNITY (TRADING CHAOS BY BILL WILLIAMS)"
#property version   "1.00"

Esto mostrará los metadatos del sistema al cargar el programa.<segmento 0151 ¶> 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 de operación. 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.

CLASE CTRADE

A continuación, debemos declarar varios indicadores importantes que utilizaremos en el sistema de trading.

int handle_Fractals = INVALID_HANDLE; //--- Initialize fractals indicator handle with an invalid handle value
int handle_Alligator = INVALID_HANDLE; //--- Initialize alligator indicator handle with an invalid handle value
int handle_AO = INVALID_HANDLE; //--- Initialize Awesome Oscillator (AO) handle with an invalid handle value
int handle_AC = INVALID_HANDLE; //--- Initialize Accelerator Oscillator (AC) handle with an invalid handle value

Aquí, configuramos las variables iniciales para almacenar los identificadores de cada indicador técnico del programa. En concreto, inicializamos cuatro variables int (integer) —«handle_Fractals», «handle_Alligator», «handle_AO» y «handle_AC»— con el valor INVALID_HANDLE.

Cada uno de estos identificadores servirá como referencia para acceder a los respectivos indicadores a lo largo del código. Al asignar el valor inicial «INVALID_HANDLE», nos aseguramos de que cada variable de controlador muestre claramente un estado no válido hasta que se produzca la inicialización adecuada más adelante en el código. Esta configuración evita errores derivados del uso de un identificador no inicializado y ayuda a detectar si algún indicador no se carga correctamente durante el proceso de inicialización.

En resumen, esto es lo que harán los indicadores individualmente.

  • "handle_Fractals" Almacenará el identificador del indicador de fractales.
  • "handle_Alligator" Almacenará el identificador del indicador Alligator.
  • "handle_AO" Almacenará el identificador para el Awesome Oscillator.
  • "handle_AC" Almacenará el identificador del Accelerator Oscillator.

A continuación, debemos definir e inicializar las matrices y constantes necesarias para almacenar y procesar datos de los indicadores utilizados en este Asesor Experto, que recuperamos específicamente de los controladores de indicadores inicializados. Lo haremos de forma secuencial para que todo quede claro y sea fácil de consultar.

double fractals_up[]; //--- Array to store values for upward fractals
double fractals_down[]; //--- Array to store values for downward fractals

double alligator_jaws[]; //--- Array to store values for Alligator's Jaw line
double alligator_teeth[]; //--- Array to store values for Alligator's Teeth line
double alligator_lips[]; //--- Array to store values for Alligator's Lips line

double ao_values[]; //--- Array to store values of the Awesome Oscillator (AO)

double ac_color[]; //--- Array to store color status of the Accelerator Oscillator (AC)
#define AC_COLOR_UP 0 //--- Define constant for upward AC color state
#define AC_COLOR_DOWN 1 //--- Define constant for downward AC color state

Comenzamos creando dos arrays (matrices), «fractals_up» y «fractals_down», que almacenarán los valores de los fractales ascendentes y descendentes, respectivamente. Estas matrices nos permitirán realizar un seguimiento de puntos fractales específicos, lo que nos ayudará a identificar reversiones o patrones significativos en los precios.

A continuación, configuramos tres matrices —«alligator_jaws», «alligator_teeth» y «alligator_lips»— para almacenar los valores de las diferentes líneas del indicador Alligator. Al mantener estos valores en matrices separadas, podemos realizar un seguimiento eficaz del estado de cada línea Alligator y utilizarlos en referencias cruzadas para señales de trading.

A continuación, definimos la matriz «ao_values» para almacenar los valores del Awesome Oscillator (AO). El AO nos ayudará a identificar el impulso y las tendencias del mercado, y el almacenamiento de estos valores nos permitirá analizar los cambios a lo largo del tiempo y aplicarlos a nuestras condiciones de negociación.

Por último, definimos la matriz «ac_color» para capturar el estado del color del oscilador acelerador (AC). Esta matriz contendrá información sobre el movimiento ascendente o descendente del aire acondicionado, que almacenaremos como un estado de color. Para facilitar esto, definimos dos constantes: «AC_COLOR_UP» (establecida en 0) y «AC_COLOR_DOWN» (establecida en 1). Estas constantes representarán los estados de color del AC, donde el verde (hacia arriba) indicará un aumento del impulso y el rojo (hacia abajo) indicará una tendencia a la desaceleración. Esta configuración simplificará nuestra lógica cuando más adelante comprobemos el estado del AC para las señales de trading.

Quizás hayas notado que podemos almacenar fácilmente los valores de otros indicadores directamente además de los fractales. Esto se debe a que sus valores están disponibles en cada barra. Sin embargo, en cuanto a los fractales, se forman en puntos de oscilación específicos que están al menos a 3 barras de la barra actual. Por lo tanto, no podemos simplemente recuperar cualquiera de las barras fractales a medida que se forman condicionalmente. Por lo tanto, necesitamos lógica para realizar un seguimiento del valor fractal anterior así como de su dirección. Esta es la lógica que adoptamos.

double lastFractal_value = 0.0; //--- Variable to store the value of the last detected fractal
enum fractal_direction {FRACTAL_UP, FRACTAL_DOWN, FRACTAL_NEUTRAL}; //--- Enum for fractal direction states
fractal_direction lastFractal_direction = FRACTAL_NEUTRAL; //--- Variable to store the direction of the last fractal

Aquí definimos variables y una enumeración para almacenar y gestionar los datos del fractal más reciente detectado en nuestro análisis comercial. Comenzamos declarando la variable «lastFractal_value» e inicializándola con el valor «0.0». Esta variable almacenará el valor numérico del último fractal que detectemos en el gráfico. Al realizar un seguimiento de este valor, podemos utilizarlo para compararlo con el precio actual y analizar las formaciones fractales en busca de posibles señales de trading.

A continuación, definimos una enumeración llamada «fractal_direction» con tres estados posibles: «FRACTAL_UP», «FRACTAL_DOWN» y «FRACTAL_NEUTRAL». Estos estados representan la dirección del último fractal:

  • «FRACTAL_UP» indicará un fractal ascendente, lo que señala posibles condiciones bajistas.
  • «FRACTAL_DOWN» indicará un fractal descendente, lo que señala posibles condiciones alcistas.
  • «FRACTAL_NEUTRAL» representa un estado en el que no se ha confirmado ninguna dirección fractal específica.

Por último, declaramos una variable, «lastFractal_direction», de tipo «fractal_direction» y la inicializamos con «FRACTAL_NEUTRAL». Esta variable contendrá la dirección del último fractal detectado, lo que nos permitirá realizar evaluaciones direccionales dentro de la lógica de negociación basadas en los datos fractales más recientes.

A partir de ahí, podemos pasar ahora a la lógica real de procesamiento del código. Necesitaremos inicializar nuestros indicadores, por lo que nos sumergiremos directamente en el controlador de eventos OnInit, que se invoca y ejecuta cada vez que se inicializa el programa.
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
   //---

   //---
   return(INIT_SUCCEEDED); //--- Return successful initialization status
}

Este es simplemente el controlador de eventos de inicialización predeterminado que utilizaremos para inicializar la lógica de control de nuestro programa. A continuación, debemos inicializar los indicadores para que se adjunten al gráfico y podamos utilizarlos para la recuperación de datos y la toma de decisiones. Primero inicializaremos el indicador de fractales como se muestra a continuación.

   handle_Fractals = iFractals(_Symbol,_Period); //--- Initialize the fractals indicator handle
   if (handle_Fractals == INVALID_HANDLE){ //--- Check if the fractals indicator failed to initialize
      Print("ERROR: UNABLE TO INITIALIZE THE FRACTALS INDICATOR. REVERTING NOW!"); //--- Print error if fractals initialization failed
      return (INIT_FAILED); //--- Exit initialization with failed status
   }

Aquí, inicializamos la variable «handle_Fractals» llamando a la función iFractals. Esta función crea un identificador para el indicador Fractales, aplicado al símbolo especificado (representado por _Symbol) y al período actual del gráfico (_Period). Al establecer «handle_Fractals» en el valor de retorno de iFractals, habilitamos el acceso a los datos del indicador, que luego podemos utilizar para analizar las formaciones fractales en nuestra estrategia.

Después de intentar inicializar el indicador fractales, verificamos si se ha realizado correctamente comprobando si «handle_Fractals» es igual a INVALID_HANDLE. Un valor «INVALID_HANDLE» indica que el indicador no se ha podido inicializar, lo que puede deberse a diversas causas, como la falta de recursos del sistema o parámetros incorrectos.

Si la inicialización falla, utilizamos la función Print para mostrar un mensaje de error: «ERROR: UNABLE TO INITIALIZE THE FRACTALS INDICATOR. REVERTING NOW!", en la pestaña Diario. Este mensaje servirá como una notificación clara del problema, lo que facilitará la resolución del mismo. A continuación, devolvemos INIT_FAILED para salir de la función OnInit, lo que indica que el proceso de inicialización no se ha completado correctamente. Esta comprobación ayuda a garantizar que el Asesor Experto no continúe con una configuración incompleta, lo que podría provocar errores durante la ejecución. Hacemos lo mismo para la inicialización del indicador Alligator.

   handle_Alligator = iAlligator(_Symbol,_Period,13,8,8,5,5,3,MODE_SMMA,PRICE_MEDIAN); //--- Initialize the alligator indicator with specific settings
   if (handle_Alligator == INVALID_HANDLE){ //--- Check if the alligator indicator failed to initialize
      Print("ERROR: UNABLE TO INITIALIZE THE ALLIGATOR INDICATOR. REVERTING NOW!"); //--- Print error if alligator initialization failed
      return (INIT_FAILED); //--- Exit initialization with failed status
   }

Inicializamos la variable «handle_Alligator» llamando a la función iAlligator. El indicador Alligator requiere varios parámetros para definir sus tres líneas (Mandíbulas, Dientes y Labios), cada una de las cuales responde a las tendencias del mercado. Especificamos estos ajustes de la siguiente manera: «13» para el periodo de las mandíbulas, «8» para el periodo de los dientes y «5» para el periodo de los labios. Además, definimos valores de desplazamiento de «8», «5» y «3» para cada línea, establecemos el método de cálculo en MODE_SMMA (media móvil suavizada) y utilizamos PRICE_MEDIAN como tipo de precio.

Después de intentar inicializar el indicador Alligator, comprobamos si «handle_Alligator» tiene un identificador válido. Si es igual a INVALID_HANDLE, indica que el proceso de inicialización ha fallado. Esto podría suceder debido a recursos insuficientes o parámetros incorrectos, lo que impediría que el indicador Alligator funcione correctamente.

Si la inicialización falla, llamamos a la función Print para mostrar un mensaje de error: «ERROR: UNABLE TO INITIALIZE THE ALLIGATOR INDICATOR. REVERTING NOW!" Este mensaje nos ayuda a detectar el problema, lo que facilita su diagnóstico y resolución. Después de imprimir el mensaje, devolvemos INIT_FAILED, lo que sale de la función OnInit e indica que la inicialización no se completó correctamente. 

Utilizamos un enfoque similar para inicializar los indicadores AO y AC de la siguiente manera.

   handle_AO = iAO(_Symbol,_Period); //--- Initialize the Awesome Oscillator (AO) indicator handle
   if (handle_AO == INVALID_HANDLE){ //--- Check if AO indicator failed to initialize
      Print("ERROR: UNABLE TO INITIALIZE THE AO INDICATOR. REVERTING NOW!"); //--- Print error if AO initialization failed
      return (INIT_FAILED); //--- Exit initialization with failed status
   }
   handle_AC = iAC(_Symbol,_Period); //--- Initialize the Accelerator Oscillator (AC) indicator handle
   if (handle_AC == INVALID_HANDLE){ //--- Check if AC indicator failed to initialize
      Print("ERROR: UNABLE TO INITIALIZE THE AC INDICATOR. REVERTING NOW!"); //--- Print error if AC initialization failed
      return (INIT_FAILED); //--- Exit initialization with failed status
   }

Una vez que todos los indicadores se hayan inicializado correctamente, podremos añadirlos automáticamente al gráfico cuando se cargue el programa. Utilizamos la siguiente lógica para lograrlo.

   if (!ChartIndicatorAdd(0,0,handle_Fractals)){ //--- Add the fractals indicator to the main chart window and check for success
      Print("ERROR: UNABLE TO ADD THE FRACTALS INDICATOR TO CHART. REVERTING NOW!"); //--- Print error if fractals addition failed
      return (INIT_FAILED); //--- Exit initialization with failed status
   }

Aquí, intentamos añadir el indicador Fractales a la ventana principal del gráfico utilizando la función ChartIndicatorAdd. Pasamos «0» como ID del gráfico (lo que indica el gráfico actual) y especificamos «0» como ID de la ventana, apuntando a la ventana principal del gráfico. La variable «handle_Fractals», que anteriormente inicializamos para almacenar el identificador del indicador de fractales, se pasa para añadir este indicador específico.

Después de llamar a la función ChartIndicatorAdd, comprobamos si la llamada a la función se ha realizado correctamente. Si devuelve «false», representado por «!», esto indica que el indicador de fractales no se pudo añadir al gráfico. El fallo aquí podría deberse a limitaciones del gráfico o a recursos insuficientes. En este caso, mostramos un mensaje de error utilizando Print para alertarnos: "ERROR: UNABLE TO ADD THE FRACTALS INDICATOR TO CHART. REVERTING NOW!" Este mensaje nos permitirá identificar rápidamente el origen del problema durante la depuración.

Si la adición falla, devolvemos INIT_FAILED para salir de la función OnInit con un estado de error, lo que garantiza que el Asesor Experto no se ejecutará si el indicador Fractales no aparece en el gráfico, lo que ayuda a evitar errores de ejecución posteriores al confirmar la disponibilidad visual del indicador. Se utiliza una lógica similar para añadir el indicador Alligator, ya que también se encuentra en la ventana principal, como se muestra a continuación.

   if (!ChartIndicatorAdd(0,0,handle_Alligator)){ //--- Add the alligator indicator to the main chart window and check for success
      Print("ERROR: UNABLE TO ADD THE ALLIGATOR INDICATOR TO CHART. REVERTING NOW!"); //--- Print error if alligator addition failed
      return (INIT_FAILED); //--- Exit initialization with failed status
   }

Para añadir los demás indicadores, se utiliza un enfoque similar, solo que ahora cambian las subventanas, ya que creamos una nueva subventana para cada indicador respectivamente, como se muestra a continuación:.

   if (!ChartIndicatorAdd(0,1,handle_AO)){ //--- Add the AO indicator to a separate subwindow and check for success
      Print("ERROR: UNABLE TO ADD THE AO INDICATOR TO CHART. REVERTING NOW!"); //--- Print error if AO addition failed
      return (INIT_FAILED); //--- Exit initialization with failed status
   }
   if (!ChartIndicatorAdd(0,2,handle_AC)){ //--- Add the AC indicator to a separate subwindow and check for success
      Print("ERROR: UNABLE TO ADD THE AC INDICATOR TO CHART. REVERTING NOW!"); //--- Print error if AC addition failed
      return (INIT_FAILED); //--- Exit initialization with failed status
   }

Añadimos los indicadores Awesome Oscillator (AO) y Accelerator Oscillator (AC) a subventanas separadas en el gráfico, asegurándonos de que cada uno tenga su vista dedicada. Para lograrlo, utilizamos la función ChartIndicatorAdd para cada indicador. Especificamos «0» para el ID del gráfico (lo que indica el gráfico actual) y utilizamos ID de ventana distintos: «1» para el indicador AO y «2» para el indicador AC, indicando que cada uno aparezca en una subventana única. La numeración aquí es crucial, ya que cada indicador debe estar en su ventana, por lo que es necesario llevar un control adecuado de la indexación de las subventanas.

A continuación, comprobamos el éxito de cada adición individualmente. Si ChartIndicatorAdd devuelve «false» para los indicadores AO o AC, significa que el proceso de adición ha fallado. En caso de fallo, mostramos un mensaje de error con «Print» para aclarar qué indicador específico no se ha podido cargar. Por ejemplo, si no se puede añadir el indicador AO, imprimimos "ERROR: UNABLE TO ADD THE AO INDICATOR TO CHART. REVERTING NOW!" Del mismo modo, mostramos un mensaje de error si falla la adición del indicador AC.

Si falla la adición de cualquiera de los indicadores, devolvemos inmediatamente INIT_FAILED, saliendo de la función OnInit e impidiendo que continúe la ejecución. Para asegurarnos de que todo está bien, podemos imprimir los indicadores en el diario.

   Print("HANDLE ID FRACTALS = ",handle_Fractals); //--- Print the handle ID for fractals
   Print("HANDLE ID ALLIGATOR = ",handle_Alligator); //--- Print the handle ID for alligator
   Print("HANDLE ID AO = ",handle_AO); //--- Print the handle ID for AO
   Print("HANDLE ID AC = ",handle_AC); //--- Print the handle ID for AC

Al ejecutar el programa, obtenemos los siguientes datos de inicialización.

EL INDICADOR MANEJA LOS DATOS

En la imagen podemos ver que los ID de los mangos comienzan en 10 y siguen secuencialmente hasta 13. Los identificadores de manejador son fundamentales en MQL5 porque nos permiten hacer referencia a cada indicador a lo largo del ciclo de vida del Asesor Experto. Cuando una función como CopyBuffer recupera valores de un indicador, se basa en estos identificadores para acceder a los datos correctos. Aquí, los números enteros representan identificadores específicos para cada indicador inicializado. Cada ID funciona como un «puntero» único dentro del entorno MQL5, vinculando cada identificador con su indicador designado. Esto ayuda al EA a saber qué datos del indicador debe extraer de la memoria, lo que favorece una ejecución eficiente y una organización clara de las tareas basadas en indicadores.

A partir de ahí, lo único que tenemos que hacer ahora es configurar los contenedores de datos como series temporales.

   ArraySetAsSeries(fractals_up,true); //--- Set the fractals_up array as a time series
   ArraySetAsSeries(fractals_down,true); //--- Set the fractals_down array as a time series
   
   ArraySetAsSeries(alligator_jaws,true); //--- Set the alligator_jaws array as a time series
   ArraySetAsSeries(alligator_teeth,true); //--- Set the alligator_teeth array as a time series
   ArraySetAsSeries(alligator_lips,true); //--- Set the alligator_lips array as a time series
   
   ArraySetAsSeries(ao_values,true); //--- Set the ao_values array as a time series
   
   ArraySetAsSeries(ac_color,true); //--- Set the ac_color array as a time series

Aquí, configuramos cada matriz para que funcione como una serie temporal, lo que significa que los datos dentro de cada matriz se organizarán desde los valores más recientes hasta los más antiguos. Para ello, aplicamos la función ArraySetAsSeries a cada matriz y pasamos «true» como segundo argumento. Esta configuración garantiza que el punto de datos más reciente siempre aparezca en el índice 0, lo que resulta especialmente útil en aplicaciones de negociación, donde acceder al valor más reciente es esencial para la toma de decisiones en tiempo real.

Comenzamos estableciendo las matrices «fractals_up» y «fractals_down» como series temporales, lo que nos permite realizar un seguimiento eficiente de los últimos valores fractales ascendentes y descendentes. Del mismo modo, aplicamos esta organización a las matrices «alligator_jaws», «alligator_teeth» y «alligator_lips», que representan las tres líneas del indicador Alligator. Esto nos permite acceder a los valores más recientes de cada línea en tiempo real, lo que facilita la detección de cualquier cambio en las tendencias del mercado.

También configuramos la matriz «ao_values», que almacena los datos del Awesome Oscillator, de la misma manera. Al configurarlo como una serie temporal, nos aseguramos de que el valor más reciente del oscilador esté siempre disponible para nuestros cálculos. Por último, aplicamos esta estructura a la matriz «ac_color», que realiza un seguimiento del estado del color del Accelerator Oscillator, de modo que se pueda acceder inmediatamente al estado de color más reciente. El fragmento de código de inicialización completo responsable de garantizar una inicialización fluida y limpia es el siguiente.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
   //---
   
   handle_Fractals = iFractals(_Symbol,_Period); //--- Initialize the fractals indicator handle
   if (handle_Fractals == INVALID_HANDLE){ //--- Check if the fractals indicator failed to initialize
      Print("ERROR: UNABLE TO INITIALIZE THE FRACTALS INDICATOR. REVERTING NOW!"); //--- Print error if fractals initialization failed
      return (INIT_FAILED); //--- Exit initialization with failed status
   }
   handle_Alligator = iAlligator(_Symbol,_Period,13,8,8,5,5,3,MODE_SMMA,PRICE_MEDIAN); //--- Initialize the alligator indicator with specific settings
   if (handle_Alligator == INVALID_HANDLE){ //--- Check if the alligator indicator failed to initialize
      Print("ERROR: UNABLE TO INITIALIZE THE ALLIGATOR INDICATOR. REVERTING NOW!"); //--- Print error if alligator initialization failed
      return (INIT_FAILED); //--- Exit initialization with failed status
   }
   handle_AO = iAO(_Symbol,_Period); //--- Initialize the Awesome Oscillator (AO) indicator handle
   if (handle_AO == INVALID_HANDLE){ //--- Check if AO indicator failed to initialize
      Print("ERROR: UNABLE TO INITIALIZE THE AO INDICATOR. REVERTING NOW!"); //--- Print error if AO initialization failed
      return (INIT_FAILED); //--- Exit initialization with failed status
   }
   handle_AC = iAC(_Symbol,_Period); //--- Initialize the Accelerator Oscillator (AC) indicator handle
   if (handle_AC == INVALID_HANDLE){ //--- Check if AC indicator failed to initialize
      Print("ERROR: UNABLE TO INITIALIZE THE AC INDICATOR. REVERTING NOW!"); //--- Print error if AC initialization failed
      return (INIT_FAILED); //--- Exit initialization with failed status
   }
   
   if (!ChartIndicatorAdd(0,0,handle_Fractals)){ //--- Add the fractals indicator to the main chart window and check for success
      Print("ERROR: UNABLE TO ADD THE FRACTALS INDICATOR TO CHART. REVERTING NOW!"); //--- Print error if fractals addition failed
      return (INIT_FAILED); //--- Exit initialization with failed status
   }
   if (!ChartIndicatorAdd(0,0,handle_Alligator)){ //--- Add the alligator indicator to the main chart window and check for success
      Print("ERROR: UNABLE TO ADD THE ALLIGATOR INDICATOR TO CHART. REVERTING NOW!"); //--- Print error if alligator addition failed
      return (INIT_FAILED); //--- Exit initialization with failed status
   }
   if (!ChartIndicatorAdd(0,1,handle_AO)){ //--- Add the AO indicator to a separate subwindow and check for success
      Print("ERROR: UNABLE TO ADD THE AO INDICATOR TO CHART. REVERTING NOW!"); //--- Print error if AO addition failed
      return (INIT_FAILED); //--- Exit initialization with failed status
   }
   if (!ChartIndicatorAdd(0,2,handle_AC)){ //--- Add the AC indicator to a separate subwindow and check for success
      Print("ERROR: UNABLE TO ADD THE AC INDICATOR TO CHART. REVERTING NOW!"); //--- Print error if AC addition failed
      return (INIT_FAILED); //--- Exit initialization with failed status
   }
   
   Print("HANDLE ID FRACTALS = ",handle_Fractals); //--- Print the handle ID for fractals
   Print("HANDLE ID ALLIGATOR = ",handle_Alligator); //--- Print the handle ID for alligator
   Print("HANDLE ID AO = ",handle_AO); //--- Print the handle ID for AO
   Print("HANDLE ID AC = ",handle_AC); //--- Print the handle ID for AC

   ArraySetAsSeries(fractals_up,true); //--- Set the fractals_up array as a time series
   ArraySetAsSeries(fractals_down,true); //--- Set the fractals_down array as a time series
   
   ArraySetAsSeries(alligator_jaws,true); //--- Set the alligator_jaws array as a time series
   ArraySetAsSeries(alligator_teeth,true); //--- Set the alligator_teeth array as a time series
   ArraySetAsSeries(alligator_lips,true); //--- Set the alligator_lips array as a time series
   
   ArraySetAsSeries(ao_values,true); //--- Set the ao_values array as a time series
   
   ArraySetAsSeries(ac_color,true); //--- Set the ac_color array as a time series
   
   //---
   return(INIT_SUCCEEDED); //--- Return successful initialization status
}

A continuación, podemos pasar al controlador de eventos OnTick, donde alojaremos la lógica de control.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//---
}
Este es simplemente el controlador de eventos predeterminado que utilizaremos como base para nuestra lógica de control. A continuación, necesitamos recuperar los valores de los datos de los indicadores para su posterior análisis.
   if (CopyBuffer(handle_Fractals,0,2,3,fractals_up) < 3){ //--- Copy upward fractals data; check if copying is successful
      Print("ERROR: UNABLE TO COPY THE FRACTALS UP DATA. REVERTING!"); //--- Print error message if failed
      return;
   }
   if (CopyBuffer(handle_Fractals,1,2,3,fractals_down) < 3){ //--- Copy downward fractals data; check if copying is successful
      Print("ERROR: UNABLE TO COPY THE FRACTALS DOWN DATA. REVERTING!"); //--- Print error message if failed
      return;
   }

Aquí, estamos copiando datos de los búferes «fractals_up» y «fractals_down» del indicador «handle_Fractals» a sus respectivas matrices. Utilizamos la función CopyBuffer para recuperar datos del identificador del indicador. En concreto, estamos intentando copiar tres puntos de datos a partir de la tercera barra más reciente (índice 2) tanto para los fractales ascendentes como para los descendentes.

En primer lugar, comprobamos si la función devuelve un valor inferior a 3, lo que indicaría que se han copiado con éxito menos de 3 valores en la matriz «fractals_up». Si esto ocurre, imprimimos un mensaje de error "ERROR: UNABLE TO COPY THE FRACTALS UP DATA. REVERTING!" y salir de la función para evitar cualquier procesamiento adicional con datos incompletos.

Del mismo modo, intentamos copiar los datos fractales descendentes en la matriz «fractals_down» utilizando la misma función CopyBuffer. De nuevo, si la copia falla (se devuelven menos de 3 valores), imprimimos el mensaje de error correspondiente "ERROR: UNABLE TO COPY THE FRACTALS DOWN DATA. REVERTING!") y salir de la función para evitar más problemas. Este enfoque garantiza que nuestro programa no proceda con datos inválidos o incompletos, manteniendo así la integridad de nuestra lógica comercial. Al verificar que se ha copiado el número correcto de valores, podemos evitar posibles errores en el análisis de fractales, que son cruciales para detectar puntos de reversión del mercado.

Sin embargo, es posible que hayas notado que los números del búfer del indicador varían, 0 y 1. Estos son índices cruciales a los que debes prestar mucha atención, ya que representan los buffers de mapeo reales de los valores del indicador. Aquí hay una ilustración para que entendamos por qué utilizamos los índices específicos.

ÍNDICES DE BÚFER

En la imagen podemos ver que el fractal ascendente es el primero, por lo que tiene el índice 0, y el fractal descendente es el segundo, con el índice 1. Siguiendo la misma lógica, trazamos las líneas del Alligator.

   if (CopyBuffer(handle_Alligator,0,0,3,alligator_jaws) < 3){ //--- Copy Alligator's Jaw data
      Print("ERROR: UNABLE TO COPY THE ALLIGATOR JAWS DATA. REVERTING!");
      return;
   }
   if (CopyBuffer(handle_Alligator,1,0,3,alligator_teeth) < 3){ //--- Copy Alligator's Teeth data
      Print("ERROR: UNABLE TO COPY THE ALLIGATOR TEETH DATA. REVERTING!");
      return;
   }
   if (CopyBuffer(handle_Alligator,2,0,3,alligator_lips) < 3){ //--- Copy Alligator's Lips data
      Print("ERROR: UNABLE TO COPY THE ALLIGATOR LIPS DATA. REVERTING!");
      return;
   }

Aquí, dado que los búferes están en 3 líneas, se puede ver que los índices de los búferes comienzan desde 0 hasta 1 y 2. En el caso de que solo haya un búfer, como en el caso del indicador AO, solo tendríamos un índice de búfer 0 para los precios, como se muestra a continuación.

   if (CopyBuffer(handle_AO,0,0,3,ao_values) < 3){ //--- Copy AO data
      Print("ERROR: UNABLE TO COPY THE AO DATA. REVERTING!");
      return;
   }

Lo mismo se aplicaría al indicador de CA si nos interesaran sus valores. Sin embargo, solo necesitamos conocer el color del histograma formado. Estos se pueden adquirir utilizando la misma lógica de número de búfer, pero en este caso, los búferes de color se asignan principalmente en el siguiente índice de búfer no disponible en la ventana de datos. Por lo tanto, en nuestro caso, es 0+1=1, como se muestra a continuación.

   if (CopyBuffer(handle_AC,1,0,3,ac_color) < 3){ //--- Copy AC color data
      Print("ERROR: UNABLE TO COPY THE AC COLOR DATA. REVERTING!");
      return;
   }

En realidad, para obtener los búferes de color, solo tienes que abrir la ventana de propiedades del indicador y el índice de colores aparecerá en la pestaña de parámetros.

ÍNDICE DE BÚFER DE COLOR

Después de recuperar y almacenar los datos, podemos utilizarlos para tomar decisiones comerciales. Para ahorrar recursos, realizaremos comprobaciones en cada barra y no en cada tick que se genere. Por lo tanto, necesitamos lógica para detectar nuevas formaciones de barras.

if (isNewBar()){ //--- Check if a new bar has formed

//---

}

Aquí utilizamos una función personalizada cuyo fragmento de código es el siguiente.

//+------------------------------------------------------------------+
//|   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
}

Aquí definimos una función booleana llamada «isNewBar» que comprueba si ha aparecido una nueva barra en el gráfico, lo que nos ayudará a detectar cuándo se forma una nueva vela o barra, algo esencial para actualizar o recalcular las condiciones de negociación. Comenzamos declarando una variable estática entera llamada «prevBars» e inicializándola con el valor 0. La palabra clave «static» garantiza que el valor de «prevBars» se mantenga en múltiples llamadas a la función, en lugar de restablecerse cada vez que se llama a la función. Esto nos permite almacenar el número de barras de la llamada anterior a la función.

A continuación, definimos una variable entera local «currBars» y utilizamos la función integrada iBars para recuperar el número actual de barras para el símbolo seleccionado (_Symbol) y el período (_Period). Esta función cuenta el número total de barras disponibles para el periodo de tiempo dado y lo almacena en «currBars».

A continuación, comparamos «prevBars» con «currBars». Si los dos valores son iguales, esto significa que no se ha formado ninguna barra nueva desde la última vez que se llamó a la función, por lo que devolvemos «false» para indicar que no se ha detectado ninguna barra nueva. Si el número de barras ha cambiado (es decir, se ha formado una nueva barra), la condición falla y actualizamos «prevBars» con el recuento actual de barras («currBars») para realizar un seguimiento del nuevo valor. Por último, devolvemos «true» para indicar que se ha detectado una nueva barra.

Ahora, dentro de esta función, podemos determinar y almacenar los datos fractales comprobando y actualizando el valor y la dirección del último fractal detectado basándonos en los datos almacenados en las matrices «fractals_up» y «fractals_down».

const int index_fractal = 0;
if (fractals_up[index_fractal] != EMPTY_VALUE){ //--- Detect upward fractal presence
   lastFractal_value = fractals_up[index_fractal]; //--- Store fractal value
   lastFractal_direction = FRACTAL_UP; //--- Set last fractal direction as up
}
if (fractals_down[index_fractal] != EMPTY_VALUE){ //--- Detect downward fractal presence
   lastFractal_value = fractals_down[index_fractal];
   lastFractal_direction = FRACTAL_DOWN;
}

Comenzamos definiendo una constante entera «index_fractal» y estableciéndola en 0. Esta constante representará el índice de los datos fractales actuales que queremos comprobar. En este caso, estamos comprobando el primer fractal de las matrices.

A continuación, comprobamos si el valor en «fractals_up[index_fractal]» no es igual a EMPTY_VALUE, lo que indica que hay un fractal ascendente válido presente. Si esta condición es verdadera, procedemos a almacenar el valor del fractal ascendente en la variable «lastFractal_value». También actualizamos «lastFractal_direction» a «FRACTAL_UP» para indicar que el último fractal detectado era un fractal ascendente.

Del mismo modo, comprobamos si el valor en «fractals_down[index_fractal]» no es igual a EMPTY_VALUE, lo que indica la presencia de un fractal descendente. Si esta condición es verdadera, almacenamos el valor fractal descendente en la variable «lastFractal_value» y establecemos «lastFractal_direction» en «FRACTAL_DOWN» para reflejar que el último fractal detectado fue descendente. A continuación, podemos registrar los datos adquiridos y comprobar su validez.

if (lastFractal_value != 0.0 && lastFractal_direction != FRACTAL_NEUTRAL){ //--- Ensure fractal is valid
   Print("FRACTAL VALUE = ",lastFractal_value);
   Print("FRACTAL DIRECTION = ",getLastFractalDirection());
}

Esto registrará los datos de "Fractals". Utilizamos una función personalizada para obtener la dirección fractal y su fragmento de código es el siguiente.

//+------------------------------------------------------------------+
//|     FUNCTION TO GET FRACTAL DIRECTION                            |
//+------------------------------------------------------------------+

string getLastFractalDirection(){
   string direction_fractal = "NEUTRAL"; //--- Default direction set to NEUTRAL
   
   if (lastFractal_direction == FRACTAL_UP) return ("UP"); //--- Return UP if last fractal was up
   else if (lastFractal_direction == FRACTAL_DOWN) return ("DOWN"); //--- Return DOWN if last fractal was down
   
   return (direction_fractal); //--- Return NEUTRAL if no specific direction
}

Aquí definimos la función «getLastFractalDirection» para determinar y devolver la dirección del último fractal detectado. La función comprueba el valor de la variable «lastFractal_direction», que registra la dirección fractal más reciente (ya sea ascendente o descendente). Comenzamos inicializando una variable string llamada «direction_fractal» y estableciéndola en «NEUTRAL» por defecto. Esto significa que, si no se encuentra ninguna dirección válida o la dirección fractal no se ha actualizado, la función devolverá «NEUTRAL» como resultado.Esto significa que, si no se encuentra ninguna dirección válida o la dirección fractal no se ha actualizado, la función devolverá «NEUTRAL» como resultado.

A continuación, comprobamos el valor de la variable «lastFractal_direction». Si es igual a «FRACTAL_UP» (lo que indica que el último fractal detectado fue un fractal ascendente), la función devuelve la cadena (string) «UP». Si «lastFractal_direction» es igual a «FRACTAL_DOWN» (lo que indica que el último fractal detectado fue un fractal descendente), la función devuelve la cadena (string) «DOWN». Si no se cumple ninguna de estas condiciones (es decir, no se ha detectado ningún fractal ascendente o descendente o la dirección sigue siendo neutra), la función devuelve el valor predeterminado «NEUTRAL», lo que indica que no hay una dirección específica en ese momento.

También podemos registrar los datos de los demás indicadores de la siguiente manera.

Print("ALLIGATOR JAWS = ",NormalizeDouble(alligator_jaws[1],_Digits));
Print("ALLIGATOR TEETH = ",NormalizeDouble(alligator_teeth[1],_Digits));
Print("ALLIGATOR LIPS = ",NormalizeDouble(alligator_lips[1],_Digits));

Print("AO VALUE = ",NormalizeDouble(ao_values[1],_Digits+1));

if (ac_color[1] == AC_COLOR_UP){
   Print("AC COLOR UP GREEN = ",AC_COLOR_UP);
}
else if (ac_color[1] == AC_COLOR_DOWN){
   Print("AC COLOR DOWN RED = ",AC_COLOR_DOWN);
}

Al ejecutarlo, obtenemos el siguiente resultado:

Confirmación de fractales:

CONFIRMACIÓN DE FRACTALES

Confirmación de otros indicadores:

CONFIRMACIÓN DE OTROS INDICADORES

A partir de la visualización, podemos ver que los datos recuperados coinciden con los datos reales que se ven en la ventana de datos, lo cual es un éxito. A continuación, podemos seguir utilizando estos datos con fines comerciales. En primer lugar, definimos algunas funciones necesarias que utilizaremos para realizar el análisis de la siguiente manera.

//+------------------------------------------------------------------+
//|        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 obtener los precios de cierre, de compra y de venta, respectivamente. A continuación, debemos definir variables booleanas para comprobar posibles señales de trading basadas en la línea «Alligator's Jaw» (Mandíbula del caimán), tal y como se muestra a continuación.

bool isBreakdown_jaws_buy = alligator_jaws[1] < getClosePrice(1) //--- Check if breakdown for buy
                            && alligator_jaws[2] > getClosePrice(2);
bool isBreakdown_jaws_sell = alligator_jaws[1] > getClosePrice(1) //--- Check if breakdown for sell
                            && alligator_jaws[2] < getClosePrice(2);

En primer lugar, definimos «isBreakdown_jaws_buy» para detectar una condición de ruptura para una posible señal de compra. La condición es que el valor de la matriz «alligator_jaws» en el índice 1 (que representa la barra anterior) debe ser inferior al precio de cierre de la barra anterior, que se obtiene llamando a la función «getClosePrice(1)». Además, el valor de la matriz «alligator_jaws» en el índice 2 (que representa la barra anterior) debe ser mayor que el precio de cierre de la barra anterior, que se obtiene llamando a la función «getClosePrice(2)». Esta combinación sugiere que la línea Alligator's Jaw ha cruzado por debajo del precio de cierre de la barra anterior, pero estaba por encima del precio de cierre de la barra anterior a esa, lo que podría interpretarse como una posible configuración para una operación de compra.

A continuación, definimos «isBreakdown_jaws_sell» para detectar una condición de ruptura para una posible señal de venta. En este caso, el valor «alligator_jaws» en el índice 1 debe ser mayor que el precio de cierre de la barra anterior, y el valor «alligator_jaws» en el índice 2 debe ser menor que el precio de cierre de la barra anterior a esa. Este escenario indica que la línea Alligator's Jaw ha cruzado por encima del precio de cierre de la barra anterior, pero estaba por debajo del precio de cierre de la barra anterior a esa, lo que sugiere una posible configuración para una operación de venta. Desde aquí podemos definir el resto de las condiciones para las posiciones abiertas.

if (lastFractal_direction == FRACTAL_DOWN //--- Conditions for Buy signal
   && isBreakdown_jaws_buy
   && ac_color[1] == AC_COLOR_UP
   && (ao_values[1] > 0 && ao_values[2] < 0)){
   Print("BUY SIGNAL GENERATED");
   obj_Trade.Buy(0.01,_Symbol,getAsk()); //--- Execute Buy order
}
else if (lastFractal_direction == FRACTAL_UP //--- Conditions for Sell signal
   && isBreakdown_jaws_sell
   && ac_color[1] == AC_COLOR_DOWN
   && (ao_values[1] < 0 && ao_values[2] > 0)){
   Print("SELL SIGNAL GENERATED");
   obj_Trade.Sell(0.01,_Symbol,getBid()); //--- Execute Sell order
}

Aquí implementamos la lógica para ejecutar señales de compra y venta basadas en la combinación de indicadores, concretamente la dirección fractal, la ruptura de la mandíbula del Alligator, el estado del color del oscilador acelerador (AC) y los valores del oscilador impresionante (AO).

En primer lugar, comprobamos si se cumplen las condiciones para una señal de compra. Verificamos que «lastFractal_direction» esté establecido en «FRACTAL_DOWN», lo que significa que el último fractal detectado fue un fractal descendente. A continuación, comprobamos si la condición «isBreakdown_jaws_buy» es verdadera, lo que indica que la línea de la mandíbula del Alligator ha cruzado por debajo del precio y ahora está preparada para una posible compra.

Además, nos aseguramos de que «ac_color[1]» sea igual a «AC_COLOR_UP», lo que significa que el oscilador acelerador se encuentra en un estado de color ascendente, lo que indica un sentimiento alcista en el mercado. Por último, comprobamos los valores del Awesome Oscillator: «ao_values[1]» debe ser mayor que cero (lo que indica un impulso positivo) y «ao_values[2]» debe ser menor que cero (lo que indica un impulso negativo anterior). Esta combinación sugiere que se está produciendo un cambio de tendencia, con el mercado pasando de negativo a positivo. Si se cumplen todas estas condiciones, se genera una señal de compra y ejecutamos una orden de compra con el tamaño de lote especificado (0.01) al precio de venta.

Por otro lado, comprobamos si se cumplen las condiciones para una señal de venta. Verificamos que «lastFractal_direction» esté establecido en «FRACTAL_UP», lo que significa que el último fractal detectado fue un fractal ascendente. A continuación, comprobamos si la condición «isBreakdown_jaws_sell» es verdadera, lo que indica que la línea de la mandíbula del Alligator ha cruzado por encima del precio y ahora está preparada para una posible orden de venta.

Además, nos aseguramos de que «ac_color[1]» sea igual a «AC_COLOR_DOWN», lo que significa que el oscilador acelerador se encuentra en un estado de color descendente, lo que indica un sentimiento bajista en el mercado. Por último, comprobamos los valores del Awesome Oscillator: «ao_values[1]» debe ser inferior a cero (lo que indica un impulso negativo) y «ao_values[2]» debe ser superior a cero (lo que indica un impulso positivo anterior). Esta combinación sugiere que el mercado está pasando de un impulso positivo a uno negativo. Si se cumplen todas estas condiciones, se genera una señal de venta y ejecutamos una orden de venta con el tamaño de lote especificado (0.01) al precio de compra.

Esto abrirá las posiciones. Sin embargo, se puede observar que no hay órdenes de salida colocadas y, por lo tanto, las posiciones permanecerán abiertas. Por lo tanto, necesitamos lógica para cerrar las posiciones basadas en las inversiones del indicador AO.

if (ao_values[1] < 0 && ao_values[2] > 0){ //--- Condition to close all Buy positions
   if (PositionsTotal() > 0){
      Print("CLOSE ALL BUY POSITIONS");
      for (int i=0; i<PositionsTotal(); i++){
         ulong pos_ticket = PositionGetTicket(i); //--- Get position ticket
         if (pos_ticket > 0 && PositionSelectByTicket(pos_ticket)){ //--- Check if ticket is valid
            ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
            if (pos_type == POSITION_TYPE_BUY){ //--- Close Buy positions
               obj_Trade.PositionClose(pos_ticket);
            }
         }
      }
   }
}
else if (ao_values[1] > 0 && ao_values[2] < 0){ //--- Condition to close all Sell positions
   if (PositionsTotal() > 0){
      Print("CLOSE ALL SELL POSITIONS");
      for (int i=0; i<PositionsTotal(); i++){
         ulong pos_ticket = PositionGetTicket(i); //--- Get position ticket
         if (pos_ticket > 0 && PositionSelectByTicket(pos_ticket)){ //--- Check if ticket is valid
            ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
            if (pos_type == POSITION_TYPE_SELL){ //--- Close Sell positions
               obj_Trade.PositionClose(pos_ticket);
            }
         }
      }
   }
}

Por lo tanto, necesitamos lógica para cerrar las posiciones basadas en las inversiones del indicador AO. En primer lugar, comprobamos si se da una condición para cerrar todas las posiciones de compra. Si «ao_values[1]» es menor que 0 y «ao_values[2]» es mayor que 0, esto indica un posible cambio de impulso negativo a impulso positivo. Esta es la condición para cerrar todas las posiciones de compra. Para ello, primero comprobamos si hay alguna posición abierta utilizando la función PositionsTotal.

Si hay posiciones, recorremos cada una de ellas y recuperamos el número de ticket de cada una utilizando la función PositionGetTicket. Para cada posición, validamos el ticket con la función PositionSelectByTicket, asegurándonos de que sea una posición válida. A continuación, recuperamos el tipo de posición utilizando PositionGetInteger y lo convertimos al enumerador ENUM_POSITION_TYPE. Si el tipo de posición es POSITION_TYPE_BUY, lo que significa que es una posición de compra, la cerramos utilizando «obj_Trade.PositionClose(pos_ticket)» e imprimimos «CLOSE ALL BUY POSITIONS» (Cerrar todas las posiciones de compra) para confirmar.

A continuación, comprobamos si se da una condición para cerrar todas las posiciones de venta. Si «ao_values[1]» es mayor que 0 y «ao_values[2]» es menor que 0, esto indica un posible cambio de impulso positivo a impulso negativo, lo que señala la necesidad de cerrar todas las posiciones de venta. Del mismo modo, primero comprobamos si hay alguna vacante. Si los hay, los recorremos, recuperamos los tickets de posición, validamos los tickets y comprobamos el tipo de posición. Si el tipo de posición es POSITION_TYPE_SELL, lo que significa que es una posición de venta, la cerramos utilizando «obj_Trade.PositionClose(pos_ticket)» e imprimimos «CLOSE ALL SELL POSITIONS» (Cerrar todas las posiciones de venta) para confirmar.

Una vez que ejecutamos el programa, obtenemos el siguiente resultado.

POSICIÓN DE COMPRA

Fue todo un éxito. Podemos ver que confirmamos y abrimos la posición de compra cuando se cumplieron todas las condiciones de entrada. Eso es todo para la implementación de la estrategia. Ahora debemos probar el programa en el Probador de estrategias y optimizarlo si es necesario para que se adapte a las condiciones actuales del mercado. Esto se hace en la siguiente sección a continuación.


Prueba y optimización de estrategias

Tras completar la implementación básica, la siguiente fase consiste ahora en probar nuestro Asesor Experto (EA) utilizando el Probador de Estrategias de MetaTrader 5 para evaluar con precisión su rendimiento en diversos escenarios de mercado. Esta fase de pruebas tiene como objetivo verificar que el comportamiento de la estrategia se ajusta a nuestras expectativas e identificar cualquier ajuste necesario para optimizar los resultados. Aquí ya hemos completado una optimización inicial, centrándonos específicamente en los parámetros fundamentales para nuestra estrategia.

Hemos prestado especial atención a los umbrales de la línea fractal y la línea del cocodrilo para evaluar la capacidad de respuesta del EA en diferentes sesiones y condiciones de negociación. Estas exhaustivas pruebas nos han permitido validar que el programa se ajusta a las señales de compra y venta esperadas con una gestión eficiente de las operaciones, lo que mejora la fiabilidad y el rendimiento al tiempo que minimiza los posibles errores. Estos son los resultados que obtuvimos del proceso de prueba.

Resultados de la prueba retrospectiva:

RESULTADOS

Gráfico de la prueba retrospectiva:

GRÁFICO


Conclusión

En este artículo, exploramos el proceso de creación de un Asesor Experto (EA) en MQL5 utilizando la estrategia de trading Profitunity, integrando fractales, el indicador Alligator y osciladores como los osciladores Awesome y Accelerator para identificar señales estratégicas de compra y venta. Partiendo de los indicadores principales y las condiciones basadas en umbrales, automatizamos las señales de trading que aprovechan el impulso del mercado y las rupturas de precios. Cada paso implica una cuidadosa construcción del código, la configuración de los indicadores y la implementación de la lógica para activar las operaciones de compra y venta de acuerdo con los criterios de la estrategia. Tras completar la implementación, sometimos al EA a rigurosas pruebas utilizando el Strategy Tester de MetaTrader 5 para validar su capacidad de respuesta y fiabilidad en diversas condiciones de mercado, haciendo hincapié en la precisión en la ejecución de las operaciones mediante parámetros optimizados.

Descargo de responsabilidad: Este artículo es una guía educativa para crear un programa personalizado basado en señales de trading impulsadas por indicadores utilizando la estrategia de trading Profitunity. Las estrategias y métodos compartidos no garantizan resultados comerciales específicos y deben utilizarse con precaución. Realice siempre pruebas y validaciones exhaustivas, adaptando las soluciones de trading automatizado a las condiciones reales del mercado y a su tolerancia personal al riesgo.

Este artículo ofrece un enfoque estructurado para automatizar las señales de trading en MQL5 utilizando la estrategia Profitunity. Esperamos que esto le anime a seguir explorando el desarrollo de MQL5 e inspire la creación de sistemas de trading más sofisticados y rentables. ¡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/16365

Lagge
Lagge | 8 jul 2025 en 20:10
Gracias. Muy bien explicado (gracias a los muchos comentarios en el código del programa) y fácil de entender incluso como un recién llegado a Mql5. Muy adecuado para los principiantes en MQL5. También voy a trabajar a través de los otros artículos de su serie y espero que entonces también puedo implementar mi propia EA.
MrBrooklin
MrBrooklin | 9 jul 2025 en 04:01

Tengo una pregunta para el autor del artículo con respecto a esta parte del texto:

В частности, мы инициализируем четыре переменные типа integer: "handle_Fractals", "handle_Alligator", "handle_AO" и "handle_AC» со значением INVALID_HANDLE.

Como principiante en programación MQL5, no me queda muy claro por qué es necesario inicializar los handles de todos los indicadores con el valor INVALID_HANDLE a la vez? ¿Qué pasará si declaramos los handles de los indicadores sin inicializarlos? ¿El Asesor Experto no funcionará o qué?

Saludos, Vladimir.

Allan Munene Mutiiria
Allan Munene Mutiiria | 5 ago 2025 en 08:49
Lagge #:
Gracias. Muy bien explicado (gracias a los muchos comentarios en el código del programa) y fácil de entender incluso como un recién llegado a Mql5. Muy adecuado para los principiantes en MQL5. También voy a trabajar a través de los otros artículos de su serie y espero que entonces también puedo implementar mi propia EA.

Gracias por los amables comentarios. Claro, bienvenido.

Allan Munene Mutiiria
Allan Munene Mutiiria | 5 ago 2025 en 09:04
MrBrooklin principiante en programación MQL5, no me queda muy claro por qué es necesario inicializar los handles de todos los indicadores con el valor INVALID_HANDLE a la vez? ¿Qué pasará si declaramos los handles de los indicadores sin inicializarlos? ¿El Asesor Experto no funcionará o qué?

Saludos, Vladimir.

Gracias por su amable comentario. No es una obligación inicializar las manijas, pero es una buena práctica de programación hacerlo para que pueda comprobar si se inicializaron después de definirlas para evitar posibles errores. Es sólo por seguridad. Por ejemplo, puedes hacer esto:

//--- en un ámbito global
int m_handleRsi; // ASA NO INICIALIZADA
OR
int m_handleRsi = INVALID_HANDLE; // MANGO INICIALIZADO


//--- en la inicialización
m_handleRsi = iRSI(m_symbol, RSI_TF, RSI_PERIOD, RSI_APP_PRICE); // PODRÍAS INICIALIZAR Y SEGUIR ADELANTE
OR
m_handleRsi = iRSI(m_symbol, RSI_TF, RSI_PERIOD, RSI_APP_PRICE); // SE PODRÍA INICIALIZAR Y COMPROBAR. ES MEJOR
if (m_handleRsi == INVALID_HANDLE) {
   Print("Failed to initialize RSI indicator");
   return false;
}

// Así que ahora cualquiera funcionará. Tomemos un caso en el que la inicialización del indicador falla, aunque es raro.
// Si no hubo verificación, no se agregará ningún indicador y por lo tanto se alterará la lógica de la estrategia.
// Para el que lo haya comprobado, el programa terminará, evitando la estrategia false. En el manejador de eventos OnInit, devolverá inicialización fallida y el programa no se ejecutará.
// Así el usuario sabrá que algo ha fallado y necesita ser comprobado. Si no lo comprobó, el programa se ejecutará pero donde necesite el indicador de fallo, la lógica fallará. ¿Lo entiendes ahora?
// La lógica de inicialización tiene este aspecto:

int OnInit() {
   if (!(YOUR LOGIC) e.g. m_handleRsi == INVALID_HANDLE) {
      return INIT_FAILED;
   }
   return INIT_SUCCEEDED;
}

¿Tiene sentido ahora? Gracias.

Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Observador de Connexus (Parte 8): Cómo agregar un observador de solicitudes Observador de Connexus (Parte 8): Cómo agregar un observador de solicitudes
En esta última entrega de nuestra serie de bibliotecas Connexus, exploramos la implementación del patrón Observer, así como refactorizaciones esenciales de rutas de archivos y nombres de métodos. Esta serie cubrió todo el desarrollo de Connexus, diseñado para simplificar la comunicación HTTP en aplicaciones complejas.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Características del Wizard MQL5 que debe conocer (Parte 47): Aprendizaje por refuerzo con diferencia temporal Características del Wizard MQL5 que debe conocer (Parte 47): Aprendizaje por refuerzo con diferencia temporal
La diferencia temporal es otro algoritmo del aprendizaje por refuerzo que actualiza los valores Q basándose en la diferencia entre las recompensas previstas y las reales durante el entrenamiento del agente. Se centra específicamente en la actualización de los valores Q sin tener en cuenta su emparejamiento estado-acción. Por lo tanto, veremos cómo aplicar esto, tal y como hemos hecho en artículos anteriores, en un Asesor Experto creado mediante un asistente.