LifeHack для трейдера: "Тихая" оптимизация или Строим распределения трейдов

Vladimir Karputov | 15 сентября, 2016

Оглавление


Зачем нужны графики распределений?

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

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

Графики строятся путём вызова Google Charts, а для их визуального представления был выбран формат HTML. Схематично графики распределений на странице отображаются в виде таблицы:

 

Рис. 1. Вид HTML отчёта  

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


Быстрый запуск

Файл "DistributionOfProfits.mqh" мы размещаем в каталоге данных, в папке ...\MQL5\Include\. Скрипт "test_report.mq5" выполняет вызов построения графиков аналитики, начиная с указанной даты "start":

//+------------------------------------------------------------------+
//|                                                  test_report.mq5 |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
//---
input datetime start=D'2010.05.11 12:05:00';
#include <DistributionOfProfits.mqh>
//---
CDistributionOfProfits Analysis;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   Analysis.AnalysisTradingHistory(start);
   Analysis.ShowDistributionOfProfits();
  }
//+------------------------------------------------------------------+


1. Реконструкция позиций из торговой истории

Для чего нужно реконструировать позиции? Дело в том, что только так можно узнать по каждой позиции такие важные параметры, как прибыль и время ее открытия. Определимся с терминами:

прибыль позиции — суммарная прибыль всех сделок в данной позиции;

время открытия позиции — время отсылки первого ордера в этой позиции.

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

position deals orders 

Рис. 2. Связь POSITION_IDENTIFIER, DEAL_POSITION_ID и ORDER_POSITION_ID

Другими словами, если выделить из торговой истории сделки с одинаковыми DEAL_POSITION_ID, мы гарантированно сможем реконструировать позицию. Здесь нужно упомянуть такую ситуацию, как переворот позиции. Из справки по идентификатору позиции:

Переворот позиции изменяет ее идентификатор на тикет ордера, в результате которого произошел переворот.

1.1. Переворот позиции

Значит, если при перевороте позиция меняет свой идентификатор, то это будет уже другая позиция. Как же происходит присвоение идентификатора сделкам в таком случае? К предыдущей или перевернутой (новой) позиции будет относиться сделка, вызвавшая переворот? Для ответа на этот вопрос я написал простой пример — скрипт position_reversal_v1.mq5

Скрипт этот выполняет три торговых действия:

Во входных параметрах скрипта нужно указать время старта — этот параметр будет использоваться для запроса торговой истории. На моём примере: я запускал скрипт 2016.09.05 в 10:32:59, а во входном параметре сделал небольшой зазор и выбрал D'2016.09.05 10:32:00'.

После каждого действия скрипт распечатывает позицию и ее идентификатор, а также историю сделок и DEAL_POSITION_ID сделки. Итак, распечатка:

10:32:59.487    position_reversal_v1 (EURUSD,M3)    Buy 0.01, "EURUSD"
10:33:00.156    position_reversal_v1 (EURUSD,M3)    Position EURUSD POSITION_IDENTIFIER #96633525
10:33:00.156    position_reversal_v1 (EURUSD,M3)    Deal EURUSD volume 0.01 DEAL_POSITION_ID #96633525 profit 0.00
10:33:00.156    position_reversal_v1 (EURUSD,M3)    
10:33:06.187    position_reversal_v1 (EURUSD,M3)    Sell 0.02, "EURUSD"
10:33:06.871    position_reversal_v1 (EURUSD,M3)    Position EURUSD POSITION_IDENTIFIER #96633564
10:33:06.871    position_reversal_v1 (EURUSD,M3)    Deal EURUSD volume 0.01 DEAL_POSITION_ID #96633525 profit 0.00
10:33:06.871    position_reversal_v1 (EURUSD,M3)    Deal EURUSD volume 0.02 DEAL_POSITION_ID #96633525 profit -0.06
10:33:06.871    position_reversal_v1 (EURUSD,M3)    
10:33:12.924    position_reversal_v1 (EURUSD,M3)    PositionClose, "EURUSD"
10:33:13.593    position_reversal_v1 (EURUSD,M3)    Deal EURUSD volume 0.01 DEAL_POSITION_ID #96633525 profit 0.00
10:33:13.593    position_reversal_v1 (EURUSD,M3)    Deal EURUSD volume 0.02 DEAL_POSITION_ID #96633525 profit -0.06
10:33:13.593    position_reversal_v1 (EURUSD,M3)    Deal EURUSD volume 0.01 DEAL_POSITION_ID #96633564 profit -0.10
10:33:13.593    position_reversal_v1 (EURUSD,M3)    

