在 MQL5 中创建交互式图形用户界面(第 2 部分):添加控制和响应·进阶篇
🖱️

在 MQL5 中创建交互式图形用户界面(第 2 部分):添加控制和响应·进阶篇

(2/3)·静态面板只是定格快照,本篇教你解冻界面、接上实时报价与可拖拽交互

偏理论 第 2/3 篇
很多交易者把 MQL5 面板写成死界面:按钮点了没反应,价格不刷新,拖不动也改不了。这种静态快照在实盘里除了占屏幕毫无用处,还容易让你错过手动干预的时机。

图表对象点击如何触发平仓逻辑

在 MT5 的 OnChartEvent 回调里,图表上任何对象被点击都会抛出 CHARTEVENT_OBJECT_CLICK 事件,并携带 idlparamdparamsparam 四个参数。其中 sparam 是触发事件的图形对象名称,用它区分是点了小车图标还是关闭按钮最稳妥。 当 sparam==ICON_CAR 时,代码直接调用 destroyPanel() 销毁面板并 ChartRedraw(0) 重绘;当 sparam==BTN_CLOSE 时,则进入平仓分支,先把按钮颜色改成 clrRed 给视觉反馈,再遍历持仓。 平仓循环用 PositionsTotal() 取总持仓数,PositionGetTicket(i) 拿ticket,再用 PositionSelectByTicket 选中后比对 PositionGetString(POSITION_SYMBOL) 是否等于 _Symbol,匹配才 obj_Trade.PositionClose(ticket)。外汇与贵金属杠杆高,这类一键平仓只平当前图表品种,实盘误触可能造成滑点损失。 挂单清理走另一段:#include <Trade/Trade.mqh> 后声明 CTrade obj_Trade,用 OrdersTotal()OrderGetTicket(i) 遍历。开 MT5 把这段接到自己的面板 EA 里,点关闭按钮就能看到指定品种仓位被清掉。

MQL5 / C++
const class="type">class="kw">string &sparam)
{
   ...
}
class=class="str">"cmt">// Print the class="num">4 function parameters
Print("ID = ",id,", LPARAM = ",lparam,", DPARAM = ",dparam,", SPARAM = ",sparam);
  class=class="str">"cmt">//Print("ID = ",id,", LPARAM = ",lparam,", DPARAM = ",dparam,", SPARAM = ",sparam);
  if (id==CHARTEVENT_OBJECT_CLICK){
      ...
  }
  if (id==CHARTEVENT_OBJECT_CLICK){
     Print("ID = ",id,", LM = ",lparam,", DM = ",dparam,", SPARAM = ",sparam);
     ...
  }
      class=class="str">"cmt">//--- if icon car is clicked, destroy the panel
      if (sparam==ICON_CAR){
         Print("BTN CAR CLICKED. DESTROY PANEL NOW");
         destroyPanel();
         ChartRedraw(class="num">0);
      }
      else if (sparam == BTN_CLOSE) {
         class=class="str">"cmt">// Button "Close" clicked. Close all orders and positions now.
         Print("BTN CLOSE CLICKED. CLOSE ALL ORDERS & POSITIONS NOW");
         class=class="str">"cmt">// Store the original class="type">class="kw">color of the button
         class="type">long originalColor = ObjectGetInteger(class="num">0, BTN_CLOSE, OBJPROP_COLOR);
         class=class="str">"cmt">// Change the button class="type">class="kw">color to red(for visual feedback)
         ObjectSetInteger(class="num">0, BTN_CLOSE, OBJPROP_COLOR, clrRed);
         ...
      }
         class=class="str">"cmt">// Iterate through all open positions
         for (class="type">int i = class="num">0; i <= PositionsTotal(); i++) {
            class="type">class="kw">ulong ticket = PositionGetTicket(i);
            if (ticket > class="num">0) {
               if (PositionSelectByTicket(ticket)) {
                  class=class="str">"cmt">// Check if the position symbol matches the current chart symbol
                  if (PositionGetString(POSITION_SYMBOL) == _Symbol) {
                     obj_Trade.PositionClose(ticket); class=class="str">"cmt">// Close the position
                  }
               }
            }
         }
