通过柱线索引读取价格、成交量、点差和时间

有时,你需要查询的不是一系列柱线的信息,而仅是某一根柱线的信息。从理论上讲,这可以通过使用前面讨论过的 Copy函数来实现,只需在函数中指定数量(count 参数)为 1,但这种方式并不够便捷。以下函数提供了更简单的方法,它们可以根据柱线在时间序列中的编号,返回该柱线的某个特定类型的单一值。

所有函数都具有相似的原型,但名称和返回类型不同。从历史上看,这些函数名称均以前缀 i开头,形式为 iValue(这些函数属于内置技术指标的大类:毕竟报价的各种特性是技术分析的主要数据来源,而几乎所有指标都是其衍生指标,因此使用字母 i)。

type iValue(const string symbol, ENUM_TIMEFRAMES timeframe, int offset)

其中,type 对应 datetimedoublelongint 中的一种类型,具体取决于函数本身。symbol 和 timeframe 用于指定要查询的时间序列。所需柱线的索引offset采用时间序列的表示方法:0 表示最新的、最右侧的柱线(通常尚未完成),数值越大,表示越早期的柱线。与 Copy 函数的情况类似,可以使用 NULL 和 0 来指定交易品种和时间周期,使其与当前图表的特性保持一致。

由于 i函数等效于调用 Copy 函数,因此在 获取报价数组的 Copy 函数概述 章节中描述的所有从不同类型程序请求时间序列的特性,同样适用于这些函数。

函数

说明

iTime

柱线开盘时间

iOpen

柱线开盘价

iHigh

柱线最高价

iLow

柱线最低价

iClose

柱线收盘价

iTickVolume

柱线的分时报价成交量(与 iVolume 类似)

iVolume

柱线的分时报价成交量(与 iTickVolume 类似)

iRealVolume

柱线的真实交易量

iSpread

柱线的最小点差(以点为单位)

这些函数会返回请求的值,如果出错则返回 0(遗憾的是,在某些情况下 0 可能是一个有效值)。如需获取详细的错误信息,可调用 GetLastError 函数。

这些函数不会缓存结果。每次调用时,它们都会从指定交易品种/时间周期的时间序列中返回实时数据。这意味着,在没有现成数据的情况下(首次调用时或同步丢失后),函数可能需要一些时间来准备结果。

举例来说,我们尝试获取每根柱线点差大小的近似真实值估算。由于报价中存储的是最小点差值,这可能会导致在设计交易策略时产生不切实际的过高预期。要获取每根柱线的平均点差、中位数点差或最大点差的绝对精确值,就必须分析实际的分时报价数据,但目前我们还未学习如何处理这类数据。此外,这也会是一个资源消耗极大的过程。更合理的方法是分析较低的 M1 时间范围的点差:对于较高时间范围的柱线,只需在其内部包含的 M1 柱线中查找最大点差即可。当然严格来说,这并非真正的最大值,而是最小点差值中的最大值,但考虑到分钟数据的瞬时特性,我们至少有望在某些 M1 柱线上检测到典型点差扩大情况,这足以在分析精度和速度之间取得可接受的平衡。

脚本SeriesSpread.mq5中实现了该算法的一个版本。在输入变量中,你可以设置用于分析的交易品种、时间范围和柱线数量。默认情况下,处理当前图表的交易品种及其时间周期(必须大于 M1)。

input string WorkSymbol = NULL// Symbol (leave empty for current)
input ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT;
input int BarCount = 100;

由于每个柱线只需关注其时间和点差信息,因此定义了一个包含两个字段的特殊结构体。我们本可以使用标准的 MqlRates结构体,并将“最大”点差数据添加到某个未使用的字段中(例如外汇交易品种的 real_volume),但这样会复制大部分字段的数据,造成内存浪费。

struct SpreadPerBar
{
   datetime time;
   int spread;
};

通过使用新的结构体类型,我们准备了一个 peaks数组,用于计算指定数量的柱线数据。

void OnStart()
{
   SpreadPerBar peaks[];
   ArrayResize(peaksBarCount);
   ZeroMemory(peaks);
   ...

随后,算法的主体部分在柱线循环中执行。对于每个柱线,我们使用iTime函数确定定义该柱线边界的两个时间戳。实际上,这两个时间戳分别是第 i 根柱线和相邻的第 i+1 根柱线的开盘时间。根据索引原则,可以认为第 i+1 根柱线是前一根柱线(更早,见变量 prev),而第 i 根柱线是后一根柱线(更新,见变量next)。柱线的开盘时间仅属于该柱线,即标签 prev属于第 i+1 根柱线,而标签next 属于第 i 根柱线。因此,在处理每根柱线时,应将其右边界排除在区间 [prev;next) 之外。

我们关注的是一分钟时间周期上的点差,因此将使用CopySpread函数并指定 PERIOD_M1 参数。在这种情况下,通过将 start/stop参数分别设置为精确的 prev 值和减少 1 秒的next 值,可实现半开区间的设置。点差信息被复制到动态数组 spreads中(内存由函数自动分配)。

   for(int i = 0i < BarCount; ++i)
   {
      int spreads[]; // receiving array for M1 spreads inside the i-th bar
      const datetime next = iTime(WorkSymbolTimeFramei);
      const datetime prev = iTime(WorkSymbolTimeFramei + 1);
      const int n = CopySpread(WorkSymbolPERIOD_M1prevnext - 1spreads);
      const int m = ArrayMaximum(spreads);
      if(m > -1)
      {
         peaks[i].spread = spreads[m];
         peaks[i].time = prev;
      }
   }

然后,我们在该数组中找出最大值,并将其与柱线时间一起保存在相应的SpreadPerBar结构体中。请注意,零号柱线不完整,未包含在分析中(如有需要,可对算法进行补充)。

循环结束后,我们将结构体数组输出到日志中。

   PrintFormat("Maximal speeds per intraday bar\nProcessed %d bars on %s %s"
      BarCountStringLen(WorkSymbol) > 0 ? WorkSymbol : _Symbol
      EnumToString(TimeFrame == PERIOD_CURRENT ? _Period : TimeFrame));
   ArrayPrintM(peaks);

在 EURUSD,H1 图表上运行该脚本后,我们将获得小时柱线内的点差统计数据(节选):

Maximal speeds per intraday bar
Processed 100 bars on EURUSD PERIOD_H1
[ 0] 2021.10.12 14:00        1
[ 1] 2021.10.12 13:00        1
[ 2] 2021.10.12 12:00        1
[ 3] 2021.10.12 11:00        1
[ 4] 2021.10.12 10:00        0
[ 5] 2021.10.12 09:00        1
[ 6] 2021.10.12 08:00        2
[ 7] 2021.10.12 07:00        2
[ 8] 2021.10.12 06:00        1
[ 9] 2021.10.12 05:00        1
[10] 2021.10.12 04:00        1
[11] 2021.10.12 03:00        1
[12] 2021.10.12 02:00        4
[13] 2021.10.12 01:00       16
[14] 2021.10.12 00:00       65
[15] 2021.10.11 23:00       15
[16] 2021.10.11 22:00        2
[17] 2021.10.11 21:00        1
[18] 2021.10.11 20:00        1
[19] 2021.10.11 19:00        2
[20] 2021.10.11 18:00        1
[21] 2021.10.11 17:00        1
[22] 2021.10.11 16:00        1
[23] 2021.10.11 15:00        2
[24] 2021.10.11 14:00        1

夜间点差明显扩大:例如临近午夜时,报价中的点差为 7-15 点,而在我们的测量中达到 15-65 点。虽然小时柱线的指标通常显示为零,但在其他时段也能发现非零值。