Для тех, кто выполняет оптимизацию эксперта впервые, порядок подготовки и проведения оптимизации можно прочитать в меню справки клиентского терминала 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