MQL5交易工具(第六部分):带脉冲动画与控件的动态全息仪表盘·进阶篇
📡

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

(2/3)· 还在手动切图表看RSI和波动率?这篇把多周期多品种浓缩进一块会呼吸的面板

含代码示例 第 2/3 篇
把十几个品种挨个切周期看RSI,是多数人的日常也最耗注意力。面板不联动、没预警,行情跳空时你往往在翻到最后一张图才反应过来。外汇贵金属高杠杆下,这种延迟可能直接吃掉计划内的止损空间。

仪表盘底层的数组与对象管家

做多品种全息仪表盘,第一步是把跨品种、跨周期的状态塞进统一的内存结构。下面这组全局声明直接决定了后面能排多少列、能切几种排序:7 个周期(M1 到 W1)写死在 periods[] 里,排序模式锁了 4 种——名称升序、成交量降序、涨跌幅绝对值降序、RSI 降序。 sortMode 默认 0,意味着面板刚画出来时按品种名排;想一眼抓异动,把 sortMode 改成 2 或 3 就行。ATR 和 RSI 的 handle 分开按品种和周期存,是为了避免每次重绘都重新 iATR / iRSI 建句柄,MT5 里句柄泄漏会让 EA 跑几天就卡死。 CObjectManager 这个类看着简单,却是面板不花屏的关键。它继承 CArrayString,AddObject 往里塞标签名,DeleteAllObjects 倒序遍历并把图表上真实存在的图形对象干掉——倒序删是因为 CArrayString 中间抽掉元素会移位,正序删会漏。 外汇和贵金属点差跳变快,这种面板只适合在策略测试器或实时盯盘辅助里用,直接跟单风险极高。把下面代码贴进 MT5 的 MQH 或 EA 头部,编译后看 totalSymbols 和 dashboardVisible 默认值,就能确认你的品种池有没有被市场报价窗口完整加载。

MQL5 / C++
class="type">long spread_array[];                    class=class="str">"cmt">//--- Array for spreads
class="type">class="kw">double change_array[];                 class=class="str">"cmt">//--- Array for percentage changes
class="type">class="kw">double vol_array[];                    class=class="str">"cmt">//--- Array for volumes
class="type">class="kw">double rsi_array[];                    class=class="str">"cmt">//--- Array for RSI values
class="type">int indices[];                        class=class="str">"cmt">//--- Array for sorted indices
ENUM_TIMEFRAMES periods[] = {PERIOD_M1, PERIOD_M5, PERIOD_H1, PERIOD_H2, PERIOD_H4, PERIOD_D1, PERIOD_W1}; class=class="str">"cmt">//--- Array of timeframes
class="type">class="kw">string logFileName = "Holographic_Dashboard_Log.txt"; class=class="str">"cmt">//--- Log file name
class="type">int sortMode = class="num">0;                     class=class="str">"cmt">//--- Current sort mode
class="type">class="kw">string sortNames[] = {"Name ASC", "Vol DESC", "Change ABS DESC", "RSI DESC"}; class=class="str">"cmt">//--- Sort mode names
class="type">int atr_handles_sym[];                class=class="str">"cmt">//--- ATR handles for symbols
class="type">int rsi_handles_sym[];                class=class="str">"cmt">//--- RSI handles for symbols
class="type">int atr_handles_tf[];                 class=class="str">"cmt">//--- ATR handles for timeframes
class="type">int rsi_handles_tf[];                 class=class="str">"cmt">//--- RSI handles for timeframes
class="type">int totalSymbols;                     class=class="str">"cmt">//--- Total number of symbols
class="type">bool dashboardVisible = true;         class=class="str">"cmt">//--- Dashboard visibility flag

class=class="str">"cmt">// Object Manager Class
class CObjectManager : class="kw">public CArrayString {
class="kw">public:
   class="type">void AddObject(class="type">class="kw">string name) {      class=class="str">"cmt">//--- Add object name to manager
      if (!Add(name)) {               class=class="str">"cmt">//--- Check if add failed
         LogError(__FUNCTION__ + ": Failed to add object name: " + name); class=class="str">"cmt">//--- Log error
      }
   }

