多币种测试

我们知道,MetaTrader 5 测试程序允许你测试交易多种金融工具的策略。从纯技术上讲,受制于计算机硬件资源,模拟所有可用金融工具同时交易是可能的。

测试这种策略对测试程序提出了几个额外的技术要求:

  • 生成所有金融工具的分时报价序列
  • 计算所有金融工具的指标
  • 计算保证金要求并模拟所有金融工具的其他交易条件

首次访问历史时,测试程序会自动从终端下载所需金融工具的历史。如果终端不包含所需的历史,则将依次向交易服务器请求。因此,在测试多币种 EA 交易之前,建议在终端的 Market Watch 中选择所需的金融工具,并下载所需的数据量。

代理应上传缺失的历史,并留有少量保证金,以便在测试时为计算指标或由 EA 交易复制提供必要的数据。从交易服务器下载的历史的最小数量取决于时间范围。例如,对于 D1 和更短的时间,为 1 年。换言之,初始历史是从相对于测试程序开始日期的前一年开始下载的。如果要求从 1 月 1 日开始测试,将提供至少 1 年的历史,如果要求从 12 月开始测试,则最多可提供近 2 年的历史。对于周时间范围,需要 100 根柱线的历史,即大约 2 年(一年有 52 周)。对于每月一次的测试,代理可请求 100 个月的历史(等于约 8 年:12 个月* 8 年= 96)。在任何情况下,在低于工作时间的时间范围内,可用的柱线数量将按比例增加。如果现有数据不足以满足预定义深度的初步历史,这一事实将记录在测试日志中。

无法配置(更改)此行为。因此,如果你需要从一开始就提供当前时间范围的特定数量的历史柱线,你应为测试设置一个更早的开始日期,然后在 EA 交易代码中“等待”所需的交易开始日期或足够数量的柱线。在此之前,你应跳过所有事件。

测试程序还可以模拟自己的 Market Watch,该程序可以从中获得金融工具信息。默认情况下,在测试开始时,测试程序的 Market Watch 只包含一个交易品种:测试开始时的交易品种。通过 API 函数访问所有附加交易品种时,这些交易品种会自动添加到测试程序的 Market Watch 中。在第一次访问来自 MQL 程序的“第三方”交易品种时,测试代理应使交易品种数据与终端同步。

在下列情况下,可以访问附加交易品种的数据:

  • 使用交易品种/时间范围对的技术指标 iCustomIndicatorCreate
  • 查询另一个交易品种的 Market Watch
    • SeriesInfoInteger
    • Bars
    • SymbolSelect
    • SymbolIsSynchronized
    • SymbolInfoDouble
    • SymbolInfoInteger
    • SymbolInfoString
    • SymbolInfoTick
    • SymbolInfoSessionQuote
    • SymbolInfoSessionTrade
    • MarketBookAdd
    • MarketBookGet
  • 使用以下函数查询交易品种/时间范围对时间序列:
    • CopyBuffer
    • CopyRates
    • CopyTime
    • CopyOpen
    • CopyHigh
    • CopyLow
    • CopyClose
    • CopyTickVolume
    • CopyRealVolume
    • CopySpread

此外,你可以通过调用 OnInit 处理程序中的 SymbolSelect 函数来显式请求所需交易品种的历史。历史数据会在 EA 交易测试开始之前提前加载。

第一次访问另一个交易品种时,测试过程会停止,交易品种/周期对历史可从终端下载给测试代理。此时也会启用分时报价序列的生成。

每个金融工具根据设定的分时报价生成模式生成自己的分时报价序列。

实现多币种 EA 交易时,不同交易品种的柱线同步特别重要,因为这会影响计算的正确性。当所有使用交易品种的最后一个柱线具有相同的打开时间时,状态被认为是同步的。

测试程序可为每种金融工具生成并播放其分时报价序列。同时,不管其他金融工具上的柱线是如何打开的,每个金融工具上都会打开一个新的柱线。这意味着在测试多币种 EA 交易时,可能会出现一种情况(这种情况经常发生),即一个新的柱线已经在一个金融工具上打开,但在另一个金融工具上还没有打开。

