在MQL5中创建交易管理面板(第九部分):代码组织(5):分析面板(AnalyticsPanel)类·进阶篇
📊

在MQL5中创建交易管理面板(第九部分):代码组织(5):分析面板(AnalyticsPanel)类·进阶篇

(2/3)·静态饼图撑不起动态盘口,本篇把指标共识与实时刷新塞进一个可复用类

实战向 第 2/3 篇
很多面板停在画完静态盈亏比就交付,行情一动全变摆设。用 CLabel 硬写数值却不接实时抓取,等于给交易者看昨天的天气。把分析逻辑焊死在主程序里,下次改策略就是拆楼。

面板标签的创建与坐标排布

在 MT5 自定义分析面板里,每个文本标签都必须有全局唯一的名字,否则同图表多次加载会互相覆盖。下面这段先把面板名和标签名拼成 unique_name,再依次走 Create → Add → Text 三步,任一步返回 false 就打印错误并退出,避免半残对象留在内存。 label.Color(clr) 设文字色,label.BackColor(clrBlack) 强制黑底,保证浅色模板下也能看清。注意 BackColor 写死黑色,若你想做暗/亮主题切换,这里得改成参数传入。 CreateControls 用 curX/curY 做流式布局:起始都取 AP_GAP,每放一个标签就把 curY 累加「高度 + 间距」。实测标签宽 150、PnL 名只占 50 而值占剩余 100,Bid/Ask 名 40 值 100——这种硬编码宽度在 4K 屏可能偏挤,可把 labelWidth 提至 200 验证。 外汇与贵金属杠杆高,面板只是辅助观测,任何数值异常都先核对经纪商点差与保证金规则,再怀疑代码。

MQL5 / C++
  class="type">class="kw">string unique_name = m_name + "_" + label_name;
  if(!label.Create(m_chart_id, unique_name, m_subwin, x, y, x+width, y+height))
  {
      Print("Failed to create label: ", unique_name, " Err=", GetLastError());
      class="kw">return false;
  }
  if(!Add(label))
  {
      Print("Failed to add label: ", unique_name, " Err=", GetLastError());
      class="kw">return false;
  }
  if(!label.Text(text))
  {
      Print("Failed to set text for label: ", unique_name);
      class="kw">return false;
  }
  label.Color(clr);                class=class="str">"cmt">// Set text class="type">class="kw">color
  label.BackColor(clrBlack);       class=class="str">"cmt">// Force background to black
  class="kw">return true;
}