Первая торговая операция — buy 0.01. Ее POSITION_IDENTIFIER равен 96633525. Также в торговой истории есть сделка — для нее установлен DEAL_POSITION_ID 96633525. Для этой сделки DEAL_POSITION_ID совпадает с POSITION_IDENTIFIER позиции — значит, эта сделка принадлежит нашей позиции. 

Вторая операция — sell 0.02. Эта операция привела к перевороту позиции: у нас была позиция buy 0.01, мы выполнили sell 0.02, в результате получили sell 0.01. У текущей позиции POSITION_IDENTIFIER изменился и стал равен 96633564 — значит, мы получили новую позицию. А что при этом можно наблюдать в истории сделок? Мы видим в торговой истории уже две сделки, обе они имеют одинаковые DEAL_POSITION_ID, равные 96633525. При этом у второй сделки "profit -0.06".

Значит, сделка, приведшая к перевороту позиции, относится к предыдущей позиции.

Третья торговая операция — buy 0.01. На данном этапе позиции уже нет (так как мы её закрыли), а в торговой истории есть три сделки: у первой и второй DEAL_POSITION_ID одинаковы и равны 96633525, а вот у третьей сделки этот идентификатор изменился на "96633564", к тому же появился "profit -0.10". То есть, третья сделка относится уже ко второй позиции, полученной в результате переворота первой.

1.2. Подсчёт прибыли позиции

Опираясь на проверенную в разделе 1.1. информацию, можно точно определиться с алгоритмом подсчёта прибыли для каждой реконструированной позиции. Суммарный финансовый результат всех сделок с одинаковым DEAL_POSITION_ID и будет расчётной прибылью позиции, у которой POSITION_IDENTIFIER равен DEAL_POSITION_ID. Но так как нас интересуют только торговые операции buy и sell, то нужно ввести ограничение по типу сделки — из перечисления ENUM_DEAL_TYPE нужно будет отбирать только следующие сделки:

ENUM_DEAL_TYPE

Идентификатор

Описание

DEAL_TYPE_BUY

Покупка

DEAL_TYPE_SELL

Продажа

 

Немного изменим наш первый скрипт и сохраним его под именем position_reversal_v2.mq5. В скрипте position_reversal_v2.mq5 по-прежнему присутствуют три торговых блока — buy 0.01, sell 0.02 и закрытие позиции. А вот новое в этом скрипте — это функция PrintProfitPositions(): в ней подсчитывается прибыль по реконструированным позициям. Остановимся на этой функции подробнее:

//+------------------------------------------------------------------+
//| Print profit positions                                           |
//+------------------------------------------------------------------+
void PrintProfitPositions(void)
  {
//--- structure profit positions;
   struct struct_positions
     {
      long              position_id;
      double            position_profit;
      //--- constructor
                        struct_positions() {position_id=0; position_profit=0.0;}
     };
   struct_positions arr_struct_positions[];
//--- request trade history 
   HistorySelect(start,TimeCurrent());
   uint     total    =HistoryDealsTotal();
   ulong    ticket   =0;
   long     deal_id  =0;
   double   profit   =0;
   long     type     =0;
//--- for all deals 
   for(uint i=0;i<total;i++)
     {
      //--- try to get deals ticket 
      if((ticket=HistoryDealGetTicket(i))>0)
        {
         //--- get deals properties 
         deal_id  =HistoryDealGetInteger(ticket,DEAL_POSITION_ID);
         profit   =HistoryDealGetDouble(ticket,DEAL_PROFIT);
         type     =HistoryDealGetInteger(ticket,DEAL_TYPE);
         //--- only buy or sell; только buy и sell
         if(type==DEAL_TYPE_BUY || type==DEAL_TYPE_SELL)
           {
            bool seach=false;
            int number=ArraySize(arr_struct_positions);
            for(int j=0;j<number;j++)
              {
               if(arr_struct_positions[j].position_id==deal_id)
                 {
                  arr_struct_positions[j].position_profit+=profit;
                  seach=true;
                  break;
                 }
              }
            if(!seach)
              {
               ArrayResize(arr_struct_positions,number+1);
               arr_struct_positions[number].position_id=deal_id;
               arr_struct_positions[number].position_profit+=profit;
              }
           }
        }
     }
//---
   int number=ArraySize(arr_struct_positions);
   for(int i=0;i<number;i++)
     {
      Print("id ",arr_struct_positions[i].position_id," profit ",arr_struct_positions[i].position_profit);
     }
  }

