MQL5交易工具(第六部分):带脉冲动画与控件的动态全息仪表盘·综合运用
◍ 用矩形标签画出发光指示线
在 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 线空间,实盘前先在策略测试器里拖几遍窗口宽度验证。
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 拼出 "_Symbol | TF: _Period",比如跑在 XAUUSD 的 H1 上会显示 "XAUUSD | TF: 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%" 占位,后续刷新函数再填真实值。外汇与贵金属波动剧烈,面板数值仅作概率参考,实盘请自行验证。
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 主线程卡顿。
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 市场报价里是否真的有符号,这属于高风险的终端配置疏忽。
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 周期若设太短,面板报警频率可能偏高,实盘前建议在策略测试器里跑一遍多品种回放。
} 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 策略测试器跑一轮观察资源占用。
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 或独立指标里验证。
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 列位置。
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 看前三个符号怎么变。小布盯盘的多品种面板就是这套思路,你改一处枚举就能换排序主轴。
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% 验证。
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% 试跑,再按品种特性下调。
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 动图呈现了切换与排序的实际效果。
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 控脉冲动画,你下载后改两处参数就能适配自己的品种池。 实操上建议先关掉脉冲动画、只留数据排序,用一周时间观察它在欧美盘切换时的延迟;外汇和贵金属波动大、滑点高,任何视觉工具都只是辅助,信号漏看或误报都可能直接吃亏损。 真要落地,就把时间周期写死在输入参数里,别让面板自由切,避免重载卡顿。剩下的交互按钮你按手感调,工具顺手了,盘才盯得住。