English Русский 中文 Deutsch 日本語 Português
preview
Dominar la dinámica del mercado: Crear un asesor experto (EA) de soportes y resistencias

Dominar la dinámica del mercado: Crear un asesor experto (EA) de soportes y resistencias

MetaTrader 5Trading | 29 octubre 2024, 08:51
855 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Introducción

En este artículo, analizaremos la estrategia de trading de Forex de soportes y resistencias, en el contexto del trading de acción de precio pura, y la creación de un asesor experto (EA) basado en ella. Vamos a explorar la definición, tipos, descripción y desarrollo de la estrategia en MetaQuotes Language 5 (MQL5) para MetaTrader 5 (MT5). No solo discutiremos la teoría detrás de la estrategia, sino también sus respectivos conceptos, análisis e identificación, y visualización en el gráfico, lo que la convertirá en una herramienta útil para que los traders aprendan a aumentar su capacidad de predecir los movimientos del mercado, tomar mejores decisiones y, eventualmente, volverse competentes en la gestión de riesgos. Utilizando los siguientes temas lograremos lo anterior:

  1. Definición de soporte y resistencia
  2. Descripción de soporte y resistencia
  3. Tipos de soportes y resistencias
  4. Descripción de la estrategia comercial
  5. Plan de la estrategia comercial
  6. Implementación en MetaQuotes Language 5 (MQL5)
  7. Resultados del Probador de estrategias
  8. Conclusión

En este viaje, utilizaremos ampliamente MetaQuotes Language 5 (MQL5) como nuestro entorno de codificación base 'Integrated Development Environment (IDE)', y ejecutaremos los archivos en el terminal de trading MetaTrader 5 (MT5). Por lo tanto, disponer de las versiones mencionadas será de vital importancia. Pues empecemos.


Definición de soporte y resistencia

La estrategia de compraventa de divisas de soporte y resistencia es una herramienta de análisis fundamental que utilizan muchos operadores de divisas para analizar e identificar los niveles de precios en los que es más probable que el mercado se detenga o retroceda. Técnicamente, estos niveles tienen una tendencia a ser rechazados por los precios históricos, lo que hace que los niveles sean significativos a lo largo del tiempo ya que el precio se detiene y se revierte una vez que alcanzan ellos, de ahí su nombre de soporte y resistencia. Cuando se construyen estos niveles, el precio normalmente rebota en los niveles clave varias veces, lo que indica un fuerte interés de compra o venta. 


Descripción de soporte y resistencia

La descripción de la estrategia de Soporte y Resistencia gira en torno a su aplicación en escenarios comerciales. Los niveles de soporte generalmente indican un límite inferior que el precio lucha por superar, lo que sugiere una concentración de demanda, mientras que los niveles de resistencia representan un límite superior que indica una concentración de oferta. Los compradores generalmente ingresan al mercado en niveles de soporte y es probable que los precios suban, por lo tanto, es un buen momento para que los operadores piensen en comprar o ir en largo. Por otro lado, los vendedores entran en la mezcla en niveles de resistencia y los precios pueden caer, lo que permite a los operadores vender o tomar posiciones en corto. Aquí hay una visualización de lo que queremos decir.

S y R

La entrada al mercado es siempre dinámica y depende del gusto y la preferencia de cada uno, aunque hay dos formas básicas de operar en los niveles. Algunos traders prefieren operar el rebote comprando cuando el precio cae hacia los niveles de soporte y vendiendo cuando el precio sube hacia los niveles de resistencia. Por el contrario, otros traders prefieren operar en la ruptura comprando cuando el precio rompe los niveles de resistencia y vendiendo cuando el precio rompe los niveles de soporte. Por lo tanto, uno puede o bien desvanecer la ruptura o bien negociar la ruptura.


Tipos de soportes y resistencias

Hay cuatro tipos de niveles de soporte y resistencia.

  • Niveles de soporte y resistencia de números redondos: Estos niveles se forman cuando el precio rebota en un nivel del mismo precio, lo que genera un canal de precios horizontal. Por ejemplo, los mínimos de un mercado podrían tener el mismo nivel de 0,65432, 0,65435 y 0,65437. Normalmente, se trata de los mismos niveles, con un ángulo de declinación insignificante, lo que indica una concentración de la demanda de precios.
  • Niveles de demanda y soporte del canal de tendencia: Los puntos de oscilación formados por líneas de tendencia ascendentes o descendentes crean zonas de oferta y demanda a las cuales los precios tienden a reaccionar.

LÍNEA DE TENDENCIA S&R

  • Niveles de soporte y resistencia de Fibonacci: Los traders utilizan Fibonacci para identificar zonas de reversión de precios, y estas zonas tienden a actuar como zonas de oferta y demanda para los niveles de soporte y resistencia.
  • Niveles de soporte y resistencia del indicador: Los indicadores técnicos, como los promedios móviles, proporcionan zonas donde los precios tienden a reaccionar para crear pivotes para los niveles de soporte y resistencia.

MA IND S&R


Descripción de la estrategia comercial

Como hemos visto, existen diferentes tipos de estrategias de soporte y resistencia en el ámbito Forex. Para el artículo, vamos a elegir y trabajar con el tipo de números redondos horizontales, y luego se puede emplear el mismo concepto y adaptarlo para los otros tipos.

Primero, analizaremos el gráfico y obtendremos las coordenadas de soporte y resistencia. Una vez señaladas las coordenadas respectivas, dibujaremos los niveles en el gráfico. Nuevamente, vimos que cada trader tiene dos opciones para operar los niveles, es decir, desvanecerse o operar la ruptura. En nuestro caso, desvaneceremos la ruptura. Abriremos posiciones de compra cuando rompamos los soportes y abriremos posiciones de venta cuando rompamos los niveles de resistencia. Tan simple como eso.


Plan de la estrategia comercial

Para comprender fácilmente el concepto que hemos transmitido, visualicémoslo en un plano.

  • Nivel de resistencia:

MODELO DE RESISTENCIA

  • Nivel de soporte:

MODELO DE SOPORTE


Implementación en MetaQuotes Language 5 (MQL5)

Después de aprender todas las teorías sobre la estrategia comercial de soporte y resistencia, automaticemos la teoría y creemos un asesor experto (EA) en MetaQuotes Language 5 (MQL5) para MetaTrader 5 (MT5).

Para crear un asesor experto (EA), en su terminal MetaTrader 5, haga clic en la pestaña Herramientas y marque el Editor de idioma MetaQuotes, o simplemente presione F4 en su teclado. Alternativamente, puede hacer clic en el icono IDE (Integrated Development Environment) en la barra de herramientas. Esto abrirá el entorno del Editor de lenguaje MetaQuotes, que permite escribir robots comerciales, 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 MQL5.

NUEVO EA

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

ASISTENTE MQL5

En las propiedades generales del Asesor Experto, debajo de la sección de nombre, proporcione el nombre de archivo de su experto. Tenga en cuenta que para especificar o crear una carpeta si no existe, utilice 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 podremos encontrarlo allí. Las demás secciones son bastante sencillas, pero puedes seguir el enlace en la parte inferior del Asistente para saber cómo realizar el proceso con precisión.

NOMBRE DEL EA

Después de proporcionar el nombre de archivo del Asesor Experto deseado, haga clic en Siguiente, luego en Siguiente y luego en Finalizar. Después de hacer todo eso, ahora estamos listos para codificar y programar nuestra estrategia.

Primero, incluimos una instancia comercial mediante el uso de #include al principio del código fuente. Esto nos da acceso a la clase CTrade que usaremos para crear un objeto comercial. Esto es crucial porque lo necesitamos para abrir operaciones.

#include <Trade/Trade.mqh>
CTrade obj_Trade;

El preprocesador reemplazará la línea #include<Trade/Trade.mqh> con 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 está incluido 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 una mejor estructura del código y una referencia más fácil. 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

