面向对象法建立多时间表及多货币面板·综合运用
🧩

面向对象法建立多时间表及多货币面板·综合运用

(3/3)· 从类图到安装落地,用 OOP 把多周期多品种监控面板的扩展成本压到最低

实战向 第 3/3 篇
想在 MT5 里同时盯多个周期和多个品种的 RSI、价格变动与自定义条件,很多人第一反应是复制指标堆图表。用面向对象把面板做成可扩展骨架,加新数据列只需写几行子类,本体代码一次都不用动。

句柄检索与价格变动行的类骨架

在 MT5 面板类工具里,先得解决「同一个指标在不同品种、不同周期下只初始化一次」的问题。下面这段 GetHandle 就是干这个的:拿 symbol 和 tf 去比对已缓存的数组,命中就返回句柄,没命中返回 INVALID_HANDLE,避免重复调用 iMA 之类接口拖慢主图。 CPriceChangeRow 这个类继承自 CRow,专门负责在表格里输出价格变动。它有两个私有开关:percentChange 决定显示绝对点值还是百分比,useArrows 决定用 Wingdings 画上下箭头还是普通 Arial 文字。构造函数里直接把这两个布尔量接进来,默认 percent 为 false。 GetName 写死返回 "PriceChg",这是面板里该列的标识。GetFont 看 useArrows:为真走 Wingdings(字符码对应箭头),否则退回到 Arial。注意 Wingdings 不是普通字体,渲染层若不支持会显示方块,实盘前要在 MT5 里确认客户端装了这款符号字体。 外汇与贵金属波动受杠杆与消息面影响,用这类面板做跨周期比对时务必认识到:信号仅描述已发生变动,不代表后续方向,操作属高风险行为。

MQL5 / C++
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 派生类即可验证显示逻辑。

MQL5 / C++
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 图表,面板即开始刷新。外汇与贵金属波动剧烈,多品种同屏仅作监控辅助,不构成方向判断。

MQL5 / C++
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 双图验证:外汇与贵金属波动大、点差跳空频繁,表格刷新延迟可能让你误读信号,属高风险场景,需实盘前用策略测试器过一遍。

MQL5 / C++
  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,看面板能否同时刷报价;外汇和贵金属杠杆高、滑点跳空频繁,实盘前务必在策略测试器用历史数据跑一遍。 代码能下、类能改,剩下的只是你愿不愿意花一个晚上把那几个虚函数读完。

把面板状态交给小布盯盘
这些多时间表与多货币的诊断视图,小布盯盘的 AIGC 看板已内置常用模板,打开对应品种页即可直接切换周期与品种维度,你只管定自己的条件逻辑。

常见问题

CTable 是面板核心类,负责绘制表格并管理所有组件行;它接收 SpyAgent 发来的自定义事件来触发更新,不直接计算数据,只调度 CRow 派生类。
每个 SpyAgent 绑定一个外部交易品种,在该品种 OnCalculate 新订单号到达时发出 CHARTEVENT_CUSTOM,通知 CTable 更新对应行,原理沿用多货币模式实现方案。
小布盯盘内置了多维度看板与条件标记,可覆盖文中 RSI、SMA 条件和价格变动的常用组合;若需完全自定义子类逻辑,仍建议在 MT5 端用本文骨架扩展。
不用。从 CRow 派生一个 CMACDRow 实现取值与绘制,注册进 CTable 即可,面板核心绘制与事件逻辑保持不变。
文中两种模式基于当前品种或当前周期切换维度,同屏叠加需自行扩展 CTable 布局;外汇贵金属波动快,叠加过多列会拉长认知延迟,建议按场景分开看。