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

Dmitriy Zabudskiy | 4 ноября, 2013


Введение

В статье Индикатор для построения графика "Крестики - Нолики" был описан один из вариантов программного построения графика «Крестики - Нолики», этот график пришел к нам из XIX века, но это не единственный график, который дошел к нам из далекого прошлого. Еще одним ярким представителем раннего представления финансового рынка является график «Каги», именно о нем и пойдет речь в этой статье.

В мае 1878 года в Токио открылась первое тогда еще не знакомое в Японии финансовое учреждение – фондовая биржа, известная сегодня как Токийская фондовая биржа. Именно это событие послужило созданию и последующему развитию графиков «Каги». Но для США и Европы графики «Каги» стали известны после публикации в 1994 году книги Стива Нисона «За гранью японских свечей» (англ. Beyond Candlesticks).

Своему названию график обязан японскому слову «каги», обозначавший в то время ключ в виде буквы «L» (в данный момент вышедший из употребления), также существует модификация этого названия в виде: «ключевой график». В книге Стива Нисона «За гранью японских свечей», так же приводятся альтернативные названия этого графика: график диапазона цен, крючковый график, дельта-график или цепной график.

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

График представляет собой совокупность толстых «Yang» и тонких «Yin» линий, меняющихся в зависимости от ситуации на рынке. В случае продолжения движения рынка в том же направлении линия продлевается, покоряя новый ценовой диапазон. Однако если рынок разворачивается на заданную величину, то в новом столбце проводится линия «Каги» в противоположном направлении. Заданная величина задается либо в пунктах (обычно применяется для валютных пар), либо в процентах от текущей цены (обычно применяется для акций). Толщина линии меняется в зависимости от прорыва ближайшего минимума или максимума.


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

Для примера возьмем данные из истории, по паре EURUSD, на часовом таймфрейме, период с 8 по 11 октября.

Пример классического построения с 15-пунктовым порогом разворота представлен на рис. 1:

Пример построения графика "Каги" EURUSD H1

Рис. 1. Пример построения графика "Каги" на паре EURUSD, часовой таймфрейм

Как мы видим, в 17:00 цена пошла на спад, это продолжалось до 21:00 и в 22:00 мы видим, как цена поднимается с уровня 1.3566 и закрывается на уровне 1.3574, то есть цена проходит 11 пунктов, что недостаточно для разворота, но и новое нижнее значение не достигнуто. Следующие два часа цена находится во флете, и спустя 3 часа в 01:00 (9 октября) мы видим сильное восходящее движение, которое закрывается на уровне 1.3591, что составляет 25 пунктов (1.3591-1.3566), это означает, что цена развернулась вверх.

Следующий час продолжается восходящее движение, которое закрывается на уровне 1.3599, это укрепляет толстую линию Yang. В 03:00 мы наблюдаем, как цена резко начинает падать и закрывается на уровне 1.3578, что составляет 21 пункт от предыдущего максимума (1.3599-1.3578), что более чем достаточно для разворота, линия направляется вниз, но сохраняет свой вид (толстая линия Yang).

Вплоть до 16:00 мы продолжаем наблюдать нисходящее движение цены, и тут мы видим, как линия прорывает ближайший минимум и меняется с толстой линии - Yang на тонкую линию Yin. Значением цены прорыва послужил минимум 1.3566, описанный ранее. Далее цена продолжает свое движение в виде линии Yin и меняется на Yang 10 октября в 14:00, преодолев ближайший максимум в 1.3524, сформированный в 23:00 (9 октября). Думаю, этим маленьким примером сумел показать, как происходит построение графика «Каги».


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

Чтобы сделать индикатор независимым от текущего таймфрейма, было решено отдельно копировать данные указанного таймфрейма, на котором предполагалось построение, и затем по полученным данным производить построение.

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

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

Не могу сказать, будет это новой идеей или нет, но я о таком построении не слышал. Суть добавленного дополнительного фильтра заключается в том, что в этом режиме фильтруется не только разворотные моменты, но и каждое движение графика. То есть, чтобы обозначить новый минимум или максимум движения (не путать с плечом/талией - разворотом), цена должна пройти заданную величину. В общем, куда бы ни шла цена, для начала она должна пройти заданную величину, и лишь потом определяется, было ли это продолжение движения или разворот.

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

Классическое и модифицированное варианты построения графика "Каги"

Рис. 2. Модифицированный (синяя линия) и классический (красная линия) варианты построения графика "Каги"

В индикаторе кроме построения графика «Каги» реализованы некоторые вспомогательные построения, как в окне индикатора, так и на основном графике.

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

На основном графике реализованы ценовые метки разворота, а также временные метки разворота, которые (в зависимости от настроек) могут быть одного цвета или менять цвет на цвет линий Yin или Yang.

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

Код условно можно разделить на три основные функции и одиннадцать вспомогательных. Основной груз расчетов и заполнения буферов не только базовых графических построений, но и вспомогательных буферных массивов ложится на функцию построения графика «Каги» в окне индикатора. Две другие функции отвечают за снабжение данными, одна копирует данные о времени, другая - о ценах каждого бара выбранного таймфрейма.

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


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

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

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

Код начинается с объявления индикатора в отдельном окне, а также 12 буферов и 8 графических построений индикатора. Для начала рассмотрим, почему же использовано 8 графических построений, две из которых «гистограммы» и шесть «линий». Каждая «гистограмма» строит свою вертикальную линию, одна отвечает за линию Yin, другая за Yang.

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

Для наглядности постараюсь объяснить это на рисунке 3, где показано, что будет, если использовать всего два графических построения типа «линия»:

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

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

Далее идет оформление меню настроек, тут пять перечислений (рассмотрим их во входящих параметрах).

Первый входящий параметр “period” - это период, на котором осуществляется построение, за ним следует "period_to_redraw" - период обновления построения графика и последний временной параметр “start_data” - имеет одноименное определение, это время с которого следует осуществлять построение.

Далее идут параметры построения графика и дополнительных обозначений:

Далее идут объявления буферов индикатора, вспомогательных буферов для хранения значений цен и времени (Price, Time), вспомогательные переменные (stop_data, bars_copied, bars_copied_time, copy_history, copy_time), массивы, служащие для хранения информации о том, на какой линии Yin или Yang произошла смена движения графика, время смены, цена смены, а также центральная цена (если на баре происходит смена линии Yin на Yang, или наоборот) и в конце объявляется одна из самых используемых глобальных переменных, содержащая информацию о количестве смены движения графика "а".

