English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Индикатор для построения графика "Ренко"

Индикатор для построения графика "Ренко"

MetaTrader 5Примеры | 5 марта 2014, 13:32
16 404 12
Dmitriy Zabudskiy
Dmitriy Zabudskiy

Введение

В статьях Индикатор для построения графика "Крестики - Нолики" и Индикатор для построения графика "Каги" были рассмотрены приёмы построения индикаторов графиков: "Крестики-Нолики" и "Каги". Продолжая эту серию статей, на этот раз рассмотрим один из видов программного построения графика "Ренко".

Свое название график "Ренко" получил от японского слова "renga" - кирпич, это неудивительно, учитывая, что он представляет собой чередование "кирпичей" по одному на вертикальную плоскость, при росте цен "кирпичи", поднимаются всё выше и выше, и, наоборот, спускаются всё ниже и ниже при падении. В переводе с японского слово "Ренко" означает "тихая походка". График "Ренко" появился в Японии, вероятно, где-то в XIX веке, в США и Европе о нём узнали после публикации в 1994 году книги Стива Нисона «За гранью японских свечей» (англ. Beyond Candlesticks).

График "Ренко", как и вышеупомянутые графики, игнорирует временную шкалу, ориентируясь только на движение цены. В отличие от графика "Крестики-Нолики", график "Ренко" строит каждый "кирпич" с нового столбца (в новой вертикальной плоскости), в остальном анализ их построения имеет много общего: есть фиксированный размер "кирпича" ("крестика", "нолика"), аналогичным образом анализируется цена и выстраиваются фигуры.

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

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

Пример того, как выглядит классический график "Ренко", можно увидеть на рис. 1:

Рис. 1. Пример изображения классического графика "Ренко"

Рис. 1. Пример изображения классического графика "Ренко"

1. Пример построения

Классический вид построения графика "Ренко" принято производить на основании цен закрытия. Сначала следует выбрать таймфрейм и в соответствии с ним величину "шага" (порога).

В данном примере используется пара EURUSD (таймфрейм H4), с "шагом" в 30 пунктов. Результат построения графика "Ренко" в период с 03.01.2014 по 31.01.2014 (примерно месяц) приведён на рис. 2, слева приведено построение с учетом временного промежутка (здесь видно горизонтальное удлинение "кирпичей"), справа показан результат построения графика Ренко:


Рис.2. Результат построения графика "Ренко" на EURUSD (H4, шаг 30 пунктов) 

Рис.2. Результат построения графика "Ренко" на EURUSD (H4, шаг 30 пунктов)

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

Как видно из графика, в конце дня 03.01.2014 свеча закрывается ниже 1.3591 ранее намеченных ценовых диапазонов графика (красные горизонтальные линии) в 1.3589 (отмечено ценовой меткой), что даёт нисходящий "кирпич" на графике.

Далее цена находится во флете (не закрывается ниже отметки 1.3561, но и не закрывается выше отметки 1.3651) до 20:00 10.01.2014 (закрытие свечи, образованной с 16:00) , где закрывается (выше ценовой отметки 1.3651) на 1.3663 (отмечено ценовой меткой). Далее видно, что цена опять входит во флет, до 20:00 14.01.2014 (закрытие свечи, образованной 16:00), где преодолевает ценовой диапазон, формируя при этом новый "кирпич" и закрывается на отметке 1.3684.

Потом видно, как направление движения цены меняется в нисходящую сторону и четыре раза прорывает ценовые диапазоны, опускаясь всё ниже и ниже на ценовом графике. В 12:00 23.01.2014 (закрытие свечи, образованной в 08:00), наблюдаем прорыв вверх сразу двух ценовых диапазонов, что в свою очередь образовывает сразу два "кирпича", закрываясь на 1.3639. Первый "кирпич" отчётливо виден, а второй вытягивается в длинную вертикальную линию (из-за одновременного появления с первым). Далее построение продолжается по тем же основным правилам.


2. Принцип построения индикатора "Ренко"

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

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

Классическое построение графика "Ренко" подразумевает построение графика по ценам закрытия (Close), но для расширения возможностей анализа была добавлена возможность использования цен Open, High, Low.

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

И последней наиболее выраженной особенностью данного индикатора является возможность производить построение типа "ZigZag" на основном графике, что также позволит расширить возможности графического анализа.

На рис. 3 приведён внешний вид индикатора в полной функциональности:

Рис.3. Внешний вид индикатора на графике EURUSD (Daily, шаг 25 пунктов)

Рис.3. Внешний вид индикатора на графике EURUSD (Daily, шаг 25 пунктов)


3. Код и алгоритм индикатора

Код индикатора довольно-таки большой, и составляет в оформлении около 900 строк. Как говорилось ранее, функции максимально отделены друг от друга, это может усложнить понимание алгоритма. За основу были взяты некоторые функции из предыдущей статьи. В случае недопонимания каких-либо аспектов можно обратиться к предыдущей статье Индикатор для построения графика "Каги" или задать вопрос мне в личных сообщениях.

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


3.1. Входные параметры индикатора

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

Рассмотрим входные параметры (их 25), поделённых на группы.

  • step - шаг или размер "кирпича";
  • type_step - вид шага, может быть как пунктовый, так и процентный (последний вычисляется в зависимости от текущей цены);
  • magic_numb - магический номер, нужен для отличия графических объектов индикатора, используется для удаления их с графика;
  • levels_number - количество уровней (0-без уровней), для обозначения разделения "кирпичей" в окне индикатора;
  • levels_color - цвет уровней в окне индикатора;
  • time_frame - задаёт период для построения графика (анализируемый период);
  • time_redraw - период обновления графика (время, через которое обновляется построение графика);
  • first_date_start - начальная дата (дата, с которой начинать построение графика);
  • type_price - вид цены для построения (задаёт цену, всего существует четыре вида: Close - построение по ценам закрытия, классический метод; Open - цены открытия; High - максимальные цены и Low - минимальные цены);
  • shadow_print - показывать ли тени (при установке этого параметра в положение true, происходит использование теней исходящих из "кирпичей", для обозначения максимальной или минимальной цены послужившей для открытия сразу нескольких "кирпичей");
  • filter_number - значение количества "кирпичей" для разворота (дополнительный параметр, не используемый в классическом построении, отвечающий за количество "кирпичей", нужных для разворота графика в другую сторону);
  • zig_zag - рисовать ZigZag на главном графике (дополнительное построение на главном графике, предполагающее облегчить анализ или как то его модернизировать);
  • zig_zag_shadow - рисовать ZigZag по ценам минимумов и максимумов (использует ближайшие цены максимумов и минимумов для построения зигзага по крайним точкам);
  • zig_zag_width - толщина линии ZigZag;
  • zig_zag_color_up - цвет восходящей линии ZigZag;
  • zig_zag_color_down - цвет нисходящей линии ZigZag;
  • square_draw - рисовать ли "кирпичи" на главном графике (в этом режиме видно движения цен, которыми были сформированы "кирпичи");
  • square_color_up - цвет "кирпича" на главном графике вверх;
  • square_color_down - цвет "кирпича" на главном графике вниз;
  • square_fill - заливка "кирпича" на главном графике;
  • square_width - толщина линии "кирпича" на главном графике;
  • frame_draw - рисовать ли рамки "кирпичей" (представляет собой окантовку для построенных "кирпичей", является дополнительным графическим параметром, использование предполагалось крайне редкое);
  • frame_width - толщина линии рамки "кирпича";
  • frame_color_up - цвет рамок "кирпичей" вверх;
  • frame_color_down - цвет рамок "кирпичей" вниз.

