MQL5 交易工具(第五部分):创建滚动行情条,实现交易品种实时监控·综合运用
📘

MQL5 交易工具(第五部分):创建滚动行情条,实现交易品种实时监控·综合运用

第 3/3 篇

「面板标签与自适应的绘制落点」

多货币 dashboard 的每段横向区块,靠 i * SectionHorizontalSpacing 控制水平位移,文本纵坐标则根据是否显示点差动态叠加:ShowSpread 为真时,基准 Y 额外多出 SpreadFontSize + 2 像素,否则少算这一段。百分比标签固定在横向偏移 105 处,箭头用 Wingdings 字体画在偏移 35 处,Bid 价文本从偏移 50 开始,三者纵向均落在基准 Y 再叠加 SectionFontSize + 2 的位置。 后台 UpdateBackground() 里面板高度不是写死的:ShowSpread 为真取 4 层字号最大值加 2 再 +40,否则取 3 层,这意味着关闭点差显示能直接砍掉一整行高度。宽度则实时读 ChartGetInteger(0, CHART_WIDTH_IN_PIXELS),随窗口缩放走。 想验证这套坐标逻辑,把 SectionHorizontalSpacing 从默认改到 200,重加载指标,看各货币块是否横向拉开;再把 ShowSpread 切 false,面板底部会肉眼可见地上缩一行。外汇与贵金属波动剧烈,此类 HUD 仅作盘口速读,不构成方向判断。

MQL5 / C++
createText(percentChangeName, percentText, (i * SectionHorizontalSpacing) + class="num">105, Y_Position + (ShowSpread ? SymbolFontSize + class="num">2 + AskFontSize + class="num">2 + SpreadFontSize + class="num">14 : SymbolFontSize + class="num">2 + AskFontSize + class="num">14), prices[i].percent_color, SectionFontSize, SectionFont); class=class="str">"cmt">//--- Create percent change text label

class=class="str">"cmt">// Section: Arrow(below currency, right of image, Wingdings)
class="type">class="kw">string arrowName = dashboardName + "_Arrow_" + IntegerToString(i); class=class="str">"cmt">//--- Define arrow object name
createText(arrowName, prices[i].arrow_char, (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) + SectionFontSize + class="num">2, prices[i].arrow_color, SectionFontSize, "Wingdings"); class=class="str">"cmt">//--- Create arrow text label

class=class="str">"cmt">// Section: Bid Price(next to arrow, horizontal)
class="type">class="kw">string bidName = dashboardName + "_Bid_" + IntegerToString(i); class=class="str">"cmt">//--- Define bid price object name
createText(bidName, StringFormat("%.5f", prices[i].bid), (i * SectionHorizontalSpacing) + class="num">50, Y_Position + (ShowSpread ? SymbolFontSize + class="num">2 + AskFontSize + class="num">2 + SpreadFontSize + class="num">14 : SymbolFontSize + class="num">2 + AskFontSize + class="num">14) + SectionFontSize + class="num">2, prices[i].bid_color, SectionFontSize, SectionFont); class=class="str">"cmt">//--- Create bid price text label
   }
}

class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Update background function                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void UpdateBackground()
{
   class="type">int width = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS); class=class="str">"cmt">//--- Get current 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">//--- Recalculate panel height
   ObjectSetInteger(class="num">0, backgroundName, OBJPROP_XSIZE, width);  class=class="str">"cmt">//--- Update panel width
   ObjectSetInteger(class="num">0, backgroundName, OBJPROP_YSIZE, height); class=class="str">"cmt">//--- Update panel height
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Update dashboard function                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void UpdateDashboard()
{
   class="kw">static class="type">class="kw">double symbolOffset = class="num">0;       class=class="str">"cmt">//--- Track symbol line offset
   class="kw">static class="type">class="kw">double askOffset = class="num">0;          class=class="str">"cmt">//--- Track ask line offset
   class="kw">static class="type">class="kw">double spreadOffset = class="num">0;       class=class="str">"cmt">//--- Track spread line offset
   class="kw">static class="type">class="kw">double sectionOffset = class="num">0;      class=class="str">"cmt">//--- Track section offset

多品种面板的横向排布与越界回收

做跨品种盯盘面板时,最容易被忽略的是横向像素分配。上面这段代码把每个品种的显示列宽拆成四组:symbol、ask、spread、section,分别乘以品种数得到总宽。以 20 个品种、各间距 60 像素算,单 symbol 行就占 1200 像素,远超多数笔记本的图表可视区。 循环里对每个品种算 x 坐标,一旦小于负间距就加总宽「绕回」左侧,相当于把超出右边界的对象从左边重新进场。配合 ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) 取到的右边界,用 OBJPROP_HIDDEN 把屏幕外的文本直接隐藏,避免 MT5 对象树里堆出几百个不可见标签拖慢刷新。 注意 ask 行和 spread 行的 y 偏移是用字体大小累加出来的:SymbolFontSize+2 再叠 AskFontSize+2。如果你改了字体号却没同步这几个常量,三行文字会叠在一起。开 MT5 把 SymbolFontSize 从 10 调到 14,肉眼就能验证行距是否还对齐。

