
Разработка передовых торговых систем (ПТС): Реализация Order Blocks в индикаторе
- 1.1: Основы создания ордер-блоков
- 1.1.1: Логика, основанная на Price Action (базовый уровень)
- 1.1.2: Логика основанная на Price Action и индикаторы (средний уровень)
- 2.0: Разработка индикатора Order Blocks
- 2.1: Настройка входных параметров и параметров индикатора
- 2.2: Создание ключевых структур и функций
- 2.3: Логика обнаружения ордер-блоков
- 2.4: Визуализация: Цвета и проверка смягчения ордер-блоков
- 2.5: Реализация оповещений для смягчения ордер-блоков и удаления объектов
1.0: Введение
Добро пожаловать, и спасибо, что читаете эту статью. В ней вы узнаете, как разработать индикатор, основанный на теории ордер-блоков Smart Money Concepts и Inner Circle Trader.
1.1: Основы создания ордер-блоков
Ордер-блоки, как следует из названия, представляют собой области на графике, где ордера ожидают активации.
Обычно это происходит, когда крупный участник рынка, например финансовое учреждение, хочет войти в рынок со значительной позицией, но ликвидности не хватает для исполнения всего ордера без передвижения рынка. Согласно основному закону спроса и предложения, когда мы исполняем часть своего ордера, цена растет очень быстро (в случае покупок) в поисках продавцов, заинтересованных в предложении ликвидности для завершения сделки.
Поскольку крупный участник не может исполнить весь свой ордер сразу, не вызвав значительного движения цены, он распределяет свои ордера на более мелкие. Таким образом, можно завершить сделку без резкого движения цены до того, как мы закончим позиционирование.
Исходя из данной концепции, мы можем определить эти области на графике, поскольку они являются местами, где возник сильный дисбаланс между спросом и предложением (либо покупкой, либо продажей). Далее мы рассмотрим три способа определения этих областей и как реализовать их в коде.
1.1.1: Логика, основанная на Price Action (базовый уровень)
Прежде, чем начать объяснение логики работы ордерных блоков, мы расскажем о частях свечи.
Свеча состоит из четырех цен:
Цена | Описание |
---|---|
High | максимальная цена, которой цена достигла за период, который охватывает свеча. |
Low | минимальная цена, достигнутая ценой в свече |
Открытие | цена, по которой откроется свеча |
Закрытие | цена, по которой закрывается свеча. |
Для лучшего понимания рассмотрим пример на графике.
Начнем с того, что если проанализировать теорию ордер-блоков, то первым важным аспектом, который необходимо выявить, является дисбаланс на рынке. Данный дисбаланс можно увидеть на графике в виде последовательности из нескольких свечей подряд в одном направлении, что указывает на четкий тренд.
Пример: Восходящий тренд с 4 последовательными свечами
В данном случае мы сосредоточимся на восходящем тренде, сформированном 4 последовательными свечами, которые соответствуют следующим правилам:
Свеча | Описание |
---|---|
Предыдущая свеча | Данная свеча предшествует восходящему движению 4 последовательных свечей. Как правило, это свеча, которая закрывается ниже начального уровня восходящего движения. |
Первая свеча | Это указывает на начало восходящего движения. Ее закрытие находится выше открытия предыдущей свечи. |
Вторая, третья и четвертая свеча | Это свечи, которые продолжают восходящий импульс, каждая из них закрывается выше закрытия предыдущей свечи. |
- Условие восходящего движения: Чтобы движение считалось действительным, все 4 свечи должны быть последовательными бычьими свечами. Первая свеча начинает дисбаланс, а последующие свечи его подтверждают.
- Распознавание ордер-блоков: Ордер-блок будет формироваться в области предыдущей свечи и первой бычьей свечи, отмечая область, где покупатели взяли контроль.
Ниже приведен пример графика с 4 последовательными бычьими свечами, указывающими на явный дисбаланс в цене.
Правила идентификации ордер-блоков по последовательным свечам:
Аспект | Бычий ордер-блок | Медвежьи ордер-блок |
---|---|---|
Состояние свеч | 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: Ордер-блоки с увеличением объема
В этом сценарии ордер-блок формируется, как только объем начинает значительно увеличиваться. Условия следующие:
- Начало движения: Оно начинается со свечи с низким объемом, указывающей на начало накопления.
- Увеличение объема: На следующей свече объем существенно увеличивается, что свидетельствует о выполнении ордера. Данный рост может продолжаться в течение 2-3 свечей подряд.
- Подтверждение ордер-блока: Ордер-блок формируется в области, где объем начинает расти, а отложенные ордера уже исполнены.
Бычий пример:
Медвежий пример:
Случай 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, указывая на то, что медвежье движение остается сильным. |
Бычий пример:
Медвежий пример:
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 }
Данный блок кода выполняет две задачи:
- Проверяет, была ли сгенерирована новая свеча.
- Обновляет переменную 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. Функция
//+------------------------------------------------------------------+ //| 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: В бычьем ордер-блоке эта цена будет соответствовать верхней параллельной стороне прямоугольника. В медвежьем ордер-блоке это будет основание прямоугольника.
Бычий пример:
Медвежий пример:
Исходя из этого, мы переходим к демонстрации функций для обнаружения смягчений в ордер-блоках.
//+------------------------------------------------------------------+ //| 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", активирует статус смягчения для ордер-блоков.
Далее мы рассмотрим функции для рисования прямоугольников и поиска ближайшей бычьей или медвежьей свечи.
Функция определения ближайшей бычьей или медвежьей свечи очень важна. Его цель - обеспечить правильное распознавание соответствующей свечи при обнаружении ордер-блоков, особенно в ситуациях, когда ордер-блок формируется в начале сильного движения. Это позволит избежать ошибочного обнаружения в середине или в конце движения, что может снизить эффективность анализа.
Бычий пример:
Мы запрограммировали данные функции следующим образом:
//+------------------------------------------------------------------+ //| 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: Логика обнаружения ордер-блоков
Логика, которую мы разработал для данной системы, выглядит следующим образом:
- Обнаружение ордер-блоков с помощью логики, которую мы установили в теории.
- Присвоение значений структурам.
- Добавление структуры в массив, где будут находиться ордер-блоки.
- Проверка ордер-блоков на смягчение.
- Рисование ордер-блоков.
- Оповещения.
Сначала мы создадим структуры для хранения значений ордер-блоков:
- Мы создаем 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; } //+------------------------------------------------------------------+
Теперь, когда мы реализовали распознавание ордер-блоков, необходимо добавить их в массив, чтобы впоследствии их можно было нарисовать.
В данном коде мы выполним следующие действия:
- Мы инициализируем переменную mitigated как false.
- Мы присваиваем имя ордер-блока по его типу, а также время one_sail.
- Наконец, мы добавляем ордер-блок в динамический массив.
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()
Начнем с определения логики:
- Нам нужно будет вызвать следующие функции:
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: с помощью данного параметра мы проверим, не был ли ранее смягчен ордер-блок, который был смягчен.
Однако сейчас нам этого не хватает:
- Что касается 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.
- Настроить оповещения.
- Нарисовать прямоугольники на графике.
Наша конечная работа:
Если вы дошли до этого момента, я искренне благодарю вас за энтузиазм и терпение в изучении более продвинутых концепций торговли. Программирование предлагает широкий спектр возможностей, от базовых понятий, таких как максимумы и минимумы периодов, до создания умных торговых роботов. Я приглашаю вас прочитать и другие статьи, чтобы продвинуться в увлекательном мире программирования.
Я буду очень рад, если вы поделитесь этой статьей с коллегами, если она им может понадобиться.
В качестве благодарности за чтение я подготовил файл, который включает весь код изученного индикатора. Также хочу отметить, что данная серия не закончена, в планах у меня есть еще несколько частей. Вот возможное оглавление для будущих статей:
Часть вторая:
- Интеграция буферов и графиков для этого индикатора (сигнальный буфер на покупку и продажу).
- Установка уровней тейк-профита (TP) и стоп-лосса (SL) после срабатывания сигнала (две линии для TP и две для SL).
- Внедрение нового усовершенствованного метода обнаружения ордер-блоков (на основе книги заявок).
Если я получу достаточную поддержку, планирую написать третью часть, в которой мы создадим советник (EA) с помощью разработанных нами индикаторных буферов.
Перевод с испанского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/es/articles/15899
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Здравствуйте,
Я только что тестировал своего советника в тестере стратегий, EURUSD H4 1/1/2025-2//1/2025, и заметил в конце прогона две проблемы с индикатором Block Order.
Во-первых, он выбрал блочный ордер на 2/3/2025, который находится за пределами тестового окна, а во-вторых, он поместил текст блока в область Chart Shift.
Наслаждайтесь
CapeCoddah
Здесь представлена переведенная на английский язык версия вашего первого индикатора. Я решил, что мне нужно понять ваши многочисленные комментарии к коду на английском языке, и пересмотрел Google Translate, поскольку DeepL не произвел на меня впечатления. Сначала я заменил все //-комментарии на #/#, чтобы Google мог перевести //-комментарии к строкам... а затем преобразовал текстовый файл в документ MS Word для ввода в Translate.После перевода я открыл новый документ, сохранил его как текстовый файл, переименовал его и начал синтаксическую обработку нового источника. По моим оценкам, Translate сделал 90 % работы, но он добавил пробелы и символы, которые требовали ручного преобразования. После нескольких дней работы он скомпилировался без ошибок. Удивительно, но он сработал с первой попытки! Я сравнил его с вашим оригинальным индикатором на 1000 баров, и они были идентичны.
Здесь представлена переведенная на английский язык версия вашего первого индикатора. Я решил, что мне нужно понять ваши многочисленные комментарии к коду на английском языке, и пересмотрел Google Translate, поскольку DeepL не произвел на меня впечатления. Сначала я заменил все //-комментарии на #/#, чтобы Google мог перевести //-комментарии к строкам... а затем преобразовал текстовый файл в документ MS Word для ввода в Translate.После перевода я открыл новый документ, сохранил его как текстовый файл, переименовал его и начал синтаксическую обработку нового источника. По моим оценкам, Translate сделал 90 % работы, но он добавил пробелы и символы, которые требовали ручного преобразования. После нескольких дней работы он скомпилировался без ошибок. Удивительно, но он сработал с первой попытки! Я сравнил его с вашим оригинальным индикатором на 1000 баров, и они были идентичны.
К сожалению, похоже, что ваш индикатор структурно несовершенен и бесполезен для торговли, поскольку он выполняет свои расчеты на будущих переменных, которые неизвестны на момент расчета, как выделено в коде ниже жирным шрифтом.
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]);
К сожалению, похоже, что ваш индикатор структурно несовершенен и бесполезен для торговли, поскольку он выполняет свои расчеты на будущих переменных, которые неизвестны на момент расчета, как выделено в коде ниже жирным шрифтом.
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 была похожа на центральную свечу. Таким образом, если я искал сильное движение, я оценивал свечи, которые следовали за ним (в серии это было бы вычитанием).