preview
Визуализации сделок на графике (Часть 1): Выбор периода для анализа

Визуализации сделок на графике (Часть 1): Выбор периода для анализа

MetaTrader 5Торговые системы | 29 мая 2024, 14:16
834 6
Aleksandr Seredin
Aleksandr Seredin

Введение

В этой статье мы будем писать с нуля скрипт для визуализации сделок при ретроспективном анализе торговых решений. Проще говоря, если мы торгуем вручную и выполняем работу по анализу наших прошлых входов в рынок с целью улучшения показателей эффективности нашей торговли, нам хочется как можно меньше тратить времени на анализ истории вручную и связанную с этим чисто техническую работу: открытие графиков, поиск сделок в истории, сохранение принт-скринов вручную в терминале, самостоятельную отрисовку Stop Loss и Take Profit по выполненным сделкам, сбор информации по уплаченным комиссиям и свопам. Скрипт, который мы будем писать в этой статье, как раз и поможет существенно сократить всю механическую работу. Используя скрипт, собрав его из исходников к этой статье, вы сможете существенно сократить время технической работы, чтобы уделить большее внимание именно анализу торговых решений. Тот, кто не хочет тратить время на сборку проектов, может скачать уже готовую версию этого скрипта из раздела Маркет на сайте MQL5. Написание скрипта будет состоять из двух частей, но исходный код будет полностью выложен в каждой части.


Зачем нужен ретроспективный анализ?

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

Статистика многих исследований подтверждает тезис, что инвестирование на финансовых рынках без контроля рисков и с необоснованным применением кредитного плеча может сделать такую инвестицию самой опасной с точки зрения рисков вложения капитала. Исследование "Комиссии по ценным бумагам и биржам США" (Release No. 34-64874, File Number: S7-30-11) за 17 лет показало, что примерно 70% трейдеров теряют деньги каждый квартал, и в среднем за 12 месяцев теряются 100% инвестиций на финансовых рынках:

" Approximately 70% of customers lose money every quarter and on average 100% of a retail customer‟s investment is lost in less than 12 months"

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

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

Эти проблемы вынуждают трейдеров постоянно задаваться вопросами: "Правильно ли я всё делаю?", "Сегодняшняя просадка — это нормально или нужно корректировать алгоритмы?", "Как улучшить результаты?". Ответы на эти вопросы важны для долгосрочного успеха на рынке. Методы их поиска варьируются: кто-то использует оптимизаторы стратегий, кто-то применяет глубокие нейронные сети, кто-то опирается на математические модели или многолетний опыт. Все эти подходы могут быть эффективными, так как рынок сам по себе является главным учителем.

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

Перейдём, собственно, к написанию скрипта, который поможет существенно облегчить рутинный процесс анализа сделок. Наш скрипт должен в итоге выводить нам информацию на принт-скрине, как показано на Рисунке 1.

Рисунок 1. Пример вывода информации скрипта

Рисунок 1. Пример вывода информации скрипта

Реализацию такого скрипта мы начнём с ввода входных данных пользователя.


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

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

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

enum input_method
  {
   Select_one_deal,
   Select_period
  };

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

input group "Enter deal ticket or select the period"
input input_method inp_method = Select_period;                          // One deal or period?

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

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

Рисунок 2. Пользовательский интерфейс ввода условия выгрузки информации.

Рисунок 2. Пользовательский интерфейс ввода условия выгрузки информации.

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

input group "For case 'Select_one_deal': Enter ticket of deal"
input long inp_d_ticket = 4339491;                                      // Ticket (global id)

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

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

input group "For case 'Select_period': Enter period for analys"
input datetime start_date = D'17.07.2023';                              // Start date
input datetime finish_date = D'19.07.2023';                             // Finish date

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

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