例如,如果我们正在测试一个使用 EURUSD 交易品种数据的 EA 交易,并且为该交易品种打开了一个新的每小时 K 线,我们将会收到 OnTick 事件。但与此同时,不能保证 GBPUSD 会有一个我们可能也会使用的新 K 线。

因此,同步算法意味着你需要检查所有金融工具的报价,并等待最后几根柱线的开盘时间相等。

只要使用了真实分时报价、所有分时报价的模拟或 OHLC M1 测试模式,就不会引起任何问题。使用这些模式,可在一个柱线上生成足够数量的分时报价,以等待来自不同交易品种的 K 线同步时刻。只需完成 OnTick 函数,并在下一条分时报价处检查 GBPUSD 上一个新柱线是否出现。但是当在“仅开盘价”模式下进行测试时,将不会有其他分时报价,因为每个柱线只调用一次 EA 交易,并且这种模式似乎不适合测试多币种 EA 交易。事实上,测试程序允许你使用 Sleep 函数(循环)或计时器来检测一个新柱线打开另一个交易品种的时刻。

首先,我们考虑一个 EA 交易示例 SyncBarsBySleep.mq5,这个示例演示了通过 Sleep 来同步柱线。

一对输入参数允许你以秒为单位设置等待其他交易品种柱线以及其他交易品种 (OtherSymbol) 名称的 Pause 时长,该交易品种必须不同于图表交易品种。

input uint Pause = 1;                   // Pause (seconds)
input string OtherSymbol = "USDJPY";

为了确定柱线打开时间的延迟模式,我们介绍一个简单的类 BarTimeStatistics。该类包含一个字段,用于计算柱线总数 (total) 和最初没有同步的柱线数 (late),即另一个交易品种的延迟。

class BarTimeStatistics
{
public:
   int total;
   int late;
   
   BarTimeStatistics(): total(0), late(0) { }
   
   ~BarTimeStatistics()
   {
      PrintFormat("%d bars on %s was late among %d total bars on %s (%2.1f%%)",
         lateOtherSymboltotal_Symbollate * 100.0 / total);
   }
};

这个类的对象可在其析构函数中打印接收到的统计数据。因为我们将使这个对象成为静态的,所以可在测试结束时打印出报告。

如果在测试程序中选择的分时报价生成模式与开盘价不同,我们可使用之前介绍的 getTickModel 函数检测到这一点,并将返回一个警告。

