English
preview
От новичка до эксперта: Создание индикатора-оверлея CRT для нескольких таймфреймов в MQL5

От новичка до эксперта: Создание индикатора-оверлея CRT для нескольких таймфреймов в MQL5

MetaTrader 5 — Трейдинг |
52 1
Clemence Benjamin
Clemence Benjamin

Теория свечного диапазона (CRT) позволяет определять надежные структурные уровни на старших таймфреймах, однако трейдерам сложно отображать эти диапазоны на графиках младших таймфреймов. В этой статье представлен индикатор на языке MQL5, который решает эту проблему, отображая элементы свечи старшего таймфрейма — полный диапазон, тело и тени — непосредственно на текущем графике младшего таймфрейма. Этот инструмент позволяет трейдерам выявлять ключевые ценовые уровни, зоны ликвидности и точки входа с учетом контекста без переключения между таймфреймами. Он превращает CRT из абстрактного понятия в постоянно видимый ориентир и помогает принимать более обоснованные решения при совершении сделок.

Содержание

  1. Введение
  2. Концепция — истоки и логика CRT
  3. Требования к корректному решению
  4. Реализация
  5. Тестирование и результаты
  6. Заключение
  7. Основные выводы
  8. Приложения


Введение

Трейдеры, использующие теорию свечного диапазона (CRT), сталкиваются с практической проблемой: структурные уровни, видимые на старших таймфреймах (H1, H4, Daily), исчезают при переходе на таймфреймы для совершения сделок (M1-M30). Потеря этого контекста приводит к ошибкам интерпретации: то, что на пятиминутном графике выглядит как пробой, на дневном часто оказывается снятием ликвидности институциональными участниками. Поэтому практическое решение должно быть точным и проверяемым: каждый бар младшего таймфрейма должен однозначно сопоставляться с соответствующей свечой старшего таймфрейма (точная привязка по времени), графическое наложение не должно перерисовываться (сигналы фиксируются только после закрытия свечи старшего таймфрейма), а полный диапазон, тело и тени этой свечи должны отображаться непосредственно на торговом графике без ручной разметки и переключения окон.

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


Концепция — истоки и логика CRT

Теория свечного диапазона (CRT) опирается на две разные традиции: давно известные рыночные принципы и современную авторскую терминологию. Эти принципы восходят к моделям Вайкоффа "Springs" (снятиям ликвидности ниже поддержки с последующим разворотом) и "Upthrusts" (снятиям ликвидности выше сопротивления с последующим разворотом). Позднее эти механизмы популяризировала Линда Брэдфорд Рашке в стратегии "Turtle Soup". Однако CRT как система с собственным названием — это современная разработка, авторство которой в сообществе, близком к ICT, обычно приписывают трейдеру Romeotpt (Raid). Raid систематизировал цикл "накопление — манипуляция — распределение" (A-M-D) в терминах CRT, используемых сегодня.

Основной принцип

CRT основана на простой аксиоме: каждая свеча представляет собой диапазон. У каждой свечи есть полный торговый диапазон (от максимума до минимума свечи — область размещения ликвидности) и диапазон от открытия до закрытия — тело свечи, отражающее уровень цены, принятый рынком. Тени показывают, что цена была отвергнута на этих уровнях. Ключевая опорная точка — цена открытия: сразу после открытия часто сначала движется против предполагаемого направления свечи, снимая ликвидность, а затем разворачивается.

Старшие таймфреймы (дневной, H4) придают этим уровням структурную значимость. На младших таймфреймах (M5, M15) они полностью скрыты — так было до сих пор.

Постоянное переключение между таймфреймами — это не просто неудобство. Это напрямую влияет на качество исполнения сделок. Когда трейдер наблюдает за пятиминутным графиком без диапазонов дневных свечей или свечей H4, он фактически не видит уровней сосредоточения ордеров институциональных участников. Например, резкое падение на M5 ниже недавнего минимума может выглядеть как медвежий пробой. Если этот минимум совпадает с ценой открытия дневной свечи (ключевым уровнем ликвидности CRT), то, вероятнее всего, происходит манипулятивное снятие ликвидности и формируется сетап для входа в длинную позицию. Без наложения данных старшего таймфрейма многие трейдеры неверно интерпретируют движение и входят в короткую позицию у самого минимума движения, уже после снятия стопов. Этот индикатор для работы на нескольких таймфреймах избавляет от догадок: структурный контекст отображается непосредственно на торговом графике, и сбивающий с толку ценовой выброс становится узнаваемым сетапом CRT.