Далее в коде идёт объявление буферов, пять основных буферов используются для графического построения и четыре для хранения данных для построения и расчётов. Price[] - буфер для хранения скопированных цен используемых для построения, Date[] - буфер для хранения скопированных дат используется для построения на главном графике, Price_high[] и Price_low[] - буфера для хранения скопированных цен максимумов и минимумов, используются для построения типа "ZigZag" на главном графике.

Следом идёт объявление массивов расчетных буферов и вспомогательных переменных для функций: func_draw_renko, func_draw_zig_zag, func_draw_renko_main_chart, о которых будет рассказано позже.

//+------------------------------------------------------------------+
//|                                                         ABCR.mq5 |
//|                                   Azotskiy Aktiniy ICQ:695710750 |
//|                          https://www.mql5.com/ru/users/Aktiniy |
//+------------------------------------------------------------------+
//--- Auto Build Chart Renko
#property copyright "Azotskiy Aktiniy ICQ:695710750"
#property link      "https://www.mql5.com/ru/users/Aktiniy"
#property version   "1.00"
#property description "Auto Build Chart Renko"
#property description "   "
#property description "This indicator makes drawing a chart Renko as a matter of indicator window, and in the main chart window"
#property indicator_separate_window
#property indicator_buffers 9
#property indicator_plots   1
//--- plot RENKO
#property indicator_label1  "RENKO"
#property indicator_type1   DRAW_COLOR_CANDLES
#property indicator_color1  clrRed,clrBlue,C'0,0,0',C'0,0,0',C'0,0,0',C'0,0,0',C'0,0,0',C'0,0,0'
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- вид построения
enum type_step_renko
  {
   point=0,   // Пункт
   percent=1, // Процент
  };
//--- вид цены для построения
enum type_price_renko
  {
   close=0, // Close
   open=1,  // Open
   high=2,  // High
   low=3,   // Low
  };
//--- input parameters
input double           step=10;                                 // Шаг
input type_step_renko  type_step=point;                         // Вид шага
input long             magic_numb=65758473787389;               // Магический номер
input int              levels_number=1000;                      // Количество уровней (0-без уровней)
input color            levels_color=clrLavender;                // Цвет уровней
input ENUM_TIMEFRAMES  time_frame=PERIOD_CURRENT;               // Период расчёта
input ENUM_TIMEFRAMES  time_redraw=PERIOD_M1;                   // Период обновления графика
input datetime         first_date_start=D'2013.09.13 00:00:00'; // Начальная дата
input type_price_renko type_price=close;                        // Вид цены для построения
input bool             shadow_print=true;                       // Показывать ли тени (цены создавшие сразу несколько кирпичей)
input int              filter_number=0;                         // Значение количества кирпичей для разворота
input bool             zig_zag=true;                            // Рисовать ZigZag на главном графике
input bool             zig_zag_shadow=true;                     // Рисовать ZigZag по ценам минимумов и максимумов
input int              zig_zag_width=2;                         // Толщина линии ZigZag
input color            zig_zag_color_up=clrBlue;                // Цвет восходящей линии ZigZag
input color            zig_zag_color_down=clrRed;               // Цвет нисходящей линии ZigZag
input bool             square_draw=true;                        // Рисовать ли кирпичи на главном графике
input color            square_color_up=clrBlue;                 // Цвет кирпича на главном графике вверх
input color            square_color_down=clrRed;                // Цвет кирпича на главном графике вниз
input bool             square_fill=true;                        // Заливка кирпича на главном графике
input int              square_width=2;                          // Толщина линии кирпича на главном графике
input bool             frame_draw=true;                         // Рисовать ли рамки кирпичей
input int              frame_width=2;                           // Толщина линии рамки кирпича
input color            frame_color_up=clrBlue;                  // Цвет рамок кирпичей вверх
input color            frame_color_down=clrRed;                 // Цвет рамок кирпичей вниз
//--- indicator buffers
double         RENKO_open[];
double         RENKO_high[];
double         RENKO_low[];
double         RENKO_close[];
double         RENKO_color[];

double         Price[];      // буфер для хранения скопированных цен
double         Date[];       // буфер для хранения скопированных дат
double         Price_high[]; // буфер для хранения скопированных цен максимумов
double         Price_low[];  // буфер для хранения скопированных цен минимумов
//--- массивы буферов калькуляции
double         up_price[];    // верхняя цена кирпича
double         down_price[];  // нижняя цена кирпича
char           type_box[];    // тип кирпича (вверх, вниз)
datetime       time_box[];    // время закрытия кирпича
double         shadow_up[];   // верхний максимум цены
double         shadow_down[]; // нижний минимум цены
int            number_id[];   // индекс из массивов Price_high и Price_low
//--- глобальные переменные для расчётов
int obj=0;           // переменная для хранения количества графических объектов
int a=0;             // переменная подсчёта кирпичей
int bars;            // количество баров
datetime date_stop;  // текущая дата
datetime date_start; // переменная начальной даты, для расчётов
bool date_change;    // переменная хранения информации об изменениях во времени


3.2. Функция инициализации индикатора

В этой функции происходит связывание индикаторных буферов с одномерными динамическими массивами, для буферов типа INDICATOR_DATA и INDICATOR_COLOR_INDEX также задаётся адресация как в таймсериях. Остальные динамические массивы (Price[], Date[], Price_high[], Price_low[]), остаются без изменения направления адресации, так как не нуждаются в этом и служат только для хранения данных.

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

Следом за этим производится присвоение значения переменной date_start (даты с которой следует начать расчёт). Происходит именно присвоение переменной, а не использование входного значения, так как график может оказаться гораздо больше, чем может в себя вместить индикаторный буфер, происходит некоторая коррекция начальной даты с предупреждением пользователя. Коррекцией времени занимается функция расчёта даты начала анализа или "func_calc_date_start".

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,RENKO_open,INDICATOR_DATA);
   ArraySetAsSeries(RENKO_open,true);
   SetIndexBuffer(1,RENKO_high,INDICATOR_DATA);
   ArraySetAsSeries(RENKO_high,true);
   SetIndexBuffer(2,RENKO_low,INDICATOR_DATA);
   ArraySetAsSeries(RENKO_low,true);
   SetIndexBuffer(3,RENKO_close,INDICATOR_DATA);
   ArraySetAsSeries(RENKO_close,true);
   SetIndexBuffer(4,RENKO_color,INDICATOR_COLOR_INDEX);
   ArraySetAsSeries(RENKO_color,true);
//---
   SetIndexBuffer(5,Price,INDICATOR_CALCULATIONS);      // инициализируем буфер цен
   SetIndexBuffer(6,Date,INDICATOR_CALCULATIONS);       // инициализируем буфер дат
   SetIndexBuffer(7,Price_high,INDICATOR_CALCULATIONS); // инициализируем буфер цен максимумов
   SetIndexBuffer(8,Price_low,INDICATOR_CALCULATIONS);  // инициализируем буфер цен минимумов
//--- задаём какие значения не будут прорисовываться
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0);
//--- устанавливаем внешний вид индикатора
   IndicatorSetString(INDICATOR_SHORTNAME,"ABCR "+IntegerToString(magic_numb)); // имя индикатора
//--- точность отображения
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
//--- запрещяем показ результатов текущих значений для индикатора
   PlotIndexSetInteger(0,PLOT_SHOW_DATA,false);
//--- присвоение значения переменной начальной даты
   date_start=first_date_start;
//---
   return(INIT_SUCCEEDED);
  }

3.3. Функция расчёта даты начала анализа

Сама функция по своему размеру небольшая и состоит в основном из цикла. Имеется всего два входных параметра, первоначально назначенная дата начала и дата окончания расчёта (текущая дата), первая из которых меняется внутри самой функции и выдаётся в виде ответа.

Тело функции начинается с измерения размера приёмного буферного массива (можно взять любой из буферных массивов для измерения размера, так как их размер одинаков и равняется количеству баров выбранного таймфрейма). Далее измеряется количество баров в выбранном промежутке времени.

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