Вначале объявляется структура struct_positions:

//--- structure profit positions;
   struct struct_positions
     {
      long              id;
      double            profit;
      //--- constructor
                        struct_positions() {id=0; profit=0.0;}
     };
   struct_positions arr_struct_positions[];

В структуре struct_positions два поля:

id — идентификатор позиции;

profit — прибыль позиции.

Сразу за этим объявляем массив структур arr_struct_positions[].

Ниже идёт блок вспомогательных переменных для обращения к истории сделок:

//--- request trade history 
   HistorySelect(start,TimeCurrent());
   uint     total    =HistoryDealsTotal();
   ulong    ticket   =0;
   long     deal_id  =0;
   double   profit   =0;
   long     type     =0;

Затем цикл обращения к истории сделок:

//--- for all deals 
   for(uint i=0;i<total;i++)
     {
      //--- try to get deals ticket 
      if((ticket=HistoryDealGetTicket(i))>0)
        {
...
         //--- only buy or sell; только buy и sell
         if(type==DEAL_TYPE_BUY || type==DEAL_TYPE_SELL)
           {
...
           }
        }
     }

При этом не забываем, что, как уже говорилось выше, мы обращаем внимание только на сделки "Buy" и "Sell".

А вот и код, в котором совершается обход по массиву структур arr_struct_positions[] — если в данном массиве найдено совпадение полей position_id и DEAL_POSITION_ID сделки из торговой истории, то в соответствующий индекс массива структур суммируется прибыль текущей сделки:

            bool seach=false;
            int number=ArraySize(arr_struct_positions);
            for(int j=0;j<number;j++)
              {
               if(arr_struct_positions[j].id==deal_id)
                 {
                  arr_struct_positions[j].profit+=profit;
                  seach=true;
                  break;
                 }
              }
            if(!seach)
              {
               ArrayResize(arr_struct_positions,number+1);
               arr_struct_positions[number].id=deal_id;
               arr_struct_positions[number].profit+=profit;
              }

Если в массиве структур не найдено совпадений поля id и DEAL_POSITION_ID сделки, то массив структур увеличивается на один элемент, и этот новый элемент сразу заполняется значениями.

После заполнения массива структур производится обход этого массива и распечатка значений идентификатора позиции и её прибыли: 

//---
   int number=ArraySize(arr_struct_positions);
   for(int i=0;i<number;i++)
     {
      Print("id ",arr_struct_positions[i].id," profit ",arr_struct_positions[i].profit);
     }

1.3. Время открытия позиции

Дополним скрипт position_reversal_v1.mq5 — добавим вывод из торговой истории ещё и всех ордеров вместе с их параметрами. Скрипт сохраним под именем position_reversal_v3.mq5.