Почему крупные игроки снова и снова разворачивают цену у границ диапазона? Ответ кроется в микроструктуре рынка: стоп-ордера и отложенные ордера скапливаются чуть выше очевидных максимумов и чуть ниже очевидных минимумов. Снимая ликвидность на этих уровнях (фаза "манипуляции"), институциональные участники вызывают срабатывание стоп-ордеров розничных трейдеров и набирают собственные крупные позиции, прежде чем направить цену к противоположной границе диапазона (фаза "распределения"). Свечи старшего таймфрейма отражают эту активность — именно в диапазонах дневных свечей или H4 сосредоточена значительная ликвидность. Паттерн CRT на дневном графике сигнализирует о том, что крупный участник рынка, вероятно, завершил накопление или распределение и цена вернется к противоположной границе диапазона. Индикатор не просто рисует линии. Он показывает следы активности институциональных участников на каждом баре младшего таймфрейма, помогая розничным трейдерам согласовывать входы с логикой профессионалов.

CRT — Multiple Timeframes

Рис. 1. Диапазон медвежьей дневной свечи (оранжевый блок), наложенный на график H1.

На рис. 1 цена снимает ликвидность ниже дневного минимума (манипуляция), разворачивается и дает сигнал на покупку с целью у дневного максимума (TP). Индикатор автоматически отображает диапазон, уровень входа, стоп-лосс и тейк-профит.

Практическая польза индикатора

Этот индикатор для работы на нескольких таймфреймах восполняет недостающий контекст. Он накладывает данные OHLC свечей старшего таймфрейма непосредственно на график младшего таймфрейма. Например, вы видите, достигает ли снятие ликвидности на M5 цены открытия дня или середины диапазона предыдущего дня — зон с высокой вероятностью разворота. Важно, что индикатор можно настроить: использовать закрытые свечи для построения запаздывающих уровней, удобных для тестирования на исторических данных, или текущую цену открытия (с проекциями на основе предыдущих диапазонов) для опережающего инструмента CRT, работающего в реальном времени. Кроме того, он отсеивает воскресные и небольшие свечи (например, на участках разрывов 22:00-00:00), которые иначе искажали бы расчет диапазона.

В результате получается постоянно отображаемое наложение CRT без перерисовки, которое сохраняет контекст старшего таймфрейма именно там, где он нужен, — на торговом графике.


Требования к корректному решению

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

Требование Определение, поддающееся проверке
Воспроизводимость Для паттерна CRT на исторических данных при каждом запуске строятся одинаковые линии входа, TP и SL
Обнаружение в реальном времени Сигнал фиксируется сразу после закрытия сигнальной свечи на старшем таймфрейме
Отсутствие ручной разметки Все прямоугольники, линии входа, уровни TP и SL отображаются на текущем графике автоматически
Алерты Звуковые оповещения и push-уведомления при формировании нового сетапа
Производительность Работа с тысячами баров без зависания графика
Гибкость Можно настраивать смещение, фильтр размера тела свечи, инверсия TP/SL и длина продления линий


Реализация

Индикатор CRT_MTF полностью написан на языке MQL5 и работает непосредственно на любом графике младшего таймфрейма. Он загружает данные старшего таймфрейма, сопоставляет каждый бар младшего таймфрейма с соответствующей свечой старшего, обнаруживает корректные паттерны CRT и автоматически отображает все графические элементы. Ниже мы пошагово рассмотрим реализацию, объясним назначение каждого компонента, обоснуем ключевые решения и покажем, как индикатор работает в реальных рыночных условиях. Полный исходный код приводится отдельно — здесь мы сосредоточимся на основных механизмах.

Шаг 1 — свойства индикатора, входные параметры и настройка инструмента

Каждый индикатор на языке MQL5 начинается с директив #property, которые задают MetaTrader 5 правила работы с пользовательским инструментом. Для CRT MTF нам нужны два буфера стрелок (один для сигналов на продажу, другой — для сигналов на покупку). Также мы указываем, что индикатор отображает данные непосредственно в окне графика (а не в отдельном подокне). Далее идут входные параметры, сгруппированные по назначению: выбор старшего таймфрейма, настройка условий CRT (смещение, множитель тела свечи), переключатели алертов, цвета всех графических элементов, настройки стрелок и параметры линий TP/SL. Эти параметры позволяют трейдерам полностью контролировать внешний вид индикатора и чувствительность сигналов без изменения кода.

//+------------------------------------------------------------------+
//|                                                      CRT_MTF.mq5 |
//|                                 Copyright 2026, Clemence Benjamin|
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Clemence Benjamin"
#property link      "https://www.mql5.com/ru/users/billionaire2024/seller"
#property version   "2.90"
#property description "CRT MTF—Intelligent entry and exit levels, plus signal alerts."

#property indicator_chart_window
#property indicator_buffers 2
#property indicator_plots 2

#property indicator_type1 DRAW_ARROW
#property indicator_width1 4
#property indicator_color1 0x8000FF
#property indicator_label1 "Sell Signal"

