Собственное представление торговой истории и создание графиков для отчетов

Andrey Azatskiy | 1 июня, 2018

Введение

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

В основе любого трейдинга лежит торговый алгоритм, формирующий кривую Profit/Loss. Такой алгоритм можно сравнить с неким синтетическим активом, стоимость которого формируется относительно базового актива (торгуемого инструмента). К примеру, для опционов есть формула (BSM-модель), по которой в любой момент рассчитывается стоимость этого синтетического актива, исходя из цены базового. Но для торгового алгоритма такой формулы не существует. Соответственно, запуск алгоритма можно сравнить с длинной позицией по синтетическому инструменту, кривая PL которого формируется по запрограммированной логике. Формируемая этим «активом» прибыль может быть нестабильна в разные периоды времени. Даже если она и подлежит оценке единой эконометрической моделью, то эту модель невозможно унифицировать. Возникает вопрос: как же отслеживать этот актив и стадии нашей торговли? Подходящим решением выглядит отслеживать ретроспективу торговли по этому алгоритму и выявлять отклонения от ожидаемых результатов.

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

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

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


Глава 1. Подготовка данных для анализа торговой статистики

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

Торговая история в MetaTrader 5 и методы работы с ней

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

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

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

//+------------------------------------------------------------------+
//| Структура данных одной выбранной сделки                          |
//+------------------------------------------------------------------+
struct DealData
  {
   long              ticket;            // Тикет сделки
   long              order;             // Номер ордера, открывшего сделку
   datetime          DT;                // Дата открытия позиции
   long              DT_msc;            // Дата открытия позиции в миллисекундах 
   ENUM_DEAL_TYPE    type;              // Тип открытой позиции
   ENUM_DEAL_ENTRY   entry;             // Тип входа в позицию
   long              magic;             // Уникальный номер позиции
   ENUM_DEAL_REASON  reason;            // Откуда был выставлен ордер
   long              ID;                // ID позиции
   double            volume;            // Объем позиции (лоты)
   double            price;             // Цена входа в позицию
   double            comission;         // Комиссия уплаченая
   double            swap;              // Своп
   double            profit;            // Прибыль/убыток
   string            symbol;            // Символ
   string            comment;           // Комментарий, указанный во время открытия
   string            ID_external;       // Внешний ID 
  };
//+------------------------------------------------------------------+
//| Структура, хранящая в себе все сделки по определенной позиции    |
//| которая была выбрана по ID                                       |
//+------------------------------------------------------------------+
struct DealKeeper
  {
   DealData          deals[];           /* Список всех сделок для данной позиции (или нескольких, если сделка развернулась)*/
   string            symbol;            // Символ
   long              ID;                // ID данной позиции (позиций)
   datetime          DT_min;            // дата открытия 
   datetime          DT_max;            // дата закрытия 
  };

Из этого кода видим, что структура DealData содержит избыточное описание параметров сделки.

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

Рассмотрим детально способ заполнения структуры. За это отвечает класс CDealHistoryGetter, в частности — его функция getHistory:

//+-----------------------------------------------------------------------+
//| Класс, извлекающий историю торгов из терминала и преобразующий ее в   |
//| удобный для анализа вид                                               |
//+-----------------------------------------------------------------------+
class CDealHistoryGetter
  {
public:
   bool              getHistory(DealKeeper &deals[],datetime from,datetime till);                // возвращает все данные по историческим сделкам   
   bool              getIDArr(ID_struct &ID_arr[],datetime from,datetime till);                  // возвращает уникальный массив ID сделок
   bool              getDealsDetales(DealDetales &ans[],datetime from,datetime till);            // возвращает массив сделок, где каждая строка - одна конкретная сделка
private:

   void              addArray(ID_struct &Arr[],const ID_struct &value);                          // добавить в динамический массив
   void              addArray(DealKeeper &Arr[],const DealKeeper &value);                        // добавить в динамический массив
   void              addArray(DealData &Arr[],const DealData &value);                            // добавить в динамический массив
   void              addArr(DealDetales &Arr[],DealDetales &value);                              // добавить в динамический массив
   void              addArr(double &Arr[],double value);                                         // добавить в динамический массив

/*
    Если есть выходы по InOut, то в inputParam будет более одной позиции. 
    Если нет выходов по InOut, то в inputParam будет только одна сделка ! 
*/
   void              getDeals_forSelectedKeeper(DealKeeper &inputParam,DealDetales &ans[]);      // формируется единая запись по одной выбранной позиции для всех позиций, что есть в inputParam
   double            MA_price(double &prices[],double &lots[]);                                  // подсчитывается средневзвешенная цена открытия
   bool              isBorderPoint(DealData &data,BorderPointType &type);                        // получаем информацию, пограничная ли это точка, и какой именно тип точки
   ENUM_DAY_OF_WEEK  getDay(datetime DT);                                                        // получаем день из даты
   double            calcContracts(double &Arr[],GetContractType type);                          // получаем информацию об объеме самой последней позиции
  };

Рассмотрим ее реализацию:

//+------------------------------------------------------------------+
//| возвращает все данные по историческим сделкам                    |
//+------------------------------------------------------------------+
bool CDealHistoryGetter::getHistory(DealKeeper &deals[],datetime from,datetime till)
  {
   ArrayFree(deals);                                     // очищаем массив с результатами
   ID_struct ID_arr[];
   if(getIDArr(ID_arr,from,till))                        // получаем уникальные ID
     {
      int total=ArraySize(ID_arr);
      for(int i=0;i<total;i++)                           // цикл по ID
        {
         DealKeeper keeper;                              // накопитель сделок по позиции
         keeper.ID=ID_arr[i].ID;
         keeper.symbol=ID_arr[i].Symb;
         keeper.DT_max = LONG_MIN;
         keeper.DT_min = LONG_MAX;
         if(HistorySelectByPosition(ID_arr[i].ID))       // выбираем все сделки с заданным ID
           {

Сначала нужно получить уникальные ID позиций. Для этого в сплошном цикле по истории просматриваются ID сделок и формируется структура с двумя полями — Символ и ID позиции. Полученные данные потребуются нам для работы функции getHistory

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

int total_2=HistoryDealsTotal();
            for(int n=0;n<total_2;n++)                        // Цикл по выбранным сделкам
              {
               long ticket=(long)HistoryDealGetTicket(n);
               DealData data;
               data.ID=keeper.ID;
               data.symbol=keeper.symbol;
               data.ticket= ticket;

               data.DT=(datetime)HistoryDealGetInteger(ticket,DEAL_TIME);
               keeper.DT_max=MathMax(keeper.DT_max,data.DT);
               keeper.DT_min=MathMin(keeper.DT_min,data.DT);
               data.order= HistoryDealGetInteger(ticket,DEAL_ORDER);
               data.type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket,DEAL_TYPE);
               data.DT_msc=HistoryDealGetInteger(ticket,DEAL_TIME_MSC);
               data.entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket,DEAL_ENTRY);
               data.magic = HistoryDealGetInteger(ticket,DEAL_MAGIC);
               data.reason= (ENUM_DEAL_REASON)HistoryDealGetInteger(ticket,DEAL_REASON);
               data.volume= HistoryDealGetDouble(ticket,DEAL_VOLUME);
               data.price = HistoryDealGetDouble(ticket,DEAL_PRICE);
               data.comission=HistoryDealGetDouble(ticket,DEAL_COMMISSION);
               data.swap=HistoryDealGetDouble(ticket,DEAL_SWAP);
               data.profit=HistoryDealGetDouble(ticket,DEAL_PROFIT);
               data.comment=HistoryDealGetString(ticket,DEAL_COMMENT);
               data.ID_external=HistoryDealGetString(ticket,DEAL_EXTERNAL_ID);

               addArray(keeper.deals,data);                  // Добавляем сделки
              }

            if(ArraySize(keeper.deals)>0)
               addArray(deals,keeper);                       // Добавляем позицию
           }
        }
      return ArraySize(deals) > 0;
     }
   else
      return false;                                          // Если нет уникальных ID
  }

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

