从新手到专家:使用 MQL5 制作动画新闻标题 (三)  指标洞察·进阶篇
📊

从新手到专家:使用 MQL5 制作动画新闻标题 (三) 指标洞察·进阶篇

(2/3)· 多子窗口压扁主图视野?四个振荡指标经 MQL5 API 重绘进同一条滚动通道

含代码示例偏理论 第 2/3 篇
把 RSI、CCI、随机、MACD 全开成子窗口,主图被压成一条缝是常见误操作。指标越多越看不清价格本身,决策反而更慢。本篇用图表内通道替代分散子窗,先把空间还给你。

「把四个指标塞进一行字串」

这段逻辑把 RSI、Stochastic、MACD、CCI 的瞬时状态压成一条可读文本,方便在 MT5 画布上做轻量盯盘。MACD 部分用 12/26/9 参数取主信号线与信号线差值,CCI 用 14 周期典型价,越界 ±100 才标超买超卖,中间地带一律算中性。 代码里每个 Compute 函数都先拿指标句柄,失败就回错误串,成功 CopyBuffer 取倒数第二根(shift=1)的 1 个值,用完 IndicatorRelease 释放,避免句柄泄漏。最后 ComputeAllInsights 用竖线拼四段,OnInit 里按 InpSeparateLanes 决定分四条画布还是合成一条,合成时透明度设 120、字号 -120,左上角写死“Indicator Insights:”做静态标签。 外汇和贵金属波动大,这类快照只反映当下概率倾向,不能当方向保证;开 MT5 把下面代码贴进 EA 的 OnInit 和相关函数,调 InpSeparateLanes 就能看分屏或合屏效果。

MQL5 / C++
class="type">int h=iMACD(NULL,PERIOD_CURRENT,class="num">12,class="num">26,class="num">9,PRICE_CLOSE);
if(h==INVALID_HANDLE) class="kw">return "["+Symbol()+"] MACD err";
class="type">class="kw">double m[],g[]; CopyBuffer(h,class="num">0,class="num">1,class="num">1,m); CopyBuffer(h,class="num">1,class="num">1,class="num">1,g); IndicatorRelease(h);
class="type">class="kw">double d=m[class="num">0]-g[class="num">0]; class="type">class="kw">string s=DoubleToString(d,class="num">2);
class="type">class="kw">string m2=d>class="num">0?"Bull("+s+")":d<class="num">0?"Bear("+s+")":"Neu(class="num">0)";
class="kw">return "["+Symbol()+"] MACD:"+m2;
}
class="type">class="kw">string ComputeCCIInsight()
{
  class="type">int h=iCCI(NULL,PERIOD_CURRENT,class="num">14,PRICE_TYPICAL);
  if(h==INVALID_HANDLE) class="kw">return "["+Symbol()+"] CCI err";
  class="type">class="kw">double b[]; CopyBuffer(h,class="num">0,class="num">1,class="num">1,b); IndicatorRelease(h);
  class="type">class="kw">double v=b[class="num">0]; class="type">class="kw">string s=DoubleToString(v,class="num">1);
  class="type">class="kw">string m=v<-class="num">100?"Oversold("+s+")":v>class="num">100?"Overbought("+s+")":"Neutral("+s+")";
  class="kw">return "["+Symbol()+"] CCI:"+m;
}
class="type">class="kw">string ComputeAllInsights()
{
  class="kw">return ComputeRSIInsight() + "  |  " +
         ComputeStochInsight() + "  |  " +
         ComputeMACDInsight() + "  |  " +
         ComputeCCIInsight();
}
class="type">int OnInit()
{
  class=class="str">"cmt">// … existing canvases for events & news …
  if(InpSeparateLanes)
  {
    rsiCanvas.CreateBitmapLabel("RsiC",class="num">0,class="num">0,canvW,lineH);
    stochCanvas.CreateBitmapLabel("StoC",class="num">0,class="num">0,canvW,lineH);
    macdCanvas.CreateBitmapLabel("MacC",class="num">0,class="num">0,canvW,lineH);
    cciCanvas.CreateBitmapLabel("CciC",class="num">0,class="num">0,canvW,lineH);
    class=class="str">"cmt">// set transparency…
  }
  else
  {
    combinedCanvas.CreateBitmapLabel("AllC",class="num">0,class="num">0,canvW,lineH);
    combinedCanvas.TransparentLevelSet(class="num">120);
    class=class="str">"cmt">// class="kw">static label at x=class="num">5: “Indicator Insights:”
    combinedCanvas.FontSizeSet(-class="num">120);
    combinedCanvas.TextOut(class="num">5, (lineH-combinedCanvas.TextHeight("Indicator Insights:"))/class="num">2,
                           "Indicator Insights:", XRGB(class="num">200,class="num">200,class="num">255), ALIGN_LEFT);
    combinedCanvas.Update(true);
  }
  class=class="str">"cmt">// … remainder of OnInit …
}