#property indicator_type2 DRAW_ARROW
#property indicator_width2 4
#property indicator_color2 0xFF9500
#property indicator_label2 "Buy Signal"

//--- input parameters
input ENUM_TIMEFRAMES InpHigherTF = PERIOD_H1;          // Higher Timeframe
input double          InpOffset = 2.0;                  // Range Offset Factor
input double          InpCandleBodyMultiplier = 3.0;    // Body Size Filter
input bool            InpEnableSoundAlerts = true;
input bool            InpEnablePushAlerts = true;

//--- Colors
input color           InpSellRangeColor   = clrLightPink;
input color           InpSellSignalColor  = clrCrimson;
input color           InpBuyRangeColor    = clrPaleGreen;
input color           InpBuySignalColor   = clrLimeGreen;
input color           InpRangeRectColor   = clrOrange;

//--- Arrow settings
input bool            InpDrawArrowOnSignal = true;

//--- TP/SL/Entry lines (fixed length)
input bool            InpDrawEntryLine = true;
input bool            InpDrawTPLine    = true;
input bool            InpDrawSLLine    = true;
input bool            InpFlipTPSL      = false;
input color           InpEntryLineColor = clrWhite;
input color           InpTPLineColor   = clrDodgerBlue;
input color           InpSLLineColor   = clrRed;
input int             InpLineExtendBars = 50;

input bool            InpDebugPrint = false;

Шаг 2 — глобальные структуры и переменные, упорядочение данных

Для эффективной работы с данными старшего таймфрейма мы определяем пользовательскую структуру HTFCandle. В ней хранятся время, цена открытия, максимум, минимум и цена закрытия одной свечи. Массив структур htfCandles[] будет хранить все загруженные свечи старшего таймфрейма. Мы также объявляем два буфера (BufferSell и BufferBuy) для отображения стрелок и массив сопоставления htfIndexMap, связывающий каждый бар младшего таймфрейма с соответствующей свечой старшего. Кроме того, вводим point (для ценового смещения), lastAlertTime (для предотвращения повторных алертов) и переменные, хранящие длительность периодов обоих таймфреймов в секундах. Эти глобальные переменные доступны из любой функции, что делает код более упорядоченным и быстрым.

//--- indicator buffers
double BufferSell[];
double BufferBuy[];

//--- globals
datetime lastAlertTime;
string   indicatorShortName;
double   point;
int      htfPeriodSeconds;
int      currPeriodSeconds;

//+------------------------------------------------------------------+
//| Structure for higher timeframe candle                            |
//+------------------------------------------------------------------+
struct HTFCandle
  {
   datetime          time;
   double            open, high, low, close;
  };
HTFCandle htfCandles[];
int       htfTotal;
int       htfIndexMap[];

Шаг 3 — инициализация в OnInit (однократная настройка)

Функция OnInit автоматически вызывается при добавлении индикатора на график. Ее задача — подготовить индикатор к работе. Сначала мы получаем значение пункта символа с помощью SymbolInfoDouble — это позволяет учитывать брокеров с пятизначными котировками, у которых один пункт равен 0,00001 вместо 0,0001. Затем мы вычисляем длительность периода в секундах для старшего таймфрейма (htfPeriodSeconds) и текущего (currPeriodSeconds). Далее мы настраиваем два буфера индикатора, задаем символы стрелок (234 — вниз, 233 — вверх) и используем EMPTY_VALUE для пустых значений, чтобы на барах без сигнала ничего не отображалось. В заключение мы задаем короткое имя для окна индикатора и возвращаем INIT_SUCCEEDED. Если какой-либо этап завершится ошибкой, индикатор не загрузится, но здесь настройка достаточно проста.

//+------------------------------------------------------------------+
//| Initializes indicator buffers and settings                       |
//+------------------------------------------------------------------+
int OnInit()
  {
   point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   htfPeriodSeconds = PeriodSeconds(InpHigherTF);
   currPeriodSeconds = PeriodSeconds(_Period);

   SetIndexBuffer(0, BufferSell, INDICATOR_DATA);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(0, PLOT_ARROW, 234);   // down arrow for sell

   SetIndexBuffer(1, BufferBuy, INDICATOR_DATA);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(1, PLOT_ARROW, 233);   // up arrow for buy

   indicatorShortName = "CRT MTF (Signal+Entry)";
   IndicatorSetString(INDICATOR_SHORTNAME, indicatorShortName);
   return(INIT_SUCCEEDED);
  }

Шаг 4 — загрузка данных старшего таймфрейма для получения структурного контекста