Реализовывать эту идею будем также через входные параметры и предусмотрим для запаса возможностей пользователя не два, а сразу четыре графика по сделкам, так как некоторые трейдеры используют в своей торговле более двух графиков, но очень немногие – более четырёх. В исключительных случаях можно будет запустить скрипт несколько раз. Для этого объявим четыре переменные перечисляемого стандартного типа ENUM_TIMEFRAMES, где переменная main_graph будет обозначать основной тайм-фрейм выгрузки, а остальные будут дополнительными. Данный код запишем в следующем виде:

input group "Enter time frames. 'current' = don't use"
input ENUM_TIMEFRAMES main_graph = PERIOD_D1;                           // Period of main chart
input ENUM_TIMEFRAMES addition_graph = PERIOD_H1;                       // Period of addition chart
input ENUM_TIMEFRAMES addition_graph_2 = PERIOD_CURRENT;                // Period of addition chart #2
input ENUM_TIMEFRAMES addition_graph_3 = PERIOD_CURRENT;                // Period of addition chart #3

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

input group "Navigate settings in bars."
input int bars_from_right_main = 15;                                    // Shift from right on main chart
input int bars_from_right_add = 35;                                     // Shift from right on addition chart
input int bars_from_right_add_2 = 35;                                   // Shift from right on addition chart #2
input int bars_from_right_add_3 = 35;                                   // Shift from right on addition chart #3

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

Представление графиков в терминале Metatrader 5 может быть настроено практически под любые вкусы и пожелания трейдера. При нажатии F8 на графике, можно настроить и режимы отображения, объекты, цветовую палитру и многое другое. Любую настройку графиков можно быстро и удобно менять создавая шаблоны различных конфигураций отображения. Пункт контекстного меню "Графики" -> "Шаблоны"  -> "Сохранить шаблон"/"Загрузить шаблон" позволяет даже без смены открытого окна графика быстро сменить настройку отображения графика цен. В итоге, множество настроек графика отображения вместились у нас в несколько переменных по числу анализируемых тайм-фреймов.

input group "Properties of charts. Enter the template name:"
input string main_template = "dailyHistorytemp";                        // Template name of main chart
input string addition_template = "hourHistoryTemp";                     // Template name of addition chart
input string addition_template_2 = "hourHistoryTemp";                   // Template name of addition chart #2
input string addition_template_3 = "hourHistoryTemp";                   // Template name of addition chart #3

В пользовательском интерфейсе входных параметров терминала это будет выглядеть как показано на Рисунке 3:

Рисунок 3. Пользовательский интерфейс входных параметров шаблонов.

Рисунок 3. Пользовательский интерфейс входных параметров шаблонов.

Теперь, когда со всеми стандартными настройками мы определились, давайте дополним настройки связанные именно с отображением полной информации по сделкам, чтобы полностью дать пользователю информацию для анализа своих торговых операций. Это в основном объекты, соответствующие цене открытия позиции, Stop Loss, Take Profit и соединительной линии. Запись соответствующих переменных типа color будет выглядеть так:

input group "Colors of deals line"
input color clr_price_open = clrWhiteSmoke;                             // Color of price open label
input color clr_price_close = clrWhiteSmoke;                            // Color of price close label
input color clr_stop = clrRed;                                          // Color of stop loss label
input color clr_take = clrLawnGreen;                                    // Color of take profit label
input color clr_main = clrWhiteSmoke;                                   // Color of deals trendline

В общем виде все входные параметры нашего скрипта будут выглядеть, как описано ниже:

#property copyright "Visit product page"
#property link      "https://www.mql5.com/ru/market/product/86223"
#property version   "1.00"
#property description "Make an automatic printscreen with a full description of all transactions for the period or 
			specify the ticket of the desired transaction."
#property script_show_inputs

enum input_method
  {
   Select_one_deal,
   Select_period
  };


input group "Enter deal ticket or select the period"
input input_method inp_method = Select_period;                          // One deal or period?

input group "For case 'Select_one_deal': Enter ticket of deal"
input long inp_d_ticket = 4339491;                                      // Ticket (global id)

