С самого начала моей работы с тестером MT4 меня жутко бесило отсутствие в результатах тестов графиков доходности по эквити во времени. Те графики доходности, которые выдает тестер практически никогда не отражает реальную действительность, так как много моментов ускользает из анализа.
Графики финансового результата во времени наоборот идеальны для анализа как отдельной стратегии, так и для анализа портфеля различных стратегий.
В начале 2015 года, когда я активно занимался разработкой торговых роботов Bollindger, я реализовал в коде советника очень простой, но достаточно эффективный способ извлечения необходимой информации по доходности каждого прогона в тестере стратегий MT4. Даже хотел опубликовать методику в блоге, но на тот момент трейдерской тусовки в блоге не было, поэтому я не стал заморачиваться.
Сейчас же среди читателей блога имеется достаточно трейдеров и поэтому я думаю, что информация для них будет интересной.
Вставки в код о которых я напишу ниже имеются во всех моих советниках и они являются абсолютно универсальными для получения дневного графика доходности по эквити тестируемого советника.
Суть кода заключается в том, чтобы во время теста закинуть ключевую информацию о состоянии счёта в отдельный массив и затем информацию из этого массива экспортировать в csv файл.
Собирается следующая информация:
- Дата
- Баланс счёта на конец дня
- Эквити счёта на конец дня
- Минимальный эквити счёта в течении дня
- Используемая маржа на конец дня
- Минимальная маржа используемая в течении дня
Вставки в код для получения графика динамики дневного эквити тестируемого советника
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); //аналогично с инфой о максимальной марже за день } ...
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); }
Использование коэффициента шарпа для оптимизации советников в МТ4
- Прибыль (максимизация баланса)
- Прибыльность. Отношение всего полученного дохода ко всем полученным убыткам
- Матожидание. Отношение прибыли к количеству сделок.
- Просадка абсолютная.
- Просадка относительная.
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); }
Такой показатель, рассчитанный на данных по приросту минимальных эквити за день, будет очень универсальным. Какую бы торговую стратегию вы не тестировали: с токсикой, без токсики, с большим количеством сделок или с маленьким, с эффектом сложного процента или с фиксированным лотом, на маленьких рисках или на запредельных — неважно, Шарп покажет адекватный результат. И чем этот результат будет выше тем лучше.
Исходя из моего опыта я выделил следующие эмпирические диапазоны для коэффициента шарпа:
- От 0 до 0.02 — плохо. Отдельно использовать такую торговую систему бессмысленно, в рамках портфеля нецелесообразно, так как она скорее всего ухудшит показатели портфеля.
- От 0.02 до 0.05 — сомнительно. Отдельно использовать такую систему сомнительно. Вполне возможно что добавление такой системы небольшой долей в портфель торговых систем улучшит показатели портфеля.
- От 0.05 до 0.08 — неплохо. Такую систему можно использовать в торговле отдельно от других и результаты в целом будут приемлемыми. Такая система будет хорошим дополнением в портфель торговых систем.
- от 0.08 до 0.12 — хорошо. Такая система будет отлично смотреться отдельно от других систем. В портфеле торговых систем такая система может быть одной из основных с существенно большей долей чем у других.
- От 0.12 до 0.20 — отлично. Гордость создателя. Такую систему можно использовать как базовую в портфеле с «хорошими». Системы с худшими результатами скорее всего начнут занижать Шарп портфеля.
- От 0.2 и выше — Грааль. Если вы написали советника с таким шарпом и подтвердили робастность системы и статистическую значимость результатов — никому не рассказывайте. Просто закидывайте свои деньги на счета различных брокеров и рубите бабло. Главное не спалитесь.