据推测,获得已根据历史记录调整的输入的 Expert Advisor 在首次(在相当短的时间内)交易中能够盈利。在我观看2006 自动交易锦标赛之后,这一推测得到了间接的证明。锦标赛刚开始时能盈利的智能交易系统很多,之后就大大减少,一些智能交易系统变得毫无竞争力。这就是我认为这些没能笑到最后的智能交易系统大部分都根据历史记录进行过调整的原因。
在实践中验证这一假设的想法源于此网站的俄语论坛中的理想的自动交易系统版块。主要的想法是每天自动优化一次 EA,然后分析获得的优化结果,并将它们记录到 EA 的变量中。为了实施这一想法,我们决定采用 MetaTrader 4 客户终端中的现成 Expert Advisor(即 MACD 样本),并把我们自己的自动优化函数插入到该 Expert Advisor 中。稍后,该自动优化器的代码就编写完毕并上传到同一论坛中的自动优化器版块中。再过了一段时间,自动优化器的子版块中出现了对该想法的第一条确认消息。后来,此优化器被转变为一个 mqh 库以提高其可用度。
以下是执行此任务的必要流程:
为方便起见,下文中我们将把原始终端称为“终端”,将副本称为“终端测试程序”。我们将使用起初由终端提供,后经略微更改的 Expert Advisor(MACD Sample_1.mq4)对时间范围 H1 内的 EURUSD 执行一次检查性测试。
终端测试程序
请勿忘记在终端测试程序中编译 MACD Sample_1.mq4。首先我们启动客户终端,然后启动策略测试程序,并按下方的屏幕截图进行设置。
优化将执行三天。这已足够检查自动优化程序了。我们将根据以下公式选择优化日期“开始日期”:当前日期减去三天。优化期间,必须下载选定交易品种(本案例中为 EURUSD)的必要历史记录。
首次执行优化的用户会在 MetaTrader 4 客户终端的“帮助”菜单中找到必要程序的说明:<帮助 - 帮助主题 F1 - 自动交易 - Expert 优化 - 设置>。或者可以阅读文章在 MetaTrader 4 客户终端中测试 Expert Advisor:外部一览。
然后我们检查要优化的变量,如下方屏幕截图中所示。
datetime SetHour = 0; // Optimization starting hour; datetime SetMinute = 1; // Optimization starting minute.
- 设置进行优化的天数(必须与预优化的设置天数相同):
int TestDay = 3;
- 设置我们之前计算出的优化结束等待时间(以分钟为单位),例如 4 分钟:
int TimeOut = 4;
- 键入 Expert Advisor 名称:
string NameMTS = "MACD Sample_1"; // EA's name
- 键入设置文件名称和设置:
// Set-file name with the settings string NameFileSet = "MACD Sample_1.set";
- 键入包含已安装终端测试程序的文件夹路径,例如:
// Path to the tester string PuthTester = "D:\Program Files\Forex Best Trade Station";
- 设置过滤优先级:
// Sorting by Maximal profit int Gross_Profit = 1; // Sorting by Maximal profit factor int Profit_Factor = 2; // Sorting by Maximal expected payoff int Expected_Payoff = 3;
- 写入用于优化的变量名称:
string Per1 = "FastEMA"; string Per2 = "SlowEMA"; string Per3 = "SignalSMA"; string Per4 = "";
- 将随附文件 auto_optimization.mqh 复制到“include”文件夹中;
- 在 Expert Advisor 中包括此库文件:
//--- Including the auto optimizer's library #include <auto_optimization.mqh>
- 剩下的就是将下方的代码复制到您 Expert Advisor 的 start() 函数的开头。MACD Sample_1.mq4 中已包含该代码。
// Not to be launched at testing and optimizing if(!IsTesting() && !IsOptimization()) { // Compare the current hour with that preset for launching if(TimeHour(TimeLocal()) == SetHour) { // Protection against restarting if(!StartTest) { // Compare the minute range to the minute // preset for launching if(TimeMinute(TimeLocal()) > SetMinute - 1) { // the range is necessary, in case that for some reasons // no new tick is available for a long time if(TimeMinute(TimeLocal()) < SetMinute + 1) { // Flag of tester launching 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 the tester launching is flagged if(StartTest) { // If more time has elapsed the launching than it was set // to be the test waiting time if(TimeLocal() - TimeStart > TimeOut*60) { // Zeroize the flag StartTest = false; } }
这就完成了。重新编译自动优化程序之后可以启动它,但它仅适用于执行预优化时所用的交易品种和时间范围。在我们的案例中,是 H1 时间范围上的 EURUSD 交易品种。要检查自动优化程序,可将下方给出的代码插入到 int init () 函数中,之后将在 Expert Advisor 启动时启动自动优化程序。
Tester(TestDay,NameMTS,NameFileSet,PuthTester,TimeOut, Gross_Profit,Profit_Factor, Expected_Payoff, Per1,Per2,Per3,Per4);
自动优化程序的工作原理是使用终端测试程序在终端中优化连接到图表的 Expert Advisor 的参数。为此,程序向终端测试程序发送一个包含优化参数 (optimise.ini) 的文件,并在优化模式下启动终端测试程序。然后,它将获取的结果 "FileReport........htm" 复制回终端,从获取的结果中筛选最佳值。
// Path to the terminal string PuthTerminal = TerminalPath() + "\experts\files"; // Name of the ini file for the tester string FileOptim = "optimise.ini"; string FileOptim1 = "\optimise.ini"; // Calculation of the starting date datetime DayStart = TimeLocal()-86400*TestDay; // Optimization starting date string DateStart = TimeToStr(DayStart,TIME_DATE); // Optimization ending date string DateStop = TimeToStr(TimeLocal(),TIME_DATE); // Tester report file name string FileReport = "FileReport_" + Symbol() + "_" + DateStop + ".htm"; string FileReport1 = "\FileReport_" + Symbol() + "_" + DateStop + ".htm"; // Limitation for the minimum amount of trades per day double MinTr = TestDay - 2; // Limitation for maximal amount of trades per day double MaxTr = (60 / Period()*TestDay) + 2; // The amount of attempts to copy the report file int KvoPptk = 10; // The amount of lines for sorting int StepRes = 12;
然后在字符串数组中写入 ini 文件的参数:
// Prepare the ini file for optimization ArrayOpttim[0] = ";optimise strategy tester"; // Enable/Disable Expert Advisors ArrayOpttim[1] = "ExpertsEnable = false"; // Name of the EA file ArrayOpttim[2] = "TestExpert=" + NameMTS; // Name of the file containing parameters ArrayOpttim[3] = "TestExpertParameters=" + NameFileSet; // Symbol ArrayOpttim[4] = "TestSymbol=" + Symbol(); // Timeframe ArrayOpttim[5] = "TestPeriod=" + Period(); // Modeling mode ArrayOpttim[6] = "TestModel=" + 0; // Recalculate ArrayOpttim[7] = "TestRecalculate=false"; // Optimization ArrayOpttim[8] = "TestOptimization=true"; // Use date ArrayOpttim[9] = "TestDateEnable=true"; // From ArrayOpttim[10] = "TestFromDate=" + DateStart; // To ArrayOpttim[11] = "TestToDate=" + DateStop; // Report file name ArrayOpttim[12] = "TestReport=" + FileReport; // Rewrite the report file ArrayOpttim[13] = "TestReplaceReport=true"; // Shut down the terminal upon completion ArrayOpttim[14] = "TestShutdownTerminal=true";
从数组中将优化参数记录到 ini 文件中。也可阅读 MetaTrader 4 客户终端“帮助”中有关如何创建 ini 文件的内容,详见 <帮助 - 帮助主题 F1 - 工具 - 启动时的配置>。
// Write data into the ini file // Find out about the array size OptimArraySize = ArraySize(ArrayOpttim); // Open a file to write opttim = FileOpen(FileOptim, FILE_CSV|FILE_WRITE, 0x7F); if(opttim > 0) { for(int i = 0; i < OptimArraySize; i++) { // from the array into the variable ini = ArrayOpttim[i]; // from the variable into the file FileWrite(opttim, ini); } // close the file FileClose(opttim); } else { Print("Failed writing data into the ini file. Error No ", GetLastError()); return(0); }
将参数记录到 ini 文件后,连接 Windows 标准交付内容中包含的 shell32.dll,并启动函数 ShellExecuteA。
#import "shell32.dll" //Connect a dll (provided with Windows) int ShellExecuteA(int hwnd,string Operation,string File,string Parameters,string Directory,int ShowCmd); #import
包含这些参数的文件将被发送到“终端测试程序”文件夹中。
// copy the ini file into the tester folder copyini = ShellExecuteA(0,"Open","xcopy", "\"" + PuthTerminal + FileOptim1 + "\" \"" + PuthTester + "\" /y", "", 3); // wait until the file is copied Sleep(1200); if(copyini < 0) { Print("Failed copying ini file"); return(0); }
然后启动测试程序,开始优化预定义变量。优化期间,Expert Advisor 处于暂停状态。
// Start Tester start = ShellExecuteA(0, "Open", "terminal.exe", FileOptim, PuthTester, 3); if(start < 0) { Print("Failed starting Tester"); return(0); } Comment("Wait until optimization is complete"); // wait until optimization is complete Sleep(60000*TimeOut);
优化完成后,测试程序将自动把结果记录到报告文件中。该文件将被复制到包含终端的文件夹中。
for(Pptk = 0; Pptk < KvoPptk; Pptk++) { //Start a cycle attempting to compy the resport file Comment("Attempt # " + Pptk + " to copy the report file"); ShellExecuteA(0, "Open", "xcopy", "\"" + PuthTester + FileReport1 + "\" \"" + PuthTerminal + "\" /y", "", 3); // wait until the file is copied Sleep(1200); // Try to open the report file file = FileOpen(FileReport, FILE_READ, 0x7F); if(file < 0) { // if it fails to open, wait some more and try again Sleep(60000); } else break; } if(file < 0) { Print("Failed copying the report file"); return(0); }
然后该报告文件中的数据将被放到字符串数组中,进行进一步的处理。
// Read from file into the array // Cycle, until the file ends while(FileIsEnding(file) == false) { // Read a string from the report file FileLine = FileReadString(file); // Find the necessary string and set the reference point there index = StringFind(FileLine, "title", 20); if(index > 0) { // Increase the array in size ArrayResize(ArrayStrg, NumStr + 1); // Record the strings from the file in the array ArrayStrg[NumStr] = FileLine; NumStr++; } } // Close the file FileClose(file); // Delete the file in order not to produce too many copies FileDelete(FileReport); // Set the array size by the amount of data read from the file ArrayResize(ArrayData, NumStr);
之后,在数组中选择必要的值:
for(text = 0; text < NumStr; text++) { select = ArrayStrg[text]; //------------------------------------------------------------------------- // Reporting text processing (These are apples and oranges) | //------------------------------------------------------------------------- // Position Pass ClStep=StringFind(select, "; \">",20)+4; // Find the end of position ClStepRazm = StringFind(select, "td>", ClStep); // Read the value CycleStep = StringSubstr(select, ClStep, ClStepRazm - ClStep); // Position Profit // Find the beginning of the position GrProf = StringFind(select, "", ClStepRazm); // Find the end of position GrProfRazm = StringFind(select, "td>", GrProf); // Read value GrossProfit = StringSubstr(select, GrProf+4, GrProfRazm - (GrProf + 4)); // Position Total Trades // Find the beginning of position TotTrad = StringFind(select, "", GrProfRazm); // Find the end of position TotTradRazm = StringFind(select, "td>", TotTrad); // Read the value TotalTrades = StringSubstr(select, TotTrad+4, TotTradRazm - (TotTrad + 4)); // Position Profitability // Find the beginning of position ProfFact = StringFind(select, "", TotTradRazm); // Find the end of position ProfFactRazm = StringFind(select, "td>", ProfFact); // Read the value ProfitFactor = StringSubstr(select, ProfFact + 4, ProfFactRazm - (ProfFact + 4)); // Position Expected Payoff // Find the beginning of position ExpPay = StringFind(select, "", ProfFactRazm); // Find the dn of position ExpPayRazm=StringFind(select, "td>", ExpPay); // Read the value ExpectedPayoff = StringSubstr(select, ExpPay+4, ExpPayRazm - (ExpPay + 4)); // Variables' positions starting with the second one // Find the beginning of position P1 = StringFind(select, Per1, 20); // Find the end of position P1k = StringFind(select, ";", P1); // Read the Variable Perem1 = StringSubstr(select, P1 + StringLen(Per1) + 1, P1k - (P1 + 1 + StringLen(Per1))); // Find the beginning of position P2 = StringFind(select, Per2, 20); // Find the end of position P2k = StringFind(select, ";", P2); // Read the Variable Perem2 = StringSubstr(select, P2 + StringLen(Per2) + 1, P2k - (P2 + 1 + StringLen(Per2))); // Find the beginning of position P3 = StringFind(select, Per3, 20); // Find the end of position P3k = StringFind(select, ";", P3); // Read the Variable Perem3 = StringSubstr(select, P3 + StringLen(Per3) + 1, P3k - (P3 + 1 + StringLen(Per3))); // Find the beginning of position P4 = StringFind(select, Per4, 20); // Find the end of position P4k = StringFind(select, ";", P4); // Read the Variable Perem4 = StringSubstr(select, P4 + StringLen(Per4) + 1, P4k - (P4 + 1 + StringLen(Per4))); Comment("The obtained results are being analyzed");
之后,获取的结果在被转变为数字格式之前,已按最小和最大交易金额进行了筛选。Profit_Factor 值中的 0 替换为 1000,以便进行正确的排序和后续的筛选。
// Transform into number format TotalTradesTransit = StrToDouble(TotalTrades); GrossProfitTransit = StrToDouble(GrossProfit); ExpectedPayoffTran = StrToDouble(ExpectedPayoff); nodubl = true; if(MinTr < TotalTradesTransit && MaxTr > TotalTradesTransit) { // Filter by the amount of trades PrFactDouble = StrToDouble(ProfitFactor); // Replace 0 in the Profit_Factor for proper analysis if(PrFactDouble == 0) { PrFactDouble = 1000; }
然后检查这些值,以进行复制和筛除。
// Filter data having identical values for(Dubl = 0; Dubl <= ResizeArayNew; Dubl++) { // Start the loop searching for identical values if(GrossProfitTransit == ArrayData[Dubl][1]) { // check whether the results for maximal profit coincide if(TotalTradesTransit == ArrayData[Dubl][2]) { // check whether the results for the amount of trades coincide if(PrFactDouble == ArrayData[Dubl][3]) { // check whether the results for Profit Factor coincide if(ExpectedPayoffTran == ArrayData[Dubl][4]) { // check whether the results for expected payoff coincide nodubl=false; // If everything coincides, flag it as coincided } } } } }
在数组中写入用于排序的值。
// Write the filtered data in the array 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++; }
然后开始以预设优先顺序分析数据。分析程序如下:
// Analyzer // Analyzing principle is the sequential checking of maximal // values according to the predefined filtering priority 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) { //Prepare for the 1st sorting if(Gross_Profit ==1) { SortTrans = Prior1; } if(Profit_Factor == 1) { SortTrans = Prior2; } if(Expected_Payoff == 1) { SortTrans = Prior3; } } if(PrioStep == 2) { // Restore if(Gross_Profit ==1) { Prior1 = Sort; } if(Profit_Factor == 1) { Prior2 = Sort; } if(Expected_Payoff == 1) { Prior3 = Sort; } //Prepare for the 2nd sorting if(Gross_Profit == 2) { SortTrans = Prior1; } if(Profit_Factor == 2) { SortTrans = Prior2; } if(Expected_Payoff == 2) { SortTrans = Prior3; } } if(PrioStep == 3) { // Restore if(Gross_Profit == 2) { Prior1 = Sort; } if(Profit_Factor == 2) { Prior2 = Sort; } if(Expected_Payoff == 2) { Prior3 = Sort; } //Prepare for the 3rd sorting 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); // Sort the array ArrayResize(ArrayTrans, StepRes); // Cut off the unnecessary things 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]; // Per1 Variable 1 ArrayData[CopyAr][5] = ArrayTrans[CopyAr][5]; // Per2 Variable 2 ArrayData[CopyAr][6] = ArrayTrans[CopyAr][6]; // Per3 Variable 3 ArrayData[CopyAr][7] = ArrayTrans[CopyAr][7]; // Per4 Variable 4 ArrayData[CopyAr][8] = ArrayTrans[CopyAr][8]; } StepRes = StepRes / 2; }
以这种方式过滤后的值被写入到全局变量中。全局变量中的值将在 EA 中被替换。
// Write the obtained results in variables double Peremen1 = ArrayTrans[0][5]; double Peremen2 = ArrayTrans[0][6]; double Peremen3 = ArrayTrans[0][7]; double Peremen4 = ArrayTrans[0][8]; // If the variable name is specified, write the result in // global variables 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); } // Function ends. That's all, automated optimization is complete.
可通过图表左上角显示的消息查看自动优化程序的运行结果,如下方的屏幕截图中所示:
// Limitation of minimal amount of trades per day double MinTr = TestDay - 2; // Limitation on maximal amount of trades per day double MaxTr = (60 / Period()*TestDay) + 2; // The amount of attempts to copy the report file int KvoPptk = 10; // The amount of strings to be sorted int StepRes = 12;
本文并非用于让新手学习优化元素,因此强烈建议在设置您的 Expert Advisor 的自动优化之前先了解正常的优化方式。最好在选择了可在不同时间以不同方式影响您的 Expert Advisor 的基本变量之后,再使用自动优化程序。即,最好使用此自动优化程序以匹配一些变量参数,这些变量的变化对 EA 操作产生的影响比其他变量的变化大,具体要看市场波幅。
除此之外,最好不要设置很长的自动优化时段。假设 Expert Advisor 总是每天要优化 6-12 个小时。那么就会出现一个问题:什么时候进行交易?换句话说,优化自身并不是必要的。建议根据 EA 要进行交易的时间范围设置优化时段(即优化程序启动的时段)。这意味着必须考虑到,一旦启动测试程序终端,就将汲取历史数据,并且有可能出现经纪人并没有指定时间段的必要历史数据。要验证本文开头的假设,您需要 24 小时稳定的互联网连接。
已开发的自动优化程序位于以下随附文件中:auto_optimization.mqh - 库自身、MACD Sample_1.mq4 - MetaTrader 4 客户终端标准交付内容中略有更改的 Expert Advisor。
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...