Оценка торговых систем - эффективности входа, выхода и сделок

Mykola Demko | 15 сентября, 2010


Введение

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


Пролог

Напомню, что год издания книги - 2003 г. При издании книги в ходу был MetaTrader  3 c языком программирования MQL-II. Как отражение того времени это была довольно передовая платформа. Так что, анализируя различия с текущим терминалом MetaTrader 5,  можно отследить, как изменились сами торговые условия. Надо отметить, что автор книги стал практически учителем для многих поколений трейдеров (учитывая тот факт, что поколения в нашем деле меняются быстро). Но время не стоит на месте, и хотя принципы, заложенные в книге можно применять и сейчас, методы всё же придётся адаптировать.

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

Это основы, но я не зря их напомнил, именно такой учёт вёлся во времена написания книги "Статистика для трейдера", такой и только такой. Каждая сделка на 1 лот должна была закрыться обратной сделкой так же на 1 лот. Но уже в 2005 году, т.е. через два года, применение такой статистики требовало преобразований. Всё дело в том, что в MetaTrader 4 разрешено частичное закрытие сделки. Таким образом, чтоб пользоваться статистикой, описанной у Булашева, потребуется доработка учёта, а именно, вести учёт не по открытию, а постфактум по закрытиям.

Ещё через 5 лет ситуация изменилась до неузнаваемости. Куда подевалось привычное понятие ордер? Ушло в небытиё. Читая на форуме поток вопросов, тут, думаю, будет уместно разъяснить, какой именно учёт применяется в MetaTrader 5.

Итак, на сегодняшний день нет классического понятия ордер. Ордером теперь именуется торговый приказ серверу ДЦ, который трейдер или МТС отдаёт на открытие или изменение торговой позиции. Теперь это позиция, для понимания сути позиции я и привёл упоминание о маржевой торговле. Дело в том, что маржевая торговля осуществляется на заёмные деньги, и вот пока существует этот заём и существует позиция.

Как только вы рассчитаетесь с заёмщиком путём закрытия позиции и соответственно фиксацией прибыли/убытков, ваша позиция перестанет существовать. Это момент, кстати, проясняет, почему переворот позиции не закрывает её. Дело в том, что заём то всё равно остаётся, и не имеет разницы  для осуществления покупки или продажи вы делали заём. Сделка - это всего лишь история осуществлённого ордера.

Теперь по возможностям торговли. В настоящий момент в MetaTrader 5 можно не только частично закрыть торговую позицию, но и добавиться к уже существующей позиции. Таким образом, классический учёт, где каждому открытию на один объём противостоит обязательное закрытие тем же объёмом, ушёл в прошлое. Но так ли невозможно его восстановить из той информации, что имеется в MetaTrader 5?  Вот, собственно, переводом учёта для начала мы и займёмся.


Эффективность входа

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

Эффективность входа и будет показывать, насколько близко к минимуму вы купили. Другими словами, эффективность входа  - это отношение расстояния между максимумом и входом ко всему пути. Почему мы измеряем расстояние до минимума через разность с максимумом? Нам ведь нужно чтобы в точке, где вход равен минимуму эффективность была равна 1 (а в точке, где вход равен максимуму эффективность была равна 0)

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

 

 

Эффективность входа в позицию показывает, насколько хорошо  МТС  в  ходе  конкретной  сделки  реализует потенциальную прибыль относительно цены входа в позицию и вычисляется по формулам:

для длинных позиций
enter_efficiency=(max_price_trade-enter_price)/(max_price_trade-min_price_trade);

для коротких позиций
enter_efficiency=(enter_price-min_price_trade)/(max_price_trade-min_price_trade);

Эффективность входа может принимать значения от 0 до 1.


Эффективность выхода

Аналогичная ситуация и с оценкой выхода


Эффективность выхода из позиции показывает, насколько хорошо МТС в ходе конкретной сделки реализует потенциальную прибыль относительно цены выхода из позиции, и вычисляется по формулам:


для длинных позиций
exit_efficiency=(exit_price - min_price_trade)/(max_price_trade - min_price_trade);

для коротких позиций
exit_efficiency=(max_price_trade - exit_price)/(max_price_trade - min_price_trade);

Эффективность выхода может принимать значения от 0 до 1.


Эффективность сделки

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

Эффективность сделки показывает, насколько хорошо МТС в ходе конкретной сделки реализует общую потенциальную прибыль и вычисляется по формулам:

для длинных позиций 
trade_efficiency=(exit_price-enter_price)/(max_price_trade-min_price_trade);

для коротких позиций
trade_efficiency=(enter_price-exit_price)/(max_price_trade-min_price_trade);

общая формула
trade_efficiency=enter_efficiency+exit_efficiency-1;

Эффективность сделки может принимать значения от -1 до 1.
Эффективность сделки должна превышать 0,2. 
Анализ эффективностей наглядно показывает направления усовершенствования системы,
так как позволяет раздельно оценить качество сигналов на вход в позицию и сигналов на выход из нее.


Преобразование учёта

Для начала, чтоб не было путаницы, следует оговорить названия объектов учёта. Поскольку и в MetaTrader 5 и у Булашева используются одни и те же названия ордер, сделка, позиция, то имеет смысл разделить эти понятия. В своей статье я буду называть объект учёта Булашева "трейд", т.е. трейд - это сделка или, как иногда звучит у автора книги, ордер, что в том контексте тождественно. Незавершённую сделку Булашев именует позиция, что будем называть незакрытым трейдом.

Отсюда понятно, что все 3 названия вполне спокойно умещаются в одном слове трейд. Ну а учёт MetaTrader 5 переименовывать не будем, и смысл этих трёх понятий будет тот же, что им дали разработчики терминала. Подводя итог, имеем 4 слова,  которыми и будем оперировать Позиция, Сделка, Ордер, Трейд.

Поскольку Ордер есть приказ серверу на открытие/изменение позиции, и, соответственно, не имеет прямого отношения к статистике, а лишь опосредованно через сделку (всё потому, что ордер не всегда может быть исполнен в полном объёме и по заявленной цене), то будет разумно вести статистику не по ордерам, а именно по сделкам.

