LifeHack для трейдера: "Тихая" оптимизация или Строим распределения трейдов
Оглавление
- Зачем нужны графики распределений?
- Быстрый запуск
- 1. Реконструкция позиций из торговой истории
- 1.1. Переворот позиции
- 1.2. Подсчёт прибыли позиции
- 1.3. Время открытия позиции
- 1.4. Промежуточное хранение реконструированных позиций
- 2. Google Charts
- 2.1. Гистограмма (тип 'bar')
- 2.2. Круговая диаграмма (тип 'corechart')
- 2.3. Гистограмма (тип 'bar') + круговая диаграмма (тип 'corechart') + гистограмма (тип 'bar')
- 3. Запуск графиков аналитики из терминала (для текущего торгового счёта)
- 4. Запуск графиков аналитики из тестера стратегий
- Это и есть "тихая" оптимизация без шума и пыли
- Изменения
Зачем нужны графики распределений?
При разработке новой торговой стратегии мы не знаем заранее, насколько удачной она окажется. Почти всегда торговый робот содержит входные параметры, лежащие в основе правил, по которым будут генерироваться сигналы на вход в рынок. При этом мы просто полагаемся на то, что после написания торгового робота тестер стратегий поможет нам найти такие комбинации входных параметров, которые покажут хорошие результаты на истории.
Но в этой статье я предлагаю взглянуть на процесс создания торгового робота немного иначе. Прежде чем запускать оптимизацию входных параметров, можно просто посмотреть на распределение прибылей и убытков в зависимости от времени входа. Ведь для многих стратегий есть "благоприятные" и "неблагоприятные" моменты для входа в рынок. В статье рассматривается построение графиков распределения прибыльности позиций (обратите внимание, не сделок, а именно позиций!) в зависимости от времени их открытия. Изучив эти графики, вы сможете посмотреть на свою стратегию немного под другим углом зрения.
Графики строятся путём вызова 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), которая ее открыла, изменила или закрыла:
Рис. 2. Связь POSITION_IDENTIFIER, DEAL_POSITION_ID и ORDER_POSITION_ID
Другими словами, если выделить из торговой истории сделки с одинаковыми DEAL_POSITION_ID, мы гарантированно сможем реконструировать позицию. Здесь нужно упомянуть такую ситуацию, как переворот позиции. Из справки по идентификатору позиции:
Переворот позиции изменяет ее идентификатор на тикет ордера, в результате которого произошел переворот.
Значит, если при перевороте позиция меняет свой идентификатор, то это будет уже другая позиция. Как же происходит присвоение идентификатора сделкам в таком случае? К предыдущей или перевернутой (новой) позиции будет относиться сделка, вызвавшая переворот? Для ответа на этот вопрос я написал простой пример — скрипт 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.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); }
Дополним скрипт 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).
В статье будут использованы два типа диаграмм: гистограмма и круговая. Рассмотрим их подробнее.
Тип 'bar' позволяет отображать на HTML-странице такие графики:
Вот код для первого графика. Сохраните его в файле с расширением *.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')
В статье будет использоваться такая диаграмма:
Код 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 и доступен для скачивания в конце статьи. Сам пример выглядит так (рисунок уменьшен):
Конфигурация, в которой одновременно присутствуют разные типы графиков — более сложный случай, так как здесь нужно правильно распределить на странице функции 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:
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:
В итоге получилась страница с графиками аналитики, на которой я предлагаю вам посмотреть на график распределения прибыли/убытков по часам:
Хорошо видно, в какие часы открывались прибыльные позиции, а в какие – убыточные. На графике я обозначил два временных периода с убытками. Что если ограничить торговлю в указанные неблагоприятные часы? Или даже может пойти еще дальше – перевернуть торговые сигналы на противоположные, чтобы вместо убытков получать прибыли? Предлагаю читателю проверить это самостоятельно.
Также интересные результаты можно получить, если запустить в тестере стратегий мультивалютный эксперт. Для образца я взял из CodeBase бесплатный советник Multicurrency Expert. Также в шапке советника был прописан файл "#include <DistributionOfProfits.mqh>", объявлена переменная "CDistributionOfProfits ExtDistribution", а в конец кода добавлена функция "OnTester()". После одиночного тестирования получена такая статистика: "TestAnalysis.htm".
Обратите внимание, что все графики получаются интерактивными. Например, на первой гистограмме — "Профит/убыток по символам (суммарно)" — при наведении курсора мыши можно видеть показатели по каждому символу:
Теперь круговые диаграммы: на них видно, какой вклад внес каждый символ в прибыльность и убыточность торговли:
На остальных диаграммах можно увидеть прибыльность по каждому символу, в зависимости от времени входа в позицию: распределение по часам входа, по дням и по месяцам.
Это и есть "тихая" оптимизация без шума и пыли
Задача по построению распределений трейдов выполнена. При этом торговая история сворачивается в позиции, и все аналитические графики строятся уже исключительно для позиций. Прибыль или убыток позиции анализируется по времени входа в трех вариантах: по часам, по дням недели и по месяцам.
С помощью предлагаемых в статье исходников вы можете любую свою стратегию проверить на благоприятные и неблагоприятные часы и дни для торговли. И кто знает, возможно вместо одной начальной стратегии вы получите пару новых, например, трендовую и контртрендовую. Или же вы явно увидите, в какую торговую сессию (азиатскую, европейскую или американскую) лучше сидеть, чем торговать. Попробуйте сами этот простой метод "тихой" оптимизации, не требующий дополнительных прогонов в тестере стратегии.
Изменения
Кратко об изменениях, которые вносятся в код, после первой публикации статьи.
"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(); } //+------------------------------------------------------------------+
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Непонятно, зачем надо было пересчитывать из сделок - позиции.
Сложить результаты всех сделок по каждому инструменту - в итоге и получим распределение profit/loss по нему.
Чем подсчет по сделкам удобнее? Например позиция держалась неделю, а сделок за это время было 100 штук. Распределение по дням и часам будет лучше заполнено.
1. Непонятно, зачем надо было пересчитывать из сделок - позиции.
...
...
2. Чем подсчет по сделкам удобнее? Например позиция держалась неделю, а сделок за это время было 100 штук. Распределение по дням и часам будет лучше заполнено.
Позиция - это целостная характеристика, а ордер и сделка - это кусочек.
В итоговом отчете мы не видим отдельных позиций, мы видим их сумму. А складывая результаты по сделкам или результаты по позициям, в конце расчетов мы получим ту же самую сумму. Но складывать просто сделки - проще и как уже упоминал ранее - распределение по дням и часам будет лучше заполнено.
На самом деле очень часто позиция состоит всего из двух сделок - первая - вход и вторая - выход.
В том же мультивалютнике, который Вы взяли для примера, при росте прибыли по позиции - происходит доливка объемов дополнительными сделками. И лишь при неудачном - закрытие и обратное открытие. А может и такой вариант - было удачным, пару доливок сделали, но цена пошла не туда и только потом закрыли и перевернули позицию. Так что вариант с всего 2-мя сделками - это частный случай.
Нельзя ли добавить расчет по сделкам, как вариант? Я например, именно им пользовался бы. Или статьи уже нельзя изменять после публикации?
Вот тут иногда возникает деление на ноль:
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);