class="macro">#include <Trade/Trade.mqh>
CTrade obj_Trade;
         class=class="str">"cmt">// Iterate through all pending orders
         for (class="type">int i = class="num">0; i <= OrdersTotal(); i++) {
            class="type">class="kw">ulong ticket = OrderGetTicket(i);

「一键撤单平仓的图表按钮逻辑」

在 MT5 面板里做「Close」按钮,核心不是发指令,而是先确认 ticket 与当前图表品种对齐,避免误杀其他货币对挂单。下面这段处理的是挂单删除分支:先按 ticket 选中,再用 OrderGetString(ORDER_SYMBOL) 比对 _Symbol,匹配才调 obj_Trade.OrderDelete。 if(ticket > 0) { if(OrderSelect(ticket)) { // 核对挂单品种是否与当前图表一致 if(OrderGetString(ORDER_SYMBOL) == _Symbol) { obj_Trade.OrderDelete(ticket); // 删除该挂单 } } } // 把按钮颜色恢复成初始值 Print("Resetting button to original color"); ObjectSetInteger(0, BTN_CLOSE, OBJPROP_COLOR, originalColor); // 强制重绘图表以反映变更 ChartRedraw(0); 点击「Close」后,先把按钮染红做视觉反馈,再遍历 PositionsTotal() 个持仓。注意循环条件是 i <= PositionsTotal(),这种写法在平仓过程中持仓数动态减少,可能漏掉末尾项,更稳的做法是倒序 for(int i = PositionsTotal()-1; i >= 0; i--)。 else if(sparam == BTN_CLOSE) { Print("BTN CLOSE CLICKED. CLOSE ALL ORDERS & POSITIONS NOW"); long originalColor = ObjectGetInteger(0, BTN_CLOSE, OBJPROP_COLOR); ObjectSetInteger(0, BTN_CLOSE, OBJPROP_COLOR, clrRed); for(int i = 0; i <= PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(ticket > 0) { if(PositionSelectByTicket(ticket)) { // 核对持仓品种 if(PositionGetString(POSITION_SYMBOL) == _Symbol) { obj_Trade.PositionClose(ticket); // 平掉该持仓 } } } } } 外汇与贵金属波动剧烈,这类一键全平脚本在滑点扩大时可能只成交部分仓位,上线前务必在策略测试器用 2023 年 XAUUSD 的 M1 数据跑一遍品种过滤逻辑。

MQL5 / C++
if(ticket > class="num">0) {
   if(OrderSelect(ticket)) {
      class=class="str">"cmt">// Check if the order symbol matches the current chart symbol
      if(OrderGetString(ORDER_SYMBOL) == _Symbol) {
         obj_Trade.OrderDelete(ticket); class=class="str">"cmt">// Delete the order
      }
   }
}
class=class="str">"cmt">// Reset the button class="type">class="kw">color to its original value
Print("Resetting button to original class="type">class="kw">color");
ObjectSetInteger(class="num">0, BTN_CLOSE, OBJPROP_COLOR, originalColor);
class=class="str">"cmt">// Force a redrawing of the chart to reflect the changes
ChartRedraw(class="num">0);
else if(sparam == BTN_CLOSE) {
   class=class="str">"cmt">// Button "Close" clicked. Close all orders and positions now.
   Print("BTN CLOSE CLICKED. CLOSE ALL ORDERS & POSITIONS NOW");
   class=class="str">"cmt">// Store the original class="type">class="kw">color of the button
   class="type">long originalColor = ObjectGetInteger(class="num">0, BTN_CLOSE, OBJPROP_COLOR);
   class=class="str">"cmt">// Change the button class="type">class="kw">color to red(for visual feedback)
   ObjectSetInteger(class="num">0, BTN_CLOSE, OBJPROP_COLOR, clrRed);
   class=class="str">"cmt">// Iterate through all open positions
   for(class="type">int i = class="num">0; i <= PositionsTotal(); i++) {
      class="type">class="kw">ulong ticket = PositionGetTicket(i);
      if(ticket > class="num">0) {
         if(PositionSelectByTicket(ticket)) {
            class=class="str">"cmt">// Check if the position symbol matches the current chart symbol
            if(PositionGetString(POSITION_SYMBOL) == _Symbol) {
               obj_Trade.PositionClose(ticket); class=class="str">"cmt">// Close the position
            }
         }
      }
   }
}

◍ 挂单与持仓的一键清理逻辑

在 MT5 面板工具里,按品种维度批量撤单和平仓是常见需求。下面这段处理两个按钮事件:BTN_CLOSE 负责删掉当前图表品种的所有挂单,BTN_MARKET 则平掉同品种的所有已开持仓。 遍历挂单时用 OrdersTotal() 取总数,循环里用 OrderGetTicket(i) 拿到票据号,再 OrderSelect 激活后比对 OrderGetString(ORDER_SYMBOL) 是否等于 _Symbol。匹配就调 obj_Trade.OrderDelete(ticket) 删单,删完用 ObjectSetInteger 把按钮颜色还原,ChartRedraw(0) 强制重绘。 平仓分支结构类似,只是换成 PositionsTotal() 与 PositionGetTicket(i),选中后比 POSITION_SYMBOL,一致则 obj_Trade.PositionClose(ticket)。外汇与贵金属杠杆高,批量平仓可能在滑点扩大时造成超预期回撤,实盘前务必在策略测试器用历史数据跑一遍。 别把循环上限写成小于总数 很多新手把 for 条件写成 i < OrdersTotal(),会漏掉最后一笔订单或持仓。用 i <= 总数才能覆盖索引 0 到 N 的全部元素,开 MT5 新建脚本改这行就能验证差异。

MQL5 / C++
for (class="type">int i = class="num">0; i <= OrdersTotal(); i++) {
   class="type">class="kw">ulong ticket = OrderGetTicket(i);
   if (ticket > class="num">0) {
      if (OrderSelect(ticket)) {
         class=class="str">"cmt">// Check if the order symbol matches the current chart symbol
         if (OrderGetString(ORDER_SYMBOL) == _Symbol) {
            obj_Trade.OrderDelete(ticket); class=class="str">"cmt">// Delete the order
         }
      }
   }
}

class=class="str">"cmt">// Reset the button class="type">class="kw">color to its original value
Print("Resetting button to original class="type">class="kw">color");
ObjectSetInteger(class="num">0, BTN_CLOSE, OBJPROP_COLOR, originalColor);

class=class="str">"cmt">// Force a redrawing of the chart to reflect the changes
ChartRedraw(class="num">0);

else if (sparam == BTN_MARKET) {
   class=class="str">"cmt">// Button "Market" clicked. Close all positions related to the current chart symbol.
   Print(sparam + " CLICKED. CLOSE ALL POSITIONS NOW");

   class=class="str">"cmt">// Iterate through all open positions
   for (class="type">int i = class="num">0; i <= PositionsTotal(); i++) {
      class="type">class="kw">ulong ticket = PositionGetTicket(i);
      if (ticket > class="num">0) {
         if (PositionSelectByTicket(ticket)) {
            class=class="str">"cmt">// Check if the position symbol matches the current chart symbol
            if (PositionGetString(POSITION_SYMBOL) == _Symbol) {
               obj_Trade.PositionClose(ticket); class=class="str">"cmt">// Close the position
            }
         }
      }
   }

   class=class="str">"cmt">// Force a redrawing of the chart to reflect the changes
   ChartRedraw(class="num">0);
}
else if (sparam == BTN_PROFIT) {
   class=class="str">"cmt">// Button "Profit" clicked. Close all positions in profit now.

按钮触发后只平同图表盈利单

在 MT5 面板里点下「Profit」按钮,脚本会先向日志打出提示,随后立刻扫描当前账户所有持仓。它并非无差别平仓,而是只处理与当前图表品种(_Symbol)一致的单子,这一过滤很关键,避免误伤其他货币对或贵金属持仓。 循环用 PositionsTotal() 作上界,逐个取 ticket;若 PositionSelectByTicket 成功且品种匹配,就读取 POSITION_PROFIT。当该值大于 0 时调用 obj_Trade.PositionClose(ticket) 平掉盈利仓,循环结束再 ChartRedraw(0) 强制重绘。 若点到「Loss」分支,逻辑镜像:只平同品种且 profit_or_loss <= 0 的亏损单。外汇与贵金属杠杆高,这类一键平仓在剧烈波动时可能因滑点导致实际平仓价偏离,建议先在模拟盘验证按钮响应。

MQL5 / C++
Print(sparam + " CLICKED. CLOSE ALL POSITIONS IN PROFIT NOW");
class=class="str">"cmt">// Iterate through all open positions
for (class="type">int i = class="num">0; i <= PositionsTotal(); i++) {
   class="type">class="kw">ulong ticket = PositionGetTicket(i);
   if (ticket > class="num">0) {
      if (PositionSelectByTicket(ticket)) {
         class=class="str">"cmt">// Check if the position symbol matches the current chart symbol
         if (PositionGetString(POSITION_SYMBOL) == _Symbol) {
            class="type">class="kw">double profit_or_loss = PositionGetDouble(POSITION_PROFIT);
            if (profit_or_loss > class="num">0) {
               obj_Trade.PositionClose(ticket); class=class="str">"cmt">// Close the position
            }
         }
      }
   }
}
class=class="str">"cmt">// Force a redrawing of the chart to reflect the changes
ChartRedraw(class="num">0);
else if (sparam == BTN_LOSS) {
   class=class="str">"cmt">// Button "Loss" clicked. Close all positions in loss now.
   Print(sparam + " CLICKED. CLOSE ALL POSITIONS IN LOSS NOW");
   class=class="str">"cmt">// Iterate through all open positions
   for (class="type">int i = class="num">0; i <= PositionsTotal(); i++) {
      class="type">class="kw">ulong ticket = PositionGetTicket(i);
      if (ticket > class="num">0) {
         if (PositionSelectByTicket(ticket)) {
            class=class="str">"cmt">// Check if the position symbol matches the current chart symbol

「一键清仓与挂单撤销的事件分支」

在 MT5 面板脚本里,按钮回调通常用 sparam 区分来源。当 sparam 命中 BTN_CLOSE,逻辑会遍历当前品种的所有持仓,只要浮亏仓位就直接市价平掉。 下面这段是亏损平仓的核心片段,注意它先用 PositionGetString 比对 _Symbol,避免误杀其他图表品种的仓位: if (PositionGetString(POSITION_SYMBOL) == _Symbol) { double profit_or_loss = PositionGetDouble(POSITION_PROFIT); if (profit_or_loss < 0) { obj_Trade.PositionClose(ticket); // Close the position } } 紧接其后,BTN_PENDING 分支用 OrdersTotal 做正向循环,通过 OrderSelect + OrderGetString(ORDER_SYMBOL) 过滤,把本品种挂单全部 OrderDelete。两个分支末尾都调 ChartRedraw(0),否则图表上的对象状态不会立刻刷新。 外汇与贵金属杠杆高,这类“一键砍亏”在快速波动中可能以滑点成交,实际平仓价与触发时浮亏有偏差。 Lots 相关控件(BTN_LOTS / LABEL_LOTS / ICON_DROP_DN1)命中后先 ObjectSetInteger 把下拉按钮置为 true 态,再生成下拉列表,这一步是后面手调手数的前置交互。

MQL5 / C++
if (PositionGetString(POSITION_SYMBOL) == _Symbol) {
  class="type">class="kw">double profit_or_loss = PositionGetDouble(POSITION_PROFIT);
  if (profit_or_loss < class="num">0) {
    obj_Trade.PositionClose(ticket); class=class="str">"cmt">// Close the position
  }
}

class=class="str">"cmt">// Force a redrawing of the chart to reflect the changes
ChartRedraw(class="num">0);

else if (sparam == BTN_PENDING) {
  class=class="str">"cmt">// Button "Pending" clicked. Delete all pending orders related to the current chart symbol.
  Print(sparam + " CLICKED. DELETE ALL PENDING ORDERS NOW");

  class=class="str">"cmt">// Iterate through all pending orders
  for (class="type">int i = class="num">0; i <= OrdersTotal(); i++) {
    class="type">class="kw">ulong ticket = OrderGetTicket(i);
    if (ticket > class="num">0) {
      if (OrderSelect(ticket)) {
        class=class="str">"cmt">// Check if the order symbol matches the current chart symbol
        if (OrderGetString(ORDER_SYMBOL) == _Symbol) {
          obj_Trade.OrderDelete(ticket); class=class="str">"cmt">// Delete the order
        }
      }
    }
  }

  class=class="str">"cmt">// Force a redrawing of the chart to reflect the changes
  ChartRedraw(class="num">0);
}

else if (sparam == BTN_LOTS || sparam == LABEL_LOTS || sparam == ICON_DROP_DN1) {
  class=class="str">"cmt">// Button "Lots," label "Lots," or dropdown icon clicked. Create a dropdown list.
  Print(sparam + " CLICKED. CREATE A DROPDOWN LIST");

  class=class="str">"cmt">// Enable the button for dropdown functionality
  ObjectSetInteger(class="num">0, BTN_LOTS, OBJPROP_STATE, true);

  class=class="str">"cmt">// Create the dropdown list

◍ 下拉选项回写与控件销毁的细节

这段逻辑处理的是下拉列表被点选后的闭环:用户点了某个选项标签,脚本先把对应文本读出来,再写回主标签,最后拆掉下拉控件并刷新图表。 以 LABEL_OPT1 为例,先通过 ObjectGetString(0, LABEL_OPT1, OBJPROP_TEXT) 拿到选项文字,再用 ObjectGetInteger(0, BTN_LOTS, OBJPROP_STATE) 读取手数按钮的当前状态。若按钮原先是启用态(true),则在回写文本后将其置为 false,避免重复触发。 destroyDropDown() 一口气删掉 BTN_DROP_DN、LABEL_OPT1~3、ICON_DRAG 共 5 个图形对象,随后 ChartRedraw(0) 让图表立即重绘。LABEL_OPT2 的分支几乎同构,只是 Print 打出 "LABEL RISK % CLICKED",证明下拉也能切到风险百分比模式。 开 MT5 把这段接进你自己的面板回调里,点一下选项就能直观看到主标签文字跳变、按钮复位、下拉消失——外汇与贵金属波动剧烈,这类 UI 交互测试请在模拟账户先做。

MQL5 / C++
      createDropDown();
      
      class=class="str">"cmt">// Redraw the chart to reflect the changes
      ChartRedraw(class="num">0);
      }
      else if (sparam == LABEL_OPT1) {
      
      class=class="str">"cmt">// Label "Lots" clicked.
      Print("LABEL LOTS CLICKED");
      
      class=class="str">"cmt">// Get the text from LABEL_OPT1
      class="type">class="kw">string text = ObjectGetString(class="num">0, LABEL_OPT1, OBJPROP_TEXT);
      
      class=class="str">"cmt">// Get the state of the button(enabled or disabled)
      class="type">bool btnState = ObjectGetInteger(class="num">0, BTN_LOTS, OBJPROP_STATE);
      
      class=class="str">"cmt">// Set the text of LABEL_LOTS to match LABEL_OPT1
      ObjectSetString(class="num">0, LABEL_LOTS, OBJPROP_TEXT, text);
      
      class=class="str">"cmt">// Destroy the dropdown list
      destroyDropDown();
      
      class=class="str">"cmt">// If the button was previously enabled, disable it
      if (btnState == true) {
      ObjectSetInteger(class="num">0, BTN_LOTS, OBJPROP_STATE, false);
      }
      
      class=class="str">"cmt">// Redraw the chart
      ChartRedraw(class="num">0);
      }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|    Function to destroy dropdown                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void destroyDropDown(){
   ObjectDelete(class="num">0,BTN_DROP_DN);
   ObjectDelete(class="num">0,LABEL_OPT1);
   ObjectDelete(class="num">0,LABEL_OPT2);
   ObjectDelete(class="num">0,LABEL_OPT3);
   ObjectDelete(class="num">0,ICON_DRAG);
   ChartRedraw(class="num">0);
}
      else if (sparam==LABEL_OPT2){
       Print("LABEL RISK % CLICKED");
       class="type">class="kw">string text = ObjectGetString(class="num">0,LABEL_OPT2,OBJPROP_TEXT);
       class="type">bool btnState = ObjectGetInteger(class="num">0,BTN_LOTS,OBJPROP_STATE);
       ObjectSetString(class="num">0,LABEL_LOTS,OBJPROP_TEXT,text);
       destroyDropDown();
       if (btnState==true){
       ObjectSetInteger(class="num">0,BTN_LOTS,OBJPROP_STATE,false);
       }
       ChartRedraw(class="num">0);
      }
      else if (sparam==LABEL_OPT3){
把重复刷新交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时报价与交互状态,你只管在面板里做决策。

常见问题

常见原因是未将刷新逻辑挂到 OnTick 或图表事件未启用;也可能面板元素未暴露更新接口,需确认控件引用在 tick 作用域内。
目前小布内置的是行情诊断与交互状态展示,不直接输出 MQL5 源码,但可对照它的交互逻辑反推你面板的响应结构。
倾向用状态比对只在进入/离开区域时重绘,而不是每 tick 重设尺寸,避免无谓重绘导致闪烁。
概率上会,若两者都写同一变量需设优先级:直接输入时锁增减、增减时覆盖输入,否则值会来回跳。