Google Chart API 图表库:用 MQL5 类封装把行情图搬进 MT5(基础篇)
(1/3)· 手写 URL 拼错一个参数就白忙?这篇把 11 类图表封装成可复用类
「用 Google Chart API 在 MT5 里画图」
MQL5 标准库之外,可以借助 Google Chart API 把 MT5 的行情数据推到网页端渲染,绕开终端自带图表的局限。这套做法在 2013-12-10 的社区示例里被提出,当时收获了 2923 次查看、7 条讨论,说明手动搬数据的需求一直存在。 核心思路是用 MQL5 的 WebRequest 把 CSV 或参数拼进 Google Chart 的 URL,浏览器打开即是交互图。外汇与贵金属波动剧烈,这类外链图表只作辅助验证,实盘决策仍以 MT5 主图为准,注意接口失效与跨域高风险。 下面这段演示了如何拼出一张折线图的请求地址,重点看 StringFormat 怎么把点位喂进 chart 参数。
class="type">class="kw">string url="https:class=class="str">"cmt">//chart.googleapis.com/chart?cht=lc&chs=300x200&chd=t:"; class="type">class="kw">string data="class="num">0,class="num">10,class="num">20,class="num">30,class="num">40"; url+=data; WebRequest("GET",url,NULL,NULL,class="num">0,NULL,class="num">0,return_code,result);
◍ Google Chart 能塞进 MT5 的十一种图
Google Chart 接口支持 11 类图表输出:线条、柱形、散点、雷达、烛形、维恩、QR 码、地图、公式、图形、饼图。对 MT5 用户而言,这意味着不用自己写绘图内核,只要拼好一条查询发给 Google 服务器,就能拿回可直接贴图的结果。 此前有文章用标准库类跑通过一个简单的仪表盘实例,但那只是这套服务能力的十分之一左右。要稳定出图,得先摸清请求结构和关键字,而不是靠试错。 本篇思路是封装一层类库:调用方只管传数据,类库负责拼请求、挂到图表上、并按 tick 或定时器动态刷新。库本身不含大量数值计算,配套有一份 Doxygen 生成的文档,里面列了公共方法、枚举值和绘图异常说明,开 MT5 前先读一遍能省掉很多编译报错。
把图表塞进MT5图形对象的坑
拿到服务器回应只是第一步,真正麻烦的是怎么把图表画到MT5界面上。MT5的Picture和Graphic类图形对象只认BMP,而远程图表服务通常只回传PNG或GIF,所以必须做一次格式转换,代码里用Convert_PNG()兜底。 GetChart()这个函数把整套链路走完了:先查对象在不在,再把请求字符串长度卡在2048以内(超了直接报错),接着用WinInet那套InternetOpenW/InternetOpenUrlW去拉数据。 数据回来是1000字节一块循环读进二进制文件,落盘到TerminalPath\MQL5\Files\<name>.png,随后转成\MQL5\Images\<name>.bmp,最后按OBJ_BITMAP或OBJ_BITMAP_LABEL把bmp挂到图形对象上。 别把远程图表当同步控件。InternetAttemptConnect返回非0、或session句柄<=0时函数直接撤,图表不会显示,实盘里若发现对象空白,优先查终端网络权限和Files目录写权限。
class="type">bool CDiagram::GetChart() { if(!ObjExist()) {SetUserError(DIAGRAM_ERR_OBJ_NOT_EXIST); class="kw">return false;} class="type">class="kw">string request=CreateRequest(); class=class="str">"cmt">//打印(请求); if(StringLen(request)>class="num">2048) {SetUserError(DIAGRAM_ERR_TOO_LARGE_REQUEST); class="kw">return false;} class=class="str">"cmt">//尝试建立一个连接 class="type">int rv=InternetAttemptConnect(class="num">0); if(rv!=class="num">0) {SetUserError(DIAGRAM_ERR_INTERNET_CONNECT_FAILED); class="kw">return false;} class=class="str">"cmt">//初始化结构 class="type">int hInternetSession=InternetOpenW("Microsoft Internet Explorer", class="num">0, "", "", class="num">0); if(hInternetSession<=class="num">0) {SetUserError(DIAGRAM_ERR_INTERNET_CONNECT_FAILED); class="kw">return false;} class=class="str">"cmt">//发送请求 class="type">int hURL=InternetOpenUrlW(hInternetSession, request, "", class="num">0, class="num">0, class="num">0); if(hURL<=class="num">0) SetUserError(DIAGRAM_ERR_INTERNET_CONNECT_FAILED); class=class="str">"cmt">//准备转换路径 CString src; src.Assign(TerminalInfoString(TERMINAL_PATH)); src.Append("\MQL5\Files\\"+name+".png"); src.Replace("\\","\\\\"); CString dst; dst.Assign(TerminalInfoString(TERMINAL_PATH)); dst.Append("\MQL5\Images\\"+name+".bmp"); dst.Replace("\\","\\\\"); DeleteFileW(dst.Str()); DeleteFileW(src.Str()); CFileBin chart_file;class=class="str">"cmt">//记录结果的文件 class=class="str">"cmt">//创建它 chart_file.Open(name+".png",FILE_BIN|FILE_WRITE); class=class="str">"cmt">//**************************** class="type">int dwBytesRead[class="num">1];class=class="str">"cmt">// 已记录文档的数目 class="type">char readed[class="num">1000];class=class="str">"cmt">//实际的数据 class=class="str">"cmt">//读取数据, 发送请求后从服务器收到 class="kw">while(InternetReadFile(hURL,readed,class="num">1000,dwBytesRead)) { if(dwBytesRead[class="num">0]<=class="num">0) break;class=class="str">"cmt">//没有数据 - 退出 chart_file.WriteCharArray(readed,class="num">0,dwBytesRead[class="num">0]);class=class="str">"cmt">//把数据记录到文件中 } InternetCloseHandle(hInternetSession);class=class="str">"cmt">//终止连接 chart_file.Close();class=class="str">"cmt">//和文件 class=class="str">"cmt">//转换文件 if(!Convert_PNG(src.Str(), dst.Str())) SetUserError(DIAGRAM_ERR_IMAGE_CONVERSION_FAILED); class=class="str">"cmt">//把文件附加到图形对象 class="kw">switch (obj_type) { case OBJ_BITMAP: { ObjectSetString(chart_ID, name, OBJPROP_BMPFILE, name+".bmp"); class="kw">return true; } case OBJ_BITMAP_LABEL: { ObjectSetString(chart_ID, name, OBJPROP_BMPFILE, class="num">0, name+".bmp"); ObjectSetString(chart_ID, name, OBJPROP_BMPFILE, class="num">1, name+".bmp");
「事件分发收口与图表重绘」
上面这段 switch 的 default 分支直接 return false,意味着任何未显式处理的事件类型都会被静默丢弃,不会触发后续逻辑。写 EA 时若漏接了比如 CHARTEVENT_OBJECT_DRAG,拖拽画线就完全没反应,这种坑只能开 MT5 挂 EA 拖两下才发现。 末尾的 ChartRedraw() 放在事件函数退出前,保证每次处理完都立刻重画。实测在 1 分钟周期手动改对象,不主动重绘的话画面可能延迟数秒才刷新,高频交互场景下必须保留这一句。 别把 default 当摆设。它不只是语法需要,更是事件过滤器的兜底——你确认不需要的事件才让它返回 false,否则调试时连日志都看不到踪影。
class="kw">return true; } class="kw">default: class="kw">return false; } class=class="str">"cmt">//重画图表 ChartRedraw(); }
◍ 用 Google Charts 接口拼一张图
在 MT5 里想绕开自带图表、把账户权益曲线丢到网页端看,最轻量的办法是直连 Google Charts 的旧版接口。请求必须固定以 http://chart.apis.google.com/chart?cht= 起头,后面紧跟图表类型,再接其余参数。 参数之间一律用 & 分隔,每个命令都是「键=值」结构。比如追加 &chtt=Title,就能把整张图的标题设成 Title,服务端按 URL 解析后直接出图。 实测该接口仅支持 HTTP、不支持 MT5 的 HTTPS 证书校验环境,若在 EA 里用 WebRequest 抓取需先把系统策略的「允许 URL」加白。外汇与贵金属交易本身高风险,这类外链可视化仅作辅助监控,不构成任何方向判断。
线条图从零搭到全特征
CLineXYChart 是 MT5 里画线条图的核心类,属性最全也最常用。最基础的图只需 Attach 到名为 "test diagram" 的组件,塞入 10 个点的 Y 数组就能出图,尺寸设 200×200 像素。 复杂化时先加轴线与网格:SetAxis 用 DIAGRAM_AXIS_BOTTOM|DIAGRAM_AXIS_LEFT 开底部和左侧轴,SetGrid 补网格。这里有个坑——GetChart() 必须放在所有配置方法之后调用,它是真正取回渲染结果的那一步,顺序错了图就是空的。 再往上叠标记、标题、图例和填充,靠 AddLine 返回的整型标识符(如 first_line、second_line)定向设置。例如 SetLineMarker(first_line, DIAGRAM_LINE_MARKERS_DIAMOND, BlueViolet, 10) 给第一条线打菱形点,ShowLegend 开图例,SetTitle 写 "My Chart"。尺寸放大到 500×300 或 700×400 时信息密度明显提升。 动态数据来了不用删线重画,优先用 SetLineData() 原地刷新;DeleteLine() 再建新线只是兜底方案。外汇与贵金属行情跳动快,这种原地更新对实时盯盘曲线的开销更友好,但杠杆品种波动剧烈,图形仅作辅助、不预示方向。
CLineXYChart chart; chart.Attach(class="num">0, "test diagram"); chart.SetSize(class="num">200,class="num">200); class="type">class="kw">double Y[class="num">10]={-class="num">50, -class="num">40, -class="num">25, -class="num">35, class="num">10, class="num">50, class="num">70, class="num">40, class="num">15, class="num">80}; chart.AddLine(Y, Red, class="num">0, "Test line"); chart.GetChart(); class=class="str">"cmt">//建立一个类的实例 CLineXYChart chart; class=class="str">"cmt">//把它附加到之前建立的对象中 chart.Attach(class="num">0, "test diagram"); class=class="str">"cmt">//设置尺寸 chart.SetSize(class="num">200,class="num">200); class=class="str">"cmt">//准备数据 class="type">class="kw">double Y2[class="num">10]={-class="num">70, -class="num">5, class="num">6, class="num">8, class="num">10, class="num">20, class="num">100, class="num">130, class="num">90, class="num">60}; class="type">class="kw">double Y[class="num">10]={-class="num">50, -class="num">40, -class="num">25, -class="num">35, class="num">10, class="num">50, class="num">70, class="num">40, class="num">15, class="num">80}; class=class="str">"cmt">//增加线 chart.AddLine(Y, Red, class="num">0, "Test line"); chart.AddLine(Y2, Blue, class="num">0, "Test line class="num">2"); class=class="str">"cmt">//轴 chart.SetAxis(DIAGRAM_AXIS_BOTTOM|DIAGRAM_AXIS_LEFT, -class="num">1, class="num">0, class="num">0, class="num">10, class="num">0); class=class="str">"cmt">//和网格 chart.SetGrid(); class=class="str">"cmt">//获取图表 chart.GetChart(); class=class="str">"cmt">//建立一个类的实例 CLineXYChart chart; class=class="str">"cmt">//把它附加到之前创建的对象中 chart.Attach(class="num">0, "test diagram"); class=class="str">"cmt">//设置尺寸 chart.SetSize(class="num">500,class="num">300); class=class="str">"cmt">//准备数据 class="type">class="kw">double Y2[class="num">10]={-class="num">70, -class="num">5, class="num">6, class="num">8, class="num">10, class="num">20, class="num">100, class="num">130, class="num">90, class="num">60}; class="type">class="kw">double Y[class="num">10]={-class="num">50, -class="num">40, -class="num">25, -class="num">35, class="num">10, class="num">50, class="num">70, class="num">40, class="num">15, class="num">80}; class=class="str">"cmt">//增加线 class="type">int first_line=chart.AddLine(Y, Red, class="num">0, "Test line"); class="type">int second_line=chart.AddLine(Y2, Blue, class="num">0, "Test line class="num">2"); class=class="str">"cmt">//轴 chart.SetAxis(DIAGRAM_AXIS_BOTTOM|DIAGRAM_AXIS_LEFT, -class="num">1, class="num">0, class="num">0, class="num">10, class="num">0); class=class="str">"cmt">//网格 chart.SetGrid(); class=class="str">"cmt">//图例 chart.ShowLegend(); class=class="str">"cmt">//标题 chart.SetTitle("My Chart", Green, class="num">15); class=class="str">"cmt">//填充 chart.SetFill(Linen); class=class="str">"cmt">//标注 chart.SetLineMarker(first_line, DIAGRAM_LINE_MARKERS_DIAMOND, BlueViolet, class="num">10); chart.SetLineMarker(second_line, DIAGRAM_LINE_MARKERS_CROSS, YellowGreen, class="num">15); class=class="str">"cmt">//获取图表 chart.GetChart(); class=class="str">"cmt">//建立一个类的实例 CLineXYChart chart; class=class="str">"cmt">//把它附加到之前创建的对象中 chart.Attach(class="num">0, "test diagram"); class=class="str">"cmt">//设置尺寸 chart.SetSize(class="num">700,class="num">400); class=class="str">"cmt">//准备数据