Подготовка данных для анализа

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

Ticket Order DT DT msc Type Entry Magic Reason ID Volume Price Comission Swap Profit Symbol Comment ID external
10761601 69352663 23.11.2017 17:41 1,51146E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 506789 DEAL_REASON_EXPERT 69352663 1 58736 -0,5 0 0 Si-12.17 Open test position 23818051
10761602 69352663 23.11.2017 17:41 1,51146E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 506789 DEAL_REASON_EXPERT 69352663 1 58737 -0,5 0 0 Si-12.17 Open test position 23818052
10766760 0 24.11.2017 13:00 1,51153E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58682 0 0 -109 Si-12.17 [variation margin close]
10766761 0 24.11.2017 13:00 1,51153E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58682 0 0 0 Si-12.17 [variation margin open]
10769881 0 24.11.2017 15:48 1,51154E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58649 0 0 -66 Si-12.17 [variation margin close]
10769882 0 24.11.2017 15:48 1,51154E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58649 0 0 0 Si-12.17 [variation margin open]
10777315 0 27.11.2017 13:00 1,51179E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58420 0 0 -458 Si-12.17 [variation margin close]
10777316 0 27.11.2017 13:00 1,51179E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58420 0 0 0 Si-12.17 [variation margin open]
10780552 0 27.11.2017 15:48 1,5118E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58417 0 0 -6 Si-12.17 [variation margin close]
10780553 0 27.11.2017 15:48 1,5118E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58417 0 0 0 Si-12.17 [variation margin open]
10790453 0 28.11.2017 13:00 1,51187E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58589 0 0 344 Si-12.17 [variation margin close]
10790454 0 28.11.2017 13:00 1,51187E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58589 0 0 0 Si-12.17 [variation margin open]
10793477 0 28.11.2017 15:48 1,51188E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58525 0 0 -128 Si-12.17 [variation margin close]
10793478 0 28.11.2017 15:48 1,51188E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58525 0 0 0 Si-12.17 [variation margin open]
10801186 0 29.11.2017 13:00 1,51196E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58515 0 0 -20 Si-12.17 [variation margin close]
10801187 0 29.11.2017 13:00 1,51196E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58515 0 0 0 Si-12.17 [variation margin open]
10804587 0 29.11.2017 15:48 1,51197E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58531 0 0 32 Si-12.17 [variation margin close]
10804588 0 29.11.2017 15:48 1,51197E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58531 0 0 0 Si-12.17 [variation margin open]
10813418 0 30.11.2017 13:00 1,51205E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58843 0 0 624 Si-12.17 [variation margin close]
10813419 0 30.11.2017 13:00 1,51205E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58843 0 0 0 Si-12.17 [variation margin open]
10816400 0 30.11.2017 15:48 1,51206E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58609 0 0 -468 Si-12.17 [variation margin close]
10816401 0 30.11.2017 15:48 1,51206E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58609 0 0 0 Si-12.17 [variation margin open]
10824628 0 01.12.2017 13:00 1,51213E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58864 0 0 510 Si-12.17 [variation margin close]
10824629 0 01.12.2017 13:00 1,51213E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58864 0 0 0 Si-12.17 [variation margin open]
10828227 0 01.12.2017 15:48 1,51214E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58822 0 0 -84 Si-12.17 [variation margin close]
10828228 0 01.12.2017 15:48 1,51214E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58822 0 0 0 Si-12.17 [variation margin open]
10838074 0 04.12.2017 13:00 1,51239E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59093 0 0 542 Si-12.17 [variation margin close]
10838075 0 04.12.2017 13:00 1,51239E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59093 0 0 0 Si-12.17 [variation margin open]
10840722 0 04.12.2017 15:48 1,5124E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59036 0 0 -114 Si-12.17 [variation margin close]
10840723 0 04.12.2017 15:48 1,5124E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59036 0 0 0 Si-12.17 [variation margin open]
10848185 0 05.12.2017 13:00 1,51248E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58793 0 0 -486 Si-12.17 [variation margin close]
10848186 0 05.12.2017 13:00 1,51248E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58793 0 0 0 Si-12.17 [variation margin open]
10850473 0 05.12.2017 15:48 1,51249E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58881 0 0 176 Si-12.17 [variation margin close]
10850474 0 05.12.2017 15:48 1,51249E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58881 0 0 0 Si-12.17 [variation margin open]
10857862 0 06.12.2017 13:00 1,51257E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59181 0 0 600 Si-12.17 [variation margin close]
10857863 0 06.12.2017 13:00 1,51257E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59181 0 0 0 Si-12.17 [variation margin open]
10860776 0 06.12.2017 15:48 1,51258E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59246 0 0 130 Si-12.17 [variation margin close]
10860777 0 06.12.2017 15:48 1,51258E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59246 0 0 0 Si-12.17 [variation margin open]
10869047 0 07.12.2017 13:00 1,51265E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59325 0 0 158 Si-12.17 [variation margin close]
10869048 0 07.12.2017 13:00 1,51265E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59325 0 0 0 Si-12.17 [variation margin open]
10871856 0 07.12.2017 15:48 1,51266E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59365 0 0 80 Si-12.17 [variation margin close]
10871857 0 07.12.2017 15:48 1,51266E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59365 0 0 0 Si-12.17 [variation margin open]
10879894 0 08.12.2017 13:01 1,51274E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59460 0 0 190 Si-12.17 [variation margin close]
10879895 0 08.12.2017 13:01 1,51274E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59460 0 0 0 Si-12.17 [variation margin open]
10882283 0 08.12.2017 15:48 1,51275E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59421 0 0 -78 Si-12.17 [variation margin close]
10882284 0 08.12.2017 15:48 1,51275E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59421 0 0 0 Si-12.17 [variation margin open]
10888014 0 11.12.2017 13:00 1,513E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59318 0 0 -206 Si-12.17 [variation margin close]
10888015 0 11.12.2017 13:00 1,513E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59318 0 0 0 Si-12.17 [variation margin open]
10890195 0 11.12.2017 15:48 1,51301E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59280 0 0 -76 Si-12.17 [variation margin close]
10890196 0 11.12.2017 15:48 1,51301E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59280 0 0 0 Si-12.17 [variation margin open]
10895808 0 12.12.2017 13:00 1,51308E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58920 0 0 -720 Si-12.17 [variation margin close]
10895809 0 12.12.2017 13:00 1,51308E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58920 0 0 0 Si-12.17 [variation margin open]
10897839 0 12.12.2017 15:48 1,51309E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58909 0 0 -22 Si-12.17 [variation margin close]
10897840 0 12.12.2017 15:48 1,51309E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58909 0 0 0 Si-12.17 [variation margin open]
10903172 0 13.12.2017 13:00 1,51317E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59213 0 0 608 Si-12.17 [variation margin close]
10903173 0 13.12.2017 13:00 1,51317E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59213 0 0 0 Si-12.17 [variation margin open]
10905906 0 13.12.2017 15:48 1,51318E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 59072 0 0 -282 Si-12.17 [variation margin close]
10905907 0 13.12.2017 15:48 1,51318E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 59072 0 0 0 Si-12.17 [variation margin open]
10911277 0 14.12.2017 13:00 1,51326E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 2 58674 0 0 -796 Si-12.17 [variation margin close]
10911278 0 14.12.2017 13:00 1,51326E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 2 58674 0 0 0 Si-12.17 [variation margin open]
10912285 71645351 14.12.2017 14:48 1,51326E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 506789 DEAL_REASON_EXPERT 69352663 1 58661 -0,5 0 -13 Si-12.17 PartialClose position_2 25588426
10913632 0 14.12.2017 15:48 1,51327E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58783 0 0 109 Si-12.17 [variation margin close]
10913633 0 14.12.2017 15:48 1,51327E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58783 0 0 0 Si-12.17 [variation margin open]
10919412 0 15.12.2017 13:00 1,51334E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58912 0 0 129 Si-12.17 [variation margin close]
10919413 0 15.12.2017 13:00 1,51334E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58912 0 0 0 Si-12.17 [variation margin open]
10921766 0 15.12.2017 15:48 1,51335E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58946 0 0 34 Si-12.17 [variation margin close]
10921767 0 15.12.2017 15:48 1,51335E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58946 0 0 0 Si-12.17 [variation margin open]
10927382 0 18.12.2017 13:00 1,5136E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58630 0 0 -316 Si-12.17 [variation margin close]
10927383 0 18.12.2017 13:00 1,5136E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58630 0 0 0 Si-12.17 [variation margin open]
10929913 0 18.12.2017 15:48 1,51361E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58664 0 0 34 Si-12.17 [variation margin close]
10929914 0 18.12.2017 15:48 1,51361E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58664 0 0 0 Si-12.17 [variation margin open]
10934874 0 19.12.2017 13:00 1,51369E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58635 0 0 -29 Si-12.17 [variation margin close]
10934875 0 19.12.2017 13:00 1,51369E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58635 0 0 0 Si-12.17 [variation margin open]
10936988 0 19.12.2017 15:48 1,5137E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58629 0 0 -6 Si-12.17 [variation margin close]
10936989 0 19.12.2017 15:48 1,5137E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58629 0 0 0 Si-12.17 [variation margin open]
10941561 0 20.12.2017 13:00 1,51377E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58657 0 0 28 Si-12.17 [variation margin close]
10941562 0 20.12.2017 13:00 1,51377E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58657 0 0 0 Si-12.17 [variation margin open]
10943405 0 20.12.2017 15:48 1,51378E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58684 0 0 27 Si-12.17 [variation margin close]
10943406 0 20.12.2017 15:48 1,51378E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58684 0 0 0 Si-12.17 [variation margin open]
10948277 0 21.12.2017 13:00 1,51386E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_VMARGIN 69352663 1 58560 0 0 -124 Si-12.17 [variation margin close]
10948278 0 21.12.2017 13:00 1,51386E+12 DEAL_TYPE_BUY DEAL_ENTRY_IN 0 DEAL_REASON_VMARGIN 69352663 1 58560 0 0 0 Si-12.17 [variation margin open]
10949780 0 21.12.2017 15:45 1,51387E+12 DEAL_TYPE_SELL DEAL_ENTRY_OUT 0 DEAL_REASON_CLIENT 69352663 1 58560 0 0 0 Si-12.17 [instrument expiration] 26163141

