Expert Advisor 的测试和优化

Michael | 12 四月, 2016

简介

本文详细介绍在 MetaTrader 4 策略测试程序中测试和优化 Expert Advisor 的过程。 此类信息的重要性以及对此出版物的需求不容低估。 很多仅刚刚入门 MetaTrader 4 交易平台的用户对使用 Expert Advisor 时需要做些什么以及应当如何做还只有很模糊的认识。

论坛上几乎每天(确实如此)都充斥着新用户提出的各种问题:如何在终端上安装 Expert Advisor,如何开始使用 Expert Advisor,什么是优化以及可如何在 MetaTrader 4 策略测试程序中执行优化,什么前向测试等。

本文为所有这些问题提供了简单而清晰的解答,并通过具体的示例提供了稍微更加专业的方法来处理这些问题。 随着本文的展开,您可以找到相关文章和 MQL4.community 网站页面的链接,以便进一步更详细地了解测试和优化过程。



Expert Advisor 的测试和优化

我们来考虑使用 Expert Advisor 时需要执行的第一步。 为了便于说明,我们将采取一个简单的 Expert Advisor - 我们修改过的移动平均 Expert Advisor。 与在 MetaTrader 4 交易平台中构建的初始版本相比,我们的版本在一条移动平均线与价格交叉时执行建仓,并在另一条移动平均线与价格反向交叉时平仓,但周期有所不同。 近来由于此程序修改很受欢迎,因此,我们的版本还进行了加强,包含用于在市场执行条件下进行建仓的函数。 Expert Advisor 的代码如下:

//+------------------------------------------------------------------+
//|                                      Moving Average_Мodified.mq4 |
//|                      Copyright © 2013, MetaQuotes Software Corp. |
//|                                      Modified by BARS            |
//+------------------------------------------------------------------+
#define MAGICMA  20050610
//-----------------------------------------
extern int     StopLoss           = 500;
extern int     TakeProfit         = 500;
extern double  Lots               = 0.1;
extern double  MaximumRisk        = 0.02;
extern double  DecreaseFactor     = 3;
extern int     MovingPeriod_Open  = 12;
extern int     MovingPeriod_Close = 21;
extern int     MovingShift        = 1;
extern color   BuyColor           = clrCornflowerBlue;
extern color   SellColor          = clrSalmon;
//---
double SL=0,TP=0;
//-- Include modules --
#include <stderror.mqh>
#include <stdlib.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void start()
  {
//--- If there are more than 100 bars in the chart and the trade flow is free
   if(Bars<100 || IsTradeAllowed()==false)
      return;
//--- If the calculated lot size is in line with the current deposit amount
   if(CalculateCurrentOrders(Symbol())==0)
      CheckForOpen();   // start working
   else
      CheckForClose();  // otherwise, close positions
  }
//+------------------------------------------------------------------+
//| Determines open positions                                        |
//+------------------------------------------------------------------+
int CalculateCurrentOrders(string symbol)
  {
   int buys=0,sells=0;
   for(int i=0; i<OrdersTotal(); i++)
     {
      if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false)
         break;
      if(OrderSymbol()==Symbol() && OrderMagicNumber()==MAGICMA)
        {
         if(OrderType()==OP_BUY) buys++;
         if(OrderType()==OP_SELL) sells++;
        }
     }
//---- return orders volume
   if(buys>0)
      return(buys);
   else
      return(-sells);
  }
//+------------------------------------------------------------------+
//| Calculates the optimum lot size                                  |
//+------------------------------------------------------------------+
double LotsOptimized()
  {
   double lot=Lots;
   int    orders=HistoryTotal(); // history orders total
   int    losses=0;              // number of loss orders without a break
//---- select lot size
   lot=NormalizeDouble(AccountFreeMargin()*MaximumRisk/1000.0,1);
//---- calcuulate number of loss orders without a break
   if(DecreaseFactor>0)
     {
      for(int i=orders-1;i>=0;i--)
        {
         if(OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)==false)
           {
            Print("Error in history!");
            break;
           }
         if(OrderSymbol()!=Symbol() || OrderType()>OP_SELL)
            continue;
         //----
         if(OrderProfit()>0)
            break;
         if(OrderProfit()<0)
            losses++;
        }
      if(losses>1)
         lot=NormalizeDouble(lot-lot*losses/DecreaseFactor,1);
     }