En el ámbito global, necesitamos definir matrices que contendrán nuestros datos de precios más altos y más bajos, que luego manipularemos y analizaremos para encontrar los niveles de soporte y resistencia. Una vez encontrados los niveles, también debemos almacenarlos en una matriz, ya que, por supuesto, habrá más de uno.

double pricesHighest[], pricesLowest[];

double resistanceLevels[2], supportLevels[2];

Aquí, declaramos dos matrices dobles que contendrán los precios más altos y más bajos para una cantidad estipulada de datos, y algunas dos adicionales que contendrán los datos de niveles de soporte y resistencia identificados y ordenados. Estas son típicamente las dos coordenadas de cada nivel. Tenga en cuenta que, las variables de precio están vacías, lo que las convierte en matrices dinámicas sin un tamaño predefinido, lo que significa que pueden contener un número arbitrario de elementos en función de los datos proporcionados. Por el contrario, las variables de nivel tienen un tamaño fijo de dos, lo que las convierte en matrices estáticas, lo que significa que pueden contener exactamente dos elementos cada una. Si desea utilizar más coordenadas, puede aumentar su tamaño hasta la cantidad específica de puntos que considere adecuados.

Finalmente, una vez que identifiquemos los niveles, necesitaremos trazarlos en el gráfico para fines de visualización. De esta forma, debemos definir los nombres de las líneas, sus respectivos colores asignados y sus respectivos prefijos para una más fácil identificación y unicidad, en caso de que existan varios expertos en la misma cuenta de trading. Esto permite que el EA sea compatible con otros EAs, ya que identificará sus niveles y trabajará con ellos de forma efectiva e independiente.

#define resLine "RESISTANCE LEVEL"
#define colorRes clrRed
#define resline_prefix "R"

#define supLine "SUPPORT LEVEL"
#define colorSup clrBlue
#define supline_prefix "S"

Usamos la palabra clave #define para definir una macro llamada «resLine» con el valor «RESISTANCE LEVEL» para almacenar fácilmente nuestro nombre de nivel de resistencia, en lugar de tener que volver a escribir repetidamente el nombre en cada instancia en la que creamos el nivel, lo que nos ahorra significativamente tiempo y reduce las posibilidades de proporcionar erróneamente el nombre. Básicamente, las macros se utilizan para sustituir texto durante la compilación.

Del mismo modo, definimos el color del nivel de resistencia como rojo y, por último, definimos el prefijo «R» para los niveles de resistencia, que utilizaremos para etiquetar e identificar las líneas de resistencia en el gráfico. De forma similar a los niveles de resistencia, definimos los niveles de soporte, siguiendo los mismos criterios.

Una vez que inicializamos el EA, necesitamos establecer nuestros datos en una serie temporal, por lo que trabajaremos primero con los datos más recientes, y prepararemos nuestras matrices de almacenamiento para contener nuestros datos. Esto se hace en el manejador de eventos OnInit.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
//---
   
   ArraySetAsSeries(pricesHighest,true);
   ArraySetAsSeries(pricesLowest,true);
   // define the size of the arrays
   ArrayResize(pricesHighest,50);
   ArrayResize(pricesLowest,50);
//---
   return(INIT_SUCCEEDED);
}

Ocurren dos cosas distintas. En primer lugar, establecemos nuestras matrices de almacenamiento de precios como series temporales utilizando la función ArraySetAsSeries incorporada en MQL5, que toma dos argumentos, la matriz de destino y la bandera booleana, true en este caso para aceptar la conversión. Esto significa que los arrays se indexarán con los datos más antiguos en el índice más alto y los datos más recientes en el índice 0. He aquí un ejemplo. Digamos que recuperamos datos del año 2020 al 2024. Aquí está el formato en el que recibimos los datos.

Año Datos
2020 0
2021 1
2022 2
2023 3
2024 4

Los datos en el formato anterior no son cómodos de utilizar, ya que están ordenados por orden cronológico, donde los datos más antiguos se indexan en el primer índice, lo que significa que se utilizan siendo los primeros. Es más conveniente utilizar primero los datos más recientes para el análisis y, por lo tanto, tenemos que ordenar los datos en orden cronológico inverso para obtener los resultados que se indican a continuación.

Año Datos
2024 4
2023 3
2022 2
2021 1
2020 0

Para conseguir el formato anterior mediante programación, hacemos uso de la función ArraySetAsSeries, como se ha explicado anteriormente. En segundo lugar, definimos el tamaño de las matrices utilizando la función ArrayResize y especificando que contienen cincuenta elementos cada una. Esto podría ser cualquier cosa, sólo un valor arbitrario que tomamos, y usted puede optar por ignorarlo. Sin embargo, por formalidad, no necesitamos demasiados datos en nuestras matrices, ya que planeamos ordenar los datos de precios recibidos para tener sólo los diez primeros datos más significativos, y el tamaño extra se reservará. Así que puedes ver por qué no tiene sentido tener un tamaño mayor en nuestras matrices.

En el manejador del evento OnDeinit, necesitamos deshacernos de los datos de almacenamiento que han estado en uso de la memoria de la computadora. Esto ayudará a ahorrar recursos.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
//---
   ArrayFree(pricesHighest);
   ArrayFree(pricesLowest);
   
   //ArrayFree(resistanceLevels); // cannot be used for static allocated array
   //ArrayFree(supportLevels); // cannot be used for static allocated array
   
   ArrayRemove(resistanceLevels,0,WHOLE_ARRAY);
   ArrayRemove(supportLevels,0,WHOLE_ARRAY);
}

Utilizamos la función ArrayFree para deshacernos de los datos que ocupan parte de la memoria del ordenador ya que el EA dejará de estar en uso, y los datos serán inútiles. La función es un tipo de dato void que toma un único parámetro o argumento, el array dinámico, y libera su buffer, y establece el tamaño de la dimensión cero a 0. Sin embargo, para las matrices estáticas, donde almacenamos los precios de soporte y resistencia, no se puede utilizar la función. Esto no significa que no podamos deshacernos de los datos. Llamamos a otra función ArrayRemove, para eliminar los datos que queremos descartar. La función es un tipo de datos booleano que toma tres argumentos para eliminar un número determinado de elementos de una matriz. Especificamos la variable array destino y proporcionamos el índice a partir del cual comienza la operación de eliminación, en nuestro caso, es cero ya que queremos eliminar todo, y por último el número de elementos a eliminar, en este caso, todo el array para deshacernos de todo.

La mayoría de nuestras actividades se ejecutarán en el manejador de eventos OnTick. Esto será pura acción de precios y dependeremos en gran medida de este controlador de eventos. Así pues, echemos un vistazo a los parámetros que la función toma a su lado ya que es el corazón de este código.

void OnTick(){
//---

}

Como ya se ha visto, se trata de una función sencilla pero crucial que no toma argumentos ni devuelve nada. Es sólo una función void, lo que significa que no tiene que devolver nada. Esta función se utiliza en los Asesores Expertos y se ejecuta cuando hay un nuevo tick, es decir, un cambio en las cotizaciones de precios de la materia prima en particular.

Ahora que hemos visto que la función OnTick se genera en cada cambio de cotización, necesitamos definir alguna lógica de control que nos permita que el código se ejecute una vez por barra y no en cada tick, al menos para evitar ejecuciones innecesarias de código, ahorrando así memoria del dispositivo. Eso será necesario a la hora de buscar niveles de soporte y resistencia. No necesitamos buscar los niveles en cada tick, aunque siempre obtendremos los mismos resultados, siempre que sigamos en la misma vela. Aquí está la lógica:

   int currBars = iBars(_Symbol,_Period);
   static int prevBars = currBars;
   if (prevBars == currBars) return;
   prevBars = currBars;

En primer lugar, declaramos una variable entera «currBars» que almacena el número calculado de barras actuales en el gráfico para el símbolo de negociación y el período especificados, o mejor dicho, el marco temporal, como lo habrá oído. Esto se consigue mediante el uso de la función iBars, que sólo toma dos argumentos, es decir, símbolo y punto. 