//+------------------------------------------------------------------+
//| Func Calculate Date Start                                        |
//+------------------------------------------------------------------+
datetime func_calc_date_start(datetime input_data_start,// первоначально назначенная дата начала
                              datetime data_stop)       // дата окончания расчёта (текущая дата)
//---
  {
   int Array_Size=ArraySize(Price);
   int Bars_Size=Bars(_Symbol,time_frame,input_data_start,data_stop);
   for(;Bars_Size>Array_Size;input_data_start+=864000) // 864000 = 10 дней
     {
      Bars_Size=Bars(_Symbol,time_frame,input_data_start,data_stop);
     }
   return(input_data_start);
//---
  }

3.4. Функции копирования данных

Для работы с данными их нужно сначала скопировать. Именно для этого существуют функции копирования данных (func_copy_price и func_copy_date), которые сейчас рассмотрим.

Начнём с функции копирования цены или func_copy_price, данная функция позволяет копировать в переданный ей массив цены Open, Close, High и Low за определённый таймфрейм и период, переданные в параметрах функции. В случае удачного копирования функция возвращает "true".

В начале вызова функции инициализируется возвращаемое значение false, далее происходит инициализация переменной результата скопированных данных, и присваивается отрицательное значение. Объявляется общий массив price_interim[] для временного хранения скопированных данных о ценах и переменная bars_to_copy для предотвращения копирования уже скопированных данных.

Далее функция обнуляет ранее объявленные переменные хранения количества скопированных данных, подсчитывает количество баров на временном промежутке и в зависимости от выбранного копируемого вида цены (0-Close, 1-Open, 2-High и 3-Low) при помощи оператора-переключателя switch присваивает значение количества ранее скопированных данных по ценам общей переменной bars_copied. Следом подсчитывается количество данных, которые нужно скопировать. Если данные копируются не в первый раз, происходит удаление информации о последнем скопированном баре, из-за того, что он мог измениться в ходе движения графика.

Опять при помощи оператора-переключателя происходит копирование нужного нам вида данных о ценах во временный массив price_interim[]. Следом проверяется результат копирования и опять при помощи оператора-переключателя switch, происходит заполнение переменных о количестве скопированных данных.

//+------------------------------------------------------------------+
//| Func Copy Price                                                  |
//+------------------------------------------------------------------+
bool func_copy_price(double &result_array[],
                     ENUM_TIMEFRAMES period,// Таим фреим
                     datetime data_start,
                     datetime data_stop,
                     char price_type) // 0-Close, 1-Open, 2-High, 3-Low
  {
//---
   int x=false;        // переменная для ответа
   int result_copy=-1; // количество скопированных данных
//---
   static double price_interim[]; // временный динамический массив для хранения скопированных данных
   static int bars_to_copy;       // количество баров для копирования
   static int bars_copied_0;      // количество уже скопированных баров с начальной даты Close
   static int bars_copied_1;      // количество уже скопированных баров с начальной даты Open
   static int bars_copied_2;      // количество уже скопированных баров с начальной даты High
   static int bars_copied_3;      // количество уже скопированных баров с начальной даты Low
   static int bars_copied;        // количество уже скопированных баров с начальной даты общая переменная
//--- обнуление переменных ввиду изменения начальной даты
   if(date_change==true)
     {
      ZeroMemory(price_interim);
      ZeroMemory(bars_to_copy);
      ZeroMemory(bars_copied_0);
      ZeroMemory(bars_copied_1);
      ZeroMemory(bars_copied_2);
      ZeroMemory(bars_copied_3);
      ZeroMemory(bars_copied);
     }
//--- узнаём текущее колличество баров на временном промежутке
   bars_to_copy=Bars(_Symbol,period,data_start,data_stop);
//--- присвоение общей переменной значение одной из копируемых переменных
   switch(price_type)
     {
      case 0:
         //--- Close
         bars_copied=bars_copied_0;
         break;
      case 1:
         //--- Open
         bars_copied=bars_copied_1;
         break;
      case 2:
         //--- High
         bars_copied=bars_copied_2;
         break;
      case 3:
         //--- Low
         bars_copied=bars_copied_3;
         break;
     }
//--- подсчитываем колличество баров которые надо скопировать
   bars_to_copy-=bars_copied; 
//--- если данные копируются не в первый раз
   if(bars_copied!=0) 
     {
      bars_copied--;
      bars_to_copy++;
     }
//--- меняем размер приёмного массива
   ArrayResize(price_interim,bars_to_copy); 
//--- копируем данные во временный массив
   switch(price_type)
     {
      case 0:
         //--- Close
        {
         result_copy=CopyClose(_Symbol,period,0,bars_to_copy,price_interim);
        }
      break;
      case 1:
         //--- Open
        {
         result_copy=CopyOpen(_Symbol,period,0,bars_to_copy,price_interim);
        }
      break;
      case 2:
         //--- High
        {
         result_copy=CopyHigh(_Symbol,period,0,bars_to_copy,price_interim);
        }
      break;
      case 3:
         //--- Low
        {
         result_copy=CopyLow(_Symbol,period,0,bars_to_copy,price_interim);
        }
      break;
     }
//--- проверяем результат копирования данных
   if(result_copy!=-1) // если копирование в промежуточный массив было успешно
     {
      ArrayCopy(result_array,price_interim,bars_copied,0,WHOLE_ARRAY); // копируем данные из временного в основной массив
      x=true;                   // присваиваем положительный ответ функции
      bars_copied+=result_copy; // увеличиваем значение скопированных данных
     }
//--- возвращение информации о скопированных данных одной из копируемых переменных
   switch(price_type)
     {
      case 0:
         //--- Close
         bars_copied_0=bars_copied;
         break;
      case 1:
         //--- Open
         bars_copied_1=bars_copied;
         break;
      case 2:
         //--- High
         bars_copied_2=bars_copied;
         break;
      case 3:
         //--- Low
         bars_copied_3=bars_copied;
         break;
     }
//---
   return(x);
  }

 Следующая функция копирования даты или "func_copy_date". Код функции схож с ранее рассмотренным блоком, различие составляет вид копируемых данных, так что без комментариев.

//+------------------------------------------------------------------+
//| Func Copy Date                                                   |
//+------------------------------------------------------------------+
bool func_copy_date(double &result_array[],
                    ENUM_TIMEFRAMES period,// таймфрейм
                    datetime data_start,
                    datetime data_stop)
  {
//---
   int x=false;                    // переменная для ответа
   int result_copy=-1;             // количество скопированных данных
   static datetime time_interim[]; // временный динамический массив для хранения скопированных данных
   static int bars_to_copy;        // количество баров для копирования
   static int bars_copied;         // количество уже скопированных баров с начальной датой
//--- обнуление переменных ввиду изменения начальной даты
   if(date_change==true)
     {
      ZeroMemory(time_interim);
      ZeroMemory(bars_to_copy);
      ZeroMemory(bars_copied);
     }
//---
   bars_to_copy=Bars(_Symbol,period,data_start,data_stop); // узнаём текущее количество баров на временном промежутке
   bars_to_copy-=bars_copied; // подсчитываем Количество баров которые надо скопировать
//---
   if(bars_copied!=0) // если данные копируются не в первый раз
     {
      bars_copied--;
      bars_to_copy++;
     }
//---
   ArrayResize(time_interim,bars_to_copy); // меняем размер приёмного массива
   result_copy=CopyTime(_Symbol,period,0,bars_to_copy,time_interim);
//---
   if(result_copy!=-1) // если копирование в промежуточный массив было успешно
     {
      ArrayCopy(result_array,time_interim,bars_copied,0,WHOLE_ARRAY); // копируем в основной массив данных, данные из временного
      x=true; // присваиваем положительный ответ функции
      bars_copied+=result_copy; // увеличиваем значение скопированных данных
     }
//---
   return(x);
  }