Приведу распринтовку учёта одной и той же позиции (чтобы вышеописанное было нагляднее):

учёт в МТ-5
сделка[ 0 ]  in      0.1   sell   1.22218   2010.06.14 13:33
сделка[ 1 ]  in/out  0.2   buy    1.22261   2010.06.14 13:36
сделка[ 2 ]  in      0.1   buy    1.22337   2010.06.14 13:39
сделка[ 3 ]  out     0.2   sell   1.22310   2010.06.14 13:41
учёт по Булашеву
трейд[ 0 ]  in 0.1  sell 1.22218 2010.06.14 13:33   out 1.22261 2010.06.14 13:36
трейд[ 1 ]  in 0.1  buy  1.22261 2010.06.14 13:36   out 1.22310 2010.06.14 13:41
трейд[ 2 ]  in 0.1  buy  1.22337 2010.06.14 13:39   out 1.22310 2010.06.14 13:41


Теперь опишу, как производились эти манипуляции. Сделка[ 0 ] открывает позицию, записываем её, как начало нового трейда:

трейд[ 0 ]  in 0.1  sell 1.22218 2010.06.14 13:33

Далее следует переворот позиции, а значит, все предыдущие трейды, должны быть закрыты. Соответственно, данные переворотной сделки[ 1 ] пойдут как в учёт закрытия, так и в учёт открытия нового трейда. После закрытия всех незакрытых трейдов следующих до сделки с направлением in/out на остаток объёма следует открыть новый трейд. Т.е., для закрытия мы используем только данные price и time выбранной сделки, тогда как на открытие трейда используется ещё тип и объём. Тут уместно будет разъяснить, что в новом учёте появилось ранее не используемое понятие направление сделки. Раньше под направлением подразумевалось покупка или продажа, этот же смысл имело и слово тип. Теперь тип и направление - разные понятия.

Тип это покупка или продажа, тогда как направление это вход в позицию или выход. Поэтому позиция всегда открывается сделкой с направлением in, а закрывается сделкой out. Но направление не ограничивается только открытием и закрытием позиции. В это понятие так же входит доливка объёма позиции (сделка in не первая в списке) и частичное закрытие (сделки out не последние в позиции). Поскольку появилась возможность частичного закрытия, то логичным было ввести и переворот позиции, это когда проводится сделка, противоположная текущему направлению позиции  и  превышающая саму позицию по объёму, т.е. сделка in/out.

Итак, мы произвели закрытие ранее открытых (до переворота позиции) трейдов:

трейд[ 0 ]  in 0.1  sell 1.22218 2010.06.14 13:33   out 1.22261 2010.06.14 13:36

Оставшийся свободный объём составил 0.1, вот на него и откроем новый трейд:

трейд[ 1 ]  in 0.1  buy  1.22261 2010.06.14 13:36

Далее следует сделка[ 2 ] с направлением in, открываем ещё один трейд:

трейд[ 2 ]  in 0.1  buy  1.22337 2010.06.14 13:39

И наконец, закрывающая позицию сделка[ 3 ] закрывает все ещё не закрытые трейды в позиции:

трейд[ 1 ]  in 0.1  buy  1.22261 2010.06.14 13:36   out 1.22310 2010.06.14 13:41
трейд[ 2 ]  in 0.1  buy  1.22337 2010.06.14 13:39   out 1.22310 2010.06.14 13:41

Выше изложенный учёт отражает суть учёта, применённого Булашевым, а именно: каждый открытый трейд имеет однозначную точку входа и такую же однозначную точку выхода, у него есть свой объём и тип. Но такая система учёта не учитывает один нюанс - частичное закрытие. Если приглядеться, то видно, что количество трейдов равно количеству in сделок (с учётом in/out). И в данной ситуации учёт по in'ам вполне оправдан, но при частичном закрытии out'ов будет больше (возможна ситуация когда in'ов и out'ов равное количество, но они не соответствуют по объёмам).

И чтобы отработать все out'ы, следует вести учёт по out-сделкам. И это противоречие кажется неразрешимым, только если делать перебор раздельно - сначала все in, потом все out(или наоборот). Если же вести перебор последовательно и на каждую ситуацию иметь своё правило обработки, то противоречий нет.

Вот пример позиции, где out'ов больше чем in'ов (с разбором ситуации):

учёт в МТ-5
сделка[ 0 ]  in      0.3   sell      1.22133   2010.06.15 08:00
сделка[ 1 ]  out     0.2   buy       1.22145   2010.06.15 08:01
сделка[ 2 ]  in/out  0.4   buy       1.22145   2010.06.15 08:02
сделка[ 3 ]  in/out  0.4   sell      1.22122   2010.06.15 08:03
сделка[ 4 ]  out     0.1   buy       1.2206    2010.06.15 08:06
учёт по Булашеву                                       
трейд[ 0 ]  in 0.2  sell    1.22133 2010.06.15 08:00   out 1.22145 2010.06.15 08:01   
трейд[ 1 ]  in 0.1  sell    1.22133 2010.06.15 08:00   out 1.22145 2010.06.15 08:02   
трейд[ 2 ]  in 0.3  buy     1.22145 2010.06.15 08:02   out 1.22122 2010.06.15 08:03   
трейд[ 3 ]  in 0.1  sell    1.22122 2010.06.15 08:03   out 1.2206  2010.06.15 08:06    

Имеем ситуацию, когда после открытия идёт закрывающая сделка, но не на полный объём, а частично (открылись на 0.3, а закрылись на 0.2). Как следует обработать такую ситуацию? Если каждый трейд имеет закрытие того же объёма, то ситуацию можно рассматривать, как открытие одной сделкой сразу нескольких трейдов. Соответственно, точки открытия у них будут общие, а вот точки закрытия разные (ну и понятно, что объём каждого трейда уже определяется закрывающим объёмом). Т.е. выбрали сделку[ 0 ] для обработки, открываем трейд :

трейд[ 0 ]  in 0.3  sell 1.22133 2010.06.15 08:00

