在MQL5中创建动态多品种、多周期相对强弱指数(RSI)指标仪表盘·进阶篇
(2/3)· 从初始化到实时刷新的底层机制,搞懂为什么你的跨周期RSI总卡顿、错位
很多交易者把RSI仪表盘当静态表格用,以为挂上多品种多周期就自动同步了。实际在MQL5里,按钮坐标、周期映射和实时回调任何一环写错,面板就会变成滞后且重叠的死图。本文接着基础篇,拆开动态刷新的内部运转方式。
用代码在图表右上角钉一个不可选按钮
想在 MT5 图表上挂一个常驻的 GUI 按钮,最省事的做法是走 ObjectCreate + ObjectSetInteger 这套图形对象接口。下面这段函数把按钮锚定在右上角(CORNER_RIGHT_UPPER),默认字体是 Arial Rounded MT Bold、字号 12,创建失败会打印错误码并返回 false。 函数开头先 ResetLastError() 清掉上一次的错误状态,再尝试用 OBJ_BUTTON 类型建对象;若返回假,就用 GetLastError() 把具体错误码打到日志里,方便你排查是重名还是图表上下文问题。 建成功后逐条写属性:XDISTANCE / YDISTANCE 控制离锚点的像素偏移,XSIZE / YSIZE 控制按钮尺寸,TEXT 和 FONT 决定显示文案与字形。OBJPROP_BACK 设成 false 表示按钮浮在 K 线之上,不会被背景吞掉。 顺手把 SELECTABLE 和 SELECTED 都置为 false,按钮就只是个展示/触发控件,不会被鼠标误选导致拖拽。最后 ChartRedraw(0) 强制重绘,改动才会立刻生效。外汇与贵金属图表上做这类 UI 改动属低风险操作,但脚本逻辑错误可能让对象堆积,建议先在模拟账户验证。
ObjectSetInteger(class="num">0, objName, OBJPROP_SELECTABLE, class="kw">false); class=class="str">"cmt">// Set if the button is selectable ObjectSetInteger(class="num">0, objName, OBJPROP_SELECTED, class="kw">false); class=class="str">"cmt">// Set if the button is selected ObjectSetInteger(class="num">0, objName, OBJPROP_XDISTANCE, xD); class=class="str">"cmt">// Set X distance ObjectSetInteger(class="num">0, objName, OBJPROP_CORNER, CORNER_RIGHT_UPPER); class=class="str">"cmt">// Set corner position ChartRedraw(class="num">0); class=class="str">"cmt">// Redraw the chart to reflect the new button class="kw">return (true); class=class="str">"cmt">// Return true if creation is successful class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Function to create a button | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool createButton(class="type">class="kw">string objName, class="type">class="kw">string text, class="type">int xD, class="type">int yD, class="type">int xS, class="type">int yS, class="type">class="kw">color clrTxt, class="type">class="kw">color clrBg, class="type">int fontSize = class="num">12, class="type">class="kw">color clrBorder = clrNONE, class="type">class="kw">string font = "Arial Rounded MT Bold" ) { ResetLastError(); class=class="str">"cmt">// Reset the last error code class=class="str">"cmt">// Attempt to create the button if (!ObjectCreate(class="num">0, objName, OBJ_BUTTON, class="num">0, class="num">0, class="num">0)) { Print(__FUNCTION__, ": failed to create Btn: ERR Code: ", GetLastError()); class=class="str">"cmt">// Print error message if button creation fails class="kw">return (class="kw">false); class=class="str">"cmt">// Return class="kw">false if creation fails } class=class="str">"cmt">// Set button properties ObjectSetInteger(class="num">0, objName, OBJPROP_XDISTANCE, xD); class=class="str">"cmt">// Set X distance ObjectSetInteger(class="num">0, objName, OBJPROP_YDISTANCE, yD); class=class="str">"cmt">// Set Y distance ObjectSetInteger(class="num">0, objName, OBJPROP_XSIZE, xS); class=class="str">"cmt">// Set X size ObjectSetInteger(class="num">0, objName, OBJPROP_YSIZE, yS); class=class="str">"cmt">// Set Y size ObjectSetInteger(class="num">0, objName, OBJPROP_CORNER, CORNER_RIGHT_UPPER); class=class="str">"cmt">// Set corner position ObjectSetString(class="num">0, objName, OBJPROP_TEXT, text); class=class="str">"cmt">// Set button text ObjectSetString(class="num">0, objName, OBJPROP_FONT, font); class=class="str">"cmt">// Set font type ObjectSetInteger(class="num">0, objName, OBJPROP_FONTSIZE, fontSize); class=class="str">"cmt">// Set font size ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, clrTxt); class=class="str">"cmt">// Set text class="type">class="kw">color ObjectSetInteger(class="num">0, objName, OBJPROP_BGCOLOR, clrBg); class=class="str">"cmt">// Set background class="type">class="kw">color ObjectSetInteger(class="num">0, objName, OBJPROP_BORDER_COLOR, clrBorder); class=class="str">"cmt">// Set border class="type">class="kw">color ObjectSetInteger(class="num">0, objName, OBJPROP_BACK, class="kw">false); class=class="str">"cmt">// Set background class="kw">property
「按钮状态复位与多周期RSI面板搭建」
在 MT5 里用 ObjectSetInteger 把按钮的 OBJPROP_STATE、OBJPROP_SELECTABLE、OBJPROP_SELECTED 都置为 false,等于把这枚图形对象从「被选中 / 可交互」状态硬复位。随后调 ChartRedraw(0) 强制重绘,界面才会立刻丢掉高亮框;函数返回 true 代表对象创建链路没断。 宏定义里把 BTN1 写成 "BTN1",XS1=90、XS2=100、YS1=25,颜色用 clrWhite 配 clrGray,说明按钮宽 90~100 像素、高 25 像素,摆位靠左上角坐标推算。periods[] 挂了 M1、M5、H1、H4、D1 五个周期,是后面多周期 RSI 标签的底层数组。 循环里 createButton(Desc + IntegerToString(i), truncPrds(periods[i]) + " RSI 14", (600 - XS1) + i * -XS2, 50, XS2, YS1, ...) 按 i 从 0 到 4 横向铺 5 个按钮,每个标题由周期缩写加 " RSI 14" 拼成。truncPrds 用 StringSubstr(EnumToString(period),7) 把 PERIOD_M1 砍成 M1,Print 打出的 BEFORE/AFTER 能直接验证截取是否如预期。 外汇与贵金属波动剧烈、杠杆风险高,这类面板只做多周期 RSI 速览,不预示方向。打开 MT5 把这段宏和循环贴进 EA 的 OnInit,用 Experts 日志比对 BEFORE 与 AFTER 输出,就能确认周期缩写截取逻辑没偏。
ObjectSetInteger(class="num">0, objName, OBJPROP_STATE, class="kw">false); class=class="str">"cmt">// 设置按钮状态为未按下 ObjectSetInteger(class="num">0, objName, OBJPROP_SELECTABLE, class="kw">false); class=class="str">"cmt">// 设置按钮不可被选中 ObjectSetInteger(class="num">0, objName, OBJPROP_SELECTED, class="kw">false); class=class="str">"cmt">// 设置按钮处于未选中 ChartRedraw(class="num">0); class=class="str">"cmt">// 重绘图表以反映按钮新状态 class="kw">return (true); class=class="str">"cmt">// 创建成功返回 true } class=class="str">"cmt">// 定义按钮标识与属性 class="macro">#define BTN1 "BTN1" class="macro">#define Desc "Desc " class="macro">#define Symb "Symb " class="macro">#define Base "Base " class="macro">#define RSI "RSI " class="macro">#define XS1 class="num">90 class="macro">#define XS2 class="num">100 class="macro">#define YS1 class="num">25 class="macro">#define clrW clrWhite class="macro">#define clrB clrBlack class="macro">#define clrW_Gray C&class="macro">#x27;class="num">230,class="num">230,class="num">230&class="macro">#x27; class=class="str">"cmt">// 定义要使用的时间周期 ENUM_TIMEFRAMES periods[] = {PERIOD_M1, PERIOD_M5, PERIOD_H1, PERIOD_H4, PERIOD_D1}; class=class="str">"cmt">// 全局变量 class="type">int handleName_Id; class=class="str">"cmt">// 存储RSI指标句柄ID class="type">class="kw">double rsi_Data_Val[]; class=class="str">"cmt">// 存储RSI数值的数组 class="type">int OnInit() { class="kw">return(INIT_SUCCEEDED); } createButton(BTN1, "PAIR", class="num">600, class="num">50, XS1, YS1, clrW, clrGray, class="num">15, clrGray); createButton(BTN1, "PAIR", class="num">600, class="num">50, XS1, YS1, clrW, clrGray, class="num">15, clrGray, "Arial Rounded MT Bold"); for(class="type">int i = class="num">0; i < ArraySize(periods); i++) { createButton(Desc + IntegerToString(i), truncPrds(periods[i]) + " RSI class="num">14", (class="num">600 - XS1) + i * -XS2, class="num">50, XS2, YS1, clrW, clrGray, class="num">13, clrGray); } class="type">class="kw">string truncPrds(ENUM_TIMEFRAMES period) { class="type">class="kw">string prd = StringSubstr(EnumToString(period), class="num">7); class="kw">return prd; } Print("BEFORE: ",EnumToString(periods[i])); Print("AFTER: ",truncPrds(periods[i]));
◍ 多品种 RSI 面板按钮的批量绘制
在 MT5 里做跨品种 RSI 仪表盘,核心是先按可见/不可见市场报价窗口把品种遍历一遍,再用嵌套循环给每个品种、每个周期生成按钮。下面这段逻辑里,SymbolsTotal(true) 取当前可见品种数,SymbolName(i,true) 拿到真实报价名,配合 createButton 把坐标算出来贴到图表右侧(基准 X 写死 600)。 当前图表品种会被单独标绿:当 SymbolName(i,true)==_Symbol 时,按钮前缀加星号且底色 clrB、文字 clrLimeGreen;其余品种用灰底灰字。最后一轮 i==SymbolsTotal(true)-1 时,在底部补一个总宽 XS1+XS2*ArraySize(periods) 的“RSI DashBoard”基按钮。 RSI 数值按钮在内侧 j 循环生成,坐标随周期索引 j 向左偏移 (j*-XS2),初始文本写“-/-”占位,宽高比主按钮小 1 像素。iRSI 句柄按 SymbolName(i,true)、periods[j]、周期 14、PRICE_CLOSE 去取——这意味着面板支持的周期完全由 periods 数组长度决定,比如放 3 个周期就出 3 列。 开 MT5 把 periods 改成自己常看的周期组合(如 M5/M15/H1),直接跑这段就能看到面板框架;外汇和贵金属波动大,面板只是概率参考,实盘仍需自担高风险。
createButton(Desc + IntegerToString(i), truncPrds(periods[i]) + " RSI class="num">14", (class="num">600 - XS1) + i * -XS2, class="num">50, XS2, YS1, clrW, clrDodgerBlue, class="num">13, clrBlack); class=class="str">"cmt">// Loop to create buttons for each symbol for(class="type">int i = class="num">0; i < SymbolsTotal(true); i++) { ... } class=class="str">"cmt">// Loop to create buttons for each symbol for(class="type">int i = class="num">0; i < SymbolsTotal(class="kw">false); i++) { Print("Index ",i,": Symbol = ",SymbolName(i,class="kw">false)); ... } class=class="str">"cmt">// Loop to create buttons for each symbol for(class="type">int i = class="num">0; i < SymbolsTotal(true); i++) { Print("Index ",i,": Symbol = ",SymbolName(i,true)); ... } createButton(Symb + IntegerToString(i), SymbolName(i, true), class="num">600, (class="num">50 + YS1) + i * YS1, XS1, YS1, clrW, clrGray, class="num">11, clrGray); if (SymbolName(i, true) == _Symbol) { createButton(Symb + IntegerToString(i), "*" + SymbolName(i, true), class="num">600, (class="num">50 + YS1) + i * YS1, XS1, YS1, clrB, clrLimeGreen, class="num">11, clrW); } else { createButton(Symb + IntegerToString(i), SymbolName(i, true), class="num">600, (class="num">50 + YS1) + i * YS1, XS1, YS1, clrW, clrGray, class="num">11, clrGray); } class=class="str">"cmt">// Create the base button for the RSI Dashboard at the end if (i == SymbolsTotal(true) - class="num">1) { createButton(Base + IntegerToString(i), "RSI DashBoard", class="num">600, (class="num">50 + YS1) + (i * YS1) + YS1, XS1 + XS2 * ArraySize(periods), YS1, clrW, clrGray, class="num">11, clrGray); } class=class="str">"cmt">// Loop to create buttons for RSI values for each symbol and timeframe for (class="type">int j = class="num">0; j < ArraySize(periods); j++) { createButton(RSI + IntegerToString(j) + " " + SymbolName(i, true), "-/-", (class="num">600 - XS1) + (j * -XS2), (class="num">50 + YS1) + (i * YS1), XS2 - class="num">1, YS1 - class="num">1, clrB, clrW, class="num">12, clrW); } } class=class="str">"cmt">// Loop to initialize RSI values and update buttons for(class="type">int i = class="num">0; i < SymbolsTotal(true); i++) { for (class="type">int j = class="num">0; j < ArraySize(periods); j++) { class=class="str">"cmt">// Get the handle ID for the RSI indicator for the specific symbol and timeframe handleName_Id = iRSI(SymbolName(i, true), periods[j], class="num">14, PRICE_CLOSE);
把多品种 RSI 塞进按钮里
做跨品种监控面板时,核心动作是把每个交易品种的 RSI 数值实时写进图形按钮。下面这段逻辑先遍历 SymbolsTotal(true) 返回的全部已选品种,再对 periods 数组里的每个周期取句柄、拷缓冲、打印,最后调 update_Button 刷新界面文字与配色。 ArraySetAsSeries(rsi_Data_Val, true) 让数组下标 0 对应最新一根 K 线,CopyBuffer(handleName_Id, 0, 0, 1, rsi_Data_Val) 只拉 1 根值就够面板显示。DoubleToString(rsi_Data_Val[0], 2) 把 RSI 截断到两位小数,避免按钮文字抖动。 update_Button 函数先用 ObjectFind(0, objName) 查按钮是否存在,找不到就 ResetLastError 并打印 GetLastError 码,返回 false;找到才用 ObjectSetString / ObjectSetInteger 改文字、文字色、背景色、边框色,然后 ChartRedraw(0) 强制重绘。外汇与贵金属波动快,这种面板只作辅助观察,信号滞后可能引发误判,属高风险用法。 别在循环里漏掉 ChartRedraw 每改一个按钮属性就重绘一次 chart,若放在双层 for 内会拖垮 MT5 响应;把 ChartRedraw(0) 提到外层或定时触发,帧率会明显好看。
ArraySetAsSeries(rsi_Data_Val, true); class=class="str">"cmt">// Set the array to be used as a time series CopyBuffer(handleName_Id, class="num">0, class="num">0, class="num">1, rsi_Data_Val); class=class="str">"cmt">// Copy the RSI values into the array ... } class=class="str">"cmt">// Loop to initialize RSI values and update buttons for(class="type">int i = class="num">0; i < SymbolsTotal(true); i++) { Print("SELECTED SYMBOL = ",SymbolName(i, true)); for (class="type">int j = class="num">0; j < ArraySize(periods); j++) { Print("PERIOD = ",EnumToString(periods[j])); ... } Print("PERIOD = ",EnumToString(periods[j]),": HANDLE ID = ",handleName_Id); ArrayPrint(rsi_Data_Val); class=class="str">"cmt">// Update the button with the RSI value and colors update_Button(RSI + IntegerToString(j) + " " + SymbolName(i, true), DoubleToString(rsi_Data_Val[class="num">0], class="num">2), clrBlack, clrW_Gray, clrWhite); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Function to update a button | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool update_Button(class="type">class="kw">string objName, class="type">class="kw">string text, class="type">class="kw">color clrTxt = clrBlack, class="type">class="kw">color clrBG = clrWhite, class="type">class="kw">color clrBorder = clrWhite ) { class="type">int found = ObjectFind(class="num">0, objName); class=class="str">"cmt">// Find the button by name class=class="str">"cmt">// Check if the button exists if (found < class="num">0) { ResetLastError(); class=class="str">"cmt">// Reset the last error code Print("UNABLE TO FIND THE BTN: ERR Code: ", GetLastError()); class=class="str">"cmt">// Print error message if button is not found class="kw">return (class="kw">false); class=class="str">"cmt">// Return class="kw">false if button is not found } else { class=class="str">"cmt">// Update button properties ObjectSetString(class="num">0, objName, OBJPROP_TEXT, text); class=class="str">"cmt">// Set button text ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, clrTxt); class=class="str">"cmt">// Set text class="type">class="kw">color ObjectSetInteger(class="num">0, objName, OBJPROP_BGCOLOR, clrBG); class=class="str">"cmt">// Set background class="type">class="kw">color ObjectSetInteger(class="num">0, objName, OBJPROP_BORDER_COLOR, clrBorder); class=class="str">"cmt">// Set border class="type">class="kw">color ChartRedraw(class="num">0); class=class="str">"cmt">// Redraw the chart to reflect the updated button class="kw">return (true); class=class="str">"cmt">// Return true if update is successful } } class=class="str">"cmt">// Declare variables for button colors
「用 RSI 阈值给仪表盘按钮染色」
在 MT5 多品种仪表盘里,按钮颜色直接反映 RSI 状态比看数字更快。下面这段逻辑用当前柱 RSI 值决定文字、背景和边框色:低于 30 视作超卖倾向,背景与边框转绿、文字白;高于 70 视作超买倾向,转红;中间地带用灰底黑字白边,视觉上弱化处理。 代码里 rsi_Data_Val[0] 是实时柱数值,阈值写死 30 / 70 是经典 RSI 切分,但你可以在回测中把边界调到 25 / 75 看信号稀疏度变化。外汇与贵金属杠杆高,RSI 极端值只代表概率倾斜,不代表反转必现。 [CODE] 中的 update_Button 调用把品种名(SymbolName(i,true) 带前缀)、RSI 两位小数(DoubleToString 精度 2)和三色一并刷到面板。开 MT5 把这段塞进你的 EA 循环,改 clrW_Gray 的 RGB 就能调中性区观感。
class="type">class="kw">color text_clr, bg_clr, border_clr; class=class="str">"cmt">// Determine button colors based on RSI value if (rsi_Data_Val[class="num">0] < class="num">30) { text_clr = clrWhite; bg_clr = clrGreen; border_clr = clrGreen; } else if (rsi_Data_Val[class="num">0] > class="num">70) { text_clr = clrWhite; bg_clr = clrRed; border_clr = clrRed; } else { text_clr = clrBlack; bg_clr = clrW_Gray; border_clr = clrWhite; } class=class="str">"cmt">// Update the button with the RSI value and colors update_Button(RSI + IntegerToString(j) + " " + SymbolName(i, true), DoubleToString(rsi_Data_Val[class="num">0], class="num">2), text_clr, bg_clr, border_clr);
◍ 初始化阶段把面板按钮铺满图表
EA 在 OnInit 里一次性把 RSI 仪表盘的界面骨架搭出来,而不是等行情 tick 才补控件。主按钮 BTN1 固定在坐标 (600,50),宽高由 XS1、YS1 决定,文字标 "PAIR",字号 15,灰底白字。 周期行用 for 循环按 periods 数组展开:每个按钮标题是 "M15 RSI 14" 这种格式(truncPrds 把枚举周期截成短名),X 坐标从 600-XS1 开始每次左移 XS2,字号 13。这样多周期并排,肉眼扫一眼就能比对。 品种行遍历 SymbolsTotal(true) 返回的全部可见品种。当前图品种前面加 "*" 且用 limeGreen 高亮,其余灰底;每行 Y 坐标递增 YS1。最后一个品种画完,紧接着在下方补一个跨度的 "RSI DashBoard" 底按钮,宽度 = XS1 + XS2*周期数。 每个品种×周期交叉格先填 "-/-" 占位,控件尺寸比标准格小 1 像素避免边框重叠。最后一段再跑一遍双重循环,用 iRSI(SymbolName(i,true), periods[j], 14, PRICE_CLOSE) 拿句柄——注意周期参数写死 14 期收盘价,若你想测 7 期或典型价,改这两个实参即可,MT5 里编译后面板会重排。外汇与贵金属杠杆高,面板只是观测辅助,信号不代表方向确定性。
class="type">int OnInit() { class=class="str">"cmt">// Create the main button for the pair with specific properties createButton(BTN1, "PAIR", class="num">600, class="num">50, XS1, YS1, clrW, clrGray, class="num">15, clrGray); class=class="str">"cmt">// Loop to create buttons for each timeframe with the corresponding RSI label for(class="type">int i = class="num">0; i < ArraySize(periods); i++) { class=class="str">"cmt">//Print("BEFORE: ",EnumToString(periods[i])); class=class="str">"cmt">//Print("AFTER: ",truncPrds(periods[i])); createButton(Desc + IntegerToString(i), truncPrds(periods[i]) + " RSI class="num">14", (class="num">600 - XS1) + i * -XS2, class="num">50, XS2, YS1, clrW, clrGray, class="num">13, clrGray); } class=class="str">"cmt">// Loop to create buttons for each symbol for(class="type">int i = class="num">0; i < SymbolsTotal(true); i++) { class=class="str">"cmt">//Print("Index ",i,": Symbol = ",SymbolName(i,true)); class=class="str">"cmt">// Check if the symbol is the current symbol being traded if (SymbolName(i, true) == _Symbol) { createButton(Symb + IntegerToString(i), "*" + SymbolName(i, true), class="num">600, (class="num">50 + YS1) + i * YS1, XS1, YS1, clrB, clrLimeGreen, class="num">11, clrW); } else { createButton(Symb + IntegerToString(i), SymbolName(i, true), class="num">600, (class="num">50 + YS1) + i * YS1, XS1, YS1, clrW, clrGray, class="num">11, clrGray); } class=class="str">"cmt">// Create the base button for the RSI Dashboard at the end if (i == SymbolsTotal(true) - class="num">1) { createButton(Base + IntegerToString(i), "RSI DashBoard", class="num">600, (class="num">50 + YS1) + (i * YS1) + YS1, XS1 + XS2 * ArraySize(periods), YS1, clrW, clrGray, class="num">11, clrGray); } class=class="str">"cmt">// Loop to create buttons for RSI values for each symbol and timeframe for (class="type">int j = class="num">0; j < ArraySize(periods); j++) { createButton(RSI + IntegerToString(j) + " " + SymbolName(i, true), "-/-", (class="num">600 - XS1) + (j * -XS2), (class="num">50 + YS1) + (i * YS1), XS2 - class="num">1, YS1 - class="num">1, clrB, clrW, class="num">12, clrW); } } class=class="str">"cmt">// Loop to initialize RSI values and update buttons for(class="type">int i = class="num">0; i < SymbolsTotal(true); i++) { class=class="str">"cmt">//Print("SELECTED SYMBOL = ",SymbolName(i, true)); for (class="type">int j = class="num">0; j < ArraySize(periods); j++) { class=class="str">"cmt">//Print("PERIOD = ",EnumToString(periods[j])); class=class="str">"cmt">// Get the handle ID for the RSI indicator for the specific symbol and timeframe handleName_Id = iRSI(SymbolName(i, true), periods[j], class="num">14, PRICE_CLOSE);