Скачать MetaTrader 5

Инструмент "Ценовая гистограмма" (Рыночный профиль) и его реализация на MQL5

26 января 2010, 14:11
Dmitry Voronkov
39
12 066

Рыночный профиль (Market Profile) пытается привнести внутреннюю
логику в контекст рынка. Это – метод анализа, который начинается
с понимания факта, что сама по себе цена еще не несет информации,
подобно словам, не имеющим смысла вне контекста или синтаксиса.
А вот объем рынка – интегральная часть его прямого выражения.
Понимание объема даст вам понимание языка рынка.
Робин Месх.

Введение

Как-то сидя в офисе одного из дилинговых центров, и перелистывая подписку журнала "ВАЛЮТНЫЙ СПЕКУЛЯНТ" (был такой журнал, сейчас он называется "Active Trader"), я наткнулся на статью   Робина Месха "Рыночный профиль и понимание языка" (октябрь 2002). (Полная версия опубликована в сборнике статей "Новое мышление в техническом анализе" под ред. Рика Бенсигнора)

Рыночный профиль был разработан Питером Стидлмайером (Peter Steidlmayer), замечательным и остроумным мыслителем. Он обнаружил естественное проявление рынка (объем) и представил его таким способом (колоколообразной кривой), чтобы можно было читать объективную информацию, генерируемую рынком. Стидлмайер предложил использовать альтернативное представление информации как о горизонтальном, так и о вертикальном движении рынка, что дает полностью отличный набор моделей. Он предположил, что у рынка существует основной рыночный пульс, или фундаментальная модель, которая называется цикл равновесия и неравновесия (cycle of equilibrium and disequilibrium).

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

  1. Рынок – это аукцион, и он двигается в направлении ценового диапазона, в котором спрос и предложение будут более или менее равны.
  2. Рынок имеет две фазы: вертикальная активность и горизонтальная активность. Рынок двигается вертикально, когда спрос и предложение не равны или неравновесны, а горизонтально – когда они равновесны или сбалансированы.

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

 

Рис 1. Рыночный профиль равновесного рынка

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

Рис 2. Рыночный профиль неравновесного рынка

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

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

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

Пример методики работы с данным инструментом Вы сможете посмотреть на англоязычном сайте http://www.enthios.com/, где с 1998 группа трейдеров занимается изучением работы с «Ценовой гистограммой», там же Вы найдете описание стратегии Enthios Universal и примеры ее использования. 

1. Ценовая гистограмма

Ценовая гистограмма (Price Histogram) - очень надежный инструмент. Он немного интуитивен, и в тоже время чрезвычайно эффективен. Ценовая гистограмма просто показывает Вам, где рынок будет «самым удобным» для  торговли. Данный индикатор является опережающим, так как показывает  заранее, где рынок может развернуться. Такие индикаторы как скользящие средние или осцилляторы, не могут с точностью указать место, где рынок встретит сопротивление или поддержку, они могут только показать, что рынок перекуплен или перепродан.

Традиционно Ценовая Гистограмма или как ее иногда называют Market Profile (Срез рынка), применяется для 30 мин. графика, чтобы исследовать рыночную активность в течение одного дня. Я предпочитаю использовать 5 мин. график для фондовых рынков и 15-30 мин. для форекс.

2. Точка Контроля

На рисунке выше видно уровень, на котором рынок торговался максимальное количество времени, он обозначен самой длинной линией в гистограмме. Ее называют Точкой Контроля, сокращенно  POC  (Point of Control). Иногда, как видно на рисунке, у гистограммы есть две вершины, одна чуть меньше другой в этом случае, хотя индикатор показывает один POC, на самом деле их два и это надо учитывать.

Кроме этого, процентный уровень диапазона в гистограмме тоже создает дополнительные уровни, так называемые вторичные уровни Secondary POC:

Рис 3. Точки контроля

Что нам показывает POC? Цену, которую помнит большинство трейдеров. Чем дольше рынок торгуется на данной цене, тем дольше рынок ее помнит.

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

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

 

Рис 4. Точка Контроля (POC) не является абсолютной, она указывает диапазон торговли

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

