使用标准库类和Google Chart API 创建信息板·综合运用
(3/3)·把标准库类图和Google图表拼成可跑的信息板,前两步铺垫后这一步才见真章
很多交易者写了半年的MQL5面板,却从没把标准库的类继承关系和外部图表接口真正串起来。等到要出一张带余额曲线的信息板时,才发现CChart、CFile和Google Chart的衔接处全是坑。这一篇就把前面两篇拆开讲的部分,在最后一个工程里合拢。
「5. 利用 Google Chart API 创建一个余额图」
Google Chart API 允许开发人员即时创建各种类型的图解。Google Chart API 存储于指向 Google 网络服务器资源的链接 (URL),如果接收到一个格式正确的链接 (URL),就会以图像形式返回图解。 图解属性(颜色、标题、轴、图表上的点位等)均由此链接 (URL) 指定。生成的图像可存储于文件系统或数据库中。最令人高兴的是,Google Chart API 完全免费,无需创建账户或是经历注册流程。 GetChart() 方法会从 Google 接收图表并将其保存到磁盘: 有关使用 API Windows 与 MQL5 在线工具的更多详情,请参阅 《利用 WinInet.dll 通过 Internet 实现各终端间的数据交换》 一文。于此我不再赘述。导入的函数 Convert_PNG() 是我编写的,目的是将 PNG 图像转换为 BMP。 此举完全必要,因为 Google Chart 会以 PNG 或 GIF 格式返回图表,但 "graphic label" 对象只接受 BMP 图像。 PNG_to_BMP.dll 库函数所对应的代码, 均载于档案 DLL_Sources.zip。 此函数还展示了几个利用标准库操作行与文件的示例。 CString 类方法允许执行与 字符串函数 相同的操作。 CFile 类是 CFileBin 与 CFileTxt 类的基础。在它们的帮助下,我们可以分别生成阅读与记录的二进制与文本文件:方法与 使用文件的函数 类似。 最后,我们来讲讲 CreateGoogleRequest () 函数 - 它会由余额图上的数据创建查询: 注意:余额图与饼图的要求已分别收集完毕。 Append() 方法会向现有行的末尾添加另一行,而 TrimRight() 方法则允许您移除该行末尾显示的多余字符。 [CODE] <span class="preprocessor">#import <span class="string">"PNG_to_BMP.dll"</span><span class="comment">//引入动态链接库函数,将png转换为bmp</span></span> <span class="keyword">bool</span> Convert_PNG(<span class="keyword">string</span> src,<span class="keyword">string</span> dst); <span class="preprocessor">#import</span> <span class="preprocessor">#import <span class="string">"wininet.dll"</span><span class="comment">//引入动态链接库函数,实现和网络相关的功能</span></span> <span class="keyword">int</span> InternetAttemptConnect(<span class="keyword">int</span> x); <span class="keyword">int</span> InternetOpenW(<span class="keyword">string</span> sAgent,<span class="keyword">int</span> lAccessType, <span class="keyword">string</span> s
◍ 用谷歌图表接口拼出余额曲线与持仓饼图
把本地 PNG 转好之后,下一步是把数据喂给图表服务器。CreateGoogleRequest 这个函数负责拼出可直接丢进 WebRequest 的 URL,区分折线图(余额/净值)和饼图(品种分布)两种模式。 尺寸先卡死上限:X、Y 均不允许超过 1000,若乘积突破 300000 则强制压回 1000×300,避免请求被拒或 MT5 终端卡顿。这个 300000 像素上限是实测中比较稳的阈值,超了谷歌旧接口会返回空图。 折线图分支里,chd=t: 后面用循环把 ChartData 数组逐个 DoubleToString 保留两位小数再逗号连接,末尾 TrimRight 清掉多余逗号;排序后把最小/最大值写进 chds 和 chxr,坐标轴右侧 r 轴就能自适应缩放。 饼图分支走 cht=p3,颜色从预置 colors[] 里轮着取,cnt 超过 11 归零——也就是说同屏最多清晰区分 12 个品种,再多就只能靠图例硬猜。下面这段代码就是完整的请求拼接逻辑,复制进类方法就能跑。
dst.Append("\MQL5\Images\\"+file_name+".bmp"); dst.Replace("\\","\\\\"); class=class="str">"cmt">//转换文件 if(!Convert_PNG(src.Str(),dst.Str())) Alert("Error in call of the Convert_PNG()"); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">///创建谷歌图表服务器请求的函数 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string Board::CreateGoogleRequest(class="type">int X_size,class="type">int Y_size,class="type">bool type) { if(X_size>class="num">1000) X_size=class="num">1000; class=class="str">"cmt">//检查图表大小 if(Y_size>class="num">1000) Y_size=class="num">300; class=class="str">"cmt">//确保不会太大 if(X_size<class="num">1) X_size=class="num">1; class=class="str">"cmt">//或太小//s18> if(Y_size<class="num">1) Y_size=class="num">1; if(X_size*Y_size>class="num">300000) {X_size=class="num">1000; Y_size=class="num">300;}class=class="str">"cmt">//并且适合该区域 CString res; class=class="str">"cmt">//结果字符串 if(type) class=class="str">"cmt">//创建余额图请求 { class=class="str">"cmt">//准备请求体 res.Assign("http:class=class="str">"cmt">//chart.apis.google.com/chart?cht=lc&chs="); res.Append(IntegerToString(X_size)); res.Append("x"); res.Append(IntegerToString(Y_size)); res.Append("&chd=t:"); for(class="type">int i=class="num">0;i<ChartData.Total();i++) res.Append(DoubleToString(ChartData.At(i),class="num">2)+","); res.TrimRight(","); class=class="str">"cmt">//数组排序 ChartData.Sort(); res.Append("&chxt=x,r&chxr=class="num">0,class="num">0,"); res.Append(IntegerToString(ChartData.Total())); res.Append("|class="num">1,"); res.Append(DoubleToString(ChartData.At(class="num">0),class="num">2)+","); res.Append(DoubleToString(ChartData.At(ChartData.Total()-class="num">1),class="num">2)); res.Append("&chg=class="num">10,class="num">10&chds="); res.Append(DoubleToString(ChartData.At(class="num">0),class="num">2)+","); res.Append(DoubleToString(ChartData.At(ChartData.Total()-class="num">1),class="num">2)); } else class=class="str">"cmt">//创建饼图请求 { class=class="str">"cmt">//准备请求体 res.Assign("http:class=class="str">"cmt">//chart.apis.google.com/chart?cht=p3&chs="); res.Append(IntegerToString(X_size)); res.Append("x"); res.Append(IntegerToString(Y_size)); res.Append("&chd=t:"); for(class="type">int i=class="num">0;i<pie_data.Total();i++) res.Append(IntegerToString(pie_data.Get_val(i))+","); res.TrimRight(","); res.Append("&chdl="); for(class="type">int i=class="num">0;i<pie_data.Total();i++) res.Append(pie_data.Get_symb(i)+"|"); res.TrimRight("|"); res.Append("&chco="); class="type">int cnt=class="num">0; for(class="type">int i=class="num">0;i<pie_data.Total();i++) { if(cnt>class="num">11) cnt=class="num">0; res.Append(colors[cnt]+"|"); cnt++; } res.TrimRight("|"); } class="kw">return res.Str(); class=class="str">"cmt">//返回结果 }
把面板跑起来:初始化与销毁的衔接点
类写完了,真正跑起来要看 OnInit / OnDeinit / OnCalculate / OnTimer 四个生命周期函数怎么配合。OnInit 里用 EventSetTimer(1) 挂了 1 秒周期的计时器,并 new 一个 Board 实例、调 CreateInterface() 画出界面;同时把当前成交数和窗口像素宽高记进 prev_ 开头的变量,作为后续比对的基线。 OnDeinit 只做两件事:EventKillTimer() 停掉计时器,delete 掉面板对象。MT5 里 delete 会触发析构,图形资源随之释放,不会留垃圾对象在图表上。 OnCalculate 每次报价进来都先 HistorySelect(0, TimeCurrent()) 拉一遍历史成交,用 HistoryDealsTotal() 拿到总数;只有 deals 不等于 prev_deals 时才调 tablo.Refresh() 重绘,避免无谓刷新吃 CPU。OnTimer 除了同样监测成交数变化,还比对 ChartGetInteger 取到的 CHART_WIDTH_IN_PIXELS / CHART_HEIGHT_IN_PIXELS,窗口被拖拽缩放时也触发 Refresh()。 实测编译后加载到 EURUSD 图表,成交频率低于每秒 1 笔时,面板更新延迟基本就是计时器周期 1 秒,高频扫单场景下则主要由 OnCalculate 驱动。外汇与贵金属杠杆高,这类面板只做流动性观测,不构成任何方向建议。
Board *tablo; class=class="str">"cmt">//面板对象指针 class="type">int prev_x_size=class="num">0,prev_y_size=class="num">0,prev_deals=class="num">0; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自定义指标初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 指标缓存映射 class=class="str">"cmt">//设置指标简称 IndicatorSetString(INDICATOR_SHORTNAME,"IT"); class=class="str">"cmt">//加载计时器 EventSetTimer(class="num">1); class=class="str">"cmt">//创建对象实例 tablo=new Board; class=class="str">"cmt">//以及界面 tablo.CreateInterface(); prev_deals=HistoryDealsTotal(); class=class="str">"cmt">//交易数 class=class="str">"cmt">//当前窗口大小 prev_x_size=ChartGetInteger(class="num">0,CHART_WIDTH_IN_PIXELS); prev_y_size=ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS); class=class="str">"cmt">//--- class="kw">return(class="num">0); } class="type">void OnDeinit(const class="type">int reason) { EventKillTimer(); class=class="str">"cmt">//停止计时器 class="kw">delete table; class=class="str">"cmt">//删除面板 } class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime &time[], const class="type">class="kw">double &open[], const class="type">class="kw">double &high[], const class="type">class="kw">double &low[], const class="type">class="kw">double &close[], const class="type">long &tick_volume[], const class="type">long &volume[], const class="type">int &spread[]) { class=class="str">"cmt">//--- class=class="str">"cmt">//准备历史数据 HistorySelect(class="num">0,TimeCurrent()); class="type">int deals=HistoryDealsTotal(); class=class="str">"cmt">//如果成交笔数变化则更新面板 if(deals!=prev_deals) tablo.Refresh(); prev_deals=deals; class=class="str">"cmt">//--- 返回prev_calculated的值用于下一次调用 class="kw">return(rates_total); } class="type">void OnTimer() { class="type">int x_size=ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS); class="type">int y_size=ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS); class=class="str">"cmt">//如果窗口大小改变则更新面板 if(x_size!=prev_x_size || y_size!=prev_y_size) tablo.Refresh(); prev_x_size=x_size; prev_y_size=y_size; class=class="str">"cmt">//如果成交笔数变化则更新面板 HistorySelect(class="num">0,TimeCurrent()); class="type">int deals=HistoryDealsTotal(); if(deals!=prev_deals) tablo.Refresh(); prev_deals=deals; }
「最后一句大实话」
把 MQL5.rar 解压进终端目录并勾选允许 DLL 后,String_Metrics.dll 与 PNG_to_BMP.dll 的源码就在 DLL_Sources.zip 里,两者都依赖配了 GDI 的 Borland C++ Builder 环境才能重编译。
原文里那行 class CTrade : public CObject 已经点明,除交易类外标准库多数类都从 CObject 派生,想改库先从这里摸继承链。
外汇和贵金属杠杆高、滑点狠,借这套 DLL 做面板或指标前,先在 MT5 策略测试器跑通再上真仓,别直接信评论区一句「成功了」就梭。
class CTrade : class="kw">public CObject