   class="type">void DeleteAllObjects() {          class=class="str">"cmt">//--- Delete all managed objects
      for (class="type">int i = Total() - class="num">1; i >= class="num">0; i--) { class=class="str">"cmt">//--- Iterate through objects
         class="type">class="kw">string name = At(i);         class=class="str">"cmt">//--- Get object name
         if (ObjectFind(class="num">0, name) >= class="num">0) { class=class="str">"cmt">//--- Check if object exists
            if (!ObjectDelete(class="num">0, name)) { class=class="str">"cmt">//--- Delete object
               LogError(__FUNCTION__ + ": Failed to class="kw">delete object: " + name + ", Error: " + IntegerToString(GetLastError())); class=class="str">"cmt">//--- Log deletion failure
            }
         }
      }
   }
};

「清理图形对象与报价读取的工具函数」

在 MT5 的 EA 或指标里,动态管理图表对象容易留下垃圾图形。上面这段把失效对象从数组移除后立刻调用 ChartRedraw(0),能让当前图表强制重绘,避免残影影响价格行为判断。 对象管理器实例 objManager 配合 Delete(i) 的循环,适合用来批量清理自建的水平线、箭头。若你做贵金属 XAUUSD 的多周期标注,建议每次 OnDeinit 前跑一遍,否则切换周期时可能堆积几十个隐藏对象。 报价获取封装成了 Ask / Bid / Spread 三个函数。Ask() 用 SymbolInfoDouble 取 SYMBOL_ASK,再用 SymbolInfoInteger 拿 SYMBOL_DIGITS 决定小数位,DoubleToString 格式化输出;取不到就走 LogError 写公共目录日志并返回 N/A。外汇和贵金属杠杆高,报价失败若不及时暴露,脚本可能以错误价格挂单。

