数组中时间序列的索引方向

由于交易中存在的细节,MQL5 为处理数组引入了附加功能。其中一个功能是数组元素可包含对应于时间点的数据。例如,包括具有金融工具报价、价格最小变动单位以及技术指标读数的数组。数据的时间顺序意味着新元素会不断添加到数组末尾,其索引也随之递增。

然而,从交易角度而言,从现在到过去进行计数更为方便。这样一来,元素 0 始终包含最近、最新的值,元素 1 始终包含前一个值,以此类推。

MQL5 允许动态选择和切换数组索引方向。从现在到过去编号的数组称为时间序列。如果索引是从过去到现在递增,则属于常规数组。在时间序列中,时间随索引的增加而递减。在普通数组中,时间如现实生活中一样递增。

务必要注意,不要求数组必须包含时间相关值才能够切换寻址顺序。只不过该功能最常用于处理历史数据,事实上也正是为此类需求而设计。

此数组属性不影响数据在内存中的布局。只是编号顺序改变。特别是,我们可以在“从后向前”循环中遍历数组,从而能够在 MQL5 中实现其类似功能。但 MQL5 提供了现成函数,可以让应用程序编程人员无需关注所有这些例行工作。

时间序列可以是在 MQL 程序中描述的任何一维动态数组,以及从 MetaTrader 5 核心传递至 MQL 程序的外部数组,诸如实用函数的参数。例如,一种特殊类型的 MQL 程序“ 指标 ”在 OnCalculate 事件处理程序中接收具有当前图表价格数据的数组。我们将在后面本书第五章了解时间序列的所有实际应用特性。

在 MQL 程序中定义的数组默认不是时间序列。

我们来研究一组函数,用于确定和更改数组的“系列”属性,以及确定数组是否“归属于”终端。包括示例在内的常规 ArrayAsSeries.mq5 脚本将在说明后提供。

bool ArrayIsSeries(const void &array[])

该函数返回一个符号,表明指定数组是否是“真实”时间序列,即它是由终端本身控制和提供的。数组的这一特性无法更改。MQL 程序能够以“只读”模式使用这种数组。

在 MQL5 文档中,术语“时间序列”和“系列”用于描述数组的逆索引以及该数组可能“属于”终端这一事实(终端为其分配内存和填充数据)。在本书中,我们将尝试消除这种歧义,将具有逆索引的数组称为“时间序列”。而终端数组就是指终端的 own 数组。

你可以通过在时间序列模式与标准模式之间来回切换,可酌情更改终端的任何自定义数组的索引方向。可使用 ArraySetAsSeries 函数来进行切换,该函数不仅适用于自己的数组,而且也适用于自定义动态数组(参见下文)。

bool ArrayGetAsSeries(const void &array[])

该函数返回一个符号,指示是否为指定数组启用了时间序列索引模式(即索引在从现在到过去的方向上增加)。可以使用 ArraySetAsSeries 函数更改索引方向。

索引方向会影响 ArrayBsearchArrayMaximumArrayMinimum 函数返回的值(参见章节 数组的比较、排序和搜索)。

bool ArraySetAsSeries(const void &array[], bool as_series)

该函数根据 as_series 参数设置数组的索引方向:true 值表示逆序索引,而 false 值表示正常元素顺序。

如果属性设置成功,该函数返回 true,如果出错,则返回 false

支持任何类型的数组,但对于多维数组和固定大小数组,禁止更改索引方向。

ArrayAsSeries.mq5 脚本描述了若干小型数组,用于进行涉及上述函数的试验。

#define LIMIT 10
 
template<typename T>
void indexArray(T &array[])
{
   for(int i = 0i < ArraySize(array); ++i)
   {
      array[i] = (T)(i + 1);
   }
}
 
class Dummy
{
   int data[];
};
 