void OnTick()
{
   const TICK_MODEL model = getTickModel();
   if(model != TICK_MODEL_OPEN_PRICES)
   {
      static bool shownOnce = false;
      if(!shownOnce)
      {
         Print("This Expert Advisor is intended to run in \"Open Prices\" mode");
         shownOnce = true;
      }
   }

接下来,OnTick 提供了有效的同步算法。

   // time of the last known bar for _Symbol
   static datetime lastBarTime = 0;
   // attribute of synchronization
   static bool synchronized = false;
   // bar counters
   static BarTimeStatistics stats;
   
   const datetime currentTime = iTime(_Symbol_Period0);
   
   // if it is executed for the first time or the bar has changed, save the bar
   if(lastBarTime != currentTime)
   {
      stats.total++;
      lastBarTime = currentTime;
      PrintFormat("Last bar on %s is %s"_SymbolTimeToString(lastBarTime));
      synchronized = false;
   }
   
   // time of the last known bar for another symbol
   datetime otherTime;
   bool late = false;
   
   // wait until the times of two bars become the same
   while(currentTime != (otherTime = iTime(OtherSymbol_Period0)))
   {
      late = true;
      PrintFormat("Wait %d seconds..."Pause);
      Sleep(Pause * 1000);
   }
   if(latestats.late++;
   
   // here we are after synchronization, save the new status
   if(!synchronized)
   {
      // use TimeTradeServer() because TimeCurrent() does not change in the absence of ticks
      Print("Bars are in sync at "TimeToString(TimeTradeServer(),
         TIME_DATE | TIME_SECONDS));
      // no longer print a message until the next out of sync
      synchronized = true;
   }
   // here is your synchronous algorithm
   // ...
}

让我们设置测试程序来对 EURUSD, H1 运行 EA 交易,其为流动性最强的金融工具。让我们使用默认的 EA 交易参数,即 USDJPY 为“其他”交易品种。

作为测试的结果,日志将包含以下条目(我们有意显示与 USDJPY 历史下载相关的日志,这发生在第一次 iTime 调用期间)。

2022.04.15 00:00:00 Last bar on EURUSD is 2022.04.15 00:00

USDJPY: load 27 bytes of history data to synchronize in 0:00:00.001

USDJPY: history synchronized from 2020.01.02 to 2022.04.20

USDJPY,H1: history cache allocated for 8109 bars and contains 8006 bars from 2021.01.04 00:00 to 2022.04.14 23:00

USDJPY,H1: 1 bar from 2022.04.15 00:00 added

USDJPY,H1: history begins from 2021.01.04 00:00

2022.04.15 00:00:00 Bars are in sync at 2022.04.15 00:00:00

2022.04.15 1:00:00 Last bar on EURUSD is 2022.04.15 1:00

2022.04.15 01:00:00 Wait 1 seconds...

2022.04.15 1:00:01 Bars are in sync at 2022.04.15 1:00:01

2022.04.15 2:00:00 Last bar on EURUSD is 2022.04.15 2:00

2022.04.15 2:00:00 Wait 1 seconds...

2022.04.15 2:00:01 Bars are in sync at 2022.04.15 2:00:01

...

2022.04.20 23:59:59 95 bars on USDJPY was late among 96 total bars on EURUSD (99.0%)

你可以看到 USDJPY 柱线有规律的延迟情况。如果你在测试程序设置中选择 USDJPY, H1,在 EA 交易参数中选择 EURUSD,将得到相反的数据。

2022.04.15 00:00:00 Last bar on USDJPY is 2022.04.15 00:00

EURUSD: load 27 bytes of history data to synchronize in 0:00:00.002

EURUSD: history synchronized from 2018.01.02 to 2022.04.20

EURUSD,H1: history cache allocated for 8109 bars and contains 8006 bars from 2021.01.04 00:00 to 2022.04.14 23:00

EURUSD,H1: 1 bar from 2022.04.15 00:00 added

EURUSD,H1: history begins from 2021.01.04 00:00

2022.04.15 00:00:00 Bars are in sync at 2022.04.15 00:00:00

2022.04.15 1:00:00 Last bar on USDJPY is 2022.04.15 1:00

2022.04.15 01:00:00 Wait 1 seconds...

2022.04.15 1:00:01 Bars are in sync at 2022.04.15 1:00:01

2022.04.15 2:00:00 Last bar on USDJPY is 2022.04.15 2:00

2022.04.15 2:00:00 Wait 1 seconds...

2022.04.15 2:00:01 Bars are in sync at 2022.04.15 2:00:01

...

2022.04.20 23:59:59 23 bars on EURUSD was late among 96 total bars on USDJPY (24.0%)

此处,在大多数情况下没有必要等待:EURUSD 的柱线在 USDJPY 的柱线形成时就已经存在了。

还有另一种方法来同步柱线:使用计时器。这种 EA 交易的一个示例 SyncBarsByTimer.mq5 也包含在本书中。请注意,通常情况下,计时器事件发生在柱线内(因为恰好到达起点的概率非常低)。因此,柱线几乎总是同步的。

我们还可以使用间谍指标 EventTickSpy.mq5提醒你同步柱线的可能性,但是该指标是基于自定义事件的,只有在可视化测试时才起作用。此外,对于需要对每条分时报价做出响应的指标,使用 #property tester_everytick_calculate 指令非常重要。我们已经在 测试指标 一节讨论过了,我们将在具体的 测试程序指令章节中再次回顾。