input group "For case 'Select_period': Enter period for analys"
input datetime start_date = D'17.07.2023';                              // Start date
input datetime finish_date = D'19.07.2023';                             // Finish date

input group "Enter time frames. 'current' = don't use"
input ENUM_TIMEFRAMES main_graph = PERIOD_D1;                           // Period of main chart
input ENUM_TIMEFRAMES addition_graph = PERIOD_H1;                       // Period of addition chart
input ENUM_TIMEFRAMES addition_graph_2 = PERIOD_CURRENT;                // Period of addition chart #2
input ENUM_TIMEFRAMES addition_graph_3 = PERIOD_CURRENT;                // Period of addition chart #3

input group "Navigate settings in bars."
input int bars_from_right_main = 15;                                    // Shift from right on main chart
input int bars_from_right_add = 35;                                     // Shift from right on addition chart
input int bars_from_right_add_2 = 35;                                   // Shift from right on addition chart #2
input int bars_from_right_add_3 = 35;                                   // Shift from right on addition chart #3

input group "Properties of charts. Enter the template name:"
input string main_template = "dailyHistorytemp";                        // Template name of main chart
input string addition_template = "hourHistoryTemp";                     // Template name of addition chart
input string addition_template_2 = "hourHistoryTemp";                   // Template name of addition chart #2
input string addition_template_3 = "hourHistoryTemp";                   // Template name of addition chart #3

input group "Colors of deals line"
input color clr_price_open = clrWhiteSmoke;                             // Color of price open label
input color clr_price_close = clrWhiteSmoke;                            // Color of price close label
input color clr_stop = clrRed;                                          // Color of stop loss label
input color clr_take = clrLawnGreen;                                    // Color of take profit label
input color clr_main = clrWhiteSmoke;                                   // Color of deals trendline

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

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

   Print("Script starts its work.");                                    // проинформировали

   ResetLastError();                                                    // сбросили ошибку

   string brok_name = TerminalInfoString(TERMINAL_COMPANY);             // получили имя брокера
   long account_num = AccountInfoInteger(ACCOUNT_LOGIN);                // получили номер счёта

//---
   ulong    ticket = 0;                                                 // тикет
   ENUM_DEAL_ENTRY entry = -1;                                          // вход или выход
   long     position_id = 0,  PositionID[];                             // основной id
   int      type = -1,        arr_type[];                               // тип сделки
   int      magic = -1,       arr_magic[];                              // мэджик номер
   ENUM_DEAL_REASON      reason = -1,      arr_reason[];                // причина

   datetime time_open = 0,    arr_time_open[];                          // время открытия сделки
   datetime time_close = 0,   arr_time_close[];                         // время закрытия

   string   symbol,           arr_symbol[];                             // символ инструмента
   string   comment,          arr_comment[];                            // комментарий
   string   externalID,       arr_extermalID[];                         // внешний айди

   double   stop_loss = 0,    arr_stop_loss[];                          // Stop Loss сделки
   double   take_profit = 0,  arr_take_profit[];                        // Take Profit сделки
   double   open = 0,         arr_open[];                               // цена открытия
   double   close = 0,        arr_close[];                              // цена закрытия
   double   volume = 0,       arr_volume[];                             // объем позиции
   double   commission = 0,   arr_commission[];                         // комиссия
   double   swap = 0,         arr_swap[];                               // своп
   double   profit = 0,       arr_profit[];                             // профит
   double   fee = 0,          arr_fee[];                                // налог

   int res = -1;                                                        // команда пользователя

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


Выбор исторических данных по периоду

Реализацию выбора со стороны пользователя сделаем через оператор логического выбора switch, в который и будем передавать значение введенной глобальной переменной inp_method и начнём обработку с варианта кейса Select_period для сбора данных по завершённым сделкам за определенный период.

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

         res = MessageBox("You have selected analysis for period. Continue?","",MB_OKCANCEL); // ждём подтверждения пользователя