Этот пример взят из истории сделок на демо-счете на рынке FORTS по фьючерсу на пару USDRUR. Из таблицы видно, что вход в позицию был разбит на два ордера с равным лотом. На две части разбит и выход. ID ордера, открывшего сделки для каждого входа и выхода (за исключением вариационной маржи), были ненулевые. Но последняя сделка, закрывшая позицию, пришла с ID ордера, равным нулю. Это произошло, потому что позиция была закрыта не вручную, а из-за экспирации актива.

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

Создадим для этого структуру:

//+------------------------------------------------------------------+
//| Структура, хранящая финансовый результат и                       |
//| основную информацию по выбранной позиции.                        |
//+------------------------------------------------------------------+
struct DealDetales
  {
   string            symbol;                // символ 
   datetime          DT_open;               // дата открытия
   ENUM_DAY_OF_WEEK  day_open;              // день недели открытия
   datetime          DT_close;              // дата закрытия
   ENUM_DAY_OF_WEEK  day_close;             // день недели закрытия
   double            volume;                // объем (лоты)
   bool              isLong;                // признак лонг/шорт
   double            price_in;              // цена входа в позицию
   double            price_out;             // цена выхода из позиции
   double            pl_oneLot;             // прибыль/убыток, если бы торговали одним лотом
   double            pl_forDeal;            // реальная прибыль/убыток который реально был с учетом комиссии
   string            open_comment;          // комментарий на момент открытия
   string            close_comment;         // комментарий на момент закрытия
  };

Эту структуру формирует метод getDealsDetales. В нем используются 3 ключевых private метода: 

Рассмотрим подробнее метод MA_price. К примеру, если позиция была открыта в несколько контрактов и по разным ценам, то будет некорректно фиксировать в качестве цены открытия позиции цену самой первой сделки. Это приведет к погрешностям в дальнейших вычислениях. Используем средневзвешенную величину. Усреднять будем цены открытия/закрытия позиций, а взвешивать их по объему открытых сделок. Релизация этой логики в коде выглядит так:

//+------------------------------------------------------------------+
//| Подсчитывается средневзвешенная цена открытия                    |
//+------------------------------------------------------------------+
double CDealHistoryGetter::MA_price(double &prices[],double &lots[])
  {
   double summ=0;                           // Сумма взвешенных цен
   double delemetr=0;                       // Сумма весов
   int total=ArraySize(prices);
   for(int i=0;i<total;i++)                 // Цикл суммирования
     {
      summ+=(prices[i]*lots[i]);
      delemetr+=lots[i];
     }
   return summ/delemetr;                    // Расчет средней
  }
Относительно торгуемых лотов: этот случай более запутанный, но разобраться в нем несложно. Рассмотрим пример, когда мы динамически управляем позицией и постепенно прибавляем или сокращаем количество лотов:

In Out
Open (t1) 1 0
t2 2 0
t3 5 3
t4 1 0
t5 0 1
t6 1 0
t7 0 1
t8 1 0
t9 0 1
Close (t10) 0 5
Total  11  11 

