管理图表上的指标

正如我们已经了解到的,图表是指标的执行和可视化环境。它们之间的紧密联系在一组内置函数中得到了进一步验证,这些函数为图表上的指标控制提供了支持。在前一章中,我们已经完成了 对这些功能的概述。在熟悉了图表之后,我们已经准备好详细探讨这些功能了。

所有函数的统一性体现在前两个参数是统一的:即图表标识符 (chartId) 和窗口编号 (window)。参数的零值分别表示当前图表和主窗口。

int ChartIndicatorsTotal(long chartId, int window)

该函数返回附加到指定图表窗口的所有指标的数量。它可用于枚举(逐一列出)附加到给定图表的所有指标。可以使用 ChartGetInteger函数并通过特性 CHART_WINDOWS_TOTAL 来获取图表的所有窗口总数。

string ChartIndicatorName(long chartId, int window, int index)

该函数通过指定图表窗口中指标列表的index,返回对应指标的短名称。短名称是通过 IndicatorSetString函数在 INDICATOR_SHORTNAME 特性中指定的名称(如果未设置,则默认等于指标文件的名称)。

int ChartIndicatorGet(long chartId, int window, const string shortname)

它会返回特定图表窗口中具有指定短名称的指标的句柄。我们可以说,在ChartIndicatorGet函数中,指标的识别正是通过短名称进行的,因此建议将其构造为包含所有输入参数值的形式。如果由于某种原因而无法通过短名称来唯一标识指标实例,还有另一种方法可以通过指标的参数列表来识别指标实例,该参数列表可以通过给定的描述符使用 IndicatorParameters 函数获取。

ChartIndicatorGet函数获取句柄会增加该指标使用的内部计数器。终端执行系统会将所有计数器值大于零的指标保持加载状态。因此,不再需要的指标必须通过调用 IndicatorRelease显式释放。否则,该指标将保持闲置状态并消耗资源。

bool ChartIndicatorAdd(long chartId, int window, int handle)

该函数会将最后一个参数中传递的描述符所指定的指标添加到指定的图表窗口中。指标和图表必须具有相同的交易品种和时间周期组合。否则,将出现错误ERR_CHART_INDICATOR_CANNOT_ADD (4114)。

要将指标添加到新窗口,window参数必须比最后一个现有窗口的索引大 1,即等于通过 ChartGetInteger 调用获取的 CHART_WINDOWS_TOTAL 特性值。如果参数值超过 ChartGetInteger(ID,CHART_WINDOWS_TOTAL)的返回值,将不会创建新窗口和指标。

