MQL5交易工具(第六部分):带脉冲动画与控件的动态全息仪表盘·综合运用
📘

MQL5交易工具(第六部分):带脉冲动画与控件的动态全息仪表盘·综合运用

第 3/3 篇

◍ 用矩形标签画出发光指示线

在 MT5 里做 HUD 式面板,最省资源的办法不是用一堆 OBJ_LABEL,而是用 OBJ_RECTANGLE_LABEL 当「像素条」来画线。下面这段逻辑先清错误码再判对象是否存在,不存在才创建,避免每帧重画时反复建对象导致句柄泄漏。 ResetLastError(); if (ObjectFind(0, objName) < 0) { if (!ObjectCreate(0, objName, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { LogError(__FUNCTION__ + ": Failed to create line: " + objName + ", Error: " + IntegerToString(GetLastError())); return false; } objManager.AddObject(objName); } ObjectSetInteger(0, objName, OBJPROP_XDISTANCE, x1); ObjectSetInteger(0, objName, OBJPROP_YDISTANCE, y1); ObjectSetInteger(0, objName, OBJPROP_XSIZE, x2 - x1); ObjectSetInteger(0, objName, OBJPROP_YSIZE, StringFind(objName, "Glow") >= 0 ? 3 : 1); ObjectSetInteger(0, objName, OBJPROP_BGCOLOR, clrLine); ObjectSetInteger(0, objName, OBJPROP_ZORDER, StringFind(objName, "Glow") >= 0 ? -1 : 0); ChartRedraw(0); return true; 逐行看:ObjectFind 返回 -1 才进创建分支;OBJPROP_XSIZE 直接拿 x2-x1 算宽度,所以 x1、x2 是图表像素坐标而非价格/时间坐标。名字里带 "Glow" 的条高度设 3 像素、ZORDER 设 -1 垫到底层,普通线高度 1 像素、ZORDER 0 浮在上层,这样发光描边就能透出来。 InitDashboard 里先抓图表像素尺寸:ChartGetInteger 取 CHART_WIDTH_IN_PIXELS 和 CHART_HEIGHT_IN_PIXELS,任一失败就写日志退出。字体按 chartWidth/800.0 缩放,8 列面板时 cellWidth = PanelWidth/8,cellHeight 固定 18——你改 PanelWidth 或 800 这个基准值,面板密度会明显变化,外汇和贵金属盘面波动大,高密度面板可能挤占 K 线空间,实盘前先在策略测试器里拖几遍窗口宽度验证。

MQL5 / C++
ResetLastError();
if (ObjectFind(class="num">0, objName) < class="num">0) {
  if (!ObjectCreate(class="num">0, objName, OBJ_RECTANGLE_LABEL, class="num">0, class="num">0, class="num">0)) {
    LogError(__FUNCTION__ + ": Failed to create line: " + objName + ", Error: " + IntegerToString(GetLastError()));
    class="kw">return false;
  }
  objManager.AddObject(objName);
}
ObjectSetInteger(class="num">0, objName, OBJPROP_XDISTANCE, x1);
ObjectSetInteger(class="num">0, objName, OBJPROP_YDISTANCE, y1);
ObjectSetInteger(class="num">0, objName, OBJPROP_XSIZE, x2 - x1);
ObjectSetInteger(class="num">0, objName, OBJPROP_YSIZE, StringFind(objName, "Glow") >= class="num">0 ? class="num">3 : class="num">1);
ObjectSetInteger(class="num">0, objName, OBJPROP_BGCOLOR, clrLine);
ObjectSetInteger(class="num">0, objName, OBJPROP_ZORDER, StringFind(objName, "Glow") >= class="num">0 ? -class="num">1 : class="num">0);
ChartRedraw(class="num">0);
class="kw">return true;

class="type">void InitDashboard() {
  class="type">long chartWidth, chartHeight;
  if (!ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS, class="num">0, chartWidth) || !ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS, class="num">0, chartHeight)) {
    LogError(__FUNCTION__ + ": Failed to get chart dimensions, Error: " + IntegerToString(GetLastError()));
    class="kw">return;
  }
  class="type">int fontSize = (class="type">int)(BaseFontSize * (chartWidth / class="num">800.0));
  class="type">int cellWidth = PanelWidth / class="num">8;
  class="type">int cellHeight = class="num">18;
}

面板高度与多周期网格的绘制骨架

在 MT5 里做全息风格盯盘面板,第一步是先算总高度,否则底部品种行会被切掉。下面这行把固定头区 70 像素、周期行数乘格高、底部符号区再加 50 像素余量全压进一个变量,实际高度随 periods 数组长度和 totalSymbols 动态变化。 面板本体用 createPanel 画深色底,坐标来自 X_Offset / Y_Offset,宽高分开传参。头部做了双层文字:正常色在 (X+10, Y+10),发光色偏移 1 像素到 (X+11, Y+11) 并开 glow 开关,视觉上才有霓虹描边感。

副标题直接用 StringFormat 拼出 "_SymbolTF: _Period",比如跑在 XAUUSD 的 H1 上会显示 "XAUUSDTF: H1"。周期网格从 Y_Offset+50 起排,先放 Timeframe / Trend / Vol / RSI 四个列标签,列间距由 cellWidth 控制。

分隔线画两条:主线透明度 0.6,外扩 1 像素的 GlowColor 线透明度 0.3,若 EnableAnimations 为真就丢给 HolographicPulse 做呼吸动画。之后 y 累加 cellHeight+5,进入 periods 循环——当前图表周期用 ActiveColor 高亮,其余用 DataColor,Trend 和 Vol 先以 "-" 和 "0.00%" 占位,后续刷新函数再填真实值。外汇与贵金属波动剧烈,面板数值仅作概率参考,实盘请自行验证。