class="type">void CAnalyticsPanel::CreateControls()
{
   class="type">int curX = AP_GAP;
   class="type">int curY = AP_GAP;
   class="type">int labelWidth = class="num">150;
   CreateLabelEx(m_lblBalance, curX, curY, labelWidth, AP_DEFAULT_LABEL_HEIGHT, "Balance", "Balance: $class="num">0.00", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
   CreateLabelEx(m_lblEquity, curX, curY, labelWidth, AP_DEFAULT_LABEL_HEIGHT, "Equity", "Equity: $class="num">0.00", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
   CreateLabelEx(m_lblMargin, curX, curY, labelWidth, AP_DEFAULT_LABEL_HEIGHT, "Margin", "Margin: class="num">0", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
   CreateLabelEx(m_lblOpenTrades, curX, curY, labelWidth, AP_DEFAULT_LABEL_HEIGHT, "OpenTrades", "Open Trades: class="num">0", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
   CreateLabelEx(m_lblPnLName, curX, curY, class="num">50, AP_DEFAULT_LABEL_HEIGHT, "PnLName", "PnL:", clrBlack);
   CreateLabelEx(m_lblPnLValue, curX+class="num">50, curY, labelWidth-class="num">50, AP_DEFAULT_LABEL_HEIGHT, "PnLValue", "$class="num">0.00", clrLime);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
   CreateLabelEx(m_lblBidName, curX, curY, class="num">40, AP_DEFAULT_LABEL_HEIGHT, "BidName", "Bid:", clrDodgerBlue);
   CreateLabelEx(m_lblBidValue, curX+class="num">40, curY, class="num">100, AP_DEFAULT_LABEL_HEIGHT, "BidValue", "class="num">0.00000", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
   CreateLabelEx(m_lblAskName, curX, curY, class="num">40, AP_DEFAULT_LABEL_HEIGHT, "AskName", "Ask:", clrDodgerBlue);
   CreateLabelEx(m_lblAskValue, curX+class="num">40, curY, class="num">100, AP_DEFAULT_LABEL_HEIGHT, "AskValue", "class="num">0.00000", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
   CreateLabelEx(m_lblSpread, curX, curY, labelWidth, AP_DEFAULT_LABEL_HEIGHT, "Spread", "Spread: class="num">0.0 pips", clrDodgerBlue);
   curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
   CreateLabelEx(m_lblIndicatorSeparator, curX, curY, labelWidth, AP_DEFAULT_LABEL_HEIGHT, "Separator", "------------------------------------------", clrDodgerBlue);

「面板布局与账户刷新的坐标算术」

这段逻辑干两件事:在图表左上角按垂直步进铺一排指标标签,以及把账户面板的三项数值刷成字符串。垂直坐标靠 curY 累加 AP_DEFAULT_LABEL_HEIGHT + AP_GAP 推进,改这两个宏就能整体压缩或拉开行距。 信号汇总列的位置用了回退算式:curY - (AP_DEFAULT_LABEL_HEIGHT+AP_GAP)*4,把 Expectation 和 Confirmation 钉在指标区顶部右侧,和左侧四个指标名对齐。若你以后加第 5 个指标,这个 4 要改成 5,否则汇总列会错位。 账户刷新部分直接取 ACCOUNT_BALANCE / EQUITY / MARGIN 三类双精度值,用 DoubleToString(..., 2) 锁两位小数。外汇与贵金属杠杆品种中 margin 跳动频繁,面板文本仅作盯盘参考,实际风控仍以终端提醒为准。 下面这段是原文中面板绘制的连续代码,逐行拆:curY 先下移一行留给区块头;CreateLabelEx 建「INDICATORS」与「SIGNAL」双表头,SIGNAL 列 x 偏移 250;之后 RSI / Stoch(K、D) / CCI / Williams 各占一行,宽度 50~70 不等;末了用负偏移把汇总标签拉回顶部,ChartRedraw 强制重绘。

MQL5 / C++
  curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
  class=class="str">"cmt">// Indicator Section Header:
  CreateLabelEx(m_lblIndicatorHeader, curX, curY, labelWidth, AP_DEFAULT_LABEL_HEIGHT, "IndicatorHeader", "INDICATORS", clrDodgerBlue);
  class=class="str">"cmt">// Summary Column Header for Signal:
  CreateLabelEx(m_lblSignalHeader, curX+class="num">250, curY , class="num">100, AP_DEFAULT_LABEL_HEIGHT, "SignalHeader", "SIGNAL", clrDodgerBlue);
  curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
  class=class="str">"cmt">// Indicator Labels:
  class=class="str">"cmt">// RSI:
  CreateLabelEx(m_lblRSIName, curX, curY, class="num">50, AP_DEFAULT_LABEL_HEIGHT, "RSIName", "RSI:", clrDodgerBlue);
  CreateLabelEx(m_lblRSIValue, curX+class="num">50, curY, class="num">90, AP_DEFAULT_LABEL_HEIGHT, "RSIValue", "N/A", clrDodgerBlue);
  curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
  class=class="str">"cmt">// Stochastic:
  CreateLabelEx(m_lblStochName, curX, curY, class="num">60, AP_DEFAULT_LABEL_HEIGHT, "StochName", "Stoch:", clrDodgerBlue);
  CreateLabelEx(m_lblStochK, curX+class="num">60, curY, class="num">70, AP_DEFAULT_LABEL_HEIGHT, "StochK", "K: N/A", clrDodgerBlue);
  CreateLabelEx(m_lblStochD, curX+class="num">150, curY, class="num">70, AP_DEFAULT_LABEL_HEIGHT, "StochD", "D: N/A", clrDodgerBlue);
  curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
  class=class="str">"cmt">// CCI:
  CreateLabelEx(m_lblCCIName, curX, curY, class="num">50, AP_DEFAULT_LABEL_HEIGHT, "CCIName", "CCI:", clrDodgerBlue);
  CreateLabelEx(m_lblCCIValue, curX+class="num">50, curY, class="num">90, AP_DEFAULT_LABEL_HEIGHT, "CCIValue", "N/A", clrDodgerBlue);
  curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
  class=class="str">"cmt">// Williams %R:
  CreateLabelEx(m_lblWilliamsName, curX, curY, class="num">70, AP_DEFAULT_LABEL_HEIGHT, "WilliamsName", "Williams:", clrDodgerBlue);
  CreateLabelEx(m_lblWilliamsValue, curX+class="num">70, curY, class="num">70, AP_DEFAULT_LABEL_HEIGHT, "WilliamsValue", "N/A", clrDodgerBlue);
  curY += AP_DEFAULT_LABEL_HEIGHT + AP_GAP;
  class=class="str">"cmt">// Summary Column: Expectation text and Confirmation symbol.
  CreateLabelEx(m_lblExpectation, curX+class="num">250, curY - (AP_DEFAULT_LABEL_HEIGHT+AP_GAP)*class="num">4, class="num">100, AP_DEFAULT_LABEL_HEIGHT, "Expectation", "Expect: N/A", clrDodgerBlue);
  CreateLabelEx(m_lblConfirmation, curX+class="num">300, curY - (AP_DEFAULT_LABEL_HEIGHT+AP_GAP)*class="num">4, class="num">50, AP_DEFAULT_LABEL_HEIGHT, "Confirmation", "N/A", clrDodgerBlue);
  ChartRedraw(m_chart_id);
}
class="type">void CAnalyticsPanel::UpdateAccountInfo()
{
  class="type">class="kw">double balance = AccountInfoDouble(ACCOUNT_BALANCE);
  class="type">class="kw">double equity  = AccountInfoDouble(ACCOUNT_EQUITY);
  class="type">class="kw">double margin  = AccountInfoDouble(ACCOUNT_MARGIN);
  m_lblBalance.Text("Balance: $" + DoubleToString(balance, class="num">2));
  m_lblEquity.Text("Equity: $" + DoubleToString(equity, class="num">2));
  class=class="str">"cmt">// Directly display the raw margin value.
  m_lblMargin.Text("Margin: " + DoubleToString(margin, class="num">2));
}

◍ 盯盘面板里的实时统计与报价刷新

想在 MT5 自定义面板里同时盯持仓盈亏和盘口跳动,核心是把持仓遍历与 tick 回调拆成两个独立方法。下面这段 CAnalyticsPanel 的成员函数给出了可直接抄的骨架。 UpdateTradeStats 先取 PositionsTotal(),再按 ticket 循环累加 POSITION_PROFIT。浮亏时标签染红、浮盈染绿,开仓数用 IntegerToString 拼到文本里——跑起来你能立刻在面板看到当前敞口汇总。

MQL5 / C++
class="type">void CAnalyticsPanel::UpdateTradeStats()
{
  class="type">int total = PositionsTotal();
  class="type">class="kw">double pnl = class="num">0;
  for(class="type">int i = class="num">0; i < total; i++)
  {
    class="type">ulong ticket = PositionGetTicket(i);
    if(ticket != class="num">0)
      pnl += PositionGetDouble(POSITION_PROFIT);
  }
  m_lblPnLName.Text("PnL:");
  m_lblPnLValue.Text("$" + DoubleToString(pnl, class="num">2));
  if(pnl < class="num">0)
    m_lblPnLValue.Color(clrRed);
  else
    m_lblPnLValue.Color(clrLime);
  m_lblOpenTrades.Text("Open Trades: " + IntegerToString(total));
}
逐行看:第3行拿到当前持仓总数;第5行用 PositionGetTicket 按索引取真实 ticket;第7行把每笔 POSITION_PROFIT 累加进 pnl;第11~14行按正负切换红绿;第15行把开仓数刷到标签。外汇与贵金属杠杆高,面板里的浮盈只是未实现,爆仓风险始终在。 UpdateMarketData 用 SymbolInfoTick 抓最新 MqlTick,bid/ask 相对上次值升则蓝、降则红,点差按 (ask-bid)*1e4 算成 pip。EURUSD 常态点差约 1.0~1.5 pips,这段代码会实时显示到小数点后一位。
MQL5 / C++
class="type">void CAnalyticsPanel::UpdateMarketData()
{
  class="type">MqlTick last_tick;
  if(SymbolInfoTick(Symbol(), last_tick))
  {
    class="type">class="kw">color bidColor = clrBlue;
    if(m_prevBid != class="num">0)
      bidColor = (last_tick.bid >= m_prevBid) ? clrBlue : clrRed;
    m_lblBidValue.Color(bidColor);
    m_lblBidValue.Text(DoubleToString(last_tick.bid, class="num">5));
    class="type">class="kw">color askColor = clrBlue;
    if(m_prevAsk != class="num">0)
      askColor = (last_tick.ask >= m_prevAsk) ? clrBlue : clrRed;
    m_lblAskValue.Color(askColor);
    m_lblAskValue.Text(DoubleToString(last_tick.ask, class="num">5));
    m_prevBid = last_tick.bid;
    m_prevAsk = last_tick.ask;
    class="type">class="kw">double spread_pips = (last_tick.ask - last_tick.bid) * class="num">1e4;
    m_lblSpread.Text("Spread: " + DoubleToString(spread_pips, class="num">1) + " pips");
  }
}
m_prevBid / m_prevAsk 是类内成员,首次调用为0故不染色,从第二帧才开始比较方向。把这两个方法挂到 OnTimer 或 ChartEvent 里,就能做轻量盯盘插件。

MQL5 / C++
class="type">void CAnalyticsPanel::UpdateTradeStats()
{
  class="type">int total = PositionsTotal();
  class="type">class="kw">double pnl = class="num">0;
  for(class="type">int i = class="num">0; i < total; i++)
  {
    class="type">ulong ticket = PositionGetTicket(i);
    if(ticket != class="num">0)
      pnl += PositionGetDouble(POSITION_PROFIT);
  }
  m_lblPnLName.Text("PnL:");
  m_lblPnLValue.Text("$" + DoubleToString(pnl, class="num">2));
  if(pnl < class="num">0)
    m_lblPnLValue.Color(clrRed);
  else
    m_lblPnLValue.Color(clrLime);
  m_lblOpenTrades.Text("Open Trades: " + IntegerToString(total));
}
class="type">void CAnalyticsPanel::UpdateMarketData()
{
  class="type">MqlTick last_tick;
  if(SymbolInfoTick(Symbol(), last_tick))
  {
    class="type">class="kw">color bidColor = clrBlue;
    if(m_prevBid != class="num">0)
      bidColor = (last_tick.bid >= m_prevBid) ? clrBlue : clrRed;
    m_lblBidValue.Color(bidColor);
    m_lblBidValue.Text(DoubleToString(last_tick.bid, class="num">5));
    class="type">class="kw">color askColor = clrBlue;
    if(m_prevAsk != class="num">0)
      askColor = (last_tick.ask >= m_prevAsk) ? clrBlue : clrRed;
    m_lblAskValue.Color(askColor);
    m_lblAskValue.Text(DoubleToString(last_tick.ask, class="num">5));
    m_prevBid = last_tick.bid;
    m_prevAsk = last_tick.ask;
    class="type">class="kw">double spread_pips = (last_tick.ask - last_tick.bid) * class="num">1e4;
    m_lblSpread.Text("Spread: " + DoubleToString(spread_pips, class="num">1) + " pips");
  }
}

RSI与 stochastic、CCI 的面板着色逻辑

这段片段把三个振荡指标的当前值抓出来,直接驱动盯盘面板上的文字颜色和买卖倾向变量。RSI 用 30/70 双阈值:低于 30 标蓝并给 suggestionRSI 赋 1,高于 70 标红并赋 -1,否则维持默认绿。 Stochastic 取 5/3/3 参数、SMA 平滑、LOWHIGH 算法,K 上穿 D 时 K 标蓝 D 标红、倾向 +1,下穿则反过来、倾向 -1,相等则清零。注意 K 和 D 的显示精度是小数点后 4 位,比 RSI 的 2 位更细。 CCI 用 14 周期典型价,跌破 -100 即标蓝并置 suggestionCCI 为 1,这是常见的超卖判定。外汇与贵金属波动剧烈,这类阈值信号仅作概率参考,实盘须自担高风险。 下面拆一下 RSI 部分的核心代码: color rsiValueColor = clrGreen; // 默认绿色,无信号状态 if(CopyBuffer(handleRSI, 0, 0, 1, rsi) > 0) // 从RSI句柄拷1根最新值到rsi数组 { if(rsi[0] < 30) // 最新RSI低于30 { rsiValueColor = clrBlue; // 买条件:文字变蓝 suggestionRSI = 1; // 记录看多倾向 } else if(rsi[0] > 70) // 最新RSI高于70 { rsiValueColor = clrRed; // 卖条件:文字变红 suggestionRSI = -1; // 记录看空倾向 } m_lblRSIValue.Color(rsiValueColor); // 刷新标签颜色 m_lblRSIValue.Text(DoubleToString(rsi[0], 2)); // 显示RSI保留2位小数 } IndicatorRelease(handleRSI); // 释放指标句柄防泄漏 开 MT5 把这段塞进自定义指标 OnCalculate,能直观看到超买超卖时标签变色。

MQL5 / C++
class="type">class="kw">color rsiValueColor = clrGreen; class=class="str">"cmt">// class="kw">default
if(CopyBuffer(handleRSI, class="num">0, class="num">0, class="num">1, rsi) > class="num">0)
{
  if(rsi[class="num">0] < class="num">30)
  {
    rsiValueColor = clrBlue; class=class="str">"cmt">// buy condition
    suggestionRSI = class="num">1;
  }
  else if(rsi[class="num">0] > class="num">70)
  {
    rsiValueColor = clrRed;  class=class="str">"cmt">// sell condition
    suggestionRSI = -class="num">1;
  }
  m_lblRSIValue.Color(rsiValueColor);
  m_lblRSIValue.Text(DoubleToString(rsi[class="num">0], class="num">2));
}
IndicatorRelease(handleRSI);

「四指标同向才给方向信号」

这段逻辑把 CCI 与 Williams %R 的判读接上前面 RSI、Stochastic 的节奏:CCI 上穿 100 倾向空头标记(clrRed,suggestionCCI=-1),反之在 -100 下方才给多头;WPR 周期取 14,大于 -80 判买、小于 -20 判卖,和常规 -20/-80 超买超卖带反着用,需注意这是原面板自定义阈值。 共识模块要求四个 suggestion 变量全部相等才输出 Buy/Sell,否则显示 No Consensus 并配橙色横杠。也就是说,实盘里四指标同时指向同一边的概率并不高,多数时间面板会让你‘歇着别动手’。 末尾的 CreateAdminPanel 只挂了一个 Analytics Panel 按钮,HandleAnalytics 里若面板为空则准备新建——说明 UI 是懒加载,点之前不占资源。外汇与贵金属波动剧烈,这类多指标共识仅作概率参考,杠杆风险自负,开 MT5 把这段塞进你自己的面板类里跑一遍最直观。

MQL5 / C++
   else if(cci[class="num">0] > class="num">100)
   {
      cciValueColor = clrRed;  class=class="str">"cmt">// sell condition
      suggestionCCI = -class="num">1;
   }
   m_lblCCIValue.Color(cciValueColor);
   m_lblCCIValue.Text(DoubleToString(cci[class="num">0], class="num">2));
  }
  IndicatorRelease(handleCCI);
  class=class="str">"cmt">// --- Williams %R ---
  class="type">int handleWPR = iWPR(Symbol(), PERIOD_CURRENT, class="num">14);
  class="type">class="kw">double williams[class="num">1];
  class="type">class="kw">color williamsValueColor = clrGreen;
  if(CopyBuffer(handleWPR, class="num">0, class="num">0, class="num">1, williams) > class="num">0)
  {
   if(williams[class="num">0] > -class="num">80)
   {
      williamsValueColor = clrBlue; class=class="str">"cmt">// buy condition
      suggestionWilliams = class="num">1;
   }
   else if(williams[class="num">0] < -class="num">20)
   {
      williamsValueColor = clrRed;  class=class="str">"cmt">// sell condition
      suggestionWilliams = -class="num">1;
   }
   m_lblWilliamsValue.Color(williamsValueColor);
   m_lblWilliamsValue.Text(DoubleToString(williams[class="num">0], class="num">2));
  }
  IndicatorRelease(handleWPR);
  class=class="str">"cmt">// --- Consensus Summary ---
  class="type">int consensus = class="num">0;
  if(suggestionRSI != class="num">0 && suggestionRSI == suggestionStoch &&
     suggestionRSI == suggestionCCI && suggestionRSI == suggestionWilliams)
     consensus = suggestionRSI;
  if(consensus == class="num">1)
  {
   m_lblExpectation.Text("Buy");
   m_lblExpectation.Color(clrBlue);
   m_lblConfirmation.Text("↑"); class=class="str">"cmt">// Up arrow for Buy
   m_lblConfirmation.Color(clrBlue);
  }
  else if(consensus == -class="num">1)
  {
   m_lblExpectation.Text("Sell");
   m_lblExpectation.Color(clrRed);
   m_lblConfirmation.Text("↓"); class=class="str">"cmt">// Down arrow for Sell
   m_lblConfirmation.Color(clrRed);
  }
  else
  {
   m_lblExpectation.Text("No Consensus");
   m_lblExpectation.Color(clrOrangeRed);
   m_lblConfirmation.Text("-");
   m_lblConfirmation.Color(clrOrangeRed);
  }
}
class="macro">#include <AnalyticsPanel.mqh>
CAnalyticsPanel *g_analyticsPanel = NULL;
class="type">bool CreateAdminPanel()
{
  class=class="str">"cmt">// Analytics Panel Button
  if(!CreateButton(g_analyticsButton, ANALYTICS_BTN_NAME, INDENT_LEFT, y, INDENT_LEFT+btnWidth, y+BUTTON_HEIGHT, "Analytics Panel"))
     class="kw">return false;

 class="kw">return true;
}
class=class="str">"cmt">//------------------------------------------------------------------
class=class="str">"cmt">// Handle Analytics Panel button click
class="type">void HandleAnalytics()
{
  if(g_analyticsPanel == NULL)
  {
交给小布盯盘看盘口
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到多指标共识信号,把重复劳动交给小布,你专注决策。

常见问题

核心补上实时数据获取与动态标签刷新,并把汇合策略的信号判定封装进类内,不再依赖写死的界面元素。
需自行扩展刷新方法与事件绑定,标准库控件默认不主动拉实时报价,要在类里组织定时或事件触发更新。
倾向不操作。外汇贵金属高风险,无共识在文中被定义为避免虚假信号的状态,概率上更适合观望。
小布盯盘的品种页已内置多指标共识视图,逻辑同汇合原则,无需你自己跑 EA 也能看到一致/无共识反馈。
因类职责分离,调视觉只需改 AnalyticsPanel 头文件内的布局方法,主程序调用不变,维护成本明显降低。