Собственное представление торговой истории и создание графиков для отчетов
Введение
Не секрет, что при трейдинге на финансовых рынках важно отслеживать результативность и уметь анализировать историю собственной торговли. По ретроспективным данным можно не только отследить динамику торгов, но и оценить общую эффективность применяемой стратегии. Это было бы полезно всем трейдерам: и предпочитающим ручную торговлю, и алгоритмистам. Предлагаю создать инструменты, которые реализуют такую функцию.
В основе любого трейдинга лежит торговый алгоритм, формирующий кривую Profit/Loss. Такой алгоритм можно сравнить с неким синтетическим активом, стоимость которого формируется относительно базового актива (торгуемого инструмента). К примеру, для опционов есть формула (BSM-модель), по которой в любой момент рассчитывается стоимость этого синтетического актива, исходя из цены базового. Но для торгового алгоритма такой формулы не существует. Соответственно, запуск алгоритма можно сравнить с длинной позицией по синтетическому инструменту, кривая PL которого формируется по запрограммированной логике. Формируемая этим «активом» прибыль может быть нестабильна в разные периоды времени. Даже если она и подлежит оценке единой эконометрической моделью, то эту модель невозможно унифицировать. Возникает вопрос: как же отслеживать этот актив и стадии нашей торговли? Подходящим решением выглядит отслеживать ретроспективу торговли по этому алгоритму и выявлять отклонения от ожидаемых результатов.
Я не стану давать советов по анализу алгоритмов, а лишь представлю набор методов, с помощью которых можно достаточно полно отразить картину истории торгов. На основании полученных данных можно строить сложные эконометрические модели, рассчитывать вероятностные характеристики и делать прочие умозаключения.
Эта статья будет разбита на 2 раздела. В первом (техническом) разделе я расскажу о методах формирования отчетов о торговле из той массы сведений, которая хранится в ваших терминалах. Этот раздел касается исходных данных для анализа. Во втором разделе сформируем основные показатели, по которым будем оценивать ретроспективу торговли по выбранным данным. Выборку данных можно будет варьировать: по всем активам или по конкретному выбранному, за всю имеющуюся историю или за определенный промежуток времени. Результаты анализа будут представлены в отдельном файле и кратко визуализированы в терминале.
Примеры данных для анализа я взял из моей реальной истории торговли. Примеры реализации кода сделаны на тестовом периоде, который специально под эти цели был "наторгован" на демо-счете.
Глава 1. Подготовка данных для анализа торговой статистики
Любая аналитика начинается с подготовки исходных данных. В нашем случае это торговая история за заданный период времени. В памяти терминала MetaTrader 5 хранятся детализированные отчеты. Но иногда информация бывает лишней, и чрезмерная подробность может не помочь, а помешать. В этом разделе попытаемся создать удобочитаемую краткую и информативную "выжимку" торговой истории, которую можно будет обсчитывать и анализировать.
Торговая история в MetaTrader 5 и методы работы с ней
Выгрузку торговой истории всегда сопровождает множество записей. Во-первых, каждая позиция разбита на сделки входа/выхода, причем и те, и другие могут проходить в несколько этапов. Во-вторых, порой складываются ситуации постепенной докупки и сокращения позиций. В-третьих, в истории есть строки, отведенные под «мнимые сделки». Это могут быть:
- закрытие/открытие позиции во время начисления/списания вариационной маржи (FORTS);
- всевозможные коррекции существующих позиций;
- начисления и выводы средств с торгового счета.
Неудобство чтения отчетов дополняется еще и тем, что история сортируется по времени открытия сделок. Из-за того, что время жизни сделок различается, это может исказить очередность представления информации о том, когда был зафиксирован результат сделки. К примеру, первая сделка открывается 1 июня, закрывается 5. Вторая открывается 2 июня - и в тот же день закрывается. Прибыль/убыток по ней мы получаем раньше, чем по первой — но в таблице она все равно будет находиться позже, поскольку позже была открыта.
Соответственно для анализа торговли, история торгов записанная в подобном виде не подходит. Однако она в полной мере отражает всю историю работы по анализируемому счету. Для работы с описываемым массивом данных, в Mql5 существует полезный набор инструментов, коими мы воспользовались для приведения истории торгов к читабельному виду. Первое что нам потребуется, это разбить озвученную кипу данных на историю торгов, отсортированную по торгуемым активам и сделкам. Для этого создадим следующие структуры:
//+------------------------------------------------------------------+ //| Структура данных одной выбранной сделки | //+------------------------------------------------------------------+ struct DealData { long ticket; // Тикет сделки long order; // Номер ордера, открывшего сделку datetime DT; // Дата открытия позиции long DT_msc; // Дата открытия позиции в миллисекундах ENUM_DEAL_TYPE type; // Тип открытой позиции ENUM_DEAL_ENTRY entry; // Тип входа в позицию long magic; // Уникальный номер позиции ENUM_DEAL_REASON reason; // Откуда был выставлен ордер long ID; // ID позиции double volume; // Объем позиции (лоты) double price; // Цена входа в позицию double comission; // Комиссия уплаченая double swap; // Своп double profit; // Прибыль/убыток string symbol; // Символ string comment; // Комментарий, указанный во время открытия string ID_external; // Внешний ID }; //+------------------------------------------------------------------+ //| Структура, хранящая в себе все сделки по определенной позиции | //| которая была выбрана по ID | //+------------------------------------------------------------------+ struct DealKeeper { DealData deals[]; /* Список всех сделок для данной позиции (или нескольких, если сделка развернулась)*/ string symbol; // Символ long ID; // ID данной позиции (позиций) datetime DT_min; // дата открытия datetime DT_max; // дата закрытия };
Из этого кода видим, что структура DealData содержит избыточное описание параметров сделки.
Нашей основной структурой будет DealKeeper. В ней находится описание позиции и всех входящих в них сделок. Основной фильтр позиции в терминале — это ее ID, показатель, который остается неизменным для всех сделок этой позиции. Поэтому если происходит разворот позиции, то она сохраняет изначальный ID, но при этом меняется на противоположную, поэтому в структуре DealKeeper будут содержаться две позиции. Фильтрация заполнения DealKeeper в нашем коде построена по ID позиции.
Рассмотрим детально способ заполнения структуры. За это отвечает класс CDealHistoryGetter, в частности — его функция getHistory:
//+-----------------------------------------------------------------------+ //| Класс, извлекающий историю торгов из терминала и преобразующий ее в | //| удобный для анализа вид | //+-----------------------------------------------------------------------+ class CDealHistoryGetter { public: bool getHistory(DealKeeper &deals[],datetime from,datetime till); // возвращает все данные по историческим сделкам bool getIDArr(ID_struct &ID_arr[],datetime from,datetime till); // возвращает уникальный массив ID сделок bool getDealsDetales(DealDetales &ans[],datetime from,datetime till); // возвращает массив сделок, где каждая строка - одна конкретная сделка private: void addArray(ID_struct &Arr[],const ID_struct &value); // добавить в динамический массив void addArray(DealKeeper &Arr[],const DealKeeper &value); // добавить в динамический массив void addArray(DealData &Arr[],const DealData &value); // добавить в динамический массив void addArr(DealDetales &Arr[],DealDetales &value); // добавить в динамический массив void addArr(double &Arr[],double value); // добавить в динамический массив /* Если есть выходы по InOut, то в inputParam будет более одной позиции. Если нет выходов по InOut, то в inputParam будет только одна сделка ! */ void getDeals_forSelectedKeeper(DealKeeper &inputParam,DealDetales &ans[]); // формируется единая запись по одной выбранной позиции для всех позиций, что есть в inputParam double MA_price(double &prices[],double &lots[]); // подсчитывается средневзвешенная цена открытия bool isBorderPoint(DealData &data,BorderPointType &type); // получаем информацию, пограничная ли это точка, и какой именно тип точки ENUM_DAY_OF_WEEK getDay(datetime DT); // получаем день из даты double calcContracts(double &Arr[],GetContractType type); // получаем информацию об объеме самой последней позиции };
Рассмотрим ее реализацию:
//+------------------------------------------------------------------+ //| возвращает все данные по историческим сделкам | //+------------------------------------------------------------------+ bool CDealHistoryGetter::getHistory(DealKeeper &deals[],datetime from,datetime till) { ArrayFree(deals); // очищаем массив с результатами ID_struct ID_arr[]; if(getIDArr(ID_arr,from,till)) // получаем уникальные ID { int total=ArraySize(ID_arr); for(int i=0;i<total;i++) // цикл по ID { DealKeeper keeper; // накопитель сделок по позиции keeper.ID=ID_arr[i].ID; keeper.symbol=ID_arr[i].Symb; keeper.DT_max = LONG_MIN; keeper.DT_min = LONG_MAX; if(HistorySelectByPosition(ID_arr[i].ID)) // выбираем все сделки с заданным ID {
Сначала нужно получить уникальные ID позиций. Для этого в сплошном цикле по истории просматриваются ID сделок и формируется структура с двумя полями — Символ и ID позиции. Полученные данные потребуются нам для работы функции getHistory.
В языке MQL5 есть очень полезная функция HistorySelectByPosition, которая выбирает всю историю сделок с переданым ID. С ее помощью мы сразу же отбросим из списка ненужные нам операции (обработка аккаунта, вводы и выводы средств и т.д.). В итоге в память вносится сформированная история всех изменений, происходивших с позицией, пока она была открыта. Данные отсортированы по дате и времени. Нам остается только перебрать их по порядку с помощью функции HistoryDealsTotal, которая возвращает общее количество всех сделок с заданным ID, которые были предварительно занесены в память.
int total_2=HistoryDealsTotal(); for(int n=0;n<total_2;n++) // Цикл по выбранным сделкам { long ticket=(long)HistoryDealGetTicket(n); DealData data; data.ID=keeper.ID; data.symbol=keeper.symbol; data.ticket= ticket; data.DT=(datetime)HistoryDealGetInteger(ticket,DEAL_TIME); keeper.DT_max=MathMax(keeper.DT_max,data.DT); keeper.DT_min=MathMin(keeper.DT_min,data.DT); data.order= HistoryDealGetInteger(ticket,DEAL_ORDER); data.type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket,DEAL_TYPE); data.DT_msc=HistoryDealGetInteger(ticket,DEAL_TIME_MSC); data.entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket,DEAL_ENTRY); data.magic = HistoryDealGetInteger(ticket,DEAL_MAGIC); data.reason= (ENUM_DEAL_REASON)HistoryDealGetInteger(ticket,DEAL_REASON); data.volume= HistoryDealGetDouble(ticket,DEAL_VOLUME); data.price = HistoryDealGetDouble(ticket,DEAL_PRICE); data.comission=HistoryDealGetDouble(ticket,DEAL_COMMISSION); data.swap=HistoryDealGetDouble(ticket,DEAL_SWAP); data.profit=HistoryDealGetDouble(ticket,DEAL_PROFIT); data.comment=HistoryDealGetString(ticket,DEAL_COMMENT); data.ID_external=HistoryDealGetString(ticket,DEAL_EXTERNAL_ID); addArray(keeper.deals,data); // Добавляем сделки } if(ArraySize(keeper.deals)>0) addArray(deals,keeper); // Добавляем позицию } } return ArraySize(deals) > 0; } else return false; // Если нет уникальных ID }
Таким образом, пройдясь по каждому уникальному ID, мы формируем массив данных, описывающих позицию. Массив структур DealKeeper отражает детализированную торговую историю по текущему аккаунту за запрашиваемый период времени.
Подготовка данных для анализа
В предыдущем разделе мы получили данные. Экспортируем их в файл. Вот как будет выглядеть список сделок по одной из изученных позиций:
Ticket | Order | DT | DT msc | Type | Entry | Magic | Reason | ID | Volume | Price | Comission | Swap | Profit | Symbol | Comment | ID external |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
10761601 | 69352663 | 23.11.2017 17:41 | 1,51146E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 506789 | DEAL_REASON_EXPERT | 69352663 | 1 | 58736 | -0,5 | 0 | 0 | Si-12.17 | Open test position | 23818051 |
10761602 | 69352663 | 23.11.2017 17:41 | 1,51146E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 506789 | DEAL_REASON_EXPERT | 69352663 | 1 | 58737 | -0,5 | 0 | 0 | Si-12.17 | Open test position | 23818052 |
10766760 | 0 | 24.11.2017 13:00 | 1,51153E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58682 | 0 | 0 | -109 | Si-12.17 | [variation margin close] | |
10766761 | 0 | 24.11.2017 13:00 | 1,51153E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58682 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10769881 | 0 | 24.11.2017 15:48 | 1,51154E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58649 | 0 | 0 | -66 | Si-12.17 | [variation margin close] | |
10769882 | 0 | 24.11.2017 15:48 | 1,51154E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58649 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10777315 | 0 | 27.11.2017 13:00 | 1,51179E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58420 | 0 | 0 | -458 | Si-12.17 | [variation margin close] | |
10777316 | 0 | 27.11.2017 13:00 | 1,51179E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58420 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10780552 | 0 | 27.11.2017 15:48 | 1,5118E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58417 | 0 | 0 | -6 | Si-12.17 | [variation margin close] | |
10780553 | 0 | 27.11.2017 15:48 | 1,5118E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58417 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10790453 | 0 | 28.11.2017 13:00 | 1,51187E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58589 | 0 | 0 | 344 | Si-12.17 | [variation margin close] | |
10790454 | 0 | 28.11.2017 13:00 | 1,51187E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58589 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10793477 | 0 | 28.11.2017 15:48 | 1,51188E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58525 | 0 | 0 | -128 | Si-12.17 | [variation margin close] | |
10793478 | 0 | 28.11.2017 15:48 | 1,51188E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58525 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10801186 | 0 | 29.11.2017 13:00 | 1,51196E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58515 | 0 | 0 | -20 | Si-12.17 | [variation margin close] | |
10801187 | 0 | 29.11.2017 13:00 | 1,51196E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58515 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10804587 | 0 | 29.11.2017 15:48 | 1,51197E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58531 | 0 | 0 | 32 | Si-12.17 | [variation margin close] | |
10804588 | 0 | 29.11.2017 15:48 | 1,51197E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58531 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10813418 | 0 | 30.11.2017 13:00 | 1,51205E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58843 | 0 | 0 | 624 | Si-12.17 | [variation margin close] | |
10813419 | 0 | 30.11.2017 13:00 | 1,51205E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58843 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10816400 | 0 | 30.11.2017 15:48 | 1,51206E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58609 | 0 | 0 | -468 | Si-12.17 | [variation margin close] | |
10816401 | 0 | 30.11.2017 15:48 | 1,51206E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58609 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10824628 | 0 | 01.12.2017 13:00 | 1,51213E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58864 | 0 | 0 | 510 | Si-12.17 | [variation margin close] | |
10824629 | 0 | 01.12.2017 13:00 | 1,51213E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58864 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10828227 | 0 | 01.12.2017 15:48 | 1,51214E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58822 | 0 | 0 | -84 | Si-12.17 | [variation margin close] | |
10828228 | 0 | 01.12.2017 15:48 | 1,51214E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58822 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10838074 | 0 | 04.12.2017 13:00 | 1,51239E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59093 | 0 | 0 | 542 | Si-12.17 | [variation margin close] | |
10838075 | 0 | 04.12.2017 13:00 | 1,51239E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59093 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10840722 | 0 | 04.12.2017 15:48 | 1,5124E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59036 | 0 | 0 | -114 | Si-12.17 | [variation margin close] | |
10840723 | 0 | 04.12.2017 15:48 | 1,5124E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59036 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10848185 | 0 | 05.12.2017 13:00 | 1,51248E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58793 | 0 | 0 | -486 | Si-12.17 | [variation margin close] | |
10848186 | 0 | 05.12.2017 13:00 | 1,51248E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58793 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10850473 | 0 | 05.12.2017 15:48 | 1,51249E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58881 | 0 | 0 | 176 | Si-12.17 | [variation margin close] | |
10850474 | 0 | 05.12.2017 15:48 | 1,51249E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58881 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10857862 | 0 | 06.12.2017 13:00 | 1,51257E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59181 | 0 | 0 | 600 | Si-12.17 | [variation margin close] | |
10857863 | 0 | 06.12.2017 13:00 | 1,51257E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59181 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10860776 | 0 | 06.12.2017 15:48 | 1,51258E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59246 | 0 | 0 | 130 | Si-12.17 | [variation margin close] | |
10860777 | 0 | 06.12.2017 15:48 | 1,51258E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59246 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10869047 | 0 | 07.12.2017 13:00 | 1,51265E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59325 | 0 | 0 | 158 | Si-12.17 | [variation margin close] | |
10869048 | 0 | 07.12.2017 13:00 | 1,51265E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59325 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10871856 | 0 | 07.12.2017 15:48 | 1,51266E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59365 | 0 | 0 | 80 | Si-12.17 | [variation margin close] | |
10871857 | 0 | 07.12.2017 15:48 | 1,51266E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59365 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10879894 | 0 | 08.12.2017 13:01 | 1,51274E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59460 | 0 | 0 | 190 | Si-12.17 | [variation margin close] | |
10879895 | 0 | 08.12.2017 13:01 | 1,51274E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59460 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10882283 | 0 | 08.12.2017 15:48 | 1,51275E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59421 | 0 | 0 | -78 | Si-12.17 | [variation margin close] | |
10882284 | 0 | 08.12.2017 15:48 | 1,51275E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59421 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10888014 | 0 | 11.12.2017 13:00 | 1,513E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59318 | 0 | 0 | -206 | Si-12.17 | [variation margin close] | |
10888015 | 0 | 11.12.2017 13:00 | 1,513E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59318 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10890195 | 0 | 11.12.2017 15:48 | 1,51301E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59280 | 0 | 0 | -76 | Si-12.17 | [variation margin close] | |
10890196 | 0 | 11.12.2017 15:48 | 1,51301E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59280 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10895808 | 0 | 12.12.2017 13:00 | 1,51308E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58920 | 0 | 0 | -720 | Si-12.17 | [variation margin close] | |
10895809 | 0 | 12.12.2017 13:00 | 1,51308E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58920 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10897839 | 0 | 12.12.2017 15:48 | 1,51309E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58909 | 0 | 0 | -22 | Si-12.17 | [variation margin close] | |
10897840 | 0 | 12.12.2017 15:48 | 1,51309E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58909 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10903172 | 0 | 13.12.2017 13:00 | 1,51317E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59213 | 0 | 0 | 608 | Si-12.17 | [variation margin close] | |
10903173 | 0 | 13.12.2017 13:00 | 1,51317E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59213 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10905906 | 0 | 13.12.2017 15:48 | 1,51318E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59072 | 0 | 0 | -282 | Si-12.17 | [variation margin close] | |
10905907 | 0 | 13.12.2017 15:48 | 1,51318E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 59072 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10911277 | 0 | 14.12.2017 13:00 | 1,51326E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58674 | 0 | 0 | -796 | Si-12.17 | [variation margin close] | |
10911278 | 0 | 14.12.2017 13:00 | 1,51326E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 2 | 58674 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10912285 | 71645351 | 14.12.2017 14:48 | 1,51326E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 506789 | DEAL_REASON_EXPERT | 69352663 | 1 | 58661 | -0,5 | 0 | -13 | Si-12.17 | PartialClose position_2 | 25588426 |
10913632 | 0 | 14.12.2017 15:48 | 1,51327E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58783 | 0 | 0 | 109 | Si-12.17 | [variation margin close] | |
10913633 | 0 | 14.12.2017 15:48 | 1,51327E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58783 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10919412 | 0 | 15.12.2017 13:00 | 1,51334E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58912 | 0 | 0 | 129 | Si-12.17 | [variation margin close] | |
10919413 | 0 | 15.12.2017 13:00 | 1,51334E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58912 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10921766 | 0 | 15.12.2017 15:48 | 1,51335E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58946 | 0 | 0 | 34 | Si-12.17 | [variation margin close] | |
10921767 | 0 | 15.12.2017 15:48 | 1,51335E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58946 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10927382 | 0 | 18.12.2017 13:00 | 1,5136E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58630 | 0 | 0 | -316 | Si-12.17 | [variation margin close] | |
10927383 | 0 | 18.12.2017 13:00 | 1,5136E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58630 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10929913 | 0 | 18.12.2017 15:48 | 1,51361E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58664 | 0 | 0 | 34 | Si-12.17 | [variation margin close] | |
10929914 | 0 | 18.12.2017 15:48 | 1,51361E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58664 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10934874 | 0 | 19.12.2017 13:00 | 1,51369E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58635 | 0 | 0 | -29 | Si-12.17 | [variation margin close] | |
10934875 | 0 | 19.12.2017 13:00 | 1,51369E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58635 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10936988 | 0 | 19.12.2017 15:48 | 1,5137E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58629 | 0 | 0 | -6 | Si-12.17 | [variation margin close] | |
10936989 | 0 | 19.12.2017 15:48 | 1,5137E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58629 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10941561 | 0 | 20.12.2017 13:00 | 1,51377E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58657 | 0 | 0 | 28 | Si-12.17 | [variation margin close] | |
10941562 | 0 | 20.12.2017 13:00 | 1,51377E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58657 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10943405 | 0 | 20.12.2017 15:48 | 1,51378E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58684 | 0 | 0 | 27 | Si-12.17 | [variation margin close] | |
10943406 | 0 | 20.12.2017 15:48 | 1,51378E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58684 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10948277 | 0 | 21.12.2017 13:00 | 1,51386E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58560 | 0 | 0 | -124 | Si-12.17 | [variation margin close] | |
10948278 | 0 | 21.12.2017 13:00 | 1,51386E+12 | DEAL_TYPE_BUY | DEAL_ENTRY_IN | 0 | DEAL_REASON_VMARGIN | 69352663 | 1 | 58560 | 0 | 0 | 0 | Si-12.17 | [variation margin open] | |
10949780 | 0 | 21.12.2017 15:45 | 1,51387E+12 | DEAL_TYPE_SELL | DEAL_ENTRY_OUT | 0 | DEAL_REASON_CLIENT | 69352663 | 1 | 58560 | 0 | 0 | 0 | Si-12.17 | [instrument expiration] | 26163141 |
Этот пример взят из истории сделок на демо-счете на рынке FORTS по фьючерсу на пару USDRUR. Из таблицы видно, что вход в позицию был разбит на два ордера с равным лотом. На две части разбит и выход. ID ордера, открывшего сделки для каждого входа и выхода (за исключением вариационной маржи), были ненулевые. Но последняя сделка, закрывшая позицию, пришла с ID ордера, равным нулю. Это произошло, потому что позиция была закрыта не вручную, а из-за экспирации актива.
Эта торговая история уже читаемая и отражает сделки (в том числе и "мнимые"), совершенные в рамках одной позиции. Но она всё еще неудобна для конечного анализа позиции и требует доработки. Нам нужна таблица, каждая строка которой будет полностью отражать финансовый результат по позиции, а также давать сопутствующую информацию.
Создадим для этого структуру:
//+------------------------------------------------------------------+ //| Структура, хранящая финансовый результат и | //| основную информацию по выбранной позиции. | //+------------------------------------------------------------------+ struct DealDetales { string symbol; // символ datetime DT_open; // дата открытия ENUM_DAY_OF_WEEK day_open; // день недели открытия datetime DT_close; // дата закрытия ENUM_DAY_OF_WEEK day_close; // день недели закрытия double volume; // объем (лоты) bool isLong; // признак лонг/шорт double price_in; // цена входа в позицию double price_out; // цена выхода из позиции double pl_oneLot; // прибыль/убыток, если бы торговали одним лотом double pl_forDeal; // реальная прибыль/убыток который реально был с учетом комиссии string open_comment; // комментарий на момент открытия string close_comment; // комментарий на момент закрытия };
Эту структуру формирует метод getDealsDetales. В нем используются 3 ключевых private метода:
- isBorderPoint, определяющий, является ли текущая сделка «пограничной» (выставлена ли она трейдером и закрывает/открывает ли она позицию);
- MA_price, рассчитывающий фактическую цену открытия/закрытия позиции;
- calcContracts, для подсчета количества контрактов, которые были в рынке на момент существования позиции.
Рассмотрим подробнее метод MA_price. К примеру, если позиция была открыта в несколько контрактов и по разным ценам, то будет некорректно фиксировать в качестве цены открытия позиции цену самой первой сделки. Это приведет к погрешностям в дальнейших вычислениях. Используем средневзвешенную величину. Усреднять будем цены открытия/закрытия позиций, а взвешивать их по объему открытых сделок. Релизация этой логики в коде выглядит так:
//+------------------------------------------------------------------+ //| Подсчитывается средневзвешенная цена открытия | //+------------------------------------------------------------------+ double CDealHistoryGetter::MA_price(double &prices[],double &lots[]) { double summ=0; // Сумма взвешенных цен double delemetr=0; // Сумма весов int total=ArraySize(prices); for(int i=0;i<total;i++) // Цикл суммирования { summ+=(prices[i]*lots[i]); delemetr+=lots[i]; } return summ/delemetr; // Расчет средней }Относительно торгуемых лотов: этот случай более запутанный, но разобраться в нем несложно. Рассмотрим пример, когда мы динамически управляем позицией и постепенно прибавляем или сокращаем количество лотов:
In | Out | |
---|---|---|
Open (t1) | 1 | 0 |
t2 | 2 | 0 |
t3 | 5 | 3 |
t4 | 1 | 0 |
t5 | 0 | 1 |
t6 | 1 | 0 |
t7 | 0 | 1 |
t8 | 1 | 0 |
t9 | 0 | 1 |
Close (t10) | 0 | 5 |
Total | 11 | 11 |
Во время существования позиции мы постоянно то докупали, то продавали торгуемый актив (иногда поочередно, а иногда почти одновременно). В результате у нас накопилось 11 лотов на покупку и на продажу. Но это не означает, что максимально достигнутый объем позиции равнялся 11 лотам. Запишем все входы и выходы в ряд: наращивание и входы — со знаком +, а выходы и сокращения — со знаком -. Просуммируем их, как суммируется кривая PL.
Объем сделки | Объем позиции | |
---|---|---|
deal 1 | 1 | 1 |
deal 2 | 2 | 3 |
deal 3 | 5 | 8 |
deal 4 | -3 | 5 |
deal 5 | 1 | 6 |
deal 6 | -1 | 5 |
deal 7 | 1 | 6 |
deal 8 | -1 | 5 |
deal 9 | 1 | 6 |
deal 10 | -1 | 5 |
deal 11 | -5 | 0 |
Из этой таблицы мы явно видим, что максимально достигавшаяся сумма лотов была равна 8. Это и есть искомая величина.
Вот как функция, отражающая количество лотов, реализована в коде:
//+------------------------------------------------------------------+ //| Рассчитываем, какой объем позиции был на самом деле, | //| и получаем информацию об объеме самой последней позиции | //+------------------------------------------------------------------+ double CDealHistoryGetter::calcContracts(double &Arr[],GetContractType type) { int total; if((total=ArraySize(Arr))>1) // если размер массива более одного { double lotArr[]; addArr(lotArr,Arr[0]); // добавляем первый лот в массив for(int i=1;i<total;i++) // цикл со второго элемента массива addArr(lotArr,(lotArr[i-1]+Arr[i])); // добавляем сумму прошлого и текущего лотов в массив (lotArr[i-1]+Arr[i])) if(type==GET_REAL_CONTRACT) return lotArr[ArrayMaximum(lotArr)]; // возвращаем максимальный реально торговавшийся лот else return lotArr[ArraySize(lotArr)-1]; // возвращаем последний торговавшийся лот } else return Arr[0]; }
Помимо простого подсчета лотов, функция еще и показывает последний активный лот (в нашем примере он равен 5).
Рассмотрим функцию isBorderPoint, которая была создана для фильтрации ненужных сделок. Исходя из структуры наших данных, важность сделки мы можем определить, исходя из 4 показателей:
- ID ордера;
- ENUM_DEAL_ENTRY — вход, выход, переворот;
- ENUM_DEAL_TYPE — покупка/продажа;
- ENUM_DEAL_REASON — способ выставления ордера.
Создадим перечисление:
//+--------------------------------------------------------------------+ //| Вспомогательное перечисление, указывающее на тип конкретной записи | //| Сама запись представлена структурой DealData | //+--------------------------------------------------------------------+ enum BorderPointType { UsualInput, // обычный тип входа (DEAL_ENTRY_IN) - Начало сделки UsualOutput, // обычный тип выхода (DEAL_ENTRY_OUT) - завершение сделки OtherPoint, // начисление баланса, корректировки позиции, выводы, вариационная маржа — все, что мы игнорируем InOut, // переворот позиции (DEAL_ENTRY_INOUT) OutBy // выход из позиции по встречному ордеру (DEAL_ENTRY_OUT_BY) };Из пяти представленных вариантов нас интересуют только четыре. Все ненужные сделки попадают под вариант OtherPoint. Сочетания параметров, удовлетворяющие каждому из вариантов перечисления, представлены в таблице. Код функции вы можете посмотреть в файлах, приложенных к статье.
Вариант перечисления | ID ордера | ENUM_DEAL_ENTRY | ENUM_DEAL_TYPE | ENUM_DEAL_REASON |
---|---|---|---|---|
UsualInput | >0 | DEAL_ENTRY_IN | DEAL_TYPE_BUY | DEAL_REASON_CLIENT |
DEAL_REASON_EXPERT | ||||
DEAL_TYPE_SELL | DEAL_REASON_WEB | |||
DEAL_REASON_MOBILE | ||||
UsualOut | >=0(=0 если была экспирация) | DEAL_ENTRY_OUT | DEAL_TYPE_BUY | DEAL_REASON_CLIENT |
DEAL_REASON_EXPERT | ||||
DEAL_REASON_WEB | ||||
DEAL_TYPE_SELL | DEAL_REASON_MOBILE | |||
DEAL_REASON_SL | ||||
DEAL_REASON_TP | ||||
DEAL_REASON_SO | ||||
OtherPoint | 0 | DEAL_ENTRY_IN | DEAL_TYPE_BUY | DEAL_REASON_ROLLOVER |
DEAL_TYPE_SELL | ||||
DEAL_TYPE_BALANCE | ||||
DEAL_TYPE_CREDIT | ||||
DEAL_TYPE_CHARGE | ||||
DEAL_TYPE_CORRECTION | ||||
DEAL_TYPE_BONUS | DEAL_REASON_VMARGIN | |||
DEAL_TYPE_COMMISSION | ||||
DEAL_TYPE_COMMISSION_DAILY | ||||
DEAL_ENTRY_OUT | DEAL_TYPE_COMMISSION_MONTHLY | |||
DEAL_TYPE_COMMISSION_AGENT_DAILY | ||||
DEAL_TYPE_COMMISSION_AGENT_MONTHLY | ||||
DEAL_TYPE_INTEREST | DEAL_REASON_SPLIT | |||
DEAL_TYPE_BUY_CANCELED | ||||
DEAL_TYPE_SELL_CANCELED | ||||
DEAL_DIVIDEND | ||||
DEAL_DIVIDEND_FRANKED | ||||
DEAL_TAX | ||||
InOut | >0 | DEAL_ENTRY_INOUT | DEAL_TYPE_BUY | DEAL_REASON_CLIENT |
DEAL_REASON_EXPERT | ||||
DEAL_TYPE_SELL | DEAL_REASON_WEB | |||
DEAL_REASON_MOBILE | ||||
OutBy | >0 | DEAL_ENTRY_OUT_BY | DEAL_TYPE_BUY | DEAL_REASON_CLIENT |
DEAL_REASON_EXPERT | ||||
DEAL_TYPE_SELL | DEAL_REASON_WEB | |||
DEAL_REASON_MOBILE |
Формировать требуемую историю будем с использованием метода getDeals_forSelectedKeeper. Рассмотрим его общую логику, а затем детально разберем действия для каждого из вариантов вышеописанных перечислений (начиная со строки 303).
//+------------------------------------------------------------------+ //| Формируется одна запись по каждой выбранной позиции в inputParam | //+------------------------------------------------------------------+ void CDealHistoryGetter::getDeals_forSelectedKeeper(DealKeeper &inputParam,DealDetales &ans[]) { ArrayFree(ans); int total=ArraySize(inputParam.deals); DealDetales detales; // переменная, в которую помещаем результаты из последующего цикла detales.symbol=inputParam.symbol; detales.volume= 0; detales.pl_forDeal=0; detales.pl_oneLot=0; detales.close_comment= ""; detales.open_comment = ""; detales.DT_open=0; // Флаг, нужно ли добавлять данную позицию в набор bool isAdd=false; bool firstPL_setted=false; // Массивы цен входа, цен выхода, лоты входа, лоты выхода, контракты double price_In[],price_Out[],lot_In[],lot_Out[],contracts[]; // 404 строка for(int i=0;i<total;i++) // цикл по сделкам (у всех сделок единый ID, но позиций может быть более, чем одна, если тип сделки InOut) { BorderPointType type; // тип сделки 408 строка double pl_total=0; if(isBorderPoint(inputParam.deals[i],type)) // узнаем, пограничная ли это сделка, и ее тип 301 строка { // 413 строка } // 414 строка else { /* Если сделка не пограничная, то просто фиксируем финансовый результат. Тут могут быть только вариационная маржа и различные коррекции. Пополнения и снятия со счета фильтруются при получении исходных данных */ detales.pl_forDeal+=(inputParam.deals[i].profit+inputParam.deals[i].comission); detales.pl_oneLot+=inputParam.deals[i].profit/calcContracts(contracts,GET_LAST_CONTRACT); } } // Фильтруем активные позиции и не сохраняем их if(isAdd && PositionSelect(inputParam.symbol)) // 541 строка { if(PositionGetInteger(POSITION_IDENTIFIER)==inputParam.ID) isAdd=false; } // 546 строка // Сохраняем зафиксированные и более неактивные позиции if(isAdd) { detales.price_in=MA_price(price_In,lot_In); detales.price_out=MA_price(price_Out,lot_Out); detales.volume=calcContracts(contracts,GET_REAL_CONTRACT); addArr(ans,detales); } }
На 404 строке функции объявляются массивы. В дальнейшем они будут использованы в условии, обозначенном с 411 или 414 строки. Внутри этого условия рассматриваются только пограничные точки (сделки, приводящие к открытию/закрытию или донабору/частичному закрытию изучаемой позиции).
Если же сделка не попадает под первое условие, то единственное требуемое действие — это подсчет прибыли/убытка по ней. Дело в том, что передаваемая нами история содержит фактически начисленную прибыль/убыток, которая разбита на сделки. Каждая сделка отражает изменение PL, начиная от прошлой сделки до момента свершения анализируемой. Общая прибыль за позицию равняется сумме PL всех этих сделок. Если считать прибыль/убыток просто как разность между ценами открытия и закрытия, то мы упустим ряд факторов: проскальзывание, изменение объема внутри позиции, комиссии и сборы.
С 541 по 546 строки кода происходит фильтрация открытых позиций, которые мы не сохраняем в качестве результата. В завершении функции рассчитываются цены открытия и закрытия, а также максимально существовавший в рынке объем позиции.
Переменная type служит для фильтрации типов пограничных точек. Если в текущий момент совершается открытие либо донабор сделки, то мы попадаем в следующее условие, которое прописано начиная с 413 строки (см. полный текст метода в приложении).
if(type==UsualInput) // если первоначальный вход или же увеличение позиции { if(detales.DT_open==0) // присваиваем дату открытия позиции { detales.DT_open=inputParam.deals[i].DT; detales.day_open=getDay(inputParam.deals[i].DT); } detales.isLong=inputParam.deals[i].type==DEAL_TYPE_BUY; // определяем направление позиции addArr(price_In,inputParam.deals[i].price); // сохраняем цену входа addArr(lot_In,inputParam.deals[i].volume); // сохраняем количество лотов pl_total=(inputParam.deals[i].profit+inputParam.deals[i].comission); detales.pl_forDeal+=pl_total; // прибыль/убыток от сделки с учетом комиссии if(!firstPL_setted) { detales.pl_oneLot=pl_total/inputParam.deals[i].volume; // прибыль/убыток от сделки с учетом комиссии, если торговать одним лотом firstPL_setted=true; } else detales.pl_oneLot=inputParam.deals[i].profit/calcContracts(contracts,GET_LAST_CONTRACT); if(StringCompare(inputParam.deals[i].comment,"")!=0) // комментарий по сделке detales.open_comment+=(StringCompare(detales.open_comment,"")==0 ? inputParam.deals[i].comment : (" | "+inputParam.deals[i].comment)); addArr(contracts,inputParam.deals[i].volume); // добавляем объем сделки со знаком "+" }
В нем мы присваиваем дату открытия позиции и расчитываем прибыль (общую и на один лот). Закрытие позиции происходит по другому условию:
if(type==UsualOut || type==OutBy) // закрытие позиции { /* Не сохраняем сразу, так как выходов может быть несколько (постепенно один за другим) Поэтому во избежание потери части данных просто устанавливаем флаг включенным */ if(!isAdd)isAdd=true; // флаг на сохранение позиции detales.DT_close=inputParam.deals[i].DT; // дата закрытия detales.day_close=getDay(inputParam.deals[i].DT); // день закрытия addArr(price_Out,inputParam.deals[i].price); // сохраняем цены выхода addArr(lot_Out,inputParam.deals[i].volume); // сохраняем объем выхода pl_total=(inputParam.deals[i].profit+inputParam.deals[i].comission); // Прибыль/убыток от сделки с учетом комиссии detales.pl_forDeal+=pl_total; if(i==total-1) detales.pl_oneLot+=pl_total/calcContracts(contracts,GET_LAST_CONTRACT); // Прибыль/убыток от сделки с учетом комиссии, если торговать одним лотом else detales.pl_oneLot+=inputParam.deals[i].profit/calcContracts(contracts,GET_LAST_CONTRACT); // Прибыль/убыток от сделки с учетом комиссии, если торговать одним лотом if(StringCompare(inputParam.deals[i].comment,"")!=0) // Комментарий по сделке detales.close_comment+=(StringCompare(detales.close_comment,"")==0 ? inputParam.deals[i].comment : (" | "+inputParam.deals[i].comment)); addArr(contracts,-inputParam.deals[i].volume); // Добавляем объем сделки со знаком "-" }
Это условие присваивает дату выхода из позиции, рассчитывает прибыль за сделку. Входов и выходов может быть несколько — а значит, эти условия могут наступать многократно.
Условие InOut — комбинация из второго и первого условий. Оно наступает только один раз и заканчивается переворотом позиции.
if(type==InOut) // Переворот позиции { /* Часть 1: Сохраняем предыдущую позицию */ firstPL_setted=true; double closingContract=calcContracts(contracts,GET_LAST_CONTRACT); // закрываемый контракт double myLot=inputParam.deals[i].volume-closingContract; // открываемый контракт addArr(contracts,-closingContract); // добавляем объем сделки со знаком "-" detales.volume=calcContracts(contracts,GET_REAL_CONTRACT); // получаем максимальный реально существовавший объем сделки detales.DT_close=inputParam.deals[i].DT; // дата закрытия detales.day_close=getDay(inputParam.deals[i].DT); // день закрытия addArr(price_Out,inputParam.deals[i].price); // цена выхода addArr(lot_Out,closingContract); // объем выхода pl_total=(inputParam.deals[i].profit*closingContract)/inputParam.deals[i].volume; // рассчитываем PL за сделку закрытия double commission_total=(inputParam.deals[i].comission*closingContract)/inputParam.deals[i].volume; detales.pl_forDeal+=(pl_total+commission_total); detales.pl_oneLot+=pl_total/closingContract; // прибыль/убыток от сделки с учетом комиссии, если торговать одним лотом if(StringCompare(inputParam.deals[i].comment,"")!=0) // сохраняем комментарий закрытия detales.open_comment+=(StringCompare(detales.open_comment,"")==0 ? inputParam.deals[i].comment : (" | "+inputParam.deals[i].comment)); detales.price_in=MA_price(price_In,lot_In); // получаем цену входа в позицию (усредненную) detales.price_out=MA_price(price_Out,lot_Out); // получаем цену выхода из позиции (усредненную) addArr(ans,detales); // добавляем сформированую позицию if(isAdd)isAdd=false; // обнуляем флаг, если он был включен // Чистка части данных ArrayFree(price_In); ArrayFree(price_Out); ArrayFree(lot_In); ArrayFree(lot_Out); ArrayFree(contracts); detales.close_comment=""; detales.open_comment=""; detales.volume=0; /* Часть 2: Сохраняем новую позицию, предварительно удалив часть данных из массива detales */ addArr(contracts,myLot); // Добавляем лот открытия позиции pl_total=((inputParam.deals[i].profit+inputParam.deals[i].comission)*myLot)/inputParam.deals[i].volume; // Прибыль/убыток от сделки с учетом комиссии detales.pl_forDeal=pl_total; detales.pl_oneLot=pl_total/myLot; //Прибыль/убыток от сделки с учетом комиссии, если торговать одним лотом addArr(lot_In,myLot); // Добавляем лот входа detales.open_comment=inputParam.deals[i].comment; // Сохраняем комментарий detales.DT_open=inputParam.deals[i].DT; // Сохраняем дату открытия detales.day_open=getDay(inputParam.deals[i].DT); // Сохраняем день открытия detales.isLong=inputParam.deals[i].type==DEAL_TYPE_BUY; // Определяем направление сделки addArr(price_In,inputParam.deals[i].price); // Сохраняем цену входа }
Итогом описанных вычислений становится таблица, где каждая строка отражает основные параметры позиции. Теперь мы можем производить все требуемые расчеты, оперируя уже позициями, а не сделками, из которых они состоят.
Instrument | Open DT | Open day | Close DT | Close day | Contracts | Direction | Price open | Price close | PL for 1 lot | PL for position | Open comment | Close comment |
---|---|---|---|---|---|---|---|---|---|---|---|---|
RTS-12.17 | 17.11.2017 19:53 | FRIDAY | 17.11.2017 19:54 | FRIDAY | 2.00000000 | Long | 113200.00000000 | 113180.00000000 | -25.78000000 | -55.56000000 | ||
RTS-12.17 | 17.11.2017 19:54 | FRIDAY | 17.11.2017 19:54 | FRIDAY | 2.00000000 | Short | 113175.00000000 | 113205.00000000 | -58.47000000 | -79.33000000 | ||
RTS-12.17 | 17.11.2017 19:58 | FRIDAY | 17.11.2017 19:58 | FRIDAY | 1.00000000 | Short | 113240.00000000 | 113290.00000000 | -63.44000000 | -63.44000000 | ||
RTS-12.17 | 17.11.2017 19:58 | FRIDAY | 17.11.2017 19:58 | FRIDAY | 1.00000000 | Long | 113290.00000000 | 113250.00000000 | -51.56000000 | -51.56000000 | ||
Si-12.17 | 17.11.2017 20:00 | FRIDAY | 17.11.2017 20:00 | FRIDAY | 10.00000000 | Long | 59464.40000000 | 59452.80000000 | -23.86000000 | -126.00000000 | ||
Si-12.17 | 17.11.2017 20:00 | FRIDAY | 17.11.2017 20:00 | FRIDAY | 5.00000000 | Short | 59453.20000000 | 59454.80000000 | -5.08666667 | -13.00000000 | ||
Si-12.17 | 17.11.2017 20:02 | FRIDAY | 17.11.2017 20:02 | FRIDAY | 1.00000000 | Short | 59460.00000000 | 59468.00000000 | -9.00000000 | -9.00000000 | ||
Si-12.17 | 17.11.2017 20:02 | FRIDAY | 17.11.2017 20:03 | FRIDAY | 2.00000000 | Long | 59469.00000000 | 59460.00000000 | -14.50000000 | -20.00000000 | ||
Si-12.17 | 21.11.2017 20:50 | TUESDAY | 21.11.2017 21:06 | TUESDAY | 2.00000000 | Long | 59467.00000000 | 59455.00000000 | -13.00000000 | -26.00000000 | ||
Si-12.17 | 23.11.2017 17:41 | THURSDAY | 21.12.2017 15:45 | THURSDAY | 2.00000000 | Long | 58736.50000000 | 58610.50000000 | -183.00000000 | -253.50000000 | Open test position | Open test position | PartialClose position_2 | [instrument expiration] |
RTS-12.17 | 23.11.2017 18:07 | THURSDAY | 14.12.2017 14:45 | THURSDAY | 1.00000000 | Short | 115680.00000000 | 114110.00000000 | 1822.39000000 | 1822.39000000 | Open test position_2 | |
RTS-3.18 | 30.01.2018 20:22 | TUESDAY | 30.01.2018 20:22 | TUESDAY | 2.00000000 | Short | 127675.00000000 | 127710.00000000 | -61.01000000 | -86.68000000 | ||
RTS-3.18 | 30.01.2018 20:24 | TUESDAY | 30.01.2018 20:24 | TUESDAY | 1.00000000 | Long | 127730.00000000 | 127710.00000000 | -26.49000000 | -26.49000000 | ||
RTS-3.18 | 30.01.2018 20:24 | TUESDAY | 30.01.2018 20:25 | TUESDAY | 1.00000000 | Long | 127730.00000000 | 127680.00000000 | -60.21000000 | -60.21000000 | ||
RTS-3.18 | 30.01.2018 20:25 | TUESDAY | 30.01.2018 20:25 | TUESDAY | 1.00000000 | Long | 127690.00000000 | 127660.00000000 | -37.72000000 | -37.72000000 | ||
RTS-3.18 | 30.01.2018 20:25 | TUESDAY | 30.01.2018 20:26 | TUESDAY | 1.00000000 | Long | 127670.00000000 | 127640.00000000 | -37.73000000 | -37.73000000 | ||
RTS-3.18 | 30.01.2018 20:29 | TUESDAY | 30.01.2018 20:30 | TUESDAY | 1.00000000 | Long | 127600.00000000 | 127540.00000000 | -71.45000000 | -71.45000000 |
Глава 2. Формирование пользовательского торгового отчета
Теперь создадим класс, формирующий торговый отчет. Определимся с требованиями к отчету.
- Отчет будет содержать и стандартные графики PL, и доработанные, для более наглядной оценки результативности. Построение графиков начинается с нуля (вне зависимости от начального капитала) — это повысит объективность оценки.
- Будет формироваться и график Buy and hold стратегии, сопоставимый с графиком PL (их будет рассчитывать одна функция). Оба этих графика будут строиться по всем активам, фигурирующим в отчете.
- Основные коэффициенты и результаты трейдинга должны отражаться в виде таблицы.
- Должны строиться дополнительные графики: например, Pl по дням.
В завершение представим проанализированные параметры в виде сменяющихся графиков и выгрузим все результаты расчетов в csv-файлы. Приводимые здесь примеры созданы по реальной истории за определенный промежуток времени. Эта история приложена в конце статьи. Скрипт для визуализации и выгрузки данных будет содержать функции: первая покажет примеры по прилагаемой истории торгов, а вторая — по вашей собственной, выгруженной из вашего терминала.
В этой части статьи будет минимум кода. Вместо этого сосредоточимся на рассмотрении полученных данных и описании их смысла. Все части класса, создающего отчет, подробно прокомментированы, поэтому вы легко в них разберетесь. Для более удобного ориентирования в описываемых функциях прилагаю структуру рассматриваемого класса.
class CReportGetter { public: CReportGetter(DealDetales &history[]); // можно задать только историю в подготовленном формате CReportGetter(DealDetales &history[],double balance); // можно задать как историю, так и баланс, относительно которого будут вестись расчеты ~CReportGetter(); bool get_PLChart(PL_chartData &pl[], PL_chartData &pl_forOneLot[], PL_chartData &Indicative_pl[], string &Symb[]); // графики PL bool get_BuyAndHold(PL_chartData &pl[], PL_chartData &pl_forOneLot[], PL_chartData &Indicative_pl[], string &Symb[]); // графики buy and hold bool get_PLHistogram(PL_chartData &pl[], PL_chartData &pl_forOneLot[], string &Symb[]); // гистограмма накопления прибыли и убытка bool get_PL_forDays(PLForDaysData &ans, DailyPL_calcBy calcBy, DailyPL_calcType type, string &Symb[]); // PL по дням недели bool get_extremePoints(PL_And_Lose &total, PL_And_Lose &forDeal, string &Symb[]); // крайние точки (максимальные и минимальные показатели за сделку и с накоплением) bool get_AbsolutePL(PL_And_Lose &total, PL_And_Lose &average, string &Symb[]); // абсолютные показатели (накопленные и усредненные PL) bool get_PL_And_Lose_percents(PL_And_Lose &ans,string &Symb[]); // диаграмма распределения прибыльных и убыточных сделок bool get_totalResults(TotalResult_struct &res,string &Symb[]); // таблица основных показателей bool get_PLDetales(PL_detales &ans,string &Symb[]); // краткая сводка по графику PL void get_Symbols(string &SymbArr[]); // получение списка имеющихся в истории символов private: DealDetales history[]; // хранит историю торгов double balance; // хранит значение баланса void addArray(DealDetales &Arr[],DealDetales &value); // добавление в динамический массив данных void addArray(PL_chartData &Arr[],PL_chartData &value); // добавление в динамический массив данных void addArray(string &Arr[],string value); // добавление в динамический массив данных void addArray(datetime &Arr[],datetime value); // добавление в динамический массив данных void sortHistory(DealDetales &arr[],bool byClose); // Сортировка истории по дате открытия или закрытия void cmpDay(int i,ENUM_DAY_OF_WEEK day,ENUM_DAY_OF_WEEK etaloneDay,PLDrowDown &ans); // Заполнение структуры PLDrowDown bool isSymb(string &Symb[],int i); // Проверка Есть ли i-тый символ из истории в массиве Symb bool get_PLChart_byHistory(PL_chartData &pl[], PL_chartData &pl_forOneLot[], PL_chartData &Indicative_pl[], DealDetales &historyData[]); // Графики PL по переданной истории ENUM_DAY_OF_WEEK getDay(datetime DT); // Получение дня торгов из даты };
3 вида графика PL
Чтобы построить график прибыли и убытка, отсортируем исходные данные по дате закрытия позиции. Не будем вдаваться в подробности построения — оно будет стандартным. Он будет состоять из двух графиков с датами закрытия позиций по оси Х. Первым будет собственно график PL, а вторым — накопленная просадка относительно максимального значения PL, в процентах. Рассмотрим 3 разновидности графиков.
- Стандартный график накопленной прибыли.
- График накопленной прибыли без учета объема торгов — как если бы мы на всей истории и по всем инструментам торговали бы одним лотом.
- График прибыли и убытка, нормированный на максимальный убыточный (или прибыльный) лот. Если график PL находится в красной зоне, то мы делим i-тый убыток на максимальный прибыльный лот. Если график находится в зеленой зоне, то мы делим i-тую прибыль на максимальный убыточный лот.
Первые два графика дают нам представление о том, как изменение объема торгов влияет на прибыль/убыток. Третий график позволяет представить экстремальную ситуацию: если бы вдруг началась череда убытков, и эти убытки равнялись бы максимально возможным историческим потерям за один лот (чего практически не может произойти). Он показывает, сколько максимальных убытков подряд может вынести наш график PL до того, как дойдет до 0 (вся прибыль будет сведена на нет) — или же наоборот, что будет, когда все убытки будут сведены на нет чередой максимальных прибылей. Ниже приведены рассматриваемые типы графиков:
График PL при торговле одним лотом выглядит куда привлекательнее, чем реальный график. К тому же на реальных торгах прибыль превышала гипотетическую "однолотовую" всего на 30 %. Размер лота варьировался от 1 до 10. Просадка в процентах доходила до 30 000 %. Звучит ужасающе, но не все так страшно. Как мы уже говорили, график PL строится с нулевой отметки, а просадка рассчитывается относительно максимального значения кривой PL. В один из моментов времени, пока убыток еще не достиг максимума (красная зона на графике), прибыль поднималась на несколько рублей, а потом падала до - 18 000 р. Отсюда и берутся эти несуразные значения. В реальности же просадка была не более 25 % на один лот и 50 % — по факту.
Нормированный график PL тоже застрял на одном значении. Это указывает на необходимость поменять подход к торговле (допустим, изменить метод управления лотами) и, как вариант, переоптимизировать торговые алгоритмы. График PL с учетом изменения лотов выглядит хуже "однолотового" за тот же период, но всё же такое управление позициями принесло свои плоды: всплески просадок стали слабее.
Класс, формирующий торговый отчет, называется CReportGetter, а графики строит метод get_PLChart_byHistory. Этот метод подробно прокомментирован в файлах с исходным кодом. Он работает следующим образом:
- Осуществляется проход по переданной истории.
- Каждая позиция участвует в расчете PL, максимальной прибыли и просадки.
- Просадка рассчитывается по накоплению относительно максимальной достигнутой прибыли. Если график PL сразу же начал опускаться в красную зону, то просадка пересчитывается с каждым обновлением минимумов. В данном случае максимальная прибыль будет равна нулю, а просадка на i-том и всех предыдущих элементах пересчитывается по максимальному убытку. Просадка в этот момент равняется 100 %. Как только максимальная прибыль оказывается больше нуля (кривая PL переходит в положительную часть графика), то просадка всегда рассчитывается относительно максимальной прибыли.
Проведем анализ, сопоставив 3 графика. Выявляется гораздо больше недочетов, чем если бы мы рассматривали один стандартный график прибыли и убытка. Графики PL стратегии buy and hold, как уже было упомянуто, строятся по тому же алгоритму. Для примера представлю только один из графиков. Остальные построятся автоматически при запуске скрипта визуализации.
График показывает, что если бы торговля теми же активами велась по стратегии Buy and hold в тех же объемах, то график PL едва ли сошелся бы к нулю, а просадка была бы порядка 80 000 р (вместо 70 000 максимально достигнутой прибыли).
Гистограмма накопления PL
Этот вид графика представляет собой две гистограммы, наложенные друг на друга. Их построение немного отличается от стандартного графика PL. Гистограмма прибылей создается постепенным суммированием только прибыльных сделок. Если i-тая позиция является убыточной, то мы игнорируем ее значение и записываем предыдущее значение на i-тый временной интервал. Гистограмма убыточных сделок строится по зеркальной логике. Ниже приведены графики гистограмм прибыли и убытка по нашим данным.
Привычный нам график PL — простая разность гистограммы прибыли и убытка. Первое, что бросается в глаза при анализе — это то, как сильно реально полученная прибыль отличается от наторгованной. Если реально полученая прибыль превосходила прибыль от торговли одним лотом примерно на 30%, то анализ графика по гистограммам прибыли дает разницу почти на 50 %. Секрет недополученных 20% прибыли кроется в отличии динамики роста прибыли над убытками на первом и втором графиках. Проиллюстрируем сказанное. Построим по этим гистограммам два графика динамики прибыли и убытка. Формула предельна проста:
Мы видим, что динамика прибыли по сравнению с убытками вначале возрастала. Это идеальное развитие событий: с каждой новой сделкой наша прибыльная гистограмма все больше отрывается от гистограммы убыточных сделок. И если на протяжении всей торговли график динамики роста прибылей над убытками имеет положительный наклон, то мы получаем один из лучших исходов событий. Прибыль накапливается все быстрее, а убытки прибавляются с прежней или же меньшей по отношению к прибылям скоростью.
Следующий отрезок графиков показывает, что гистограмма прибыли прекратила увеличивать процентный отрыв от гистограммы убытков, но зависла в боковике. Если боковик прироста прибылей над убытками имеет любой интервал больше единицы, то это тоже хороший сигнал. Если интервал равен единице, то кривая PL сведется к нулю (каждая новая прибыль будет равна каждому новому убытку). Если же интервал "зависания" нашей гистограммы меньше 1, то мы перешли к постепенному сливу. Иначе говоря, рассматриваемые графики — это графики постепенного изменения профит-фактора. Проиллюстрируем это:
Здесь синяя гистограмма — это линейное начисление убытка (по сути, это график от 0 до 100).
- Зеленая линяя — гистограмма убытков * 1,3,
- Серая — гистограмма убытков * 1,
- Красная — гистограмма убытков * 0,7.
Графики показывают, что коэффициент превышения прибылей над убытками (профит-фактор) всегда должен быть больше единицы. Идеальный вариант — если он постепенно растет. При этом PL графика будет расти экспоненциально. К сожалению, это несбыточно в долгосрочной перспективе, но может случиться в короткий период удачи, если повышать лот после каждой прибыльной сделки. Рассмотренный пример дает право со 100% уверенностью утверждать, что главное в торговле — не то, как меняется график PL, а то, как себя ведет гистограмма прибыльных и убыточных сделок, а следовательно — какова динамика изменения профит-фактора.
Теперь поговорим про недополученные 20% прибыли. На графике динамики роста прибыли на один лот мы видим, что приросты прибыли зависли на отметке [1,4 — 1,6]. Это выше, чем [1.2 — 1.4]. Со временем это различие и "съело" 20% потенциальной прибыли. Иначе говоря, управление размером позиций не прошло даром. Если вы используете методы наподобие мартингейла/антимартингейла, то эти графики — и особенно показатели, рассчитанные на их основе, — могут дать много полезной информации для анализа. Эти графики строит функция get_PLHistogram.
Прибыль и убыток по дням
Подобный график знаком всем, кто хоть раз тестировал роботов в тестере терминала. Смысл и методика построения этой гистограммы неизменны, но я добавил возможность строить ее по «абсолютным» данным (получаемым методом простого суммирования прибылей и убытков по дням) и усредненным данным. В приложенном классе используется усреднение методом простого среднего значения. Но другие методы усреднения (мода/медиана, взвешенное усреднение) могут дать результаты поинтереснее. Эти типы усреднения вы можете реализовать самостоятельно, если захотите.
Также я добавил сумму сделок (положительных и отрицательных): она показывает количество сделок по дням. Совместный анализ этой гистограммы и гистограммы PL по дням дает более верное представление о торговле в разрезе недели. Например, он позволит отследить ситуацию, когда в один из дней будет получено 50 малых убытков и 3 прибыли, которые с лихвой перекроют все потери. Для примера приведу график, построенный по абсолютным данным и по ценам закрытия позиций.
Как видно из графика по всем дням недели, положительные сделки перевешивали отрицательные, однако отрицательных было всегда больше (и это в целом норма для алгоритмического трейдинга). И ни в один из дней отрицательных сделок не было больше, чем положительных, более чем на 25 %, что тоже в целом нормально.
График крайних точек и абсолютных показателей
График крайних точек — это гистограмма из 4 столбцов, разделенных на две группы. Крайние точки — это максимальные и минимальные отклонения на графике PL (максимально достигнутая прибыль и максимальная накопленная просадка). Еще два столбца — максимально прибыльная и убыточная позиции. Обе группы данных строятся по данным реальных торгов, а не по данным прибыли и убытка за одну сделку. Подсчет максимальной прибыли по кривой PL — это самая высокая точка на ней. Методику расчета максимальной просадки я описывал выше. Для большей конкретики сверну все в формулу:
Этот график иллюстрирует, какой был максимальный разброс прибыли и убытка. Доля максимальной просадки по сравнению с максимальной прибылью составила 30 %, а максимально убыточной сделки по сравнению с максимально прибыльной — 60%. В коде это реализуется как цикл с постепенным сравнением данных кривой PL по обозначенным условиям.
Что же касается абсолютных показателей, то их лучше всего представить в виде таблицы. Это тоже две группы данных: первая — сумма всех прибылей и сумма всех убытков, а вторая — средние значения прибылей и убытков по анализируемой истории.
Общая | Усредненная | |
---|---|---|
Прибыль | 323237 | 2244,701 |
Просадка | 261534 | 1210,806 |
Таблицы с краткой сводкой по графику PL и основными показателями результатов торговли
В этих таблицах дается характеристика результативности торговли в цифрах. Программно они создаются в виде следующих структур:
//+------------------------------------------------------------------+ //| Структура результатов торговли | //+------------------------------------------------------------------+ struct TotalResult_struct { double PL; // Общая прибыль или убыток double PL_to_Balance; // Отношение PL к текущему баллансу double averagePL; // Средняя прибыль или убыток double averagePL_to_Balance; // Отношение averagePL к текущему балансу double profitFactor; // Фактор прибыльности double reciveryFactor; // Фактор востановления double winCoef // Коэффициент выигрыша double dealsInARow_to_reducePLTo_zerro;/* Если сейчас прибыль - сделок подряд с максимальным убытком за сделку, чтобы свести текущую Прибыль по счету в ноль. Если сейчас убыток - сделок подряд с максимальной прибылью за сделку, чтобы убыток свести в ноль*/ double maxDrowDown_byPL; // Максимальная просадка по PL double maxDrowDown_forDeal; // Максимальная просадка за сделку double maxDrowDown_inPercents; // Максимальная просадка по PL в процентах к текущему балансу datetime maxDrowDown_byPL_DT; // Дата максимальной просадки по PL datetime maxDrowDown_forDeal_DT //Дата максимальной просадки за сделку double maxProfit_byPL; // Максимальная прибыль по PL double maxProfit_forDeal; // Максимальная прибыль за сделку double maxProfit_inPercents; // Максимальная прибыль по PL в процентах к текущему балансу datetime maxProfit_byPL_DT; // Дата максимальной прибыль по PL datetime maxProfit_forDeal_DT; // Дата максимальной прибыли за сделку }; //+------------------------------------------------------------------+ //| Часть структуры PL_detales (объявлена ниже) | //+------------------------------------------------------------------+ struct PL_detales_item { int orders; // количество сделок double orders_in_Percent; // количество ордеров в % к общему количеству ордеров int dealsInARow; // Сделок подряд double totalResult; // Суммарный результат в валюте депозита double averageResult; // Средний результат в валюте депозита }; //+-------------------------------------------------------------------+ //| Краткая сводка по графику PL разбитая на 2 основополагающих блока | //+-------------------------------------------------------------------+ struct PL_detales { PL_detales_item profits; // Информация по прибыльным сделкам PL_detales_item loses; // Информация по убыточным сделкам };
Первая структура — TotalResult_struct — представляет собой сводку основных показателей по всей запрашиваемой торговой истории. В нее входят и очевидно необходимые показатели (прибыль и убыток за сделку и т.д.), и рассчитанные коэффициенты результативности трейдинга.
Вторая и третья структуры неразрывно связаны. Основная из них — PL_detales, в ней сосредоточена краткая сводка по гистограмме прибыли и убытка. По анализируемой нами истории были получены следующие показатели:
Показатель | Значение |
---|---|
PL | 65039 |
PL к балансу | 21,8986532 |
Средний PL | 180,1634349 |
Средний PL к балансу | 0,06066109 |
Профит фактор | 1,25097242 |
Фактор востановления | 3,0838154 |
Коэфицент выигрыша | 1,87645863 |
Сделок до сведения PL в нуль | 24,16908213 |
Просадка по PL | -23683 |
Просадка за сделку 1 лотом | -2691 |
Просадка к балансу | -0,07974074 |
Дата просадки по PL | 24.07.2017 13:04 |
Дата просадки за сделку 1 лотом | 31.01.2017 21:53 |
Прибыль по PL | 73034 |
Прибыль за сделку 1 лотом | 11266 |
Прибыль к балансу | 0,24590572 |
Дата прибыли по PL | 27.06.2017 23:42 |
Дата прибыли за сделку 1 лотом | 14.12.2016 12:51 |
Вторая таблица выглядит следующим образом:
Прибыли | Убытки | |
---|---|---|
Средний результат | 2244,701 | -1210,81 |
Сделок подряд | 5 | 10 |
Сделок всего | 144 | 216 |
Сделок в % | 0,398892 | 0,598338 |
Общий результат: | 323237 | -261534 |
Распределение убыточных и прибыльных позиций можно представить в виде круговой диаграммы:
Как видно из результата, 40 % сделок были положительными.
Заключение
Не секрет, что при трейдинге с помощью алгоритмов недостаточно просто оптимизировать их и запустить. Результат запуска алгоритма на всю катушку сразу же после оптимизации по наитию, и без разделения по структуре портфеля может оказаться более чем неожиданным. Конечно же, нашему брату-трейдеру свойственно играть в русскую рулетку, но для тех, кто все же предпочитает рассчитывать свои шаги наперед, использованная в статье методика разбиения истории дает интересную возможность: протестировать, насколько оптимален подбор весов, присвоенных каждому алгоритму трейдинга.
Особенно интересно будет посидеть над разными методиками расчета портфеля и протестировать их на наторгованной истории, сравнивая результат торговли по свежесформированному портфелю и фактически полученный результат. Осуществить задуманное поможет торговля одним лотом, которая рассчитывается по реально наторгованной истории в первой главе этой статьи. Но надо помнить, что данные, полученные при подобных расчетах, будут лишь приблизительными, потому что в них не учитывается ликвидность активов, комиссии и всевозможные форсмажоры.
К статье прилагаются следующие файлы:
- CSV-файл dealHistory.csv с историей торгов (находится в папке MQL5\Files\article_4803), по которой в текущей статье приводились примеры.
- Исходные файлы для MetaTrader 5, которые находятся в папке MQL5\Scripts\Get_TradigHistory
- Проект тестового скрипта, разделенный на нижеописанные части
- Файл ListingFilesDirectory.mqh – библиотека функций WinAPI, которая позволяет оперировать файлами повсюду на компьютере.
- Файлы Tests.mqh и Tests.mq5 – cохраняют полученный отчет в файл, предварительно создав папки по переданным путям.
- Файл PlotCharts.mqh – строит графики визуализации полученных отчетов в самом терминале.
- Сам тестовый скрипт - Get_TradingHistory.mq5, в нем есть 2 функции:
- Первая “test_1” – принимает в качестве параметра путь к файлу с приложенной тестовой историей и, в качестве второго параметра, путь к папке с результатами. Данная функция создает отчет по тестовым файлам переданным по первому параметру функции.
- Вторая “test_2” – Принимает в качестве параметра лишь путь ко второй папке (пути должны быть разными), где будет сохранен отчет Вашей торговли.
Основные файлы:
- Файл DealHistoryGetter.mqh – здесь функции, реализующие механизм выгрузки данных, описанной в первой главе данной статьи.
- Файл ReportGetter.mqh – в нем реализован механизм создания самих отчетов торгов. Класс, содержащийся в данном файле, принимает сформированную историю и обрабатывает ее.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Да понятно, вопрос в том, в те ли функцию это поместил? Вопрос не в точности, иначе лучше стринг приобразовать. Просто искать фьючи 2017 года как то не охота.
Да в те.
Можно ли считать историю теста тестера в свои файлы? Не из советника, а путем накидывания скрипта?
Да, собственно это как раз и есть скрипт который считывает историю со всего терминала не разбивая ее на советники. Собственно говоря в статье я описывал класс по большей мере который выполняет всю работу, Вы можете им воспользоваться и написать собственный скрипт.
Хочу использовать Вашу разработку для анализа истории при оптимизации в момент деинициализации.
Подскажите, пожалуйста, как это лучше сделать, что бы получить изменение баланса в один массив? Инструмент торгуется только текущий.
Хочу использовать Вашу разработку для анализа истории при оптимизации в момент деинициализации.
Подскажите, пожалуйста, как это лучше сделать, что бы получить изменение баланса в один массив? Инструмент торгуется только текущий.
Для этого лучше использовать доработанный класс из моей второй статьи. Класс "ReportCreator"
Используя данный класс я с помощью вот этого кода в OnDeinit в своих роботах выгружаю отчеты оптимизации:Данный код находится в отдельном файле который я подключаю через #include к роботу.