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

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

MetaTrader 5Примеры | 29 марта 2013, 08:47
17 538 2
Dmitriy Zabudskiy
Dmitriy Zabudskiy

Введение

Существует множество типов графиков, которые представляют информацию о текущей ситуации на рынке. Многие из них пришли к нам из далёкого прошлого, и как раз одним из таких является график "Крестики-нолики" (Point and figure chart).

Этот тип графика был известен еще с конца XIX века. Его первоначальное название "книжный метод" употребил ещё Чарльз Доу в редакционной статье номера "Уолл-Стрит Джорнал" от 20 июля 1901 года. Хоть Доу и утверждал, что "книжный метод" уже существует пятнадцать лет (то есть дата его рождения 1886 год), но именно он зафиксировал его употребление и по сей день.

Несмотря на то, что Доу ограничился описанием этого метода в редакционных статьях, сейчас можно найти множество книг, в которых подробно раскрывается этот метод. Одной из таких книг, которую я бы посоветовал прочитать начинающим трейдерам, является книга Томаса Дж. Дорси "Метод графического анализа крестики-нолики" (оригинал "Point and Figure Charting: The Essential Application for Forecasting and Tracking Market Prices").

 

Описание

График "Крестики-нолики" представляет собой совокупность вертикальных столбцов: крестиков "X" - символизирующих рост цен, и ноликов "O" - символизирующих падение. Особенность этого графика заключается в том, что он строится на основании цен, не учитывая при этом времени. Таким образом, убрав одно значение из данных (время) для построения ценовых графиков, мы получаем графики с каналами ровно в 45 градусов.

В построении графика "Крестики-нолики" участвуют две заранее оговоренные величины:

  • Коробка (box size) - количество пунктов изменения цены, которые регистрируются на графике как крестик или нолик (изначально это значение предполагало стоимость в долларах за акцию, но в дальнейшем переросло в пункты, что мы и будем использовать в нашем индикаторе).
  • Разворот (reversal) - величина противоположного движения цены в единицах величины коробки (box size), при котором происходит смена "Х" на "О" или наоборот (например, разворот равный 3 при величине коробки в 10 пунктов будет соответствовать 30 пунктам).

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

График "Крестики-нолики" обычно строится для удобства на клетчатой бумаге. Для более детального представления разберем небольшой пример построения графика. Пусть имеются данные:

Дата Цена High Цена Low
07.03.2013 12:00 - 07.03.2013 20:00 1.3117 1.2989
07.03.2013 20:00 - 08.03.2013 04:00 1.3118 1.3093
08.03.2013 04:00 - 08.03.2013 12:00 1.3101 1.3080
08.03.2013 12:00 - 08.03.2013 20:00 1.3134 1.2955

С учетом того, что размер коробки берем равным 10 и параметр разворота равным 3, построим график "Крестики-нолики":

  • Сначала цена растет на 128 пунктов, пройдя от цены 1.2989 до цены 1.3117 - рисуем 12 крестиков.
  • Далее цена движется вниз от 1.3118 до 1.3093 на 25 пунктов, чего недостаточно для разворота, так что ничего не рисуем.
  • После мы видим, что цена падает еще до уровня 1.3080, и учитывая верхнее значение 1.3118 мы получаем движение в 38 пунктов - значит можем начать формировать новый столбец двумя ноликами (хоть цена и прошла на три размера коробки, рисуем два нолика, так как следующий столбец рисуется на один размер коробки ниже).
  • Далее цена растет с 1.3080 до 1.3134 на 54 пункта и падает до 1.2955 - это 179 пунктов. То есть мы получаем следующий столбец в четыре крестика вверх и далее еще один с 16 ноликами вниз.

Изобразим это на рисунке:

Рис 1. Слева ценовой график в виде "Японских свечей", справа график "Крестики-нолики".

Рис 1. Слева ценовой график в виде "Японских свечей", справа график "Крестики-нолики".

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

 

Принцип построения

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

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

