Набор инструментов для ручной разметки графиков и торговли (Часть II). Рисование разметки

16 октября 2020, 08:37
Oleh Fedorov
4
4 114

Введение

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

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

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

Объекты, которые умеет рисовать текущая версия библиотеки:

  • Простые ("бесконечные") прямые линии — горизонтали и вертикали.
  • Обычные трендовые линии (по двум ближайшим к мыши экстремумам). Можно настраивать, будет ли линия лучом или просто отрезком. Если линия — в виде отрезка, то можно задать режим, в котором она будет одним концом в будущем. В этом случае размер линии равен расстоянию между экстремумами, умноженному на определённый коэффициент, который можно настраивать в параметрах эксперта.
  • Горизонтальные уровни определённой длины (не бесконечные). Можно рисовать короткие и "удлинённые" — с заданным относительно короткого коэффициентом.
  • Вертикальный отрезок с отметками уровней.
  • Веер Фибоначчи. Параметры уровней можно настраивать, но я использую чуть доработанный мною вариант, который показал когда-то на "Ониксе" человек с ником Vadimcha. Там этот веер назвали VFan, и в своём коде я придерживаюсь именно этого названия.
  • Набор Вил Эндрюса, состоящий из трёх объектов.

Структура проекта довольно проста. В библиотеке есть пять взаимосвязанных файлов. "GlobalVariables.mqh", "Graphics.mqh", "Mouse.mqh", "Shortcuts.mqh", " Utilites.mqh". Все файлы располагаются в одной папке "Shortcuts" в стандартном каталоге "Include".

Главный файл — "Shortcuts.mqh", к нему подключаются все остальноые файлы.  В нём же создаётся экземпляр класса CShortcuts, что позволяет легко подключить библиотеку к вашему основному эксперту.

В предыдущей статье я сосредотачивался на файле вспомогательных функций, "Utilites.mqh", в этой же части основной упор будет на файле  "Graphics.mqh",содержащем логику рисования.


Файл глобальных настроек

Во второй версии библиотеки существенно расширены возможности настроек, так как появилось больше объектов, на которые можно повлиять. Полный код текущей версии:

//+------------------------------------------------------------------+
//|                                              GlobalVariables.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                            https://www.mql5.com/ru/articles/7908 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2020, MetaQuotes Software Corp."
#property link      "https://www.mql5.com/ru/articles/7908"
//+------------------------------------------------------------------+
//| Файл описания параметров, доступных пользователю                 |
//+------------------------------------------------------------------+
#define VERSION 2.0 // Не #property, просто константа. Чуть удобнее.   
//+------------------------------------------------------------------+
//| Настройки клавиш                                                 |
//+------------------------------------------------------------------+
input string   Keys="=== Настройки клавиш ===";
input string   Up_Key="U";                          // Переключить таймфрейм вверх
input string   Down_Key="D";                        // Переключить таймфрейм вниз
input string   Trend_Line_Key="T";                  // Трендовая линия
input string   Switch_Trend_Ray_Key="R";            // Признак луча трендовой
input string   Z_Index_Key="Z";                     // Признак графика сверху
input string   Vertical_With_Short_Levels_Key="V";  // Вертикальный отрезок 
input string   Short_Level_Key="S";                 // Короткий уровень
input string   Long_Level_Key="L";                  // Удлинённый уровень
input string   Simple_Horizontal_Line_Key="H";      // Простая горизонталь
input string   Simple_Vertical_Line_Key="I";        // Простая вертикаль
input string   VFun_Key="F";                        // Веер Фибоначчи
input string   Pitchfork_Key="P";                   // Вилы Эндрюса

//+------------------------------------------------------------------+
//| Настройки цветов                                                 |
//+------------------------------------------------------------------+
input string   Colors="=== Настройки цветов ===";
input color    VFan_Color=clrLightGray;            // цвет линий веера 
                                                   //   (и вспоомгательный 
                                                   //    для специальных случаев)
//---
input color    Pitchfork_Main_Color = clrBlue;     // цвет вил Эндрюса
input color    Pitchfork_Shiff_Color = clrRed;     // цвет вил Шиффа
input color    Pitchfork_Reverce_Color = clrYellow;// цвет "обратных" вил

//+------------------------------------------------------------------+
//| Настройки размеров                                               |
//+------------------------------------------------------------------+
input string   Dimensions="=== Настройки размеров ===";
input int      Short_Level_Length=12;     // Длина короткого уровня (бар)
input int      Short_Level_Width=1;       // Ширина линии короткого уровня
input int      Long_Level_Width=2;        // Ширина линии длинного уровня
input int      Vertical_With_Short_Levels_Width=1; // Ширина линии вертикали с уровнями
input int      Short_Level_7_8_Width=1;   // Ширина линии уровня 7/8
input int      Short_Level_14_8_Width=1;  // Ширина линии уровня 14/8
input int      Simple_Vertical_Width=1;   // Ширина простой вертикально1й линии
input int      Simple_Horizontal_Width=1; // Ширина простой горизонтальной линии
input int      Trend_Line_Width=2;        // Ширина трендовой линии

//+------------------------------------------------------------------+
//| Стили отображения                                                |
//+------------------------------------------------------------------+
input string   Styles="=== Стили отображения ===";
input ENUM_LINE_STYLE      Vertical_With_Short_Levels_Style=STYLE_SOLID; // Стиль Вертикаль
                                                                         // с уровнями
input ENUM_LINE_STYLE      Short_Level_Style=STYLE_SOLID;      // Стиль Короткий уровень
input ENUM_LINE_STYLE      Long_Level_Style=STYLE_SOLID;       // Стиль Длинный уровень
input ENUM_LINE_STYLE      Short_Level_7_8_Style=STYLE_SOLID;  // Стиль Уровень 7/8
input ENUM_LINE_STYLE      Short_Level_14_8_Style=STYLE_DOT;   // Стиль Уровень 14/8
input ENUM_LINE_STYLE      Simple_Vertical_Style=STYLE_DOT;    // Стиль Простая вертикаль
input ENUM_LINE_STYLE      Simple_Horizontal_Style=STYLE_DOT;  // Стиль Простая горизонталь
input ENUM_LINE_STYLE      VFun_Levels_Style=STYLE_SOLID;      // Стиль Веер
input ENUM_LINE_STYLE      Trend_Line_Style=STYLE_SOLID;       // Стиль Трендовая
//---
input ENUM_LINE_STYLE      Pitchfork_Main_Style = STYLE_SOLID;    // Стиль Вилы Эндрюса
input ENUM_LINE_STYLE      Pitchfork_Shiff_Style = STYLE_SOLID;   // Стиль Вилы Шиффа
input ENUM_LINE_STYLE      Pitchfork_Reverce_Style = STYLE_SOLID; // Стиль "Обратные" вилы
//+------------------------------------------------------------------+
//| Параметры экстремумов вил                                        |
//+------------------------------------------------------------------+
input string               Pitchforks="=== Параметры экстремумов вил ===";
//---
input int                  Pitchfork_First_Point_Left_Bars=6;   // Вилы - 1 точка, баров слева
input int                  Pitchfork_First_Point_Right_Bars=6;  // Вилы - 1 точка, баров справа
//---
input int                  Pitchfork_Second_Point_Left_Bars=6;  // Вилы - 2 точка, баров слева
input int                  Pitchfork_Second_Point_Right_Bars=6; // Вилы - 2 точка, баров справа
//---
input int                  Pitchfork_Third_Point_Left_Bars=6;   // Вилы - 3 точка, баров слева
input int                  Pitchfork_Third_Point_Right_Bars=2;  // Вилы - 3 точка, баров справа
//+------------------------------------------------------------------+
//| Остальные параметры                                              |
//+------------------------------------------------------------------+
input string               Others="=== Остальные параметры ===";
input double               Vertical_Short_Level_Coefficient=0.825;  // Коэффициент вертикальных уровней
input double               Long_Level_Multiplicator=2;              // Коэффициент для длинного уровня
input int                  Trend_Length_Coefficient=4;              // Коэффициент длины трендовой
input bool                 Is_Trend_Ray=false;                      // Трендовая - луч
input bool                 Is_Change_Timeframe_On_Create = true;    // Скрывать объекты на старших таймфреймах?
                                                                    //   (true - скрывать, false - отображать)
