preview
Desarrollo de Sistemas Avanzados de Trading ICT: Implementación de Order Blocks en un Indicador

Desarrollo de Sistemas Avanzados de Trading ICT: Implementación de Order Blocks en un Indicador

MetaTrader 5Ejemplos | 30 octubre 2024, 11:02
373 0
Niquel Mendoza
Niquel Mendoza


1.0: Introducción 

Bienvenido, y gracias por leer nuestro artículo, en este artículo aprenderás a diseñar un indicador con base en la teoría de order blocks de Smart Money Concepts e Inner Circle Trader.  

1.1: Fundamentos de la Creación de Order Blocks 

Los Order Blocks, como su nombre lo indica, son zonas en el gráfico donde hay órdenes pendientes de ser activadas.

Esto sucede normalmente cuando un gran participante del mercado, como una institución financiera, quiere entrar con una posición significativa, pero no hay suficiente liquidez para ejecutar su orden completa sin mover el mercado. Según la ley básica de la oferta y la demanda, al ejecutar una parte de su orden, el precio sube (en el caso de compras) de manera agresiva en busca de vendedores interesados en ofrecer liquidez para completar la transacción.

Dado que el gran participante no puede ejecutar toda su orden de una sola vez sin provocar un movimiento considerable en el precio, distribuye sus órdenes en partes más pequeñas. De este modo, puede completar su operación sin que el precio se mueva drásticamente antes de que haya terminado de posicionarse.

A partir de este concepto, podemos identificar estas zonas en el gráfico, ya que son áreas donde ha ocurrido un fuerte desequilibrio entre la oferta y la demanda (ya sea de compra o venta). A continuación, veremos tres formas de identificar estas zonas y cómo implementarlas en código.

1.1.1: Lógica Basada en Acción del Precio (Nivel Básico) 


Antes de iniciar a explicar la lógica de los order blocks, explicaré las partes de una vela, ya que esto no será muy importante.

Una vela se compone de cuatro precios:                    

Precio Descripción
  Alto precio máximo que alcanzo el precio en un periodo que sería una vela
  Bajo precio mínimo que alcanzo el precio en una vela
  Apertura precio donde abrirá la vela
  Cierre precio en el cual cierra una vela.   

Veamos un ejemplo en el gráfico, para que lo entienda mejor.

          OHCL

Para comenzar, si analizamos la teoría del Order Block, el primer aspecto importante a identificar un desequilibrio en el mercado. Este desequilibrio puede observarse en el gráfico como una secuencia de varias velas consecutivas en la misma dirección, lo cual indica una tendencia clara.

Ejemplo: Tendencia Alcista con 4 velas Consecutivas

En este caso, vamos a centrarnos en una tendencia alcista formada por 4 velas consecutivas, que cumplen las siguientes reglas:      

Vela Descripción
Vela Anterior Esta vela precede al movimiento alcista de las 4 velas consecutivas. Generalmente, es una vela que cierra por debajo del nivel de inicio del movimiento alcista.
Primera Vela Marca el inicio del movimiento alcista. Su cierre está por encima de la apertura de la Vela Anterior.
Segunda,   Tercera y Cuarta   Vela
Son las velas que continúan el impulso alcista, cada una cerrando por encima del cierre de la vela anterior.

Reglas:
  • Condición del Movimiento Alcista: Para que se considere un movimiento válido, las 4 velas deben ser alcistas consecutivas. La primera vela inicia el desequilibrio, y las siguientes lo confirman.
  • Identificación del Order Block: El Order Block se formará en la zona de la vela anterior y la primera vela alcista, marcando el área donde los compradores tomaron el control.

A continuación, podemos observar un ejemplo gráfico en el que se muestran 4 velas alcistas consecutivas, indicando un claro desequilibrio en el precio.

Simple OB Example

Reglas para la identificación de los order blocks por velas consecutivas:

Aspecto Order Block Alcista Order Block Bajista
Condición de las Velas Velas 1, 2, 3 y 4 alcistas consecutivas. Cierre por encima de la apertura. Velas 1, 2, 3 y 4 bajistas consecutivas. Cierre por debajo de la apertura.
Validación del Extremo de la Vela 2 Mínimo de la vela 2 por encima de la mitad del cuerpo de la vela 1.
 (Excepción: Martillo)
Alto de la vela 2 por debajo de la mitad del cuerpo de la vela 1.
 (Excepción: Martillo)
Condición del Cuerpo de la Vela 2 Al menos el 40% del cuerpo de la vela 2 supera el alto de la vela 1. Al menos el 40% del cuerpo de la vela 2 está por debajo del mínimo de la vela 1.
Validación del Extremo de la Vela 3  Mínimo de la vela 3 por encima del 25% del cuerpo de la vela 2. Alto de la vela 3 por debajo del 25% del cuerpo de la vela 2.
Condición del Cuerpo de la Vela 3 La mitad del cuerpo de la vela 3 supera el alto de la vela 2. La mitad del cuerpo de la vela 3 está por debajo del mínimo de la vela 2

Objetivo de las Reglas:

Estas reglas están diseñadas para asegurar que el patrón de 4 velas sea lo suficientemente fuerte para validar un Order Block y para confirmar que las órdenes pendientes en la zona no han sido ejecutadas previamente.

1.1.2: Lógica con Acción del Precio e Indicadores (Nivel Intermedio) 

En este enfoque más avanzado, no solo consideramos la acción del precio, sino que también introducimos el uso de indicadores para validar la fuerza del movimiento, en este caso, utilizando el volumen.

Fundamentos de la Estrategia

Como mencionamos anteriormente, un movimiento significativo suele comenzar con un volumen relativamente bajo, seguido de un incremento en el volumen cuando se ejecutan las órdenes importantes. Este aumento en el volumen suele extenderse a lo largo de varias velas (2 o 3), validando el inicio de un Order Block.

Podemos abordar esta lógica en dos casos principales:

Caso 1: Order Block con Incremento de Volumen

En este escenario, el Order Block se forma en el momento en que el volumen comienza a incrementarse notablemente. Las condiciones son las siguientes:

  1. Inicio del Movimiento: Comienza con una vela de bajo volumen que indica el inicio de la acumulación.
  2. Incremento en el Volumen: En la vela siguiente, el volumen se incrementa significativamente, indicando la ejecución de órdenes. Este incremento puede extenderse durante 2 o 3 velas consecutivas.
  3. Confirmación del Order Block: El Order Block se forma en la zona donde el volumen comienza a aumentar y las órdenes pendientes han sido ejecutadas.

Ejemplo alcista: 

 Bullish Increasing Volume

Ejemplo bajista:         

Bearish Increasing Volume

Caso 2: Order Block con Pico de Volumen Simple

En este caso, el Order Block se forma cuando se observa un pico significativo de volumen en una vela clave, que llamaremos 1.ª vela. Las condiciones que validan el Order Block se basan tanto en el comportamiento del precio como en el análisis del volumen, y el patrón se desarrolla con 3 velas consecutivas, alcistas o bajistas.

Reglas:

Aspecto Order Block Alcista Order Block Bajista
Pico de Volumen en la Vela 1
La vela 1 debe concentrar el mayor volumen de las tres velas y su volumen debe ser superior al de la vela anterior y al de la vela 2.
La vela 1 debe concentrar el mayor volumen de las tres velas y su volumen debe ser superior al de la vela anterior y al de la vela 2.
Validación del mínimo de la Vela 2
El mínimo de la vela 2 debe ser mayor a la mitad del cuerpo de la vela 1, lo que indica que no se ha alcanzado la zona de acumulación del Order Block.
(Excepción: vela 1 es un martillo)
El máximo de la vela 2 debe ser menor a la mitad del cuerpo de la vela 1, lo que indica que no se ha alcanzado la zona de acumulación del Order Block.
(Excepción: vela 1 es un martillo invertido)
Condición del 60% del Cuerpo de la Vela 2 El 60% del cuerpo de la vela 2 debe superar el alto de la vela 1, lo que muestra una continuación del impulso alcista. El 60% del cuerpo de la vela 2 debe ser menor al bajo de la vela 1, lo que muestra una continuación del impulso bajista.
Validación del alto de la Vela 3 El alto de la vela 3 debe estar por encima del precio de apertura de la vela 2, confirmando que el movimiento alcista se mantiene fuerte. El bajo de la vela 3 debe estar por debajo del precio de apertura de la vela 2, confirmando que el movimiento bajista se mantiene fuerte.

Ejemplo alcista: 

Bullish Volume Peak

 Ejemplo bajista:

Bearish Volume Peak



2.0: Desarrollo del Indicador de Order Blocks

2.1: Configuración de Inputs y Parámetros del Indicador


Por fin, luego de mucha teoría pasaremos a la parte que seguro, muchos de ustedes estarían esperando, programar todo lo aprendido. 

1. Van a crear un nuevo programa del tipo "Indicador Personalizado":

2. Escribimos el nombre del Indicador y el autor.

3. Seleccionamos "OnCalculate()" para los cálculos que haremos más adelante.

 4. Hacer "Clic" izquierdo en finalizar.

Por ahora no configuraremos:

indicator_buffers 
indicator_plots 

