
Набор инструментов для ручной разметки графиков и торговли (Часть I). Подготовка - описание структуры и класс вспомогательных функций
Введение
Я — "ручник". В смысле — анализирую графики не с помощью сложных формул и индикаторов, а "вручную", графически. Это даёт мне возможность быть более гибким в торговле, замечать некоторые вещи, которые формализовать довольно сложно или не целесообразно, легко переключаться между таймфреймами, если я вижу, что движение усиливается или замедляется, знать вероятное поведение цены задолго до того, как буду входить в сделку.
В основном для анализа я использую трендовые линии в разных сочетаниях (вилы, веера, уровни и т.д.). Вот я и создал удобный для меня инструмент, который позволяет рисовать разные трендовые очень быстро, буквально в одно нажатие клавиши. И теперь этим инструментом хочу поделиться с сообществом.
Видеопрезентация. Как это работает
Общая концепция. Постановка задачи
Итак, необходим инструмент, который помогает с помощью сочетаний клавиш выполнять наиболее частые операции, которые я выполняю.
Что это за задачи? Мне хочется, например:
- чтобы простая горизонтальная линия рисовалась при нажатии клавиши "H" (от английского "Horizontal" — горизонталь), а вертикальная — по нажатию клавиши "i" ([ай], которая у меня ассоциируется по внешнему виду);
- рисовать горизонтальные уровни определённой длины (НЕ бесконечные) от любой точки графика;
- рисовать вертикальные уровни на определённом (произвольном) расстоянии от начальной точки;
- переключать клавишами таймфрейм и порядок расположения слоёв на графике;
- веер Фибоначчи с заданными мною уровнями по ближайшим экстремумам;
- трендовые линии по ближайшим экстремумам — их длина должна быть кратной расстоянию между экстремумами, хотя в некоторых случаях линия может быть и лучом;
- вилы Эндрюса разных видов (обычные, Шиффа, "обратные вилы Шиффа" — для быстрых трендов) (см. видео);
- для экстремумов вил, прямых и вееров должна быть возможность настраивать "порядок" экстремумов (количество баров — отдельно справа и отдельно слева);
- графический интерфейс, позволяющий настраивать параметры нужных линий и экстремумов, не открывая главное окно настроек;
- набор функций для сопровождения ордера: открытие ордеров в процентах от депозита, автоматический стоп при открытии ордера по рынку или срабатывании отложки без стопа, частичное закрытие позиций по уровням, для некоторых стратегий — трал.
Для большинства этих задач можно, конечно, создать скрипты. У меня есть несколько, и я ими поделюсь во вложении. Но мне ближе другой подход.
В любом советнике или индикаторе можно создать метод OnChartEvent, в котором и будет описана реакция на любое событие: нажатие клавиш, движение мыши, создание графического объекта и его удаление.
Поэтому я решил, что программа будет выполнена в виде подключаемых файлов. Все функции и переменные распределены по нескольким классам, чтобы к ним удобнее было обращаться. Сейчас мне классы нужны только для удобства группировки функций, поэтому в первых реализациях точно не будет чего-то сложного типа наследования или каких-нибудь "фабрик". Это просто сборник.
И, конечно, я хочу, чтобы класс получился кросс-платформенным и работал похожим образом как в MQL4, так и в MQL5.
Структура программы
Библиотека содержит пять взаимосвязанных файлов. Все файлы располагаются в одной папке "Shortcuts" в каталоге Include. Их имена видны на рисунке: GlobalVariables.mqh, Graphics.mqh, Mouse.mqh, Shortcuts.mqh, Utilites.mqh.
Главный файл библиотеки(Shortcuts.mqh)
Главный файл программы — "Shortcuts.mqh". В нём будет прописана логика реагирования на клавиши. И именно этот файл будет подключен к эксперту. В него также будут подключены все вспомогательные файлы.
//+------------------------------------------------------------------+ //| Shortcuts.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://www.mql5.com/ru/articles/7468 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://www.mql5.com/ru/articles/7468" #include "GlobalVariables.mqh" #include "Mouse.mqh" #include "Utilites.mqh" #include "Graphics.mqh" //+------------------------------------------------------------------+ //| Основной управляющий класс программы. Именно его нужно подключать| //| в советник. | //+------------------------------------------------------------------+ class CShortcuts { private: CGraphics m_graphics; // Object for drawing m_graphics public: CShortcuts(); void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam); }; //+------------------------------------------------------------------+ //| Конструктор по умолчанию | //+------------------------------------------------------------------+ CShortcuts::CShortcuts(void) { ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,true); } //+------------------------------------------------------------------+ //| Функция обработки событий | //+------------------------------------------------------------------+ void CShortcuts::OnChartEvent( const int id, const long &lparam, const double &dparam, const string &sparam ) { //--- // Здесь будет описание событий нажатия кнопок клавиатуры и движений // мыши // ... } } CShortcuts shortcuts;
Файл содержит описание класса CShortcuts.
В начала файла подключаются все вспомогательные классы.
В классе всего два метода: первый — обработчик событий OnChartEvent, в котором, собственно, и будет происходить обработка событий клавиш и движения мыши, второй — конструктор по умолчанию, в котором разрешается обрабатывать движения мыши.
После описания класса создаётся переменная shortcuts,которую необходимо использовать в методе OnChartEvent основного эксперта при подключении библиотеки.
Само подключение требует две строки:
//+------------------------------------------------------------------+ //| Основной эксперт (файл "Shortcuts-Main-Expert.mq5") | //+------------------------------------------------------------------+ #include <Shortcuts\Shortcuts.mqh> // ... //+------------------------------------------------------------------+ //| Функция ChartEvent | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- shortcuts.OnChartEvent(id,lparam,dparam,sparam); }
Первая строка подключает файл класса, вторая — передаёт классу управление обработкой событий.
После этого советник будет готов к работе, его можно будет скомпилировать и сразу начать рисовать линии.
Класс обработки движений мыши
Класс, который будет хранить все основные параметры текущего положения курсора: координаты X, Y — в пикселях и ценах/времени, номер бара, на котором находится указатель, ну, и так далее, хранится в файле "Mouse.mqh".
//+------------------------------------------------------------------+ //| Mouse.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://www.mql5.com/ru/articles/7468 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://www.mql5.com/ru/articles/7468" //+------------------------------------------------------------------+ //| Класс обработки движений мыши | //+------------------------------------------------------------------+ class CMouse { //--- Members private: static int m_x; // X static int m_y; // Y static int m_barNumber; // Номер бара static bool m_below; // Признак курсора над ценой static bool m_above; // Признак курсора под ценой static datetime m_currentTime; // Текущее время static double m_currentPrice;// Текущая цена //--- Methods public: //--- Запоминает основные параметры курсора мыши static void SetCurrentParameters( const int id, const long &lparam, const double &dparam, const string &sparam ); //--- Возвращает координату X (в пикселах) текущего положения курсора static int X(void) {return m_x;} //--- Возвращает координату Y (в пикселах) текущего положения курсора static int Y(void) {return m_y;} //--- Возвращает цену текущего положения курсора static double Price(void) {return m_currentPrice;} //--- Возвращает время текущего положения курсора static datetime Time(void) {return m_currentTime;} //--- Возвращает номер бара текущего положения курсора static int Bar(void) {return m_barNumber;} //--- Возвращает флаг, показывающий, что цена находится ниже Low текущего бара static bool Below(void) {return m_below;} //--- Возвращает признак того, что цена находится выше High текущего бара static bool Above(void) {return m_above;} }; //--- int CMouse::m_x=0; int CMouse::m_y=0; int CMouse::m_barNumber=0; bool CMouse::m_below=false; bool CMouse::m_above=false; datetime CMouse::m_currentTime=0; double CMouse::m_currentPrice=0; //+------------------------------------------------------------------+ //| Запоминает основные параметры при движении мыши: координаты | //| курсора в пикселях и в ценах/времени, находится ли курсор над | //| или под ценами. | //|+-----------------------------------------------------------------+ static void CMouse::SetCurrentParameters( const int id, const long &lparam, const double &dparam, const string &sparam ) { //--- int window = 0; //--- ChartXYToTimePrice( 0, (int)lparam, (int)dparam, window, m_currentTime, m_currentPrice ); m_x=(int)lparam; m_y=(int)dparam; m_barNumber=iBarShift( Symbol(), PERIOD_CURRENT, m_currentTime ); m_below=m_currentPrice<iLow(Symbol(),PERIOD_CURRENT,m_barNumber); m_above=m_currentPrice>iHigh(Symbol(),PERIOD_CURRENT,m_barNumber); } //+------------------------------------------------------------------+
Данный класс может быть использован из любого места программы, поскольку его методы объявлены статическими. Не обязательно создавать экземпляр этого класса, чтобы им пользоваться.
Описание блока настроек эксперта. Файл "GlobalVariables.mqh"
Все настройки, доступные пользователю, хранятся в файле "GlobalVariables.mqh".
В следующей статье будут описаны гораздо больше настроек, так как появится больше объектов для рисования.
А сейчас — код того, что используется в текущей версии:
//+------------------------------------------------------------------+ //| GlobalVariables.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://www.mql5.com/ru/articles/7468 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://www.mql5.com/ru/articles/7468" //+------------------------------------------------------------------+ //| Файл описания параметров, доступных пользователю | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Настройки клавиш | //+------------------------------------------------------------------+ 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 Dimensions="=== Настройки размеров ==="; input int Trend_Line_Width=2; // Ширина трендовой линии //+------------------------------------------------------------------+ //| Стили отображения | //+------------------------------------------------------------------+ input string Styles="=== Стили отображения ==="; input ENUM_LINE_STYLE Trend_Line_Style=STYLE_SOLID; // Стиль Трендовая //+------------------------------------------------------------------+ //| Остальные параметры | //+------------------------------------------------------------------+ input string Others="=== Остальные параметры ==="; 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; // Размер фрактала справа //+------------------------------------------------------------------+ //| Префиксы имён рисуемых фигур (меняются только в коде, | //| в параметрах советника не видны) | //+------------------------------------------------------------------+ // string Prefixes="=== Prefixes ==="; string Trend_Line_Prefix="Trend_"; // Префикс трендовой //+------------------------------------------------------------------+ //| Цвета объектов одного таймфрейма (меняются только в коде, | //| в параметрах советника не видны) | //+------------------------------------------------------------------+ // string TimeframeColors="=== Time frame colors ==="; 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__," === " //--- Константы для описания основных таймфреймов при рисовании #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 //+------------------------------------------------------------------+
Вспомогательные функции
В программе есть множество функций, которые непосредственно к рисованию отношения не имеют, но помогают найти экстремумы, переключить таймфреймы — и так далее. Все они вынесены в файл "Utilites.mqh".
Заголовок файла Utilites.mqh
//+------------------------------------------------------------------+ //| Utilites.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://www.mql5.com/ru/articles/7468 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://www.mql5.com/ru/articles/7468" //+------------------------------------------------------------------+ //| Класс для описания вспомогательных функций | //+------------------------------------------------------------------+ class CUtilites { public: //--- Изменяет таймфрейм на следующий в панели инструментов static void ChangeTimeframes(bool isUp); //--- Преобразует строковые константы команд в коды клавиш static int GetCurrentOperationChar(string keyString); //--- Переключает слои в графиках (график поверх всех объектов) static void ChangeChartZIndex(void); //--- Возвращает номер ближайшего экстремального бара static int GetNearestExtremumBarNumber( int starting_number=0, bool is_search_right=false, bool is_up=false, int left_side_bars=1, int right_side_bars=1, string symbol=NULL, ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT ); //--- Возвращает цвет для текущего таймфрейма static color GetTimeFrameColor(long allDownPeriodsValue); //--- Возвращает список всех таймфреймов, лежащих ниже текущего (включая текущий) static long GetAllLowerTimeframes(int NeededTimeframe=PERIOD_CURRENT); //--- Координаты прямой. Заносит в точки p1 и p2 номера экстремалных баров static void SetExtremumsBarsNumbers(bool _is_up,int &p1, int &p2); //--- Преобразование строки в массив чисел с плавающей точкой static void StringToDoubleArray( string _haystack, double &_result[], const string _delimiter="," ); //--- Определяет имя текущего объекта static string GetCurrentObjectName( const string _prefix, const ENUM_OBJECT _type=OBJ_TREND, int _number = -1 ); //--- Получает номер следующего объекта static int GetNextObjectNumber( const string _prefix, const ENUM_OBJECT _object_type, bool true ); //--- Возвращает расстояние в экранных пикселях между соседними барами static int GetBarsPixelDistance(void); //--- Конвертация числового значения таймфрейма в его строковое имя static string GetTimeframeSymbolName( ENUM_TIMEFRAMES _timeframe=PERIOD_CURRENT // Нужный таймфрейм ); };
Функция, последовательно изменяющая период графика
Первые функции в этом файле тривиальны. Например, функция, изменяющая периоды текущего графига последовательно, друг за другом, выглядит следующим образом:
//+------------------------------------------------------------------+ //| Последовательно изменяет период текущего графика | //| | //| В данной реализации используются только таймфреймы, выведенные | //| в стандартную панель. | //| | //| Параметры: | //| _isUp - направление смены таймфрейма: вверх (true) | //| или вниз (false) | //+------------------------------------------------------------------+ static void CUtilites::ChangeTimeframes(bool _isUp) { ENUM_TIMEFRAMES timeframes[] = { PERIOD_CURRENT, PERIOD_M1, PERIOD_M5, PERIOD_M15, PERIOD_M30, PERIOD_H1, PERIOD_H4, PERIOD_D1, PERIOD_W1, PERIOD_MN1 }; int period = Period(); int shift = ArrayBsearch(timeframes,period); if(_isUp && shift < ArraySize(timeframes)-1) { ChartSetSymbolPeriod(0,NULL,timeframes[++shift]); } else if(!_isUp && shift > 1) { ChartSetSymbolPeriod(0,NULL,timeframes[--shift]); } }
Для начала в этой функции описывается массив всех таймфреймов, указанных в стандартной панели управления по умолчанию. Если лично вам необходимо переключаться между всеми доступными в MetaTrader 5 временными периодами, нужно просто дописать соответствующие константы в нужные места массива. Правда, в этом случае может потеряться обратная совместимость, то есть библиотека может перестать работать с MQL4.
Дальше используем стандартные функции, чтобы получить текущий период и найти его в списке.
А потом для переключения временных периодов графика используется стандартная функция ChartSetSymbolPeriod, в которую передаётся период, следующий за текущим.
По остальным функциям особо комментировать вне кода не вижу смысла. Просто код.
Несколько простых функций
//+------------------------------------------------------------------+ //| Преобразует строковые константы команд в коды клавиш | //+------------------------------------------------------------------+ static int CUtilites::GetCurrentOperationChar(string keyString) { string keyValue = keyString; StringToUpper(keyValue); return(StringGetCharacter(keyValue,0)); }
//+------------------------------------------------------------------+ //| Переключает расположение графика поверх других объектов | //+------------------------------------------------------------------+ static void CUtilites::ChangeChartZIndex(void) { ChartSetInteger( 0, CHART_FOREGROUND, !(bool)ChartGetInteger(0,CHART_FOREGROUND) ); ChartRedraw(0); }
//+------------------------------------------------------------------+ //| Возвращает строковое имя для любого стандартного таймфрейма | //| Параметры: | //| _timeframe - числовое значение ENUM_TIMEFRAMES, для которого | //| нужно подобрать строковое название | //| Возвращаемое значение: | //| строковое название нужного таймфрейма | //+------------------------------------------------------------------+ static string CUtilites::GetTimeframeSymbolName( ENUM_TIMEFRAMES _timeframe=PERIOD_CURRENT // Нужный таймфрейм ) { ENUM_TIMEFRAMES current_timeframe; // текущий таймфрейм string result = ""; //--- if(_timeframe == PERIOD_CURRENT) { current_timeframe = Period(); } else { current_timeframe = _timeframe; } //--- switch(current_timeframe) { case PERIOD_M1: return "M1"; case PERIOD_M2: return "M2"; case PERIOD_M3: return "M3"; case PERIOD_M4: return "M4"; case PERIOD_M5: return "M5"; case PERIOD_M6: return "M6"; case PERIOD_M10: return "M10"; case PERIOD_M12: return "M12"; case PERIOD_M15: return "M15"; case PERIOD_M20: return "M20"; case PERIOD_M30: return "M30"; case PERIOD_H1: return "H1"; case PERIOD_H2: return "M1"; case PERIOD_H3: return "H3"; case PERIOD_H4: return "H4"; case PERIOD_H6: return "H6"; case PERIOD_H8: return "H8"; case PERIOD_D1: return "D1"; case PERIOD_W1: return "W1"; case PERIOD_MN1: return "MN1"; default: return "Unknown"; } }
//+------------------------------------------------------------------+ //| Возвращает стандартный цвет для текущего таймфрейма | //+------------------------------------------------------------------+ static color CUtilites::GetTimeFrameColor(long _all_down_periods_value) { if(Is_Different_Colors) { switch((int)_all_down_periods_value) { case OBJ_PERIOD_M1: return (m1_color); case PERIOD_LOWER_M5: return (m5_color); case PERIOD_LOWER_M15: return (m15_color); case PERIOD_LOWER_M30: return (m30_color); case PERIOD_LOWER_H1: return (h1_color); case PERIOD_LOWER_H4: return (h4_color); case PERIOD_LOWER_D1: return (d1_color); case PERIOD_LOWER_W1: return (w1_color); case OBJ_ALL_PERIODS: return (mn1_color); default: return (common_color); } } else { return (common_color); } }
Функция поиска экстремумов и её применение
Следующая функция помогает искать экстремумы. В зависимости от параметров экстремумы могут быть найдены как справа, так и слева от указателя мыши, и можно задавать, сколько баров должно быть слева от экстремума, и сколько — справа.
//+------------------------------------------------------------------+ //| Возвращает номер бара ближайшего "фрактала" в выбранном | //| направлении | //| Параметры: | //| starting_number - номер бара, с которого стартует поиск | //| is_search_right - Ищем вправо (true) или влево(false)? | //| is_up - если "true" - поиск по уровням High, иначе - Low | //| left_side_bars - количество баров слева | //| right_side_bars - количество баров справа | //| symbol - имя символа для поиска | //| timeframe - временной интервал | //| Возвращаемое значение: | //| номер ближайшего к starting_number экстремума, | //| соответствующего заданным параметрам | //+------------------------------------------------------------------+ static int CUtilites::GetNearestExtremumBarNumber( int starting_number=0, // Номер начального бара const bool is_search_right=false, // Направление вправо const bool is_up=false, // Искать по "верхам" (High) const int left_side_bars=1, // Количество баров слева const int right_side_bars=1, // Количество баров справа const string symbol=NULL, // Нужный символ const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT // Нужный таймфрейм ) { //--- int i, nextExtremum, sign = is_search_right ? -1 : 1; //--- Если стартовый бар указан неверно //--- (лежит за границами текущего графика) //--- и поиск - в сторону границы. if((starting_number-right_side_bars<0 && is_search_right) || (starting_number+left_side_bars>iBars(symbol,timeframe) && !is_search_right) ) { //--- Необходимо вывести сообщение об ошибке if(Print_Warning_Messages) { Print(DEBUG_MESSAGE_PREFIX, "Не могу найти экстремум: ", "неверное направление"); Print("left_side_bars = ",left_side_bars,"; ", "right_side_bars = ",right_side_bars); } return (-2); } else { //--- иначе - направление позволяет выбрать правильный бар. //--- бежим по всем барам в нужном направлении, //--- пока находимся за границами известного графика while((starting_number-right_side_bars<0 && !is_search_right) || (starting_number+left_side_bars>iBars(symbol,timeframe) && is_search_right) ) { starting_number +=sign; } } //--- i=starting_number; //--- Подготовка закончена. Приступаем к поиску while(i-right_side_bars>=0 && i+left_side_bars<iBars(symbol,timeframe) ) { //--- В зависимости от уровня проверяем нужный экстремум if(is_up) { //--- ...либо верхний... nextExtremum = iHighest( Symbol(), Period(), MODE_HIGH, left_side_bars+right_side_bars+1, i-right_side_bars ); } else { //--- ...либо нижний... nextExtremum = iLowest( Symbol(), Period(), MODE_LOW, left_side_bars+right_side_bars+1, i-right_side_bars ); } if(nextExtremum == i) // Если текущий бар - экстремум, { return nextExtremum; // то задача решена } else // Иначе - продолжаем смещать счётчик проверяемой свечи в нужную сторону if(is_search_right) { if(nextExtremum<i) { i=nextExtremum; } else { i--; } } else { if(nextExtremum>i) { i=nextExtremum; } else { i++; } } } //--- Если добежали до края и никаких экстремумов не нашли, if(Print_Warning_Messages) { //--- нужно сообщить об ошибке. Print(DEBUG_MESSAGE_PREFIX, "Не могу найти экстремум: ", "ошибочно выбрана начальная точка либо граничные условия."); Print("left_side_bars = ",left_side_bars,"; ", "right_side_bars = ",right_side_bars); } return (-1); }
Для рисования трендовых линий нужна функция, которая находит два ближайших экстремума справа от мышки. Эта функция может использовать предыдущую:
//+------------------------------------------------------------------+ //| Находит 2 ближайших экстремума справа от текущей позиции | //| указателя мыши | //| Параметры: | //| _is_up - поиск по High (true) или по Low (false) | //| int &_p1 - номер бара первой точки | //| int &_p2 - номер бара второй точки | //+------------------------------------------------------------------+ static void CUtilites::SetExtremumsBarsNumbers( bool _is_up, // поиск по High (true) или по Low (false) int &_p1, // номер бара первой точки int &_p2 // номер бара второй точки ) { int dropped_bar_number=CMouse::Bar(); //--- _p1=CUtilites::GetNearestExtremumBarNumber( dropped_bar_number, true, _is_up, Fractal_Size_Left, Fractal_Size_Right ); _p2=CUtilites::GetNearestExtremumBarNumber( _p1-1, // Бар левее предыдущего найденного экстремума true, _is_up, Fractal_Size_Left, Fractal_Size_Right ); if(_p2<0) { _p2=0; } }
Генерация имён объектов
Для того, чтобы рисовать одинаковые объекты сериями, им надо давать уникальные имена. Для этого проще всего взять какой-нибудь префикс, соответствующий данному типу объектов, и добавить к нему уникальный номер. Префиксы для разных типов объектов перечислены в файле "GlobalVariables.mqh".
Номера же генерируются соответствующей функцией.
//+------------------------------------------------------------------+ //| Возвращает номер следующего объекта в серии | //| Параметры: | //| prefix - префикс имени для данной группы объектов. | //| object_type - тип тех объектов, которые нужно искать. | //| only_prefixed - если "false", то поиск - по всем объектам | //| данного типа, "true" - только по объектам | //| с указанным префиксом | //| Возвращаемое значение: | | //| номер следующего объекта серии. Если ищем по префиксам, | //| и существующая нумерация имеет "пробел", следующий номер | //| будет в начале этого "пробела". | //+------------------------------------------------------------------+ int CUtilites::GetNextObjectNumber( const string prefix, const ENUM_OBJECT object_type, bool true ) { int count = ObjectsTotal(0,0,object_type), i, current_element_number, total_elements = 0; string current_element_name = "", comment_text = ""; //--- if(only_prefixed) { for(i=0; i<count; i++) { current_element_name=ObjectName(0,i,0,object_type); if(StringSubstr(current_element_name,0,StringLen(prefix))==prefix) { current_element_number= (int)StringToInteger( StringSubstr(current_element_name, StringLen(prefix), -1) ); if(current_element_number!=total_elements) { break; } total_elements++; } } } else { total_elements = ObjectsTotal(0,-1,object_type); do { current_element_name = GetCurrentObjectName( prefix, object_type, total_elements ); if(ObjectFind(0,current_element_name)>=0) { total_elements++; } } while(ObjectFind(0,current_element_name)>=0); } //--- return(total_elements); }
В коде реализовано два алгоритма поиска. Первый (основной для данной библиотеки) — перебирает все объекты, соответствующие типу и имеющие указанный префикс. Как только находит свободный номер, возвращет его пользователю. Это позволяет закрывать "дыры" в нумерации.
Однако такой алгоритм не очень подходит для случая, когда могут быть несколько объектов с одинаковым номером, но разными суффиксами. Я в ранних версиях, когда ещё пользовался для рисования скриптами, так именовал комплекты вил.
Поэтому в библиотеке реализован и второй способ поиска. Берётся общее число объектов данного типа, а затем проверяется, есть ли имя, которое начинается на тот же префикс и имеет тот же номер. Если да, номер увеличивается на 1 до тех пор, пока не найдётся свободное значение.
Ну, и когда номер уже есть (или легко может быть получен с помощью функции), составить имя становится совсем просто.
//+------------------------------------------------------------------+ //| Генерирует имя текущего элемента | //| Параметры: | //| _prefix - префикс имени для данной группы объектов. | //| _type - тип тех объектов, которые нужно искать. | //| _number - номер текущего объекта, если он уже готов. | //| Возвращаемое значение: | //| строка имени текущего объекта. | //+------------------------------------------------------------------+ string CUtilites::GetCurrentObjectName( string _prefix, ENUM_OBJECT _type=OBJ_TREND, int _number = -1 ) { int Current_Line_Number; //--- Дополнение к префиксу - текущий таймфрейм string Current_Line_Name=IntegerToString(PeriodSeconds()/60)+"_"+_prefix; //--- Получаем номер элемента if(_number<0) { Current_Line_Number = GetNextObjectNumber(Current_Line_Name,_type); } else { Current_Line_Number = _number; } //--- Генерируем имя Current_Line_Name +=IntegerToString(Current_Line_Number,4,StringGetCharacter("0",0)); //--- return (Current_Line_Name); }
Дистанция между соседними барами (в пикселах)
Иногда бывает нужно вычислить расстояние до определённой точки в будущем. Один из самых надёжных способов этого достичь d — рассчитать расстояние в пикселах между двумя соседними барами и затем умножить его на нужный коэффициент (сколько баров хотим отступить).
Дистанцию между соседними барами можно вычислить с помощью следующей функции:
//+------------------------------------------------------------------+ //| Вычисляет расстояние в пикселах между двумя соседними барами | //+------------------------------------------------------------------+ int CUtilites::GetBarsPixelDistance(void) { double price; // произвольная цена на графике (нужна для // стандартных функций вычисления координат datetime time1,time2; // время текущей и соседней свечей int x1,x2,y1,y2; // экранные координаты двух точек // на соседних свечах int deltha; // результат - искомое расстояние //--- Начальные установки price = iHigh(Symbol(),PERIOD_CURRENT,CMouse::Bar()); time1 = CMouse::Time(); //--- Получение времени соседней свечи if(CMouse::Bar()<Bars(Symbol(),PERIOD_CURRENT)){ // если в середине графика, time2 = time1+PeriodSeconds(); // берём свечу слева, } else { // иначе time2 = time1; time1 = time1-PeriodSeconds(); // берём свечу справа } //--- Преобразование координат из значений цена/время в экранные пикселы ChartTimePriceToXY(0,0,time1,price,x1,y1); ChartTimePriceToXY(0,0,time2,price,x2,y2); //--- Вычисление расстояния deltha = MathAbs(x2-x1); //--- return (deltha); }
Функция, преобразующая строку в массив чисел double
Для настроек уровней Фибоначчи с помощью параметров советника проще всего использовать строки, которые состоят из значений, разделённых запятой. Однако MQL требует, чтобы для задания уровня использовались числа double.
Вытащить числа из строки поможет следующая функция.
//+------------------------------------------------------------------+ //| Преобразует строку с разделителями в массив двоичных (double) | //| чисел | //| Параметры: | //| _haystack - исходная строка для конвертации | //| _result[] - результирующий массив | //| _delimiter - символ-разделитель | //+------------------------------------------------------------------+ static void CUtilites::StringToDoubleArray( string _haystack, // исходная строка double &_result[], // массив результатов const string _delimiter="," // символ-разделитель ) { //--- string haystack_pieces[]; // Массив фрагментов строки int pieces_count, // Количество фрагментов i; // Счётчик string current_number=""; // Текущий фрагмент строки (предполагаемое число) //--- Разбиение строки на фрагменты pieces_count=StringSplit(_haystack,StringGetCharacter(_delimiter,0),haystack_pieces); //--- Конвертация if(pieces_count>0) { ArrayResize(_result,pieces_count); for(i=0; i<pieces_count; i++) { StringTrimLeft(haystack_pieces[i]); StringTrimRight(haystack_pieces[i]); _result[i]=StringToDouble(haystack_pieces[i]); } } else { ArrayResize(_result,1); _result[0]=0; } }
Класс рисования: пример использования функций-утилит
Статья получается очень объёмной, поэтому описание большей части функций рисования я вынесу в следующую часть. Однако, чтобы протестировать возможности некоторых уже созданных функций, приведу здесь код, который помогает рисовать простые (по двум соседним экстремумам) прямые линии.
Заголовок файла рисования графики Graphics.mqh
//+------------------------------------------------------------------+ //| Graphics.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://www.mql5.com/ru/articles/7468 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://www.mql5.com/ru/articles/7468" //+------------------------------------------------------------------+ //| Класс для рисования графических объектов | //+------------------------------------------------------------------+ class CGraphics { //--- Поля private: bool m_Is_Trend_Ray; bool m_Is_Change_Timeframe_On_Create; //--- Методы private: //--- Устанавливает общие параметры для любого вновь созданного объекта void CurrentObjectDecorate( const string _name, const color _color=clrNONE, const int _width = 1, const ENUM_LINE_STYLE _style = STYLE_SOLID ); public: //--- Конструктор по умолчанию CGraphics(); //--- Универсальная функция для создания трендовых линий с заданными параметрами bool TrendCreate( const long chart_ID=0, // ID графика const string name="TrendLine", // имя линии const int sub_window=0, // номер подокна datetime time1=0, // время первой точки double price1=0, // цена первой точки datetime time2=0, // время второй точки double price2=0, // цена второй точки const color clr=clrRed, // цвет линии const ENUM_LINE_STYLE style=STYLE_SOLID, // стиль линии const int width=1, // толщина линии const bool back=false, // на заднем плане const bool selection=true, // выбрана ли линия const bool ray_right=false, // луч вправо const bool hidden=true, // скрывать в списке объектов const long z_order=0 // приоретет щелчка мыши (Z-Index) ); //--- Отрисовывает трендовую линию по двум ближайшим (справа от мыши) экстремумам void DrawTrendLine(void); //--- Проверяет, является ли прямая лучом bool IsRay() {return m_Is_Trend_Ray;} //--- Устанавливает признак луча (продлевать ли прямую вправо) void IsRay(bool _is_ray) {m_Is_Trend_Ray = _is_ray;} //--- Проверяет, менять ли отображение создаваемых программой объектов на старших таймфреймах bool IsChangeTimeframe(void) {return m_Is_Change_Timeframe_On_Create;} //--- Устанавливает признак отображения создаваемых программой объектов на старших таймфреймах void IsChangeTimeframe(bool _is_tf_change) {m_Is_Change_Timeframe_On_Create = _is_tf_change;} };
Функция, устанавливающая общие параметры для любого вновь созданного графического объекта
//+------------------------------------------------------------------+ //| Устанавливает общие параметры для всех новых объектов | //| Параметры: | //| _name - имя изменяемого объекта | //| _color - цвет изменяемого объекта. Если не задан, | //| используется "стандартный" цвет текущего периода | //| _width - ширина линии объекта | //| _style - тип линии объекта | //+------------------------------------------------------------------+ void CGraphics::CurrentObjectDecorate( const string _name, // имя изменяемого объекта const color _color=clrNONE, // цвет изменяемого объекта const int _width = 1, // ширина линии объекта const ENUM_LINE_STYLE _style = STYLE_SOLID // тип линии объекта ) { long timeframes; // таймфреймы, на которых будет отображён объект color currentColor; // цвет объекта //--- Уточнение таймфреймов, на которых будет отображен объект if(Is_Change_Timeframe_On_Create) { timeframes = CUtilites::GetAllLowerTimeframes(); } else { timeframes = OBJ_ALL_PERIODS; } //--- Уточнение цвета объекта if(_color != clrNONE) { currentColor = _color; } else { currentColor = CUtilites::GetTimeFrameColor(timeframes); } //--- Задание атрибутов ObjectSetInteger(0,_name,OBJPROP_COLOR,currentColor); // Цвет ObjectSetInteger(0,_name,OBJPROP_TIMEFRAMES,timeframes); // Периоды ObjectSetInteger(0,_name,OBJPROP_HIDDEN,true); // Скрывать в списке объектов ObjectSetInteger(0,_name,OBJPROP_SELECTABLE,true); // Возможность выделять ObjectSetInteger(0,_name,OBJPROP_SELECTED,Is_Select_On_Create); // Оставлять ли выделенным после создания ObjectSetInteger(0,_name,OBJPROP_WIDTH,_width); // Толщина линии ObjectSetInteger(0,_name,OBJPROP_STYLE,_style); // Стиль линии }
Функции рисования прямой
//+------------------------------------------------------------------+ //| Универсальная функция для создания трендовых линий с заданными | //| параметрами | //| Параметры: | //| chart_ID - ID графика | //| name - имя линии | //| sub_window - номер подокна | //| time1 - время первой точки | //| price1 - цена первой точки | //| time2 - время второй точки | //| price2 - цена второй точки | //| clr - цвет линии | //| style - стиль линии | //| width - толщина линии | //| back - на заднем плане | //| selection - выбрана ли линия | //| ray_right - луч вправо | //| hidden - скрывать в списке объектов | //| z_order - приоретет щелчка мыши (Z-Index) | //| Возвращаемое значение: | //| признак удачного завершения. Если нарисовать линию не вышло, | //| вернёт false, иначе - true | //+------------------------------------------------------------------+ bool CGraphics::TrendCreate( const long chart_ID=0, // ID графика const string name="TrendLine", // имя линии const int sub_window=0, // номер подокна datetime time1=0, // время первой точки double price1=0, // цена первой точки datetime time2=0, // время второй точки double price2=0, // цена второй точки const color clr=clrRed, // цвет линии const ENUM_LINE_STYLE style=STYLE_SOLID, // стиль линии const int width=1, // толщина линии const bool back=false, // на заднем плане const bool selection=true, // выбрана ли линия const bool ray_right=false, // луч вправо const bool hidden=true, // скрывать в списке объектов const long z_order=0 // приоретет щелчка мыши (Z-Index) ) { //--- Сбросить последнее сообщение об ошибке ResetLastError(); //--- Создаём линию if(!ObjectCreate(chart_ID,name,OBJ_TREND,sub_window,time1,price1,time2,price2)) { if(Print_Warning_Messages) // Если не получилось, сообщаем об ошибке { Print(__FUNCTION__, ": Can't create trend line! Error code = ",GetLastError()); } return(false); } //--- Задаём дополнительные атрибуты CurrentObjectDecorate(name,clr,width,style); //--- линия на переднем (false) или на заднем (true) плане? ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back); //--- луч вправо (true) или чёткие границы (false)? ObjectSetInteger(chart_ID,name,OBJPROP_RAY_RIGHT,ray_right); //--- приоретет кликов мыши (Z-index) ObjectSetInteger(chart_ID,name,OBJPROP_ZORDER,z_order); //--- Обновим картинку графика ChartRedraw(0); //--- Всё в порядке. Линия успешно нарисована. return(true); }
И на основе этой общей функции создадим другую функцию, которая рисует прямую по двум соседним экстремумам.
//+------------------------------------------------------------------+ //| Рисует трендовую линию по ближайшим справа экстремумам | //+------------------------------------------------------------------+ void CGraphics::DrawTrendLine(void) { int dropped_bar_number=CMouse::Bar(); // номер свечи под мышью int p1=0,p2=0; // номера первой и второй точек string trend_name = // имя линии тренда CUtilites::GetCurrentObjectName(Trend_Line_Prefix,OBJ_TREND); double price1=0, // цена первой точки price2=0, // цена второй точки tmp_price; // переменная для временного хранения цены datetime time1=0, // Время первой точки time2=0, // Время второй точки tmp_time; int x1,x2,y1,y2; // Координаты точек int window=0; // Номер подокна //--- Установка начальных параметров if(CMouse::Below()) // Если мышь под Low свечи { //--- Находим два экстремума снизу CUtilites::SetExtremumsBarsNumbers(false,p1,p2); //--- Определяем координаты точек time1=iTime(Symbol(),PERIOD_CURRENT,p1); price1=iLow(Symbol(),PERIOD_CURRENT,p1); time2=iTime(Symbol(),PERIOD_CURRENT,p2); price2=iLow(Symbol(),PERIOD_CURRENT,p2); } else // иначе if(CMouse::Above()) // если мышь над High свечи { //--- Находим два экстремума сверху CUtilites::SetExtremumsBarsNumbers(true,p1,p2); //--- Определяем координаты точек time1=iTime(Symbol(),PERIOD_CURRENT,p1); price1=iHigh(Symbol(),PERIOD_CURRENT,p1); time2=iTime(Symbol(),PERIOD_CURRENT,p2); price2=iHigh(Symbol(),PERIOD_CURRENT,p2); } //--- Отрисовка линии 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); }
Хочу ещё раз обратить ваше внимание на вызов функции CUtilites::SetExtremumsBarsNumbers, которая получает номера баров для точек 1 и 2. Её код был описан выше. Остальное мне кажется очевидным и не требующим длинного описания.
Итоговая функция рисует простую прямую по двум точкам. В зависимости от глобального параметра Is_Trend_Ray, (описан в файле "GlobalVariables.mqh") линия будет либо лучом, продлённым вправо, либо просто коротким отрезком между двумя экстремумами.
Возможность переключать продление линии тоже выведем на клавиатуру.
Создание блока управления: настройка метода OnChartEvent
Теперь, когда основные функции написаны, можно настроить сочетания клавиш.
В "Shortcuts.mqh" прописываем метод CShortcuts::OnChartEvent.
//+------------------------------------------------------------------+ //| Функция обработки событий | //+------------------------------------------------------------------+ void CShortcuts::OnChartEvent( const int id, const long &lparam, const double &dparam, const string &sparam ) { //--- switch(id) { //--- Сохранить координаты курсора мыши case CHARTEVENT_MOUSE_MOVE: CMouse::SetCurrentParameters(id,lparam,dparam,sparam); break; //--- Обработка нажатий клавиш case CHARTEVENT_KEYDOWN: //--- Изменить таймфрейм на 1 уровень вверх if(CUtilites::GetCurrentOperationChar(Up_Key) == lparam) { CUtilites::ChangeTimeframes(true); }; //--- Изменить таймфрейм на 1 уровень вниз if(CUtilites::GetCurrentOperationChar(Down_Key) == lparam) { CUtilites::ChangeTimeframes(false); }; //--- Изменить Z-Index графика (график поверх всех объектов) if(CUtilites::GetCurrentOperationChar(Z_Index_Key) == lparam) { CUtilites::ChangeChartZIndex(); } //--- Нарисовать трендовую линию if(CUtilites::GetCurrentOperationChar(Trend_Line_Key) == lparam) { m_graphics.DrawTrendLine(); } //--- Переключить параметр Is_Trend_Ray if(CUtilites::GetCurrentOperationChar(Switch_Trend_Ray_Key) == lparam) { m_graphics.IsRay(!m_graphics.IsRay()); } break; //--- } }
Клавиши, используемые в текущей реализации библиотеки
Действие | Клавиша | От английского слова |
---|---|---|
Перейти на таймфрейм вверх по основным периодам (из панели периодов) | U | Up |
Перейти на таймфрейм вниз | D | Down |
Смена Z-уровня графика (график сверху или снизу объектов) | Z | Z-order |
Рисование наклонной трендовой линии по двум ближайшим к мыши однонаправленным экстремумам | T | Trend line |
Переключение режима луча для новых прямых | R | Ray |
Заключение
В прикреплённом файле архив текущей версии библиотеки. Также в файле лежит три скрипта.
- Первый называется Del-All-Graphics. Он удаляет все графические объекты в текущем окне. В своём терминале я назначил ему сочетание клавиш Ctrl+A (All).
- Второй — Del-All-Prefixed. С его помощью можно удалить все объекты по префиксу имени (например, все трендовые линии, или все объекты, начинающиеся на H1). Я его вызываю с помощью Alt+R (Remove).
- И, наконец, третий скрипт (DeselectAllObjects) позволяет снять все выделения в текущем окне. Моё сочетание для него Ctrl+D (Deselect, как в Фотошопе).
Библиотеку лучше подключать к эксперту, а не к индикатору, так как если подключить к индикатору и попытаться использовать этот индикатор вместе с каким-нибудь другим советником, то будут жуткие тормоза. Во всяком случае, у меня было так. Может, я не умею его готовить?
Ну и напоследок — о планах.
Вторая версия библиотеки будет содержать, собственно, рисование тех полезных объектов, что показаны на видео. Некоторые объекты примитивны (вроде вертикальной или горизонтальной линии), над некоторыми, типа линий нужной мне длины, пришлось немного поломать голову — и они всё равно не всегда работают идеально из-за "ошибки выходных" или ещё по каким причинам. Я расскажу свои решения, и, конечно, буду рад конструктивной критике.
Третья версия будет содержать графический интерфейс для настройки параметров графических инструментов.
Четвёртая — если дойдут руки — будет уже полноценным советником-сопроводилкой и позволит облегчить ручную торговлю. Вот тут вопрос к сообществу. Я не уверен, что буду применять какие-то новые решения по сравнению с существующими аналогами. Интерфейс, конечно, буду рисовать сам, и могу выслушать пожелания, но торговля руками есть торговля руками :-). Соответственно, оно кому-нибудь надо?





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Добрый день.
Спасибо за библиотеку. Многое не пришлось придумывать заново.
Так же торгую в ручном режиме, и под себя делаю некоторые примочки. Надеюсь что когда реализую планы поделюсь с сообществом.
Помогите реализовать такую вещь:
Есть фигура(линия, прямоугольник).
Активируем инструмент нажатием горячей клавиши, устанавливаем начало левой кнопкой мыши и конец левой кнопкой мыши.
как тут
Помогите реализовать такую вещь:
Есть фигура(линия, прямоугольник).
Активируем инструмент нажатием горячей клавиши, устанавливаем начало левой кнопкой мыши и конец левой кнопкой мыши.
как тут
Хорошо.
Вас интересуют только прямые и прямоугольники? Или надо что-то еще?
Там еще пару неочевидных багов надо подправить, так что в ближайшее время постараюсь выложить все изменения в кодобазу.
Хорошо.
Вас интересуют только прямые и прямоугольники? Или надо что-то еще?
Там еще пару неочевидных багов надо подправить, так что в ближайшее время постараюсь выложить все изменения в кодобазу.
Да только они.