Сам принцип построения достаточно прост:

  • Берется начальная точка - цена открытия первого минутного бара.
  • Далее, если цена проходит расстояние равное размеру коробки умноженной на показатель разворота или более, то рисуются соответствующие символы ("О" - в случае нисходящего движения и "Х" - в случае восходящего). Запоминаются данные для последующего построения о цене последнего символа.
  • В случае продолжения движения цены в том же направлении на размер коробки, рисуется соответствующий символ.
  • Далее, в случае разворота цены, отсчет производится не от самой большой цены валютной пары, а от цены последнего символа, то есть если цена не прошла больше 50% размера "Коробки", то она просто игнорируется.

Давайте теперь определимся со стилем отрисовки графика "Крестики-нолики". Язык MQL5 поддерживает семь стилей построения индикатора: линия, секция (отрезок), гистограмма, стрелка (символ), закрашиваемая область (канал с заливкой), бары и японские свечи.

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

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

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

Рис 2. Внешний вид построения индикатора на паре EURUSD на дневном таимфрейме.

Рис 2. Внешний вид построения индикатора на паре EURUSD на дневном таимфрейме.

 

Алгоритм индикатора

Для начала нам следует определиться с входными параметрами индикатора. Так как график "Крестики-нолики" не учитывает данные времени, и мы используем для построения данные с минутных баров, то стоит определиться с количеством обрабатываемых данных, для того чтобы не использовать лишние ресурсы системы. Да и строить график "Крестики-нолики" на всей истории незачем.  Таким образом введем первый параметр - "History". В нем будет учитываться количество минутных баров для расчета.

Далее нужно определиться с размером коробки и параметром разворота. Для этого введем переменные "Cell" (клетка) и "CellForChange" (клетки для изменения)  соответственно. Также введем параметр цвета крестиков "ColorUp" и ноликов "ColorDown". И наконец, последний параметр - это цвет границ деления коробок "LineColor".

// +++ Начало программы +++
//+------------------------------------------------------------------+
//|                                                         APFD.mq5 |
//|                                            Aktiniy ICQ:695710750 |
//|                                                    ICQ:695710750 |
//+------------------------------------------------------------------+
#property copyright "Aktiniy ICQ:695710750"
#property link      "ICQ:695710750"
#property version   "1.00"
//--- Рисование индикатора в отдельном окне
#property indicator_separate_window
#property indicator_buffers 5
#property indicator_plots   1
//--- plot Label1
#property indicator_label1  "APFD"
#property indicator_type1   DRAW_COLOR_CANDLES
#property indicator_style1  STYLE_SOLID
#property indicator_color1  clrRed,clrGold
#property indicator_width1  1
//--- Устанавливаем входные параметры
input int   History=10000;
input int   Cell=5;
input int   CellForChange=3;
input color ColorUp=clrRed;
input color ColorDown=clrGold;
input color LineColor=clrAqua;
//--- Объявляем индикаторные буферы
double CandlesBufferOpen[];
double CandlesBufferHigh[];
double CandlesBufferLow[];
double CandlesBufferClose[];
double CandlesBufferColor[];
//--- Массив для копирования расчетных данных с минутных баров
double OpenPrice[];
// Переменные для расчетов
double PriceNow=0;
double PriceBefore=0;
//--- Введем вспомогательные переменные
char   Trend=0;      // Направление тенденции движения цены
double BeginPrice=0; // Начальная цена для отсчёта
char   FirstTrend=0; // Направление начальной тенденции рынка
int    Columns=0;    // Переменная для подсчёта столбцов
double InterimOpenPrice=0;
double InterimClosePrice=0;
double NumberCell=0; // Переменная подсчёта клеток
double Tick=0;       // Размер тика
double OldPrice=0;   // Значение последней расчётной цены
//--- Создаём массивы для временного хранения данных о ценах открытия и закрытия столбиков
double InterimOpen[];
double InterimClose[];
// +++ Начало программы +++

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

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