Далее выбираем сделку[ 1 ], закрываем открытый трейд, и  в процессе закрытия выясняется, что закрывающего объёма не достаточно. Значит, делаем копию ранее открытого трейда, указывая недостающий объём в качестве параметра "объём" трейда. После чего закрываем исходный трейд на объём сделки (т.е. меняем в исходном трейде объём, ранее указанный при открытии, на закрывающий объём):

трейд[ 0 ]  in 0.2  sell 1.22133 2010.06.15 08:00   out 1.22145 2010.06.15 08:01   
трейд[ 1 ]  in 0.1  sell 1.22133 2010.06.15 08:00

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

Система статистического учёта, описанная в книге Булашева, не имеет эмоций, и позволяет объёктивно оценить принимаемые решения с позиции эффективности входа, выхода и в целом по совокупности этих показателей. А возможность трансляции учёта (один в другой без потери данных) доказывает, что все разговоры о том, что нельзя закодировать МТС, разработанную для MetaTrader 4 в систему учёта MetaTrader 5, беспочвенны. Единственной потерей при переводе учёта может стать принадлежность объёма тому или иному ордеру (MetaTrader 4). Но по факту, если ордеров (в старом понимании этого слова) как объектов учёта больше нет, то это всего лишь субъективная оценка самого трейдера.


Код преобразования учёта

Перейдём к разбору самого кода. Для приготовления транслятора нам понадобятся такие возможности ООП как наследование. Поэтому советую тем, кто ещё не выучил, что это такое, раскрыть Справочник MQL5 и поучить матчасть. Для начала опишем структуру учёта сделки (для ускорения кода, возможно, было бы лучше получать значения напрямую через стандартные функции MQL 5, но так менее читабельно и проще запутаться).

//+------------------------------------------------------------------+
//| структура сделки                                                 |
//+------------------------------------------------------------------+
struct S_Stat_Deals
  {
public:
   ulong             DTicket;         // тикет сделки   
   ENUM_DEAL_TYPE     deals_type;      // тип сделки
   ENUM_DEAL_ENTRY    deals_entry;     // направление сделки 
   double            deals_volume;    // объём сделки    
   double            deals_price;     // цена открытия сделки   
   datetime          deals_date;      // время открытия сделки  

                     S_Stat_Deals(){};
                    ~S_Stat_Deals(){};
  };

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

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

//+------------------------------------------------------------------+
//| структура трейда                                                 |
//+------------------------------------------------------------------+
struct S_Stat_Trades
  {
public:
   ulong             OTicket;         // тикет открывающей сделки 
   ulong             CTicket;         // тикет закрывающей сделки     
   ENUM_DEAL_TYPE     trade_type;     // тип трейда
   double            trade_volume;    // объём трейда
   double            max_price_trade; // максимальная цена трейда
   double            min_price_trade; // минимальная цена трейда
   double            enter_price;     // цена открытия трейда
   datetime          enter_date;      // время открытия трейда 
   double            exit_price;      // цена закрытия трейда
   datetime          exit_date;       // время закрытия трейда

   double            enter_efficiency;// эффективность входа
   double            exit_efficiency; // эффективность выхода
   double            trade_efficiency;// эффективность трейда

                     S_Stat_Trades(){};
                    ~S_Stat_Trades(){};
  };


Теперь, когда у нас созданы две основные структуры, можно описать новый класс C_Pos, который преобразовывает учёт. Для начала объявим указатели на структуры учёта сделок и трейдов. Поскольку данные потребуются и в наследниках, то объявление следует public, а поскольку сделок и трейдов может быть много то, указателем на структуру объявляем не переменные, а массивы. Таким образом, данные будут структурированы и доступны из любого места.

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

//+------------------------------------------------------------------+
//| класс преобразования сделок в трейды                             |
//+------------------------------------------------------------------+
class C_Pos
  {
public:
   S_Stat_Deals      m_deals_stats[];  // структура сделок
   S_Stat_Trades     m_trades_stats[]; // структура трейдов
   long              pos_id;          // id позиции
   string            symbol;          // символ позиции
   int               count_deals;     // количество сделок
   int               count_trades;    // количество трейдов
   int               trades_ends;     // количество закрытых трейдов
   int               DIGITS;          // точность минимального лота по инструменту позиции  
                     C_Pos()
     {
      count_deals=0;
      count_trades=0;
      trades_ends=0;
     };
                    ~C_Pos(){};
   void              OnHistory();         // создание истории позиции
   void              OnHistoryTransform();// преобразование истории позиции в новую систему учёта
   void              efficiency();        // расчёт эффективности по Булашеву

private:
   void              open_pos(int c);
   void              copy_pos(int x);
   void              close_pos(int i,int c);
   double            nd(double v){return(NormalizeDouble(v,DIGITS));};// нормализация до мин лота
   void              DigitMinLots(); // точность минимального лота
   double            iHighest(string          symbol_name,// имя символа
                              ENUM_TIMEFRAMES  timeframe,  // период
                              datetime         start_time, // с какой даты
                              datetime         stop_time   // по какую дату
                              );
   double            iLowest(string          symbol_name,// имя символа
                             ENUM_TIMEFRAMES  timeframe,  // период
                             datetime         start_time, // с какой даты
                             datetime         stop_time   // по какую дату
                             );
  };


Класс имеет три публичных метода, обрабатывающих позиции.

OnHistory() создаёт историю позиции:
//+------------------------------------------------------------------+
//| заполнение структур истории сделок                               |
//+------------------------------------------------------------------+
void C_Pos::OnHistory()
  {
   ArrayResize(m_deals_stats,count_deals);
   for(int i=0;i<count_deals;i++)
     {
      m_deals_stats[i].DTicket=HistoryDealGetTicket(i);
      m_deals_stats[i].deals_type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(m_deals_stats[i].DTicket,DEAL_TYPE);   // тип сделки
      m_deals_stats[i].deals_entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(m_deals_stats[i].DTicket,DEAL_ENTRY);// направление сделки
      m_deals_stats[i].deals_volume=HistoryDealGetDouble(m_deals_stats[i].DTicket,DEAL_VOLUME);              // объём сделки
      m_deals_stats[i].deals_price=HistoryDealGetDouble(m_deals_stats[i].DTicket,DEAL_PRICE);                // цена открытия
      m_deals_stats[i].deals_date=(datetime)HistoryDealGetInteger(m_deals_stats[i].DTicket,DEAL_TIME);        // время открытия
     }
  };

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