MQL5 / C++
  class="type">int totalWidthSymbol = totalSymbols * SymbolHorizontalSpacing;   class=class="str">"cmt">//--- Calculate total symbol line width
  class="type">int totalWidthAsk = totalSymbols * AskHorizontalSpacing;         class=class="str">"cmt">//--- Calculate total ask line width
  class="type">int totalWidthSpread = totalSymbols * SpreadHorizontalSpacing;   class=class="str">"cmt">//--- Calculate total spread line width
  class="type">int totalWidthSection = totalSymbols * SectionHorizontalSpacing; class=class="str">"cmt">//--- Calculate total section width
  class="type">int rightEdge = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS);  class=class="str">"cmt">//--- Get chart right boundary
  
  class=class="str">"cmt">//--- Update text and image objects
  for(class="type">int i = class="num">0; i < totalSymbols; i++)                            class=class="str">"cmt">//--- Iterate through all symbols
  {
      class=class="str">"cmt">// Symbol line(first line)
      class="type">class="kw">string symbolName = dashboardName + "_Symbol_" + IntegerToString(i); class=class="str">"cmt">//--- Define symbol object name
      class="type">class="kw">double symbolXPos = (i * SymbolHorizontalSpacing) - symbolOffset; class=class="str">"cmt">//--- Calculate symbol x-position
      if(symbolXPos < -SymbolHorizontalSpacing) symbolXPos += totalWidthSymbol; class=class="str">"cmt">//--- Wrap around if off-screen
      createText(symbolName, StringFormat("%-10s", symbolArray[i]), (class="type">int)symbolXPos, Y_Position, FontColor, SymbolFontSize, SymbolFont); class=class="str">"cmt">//--- Update symbol text
      ObjectSetInteger(class="num">0, symbolName, OBJPROP_HIDDEN, symbolXPos > rightEdge || symbolXPos < class="num">0); class=class="str">"cmt">//--- Hide if off-screen
      
      class=class="str">"cmt">// Ask line(second line)
      class="type">class="kw">string askName = dashboardName + "_Ask_" + IntegerToString(i); class=class="str">"cmt">//--- Define ask object name
      class="type">class="kw">double askXPos = (i * AskHorizontalSpacing) - askOffset;       class=class="str">"cmt">//--- Calculate ask x-position
      if(askXPos < -AskHorizontalSpacing) askXPos += totalWidthAsk;  class=class="str">"cmt">//--- Wrap around if off-screen
      createText(askName, StringFormat("%.5f", prices[i].ask), (class="type">int)askXPos, Y_Position + SymbolFontSize + class="num">2, clrMagenta, AskFontSize, AskFont); class=class="str">"cmt">//--- Update ask text
      ObjectSetInteger(class="num">0, askName, OBJPROP_HIDDEN, askXPos > rightEdge || askXPos < class="num">0); class=class="str">"cmt">//--- Hide if off-screen
      
      class=class="str">"cmt">// Spread line(third line)
      if(ShowSpread)                                                class=class="str">"cmt">//--- Check if spread display is enabled
      {
         class="type">class="kw">string spreadName = dashboardName + "_Spread_" + IntegerToString(i);      class=class="str">"cmt">//--- Define spread object name
         class="type">class="kw">double spreadXPos = (i * SpreadHorizontalSpacing) - spreadOffset;         class=class="str">"cmt">//--- Calculate spread x-position
         if(spreadXPos < -SpreadHorizontalSpacing) spreadXPos += totalWidthSpread; class=class="str">"cmt">//--- Wrap around if off-screen
         createText(spreadName, StringFormat("%.1f", prices[i].spread), (class="type">int)spreadXPos, Y_Position + SymbolFontSize + class="num">2 + AskFontSize + class="num">2, clrAqua, SpreadFontSize, SpreadFont); class=class="str">"cmt">//--- Update spread text

◍ 仪表盘板块的环绕隐藏与坐标排布

这段逻辑管的是 MT5 图表上那个多品种仪表盘的每个板块(图片、货币名、涨跌幅、箭头、买价)怎么随图表宽度滚动。核心思路是:算出板块横坐标 sectionXPos,若它小于一个负间距就整体加 totalWidthSection 实现左出右进的环绕;一旦超出右边界或小于 0,立刻用 OBJPROP_HIDDEN 藏掉,避免对象堆在可视区外吃资源。 具体坐标依赖一个链式偏移:Y 方向先叠 SymbolFontSize+2 与 AskFontSize,若开了点差显示再叠 SpreadFontSize+14,否则只补 14 像素行距。货币名固定在图片右移 35 像素处,涨跌幅再右移 105 像素,箭头在货币名下一行同 35 像素偏移并用 Wingdings 字体画方向符。 你在 EA 里改 SectionHorizontalSpacing 就能压扁或拉长板块间距;把 rightEdge 换成 ChartGetInteger(0,CHART_WIDTH_IN_PIXELS) 实时值,隐藏触发会更跟手。外汇与贵金属品种波动大,这类悬浮对象在快速翻页时可能闪跳,建议在实盘前用策略测试器先跑一轮可见性逻辑。

MQL5 / C++
ObjectSetInteger(class="num">0, spreadName, OBJPROP_HIDDEN, spreadXPos > rightEdge || spreadXPos < class="num">0); class=class="str">"cmt">//--- Hide if off-screen
}

class=class="str">"cmt">// Section(Image, Currency, Percent Change, Arrow, Bid Price)
class="type">class="kw">double sectionXPos = (i * SectionHorizontalSpacing) - sectionOffset;          class=class="str">"cmt">//--- Calculate section x-position
if(sectionXPos < -SectionHorizontalSpacing) sectionXPos += totalWidthSection; class=class="str">"cmt">//--- Wrap around if off-screen

class=class="str">"cmt">// Image(left)
class="type">class="kw">string imageName = dashboardName + "_Image_" + IntegerToString(i); class=class="str">"cmt">//--- Define image object name
ObjectSetInteger(class="num">0, imageName, OBJPROP_XDISTANCE, (class="type">int)sectionXPos); class=class="str">"cmt">//--- Update 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">//--- Update image y-coordinate
ObjectSetInteger(class="num">0, imageName, OBJPROP_HIDDEN, sectionXPos > rightEdge || sectionXPos < class="num">0); class=class="str">"cmt">//--- Hide if off-screen

class=class="str">"cmt">// Currency(top, right of image)
class="type">class="kw">string currencyName = dashboardName + "_Currency_" + IntegerToString(i); class=class="str">"cmt">//--- Define currency object name
createText(currencyName, StringFormat("%-10s", symbolArray[i]), (class="type">int)sectionXPos + 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, "Arial Bold"); class=class="str">"cmt">//--- Update currency text

class=class="str">"cmt">// 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
createText(percentChangeName, percentText, (class="type">int)sectionXPos + class="num">105, Y_Position + (ShowSpread ? SymbolFontSize + class="num">2 + AskFontSize + class="num">2 + SpreadFontSize + class="num">14 : SymbolFontSize + class="num">2 + AskFontSize + class="num">14), prices[i].percent_color, SectionFontSize, SectionFont); class=class="str">"cmt">//--- Update percent change text

class=class="str">"cmt">// Arrow(below currency, right of image, Wingdings)
class="type">class="kw">string arrowName = dashboardName + "_Arrow_" + IntegerToString(i); class=class="str">"cmt">//--- Define arrow object name
createText(arrowName, prices[i].arrow_char, (class="type">int)sectionXPos + 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) + SectionFontSize + class="num">2, prices[i].arrow_color, SectionFontSize, "Wingdings"); class=class="str">"cmt">//--- Update arrow text