Результат обработки события нажатия пользователем кнопки поместим в переменную res, чтобы реализовать сам механизм прерывания работы скрипта. Технически прерывание проще всего сделать через оператор возврата return, если в переменной res окажется значение IDCANCEL, что и будет означать, что пользователь нажал соответствующую кнопку. Данный блок представим через условный оператор логического выбора if в следующем виде.

         if(res == IDCANCEL)                                            // если прервано пользователем
           {
            printf("%s - %d -> Scrypt was stoped by user.",__FUNCTION__,__LINE__); // проинформировали
            return;                                                     // не идём дальше
           }

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

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

            int total = HistoryDealsTotal();                            // получили общее количество сделок

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

            if(total <= 0)                                              // если ничего не найдено
              {
               printf("%s - %d -> No deals were found for the specified period.",__FUNCTION__,__LINE__); // проинформировали
               MessageBox("No deals were found for the specified period: "+TimeToString(start_date)+"-"+TimeToString(finish_date)+". Script is done.");
               return;
              }

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

            for(int i=0; i<total; i++)                                  // идём по количеству сделок

Отбор и запрос данных по каждой отдельной исторической сделке будем осуществлять через её уникальный идентификационный номер - тикет. Чтобы получить данный номер, будем использовать предопределенную функцию терминала HistoryDealGetTicket(), в параметры которой будем передавать порядковые номера от 0 до total и получать возвращаемое значение в виде уникального идентификационного номера сделки, как показано ниже. При этом не забудем сделать проверку на корректность возвращаемого значения.

               //--- try to get deals ticket
               if((ticket=HistoryDealGetTicket(i))>0)                   // взяли тикет

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

                  //--- get deals properties
                  position_id = HistoryDealGetInteger(ticket,DEAL_POSITION_ID);        // взяли основной id
                  entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket,DEAL_ENTRY);   // вход или выход?
                  type = (int)HistoryDealGetInteger(ticket,DEAL_TYPE);  	       // тип сделки

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

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

                  //---проверяем тип сделки
                  if(type == DEAL_TYPE_BUY            ||                // если тип покупка
                     type == DEAL_TYPE_SELL           ||                // если тип продажа
                     type == DEAL_TYPE_BUY_CANCELED   ||                // если тип закрытая покупка
                     type == DEAL_TYPE_SELL_CANCELED                    // если тип закрытая продажа
                    )

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

№ п/п Вход в позицию (DEAL_ENTRY_IN) Выход из позиции (DEAL_ENTRY_OUT)
 1  open (DEAL_PRICE)  close (DEAL_PRICE)
 2  time_open (DEAL_TIME)  time_close (DEAL_TIME)
 3  symbol (DEAL_SYMBOL)  reason (DEAL_REASON)
 4  stop_loss (DEAL_SL)  swap (DEAL_SWAP)
 5  take_profit (DEAL_TP)  profit (DEAL_PROFIT)
 6  magic (DEAL_MAGIC)  fee (DEAL_FEE)
 7  comment (DEAL_COMMENT)  -
 8  externalID (DEAL_EXTERNAL_ID)  -
 9  volume (DEAL_VOLUME)  -
 10   commission (DEAL_COMMISSION)  -