OnHistoryTransform() преобразовывает историю позиции в новую систему учёта:

Советую этот приём делать везде, где нужно, при появлении нового объёкта учёта распределять память. Если нет точных данных о потребностях (сколько памяти нужно), то следует распределить на приближённое значение. В любом случае это будет экономней, чем на каждом шаге заново перераспределять весь массив.

Далее следует цикл, где все сделки позиции проходят фильтрацию через три фильтра: если сделка in, in/out, out. По каждому из вариантов следуют отдельные действия. Фильтры выстроены последовательно, вложено. Т.е., если один фильтр отдал false, то лишь тогда делаем проверку следующего фильтра. Такая конструкция экономна по ресурсам, т.к. отсекаются ненужные действия. Для увеличения читабельности кода многие действия вынесены в функции, объявленные в классе как private. Кстати говоря, при разработке эти функции были public , но в процессе разработки выяснилось, что в других местах кода надобности в них нет, и они были переобъявлены как private. Вот так просто в ООП можно манипулировать областью видимости данных.

Итак, в фильтре in происходит создание нового трейда (функция open_pos()), поэтому увеличиваем размер массива указателей на один и производим копирование структуры сделки в соответствующие поля структуры трейда. При этом, т.к. у структуры трейда полей цена и время в два раза больше, то при открытии заполняются поля открытия и трейд числится незавершённым, что можно понять из разности count_trades и trades_ends. Всё дело в том, что эти счётчики вначале имеют значение ноль. При появлении трейда, тут же увеличивается счётчик count_trades, а при его закрытии так же увеличивается trades_ends. Таким образом, по разности count_trades и trades_ends в любой момент можно определить, сколько трейдов не закрыто.

Функция open_pos() довольно простая, она лишь занимается открытием трейда и двигает соответствующий счётчик, другие функции из этой серии не так просты. И так если сделка не является типом in, то из торговых сделок остаётся два варианта: in/out и out. Из двух вариантов проверяем вначале тот, что проще исполняется (это не принципиально, но я выстроил проверку по мере возрастания сложности функций исполнения).

Функция, которая отрабатывает фильтр in/out, суммирует открытую позицию по всем незакрытым трейдам (выше я уже писал о том, как выяснить, какие трейды не закрыты по разности count_trades и trades_ends). Таким образом, выясняется суммарный объём, который данной сделкой закрывается (и соответственно, оставшийся объём будет открыт заново, но уже по типу текущей сделки). Тут нужно отметить, что раз сделка имеет направление in/out, то это означает, что её объём обязательно превышает совокупный объём открытой до сих пор позиции. Поэтому логично искать разницу между позицией и сделкой in/out, чтоб знать на какой объём следует открывать новый трейд.

Если же сделка имеет направление out, то всё ещё сложнее. Во-первых, последняя сделка в позиции всегда имеет направление out, поэтому сразу делаем исключение: если сделка последняя, то закрываем всё что есть. Иначе (если сделка не последняя),  возможны два варианта. Поскольку сделка не in/out, а просто out, то варианты могут быть: первый вариант - объём точно попадает в предыдущий открывающий, т.е. открывающая сделка равна закрывающей по объёму, и второй вариант - они не равны.

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

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

//+------------------------------------------------------------------+
//| преобразование сделок в трейды (engine classes)                  |
//+------------------------------------------------------------------+
void C_Pos::OnHistoryTransform()
  {
   DigitMinLots();// заполняем значение DIGITS
   count_trades=0;trades_ends=0;
   ArrayResize(m_trades_stats,count_trades,count_deals);
   for(int c=0;c<count_deals;c++)
     {
      if(m_deals_stats[c].deals_entry==DEAL_ENTRY_IN)
        {
         open_pos(c);
        }
      else// else in
        {
         double POS=0;
         for(int i=trades_ends;i<count_trades;i++)POS+=m_trades_stats[i].trade_volume;

         if(m_deals_stats[c].deals_entry==DEAL_ENTRY_INOUT)
           {
            for(int i=trades_ends;i<count_trades;i++)close_pos(i,c);
            trades_ends=count_trades;
            open_pos(c);
            m_trades_stats[count_trades-1].trade_volume=m_deals_stats[c].deals_volume-POS;
           }
         else// else in/out
           {
            if(m_deals_stats[c].deals_entry==DEAL_ENTRY_OUT)
              {
               if(c==count_deals-1)// если последняя сделка
                 {
                  for(int i=trades_ends;i<count_trades;i++)close_pos(i,c);
                  trades_ends=count_trades-1;
                 }
               else// если не последняя сделка
                 {
                  double out_vol=nd(m_deals_stats[c].deals_volume);
                  while(nd(out_vol)>0)
                    {
                     if(nd(out_vol)>=nd(m_trades_stats[trades_ends].trade_volume))
                       {
                        close_pos(trades_ends,c);
                        out_vol-=nd(m_trades_stats[trades_ends].trade_volume);
                        trades_ends++;
                       }
                     else// если остаток закрывающейся позиции меньше следующего трейда
                       {
                        // сдвигаем все трейды на один вперёд
                        count_trades++;
                        ArrayResize(m_trades_stats,count_trades);
                        for(int x=count_trades-1;x>trades_ends;x--)copy_pos(x);
                        // открываем копию на разницу текущей позиции и остаток
                        m_trades_stats[trades_ends+1].trade_volume=nd(m_trades_stats[trades_ends].trade_volume-out_vol);
                        // закрываем текущий трейд с новым объёмом равным остатку
                        close_pos(trades_ends,c);
                        m_trades_stats[trades_ends].trade_volume=nd(out_vol);
                        out_vol=0;
                        trades_ends++;
                       }
                    }// while(out_vol>0)
                 }// если не последняя сделка
              }// if out
           }// else in/out
        }// else in
     }
  };


Расчёт эффективности

