Разработка классического индикатора «Крестики-нолики» на языке MQL5
Введение
В MetaTrader 5 отсутствует встроенный график «Крестики-нолики», а традиционные индикаторы в основном ориентированы на привязанные ко времени ценовые бары. Это затрудняет изучение чистого движения цен без искажения во времени. Для преодоления этого ограничения разработаем на языке MQL5 собственный индикатор «Крестики-нолики», который использует фиксированный размер клетки и логику разворота для более наглядного отображения структуры рынка, не полагаясь на формирование свечей с привязкой ко времени.
Индикатор преобразует исходные данные о ценах в структурированные колонки X и O, что делает его полезным для выявления трендов, прорывов, уровней поддержки и сопротивления, а также классических графических паттернов. Для достижения этой цели мы будем:
- определять базовую цену,
- строить конструкцию «Крестики-нолики»,
- выявлять тренд и развороты,
- устанавливать масштаб индикатора,
- отображать график в отдельном окне с помощью объектов MQL5.
Обзор проекта и план его реализации
Перед программной реализацией индикатора на MQL5 имеет смысл определить, что именно мы хотим создать и как планируем это сделать. Основной принцип построения графиков «Крестики-нолики» существенно отличается от традиционных подходов к построению графиков, поскольку он основан на движении цены, а не на временной последовательности. План реализации представляет собой дорожную карту, которая разъясняет этапы и помогает отслеживать процесс разработки. В этой части опишем общую структуру индикатора и разделим проект на выполнимые этапы.
Что мы будем создавать
В отличие от традиционных методов построения графиков, в методе «Крестики-нолики» движение цены рассматривается как единственный релевантный аспект. Вместо того чтобы каждую минуту, час или день создавать новый столбец, график не меняется, если только цена не смещается на заранее определенный размер клетки. Полученный график помогает трейдерам сосредоточиться на наиболее значительных изменениях цен и дает более четкое представление о направлении рынка, исключая периоды незначительных движений.
График «Крестики-нолики» состоит из столбцов, содержащих отметки «X» и «O». Столбец крестиков обозначает рост цен, а столбец ноликов — их падение. По мере того, как рынок продолжает двигаться в том же направлении, в текущий столбец добавляются дополнительные крестики или нолики. Когда цена меняет направление на определенную величину, известную как величина разворота, начинается новый столбец в противоположном направлении.

Рассмотрим простой пример, где размер клетки равен 1.00, а базовая цена установлена на уровне 100.00. Разница между ценой закрытия и базовой ценой составляет 5.00, если последующая свеча закроется на уровне 105.00. На графике отмечено пять крестиков, потому что это изменение цены соответствует пяти завершенным клеткам, каждая из которых представляет собой изменение на 1.00.

Если цена впоследствии поднимется до 108.00, к ней добавятся три дополнительных символа крестика, поскольку цена прошла еще три интервала-клетки.

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

Этот метод помогает выделить значительные изменения на рынке, предотвращая постоянное изменение структуры графика из-за незначительных колебаний цен.
Исключая влияние времени и небольших колебаний рынка, графики «Крестики-нолики» обеспечивают более наглядное отображение динамики цен. Полученная структура упрощает анализ трендов, прорывов и важных рыночных уровней, выделяя самые значительные колебания. Чтобы организовать этот процесс, создадим индикатор для MetaTrader 5, который преобразует цены в столбцы X/O и отображает их в отдельном окне индикатора.
План реализации
Перед началом реализации следует составить четкий план процесса разработки. В этой части мы рассмотрим процесс создания индикатора «Крестики-нолики», начиная с обработки исходных данных о цене и заканчивая построением готового графика.
Определение базовой цены:
На этом этапе определим базовую цену как первую доступную цену закрытия. Все последующие движения отсчитываются от нее с использованием фиксированного размера клетки.
Построение структуры данных «Крестики-нолики»:
Начнем с преобразования исходных рыночных цен в структурированные единицы «Крестики-нолики», которые станут основой нашего графика. Будем последовательно обрабатывать цены закрытия, сравнивать каждое значение с последним уровнем конструкции «Крестики-нолики» и преобразовывать разницу в целые клетки, используя фиксированный размер клетки. Перемещения размером менее одной клетки игнорируются.
Определение направления тренда и разворотов:
На этом этапе мы определяем логику тренда и правила разворота, которые управляют реакцией структуры на движение цены. Сначала определяем направление последнего движения цены, сравнивая текущее изменение цены с предыдущим значением «Крестики-нолики». Это позволяет нам классифицировать движение как восходящее или нисходящее. Если мы впервые создаем тренд, сразу же начинаем строить конструкцию «Крестики-нолики», добавляя необходимое количество перемещений клеток в этом направлении после установки начального тренда на основе обнаруженного направления.
После того как тренд установлен, для каждой сформированной клетки добавляем новые значения «Крестики-нолики» и продолжаем расширять ее всякий раз, когда цена движется в том же направлении. Однако мы не спешим возвращаться к прежним позициям, когда цена движется против текущего тренда. Вместо этого мы используем традиционный критерий разворота «Крестики-нолики», согласно которому разворот подтверждается только тогда, когда цена перемещается в противоположном направлении как минимум на три клетки. При выполнении этого условия мы разворачиваем направление тренда и начинаем создавать новый столбец в другом направлении.
Настройка диапазона окна индикатора:
На этом этапе мы настраиваем видимую область окна индикатора таким образом, чтобы вся конструкция «Крестики-нолики» корректно отображалась на графике. Для этого мы сканируем все значения «Крестики-нолики», хранящиеся в нашем наборе данных, и определяем самые высокие и самые низкие достигнутые уровни цен. Эти значения определяют естественные границы конструкции «Крестики-нолики».
После определения этих крайних значений добавляем небольшой буфер, используя размер клетки, чтобы немного расширить как верхний, так и нижний пределы. Это предотвращает чрезмерное сжатие графика по краям и обеспечивает лучшую визуальную четкость при отображении крестиков и ноликов. Наконец, чтобы MetaTrader 5 мог автоматически изменять размер окна индикатора, мы применяем эти измененные значения к настройкам масштабирования индикатора. Вне зависимости от изменений на рынке это гарантирует, что вся конструкция «Крестики-нолики» всегда будет видна и надлежащим образом масштабирована.
Отображение графика «Крестики-нолики» в окне индикатора:
На заключительном этапе мы преобразуем все обработанные данные «Крестики-нолики» в видимый график, отрисовывая его непосредственно в окне индикатора с помощью графических объектов. Теперь у нас уже есть полный набор значений «Крестики-нолики», отражающих движение рынка в структурированной форме. Теперь мы перебираем эти значения и определяем, как каждая точка должна отображаться: либо как крестик при движении вверх, либо как нолик при движении вниз, в зависимости от направления тренда.
Мы создаем или обновляем объект диаграммы и используем временные и ценовые координаты для его правильного размещения по каждому значению «Крестики-нолики». Для того чтобы столбцы X и O визуально выравнивались в виде четкой и читаемой структуры, мы также следим за правильным расстоянием между символами. Для обеспечения выравнивания столбцов мы перед построением корректируем расстояния между символами, чтобы гарантировать равномерное размещение. Затем обновляем график, чтобы отобразить вновь созданные объекты.
Реализация на языке MQL5
В этой главе займемся реализацией на языке MQL5 всего того, что обсуждалось в предшествующих разделах.
Определение базовой цены
Как обсуждалось в плане реализации, первым шагом является определение базовой цены.
Пример:#property indicator_separate_window #property indicator_buffers 0 #property indicator_plots 0 //--- User input parameters input double InpPFSize = 1; // Box size (price movement per brick) input int InpNumPF = 100; // Maximum number of PF (Point and Figure) displayed input color InpUpColor = clrGreen; // Color for upward movement (X) input color InpDnColor = clrRed; // Color for downward movement (O) //--- Array storing Point & Figure prices double pfclose[]; //--- Current number of stored pf int pfSize = 0; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- indicator buffers mapping //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int32_t &spread[]) { //--- Ensure enough data if(rates_total < InpNumPF + 2) return 0; //--- First run initialization if(prev_calculated == 0) { ArrayResize(pfclose, 1); pfclose[0] = close[0]; // base price pfSize = 1; } //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+
То есть:
Во-первых, укажем, что индикатор будет отображаться в отдельном окне под основным графиком. Эта настройка важна, поскольку графику «Крестики-нолики» требуется отдельная область отображения; традиционные свечи в ней не используются. Поскольку мы не используем традиционные индикаторы в виде линий или гистограмм, мы также отключаем использование обычных буферов и построений индикаторов. Вместо этого мы будем создавать и иллюстрировать конструкцию «Крестики-нолики» вручную.
Затем определим пользовательские параметры ввода. Размер клетки — ключевой параметр: он определяет минимальное перемещение, необходимое для добавления одного крестика или нолика. В данной реализации размер клетки рассматривается как фиксированный ценовой интервал, а не как процентное значение или значение, основанное на данных индикатора. Это означает, что он напрямую показывает, насколько должна измениться цена, прежде чем мы зарегистрируем новый шаг «Крестики-нолики». Все вычисления в системе основаны на этом значении, полученном путем простого сложения и вычитания в зависимости от изменения цены.
Важно:
Размер клетки зависит от торгового символа, поскольку на каждом рынке действуют разные ценовые шкалы и уровни волатильности, поэтому универсально применить единое фиксированное значение ко всем инструментам невозможно. Например, в паре EURUSD размер клетки 0.0010 соответствует 10 пипсам, то есть цена должна измениться как минимум на 0.0010, прежде чем сформируется новый крестик или нолик. Однако таймфрейм также играет важную роль в том, как ведет себя этот размер клетки.
Например, значение 0.0010 может подойти для M5, но для W1 оно может оказаться слишком маленьким и привести к созданию слишком плотного графика «Крестики-нолики». Поэтому, как правило, лучше корректировать размер клетки в соответствии с используемым таймфреймом для более сбалансированной и лучше читаемой конструкции «Крестики-нолики».
| Символ | Рекомендуемый размер клетки |
|---|---|
| EUR/USD – Евро/Доллар США | 0.0010 (10 пипсов) |
| GBP/USD – Британский фунт/Доллар США | 0.0010 (10 пипсов) |
| USD/JPY – Доллар США/Японская иена | 0.10 (10 пипсов) |
| AUD/USD – Австралийский доллар/Доллар США | 0.0010 (10 пипсов) |
| USD/CAD – Доллар США/Канадский доллар | 0.0010 (10 пипсов) |
| XAU/USD – Золото/Доллар США | 5.0 |
Второй параметр ограничивает количество значений «Крестики-нолики», которые будут храниться и отображаться, что поможет нам контролировать использование памяти и четкость графика. Кроме того, мы зададим два цвета, один для движения вверх, другой для движения вниз, которые впоследствии будут использоваться для визуального различения построений в виде крестиков и в виде ноликов. Затем мы создадим массив, который будет хранить обработанные значения «Крестики-нолики». Этот массив представляет собой основную структуру данных, которая будет хранить преобразованные изменения цен. Помимо этого, мы поддерживаем счетчик, который отслеживает, сколько значений «Крестики-нолики» было сгенерировано на данный момент. Основная функция вычислений — это то, с чего начинается вся обработка данных при появлении новой рыночной информации. Прежде чем что-либо предпринимать, сначала проверим, достаточно ли исторических данных для работы.
Это гарантирует, что индикатор не будет пытаться производить вычисления на основе недостаточной истории цен. При первом запуске индикатора мы инициализируем конструкцию «Крестики-нолики», изменяя размер массива хранения и назначая самую первую цену закрытия в качестве базовой цены. Эта базовая цена служит основой для всех будущих расчетов в индикаторе «Крестики-нолики». С этого момента каждое изменение цены будет измеряться относительно этого значения.
Построение структуры данных «Крестики-нолики»
Это второй этап плана реализации, во время которого мы сосредоточимся на формировании данных «Крестики-нолики».
Важно:
По мере продвижения будем выделять конкретные разделы кода, относящиеся к каждому этапу реализации, обеспечивая четкое понимание каждой части в отдельности, не смешивая ее содержание с объяснениями из предыдущих разделов.
Пример:
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int32_t &spread[]) { //--- Ensure enough data if(rates_total < InpNumPF + 2) return 0; //--- First run initialization if(prev_calculated == 0) { ArrayResize(pfclose, 1); pfclose[0] = close[0]; // base price pfSize = 1; } //--- Set loop start index int start = (prev_calculated == 0) ? 1 : prev_calculated; //--- Build Point & Figure data for(int i = start; i < rates_total; i++) { double price = close[i]; double pf_size = InpPFSize; double last = pfclose[0]; double diff = price - last; int pf_no = (int)MathFloor(MathAbs(diff) / pf_size); if(pf_no <= 0) continue; } //--- return value of prev_calculated for next call return(rates_total); }
То есть:
Перед созданием данных для «Крестики-нолики» необходимо определить подходящую начальную точку цикла. Поскольку в настоящее время нет исторических вычислений, начнем с индекса 1, если индикатор запускается впервые. В противном случае мы используем prev_calculated для продолжения обработки с последней обработанной позиции. Это повышает скорость, поскольку индикатор обновляет только самые последние данные, вместо того чтобы перестраивать всю структуру при каждом цикле вычислений.
После определения правильной начальной точки мы входим в цикл, который проходит по всем ценовым данным, начиная с исходного индекса и заканчивая последним баром. Внутри этого цикла мы обрабатываем каждую цену шаг за шагом, чтобы построить и обновить конструкцию «Крестики-нолики» на основе новых рыночных движений. Мы берем текущую цену закрытия из данных графика, которая отражает последнюю рыночную стоимость на каждом шаге цикла. На данный момент это цена, которую мы будем использовать в качестве отправной точки для всех сравнений. Затем определяем размер клетки, который представляет собой фиксированный ценовой интервал, определяющий, какое перемещение необходимо для формирования нового единичного элемента «Крестики-нолики». Это значение остается постоянным и используется на протяжении всех расчетов для преобразования исходного движения цены в структурированные шаги X и O.
Программа извлекает последнее сохраненное значение «Крестики-нолики» из нашего массива данных. Оно представляет собой самый последний подтвержденный уровень в нашей конструкции «Крестики-нолики» и служит отправной точкой для измерения нового движения цены. Вычисляем разницу между текущей рыночной ценой и последним значением «Крестики-нолики». Эта разница показывает, насколько цена отклонилась от последнего подтвержденного уровня «Крестики-нолики», и становится основой для принятия решения о том, сколько клеток следует добавить или не произошло ли существенного движения.
Далее программа вычисляет абсолютное значение разницы цен, чтобы мы могли измерить движение независимо от того, движется цена вверх или вниз. Затем мы делим это движение на размер клетки, что показывает, сколько полных ценовых интервалов было завершено. Поскольку метод «Крестики-нолики» учитывает только полностью завершенные клетки, мы применяем операцию округления вниз, чтобы исключить любые дробные значения, не достигающие полной клетки. Полученный результат затем преобразуется в целое число, что позволяет определить точное количество допустимых клеток «Крестики-нолики», сформированных текущим движением цены.
Определение направления тренда и разворотов
Это третий этап плана реализации, на котором мы преобразуем движение цены в клетки «Крестики-нолики» и решаем, продолжать тренд или развернуться.
Пример:
//--- Current trend direction (1 = up, -1 = down, 0 = none) int trend = 0; //+------------------------------------------------------------------+ //| Add new PF value at beginning of array | //+------------------------------------------------------------------+ void PF_Unshift(double value) { ArrayResize(pfclose, pfSize + 1); //--- Shift existing values to the right for(int i = pfSize; i > 0; i--) pfclose[i] = pfclose[i - 1]; //--- Insert newest value at index 0 pfclose[0] = value; pfSize++; } //+------------------------------------------------------------------+ //| Remove oldest PF value from array | //+------------------------------------------------------------------+ void pf_Pop() { if(pfSize > 0) pfSize--; }
Пример:
//--- Build Point & Figure data for(int i = start; i < rates_total; i++) { double price = close[i]; double pf_size = InpPFSize; double last = pfclose[0]; double diff = price - last; int pf_no = (int)MathFloor(MathAbs(diff) / pf_size); if(pf_no <= 0) continue; //--- Determine direction int direction = (diff > 0) ? 1 : -1; //--- Initialize trend if(trend == 0) { trend = direction; for(int b = 0; b < pf_no; b++) { last += trend * pf_size; PF_Unshift(last); } continue; } //--- Continue trend if(direction == trend) { for(int b = 0; b < pf_no; b++) { last += trend * pf_size; PF_Unshift(last); } } //--- Reversal condition else if(pf_no >= 3) { trend = direction; for(int b = 0; b < pf_no; b++) { last += trend * pf_size; PF_Unshift(last); } } //--- Limit PF size while(pfSize > InpNumPF) pf_Pop(); }
То есть:
Цель функции PF_Unshift() — поддержание упорядоченной по времени конструкции «Крестики-нолики», в которой самые свежие данные отображаются первыми. Размер массива сначала увеличивается для размещения нового элемента. Чтобы ничего не потерять, все текущие значения затем сдвигаются на один шаг вправо. Затем обновляется информация о последнем состоянии рынка посредством вставки нового значения «Крестики-нолики» в нулевой индекс в начале массива. Для обеспечения единообразия конструкции общее количество «Крестики-нолики» затем увеличивается.
Функция pf_Pop() ограничивает использование памяти, уменьшая pfSize, фактически игнорируя самые старые записи без сдвига массива. После этого определяем направление движения текущей цены. Для этого мы проверим, является разница между текущей ценой и последним значением «Крестики-нолики» положительной или отрицательной. Если цена выросла, назначаем направление вверх, а если снизилась, то направление вниз. Затем определяем, сформировался ли уже тренд.
Если это первый запуск системы, а тренд еще не сформировался, начинаем формирование тренда, используя текущее направление. После определения тренда перебираем количество завершенных элементов клеток, чтобы начать создание первой конструкции «Крестики-нолики». В зависимости от направления тренда прибавляем размер клетки к предыдущему размеру или вычитаем из него это значение, чтобы обновить предыдущее значение «Крестики-нолики». Обновленное значение затем включается в конструкцию. Когда тренд уже сформировался, мы оцениваем, продолжается ли текущее движение цены в том же направлении, что и существующий тренд. Если направление отражает существующий тренд, это говорит о том, что рынок по-прежнему движется в той же трендовой фазе без каких-либо разворотов. В этом случае после завершения каждого перемещения клетки мы добавляем новые значения «Крестики-нолики», чтобы продолжить построение конструкции.
В зависимости от текущего состояния тренда мы обновляем последнее значение «Крестики-нолики» для каждой клетки, перемещая ее на один шаг в направлении тренда: либо вверх, либо вниз. Затем текущий столбец расширяется путем добавления в конструкцию каждого нового значения. Когда текущее движение цены противоречит активному тренду, система проверяет наличие допустимой для разворота ситуации. В методологии «Крестики-нолики» изменение тренда подтверждается только в том случае, если цена перемещается как минимум на три клетки в противоположном направлении. Как только это условие выполняется, это указывает на существенное изменение рыночного давления, и тренд обновляется в новом направлении, заменяя предыдущий.
После подтверждения разворота мы начинаем создавать новый столбец в противоположном направлении. Для каждого завершенного перемещения клетки обновляем последнее значение «Крестики-нолики», перемещая его в новом направлении тренда, и вставляем его в конструкцию. Для управления памятью и поддержания конструкции в пределах контролируемых размеров мы устанавливаем ограничение на количество одновременно хранящихся значений. После добавления новых значений проверяем, не превысило ли общее количество сохраненных записей «Крестики-нолики» заданный пользователем максимальный предел. Если превысило, то мы повторяем процедуру удаления самых старых значений из конструкции, пока ее размер не уменьшится до допустимого порогового значения.
Настройка диапазона окна индикатора
На этом этапе мы будем динамически корректировать масштаб индикатора, чтобы обеспечить четкое отображение всей конструкции «Крестики-нолики» в окне индикатора.
Пример://--- Set indicator window range based on PF data if(prev_calculated == 0) { double maxPrice = 0; double minPrice = pfclose[0]; for(int i = 0; i < pfSize && i < InpNumPF; i++) { //--- Track maximum price if(pfclose[i] > maxPrice) maxPrice = pfclose[i]; //--- Track minimum price if(pfclose[i] < minPrice) minPrice = pfclose[i]; //--- Adjust indicator scale IndicatorSetDouble(INDICATOR_MAXIMUM, maxPrice + (InpPFSize * 2)); IndicatorSetDouble(INDICATOR_MINIMUM, minPrice - (InpPFSize * 2)); } }
То есть:
Эта часть отвечает за использование ранее сгенерированных данных «Крестики-нолики» для определения диапазона видимости окна индикатора. Для корректного масштабирования графика терминалу MetaTrader 5 необходимо знать максимальное и минимальное значения, которые должны быть видимыми, поскольку индикатор отображается в отдельном окне. Исходным шагом в этом процессе является проверка работоспособности индикатора в первый раз. Поскольку конструкция уже создана и содержит числа, которые мы хотим отобразить, нам нужно только однократно задать начальный диапазон окна.
Создаем две переменные для хранения максимальных и минимальных значений «Крестики-нолики» в наборе данных, чтобы определить соответствующий диапазон. Далее сравниваем каждое сохраненное значение с текущими максимальным и минимальным значениями, перебирая их все. При обнаружении меньшего значения оно становится новым минимумом, а при обнаружении большего значения — оно станет новым максимумом. К концу сканирования мы определили весь вертикальный диапазон, который занимает конструкция «Крестики-нолики».
Однако вместо того, чтобы использовать эти значения непосредственно, мы добавляем крошечный буфер выше максимального и ниже минимального значения, чтобы немного расширить диапазон. Для обеспечения естественной подгонки интервала к масштабу графика, буфер вычисляется с использованием размера клетки. В окне индикатора отображаются эти обновленные максимальные и минимальные значения. Это предотвращает прижимание графика к верхнему или нижнему пределу окна, позволяя терминалу MetaTrader 5 автоматически масштабировать отображение таким образом, чтобы все формы крестиков и ноликов удобно помещались в видимой области.
Отображение графика «Крестики-нолики» в окне индикатора
Как обсуждалось в плане реализации, заключительным этапом является визуализация обработанных данных «Крестики-нолики» в виде формаций X и O на отдельном графике индикатора.
Пример:
//--- Prefix for all chart objects string PREFIX; //--- Indicator subwindow index int indicator_window; //+------------------------------------------------------------------+ //| Draw or update a Point & Figure | //+------------------------------------------------------------------+ void DrawBrick(int index, datetime time, double price, bool up) { string name = PREFIX + IntegerToString(index); //--- Create object if it does not already exist if(ObjectFind(0, name) < 0) ObjectCreate(0, name, OBJ_TEXT, indicator_window, time, price); //--- Set color based on direction ObjectSetInteger(0, name, OBJPROP_COLOR, up ? InpUpColor : InpDnColor); //--- Set symbol (X or O) ObjectSetString(0, name, OBJPROP_TEXT, up ? "X" : "O"); //--- Move object to correct position ObjectMove(0, name, 0, time, price); } //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set indicator name IndicatorSetString(INDICATOR_SHORTNAME, "PnF"); //--- Get indicator subwindow index indicator_window = ChartWindowFind(ChartID(), "PnF"); //--- Create unique prefix for objects PREFIX = "PF_" + EnumToString(_Period) + "_"; //--- return(INIT_SUCCEEDED); }
Пример:
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int32_t rates_total, const int32_t prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int32_t &spread[]) { //--- Ensure enough data if(rates_total < InpNumPF + 2) return 0; bool need_redraw = false; //--- First run initialization if(prev_calculated == 0) { ArrayResize(pfclose, 1); pfclose[0] = close[0]; // base price pfSize = 1; } //--- Set loop start index int start = (prev_calculated == 0) ? 1 : prev_calculated; //--- Build Point & Figure data for(int i = start; i < rates_total; i++) { double price = close[i]; double pf_size = InpPFSize; double last = pfclose[0]; double diff = price - last; int pf_no = (int)MathFloor(MathAbs(diff) / pf_size); if(pf_no <= 0) continue; //--- Determine direction int direction = (diff > 0) ? 1 : -1; //--- Initialize trend if(trend == 0) { trend = direction; for(int b = 0; b < pf_no; b++) { last += trend * pf_size; PF_Unshift(last); need_redraw = true; } continue; } //--- Continue trend if(direction == trend) { for(int b = 0; b < pf_no; b++) { last += trend * pf_size; PF_Unshift(last); need_redraw = true; } } //--- Reversal condition else if(pf_no >= 3) { trend = direction; for(int b = 0; b < pf_no; b++) { last += trend * pf_size; PF_Unshift(last); need_redraw = true; } } //--- Limit PF size while(pfSize > InpNumPF) pf_Pop(); } //--- Drawing section int shift = 1; datetime t2 = iTime(_Symbol, PERIOD_CURRENT, shift); //--- Set indicator window range based on PF data if(need_redraw || prev_calculated == 0) { double maxPrice = 0; double minPrice = pfclose[0]; for(int i = 0; i < pfSize && i < InpNumPF; i++) { //--- Track maximum price if(pfclose[i] > maxPrice) maxPrice = pfclose[i]; //--- Track minimum price if(pfclose[i] < minPrice) minPrice = pfclose[i]; //--- Adjust indicator scale IndicatorSetDouble(INDICATOR_MAXIMUM, maxPrice + (InpPFSize * 2)); IndicatorSetDouble(INDICATOR_MINIMUM, minPrice - (InpPFSize * 2)); //--- Adjust spacing alignment if(i - 1 >= 1 && i + 1 < pfSize && i + 1 < InpNumPF && pfclose[i + 1] == pfclose[i - 1]) { shift++; } t2 = iTime(_Symbol, _Period, shift); //--- Determine upward movement bool up = (i + 1 < pfSize && i + 1 < InpNumPF && pfclose[i] > pfclose[i + 1]); DrawBrick(i, t2, pfclose[i], up); } //--- Refresh chart ChartRedraw(0); } //--- return value of prev_calculated for next call return(rates_total); }
Выводимая информация:

То есть:
Мы начали с создания функции DrawBrick(). Эта функция отвечает либо за создание нового графического объекта, либо за обновление существующего, чтобы графически представить каждый элемент единицы «Крестики-нолики» на графике. Процесс начинается с использования заданного индекса для создания уникального имени для каждого элемента «Крестики-нолики». Это гарантирует индивидуальную идентификацию и бесконфликтное управление каждым крестиком или ноликом на графике. Эта функция определяет, существует ли уже на графике объект с таким именем. Если новый текстовый объект еще не существует, он создается и размещается в дополнительном окне индикатора в заданных временных и ценовых координатах.
После создания объекта или подтверждения его существования его внешний вид изменяется в соответствии с направлением движения рынка. Указанный бычий цвет используется для оформления и отображения символа «X», если движение восходящее. Если движение нисходящее, это обозначается символом «О». Для точного определения местоположения объекта на графике используются его временные и ценовые значения. Для создания структурированного и удобочитаемого графика «Крестики-нолики» это гарантирует точное выравнивание каждого элемента как по горизонтали, так и по вертикали.
Функция OnInit() подготавливает индикатор «Крестики-нолики» до начала каких-либо вычислений. Она создает базовую среду, необходимую для правильной идентификации и управления всеми графическими объектами, которые будут созданы позже. Первый шаг — присвоение индикатору пользовательского имени. Это имя будет отображаться в списке индикаторов MetaTrader 5, помогая пользователю с легкостью идентифицировать инструмент «Крестики-нолики» на графике. Далее функция определяет вспомогательное окно, в котором будет отображаться индикатор. Поскольку графика «Крестики-нолики» отображается в отдельном окне, нам необходимо правильно определить и сохранить индекс этого окна, чтобы все объекты были размещены в нужном месте. После этого для всех объектов графика, сгенерированных с помощью этого индикатора, создается уникальный префикс. Чтобы предотвратить конфликты между объектами, относящимися к разным периодам времени, этот префикс включает текущий временной интервал. Кроме того, он упрощает управление, обновление или удаление только тех элементов, которые связаны с конкретным экземпляром индикатора.
Переменная need_redraw служит триггером визуального обновления графика. В начале каждого цикла вычислений ей присваивается значение false, и это означает, что обновление графика пока не требуется. Поскольку код обрабатывает данные о ценах и создает или обновляет нашу конструкцию, переменной need_redraw присваивается значение true только тогда, когда происходит фактическое изменение данных. Это происходит в трех случаях: первоначальное формирование тренда, продолжение тренда с новыми значениями «Крестики-нолики» и подтвержденный разворот. Поскольку в каждой из этих ситуаций в структуру добавляются новые крестики и нолики, визуальное представление графика устаревает и нуждается в обновлении. Использование этого флага предотвращает ненужные перерисовки графика. Перерисовка начинается только при существенных структурных изменениях в данных «Крестики-нолики», в отличие от непрерывного обновления графика с каждым изменением тика или бара.
Переменная shift установлена на 1, и это означает, что мы начинаем отсчет с самого последнего завершенного бара на графике, а не с текущего формирующегося бара. Это важно, потому что значения «Крестики-нолики» должны совпадать с полными данными о ценах, чтобы избежать нестабильных или изменяющихся расположений. Далее мы используем это значение сдвига, чтобы получить соответствующее время из графика. Функция iTime возвращает время открытия бара в указанной позиции сдвига. В таком случае она дает нам время предыдущей свечи, которое становится начальной точкой отсчета для размещения первого символа «Крестики-нолики». Совместно эти две линии определяют отправную точку для сопоставления значений с реальным временем на графике, обеспечивая начало процесса построения графика со стабильной и достоверной временной привязки.
Затем условие проверяет взаимосвязь между соседними значениями. Когда значение pfclose[i + 1] совпадает со значением pfclose[i - 1], это часто означает, что текущая точка находится в структурном узле, где движение прекращает быть плавным и однонаправленным. На практике это тот тип конфигурации, который возникает, когда ценовое движение готовится сместиться от восходящего столбца (X) к нисходящему столбцу (O) или наоборот. Вместо того чтобы сразу рассматривать это как изменение направления, код использует это как сигнал для обозначения интервала. Когда появляется такая закономерность, сдвиг усиливается. Это немного смещает следующий отображаемый элемент назад во времени, создавая визуальный промежуток между завершающимся и новым столбцами.
Затем мы пересчитываем временную координату, используя обновленный сдвиг, так что новый столбец начинается с отдельной позиции. Эта логика создает визуальный разрыв в опорных точках за счет увеличения временного сдвига, когда значения соседних элементов «Крестики-нолики» совпадают. В результате новые столбцы начинаются отдельно и не перекрывают друг друга. Затем программа определяет направление каждого элемента «Крестики-нолики», чтобы он корректно отображался на графике либо как «X», либо как «O». Логика сравнивает текущее значение «Крестики-нолики» со следующим значением в последовательности. Если текущее значение больше следующего значения, это означает, что рынок находится в фазе восходящего движения, поэтому элемент помечается как бычий. В противном случае это рассматривается как нисходящее движение, соответствующее медвежьему или нисходящему столбцу.
Именно это сравнение позволяет системе различать две основные конструкции графика «Крестики-нолики»: восходящие столбцы с крестиками (X) и нисходящие столбцы с ноликами (O). После определения направления элемент отображается на графике с использованием рассчитанной временной позиции и ценового уровня «Крестики-нолики». Функция рисования размещает либо «X», либо «O» в точно указанных координатах, используя флаг направления для определения того, какой символ и цвет следует использовать.
Важно:
Для обеспечения надлежащего расстояния между объектами «Крестики-нолики» и их полной видимости диапазон окна индикатора необходимо скорректировать в соответствии с размером клетки, используемым при расчете элементов «Крестики-нолики». Если этого не сделать, формы X и O могут быть сжаты или чрезмерно сгруппированы, что затруднит чтение графика и визуально исказит его, особенно если размер клетки велик или мал по отношению к движению цены. Мы обеспечиваем четкость, сбалансированность и простоту понимания каждой клетки, а также конструкцию «Крестики-нолики» в целом за счет масштабирования окна индикатора в соответствии с диапазоном «Крестики-нолики». Кроме того, важно отметить, что коэффициент масштабирования основного графика влияет на окно индикатора; при максимальном приближении отображение будет наиболее наглядным.
Чтобы избежать устаревших символов и визуального беспорядка во время перерасчета, удаляем объекты «Крестики-нолики», которые больше не нужны.
Пример:
//+------------------------------------------------------------------+ //| Delete all indicator objects | //+------------------------------------------------------------------+ void DeleteAll() { for(int i = ObjectsTotal(0) - 1; i >= 0; i--) { string name = ObjectName(0, i); //--- Delete only objects created by this indicator if(StringFind(name, PREFIX) == 0) ObjectDelete(0, name); } }
Пример:
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set indicator name IndicatorSetString(INDICATOR_SHORTNAME, "PnF"); //--- Get indicator subwindow index indicator_window = ChartWindowFind(ChartID(), "PnF"); //--- Create unique prefix for objects PREFIX = "PF_" + EnumToString(_Period) + "_"; //--- Clean old objects on start DeleteAll(); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Remove all objects when indicator is removed DeleteAll(); }
То есть:
Очистка всех графических объектов, созданных индикатором «Крестики-нолики», является задачей функции DeleteAll(). Принцип работы заключается в том, что программа проходит по каждому объекту, находящемуся на графике, и ищет его название. Функция использует уникальный префикс, присваиваемый каждому объекту, сформированному этим индикатором, в качестве фильтра для идентификации только тех объектов, которые являются его частью. После обнаружения совпадения элемент удаляется с графика. Это гарантирует, что не будут затронуты никакие другие индикаторы или нарисованные вручную элементы на графике и что будут удалены только элементы «Крестики-нолики».
Эта функция крайне важна для сброса графика перед началом работы индикатора на этапе инициализации (OnInit). Функция DeleteAll() используется для удаления всех оставшихся объектов из предыдущих запусков после того, как было определено имя индикатора, создано соответствующее дополнительное окно и присвоен уникальный префикс объекта, основанный на периоде. Это предотвращает наложение или дублирование конструкций «Крестики-нолики» из предыдущих сессий и гарантирует, что индикатор начнет работу с чистым графическим изображением. Когда индикатор удаляется с графика на этапе деинициализации (OnDeinit), все созданные объекты очищаются с помощью той же функции. Это позволяет поддерживать чистоту графика и возвращать его в исходное состояние, гарантируя отсутствие остаточных символов X или O при закрытии индикатора.
Заключение
В этой статье мы создали полноценный индикатор «Крестики-нолики» в традиционном стиле на языке MQL5 и преобразовали исходное движение цены в четкий, структурированный график типа «Крестики-нолики». Вместо того чтобы полагаться на временные свечи, индикатор фокусируется только на значимых изменениях цены, используя фиксированный размер клетки для фильтрации рыночного шума и выделения базовой ценовой структуры.
Шаг за шагом мы создали индикатор, который:
- преобразует движение цены в фиксированные интервалы-клетки на основе заданного пользователем размера клетки;
- создает ряд данных «Крестики-нолики», который фиксирует только существенные изменения цен;
- определяет направление тренда и обрабатывает корректные развороты в соответствии с классическими правилами движения клетки;
- отфильтровывает незначительные колебания, так что отображаются только завершенные клетки «Крестики-нолики»;
- контролирует размер хранимых данных «Крестики-нолики» для обеспечения эффективности индикатора;
- масштабирует окно индикатора таким образом, чтобы оно соответствовало всей структуре графика;
- рисует символы X и O в отдельном окне индикатора с помощью графических объектов;
- регулирует расстояние между столбцами, чтобы они оставались чистыми и выровненными;
- удаляет устаревшие объекты, чтобы график оставался точным и визуально актуальным.
В результате мы получили наглядный и практичный график «Крестики-нолики», который устраняет отвлекающие факторы, связанные с временными колебаниями рынка, и помогает сосредоточиться на действительно важных вещах: направлении движения цены, структуре и разворотах.
Кроме того, этот индикатор может послужить прочной основой для более совершенных инструментов. Теперь эту систему можно расширить, добавив обнаружение прорывов, распознавание паттернов, генерацию сигналов или даже автоматизированную торговую логику, основанную на рыночной конструкции «Крестики-нолики».
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/22875
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Построение моделей волатильности в MQL5 (Часть III): Реализация алгоритма SLSQP для оценки моделей
RiskGate: Централизованное управление рисками для нескольких советников
Создание индикатора для моделирования будущих свечей после пересечения EMA в MQL5
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Интересно, стал бы график P&F более похожим на стандартный , если бы использовать методику , описанную в этой статье?: MetaTrader 5: Создайте рынок под свою стратегию — Ренко/диапазон/объем, синтетические инструменты и стресс-тесты на пользовательских символах — Статьи MQL5
Спасибо, что разместили эту статью. Я уже давно торгую по методу Ренко, но не понимаю, как я мог пропустить её в мае. Возможно, я добавлю это в свой арсенал инструментов для торговли по методу Ренко.
Не совсем понимаю, что вы имеете в виду. Это как сравнивать яблоки с апельсинами. Существующий индикатор P&F — это, по сути, индикатор, причём индикатор в отдельном окне. Насколько я знаю, P&F действительно предназначен для использования в качестве самостоятельного инструмента. В отличие от него, скрипты, индикаторы и советники можно привязывать к пользовательским графикам Ренко, диапазона и равного объема. Очевидно, что это пользовательские графики, а не индикаторы.
Спасибо, что опубликовали это. Я уже давно торгую по методу Ренко, но понятия не имею, как я это пропустил в мае. Возможно, я добавлю это в свой арсенал методов Ренко.
Не совсем понимаю, что вы имеете в виду. Это как сравнивать яблоки с апельсинами. Нынешний индикатор P&F — это, ну, индикатор, причём индикатор в отдельном окне. Насколько я знаю, P&F действительно предназначен для использования в качестве самостоятельного инструмента. В отличие от него, скрипты, индикаторы и советники можно привязывать к пользовательским графикам Ренко, диапазона и равного объема. Очевидно, что это пользовательские графики, а не индикаторы.