可视化使用选定标准优化的结果(基础篇)
📘

可视化使用选定标准优化的结果(基础篇)

第 1/3 篇

「把优化结果摆到桌面上看」

在 MT5 的策略测试器跑完参数优化后,结果通常是一张密密麻麻的表格,肉眼筛可选集效率很低。Anatoli Kazharski 在 2018 年 9 月 3 日发布的示例里,用自定义图形界面把「选定标准的优化结果」直接可视化,让交易者在面板里点选、高亮、对比。 这套思路的核心不是新指标,而是把测试器导出的帧数据接进 GUI 表格,再用代码控制行的高亮与交互。原文给出的章节脉络是:开发图形界面 → 选择优化结果 → 编程突出显示表格行 → 帧数据辅助方法 → 事件处理。 对读者来说,可立刻验证的动作是:打开 MT5 策略测试器,跑一组 EA 的参数优化,观察结果标签页默认只按盈利排序,而自定义面板能按你定的标准(如回撤阈值)反向标红。外汇与贵金属品种杠杆高、滑点敏感,优化结果过拟合概率偏大,面板筛选只是辅助,不等于实盘胜率保证。

◍ 把优化结果摊在独立图表上

接着上一篇的多数据类型处理思路,这一节要解决的是:优化跑完以后,怎么更快把“按别的标准挑出来的最佳参数”看见。之前已经能把统计参数、品种余额数组、存款回撤塞进帧数组再展开,现在要往前再走一步——不止在表格里高亮对应行,而是让被选中的那几条余额曲线直接画在独立图表里。 表格里高亮行、累计图里同步出选中余额曲线,这个联动能让你在对比优化结果时少切几次窗口,评估回撤和权益走势更直观。优化后的结果若只在原表里反白,视线来回跳的成本其实不低。 另外按论坛成员提的需求,这一版给开发库里的 CTable 类加了键盘控制高亮行的能力。不用鼠标点,方向键或指定按键就能挪动选中行,配合前面的独立图表出图,批量扫参数的效率会上来一些。外汇与贵金属优化本身高风险,回测好不等于实盘稳,键盘筛行只是减少操作摩擦。

把优化结果界面重构成多页联动

上一版程序的图形界面分了三页:Frames、Results、Balance。现在把 Results 和 Balance 合并,选中表格某一行后图形直接联动刷新,不用再手动切页看回撤。 Frames 页保留重新滚动结果的控件:输入显示数量的栏位、滚动延迟毫秒数、启动按钮,以及余额曲线和全部结果的图形。Results 页下再分 Balances 和 Favorites 两组——前者画多品种余额与回撤,后者用图形展示表格里的最佳结果,并加了 CComboBox 下拉框来切换“最佳”的判定标准。 整套界面从窗口、状态栏到两组标签页和图形表格,都封装在 CProgram 类里。下面这段是类声明里 GUI 相关成员的骨架,可以直接抄进独立文件跟 MQL 程序类一起编译。 别把下拉框标准写死 CComboBox 的判定标准建议从配置文件读,不然每次改“最佳”定义都要重编译,实盘调外汇品种参数时很拖节奏。贵金属和多货币对回测高风险,下拉项最好留自定义入口。

MQL5 / C++
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 账户验证布局再上实盘。

MQL5 / C++
  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 下拉换标准会重跑选取。外汇与贵金属优化属高风险环境,历史最优不等于未来能跑顺,上真实账户前务必用策略测试器复核。

MQL5 / C++
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);

常见问题

用独立窗口把每组合的参数与净值曲线重绘在同一张图,切换输入即可看形态差异,不必反复开报告。
把结果面板拆成多个子页并用同一组控件联动,改一个筛选条件所有页同步刷新,省去手动翻页。
可以,小布能按你设的标准把最优解批量抓进同一视图并标出分布,你只管看偏离和聚集区。
先在一个结构体里集中声明控件句柄,再用创建函数绑定到独立窗口,事件里按 id 分发处理即可。
因止损与过滤参数不同导致回撤节奏差异,摊图后看净值斜率和回撤带比单看最终利润更靠谱。