主要指标事件:OnCalculate
OnCalculate函数是 MQL5 指标代码的主要入口点。每当发生 OnCalculate 事件时,就会调用该函数,而当价格数据发生变化时,将触发该事件。例如,当某个交易品种的新分时报价到达时,或旧价格发生变动时(填补历史数据缺口或从服务器下载缺失数据时),都会触发该事件。
该函数有两种变体,区别在于计算所依据的源数据不同:
- 完整版 - 通过参数提供一组标准价格时间序列(OHLC 价、成交量、点差)
- 简化版 - 针对单一任意时间序列(不一定是标准序列)
一个指标只能选择两种形式中的一种使用,无法在同一指标中混用这两种形式。
使用简化版OnCalculate函数时,将指标添加到图表上后,其特性对话框中会多出一个选项卡。该选项卡提供了一个Apply to下拉列表,你需要在此选择指标计算所基于的初始时间序列。如果未选择任何时间序列,则默认以 Close价格值作为计算依据。
使用简化版 OnCalculate 为指标选择初始时间序列
下拉列表始终提供标准价格类型,但如果图表上已加载其他指标,该设置允许你选择其中一个指标作为另一个指标的数据源,从而构建指标处理链。我们将在 跳过初始柱线的绘制章节尝试基于其他指标构建新指标。使用完整版 OnCalculate 时,该选项不可用。
禁止将指标应用于以下内置指标:Fractals、Gator、Ichimoku 和 Parabolic SAR。
简化版 OnCalculate具有以下原型:
int OnCalculate(const int rates_total, const int prev_calculated, const int begin,
const double &data[])
data数组包含计算所需的初始数据。该数据可以是价格时间序列之一,或另一个指标的计算缓冲区。rates_total参数指定data 数组的大小。 ArraySize(data) 或 iBars(NULL, 0)调用应提供与 rates_total 相同的值。
prev_calculated旨在让指标仅对少量新增柱线(通常是最后一根)进行有效重新计算,而非重新计算所有柱线。prev_calculated值等于上一次调用OnCalculate 函数时返回给运行时系统的结果。例如,当收到下一个分时报价时,如果指标已计算所有柱线的公式,则应从 OnCalculate函数返回 rates_totalA 值(此处索引 A 表示初始时刻)。然后,在收到下一个分时报价并触发 OnCalculate事件时,终端会将 prev_calculated 设置为先前返回的值 rates_totalA。然而,在此期间柱线数量可能已经发生变化,新的 rates_total值会增大,我们将该值设为 rates_totalB。因此,只需计算从 prev_calculated(即 rates_totalA)到 rates_totalB 之间的柱线数据。
不过,最常见的情况新分时报价适合当前零柱线,即 rates_total不会变化。因此在大多数 OnCalculate 调用中,会出现prev_calculated == rates_total 等式。这种情况下是否需要重新计算?这取决于计算的性质。例如,如果指标是基于开盘价计算的(开盘价固定不变),则无需重新计算。但如果指标使用收盘价(实际上是最后一个已知分时报价的价格)或任何其他依赖于 Close的汇总价格,则应始终重新计算最后一根柱线。
当第一次调用 OnCalculate函数时,prev_calculated 的值为 0。
如果自上次调用 OnCalculate函数以来,价格数据发生了变化(例如加载了更深层次的历史数据或填补了缺口),则终端也会将prev_calculated 参数的值设为 0。因此,指标将收到信号,需要对整个可用历史数据进行完整重新计算。
如果 OnCalculatee 函数返回空值,则不会绘制指标,且其缓冲区的名称和值将在 Data window 中隐藏。
请注意,返回全部柱线数量rates_total,是向终端以及将使用该指标的其他 MQL 程序表明指标数据已准备就绪的唯一标准方式。即使指标仅用于计算和显示有限数据量,仍应返回 rates_total。
data数组的索引方向可以通过调用 ArraySetAsSeries 函数选择(默认值为 false,可通过调用ArrayGetAsSeries 验证)。同时,如果对该数组应用 ArrayIsSeries 函数,将返回true。这意味着该数组是由终端管理的内部数组。指标无法以任何方式对它进行修改,只能读取,因为参数声明中有 const修饰符。
begin参数报告data 数组中应从计算中排除的初始值数量。当用户将指标配置为接收其他指标的data时((见上图),系统会自动设置该参数。例如,如果所选数据源指标计算周期为 N的移动平均线,根据定义,前 N - 1 根柱线不包含有效数据,因为无法对不足N 根柱线进行平均计算。如果开发者在源指标中设置了特殊特性,该特性将通过begin参数正确传递给当前指标。我们很快将在实践中验证这一点(请参阅 跳过初始柱线的绘制章节)。
我们来尝试创建一个简化版OnCalculate的空指标模板。该指标目前不执行任何计算,但可作为后续实验的准备。原始文件 IndStub.mq5可在MQL5/Indicators/MQL5Book/p5/ 文件夹中找到。为确认指标正常运行,我们在 OnCalculate中添加以下功能:能够将 prev_calculated 和 rates_total 值输出到日志,能够统计函数调用次数。
int OnCalculate(const int rates_total,
|
通过判断 prev_calculated和rates_total 是否不相等,可以确保仅在以下情况输出消息:当指标第一次添加到图表时输出消息,以及当新柱线形成时输出消息。在当前柱线形成过程中到达的所有分时报价不会改变柱线数量,因此 prev_calculated和rates_total 将保持相等。但我们仍会使用 count 变量统计所有分时报价的总数。
其余参数目前尚未启用,但我们将逐步使用全部功能。
这段源代码能够成功编译,但会生成两条警告。
no indicator window property is defined, indicator_chart_window is applied
|
它们提示缺少某些#property指令,这些指令虽然并非强制要求,但用于设置指标的基本特性。具体来说,第一条警告表明尚未为指标选择绑定方式:主窗口还是子窗口,因此默认将使用主图表窗口。第二条警告与我们尚未设置显示的图表数量有关。如前所述,虽然某些指标特意设计为不带缓冲区,因为它们旨在执行其他操作,但当前警告提醒我们稍后添加可视化部分。
我们将在接下来的章节中解决这些警告问题,但现在,我们将在 EURUSD,M1 图表上启动该指标。我们使用 M1 时间范围,因为这样可以快速观察到新柱线的形成以及日志中消息的更新。
calculated=0 rates=10002; 1 ticks
|
因此,我们看到OnCalculate处理程序按预期调用,你可以在其中基于每个分时报价或基于每个柱线执行计算。如需从图表移除指标,可通过图表上下文菜单调用 Indicator List对话框:选择目标指标,然后按 Delete。
现在,我们回到OnCalculate函数的另一种原型。我们已经在实践中试用了简化版,同样也可以为完整版创建完全相同的空白模板。
完整版用于基于标准价格时间序列进行计算,其原型如下:
int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[],
const double &open[], const double &high[], const double &low[], const double &close[],
const long &tick_volume[], const long &volume[], const int &spread[])
rates_total和 prev_calculated 参数的含义与简化版OnCalculate 相同:rates_total设置传递的时间序列大小(所有数组长度相同,因为它对应图表的柱线总数),prev_calculated 包含上次调用时已处理的柱线数量(即 OnCalculate 函数之前通过 return 语句返回给终端的值)
数组 open、high、low 和close 包含当前图表柱线的相关价格:即操作中的交易品种和时间范围的价格时间序列。time数组包含每根柱线的开盘时间,tick_volume 和 volume 包含每根柱线的交易成交量(分时报价和交易所成交量)。
在上一章中,我们学习了终端通过一组函数为 MQL 程序提供的标准价格和成交量类型的 时间序列 。为便于指标计算,这些时间序列将以数组引用的形式直接传递给OnCalculate处理程序。这样便无需调用这些函数并将报价复制(重复)到内部数组。当然,这种技术仅适用于基于特定条件进行计算的指标:即基于与当前图表匹配的单个交易品种和时间范围的组合。但 MQL5 允许创建多货币、多时间范围指标,以及适用于非当前图表交易品种和时间范围的指标。在所有这些情况下,都必须借助时间序列访问函数来实现。具体实现方法,我们稍后将详细探讨。
如果使用 ArrayIsSeries 检查所有传入的数组是否属于终端,该函数将返回true。所有数组均为只读。参数说明中的 const修饰符也强调了这一特性。
基于计算算法所需的数据,选择完整版或简化版。例如,要使用移动平均算法对数组进行平滑处理,仅需一个输入数组,因此,该指标可以构建为支持用户选择的任意价格类型。但诸如 ParabolicSAR或 ZigZag 等经典指标需要使用 High 和 Low 价,因此必须使用完整版OnCalculate。在接下来的章节中,我们将看到基于 OnCalculate简化版和完整版的指标示例。