简介
我们不时会听到关于在 MT4 测试程序中扩展优化标准集的必要性的意见。可以想见,无论开发者添加什么标准,始终会有用户和情形需要其他标准。这个问题是否能在 MQL4 和 MetaTrader 内部解决呢?答案是肯定的。本文通过标准 Expert Advisor 移动平均线的例子演示自定义优化标准的使用。这里的标准是获利/亏损关系。
Expert Advisor
让我们从优化标准开始。关于其计算,我们需要在测试最大盈余和亏损时追踪。为了不依赖于 Expert Advisor 运行的逻辑,我们在 start() 函数的开始处添加现有的代码串。
if (AccountEquity() > MaxEqu) MaxEqu = AccountEquity(); if (MaxEqu-AccountEquity() > MaxDD) MaxDD = MaxEqu-AccountEquity();
为了处理最后的价格变动,它们应该在 deinit() 中重复。之后可以计算优化标准的值。
Criterion = (AccountBalance()-StartBalance)/MaxDD;
现在可以开始主体部分——优化过程的维护。我们有一个问题——MQL4 没有决定优化结束的内置方法。我所知道的唯一方法是所谓的“计数器的优化”。含义如下:Expert Advisor 的唯一可更换参数是特殊的外部变量——计数器。这里仍有一个严重的后果——我们丧失了以标准方式更改 Expert Advisor 的真实参数的可能性,应该自行提供。另一个缺陷是来自朋友的优化缓存变成了敌人。但结果肯定了这种方法,所以我们将继续。
我们来添加外部变量:
extern int Counter = 1; // Counter of the tester's running extern int TestsNumber = 200; // he check digit - total number of runs extern int MovingPeriodStepsNumber = 20; // Number of optimization steps for MovingPeriod extern int MovingShiftStepsNumber = 10; // Number of optimization steps for MovingShift extern double MovingPeriodLow = 150; // Lower limit of the optimization range for MovingPeriod extern double MovingShiftLow = 1; // Lower limit of the optimization range for MovingShift extern double MovingPeriodStep = 1; // Optimization step for MovingPeriod extern double MovingShiftStep = 1; // Optimization step for MovingShift
首先是计数器。下一个变量是检查变量(和信息)。然后为用于优化的移动平均线的两个内置变量指定步数、下限和优化步长。可以看到一些冗余:如果打算进行完整的检验(我们接下来即如此做),MovingPeriodStepsNumber 和 MovingShiftStepsNumber 之积应该等于 TestsNumber。
每次测试后,Expert Advisor 彻底结束运行,下一次运行可以视为再生。我们有两种方式来组织“遗传存储”:全局变量和单独的文件。我们两种都会使用。
让我们来修改 init() 函数:
int init() { if (IsTesting() && TestsNumber > 0) { if (GlobalVariableCheck("FilePtr")==false || Counter == 1) { FilePtr = 0; GlobalVariableSet("FilePtr",0); } else { FilePtr = GlobalVariableGet("FilePtr"); } MovingPeriod = MovingPeriodLow+((Counter-1)/MovingShiftStepsNumber)*MovingPeriodStep; MovingShift = MovingShiftLow+((Counter-1)%MovingShiftStepsNumber)*MovingShiftStep; StartBalance = AccountBalance(); MaxEqu = 0; MaxDD = 0; } return(0); }
我们的补充仅位于测试程序运行条件内部和非零的 TestsNumber。因此,TestsNumber=0 会将 Expert Advisor 变成一个标准移动平均线。在讨论优化过程时,我们必须尽可能的加快进程。这就是为什么代码以使用全局变量管理全程(贯穿测试程序运行)文件指针的规定开始。然后计算可更换参数的值,并初始化用于优化标准计算的变量。
主体工作应该在函数 deinit() 完成。在测试结果上,我们将在文本文件中保存优化标准的值、已优化的参数值和测试运行的数量。在优化结束后,结果将根据优化标准分类并保存在相同的文件。所以,我们需要处理三种情形:第一次启动、最后一次启动和所有其他启动。我们使用测试程序运行计数器对其进行区分。
处理第一次启动:
if (Counter == 1) { // First run, create/initialize a datafile. h=FileOpen("test.txt",FILE_CSV|FILE_WRITE,';'); FileWrite(h,Criterion,MovingPeriod,MovingShift,Counter); // Remember the position of the file pointer after writing in the global variable FilePtr = FileTell(h); GlobalVariableSet("FilePtr",FilePtr); FileClose(h);
处理其他启动的特殊性在于有新数据添加到文件:
} else { // After the first start is processed, the data are added into the file h=FileOpen("test.txt",FILE_CSV|FILE_READ|FILE_WRITE,';'); // It is time to use the file pointer written in the global variable FilePtr = GlobalVariableGet("FilePtr"); FileSeek(h,FilePtr, SEEK_SET); FileWrite(h,Criterion,MovingPeriod,MovingShift,Counter); // Remember the file pointer position once again FilePtr = FileTell(h); GlobalVariableSet("FilePtr",FilePtr);
现在我们来处理最后一次启动:
if (Counter == TestsNumber) { ArrayResize(Data,TestsNumber); // Returns the file pointer to the beginning FileSeek(h,0,SEEK_SET); // Read from the file the results of all testings int i = 0; while (i<TestsNumber && FileIsEnding(h)== false) { for (int j=0;j<4;j++) { Data[i][j]=FileReadNumber(h); } i++; } // Sort the array according to our optimization criterion ArraySort(Data,WHOLE_ARRAY,0,MODE_DESCEND); // Now let us arrange the results. Reopen the file FileClose(h); h=FileOpen("test.txt",FILE_CSV|FILE_WRITE,' '); FileWrite(h," Criterion"," MovingPeriod"," MovingShift"," Counter"); for (i=0;i<TestsNumber;i++) { FileWrite(h,DoubleToStr(Data[i][0],10)," ",Data[i][1]," ",Data[i][2]," ",Data[i][3]); }
数组最初初始化为 double Data[][4]。就是这些。我们来改进:
GlobalVariableDel("FilePtr"); } FileClose(h); } }
编译、打开测试程序并选择 Expert Advisor。然后打开 Expert Advisor 的属性表并检查四个位置:
- MovingPeriodStepsNumber 和 MovingShiftStepsNumber 之积必须等于 TestsNumber。
- 优化必须只对计数器进行,
- 优化范围必须从 1 到 TestsNumber,步长为 1。
-必须禁用遗传算法。
开始优化。结束后,前往 [Meta Trader]\tester\files 文件夹并在 test.txt 文件查看结果。作者从 2004 年年中在开盘价上对 EURUSD_H1 处理,得到以下结果:
现在让我们回到缓存是我们的敌人的话题。实际上,当我们从缓存得到测试结果时,函数 init() 和 deinit() 没有启动。因此,在优化重新启动时全部或部分变体未予说明。此外,实际运行数将少于 TestsNumber,Data 数组将包含一些零。作者建议两种消除“缓存效应”的方法:重新编译 Expert Advisor 或关闭/暂停/打开测试程序窗口。
缓存干扰可以通过独立的测试运行计数检测出。对于使用特殊的全局变量组织这种计数有三个注释插入,如所附的 EA 代码中所示:
// Code of the independent counter if (GlobalVariableCheck("TestsCnt")==false || Counter == 1) { TestsCnt = 0; GlobalVariableSet("TestsCnt",0); } else { TestsCnt = GlobalVariableGet("TestsCnt"); }
// Code of the independent counter TestsCnt++; GlobalVariableSet("TestsCnt",TestsCnt);
// Code of the independent counter GlobalVariableDel("TestsCnt");
还有最后一点。留心的读者一定注意到:我们不使用 FilePtr 变量(以及伴随的全局变量)也无妨——数据写在文件最后,从头读取。那么它有什么用呢?答案如下:该 Expert Advisor 用于优化维护方法的演示。该方法可以使用之前测试的结果管理实时运行。在这里,全程文件指针以及独立计数器会非常有用。作为要求使用之前实时结果的任务示例,我们可以对样本外测试的管理和自己的遗传算法实施进行命名。
总结
对该问题的兴趣源自于以下论坛的话题:http://forum.mql4.com。