// +++ Функция OnInit +++
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,CandlesBufferOpen,INDICATOR_DATA);
   SetIndexBuffer(1,CandlesBufferHigh,INDICATOR_DATA);
   SetIndexBuffer(2,CandlesBufferLow,INDICATOR_DATA);
   SetIndexBuffer(3,CandlesBufferClose,INDICATOR_DATA);
   SetIndexBuffer(4,CandlesBufferColor,INDICATOR_COLOR_INDEX);
//--- Устанавливаем значение индикатора без отрисовки
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0);
//--- Вычисляем размер одного тика
   Tick=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE);
//--- Задаём цветовую схему
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,0,ColorUp);
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,1,ColorDown);
//--- Устанавливаем направление индексации в массивах как в таймсериях
   ArraySetAsSeries(CandlesBufferClose,true);
   ArraySetAsSeries(CandlesBufferColor,true);
   ArraySetAsSeries(CandlesBufferHigh,true);
   ArraySetAsSeries(CandlesBufferLow,true);
   ArraySetAsSeries(CandlesBufferOpen,true);
//--- Проверим входной параметр на корректность
   if(CellForChange<2)
      Alert("Параметр CellForChange должен быть больше 1, ввиду особенности построения графика");
//---
   return(0);
  }
// +++ Функция OnInit +++

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

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

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

//+------------------------------------------------------------------+
//| Функция копирования данных для расчёта                           |
//+------------------------------------------------------------------+
int FuncCopy(int HistoryInt)
  {
//--- Изменяем размер массива для копирования расчетных данных
   ArrayResize(OpenPrice,(HistoryInt));
//--- Копируем данные с минутных баров в массив
   int Open=CopyOpen(Symbol(),PERIOD_M1,0,(HistoryInt),OpenPrice);
//---
   return(Open);
  }

2.  Функция расчета числа столбцов

Эта функция вычисляет количество столбцов для построения графика "Крестики-нолики".

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

  •  0 - неопределенный тренд.
  •  1 - восходящий тренд.
  • -1 - нисходящий тренд.

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

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

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

И так далее, пока не будут определены все столбцы. 

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

//+------------------------------------------------------------------+
//| Функция расчёта числа столбцов                                   |
//+------------------------------------------------------------------+
int FuncCalculate(int HistoryInt)
  {
   int ColumnsInt=0;

//--- Обнуляем вспомогательные переменные
   Trend=0;                 // Направление тенденции движения цены
   BeginPrice=OpenPrice[0]; // Начальная цена для отсчёта
   FirstTrend=0;            // Направление начальной тенденции рынка
   Columns=0;               // Переменная для подсчёта столбцов
   InterimOpenPrice=0;
   InterimClosePrice=0;
   NumberCell=0;            // Переменная подсчёта клеток
//--- Цикл для подсчёта количества основных буферов (цены открытия и закрытия столбцов)
   for(int x=0; x<HistoryInt; x++)
     {
      if(Trend==0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
        {
         //--- Тренд вниз
         if(((BeginPrice-OpenPrice[x])/Tick)>0)
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpenPrice=BeginPrice;
            InterimClosePrice=BeginPrice-(NumberCell*Cell*Tick);
            InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits());
            Trend=-1;
           }
         //--- Тренд вверх
         if(((BeginPrice-OpenPrice[x])/Tick)<0)
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpenPrice=BeginPrice;
            InterimClosePrice=BeginPrice+(NumberCell*Cell*Tick);
            InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits());
            Trend=1;
           }
         BeginPrice=InterimClosePrice;
         ColumnsInt++;
         FirstTrend=Trend;
        }
      //--- Определение дальнейших действий при нисходящем тренде
      if(Trend==-1)
        {
         if(((BeginPrice-OpenPrice[x])/Tick)>0 && (Cell)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimClosePrice=BeginPrice-(NumberCell*Cell*Tick);
            InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits());
            Trend=-1;
            BeginPrice=InterimClosePrice;
           }
         if(((BeginPrice-OpenPrice[x])/Tick)<0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            ColumnsInt++;
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpenPrice=BeginPrice+(Cell*Tick);
            InterimClosePrice=BeginPrice+(NumberCell*Cell*Tick);
            InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits());
            Trend=1;
            BeginPrice=InterimClosePrice;
           }
        }
      //--- Определение дальнейших действий при восходящем тренде
      if(Trend==1)
        {
         if(((BeginPrice-OpenPrice[x])/Tick)>0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            ColumnsInt++;
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpenPrice=BeginPrice-(Cell*Tick);
            InterimClosePrice=BeginPrice-(NumberCell*Cell*Tick);
            InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits());
            Trend=-1;
            BeginPrice=InterimClosePrice;
           }
         if(((BeginPrice-OpenPrice[x])/Tick)<0 && (Cell)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimClosePrice=BeginPrice+(NumberCell*Cell*Tick);
            InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits());
            Trend=1;
            BeginPrice=InterimClosePrice;
           }
        }
     }