2016.09.06 15:05:34.399 position_reversal_v3 (USDJPY,M1)        Buy 0.01, "EURUSD"
2016.09.06 15:05:35.068 position_reversal_v3 (USDJPY,M1)        Position EURUSD POSITION_IDENTIFIER #96803513
2016.09.06 15:05:35.068 position_reversal_v3 (USDJPY,M1)        Deal EURUSD volume 0.01 DEAL_POSITION_ID #96803513 profit 0.00
2016.09.06 15:05:35.068 position_reversal_v3 (USDJPY,M1)        Order EURUSD initial_volume 0.01 ORDER_POSITION_ID #96803513 ORDER_TICKET 96803513
2016.09.06 15:05:35.068 position_reversal_v3 (USDJPY,M1)        
2016.09.06 15:05:41.088 position_reversal_v3 (USDJPY,M1)        Sell 0.02, "EURUSD"
2016.09.06 15:05:41.767 position_reversal_v3 (USDJPY,M1)        Position EURUSD POSITION_IDENTIFIER #96803543
2016.09.06 15:05:41.767 position_reversal_v3 (USDJPY,M1)        Deal EURUSD volume 0.01 DEAL_POSITION_ID #96803513 profit 0.00
2016.09.06 15:05:41.767 position_reversal_v3 (USDJPY,M1)        Deal EURUSD volume 0.02 DEAL_POSITION_ID #96803513 profit -0.08
2016.09.06 15:05:41.767 position_reversal_v3 (USDJPY,M1)        Order EURUSD initial_volume 0.01 ORDER_POSITION_ID #96803513 ORDER_TICKET 96803513
2016.09.06 15:05:41.767 position_reversal_v3 (USDJPY,M1)        Order EURUSD initial_volume 0.02 ORDER_POSITION_ID #96803543 ORDER_TICKET 96803543
2016.09.06 15:05:41.767 position_reversal_v3 (USDJPY,M1)        
2016.09.06 15:05:47.785 position_reversal_v3 (USDJPY,M1)        PositionClose, "EURUSD"
2016.09.06 15:05:48.455 position_reversal_v3 (USDJPY,M1)        Deal EURUSD volume 0.01 DEAL_POSITION_ID #96803513 profit 0.00
2016.09.06 15:05:48.455 position_reversal_v3 (USDJPY,M1)        Deal EURUSD volume 0.02 DEAL_POSITION_ID #96803513 profit -0.08
2016.09.06 15:05:48.455 position_reversal_v3 (USDJPY,M1)        Deal EURUSD volume 0.01 DEAL_POSITION_ID #96803543 profit -0.05
2016.09.06 15:05:48.455 position_reversal_v3 (USDJPY,M1)        Order EURUSD initial_volume 0.01 ORDER_POSITION_ID #96803513 ORDER_TICKET 96803513
2016.09.06 15:05:48.455 position_reversal_v3 (USDJPY,M1)        Order EURUSD initial_volume 0.02 ORDER_POSITION_ID #96803543 ORDER_TICKET 96803543
2016.09.06 15:05:48.455 position_reversal_v3 (USDJPY,M1)        Order EURUSD initial_volume 0.01 ORDER_POSITION_ID #96803543 ORDER_TICKET 96803561

Этот скрипт наглядно демонстрирует следующую цитату из справки :

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

Значит, чтобы определить время открытия позиции, нужно просто найти в торговой истории ордер, тикет которого (ORDER_TICKET)  будет равен идентификатору позиции (POSITION_IDENTIFIER) и получить время найденного ордера (ORDER_TIME_DONE).

1.4. Промежуточное хранение реконструированных позиций

Реконструированные позиции будут храниться в массиве структур struct_positions:

struct struct_positions
  {
   long              id;
   datetime          time;
   double            loss;
   double            profit;
   string            symbol_name;
   //--- constructor 
                     struct_positions() {id=0; time=0; loss=0.0; profit=0.0; symbol_name=NULL;}
  };

где

id — идентификатор позиции;

time — время открытия позиции;

loss — убыток позиции, при этом в данном поле запись будет со знаком "+" (это нужно для лучшего визуального представления графиков)

profit — прибыль позиции

symbol_name — название символа, по которому была открыта позиция.

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


2. Google Charts

Для отображения аналитики будет использоваться сервис Google Charts, для этого графики будут размещаться на HTML-странице. Затем эта страница будет открыта в браузере, установленном по умолчанию в операционной системе (при помощи Win API функции ShellExecuteW).

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

2.1. Гистограмма (тип 'bar')

Тип 'bar' позволяет отображать на HTML-странице такие графики:

bar  bar1

