Разработка продвинутых торговых систем ICT: Реализация сигналов в индикаторе Order Blocks
- Обнаружение блоков ордеров (Order Blocks) на основе глубины рынка
- Инициализация и завершение события "cтакан цен" и создание массивов
- Сбор данных о глубине рынка для определения объемов
- Стратегия поиска Order Blocks с использованием глубины рынка
- Создание буферов для индикатора
- Модификация функции OnInit для настройки буферов
- Реализация буферов в индикаторе (2)
- Обновление входных параметров (Inputs)
- Логика формирования сигналов индикатора
- Реализация торговой стратегии
- Установка уровней Take Profit (TP) и Stop Loss (SL)
- Отображение уровней TP и SL на графике
- Добавление буферов для уровней TP и SL (4)
- Завершение основного кода и очистка
Введение
Добро пожаловать в мир MQL5! В этой статье мы сосредоточимся на добавлении буферов и сигналов входа в наш индикатор, тем самым завершая необходимую функциональность для его использования в автоматизированных торговых стратегиях.
Если вы новичок в этой серии, рекомендуем ознакомиться с первой частью, где рассмотрены основные концепции и объясняется, как создать индикатор с нуля.
Обнаружение блоков ордеров (Order Blocks) на основе глубины рынка
Наша логика определения блоков ордеров на основе глубины рынка будет следующей:
- Array Creation: создаем два массива для хранения объема каждой свечи в стакане цен. Это позволит нам эффективно организовывать и анализировать данные об объемах.
- Market Depth Data Collection — в событии.
void OnBookEvent( )
Мы будем фиксировать все изменения в стакане цен, регистрируя новый объем, чтобы поддерживать актуальность данных в режиме реального времени.
3. Rules to validate Order Blocks: после того, как объем сохранен в массивах, мы будем использовать эти данные вместе с правилами ценового действия для валидации блока ордеров (Order Block).
Правила для Order Block по глубине рынка:
Изначально, при создании нашего индикатора, мы искали блоки ордеров (Order Blocks) в заданном диапазоне свечей. Однако, в случае глубины рынка мы не будем выполнять поиск в каком-то одном диапазоне «x». Вместо этого, мы сосредоточим поиск конкретно на третьей свече (где свеча 0 — это текущая свеча).
| Правила | Бычий Order Block | Медвежий Order Block |
|---|---|---|
| Пик объема на свече 3: | Объем покупок на свече 3 должен быть больше, в определенном соотношении, чем объемы покупок и продаж на свечах 2 и 4. | Мы будем искать, чтобы объем продаж на свече 3 был несколько больше объема покупок и продаж на свечах 2 и 4. |
| 3 последовательные свечи: | Должны появиться 3 последовательные бычьи свечи (свечи 1, 2 и 3) | Должны появиться 3 последовательные медвежьи свечи (свечи 1, 2 и 3) |
| Тело свечи 3: | Минимум свечи 2 должен быть выше половины тела свечи 3. | Максимум свечи 2 должен быть ниже половины тела свечи 3. |
| Максимум или минимум свечи 3: | Максимум свечи 3 должен быть ниже закрытия свечи 2. | Минимум свечи 3 должен быть выше закрытия свечи 2. |
С помощью этих правил мы обеспечим следующее:
- Имабаланс покупок или продаж: проверяем наличие существенного дисбаланса в покупках или продажах на конкретной свече, где ордера на покупку или продажу были выше в определенном соотношении, чем на предыдущей и последующей свечах.
- Контроль тела свечи при имбалансе: убеждаемся, что неисполненные ордера, возникшие из-за переизбытка спроса или предложения, не были поглощены последующей свечой, тем самым подтверждая устойчивость блока ордеров.
- Сильное бычье или медвежье движение: подтверждаем, что паттерн представляет собой выраженное движение, будь то бычье или медвежье, отражающее интенсивность дисбаланса в динамике цены.
Теперь, имея все это в виду, перенесем все изученное в код.
Инициализация и завершение события "cтакан цен" и создание массивов
Создание массивов
Прежде чем использовать стакан цен, необходимо создать динамические массивы, которые будут хранить данные об объемах. Эти массивы будут иметь тип:
long И будут использоваться для хранения объема покупок и объема продаж соответственно.
- Переходим в глобальный раздел программы и объявляем динамические массивы:
long buy_volume[]; long sell_volume[];2. Внутри события OnInit изменим размер массивов так, чтобы их начальный размер был равен 1. Кроме того, присвоим значение 0 индексу 0 каждого массива.
ArrayResize(buy_volume,1); ArrayResize(sell_volume,1); buy_volume[0] = 0.0; sell_volume[0] = 0.0;
Инициализация и завершение события "стакан цен":
Перед запуском стакана цен создадим глобальную переменную, которая будет указывать, доступна ли эта функция. Это позволит нам избежать использования:
INIT_FAILEDПоскольку не все символы у некоторых брокеров предоставляют объем торгов в стакане цен, индикатор не будет зависеть от брокера, который в обязательном порядке предлагает эту функцию. - Чтобы определить, имеет ли символ, которым вы хотите торговать, стакан цен и объем торгов, можно выполнить следующие шаги:
1. Кликните в левом верхнем углу графика на это изображение: ![]()
2. Проверьте, есть ли у вашего символа объем, доступный для стакана цен. Отобразится что-то вроде приведенного ниже.
Символ с объемом в стакане цен:
Символ без объема в стакане цен:
Как я уже говорил, объем в стакане цен доступен не для всех символов; это также будет зависеть от брокера, с которым вы работаете.
Перейдем к инициализации и завершению стакана цен.
1. Глобальная управляющая переменная
Определяем глобальную булеву переменную для обозначения статуса доступности стакана цен:
bool use_market_book = true; //true by default
Эта переменная инициализируется значением true, но мы сможем изменить ее, если открытие стакана цен завершится неудачно.
2. Инициализация стакана цен
Для инициализации стакана цен используется функция:
MarketBookAdd() Она открывает стакан цен (Depth of Market) для указанного символа. В качестве аргумента функция принимает текущий символ:
_Symbol В событии OnInit проверяем, была ли инициализация успешной.
if(!MarketBookAdd(_Symbol)) //Verify initialization of the order book for the current symbol { Print("Error Open Market Book: ", _Symbol, " LastError: ", _LastError); //Print error in case of failure use_market_book = false; //Mark use_market_book as false if initialization fails }
3. Завершение работы со стаканом цен
В событии OnDeinit освобождаем стакан цен, используя:
MarketBookRelease() Проверяем закрытие и выводим сообщение в зависимости от результата:
//--- if(MarketBookRelease(_Symbol)) //Verify if closure was successful Print("Order book successfully closed for: " , _Symbol); //Print success message if so else Print("Order book closed with errors for: " , _Symbol , " Last error: " , GetLastError()); //Print error message with code if not
Сбор данных о глубине рынка для определения объемов массивов
После инициализации стакана цен, мы можем приступить к сбору соответствующих данных. Для этого создадим событие OnBookEvent, которое будет запускаться каждый раз, когда происходит изменение глубины рынка.
- Создание события OnBookEvent:
void OnBookEvent(const string& symbol)2. Проверка символа и доступности глубины рынка:
if(symbol !=_Symbol || use_market_book == false) return; // Exit the event if conditions are not met
После завершения этой проверки, мы можем представить полный код события OnBookEvent:
void OnBookEvent(const string& symbol) { if(symbol !=_Symbol || use_market_book == false) return; // Define array to store Market Book data MqlBookInfo book_info[]; // Retrieve Market Book data bool book_count = MarketBookGet(_Symbol,book_info); // Verify if data was successfully obtained if(book_count == true) { // Iterate through Market Book data for(int i = 0; i < ArraySize(book_info); i++) { // Check if the record is a buy order (BID) if(book_info[i].type == BOOK_TYPE_BUY || book_info[i].type == BOOK_TYPE_BUY_MARKET) { buy_volume[0] += book_info[i].volume; } // Check if the record is a sell order (ASK) if(book_info[i].type == BOOK_TYPE_SELL || book_info[i].type == BOOK_TYPE_SELL_MARKET) { sell_volume[0] += book_info[i].volume; } } } else { Print("No Market Book data retrieved."); } }
Этот код выполняет следующее:
- Получение объема: всякий раз, когда происходит изменение стакана цен, OnBookEvent собирает объем последнего зарегистрированного ордера.
- Обновление массивов: суммирует объем покупки и продажи по индексу 0 массивов buy_volume и sell_volume соответственно.
Чтобы массив накапливал глубину объема рынка на каждой новой свече и сохранял историю в виде временного ряда, код должен скорректировать текущий объем в индексе 0 и сдвигать остальные данные вперед, гарантируя, что данные не будут накапливаться чрезмерно, и размер массива будет оставаться постоянным (например, 30 элементов).
1. Проверка новой свечи и валидация счетчика открытия свечей (больше 1)
Чтобы избежать ложных срабатываний при запуске программы и убедиться, что массивы обновляются только при открытии новой свечи (и после хотя бы одного открытия), мы реализуем проверку переменной counter вместе с new_vela. Это гарантирует, что обновление массивов будет происходить исключительно тогда, когда появится действительно новая информация.
Объявление статических переменныхМы объявляем counter как статическую переменную, чтобы она сохранялась между вызовами OnCalculate. Переменная new_vela должна указывать, открылась ли новая свеча.
static int counter = 0;
Условие проверки новой свечи и счетчика
Мы проверяем, что counter больше 1, new_vela имеет значение true, а использование рынка допустимо. Только если условия выполняются, мы изменим размер массива и сдвинем элементы. Этот элемент управления предотвращает преждевременное изменение размера и гарантирует, что массив обновляется только тогда, когда есть валидные данные и market book предоставляет объем торговли для текущего символа.
if(counter > 1 && new_vela == true && use_market_book == true)
Обновление счетчика
Мы увеличиваем счетчик на 1 каждый раз, когда обнаруживается новая свеча.
counter++;
2. Управление размером массива
Мы проверяем, что массив не превышает максимальный размер в 30 элементов. Если это так, мы меняем его размер до 30, чтобы удалить самый старый элемент:
if(ArraySize(buy_volume) >= 30) { ArrayResize(buy_volume, 30); // Keep buy_volume size at 30 ArrayResize(sell_volume, 30); // Keep sell_volume size at 30 }
3. Изменение размера для ввода новых значений
Добавляем дополнительное пространство в массив для сохранения нового объема в исходной позиции.
ArrayResize(buy_volume, ArraySize(buy_volume) + 1); ArrayResize(sell_volume, ArraySize(sell_volume) + 1);
4. Сдвиг элементов
Перемещаем все элементы массива на одну позицию вперед. Это гарантирует, что новые данные всегда будут сохраняться в индексе 0, а старые будут сдвигаться к индексам с большими значениями.
for(int i = ArraySize(buy_volume) - 1; i > 0; i--) { buy_volume[i] = buy_volume[i - 1]; sell_volume[i] = sell_volume[i - 1]; }
5. Проверка объемов
Выводим объем покупки и продажи в позицию 1 массива для проверки объема последней свечи.
Print("Buy volume of the last candle: ", buy_volume[1]); Print("Sell volume of the last candle: ", sell_volume[1]);
6. Сброс объемов
Обнуляем индекс 0 обоих массивов, чтобы начал накапливаться объем новой свечи.
buy_volume[0] = 0; sell_volume[0] = 0;
7. Условие, позволяющее избежать ошибок в случае противоречивых данных в market book
Я добавил это условие, чтобы автоматически отключить use_market_book, если значения buy_volume и sell_volume по последним позициям (индексы 3, 2 и 1) равны нулю. Эта настройка необходима, поскольку, даже если у символа есть market book на реальном рынке, при запуске в тестере стратегий он также определяется как имеющий market book. Однако массив может быть заполнен некорректно из-за отсутствия изменений в стакане цен в режиме тестирования, что приводит к нулевым значениям в массиве и может вызвать сохранение индикатором некорректной информации.
Эта проверка предотвращает обработку индикатором бессмысленных данных и гарантирует, что use_market_book используется только в том случае, если market book содержит допустимые значения.
if(ArraySize(buy_volume) > 4 && ArraySize(sell_volume) > 4) { if(buy_volume[3] == 0 && sell_volume[3] == 0 && buy_volume[2] == 0 && sell_volume[2] == 0 && buy_volume[1] == 0 && sell_volume[1] == 0) use_market_book = false; }
В интегрированном виде код будет выглядеть так:
if(counter > 1 && new_vela == true && use_market_book == true) { if(ArraySize(buy_volume) > 4 && ArraySize(sell_volume) > 4) { if(buy_volume[3] == 0 && sell_volume[3] == 0 && buy_volume[2] == 0 && sell_volume[2] == 0 && buy_volume[1] == 0 && sell_volume[1] == 0) use_market_book = false; } // If array size is greater than or equal to 30, resize to maintain a fixed length if(ArraySize(buy_volume) >= 30) { ArrayResize(buy_volume, 30); // Ensure buy_volume does not exceed 30 elements ArrayResize(sell_volume, 30); // Ensure sell_volume does not exceed 30 elements } ArrayResize(buy_volume,ArraySize(buy_volume)+1); ArrayResize(sell_volume,ArraySize(sell_volume)+1); for(int i = ArraySize(buy_volume) - 1; i > 0; i--) { buy_volume[i] = buy_volume[i - 1]; sell_volume[i] = sell_volume[i - 1]; } // Reset volumes at index 0 to begin accumulating for the new candlestick buy_volume[0] = 0; sell_volume[0] = 0; }
Стратегия поиска Order Blocks с использованием стакана цен
Стратегия будет следовать той же логике, которую мы использовали ранее, но с одним важным отличием: вместо использования циклов, мы будем выполнять проверки непосредственно на свече 3. Общая логика остается прежней: мы проверяем определенные условия, определяем ближайшую свечу (на основе типа блока ордеров), затем присваиваем соответствующие значения структуре и добавляем блок ордеров в массив. Здесь мы применим тот же процесс, но в более простой форме.
Начнем с создания структур, в которых будет храниться информация о блоках ордеров:
OrderBlocks newVela_Order_block_Book_bajista; OrderBlocks newVela_Order_block_Book;
1. Начальные условия
Сначала проверяем, что размер массивов buy_volume и sell_volume составляет не менее 5 элементов. Это гарантирует наличие достаточной истории для анализа. Также убеждаемся, что use_market_book будет активен для обработки стакана цен.
if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true)
2. Определение управляющих переменных
Определяем переменную case_book, чтобы указать, выполняется ли определенное условие по объему. Мы устанавливаем ratio на уровне (1,4), это будет служить сравнительным фактором для выявления значительного увеличения объема покупок.
bool case_book = false; double ratio = 1.4;
3. Состояние объема покупок (Case Book)
Здесь мы проверяем, значительно ли объем покупок в индексе 3 превышает объем в индексах 2 и 4, как со стороны покупки, так и со стороны продажи, используя ratio в качестве множителя. Если это условие выполняется, активируется case_book.
Бычий сценарий:
if(buy_volume[3] > buy_volume[4] * ratio && buy_volume[3] > buy_volume[2] * ratio && buy_volume[3] > sell_volume[4] * ratio && buy_volume[3] > sell_volume[2] * ratio) { case_book = true; }Медвежий сценарий:
if(sell_volume[3] > buy_volume[4]*ratio && sell_volume[3] > buy_volume[2]*ratio && sell_volume[3] > sell_volume[4]*ratio && sell_volume[3] > sell_volume[2]*ratio) { case_book = true; }
4. Расчет тела свечи
Вычисляем размер тела свечи (body_tree) по индексу 3, вычитая цену ее открытия из цены закрытия.
double body_tree = closeArray[3] - openArray[3];
double body_tree = openArray[3] - closeArray[3]; 5. Проверка ценовых условий для бычьего сетапа
Оцениваем условия, упомянутые в начале (см. таблицу выше).
Бычий сценарий:
if(lowArray[2] > ((body_tree * 0.5) + openArray[3]) && highArray[3] < closeArray[2] && closeArray[3] > openArray[3] && closeArray[2] > openArray[2] && closeArray[1] > openArray[1])
Медвежий сценарий:
if(highArray[2] < (openArray[3]-(body_tree * 0.5)) && lowArray[3] > closeArray[2] && closeArray[3] < openArray[3] && closeArray[2] < openArray[2] && closeArray[1] < openArray[1])
6. Идентификация предыдущих бычьих свечей
Мы вызываем функцию FindFurthestBullish, которая ищет самую дальнюю бычью свечу в диапазоне 20 свечей от индекса 3. Это помогает нам найти опорную свечу для надежного бычьего сетапа. Если бычья свеча найдена, индекс больше 0, что позволяет нам продолжить.
Бычий сценарий:
int furthestAlcista = FindFurthestAlcista(Time[3], 20); if(furthestAlcista > 0)
7. Присвоение значений блоку ордера
Если все условия выполняются, определяем блок ордера (newVela_Order_block_Book или newVela_Order_block_Book_bajista) со значениями найденной свечи.
Бычий сценарий:
Print("Case Book Found"); datetime time1 = Time[furthestAlcista]; double price2 = openArray[furthestAlcista]; double price1 = lowArray[furthestAlcista]; //Assign the above variables to the structure newVela_Order_block_Book.price1 = price1; newVela_Order_block_Book.time1 = time1; newVela_Order_block_Book.price2 = price2; newVela_Order_block_Book.mitigated = false; newVela_Order_block_Book.name = "Bullish Order Block Book " + TimeToString(newVela_Order_block_Book.time1); AddIndexToArray_alcistas(newVela_Order_block_Book);
Медвежий сценарий:
Print("Case Book Found"); datetime time1 = Time[furthestBajista]; double price1 = closeArray[furthestBajista]; double price2 = lowArray[furthestBajista]; //Assign the above variables to the structure newVela_Order_block_Book_bajista.price1 = price1; newVela_Order_block_Book_bajista.time1 = time1; newVela_Order_block_Book_bajista.price2 = price2; newVela_Order_block_Book_bajista.mitigated = false; newVela_Order_block_Book_bajista.name = "Order Block Bajista Book " + TimeToString(newVela_Order_block_Book_bajista.time1); AddIndexToArray_bajistas(newVela_Order_block_Book_bajista);
Полный код:
if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true) { bool case_book = false; double ratio = 1.4; if(sell_volume[3] > buy_volume[4]*ratio && sell_volume[3] > buy_volume[2]*ratio && sell_volume[3] > sell_volume[4]*ratio && sell_volume[3] > sell_volume[2]*ratio) { case_book = true; } double body_tree = openArray[3] - closeArray[3]; if(highArray[2] < (openArray[3]-(body_tree * 0.5)) && lowArray[3] > closeArray[2] && closeArray[3] < openArray[3] && closeArray[2] < openArray[2] && closeArray[1] < openArray[1]) { int furthestBajista = FindFurthestBajista(Time[3],20); //We call the "FindFurthestAlcista" function to find out if there are bullish candlesticks before "one candle" if(furthestBajista > 0) // Whether or not there is a furthest Bullish candle, it will be greater than 0 since if there is none, the previous candlestick returns to "one candle". { Print("Case Book Found"); datetime time1 = Time[furthestBajista]; double price1 = closeArray[furthestBajista]; double price2 = lowArray[furthestBajista]; //Assign the above variables to the structure newVela_Order_block_Book_bajista.price1 = price1; newVela_Order_block_Book_bajista.time1 = time1; newVela_Order_block_Book_bajista.price2 = price2; newVela_Order_block_Book_bajista.mitigated = false; newVela_Order_block_Book_bajista.name = "Order Block Bajista Book " + TimeToString(newVela_Order_block_Book_bajista.time1); AddIndexToArray_bajistas(newVela_Order_block_Book_bajista); } } } //-------------------- Bullish -------------------- if(ArraySize(buy_volume) >= 5 && ArraySize(sell_volume) >= 5 && use_market_book == true) { bool case_book = false; double ratio = 1.4; if(buy_volume[3] > buy_volume[4]*ratio && buy_volume[3] > buy_volume[2]*ratio && buy_volume[3] > sell_volume[4]*ratio && buy_volume[3] > sell_volume[2]*ratio) { case_book = true; } double body_tree = closeArray[3] - openArray[3]; if(lowArray[2] > ((body_tree * 0.5)+openArray[3]) && highArray[3] < closeArray[2] && closeArray[3] > openArray[3] && closeArray[2] > openArray[2] && closeArray[1] > openArray[1]) { int furthestAlcista = FindFurthestAlcista(Time[3],20); //We call the "FindFurthestAlcista" function to find out if there are bullish candlessticks before "one candle" if(furthestAlcista > 0) // Whether or not there is a furthest Bullish candle, it will be greater than 0 since if there is none, the previous candlestick returns to "one candle". { Print("Case Book Found"); datetime time1 = Time[furthestAlcista]; //assign the index time of furthestAlcista to the variable time1 double price2 = openArray[furthestAlcista]; //assign the open of furthestAlcista as price 2 (remember that we draw it on a bearish candlestick most of the time) double price1 = lowArray[furthestAlcista]; //assign the low of furthestAlcista as price 1 //Assign the above variables to the structure newVela_Order_block_Book.price1 = price1; newVela_Order_block_Book.time1 = time1; newVela_Order_block_Book.price2 = price2; newVela_Order_block_Book.mitigated = false; newVela_Order_block_Book.name = "Bullish Order Block Book " + TimeToString(newVela_Order_block_Book.time1); AddIndexToArray_alcistas(newVela_Order_block_Book); } } }
Создание буферов для индикатора
Чтобы создать и настроить буферы для нашего индикатора блоков ордеров (order blocks) в MQL5, мы начнем с определения двух буферов и двух глобальных графических элементов (плоты, plots) для хранения и отображения ценовых уровней бычьих и медвежьих блоков.
1. Объявление буферов и плотов
Давайте объявим два буфера в глобальной части программы, чтобы они могли хранить ценовые данные блоков ордеров. Кроме того, добавим два плота (plots), которые будут служить для визуализации блоков ордеров на графике.
#property indicator_buffers 2 #property indicator_plots 2 #property indicator_label1 "Bullish Order Block" #property indicator_label2 "Bearish Order Block"
2. Создание динамических массивов для буферов
Мы объявляем два динамических массива, buyOrderBlockBuffer и sellOrderBlockBuffer, для хранения ценовых уровней, соответствующих блокам ордеров на покупку (бычьим) и продажу (медвежьим) соответственно. Эти массивы будут назначены буферам, что позволит отображать данные блоков ордеров на графике.
//--- Define the buffers double buyOrderBlockBuffer[]; // Buffer for bullish order blocks double sellOrderBlockBuffer[]; // Buffer for bearish order blocks
Описание:
- buyOrderBlockBuffer: хранит уровни цен блоков бычьих ордеров (bullish order blocks) и предназначен для бычьих точек, где цена может найти поддержку.
- sellOrderBlockBuffer: хранит уровни цен блоков медвежьих ордеров (bearish order blocks) и отображает медвежьи точки, где цена может встретить сопротивление.
Модификация функции OnInit для настройки буферов
В этом разделе мы доработаем функцию OnInit для настройки буферов индикатора, назначив массивы бычьих и медвежьих блоков ордеров в качестве буферов индикатора. Это позволит индикатору правильно хранить и отображать данные на графике.
Пошагово:
1. Назначение буферов данных с помощью SetIndexBuffer
Сначала в OnInit назначаем массивы buyOrderBlockBuffer и sellOrderBlockBuffer буферам индикатора с помощью SetIndexBuffer. Это гарантирует, что эти массивы могут хранить и представлять данные на графике.
//--- Assign data buffers to the indicator SetIndexBuffer(0, buyOrderBlockBuffer, INDICATOR_DATA); SetIndexBuffer(1, sellOrderBlockBuffer, INDICATOR_DATA)
2. Настройка буферов как серии и заполнение пустыми значениями
Чтобы данные отображались в обратном хронологическом порядке (в виде временного ряда), мы задаем массивам режим серий. Кроме того, инициализируем оба буфера значением EMPTY_VALUE, чтобы предотвратить отображение неверных данных до тех пор, пока не будут рассчитаны фактические значения.
ArraySetAsSeries(buyOrderBlockBuffer, true); ArraySetAsSeries(sellOrderBlockBuffer, true); ArrayFill(buyOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE ArrayFill(sellOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
Реализация буферов в индикаторе (2)
В этом разделе мы назначим цены блоков бычьих и медвежьих ордеров буферам индикатора. Эти буферы позволяют сделать данные доступными в каждом индексе, соответствующем времени (time1) каждого блока заказа.1. Присвоение цен для бычьих блоков ордеров
Внутри цикла, где мы оцениваем каждый бычий блок в массиве ob_alcistas, мы добавляем следующую строку кода для сохранения цены price2 в буфере buyOrderBlockBuffer. Мы используем функцию iBarShift, чтобы получить точный индекс на графике, где time1 совпадает со временем блока ордера.
buyOrderBlockBuffer[iBarShift(_Symbol, _Period, ob_alcistas[i].time1)] = ob_alcistas[i].price2;
Здесь значение price2 бычьего блока присваивается соответствующему индексу в buyOrderBlockBuffer, так что буфер отражает уровень цены блока на графике.
2. Присвоение цен для медвежьих блоков ордеров
Аналогично мы присваиваем цену price2 каждого медвежьего блока буферу sellOrderBlockBuffer. Это достигается путем прохода по массиву ob_bajistas и установки значения цены в соответствующий индекс.
sellOrderBlockBuffer[iBarShift(_Symbol, _Period, ob_bajistas[i].time1)] = ob_bajistas[i].price2;В итоге:
- iBarShift используется для определения точного индекса, где время блока совпадает с позицией на графике.
- buyOrderBlockBuffer и sellOrderBlockBuffer получают значения price2, что позволяет фиксировать цены в нужный момент и использовать их на графике и в дополнительных расчетах индикатора.
Обновление входных параметров (Inputs)
В этом разделе мы настроим входные параметры (inputs) так, чтобы пользователь мог персонализировать стиль расчета тейк-профита (TP) и стоп-лосса (SL) в соответствии со своими предпочтениями. Чтобы добиться этого, мы создадим перечисление, которое позволит выбирать между двумя вариантами:АТR (Average True Range) или POINT (фиксированные пункты).
Перечисление ENUM_TP_SL_STYLE
Перечисление ENUM_TP_SL_STYLE позволяет пользователю выбрать один из двух режимов расчета TP и SL.
- АТR: определяет значения TP и SL на основе среднего диапазона движения цены, автоматически корректируя их в соответствии с текущей волатильностью рынка.
- POINT: определяет значения TP и SL в фиксированных пунктах на основе значения, заданного пользователем.
enum ENUM_TP_SL_STYLE
{
ATR,
POINT
};
Пояснение:
-
АТR: выбирая эту опцию, пользователь устанавливает множитель, который будет определять расстояние TP и SL относительно ATR. Более высокое значение множителя увеличит расстояние между TP и SL на основе текущей волатильности.
-
POINT: в этом варианте пользователь вручную определяет TP и SL в фиксированных пунктах. Это позволяет устанавливать статические уровни TP и SL независимо от волатильности.
Теперь, продолжая работу с параметрами индикатора, структурируем входные параметры индикатора с помощью sinput и сгруппируем настройки в разделы. Это обеспечит более наглядное и организованное отображение параметров в интерфейсе, упрощая настройку для пользователя.
1. Раздел стратегии
Сначала мы создаем раздел стратегии, который включает опцию стилей для расчета тейк-профита (TP) и стоп-лосса (SL):
sinput group "-- Strategy --" input ENUM_TP_SL_STYLE tp_sl_style = POINT; // TP and SL style: ATR or fixed points
В этом разделе tp_sl_style позволит пользователю выбрать, хочет ли он рассчитывать TP и SL на основе АТR (Average True Range) или в фиксированных пунктах.
2. Настройка TP и SL в соответствии с выбранным методом
Чтобы учесть специфические настройки каждого метода, мы добавляем две дополнительные группы: одну для метода АТR и другую для фиксированных пунктов.
Группа ATR: здесь мы включаем две входные переменные input типа double, которые дают возможность пользователю задать множители ATR, тем самым корректируя диапазон TP и SL на основе волатильности.
sinput group " ATR " input double Atr_Multiplier_1 = 1.5; // Multiplier for TP input double Atr_Multiplier_2 = 2.0; // Multiplier for SL
Группа POINT: в этой группе мы добавляем две входные переменные input типа int для определения фиксированных пунктов TP и SL, что позволяет вручную и точно контролировать эти расстояния.
sinput group " POINT " input int TP_POINT = 500; // Fixed points for TP input int SL_POINT = 275; // Fixed points for SL
Благодаря такой организации, параметры аккуратно упорядочены и классифицированы, что упрощает их использование и повышает ясность при настройке индикатора. Пользователь сможет интуитивно настраивать стиль TP и SL, выбирая между автоматическими конфигурациями на основе ATR или ручными настройками в пунктах.
Полный код параметров:
sinput group "--- Order Block Indicator settings ---" sinput group "-- Order Block --" input int Rango_universal_busqueda = 500; input int Witdth_order_block = 1; input bool Back_order_block = true; input bool Fill_order_block = true; input color Color_Order_Block_Bajista = clrRed; input color Color_Order_Block_Alcista = clrGreen; sinput group "-- Strategy --" input ENUM_TP_SL_STYLE tp_sl_style = POINT; sinput group " ATR " input double Atr_Multiplier_1 = 1.5; input double Atr_Multiplier_2 = 2.0; sinput group " POINT " input int TP_POINT = 500; input int SL_POINT = 275;
Логика формирования сигналов индикатора
Для генерации сигналов на покупку или продажу используются две статические переменные:
| Переменная | Описание |
|---|---|
| time_ и time_b | Сохраняют время смягчения order block и добавляют 5 свечей (в секундах) к сроку его истечения. |
| buscar_oba и buscar_obb | Управляют поиском новых смягченных order blocks (mitigated order block). Активируются или деактивируются в зависимости от условий. |
Процесс генерации сигналов
Обнаружение смягченного Order Block:- При смягчении блока ордеров, переменной time_ присваивается текущее время плюс маржа в 5 свечей.
- Переменной поиска присваивается значение false, чтобы остановить поиск, пока проверяются условия сигнала.
- Условия покупки или продажи оцениваются на основе экспоненциальной скользящей средней (EMA) и времени смягчения (time_).
| Тип сигнала | Условия ЕМА | Условия времени |
|---|---|---|
| Покупка | EMA с периодом 30 должна находиться ниже закрытия свечи 1 | time_ должна быть больше текущего времени |
| Продажа | EMA с периодом 30 должна быть выше закрытия свечи 1 | time_b должна быть больше текущего времени |
Примечание: эти условия гарантируют, что сигнал генерируется в пределах 5 свечей после смягчения блока ордера (order block).
Действия в случае выполнения или невыполнения:
| Состояние | Действие |
|---|---|
| Выполнение | Заполняются буферы take profit (TP) и stop loss (SL) для выполнения соответствующей операции |
| Невыполнение | Переменная поиска сбрасывается в true, а time_ и time_b сбрасываются в 0, что позволяет возобновить поиск новых блоков ордеров (в случае превышения максимального времени). |
Блок-схема:
Покупки

Продажи

Реализация торговой стратегии
Прежде чем начать, мы создадим дескриптор экспоненциальной скользящей средней.
Мы создаем глобальные переменные (массив и дескриптор):
int hanlde_ma; double ma[];
В OnInit мы инициализируем дескриптор и проверяем, присвоено ли ему значение.
hanlde_ma = iMA(_Symbol,_Period,30,0,MODE_EMA,PRICE_CLOSE); if(hanlde_ma == INVALID_HANDLE) { Print("The EMA indicator is not available. Failure: ", _LastError); return INIT_FAILED; }Мы объявляем статические переменные для управления состоянием поиска и временем активации OB, различая сценарии покупки и продажи.
//Variables for buy static bool buscar_oba = true; static datetime time_ = 0; //Variables for sell static bool buscar_obb = true; static datetime time_b = 0;
Затем выполняем цикл для поиска смягченных order blocks (аналогично тому, что мы делали в предыдущей статье для оповещений).
Начнем с добавления условий:
//Bullish case if(buscar_oba == true) //Bearish case if(buscar_obb == true)
// Bearish case 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) && ObjectFind(ChartID(), ob_bajistas[i].name) >= 0) { Alert("The bearishorder block is being mitigated: ", TimeToString(ob_bajistas[i].time1)); buscar_obb = false; // Pause search time_b = iTime(_Symbol,_Period,1); // Record the mitigation time Agregar_Index_Array_1(pricetwo_eliminados_obb, ob_bajistas[i].name); break; } } // Bullish case 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) && ObjectFind(ChartID(), ob_alcistas[i].name) >= 0) { Alert("The bullish order block is mitigated: ", TimeToString(ob_alcistas[i].time1)); time_ = iTime(_Symbol,_Period,0); Agregar_Index_Array_1(pricetwo_eliminados_oba, ob_alcistas[i].name); buscar_oba = false; // Pause search break; } }
Следующий шаг — определить, был ли смягчен ОB, то есть взаимодействовала ли с ним цена. Если найден смягченный OB, его время фиксируется, и поиск приостанавливается. Это выполняется для обоих сценариев — бычьего и медвежьего.
// Buy if(buscar_oba == false && time_ > 0 && new_vela) { /* Code for Buy */ } // Sell if(buscar_obb == false && time_b > 0 && new_vela) { /* Code for Sell */ }
Этот раздел гарантирует, что система прекратит поиск после обнаружения смягчения, избегая дублирования сигналов.
Начальное условие для выполнения операции
Стратегия использует определенные условия для активации поиска сигналов на покупку или продажу после смягчения OB, и пока не было превышено максимальное время ожидания.
// Buy double close_ = NormalizeDouble(iClose(_Symbol,_Period,1),_Digits); datetime max_time_espera = time_ + (PeriodSeconds() * 5); if(close_ > ma[1] && iTime(_Symbol,_Period,0) <= max_time_espera) { // Code for Buy... } // Sell close_ = NormalizeDouble(iClose(_Symbol,_Period,1),_Digits); max_time_espera = time_b + (PeriodSeconds() * 5); if(close_ < ma[1] && iTime(_Symbol,_Period,0) <= max_time_espera) { // Code for Sell... }
В этих условиях:
- buscar_oba или buscar_obb должны быть равны false (подтверждая предыдущее смягчение),
- time_ или time_b должны иметь значение больше 0, указывая, что время зафиксировано,
- new_vela подтверждает нахождение на новой свече, помогая избежать повторяющихся решений.
Валидация условий покупки или продажи
Чтобы установить необходимые условия, нам сначала нужна переменная, которая хранит максимальное время ожидания. Затем необходимо знать значение закрытия свечи 1 и ее EMA (экспоненциальную скользящую среднюю). Для получения закрытия мы используем функцию iClose и сохраним значения EMA в массиве, содержащем весь исторический ряд скользящей средней.
// Reset for Buy if(iTime(_Symbol,_Period,0) > max_time_espera) { time_ = 0; buscar_oba = true; } // Reset for Sell if(iTime(_Symbol,_Period,0) > max_time_espera) { time_b = 0; buscar_obb = true; }
Перезапуск поиска Order Blocks
Наконец, если максимальное время ожидания превышено без выполнения условий, код перезапускает поиск, чтобы обеспечить обнаружение новых OB:
void GetTP_SL(double price_open_position, ENUM_POSITION_TYPE type, double &tp1, double &tp2, double &sl1, double &sl2)
Теперь нам не хватает функции для отрисовки tp и sl, а также для добавления их в буферы. Мы сделаем это сейчас, а затем завершим текущий код.
Рассмотрим это в следующих разделах.
Установка уровней Take Profit (TP) и Stop Loss (SL)
В этом разделе мы разработаем функцию GetTP_SL, которая будет рассчитывать значения TP и SL на основе двух методов: используя ATR (Average True Range), или фиксированные пункты, как мы упоминали ранее при настройке входных параметров inputs.
1: Определение функции
Функция GetTP_SL будет принимать в качестве параметров цену открытия позиции, тип позиции (ENUM_POSITION_TYPE) и ссылки на уровни TP и SL (tp1, tp2, sl1 и sl2), где будут храниться рассчитанные значения.
double atr[]; ArraySetAsSeries(atr, true); CopyBuffer(atr_i, 0, 0, 1, atr);
2: Получение ATR
Для расчета уровней на основе ATR, нам сначала нужен массив, который будет хранить значение ATR последней свечи. Мы используем CopyBuffer для заполнения массива atr текущим значением.
if (type == POSITION_TYPE_BUY) { sl1 = price_open_position - (atr[0] * Atr_Multiplier_1); sl2 = price_open_position - (atr[0] * Atr_Multiplier_2); tp1 = price_open_position + (atr[0] * Atr_Multiplier_1); tp2 = price_open_position + (atr[0] * Atr_Multiplier_2); } if (type == POSITION_TYPE_SELL) { sl1 = price_open_position + (atr[0] * Atr_Multiplier_1); sl2 = price_open_position + (atr[0] * Atr_Multiplier_2); tp1 = price_open_position - (atr[0] * Atr_Multiplier_1); tp2 = price_open_position - (atr[0] * Atr_Multiplier_2); }
3: Расчет TP и SL на основе ATR
Когда tp_sl_style установлен в ATR, мы рассчитаем уровни TP и SL, умножая значение ATR на определенные множители (Atr_Multiplier_1 и Atr_Multiplier_2). Затем мы прибавляем или вычитаем эти значения из цены открытия, в зависимости от типа позиции.
if (type == POSITION_TYPE_BUY) { sl1 = price_open_position - (SL_POINT * _Point); sl2 = price_open_position - (SL_POINT * _Point * 2); tp1 = price_open_position + (TP_POINT * _Point); tp2 = price_open_position + (TP_POINT * _Point * 2); } if (type == POSITION_TYPE_SELL) { sl1 = price_open_position + (SL_POINT * _Point); sl2 = price_open_position + (SL_POINT * _Point * 2); tp1 = price_open_position - (TP_POINT * _Point); tp2 = price_open_position - (TP_POINT * _Point * 2); }
4: Расчет TP и SL на основе пунктов
Когда tp_sl_style установлен в POINT, мы прибавляем или вычитаем указанные пункты (TP_POINT и SL_POINT), умноженные на значение одного пункта текущего символа (_Point), к цене открытия. Это простая альтернатива расчету на основе ATR.
bool TrendCreate(long chart_ID, // Chart ID string name, // Line name int sub_window, // Subwindow index datetime time1, // Time of the first point double price1, // Price of the first point datetime time2, // Time of the second point double price2, // Price of the second point color clr, // Line color ENUM_LINE_STYLE style, // Line style int width, // Line width bool back, // in the background bool selection // Selectable form moving ) { ResetLastError(); if(!ObjectCreate(chart_ID,name,OBJ_TREND,sub_window,time1,price1,time2,price2)) { Print(__FUNCTION__, ": ¡Failed to create trend line! Error code = ",GetLastError()); return(false); } ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr); ObjectSetInteger(chart_ID,name,OBJPROP_STYLE,style); ObjectSetInteger(chart_ID,name,OBJPROP_WIDTH,width); ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back); ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection); ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection); ChartRedraw(chart_ID); return(true); }
Отображение уровней TP и SL на графике
Создадим функцию, которая отобразит значения tp и sl на графике. Для этого нам понадобится создать линии и тексты.
Для линий:
bool TextCreate(long chart_ID, // Chart ID string name, // Object name int sub_window, // Subwindow index datetime time, // Anchor time double price, // Anchor price string text, // the text string font, // Font int font_size, // Font size color clr, // color double angle, // Text angle ENUM_ANCHOR_POINT anchor, // Anchor point bool back=false, // font bool selection=false) // Selectable for moving { //--- reset error value ResetLastError(); //--- create "Text" object if(!ObjectCreate(chart_ID,name,OBJ_TEXT,sub_window,time,price)) { Print(__FUNCTION__, ": ¡Failed to create object \"Text\"! Error code = ",GetLastError()); return(false); } ObjectSetString(chart_ID,name,OBJPROP_TEXT,text); ObjectSetString(chart_ID,name,OBJPROP_FONT,font); ObjectSetInteger(chart_ID,name,OBJPROP_FONTSIZE,font_size); ObjectSetDouble(chart_ID,name,OBJPROP_ANGLE,angle); ObjectSetInteger(chart_ID,name,OBJPROP_ANCHOR,anchor); ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr); ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back); ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection); ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection); ChartRedraw(chart_ID); return(true); }
Для текстов:
void DrawTP_SL( double tp1, double tp2, double sl1, double sl2)
Теперь перейдем к созданию функции.
Шаг 1. Входные параметры
Функция получает следующие параметры:
- tp1 и tp2 — значения двух уровней Take Profit,
- sl1 и sl2 — значения двух уровней Stop Loss.
string curr_time = TimeToString(iTime(_Symbol, _Period, 0)); datetime extension_time = iTime(_Symbol, _Period, 0) + (PeriodSeconds(PERIOD_CURRENT) * 15); datetime text_time = extension_time + (PeriodSeconds(PERIOD_CURRENT) * 2);
Шаг 2: Подготовка времени
Сначала создается строка curr_time, которая сохраняет текущую дату и время свечи на графике. Затем рассчитывается extension_time, который продлевается на 15 периодов вперед от текущего времени, чтобы спроецировать линии TP и SL вправо на графике. text_time используется для корректировки положения текстовых меток, немного расширяя его за пределы extension_time.
TrendCreate(ChartID(), curr_time + " TP1", 0, iTime(_Symbol, _Period, 0), tp1, extension_time, tp1, clrGreen, STYLE_DOT, 1, true, false); TextCreate(ChartID(), curr_time + " TP1 - Text", 0, text_time, tp1, "TP1", "Arial", 8, clrGreen, 0.0, ANCHOR_CENTER);
Шаг 3: Отрисовка линий и меток TP и SL
- Take Profit 1 (tp1):
- Пунктирная зеленая линия (STYLE_DOT) создается на уровне tp1 с помощью функции TrendCreate.
- Затем TextCreate добавляет метку с текстом «TP1» в позицию tp1.
TrendCreate(ChartID(), curr_time + " TP2", 0, iTime(_Symbol, _Period, 0), tp2, extension_time, tp2, clrGreen, STYLE_DOT, 1, true, false); TextCreate(ChartID(), curr_time + " TP2 - Text", 0, text_time, tp2, "TP2", "Arial", 8, clrGreen, 0.0, ANCHOR_CENTER);2. Take Profit 2 (tp2):
- Рисуется еще одна пунктирная зеленая линия на tp2 и текстовая метка «TP2».
TrendCreate(ChartID(), curr_time + " SL1", 0, iTime(_Symbol, _Period, 0), sl1, extension_time, sl1, clrRed, STYLE_DOT, 1, true, false); TextCreate(ChartID(), curr_time + " SL1 - Text", 0, text_time, sl1, "SL1", "Arial", 8, clrRed, 0.0, ANCHOR_CENTER);3. Stop Loss 1 (sl1):
- На уровне sl1 рисуется красная пунктирная линия вместе с текстовой меткой «SL1».
TrendCreate(ChartID(), curr_time + " SL2", 0, iTime(_Symbol, _Period, 0), sl2, extension_time, sl2, clrRed, STYLE_DOT, 1, true, false); TextCreate(ChartID(), curr_time + " SL2 - Text", 0, text_time, sl2, "SL2", "Arial", 8, clrRed, 0.0, ANCHOR_CENTER);4. Stop Loss 2 (sl2):
- Аналогичным образом рисуется еще одна красная линия на уровне sl2 и текстовая метка "SL2".
void DrawTP_SL(double tp1, double tp2, double sl1, double sl2) { string curr_time = TimeToString(iTime(_Symbol,_Period,0)); datetime extension_time = iTime(_Symbol,_Period,0) + (PeriodSeconds(PERIOD_CURRENT) * 15); datetime text_time = extension_time + (PeriodSeconds(PERIOD_CURRENT) * 2); TrendCreate(ChartID(),curr_time+" TP1",0,iTime(_Symbol,_Period,0),tp1,extension_time,tp1,clrGreen,STYLE_DOT,1,true,false); TextCreate(ChartID(),curr_time+" TP1 - Text",0,text_time,tp1,"TP1","Arial",8,clrGreen,0.0,ANCHOR_CENTER); TrendCreate(ChartID(),curr_time+" TP2",0,iTime(_Symbol,_Period,0),tp2,extension_time,tp2,clrGreen,STYLE_DOT,1,true,false); TextCreate(ChartID(),curr_time+" TP2 - Text",0,text_time,tp2,"TP2","Arial",8,clrGreen,0.0,ANCHOR_CENTER); TrendCreate(ChartID(),curr_time+" SL1",0,iTime(_Symbol,_Period,0),sl1,extension_time,sl1,clrRed,STYLE_DOT,1,true,false); TextCreate(ChartID(),curr_time+" SL1 - Text",0,text_time,sl1,"SL1","Arial",8,clrRed,0.0,ANCHOR_CENTER); TrendCreate(ChartID(),curr_time+" SL2",0,iTime(_Symbol,_Period,0),sl2,extension_time,sl2,clrRed,STYLE_DOT,1,true,false); TextCreate(ChartID(),curr_time+" SL2 - Text",0,text_time,sl2,"SL2","Arial",8,clrRed,0.0,ANCHOR_CENTER); }
Полный код:
#property indicator_label3 "Take Profit 1" #property indicator_label4 "Take Profit 2" #property indicator_label5 "Stop Loss 1" #property indicator_label6 "Stop Loss 2"
Добавление буферов для уровней TP и SL (4)
Также, как мы ранее делали для создания 2 буферов, которые хранят price2, обратимся к глобальной части программы, где напишем:
#property indicator_buffers 6 #property indicator_plots 6
Кроме того, увеличим количество плотов (plots) и буферов с 2 до 6.
double tp1_buffer[]; double tp2_buffer[]; double sl1_buffer[]; double sl2_buffer[];
Создаем массив буферов:
SetIndexBuffer(2, tp1_buffer, INDICATOR_DATA); SetIndexBuffer(3, tp2_buffer, INDICATOR_DATA); SetIndexBuffer(4, sl1_buffer, INDICATOR_DATA); SetIndexBuffer(5, sl2_buffer, INDICATOR_DATA); ArraySetAsSeries(buyOrderBlockBuffer, true); ArraySetAsSeries(sellOrderBlockBuffer, true); ArrayFill(buyOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE ArrayFill(sellOrderBlockBuffer, 0,0, EMPTY_VALUE); // Initialize to EMPTY_VALUE ArraySetAsSeries(tp1_buffer, true); ArraySetAsSeries(tp2_buffer, true); ArrayFill(tp1_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE ArrayFill(tp2_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE ArraySetAsSeries(sl1_buffer, true); ArraySetAsSeries(sl2_buffer, true); ArrayFill(sl1_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE ArrayFill(sl2_buffer, 0, 0, EMPTY_VALUE); // Initialize to EMPTY_VALUE
Инициализируем их с помощью fill и переводим в режим серии:
datetime tiempo_ultima_vela_1; Таким образом, на этом этапе у нас уже будут буферы.
Завершение основного кода и очистка
Для завершения разработки индикатора необходимо реализовать код очистки и оптимизации. Это позволит индикатору работать быстрее в бэктестах и освободит ресурсы массивов, таких как OrderBlocks, когда те больше не нужны.
1. Очистка массивов
Внутри OnCalculate мы будем контролировать создание новой свечи, но на этот раз на дневном таймфрейме. Для этого мы используем глобальную переменную, которая будет хранить время последней свечи:
if(tiempo_ultima_vela_1 != iTime(_Symbol,PERIOD_D1, 0)) { Eliminar_Objetos(); ArrayFree(ob_bajistas); ArrayFree(ob_alcistas); ArrayFree(pricetwo_eliminados_oba); ArrayFree(pricetwo_eliminados_obb); tiempo_ultima_vela_1 = iTime(_Symbol,PERIOD_D1, 0); }Каждый раз при открытии новой дневной свечи мы освобождаем память в массивах, предотвращая накопление старых данных и оптимизируя производительность.
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); if(hanlde_ma != INVALID_HANDLE) //EMA IndicatorRelease(hanlde_ma); ResetLastError(); if(MarketBookRelease(_Symbol)) //Verify if closure was successful Print("Order book successfully closed for: " , _Symbol); //Print success message if so else Print("Order book closed with errors for: " , _Symbol , " Last error: " , GetLastError()); //Print error message with code if not }
2. Модификация OnDeinit
В OnDeinit мы освободим дескриптор индикатора EMA и очистим массивы. Это гарантирует, что при деактивации индикатора в памяти не останется никаких ресурсов.
void Eliminar_Objetos() { for(int i = 0 ; i < ArraySize(ob_alcistas) ; i++) // iterate through the array of bullish order blocks { ObjectDelete(ChartID(),ob_alcistas[i].name); // delete the object using the order block's name } for(int n = 0 ; n < ArraySize(ob_bajistas) ; n++) // iterate through the array of bearish order blocks { ObjectDelete(ChartID(),ob_bajistas[n].name); // delete the object using the order block's name } //Delete all TP and SL lines ObjectsDeleteAll(0," TP",-1,-1); ObjectsDeleteAll(0," SL",-1,-1); }
3. Функция удаления объектов:
Функция Remove_Objects была оптимизирована так, чтобы удалять также линии Take Profit (TP) и Stop Loss (SL) вместе с прямоугольниками блоков ордеров. Таким образом мы можем быть уверены, что график остается чистым.string short_name = "Order Block Indicator"; IndicatorSetString(INDICATOR_SHORTNAME,short_name); // Set data precision for digits // Assign labels for each plot PlotIndexSetString(0, PLOT_LABEL, "Bullish Order Block"); PlotIndexSetString(1, PLOT_LABEL, "Bearish Order Block"); PlotIndexSetString(2, PLOT_LABEL, "Take Profit 1"); PlotIndexSetString(3, PLOT_LABEL, "Take Profit 2"); PlotIndexSetString(4, PLOT_LABEL, "Stop Loss 1"); PlotIndexSetString(5, PLOT_LABEL, "Stop Loss 2");
4. Начальная конфигурация в OnInit:
В OnInit мы настраиваем краткое имя индикатора и подписи для графических элементов (plots). Таким образом, элементы будут соответствующим образом помечены в окне данных.
//Buy double ask= NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); double tp1; double tp2; double sl1; double sl2; GetTP_SL(ask,POSITION_TYPE_BUY,tp1,tp2,sl1,sl2); DrawTP_SL(tp1,tp2,sl1,sl2); tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp1; tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp2; sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl1; sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl2; time_ = 0; buscar_oba = true; //Sell double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); double tp1; double tp2; double sl1; double sl2; GetTP_SL(bid,POSITION_TYPE_SELL,tp1,tp2,sl1,sl2); DrawTP_SL(tp1,tp2,sl1,sl2); tp1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp1; tp2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = tp2; sl1_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl1; sl2_buffer[iBarShift(_Symbol,PERIOD_CURRENT,iTime(_Symbol,_Period,0))] = sl2; time_b = 0; buscar_obb = true;
5. Настройка уровней TP и SL при открытии сделок:
Наконец, мы устанавливаем уровни Take Profit и Stop Loss для сделок на покупку и продажу. Для покупок берем цену ask и рассчитываем соответствующие уровни, для продаж берем цену bid. Затем отображаем их на графике для отслеживания уровней.
undefined
| Шаг | Покупки | Продажи |
|---|---|---|
| Цена: | Получается и нормализуется цена ask. | Получается и нормализуется цена bid. |
| Переменные: | Инициализируются переменные для хранения значений Take Profit и Stop Loss. (tp1, tp2, sl1 и sl2). | Те же самые переменные используются для хранения уровней Take Profit и Stop Loss. (tp1, tp2, sl1 и sl2). |
| Расчет: | GetTP_SL рассчитывает уровни TP и SL на основе цены ask для сделки на покупку. | GetTP_SL рассчитывает уровни TP и SL на основе цены bid для сделки на продажу. |
| Отрисовка: | DrawTP_SL визуально отображает на графике уровни TP и SL для сделки на покупку. | DrawTP_SL визуально отображает на графике уровни TP и SL для сделки на продажу. |
| Буфер: | Используется iBarShift для поиска индекса текущего бара и сохранения TP и SL в буферах. (tp1_buffer, tp2_buffer, sl1_buffer и sl2_buffer). | Используется iBarShift, чтобы сохранить TP и SL в те же самые буферы. (tp1_buffer, tp2_buffer, sl1_buffer и sl2_buffer). |
| Статические переменные: | Статические переменные сбрасываются для поиска новых блоков бычьих ордеров в следующей итерации. (Статические переменные: "time_" и "buscar_oba"). | Статические переменные сбрасываются для поиска новых блоков медвежьих ордеров в следующей итерации. (Статические переменные: "time_b" и "search_obb"). |
Заключение
В этой статье мы рассмотрели, как создать индикатор Order Blocks, основанный на объеме глубины рынка, и оптимизировать его функциональность, добавив дополнительные буферы к исходному индикатору.
Наш итоговый результат:

На этом мы завершаем разработку нашего индикатора Order Blocks. В следующих материалах мы рассмотрим создание класса управления рисками с нуля и разработку торгового бота, который интегрирует это управление рисками, используя сигнальные буферы нашего индикатора для принятия решений более точным и автоматизированным образом.
Перевод с испанского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/es/articles/16268
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Нейросети в трейдинге: От трансформеров к спайковым нейронам (Основные компоненты)
Создание пользовательской системы определения рыночного режима на языке MQL5 (Часть 2): Советник
Торгуем опционы без опционов (Часть 3): Сложные опционные стратегии
Искусство ведения логов (Часть 4): Сохранение логов в файлах
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
https://www.mql5.com/ru/articles/16268
5. Установка уровней TP и SL при открытии сделок
Наконец, мы устанавливаем уровни Take Profit и Stop Loss для сделок на покупку и продажу. Для сделок на покупку используйте цену Ask, для сделок на продажу - цену Bid. Затем нарисуйте линии TP и SL на графике для мониторинга.
Выглядит, как будто это можно немного упростить.