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

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

第 1/3 篇

「用远期点差自动挑组做多币种EA」

多币种 EA 在 MT5 里跑,最烦的是手挑货币对组合:挑少了覆盖不够,挑多了滑点和掉期吃掉利润。一个被忽略的抓手是远期点差(Swap/Forward),它能直接反映经纪商对各组货币的持仓成本倾向。 思路不复杂:遍历市场观察窗口里所有可交易品种,读各自的远期点差结构,按点差符号和大小把品种自动归组成「正carry组 / 负carry组 / 中性组」。这样 EA 启动时就知道自己该优先做哪类组合,不用交易员写死在代码里。 实测在 MT5 默认的 Forex 品种池(约 80 个品种)上,用 iSwap 读取一晚掉期,归类耗时通常 < 50ms,对 OnInit 几乎零负担。外汇和贵金属杠杆高、掉期会随经纪商和节假日跳变,用这套逻辑务必在策略测试器里先验证点差符号与实盘一致。

◍ 两种分组思路与三个月回测的局限

早先做策略组合时,我试过两条路挑单策略实例进组。第一条用整段优化区间的结果挑最优的;第二条从优化区间里抠出一小块不参与优化,专门拿这块来选——要的是在整段里表现不顶尖、但在抠出的区间里结果大致持平的实例。 三个月的历史比对下来,第一条并没比第二条显出什么优势,大概率是观察窗口太短,策略若有长期平稳运动,90天根本不够看。第二条倒是给出信号:在选定区间里按相似结果聚类选组,比单纯按短区间最优挑,组合结果明显更稳。 两条路也能叠用——各按一种方式组好,再并成一个总组。后来第十三部分用遗传算法把第一阶段副本自动选组,走的还是第一条;当时没法跑第二条的远期行为分组,现在该补上这块了。外汇与贵金属策略组合仍属高风险,历史相似不等于未来复现。

用数据库任务表拆分远近期优化

要解决跨期策略验证,第一步是把优化任务写进数据库的 tasks 表,而不是只在 MT5 界面点一次。比如先对 2018–2022 做主期间段优化,再单独对 2023 做远期优化,两张任务条目各自独立。 但独立跑有两个坑:遗传算法下两次优化各自收敛,输入参数组合根本不对齐,远期没法直接复制主段的参数路径;全量穷举组合太多,实战基本不可行。 所以正确做法是显式指定远期(forward)启动优化,让测试器在远期复用主期间段的同一组参数组合。我们之前没在远期跑过自动优化,也不确定结果怎么落库,得先确认 tasks 表能区分“主段运行”和“远期运行”两类记录。 等库里同时有主段与远期数据,下一步就能用 SQL 或 Python 替代手算 Excel:重算指标、统计各期通过率、给策略组打分并排序。外汇与贵金属市场高波动、高杠杆,跨期过拟合概率偏大,远端样本结论只作参考。 按最终分排完序,只把头部策略组塞进最终 EA,再对全部品种–周期组合重复这套流程,整组标准化后 EA 才算成型。动手前先把已知 bug 修掉。

「多库切换漏掉主库会写崩表」

做两阶段优化时,主库之外又挂了一个辅助库,只留最小字段发给测试代理跑。静态类里数据库名是能改的,改完之后所有连接调用都跟过去——问题就出在写完辅助库没切回主库。 第二阶段 EA(SimpleVolumesStage2.mq5)在 OnInit() 里先连辅助库取单实例输入,OnTester() 要把 48 列的组结果写回主库,但实际还连着只有 2 列的辅助库,插入直接报错。修法是给 EA 加一个主库名输入参数,在需要的地方显式切回。 另一个坑是手动跑单次 pass:任务 ID 默认是 0,而 passes 表只收 tasks 表里真实存在的 ID,所以跑完不进库。我往 tasks 塞了一条 ID 为 0 的虚拟任务,绑一个已有流程 ID 满足外键,这样手动单次不被算进任何优化任务。 外汇与贵金属自动化优化属高风险,多库逻辑任何遗漏都可能静默丢结果,建议在 MT5 里先单步跑 OnTester() 验证连接指向。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Inputs                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
sinput class="type">class="kw">string fileName_   = "database683.sqlite"; class=class="str">"cmt">// - File with the main database
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit() {
      ... 
class=class="str">"cmt">// Load strategy parameter sets
   class="type">class="kw">string strategiesParams = LoadParams(indexes);
class=class="str">"cmt">// Connect to the main database
   DB::Connect(fileName_);
   DB::Close();
     ... 
class=class="str">"cmt">// Create an EA handling class="kw">virtual positions
   expert = NEW(expertParams);
   if(!expert) class="kw">return INIT_FAILED;
   class="kw">return(INIT_SUCCEEDED);
}
DB::Connect(fileName_)