3.5. Функция расчёта "Кирпичей"

Как видно из параметров индикатора, размер кирпича можно задавать не только в пунктах, но и в процентах от текущей цены. С пунктами всё легко, это фиксированная величина, а вот как же поступить с процентами? Для этого есть функция расчёта "кирпичей" или "func_calc_dorstep".

Функция имеет три входных параметра: текущая цена (цена от которой будет рассчитываться процент, в случае выбора процентного размера "кирпича"), выбранный тип расчёта (пунктовый или процентный), и размер шага (задаётся одной величиной, которая в зависимости от выбранного типа расчёта, считается за проценты или пункты).

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

//+------------------------------------------------------------------+
//| Func Calculate Doorstep                                          |
//+------------------------------------------------------------------+
int func_calc_dorstep(double price,      // цена
                      char type_doorstep,// тип шага
                      double doorstep)   // шаг
  {
   double x=0;          // переменная для ответа

   if(type_doorstep==0) // если расчёт нужно произволить по пунктам
     {
      x=doorstep;
     }

   if(type_doorstep==1) // если расчёт нужно производить в процентах
     {
      x=price/_Point*doorstep/100;
     }

   return((int)x);
  }

3.6. Основная функция - построение графика "Ренко"

Самая главная функция построения графика "Ренко" - "func_draw_renko". Эта функция отвечает за заполнение графических буферов (буферы индикатора) и массивов расчетных буферов. Расчетные буферы нужны для хранения информации о каждом "кирпиче", они представляют собой источник корректной информации, то есть разобранной и рассчитанной из первоначальных данных.

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

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

В самом начале функции происходит обнуление графических буферов, чтобы исключить отображение ячеек, которые не заполнены значениями. Далее вводятся вспомогательные переменные для расчётов: "doorstep_now" используется для шага (в основном нужна при пересчёте его размера при процентном виде шага), переменная "point_go" хранит информацию о расстоянии, пройденном с последнего образованного "кирпича", переменная "a" используется при подсчёте "кирпичей", "up_price_calc" и "down_price_calc" - последняя анализируемая верхняя и нижняя цены, "type_box_calc" - вид последнего анализируемого "кирпича" (верх или вниз).

Первая часть функции, как и вторая, состоит из цикла, вторая представляет собой дополненную первую. Разберём всё по-порядку.

Первый цикл проходит по всем скопированным значениям, количество скопированных данных определяет переменная "bars" (подсчитывается в функции "func_concolidation", которая будет рассмотрена позже). Далее в цикле происходит вызов функции вычисления размера "кирпича". Так как каждый бар имеет разную цену закрытия, соответственно, если использовать процентный размер шага, то его нужно высчитывать для каждого бара по отдельности.

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

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

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

Следом происходит обнуление переменной подсчёта "кирпичей" и начинается главный цикл. Отличие главного цикла от предыдущего в том, что в нём кроме выше указанных расчётов происходит ещё заполнение массивов расчетных буферов и сброс счётчика "кирпичей" по предварительно рассчитанным параметрам перед началом цикла.

В самом конце функции происходит заполнение графических буферов.