class=class="str">"cmt">// Bid Price(next to arrow, horizontal)

「滚动偏移与跨日开盘价的定时器接管」

面板里的 bid 文本对象名由前缀、固定串和索引拼出,位置 Y 值根据是否显示点差在 SymbolFontSize+2+AskFontSize+14 与再叠加 SpreadFontSize+14 之间切换,说明布局高度是条件驱动的。 四个滚动偏移量都用 fmod 对各自总宽取余:symbolOffset 每次加 SymbolScrollSpeed 后对 totalWidthSymbol 取模,ask/spread/section 同理。这样能制造无缝循环滚动的视觉效果,改 Speed 参数即可调快慢。 OnTimer 里先用 TimeCurrent()/86400 算当前「天序号」,若大于 lastDay 就遍历全部品种用 iOpen(symbolArray[i], PERIOD_D1, 0) 重写 daily_open,随后更新 lastDay。外汇与贵金属为高杠杆品种,Daily Open 重置逻辑只反映日历日切换,不预示任何方向。 定时器由 EventSetMillisecondTimer(UpdateInterval) 拉起,周期以毫秒计;每次触发先 UpdateBackground 防 resize 变形,再 UpdateDashboard 重绘。想验证滚动是否顺滑,把 UpdateInterval 从默认调小到 50 看 CPU 占用变化。