//+------------------------------------------------------------------+
//|                                                         BKCV.mq5 |
//|                                   Azotskiy Aktiniy ICQ:695710750 |
//|                          https://www.mql5.com/ru/users/Aktiniy |
//+------------------------------------------------------------------+
//--- Build Kagi Chart Variable
#property copyright "Azotskiy Aktiniy ICQ:695710750"
#property link      "https://www.mql5.com/ru/users/Aktiniy"
#property version   "1.00"
#property description "Build Kagi Chart Variable"
#property description " "
#property description "This indicator makes drawing a chart Kagi as a matter of indicator window, and in the main chart window"
#property indicator_separate_window
#property indicator_buffers 12
#property indicator_plots   8
//--- plot Yin
#property indicator_label1  "Yin"
#property indicator_type1   DRAW_HISTOGRAM2
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- plot Yin1
#property indicator_label2  "Yin1"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
//--- plot Yin2
#property indicator_label3  "Yin2"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrRed
#property indicator_style3  STYLE_SOLID
#property indicator_width3  1
//--- plot Yin3
#property indicator_label4  "Yin3"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrRed
#property indicator_style4  STYLE_SOLID
#property indicator_width4  1
//--- plot Yang
#property indicator_label5  "Yang"
#property indicator_type5   DRAW_HISTOGRAM2
#property indicator_color5  clrRed
#property indicator_style5  STYLE_SOLID
#property indicator_width5  2
//--- plot Yang1
#property indicator_label6  "Yang1"
#property indicator_type6   DRAW_LINE
#property indicator_color6  clrRed
#property indicator_style6  STYLE_SOLID
#property indicator_width6  2
//--- plot Yang2
#property indicator_label7  "Yang2"
#property indicator_type7   DRAW_LINE
#property indicator_color7  clrRed
#property indicator_style7  STYLE_SOLID
#property indicator_width7  2
//--- plot Yang3
#property indicator_label8  "Yang3"
#property indicator_type8   DRAW_LINE
#property indicator_color8  clrRed
#property indicator_style8  STYLE_SOLID
#property indicator_width8  2
//--- Перечисления в качестве входных данных (для более красивого оформления)
//--- Тип построения графика Каги
enum kagi_type_enum
  {
   classic=0,  // Classic
   modified=1, // Modified
  };
//--- Тип используемой цены для построения
enum price_type_enum
  {
   c=0, // Close
   o=1, // Open
   h=2, // High
   l=3, // Low
  };
//--- Тип используемого разворота
enum type_doorstep_enum
  {
   point=0,   // Point
   procent=1, // Procent
  };
//--- Тип расположения уровней
enum levels_type_enum
  {
   cor=0, // Cornering
   equ=1, // Equal distance
  };
//--- Тип изменения цвета уровней (работает при выборе "Тип расположения уровней"="Cornering")
enum levels_change_color_enum
  {
   up_down=0,  // Up & Down
   yin_yang=1, // Yin & Yang
   no=2,       // Don't change
  };
//--- input parameters
input ENUM_TIMEFRAMES period=PERIOD_CURRENT;                // Calculation period to build the chart
input ENUM_TIMEFRAMES period_to_redraw=PERIOD_M1;           // Refresh period chart
input datetime start_data=D'2013.07.10 00:00:00';           // Start time to build the chart
input kagi_type_enum kagi_type=classic;                     // The type to build Kagi chart
input price_type_enum price_type=c;                         // Price used to build chart
input type_doorstep_enum type_doorstep=point;               // Type calculate doorstep
input double   doorstep=25;                                 // Doorstep reversal
input color    color_yin=clrRed;                            // Color Yin line (indicator window)
input color    color_yang=clrRed;                           // Color Yang line (indicator window)
input char     width_yin=1;                                 // Width Yin line (indicator window)
input char     width_yang=2;                                // Width Yang line (indicator window)
input bool     levels_on_off=false;                         // Draw level (indicator window)
input levels_type_enum levels_type=cor;                     // Type of drawing levels (indicator window)
input uint     levels_number=6;                             // Number of levels  (indicator window)
input levels_change_color_enum levels_change_color=up_down; // Type change color of levels (indicator window)
input color    levels_first_color=clrBeige;                 // The first color of level  (indicator window)
input color    levels_second_color=clrCoral;                // The second color of level  (indicator window)
input bool     label_1=true;                                // Draw price label on (indicator window)
input uint     label_1_number=10;                           // The number of labels (indicator window)
input color    label_1_color=clrGreenYellow;                // The color of labels (indicator window)
input bool     label_2=true;                                // Draw price label on (main chart)
input color    label_2_color=clrGreenYellow;                // The color of labels (main chart)
input bool     time_line_draw=true;                         // Draw a timeline reversal (main chart)
input bool     time_separate_windows=false;                 // Draw a timeline reversal on indicator window
input bool     time_line_change_color=true;                 // Different color timeline on the Yin and Yang lines (main chart)
input color    time_first_color=clrRed;                     // The first color of timeline (main chart)
input color    time_second_color=clrGreenYellow;            // The second color of timeline (main chart)
input bool     kagi_main_chart=true;                        // Draw Kagi on main chart (main chart)
input color    color_yin_main=clrRed;                       // Color Yin line (main chart)
input color    color_yang_main=clrRed;                      // Color Yang line (main chart)
input char     width_yin_main=1;                            // Width Yin line (main chart)
input char     width_yang_main=2;                           // Width Yang line (main chart)
input long     magic_numb=65758473787389;                   // The magic number for draw objects
//--- indicator buffers
double         YinBuffer1[];
double         YinBuffer2[];
double         Yin1Buffer[];
double         Yin2Buffer[];
double         Yin3Buffer[];
double         YangBuffer1[];
double         YangBuffer2[];
double         Yang1Buffer[];
double         Yang2Buffer[];
double         Yang3Buffer[];
//--- вспомогательные переменные
double Price[]; // Буфер для хранения скопированных данных о ценах
double Time[];  // Буфер для хранения скопированных данных о времени
//---
datetime stop_data;      // Текущее время
int bars_copied=0;       // Количество уже скопированных баров с начальной даты
int bars_copied_time;    // Количество уже скопированных баров с начальной датой
bool copy_history=false; // Результат копирования истории о ценах
bool copy_time=false;    // Результат копирования истории о времени
//---
datetime time_change[];      // Массив для записи времени начала смены движения графика (вверх или вниз)
char time_line[];            // Массив для хранения информации на какой линии (Yin=0 или Yang=1) сменилось напрвление
double time_change_price[];  // Массив для записи цены смены движения графика
double time_central_price[]; // Массив для записи средней цены при смене движения графика

uint a=0; // Переменная для построения графика, фиксирует количество разворотов графика

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

Далее идет функция инициализации индикатора. В ней указываются индикаторные буферы, их индексация (в основном в виде таймсерий, так как график «Каги» короче основного графика, то его лучше рисовать задом наперед). Также задаются значения, которые не будут выводиться на экран (EMPTY_VALUE=-1).

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

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

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,YinBuffer1,INDICATOR_DATA);
   ArraySetAsSeries(YinBuffer1,true);
   SetIndexBuffer(1,YinBuffer2,INDICATOR_DATA);
   ArraySetAsSeries(YinBuffer2,true);
   SetIndexBuffer(2,Yin1Buffer,INDICATOR_DATA);
   ArraySetAsSeries(Yin1Buffer,true);
   SetIndexBuffer(3,Yin2Buffer,INDICATOR_DATA);
   ArraySetAsSeries(Yin2Buffer,true);
   SetIndexBuffer(4,Yin3Buffer,INDICATOR_DATA);
   ArraySetAsSeries(Yin3Buffer,true);
