开发多币种 EA 交易(第 24 部分):添加新策略(二)·综合运用
🧩

开发多币种 EA 交易(第 24 部分):添加新策略(二)·综合运用

(3/3)·库与项目解耦后接入新策略的坑比想象多,本篇用真实代码拆分演示如何零重写迁移

案例拆解新手友好 第 3/3 篇
把新策略硬塞进老EA,多数人第一反应是改项目文件,结果库层耦合炸了一堆编译错误。其实只要守住库独立于项目的边界,加策略类反而比补丁式拷贝更省事。本篇顺着真实迁移过程拆给你看。

用类封装把优化项目塞进本地库

在 MT5 里批量跑策略优化,最忌每次手写 SQL 连库。把项目抽象成 COptimizationProject 类,构造时传文件名即可直连本地数据库,并自动起事务,省掉重复胶水代码。 AddStage 是核心入口:它接收父级、EA 名、品种、周期、优化模式等 19 个参数,默认存款 10000 USD、杠杆 200、优化准则代号 7、状态标记 Done。你在 EURUSD、M15 上挂一个新阶段,只要填前几个必填项,其余沿用默认值就能入库。 批量铺任务靠 AddJobs 和 AddTasks:前者按品种+周期矩阵套用模板函数生成作业,后者按优化准则字符串数组补任务。写完调 Queue() 把项目丢进执行队列,框架层就去排程了。 StringToTimeframe 是个静态工具,把 "H1" 这类字符串转成 ENUM_TIMEFRAMES 枚举,避免手填枚举出错。下面这段头与构造实现,直接拷进 MQ5 就能编译验证。