input bool                 Is_Select_On_Create=true;                // Выделение при создании
input bool                 Is_Different_Colors=true;                // Менять ли цвета для таймов

// Количество баров слева и спарава
// для экстремумов веера и трендовой
input int                  Fractal_Size_Left=1;                     // Размер фрактала слева
input int                  Fractal_Size_Right=1;                    // Размер фрактала справа

input bool                 Pitchfork_Show_Main = true;     // Отображать вмлы Эндрюса
input bool                 Pitchfork_Show_Shiff = true;    // Отображать вмлы Шиффа
input bool                 Pitchfork_Show_Reverce = true;  // Отображать "обратные" вмлы
input bool                 Print_Warning_Messages=true;    // Выводить сообщения об ошибках
input string               VFun_Levels="-1.5,-0.618,-0.236,"+
                                       "  0,0.236,0.382,"+
                                       "  0.618,0.786,0.886,0.942";  // Уровни веера
input string               Array_Delimiter=",";            // Разделитель элементов массива
//---

//+------------------------------------------------------------------+
//| Префиксы имён рисуемых фигур (меняются только в коде,            |
//| в параметрах советника не видны)                                 |
//+------------------------------------------------------------------+
//string   Prefixes="=== Prefixes ===";
//string   Vertical_With_Short_Levels_Prefix="Vertical_";  // Префикс вертикали с уровнями
//string   Short_Level_Prefix="Short_Level_";              // Префикс короткого уровня
//string   Long_Level_Prefix="Long_Level_";                // Префикс длинного уровня
//string   Simple_Horizontal_Prefix="Simple_H_";           // Префикс простой горизонтали
//string   Simple_Vertical_Prefix="Simple_V_";             // Префикс простой вертикали
//string   VFan_Prefix="VFan_";                            // Префикс веера
//string   Trend_Line_Prefix="Trend_";                     // Префикс трендовой
//string   Pitchfork_Prefix="Pitchfork_";                  // Префикс вил
string   allPrefixes[] =      // Префиксы имён объектов
  {
   "Trend_",            // 0 - Префикс трендовой
   "Simple_H_",         // 1 - Префикс простой горизонтали
   "Simple_V_",         // 2 - Префикс простой вертикали
   "VFan_",             // 3 - Префикс веера
   "Pitchfork_",        // 4 - Префикс вил
   "Vertical_",         // 5 - Префикс вертикали с уровнями
   "Short_Level_",      // 6 - Префикс короткого уровня
   "Long_Level_"        // 7 - Префикс длинного уровня
  };

//+------------------------------------------------------------------+
//| Цвета объектов одного таймфрейма (меняются только в коде,        |
//| в параметрах советника не видны)                                 |
//+------------------------------------------------------------------+
// string TimeframeColors="=== Стандартные цвета для таймфреймов  ===";
color mn1_color=clrCrimson;
color w1_color=clrDarkOrange;
color d1_color=clrGoldenrod;
color h4_color=clrLimeGreen;
color h1_color=clrLime;
color m30_color=clrDeepSkyBlue;
color m15_color=clrBlue;
color m5_color=clrViolet;
color m1_color=clrDarkViolet;
color common_color=clrGray;

//--- Вспомогательная константа для вывода сообщаений об ошибках
#define DEBUG_MESSAGE_PREFIX "=== ",__FUNCTION__," === "

//--- Константы для описания основных таймфреймов при рисовании
//---    Для совместимости с версией 4 таймфреймы, которых нет 
//---    в панели инструментов, исключены.
#define PERIOD_LOWER_M5 OBJ_PERIOD_M1|OBJ_PERIOD_M5
#define PERIOD_LOWER_M15 PERIOD_LOWER_M5|OBJ_PERIOD_M15
#define PERIOD_LOWER_M30 PERIOD_LOWER_M15|OBJ_PERIOD_M30
#define PERIOD_LOWER_H1 PERIOD_LOWER_M30|OBJ_PERIOD_H1
#define PERIOD_LOWER_H4 PERIOD_LOWER_H1|OBJ_PERIOD_H4
#define PERIOD_LOWER_D1 PERIOD_LOWER_H4|OBJ_PERIOD_D1
#define PERIOD_LOWER_W1 PERIOD_LOWER_D1|OBJ_PERIOD_W1
//+------------------------------------------------------------------+

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

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

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

Рисование "примитивов": вертикаль и горизонталь

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

Вот код:

//+------------------------------------------------------------------+
//| Рисует простые прямые линии (вертикаль и горизонталь) в заданной |
//| указанной мышью мышью или параметрами позиции                    |
//| Параметры:                                                       |
//|   _object_type - тип объекта. Может быть OBJ_VLINE или OBJ_HLINE |
//|   _time - время. Если не указано, берётся время мыши             |
//|   _price - цена. Если не указана, берётся цена под указателем.   |
//+------------------------------------------------------------------+
void              CGraphics::DrawSimple(
   ENUM_OBJECT _object_type, // тип объекта
   datetime    _time=-1,     // время
   double      _price=-1     // цена
)
  {
//---
   string Current_Object_Name;   // имя будущего объекта
   color Current_Object_Color=   // цвет (зависит от "стандартного" цвета таймфрейма)
      CUtilites::GetTimeFrameColor(CUtilites::GetAllLowerTimeframes());
   datetime Current_Object_Time; // время начальной точки
   double Current_Object_Price;  // цена начальной точки
   ENUM_LINE_STYLE Current_Object_Style=STYLE_DOT; // стиль линии
   int Current_Object_Width=1;   // ширина линии
   int window=0;                 // номер подокна

//--- Настраиваем параметры линии в зависимости от типа
   if(_object_type==OBJ_VLINE)   // Для вертикали
     {
      Current_Object_Name=       // генерируем имя
         CUtilites::GetCurrentObjectName(
            Simple_Vertical_Prefix,
            _object_type
         );
      // стиль - в соответствии с глобальными параметрами
      Current_Object_Style=Simple_Vertical_Style;
      // ширина - в соответствии с глобальными параметрами
      Current_Object_Width=Simple_Vertical_Width;
     }
   else
      if(_object_type==OBJ_HLINE)// Для горизонтали
        {
         Current_Object_Name=    // генерируем имя
            CUtilites::GetCurrentObjectName(
               Simple_Horizontal_Prefix,
               _object_type
            );
         // стиль - в соответствии с глобальными параметрами
         Current_Object_Style=Simple_Horizontal_Style;
         // ширина - в соответствии с глобальными параметрами
         Current_Object_Width=Simple_Horizontal_Width;
        }
      else  // Данная функция рисует только горизонтали и вертикали.
        {
         // Если в параметрах передано что-то другое...
         if(Print_Warning_Messages)
           {
            // ...сообщеяем об ошибке...
            Print(DEBUG_MESSAGE_PREFIX,"Error, wrong object type");
           }
         // ...и выходим.
         return;
        }

//--- Если в параметрах не указаны координаты, то берём координаты мыши
   Current_Object_Price = _price==-1 ? CMouse::Price() : _price;
   Current_Object_Time = _time==-1 ? CMouse::Time() : _time;

//--- Создаём объект
   ObjectCreate(
      0,
      Current_Object_Name,
      _object_type,
      0,
      Current_Object_Time,
      Current_Object_Price
   );

//--- Задаём созданному объекту параметры отображения
   CurrentObjectDecorate(
      Current_Object_Name,
      Current_Object_Color,
      Current_Object_Width,
      Current_Object_Style
   );

//--- И перерисовываем график, завершая работу
   ChartRedraw(0);
  }