Таблица 1. Источники получения информации по всей позиции в зависимости от типа сделки.

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

                     if(entry == DEAL_ENTRY_IN)                         		// если это вход
                       {
                        open = HistoryDealGetDouble(ticket,DEAL_PRICE);                 // взяли цену открытия
                        time_open  =(datetime)HistoryDealGetInteger(ticket,DEAL_TIME);  // взяли время открытия
                        symbol=HistoryDealGetString(ticket,DEAL_SYMBOL);   		// взяли символ
                        stop_loss = HistoryDealGetDouble(ticket,DEAL_SL);  		// взяли Stop Loss
                        take_profit = HistoryDealGetDouble(ticket,DEAL_TP);		// взяли Take Profit

                        magic = (int)HistoryDealGetInteger(ticket,DEAL_MAGIC);   	// взяли мэджик
                        comment=HistoryDealGetString(ticket,DEAL_COMMENT);       	// взяли комментарий
                        externalID=HistoryDealGetString(ticket,DEAL_EXTERNAL_ID);	// взяли внешний id
                        volume = HistoryDealGetDouble(ticket,DEAL_VOLUME);          	// взяли объем
                        commission = HistoryDealGetDouble(ticket,DEAL_COMMISSION);  	// взяли размер комиссии
                       }

                     if(entry == DEAL_ENTRY_OUT)                        	 	// если это выход
                       {
                        close = HistoryDealGetDouble(ticket,DEAL_PRICE);               	// взяли цену закрытия
                        time_close  =(datetime)HistoryDealGetInteger(ticket,DEAL_TIME);	// взяли время закрытия

                        reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(ticket,DEAL_REASON); // 
                        swap = HistoryDealGetDouble(ticket,DEAL_SWAP);        		// своп
                        profit = HistoryDealGetDouble(ticket,DEAL_PROFIT);    		// профит
                        fee = HistoryDealGetDouble(ticket,DEAL_FEE);          		// налог
                       }

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

С учетом того, что каждое свойство отдельной позиции нужно хранить в разных форматах в виде строки, целочисленных значениях или дробных, функцию Find() логично объявить как перегружаемую через шаблон. Язык программирования Mql5 позволяет гибко и достаточно удобно реализовать данный функционал через ключевое слово template. Это позволит нам один раз объявить шаблон нашей функции с перегружаемым типом данных typename, а компилятор уже самостоятельно подставит для каждого типа данных нужную реализацию. Так как мы не будем передавать туда пользовательские типы данных, то проблем с неявным приведением различных типов не будет, и не нужно будет делать никаких перегрузок операторов. Реализация шаблона пользовательской функции Find() представлена ниже.