A continuación, declaramos otra variable estática entera «prevBars» para almacenar el número total de barras anteriores en el gráfico cuando se genera una nueva barra y la inicializamos con el valor de las barras actuales en el gráfico para la primera ejecución de la función. Lo utilizaremos para comparar el número actual de barras con el número anterior de barras, para determinar la instancia de una nueva generación de barras en el gráfico.

Por último, utilizamos una sentencia condicional para comprobar si el número de compases actual es igual al número de compases anterior. Si son iguales, significa que no se ha formado ninguna barra nueva, por lo que terminamos la ejecución y volvemos. En caso contrario, si el recuento de la barra actual y el de la anterior no son iguales, indica que se ha formado una nueva barra. En este caso, procedemos a actualizar la variable barras anteriores a las barras actuales, de forma que en el siguiente tick, será igual al número de barras del gráfico no a menos que nos graduemos a una nueva.

Las barras que se tienen en cuenta para el análisis son sólo las barras visibles en el gráfico. Esto se debe a que no necesitamos considerar los datos más antiguos de unos diez millones de compases, ya que serían inútiles. Imagínese que tiene un nivel de apoyo que se remonta al año anterior. No tiene sentido, ¿verdad? Por lo tanto, ahora sólo tenemos en cuenta las barras que son visibles en el gráfico, ya que son los datos viables más recientes a las condiciones actuales del mercado. Para ello, utilizamos la siguiente lógica.

   int visible_bars = (int)ChartGetInteger(0,CHART_VISIBLE_BARS);

Declaramos la variable entera 'visible_bars' y utilizamos la función ChartGetInteger para obtener el total de barras visibles en el gráfico. Es una función de tipo de datos 'long', por lo que la convertimos a un entero agregando (int) antes de la función. Por supuesto, podríamos definir nuestra variable de destino como long, pero no necesitamos asignar bytes de memoria tan altos. 

Para buscar los niveles, tendremos que hacer un bucle a través de cada barra. Un bucle for es esencial para conseguirlo.

   for (int i=1; i<=visible_bars-1; i++){
   ...
   
   }

El bucle se realiza inicializando primero la variable entera contador de bucle "i" a uno, para designar el punto inicial del bucle. Uno significa que comenzamos en la barra anterior a la barra actual, ya que la barra actual está en la etapa de formación y, por lo tanto, aún está indecisa. Puede tener cualquier resultado. A continuación se muestra la condición que debe ser verdadera para que el bucle continúe ejecutándose. Mientras "i" sea menor o igual al total de barras consideradas menos uno, el bucle seguirá ejecutándose. Por último, incrementamos el contador de bucle "i" en uno cada vez que se ejecuta el bucle. Simplemente, i++ es lo mismo que i=i+1. También podríamos utilizar un bucle decreciente que tendría el operador de contador de bucle --, lo que llevaría a un análisis que comienza con la última barra más antigua hasta la barra actual, pero elegimos tener un bucle incremental para que el análisis de barras comience desde la barra más reciente hasta la barra más antigua.

En cada bucle, seleccionamos una barra o vela y, por lo tanto, necesitamos obtener las propiedades de la barra. En este caso, solo las propiedades de apertura, máximo, mínimo, cierre y tiempo de la barra son importantes para nosotros.

      double open = iOpen(_Symbol,_Period,i);
      double high = iHigh(_Symbol,_Period,i);
      double low = iLow(_Symbol,_Period,i);
      double close = iClose(_Symbol,_Period,i);
      datetime time = iTime(_Symbol,_Period,i);

Aquí, declaramos las respectivas variables de tipo de datos y las inicializamos con los datos correspondientes. Por ejemplo, utilice la función iOpen para obtener el precio de apertura de la barra, proporcionando el nombre del símbolo del instrumento financiero, su período y el índice de la barra de destino.

Después de obtener los datos de la barra, tendremos que comparar los datos con el resto de las barras anteriores para encontrar una barra que tenga los mismos datos que la seleccionada, lo que significará un nivel al que el precio ha reaccionado varias veces en el pasado. Sin embargo, no tiene sentido tener un nivel de soporte o resistencia que comprenda dos niveles consecutivos. Los niveles deberían al menos estar alejados. Definamos esto primero.

      int diff_i_j = 10;

Definimos una variable entera que contendrá la diferencia en barras entre la barra actual y la barra que se debe considerar para la verificación de una coincidencia del mismo nivel. En nuestro caso es 10. Representado visualmente, esto es lo que queremos decir.

DIFERENCIA DE BARRA

Ahora podemos iniciar un bucle que incorpore la lógica.

      for (int j=i+diff_i_j; j<=visible_bars-1; j++){
      ...
      
      }

Para el bucle for interno, utilizamos la variable de contador entero "j" que comienza a diez barras de la barra actual y llega hasta la penúltima barra. Para entenderlo fácilmente y sentirnos cómodos con los resultados antes de continuar, vamos a visualizarlo imprimiendo la salida.

//Print in the outer loop
      Print(":: BAR NO: ",i);

      //Print in the inner loop
         Print("BAR CHECK NO: ",j);

BARRAS DE BUCLE SELECCIONADAS

Puedes ver que, por ejemplo, para una barra seleccionada en el índice 15, inicializamos el bucle 10 barras a partir de la barra seleccionada actualmente. Matemáticamente, esto es 15+10=25. Luego, a partir de la vigésima quinta barra, el bucle se ejecuta hasta la penúltima barra, que en nuestro caso es la 33.

Ahora que podemos seleccionar correctamente el intervalo de las barras según sea necesario, también podemos obtener las propiedades de las barras seleccionadas.

         double open_j = iOpen(_Symbol,_Period,j);
         double high_j = iHigh(_Symbol,_Period,j);
         double low_j = iLow(_Symbol,_Period,j);
         double close_j = iClose(_Symbol,_Period,j);
         datetime time_j = iTime(_Symbol,_Period,j);

Se considera la misma lógica que la recuperación de propiedades del bucle externo. La única diferencia es que definimos nuestras variables con un guion bajo adicional "_j" para indicar que las propiedades son del bucle for interno, y el índice objetivo de la barra cambia a "j".

Como ya tenemos todos los datos de precios necesarios, podemos proceder a comprobar los niveles de soporte y resistencia.

         // CHECK FOR RESISTANCE
         double high_diff = NormalizeDouble((MathAbs(high-high_j)/_Point),0);
         bool is_resistance = high_diff <= 10;
         
         // CHECK FOR SUPPORT
         double low_diff = NormalizeDouble((MathAbs(low-low_j)/_Point),0);
         bool is_support = low_diff <= 10;

Para verificar los niveles de resistencia, definimos una variable doble high_diff que almacenará nuestros datos para la diferencia entre el máximo de la barra actualmente seleccionada en el bucle externo y la barra actualmente seleccionada en el bucle interno. MathAbs: La función se utiliza para garantizar que el resultado sea un número positivo independientemente de qué precio sea más alto, devolviendo el valor absoluto o del módulo de la entrada. Por ejemplo, podríamos tener 0,65432 – 0,05456 = -0,00024. Nuestra respuesta contiene un negativo, pero la función ignorará el signo negativo y generará 0,00024. Nuevamente, divida el resultado por el punto, el cambio de precio más pequeño posible de un instrumento, para obtener la forma de puntos de la diferencia. Usando nuestro ejemplo nuevamente, esto sería 0.00024/0.00001 = 24.0. Finalmente, para ser precisos, formateamos el número de punto flotante a una cantidad específica de dígitos mediante el uso de la función NormalizeDouble. En este caso, tenemos cero, lo que significa que nuestra salida será un número entero. Usando nuestro ejemplo nuevamente, tendríamos 24 sin el punto decimal.