LogError 用 CFileTxt 以 FILE_WRITEFILE_TXTFILE_COMMON 开公共日志,追加写入后关闭,同时 Print 到终端。这样回测或实盘断线时,你能从公共文件夹定位是哪一行取价失败,而不是盲猜。
MQL5 / C++
      Delete(i);                     class=class="str">"cmt">//--- Remove from array
      }
      ChartRedraw(class="num">0);                class=class="str">"cmt">//--- Redraw chart
   }
};
CObjectManager objManager;           class=class="str">"cmt">//--- Object manager instance
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Utility Functions                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void LogError(class="type">class="kw">string message) {
   CFileTxt file;                    class=class="str">"cmt">//--- Create file object
   if (file.Open(logFileName, FILE_WRITE | FILE_TXT | FILE_COMMON, true) >= class="num">0) { class=class="str">"cmt">//--- Open log file
      file.WriteString(message + "\n"); class=class="str">"cmt">//--- Write message
      file.Close();                  class=class="str">"cmt">//--- Close file
   }
   Print(message);                   class=class="str">"cmt">//--- Print message
}
class="type">class="kw">string Ask(class="type">class="kw">string symbol) {
   class="type">class="kw">double value;                     class=class="str">"cmt">//--- Variable for ask price
   if (SymbolInfoDouble(symbol, SYMBOL_ASK, value)) { class=class="str">"cmt">//--- Get ask price
      class="kw">return DoubleToString(value, (class="type">int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)); class=class="str">"cmt">//--- Return formatted ask
   }
   LogError(__FUNCTION__ + ": Failed to get ask price for " + symbol + ", Error: " + IntegerToString(GetLastError())); class=class="str">"cmt">//--- Log error
   class="kw">return "N/A";                     class=class="str">"cmt">//--- Return N/A on failure
}
class="type">class="kw">string Bid(class="type">class="kw">string symbol) {
   class="type">class="kw">double value;                     class=class="str">"cmt">//--- Variable for bid price
   if (SymbolInfoDouble(symbol, SYMBOL_BID, value)) { class=class="str">"cmt">//--- Get bid price
      class="kw">return DoubleToString(value, (class="type">int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)); class=class="str">"cmt">//--- Return formatted bid
   }
   LogError(__FUNCTION__ + ": Failed to get bid price for " + symbol + ", Error: " + IntegerToString(GetLastError())); class=class="str">"cmt">//--- Log error
   class="kw">return "N/A";                     class=class="str">"cmt">//--- Return N/A on failure
}
class="type">class="kw">string Spread(class="type">class="kw">string symbol) {
   class="type">long value;                       class=class="str">"cmt">//--- Variable for spread
   if (SymbolInfoInteger(symbol, SYMBOL_SPREAD, value)) { class=class="str">"cmt">//--- Get spread

◍ 辅助函数怎样撑起面板渲染

上面这段是指标里几个不起眼但缺了就跑不起来的辅助函数。先说字符串处理:PercentChange 用 ((current - previous) / previous) * 100 算百分比,previous 为 0 时直接返回 "0.00%",避免除零崩脚本;TruncPeriod 从 ENUM_TIMEFRAMES 枚举字符串第 7 位截起,把 PERIOD_M15 变成 M15 这类短名,省面板空间。 HolographicPulse 是纯视觉脉冲:靠 EnableAnimations 开关控制,循环 PulseCycles 次,每次先涂 glowClr 再涂 mainClr,中间用 Sleep(delay) 和 Sleep(delay/2) 拉出呼吸节奏。注意 Sleep 会卡主线程,PulseCycles 设 10、AnimationSpeed 设 100 时单次脉冲就吃掉约 1.5 秒主线程时间,盯盘时若觉卡顿优先调小这俩参数。 createText 负责往图表塞文字对象,开头 ResetLastError() 清错误码是好习惯——后面若对象创建失败,GetLastError 才能拿到干净的错误号而不是被别处污染。外汇和贵金属波动快、点差跳变频繁,这类面板刷新逻辑建议先在模拟盘验证再上实盘,杠杆品种高风险,参数没调稳可能误读信号。

MQL5 / C++
class="type">class="kw">string PercentChange(class="type">class="kw">double current, class="type">class="kw">double previous) {
  if (previous == class="num">0) class="kw">return "class="num">0.00%"; class=class="str">"cmt">//--- Handle zero previous value
  class="kw">return StringFormat("%.2f%%", ((current - previous) / previous) * class="num">100); class=class="str">"cmt">//--- Calculate and format percentage
}
class="type">class="kw">string TruncPeriod(ENUM_TIMEFRAMES period) {
  class="kw">return StringSubstr(EnumToString(period), class="num">7); class=class="str">"cmt">//--- Truncate timeframe class="type">class="kw">string
}
class="type">void HolographicPulse(class="type">class="kw">string objName, class="type">class="kw">color mainClr, class="type">class="kw">color glowClr) {
  if (!EnableAnimations) class="kw">return;     class=class="str">"cmt">//--- Exit if animations disabled
  class="type">int cycles = PulseCycles;          class=class="str">"cmt">//--- Set pulse cycles
  class="type">int delay = AnimationSpeed;        class=class="str">"cmt">//--- Set animation delay
  for (class="type">int i = class="num">0; i < cycles; i++) { class=class="str">"cmt">//--- Iterate through cycles
    ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, glowClr); class=class="str">"cmt">//--- Set glow class="type">class="kw">color
    ChartRedraw(class="num">0);                  class=class="str">"cmt">//--- Redraw chart
    Sleep(delay);                    class=class="str">"cmt">//--- Delay
    ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, mainClr); class=class="str">"cmt">//--- Set main class="type">class="kw">color
    ChartRedraw(class="num">0);                  class=class="str">"cmt">//--- Redraw chart
    Sleep(delay / class="num">2);                class=class="str">"cmt">//--- Shorter delay
  }
}
class="type">bool createText(class="type">class="kw">string objName, class="type">class="kw">string text, class="type">int x, class="type">int y, class="type">class="kw">color clrTxt, class="type">int fontsize, class="type">class="kw">string font, class="type">bool animate = false, class="type">class="kw">double opacity = class="num">1.0) {
  ResetLastError();                  class=class="str">"cmt">//--- Reset error code

用 OBJ_LABEL 在图表左上角贴文字的坑

想在 MT5 图表上动态贴一段提示文字,最轻量的做法是直接用 OBJ_LABEL 对象,而不是去搞面板。下面这段逻辑先查对象是否存在,不存在就建,建完统一设坐标、文字、字体和层级,最后按需做一次 50 毫秒的闪色动画。 坐标体系用的是 CORNER_LEFT_UPPER 加 XDISTANCE / YDISTANCE 像素偏移,这意味着你写的 x、y 是相对左上角的绝对像素,不会跟着价格轴跑。做外汇或贵金属盯盘时,这种标签适合放固定位的状态行,但注意高波动品种快速刷新可能让标签闪烁。 ZORDER 那行有个细节:对象名里带 "Glow" 的会被压到 -1 层,其余是 0 层。如果你后面自己扩展发光描边标签,命名里不加 Glow 就会盖不住主文字,调试时容易以为是颜色没设对。 动画分支里先临时换成 DataColor 再 Sleep(50) 然后还原 clrTxt,本质是借 ChartRedraw 强制重绘做肉眼可见的「闪一下」。实盘里别把 EnableAnimations 常开,每次刷新都 Sleep 会拖慢 OnTick 节奏。

MQL5 / C++
if (ObjectFind(class="num">0, objName) < class="num">0) { class=class="str">"cmt">//--- Check if object exists
      if (!ObjectCreate(class="num">0, objName, OBJ_LABEL, class="num">0, class="num">0, class="num">0)) { class=class="str">"cmt">//--- Create label
         LogError(__FUNCTION__ + ": Failed to create label: " + objName + ", Error: " + IntegerToString(GetLastError())); class=class="str">"cmt">//--- Log error
         class="kw">return false; class=class="str">"cmt">//--- Return failure
      }
      objManager.AddObject(objName); class=class="str">"cmt">//--- Add to manager
   }
   ObjectSetInteger(class="num">0, objName, OBJPROP_XDISTANCE, x); class=class="str">"cmt">//--- Set x-coordinate
   ObjectSetInteger(class="num">0, objName, OBJPROP_YDISTANCE, y); class=class="str">"cmt">//--- Set y-coordinate
   ObjectSetInteger(class="num">0, objName, OBJPROP_CORNER, CORNER_LEFT_UPPER); class=class="str">"cmt">//--- Set corner
   ObjectSetString(class="num">0, objName, OBJPROP_TEXT, text); class=class="str">"cmt">//--- Set text
   ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, clrTxt); class=class="str">"cmt">//--- Set class="type">class="kw">color
   ObjectSetInteger(class="num">0, objName, OBJPROP_FONTSIZE, fontsize); class=class="str">"cmt">//--- Set font size
   ObjectSetString(class="num">0, objName, OBJPROP_FONT, font); class=class="str">"cmt">//--- Set font
   ObjectSetInteger(class="num">0, objName, OBJPROP_BACK, false); class=class="str">"cmt">//--- Set foreground
   ObjectSetInteger(class="num">0, objName, OBJPROP_SELECTABLE, false); class=class="str">"cmt">//--- Disable selection
   ObjectSetInteger(class="num">0, objName, OBJPROP_ZORDER, StringFind(objName, "Glow") >= class="num">0 ? -class="num">1 : class="num">0); class=class="str">"cmt">//--- Set z-order
   if (animate && EnableAnimations) { class=class="str">"cmt">//--- Check for animation
      ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, DataColor); class=class="str">"cmt">//--- Set temporary class="type">class="kw">color
      ChartRedraw(class="num">0); class=class="str">"cmt">//--- Redraw chart
      Sleep(class="num">50); class=class="str">"cmt">//--- Delay
      ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, clrTxt); class=class="str">"cmt">//--- Set final class="type">class="kw">color
   }
   ChartRedraw(class="num">0); class=class="str">"cmt">//--- Redraw chart
   class="kw">return true; class=class="str">"cmt">//--- Return success
}