Операции тривиальны. Генерируем имя, берём параметры настроек из input-переменных, описанных в файле "GlobalVariables.mqh", получаем координаты стартовой точки объекта (либо из параметров функции, либо просто как координаты мыши) — и вот, наш объект готов.

Ура, товарищи!

Осталось добавить эту функцию в заголовок файла

//+------------------------------------------------------------------+
//|                                                     Graphics.mqh |
//|                        Copyright 2020, MetaQuotes Software Corp. |
//|                            https://www.mql5.com/ru/articles/7468 |
//+------------------------------------------------------------------+

// ...

//+------------------------------------------------------------------+
//| Класс для рисования графических объектов                         |
//+------------------------------------------------------------------+
class CGraphics
  {

   // ...

public:

   // ...

   //--- Рисует простиые прямые линии (вертикали и горизонтали)
   void              CGraphics::DrawSimple(
      ENUM_OBJECT _object_type, // тип объекта
      datetime    _time=-1,     // время
      double      _price=-1     // цена
   )

   // ...
  }
;
//+------------------------------------------------------------------+

Ну, и добавить обработку нажатия соответствующих клавиш:

//+------------------------------------------------------------------+
//|                                                    Shortcuts.mqh |
//+------------------------------------------------------------------+

// ...

//+------------------------------------------------------------------+
//| Функция обработки событий                                        |
//+------------------------------------------------------------------+
void CShortcuts::OnChartEvent(
   const int id,
   const long &lparam,
   const double &dparam,
   const string &sparam
)
  {
//---
   int window = 0;
//---
   switch(id)
     {

      // ...

      //--- Обработка нажатий клавиш
      case CHARTEVENT_KEYDOWN:

         // ...

         //--- Нарисовать простую вертикаль
         if(CUtilites::GetCurrentOperationChar(Simple_Vertical_Line_Key) == lparam)
           {
            m_graphics.DrawSimple(OBJ_VLINE);
           }
         //--- Нарисовать простую горизонталь
         if(CUtilites::GetCurrentOperationChar(Simple_Horizontal_Line_Key) == lparam)
           {
            m_graphics.DrawSimple(OBJ_HLINE);
           }

         // ...

         break;
         //---

     }
  }

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

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

Графические "примитивы": вертикали и горизонтали

Клавиши, соответствующие этим прямым, по умолчанию обозначены как "I" (i) и "H" (h).

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

Графйические примитивы: прямыце на M30

Графические примитивы: демонстрация таймфреймов

В целях совместимости с MQL4 используются только таймфреймы из стандартной панели инструментов, которые отображены по умолчанию. Эти же таймфреймы перебираются, если пролистывать их с помощью клавиш "U" "D" (при нажатии на эти клавиши период графика изменяется, соответственно, на фрейм вверх и на фрейм вниз — см. функцию CUtilites::ChangeTimeframes).

VFun, он же — веер Фибоначчи

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

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

//+------------------------------------------------------------------+
//| Задаёт значения уровней м оформление в любом объекте Фибоначчи   |
//|    Цвет и стиль уровней берёт из полей класса                    |                        
//| Параметры:                                                       |
//|    _object_name - имя объекта Фибоначчи                          |
//|    _levels_values[] - массив значений уровней                    |
//+------------------------------------------------------------------+
void CGraphics::SetFiboLevels(
   string _object_name,                      // имя объекта
   const double &_levels_values[]            // массив значений
)
  {
   int i,                                      // счетчик текущего уровня
       levels_count=ArraySize(_levels_values); // общее количество уровней

//--- Проверяем, что количество значений в массиве не в допустимом диапазоне
   if(levels_count>32 || levels_count==0)
     {
      Print(DEBUG_MESSAGE_PREFIX,": Невозможно задать уровни! Неверно задан массив данных. ");
      return;
     }

//--- Приступем к созданию

//--- Устанавливаем количество уровней для текущего объекта
   ObjectSetInteger(0,_object_name,OBJPROP_LEVELS,levels_count);
//--- Для каждого уровня задаём значение, цвет и стиль.
   for(i=0; i<levels_count; i++)
     {
      ObjectSetDouble(0,_object_name,OBJPROP_LEVELVALUE,i,_levels_values[i]);
      ObjectSetInteger(0,_object_name,OBJPROP_LEVELCOLOR,i,m_Fibo_Default_Color);
      ObjectSetInteger(0,_object_name,OBJPROP_LEVELSTYLE,i,m_Fibo_Default_Style);
     }
//--- Перед завершением работы перерисовываем график
   ChartRedraw(0);
  }

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

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

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

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

Переменные, описывающие параметры по умолчанию, описаны как приватные члены класса CGraphics и инициируются в конструкторе этого класса значениями из параметров советника.

