Эквити в тестах MT4 и оптимизация по Шарпу


С самого начала моей работы с тестером MT4 меня жутко бесило отсутствие в результатах тестов графиков доходности по эквити во времени. Те графики доходности, которые выдает тестер практически никогда не отражает реальную действительность, так как много моментов ускользает из анализа.

Графики финансового результата во времени наоборот идеальны для анализа как отдельной стратегии, так и для анализа портфеля различных стратегий.

В начале 2015 года, когда я активно занимался разработкой торговых роботов Bollindger, я реализовал в коде советника очень простой, но достаточно эффективный способ извлечения необходимой информации по доходности каждого прогона в тестере стратегий MT4. Даже хотел опубликовать методику в блоге, но на тот момент трейдерской тусовки в блоге не было, поэтому я не стал заморачиваться.

Сейчас же среди читателей блога имеется достаточно трейдеров и поэтому я думаю, что информация для них будет интересной.

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

Суть кода заключается в том, чтобы во время теста закинуть ключевую информацию о состоянии счёта в отдельный массив и затем информацию из этого массива экспортировать в csv файл.

Собирается следующая информация:
  • Дата
  • Баланс счёта на конец дня
  • Эквити счёта на конец дня
  • Минимальный эквити счёта в течении дня
  • Используемая маржа на конец дня
  • Минимальная маржа используемая в течении дня
Массив такой информации отлично подойдет для анализа результатов тестирования.


Вставки в код для получения графика динамики дневного эквити тестируемого советника

1. Прежде всего вставляем код в начала обработчика событий OnTick() или OnTimer(). У меня это выглядит следующим образом:
double ArrayForExportEquity[7][10000]; //массив в который будем писать инфу о дневных показателях счёта
int i_for_exp=0; //счетчик дней за время тестирования
 