MQL5 / C++
class="type">class="kw">string bidName = dashboardName + "_Bid_" + IntegerToString(i); class=class="str">"cmt">//--- Define bid object name
createText(bidName, StringFormat("%.5f", prices[i].bid), (class="type">int)sectionXPos + class="num">50, Y_Position + (ShowSpread ? SymbolFontSize + class="num">2 + AskFontSize + class="num">2 + SpreadFontSize + class="num">14 : SymbolFontSize + class="num">2 + AskFontSize + class="num">14) + SectionFontSize + class="num">2, prices[i].bid_color, SectionFontSize, SectionFont); class=class="str">"cmt">//--- Update bid price text
}

class=class="str">"cmt">//--- Increment offsets for scrolling effect
symbolOffset = fmod(symbolOffset + SymbolScrollSpeed, totalWidthSymbol);     class=class="str">"cmt">//--- Update symbol line offset
askOffset = fmod(askOffset + AskScrollSpeed, totalWidthAsk);                 class=class="str">"cmt">//--- Update ask line offset
spreadOffset = fmod(spreadOffset + SpreadScrollSpeed, totalWidthSpread);     class=class="str">"cmt">//--- Update spread line offset
sectionOffset = fmod(sectionOffset + SectionScrollSpeed, totalWidthSection); class=class="str">"cmt">//--- Update section offset

class=class="str">"cmt">//--- Redraw chart
ChartRedraw();                                                      class=class="str">"cmt">//--- Refresh chart display
}

class=class="str">"cmt">//--- Set timer
EventSetMillisecondTimer(UpdateInterval);                           class=class="str">"cmt">//--- Set timer for updates
class=class="str">"cmt">//--- Initialize last day
lastDay = TimeCurrent() / class="num">86400;                                    class=class="str">"cmt">//--- Set current day for daily open tracking

class="type">void OnTimer()
{
  class=class="str">"cmt">//--- Check for new day to update daily open
  class="type">class="kw">datetime currentDay = TimeCurrent() / class="num">86400;  class=class="str">"cmt">//--- Calculate current day
  if(currentDay > lastDay)                      class=class="str">"cmt">//--- Check if new day
  {
    for(class="type">int i = class="num">0; i < totalSymbols; i++)       class=class="str">"cmt">//--- Iterate through symbols
    {
      prices[i].daily_open = iOpen(symbolArray[i], PERIOD_D1, class="num">0); class=class="str">"cmt">//--- Update daily open price
    }
    lastDay = currentDay;                       class=class="str">"cmt">//--- Update last day
  }