//+------------------------------------------------------------------+
//| Func Draw Renko                                                  |
//+------------------------------------------------------------------+
void func_draw_renko(double &price[],   // массив цен
                     double &date[],    // массив дат
                     int number_filter, // количество кирпичей для разворота
                     bool draw_shadow,  // рисовать тень
                     char type_doorstep,// тип шага
                     double doorstep)   // шаг
  {
//--- обнуление массивов
//--- массивы буферов отрисовки
   ZeroMemory(RENKO_close);
   ZeroMemory(RENKO_color);
   ZeroMemory(RENKO_high);
   ZeroMemory(RENKO_low);
   ZeroMemory(RENKO_open);
//--- вспомогательные переменные для расчётов
   int doorstep_now; // текущий шаг
   int point_go;     // пройдено пунктов
//--- вспомогательные переменные для подсчёта количества кирпичей
   a=0;
   double up_price_calc=price[0];
   double down_price_calc=price[0];
   char type_box_calc=0;

   for(int z=0; z<bars; z++) //---> цикл подсчёта кирпичей
     {
      //--- вычисляем размер шага учитывая текущюю расматриваему цену
      doorstep_now=func_calc_dorstep(price[z],type_doorstep,doorstep);
      //--- если цена идёт вверх
      if((price[z]-up_price_calc)/_Point>=doorstep_now)
        {
         //--- вычисляем пройденное количество пунктов
         point_go=int((price[z]-up_price_calc)/_Point);
         //--- до этого цена шла вверх или направление цены неизвестно
         if(type_box_calc==1 || type_box_calc==0)
           {
            for(int y=point_go; y>=doorstep_now; y-=doorstep_now)
              {
               //--- добавляем следующий кирпич
               a++;
               //--- устанавливаем значение нижней цены следующего кирпича
               down_price_calc=up_price_calc;
               //--- устанавливаем значение верхней цены следующего кирпича
               up_price_calc=down_price_calc+(doorstep_now*_Point);
               //--- установка типа кирпича (вверх)
               type_box_calc=1;
              }
           }
         //--- до этого цена шла вниз
         if(type_box_calc==-1)
           {
            if((point_go/doorstep_now)>=number_filter)
              {
               for(int y=point_go; y>=doorstep_now; y-=doorstep_now)
                 {
                  //--- добавляем следующий кирпич
                  a++;
                  //--- устанавливаем значение нижней цены следующего кирпича
                  down_price_calc=up_price_calc;
                  //--- устанавливаем значение верхней цены следующего кирпича
                  up_price_calc=down_price_calc+(doorstep_now*_Point);
                  //--- установка типа кирпича (вверх)
                  type_box_calc=1;
                 }
              }
           }
        }
      //--- если цена идёт вниз
      if((down_price_calc-price[z])/_Point>=doorstep_now)
        {
         //--- вычисляем пройденное количество пунктов
         point_go=int((down_price_calc-price[z])/_Point);
         //--- до этого цена шла вниз или предыдущее направление неизвестно
         if(type_box_calc==-1 || type_box_calc==0)
           {
            for(int y=point_go; y>=doorstep_now; y-=doorstep_now)
              {
               //--- добавляем следующий кирпич
               a++;
               //--- устанавливаем значение нижней цены следующего кирпича
               up_price_calc=down_price_calc;
               //--- устанавливаем значение верхней цены следующего кирпича
               down_price_calc=up_price_calc-(doorstep_now*_Point);
               //--- установка типа кирпича (вверх)
               type_box_calc=-1;
              }
           }
         //--- до этого цена шла вверх
         if(type_box_calc==1)
           {
            if((point_go/doorstep_now)>=number_filter)
              {
               for(int y=point_go; y>=doorstep_now; y-=doorstep_now)
                 {
                  //--- добавляем следующий кирпич
                  a++;
                  //--- устанавливаем значение нижней цены следующего кирпича
                  up_price_calc=down_price_calc;
                  //--- устанавливаем значение верхней цены следующего кирпича
                  down_price_calc=up_price_calc-(doorstep_now*_Point);
                  //--- установка типа кирпича (вверх)
                  type_box_calc=-1;
                 }
              }
           }
        }
     } //---< цикл подсчёта кирпичей
//--- подсчитываем количество отображаемых баров
   int b=Bars(_Symbol,PERIOD_CURRENT);
//--- меняем размеры массивов
   ArrayResize(up_price,b);
   ArrayResize(down_price,b);
   ArrayResize(type_box,b);
   ArrayResize(time_box,b);
   ArrayResize(shadow_up,b);
   ArrayResize(shadow_down,b);
   ArrayResize(number_id,b);
//--- обнуляем массивы буферов калькуляции
   ZeroMemory(up_price);
   ZeroMemory(down_price);
   ZeroMemory(type_box);
   ZeroMemory(time_box);
   ZeroMemory(shadow_up);
   ZeroMemory(shadow_down);
   ZeroMemory(number_id);
//--- заполняем начальными значениями массивы
   up_price[0]=price[0];
   down_price[0]=price[0];
   type_box[0]=0;
//--- высчитываем количество лишних кирпичей
   int l=a-b;
   int turn_cycle=l/(b-1);
   int turn_rest=(int)MathMod(l,(b-1))+2;
   int turn_var=0;
//--- выводим сообщение о частичном отображении кирпичей
   if(a>b)Alert("Кирпичей больше чем может поместиться на графике, возможно шаг мал");

   a=0; //--- обнуляем переменную подсчёта кирпичей
   for(int z=0; z<bars; z++) //---> Главный цикл
     {
      //--- вычисляем размер шага учитывая текущюю расматриваему цену
      doorstep_now=func_calc_dorstep(price[z],type_doorstep,doorstep);
      //--- если цена идёт вверх
      if((price[z]-up_price[a])/_Point>=doorstep_now)
        {
         //--- вычисляем пройденное количество пунктов
         point_go=int((price[z]-up_price[a])/_Point);
         //--- до этого цена шла вверх или направление цены неизвестно
         if(type_box[a]==1 || type_box[a]==0)
           {
            for(int y=point_go; y>=doorstep_now; y-=doorstep_now)
              {
               a++; //--- добавляем следующий кирпич
               if((a==b && turn_var<turn_cycle) || (turn_var==turn_cycle && turn_rest==a))
                 {
                  up_price[0]=up_price[a-1];
                  a=1;        // скидывание счётчика кирпичей
                  turn_var++; // счётчик циклов сброса
                 }
               //--- устанавливаем значение нижней цены следующего кирпича
               down_price[a]=up_price[a-1];
               //--- устанавливаем значение верхней цены следующего кирпича
               up_price[a]=down_price[a]+(doorstep_now*_Point);

               //--- установка значения верхней тени
               if(shadow_print==true) shadow_up[a]=price[z]; // на уровень последней наивысшей цены 
               else shadow_up[a]=up_price[a];                // на уровень верхней цены кирпича 

               //--- установка значения нижней цены (на уровень цены кирпича)
               shadow_down[a]=down_price[a];
               //--- установка значения времени закрытия кирпича
               time_box[a]=(datetime)Date[z];
               //--- установка типа кирпича (вверх)
               type_box[a]=1;
               //--- установка индекса
               number_id[a]=z;
              }
           }
         //--- до этого цена шла вниз
         if(type_box[a]==-1)
           {
            if((point_go/doorstep_now)>=number_filter)
              {
               for(int y=point_go; y>=doorstep_now; y-=doorstep_now)
                 {
                  a++; //--- добавляем следующий кирпич

                  if((a==b && turn_var<turn_cycle) || (turn_var==turn_cycle && turn_rest==a))
                    {
                     up_price[0]=up_price[a-1];
                     a=1;        // скидывание счётчика кирпичей
                     turn_var++; // счётчик циклов сброса
                    }
                  //--- устанавливаем значение нижней цены следующего кирпича
                  down_price[a]=up_price[a-1];
                  //--- устанавливаем значение верхней цены следующего кирпича
                  up_price[a]=down_price[a]+(doorstep_now*_Point);

                  //--- установка значения верхней тени
                  if(shadow_print==true) shadow_up[a]=price[z]; // на уровень последней наивысшей цены
                  else shadow_up[a]=up_price[a];                // на уровень верхней цены кирпича

                  //--- установка значения нижней цены (на уровень цены кирпича)
                  shadow_down[a]=down_price[a];
                  //--- установка значения времени закрытия кирпича
                  time_box[a]=(datetime)Date[z];
                  //--- установка типа кирпича (вверх)
                  type_box[a]=1;
                  //--- установка индекса
                  number_id[a]=z;
                 }
              }
           }
        }

      //--- если цена идёт вниз
      if((down_price[a]-price[z])/_Point>=doorstep_now)
        {
         //--- вычисляем пройденное количество пунктов
         point_go=int((down_price[a]-price[z])/_Point);
         //--- до этого цена шла вниз или предыдущее направление неизвестно
         if(type_box[a]==-1 || type_box[a]==0)
           {
            for(int y=point_go; y>=doorstep_now; y-=doorstep_now)
              {
               a++; //--- добавляем следующий кирпич
               if((a==b && turn_var<turn_cycle) || (turn_var==turn_cycle && turn_rest==a))
                 {
                  down_price[0]=down_price[a-1];
                  a=1;        // скидывание счётчика кирпичей
                  turn_var++; // счётчик циклов сброса
                 }
               //--- устанавливаем значение нижней цены следующего кирпича
               up_price[a]=down_price[a-1];
               //--- устанавливаем значение верхней цены следующего кирпича
               down_price[a]=up_price[a]-(doorstep_now*_Point);

               //--- установка значения нижней тени 
               if(shadow_print==true) shadow_down[a]=price[z]; //--- на уровень последней наименьшей цены
               else shadow_down[a]=down_price[a];              //--- на уровень нижней цены кирпича

               //--- установка значения верхней цены (на уровень цены кирпича)
               shadow_up[a]=up_price[a];
               //--- установка значения времени закрытия кирпича
               time_box[a]=(datetime)Date[z];
               //--- установка типа кирпича (вниз)
               type_box[a]=-1;
               //--- установка индекса
               number_id[a]=z;
              }
           }
         //--- до этого цена шла вверх
         if(type_box[a]==1)
           {
            if((point_go/doorstep_now)>=number_filter)
              {
               for(int y=point_go; y>=doorstep_now; y-=doorstep_now)
                 {
                  a++; //--- добавляем следующий кирпич
                  if((a==b && turn_var<turn_cycle) || (turn_var==turn_cycle && turn_rest==a))
                    {
                     down_price[0]=down_price[a-1];
                     a=1;        // скидывание счётчика кирпичей
                     turn_var++; // счётчик циклов сброса
                    }

                  up_price[a]=down_price[a-1]; //--- устанавливаем значение нижней цены следующего кирпича
                  down_price[a]=up_price[a]-(doorstep_now*_Point); //--- устанавливаем значение верхней цены следующего кирпича

                  //--- установка значения нижней тени 
                  if(shadow_print==true) shadow_down[a]=price[z]; // на уровень последней наименьшей цены
                  else shadow_down[a]=down_price[a];              // на уровень нижней цены кирпича

                  //--- установка значения верхней цены (на уровень цены кирпича)
                  shadow_up[a]=up_price[a];
                  //--- установка значения времени закрытия кирпича
                  time_box[a]=(datetime)Date[z];
                  //--- установка типа кирпича (вниз)
                  type_box[a]=-1;
                  //--- установка индекса
                  number_id[a]=z;
                 }
              }
           }
        }
     } //---< Главный цикл

//--- заполняем буфера отрисовки
   int y=a;
   for(int z=0; z<a; z++)
     {
      if(type_box[y]==1)RENKO_color[z]=0;
      else RENKO_color[z]=1;
      RENKO_open[z]=down_price[y];
      RENKO_close[z]=up_price[y];
      RENKO_high[z]=shadow_up[y];
      RENKO_low[z]=shadow_down[y];
      y--;
     }
  }


3.7. Функции создания графических объектов "трендовая линия" и "прямоугольник"