Luego verificamos si la diferencia es menor o igual a diez, un rango predefinido en el que la diferencia es permitida, y almacenamos el resultado en una variable booleana is_resistance. La misma lógica se aplica a la verificación del nivel de soporte.

Si se cumplen las condiciones de nuestros niveles, nuestros niveles de soporte y resistencia serán verdaderos, de lo contrario serán falsos. Para ver si podemos identificar los niveles, imprimimoslos en el diario. Para comprobarlo todo, imprimimos los precios de las coordenadas del nivel de resistencia, junto con sus diferencias de precios para comprobar que cumplen nuestras condiciones.

         if (is_resistance){
            Print("RESISTANCE AT BAR ",i," (",high,") & ",j," (",high_j,"), Pts = ",high_diff);
          ...  
         }

Aquí está el resultado que obtenemos.

IMPRESIONES DE RESISTENCIA

Podemos identificar los niveles, pero son simplemente niveles cualesquiera. Como queremos niveles que estén en los puntos más altos o más bajos, necesitaremos alguna lógica de control adicional para garantizar que solo consideremos los niveles más significativos. Para lograr esto, necesitaremos copiar los precios altos y bajos de las barras en consideración, ordenarlos en orden ascendente y descendente respectivamente, y luego tomar la primera y la última cantidad de barras necesarias respectivamente. Hacemos esto antes de los bucles for.

   ArrayFree(pricesHighest);
   ArrayFree(pricesLowest);
   
   int copiedBarsHighs = CopyHigh(_Symbol,_Period,1,visible_bars,pricesHighest);
   int copiedBarsLows = CopyLow(_Symbol,_Period,1,visible_bars,pricesLowest);

Antes del almacenamiento, liberamos nuestras matrices de todos los datos. Luego copiamos los máximos de las barras a la matriz de destino. Esto se consigue mediante el uso de la función de tipo de datos entero CopyHigh, proporcionando el símbolo, el período, el índice inicial de la barra que se va a copiar, el número de barras y la matriz de almacenamiento de destino. El resultado, que es la cantidad de barras copiadas, se asigna a la variable entera copiedBarsHighs. Lo mismo ocurre con los precios bajos. Para asegurarnos de que obtenemos los datos, imprimimos las matrices en el diario, mediante el uso de la función ArrayPrint.

   ArrayPrint(pricesHighest);
   ArrayPrint(pricesLowest);

Estos son los resultados que obtenemos.

DATOS SIN ORDENAR

A continuación, ordenamos los datos en orden ascendente mediante el uso de la función ArraySort e imprimimos de nuevo los resultados.

         // sort the array in ascending order
   ArraySort(pricesHighest);
   ArraySort(pricesLowest);

   ArrayPrint(pricesHighest);
   ArrayPrint(pricesLowest);

Esto es lo que obtenemos.

DATOS ORDENADOS EN ORDEN ASCENDENTE

Por último, necesitamos obtener los primeros diez precios: los precios altos, y los últimos diez precios, los precios bajos, datos que formarán nuestros puntos extremos.

   ArrayRemove(pricesHighest,10,WHOLE_ARRAY);

Para obtener los diez primeros precios máximos de las barras más altas, utilizamos la función ArrayRemove, y proporcionamos el array destino, índice desde donde comienza la eliminación, en nuestro caso diez, y por último el número de elementos a eliminar, en nuestro caso el resto de datos.

Para obtener los diez últimos precios bajos de las barras más bajas, se realiza una operación similar, pero con un método más complejo y menos sencillo.

   ArrayRemove(pricesLowest,0,visible_bars-10);

Utilizamos la misma función, pero nuestro índice de inicio es cero, ya que no nos interesan los primeros valores, y el recuento es el número total de barras consideradas menos diez. Cuando imprimimos los datos, obtenemos la siguiente salida.

DATOS REQUERIDOS FIJOS FINALES

Ahora que tenemos los datos de las barras más altas, podemos seguir verificando los niveles y determinar las configuraciones válidas. Iniciamos otro bucle for para realizar la operación.

            for (int k=0; k<ArraySize(pricesHighest); k++){
            ...
            }

Esta vez, nuestra variable de contador k comienza desde cero ya que queremos considerar todos los precios de la matriz. 

Como queremos encontrar las coincidencias de precios, declaramos variables de almacenamiento booleanas que almacenarán los indicadores de los resultados de la coincidencia, fuera de los bucles for, y los inicializamos como falsos.

   bool matchFound_high1 = false, matchFound_low1 = false;
   bool matchFound_high2 = false, matchFound_low2 = false;

Si el precio almacenado seleccionado es igual al máximo de la barra en el primer bucle, establecemos el indicador para el primer máximo encontrado como verdadero e informamos de la instancia. De manera similar, si el precio almacenado elegido es igual al máximo de la barra en el segundo bucle, establecemos el indicador para el segundo máximo encontrado como verdadero e informamos de la instancia.

               if (pricesHighest[k]==high){
                  matchFound_high1 = true;
                  Print("> RES H1(",high,") FOUND @ ",k," (",pricesHighest[k],")");
               }
               if (pricesHighest[k]==high_j){
                  matchFound_high2 = true;
                  Print("> RES H2(",high_j,") FOUND @ ",k," (",pricesHighest[k],")");
               }

Si se encuentra la coincidencia para las dos coordenadas pero los niveles de resistencia actuales son iguales a los precios, entonces significa que ya tenemos los niveles. Así que no necesitamos continuar creando más niveles de resistencia. Informamos de la instancia, establecemos el indicador stop_processing en verdadero y salimos del bucle prematuramente.

               if (matchFound_high1 && matchFound_high2){
                  if (resistanceLevels[0]==high || resistanceLevels[1]==high_j){
                     Print("CONFIRMED BUT This is the same resistance level, skip updating!");
                     stop_processing = true; // Set the flag to stop processing
                     break; // stop the inner loop prematurely
                  }
                  ...
                  
               }

El indicador stop_processing se define fuera de los bucles for en la parte superior y se incorpora en la lógica de ejecución del primer bucle for, para garantizar que ahorremos recursos.

   bool stop_processing = false; // Flag to control outer loop

//...
   for (int i=1; i<=visible_bars-1 && !stop_processing; i++){
      ...
   
   }

De lo contrario, si se encuentra la coincidencia para las dos coordenadas pero no son iguales a los precios actuales, significa que tenemos otro nuevo nivel de resistencia y podemos actualizarlo a los últimos datos. 

                  else {
                     Print(" ++++++++++ RESISTANCE LEVELS CONFIRMED @ BARS ",i,
                     "(",high,") & ",j,"(",high_j,")");
                     resistanceLevels[0] = high;
                     resistanceLevels[1] = high_j;
                     ArrayPrint(resistanceLevels);
                     
                     ...
                  }

Aquí está la visualización de los resultados que obtenemos.

NIVELES DE RESISTENCIA CONFIRMADOS

Para visualizar los niveles, vamos a mapearlos al gráfico.

                     draw_S_R_Level(resLine,high,colorRes,5);
                     draw_S_R_Level_Point(resline_prefix,high,time,218,-1,colorRes,90);
                     draw_S_R_Level_Point(resline_prefix,high,time_j,218,-1,colorRes,90);

                     stop_processing = true; // Set the flag to stop processing
                     break;

Utilizamos dos funciones para el trabajo. La primera función draw_S_R_Level toma el nombre de la línea a dibujar, el precio, el color y el ancho de la línea.

void draw_S_R_Level(string levelName,double price,color clr,int width){
   if (ObjectFind(0,levelName) < 0){
      ObjectCreate(0,levelName,OBJ_HLINE,0,TimeCurrent(),price);
      ObjectSetInteger(0,levelName,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,levelName,OBJPROP_WIDTH,width);
   }
   else {
      ObjectSetDouble(0,levelName,OBJPROP_PRICE,price);
   }
   ChartRedraw(0);
}