//---
   return(ColumnsInt);
  }

3.  Функция раскраски столбцов

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

//+------------------------------------------------------------------+
//| Функция раскраски столбцов                                       |
//+------------------------------------------------------------------+
int FuncColor(int ColumnsInt)
  {
   int x;
//--- Заполняем буфер цветов для отрисовки
   for(x=0; x<ColumnsInt; x++)
     {
      if(FirstTrend==-1)
        {
         if(x%2==0) CandlesBufferColor[x]=1; // Все чётные буферы цвета 1
         if(x%2>0) CandlesBufferColor[x]=0;  // Все нечётные буферы цвета 0
        }
      if(FirstTrend==1)
        {
         if(x%2==0) CandlesBufferColor[x]=0; // Все нечётные буферы цвета 0
         if(x%2>0) CandlesBufferColor[x]=1;  // Все чётные буферы цвета 1
        }
     }
//---
   return(x);
  }

4.  Функция определения размеров столбцов

После того как мы определили количество используемых столбцов и установили цвета раскраски, нужно позаботиться об их вертикальном размере. Для этого создадим временные массивы "InterimOpen[]" и "InterimClose[]", в которых мы будем хранить цены открытия и закрытия для каждого столбика. Размерность этих массивов установим равной количеству столбцов.

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

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

//+------------------------------------------------------------------+
//| Функция определения размеров столбцов                            |
//+------------------------------------------------------------------+
int FuncDraw(int HistoryInt)
  {
//--- Определяем размеры временных массивов
   ArrayResize(InterimOpen,Columns);
   ArrayResize(InterimClose,Columns);
//--- Обнуляем вспомогательные переменные
   Trend=0;                 // Направление тенденции движения цены
   BeginPrice=OpenPrice[0]; // Начальная цена для отсчёта
   NumberCell=0;            // Переменная подсчёта клеток
   int z=0;                 // Переменная для индексов временных массивов
//--- Цикл для заполнения основных буферов (цены открытия и закрытия столбцов)
   for(int x=0; x<HistoryInt; x++)
     {
      if(Trend==0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
        {
         //--- Тренд вниз
         if(((BeginPrice-OpenPrice[x])/Tick)>0)
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpen[z]=BeginPrice;
            InterimClose[z]=BeginPrice-(NumberCell*Cell*Tick);
            InterimClose[z]=NormalizeDouble(InterimClose[z],Digits());
            Trend=-1;
           }
         //--- Тренд вверх
         if(((BeginPrice-OpenPrice[x])/Tick)<0)
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpen[z]=BeginPrice;
            InterimClose[z]=BeginPrice+(NumberCell*Cell*Tick);
            InterimClose[z]=NormalizeDouble(InterimClose[z],Digits()); // Нормализуем количество знаков после запятой
            Trend=1;
           }
         BeginPrice=InterimClose[z];
        }
      //--- Определение дальнейших действий при нисходящем тренде
      if(Trend==-1)
        {
         if(((BeginPrice-OpenPrice[x])/Tick)>0 && (Cell)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimClose[z]=BeginPrice-(NumberCell*Cell*Tick);
            InterimClose[z]=NormalizeDouble(InterimClose[z],Digits());
            Trend=-1;
            BeginPrice=InterimClose[z];
           }
         if(((BeginPrice-OpenPrice[x])/Tick)<0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            z++;
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpen[z]=BeginPrice+(Cell*Tick);
            InterimClose[z]=BeginPrice+(NumberCell*Cell*Tick);
            InterimClose[z]=NormalizeDouble(InterimClose[z],Digits());
            Trend=1;
            BeginPrice=InterimClose[z];
           }
        }
      //--- Определение дальнейших действий при восходящем тренде
      if(Trend==1)
        {
         if(((BeginPrice-OpenPrice[x])/Tick)>0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            z++;
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpen[z]=BeginPrice-(Cell*Tick);
            InterimClose[z]=BeginPrice-(NumberCell*Cell*Tick);
            InterimClose[z]=NormalizeDouble(InterimClose[z],Digits());
            Trend=-1;
            BeginPrice=InterimClose[z];
           }
         if(((BeginPrice-OpenPrice[x])/Tick)<0 && (Cell)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimClose[z]=BeginPrice+(NumberCell*Cell*Tick);
            InterimClose[z]=NormalizeDouble(InterimClose[z],Digits());
            Trend=1;
            BeginPrice=InterimClose[z];
           }
        }
     }
