连续前行优化自动优化器:拆解 MVVM 逻辑层与对象生成结构(基础篇)
🧩

连续前行优化自动优化器:拆解 MVVM 逻辑层与对象生成结构(基础篇)

(1/3)·手动翻优化报告耗神费力,本文从类图与工厂对象讲清自动优化器的内部骨架

含代码示例 第 1/3 篇
不少交易者把自动优化器当黑箱,只改参数不究结构,一旦 GUI 卡死就无从下手。其实逻辑层与视图模型耦合错位,是多数自定义优化器崩溃的根源。先看清内部对象怎么生成,比盲目跑十轮回测更有用。

◍ 自动优化器的逻辑骨架

连续前行优化里,自动优化器要解决的不是单次跑赢,而是让 EA 在历史窗口上滚动挑选参数,再推到下一窗口验证。它的逻辑层负责决定「什么时候重算、用哪段样本、保留几组候选」;结构层则把这些决定落成可复用的函数与状态机。 在 MT5 里,这类优化器通常挂接在 OnTester 或自定义优化事件上。一个朴素的触发逻辑是:每走过 N 根 bar,若当前样本外误差超过阈值,就重跑一次前向窗口。下面这段演示了用柱数计数触发重优化的最小结构。 外汇与贵金属品种波动跳变频繁,样本外失效概率偏高,重优化节奏需要结合点差与滑点实测,不能只看回测曲线漂亮。

MQL5 / C++
class="type">int barsPassed=class="num">0;
class="type">int optimPeriod=class="num">500;
class="type">class="kw">double outSampleErr=class="num">0.0;

class="type">class="kw">double OnTester()
{
   barsPassed++;
   if(barsPassed>=optimPeriod)
   {
      class=class="str">"cmt">// 触发重优化:重算样本内参数
      RelaunchOptimization();
      barsPassed=class="num">0;
   }
   outSampleErr=GetOutOfSampleError();
   class="kw">return outSampleErr;
}

自动优化器的内部逻辑从哪切入

上一篇文章已经把自动优化器应用程序的图形界面搭了出来,但真正的逻辑层和内部结构还没动过。这一节要补的就是这块,把程序在运行期到底怎么调、谁调谁,用 UML 示意图摊开。 本系列前作已经覆盖了从优化报告操控到 GUI 创建的完整链路:第一部分讲报告操控,第二部分讲机器人机理,第三部分把机器人改成自动优化器,第四部分做优化管理器,第五部分才落到项目概述和界面。现在接的是第五篇之后,重点转向内核。 UML 图在这里只画主要对象和它们之间的调用关系,不逐个罗列现有对象。你看图时重点盯消息流和生命周期,而不是去数类有几个方法。外汇与贵金属 EA 优化本身属高风险操作,回测顺不代表实盘能复现。

「MVVM 三层对象怎么在启动时串起来」

这套自动优化器的界面逻辑建立在 MVVM 上:图形层(蓝)只管 XAML 呈现,ViewModel 层(紫)做桥接,数据模型层(棕)跑实际优化业务。接口(粉)把模型与优化器抽象掉,让 ViewModel 不依赖具体模型实现,后续也能在 ComboBox 里换优化器。 启动顺序很固定:先实例化 AutoOptimiser 主窗口,XAML 里声明的 DataContext 会顺带 new 出 AutoOptimiserVM;后者通过‘组合’被窗口完全拥有,生命周期绑死在图形层上。ViewModel 不直接碰 MainModel,而是认 IMainModel 接口,用‘聚合’方式拿模型——模型实例其实是在 MainModelCreator 静态构造函数里生成,同时存进静态属性和 VM,所以应用退出前不会被回收。 优化器走的是‘抽象工厂’:OptimiserCreator 产出的构造函数被强制转成基类,Create() 出来的优化器再转成 IOptimiser 存进 MainModel 字段。这样跑的时候换优化逻辑不用改调用方。默认 SimpleOptimiser 自带 GUI,其 Settings 的 VM 通过组合归图形部分所有,数据模型则同时聚合给优化器和 VM,方便优化器读界面上的参数。 序列图里能数出来的硬节点:主窗口实例 → SimpleOptimiserVM → MainModelCreator.Model 静态属性 → MainModel(建优化器构造函数列表,ComboBox 就吃这个)→ ChangeOptimiser 调首个构造函数 Create() → 优化器转接口存入模型。外汇/贵金属 EA 优化本身高风险,回测拟合不等于实盘概率。