Рассмотрим рис. 4. Точка Контроля на 29/12/2009 находилась на уровне 68.87. Даже без гистограммы и линии POC видно, что в этот день рынок большую часть дня провел в диапазоне 68.82~68.96. В конце дня рынок закрылся на 5 пунктов ниже POC. На следующий день это привело к открытию с гепом вниз.

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

После открытия рынка 30/12/2009 мы видим, что после открытия с гепом рынок коснулся POC предыдущего дня, а затем быстро вернулся к открытию и после обновил минимум.

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

Обратите внимание, что точно также рынок повел себя 31/12/2009. Когда цена коснулась POC, покупатели уступили продавцам. 

3. «Девственная» Точка Контроля

Virgin POC («Девственная» Точкой Контроля) – это уровень, который цена не достигла в последующие дни.

Логика проста, как описано выше, POC является для рынка точкой притяжения. Поскольку цена удаляется от POC, сила притяжения растет. И чем дальше цена уходит от Virgin POC, тем больше вероятность того, что при возврате к данному уровню возникнет отскок, а возможно, и разворот цены.

Рис 5. Бывшие и текущие Virgin POC

На рис 5. кружочками отмечены бывшие Virgin POC, которые служили линиями поддержки или сопротивления, ценовыми значениями отмечены «рабочие» Virgin POC.

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

Подробнее о работе с ценовыми уровнями Вы можете прочитать в книге Эрика Наймана «Мастер-трейдинг: Секретные материалы», глава 4 «Уровень – линия опоры».

4. Реализация «Ценовой гистограммы» на языке MQL5

Первая версия «Ценовой гистограммы» в моем исполнении появилась в 2006 году и была написана на MQL4 в МetaТrader4 для личного использования. При создании данного индикатора мне пришлось столкнуться с несколькими проблемами, вот некоторые из них:

  1. количество хранимых баров периода М5 (очень малый объем дневной истории), про период М1 я просто промолчу;
  2. необходимость написания функций манипулирования историей, таких как возврат на день назад с учетом выходных, проверки времени закрытия рынка в пятницу, проверка времени открытия и закрытия рынка CFD и т.д.;
  3. пересчет индикатора при переходе с одного временного периода на другой, и соответственно, «торможение» терминала.

Поэтому когда началось тестирование МetaТrader5 и языка MQL5, я сразу решил перевести свой индикатор на новые рельсы.

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

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

Теперь объясню, почему получился «ком». Я не учел специфику работы индикаторов в MQL5:

  1. «критичность» времени исполнения;
  2. особенности работы индикатора при переключении временного диапазона.

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

Но, как говорится, если не знаешь, что делать - «ЧИТАЙ ДОКУМЕНТАЦИЮ», в нашем случае справку по MQL5. Решение оказалось очень простым - реализовать данный индикатор в форме не торгующего эксперта.

В чем преимущество эксперта:

  1. не критичность времени обработки события Init в обработчике OnTick();
  2. возможность получения параметров обработчика OnDeinit (const int reason).

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

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

Объектно-ориентированное программирование (ООП) — стиль написания программ, в котором основными концепциями являются понятия объектов и классов.

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

Класс – частный случай абстрактного типа данных в объектно-ориентированном программировании (ООП), характеризуемый способом своего построения. Класс является ключевым понятием в ООП. Суть отличия классов от других абстрактных типов данных состоит в том, что при задании типа данных класс определяет одновременно и интерфейс (методы класса).

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

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

Итак, приступим…

