连续前行优化 (第七部分): 将自动优化器的逻辑部分与图形绑定,并通过程序控制图形·综合运用
「回测结束后的界面状态复位逻辑」
在 MT5 优化器外壳的 C# 桥接层里,Model_PropertyChanged 是监听底层回测引擎状态翻转的核心入口。当 e.PropertyName 命中 "StopTest" 或 "ResumeEnablingTogle",说明测试收尾或按钮锁需要解除,此时把 EnableMainTogles 置 true,并把 Status 清空、Progress 归 0,再通过 dispatcher.Invoke 把这三个属性变更推给 GUI 线程。 若命中 "AllOptimisationResults",代表一轮优化_pass_列表已刷新:先 Clear 旧的 ReportDateBorders,再遍历 model.AllOptimisationResults 的 Keys 把日期边界塞回去,SelectedReportDateBorder 强制选 0 号位。随后用 ReplaceBotFixedParam 把 Expert / Deposit / Currency / Laverage 四个固定项按已加载结果覆写,最后弹 MessageBox "Report params where updated" 告知载入完成。 这段机制对外汇与贵金属品种同样适用,但跨品种优化时点差与杠杆跳变可能引发结果失真,回测结论仅具概率意义,实盘前必须在 MT5 用真实点差重跑验证。
class="kw">private class="type">void Model_PropertyChanged(object sender, PropertyChangedEventArgs e) { class=class="str">"cmt">// The test has completed, or you need to resume the availability of the buttons locked at the optimization or test start if (e.PropertyName == "StopTest" || e.PropertyName == "ResumeEnablingTogle") { class=class="str">"cmt">// button accessibility class="kw">switch = true EnableMainTogles = true; class=class="str">"cmt">// Reset status and progress Status = ""; Progress = class="num">0; class=class="str">"cmt">// Notify the GUI of changes dispatcher.Invoke(() => { OnPropertyChanged("EnableMainTogles"); OnPropertyChanged("Status"); OnPropertyChanged("Progress"); }); } class=class="str">"cmt">// Changed the list of passed optimization passes if (e.PropertyName == "AllOptimisationResults") { dispatcher.Invoke(() => { class=class="str">"cmt">// Clear the previously saved optimization passes and add new ones ReportDateBorders.Clear(); foreach(var item in model.AllOptimisationResults.AllOptimisationResults.Keys) { ReportDateBorders.Add(item); } class=class="str">"cmt">// Select the very first date SelectedReportDateBorder = class="num">0; class=class="str">"cmt">// Fill in the fixed settings of the tester in accordance with the settings of the uploaded results ReplaceBotFixedParam("Expert", model.AllOptimisationResults.Expert); ReplaceBotFixedParam("Deposit", model.AllOptimisationResults.Deposit.ToString()); ReplaceBotFixedParam("Currency", model.AllOptimisationResults.Currency); ReplaceBotFixedParam("Laverage", model.AllOptimisationResults.Laverage.ToString()); OnPropertyChanged("OptimiserSettingsForResults_fixed"); }); class=class="str">"cmt">// Notify when data loading is complete System.Windows.MessageBox.Show("Report params where updated"); } class=class="str">"cmt">// Filter or sort optimization passes if (e.PropertyName == "SortedResults" ||
◍ 属性变更驱动下的报告集合刷新
这段逻辑挂在 ViewModel 的属性变更通知上,靠 e.PropertyName 区分是哪一类数据到了,再借 dispatcher.Invoke 把更新塞回 UI 线程,避免跨线程写 ObservableCollection 直接抛异常。 FilteredResults 触发时只重设了 SelectedReportDateBorder 自身绑定,相当于通知界面边界索引变了;ForwardOptimisations 和 HistoryOptimisations 则是先 Clear 再逐条 new ReportItem(item) 填回,保证前后向与历史优化结果列表和底层 model 同步。 CSV 分支最简单,收到信号就弹个 MessageBox 提示 '(*.csv) File saved',实盘回测导出时你能靠这个确认落盘。 SelectedReportDateBorder 的 setter 里有个隐藏点:value 为 -1 时把内部字段归 0 并直接 return,不加载任何优化结果;否则按 ReportDateBorders[value] 从 model 的总表里取对应区间,清空 AllOptimisations 后重新 Add。ReportDateBorders 为空时同样提前 return,避免索引越界。
if (e.PropertyName == "FilteredResults") { dispatcher.Invoke(() => { SelectedReportDateBorder = SelectedReportDateBorder; }); } class=class="str">"cmt">// Updated forward optimization data if (e.PropertyName == "ForwardOptimisations") { dispatcher.Invoke(() => { ForwardOptimisations.Clear(); foreach(var item in model.ForwardOptimisations) { ForwardOptimisations.Add(new ReportItem(item)); } }); } class=class="str">"cmt">// Updated historical optimization data if (e.PropertyName == "HistoryOptimisations") { dispatcher.Invoke(() => { HistoryOptimisations.Clear(); foreach(var item in model.HistoryOptimisations) { HistoryOptimisations.Add(new ReportItem(item)); } }); } class=class="str">"cmt">// Save(*.csv) file with optimization/test results if (e.PropertyName == "CSV") { System.Windows.MessageBox.Show("(*.csv) File saved"); } class="macro">#region Selected optimisation date border index keeper class="kw">private class="type">int _selectedReportDateBorder; class="kw">public class="type">int SelectedReportDateBorder { get => _selectedReportDateBorder; set { AllOptimisations.Clear(); if (value == -class="num">1) { _selectedReportDateBorder = class="num">0; class="kw">return; } _selectedReportDateBorder = value; if (ReportDateBorders.Count == class="num">0) class="kw">return; List<OptimisationResult> collection = model.AllOptimisationResults.AllOptimisationResults[ReportDateBorders[value]]; foreach(var item in collection) { AllOptimisations.Add(new ReportItem(item)); } } } class="macro">#endregion
记住这一条就够了
这套连续前行优化器的代码已经随文打包在 Auto_Optimiser.zip(125.7 KB)里,编译入口是 MetaTrader Auto Optimiser 项目,依赖第一篇文章里的 ReportManager.dll 丢进 MQL5/Libraries 才能跑通。没碰过 Visual Studio 的人,按 CTRL+SHIFT+B 是最省事的编译方式,成品会落在 bin/Debug 或 bin/Release 两个文件夹之一。 下一篇才会补上用户评论里提到的 UI 改进和自动分段功能,这一篇只交代清楚已有工程怎么接你自己的 EA——第三、四篇讲了机器人适配自动优化器的必需步骤,照着接就行。外汇和贵金属市场杠杆高、滑点狠,这类优化器只是把回测流程自动化,实盘表现仍可能大幅偏离历史。 真要动手验证,先编译附带的测试机器人,再拿你手头任一个 MT5 EA 走一遍第三、四篇的链接流程;跑通一次,比读十篇说明都管用。