void OnTick() 
  {  
 
   if(timeprev == Time[0]) return; //мои советники работают только на открытии новой свечи, поэтому все остальное игнорирую
   timeprev=Time[0];
 
   if (ExportToCSV_equity || on_tester) //это внешние переменные, в которых я задаю нужно ли нам воощбе делать экспорт эквити или считать коэффициент шарпа для результата
     {
      if (TimeDay(Time[0])!=TimeDay(Time[1]) && IsTesting()) //день закончился? тогда записываем информацию о состоянии счёта на конец дня в массив
        {         
         ArrayForExportEquity[0][i_for_exp]= StrToTime(TimeYear(Time[0])+"."+TimeMonth(Time[0])+"."+TimeDay(Time[0]));
         ArrayForExportEquity[1][i_for_exp] = AccountBalance();
         ArrayForExportEquity[2][i_for_exp] = AccountEquity();
         ArrayForExportEquity[3][i_for_exp+1] = AccountEquity();     //информацию о минимальном эквити пишим в следующий день
         ArrayForExportEquity[5][i_for_exp] = AccountMargin()*(AccountLeverage()/100); //информация об используемой марже пишется исходя из расчёта что используется плечо 1:100, если плечо используется другое, то величина корректируется
         ArrayForExportEquity[6][i_for_exp+1] = AccountMargin()*(AccountLeverage()/100); //информацию о максимальной марже пишем в следующий день          
 
         i_for_exp+=1;
        }
      if (AccountEquity()<ArrayForExportEquity[3][i_for_exp])ArrayForExportEquity[3][i_for_exp]=AccountEquity(); //информацию о минимальной марже за день проверяем на каждом открытии свечи
      if (AccountMargin()*(AccountLeverage()/100)>ArrayForExportEquity[6][i_for_exp])ArrayForExportEquity[6][i_for_exp]=AccountMargin()*(AccountLeverage()/100); //аналогично с инфой о максимальной марже за день
     } 
   ...

Дальше уже идет код вашего советника.

Затем нам необходимо в обработчик deinit(), который запускается перед отключением советника, добавить запуск функции непосредственного экспорта собранной информации.
int deinit()
  {
   if (IsTesting() && ExportToCSV_equity)ExportToCSV_equity();
   ...

После чего запускается непосредственно функция экспорта.
int  ExportToCSV_equity() 
  { 
   string InpFileName=MQLInfoString(MQL_PROGRAM_NAME)+" equity "+Symbol()+"-"+StdDevPeriod+"-"+StdDevKoef+"-"+tp_koef1+"-"+sl_koef+".csv";  // Создадим имя файла, я в имя файла добавляю используемые настройки  для того чтобы сохранялись разные прогоны                                            
   FileDelete(StringConcatenate("Report ",AccountNumber())+"//"+InpFileName); //если файл с таким именем есть, то мы его удаляем
   int  file_handle=FileOpen(StringConcatenate("Report ",AccountNumber())+"//"+InpFileName,FILE_READ|FILE_WRITE|FILE_CSV); // Создадим дирректорию и сам файл
   if(file_handle==INVALID_HANDLE)printf("ошибка открытия файла"); 
   if(file_handle!=INVALID_HANDLE)  // Поверим удалось ли создать
     {
      FileWrite(file_handle,"прогон","пара","дата","balance","equity","min_equity","margin","max_margin"); //Создадим шапку в файле
      FileSeek(file_handle,0,SEEK_END);    //Передвинем строку
      for(int i=0; i<i_for_exp; i++) // Перебор счётчика дней
        {
         // Создадим запись в файле при получении информации о ордере
         if (TimeDayOfWeek( ArrayForExportEquity[0][i])==0 || TimeDayOfWeek( ArrayForExportEquity[0][i])==6)continue; //информацию о субботе и воскресеньи, если такие дни попали в массив данных, убираем       
         FileWrite(file_handle,  
               MQLInfoString(MQL_PROGRAM_NAME)+" equity "+StdDevPeriod+"-"+StdDevKoef+"-"+tp_koef1+"-"+sl_koef+".csv", //записываем название прогона
               Symbol(), //записываем символ
               TimeToStr(ArrayForExportEquity[0][i],TIME_DATE|TIME_MINUTES), //записываем дату
               ArrayForExportEquity[1][i], //баланс на конец дня
               ArrayForExportEquity[2][i], //эквити на конец дня
               ArrayForExportEquity[3][i], //мин эквити за день
               ArrayForExportEquity[5][i], //маржа на конец дня
               ArrayForExportEquity[6][i]);//макс маржа за день
        }
      FileClose(file_handle); // Закроем файл
     }
   return(0);     
  }  
Файлы с необходимой информацией сохраняются в директорию:  "\\tester\files\Report [номер вашего счёта]".

Сравним результаты того, что мы видим в окне тестера с тем, что мы видим в экспортированном файле с подневными данными эквити на счёте.

К примеру сравним в от этот результат теста.

Результат этого же теста на грфике выглядит следующим образом.

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

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

Вот как в тестере выглядит результат прогона робота Bollindger, который использует в торговле элементы токсичности.

На графике из тестера частично видны моменты роста загрузки депозита и просаживания эквити. Но отражает ли график тестера реальное положение вещей? Сравним с графиком построенном на данных из файла экспорта того же прогона в тестере.

Разница довольно очевидна. К плюшкам так же можно отнести возможность на данных из экспорта построить график динаммики просадок и график используемого плеча на счёте. Как например эти.

Файл экспорта с примерами построения графиков приведенных выше можно скачать отсюда.


Использование коэффициента шарпа для оптимизации советников в МТ4

Подробно об этом коэффициенте я писал в отдельной статье. Я считаю этот показатель самым универсальным для оценки "качества" торговли.

Посмотрим на критерии по которым МТ4 предлагает проводить оптимизацию:

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

Среди показателей нет ни одного стандартизированного коэффициента, который бы одновременно учитывал всю динамику на счете и при этом не привязывался бы никак к абсолютным величинам.

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

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

Единственным более-менее показателем, которым, за не имением лучшего в тестере МТ4, приходится пользоваться большинству — это показатель "прибыльности". Показатель относительный. Например у робота пересиживателя прибыльность (он же более известный как профит фактор) скорее всего будет более чем 2 или 3, в том числе и при очень сомнительном качестве торговли.

В свою очередь, скальпер с большим количеством сделок может иметь профит фактор от 1.1. до 1.3 но при этом иметь очень высокий уровень как качества торговли так и коэффициента комфортности.

Для базовой, первичной оценки качества тестируемой стратегии я использую в МТ4 коэффициент шарпа. МТ4 позволяет считать пользовательские показатели для проведения оптимизации в обработчике OnTester(). Для расчёта коэффициента шарпа я использую тот же массив данных, который используется для экспорта динамики показателей тестового счёта в csv файл. Для расчёта нужно в советник, в дополнение к предыдущему, добавить следующий код.
double OnTester() 
  {
   double ArrDayProfits[];
   ArrayResize(ArrDayProfits,i_for_exp-1); //нам нужен одномерный массив 
   //для использования встроинных функций работающих с массивами. К тому же нам 
   //необходим массив с данными дневных приростов, что на их основе считать среднюю
   //дневную доходность и среднее квадратичное отклонение дневной доходности
 
   for(int i=1; i<i_for_exp; i++)  
     {
       ArrDayProfits[i-1]=ArrayForExportEquity[3][i]/ArrayForExportEquity[3][i-1]-1;
       //рассчитываем дневные приросты МИНИМАЛЬНОГО ЭКВИТИ!!! Использование минимального 
       //эквити является наиболее правильным способом оценки качества торговой системы по шарпу
       //с учётом всех рисков        
     }
   ArraySetAsSeries(ArrDayProfits,true);    
   double aver=iMAOnArray(ArrDayProfits,0,i_for_exp-1,0,0,0); 
   //считаем математическое ожидание дневной доходности
 
   double stddev=iStdDevOnArray(ArrDayProfits,0,i_for_exp-1,0,0,0);
   //считаем среднее квадратичное отклонение дневной доходности
 
   //if (aver<0) return(0);
   if (stddev!=0)return(aver/stddev); //возвращяем коэффициет шарпа
   else return(0); 
  }
В итоге после каждого прогона советника вы получите информацию о рассчитанной величине параметра в журнале.

А в настройках тестера можно выставить параметр оптимизации Custom.

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

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

Исходя из моего опыта я выделил следующие эмпирические диапазоны для коэффициента шарпа:
  • От 0 до 0.02 — плохо. Отдельно использовать такую торговую систему бессмысленно, в рамках портфеля нецелесообразно, так как она скорее всего ухудшит показатели портфеля.
  • От 0.02 до 0.05 — сомнительно. Отдельно использовать такую систему сомнительно. Вполне возможно что добавление такой системы небольшой долей в портфель торговых систем улучшит показатели портфеля.
  • От 0.05 до 0.08 — неплохо. Такую систему можно использовать в торговле отдельно от других и результаты в целом будут приемлемыми. Такая система будет хорошим дополнением в портфель торговых систем.
  • от 0.08 до 0.12 — хорошо. Такая система будет отлично смотреться отдельно от других систем. В портфеле торговых систем такая система может быть одной из основных с существенно большей долей чем у других.
  • От 0.12 до 0.20 — отлично. Гордость создателя. Такую систему можно использовать как базовую в портфеле с "хорошими". Системы с худшими результатами скорее всего начнут занижать Шарп портфеля.
  • От 0.2 и выше — Грааль. Если вы написали советника с таким шарпом и подтвердили робастность системы и статистическую значимость результатов — никому не рассказывайте. Просто закидывайте свои деньги на счета различных брокеров и рубите бабло. Главное не спалитесь.
Но ни стоит забывать, что на сколько бы показатель Шарпа не был бы крут и универсален для оценки торговой стратегии, его одного недостаточно чтобы однозначно утверждать о качестве торговой стратегии. Для более глубокого анализа необходимы дополнительные исследования. Но в качестве базовой оценки качества торговой системы Шарп подходит идеально.

Читай нас в телеграме t.me/hibru. Обсуждай инвестиции на форексе в нашем чате t.me/hibruchat.
Понравился пост? Подпишись на обновления блога по Блог Инвестора Домоседа RSS Блог Инвестора Домоседа по Email Блог Инвестора Домоседа по Вконтакте Блог Инвестора Домоседа по Facebook Блог Инвестора Домоседа по Telegram !