La función es un tipo de datos nulo, lo que significa que no tiene que devolver nada. Luego utilizamos una declaración condicional para verificar si el objeto existe mediante el uso de la función ObjectFind, que devuelve un entero negativo en caso de que no se encuentre el objeto. Si es el caso, procedemos a crear el objeto identificado como OBJ_HLINE, a la hora actual y al precio especificado, ya que sólo requiere una única coordenada. Luego establecemos su color y ancho. Si se encuentra el objeto, simplemente actualizamos su precio al precio especificado y redibujamos el gráfico para que se apliquen los cambios actuales. Esta función solo dibuja una línea simple en el gráfico. Esto es lo que obtenemos.

LÍNEA DE RESISTENCIA PLANA

La segunda función draw_S_R_Level_Point toma el nombre de la línea a dibujar, el precio, la hora, el código de la flecha, la dirección, el color y el ángulo de la etiqueta de descripción. Esta función dibuja los puntos de nivel para que estén más definidos en la línea de resistencia que se ha dibujado.
void draw_S_R_Level_Point(string objName,double price,datetime time,
      int arrowcode,int direction,color clr,double angle){
   //objName = " ";
   StringConcatenate(objName,objName," @ \nTime: ",time,"\nPrice: ",DoubleToString(price,_Digits));
   if (ObjectCreate(0,objName,OBJ_ARROW,0,time,price)) {
      ObjectSetInteger(0,objName,OBJPROP_ARROWCODE,arrowcode);
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,objName,OBJPROP_FONTSIZE,10);
      if (direction > 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_TOP);
      if (direction < 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
   }
   string prefix = resline_prefix;
   string txt = "\n"+prefix+"("+DoubleToString(price,_Digits)+")";
   string objNameDescription = objName + txt;
   if (ObjectCreate(0,objNameDescription,OBJ_TEXT,0,time,price)) {
     // ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "" + txt);
      ObjectSetInteger(0,objNameDescription,OBJPROP_COLOR,clr);
      ObjectSetDouble(0,objNameDescription,OBJPROP_ANGLE, angle);
      ObjectSetInteger(0,objNameDescription,OBJPROP_FONTSIZE,10);
      if (direction > 0) {
         ObjectSetInteger(0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_LEFT);
         ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "    " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
         ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "    " + txt);
      }
   }
   ChartRedraw(0);
}

La función personalizada "draw_S_R_Level_Point" toma siete parámetros para facilitar su reutilización. Las funciones de los parámetros son las siguientes:

  • objName: Cadena que representa el nombre del objeto gráfico que se va a crear.
  • price: Un valor doble que representa la coordenada de precio donde debe colocarse el objeto.
  • time: Un valor datetime que indica la coordenada horaria en la que debe colocarse el objeto.
  • arrowCode: Un entero que especifica el código de flecha para el objeto flecha.
  • direction: Un número entero que indica la dirección (arriba o abajo) para posicionar la etiqueta de texto.
  • clr: Un valor de color (por ejemplo, clrAzul, clrRojo) para los objetos gráficos.
  • angle: Ángulo de orientación de la etiqueta de descripción.

La función primero concatena el nombre del objeto con hora y precio para la distinción de los puntos de nivel. Esto garantiza que cuando pasemos el cursor sobre la etiqueta, aparezca una ventana emergente con la descripción con la hora y el precio únicos y específicos de la coordenada.

Luego, la función verifica si ya existe un objeto con el objName especificado en el gráfico. En caso contrario, procede a crear los objetos. La creación del objeto se consigue mediante el uso de la función incorporada «ObjectCreate», que requiere la especificación del objeto a dibujar, en este caso, el objeto flecha identificado como «OBJ_ARROW», así como la hora y el precio, que forman las ordenadas del punto de creación del objeto. Después, establecemos las propiedades del objeto: código de flecha, color, tamaño de fuente y punto de anclaje. Para el código de la flecha, MQL5 tiene algunos caracteres ya predefinidos de la fuente Wingdings que se pueden utilizar directamente. He aquí una tabla en la que se especifican los caracteres:

WINGDINGS

Hasta este punto, solo dibujamos la flecha especificada en el gráfico de la siguiente manera:

RESISTENCIA + FLECHA

Podemos ver que logramos dibujar los puntos de resistencia con el código de flecha especificado, en este caso usamos el código de flecha 218, pero no hay descripción de ellos. Por lo tanto, para agregar la descripción respectiva, procedemos a concatenar la flecha con un texto. Creamos otro objeto de texto especificado como "OBJ_TEXT" y también establecemos sus respectivas propiedades. La etiqueta de texto sirve como una anotación descriptiva asociada con los puntos de resistencia, al proporcionar contexto o información adicional sobre los puntos de resistencia, haciéndolos más informativos para los comerciantes y analistas. Elegimos que el valor del texto sea un precio específico, lo que significa que es un punto de resistencia.

Luego se crea la variable "objNameDescription" concatenando el "objName" original con el texto descriptivo. Este nombre combinado garantiza que la flecha y su etiqueta de texto asociada estén vinculadas entre sí. Este fragmento de código específico se utiliza para lograrlo.

   string prefix = resline_prefix;
   string txt = "\n"+prefix+"("+DoubleToString(price,_Digits)+")";
   string objNameDescription = objName + txt;
   if (ObjectCreate(0,objNameDescription,OBJ_TEXT,0,time,price)) {
     // ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "" + txt);
      ObjectSetInteger(0,objNameDescription,OBJPROP_COLOR,clr);
      ObjectSetDouble(0,objNameDescription,OBJPROP_ANGLE, angle);
      ObjectSetInteger(0,objNameDescription,OBJPROP_FONTSIZE,10);
      if (direction > 0) {
         ObjectSetInteger(0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_LEFT);
         ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "    " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
         ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "    " + txt);
      }
   }

Esto es lo que obtenemos como resultado de la concatenación de puntos de resistencia con sus descripciones.

RESISTENCIA + FLECHA + DESCRIPCION

Al mismo tiempo, para mapear los niveles de soporte, se aplica la misma lógica pero con condiciones inversas.

         else if (is_support){
            //Print("SUPPORT AT BAR ",i," (",low,") & ",j," (",low_j,"), Pts = ",low_diff);
            
            for (int k=0; k<ArraySize(pricesLowest); k++){
               if (pricesLowest[k]==low){
                  matchFound_low1 = true;
                  //Print("> SUP L1(",low,") FOUND @ ",k," (",pricesLowest[k],")");
               }
               if (pricesLowest[k]==low_j){
                  matchFound_low2 = true;
                  //Print("> SUP L2(",low_j,") FOUND @ ",k," (",pricesLowest[k],")");
               }
               if (matchFound_low1 && matchFound_low2){
                  if (supportLevels[0]==low || supportLevels[1]==low_j){
                     Print("CONFIRMED BUT This is the same support level, skip updating!");
                     stop_processing = true; // Set the flag to stop processing
                     break; // stop the inner loop prematurely
                  }
                  else {
                     Print(" ++++++++++ SUPPORT LEVELS CONFIRMED @ BARS ",i,
                     "(",low,") & ",j,"(",low_j,")");
                     supportLevels[0] = low;
                     supportLevels[1] = low_j;
                     ArrayPrint(supportLevels);
                     
                     draw_S_R_Level(supLine,low,colorSup,5);
                     draw_S_R_Level_Point(supline_prefix,low,time,217,1,colorSup,-90);
                     draw_S_R_Level_Point(supline_prefix,low,time_j,217,1,colorSup,-90);

                     stop_processing = true; // Set the flag to stop processing
                     break;
                  }
               }
            }
         }

El resultado final que obtenemos debido a la identificación de niveles y su respectivo mapeo en el hito del gráfico es el que se muestra a continuación.

NIVELES DE SOPORTE Y RESISTENCIA