  class=class="str">"cmt">//--- Update background size in case chart is resized
  UpdateBackground();                           class=class="str">"cmt">//--- Update background dimensions

  class=class="str">"cmt">//--- Update dashboard display
  UpdateDashboard();                            class=class="str">"cmt">//--- Update dashboard visuals
}

class="type">void UpdatePrices()
{

逐tick刷色与日变幅的计算落点

多品种盯盘面板的核心循环,就是拿上一tick的 bid 和这一tick的 bid 做比较,决定箭头和文字颜色。下面这段代码跑在 for 循环里,totalSymbols 是你要监控的货币对或贵金属个数,symbolArray 存的是品种名数组。 价格取数先调 SymbolInfoDouble 拿 SYMBOL_BID 和 SYMBOL_ASK。若返回 0,说明该品种当前无报价或权限不足,直接 LogError 并 continue 跳到下一个,避免把脏数据写进面板。外汇与贵金属报价受流动性影响,0 值在非农前后出现概率会明显升高,需留意外盘休市时段。 bid 大于 prev_bid 时箭头用字符 236(▲)、着 UpColor;小于则用 238(▼)、着 DownColor;相等或首tick用默认色和 236。注意 prev_bid 为 0 时强制走默认分支,这是防止开盘首帧误染色的保护。 日变幅按 (bid - daily_open) / daily_open * 100 算,daily_open 为 0 时强制返回 0;变幅 ≥0 用 UpColor,否则 DownColor。把这套逻辑直接贴进 EA 的 OnTick,就能在 MT5 上验证多品种面板的实时刷色效果。

MQL5 / C++
for(class="type">int i = class="num">0; i < totalSymbols; i++)                      class=class="str">"cmt">//--- Iterate through all symbols
   {
      class="type">class="kw">double bid = SymbolInfoDouble(symbolArray[i], SYMBOL_BID); class=class="str">"cmt">//--- Retrieve current bid price
      class="type">class="kw">double ask = SymbolInfoDouble(symbolArray[i], SYMBOL_ASK); class=class="str">"cmt">//--- Retrieve current ask price
      
      class=class="str">"cmt">//--- Validate prices
      if(bid == class="num">0 || ask == class="num">0)                                  class=class="str">"cmt">//--- Check for invalid prices
      {
         LogError("UpdatePrices: Failed to retrieve prices for " + symbolArray[i]); class=class="str">"cmt">//--- Log price retrieval failure
         class="kw">continue;                                              class=class="str">"cmt">//--- Skip to next symbol
      }
      
      class=class="str">"cmt">//--- Update class="type">class="kw">color and arrow based on price change(tick-to-tick for bid and arrow)
      if(bid > prices[i].prev_bid && prices[i].prev_bid != class="num">0) class=class="str">"cmt">//--- Check if bid increased
      {
         prices[i].bid_color = UpColor;                        class=class="str">"cmt">//--- Set bid class="type">class="kw">color to up class="type">class="kw">color
         prices[i].arrow_char = CharToString(class="num">236);             class=class="str">"cmt">//--- Set up arrow character
         prices[i].arrow_color = ArrowUpColor;                 class=class="str">"cmt">//--- Set arrow to up class="type">class="kw">color
      }
      else if(bid < prices[i].prev_bid && prices[i].prev_bid != class="num">0) class=class="str">"cmt">//--- Check if bid decreased
      {
         prices[i].bid_color = DownColor;                     class=class="str">"cmt">//--- Set bid class="type">class="kw">color to down class="type">class="kw">color
         prices[i].arrow_char = CharToString(class="num">238);             class=class="str">"cmt">//--- Set down arrow character
         prices[i].arrow_color = ArrowDownColor;               class=class="str">"cmt">//--- Set arrow to down class="type">class="kw">color
      }
      else                                                     class=class="str">"cmt">//--- Handle no change or first tick
      {
         prices[i].bid_color = FontColor;                     class=class="str">"cmt">//--- Set bid class="type">class="kw">color to class="kw">default
         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 arrow to class="kw">default class="type">class="kw">color
      }
      
      class=class="str">"cmt">//--- Calculate daily percentage change
      prices[i].percent_change = prices[i].daily_open != class="num">0 ? ((bid - prices[i].daily_open) / prices[i].daily_open) * class="num">100 : class="num">0; class=class="str">"cmt">//--- Compute percentage change
      prices[i].percent_color = prices[i].percent_change >= class="num">0 ? UpColor : DownColor; class=class="str">"cmt">//--- Set percent class="type">class="kw">color based on change
      
      class=class="str">"cmt">//--- Update data
      prices[i].bid = bid;                                     class=class="str">"cmt">//--- Store current bid
      prices[i].ask = ask;                                     class=class="str">"cmt">//--- Store current ask
   }

◍ 实时刷新与退出清理的实现落点

在 OnTick 里直接调 UpdatePrices(),意味着每个报价到来都会重算各品种 spread 与 prev_bid,MT5 上 EURUSD 在常规行情下每秒可能触发 5~20 次 tick,图形对象随之连续重绘。 OnDeinit 用倒序遍历 objManager 删除自建对象,先 ObjectFind 确认存在再 ObjectDelete,失败就记错误码。最后 EventKillTimer 停掉定时器,避免残留句柄。 把下面这段直接塞进 EA 框架就能在 MT5 验证:OnTick 持续推价格、OnDeinit 干净拆场。外汇与贵金属点差跳动频繁,实盘跑这套逻辑需注意高频重绘带来的资源占用风险。

MQL5 / C++
   prices[i].spread = (ask - bid) * MathPow(class="num">10, SymbolInfoInteger(symbolArray[i], SYMBOL_DIGITS)); class=class="str">"cmt">//--- Calculate spread
   prices[i].prev_bid = bid;                                                                       class=class="str">"cmt">//--- Update previous bid
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick()
{
   class=class="str">"cmt">//--- Update prices on every tick for live changes
   UpdatePrices();                                                                                 class=class="str">"cmt">//--- Update symbol prices
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason)
{
   class=class="str">"cmt">//--- Clean up objects
   for(class="type">int i = objManager.Total() - class="num">1; i >= class="num">0; i--) class=class="str">"cmt">//--- Iterate through all managed objects
   {
      class="type">class="kw">string name = objManager.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("OnDeinit: Failed to class="kw">delete object: " + name + ", Error: " + IntegerToString(GetLastError())); class=class="str">"cmt">//--- Log deletion failure
      }
      objManager.Delete(i);                                                                            class=class="str">"cmt">//--- Remove object from manager
   }
   EventKillTimer();                                                                                  class=class="str">"cmt">//--- Stop timer
}