Функция создания графического объекта типа "трендовая линия" или "func_create_trend_line" и функция создания графического объекта типа "прямоугольник" или "func_create_square_or_rectangle" созданы по материалам, указанным в справке для объектов OBJ_RECTANGLE и OBJ_TREND. Служат для создания графических объектов в функции построения графика "Ренко" и для построения графика типа "ZigZag" на главном графике.

//+------------------------------------------------------------------+
//| Func Create Trend Line                                           |
//+------------------------------------------------------------------+
void func_create_trend_line(string name,
                            double price1,
                            double price2,
                            datetime time1,
                            datetime time2,
                            int width,
                            color color_line)
  {
   ObjectCreate(0,name,OBJ_TREND,0,time1,price1,time2,price2);
//--- установим цвет линии
   ObjectSetInteger(0,name,OBJPROP_COLOR,color_line);
//--- установим стиль отображения линии
   ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_SOLID);
//--- установим толщину линии
   ObjectSetInteger(0,name,OBJPROP_WIDTH,width);
//--- отобразим на переднем (false) или заднем (true) плане
   ObjectSetInteger(0,name,OBJPROP_BACK,false);
//--- включим (true) или отключим (false) режим продолжения отображения линии влево
   ObjectSetInteger(0,name,OBJPROP_RAY_LEFT,false);
//--- включим (true) или отключим (false) режим продолжения отображения линии вправо
   ObjectSetInteger(0,name,OBJPROP_RAY_RIGHT,false);
  }
//+------------------------------------------------------------------+
//| Func Create Square or Rectangle                                  |
//+------------------------------------------------------------------+
void func_create_square_or_rectangle(string name,
                                     double price1,
                                     double price2,
                                     datetime time1,
                                     datetime time2,
                                     int width,
                                     color color_square,
                                     bool fill)
  {
//--- создадим прямоугольник по заданным координатам
   ObjectCreate(0,name,OBJ_RECTANGLE,0,time1,price1,time2,price2);
//--- установим цвет прямоугольника
   ObjectSetInteger(0,name,OBJPROP_COLOR,color_square);
//--- установим стиль линий прямоугольника
   ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_SOLID);
//--- установим толщину линий прямоугольника
   ObjectSetInteger(0,name,OBJPROP_WIDTH,width);
//--- включим (true) или отключим (false) режим заливки прямоугольника
   ObjectSetInteger(0,name,OBJPROP_FILL,fill);
//--- отобразим на переднем (false) или заднем (true) плане
   ObjectSetInteger(0,name,OBJPROP_BACK,false);
  }


3.8. Построение графика "Ренко" на главном графике

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

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

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

//+------------------------------------------------------------------+
//| Func Draw Renko Main Chart                                       |
//+------------------------------------------------------------------+
void func_draw_renko_main_chart(color color_square_up,
                                color color_square_down,
                                color color_frame_up,
                                color color_frame_down,
                                int width_square,
                                int width_frame,
                                bool square,
                                bool fill,
                                bool frame)
  {
   string name_square;
   string name_frame;

   for(int z=2; z<=a; z++)
     {
      name_square=IntegerToString(magic_numb)+"_Square_"+IntegerToString(z);
      name_frame=IntegerToString(magic_numb)+"_Frame_"+IntegerToString(z);
      if(type_box[z]==1)
        {
         if(square==true)func_create_square_or_rectangle(name_square,up_price[z],down_price[z],time_box[z-1],time_box[z],width_square,color_square_up,fill);
         if(frame==true)func_create_square_or_rectangle(name_frame,up_price[z],down_price[z],time_box[z-1],time_box[z],width_frame,color_frame_up,false);
        }
      if(type_box[z]==-1)
        {
         if(square==true)func_create_square_or_rectangle(name_square,up_price[z],down_price[z],time_box[z-1],time_box[z],width_square,color_square_down,fill);
         if(frame==true)func_create_square_or_rectangle(name_frame,up_price[z],down_price[z],time_box[z-1],time_box[z],width_frame,color_frame_down,false);
        }
     }
  }

3.9. Построение графика типа "ZigZag" на главном графике

Следующая функция, которая является своеобразном дополнением к индикатору, это функция построения графика "ZigZag" или "func_draw_zig_zag".

Входными параметрами тут является: вид прорисовки (по максимальным или минимальным ценам, или по точкам построения графика), толщина линии, цвет восходящей и нисходящей линии.

Пример изменения параметра "zig_zag_shadow" можно наблюдать на рисунке 4, здесь видно при выставлении в положении "true", индикатор рисует линии "ZigZag" по кончикам теней (то есть по максимальным или минимальным ценам), в положении "false", линии "ZigZag" рисуются по максимумам и минимумам графика "Ренко".


Рис.4. Пример влияния параметра "zig_zag_shadow" на EURUSD, H1, 10 пунктов.

Рис.4. Пример влияния параметра "zig_zag_shadow" на EURUSD, H1, 10 пунктов. 

Поскольку для построения объекта "трендовая линия" необходимы две точки (начальная и конечная), вводим две переменные, отвечающие за параметр цены, и две переменные, отвечающие за параметр даты. Задаём первую точку в зависимости от вида начального "кирпича" при помощи условных операторов.

Следом идёт цикл, при помощи которого и осуществляется построение всех объектов. Как можно заметить, цикл начинается с анализа второго "кирпича", так как первая точка уже задана. Далее условным оператором проверяется, изменился ли тип "кирпича" (направление движения цены). Затем заполняется переменная названия объекта, и, в зависимости от вида смены движения, при помощи условных операторов происходит раздвоение цикла. В свою очередь, оно также делится на два варианта в зависимости от параметра вида прорисовки.

Если используется отображение по максимальным и минимальным ценам, то задействуются описанные ранее динамические массивы данных Price_high[] и Price_low[], с их помощью осуществляется поиск приближенных минимальных или максимальных точек, поиск ограничен соседними барами.

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

После определения с двумя точками построения, вызывается уже знакомая функция построения графического объекта "трендовая линия". На этом функция заканчивает анализ и построение графика "ZigZag".

//+------------------------------------------------------------------+
//| Func Draw Zig Zag                                                |
//+------------------------------------------------------------------+
void func_draw_zig_zag(bool price_shadow,
                       int line_width,
                       color line_color_up,
                       color line_color_down)
  {
   double price_1=0;
   double price_2=0;
   datetime date_1=0;
   datetime date_2=0;

   if(type_box[1]==1)price_1=down_price[1];
   if(type_box[1]==-1)price_1=up_price[1];
   date_1=time_box[1];
   int id=0; // переменная хранения идентификатора массива Low & High
   int n=0;  // переменная для формирования имени

   string name_line; //--- переменная, отвечающая за название объекта "трендовая линия"

   for(int z=2; z<=a; z++)
     {
      if(type_box[z]!=type_box[z-1])
        {
         n++;
         name_line=IntegerToString(magic_numb)+"_Line_"+IntegerToString(n);
         if(type_box[z]==1)
           {
            if(price_shadow==true)
              {
               id=number_id[z-1];
               if((id-1)>0 && Price_low[id-1]<Price_low[id])id--;
               if(Price_low[id+1]<Price_low[id])id++;
               price_2=Price_low[id];
               date_2=(datetime)Date[id];
              }
            else
              {
               price_2=down_price[z-1];
               date_2=time_box[z-1];
              }
            func_create_trend_line(name_line,price_1,price_2,date_1,date_2,line_width,line_color_down);
            price_1=price_2;
            date_1=date_2;
           }
         if(type_box[z]==-1)
           {
            if(price_shadow==true)
              {
               id=number_id[z-1];
               if((id-1)>0 && Price_high[id-1]>Price_high[id])id--;
               if(Price_high[id+1]>Price_high[id])id++;
               price_2=Price_high[id];
               date_2=(datetime)Date[id];
              }
            else
              {
               price_2=up_price[z-1];
               date_2=time_box[z-1];
              }
            func_create_trend_line(name_line,price_1,price_2,date_1,date_2,line_width,line_color_up);
            price_1=price_2;
            date_1=date_2;
           }
        }
     }
  }