「在 MT5 图表上用代码画一个可交互按钮」

想给盯盘面板加自定义按钮,核心是先判重再建对象,避免重复创建把图表对象树搞乱。下面这段函数把坐标、尺寸、配色、字体一次性设完,还顺手处理了动画闪烁。 函数开头用 ResetLastError 清掉旧错误码,再用 ObjectFind 查对象名是否存在;不存在才走 ObjectCreate 建 OBJ_BUTTON,失败就写日志返回 false。建完立刻丢进 objManager 做生命周期管理,防止脚本退出后按钮成孤儿。 坐标和尺寸全靠 OBJPROP_XDISTANCE / YDISTANCE / XSIZE / YSIZE 四个整型属性定位,文本和三种颜色走 ObjectSetString 与 ObjectSetInteger。注意字体大小那行:名字里带 SwitchTFBtn 的按钮会多撑 3 号字(BaseFontSize+3),其余用基准值,这是区分周期切换键和普通键的土办法。 动画分支只在 animate 且 EnableAnimations 为真时生效:先刷成 clrLightGray,ChartRedraw 重绘,Sleep(50) 撑 50 毫秒再还原底色,制造一次点击反馈。外汇与贵金属波动剧烈、杠杆风险高,这类 UI 只辅助看盘,不替代风控。