Luego procedemos a monitorear los niveles y si los niveles quedan fuera de la proximidad de las barras visibles, consideramos que el nivel de resistencia es inválido y lo eliminamos. Para lograr esto, necesitaremos encontrar las líneas a nivel de objeto y, una vez encontradas, verificaremos las condiciones para su validez.

   if (ObjectFind(0,resLine) >= 0){
      double objPrice = ObjectGetDouble(0,resLine,OBJPROP_PRICE);
      double visibleHighs[];
      ArraySetAsSeries(visibleHighs,true);
      CopyHigh(_Symbol,_Period,1,visible_bars,visibleHighs);
      //Print("Object Found & visible bars is: ",ArraySize(visibleHighs));
      //ArrayPrint(visibleHighs);
      bool matchHighFound = false;

      ...
   }

Aquí, verificamos si se encuentra el objeto de la línea de resistencia y, de ser así, obtenemos su precio. Copiamos nuevamente los máximos de las barras visibles en el gráfico y los almacenamos en la variable de matriz doble visibleHighs.

Después, recorremos los precios altos e intentamos encontrar si hay una coincidencia entre el precio de la barra actualmente seleccionada y el precio de la línea de resistencia. Si hay una coincidencia, establecemos el indicador matchHighFound en verdadero y finalizamos el bucle.

      for (int i=0; i<ArraySize(visibleHighs); i++){
         if (visibleHighs[i] == objPrice){
            Print("> Match price for resistance found at bar # ",i+1," (",objPrice,")");
            matchHighFound = true;
            break;
         }
      }

Si no hay coincidencia significa que el nivel de resistencia está fuera de proximidad. Informamos de la instancia y utilizamos una función personalizada para eliminar el objeto.

      if (!matchHighFound){
         Print("(",objPrice,") > Match price for the resistance line not found. Delete!");
         deleteLevel(resLine);
      }

La función personalizada deleteLevel toma solo un argumento, el nombre del nivel que se eliminará, y utiliza la función ObjectDelete para eliminar el objeto definido.

void deleteLevel(string levelName){
   ObjectDelete(0,levelName);
   ChartRedraw(0);
}

La misma lógica se aplica a la línea de nivel de soporte, pero prevalecen condiciones inversas.

   if (ObjectFind(0,supLine) >= 0){
      double objPrice = ObjectGetDouble(0,supLine,OBJPROP_PRICE);
      double visibleLows[];
      ArraySetAsSeries(visibleLows,true);
      CopyLow(_Symbol,_Period,1,visible_bars,visibleLows);
      //Print("Object Found & visible bars is: ",ArraySize(visibleLows));
      //ArrayPrint(visibleLows);
      bool matchLowFound = false;
      
      for (int i=0; i<ArraySize(visibleLows); i++){
         if (visibleLows[i] == objPrice){
            Print("> Match price for support found at bar # ",i+1," (",objPrice,")");
            matchLowFound = true;
            break;
         }
      }
      if (!matchLowFound){
         Print("(",objPrice,") > Match price for the support line not found. Delete!");
         deleteLevel(supLine);
      }
   }

Finalmente, si hasta este punto, los niveles de resistencia y soporte aún están dentro del gráfico, significa que son válidos, y por lo tanto, podemos continuar creando una lógica que determinará si están rotos y abrir una posición de mercado. Consideremos primero la ruptura del nivel de resistencia.