//---
   SetIndexBuffer(5,YangBuffer1,INDICATOR_DATA);
   ArraySetAsSeries(YangBuffer1,true);
   SetIndexBuffer(6,YangBuffer2,INDICATOR_DATA);
   ArraySetAsSeries(YangBuffer2,true);
   SetIndexBuffer(7,Yang1Buffer,INDICATOR_DATA);
   ArraySetAsSeries(Yang1Buffer,true);
   SetIndexBuffer(8,Yang2Buffer,INDICATOR_DATA);
   ArraySetAsSeries(Yang2Buffer,true);
   SetIndexBuffer(9,Yang3Buffer,INDICATOR_DATA);
   ArraySetAsSeries(Yang3Buffer,true);
//--- добавляем буфер для копирования данных о ценах, дла расчета
   SetIndexBuffer(10,Price,INDICATOR_CALCULATIONS);
//--- добавляем буфер для копирования данных о времени открытия бара, для построения
   SetIndexBuffer(11,Time,INDICATOR_CALCULATIONS);

//--- задание какие значения не будут прорисовываться
   for(char x=0; x<8; x++)
     {
      PlotIndexSetDouble(x,PLOT_EMPTY_VALUE,-1);
     }
//--- устанавливаем внешний вид индикатора
   IndicatorSetString(INDICATOR_SHORTNAME,"BKCV "+IntegerToString(magic_numb)); // Имя индикатора
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits); // Точность отображения
//--- присваиваем названия графическим построениям
   PlotIndexSetString(0,PLOT_LABEL,"Yin");
   PlotIndexSetString(1,PLOT_LABEL,"Yin");
   PlotIndexSetString(2,PLOT_LABEL,"Yin");
   PlotIndexSetString(3,PLOT_LABEL,"Yin");
   PlotIndexSetString(4,PLOT_LABEL,"Yang");
   PlotIndexSetString(5,PLOT_LABEL,"Yang");
   PlotIndexSetString(6,PLOT_LABEL,"Yang");
   PlotIndexSetString(7,PLOT_LABEL,"Yang");
