请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化平台 帖子:3364694 新帖:15

如何实施你自己的优化标准

舞蹈家发表于:4 月 17 日 19:29回复(1)

简介

我们不时会听到关于在 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。


全部回复

0/140

量化课程

    移动端课程