В OnCalculate сначала загружаются свечи старшего таймфрейма. Мы определяем общее количество доступных баров старшего таймфрейма с помощью функции Bars. Если баров меньше трех, мы сразу завершаем выполнение: нужны как минимум две свечи (свеча диапазона и сигнальная свеча) и еще одна для согласования. Затем мы изменяем размер массива htfCandles, чтобы он вмещал все свечи старшего таймфрейма. С помощью функций CopyTime, CopyOpen, CopyHigh, CopyLow и CopyClose мы загружаем данные во временные массивы. Затем для каждого массива устанавливается индексация как у временного ряда (индекс 0 = самый новый), чтобы согласовать ее с индексацией данных младшего таймфрейма. Наконец, для удобства доступа мы копируем значения в элементы массива структур htfCandles. При таком подходе вся история старшего таймфрейма загружается за один раз, что повышает эффективность и позволяет избежать повторного копирования на каждом тике.

//--- load higher TF data
htfTotal = Bars(_Symbol, InpHigherTF);
if(htfTotal < 3)
   return(rates_total);

ArrayResize(htfCandles, htfTotal);
datetime htfTimeArray[];
double htfOpen[], htfHigh[], htfLow[], htfClose[];

if(CopyTime(_Symbol, InpHigherTF, 0, htfTotal, htfTimeArray) <= 0 ||
    CopyOpen(_Symbol, InpHigherTF, 0, htfTotal, htfOpen) <= 0 ||
    CopyHigh(_Symbol, InpHigherTF, 0, htfTotal, htfHigh) <= 0 ||
    CopyLow(_Symbol, InpHigherTF, 0, htfTotal, htfLow) <= 0 ||
    CopyClose(_Symbol, InpHigherTF, 0, htfTotal, htfClose) <= 0)
   return(rates_total);

ArraySetAsSeries(htfTimeArray, true);
ArraySetAsSeries(htfOpen, true);
ArraySetAsSeries(htfHigh, true);
ArraySetAsSeries(htfLow, true);
ArraySetAsSeries(htfClose, true);

for(int i = 0; i < htfTotal; i++)
  {
   htfCandles[i].time = htfTimeArray[i];
   htfCandles[i].open = htfOpen[i];
   htfCandles[i].high = htfHigh[i];
   htfCandles[i].low  = htfLow[i];
   htfCandles[i].close = htfClose[i];
  }

Шаг 5 — сопоставление баров младшего таймфрейма со свечами старшего (бинарный поиск)

Чтобы определить, к какой свече старшего таймфрейма относится каждый бар младшего, мы строим карту индексов с помощью бинарного поиска. Для каждого бара младшего таймфрейма со временем bt мы находим свечу старшего таймфрейма, для которой start ≤ bt < end (где end = start + htfPeriodSeconds). Этот подход со сложностью O(log n) эффективен даже при работе с тысячами баров. Мы перебираем все бары младшего таймфрейма, выполняем бинарный поиск в массиве htfCandles и сохраняем найденный индекс в htfIndexMap[i]. Если соответствующая свеча не найдена (например, бар предшествует самому раннему бару старшего таймфрейма), мы сохраняем -1 и впоследствии пропускаем этот бар. Это сопоставление вычисляется один раз при каждом вызове OnCalculate и повторно используется во всех процедурах обнаружения сигналов и отрисовки.

//--- map lower bars to HTF indices (binary search)
ArrayResize(htfIndexMap, rates_total);
ArrayInitialize(htfIndexMap, -1);

for(int i = 0; i < rates_total; i++)
  {
   datetime bt = time[i];
   int lo = 0, hi = htfTotal - 1, found = -1;

   while(lo <= hi)
     {
      int mid = (lo + hi) / 2;
      datetime start = htfCandles[mid].time;
      datetime end = start + htfPeriodSeconds;

      if(bt >= start && bt < end)
        {
         found = mid;
         break;
        }
      else if(bt < start)
         lo = mid + 1;
      else
         hi = mid - 1;
     }
   htfIndexMap[i] = found;
  }

Шаг 6 — логика обнаружения сигналов CRT (основной механизм распознавания паттернов)

Индикатор проверяет каждый бар младшего таймфрейма, завершающий соответствующую свечу старшего: подтвердить паттерн CRT можно только после полного закрытия сигнальной свечи. Для этих баров мы обращаемся к предыдущей свече (rangeIdx = signalIdx + 1) и проверяем условия CRT. Для сигнала на продажу нужна бычья свеча диапазона (close > open). Сигнальная свеча должна пробить максимум диапазона, но закрыться ниже него и ниже собственной цены открытия. Ее тело должно быть небольшим (меньше диапазона, деленного на множитель тела), а минимум — оставаться выше минимума диапазона плюс смещение (диапазон, деленный на InpOffset). Для покупки условия обратные: свеча диапазона должна быть медвежьей, а сигнальная — пробить минимум диапазона, но закрыться выше него и выше собственной цены открытия. Тело сигнальной свечи должно быть небольшим, а ее максимум — оставаться ниже максимума диапазона за вычетом смещения. Эти правила отличаются математической точностью и исключают субъективную интерпретацию.