//--- запрещаем показ результатов текущих значений для графических построений
   PlotIndexSetInteger(0,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(1,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(2,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(3,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(4,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(5,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(6,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(7,PLOT_SHOW_DATA,false);
//--- установка цвета для линии Yin
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,color_yin);
   PlotIndexSetInteger(1,PLOT_LINE_COLOR,color_yin);
   PlotIndexSetInteger(2,PLOT_LINE_COLOR,color_yin);
   PlotIndexSetInteger(3,PLOT_LINE_COLOR,color_yin);
//--- установка цвета для линии Yang
   PlotIndexSetInteger(4,PLOT_LINE_COLOR,color_yang);
   PlotIndexSetInteger(5,PLOT_LINE_COLOR,color_yang);
   PlotIndexSetInteger(6,PLOT_LINE_COLOR,color_yang);
   PlotIndexSetInteger(7,PLOT_LINE_COLOR,color_yang);
//--- установка толщины линии Yin
   PlotIndexSetInteger(0,PLOT_LINE_WIDTH,width_yin);
   PlotIndexSetInteger(1,PLOT_LINE_WIDTH,width_yin);
   PlotIndexSetInteger(2,PLOT_LINE_WIDTH,width_yin);
   PlotIndexSetInteger(3,PLOT_LINE_WIDTH,width_yin);
//--- установка толщины линии Yang
   PlotIndexSetInteger(4,PLOT_LINE_WIDTH,width_yang);
   PlotIndexSetInteger(5,PLOT_LINE_WIDTH,width_yang);
   PlotIndexSetInteger(6,PLOT_LINE_WIDTH,width_yang);
   PlotIndexSetInteger(7,PLOT_LINE_WIDTH,width_yang);
//--- установка колличества уровней в окне индикатора
   IndicatorSetInteger(INDICATOR_LEVELS,levels_number);
//---
   return(INIT_SUCCEEDED);
  }


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

Теперь рассмотрим функции копирования данных.

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

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

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

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

//+------------------------------------------------------------------+
//| Func Copy History                                                |
//+------------------------------------------------------------------+
bool func_copy_history(double &result_array[],
                       datetime data_start,
                       datetime data_stop)
  {
//---
   int x=false; // Переменная для ответа

   int result_copy=-1; // Количество скопированных данных

   static double price_interim[]; // Временный динамический массив для хранения скопированных данных
   static int bars_to_copy;       // Количество баров для копирования

   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(price_interim,bars_to_copy); // Меняем размер приемного массива

   switch(price_type)
     {
      case 0:
         result_copy=CopyClose(_Symbol,period,0,bars_to_copy,price_interim);
         break;
      case 1:
         result_copy=CopyOpen(_Symbol,period,0,bars_to_copy,price_interim);
         break;
      case 2:
         result_copy=CopyHigh(_Symbol,period,0,bars_to_copy,price_interim);
         break;
      case 3:
         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; // Увеличиваем значение скопированных данных
     }
//---
   return(x);
  }

Следующая функция - функция копирования данных о времени. Она отличается от предыдущей тем, что оперирует другим типом переменной - datetime (производит ее преобразование в double при копировании в буферный массив Time – массив ответа функции). Еще одно отличие в том, что нет необходимости в использовании оператора switch(), так как не надо производить выбор копируемых данных.

//+------------------------------------------------------------------+
//| Func Copy Time                                                   |
//+------------------------------------------------------------------+
bool func_copy_time(double &result_array[],
                    datetime data_start,
                    datetime data_stop)
  {
//---
   int x=false; // Переменная для ответа
   int result_copy=-1; // Количество скопированных данных

   static datetime time_interim[]; // Временный динамический массив для хранения скопированных данных
   static int bars_to_copy_time; // Количество баров для копирования

   bars_to_copy_time=Bars(_Symbol,period,data_start,data_stop); // Узнаем текущее количество баров на временном промежутке
   bars_to_copy_time-=bars_copied_time; // Подсчитываем количество баров, которые надо скопировать

   if(bars_copied_time!=0) // Если данные копируются не в первый раз
     {
      bars_copied_time--;
      bars_to_copy_time++;
     }
   ArrayResize(time_interim,bars_to_copy_time); // Меняем размер приемного массива
   result_copy=CopyTime(_Symbol,period,0,bars_to_copy_time,time_interim);

   if(result_copy!=-1) // Если копирование в промежуточный массив было успешно
     {
      ArrayCopy(result_array,time_interim,bars_copied_time,0,WHOLE_ARRAY); // Копируем в основной массив данных, данные из временного
      x=true; // присваиваем положительный ответ функции
      bars_copied_time+=result_copy; // Увеличиваем значение скопированных данных
     }
//---
   return(x);
  }

3.4. Функция расчета параметра разворота

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

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

//+------------------------------------------------------------------+
//| Func Calculate Doorstep                                          |
//+------------------------------------------------------------------+
int func_calc_dorstep(double price)
  {
   double x=0; // Переменная для ответа
   if(type_doorstep==0) // Если расчет нужно произволить по пунктам
     {
      x=doorstep;
     }
   if(type_doorstep==1) // Если расчет нужно производить в процентах
     {
      x=price/_Point*doorstep/100;
     }
   return((int)x);
  }
 

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

На этом мы рассмотрели все функции, которые необходимы нам для работы основной функции - построения графика «Каги» в индикаторном окне (т.е. заполнение индикаторных буферов). Входные параметры функции составляют массивы данных, два из которых - уже знакомые нам расчетные буферы, скопированные ранее Price и Time, все остальные - массивы буферов графического построения индикатора.

Внутри функции объявляются переменные, необходимые для хранения информации о построении графика, так как график строится посредством оператор цикла for, то необходимо иметь информацию, на чем закончился предыдущий проход. Это реализуется с помощью шести переменных: line_move - куда шла цена на предыдущем проходе, line_gauge - калибр линии (толщина линии) - Yin или Yang, price_1 и price_2 - предыдущая и текущая рассматриваемая цена, price_down и price_up - предыдущая цена плеча и талии. Как мы видим, price_1 сразу приравнивается первый элемент массива скопированных цен, это потому что с самого начала цикла эта переменная участвует в расчетах перед сравнением.

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

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

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

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

Перед каждым проходом цикла переменной price_2 присваивается значение текущей анализируемой цены из буфера Price для дальнейшего сравнения в условных операторах if-else. Потом поэтапно проходит анализ буферного массива скопированных данных, и заполняются вышеуказанные массивы. Каждый условный оператор if-else выполняет определенные действия в зависимости от условий: предыдущее направление движения линии графика (вверх или вниз) и предыдущий вид линии (Yin или Yang). Далее в зависимости от вида построения (классическое или модифицированное), происходит проверка условия движения (прошла ли цена условное количество пунктов).

Ну и если все это состоялось, то происходит переназначение или определение новых переменных (элементов массивов). В самом начале происходит определение начального движения, какая линия будет первая: Yin или Yang. Далее уже в зависимости от движения и предыдущих действий происходит распределение.

Всего существует два направления движения:

  1. Цена движется вверх;
  2. Цена движется вниз.

И по четыре вида предыдущих действий в каждом направлении движения:

  1. Предыдущая линия была Yin и двигалась вверх;
  2. Предыдущая линия была Yang и двигалась вверх;
  3. Предыдущая линия была Yin и двигалась вниз;
  4. Предыдущая линия была Yang и двигалась вниз.

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

После этого основной цикл заканчивается и производится перераспределение (переворачивание) и заполнение буферов для построения графика, в уже меньшем цикле из числа переворотов графика «Каги», которые были определены ранее в основном цикле и записаны в переменную “a”. Что касается распределений верхних и нижних значений цен, вертикальных линий, то тут все просто: происходит простое переворачивание, то есть присвоение первичных значений (массивов с индексами 0,1,2,3...) полученных ранее конечным значениям буферов (в роли конечного значения выступает элемент с индексом “а”, то есть а,а-1,а-2,а-3...). Что же касается линий разворотов (горизонтальные линии), то чтобы они не слиплись, как описывалось ранее, производится чередование при помощи оператора-переключателя switch.

На этом основная функция построения графика «Каги» заканчивает свою работу.

//+------------------------------------------------------------------+
//| Func Draw Kagi                                                   |
//+------------------------------------------------------------------+
void func_draw_kagi(double &array_input[],
                    double &arr_yin_1[],
                    double &arr_yin_2[],
                    double &arr_yin_lin1[],
                    double &arr_yin_lin2[],
                    double &arr_yin_lin3[],
                    double &arr_yang_1[],
                    double &arr_yang_2[],
                    double &arr_yang_lin1[],
                    double &arr_yang_lin2[],
                    double &arr_yang_lin3[],
                    double &arr_time[])
  {
//---
   a=0; // Переменная для построения графика, фиксирует количество разворотов графика
   char line_move=0; // Предыдущее направление движения цены 1-вверх, -1-вниз
   char line_gauge=0; // Предыдущий вид линии 1-толстая yang, -1-тонкая yin
   double price_1=0,price_2=0; // Вспомогательные переменные для определения движения цены
   double price_down=-99999,price_up=99999; // Вспомогательные переменные для хранения значений о ценах переворота
   price_1=array_input[0];
//--- вспомогательные массивы для начального хранения данных перед переворотом (переводом в буфера)
   double yin_int_1[];
   double yin_int_2[];
   double lin_yin[];
   double yang_int_1[];
   double yang_int_2[];
   double lin_yang[];
//--- меняем размеры динамических массивов
   ArrayResize(yin_int_1,bars_copied);
   ArrayResize(yin_int_2,bars_copied);
   ArrayResize(yang_int_1,bars_copied);
   ArrayResize(yang_int_2,bars_copied);
   ArrayResize(lin_yin,bars_copied);
   ArrayResize(lin_yang,bars_copied);
//--- массивы хранения данных о времени
   ArrayResize(time_change,bars_copied_time);
   ArrayResize(time_line,bars_copied_time); // Вид линии Yin = 0 или Yang = 1
   ArrayResize(time_change_price,bars_copied_time);
   ArrayResize(time_central_price,bars_copied_time);
//--- присваиваем -1 (не отрисовываемое) значение переданным буферам
   for(int z=0; z<bars_copied; z++)
     {
      arr_yin_1[z]=-1;
      arr_yin_2[z]=-1;
      arr_yin_lin1[z]=-1;
      arr_yin_lin2[z]=-1;
      arr_yin_lin3[z]=-1;
      arr_yang_1[z]=-1;
      arr_yang_2[z]=-1;
      arr_yang_lin1[z]=-1;
      arr_yang_lin2[z]=-1;
      arr_yang_lin3[z]=-1;
     }
//--- приравниваем -1 (не отрисовываемое) значение массивам
   for(int z=0; z<bars_copied; z++)
     {
      yin_int_1[z]=-1;
      yin_int_2[z]=-1;
      lin_yin[z]=-1;
      yang_int_1[z]=-1;
      yang_int_2[z]=-1;
      lin_yang[z]=-1;
      time_change[z]=-1;
      time_line[z]=-1;
      time_change_price[z]=-1;
      time_central_price[z]=-1;
     }
//--- основной цикл функции
   for(int z=0; z<bars_copied; z++)
     {
      price_2=array_input[z];
      //--- для начала определим начальное направление движения рынка
      //--- первая линия ТОНКАЯ ВНИЗ
      if(((price_1-price_2)/_Point>func_calc_dorstep(price_2)) && line_move==0)
        {
         yin_int_1[a]=price_1;
         yin_int_2[a]=price_2;

         line_move=-1;
         line_gauge=-1;

         price_1=price_2;

         time_change[a]=(datetime)arr_time[z];
         time_line[a]=0;
        }
      //--- первая линия ТОЛСТАЯ ВВЕРХ
      if(((price_1-price_2)/_Point<-func_calc_dorstep(price_2)) && line_move==0)
        {
         yang_int_1[a]=price_1;
         yang_int_2[a]=price_2;

         line_move=1;
         line_gauge=1;

         price_1=price_2;

         time_change[a]=(datetime)arr_time[z];
         time_line[a]=1;
        }
      //--- цена идет ВНИЗ
      //--- если до этого цена шла ВНИЗ линия ТОНКАЯ
      if(line_move==-1 && line_gauge==-1)
        {
         if(((price_1-price_2)/_Point>func_calc_dorstep(price_2)) || (kagi_type==0 && (price_1-price_2)/_Point>0))
           {
            yin_int_2[a]=price_2;

            line_move=-1;
            line_gauge=-1;

            price_1=price_2;

            time_change[a]=(datetime)arr_time[z];
            time_line[a]=0;
           }
        }
      //--- если до этого цена шла ВНИЗ линия ТОЛСТАЯ
      if(line_move==-1 && line_gauge==1)
        {
         if(((price_1-price_2)/_Point>func_calc_dorstep(price_2)) || (kagi_type==0 && (price_1-price_2)/_Point>0))
           {
            if(price_2<price_down) // Если толстая линия при движении вниз перешла нижнее плечо
              {
               yin_int_1[a]=price_down;
               yin_int_2[a]=price_2;

               yang_int_2[a]=price_down;

               line_move=-1;
               line_gauge=-1;

               price_1=price_2;

               time_change[a]=(datetime)arr_time[z];
               time_central_price[a]=price_down;
               time_line[a]=0;
              }
            else //if(price_2>=price_down) // Если толстая линия при движении вниз не перешла нижнее плечо
              {
               yang_int_2[a]=price_2;

               line_move=-1;
               line_gauge=1;

               price_1=price_2;

               time_change[a]=(datetime)arr_time[z];
               time_line[a]=1;
              }
           }
        }
      //--- если до этого цена шла ВВЕРХ линия ТОНКАЯ
      if(line_move==1 && line_gauge==-1)
        {
         if((price_1-price_2)/_Point>func_calc_dorstep(price_2))
           {
            a++;
            yin_int_1[a]=price_1;
            yin_int_2[a]=price_2;

            lin_yin[a]=price_1;

            line_move=-1;
            line_gauge=-1;

            price_up=price_1;

            price_1=price_2;

            time_change[a]=(datetime)arr_time[z];
            time_line[a]=0;
            time_change_price[a]=lin_yin[a];
           }
        }
      //--- если до этого цена шла ВВЕРХ линия ТОЛСТАЯ
      if(line_move==1 && line_gauge==1)
        {
         if((price_1-price_2)/_Point>func_calc_dorstep(price_2))
           {
            a++;
            if(price_2<price_down) // Если толстая линия при движении вниз перешла нижнее плечо
              {
               yin_int_1[a]=price_down;
               yin_int_2[a]=price_2;

               yang_int_1[a]=price_1;
               yang_int_2[a]=price_down;

               lin_yang[a]=price_1;

               line_move=-1;
               line_gauge=-1;

               price_up=price_1;

               price_1=price_2;

               time_change[a]=(datetime)arr_time[z];
               time_line[a]=0;
               time_change_price[a]=lin_yang[a];
               time_central_price[a]=price_down;
              }
            else//if(price_2>=price_down) // Если толстая линия при движении вниз не перешла нижнее плечо
              {
               yang_int_1[a]=price_1;
               yang_int_2[a]=price_2;

               lin_yang[a]=price_1;

               line_move=-1;
               line_gauge=1;

               price_up=price_1;

               price_1=price_2;

               time_change[a]=(datetime)arr_time[z];
               time_line[a]=1;
               time_change_price[a]=lin_yang[a];
              }
           }
        }
      //--- цена идет ВВЕРХ
      //--- если до этого цена шла ВВЕРХ линия ТОЛСТАЯ
      if(line_move==1 && line_gauge==1)
        {
         if(((price_1-price_2)/_Point<-func_calc_dorstep(price_2)) || (kagi_type==0 && (price_1-price_2)/_Point<0))
           {
            yang_int_2[a]=price_2;

            line_move=1;
            line_gauge=1;

            price_1=price_2;

            time_change[a]=(datetime)arr_time[z];
            time_line[a]=1;
           }
        }

      //--- если до этого цена шла ВВЕРХ линия ТОНКАЯ
      if(line_move==1 && line_gauge==-1)
        {
         if(((price_1-price_2)/_Point<-func_calc_dorstep(price_2)) || (kagi_type==0 && (price_1-price_2)/_Point<0))
           {
            if(price_2>price_up) // Если тонкая линия при движении вверх перешла верхнее плечо
              {
               yin_int_2[a]=price_up;

               yang_int_1[a]=price_up;
               yang_int_2[a]=price_2;

               line_move=1;
               line_gauge=1;

               price_1=price_2;

               time_change[a]=(datetime)arr_time[z];
               time_central_price[a]=price_up;
               time_line[a]=1;
              }
            else//if(price_2<=price_up) // Если тонкая линия при движении вверх не перешла верхнее плечо
              {
               yin_int_2[a]=price_2;

               line_move=1;
               line_gauge=-1;

               price_1=price_2;

               time_change[a]=(datetime)arr_time[z];
               time_line[a]=0;
              }
           }
        }

      //--- если до этого цена шла ВНИЗ линия ТОЛСТАЯ
      if(line_move==-1 && line_gauge==1)
        {
         if((price_1-price_2)/_Point<-func_calc_dorstep(price_2))
           {
            a++;

            yang_int_1[a]=price_1;
            yang_int_2[a]=price_2;

            lin_yang[a]=price_1;

            line_move=1;
            line_gauge=1;

            price_down=price_1;
            price_1=price_2;

            time_change[a]=(datetime)arr_time[z];
            time_line[a]=1;
            time_change_price[a]=lin_yang[a];
           }
        }

      //--- если до этого цена шла ВНИЗ линия ТОНКАЯ
      if(line_move==-1 && line_gauge==-1)
        {
         if((price_1-price_2)/_Point<-func_calc_dorstep(price_2))
           {
            a++;
            if(price_2>price_up) // Если тонкая линия при движении вверх перешла верхнее плечо
              {
               yin_int_1[a]=price_1;
               yin_int_2[a]=price_up;

               yang_int_1[a]=price_up;
               yang_int_2[a]=price_2;

               lin_yin[a]=price_1;

               line_move=1;
               line_gauge=1;

               price_down=price_1;
               price_1=price_2;

               time_change[a]=(datetime)arr_time[z];
               time_line[a]=1;
               time_change_price[a]=lin_yin[a];
               time_central_price[a]=price_up;
              }
            else //if(price_2<=price_up) // Если тонкая линия при движении вверх не перешла верхнее плечо
              {
               yin_int_1[a]=price_1;
               yin_int_2[a]=price_2;

               lin_yin[a]=price_1;

               line_move=1;
               line_gauge=-1;

               price_down=price_1;
               price_1=price_2;

               time_change[a]=(datetime)arr_time[z];
               time_line[a]=0;
               time_change_price[a]=lin_yin[a];
              }
           }
        }

     }
//--- основной цикл функции
//--- присвоение буферам отрисовки действующих значений
   uint y=a;
//--- вспомогательные переменные для содержании информации о заполении текущего буфера
   char yin=1;
   char yang=1;
   for(uint z=0; z<=a; z++)
     {
      arr_yin_1[z]=yin_int_1[y];
      arr_yin_2[z]=yin_int_2[y];

      switch(yin)
        {
         case 1:
           {
            arr_yin_lin1[z]=lin_yin[y];
            arr_yin_lin1[z+1]=lin_yin[y];
            yin++;
           }
         break;
         case 2:
           {
            arr_yin_lin2[z]=lin_yin[y];
            arr_yin_lin2[z+1]=lin_yin[y];
            yin++;
           }
         break;
         case 3:
           {
            arr_yin_lin3[z]=lin_yin[y];
            arr_yin_lin3[z+1]=lin_yin[y];
            yin=1;
           }
         break;
        }

      arr_yang_1[z]=yang_int_1[y];
      arr_yang_2[z]=yang_int_2[y];

      switch(yang)
        {
         case 1:
           {
            arr_yang_lin1[z]=lin_yang[y];
            arr_yang_lin1[z+1]=lin_yang[y];
            yang++;
           }
         break;
         case 2:
           {
            arr_yang_lin2[z]=lin_yang[y];
            arr_yang_lin2[z+1]=lin_yang[y];
            yang++;
           }
         break;
         case 3:
           {
            arr_yang_lin3[z]=lin_yang[y];
            arr_yang_lin3[z+1]=lin_yang[y];
            yang=1;
           }
         break;
        }
      y--;
     }
//---
  }


3.6. Функция создания графического объекта «трендовая линия»

Теперь рассмотрим функцию создания графического объекта «трендовая линия». Эта функция нужна для того, чтобы рисовать график «Каги» на основном графике.

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

//+------------------------------------------------------------------+
//| Func Object 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);
  }

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

Следующая функция, которая многократно обращается к предыдущей, является функция построения графика «Каги» на главном графике. В качестве входных переменных используется глобальные массивы, которые были заполнены в основной функции построения графика «Каги» (рассмотрена ранее): массив цен переворотов («плеч» и «талий»), массив цен переходов или центральных цен (цена, при которой происходит смена линии Yin на Yang или наоборот), массив времени переворотов (он находится в реальном времени и для обозначения начала переворота используется индекс массива [z-1]), массив типа линии, на которой произошел переворот, он также идет на один элемент впереди, как и массив времени.

Тело функции состоит из цикла. Цикл разделен на две части: рисования вертикальных линий и горизонтальных линий. Первый поделен еще на два, прорисовка вертикалей, учитывая смену линии (наличие центральной цены перехода) и учитывая, что линия не меняется. Обратите внимание на передаваемые параметры функции создания объекта «линия тренда».

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

//+------------------------------------------------------------------+
//| Func Kagi Main Chart                                             |
//+------------------------------------------------------------------+
void func_kagi_main_chart(double &price[],         // Массив цен плеч
                          double &central_price[], // Массив цен перехода через плечи
                          datetime &time[],        // Массив времени текущего нахождения ([-1] - начало плеча)
                          char &type_line_end[])   // Тип линии по началу формирования плеча
  {
//--- начало цикла
   for(uint z=1; z<=a; z++)
     {
      //--- проверка условия наличия перехода (перехода нет)
      if(central_price[z]==-1)
        {
         if(type_line_end[z-1]==0 && price[z+1]!=-1)
           {
            func_create_trend_line(IntegerToString(magic_numb)+"_trend_yin_v"+IntegerToString(z),
                                   price[z],price[z+1],time[z],time[z],width_yin_main,color_yin_main);
           }
         if(type_line_end[z-1]==1 && price[z+1]!=-1)
           {
            func_create_trend_line(IntegerToString(magic_numb)+"_trend_yang_v"+IntegerToString(z),
                                   price[z],price[z+1],time[z],time[z],width_yang_main,color_yang_main);
           }
        }
      else //--- проверка условия наличия перехода (переход есть)
        {
         if(type_line_end[z-1]==0 && price[z+1]!=-1)
           {
            func_create_trend_line(IntegerToString(magic_numb)+"_trend_yin_v"+IntegerToString(z),
                                   central_price[z],price[z],time[z],time[z],width_yin_main,color_yin_main);
            func_create_trend_line(IntegerToString(magic_numb)+"_trend_yang_v"+IntegerToString(z),
                                   central_price[z],price[z+1],time[z],time[z],width_yang_main,color_yang_main);
           }
         if(type_line_end[z-1]==1 && price[z+1]!=-1)
           {
            func_create_trend_line(IntegerToString(magic_numb)+"_trend_yin_v"+IntegerToString(z),
                                   central_price[z],price[z+1],time[z],time[z],width_yin_main,color_yin_main);
            func_create_trend_line(IntegerToString(magic_numb)+"_trend_yang_v"+IntegerToString(z),
                                   central_price[z],price[z],time[z],time[z],width_yang_main,color_yang_main);
           }
        }
      //--- проверка условия наличия перехода (переход есть)
      //--- отрисовка горизонталей
      if(type_line_end[z-1]==0)
        {
         func_create_trend_line(IntegerToString(magic_numb)+"_trend_h"+IntegerToString(z),
                                price[z],price[z],time[z-1],time[z],width_yin_main,color_yin_main);
        }
      if(type_line_end[z-1]==1)
        {
         func_create_trend_line(IntegerToString(magic_numb)+"_trend_h"+IntegerToString(z),
                                price[z],price[z],time[z-1],time[z],width_yang_main,color_yang_main);
        }
      //--- отрисовка горизонталей
     }
  }

3.8. Построение дополнительных меток

Ранее было сказано, что индикатор производит построение дополнительных меток. Рассмотрим функцию, которая строит эти метки на главном графике. Тут присутствует всего два вида меток: метка цены разворота и метка времени разворота, выраженных через объект «ценовая метка» и «вертикальная линия». В качестве входных параметров передается параметры: признак отрисовки метки цены разворота и ее цвет, признак отрисовки метки времени разворота и признак изменения ее цвета, первый цвет и второй цвет окраски времени разворота.

Вся функция разделена на две части: первая часть отвечает за временные метки, вторая за ценовые. Обе части функции состоят из циклов, ограниченных количеством разворотов графика (переменная “a”), перед циклом стоит условный оператор if-else, который проверяет необходимость их отрисовки в соответствии с настройками индикатора.

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

Второй цикл отвечает за создание ценовых меток разворота, сначала идет создание названия объекта, далее в зависимости от того, будет строиться график «Каги» на главном графике или нет, устанавливается выбор индекса массива времени. Если этого не сделать, то метки будут находиться "в воздухе" и будет не совсем понятно, с какого места произошел переворот, далее создается объект типа «ценовая метка» и производится его настройка.

//+------------------------------------------------------------------+
//| Func Label Main Chart                                            |
//+------------------------------------------------------------------+
void func_label_main_chart(bool label_print,
                           color label_color,
                           bool time_change_print,
                           bool time_change_color,
                           color time_color_first,
                           color time_color_second)
  {
   if(time_change_print==true)
     {
      for(uint z=1; z<=a; z++)
        {
         string name=IntegerToString(magic_numb)+"_time_2_"+IntegerToString(z);
         //--- создадим объект типа вертикальной линии
         ObjectCreate(0,name,OBJ_VLINE,0,time_change[z],0);
         //--- установим цвет линии
         color color_line=clrBlack;
         if(time_change_color==true)
           {
            if(time_line[z]==0)color_line=time_color_first;
            if(time_line[z]==1)color_line=time_color_second;
           }
         else color_line=time_color_first;
         ObjectSetInteger(0,name,OBJPROP_COLOR,color_line);
         //--- установим стиль отображения линии
         ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_SOLID);
         //--- установим толщину линии
         ObjectSetInteger(0,name,OBJPROP_WIDTH,1);
         //--- отобразим на переднем (false) или заднем (true) плане
         ObjectSetInteger(0,name,OBJPROP_BACK,false);
         //--- включим (true) или отключим (false) режим отображения линии в подокнах графика
         ObjectSetInteger(0,name,OBJPROP_RAY,time_separate_windows);
        }
     }
   if(label_print==true)
     {
      for(uint z=1; z<=a; z++)
        {
         string name=IntegerToString(magic_numb)+"_label_2_"+IntegerToString(z);
         uint numb_time;
         if(kagi_main_chart==true)numb_time=z;
         else numb_time=z-1;
         //--- создадим объект типа метки
         ObjectCreate(0,name,OBJ_ARROW_RIGHT_PRICE,0,time_change[numb_time],time_change_price[z]);
         //--- установим цвет метки
         ObjectSetInteger(0,name,OBJPROP_COLOR,label_color);
         //--- установим стиль окаймляющей линии
         ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_SOLID);
         //--- установим размер метки
         ObjectSetInteger(0,name,OBJPROP_WIDTH,1);
         //--- отобразим на переднем (false) или заднем (true) плане
         ObjectSetInteger(0,name,OBJPROP_BACK,false);
        }
     }
  }

С установкой меток на главном графике разобрались, теперь посмотрим, как можно установить метки в индикаторном окне.

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

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

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

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

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

Смена цветов ценовых уровней согласно настройкам осуществляется посредством условных операторов if-else.

//+------------------------------------------------------------------+
//| Func Label Indicator Window                                      |
//+------------------------------------------------------------------+
void func_label_indicator_window(bool label_print,         // Рисовать ценовые метки
                                 bool levels_print,        // Рисовать уровни
                                 char levels_type_draw,    // Тип рисования уровней по разворотам или на равном расстоянии всего ценового диапазона
                                 char levels_color_change) // Смена цвета линий
  {
   uint number=a;
   if(label_print==true)
     {
      for(uint z=0; z<=label_1_number; z++)
        {
         if(z<number)
           {
            string name=IntegerToString(magic_numb)+"_label_1_"+IntegerToString(z);
            //--- создадим объект типа метки
            ObjectCreate(0,name,OBJ_ARROW_RIGHT_PRICE,ChartWindowFind(),(datetime)Time[(bars_copied_time-z-2)],time_change_price[number-z]);
            //--- установим цвет метки
            ObjectSetInteger(0,name,OBJPROP_COLOR,label_1_color);
            //--- установим стиль окаймляющей линии
            ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_SOLID);
            //--- установим размер метки
            ObjectSetInteger(0,name,OBJPROP_WIDTH,1);
            //--- отобразим на переднем (false) или заднем (true) плане
            ObjectSetInteger(0,name,OBJPROP_BACK,false);
           }
        }
     }
   if(levels_print==true)
     {
      if(levels_type_draw==0)
        {
         for(uint z=0; z<=levels_number; z++)
           {
            if(z<number)
              {
               IndicatorSetDouble(INDICATOR_LEVELVALUE,z,time_change_price[number-z]);
               if(levels_change_color==0)
                 {
                  double numb_even=z;
                  if(MathMod(numb_even,2)==0)
                    {
                     IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,levels_first_color);
                    }
                  if(MathMod(numb_even,2)!=0)
                    {
                     IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,levels_second_color);
                    }
                 }
               if(levels_change_color==1)
                 {
                  if(time_line[number-z]==0)IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,levels_first_color);
                  if(time_line[number-z]==1)IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,levels_second_color);
                 }
               if(levels_change_color==2)
                 {
                  IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,levels_first_color);
                 }
              }
           }
        }
      if(levels_type_draw==1)
        {
         double max_price=Price[ArrayMaximum(Price)];
         double min_price=Price[ArrayMinimum(Price,1,ArrayMinimum(Price)-1)];
         double number_difference=(max_price-min_price)/levels_number;
         NormalizeDouble(number_difference,_Digits);
         for(uint z=0; z<=levels_number; z++)
           {
            IndicatorSetDouble(INDICATOR_LEVELVALUE,z,(min_price+(z*number_difference)));
            IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,levels_first_color);
           }
        }
     }
  }