3.10. Функция для удаления графических объектов

Ранее было продумано использование "магического" номера для определения принадлежности объектов к индикатору, это делалось для удобства запуска нескольких индикаторов на одном графике и для удобства удаления объектов.

Следующая функция - это функция удаления объектов или "func_delete_objects". Здесь всего два входных параметра: имя (назначается в зависимости от вида объекта: трендовая линия или прямоугольник) и число объектов. Функция по очереди перебирает все объекты и если объекты с переданным именем существуют, то она их удаляет.

//+------------------------------------------------------------------+
//| Func Delete Objects                                              |
//+------------------------------------------------------------------+
void func_delete_objects(string name,
                         int number)
  {
   string name_del;
   for(int x=0; x<=number; x++)
     {
      name_del=name+IntegerToString(x);
      ObjectDelete(0,name_del);
     }
  }

Для удобства была создана функция, которая консолидирует в себе функции для удаления всех объектов, созданных индикатором.

//+------------------------------------------------------------------+
//| Func All Delete                                                  |
//+------------------------------------------------------------------+
void func_all_delete()
  {
//--- подсчёт графических обьектов
   obj=ObjectsTotal(0,-1,-1);
//--- удаление всех графических объектов принадлежащих индикатору
   func_delete_objects(IntegerToString(magic_numb)+"_Line_",obj);
   func_delete_objects(IntegerToString(magic_numb)+"_Square_",obj);
   func_delete_objects(IntegerToString(magic_numb)+"_Frame_",obj);
//--- периресовка графика
   ChartRedraw(0);
  }


3.11. Функция создания уровней

Ещё одна небольшая функция для более удобного представления графика в окне индикатора, это функция создания уровней или "func_create_levels". Имеет всего два входных параметра: количество создаваемых уровней и цвет уровней.

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

//+------------------------------------------------------------------+
//| Func Create Levels                                               |
//+------------------------------------------------------------------+
void func_create_levels(int level_number,
                        color level_color)
  {
//--- установка количества уровней в окне индикатора
   IndicatorSetInteger(INDICATOR_LEVELS,level_number);
//--- с какого кирпича начинается прорисовка уровней
   int k=0;
   if(a>level_number)k=a-level_number;
//--- назначение цен уровней
   for(int z=0;(z<=level_number && k<=a); z++,k++)
     {
      IndicatorSetDouble(INDICATOR_LEVELVALUE,z,up_price[k]);
      IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,level_color);
     }
  }

3.12. Функция консолидации

Для объединения всех выше разобранных функций была создана функция консолидации или "func_concolidation".

Функция имеет лишь одну задачу - вызывать все исполнительные функции в зависимости от настраиваемых параметров.

//+------------------------------------------------------------------+
//| Func Consolidation                                               |
//+------------------------------------------------------------------+
void func_concolidation()
  {
//--- удаление всех графических объектов принадлежащих индикатору
   func_all_delete();
//--- получение текущей даты
   date_stop=TimeCurrent();
//--- изменение начальной даты ввиду ограниченных возможностей размера буфера
   if((bars=Bars(_Symbol,time_frame,date_start,date_stop))>ArraySize(Price))
     {
      date_start=func_calc_date_start(date_start,date_stop);
      Alert("Начальная дата была изменена системой из за нехватка размера графика");
      date_change=true;
      //--- подсчёт баров на расчётном промежутке времени
      bars=Bars(_Symbol,time_frame,date_start,date_stop);
     }
//---
   bool result_copy_price=func_copy_price(Price,time_frame,date_start,date_stop,type_price);
   bool result_copy_date=func_copy_date(Date,time_frame,date_start,date_stop);
//--- меняем параметр изменения даты
   if(result_copy_price=true && result_copy_date==true)date_change=false;
//---
   if(zig_zag_shadow==true)
     {
      func_copy_price(Price_high,time_frame,date_start,date_stop,2);
      func_copy_price(Price_low,time_frame,date_start,date_stop,3);
     }
//---
   func_draw_renko(Price,Date,filter_number,shadow_print,type_step,step);
   if(zig_zag==true)func_draw_zig_zag(zig_zag_shadow,zig_zag_width,zig_zag_color_up,zig_zag_color_down);
//---
   func_draw_renko_main_chart(square_color_up,square_color_down,frame_color_up,frame_color_down,square_width,frame_width,square_draw,square_fill,frame_draw);
   func_create_levels(levels_number,levels_color);
//--- перерисовка графика
   ChartRedraw(0);
  }

3.13. Функции OnCalculate() и OnChartEvent()

Перед тем как перейти к функции OnCalculate(), рассмотрим ещё маленькую функцию анализа появления нового бара или "func_new_bar".

Функция представляет собой упрощённый вид функции, описанной в IsNewBar.

//+------------------------------------------------------------------+
//| Func New Bar                                                     |
//+------------------------------------------------------------------+
bool func_new_bar(ENUM_TIMEFRAMES period_time)
  {
//---
   static datetime old_times; // переменная хранения старых значений
   bool res=false;            // переменная результата анализа  
   datetime new_time[1];      // время нового бара
//---
   int copied=CopyTime(_Symbol,period_time,0,1,new_time); // скопируем время последнего бара в ячейку new_time  
//---
   if(copied>0) // все ок. данные скопированы
     {
      if(old_times!=new_time[0])    // если старое время бара не равно новому
        {
         if(old_times!=0) res=true; // если это не первый запуск, то истина = новый бар
         old_times=new_time[0];     // запоминаем время бара
        }
     }
//---
   return(res);
  }

Функция OnCalculate() отвечает за запуск функции консолидации в случае, если появляется новый бар на периоде обновления графика, заданном в параметрах индикатора. 

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//---
   if(func_new_bar(time_redraw)==true)
     {
      func_concolidation();
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }

Функция OnChartEvent() имеет два действия: это очистка графика в случае нажатия кнопки "C" (осуществляется вызовом функции удаления графических объектов) и перерисовка графика (вызов функции консолидации) в случае нажатия кнопки "R".

//+------------------------------------------------------------------+
//| OnChartEvent                                                     |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // идентификатор события  
                  const long& lparam,   // параметр события типа long
                  const double& dparam, // параметр события типа double
                  const string& sparam) // параметр события типа string
  {
//--- событие нажатия клавиши на клавиатуре
   if(id==CHARTEVENT_KEYDOWN)
     {
      if(lparam==82) //--- была нажата клавиша "R"
        {
         //--- вызов функции консолидации
         func_concolidation();
        }
      if(lparam==67) //--- была нажата клафиша "C"
        {
         //--- очистка графика от построенных объектов индикатора
         func_all_delete();
        }
     }
  }


3.14. Функция OnDeinit()

И, наконец, функция выхода или OnDeinit(). Функция вызывает только функцию удаления всех графических объектов, созданных индикатором.

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+ 
void OnDeinit(const int reason)
  {
//--- очистка графика от построенных объектов индикатора
   func_all_delete();
  }


4. Практическое применение графика "Ренко"

График "Ренко" ориентирован в основном для определения основной тенденции движения.

Пример классической работы на графике - "продавай, когда восходящий "кирпич" меняется на нисходящий, и покупай, когда нисходящий "кирпич" меняется на восходящий".

Иллюстрацию этого классического метода можно наблюдать на рис. 5:


Рис.5. Пример классической работы на графике "Ренко" на EURUSD (H4, 20 пунктов)

Рис.5. Пример классической работы на графике "Ренко" на EURUSD (H4, 20 пунктов)