「别急着下结论」

这套滚动行情条把买价、点差和日内涨跌幅压进一条横向滚动带,靠 SymbolData 结构管和 UpdateDashboard、UpdatePrices 两个函数刷新,MT5 里同时盯十几个品种不再靠切窗口。它自带字体、颜色、速度参数,波动一来视觉上先撞你一眼,比看静态报价表反应快半拍。 外汇和贵金属杠杆高、跳空频繁,这种工具只解决“看见”的问题,不替你判断方向,实盘里该错过的还是可能错过。 真要落地,把附带的 ROLLING_TICKER_TIMER_EA.mq5 拖进 MT5 编辑器编译,先调 ScrollSpeed 到你能跟上的节奏,再按品种波动改颜色阈值,剩下的就是盯盘时少分心。

常见问题

用横向排布加越界回收:当 x 坐标超过画布右边界就换行或回收最早创建的标签,保证都在可视区。
用定时器接管跨日逻辑,在新交易日首 tick 把基准偏移重置为当日开盘价,避免昨日数据串入。
小布可托管多品种面板的逐 tick 刷色与退出清理,自动回收资源并在卡顿时报错,你只管看信号。
把日变幅计算放在逐 tick 刷色函数里、以当日开盘为基准,别在定时器里算,能对齐实时价。
用环绕隐藏:按坐标排布把低频板块移出主视区,鼠标悬停或快捷键再唤出,减少视觉干扰。