3.9. Удаление ранее созданных графических объектов

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

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

//+------------------------------------------------------------------+
//| 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);
     }
  }

3.10. Функция запуска построения графика

Теперь рассмотрев все функции по расчетам и построению графика «Каги», созданию и удалению объектов, рассмотрим еще одну небольшую функцию проверки появления нового бара. Функция довольно таки проста и имеет один входящий параметр - это рассматриваемый период. Ответ функции так же банально прост и имеет тип bool, с ответом есть новый бар или его нет. Основа тела функции это оператор-переключатель switch, который передает управление в зависимости от периода разным своим операторам.

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

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

//+------------------------------------------------------------------+
//| Func New Bar                                                     |
//+------------------------------------------------------------------+
bool func_new_bar(ENUM_TIMEFRAMES period_time)
  {
//----
   static datetime old_Times[22];// массив для хранения старых значений
   bool res=false;               // переменная результата анализа  
   int  i=0;                     // номер ячейки массива old_Times[]    
   datetime new_Time[1];         // время нового бара

   switch(period_time)
     {
      case PERIOD_M1:  i= 0; break;
      case PERIOD_M2:  i= 1; break;
      case PERIOD_M3:  i= 2; break;
      case PERIOD_M4:  i= 3; break;
      case PERIOD_M5:  i= 4; break;
      case PERIOD_M6:  i= 5; break;
      case PERIOD_M10: i= 6; break;
      case PERIOD_M12: i= 7; break;
      case PERIOD_M15: i= 8; break;
      case PERIOD_M20: i= 9; break;
      case PERIOD_M30: i=10; break;
      case PERIOD_H1:  i=11; break;
      case PERIOD_H2:  i=12; break;
      case PERIOD_H3:  i=13; break;
      case PERIOD_H4:  i=14; break;
      case PERIOD_H6:  i=15; break;
      case PERIOD_H8:  i=16; break;
      case PERIOD_H12: i=17; break;
      case PERIOD_D1:  i=18; break;
      case PERIOD_W1:  i=19; break;
      case PERIOD_MN1: i=20; break;
      case PERIOD_CURRENT: i=21; break;
     }
   // скопируем время последнего бара в ячейку new_Time[0]  
   int copied=CopyTime(_Symbol,period_time,0,1,new_Time);
  
   if(copied>0) // все ок. данные скопированы
      {
      if(old_Times[i]!=new_Time[0])       // если старое время бара не равно новому
         {
         if(old_Times[i]!=0) res=true;    // если это не первый запуск, то истина = новый бар
         old_Times[i]=new_Time[0];        // запоминаем время бара
         }
      }
//----
   return(res);
  }

 

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

