使用图形界面处理优化结果(基础篇)
把 MT5 优化结果搬进图形界面
MT5 策略测试器的优化结果默认以表格呈现,当参数组合超过数千组时,肉眼比对极易漏掉局部最优区间。2018 年 7 月发布的这套思路,核心是把优化输出先落盘,再借图形界面做二次可视与交互。 实际做法是:优化跑完后把结果保存为文件,用数据帧结构承载各参数与对应的回测指标,随后在自定义面板里展开。这样交易者能在 MT5 内直接点选、排序、圈定感兴趣的区域,而不是导出到外部表格软件再切回来。 外汇与贵金属品种受点差和滑点影响大,优化甜区在实盘可能漂移,图形界面只解决「看清楚」,不替代样本外验证。开 MT5 跑一组两参数优化,保存结果后用本文方式加载,比干盯表格更快定位可疑组合。
「把百优结果与多品种曲线接起来」
上一篇用 MQL5 图形界面把优化结果做了可视化,本篇把任务往前推一步:从完整优化报告中筛出表现最好的 100 组参数,直接落进 GUI 表格里供点选。 另一篇文章里提过多交易品种余额图的思路,这里把它和百优表格合并——用户在表格里选中任意一行,独立图表就画出对应参数下的多品种余额与回撤曲线。 优化完 EA 参数后,交易者能借此快速横向比对这 100 组结果的资金曲线形态与回撤深度,再挑出倾向更稳的一组去跑实盘或进一步验证。外汇与贵金属市场高波动、高杠杆,任何历史优化优势都只是概率倾向,开 MT5 自测前先确认点差与滑点设置贴近真实环境。
◍ 把回测面板拆成可停靠的控件树
做 EA 优化回放界面时,先把所有可见元件列清楚:主窗体、状态栏、分页容器,以及分页里的文本框(控制回放帧数、滚动延迟毫秒)、启动按钮、四张图(全部余额、筛选后余额、选中行多品种余额、回撤)、百行结果表、进度条。这些创建逻辑建议丢进独立 include 文件,方便在多个 MQL 程序里直接复用。 表格默认承载 100 个最优优化结果(按利润最大排序),但 GUI 在优化前就生成,所以初始为空,列数和表头由后续数据处理类确定。表格要支持表头显示、点击排序、单选行、锁定选中、手动调列宽和斑马纹,否则盯盘时容易看错行。 把表格挂到第二页时,传对象必须指定页面索引,主类设为 Tabs 元件。这样页面区域缩放,表格会跟着主元件自适应——这个属性在 Table 类里配,漏了就会在分屏时错位。 下面这段是 CProgram 类骨架,私有区把窗体、状态栏、Tabs、两个文本框、按钮、四个图、表格、进度条全声明了,公有只暴露 CreateGUI,其余创建函数私有化。复制到 MT5 的 include 里,补全 CreateGUI 调用顺序即可跑出空壳界面。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用于创建应用程序的类 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CProgram : class="kw">public CWndEvents { class="kw">private: class=class="str">"cmt">//--- 窗口 CWindow m_window1; class=class="str">"cmt">//--- 状态条 CStatusBar m_status_bar; class=class="str">"cmt">//--- 页面 CTabs m_tabs1; class=class="str">"cmt">//--- 文本框 CTextEdit m_curves_total; CTextEdit m_sleep_ms; class=class="str">"cmt">//--- 按钮 CButton m_reply_frames; class=class="str">"cmt">//--- 图表 CGraph m_graph1; CGraph m_graph2; CGraph m_graph3; CGraph m_graph4; class=class="str">"cmt">//--- 表格 CTable m_table_param; class=class="str">"cmt">//--- 进度条 CProgressBar m_progress_bar; class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 创建图形界面 class="type">bool CreateGUI(class="type">void); class=class="str">"cmt">//--- class="kw">private: class=class="str">"cmt">//--- 表单 class="type">bool CreateWindow(const class="type">class="kw">string text); class=class="str">"cmt">//--- 状态条 class="type">bool CreateStatusBar(const class="type">int x_gap,const class="type">int y_gap); class=class="str">"cmt">//--- 页面 class="type">bool CreateTabs1(const class="type">int x_gap,const class="type">int y_gap); class=class="str">"cmt">//--- 文本框 class="type">bool CreateCurvesTotal(const class="type">int x_gap,const class="type">int y_gap,const class="type">class="kw">string text); class="type">bool CreateSleep(const class="type">int x_gap,const class="type">int y_gap,const class="type">class="kw">string text); class=class="str">"cmt">//--- 按钮 class="type">bool CreateReplyFrames(const class="type">int x_gap,const class="type">int y_gap,const class="type">class="kw">string text); class=class="str">"cmt">//--- 图表 class="type">bool CreateGraph1(const class="type">int x_gap,const class="type">int y_gap); class="type">bool CreateGraph2(const class="type">int x_gap,const class="type">int y_gap);
主参数表格的自动伸缩与排序配置
在 MT5 自定义面板里,主表格负责承载实时参数。上面这段 CProgram::CreateMainTable 把表格挂到标签页索引 1 之下,并设成 1 行 1 列的基础结构,后续由外部数据填充。 表格开启了表头显示、排序模式、行可选中且不可取消选中,同时打开列宽拖拽与斑马纹(clrWhiteSmoke)。这些属性让交易者点表头就能按数值重排,肉眼区分奇偶行降低盯盘疲劳。 自动伸缩部分给了右偏移 2 像素、底偏移 2 像素,X/Y 方向都随父容器拉伸。实际在 1920×1080 屏上,面板贴右边时表格通常会吃到约 2px 余量,不会顶满边框。 [CODE] bool CProgram::CreateMainTable(const int x_gap,const int y_gap) { //--- 保存主控件的指针 m_table_param.MainPointer(m_tabs1); //--- 附加到页面 m_tabs1.AddToElementsArray(1,m_table_param); //--- 属性 m_table_param.TableSize(1,1); m_table_param.ShowHeaders(true); m_table_param.IsSortMode(true); m_table_param.SelectableRow(true); m_table_param.IsWithoutDeselect(true); m_table_param.ColumnResizeMode(true); m_table_param.IsZebraFormatRows(clrWhiteSmoke); m_table_param.AutoXResizeMode(true); m_table_param.AutoYResizeMode(true); m_table_param.AutoXResizeRightOffset(2); m_table_param.AutoYResizeBottomOffset(2); //--- 创建控件 if(!m_table_param.CreateTable(x_gap,y_gap)) return(false); //--- 把对象加到对象组的统一数组中 CWndContainer::AddToElementsArray(0,m_table_param); return(true); } [/CODE] 逐行拆解:MainPointer 绑定父标签控件;AddToElementsArray(1,...) 表示放进第 2 个分页(索引从 0 计);TableSize(1,1) 先建空壳;ShowHeaders 到 ColumnResizeMode 这组布尔开的是交互;IsZebraFormatRows 收一个颜色常量做隔行底色;AutoX/YResize* 四行决定贴边留白逻辑;CreateTable 失败直接返 false,成功再进全局元素数组。 外汇与贵金属波动剧烈、杠杆风险高,面板只是辅助,参数表里的数值不能当成方向依据。复制这段代码进你的 .mqh,把 AutoXResizeRightOffset 改成 10 看看拖拽手感差异,比读文档来得快。
class="type">bool CProgram::CreateMainTable(const class="type">int x_gap,const class="type">int y_gap) { class=class="str">"cmt">//--- 保存主控件的指针 m_table_param.MainPointer(m_tabs1); class=class="str">"cmt">//--- 附加到页面 m_tabs1.AddToElementsArray(class="num">1,m_table_param); class=class="str">"cmt">//--- 属性 m_table_param.TableSize(class="num">1,class="num">1); m_table_param.ShowHeaders(true); m_table_param.IsSortMode(true); m_table_param.SelectableRow(true); m_table_param.IsWithoutDeselect(true); m_table_param.ColumnResizeMode(true); m_table_param.IsZebraFormatRows(clrWhiteSmoke); m_table_param.AutoXResizeMode(true); m_table_param.AutoYResizeMode(true); m_table_param.AutoXResizeRightOffset(class="num">2); m_table_param.AutoYResizeBottomOffset(class="num">2); class=class="str">"cmt">//--- 创建控件 if(!m_table_param.CreateTable(x_gap,y_gap)) class="kw">return(false); class=class="str">"cmt">//--- 把对象加到对象组的统一数组中 CWndContainer::AddToElementsArray(class="num">0,m_table_param); class="kw">return(true); }
「把优化结果塞进数据帧的封装顺序」
多品种 EA 优化时,FrameAdd() 不接受字符串数组,编译期就会报“字符串数组和包含对象的结构不被允许”。若改为写文件再传帧,又得高频落盘,不划算。折中方案是把所有要可视化的数据收进一个 double 数组,再按帧参数里的键值在接收端展开。 数组内的排布有固定次序:最前面是统计变量(常数数量,本类用 STAT_TOTAL=6 预留),接着总余额,再是每个品种独立余额,最后是横纵轴回撤。品种枚举用‘;’拼成字符串随帧传,顺序与数组一致,展开时据此对齐。 统计段只取 5 个量加通过数:利润 STAT_PROFIT、交易数 STAT_TRADES、回撤 STAT_EQUITY_DDREL_PERCENT、采收率 STAT_RECOVERY_FACTOR,以及通过编号。余额段长度对总数据和单品种相同,作为 double 参数传给 FrameAdd(),品种名则作为 string 参数同传。 GetHistorySymbols() 从历史成交提取品种;若多于一种,数组大小加 1,首元素留总余额。CopyDataToMainArray() 按顺序把各品种余额并回通用数组,末轮拷回撤;GetStatData() 把统计量前置,数组尺寸 = 余额尺寸 + 统计量数。OnTesterEvent() 调这一系列,优化末轮由 FinalRecalculateFrames() 重算、填表头、按列排序取前 100 行。默认排序列索引 2(最大利润),要在 OnInit 阶段用 ColumnSortIndex() 改标准。外汇与贵金属优化高风险,参数空间过大会显著拉长回测时间,建议先窄范围验证封装逻辑。
<span class="comment">class=class="str">"cmt">//--- 用于所有交易品种余额的数组</span> <span class="keyword">class="kw">struct</span> CSymbolBalance { <span class="keyword">class="type">class="kw">double</span> m_data[]; }; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| 用于操作优化结果的类 |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class</span> CFrameGenerator { <span class="keyword">class="kw">private</span>: <span class="comment">class=class="str">"cmt">//--- 余额结构</span> CSymbolBalance m_symbols_balance[]; }; <span class="comment">class=class="str">"cmt">//--- 统计参数的数量</span> <span class="preprocessor">class="macro">#define </span>STAT_TOTAL <span class="number">class="num">6</span> ::<span class="functions">FrameAdd</span>(<span style="background-class="type">color:rgb(class="num">216, class="num">232, class="num">194);">m_report_symbols</span>,<span class="number">class="num">1</span>,<span style="background-class="type">color:rgb(class="num">255, class="num">242, class="num">153);">data_count</span>,stat_data); <span class="preprocessor">class="macro">#include </span><Trade\DealInfo.mqh> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| 用于操作优化结果的类 |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class</span> CFrameGenerator { <span class="keyword">class="kw">private</span>: <span class="comment">class=class="str">"cmt">//--- 操作交易</span> CDealInfo m_deal_info; <span class="comment">class=class="str">"cmt">//--- 报告中的交易品种</span> <span style="background-class="type">color:rgb(class="num">216, class="num">232, class="num">194);"><span class="keyword">class="type">class="kw">string</span> m_report_symbols;</span> <span class="comment">class=class="str">"cmt">//---</span> <span class="keyword">class="kw">private</span>: <span class="comment">class=class="str">"cmt">//--- 从账户历史中取得交易品种并返回它们的数量</span> <span class="keyword">class="type">int</span> GetHistorySymbols(<span class="keyword">class="type">void</span>); }; <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment">class=class="str">"cmt">//| 从账户历史中取得交易品种并返回它们的数量 |</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class="type">int</span> CFrameGenerator::GetHistorySymbols(<span class="keyword">class="type">void</span>) { <span class="comment">class=class="str">"cmt">//--- 第一次在循环中遍历,取得交易品种</span> <span class="keyword">class="type">int</span> deals_total=::<span class="functions">HistoryDealsTotal</span>(); <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i=<span class="number">class="num">0</span>; i<deals_total; i++) {