在测试程序中生成分时报价
不强制要求 EA 交易中必须存在 OnTick 处理程序才能在测试程序中进行测试。EA 交易可以使用一个或多个其他熟悉的函数:
- OnTick 新分时报价到达的事件处理程序
- OnTrade 交易事件处理程序
- OnTradeTransaction 交易处理程序
- OnTimer 计时器信号处理程序
- OnChartEvent 图表(包括自定义图表)上的事件处理程序
同时,在测试程序内部,时间过程的主要等价指标是一串分时报价,其中不仅包含价格变化,还包含精确到毫秒的时间。因此,为了测试 EA 交易,有必要生成分时报价序列。MetaTrader 5 测试程序有 4 种分时报价生成模式:
- 真实分时报价(如果经纪商提供了其历史)
- 每条分时报价(基于可用的 M1 时段报价的模拟)
- 分钟柱线的 OHLC 价格(1 分钟 OHLC)
- 仅开盘价(每根柱线 1 笔分时报价)
另一种操作模式是数学计算,我们将在后面分析,因为这种模式与报价和分时报价无关。
无论用户选择 4 种模式中的哪一种,终端都会加载可用的历史数据进行测试。如果选择真实分时报价模式,并且经纪商没有该金融工具的真实分时报价,则使用“所有分时报价”模式。测试程序在其报告中以图形和百分比形式指示分时报价生成的特性(其中 100% 表示所有分时报价都是真实的)。
在开始测试过程之前,终端会从交易服务器同步并下载测试程序设置中所选金融工具的历史。同时,终端会第一次从交易服务器下载历史到需要的深度(留有一定裕量,视时间范围而定,至少在测试开始前 1 年),以免以后申请。之后,只会下载新数据。所有这些都会在测试程序日志中生成相应消息。
测试代理在测试开始后会立即从客户终端接收被测金融工具的历史。如果测试过程使用其他金融工具上的数据(例如,这是一个多币种 EA 交易),那么在这种情况下,测试代理会在第一次调用时从客户终端请求所需的历史。如果终端上有可用的历史数据,这些数据会立即传输到测试代理。如果数据丢失,终端将请求并从服务器下载数据,然后将其传送到测试代理。
在交易操作期间计算交叉汇率价格时,还会使用其他金融工具。例如,当测试存款货币为美元的 EURCHF 策略时,在处理第一笔交易操作之前,测试代理将从客户终端请求 EURUSD 和 USDCHF的历史,尽管该策略并不直接引用这些金融工具。
在这方面,在测试多币种策略之前,建议首先将所有必要的历史数据下载到客户终端。这将有助于避免与恢复数据相关的测试/优化延迟。例如,你可以通过打开相应的图表并将其滚动到历史的开头来下载历史。
现在我们更详细地了解一下分时报价生成模式。
来自历史数据的真实分时报价
对真实分时报价的测试和优化应尽可能接近真实条件。这些是交易所和流动性提供商的报价。
如果在交易品种的历史中有一个分钟柱线,但没有该分钟的分时报价数据,测试程序将在“每条分时报价”模式下生成分时报价(详见下文)。这允许你在经纪商的不完整分时报价数据的情况下,在测试程序中构建正确的图表。此外,由于各种原因,分时报价数据可能与分钟柱线不匹配。例如,由于从源到客户终端的数据传输断开或其他故障。测试时,分钟数据被认为更可靠。
分时报价存储在策略测试程序的交易品种缓存中。缓存大小为不超过 128,000 条分时报价。当新的分时报价到达时,最旧的数据会被推出。但是,使用 CopyTicks 函数,你可以获得缓存之外的分时报价(仅当使用真实分时报价进行测试时)。在这种情况下,将从测试程序的分时报价数据库请求数据,该数据库完全对应于客户终端的类似数据库。不会对该基数进行分钟柱线调整。因此,其中的分时报价可能与缓存中的分时报价不同。
每条分时报价(模拟)
如果没有真实分时报价历史,或者如果你需要最小化网络流量(因为实际分时报价的存档会消耗大量资源),你可以选择基于 M1 时间范围的可用报价人工生成分时报价。
金融工具的报价历史可以紧密打包的分钟柱线块形式从交易服务器传输到 MetaTrader 5 的客户终端。历史查询过程和所需时间范围的构造在 时间序列的组织和存储技术特性章节中详细介绍过。
价格历史的最小元素是一个分钟柱线,从中可以获得关于四个 OHLC 价格值的信息:开盘价、最高价、最低价和收盘价。
新的分钟柱线不是在新的一分钟开始时(秒钟数变为 0)打开,而是在出现分时报价(至少一个点的价格发生变化)时打开。类似地,我们不能从一秒精度的柱线确定对应于该分钟柱线收盘价的分时报价到达时间:我们只知道一分钟柱线的最新价,记录为收盘价。
因此,对于每个分钟柱线,我们知道 4 个控制点,我们可以肯定地说价格已经存在了。如果该柱线只有 4 条分时报价,那么这个信息就足够测试了,但是通常分时报价的数量都超过 4 个。这意味着需要为 Open、High、Low 和 Close 价格之间的分时报价生成额外的检查点。在“每条分时报价”模式下生成分时报价的基础知识在 文档中介绍过。
在“每条分时报价”模式下测试时,将对每个生成的分时报价调用 EA 交易的 OnTick 函数。EA 交易会以与在线工作时相同的方式接收时间和 Ask/Bid/Last 价格。
“每条分时报价”测试模式是最准确的(在真实分时报价模式之后),但也是最耗时的。对于大多数交易策略的主要估算,使用这两种简化测试模式中的一种就足够了:OHLC M1 价格或所选时段的开盘价。
1 分钟 OHLC
在“1分钟 OHLC”模式下,分时报价序列仅由分钟柱线的 OHLC 价格构建,OnTick 函数调用次数显著减少;因此,测试时间也减少。这是一种非常有效且有用的模式,在测试精度和速度之间提供了一种折衷。但是,当涉及到别的 EA 交易时,需要特别小心。
如果拒绝在 Open、High、Low 和 Close 价格之间生成额外的中间分时报价,会导致从 Open 价格被定义的那一刻起,价格的发展就出现了硬性决定。这样便可以创建一个 "Testing Grail",在测试时显示一个漂亮的上升趋势平衡图。
对于一个分钟柱线,已知 4 个价格,其中第一个为 Open 价格,最后一个为 Close 价格。它们之间记录的价格有 High 和 Low 价格,关于它们出现顺序信息会丢失,但我们知道 High 价格大于等于 Open 价格, Low 价格小于或等于 Open 价格。
收到 Open 价格后,我们只需要分析下一条分时报价,就可以确定它是 High 还是 Low 价格。如果该价格低于 Open 价格,即在这个分时报价上的 Low 价格 - 买入价,因为下一个分时报价将对应于 High 价格,在此处我们应关闭买入交易并开设卖出交易。下一个分时报价是柱线的最后一个,即 Close 价格,在此处我们将关闭卖出交易。
如果一个价格高于开盘价的分时报价出现在我们的价格之后,那么交易顺序就颠倒了。看起来,在这种模式下,可以在任何一根柱线上进行交易。当在历史上测试这样的 EA 交易时,一切都很完美,但在线测试却会失败。
由于计算算法(例如,统计计算)和分时报价生成的函数组合,可能会无意中产生类似的影响。
因此,在粗略测试模式(“1 分钟 OHLC” “仅开盘价”)下找到最佳的 EA 交易设置后,在“每条分时报价”模式下进行测试,或者更好地基于真实分时报价进行测试,总是非常重要的。
仅开盘价
在这种模式下,分时报价是使用所选测试时间范围的 OHLC 价格生成的。在这种情况下,OnTick 函数仅在每个柱线的开头运行一次。由于这个特性,止损水平和挂单可能以不同于请求的价格触发(特别是在更高的时间范围上测试时)。作为交换,我们有机会快速对 EA 交易进行评估测试。
例如,EA 交易在“仅开盘价”模式下进行 EURUSD H1 测试。在这种情况下,分时报价(控制点)的总数将是测试间隔内小时柱线数量的 4 倍。但在这种情况下,OnTick 处理程序只会在小时柱线打开时调用。对于其余的分时报价(对 EA 交易“隐藏”的),会执行正确测试所需的以下检查:
- 保证金要求的计算
- 触发 Stop Loss 和止盈 Take Profit
- 触发挂单
- 过期时删除挂单
如果没有未结仓位或挂单,就不需要对隐藏的分时报价进行这些检查,速度会显著提高。
在“仅开盘价”模式下生成分时报价时的一个例外是 W1 和 MN1 时段:对于这些时段,分时报价是为每天的 OHLC 价格生成的,而不是分别为每周或每月生成的。
这种“仅开盘价”模式非常适合测试只在柱线打开时、不使用挂单以及不使用 Stop Loss 和 Take Profit 水平的交易策略。对于这类策略,保留了所有必要的测试精度。
MQL5 API 不允许程序找出其在测试程序中运行的模式。同时,这可能对 EA 交易或其使用的指标很重要,例如,它们并未设计为正确处理开盘价或 OHLC。在这方面,我们实现了一个简单的模式检测机制。源代码附在 TickModel.mqh 文件中。
我们用现有模式来说明我们的枚举。
enum TICK_MODEL
|
除了第一个元素(保留用于模式尚未确定或由于某种原因无法确定的情况)之外,所有其他元素都以模拟质量的降序排列,从真实价格开始,以开盘价结束(对于这些元素,开发人员必须检查其交易仅在新柱线打开时进行这一事实的兼容性策略)。最后一种模式 _MODEL_MATH_CALC 运行时完全没有分时报价;我们会 单独介绍。
模式检测原理基于在开始测试时,对分时报价的可用性及其前两条分时报价时间的检查。检查本身被包装在 getTickModel 函数中,EA 交易应从 OnTick 处理程序中调用这个函数。由于检查只进行一次,静态变量模型在最初设置为分时报价 TICK_MODEL_UNKNOWN 的函数中进行说明。该模型可存储并切换检查的当前状态,这将需要区分 OHLC 模式和 开盘价。
TICK_MODEL getTickModel()
|
在第一个分析的分时报价上,该模型等于 TICK_MODEL_UNKNOWN,并试图通过调用 CopyTicks 来获得真实分时报价。
if(model == TICK_MODEL_UNKNOWN)
|
如果成功,检测立即结束,将该模型设置为 TICK_MODEL_REAL。如果没有真实分时报价,系统会返回某种错误代码,根据这个错误代码我们可以得出以下结论。错误代码 ERR_NOT_ENOUGH_MEMORY 对应于分时报价模拟模式。为什么该代码是这样的还不完全清楚,但是这是一个典型的特性,我们只是此处使用该代码。在其他两种分时报价生成模式中,我们将得到 ERR_FUNCTION_NOT_ALLOWED 错误。
你可以通过分时报价时间来区分这两种模式。如果结果不是一条分时报价时间范围的倍数,那么我们就是在讨论 OHLC 模式。但这里的问题是,两种模式中的第一条分时报价都可以与柱线打开时间对齐。因此,我们将获得 TICK_MODEL_OPEN_PRICES 值,但需要进行指定。因此,对于最终结论,应再分析一条分时报价(如果之前收到了 TICK_MODEL_OPEN_PRICES,则再次调用其函数)。对于这种情况,在函数内部提供了以下 if 分支。
else if(model == TICK_MODEL_OPEN_PRICES)
|
我们在一个简单的 EA 交易 TickModel.mq5 中检查检测程序的运行。在 TickCount 输入参数中,我们指定了分析的分时报价的最大数量,即 getTickModel 将被调用的次数。我们知道两条分时报价就足够了,但是为了确保模型随后不会改变,默认情况下,建议使用 5 条分时报价。我们还提供了 RequireTickModel 参数,如果模拟水平低于要求的水平,该参数会指示 EA 交易终止操作。默认情况下,其值为 TICK_MODEL_UNKNOWN,这意味着没有模式限制。
input int TickCount = 5;
|
在 OnTick 处理程序中,我们只在测试程序中运行代码时才运行它。
void OnTick()
|
我们通过选择 EURUSD H1 的常见组合,尝试在测试程序中以不同的分时报价生成模式运行 EA 交易。
EA 交易中的 RequireTickModel 参数被设置为 OHLC M1。如果测试程序模式是“每条分时报价”,我们将在日志中收到相应的消息,EA 交易将继续工作。
[time] [bid] [ask] [last] [volume] [time_msc] [flags] [volume_real] [0] 2022.04.01 00:00:30 1.10656 1.10679 1.10656 0 1648771230000 14 0.00000 NOT_ENOUGH_MEMORY 1 TICK_MODEL_GENERATED [time] [bid] [ask] [last] [volume] [time_msc] [flags] [volume_real] [0] 2022.04.01 0:01:00 1.10656 1.10680 1.10656 0 1648771260000 12 0.00000 2 TICK_MODEL_GENERATED [time] [bid] [ask] [last] [volume] [time_msc] [flags] [volume_real] [0] 2022.04.01 0:01:30 1.10608 1.10632 1.10608 0 1648771290000 14 0.00000 3 TICK_MODEL_GENERATED |
OHLC M1 和真实分时报价模式也适用,在后一种情况下,将不会有错误代码。
[time] [bid] [ask] [last] [volume] [time_msc] [flags] [volume_real] [0] 2022.04.01 00:00:00 1.10656 1.10687 0.0000 0 1648771200122 134 0.00000 1 TICK_MODEL_REAL [time] [bid] [ask] [last] [volume] [time_msc] [flags] [volume_real] [0] 2022.04.01 00:00:00 1.10656 1.10694 0.0000 0 1648771200417 4 0.00000 2 TICK_MODEL_REAL [time] [bid] [ask] [last] [volume] [time_msc] [flags] [volume_real] [0] 2022.04.01 00:00:00 1.10656 1.10691 0.0000 0 1648771200816 4 0.00000 3 TICK_MODEL_REAL |
但是,如果你将测试程序中的模式更改为“仅开盘价”,EA 交易将在第二条分时报价后停止。
[time] [bid] [ask] [last] [volume] [time_msc] [flags] [volume_real] [0] 2022.04.01 00:00:00 1.10656 1.10679 1.10656 0 1648771200000 14 0.00000 FUNCTION_NOT_ALLOWED 1 TICK_MODEL_OPEN_PRICES [time] [bid] [ask] [last] [volume] [time_msc] [flags] [volume_real] [0] 2022.04.01 1:00:00 1.10660 1.10679 1.10660 0 1648774800000 14 0.00000 2 TICK_MODEL_OPEN_PRICES Tick model is incorrect (TICK_MODEL_OHLC_M1 or better is required), terminating ExpertRemove() function called |
这种方法需要运行一次测试并等待几条分时报价来确定模式。换言之,我们不能通过从 OnInit 返回一个错误来提前停止测试。更重要的是,当使用错误类型的分时报价生成开始优化时,我们将无法停止优化,只能通过 OnTesterInit 函数来完成。因此,测试程序将尝试在优化过程中完成所有传递,尽管它们会在最开始时停止。这是当前平台的局限性。