//+------------------------------------------------------------------+
//|                                                     Graphics.mqh |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Класс для рисования графических объектов                         |
//+------------------------------------------------------------------+
class CGraphics
  {
   //--- Поля
private:
   // ...
   color             m_Fibo_Default_Color;
   ENUM_LINE_STYLE   m_Fibo_Default_Style;
   // ...
//+------------------------------------------------------------------+
//| Конструктор по умолчанию                                         |
//+------------------------------------------------------------------+
CGraphics::CGraphics(void)
  {
   //...
   m_Fibo_Default_Color = Fibo_Default_Color;
   m_Fibo_Default_Style = VFun_Levels_Style;
  }
   // ...

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

Приведу ещё одну функцию, которая задаёт описания уровней для любого объекта Фибоначчи.

//+------------------------------------------------------------------+
//| Задаёт описания уровней в любом объекте Фибоначчи                |
//|    _object_name - имя объекта Фибоначчи                          |
//|    _levels_descriptions[] - массив описаний уровней              |
//+------------------------------------------------------------------+
void CGraphics::SetFiboDescriptions(
   string _object_name,                  // имя объекта
   const string &_levels_descriptions[]  // массив описаний
)
  {
   int i,                                                                  // счетчик текущего уровня
       levels_count=(int)ObjectGetInteger(0,_object_name,OBJPROP_LEVELS),  // Реальное количество уровней
       array_size=ArraySize(_levels_descriptions);                         // Количество полученных описаний
//--- Перебрать все уровни
   for(i=0; i<levels_count; i++)
     {
      if(array_size>0 && i<array_size) // Выбираем описание из массива
        {
         //--- и записываем его на уровень
         ObjectSetString(0,_object_name,OBJPROP_LEVELTEXT,i,_levels_descriptions[i]);
        }
      else // если же описаний недостаточное количество...
        {
         ObjectSetString(0,_object_name,OBJPROP_LEVELTEXT,i,""); // ...Описание оставляем пустым
        }
     }
//--- Перед завершением работы перерисовываем график
   ChartRedraw(0);
  }

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

И вот теперь, когда уровни добавлять стало просто, можно написать саму функцию добавления веера.

//+------------------------------------------------------------------+
//| Рисует веер Фибоначи от ближайшего локального экстремума.        |
//+------------------------------------------------------------------+
void CGraphics::DrawVFan(void)
  {
//---
   double levels_values[];                 // Массив значений уровней
   string levels_descriptions[] = {};      // Массив описаний уровней
   int p1=0,                               // Номер бара начальной точки веера
       p2=0;                               // Номер бара конечной точки веера
   double price1=0,                        // Цена первой точки
          price2=0;                        // Цена второй точки
   string fun_name =                       // Имя веера
      CUtilites::GetCurrentObjectName(allPrefixes[3],OBJ_FIBOFAN),
      fun_0_name =
         CUtilites::GetCurrentObjectName(allPrefixes[3]+"0_",OBJ_TREND);

//--- Получаем данные для веера из строки параметров
   CUtilites::StringToDoubleArray(VFun_Levels,levels_values);

//--- Находим ближайшие к мышке экстремумы
   if(CMouse::Below())     // если мышь под ценой
     {
      CUtilites::SetExtremumsBarsNumbers(false,p1,p2);
      price1=iLow(Symbol(),PERIOD_CURRENT,p1);
      price2=iLow(Symbol(),PERIOD_CURRENT,p2);
     }
   else
      if(CMouse::Above())  // или если мышь над ценой
        {
         CUtilites::SetExtremumsBarsNumbers(true,p1,p2);
         price1=iHigh(Symbol(),PERIOD_CURRENT,p1);
         price2=iHigh(Symbol(),PERIOD_CURRENT,p2);
        }
//--- Создаём объект веера
   ObjectCreate(
      0,fun_name,OBJ_FIBOFAN,0,
      iTime(Symbol(),PERIOD_CURRENT,p1),
      price1,
      iTime(Symbol(),PERIOD_CURRENT,p2),
      price2
   );

//--- Нулевой луч этого объекта обозначаем цветной прямой (для совместимости с МТ4)
   TrendCreate(
      0,
      fun_0_name,
      0,
      iTime(Symbol(),PERIOD_CURRENT,p1),
      price1,
      iTime(Symbol(),PERIOD_CURRENT,p2),
      price2,
      CUtilites::GetTimeFrameColor(CUtilites::GetAllLowerTimeframes()),
      0,1,false,true,true
   );

//--- Описываем уровни веера
   SetFiboLevels(fun_name,levels_values);
   SetFiboDescriptions(fun_name, levels_descriptions);

//--- Задаём стандартные параметры (такие, как отобрающие таймфреймы и выделение после создания
   CurrentObjectDecorate(fun_name,m_Fibo_Default_Color);
//--- Также оформляем луч-"заместитель"
   CurrentObjectDecorate(
      fun_0_name,
      CUtilites::GetTimeFrameColor(
         CUtilites::GetAllLowerTimeframes()
      )
   );

//---
   ChartRedraw(0);
  }

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

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

А массив значений создаётся из параметров советника с помощью функции-утилиты

CUtilites::StringToDoubleArray(VFun_Levels,levels_values);

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

Добавляем к списку описания команд команду для рисования веера:

//+------------------------------------------------------------------+
//|                                                   Shortcuts.mqhh |
//+------------------------------------------------------------------+

   //...
//--- Нарисовать веер Фибоначчи (VFum)
         if(CUtilites::GetCurrentOperationChar(VFun_Key) == lparam)
           {
            m_graphics.DrawVFan();
           }
         break;
   //...

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

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

Веер Фибоначчи (VFun)

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

Так оно и оказалось... Но это — уже совсем другая история.


Веер Фибоначчи (будущее)

Вилы Эндрюса

Я использую 3 типа вил.

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

"Обычные" вилы Эндрюса

Второй тип "вил", которые описывает Эндрюс, — вилы "Шиффа". Точка 1 этих вил смещена на половину расстояния 1-2 по тренду. Соответственно, наклон средней линии получается меньше. Если движение вписывается в эти вилы, вероятнее всего, движение флетовое, то есть цена находится в "коррекционном" движении.

Вилы Шиффа

И, наконец, вилы, которые я называю "обратными". Точка 1 этих вил смещена против тренда — так же точно, на половину расстояния 1-2. В эти вилы вписываются быстрые движения. Обычно они менее продолжительны по времени, но зато идут на большее расстояние по цене.

"Обратные" вилы

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

Комплект вил Эндрюса

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

//+------------------------------------------------------------------+
//| Создаёт вилы Эндрюса по заданныыми координатам                   |
//| Параметры:                                                       |
//|    _name - имя создаваемых вил                                   |
//|    _base - структура, содержащаа координаты трёх базовых точек   |
//|    _type - тип вил (SIMPLE,SHIFF,REVERCE)                        |
//+------------------------------------------------------------------+
void  CGraphics::MakePitchfork(
   string _name,           // Имя создаваемого объекта
   PitchforkPoints &_base, // Структура, описывающая точки базы вил
   PitchforkType _type     // Тип вил (SIMPLE,SHIFF,REVERCE)
)
  {
//---
   double price_first;                 // Цена первой точки (зависит от типа)
   color pitchfork_color;              // Цвет вил (зависит от типа)
   int pitchfork_width;                // Толщина линии (зависит от типа)
   ENUM_LINE_STYLE pitchfork_style;    // Стиль отрисовки линий (зависит от типа)
   double fibo_levels[] = {1};         // Добавление внешних уровней (только для MQL5)
   string fibo_descriptions[] = {""};  // Описание уровней (только для MQL5)

//--- Устанавливаем параметры, зависимыце от типа:
   if(_type == SHIFF)      // вилы Шиффа
     {
      price_first = _base.shiffMainPointPrice;
      pitchfork_color = Pitchfork_Shiff_Color;
      pitchfork_width = Pitchfork_Shiff_Width;
      pitchfork_style = Pitchfork_Shiff_Style;
     }
   else
      if(_type == REVERCE) // "обратные" вилы
        {
         price_first = _base.reverceMainPointPrice;
         pitchfork_color = Pitchfork_Reverce_Color;
         pitchfork_width = Pitchfork_Reverce_Width;
         pitchfork_style = Pitchfork_Reverce_Style;
        }
      else
        {
         // "классические" вилы
         price_first =_base.mainPointPrice;
         pitchfork_color = Pitchfork_Main_Color;
         pitchfork_width = Pitchfork_Main_Width;
         pitchfork_style = Pitchfork_Main_Style;
        }

//--- Рисуем
   ObjectCreate(0,_name,OBJ_PITCHFORK,0,
                _base.time1,price_first,
                _base.time2,_base.secondPointPrice,
                _base.time3,_base.thirdPointPrice
               );
//--- Устанавливаем параметры, общие для всех графических объектов
   CurrentObjectDecorate(
      _name,
      pitchfork_color,
      pitchfork_width,
      pitchfork_style
   );
//--- Если MQL5
#ifdef __MQL5__
//--- добавляем наружные уровни (у Эндрюса - "предупреждающие")
   SetFiboLevels(_name,fibo_levels);
   SetFiboDescriptions(_name,fibo_descriptions);
#endif

//--- Обновляем картинку графика
   ChartRedraw(0);
  }

Вторая функция рассчитывает координаты точек 1,  2, и 3 (базы) для создаваемых вил и последовательно запускает отрисовку всех трёх объектов. По этим точкам и рисуются вилы с помощью выше описанной функции CGraphics::MakePitchfork.

//+------------------------------------------------------------------+
//| Рисует "комплект" вил Эндрюса на одной базе. В комплект входят   |
//|    все три типа вил: обычные, вилы Шиффа и "обратные вилы Шиффа" |
//|    (они же - "какнал micmed-а")                                  |
//+------------------------------------------------------------------+
void CGraphics::DrawPitchforksSet(void)
  {
   bool up=true;                             // направление (мышь снизу или сверху от цены )
   double dropped_price = CMouse::Price();   // цена "точки старта" (откуда ищем)
   int dropped_bar = CMouse::Bar();          // номер бара точки старта
   string name = "";                         // имя текущего объекта
   PitchforkPoints base;                     // структура для координат базы
//---
   if(CMouse::Below())
     {
      up=false;
     }
   else
     {
      if(!CMouse::Above()) // Если указатель мыши прямо на свече - ничего не делаем
        {
         if(Print_Warning_Messages)
           {
            Print(DEBUG_MESSAGE_PREFIX,": Нужно указать точку выше или ниже экстремальной цены бара");
           }
         return;
        }
     }
//--- Находим номера баров экстремумов
   int bar_first = CUtilites::GetNearestExtremumBarNumber(
                      dropped_bar,
                      true,
                      up,
                      Pitchfork_First_Point_Left_Bars,
                      Pitchfork_First_Point_Right_Bars
                   );
   int bar_second = CUtilites::GetNearestExtremumBarNumber(
                       bar_first-1,
                       true,
                       !up,
                       Pitchfork_Second_Point_Left_Bars,
                       Pitchfork_Second_Point_Right_Bars
                    );
   int bar_third = CUtilites::GetNearestExtremumBarNumber(
                      bar_second-1,
                      true,
                      up,
                      Pitchfork_Third_Point_Left_Bars,
                      Pitchfork_Third_Point_Right_Bars
                   );
//--- Если не нашли - сообщаем об ошибке
   if(bar_first<0||bar_second<0||bar_third<0)
     {
      if(Print_Warning_Messages)
        {
         Print(DEBUG_MESSAGE_PREFIX,": Не могу найти точки, соответствующие всем условиям.");
        }
      return;
     }

//--- Заполняем структуру для базовых опорных точек
   base.mainPointPrice = up ?                               // Цена - первая базовая точка
                         iHigh(Symbol(),PERIOD_CURRENT,bar_first)
                         : iLow(Symbol(),PERIOD_CURRENT,bar_first);
   base.secondPointPrice = up ?                             // Цена - вторая базовая точка
                           iLow(Symbol(),PERIOD_CURRENT,bar_second)
                           : iHigh(Symbol(),PERIOD_CURRENT,bar_second);
   base.thirdPointPrice = up ?                              // Цена - третья базовая точка
                          iHigh(Symbol(),PERIOD_CURRENT,bar_third)
                          : iLow(Symbol(),PERIOD_CURRENT,bar_third);
   base.shiffMainPointPrice = base.mainPointPrice-          // Цена - первая точка вил Шиффа
                              (base.mainPointPrice-base.secondPointPrice)/2;
   base.reverceMainPointPrice = base.mainPointPrice+        // Цена - первая точка "обратных" вил
                                (base.mainPointPrice-base.secondPointPrice)/2;
   base.time1 = iTime(Symbol(),PERIOD_CURRENT,bar_first);   // Время первой точки
   base.time2 = iTime(Symbol(),PERIOD_CURRENT,bar_second);  // Время второй точки
   base.time3 = iTime(Symbol(),PERIOD_CURRENT,bar_third);   // время третьей точки


//--- Рисуем "обычные" вилы
   if(Pitchfork_Show_Main)
     {
      name =CUtilites::GetCurrentObjectName(allPrefixes[4]+"_main",OBJ_PITCHFORK);
      MakePitchfork(name,base,SIMPLE);
     }

//--- Рисуем вилы Шиффа
   if(Pitchfork_Show_Shiff)
     {
      name =CUtilites::GetCurrentObjectName(allPrefixes[4]+"_shiff",OBJ_PITCHFORK);
      MakePitchfork(name,base,SHIFF);
     }

//--- Рисуем "обратные" вилы
   if(Pitchfork_Show_Reverce)
     {
      name =CUtilites::GetCurrentObjectName(allPrefixes[4]+"_reverce",OBJ_PITCHFORK);
      MakePitchfork(name,base,REVERCE);
     }
//---
//ChartRedraw(0); здесь не нужен, поскольку он вызывается при рисовании каждого объекта
  }

Для описания типов вил я использую перечисление:

//+------------------------------------------------------------------+
//| Возможные типы вил Эндрюса                                       |
//+------------------------------------------------------------------+
   enum PitchforkType
     {
      SIMPLE,
      SHIFF,
      REVERCE
     };

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

//+------------------------------------------------------------------+
//|  Структура, описывающая "базу" для вил Эндрюса                   |
//+------------------------------------------------------------------+
   struct PitchforkPoints
     {
      double            mainPointPrice;        // Цена - первая базовая точка
      double            shiffMainPointPrice;   // Цена - вторая базовая точка
      double            reverceMainPointPrice; // Цена - третья базовая точка
      double            secondPointPrice;      // Цена - первая точка вил Шиффа
      double            thirdPointPrice;       // Цена - первая точка "обратных" вил
      datetime          time1;                 // Время первой точки
      datetime          time2;                 // Время второй точки
      datetime          time3;                 // Время третьей точки
     };

Ну, и, наконец, добавляем описание реакции на управляющую клавишу в файл "Shortcuts.mqh":

//+------------------------------------------------------------------+
//|                                                   Shortcuts.mqhh |
//+------------------------------------------------------------------+

   //...
//--- Нарисовать Вилы Эндрюса
         if(CUtilites::GetCurrentOperationChar(Pitchfork_Key) == lparam)
           {
            m_graphics.DrawPitchforksSet();
           }
         break;
   //...


Компилируем, проверяем...

Чтобы на графике появился комплект вил, нужно нажать клавишу "P" (Pitchfork).


Особенности рисования трендовых линий в MetaTrader

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

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

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

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

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

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

Пятница, две линии, нарисованные автоматом

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

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

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

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

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

Иногда при наклонной линии я наблюдал, как линия разворачивалась в обратную сторону. МТ не мог преобразовать координаты точки в правильную дату, поэтому... просто сбрасывал её в 0 (соответственно, получалась дата 1 января 1970 года)... Если рисовать линии по датам, такого эффекта не наступает.

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

Значит, будем делать!

Функция для получения даты в будущем

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

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

//+------------------------------------------------------------------+
//|                                              GlobalVariables.mqh |
//+------------------------------------------------------------------+

//...
//+------------------------------------------------------------------+
//| Перечисление описывает возможные варианты рассчета времени       |
//|   будущего бара                                                  |
//|      COUNT_IN_BARS - рассчитывать дату по количеству баров       |
//|      COUNT_IN_PIXELS - рассчитывать дату по количеству пикселов  |
//+------------------------------------------------------------------+
enum ENUM_FUTURE_COUNT {
   COUNT_IN_BARS,    // По барам
   COUNT_IN_PIXELS   // По пикселам
};
//...

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

Сама функция ничего не рисует, и к мыши не имеет никакого отношения. Поэтому — в утилиты её!

//+------------------------------------------------------------------+
//|                                                     Utilites.mqh |
//+------------------------------------------------------------------+

//...
class CUtilites
  {
public:
//...
   //--- Вычисляет дату в будущемотносительно стартовой даты через интервал _length, заданный в пикселах или барах
   static datetime          GetTimeInFuture(
      const datetime _start_time,                         // Время, от которого считается будущий бар
      const int _length,                                  // Длина интервала (в барах или в пикселах)
      const ENUM_FUTURE_COUNT _count_type=COUNT_IN_BARS   // Тип интервала (пикселы или бары).
   );

//...

//+------------------------------------------------------------------+
//| Функция пытается вычислить дату в будущем, используя экранные    |
//|   координаты x и y                                               |
//| Если расчет неудачен (время вышло за пределы), рассчитывает      |
//|   время с погрешностью: как сумму дат, без учета воскресений.    |
//| Параметры:                                                       |
//|   _current_time,              Исходное время,                    |
//|   _length,                    Длина интервала                    |
//|                                 (в барах или в пикселах)         |
//|   _count_type=COUNT_IN_BARS   В чём измеряется длина интервала.  |
//|      COUNT_IN_BARS - интервал задан в барах;                     |
//|      COUNT_IN_PIXELS - интервал задан в пикселах.                |
//| Возвращает:                                                      |
//|   Время в будущем, отстоящее от текущего (_current_time)         |
//|      на заданный в пикселах или свечах интервал (_length)        |
//+------------------------------------------------------------------+
datetime CUtilites::GetTimeInFuture(
   const datetime _start_time,                         // Время, от которого считается будущий бар
   const int _length,                                  // Длина интервала (в барах или в пикселах)
   const ENUM_FUTURE_COUNT _count_type=COUNT_IN_BARS   // Тип интервала (пикселы или бары).
)
  {
//---
   datetime
      future_time;      // переменная для результата
   int
      bar_distance =  GetBarsPixelDistance(),   // расстояние в пикселах между двумя соседними барами
      current_x,                                // координата х для начальной точки
      future_x,                                 // координата х результата
      current_y,                                // координата у, на результат не влияет; нужна, чтобы сработала функция конвертации
      subwindow = 0;                            // номер подокна
   double current_price;                        // любая начальная цена, на результат не влияет
   

//--- Конвертируем переданное в параметрах время в экранную координату х
   ChartTimePriceToXY(0,subwindow,_start_time,CMouse::Price(),current_x,current_y);

//--- Вычисляем точку в будущем в экранных координатах
   if(COUNT_IN_BARS == _count_type) // Если длина указана в барах...
     {
      // ... то размер интервала нужно пересчитать в пикселы...
      future_x = current_x + _length*bar_distance;
     }
   else // ... Если же длина - в пикселах...
     {
      // ... то мы можем её использовать непосредственно
      future_x = current_x + _length;
     }

//--- Конвертируем экранные координаты во время
   if(ChartGetInteger(0,CHART_WIDTH_IN_PIXELS)>=future_x) // Если всё в порядке, то...
     {
      ChartXYToTimePrice(0,future_x,current_y,subwindow,future_time,current_price);  // ...конвертируем полученное значение
     }
   else // Иначе, если время рассчитать невозможно из-за выхода запределы
     {
      future_time =        // Считаем время, как обычно, в секундах
         _start_time       // к начальному времени...
         +(                // ...добавляем..
            ((COUNT_IN_BARS == _count_type) ? _length : _length/bar_distance) // ...размер интервала в барах...
            *PeriodSeconds()  // ... умноженный на количество секунд в текущем периоде
         );
     }
//--- Возвращаем значение результата
   return future_time;
  }

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

//+------------------------------------------------------------------+
//|                                                     Utilites.mqh |
//+------------------------------------------------------------------+
//...
//+------------------------------------------------------------------+
//| Вычисляет расстояние в пикселах между двумя соседними барами     |
//+------------------------------------------------------------------+
int        CUtilites::GetBarsPixelDistance(void)
  {
//--- Вычисление расстояния
   return ((int)MathPow(2,ChartGetInteger(0,CHART_SCALE)));
  }
//...


Ограниченные горизонтальные уровни

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

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

И ещё хотелось, чтобы можно было рисовать на одном масштабе "обычную" линию-уровень и "удлинённую".

Вот что получилось:

//+------------------------------------------------------------------+
//|                                                     Graphics.mqh |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Рисует горизонтальный уровень                                    |
//| Параметры:                                                       |
//|   _multiplicator - множитель для определия длины                 |
//|                большего уровня (во сколько раз большой - больше?)|
//+------------------------------------------------------------------+
//---
void CGraphics::DrawHorizontalLevel(
   double _multiplicator // Множитель длины уровня
)
  {
//--- Описание переменных
   datetime p2_time;          // Время точки 2
   string Level_Name ="";     // Имя уровня

//Цвет текущей линии (равен общему цвету для текущего временного интервала)
   color Level_Color=CUtilites::GetTimeFrameColor(CUtilites::GetAllLowerTimeframes());
   int window = 0;            // Номер подокна, в котором рисуем
   ENUM_LINE_STYLE Current_Style = STYLE_SOLID; // Стиль линии
   int Current_Width=1;                         // Толщина линии
   int level_length = 0;                        // Длина уровня

//--- Получаем длину (в пикселах)
   if(Short_Level_Length_In_Pixels)
     {
      // Если в параметрах советника указано, что измеряем в пикселах...
      level_length = Short_Level_Length_Pix; // ...Берём длину из параметров
     }
   else
     {
      // Иначе - в параметрах указано количество свечей
      level_length = Short_Level_Length * CUtilites::GetBarsPixelDistance();
     }

//--- Устанавливаем параметры уровня
   if(_multiplicator>1) // Если уровень удлинённый...
     {
      Level_Name = CUtilites::GetCurrentObjectName(allPrefixes[7]);
      Current_Style = Long_Level_Style;
      Current_Width = Long_Level_Width;
     }
   else                 // ...И если - короткий
     {
      Level_Name = CUtilites::GetCurrentObjectName(allPrefixes[6]);
      Current_Style = Short_Level_Style;
      Current_Width = Short_Level_Width;
     }

//--- Вычисляем реальные координаты (цену и время) для второй точки
   p2_time = CUtilites::GetTimeInFuture(CMouse::Time(),level_length*_multiplicator,COUNT_IN_PIXELS);


//--- По известным координатам рисуем линию...
   TrendCreate(0,
               Level_Name,
               0,
               CMouse::Time(),
               CMouse::Price(),
               p2_time,
               CMouse::Price(),
               Level_Color,
               Current_Style,
               Current_Width
              );
//---
   
   ChartRedraw(0);
  }

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

Осталось добавить управляющие команды в файл Shortcuts.mqh:

//+------------------------------------------------------------------+
//|                                                    Shortcuts.mqh |
//+------------------------------------------------------------------+

// ...

//--- Нарисовать короткий ограниченный уровень
         if(CUtilites::GetCurrentOperationChar(Short_Level_Key) == lparam)
           {
            m_graphics.DrawHorizontalLevel(1);
           }
//--- Нарисовать удлинённый ограниченный уровень
         if(CUtilites::GetCurrentOperationChar(Long_Level_Key) == lparam)
           {
            m_graphics.DrawHorizontalLevel(Long_Level_Multiplicator);
           }
// ...

В итоге, если параметр Short_Level_Length_In_Pixels — истина (true), по нажатию клавиши S (Short) программа рисует горизонтальную палочку с длиной в пикселах, указанной в параметре Short_Level_Length_Pix.

Если же Short_Level_Length_In_Pixels == false, длина уровня измеряется в свечах и берётся из параметра Short_Level_Length.

Если нажать клавишу "L" (Long), длина линии удвоится (точнее, будет умножена на число, указанное в параметре Long_Level_Multiplicator).



Ограниченная трендовая линия

Трендовая линия в моём представлении может нести двойную нагрузку.

С одной стороны, она показывает ограничение по скорости изменения цены ("не быстрее" — если цена под линией, или "не медленнее" — если над).

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

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

Выглядит это примерно так:

Ограниченные трендовые линии

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

//+------------------------------------------------------------------+
//|                                                     Graphics.mqh |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Рисует трендовую линию по двум ближайшим экстремумам.            |
//|   ла экстремума (количество баров слева и справа) задаётся       |
//|   параметрами Fractal_Size_Left и Fractal_Size_Right             |
//|                                                                  |
//| В глобальных параметрах есть переменная "Trend_Points".          |
//|                                                                  |
//| Если значение этой переменной равно "TREND_DOTS_EXTREMUMS",      |
//|   концевые точки прямой будут лежать строго на экстремумах.      |
//| Если же значение равно "TREND_DOTS_HALF", прямая будет           |
//|   продолжена в будущее на расстояние                             |
//|   (p2-p1)*Trend_Length_Coefficient                               |
//+------------------------------------------------------------------+
void              CGraphics::DrawTrendLine(void)
  {
   int dropped_bar_number=CMouse::Bar(); // номер свечи под мышью
   int p1=0,p2=0;                        // номера первой и второй точек
   string trend_name =                   // имя линии тренда
      CUtilites::GetCurrentObjectName(allPrefixes[0],OBJ_TREND);
   double
      price1=0,   // цена первой точки
      price2=0,   // цена второй точки
      tmp_price;  // переменная для временного хранения цены
   datetime
      time1=0,    // Время первой точки
      time2=0,    // Время второй точки
      tmp_time;   // переменная для хранения времени



//--- Установка начальных параметров
   if(CMouse::Below()) // Если мышь под Low свечи
     {
      //--- Находим два экстремума снизу
      CUtilites::SetExtremumsBarsNumbers(false,p1,p2);

      //--- Определяем цены точек по Low
      price1=iLow(Symbol(),PERIOD_CURRENT,p1);
      price2=iLow(Symbol(),PERIOD_CURRENT,p2);
     }
   else // иначе
      if(CMouse::Above()) // если мышь над High свечи
        {
         //--- Находим два экстремума сверху
         CUtilites::SetExtremumsBarsNumbers(true,p1,p2);

         //--- Определяем цены точек по High
         price1=iHigh(Symbol(),PERIOD_CURRENT,p1);
         price2=iHigh(Symbol(),PERIOD_CURRENT,p2);
        }
      else
        {
         return;
        }
//--- Время первой и второй точек не зависит от направления
   time1=iTime(Symbol(),PERIOD_CURRENT,p1);
   time2=iTime(Symbol(),PERIOD_CURRENT,p2);

//--- Если требуется продлевать линиюю вправо
   if(Trend_Points == TREND_POINTS_HALF)
     {
      //--- Временно сохраняем координаты точки 2
      tmp_price = price2;
      tmp_time = time2;
      
      //--- Вычисляем время второй точки
      time2 = CUtilites::GetTimeInFuture(time1,(p1-p2)*Trend_Length_Coefficient);

      //--- Вычисляем цену второй точки
      price2 = NormalizeDouble(price1 + (tmp_price - price1)*Trend_Length_Coefficient,Digits());

      //--- Рисуем граничные уровни по цене и по времени
      DrawSimple(OBJ_HLINE,time2,price2);
      DrawSimple(OBJ_VLINE,time2,price2);
     }

//--- Отрисовка линии
   TrendCreate(0,trend_name,0,
               time1,price1,time2,price2,
               CUtilites::GetTimeFrameColor(CUtilites::GetAllLowerTimeframes()),
               0,Trend_Line_Width,false,true,m_Is_Trend_Ray
              );

//--- Перерисовка графика
   ChartRedraw(0);
  }

Основные изменения — в коде, выделенном жёлтым цветом.

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

Затем считаем цену, рисуем уровни с помощью ранее описанной  функции DrawSimple, которая находится в этом же классе, и рисуем основную прямую.

У новичков после прочтения аналогичного кода иногда остаётся вопрос: "Как функция "знает", куда надо добавлять цену: вверх - или вниз? Если линия идёт сверху вниз, то цену надо бы отнимать, а если снизу вверх — то прибавлять...".

Обратите внимание, что, поскольку нам не важно, к минимумам мы привязаны или к максимумам (мы это уже проверили, в начале функции), то направление однозначно определяется выражением  price1 + (tmp_price - price1).

Если линия идёт сверху, то price1 будет больше цены второй точки, и, значит, выражение (tmp_price - price1) будет отрицательным. Соответственно, от начальной цены мы отнимаем нужное нам расстояние.

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

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

В файле Shortcuts.mqh никаких изменений вносить не нужно. Линия всё так же рисуется с помощью клавиши "T" (Trend), и для её рисования надо просто вызвать вышеописанную функцию.

//+------------------------------------------------------------------+
//|                                                    Shortcuts.mqh |
//+------------------------------------------------------------------+

//...
//--- Нарисовать трендовую линию
         if(CUtilites::GetCurrentOperationChar(Trend_Line_Key) == lparam)
           {
            m_graphics.DrawTrendLine();
           }
//...


Рисование вертикальных уровней

Поскольку рынки склонны к трендам, и движение цен не совсем случайно (или совсем не случайно не знаю, как правильно пишется ;-) ), чаще всего для торговли можно пользоваться правилом, гласящим ,что цена всегда стремится пройти такое же расстояние, которое уже прошла. В какую сторону — это отдельный вопрос, для данной статьи — несущественный. Часто, пробивая, скажем, границу пин-бара или какой-нибудь большой свечи, цена идёт на такое же расстояние, которое было отмерено этим баром, а потом разворачивается.

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

Поэтому и я использую дробные уровни для торговли. Мне помогают уровни 7/8, остальные я использую гораздо реже... Вот для того, чтобы видеть эти уровни на экране, и создан последний из рассматриваемых в статье инструмент.

После всего разобранного функция рисования уровней кажется абсолютно тривиальной.

//+------------------------------------------------------------------+
//|                                                     Graphics.mqh |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Рисует вертикальную линию с уровнями 7/8 и 14/8 от размера       |
//|   текущей свечи                                                  |
//+------------------------------------------------------------------+
void CGraphics::DrawVerticalLevels(void)
  {
//--- Описание переменных
   string
   Current_Vertical_Name =   // Имя базовой вертикальной линии
      CUtilites::GetCurrentObjectName(allPrefixes[5]),
      Current_Level_Name =          // Имя текущего уровня
         CUtilites::GetCurrentObjectName(allPrefixes[5]+"7_8_");
   double
   Current_Line_Lenth,           // Длина текущей линии (уровня или вертикали)
   Current_Extremum,             // Рабочая экстремум (High или Low взависимости от положения мыши
   Level_Price,                  // Цена уровня
   High = iHigh(Symbol(),PERIOD_CURRENT,CMouse::Bar()), // Цена High текущей свечи
   Low =  iLow(Symbol(),PERIOD_CURRENT,CMouse::Bar());  // Цена Low текущей свечи
   int
   direction=0;                  // Знак приращения цены
   long timeframes;                 // Список рабочих таймфреймов
   datetime
   Current_Date =                // Время текущего бара
      iTime(Symbol(),PERIOD_CURRENT,CMouse::Bar()),
      Right_End_Time =              // Время правой границы уровня
         CUtilites::GetTimeInFuture(Current_Date,Short_Level_Length);

//--- Вычисление длины свечи
   Current_Line_Lenth = (High-Low)*2;

//--- Инициализация основных переменных в зависимости от нужного направления рисования
   if(CMouse::Above()) // Если мышка сверху
     {
      Current_Extremum = High;   // основная цена - High
      direction = -1;            // направление рисования - вниз
     }
   else              // Иначе
     {
      if(CMouse::Below()) // Если мышка снизу
        {
         Current_Extremum = Low; // основная цена - Low
         direction = 1;          // направление рисования - вверх
        }
      else         // если мышка посредине свечи - выходим
        {
         return;
        }
     }

//--- Вертикальная линия
   TrendCreate(0,
               Current_Vertical_Name,
               0,
               Current_Date,
               Current_Extremum,
               Current_Date,
               Current_Extremum+(Current_Line_Lenth*2)*direction,
               CUtilites::GetTimeFrameColor(CUtilites::GetAllLowerTimeframes()),
               Vertical_With_Short_Levels_Style,
               Vertical_With_Short_Levels_Width
              );
//--- Первый уровень (7/8)
   Level_Price = Current_Extremum+(Current_Line_Lenth*Vertical_Short_Level_Coefficient)*direction;
   TrendCreate(0,
               Current_Level_Name,
               0,
               Current_Date,
               Level_Price,
               Right_End_Time,
               Level_Price,
               CUtilites::GetTimeFrameColor(CUtilites::GetAllLowerTimeframes()),
               Short_Level_7_8_Style,
               Short_Level_7_8_Width
              );
//--- Второй уровень (14/8)
   Current_Level_Name = CUtilites::GetCurrentObjectName(allPrefixes[5]+"14_8_");
   Level_Price = Current_Extremum+(Current_Line_Lenth*2*Vertical_Short_Level_Coefficient)*direction;
   TrendCreate(0,
               Current_Level_Name,
               0,
               Current_Date,
               Level_Price,
               Right_End_Time,
               Level_Price,
               CUtilites::GetTimeFrameColor(CUtilites::GetAllLowerTimeframes()),
               Short_Level_14_8_Style,
               Short_Level_14_8_Width
              );
  }

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

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

Итоговая фигура выглядит примерно так, как показано на рисунке.

В файле Shortcuts.mqh добавляем управляющую конструкцию:

//+------------------------------------------------------------------+
//|                                                    Shortcuts.mqh |
//+------------------------------------------------------------------+

//...
//--- Нарисовать вертикаль с уровнями 7/8
         if(CUtilites::GetCurrentOperationChar(Vertical_With_Short_Levels_Key)  == lparam)
           {
            m_graphics.DrawVerticalLevels();
           }
         break;
Клавиша для рисования - V (Vertical).

Клавиши, используемые в текущей реализации библиотеки

Действие
 Клавиша От английского слова
 Перейти на таймфрейм вверх по основным периодам (из панели периодов)  U  Up
 Перейти на таймфрейм вниз  D  Down
 Смена Z-уровня графика (график сверху или снизу объектов)  Z  Z-order
 Рисование наклонной трендовой линии по двум ближайшим к мыши однонаправленным экстремумам  T  Trend line
 Переключение режима луча для новых прямых
 R  Ray
 Рисование простой вертикальной черты
 I(i) [Only visual  vertical]
 Рисование простой горизонтальной черты
 H  Horizontal
 Рисование комплекта вил Эндрюса
 P  Pitchfork
 Рисование веера Фибоначчи (VFun)
 F  Fun
 Рисование короткого горизонтального уровня
 S  Short
 Рисование удлинённого горизонтального уровня
 L  Long
 Рисование вертикальной черты с отметками уровней
 V  Vertical

Заключение

Как и многие другие, я надеюсь, что статья будет вам полезной, но не гарантирую вообще ничего :-) Инструмент получился очень гибким, пригодным для работы на любых рынках, с которыми лично я сталкивался, но если все, кто прочтёт эту статью, будут его использовать с настройками по умолчанию, вероятно, какие-то рынки могут поменяться... Вероятно, не слишком существенно, ведь изменения — это суть рынка...

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