Консолидация всех выше описанных функций осуществляется в одноимённой функции: Func Consolidation. Эта функция запускается каждый раз при появлении нового бара в функции OnCalculate() и при нажатии на клавишу "R" на клавиатуре из функции OnChartEvent(). 

Перед построением или обновлением графика, в функции консолидации (Func Consolidation), происходит вызов функции удаления всех графических объектов. Так как объектов существует немало и они делятся на ценовые метки главного графика и окна индикатора, вертикальные линии, обозначающие время разворота, а также трендовые вертикальные и горизонтальные линии Yin и Yang, то общее количество вызовов функций составляет 7.

Далее производится копирование исторических данных по ценам и времени. Затем следом запускается основная функция построения графика «Каги». Далее производится вызов функции построения всех ценовых меток на главном графике и окне индикатора. В конце происходит построение графика «Каги» на главном графике и запускается функция перерисовки объектов.

//+------------------------------------------------------------------+
//| Func Consolidation                                               |
//+------------------------------------------------------------------+
void func_consolidation()
  {
//--- дата окончания построения
   stop_data=TimeCurrent();

//--- удаление всех графических объектов принадлежащих индикатору
   func_delete_objects(IntegerToString(magic_numb)+"_label_2_",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_label_1_",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_time_2_",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_yin_v",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_yang_v",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_h",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_h",ObjectsTotal(0,-1,-1));

//--- копирование данных о ценах в основной буфер
   copy_history=func_copy_history(Price,start_data,stop_data);

//--- вывод информации об ошибке при копировании данных о ценах
   if(copy_history==false)Alert("Error of copy history Price");

//--- копирование данных о времени в основной буфер
   copy_time=func_copy_time(Time,start_data,stop_data);

//--- вывод информации что при копировании данных о времени возникла ошибка
   if(copy_time==false)Alert("Error of copy history Time");

//--- построение графика Каги в окне индикатора
   func_draw_kagi(Price,YinBuffer1,YinBuffer2,Yin1Buffer,Yin2Buffer,Yin3Buffer,
                  YangBuffer1,YangBuffer2,Yang1Buffer,Yang2Buffer,Yang3Buffer,Time);

//--- рисование меток на основном графике
   func_label_main_chart(label_2,label_2_color,time_line_draw,time_line_change_color,time_first_color,time_second_color);

//--- рисование меток в окне индикатора
   func_label_indicator_window(label_1,levels_on_off,levels_type,levels_change_color);

//--- построение графика Каги в главном окне
   if(kagi_main_chart==true)func_kagi_main_chart(time_change_price,time_central_price,time_change,time_line);

//--- перерисовка графика
   ChartRedraw(0);
//---
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//---
   if(func_new_bar(period_to_redraw)==true)
     {
      func_consolidation();
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| 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_consolidation();
        }
     }
  }

 

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

