开发多币种 EA 交易(第 18 部分):考虑远期的自动化组选择·进阶篇
◍ 把优化任务记录拼回测试配置
在 MT5 的本地或云优化结果库里,每条任务都带着专家名、品种、周期与样本区间。想复跑某一组参数,得先把库里的字段重新组装成 [Tester] 区块,否则测试器不认。 下面这段结构把一次优化任务的元数据收进 Row。expert 存 EA 名,optimization 用整型标记是否开启优化(0/1),forward_mode 与 forward_date 控制前向检验的切分方式,id_task 是任务在库里的唯一 ulong 编号。 读取首行后,用 StringFormat 把这些字段填回 ini 样式的字符串。注意 Expert=%s、Optimization=%d、ForwardMode=%d、ForwardDate=%s 四处直接来自 Row,其余如 Deposit=10000、Leverage=200 是写死的测试环境常量,改它们不会影响优化结果本身,但会改变权益曲线基准。外汇与贵金属品种杠杆高,重跑前请确认 Deposit 与 Leverage 和你的实盘风险偏好一致。 别把常量当默认值 Deposit 和 Leverage 写死在模板里,若直接拿去测 XAUUSD 却留着 200 倍杠杆, Margin Call 概率会被低估。复制代码后先改这两行再跑。
class="kw">struct Row { class="type">class="kw">string expert; class="type">int optimization; class="type">class="kw">string from_date; class="type">class="kw">string to_date; class="type">int forward_mode; class="type">class="kw">string forward_date; class="type">class="kw">string symbol; class="type">class="kw">string period; class="type">class="kw">string tester_inputs; class="type">class="kw">ulong id_task; class="type">int optimization_criterion; } row; class=class="str">"cmt">// Read data from the first result class="type">class="kw">string if(DatabaseReadBind(request, row)) { setting = StringFormat( "[Tester]\r\n" "Expert=%s\r\n" "Symbol=%s\r\n" "Period=%s\r\n" "Optimization=%d\r\n" "Model=class="num">1\r\n" "FromDate=%s\r\n" "ToDate=%s\r\n" "ForwardMode=%d\r\n" "ForwardDate=%s\r\n" "Deposit=class="num">10000\r\n" "Currency=USD\r\n" "ProfitInPips=class="num">0\r\n" "Leverage=class="num">200\r\n" "ExecutionMode=class="num">0\r\n" "OptimizationCriterion=%d\r\n" "[TesterInputs]\r\n"
「从数据库行拼出优化任务参数」
这段逻辑处在任务派发函数末尾,作用是把 SQL 查询结果的一行记录,按格式字符串拼成可回放的配置文件内容,并把任务 id 作为返回值带出去。 格式化串里 "idTask_=%d\r\n" 与 "fileName_=%s\r\n" 都是写盘用的键值对,后面紧跟的 %s\r\n 留给通用备注段,整体对应 GetProgramPath(row.expert) 取出的 EA 相对路径、row.symbol、row.period 等字段。 如果 SQL 查询或取行失败,两个 else 分支都会用 PrintFormat 打出 __FUNCTION__ 和 GetLastError() 代码,方便在 MT5 专家日志里直接定位断点;正常走完则调用 DB::Close() 释放连接。 GetProgramPath 单独负责路径换算:先用 MQLInfoString(MQL_PROGRAM_PATH) 拿当前程序全路径,再截掉 TERMINAL_DATA_PATH 下的 "\MQL5\Experts\" 前缀,只保留 EA 相对终端根目录的那一段,供集群节点在自己的盘符结构里重建绝对路径。
"idTask_=%d\r\n"
"fileName_=%s\r\n"
"%s\r\n",
GetProgramPath(row.expert),
row.symbol,
row.period,
row.optimization,
row.from_date,
row.to_date,
row.forward_mode,
row.forward_date,
row.optimization_criterion,
row.id_task,
fileName_,
row.tester_inputs
);
res = row.id_task;
} else {
class=class="str">"cmt">// Report an error if necessary
PrintFormat(__FUNCTION__" | ERROR: Reading row for request \n%s\nfailed with code %d",
query, GetLastError());
}
} else {
class=class="str">"cmt">// Report an error if necessary
PrintFormat(__FUNCTION__" | ERROR: request \n%s\nfailed with code %d", query, GetLastError());
}
class=class="str">"cmt">// Close the database
DB::Close();
}
class="kw">return res;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Getting the path to the file of the optimized EA from the current|
class=class="str">"cmt">//| folders relative to the root folder of terminal EAs |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string GetProgramPath(class="type">class="kw">string name) {
class="type">class="kw">string path = MQLInfoString(MQL_PROGRAM_PATH);
class="type">class="kw">string programName = MQLInfoString(MQL_PROGRAM_NAME) + ".ex5";
class="type">class="kw">string terminalPath = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Experts\\";
path = StringSubstr(path,
StringLen(terminalPath),把优化结果写进数据库的那几行
这段逻辑干的事很直接:每跑完一次测试代理的 pass,就把这次的参数和结果拼成 SQL 插进去,方便后面按 rowid 捞出来比。 GetInsertQuery 里用 StringFormat 拼出 INSERT 语句,s_idTask 和 pass 是任务与遍号,MQLInfoInteger(MQL_FORWARD) 强转 int 标记是不是前向测试,values、inputs 和本地时间字符串一并写库。 Tester 回调里另拼了一段 data:优化标志、前向标志加 data 和 params,格式是 "%d, %d, %s,'%s'"。开 MT5 把这段塞进自己的 CTesterHandler,跑一遍优化,数据库里就能看到每遍的自定义准则和 EA 参数描述。 外汇与贵金属品种波动受杠杆和消息面影响大,回测通过不代表实盘概率同向,验证时请控制仓位。
StringLen(path) - (StringLen(terminalPath) + StringLen(programName))); class="kw">return path + name; } class="type">class="kw">string CTesterHandler::GetInsertQuery(class="type">class="kw">string values, class="type">class="kw">string inputs, class="type">class="kw">ulong pass) { class="kw">return StringFormat("INSERT INTO passes " "VALUES(NULL, %d, %I64u, %d, %s,\n&class="macro">#x27;%s&class="macro">#x27;,\n&class="macro">#x27;%s&class="macro">#x27;) RETURNING rowid;", s_idTask, pass, (class="type">int) MQLInfoInteger(MQL_FORWARD), values, inputs, TimeToString(TimeLocal(), TIME_DATE | TIME_SECONDS)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Handling completion of tester pass for agent | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CTesterHandler::Tester(class="type">class="kw">double custom, class=class="str">"cmt">// Custom criteria class="type">class="kw">string params class=class="str">"cmt">// Description of EA parameters in the current pass ) { class=class="str">"cmt">// ... class=class="str">"cmt">// Generate a class="type">class="kw">string with pass data data = StringFormat("%d, %d, %s,&class="macro">#x27;%s&class="macro">#x27;", MQLInfoInteger(MQL_OPTIMIZATION), MQLInfoInteger(MQL_FORWARD), data, params); class=class="str">"cmt">// ... }
◍ 两阶段实盘化优化怎么排
数据库里远期通过结果核对无误后,就能跑更接近真实环境的自动优化。做法是往干净库里塞两个阶段:第一阶段只优化策略单实例,限定 EURGBP H1、2018–2023,不挂远期;第二阶段拿第一阶段筛出的好实例再优化,整段 2023 年划给远期。 jobs 表给每阶段建任务,除了品种周期,还要写清 EA 输入参数的优化区间和步长。第一作业(id_job=1)跑了 optimization_criterion 从 0 到 7 共 8 种标准,首尾两次用复杂标准 7;第二作业(id_job=2)固定走自定义标准 6。 在任意图表挂自动优化 EA,等代理把任务跑完。实测用现有代理全程约 4 小时,外汇与贵金属品种波动大、滑点跳空频繁,优化结果仅代表历史样本概率倾向,实盘前务必小仓验证。
「点云里藏着过拟合的尾巴」
主周期优化里,自定义标准化年平均利润落在 1000–8000 美元区间。图5点云中贴着 0 轴的红点是无效参数组——某些实例索引组合算出重复值,被直接剔除,不参与后续评估。 远期阶段因为主周期已砍掉劣质组合,样本从约 17000 缩到 13000。第一轮优化下,远期点云(图6)显示:通过次数中段利润冲高,越往后均值掉得越快,临近右边界时下滑加速,并不随迭代单调变好。 换一组迭代范围重跑,主周期区间变成 1500–5000 美元,远期却反转成「尾段才出最大值」,且整体均值比主周期小约 10 倍(首轮仅小 3 倍)。外汇与贵金属品种波动结构易变,这种跨期背离就是过拟合的典型指纹。 直觉上该挑主周期与远期结果接近的通过组来保稳定,但远期峰值远弱于主周期平庸值,实盘外推有效性存疑。下一脚还是踩下去:定位两期「接近」的通过,拉出它们在主周期、远期及 2024 年的真实表现再说。