//---- return lot size
   if(lot<0.1)
      lot=0.1;
   return(lot);
  }
//+------------------------------------------------------------------+
//| Position opening function                                        |
//+------------------------------------------------------------------+
void CheckForOpen()
  {
   double ma;
   int    res;
//---- go trading only for first tiks of new bar
   if(Volume[0]>1)
      return;
//---- get Moving Average 
   ma=iMA(NULL,0,MovingPeriod_Open,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//---- sell conditions
   if(Open[1]>ma && Close[1]<ma)
     {
      if(StopLoss>0)
         SL=Bid+Point*StopLoss;
      if(TakeProfit>0)
         TP=Bid-Point*TakeProfit;
      res=WHCOrderSend(Symbol(),OP_SELL,LotsOptimized(),Bid,3,SL,TP,"Moving Average",MAGICMA,0,SellColor);
      if(res<0)
        {
         Print("Error when opening a SELL order #",GetLastError());
         Sleep(10000);
         return;
        }
     }
//---- buy conditions
   if(Open[1]<ma && Close[1]>ma)
     {
      SL=0;TP=0;
      if(StopLoss>0)
         SL=Ask-Point*StopLoss;
      if(TakeProfit>0)
         TP=Ask+Point*TakeProfit;
      res=WHCOrderSend(Symbol(),OP_BUY,LotsOptimized(),Ask,3,SL,TP,"Moving Average",MAGICMA,0,BuyColor);
      if(res<0)
        {
         Print("Error when opening a BUY order #",GetLastError());
         Sleep(10000);
         return;
        }
     }
//----
  }
//+------------------------------------------------------------------+
//| Position closing function                                        |
//+------------------------------------------------------------------+
void CheckForClose()
  {
   double ma;
//---- go trading only for first tiks of new bar
//(start working at the first tick of the new bar)
   if(Volume[0]>1) return;
//---- get Moving Average 
   ma=iMA(NULL,0,MovingPeriod_Close,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//----
   for(int i=0;i<OrdersTotal();i++)
     {
      if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
      if(OrderMagicNumber()!=MAGICMA || OrderSymbol()!=Symbol()) continue;
      //---- check order type 
      if(OrderType()==OP_BUY)
        {
         if(Open[1]>ma && Close[1]<ma)
            OrderClose(OrderTicket(),OrderLots(),Bid,3,BuyColor);     break;
        }
      if(OrderType()==OP_SELL)
        {
         if(Open[1]<ma && Close[1]>ma)
            OrderClose(OrderTicket(),OrderLots(),Ask,3,SellColor);     break;
        }
     }
  }
//+-------------------------------------------------------------------+
//| Opens positions in Market Execution mode                          |
//+-------------------------------------------------------------------+
int WHCOrderSend(string    symbol,
                 int       cmd,
                 double    volume,
                 double    price,
                 int       slippage,
                 double    stoploss,
                 double    takeprofit,
                 string    comment,
                 int       magic,
                 datetime  expiration,
                 color     arrow_color)
  {
   int ticket=OrderSend(symbol,cmd,volume,price,slippage,0,0,comment,magic,expiration,arrow_color);
   int check=-1;
   if(ticket>0 && (stoploss!=0 || takeprofit!=0))
     {
      if(!OrderModify(ticket,price,stoploss,takeprofit,expiration,arrow_color))
        {
         check=GetLastError();
         if(check!=ERR_NO_MQLERROR)
            Print("OrderModify error: ",ErrorDescription(check));
        }
     }
   else
     {
      check=GetLastError();
      if(check!=ERR_NO_ERROR)
         Print("OrderSend error: ",ErrorDescription(check));
     }
   return(ticket);
  }
//+------------------------------------------------------------------+

下载 Moving Average_Мodify Expert Advisor 的附加文件。 下载文件必须位于 MetaTrader 4 交易终端的 \MQL4\experts\ 文件夹中。 例如,如果在 C:\Program Files\MetaTrader 4\ 下安装了终端,将需要将 Expert Advisor 放置在 C:\Program Files\MetaTrader 4\MQL4\experts\ 中。 然后,启动(重启)终端。

Expert Advisor 的文件 - Moving Average_Мodify - 应出现在 Navigator 窗口 ->Expert Advisors 中的左侧。

从 MetaTrader 4 交易终端的菜单中启动策略测试程序 (Ctrl+R)。 进一步的步骤如下(请参见以下屏幕截屏):

关于此模式,我们应简单地谈几句。 在此模式下工作时,将仅在建立下一个新的条柱时才会收到建仓和平仓的信号。 这是为我们的 Expert Advisor 的操作算法提供的入场模式! 有关建模模式的更多信息,可在标题为“策略测试程序: 测试期间的建模模式”

我们来进入测试阶段。 现在,我们将在 Expert Advisor 设置中使用默认参数。 单击策略测试程序右下角的“开始”,一旦进度条到达结尾部分,准备查看测试结果。 要查看结果,打开策略测试程序的“报告”选项卡。 或者,可以打开测试通过的余额图表 -“图表”选项卡(请参见上述屏幕截图)。

遗憾的是,结果很让人沮丧。 我们的初始保证金逐步趋于零... 我们可以在“报告”选项卡中查看此测试通过的广义统计数据。 新用户可能会发现难于理解文章“Expert 测试报告中的数字表示的含义”。

总结: 我们的 Expert Advisor 使用 Expert Advisor 设置中默认设置的参数交易指定交易品种和时间范围时,无法获得收益。 我们应找到能够让 Expert Advisor 可获利的参数值!

换言之,“优化” 意味着为选定交易系统找到能够让我们获得最好的结果的参数(请参见“如何不掉入优化陷阱?”文章)。

在优化模式下,Expert Advisor 使用不同的外部变量反复运行,这些外部变量因我们在 Expert Advisor 设置中设置的方案的不同而不同:开始值-步长-停止值。 MetaTrader 4 策略测试程序允许同时优化多个参数。 现在,我们需要设置优化参数。 因此,单击“Expert 属性”按钮(右上按钮)并打开“输入”选项卡:

在左侧,选中我们要优化的参数的框,并分别在“开始”、“停止”和“步长”列中设置它们的开始值、停止值和步长。 上述屏幕截图显示了我们要优化(查找)的参数:

优化的开始值和停止值是基于常事设置的。 对于 EURUSD Н1,设置为上述屏幕截图中显示的值,非常合理。 请记住,在这种情况下,我们在 MetaTrader 4 中有五位数的报价。

在“仅开盘价”模式下,优化速度很快。 因此,我们可以承受同时优化四个选定参数。 单击“确定”关闭“Expert 属性”窗口,并在策略测试程序的右侧选中“优化”框。 然后,单击策略测试程序右下角的“开始”。 现在,正在进行优化。

您可以使用策略测试程序的“优化结果”和“优化图表”选项卡目测监控整个过程。 如果显示绿色区域而不是优化图表,我们建议您在出现的窗口中取消选中“二维平面 - 空间”选项。

优化完成后(当进度条到达结尾部分时),打开策略测试程序的“优化结果”选项卡,并通过向上拖动上边框调整大小。 然后,单击第二列“获利”以降序排列通过结果。 我们将能够看到下表:

如屏幕截图所示,Expert Advisor 参数的一些组合产生了 $4,658 的最高获利。 然而,不要急于在我们的 Expert Advisor 中设置这些参数。 假定此获利值,但亏损太大了! 首先,我们应使用结果来选择一个能够实现最大获利与合理亏损的最合适的组合。 这就是为什么我们将取通过 2406 的原因,它的获利值为 $2,715,最小相对亏损为 19,83%。

右键单击此行。 将打开一个窗口,您应单击其中的“设置输入参数”。 在这种情况下,Expert Advisor 中将自动设置选定参数。 单击策略测试程序右下角的“开始”按钮。 在测试完成(通过)后,打开“报告”选项卡以检查测试结果。

这些结果的稳定性如何? Expert Advisor 实时交易的结果会与在策略测试程序中运行时所显示的结果相同吗? 这些问题在一定程度上可以在所谓的前向测试期间获得解答!

让我来提醒您,我们设置了从 2008 年 8 月 1 日到 2009 年 5 月 1 日的测试和优化周期。 我们有意回避了从 2008 年 8 月到当前日期的时间间隔的优化,在指定的时间间隔内训练 Expert Advisor。 现在,是时候进行测试了,看看 Expert Advisor 的能力。

即,在优化周期以外 - 从 2009 年 5 月 2 日到 6 月 8 日使用相同的参数测试 Expert Advisor! 按照惯例将此测试运行称为前向测试”,与之前的测试 - 回溯测试正好相反。

前向测试结果允许我们更自信和可观地评估我们的 Expert Advisor 实时交易的预期。 我不会让您再等下去,最后按照如上所述执行前向测试。 为此,我们需要在策略测试程序中设置从 2009 年 5 月 2 日到现在(6 月 8 日)的时间间隔,然后单击“开始”! 结果如下:

多么令人惊讶的结果! 这种情况在首次运行时非常罕见,完全意料不到。 前向测试显示非常好的获利,然而并不是没有亏损。 在理想情况下,应在策略测试程序的可视化模式下检查导致最大亏损值的亏损交易。本文的任何读者都可以重复上述步骤(在 MetaTrader 4 Alpari),并查看显示的所有结果是否完全公平。 注意,使用具有更高获利性的优化参数和更大亏损(3,061 和 771.95)的类似前向测试产生了更为糟糕的结果。

但不要报太大希望。 对 Expert Advisor 的性能进行更为客观的评估需要基于历史数据执行一系列此类前向测试。 一系列标题为“基于常见交易系统的 Expert Advisor 和交易机器人优化的惊人作用”的文章中提供了有关所有必要步骤和总体结果评估的充分而清晰的说明。

在这里,我们旨在让读者熟悉使用 Expert Advisor 的首要基本步骤。 让我们回到我们在运行前向测试时看到的高亏损值。 图表显示在 2009 年 5 月 18 号以后 - 交易 18 至 20 发生了亏损。 我们将尝试在策略测试程序的可视化模式下监控此情况,同时为了清晰起见,将在“模型”字段中将模式更改为“每次变动”。 可视化模式允许我们控制时速(即报价馈送速率)。 设置始于 2009 年 5 月 18 日的日期,然后单击“开始”。

下面是我们在历史数据的亏损部分看到的内容:

此重大亏损是三个连续的亏损卖出交易所导致的。 它们不在趋势的方向上,这立即揭示了我们的 Expert Advisor 的主要缺点:Expert Advisor 的运行算法过于原始。 我们需要至少添加一个基本趋势过滤器并/或按照与文章“非标准自动交易”中建议的方式大体类似的方式区分长期交易和短期交易的进场机制。 然而,这些主题不在本文的范围之内。



总结

我们介绍了在策略测试程序中测试和优化 Expert Advisors 的基本工作实践。 要更深入地了解和进一步体验此类测试,您可以查看“如何实施您自己的优化标准”和“样本外的优化和测试”文章。

总之,我们来考虑新用户在测试 EA 时通常会遇到的一些最常见的问题。

1. 在不同的交易中心进行相同的测试的结果却有所差异,这是什么原因导致的?

不同中心的测试结果的差异是由于报价差异而导致的。 每个经纪人都有其自己的报价馈送来源。 这会产生价格差异,之后会在测试结果中体现这一点。

2. 为什么在同一个交易中心进行的相同的测试产生了不同结果?

在同一个交易中心获得不同的结果可能是由于多个原因导致的,其中一个最常见的原因是:
浮动范围 - 它对结果有相当大的影响,尤其是当在较低的时间范围和使用“每次变动”模式进行测试时。 MetaTrader 4 Strategy Tester 存储最后一个范围值。 此范围在下一次运行过程中可能会发生变化,因而,测试结果会有所差异。

3. 在“每次变动”和“仅开盘价”模式下进行测试的结果有所差异,这是什么原因导致的?

如果 Expert Advisor 基于变动操作,它将在每次变动时获得并分析数据,而如果基于开盘价操作,它将仅在出现条柱时才获得数据和发送信号,因而,出现了这种情况... 总结: 必须确定 Expert Advisor 如何在适当的模式下操作和启动策略测试程序。

4. 为什么 Expert Advisor 没有建任何仓?

首先,您应打开策略测试程序日志。 它将显示可能的错误代码。 代码说明可在错误代码部分中找到。

希望已经解答了 MetaTrader 4 交易平台的新用户的很多问题。 新用户有机会通过独立执行(重复)上述步骤和掌握基本技能来获取一些经验以便将来试验之用!

本文使用文中参考的资料以及 I.Kim 网站中如何运行 Expert Advisor?网页的信息编写而成。