Для тех, кто выполняет оптимизацию эксперта впервые, порядок подготовки и проведения оптимизации можно прочитать в меню справки клиентского терминала MetaTrader 4 <Справка - Вызов справки F1 - Автотрейдинг - Оптимизация советников - Настройка>, а также можно почитать статью Тестирование экспертов в клиентском терминале MetaTrader 4. Взгляд изнутри.
Далее отметим галочками переменные, которые будем оптимизировать, как показано на скриншоте.
Автооптимизация ограничена четырьмя переменными, но нам для проверки в целях сокращения времени оптимизации достаточно и трех. После того как переменные выбраны, сохраним установленные настройки оптимизации в set-файл с именем MACD Sample_1.set. Этот файл должен быть сохранен в папку tester Терминал-Тестера. Далее запустим предварительную оптимизацию и запомним время старта. Это нужно, чтобы можно было вычислить время,
необходимое для автооптимизации с установленными параметрами. После окончания оптимизации вычислим необходимое время ожидания. Затем этот терминал нужно закрыть, так как иначе мы не сможем запустить его программно.
Подготовка эксперта, находящегося в Терминале. Для этого откроем в редакторе MetaEditor проверочный эксперт
MACD Sample_1.mq4 и выполним соответствующие настройки.
- установим время запуска автооптимизации, например в 00:01 каждые сутки.
datetime SetHour = 0;
datetime SetMinute = 1;
- установим количество дней для оптимизации (должно совпадать с количеством дней предварительной оптимизации):
- установим время ожидания окончания оптимизации в минутах, которое мы вычислили ранее, например 4 минуты.
- впишем имя советника:
string NameMTS = "MACD Sample_1";
- впишем имя set-файла с настройками:
string NameFileSet = "MACD Sample_1.set";
- впишем путь к папке с установленным "терминал-тестером", например:
string PuthTester = "D:\Program Files\Forex Best Trade Station";
- установим очередность фильтрации:
int Gross_Profit = 1;
int Profit_Factor = 2;
int Expected_Payoff = 3;
- запишем имена переменных для оптимизации:
string Per1 = "FastEMA";
string Per2 = "SlowEMA";
string Per3 = "SignalSMA";
string Per4 = "";
- далее скопируем приложенный файл auto_optimization.mqh в папку "include";
- подключим файл библиотеки в эксперте:
#include <auto_optimization.mqh>
- осталось скопировать указанный ниже код в начало функции start() вашего эксперта. В MACD Sample_1.mq4 он уже есть.
if(!IsTesting() && !IsOptimization())
{
if(TimeHour(TimeLocal()) == SetHour)
{
if(!StartTest)
{
if(TimeMinute(TimeLocal()) > SetMinute - 1)
{
if(TimeMinute(TimeLocal()) < SetMinute + 1)
{
StartTest = true;
TimeStart = TimeLocal();
Tester(TestDay, NameMTS, NameFileSet, PuthTester,
TimeOut, Gross_Profit, Profit_Factor,
Expected_Payoff, Per1, Per2, Per3, Per4);
}
}
}
}
}
FastEMA = GlobalVariableGet(Per1);
SlowEMA = GlobalVariableGet(Per2);
SignalSMA = GlobalVariableGet(Per3);
TrailingStop = GlobalVariableGet(Per4);
if(StartTest)
{
if(TimeLocal() - TimeStart > TimeOut*60)
{
StartTest = false;
}
}
Вот и все. После перекомпиляции автооптимизатор можно запускать, причем запускать нужно на том же инструменте и на том же периоде, на котором проводилась предварительная оптимизация, в данном случае на EURUSD периода H1. Для проверки автооптимизатора можно вставить указанный ниже код в функцию int init (), тогда автооптимизатор запустится сразу в момент старта советника.
Tester(TestDay,NameMTS,NameFileSet,PuthTester,TimeOut, Gross_Profit,Profit_Factor,
Expected_Payoff, Per1,Per2,Per3,Per4);
Принцип работы автооптимизатора
Принцип работы автооптимизатора заключается в использовании Терминал-тестера для оптимизации параметров советника, установленного на график в Терминале. Для этого программа пересылает в Терминал-Тестер файл с параметрами оптимизации (optimise.ini) и запускает Терминал-Тестер в режиме оптимизации. Потом копирует полученные результаты "FileReport........htm" обратно в Терминал и отфильтровывает из полученных результатов лучшие значения.
Подробнее о работе автооптимизатора.В установленное время, например, в 00.01, запускается автооптимизатор. Переменные заполняются значениями.
string PuthTerminal = TerminalPath() + "\experts\files";
string FileOptim = "optimise.ini";
string FileOptim1 = "\optimise.ini";
datetime DayStart = TimeLocal()-86400*TestDay;
string DateStart = TimeToStr(DayStart,TIME_DATE);
string DateStop = TimeToStr(TimeLocal(),TIME_DATE);
string FileReport = "FileReport_" + Symbol() + "_" + DateStop + ".htm";
string FileReport1 = "\FileReport_" + Symbol() + "_" + DateStop + ".htm";
double MinTr = TestDay - 2;
double MaxTr = (60 / Period()*TestDay) + 2;
int KvoPptk = 10;
int StepRes = 12;
Далее в строковый массив записываются параметры ini-файла.
ArrayOpttim[0] = ";optimise strategy tester";
ArrayOpttim[1] = "ExpertsEnable = false";
ArrayOpttim[2] = "TestExpert=" + NameMTS;
ArrayOpttim[3] = "TestExpertParameters=" + NameFileSet;
ArrayOpttim[4] = "TestSymbol=" + Symbol();
ArrayOpttim[5] = "TestPeriod=" + Period();
ArrayOpttim[6] = "TestModel=" + 0;
ArrayOpttim[7] = "TestRecalculate=false";
ArrayOpttim[8] = "TestOptimization=true";
ArrayOpttim[9] = "TestDateEnable=true";
ArrayOpttim[10] = "TestFromDate=" + DateStart;
ArrayOpttim[11] = "TestToDate=" + DateStop;
ArrayOpttim[12] = "TestReport=" + FileReport;
ArrayOpttim[13] = "TestReplaceReport=true";
ArrayOpttim[14] = "TestShutdownTerminal=true";
Из массива параметры оптимизации переписываются в ini-файл. О формировании ini-файла также можно прочитать в справке клиентского терминала MetaTrader 4 в разделе <Справка - Вызов справки F1 - Сервис - Конфигурация при старте>.
OptimArraySize = ArraySize(ArrayOpttim);
opttim = FileOpen(FileOptim, FILE_CSV|FILE_WRITE, 0x7F);
if(opttim > 0)
{
for(int i = 0; i < OptimArraySize; i++)
{
ini = ArrayOpttim[i];
FileWrite(opttim, ini);
}
FileClose(opttim);
}
else
{
Print("Не удалось записать данные в ini-файл. Ошибка № ",
GetLastError());
return(0);
}
После записи параметров в ini-файл подключается входящая в состав Windows библиотека shell32.dll и запускается функция ShellExecuteA.
#import "shell32.dll"
int ShellExecuteA(int hwnd,string Operation,string
File,string Parameters,string Directory,int ShowCmd);
#import
Файл с параметрами пересылается в папку Терминал-тестера.
copyini = ShellExecuteA(0,"Open","xcopy", "\"" + PuthTerminal +
FileOptim1 + "\" \"" + PuthTester + "\" /y",
"", 3);
Sleep(1200);
if(copyini < 0)
{
Print("Не удалось скопировать ini-файл");
return(0);
}
Далее запускается тестер и начинает оптимизацию заданных переменных. Пока идет оптимизация, эксперт находится в состоянии ожидания.
start = ShellExecuteA(0, "Open", "terminal.exe", FileOptim,
PuthTester, 3);
if(start < 0)
{
Print("Не удалось запустить тестер");
return(0);
}
Comment("Ожидаем окончания оптимизации");
Sleep(60000*TimeOut);
После завершения оптимизации тестер автоматически записывает результаты в файл отчета. Этот файл копируется в папку с терминалом.
for(Pptk = 0; Pptk < KvoPptk; Pptk++)
{
Comment("Попытка № " + Pptk + " скопировать файл отчета");
ShellExecuteA(0, "Open", "xcopy", "\"" + PuthTester + FileReport1 +
"\" \"" + PuthTerminal + "\" /y", "", 3);
Sleep(1200);
file = FileOpen(FileReport, FILE_READ, 0x7F);
if(file < 0)
{
Sleep(60000);
}
else
break;
}
if(file < 0)
{
Print("Не удалось скопировать файл отчета");
return(0);
}
Затем данные из файла отчета заносятся в строковый массив для дальнейшей обработки.
while(FileIsEnding(file) == false)
{
FileLine = FileReadString(file);
index = StringFind(FileLine, "title", 20);
if(index > 0)
{
ArrayResize(ArrayStrg, NumStr + 1);
ArrayStrg[NumStr] = FileLine;
NumStr++;
}
}
FileClose(file);
FileDelete(FileReport);
ArrayResize(ArrayData, NumStr); строк
.
Далее происходит выборка нужных значений из массива
for(text = 0; text < NumStr; text++)
{
select = ArrayStrg[text];
ClStep=StringFind(select, "; \">",20)+4;
ClStepRazm = StringFind(select, "td>", ClStep);
CycleStep = StringSubstr(select, ClStep, ClStepRazm - ClStep);
GrProf = StringFind(select, "", ClStepRazm);
GrProfRazm = StringFind(select, "td>", GrProf);
GrossProfit = StringSubstr(select, GrProf+4, GrProfRazm - (GrProf + 4));
TotTrad = StringFind(select, "", GrProfRazm);
TotTradRazm = StringFind(select, "td>", TotTrad);
TotalTrades = StringSubstr(select, TotTrad+4, TotTradRazm -
(TotTrad + 4));
ProfFact = StringFind(select, "", TotTradRazm);
ProfFactRazm = StringFind(select, "td>", ProfFact);
ProfitFactor = StringSubstr(select, ProfFact + 4, ProfFactRazm -
(ProfFact + 4));
ExpPay = StringFind(select, "", ProfFactRazm);
ExpPayRazm=StringFind(select, "td>", ExpPay);
ExpectedPayoff = StringSubstr(select, ExpPay+4, ExpPayRazm -
(ExpPay + 4));
P1 = StringFind(select, Per1, 20);
P1k = StringFind(select, ";", P1);
Perem1 = StringSubstr(select, P1 + StringLen(Per1) + 1, P1k -
(P1 + 1 + StringLen(Per1)));
P2 = StringFind(select, Per2, 20);
P2k = StringFind(select, ";", P2);
Perem2 = StringSubstr(select, P2 + StringLen(Per2) + 1, P2k -
(P2 + 1 + StringLen(Per2)));
P3 = StringFind(select, Per3, 20);
P3k = StringFind(select, ";", P3);
Perem3 = StringSubstr(select, P3 + StringLen(Per3) + 1, P3k -
(P3 + 1 + StringLen(Per3)));
P4 = StringFind(select, Per4, 20);
P4k = StringFind(select, ";", P4);
Perem4 = StringSubstr(select, P4 + StringLen(Per4) + 1, P4k -
(P4 + 1 + StringLen(Per4)));
Comment("Идет анализ полученных результатов");
После этого полученные значения перед переводом их в числовой формат фильтруются по минимальному и максимальному количеству сделок. Ноль в значениях прибыльности (Profit_Factor) заменяется на 1000 для правильной сортировки и последующего отсеивания.
TotalTradesTransit = StrToDouble(TotalTrades);
GrossProfitTransit = StrToDouble(GrossProfit);
ExpectedPayoffTran = StrToDouble(ExpectedPayoff);
nodubl = true;
if(MinTr < TotalTradesTransit && MaxTr > TotalTradesTransit)
{
PrFactDouble = StrToDouble(ProfitFactor);
if(PrFactDouble == 0)
{
PrFactDouble = 1000;
}
Далее значения фильтруются на наличие дубликатов.
for(Dubl = 0; Dubl <= ResizeArayNew; Dubl++)
{
if(GrossProfitTransit == ArrayData[Dubl][1])
{
if(TotalTradesTransit == ArrayData[Dubl][2])
{
if(PrFactDouble == ArrayData[Dubl][3])
{
if(ExpectedPayoffTran == ArrayData[Dubl][4])
{
nodubl=false;
}
}
}
}
}
Далее подготовленные для сортировки значения записываются в массив.
if(nodubl)
{
ArrayData[text][1] = GrossProfitTransit;
ArrayData[text][2] = TotalTradesTransit;
ArrayData[text][3] = PrFactDouble;
ArrayData[text][4] = ExpectedPayoffTran;
ArrayData[text][5] = StrToDouble(Perem1);
ArrayData[text][6] = StrToDouble(Perem2);
ArrayData[text][7] = StrToDouble(Perem3);
ArrayData[text][8] = StrToDouble(Perem4);
ResizeArayNew++;
}
Далее начинается анализ данных в порядке очередности, установленной ранее. Анализ происходит так:
- запускается цикл, и при первом проходе выполняется сортировка значений по первому параметру, например, по максимальной прибыли; выбирается несколько лучших значений (по умолчанию 12), остальные отсекаются;
- при втором проходе происходит сортировка по второму параметру, например, по прибыльности; также выбираются несколько лучших значений, половина после первой сортировки, остальные отсекаются;
- при третьем проходе происходит последняя сортировка по третьему параметру, например, по матожиданию; так же выбирается половина значений, но уже после второй сортировки, остальные отсекаются.
ArrayResize(ArrayTrans, ResizeArayNew - 1);
for(int PrioStep = 1; PrioStep < 4; PrioStep++)
{
for(PrCycle = 0; PrCycle < ResizeArayNew; PrCycle++)
{
Sort = ArrayData[PrCycle][0];
Prior1 = ArrayData[PrCycle][1];
transit = ArrayData[PrCycle][2];
Prior2 = ArrayData[PrCycle][3];
Prior3 = ArrayData[PrCycle][4];
transit1 = ArrayData[PrCycle][5];
transit2 = ArrayData[PrCycle][6];
transit3 = ArrayData[PrCycle][7];
transit4 = ArrayData[PrCycle][8];
if(PrioStep == 1)
{
if(Gross_Profit ==1)
{
SortTrans = Prior1;
}
if(Profit_Factor == 1)
{
SortTrans = Prior2;
}
if(Expected_Payoff == 1)
{
SortTrans = Prior3;
}
}
if(PrioStep == 2)
{
if(Gross_Profit ==1)
{
Prior1 = Sort;
}
if(Profit_Factor == 1)
{
Prior2 = Sort;
}
if(Expected_Payoff == 1)
{
Prior3 = Sort;
}
if(Gross_Profit == 2)
{
SortTrans = Prior1;
}
if(Profit_Factor == 2)
{
SortTrans = Prior2;
}
if(Expected_Payoff == 2)
{
SortTrans = Prior3;
}
}
if(PrioStep == 3)
{
if(Gross_Profit == 2)
{
Prior1 = Sort;
}
if(Profit_Factor == 2)
{
Prior2 = Sort;
}
if(Expected_Payoff == 2)
{
Prior3 = Sort;
}
if(Gross_Profit ==3)
{
SortTrans = Prior1;
}
if(Profit_Factor == 3)
{
SortTrans = Prior2;
}
if(Expected_Payoff == 3)
{
SortTrans = Prior3;
}
}
ArrayTrans[PrCycle][0] = SortTrans;
ArrayTrans[PrCycle][1] = Prior1;
ArrayTrans[PrCycle][2] = transit;
ArrayTrans[PrCycle][3] = Prior2;
ArrayTrans[PrCycle][4] = Prior3;
ArrayTrans[PrCycle][5] = transit1;
ArrayTrans[PrCycle][6] = transit2;
ArrayTrans[PrCycle][7] = transit3;
ArrayTrans[PrCycle][8] = transit4;
}
ArraySort(ArrayTrans,StepRes, 0, MODE_DESCEND);
ArrayResize(ArrayTrans, StepRes);
for(int CopyAr = 0; CopyAr < StepRes; CopyAr++)
{
ArrayData[CopyAr][0] = ArrayTrans[CopyAr][0];
ArrayData[CopyAr][1] = ArrayTrans[CopyAr][1];
ArrayData[CopyAr][2] = ArrayTrans[CopyAr][2];
ArrayData[CopyAr][3] = ArrayTrans[CopyAr][3];
ArrayData[CopyAr][4] = ArrayTrans[CopyAr][4];
ArrayData[CopyAr][5] = ArrayTrans[CopyAr][5];
ArrayData[CopyAr][6] = ArrayTrans[CopyAr][6];
ArrayData[CopyAr][7] = ArrayTrans[CopyAr][7];
ArrayData[CopyAr][8] = ArrayTrans[CopyAr][8];
}
StepRes = StepRes / 2;
}
Отфильтрованные таким образом значения записываются в глобальные переменные. Из глобальных переменных значения подставляются в советник.
double Peremen1 = ArrayTrans[0][5];
double Peremen2 = ArrayTrans[0][6];
double Peremen3 = ArrayTrans[0][7];
double Peremen4 = ArrayTrans[0][8];
if(Per1 != "")
{
GlobalVariableSet(Per1, Peremen1);
}
if(Per2 != "")
{
GlobalVariableSet(Per2,Peremen2);
}
if(Per3 != "")
{
GlobalVariableSet(Per3,Peremen3);
}
if(Per4 != "")
{
GlobalVariableSet(Per4,Peremen4);
}
Comment(Per1, " ", Peremen1, " | ", Per2, " ", Peremen2, " | ", Per3,
" ", Peremen3, " | ", Per4, " ", Peremen4);
Print(Per1, " ", Peremen1, " | ", Per2, " ", Peremen2, " | ", Per3,
" ", Peremen3," | ",Per4," ",Peremen4);
}
Результат работы автооптимизатора
Результат работы автооптимизатора можно отслеживать по сообщениям появляющимся в верхнем левом углу графика, как показано на скриншоте:
Расчетное время окончания оптимизации.
Анализ полученных после оптимизации значений.
Итоговые значения переменных.
Появление результатов оптимизации в сообщении говорит о том, что оптимизация завершена и данные получены.
Для самостоятельной оценки работы автооптимизатора можно посмотреть сохраненные в процессе работы файлы с промежуточными данными. Тестер сохраняет данные в файл с названием "FileReport_EURUSD_2007.03.12.htm", где символ инструмента и дата подставляются в название файла в зависимости от выбранного инструмента и текущей даты оптимизации. Находится этот файл в папке Терминал-тестера. Эти файлы с отчетами автоматически не удаляются и по ним можно отслеживать изменение параметров.
Следующий файл FileTest1.csv сохраняется после фильтрации значений по количеству сделок и удалению дубликатов. Файл сохраняется в папку: D:\Program Files\имя папки терминала\experts\files
Далее в файл FileTest2.csv сохраняются значения после каждого шага отсеивания. Файл также сохраняется в папку: D:\Program Files\имя папки терминала\experts\files
Из вышеприведенных таблиц видно, как происходит фильтрация полученных значений. Порядок фильтрации был установлен по умолчанию: 1- Gross_Profit, 2- Profit_Factor, 3- Expected_Payoff.
В коде автооптимизатора даны подробные комментарии и при необходимости вы можете сами подобрать подходящие для вас параметры переменных. Например, вы хотите оптимизировать не за последние дни, а за какой то другой промежуток времени, или предполагаете увеличить или уменьшить количество сделок за период оптимизации. Для этого вам достаточно изменить соответствующие переменные непосредственно в библиотеке auto_optimization.mqh.
double MinTr = TestDay - 2;
double MaxTr = (60 / Period()*TestDay) + 2;
int KvoPptk = 10;
int StepRes = 12;
Заключение
Целью статьи не является обучение новичков азам оптимизации, поэтому настоятельно рекомендуется, прежде чем настраивать автоматическую оптимизацию вашего эксперта, изначально научится оптимизировать обычным способом. Использовать автооптимизатор лучше после того, как вы уже определились с основными переменными, влияющими на работу вашего эксперта по разному в разное время. То есть при помощи этого автооптимизатора лучше подстраивать параметры именно тех переменных, изменение которые сильнее других влияют на работу советника в зависимости от изменчивости рынка.
Кроме того, желательно не устанавливать период автооптимизации слишком большим. Предположим, эксперт будет каждые сутки оптимизироваться по 6 - 12 часов. Тогда возникает вопрос, а когда он будет торговать? Иначе говоря, оптимизация не должна быть ради самой оптимизации. Устанавливать периодичность (имеется в виду периодичность запуска автооптимизатора) проведения оптимизации желательно с учетом периода, на котором предполагается торговля советника. То есть, нужно учитывать, что во время запуска Терминал-Тестера происходит подкачка исторических данных и у брокера может просто не быть нужных исторических данных за установленный период. Для проверки описанной в начале статьи гипотезы необходимо наличие круглосуточно подключенного к интернету компьютера и стабильного интернета.
Разработанные программы автоматической оптимизации расположены в прикрепленных файлах: auto_optimization.mqh - непосредственно сама библиотека, MACD Sample_1.mq4 - слегка измененный советник, входящий в состав клиентского терминала MetaTrader 4.