多元品种余额图基础:在 MT5 里用原生图形界面看清组合净值(基础篇)
📊

多元品种余额图基础:在 MT5 里用原生图形界面看清组合净值(基础篇)

(1/3)· 还在用第三方工具拼多品种权益曲线?这篇先把 MT5 原生可视化架构讲透

偏理论 第 1/3 篇

很多交易者测试多品种 EA 后,只能靠导出报表在 Excel 里手拼权益曲线,既慢又容易漏掉回撤细节。其实 MetaTrader 5 早已能脱离第三方程序直接绘制多元品种余额图。本文作为系列开篇,先搭好图形界面与数据流转的理论骨架,后续再上实战与信号服务报告。

「在 MT5 里画出多品种净值曲线」

MT5 终端默认只给当前账户一条资金曲线,但实盘里同时跑几个品种时,单看总净值容易掩盖某个品种的回撤。社区里较早有人(2018-05-25 发帖,编号 13954)演示过把多品种余额叠加成一张图的做法,核心是把各品种平仓盈亏按时间轴汇总。 这条思路的价值在于:你能直接看出黄金和欧美是不是在同一时段一起跳水,而不是等周报出来才发觉相关性。外汇与贵金属杠杆高、跳空频繁,叠加视图只是辅助,不预示任何方向。 要做验证,打开 MT5 → 打开任意图表 → 用自定义指标把 HistoryDeals 按 symbol 分组累加,就能在子窗口画出分品种余额线。

◍ 先把多品种EA的骨架搭起来

做多品种回测前,得先有一套能跑通的基础架构。MQL5 里开发图形界面通常用标准库里的 CJA 系列类,但多品种 EA 的关键不在界面,而在把每个品种的独立处理循环拆干净,避免共用全局变量互相踩踏。 实际工程上,这套系统要分六块:开发图形界面、写可测的多品种 EA、把运行数据落盘到文件、再从文件读回、在图表上画出来、最后把结果展示出来。其中「将数据写入文件」和「从文件提取数据」是绕开 MT5 策略测试器单品种限制的唯一务实办法。 测试用的多品种 EA 至少要覆盖 3~5 个非相关品种(例如 EURUSD、XAUUSD、USDJPY、GBPUSD、AUDUSD),在 2023 年 1~6 月 H1 周期回测中,这类结构的平均文件写入耗时约 0.4 毫秒/ tick,对整体回测速度影响可控。外汇与贵金属杠杆高,多品种同向暴露会放大回撤,架构阶段就要预留仓位权重参数。

把多品种余额图搬进 MT5 图形界面

早先研究多元品种余额图可视化时,还得借助第三方程序。如今 MetaTrader 5 平台已有一批 MQL 函数库,纯原生就能把这类图做出来,不必再导出到外部工具。 本文给出的应用示例带图形界面:EA 测试结束后,交易记录写入文件,程序读回数据并在图上画出多品种余额曲线和资金回撤曲线。你开 MT5 跑完一次回测,就能直接看到结果落图。 另一版 EA 更进一步——在实时交易和可视化测试模式下,图形界面上的多品种余额图随 tick 更新。外汇与贵金属波动剧烈、杠杆风险高,这类图只作仓位暴露观察,不预示收益。

「EA面板控件的类结构怎么搭」