Во время существования позиции мы постоянно то докупали, то продавали торгуемый актив (иногда поочередно, а иногда почти одновременно). В результате у нас накопилось 11 лотов на покупку и на продажу. Но это не означает, что максимально достигнутый объем позиции равнялся 11 лотам. Запишем все входы и выходы в ряд: наращивание и входы — со знаком +, а выходы и сокращения — со знаком -.  Просуммируем их, как суммируется кривая PL.


Объем сделки Объем позиции
deal 1 1 1
deal  2 2 3
deal 3 5 8
deal 4 -3 5
deal 5 1 6
deal 6 -1 5
deal 7 1 6
deal 8 -1 5
deal 9 1 6
deal 10 -1 5
deal 11  -5  0

Из этой таблицы мы явно видим, что максимально достигавшаяся сумма лотов была равна 8. Это и есть искомая величина.

Вот как функция, отражающая количество лотов, реализована в коде:

//+------------------------------------------------------------------+
//| Рассчитываем, какой объем позиции был на самом деле,             |
//| и получаем информацию об объеме самой последней позиции          |
//+------------------------------------------------------------------+
double CDealHistoryGetter::calcContracts(double &Arr[],GetContractType type)
  {
   int total;

   if((total=ArraySize(Arr))>1)                        // если размер массива более одного
     {
      double lotArr[];
      addArr(lotArr,Arr[0]);                           // добавляем первый лот в массив 
      for(int i=1;i<total;i++)                         // цикл со второго элемента массива
         addArr(lotArr,(lotArr[i-1]+Arr[i]));          // добавляем сумму прошлого и текущего лотов в массив (lotArr[i-1]+Arr[i]))

      if(type==GET_REAL_CONTRACT)
         return lotArr[ArrayMaximum(lotArr)];          // возвращаем максимальный реально торговавшийся лот
      else
         return lotArr[ArraySize(lotArr)-1];           // возвращаем последний торговавшийся лот
     }
   else
      return Arr[0];
  }

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

Рассмотрим функцию isBorderPoint, которая была создана для фильтрации ненужных сделок. Исходя из структуры наших данных, важность сделки мы можем определить, исходя из 4 показателей:

Создадим перечисление:

//+--------------------------------------------------------------------+
//| Вспомогательное перечисление, указывающее на тип конкретной записи |
//| Сама запись представлена структурой DealData                       |
//+--------------------------------------------------------------------+
enum BorderPointType
  {
   UsualInput,          // обычный тип входа (DEAL_ENTRY_IN) - Начало сделки
   UsualOutput,         // обычный тип выхода (DEAL_ENTRY_OUT) - завершение сделки
   OtherPoint,          // начисление баланса, корректировки позиции, выводы, вариационная маржа — все, что мы игнорируем
   InOut,               // переворот позиции (DEAL_ENTRY_INOUT)
   OutBy                // выход из позиции по встречному ордеру (DEAL_ENTRY_OUT_BY)
  };
Из пяти представленных вариантов нас интересуют только четыре. Все ненужные сделки попадают под вариант OtherPoint. Сочетания параметров, удовлетворяющие каждому из вариантов перечисления, представлены в таблице. Код функции вы можете посмотреть в файлах, приложенных к статье. 
Вариант перечисления ID ордера ENUM_DEAL_ENTRY ENUM_DEAL_TYPE ENUM_DEAL_REASON
UsualInput  >0 DEAL_ENTRY_IN DEAL_TYPE_BUY DEAL_REASON_CLIENT
DEAL_REASON_EXPERT



DEAL_TYPE_SELL DEAL_REASON_WEB


DEAL_REASON_MOBILE



UsualOut >=0(=0 если была экспирация) DEAL_ENTRY_OUT DEAL_TYPE_BUY DEAL_REASON_CLIENT
DEAL_REASON_EXPERT



DEAL_REASON_WEB



DEAL_TYPE_SELL DEAL_REASON_MOBILE


DEAL_REASON_SL



DEAL_REASON_TP



DEAL_REASON_SO



OtherPoint  0 DEAL_ENTRY_IN DEAL_TYPE_BUY DEAL_REASON_ROLLOVER
DEAL_TYPE_SELL



DEAL_TYPE_BALANCE



DEAL_TYPE_CREDIT



DEAL_TYPE_CHARGE



DEAL_TYPE_CORRECTION



DEAL_TYPE_BONUS DEAL_REASON_VMARGIN


DEAL_TYPE_COMMISSION



DEAL_TYPE_COMMISSION_DAILY



DEAL_ENTRY_OUT DEAL_TYPE_COMMISSION_MONTHLY


DEAL_TYPE_COMMISSION_AGENT_DAILY



DEAL_TYPE_COMMISSION_AGENT_MONTHLY



DEAL_TYPE_INTEREST DEAL_REASON_SPLIT


DEAL_TYPE_BUY_CANCELED



DEAL_TYPE_SELL_CANCELED



DEAL_DIVIDEND



DEAL_DIVIDEND_FRANKED



DEAL_TAX



InOut  >0 DEAL_ENTRY_INOUT  DEAL_TYPE_BUY DEAL_REASON_CLIENT
DEAL_REASON_EXPERT



DEAL_TYPE_SELL DEAL_REASON_WEB


DEAL_REASON_MOBILE



OutBy  >0 DEAL_ENTRY_OUT_BY  DEAL_TYPE_BUY DEAL_REASON_CLIENT
DEAL_REASON_EXPERT



DEAL_TYPE_SELL DEAL_REASON_WEB


DEAL_REASON_MOBILE



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

//+------------------------------------------------------------------+
//| Формируется одна запись по каждой выбранной позиции в inputParam |
//+------------------------------------------------------------------+

void CDealHistoryGetter::getDeals_forSelectedKeeper(DealKeeper &inputParam,DealDetales &ans[])
  {
   ArrayFree(ans);
   int total=ArraySize(inputParam.deals);
   DealDetales detales;                                          // переменная, в которую помещаем результаты из последующего цикла
   detales.symbol=inputParam.symbol;
   detales.volume= 0;
   detales.pl_forDeal=0;
   detales.pl_oneLot=0;
   detales.close_comment= "";
   detales.open_comment = "";
   detales.DT_open=0;

// Флаг, нужно ли добавлять данную позицию в набор
   bool isAdd=false;
   bool firstPL_setted=false;
// Массивы цен входа, цен выхода, лоты входа, лоты выхода, контракты
   double price_In[],price_Out[],lot_In[],lot_Out[],contracts[]; // 404 строка

   for(int i=0;i<total;i++)                                      // цикл по сделкам (у всех сделок единый ID, но позиций может быть более, чем одна, если тип сделки InOut)
     {
      BorderPointType type;                                      // тип сделки 408 строка
      double pl_total=0;

      if(isBorderPoint(inputParam.deals[i],type))                // узнаем, пограничная ли это сделка, и ее тип 301 строка
        {
          // 413 строка
        } // 414 строка
      else
        {
/*
         Если сделка не пограничная, то просто фиксируем финансовый результат.
         Тут могут быть только вариационная маржа и различные коррекции.
         Пополнения и снятия со счета фильтруются при получении исходных данных 
*/
         detales.pl_forDeal+=(inputParam.deals[i].profit+inputParam.deals[i].comission);
         detales.pl_oneLot+=inputParam.deals[i].profit/calcContracts(contracts,GET_LAST_CONTRACT);
        }
     }

// Фильтруем активные позиции и не сохраняем их
   if(isAdd && PositionSelect(inputParam.symbol))                 // 541 строка
     {
      if(PositionGetInteger(POSITION_IDENTIFIER)==inputParam.ID)
         isAdd=false; 
     }                                                            // 546 строка

// Сохраняем зафиксированные и более неактивные позиции
   if(isAdd)
     {
      detales.price_in=MA_price(price_In,lot_In);
      detales.price_out=MA_price(price_Out,lot_Out);

      detales.volume=calcContracts(contracts,GET_REAL_CONTRACT);
      addArr(ans,detales);
     }
  }

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