Вот код для первого графика. Сохраните его в файле с расширением *.html (или скачайте файл bar.html в конце статьи) и запустите этот файл в браузере:

<html>
  <head>
    <!--Load the AJAX API-->
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">

      google.charts.load('current', {'packages':['bar']});
      google.charts.setOnLoadCallback(drawChart1);
      function drawChart1() {
        var data1 = google.visualization.arrayToDataTable([
          ['Symbol', 'Profit', 'Loss'],
          ['Si-6.16', 82.00, 944.00],
          ['Si-9.16', 56.00, 11.00],
          ['SBRF-9.16', 546.00, 189.00],
        ]);

        var options1 = {
          chart: {
            title: 'Profit/loss by Symbols',
            subtitle: 'Summary',
          },
          bars: 'vertical',
          vAxis: {format: 'decimal'},
          width: 440,
          height: 400,
          colors: ['#5b9bd5', '#ed7d31', '#7570b3']
        };
        var chart = new google.charts.Bar(document.getElementById('chart_div1'));
        chart.draw(data1, options1);
      }

    </script>
  </head>

  <body>
    <!--Div that will hold the pie chart-->
      <div id="chart_div1"></div>
    <br/>
  </body>
</html>

Для использования Google Charts нужно придерживаться нижеописанных правил по размещению кода.

В блоке <head> подключаются файлы загрузчика и библиотек:

    <!--Load the AJAX API-->
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">

Далее указывается тип графика и функция, в которой находятся данные для рисования (drawChart):

      google.charts.load('current', {'packages':['bar']});
      google.charts.setOnLoadCallback(drawChart1);

Сама функция drawChart1() содержит три блока:

      function drawChart1() {
        var data1 = google.visualization.arrayToDataTable([
          ['Symbol', 'Profit', 'Loss'],
          ['Si-6.16', 82.00, 944.00],
          ['Si-9.16', 56.00, 11.00],
          ['SBRF-9.16', 546.00, 189.00],
        ]);

        var options1 = {
          chart: {
            title: 'Profit/loss by Symbols',
            subtitle: 'Summary',
          },
          bars: 'vertical',
          vAxis: {format: 'decimal'},
          width: 440,
          height: 400,
          colors: ['#5b9bd5', '#ed7d31', '#7570b3']
        };
        var chart = new google.charts.Bar(document.getElementById('chart_div1'));
        chart.draw(data1, options1);
      }

при этом наличие или отсутствие последней запятой в строке

          ['SBRF-9.16', 546.00, 189.00],

не сказывается на работоспособности кода HTML-страницы, и это сильно облегчает алгоритм создания блока данных. Обратите внимание: так как мы строим график типа 'bar', то указываем этот тип так:

        var chart = new google.charts.Bar(document.getElementById('chart_div1'));

Сам контейнер прописывается в <body>:

    </script>
  </head>

  <body>
    <!--Div that will hold the pie chart-->
      <div id="chart_div1"></div>
    <br/>
  </body>
</html>

2.2. Круговая диаграмма (тип 'corechart')

В статье будет использоваться такая диаграмма:

corechart 

Код HTML-страницы, который генерирует создание круговой диаграммы, приведен ниже.  Сохраните этот код в файле с расширением *.html (или скачайте файл corechart.html в конце статьи) и запустите этот файл в браузере.

<html>
  <head>
    <!--Load the AJAX API-->
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">

      google.charts.load('current', {'packages':['corechart']});
      google.charts.setOnLoadCallback(drawChart2);
      function drawChart2() {
        var data2 = google.visualization.arrayToDataTable([
          ['Symbols', 'Profit'],
          ['Si-6.16', 82.00],
          ['Si-9.16', 56.00],
          ['SBRF-9.16', 546.00],
        ]);

        var options2 = {
            title: 'Profit by Symbols, %',
            pieHole: 0.4,
            width: 440,
            height: 400,
        };
        var chart = new google.visualization.PieChart(document.getElementById('chart_div2'));
        chart.draw(data2, options2);
      }

    </script>
  </head>

  <body>
    <!--Div that will hold the pie chart-->
      <div id="chart_div2"></div>
    <br/>
  </body>
