English 中文 Español Deutsch 日本語 Português
LifeHack для трейдера: "Тихая" оптимизация или Строим распределения трейдов

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

MetaTrader 5Примеры |
5 927 11
Vladimir Karputov
Vladimir Karputov

Оглавление


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

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

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

Графики строятся путём вызова 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

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

  • buy 0.01 — открытие позиции
  • sell 0.02 — переворот позиции
  • закрытие позиции

Во входных параметрах скрипта нужно указать время старта — этот параметр будет использоваться для запроса торговой истории. На моём примере: я запускал скрипт 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() содержит три блока:

  • "var data1" — блок данных, на основании которых строится график
  • "var options1" — блок опций, которые уточняют параметры графика
  • блок, указывающий контейнер, в котором будет отображаться график:

      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();
  }
//+------------------------------------------------------------------+


 

Прикрепленные файлы |
html.zip (6.72 KB)
test_report.mq5 (3.01 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (11)
okwh
okwh | 17 окт. 2016 в 03:55
Торговый дистрибутив Отличный инструмент!
Rodrigo da Silva Boa
Rodrigo da Silva Boa | 25 окт. 2016 в 02:42

Спасибо за ваш вклад,

Отличный материал.

Forester
Forester | 9 дек. 2016 в 22:13

Вот тут иногда возникает деление на ноль:

                     temp=(m_arr_struct_positions[j].prev_volume*m_arr_struct_positions[j].prev_price+volume*price)/
                          (m_arr_struct_positions[j].prev_volume+volume);

2016.12.10 00:01:41.696    Core 1    2016.12.05 23:59:55   zero divide in 'DistributionOfProfits.mqh' (292,116)
Sidnei Da Silva Santos Junior
Sidnei Da Silva Santos Junior | 11 нояб. 2020 в 19:44
Есть ли способ добавить среднюю просадку по часам?
Vladimir Karputov
Vladimir Karputov | 12 нояб. 2020 в 05:44
Sidnei Da Silva Santos Junior :
Можно ли как-то добавить среднюю просадку капитала по часам?

Вам нужно назначить начальный капитал, а затем работать по аналогии.

Графические интерфейсы X: Обновления для библиотеки Easy And Fast (build 3) Графические интерфейсы X: Обновления для библиотеки Easy And Fast (build 3)
В этой статье представлена следующая версия библиотеки Easy And Fast (версия 3). Исправлены некоторые недоработки и добавлены новые возможности. Подробнее читайте далее в статье.
Сравнение MQL5 и QLUA - почему торговые операции в MQL5 до 28 раз быстрее? Сравнение MQL5 и QLUA - почему торговые операции в MQL5 до 28 раз быстрее?
Многие трейдеры зачастую не задумываются над тем, как быстро доходит их заявка до биржи, как долго она там исполняется, и когда наконец-то торговый терминал трейдера узнает о результате торговой операции. Мы обещали дать сравнение скорости торговых операций, ведь никто до нас не делал таких замеров с помощью программ на MQL5 и QLUA.
Быстрая оценка сигнала: торговая активность, графики просадки/загрузки и распределения MFE/MAE Быстрая оценка сигнала: торговая активность, графики просадки/загрузки и распределения MFE/MAE
При поиске Сигнала подписчики в первую очередь ориентируются на общий прирост на торговом счете Поставщика, и это, в общем-то, логично. Но при этом также важно принимать во внимание потенциальные риски, которые несет конкретная торговая стратегия. В этой статье мы покажем, как просто и наглядно можно оценить заинтересовавший Сигнал с помощью нескольких показателей.
Портфельная торговля в MetaTrader 4 Портфельная торговля в MetaTrader 4
В статье обсуждаются принципы портфельной торговли и особенности применения к валютному рынку. Рассматриваются несколько простых математических моделей для формирования портфеля. Приводятся примеры практической реализации портфельной торговли в MetaTrader 4: портфельный индикатор и советник для полуавтоматической торговли. Описываются элементы торговых стратегий, их достоинства и "подводные камни".