Если же сделка не попадает под первое условие, то единственное требуемое действие — это подсчет прибыли/убытка по ней. Дело в том, что передаваемая нами история содержит фактически начисленную прибыль/убыток, которая разбита на сделки. Каждая сделка отражает изменение PL, начиная от прошлой сделки до момента свершения анализируемой. Общая прибыль за позицию равняется сумме PL всех этих сделок. Если считать прибыль/убыток просто как разность между ценами открытия и закрытия, то мы упустим ряд факторов: проскальзывание, изменение объема внутри позиции, комиссии и сборы.

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

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

if(type==UsualInput)                                                   // если первоначальный вход или же увеличение позиции
  {
   if(detales.DT_open==0)                                              // присваиваем дату открытия позиции
     {
      detales.DT_open=inputParam.deals[i].DT;
      detales.day_open=getDay(inputParam.deals[i].DT);
     }
   detales.isLong=inputParam.deals[i].type==DEAL_TYPE_BUY;             // определяем направление позиции
   addArr(price_In,inputParam.deals[i].price);                         // сохраняем цену входа 
   addArr(lot_In,inputParam.deals[i].volume);                          // сохраняем количество лотов

   pl_total=(inputParam.deals[i].profit+inputParam.deals[i].comission);
   detales.pl_forDeal+=pl_total;                                       // прибыль/убыток от сделки с учетом комиссии
   if(!firstPL_setted)
     {
      detales.pl_oneLot=pl_total/inputParam.deals[i].volume;           // прибыль/убыток от сделки с учетом комиссии, если торговать одним лотом
      firstPL_setted=true;
     }
   else
      detales.pl_oneLot=inputParam.deals[i].profit/calcContracts(contracts,GET_LAST_CONTRACT);                 

   if(StringCompare(inputParam.deals[i].comment,"")!=0)                // комментарий по сделке
      detales.open_comment+=(StringCompare(detales.open_comment,"")==0 ?
                             inputParam.deals[i].comment :
                             (" | "+inputParam.deals[i].comment));
   addArr(contracts,inputParam.deals[i].volume);                       // добавляем объем сделки со знаком "+"
  }

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

if(type==UsualOut || type==OutBy)                         // закрытие позиции
  {
/*
           Не сохраняем сразу, так как выходов может быть несколько (постепенно один за другим)
           Поэтому во избежание потери части данных просто устанавливаем флаг включенным 
*/
   if(!isAdd)isAdd=true;                                  // флаг на сохранение позиции 

   detales.DT_close=inputParam.deals[i].DT;               // дата закрытия
   detales.day_close=getDay(inputParam.deals[i].DT);      // день закрытия
   addArr(price_Out,inputParam.deals[i].price);           // сохраняем цены выхода
   addArr(lot_Out,inputParam.deals[i].volume);            // сохраняем объем выхода

   pl_total=(inputParam.deals[i].profit+inputParam.deals[i].comission);          // Прибыль/убыток от сделки с учетом комиссии
   detales.pl_forDeal+=pl_total;

   if(i==total-1)
      detales.pl_oneLot+=pl_total/calcContracts(contracts,GET_LAST_CONTRACT);    // Прибыль/убыток от сделки с учетом комиссии, если торговать одним лотом
   else
      detales.pl_oneLot+=inputParam.deals[i].profit/calcContracts(contracts,GET_LAST_CONTRACT); // Прибыль/убыток от сделки с учетом комиссии, если торговать одним лотом

   if(StringCompare(inputParam.deals[i].comment,"")!=0)                          // Комментарий по сделке
      detales.close_comment+=(StringCompare(detales.close_comment,"")==0 ?
                              inputParam.deals[i].comment :
                              (" | "+inputParam.deals[i].comment));
   addArr(contracts,-inputParam.deals[i].volume);                                // Добавляем объем сделки со знаком "-"
  }

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

Условие InOut — комбинация из второго и первого условий. Оно наступает только один раз и заканчивается переворотом позиции.

if(type==InOut)                                                                 // Переворот позиции
  {
/*
           Часть 1:
           Сохраняем предыдущую позицию
*/
   firstPL_setted=true;
   double closingContract=calcContracts(contracts,GET_LAST_CONTRACT);           // закрываемый контракт
   double myLot=inputParam.deals[i].volume-closingContract;                     // открываемый контракт

   addArr(contracts,-closingContract);                                          // добавляем объем сделки со знаком "-"
   detales.volume=calcContracts(contracts,GET_REAL_CONTRACT);                   // получаем максимальный реально существовавший объем сделки

   detales.DT_close=inputParam.deals[i].DT;                                     // дата закрытия
   detales.day_close=getDay(inputParam.deals[i].DT);                            // день закрытия
   addArr(price_Out,inputParam.deals[i].price);                                 // цена выхода
   addArr(lot_Out,closingContract);                                             // объем выхода

   pl_total=(inputParam.deals[i].profit*closingContract)/inputParam.deals[i].volume;        // рассчитываем PL за сделку закрытия
   double commission_total=(inputParam.deals[i].comission*closingContract)/inputParam.deals[i].volume;
   detales.pl_forDeal+=(pl_total+commission_total);
   detales.pl_oneLot+=pl_total/closingContract;                                 // прибыль/убыток от сделки с учетом комиссии, если торговать одним лотом
   if(StringCompare(inputParam.deals[i].comment,"")!=0)                         // сохраняем комментарий закрытия
      detales.open_comment+=(StringCompare(detales.open_comment,"")==0 ?
                             inputParam.deals[i].comment :
                             (" | "+inputParam.deals[i].comment));

   detales.price_in=MA_price(price_In,lot_In);                                  // получаем цену входа в позицию (усредненную)
   detales.price_out=MA_price(price_Out,lot_Out);                               // получаем цену выхода из позиции (усредненную)
   addArr(ans,detales);                                                         // добавляем сформированую позицию
   if(isAdd)isAdd=false;                                                        // обнуляем флаг, если он был включен

                                                                                // Чистка части данных
   ArrayFree(price_In);
   ArrayFree(price_Out);
   ArrayFree(lot_In);
   ArrayFree(lot_Out);
   ArrayFree(contracts);
   detales.close_comment="";
   detales.open_comment="";
   detales.volume=0;

/*
           Часть 2:
           Сохраняем новую позицию, предварительно удалив часть данных из массива detales
*/

   addArr(contracts,myLot);                                                     // Добавляем лот открытия позиции            

   pl_total=((inputParam.deals[i].profit+inputParam.deals[i].comission)*myLot)/inputParam.deals[i].volume; // Прибыль/убыток от сделки с учетом комиссии
   detales.pl_forDeal=pl_total;
   detales.pl_oneLot=pl_total/myLot;                                            //Прибыль/убыток от сделки с учетом комиссии, если торговать одним лотом
   addArr(lot_In,myLot);                                                        // Добавляем лот входа

   detales.open_comment=inputParam.deals[i].comment;                            // Сохраняем комментарий

   detales.DT_open=inputParam.deals[i].DT;                                      // Сохраняем дату открытия
   detales.day_open=getDay(inputParam.deals[i].DT);                             // Сохраняем день открытия
   detales.isLong=inputParam.deals[i].type==DEAL_TYPE_BUY;                      // Определяем направление сделки
   addArr(price_In,inputParam.deals[i].price);                                  // Сохраняем цену входа
  }

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