И получайте стабильные профиты!

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (4)
Валерий Кожухарь
Валерий Кожухарь | 28 ноя 2020 в 18:09
Спасибо за работу и открытость, очень удобный иструмент! Подскажите какие изменения кода необходимы, чтобы при рисовании короткого или удлиненного горизонтального уровня размер менялся только по горизонтали, есть необходимость продлевать линии но без смещения по вертикали ?
Oleh Fedorov
Oleh Fedorov | 29 ноя 2020 в 01:30
Валерий Кожухарь:
Спасибо за работу и открытость, очень удобный иструмент! Подскажите какие изменения кода необходимы, чтобы при рисовании короткого или удлиненного горизонтального уровня размер менялся только по горизонтали, есть необходимость продлевать линии но без смещения по вертикали ?

Если я правильно понял вопрос, то кнопка "L" или "S" Вам поможет. Параметр в настройках "Длина короткого уровня" (в пикселах и в барах). Правда, сейчас этот же параметр влияет и на вертикальную линию с урровнями (точнее, собственно, на длину уровней). Есть еще "Коэффициент для длинного уровня" - по умолчанию стоИт значение 2.

Ну, и, наконец, можно тупо поменять масштаб графика. При уменьшении масштаба линия, нарисованная клавишей "L" или "S" - удлиняется. На вертикалях - остаётся того же размера.

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

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