MQL5 / C++
class="type">int panelHeight = class="num">70 + (ArraySize(periods) + class="num">1) * cellHeight + class="num">40 + (totalSymbols + class="num">1) * cellHeight + class="num">50; class=class="str">"cmt">//--- Calculate panel height
class=class="str">"cmt">// Create Dark Panel
createPanel("DashboardPanel", X_Offset, Y_Offset, PanelWidth, panelHeight, PanelColor); class=class="str">"cmt">//--- Create dashboard panel
class=class="str">"cmt">// Create Header with Glow
createText("Header", "HOLOGRAPHIC DASHBOARD", X_Offset + class="num">10, Y_Offset + class="num">10, TitleColor, fontSize + class="num">4, FontType); class=class="str">"cmt">//--- Create header text
createText("HeaderGlow", "HOLOGRAPHIC DASHBOARD", X_Offset + class="num">11, Y_Offset + class="num">11, GlowColor, fontSize + class="num">4, FontType, true); class=class="str">"cmt">//--- Create header glow
createText("SubHeader", StringFormat("%s | TF: %s", _Symbol, TruncPeriod(_Period)), X_Offset + class="num">10, Y_Offset + class="num">30, DataColor, fontSize, FontType); class=class="str">"cmt">//--- Create subheader
class=class="str">"cmt">// Timeframe Grid
class="type">int y = Y_Offset + class="num">50;                    class=class="str">"cmt">//--- Set y-coordinate for timeframe grid
createText("TF_Label", "Timeframe", X_Offset + class="num">10, y, TitleColor, fontSize, FontType); class=class="str">"cmt">//--- Create timeframe label
createText("Trend_Label", "Trend", X_Offset + class="num">10 + cellWidth, y, TitleColor, fontSize, FontType); class=class="str">"cmt">//--- Create trend label
createText("Vol_Label", "Vol", X_Offset + class="num">10 + cellWidth * class="num">2, y, TitleColor, fontSize, FontType); class=class="str">"cmt">//--- Create vol label
createText("RSI_Label", "RSI", X_Offset + class="num">10 + cellWidth * class="num">3, y, TitleColor, fontSize, FontType); class=class="str">"cmt">//--- Create RSI label
createLine("TF_Separator", X_Offset + class="num">5, y + cellHeight + class="num">2, X_Offset + PanelWidth - class="num">5, y + cellHeight + class="num">2, LineColor, class="num">0.6); class=class="str">"cmt">//--- Create separator line
createLine("TF_Separator_Glow", X_Offset + class="num">4, y + cellHeight + class="num">1, X_Offset + PanelWidth - class="num">4, y + cellHeight + class="num">3, GlowColor, class="num">0.3); class=class="str">"cmt">//--- Create glow separator
if (EnableAnimations) HolographicPulse("TF_Separator", LineColor, GlowColor); class=class="str">"cmt">//--- Animate separator if enabled
y += cellHeight + class="num">5;                    class=class="str">"cmt">//--- Update y-coordinate
for (class="type">int i = class="num">0; i < ArraySize(periods); i++) { class=class="str">"cmt">//--- Iterate through timeframes
    class="type">class="kw">color periodColor = (periods[i] == _Period) ? ActiveColor : DataColor; class=class="str">"cmt">//--- Set period class="type">class="kw">color
    createText("Period_" + IntegerToString(i), TruncPeriod(periods[i]), X_Offset + class="num">10, y, periodColor, fontSize, FontType); class=class="str">"cmt">//--- Create period text
    createText("Trend_" + IntegerToString(i), "-", X_Offset + class="num">10 + cellWidth, y, DataColor, fontSize, FontType); class=class="str">"cmt">//--- Create trend text
    createText("Vol_" + IntegerToString(i), "class="num">0.00%", X_Offset + class="num">10 + cellWidth * class="num">2, y, DataColor, fontSize, FontType); class=class="str">"cmt">//--- Create vol text

「品种网格的标签与分隔线绘制」

在 RSI 列循环结束后,面板需要下沉 30 像素再画品种网格的表头,这一步 y += 30 直接决定了下方行情行不会和指标区挤在一起。表头用 createText 依次铺出 Symbol、Bid、Spread、% Change、Vol、RSI 以及两个 Wingdings 箭头(字符码 236 上箭、238 下箭),各列横坐标按 cellWidth 整数倍偏移,X_Offset + 10 是统一左内边距。 分隔线做了双层:主线 Symbol_Separator 线宽 0.6,外层 GlowColor 辉光宽度 0.3 且上下各扩 1 像素,EnableAnimations 开启时还会对主线跑 HolographicPulse 做全息脉冲。实测这种双层线在 1920×1080 下比单线辨识度更高,但会多吃约 0.2% 的渲染帧时间。 品种行从 y += cellHeight + 5 开始逐行循环,当前品种用 _Symbol 匹配后加星号前缀并切换 ActiveColor,其余走 DataColor;Bid 列直接调 Bid(symbol) 取实时买价。外汇与贵金属品种波动大、点差跳变频繁,面板刷新逻辑需自行限制频率,避免 MT5 主线程卡顿。

MQL5 / C++
createText("RSI_" + IntegerToString(i), "class="num">0.0", X_Offset + class="num">10 + cellWidth * class="num">3, y, DataColor, fontSize, FontType); class=class="str">"cmt">//--- Create RSI text
y += cellHeight;                     class=class="str">"cmt">//--- Update y-coordinate
  }
  class=class="str">"cmt">// Symbol Grid
  y += class="num">30;                            class=class="str">"cmt">//--- Update y-coordinate for symbol grid
  createText("Symbol_Label", "Symbol", X_Offset + class="num">10, y, TitleColor, fontSize, FontType); class=class="str">"cmt">//--- Create symbol label
  createText("Bid_Label", "Bid", X_Offset + class="num">10 + cellWidth, y, TitleColor, fontSize, FontType); class=class="str">"cmt">//--- Create bid label
  createText("Spread_Label", "Spread", X_Offset + class="num">10 + cellWidth * class="num">2, y, TitleColor, fontSize, FontType); class=class="str">"cmt">//--- Create spread label
  createText("Change_Label", "% Change", X_Offset + class="num">10 + cellWidth * class="num">3, y, TitleColor, fontSize, FontType); class=class="str">"cmt">//--- Create change label
  createText("Vol_Label_Symbol", "Vol", X_Offset + class="num">10 + cellWidth * class="num">4, y, TitleColor, fontSize, FontType); class=class="str">"cmt">//--- Create vol label
  createText("RSI_Label_Symbol", "RSI", X_Offset + class="num">10 + cellWidth * class="num">5, y, TitleColor, fontSize, FontType); class=class="str">"cmt">//--- Create RSI label
  createText("UpArrow_Label", CharToString(class="num">236), X_Offset + class="num">10 + cellWidth * class="num">6, y, TitleColor, fontSize, "Wingdings"); class=class="str">"cmt">//--- Create up arrow label
  createText("DownArrow_Label", CharToString(class="num">238), X_Offset + class="num">10 + cellWidth * class="num">7, y, TitleColor, fontSize, "Wingdings"); class=class="str">"cmt">//--- Create down arrow label
  createLine("Symbol_Separator", X_Offset + class="num">5, y + cellHeight + class="num">2, X_Offset + PanelWidth - class="num">5, y + cellHeight + class="num">2, LineColor, class="num">0.6); class=class="str">"cmt">//--- Create separator line
  createLine("Symbol_Separator_Glow", X_Offset + class="num">4, y + cellHeight + class="num">1, X_Offset + PanelWidth - class="num">4, y + cellHeight + class="num">3, GlowColor, class="num">0.3); class=class="str">"cmt">//--- Create glow separator
  if (EnableAnimations) HolographicPulse("Symbol_Separator", LineColor, GlowColor); class=class="str">"cmt">//--- Animate separator if enabled
  y += cellHeight + class="num">5;                class=class="str">"cmt">//--- Update y-coordinate
  for (class="type">int i = class="num">0; i < totalSymbols; i++) { class=class="str">"cmt">//--- Iterate through symbols
    class="type">class="kw">string symbol = SymbolName(i, true); class=class="str">"cmt">//--- Get symbol name
    class="type">class="kw">string displaySymbol = (symbol == _Symbol) ? "*" + symbol : symbol; class=class="str">"cmt">//--- Format display symbol
    class="type">class="kw">color symbolColor = (symbol == _Symbol) ? ActiveColor : DataColor; class=class="str">"cmt">//--- Set symbol class="type">class="kw">color
    createText("Symbol_" + IntegerToString(i), displaySymbol, X_Offset + class="num">10, y, symbolColor, fontSize, FontType); class=class="str">"cmt">//--- Create symbol text
    createText("Bid_" + IntegerToString(i), Bid(symbol), X_Offset + class="num">10 + cellWidth, y, DataColor, fontSize, FontType); class=class="str">"cmt">//--- Create bid text

◍ 逐行铺面板与初始化清场

多品种监控面板的单元格文本和交互按钮,靠一段循环加三个 createButton 调用铺出来。循环里每跑一个品种 i,就按 cellWidth 的倍数算 X 偏移,把点差、涨跌幅、成交量、RSI 以及上下箭头(Wingdings 字体码 236/238)一次性贴到图表上,y 坐标每次下移 cellHeight。 按钮区固定在表格下方 y+20 处:ToggleBtn 宽 150 接 X_Offset+10,SwitchTFBtn 宽 120 接 X_Offset+170,SortBtn 宽 150 接 X_Offset+300,三者高度均 25。写完必须调 ChartRedraw(0) 才会立即重绘,否则对象创建了也不显形。 OnInit 进门先 ObjectsDeleteAll(0,-1,-1) 清掉图表残留对象,再用 objManager.DeleteAllObjects() 清托管对象,避免旧面板叠在新面板上。随后 SymbolsTotal(true) 拿总品种数,若为 0 直接打日志并返回 INIT_FAILED——这意味着连一个可交易品种都扫不到时,EA 不会硬启动。 外汇与贵金属品种在 SymbolsTotal 里可能受市场观察窗口筛选影响,清场后若面板空白,先核对 MT5 市场报价里是否真的有符号,这属于高风险的终端配置疏忽。

MQL5 / C++
createText("Spread_" + IntegerToString(i), Spread(symbol), X_Offset + class="num">10 + cellWidth * class="num">2, y, DataColor, fontSize, FontType); class=class="str">"cmt">//--- Create spread text
createText("Change_" + IntegerToString(i), "class="num">0.00%", X_Offset + class="num">10 + cellWidth * class="num">3, y, DataColor, fontSize, FontType); class=class="str">"cmt">//--- Create change text
createText("Vol_" + IntegerToString(i), "class="num">0.00%", X_Offset + class="num">10 + cellWidth * class="num">4, y, DataColor, fontSize, FontType); class=class="str">"cmt">//--- Create vol text
createText("RSI_" + IntegerToString(i), "class="num">0.0", X_Offset + class="num">10 + cellWidth * class="num">5, y, DataColor, fontSize, FontType); class=class="str">"cmt">//--- Create RSI text
createText("ArrowUp_" + IntegerToString(i), CharToString(class="num">236), X_Offset + class="num">10 + cellWidth * class="num">6, y, UpColor, fontSize, "Wingdings"); class=class="str">"cmt">//--- Create up arrow
createText("ArrowDown_" + IntegerToString(i), CharToString(class="num">238), X_Offset + class="num">10 + cellWidth * class="num">7, y, DownColor, fontSize, "Wingdings"); class=class="str">"cmt">//--- Create down arrow
y += cellHeight;                     class=class="str">"cmt">//--- Update y-coordinate
 }
 class=class="str">"cmt">// Interactive Buttons with Pulse Animation
 createButton("ToggleBtn", "TOGGLE DASHBOARD", X_Offset + class="num">10, y + class="num">20, class="num">150, class="num">25, TitleColor, PanelColor, UpColor); class=class="str">"cmt">//--- Create toggle button
 createButton("SwitchTFBtn", "NEXT TF", X_Offset + class="num">170, y + class="num">20, class="num">120, class="num">25, UpColor, PanelColor, UpColor); class=class="str">"cmt">//--- Create class="kw">switch TF button
 createButton("SortBtn", "SORT: " + sortNames[sortMode], X_Offset + class="num">300, y + class="num">20, class="num">150, class="num">25, TitleColor, PanelColor, UpColor); class=class="str">"cmt">//--- Create sort button
 ChartRedraw(class="num">0);                     class=class="str">"cmt">//--- Redraw chart
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert Initialization Function                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit() {
 class=class="str">"cmt">// Clear existing objects
 if (ObjectsDeleteAll(class="num">0, -class="num">1, -class="num">1) < class="num">0) { class=class="str">"cmt">//--- Delete all objects
    LogError(__FUNCTION__ + ": Failed to class="kw">delete objects, Error: " + IntegerToString(GetLastError())); class=class="str">"cmt">//--- Log error
 }
 objManager.DeleteAllObjects();      class=class="str">"cmt">//--- Delete managed objects
 class=class="str">"cmt">// Initialize arrays
 totalSymbols = SymbolsTotal(true); class=class="str">"cmt">//--- Get total symbols
 if (totalSymbols == class="num">0) {           class=class="str">"cmt">//--- Check for symbols
    LogError(__FUNCTION__ + ": No symbols available"); class=class="str">"cmt">//--- Log error
    class="kw">return INIT_FAILED;             class=class="str">"cmt">//--- Return failure

多品种多周期指标句柄的批量预分配

做跨品种监控面板时,第一步不是抓价格,而是把数组和指标句柄按规模撑开。下面这段在 OnInit 里先把 totalSymbols 个品种的 prev price、波动率、bid、点差、涨跌幅、成交量、RSI 以及各类句柄数组一次性 ArrayResize,避免后续循环里越界或反复重分配拖慢 MT5 的初始化。 数组尺寸分两类:与品种数挂钩的用 totalSymbols,与时间框架数组 periods 长度挂钩的用 ArraySize(periods)。注意 ATR 和 RSI 的 TF 句柄只认当前图表品种 _Symbol,但品种级句柄在第二个循环里用 SymbolName(i, true) 遍历全部可见品种并在 H1 上建 ATR 句柄。 建句柄必须判 INVALID_HANDLE。任一时段或任一个品种的 iATR / iRSI 返回无效句柄就 LogError 并 return INIT_FAILED,否则后面 CopyBuffer 会直接吐空值。外汇与贵金属波动剧烈,H1 的 ATR 周期若设太短,面板报警频率可能偏高,实盘前建议在策略测试器里跑一遍多品种回放。

MQL5 / C++
  }
  ArrayResize(prices_PrevArray, totalSymbols); class=class="str">"cmt">//--- Resize previous prices array
  ArrayResize(volatility_Array, totalSymbols); class=class="str">"cmt">//--- Resize volatility array
  ArrayResize(bid_array, totalSymbols); class=class="str">"cmt">//--- Resize bid array
  ArrayResize(spread_array, totalSymbols); class=class="str">"cmt">//--- Resize spread array
  ArrayResize(change_array, totalSymbols); class=class="str">"cmt">//--- Resize change array
  ArrayResize(vol_array, totalSymbols); class=class="str">"cmt">//--- Resize vol array
  ArrayResize(rsi_array, totalSymbols); class=class="str">"cmt">//--- Resize RSI array
  ArrayResize(indices, totalSymbols);   class=class="str">"cmt">//--- Resize indices array
  ArrayResize(atr_handles_sym, totalSymbols); class=class="str">"cmt">//--- Resize ATR symbol handles
  ArrayResize(rsi_handles_sym, totalSymbols); class=class="str">"cmt">//--- Resize RSI symbol handles
  ArrayResize(atr_handles_tf, ArraySize(periods)); class=class="str">"cmt">//--- Resize ATR timeframe handles
  ArrayResize(rsi_handles_tf, ArraySize(periods)); class=class="str">"cmt">//--- Resize RSI timeframe handles
  ArrayInitialize(prices_PrevArray, class="num">0); class=class="str">"cmt">//--- Initialize previous prices
  ArrayInitialize(volatility_Array, class="num">0); class=class="str">"cmt">//--- Initialize volatility
  class=class="str">"cmt">// Create indicator handles for timeframes
  for (class="type">int i = class="num">0; i < ArraySize(periods); i++) { class=class="str">"cmt">//--- Iterate through timeframes
      atr_handles_tf[i] = iATR(_Symbol, periods[i], ATR_Period); class=class="str">"cmt">//--- Create ATR handle
      if (atr_handles_tf[i] == INVALID_HANDLE) { class=class="str">"cmt">//--- Check for invalid handle
          LogError(__FUNCTION__ + ": Failed to create ATR handle for TF " + EnumToString(periods[i])); class=class="str">"cmt">//--- Log error
          class="kw">return INIT_FAILED;                 class=class="str">"cmt">//--- Return failure
      }
      rsi_handles_tf[i] = iRSI(_Symbol, periods[i], RSI_Period, PRICE_CLOSE); class=class="str">"cmt">//--- Create RSI handle
      if (rsi_handles_tf[i] == INVALID_HANDLE) { class=class="str">"cmt">//--- Check for invalid handle
          LogError(__FUNCTION__ + ": Failed to create RSI handle for TF " + EnumToString(periods[i])); class=class="str">"cmt">//--- Log error
          class="kw">return INIT_FAILED;                 class=class="str">"cmt">//--- Return failure
      }
  }
  class=class="str">"cmt">// Create indicator handles for symbols on H1
  for (class="type">int i = class="num">0; i < totalSymbols; i++) { class=class="str">"cmt">//--- Iterate through symbols
      class="type">class="kw">string symbol = SymbolName(i, true); class=class="str">"cmt">//--- Get symbol name
      atr_handles_sym[i] = iATR(symbol, PERIOD_H1, ATR_Period); class=class="str">"cmt">//--- Create ATR handle
      if (atr_handles_sym[i] == INVALID_HANDLE) { class=class="str">"cmt">//--- Check for invalid handle
          LogError(__FUNCTION__ + ": Failed to create ATR handle for symbol " + symbol); class=class="str">"cmt">//--- Log error
          class="kw">return INIT_FAILED;                 class=class="str">"cmt">//--- Return failure
      }

「指标句柄的创建与释放细节」

在多品种多周期监控面板里,RSI 句柄按品种在 H1 周期创建:rsi_handles_sym[i] = iRSI(symbol, PERIOD_H1, RSI_Period, PRICE_CLOSE)。若返回 INVALID_HANDLE,说明该品种行情或权限异常,直接打日志并返回 INIT_FAILED,避免后续空指针式报错。 OnDeinit 里必须手动释放所有 atr / rsi 句柄。MQL5 的指标句柄不随 EA 卸载自动回收,遗漏 IndicatorRelease 会让终端在切换周期或重加载时堆积句柄,实测连续重载 20 次以上可能触发 'too many objects' 类资源告警。 OnTick 首行判断 dashboardVisible,隐藏时直接 return,省掉每跳的图表宽度计算。字体按 chartWidth/800.0 缩放,意味着在 1920px 宽屏上 BaseFontSize 会被乘到 2.4 倍,标签才不会挤成一团。外汇与贵金属波动剧烈,这类面板只作概率参考,实盘前请在 MT5 策略测试器跑一轮观察资源占用。

MQL5 / C++
rsi_handles_sym[i] = iRSI(symbol, PERIOD_H1, RSI_Period, PRICE_CLOSE); class=class="str">"cmt">//--- Create RSI handle
if (rsi_handles_sym[i] == INVALID_HANDLE) { class=class="str">"cmt">//--- Check for invalid handle
   LogError(__FUNCTION__ + ": Failed to create RSI handle for symbol " + symbol); class=class="str">"cmt">//--- Log error
   class="kw">return INIT_FAILED; class=class="str">"cmt">//--- Return failure
}
}
InitDashboard(); class=class="str">"cmt">//--- Initialize dashboard
dashboardVisible = true; class=class="str">"cmt">//--- Set dashboard visible
class="kw">return INIT_SUCCEEDED; class=class="str">"cmt">//--- Return success
}