//---
   return(z);
  }

5.  Функция разворота массивов

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

//+------------------------------------------------------------------+
//| Функция разворота массивов                                       |
//+------------------------------------------------------------------+
int FuncTurnArray(int ColumnsInt)
  {
//--- Переменная для разворота массивов
   int d=ColumnsInt;
   for(int x=0; x<ColumnsInt; x++)
     {
      d--;
      CandlesBufferOpen[x]=InterimOpen[d];
      CandlesBufferClose[x]=InterimClose[d];
      if(CandlesBufferClose[x]>CandlesBufferOpen[x])
        {
         CandlesBufferHigh[x]=CandlesBufferClose[x];
         CandlesBufferLow[x]=CandlesBufferOpen[x];
        }
      if(CandlesBufferOpen[x]>CandlesBufferClose[x])
        {
         CandlesBufferHigh[x]=CandlesBufferOpen[x];
         CandlesBufferLow[x]=CandlesBufferClose[x];
        }
     }
//---
   return(d);
  }

6.  Функция для рисования горизонтальных линий

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

//+------------------------------------------------------------------+
//| Функция для рисования горизонтальных линий                       |
//+------------------------------------------------------------------+
int FuncDrawHorizontal(bool Draw)
  {
   int Horizontal=0;
   if(Draw==true)
     {
      //--- Создаём горизонтали (строчки для разделения столбиков)
      ObjectsDeleteAll(0,ChartWindowFind(),OBJ_HLINE); // Удаляем все старые горизонтали
      int MaxPriceElement=ArrayMaximum(OpenPrice);     // Определяем максимальный уровень цены
      int MinPriceElement=ArrayMinimum(OpenPrice);     // Определяем минимальный уровень цены
      for(double x=OpenPrice[0]; x<=OpenPrice[MaxPriceElement]+(Cell*Tick); x=x+(Cell*Tick))
        {
         ObjectCreate(0,DoubleToString(x,Digits()),OBJ_HLINE,ChartWindowFind(),0,NormalizeDouble(x,Digits()));
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_COLOR,LineColor);
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_STYLE,STYLE_DOT);
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_SELECTED,false);
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_WIDTH,1);
         Horizontal++;
        }
      for(double x=OpenPrice[0]-(Cell*Tick); x>=OpenPrice[MinPriceElement]; x=x-(Cell*Tick))
        {
         ObjectCreate(0,DoubleToString(x,Digits()),OBJ_HLINE,ChartWindowFind(),0,NormalizeDouble(x,Digits()));
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_COLOR,LineColor);
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_STYLE,STYLE_DOT);
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_SELECTED,false);
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_WIDTH,1);
         Horizontal++;
        }
      ChartRedraw();
     }
//---
   return(Horizontal);
  }

