连续前行优化 (第五部分): 自动优化器项目概述和 GUI 的创建·综合运用
📘

连续前行优化 (第五部分): 自动优化器项目概述和 GUI 的创建·综合运用

第 3/3 篇

路径拼接与终端退出的底层处理

这段逻辑负责把分散的目录片段拼回完整路径,并递归收集某终端目录下所有 .ex5 文件。注意 GetRelevantPath 里用 Path.Combinepath_parts[i] 开始向后累加,跳过的那个 i 正是调用前已取出的根段。 递归收集时先抓当前目录的 *.ex5,再对子目录 GetDirectories() 逐个下沉。若你的策略库有 200 个 ex5 分散在 5 层文件夹,这套写法只跑一遍就能列全,不必手动维护清单。 终端关闭事件被包成 Process_Exited,内部只做一件事:触发 TerminalClosed 委托。外部订阅后可在 MT5 进程意外退出时立刻释放资源或写日志。 CheckDirectories 会在初始化时校验三件事:安装目录存在、可变更数据目录存在、安装目录下能找到 terminal64.exe。任一不满足直接抛 `ArgumentException,能在最早阶段暴露配置错误而不是跑一半崩。

MQL5 / C++
class="type">class="kw">string ans = path_parts[i];
for (i++; i < path_parts.Length; i++)
{
    ans = Path.Combine(ans, path_parts[i]);
}
class="kw">return ans;

List<class="type">class="kw">string> files = new List<class="type">class="kw">string>();
IEnumerable<DirectoryInfo> directories = path.GetDirectories();
files.AddRange(path.GetFiles("*.ex5").Select(x => GetRelevantPath(x.FullName)));
foreach(var item in directories)
    files.AddRange(GetEX5FilesR(item, RelativeDirectory));
class="kw">return files;

class=class="str">"cmt">/// <summary>
class=class="str">"cmt">/// Terminal closing event
class=class="str">"cmt">/// </summary>
class="kw">private class="type">void Process_Exited(object sender, EventArgs e)
{
    TerminalClosed?.Invoke(this);
}

class="kw">private class="type">void CheckDirectories()
{
    if (!TerminalInstallationDirectory.Exists)
        class="kw">throw new ArgumentException("PathToTerminalInstallationDirectory doesn`t exists");
    if (!TerminalChangeableDirectory.Exists)
        class="kw">throw new ArgumentException("PathToTerminalChangeableDirectory doesn`t exists");
    if (!TerminalInstallationDirectory.GetFiles().Any(x => x.Name == "terminal64.exe"))
        class="kw">throw new ArgumentException($"Can`t find terminal(terminal64.exe) in the instalation folder {TerminalInstallationDirectory.FullName}");
}