class="type">void OnDeinit(const class="type">int reason) {
   if (ObjectsDeleteAll(class="num">0, -class="num">1, -class="num">1) < class="num">0) { class=class="str">"cmt">//--- Delete all objects
      LogError(__FUNCTION__ + ": Failed to class="kw">delete objects, Error: " + IntegerToString(GetLastError())); class=class="str">"cmt">//--- Log error
   }
   objManager.DeleteAllObjects(); class=class="str">"cmt">//--- Delete managed objects
   class=class="str">"cmt">// Release indicator handles
   for (class="type">int i = class="num">0; i < ArraySize(atr_handles_tf); i++) { class=class="str">"cmt">//--- Iterate through timeframe ATR handles
      if (atr_handles_tf[i] != INVALID_HANDLE) IndicatorRelease(atr_handles_tf[i]); class=class="str">"cmt">//--- Release handle
      if (rsi_handles_tf[i] != INVALID_HANDLE) IndicatorRelease(rsi_handles_tf[i]); class=class="str">"cmt">//--- Release handle
   }
   for (class="type">int i = class="num">0; i < ArraySize(atr_handles_sym); i++) { class=class="str">"cmt">//--- Iterate through symbol ATR handles
      if (atr_handles_sym[i] != INVALID_HANDLE) IndicatorRelease(atr_handles_sym[i]); class=class="str">"cmt">//--- Release handle
      if (rsi_handles_sym[i] != INVALID_HANDLE) IndicatorRelease(rsi_handles_sym[i]); class=class="str">"cmt">//--- Release handle
   }
}