◍ 定时器里的画布重排与滚动刷新

MT5 指标靠 OnTimer 做周期重绘时,最先干的事是把各画布按偏移量重新定位。分开车道模式(InpSeparateLanes=true)下,事件画布 EvC 占顶部,新闻画布 NwC 下挪 3 个行高,RSI/Stoch/MACD/CCI 各自再顺延 4~7 个 lineH;合并模式则只用一个 AllC 接在 NwC 下面。 宽度自适应不能漏:每次进定时器都用 ChartGetInteger(0,CHART_WIDTH_IN_PIXELS) 取最新像素宽,和缓存的 canvW 不同就逐个 ObjectSetInteger 改 OBJPROP_WIDTH。图表被拖拽缩放时,不补这段就会留下右侧空白带。 新闻和指标洞察用的是向左滚动写法——offNews 每帧减 InpNewsSpeed,当整段文本右边出了 -20 像素就把偏移重置回 canvW。新闻底色的 ARGB(170,0,0,0) 是半透明黑,RSI 车道用 ARGB(120,0,0,0) 更淡,文字色却给了 XRGB(180,220,255) 的浅蓝,对比度够但不过曝。 外汇与贵金属行情受新闻冲击频繁,这类滚动面板能帮交易者盯住突发标题;但指标洞察只是概率性提示,不代表方向必然成立,实盘仍属高风险。