//--- Only evaluate when this lower bar is the LAST bar of its HTF candle
bool isLastBarOfThisCandle = false;
if(i == 0)
   isLastBarOfThisCandle = true;
else if(htfIndexMap[i-1] != htfIdx)
   isLastBarOfThisCandle = true;

if(!isLastBarOfThisCandle)
   continue;

//--- CRT condition checks
double rangeRange = rangeHigh - rangeLow;
double sigBody = MathAbs(sigOpen - sigClose);

bool isSell = false, isBuy = false;

if(rangeClose > rangeOpen && sigHigh > rangeHigh && sigClose < rangeHigh &&
    sigClose < sigOpen && sigLow > rangeOpen &&
    sigBody < rangeRange / InpCandleBodyMultiplier &&
    sigLow > rangeLow + (rangeRange / InpOffset))
  {
   isSell = true;
  }
else if(rangeClose < rangeOpen && sigLow < rangeLow && sigClose > rangeLow &&
         sigClose > sigOpen && sigHigh < rangeOpen &&
         sigBody < rangeRange / InpCandleBodyMultiplier &&
         sigHigh < rangeHigh - (rangeRange / InpOffset))
  {
   isBuy = true;
  }

Шаг 7 — сохранение сигналов и заполнение буферов стрелок (фиксация результатов)

При обнаружении корректного паттерна необходимо точно определить бар младшего таймфрейма, на котором следует разместить сигнал: это последний бар в пределах сигнальной свечи старшего таймфрейма. Для этого мы просматриваем бары вперед от текущего индекса i, пока не изменится индекс соответствующей свечи старшего таймфрейма. На этом последнем баре (lastIdx) завершается сигнальная свеча в представлении младшего таймфрейма. Затем мы сохраняем все необходимые сведения о сигнале (индекс свечи диапазона, индекс сигнальной свечи, направление, lastIdx, цена входа = close[lastIdx]) в динамическом массиве структур SignalInfo. Если пользователь включил отображение стрелок, мы записываем значение в соответствующий элемент буфера: для сигнала на продажу стрелка располагается выше максимума бара с небольшим отступом, а для сигнала на покупку — ниже минимума с отступом. При этом на графике появляется четкий маркер.

//--- Find the absolute last lower bar index for this signal candle
int lastIdx = i;
for(int j = i + 1; j < rates_total; j++)
  {
   if(htfIndexMap[j] == signalIdx)
      lastIdx = j;
   else
      break;
  }

SignalInfo sig;
sig.rangeHtfIdx = rangeIdx;
sig.signalHtfIdx = signalIdx;
sig.isSell = isSell;
sig.lastSignalLowerIdx = lastIdx;
sig.entryPrice = close[lastIdx];

ArrayResize(signals, ArraySize(signals) + 1);
signals[ArraySize(signals) - 1] = sig;

//--- Place arrow buffer
if(InpDrawArrowOnSignal)
  {
   if(isSell)
      BufferSell[lastIdx] = high[lastIdx] + InpOffset * point;
   else
      BufferBuy[lastIdx]  = low[lastIdx]  - InpOffset * point;
  }

Шаг 8 — обработка алертов и уведомление трейдера в реальном времени

Алерты отправляются только для последнего бара (i == rates_total-1), чтобы при первой загрузке индикатора не выдавать множество уведомлений об исторических сигналах. Статическая переменная lastAlertTime хранит временную метку последнего алерта; если время бара нового сигнала отличается от нее, формируется алерт. Вспомогательная функция SendAlert проверяет входные параметры: если включены звуковые алерты, она вызывает Alert(); если включены Push-уведомления — SendNotification. Сообщение алерта содержит символ, текущий таймфрейм, тип сигнала (BUY/SELL) и время бара. Это гарантирует, что трейдер не упустит свежий сетап, даже находясь вдали от экрана.

//--- SIGNAL ALERT: when setup completes (arrow placed)
//--- Only alert on the most recent bar (to avoid spamming historical alerts)
if(i == rates_total - 1 && time[lastIdx] != lastAlertTime)
  {
   string signalType = (isSell) ? "SELL" : "BUY";
   string alertMsg = StringFormat("CRT MTF | %s %s | %s signal at %s",
                                  Symbol(), EnumToString(_Period), signalType, TimeToString(time[lastIdx]));
   SendAlert(alertMsg);
   lastAlertTime = time[lastIdx];
  }

Шаг 9 — отрисовка объектов: цветные свечи, прямоугольник диапазона и линии входа, TP и SL