template<typename A>
int               Find(A &aArray[],A aValue)
  {
   for(int i=0; i<ArraySize(aArray); i++)
     {
      if(aArray[i]==aValue)
        {
         return(i);                                                     // Элемент существует, то возвращаем индекс элемента
        }
     }
   return(-1);                                                          // Нет такого элемента, возвращаем -1
  }

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

                     //---вносим данные в основное хранилище
                     //---проверяем есть ли такой id
                     if(Find(PositionID,position_id)==-1)               // если такой сделки ещё не было,                       {

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

      case  Select_period:                                              			// смотрим за период

         res = MessageBox("You have selected analysis for period. Continue?","",MB_OKCANCEL); 	// ждём подтверждения пользователя

         if(res == IDCANCEL)                                            			// если прервано пользователем
           {
            printf("%s - %d -> Scrypt was stoped by user.",__FUNCTION__,__LINE__); 		// проинформировали
            return;                                                     			// останавливаем
           }

         MessageBox("Please press 'Ok' and wait for the next message until script will be done."); // проинформировали

         //---собираем данные по истории
         if(HistorySelect(start_date,finish_date))                      // выбрали нужный период в истории
           {
            int total = HistoryDealsTotal();                            // получили общее количество сделок

            if(total <= 0)                                              // если ничего не найдено
              {
               printf("%s - %d -> No deals were found for the specified period.",__FUNCTION__,__LINE__); // проинформировали
               MessageBox("No deals were found for the specified period: "+TimeToString(start_date)+"-"+TimeToString(finish_date)+". Script is done.");
               return;
              }

            for(int i=0; i<total; i++)                                  // идём по количеству сделок
              {
               //--- try to get deals ticket
               if((ticket=HistoryDealGetTicket(i))>0)                   // взяли тикет
                 {
                  //--- get deals properties
                  position_id = HistoryDealGetInteger(ticket,DEAL_POSITION_ID);        // взяли основной id
                  entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket,DEAL_ENTRY);   // вход или выход?
                  type = (int)HistoryDealGetInteger(ticket,DEAL_TYPE);  	       // вход или выход?

                  //---проверяем тип сделки
                  if(type == DEAL_TYPE_BUY            ||                // если тип покупка
                     type == DEAL_TYPE_SELL           ||                // если тип продажа
                     type == DEAL_TYPE_BUY_CANCELED   ||                // если тип закрытая покупка
                     type == DEAL_TYPE_SELL_CANCELED                    // если тип закрытая продажа
                    )
                    {
                     //---это вход или выход?
                     if(entry == DEAL_ENTRY_IN)                         // если это вход
                       {
                        open = HistoryDealGetDouble(ticket,DEAL_PRICE);                	// взяли цену открытия
                        time_open  =(datetime)HistoryDealGetInteger(ticket,DEAL_TIME); 	// взяли время открытия
                        symbol=HistoryDealGetString(ticket,DEAL_SYMBOL);   		// взяли символ
                        stop_loss = HistoryDealGetDouble(ticket,DEAL_SL);  		// взяли Stop Loss
                        take_profit = HistoryDealGetDouble(ticket,DEAL_TP);		// взяли Take Profit

                        magic = (int)HistoryDealGetInteger(ticket,DEAL_MAGIC);   	// взяли мэджик
                        comment=HistoryDealGetString(ticket,DEAL_COMMENT);       	// взяли комментарий
                        externalID=HistoryDealGetString(ticket,DEAL_EXTERNAL_ID);	// взяли внешний id
                        volume = HistoryDealGetDouble(ticket,DEAL_VOLUME);          	// взяли объем
                        commission = HistoryDealGetDouble(ticket,DEAL_COMMISSION);  	// взяли размер комиссии
                       }

                     if(entry == DEAL_ENTRY_OUT)                        		// если это выход
                       {
                        close = HistoryDealGetDouble(ticket,DEAL_PRICE);               	// взяли цену закрытия
                        time_close  =(datetime)HistoryDealGetInteger(ticket,DEAL_TIME);	// взяли время закрытия

                        reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(ticket,DEAL_REASON); // причина
                        swap = HistoryDealGetDouble(ticket,DEAL_SWAP);        		// своп
                        profit = HistoryDealGetDouble(ticket,DEAL_PROFIT);    		// профит
                        fee = HistoryDealGetDouble(ticket,DEAL_FEE);          		// налог
                       }


                     //---вносим данные в основное хранилище
                     //---проверяем есть ли такой id
                     if(Find(PositionID,position_id)==-1)               		// если такой сделки ещё не было,
                       {
                        //---меняем размерность контейнеров
                        ArrayResize(arr_time_open,ArraySize(arr_time_open)+1);       
                        ArrayResize(arr_time_close,ArraySize(arr_time_close)+1);     
                        ArrayResize(arr_symbol,ArraySize(arr_symbol)+1);             
                        ArrayResize(arr_stop_loss,ArraySize(arr_stop_loss)+1);       
                        ArrayResize(arr_take_profit,ArraySize(arr_take_profit)+1);   
                        ArrayResize(arr_open,ArraySize(arr_open)+1);                 
                        ArrayResize(arr_close,ArraySize(arr_close)+1);               
                        ArrayResize(PositionID,ArraySize(PositionID)+1);             

                        ArrayResize(arr_magic,ArraySize(arr_magic)+1);               
                        ArrayResize(arr_extermalID,ArraySize(arr_extermalID)+1);     
                        ArrayResize(arr_comment,ArraySize(arr_comment)+1);           
                        ArrayResize(arr_volume,ArraySize(arr_volume)+1);             
                        ArrayResize(arr_commission,ArraySize(arr_commission)+1);     
                        ArrayResize(arr_reason,ArraySize(arr_reason)+1);             
                        ArrayResize(arr_swap,ArraySize(arr_swap)+1);                 
                        ArrayResize(arr_profit,ArraySize(arr_profit)+1);             
                        ArrayResize(arr_fee,ArraySize(arr_fee)+1);                   

                        PositionID[ArraySize(arr_time_open)-1]=position_id;          


                        if(entry == DEAL_ENTRY_IN)                      		  // если это был вход,
                          {
                           arr_time_open[    ArraySize(arr_time_open)-1]   = time_open;   // время сделки
                           arr_symbol[       ArraySize(arr_symbol)-1]      = symbol;      // символ инструмента
                           arr_stop_loss[    ArraySize(arr_stop_loss)-1]   = stop_loss;   // Stop Loss сделки
                           arr_take_profit[  ArraySize(arr_take_profit)-1] = take_profit; // Take Profit сделки
                           arr_open[         ArraySize(arr_open)-1]        = open;        // цена открытия
                           //---
                           arr_magic[        ArraySize(arr_magic)-1]       = magic;       // мэджик
                           arr_comment[      ArraySize(arr_comment)-1]     = comment;     // комментарий
                           arr_extermalID[   ArraySize(arr_extermalID)-1]  = externalID;  // внешний id
                           arr_volume[       ArraySize(arr_volume)-1]      = volume;      // объем
                           arr_commission[   ArraySize(arr_commission)-1]  = commission;  // комиссия
                          }

                        if(entry == DEAL_ENTRY_OUT)                     		  // если это был выход
                          {
                           arr_time_close[   ArraySize(arr_time_close)-1]  = time_close;  // время закрытия
                           arr_close[        ArraySize(arr_close)-1]       = close;       // цена закрытия
                           //---
                           arr_reason[       ArraySize(arr_reason)-1]      = reason;      // причина
                           arr_swap[         ArraySize(arr_swap)-1]        = swap;        // своп
                           arr_profit[       ArraySize(arr_profit)-1]      = profit;      // профит
                           arr_fee[          ArraySize(arr_fee)-1]         = fee;         // налог
                          }
                       }
                     else
                       {
                        int index = Find(PositionID,position_id);       // если есть, находим индекс

                        if(entry == DEAL_ENTRY_IN)                      // если это был вход
                          {
                           arr_time_open[index]   = time_open;          // время сделки
                           arr_symbol[index]      = symbol;             // символ инструмента
                           arr_stop_loss[index]   = stop_loss;          // Stop Loss сделки
                           arr_take_profit[index] = take_profit;        // Take Profit сделки
                           arr_open[index]        = open;               // цена открытия
                           //---
                           arr_magic[index]       = magic;              // мэджик
                           arr_comment[index]     = comment;            // комментарий
                           arr_extermalID[index]  = externalID;         // внешний id
                           arr_volume[index]      = volume;             // объем
                           arr_commission[index]  = commission;         // комиссия
                          }

                        if(entry == DEAL_ENTRY_OUT)                     // если это был выход
                          {
                           arr_time_close[index]  = time_close;         // время закрытия сделки
                           arr_close[index]       = close;              // цена закрытия сделки
                           //---
                           arr_reason[index]      = reason;             // причина
                           arr_swap[index]        = swap;               // своп
                           arr_profit[index]      = profit;             // профит
                           arr_fee[index]         = fee;                // налог
                          }
                       }
                    }
                 }
              }
           }
         else
           {
            printf("%s - %d -> Error of selecting history deals: %d",__FUNCTION__,__LINE__,GetLastError()); // проинформировали
            printf("%s - %d -> No deals were found for the specified period.",__FUNCTION__,__LINE__);       // проинформировали
            MessageBox("No deals were found for the specified period: "+TimeToString(start_date)+"-"+TimeToString(finish_date)+". Script is done.");
            return;
           }
         break;