MQL5 / C++
  class="type">void                Update();   class=class="str">"cmt">// Update an entry in the database
  class=class="str">"cmt">// Add a new stage to the database
  COptimizationProject* AddStage(COptimizationStage* parentStage, class="type">class="kw">string stageName,
                                 class="type">class="kw">string stageExpertName,
                                 class="type">class="kw">string stageSymbol, class="type">class="kw">string stageTimeframe,
                                 class="type">int stageOptimization, class="type">int stageModel,
                                 class="type">class="kw">datetime stageFromDate, class="type">class="kw">datetime stageToDate,
                                 class="type">int stageForwardMode, class="type">class="kw">datetime stageForwardDate,
                                 class="type">int stageDeposit = class="num">10000, class="type">class="kw">string stageCurrency = "USD",
                                 class="type">int stageProfitInPips = class="num">0, class="type">int stageLeverage = class="num">200,
                                 class="type">int stageExecutionMode = class="num">0, class="type">int stageOptimizationCriterion = class="num">7,
                                 class="type">class="kw">string stageStatus = "Done");
  class=class="str">"cmt">// Add new jobs to the database for the specified symbols and timeframes
  COptimizationProject* AddJobs(class="type">class="kw">string p_symbols, class="type">class="kw">string p_timeframes,
TJobsTemplateFunc p_templateFunc);
  COptimizationProject* AddJobs(class="type">class="kw">string &p_symbols[], class="type">class="kw">string &p_timeframes[],
TJobsTemplateFunc p_templateFunc);
  class=class="str">"cmt">// Add new tasks to the database for the specified optimization criteria
  COptimizationProject* AddTasks(class="type">class="kw">string p_criterions);
  COptimizationProject* AddTasks(class="type">class="kw">string &p_criterions[]);
  class="type">void                Queue();      class=class="str">"cmt">// Put the project in the execution queue
  class=class="str">"cmt">// Convert a class="type">class="kw">string name to a timeframe
  class="kw">static ENUM_TIMEFRAMES  StringToTimeframe(class="type">class="kw">string s);
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
COptimizationProject::COptimizationProject(class="type">class="kw">string p_fileName) :
  m_fileName(p_fileName), id_project(class="num">0) {
class=class="str">"cmt">// Connect to the database
  if (DB::Connect(m_fileName)) {
    class=class="str">"cmt">// Start a transaction
    DatabaseTransactionBegin(DB::Id());
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Destructor                                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
COptimizationProject::~COptimizationProject() {
class=class="str">"cmt">// If no errors occurred, then

◍ 批量往优化库里塞品种与周期任务

在 MT5 自建策略优化管线时,事务收尾和资源释放必须先做干净:提交或回滚要看 DB::Res() 的返回值,之后立刻 DB::Close() 断开库连接,再用 FOREACH 把 m_tasks、m_jobs、m_stages 里的对象逐个 delete,否则跑多轮优化会漏内存。 往数据库加任务有两条重载路径。字符串版本 AddJobs 先把分号替换成逗号,用 StringSplit 拆出 symbols 和 timeframes 数组,再转调数组版本;这样你在 EA 面板里填 "EURUSD;XAUUSD" 和 "M15;H1" 就能直接派发,不用手写数组。 数组版 AddJobs 用 FOREACH_AS 双重遍历品种和周期,对每个组合调 p_templateFunc 拿到参数串,new 一个 COptimizationJob 并 Insert 进库,同时挂到总任务数组和当前 stage 的任务数组里。实测在 2 个品种 × 4 个周期下,一次调用会生成 8 条 job 记录,返回 this 指针方便链式继续配置。 别把正态当圣经:模板函数返回的 params 若含空串,Insert 仍会写库但后续优化会静默失败,建议在 p_templateFunc 后加一行 Print 校验长度再继续。

MQL5 / C++
if(DB::Res()) {
    class=class="str">"cmt">// Confirm the transaction
    DatabaseTransactionCommit(DB::Id());
} else {
    class=class="str">"cmt">// Otherwise, cancel the transaction
    DatabaseTransactionRollback(DB::Id());
}
class=class="str">"cmt">// Close connection to the database
  DB::Close();
class=class="str">"cmt">// Delete created task, job, and stage objects
  FOREACH(m_tasks)  {
      class="kw">delete m_tasks[i];
  }
  FOREACH(m_jobs)  {
      class="kw">delete m_jobs[i];
  }
  FOREACH(m_stages) {
      class="kw">delete m_stages[i];
  }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Add new jobs to the database for the specified                    |
class=class="str">"cmt">//| symbols and timeframes in strings                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
COptimizationProject* COptimizationProject::AddJobs(class="type">class="kw">string p_symbols, class="type">class="kw">string p_timeframes,
TJobsTemplateFunc p_templateFunc) {
class=class="str">"cmt">// Array of symbols for strategies
  class="type">class="kw">string symbols[];
  StringReplace(p_symbols, ";", ",");
  StringSplit(p_symbols, &class="macro">#x27;,&class="macro">#x27;, symbols);
class=class="str">"cmt">// Array of timeframes for strategies
  class="type">class="kw">string timeframes[];
  StringReplace(p_timeframes, ";", ",");
  StringSplit(p_timeframes, &class="macro">#x27;,&class="macro">#x27;, timeframes);
  class="kw">return AddJobs(symbols, timeframes, p_templateFunc);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Add new jobs to the database for the specified                    |
class=class="str">"cmt">//| symbols and timeframes in arrays                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
COptimizationProject* COptimizationProject::AddJobs(class="type">class="kw">string &p_symbols[], class="type">class="kw">string &p_timeframes[],
TJobsTemplateFunc p_templateFunc) {
  class=class="str">"cmt">// For each symbol
  FOREACH_AS(p_symbols, m_symbol) {
    class=class="str">"cmt">// For each timeframe
    FOREACH_AS(p_timeframes, m_timeframe) {
      class=class="str">"cmt">// Get the parameters for work for a given symbol and timeframe
      class="type">class="kw">string params = p_templateFunc(&this);
      
      class=class="str">"cmt">// Create a new job object
      m_job = new COptimizationJob(class="num">0, m_stage, m_symbol, m_timeframe, params);
      
      class=class="str">"cmt">// Insert it into the optimization database
      m_job.Insert();
      class=class="str">"cmt">// Add it to the array of all jobs
      APPEND(m_jobs, m_job);
      
      class=class="str">"cmt">// Add it to the array of current stage jobs
      APPEND(m_stage.jobs, m_job);
    }
  }
  
  class="kw">return &this;
}

「优化阶段对象怎么对接数据库字段」

COptimizationStage 类看起来属性繁多,根源在于优化数据库的 stages 表字段本身就多——表里有几个字段,类里就对应暴露几个属性,属于一一映射,不是冗余设计。 构造函数和写入 stages 表的方法逻辑很直白:把传入的参数暂存进对象属性,再拼成 SQL 插入语句写进对应的优化库表。 注意构造阶段对象时,要同时传「所属项目对象指针」和「上一阶段对象指针」;第一阶段没有前驱,这个参数直接传 NULL 即可,否则后续链式回溯会崩。 默认参数里写死了 symbol="GBPUSD"、timeframe="H1"、deposit=10000、currency="USD"、leverage=200,开 MT5 跑自己的品种前务必改掉这几个字面量。外汇与贵金属杠杆交易高风险,参数误用可能导致回测与实盘偏差极大。

MQL5 / C++
class COptimizationStage {
class="kw">public:
  class="type">ulong              id_stage;
  class="type">ulong              id_project;
  class="type">ulong              id_parent_stage;
  class="type">class="kw">string             name;
  class="type">class="kw">string             expert;
  class="type">class="kw">string             symbol;
  class="type">class="kw">string             period;
  class="type">int                optimization;
  class="type">int                model;
  class="type">class="kw">datetime           from_date;
  class="type">class="kw">datetime           to_date;
  class="type">int                forward_mode;
  class="type">class="kw">datetime           forward_date;
  class="type">int                deposit;
  class="type">class="kw">string             currency;
  class="type">int                profit_in_pips;
  class="type">int                leverage;
  class="type">int                execution_mode;
  class="type">int                optimization_criterion;
  class="type">class="kw">string             status;
  COptimizationProject* project;
  COptimizationStage* parent_stage;
  COptimizationJob* jobs[];
                     COptimizationStage(class="type">ulong p_idStage, COptimizationProject* p_project,
COptimizationStage* parentStage,
                       class="type">class="kw">string p_name, class="type">class="kw">string p_expertName,
                       class="type">class="kw">string p_symbol = "GBPUSD", class="type">class="kw">string p_timeframe = "H1",
                       class="type">int p_optimization = class="num">0, class="type">int p_model = class="num">0,
                       class="type">class="kw">datetime p_fromDate = class="num">0, class="type">class="kw">datetime p_toDate = class="num">0,
                       class="type">int p_forwardMode = class="num">0, class="type">class="kw">datetime p_forwardDate = class="num">0,
                       class="type">int p_deposit = class="num">10000, class="type">class="kw">string p_currency = "USD",
                       class="type">int p_profitInPips = class="num">0, class="type">int p_leverage = class="num">200,

把优化阶段写进数据库的结构

在 MT5 的 MQL5 回测框架里,一次优化任务往往要落库追踪。下面这段 COptimizationStage 的构造函数把项目 ID、父阶段、品种、周期、优化模式等 20 多个字段一次性绑到成员上,execution_mode 默认 0、optimization_criterion 默认 7,状态写死 "Done",意味着对象生成即代表该阶段已完成。 Insert() 方法用 StringFormat 拼 SQL,把 id_stage、id_project(%I64u 对应 64 位无符号)、name、expert、symbol、period 等逐列映射进 stages 表。注意 id_project 和 id_parent_stage 在构造时先用 !! 判空,空指针落 0,避免库里出现脏外键。 实战里你可以直接把这段抄进自己的 EA 管理类,把 p_optimizationCriterion 从 7 改成其他值(比如 0=平衡、4=盈利因子),就能在本地 SQLite 里区分不同优化目标的批次。外汇与贵金属回测含高杠杆风险,落库参数仅作复盘用,不代表任何实盘胜率。

MQL5 / C++
class="type">int p_executionMode = class="num">0, class="type">int p_optimizationCriterion = class="num">7,
class="type">class="kw">string p_status = "Done") :
       id_stage(p_idStage),
       project(p_project),
       id_project(!!p_project ? p_project.id_project : class="num">0),
       parent_stage(parentStage),
       id_parent_stage(!!parentStage ? parentStage.id_stage : class="num">0),
       name(p_name), expert(p_expertName), symbol(p_symbol),
       period(p_timeframe), optimization(p_optimization), model(p_model),
       from_date(p_fromDate), to_date(p_toDate), forward_mode(p_forwardMode),
       forward_date(p_forwardDate), deposit(p_deposit), currency(p_currency),
       profit_in_pips(p_profitInPips), leverage(p_leverage),
execution_mode(p_executionMode),
       optimization_criterion(p_optimizationCriterion), status(p_status) {}
  class=class="str">"cmt">// Create a stage in the database
  class="type">void      Insert();
};
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create a stage in the database                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void COptimizationStage::Insert() {
  class="type">class="kw">string query = StringFormat("INSERT INTO stages VALUES("
                             "%s,"  class=class="str">"cmt">// id_stage
                             "%I64u," class=class="str">"cmt">// id_project
                             "%s,"    class=class="str">"cmt">// id_parent_stage
                             "&class="macro">#x27;%s&class="macro">#x27;,"  class=class="str">"cmt">// name
                             "&class="macro">#x27;%s&class="macro">#x27;,"  class=class="str">"cmt">// expert
                             "&class="macro">#x27;%s&class="macro">#x27;,"  class=class="str">"cmt">// symbol
                             "&class="macro">#x27;%s&class="macro">#x27;,"  class=class="str">"cmt">// period
                             "%d,"    class=class="str">"cmt">// optimization
                             "%d,"    class=class="str">"cmt">// model

◍ 回测任务入库的字段拼装

这段代码片段来自一个把优化任务写进数据库的函数体,负责把每一轮 EA 回测的上下文参数拼成一条 INSERT 语句。它暴露了 MT5 策略测试器在批量优化时实际落库的最小字段集:时间区间、前向模式、保证金、杠杆与执行模式都在列。 从 from_date 到 symbol,共 16 个绑定变量。其中 id_stage 与 id_parent_stage 用三元表达式处理:当值为 0 时写字符串 "NULL",否则强转成字符串——这意味着数据库里这两个外键字段允许空引用,用来标记根任务或独立项目。 如果你要在自己的 MQL5 脚本里复用这套落库逻辑,直接照字段顺序填值即可,但注意 profit_in_pips 和 leverage 都是 %d 整型,传浮点净值会被截断。外汇与贵金属回测涉及高杠杆,实盘前务必在 MT5 用历史数据重跑验证字段映射无误。

MQL5 / C++
              "&class="macro">#x27;%s&class="macro">#x27;,"  class=class="str">"cmt">// from_date
              "&class="macro">#x27;%s&class="macro">#x27;,"  class=class="str">"cmt">// to_date
              "%d,"    class=class="str">"cmt">// forward_mode
              "%s,"    class=class="str">"cmt">// forward_date
              "%d,"    class=class="str">"cmt">// deposit
              "&class="macro">#x27;%s&class="macro">#x27;,"  class=class="str">"cmt">// currency
              "%d,"    class=class="str">"cmt">// profit_in_pips
              "%d,"    class=class="str">"cmt">// leverage
              "%d,"    class=class="str">"cmt">// execution_mode
              "%d,"    class=class="str">"cmt">// optimization_criterion
              "&class="macro">#x27;%s&class="macro">#x27;"   class=class="str">"cmt">// status
              ");",
              (id_stage == class="num">0 ? "NULL" : (class="type">class="kw">string) id_stage), class=class="str">"cmt">// id_stage
              id_project,                                      class=class="str">"cmt">// id_project
              (id_parent_stage == class="num">0 ?
               "NULL" : (class="type">class="kw">string) id_parent_stage),  class=class="str">"cmt">// id_parent_stage
              name,                                            class=class="str">"cmt">// name
              expert,                                          class=class="str">"cmt">// expert
              symbol                                           class=class="str">"cmt">// symbol

「把回测参数塞进查询串」

这段代码片段是往数据库里写一次优化任务记录的拼接动作,核心是把周期、优化模式、样本区间等字段逐一填进 SQL 的 INSERT 语句。 period 对应你跑 EA 的图表周期;optimization 和 model 决定用的是快速遗传还是完整遍历;from_date 与 to_date 经 TimeToString(..., TIME_DATE) 转成纯日期字符串,只保留年月日,不带入时分秒。 forward_mode 是个分水岭:当值等于 4 时,forward_date 会被包成带单引号的日期文本写进去;否则直接落 NULL,意味着不做前向验证。外汇与贵金属品种做这类跨样本测试时波动跳空频繁,前向区间失真概率偏高,需自行核对点差设定。 最后 PrintFormat 把整条 query 打到日志,DB::Insert 返回的自增 id 存进 id_stage,方便后续用这个 id 回查该次优化结果。开 MT5 把这段嵌进你自己的框架,改一下 forward_mode 看 NULL 与日期两种落库差异最直接。

MQL5 / C++
                                                                 period,                                class=class="str">"cmt">// period
                                                                 optimization,                          class=class="str">"cmt">// optimization
                                                                 model,                                 class=class="str">"cmt">// model
                                                                 TimeToString(from_date, TIME_DATE),    class=class="str">"cmt">// from_date
                                                                 TimeToString(to_date, TIME_DATE),      class=class="str">"cmt">// to_date
                                                                 forward_mode,                          class=class="str">"cmt">// forward_mode
                                                                 (forward_mode == class="num">4 ?
                                                                       "&class="macro">#x27;" + TimeToString(forward_date, TIME_DATE) + "&class="macro">#x27;"
                                                                       : "NULL"),                          class=class="str">"cmt">// forward_date
                                                                 deposit,                               class=class="str">"cmt">// deposit
                                                                 currency,                              class=class="str">"cmt">// currency
                                                                 profit_in_pips,                        class=class="str">"cmt">// profit_in_pips
                                                                 leverage,                              class=class="str">"cmt">// leverage
                                                                 execution_mode,                        class=class="str">"cmt">// execution_mode
                                                                 optimization_criterion,                class=class="str">"cmt">// optimization_criterion
                                                                 status                                 class=class="str">"cmt">// status
                                                                 );
   PrintFormat(__FUNCTION__" | %s", query);
   id_stage = DB::Insert(query);

作业类如何挂到阶段对象上

COptimizationJob 的内存布局和 COptimizationStage 基本一致:构造函数只干一件事,把传入的 job ID、所属阶段指针、品种、周期、参数串和状态记进成员变量,不碰数据库。 真正落库靠 Insert() 往优化库的 jobs 表插一行;构造时如果 p_stage 非空,id_stage 会自动取 stage.id_stage,否则写 0,这个细节决定了后面按阶段回溯任务时能不能直接 join。 顺带一提,每个 job 对象里都存了 COptimizationStage* 和 COptimizationTask* 数组,也就是说阶段和任务在对象层就双向绑死了,不需要再查表找从属关系。 最后的 COptimizationTask 类按同一套思路写,这里就不贴代码了,开 MT5 把下面这段类定义丢进头文件就能编译验证。

MQL5 / C++
class COptimizationJob {
class="kw">public:
  class="type">ulong      id_job;     class=class="str">"cmt">// job ID
  class="type">ulong      id_stage;   class=class="str">"cmt">// stage ID
  class="type">class="kw">string     symbol;     class=class="str">"cmt">// Symbol
  class="type">class="kw">string     timeframe;  class=class="str">"cmt">// Timeframe
  class="type">class="kw">string     params;     class=class="str">"cmt">// Optimizer operation parameters
  class="type">class="kw">string     status;     class=class="str">"cmt">// Status
  COptimizationStage* stage;     class=class="str">"cmt">// Stage a job belongs to
  COptimizationTask* tasks[];    class=class="str">"cmt">// Array of tasks related to the job
  class=class="str">"cmt">// Constructor
  COptimizationJob(class="type">ulong p_jobId, COptimizationStage* p_stage,
                   class="type">class="kw">string p_symbol, class="type">class="kw">string p_timeframe,
                   class="type">class="kw">string p_params, class="type">class="kw">string p_status = "Done");
  class=class="str">"cmt">// Create a job in the database
  class="type">void       Insert();
};

COptimizationJob::COptimizationJob(class="type">ulong p_jobId,
                     COptimizationStage* p_stage,
                     class="type">class="kw">string p_symbol, class="type">class="kw">string p_timeframe,
                     class="type">class="kw">string p_params, class="type">class="kw">string p_status = "Done") :
  id_job(p_jobId),
  stage(p_stage),
  id_stage(!!p_stage ? p_stage.id_stage : class="num">0),
  symbol(p_symbol),
  timeframe(p_timeframe),
  params(p_params),
  status(p_status) {}

class="type">void COptimizationJob::Insert() {
class=class="str">"cmt">// Request to create a second stage job for a given symbol and timeframe
  class="type">class="kw">string query = StringFormat("INSERT INTO jobs "

◍ 把任务写进数据库的那几行

这段片段负责把一次回测或分析任务落库,核心动作是拼出一条 INSERT 语句再交给 DB::Insert 执行。 注意 VALUES 里第一个字段写的是 NULL,说明主键 id 由数据库自增;后面依次塞入 id_stage、symbol、timeframe、params、status 五个字段,其中 %I64u 对应 64 位无符号整数(stage 编号),其余 %s 均为字符串。 执行后 id_job 拿到自增返回的主键,PrintFormat 把完整 query 和 id_job 打印出来,方便在 MT5 Experts 日志里核对哪条记录写进去了。 开 MT5 跑这段前,先确认 DB 连接已开且表结构含这五个列,否则 Insert 会静默失败只留一条报错日志。

MQL5 / C++
              " VALUES(NULL,%I64u,&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;%s&class="macro">#x27;,&class="macro">#x27;%s&class="macro">#x27;);",
              id_stage, symbol, timeframe, params, status);
   id_job = DB::Insert(query);
   PrintFormat(__FUNCTION__" | %s -> %I64u", query, id_job);
}

「逐个参数拆开 CreateProject 的默认配置」

CreateProject.mq5 放在项目分区里,作用就是给单个优化项目写死一套默认参数,省得每次启动手动改。它最先定义的是优化数据库文件名,后面所有阶段的回测结果都会落进这个 sqlite 里。 symbols_ 和 timeframes_ 用逗号隔开,分别填第一阶段和第二阶段要跑的品种与周期。上面例子里是 GBPUSD,EURUSD,EURGBP 配 H1,M30,排列组合出来就是 3×2=6 个作业,测试器会按这个数量起任务。 账户组里的 mainSymbol_ 很关键:像加密货币这种周末不停盘的品种,如果选错主品种,第三阶段多品种测试可能整周都不生成周末用例。默认填 GBPUSD 只是常规外汇项目的稳妥做法,贵金属或加密项目得按实际改。 第一阶段 stage1Criterions_ 写了 "6,6,6",数字 6 对应自定义优化标准,三次重复意味着每个作业内部要解三个同准则的优化问题。第二阶段多了 stage2MinTrades_=20 这类门槛参数,只有第一阶段实跑满 20 笔交易的策略实例才进分组;stage2UseClusters_ 当前被注释掉,要用的话直接换成 false 接上。 第三阶段除了 EA 名,还用 projectName_ 和 stage3Magic_ 拼最终库名,stage3Tester_ 控制要不要加 ".test" 后缀。跑完第三阶段后库文件随便重命名都行,不影响后续调用。 下面这段输入参数定义了整条流水线的品种、周期与阶段门槛,直接贴出来逐行看更直观:

MQL5 / C++
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">string</span> fileName_&nbsp;&nbsp;= <span class="class="type">class="kw">string">"article.class="num">17328.db.sqlite"</span>; <span class="comment">class=class="str">"cmt">// - Optimization database file</span>
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">string</span>&nbsp;&nbsp;symbols_ = <span class="class="type">class="kw">string">"GBPUSD,EURUSD,EURGBP"</span>;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// - Symbols</span>
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">string</span>&nbsp;&nbsp;timeframes_ = <span class="class="type">class="kw">string">"H1,M30"</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// - Timeframes</span>
<span class="keyword">input</span> group <span class="class="type">class="kw">string">"::: Project parameters - Optimization interval"</span>
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">datetime</span> fromDate_ = <span class="class="type">class="kw">string">D&class="macro">#x27;class="num">2022-class="num">09-class="num">01&class="macro">#x27;</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// - Start date</span>
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">datetime</span> toDate_ = <span class="class="type">class="kw">string">D&class="macro">#x27;class="num">2023-class="num">01-class="num">01&class="macro">#x27;</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// - End date</span>
<span class="keyword">input</span> group <span class="class="type">class="kw">string">"::: Project parameters - Account"</span>
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">string</span>&nbsp;&nbsp; mainSymbol_ = <span class="class="type">class="kw">string">"GBPUSD"</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// - Main symbol</span>
<span class="keyword">input</span> <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;deposit_ = <span class="number">class="num">10000</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// - Initial deposit</span>
<span class="keyword">input</span> group <span class="class="type">class="kw">string">"::: Stage class="num">1. Search"</span>
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">string</span>&nbsp;&nbsp; stage1ExpertName_ = <span class="class="type">class="kw">string">"Stage1.ex5"</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// - Stage EA</span>
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">string</span>&nbsp;&nbsp; stage1Criterions_ = <span class="class="type">class="kw">string">"class="num">6,class="num">6,class="num">6"</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// - Optimization criteria for tasks</span>
<span class="keyword">input</span> group <span class="class="type">class="kw">string">"::: Stage class="num">2. Grouping"</span>
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">string</span>&nbsp;&nbsp; stage2ExpertName_ = <span class="class="type">class="kw">string">"Stage2.ex5"</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// - Stage EA</span>
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">string</span>&nbsp;&nbsp; stage2Criterion_&nbsp;&nbsp;= <span class="class="type">class="kw">string">"class="num">6"</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// - Optimization criterion for tasks</span>
<span class="comment">class=class="str">"cmt">//input class="type">bool&nbsp;&nbsp;&nbsp;&nbsp; stage2UseClusters_= false;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // - Use clustering?</span>
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp; stage2MinCustomOntester_ = <span class="number">class="num">500</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// - Min value of norm. profit</span>
<span class="keyword">input</span> <span class="keyword">class="type">uint</span>&nbsp;&nbsp;&nbsp;&nbsp; stage2MinTrades_&nbsp;&nbsp;= <span class="number">class="num">20</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// - Min number of trades</span>
<span class="keyword">input</span> <span class="keyword">class="type">class="kw">double</span>&nbsp;&nbsp; stage2MinSharpeRatio_ = <span class="number">class="num">0.7</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// - Min Sharpe ratio</span>

多阶段优化里的参数模板与数据库命名

做多阶段 EA 优化时,第二阶段要把同品种同周期的策略聚成一组,stage2Count_ 设成 8 就表示每组最多塞 8 个策略进下一轮筛选。回测和实盘用的库文件得分开:带 .test.db.sqlite 后缀的只在策略测试器里跑,裸的 .db.sqlite 才是挂真实账户用的,别搞混路径。 第三阶段的输入块用 input group 归到 "::: Stage 3. Result" 下,stage3Magic_ 取 27183、stage3Tester_ 开 true 代表当前还是测试态,Stage3.ex5 负责把前两层吐出的结果再整合。外汇与贵金属杠杆高,这类多阶段自动优化若直接接实盘,滑点和过拟合会让样本外表现明显衰减,务必先离线验证。 paramsTemplate1 用 StringFormat 拼出第一阶段优化参数:signalSeqLen_ 从 4 起按步长 2 走到 8,periodATR_ 在 7~48 之间以 2 步进,stopLevel_ 和 takeLevel_ 分别锁在 2.34/4.55 附近做 0.01 精度扫描,maxCountOfOrders_ 上限 30。paramsTemplate2 则先靠 SEARCH 按 symbol+timeframe 匹配到父任务 id_job,再把 idParentJob_、minSharpeRatio_ 等约束写进第二阶模板,保证只继承同品种同周期的合格父作业。

MQL5 / C++
input class="type">uint      stage2Count_        = class="num">8;                 class=class="str">"cmt">// - Number of strategies in the group
<Project name>-<Magic>.test.db.sqlite class=class="str">"cmt">// To run in the tester
<Project name>-<Magic>.db.sqlite      class=class="str">"cmt">// To run on a trading account
input group "::: Stage class="num">3. Result"
input class="type">class="kw">string  stage3ExpertName_ = "Stage3.ex5";        class=class="str">"cmt">// - Stage EA
input class="type">ulong   stage3Magic_      = class="num">27183;              class=class="str">"cmt">// - Magic
input class="type">bool    stage3Tester_     = true;               class=class="str">"cmt">// - For the tester?
class=class="str">"cmt">// Template of optimization parameters at the first stage
class="type">class="kw">string paramsTemplate1(COptimizationProject *p) {
   class="type">class="kw">string params = StringFormat(
                      "symbol_=%s\n"
                      "period_=%d\n"
                      "; ===   Open signal parameters\n"
                      "signalSeqLen_=class="num">4||class="num">2||class="num">1||class="num">8||Y\n"
                      "periodATR_=class="num">21||class="num">7||class="num">2||class="num">48||Y\n"
                      "; ===   Pending order parameters\n"
                      "stopLevel_=class="num">2.34||class="num">0.01||class="num">0.01||class="num">5.0||Y\n"
                      "takeLevel_=class="num">4.55||class="num">0.01||class="num">0.01||class="num">5.0||Y\n"
                      "; ===   Capital management parameters\n"
                      "maxCountOfOrders_=class="num">15||class="num">1||class="num">1||class="num">30||Y\n",
                      p.m_symbol, p.StringToTimeframe(p.m_timeframe));
   class="kw">return params;
}
class=class="str">"cmt">// Template of optimization parameters for the second stage
class="type">class="kw">string paramsTemplate2(COptimizationProject *p) {
   class=class="str">"cmt">// Find the parent job ID for the current job
   class=class="str">"cmt">// by matching the symbol and timeframe at the current and parent stages
   class="type">int i;
   SEARCH(p.m_stage.parent_stage.jobs,
          (p.m_stage.parent_stage.jobs[i].symbol == p.m_symbol
           && p.m_stage.parent_stage.jobs[i].timeframe == p.m_timeframe),
          i);
   class="type">ulong parentJobId = p.m_stage.parent_stage.jobs[i].id_job;
   class="type">class="kw">string params = StringFormat(
                      "idParentJob_=%I64u\n"
                      "useClusters_=%s\n"
                      "minCustomOntester_=%f\n"
                      "minTrades_=%u\n"
                      "minSharpeRatio_=%.2f\n"
                      "count_=%u\n",

◍ 三阶段优化项目的参数模板与初始化落地

这段代码片段展示了一个多阶段 EA 优化工程的参数拼装方式,核心在 paramsTemplate2 与 paramsTemplate3 两个函数,它们分别决定第二阶段与第三阶段的任务下发格式。 paramsTemplate2 里把 parentJobId、stage2MinCustomOntester_、stage2MinTrades_、stage2MinSharpeRatio_、stage2Count_ 等变量通过 StringFormat 塞进字符串,其中 stage2UseClusters_ 被强制写成 (string)false,意味着该阶段默认不走聚类筛选,读者若想开集群并行可以把这个常量改掉再编译。 paramsTemplate3 则只拼了 groupName_、advFileName_、passes_ 三项,advFileName_ 用 %s-%I64u%s.db.sqlite 规则生成,stage3Tester_ 为真时补 .test 后缀,这直接决定回测结果落盘的文件名,改前缀 p.name 就能在 MT5 的 Agents 日志里快速区分不同项目。 OnInit 里用 COptimizationProject p(fileName_) 挂数据库,连续 AddStage 三次,前两段写死 H1 周期、遗传代数 2/2、deposit_ 入金,AddJobs 与 AddTasks 把 symbols_、timeframes_ 和对应模板绑上;外汇与贵金属品种在此类批量优化中滑点敏感,实盘前务必用历史数据重跑验证,参数过拟合概率偏高。

MQL5 / C++
                                                                              parentJobId,
                                                                              (class="type">class="kw">string) false, class=class="str">"cmt">//(class="type">class="kw">string) stage2UseClusters_,
                                                                              stage2MinCustomOntester_,
                                                                              stage2MinTrades_,
                                                                              stage2MinSharpeRatio_,
                                                                              stage2Count_
                                                                     );
   class="kw">return params;
}
class=class="str">"cmt">// Template of optimization parameters at the third stage
class="type">class="kw">string paramsTemplate3(COptimizationProject *p) {
   class="type">class="kw">string params = StringFormat(
                                                                              "groupName_=%s\n"
                                                                              "advFileName_=%s\n"
                                                                              "passes_=\n",
                                                                              StringFormat("%s_v.%s_%s",
                                                                                   p.name, p.version, TimeToString(toDate_, TIME_DATE)),
                                                                              StringFormat("%s-%I64u%s.db.sqlite",
                                                                                   p.name, stage3Magic_, (stage3Tester_ ? ".test" : "")));
   class="kw">return params;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit() {
class=class="str">"cmt">// Create an optimization project object for the given database
   COptimizationProject p(fileName_);
class=class="str">"cmt">// Create a new project in the database
   p.Create(projectName_, projectVersion_,
                                                                              StringFormat("%s - %s",
                                                                                   TimeToString(fromDate_, TIME_DATE),
                                                                                   TimeToString(toDate_, TIME_DATE)));
class=class="str">"cmt">// Add the first stage
   p.AddStage(NULL, "First", stage1ExpertName_, mainSymbol_, "H1", class="num">2, class="num">2,
                                                                              fromDate_, toDate_, class="num">0, class="num">0, deposit_);
class=class="str">"cmt">// Adding the first stage jobs
   p.AddJobs(symbols_, timeframes_, paramsTemplate1);
class=class="str">"cmt">// Add tasks for the first stage jobs
   p.AddTasks(stage1Criterions_);
class=class="str">"cmt">// Add the second stage
   p.AddStage(p.m_stages[class="num">0], "Second", stage2ExpertName_, mainSymbol_, "H1", class="num">2, class="num">2,
                                                                              fromDate_, toDate_, class="num">0, class="num">0, deposit_);
class=class="str">"cmt">// Add the second stage jobs
   p.AddJobs(symbols_, timeframes_, paramsTemplate2);
class=class="str">"cmt">// Add tasks for the second stage jobs
   p.AddTasks(stage2Criterion_);
class=class="str">"cmt">// Add the third stage

「把多阶段优化任务压进队列并自毁EA」

上面这段落在初始化函数末尾,负责把已配置好的第三阶段任务真正提交给 MT5 优化内核。p.AddStage 调用里写死了 "H1" 周期与 stage3ExpertName_ 的 EA 名,并将 fromDate_ 到 toDate_ 作为回测区间传入,deposit_ 控制初始保证金规模。 AddJobs 用 paramsTemplate3 模板把符号与周期绑定到该阶段,AddTasks("0") 则声明任务编号为 0 的批次。Queue() 一旦执行,项目就进入终端的并行优化队列,不再依赖当前 EA 驻留。 最后 ExpertRemove() 会直接注销本 EA,避免它在图表上继续 tick;return(INIT_SUCCEEDED) 仅表示初始化流程正常结束。你在 MT5 里跑这类脚本时,若没看到优化任务进队列,优先查 AddStage 的日期区间是否被置成了 0。

MQL5 / C++
  p.AddStage(p.m_stages[class="num">1], "Save to library", stage3ExpertName_, mainSymbol_,
                  "H1", class="num">0, class="num">2, fromDate_, toDate_, class="num">0, class="num">0, deposit_);
class=class="str">"cmt">// Add the third stage job
  p.AddJobs(mainSymbol_, "H1", paramsTemplate3);
class=class="str">"cmt">// Add a task for the third stage job
  p.AddTasks("class="num">0");
class=class="str">"cmt">// Put the project in the execution queue
  p.Queue();
class=class="str">"cmt">// Delete the EA
  ExpertRemove();
class=class="str">"cmt">// Successful initialization
  class="kw">return(INIT_SUCCEEDED);
}

阶段化 EA 的拼装方式

前面已经落地的 Stage1.mq5 只补了一个 maxSpread_ 参数,逻辑没动。到了第二、第三阶段,EA 主体几乎不用重写——只要改 __NAME__ 常量、挂上对应策略头文件,再引用库里的 Stage1/Stage2 公共部分即可。 下面这段是第二阶段 EA 的核心骨架:命名常量拼上程序名,引入 SimpleCandlesStrategy.mqh 与 Stage1.mqh 通用层,剩下全是输入参数和把参数拼成策略初始化字符串的函数。 输入区里几个数值得直接拿去调:signalSeqLen_ 默认 6 根同向蜡烛才触发;stopLevel_ 给到 25000、takeLevel_ 3630,若 periodATR_ 为 0 则按点算;maxSpread_ 限 10 点,maxCountOfOrders_ 最多 9 张单同开。外汇与贵金属杠杆高,点值波动可能瞬间吃掉这些预设边界,实盘前请在 MT5 策略测试器用历史数据验证。 第三阶段只是把 include 换成 Stage2.mqh,结构完全一致,复制改一行就能跑。

MQL5 / C++
class=class="str">"cmt">// class="num">1. Define a constant with the EA name
class="macro">#define  __NAME__ "SimpleCandles" + MQLInfoString(MQL_PROGRAM_NAME)
class=class="str">"cmt">// class="num">2. Connect the required strategy
class="macro">#include "Strategies/SimpleCandlesStrategy.mqh";
class=class="str">"cmt">// class="num">3. Connect the general part of the first stage EA from the Advisor library
class="macro">#include <antekov/Advisor/Experts/Stage1.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">4. Strategy inputs                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
sinput class="type">class="kw">string      symbol_            = "";     class=class="str">"cmt">// Symbol
sinput ENUM_TIMEFRAMES period_        = PERIOD_CURRENT;   class=class="str">"cmt">// Timeframe for candles
input group "===  Opening signal parameters"
input class="type">int          signalSeqLen_      = class="num">6;     class=class="str">"cmt">// Number of unidirectional candles
input class="type">int          periodATR_         = class="num">0;     class=class="str">"cmt">// ATR period(if class="num">0, then TP/SL in points)
input group "===  Pending order parameters"
input class="type">class="kw">double       stopLevel_         = class="num">25000; class=class="str">"cmt">// Stop Loss(in ATR fraction or points)
input class="type">class="kw">double       takeLevel_         = class="num">3630;  class=class="str">"cmt">// Take Profit(in ATR fraction or points)
input group "===  Money management parameters"
input class="type">int          maxCountOfOrders_  = class="num">9;     class=class="str">"cmt">// Max number of simultaneously open orders
input class="type">int          maxSpread_         = class="num">10;    class=class="str">"cmt">// Max acceptable spread(in points)
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| class="num">5. Strategy initialization class="type">class="kw">string generation function             |
class=class="str">"cmt">//|    from the inputs                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">string GetStrategyParams() {
   class="kw">return StringFormat(
            "class CSimpleCandlesStrategy(\"%s\",%d,%d,%d,%.3f,%.3f,%d,%d)",
            (symbol_ == "" ? Symbol() : symbol_), period_,
            signalSeqLen_, periodATR_, stopLevel_, takeLevel_,
            maxCountOfOrders_, maxSpread_
            );
}
class=class="str">"cmt">// class="num">1. Define a constant with the EA name
class="macro">#define  __NAME__ "SimpleCandles" + MQLInfoString(MQL_PROGRAM_NAME)
class=class="str">"cmt">// class="num">2. Connect the required strategy
class="macro">#include "Strategies/SimpleCandlesStrategy.mqh";
class="macro">#include <antekov/Advisor/Experts/Stage2.mqh>
class=class="str">"cmt">// class="num">1. Define a constant with the EA name

◍ 把策略挂到EA骨架上的两行关键代码

在 MT5 里写一个自定义 EA 时,最容易被忽略的一步是给程序起一个能自我标识的名字,并把写好的策略文件真正接进来。下面这两行就干了这件事。 第一行用宏拼接出 EA 的运行时名称:把固定字符串 "SimpleCandles" 和当前程序名通过 MQLInfoString(MQL_PROGRAM_NAME) 动态拼在一起,赋值给 __NAME__。这样在日志或图表上能看到带程序名的标识,方便多 EA 同跑时排查。 第二、三行是包含指令:#include 把 Strategies/SimpleCandlesStrategy.mqh 里的策略逻辑拉进当前文件;再 include 第三方 antekov 库下的 Stage3.mqh,承接其已封装的 Expert 框架。缺了这两行,编译能过但策略不会被执行。外汇与贵金属波动剧烈,接错版本可能导致信号失效,建议在 MT5 的 MetaEditor 里核对路径后再编译。

MQL5 / C++
class="macro">#define  __NAME__ "SimpleCandles" + MQLInfoString(MQL_PROGRAM_NAME)
class=class="str">"cmt">// class="num">2. Connect the required strategy
class="macro">#include "Strategies/SimpleCandlesStrategy.mqh";
class="macro">#include <antekov/Advisor/Experts/Stage3.mqh>

「把策略接进最终 EA 的极简写法」

做最终 EA 时,核心动作只剩一件:把用到的策略文件挂进来。__NAME__ 常量不用在 EA 里重复声明,因为它和生成初始化字符串的函数都已经写在库里的包含文件中了。 下面这段代码里,EA 名称和初始化函数的样子被放在注释中。真要改逻辑,把注释符号去掉再编辑就行,不用动整体框架。 项目部分只需包含策略头文件与 Expert 基类:SimpleCandlesStrategy.mqh 和 antekov/Advisor/Experts/Expert.mqh。编译时对每个 mq5 文件都会产出对应的 ex5,这是 MT5 的标准行为,可立即在编辑器里验证。 注释里的 GetStrategyParams 展示了从 EA 数据库按 groupId_ 导入初始化串的路径:调用 CVirtualAdvisor::Import,若串为空且开启自动更新则回填空串。外汇与贵金属自动化策略回测与实盘偏差可能很大,属高风险操作。

MQL5 / C++
class=class="str">"cmt">// class="num">1. Define a constant with the EA name
class=class="str">"cmt">//class="macro">#define  __NAME__ MQLInfoString(MQL_PROGRAM_NAME)
class=class="str">"cmt">// class="num">2. Connect the required strategy
class="macro">#include "Strategies/SimpleCandlesStrategy.mqh";
class="macro">#include <antekov/Advisor/Experts/Expert.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Function for generating the strategy initialization class="type">class="kw">string       |
class=class="str">"cmt">//| from the class="kw">default inputs(if no name was specified).             |
class=class="str">"cmt">//| Import the initialization class="type">class="kw">string from the EA database            |
class=class="str">"cmt">//| by the strategy group ID                                         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//class="type">class="kw">string GetStrategyParams() {
class=class="str">"cmt">//// Take the initialization class="type">class="kw">string from the new library for the selected group
class=class="str">"cmt">//// (from the EA database)
class=class="str">"cmt">//  class="type">class="kw">string strategiesParams = CVirtualAdvisor::Import(
class=class="str">"cmt">//                                        CVirtualAdvisor::FileName(__NAME__, magic_),
class=class="str">"cmt">//                                        groupId_
class=class="str">"cmt">//                                         );
class=class="str">"cmt">//
class=class="str">"cmt">//// If the strategy group from the library is not specified, then we interrupt the operation
class=class="str">"cmt">//  if(strategiesParams == NULL && useAutoUpdate_) {
class=class="str">"cmt">//    strategiesParams = "";
class=class="str">"cmt">//  }
class=class="str">"cmt">//
class=class="str">"cmt">//  class="kw">return strategiesParams;
class=class="str">"cmt">//}

从数据库到实盘的四步落地

跑通这套自动优化流水线,先拖 CreateProject.ex5 到任意图表,不用进测试器。源码里输入参数已预填当前值,对话框直接点确定,就会生成 article.17328.db.sqlite 优化库。 第二步上 Optimization.ex5,同样免测试器,但必须勾选允许 DLL,并核对数据库名。跑起来后图表显示「Total tasks in queue / Current Task ID」,测试器从第一个品种-周期组合开始第一阶段优化。默认配置下 33 个代理跑完约 4 小时;末段不再优化,而是单次跑第三阶段 EA,产出 SimpleCandles-27183.test.db.sqlite。 进测试器验最终 EA 时,默认 groupId_=0 且 useAutoUpdate_=true,会加载库里最后一组策略。但要注意:若优化区间设为 2022.09.01-2023.01.01,组的形成日期就是 2023.01.01,自动更新开启时模拟日期不早于该日就载不进组。要么关掉自动更新并指定 groupId_=1,要么换优化结束后的区间。只跑过一个项目时库里只有 ID 1 的组,填 0 或 1 无差别。 上实盘前把 .test 后缀去掉,重命名为 SimpleCandles-27183.db.sqlite 放回通用终端目录,EA 拖图表用默认值即可,当前日期必然大于组创建日。模拟账户约一周测试中回撤 1.27%、利润约 2%,重启几次后靠本地虚拟仓位信息恢复继续跑。外汇与贵金属属高风险品种,这类表现不预示后续收益。

◍ 工具落地后的真实减负与边界

把前面十几节攒下来的自动化优化框架跑通后,最直观的变化是人工环节被砍掉一大截。以前每轮优化完得手动盯进度、另存中间结果再塞回 EA,现在这些动作交给调度器,人只需要盯策略逻辑本身。 跨品种分散这块,即便用很朴素的进场规则,回测曲线稳定性也出现了可观察的改善——当然这只是历史样本里的现象,不等于实盘必然复现。 Web 端统一管理还是远期目标,当前版本只能在终端侧看运行状态和基础统计。这类工具属于研究性质,外汇与贵金属杠杆高、滑点跳空频繁,直接挂真仓前务必用 MT5 策略测试器自己重跑一遍历史。

「把工具请下神坛」

上面那张清单列了 47 个文件,从 Advisor.mqh 1.04 到 VirtualSymbolReceiver.mqh 1.00,覆盖 Experts、Include 下 9 个子目录,ZIP 包 109.25 KB。它本质是一套可复用的多币种 EA 骨架,不是点一下就盈利的黑箱。 真正值钱的是分层:基类管仓位转换,Database 系列管策略落库,Optimizer 系列管自动跑参。你下载解压后,直接把 MQL5/Include/antekov 拖进本地 MetaEditor 的 Include 目录,开 MT5 用 SimpleCandles.mq5 挂一个符号就能看见组库参数怎么被读出来。 外汇和贵金属杠杆高、滑点狠,任何回测漂亮的组策略上了真盘都可能变形。把它当脚手架,而不是圣杯——改两行 Macros.mqh 的数组宏,比信作者 Telegram 频道有用。

让小布替你跑这套
多币种EA的优化任务编排和策略切换诊断,小布盯盘的AIGC已内置,打开对应品种页即可看到阶段运行状态,你只需盯决策点。

常见问题

破开后新策略能快速塞进去,但后续优化输送机、阶段EA都要跟着改,维护成本指数级涨。保持边界只需在库加少量接口。
需要在COptimizationStage和COptimizationJob里扩展字段,并在重写时把策略名作为可配参数传入,数据库结构同步微调。
数千次里几十次,概率低但会丢结果。CDatabase里收帧实例的插入逻辑加重试与队列即可,不必大改架构。
可以,小布的品种页支持读取阶段EA生成的优化库,自动标注哪些品种周期已跑完输送机任务。
模型假设不同,但都建议用数月短间隔测输送机,长周期留到实盘前回测,避免单轮耗时拖垮迭代。