После того, как была произведена трансляция системы учёта, мы тут же можем оценить эффективность трейдов по Булашеву. Функции необходимые для такой оценки находятся в методе efficiency(), там же происходит и заполнение структуры трейда рассчитанными данными. Эффективность входа и выхода оценивается от 0 до 1, в целом трейда от   -1 до 1.

//+------------------------------------------------------------------+
//| расчёт эффективности                                             |
//+------------------------------------------------------------------+
void C_Pos::efficiency()
  {
   for(int i=0;i<count_trades;i++)
     {
      m_trades_stats[i].max_price_trade=iHighest(symbol,PERIOD_M1,m_trades_stats[i].enter_date,m_trades_stats[i].exit_date); // максимальная цена трейда
      m_trades_stats[i].min_price_trade=iLowest(symbol,PERIOD_M1,m_trades_stats[i].enter_date,m_trades_stats[i].exit_date);  // минимальная цена трейда
      double minimax=0;
      minimax=m_trades_stats[i].max_price_trade-m_trades_stats[i].min_price_trade;// разность максимума и минимума
      if(minimax!=0)minimax=1.0/minimax;
      if(m_trades_stats[i].trade_type==DEAL_TYPE_BUY)
        {
         //Эффективность входа в позицию
         m_trades_stats[i].enter_efficiency=(m_trades_stats[i].max_price_trade-m_trades_stats[i].enter_price)*minimax;
         //Эффективность выхода из позиции
         m_trades_stats[i].exit_efficiency=(m_trades_stats[i].exit_price-m_trades_stats[i].min_price_trade)*minimax;
         //Эффективность сделки 
         m_trades_stats[i].trade_efficiency=(m_trades_stats[i].exit_price-m_trades_stats[i].enter_price)*minimax;
        }
      else
        {
         if(m_trades_stats[i].trade_type==DEAL_TYPE_SELL)
           {
            //Эффективность входа в позицию
            m_trades_stats[i].enter_efficiency=(m_trades_stats[i].enter_price-m_trades_stats[i].min_price_trade)*minimax;
            //Эффективность выхода из позиции
            m_trades_stats[i].exit_efficiency=(m_trades_stats[i].max_price_trade-m_trades_stats[i].exit_price)*minimax;
            //Эффективность сделки 
            m_trades_stats[i].trade_efficiency=(m_trades_stats[i].enter_price-m_trades_stats[i].exit_price)*minimax;
           }
        }
     }
  }


Метод использует два приватных метода iHighest() и iLowest(), они однотипны и отличаются лишь запрашиваемыми данными и функцией поиска  fmin или fmax.

//+------------------------------------------------------------------+
//| поиск максимума в периоде start_time --> stop_time               |
//+------------------------------------------------------------------+
double C_Pos::iHighest(string           symbol_name,// имя символа
                       ENUM_TIMEFRAMES  timeframe,  // период
                       datetime         start_time, // с какой даты
                       datetime         stop_time   // по какую дату
                       )
  {
   double  buf[];
   datetime  start_t=(start_time/60)*60;// нормализация времени открытия 
   datetime  stop_t=(stop_time/60+1)*60;// нормализация времени закрытия  
   int period=CopyHigh(symbol_name,timeframe,start_t,stop_t,buf);
   double res=buf[0];
   for(int i=1;i<period;i++)
      res=fmax(res,buf[i]);
   return(res);
  }

Метод ведёт поиск максимального значения на участке истории между двумя определёнными датами. Даты передаются в функцию в виде параметров start_time и stop_time. Поскольку в функцию передаются даты трейдов, а торговый приказ может прийти в середине даже минутного бара, то уже внутри функции происходит нормализация даты до ближайшего значения бара. Так же происходит и в функции iLowest(). Разработав метод efficiency(), мы имеем весь функционал для работы с позицией, но вот учёта самой позиции пока нет. Наверстаем это упущение, описав новый класс, которому будут доступны все предыдущие методы, т.е. объявим класс как наследник из C_Pos.


Класс наследник (engine classes)

class C_PosStat:public C_Pos

Чтобы учитывать данные статистики создадим структуру, которая будет придана новому классу.

//+------------------------------------------------------------------+
//| структура эффективности                                          |
//+------------------------------------------------------------------+
struct S_efficiency

  {
   double            enter_efficiency; // эффективность входа
   double            exit_efficiency;  // эффективность выхода
   double            trade_efficiency; // эффективность трейда
                     S_efficiency()
     {
      enter_efficiency=0;
      exit_efficiency=0;
      trade_efficiency=0;
     };
                    ~S_efficiency(){};
  };


Ну и, собственно, тело класса:

//+------------------------------------------------------------------+
//| класс статистики торговли в целом                                |
//+------------------------------------------------------------------+
class C_PosStat:public C_Pos
  {
public:
   int               PosTotal;         // количество позиций в истории
   C_Pos             pos[];            // массив указателей на позиции
   int               All_count_trades; // общее количество трейдов в истории
   S_efficiency      trade[];          // массив указателей на структуру эффективности входов,выходов,трейдов
   S_efficiency      avg;              // указатель на структуру среднего значения эффективности входов,выходов,трейдов
   S_efficiency      stdev;            // указатель на структуру стандартного отклонения от 
                                       // среднего значения эффективности входов,выходов,трейдов

                     C_PosStat(){PosTotal=0;};
                    ~C_PosStat(){};
   void              OnPosStat();                         // engine classes
   void              OnTradesStat();                      // сбор информации о трейдах в общий массив
   
   // функции вывода информации в файл
   void              WriteFileDeals(string folder="deals");
   void              WriteFileTrades(string folder="trades");
   void              WriteFileTrades_all(string folder="trades_all");
   void              WriteFileDealsHTML(string folder="deals");
   void              WriteFileDealsHTML2(string folder="deals");
   void              WriteFileTradesHTML(string folder="trades");
   void              WriteFileTradesHTML2(string folder="trades");
   string            enum_translit(ENUM_DEAL_ENTRY x,bool latin=true);// преобразование перечисления в стринг
   string            enum_translit(ENUM_DEAL_TYPE x,bool latin=true); 
                                                              // преобразование перечисления в стринг(перегруженная)
private:   

   S_efficiency      AVG(int count);                                        // среднеарифметическое
   S_efficiency      STDEV(const S_efficiency &mo,int count);               // стандартное отклонение
   S_efficiency      add(const S_efficiency &a,const S_efficiency &b);      //добавить
   S_efficiency      take(const S_efficiency &a,const S_efficiency &b);     //отнять
   S_efficiency      multiply(const S_efficiency &a,const S_efficiency &b); //умножить
   S_efficiency      divided(const S_efficiency &a,double b);               //разделить
   S_efficiency      square_root(const S_efficiency &a);                    //квадратный корень
   string            Head_style(string title);
  };  


