将输出组合到主窗口和辅助窗口

让我们回到在主窗口和子窗口中显示同一指标图形的问题,因为我们在开发示例UseDemoAllSimple.mq5时遇到了该问题。我原本认为,独立窗口不适合在主图表上可视化,而主窗口指标又没有额外窗口。有几种替代方案:

  • 为独立窗口实现一个父指标,在其中显示图表,并在主窗口中使用它来显示 图形对象类型的数据。该方案存在缺陷,因为从对象读取数据的方式和从时间序列读取不同,且许多对象会消耗额外的资源。
  • 为主窗口开发自定义虚拟面板(类),并在正确缩放的情况下,在其中显示本应在子窗口显示的时间序列数据。
  • 使用多个指标(至少一个用于主窗口,一个用于子窗口),并通过共享内存(需调用 DLL)、 资源数据库在指标间交换数据。
  • 在主窗口和子窗口的指标中复用计算逻辑(使用公共源代码)。

我们将介绍一种超出单个 MQL 程序限制的解决方案:需要一个具备indicator_separate_window特性的额外指标。实际上我们已经有了,因为已经通过请求句柄创建了其计算部分。我们只需以某种方式将其显示在独立子窗口中即可。

在新版(完整版)的 UseDemoAll.mq5中,我们将分析通过相应的IndicatorType 枚举元素所请求创建的指标的元数据。回想一下,除其他因素外,每种内置指标的工作窗口类型也在其中进行了编码。当某个指标需要独立窗口时,我们将通过特殊的 MQL5 函数创建一个独立窗口,这些函数我们稍后会详细学习。

目前无法获取自定义指标的工作窗口信息。因此,我们添加一个输入变量 IndicatorCustomSubwindow,允许用户声明需要一个子窗口。

input bool IndicatorCustomSubwindow = false// Custom Indicator Subwindow

OnInit中,我们将隐藏用于子窗口的缓冲区。

int OnInit()
{
   ...
   const bool subwindow = (IND_WINDOW(IndicatorSelector) > 0)
      || (IndicatorSelector == iCustom_ && IndicatorCustomSubwindow);
   for(int i = 0i < BUF_NUM; ++i)
   {
      ...
      PlotIndexSetInteger(iPLOT_DRAW_TYPE,
         i < n && !subwindow ? DrawType : DRAW_NONE);
   }
   ...

完成该设置后,还需要调用一些函数,这些函数不仅适用于指标操作,也适用于图表操作。我们将在相应章节中详细学习这些函数,而 上一节已对它们进行了介绍性概述。

其中,ChartIndicatorAdd函数允许将句柄指定的指标添加到窗口中,不仅可添加到主要部分,还能添加到子窗口。关于图表标识符和窗口编号,我们将在 图表章节中讨论,目前只需知道在下次调用 ChartIndicatorAdd函数时,会将指定 handle 的指标添加到当前图表的新建子窗口中。

 inthandle = ...// get indicator handle, iCustom or IndicatorCreate
 
                    // set the current chart (0)
                    // |
                    // |     set the window number to the current number of windows
                    // |                          |
                    // |                          | passing the descriptor
                    // |                          |                       |
                    // v                          v                       v
   ChartIndicatorAdd(  0, (int)ChartGetInteger(0CHART_WINDOWS_TOTAL), handle); 

了解这一可能性后,我们可以考虑调用 ChartIndicatorAdd,并向其传递一个已创建的从属指标的句柄。

我们需要的第二个函数是ChartIndicatorName。该函数通过指标句柄返回其简称。该名称对应指标代码中设置的 INDICATOR_SHORTNAME 特性,可能与文件名不同。该名称是必需的,用于清理自身的操作,即在删除或重新配置父指标后,移除辅助指标及其子窗口。

string subTitle = "";
   
int OnInit()
{
   ...
   if(subwindow)
   {
      // show a new indicator in the subwindow
      const int w = (int)ChartGetInteger(0CHART_WINDOWS_TOTAL);
      ChartIndicatorAdd(0wHandle);
      // save the name to remove the indicator in OnDeinit
      subTitle = ChartIndicatorName(0w0);
   }
   ...
}

OnDeinit处理程序中,我们使用保存的 subTitle 调用另一个函数ChartIndicatorDelete(后续会详细讲解)。该函数通过最后一个参数指定的指标名称,从图表中移除对应指标。

void OnDeinit(const int)
{
   Print(__FUNCSIG__, (StringLen(subTitle) > 0 ? " deleting " + subTitle : ""));
   if(StringLen(subTitle) > 0)
   {
      ChartIndicatorDelete(0, (int)ChartGetInteger(0CHART_WINDOWS_TOTAL) - 1,
         subTitle);
   }
}

这里假设图表中仅运行我们这一个指标,且仅存在一个实例。在更通用的场景中,应分析所有子窗口以确保正确删除,但这需要用到 图表章节将介绍的更多函数,因此目前我们暂时采用简化版本。

现在,如果我们运行UseDemoAll并从列表中选择带星号标记的指标(即需要子窗口的指标),例如 RSI 指标,就能看到预期效果:RSI 在独立窗口中。

RSI 在由 UseDemoAll 指标创建的子窗口中

RSI 在由 UseDemoAll 指标创建的子窗口中