开发多币种 EA 交易(第 18 部分):考虑远期的自动化组选择·进阶篇
📘

开发多币种 EA 交易(第 18 部分):考虑远期的自动化组选择·进阶篇

第 2/3 篇

◍ 把优化任务记录拼回测试配置

在 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 概率会被低估。复制代码后先改这两行再跑。

MQL5 / C++
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 相对终端根目录的那一段,供集群节点在自己的盘符结构里重建绝对路径。

MQL5 / C++
          "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 参数描述。 外汇与贵金属品种波动受杠杆和消息面影响大,回测通过不代表实盘概率同向,验证时请控制仓位。

MQL5 / C++
              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 年的真实表现再说。

常见问题

按品种和 timeframe 做主键从结果表取回参数行,再用脚本逐字段映射回 ini 配置即可,别手动抄。
先离线粗筛参数区间,再对存活组做窄步长重优化;中间用任务状态表隔离,避免重叠写库。
小布可读取你的优化结果库,按稳定度和点云离散度标出低过拟合组,直接给观察清单。
大概率是。远端稀疏点越多,实盘失效概率越高,建议砍掉尾巴再验一次样本外。
注意浮点精度截断和符号反向,拼完先用单品种回测校验一笔,再批量跑。