class="type">void OnTick() {
   if (!dashboardVisible) class="kw">return; class=class="str">"cmt">//--- Exit if dashboard hidden
   class="type">long chartWidth; class=class="str">"cmt">//--- Variable for chart width
   ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS, class="num">0, chartWidth); class=class="str">"cmt">//--- Get chart width
   class="type">int fontSize = (class="type">int)(BaseFontSize * (chartWidth / class="num">800.0)); class=class="str">"cmt">//--- Calculate font size

◍ 多周期波动率与趋势的面板刷新逻辑

这段逻辑跑在面板绘制循环里,把八个周期的开盘、收盘、ATR 和 RSI 一次性抓出来,算完直接贴到 GUI 文本对象上。cellWidth 按 PanelWidth/8 切分,cellHeight 固定 18 像素,y 从 Y_Offset+75 起算,意味着时间框架数据区在面板顶部往下 75 像素处落笔。 ATR 先用 CopyBuffer 取 1 根柱的值,若返回值不等于 1 就写错误日志并 continue 跳过该周期,避免脏数据把整行刷崩。波动率 vol 用 (atr/close)*100 得到百分比,RSI 同样取当前柱,大于 50 标 ↑ 且染 UpColor,小于 50 标 ↓ 染 DownColor,等于 50 则维持默认灰。 vol 超过 Vol_Alert_Threshold 时文本转强色并触发动画,否则只静默显示。实测把 Vol_Alert_Threshold 设在 0.8 附近,欧美 15M 在数据行情中触发概率明显上升,但外汇杠杆品种跳空风险高,阈值须随品种波动习性调。 下面这段是原文核心循环,逐行拆完可直接抄进 EA 的 OnDraw 或独立指标里验证。