void OnStart()
{
   double array2D[][2];
   double fixed[LIMIT];
   double dynamic[];
   MqlRates rates[];
   Dummy dummies[];
   
   ArrayResize(dynamicLIMIT); // allocating memory
   // fill in a couple of arrays with numbers: 1, 2, 3,...
   indexArray(fixed);
   indexArray(dynamic);
   ...

我们有二维数组 array2D、固定和动态数组(类型均为 double)以及结构体数组和类对象数组。出于演示目的,fixeddynamic 数组以连续整数填充(使用辅助函数 indexArray)。对于其它类型的数组,我们将只检查“系列”模式的适用性,因为逆索引效应的概念已经通过填充数组示例解释清楚。

首先,确保没有数组是终端自身的数组:

   PRTS(ArrayIsSeries(array2D)); // false
   PRTS(ArrayIsSeries(fixed));   // false
   PRTS(ArrayIsSeries(dynamic)); // false
   PRTS(ArrayIsSeries(rates));   // false

所有 ArrayIsSeries 调用均返回 false,因为我们在 MQL 程序中定义了所有数组。我们会看到指标中 OnCalculate 函数的参数数组的值为 true(在第五章)。

接下来,我们检查数组索引的初始方向:

   PRTS(ArrayGetAsSeries(array2D)); // false, cannot be true
   PRTS(ArrayGetAsSeries(fixed));   // false
   PRTS(ArrayGetAsSeries(dynamic)); // false
   PRTS(ArrayGetAsSeries(rates));   // false
   PRTS(ArrayGetAsSeries(dummies)); // false

我们会再次全部得到 false

我们将 fixeddynamic 数组输出到日志,以查看元素的原始顺序。

   ArrayPrint(fixed, 1);
   ArrayPrint(dynamic, 1);
   /*
       1.0  2.0  3.0  4.0  5.0  6.0  7.0  8.0  9.0 10.0
       1.0  2.0  3.0  4.0  5.0  6.0  7.0  8.0  9.0 10.0
   */

现在我们尝试更改索引顺序:

   // error: parameter conversion not allowed
   // PRTS(ArraySetAsSeries(array2D, true));
 
   // warning: cannot be used for static allocated array
   PRTS(ArraySetAsSeries(fixedtrue));   // false
 
   // after this everything is standard
   PRTS(ArraySetAsSeries(dynamictrue)); // true
   PRTS(ArraySetAsSeries(ratestrue));   // true
   PRTS(ArraySetAsSeries(dummiestrue)); // true

array2D 数组的一个语句导致编译错误,因此被注释掉。

fixed 数组的一个语句发出编译器警告,提示其不能被应用到恒定大小的数组。在运行时,所有 3 个最后语句均返回成功 (true)。我们看看数组的属性如何改变:

   // attribute checks:
   // first, whether they are native to the terminal
   PRTS(ArrayIsSeries(fixed));            // false
   PRTS(ArrayIsSeries(dynamic));          // false
   PRTS(ArrayIsSeries(rates));            // false
   PRTS(ArrayIsSeries(dummies));          // false
   
   // second, indexing direction
   PRTS(ArrayGetAsSeries(fixed));         // false
   PRTS(ArrayGetAsSeries(dynamic));       // true
   PRTS(ArrayGetAsSeries(rates));         // true
   PRTS(ArrayGetAsSeries(dummies));       // true

如预期的那样,数组没有转变为终端自身的数组。然而,四个数组中有三个将它们的索引方式更改为时间序列模式,包括一个结构体数组和对象数组。为演示结果,fixeddynamic 数组再次显示在日志中。

   ArrayPrint(fixed, 1);    // without changes 
   ArrayPrint(dynamic, 1);  // reverse order
   /*
       1.0  2.0  3.0  4.0  5.0  6.0  7.0  8.0  9.0 10.0
      10.0  9.0  8.0  7.0  6.0  5.0  4.0  3.0  2.0  1.0
   */

由于该模式不适用于恒定大小的数组,因此其保持不变。dynamic 数组现在以逆序显示。

如果你将数组置于逆索引模式,调整其大小,然后恢复为之前的索引方式,则添加的元素将插入在数组的开头。