Разбор этого класса предлагаю начать, наоборот, от конца к началу. Завершается всё выводом таблицы сделок и трейдов в файлы. Для чего написан ряд функций (назначение каждой просматривается в названиях). Функции выводят csv-отчёт о сделках и трейдах, а также html-отчёты двух видов (отличаются только визуально), но одинаковы по содержанию.

      void              WriteFileDeals();      // вывод csv-отчёта о сделках
      void              WriteFileTrades();     // вывод csv-отчёта о трейдах
      void              WriteFileTrades_all(); // вывод сводного csv-отчёта фитнес-функций
      void              WriteFileDealsHTML2(); // вывод html-отчёта о сделках 1 вариант
      void              WriteFileTradesHTML2();// вывод html-отчёта о трейдах 2 вариант

Функции enum_translit() написаны для перевода значений перечислений в string для вывода в файл. В private имеется ряд функций структуры S_efficiency. Все функции восполняют недостатки языка, а именно, арифметические операции со структурами. Вообще, однозначного мнения по поводу реализации этих методов нет, их можно реализовать по-разному. Я реализовал как методы арифметических операций над полями структур. Кто-то скажет, что лучше для обработки каждого поля структуры писать отдельный метод. Обобщая, скажу, сколько программистов - столько мнений. Надеюсь, что в перспективе будет возможность встроенными методами производить подобные операции.

Метод AVG() ведёт расчёт среднеарифметического значения от переданного массива, но среднее арифметическое не полно отражает характер распределения, поэтому он дополнен ещё и методом, ведущим расчёт стандартного отклонения STDEV(). Функция OnTradesStat() принимает значения эффективности (ранее рассчитанные в OnPosStat()) и обрабатывает их статистическими методами. И наконец, главная функция класса OnPosStat().

Эту функции требуется разобрать подробно. Состоит она из двух частей и в принципе при желании может быть не сложно разделена. Первая часть производит поиск всех позиций и учёт их id с сохранением в массив временного хранения id_pos. Пошагово: выбираем всю доступную историю, определяем количество сделок, цикл перебора сделок. В цикле: если тип сделки balans, пропускаем (учитывать стартовую сделку незачем) иначе - сохраняем id позиции в переменную и ведём поиск. Если такой id уже есть в базе (массиве id_pos), то уходим за следующей сделкой, иначе - записываем id в базу. Таким образом, перебрав все сделки, мы имеем массив, заполненный всеми существующими id позиций и количество позиций.

   long  id_pos[];// вспомогательный массив для создания истории позиций
   if(HistorySelect(0,TimeCurrent()))
     {
      int HTD=HistoryDealsTotal();
      ArrayResize(id_pos,PosTotal,HTD);
      for(int i=0;i<HTD;i++)
        {
         ulong DTicket=(ulong)HistoryDealGetTicket(i);
         if((ENUM_DEAL_TYPE)HistoryDealGetInteger(DTicket,DEAL_TYPE)==DEAL_TYPE_BALANCE)
            continue;// если сделка начисления баланса пропускаем
         long id=HistoryDealGetInteger(DTicket,DEAL_POSITION_ID);
         bool present=false; // начальное состояние,позиция не имеется в наличии           
         for(int j=0;j<PosTotal;j++)
           { if(id==id_pos[j]){ present=true; break; } }// если позиция уже была break

         if(!present)// при появлении новой позиции записываем id
           {
            PosTotal++;
            ArrayResize(id_pos,PosTotal);
            id_pos[PosTotal-1]=id;
           }
        }
     }
   ArrayResize(pos,PosTotal);

Во второй части функции мы запускаем в реализацию все методы, описанные ранее в базовом классе C_Pos. Она состоит из цикла, в котором перебираются позиции и запускаются соответствующие методы обработки позиции. Описание вызова в ниже приведённом коде.

   for(int p=0;p<PosTotal;p++)
     {
      if(HistorySelectByPosition(id_pos[p]))// выбрать позицию
        {
         pos[p].pos_id=id_pos[p]; // присвоить id позиции в соответствующее поле класса C_Pos
         pos[p].count_deals=HistoryDealsTotal();// присвоить количество сделок в позиции в поле класса C_Pos
         pos[p].symbol=HistoryDealGetString(HistoryDealGetTicket(0),DEAL_SYMBOL);// тоже с символом
         pos[p].OnHistory();          // запуск заполнения структуры sd историей позиции
         pos[p].OnHistoryTransform(); // преобразование учёта, заполнение структуры st.
         pos[p].efficiency();         // расчёт эффективности полученных данных
         All_count_trades+=pos[p].count_trades;// сохраняем количество трейдов для выведения общего числа
        }
     }


Вызов методов класса

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

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
#include <Bulaschev_Statistic.mqh> void OnStart()
  {
   C_PosStat  start;
   start.OnPosStat();
   start.OnTradesStat();
   start.WriteFileDeals();
   start.WriteFileTrades();
   start.WriteFileTrades_all();
   start.WriteFileDealsHTML2();
   start.WriteFileTradesHTML2();
   Print("cko tr ef=" ,start.stdev.trade_efficiency);
   Print("mo  tr ef=" ,start.avg.trade_efficiency);
   Print("cko out ef=",start.stdev.exit_efficiency);
   Print("mo  out ef=",start.avg.exit_efficiency);
   Print("cko in ef=" ,start.stdev.enter_efficiency);
   Print("mo  in ef=" ,start.avg.enter_efficiency);
  }