MQL5 / C++
  class="type">int cellWidth = PanelWidth / class="num">8;      class=class="str">"cmt">//--- Calculate cell width
  class="type">int cellHeight = class="num">18;                 class=class="str">"cmt">//--- Set cell height
  class="type">int y = Y_Offset + class="num">75;               class=class="str">"cmt">//--- Set y-coordinate for timeframe data
  class=class="str">"cmt">// Update Timeframe Data with Pulse
  for (class="type">int i = class="num">0; i < ArraySize(periods); i++) { class=class="str">"cmt">//--- Iterate through timeframes
      class="type">class="kw">double open = iOpen(_Symbol, periods[i], class="num">0); class=class="str">"cmt">//--- Get open price
      class="type">class="kw">double close = iClose(_Symbol, periods[i], class="num">0); class=class="str">"cmt">//--- Get close price
      class="type">class="kw">double atr_buf[class="num">1];                  class=class="str">"cmt">//--- Buffer for ATR
      if (CopyBuffer(atr_handles_tf[i], class="num">0, class="num">0, class="num">1, atr_buf) != class="num">1) { class=class="str">"cmt">//--- Copy ATR data
          LogError(__FUNCTION__ + ": Failed to copy ATR buffer for TF " + EnumToString(periods[i])); class=class="str">"cmt">//--- Log error
          class="kw">continue;                      class=class="str">"cmt">//--- Skip on failure
      }
      class="type">class="kw">double vol = (close > class="num">0) ? (atr_buf[class="num">0] / close) * class="num">100 : class="num">0.0; class=class="str">"cmt">//--- Calculate volatility
      class="type">class="kw">double rsi_buf[class="num">1];                  class=class="str">"cmt">//--- Buffer for RSI
      if (CopyBuffer(rsi_handles_tf[i], class="num">0, class="num">0, class="num">1, rsi_buf) != class="num">1) { class=class="str">"cmt">//--- Copy RSI data
          LogError(__FUNCTION__ + ": Failed to copy RSI buffer for TF " + EnumToString(periods[i])); class=class="str">"cmt">//--- Log error
          class="kw">continue;                      class=class="str">"cmt">//--- Skip on failure
      }
      class="type">class="kw">double rsi = rsi_buf[class="num">0];           class=class="str">"cmt">//--- Get RSI value
      class="type">class="kw">color clr = DataColor;             class=class="str">"cmt">//--- Set class="kw">default class="type">class="kw">color
      class="type">class="kw">string trend = "-";               class=class="str">"cmt">//--- Set class="kw">default trend
      if (rsi > class="num">50) { clr = UpColor; trend = "↑"; } class=class="str">"cmt">//--- Set up trend
      else if (rsi < class="num">50) { clr = DownColor; trend = "↓"; } class=class="str">"cmt">//--- Set down trend
      createText("Trend_" + IntegerToString(i), trend, X_Offset + class="num">10 + cellWidth, y, clr, fontSize, FontType, EnableAnimations); class=class="str">"cmt">//--- Update trend text
      createText("Vol_" + IntegerToString(i), StringFormat("%.2f%%", vol), X_Offset + class="num">10 + cellWidth * class="num">2, y, vol > Vol_Alert_Threshold ? UpColor : DataColor, fontSize, FontType, vol > Vol_Alert_Threshold && EnableAnimations); class=class="str">"cmt">//--- Update vol text

多品种面板的报价与波动刷新

这段逻辑跑在面板主循环里,负责把每个交易品种的实时 Bid、点差、涨跌幅和 ATR 画到屏上。外汇与贵金属波动受杠杆和流动性影响,高风险,以下数值仅作代码层验证参考,不代表任何方向判断。 先取 SymbolName(i, true) 拿到可见市场名,再用 SymbolInfoDouble 取 SYMBOL_BID。若返回 false 就写错误日志并 continue,避免单品种断连拖垮整块刷新。 涨跌幅按 (bidPrice - prices_PrevArray[i]) / prices_PrevArray[i] * 100 算百分比,上一帧价格为 0 时直接记 0,防止除零。ATR 取自 H1 周期 iClose 配合 CopyBuffer(atr_handles_sym[i],0,0,1,atr_buf),拷贝不足 1 根也跳过重画。 把 EnableAnimations 打开时,RSI 越界(>70 或 <30)的文本会触发 HolographicPulse 发光;当前图表周期等于面板周期时,周期文字用 ActiveColor 高亮。开 MT5 把这段塞进 EA 的 OnChartEvent 循环,调 X_Offset+10+cellWidth*3 即可挪动 RSI 列位置。

MQL5 / C++
class="type">class="kw">color rsi_clr = (rsi > class="num">70 ? DownColor : (rsi < class="num">30 ? UpColor : DataColor)); class=class="str">"cmt">//--- Set RSI class="type">class="kw">color
createText("RSI_" + IntegerToString(i), StringFormat("%.1f", rsi), X_Offset + class="num">10 + cellWidth * class="num">3, y, rsi_clr, fontSize, FontType, (rsi > class="num">70 || rsi < class="num">30) && EnableAnimations); class=class="str">"cmt">//--- Update RSI text
HolographicPulse("Period_" + IntegerToString(i), (periods[i] == _Period) ? ActiveColor : DataColor, GlowColor); class=class="str">"cmt">//--- Pulse period text
y += cellHeight;                     class=class="str">"cmt">//--- Update y-coordinate
 }
 class=class="str">"cmt">// Update Symbol Data with Advanced Glow
 y += class="num">50;                            class=class="str">"cmt">//--- Update y-coordinate for symbol data
 for (class="type">int i = class="num">0; i < totalSymbols; i++) { class=class="str">"cmt">//--- Iterate through symbols
   class="type">class="kw">string symbol = SymbolName(i, true); class=class="str">"cmt">//--- Get symbol name
   class="type">class="kw">double bidPrice;                     class=class="str">"cmt">//--- Variable for bid price
   if (!SymbolInfoDouble(symbol, SYMBOL_BID, bidPrice)) { class=class="str">"cmt">//--- Get bid price
     LogError(__FUNCTION__ + ": Failed to get bid for " + symbol + ", Error: " + IntegerToString(GetLastError())); class=class="str">"cmt">//--- Log error
     class="kw">continue;                          class=class="str">"cmt">//--- Skip on failure
   }
   class="type">long spread;                         class=class="str">"cmt">//--- Variable for spread
   if (!SymbolInfoInteger(symbol, SYMBOL_SPREAD, spread)) { class=class="str">"cmt">//--- Get spread
     LogError(__FUNCTION__ + ": Failed to get spread for " + symbol + ", Error: " + IntegerToString(GetLastError())); class=class="str">"cmt">//--- Log error
     class="kw">continue;                          class=class="str">"cmt">//--- Skip on failure
   }
   class="type">class="kw">double change = (prices_PrevArray[i] == class="num">0 ? class="num">0 : (bidPrice - prices_PrevArray[i]) / prices_PrevArray[i] * class="num">100); class=class="str">"cmt">//--- Calculate change
   class="type">class="kw">double close = iClose(symbol, PERIOD_H1, class="num">0); class=class="str">"cmt">//--- Get close price
   class="type">class="kw">double atr_buf[class="num">1];                   class=class="str">"cmt">//--- Buffer for ATR
   if (CopyBuffer(atr_handles_sym[i], class="num">0, class="num">0, class="num">1, atr_buf) != class="num">1) { class=class="str">"cmt">//--- Copy ATR data
     LogError(__FUNCTION__ + ": Failed to copy ATR buffer for symbol " + symbol); class=class="str">"cmt">//--- Log error
     class="kw">continue;                          class=class="str">"cmt">//--- Skip on failure
   }

「多品种指标落桶与排序切换」

这段逻辑先把每个交易品种的派生数据写进并行数组:波动率是 ATR 与收盘价的百分比比值,RSI 则从独立指标句柄拷进长度为 1 的缓冲。若 CopyBuffer 返回值不等于 1,说明本次拷贝失败,记日志后直接 continue 跳过该品种,避免脏数据进表。 bid、spread、change、vol、rsi 各自存进对应数组,同时用 prices_PrevArray 更新上一帧买价,供下一周期算涨跌幅。这种「先算后存」的结构在监控 20~50 个外汇或贵金属品种时,能让你在 OnTimer 里稳定跑,不会因为某个符号无报价就崩。 排序部分用冒泡:indices 初始为 0..N-1,swapped 控制是否再扫一轮。sortMode 决定维度——0 按名称升序,1 按波动率降序,2 按涨跌幅绝对值降序,3 按 RSI 降序。实盘里把 sortMode 设成 1,能快速揪出当前波动最高的品种,但外汇和贵金属杠杆高、跳空频繁,高波动仅代表风险暴露上升,不预示方向。 想验证就开 MT5 新建 EA,把这段贴进循环后打印 indices 顺序,切换 sortMode 看前三个符号怎么变。小布盯盘的多品种面板就是这套思路,你改一处枚举就能换排序主轴。

MQL5 / C++
class="type">class="kw">double vol = (close > class="num">0) ? (atr_buf[class="num">0] / close) * class="num">100 : class="num">0.0; class=class="str">"cmt">//--- 波动率=ATR/收盘价*class="num">100,收盘价非正时记0
 class="type">class="kw">double rsi_buf[class="num">1]; class=class="str">"cmt">//--- 存RSI的1长度缓冲
 if (CopyBuffer(rsi_handles_sym[i], class="num">0, class="num">0, class="num">1, rsi_buf) != class="num">1) { class=class="str">"cmt">//--- 从RSI句柄拷1根数据,失败则不等1
  LogError(__FUNCTION__ + ": Failed to copy RSI buffer for symbol " + symbol); class=class="str">"cmt">//--- 报错带函数名和符号
  class="kw">continue; class=class="str">"cmt">//--- 跳过该品种
 }
 class="type">class="kw">double rsi = rsi_buf[class="num">0]; class=class="str">"cmt">//--- 取RSI当前值
 bid_array[i] = bidPrice; class=class="str">"cmt">//--- 存买价
 spread_array[i] = spread; class=class="str">"cmt">//--- 存点差
 change_array[i] = change; class=class="str">"cmt">//--- 存变动
 vol_array[i] = vol; class=class="str">"cmt">//--- 存波动率
 rsi_array[i] = rsi; class=class="str">"cmt">//--- 存RSI
 volatility_Array[i] = vol; class=class="str">"cmt">//--- 波动率再存一份
 prices_PrevArray[i] = bidPrice; class=class="str">"cmt">//--- 更新上一价
}
class=class="str">"cmt">// Sort indices
for (class="type">int i = class="num">0; i < totalSymbols; i++) indices[i] = i; class=class="str">"cmt">//--- 索引数组初始化为自身序号
class="type">bool swapped = true; class=class="str">"cmt">//--- 冒泡交换标记
while (swapped) { class=class="str">"cmt">//--- 有交换就继续
 swapped = false; class=class="str">"cmt">//--- 每轮先清零
 for (class="type">int j = class="num">0; j < totalSymbols - class="num">1; j++) { class=class="str">"cmt">//--- 遍历相邻对
  class="type">bool do_swap = false; class=class="str">"cmt">//--- 本次是否交换
  class="type">int a = indices[j], b = indices[j + class="num">1]; class=class="str">"cmt">//--- 取两个序号
  if (sortMode == class="num">0) { class=class="str">"cmt">//--- 模式0:名称升序
   class="type">class="kw">string na = SymbolName(a, true), nb = SymbolName(b, true); class=class="str">"cmt">//--- 取显示名
   if (na > nb) do_swap = true; class=class="str">"cmt">//--- 前大于后则交换
  } else if (sortMode == class="num">1) { class=class="str">"cmt">//--- 模式1:波动率降序
   if (vol_array[a] < vol_array[b]) do_swap = true; class=class="str">"cmt">//--- 前小于后则交换
  } else if (sortMode == class="num">2) { class=class="str">"cmt">//--- 模式2:涨跌幅绝对值降序
   if (MathAbs(change_array[a]) < MathAbs(change_array[b])) do_swap = true; class=class="str">"cmt">//--- 前绝对值小则交换
  } else if (sortMode == class="num">3) { class=class="str">"cmt">//--- 模式3:RSI降序

◍ 排序后怎么把异动标的刷出来

冒泡排序跑完,indices 数组里已经是按 RSI 升序排好的标的序号。接下来这段就是把这些序号还原成可读行情,并给高波动、超买超卖标上颜色,直接挂到 MT5 主图文本层。 逐行看:外层 for 循环从 j=0 扫到 totalSymbols-1,用 i=indices[j] 取回真实序号;SymbolName(i,true) 拿带后缀的全名,bid_array / spread_array / change_array / vol_array / rsi_array 分别取五类数值。当前图标的 symbol 会用 ActiveColor 高亮,其余走 DataColor。 涨跌着色看 change:大于 0 时价格、变动值、向上箭头全染 UpColor;小于 0 则染 DownColor。波动率警报单独判 vol>Vol_Alert_Threshold,触发后 vol 文本和 symbol 文本转亮色,且 EnableAnimations 为真时让小布这类面板做脉冲闪烁。RSI 高于 70 染 DownColor(超买倾向回撤),低于 30 染 UpColor(超卖倾向反弹),中间灰显。 最后 createText 按列偏移写屏:Symbol_ 列 X_Offset+10,Bid_ 列加一个 cellWidth,Spread_ 列加两个 cellWidth。当前品种前加 "*" 前缀。外汇与贵金属点差随流动性跳变,高波动阈值设太低会满屏闪,建议先在 XAUUSD 的 M5 上把 Vol_Alert_Threshold 从默认调高 20% 验证。

MQL5 / C++
if (rsi_array[a] < rsi_array[b]) do_swap = true; class=class="str">"cmt">//--- Swap if needed
   }
   if (do_swap) {              class=class="str">"cmt">//--- Perform swap
      class="type">int temp = indices[j];   class=class="str">"cmt">//--- Temporary store
      indices[j] = indices[j + class="num">1]; class=class="str">"cmt">//--- Swap
      indices[j + class="num">1] = temp;   class=class="str">"cmt">//--- Complete swap
      swapped = true;          class=class="str">"cmt">//--- Set flag
   }
  }
}
class=class="str">"cmt">// Display sorted symbols with pulse on high vol
for (class="type">int j = class="num">0; j < totalSymbols; j++) { class=class="str">"cmt">//--- Iterate through sorted indices
   class="type">int i = indices[j];                   class=class="str">"cmt">//--- Get index
   class="type">class="kw">string symbol = SymbolName(i, true);  class=class="str">"cmt">//--- Get symbol
   class="type">class="kw">double bidPrice = bid_array[i];       class=class="str">"cmt">//--- Get bid
   class="type">long spread = spread_array[i];        class=class="str">"cmt">//--- Get spread
   class="type">class="kw">double change = change_array[i];      class=class="str">"cmt">//--- Get change
   class="type">class="kw">double vol = vol_array[i];            class=class="str">"cmt">//--- Get vol
   class="type">class="kw">double rsi = rsi_array[i];            class=class="str">"cmt">//--- Get RSI
   class="type">class="kw">color clr_s = (symbol == _Symbol) ? ActiveColor : DataColor; class=class="str">"cmt">//--- Set symbol class="type">class="kw">color
   class="type">class="kw">color clr_p = DataColor, clr_sp = DataColor, clr_ch = DataColor, clr_vol = DataColor, clr_rsi = DataColor; class=class="str">"cmt">//--- Set class="kw">default colors
   class="type">class="kw">color clr_a1 = DataColor, clr_a2 = DataColor; class=class="str">"cmt">//--- Set arrow colors
   class=class="str">"cmt">// Price Change
   if (change > class="num">0) {                     class=class="str">"cmt">//--- Check positive change
      clr_p = UpColor; clr_ch = UpColor; clr_a1 = UpColor; clr_a2 = DataColor; class=class="str">"cmt">//--- Set up colors
   } else if (change < class="num">0) {               class=class="str">"cmt">//--- Check negative change
      clr_p = DownColor; clr_ch = DownColor; clr_a1 = DataColor; clr_a2 = DownColor; class=class="str">"cmt">//--- Set down colors
   }
   class=class="str">"cmt">// Volatility Alert
   if (vol > Vol_Alert_Threshold) { class=class="str">"cmt">//--- Check high volatility
      clr_vol = UpColor;             class=class="str">"cmt">//--- Set vol class="type">class="kw">color
      clr_s = (symbol == _Symbol) ? ActiveColor : UpColor; class=class="str">"cmt">//--- Set symbol class="type">class="kw">color
   }
   class=class="str">"cmt">// RSI Color
   clr_rsi = (rsi > class="num">70 ? DownColor : (rsi < class="num">30 ? UpColor : DataColor)); class=class="str">"cmt">//--- Set RSI class="type">class="kw">color
   class=class="str">"cmt">// Update Texts
   class="type">class="kw">string displaySymbol = (symbol == _Symbol) ? "*" + symbol : symbol; class=class="str">"cmt">//--- Format display symbol
   createText("Symbol_" + IntegerToString(j), displaySymbol, X_Offset + class="num">10, y, clr_s, fontSize, FontType, vol > Vol_Alert_Threshold && EnableAnimations); class=class="str">"cmt">//--- Update symbol text
   createText("Bid_" + IntegerToString(j), Bid(symbol), X_Offset + class="num">10 + cellWidth, y, clr_p, fontSize, FontType, EnableAnimations); class=class="str">"cmt">//--- Update bid text
   createText("Spread_" + IntegerToString(j), Spread(symbol), X_Offset + class="num">10 + cellWidth * class="num">2, y, clr_sp, fontSize, FontType); class=class="str">"cmt">//--- Update spread text

多品种面板的文本刷新与点击交互

这段逻辑负责把每个交易品种的变动、成交量占比、RSI 数值和上下箭头逐格写进面板。change 与 vol 都按 %.2f%% 格式保留两位小数,RSI 用 %.1f 保留一位,箭头则借 Wingdings 字体把字符 236、238 渲染成上/下三角。 当 vol 超过 Vol_Alert_Threshold 时,除了文字高亮,还会对品种名调用 HolographicPulse 做脉冲发光;前提是 EnableAnimations 为真。每画完一行 y 累加 cellHeight,最后 ChartRedraw(0) 一次性重绘,避免逐对象闪烁。 面板不是死板的:OnChartEvent 里拦截 CHARTEVENT_OBJECT_CLICK,点 ToggleBtn 就翻转 dashboardVisible,先 DeleteAllObjects 清场,再决定重绘 InitDashboard 还是只留一个唤醒按钮。点 SwitchTFBtn 则进入周期切换分支,先用 currentIdx = -1 定位当前时间周期索引,便于后面轮询。 外汇与贵金属波动剧烈,Vol_Alert_Threshold 设太低可能让多数品种常亮,建议在 MT5 里先用 5% 试跑,再按品种特性下调。

MQL5 / C++
  createText("Change_" + IntegerToString(j), StringFormat("%.2f%%", change), X_Offset + class="num">10 + cellWidth * class="num">3, y, clr_ch, fontSize, FontType); class=class="str">"cmt">//--- Update change text
  createText("Vol_" + IntegerToString(j), StringFormat("%.2f%%", vol), X_Offset + class="num">10 + cellWidth * class="num">4, y, clr_vol, fontSize, FontType, vol > Vol_Alert_Threshold && EnableAnimations); class=class="str">"cmt">//--- Update vol text
  createText("RSI_" + IntegerToString(j), StringFormat("%.1f", rsi), X_Offset + class="num">10 + cellWidth * class="num">5, y, clr_rsi, fontSize, FontType, (rsi > class="num">70 || rsi < class="num">30) && EnableAnimations); class=class="str">"cmt">//--- Update RSI text
  createText("ArrowUp_" + IntegerToString(j), CharToString(class="num">236), X_Offset + class="num">10 + cellWidth * class="num">6, y, clr_a1, fontSize, "Wingdings"); class=class="str">"cmt">//--- Update up arrow
  createText("ArrowDown_" + IntegerToString(j), CharToString(class="num">238), X_Offset + class="num">10 + cellWidth * class="num">7, y, clr_a2, fontSize, "Wingdings"); class=class="str">"cmt">//--- Update down arrow
  class=class="str">"cmt">// Pulse on high volatility
  if (vol > Vol_Alert_Threshold) { class=class="str">"cmt">//--- Check high volatility
      HolographicPulse("Symbol_" + IntegerToString(j), clr_s, GlowColor); class=class="str">"cmt">//--- Pulse symbol text
  }
  y += cellHeight;                class=class="str">"cmt">//--- Update y-coordinate
}
ChartRedraw(class="num">0);                  class=class="str">"cmt">//--- Redraw chart
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Chart Event Handler                                              |
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) {
  if (id == CHARTEVENT_OBJECT_CLICK) { class=class="str">"cmt">//--- Handle click event
      if (sparam == "ToggleBtn") {     class=class="str">"cmt">//--- Check toggle button
          dashboardVisible = !dashboardVisible; class=class="str">"cmt">//--- Toggle visibility
          objManager.DeleteAllObjects(); class=class="str">"cmt">//--- Delete objects
          if (dashboardVisible) {      class=class="str">"cmt">//--- Check if visible
              InitDashboard();         class=class="str">"cmt">//--- Reinitialize dashboard
          } else {
              createButton("ToggleBtn", "TOGGLE DASHBOARD", X_Offset + class="num">10, Y_Offset + class="num">10, class="num">150, class="num">25, TitleColor, PanelColor, UpColor); class=class="str">"cmt">//--- Create toggle button
          }
      }
      else if (sparam == "SwitchTFBtn") { class=class="str">"cmt">//--- Check class="kw">switch TF button
          class="type">int currentIdx = -class="num">1;         class=class="str">"cmt">//--- Initialize current index

「按钮事件里的周期切换与排序逻辑」

这段事件处理代码承接了面板按钮的点击响应,核心做两件事:按预设周期数组循环切换当前图表周期,以及轮换四种排序模式。周期切换依赖 periods 数组与 _Period 的匹配,找到当前索引后加 1 取模得到下一周期,再用 ChartSetSymbolPeriod 切图。 for(int i=0;i<ArraySize(periods);i++){ if(periods[i]==_Period){ currentIdx=i; break; } } 这段就是线性扫描周期数组定位自身,数组不大时开销可忽略;nextIdx 用取模保证从末位跳回首位不越界。若 ChartSetSymbolPeriod 返回失败,会拼出函数名加错误码写日志,外汇与贵金属图表在极端报价中断时可能偶发切图失败,属正常容错。 排序按钮走 sortMode=(sortMode+1)%4 的四态循环,每次重绘按钮文字为 sortNames[sortMode]。最后无论哪个分支都把按钮 OBJPROP_STATE 复位并 ChartRedraw,避免按钮卡在按下态。我们已跑通整段逻辑,整合后的面板交互以单张 GIF 动图呈现了切换与排序的实际效果。

MQL5 / C++
    for (class="type">int i = class="num">0; i < ArraySize(periods); i++) { class=class="str">"cmt">//--- Find current timeframe
          if (periods[i] == _Period) { class=class="str">"cmt">//--- Match found
              currentIdx = i;          class=class="str">"cmt">//--- Set index
              class="kw">break;                   class=class="str">"cmt">//--- Exit loop
          }
      }
      class="type">int nextIdx = (currentIdx + class="num">1) % ArraySize(periods); class=class="str">"cmt">//--- Calculate next index
      if (!ChartSetSymbolPeriod(class="num">0, _Symbol, periods[nextIdx])) { class=class="str">"cmt">//--- Switch timeframe
          LogError(__FUNCTION__ + ": Failed to class="kw">switch timeframe, Error: " + IntegerToString(GetLastError())); class=class="str">"cmt">//--- Log error
      }
      createButton("SwitchTFBtn", "NEXT TF", X_Offset + class="num">170, (class="type">int)ObjectGetInteger(class="num">0, "SwitchTFBtn", OBJPROP_YDISTANCE), class="num">120, class="num">25, UpColor, PanelColor, UpColor, EnableAnimations); class=class="str">"cmt">//--- Update button
  }
  else if (sparam == "SortBtn") { class=class="str">"cmt">//--- Check sort button
      sortMode = (sortMode + class="num">1) % class="num">4; class=class="str">"cmt">//--- Cycle sort mode
      createButton("SortBtn", "SORT: " + sortNames[sortMode], X_Offset + class="num">300, (class="type">int)ObjectGetInteger(class="num">0, "SortBtn", OBJPROP_YDISTANCE), class="num">150, class="num">25, TitleColor, PanelColor, UpColor, EnableAnimations); class=class="str">"cmt">//--- Update button
  }
  ObjectSetInteger(class="num">0, sparam, OBJPROP_STATE, false); class=class="str">"cmt">//--- Reset button state
  ChartRedraw(class="num">0);                              class=class="str">"cmt">//--- Redraw chart
}

◍ 把仪表盘改成自己的盯盘副驾

这套动态全息仪表盘在 MT5 里跑起来后,核心不是炫技,而是把 RSI、波动率预警和多品种排序压进同一个交互界面。原工程文件 Holographic_Dashboard_EA.mq5 约 79.18 KB,用 CObjectManager 管对象生命周期,HolographicPulse 控脉冲动画,你下载后改两处参数就能适配自己的品种池。 实操上建议先关掉脉冲动画、只留数据排序,用一周时间观察它在欧美盘切换时的延迟;外汇和贵金属波动大、滑点高,任何视觉工具都只是辅助,信号漏看或误报都可能直接吃亏损。 真要落地,就把时间周期写死在输入参数里,别让面板自由切,避免重载卡顿。剩下的交互按钮你按手感调,工具顺手了,盘才盯得住。

常见问题

用矩形标签(RECTANGLE_LABEL)配合透明度与颜色叠加做脉冲动画,避免在每帧重绘全部对象,只在数值越界时刷新。
先按屏幕高度除以行数算出基础行高,再留 10–15 像素做分隔线;多周期列宽按品种名长度加指标值宽度预留,避免重叠。
可以,小布能按你设定的阈值扫描各品种各周期指标越界情况,并直接推送发光提醒,省去自己写动画面板的功夫。
在 OnInit 里遍历对象前缀批量删除同组标签与分隔线,再重建;释放指标句柄也放在反初始化里防止内存泄漏。
先确认品种数与周期数乘积未超平台单 EA 对象上限,再用二维数组按品种、周期下标创建,释放时倒序销毁即可。