Удаление всех объектов производится в функции деинициализации индикатора.

//+------------------------------------------------------------------+
//| OnDeinit                                                         |
//+------------------------------------------------------------------+ 
void OnDeinit(const int reason)
  {
//--- удаление всех графических объектов принадлежащих индикатору
   func_delete_objects(IntegerToString(magic_numb)+"_label_2_",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_label_1_",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_time_2_",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_yin_v",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_yang_v",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_h",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_h",ObjectsTotal(0,-1,-1));
//--- перерисовка графика
   ChartRedraw(0);
  }

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


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

Существует много стратегий торговли по графикам «Каги», приступим к рассмотрению некоторых их них.

Начнем с самой популярной стратегии: Продавай при переходе с Yang на Yin и покупай при переходе с Yin на Yang. Постараюсь это показать на рис. 4:

Иллюстрация стратегии "Покупай и продавай при переходах"

Рис. 4. Иллюстрация стратегии "Продавай при переходе с Yang на Yin и покупай при переходе с Yin на Yang" 

Как видно из рис. 4 (EURUSD M30, 5 пунктов), данная стратегия показывает неплохие результаты, что радует. На рисунке показаны 8 точек на 4 сигнала, первый сигнал (1) видно, что нужно покупать на 1.3518, что, в принципе, правильно, так как цена потом достигает значения примерно 1.3560, что составляет 42 пункта за один день, согласитесь неплохо.