Все графические построения выполняются один раз при появлении нового бара, чтобы не создавать лишние объекты и не загромождать график. Сначала мы удаляем все объекты с префиксом "CRT_" с помощью ObjectsDeleteAll. Затем мы заново формируем массив drawnSignalIds, чтобы отслеживать уже обработанные идентификаторы сигналов (заданные временем свечи диапазона). Для каждого сигнала в массиве signals мы проверяем, отображен ли уже сигнал с его signalId; если нет, отрисовываем все графические элементы. К ним относятся:

  1. Цветные прямоугольники позади каждого бара младшего таймфрейма, относящегося к свече диапазона (с цветом InpSellRangeColor или InpBuyRangeColor), и каждого бара сигнальной свечи (с цветом InpSellSignalColor или InpBuySignalColor). Это наглядно выделяет две ключевые свечи старшего таймфрейма.
  2. Полупрозрачный оранжевый прямоугольник очерчивает границы свечи диапазона старшего таймфрейма (от максимума до минимума) на всем ее временном интервале, создавая четкий ориентир в виде зоны.
  3. Пунктирная линия входа начинается после закрытия сигнальной свечи (lineStartTime = время сигнальной свечи + htfPeriodSeconds) и продолжается вправо на InpLineExtendBars баров младшего таймфрейма. В конце линии размещается текстовая метка ("Buy Entry" или "Sell Entry").
  4. Линии TP и SL (пунктирные) строятся по выбранным уровням: по умолчанию для продажи TP = минимум диапазона, SL = максимум сигнальной свечи; для покупки TP = максимум диапазона, SL = минимум сигнальной свечи. Параметр InpFlipTPSL позволяет при необходимости поменять эти значения местами.

Все объекты создаются со свойствами OBJPROP_BACK = true, чтобы располагаться позади ценовых баров, и OBJPROP_SELECTABLE = false, чтобы исключить случайное перетаскивание. Благодаря этому график остается наглядным и интерактивным.

//--- Draw objects on new bar only
static datetime lastDrawTime = 0;
datetime curTime = time[rates_total - 1];

if(curTime != lastDrawTime)
  {
   ObjectsDeleteAll(0, "CRT_");
   lastDrawTime = curTime;
  }

//--- Example: drawing entry line
string entryLineName = "CRT_Entry_" + IntegerToString(signalId);
if(ObjectFind(0, entryLineName) < 0)
  {
   ObjectCreate(0, entryLineName, OBJ_TREND, 0, lineStartTime, entryPrice, lineEndTime, entryPrice);
   ObjectSetInteger(0, entryLineName, OBJPROP_COLOR, InpEntryLineColor);
   ObjectSetInteger(0, entryLineName, OBJPROP_STYLE, STYLE_DASH);
   ObjectSetInteger(0, entryLineName, OBJPROP_WIDTH, 1);
   ObjectSetInteger(0, entryLineName, OBJPROP_BACK, false);
   ObjectSetInteger(0, entryLineName, OBJPROP_RAY_RIGHT, false);
  }

Шаг 10 — оптимизация и граничные случаи — надежность и быстродействие

Несколько проектных решений обеспечивают эффективную работу индикатора даже при большом объеме исторических данных. Во-первых, сопоставление с помощью бинарного поиска имеет сложность O(log n) для каждого бара младшего таймфрейма и позволяет избежать линейного перебора, замедляющего обработку тысяч баров. Во-вторых, мы проверяем условия CRT только на последнем баре младшего таймфрейма внутри каждой свечи старшего. Это сокращает объем вычислений максимум на 95%, поскольку большинство младших баров находятся внутри свечи и не являются последними. В-третьих, проверка дубликатов объектов с помощью массива drawnSignalIds предотвращает повторную отрисовку одного и того же сетапа на каждом тике. В-четвертых, если доступно менее трех свечей старшего таймфрейма, мы сразу завершаем обработку, предотвращая ошибки.

Обработка граничных случаев: гэпы выходного дня и нерегулярные торговые часы не нарушают работу бинарного поиска, поскольку сравнивается точное время начала и окончания. Бары младшего таймфрейма за пределами самой ранней свечи старшего таймфрейма игнорируются (htfIndexMap[i] = -1). Дополнительная возможность перестановки TP/SL реализована без деления на ноль и формирования недопустимых уровней. При этом индикатор не перерисовывается: сигналы фиксируются при закрытии сигнальной свечи, что обеспечивает надежность тестирования на исторических данных.


Тестирование и результаты

Чтобы протестировать индикатор, скомпилируйте файл CRT_MTF.mq5 в MetaEditor (нажмите F7). Добавьте скомпилированный индикатор на любой график младшего таймфрейма (M1, M5, M15, M30, H1). Индикатор автоматически загружает данные старшего таймфрейма и отображает все исторические сетапы CRT в пределах периода анализа.

