English Español Deutsch 日本語 Português
preview
Разработка передовых торговых систем (ПТС): Реализация Order Blocks в индикаторе

Разработка передовых торговых систем (ПТС): Реализация Order Blocks в индикаторе

MetaTrader 5Примеры |
604 18
Niquel Mendoza
Niquel Mendoza


1.0: Введение 

Добро пожаловать, и спасибо, что читаете эту статью. В ней вы узнаете, как разработать индикатор, основанный на теории ордер-блоков Smart Money Concepts и Inner Circle Trader.  

1.1: Основы создания ордер-блоков 

Ордер-блоки, как следует из названия, представляют собой области на графике, где ордера ожидают активации.

Обычно это происходит, когда крупный участник рынка, например финансовое учреждение, хочет войти в рынок со значительной позицией, но ликвидности не хватает для исполнения всего ордера без передвижения рынка. Согласно основному закону спроса и предложения, когда мы исполняем часть своего ордера, цена растет очень быстро (в случае покупок) в поисках продавцов, заинтересованных в предложении ликвидности для завершения сделки.

Поскольку крупный участник не может исполнить весь свой ордер сразу, не вызвав значительного движения цены, он распределяет свои ордера на более мелкие. Таким образом, можно завершить сделку без резкого движения цены до того, как мы закончим позиционирование.

Исходя из данной концепции, мы можем определить эти области на графике, поскольку они являются местами, где возник сильный дисбаланс между спросом и предложением (либо покупкой, либо продажей). Далее мы рассмотрим три способа определения этих областей и как реализовать их в коде.

1.1.1: Логика, основанная на Price Action (базовый уровень) 


Прежде, чем начать объяснение логики работы ордерных блоков, мы расскажем о частях свечи.

Свеча состоит из четырех цен:                    

Цена Описание
  High максимальная цена, которой цена достигла за период, который охватывает свеча.
  Low минимальная цена, достигнутая ценой в свече
  Открытие цена, по которой откроется свеча
  Закрытие цена, по которой закрывается свеча.   

Для лучшего понимания рассмотрим пример на графике.

          OHCL

Начнем с того, что если проанализировать теорию ордер-блоков, то первым важным аспектом, который необходимо выявить, является дисбаланс на рынке. Данный дисбаланс можно увидеть на графике в виде последовательности из нескольких свечей подряд в одном направлении, что указывает на четкий тренд.

Пример: Восходящий тренд с 4 последовательными свечами

В данном случае мы сосредоточимся на восходящем тренде, сформированном 4 последовательными свечами, которые соответствуют следующим правилам:      

Свеча Описание
Предыдущая свеча Данная свеча предшествует восходящему движению 4 последовательных свечей. Как правило, это свеча, которая закрывается ниже начального уровня восходящего движения.
Первая свеча Это указывает на начало восходящего движения. Ее закрытие находится выше открытия предыдущей свечи.
Вторая, третья и четвертая свеча
Это свечи, которые продолжают восходящий импульс, каждая из них закрывается выше закрытия предыдущей свечи.

Правила:
  • Условие восходящего движения: Чтобы движение считалось действительным, все 4 свечи должны быть последовательными бычьими свечами. Первая свеча начинает дисбаланс, а последующие свечи его подтверждают.
  • Распознавание ордер-блоков: Ордер-блок будет формироваться в области предыдущей свечи и первой бычьей свечи, отмечая область, где покупатели взяли контроль.

Ниже приведен пример графика с 4 последовательными бычьими свечами, указывающими на явный дисбаланс в цене.

Simple OB Example

Правила идентификации ордер-блоков по последовательным свечам:

Аспект Бычий ордер-блок Медвежьи ордер-блок
Состояние свеч 1, 2, 3 и 4 последовательные бычьи свечи. Закрываем выше открытия. 1, 2, 3 и 4 последовательные медвежьи свечи. Закрываем ниже открытия.
Проверка конца свечи 2 Минимум свечи 2 выше половины тела свечи 1.
 (Исключение: Молоток)
Высота свечи 2 ниже середины тела свечи 1.
 (Исключение: Молоток)
Состояние тела свечи 2 По меньшей мере 40% тела свечи 2 превышает высоту свечи 1. По меньшей мере 40% тела свечи 2 находится ниже минимума свечи 1.
Проверка конца свечи 3  Минимум свечи 3 выше 25% от тела свечи 2. Высота свечи 3 меньше 25% от высоты свечи 2.
Состояние тела свечи 3 Половина тела свечи 3 превышает высоту свечи 2. Половина тела свечи 3 находится ниже минимума свечи 2.

Цель Правил:

Данные правила разработаны для того, чтобы убедиться, что 4-свечной паттерн достаточно силен для подтверждения ордер-блоков, а также для подтверждения того, что отложенные ордера в этой области не были исполнены ранее.

1.1.2: Логика основанная на Price Action и индикаторы (средний уровень)

В этом более продвинутом подходе мы не только рассматриваем действия цен, но и вводим использование индикаторов для подтверждения силы движения, в данном случае с помощью объема.

Принципы стратегии

Как уже говорилось выше, значительное движение обычно начинается с относительно низкого объема, за которым следует увеличение объема по мере выполнения важных ордеров. Такое увеличение объема обычно продолжается в течение нескольких свечей (2 или 3), подтверждая начало ордер-блоков.

Мы можем рассмотреть данную логику в двух основных случаях:

Случай 1: Ордер-блоки с увеличением объема

В этом сценарии ордер-блок формируется, как только объем начинает значительно увеличиваться. Условия следующие:

  1. Начало движения: Оно начинается со свечи с низким объемом, указывающей на начало накопления.
  2. Увеличение объема: На следующей свече объем существенно увеличивается, что свидетельствует о выполнении ордера. Данный рост может продолжаться в течение 2-3 свечей подряд.
  3. Подтверждение ордер-блока: Ордер-блок формируется в области, где объем начинает расти, а отложенные ордера уже исполнены.

Бычий пример: 

 Bullish Increasing Volume

Медвежий пример:         

Bearish Increasing Volume

Случай 2: Ордер-блок с простым пиком объема

В данном случае ордер-блок формируется, когда на ключевой свече наблюдается значительный пик объема, который мы назовем первой свечой. Условия, подтверждающие ордер-блок, основаны как на поведении цены, так и на анализе объема, а паттерн развивается из 3 последовательных свечей, бычьих или медвежьих.

Правила:

Аспект Бычий ордер-блок Медвежий ордер-блок
Пик объема на свече 1
Свеча 1 должна концентрировать наибольший объем из трех свечей, и ее объем должен быть выше, чем у предыдущей свечи и свечи 2.
Свеча 1 должна концентрировать наибольший объем из трех свечей, и ее объем должен быть выше, чем у предыдущей свечи и свечи 2.
Проверка минимума свечи 2
Минимум свечи 2 должен быть больше половины тела свечи 1, что указывает на то, что область накопления ордер-блока не достигнута.
(Исключение: свеча 1 - это молоток)
Максимум свечи 2 должен быть меньше половины тела свечи 1, что указывает на то, что область накопления ордер-блока не достигнута.
(Исключение: свеча 1 - перевернутый молот)
Условие 60% теласвечи 2 60% тела свечи 2 должно превышать максимум свечи 1, что свидетельствует о продолжении восходящего импульса. 60% тела свечи 2 должно быть ниже минимума свечи 1, что указывает на продолжение медвежьего импульса.
Проверка максимума свечи 3 Максимум свечи 3 должен быть выше цены открытия свечи 2, указывая на то, что восходящее движение остается сильным. Минимум свечи 3 должен быть ниже цены открытия свечи 2, указывая на то, что медвежье движение остается сильным.