Dado que el precio podría superar el nivel de resistencia varias veces, lo que genera múltiples generaciones de señales, necesitamos lógica para garantizar que, una vez que rompamos un nivel de resistencia y generemos una señal, no activemos la señal nuevamente cuando lo rompamos después si es el mismo nivel. Para lograr esto, declaramos una variable doble estática que contendrá nuestro precio para la señal, que mantendrá su valor hasta que tengamos otra señal diferente.

   static double ResistancePriceTrade = 0;
   if (ObjectFind(0,resLine) >= 0){
      double ResistancePriceLevel = ObjectGetDouble(0,resLine,OBJPROP_PRICE);
      if (ResistancePriceTrade != ResistancePriceLevel){
      ...

   }

Luego verificamos la existencia de la línea de resistencia y, si existe, obtenemos su precio. Utilizando una declaración condicional, verificamos si la señal no es igual al precio de la línea de resistencia, lo que significa que aún no tenemos una señal generada para ese nivel en particular y que podemos proceder a verificar la señal de ruptura. Para realizar la comprobación, necesitaremos los datos de la barra anterior, así como las cotizaciones actualizadas.

         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
         double open1 = iOpen(_Symbol,_Period,1);
         double high1 = iHigh(_Symbol,_Period,1);
         double low1 = iLow(_Symbol,_Period,1);
         double close1 = iClose(_Symbol,_Period,1);

Luego utilizamos declaraciones condicionales para verificar si el precio supera el nivel de resistencia. En caso afirmativo, informamos de la señal de venta mediante una impresión en el diario. Luego utilizamos nuestro objeto comercial y el operador de punto para obtener acceso al método de entrada de venta y proporcionar los parámetros necesarios. Finalmente, actualizamos el valor de la variable de señal al nivel de resistencia actual para no molestarnos en generar otra señal basada en el mismo nivel de resistencia.

         if (open1 > close1 && open1 < ResistancePriceLevel
            && high1 > ResistancePriceLevel && Bid < ResistancePriceLevel){
            Print("$$$$$$$$$$$$ SELL NOW SIGNAL!");
            obj_Trade.Sell(0.01,_Symbol,Bid,Bid+350*5*_Point,Bid-350*_Point);
            ResistancePriceTrade = ResistancePriceLevel;
         }

La misma lógica se aplica a la lógica de ruptura del soporte, pero prevalecen las condiciones inversas.

   static double SupportPriceTrade = 0;
   if (ObjectFind(0,supLine) >= 0){
      double SupportPriceLevel = ObjectGetDouble(0,supLine,OBJPROP_PRICE);
      if (SupportPriceTrade != SupportPriceLevel){
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
         double open1 = iOpen(_Symbol,_Period,1);
         double high1 = iHigh(_Symbol,_Period,1);
         double low1 = iLow(_Symbol,_Period,1);
         double close1 = iClose(_Symbol,_Period,1);

         if (open1 < close1 && open1 > SupportPriceLevel
            && low1 < SupportPriceLevel && Ask > SupportPriceLevel){
            Print("$$$$$$$$$$$$ BUY NOW SIGNAL!");
            obj_Trade.Buy(0.01,_Symbol,Ask,Ask-350*5*_Point,Ask+350*_Point);
            SupportPriceTrade = SupportPriceLevel;
         }
         
      }
   }

Aquí está la representación del hito.

OPERACIONES COMPLETAS DE SOPORTE Y RESISTENCIA

El siguiente es el código completo que se necesita para crear una estrategia de trading de Forex de soporte y resistencia en MQL5 que identifica los niveles, los mapea en el gráfico y abre posiciones de mercado respectivamente.

//+------------------------------------------------------------------+
//|                                       RESISTANCE AND SUPPORT.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"

#include <Trade/Trade.mqh>
CTrade obj_Trade;

//bool stop_processing = false;

double pricesHighest[], pricesLowest[];

double resistanceLevels[2], supportLevels[2];


#define resLine "RESISTANCE LEVEL"
#define colorRes clrRed
#define resline_prefix "R"

#define supLine "SUPPORT LEVEL"
#define colorSup clrBlue
#define supline_prefix "S"

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
//---
   
   ArraySetAsSeries(pricesHighest,true);
   ArraySetAsSeries(pricesLowest,true);
   // define the size of the arrays
   ArrayResize(pricesHighest,50);
   ArrayResize(pricesLowest,50);
//---
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
//---
   ArrayFree(pricesHighest);
   ArrayFree(pricesLowest);
   
   //ArrayFree(resistanceLevels); // cannot be used for static allocated array
   //ArrayFree(supportLevels); // cannot be used for static allocated array
   
   ArrayRemove(resistanceLevels,0,WHOLE_ARRAY);
   ArrayRemove(supportLevels,0,WHOLE_ARRAY);
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
//---
   
   int currBars = iBars(_Symbol,_Period);
   static int prevBars = currBars;
   if (prevBars == currBars) return;
   prevBars = currBars;
   
   int visible_bars = (int)ChartGetInteger(0,CHART_VISIBLE_BARS);
   bool stop_processing = false; // Flag to control outer loop
   bool matchFound_high1 = false, matchFound_low1 = false;
   bool matchFound_high2 = false, matchFound_low2 = false;
   
   ArrayFree(pricesHighest);
   ArrayFree(pricesLowest);
   
   int copiedBarsHighs = CopyHigh(_Symbol,_Period,1,visible_bars,pricesHighest);
   int copiedBarsLows = CopyLow(_Symbol,_Period,1,visible_bars,pricesLowest);
   
   //ArrayPrint(pricesHighest);
   //ArrayPrint(pricesLowest);
         // sort the array in ascending order
   ArraySort(pricesHighest);
   ArraySort(pricesLowest);
   //ArrayPrint(pricesHighest);
   //ArrayPrint(pricesLowest);
   ArrayRemove(pricesHighest,10,WHOLE_ARRAY);
   ArrayRemove(pricesLowest,0,visible_bars-10);
   //Print("FIRST 10 HIGHEST PRICES:");
   //ArrayPrint(pricesHighest);
   //Print("LAST 10 LOWEST PRICES:");
   //ArrayPrint(pricesLowest);
   
   for (int i=1; i<=visible_bars-1 && !stop_processing; i++){
      //Print(":: BAR NO: ",i);
      double open = iOpen(_Symbol,_Period,i);
      double high = iHigh(_Symbol,_Period,i);
      double low = iLow(_Symbol,_Period,i);
      double close = iClose(_Symbol,_Period,i);
      datetime time = iTime(_Symbol,_Period,i);
      
      int diff_i_j = 10;
      
      for (int j=i+diff_i_j; j<=visible_bars-1; j++){
         //Print("BAR CHECK NO: ",j);
         double open_j = iOpen(_Symbol,_Period,j);
         double high_j = iHigh(_Symbol,_Period,j);
         double low_j = iLow(_Symbol,_Period,j);
         double close_j = iClose(_Symbol,_Period,j);
         datetime time_j = iTime(_Symbol,_Period,j);
         
         // CHECK FOR RESISTANCE
         double high_diff = NormalizeDouble((MathAbs(high-high_j)/_Point),0);
         bool is_resistance = high_diff <= 10;
         
         // CHECK FOR SUPPORT
         double low_diff = NormalizeDouble((MathAbs(low-low_j)/_Point),0);
         bool is_support = low_diff <= 10;
         
         if (is_resistance){
            //Print("RESISTANCE AT BAR ",i," (",high,") & ",j," (",high_j,"), Pts = ",high_diff);
            
            for (int k=0; k<ArraySize(pricesHighest); k++){
               if (pricesHighest[k]==high){
                  matchFound_high1 = true;
                  //Print("> RES H1(",high,") FOUND @ ",k," (",pricesHighest[k],")");
               }
               if (pricesHighest[k]==high_j){
                  matchFound_high2 = true;
                  //Print("> RES H2(",high_j,") FOUND @ ",k," (",pricesHighest[k],")");
               }
               if (matchFound_high1 && matchFound_high2){
                  if (resistanceLevels[0]==high || resistanceLevels[1]==high_j){
                     Print("CONFIRMED BUT This is the same resistance level, skip updating!");
                     stop_processing = true; // Set the flag to stop processing
                     break; // stop the inner loop prematurily
                  }
                  else {
                     Print(" ++++++++++ RESISTANCE LEVELS CONFIRMED @ BARS ",i,
                     "(",high,") & ",j,"(",high_j,")");
                     resistanceLevels[0] = high;
                     resistanceLevels[1] = high_j;
                     ArrayPrint(resistanceLevels);
                     
                     draw_S_R_Level(resLine,high,colorRes,5);
                     draw_S_R_Level_Point(resline_prefix,high,time,218,-1,colorRes,90);
                     draw_S_R_Level_Point(resline_prefix,high,time_j,218,-1,colorRes,90);

                     stop_processing = true; // Set the flag to stop processing
                     break;
                  }
               }
            }
         }
         
         else if (is_support){
            //Print("SUPPORT AT BAR ",i," (",low,") & ",j," (",low_j,"), Pts = ",low_diff);
            
            for (int k=0; k<ArraySize(pricesLowest); k++){
               if (pricesLowest[k]==low){
                  matchFound_low1 = true;
                  //Print("> SUP L1(",low,") FOUND @ ",k," (",pricesLowest[k],")");
               }
               if (pricesLowest[k]==low_j){
                  matchFound_low2 = true;
                  //Print("> SUP L2(",low_j,") FOUND @ ",k," (",pricesLowest[k],")");
               }
               if (matchFound_low1 && matchFound_low2){
                  if (supportLevels[0]==low || supportLevels[1]==low_j){
                     Print("CONFIRMED BUT This is the same support level, skip updating!");
                     stop_processing = true; // Set the flag to stop processing
                     break; // stop the inner loop prematurely
                  }
                  else {
                     Print(" ++++++++++ SUPPORT LEVELS CONFIRMED @ BARS ",i,
                     "(",low,") & ",j,"(",low_j,")");
                     supportLevels[0] = low;
                     supportLevels[1] = low_j;
                     ArrayPrint(supportLevels);
                     
                     draw_S_R_Level(supLine,low,colorSup,5);
                     draw_S_R_Level_Point(supline_prefix,low,time,217,1,colorSup,-90);
                     draw_S_R_Level_Point(supline_prefix,low,time_j,217,1,colorSup,-90);

                     stop_processing = true; // Set the flag to stop processing
                     break;
                  }
               }
            }
         }
         
         
         
         if (stop_processing){break;}
      }
      if (stop_processing){break;}
   }
   
   if (ObjectFind(0,resLine) >= 0){
      double objPrice = ObjectGetDouble(0,resLine,OBJPROP_PRICE);
      double visibleHighs[];
      ArraySetAsSeries(visibleHighs,true);
      CopyHigh(_Symbol,_Period,1,visible_bars,visibleHighs);
      //Print("Object Found & visible bars is: ",ArraySize(visibleHighs));
      //ArrayPrint(visibleHighs);
      bool matchHighFound = false;
      
      for (int i=0; i<ArraySize(visibleHighs); i++){
         if (visibleHighs[i] == objPrice){
            Print("> Match price for resistance found at bar # ",i+1," (",objPrice,")");
            matchHighFound = true;
            break;
         }
      }
      if (!matchHighFound){
         Print("(",objPrice,") > Match price for the resistance line not found. Delete!");
         deleteLevel(resLine);
      }
   }
   
   if (ObjectFind(0,supLine) >= 0){
      double objPrice = ObjectGetDouble(0,supLine,OBJPROP_PRICE);
      double visibleLows[];
      ArraySetAsSeries(visibleLows,true);
      CopyLow(_Symbol,_Period,1,visible_bars,visibleLows);
      //Print("Object Found & visible bars is: ",ArraySize(visibleLows));
      //ArrayPrint(visibleLows);
      bool matchLowFound = false;
      
      for (int i=0; i<ArraySize(visibleLows); i++){
         if (visibleLows[i] == objPrice){
            Print("> Match price for support found at bar # ",i+1," (",objPrice,")");
            matchLowFound = true;
            break;
         }
      }
      if (!matchLowFound){
         Print("(",objPrice,") > Match price for the support line not found. Delete!");
         deleteLevel(supLine);
      }
   }
   
   static double ResistancePriceTrade = 0;
   if (ObjectFind(0,resLine) >= 0){
      double ResistancePriceLevel = ObjectGetDouble(0,resLine,OBJPROP_PRICE);
      if (ResistancePriceTrade != ResistancePriceLevel){
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
         double open1 = iOpen(_Symbol,_Period,1);
         double high1 = iHigh(_Symbol,_Period,1);
         double low1 = iLow(_Symbol,_Period,1);
         double close1 = iClose(_Symbol,_Period,1);

         if (open1 > close1 && open1 < ResistancePriceLevel
            && high1 > ResistancePriceLevel && Bid < ResistancePriceLevel){
            Print("$$$$$$$$$$$$ SELL NOW SIGNAL!");
            obj_Trade.Sell(0.01,_Symbol,Bid,Bid+350*5*_Point,Bid-350*_Point);
            ResistancePriceTrade = ResistancePriceLevel;
         }
         
      }
   }
   
   static double SupportPriceTrade = 0;
   if (ObjectFind(0,supLine) >= 0){
      double SupportPriceLevel = ObjectGetDouble(0,supLine,OBJPROP_PRICE);
      if (SupportPriceTrade != SupportPriceLevel){
         double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);
         double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits);
         double open1 = iOpen(_Symbol,_Period,1);
         double high1 = iHigh(_Symbol,_Period,1);
         double low1 = iLow(_Symbol,_Period,1);
         double close1 = iClose(_Symbol,_Period,1);

         if (open1 < close1 && open1 > SupportPriceLevel
            && low1 < SupportPriceLevel && Ask > SupportPriceLevel){
            Print("$$$$$$$$$$$$ BUY NOW SIGNAL!");
            obj_Trade.Buy(0.01,_Symbol,Ask,Ask-350*5*_Point,Ask+350*_Point);
            SupportPriceTrade = SupportPriceLevel;
         }
         
      }
   }
   
}
//+------------------------------------------------------------------+

