
Разработка продвинутых торговых систем 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 por defecto
Эта переменная инициализируется значением true, но мы сможем изменить ее, если открытие стакана цен завершится неудачно.
2. Инициализация стакана цен
Для инициализации стакана цен используется функция:
MarketBookAdd()
Она открывает стакан цен (Depth of Market) для указанного символа. В качестве аргумента функция принимает текущий символ:
_Symbol
В событии OnInit проверяем, была ли инициализация успешной.
if(!MarketBookAdd(_Symbol)) //Verificamos la inicialización del libro de ordenes para el símbolo actual { Print("Error Open Market Book: ", _Symbol, " LastError: ", _LastError); //Imprimimos un error en caso de fallo use_market_book = false; //Marcamos la variable use_market_book como falseen caso de fallo }
3. Завершение работы со стаканом цен
В событии OnDeinit освобождаем стакан цен, используя:
MarketBookRelease()
Проверяем закрытие и выводим сообщение в зависимости от результата:
//--- if(MarketBookRelease(_Symbol)) //Verificamos si el cierre fue exitoso Print("Libro de ordenes cerrado con éxito para: " , _Symbol); //Imprimimos mensaje de éxito si lo fue else Print("Libro de ordenes cerrado con errores para: " , _Symbol , " Ultimo error: " , GetLastError()); //En caso no lo fue imprimimos el mensaje de error además del código de error
Сбор данных о глубине рынка для определения объемов массивов
После инициализации стакана цен, мы можем приступить к сбору соответствующих данных. Для этого создадим событие OnBookEvent, которое будет запускаться каждый раз, когда происходит изменение глубины рынка.
- Создание события OnBookEvent:
void OnBookEvent(const string& symbol)2. Проверка символа и доступности глубины рынка:
if(symbol !=_Symbol || use_market_book == false) return; // Si no se cumplen las condiciones, salimos del evento
После завершения этой проверки, мы можем представить полный код события OnBookEvent:
void OnBookEvent(const string& symbol) { if(symbol !=_Symbol || use_market_book == false) return; // Definir el array para almacenar los datos del Market Book MqlBookInfo book_info[]; // Obtener los datos del Market Book bool book_count = MarketBookGet(_Symbol,book_info); // Verificar si se obtuvieron datos if(book_count == true) { // Iterar sobre los datos del Market Book for(int i = 0; i < ArraySize(book_info); i++) { // Verificar si el registro es una oferta de compra (BID) if(book_info[i].type == BOOK_TYPE_BUY || book_info[i].type == BOOK_TYPE_BUY_MARKET) { buy_volume[0] += book_info[i].volume; } // Verificar si el registro es una oferta de venta (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 se han obtenido datos del Market Book."); } }
Этот код выполняет следующее:
- Получение объема: всякий раз, когда происходит изменение стакана цен, 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); // Mantiene el tamaño de buy_volume en 30 ArrayResize(sell_volume, 30); // Mantiene el tamaño de sell_volume en 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("Volumen de compra de la última vela: ", buy_volume[1]); Print("Volumen de venta de la última vela: ", 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; } // Si el tamaño del array es mayor o igual a 30, elimina el último elemento if(ArraySize(buy_volume) >= 30) { ArrayResize(buy_volume, 30); // Asegurar que el tamaño de buy_volume no exceda 30 ArrayResize(sell_volume, 30); // Asegurar que el tamaño de sell_volume no exceda 30 } 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]; } // Reiniciar el volumen para la nueva vela 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 Encotrado"); datetime time1 = Time[furthestAlcista]; double price2 = openArray[furthestAlcista]; double price1 = lowArray[furthestAlcista]; //asiganmos las variables mencionadas ala estructura 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 = "Order Block Alcista Book " + TimeToString(newVela_Order_block_Book.time1); AddIndexToArray_alcistas(newVela_Order_block_Book);
Медвежий сценарий:
Print("Case Book Encotrado"); datetime time1 = Time[furthestBajista]; double price1 = closeArray[furthestBajista]; double price2 = lowArray[furthestBajista]; //asiganmos las variables mencionadas ala estructura 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); //llamamos ala funcoin "FindFurthestAlcista" para saber si anterior a "one vela" hay velas alcistas if(furthestBajista > 0) // haiga o no haiga furthestAlcista sera mayor a 0 ya que si no hay retorna la vela anterior a "one vela". { Print("Case Book Encotrado"); datetime time1 = Time[furthestBajista]; double price1 = closeArray[furthestBajista]; double price2 = lowArray[furthestBajista]; //asiganmos las variables mencionadas ala estructura 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); } } } //-------------------- Alcista -------------------- 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); //llamamos ala funcoin "FindFurthestAlcista" para saber si anterior a "one vela" hay velas alcistas if(furthestAlcista > 0) // haiga o no haiga furthestAlcista sera mayor a 0 ya que si no hay retorna la vela anterior a "one vela". { Print("Case Book Encotrado"); datetime time1 = Time[furthestAlcista]; //asiganmos el tiempo del indice de furthestAlcista ala variable time1 double price2 = openArray[furthestAlcista]; //asiganmos el open de furthestAlcista como precio 2 (recodar que lo dibujamos en una vela bajista al mayoria de veces) double price1 = lowArray[furthestAlcista]; //asignamos el low de furthestAlcista como precio 1 //asiganmos las variables mencionadas ala estructura newVela_Order_block_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 = "Order Block Alcista 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, для хранения ценовых уровней, соответствующих блокам ордеров на покупку (бычьим) и продажу (медвежьим) соответственно. Эти массивы будут назначены буферам, что позволит отображать данные блоков ордеров на графике.
//--- Definir los buffers double buyOrderBlockBuffer[]; // Buffer para bloques de órdenes de compra double sellOrderBlockBuffer[]; // Buffer para bloques de órdenes de venta
Описание:
- buyOrderBlockBuffer: хранит уровни цен блоков бычьих ордеров (bullish order blocks) и предназначен для бычьих точек, где цена может найти поддержку.
- sellOrderBlockBuffer: хранит уровни цен блоков медвежьих ордеров (bearish order blocks) и отображает медвежьи точки, где цена может встретить сопротивление.
Модификация функции OnInit для настройки буферов
В этом разделе мы доработаем функцию OnInit для настройки буферов индикатора, назначив массивы бычьих и медвежьих блоков ордеров в качестве буферов индикатора. Это позволит индикатору правильно хранить и отображать данные на графике.
Пошагово:
1. Назначение буферов данных с помощью SetIndexBuffer
Сначала в OnInit назначаем массивы buyOrderBlockBuffer и sellOrderBlockBuffer буферам индикатора с помощью SetIndexBuffer. Это гарантирует, что эти массивы могут хранить и представлять данные на графике.
//--- Asignar buffers de datos al indicador 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); // Inicializar a EMPTY_VALUE ArrayFill(sellOrderBlockBuffer, 0,0, EMPTY_VALUE); // Inicializar a 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; // Estilo de TP y SL: por ATR o puntos fijos
В этом разделе 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; // Multiplicador para TP input double Atr_Multiplier_2 = 2.0; // Multiplicador para SL
Группа POINT: в этой группе мы добавляем две входные переменные input типа int для определения фиксированных пунктов TP и SL, что позволяет вручную и точно контролировать эти расстояния.
sinput group " POINT " input int TP_POINT = 500; // Puntos fijos para TP input int SL_POINT = 275; // Puntos fijos para 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("El indicador de ema no esta disponible Fallo: ", _LastError); return INIT_FAILED; }Мы объявляем статические переменные для управления состоянием поиска и временем активации OB, различая сценарии покупки и продажи.
//Variables para compra static bool buscar_oba = true; static datetime time_ = 0; //Variables para venta static bool buscar_obb = true; static datetime time_b = 0;
Затем выполняем цикл для поиска смягченных order blocks (аналогично тому, что мы делали в предыдущей статье для оповещений).
Начнем с добавления условий:
//caso alcista if(buscar_oba == true)
//caso bajista if(buscar_obb == true)
Следующий шаг — определить, был ли смягчен ОB, то есть взаимодействовала ли с ним цена. Если найден смягченный OB, его время фиксируется, и поиск приостанавливается. Это выполняется для обоих сценариев — бычьего и медвежьего.
// Caso Bajista 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("El order block bajista está siendo mitigado: ", TimeToString(ob_bajistas[i].time1)); buscar_obb = false; // Pausa la búsqueda time_b = iTime(_Symbol,_Period,1); // Registra el tiempo de mitigación Agregar_Index_Array_1(pricetwo_eliminados_obb, ob_bajistas[i].name); break; } } // Caso Alcista 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("El order block alcista está siendo mitigado: ", TimeToString(ob_alcistas[i].time1)); time_ = iTime(_Symbol,_Period,0); Agregar_Index_Array_1(pricetwo_eliminados_oba, ob_alcistas[i].name); buscar_oba = false; // Pausa la búsqueda break; } }
Этот раздел гарантирует, что система прекратит поиск после обнаружения смягчения, избегая дублирования сигналов.
Начальное условие для выполнения операции
Стратегия использует определенные условия для активации поиска сигналов на покупку или продажу после смягчения OB, и пока не было превышено максимальное время ожидания.
// Compra if(buscar_oba == false && time_ > 0 && new_vela) { /* Código de compra */ } // Venta if(buscar_obb == false && time_b > 0 && new_vela) { /* Código de venta */ }
В этих условиях:
- buscar_oba или buscar_obb должны быть равны false (подтверждая предыдущее смягчение),
- time_ или time_b должны иметь значение больше 0, указывая, что время зафиксировано,
- new_vela подтверждает нахождение на новой свече, помогая избежать повторяющихся решений.
Валидация условий покупки или продажи
Чтобы установить необходимые условия, нам сначала нужна переменная, которая хранит максимальное время ожидания. Затем необходимо знать значение закрытия свечи 1 и ее EMA (экспоненциальную скользящую среднюю). Для получения закрытия мы используем функцию iClose и сохраним значения EMA в массиве, содержащем весь исторический ряд скользящей средней.
// Compra 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) { // Código de compra... } // Venta 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) { // Código de venta... }
Перезапуск поиска Order Blocks
Наконец, если максимальное время ожидания превышено без выполнения условий, код перезапускает поиск, чтобы обеспечить обнаружение новых OB:
// Reinicio para compra if(iTime(_Symbol,_Period,0) > max_time_espera) { time_ = 0; buscar_oba = true; } // Reinicio para venta if(iTime(_Symbol,_Period,0) > max_time_espera) { time_b = 0; buscar_obb = true; }
Теперь нам не хватает функции для отрисовки 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), где будут храниться рассчитанные значения.
void GetTP_SL(double price_open_position, ENUM_POSITION_TYPE type, double &tp1, double &tp2, double &sl1, double &sl2)
2: Получение ATR
Для расчета уровней на основе ATR, нам сначала нужен массив, который будет хранить значение ATR последней свечи. Мы используем CopyBuffer для заполнения массива atr текущим значением.
double atr[]; ArraySetAsSeries(atr, true); CopyBuffer(atr_i, 0, 0, 1, atr);
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 - (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); }
4: Расчет TP и SL на основе пунктов
Когда tp_sl_style установлен в POINT, мы прибавляем или вычитаем указанные пункты (TP_POINT и SL_POINT), умноженные на значение одного пункта текущего символа (_Point), к цене открытия. Это простая альтернатива расчету на основе ATR.
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); }
Отображение уровней TP и SL на графике
Создадим функцию, которая отобразит значения tp и sl на графике. Для этого нам понадобится создать линии и тексты.
Для линий:
bool TrendCreate(long chart_ID, // ID del gráfico string name, // Nombre de la línea int sub_window, // índice de subventana datetime time1, // hora del primer punto double price1, // precio del primer punto datetime time2, // hora del segundo punto double price2, // precio del segundo punto color clr, // color de la línea ENUM_LINE_STYLE style, // estilo de la línea int width, // grosor de la línea bool back, // al fondo bool selection // seleccionar para mover ) { ResetLastError(); if(!ObjectCreate(chart_ID,name,OBJ_TREND,sub_window,time1,price1,time2,price2)) { Print(__FUNCTION__, ": ¡Fallo al crear la línea de tendencia! Código del error = ",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); }
Для текстов:
bool TextCreate(long chart_ID, // ID del gráfico string name, // nombre del objeto int sub_window, // número de subventana datetime time, // hora del punto de anclaje double price, // precio del punto de anclaje string text, // el texto string font, // fuente int font_size, // tamaño de la fuente color clr, // color double angle, // inclinación del texto ENUM_ANCHOR_POINT anchor, // modo de anclaje bool back=false, // al fondo bool selection=false) // seleccionar para mover { //--- anulamos el valor del error ResetLastError(); //--- creamos el objeto "Texto" if(!ObjectCreate(chart_ID,name,OBJ_TEXT,sub_window,time,price)) { Print(__FUNCTION__, ": ¡Fallo al crear el objeto \"Texto\"! Código del error = ",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); }
Теперь перейдем к созданию функции.
Шаг 1. Входные параметры
Функция получает следующие параметры:
- tp1 и tp2 — значения двух уровней Take Profit,
- sl1 и sl2 — значения двух уровней Stop Loss.
void DrawTP_SL( double tp1, double tp2, double sl1, double sl2)
Шаг 2: Подготовка времени
Сначала создается строка curr_time, которая сохраняет текущую дату и время свечи на графике. Затем рассчитывается extension_time, который продлевается на 15 периодов вперед от текущего времени, чтобы спроецировать линии TP и SL вправо на графике. text_time используется для корректировки положения текстовых меток, немного расширяя его за пределы extension_time.
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);
Шаг 3: Отрисовка линий и меток TP и SL
- Take Profit 1 (tp1):
- Пунктирная зеленая линия (STYLE_DOT) создается на уровне tp1 с помощью функции TrendCreate.
- Затем TextCreate добавляет метку с текстом «TP1» в позицию tp1.
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);2. Take Profit 2 (tp2):
- Рисуется еще одна пунктирная зеленая линия на tp2 и текстовая метка «TP2».
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);3. Stop Loss 1 (sl1):
- На уровне sl1 рисуется красная пунктирная линия вместе с текстовой меткой «SL1».
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);4. Stop Loss 2 (sl2):
- Аналогичным образом рисуется еще одна красная линия на уровне sl2 и текстовая метка "SL2".
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);
Полный код:
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); }
Добавление буферов для уровней TP и SL (4)
Также, как мы ранее делали для создания 2 буферов, которые хранят price2, обратимся к глобальной части программы, где напишем:
#property indicator_label3 "Take Profit 1" #property indicator_label4 "Take Profit 2" #property indicator_label5 "Stop Loss 1" #property indicator_label6 "Stop Loss 2"
Кроме того, увеличим количество плотов (plots) и буферов с 2 до 6.
#property indicator_buffers 6 #property indicator_plots 6
Создаем массив буферов:
double tp1_buffer[]; double tp2_buffer[]; double sl1_buffer[]; double sl2_buffer[];
Инициализируем их с помощью fill и переводим в режим серии:
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); // Inicializar a EMPTY_VALUE ArrayFill(sellOrderBlockBuffer, 0,0, EMPTY_VALUE); // Inicializar a EMPTY_VALUE ArraySetAsSeries(tp1_buffer, true); ArraySetAsSeries(tp2_buffer, true); ArrayFill(tp1_buffer, 0, 0, EMPTY_VALUE); // Inicializar a EMPTY_VALUE ArrayFill(tp2_buffer, 0, 0, EMPTY_VALUE); // Inicializar a EMPTY_VALUE ArraySetAsSeries(sl1_buffer, true); ArraySetAsSeries(sl2_buffer, true); ArrayFill(sl1_buffer, 0, 0, EMPTY_VALUE); // Inicializar a EMPTY_VALUE ArrayFill(sl2_buffer, 0, 0, EMPTY_VALUE); // Inicializar a EMPTY_VALUE
Таким образом, на этом этапе у нас уже будут буферы.
Завершение основного кода и очистка
Для завершения разработки индикатора необходимо реализовать код очистки и оптимизации. Это позволит индикатору работать быстрее в бэктестах и освободит ресурсы массивов, таких как OrderBlocks, когда те больше не нужны.
1. Очистка массивов
Внутри OnCalculate мы будем контролировать создание новой свечи, но на этот раз на дневном таймфрейме. Для этого мы используем глобальную переменную, которая будет хранить время последней свечи:
datetime tiempo_ultima_vela_1;
Каждый раз при открытии новой дневной свечи мы освобождаем память в массивах, предотвращая накопление старых данных и оптимизируя производительность. 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); }
2. Модификация OnDeinit
В OnDeinit мы освободим дескриптор индикатора EMA и очистим массивы. Это гарантирует, что при деактивации индикатора в памяти не останется никаких ресурсов.
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)) //Verificamos si el cierre fue exitoso Print("Libro de ordenes cerrado con éxito para: " , _Symbol); //Imprimimos mensaje de éxito si lo fue else Print("Libro de ordenes cerrado con errores para: " , _Symbol , " Ultimo error: " , GetLastError()); //En caso no lo fue imprimimos el mensaje de error además del código de error }
3. Функция удаления объектов:
Функция Remove_Objects была оптимизирована так, чтобы удалять также линии Take Profit (TP) и Stop Loss (SL) вместе с прямоугольниками блоков ордеров. Таким образом мы можем быть уверены, что график остается чистым.void Eliminar_Objetos() { for(int i = 0 ; i < ArraySize(ob_alcistas) ; i++) // iteramos por el array de los order blocks alcistas { ObjectDelete(ChartID(),ob_alcistas[i].name); // eliminamos el objeto usando el nombre del order block } for(int n = 0 ; n < ArraySize(ob_bajistas) ; n++) // iteramos por el array de los order blocks bajistas { ObjectDelete(ChartID(),ob_bajistas[n].name); // eliminamos el objeto usando el nombre del order block } //Eliminación de líneas del tp y sl ObjectsDeleteAll(0," TP",-1,-1); ObjectsDeleteAll(0," SL",-1,-1); }
4. Начальная конфигурация в OnInit:
В OnInit мы настраиваем краткое имя индикатора и подписи для графических элементов (plots). Таким образом, элементы будут соответствующим образом помечены в окне данных.
string short_name = "Order Block Indicator"; IndicatorSetString(INDICATOR_SHORTNAME,short_name); // Configuración de propiedades de precisión de los datos (número de dígitos) // Asignar etiquetas a cada uno de los plots para la ventana de datos 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");
5. Настройка уровней TP и SL при открытии сделок:
Наконец, мы устанавливаем уровни Take Profit и Stop Loss для сделок на покупку и продажу. Для покупок берем цену ask и рассчитываем соответствующие уровни, для продаж берем цену bid. Затем отображаем их на графике для отслеживания уровней.
//Compras 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; //Ventas 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;
Шаг | Покупки | Продажи |
---|---|---|
Цена: | Получается и нормализуется цена 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 не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.




- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования