English
preview
Создание традиционного индикатора Ренко с наложением на график в MQL5

Создание традиционного индикатора Ренко с наложением на график в MQL5

MetaTrader 5 — Индикаторы |
33 0
ALGOYIN LTD
Israel Pelumi Abioye

Введение

Обычные свечные графики содержат много краткосрочного шума, который мешает работе алгоритмических правил, искажает сигналы и затрудняет тестирование на исторических данных и визуальную проверку. Наша цель — устранить этот шум непосредственно на графике: представить движение цены фиксированными шагами и наложить результат на график MetaTrader 5. Для этого мы реализуем наложение Ренко в MQL5: движение отслеживается по ценам закрытия свечей, расчеты привязаны к определенной базовой цене (первой доступной цене закрытия), а новый кирпич формируется только при изменении цены на заданный пользователем размер кирпича. Индикатор хранит цены закрытия кирпичей в динамическом массиве и ограничивает объем отображаемой истории. Продолжение тренда обрабатывается обычным образом, а для подтверждения разворота требуется движение в противоположном направлении на два кирпича. Кирпичи отображаются объектами-прямоугольниками с минимумом лишних перерисовок; при переинициализации или удалении индикатор удаляет созданные им объекты.


    Обзор проекта и план реализации

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

    Что мы создаем

    В этой статье мы создадим индикатор наложения Ренко на график MetaTrader 5. В отличие от стандартного свечного графика, графики Ренко исключают временной фактор и отражают только движение цены. Каждый кирпич формируется только после движения цены на фиксированное расстояние, называемое размером кирпича, независимо от затраченного времени. Наш индикатор будет отображать кирпичи Ренко поверх обычного свечного графика, чтобы вы могли видеть их одновременно со свечами. Так вы получаете фильтрацию шума средствами Ренко, сохраняя контекст реальных рыночных свечей.

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

    Минимальное движение цены для формирования нового кирпича Ренко задается размером кирпича, выраженным ценовым интервалом. Размер кирпича необходимо подбирать с учетом различий в масштабах цен разных символов. Например, для EURUSD значение 0,0001 соответствует движению на один пипс и подходит для отражения типичных рыночных движений, а для XAUUSD больше подходит значение 1 из-за иного масштаба цены. Это обеспечивает единообразную работу индикатора на разных рынках и формирование значимых структур Ренко без изменения основной логики.

    Figure 1. Renko Indicator

    Мы используем цену закрытия каждой свечи для измерения движения цены и его преобразования в кирпичи Ренко. По величине этого движения можно определить, сколько кирпичей нужно добавить. Рассмотрим пример: базовая цена равна 1 000, а размер кирпича составляет 20 ценовых интервалов. Если следующая свеча закрывается на отметке 1 100, величина движения равна 1 100 минус 1 000. Делим результат (100) на 20 и получаем количество кирпичей — 5. Это означает, что вместо одной свечи мы отобразим пять восходящих кирпичей, каждый из которых отражает движение на 20 единиц цены.

    Теперь представьте, что следующая свеча закрывается на отметке 1 095. Относительно цены закрытия последнего кирпича, равной 1 100, движение составляет всего 5 ценовых интервалов. Поскольку это меньше размера кирпича, равного 20, новый кирпич не формируется. Этот пример показывает, как в Ренко игнорируются небольшие движения цены в свечах. Мы создаем систему, которая считывает данные свечей, измеряет изменения цены, рассчитывает по ним количество кирпичей с учетом заданного размера и последовательно добавляет их. В результате формируется более четкая структура, отображающая только значимые движения цены и облегчающая анализ трендов.

    План реализации

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

    Определение базовой цены

    Первый этап — определение базовой цены. От нее будут отсчитываться все кирпичи Ренко. Здесь начальным опорным значением служит цена закрытия первой доступной свечи.

    Figure 2. Base Price

    Этот этап критически важен, поскольку все последующие кирпичи рассчитываются относительно базовой цены. Без четкой начальной точки нельзя было бы надежно отслеживать изменения цены и определять момент завершения формирования кирпича. Привязка системы к базовой цене обеспечивает согласованность формирования кирпичей, определения тренда и разворотов. Кроме того, мы объявим массив для хранения цен закрытия всех кирпичей Ренко по мере их формирования. Это позволит эффективно отслеживать всю последовательность кирпичей и управлять ею на протяжении работы индикатора.

    Преобразование движения цены в кирпичи Ренко

    На втором этапе мы последовательно обрабатываем данные свечей и преобразуем исходное движение цены в упорядоченную структуру кирпичей Ренко. Мы задаем базовую цену значением close[0], которое служит начальной опорной точкой для всех расчетов. Затем начинаем обработку со второй свечи (то есть с i = 1 в цикле), пропуская первую, поскольку она уже используется как база. Затем сравниваем цену закрытия каждой свечи с текущим опорным значением — базовой ценой или ценой закрытия последнего сформированного кирпича. Разница цен делится на размер кирпича, чтобы определить, сколько кирпичей Ренко нужно сформировать по этому движению.

    Для наглядности рассмотрим простой пример из трех баров. Начнем с первого бара: его цена закрытия, например 1 000, используется как базовая цена. Это значение становится начальной опорной точкой для последующих расчетов и стартовым уровнем построения первой структуры Ренко.

    Figure 3. First Close

    Если второй бар закрывается на уровне 1 100, изменение относительно базовой цены 1 000 составляет 100 ценовых интервалов. При размере кирпича 20 получаются пять полностью сформированных кирпичей.

    Figure 4. Bricks Per Bar

    Это означает, что система формирует пять восходящих кирпичей, каждый из которых отражает движение на 20 ценовых интервалов. После формирования этих кирпичей цена закрытия последнего из них становится новой опорной точкой для дальнейших расчетов.

    Figure 5. Total Bricks

    На третьем баре исходная базовая цена больше не используется как точка отсчета. Новым ориентиром служит цена закрытия последнего завершенного кирпича (1 100). Например, при размере кирпича 20 ценовых интервалов и движении третьей свечи на 95 ценовых интервалов от нового опорного значения мы делим 95 на 20 и получаем четыре полных кирпича и остаток.

    Figure 6. Bricks Close

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

    Figure 7. Trend Continuation

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

    Определение направления тренда

    Это следующий этап, который мы реализуем при написании кода. На этом этапе направление тренда определяется по движению цены относительно закрытия последнего подтвержденного кирпича Ренко. Мы проверяем, достаточно ли этого движения для продолжения или разворота тренда.

    Если активного тренда нет, первое допустимое движение задает его начальное направление. Если цена движется в направлении текущего тренда, мы подтверждаем его продолжение и формируем новые кирпичи от цены закрытия последнего кирпича.

    Чтобы выявить возможную смену тренда, мы сравниваем текущую цену с ценой закрытия последнего подтвержденного кирпича. Например, при размере кирпича 20 и цене закрытия последнего кирпича 1 180 разворот рассматривается только после движения цены как минимум на 40 ценовых интервалов в противоположном направлении. Это означает, что цена должна снизиться до 1 140 или ниже. До достижения этого порога текущий тренд сохраняется, а меньшее обратное движение игнорируется.

    Figure 8. Trend Reversal

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

    Отображение кирпичей Ренко на основном графике

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

    Индикатор также позволяет пользователю задавать количество отображаемых кирпичей с помощью входного параметра. Пользователь может отображать только последние кирпичи, чтобы график оставался простым и лаконичным, без лишних исторических данных. При отрисовке каждый кирпич привязывается к последнему закрытому бару графика. Постепенный сдвиг или удаление старых кирпичей в соответствии с заданным пользователем лимитом поддерживает синхронизацию наложения Ренко с актуальными рыночными данными. Алгоритм постоянно обновляет график, добавляя новые объекты и изменяя существующие при поступлении ценовых данных и формировании новых кирпичей. В результате получается динамическое наложение в упрощенном формате Ренко, которое обновляется в реальном времени и наглядно отображает движение цены.

     

    Создание кирпичей Ренко

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

    Определение базовой цены

    Базовая цена служит отправной точкой для расчета всех кирпичей. В этой реализации опорной точкой служит цена закрытия первой доступной свечи; она же задает уровень открытия первого кирпича Ренко.

    Пример:

    #property indicator_chart_window
    #property indicator_buffers 0
    #property indicator_plots   0
    
    //--- User inputs
    input double InpBrickSize  = 1;   // Size of each Renko brick (price intervals)
    input int    InpNumBricks  = 100;   // Maximum number of bricks to display
    
    //--- Renko data storage
    double rclose[];   // Stores brick close prices
    int    rcSize = 0; // Current number of bricks
    
    //+------------------------------------------------------------------+
    //| Custom indicator initialization function                         |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- indicator buffers mapping
    
    //---
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+
    //| Custom indicator iteration function                              |
    //+------------------------------------------------------------------+
    int OnCalculate(const int rates_total,
                    const int 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 int      &spread[])
      {
    //---
    // Ensure enough data
       if(rates_total < InpNumBricks + 5)
          return 0;
    //--- First initialization
       if(prev_calculated == 0)
         {
          ArrayResize(rclose, 1);
          rclose[0] = close[0]; // First base price
          rcSize = 1;
         }
    
    //--- return value of prev_calculated for next call
       return(rates_total);
      }
    //+------------------------------------------------------------------+

    Пояснение:

    Первые три строчки задают отображение индикатора непосредственно на основном графике без использования буферов или графических построений индикатора. Кирпичи Ренко будут отображаться с помощью графических объектов, а не традиционным способом на основе буферов.

    Мы вводим два важных входных параметра, задаваемых пользователем. Первый — размер кирпича, то есть фиксированный ценовой интервал для внутренних расчетов индикатора. Все расчеты основаны на непосредственном изменении цены, а не на пунктах. Например, для EURUSD размер кирпича 0,0001 соответствует одному пипсу, то есть цена должна измениться на 0,0001, чтобы сформировался новый кирпич. Для XAUUSD размер кирпича 1 означает, что новый кирпич сформируется после изменения цены с 4 791 до 4 792. Это позволяет применять одну и ту же ценовую логику к разным инструментам без привязки к значениям пункта конкретного брокера. Второй параметр задает максимальное количество отображаемых кирпичей, позволяя пользователю регулировать видимый объем исторических данных.

    Первое условие проверяет достаточность исторических ценовых данных перед началом формирования кирпичей. Поскольку расчеты Ренко зависят от непрерывного движения цены, системе необходимо минимальное количество баров для корректного получения значимых результатов. Если объем доступных данных меньше требуемого порога, функция немедленно завершает работу, предотвращая некорректные вычисления или неполное формирование кирпичей. Второе условие обрабатывает полную или повторную инициализацию индикатора. Это происходит при добавлении индикатора на график, смене таймфрейма или обновлении графика. Во всех этих случаях прежнее внутреннее состояние теряет актуальность, поскольку кирпичи Ренко зависят от структуры данных конкретного таймфрейма.

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

    Наконец, счетчику кирпичей присваивается 1: в системе пока хранится только начальное опорное значение. Далее все новые движения цены измеряются относительно этой базовой цены, обеспечивая согласованное и корректное восстановление структуры Ренко из сброшенного начального состояния.

    Преобразование движения цены в кирпичи Ренко

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

    Пример:
    //+------------------------------------------------------------------+
    //| Custom indicator iteration function                              |
    //+------------------------------------------------------------------+
    int OnCalculate(const int rates_total,
                    const int 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 int      &spread[])
      {
    //---
    // Ensure enough data
       if(rates_total < InpNumBricks + 5)
          return 0;
    
    //--- First initialization
       if(prev_calculated == 0)
         {
          ArrayResize(rclose, 1);
          rclose[0] = close[0]; // First base price
          rcSize = 1;
         }
    
    //--- Build Renko bricks
       int start = (prev_calculated == 0) ? 1 : prev_calculated;
    
       for(int i = start; i < rates_total; i++)
         {
    
         }
    
    //--- return value of prev_calculated for next call
       return(rates_total);
      }
    //+------------------------------------------------------------------+

    Пояснение:

    Эта часть кода определяет, с какого места индикатор начинает обработку данных свечей. Сначала проверяется, обрабатывал ли индикатор какие-либо бары ранее. При первом запуске обработка начинается с первой допустимой исторической свечи, а не с текущей формирующейся. Это важно, поскольку текущая свеча еще изменяется и может не отражать завершенное движение цены. Если индикатор уже запускался, он не обрабатывает все заново, а продолжает с последнего обработанного места. Обработка только новых баров повышает эффективность расчетов и исключает повторную работу со старыми данными.

    Определив начальную точку, система перебирает все оставшиеся свечные бары. Бары обрабатываются последовательно, чтобы оценивать движение цены и поэтапно строить или обновлять структуру Ренко.

    Далее мы последовательно обработаем каждую свечу, преобразуя исходное движение цены в структуру Ренко. Для каждого бара берем цену закрытия и сравниваем ее с текущим опорным уровнем — ценой закрытия последнего подтвержденного кирпича Ренко.

    Пример:

    //--- Build Renko bricks
    int start = (prev_calculated == 0) ? 1 : prev_calculated;
    for(int i = start; i < rates_total; i++)
      {
       double price = close[i];
       double box   = InpBrickSize;
    
       double last = rclose[0];
       double diff = price - last;
       int bricks = (int)MathFloor(MathAbs(diff) / box);
    
       if(bricks <= 0)
          continue;
      }

    Пояснение:

    Здесь используется цикл, который обрабатывает все доступные свечи по отдельности, начиная с заданного индекса. Поскольку при инициализации базовой ценой стала цена закрытия бара с индексом 0, начальный индекс задается равным 1. Чтобы работать с полностью корректными данными, вычисления нужно начинать с индекса 1. При инициализации массив rclose[] предназначается для хранения цен закрытия всех формируемых кирпичей Ренко. Изначально размер массива равен 1: он содержит только close[0], используемый как базовая цена. По мере формирования кирпичей массив растет, а rclose[0] всегда хранит цену закрытия последнего кирпича Ренко — текущую опорную точку для дальнейших расчетов.

    Для каждой свечи сравниваем ее цену закрытия (close[i]) с rclose[0], определяя величину движения от уровня последнего подтвержденного кирпича. Так мы получаем общую величину движения цены, которую нужно преобразовать в кирпичи Ренко. Чтобы рассчитать количество кирпичей, сначала берем абсолютное значение разности цен с помощью MathAbs. Это важно, поскольку на данном этапе нас интересует только величина движения, а не его направление. Направление учитывается отдельно, на следующем этапе алгоритма, поэтому использование абсолютного значения обеспечивает одинаковый расчет кирпичей в обоих направлениях.

    Абсолютную величину движения делим на размер кирпича (InpBrickSize), определяя количество полных кирпичей. Затем применяем MathFloor, чтобы учитывать только полные кирпичи. При этом из текущего расчета исключается остаток движения, не достигающий размера кирпича, и структура Ренко формируется строго и последовательно. Результаты меньше одного полного кирпича игнорируются, чтобы последовательность Ренко отражала только значимые изменения цены.

    Определение направления тренда

    На этом этапе определяем, совпадает ли направление текущего движения цены с последним подтвержденным кирпичом Ренко или противоположно ему. Для этого сравниваем направление движения с текущим состоянием тренда и проверяем, достаточно ли кирпичей для подтверждения разворота.

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

    Пример:
    int trend  = 0; // Trend direction: 1 = up, -1 = down, 0 = undefined
    //------------------------------------------------+
    // Add new brick at the beginning of array        |
    //------------------------------------------------+
    void RC_Unshift(double value)
      {
       ArrayResize(rclose, rcSize + 1);
    
    // Shift elements to the right
       for(int i = rcSize; i > 0; i--)
          rclose[i] = rclose[i - 1];
    
    // Insert new value at index 0
       rclose[0] = value;
       rcSize++;
      }
    //------------------------------------------------+
    // Remove oldest brick (from end)                 |
    //------------------------------------------------+
    void RC_Pop()
      {
       if(rcSize > 0)
          rcSize--;
      }
    
    // Ensure enough data
    if(rates_total < InpNumBricks + 5)
       return 0;
    
    //--- First initialization
    if(prev_calculated == 0)
      {
       ArrayResize(rclose, 1);
       rclose[0] = close[0]; // First base price
       rcSize = 1;
       trend = 0;
      }
    //--- Build Renko bricks
    int start = (prev_calculated == 0) ? 1 : prev_calculated;
    
    for(int i = start; i < rates_total; i++)
      {
       double price = close[i];
       double box   = InpBrickSize;
    
       double last = rclose[0];
       double diff = price - last;
    
       int bricks = (int)MathFloor(MathAbs(diff) / box);
    
       if(bricks <= 0)
          continue;
    
    //--- Determining the Trend Direction
       int direction = (diff > 0) ? 1 : -1;
    
    //--- Start trend
       if(trend == 0)
         {
          trend = direction;
    
          for(int b = 0; b < bricks; b++)
            {
             last += trend * box;
             RC_Unshift(last);
            }
          continue;
         }
    //--- Continuation
       if(direction == trend)
         {
          for(int b = 0; b < bricks; b++)
            {
             last += trend * box;
             RC_Unshift(last);
            }
         }
    //--- Reversal (requires 2 bricks)
       else
          if(bricks >= 2)
            {
             trend = direction;
             for(int b = 0; b < bricks; b++)
               {
                last += trend * box;
                RC_Unshift(last);
               }
            }
    // Limit total number of bricks
       while(rcSize > InpNumBricks)
          RC_Pop();
      }
    

    Пояснение:

    Первая функция вставляет цену закрытия нового кирпича в начало rclose[], сохраняя порядок элементов. Сначала размер массива увеличивается на один элемент. Поскольку массивы в MQL5 имеют фиксированный размер, перед добавлением нового значения нужно явно увеличить размер массива. После изменения размера каждый элемент массива сдвигается на одну позицию вправо. Для этого выполняется обратный проход от последнего допустимого индекса. Каждый элемент копируется в ячейку со следующим индексом. Такой сдвиг позволяет записать новое значение по индексу 0, сохранив уже имеющиеся цены закрытия кирпичей.

    После сдвига всех элементов позиция с индексом 0 доступна для нового значения. Теперь в элемент с индексом 0 записывается цена закрытия нового кирпича. Благодаря этому цена закрытия последнего кирпича всегда находится в начале массива и доступна как текущий опорный уровень для дальнейших вычислений. Наконец, счетчик кирпичей (rcSize) увеличивается на единицу, учитывая добавленный кирпич. Счетчик отражает количество кирпичей, учитываемых системой в данный момент. Вторая функция исключает из учета самую старую цену закрытия кирпича в rclose[], уменьшая количество активных элементов. Сначала функция проверяет, сохранена ли в массиве хотя бы одна цена закрытия кирпича. Это крайне важно, поскольку попытка удалить данные из пустой структуры привела бы к недопустимому доступу к памяти.

    Если сохранен хотя бы один кирпич, функция уменьшает rcSize на единицу. Это не удаляет значение из памяти и не сдвигает элементы массива. Вместо этого уменьшается количество учитываемых элементов: последний элемент больше не относится к используемым данным Ренко. После уменьшения счетчика последнее значение не учитывается в дальнейших вычислениях и обновлениях. Этот метод эффективен, поскольку не требует лишнего сдвига элементов массива или перераспределения памяти. Это ограничивает количество учитываемых кирпичей Ренко заданным пользователем пределом, сохраняя производительность системы.

    Текущее направление структуры Ренко хранится в переменной trend. Возможны три состояния: 0 — тренд еще не определен, -1 — нисходящий тренд, 1 — восходящий. Начальное значение 0 важно: оно указывает, что система начинает работу заново и еще не определила направление рынка. Переменной trend явно присваивается 0 при первом запуске индикатора или его сбросе во время первичной инициализации. Это выполняется вместе с инициализацией rclose[] и заданием базовой цены. До анализа достаточного количества данных система остается в неопределенном состоянии: пока нет надежной истории движения цены для определения направления.

    С началом обработки ценовых данных система определяет направление движения по текущей разности цен. Положительной разности соответствует направление 1, отрицательной — -1. Так система получает предварительное направление движения по текущей динамике цены. Первое условие проверяет, определен ли тренд. Условие trend == 0 означает, что система только начинает работу и еще не выбрала направление. На этом этапе первое движение цены, удовлетворяющее условиям, определяет направление рынка. При выполнении условия система присваивает переменной trend обнаруженное направление движения (direction). Теперь система определяет движение рынка как восходящее или нисходящее. Это направление становится основой дальнейшего формирования кирпичей Ренко.

    После определения тренда система формирует все кирпичи текущего движения цены в цикле от b = 0 до bricks - 1. Переменная цикла b последовательно перебирает кирпичи. На каждом шаге уровень закрытия кирпича (last) обновляется прибавлением или вычитанием размера кирпича (box) в зависимости от направления тренда. Структура обновляется в реальном времени: каждое новое значение цены закрытия завершенного кирпича сразу сохраняется. Оператор continue переводит выполнение к следующей итерации цикла без дополнительной обработки.

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

    Отображение кирпичей Ренко на основном графике

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

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

    Пример:

    //--- User inputs
    input double InpBrickSize  = 1;   // Size of each Renko brick (price interval)
    input int    InpNumBricks  = 100;   // Maximum number of bricks to display
    input color  InpUpColor    = clrLime; // Color for bullish bricks
    input color  InpDnColor    = clrRed;  // Color for bearish bricks
    //--- Prefix for all chart objects
    string PREFIX = "RKO_";
    //------------------------------------------------+
    // Draw or update a Renko brick                   |
    //------------------------------------------------+
    void DrawBrick(int index, double top, double bottom, bool up)
      {
       string name = PREFIX + IntegerToString(index);
    
    // Define time range for the rectangle
       datetime t1 = iTime(_Symbol, _Period, index + 1);
       datetime t2 = iTime(_Symbol, _Period, index);
    
    // Create object only if it doesn't exist
       if(ObjectFind(0, name) < 0)
          ObjectCreate(0, name, OBJ_RECTANGLE, 0, t1, top, t2, bottom);
    
    // Update visual properties
       ObjectSetInteger(0, name, OBJPROP_COLOR, up ? InpUpColor : InpDnColor);
       ObjectSetInteger(0, name, OBJPROP_FILL, true);
       ObjectSetInteger(0, name, OBJPROP_BACK, true);
    
    // Update position
       ObjectMove(0, name, 0, t1, top);
       ObjectMove(0, name, 1, t2, bottom);
      }
    //------------------------------------------------+
    // Delete all indicator objects                   |
    //------------------------------------------------+
    void DeleteAll()
      {
       for(int i = ObjectsTotal(0) - 1; i >= 0; i--)
         {
          string name = ObjectName(0, i);
    
          // Only delete objects created by this indicator
          if(StringFind(name, PREFIX) == 0)
             ObjectDelete(0, name);
         }
      }
    
    bool need_redraw = false;
    //--- First initialization
    if(prev_calculated == 0)
      {
       ArrayResize(rclose, 1);
       rclose[0] = close[0]; // First base price
       rcSize = 1;
       trend = 0;
      }
    
    //--- Build Renko bricks
    int start = (prev_calculated == 0) ? 1 : prev_calculated;
    
    for(int i = start; i < rates_total; i++)
      {
       double price = close[i];
       double box   = InpBrickSize;
       double last = rclose[0];
       double diff = price - last;
    
       int bricks = (int)MathFloor(MathAbs(diff) / box);
    
       if(bricks <= 0)
          continue;
    //--- Determining the Trend Direction
       int direction = (diff > 0) ? 1 : -1;
    
    //--- Start trend
       if(trend == 0)
         {
          trend = direction;
    
          for(int b = 0; b < bricks; b++)
            {
             last += trend * box;
             RC_Unshift(last);
             need_redraw = true;
            }
          continue;
         }
    
    //--- Continuation
       if(direction == trend)
         {
          for(int b = 0; b < bricks; b++)
            {
             last += trend * box;
             RC_Unshift(last);
             need_redraw = true;
            }
         }
    //--- Reversal (requires 2 bricks)
       else
          if(bricks >= 2)
            {
             trend = direction;
    
             for(int b = 0; b < bricks; b++)
               {
                last += trend * box;
                RC_Unshift(last);
                need_redraw = true;
               }
            }
    
    // Limit total number of bricks
       while(rcSize > InpNumBricks)
          RC_Pop();
      }
    
    //--- Draw only when needed
    if(need_redraw || prev_calculated == 0)
      {
       for(int i = 1; i < rcSize && i < InpNumBricks; i++)
         {
          double top = MathMax(rclose[i - 1], rclose[i]);
          double bot = MathMin(rclose[i - 1], rclose[i]);
          bool up = (rclose[i - 1] > rclose[i]);
    
          DrawBrick(i, top, bot, up);
         }
    // Redraw chart only when changes occur
       ChartRedraw(0);
      }
    
    // Display info on chart
    Comment("Renko | Bricks: ", rcSize, " | Trend: ", trend);

    Пояснение:

    Первая функция отображает каждый кирпич Ренко на основном графике объектом-прямоугольником и обновляет его положение. Сначала каждому объекту кирпича нужно присвоить уникальное имя. Для этого индекс кирпича объединяется с заранее заданным префиксом. Индекс обеспечивает уникальную идентификацию каждого кирпича и позволяет впоследствии изменять отдельные кирпичи, не затрагивая остальные.

    Затем определяется положение по времени — важная часть размещения объектов на графике. Функция получает значения времени из ценового ряда по текущему индексу графика. Нужны временные значения двух соседних свечей: с текущим индексом и с индексом на единицу больше. Левая и правая границы прямоугольника определяются на основе этих двух временных значений. Хотя Ренко строится по цене, для отображения каждый кирпич привязывается к временному интервалу на графике. Индексация размещает кирпичи в соответствии со свечной структурой графика, позволяя соотносить их с исходной историей цен.

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

    Положение прямоугольника обновляется заданием временных и ценовых координат двух его углов, что обеспечивает правильное размещение кирпича по горизонтали и вертикали. Функция DeleteAll отвечает за удаление всех объектов Ренко при сбросе или удалении индикатора. Она перебирает объекты графика в обратном порядке, по префиксу определяет созданные индикатором и удаляет их, не оставляя лишних построений и не затрагивая другие объекты.

    Если имя объекта соответствует префиксу, объект определяется как часть системы Ренко и безопасно удаляется, не затрагивая остальные элементы графика. Эта функция вызывается при инициализации для удаления прежних построений и при деинициализации для удаления всех объектов индикатора. Переменная need_redraw служит флагом обновления графика: изначально она равна false, а перерисовка запускается только при создании новых кирпичей.

    В начале каждого цикла переменной need_redraw присваивается false: обновление отображения пока не требуется. Флаг получает true только при добавлении новых кирпичей: внутренняя структура изменилась, поэтому график нужно обновить. Это повышает эффективность: перерисовка выполняется только при необходимости. Для первоначального отображения перерисовка запускается при первом выполнении или при добавлении новых кирпичей. Поскольку каждый кирпич задается двумя последовательными значениями, при выполнении условия система перебирает кирпичи с индекса 1. Для каждого кирпича верхней границей становится большее из двух значений цены закрытия, а нижней — меньшее. Так кирпич корректно задается независимо от направления.

    Затем определяется направление кирпича. Кирпич считается восходящим, если предыдущая цена закрытия (rclose[i - 1]) выше текущей (rclose[i]); иначе он считается нисходящим. Затем по этому направлению выбирается соответствующий цвет. После определения верхней и нижней границ и направления данные кирпича и его индекс передаются в функцию отрисовки. Затем эта функция создает или обновляет прямоугольник на графике. График обновляется после обработки всех кирпичей. Для снижения накладных расходов график перерисовывается только при изменении кирпичей. Наконец, на графике выводится комментарий с текущим направлением тренда и количеством кирпичей. Это позволяет кратко оценить внутреннее состояние индикатора непосредственно на графике.

    Результат:

    Figure 9. Tradotional Renko Ovrely Indicator

    На разных таймфреймах кирпичи могут выглядеть по-разному. На младших графиках движения цены отображаются чаще и уровни кажутся ближе друг к другу, а на старших движение объединяется за более длительные периоды и уровни выглядят более разнесенными. Сам размер кирпича не меняется. Для получения более четких и информативных структур Ренко также рекомендуется выбирать размер кирпича с учетом символа и таймфрейма.

    Индикатор формирует отдельную структуру Ренко для каждого таймфрейма графика. В нем нет общего набора данных Ренко для нескольких таймфреймов. При каждой смене таймфрейма последовательность Ренко строится заново по ценовым данным текущего периода графика.


    Заключение

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

    • кирпичи формируются только после движения на полный размер кирпича, отсчитываемого от базовой цены или цены закрытия последнего подтвержденного кирпича;
    • первое допустимое движение задает начальный тренд, его продолжение обрабатывается обычным образом, а для подтверждения разворота требуется движение как минимум на два кирпича в противоположном направлении, что помогает избежать ложных разворотов;
    • цены закрытия кирпичей хранятся в динамическом массиве, а количество отображаемых кирпичей ограничивается пользовательской настройкой;
    • кирпичи отображаются на основном графике объектами OBJ_RECTANGLE, цвет которых зависит от направления;
    • перерисовка выполняется только при добавлении новых кирпичей (по установленному флагу), а при сбросе или удалении индикатора все его объекты удаляются;
    • для каждого таймфрейма строится отдельная структура Ренко; при смене таймфрейма она формируется заново. Выберите размер кирпича с учетом масштаба цены инструмента (например, 0,0001 для EURUSD или 1 для XAUUSD), чтобы получить информативные структуры Ренко с отфильтрованным шумом для построения торговой логики, фильтрации сигналов и визуального анализа.


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

    Прикрепленные файлы |
    От новичка до эксперта: Автоматизация распознавания геометрических паттернов внутри базовых свечей зон ликвидности в MQL5 От новичка до эксперта: Автоматизация распознавания геометрических паттернов внутри базовых свечей зон ликвидности в MQL5
    В статье описывается реализация модуля на MQL5, который анализирует бары младшего таймфрейма внутри каждой базовой свечи зоны ликвидности. Он обнаруживает свинговые точки и по объективным правилам классифицирует внутреннюю структуру: восходящий, нисходящий или симметричный треугольник, прямоугольник, M, W либо неопределенная структура. Индикатор отображает на графике метки геометрических фигур и добавляет название паттерна в алерты, сокращая необходимость ручной проверки на младшем таймфрейме.
    Нейросети в трейдинге: Habi — быстрое принятие решений без сложного планирования Нейросети в трейдинге: Habi — быстрое принятие решений без сложного планирования
    Адаптируем фреймворк Habi для алгоритмической торговли на финансовых рынках. Его основной принцип — перенос опыта сложного планирования в генеративную политику для быстрого принятия решений. Модель учитывает рыночные данные и состояние счёта, формирует варианты управления торговой позицией, а Critic оценивает их потенциальные результаты. Для обучения используются эталонные действия Oracul. Рассматриваем архитектуру торговой адаптации и реализацию OpenCL-кернелов пакетного семплирования и обратного распространения градиентов.
    Создание системы торговой аналитики (Часть 2): Как фиксировать закрытые сделки и передавать данные в формате JSON с помощью MQL5 Создание системы торговой аналитики (Часть 2): Как фиксировать закрытые сделки и передавать данные в формате JSON с помощью MQL5
    Мы создаем легковесный мост, который фиксирует закрытые сделки в MetaTrader 5 и отправляет данные о них во внешний бэкенд по HTTP в формате JSON. Он использует OnTradeTransaction для обнаружения событий, считывает сведения из истории сделок, формирует данные в формате JSON и отправляет их POST-запросом через WebRequest. Для проверки обмена данными используется локальный API Flask. Так мы получаем работающий механизм передачи торговых данных за пределы терминала.
    Как выявлять ликвидность на круглых ценовых уровнях в MQL5 Как выявлять ликвидность на круглых ценовых уровнях в MQL5
    В статье представлен метод обнаружения психологических круглых уровней на языке MQL5: цены преобразуются в строки, после чего подсчитываются конечные нули (ZeroSize). В ней излагается теория институциональной ликвидности на целочисленных уровнях, объясняется логика GetZeroCount с нормализацией по размеру тика для предотвращения ошибок вычислений с плавающей точкой и подробно описывается иерархическая визуализация. Примеры для рынка Forex, металлов и криптовалют, а также фильтры по таймфреймам и входные параметры показывают, как на практике использовать совпадение факторов и базовые механизмы управления рисками.