Текст советника находится в четырех файлах. Основной файл PriceHistogram.mq5, и три включаемых (#include) файла ClassExpert.mqh, ClassPriceHistogram.mqh и ClassProgressBar.mqh. Во включаемых файлах, с расширением .mqh, находятся описания классов, и методов работы с классами. Все файлы должны находятся в одном каталоге, в моем случае это каталог ..\MQL5\Expert\PriceHistogram\.

4.1. PriceHistogram.mq5

Первая функциональная команда, которую мы встречаем в тексте программы:

#include "ClassExpert.mqh"

Директива #include включает в программный текст содержимое указанного файла. В нашем случае описание класса CExpert (рассмотрим ниже).

Вслед за директивой #include, следует блок входных параметров, input переменных.

// Блок входных парамертов 
input int         DayTheHistogram   = 10;          // Дней с гистограммой 
input int         DaysForCalculation= 500;         // Дней для расчета(-1 вся) 
input uint        RangePercent      = 70;          // Процент диапазона 
input color       InnerRange        =Indigo;       // Внутренний диапазон 
input color       OuterRange        =Magenta;      // Внешний диапазон 
input color       ControlPoint      =Orange;       // Контрольная точка(POC) 
input bool        ShowValue         =true;         // Показать значения

Затем объявляется переменная класса CExpert ExtExpert.

Далее следуют стандартные события, присущие программам, написанным на языке MQL5, в которых я передаю управление соответствующим методам класса CExpert.

Единственный метод, в котором производятся некоторые манипуляции, перед тем как передать управление методу класса CExpert, это метод OnInit().

int OnInit()
  {
//---
// Проверяем синхронизацию инструмента перед началом расчетов 
   int err=0;
   while(!(bool)SeriesInfoInteger(Symbol(),0,SERIES_SYNCRONIZED) && err<AMOUNT_OF_ATTEMPTS)
     {
      Sleep(500);
      err++;
     }
// Инициализация класса CExpert 
   ExtExpert.RangePercent=RangePercent;
   ExtExpert.InnerRange=InnerRange;
   ExtExpert.OuterRange=OuterRange;
   ExtExpert.ControlPoint=ControlPoint;
   ExtExpert.ShowValue=ShowValue;
   ExtExpert.DaysForCalculation=DaysForCalculation;
   ExtExpert.DayTheHistogram=DayTheHistogram;
   ExtExpert.Init();
   return(0);
  }

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

Хорошо, что разработчики позаботились о нас, программистах, в MetaEditor5 есть отладчик. Я сейчас с ужасом вспоминаю количество команд Print() и Comment(), используемых для проверки значений переменных и отладки программ, написанных в среде MetaEditor4. Разработчикам MetaEditor5 огромное спасибо.

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

После того, как данные были синхронизированы, или цикл завершился по счетчику в случае отсутствия связи, мы передаем входные параметры эксперта нашему классу CExpert, и вызываем метод инициализации класса Init().

Как вы видите, благодаря появлению в MQL5 понятия классов, наш файл PriceHistogram.mq5 превратился в простой шаблон, а вся дальнейшая обработка передана классу CExpert который находится в файле ClassExpert.mqh.

4.2. ClassExpert.mqh

Описание класса.

//+------------------------------------------------------------------+
//|   Класс CExpert                                                  |
//|   Описание класса                                                |
//+------------------------------------------------------------------+
class CExpert
  {
public:
   int               DaysForCalculation; // Дней для расчета(-1 вся)
   int               DayTheHistogram;    // Дней с гистограммой 
   int               RangePercent;       // Процент диапазона 
   color             InnerRange;         // Цвет внутренний диапазон 
   color             OuterRange;         // Цвет внешний диапазон 
   color             ControlPoint;       // Цвет Контрольная точка(POC) 
   bool              ShowValue;          // Показать значения 

Раздел public – открытых, доступных извне переменных.  Вы сразу заметите, что имена переменных полностью совпадают с именами в разделе входных параметров программы, описанных в PriceHistogram.mq5. Можно этого не делать, переменные входных параметров являются глобальными. Но в данном случае – это дань правилам хорошего тона, использование внешних переменных внутри класса желательно избегать.

private:
   CList             list_object;        // Динамический список экземпляров класса CObject
   string            name_symbol;        // Имя символа
   int               count_bars;         // Количество дневных баров 
   bool              event_on;           // Флаг обработки событий

Раздел private – закрытых извне, доступных только внутри класса переменных. Хотелось бы обратить ваше внимание на переменную CList list_object, которая является переменной класса, входящего в состав стандартной библиотеки языка MQL5. Класс CList является классом динамического списка экземпляров класса CObject и его наследников. Я буду использовать данный список для хранения указателей на элементы своего класса CPriceHistogram, который я сделал наследником класса CObject, подробнее мы рассмотрим его ниже. Описание класса CList находится в файле List.mqh, и подключается с помощью директивы #include <Arrays\List.mqh>.

public:
   // Конструктор класса 
                     CExpert();
   // Деструктор класса 
                    ~CExpert(){Deinit(REASON_CHARTCLOSE);}
   // Метод инициализации 
   bool              Init();
   // Метод деинициализации 
   void              Deinit(const int reason);
   // Метод обработки OnTick 
   void              OnTick();
   // Метод обработки события OnChartEvent() 
   void              OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   // Метод обработки события OnTimer() 
   void              OnTimer();
};

Далее следует раздел public методов. Как вы уже догадались – это методы (функции), доступные извне класса.

И завершает описание класса фигурная скобка и точка с запятой.

Рассмотрим методы класса подробнее.

Конструктор класса (от англ. Constructor) — специальный блок инструкций, вызываемый при создании объекта. Конструктор схож с методом, но отличается от метода тем, что не имеет явным образом определённого типа возвращаемых данных. В языке MQL5, конструкторы не могут иметь входных параметров, и каждый класс может иметь только один конструктор. В нашем случае, в конструкторе происходит первичная инициализация переменных.

Деструктор — специальный метод класса, служащий для деинициализации объекта (например, освобождения памяти). В нашем случае вызывается метод Deinit(REASON_CHARTCLOSE);

Init() – метод инициализации класса. Это самый важный метод класса CExpert, в нем происходит создание объектов гистограммы. Читайте комментарии, я постарался снабдить ими все свои действия. Но хотел бы обратить более пристальное внимание на три момента.

Во-первых, для построения дневной «Ценовой гистограммы» нам необходимо получить массив времени открытия обрабатываемого количества дней. Здесь хотелось бы сделать небольшое отступление и обратить ваше внимание на особенности работы с таймсериями. Для получения данных с других временных диапазонов необходимо время (данные хранятся в минутках, их нужно подготовить), поэтому функции Bars() и CopyTime(), как и другие функции работы с таймсериями, не всегда возвращают нужные данные с первого вызова. Поэтому мне пришлось поместить данную функцию в теле цикла do {…} while(), а чтобы цикл не стал бесконечным, ввести переменную счетчик.

 int err=0;
 do
   {
    // Расчет количества дней исходя из доступной истории 
    count_bars=Bars(NULL,PERIOD_D1);
    if(DaysForCalculation+1<count_bars)
       count=DaysForCalculation+1;
    else
       count=count_bars;
    if(DaysForCalculation<=0) count=count_bars;
    rates_total=CopyTime(NULL,PERIOD_D1,0,count,day_time_open);
    Sleep(1);
    err++;
   }
 while(rates_total<=0 && err<AMOUNT_OF_ATTEMPTS);
 if(err>=AMOUNT_OF_ATTEMPTS)
   {
   Print("There is no accessible history PERIOD_D1");
   name_symbol=NULL;
   return(false);
   }

Во-вторых, так как минутная история в MetaTrader 5 равна количеству всех доступных дней, время, затраченное на ее обработку, может быть довольно длительным, поэтому возникла необходимость как-то визуализировать процесс расчетов. С этой целью был создан класс CProgressBar (#include "ClassProgressBar.mqh"). Данный класс создает в окне графика прогресс-бар, и обновляет его в процессе обработки истории.

 // Создаем объект для вывода на график процесса загрузки
 CProgressBar   *progress=new CProgressBar;
 progress.Create(0,"Loading",0,150,20);
 progress.Text("Calculation:");
 progress.Maximum=rates_total; 

В-третьих, в цикле при помощи команды new, создаем объект класса CPriceHistogram, затем с помощью методов данного класса устанавливаем входные параметры и через метод Init() инициализируем его. В случае удачной инициализации помещаем его в динамический список list_object, если инициализация не удалась, обязательно удаляем объект hist_obj командой delete. Описание класса CPriceHistogram я дам позже, а пока смотрите комментарии в тексте программы.

 // В данном цикле происходит создание объекта CPriceHistogram  
 // его инициализация и внесение в список объектов
 for(int i=0;i<rates_total;i++)
   {
    CPriceHistogram  *hist_obj=new CPriceHistogram();
    //         hist_obj.StepHistigram(step);
    // Устанавливаем флаг отображения текстовых меток 
    hist_obj.ShowLevel(ShowValue);
    // Устанавливаем цвет POCs 
    hist_obj.ColorPOCs(ControlPoint);
    // Устанавливаем цвет внутри диапазона 
    hist_obj.ColorInner(InnerRange);
    // Устанавливаем цвет за диапазоном 
    hist_obj.ColorOuter(OuterRange);
    // Устанавливаем процент диапазона 
    hist_obj.RangePercent(RangePercent);
    //  hist_obj.ShowSecondaryPOCs((i>=rates_total-DayTheHistogram),PeriodSeconds(PERIOD_D1));
    if(hist_obj.Init(day_time_open[i],day_time_open[i]+PeriodSeconds(PERIOD_D1),(i>=rates_total-DayTheHistogram)))
       list_object.Add(hist_obj);
    else
       delete hist_obj; // Удаляем если возникла ошибка 
    progress.Value(i);
   }; 

OnTick() – метод, вызываемый при поступлении нового тика по символу. Мы сравниваем значения количества дней, хранимых в переменной count_bars с количеством дневных баров, возвращаемых функцией Bars(Symbol(),PERIOD_D1) и если они не равны, принудительно вызываем метод Init() инициализации класса, очистив список list_object и изменив значение переменной name_symbol на NULL. В случае, если количество дней не изменилось, в цикле проходим по всем объектам класса CPriceHistogram сохраненным в list_object, и выполняем метод Redraw(), для тех из них, которые являются Virgin («девственными»).

Deinit() – метод деинициализации класса. В случае получения параметра REASON_PARAMETERS (входные параметры были изменены пользователем) производим очистку списка list_object и устанавливаем значение переменной name_symbol в NULL. В остальных случаях эксперт ничего не делает, но если вам захочется что-то добавить, читайте комментарии.

OnEvent() – метод обработки событий клиентского терминала. События генерируются клиентским терминалом при работе пользователя с графиком. Более подробную информацию вы найдете в документации по языку MQL5. В данном эксперте я для примера использую событие CHARTEVENT_OBJECT_CLICK. При нажатии мышкой на элемент гистограммы, на график выводятся вторичные уровни POCs, и производится инверсия цвета гистограммы.

OnTimer(void) – метод обработки события таймера. В моей программе не используется, если захотите довесить часики, показывать время, оставшееся до закрытия свечи или выполнять другие действия по таймеру - это здесь!!! Перед использованием необходимо в конструктор класса добавить строку:

EventSetTimer(время в секундах);

А в деструктор строку:

EventKillTimer(); 

Перед вызовом метода Deinit(REASON_CHARTCLOSE).

На этом рассмотрение класса CExpert закончено, так как он был создан исключительно для демонстрации методов объектов класса CPriceHistogram.

4.3. ClassPriceHistogram.mqh
//+------------------------------------------------------------------+
//|   Класс CPriceHistogram                                          |
//|   Описание класса                                                |
//+------------------------------------------------------------------+
class CPriceHistogram : public CObject
  {
private:
   // Переменные класса
   double            high_day,low_day;
   bool              Init_passed;      // Флаг, инициализация пройдена
   CChartObjectTrend *POCLine;
   CChartObjectTrend *SecondTopPOCLine,*SecondBottomPOCLine;
   CChartObjectText  *POCLable;
   CList             ListHistogramInner; // для хранения линий гистограммы Inner 
   CList             ListHistogramOuter; // для хранения линий гистограммы Outer 
   bool              show_level;         // показать значения уровня
   bool              virgin;             // это девственный уровень
   bool              show_second_poc;    // показывать вторичные уровни POCs
   double            second_poc_top;     // значение верхнего уровня вторичной POCs
   double            second_poc_bottom;  // значение нижнего уровня вторичной POCs 
   double            poc_value;          // значение уровня POCs 
   color             poc_color;          // цвет уровня POCs 
   datetime          poc_start_time;
   datetime          poc_end_time;
   bool              show_histogram;     // показывать гистограмму  
   color             inner_color;        // внутренний цвет гистограммы 
   color             outer_color;        // внешний цвет гистограммы
   uint              range_percent;      // процент диапазона 
   datetime          time_start;         // время для начала построения 
   datetime          time_end;           // время завершения построения 
public:
   // Конструктор класса 
                     CPriceHistogram();
   // Диструктор класса 
                    ~CPriceHistogram(){Delete();}
   // Инициализация класса 
   bool              Init(datetime time_open,datetime time_close,bool showhistogram);
   // Показать значение уровня 
   void              ShowLevel(bool show){show_level=show; if(Init_passed) RefreshPOCs();}
   bool              ShowLevel(){return(show_level);}
   // Показать гистограмму 
   void              ShowHistogram(bool show);
   bool              ShowHistogram(){return(show_histogram);}
   // Показать Вторичные уровни POCs 
   void              ShowSecondaryPOCs(bool show){show_second_poc=show;if(Init_passed)RefreshPOCs();}
   bool              ShowSecondaryPOCs(){return(show_second_poc);}
   // Установить цвет уровней POCs 
   void              ColorPOCs(color col){poc_color=col; if(Init_passed)RefreshPOCs();}
   color             ColorPOCs(){return(poc_color);}
   // Установить внутренний цвет гистограммы 
   void              ColorInner(color col);
   color             ColorInner(){return(inner_color);}
   // Установить внешний цвет гистограммы 
   void              ColorOuter(color col);
   color             ColorOuter(){return(outer_color);}
   // Установить процент диапазона 
   void              RangePercent(uint percent){range_percent=percent; if(Init_passed)calculationPOCs();}
   uint              RangePercent(){return(range_percent);}
   // Возвращает значение девственности уровня POCs 
   bool              VirginPOCs(){return(virgin);}
   // Возвращает стартовое время построения гистограммы
   datetime          GetStartDateTime(){return(time_start);}
   // Обновление уровней POCs
   bool              RefreshPOCs();
private:
   // Расчеты гистограммы и уровней POCs
   bool              calculationPOCs();
   // Удаление класса
   void              Delete();
  }; 

В описании класса я постарался максимально снабдить комментариями переменные и методы данного класса. Подробно остановимся лишь на некоторых из них.

//+------------------------------------------------------------------+
//|   Инициализация класса                                           |
//+------------------------------------------------------------------+
bool CPriceHistogram::Init(datetime time_open,datetime time_close,bool showhistogram) 

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

В моем примере (класс CExpert) входными параметрами передаются время открытия дня и время открытия следующего дня day_time_open[i]+PeriodSeconds(PERIOD_D1). Но вам при использовании данного класса ничто не мешает задать, например, время европейской, американской сессии, или промежуток размером в неделю, в месяц и т.д.

//+---------------------------------------------------------------------------------------+
//|   Расчеты гистограммы и уровней POCs                                                  |
//+---------------------------------------------------------------------------------------+
bool CPriceHistogram::calculationPOCs() 

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

// Получаем данные за период с time_start до time_end
   int err=0;
   do
     {
      //--- скопируем в массив время открытия каждого бара
      rates_time=CopyTime(NULL,PERIOD_M1,time_start,time_end,iTime);
      if(rates_time<0)
         PrintErrorOnCopyFunction("CopyTime",_Symbol,PERIOD_M1,GetLastError());

      //--- скопируем в массив цены High
      rates_high=CopyHigh(NULL,PERIOD_M1,time_start,time_end,iHigh);
      if(rates_high<0)
         PrintErrorOnCopyFunction("CopyHigh",_Symbol,PERIOD_M1,GetLastError());

      //--- скопируем в массив цены Low
      rates_total=CopyLow(NULL,PERIOD_M1,time_start,time_end,iLow);
      if(rates_total<0)
         PrintErrorOnCopyFunction("CopyLow",_Symbol,PERIOD_M1,GetLastError());

      err++;
     }
   while((rates_time<=0 || (rates_total!=rates_high && rates_total!=rates_time)) && err<AMOUNT_OF_ATTEMPTS&&!IsStopped());
   if(err>=AMOUNT_OF_ATTEMPTS)
     {
      return(false);
     }
   poc_start_time=iTime[0];
   high_day=iHigh[ArrayMaximum(iHigh,0,WHOLE_ARRAY)];
   low_day=iLow[ArrayMinimum(iLow,0,WHOLE_ARRAY)];
   int count=int((high_day-low_day)/_Point)+1;
// Подсчет длительности нахождения цены на каждом уровне
   int ThicknessOfLevel[];    // создаем массив для подсчета тиков
   ArrayResize(ThicknessOfLevel,count);
   ArrayInitialize(ThicknessOfLevel,0);
   for(int i=0;i<rates_total;i++)
     {
      double C=iLow[i];
      while(C<iHigh[i])
        {
         int Index=int((C-low_day)/_Point);
         ThicknessOfLevel[Index]++;
         C+=_Point;
        }
     }
   int MaxLevel=ArrayMaximum(ThicknessOfLevel,0,count);
   poc_value=low_day+_Point*MaxLevel;

Сначала мы получаем данные минутной истории за заданный промежуток времени, массив времени iTime[], массив максимумов iHigh[] и массив минимумов iLow[]. Затем мы находим максимальный элемент iHigh  и минимальный iLow. Вычисляем count - количество пунктов от минимума до максимума, и резервируем массив ThicknessOfLevel размером count. В цикле мы проходим по каждой минутной свече от Low до High, и вносим в массив данные о длительности нахождения цены на данном уровне. Затем находим элемент массива ThicknessOfLevel с максимальном значением - это и будет уровень, на котором цена находилась дольше всего. Это наш уровень POC.

// Находим вторичные POCs
   int range_min=ThicknessOfLevel[MaxLevel]-ThicknessOfLevel[MaxLevel]*range_percent/100;
   int DownLine=0;
   int UpLine=0;
   for(int i=0;i<count;i++)
     {
      if(ThicknessOfLevel[i]>=range_min)
        {
         DownLine=i;
         break;
        }
     }
   for(int i=count-1;i>0;i--)
     {
      if(ThicknessOfLevel[i]>=range_min)
        {
         UpLine=i;
         break;
        }
     }
   if(DownLine==0)
      DownLine=MaxLevel;
   if(UpLine==0)
      UpLine=MaxLevel;
   second_poc_top=low_day+_Point*UpLine;
   second_poc_bottom=low_day+_Point*DownLine;

Следующий этап - нахождение вторичных уровней POC. Вспомним о том, что наша гистограмма делится на два диапазона, внутренний и внешний (отображаются разным цветом), размер диапазона задается в процентах времени нахождения цены на данном уровне. Границы внутреннего диапазона и являются вторичными уровнями POC – Secondary POC.

После нахождения Secondary POC – границ процентного диапазона, приступаем к построению гистограммы.

// Формирование гистограммы 
   if(show_histogram)
     {
      datetime Delta=(iTime[rates_total-1]-iTime[0]-PeriodSeconds(PERIOD_H1))/ThicknessOfLevel[MaxLevel];
      int step=1;
      
      if(count>100)
         step=count/100// Задаем шаг гистограммы максимум 100 линий 

      ListHistogramInner.Clear();
      ListHistogramOuter.Clear();
      for(int i=0;i<count;i+=step)
        {
         string name=TimeToString(time_start)+" "+IntegerToString(i);
         double StartY= low_day+_Point*i;
         datetime EndX= iTime[0]+(ThicknessOfLevel[i])*Delta;

         CChartObjectTrend *obj=new CChartObjectTrend();
         obj.Create(0,name,0,poc_start_time,StartY,EndX,StartY);
         obj.Background(true);
         if(i>=DownLine && i<=UpLine)
           {
            obj.Color(inner_color);
            ListHistogramInner.Add(obj);
           }
         else
           {
            obj.Color(outer_color);
            ListHistogramOuter.Add(obj);
           }
        }
     }

Здесь необходимо упомянуть о том, что для уменьшения нагрузки на терминал, я вывожу на экран максимум 100 линий для каждой гистограммы. Линии гистограммы хранятся в двух списках ListHistogramInner и ListHistogramOuter, которые являются объектами известного уже нам класса CList. А хранятся в них указатели объектов стандартного класса CChartObjectTrend. Почему два списка, я думаю, можно догадаться из названия, для возможности изменять цвет гистограммы.

// Получаем массивы данных начиная конечного времени гистограммы и до текущего времени
   err=0;
   do
     {
      rates_time=CopyTime(NULL,PERIOD_M1,time_end,last_tick.time,iTime);
      rates_high=CopyHigh(NULL,PERIOD_M1,time_end,last_tick.time,iHigh);
      rates_total=CopyLow(NULL,PERIOD_M1,time_end,last_tick.time,iLow);
      err++;
     }
   while((rates_time<=0 || (rates_total!=rates_high && rates_total!=rates_time)) && err<AMOUNT_OF_ATTEMPTS);
// Если истории нет, текущий день, уровень является девственным, поднимаем флаг
   if(rates_time==0)
     {
      virgin=true;
     }
   else
// Иначе проверяем историю
    {
      for(index=0;index<rates_total;index++)
         if(poc_value<iHigh[index] && poc_value>iLow[index]) break;

      if(index<rates_total)   // если уровень пересечен ценой 
         poc_end_time=iTime[index];
      else
         virgin=true;
     }
   if(POCLine==NULL)
     {     
      POCLine=new CChartObjectTrend();
      POCLine.Create(0,TimeToString(time_start)+" POC ",0,poc_start_time,poc_value,0,0);
     }
   POCLine.Color(poc_color);
   RefreshPOCs();

Я постарался снабдить класс CPriceHistogram всеми необходимыми методами, если их недостаточно, можете добавить сами, я окажу любую посильную помощь.

Заключение

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

Спасибо за внимание. Отвечу на любые Ваши вопросы.

Прикрепленные файлы |
classexpert.mqh (11.48 KB)
pricehistogram.mq5 (4.06 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (39)
sngsv
sngsv | 9 фев 2015 в 13:54

Доброго времени суток,

индикатор установил, на графике появился в предыдущих днях, а текущий день не отрисовывает...

Подскажите, как исправить?

Svzem
Svzem | 9 янв 2016 в 22:30
Здравствуйте, Дмитрий! Скажите пожалуйста возможно ли отключить построение virgin point of control дабы не загромождать график чрезмерным количеством линий, и как это сделать если возможно? Спасибо.
Yury Golyakov
Yury Golyakov | 17 янв 2016 в 16:49

Великолепная работа!

Не хватает только:

- возможности построить профиль на произвольно задаваемом временном диапазоне. Неделя, месяц, год.

- настраивать толщину и стиль линий.

EPA
EPA | 21 мар 2017 в 21:22
Подскажите пожалуйста как исправить ошибку
EPA
EPA | 21 мар 2017 в 21:24
Строит только уровни. Профиль не строит.
Передача данных между индикаторами - простое решение наболевшей проблемы Передача данных между индикаторами - простое решение наболевшей проблемы

Мы хотим создать среду, которая предоставляла бы возможность обращения к показаниям индикаторов, присоединенных к тому или иному графику терминала, и обладала бы следующими свойствами: отсутствие копирования данных; минимальное вмешательство в код уже имеющихся инструментов при необходимости их «подключения»; реализация преимущественно средствами MQL (естественно, механизм DLL нам все же потребуется, однако, как мы увидим, его использование будет ограничиваться не более чем десятком строк на С++). В статье объясняется, как можно довольно просто создать в терминале MetaTrader программную среду, обеспечивающую средства для доступа к буферам индикаторов из других MQL-программ.

Как написать индикатор в MQL5 Как написать индикатор в MQL5

Что представляет собою индикатор? Это набор вычисленных значений, которые мы хотим отобразить на экране монитора удобным для нас образом. Наборы значений представляются в программах в виде массивов. Таким образом, создание индикатора - это написание алгоритма, который обрабатывает одни массивы (массивы цен) и записывает результаты обработки в другие массивы (значения индикаторов). На примере создания индикатора True Strength Index в статье рассказывается, как писать индикаторы на MQL5

Как за 10 минут написать DLL библиотеку для MQL5 и обмениваться данными? Как за 10 минут написать DLL библиотеку для MQL5 и обмениваться данными?

Так уж сложилось, что сейчас мало кто из разработчиков помнит, как написать простую DLL библиотеку и в чем особенности связывания разнородных систем. Я постараюсь за 10 минут на примерах продемонстрировать весь процесс создания простых DLL библиотек и раскрою некоторые технические детали нашей реализации связывания. Покажу пошаговый процесс создания DLL библиотеки в Visual Studio с примерами передачи разных типов переменных (числа, массивы, строки и т.д.) и защиту клиентского терминала от падений в пользовательских DLL.

Индикатор от индикатора в MQL5 Индикатор от индикатора в MQL5

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