跟踪柱线形成

上一节讨论的 IndUnityPercent.mq5指标由于使用Close 价,会在每个分时报价时对最后一根柱线重新计算。某些指标和 EA 交易会特别设计为更高效的模式,即每根柱线仅计算一次。例如,我们可以使用开盘价来计算 Unity 公式,在这种情况下跳过分时报价是合理的。检测新柱线的方法有几种:

  • 记录当前 0 号柱线的时间(通过 OnCalculatetime 参数 – time[0],或通用 iTime(symbol, period, 0)),并等待其发生变化
  • 记住柱线数量 rates_total(或通过 iBars(symbol, period)),并在数量增加 1 时响应(如果柱线数量增减变化异常,可能意味着历史数据被修改)
  • 等待出现分时报价成交量等于 1 的柱线(即柱线的第一个分时报价)

然而,由于该指标的多货币特性,新柱线形成的概念变得不再那么明确。

在每个交易品种上,新柱线会在其自身分时报价到达时生成,而不同交易品种的分时报价到达时间通常不同。在这种情况下,指标开发者必须确定如何处理:是等待所有交易品种出现相同时间的柱线后再进行计算,还是在任意一个交易品种出现新柱线后,对最新柱线多次重新计算指标。

在本节中,我们将介绍一个简单的类 MultiSymbolMonitor(请参阅文件 MultiSymbolMonitor.mqh),用于根据给定的交易品种列表跟踪新柱线的形成。

所需的时间范围可通过类构造函数传入。默认情况下,该类会跟踪程序运行所在的当前图表的时间范围。

class MultiSymbolMonitor
{
protected:
   ENUM_TIMEFRAMES period;
   
public:
   MultiSymbolMonitor(): period(_Period) {}
   MultiSymbolMonitor(const ENUM_TIMEFRAMES p): period(p) {}
   ...

要存储被跟踪交易品种的列表,我们将使用上一节中的辅助类MapArray。在该数组中,我们将编写 [symbol name;timestamp of the last bar] 对,即模板类型<string,datetime>attach方法用于填充该数组。

protected:
   MapArray<string,datetimelastTime;
...
public:
   void attach(const string symbol)
   {
      lastTime.put(symbolNULL);
   }

对于给定数组,该类可以在 check方法中通过循环调用 iTime 函数来更新和检查交易品种的时间戳。

   ulong check(const bool refresh = false)
   {
      ulong flags = 0;
      for(int i = 0i < lastTime.getSize(); i++)
      {
         const string symbol = lastTime.getKey(i);
         const datetime dt = iTime(symbolperiod0);
        
         if(dt != lastTime[symbol]) // are there any changes?
         {
            flags |= 1 << i;
         }
         
         if(refresh// update timestamp
         {
            lastTime.put(symboldt);
         }
      }
      return flags;
   }

调用代码应自行决定何时调用check方法,通常是在分时报价到达时或通过计时器触发。严格来说,这两种方式都无法即时响应其他金融工具的分时报价(及新柱线形成),因为 OnCalculate事件仅在图表当前交易品种的分时报价时触发,如果在两次触发之间出现其他交易品种的分时报价,我们只能等到下一次当前交易品种的分时报价时才会知晓。

我们将在交互式图表事件章节中讨论对多个金融工具分时报价的实时监控(请参阅 自定义事件生成章节中的 EventTickSpy.mq5监控指标)。

目前,我们将以可行精度检查柱线。接下来,我们继续讨论 check方法。

每个时间点的特点在于数组中为所有交易品种设置的时间戳状态。例如,流动性最高的金融工具可能在 12:00 形成新柱线,而其他几个金融工具的分时报价可能在几毫秒甚至几秒后才出现。在该时间间隔内,数组中只有一个元素会进行更新,其余元素仍为旧值。随后,所有交易品种会逐渐生成 12:00 的柱线。

对于最后一根柱线的开盘时间与保存时间不一致的所有交易品种,该方法会设置对应交易品种编号的位,从而形成一个包含变化的位掩码。该列表最多只能包含 64 个交易品种。

如果返回值为零,则表示未检测到任何变化。

refresh参数指定 check 方法是仅记录变化 (false),还是根据当前市场情况更新状态 (true)。

describe方法允许通过位掩码获取发生变化的交易品种列表。

   string describe(ulong flags = 0)
   {
      string message = "";
      if(flags == 0flags = check();
      for(int i = 0i < lastTime.getSize(); i++)
      {
         if((flags & (1 << i)) != 0)
         {
            message += lastTime.getKey(i) + "\t";
         }
      }
      return message;
   }

接下来,我们将使用inSync方法来判断数组中所有交易品种的最后一根柱线时间是否一致。该方法仅适用于交易时段相同的一组货币。

   bool inSync() const
   {
      if(lastTime.getSize() == 0return false;
      const datetime first = lastTime[0];
      for(int i = 1i < lastTime.getSize(); i++)
      {
         if(first != lastTime[i]) return false;
      }
      return true;
   }

利用上述类,我们实现了一个简单的多货币对指标 IndMultiSymbolMonitor.mq5,其唯一任务是检测交易品种列表中的新柱线。

由于未提供该指标的绘图设置,其缓冲区和图表数量为 0。

#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots   0

金融工具列表由对应的输入变量指定,随后会被转换为数组并注册到 monitor对象中。

input string Instruments = "EURUSD,GBPUSD,USDCHF,USDJPY,AUDUSD,USDCAD,NZDUSD";
   
#include <MQL5Book/MultiSymbolMonitor.mqh>
   
MultiSymbolMonitor monitor;
   
void OnInit()
{
   string symbols[];
   const int n = StringSplit(Instruments, ',', symbols);
   for(int i = 0i < n; ++i)
   {
      monitor.attach(symbols[i]);
   }
}

OnCalculate处理程序会在每个分时报价时调用监控,并将状态变化输出到日志中。

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
{
   const ulong changes = monitor.check(true);
   if(changes != 0)
   {
      Print("New bar(s) on: "monitor.describe(changes),
         ", in-sync:"monitor.inSync());
   }
   return rates_total;
}

要检查该指标,我们需要在终端中花费大量在线时间。不过,MetaTrader 5 提供了更简便的方式,即借助测试程序。我们将在下一节中讨论这一点。