MQL5 / C++
class="type">bool createButton(class="type">class="kw">string objName, class="type">class="kw">string text, class="type">int x, class="type">int y, class="type">int width, class="type">int height, class="type">class="kw">color textColor, class="type">class="kw">color bgColor, class="type">class="kw">color borderColor, class="type">bool animate = false) {
   ResetLastError();                      class=class="str">"cmt">//--- Reset error code
   if (ObjectFind(class="num">0, objName) < class="num">0) {      class=class="str">"cmt">//--- Check if object exists
      if (!ObjectCreate(class="num">0, objName, OBJ_BUTTON, class="num">0, class="num">0, class="num">0)) { class=class="str">"cmt">//--- Create button
         LogError(__FUNCTION__ + ": Failed to create button: " + objName + ", Error: " + IntegerToString(GetLastError())); class=class="str">"cmt">//--- Log error
         class="kw">return false;                    class=class="str">"cmt">//--- Return failure
      }
      objManager.AddObject(objName);      class=class="str">"cmt">//--- Add to manager
   }
   ObjectSetInteger(class="num">0, objName, OBJPROP_XDISTANCE, x); class=class="str">"cmt">//--- Set x-coordinate
   ObjectSetInteger(class="num">0, objName, OBJPROP_YDISTANCE, y); class=class="str">"cmt">//--- Set y-coordinate
   ObjectSetInteger(class="num">0, objName, OBJPROP_XSIZE, width); class=class="str">"cmt">//--- Set width
   ObjectSetInteger(class="num">0, objName, OBJPROP_YSIZE, height); class=class="str">"cmt">//--- Set height
   ObjectSetString(class="num">0, objName, OBJPROP_TEXT, text); class=class="str">"cmt">//--- Set text
   ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, textColor); class=class="str">"cmt">//--- Set text class="type">class="kw">color
   ObjectSetInteger(class="num">0, objName, OBJPROP_BGCOLOR, bgColor); class=class="str">"cmt">//--- Set background class="type">class="kw">color
   ObjectSetInteger(class="num">0, objName, OBJPROP_BORDER_COLOR, borderColor); class=class="str">"cmt">//--- Set border class="type">class="kw">color
   ObjectSetInteger(class="num">0, objName, OBJPROP_FONTSIZE, BaseFontSize + (StringFind(objName, "SwitchTFBtn") >= class="num">0 ? class="num">3 : class="num">0)); class=class="str">"cmt">//--- Set font size
   ObjectSetString(class="num">0, objName, OBJPROP_FONT, FontType); class=class="str">"cmt">//--- Set font
   ObjectSetInteger(class="num">0, objName, OBJPROP_ZORDER, class="num">1); class=class="str">"cmt">//--- Set z-order
   ObjectSetInteger(class="num">0, objName, OBJPROP_STATE, false); class=class="str">"cmt">//--- Reset state
   if (animate && EnableAnimations) {     class=class="str">"cmt">//--- Check for animation
      ObjectSetInteger(class="num">0, objName, OBJPROP_BGCOLOR, clrLightGray); class=class="str">"cmt">//--- Set temporary background
      ChartRedraw(class="num">0);                     class=class="str">"cmt">//--- Redraw chart
      Sleep(class="num">50);                          class=class="str">"cmt">//--- Delay
      ObjectSetInteger(class="num">0, objName, OBJPROP_BGCOLOR, bgColor); class=class="str">"cmt">//--- Set final background
   }

