多元品种余额图基础:在 MT5 里用原生图形界面看清组合净值(基础篇)
(1/3)· 还在用第三方工具拼多品种权益曲线?这篇先把 MT5 原生可视化架构讲透
很多交易者测试多品种 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 图表上就能看到面板雏形。外汇与贵金属波动剧烈,这类面板仅作复盘辅助,不预示任何方向。
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 相关的变量,避免高分辨率屏上控件挤成一团。外汇与贵金属品种波动剧烈,面板失效可能让你漏掉关键信号,改完务必在策略测试器里切几个品种验证。
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) 里捞名字。
<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 =<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> <span class="keyword">class="type">class="kw">double</span> InpLots =<span class="number">class="num">0.1</span>; <span class="comment">class=class="str">"cmt">// 手数</span> <span class="keyword">class="kw">input</span> <span class="keyword">class="type">int</span> InpTakeProfit =<span class="number">class="num">167</span>; <span class="comment">class=class="str">"cmt">// 止盈 (点数)</span> <span class="keyword">class="kw">input</span> <span class="keyword">class="type">int</span> InpTrailingStop =<span class="number">class="num">97</span>; <span class="comment">class=class="str">"cmt">// 尾随停止级别 (点数)</span> <span class="keyword">class="kw">input</span> <span class="keyword">class="type">int</span> InpMACDOpenLevel =<span class="number">class="num">16</span>; <span class="comment">class=class="str">"cmt">// MACD 开仓级别 (点数)</span> <span class="keyword">class="kw">input</span> <span class="keyword">class="type">int</span> InpMACDCloseLevel =<span class="number">class="num">19</span>; <span class="comment">class=class="str">"cmt">// MACD 平仓级别 (点数)</span> <span class="keyword">class="kw">input</span> <span class="keyword">class="type">int</span> InpMATrendPeriod =<span class="number">class="num">14</span>; <span class="comment">class=class="str">"cmt">// MA 趋势周期</span> <span class="keyword">class</span> CProgram : <span class="keyword">class="kw">public</span> CWndEvents { <span class="keyword">class="kw">private</span>: <span class="comment">class=class="str">"cmt">//--- 检查所传递数组中的交易品种并返回可用数组之一</span> <span class="keyword">class="type">void</span> CheckTradeSymbols(<span class="keyword">class="type">class="kw">string</span> &source_array[],<span class="keyword">class="type">class="kw">string</span> &checked_array[]); }; <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="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> &source_array[]</span>,<span class="keyword">class="type">class="kw">string</span> &checked_array[]) { <span class="keyword">class="type">int</span> symbols_total =::<span class="functions">SymbolsTotal</span>(<span class="macro">class="kw">false</span>); <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> <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i=<span class="number">class="num">0</span>; i<size_source_array; i++) { <span class="keyword">for</span>(<span class="keyword">class="type">int</span> s=<span class="number">class="num">0</span>; s<symbols_total; s++) { <span class="comment">class=class="str">"cmt">//--- 获取公共列表中当前品种的名称</span>