支持多个交易品种和时间范围

到目前为止,在所有指标示例中,我们都只为与当前图表相同的交易品种和时间范围创建描述符。但实际上并没有此类限制。我们可以在任何交易品种和时间范围上创建辅助指标。当然,在这种情况下,就像之前通过计时器那样,需要等待第三方时间序列就绪。

让我们实现一个 WPR 多时间范围指标(请参阅文件 UseWPRMTF.mq5),该指标还可以指定在任意交易品种(不同于图表上的交易品种)上进行计算。

我们将基于 ENUM_TIMEFRAMES 枚举中的所有标准时间范围,显示指定周期的 WPR 指标值。时间范围数量为 21,因此该指标将始终显示在最后 21 根柱线上。最右侧的 0 号柱线显示 M1 的 WPR,下一根柱线显示 M2 的 WPR,依此类推,直到第 20 根柱线显示月级时间范围的 WPR。为便于阅读,我们将绘图设置不同颜色:分钟级时间范围为红色,小时级时间范围为绿色,日线及更大时间范围为蓝色。

由于可以在指标中设置工作交易品种并在同一图表上为不同交易品种创建多个副本,我们将选择 DRAW_ARROW 绘制风格并提供一个用于指定交易品种的输入参数。通过这种方式,可以区分不同交易品种的指标显示。着色需要一个额外的缓冲区。

#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots   1
   
#property indicator_type1   DRAW_COLOR_ARROW
#property indicator_color1  clrRed,clrGreen,clrBlue
#property indicator_width1  3
#property indicator_label1  "WPR"

WPR 值将被转换到 [-1,+1] 范围。让我们为子窗口选择一个在该范围基础上留有一定裕度的刻度。值为 ±0.6 的水平位对应 WPR 转换前的标准 -20 和 -80。

#property indicator_maximum    +1.2
#property indicator_minimum    -1.2
   
#property indicator_level1     +0.6
#property indicator_level2     -0.6
#property indicator_levelstyle STYLE_DOT
#property indicator_levelcolor clrSilver
#property indicator_levelwidth 1

输入变量包括:WPR 周期、工作交易品种、显示箭头的代码。如果交易品种留空,则使用当前图表的交易品种。

input int WPRPeriod = 14;
input string WorkSymbol = ""// Symbol
input int Mark = 0;
   
const string _WorkSymbol = (WorkSymbol == "" ? _Symbol : WorkSymbol);

为便于编码,时间范围集合列于数组 TF中。

#define TFS 21
   
ENUM_TIMEFRAMES TF[TFS] =
{
   PERIOD_M1,
   PERIOD_M2,
   PERIOD_M3,
   ...
   PERIOD_D1,
   PERIOD_W1,
   PERIOD_MN1,
};

每个时间范围的指标描述符存储在数组 Handle中。

int Handle[TFS];

我们将通过 OnInit函数配置指标缓冲区并获取句柄。

double WPRBuffer[];
double Colors[];
   
