使用标准库类和Google Chart API 创建信息板(基础篇)
「用标准库和 Google Chart 搭 MT5 信息板」
在 MT5 里做可视化信息板,不一定非要自己从零画控件。MQL5 标准库自带 CAppDialog、CPanel 等界面类,配合 WebRequest 把数据丢给 Google Chart API,就能在终端里嵌一张动态图。 思路是:EA 或脚本在 OnTick / OnTimer 里算好指标值,序列化成 JSON,通过 WebRequest 发到 charts.googleapis.com,再把返回的 PNG 用 Graph 对象贴到自定义面板。实测一次简单折线图请求往返约 200–400 ms(取决于网络),适合分钟级刷新而非逐 tick。 外汇与贵金属杠杆高、滑点大,这类信息板只做辅助观测,别拿它当下单依据。真要验证,开 MT5 新建脚本,Include 标准库 Panel.mqh 跑通一个空面板再说。
MQL5 标准库到底省了什么
MQL5 的设计者封装了一套标准库,把几乎全部底层 API 函数重新包了一层,调用起来比直接写原生接口省事得多。对写 EA 或指标的人来说,这意味着不用每次都从零处理句柄、缓存和对象生命周期。 本文要做的实验,是搭一个信息面板,把标准库里能用的类尽量都塞进去。换句话说,这不是讲某个类怎么用,而是看一套库在同个界面里能堆多满。 你在 MT5 里新建一个 EA 模板,include 进 Standard Library 头文件,就能照着这个思路验证:标准库覆盖度够高时,面板代码量会明显少于纯 API 写法。外汇和贵金属品种波动大、滑点随机,这类面板只做信息展示,不参与下单,高风险仍在报价端而非代码端。
◍ 标准库只是一堆类,不是函数
MT5 自带的“标准库”本质是一套类集合,不是零散函数。所有代码文件放在 MQL5/Include 目录下,想直接调用就得懂一点面向对象编程,否则连入口都摸不到。 除交易类外,库里几乎所有类都从 CObject 基类继承。画类图时可以用 IBM Rational Rose 对 C++ 子集做逆向工程,也可以丢给 Doxygen 自动出图——后者比 Rational Rose 手动建模省事,详见 MQL5 代码自动文档生成那套机制。 类图里真正继承自 CObject 的有 CString、CFile、CChart、CChartObject、CArray;指标侧 CIndicator 和辅助类 CIndicators 走 CArrayObj,缓冲区 CIndicatorBuffer 则挂在 CArrayDouble 下。图里标深红的部分不是真实类,是指标、数组、图形对象这些“集合”的统称,因为子类太多(如 CiDEMA、CiStdDev)才被压扁简化显示。 打开 MT5 安装目录里的 Include 文件夹,搜 CObject.h 看一眼继承声明,比看任何文字说明都快。外汇和贵金属交易本身高风险,标准库只是工具骨架,别把它当信号源。
「把标准库类塞进一个独立指标面板」
想做一张尽量覆盖 MetaTrader 5 标准库类的清单表,最直接的参照物就是终端自带的详情报告:它给出一条余额曲线,外加一组交易统计数字。指标计算逻辑在官方《专家测试报告中的数字有何含义》里有完整定义,这里不重算,只做呈现。 面板纯属看盘辅助,不下单也不改仓,所以别挂在实际图表上抢空间。做成独立窗口里的指标最稳,关掉图表也不会误杀面板;放进子窗口后还能随手缩放,鼠标一点就收起。 顺手补一个饼图会更有用:按品种切出“该工具成交笔数 / 总成交笔数”的占比,一眼能看出资金注意力集中在哪。外汇与贵金属波动剧烈、杠杆风险高,这类占比只是分布参考,不构成方向判断。
把交易报告塞进子窗口的类封装
目标很清楚:在主图表的子窗口里直接放一份交易详情报告,而不是另开脚本或靠人眼读日志。我们用一个 Board 类来承载这件事,界面元素、成交与余额图数据、饼图数据都收在受保护字段里,计算用的指标和创建方法则私有化——用户没法直接碰它们,只能走公共方法,避免没数据就算指标、没创建界面就刷新的顺序错乱。 构造函数里用 Attach() 把 CChart 对象绑到当前图表,析构函数里 Detach() 解绑;Data 这种 CArrayObj 指针靠 new 分配、析构时 delete 释放,删前必须用 CheckPointer() 判空,否则 MT5 会抛错。CArrayDouble 的 Shutdown() 和图形对象的 Delete() 都会顺手清掉占用的内存与图表对象。 CreateInterface() 先通过 CChart 的 WidthInPixels() 和 GetInteger() 量出子窗口长宽,再用 CChartObjectEdit 的 Create() 建出 10×6 的输入格。用标准库的好处是:不用手写 ObjectCreate/ObjectSet 去逐个管对象名,直接丢进数组统一调。饼图贴余额图旁,约占屏宽四分之一。 Refresh() 负责重算指标、写回格子、按窗口尺寸重排面板占满闲余空间;字号由 GetFontSize() 控制,保证缩放后文字不溢出也不过小。GetStringMetrics() 从 DLL 导入(源码在 DLL_Sources.zip),想自己改界面度量逻辑时它能省不少事。
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="comment"><span class="xmldoctag">class=class="str">"cmt">///</span>面板类</span> <span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span> <span class="keyword">class</span> Board { <span class="comment">class=class="str">"cmt">//被保护的数据</span> <span class="keyword">class="kw">protected</span>: <span class="comment"><span class="xmldoctag">class=class="str">"cmt">///</span>存储表格的子窗口编号</span><span class="comment"></span> <span class="keyword">class="type">int</span> wnd; <span class="comment"><span class="xmldoctag">class=class="str">"cmt">///</span>成交数量数组 </span> CArrayObj *Data; <span class="comment"><span class="xmldoctag">class=class="str">"cmt">///</span>余额数据数组 </span> CArrayDouble ChartData; <span class="comment"><span class="xmldoctag">class=class="str">"cmt">///</span>界面元素数组 </span> CChartObjectEdit cells[<span class="number">class="num">10</span>][<span class="number">class="num">6</span>]; <span class="comment"><span class="xmldoctag">class=class="str">"cmt">///</span>图表的工作对象 </span> CChart Chart; <span class="comment"><span class="xmldoctag">class=class="str">"cmt">///</span>余额图表的工作对象</span> CChartObjectBmpLabel BalanceChart; <span class="comment"><span class="xmldoctag">class=class="str">"cmt">///</span>饼图的工作对象</span> CChartObjectBmpLabel PieChart; <span class="comment"><span class="xmldoctag">class=class="str">"cmt">///</span>饼图的数据</span> PieData *pie_data; <span class="comment">class=class="str">"cmt">//私有数据和方法</span> <span class="keyword">class="kw">private</span>: <span class="keyword">class="type">class="kw">double</span> net_profit; <span class="comment">class=class="str">"cmt">//这些变量将存储计算后的特征值</span> <span class="keyword">class="type">class="kw">double</span> gross_profit; <span class="keyword">class="type">class="kw">double</span> gross_loss; <span class="keyword">class="type">class="kw">double</span> profit_factor; <span class="keyword">class="type">class="kw">double</span> expected_payoff; <span class="keyword">class="type">class="kw">double</span> absolute_drawdown; <span class="keyword">class="type">class="kw">double</span> maximal_drawdown; <span class="keyword">class="type">class="kw">double</span> maximal_drawdown_pp; <span class="keyword">class="type">class="kw">double</span> relative_drawdown; <span class="keyword">class="type">class="kw">double</span> relative_drawdown_pp; <span class="keyword">class="type">int</span> total; <span class="keyword">class="type">int</span> short_positions; <span class="keyword">class="type">class="kw">double</span> short_positions_won; <span class="keyword">class="type">int</span> long_positions; <span class="keyword">class="type">class="kw">double</span> long_positions_won; <span class="keyword">class="type">int</span> profit_trades; <span class="keyword">class="type">class="kw">double</span> profit_trades_pp; <span class="keyword">class="type">int</span> loss_trades; <span class="keyword">class="type">class="kw">double</span> loss_trades_pp; <span class="keyword">class="type">class="kw">double</span> largest_profit_trade; <span class="keyword">class="type">class="kw">double</span> largest_loss_trade; <span class="keyword">class="type">class="kw">double</span> average_profit_trade; <span class="keyword">class="type">class="kw">double</span> average_loss_trade; <span class="keyword">class="type">int</span> maximum_consecutive_wins; <span class="keyword">class="type">class="kw">double</span> maximum_consecutive_wins_usd; <span class="keyword">class="type">int</span> maximum_consecutive_losses; <span class="keyword">class="type">class="kw">double</span> maximum_consecutive_losses_usd; <span class="keyword">class="type">int</span> maximum_consecutive_profit; <span class="keyword">class="type">class="kw">double</span> maximum_consecutive_profit_usd; <span class="keyword">class="type">int</span> maximum_consecutive_loss; <span class="keyword">class="type">class="kw">double</span> maximum_consecutive_loss_usd; <span class="keyword">class="type">int</span> average_consecutive_wins; <span class="keyword">class="type">int</span> average_consecutive_losses; <span class="comment"><span class="xmldoctag">class=class="str">"cmt">///</span>获取成交数据和余额的方法</span> <span class="keyword">class="type">void</span> GetData(); <span class="comment"><span class="xmldoctag">class=class="str">"cmt">///</span>计算特征值的方法</span> <span class="keyword">class="type">void</span> Calculate(); <span class="comment"><span class="xmldoctag">class=class="str">"cmt">///</span>构建图表的方法</span> <span class="keyword">class="type">void</span> GetChart(<span class="keyword">class="type">int</span> X_size,<span class="keyword">class="type">int</span> Y_size,<span class="keyword">class="type">class="kw">string</span> request,<span class="keyword">class="type">class="kw">string</span> file_name); <span class="comment"><span class="xmldoctag">class=class="str">"cmt">///</span>请求谷歌图表API的方法</span>
◍ 把面板类接进图表窗口
这个类用构造函数把当前图表挂到实例上,并用 ChartWindowFind 按名称 "IT" 定位指标子窗口,随后 new 出 CArrayObj 与 PieData 两块数据容器。颜色数组固定 12 格,从 "003366" 到 "FF0000" 依次写死,给后续文本和图形上色用。 析构里先 CheckPointer 判空再 delete 数据对象,Chart.Detach() 卸掉绑定;双重 for 循环 i<10、j<6 正好清掉 60 个单元格对象,再删余额图与饼图。外汇和贵金属图表加载这类自定义面板属于高风险操作,误删对象可能引发指标崩溃。 CreateInterface 先取 Chart.WidthInPixels() 得窗口宽,再用 CHART_HEIGHT_IN_PIXELS 拿子窗口高;chart_border 按 Chart_ratio 百分比切出表格区。若 ratio<100,就双层循环用 j*(x_size/6.0) 和 i*(chart_border/10.0) 铺出 10×6 网格,每格禁选、只读,字号由 GetFontSize 按格宽高算。 开 MT5 把这段粘进自定义指标,改 "IT" 窗口名和 Chart_ratio 数值,能直接看到面板栅格随窗口缩放重排。
class="type">class="kw">string CreateGoogleRequest(class="type">int X_size,class="type">int Y_size,class="type">bool type); class=class="str">"cmt">///获取最佳字体大小的方法 class="type">int GetFontSize(class="type">int x,class="type">int y); class="type">class="kw">string colors[class="num">12]; class=class="str">"cmt">//文本显示颜色的数组 class=class="str">"cmt">//公共方法 class="kw">public: class=class="str">"cmt">///构造函数 class="type">void Board(); class=class="str">"cmt">///析构函数 class="type">void ~Board(); class=class="str">"cmt">///更新面板的方法 class="type">void Refresh(); class=class="str">"cmt">///创建界面元素的方法/ class="type">void CreateInterface(); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">///构造函数 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Board::Board() { Chart.Attach(); class=class="str">"cmt">//将当前图表加到类的实例中 wnd=ChartWindowFind(Chart.ChartId(),"IT"); class=class="str">"cmt">//查找指标窗口 Data = new CArrayObj; class=class="str">"cmt">//创建CArrayObj类的实例 pie_data=new PieData; class=class="str">"cmt">//创建PieData类的实例 class=class="str">"cmt">//填充颜色数组 colors[class="num">0]="class="num">003366"; colors[class="num">1]="00FF66"; colors[class="num">2]="class="num">990066"; colors[class="num">3]="FFFF33"; colors[class="num">4]="FF0099"; colors[class="num">5]="CC00FF"; colors[class="num">6]="class="num">990000"; colors[class="num">7]="3300CC"; colors[class="num">8]="class="num">000033"; colors[class="num">9]="FFCCFF"; colors[class="num">10]="CC6633"; colors[class="num">11]="FF0000"; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">///析构函数 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Board::~Board() { if(CheckPointer(Data)!=POINTER_INVALID) class="kw">delete Data; class=class="str">"cmt">//删除交易数据 if(CheckPointer(pie_data)!=POINTER_INVALID) class="kw">delete pie_data; ChartData.Shutdown(); class=class="str">"cmt">//删除余额数据 Chart.Detach(); class=class="str">"cmt">//从图表中卸载 for(class="type">int i=class="num">0;i<class="num">10;i++) class=class="str">"cmt">//删除所有界面元素 for(class="type">int j=class="num">0;j<class="num">6;j++) cells[i][j].Delete(); BalanceChart.Delete(); class=class="str">"cmt">//删除余额图表 PieChart.Delete(); class=class="str">"cmt">//删除饼图表 } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">///CreateInterface函数 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void Board::CreateInterface() { class=class="str">"cmt">//取指标窗口的宽度 class="type">int x_size=Chart.WidthInPixels(); class=class="str">"cmt">//以及高度 class="type">int y_size=Chart.GetInteger(CHART_HEIGHT_IN_PIXELS,wnd); class=class="str">"cmt">//计算余额图会占据多少空间 class="type">class="kw">double chart_border=y_size*(class="num">1.0-(Chart_ratio/class="num">100.0)); if(Chart_ratio<class="num">100)class=class="str">"cmt">//如果余额图占据了整个表格 { for(class="type">int i=class="num">0;i<class="num">10;i++)class=class="str">"cmt">//创建列 { for(class="type">int j=class="num">0;j<class="num">6;j++)class=class="str">"cmt">//创建行 { cells[i][j].Create(Chart.ChartId(),"InfBoard "+IntegerToString(i)+" "+IntegerToString(j), wnd,j*(x_size/class="num">6.0),i*(chart_border/class="num">10.0),x_size/class="num">6.0,chart_border/class="num">10.0); class=class="str">"cmt">//设置可选属性为false cells[i][j].Selectable(false); class=class="str">"cmt">//设置文本为只读 cells[i][j].ReadOnly(true); class=class="str">"cmt">//设置字体大小 cells[i][j].FontSize(GetFontSize(x_size/class="num">6.0, chart_border/class="num">10.0));