Entonces para saltarnos el error:"no indicator plot defined for indicator00".

Lo que haremos es que pondremos en la parte superior lo siguiente:

#property  indicator_buffers 1
#property  indicator_plots 1

Con esto se nos debería quitaría la advertencia.

Empezaremos configurando los inputs de la siguiente manera:

  • Colores de los Order Blocks Alcistas y Bajistas:

    Permitirá al usuario seleccionar los colores que representarán los Order Blocks, alcistas y bajistas en el gráfico, facilitando la identificación visual de cada uno.

  • Personalización de los Rectángulos:

    Se añadirán opciones para ajustar las propiedades de los rectángulos que marcan los Order Blocks:

    • Ancho del Borde: Define el grosor de los bordes del rectángulo.
    • Fondo: Configura si el rectángulo aparece detrás de las velas o superpuesto.
    • Seleccionable: Permite activar o desactivar la opción de seleccionar los rectángulos en el gráfico, facilitando la interacción.
  • Rango de Búsqueda para Order Blocks:

    Un parámetro que establece el rango de barras hacia atrás desde la vela actual en el que se buscarán los Order Blocks. Esto puede ajustarse según la estrategia o el marco temporal que se esté utilizando.

  • Definición de los Inputs y su Organización:

    Los inputs son parámetros de entrada que el usuario puede modificar fuera del programa, lo que proporciona flexibilidad al ajustar el comportamiento del indicador según sus necesidades. Sin embargo, para mejorar la claridad y comprensión de estos parámetros, utilizamos el concepto de:     

    sinput

    El uso de  la palabra clave mencionada, nos permite organizar mejor los parámetros mediante la agrupación de los mismos en categorías, utilizando la propiedad:

    group
  • Esto no solo mejora la estructura del código, sino que también facilita al usuario la identificación de los distintos grupos de parámetros relacionados con aspectos específicos del indicador, como la personalización de los rectángulos o  el análisis de Order Blocks.

    sinput group "--- Order Block Indicator settings ---"
    input          int  Rango_universal_busqueda = 500; // Este es el rango universal para la búsqueda de order blocks
    input          int  Witdth_order_block = 1; //el ancho de las líneas del order block
    
    input          bool Back_order_block = true; // activamos objeto en el fondo
    input          bool Fill_order_block = true; // activamos el fondo del order block
    
    input          color Color_Order_Block_Bajista = clrRed; // para el order block bajista asignamos un color rojo
    input          color Color_Order_Block_Alcista = clrGreen; // para el order block alcista asignamos un color verde

    2.2: Creación de Estructuras y Funciones Clave

    En este apartado, definiremos las estructuras y funciones fundamentales para gestionar los Order Blocks en nuestro indicador. Esto nos permitirá almacenar y organizar la información clave de cada Order Block, así como manejar los datos de forma eficiente mediante arrays dinámicos.

    1. Variable para Almacenar el Tiempo de la Última Vela

    Primero, crearemos una variable que almacenará el tiempo de la última vela procesada. Esta será esencial para evitar duplicaciones de Order Blocks en las mismas velas y garantizar un seguimiento correcto a lo largo del tiempo.

    datetime tiempo_ultima_vela;
    

    2. Manejador para el Indicador ATR:

    El segundo paso será crear un handler para el indicador:

    ATR (Average True Range)
    

    Que nos ayudará a medir la volatilidad del mercado y complementar la lógica del indicador. Este manejador se inicializa al comienzo para que pueda ser utilizado en el cálculo de los Order Blocks.

    int atr_i;
    

    3. Creación de la Estructura para Almacenar los Datos del Order Block

    Ahora crearemos una estructura que almacenará los datos relevantes de cada Order Block. Esta estructura es crucial, ya que contendrá información sobre el tiempo, los precios clave, el nombre del bloque, y si ha sido mitigado o no. Además, crearemos un array dinámico que almacenará todos los Order Blocks detectados en el gráfico.

    struct OrderBlock
    {
       datetime time1;      // Tiempo de la vela anterior a la primera vela del order block
       double price1;       // Precio superior del order block (nivel 1)
       double price2;       // Precio inferior del order block (nivel 2)
       string name;         // Nombre del order block
       bool mitigated;      // Estado del order block (true si ha sido mitigado, false si no)
    };

    Descripción de los Campos de la Estructura

    La estructura OrderBlock se compone de los siguientes campos:

    • time1: Este campo almacenará el tiempo de la vela anterior a la primera vela que inicia el Order Block. Es útil para saber cuándo se formó el bloque y para realizar comparaciones temporales.

    • price1: Representa el nivel de precio más alto o el primer precio clave del Order Block. Será el nivel superior en el caso de un Order Block alcista.

    • price2: Este campo contendrá el nivel de precio más bajo o el segundo precio clave del Order Block. Es el nivel inferior en el caso de un Order Block alcista.

    • name: Almacenará un nombre único para identificar el Order Block en el gráfico. Este nombre se utilizará para etiquetar el bloque de manera clara y reconocerlo visualmente.

    • mitigated: Indica si el Order Block ha sido mitigado o no. Si ha sido mitigado (es decir, si el precio ha tocado o superado los niveles del bloque), este valor será true de lo contrario, será false.

    4. Array Dinámico para Almacenar Order Blocks

    Finalmente, crearemos un arrays dinámicos  que contendrá todos los Order Blocks identificados. Estos arrays nos permitirá almacenar múltiples bloques en memoria y gestionarlos dinámicamente, activando o desactivando order blocks según sea necesario a lo largo del tiempo.

    OrderBlocks ob_bajistas[];
    OrderBlocks ob_alcistas[];

    Función OnInit():

    La función OnInit() se encarga de inicializar todos los elementos del indicador y realizar comprobaciones necesarias para asegurar que todo esté en orden antes de que el indicador comience a funcionar. A continuación, explicamos paso a paso lo que ocurre en el código.

    1. Inicialización de Variables

    Al comienzo, se establece el valor inicial de la variable:

    "tiempo_ultima_vela"

    A 0. Esta variable es importante porque almacenará el tiempo de la última vela procesada, permitiendo evitar duplicaciones y gestionar el flujo del indicador correctamente.

    tiempo_ultima_vela = 0;

    Luego, se inicializa el handler del indicador:

    "ATR" (Average True Range)

    Con un periodo de 14 velas. El ATR será utilizado más adelante para medir la volatilidad del mercado y contribuir a la lógica de los Order Blocks.

    atr_i = iATR(_Symbol, PERIOD_CURRENT, 14);
    

    2. Comprobación de Parámetros de Entrada

    Después de la inicialización, el código verifica si el valor de la variable:

    Rango_universal_busqueda

    Es menor a 40. Esta variable define el rango en el que el indicador buscará Order Blocks. Si este rango es demasiado pequeño, podría afectar la precisión y eficacia del indicador, por lo que se imprime un mensaje de advertencia y el indicador se detiene al devolver el valor INIT_PARAMETERS_INCORRECT.

    if (Rango_universal_busqueda < 40)
    {
       Print("Rango de busqueda muy pequeño");
       return (INIT_PARAMETERS_INCORRECT);
    }

    Esta verificación asegura que el rango de búsqueda tenga un tamaño adecuado para que el indicador funcione correctamente y evita configuraciones incorrectas que podrían afectar su rendimiento.

    3. Comprobación de la Inicialización del Indicador ATR

    El siguiente paso es verificar si el handler del indicador ATR se inicializó correctamente.

    Sí: 

    "atr_i"

    Tiene el valor:

    INVALID_HANDLE

    Significa que hubo un error al intentar crear el indicador, por lo tanto, se imprime un mensaje de error y se devuelve:

    INIT_FAILED

    Lo que detiene la ejecución del indicador.

    if (atr_i == INVALID_HANDLE)
    {
       Print("Error al copiar data para los indicadores");
       return (INIT_FAILED);
    }

    4. Redimensionamiento de Arrays Dinámicos

    A continuación, se redimensionan los Array dinámicos que almacenan los Order Blocks, bajistas y alcistas, estableciéndolos inicialmente con un tamaño de 0. Esto asegura que ambos Arrays estén vacíos al comienzo del programa, listos para almacenar nuevos Order Blocks que se detecten más adelante.

    ArrayResize(ob_bajistas, 0);
    ArrayResize(ob_alcistas, 0);

    El uso de arrays dinámicos es esencial para gestionar la cantidad de Order Blocks detectados, ya que permite que estos arrays crezcan o se reduzcan según sea necesario.

    Finalización Exitosa de la Inicialización

    Si todas las comprobaciones y las inicializaciones se completan sin errores, la función:

    OnInit()

    Retorna:

    INIT_SUCCEEDED

    Lo que indica que el indicador ha sido inicializado correctamente y está listo para comenzar a operar en el gráfico.

    return (INIT_SUCCEEDED);

    El código completo:

    int OnInit()
      {
    //--- indicator buffers mapping
       tiempo_ultima_vela = 0;
       atr_i = iATR(_Symbol,PERIOD_CURRENT,14);
    
       if(Rango_universal_busqueda < 40)
         {
          Print("Rango de busqueda muy pequeño");
          return (INIT_PARAMETERS_INCORRECT);
         }
    
       if(atr_i== INVALID_HANDLE)
         {
          Print("Error al copiar data  para los indicadores");
          return(INIT_FAILED);
         }
    
       ArrayResize(ob_bajistas,0);
       ArrayResize(ob_alcistas,0);
    
    
    
    //---
       return(INIT_SUCCEEDED);
      }

    Ahora lo que haremos será agregar código al evento de  deinicialización  del indicador para liberar memoria:

    void OnDeinit(const int reason)
      {
    //---
      Eliminar_Objetos();
    
      ArrayFree(ob_bajistas);
      ArrayFree(ob_alcistas);
       
      }
    Eliminar_Objetos();

    Será una función que usaremos más adelante para eliminar los rectángulos que crearemos.

    Verificación de Nueva Vela

    El objetivo de este código es optimizar el rendimiento del indicador, asegurando que solo se ejecute cuando se abra una nueva vela, en lugar de cada vez que el precio cambia. Ejecutar el indicador con cada cambio de precio generaría un consumo innecesario de recursos del ordenador, especialmente si estamos analizando múltiples activos o utilizando varios indicadores.

    Para lograr esto, se realiza una verificación del tiempo de la última vela procesada. Si se detecta que ha aparecido una nueva vela, se activa el procesamiento del indicador. A continuación, se explica cada parte del código.

    1. Inicialización 

    Primero, se crea una variable booleana llamada: 

    "new_vela"

    Que actúa como un disparador. Se inicializa en falso, lo que indica que, por defecto, no ha ocurrido la apertura de una nueva vela.

    bool new_vela = false; // Asignamos el gatillo que nos indica si se ha abierto una nueva vela a false

    2. Comprobación de la Nueva Vela

    El siguiente paso es comprobar si el tiempo de la última vela procesada:

    tiempo_ultima_vela

    Es diferente al tiempo de la vela actual en el gráfico. La función:

     iTime()

    Devuelve el tiempo de apertura de una vela específica, en este caso, la vela más reciente (indexada como 0). Si el tiempo no coincide, significa que ha aparecido una nueva vela.

    if(tiempo_ultima_vela != iTime(_Symbol, PERIOD_CURRENT, 0)) // Comprobamos si el tiempo actual es diferente al tiempo almacenado
    {
        new_vela = true; // Si no coincide, activamos el indicador de nueva vela
        tiempo_ultima_vela = iTime(_Symbol, PERIOD_CURRENT, 0); // Actualizamos el tiempo de la última vela procesada
    }

    Este bloque de código hace dos cosas:

    1. Verifica si se ha generado una nueva vela.
    2. Actualiza la variable tiempo_ultima_vela con el tiempo de la nueva vela para futuras comparaciones.

    Ejecución del Código Principal

    Si la variable: 

    new_vela

    Es true, significa que se ha abierto una nueva vela. En este caso, podemos ejecutar el código principal del indicador que procesa los Order Blocks o cualquier otra lógica relevante. Al hacer esta verificación, evitamos ejecutar el código en cada tic de precio, y lo hacemos únicamente cuando una nueva vela se forma en el gráfico.

    if(new_vela == true)
    {
       // Aquí pondremos el código principal del indicador que se ejecutará solo al abrirse una nueva vela
    }

    Creación de Arrays para Almacenar Datos de Velas, Volumen y ATR

    En este bloque de código, se configuran "arrays" para almacenar información clave sobre las velas, el volumen de "ticks"  y el  "ATR". Estos datos son fundamentales para el análisis del comportamiento del precio y la identificación de Order Blocks.

    1. Declaración de los Arrays

    Se declaran arrays de tipo double, datetime y long para almacenar los valores correspondientes:

    double openArray[];// Almacena el precio de apertura (Open) de las velas.
    double closeArray[];// Almacena el precio de cierre (Close) de las velas.
    double highArray[];// Almacena el precio máximo (High) de las velas.
    double lowArray[];// Almacena el precio mínimo (Low) de las velas.
    datetime Time[];// Almacena el tiempo de cada vela.
    double atr[];// Almacena los valores del ATR, que indican la volatilidad del mercado.
    long Volumen[]; // Almacena el volumen de ticks, representando el número de transacciones en cada vela.
    

    2. Configuración de Arrays como Serie Temporal

    Utilizamos ArraySetAsSeries() para que los arrays funcionen como series temporales. Esto significa que el índice 0 representará la vela más reciente, lo cual facilita el acceso a los datos de las últimas velas:

    ArraySetAsSeries(openArray, true);
    ArraySetAsSeries(closeArray, true);
    ArraySetAsSeries(highArray, true);
    ArraySetAsSeries(lowArray, true);
    ArraySetAsSeries(Time, true);
    ArraySetAsSeries(Volumen, true);
    ArraySetAsSeries(atr, true);

    3. Copiar Datos de las Velas y el ATR

    A continuación, se utilizan las funciones "CopyOpen, CopyClose, CopyHigh, CopyLow, CopyTime y CopyTickVolume" para copiar datos de las velas y el volumen de ticks en los arrays correspondientes. También se utiliza CopyBuffer para obtener los valores del ATR:

    int copiedBars = CopyOpen(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), openArray);
    if(copiedBars < 0)
    {
        Print("Error copying data from Open: ", GetLastError());
    }
    CopyClose(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), closeArray);
    CopyHigh(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), highArray);
    CopyLow(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), lowArray);
    CopyTime(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), Time);
    CopyTickVolume(_Symbol, PERIOD_CURRENT, 0, (Rango_universal_busqueda * 2), Volumen);
    CopyBuffer(atr_i, 0, 0, (Rango_universal_busqueda * 2), atr);

    4. Manejo de Errores

    Al copiar los datos de apertura, se verifica si el número de barras copiadas es negativo, lo que indicaría un error. En tal caso, se imprime un mensaje de error utilizando GetLastError() para facilitar la depuración.

    Preparativos para la Programación de la Lógica de Detección de Order Blocks

    Antes de implementar la lógica para detectar los Order Blocks, realizaremos una serie de preparativos esenciales:

    1. Detección de Velas Alcistas Previas: Crearemos una función que identifique si hay velas alcistas anteriores a la primera vela del patrón. Si se detectan, asignaremos el valor de esta primera vela a la más cercana, lo que nos permitirá dibujar el Order Block desde el inicio del movimiento.

    2. Dibujo de Rectángulos: Implementaremos una función específica para dibujar rectángulos que representen visualmente los Order Blocks en el gráfico.

    3. Gestión de Arrays: Desarrollaremos funciones para agregar los Order Blocks detectados a sus respectivos arrays. Esto incluirá:

      • Verificación de Duplicados: Una función que asegure que el Order Block que se intenta agregar no haya sido registrado previamente. De esta forma, solo se añadirán Order Blocks nuevos.
    4. Mitigación de Order Blocks: Crearemos una función que revise si un Order Block ha sido mitigado.

    5. Eliminación de Order Blocks: Implementaremos una función para marcar Order Blocks como eliminados, lo que nos ayudará a mantener nuestras alertas organizadas y limpias.

    Con estas funciones en su lugar, podremos comenzar a agregar los Order Blocks a los arrays y asegurar que solo se registren los nuevos. A partir de este punto, no proporcionaré explicaciones línea por línea debido a la extensión del código, en su lugar, ofreceré resúmenes para cada sección relevante.

    1. Función

    //+------------------------------------------------------------------+
    //|     Funciones para Manejar y Añadir valores a los Arrays         |
    //+------------------------------------------------------------------+
    void AddIndexToArray_alcistas(OrderBlocks &newVela_Order_block_alcista)
      {
       if(!IsDuplicateOrderBlock_alcista(newVela_Order_block_alcista))  //aquí verificamos si la estructura que vamos a pasar ya existe en el array
         {
          int num_orderblocks_alcista = ArraySize(ob_alcistas); //asignamos al a variable "num_orderblocks_alcista" el tamaño del array de ob_alcistas
          ArrayResize(ob_alcistas, num_orderblocks_alcista + 1); //redimensionamos el array a su valor anterior + 1 , esto para crear una nueva posición donde guardaremos los datos de un nuevo order block
          ob_alcistas[num_orderblocks_alcista] = newVela_Order_block_alcista; //asignamos el índice = al tamaño del array anterior al nuevo order block
         }
      }
    
    bool IsDuplicateOrderBlock_alcista(const OrderBlocks &newBlock)
      {
       for(int i = 0; i < ArraySize(ob_alcistas); i++) //empezamos un bucle recorriendo todos las posiciones del array de ob_alcistas
         {
          if(ob_alcistas[i].time1 == newBlock.time1 &&
             ob_alcistas[i].name == newBlock.name
            ) //verificamos si el tiempo1 y el nombre del order block ya existen en el array
            {
             return true; // Si existe en el order block la función retorna true ósea que si existe por lo tanto estamos en un caso de duplicado
             break; //nos salimos del bucle
            }
         }
       return false; //si no se encuentra ningún duplicado retornamos false = falso
      }
    
    // aquí seria lo mismo pero una versión bajista
    void AddIndexToArray_bajistas(OrderBlocks &newVela_Order_block_bajista)
      {
       if(!IsDuplicateOrderBlock_bajista(newVela_Order_block_bajista))
         {
          int num_orderblocks_bajistas = ArraySize(ob_bajistas);
          ArrayResize(ob_bajistas, num_orderblocks_bajistas + 1);
          ob_bajistas[num_orderblocks_bajistas] = newVela_Order_block_bajista;
         }
      }
    
    bool IsDuplicateOrderBlock_bajista(const OrderBlocks &newBlock)
      {
       for(int i = 0; i < ArraySize(ob_bajistas); i++)
         {
          if(ob_bajistas[i].time1 == newBlock.time1 &&
             ob_bajistas[i].name == newBlock.name
            )
            {
             return true; // Duplicate found
             break;
            }
         }
       return false; // No duplicate found
      }

    Ahora que hemos implementado las funciones que nos ayudarán a verificar si un Order Block se ha duplicado y a agregarlo a un array dinámico, es momento de explicar cómo dibujaremos estos Order Blocks en el gráfico.

    Para ello, utilizaremos dos precios clave:

    • Price 1: En el caso de un Order Block alcista, este precio representará la base del rectángulo. Para un Order Block, bajista, será el lado paralelo superior.

    • Price 2: En un Order Block alcista, este precio corresponderá al lado paralelo superior del rectángulo. En un Order Block, bajista, será la base del rectángulo.

    Ejemplo alcista:

         Bullish OB

     Ejemplo bajista:

          Bearish Order Block

    Ahora con esto en mente procede a mostraros las funciones para detectar mitigaciones en order blocks.

    //+------------------------------------------------------------------+
    //|             Funciones para Order Blocks                          |
    //+------------------------------------------------------------------+
    datetime  mitigados_alcsitas(double price, double &openArray[], double &closeArray[], double &highArray[], double &lowArray[], datetime &Time[], datetime start, datetime end)
      {
       int startIndex = iBarShift(_Symbol,PERIOD_CURRENT,start); //aquí con iBarShift buscamos el índice de su vela pasando el tiempo que seria "start".
       int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end);// aquí con iBarShift buscamos el índice de su vela pasando el tiempo que seria "end".
       NormalizeDouble(price,_Digits); //normalizamos el precio con el que trabajaremos mas adelante
    
       for(int i = startIndex - 2 ; i >= endIndex + 1 ; i--) //empezamos un bucle recorriendo desde Start que en nuestro caso será el time1 del order block
         {
          //terminado por endIndex que sera el time[0] + 1 = time[1]  --> ósea buscaremos mitigación de atrás --> hacia adelante
          NormalizeDouble(lowArray[i],_Digits);
          NormalizeDouble(openArray[i],_Digits);
          NormalizeDouble(highArray[i],_Digits);
          NormalizeDouble(openArray[i],_Digits);
          //Normalizamos todas laas variable
    
          if(price > lowArray[i] || price > openArray[i] || price > closeArray[i] || price > highArray[i]) // aca verficamos si el OHCL cerro pro debajo de price
            {
             return Time[i]; //si encuentra que si hubo retorna el tiempo de la vela donde hubo la mitigación
             Print("el orderblock tuvo mitigaciones", TimeToString(end));
            }
         }
    
       return 0; //En caso no se haya encontrado  ninguna mitigación retorna 0
      }
    
    // lo mismo en el caso bajista pero cambiando algo
    // envés de que el precio cierre por debajo del price
    datetime  mitigado_bajista(double price, double &openArray[], double &closeArray[], double &highArray[], double &lowArray[], datetime &Time[], datetime start, datetime end)
      {
    
       int startIndex = iBarShift(_Symbol,PERIOD_CURRENT,start);
       int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end);
       NormalizeDouble(price,_Digits);
       for(int i = startIndex - 2 ; i >= endIndex + 1 ; i--)
         {
          NormalizeDouble(lowArray[i],_Digits);
          NormalizeDouble(openArray[i],_Digits);
          NormalizeDouble(highArray[i],_Digits);
          NormalizeDouble(openArray[i],_Digits);
          if(highArray[i] > price || closeArray[i] > price || openArray[i] > price || lowArray[i] > price)
            {
    
             return Time[i]; //retorna el time de la vela encontrada
             Print("el orderblock tuvo mitigaciones", TimeToString(end));
    
            }
         }
    
       return 0; // no se mitigo hasta el momento
      }
    
    datetime esOb_mitigado_array_alcista(OrderBlocks &newblock, datetime end)
      {
       int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end);
       NormalizeDouble(newblock.price2,_Digits);
    
       for(int i = 0 ; i <  endIndex -2  ; i++)
         {
    
          double low = NormalizeDouble(iLow(_Symbol,PERIOD_CURRENT,i),_Digits);
          double close = NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,i),_Digits);
          double open = NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,i),_Digits);
    
    
          if((newblock.price2 >= low || newblock.price2 >= open) || newblock.price2 >= close)
            {
             newblock.mitigated = true;
    
    
             return iTime(_Symbol,PERIOD_CURRENT,i); //retorna el time de la vela encontrada
            }
         }
    
       return 0; // no se mitigo hasta el momento
      }
    
    datetime esOb_mitigado_array_bajista(OrderBlocks &newblock, datetime end)
      {
    
       int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end);
       NormalizeDouble(newblock.price2,_Digits);
    
       for(int i = 0 ; i<  endIndex -2  ; i++)
         {
    
          double high = NormalizeDouble(iHigh(_Symbol,PERIOD_CURRENT,i),_Digits);
          double close = NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,i),_Digits);
          double open = NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,i),_Digits);
    
          if((high >= newblock.price2 || close >= newblock.price2) || open >= newblock.price2)
            {
             newblock.mitigated = true;
             //retorna el time de la vela encontrada
             return iTime(_Symbol,PERIOD_CURRENT,i);
            }
         }
    
       return 0; // no se mitigo hasta el momento
      }
    

    Estas funciones se encargarán de verificar el estado de los Order Blocks y son las siguientes:

    • Función de Verificación de Mitigaciones: Esta función comprobará si ha habido mitigaciones en los Order Blocks y se utilizará para evaluar la estructura que se va a agregar.

    • Función de Activación de Mitigación: La segunda función, que incluye la palabra clave "array", activará el estado de mitigación para los Order Blocks.

    A continuación, abordaremos las funciones para dibujar los rectángulos y para encontrar la vela alcista o bajista más cercana.

    La función para identificar la vela alcista o bajista más cercana es crucial. Su propósito es asegurarse de que, al detectar un Order Block, se pueda identificar correctamente la vela relevante, especialmente en situaciones donde el Order Block se forma al inicio de un movimiento fuerte. Esto evitará que se detecte de manera errónea en la mitad o al final del movimiento, lo que podría comprometer la efectividad del análisis.

    Ejemplo alcista:

    Example Function OB+

    He programado estas funciones tal que así:

    //+------------------------------------------------------------------+
    //|  Funciones para encontrar ala vela alcista - bajista mas cercana |
    //+------------------------------------------------------------------+
    int FindFurthestAlcista(datetime start, int numVelas)
      {
       int startVela = iBarShift(_Symbol, PERIOD_CURRENT, start); // usamos iBarShift para encontrar el índice pro el tiempo de la vela que seria start.
    //inisializamos variables
       int furthestVela = 0;
       int counter_seguidas = 0;
    
       for(int i = startVela  + 1; i <= startVela + numVelas ; i++)  //hacemos un bucle desde la start vela  + 1, por que al momento de pasar "start" = time1 del order block
         {
          //entonces es obvio qu ela vela del time1 es alcista (en esta funcion) , por eso aumentamos +1 , luego verificamos que i sea menor o igual que el indice de startVela + num velas.
          //aqui num velas seria el numero de velas a buscar
          double Close = NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,i),_Digits); //obtenemos el open por índice.
          double Open = NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,i),_Digits);  //obtenemos el close por índice.
    
          if(Close > Open || Close == Open)  // verificamos si es una vela alcista (close > open) , ósea el cierre debe ser mayor ala apertura de la vela.
            {
             counter_seguidas++; //si es esto cierto aumentamos una variable en 1,  que usaremos mas adelante.
            }
          else
             if(Open > Close)
               {
                furthestVela = startVela  + 1 + counter_seguidas; //en caso la vela encontrada no sea alcista es obvio que será bajista por lo tanto asignamos el indice de la vela antorior ala que inicio el movimiento
                // start vela : es la vela que pasamos  luego le sumamos 1 por que como dijimos antes dibujaremos el order block una vela antes del movimiento alcista (osea vela bajista)
                // a esto le sumamos el contador de velas seguidas.
    
                break; //nos salimos del bucle
               }
         }
    
    //verificamos si el cuerpo de la vela anterior al movimiento es mas grande en un 30% ala vela que inicia el movimiento si lo es lo regresamos a valor nornaml
       double body1 = NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,(furthestVela -1)),_Digits) - NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,(furthestVela -1)),_Digits);;
       double body_furtles =  NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,furthestVela),_Digits) -  NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,furthestVela),_Digits);
    
       if(body_furtles > (1.3 * body1))
          furthestVela--;
    
       return furthestVela; // retornamos el índice de la vela encontrada.
      }
    
    // Función para buscar la vela bajista más lejana con velas bajistas consecutivas
    int FindFurthestBajista(datetime start, int numVelas)
      {
       int startVela = iBarShift(_Symbol, PERIOD_CURRENT, start); // Índice de la vela inicial
       int furthestVela = 0; // Inicializamos la variable
       int counter_seguidas = 0; // Contador de velas bajistas consecutivas
    
       for(int i = startVela + 1; i <= startVela + numVelas; i++)
         {
          double Close = NormalizeDouble(iClose(_Symbol, PERIOD_CURRENT, i), _Digits);
          double Open = NormalizeDouble(iOpen(_Symbol, PERIOD_CURRENT, i), _Digits);
    
          // Si la vela es bajista
          if(Close < Open || Close == Open)
            {
             counter_seguidas++; // Aumentamos el contador de velas bajistas seguidas
            }
          // Si la vela es alcista, nos detenemos
          else
             if(Close > Open)
               {
                // Devuelve la vela donde la secuencia de bajistas se interrumpe por una alcista
                furthestVela = startVela + 1 + counter_seguidas;
                break;
               }
         }
    
       return furthestVela;
      }

    Ahora solo  nos quedaría crear la función para el dibujado de los rectángulos:

     void RectangleCreate(long chart_ID, string name, const int sub_window, datetime time1, double price1, datetime time2, double price2, color clr, int width, bool fill, bool back , ENUM_LINE_STYLE style , bool select = false)
      {
       ResetLastError(); //reseteamos el ultimo error
    
     //comprobacion y creacion del rectangulo
       if(!ObjectCreate(chart_ID, name, OBJ_RECTANGLE, sub_window, time1, price1, time2, price2))
         {
          Print(__FUNCTION__, ": Falo al crear el rectangulo ! Error code = ", GetLastError()  , "El nombre del rectangulo es  : " , name); //si falla el creado imprimos la funcion + el codigo de error y el nombre del rectangulo
         }
    
      //Establecemos las propiedades de los rectangulos
       ObjectSetInteger(chart_ID, name, OBJPROP_COLOR, clr);
       ObjectSetInteger(chart_ID, name, OBJPROP_STYLE, STYLE_SOLID);
       ObjectSetInteger(chart_ID, name, OBJPROP_WIDTH, width);
       ObjectSetInteger(chart_ID, name, OBJPROP_FILL, fill);
       ObjectSetInteger(chart_ID, name, OBJPROP_BACK, back);
       ObjectSetInteger(chart_ID, name, OBJPROP_SELECTABLE, select);
       ObjectSetInteger(chart_ID, name, OBJPROP_SELECTED, select);
       ObjectSetInteger(Chart_ID, name, OBJPROP_STYLE ,style);  
      }

    Una vez tengamos todas estas funciones pasaremos ala siguiente parte.

    2.3: Lógica para la Detección de Order Blocks


    La lógica que yo  he diseñado para este sistema es el siguiente:

    1. Detección de order block por las lógicas que establecimos en la teoría.
    2. Asignación de valores a las estructuras. 
    3. Agregar las estructuras al array donde estarán los order blocks.
    4. Verificación si el order block  ha sido mitigado o no.
    5. Dibujado del order block.
    6. Alertas. 
    Con esto en cuenta empezaremos programando el indicador.

    Primero empezaremos creando estructuras para que estas guarden los valores de los order blocks:

    • Lo que hacemos es crear 4 variables que tendrán la forma de la estructura OrderBlocks.
    • 2 Para guardar los order blocks alcistas (Order blocks por Indicadores y Acción del precio).
    • 2 Para guardar los order blocks bajistas  (Order blocks por Indicadores y Acción del precio).

      OrderBlocks  newVela_Order_block_alcista;
      OrderBlocks  newVela_Order_block_volumen;
      OrderBlocks newVela_Order_Block_bajista;
      OrderBlocks newVela_Order_Block_bajista_2;
      

      Agregando estas estructuras ya tenemos las variables que guardaran  los valores del order block.

      Ahora si solo nos dedicáramos a la lógica para detectar los order blocks vamos.

      Empezaremos por la lógica:

      • Necesitaremos buscar los order blocks dentro de un rango de velas  y asignarle un índice, lo que es igual a buscar un patrón de velas con las condiciones que establecimos en la lógica.
      • Para esto nos ayudaremos de for.

      Vamos a programarlo:

              
      for(int i = Rango_universal_busqueda  ; i  > 5  ; i--)
         {
      
      
      //verficacion de errores
      if(i + 3> ArraySize(highArray)  || i + 3 > ArraySize(atr))
      continue;
      if(i < 0)
      continue;
      
      //--------Declaracion Variables--------------------------------------------//
      
      // Actualizar los índices de las velass
      int one_vela = i ; //vela central
      int  vela_atras_two = i +2;
      int vela_atras_one = one_vela +1;
      int two_vela = one_vela - 1;
      int tree_vela = one_vela - 2;
      int four_vela = one_vela -3;
      
      NormalizeDouble(highArray[vela_atras_one],_Digits);
      NormalizeDouble(lowArray[vela_atras_one ], _Digits);
      NormalizeDouble(closeArray[vela_atras_one ],_Digits);
      NormalizeDouble(openArray[vela_atras_one ],_Digits);
      
      NormalizeDouble(highArray[two_vela],_Digits);
      NormalizeDouble(lowArray[two_vela], _Digits);
      NormalizeDouble(closeArray[two_vela],_Digits);
      NormalizeDouble(openArray[two_vela],_Digits);
      
      NormalizeDouble(highArray[tree_vela],_Digits);
      NormalizeDouble(lowArray[tree_vela], _Digits);
      NormalizeDouble(closeArray[tree_vela],_Digits);
      NormalizeDouble(openArray[tree_vela],_Digits);
      
      NormalizeDouble(highArray[one_vela],_Digits);
      NormalizeDouble(lowArray[one_vela], _Digits);
      NormalizeDouble(closeArray[one_vela],_Digits);
      NormalizeDouble(openArray[one_vela],_Digits);
      
      // Calcular el promedio del volumen de las velas anteriores
      double body1 = closeArray[one_vela] - openArray[one_vela];
      double body2 = closeArray[two_vela] - openArray[two_vela];
      double body3 = closeArray[tree_vela] - openArray[two_vela];
      
      //Volumen Condición
      long Volumen_one_vela = Volumen[one_vela];
      long Volumen_two_vela = Volumen[two_vela];
      long volumen_vela_atras_one = Volumen[vela_atras_one];

      • Básicamente, lo que hacemos en este código es crear un bucle que irá desde la vela máxima, que será:

      (Rango_universal_busqueda)

      y terminado en el índice 6 por:

       i  > 5

      en caso i > 5 

      Restaremos en 1 el valor de i.

      • Normalizamos masivamente el OHCL de las velas con las que trabajaremos.
      • Asignamos el cuerpo de la vela como:  close - open.
      • Obtenemos el valor de tick esto solo para el caso de pico:

      //Volumen 
      long Volumen_one_vela = Volumen[one_vela];
      long Volumen_two_vela = Volumen[two_vela];
      long volumen_vela_atras_one = Volumen[vela_atras_one];

      Ahora agregue un caso más que sería el atr, básicamente con el atr busco que el precio se haya movido fuertemente a una dirección.  

      • En este trozo que código que veréis a continuación es básicamente  las condiciones de la lógica básica y  con indicador atr:
      //Variables Booleanas para detectar si se cumple el caso (solo Acción del precio)
      bool esVelaCorrecta_case_normal =false;
      bool  esVela_Martillo = false;
      
      //aqui comprobamos que se hayan creando 4 velas consecutivas alcistas esto con close > open
      if(
         closeArray[one_vela] > openArray[one_vela] &&
         closeArray[two_vela] > openArray[two_vela] &&
         closeArray[tree_vela] > openArray[tree_vela] &&
         closeArray[four_vela] > openArray[four_vela]
      )
        {
      
         esVelaCorrecta_case_normal =true; //si esto es cierto asignamos a "esVelaCorrecta_case_normal"  el valor de true = verdadero.
        }
      else
         esVelaCorrecta_case_normal =false; // en cambio si no lo es asignamos a falso = false
      
      
      bool fuerte_movimiento_alcista =false; //creamos una variable que solo se activar si ocurre un fuerte movimiento alcista
      
      //verificamos si se creo un movimiento de 6 velas consecutivas alcistas
      if(
         closeArray[one_vela + 2] > openArray[one_vela + 2] &&
         closeArray[one_vela + 1] > openArray[one_vela +1] &&
         closeArray[one_vela] > openArray[one_vela] &&
         closeArray[two_vela] > openArray[two_vela] &&
         closeArray[tree_vela] > openArray[tree_vela] &&
         closeArray[four_vela] > openArray[four_vela]
      )
        {
         fuerte_movimiento_alcista = true; //si se creo asignamos a "fuerte_movimiento_alcista" el valor de  true = verdadero.
        }
      
      //verificamos si es vela martillo
      if(openArray[one_vela] - lowArray[one_vela] > closeArray[one_vela] - openArray[one_vela]) //acá verificamos si la mecha inferior es mayor al cuerpo de la vela
        {
         esVela_Martillo = true; // si lo es la variable " esVela_Martillo " se hace true.
        }
      
      
      bool atr_case = false;
      
      
      if(atr[vela_atras_two] > atr[one_vela] && atr[two_vela] > atr[one_vela] && atr[two_vela] > atr[vela_atras_two] && closeArray[one_vela] > openArray[one_vela]
         && closeArray[four_vela] > openArray[four_vela] && closeArray[tree_vela] > openArray[tree_vela])
         atr_case = true;  // en este código buscamos que haya habido primero una caida del atr en one vela
      //luego una subida del atr y finalizando con  1vela , 3 vela , 4 vela deben ser alcistas la segunda que digamos no es necesario para este caso.
      
      //verificacion para el caso nomral
      if((esVelaCorrecta_case_normal == true && ((lowArray[two_vela] > ((body1 *0.5)+openArray[one_vela]) && ((body2 * 0.4)+openArray[two_vela]) > highArray[one_vela]) || esVela_Martillo == true)
          && lowArray[tree_vela] > ((body2 * 0.25) +openArray[two_vela])) || fuerte_movimiento_alcista == true || atr_case == true)  //acá lo del inicio buscamos que las mechas no sobrepasen puntos importantes
        {
         int furthestAlcista = FindFurthestAlcista(Time[one_vela],20); //llamamos ala función "FindFurthestAlcista" para saber si anterior a "one vela" hay velas alcistas
         if(furthestAlcista > 0) // haiga o no haiga furthestAlcista será mayor a 0 ya que si no hay retorna la vela anterior a "one vela".
           {
      
            datetime time1 = Time[furthestAlcista]; //asignamos el tiempo del índice de furthestAlcista ala variable time1
            double price2 = openArray[furthestAlcista]; //asignamos el open de   furthestAlcista como precio 2 (recodar que lo dibujamos en una vela bajista al mayoria de veces)
            double price1 = lowArray[furthestAlcista]; //asignamos el low de furthestAlcista como precio 1
      
            //asiganmos las variables mencionadas ala estructura
            newVela_Order_block_alcista.price1 = price1;
            newVela_Order_block_alcista.time1 = time1;
            newVela_Order_block_alcista.price2 = price2;
      
            case_OrderBlockAlcista_normal = true; //si todo es es cierto se activa al caso normal
           }
         else
            case_OrderBlockAlcista_normal =false;
      
      
        }
      //versión bajista
      
      bool case_OrderBlockBajista_normal = false;
      bool case_OrderBlockBajista_volumen = false;
      
      //---------------Caondicionales Para Los Order Blocks--------------------//
      //+------------------------------------------------------------------+
      //| Condiciones Para Caso Order Block Bajista case_normal            |
      //+------------------------------------------------------------------+
      if(closeArray[one_vela] < openArray[one_vela]  &&
         closeArray[two_vela] < openArray[two_vela]  &&
         closeArray[tree_vela] < openArray[tree_vela]  &&
         closeArray[one_vela-3]< openArray[one_vela-3]
        )
        {
         esVelaCorrecta_case_normal =true;
        }
      else
         esVelaCorrecta_case_normal =false;
      
      bool a = false;
      
      
      if(atr[vela_atras_two] > atr[one_vela] && atr[two_vela] > atr[one_vela] && atr[two_vela] > atr[vela_atras_two] && esVelaCorrecta_case_normal)
         a= true;
      
      bool fuerte_movimiento_bajista =false;
      
      if(
         closeArray[one_vela + 2] < openArray[one_vela + 2] &&
         closeArray[one_vela + 1] < openArray[one_vela +1] &&
         closeArray[one_vela] < openArray[one_vela] &&
         closeArray[two_vela] < openArray[two_vela] &&
         closeArray[tree_vela] < openArray[tree_vela] &&
         closeArray[one_vela - 3] <= openArray[one_vela - 3]
      )
        {
         fuerte_movimiento_bajista = true;
        }
      
      //verificación para el caso normal
      if((esVelaCorrecta_case_normal == true && highArray[two_vela] < ((body1 *0.70)+closeArray[one_vela]) && ((body2 * 0.4)+closeArray[two_vela]) < lowArray[one_vela] && highArray[tree_vela] < highArray[two_vela])
         || a == true || fuerte_movimiento_bajista == true
        )
        {
         int furthestBajista = FindFurthestBajista(Time[one_vela], 20);
         if(furthestBajista != -1)
           {
      
      
            datetime time1 = Time[furthestBajista];
            double price1 = closeArray[furthestBajista];
            double price2 = lowArray[furthestBajista];
      
            newVela_Order_Block_bajista.price1 = price1;
            newVela_Order_Block_bajista.time1 = time1;
            newVela_Order_Block_bajista.price2 = price2 ;
      
           }
         else
            case_OrderBlockBajista_normal =false;
        }
      //+------------------------------------------------------------------+

      Voy a explicar cada función con sus respectivos comentarios. En resumen, buscamos identificar ciertos patrones, y cuando los encontramos, activamos variables de tipo booleano.

      A continuación, verificamos si hay velas alcistas anteriores a la one_vela, que es la vela que inicia el movimiento.

      Finalmente, asignamos los valores de precio y tiempo a los Order Blocks.

      Ahora, pasemos a analizar el caso por volumen, donde consideraremos tanto los picos de volumen como el volumen creciente.

      //condicion orderblock volumen--------------------------------//
      if(Volumen_one_vela  > Volumen_two_vela && Volumen_one_vela > volumen_vela_atras_one)
        {
         VolumenCorrecto = true; //acá verificamos el pico de volumen
        }
      else
         VolumenCorrecto = false;
      
      //para que la vela alcista atras sea bajista y 2 alcistas
      if(closeArray[one_vela] > openArray[one_vela]  &&
         closeArray[two_vela] > openArray[two_vela])
        {
         VelaCorrecta_casevolumen = true;
        }
      
      //caso consecutivo
      bool case_vol_2 = false;
      if(Volumen[one_vela] > volumen_vela_atras_one && Volumen[two_vela] > Volumen[one_vela] && openArray[tree_vela] < closeArray[tree_vela] && openArray[four_vela] < closeArray[four_vela])
         case_vol_2 = true;
      
      //acá verificamos que no las mechas no mitigen al order block
      if((VolumenCorrecto == true && VelaCorrecta_casevolumen == true
          && ((lowArray[two_vela] > ((body1 * 0.5)+openArray[one_vela]) && ((body2 *0.6)+openArray[two_vela]) > highArray[one_vela]) || esVela_Martillo == true)
          && highArray[tree_vela] > openArray[two_vela]) || case_vol_2 == true)
        {
      //todo esto ya lo explique mas arriba literlamte es lo mismo buscamos el mas cercano alcista y asignamos valor al anterior a este
         int furthestAlcista = FindFurthestAlcista(Time[one_vela],20);
         if(furthestAlcista > 0)
           {
      
            datetime time1 = Time[furthestAlcista];
            double price2 = openArray[furthestAlcista];
            double price1 = lowArray[furthestAlcista];
      
            newVela_Order_block_volumen.price1 = price1;
            newVela_Order_block_volumen.time1 = time1;
            newVela_Order_block_volumen.price2 = price2;
      
            case_orderblock_vol= true;
           }
         else
            case_orderblock_vol =false;
      
      
        }
      //version bajista
      //+------------------------------------------------------------------+
      
      //+------------------------------------------------------------------+
      //| Condiciones Para Caso Order Block Alcista case_Volumen           |
      //+------------------------------------------------------------------+
      
      bool VelaCorrecta_casevolumen = false;
      bool VolumenCorrecto;
      //condicion orderblock volumen--------------------------------//
      //por pico de volumen
      if(Volumen_one_vela  > Volumen_two_vela && Volumen_one_vela > volumen_vela_atras_one)
        {
         VolumenCorrecto = true;
        }
      else
         VolumenCorrecto = false;
      //buscamos aqui 2 velas bajistas consecutivas
      if(closeArray[one_vela] < openArray[one_vela]  &&
         closeArray[two_vela] < openArray[two_vela])
        {
      
         VelaCorrecta_casevolumen = true; //establecemos como true a la variable "VelaCorrecta_casevolumen "
        }
      //buscamos un volumen creciente además de que la 3 vela y 4 vela sean bajista
      bool case_vol_2 = false;
      if(Volumen[one_vela] > volumen_vela_atras_one && Volumen[two_vela] > Volumen[one_vela] && openArray[tree_vela] > closeArray[tree_vela] && openArray[four_vela] > closeArray[four_vela])
         case_vol_2 = true;
      
      
      if((VolumenCorrecto == true && VelaCorrecta_casevolumen == true && highArray[two_vela] < ((body1 * 0.5)+closeArray[one_vela]) && ((body2 *0.5)+closeArray[two_vela]) < lowArray[one_vela]) || case_vol_2 == true)   // verificamos si se cumple
        {
      // el caso pico de volumen o caso volumen creciente
         int furthestBajista = FindFurthestBajista(Time[one_vela],20); //buscamos la vela bajista mas cercana ala 1 vela
      
         if(furthestBajista > 0)
           {
            //si esto es cierto que como dije antes siempre lo va a ser asignamos los valores de la vela
            //a las variables de la estructura para dibujar los rectángulos
            datetime time1 = Time[furthestBajista];
            double price1 = closeArray[furthestBajista];
            double price2 = lowArray[furthestBajista];
      
            newVela_Order_Block_bajista_2.price1 = price1;
            newVela_Order_Block_bajista_2.time1 = time1;
            newVela_Order_Block_bajista_2.price2 = price2 ;
      
            case_OrderBlockBajista_volumen = true;
           }
         else
            case_OrderBlockBajista_volumen = false;
        }
      //+------------------------------------------------------------------+
      

      Ahora que hemos implementado la detección de Order Blocks, es necesario agregarlos al array para poder dibujarlos posteriormente.

      En el siguiente código realizamos los siguientes pasos:

      1. Inicializamos la variable mitigated como false.
      2. Asignamos el nombre del Order Block según su tipo, junto con el tiempo de la one_vela.
      3. Finalmente, añadimos el Order Block al array dinámico.
      if(case_OrderBlockAlcista_normal == true
         && mitigados_alcsitas(newVela_Order_block_alcista.price2,openArray,closeArray,highArray,lowArray,Time,newVela_Order_block_alcista.time1,Time[0]) == 0)  //verificamos que el order block no haya sido mitigado
        {
         newVela_Order_block_alcista.mitigated = false; //activamos el estado del order block como no mitigado = false
         newVela_Order_block_alcista.name =  "Order Block Alcista normal" + TimeToString(newVela_Order_block_alcista.time1) ;  //asignamos de nombre "Order Block Alcista normal" + el tiempo de one_Vela
         AddIndexToArray_alcistas(newVela_Order_block_alcista); //agregamos al arraya para luego verificarlo si están siendo mitigados y dibujarlos
      
        }
      //lo mismo seria para el caso volumen
      if(case_orderblock_vol == true
         && mitigados_alcsitas(newVela_Order_block_volumen.price2,openArray,closeArray,highArray,lowArray,Time,newVela_Order_block_volumen.time1,Time[0]) == 0)
        {
         newVela_Order_block_volumen.mitigated = false;
         newVela_Order_block_volumen.name =  "Order Block Alcista vol" + TimeToString(newVela_Order_block_volumen.time1) ;
         AddIndexToArray_alcistas(newVela_Order_block_volumen);
      
        }
      
      
        } //parentesis del for
      
      //--- Version bajista
      
      if(case_OrderBlockBajista_normal == true  && mitigado_bajista(newVela_Order_Block_bajista.price2,openArray, closeArray, highArray, lowArray, Time, Time[0],newVela_Order_Block_bajista.time1) == 0
        ) //verificamos is el order block bajista no fue mitigado y el caso normal sea cierto
        {
         newVela_Order_Block_bajista.mitigated = false; //inicializamos el estado del order block como no mitigado = false
         newVela_Order_Block_bajista.name = ("Order Block Bajista ")+ TimeToString(newVela_Order_Block_bajista.time1) ; //asignamos el nombre como "Order Block Bajista " + el tiempo de la 1 vela
         AddIndexToArray_bajistas(newVela_Order_Block_bajista); //agregamos la estructura al array
        }
      
      if(case_OrderBlockBajista_volumen == true   && mitigado_bajista(newVela_Order_Block_bajista_2.price2, openArray,closeArray,highArray,lowArray,Time,Time[0],newVela_Order_Block_bajista_2.time1)== 0
        )//verificamos is el order block bajista no fue mitigado y el caso por volumen sea cierto
        {
         newVela_Order_Block_bajista_2.mitigated = false; //inicializamos el estado del order block como no mitigado = false
         newVela_Order_Block_bajista_2.name = ("Order Block Bajista ") + TimeToString(newVela_Order_Block_bajista_2.time1) ; //asignamos el nombre como "Order Block Bajista " + el tiempo de la 1 vela
         AddIndexToArray_bajistas(newVela_Order_Block_bajista_2); //agregamos la estructura al array
      
        }
        } //paréntesis del for
      //+------------------------------------------------------------------+

      Ahora pasemos con la parte del dibujado  y verificación del mitigado.

      2.4: Visualización: Colores y Verificación de Mitigación de Order Blocks


      En esta parte lo que veremos es cómo vamos a actualizar los order blocks, dibujarlos  y activar su estado mitigado.

      •  Los dibujaremos recorriendo "ob_alcistas" y "ob_bajistas" estos arrays previamente mencionados como dije, estos almacenarán la información de los order blocks.
      •  Moveremos los Objetos con "ObjectMove", ya que no buscaremos redibujar todo, ya que esto reduce la eficacia del programa, además que consume más recursos del ordenador.

      Repasado todo esto vamos a ver el código que prepare para cumplir con estos requisitos mencionados:

      for(int i = 0; i < ArraySize(ob_alcistas); i++) //Iteramos en todos los índices del array donde se almacena la información de los order blocks
        {
         datetime mitigadoTime = esOb_mitigado_array_alcista(ob_alcistas[i],ob_alcistas[i].time1); //llamamos ala función que nos indicara si el índice i ha sido mitigado o no si lo es activamos su estado a true
      
         if(ob_alcistas[i].mitigated == false)  //verificamos que no se haya mitigado
           {
            if(mitigadoTime == 0) //Condicionamos que el order block no haya sufrido toques del precio
              {
      
               if(ObjectFind(ChartID(),ob_alcistas[i].name) < 0) //verificamos si el objeto existe en el grafico con ObjectFind
                 {
                  RectangleCreate(ChartID(), ob_alcistas[i].name, 0, ob_alcistas[i].time1, ob_alcistas[i].price1,
                                  Time[0], ob_alcistas[i].price2,Color_Order_Block_Alcista, Witdth_order_block, Fill_order_block, Back_order_block,STYLE_SOLID); // creamos el rectángulo con los datos
      
                 }
               else
                  ObjectMove(ChartID(),ob_alcistas[i].name,1,Time[0],ob_alcistas[i].price2);     //por el contrario si si existe el objeto lo único que haremos es actualizarlo al tiempo actual usando el punto de anclaje 1
              }
           }
        }
      
      // Draw all order blocks from the orderBlocks array
      for(int i = 0; i < ArraySize(ob_bajistas); i++)
        {
         datetime mitigadoTime = esOb_mitigado_array_bajista(ob_bajistas[i],ob_bajistas[i].time1);
      
         if(ob_bajistas[i].mitigated == false)
           {
      
            if(mitigadoTime == 0)
              {
      
               if(ObjectFind(ChartID(),ob_bajistas[i].name) < 0)
                 {
                  RectangleCreate(ChartID(), ob_bajistas[i].name,0, ob_bajistas[i].time1, ob_bajistas[i].price1,
                                  Time[0], ob_bajistas[i].price2,Color_Order_Block_Bajista,Witdth_order_block,Fill_order_block,Back_order_block,STYLE_SOLID);
      
                 }
               else
                  ObjectMove(ChartID(),ob_bajistas[i].name,1,Time[0],ob_bajistas[i].price2);
      
              }
           }
      
        }
      //+------------------------------------------------------------------+
      • Cabe resaltar que la detección de los order blocks se hace dentro de for y este a su vez dentro de la condición:
      new_vela == true
      • El dibujado de los rectángulos se hace a fuera del for pero dentro de la condición:
      new_vela == true

      2.5: Implementación de Alertas para Mitigación de Order Blocks y eliminación de objetos


      En esta sección veremos como implementar alertas cuando un order block es mitigado y crearemos la función que mencione al inicio:

      Eliminar_Objetos()

      Empezaremos definiendo la logica:

      1. Necesitaremos llamar a las siguientes funciones:

      esOb_mitigado_array_bajista  

      esOb_mitigado_array_alcista

      Para detectar si un order block fue mitigado en caso lo es retornamos el tiempo de la vela que lo mitiga, además de activar el estado del order block a true  lo que es igual  que el order block fue mitigado.

      Entonces, para saber si un order block fue mitigado o no, nos ayudaremos de su estado:

      mitigated

      Ahora revisando la estructura que tienen los order blocks vemos que tienen sus precios, su tiempo, su estado y su nombre:

      struct OrderBlocks
      {
       datetime time1;
       double price1;
       double price2;
       string name;
       bool mitigated;
       
      };

       De esta estructura nos importa en concreto 2 variables para las alertas:

       string name;
       bool mitigated;

      • mitigated: con este variable tipo booleana nos daremos cuenta si el order block fue mitigado.
      • name: con esta verificaremos si el order block que fue mitigado ya ha sido mitigado anteriormente
      Recordemos que una vez que se activa "mitigated" el order block siempre se estará mitigando, entonces tenemos que tener un filtro para que a cada rato el indicador  no nos muestre alertas, este filtro será el nombre del order block.

      Ahora bien, nos faltará:

      • Unos 2 arrays, estos arrays serán de tipo string además de dinámicos, ya que iremos actualizando su tamaño conforme más order blocks  se mitiguen.
      • Función que nos añada a los arrays string.
      • Función para ver si el string que pasemos como nombre del order block ya está el array.

      Bien, he integrado las funciones y arrays que nos faltan:

      • Iremos a la parte global del programa donde escribiremos:

      string pricetwo_eliminados_oba[];
      string pricetwo_eliminados_obb[];

      Estos serían los arrays que necesitaremos.

      Luego crearemos las siguientes funciones:

        bool Es_Eliminado_PriceTwo(string pName_ob , string &pArray_price_two_eliminados[])
        {
         bool a = false; // creamos la variable "a"  y la inicializamos a false
           for(int i = 0 ; i < ArraySize(pArray_price_two_eliminados) ; i++) //recorremos por todos los índices del array pasado como referencia
           {
            if(pName_ob == pArray_price_two_eliminados[i]) // compararemos todas las posiciones del array con la variable "pName_ob".
            { // si la comparacion es identica la variable "a " se vuelve true
             a = true; 
              break; // salimos del bucle
            } 
           } 
        return a; //retornamos el valor de "a"
       }   
       
       //funcion para añadir valores  y asignar un nuevo tamaño al array pasado pro referencia 
       void Agregar_Index_Array_1(string &array[], string pValor_Aagregar) {
          int num_array = ArraySize(array);
          if (ArrayResize(array, num_array + 1) == num_array + 1) {
              array[num_array] = pValor_Aagregar;
          } else {
            Print("Error resizing array");
          }
      }
      • Estas funciones nos ayudarán a verificar al momento que un order block se haya mitigado, este no se haya mitigado antes para no hacer un spam masivo en alertas.

      Ahora nos dirigimos ala parte dentro de OnCalculate() para terminar de programar las alertas:

      • Iniciamos creando un bucle para recorrer todos los índices del array de los order blocks.
      • Verificamos con un if() el estado del order block, además de comprobar que el nombre del order block NO este en el array string donde almacenamos nombres de order blocks.
      • Si todo esto es cierto, informamos al usuario con una alerta que un order block fue mitigado.
      • Agregamos el nombre del order block al array tipo string para evitar repeticiones.
      • Finalizamos saliéndonos del bucle con un break.
      // Recorre los bloques de orden
      for(int i = 0; i < ArraySize(ob_alcistas); i++)
        {
         if(ob_alcistas[i].mitigated == true && Es_Eliminado_PriceTwo(ob_alcistas[i].name, pricetwo_eliminados_oba) == false)
           {
            Alert("El order block alcista esta siendo mitigado: ", TimeToString(ob_alcistas[i].time1));
      
            Agregar_Index_Array_1(pricetwo_eliminados_oba, ob_alcistas[i].name);
      
            break;
           }
        }
      
      // Recorre los bloques de orden
      for(int i = 0; i < ArraySize(ob_bajistas); i++)
        {
         if(ob_bajistas[i].mitigated == true && Es_Eliminado_PriceTwo(ob_bajistas[i].name, pricetwo_eliminados_obb) == false)
           {
      
            Alert("El order block bajista esta siendo mitigado: ", TimeToString(ob_bajistas[i].time1));
      
            Agregar_Index_Array_1(pricetwo_eliminados_obb, ob_bajistas[i].name);
      
            break;
           }
        }
      //+------------------------------------------------------------------+

      Ahora que terminamos las alertas pasemos con la eliminación de objetos, revisando la documentación:

      bool  ObjectDelete(
         long    chart_id,     // identificador del gráfico
         string  name          // nombre del objeto
         );

      Entonces necesitaremos el identificador del gráfico actual:

      ChartID()

      El nombre del order block:

      name

      Con todo esto en mente necesitaremos un bucle que recorra todas las posiciones de nuestros order blocks, luego llamar a:

      ObjectDelete() 

      Para eliminar todos los objetos que creamos:

        
        void Eliminar_Objetos()
        {
        
        for(int i = 0 ; i < ArraySize(ob_alcistas) ; i++) // iteramos por el array de los order blocks alcistas 
        {
         ObjectDelete(ChartID(),ob_alcistas[i].name); // eliminamos el objeto usando el nombre del order block
        }
        for(int n = 0 ; n < ArraySize(ob_bajistas) ; n++) // iteramos por el array de los order blocks bajistas 
        {
        ObjectDelete(ChartID(),ob_bajistas[n].name);  // eliminamos el objeto usando el nombre del order block
        }
        
       }

      Con esto, habremos finalizado nuestro indicador, pero aún nos falta modificar las funciones:

      OnInit() y OnDeinit()

      Para manejar correctamente las nuevas variables y arrays que hemos agregado.

      En: 

      OnDeinit()

      Nos aseguramos de liberar los recursos utilizados por el indicador, eliminando objetos gráficos y liberando la memoria de los arrays dinámicos que almacenan los datos de los Order Blocks.

      Además, es importante asegurarse de que el handler del ATR se libere adecuadamente si fue inicializado correctamente, para evitar fugas de memoria o errores al cerrar el indicador. Esto lo hacemos con:

      if(atr_i != INVALID_HANDLE) 
          IndicatorRelease(atr_i);

      La implementación final de:

      OnDeinit()

      Quedaría así:

       void OnDeinit(const int reason)
        {
      //---
        Eliminar_Objetos();
      
        ArrayFree(ob_bajistas);
        ArrayFree(ob_alcistas);
        ArrayFree(pricetwo_eliminados_oba);
        ArrayFree(pricetwo_eliminados_obb); 
      
        if(atr_i  != INVALID_HANDLE) IndicatorRelease(atr_i );
      
        }
      
      //---
      
      
      int OnInit()
        {
      //--- indicator buffers mapping
        tiempo_ultima_vela = 0;
           atr_i = iATR(_Symbol,PERIOD_CURRENT,14);
           
           if(Rango_universal_busqueda < 40)
           {
            Print("Rango de busqueda muy pequeño");
            return (INIT_PARAMETERS_INCORRECT);
           }  
           
            if( atr_i== INVALID_HANDLE)
           {
            Print("Error al copiar data  para los indicadores");  
            return(INIT_FAILED);
           }
        
        ArrayResize(ob_bajistas,0);
        ArrayResize(ob_alcistas,0);
        ArrayResize(pricetwo_eliminados_oba,0);
        ArrayResize(pricetwo_eliminados_obb,0);
         
      //---
         return(INIT_SUCCEEDED);
        }


      3.0: Conclusión 

      En este artículo has aprendido a:

      • Crear un indicador basado en Smart Money Concepts e Inner Circle Trader.
      • Configurar alertas.
      • Dibujar rectángulos en el gráfico.

      Nuestro trabajo final:

                 Order-block-example-GIF

      Si has llegado hasta aquí, te agradezco sinceramente por tu entusiasmo y paciencia en el aprendizaje de conceptos más avanzados en trading. La programación ofrece una amplia variedad de posibilidades, desde los conceptos básicos como los máximos y mínimos de un periodo, hasta la creación de robots de trading inteligentes. Te invito a seguir explorando más artículos para avanzar en este fascinante mundo de la programación.

      Me alegraría mucho si compartes este artículo con alguien que pueda necesitarlo.

      Como agradecimiento por tu lectura, he preparado un archivo  que incluye todo el código del indicador que hemos discutido aquí. Además, quiero mencionarte que esta serie no termina, tengo en mente desarrollar más partes. A continuación, te presento el posible índice de futuros artículos:

      Segunda Parte:

      • Integrar buffers y plots para este indicador (buffer de señal de compra y venta).
      • Implementar niveles de take profit (TP) y stop loss (SL) una vez que se active una señal (dos líneas para TP y dos para SL).
      • Introducir un nuevo método avanzado de detección de Order Blocks (basado en el libro de órdenes).

      Si recibo suficiente apoyo, planeo hacer una tercera parte donde crearemos un Asesor Experto (EA) utilizando los buffers del indicador que hemos desarrollado.

      Archivos adjuntos |
      Redes neuronales: así de sencillo (Parte 87): Segmentación de series temporales Redes neuronales: así de sencillo (Parte 87): Segmentación de series temporales
      La previsión juega un papel esencial en el análisis de series temporales. En este nuevo artículo, hablaremos de las ventajas de la segmentación de series temporales.
      Añadimos un LLM personalizado a un robot comercial (Parte 4): Entrena tu propio LLM con GPU Añadimos un LLM personalizado a un robot comercial (Parte 4): Entrena tu propio LLM con GPU
      Con el rápido desarrollo de la inteligencia artificial en la actualidad, los modelos lingüísticos (LLM) son una parte importante de la inteligencia artificial, por lo que deberíamos pensar en cómo integrar potentes LLM en nuestras operaciones algorítmicas. Para la mayoría de la gente, es difícil ajustar estos potentes modelos a sus necesidades, desplegarlos localmente y luego aplicarlos a la negociación algorítmica. Esta serie de artículos abordará paso a paso la consecución de este objetivo.
      Optimización automatizada de parámetros para estrategias de negociación con Python y MQL5 Optimización automatizada de parámetros para estrategias de negociación con Python y MQL5
      Existen varios tipos de algoritmos para la autooptimización de estrategias y parámetros de negociación. Estos algoritmos se utilizan para mejorar automáticamente las estrategias de negociación basándose en datos históricos y actuales del mercado. En este artículo veremos uno de ellos con ejemplos en Python y MQL5.
      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.