</html>

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

        var data2 = google.visualization.arrayToDataTable([
          ['Symbols', 'Profit'],
          ['Si-6.16', 82.00],
          ['Si-9.16', 56.00],
          ['SBRF-9.16', 546.00],
        ]);

        var options2 = {
            title: 'Profit by Symbols, %',
            pieHole: 0.4,
            width: 440,
            height: 400,
        };

и поскольку мы строим график типа 'corechart', то блок, который указывает контейнер, выглядит так:

        var chart = new google.visualization.PieChart(document.getElementById('chart_div2'));
        chart.draw(data2, options2);

2.3. Гистограмма (тип 'bar') + круговая диаграмма (тип 'corechart') + гистограмма (тип 'bar')

Код этого примера сохранён в файле bar_corechart_bar.html и доступен для скачивания в конце статьи. Сам пример выглядит так (рисунок уменьшен): 

bar+ corechart + bar 

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

      google.charts.load('current', {'packages':['bar', 'corechart']});

Общая схема файла bar_corechart_bar.html будет такой:

<html>
  <head>
    <!--Load the AJAX API-->
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">

      google.charts.load('current', {'packages':['bar', 'corechart']});
      google.charts.setOnLoadCallback(drawChart1);
      google.charts.setOnLoadCallback(drawChart2);
      google.charts.setOnLoadCallback(drawChart4);
      function drawChart1() {
        var data1 = ...

        var options1 = ...

        var chart = new google.charts.Bar(document.getElementById('chart_div1'));
        chart.draw(data1, options1);
      }

      function drawChart2() {
        var data2 = ...

        var options2 = ...

        var chart = new google.visualization.PieChart(document.getElementById('chart_div2'));
        chart.draw(data2, options2);
      }

      function drawChart4() {
        var data4 = ...

        var options4 = ...

        var chart = new google.charts.Bar(document.getElementById('chart_div4'));
        chart.draw(data4, options4);
      }

    </script>
  </head>

  <body>
    <!--Div that will hold the pie chart-->
      <table>
        <tr>
          <td><div id="chart_div1"></div></td>
          <td><div id="chart_div2"></div></td>
          <td><div id="chart_div4"></div></td>
      </table>
    <br/>
  </body>
</html>

Функции drawChart1 и drawChart4 рисуют гистограммы, функция drawChart2 — круговую диаграмму.


3. Запуск графиков аналитики из терминала (для текущего торгового счёта)

Самый простой вариант — это небольшой скрипт, который после присоединения к графику запускает браузер и отображает графики аналитики распределения трейдов в зависимости от времени входа. Главное — указать дату, начиная с которой нужно будет строить аналитику (входной параметр "start"):

//+------------------------------------------------------------------+
//|                                                  test_report.mq5 |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
//---
input datetime start=D'2010.05.11 12:05:00';
#include <DistributionOfProfits.mqh>
//---
CDistributionOfProfits Analysis;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   Analysis.AnalysisTradingHistory(start);
   Analysis.ShowDistributionOfProfits();
  }
//+------------------------------------------------------------------+

Так как в файле "DistributionOfProfits.mqh" используется вызов системной dll, при запуске тестового скрипта нужно разрешить импорт dll:

dependencies 


4. Запуск графиков аналитики из тестера стратегий

Для примера возьмём советник "MACD Sample.mq5", который идёт в стандартной поставке (каталог данных\MQL5\Experts\Examples\MACD\MACD Sample.mq5). Скопируем этот эксперт в отдельную папку для того, чтобы наши изменения не затронули оригинальный файл. Теперь переименуем его в "MACD Sample report.mq5".  Внесём такие изменения в советник:

//+------------------------------------------------------------------+
//|                                           MACD Sample report.mq5 |
//|                   Copyright 2009-2016, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright   "Copyright 2009-2016, MetaQuotes Software Corp."
#property link        "https://www.mql5.com"
#property version     "5.50"
#property description "It is important to make sure that the expert works with a normal"
#property description "chart and the user did not make any mistakes setting input"
#property description "variables (Lots, TakeProfit, TrailingStop) in our case,"
#property description "we check TakeProfit on a chart of more than 2*trend_period bars"

