开发多币种 EA 交易(第 20 部分):整理自动项目优化阶段的输送机(一)(基础篇)
多币种EA的优化输送机怎么搭
做多币种 EA 时,最容易被忽视的不是策略逻辑,而是自动优化阶段的数据流转通道。在 MT5 里,测试器跑完一组参数后,结果若不能自动喂给下一阶段,就得人工导出再导入,既慢又容易错版本。 这里说的「输送机」,本质是一套把优化结果按币种、按时间窗归档并转交后续流程的机制。以 2025 年 10 月 6 日发布的系列第 20 部分为例,作者 Yuriy Bykov 把该阶段拆成连续子任务,让测试器在每轮结束后直接写文件,而不是等人工收口。 实操上,开 MT5 后先确认「测试器 → 优化 → 使用自定义标准」是否开启,否则输送机拿不到每轮的中间结果。外汇与贵金属品种波动属性差异大,多币种同跑时务必单独隔离各品种的输出目录,否则高杠杆品种的结果可能覆盖黄金的回测数据,带来误判风险。
◍ 让优化任务自己跑完整个流水线
这套自动优化系统的核心,是让一个「优化 EA」在无人干预下,从数据库里捞任务、跑参数组合、再把结果写回去。它盯着的 task 表目前只用三个状态值:Queued(排队)、Process(处理)、Done(完成),EA 每轮先扫 Queued 任务,跑完就把状态翻成 Done。 现在的问题在于,状态机只覆盖了 task 这一类。作业(job)、阶段(stage)、项目(project)这些对象在库里还没有自动状态流转,仍得人工去推。 下一步要做的,是把状态自动更新扩展到所有对象类别,并串起必要阶段的自动执行。目标很直接:一路跑到底,产出一个不依赖数据库连接、能独立工作的终版 EA。外汇与贵金属品种波动剧烈,这类全自动优化若直接上实盘,参数过拟合风险很高,建议先在 MT5 策略测试器里用历史数据验证。
「状态流转与任务排序的重构思路」
先把数据库里带状态字段的对象类逐一梳理,给每一类状态切换定死明确规则。规则落地有两种路径:要么在优化 EA 或阶段 EA 里发 SQL 查询主动改状态,要么在数据库侧写触发器,数据变更事件一触发就自动跑。 过去训练时每次都建新库,按固定次序往里塞项目阶段、工作和任务,优先级靠人工顺序就能定。现在要在一个库里存多个项目、甚至自动进新项目,老办法彻底失效,任务完成顺序的判定逻辑必须重新设计。 验证整条自动优化流水线前,原先手动的活儿得尽量自动化。比如第二阶段优化完,要挑最佳参数组进最终 EA,以前是人在输送机外手动跑第三阶段 EA,还用第三方 MQL5 数据库接口手填第二阶段的最优 ID。这类操作要封装进流水线。 改完的目标是一条全自动的优化输送线直接产出最终 EA。顺带要验证一件事:不同交易策略下,第二阶段及之后的阶段逻辑是否真的一致;以及小项目拆分和大项目里加阶段,哪种更利于提升效率。
状态机级联:从项目到任务的递推规则
自动优化输送机的状态模型里,项目、阶段、作业、任务四类对象共用同一套状态枚举:Queued(排队)、Process(处理中)、Done(完成)。早期库里项目还多一个 Created、任务用 Processing,现在已统一命名,避免触发器逻辑分支膨胀。 状态变更靠级联触发。把项目置为 Queued 的那一刻,它下属所有阶段、阶段下所有作业、作业下所有任务都会被递归标成 Queued,同时任务层清空起止日期。整棵对象树静止在 Queued,直到 Optimization.ex5 EA 拉起。 EA 启动后扫描到首个 Queued 任务,将其翻成 Process:写开始时间、清掉历史通过记录、把所属作业也置 Process,作业再带阶段、阶段带项目一路向上翻成 Process。任务跑完置 Done,写结束时间,若同作业已无 Queued 任务则作业 Done,同理向上递推;最后一阶段最后一作业最后一任务 Done 时,项目才整体 Done。 这套规则直接决定后续触发器怎么写——任何一层状态跳变都要在数据库侧自动向下广播或向上回收,否则 conveyor 会在中间层卡死。
◍ 级联触发器怎么串起任务流
把优化项目跑起来,第一步是让状态机自己往下淌。项目被置为 Queued 后,下面的阶段、作业、任务应当层层同步成 Queued,靠的是 AFTER UPDATE 触发器逐级 UPDATE,而不是在应用层手动刷。 任务真正开始跑的时候,要写 start_date、清掉上次传递的 passes 数据,并把所属作业翻成 Process;作业变 Process 再推阶段,阶段变 Process 再推项目,形成自下而上的级联。 任务 Done 的触发器里有个判断点:同作业队列里若还有 Queued 的任务,作业保持 Process,否则作业也 Done。这一阶段联级到阶段和项目,状态才会最终收敛。 项目自身置 Done 时,可顺手把残留的未执行、进行中任务全标 Done,从而倒逼作业与阶段收口;这不是自动优化的必选项,但手动收尾时很省事。 下面这段 SQL 是上面逻辑的直接落地,开任意 SQLite 后端粘进去就能建表触发: CREATE TRIGGER upd_project_status_queued AFTER UPDATE OF status ON projects WHEN NEW.status = 'Queued' BEGIN UPDATE stages SET status = 'Queued' WHERE id_project = NEW.id_project; END; CREATE TRIGGER upd_stage_status_queued AFTER UPDATE ON stages WHEN NEW.status = 'Queued' AND OLD.status <> NEW.status BEGIN UPDATE jobs SET status = 'Queued' WHERE id_stage = NEW.id_stage; END; CREATE TRIGGER upd_job_status_queued AFTER UPDATE OF status ON jobs WHEN NEW.status = 'Queued' BEGIN UPDATE tasks SET status = 'Queued' WHERE id_job = NEW.id_job; END; CREATE TRIGGER upd_task_status_queued AFTER UPDATE OF status ON tasks WHEN NEW.status = 'Queued' BEGIN UPDATE tasks SET start_date = NULL, finish_date = NULL WHERE id_task = NEW.id_task; END; CREATE TRIGGER upd_task_status_process AFTER UPDATE OF status ON tasks WHEN NEW.status = 'Process' BEGIN UPDATE tasks SET start_date = DATETIME('NOW') WHERE id_task = NEW.id_task; DELETE FROM passes WHERE id_task = NEW.id_task; UPDATE jobs SET status = 'Process' WHERE id_job = NEW.id_job; END; CREATE TRIGGER upd_job_status_process AFTER UPDATE OF status ON jobs WHEN NEW.status = 'Process' BEGIN UPDATE stages SET status = 'Process' WHERE id_stage = NEW.id_stage; END; CREATE TRIGGER upd_stage_status_process AFTER UPDATE OF status ON stages WHEN NEW.status = 'Process' BEGIN UPDATE projects SET status = 'Process' WHERE id_project = NEW.id_project; END; CREATE TRIGGER upd_task_status_done AFTER UPDATE OF status ON tasks WHEN NEW.status = 'Done' BEGIN UPDATE tasks SET finish_date = DATETIME('NOW') WHERE id_task = NEW.id_task; UPDATE jobs SET status = ( SELECT CASE WHEN ( SELECT COUNT( * ) FROM tasks t WHERE t.status = 'Queued' AND t.id_job = NEW.id_job ) > 0 THEN 'Process' ELSE 'Done' END) WHERE id_job = NEW.id_job; END; 建完触发器后,下一步该想的是任务在同一个作业里按什么顺序出队——这决定了优化跑批会不会乱序。
CREATE TRIGGER upd_project_status_queued
AFTER UPDATE OF status
ON projects
WHEN NEW.status = &class="macro">#x27;Queued&class="macro">#x27;
BEGIN
UPDATE stages
SET status = &class="macro">#x27;Queued&class="macro">#x27;
WHERE id_project = NEW.id_project;
END;
CREATE TRIGGER upd_stage_status_queued
AFTER UPDATE
ON stages
WHEN NEW.status = &class="macro">#x27;Queued&class="macro">#x27; AND
OLD.status <> NEW.status
BEGIN
UPDATE jobs
SET status = &class="macro">#x27;Queued&class="macro">#x27;
WHERE id_stage = NEW.id_stage;
END;
CREATE TRIGGER upd_job_status_queued
AFTER UPDATE OF status
ON jobs
WHEN NEW.status = &class="macro">#x27;Queued&class="macro">#x27;
BEGIN
UPDATE tasks
SET status = &class="macro">#x27;Queued&class="macro">#x27;
WHERE id_job = NEW.id_job;
END;
CREATE TRIGGER upd_task_status_queued
AFTER UPDATE OF status
ON tasks
WHEN NEW.status = &class="macro">#x27;Queued&class="macro">#x27;
BEGIN
UPDATE tasks
SET start_date = NULL,
finish_date = NULL
WHERE id_task = NEW.id_task;
END;
CREATE TRIGGER upd_task_status_process
AFTER UPDATE OF status
ON tasks
WHEN NEW.status = &class="macro">#x27;Process&class="macro">#x27;
BEGIN
UPDATE tasks
SET start_date = DATETIME(&class="macro">#x27;NOW&class="macro">#x27;)
WHERE id_task = NEW.id_task;
DELETE FROM passes
WHERE id_task = NEW.id_task;
UPDATE jobs
SET status = &class="macro">#x27;Process&class="macro">#x27;
WHERE id_job = NEW.id_job;
END;
CREATE TRIGGER upd_job_status_process
AFTER UPDATE OF status
ON jobs
WHEN NEW.status = &class="macro">#x27;Process&class="macro">#x27;
BEGIN
UPDATE stages
SET status = &class="macro">#x27;Process&class="macro">#x27;
WHERE id_stage = NEW.id_stage;
END;
CREATE TRIGGER upd_stage_status_process
AFTER UPDATE OF status
ON stages
WHEN NEW.status = &class="macro">#x27;Process&class="macro">#x27;
BEGIN
UPDATE projects
SET status = &class="macro">#x27;Process&class="macro">#x27;
WHERE id_project = NEW.id_project;
END;
CREATE TRIGGER upd_task_status_done
AFTER UPDATE OF status
ON tasks
WHEN NEW.status = &class="macro">#x27;Done&class="macro">#x27;
BEGIN
UPDATE tasks
SET finish_date = DATETIME(&class="macro">#x27;NOW&class="macro">#x27;)
WHERE id_task = NEW.id_task;
UPDATE jobs
SET status = (
SELECT CASE WHEN(
SELECT COUNT( * )
FROM tasks t
WHERE t.status = &class="macro">#x27;Queued&class="macro">#x27; AND
t.id_job = NEW.id_job
) > class="num">0 THEN &class="macro">#x27;Process&class="macro">#x27; ELSE &class="macro">#x27;Done&class="macro">#x27; END)
WHERE id_job = NEW.id_job;
END;