Теперь, когда все базовые функции описаны, рассмотрим порядок их вызова в OnCalculate():

  • Запускаем функцию копирования данных для вычислений (при условии, что нет еще рассчитанных баров).
  • Вызываем функцию расчета числа столбцов.
  • Определяем цвета столбцов.
  • Определяем размеры столбцов.
  • Вызываем функцию для разворота данных в массивах.
  • Вызываем функцию для построения горизонтальных линий, которые будут разграничивать столбцы на "Коробки".
// +++ Основные расчеты и построение +++
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- Переворачиваем массив для удобства получения значения последней цены
   ArraySetAsSeries(close,true);
//---
   if(prev_calculated==0)
     {
      //--- Запускаем функцию копирования данных для расчёта
      int ErrorCopy=FuncCopy(History);
      //--- В случае ошибки выводим сообщение
      if(ErrorCopy==-1)
        {
         Alert("Не удалось провести копирование. Данные еще подгружаются.");
         return(0);
        }
      //--- Вызываем функцию расчета числа столбцов
      Columns=FuncCalculate(History);
      //--- Вызываем функцию раскраски столбцов
      int ColorCalculate=FuncColor(Columns);
      //--- Вызываем функцию определения размеров столбцов
      int z=FuncDraw(History);
      //--- Запускаем функцию разворота массивов
      int Turn=FuncTurnArray(Columns);
      //--- Запускаем функцию рисования горизонталей
      int Horizontal=FuncDrawHorizontal(true);
      //--- Запоминаем в переменную значений последней цены закрытия
      OldPrice=close[0];
     }
//--- Если цена изменилась по отношению к предыдущей на размер коробки, 
//--- то происходит перерасчёт индикатора
   if(fabs((OldPrice-close[0])/Tick)>Cell)
      return(0);
//--- return value of prev_calculated for next call
   return(rates_total);
  }
// +++ Основные расчеты и построение +++

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

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

// +++ Дополнительные действия для клавиш "С" - очистка и "R" - перерисовка +++
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if(id==CHARTEVENT_KEYDOWN)
     {
      //--- 67 - Код клавиши "C", очищает индикаторный буфер
      if(lparam==67)
        {
         for(int x=0; x<Bars(Symbol(),PERIOD_CURRENT); x++)
            CandlesBufferOpen[x]=0;
         ChartRedraw();
        }
      // 82 - Код клавиши "R", перерисовывает индикатор
      if(lparam==82)
        {
         //--- Запускаем функцию копирования
         int ErrorCopy=FuncCopy(History);
         //--- В случае ошибки выводим сообщение
         if(ErrorCopy==-1)
            Alert("Не удалось провести копирование данных.");
         //--- Вызываем функцию расчета числа столбцов
         Columns=FuncCalculate(History);
         //--- Вызываем функцию раскраски столбцов
         int ColorCalculate=FuncColor(Columns);
         //--- Вызываем функцию определения размеров столбцов
         int z=FuncDraw(History);
         //--- Запускаем функцию разворота массивов
         int Turn=FuncTurnArray(Columns);
         //--- Запускаем функцию рисования горизонталей
         int Horizontal=FuncDrawHorizontal(true);
        }
     }
  }
//+------------------------------------------------------------------+
// +++ Дополнительные действия для клавиш "С" - очистка и "R" - перерисовка +++

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

 

Стандартные сигналы

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

