Оценка торговых систем - эффективности входа, выхода и сделок
Введение
Существует масса критериев, которые определяют эффективность торговых систем, а уж трейдеры сами выбирают те из них, что им нравятся. В этой статье предлагается рассмотреть подходы, описанные в книге Булашева С.В. "Статистика для трейдера". К сожалению, эта книга была выпущена малым тиражом и уже давно не переиздавалась, хотя в электронном виде доступна на многих сайтах.
Пролог
Напомню, что год издания книги - 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() преобразовывает историю позиции в новую систему учёта:
- Ранее я описывал, как должны преобразовываться данные теперь стоит разобрать это на примере. Для преобразования нам потребуется значение, до какой точности рассчитывать объём сделки (мин. лот), DigitMinLots() как раз этим занимается, но в случае если программист уверен, что его код не будет запускаться в других условиях, то можно задать этот параметр в конструкторе, а функцию опустить.
- Далее выставляем счётчики count_trades, trades_ends в ноль. После чего перераспределяем память под структуры учёта трейдов. Поскольку нам точно неизвестно, сколько трейдов будет, то реальную память распределяем по количеству сделок в позиции. Если при этом впоследствии окажется, что трейдов больше, то мы ещё несколько раз перераспределим память, но при этом под большинство трейдов выделенной памяти будет хватать, а выделение памяти сразу на весь массив нам сильно экономит машинное время.
Советую этот приём делать везде, где нужно, при появлении нового объёкта учёта распределять память. Если нет точных данных о потребностях (сколько памяти нужно), то следует распределить на приближённое значение. В любом случае это будет экономней, чем на каждом шаге заново перераспределять весь массив.
Далее следует цикл, где все сделки позиции проходят фильтрацию через три фильтра: если сделка 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 её видеть в разделе [Результаты].
Заключение
В заключение хотелось бы высказать пожелания разработчикам
- добавить возможность не только оптимизировать советник по пользовательскому
параметру, но и совмещать его со стандартными, как это сделано с другими
функциями оптимизации. В целом же, подводя итог статье, можно сказать, что в
ней заложены лишь основы, так сказать стартовый потенциал, и надеюсь, что
пытливый читатель сам сможет доработать класс по своему вкусу. Удачи.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Болтология пошла.
Если вас интересует максимальная прибыль в пипсах - это элементарно.
Если с учетом вложенных средств - аналогично.
P.S. На основании потиковых данных (аггрегация нескольких ECN/STP-фидов) за крайнюю пятницу - 27.07.2012:
https://www.mql5.com/ru/code/9234#21822
Имеем две взаимно противоположные задачи:
чем меньше горизонт тем выше потенциальная прибыль.(этот вывод из материалов по ссылке)
чем выше горизонт тем проще получить прибыль.(этот вывод почерпнул из многочисленных постов на форуме)
ЗЫ осталось решить исследовательскую задачу о золотой середине.
ЗЫ осталось решить исследовательскую задачу о золотой середине.
Интересно, откуда такая любовь к ТФ?
Что за тяга к квантованию исходного ряда {Price, Time} именно по времени?
Интересно, откуда такая любовь к ТФ?
Что за тяга к квантованию исходного ряда {Price, Time} именно по времени?