class="kw">public class="kw">virtual class="type">void UpdateParams()
{
    _params.Clear();
    class="kw">using (var file = FileInfo.OpenText())
    {
        class="type">class="kw">string line;
        class="kw">while ((line = file.ReadLine()) != null)
        {
            if (line[class="num">0].CompareTo(&class="macro">#x27;;&class="macro">#x27;) != class="num">0 && line[class="num">0].CompareTo(&class="macro">#x27;#&class="macro">#x27;) != class="num">0)
            {
                class="type">class="kw">string[] key_value = line.Replace(" ", "").Split(&class="macro">#x27;=&class="macro">#x27;);

◍ 把配置串拆成可优化参数对象

在 MT5 的EA或指标工程里,经常用 `变量名起始步长终止是否优化` 这种竖线分隔串来批量描述参数。下面这段代码干的事,就是把读到的配置行拆开,塞进一个 ParamsItem 结构里。

拆串时先按 | 切分,并丢掉空段;如果切出来的数组长度不够,对应字段就填 null,避免越界。是否优化那一项只看第5段是不是字母 Y,不是就默认 false。 跑这段逻辑时,建议你在 OnTesterInit 或自定义配置解析函数里打断点,观察 value_data.Length 的分布。外汇与贵金属杠杆高、滑点随机,参数边界设错可能在回测里看不出,实盘却直接爆仓,务必小资金验证。 别把正态当圣经 参数 Start/Step/Stop 只是给优化器划范围,真实行情里最优值可能落在边界外,别迷信跑出来的极值。

MQL5 / C++
class="type">class="kw">string[] value_data = key_value[class="num">1].Split(new[] { &class="macro">#x27;|&class="macro">#x27; }, StringSplitOptions.RemoveEmptyEntries);
ParamsItem item = new ParamsItem
{
    Variable = key_value[class="num">0],
    Value = (value_data.Length > class="num">0 ? value_data[class="num">0] : null),
    Start = (value_data.Length > class="num">1 ? value_data[class="num">1] : null),
    Step = (value_data.Length > class="num">2 ? value_data[class="num">2] : null),
    Stop = (value_data.Length > class="num">3 ? value_data[class="num">3] : null),
    IsOptimize = (value_data.Length > class="num">4 ? value_data[class="num">4].CompareTo("Y") == class="num">0 : false)
};
_params.Add(item);

「自动优化器的 Data 目录怎么落地」

MT5 终端启动自动优化器时,若本地没有 Data 目录才会新建,否则路径由 WorkingDirectory 类的 WDRoot 属性托管。这个目录既是工作区也是存储区,所有要读写的文件都应放在这里,避免去碰终端默认目录导致路径错乱。 首次启动后 Data 内仅有一个空的 Reports 子目录。等到第一次跑优化或测试,才会通过属性调用生成 Tester 子目录,并复制一份 {Terminal ID}.ini 配置文件,防止覆盖你原有的默认配置。Tester 里只有 cache 文件夹,用来临时搬走终端 Tester 目录下的已有优化缓存——因为优化器逻辑是:若发现同配置的历史文件就直接加载旧结果,不会重跑。我们为了强制生成新报告,必须让这些文件暂时消失,跑完再还原。 Reports 下每次优化会按前缀建独立目录,里面固定三个文件:Forward.xml(仅向前验证)、History.xml(仅历史复盘)、Report.xml(全部历史时段优化)。GUI 里点 Load 时三个文件全读进对应表格,缺任意一个就弹提示且那张表留空。想换机器分析,直接拷 Reports 目录丢进另一台设备的对应路径即可,启动后就能下载查看。 别把终端 Tester 缓存当理所当然 自动优化器靠临时转移 cache 文件来骗过“复用旧优化”的逻辑,如果你手动在终端目录留了同配置结果,就可能白等半天没出新报告。真要排查,先看 Data/Tester/cache 是否为空。

MQL5 / C++
class=class="str">"cmt">/// <summary>
class=class="str">"cmt">/// The object describing the Data directory with the auto optimizer&class="macro">#x27;s mutable files.
class=class="str">"cmt">/// </summary>
class WorkingDirectory
{
    class=class="str">"cmt">/// <summary>
    class=class="str">"cmt">/// Default constructor
    class=class="str">"cmt">/// </summary>
    class="kw">public WorkingDirectory()
    {
        class=class="str">"cmt">// Create a root directory with mutable files
        WDRoot = new DirectoryInfo("Data");
        if (!WDRoot.Exists)
            WDRoot.Create();
        class=class="str">"cmt">// Create a subdirectory with optimization reports
        Reports = WDRoot.GetDirectory("Reports", true);
    }
    class=class="str">"cmt">/// <summary>
    class=class="str">"cmt">/// Nested directory with optimization reports
    class=class="str">"cmt">/// </summary>
    class="kw">public DirectoryInfo Reports { get; }
    class=class="str">"cmt">/// <summary>
    class=class="str">"cmt">/// Root directory with mutable files and folders
    class=class="str">"cmt">/// </summary>
    class="kw">public DirectoryInfo WDRoot { get; }
    class=class="str">"cmt">/// <summary>
    class=class="str">"cmt">/// Get or create(if not previously created) a directory nested inside the Reports directory.
    class=class="str">"cmt">/// The resulting directory stores the results of a particular optimization pass.
    class=class="str">"cmt">/// </summary>
    class=class="str">"cmt">/// <param name="Symbol">The symbol on which the optimization was performed</param>
    class=class="str">"cmt">/// <param name="ExpertName">Robot name</param>
    class=class="str">"cmt">/// <param name="DirectoryPrefix">Prefix added to the directory name</param>
    class=class="str">"cmt">/// <param name="OptimiserName">The name of the use optimizer</param>
    class=class="str">"cmt">/// <returns>
    class=class="str">"cmt">/// Path to the directory with the optimization results.
    class=class="str">"cmt">/// The name of the directory is formed as follows: class="kw">public DirectoryInfo WDRoot { get; }
    class=class="str">"cmt">/// {DirectoryPrefix} {OptimiserName} {ExpertName} {Symbol}
    class=class="str">"cmt">/// </returns>
    class="kw">public DirectoryInfo GetOptimisationDirectory(class="type">class="kw">string Symbol, class="type">class="kw">string ExpertName,
                                                  class="type">class="kw">string DirectoryPrefix, class="type">class="kw">string OptimiserName)
    {

把回测文件从终端目录里捞出来

上面这段 C# 风格的目录封装,核心就一句话:用 WDRoot.GetDirectory("Tester", true) 拿到 Data/Tester 路径。它的实际用途是把优化器跑完的产物从终端同名目录临时挪走,避免和正式测试文件混在一起。 Reports.GetDirectory 的拼接串里有 DirectoryPrefix、OptimiserName、ExpertName、Symbol 四个变量,意味着同一策略不同品种会落到不同子目录。你在 MT5 里点开数据文件夹,顺着 MQL5/Files 往下翻,就能看到这种带前缀的嵌套结构。 要验证这段逻辑是否生效,最直接的方法是改一次 DirectoryPrefix 再跑一遍优化,观察文件是否按新前缀落盘。外汇与贵金属回测涉及历史质量差异,挪文件动作本身不影响结果,但路径错了会导致报告丢失。

MQL5 / C++
    class="kw">return Reports.GetDirectory($"{DirectoryPrefix} {OptimiserName} {ExpertName} {Symbol}", true);
    }
    class=class="str">"cmt">/// <summary>
    class=class="str">"cmt">/// Path to Data/Tester 
    class=class="str">"cmt">/// Needed to temporarily move files from the terminal directory of the same name
    class=class="str">"cmt">/// </summary>
    class="kw">public DirectoryInfo Tester => WDRoot.GetDirectory("Tester", true);
}

◍ 把工具请下神坛

这套自动优化器走到这一步,技术骨架已经摊开:图形界面、文件修订、项目逻辑分层都讲完了,下一篇才进程序逻辑的实现。眼下能落地的,是把第四篇里那个带交易机器人的项目跑起来——编译优化器与测试机器人文件,再把 ReportManager.dll 丢进 MQL5/Libraries,就能测你自己的 EA。 评论区有个实测坑值得记:有用户用市场里的闭源 EA 和自带的 ExpertMACD 都报“没有优化数据可保存”,三个计数全是 0,终端里明明有优化结果。作者回得直白——必须用带源代码的 EA,并按第四篇给示例加上数据上传功能,否则优化结果进不了优化器。 外汇和贵金属市场高波动、高杠杆,这类自动化工具只是把重复劳动外包出去,不替你扛回撤。真要验证它值不值,开 MT5 把附件 ZIP 里的 Auto_Optimiser 解了,接一个你读得懂源码的机器人跑一轮向前优化,比看十篇综述都实在。

常见问题

先确认退出前用绝对路径而非相对路径写配置,并在退出钩子里强制 Flush 文件流;终端异常关闭时优先检查临时目录是否残留锁文件。
拆成键值对对象后反而更清晰,建议用版本号字段隔离不同优化批次,避免旧参数被新任务误读。
可以,小布能读取你本地优化输出目录,按品种和周期打标签并生成对比卡片,你只需点开对应品种页查看。
建议放在独立固态盘根目录下的专用文件夹,桌面路径层级深且易被同步软件扫描,会增加 IO 延迟。
写个按修改时间和后缀过滤的脚本一次性复制到统一落点,比手动翻找更不容易漏掉最新那批。