如果将一个本应绘制在独立子窗口中的指标(例如内置的 iMACD 或通过#property indicator_separate_window指定特性的自定义指标)添加到主图表窗口,则该指标可能看似不可见,尽管它会存在于指标列表中。这通常意味着该指标的值未落入价格图表的显示范围内。这种“不可见”指标的数值可以在“Data window”中观察到,并可通过其他 MQL 程序中的函数读取。

将指标添加到图表中时,由于其与图表的绑定关系,会增加其使用的内部计数器。如果 MQL 程序持有指标的描述符且不再需要它,那么值得通过调用IndicatorRelease来删除它。这实际上会减少计数器,但指标仍会保留在图表上。

bool ChartIndicatorDelete(long chartId, int window, const string shortname)

这个函数会从标识符为 chartId的图表中,移除编号为window 的窗口中具有指定短名称的指标。如果在指定的图表子窗口中有多个具有相同短名称的指标,将按顺序删除第一个指标。

如果同一图表上的其他指标是使用已移除指标的值计算得出的,那么这些其他指标也会被移除。

从图表中删除一个指标并不意味着其计算部分也会从终端内存中删除 ――前提是该指标的描述符仍保留在 MQL 程序中。若要释放指标句柄,可使用 IndicatorRelease 函数。

ChartWindowFind函数用于返回指标所在子窗口的编号。有两种形式可用于搜索指标:一种是在当前指标所在的图表上搜索该指标,另一种是在标识符为 chartId的任意图表上搜索具有给定短名称的指标。

int ChartWindowFind()

int ChartWindowFind(long chartId, string shortname)

第二种形式可用于脚本和 EA 交易。

作为展示这些函数的第一个示例,让我们来看一下完整版本的脚本ChartList.mq5。在前面的章节中,我们逐步创建并完善了它,直至 获取窗口/子窗口的数量和可见性这一章节。与之前展示的 ChartList4.mq5相比,我们将添加输入变量,以便能够仅列出运行 MQL 程序的图表,并禁止显示隐藏窗口。

input bool IncludeEmptyCharts = true;
input bool IncludeHiddenWindows = true;

IncludeEmptyCharts参数的默认值为true,表示在列表中包含所有图表,包括空图表。IncludeHiddenWindows参数用于设置默认是否显示隐藏窗口。这些设置与之前的脚本逻辑 ChartListN相对应。

为了计算指标总数和子窗口中的指标数量,我们定义了 indicatorssubs 变量。

void ChartList()
{
   ...
   int indicators = 0subs = 0;
   ...

在当前图表的窗口循环处理部分,代码发生了重大变化。

void ChartList()
{
      ...
      for(int i = 0i < wini++)
      {
         const bool visible = ChartGetInteger(idCHART_WINDOW_IS_VISIBLEi);
         if(!visible && !IncludeHiddenWindowscontinue;
         if(!visible)
         {
            Print("  "i"/Hidden");
         }
         const int n = ChartIndicatorsTotal(idi);
         for(int k = 0k < nk++)
         {
            if(temp == 0)
            {
               Print(header);
            }
            Print("  "i"/"k" [I] "ChartIndicatorName(idik));
            indicators++;
            if(i > 0subs++;
            temp++;
         }
      }
      ...

其中新增了 ChartIndicatorsTotalChartIndicatorName 调用。现在,列表将提及所有类型的 MQL 程序:[E] – EA 交易、[S] – 脚本、[I] – 指标

以下是脚本在默认设置下生成的日志条目示例。

Chart List
N, ID, Symbol, TF, #subwindows, *active, Windows handle
0 132358585987782873 EURUSD M15 #1    133538
  1/0 [I] ATR(11)
1 132360375330772909 EURUSD D1     133514
2 132544239145024745 EURUSD M15   *   395646
 [S] ChartList
3 132544239145024732 USDRUB D1     395688
4 132544239145024744 EURUSD H1 #2  active  2361730
  1/0 [I] %R(14)
  2/Hidden
  2/0 [I] Momentum(15)
5 132544239145024746 EURUSD H1     133584
Total chart number: 6, with MQL-programs: 3
Experts: 0, Scripts: 1, Indicators: 3 (main: 0 / sub: 3)

如果将两个输入参数都设置为 false,我们将得到一个精简的列表。

Chart List
N, ID, Symbol, TF, #subwindows, *active, Windows handle
0 132358585987782873 EURUSD M15 #1    133538
  1/0 [I] ATR(11)
2 132544239145024745 EURUSD M15   * active  395646
 [S] ChartList
4 132544239145024744 EURUSD H1 #2    2361730
  1/0 [I] %R(14)
Total chart number: 6, with MQL-programs: 3
Experts: 0, Scripts: 1, Indicators: 2 (main: 0 / sub: 2)

作为第二个示例,我们来看一个有趣的脚本 ChartIndicatorMove.mq5

当在图表上运行多个指标时,我们可能经常需要更改指标的顺序。MetaTrader 5 没有为此提供内置工具,这迫使你删除某些指标并重新添加它们,而在此过程中保存和恢复设置就显得尤为重要。ChartIndicatorMove.mq5脚本提供了一种自动化此过程的选项。需要注意的是,该脚本只是转移指标:如果要更改子窗口的顺序(包括其中的图形对象),则应使用 tpl 模板

ChartIndicatorMove.mq5的工作原理如下。当脚本应用于图表时,它会确定该脚本被添加到了哪个窗口/子窗口,然后开始向用户列出在那里找到的指标,并请求用户确认转移操作。用户可以同意,或者继续列举。

移动方向(向上或向下)通过MoveDirection输入变量设置。DIRECTION 枚举将对其进行描述。

#property script_show_inputs
   
enum DIRECTION
{
   Up = -1,
   Down = +1,
};
   
input DIRECTION MoveDirection = Up;

我们还引入了 jumpover输入变量,可以将指标转移到下一个子窗口(而非相邻子窗口),即实际交换子窗口中指标的位置(该需求较为常见)。

input bool JumpOver = true;

通过 ChartWindowOnDropped获取的目标窗口的指标,循环在 OnStart 中开始执行。

void OnStart()
{
   const int w = ChartWindowOnDropped();
   if(w == 0 && MoveDirection == Up)
   {
      Alert("Can't move up from window at index 0");
      return;
   }
   const int n = ChartIndicatorsTotal(0w);
   for(int i = 0i < n; ++i)
   {
      ...
   }
}

在循环内部,我们会执行以下操作:定义下一个指标的名称,向用户显示消息,并通过以下一系列操作将指标从一个窗口移动到另一个窗口:

  • 通过调用以下函数获取句柄: ChartIndicatorGet.
  • 根据所选方向,通过 ChartIndicatorAdd将其添加至当前窗口的上方或下方窗口;当向下移动时,可自动创建新的子窗口。
  • 通过以下函数从先前的窗口中移除指标: ChartIndicatorDelete.
  • 释放描述符,因为程序中不再需要它。

      ...
      const string name = ChartIndicatorName(0wi);
      const string caption = EnumToString(MoveDirection);
      const int button = MessageBox("Move '" + name + "' " + caption + "?",
         captionMB_YESNOCANCEL);
      if(button == IDCANCELbreak;
      if(button == IDYES)
      {
         const int h = ChartIndicatorGet(0wname);
         ChartIndicatorAdd(0w + MoveDirectionh);
         ChartIndicatorDelete(0wname);
         IndicatorRelease(h);
         break;
      }
      ...

下图展示了交换包含指标 WPRMomentum 的子窗口后的结果。该脚本通过将其拖放到包含 WPR指标的顶部子窗口进行启动,移动方向选择为向下(Down),且默认启用了跳转(JumpOver)功能。

在子窗口中交换指标

在子窗口中交换指标

请注意,如果将指标从子窗口移至主窗口,由于其数值超出显示的价格范围,指标图表很可能不显示。如果误操作导致发生这种情况,你可以使用该脚本将指标移回子窗口。