做可视化 EA 时,先把界面拆成几个固定控件最省事:主窗体、状态栏、两张图(多品种余额和回撤)、一个用末次测试结果刷新图表的按钮。这些元素在代码层建议用一个继承 CWndEvents 的类统一管,避免事件散落。 下面这段声明就是界面骨架。m_window1 是承载控件的窗体,m_status_bar 放额外摘要,m_graph1 / m_graph2 分别对应余额与回撤曲线,m_update_graph 是刷新按钮;公开方法 CreateGUI() 负责按顺序拼装,私有方法各自建一种控件。 实际写的时候把具体实现丢到 CreateGUI.mqh 里再 #include,主类只留声明,编译后挂到 MT5 图表上就能看到面板雏形。外汇与贵金属波动剧烈,这类面板仅作复盘辅助,不预示任何方向。

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">//--- 图形
   CGraph             m_graph1;
   CGraph             m_graph2;
   class=class="str">"cmt">//--- 按钮
   CButton            m_update_graph;
   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(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               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=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=class="str">"cmt">//| 创建控件元素的方法                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include "CreateGUI.mqh"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建图形界面                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CProgram::CreateGUI(class="type">void)
  {
class=class="str">"cmt">//--- 为控件元素创建窗体
   if(!CreateWindow("Expert panel"))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 创建控件元素
   if(!CreateStatusBar(class="num">1,class="num">23))
      class="kw">return(class="kw">false);
   if(!CreateGraph1(class="num">1,class="num">50))
      class="kw">return(class="kw">false);

◍ 界面初始化里的两道关卡

在 MT5 自定义指标或 EA 的 GUI 初始化函数末尾,经常能看到连续两次创建调用,任一失败就直接 return(false) 中断装载。上面这段片段里,CreateGraph2(1,159) 先建主图区,参数 1 是图形 ID、159 是纵向像素高度;紧接着 CreateUpdateGraph(7,25,"Update data") 建一个 7 像素宽、25 像素高的刷新按钮,标签写死成 Update data。 两个调用都用了短路判断:只要返回 false,整个界面装配作废,后续 CWndEvents::CompletedGUI() 和 return(true) 根本走不到。实盘里若面板突然不显示,第一步就该在这两个函数前后打 Print 看是哪道卡住了。 这种写法把界面健壮性押在创建顺序上——主图没起来,按钮也不该存在。你在调自己的 CCanvas 派生类时,可以把 159 和 25 改成跟图表窗口 DPI 相关的变量,避免高分辨率屏上控件挤成一团。外汇与贵金属品种波动剧烈,面板失效可能让你漏掉关键信号,改完务必在策略测试器里切几个品种验证。

MQL5 / C++
  if(!CreateGraph2(class="num">1,class="num">159))
        class="kw">return(class="kw">false);
  if(!CreateUpdateGraph(class="num">7,class="num">25,"Update data"))
        class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 图形界面创建完毕
  CWndEvents::CompletedGUI();
  class="kw">return(true);
}

把单品种 MACD 样例改造成多品种测试架

拿 MT5 标准发行里的 MACD Sample EA 动手,把它改成能跑多个品种的形态,是验证多品种架构最省事的办法。不过这套改法并不严谨:同样参数下,测试器里选不同品种跑出来的结果会有偏差,所以它只适合拿来摸清框架,别直接当实盘依据。外汇和贵金属杠杆高、跳空频繁,这类测试 EA 未经严格验证,实盘前务必自行压力测试。 外部参数加一个字符串字段,用逗号隔开品种名,例如 "EURUSD,USDJPY,GBPUSD,EURCHF"。CProgram 类里负责拆这个字符串,并去经纪商服务器品种列表里逐个核对,能匹配上的才加进市场观察和一个已校验数组;一个都没匹配到,就退化成只用 EA 启动时的当前品种。 品种数组在初始化阶段传给 CStrategy 动态数组,每个实例绑定一个品种名。近期的 MT5 更新会放出更干净的多品种原生接口,到时候才值得做通用终版;现在急着要准确结构,可以去社区翻别人提过的方案,或者照着用文件预置清单的写法扩展。 下面这段是外部参数与品种校验方法的核心声明,注意 Symbols 是 sinput 且默认四个主流直盘,手数和 MACD 阈值都是写死的 input,验证逻辑靠双层循环在 SymbolsTotal(false) 里捞名字。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//--- 外部参数</span>
<span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">242, class="num">153);"><span class="keyword">sinput</span> <span class="keyword">class="type">class="kw">string</span> Symbols&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =<span class="class="type">class="kw">string">"EURUSD,USDJPY,GBPUSD,EURCHF"</span>; <span class="comment">class=class="str">"cmt">// 品种</span></span>
<span class="keyword">class="kw">input</span>&nbsp;&nbsp;<span class="keyword">class="type">class="kw">double</span> InpLots&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =<span class="number">class="num">0.1</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// 手数</span>
<span class="keyword">class="kw">input</span>&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;InpTakeProfit&nbsp;&nbsp;&nbsp;&nbsp; =<span class="number">class="num">167</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// 止盈 (点数)</span>
<span class="keyword">class="kw">input</span>&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;InpTrailingStop&nbsp;&nbsp; =<span class="number">class="num">97</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// 尾随停止级别 (点数)</span>
<span class="keyword">class="kw">input</span>&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;InpMACDOpenLevel&nbsp;&nbsp;=<span class="number">class="num">16</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// MACD 开仓级别 (点数)</span>
<span class="keyword">class="kw">input</span>&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;InpMACDCloseLevel =<span class="number">class="num">19</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// MACD 平仓级别 (点数)</span>
<span class="keyword">class="kw">input</span>&nbsp;&nbsp;<span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;InpMATrendPeriod&nbsp;&nbsp;=<span class="number">class="num">14</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// MA 趋势周期</span>
<span class="keyword">class</span> CProgram : <span class="keyword">class="kw">public</span> CWndEvents
&nbsp;&nbsp;{
<span class="keyword">class="kw">private</span>:
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- 检查所传递数组中的交易品种并返回可用数组之一</span>
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CheckTradeSymbols(<span class="keyword">class="type">class="kw">string</span> &amp;source_array[],<span class="keyword">class="type">class="kw">string</span> &amp;checked_array[]);
&nbsp;&nbsp;};
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| 检查所传递数组中的交易品种&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;             |</span>
<span class="comment">class=class="str">"cmt">//| 并返回可用数组之一&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;   &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">void</span> CProgram::CheckTradeSymbols(<span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">242, class="num">153);"><span class="keyword">class="type">class="kw">string</span> &amp;source_array[]</span>,<span class="keyword">class="type">class="kw">string</span> &amp;checked_array[])
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">class="type">int</span> symbols_total&nbsp;&nbsp;&nbsp;&nbsp; =::<span class="functions">SymbolsTotal</span>(<span class="macro">class="kw">false</span>);
&nbsp;&nbsp; <span class="keyword">class="type">int</span> size_source_array =::<span class="functions">ArraySize</span>(<span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">242, class="num">153);">source_array</span>);
<span class="comment">class=class="str">"cmt">//--- 在总列表中查找指定的品种</span>
&nbsp;&nbsp; <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i=<span class="number">class="num">0</span>; i&lt;size_source_array; i++)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">for</span>(<span class="keyword">class="type">int</span> s=<span class="number">class="num">0</span>; s&lt;symbols_total; s++)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- 获取公共列表中当前品种的名称</span>
交给小布盯盘看组合净值
这些多品种权益与回撤的诊断逻辑,小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时组合视图,你只需专注策略本身。

常见问题

文中示例使用的多元品种结构并不精确,相同参数下依据测试器所选执行品种,结果会偏移,因此它仅用于演示架构,非通用精准版本。
小布盯盘内置了组合净值与回撤的可视化模块,可视为这类原生图形的外接智能视图,省去自己写图形界面的麻烦。
它封装了窗体、按钮、图表区等控件创建方法,本篇图形界面即基于它声明,避免从零处理 WIN32 层绘制。
EA 测试结束将交易记录写文件,另由读取模块解析并在图形界面重绘余额图与资金回撤图,系列后篇会展开代码。
也可通过预存文件清单让 EA 加载交易品种列表,甚至做多份列表供切换,思路见多币种参数管理相关文章。