用于获取报价数组的 Copy 函数概述
MQL5 API 包含多个用于将报价时间序列读取到数组中的函数。它们的名称如下表所示。
函数 |
操作 |
|---|---|
CopyRates |
将报价历史数据获取到 MqlRates结构体数组中 |
CopyTime |
将柱线开盘时间历史数据获取到以下类型的数组中: datetime |
CopyOpen |
将柱线开盘价历史数据获取到以下类型的数组中: double |
CopyHigh |
将柱线最高价历史数据获取到以下类型的数组中: double |
CopyLow |
将柱线最低价历史数据获取到以下类型的数组中: double |
CopyClose |
将柱线收盘价历史数据获取到以下类型的数组中: double |
CopyTickVolume |
将分时报价成交量历史数据获取到以下类型的数组中: long |
CopyRealVolume |
将成交量历史数据获取到以下类型的数组中: long |
CopySpread |
将点差历史数据获取到以下类型的数组中: int |
所有函数的前两个参数均为所需交易品种的名称和周期,这可以通过以下伪代码有条件地表示:
int Copy***(const string symbol, ENUM_TIMEFRAMES timeframe, ...) |
此外,所有函数的原型均有三种变体,其区别在于请求范围的设置方式:
- 起始柱线索引和柱线数量: Copy***(..., int offset, int count, ...)
- 范围起始时间和柱线数量: Copy***(..., datetime start, int count, ...)
- 范围起始时间和结束时间: Copy***(..., datetime start, datetime stop, ...)
同时,参数表示法意味着所请求的数据具有时间序列式的索引方向,即索引为 0 的offset位置存储当前未完成柱线的数据,索引增大对应向价格历史的更早期移动。因此,特别是对于第二种选项,所指定的柱线数量 count将从 offset 范围起始点开始反向计数,即沿时间递减的方向计数。
第三种选项提供了更高的灵活性:起始日期和结束日期的指定顺序 (start/stop) 无关紧要,因为函数在任何情况下都会返回从较小日期到较大日期范围内的数据。符合条件的柱线将按以下方式选择:其开盘时间位于 start/stop时间点之间,或等于其中之一,即区间 [start; stop] 为包含边界的闭区间。
选择哪种函数选项取决于以下哪个因素更重要:是获取有保证的元素数量(例如用于机器学习算法),还是覆盖特定日期区间(例如针对预先确定的统一市场行为时段)。
datetime类型的时间表示精度为 1 秒。start/stop值无需按周期大小取整。例如,从 14:59 到 16:01 的时间范围将允许你在 H1 时间范围上选择 15:00 和 16:00 两个柱形。具有相等且取整标签的退化范围(例如 H1 报价中的 15:00)对应一个柱形。
即使起始/终止参数中存在非零的小时/分钟/秒(尽管 D1 时间范围的柱线标签时间为 00:00),你仍可请求日时间范围的柱线。在这种情况下,仅开盘时间位于start/stop最小值之后且截止到start/stop 最大值的 D1 柱线(由于所需时间包含小时/分钟/秒,因此此时不可能与日柱线的标签完全一致)。例如,在 D'2021.09.01 12:00' 至 D'2021.09.03 07:00' 之间,存在两根 D1 柱线的开盘时间 - D'2021.09.02' 和 D'2021.09.03'。这些柱线将包含在结果中。D'2021.09.01' 柱线的开盘时间为 00:00,早于时间范围的起始点,因此会被排除。D'2021.09.03' 柱线会被包含在结果中,尽管当天只有早上 7 小时属于该时间范围。另一方面,如果请求的是一天内的几个小时,例如在 D'2021.09.01 12:00' 至 D'2021.09.01 15:00' 之间,该时间段将不覆盖任何单个日柱线(D'2021.09.01' 柱线的开盘时间不在此范围内),因此接收数组将为空。
表格中所有函数之间的唯一区别在于接收数据的数组类型,该数组作为最后一个参数通过引用传递。例如,CopyRates函数将请求的数据放入结构体数组 MqlRates 中,而 CopyTime函数则将柱线开盘时间放入 datetime 类型的数组中,依此类推。
因此,常见函数原型可表示如下:
int Copy***(const string symbol, ENUM_TIMEFRAMES timeframe, int offset, int count, type &result[])
int Copy***(const string symbol, ENUM_TIMEFRAMES timeframe, datetime start, int count, type &result[])
int Copy***(const string symbol, ENUM_TIMEFRAMES timeframe, datetime start, datetime stop, type &result[])
此处,type与 MqlRates、datetime、double、long 或 int 中的任意一种类型匹配,具体取决于函数本身。
这些函数会返回复制到数组中的元素数量;如果发生错误,则返回 -1。具体来说,如果服务器上没有请求时间间隔内的数据,或该时间间隔超出了图表上的最大柱线数(通过 (TerminalInfoInteger(TERMINAL_MAXBARS)) 获取),我们将得到 -1。
需要特别注意的是,在接收数组中,所接收的数据始终按物理顺序按时间先后排列(从过去到未来)。因此,如果对接收数组使用标准索引(即通过 ArraySetAsSeries函数设置),则索引 0 处的元素为最早数据,而最后一个元素为最新数据。如果对数组执行了ArraySetAsSeries(result, true)指令,则编号将按时间序列的相反顺序进行:索引 0 处的元素将是时间范围内的最新数据,而最后一个元素将是最旧的数据。这一点如下图所示。