MQL5 / C++
class="type">void OnTimer()
{
  class=class="str">"cmt">// reposition canvases
  SetCanvas("EvC",InpPositionTop,InpTopOffset);
  SetCanvas("NwC",InpPositionTop,InpTopOffset+class="num">3*lineH);
  if(InpSeparateLanes)
  {
    SetCanvas("RsiC",InpPositionTop,InpTopOffset+class="num">4*lineH);
    SetCanvas("StoC",InpPositionTop,InpTopOffset+class="num">5*lineH);
    SetCanvas("MacC",InpPositionTop,InpTopOffset+class="num">6*lineH);
    SetCanvas("CciC",InpPositionTop,InpTopOffset+class="num">7*lineH);
  }
  else
  {
    SetCanvas("AllC",InpPositionTop,InpTopOffset+class="num">4*lineH);
  }
  class=class="str">"cmt">// refresh data
  ReloadEvents();
  FetchAlphaVantageNews();
  class=class="str">"cmt">// resize if needed
  class="type">int wNew=(class="type">int)ChartGetInteger(class="num">0,CHART_WIDTH_IN_PIXELS);
  if(wNew!=canvW)
  {
    canvW=wNew;
    ObjectSetInteger(class="num">0,"EvC",OBJPROP_WIDTH,canvW);
    ObjectSetInteger(class="num">0,"NwC",OBJPROP_WIDTH,canvW);
    if(InpSeparateLanes)
    {
      ObjectSetInteger(class="num">0,"RsiC",OBJPROP_WIDTH,canvW);
      ObjectSetInteger(class="num">0,"StoC",OBJPROP_WIDTH,canvW);
      ObjectSetInteger(class="num">0,"MacC",OBJPROP_WIDTH,canvW);
      ObjectSetInteger(class="num">0,"CciC",OBJPROP_WIDTH,canvW);
    }
    else
    {
      ObjectSetInteger(class="num">0,"AllC",OBJPROP_WIDTH,canvW);
    }
  }
  class=class="str">"cmt">// draw events
  DrawAll();
  class=class="str">"cmt">// draw news
  newsCanvas.Erase(ARGB(class="num">170,class="num">0,class="num">0,class="num">0));
  class="type">class="kw">string nt = totalNews>class="num">0?newsHeadlines[class="num">0]:placeholder;
  newsCanvas.TextOut(offNews,(lineH-newsCanvas.TextHeight(nt))/class="num">2,
                     nt,XRGB(class="num">255,class="num">255,class="num">255),ALIGN_LEFT);
  offNews-=InpNewsSpeed;
  if(offNews+newsCanvas.TextWidth(nt)<-class="num">20) offNews=canvW;
  class=class="str">"cmt">// draw insights
  if(InpSeparateLanes)
  {
    class="type">class="kw">string t;
    t=ComputeRSIInsight();
    rsiCanvas.Erase(ARGB(class="num">120,class="num">0,class="num">0,class="num">0));
    rsiCanvas.TextOut(offRSI,(lineH-rsiCanvas.TextHeight(t))/class="num">2,
                      t,XRGB(class="num">180,class="num">220,class="num">255),ALIGN_LEFT);
    offRSI-=InpInsightSpeed;
    if(offRSI+rsiCanvas.TextWidth(t)<-class="num">20) offRSI=canvW;
    t=ComputeStochInsight();
    stochCanvas.Erase(ARGB(class="num">120,class="num">0,class="num">0,class="num">0));

滚动文字与画布回收的实现细节

这段逻辑负责把 Stoch、MACD、CCI 三条指标的解读文字在独立画布上做横向滚动,每次偏移量由 InpInsightSpeed 控制,颜色统一用 XRGB(180,220,255) 的浅蓝。当文字完全移出左侧边界(off+TextWidth < -20)时,把偏移重置为画布宽度 canvW,形成闭环跑马灯。 若关闭分栏(InpSeparateLanes=false),则走 combinedCanvas 分支,用 ComputeAllInsights() 拼成一段长文本整体滚动,复位条件是 offCombined+TextWidth < 100,比单指标更晚回收,避免长句被截断。 末尾的 Update(true) 是批量刷新关键:分栏时分别更新 rsi/stoch/macd/cci 四个画布,合屏时只更新 combinedCanvas,少一次 GDI 调用就可能少一点卡顿。外汇与贵金属行情跳动快,这类高频 OnTimer 重绘务必在 OnDeinit 里 EventKillTimer() 并 Destroy 画布,否则 MT5 图表关闭后资源可能残留。 OnDeinit 中每个画布配一对 Destroy() 和 ObjectDelete(0,"RsiC") 之类的清理,对象名与创建时严格对应;漏删一个都会导致图表上留一块透明位图,只能手动删对象。

MQL5 / C++
  stochCanvas.TextOut(offStoch,(lineH-stochCanvas.TextHeight(t))/class="num">2,
                      t,XRGB(class="num">180,class="num">220,class="num">255),ALIGN_LEFT);
  offStoch-=InpInsightSpeed;
  if(offStoch+stochCanvas.TextWidth(t)<-class="num">20) offStoch=canvW;
  t=ComputeMACDInsight();
  macdCanvas.Erase(ARGB(class="num">120,class="num">0,class="num">0,class="num">0));
  macdCanvas.TextOut(offMACD,(lineH-macdCanvas.TextHeight(t))/class="num">2,
                     t,XRGB(class="num">180,class="num">220,class="num">255),ALIGN_LEFT);
  offMACD-=InpInsightSpeed;
  if(offMACD+macdCanvas.TextWidth(t)<-class="num">20) offMACD=canvW;
  t=ComputeCCIInsight();
  cciCanvas.Erase(ARGB(class="num">120,class="num">0,class="num">0,class="num">0));
  cciCanvas.TextOut(offCCI,(lineH-cciCanvas.TextHeight(t))/class="num">2,
                    t,XRGB(class="num">180,class="num">220,class="num">255),ALIGN_LEFT);
  offCCI-=InpInsightSpeed;
  if(offCCI+cciCanvas.TextWidth(t)<-class="num">20) offCCI=canvW;
 }
 else
 {
   combinedCanvas.Erase(ARGB(class="num">120,class="num">0,class="num">0,class="num">0));
   class="type">class="kw">string txt=ComputeAllInsights();
   combinedCanvas.TextOut(offCombined,(lineH-combinedCanvas.TextHeight(txt))/class="num">2,
                          txt,XRGB(class="num">180,class="num">220,class="num">255),ALIGN_LEFT);
   offCombined-=InpInsightSpeed;
   if(offCombined+combinedCanvas.TextWidth(txt)<class="num">100) offCombined=canvW;
 }
 class=class="str">"cmt">// batch update
 eventsCanvas.Update(true);
 newsCanvas.  Update(true);
 if(InpSeparateLanes)
 {
   rsiCanvas.  Update(true);
   stochCanvas.Update(true);
   macdCanvas. Update(true);
   cciCanvas.  Update(true);
 }
 else
 {
   combinedCanvas.Update(true);
 }
}
class="type">void OnDeinit(const class="type">int reason)
{
  EventKillTimer();
  eventsCanvas.Destroy(); ObjectDelete(class="num">0,"EvC");
  newsCanvas.Destroy();   ObjectDelete(class="num">0,"NwC");
  if(InpSeparateLanes)
  {
    rsiCanvas.Destroy();  ObjectDelete(class="num">0,"RsiC");
    stochCanvas.Destroy();ObjectDelete(class="num">0,"StoC");
    macdCanvas.Destroy(); ObjectDelete(class="num">0,"MacC");
    cciCanvas.Destroy();  ObjectDelete(class="num">0,"CciC");
  }
  else
  {

「销毁画布与清空数组的收尾动作」

指标卸载或重绘前,先把合成画布对象从图表上拆掉,否则 MT5 会留下残留的 OBJ_BITMAP_LABEL ghost 图层。 combinedCanvas.Destroy() 负责释放 CCanvas 类申请的 GDI 资源;紧跟着 ObjectDelete(0,"AllC") 用图表 ID 0 删除名为 AllC 的图形对象,这两步必须成对出现,少一步都可能让下次加载时报「对象已存在」。 随后三个 for 循环把 highArr、medArr、lowArr 里每个 new 出来的元素逐个 delete,数组尺寸由 ArraySize 实时取,避免漏删造成内存泄漏;最后 ArrayResize(newsHeadlines,0) 把新闻标题缓存直接缩容到 0,完成一次干净的反初始化。 在外汇与贵金属品种上跑这类指标时,内存没清干净可能让 EA 在切换周期时变卡,属于高频切换图表场景下的隐性风险。

MQL5 / C++
  combinedCanvas.Destroy();ObjectDelete(class="num">0,"AllC");
  }
  for(class="type">int i=class="num">0;i<ArraySize(highArr);i++) class="kw">delete highArr[i];
  for(class="type">int i=class="num">0;i<ArraySize(medArr);i++)   class="kw">delete medArr[i];
  for(class="type">int i=class="num">0;i <ArraySize(lowArr);i++)   class="kw">delete lowArr[i];
  ArrayResize(newsHeadlines,class="num">0);
}
让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到指标拥挤度提示与通道化建议,你只需判断是否采纳。

常见问题

通道按指标分色与阈值线绘制,各自归一化到独立刻度带,视觉上并行不叠加,读数与子窗口一致。
仅在 OnCalculate 触发时局部刷新,比四个独立子窗口的重复渲染更省资源,老机型也倾向流畅。
二者周期逻辑不同,默认折叠态可同屏概览,展开态各自单通道,避免动量交叉误读。
小布盯盘的品种页已集成类似通道化显示,可对照本文概念检查自己的 MQL5 实现是否漏了归一化。
高杠杆下信号仅作概率参考,通道提示超买超卖不代表反转必至,仍需结合价格行为确认。