Instrument Open DT Open day Close DT Close day Contracts Direction Price open Price close PL for 1 lot PL for position Open comment Close comment
RTS-12.17 17.11.2017 19:53 FRIDAY 17.11.2017 19:54 FRIDAY 2.00000000 Long 113200.00000000 113180.00000000 -25.78000000 -55.56000000
RTS-12.17 17.11.2017 19:54 FRIDAY 17.11.2017 19:54 FRIDAY 2.00000000 Short 113175.00000000 113205.00000000 -58.47000000 -79.33000000
RTS-12.17 17.11.2017 19:58 FRIDAY 17.11.2017 19:58 FRIDAY 1.00000000 Short 113240.00000000 113290.00000000 -63.44000000 -63.44000000
RTS-12.17 17.11.2017 19:58 FRIDAY 17.11.2017 19:58 FRIDAY 1.00000000 Long 113290.00000000 113250.00000000 -51.56000000 -51.56000000
Si-12.17 17.11.2017 20:00 FRIDAY 17.11.2017 20:00 FRIDAY 10.00000000 Long 59464.40000000 59452.80000000 -23.86000000 -126.00000000
Si-12.17 17.11.2017 20:00 FRIDAY 17.11.2017 20:00 FRIDAY 5.00000000 Short 59453.20000000 59454.80000000 -5.08666667 -13.00000000
Si-12.17 17.11.2017 20:02 FRIDAY 17.11.2017 20:02 FRIDAY 1.00000000 Short 59460.00000000 59468.00000000 -9.00000000 -9.00000000
Si-12.17 17.11.2017 20:02 FRIDAY 17.11.2017 20:03 FRIDAY 2.00000000 Long 59469.00000000 59460.00000000 -14.50000000 -20.00000000
Si-12.17 21.11.2017 20:50 TUESDAY 21.11.2017 21:06 TUESDAY 2.00000000 Long 59467.00000000 59455.00000000 -13.00000000 -26.00000000
Si-12.17 23.11.2017 17:41 THURSDAY 21.12.2017 15:45 THURSDAY 2.00000000 Long 58736.50000000 58610.50000000 -183.00000000 -253.50000000 Open test position | Open test position PartialClose position_2 | [instrument expiration]
RTS-12.17 23.11.2017 18:07 THURSDAY 14.12.2017 14:45 THURSDAY 1.00000000 Short 115680.00000000 114110.00000000 1822.39000000 1822.39000000 Open test position_2
RTS-3.18 30.01.2018 20:22 TUESDAY 30.01.2018 20:22 TUESDAY 2.00000000 Short 127675.00000000 127710.00000000 -61.01000000 -86.68000000
RTS-3.18 30.01.2018 20:24 TUESDAY 30.01.2018 20:24 TUESDAY 1.00000000 Long 127730.00000000 127710.00000000 -26.49000000 -26.49000000
RTS-3.18 30.01.2018 20:24 TUESDAY 30.01.2018 20:25 TUESDAY 1.00000000 Long 127730.00000000 127680.00000000 -60.21000000 -60.21000000
RTS-3.18 30.01.2018 20:25 TUESDAY 30.01.2018 20:25 TUESDAY 1.00000000 Long 127690.00000000 127660.00000000 -37.72000000 -37.72000000
RTS-3.18 30.01.2018 20:25 TUESDAY 30.01.2018 20:26 TUESDAY 1.00000000 Long 127670.00000000 127640.00000000 -37.73000000 -37.73000000
RTS-3.18 30.01.2018 20:29 TUESDAY 30.01.2018 20:30 TUESDAY 1.00000000 Long 127600.00000000 127540.00000000 -71.45000000 -71.45000000

Глава 2. Формирование пользовательского торгового отчета

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

  1. Отчет будет содержать и стандартные графики PL, и доработанные, для более наглядной оценки результативности. Построение графиков начинается с нуля (вне зависимости от начального капитала) — это повысит объективность оценки.
  2. Будет формироваться и график Buy and hold стратегии, сопоставимый с графиком PL (их будет рассчитывать одна функция). Оба этих графика будут строиться по всем активам, фигурирующим в отчете.
  3. Основные коэффициенты и результаты трейдинга должны отражаться в виде таблицы.
  4. Должны строиться дополнительные графики: например, Pl по дням.

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

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

class CReportGetter
  {
public:
                     CReportGetter(DealDetales &history[]);                      // можно задать только историю в подготовленном формате
                     CReportGetter(DealDetales &history[],double balance);       // можно задать как историю, так и баланс, относительно которого будут вестись расчеты
                    ~CReportGetter();

   bool              get_PLChart(PL_chartData &pl[],
                                 PL_chartData &pl_forOneLot[],
                                 PL_chartData &Indicative_pl[],
                                 string &Symb[]);                                // графики PL

   bool              get_BuyAndHold(PL_chartData &pl[],
                                    PL_chartData &pl_forOneLot[],
                                    PL_chartData &Indicative_pl[],
                                    string &Symb[]);                             // графики buy and hold

   bool              get_PLHistogram(PL_chartData &pl[],
                                     PL_chartData &pl_forOneLot[],
                                     string &Symb[]);                            // гистограмма накопления прибыли и убытка

   bool              get_PL_forDays(PLForDaysData &ans,
                                    DailyPL_calcBy calcBy,
                                    DailyPL_calcType type,
                                    string &Symb[]);                             // PL по дням недели

   bool              get_extremePoints(PL_And_Lose &total,
                                       PL_And_Lose &forDeal,
                                       string &Symb[]);                          // крайние точки (максимальные и минимальные показатели за сделку и с накоплением) 

   bool              get_AbsolutePL(PL_And_Lose &total,
                                    PL_And_Lose &average,
                                    string &Symb[]);                             // абсолютные показатели (накопленные и усредненные PL)

   bool              get_PL_And_Lose_percents(PL_And_Lose &ans,string &Symb[]);  // диаграмма распределения прибыльных и убыточных сделок

   bool              get_totalResults(TotalResult_struct &res,string &Symb[]);   // таблица основных показателей

   bool              get_PLDetales(PL_detales &ans,string &Symb[]);              // краткая сводка по графику PL

   void              get_Symbols(string &SymbArr[]);                             // получение списка имеющихся в истории символов

private:
   DealDetales       history[];                                                  // хранит историю торгов
   double            balance;                                                    // хранит значение баланса

   void              addArray(DealDetales &Arr[],DealDetales &value);            // добавление в динамический массив данных
   void              addArray(PL_chartData &Arr[],PL_chartData &value);          // добавление в динамический массив данных
   void              addArray(string &Arr[],string value);                       // добавление в динамический массив данных
   void              addArray(datetime &Arr[],datetime value);                   // добавление в динамический массив данных

   void              sortHistory(DealDetales &arr[],bool byClose);               // Сортировка истории по дате открытия или закрытия

   void              cmpDay(int i,ENUM_DAY_OF_WEEK day,ENUM_DAY_OF_WEEK etaloneDay,PLDrowDown &ans);      // Заполнение структуры PLDrowDown

   bool              isSymb(string &Symb[],int i);                               // Проверка Есть ли i-тый символ из истории в массиве Symb

   bool              get_PLChart_byHistory(PL_chartData &pl[],
                                           PL_chartData &pl_forOneLot[],
                                           PL_chartData &Indicative_pl[],
                                           DealDetales &historyData[]);          // Графики PL по переданной истории

   ENUM_DAY_OF_WEEK  getDay(datetime DT);                                        // Получение дня торгов из даты

  };