Теперь поговорим подробнее о самих паттернах:

  1. Паттерны "Двойная вершина" и "Двойное дно".

    "Двойная вершина" образуется, когда цена идет вверх, затем разворачивается, образуя некоторый столбец из ноликов, а затем снова идет вверх, при этом переходя предыдущий столбец крестиков на одну "Коробку". Формирование такого паттерна является сигналом для совершения покупки.

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

    Рис 3. Паттерны "Двойная вершина" и "Двойное дно".

    Рис 3. Паттерны "Двойная вершина" и "Двойное дно".

  2. Паттерны "Тройная вершина" и "Тройное основание".

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

    "Тройная вершина" образуется, когда цена достигает дважды одного ценового диапазона, а на третий раз прорывает его вверх, что дает сигнал для совершения покупки.

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

    Рис 4. Паттерны "Тройная вершина" и "Тройное основание".

    Рис 4. Паттерны "Тройная вершина" и "Тройное основание".

  3. Паттерны "Прорыв симметричного треугольника" вверх и вниз.

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

    Рис 5. Паттерны "Прорыв симметричного треугольника" вверх и вниз.

    Рис 5. Паттерны "Прорыв симметричного треугольника" вверх и вниз.

  4. Паттерны "Бычья катапульта" и "Медвежья катапульта".

    "Катапульты" также напоминают модели технического анализа "Восходящий треугольник" и "Нисходящий треугольник". Суть их сигналов схожа - как только цена прорывает треугольник в параллельной стороне, то дается сигнал к покупке или продаже. В случае "Бычьей катапульты" прорыв происходит вверх и это сигнал к покупке (рисунок слева), а в случае "Медвежьей катапульты" прорыв проходит вниз, и это сигнал к продаже (рисунок справа).

    Рис 6. Паттерны "Бычья катапульта" и "Медвежья катапульта".

    Рис 6. Паттерны "Бычья катапульта" и "Медвежья катапульта".

  5. Паттерн "45-градусная трендовая линия".

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

    Рис 7. Паттерн "45-градусная трендовая линия".

    Рис 7. Паттерн "45-градусная трендовая линия".

Мы рассмотрели стандартные модели и сигналы графика "Крестики-нолики". Теперь найдем некоторые из них на графике индикатора, который был размещен в начале статьи:

Рис 8. Определение паттернов на графике "Крестики-нолики".

Рис 8. Определение паттернов на графике "Крестики-нолики".

 

Заключение

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

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

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

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

Прикрепленные файлы |
apfd.mq5 (18.89 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
---
--- | 4 апр. 2013 в 09:48

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

Dmitriy Zabudskiy
Dmitriy Zabudskiy | 7 апр. 2013 в 09:26
sergeev:

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

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

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

Итоги MQL5 Маркета за 1 квартал 2013 года Итоги MQL5 Маркета за 1 квартал 2013 года
С момента своего основания MQL5 Маркет - магазин торговых роботов и технических индикаторов - уже привлек в свои ряды более 250 разработчиков, которые опубликовали 580 продуктов. Итоги первого квартала 2013 года показывают, что некоторые продавцы довольно успешны в MQL5 Маркете и уже получили с продаж солидную прибыль.
Jeremy Scott - успешный продавец MQL5 Маркета Jeremy Scott - успешный продавец MQL5 Маркета
Джереми Скотт (Jeremy Scott), более известный в MQL5.community под ником Johnnypasado, снискал славу на ниве нашего сервиса MQL5 Маркет. Он уже заработал несколько тысяч долларов в Маркете и это далеко не предел. Мы решили внимательнее присмотреться к будущему миллионеру и получить от него инструкцию успеха для продавцов в MQL5 Маркете.
Рецепты MQL5 - Как не получить ошибку при установке/изменении торговых уровней? Рецепты MQL5 - Как не получить ошибку при установке/изменении торговых уровней?
Продолжая работу над экспертом из предыдущей статьи "Рецепты MQL5 - Изучение свойств позиции в тестере MetaTrader 5", внедрим в него еще целый ряд полезных функций, а также усовершенствуем и оптимизируем уже имеющиеся. На этот раз эксперт будет снабжен внешними параметрами, которые можно будет оптимизировать в тестере MetaTrader 5. Это уже будет немного похоже на простую торговую систему.
Рецепты MQL5 - Изучение свойств позиции в тестере MetaTrader 5 Рецепты MQL5 - Изучение свойств позиции в тестере MetaTrader 5
Модифицированная версия эксперта из предыдущей статьи "Рецепты MQL5 - Свойства позиции на пользовательской информационной панели". Рассмотрим ряд вопросов: получение данных баров, отслеживание события "новый бар" на текущем символе, подключение торгового класса стандартной библиотеки, создание функции поиска торговых сигналов, создание функции для выполнения торговых операций, а также определение торгового события в функции OnTrade().