Ручное тестирование на исторических данных: прокрутите график влево. Вы увидите цветные прямоугольники позади исторических свечей, задающих диапазон, и сигнальных свечей, линии входа с метками, уровни TP/SL и стрелки-маркеры точно в местах появления сигналов. Поскольку индикатор не перерисовывается, прошлые сигналы остаются неизменными — это идеально подходит для оценки эффективности стратегии на исторических данных.

Приведенная ниже GIF-анимация показывает, как при прокрутке графика к прошлым данным индикатор отображает ранее сформированные сетапы CRT.

CRT_MTF live demo chart walk back

Рис. 2. Тестирование на индексе Volatility 75 (1s), M5.

При закрытии новой сигнальной свечи индикатор выдает алерт (звуковое оповещение и/или Push-уведомление) и сразу отображает линию входа, TP, SL и стрелки. Линии продлеваются вправо на количество баров, указанное в параметре InpLineExtendBars. Индикатор протестировали на графиках M1, M5, M15, M30 и H1 со старшими таймфреймами от H1 до дневного; на объеме исторических данных до 10 000 баров снижения производительности не наблюдалось.


Заключение

Мы разработали инженерное решение задачи CRT: индикатор на языке MQL5, который загружает данные OHLC старшего таймфрейма, сопоставляет каждый бар младшего таймфрейма с соответствующей свечой старшего и отображает ее диапазон, тело и тени на активном графике. Обнаружение паттернов CRT выполняется только после закрытия сигнальной свечи старшего таймфрейма, что гарантирует отсутствие перерисовки сигналов.

Кроме того, индикатор отмечает подтвержденные входы стрелками, строит уровни входа, TP и SL по диапазону и экстремумам сигнальной свечи и выдает настраиваемые звуковые оповещения и Push-уведомления. Встроенные фильтры (размер тела свечи, смещения, исключение свечей малого диапазона и свечей на участках разрывов торговых сессий) и оптимизация производительности (сопоставление с помощью бинарного поиска и определение последнего бара) делают графическое наложение пригодным для объективного тестирования на исторических данных и совершения сделок в реальном времени.

Итак, вы получаете воспроизводимый инструмент CRT для работы на нескольких таймфреймах, основанный на правилах. Скомпилируйте прилагаемый файл CRT_MTF.mq5, добавьте полученный индикатор на любой график младшего таймфрейма, выберите старший таймфрейм и остальные настройки — и торгуйте, видя точный контекст старшего таймфрейма непосредственно на торговом графике.

.

Основные выводы

Основные выводы Описание
1. Бинарный поиск для сопоставления баров Бинарный поиск (O(log n)) при сопоставлении баров младшего таймфрейма со свечами старшего гораздо эффективнее линейного перебора, особенно при большом объеме исторических данных. Это критически важная оптимизация для любого индикатора, работающего с несколькими таймфреймами.
2. Определение последнего бара для точного формирования сигнала Сигнал CRT должен формироваться только на последнем баре младшего таймфрейма внутри сигнальной свечи старшего. Последовательный просмотр вперед до изменения индекса свечи старшего таймфрейма позволяет использовать цену закрытия этого бара как цену входа, исключая перерисовку.
3. Архитектура без перерисовки для тестирования на исторических данных Запись значений в буферы стрелок только после закрытия сигнальной свечи и использование фиксированных цен входа гарантируют неизменность исторических сигналов. Благодаря этому индикатор пригоден для надежного ручного и автоматизированного тестирования на исторических данных.
4. Исключение дублирования графических объектов Статический массив (drawnSignalIds[]) отслеживает уже отображенные сетапы и предотвращает повторное создание прямоугольников и линий на каждом тике. Это уменьшает загромождение графика и повышает производительность.
5. Ограничение частоты алертов с помощью временных меток Хранение времени последнего бара, по которому выдан алерт (lastAlertTime), и сравнение этого времени со временем текущего сигнального бара предотвращают повторные алерты по одному сетапу даже при многократном пересчете индикатора.
6. Настраиваемые TP/SL на основе структуры старшего таймфрейма Уровни TP и SL, рассчитанные непосредственно по экстремумам свечи, задающей диапазон, и сигнальной свечи (с возможностью перестановки), устраняют субъективность. Так паттерн, трактуемый по усмотрению трейдера, превращается в торговую систему на основе правил.
7. Устойчивость бинарного поиска к гэпам Использование точных временных границ (start ≤ bar time < start + period) позволяет учитывать гэпы выходного дня и нерегулярные торговые часы без нарушения сопоставления — в отличие от простого подсчета баров.
8. Двухпроходная обработка в OnCalculate Разделение отображения исторических сигналов (полный цикл) и формирования алертов в реальном времени (только последний бар) обеспечивает наглядное тестирование на исторических данных и своевременные уведомления без назойливых повторов.
9. Визуальное выделение свечей старшего таймфрейма Цветные прямоугольники позади каждого бара младшего таймфрейма, входящего в свечу диапазона или сигнальную свечу, создают наглядный контекст. Установка OBJPROP_BACK = true сохраняет читаемость ценового графика.
10. Продлеваемые линии для планирования Линии входа, TP и SL, построенные как горизонтальные трендовые линии с настраиваемым продлением вправо (InpLineExtendBars), помогают трейдерам видеть будущие ценовые цели непосредственно на графике.
11. Математическое описание условий CRT Правила продажи и покупки (бычий диапазон + более высокий максимум + закрытие ниже верхней границы диапазона + небольшое тело свечи + смещение минимума) выражаются точными неравенствами. Это исключает догадки и обеспечивает единообразное распознавание паттернов в любых рыночных условиях.
12. Синхронизация данных нескольких таймфреймов Загрузка данных старшего таймфрейма один раз за вызов OnCalculate() и их хранение в массиве пользовательских структур (HTFCandle[]) позволяют избежать избыточного копирования. Этот подход можно повторно использовать в любом индикаторе, которому требуется контекст старшего таймфрейма.