Следующая точка (2) продажа на уровне 1.3519 и как видим, цена направляется вниз, и пробивает границу 1.3485, что составляет 34 пункта, примерно за два часа.

Идем дальше к точке (3), здесь как мы видим, покупка осуществляется на 1.3538 и далее цена идет вверх и достигает значения 1.3695, что составляет уже 157 пунктов, за полтора дня. Конечно, это максимальные возможные прибыли, но все же, согласитесь неплохо.

Следующая стратегия торговли - отталкивание от линии тренда, приведенная на рис. 5 (EURUSD M30, 5 пунктов), примерно с 7 по 18 октября:

Иллюстрация стратегии "Отталкивание от линии тренда"

Рис. 5. Иллюстрация стратегии "Отталкивание от линии тренда"

Можно пойти дальше и торговать по каналам, пример нахождения каналов можно видеть на рисунке 6 (EURUSD H1, 5 пунктов), примерно тот же период:

Иллюстрация стратегии "Торговля по каналам"

Рис. 6. Иллюстрация стратегии "Торговля по каналам"

Менее популярная стратегия заключается в том, что после 7 - 10 последовательно возрастающих «плечей» или убывающих «талий», обязательно будет обратное движение (падение или подъем).

Это показано на рисунке 7 (GBPUSD H4, 25 пунктов), период с 10 июля по 18 октября:

Иллюстрация стратегии "7 - 10 последовательно возрастающих «плечей» или убывающих «талий»"

Рис. 7. Иллюстрация стратегии "7 - 10 последовательно возрастающих «плечей» или убывающих «талий»"

Как видно из рисунка, после семи поднимающихся все выше и выше плеч, происходит неплохой спад, равный примерно половине восхождения (около 300 пунктов).

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

Наглядно действия представлены на рис. 8 (GBPUSD H4, 30 пунктов, модифицированное построение):

Иллюстрация стратегии «Торгуй через ценовую метку»

Рис. 8. Иллюстрация стратегии «Торгуй через ценовую метку» 

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

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

К примеру, возьмем график акции #IBM (H4, 1%, классическое построение), приведенный на рис. 9:

Вид подачи сигнала о направленности тренда при помощи временных меток

Рис. 9. Вид подачи сигнала о направленности тренда при помощи временных меток

На графике видно, что синие линии в основном присутствуют на возвышении графика, а красные на спаде.


Заключение

На этом думаю, статья требует своего окончания. Могу сказать точно, что график «Каги» не исчерпал свою силу на рынке, и до сих пор может помогать успешно торговать на рынке как основа стратегии или как вспомогательный инструмент для более точного и уверенного анализа.

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

Буду рад рассмотреть новые идеи и дополнения к индикатору, возможно в будущем они будут реализованы. Также жду комментариев и буду рад ответить на вопросы по индикатору.

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