#define MACD_MAGIC 1234502
//---
#include <Trade\Trade.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
#include <DistributionOfProfits.mqh>
//---
input double InpLots          =0.1; // Lots
input int    InpTakeProfit    =50;  // Take Profit (in pips)
input int    InpTrailingStop  =30;  // Trailing Stop Level (in pips)
input int    InpMACDOpenLevel =3;   // MACD open level (in pips)
input int    InpMACDCloseLevel=2;   // MACD close level (in pips)
input int    InpMATrendPeriod =26;  // MA trend period
//---
int ExtTimeOut=10; // time out in seconds between trade operations
CDistributionOfProfits ExtDistribution;
//+------------------------------------------------------------------+
//| MACD Sample expert class                                         |
//+------------------------------------------------------------------+
class CSampleExpert
  {
protected:

В самый конец его добавим функцию OnTester():

//+------------------------------------------------------------------+
//| Tester function                                                  |
//+------------------------------------------------------------------+
double OnTester()
  {
//---
   double ret=0.0;
   ExtDistribution.AnalysisTradingHistory(0);
   ExtDistribution.ShowDistributionOfProfits();
//---
   return(ret);
  }

Перед запуском тестера нужно в настройках терминала разрешить использование dll:

options ea 

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

profit/loss hours

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

Также интересные результаты можно получить, если запустить в тестере стратегий мультивалютный эксперт. Для образца я взял из CodeBase бесплатный советник Multicurrency Expert. Также в шапке советника был прописан файл "#include <DistributionOfProfits.mqh>", объявлена переменная "CDistributionOfProfits ExtDistribution", а в конец кода добавлена функция "OnTester()". После одиночного тестирования получена такая статистика: "TestAnalysis.htm".

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

profit/loss 1 profit/loss 2

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

profit percent  loss percent

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

 

Это и есть "тихая" оптимизация без шума и пыли

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

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

 

Изменения

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

"DistributionOfProfits.mqh" v.1.027: Внесена защита при работе программы в процессе оптимизации. В конструкторе имя файл "Non File", а в двух публичных функциях производится проверка константы MQL_OPTIMIZATION:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CDistributionOfProfits::CDistributionOfProfits(void) : m_name_file("Non File"),
                                                       m_color_loss("ed7d31"),
                                                       m_color_profit("5b9bd5"),
                                                       m_width("440"),
                                                       m_height("400"),
                                                       m_data_number(1)
  {
  }
//+------------------------------------------------------------------+
//| Analysis Trading History                                         |
//+------------------------------------------------------------------+
bool CDistributionOfProfits::AnalysisTradingHistory(const datetime start_time=0)
  {
//---
   if(MQLInfoInteger(MQL_OPTIMIZATION))
      return(false);
//+------------------------------------------------------------------+
//| Show Distribution Of Profits (start of the browser)              |
//+------------------------------------------------------------------+
void CDistributionOfProfits::ShowDistributionOfProfits(void)
  {
//---
   if(MQLInfoInteger(MQL_OPTIMIZATION))
      return;

"DistributionOfProfits.mqh" v.1.033: Теперь можно анализировать позиции в деньгах, в пунктах, в деньгах и в пунктах.

Выбор типа анализа задаётся так:

//+------------------------------------------------------------------+
//|                                                  test_report.mq5 |
//|                        Copyright 2016, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs
//+------------------------------------------------------------------+
//| type of report                                                   |
//+------------------------------------------------------------------+
enum ANALYSIS_TYPE
  {
   ONLY_MONEY=0,        // in money 
   ONLY_POINTS=1,       // in points 
   MONEY_AND_POINTS=2,  // in money and points
  };
//---
input datetime start=D'2016.06.28 09:10:00';
input ANALYSIS_TYPE report_type=MONEY_AND_POINTS;
//---
#include <DistributionOfProfits.mqh>
//---
CDistributionOfProfits Analysis;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   Analysis.SetTypeOfAnalysis(report_type);
   Analysis.AnalysisTradingHistory(start);
   Analysis.ShowDistributionOfProfits();
  }
//+------------------------------------------------------------------+