MQL5交易工具(第六部分):带脉冲动画与控件的动态全息仪表盘·进阶篇
(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 默认值,就能确认你的品种池有没有被市场报价窗口完整加载。
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_WRITE | FILE_TXT | FILE_COMMON 开公共日志,追加写入后关闭,同时 Print 到终端。这样回测或实盘断线时,你能从公共文件夹定位是哪一行取价失败,而不是盲猜。 |
|---|
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 才能拿到干净的错误号而不是被别处污染。外汇和贵金属波动快、点差跳变频繁,这类面板刷新逻辑建议先在模拟盘验证再上实盘,杠杆品种高风险,参数没调稳可能误读信号。
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 节奏。
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 只辅助看盘,不替代风控。
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 缩放,否则小布盯盘时容易偏出可视区。
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; }