终端时间序列和接收数组
如果成功,终端自身(内部)时间序列中指定数量的元素将被复制到目标数组。当按日期范围 (start/stop) 请求数据时,结果数组中的元素数量将基于该范围内的历史内容间接确定。因此,要复制数量未知的值,建议使用动态数组:复制函数会独立分配目标数组所需的容量(大小可以增大或减小)。
如果你需要复制已知数量的元素,或者需要频繁进行复制操作(例如在 EA 交易的 OnTick 函数或指标的 OnCalculate 函数每次调用时),则最好使用静态分配的数组。事实上,动态数组的内存分配操作需要额外时间,并且可能影响性能,尤其是在测试和优化期间。
如果请求的数据尚未准备好,不同类型的 MQL 程序对时间序列的访问方式也会有所不同。例如,在自定义 指标, Copy 中,函数会立即返回错误,因为指标在终端的公共界面线程中执行,无法等待数据接收(通常假设指标会在其事件处理程序的下一次调用时请求数据,而此时时间序列已完成下载和构建)。此外,在关于指标的章节中,我们将了解到,要访问指标所在的“原生”图表的报价,指标无需使用 Copy函数,因为所有时间序列都会通过处理程序 OnCalculate的数组参数自动传递。
当从 EA 交易和脚本访问时,会尝试多次接收数据,并在每次尝试之间进行短暂暂停(函数内部会进行等待),以便系统有时间加载和计算缺失的时间序列。该函数将返回超时到期时已准备好的数据量,但历史数据加载会持续进行,下一次类似请求将返回更多数据。
无论如何情况,你都应做好准备,即 Copy函数可能会返回错误而非数据(原因多种多样:连接失败、请求的数据不存在、并行请求大量新时间序列导致处理器负载过高等):在代码中分析问题原因 (_LastError),稍后重试,修正设置,或通知用户。
交易品种是否显示在 Market Watch中并非使用Copy 函数请求其时间序列数据的必要条件,不过,对于已添加到该窗口的交易品种,相关查询通常会执行得更快,因为部分数据可能已从服务器下载完毕,并且可能已针对请求的周期完成计算。关于如何通过编程方式向 Market Watch添加交易品种,我们将在 编辑市场观察列表章节中详细介绍。
为了在实践中解释这些函数的工作原理,我们以脚本SeriesCopy.mq5为例进行说明。该脚本多次调用 CopyTime函数,这使你能够直观地看到时间戳与柱线编号之间的关联。
该脚本定义了一个动态数组times来接收数据。所有请求均针对 "EURUSD" 交易品种和 H1 时间范围。
void OnStart()
|
首先,请求从 2021 年 9 月 5 日开始,向过去方向的 10 根柱线数据。由于这一天是周日,因此前一个交易日的柱线是 3 日周五(见下方日志)。
PRTF(CopyTime("EURUSD", PERIOD_H1, D'2021.09.05', 10, times)); // 10 / ok
|
数组的输出默认按时间顺序进行(尽管函数参数是在反向坐标系中设置的:如在时间序列中)。我们来更改接收数组中的索引顺序,然后重新输出。
PRTF(ArraySetAsSeries(times, true)); // true / ok
|
对于后续实验,我们将恢复常规顺序。
PRTF(ArraySetAsSeries(times, false)); // true / ok |
现在,我们来请求两个时间点之间的不确定数量的柱线(数量未知,例如因为该时间段内可能存在假期)。我们将通过两种方式实现:第一种情况,指定从未来到过去的时间范围;第二种情况,指定从过去到未来的时间范围。结果两者一致。
// FROM TO
|
通过输出数组,我们可以看到它们是相同的。让我们回到时间序列索引模式,并讨论另一个要点。
PRTF(ArraySetAsSeries(times, true)); // true / ok
|
尽管两个时间戳相隔 24 小时,这意味着数组中应包含 25 个元素(请记住起始和结束时间都包含在内),但结果仅包含 4 根柱线。原因是 9 月 5 日是周日,因此在整个时间范围内,仅在 6 日上午有交易活动。
另外,请注意接收数组的大小已自动从 10 个元素缩减为 4 个。
最后,我们将从第 100 根柱线开始请求 10 根柱线(具体结果将取决于你当前的时间和可用历史数据)。
PRTF(CopyTime("EURUSD", PERIOD_H1, 100, 10, times)); // 10 / ok
|
由于采用时间序列式的索引方式,数组将按逆时间顺序显示。