void draw_S_R_Level(string levelName,double price,color clr,int width){
   if (ObjectFind(0,levelName) < 0){
      ObjectCreate(0,levelName,OBJ_HLINE,0,TimeCurrent(),price);
      ObjectSetInteger(0,levelName,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,levelName,OBJPROP_WIDTH,width);
   }
   else {
      ObjectSetDouble(0,levelName,OBJPROP_PRICE,price);
   }
   ChartRedraw(0);
}

void deleteLevel(string levelName){
   ObjectDelete(0,levelName);
   ChartRedraw(0);
}

void draw_S_R_Level_Point(string objName,double price,datetime time,
      int arrowcode,int direction,color clr,double angle){
   //objName = " ";
   StringConcatenate(objName,objName," @ \nTime: ",time,"\nPrice: ",DoubleToString(price,_Digits));
   if (ObjectCreate(0,objName,OBJ_ARROW,0,time,price)) {
      ObjectSetInteger(0,objName,OBJPROP_ARROWCODE,arrowcode);
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,objName,OBJPROP_FONTSIZE,10);
      if (direction > 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_TOP);
      if (direction < 0) ObjectSetInteger(0,objName,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
   }
   string prefix = resline_prefix;
   string txt = "\n"+prefix+"("+DoubleToString(price,_Digits)+")";
   string objNameDescription = objName + txt;
   if (ObjectCreate(0,objNameDescription,OBJ_TEXT,0,time,price)) {
     // ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "" + txt);
      ObjectSetInteger(0,objNameDescription,OBJPROP_COLOR,clr);
      ObjectSetDouble(0,objNameDescription,OBJPROP_ANGLE, angle);
      ObjectSetInteger(0,objNameDescription,OBJPROP_FONTSIZE,10);
      if (direction > 0) {
         ObjectSetInteger(0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_LEFT);
         ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "    " + txt);
      }
      if (direction < 0) {
         ObjectSetInteger(0,objNameDescription,OBJPROP_ANCHOR,ANCHOR_BOTTOM);
         ObjectSetString(0,objNameDescription,OBJPROP_TEXT, "    " + txt);
      }
   }
   ChartRedraw(0);
}

¡Brindemos por nosotros! Ahora creamos un sistema de comercio de acción de precio puro basado en la estrategia de comercio de divisas de Soporte y Resistencia no sólo para generar señales de comercio, sino también para abrir posiciones de mercado basadas en las señales generadas.


Resultados del Probador de estrategias

Tras probarlo en el probador de estrategias, he aquí los resultados.

  • Gráfico Balance/Equidad:

GRÁFICO

  • Resultados de las pruebas retrospectivas:

RESULTADOS


Conclusión

En conclusión, la automatización de la estrategia de trading de Forex de Soporte y Resistencia es posible y fácil, como hemos visto. Todo lo que se necesita es tener una comprensión clara de la estrategia, así como su proyecto, y luego utilizar el conocimiento para un gran avance. Aprovechamos con confianza las potentes funciones del lenguaje MQL5 para elaborar una estrategia de negociación precisa y eficaz. El análisis y la creación de EA han demostrado que la automatización no sólo ahorra un tiempo valioso, sino que también aumenta la eficacia de las operaciones al reducir los errores humanos y las interferencias emocionales.

Descargo de responsabilidad: La información ilustrada en este artículo sólo tiene fines educativos. Sólo pretende mostrar ideas sobre cómo crear un Asesor Experto (EA) de Soporte y Resistencia basado en un enfoque de precio puro y por lo tanto debe ser utilizado como base para la creación de un mejor asesor experto con más optimización y extracción de datos tenidos en cuenta. La información presentada no garantiza ningún resultado comercial.

Esperamos sinceramente que el artículo haya sido instructivo y útil para usted en la automatización de un EA de Soportes y Resistencias. Este tipo de integración de sistemas automatizados aumentará sin duda su frecuencia a medida que los mercados financieros sigan desarrollándose, proporcionando a los operadores instrumentos de vanguardia para manejar todos los aspectos de la dinámica del mercado. Con tecnologías como MQL5, que siguen avanzando y abriendo la puerta a soluciones de trading más complejas e inteligentes, el futuro del trading parece brillante.


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

Creación de un modelo de restricción de tendencia de velas (Parte 5): Sistema de notificaciones (Parte II) Creación de un modelo de restricción de tendencia de velas (Parte 5): Sistema de notificaciones (Parte II)
Hoy discutiremos sobre la integración funcional de Telegram para las notificaciones de indicadores de MetaTrader 5 utilizando el poder de MQL5, en asociación con Python y la API Telegram Bot. Lo explicaremos todo con detalle para que nadie se pierda ningún punto. Al finalizar este proyecto, habrá adquirido conocimientos valiosos para aplicar en sus proyectos.
Redes neuronales: así de sencillo (Parte 86): Transformador en U Redes neuronales: así de sencillo (Parte 86): Transformador en U
Continuamos nuestro repaso a los algoritmos de previsión de series temporales. En este artículo nos familiarizaremos con los métodos del Transformador en U.
Desarrollamos un asesor experto multidivisa (Parte 9): Recopilamos los resultados de optimización de las instancias individuales de una estrategia comercial Desarrollamos un asesor experto multidivisa (Parte 9): Recopilamos los resultados de optimización de las instancias individuales de una estrategia comercial
Hoy vamos a esbozar los principales pasos para desarrollar nuestro EA. Uno de los primeros será realizar una optimización en una sola instancia de la estrategia comercial desarrollada. Así, intentaremos reunir en un solo lugar toda la información necesaria sobre las pasadas del simulador durante la optimización.
Creación de un modelo de restricción de tendencia de velas (Parte 5): Sistema de notificaciones (Parte I) Creación de un modelo de restricción de tendencia de velas (Parte 5): Sistema de notificaciones (Parte I)
Desglosaremos el código principal de MQL5 en fragmentos de código especificados para ilustrar la integración de Telegram y WhatsApp para recibir notificaciones de señales del indicador Trend Constraint que estamos creando en esta serie de artículos. Esto ayudará a los traders, tanto novatos como experimentados, a comprender el concepto con facilidad. En primer lugar, vamos a cubrir la configuración de MetaTrader 5 para las notificaciones y su importancia para el usuario. Esto ayudará a los desarrolladores a tomar notas para aplicarlas en sus sistemas.