Приложения

Имя файла Тип Версия Описание
CRT_MTF.mq5 Индикатор 2,90 Полный исходный код индикатора CRT для работы на нескольких таймфреймах. Скомпилируйте код в MetaEditor и добавьте полученный индикатор на любой график младшего таймфрейма.

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

Прикрепленные файлы |
CRT_MTF.mq5 (17.59 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (1)
Mustafa Nail Sertoglu
Mustafa Nail Sertoglu | 12 мая 2026 в 11:19

Спасибо за код, но в нём отсутствует терминал DOWN; а в журнале записано следующее:

12.05.2026 13:22:10.388 CB_CRT_MTF (EURUSD,M1) индикатор работает слишком медленно, 2016 мс. Перепишите индикатор, пожалуйста


Создание объектно-ориентированного механизма статистического арбитража на основе Z-показателя в MQL5 Создание объектно-ориентированного механизма статистического арбитража на основе Z-показателя в MQL5
В статье показано, как реализовать готовый к практическому применению модуль расчёта Z-показателя на языке MQL5 с использованием объектно-ориентированного подключаемого файла. Библиотека вычисляет скользящее среднее и стандартное отклонение генеральной совокупности, предоставляет параметр сдвига для запросов по историческим данным и позволяет избежать избыточных вычислений на тиках, выполняя расчёты при закрытии бара. Советник открывает позиции в соответствии с установленными правилами при достижении положительных/отрицательных пороговых значений сигмы и закрывает их при возврате к среднему значению; пользовательский индикатор обеспечивает визуальную проверку.
Версионирование параметров советника в MQL5 с помощью файлов Версионирование параметров советника в MQL5 с помощью файлов
В этой статье рассказывается, как реализовать версионирование параметров на языке MQL5 с помощью бинарных файлов и упакованных структур. Здесь показано, как с помощью функций FileWriteStruct и FileReadStruct в режиме FILE_BIN записывать и считывать записи фиксированного размера, содержащие номера версий, временные метки и контрольную сумму. Вы также узнаете, как обнаруживать изменения с помощью контрольных сумм, безопасно добавлять записи в конец файла и загружать последнюю конфигурацию, не перезаписывая предыдущие настройки.
Renko внутри советника: построение по тикам без offline-графика Renko внутри советника: построение по тикам без offline-графика
Покажу, как строить Renko прямо внутри советника на MQL5 — без offline-графиков и пользовательских символов. Разберём развороты на один и два кирпича, обработку нескольких кирпичей за тик и типичные ошибки. Проверим повторяемость построения и выгрузим результат в CSV. К статье приложены класс CRenkoBuilder и демонстрационный советник.
Создание системы торговой аналитики (Часть 2): Как фиксировать закрытые сделки и передавать данные в формате JSON с помощью MQL5 Создание системы торговой аналитики (Часть 2): Как фиксировать закрытые сделки и передавать данные в формате JSON с помощью MQL5
Мы создаем легковесный мост, который фиксирует закрытые сделки в MetaTrader 5 и отправляет данные о них во внешний бэкенд по HTTP в формате JSON. Он использует OnTradeTransaction для обнаружения событий, считывает сведения из истории сделок, формирует данные в формате JSON и отправляет их POST-запросом через WebRequest. Для проверки обмена данными используется локальный API Flask. Так мы получаем работающий механизм передачи торговых данных за пределы терминала.