面向对象法建立多时间表及多货币面板·综合运用
(3/3)· 从类图到安装落地,用 OOP 把多周期多品种监控面板的扩展成本压到最低
句柄检索与价格变动行的类骨架
在 MT5 面板类工具里,先得解决「同一个指标在不同品种、不同周期下只初始化一次」的问题。下面这段 GetHandle 就是干这个的:拿 symbol 和 tf 去比对已缓存的数组,命中就返回句柄,没命中返回 INVALID_HANDLE,避免重复调用 iMA 之类接口拖慢主图。 CPriceChangeRow 这个类继承自 CRow,专门负责在表格里输出价格变动。它有两个私有开关:percentChange 决定显示绝对点值还是百分比,useArrows 决定用 Wingdings 画上下箭头还是普通 Arial 文字。构造函数里直接把这两个布尔量接进来,默认 percent 为 false。 GetName 写死返回 "PriceChg",这是面板里该列的标识。GetFont 看 useArrows:为真走 Wingdings(字符码对应箭头),否则退回到 Arial。注意 Wingdings 不是普通字体,渲染层若不支持会显示方块,实盘前要在 MT5 里确认客户端装了这款符号字体。 外汇与贵金属波动受杠杆与消息面影响,用这类面板做跨周期比对时务必认识到:信号仅描述已发生变动,不代表后续方向,操作属高风险行为。
class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 寻找给定交易品种及时间帧的指标句柄; | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">int CPriceMARow::GetHandle(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf) { for(class="type">int i=class="num">0; i<ArraySize(timeframes); i++) if(symbols[i]==symbol && timeframes[i]==tf) class="kw">return(handles[i]); class="kw">return(INVALID_HANDLE); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| CPriceChangeRow类 | class=class="str">"cmt">//+------------------------------------------------------------------ class CPriceChangeRow : class="kw">public CRow { class="kw">private: class="type">bool percentChange; class="type">bool useArrows; class="kw">public: class=class="str">"cmt">//--- 构造函数 CPriceChangeRow(class="type">bool arrows,class="type">bool percent=false); class=class="str">"cmt">//--- 重写 CRow 的默认GetName() 方法 class="kw">virtual class="type">class="kw">string GetName(); class=class="str">"cmt">//--- 重写 CRow 的默认GetFont() 方法 class="kw">virtual class="type">class="kw">string GetFont(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf); class=class="str">"cmt">//--- 重写 CRow 的默认GetValue(..) 方法 class="kw">virtual class="type">class="kw">string GetValue(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf); class=class="str">"cmt">//--- 重写 CRow 的默认GetColor(..) 方法 class="kw">virtual class="type">color GetColor(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf); }; class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| CPriceChangeRow 类的构造函数 | class=class="str">"cmt">//+------------------------------------------------------------------ CPriceChangeRow::CPriceChangeRow(class="type">bool arrows,class="type">bool percent=false) { percentChange=percent; useArrows=arrows; } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 重写 CRow 的默认GetName()方法 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">class="kw">string CPriceChangeRow::GetName() { class="kw">return("PriceChg"); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 重写 CRow 的默认GetFont()方法 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">class="kw">string CPriceChangeRow::GetFont(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf) { class=class="str">"cmt">//--- 我们使用 Wingdings 字体画箭头 (上/下) if(useArrows) class="kw">return("Wingdings"); else class="kw">return("Arial"); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 重写 CRow 的默认GetValue(..)方法 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">class="kw">string CPriceChangeRow::GetValue(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf) {
「柱线涨跌的箭头与配色怎么算出来」
在自定义表格行里,取当前柱线的开盘与收盘是第一道工序。用 CopyClose 和 CopyOpen 各拉 1 根柱(shift=0),若返回负值说明行情数据没就绪,直接交白卷或退回白色,避免脏数据上屏。 涨跌方向靠 close[0]-open[0] 的符号判断。若 useArrows 为真,change>0 回 CharToString(233) 即上箭头,change<0 回 234 即下箭头;否则走数值:percentChange 为真时算 (change/open[0]*100.0) 并保留 3 位小数加 '%',否则按品种小数位 SymbolInfoInteger(symbol,SYMBOL_DIGITS) 原样输出点位差。 GetColor 复用同一套开收取值,但只做三态映射:收大于开给 clrLime,收小于开给 clrRed,持平或取数失败给 clrWhite。这样表格里一眼能分出阳线绿、阴线红,XAUUSD 这类高波动品种跳空时白行也便于察觉数据异常。 外汇与贵金属杠杆高、滑点随机,箭头和颜色只是结构提示,不代表后续概率倾斜,开 MT5 把这段代码塞进 CRow 派生类即可验证显示逻辑。
class="type">class="kw">double close[class="num">1]; class="type">class="kw">double open[class="num">1]; class=class="str">"cmt">//--- 取当前柱线的开盘价和收盘价 if(CopyClose(symbol,tf,class="num">0, class="num">1, close) < class="num">0) class="kw">return(" "); if(CopyOpen(symbol, tf, class="num">0, class="num">1, open) < class="num">0) class="kw">return(" "); class=class="str">"cmt">//--- 当前柱线的价格改变 class="type">class="kw">double change=close[class="num">0]-open[class="num">0]; if(useArrows) { if(change > class="num">0) class="kw">return(CharToString(class="num">233)); class=class="str">"cmt">// 返回上箭头代码 if(change < class="num">0) class="kw">return(CharToString(class="num">234)); class=class="str">"cmt">// 返回下箭头代码 class="kw">return(" "); }else{ if(percentChange) { class=class="str">"cmt">//--- 计算百分比变化 class="kw">return(DoubleToString(change/open[class="num">0]*class="num">100.0,class="num">3)+"%"); }else{ class="kw">return(DoubleToString(change,(class="type">int)SymbolInfoInteger(symbol,SYMBOL_DIGITS))); } } } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 重写 CRow 的默认GetColor(..) 方法 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">color CPriceChangeRow::GetColor(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf) { class="type">class="kw">double close[class="num">1]; class="type">class="kw">double open[class="num">1]; class=class="str">"cmt">//--- 取当前柱线的开盘价和收盘价 if(CopyClose(symbol,tf,class="num">0, class="num">1, close) < class="num">0) class="kw">return(clrWhite); if(CopyOpen(symbol, tf, class="num">0, class="num">1, open) < class="num">0) class="kw">return(clrWhite); if(close[class="num">0] > open[class="num">0]) class="kw">return(clrLime); if(close[class="num">0] < open[class="num">0]) class="kw">return(clrRed); class="kw">return(clrWhite); }
◍ 把面板跑起来的装配步骤
做一个能用的多货币面板,先建指标 TableSample,核心是在 OnInit() 里动态 new 一个 CTable 指针,并决定走多货币还是多时间表。代码里给的是多货币写法:symbols 数组塞了 EURUSD、GBPUSD、USDJPY、AUDCHF 四个品种,传给 CTable(symbols);若改多时间表,注释里的 timeframes 数组(M1/H1/D1/W1)配合 new CTable(timeframes) 即可切换。 行组件靠 AddRow 逐个挂:CPriceRow 出当前价,CPriceChangeRow 出柱线涨跌与百分比,CRSIRow(14) 和 CRSIRow(10) 双周期 RSI,CPriceMARow 用 SMA20 判价格位置。SetFont 设 Arial 10 号黄字,SetCellSize(60,20) 定格宽高,首次绘制末尾必须调 Update,否则面板空白。 OnDeinit 只做 delete table 释放对象;OnCalculate 与 OnChartEvent 都只调 Update。多货币模式才需要 OnChartEvent 接 SpyAgents 事件,多时间表模式仅 OnCalculate 盯当前图就够。把指标拖上 MT5 图表,面板即开始刷新。外汇与贵金属波动剧烈,多品种同屏仅作监控辅助,不构成方向判断。
class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| TableSample.mq5 | class=class="str">"cmt">//| Marcin Konieczny | class=class="str">"cmt">//| | class=class="str">"cmt">//+------------------------------------------------------------------ class="macro">#class="kw">property copyright "Marcin Konieczny" class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property indicator_chart_window class="macro">#class="kw">property indicator_plots class="num">0 class="macro">#include <Table.mqh> class="macro">#include <PriceRow.mqh> class="macro">#include <PriceChangeRow.mqh> class="macro">#include <RSIRow.mqh> class="macro">#include <PriceMARow.mqh> CTable *table; class=class="str">"cmt">// CTable对象指针 class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 指标初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">int OnInit() { class=class="str">"cmt">//--- 表格使用的时间帧 (在多时间帧模式) ENUM_TIMEFRAMES timeframes[class="num">4]={PERIOD_M1,PERIOD_H1,PERIOD_D1,PERIOD_W1}; class=class="str">"cmt">//--- 表格使用的交易品种(在多货币模式) class="type">class="kw">string symbols[class="num">4]={"EURUSD","GBPUSD","USDJPY","AUDCHF" }; class=class="str">"cmt">//-- CTable 对象创建 class=class="str">"cmt">// table = new CTable(timeframes); // 多时间帧模式 table=new CTable(symbols); class=class="str">"cmt">// 多货币对模式 class=class="str">"cmt">//--- 表格中加入行 table.AddRow(new CPriceRow()); class=class="str">"cmt">// 显示当前价格 table.AddRow(new CPriceChangeRow(false)); class=class="str">"cmt">// 显示最后柱线的价格变化 table.AddRow(new CPriceChangeRow(false,true)); class=class="str">"cmt">// 显示最后柱线的价格百分比 table.AddRow(new CPriceChangeRow(true)); class=class="str">"cmt">// 显示价格改变的箭头 table.AddRow(new CRSIRow(class="num">14)); class=class="str">"cmt">// 显示 RSI(class="num">14) table.AddRow(new CRSIRow(class="num">10)); class=class="str">"cmt">// 显示 RSI(class="num">10) table.AddRow(new CPriceMARow(MODE_SMA,class="num">20,class="num">0)); class=class="str">"cmt">// 显示如果 SMA(class="num">20) > 当前价格 class=class="str">"cmt">//--- 设置表格参数 table.SetFont("Arial",class="num">10,clrYellow); class=class="str">"cmt">// 字体, 大小, 颜色 table.SetCellSize(class="num">60, class="num">20); class=class="str">"cmt">// 宽度, 高度
表格对象的生命周期与事件刷新
在 MT5 自定义指标里,把表格对象交给 OnDeinit 析构才是稳妥做法。上面代码在去初始化时直接 delete(table),能避免指标反复加载造成的内存泄漏和残留图形对象。 定位坐标用 table.SetDistance(10, 10) 把表格钉在图表右上角,距边各 10 像素;随后 table.Update() 强制重绘,让布局立即生效而非等下一根 K 线。 迭代函数 OnCalculate 每次返回 rates_total,并在内部调 table.Update() 保持表格随行情重算。若接了 SpyAgent 类指标发来的 CHARTEVENT_CUSTOM(仅多货币对模式),OnChartEvent 里再补一次 table.Update() 即可抓到跨品种信号刷新。 跑这段代码时建议开 EURUSD 与 XAUUSD 双图验证:外汇与贵金属波动大、点差跳空频繁,表格刷新延迟可能让你误读信号,属高风险场景,需实盘前用策略测试器过一遍。
table.SetDistance(class="num">10, class="num">10); class=class="str">"cmt">// 图表右上角的距离 table.Update(); class=class="str">"cmt">// 强制重绘图表 class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 指标去初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">//--- 调用析构函数并释放内存 class="kw">delete(table); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 指标迭代函数 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">int begin, const class="type">class="kw">double &price[]) { class=class="str">"cmt">//--- 更新表格: 重算/重绘 table.Update(); class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| OnChartEvent处理函数 | class=class="str">"cmt">//|处理SpyAgent指标发送的CHARTEVENT_CUSTOM事件 | class=class="str">"cmt">//| 只在多货币对模式! | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">void OnChartEvent(const class="type">int id,const class="type">long &lparam,const class="type">class="kw">double &dparam,const class="type">class="kw">string &sparam) { table.Update(); class=class="str">"cmt">// 更新表格: 重算/重绘 }
「编译与目录落位」
SpyAgent 与 TableSample 两个指标文件,必须放进 terminal_data_folder\MQL5\Indicators 才会被 MT5 识别;其余依赖项属于 include 类文件,只能落在 terminal_data_folder\MQL5\Include,放错目录编译会直接报找不到头文件。 所有 mq5 文件在复制前都要先过一遍编译,没通过的 include 不会自动报错但会导致指标运行时崩溃。 实际启动只需把 TableSample 挂到任意图表,SpyAgent 不必手动附加——它作为后台依赖会被自动拉起。这一机制意味着你验证环境是否装对,最快的办法就是开一张 EURUSD 图表挂 TableSample,看是否出现面板且无「cannot open include」类提示。
◍ 把面板拿去跑一遍再说
这套多时间表多货币面板的 OOP 写法,核心是把行、列、单元格拆成可继承的类,后续加币种或加周期只改子类不碰主框架。原文附的 multi_currency_panel_oop_mql5.zip 约 10.6 KB,下载后直接拖进 MT5 的 MQL5/Experts 目录能编译,但 2020 年有用户反馈在新版终端编译报错,多半是标准库接口微调导致的。 真要验证扩展性,自己建个 CRow 的子类挂上 XAUUSD 的 M5 与 EURUSD 的 H1,看面板能否同时刷报价;外汇和贵金属杠杆高、滑点跳空频繁,实盘前务必在策略测试器用历史数据跑一遍。 代码能下、类能改,剩下的只是你愿不愿意花一个晚上把那几个虚函数读完。