На рисунке 5 обозначены шесть возможных точек (A,B,C,D,E,F) для входа в рынок.

В точке "A" видно, что после восходящего "кирпича" образовался нисходящий "кирпич".

Вход осуществляется сразу, здесь как и в других точках (B,C,D), первый образованный "кирпич" в противоположном направление образован одним движением, что нельзя сказать о точке "E", так как там на одном движении было образованно сразу два "кирпича". Это видно по образованным теням, вниз заканчивающимися на одном уровне.

В этом случае вход возможен между точками "E" и "F", как видно из графика это не совсем удачный вход, так как цена идёт в противоположном направлении, на точке "F" аналогичная ситуация. Там тоже из-за одного движения образуется сразу два "кирпича". Это видно по теням вверх, заканчивающихся на одном уровне. Хотя это и было сильное движение, но цена сохранила своё направление.

Из этого можно сделать вывод, что когда рынок более спокоен, и одно движение образует только один "кирпич" (об этом можно судить по теням) в противоположном направлении, то это наиболее благоприятный момент для входа в рынок, а если видно образование сразу нескольких "кирпичей", то такой вход может быть небезопасным.

Построение типа "ZigZag" на данном графике можно использовать для графического анализа, на рис. 6 показано несколько таких примеров: определение линии "поддержки" и "сопротивления", а также модели "Голова и плечи".


Рис.6. Пример использования графического анализа на GBPUSD (H4, 20 пунктов) 

Рис.6. Пример использования графического анализа на GBPUSD (H4, 20 пунктов)

Ещё один пример графического анализа "Равноудаленный канал", показан на рис. 7.

Для более точного построения индикатор был настроен на анализ часового таймфрейма, а сам вывод построения был сделан на четырёхчасовом таймфрейме.

Такие настройки позволяют рассматривать сигналы на нескольких таймфреймах одновременно, то есть на одном можно применить один индикатор, а на втором другой.


Рис.7. Пример графического анализа "Равноудаленный Канал" USDCHF, H4, настройки H1, 20 пунктов. 

Рис.7. Пример графического анализа "Равноудаленный Канал" USDCHF, H4, настройки H1, 20 пунктов.

На рис. 8 приведён ещё один пример получения на одном графике сигналов с различных таймфреймов.

Можно видеть, что часовой график показывает возможные ближайшие перевороты, четырехчасовой график их заглаживает (отфильтровывает лишние сигналы), а дневной график утверждает долгосрочное направление тенденции.


Рис.8. Пример использования индикатора на GBPUSD, H1, H4 и D1. 

Рис.8. Пример использования индикатора на GBPUSD, H1, H4 и D1

Ещё один пример, ориентированный именно на индикатор, представлен на рис. 9. Правило гласит: "строй восходящую линию между двумя ближайшими красными "кирпичами", имеющие между собой хоть один синий "кирпич", в дальнейшем при образовании "кирпича", под линией, продавай".

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

Цвета в правиле указаны в соответствии с рис. 9. На рис. 9 синими и красными мелкими стрелками также обозначены места, служащие опорой для построения линии и крупными стрелками места, служащие сигналами для купли или продажи.

Рис.9. Пример использования индикатора на GBPUSD, H4, 25 пунктов 

Рис.9. Пример использования индикатора на GBPUSD, H4, 25 пунктов

Заключение

График "Ренко" будет интересен как для начинающих, так и для профессионалов рынка. Спустя много лет, он не остался позади, и по сей день находит применение на рынке.

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

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

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


Прикрепленные файлы |
abcr.mq5 (39.26 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (12)
Dmitriy Zabudskiy
Dmitriy Zabudskiy | 16 февр. 2017 в 18:15
Stanislav Korotky:

Почему не сформированы квадраты вверх (малиновый) и вниз (оранжевый), помеченные знаками вопроса ниже? Там цена прошла размер бокса в прежнем направлении.

 

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

То есть :

input type_price_renko type_price=close;                        // Вид цены для построения

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

Да, согласен, что это не совсем корректно в отношении классической теории, но это можно подправить изменив например период расчёта, то есть берем график H1, а в настройках  time_frame (смотрите ниже), ставим M1. 

input ENUM_TIMEFRAMES  time_frame=PERIOD_CURRENT;               // Период расчёта
Stanislav Korotky
Stanislav Korotky | 17 февр. 2017 в 12:27
Dmitriy Zabudskiy:

То есть, если одна свеча очень большая, то её размер указывается в тени. Это нужно только для того чтобы знать, какие боксы сформированы одной свечой (одним периодом). 

Понятно. Значит это такая специальная модификация ренко.
talkfusion
talkfusion | 28 апр. 2017 в 09:28

Hello guy:

I downloaded your indicator, but it seems work not well ,you can see from the picture what was happening.



I run this indicator at Roboforex's MT5, but it can not display like the picture that you uploaded, I mean I wanna to see the renko box on the charting of a timeframe just like this .

图例.4. 参数对 "zig_zag_shadow" 的影响 EURUSD, H1, 10 点。


Can you tell me how to make this indicator works normally ? Thank you very much .

Dmitriy Zabudskiy
Dmitriy Zabudskiy | 18 авг. 2019 в 21:26
talkfusion:

Hello guy:

I downloaded your indicator, but it seems work not well ,you can see from the picture what was happening.

I run this indicator at Roboforex's MT5, but it can not display like the picture that you uploaded, I mean I wanna to see the renko box on the charting of a timeframe just like this .

Can you tell me how to make this indicator works normally ? Thank you very much .

Perhaps the old data is somehow connected, you can try to close the chart, open and add an indicator. If this does not work, then write how you run it.

behtilb
behtilb | 9 авг. 2022 в 23:11
Здравствуйте! А нет ли вариации данного индикатора под МТ4?
Склейка фьючерсов в MetaTrader 5 Склейка фьючерсов в MetaTrader 5
Технический анализ фьючерсов затруднён из-за непродолжительного срока их обращения. На относительно коротких графиках трудно проводить технический анализ, к примеру, количество баров на дневном графике фьючерса на индекс Украинской биржи UX-9.13 чуть больше 100. Поэтому перед трейдером стоит вопрос построения синтетических инструментов по фьючерсам. В статье рассматривается вопрос склейки истории по фьючерсным контрактам с различными датами обращения в терминале MetaTrader 5.
Видео: Как работают торговые сигналы на платформе MetaTrader Видео: Как работают торговые сигналы на платформе MetaTrader
Короткий видеоролик за 15 минут расскажет и покажет, что такое торговые сигналы, как оформить на них подписку и как самому стать Поставщиком. В видео максимально подробно показаны все особенности нашего сервиса. После внимательного просмотра материала вы сможете самостоятельно подписаться на любой Сигнал из обширной базы или начать продавать собственные сигналы.
Типичные ошибки в программах на MQL4 и методы их устранения Типичные ошибки в программах на MQL4 и методы их устранения
В старой версии компилятора во избежание критического завершения программ многие ошибки обрабатывались средой исполнения. Например, деление на ноль или выход за пределы массива являются критическими ошибками и обычно приводят к аварийному завершению работы программ. Новый компилятор позволяет обнаружить реальные или потенциальные источники ошибок и повысить качество кода. В этой статье мы рассмотрим возможные ошибки, возникающие при компиляции старых программ и методы их устранения.
Обновление на новый MetaTrader 4 билд 600 и выше Обновление на новый MetaTrader 4 билд 600 и выше
В новой версии терминала MetaTrader 4 была изменена структура хранения пользовательских данных. Если раньше все программы, шаблоны, профили и т.д. хранились прямо в папке установки терминала, то теперь данные, необходимые для работы конкретного пользователя терминала, хранятся в отдельной специальной папке, называемой каталог данных. В этой статье собраны ответы на популярные вопросы.