Jebs siptungkha
Jebs siptungkha | 22 фев 2021 в 05:09

hello Oleh

it seems that this is what was looking for 

but unable to install the files

i inserted shortcuts folder files to scripts, experts and include folders

and not working yet.


is there any instructions on how to install them properly?

Oleh Fedorov
Oleh Fedorov | 24 фев 2021 в 07:35
Jebs siptungkha:

hello Oleh

...

is there any instructions on how to install them properly?

Nothing special.

In archive there are two folders: MQL4 and MQL5.

As usual, copy one depending on your version into your MQL data   folder (File -> Open Data Folder) and then restart terminal (or select Tools -> MetaQuotes Languge Editor and compile file Experts\Shortcuts\Shortcuts.mq*).

Return to terminal and put expert on the needed chart.

P.S. If you use scripts from first article and prefer to compile expert "by hand", you need to compile an each script too.

Нейросети — это просто (Часть 6): Эксперименты с коэффициентом обучения нейронной сети Нейросети — это просто (Часть 6): Эксперименты с коэффициентом обучения нейронной сети

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

Работа с таймсериями в библиотеке DoEasy (Часть 54): Классы-наследники абстрактного базового индикатора Работа с таймсериями в библиотеке DoEasy (Часть 54): Классы-наследники абстрактного базового индикатора

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

Работа с таймсериями в библиотеке DoEasy (Часть 55): Класс-коллекция индикаторов Работа с таймсериями в библиотеке DoEasy (Часть 55): Класс-коллекция индикаторов

В статье продолжим развитие классов объектов-индикаторов и их коллекции. Создадим для каждого объекта-индикатора его описание и скорректируем класс-коллекцию для безошибочного хранения и получения объектов-индикаторов из списка-коллекции.

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

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