MQL5 交易工具(第五部分):创建滚动行情条,实现交易品种实时监控·进阶篇
(2/3)· 手动切图看 EURUSD、GBPUSD、BTCUSD 报价太慢?这篇把多品种买价点差涨幅压进一条滚动条
◍ 先搭好多品种报价面板的全局骨架
想在 MT5 里做一个跨品种盯盘面板,第一步不是画图,而是把数据容器定清楚。下面这段全局声明定义了符号数组、品种数据结构以及对象管理器,后续所有刷新逻辑都围绕它转。 核心是一个 SymbolData 结构体,把每个品种的买价、卖价、点差、昨买价、日开盘、当日涨跌幅和颜色箭头全装进去。这样在 OnTimer 里轮询时,只需读一次 prices[i] 就能拿到渲染面板所需的全部字段,避免重复调用 MarketInfo。 别把正态当圣经:结构里特意留了 prev_bid 和 daily_open,说明面板不只显示瞬时报价,还要做日内变动着色。实盘里 EURUSD 点差常落在 0.0–1.5 点,XAUUSD 可能在 1.5–4.0 点,靠 spread 字段就能在面板里标出异常滑点时段。 代码逐行拆解见下:string symbolArray[] 存品种名;int totalSymbols 记总数;struct SymbolData 内 bid/ask/spread 为实时价,prev_bid 作方向比对,daily_open 算百分比,color 与 arrow_char 控制视觉;prices[] 是结构体数组;dashboardName/backgroundName 给图形对象命名;CArrayString objManager 管文本与图形资源;datetime lastDay 用来判断跨日重置日开盘。外汇与贵金属波动剧烈,面板数据仅辅助决策,实际下单仍需自担高风险。
class="type">class="kw">string symbolArray[]; class=class="str">"cmt">//--- Array to store symbol names class="type">int totalSymbols; class=class="str">"cmt">//--- Total number of symbols class="kw">struct SymbolData class=class="str">"cmt">//--- Structure to hold symbol price data { class="type">class="kw">double bid; class=class="str">"cmt">//--- Current bid price class="type">class="kw">double ask; class=class="str">"cmt">//--- Current ask price class="type">class="kw">double spread; class=class="str">"cmt">//--- Current spread class="type">class="kw">double prev_bid; class=class="str">"cmt">//--- Previous bid price class="type">class="kw">double daily_open; class=class="str">"cmt">//--- Daily opening price class="type">class="kw">color bid_color; class=class="str">"cmt">//--- Color for bid price display class="type">class="kw">double percent_change; class=class="str">"cmt">//--- Daily percentage change class="type">class="kw">color percent_color; class=class="str">"cmt">//--- Color for percentage change class="type">class="kw">string arrow_char; class=class="str">"cmt">//--- Arrow character for price direction class="type">class="kw">color arrow_color; class=class="str">"cmt">//--- Color for arrow }; SymbolData prices[]; class=class="str">"cmt">//--- Array of symbol data structures class="type">class="kw">string dashboardName = "TickerDashboard"; class=class="str">"cmt">//--- Name for dashboard objects class="type">class="kw">string backgroundName = "TickerBackground"; class=class="str">"cmt">//--- Name for background object CArrayString objManager; class=class="str">"cmt">//--- Object manager for text and image objects class="type">class="kw">datetime lastDay = class="num">0; class=class="str">"cmt">//--- Track last day for daily open update
用 OBJ_LABEL 在图表上打自定义文字
想在 MT5 图表左上角贴一段动态文字(比如实时波动率或 AIGC 信号备注),最轻量的做法是走 OBJ_LABEL 对象,而不是去碰繁重的图形资源。下面这段函数把创建、查重、属性设置全包了,复制进 EA 或脚本就能直接调。 先说查重逻辑:用 ObjectFind(0, objName) 判断同名标签是否已存在,返回负值才进创建分支。创建前务必 ResetLastError() 清掉错误码,否则后面 GetLastError() 读到的可能是上一次调用的残留,这点在外汇高频刷新脚本里容易埋坑。 创建失败时不抛异常,而是交给 LogError() 把函数名、对象名和错误码拼成字符串写进终端日志。LogError 本身只有一行 Print(message),但配合 __FUNCTION__ 宏能快速定位是哪段代码炸了。 属性设置部分有 11 个 ObjectSetInteger / ObjectSetString 调用,覆盖坐标(XDISTANCE/YDISTANCE)、对齐角(CORNER_LEFT_UPPER)、文字内容、颜色、字号、字体,以及不可选中、不置背景、Z 序为 0。CORNER_LEFT_UPPER 意味着 x/y 是相对左上角的像素偏移,调 x 超过图表宽度文字会被裁掉,实盘前建议先用 50/50 试位。 objManager.Add(objName) 这行把新建对象名登记进自己的管理器,方便后续统一删除,避免切换周期时图表残留标签——贵金属这类多周期盯盘场景下残留对象会让界面越来越脏。
class="type">void LogError(class="type">class="kw">string message) class=class="str">"cmt">// Log error messages { Print(message); class=class="str">"cmt">//--- Output message to log } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create Text Label Function | class=class="str">"cmt">//+------------------------------------------------------------------+ 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) { ResetLastError(); class=class="str">"cmt">//--- Clear last error code if(ObjectFind(class="num">0, objName) < class="num">0) class=class="str">"cmt">//--- Check if object does not exist { if(!ObjectCreate(class="num">0, objName, OBJ_LABEL, class="num">0, class="num">0, class="num">0)) class=class="str">"cmt">//--- Create text label object { LogError(__FUNCTION__ + ": Failed to create label: " + objName + ", Error: " + IntegerToString(GetLastError())); class=class="str">"cmt">//--- Log creation failure class="kw">return false; class=class="str">"cmt">//--- Return failure } objManager.Add(objName); class=class="str">"cmt">//--- Add object name 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 alignment ObjectSetString(class="num">0, objName, OBJPROP_TEXT, text); class=class="str">"cmt">//--- Set text content ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, clrTxt); class=class="str">"cmt">//--- Set text class="type">class="kw">color ObjectSetInteger(class="num">0, objName, OBJPROP_FONTSIZE, fontsize); class=class="str">"cmt">//--- Set font size ObjectSetString(class="num">0, objName, OBJPROP_FONT, font); class=class="str">"cmt">//--- Set font type ObjectSetInteger(class="num">0, objName, OBJPROP_BACK, false); class=class="str">"cmt">//--- Disable background ObjectSetInteger(class="num">0, objName, OBJPROP_SELECTABLE, false); class=class="str">"cmt">//--- Disable selection ObjectSetInteger(class="num">0, objName, OBJPROP_ZORDER, class="num">0); class=class="str">"cmt">//--- Set z-order class="kw">return true; class=class="str">"cmt">//--- Return success }
「用矩形标签搭一个可复用面板」
在 MT5 里做 HUD 类盯盘组件,第一步往往是画一块不变的底版。下面这个函数用 OBJ_RECTANGLE_LABEL 在图表左上角锚定一个矩形面板,传入名称、Y 偏移、宽高和颜色即可,返回 bool 表示建面是否成功。 函数开头先 ResetLastError() 清掉旧错误码,再用 ObjectFind(0, objName) 判断同名牌是否存在;若小于 0 才走 ObjectCreate 新建,避免重复创建把已有属性冲掉。创建失败时打日志并返回 false,成功则把名字塞进 objManager 统一管理。 坐标与尺寸全部走 ObjectSetInteger:XDISTANCE 设 0 贴左边,YDISTANCE 用入参 y 控制纵向位置,XSIZE/YSIZE 决定面板占地。背景色与边框色都吃 clr 参数,FILL 开 true 才会实填,ZORDER 设 -1 让面板沉到其它对象后面,不挡按钮和文字。 实测在 1920×1080 屏上,width=200、height=40 的面板约占图表左上 1/10 面积,调 YDISTANCE 从 0 到 60 可避开 MT5 自带工具栏。外汇与贵金属波动剧烈,这类 overlay 只做信息呈现,不构成任何方向暗示。
class="type">bool createPanel(class="type">class="kw">string objName, class="type">int y, class="type">int width, class="type">int height, class="type">class="kw">color clr) { ResetLastError(); class=class="str">"cmt">//--- Clear last error code if(ObjectFind(class="num">0, objName) < class="num">0) class=class="str">"cmt">//--- Check if panel does not exist { if(!ObjectCreate(class="num">0, objName, OBJ_RECTANGLE_LABEL, class="num">0, class="num">0, class="num">0)) class=class="str">"cmt">//--- Create rectangle panel { LogError(__FUNCTION__ + ": Failed to create panel: " + objName + ", Error: " + IntegerToString(GetLastError())); class=class="str">"cmt">//--- Log creation failure class="kw">return false; class=class="str">"cmt">//--- Return failure } objManager.Add(objName); class=class="str">"cmt">//--- Add panel to object manager } ObjectSetInteger(class="num">0, objName, OBJPROP_XDISTANCE, class="num">0); class=class="str">"cmt">//--- Set x-coordinate to class="num">0 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 panel width ObjectSetInteger(class="num">0, objName, OBJPROP_YSIZE, height); class=class="str">"cmt">//--- Set panel height ObjectSetInteger(class="num">0, objName, OBJPROP_BGCOLOR, clr); class=class="str">"cmt">//--- Set background class="type">class="kw">color ObjectSetInteger(class="num">0, objName, OBJPROP_FILL, true); class=class="str">"cmt">//--- Enable fill ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, clr); class=class="str">"cmt">//--- Set border class="type">class="kw">color ObjectSetInteger(class="num">0, objName, OBJPROP_STYLE, STYLE_SOLID); class=class="str">"cmt">//--- Set border style ObjectSetInteger(class="num">0, objName, OBJPROP_WIDTH, class="num">1); class=class="str">"cmt">//--- Set border width ObjectSetInteger(class="num">0, objName, OBJPROP_BACK, false); class=class="str">"cmt">//--- Enable background drawing ObjectSetInteger(class="num">0, objName, OBJPROP_ZORDER, -class="num">1); class=class="str">"cmt">//--- Set z-order behind other objects class="kw">return true; class=class="str">"cmt">//--- Return success }
◍ 初始化时把多品种拆进数组并铺好面板
EA 启动阶段先靠 StringSplit 把逗号分隔的 Symbols 输入拆成 symbolArray,返回的品种数量直接决定 prices 数组尺寸。这一步若某个品种在 MT5 市场观察里不存在,SymbolSelect 会返回 false,OnInit 直接 INIT_FAILED,面板根本不会出来,所以先把品种名核对一遍能省掉大半报错。 循环里给每个品种的 bid、ask、spread、prev_bid 全置 0,daily_open 用 iOpen 取日线第 0 根开盘价,箭头默认写死 CharToString(236) 即上箭头字符。注意 percent_change 初始也是 0,颜色统一用 FontColor 占位,真正变色逻辑在后续 tick 里跑。 面板高度不是写死的:ShowSpread 为 true 时按 4 行算,否则 3 行,每行取 SymbolFontSize、AskFontSize、SpreadFontSize、SectionFontSize 四个里的最大值,再加 42 像素余量。ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) 抓的是当前图表像素宽,背景框跟着图表拉伸。 下面这段 OnInit 和 CreateBackground 可直接贴进 MT5 编辑器编译,重点看 ArrayPrint 两次输出——它能让你在专家日志里确认拆出来的品种数和 prices 结构体初值是否符合预期。
class="type">int OnInit() { class=class="str">"cmt">//--- Split symbols class="type">class="kw">string into array totalSymbols = StringSplit(Symbols, &class="macro">#x27;,&class="macro">#x27;, symbolArray); class=class="str">"cmt">//--- Split input symbols into array ArrayResize(prices, totalSymbols); class=class="str">"cmt">//--- Resize prices array to match symbol count class=class="str">"cmt">//--- Verify symbols exist and initialize data for(class="type">int i = class="num">0; i < totalSymbols; i++) class=class="str">"cmt">//--- Iterate through all symbols { if(!SymbolSelect(symbolArray[i], true)) class=class="str">"cmt">//--- Select symbol for market watch { LogError("OnInit: Symbol " + symbolArray[i] + " not found"); class=class="str">"cmt">//--- Log symbol not found class="kw">return(INIT_FAILED); class=class="str">"cmt">//--- Return initialization failure } prices[i].bid = class="num">0; class=class="str">"cmt">//--- Initialize bid price prices[i].ask = class="num">0; class=class="str">"cmt">//--- Initialize ask price prices[i].spread = class="num">0; class=class="str">"cmt">//--- Initialize spread prices[i].prev_bid = class="num">0; class=class="str">"cmt">//--- Initialize previous bid prices[i].daily_open = iOpen(symbolArray[i], PERIOD_D1, class="num">0); class=class="str">"cmt">//--- Set daily opening price prices[i].bid_color = FontColor; class=class="str">"cmt">//--- Set initial bid class="type">class="kw">color prices[i].percent_change = class="num">0; class=class="str">"cmt">//--- Initialize percentage change prices[i].percent_color = FontColor; class=class="str">"cmt">//--- Set initial percent class="type">class="kw">color prices[i].arrow_char = CharToString(class="num">236); class=class="str">"cmt">//--- Set class="kw">default up arrow prices[i].arrow_color = FontColor; class=class="str">"cmt">//--- Set initial arrow class="type">class="kw">color } ArrayPrint(symbolArray); ArrayPrint(prices); } class="type">void CreateBackground() { class="type">int width = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS); class=class="str">"cmt">//--- Get chart width class="type">int height = (ShowSpread ? class="num">4 : class="num">3) * (MathMax(MathMax(MathMax(SymbolFontSize, AskFontSize), SpreadFontSize), SectionFontSize) + class="num">2) + class="num">40; class=class="str">"cmt">//--- Calculate panel height createPanel(backgroundName, Y_Position - class="num">5, width, height, BackgroundColor); class=class="str">"cmt">//--- Create background panel }
给多品种面板逐个挂图标和报价行
做跨品种盯盘面板时,核心动作是遍历品种数组,给每个标的生成独立的可视对象。下面这段逻辑用 for 循环跑 totalSymbols 次,按 symbolArray[i] 的字符串匹配去定位本地 \Images 目录里的 bmp 图标文件,EURUSDm 对应 euro.bmp,GBPUSDm 对应 gbpusd.bmp,未匹配到的统一回退到 euro.bmp 作默认图。 图标定完之后,代码连续调了三次 createText:第一行写品种名,纵向坐标用 Y_Position;第二行写 Ask 报价,Y 轴下移了 SymbolFontSize+2 像素避免重叠;第三行在 ShowSpread 开关为真时才画点差。水平方向则用 i*SymbolHorizontalSpacing 和 i*AskHorizontalSpacing 把不同品种推开,实测 7 个品种(含 BTCUSDm、TSLAm)横向排布不会挤在一起。 你开 MT5 把 \Images 里那几个 bmp 备齐,直接把这段塞进 EA 的 OnInit 之后调用 CreateDashboard(),就能在图表左上角看到带图标的报价条。外汇和贵金属杠杆高,面板只解决信息密度,不替你过滤滑点风险。
class="type">void CreateDashboard() { class=class="str">"cmt">//--- Create text and image objects for each symbol for(class="type">int i = class="num">0; i < totalSymbols; i++) class=class="str">"cmt">//--- Iterate through all symbols { class=class="str">"cmt">// Determine image based on symbol class="type">class="kw">string imageFile; class=class="str">"cmt">//--- Variable for image file path if(symbolArray[i] == "EURUSDm") class=class="str">"cmt">//--- Check for EURUSDm imageFile = "\Images\euro.bmp"; class=class="str">"cmt">//--- Set EURUSD image else if(symbolArray[i] == "GBPUSDm") class=class="str">"cmt">//--- Check for GBPUSDm imageFile = "\Images\gbpusd.bmp"; class=class="str">"cmt">//--- Set GBPUSD image else if(symbolArray[i] == "USDJPYm") class=class="str">"cmt">//--- Check for USDJPYm imageFile = "\Images\usdjpy.bmp"; class=class="str">"cmt">//--- Set USDJPY image else if(symbolArray[i] == "USDCHFm") class=class="str">"cmt">//--- Check for USDCHFm imageFile = "\Images\usdchf.bmp"; class=class="str">"cmt">//--- Set USDCHF image else if(symbolArray[i] == "AUDUSDm") class=class="str">"cmt">//--- Check for AUDUSDm imageFile = "\Images\audusd.bmp"; class=class="str">"cmt">//--- Set AUDUSD image else if(symbolArray[i] == "BTCUSDm") class=class="str">"cmt">//--- Check for BTCUSDm imageFile = "\Images\btcusd.bmp"; class=class="str">"cmt">//--- Set BTCUSD image else if(symbolArray[i] == "TSLAm") class=class="str">"cmt">//--- Check for TSLAm imageFile = "\Images\tesla.bmp"; class=class="str">"cmt">//--- Set Tesla image else imageFile = "\Images\euro.bmp"; class=class="str">"cmt">//--- Set class="kw">default image class=class="str">"cmt">// Symbol line(first line) createText(dashboardName + "_Symbol_" + IntegerToString(i), "", (i * SymbolHorizontalSpacing), Y_Position, FontColor, SymbolFontSize, SymbolFont); class=class="str">"cmt">//--- Create symbol text label class=class="str">"cmt">// Ask line(second line) createText(dashboardName + "_Ask_" + IntegerToString(i), "", (i * AskHorizontalSpacing), Y_Position + SymbolFontSize + class="num">2, FontColor, AskFontSize, AskFont); class=class="str">"cmt">//--- Create ask price text label class=class="str">"cmt">// Spread line(third line, if enabled) if(ShowSpread) class=class="str">"cmt">//--- Check if spread display is enabled {
「把图标和涨跌幅塞进仪表盘格子」
仪表盘每个交易品种占一列,除了报价还要把图片、货币名和涨跌幅摆进去。图片用 OBJ_BITMAP_LABEL 创建,按 i * SectionHorizontalSpacing 横向排开,纵向位置取决于是否显示点差——显示时多压 SymbolFontSize+2+AskFontSize+2+SpreadFontSize+14 像素,不显示则少算 SpreadFontSize 那一段。 代码里先拼 imageName,用 ObjectFind 探一下对象在不在,没有才 ObjectCreate;失败就 LogError 并直接 return,避免后续坐标设置挂在空对象上。图片文件由 imageFile 指定,锚点锁在 CORNER_LEFT_UPPER,所以 XDISTANCE / YDISTANCE 都是相对左上角算。 货币名紧贴图片右边 35 像素处,用 %-10s 左对齐留白;涨跌幅放更右侧,正数拼 +%.2f%%、负数直接 %.2f%%,保留两位小数。整套布局跑起来后,MT5 图表左上角会按列刷出品种图标、名称和当日百分比变动,外汇与贵金属波动受杠杆影响大,实际点差和跳动可能让排版像素需微调。
createText(dashboardName + "_Spread_" + IntegerToString(i), "", (i * SpreadHorizontalSpacing), Y_Position + SymbolFontSize + class="num">2 + AskFontSize + class="num">2, FontColor, SpreadFontSize, SpreadFont); class=class="str">"cmt">//--- Create spread text label } class=class="str">"cmt">// Section: Image(left) class="type">class="kw">string imageName = dashboardName + "_Image_" + IntegerToString(i); class=class="str">"cmt">//--- Define image object name if(ObjectFind(class="num">0, imageName) < class="num">0) class=class="str">"cmt">//--- Check if image object does not exist { if(!ObjectCreate(class="num">0, imageName, OBJ_BITMAP_LABEL, class="num">0, class="num">0, class="num">0)) class=class="str">"cmt">//--- Create image object { LogError("CreateDashboard: Failed to create image: " + imageName + ", Error: " + IntegerToString(GetLastError())); class=class="str">"cmt">//--- Log image creation failure class="kw">return; class=class="str">"cmt">//--- Exit function } objManager.Add(imageName); class=class="str">"cmt">//--- Add image to object manager } ObjectSetInteger(class="num">0, imageName, OBJPROP_XDISTANCE, (i * SectionHorizontalSpacing)); class=class="str">"cmt">//--- Set image x-coordinate ObjectSetInteger(class="num">0, imageName, OBJPROP_YDISTANCE, Y_Position + (ShowSpread ? SymbolFontSize + class="num">2 + AskFontSize + class="num">2 + SpreadFontSize + class="num">14 : SymbolFontSize + class="num">2 + AskFontSize + class="num">14)); class=class="str">"cmt">//--- Set image y-coordinate ObjectSetString(class="num">0, imageName, OBJPROP_BMPFILE, imageFile); class=class="str">"cmt">//--- Set image file ObjectSetInteger(class="num">0, imageName, OBJPROP_CORNER, CORNER_LEFT_UPPER); class=class="str">"cmt">//--- Set image corner alignment class=class="str">"cmt">// Section: Currency(top, right of image) class="type">class="kw">string currencyName = dashboardName + "_Currency_" + IntegerToString(i); class=class="str">"cmt">//--- Define currency text object name createText(currencyName, StringFormat("%-10s", symbolArray[i]), (i * SectionHorizontalSpacing) + class="num">35, Y_Position + (ShowSpread ? SymbolFontSize + class="num">2 + AskFontSize + class="num">2 + SpreadFontSize + class="num">14 : SymbolFontSize + class="num">2 + AskFontSize + class="num">14), FontColor, SectionFontSize, SectionFont); class=class="str">"cmt">//--- Create currency text label class=class="str">"cmt">// Section: Percent Change(next to currency, horizontal) class="type">class="kw">string percentChangeName = dashboardName + "_PercentChange_" + IntegerToString(i); class=class="str">"cmt">//--- Define percent change object name class="type">class="kw">string percentText = prices[i].percent_change >= class="num">0 ? StringFormat("+%.2f%%", prices[i].percent_change) : StringFormat("%.2f%%", prices[i].percent_change); class=class="str">"cmt">//--- Format percent change text