在 MQL5 中创建交互式图形用户界面(第 2 部分):添加控制和响应·进阶篇
(2/3)·静态面板只是定格快照,本篇教你解冻界面、接上实时报价与可拖拽交互
图表对象点击如何触发平仓逻辑
在 MT5 的 OnChartEvent 回调里,图表上任何对象被点击都会抛出 CHARTEVENT_OBJECT_CLICK 事件,并携带 id、lparam、dparam、sparam 四个参数。其中 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 里,点关闭按钮就能看到指定品种仓位被清掉。
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 数据跑一遍品种过滤逻辑。
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 新建脚本改这行就能验证差异。
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 的亏损单。外汇与贵金属杠杆高,这类一键平仓在剧烈波动时可能因滑点导致实际平仓价偏离,建议先在模拟盘验证按钮响应。
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 态,再生成下拉列表,这一步是后面手调手数的前置交互。
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 交互测试请在模拟账户先做。
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){