Заключение к первой части

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

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

Прикрепленные файлы |
DealsPrintScreen.mq5 (105.98 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (6)
Aleksandr Seredin
Aleksandr Seredin | 29 мая 2024 в 15:19
Yevgeniy Koshtenko #:
Ух ты! Интересно, а реально ли выгружать сделки скажем, в канал Телеграмм? Скрины)

Сейчас функционал заточен именно на сохранение принт скрина. Теоретически нет ограничений на отправку этого принт-скрина в итоге через телеграм. По-моему через предопределенную функцию webrequest() или что-то такое))

Yuriy Bykov
Yuriy Bykov | 30 мая 2024 в 07:35
Спасибо за статью!
 
Посмотрите на использование структур для хранения однотипных наборов информации. Вместо отдельных массивов для каждого параметра сделки можно определить свой тип данных, включающий все параметры, и после этого хранить в одном массиве элементы нашего нового типа. Возможно, это будет удобнее.
Aleksandr Seredin
Aleksandr Seredin | 30 мая 2024 в 08:26
Yuriy Bykov #:
Спасибо за статью!
 
Посмотрите на использование структур для хранения однотипных наборов информации. Вместо отдельных массивов для каждого параметра сделки можно определить свой тип данных, включающий все параметры, и после этого хранить в одном массиве элементы нашего нового типа. Возможно, это будет удобнее.

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