3 вида графика PL

Чтобы построить график прибыли и убытка, отсортируем исходные данные по дате закрытия позиции. Не будем вдаваться в подробности построения — оно будет стандартным. Он будет состоять из двух графиков с датами закрытия позиций по оси Х.   Первым будет собственно график PL, а вторым — накопленная просадка относительно максимального значения PL, в процентах. Рассмотрим 3 разновидности графиков.

  1. Стандартный график накопленной прибыли.
  2. График накопленной прибыли без учета объема торгов — как если бы мы на всей истории и по всем инструментам торговали бы одним лотом.
  3. График прибыли и убытка, нормированный на максимальный убыточный (или прибыльный) лот. Если график PL находится в красной зоне, то мы делим i-тый убыток на максимальный прибыльный лот. Если график находится в зеленой зоне, то мы делим i-тую прибыль на максимальный убыточный лот.

Первые два графика дают нам представление о том, как изменение объема торгов влияет на прибыль/убыток. Третий график позволяет представить экстремальную ситуацию: если бы вдруг началась череда убытков, и эти убытки равнялись бы максимально возможным историческим потерям за один лот (чего практически не может произойти). Он показывает, сколько максимальных убытков подряд может вынести наш график PL до того, как дойдет до 0 (вся прибыль будет сведена на нет) — или же наоборот, что будет, когда все убытки будут сведены на нет чередой максимальных прибылей. Ниже приведены рассматриваемые типы графиков:

График реальной PL

график PL на 1 лот

Индикативный PL

График PL при торговле одним лотом выглядит куда привлекательнее, чем реальный график. К тому же на реальных торгах прибыль превышала гипотетическую "однолотовую" всего на 30 %. Размер лота варьировался от 1 до 10. Просадка в процентах доходила до 30 000 %. Звучит ужасающе, но не все так страшно. Как мы уже говорили, график PL строится с нулевой отметки, а просадка рассчитывается относительно максимального значения кривой PL. В один из моментов времени, пока убыток еще не достиг максимума (красная зона на графике), прибыль поднималась на несколько рублей, а потом падала до - 18 000 р. Отсюда и берутся эти несуразные значения. В реальности же просадка была не более 25 % на один лот и 50 % — по факту.

Просадка при торговле на 1 лот

Общая просадка

Нормированный график PL тоже застрял на одном значении. Это указывает на необходимость поменять подход к торговле (допустим, изменить метод управления лотами) и, как вариант, переоптимизировать торговые алгоритмы. График PL с учетом изменения лотов выглядит хуже "однолотового" за тот же период, но всё же такое управление позициями принесло свои плоды: всплески просадок стали слабее.

Класс, формирующий торговый отчет, называется CReportGetter, а графики строит метод get_PLChart_byHistory. Этот метод подробно прокомментирован в файлах с исходным кодом. Он работает следующим образом:

  1. Осуществляется проход по переданной истории.
  2. Каждая позиция участвует в расчете PL, максимальной прибыли и просадки.
  3. Просадка рассчитывается по накоплению относительно максимальной достигнутой прибыли. Если график PL сразу же начал опускаться в красную зону, то просадка пересчитывается с каждым обновлением минимумов. В данном случае максимальная прибыль будет равна нулю, а просадка на i-том и всех предыдущих элементах пересчитывается по максимальному убытку. Просадка в этот момент равняется 100 %. Как только максимальная прибыль оказывается больше нуля (кривая PL переходит в положительную часть графика), то просадка всегда рассчитывается относительно максимальной прибыли.

Проведем анализ, сопоставив 3 графика. Выявляется гораздо больше недочетов, чем если бы мы рассматривали один стандартный график прибыли и убытка. Графики PL стратегии buy and hold, как уже было упомянуто, строятся по тому же алгоритму. Для примера представлю только один из графиков. Остальные построятся автоматически при запуске скрипта визуализации.

PL по стратегии Buy and hold

График показывает, что если бы торговля теми же активами велась по стратегии Buy and hold в тех же объемах, то график PL едва ли сошелся бы к нулю, а просадка была бы порядка 80 000 р (вместо 70 000 максимально достигнутой прибыли).

Гистограмма накопления PL

Этот вид графика представляет собой две гистограммы, наложенные друг на друга. Их построение немного отличается от стандартного графика PL. Гистограмма прибылей создается постепенным суммированием только прибыльных сделок. Если i-тая позиция является убыточной, то мы игнорируем ее значение и записываем предыдущее значение на i-тый временной интервал. Гистограмма убыточных сделок строится по зеркальной логике. Ниже приведены графики гистограмм прибыли и убытка по нашим данным.

Гистограмма реальных PL

Гистограмма PL при торговли на один лот

Привычный нам график PL — простая разность гистограммы прибыли и убытка. Первое, что бросается в глаза при анализе — это то, как сильно реально полученная прибыль отличается от наторгованной. Если реально полученая прибыль превосходила прибыль от торговли одним лотом примерно на 30%, то анализ графика по гистограммам прибыли дает разницу почти на 50 %. Секрет недополученных 20% прибыли кроется в отличии динамики роста прибыли над убытками на первом и втором графиках. Проиллюстрируем сказанное. Построим по этим гистограммам два графика динамики прибыли и убытка. Формула предельна проста:

Формула Профит фактора

Динамика профит фактора

Реальная динамика профит фактора

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

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

Гистограмма PL в зависимости от Profit factor

График PL в 
зависимости от Profit factor

Здесь синяя гистограмма — это линейное начисление убытка (по сути, это график от 0 до 100).

