以资源形式连接自定义指标
为便于运算,MQL 程序可能需要一个或多个自定义指标。所有这些都可以作为资源包含在 ex5 可执行文件中,使其易于分发和安装。
包含嵌套指标说明的 #resource 指令格式如下:
#resource "path_indicator_name.ex5" |
设置和搜索指定文件的规则,与一般所有 资源 的处理方式相同。
我们已经在 大型智能交易系统示例的UnityMartingale.mq5 最终版本中使用了这一功能。
#resource "\\Indicators\\MQL5Book\\p6\\UnityPercentEvent.ex5" |
在该智能交易系统中,传递给 iCustom 函数的不是指标名称,而是该资源:"::Indicators\\MQL5Book\\p6\\UnityPercentEvent.ex5"。
关于 OnInit 函数里的自定义指标创建自身一个或多个实例的情况,需要特别加以考虑(如果这个技术方案本身看起来有点奇怪,我们会在介绍性范例之后提供一个实际应用的例子)。
我们知道,要使用 MQL 程序中的资源,必须以如下形式指定:path_file_name.ex5::resource_name。例如,如果 EmbeddedIndicator.ex5 指标作为资源包含在另一个指标 MainIndicator.mq5(更准确地说,包含在其二进制映像 MainIndicator.ex5)中,那么通过 iCustom 调用时指定的名称就不能再是简短的无路径名称,路径必须包含 MQL5 文件夹中“父”指标的位置。否则,系统将无法找到嵌套指标。
事实上,在正常情况下,指标可以使用 iCustom(_Symbol, _Period, myself,...) 运算符调用自身,其中 myself 是一个字符串,等于 MQLInfoString(MQL_PROGRAM_NAME) 或之前在代码中分配给 INDICATOR_SHORTNAME 特性的名称。但是,当指标作为资源位于另一个 MQL 程序中时,名称就不再指向相应的文件,因为作为资源原型的文件仍保留在进行编译的计算机上,而在用户的计算机上只有 MainIndicator.ex5 文件。这就需要在启动程序时对程序环境进行一些分析。
我们来实际操作一下。
首先,我们来创建一个 NonEmbeddedIndicator.mq5 指标。需要注意的是,它位于 MQL5/Indicators/MQL5Book/p7/SubFolder/ 文件夹中,即相对于本书这章所有指标所在的 p7 文件夹下的 SubFolder 中。这样做的目的是模拟用户计算机上没有编译文件的情况。现在我们来看看它是如何运作的(或者说,演示了这个问题)。
该指标只有一个输入参数 Reference。它的作用是计算自身副本的数量:在首次创建时,参数值为 0,指标将创建参数值为 1 的自身副本。第二个副本在“侦测到”数值 1 之后,就不会再建立新的副本了(否则,如果没有停止复制的边界条件,我们的资源很快就会用尽)。
input int Reference = 0; |
handle 变量保留给复制指标的句柄。
int handle = 0; |
在处理程序 OnInit 中,为了清晰起见,我们首先显示 MQL 程序的名称和路径。
int OnInit()
|
接下来是适合自启动单独指标的代码(以我们熟悉的文件 NonEmbeddedIndicator.ex5 的形式存在)。
if(Reference == 0)
|
我们可以成功地在图表上放置这样一个指标,并在日志中接收以下类型的条目(你将拥有自己的文件系统路径):
0 Name: NonEmbeddedIndicator Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\SubFolder\NonEmbeddedIndicator.ex5 Success 1 Name: NonEmbeddedIndicator Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\SubFolder\NonEmbeddedIndicator.ex5 Success |
只需使用 "NonEmbeddedIndicator" 这一名称,复制就能成功启动。
我们现在暂时先不讨论这个指标,来创建第二个指标 FaultyIndicator.mq5。我们会将第一个指标作为资源包含进这个新指标中(请注意资源相对路径中 subfolder 的指定;这是必要的,因为 FaultyIndicator.mq5 指标位于上一层文件夹:MQL5/Indicators/MQL5Book/p7/)。
// FaultyIndicator.mq5
|
如果你尝试运行已编译的 FaultyIndicator.ex5,则会出现错误:
0 Name: NonEmbeddedIndicator Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\FaultyIndicator.ex5 » » ::SubFolder\NonEmbeddedIndicator.ex5 cannot load custom indicator 'NonEmbeddedIndicator' [4802] |
当启动一个内嵌指标的副本时,系统会在描述该资源的那个主指标所在的文件夹中搜寻它。但是并没有 NonEmbeddedIndicator.ex5 文件,因为所需资源在 FaultyIndicator.ex5 中。
为了解决这个问题,我们需要修改 NonEmbeddedIndicator.mq5。首先,我们给它取一个更合适的名字,即 EmbeddedIndicator.mq5。在源代码中,我们需要添加一个辅助函数 GetMQL5Path,它可以从已启动的 MQL 程序的常规路径中分离出 MQL5 文件夹内的相对部分(如果指标是从资源启动的,这部分还将包含资源名称)。
// EmbeddedIndicator.mq5
|
考虑到新函数,我们将更改 OnInit 处理程序中的 iCustom 调用。
int OnInit()
|
我们确保这次修改不会破坏指标的启动。在图表上叠加后,日志中会出现预期的文本行:
0 Name: EmbeddedIndicator Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\SubFolder\EmbeddedIndicator.ex5 Location in MQL5:\Indicators\MQL5Book\p7\SubFolder\EmbeddedIndicator.ex5 Success 1 Name: EmbeddedIndicator Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\SubFolder\EmbeddedIndicator.ex5 Location in MQL5:\Indicators\MQL5Book\p7\SubFolder\EmbeddedIndicator.ex5 Success |
我们在这里添加了 GetMQL5Path 函数接收到的相对路径的调试输出。这一行现在用于 iCustom,并在此模式下运作:创建了一个副本。
现在,我们将该指标作为资源嵌入 MQL5Book/p7 文件夹中的另一个指标,其名称为 MainIndicator.mq5。除了连接的资源外,MainIndicator.mq5 与 FaultyIndicator.mq5 完全相同。
// MainIndicator.mq5
|
我们来编译并运行它。日志中出现的条目会带有一个包含嵌套资源的新相对路径。
0 Name: EmbeddedIndicator Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\MainIndicator.ex5 » » ::SubFolder\EmbeddedIndicator.ex5 Location in MQL5:\Indicators\MQL5Book\p7\MainIndicator.ex5::SubFolder\EmbeddedIndicator.ex5 Success 1 Name: EmbeddedIndicator Full path: C:\Program Files\MT5East\MQL5\Indicators\MQL5Book\p7\MainIndicator.ex5 » » ::SubFolder\EmbeddedIndicator.ex5 Location in MQL5:\Indicators\MQL5Book\p7\MainIndicator.ex5::SubFolder\EmbeddedIndicator.ex5 Success |
我们可以看到,这次嵌套指标成功地创建了自己的副本,因为它使用了带有相对路径的限定名和资源名 "\\Indicators\\MQL5Book\\p7\\MainIndicator.ex5::SubFolder\\EmbeddedIndicator.ex5"。
在启动该指标的多次实验中,请注意在移除主指标后,嵌套副本不会立即从图表中卸载。因此,只有在等待卸载后才能重新启动:否则,仍在运行的副本将被重复使用,上述初始化行将不会出现在日志中。为了控制卸载,我们在 OnDeinit 处理程序中添加了 Reference 值的打印输出。
我们承诺向大家展示,创建指标的副本并不是什么特别的事情。作为该技术的应用演示,我们使用指标 DeltaPrice.mq5,它可以计算给定阶数的价格增量差。阶数 0 表示无差异(仅检查原始时间序列),1 表示单差异,2 表示双差异,以此类推。
阶数在 Differentiating 输入参数中指定。
input int Differencing = 1; |
差值序列将显示在子窗口的单个缓冲区中。
#property indicator_separate_window
|
在 OnInit 处理程序中,我们将设置缓冲区并创建相同的指标,在输入参数中传递减 1 的值。
#include <MQL5Book/AppliedTo.mqh> // APPLIED_TO_STR macro
|
为了避免将指标作为资源嵌入时可能出现的问题,我们使用了已证明有效的函数 GetMQL5Path。
在 OnCalculate 函数中,我们执行减去时间序列相邻值的运算。当 Differentiating 等于 1 时,操作数是 price 数组的元素。如果 Differentiating 值较大,我们将读取为前一个阶数创建的指标副本的缓冲区。
int OnCalculate(const int rates_total,
|
差分价格的初始类型在指标设置对话框的Apply to下拉列表中设置。默认为Close价。
这就是在图表上,同一个指标的数个副本以不同的差分阶数呈现出来的样子。
不同差分阶数的收盘价差异