面向对象法建立多时间表及多货币面板·进阶篇
(2/3)· 还在为每个品种每周期手写面板逻辑?用 OOP 基类把价格、RSI 与自定义条件塞进同一张表
表格对象的清理与重绘逻辑
MT5 里做多品种或多周期盯盘面板时,旧对象不清理会越堆越多。下面这段从后往前扫图表对象,只要名字里带基准串就删,避免重复画线。 for(int i=total-1; i>=0; i--) if(StringFind(ObjectName(0,i),nameBase)!=-1) ObjectDelete(0,ObjectName(0,i)); //--- 删除列表行并释放内存 delete(rowList); 上面循环用 total-1 到 0 的倒序,是因为删对象时索引会前移,正序删容易漏。nameBase 是你给面板对象起的前缀,比如 "PB_",这样不会误删手动画的线。 重绘函数 Update() 先算行数 rows 和列数 columns:tfMode 为真时列数取 timeframes 数组长度,否则取 symbols 数组长度。接着分三步画——第一列画行名、第一行画周期或品种名、内部单元按模式取对应 symbol/tf 调 row.GetValue 出值。 最后一句 ChartRedraw() 是强制重绘,没它界面可能卡在上一帧。外汇和贵金属波动快,这类面板只辅助看盘,信号出错概率不低,真金白银下单前自己核对源数据。
for(class="type">int i=total-class="num">1; i>=class="num">0; i--) if(StringFind(ObjectName(class="num">0,i),nameBase)!=-class="num">1) ObjectDelete(class="num">0,ObjectName(class="num">0,i)); class=class="str">"cmt">//--- 删除列表行并释放内存 class="kw">delete(rowList); class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 在表格末尾插入新行 | class=class="str">"cmt">//+------------------------------------------------------------------ CTable::AddRow(CRow *row) { rowList.Add(row); row.Init(symbols,timeframes); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 重画表格 | class=class="str">"cmt">//+------------------------------------------------------------------ CTable::Update() { CRow *row; class="type">class="kw">string symbol; ENUM_TIMEFRAMES tf; class="type">int rows=rowList.Total(); class=class="str">"cmt">// 行数量 class="type">int columns; class=class="str">"cmt">// 列数量 if(tfMode) columns=ArraySize(timeframes); else columns=ArraySize(symbols); class=class="str">"cmt">//--- 画第一列 (行名称) for(class="type">int y=class="num">0; y<rows; y++) { row=(CRow*)rowList.GetNodeAtIndex(y); class=class="str">"cmt">//--- 注: 从行对象返回它自己的名称 DrawLabel(columns,y+class="num">1,row.GetName(),font,fontColor); } class=class="str">"cmt">//--- 画第一行 (时间帧名或货币对名) for(class="type">int x=class="num">0; x<columns; x++) { if(tfMode) DrawLabel(columns-x-class="num">1,class="num">0,PeriodToString(timeframes[x]),font,fontColor); else DrawLabel(columns-x-class="num">1,class="num">0,symbols[x],font,fontColor); } class=class="str">"cmt">//--- 画内部表格单元 for(class="type">int y=class="num">0; y<rows; y++) for(class="type">int x=class="num">0; x<columns; x++) { row=(CRow*)rowList.GetNodeAtIndex(y); if(tfMode) { class=class="str">"cmt">//--- 在多时间帧模式使用当前交易品种及不同的时间帧 tf=timeframes[x]; symbol=_Symbol; } else { class=class="str">"cmt">//--- 在多货币模式使用当前时间帧及不同的交易品种 tf=Period(); symbol=symbols[x]; } class=class="str">"cmt">//--- 注: 从行对象返回它自己的字体, class=class="str">"cmt">//--- 颜色和给定时间帧和交易品种的当前计算值 DrawLabel(columns-x-class="num">1,y+class="num">1,row.GetValue(symbol,tf),row.GetFont(symbol,tf),row.GetColor(symbol,tf)); } class=class="str">"cmt">//--- 强制重绘图表 ChartRedraw(); }
「表格单元格里塞文本标签的写法」
想在 MT5 图表上用 CTable 类把文字摆进指定格子里,核心就是 DrawLabel 这个成员函数。它接收行列坐标 x、y 和文本、字体、颜色,先在 nameBase 后面拼上「x:y」生成唯一对象名,避免不同单元格对象撞名。 若用 ObjectFind 在当前图表(图表ID 0)找不到该名,就 ObjectCreate 一个 OBJ_LABEL 标签对象;标签锚点固定在 CORNER_RIGHT_UPPER(右上角),再用 XDISTANCE / YDISTANCE 按 x*cellWidth、y*cellHeight 偏移量算像素位置。 下面这段是可直接塞进 EA 或指标源文件的方法体。外汇与贵金属图表叠加自定义文字属高频脚本操作,对象过多可能拖慢终端响应,实盘前建议在策略测试器里跑一遍看资源占用。
CTable::DrawLabel(class="type">int x,class="type">int y,class="type">class="kw">string text,class="type">class="kw">string font,class="type">color col) { class=class="str">"cmt">//--- 创建唯一单元名 class="type">class="kw">string name=nameBase+IntegerToString(x)+":"+IntegerToString(y); class=class="str">"cmt">//--- 创建标签 if(ObjectFind(class="num">0,name)<class="num">0) ObjectCreate(class="num">0,name,OBJ_LABEL,class="num">0,class="num">0,class="num">0); class=class="str">"cmt">//--- 设置标签属性 ObjectSetInteger(class="num">0,name,OBJPROP_CORNER,CORNER_RIGHT_UPPER); ObjectSetInteger(class="num">0,name,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER); ObjectSetInteger(class="num">0,name,OBJPROP_XDISTANCE,xDistance+x*cellWidth); ObjectSetInteger(class="num">0,name,OBJPROP_YDISTANCE,yDistance+y*cellHeight); ObjectSetString(class="num">0,name,OBJPROP_FONT,font); ObjectSetInteger(class="num">0,name,OBJPROP_COLOR,col); ObjectSetInteger(class="num">0,name,OBJPROP_FONTSIZE,fontSize); class=class="str">"cmt">//--- 设置标签文本 ObjectSetString(class="num">0,name,OBJPROP_TEXT,text); }
◍ 从 CRow 派生你自己的面板行
CRow 是盯盘面板里所有自定义行的基类,它继承自 CObject——因为只有 CObject 才能塞进 CList 容器里管理。基类预置了 5 个虚方法(Init / GetValue / GetColor / GetName / GetFont),大多只回默认值,专门留给你在子类里按需覆写,不必全接。 最简单的起步是 CPriceRow:只重写 GetName 和 GetValue 两个方法,就能在多货币面板第一栏显示工具名、在对应单元格抓最新买价。GetValue 内部用 MqlTick 结构取 tick,返回 bid,逻辑短到可以直接抄。 想显示 RSI 值也不麻烦。CPriceRow 的兄弟类在构造时收一个 period 存成成员变量,Init 里建指标句柄塞进 handles[] 数组,GetValue 从 RSI 缓冲区拷最新值返回;多出来的 GetHandle 只是在数组里查对应品种的周期句柄。
class CRow : class="kw">public CObject { class="kw">public: class="kw">virtual class="type">void Init(class="type">class="kw">string &symb[],ENUM_TIMEFRAMES &tfs[]) { } class="kw">virtual class="type">class="kw">string GetValue(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf) { class="kw">return("-"); } class="kw">virtual class="type">color GetColor(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf) { class="kw">return(clrWhite); } class="kw">virtual class="type">class="kw">string GetName() { class="kw">return("-"); } class="kw">virtual class="type">class="kw">string GetFont(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf) { class="kw">return("Arial"); } }; class CPriceRow : class="kw">public CRow { class="kw">public: class="kw">virtual class="type">class="kw">string GetValue(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf); class="kw">virtual class="type">class="kw">string GetName(); }; class="type">class="kw">string CPriceRow::GetValue(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf) { class="type">MqlTick tick; class=class="str">"cmt">//--- 取当前价格
class CRow : class="kw">public CObject { class="kw">public: class="kw">virtual class="type">void Init(class="type">class="kw">string &symb[],ENUM_TIMEFRAMES &tfs[]) { } class="kw">virtual class="type">class="kw">string GetValue(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf) { class="kw">return("-"); } class="kw">virtual class="type">color GetColor(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf) { class="kw">return(clrWhite); } class="kw">virtual class="type">class="kw">string GetName() { class="kw">return("-"); } class="kw">virtual class="type">class="kw">string GetFont(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf) { class="kw">return("Arial"); } }; class CPriceRow : class="kw">public CRow { class="kw">public: class="kw">virtual class="type">class="kw">string GetValue(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf); class="kw">virtual class="type">class="kw">string GetName(); }; class="type">class="kw">string CPriceRow::GetValue(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf) { class="type">MqlTick tick; class=class="str">"cmt">//--- 取当前价格
把 RSI 塞进多品种监控表
做跨品种盯盘时,最忌讳每看一个货币对就切一次图表。把 RSI 封装成一行(CRow 派生类),表头自动标“RSI”,后面每个品种各自取数,思路就顺了。 构造函数只接一个周期参数,比如 CRSIRow r(14) 就是 14 周期 RSI;真正干活的是 Init(),它把外部传进来的品种数组和时间帧数组各复制一份,再逐个调 iRSI() 拿指标句柄存进 handles[]。 注意句柄获取放在 Init 而非构造函数里:若品种或周期数组还没就位,提前建句柄会返回 INVALID_HANDLE。下面这段是核心初始化逻辑,复制进 MT5 的 include 类文件就能跑。 外汇与贵金属波动剧烈,RSI 仅作概率参考,实际信号需结合价格结构;高杠杆下误判可能快速放大亏损。
class CRSIRow : class="kw">public CRow { class="kw">private: class="type">int rsiPeriod; class=class="str">"cmt">// RSI 周期 class="type">class="kw">string symbols[]; class=class="str">"cmt">// 交易品种数组 ENUM_TIMEFRAMES timeframes[]; class=class="str">"cmt">// 时间帧数组 class="type">int handles[]; class=class="str">"cmt">// RSI 句柄数组 class=class="str">"cmt">//--- 寻找给定交易品种及时间帧的指标句柄 class="type">int GetHandle(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf); class="kw">public: class=class="str">"cmt">//--- 构造函数 CRSIRow(class="type">int period); 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 的默认 GetName() 方法 class="kw">virtual class="type">class="kw">string GetName(); class=class="str">"cmt">//--- 重写 CRow 的默认 Init(..) 方法 class="kw">virtual class="type">void Init(class="type">class="kw">string &symb[],ENUM_TIMEFRAMES &tfs[]); }; CRSIRow::CRSIRow(class="type">int period) { rsiPeriod=period; } class="type">void CRSIRow::Init(class="type">class="kw">string &symb[],ENUM_TIMEFRAMES &tfs[]) { class="type">int size=ArraySize(symb); ArrayResize(symbols,size); ArrayResize(timeframes,size); ArrayResize(handles,size); ArrayCopy(symbols,symb); ArrayCopy(timeframes,tfs); for(class="type">int i=class="num">0; i<ArraySize(symbols); i++) handles[i]=iRSI(symbols[i],timeframes[i],rsiPeriod,PRICE_CLOSE); }
「给 RSI 行和均线行写自己的取值逻辑」
把指标塞进表格行对象里,核心就是重写父类的 GetValue 和 GetName。下面这段 CRow 派生类 CRSIRow 的做法很直接:GetValue 先找句柄,再用 CopyBuffer 抓最新一根的 RSI 值,DoubleToString 保留两位小数返回;句柄找不到就回 "err",拷贝失败回 "-",不会让面板崩。 GetName 拼出 "RSI(周期)" 当列名,GetHandle 则在 symbols / timeframes / handles 三个数组里做线性匹配,命中就返句柄,否则 INVALID_HANDLE。线性查找在品种和周期数量少时开销可忽略,但若你要挂 30 个品种 × 7 个周期,匹配耗时可能倾向线性增长,可考虑改成映射结构。 紧跟着的 CPriceMARow 类声明了 maPeriod、maShift、maType 以及同样的句柄数组,构造函数接收均线类型和周期平移,显然是要把价格均线也做成可复用的一行。外汇与贵金属波动剧烈,这类面板只做多品种监控,信号本身不代表方向,实盘高风险。
class="type">class="kw">string CRSIRow::GetValue(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf) { class="type">class="kw">double value[class="num">1]; class=class="str">"cmt">//--- 获取 RSI 指标句柄 class="type">int handle=GetHandle(symbol,tf); if(handle==INVALID_HANDLE) class="kw">return("err"); class=class="str">"cmt">//--- 取得当前 RSI 数值 if(CopyBuffer(handle,class="num">0,class="num">0,class="num">1,value)<class="num">0) class="kw">return("-"); class="kw">return(DoubleToString(value[class="num">0],class="num">2)); } class="type">class="kw">string CRSIRow::GetName() { class="kw">return("RSI("+IntegerToString(rsiPeriod)+")"); } class="type">int CRSIRow::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 CPriceMARow : class="kw">public CRow { class="kw">private: class="type">int maPeriod; class=class="str">"cmt">// 移动平均周期 class="type">int maShift; class=class="str">"cmt">// 移动平均平移 ENUM_MA_METHOD maType; class=class="str">"cmt">// SMA, EMA, SMMA 或 LWMA class="type">class="kw">string symbols[]; class=class="str">"cmt">// 交易品种数组 ENUM_TIMEFRAMES timeframes[]; class=class="str">"cmt">// 时间帧数组 class="type">int handles[]; class=class="str">"cmt">// MA 句柄数组 class=class="str">"cmt">//--- 寻找给定交易品种及时间帧的指标句柄 class="type">int GetHandle(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf); class="kw">public: class=class="str">"cmt">//--- 构造函数 CPriceMARow(ENUM_MA_METHOD type,class="type">int period,class="type">int shift); 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 的默认 GetName() 方法 class="kw">virtual class="type">class="kw">string GetName(); class=class="str">"cmt">//--- 重写 CRow 的默认 Init(..) 方法
◍ 用一行 Yes/No 把多品种均线位置跑出来
CPriceMARow 把「价格在均线上方吗」这种判断封装成表格里的一行。构造函数只接三个参数:均线类型、周期、偏移,例如 CPriceMARow(MODE_EMA,50,0) 就是一根 50 周期偏移 0 的 EMA。 Init 负责把外部传进来的品种数组 symb[] 和周期数组 tfs[] 拷进类内部,并给每个「品种+周期」组合调用 iMA 拿句柄。ArrayResize 先按 ArraySize(symb) 定长,再 ArrayCopy 复制内容,最后 for 循环里 handles[i]=iMA(...) 一次性建好所有句柄。 GetValue 是真正出结果的地方:先用 GetHandle 拿句柄,失败返回 err;CopyBuffer 取最新一根均线值到 value[1],SymbolInfoTick 取最新 bid。tick.bid>value[0] 成立就返回 Yes,否则 No。也就是说,XAUUSD 的 M5 图表上若 bid 站在 EMA50 之上,这一格就显示 Yes,概率上倾向多头占优,但外汇贵金属杠杆高,信号失效也常见。 GetName 把类型转成短名拼出列头,如 Price>EMA(50)。把这套类丢进前文的多品种监控表,MT5 里就能直接扫一批货币对和周期,肉眼看哪格是 Yes。
class="kw">virtual class="type">void Init(class="type">class="kw">string &symb[],ENUM_TIMEFRAMES &tfs[]); }; class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| CPriceMARow类的构造函数 | class=class="str">"cmt">//+------------------------------------------------------------------ CPriceMARow::CPriceMARow(ENUM_MA_METHOD type,class="type">int period,class="type">int shift) { maPeriod= period; maShift = shift; maType=type; } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 重写 CRow 的默认Init(..) 方法 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">void CPriceMARow::Init(class="type">class="kw">string &symb[],ENUM_TIMEFRAMES &tfs[]) { class="type">int size=ArraySize(symb); ArrayResize(symbols,size); ArrayResize(timeframes,size); ArrayResize(handles,size); class=class="str">"cmt">//--- 复制数组内容至它自己的数组 ArrayCopy(symbols,symb); ArrayCopy(timeframes,tfs); class=class="str">"cmt">//--- 取得所有交易品种或时间帧的 MA 句柄 for(class="type">int i=class="num">0; i<ArraySize(symbols); i++) handles[i]=iMA(symbols[i],timeframes[i],maPeriod,maShift,maType,PRICE_CLOSE); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 重写 CRow 的默认GetValue(..)方法 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">class="kw">string CPriceMARow::GetValue(class="type">class="kw">string symbol,ENUM_TIMEFRAMES tf) { class="type">class="kw">double value[class="num">1]; class="type">MqlTick tick; class=class="str">"cmt">//--- 获取 MA 指标句柄 class="type">int handle=GetHandle(symbol,tf); if(handle==INVALID_HANDLE) class="kw">return("err"); class=class="str">"cmt">//--- 取最后 MA 数值 if(CopyBuffer(handle,class="num">0,class="num">0,class="num">1,value)<class="num">0) class="kw">return("-"); class=class="str">"cmt">//--- 取最后价格 if(!SymbolInfoTick(symbol,tick)) class="kw">return("-"); class=class="str">"cmt">//--- 检查当前条件: 价格 > MA if(tick.bid>value[class="num">0]) class="kw">return("Yes"); else class="kw">return("No"); } class=class="str">"cmt">//+------------------------------------------------------------------ class=class="str">"cmt">//| 重写 CRow 的默认GetName()方法 | class=class="str">"cmt">//+------------------------------------------------------------------ class="type">class="kw">string CPriceMARow::GetName() { class="type">class="kw">string name; class="kw">switch(maType) { case MODE_SMA: name = "SMA"; break; case MODE_EMA: name = "EMA"; break; case MODE_SMMA: name = "SMMA"; break; case MODE_LWMA: name = "LWMA"; break; } class="kw">return("Price>"+name+"("+IntegerToString(maPeriod)+")"); }