从新手到专家:使用 MQL5 制作动画新闻标题 (三) 指标洞察·进阶篇
(2/3)· 多子窗口压扁主图视野?四个振荡指标经 MQL5 API 重绘进同一条滚动通道
「把四个指标塞进一行字串」
这段逻辑把 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 就能看分屏或合屏效果。
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) 的浅蓝,对比度够但不过曝。 外汇与贵金属行情受新闻冲击频繁,这类滚动面板能帮交易者盯住突发标题;但指标洞察只是概率性提示,不代表方向必然成立,实盘仍属高风险。
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") 之类的清理,对象名与创建时严格对应;漏删一个都会导致图表上留一块透明位图,只能手动删对象。
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 在切换周期时变卡,属于高频切换图表场景下的隐性风险。
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); }