Скрипт создаёт 5 файлов отчёта, по количеству функций выводящих данные в файл в директории Files\OnHistory. Из основных функций расчёта тут присутствует OnPosStat() и OnTradesStat(), через них и происходит вызов всех требуемых методов. В конце скрипта следует распринтовка полученных значений эффективности торговли в целом. По каждому их этих значений можно вести генетическую оптимизацию.

Поскольку при оптимизации нет смысла выводить каждый отчёт в файл, то вызов класса в советнике будет иметь немного другой вид. Во-первых: советник в отличие от скрипта может быть запущен в тестере (собственно, к этому мы его и готовим). Работа в тестере имеет свою специфику. При оптимизации есть доступ к функции OnTester(), при этом её исполнение идёт перед исполнением функции OnDeinit(). Поэтому вызов основных методов преобразования можно разъединить. Для удобства изменения фитнес-функции из параметров советника я описал перечисление, которое не является частью класса, а объявлено глобально. При этом находится перечисление на одном листе с методами класса C_PosStat.

//+------------------------------------------------------------------+
//| перечисление фитнес-функций                                      |
//+------------------------------------------------------------------+
enum Enum_Efficiency
  {
   avg_enter_eff,
   stdev_enter_eff,
   avg_exit_eff,
   stdev_exit_eff,
   avg_trade_eff,
   stdev_trade_eff
  };

 Вот это следует добавить в шапку советника.

#include <Bulaschev_Statistic.mqh>
input Enum_Efficiency result=0;// Фитнес функция


Теперь можно просто описать передачу требуемого параметра через оператор switch.

//+------------------------------------------------------------------+
//| Expert optimization function                                     |
//+------------------------------------------------------------------+
double OnTester()
  {
   start.OnPosStat();
   start.OnTradesStat();
   double res;
   switch(result)
     {
      case 0: res=start.avg.enter_efficiency;    break;
      case 1: res=-start.stdev.enter_efficiency; break;
      case 2: res=start.avg.exit_efficiency;     break;
      case 3: res=-start.stdev.exit_efficiency;  break;
      case 4: res=start.avg.trade_efficiency;    break;
      case 5: res=-start.stdev.trade_efficiency; break;
      default : res=0; break;
     }  
   return(res);
  }

Прошу обратить внимание читателя на то, что через OnTester() ведётся максимизация пользовательской функции. Если вам требуется найти минимум пользовательской функции, то имеет смысл перевернуть саму функцию, просто умножив её на -1. Как в примере со стандартным отклонением, все понимают, что чем меньше stdev, тем меньше разница между эффективностью трейдов, а, значит, выше стабильность торговли в целом. Поэтому stdev имеет смысл минимизировать. Теперь, когда мы разобрались с вызовом методов класса, стоит рассмотреть вывод отчётов в файл.

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

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

Поэтому, лучший вариант - поставить фильтр (запускать отчёт только в режиме Оптимизация [отключена]). Таким образом, диск не будет забиваться отчётами, которые никто никогда не смотрит. Кроме того - возрастает скорость оптимизации (ведь не секрет, что самые медленные операции это файловые),  и при этом сохраняется возможность быстро получить отчёт на интересующих параметрах. Собственно, где именно будет стоять фильтр в OnTester или OnDeinit - не важно. Главное, чтобы методы класса, создающие отчёт, вызывались после основных методов, делающих преобразование. Чтобы не загромождать код, я поместил фильтр в  OnDeinit():

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(!(bool)MQL5InfoInteger(MQL5_OPTIMIZATION))
     {
      start.WriteFileDeals();      // вывод csv-отчёта о сделках
      start.WriteFileTrades();     // вывод csv-отчёта о трейдах
      start.WriteFileTrades_all(); // вывод сводного csv-отчёта фитнес-функций
      start.WriteFileDealsHTML2(); // вывод html-отчёта о сделках
      start.WriteFileTradesHTML2();// вывод html-отчёта о трейдах
     }
  }
//+------------------------------------------------------------------+

Последовательность вызова методов не важна. Всё необходимое для отчёта готовится в методах OnPosStat и OnTradesStat. Так же, не имеет значения, будут ли вызываться все перечисленные методы вывода отчёта или некоторые будут исключены, работа каждого метода строго индивидуальна и является, по сути, интерпретацией информации, которая уже хранится в объектах класса.

Проверка в тестере

Результат одиночного прогона в тестере выглядит так:

Trades Report Moving Averages Statistic
# Ticket type volume Open Close Price Efficiency
open close price time price time max min enter exit trade
pos[0] id 2 EURUSD
0 2 3 buy 0.1 1.37203 2010.03.15 13:00:00 1.37169 2010.03.15 14:00:00 1.37236 1.37063 0.19075 0.61272 -0.19653
pos[1] id 4 EURUSD
1 4 5 sell 0.1 1.35188 2010.03.23 08:00:00 1.35243 2010.03.23 10:00:00 1.35292 1.35025 0.61049 0.18352 -0.20599
pos[2] id 6 EURUSD
2 6 7 sell 0.1 1.35050 2010.03.23 12:00:00 1.35343 2010.03.23 16:00:00 1.35600 1.34755 0.34911 0.30414 -0.34675
pos[3] id 8 EURUSD
3 8 9 sell 0.1 1.35167 2010.03.23 18:00:00 1.33343 2010.03.26 05:00:00 1.35240 1.32671 0.97158 0.73842 0.71000
pos[4] id 10 EURUSD
4 10 11 sell 0.1 1.34436 2010.03.30 16:00:00 1.33616 2010.04.08 23:00:00 1.35904 1.32821 0.52384 0.74213 0.26597
pos[5] id 12 EURUSD
5 12 13 buy 0.1 1.35881 2010.04.13 08:00:00 1.35936 2010.04.15 10:00:00 1.36780 1.35463 0.68261 0.35915 0.04176
pos[6] id 14 EURUSD
6 14 15 sell 0.1 1.34735 2010.04.20 04:00:00 1.34807 2010.04.20 10:00:00 1.34890 1.34492 0.61055 0.20854 -0.18090
pos[7] id 16 EURUSD
7 16 17 sell 0.1 1.34432 2010.04.20 18:00:00 1.33619 2010.04.23 17:00:00 1.34491 1.32016 0.97616 0.35232 0.32848
pos[8] id 18 EURUSD
8 18 19 sell 0.1 1.33472 2010.04.27 10:00:00 1.32174 2010.04.29 05:00:00 1.33677 1.31141 0.91916 0.59267 0.51183
pos[9] id 20 EURUSD
9 20 21 sell 0.1 1.32237 2010.05.03 04:00:00 1.27336 2010.05.07 20:00:00 1.32525 1.25270 0.96030 0.71523 0.67553