Aleksandr Slavskii
Aleksandr Slavskii | 30 мая 2024 в 16:50
Yevgeniy Koshtenko #:
Ух ты! Интересно, а реально ли выгружать сделки скажем, в канал Телеграмм? Скрины)

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

Правда сейчас ChartScreenShot сломалась, объекты которые LABEL, сдвигаются в сторону.

Надо думать как делать скрины какой то сторонней прогой, а не штатной.

Aleksandr Seredin
Aleksandr Seredin | 30 мая 2024 в 17:55
Aleksandr Slavskii #:

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

Правда сейчас ChartScreenShot сломалась, объекты которые LABEL, сдвигаются в сторону.

Надо думать как делать скрины какой то сторонней прогой, а не штатной.

Спасибо за ссылку. Обязательно прочитаю.

Нейросети — это просто (Часть 92): Адаптивное прогнозирование в частотной и временной областях Нейросети — это просто (Часть 92): Адаптивное прогнозирование в частотной и временной областях
Авторы метода FreDF экспериментально подтвердили преимущество комбинированного прогнозирования в частотной и временной областях. Однако применение весового гиперпараметра не является оптимальным для нестационарных временных рядов. В данной статье я предлагаю познакомиться с методом адаптивного сочетания прогнозов в частотной и временной областях.
Разработка робота на Python и MQL5 (Часть 2): Выбор модели, создание и обучение, кастомный тестер Python Разработка робота на Python и MQL5 (Часть 2): Выбор модели, создание и обучение, кастомный тестер Python
Продолжаем цикл статей по созданию торгового робота на Python и MQL5. Сегодня решим задачу выбора и обучения модели, ее тестирования, внедрения кросс-валидации, поиска по сетке, а также задачу ансамблирования моделей.
Введение в MQL5 (Часть 2): Предопределенные переменные, общие функции и операторы потока управления Введение в MQL5 (Часть 2): Предопределенные переменные, общие функции и операторы потока управления
В этой статье мы продолжаем знакомиться с языком программирования MQL5. Данная серия статей — не просто учебный материал пособия, это двери в мир программирования. Что делает их особенными? Я постарался в объяснениях сохранять простоту изложения, чтобы сделать сложные концепции доступными для всех. При всей доступности материала, для наилучшего результата вам нужно активно воспроизводить все, о чем мы будем говорить. Только в этом случае вы получите максимальную выгоду от данных статей.
Возможности Мастера MQL5, которые вам нужно знать (Часть 11): Числовые стены Возможности Мастера MQL5, которые вам нужно знать (Часть 11): Числовые стены
Числовые стены (Number Walls) — это вариант регистра сдвига с линейной обратной связью (Linear Shift Back Registers), который предварительно оценивает последовательности на предмет предсказуемости путем проверки на сходимость. Мы посмотрим, как эти идеи могут быть использованы в MQL5.