◍ 用矩形标签搭可复用面板

在 MT5 里做 HUD 类界面,最稳的载体是 OBJ_RECTANGLE_LABEL。它不绑定时间轴,用 XDISTANCE / YDISTANCE 以像素定位,适合挂单价面板、波动读数条这类浮层。 createPanel 先 ResetLastError 清掉旧错误码,再用 ObjectFind 判对象是否存在;不存在才 ObjectCreate,失败就走 LogError 并返回 false,避免重复创建把图表对象池拖垮。 坐标与尺寸全靠 ObjectSetInteger 写:x/y 是距图表左上角像素,width/height 决定碰撞范围,BGCOLOR 填背景,BORDER_FLAT 去掉立体边,ZORDER 设 -1 把面板压到 K 线下方。最后 ChartRedraw(0) 强制重绘,返回 true 表示落盘成功。 外汇与贵金属杠杆高,这类面板只做信息呈现,别把它当成信号确认;参数 x/y/width 建议先在 1920×1080 屏上测,再按 DPI 缩放,否则小布盯盘时容易偏出可视区。

MQL5 / C++
class="type">bool createPanel(class="type">class="kw">string objName, class="type">int x, class="type">int y, class="type">int width, class="type">int height, class="type">class="kw">color clr, class="type">class="kw">double opacity = class="num">1.0) {
   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 panel: " + objName + ", Error: " + IntegerToString(GetLastError()));
         class="kw">return false;
      }
      objManager.AddObject(objName);
   }
   ObjectSetInteger(class="num">0, objName, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(class="num">0, objName, OBJPROP_YDISTANCE, y);
   ObjectSetInteger(class="num">0, objName, OBJPROP_XSIZE, width);
   ObjectSetInteger(class="num">0, objName, OBJPROP_YSIZE, height);
   ObjectSetInteger(class="num">0, objName, OBJPROP_BGCOLOR, clr);
   ObjectSetInteger(class="num">0, objName, OBJPROP_BORDER_TYPE, BORDER_FLAT);
   ObjectSetInteger(class="num">0, objName, OBJPROP_ZORDER, -class="num">1);
   ChartRedraw(class="num">0);
   class="kw">return true;
}
让小布替你跑这套
这些多品种RSI与ATR波动率的诊断小布盯盘已内置,打开对应品种页即可看到脉冲式预警,你只需把决策留给自己的交易逻辑。

常见问题

通常用数组存symbol列表,循环调用iRSI与iATR拿到句柄并存入句柄数组,后续在刷新循环里按索引取缓冲区数据,避免重复创建。
多基于OnTimer或自定义计时变量控制重绘间隔,用透明度或外发光颜色周期性变化模拟脉冲,速度参数建议30ms级以免占用主线程。
可以,小布盯盘的品种页已集成类似ATR波动率与RSI的分屏监控及预警,不必自己写EA也能获得同效看盘体验。
倾向按波动率阈值或RSI偏离中线幅度排序,能更快暴露异动品种;也可做成按钮切换排序键,适配不同市况。
集中刷新能降低重绘冲突与资源抖动,保证多控件状态一致,也方便用单一计时器统一驱动动画与数据拉取。