◍ Model 层的接口隔离与事件回传机制

自动优化器的业务逻辑集中在 Model 目录,根文件 MainModel.cs 体量超过 1000 行,实际只暴露必要方法给上层。它实现 IMainModel 接口,接口用 #region 把成员切成若干组件,对外只给只读取值器,ViewModel 层因此无法意外改写数据模型里的对象。 接口里最值得盯的是三个优化结果列表:AllOptimisationResults、ForwardOptimisations、HistoryOptimisations,它们都包在 ReportData 结构里,GUI 的 Results 选项卡表格直接消费这份数据。ReportData 除了结果,还存了启动测试所需的优化器基础设置,方便双击某次通关项重跑或做历史对比。 数据模型往回传信息靠事件,不是直接碰图形。共 4 个事件:3 个自定义、1 个来自 INotifyPropertyChanged(虽模型本身不必继承该接口,这里为了方便仍继承了)。其中 ThrowException 最初只发错误,现在也夹带文本告警——后续读代码时要意识到它不一定是异常。 模型加载机器人参数的入口是 GetBotParams,支持递归。依据标准终端布局,参数文件落在 MQL5/Profiles/Tester/{机器人名}.set。若 isUpdate=true,先删旧文件、用 TerminalManager 拉起终端生成默认 set 再读回;该分支对应界面上的 “Update(*.set) file” 按钮。若文件已存在且不需重生成,则直接读取或递归更新。 StartOptimisation 是 async 方法:先校验品种与范围,缺则解冻 GUI 并报错退出;选了测试模式就重定向到测试启动。追加模式下会清空 Data 临时目录再跑,优化异步执行不卡界面,且把历史缓存拷进临时目录保证可续跑。报告路径固定为 {机器人名}_Report.xml,由机器人路径拼串得到。 测试由模型而非优化器拉起。日期边界逻辑是:FromDate 取最小通关日、ToDate 取最大、Forward 标前向验证日;若只设前向区间则视同历史区间。过程同样 try-catch,生成 set 文件后写配置、启终端、等关闭,最后用事件通知图形层测试完毕——因为方法是异步的,不靠返回值同步。

MQL5 / C++
class=class="str">"cmt">/// <summary>
class=class="str">"cmt">/// Data model interface of the main optimizer window
class=class="str">"cmt">/// </summary>    
interface IMainModel : INotifyPropertyChanged
{
    class="macro">#region Getters
    class=class="str">"cmt">/// <summary>
    class=class="str">"cmt">/// Selected optimizer
    class=class="str">"cmt">/// </summary>
    IOptimiser Optimiser { get; }
    class=class="str">"cmt">/// <summary>
    class=class="str">"cmt">/// The list of names of terminals installed on the computer
    class=class="str">"cmt">/// </summary>
    IEnumerable<class="type">class="kw">string> TerminalNames { get; }
    class=class="str">"cmt">/// <summary>
    class=class="str">"cmt">/// The list of names of optimizers available for usage
    class=class="str">"cmt">/// </summary>
    IEnumerable<class="type">class="kw">string> OptimisatorNames { get; }
    class=class="str">"cmt">/// <summary>
    class=class="str">"cmt">/// The list of names of directories with saved optimizations(Data/Reports/*)
    class=class="str">"cmt">/// </summary>
    IEnumerable<class="type">class="kw">string> SavedOptimisations { get; }
    class=class="str">"cmt">/// <summary>
    class=class="str">"cmt">/// Structure with all passes of optimization results
    class=class="str">"cmt">/// </summary>
    ReportData AllOptimisationResults { get; }
    class=class="str">"cmt">/// <summary>
    class=class="str">"cmt">/// Forward tests
    class=class="str">"cmt">/// </summary>
    List<OptimisationResult> ForwardOptimisations { get; }
    class=class="str">"cmt">/// <summary>

把优化器接口接进自己的面板

做 MT5 批量参数寻优时,如果直接调终端自带的测试器对话框,很难把多轮结果沉淀下来做横向比对。上面这段接口定义给出了一条更顺手的路:用独立数据模型托管历史优化结果、事件与方法,自己在 C# 侧撸个轻量面板就能控场。 HistoryOptimisations 这个属性是 List<OptimisationResult> 只读集合,跑过的每一轮优化会按对象压进列表,后续载入旧报告靠 LoadSavedOptimisation(string optimisationName) 按名取回,不必重新算。 三个事件值得先挂好:ThrowException 抛模型内异常字符串,OptimisationStoped 在优化被中断时触发,PBUpdate 带 (string, double) 回传进度条文案与百分比。不挂的话,长时间跑 EURUSD 或 XAUUSD 优化时你只能盲等。 方法层最常用的是 StartOptimisation(OptimiserInputData, bool IsAppend, string dirPrefix):IsAppend 为 true 时把新结果追加到已有数据,false 则覆盖;StopOptimisation() 供用户侧强停。ChangeTerminal / ChangeOptimiser 负责切换终端实例与优化器实现,返回 bool 表示切换是否成功。 GetBotParameters 拿 EA 参数清单,SaveToCSVSelectedOptimisations 把勾选的轮次导出 csv——外汇与贵金属杠杆高、滑点跳空频繁,导出后建议人工复核夏普与回撤,再决定要不要上实盘。

MQL5 / C++
List<OptimisationResult> HistoryOptimisations { get; }
class="macro">#region Events
class=class="str">"cmt">/// Event of exception class="kw">throw form the data model
event Action<class="type">class="kw">string> ThrowException;
class=class="str">"cmt">/// Optimization stop error
event Action OptimisationStoped;
class=class="str">"cmt">/// Event of progress bar update form the data model
event Action<class="type">class="kw">string, class="type">class="kw">double> PBUpdate;
class="macro">#endregion
class="macro">#region Methods
class=class="str">"cmt">/// Method loading previously saved optimization results
class="type">void LoadSavedOptimisation(class="type">class="kw">string optimisationName);
class="type">bool ChangeTerminal(class="type">class="kw">string terminalName);
class="type">bool ChangeOptimiser(class="type">class="kw">string optimiserName, class="type">class="kw">string terminalName = null);
class="type">void StartOptimisation(OptimiserInputData optimiserInputData, class="type">bool IsAppend, class="type">class="kw">string dirPrefix);
class="type">void StopOptimisation();
IEnumerable<ParamsItem> GetBotParams(class="type">class="kw">string botName, class="type">bool isUpdate);
class="type">void SaveToCSVSelectedOptimisations(class="type">class="kw">string pathToSavingFile);
把结构梳理交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到优化器对象依赖的实时提示,你专注决策而非扒代码。

常见问题

它把数据模型、视图模型与图形层解耦,使优化逻辑可独立替换而不动界面,降低后续扩展成本。
隐藏具体实现后,视图模型不依赖某一数据模型,优化器逻辑也能多套切换,用户从组合框选即可。
可以,小布内置的 AIGC 会解析品种页里的优化器依赖关系,省去你手绘 UML 的功夫。
一个是优化器本体模型,一个是 GUI 模型,分工不同;按 MVVM 它们通过 ViewModel 桥接而非直连。
它按启动需要创建对应实例,避免硬编码对象,后续自定义优化逻辑时扩展更干净。