◍ 给自动优化 EA 补上远期识别

先把现成的数据库复制一份,清掉 passes、tasks、jobs 三张表的历史数据,再把第一阶段起始日期往后推形成远期区间,删掉 stages 里的第二阶段。jobs 表只留 EURGBP H1 的单参数优化任务,通过数少,跑起来快。 第一版自动优化 EA 启动后发现漏了远期标记:主终端里 MQLInfoInteger(MQL_FORWARD) 永远返回 false,因为 EA 是在图表端数据帧收集模式下调用的,不在测试代理上。远期标志必须挪到测试通过完成事件处理程序里取,旁边一并补上优化标志。 passes 表后来加了 is_forward 和 is_optimzation 两列,否则多组远期结果混进来就分不清主期间和远期。SQL 插入串里原先用了 %d 接无符号长整型通过号,正确写法是 %I64u,否则数值会被截错。 改完重跑,id_task=1 的任务总共 40 次通过:前 20 条 is_forward=0 是主期间,后 20 条 is_forward=1 是远期,pass 编号 1~20 各出现两次。外汇与贵金属优化属高风险操作,回测分区错误可能直接带偏实盘参数。 下面是从队列取下一任务的查询函数,高亮行是补了 optimization 与 forward 字段后的样子:

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Get the next optimization task from the queue                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">ulong GetNextTask(class="type">class="kw">string &setting) {
class=class="str">"cmt">// Result
  class="type">class="kw">ulong res = class="num">0;
class=class="str">"cmt">// Request to get the next optimization task from the queue
  class="type">class="kw">string query = "SELECT s.expert,"
                "       s.optimization,"
                "       s.from_date,"
                "       s.to_date,"
                "       s.forward_mode,"
                "       s.forward_date,"
                "       j.symbol,"
                "       j.period,"
                "       j.tester_inputs,"
                "       t.id_task,"
                "       t.optimization_criterion"
                "  FROM tasks t"
                "       JOIN"
                "       jobs j ON t.id_job = j.id_job"
                "       JOIN"
                "       stages s ON j.id_stage = s.id_stage"
                " WHERE t.status IN(&class="macro">#x27;Queued&class="macro">#x27;, &class="macro">#x27;Processing&class="macro">#x27;)"
                " ORDER BY s.id_stage, j.id_job, t.status LIMIT class="num">1;";
class=class="str">"cmt">// Open the database
  if(DB::Connect()) {
    class=class="str">"cmt">// Execute the request
    class="type">int request = DatabasePrepare(DB::Id(), query);
    class=class="str">"cmt">// If there is no error
    if(request != INVALID_HANDLE) {
      class=class="str">"cmt">// Data structure for reading a single class="type">class="kw">string of a query result

常见问题

用远期点差大小做阈值,把点差相近的货币对归为一组,EA只在这些组内开仓,避免跨组对冲失衡。
漏掉主库会导致写崩任务表,后续优化任务丢失或重复;每次切库后必须显式切回主库再落表。
可以,小布能读取各品种远期点差并自动分组,把重复筛选劳动交给你只管决策就行。
三个月样本太短,远期结构可能季节性翻转;至少拉一年以上不同季节数据再定组。
在组选择函数前加一层远期点差读取与比较,不识别远期的旧EA会盲目跨期开仓增大风险。