Бычий пример: 

Bullish Volume Peak

 Медвежий пример:

Bearish Volume Peak



2.0: Разработка индикатора Order Blocks

2.1: Настройка входных параметров и параметров индикатора


Наконец, после большого количества теории, мы перейдем к программированию всего изученного. 

1. Мы сначала создаем новую программу типа "Пользовательский индикатор":

2. Далее пишем название индикатора и имя автора.

3. Потом выбираем "OnCalculate()" для последующих вычислений.

 4. И щелкнем левой кнопкой мыши на "Завершить".

Пока что мы не совершим настройки.

indicator_buffers 
indicator_plots 

Затем, чтобы избегать ошибку: "no indicator plot defined for indicator00".

В верхней части мы разместим следующее:

#property  indicator_buffers 1
#property  indicator_plots 1

С этим мы должны избавиться от предупреждения.

Для начала мы настроим входные параметры следующим образом:

  • Цвета бычьих и медвежьих ордер-блоков:

    Это позволит нам выбрать цвета, которыми будут обозначены бычьи и медвежьи ордер-блоки на графике, что облегчит визуальную идентификацию каждого из них.

  • Персонализация прямоугольников:

    Также добавим опции для настройки свойств прямоугольников, обозначающих ордер-блоки:

    • Ширина краев: Определяет толщину краев прямоугольника.
    • Фон: Устанавливает, будет ли прямоугольник отображаться за свечами или внахлест.
    • Возможность выделения: Включает или отключает возможность выделения прямоугольников на графике, облегчая взаимодействие.
  • Диапазон поиска для ордер-блоков:

    Параметр, задающий диапазон баров назад от текущей свечи, в котором осуществится поиск ордер-блоков. Данный параметр можно настроить в зависимости от используемой стратегии или таймфрейма.

  • Определение входных параметров и их организация:

    Входные параметры - это параметры, которые могут быть изменены пользователем вне программы, что обеспечивает гибкость в настройке поведения индикатора в соответствии с потребностями пользователя. Однако, чтобы эти параметры стали понятнее, мы используем концепцию:     

    sinput

    Использование вышеупомянутого ключевого слова позволяет нам лучше организовать параметры, сгруппировав их по категориям, с помощью свойства:

    group
  • Это не только улучшает структуру кода, но и облегчает пользователю распознавание различных наборов параметров, связанных с конкретными аспектами индикатора, такими как настройка прямоугольников или анализ ордер-блоков.

    sinput group "--- Order Block Indicator settings ---"
    input          int  Rango_universal_busqueda = 500; // Universal range for searching order blocks
    input          int  Witdth_order_block = 1;         // Width of the order block lines
    
    input          bool Back_order_block = true; // Enable object to be drawn in the background
    input          bool Fill_order_block = true; // Enable fill for the order block rectangle
    
    input          color Color_Order_Block_Bajista = clrRed;   // Assign red color for bearish order block
    input          color Color_Order_Block_Alcista = clrGreen; // Assign green color for bullish order block
    

    2.2: Создание ключевых структур и функций

    В этом разделе мы определим основные структуры и функции для управления ордер-блоков в нашем индикаторе. Это позволит нам хранить и упорядочивать ключевую информацию каждого ордер-блока, а также эффективно управлять данными с помощью динамических массивов.

    1. Переменная для хранения времени последней свечи

    Сначала мы создадим переменную, которая будет хранить время последней обработанной свечи. Это необходимо для того, чтобы избежать дублирования ордер-блоков на одни и те же свечи и обеспечить правильное отслеживание по времени.

    datetime tiempo_ultima_vela;
    
    

    2. Обработчик для индикатора ATR:

    Второй шаг - создание обработчика для индикатора:

    ATR (Average True Range)
    

    Это поможет нам замерить волатильность рынка и дополнит логику индикатора. Данный обработчик инициализируется в самом начале, чтобы его можно было использовать при расчете ордер-блоков.

    int atr_i;
    

    3. Создание структуры для хранения данных ордер-блоков

    Теперь мы создадим структуру, в которой будут храниться соответствующие данные для каждого ордер-блока. Эта структура очень важна, так как содержит информацию о времени, ключевых ценах, названии блока и о том, был ли он смягчен или нет. Кроме того, мы создадим динамический массив, в котором будут храниться все обнаруженные в графе ордер-блоки.

    struct OrderBlock
    {
       datetime time1;      // Time of the candle prior to the first candle of the order block
       double price1;       // Upper price level of the order block (level 1)
       double price2;       // Lower price level of the order block (level 2)
       string name;         // Name of the order block
       bool mitigated;      // Status of the order block (true if mitigated, false if not)
    };
    

    Описание полей структуры

    Структура OrderBlock состоит из следующих полей:

    • time1: В этом поле хранится время свечи, предшествующей первой свече, с которой начинается ордер-блок. Оно полезно чтобы знать, когда был сформирован блок, и проводить временные сравнения.

    • price1: Представляет собой максимальный уровень цены или первую ключевую цену ордер-блока. Это будет максимальный уровень в случае бычьего ордер-блока.

    • price2: Данное поле должно содержать самый низкий уровень цены или вторую ключевую цену ордер-блока. Это нижний уровень в случае бычьего ордер-блока.

    • name: Будет хранить уникальное имя для идентификации ордер-блоков на графике. Данное название будет использоваться для четкой маркировки блока и его визуальной узнаваемости.

    • mitigated: Указывает, был ли смягчен ордер-блок или нет. Если ордер-блок был смягчен (т.е. цена коснулась или превысила уровни блока), это значение будет равно true, в противном случае - false.

    4. Динамический массив для хранения ордер-блоков

    Наконец, мы создадим динамический массив, содержащий все идентифицированные ордер-блоки. Данные массивы позволят нам хранить в памяти несколько блоков и управлять ими динамически, включая или выключая ордер-блоки по мере необходимости с течением времени.

    OrderBlocks ob_bajistas[];
    OrderBlocks ob_alcistas[];
    

    Функция OnInit():

    Функция OnInit() отвечает за инициализацию всех элементов индикатора и выполняет проверку, чтобы убедиться, что всё в порядке, прежде чем индикатор начнет работать. Ниже мы пошагово объясним, что происходит в коде.

    1. Инициализация переменных

    В начале задается начальное значение переменной:

    "tiempo_ultima_vela"

    A 0. Данная переменная важна тем, что в ней будет храниться время последней обработанной свечи, что позволит избежать дублирования и правильно управлять потоком индикатора.

    tiempo_ultima_vela = 0;

    Затем запускается обработчик индикатора:

    "ATR" (Average True Range)

    С периодом 14 свечей. В дальнейшем ATR будет использоваться для измерения волатильности рынка и внесет свой вклад в логику работы ордер-блоков.

    atr_i = iATR(_Symbol, PERIOD_CURRENT, 14);
    
    

    2. Проверка входных параметров

    После инициализации код проверяет, изменилось ли значение переменной:

    Rango_universal_busqueda

    Оно меньше 40. Данная переменная определяет диапазон, в котором индикатор будет искать ордер-блоки. Если этот диапазон слишком мал, это может повлиять на точность и эффективность индикатора, поэтому выводится предупреждающее сообщение и индикатор останавливается, возвращая значение INIT_PARAMETERS_INCORRECT.

    if (Rango_universal_busqueda < 40)
    {
       Print("Search range too small");
       return (INIT_PARAMETERS_INCORRECT);
    }
    

    Данная проверка позволяет убедиться в том, что диапазон поиска имеет соответствующие размеры для правильной работы индикатора, и избежать неправильных настроек, которые могут повлиять на его работу.

    3. Проверка инициализации индикатора ATR

    Следующим шагом будет проверка правильности инициализации обработчика индикатора ATR.

    Если: 

    "atr_i"

    У него есть значение:

    INVALID_HANDLE

    Это означает, что при попытке создать индикатор произошла ошибка, поэтому выводится и возвращается сообщение об ошибке:

    INIT_FAILED

    И это останавливает выполнение индикатора.

    if (atr_i == INVALID_HANDLE)
    {
       Print("Error copying data for indicators");
       return (INIT_FAILED);
    }
    

    4. Изменение в размере динамических массивов

    Затем динамические массивы, в которых хранятся медвежьи и бычьиордер-блоки, изменяют размер, первоначально установив его равным 0. Это гарантирует, что оба массива будут пустыми в начале программы, готовыми к хранению новых ордер-блоков, которые будут обнаружены позже.

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

    Использование динамических массивов очень важно для управления количеством обнаруженных ордер-блоков, поскольку позволяет данным массивам увеличиваться или уменьшаться по мере необходимости.

    Успешное завершение инициализации

    Если все проверки и инициализации завершены без ошибок, то функция:

    OnInit()

    Возвращает:

    INIT_SUCCEEDED

    Это означает, что индикатор правильно инициализирован и готов приступать к торговле на графике.

    return (INIT_SUCCEEDED);

    Полный код:

    int OnInit()
      {
    //--- indicator buffers mapping
       tiempo_ultima_vela = 0;
       atr_i = iATR(_Symbol,PERIOD_CURRENT,14);
    
       if(Rango_universal_busqueda < 40)
         {
          Print("Search range too small");
          return (INIT_PARAMETERS_INCORRECT);
         }
    
       if(atr_i== INVALID_HANDLE)
         {
          Print("Error copying data of indicators");
          return(INIT_FAILED);
         }
    
       ArrayResize(ob_bajistas,0);
       ArrayResize(ob_alcistas,0);
    
    
    
    //---
       return(INIT_SUCCEEDED);
      }
    

    Теперь мы добавим код в событие деинициализации индикатора, чтобы освободить память:

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

    Это будет функция, которую будем использовать позже для удаления прямоугольников, которые мы создадим.

    Проверка новой свечи

    Цель данного кода - оптимизировать работу индикатора, обеспечив его выполнение только при открытии новой свечи, а не при каждом изменении цены. Запуск индикатора при каждом изменении цены привел бы к излишнему расходу ресурсов компьютера, особенно если мы проводим анализ нескольких активов или используем несколько индикаторов.

    Для этого выполняется проверка времени последней обработанной свечи. Если обнаружено появление новой свечи, активируется обработка индикатора. Ниже приводится описание каждой части кода.

    1. Инициализация 

    Во-первых, булевая переменная под названием: 

    "new_vela"

    Это действует как триггер. Ложная инициализация, которая указывает на то, что по умолчанию нового открытия свечи не произошло.

    bool new_vela = false; // We assign the trigger that tells us whether a new candle has opened to false

    2. Проверка новой свечи

    Следующим шагом будет проверка времени последней обработанной свечи:

    tiempo_ultima_vela

    Оно отличается от времени текущей свечи на графике. Функция:

    iTime()

    Возвращает время открытия определенной свечи, в данном случае самой последней свечи (которая индексируется как 0). Если время не совпадает, это означает, что появилась новая свеча.

    if(tiempo_ultima_vela != iTime(_Symbol, PERIOD_CURRENT, 0)) // Check if the current time is different from the stored time
    {
        new_vela = true; // If it doesn't match, set the new candle indicator to true
        tiempo_ultima_vela = iTime(_Symbol, PERIOD_CURRENT, 0); // Update the last processed candle time
    }
    

    Данный блок кода выполняет две задачи:

    1. Проверяет, была ли сгенерирована новая свеча.
    2. Обновляет переменную last_sail_time временем новой свечи для последующего сравнения.

    Выполнение основного кода

    Если переменная: 

    new_vela

    Равна true, это означает, что открылась новая свеча. В этом случае мы можем выполнить основной код индикатора, который обрабатывает ордер-блоки, или любую другую соответствующую логику. Выполняя данную проверку, мы избегаем выполнения кода на каждом ценовом тике, а делаем это только тогда, когда на графике образуется новая свеча.

    if(new_vela == true)
    {
       // Here we will place the main code of the indicator that will run only when a new candlestick opens
    }
    

    Создание массивов для хранения данных о свечах, объемах и ATR

    В данном блоке кода массивы настроены на хранение ключевой информации о свечах, тиковом объеме и ATR. Эти данные необходимы для анализа поведения цен и идентификации ордер-блоков.

    1. Объявление массивов

    Массивы типов double, datetime и long объявлены для хранения соответствующих значений:

    double openArray[];  // Stores Open price of candlesticks
    double closeArray[]; // Stores Close price of candlesticks
    double highArray[];  // Stores High price of candlesticks
    double lowArray[];   // Stores Low price of candlesticks
    datetime Time[];     // Stores the time of each candlestick
    double atr[];        // Stores ATR values, which indicator market volatility
    long Volumen[];      // Stores tick volume, representing the number of transactions in each candlestick
    

    2. Настройка массивов как временных рядов

    Используется функция ArraySetAsSeries(), чтобы заставить массивы работать как временные ряды. Это означает, что индекс 0 будет представлять самую последнюю свечу, что облегчает доступ к последним данным свечей:

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

    3. Копирование данных о свече и ATR

    Затем функции "CopyOpen, CopyClose, CopyHigh, CopyLow, CopyTime и CopyTickVolume" используются для копирования данных о свечах и тиковых объемах в нужные массивы. CopyBuffer также используется для получения значений 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. Обработка ошибок

    При копировании данных об открытии проверяется, не является ли количество скопированных баров отрицательным, что указывает об ошибке. В данном случае для облегчения отладки выводится сообщение об ошибке с помощью функции GetLastError().

    Подготовка к программированию логики обнаружения ордер-блоков

    Прежде, чем реализовать логику обнаружения ордер-блоков, мы осуществим ряд необходимых приготовлений:

    1. Обнаружение предыдущих бычьих свечей: Мы создадим функцию, которая будет определять, есть ли бычьи свечи перед первой свечой в паттерне. Если они будут обнаружены, мы присвоим значение этой первой свечи ближайшей свече, что позволит нам нарисовать ордер-блоки от начала движения.

    2. Рисование прямоугольников: Мы реализуем специальную функцию для рисования прямоугольников, чтобы визуально представить ордер-блоки на графике.

    3. Управление массивами: Мы разработаем функции для добавления обнаруженных ордер-блоков в соответствующие массивы. Это включает в себя:

      • Проверка дубликатов: Функция, позволяющая убедиться в том, что ордер-блок, который мы пытаемся добавить, не был зафиксирован ранее. Таким образом, будут добавлены только новые ордер-блоки.
    4. Смягчение последствий блокировки ордер-блоков: Мы создадим функцию, которая будет проверять, был ли смягчен ордер-блок.

    5. Удаление ордер-блоков: Мы внедрим функцию отметки ордер-блоков как удаленных, что поможет нам поддерживать порядок и чистоту в оповещениях.

    С помощью данных функций мы можем начать добавлять ордер-блоки в массивы и следить за тем, чтобы регистрировались только новые блоки. Начиная с этого момента мы не будем давать построчные объяснения из-за объема кода, а вместо этого приведем краткое описание каждого соответствующего раздела.

    1. Функция

    //+------------------------------------------------------------------+
    //|     Functions to Manage and Add Values to the Arrays             |
    //+------------------------------------------------------------------+
    void AddIndexToArray_alcistas(OrderBlocks &newVela_Order_block_alcista)
      {
       if(!IsDuplicateOrderBlock_alcista(newVela_Order_block_alcista))  // Here we check if the structure we are about to add already exists in the array
         {
          int num_orderblocks_alcista = ArraySize(ob_alcistas);  // We assign the variable "num_orderblocks_alcista" the current size of the ob_alcistas array
          ArrayResize(ob_alcistas, num_orderblocks_alcista + 1); // Resize the array by increasing its size by 1 to make space for a new order block
          ob_alcistas[num_orderblocks_alcista] = newVela_Order_block_alcista; // Assign the new order block to the new index (last position) in the array
         }
      }
    
    bool IsDuplicateOrderBlock_alcista(const OrderBlocks &newBlock)
      {
       for(int i = 0; i < ArraySize(ob_alcistas); i++) //Start a loop to go through all positions of the ob_alcistas array
         {
          if(ob_alcistas[i].time1 == newBlock.time1 &&
             ob_alcistas[i].name == newBlock.name
            ) // Check if both time1 and name of the order block already exist in the array
            {
             return true; // If they do, return true (i.e., it is a duplicate)
             break; // Exit the loop
            }
         }
       return false; // If no duplicate is found, return false
      }
    
    // This would be the same logic but for bearish order blocks
    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
      }
    

    Теперь, когда мы реализовали функции, которые помогут нам проверить, был ли ордер-блок продублирован, и добавить его в динамический массив, пришло время объяснить, как мы будем рисовать эти ордер-блоки на графике.

    Для этого мы будем использовать две ключевые цены:

    • Price 1: В случае бычьего ордер-блока данная цена будет представлять собой основание прямоугольника. Для медвежьего ордер-блока это будет верхняя параллельная сторона.

    • Цена 2: В бычьем ордер-блоке эта цена будет соответствовать верхней параллельной стороне прямоугольника. В медвежьем ордер-блоке это будет основание прямоугольника.

    Бычий пример:

         Bullish OB

     Медвежий пример:

          Bearish Order Block

    Исходя из этого, мы переходим к демонстрации функций для обнаружения смягчений в ордер-блоках.

    //+------------------------------------------------------------------+
    //|             Functions for 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); // Using iBarShift we find the index of the candle by passing the "start" time
       int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end);   // Using iBarShift we find the index of the candle by passing the "end" time
       NormalizeDouble(price,_Digits); // Normalize the price we will work with
    
       for(int i = startIndex - 2 ; i >= endIndex + 1 ; i--) // Start a loop from start (time1 of the order block) to end (time[1])
         {
          //terminated by endIndex which will be time[0] + 1 = time[1]  --> We are searching for mitigation from past to present (backward)
          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]) // Check if OHLC closed below price
            {
             return Time[i]; //If mitigation is found, return the time of the candle where it happened
             Print("el orderblock tuvo mitigaciones", TimeToString(end));
            }
         }
    
       return 0; //If no mitigation was found, return 0
      }
    
    // the same in the bearish case but changing something
    // instead of the price closing below the 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]; // returns the time of the candle found
             Print("el orderblock tuvo mitigaciones", TimeToString(end));
    
            }
         }
    
       return 0; // not mitigated so far
      }
    
    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); // returns the time of the found candle
            }
         }
    
       return 0; // not mitigated so far
      }
    
    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;
             // returns the time of the found candlestick
             return iTime(_Symbol,PERIOD_CURRENT,i);
            }
         }
    
       return 0; // not mitigated so far
      }
    
    

    Данные функции отвечают за проверку состояния ордер-блоков и являются следующими:

    • Функция проверки смягчения: Эта функция проверяет наличие смягчений в ордер-блоках и используется для оценки добавляемой структуры.

    • Функция активации смягчения: Вторая функция, которая включает ключевое слово "array", активирует статус смягчения для ордер-блоков.

    Далее мы рассмотрим функции для рисования прямоугольников и поиска ближайшей бычьей или медвежьей свечи.

    Функция определения ближайшей бычьей или медвежьей свечи очень важна. Его цель - обеспечить правильное распознавание соответствующей свечи при обнаружении ордер-блоков, особенно в ситуациях, когда ордер-блок формируется в начале сильного движения. Это позволит избежать ошибочного обнаружения в середине или в конце движения, что может снизить эффективность анализа.

    Бычий пример:

    Example Function OB+

    Мы запрограммировали данные функции следующим образом:

    //+------------------------------------------------------------------+
    //|  Functions to find the nearest bullish or bearish candle         |
    //+------------------------------------------------------------------+
    int FindFurthestAlcista(datetime start, int numVelas)
      {
       int startVela = iBarShift(_Symbol, PERIOD_CURRENT, start); // Function to find the furthest bullish candle in a consecutive sequence
    // Initialize variables
       int furthestVela = 0;
       int counter_seguidas = 0;
    
       for(int i = startVela  + 1; i <= startVela + numVelas ; i++)  // Since the candle at "start" is already known to be bullish, we skip it (+1)
         {
          //then it is obvious that the candle at time 1 is bullish (in this function), that's why we increase +1, then we check that i is less than or equal to the index of startVela + num candles
          //here num candles would be the number of candles to search for
          double Close = NormalizeDouble(iClose(_Symbol,PERIOD_CURRENT,i),_Digits); //we get the open by index
          double Open = NormalizeDouble(iOpen(_Symbol,PERIOD_CURRENT,i),_Digits);  //we get the close by index
    
          if(Close > Open || Close == Open)  // we check if it's a bullish candle (close > open), that is, the close must be greater than the open
            {
             counter_seguidas++; // if this is true, we increase a variable by 1, which we will use later
            }
          else
             if(Open > Close)
               {
                furthestVela = startVela  + 1 + counter_seguidas; //if the found candle is not bullish, it is obviously bearish, therefore we assign the index of the previous candle to the one that started the bullish move
                // startVela: is the candle we passed, then we add 1 because as we said before, we will draw the order block one candle before the bullish move (i.e., bearish candle)
                // to this we add the counter of consecutive candles
    
                break; // exit the loop
               }
         }
    
    //we check if the body of the candle before the move is more than 30% larger than the candle that starts the move; if it is, we revert to the normal value
       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; // return the index of the found candle
      }
    
    // Function to search for the furthest bearish candle with consecutive bearish candles
    int FindFurthestBajista(datetime start, int numVelas)
      {
       int startVela = iBarShift(_Symbol, PERIOD_CURRENT, start); // Index of the initial candle
       int furthestVela = 0; // Initialize variable
       int counter_seguidas = 0; // Counter of consecutive bearish candles
    
       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);
    
          // If the candle is bearish
          if(Close < Open || Close == Open)
            {
             counter_seguidas++; // Increase the counter of consecutive bearish candles
            }
          // If the candle is bullish, we stop
          else
             if(Close > Open)
               {
                // Return the candle where the bearish sequence is interrupted by a bullish one
                furthestVela = startVela + 1 + counter_seguidas;
                break;
               }
         }
    
       return furthestVela;
      }
    

    Теперь нам осталось создать функцию для рисования прямоугольников:

     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(); // reset the error
    
     // check and create rectangles
       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); //if creation fails, print the function + error code and rectangle name
         }
    
      // set the properties of the rectangles
       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);  
      }
    

    После того, как все эти функции будут готовы, мы перейдем к следующей части.

    2.3: Логика обнаружения ордер-блоков


    Логика, которую мы разработал для данной системы, выглядит следующим образом:

    1. Обнаружение ордер-блоков с помощью логики, которую мы установили в теории.
    2. Присвоение значений структурам. 
    3. Добавление структуры в массив, где будут находиться ордер-блоки.
    4. Проверка ордер-блоков на смягчение.
    5. Рисование ордер-блоков.
    6. Оповещения. 
    Исходя из этого, мы начнем программировать индикатор.

    Сначала мы создадим структуры для хранения значений ордер-блоков:

    • Мы создаем 4 переменные, которые будут иметь вид структуры OrderBlocks.
    • 2 Чтобы сохранить бычьи ордер-блоки (блоки ордеров по индикаторам и Price Action).
    • 2 Чтобы сохранить медвежьи ордер-блоки (блоки ордеров по индикаторам и Price Action)..

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

    Добавив данные структуры, мы уже имеем переменные, в которых будут храниться значения ордер-блоков.

    Теперь мы только используем логику для обнаружения ордер-блоков. Итак, начинаем.

    Мы начнем с логики:

    • Нам нужно будет искать ордер-блоки в диапазоне свечей и присваивать им индекс, что аналогично поиску свечного паттерна с условиями, которые мы задали в логике.
    • Для этого мы будем использовать for.

    Давайте программировать это:   

    for(int i = Rango_universal_busqueda  ; i  > 5  ; i--)
       {
    
    //checking errors
    if(i + 3> ArraySize(highArray)  || i + 3 > ArraySize(atr))
    continue;
    if(i < 0)
    continue;
    
    //--------Variable Declaration--------------------------------------------//
    
    // Update candle indices
    int one_vela = i ; // central candlestick
    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);
    
    // Calculate average body size of previous candles
    double body1 = closeArray[one_vela] - openArray[one_vela];
    double body2 = closeArray[two_vela] - openArray[two_vela];
    double body3 = closeArray[tree_vela] - openArray[two_vela];
    
    // Volume condition
    long Volumen_one_vela = Volumen[one_vela];
    long Volumen_two_vela = Volumen[two_vela];
    long volumen_vela_atras_one = Volumen[vela_atras_one];
    

    • По сути, в этом коде мы создаем цикл, который будет двигаться от максимального значения свечи, а оно будет:

    (Rango_universal_busqueda)

    и завершается на индексе 6:

     i  > 5

    в случае i > 5 

    Вычтем значение i на 1.

    • Мы значительно нормализуем OHCL свечей, с которыми будем работать.
    • Потом мы назначаем тело свечи так: close - open.
    • Мы получаем значение тика только для случая пика:

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

    Теперь мы добавим еще один случай, это atr; в основном с attr мы ищем, чтобы цена сильно двигалась в одном направлении.  

    • В данном фрагменте кода, который показан ниже, можно увидеть основные логические условия и с индикатором atr:
    //Boolean variables to detect if the case is met (only Price Action)
    bool esVelaCorrecta_case_normal =false;
    bool  esVela_Martillo = false;
    
    //Here we check that 4 consecutive bullish candles have formed with 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; // if true, assign true to "esVelaCorrecta_case_normal"
      }
    else
       esVelaCorrecta_case_normal =false; // otherwise assign false
    
    
    bool fuerte_movimiento_alcista =false; // create a variable that activates only if a strong bullish movement occurs
    
    // Check if a movement of 6 consecutive bullish candles was created
    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; // if true assign true to "fuerte_movimiento_alcista"
      }
    
    //verificamos si es vela martillo
    if(openArray[one_vela] - lowArray[one_vela] > closeArray[one_vela] - openArray[one_vela]) // check if lower wick is larger than the candle body
      {
       esVela_Martillo = true; // if so set "esVela_Martillo" to 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;  // in this code we look for ATR to first fall in one candle
    //then rise, and candles 1, 3, 4 must be bullish; second candle not necessary for this case
    
    //Verification for normal case
    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)  
      {
       int furthestAlcista = FindFurthestAlcista(Time[one_vela],20); // call function to find previous bullish candles before "one_vela"
       if(furthestAlcista > 0) // whether or not found, will be > 0 since it returns previous candle index if none found
         {
    
          datetime time1 = Time[furthestAlcista];     //assign time of furthestAlcista candle to time1
          double price2 = openArray[furthestAlcista]; //assign open of furthestAlcista as price2 (usually drawn on a bearish candle)
          double price1 = lowArray[furthestAlcista];  //assign low of furthestAlcista as price1
    
          //assign mentioned variables to the structure
          newVela_Order_block_alcista.price1 = price1;
          newVela_Order_block_alcista.time1 = time1;
          newVela_Order_block_alcista.price2 = price2;
    
          case_OrderBlockAlcista_normal = true; //if all true, activate normal bullish case
         }
       else
          case_OrderBlockAlcista_normal =false;
    
      }
    //versión bajista
    
    bool case_OrderBlockBajista_normal = false;
    bool case_OrderBlockBajista_volumen = false;
    
    //---------------Conditions for Order Blocks--------------------//
    //+------------------------------------------------------------------+
    //| Conditions For Bearish Order Block 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;
      }
    
    // Verification for normal bearish case
    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;
      }
    //+------------------------------------------------------------------+
    

    Давайте расскажем о каждой функции с соответствующими комментариями. Одним словом, мы стремимся выявить определенные закономерности, и когда мы их находим, то активируем булевы переменные.

    Далее мы проверяем наличие бычьих свечей перед one_candle, которая является свечой, инициирующей движение.

    Наконец, мы присваиваем ордер-блокам значения цены и времени.

    Сейчас перейдем к случаю с объемом, где мы рассмотрим как пики объема, так и растущий объем.

    //condition orderblock volume --------------------------------//
    if(Volumen_one_vela  > Volumen_two_vela && Volumen_one_vela > volumen_vela_atras_one)
      {
       VolumenCorrecto = true; //here we check the volume peak
      }
    else
       VolumenCorrecto = false;
    
    //so that the bullish candle behind is bearish and 2 bullish
    if(closeArray[one_vela] > openArray[one_vela]  &&
       closeArray[two_vela] > openArray[two_vela])
      {
       VelaCorrecta_casevolumen = true;
      }
    
    //consecutive case
    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;
    
    //here we verify that the highlights do not mitigate the 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)
      {
    //I already explained all this above, it is literally the same, we look for the closest bullish trend and assign a value to the one before it
       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;
    
      }
    //Bearish version
    //+------------------------------------------------------------------+
    
    //+------------------------------------------------------------------+
    //| Condition for Bullish Order Block Case case_Volumen              |
    //+------------------------------------------------------------------+
    
    bool VelaCorrecta_casevolumen = false;
    bool VolumenCorrecto;
    //condition orderblock volume --------------------------------//
    //by peak volume
    if(Volumen_one_vela  > Volumen_two_vela && Volumen_one_vela > volumen_vela_atras_one)
      {
       VolumenCorrecto = true;
      }
    else
       VolumenCorrecto = false;
    //we look here for 2 consecutive bearish candles
    if(closeArray[one_vela] < openArray[one_vela]  &&
       closeArray[two_vela] < openArray[two_vela])
      {
    
       VelaCorrecta_casevolumen = true; //we set the variable "VelaCorrecta_casevolumen" to true
      }
    //we look for an increasing volume in addition to the 3rd candle and 4th candle being bearish
    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
      {
    // the peak volume case or increasing volume case
       int furthestBajista = FindFurthestBajista(Time[one_vela],20); //we look for the bearish candle closest to the 1st candle
    
       if(furthestBajista > 0)
         {
          //if this is true, which as I said before it will always be, we assign the candle values
          //to the structure variables to draw the rectangles
          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;
      }
    //+------------------------------------------------------------------+
    
    

    Теперь, когда мы реализовали распознавание ордер-блоков, необходимо добавить их в массив, чтобы впоследствии их можно было нарисовать.

    В данном коде мы выполним следующие действия:

    1. Мы инициализируем переменную mitigated как false.
    2. Мы присваиваем имя ордер-блока по его типу, а также время one_sail.
    3. Наконец, мы добавляем ордер-блок в динамический массив.
    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)  //we verify that the order block has not been mitigated
      {
       newVela_Order_block_alcista.mitigated = false; //we activate the order block status as unmitigated = false
       newVela_Order_block_alcista.name =  "Order Block Alcista normal" + TimeToString(newVela_Order_block_alcista.time1) ;  //we assign the name "Normal Bullish Order Block" + the time of one_Vela
       AddIndexToArray_alcistas(newVela_Order_block_alcista); //we add to the array to then check if they are being mitigated and draw them
    
      }
    //the same would be for the volume case
    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);
    
      }
    
      } 
    
    //--- Bearish version
    
    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
      ) //we check if the bearish order block was not mitigated and the normal case is true
      {
       newVela_Order_Block_bajista.mitigated = false; //we initialize the state of the order block as unmitigated = false
       newVela_Order_Block_bajista.name = ("Order Block Bajista ")+ TimeToString(newVela_Order_Block_bajista.time1) ; //we assign the name as "Bearish Block Order" + the time of the 1st candle
       AddIndexToArray_bajistas(newVela_Order_Block_bajista); //we add the structure to the 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
      )//we check if the bearish order block was not mitigated and the volume case is true
      {
       newVela_Order_Block_bajista_2.mitigated = false; //we initialize the state of the order block as unmitigated = false
       newVela_Order_Block_bajista_2.name = ("Order Block Bajista ") + TimeToString(newVela_Order_Block_bajista_2.time1) ; //we assign the name as "Bearish Block Order" + the time of the 1st candle
       AddIndexToArray_bajistas(newVela_Order_Block_bajista_2); //we add the structure to the array
    
      }
      } 
    //+------------------------------------------------------------------+
    

    Теперь перейдем к рисованию и проверке смягчения.

    2.4: Визуализация: Цвета и Проверка смягчения ордер-блоков


    В этой части мы рассмотрим, как обновить ордер-блоки, нарисовать их и активировать их смягченное состояние.

    •  Мы будем рисовать их, перебирая "ob_bullish" и "ob_bearish", эти массивы, как мы уже говорили, будут хранить информацию об ордер-блоках.
    •  Мы переместим объекты с помощью "ObjectMove", так как мы не хотим перерисовывать всё заново, поскольку это снижает эффективность программы, а также потребляет больше ресурсов компьютера.

    Теперь, когда мы прошли через всё это, давайте посмотрим на код, который я подготовил для выполнения этих требований:

    for(int i = 0; i < ArraySize(ob_alcistas); i++) //We iterate through all the indexes of the array where the order blocks information is stored
      {
       datetime mitigadoTime = esOb_mitigado_array_alcista(ob_alcistas[i],ob_alcistas[i].time1); //we call the function that will tell us if index i has been mitigated or not. If it is, we activate its state to true
    
       if(ob_alcistas[i].mitigated == false)  //we verify that it has not been mitigated
         {
          if(mitigadoTime == 0) //We condition that the order block has not been touched by the price
            {
    
             if(ObjectFind(ChartID(),ob_alcistas[i].name) < 0) //we check if the object exists in the graph with 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); //  we create the rectangle with the data
    
               }
             else
                ObjectMove(ChartID(),ob_alcistas[i].name,1,Time[0],ob_alcistas[i].price2);     //on the contrary, if the object exists, the only thing we will do is update it to the current time using anchor point 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);
    
            }
         }
    
      }
    //+------------------------------------------------------------------+
    
    • Следует отметить, что обнаружение происходит внутри for, а он, в свою очередь, внутри условия:
    new_vela == true
    • Рисование прямоугольников выполняется вне for, но внутри условия:
    new_vela == true

    2.5: Реализация оповещений для смягчения ордер-блоков и удаления объектов


    В этом разделе мы рассмотрим, как реализовать оповещения при смягчении ордер-блоков, и создадим функцию, о которой мы упоминали в начале:

    Eliminar_Objetos()

    Начнем с определения логики:

    1. Нам нужно будет вызвать следующие функции:

    esOb_mitigado_array_bajista  

    esOb_mitigado_array_alcista

    При обнаружении, если ордер-блок был смягчен, мы возвращаем время смягчающей свечи, а также устанавливаем статус ордер-блока в true, что равносильно тому, что ордер-блок был смягчен.

    Поэтому, чтобы узнать, смягчен ордер-блок или нет, мы будем использовать его состояние:

    mitigated

    Теперь, глядя на структуру ордер-блока, мы видим, что он имеет свою цену, свое время, свое состояние и свое имя:

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

     Из этой структуры нас особенно интересуют 2 переменные для оповещений:

     string name;
     bool mitigated;
    

    • mitigated: с помощью этой булевой переменной мы узнаем, был ли смягчен ордер-блок.
    • name: с помощью данного параметра мы проверим, не был ли ранее смягчен ордер-блок, который был смягчен.
    Помните, что после активации "mitigated" ордер-блок всегда будет смягчаться, поэтому нам необходимо иметь фильтр, чтобы индикатор не показывал оповещений время от времени: данный фильтр будет названием ордер-блока.

    Однако сейчас нам этого не хватает:

    • Что касается 2 массивов, они будут строкового типа, а также динамическими, так как мы будем обновлять их размер по мере уменьшения количества ордер-блоков.
    • Функция для добавления к строковым массивам.
    • Функция проверяет, есть ли строка, которую мы передаем в качестве имени ордер-блока, в массиве.

    Я интегрировал недостающие функции и массивы:

    • Мы перейдем к глобальной части программы, где напишем:

    string pricetwo_eliminados_oba[];
    string pricetwo_eliminados_obb[];
    

    Это массивы, которые нам понадобятся.

    Затем мы создадим следующие функции:

      bool Es_Eliminado_PriceTwo(string pName_ob , string &pArray_price_two_eliminados[])
      {
       bool a = false; // we create the variable "a" and initialize it to false
         for(int i = 0 ; i < ArraySize(pArray_price_two_eliminados) ; i++) // we traverse all the indices of the array passed as a reference
         {
          if(pName_ob == pArray_price_two_eliminados[i]) // we will compare all the positions in the array with the variable "pName_ob"
          { // if the comparison is identical the variable "a" becomes true
           a = true; 
            break; // we exit the loop
          } 
         } 
      return a; //we return the value of "a"
     }   
     
     //function to add values and assign a new size to the array passed by reference 
     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");
        }
    }
    
    • Данные функции помогут нам проверить время смягчения ордер-блока (если ордер-блок был уже смягчен ранее), чтобы не рассылать оповещений массово.

    Теперь мы переходим к части внутри OnCalculate(), чтобы завершить программирование оповещений:

    • Начнем с создания цикла, который будет перебирать все индексы массива ордер-блоков.
    • С помощью if(), мы проверяем состояние ордер-блока, а также проверяем, что имя ордер-блока НЕ находится в строке массива, где хранятся имена ордер-блоков.
    • Если всё это так, мы сообщаем пользователю о том, что ордер-блок был удален.
    • Мы добавляем имя ордер-блока в строковой массив, чтобы избежать повторений.
    • И завершаем цикл с помощью break.
    // Loop through the order blocks
    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;
         }
      }
    
    // Loop through the order blocks
    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;
         }
      }
    //+------------------------------------------------------------------+
    

    Теперь, когда мы закончили с оповещениями, давайте перейдем к удалению объектов, изучив документацию:

    bool  ObjectDelete(
       long    chart_id,     // chart identifier
       string  name          // object name
       );
    

    Затем нам понадобится идентификатор текущего графика:

    ChartID()

    Имя ордер-блока:

    name

    Учитывая всё это, нам нужно будет пройтись циклом по всем позициям наших ордер-блоков, а затем вызвать:

    ObjectDelete() 

    Чтобы удалить все созданные нами объекты:

      
      void Eliminar_Objetos()
      {
      
      for(int i = 0 ; i < ArraySize(ob_alcistas) ; i++) // we iterate through the array of bullish order blocks 
      {
       ObjectDelete(ChartID(),ob_alcistas[i].name); // we delete the object using the name of the order block
      }
      for(int n = 0 ; n < ArraySize(ob_bajistas) ; n++) // we iterate through the array of bearish order blocks 
      {
      ObjectDelete(ChartID(),ob_bajistas[n].name);  // we delete the object using the name of the order block
      }
      
     }
    

    На этом мы закончили работу над индикатором, но нам ещё нужно изменить функции:

    OnInit() y OnDeinit()

    Чтобы правильно обрабатывать новые переменные и массивы, которые мы добавили.

    В: 

    OnDeinit()

    Мы позаботимся о том, чтобы освободить ресурсы, используемые индикатором, удалив графические объекты и освободив память динамических массивов, в которых хранятся данные ордер-блоков.

    Кроме того, важно убедиться в том, что обработчик ATR правильно освобожден, если мы правильно его инициализировали, чтобы избежать утечек памяти или ошибок при закрытии индикатора. Мы сделаем это с помощью:

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

    Итоговая реализация:

    OnDeinit()

    Будет выглядеть так:

     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("Search range too small");
          return (INIT_PARAMETERS_INCORRECT);
         }  
         
          if( atr_i== INVALID_HANDLE)
         {
          Print("Error copying data for indicators");  
          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: Заключение 

    В этой статье мы научились:

    • Создавать индикатор на основе концепций Smart Money Concepts и Inner Circle Trader.
    • Настроить оповещения.
    • Нарисовать прямоугольники на графике.

    Наша конечная работа:

               Order-block-example-GIF

    Если вы дошли до этого момента, я искренне благодарю вас за энтузиазм и терпение в изучении более продвинутых концепций торговли. Программирование предлагает широкий спектр возможностей, от базовых понятий, таких как максимумы и минимумы периодов, до создания умных торговых роботов. Я приглашаю вас прочитать и другие статьи, чтобы продвинуться в увлекательном мире программирования.

    Я буду очень рад, если вы поделитесь этой статьей с коллегами, если она им может понадобиться.

    В качестве благодарности за чтение я подготовил файл, который включает весь код изученного индикатора. Также хочу отметить, что данная серия не закончена, в планах у меня есть еще несколько частей. Вот возможное оглавление для будущих статей:

    Часть вторая:

    • Интеграция буферов и графиков для этого индикатора (сигнальный буфер на покупку и продажу).
    • Установка уровней тейк-профита (TP) и стоп-лосса (SL) после срабатывания сигнала (две линии для TP и две для SL).
    • Внедрение нового усовершенствованного метода обнаружения ордер-блоков (на основе книги заявок).

    Если я получу достаточную поддержку, планирую написать третью часть, в которой мы создадим советник (EA) с помощью разработанных нами индикаторных буферов.

    Перевод с испанского произведен MetaQuotes Ltd.
    Оригинальная статья: https://www.mql5.com/es/articles/15899

    Прикрепленные файлы |
    Последние комментарии | Перейти к обсуждению на форуме трейдеров (18)
    CapeCoddah
    CapeCoddah | 14 июл. 2025 в 10:56

    Здравствуйте,

    Я только что тестировал своего советника в тестере стратегий, EURUSD H4 1/1/2025-2//1/2025, и заметил в конце прогона две проблемы с индикатором Block Order.

    Во-первых, он выбрал блочный ордер на 2/3/2025, который находится за пределами тестового окна, а во-вторых, он поместил текст блока в область Chart Shift.


    Наслаждайтесь


    CapeCoddah

    CapeCoddah
    CapeCoddah | 16 июл. 2025 в 10:34

    Здесь представлена переведенная на английский язык версия вашего первого индикатора. Я решил, что мне нужно понять ваши многочисленные комментарии к коду на английском языке, и пересмотрел Google Translate, поскольку DeepL не произвел на меня впечатления. Сначала я заменил все //-комментарии на #/#, чтобы Google мог перевести //-комментарии к строкам... а затем преобразовал текстовый файл в документ MS Word для ввода в Translate.После перевода я открыл новый документ, сохранил его как текстовый файл, переименовал его и начал синтаксическую обработку нового источника. По моим оценкам, Translate сделал 90 % работы, но он добавил пробелы и символы, которые требовали ручного преобразования. После нескольких дней работы он скомпилировался без ошибок. Удивительно, но он сработал с первой попытки! Я сравнил его с вашим оригинальным индикатором на 1000 баров, и они были идентичны.

    Niquel Mendoza
    Niquel Mendoza | 16 июл. 2025 в 15:07
    CapeCoddah # :

    Здесь представлена переведенная на английский язык версия вашего первого индикатора. Я решил, что мне нужно понять ваши многочисленные комментарии к коду на английском языке, и пересмотрел Google Translate, поскольку DeepL не произвел на меня впечатления. Сначала я заменил все //-комментарии на #/#, чтобы Google мог перевести //-комментарии к строкам... а затем преобразовал текстовый файл в документ MS Word для ввода в Translate.После перевода я открыл новый документ, сохранил его как текстовый файл, переименовал его и начал синтаксическую обработку нового источника. По моим оценкам, Translate сделал 90 % работы, но он добавил пробелы и символы, которые требовали ручного преобразования. После нескольких дней работы он скомпилировался без ошибок. Удивительно, но он сработал с первой попытки! Я сравнил его с вашим оригинальным индикатором на 1000 баров, и они были идентичны.

    Привет, CapeCoddah, я думаю, что код, который вы создали, был превосходным. Извините, что не ответил раньше; я занят в нескольких проектах, что ограничивает мою доступность для помощи вам. Однако сегодня я могу посвятить некоторое время работе над улучшенной версией индикатора. Я делюсь кодом ниже.
    CapeCoddah
    CapeCoddah | 18 июл. 2025 в 12:48

    К сожалению, похоже, что ваш индикатор структурно несовершенен и бесполезен для торговли, поскольку он выполняет свои расчеты на будущих переменных, которые неизвестны на момент расчета, как выделено в коде ниже жирным шрифтом.

    for( int i = Universal_search_range ; i > 5 ; i--) {
    //проверка ошибок
    if( i + 3 > ArraySize(highArray) || i + 3 > ArraySize(atr))
    continue ;
    if( i < 0) continue;

    // Обновление индексов свечей
    one_candle = i ; //центральная свеча
    candle_behind_two = i +2;
    candle_behind_one = one_candle +1;
    две_свечи = одна_свеча - 1;
    три_свечи = одна_свеча - 2;
    four_candle = one_candle -3
    ;

    // Рассчитываем средний объем предыдущих свечей
    body1 = MathAbs(closeArray[one_candle] - openArray[one_candle]);
    body2 = MathAbs(closeArray[two_candle] - openArray[two_candle]);
    body3 = MathAbs(closeArray[three_candle] - openArray[three_candle]);

    Niquel Mendoza
    Niquel Mendoza | 27 июл. 2025 в 13:23
    CapeCoddah #:

    К сожалению, похоже, что ваш индикатор структурно несовершенен и бесполезен для торговли, поскольку он выполняет свои расчеты на будущих переменных, которые неизвестны на момент расчета, как выделено в коде ниже жирным шрифтом.

    for( int i = Universal_search_range ; i > 5 ; i--) {
    //проверка ошибок
    if( i + 3 > ArraySize(highArray) || i + 3 > ArraySize(atr))
    continue ;
    if( i < 0) continue;

    // Обновление индексов свечей
    one_candle = i ; //центральная свеча
    candle_behind_two = i +2;
    candle_behind_one = one_candle +1;
    две_свечи = одна_свеча - 1;
    три_свечи = одна_свеча - 2;
    four_candle = one_candle -3
    ;

    // Рассчитываем средний объем предыдущих свечей
    body1 = MathAbs(closeArray[one_candle] - openArray[one_candle]);
    body2 = MathAbs(closeArray[two_candle] - openArray[two_candle]);
    body3 = MathAbs(closeArray[three_candle] - openArray[three_candle]);

    Здравствуйте CapeCoddah, я думаю, что это не так, поскольку, например, индикатор делает все вычисления, используя массивы последовательно (хотя это не распространено, они обычно делаются без серии) в контексте первого показанного цикла, этот цикл используется для обнаружения блоков ордеров, что делается, так это переход от свечи "Universal_search_range" (помните, что в серии свеча 0 является самой последней) к свече 6, поэтому я не вижу, чтобы использовались будущие свечи, и если бы это было так, то two_candle или другой индекс привели бы к значению меньше 0, что привело бы к выходу из диапазона. Таким образом, свеча four_candle = one_candle - 3 будет наиболее близка к 0 в случае, если цикл заканчивается там, где i = 6, тогда four_candle = 3, поэтому, принимая во внимание, что текущая свеча равна 0, я могу сказать, что ни в какое время я не использую будущие свечи. Название может показаться запутанным, я знаю, но я сделал его таким, потому что мне так было проще понять, учитывая, что когда дело доходило до получения блоков ордеров, one_vela была похожа на центральную свечу. Таким образом, если я искал сильное движение, я оценивал свечи, которые следовали за ним (в серии это было бы вычитанием).

    Поэтапный отбор признаков на MQL5 Поэтапный отбор признаков на MQL5
    В этой статье мы представляем модифицированную версию поэтапного отбора признаков, реализованную в MQL5. Настоящий подход основан на методах, описанных Тимоти Мастерсом (Timothy Masters) в работе "Современных алгоритмах интеллектуального анализа данных на C++" и "CUDA C".
    Анализ нескольких символов с помощью Python и MQL5 (Часть II): Анализ главных компонентов для оптимизации портфеля Анализ нескольких символов с помощью Python и MQL5 (Часть II): Анализ главных компонентов для оптимизации портфеля
    Управление рисками торгового счета является сложной задачей для всех трейдеров. Можем ли мы разработать торговые приложения, которые динамически изучают режимы высокого, среднего и низкого риска для различных символов в MetaTrader 5? Используя PCA, мы получаем лучший контроль над дисперсией портфеля. Я продемонстрирую, как создавать приложения, которые изучают эти три режима риска на основе рыночных данных, полученных из MetaTrader 5.
    Генеративно-состязательные сети (GAN) для синтетических данных в сфере финансового моделирования (Часть 1): Введение в GAN и синтетические данные в сфере финансового моделирования Генеративно-состязательные сети (GAN) для синтетических данных в сфере финансового моделирования (Часть 1): Введение в GAN и синтетические данные в сфере финансового моделирования
    Настоящая статья знакомит трейдеров с Генеративно-состязательными сетями (GAN) для генерации Синтетических финансовых данных, устраняя ограничения данных в процессе обучения модели. В ней рассматриваются основы GAN, реализация кода на python и MQL5, а также практическое применение в финансовой сфере, позволяющее трейдерам повысить точность и надежность моделей с помощью синтетических данных.
    Создание торговой панели администратора на MQL5 (Часть VI): Мультифункциональный интерфейс (I) Создание торговой панели администратора на MQL5 (Часть VI): Мультифункциональный интерфейс (I)
    Роль администратора выходит за рамки простого общения в Telegram; он также может заниматься различными видами контроля, включая управление ордерами, отслеживание позиций и настройку интерфейса. В этой статье мы поделимся практическими советами по расширению нашей программы для поддержки множества функций в MQL5. Это обновление направлено на преодоление ограничений текущей панели администратора, которая в первую очередь сосредоточена на общении.