在终端获取数据帧
通过 FrameAdd 函数从测试代理发送的帧被传送到终端,并按照接收顺序写入 mqd 文件,该文件以 EA 交易的名称命名,位于文件夹 terminal_directory/MQL5/Files/Tester 中。一个或多个帧同时到达会生成 OnTesterPass 事件。
MQL5 API 提供了下列 4 个用于分析和读取帧的函数:FrameFirst、FrameFilter、FrameNext 和 FrameInputs。所有函数都会返回一个布尔值,指示成功 (true) 或错误 (false)。
为了访问现有的帧,内核采用了指向当前帧的内部指针这一隐喻机制。当 FrameNext 函数读取下一帧时,指针会自动向前移动,但可以使用 FrameFirst 或 FrameFilter 返回到所有帧的开头。因此,MQL 程序可以在一个循环中组织帧的迭代,直到检查完所有的帧。如果需要,可以重复这个过程,例如,通过在 OnTesterDeinit 中应用不同的筛选器。
bool FrameFirst()
FrameFirst 函数可将内部帧读取指针设置为开始,并重置筛选器(如果之前使用 FrameFilter 函数设置了筛选器)。
理论上,对于所有帧的单次接收和处理,没有必要调用 FrameFirst,因为当优化开始时,指针已经在开头位置。
bool FrameFilter(const string name, ulong id)
该函数可设置帧读取筛选器并将内部帧指针设置为开头位置。筛选器将影响 FrameNext 的后续调用中包含哪些帧。
如果一个空字符串作为第一个参数传递,筛选器将只通过一个数字参数工作,即所有具有指定 id 的帧。如果第二个参数的值等于 ULONG_MAX,那么只有文本筛选器起作用。
调用 FrameFilter("", ULONG_MAX) 相当于调用 FrameFirst(),相当于没有筛选器。
如果你在 OnTesterPass 中调用l FrameFirst 或 FrameFilter ,请确保确实是你所需要的:其代码可能包含逻辑错误,因为它可能会循环、读取相同帧或以指数方式增加计算负载。
bool FrameNext(ulong &pass, string &name, ulong &id, double &value)
bool FrameNext(ulong &pass, string &name, ulong &id, double &value, void &data[])
FrameNext 函数可读取一帧并将指针移动到下一帧。pass 参数中可记录优化测试轮次编号。name、id 和 value 参数可接收在 FrameAdd 函数的相应参数中传递的值。
值得注意的是,当没有更多帧需要读取时,该函数在正常运行时可能会返回 false。在这种情况下,内置变量 _LastError 包含值 4000(没有内置符号)。
无论使用哪种形式的 FrameAdd 函数来发送数据,文件或数组的内容都会放在接收data 数组中。接收数组的类型必须与发送数组的类型相匹配,在发送文件的情况下有一些细微差别。
二进制文件 (FILE_BIN) 最好在字节数组 uchar 中接受,以确保与任何大小兼容(因为其他更大的类型可能不是文件大小的倍数)。如果文件大小(实际上为接收帧中数据块的大小)不是接收数组类型大小的倍数,FrameNext 函数将不会读取数据,并将返回 INVALID_ARRAY (4006) 错误。
Unicode 文本文件(没有 FILE_ANSI 修饰符的 FILE_TXT 或 FILE_CSV)应被 ushort 类型的数组接受,然后通过调用 ShortArrayToString 转换为字符串。ANSI 文本文件应以 uchar 数组的形式接收,并使用 CharArrayToString 进行转换。
bool FrameInputs(ulong pass, string ¶meters[], uint &count)
FrameInputs 函数允许你获取 EA 交易 input 参数的说明和值,形成具有指定传递编号的测试轮次。parameters 字符串数组用类似 "ParameterNameN=ValueParameterN" 的行填充。count 参数用 parameters 数组中的元素数填充。
这四个函数只允许在 OnTesterPass 和OnTesterDeinit 处理程序内部进行调用。
帧可以成批到达终端,在这种情况下,其需要时间传送。因此,没有必要让它们都有时间生成 OnTesterPass 事件,并一直处理到优化结束。在这方面,为了保证接收到所有迟到的帧,有必要在 OnTesterDeinit 中放置一个代码块对其进行处理(使用 FrameNext 函数)。
考虑一个简单的示例 FrameTransfer.mq5。
EA 交易有四个测试参数。除了最后一个字符串,所有这些参数都可以包含在优化中。
input bool Parameter0;
|
但是,为了简化该示例,参数 Parameter1 和 Parameter2 的步骤数被限制为 10 步(对于每个参数)。因此,如果不使用 Parameter0,最大传递次数为 121 次。 Parameter3 是一个不能包含在优化中的参数示例。
EA 交易不进行交易,而是生成模拟任意应用数据的随机数据。切勿在你的工作项目中像这样使用随机化:其仅适用于演示。
ulong startup; // track the time of one run (just like demo data)
|
数据以两种类型的帧发送:文件和数组。每种类型都有自己的标识符。
#define MY_FILE_ID 100
|
该文件以二进制格式编写,包含简单的字符串。OnTester 的结果(准则)是一个简单的算术表达式,包含 Parameter1 和 Parameter2。
在接收端,在终端图表上以服务模式运行的 EA 交易实例中,我们可从所有带文件的帧中收集数据,并将它们放入一个公共 CSV 文件中。该文件可在 OnTesterInit 处理程序中打开。
int handle; // file for collecting applied results
|
如前所述,不一定所有帧都有时间进入 OnTesterPass 处理程序,需要在 OnTesterDeinit 中进行额外检查。因此,我们实现了一个辅助函数 ProcessFileFrames,我们将通过 OnTesterPass 和 OnTesterDeinit调用该函数。
在 ProcessFileFrames 中,我们保存了已处理帧的内部计数器 framecount。以此为例,我们将确保帧的到达顺序和测试轮次编号通常不会匹配。
void ProcessFileFrames()
|
为了在函数中接收帧,说明了根据原型 FrameNext 所需的变量。此处,接收数据数组描述为 uchar。如果我们要写一些结构体到我们的二进制文件中,我们可以把它们直接放入一个相同类型的结构体数组中。
ulong pass;
|
下面介绍了几个变量,用于获得帧所属的当前测试轮次的 EA 交易输入。
string params[];
|
然后我们用 FrameNext 循环读取帧。请注意,几个帧可以同时进入该处理程序,所以需要一个循环。对于每个帧,我们向终端日志输出轮次编号、帧名和生成的 double 值。我们可跳过 ID 不同于 MY_FILE_ID 的帧,稍后再处理它们。
ResetLastError();
|
对于具有 MY_FILE_ID 的帧,我们执行以下操作:查询输入变量,找出哪些变量包含在优化中,并将它们的值与来自帧的信息一起保存到公共 CSV 文件中。当帧数为 0 时,我们可在 header 变量中形成 CSV 文件的标题。在所有帧中,CSV 文件的当前(新)记录是在 record 变量中形成的。
void ProcessFileFrames()
|
调用 ParameterGetRange 也可以更有效地完成,只是 framecount 的值为零。你可以尝试这样做。
在 OnTesterPass 处理程序中,我们只调用 ProcessFileFrames。
void OnTesterPass()
|
此外,我们从 OnTesterDeinit 调用相同的函数并关闭 CSV 文件。
void OnTesterDeinit()
|
在 OnTesterDeinit 中,我们用 MY_TIME_ID 处理帧。测试轮次的持续时间在这些帧中提供,并且一个轮次的平均持续时间在此处计算。理论上,只在程序中进行分析时这样做是有意义的,因为对于用户而言,测试程序已经在日志中显示了测试轮次的持续时间。
void OnTesterDeinit()
|
EA 交易准备就绪。我们为其启用完全优化(因为选项的总数是人为限制的,对于遗传算法而言太小了)。我们只能选择开盘价,因为 EA 交易并不执行交易。因此,你应选择一个自定义准则(所有其他准则将给出 0)。例如,我们以单步长从 1 到 10 设置范围 Parameter1,以 0.1 步长从 -0.5 到 +0.5 设置 Parameter2。
我们运行优化。在终端的专家日志中,我们将看到关于接收帧的条目,格式如下:
Pass: 0 Frame: binfile Value:5105.000000
|
带有轮次编号、参数值和帧内容的相应行将出现在 output.csv 文件中:
Counter,Pass ID,Parameter1,Parameter2,Value,File Content
|
显然,我们的内部编号(Count 列)是按顺序进行的,轮次编号 Pass ID 可以混合(具体取决于代理并行处理批任务的许多因素)。特别是,该批任务可以是第一次分配完成具有较高序号任务的代理的任务:在这种情况下,文件中的编号将从较高的轮次开始。
在测试程序的日志中,你可以按帧检查服务的统计数据。
242 frames (42.78 Kb total, 181 bytes per frame) received
|
需要注意的是,在遗传优化过程中,运行编号在优化报告中是成对 (generation number, copy number) 出现的,而在 FrameNext 函数中获得的传递编号为 ulong。事实上,其为当前优化运行环境中批次作业的轮次编号。MQL5 没有提供将轮次编号与遗传报告相匹配的方法。为此,应计算每个轮次输入参数的校验和。具有优化缓存的 Opt 文件已经包含这样一个具有 MD5 散列的字段。