可视化使用选定标准优化的结果(基础篇)
「把优化结果摆到桌面上看」
在 MT5 的策略测试器跑完参数优化后,结果通常是一张密密麻麻的表格,肉眼筛可选集效率很低。Anatoli Kazharski 在 2018 年 9 月 3 日发布的示例里,用自定义图形界面把「选定标准的优化结果」直接可视化,让交易者在面板里点选、高亮、对比。 这套思路的核心不是新指标,而是把测试器导出的帧数据接进 GUI 表格,再用代码控制行的高亮与交互。原文给出的章节脉络是:开发图形界面 → 选择优化结果 → 编程突出显示表格行 → 帧数据辅助方法 → 事件处理。 对读者来说,可立刻验证的动作是:打开 MT5 策略测试器,跑一组 EA 的参数优化,观察结果标签页默认只按盈利排序,而自定义面板能按你定的标准(如回撤阈值)反向标红。外汇与贵金属品种杠杆高、滑点敏感,优化结果过拟合概率偏大,面板筛选只是辅助,不等于实盘胜率保证。
◍ 把优化结果摊在独立图表上
接着上一篇的多数据类型处理思路,这一节要解决的是:优化跑完以后,怎么更快把“按别的标准挑出来的最佳参数”看见。之前已经能把统计参数、品种余额数组、存款回撤塞进帧数组再展开,现在要往前再走一步——不止在表格里高亮对应行,而是让被选中的那几条余额曲线直接画在独立图表里。 表格里高亮行、累计图里同步出选中余额曲线,这个联动能让你在对比优化结果时少切几次窗口,评估回撤和权益走势更直观。优化后的结果若只在原表里反白,视线来回跳的成本其实不低。 另外按论坛成员提的需求,这一版给开发库里的 CTable 类加了键盘控制高亮行的能力。不用鼠标点,方向键或指定按键就能挪动选中行,配合前面的独立图表出图,批量扫参数的效率会上来一些。外汇与贵金属优化本身高风险,回测好不等于实盘稳,键盘筛行只是减少操作摩擦。
把优化结果界面重构成多页联动
上一版程序的图形界面分了三页:Frames、Results、Balance。现在把 Results 和 Balance 合并,选中表格某一行后图形直接联动刷新,不用再手动切页看回撤。 Frames 页保留重新滚动结果的控件:输入显示数量的栏位、滚动延迟毫秒数、启动按钮,以及余额曲线和全部结果的图形。Results 页下再分 Balances 和 Favorites 两组——前者画多品种余额与回撤,后者用图形展示表格里的最佳结果,并加了 CComboBox 下拉框来切换“最佳”的判定标准。 整套界面从窗口、状态栏到两组标签页和图形表格,都封装在 CProgram 类里。下面这段是类声明里 GUI 相关成员的骨架,可以直接抄进独立文件跟 MQL 程序类一起编译。 别把下拉框标准写死 CComboBox 的判定标准建议从配置文件读,不然每次改“最佳”定义都要重编译,实盘调外汇品种参数时很拖节奏。贵金属和多货币对回测高风险,下拉项最好留自定义入口。
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; CTabs m_tabs2; class=class="str">"cmt">//--- 输入栏位 CTextEdit m_curves_total; CTextEdit m_sleep_ms; class=class="str">"cmt">//--- 按钮 CButton m_reply_frames; class=class="str">"cmt">//--- 组合框 CComboBox m_criterion; class=class="str">"cmt">//--- 图形 CGraph m_graph1; CGraph m_graph2; CGraph m_graph3; CGraph m_graph4; CGraph m_graph5; class=class="str">"cmt">//--- 表格 CTable m_table_main; CTable m_table_symbols; class=class="str">"cmt">//--- 进度条 CProgressBar m_progress_bar; class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 创建 GUI class="type">bool CreateGUI(class="type">void); class=class="str">"cmt">//--- class="kw">private: class=class="str">"cmt">//--- 表单 class="type">bool CreateWindow(class="kw">const class="type">class="kw">string text); class=class="str">"cmt">//--- 状态条 class="type">bool CreateStatusBar(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap); class=class="str">"cmt">//--- 页面 class="type">bool CreateTabs1(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap); class="type">bool CreateTabs2(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap); class=class="str">"cmt">//--- 输入栏位 class="type">bool CreateCurvesTotal(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="kw">const class="type">class="kw">string text); class="type">bool CreateSleep(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="kw">const class="type">class="kw">string text); class=class="str">"cmt">//--- 按钮
「面板控件的声明与挂接方式」
在 MT5 自定义面板里,所有图形控件都走同一套创建接口:传入相对坐标 x_gap、y_gap 再加可选文本,返回 bool 表示创建成功与否。上面这组声明覆盖了回复框、组合框、5 个图形区、更新按钮、主表与品种表、进度条,共 12 个方法。 实际写 EA 或指标时,把这些方法原样塞进一个类,再 #include "CreateGUI.mqh" 把实现拉进来,就能在 OnCreate 里逐个调用。若某个 CreateGraph3 返回 false,大概率是坐标重叠或父窗口句柄无效,开 MT5 跑一下就能定位。 外汇与贵金属图表加载这类面板属于高风险操作环境,控件创建失败不会爆仓但会让你漏看信号,建议先在 DEMO 账户验证布局再上实盘。
class="type">bool CreateReplyFrames(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="kw">const class="type">class="kw">string text); class=class="str">"cmt">//--- 组合框 class="type">bool CreateCriterion(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="kw">const class="type">class="kw">string text); class=class="str">"cmt">//--- 图形 class="type">bool CreateGraph1(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap); class="type">bool CreateGraph2(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap); class="type">bool CreateGraph3(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap); class="type">bool CreateGraph4(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap); class="type">bool CreateGraph5(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap); class=class="str">"cmt">//--- 按钮 class="type">bool CreateUpdateGraph(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="kw">const class="type">class="kw">string text); class=class="str">"cmt">//--- 表格 class="type">bool CreateMainTable(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap); class="type">bool CreateSymbolsTable(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap); class=class="str">"cmt">//--- 进度条 class="type">bool CreateProgressBar(class="kw">const class="type">int x_gap,class="kw">const class="type">int y_gap,class="kw">const class="type">class="kw">string text); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用于创建控件的方法 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "CreateGUI.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 把百条最优解捞进同一张图
在 MT5 优化器里跑完参数遍历后,最实用的不是看单条通过,而是把前 N 条最佳结果叠到一张余额曲线图里对比。默认逻辑会抓取 100 个最优解,这个数量由 m_best_results_total 写死为 100,想看更少或更多直接改构造里的初值即可。 CFrameGenerator::UpdateBestResultsGraph() 用双重循环干这事:外层按最佳结果数量封顶,同时卡住表格行数边界;每次迭代前把帧指针用 FrameFirst() 拽回开头。内层用 FrameNext() 遍历所有帧,靠 m_pass 和预排序数组 m_columns[0].m_rows[i] 比对,命中才取出 EA 参数与余额序列。 余额数据藏在帧数组 m_data 里统计指标之后,ArrayCopy 从 STAT_TOTAL 偏移拷出。终值大于初始存款就绘绿线,否则红线——一眼能分出哪些通过是实打实盈利的。循环跑满后计数器重置、X 轴边界按最大序列长定,方法返回 false 表示捞取结束。 全帧遍历很耗时,所以 CProgram::GetBestOptimizationResults() 套了 while 循环调上面的方法,并挂 CProgressBar 让用户看见进度。循环前进度条置可见,结束后隐藏。优化收尾时记得在结束逻辑里调用它,否则用户不知道程序还在挑结果。 实际验证路径:优化完转 Results 页切 Favorites,默认按 Profit 载百条;随时在 Criterion 下拉换标准会重跑选取。外汇与贵金属优化属高风险环境,历史最优不等于未来能跑顺,上真实账户前务必用策略测试器复核。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用于操作优化结果的类 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CFrameGenerator { class="kw">private: class=class="str">"cmt">//--- 最佳结果的数量 class="type">int m_best_results_total; class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 更新结果图 class="type">bool UpdateBestResultsGraph(class="type">void); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 构造函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ CFrameGenerator::CFrameGenerator(class="type">void) : m_best_results_total(class="num">100) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 更新最佳结果图 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CFrameGenerator::UpdateBestResultsGraph(class="type">void) { for(class="type">int i=(class="type">int)m_frames_counter; i<m_best_results_total && i<m_rows_total; i++) { class=class="str">"cmt">//--- 把帧指针移动到开始位置 ::FrameFirst(); class=class="str">"cmt">//--- 数据的展开 while(::FrameNext(m_pass,m_name,m_id,m_value,m_data)) { class=class="str">"cmt">//--- 通过索引不匹配,转到下一个 if(m_pass!=(class="type">class="kw">ulong)m_columns[class="num">0].m_rows[i]) class="kw">continue; class=class="str">"cmt">//--- 取得参数和它们的数值 GetParametersTotal(); class=class="str">"cmt">//--- 取得当前余额结果 class="type">class="kw">double serie[]; ::ArrayCopy(serie,m_data,class="num">0,STAT_TOTAL,(class="type">int)m_value); class=class="str">"cmt">//--- 发送数组,以在余额图表上显示 CCurve *curve=m_graph_best.CurveGetByIndex(i); curve.Name((class="type">class="kw">string)m_pass);