Effectiveness Report
Fitness Func Average Value Standard Deviation
Enter 0.68 0.26
Exit 0.48 0.21
Trades 0.16 0.37


Ну и график баланса:

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

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

pos[286] id 1019514 EURUSD
944 1092288 1092289 buy 0.1 1.26733 2010.07.08 21:14:49 1.26719 2010.07.08 21:14:57 1.26752 1.26703 0.38776 0.32653 -0.28571
pos[287] id 1019544 EURUSD
945 1092317 1092322 sell 0.2 1.26761 2010.07.08 21:21:14 1.26767 2010.07.08 21:22:29 1.26781 1.26749 0.37500 0.43750 -0.18750
946 1092317 1092330 sell 0.2 1.26761 2010.07.08 21:21:14 1.26792 2010.07.08 21:24:05 1.26782 1.26749 0.36364 -0.30303 -0.93939
947 1092319 1092330 sell 0.3 1.26761 2010.07.08 21:21:37 1.26792 2010.07.08 21:24:05 1.26782 1.26749 0.36364 -0.30303 -0.93939
pos[288] id 1019623 EURUSD
948 1092394 1092406 buy 0.1 1.26832 2010.07.08 21:36:43 1.26843 2010.07.08 21:37:38 1.26882 1.26813 0.72464 0.43478 0.15942
pos[289] id 1019641 EURUSD
949 1092413 1092417 buy 0.1 1.26847 2010.07.08 21:38:19 1.26852 2010.07.08 21:38:51 1.26910 1.26829 0.77778 0.28395 0.06173
950 1092417 1092433 sell 0.1 1.26852 2010.07.08 21:38:51 1.26922 2010.07.08 21:39:58 1.26916 1.26829 0.26437 -0.06897 -0.80460
pos[290] id 1150923 EURUSD
951 1226007 1226046 buy 0.2 1.31653 2010.08.05 16:06:20 1.31682 2010.08.05 16:10:53 1.31706 1.31611 0.55789 0.74737 0.30526
952 1226024 1226046 buy 0.3 1.31632 2010.08.05 16:08:31 1.31682 2010.08.05 16:10:53 1.31706 1.31611 0.77895 0.74737 0.52632
953 1226046 1226066 sell 0.1 1.31682 2010.08.05 16:10:53 1.31756 2010.08.05 16:12:49 1.31750 1.31647 0.33981 -0.05825 -0.71845
954 1226046 1226078 sell 0.2 1.31682 2010.08.05 16:10:53 1.31744 2010.08.05 16:15:16 1.31750 1.31647 0.33981 0.05825 -0.60194
pos[291] id 1155527 EURUSD
955 1230640 1232744 sell 0.1 1.31671 2010.08.06 13:52:11 1.32923 2010.08.06 17:39:50 1.33327 1.31648 0.01370 0.24062 -0.74568
956 1231369 1232744 sell 0.1 1.32584 2010.08.06 14:54:53 1.32923 2010.08.06 17:39:50 1.33327 1.32518 0.08158 0.49938 -0.41904
957 1231455 1232744 sell 0.1 1.32732 2010.08.06 14:58:13 1.32923 2010.08.06 17:39:50 1.33327 1.32539 0.24492 0.51269 -0.24239
958 1231476 1232744 sell 0.1 1.32685 2010.08.06 14:59:47 1.32923 2010.08.06 17:39:50 1.33327 1.32539 0.18528 0.51269 -0.30203
959 1231484 1232744 sell 0.2 1.32686 2010.08.06 15:00:20 1.32923 2010.08.06 17:39:50 1.33327 1.32539 0.18655 0.51269 -0.30076
960 1231926 1232744 sell 0.4 1.33009 2010.08.06 15:57:32 1.32923 2010.08.06 17:39:50 1.33327 1.32806 0.38964 0.77543 0.16507
961 1232591 1232748 sell 0.4 1.33123 2010.08.06 17:11:29 1.32850 2010.08.06 17:40:40 1.33129 1.32806 0.98142 0.86378 0.84520
962 1232591 1232754 sell 0.4 1.33123 2010.08.06 17:11:29 1.32829 2010.08.06 17:42:14 1.33129 1.32796 0.98198 0.90090 0.88288
963 1232591 1232757 sell 0.2 1.33123 2010.08.06 17:11:29 1.32839 2010.08.06 17:43:15 1.33129 1.32796 0.98198 0.87087 0.85285
pos[292] id 1167490 EURUSD
964 1242941 1243332 sell 0.1 1.31001 2010.08.10 15:54:51 1.30867 2010.08.10 17:17:51 1.31037 1.30742 0.87797 0.57627 0.45424
965 1242944 1243333 sell 0.1 1.30988 2010.08.10 15:55:03 1.30867 2010.08.10 17:17:55 1.31037 1.30742 0.83390 0.57627 0.41017
pos[293] id 1291817 EURUSD
966 1367532 1367788 sell 0.4 1.28904 2010.09.06 00:24:01 1.28768 2010.09.06 02:53:21 1.28965 1.28710 0.76078 0.77255 0.53333


Вот так выглядят преобразованные данные, но для того, чтобы читатель мог во всём не торопясь разобраться (а всё познаётся в сравнении), в отдельный файл сохраняется ещё и оригинальная история сделок - та самая, которой так пока не достаёт многим трейдерам, привыкшим на MetaTrader 4 её видеть в разделе [Результаты].

Заключение

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