int OnInit()
{
   SetIndexBuffer(0WPRBuffer);
   SetIndexBuffer(1ColorsINDICATOR_COLOR_INDEX);
   ArraySetAsSeries(WPRBuffertrue);
   ArraySetAsSeries(Colorstrue);
   PlotIndexSetString(0PLOT_LABEL_WorkSymbol + " WPR");
   
   if(Mark != 0)
   {
      PlotIndexSetInteger(0PLOT_ARROWMark);
   }
   
   for(int i = 0i < TFS; ++i)
   {
      Handle[i] = iCustom(_WorkSymbolTF[i], "IndWPR"WPRPeriod);
      if(Handle[i] == INVALID_HANDLEreturn INIT_FAILED;
   }
   
   IndicatorSetInteger(INDICATOR_DIGITS2);
   IndicatorSetString(INDICATOR_SHORTNAME,
      "%Rmtf" + "(" + _WorkSymbol + "/" + (string)WPRPeriod + ")");
   
   return INIT_SUCCEEDED;
}

OnCalculate函数的计算遵循常规方案:等待数据就绪,初始化,在新柱线填充数据。辅助函数 IsDataReadyFillData 用于执行与描述符的直接交互(见下文)。

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &data[])
{
   // waiting for slave indicators to be ready
   if(!IsDataReady())
   {
      EventSetTimer(1); // if not ready, postpone the calculation
      return prev_calculated;
   }
   if(prev_calculated == 0// initialization
   {
      ArrayInitialize(WPRBufferEMPTY_VALUE);
      ArrayInitialize(ColorsEMPTY_VALUE);
      // constant colors for the latest TFS bars
      for(int i = 0i < TFS; ++i)
      {
         Colors[i] = i < 11 ? 0 : (i < 18 ? 1 : 2);
      }
   }
   else // preparing a new bar
   {
      for(int i = prev_calculatedi < rates_total; ++i)
      {
         WPRBuffer[i] = EMPTY_VALUE;
         Colors[i] = 0;
      }
   }
   
   if(prev_calculated != rates_total// new bar
   {
      // clear the label on the oldest bar that moved to the left beyond TFS bars
      WPRBuffer[TFS] = EMPTY_VALUE;
      // update bar coloring
      for(int i = 0i < TFS; ++i)
      {
         Colors[i] = i < 11 ? 0 : (i < 18 ? 1 : 2);
      }
   }
   
   // copy the data from the subordinate indicators to our buffer
   FillData();
   return rates_total;
}

如有必要,我们会通过计时器启动重新计算。

void OnTimer()
{
   ChartSetSymbolPeriod(0_Symbol_Period);
   EventKillTimer();
}

以下是 IsDataReadyFillData 函数的实现。

bool IsDataReady()
{
   for(int i = 0i < TFS; ++i)
   {
      if(BarsCalculated(Handle[i]) != iBars(_WorkSymbolTF[i]))
      {
         Print("Waiting for "_WorkSymbol" "EnumToString(TF[i]));
         return false;
      }
   }
   return true;
}
   
void FillData()
{
   for(int i = 0i < TFS; ++i)
   {
      double data[1];
      // taking the last actual value (buffer 0, index 0)
      if(CopyBuffer(Handle[i], 001data) == 1)
      {
         WPRBuffer[i] = (data[0] + 50) / 50;
      }
   }
}

让我们编译该指标,观察其在图表上的显示效果。例如,我们可以为 EURUSD、USDRUB 和 XAUUSD 创建三个副本。

不同工作交易品种的多时间范围 WPR 的三个实例

不同工作交易品种的多时间范围 WPR 的三个实例

在第一次计算时,指标可能需要大量时间来为所有时间范围准备时间序列。

在计算层面,完全相同的指标UseWPRMTFDashboard.mq5被设计为交易员常用的仪表盘形式。对于每个交易品种,我们通过指标的Level参数设置独立的垂直缩进。所有时间范围的 WPR 值将在此处以标记线的形式显示,并且这些值通过颜色编码区分。在该版本中,WPR 值被归一化到 [0..1] 区间,因此使用间隔数十个单位(例如在下面截图中为 20)的水平标尺,可在同一子窗口无重叠地布局多个指标实例(例如 80、100、120 等)。每个副本用于其各自的工作交易品种。此外,由于 Level大于 1.0 且 WPR 值较小,它们在Data window中分别显示为:小数点左侧和小数点右侧。

标签标尺的标签由 OnInit中动态添加的水平位提供。

不同工作交易品种的三线式多时间范围 WPR 面板

不同工作交易品种的三线式多时间范围 WPR 面板

你可以研究 UseWPRMTFDashboard.mq5的源代码,并将其与UseWPRMTF.mq5 进行比较。为生成一系列渐变色,我们使用了 ColorMix.mqh文件。

在完成对 内置指标(包括 iWPR)的学习后,我们可以将自定义 IndWPR 替换为内置 iWPR

关于复合指标的效率和资源占用
 
上述通过生成多个辅助指标的方法在执行速度和资源占用方面并不高效。这主要是一个集成 MQL 程序并在这些程序之间交换数据的示例。但与任何技术一样,应合理使用该方法。
 
所创建的两个指标均会计算时间序列中所有柱线的 WPR,但最终只有最后一个值被调用指标所采用。这样既浪费内存又消耗处理器时间。
 
如果辅助指标的源代码可用或其运算逻辑已知,最优方法是将计算算法嵌入主指标(或 EA 交易)内部,并仅针对最小所需深度的近期历史数据范围执行计算。
 
在某些情况下,你可以通过在当前时间范围上执行等效计算来避免引用更高时间范围:例如,无需 14 根日柱线的价格区间(需要构建完整的 D1 时间序列),你可以使用 14 * 24 H1 柱线上的价格区间,但前提是 24 小时交易且指标在 H1 图表上运行。
 
当交易系统中使用商用指标(无源代码)时,只能通过开放编程接口获取其数据。这种情况下,创建句柄并通过CopyBuffer从指标缓冲区读取数据,是唯一可行且兼具便利性与通用性的方法。需要注意的是,调用 API 函数的开销显著高于操作 MQL 程序内部数组或调用本地函数。如果你需要同时运行多个终端,且每个终端都可能运行一组未优化的 MQL 程序,在资源有限的情况下,系统性能可能会下降。