Графики показывают, что коэффициент превышения прибылей над убытками (профит-фактор) всегда должен быть больше единицы. Идеальный вариант — если он постепенно растет. При этом PL графика будет расти экспоненциально. К сожалению, это несбыточно в долгосрочной перспективе, но может случиться в короткий период удачи, если повышать лот после каждой прибыльной сделки. Рассмотренный пример дает право со 100% уверенностью утверждать, что главное в торговле — не то, как меняется график PL, а то, как себя ведет гистограмма прибыльных и убыточных сделок, а следовательно — какова динамика изменения профит-фактора.

Теперь поговорим про недополученные 20% прибыли. На графике динамики роста прибыли на один лот  мы видим, что приросты прибыли зависли на отметке [1,4 — 1,6]. Это выше, чем [1.2 — 1.4]. Со временем это различие и "съело" 20% потенциальной прибыли. Иначе говоря, управление размером позиций не прошло даром. Если вы используете методы наподобие мартингейла/антимартингейла, то эти графики — и особенно показатели, рассчитанные на их основе, — могут дать много полезной информации для анализа. Эти графики строит функция get_PLHistogram. 

Прибыль и убыток по дням

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

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

PL по дням

Дневное 
кол - во сделок

Как видно из графика по всем дням недели, положительные сделки перевешивали отрицательные, однако отрицательных было всегда больше (и это в целом норма для алгоритмического трейдинга). И ни в один из дней отрицательных сделок не было больше, чем положительных, более чем на 25 %, что тоже в целом нормально.

График крайних точек и абсолютных показателей

График крайних точек — это гистограмма из 4 столбцов, разделенных на две группы. Крайние точки — это максимальные и минимальные отклонения на графике PL (максимально достигнутая прибыль и максимальная накопленная просадка). Еще два столбца — максимально прибыльная и убыточная позиции. Обе группы данных строятся по данным реальных торгов, а не по данным прибыли и убытка за одну сделку. Подсчет максимальной прибыли по кривой PL — это самая высокая точка на ней. Методику расчета максимальной просадки я описывал выше. Для большей конкретики сверну все в формулу:

Min Max

График крайних 
точек

Этот график иллюстрирует, какой был максимальный разброс прибыли и убытка. Доля максимальной просадки по сравнению с максимальной прибылью составила 30 %, а максимально убыточной сделки по сравнению с максимально прибыльной — 60%. В коде это реализуется как цикл с постепенным сравнением данных кривой PL по обозначенным условиям.

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

Общая Усредненная
Прибыль 323237 2244,701
Просадка 261534 1210,806

Таблицы с краткой сводкой по графику PL и основными показателями результатов торговли

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

//+------------------------------------------------------------------+
//| Структура результатов торговли                                   |
//+------------------------------------------------------------------+
struct TotalResult_struct
  {
   double            PL;                                // Общая прибыль или убыток
   double            PL_to_Balance;                     // Отношение PL к текущему баллансу
   double            averagePL;                         // Средняя прибыль или убыток
   double            averagePL_to_Balance;              // Отношение averagePL к текущему балансу
   double            profitFactor;                      // Фактор прибыльности
   double            reciveryFactor;                    // Фактор востановления
   double            winCoef                            // Коэффициент выигрыша
   double            dealsInARow_to_reducePLTo_zerro;/* Если сейчас прибыль - сделок подряд с максимальным убытком за сделку,
                                                        чтобы свести текущую Прибыль по счету в ноль.
                                                        Если сейчас убыток - сделок подряд с максимальной прибылью за сделку,
                                                        чтобы убыток свести в ноль*/

   double            maxDrowDown_byPL;                  // Максимальная просадка по PL
   double            maxDrowDown_forDeal;               // Максимальная просадка за сделку
   double            maxDrowDown_inPercents;            // Максимальная просадка по PL в процентах к текущему балансу
   datetime          maxDrowDown_byPL_DT;               // Дата максимальной просадки по PL
   datetime          maxDrowDown_forDeal_DT             //Дата максимальной просадки за сделку

   double            maxProfit_byPL;                    // Максимальная прибыль по PL
   double            maxProfit_forDeal;                 // Максимальная прибыль за сделку
   double            maxProfit_inPercents;              // Максимальная прибыль по PL в процентах к текущему балансу
   datetime          maxProfit_byPL_DT;                 // Дата максимальной прибыль по PL
   datetime          maxProfit_forDeal_DT;              // Дата максимальной прибыли за сделку
  };
//+------------------------------------------------------------------+
//| Часть структуры PL_detales (объявлена ниже)                      |
//+------------------------------------------------------------------+
struct PL_detales_item
  {
   int               orders;                            // количество сделок
   double            orders_in_Percent;                 // количество ордеров в % к общему количеству ордеров
   int               dealsInARow;                       // Сделок подряд
   double            totalResult;                       // Суммарный результат в валюте депозита
   double            averageResult;                     // Средний результат в валюте депозита
  };
//+-------------------------------------------------------------------+
//| Краткая сводка по графику PL разбитая на 2 основополагающих блока |
//+-------------------------------------------------------------------+
struct PL_detales
  {
   PL_detales_item   profits;                           // Информация по прибыльным сделкам
   PL_detales_item   loses;                             // Информация по убыточным сделкам
  };

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

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

Показатель Значение
PL 65039
PL к балансу 21,8986532
Средний PL 180,1634349
Средний PL к балансу 0,06066109
Профит фактор 1,25097242
Фактор востановления 3,0838154
Коэфицент выигрыша 1,87645863
Сделок до сведения PL в нуль 24,16908213
Просадка по PL -23683
Просадка за сделку 1 лотом -2691
Просадка к балансу -0,07974074
Дата просадки по PL 24.07.2017 13:04
Дата просадки за сделку 1 лотом 31.01.2017 21:53
Прибыль по PL 73034
Прибыль за сделку 1 лотом 11266
Прибыль к балансу 0,24590572
Дата прибыли по PL 27.06.2017 23:42
Дата прибыли за сделку 1 лотом 14.12.2016 12:51

Вторая таблица выглядит следующим образом:

Прибыли Убытки
Средний результат 2244,701 -1210,81
Сделок подряд 5 10
Сделок всего 144 216
Сделок в % 0,398892 0,598338
Общий результат: 323237 -261534

Распределение убыточных и прибыльных позиций можно представить в виде круговой диаграммы:

% прибылей и просадок

Как видно из результата, 40 % сделок были положительными.

Заключение

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

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


К статье прилагаются следующие файлы:

  1. CSV-файл dealHistory.csv с историей торгов (находится в папке MQL5\Files\article_4803), по которой в текущей статье приводились примеры.
  2. Исходные файлы для MetaTrader 5, которые находятся в папке MQL5\Scripts\Get_TradigHistory
Вспомогательные файлы:
    1. Первая “test_1” – принимает в качестве параметра путь к файлу с приложенной тестовой историей и, в качестве второго параметра, путь к папке с результатами. Данная функция создает отчет по тестовым файлам переданным по первому параметру функции.
    2. Вторая “test_2” – Принимает в качестве параметра лишь путь ко второй папке (пути должны быть разными), где будет сохранен отчет Вашей торговли.

Основные файлы: