自动优化器图形绑定基础:理清逻辑层与 ViewModel 的连接脉络(基础篇)
(1/3)· 想把 MQL5 自动优化器跑顺,却卡在逻辑和图形各说各话?这篇先铺底层骨架
不少交易者把优化器逻辑写完了,界面却点不动、字段不刷新,以为是 EA bug,其实是 ViewModel 没把属性暴露对。图形层和逻辑层各写各的,后期改一处崩一片。先搞清连接器怎么干活,后面两篇才能直接上实战。
把自动优化器的逻辑挂到图形上
这一节要解决的问题很具体:自动优化器跑起来之后,逻辑层产生的状态、进度、阶段结果,怎么实时反映到 MT5 面板里,而不是只在日志里刷行。 原文给出的系列背景是,前六篇已经把优化报告操控、机器人改造、优化管理器、GUI 骨架和逻辑结构都铺完了;本篇(系列第七部分,发布于 2020-08-14,原文浏览量 2212)重点是把「逻辑部分」和「图形呈现」用程序真正绑起来。 核心落点是:从一次优化运行的启动开始,逐层跟到自动优化器类的各个阶段,搞清楚显示模块从哪里被调用、参数怎么从业务代码推到图形控件。读者在 MT5 里打开自己的 EA 面板时,可以对照检查——你的 OnTimer 或 worker 线程里有没有显式调用图形刷新,还是只更新了内部变量。 外汇与贵金属品种上跑这类自动优化,参数组合爆炸快、过拟合风险高,任何「阶段收敛」都只是概率倾向,需以样本外前行检验复核。
「图形字段如何跟逻辑层双向绑定」
做 MT5 配套工具时,最容易被绕晕的是 WPF 那套数据绑定:ViewModel 类就是图形控件和底层逻辑的桥,它暴露的公开属性直接映射到界面上每个可编辑框。属性若是只读 getter,界面改不了;带 setter 才能从前端覆盖背后对象。前面聊过绑定原理,这里只给能直接抄的片段。 文本字段用可读写属性接就行。下面这段 XAML 定义了一个宽 100 的资产名输入框:IsEnabled 控制能否编辑,Text 接 AssetName。Binding 后面跟的 UpdateSourceTrigger=PropertyChanged 意味着只有 ViewModel 里触发 OnPropertyChanged 事件时,界面才刷新,不是每次按键都重绘。 类型约束是白捡的:Text 绑到 double 就只收数字,绑到 int 就只收整数。后端 EnableMainTogles 属性设为 private set,意味着只有 AutoOptimiserVM 类自己能写,外部界面锁死改不了。 遇到数据集合,比如前向验证结果表,属性里放的是列表。XAML 里 ListView 引类、建 GridView、列字段,三件套对应 System.Windows.Controls 下的 ListView / GridView / GridViewColumn。ItemsSource 绑到 ViewModel 的集合,表格每行就由公开属性类填充;SelectedIndex 再绑 SelectedForwardItem,用户点哪行逻辑层就知道。 后端用 ObservableCollection 做数据源,它自带变更通知,每次增删元素自动调事件推给图形。SelectedForwardItem 的 setter 里写了回调:value 大于 -1 才去填交易参数、日均利、最大回撤那些关联表;等于 -1 说明表空,直接跳过。行内按钮或双击则走 ICommand 接口接逻辑。
<p style="text-align:justify;"><span class="tag"><<span class="title">TextBox</span><span class="attribute"> Width=<span class="value">"class="num">100"</span></span> <span class="attribute"> IsEnabled=<span class="value">"{Binding EnableMainTogles, UpdateSourceTrigger=PropertyChanged}"</span></span> <span class="attribute"> Text=<span class="value">"{Binding AssetName}"</span></span>/></span></p> <p style="text-align:justify;"><span class="comment">class=class="str">"cmt">/// <summary></span> <span class="comment">class=class="str">"cmt">/// If the class="kw">switch = class="kw">false, then the most important fields are not available</span> <span class="comment">class=class="str">"cmt">/// </summary></span> <span class="keyword">class="kw">public</span> <span class="keyword">class="type">bool</span> <span style="background-class="type">class="kw">color:rgb(class="num">249, class="num">204, class="num">202);">EnableMainTogles</span> { get; <span class="keyword">class="kw">private</span> set; } = <span class="macro">true</span>;</p> <p style="text-align:justify;"><span class="comment">class=class="str">"cmt">/// <summary></span> <span class="comment">class=class="str">"cmt">/// Name of the asset selected for tests / optimization</span> <span class="comment">class=class="str">"cmt">/// </summary></span> <span class="keyword">class="kw">public</span> <span class="keyword">class="type">class="kw">string</span> AssetName { get; set; }</p> <p style="text-align:justify;"><span style="background-class="type">class="kw">color:rgb(class="num">249, class="num">204, class="num">202);"><ListView</span> <span style="background-class="type">class="kw">color:rgb(class="num">216, class="num">232, class="num">194);">ItemsSource="{Binding ForwardOptimisations}"</span> <span style="background-class="type">class="kw">color:rgb(class="num">222, class="num">188, class="num">133);">SelectedIndex="{Binding SelectedForwardItem}"</span> v:ListViewExtention.DoubleClickCommand="{Binding StartTestForward}"> <span style="background-class="type">class="kw">color:rgb(class="num">232, class="num">183, class="num">215);"><ListView.View> <GridView></span> <<span style="background-class="type">class="kw">color:rgb(class="num">164, class="num">192, class="num">228);">GridViewColumn</span> Header="Date From" <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">DisplayMemberBinding="{Binding From}"</span>/> <<span style="background-class="type">class="kw">color:rgb(class="num">164, class="num">192, class="num">228);">GridViewColumn</span> Header="Date Till" <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">DisplayMemberBinding="{Binding Till}"</span>/> <<span style="background-class="type">class="kw">color:rgb(class="num">164, class="num">192, class="num">228);">GridViewColumn</span> Header="Payoff" <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">DisplayMemberBinding="{Binding Payoff}"</span>/> <<span style="background-class="type">class="kw">color:rgb(class="num">164, class="num">192, class="num">228);">GridViewColumn</span> Header="Profit pactor"
◍ 回测结果表的列绑定清单
在 MT5 的回测分析面板里,ListView 的 GridView 列直接决定了你能一眼看到哪些风险与收益指标。下面这段 XAML 式绑定把盈利因子、恢复因子、VaR 系列和交易次数都铺到了表头。 ProfitFactor 与 AverageProfitFactor 两列分别绑定总盈利因子和均值,RecoveryFactor / AverageRecoveryFactor 同理对应回撤恢复能力。DD 列直连最大回撤,PL 是净利润,AltmanZScore 给出破产概率的 Z 值参考。 VaR 90 / 95 / 99 三列把不同置信下的在险价值都列出来,Mx 与 Std 则对应收益序列的均值与标准差。TotalTrades 让样本量透明——少于 30 笔的 forward test 结论可信度倾向偏低。 外汇与贵金属杠杆高,VaR 99 若超过本金 20% 应视为高危信号,可能触发强平。复制下列绑定到你的 ListView 即可在本地验证字段是否对齐。
<ListView.View>
<GridView>
<GridViewColumn Header="Profit Factor" DisplayMemberBinding="{Binding ProfitFactor}"/>
<GridViewColumn Header="Average Profit Factor" DisplayMemberBinding="{Binding AverageProfitFactor}"/>
<GridViewColumn Header="Recovery factor" DisplayMemberBinding="{Binding RecoveryFactor}"/>
<GridViewColumn Header="Average Recovery Factor" DisplayMemberBinding="{Binding AverageRecoveryFactor}"/>
<GridViewColumn Header="PL" DisplayMemberBinding="{Binding PL}"/>
<GridViewColumn Header="DD" DisplayMemberBinding="{Binding DD}"/>
<GridViewColumn Header="Altman Z score" DisplayMemberBinding="{Binding AltmanZScore}"/>
<GridViewColumn Header="Total trades" DisplayMemberBinding="{Binding TotalTrades}"/>
<GridViewColumn Header="VaR class="num">90" DisplayMemberBinding="{Binding VaR90}"/>
<GridViewColumn Header="VaR class="num">95" DisplayMemberBinding="{Binding VaR95}"/>
<GridViewColumn Header="VaR class="num">99" DisplayMemberBinding="{Binding VaR99}"/>
<GridViewColumn Header="Mx" DisplayMemberBinding="{Binding Mx}"/>
<GridViewColumn Header="Std" DisplayMemberBinding="{Binding Std}"/>
</GridView>
</ListView.View>
</ListView>把前向优化结果塞进可绑定的报表项
做前向优化(forward optimisation)可视化时,最省事的办法是把每一段样本外跑出来的结果包成一个轻量对象,再丢进 ObservableCollection 让界面自动刷新。下面这段 C# 风格的实现,核心就是 ReportItem 这个包装类,把 OptimisationResult 里的日期边界、盈利因子、回撤、VaR 分位都暴露成属性。 _selectedForwardItem 这个私有字段配合 SelectedForwardItem 的 setter,在用户从列表里挑中某一段前向测试(value > -1)时,会一口气调 FillInBotParams、FillInDailyPL、FillInMaxPLDD 三个方法把对应那段的数据填进图表与参数区。也就是说,切换样本外区间和刷新面板是绑定在一起的,不用手动按钮。 ReportItem 里暴露的 VaR90 / VaR95 / VaR99 分别对应 90%、95%、99% 置信度的在险价值,还有 Mx(均值)和 Std(标准差)直接来自 VaR 子对象。做贵金属或外汇 EA 的前向验证时,盯这几个分位比只看总利润更不容易被过拟合曲线骗到。这类品种杠杆高、跳空频繁,样本外 VaR99 若明显膨胀,实盘爆仓概率可能急剧上升。 直接把上面结构抄进你的优化报告窗体,开 MT5 跑一组 EURUSD 的向前步进优化,切几个区间看看 VaR95 是不是随时间段平稳,比盯着 Payoff 单值靠谱。
class="kw">public ObservableCollection<ReportItem> ForwardOptimisations { get; } = new ObservableCollection<ReportItem>(); class=class="str">"cmt">/// <summary> class=class="str">"cmt">/// Selected forward pass class=class="str">"cmt">/// </summary> class="kw">private class="type">int _selectedForwardItem; class="kw">public class="type">int SelectedForwardItem { get => _selectedForwardItem; set { _selectedForwardItem = value; if (value > -class="num">1) { FillInBotParams(model.ForwardOptimisations[value]); FillInDailyPL(model.ForwardOptimisations[value]); FillInMaxPLDD(model.ForwardOptimisations[value]); } } } class=class="str">"cmt">/// <summary> class=class="str">"cmt">/// Class - a wrapper for a report item(for a graphical interval) class=class="str">"cmt">/// </summary> class ReportItem { class=class="str">"cmt">/// <summary> class=class="str">"cmt">/// Constructor class=class="str">"cmt">/// </summary> class=class="str">"cmt">/// <param name="item">Item</param> class="kw">public ReportItem(OptimisationResult item) { result = item; } class=class="str">"cmt">/// <summary> class=class="str">"cmt">/// Report item class=class="str">"cmt">/// </summary> class="kw">private readonly OptimisationResult result; class="kw">public DateTime From => result.report.DateBorders.From; class="kw">public DateTime Till => result.report.DateBorders.Till; class="kw">public class="type">class="kw">double SortBy => result.SortBy; class="kw">public class="type">class="kw">double Payoff => result.report.OptimisationCoefficients.Payoff; class="kw">public class="type">class="kw">double ProfitFactor => result.report.OptimisationCoefficients.ProfitFactor; class="kw">public class="type">class="kw">double AverageProfitFactor => result.report.OptimisationCoefficients.AverageProfitFactor; class="kw">public class="type">class="kw">double RecoveryFactor => result.report.OptimisationCoefficients.RecoveryFactor; class="kw">public class="type">class="kw">double AverageRecoveryFactor => result.report.OptimisationCoefficients.AverageRecoveryFactor; class="kw">public class="type">class="kw">double PL => result.report.OptimisationCoefficients.PL; class="kw">public class="type">class="kw">double DD => result.report.OptimisationCoefficients.DD; class="kw">public class="type">class="kw">double AltmanZScore => result.report.OptimisationCoefficients.AltmanZScore; class="kw">public class="type">int TotalTrades => result.report.OptimisationCoefficients.TotalTrades; class="kw">public class="type">class="kw">double VaR90 => result.report.OptimisationCoefficients.VaR.Q_90; class="kw">public class="type">class="kw">double VaR95 => result.report.OptimisationCoefficients.VaR.Q_95; class="kw">public class="type">class="kw">double VaR99 => result.report.OptimisationCoefficients.VaR.Q_99; class="kw">public class="type">class="kw">double Mx => result.report.OptimisationCoefficients.VaR.Mx; class="kw">public class="type">class="kw">double Std => result.report.OptimisationCoefficients.VaR.Std; }