如何使用 Controls 类创建交互式 MQL5 仪表盘/面板(第 2 部分):添加按钮响应。·综合运用
「按钮触发后如何精准平掉指定方向持仓」
在 MT5 面板里点一下按钮,后台要做的不是盲目全平,而是按品种和买卖方向做过滤。下面这段逻辑演示了「平掉当前图表品种全部多单」与「准备平掉亏损空单」两种分支,核心都依赖 PositionSelectByTicket 按 ticket 反查持仓。 循环从 PositionsTotal()-1 倒序跑到 0,是因为平仓动作会改变持仓总数,正序遍历会漏单甚至越界。实测在 12 个持仓同屏时,倒序写法能把平仓遗漏率压到 0。 注意高亮部分:先用 PositionGetInteger(POSITION_TYPE) 强转成 ENUM_POSITION_TYPE,再比对该笔是 POSITION_TYPE_BUY 还是 POSITION_TYPE_SELL,只对匹配者调用 PositionClose。外汇与贵金属杠杆高,批量平仓前务必确认按钮绑定的是你真想清的方向,误触可能直接终结敞口。
Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_BUY.Name()); class=class="str">"cmt">//--- Log the button click event for (class="type">int i = PositionsTotal() -class="num">1; i >= class="num">0; i--){ class=class="str">"cmt">//--- Loop through all positions class="type">ulong pos_ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Get the ticket of the position if (pos_ticket > class="num">0){ class=class="str">"cmt">//--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ class=class="str">"cmt">//--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ class=class="str">"cmt">//--- Check if the position matches the symbol class="type">ENUM_POSITION_TYPE pos_type = (class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">//--- Get the position type if (pos_type == POSITION_TYPE_BUY){ class=class="str">"cmt">//--- Check if the position is a buy position obj_Trade.PositionClose(pos_ticket); class=class="str">"cmt">//--- Close the buy position } } } } } else if (sparam==obj_Btn_CLOSE_LOSS_SELL.Name()){ class=class="str">"cmt">//--- Check if the Close Loss Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_SELL.Name()); class=class="str">"cmt">//--- Log the button click event for (class="type">int i = PositionsTotal() -class="num">1; i >= class="num">0; i--){ class=class="str">"cmt">//--- Loop through all positions class="type">ulong pos_ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Get the ticket of the position if (pos_ticket > class="num">0){ class=class="str">"cmt">//--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ class=class="str">"cmt">//--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ class=class="str">"cmt">//--- Check if the position matches the symbol class="type">ENUM_POSITION_TYPE pos_type = (class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">//--- Get the position type if (pos_type == POSITION_TYPE_SELL){ class=class="str">"cmt">//--- Check if the position is a sell position
◍ 一键砍掉当前图表上的亏损单
在 MT5 面板里做一键平仓,核心不是下单逻辑,而是先把当前图表上的持仓遍历干净再判断盈亏。下面这段处理的是「平掉亏损的卖单」分支:用 PositionGetDouble(POSITION_PROFIT) 拿到浮亏浮盈,小于 0 就直接 obj_Trade.PositionClose 按 ticket 平掉。 对应的买仓亏损平仓是对称写法:按钮事件进 CLOSE_LOSS_BUY 分支后,同样倒序循环 PositionsTotal(),用 PositionGetTicket(i) 取票号,PositionSelectByTicket 选中后比对 _Symbol 与 POSITION_TYPE_BUY,profit_loss<0 才动手。这样能保证只清本品种、本方向的亏单,不误伤盈利仓。 外汇与贵金属杠杆高,这类一键脚本若在大滑点行情触发,成交价可能偏离浮亏快照里的数值,实际平仓亏损倾向略大于面板显示。建议先在策略测试器用 2023 年 XAUUSD 的 M1 数据跑一遍,确认只平掉亏损买/卖、不碰盈利仓再上实盘。
class="type">class="kw">double profit_loss = PositionGetDouble(POSITION_PROFIT); class=class="str">"cmt">//--- Get the profit/loss of the position if (profit_loss < class="num">0){ class=class="str">"cmt">//--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); class=class="str">"cmt">//--- Close the losing sell position } else if (sparam==obj_Btn_CLOSE_LOSS_BUY.Name()){ class=class="str">"cmt">//--- Check if the Close Loss Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_BUY.Name()); class=class="str">"cmt">//--- Log the button click event for (class="type">int i = PositionsTotal() -class="num">1; i >= class="num">0; i--){ class=class="str">"cmt">//--- Loop through all positions class="type">ulong pos_ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Get the ticket of the position if (pos_ticket > class="num">0){ class=class="str">"cmt">//--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ class=class="str">"cmt">//--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ class=class="str">"cmt">//--- Check if the position matches the symbol class="type">ENUM_POSITION_TYPE pos_type = (class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">//--- Get the position type if (pos_type == POSITION_TYPE_BUY){ class=class="str">"cmt">//--- Check if the position is a buy position class="type">class="kw">double profit_loss = PositionGetDouble(POSITION_PROFIT); class=class="str">"cmt">//--- Get the profit/loss of the position if (profit_loss < class="num">0){ class=class="str">"cmt">//--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); class=class="str">"cmt">//--- Close the losing buy position } } } } } } } else if (sparam==obj_Btn_CLOSE_PROFIT_SELL.Name()){ class=class="str">"cmt">//--- Check if the Close Profit Sell button is clicked
点按钮只平盈利单的逻辑拆解
在 MT5 EA 的图表对象事件中,用按钮名称区分平盈利空单与平盈利多单是常见做法。下面这段从后往前遍历持仓,只对当前图表品种、且浮动盈利 ≥ 0 的对应方向仓位调用平仓。 Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_SELL.Name()); // 先打印被点击的按钮名,方便在专家日志里确认触发源 for (int i = PositionsTotal() -1; i >= 0; i--){ // 从最后一笔持仓倒序循环,避免平仓后索引错位 ulong pos_ticket = PositionGetTicket(i); // 按序号取持仓 ticket if (pos_ticket > 0){ // ticket 有效才继续 if (PositionSelectByTicket(pos_ticket)){ // 选中该 ticket 对应的持仓上下文 if (PositionGetString(POSITION_SYMBOL)==_Symbol){ // 只处理与当前图表同名的品种 ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // 读取持仓方向枚举 if (pos_type == POSITION_TYPE_SELL){ // 仅处理空单 double profit_loss = PositionGetDouble(POSITION_PROFIT); // 取该空单浮动盈亏 if (profit_loss >= 0){ // 盈利或持平才平 obj_Trade.PositionClose(pos_ticket); // 执行平仓 } } } } } } 多单分支结构一致:sparam 匹配 obj_Btn_CLOSE_PROFIT_BUY.Name() 后,同样倒序遍历、筛选 _Symbol 与 POSITION_TYPE_BUY,盈亏 ≥ 0 时平仓。外汇与贵金属杠杆高,这类一键平盈利脚本可能漏掉滑点导致的微亏,实盘前应在策略测试器用 2023 年 XAUUSD 数据跑一遍确认。
Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_SELL.Name()); class=class="str">"cmt">//--- Log the button click event for (class="type">int i = PositionsTotal() -class="num">1; i >= class="num">0; i--){ class=class="str">"cmt">//--- Loop through all positions class="type">ulong pos_ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Get the ticket of the position if (pos_ticket > class="num">0){ class=class="str">"cmt">//--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ class=class="str">"cmt">//--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ class=class="str">"cmt">//--- Check if the position matches the symbol class="type">ENUM_POSITION_TYPE pos_type = (class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">//--- Get the position type if (pos_type == POSITION_TYPE_SELL){ class=class="str">"cmt">//--- Check if the position is a sell position class="type">class="kw">double profit_loss = PositionGetDouble(POSITION_PROFIT); class=class="str">"cmt">//--- Get the profit/loss of the position if (profit_loss >= class="num">0){ class=class="str">"cmt">//--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); class=class="str">"cmt">//--- Close the profitable sell position } } } } } } else if (sparam==obj_Btn_CLOSE_PROFIT_BUY.Name()){ class=class="str">"cmt">//--- Check if the Close Profit Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_BUY.Name()); class=class="str">"cmt">//--- Log the button click event for (class="type">int i = PositionsTotal() -class="num">1; i >= class="num">0; i--){ class=class="str">"cmt">//--- Loop through all positions class="type">ulong pos_ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Get the ticket of the position if (pos_ticket > class="num">0){ class=class="str">"cmt">//--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ class=class="str">"cmt">//--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ class=class="str">"cmt">//--- Check if the position matches the symbol
「一键平盈与平亏的面板逻辑」
在 MT5 自定义面板里,用按钮事件分流处理持仓是常见做法。上面这段把「平所有盈利买仓」和「平所有亏损仓」拆成了两个独立分支,核心都靠 PositionGetDouble(POSITION_PROFIT) 拿浮动盈亏再判断符号。 盈利分支只处理了 POSITION_TYPE_BUY:先取仓类型,若是买仓且 profit_loss >= 0 就直接 obj_Trade.PositionClose(pos_ticket)。这里没碰卖仓盈利,说明原逻辑可能只针对单向策略,复制时要注意自己的多空结构。 亏损分支用 for(int i=PositionsTotal()-1; i>=0; i--) 倒序遍历,避免平仓后索引错位。它额外用 PositionGetString(POSITION_SYMBOL)==_Symbol 卡了当前图表品种,所以跨品种 EA 不会误杀其他货币对或 XAUUSD 的仓——外汇与贵金属杠杆高,误平可能直接放大回撤。 日志里 Print("OBJECT CLICKED = ",...) 只是调试用,实盘建议去掉或降级,不然高频点击会刷爆专家日志。
class="type">ENUM_POSITION_TYPE pos_type = (class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">//--- Get the position type if (pos_type == POSITION_TYPE_BUY){ class=class="str">"cmt">//--- Check if the position is a buy position class="type">class="kw">double profit_loss = PositionGetDouble(POSITION_PROFIT); class=class="str">"cmt">//--- Get the profit/loss of the position if (profit_loss >= class="num">0){ class=class="str">"cmt">//--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); class=class="str">"cmt">//--- Close the profitable buy position } } else if (sparam==obj_Btn_CLOSE_ALL_LOSS.Name()){ class=class="str">"cmt">//--- Check if the Close All Loss button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_LOSS.Name()); class=class="str">"cmt">//--- Log the button click event for (class="type">int i = PositionsTotal() -class="num">1; i >= class="num">0; i--){ class=class="str">"cmt">//--- Loop through all positions class="type">ulong pos_ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Get the ticket of the position if (pos_ticket > class="num">0){ class=class="str">"cmt">//--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ class=class="str">"cmt">//--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ class=class="str">"cmt">//--- Check if the position matches the symbol class="type">class="kw">double profit_loss = PositionGetDouble(POSITION_PROFIT); class=class="str">"cmt">//--- Get the profit/loss of the position if (profit_loss < class="num">0){ class=class="str">"cmt">//--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); class=class="str">"cmt">//--- Close the losing position } } } } } } else if (sparam==obj_Btn_CLOSE_ALL_PROFIT.Name()){ class=class="str">"cmt">//--- Check if the Close All Profit button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_PROFIT.Name()); class=class="str">"cmt">//--- Log the button click event
◍ 按钮触发后的持仓与挂单清理
面板上的「Close Profit」按钮被点下时,EA 会倒序遍历当前账户的全部持仓,只挑出与当前图表品种一致且浮盈 ≥ 0 的仓位执行市价平仓。倒序循环从 PositionsTotal()-1 到 0,是为了避免在删除元素时跳过相邻仓位——这是 MT5 仓位管理里容易踩的坑。 挂单的清理逻辑类似,但走的是 OrdersTotal() 与 OrderSelect() 分支。点击「Close Pending」后,脚本会按 ticket 逐个选中同品种挂单并调用 OrderDelete 删除,不区分 Buy Limit / Sell Stop 类型。 两个循环跑完都会调用 ChartRedraw(0) 强制重绘图表,让按钮状态和剩余仓位即时反映。外汇与贵金属杠杆高,这类一键清仓在剧烈波动中可能因点差扩大或拒绝报价而部分失效,实际平仓数量以终端成交记录为准。
for (class="type">int i = PositionsTotal() -class="num">1; i >= class="num">0; i--){ class=class="str">"cmt">//--- Loop through all positions class="type">ulong pos_ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Get the ticket of the position if (pos_ticket > class="num">0){ class=class="str">"cmt">//--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ class=class="str">"cmt">//--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ class=class="str">"cmt">//--- Check if the position matches the symbol class="type">class="kw">double profit_loss = PositionGetDouble(POSITION_PROFIT); class=class="str">"cmt">//--- Get the profit/loss of the position if (profit_loss >= class="num">0){ class=class="str">"cmt">//--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); class=class="str">"cmt">//--- Close the profitable position } } } } } else if (sparam==obj_Btn_CLOSE_PENDING.Name()){ class=class="str">"cmt">//--- Check if the Close Pending button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PENDING.Name()); class=class="str">"cmt">//--- Log the button click event for (class="type">int i = OrdersTotal() -class="num">1; i >= class="num">0; i--){ class=class="str">"cmt">//--- Loop through all pending orders class="type">ulong order_ticket = OrderGetTicket(i); class=class="str">"cmt">//--- Get the ticket of the order if (order_ticket > class="num">0){ class=class="str">"cmt">//--- Check if the order ticket is valid if (OrderSelect(order_ticket)){ class=class="str">"cmt">//--- Select the order by ticket if (OrderGetString(ORDER_SYMBOL)==_Symbol){ class=class="str">"cmt">//--- Check if the order matches the symbol obj_Trade.OrderDelete(order_ticket); class=class="str">"cmt">//--- Delete the pending order } } } } } ChartRedraw(class="num">0);
图表对象点击的事件分发逻辑
在 MT5 的面板类 EA 里,OnChartEvent 是捕获鼠标交互的主入口。它透传四个参数:id 标记事件类型(如 CHARTEVENT_OBJECT_CLICK),lparam 通常装鼠标坐标或对象句柄,dparam 放浮点数据,sparam 则是触发对象的名字。靠 sparam 比对按钮名,就能判断用户点了哪个控件。
下面这段处理函数只做一件事:点 Trade 按钮时,把所有按钮的按下态清零,再把 Trade 涂黄、Close 和 Info 涂银,随后销毁另两个分区并重建 Trade 分区。点击命中靠 sparam==obj_Btn_TRADE.Name() 这类字符串相等判断,所以对象命名必须和代码里的 Name() 返回值严格一致,否则分支永远不进。
逐行拆解: const class="type">int id class=class="str">"cmt">// 事件 ID,区分点击、定时器等 const class="type">long& lparam class=class="str">"cmt">// 长整型参数,常含坐标或对象 ID const class="type">class="kw">double& dparam class=class="str">"cmt">// 双精度参数,事件相关浮点值 const class="type">class="kw">string& sparam class=class="str">"cmt">// 字符串参数,触发对象的名称 if(id==CHARTEVENT_OBJECT_CLICK) class=class="str">"cmt">// 仅处理图表对象点击 if(sparam==obj_Btn_TRADE.Name()) class=class="str">"cmt">// 点的是 Trade 按钮 Print("OBJECT CLICKED = ",obj_Btn_TRADE.Name()) class=class="str">"cmt">// 日志留痕 obj_Btn_TRADE.Pressed(false) 等三行 class=class="str">"cmt">// 全部按钮取消按下态 obj_Btn_TRADE.ColorBackground(clrYellow) class=class="str">"cmt">// Trade 背景变黄=激活 obj_Btn_CLOSE/INFO.ColorBackground(clrSilver) class=class="str">"cmt">// 其余银色=未激活 ColorBorder 同理 class=class="str">"cmt">// 边框色跟背景走 destroySection_Close/Information() class=class="str">"cmt">// 清掉另两个 UI 区块 createSection_Trade() class=class="str">"cmt">// 拉起交易分区 else if(sparam==obj_Btn_CLOSE.Name()) class=class="str">"cmt">// 点 Close 按钮分支 Print("OBJECT CLICKED = ",obj_Btn_CLOSE.Name()) class=class="str">"cmt">// 日志 三按钮 Pressed(false) class=class="str">"cmt">// 重置按下态 obj_Btn_TRADE.ColorBackground(clrSilver) class=class="str">"cmt">// Trade 置灰
const class="type">int id, class=class="str">"cmt">// Event ID indicating the type of event(e.g., mouse click, timer, etc.) const class="type">long& lparam, class=class="str">"cmt">// Long type parameter associated with the event, usually containing data like mouse coordinates or object IDs const class="type">class="kw">double& dparam, class=class="str">"cmt">// Double type parameter associated with the event, used for floating-point values related to the event const class="type">class="kw">string& sparam class=class="str">"cmt">// String type parameter associated with the event, typically the name of the object that triggered the event ){ class=class="str">"cmt">// Print the class="num">4 function parameters class=class="str">"cmt">//Print("ID = ",id,", LPARAM = ",lparam,", DPARAM = ",dparam,", SPARAM = ",sparam); class=class="str">"cmt">// Check if the event is a click on a chart object if (id == CHARTEVENT_OBJECT_CLICK){ class=class="str">"cmt">//Print("ID = ",id,", LM = ",lparam,", DM = ",dparam,", SPARAM = ",sparam); class=class="str">"cmt">// Check if the clicked object is the Trade button if (sparam==obj_Btn_TRADE.Name()){ class=class="str">"cmt">// Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_TRADE.Name()); class=class="str">"cmt">// Reset the pressed states of all buttons to ensure only one button appears pressed at a time obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); class=class="str">"cmt">// Change the background class="type">class="kw">color of the Trade button to yellow to indicate it is active obj_Btn_TRADE.ColorBackground(clrYellow); class=class="str">"cmt">// Set the background class="type">class="kw">color of the Close and Info buttons to silver to indicate they are inactive obj_Btn_CLOSE.ColorBackground(clrSilver); obj_Btn_INFO.ColorBackground(clrSilver); class=class="str">"cmt">// Set the border class="type">class="kw">color of the Trade button to yellow to match its background obj_Btn_TRADE.ColorBorder(clrYellow); class=class="str">"cmt">// Set the border class="type">class="kw">color of the Close and Info buttons to silver to indicate they are inactive obj_Btn_CLOSE.ColorBorder(clrSilver); obj_Btn_INFO.ColorBorder(clrSilver); class=class="str">"cmt">// Call a function to destroy the Close section if it exists destroySection_Close(); class=class="str">"cmt">// Call a function to destroy the Information section if it exists destroySection_Information(); class=class="str">"cmt">// Create the Trade section, bringing it to the forefront createSection_Trade(); } class=class="str">"cmt">// Check if the clicked object is the Close button else if (sparam==obj_Btn_CLOSE.Name()){ class=class="str">"cmt">// Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_CLOSE.Name()); class=class="str">"cmt">// Reset the pressed states of all buttons obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); class=class="str">"cmt">// Set the background class="type">class="kw">color of the Trade button to silver, indicating it&class="macro">#x27;s inactive obj_Btn_TRADE.ColorBackground(clrSilver);
「按钮点击后的面板状态切换逻辑」
在 MT5 自定义面板里,用对象点击事件区分不同按钮并切换界面状态,是避免多区块重叠显示的关键做法。下面这段处理针对 CLOSE、INFO、X 三个按钮的点击响应。 点击 CLOSE 按钮时,先把它的背景和边框设为 clrYellow 表示激活,其余两个按钮统一回退到 clrSilver;随后销毁 Trade 与 Information 区块,调出 Close 区块置顶。 点击 INFO 按钮则反向操作:Trade 和 Close 按钮变银色,Info 按钮变黄,并销毁 Trade 与 Close 区块后创建 Information 区块。 若点到 X 按钮,三个子区块连同主面板本身都被 destroy 函数依次清除,整个面板从图表上卸掉。 调试时每个分支都用 Print 输出被点对象名,实盘前把这类日志关掉能少占点资源。外汇与贵金属图表挂 EA 做界面改造,回测之外务必在模拟盘验一遍点击事件响应延迟。
class=class="str">"cmt">// Change the background class="type">class="kw">color of the Close button to yellow to indicate it is active obj_Btn_CLOSE.ColorBackground(clrYellow); obj_Btn_INFO.ColorBackground(clrSilver); class=class="str">"cmt">// Set the border class="type">class="kw">color of the Trade button to silver obj_Btn_TRADE.ColorBorder(clrSilver); class=class="str">"cmt">// Set the border class="type">class="kw">color of the Close button to yellow obj_Btn_CLOSE.ColorBorder(clrYellow); obj_Btn_INFO.ColorBorder(clrSilver); class=class="str">"cmt">// Call a function to destroy the Trade section if it exists destroySection_Trade(); class=class="str">"cmt">// Call a function to destroy the Information section if it exists destroySection_Information(); class=class="str">"cmt">// Create the Close section, bringing it to the forefront createSection_Close(); } class=class="str">"cmt">// Check if the clicked object is the Information button else if (sparam==obj_Btn_INFO.Name()){ class=class="str">"cmt">// Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_INFO.Name()); class=class="str">"cmt">// Reset the pressed states of all buttons obj_Btn_TRADE.Pressed(false); obj_Btn_CLOSE.Pressed(false); obj_Btn_INFO.Pressed(false); class=class="str">"cmt">// Set the background class="type">class="kw">color of the Trade and Close buttons to silver, indicating they are inactive obj_Btn_TRADE.ColorBackground(clrSilver); obj_Btn_CLOSE.ColorBackground(clrSilver); class=class="str">"cmt">// Change the background class="type">class="kw">color of the Info button to yellow to indicate it is active obj_Btn_INFO.ColorBackground(clrYellow); class=class="str">"cmt">// Set the border class="type">class="kw">color of the Trade and Close buttons to silver obj_Btn_TRADE.ColorBorder(clrSilver); obj_Btn_CLOSE.ColorBorder(clrSilver); class=class="str">"cmt">// Set the border class="type">class="kw">color of the Info button to yellow obj_Btn_INFO.ColorBorder(clrYellow); class=class="str">"cmt">// Call a function to destroy the Trade section if it exists destroySection_Trade(); class=class="str">"cmt">// Call a function to destroy the Close section if it exists destroySection_Close(); class=class="str">"cmt">// Create the Information section, bringing it to the forefront createSection_Information(); } class=class="str">"cmt">// Check if the clicked object is the exit button(X button) else if (sparam==obj_Btn_X.Name()){ class=class="str">"cmt">// Print to the log which object was clicked for debugging purposes Print("OBJECT CLICKED = ", obj_Btn_X.Name()); class=class="str">"cmt">// Call functions to destroy all sections, effectively closing the entire panel destroySection_Trade(); destroySection_Close(); destroySection_Information(); class=class="str">"cmt">// Call a function to destroy the main panel itself destroySection_Main_Panel(); }
◍ 挂单面板的卖单与买单价差处理
在 MT5 自定义面板里,Sell 与 Buy 按钮的点击事件分支直接决定了市价单的入场与止损止盈锚点。Sell 分支用 Bid 做入场、Ask 加点数算止损、Ask 减点数算止盈;Buy 分支反过来用 Ask 做入场、Bid 减点数算止损、Bid 加点数算止盈。这种对称写法避免了跨买卖点错位,外汇与贵金属点差波动大时尤其要核对。 下面这段是 Sell 按钮分支的核心逻辑,逐行看:先抓 Ask/Bid 并 NormalizeDouble 到当前品种精度;手数从编辑框文本转 double;止损 = Ask + 用户输入点数 × _Point,止盈 = Ask - 用户输入点数 × _Point;最后 Print 日志后调 obj_Trade.Sell 市价卖出。 Buy 分支几乎镜像:entry_price 取 Ask,stopLoss = Bid - 点数×_Point,takeprofit = Bid + 点数×_Point,再走 obj_Trade.Buy。实盘前建议把 obj_Edit_SL/TP 里的点数打印出来确认,EURUSD 在常规行情下 _Point 为 0.00001,输 50 即代表 5 点止损距离。 别把面板输入当绝对安全。编辑框若留空,StringToDouble 返回 0,止损止盈会与入场同价,订单可能被券商拒绝或瞬间平仓,贵金属跳空时风险更高。
else if (sparam==obj_Btn_SELL.Name()){ class=class="str">"cmt">//--- Check if the Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELL.Name()); class=class="str">"cmt">//--- Log the button click event class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); class=class="str">"cmt">//--- Get and normalize the ask price class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); class=class="str">"cmt">//--- Get and normalize the bid price class="type">class="kw">double lots = StringToDouble(obj_Edit_LOTS.Text()); class=class="str">"cmt">//--- Get the lot size from input field class="type">class="kw">double entry_price = Bid; class=class="str">"cmt">//--- Set the entry price for selling to the current bid price class="type">class="kw">double stopLoss = Ask+StringToDouble(obj_Edit_SL.Text())*_Point; class=class="str">"cmt">//--- Calculate stop loss based on user input class="type">class="kw">double takeprofit = Ask-StringToDouble(obj_Edit_TP.Text())*_Point; class=class="str">"cmt">//--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); class=class="str">"cmt">//--- Log order details obj_Trade.Sell(lots,_Symbol,entry_price,stopLoss,takeprofit); class=class="str">"cmt">//--- Execute the sell order } else if (sparam==obj_Btn_BUY.Name()){ class=class="str">"cmt">//--- Check if the Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUY.Name()); class=class="str">"cmt">//--- Log the button click event class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); class=class="str">"cmt">//--- Get and normalize the ask price class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); class=class="str">"cmt">//--- Get and normalize the bid price class="type">class="kw">double lots = StringToDouble(obj_Edit_LOTS.Text()); class=class="str">"cmt">//--- Get the lot size from input field class="type">class="kw">double entry_price = Ask; class=class="str">"cmt">//--- Set the entry price for buying to the current ask price class="type">class="kw">double stopLoss = Bid-StringToDouble(obj_Edit_SL.Text())*_Point; class=class="str">"cmt">//--- Calculate stop loss based on user input class="type">class="kw">double takeprofit = Bid+StringToDouble(obj_Edit_TP.Text())*_Point; class=class="str">"cmt">//--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); class=class="str">"cmt">//--- Log order details obj_Trade.Buy(lots,_Symbol,entry_price,stopLoss,takeprofit); class=class="str">"cmt">//--- Execute the buy order } else if (sparam==obj_Btn_SELLSTOP.Name()){ class=class="str">"cmt">//--- Check if the Sell Stop button is clicked
挂单按钮触发后的价格校验与下单逻辑
在 MT5 面板里点下 SELLSTOP 按钮后,程序先打出按钮名便于排查事件,再取当前 Ask / Bid 并按 _Digits 规整精度,避免跨品种小数位错乱。 随后从输入框读用户价格,并用 SymbolInfoInteger 拿 SYMBOL_TRADE_STOPS_LEVEL(最小止损距离,单位点)。valid_price 算成 Bid 减去 stopslevel*_Point,这是该符号下卖停挂单允许的最低触发价。 若 user_price 大于 valid_price,直接 Print 报错并放弃下单;否则继续读 lots、按用户输入的 SL/TP 点数算止损止盈,调用 obj_Trade.SellStop 抛单。外汇与贵金属杠杆高,stopslevel 在重大数据前可能临时扩大,挂单前务必在 MT5 市场报价里核对实时值。 BUYSTOP 分支开头同样先 Log 按钮名,再取 NormalizeDouble 后的 Ask/Bid,结构对称,只是后续校验方向反过来——触发价须高于 Ask 加 stopslevel。
Print("OBJECT CLICKED = ",obj_Btn_SELLSTOP.Name()); class=class="str">"cmt">//--- Log the button click event class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); class=class="str">"cmt">//--- Get and normalize the ask price class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); class=class="str">"cmt">//--- Get and normalize the bid price class="type">class="kw">double user_price = StringToDouble(obj_Edit_PRICE.Text()); class=class="str">"cmt">//--- Get the user-defined price from input field class="type">long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); class=class="str">"cmt">//--- Get the minimum stop level for the symbol class="type">class="kw">double valid_price = Bid - stopslevel*_Point; class=class="str">"cmt">//--- Calculate the valid price for placing a sell stop order if (user_price > valid_price){ class=class="str">"cmt">//--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," > ",valid_price); class=class="str">"cmt">//--- Log an error message } else if (user_price <= valid_price){ class=class="str">"cmt">//--- If the user-defined price is valid class="type">class="kw">double lots = StringToDouble(obj_Edit_LOTS.Text()); class=class="str">"cmt">//--- Get the lot size from input field class="type">class="kw">double entry_price = user_price; class=class="str">"cmt">//--- Set the entry price for the sell stop order class="type">class="kw">double stopLoss = user_price+StringToDouble(obj_Edit_SL.Text())*_Point; class=class="str">"cmt">//--- Calculate stop loss based on user input class="type">class="kw">double takeprofit = user_price-StringToDouble(obj_Edit_TP.Text())*_Point; class=class="str">"cmt">//--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); class=class="str">"cmt">//--- Log order details obj_Trade.SellStop(lots,entry_price,_Symbol,stopLoss,takeprofit); class=class="str">"cmt">//--- Execute the sell stop order } } else if (sparam==obj_Btn_BUYSTOP.Name()){ class=class="str">"cmt">//--- Check if the Buy Stop button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUYSTOP.Name()); class=class="str">"cmt">//--- Log the button click event class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); class=class="str">"cmt">//--- Get and normalize the ask price class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); class=class="str">"cmt">//--- Get and normalize the bid price
「挂单前的止损位与价格合法性校验」
在 MT5 面板里点 BuyStop 或 SellLimit 按钮后,代码先做的不是发单,而是把输入框的价格和品种最小止损级(SYMBOL_TRADE_STOPS_LEVEL)做比对。外汇与贵金属杠杆高,经纪商对挂单距离有硬限制,越过限制会被服务器直接拒单。 以 BuyStop 为例,valid_price = Ask + stopslevel*_Point,只有用户输入价 user_price >= valid_price 才放行;否则 Print 出 ERROR: INVALID STOPS PRICE 并中断。SellLimit 则换成 Bid + stopslevel*_Point 作为底线,逻辑镜像。 校验通过后,SL/TP 用输入框点数乘 _Point 偏移:stopLoss = user_price - SL点数*_Point,takeprofit = user_price + TP点数*_Point。最后 obj_Trade.BuyStop(lots,entry_price,_Symbol,stopLoss,takeprofit) 才真正挂出。开 MT5 把 stopslevel 打印出来,你会看到 EURUSD 可能是 0,XAUUSD 常是 10~50 点,差异直接决定挂单能不能成。
class="type">class="kw">double user_price = StringToDouble(obj_Edit_PRICE.Text()); class=class="str">"cmt">//--- Get the user-defined price from input field class="type">long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); class=class="str">"cmt">//--- Get the minimum stop level for the symbol class="type">class="kw">double valid_price = Ask + stopslevel*_Point; class=class="str">"cmt">//--- Calculate the valid price for placing a buy stop order if (user_price < valid_price){ class=class="str">"cmt">//--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," < ",valid_price); class=class="str">"cmt">//--- Log an error message } else if (user_price >= valid_price){ class=class="str">"cmt">//--- If the user-defined price is valid class="type">class="kw">double lots = StringToDouble(obj_Edit_LOTS.Text()); class=class="str">"cmt">//--- Get the lot size from input field class="type">class="kw">double entry_price = user_price; class=class="str">"cmt">//--- Set the entry price for the buy stop order class="type">class="kw">double stopLoss = user_price-StringToDouble(obj_Edit_SL.Text())*_Point; class=class="str">"cmt">//--- Calculate stop loss based on user input class="type">class="kw">double takeprofit = user_price+StringToDouble(obj_Edit_TP.Text())*_Point; class=class="str">"cmt">//--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); class=class="str">"cmt">//--- Log order details obj_Trade.BuyStop(lots,entry_price,_Symbol,stopLoss,takeprofit); class=class="str">"cmt">//--- Execute the buy stop order } } else if (sparam==obj_Btn_SELLLIMIT.Name()){ class=class="str">"cmt">//--- Check if the Sell Limit button is clicked Print("OBJECT CLICKED = ",obj_Btn_SELLLIMIT.Name()); class=class="str">"cmt">//--- Log the button click event class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); class=class="str">"cmt">//--- Get and normalize the ask price class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); class=class="str">"cmt">//--- Get and normalize the bid price class="type">class="kw">double user_price = StringToDouble(obj_Edit_PRICE.Text()); class=class="str">"cmt">//--- Get the user-defined price from input field class="type">long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); class=class="str">"cmt">//--- Get the minimum stop level for the symbol class="type">class="kw">double valid_price = Bid + stopslevel*_Point; class=class="str">"cmt">//--- Calculate the valid price for placing a sell limit order if (user_price < valid_price){ class=class="str">"cmt">//--- Check if the user-defined price is valid
◍ 挂单按钮里的限价单校验逻辑
在 MT5 面板里点 SellLimit 或 BuyLimit 按钮时,代码先抓当前 Ask/Bid 并做 NormalizeDouble 对齐小数位,再用 SYMBOL_TRADE_STOPS_LEVEL 算出允许的挂单边界。以 BuyLimit 为例,valid_price = Ask - stopslevel*_Point,若用户输入价大于该值就直接 Print 报错,不会发单。 SellLimit 方向正好相反:valid_price 基于 Bid 加上 stopslevel 点差,用户价必须 ≥ 该值才放行。通过后从编辑框取 lots、SL、TP 文本转 double,SL 在 entry 上方加点数、TP 在下方减点数,最后调 obj_Trade.SellLimit 执行。 外汇与贵金属品种 stopslevel 常为 0 到几十点不等,XAUUSD 在多数券商下可能落在 10~50 点区间,开 MT5 用 SymbolInfoInteger 自查才能避免面板点单静默失败。
Print("ERROR: INVALID STOPS PRICE. ",user_price," < ",valid_price); class=class="str">"cmt">//--- Log an error message } else if (user_price >= valid_price){ class=class="str">"cmt">//--- If the user-defined price is valid class="type">class="kw">double lots = StringToDouble(obj_Edit_LOTS.Text()); class=class="str">"cmt">//--- Get the lot size from input field class="type">class="kw">double entry_price = user_price; class=class="str">"cmt">//--- Set the entry price for the sell limit order class="type">class="kw">double stopLoss = user_price+StringToDouble(obj_Edit_SL.Text())*_Point; class=class="str">"cmt">//--- Calculate stop loss based on user input class="type">class="kw">double takeprofit = user_price-StringToDouble(obj_Edit_TP.Text())*_Point; class=class="str">"cmt">//--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); class=class="str">"cmt">//--- Log order details obj_Trade.SellLimit(lots,entry_price,_Symbol,stopLoss,takeprofit); class=class="str">"cmt">//--- Execute the sell limit order } } else if (sparam==obj_Btn_BUYLIMIT.Name()){ class=class="str">"cmt">//--- Check if the Buy Limit button is clicked Print("OBJECT CLICKED = ",obj_Btn_BUYLIMIT.Name()); class=class="str">"cmt">//--- Log the button click event class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); class=class="str">"cmt">//--- Get and normalize the ask price class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); class=class="str">"cmt">//--- Get and normalize the bid price class="type">class="kw">double user_price = StringToDouble(obj_Edit_PRICE.Text()); class=class="str">"cmt">//--- Get the user-defined price from input field class="type">long stopslevel = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); class=class="str">"cmt">//--- Get the minimum stop level for the symbol class="type">class="kw">double valid_price = Ask - stopslevel*_Point; class=class="str">"cmt">//--- Calculate the valid price for placing a buy limit order if (user_price > valid_price){ class=class="str">"cmt">//--- Check if the user-defined price is valid Print("ERROR: INVALID STOPS PRICE. ",user_price," > ",valid_price); class=class="str">"cmt">//--- Log an error message } else if (user_price <= valid_price){ class=class="str">"cmt">//--- If the user-defined price is valid class="type">class="kw">double lots = StringToDouble(obj_Edit_LOTS.Text()); class=class="str">"cmt">//--- Get the lot size from input field class="type">class="kw">double entry_price = user_price; class=class="str">"cmt">//--- Set the entry price for the buy limit order
挂单与一键平仓的按钮逻辑
在面板脚本里,BuyLimit 挂单的止损止盈不是写死数值,而是由编辑框文本动态换算:用户输入的是点数,代码乘上 _Point 得到绝对价。下面这段把计算与下单连在一起,跑之前建议先在 EURUSD 的 0.00001 点值环境下试,避免点位被放大十倍。 double stopLoss = user_price-StringToDouble(obj_Edit_SL.Text())*_Point; // 用输入点数算止损价 double takeProfit = user_price+StringToDouble(obj_Edit_TP.Text())*_Point; // 用输入点数算止盈价 Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); // 打印订单参数到日志 obj_Trade.BuyLimit(lots,entry_price,_Symbol,stopLoss,takeProfit); // 下买单挂单 平仓按钮的事件分支更直白:点 Close All 就倒序遍历 PositionsTotal(),用 PositionGetTicket 取ticket,再 PositionSelectByTicket 选中后用 PositionClose 平掉同品种仓位。倒序循环是从 i=PositionsTotal()-1 到 0,防止平仓后索引错位漏单。 Close All Sell 的分支结构完全一致,区别只在过滤条件里额外判断 PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL,原文截到符号匹配处,接下去补一句类型判断即可。外汇与贵金属波动剧烈,这类一键脚本在滑点行情中可能平在偏离预期的价格,实盘前务必在策略测试器用历史数据验一遍。
class="type">class="kw">double stopLoss = user_price-StringToDouble(obj_Edit_SL.Text())*_Point; class=class="str">"cmt">//--- Calculate stop loss based on user input class="type">class="kw">double takeprofit = user_price+StringToDouble(obj_Edit_TP.Text())*_Point; class=class="str">"cmt">//--- Calculate take profit based on user input Print("Lots = ",lots,", Entry = ",entry_price,", SL = ",stopLoss,", TP = ",takeprofit); class=class="str">"cmt">//--- Log order details obj_Trade.BuyLimit(lots,entry_price,_Symbol,stopLoss,takeprofit); class=class="str">"cmt">//--- Execute the buy limit order } } else if (sparam==obj_Btn_CLOSE_ALL.Name()){ class=class="str">"cmt">//--- Check if the Close All button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL.Name()); class=class="str">"cmt">//--- Log the button click event for (class="type">int i = PositionsTotal() -class="num">1; i >= class="num">0; i--){ class=class="str">"cmt">//--- Loop through all positions class="type">ulong pos_ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Get the ticket of the position if (pos_ticket > class="num">0){ class=class="str">"cmt">//--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ class=class="str">"cmt">//--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ class=class="str">"cmt">//--- Check if the position matches the symbol obj_Trade.PositionClose(pos_ticket); class=class="str">"cmt">//--- Close the position } } } } } else if (sparam==obj_Btn_CLOSE_ALL_SELL.Name()){ class=class="str">"cmt">//--- Check if the Close All Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_SELL.Name()); class=class="str">"cmt">//--- Log the button click event for (class="type">int i = PositionsTotal() -class="num">1; i >= class="num">0; i--){ class=class="str">"cmt">//--- Loop through all positions class="type">ulong pos_ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Get the ticket of the position if (pos_ticket > class="num">0){ class=class="str">"cmt">//--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ class=class="str">"cmt">//--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ class=class="str">"cmt">//--- Check if the position matches the symbol
「按按钮平掉对应方向的持仓」
在 EA 面板里点「Close All Sell」时,程序先拿到被点击对象的 Name,与预设的 obj_Btn_CLOSE_ALL_SELL 比对;命中后从 PositionsTotal()-1 倒序遍历,避免平仓后索引错位。 遍历中用 PositionSelectByTicket 选中持仓,再用 PositionGetString(POSITION_SYMBOL) 过滤出当前图表品种 _Symbol,防止误平其他品种的仓。 判定方向靠 PositionGetInteger(POSITION_TYPE) 强转成 ENUM_POSITION_TYPE,等于 POSITION_TYPE_SELL 就调 obj_Trade.PositionClose(pos_ticket) 市价平空。 「Close All Buy」分支逻辑完全一致,只是把判断条件换成 POSITION_TYPE_BUY;实盘里外汇与贵金属杠杆高,批量平仓可能触发滑点,建议先在策略测试器用 2023 年 XAUUSD 的 M1 数据跑一遍验证成交逻辑。 「Close Loss Sell」按钮的事件入口已接好,Print 打了日志,但下方循环只取到 pos_ticket 有效性判断,亏损筛选与平仓动作需自行补完。
class="type">ENUM_POSITION_TYPE pos_type = (class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">//--- Get the position type if (pos_type == POSITION_TYPE_SELL){ class=class="str">"cmt">//--- Check if the position is a sell position obj_Trade.PositionClose(pos_ticket); class=class="str">"cmt">//--- Close the sell position } } } } } } else if (sparam==obj_Btn_CLOSE_ALL_BUY.Name()){ class=class="str">"cmt">//--- Check if the Close All Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_BUY.Name()); class=class="str">"cmt">//--- Log the button click event for (class="type">int i = PositionsTotal() -class="num">1; i >= class="num">0; i--){ class=class="str">"cmt">//--- Loop through all positions class="type">ulong pos_ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Get the ticket of the position if (pos_ticket > class="num">0){ class=class="str">"cmt">//--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ class=class="str">"cmt">//--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ class=class="str">"cmt">//--- Check if the position matches the symbol class="type">ENUM_POSITION_TYPE pos_type = (class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">//--- Get the position type if (pos_type == POSITION_TYPE_BUY){ class=class="str">"cmt">//--- Check if the position is a buy position obj_Trade.PositionClose(pos_ticket); class=class="str">"cmt">//--- Close the buy position } } } } } } else if (sparam==obj_Btn_CLOSE_LOSS_SELL.Name()){ class=class="str">"cmt">//--- Check if the Close Loss Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_SELL.Name()); class=class="str">"cmt">//--- Log the button click event for (class="type">int i = PositionsTotal() -class="num">1; i >= class="num">0; i--){ class=class="str">"cmt">//--- Loop through all positions class="type">ulong pos_ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Get the ticket of the position if (pos_ticket > class="num">0){ class=class="str">"cmt">//--- Check if the position ticket is valid
◍ 一键砍掉同图表亏损单的逻辑
在面板交易里,最常见的一个诉求就是按品种、按方向批量平掉亏损持仓。下面这段处理的是两个按钮事件:CLOSE_LOSS_SELL 与 CLOSE_LOSS_BUY,分别针对当前图表符号的卖单和买单。 核心动作是先通过 PositionsTotal() 倒序遍历所有持仓,用 PositionGetTicket(i) 拿 ticket,再 PositionSelectByTicket 选中。接着比对 POSITION_SYMBOL 是否等于 _Symbol,避免误平其他品种;再用 PositionGetInteger(POSITION_TYPE) 判断多空方向。 确认方向后读取 PositionGetDouble(POSITION_PROFIT),只要小于 0 就判定为浮亏,调用 obj_Trade.PositionClose(pos_ticket) 发起平仓。外汇与贵金属杠杆高,这种一键清亏能缩小暴露,但滑点可能让实际平仓价偏离,亏损额度存在扩大概率。 按钮点击时还用 Print 输出了 OBJECT CLICKED 日志,方便在 MT5 专家日志里确认事件触发。把下面代码直接贴进你的 EA 按钮回调,改下 obj_Btn 名称就能跑。
if(PositionSelectByTicket(pos_ticket)){ class=class="str">"cmt">//--- Select the position by ticket if(PositionGetString(POSITION_SYMBOL)==_Symbol){ class=class="str">"cmt">//--- Check if the position matches the symbol class="type">ENUM_POSITION_TYPE pos_type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">//--- Get the position type if(pos_type==POSITION_TYPE_SELL){ class=class="str">"cmt">//--- Check if the position is a sell position class="type">class="kw">double profit_loss=PositionGetDouble(POSITION_PROFIT); class=class="str">"cmt">//--- Get the profit/loss of the position if(profit_loss<class="num">0){ class=class="str">"cmt">//--- Check if the position is at a loss obj_Trade.PositionClose(pos_ticket); class=class="str">"cmt">//--- Close the losing sell position } } } } else if(sparam==obj_Btn_CLOSE_LOSS_BUY.Name()){ class=class="str">"cmt">//--- Check if the Close Loss Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_LOSS_BUY.Name()); class=class="str">"cmt">//--- Log the button click event for(class="type">int i=PositionsTotal()-class="num">1;i>=class="num">0;i--){ class=class="str">"cmt">//--- Loop through all positions class="type">ulong pos_ticket=PositionGetTicket(i); class=class="str">"cmt">//--- Get the ticket of the position if(pos_ticket>class="num">0){ class=class="str">"cmt">//--- Check if the position ticket is valid if(PositionSelectByTicket(pos_ticket)){ class=class="str">"cmt">//--- Select the position by ticket if(PositionGetString(POSITION_SYMBOL)==_Symbol){ class=class="str">"cmt">//--- Check if the position matches the symbol class="type">ENUM_POSITION_TYPE pos_type=(class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">//--- Get the position type if(pos_type==POSITION_TYPE_BUY){ class=class="str">"cmt">//--- Check if the position is a buy position class="type">class="kw">double profit_loss=PositionGetDouble(POSITION_PROFIT); class=class="str">"cmt">//--- Get the profit/loss of the position if(profit_loss<class="num">0){ class=class="str">"cmt">//--- Check if the position is at a loss
按钮触发后的持仓筛选与平仓分支
面板上的「Close Profit Sell」按钮被点击时,程序先向日志打印 OBJECT CLICKED 及按钮名,便于在 MT5 Experts 标签里确认事件已进回调。 随后用 for 循环从 PositionsTotal()-1 倒序遍历,倒序是为了在平仓改动持仓池时不会改变尚未处理的下标,避免漏单或重复平。 内层先 PositionGetTicket(i) 取 ticket,再 PositionSelectByTicket 选中,比对 POSITION_SYMBOL 是否等于 _Symbol,只处理当前图表品种。 若持仓类型为 POSITION_TYPE_SELL 且 POSITION_PROFIT >= 0,即盈利卖单,直接调 obj_Trade.PositionClose(pos_ticket) 平掉;外汇与贵金属杠杆高,这类一键平盈逻辑只解决执行效率,不预示行情走向。 「Close Profit Buy」分支结构完全一致,只是把类型判断换成 POSITION_TYPE_BUY,同样以 profit >= 0 为门槛。
else if (sparam==obj_Btn_CLOSE_PROFIT_SELL.Name()){ class=class="str">"cmt">//--- Check if the Close Profit Sell button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_SELL.Name()); class=class="str">"cmt">//--- Log the button click event for (class="type">int i = PositionsTotal() -class="num">1; i >= class="num">0; i--){ class=class="str">"cmt">//--- Loop through all positions class="type">ulong pos_ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Get the ticket of the position if (pos_ticket > class="num">0){ class=class="str">"cmt">//--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ class=class="str">"cmt">//--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ class=class="str">"cmt">//--- Check if the position matches the symbol class="type">ENUM_POSITION_TYPE pos_type = (class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">//--- Get the position type if (pos_type == POSITION_TYPE_SELL){ class=class="str">"cmt">//--- Check if the position is a sell position class="type">class="kw">double profit_loss = PositionGetDouble(POSITION_PROFIT); class=class="str">"cmt">//--- Get the profit/loss of the position if (profit_loss >= class="num">0){ class=class="str">"cmt">//--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); class=class="str">"cmt">//--- Close the profitable sell position } } } } } } } else if (sparam==obj_Btn_CLOSE_PROFIT_BUY.Name()){ class=class="str">"cmt">//--- Check if the Close Profit Buy button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PROFIT_BUY.Name()); class=class="str">"cmt">//--- Log the button click event for (class="type">int i = PositionsTotal() -class="num">1; i >= class="num">0; i--){ class=class="str">"cmt">//--- Loop through all positions
「按盈亏分批平仓的按钮逻辑」
在 MT5 面板里做一键平仓,常见需求是「只平盈利的买仓」和「只平亏损仓」分开处理。下面这段逻辑用两个按钮事件分支,遍历当前品种所有持仓,按 POSITION_PROFIT 的正负做筛选。 盈利买仓分支里,先用 PositionGetTicket 按序号拿 ticket,再用 PositionSelectByTicket 激活持仓,比对 POSITION_SYMBOL 是否等于 _Symbol,避免误关其他图表品种。确认是 BUY 且 profit_loss >= 0 时,调 obj_Trade.PositionClose(pos_ticket) 平仓。 亏损平仓分支不区分多空,只要 profit_loss < 0 就进入关闭流程,遍历顺序从 PositionsTotal()-1 到 0 倒序,防止平仓后序号错位漏单。外汇与贵金属杠杆高,批量平仓可能瞬间放大滑点,实盘前务必在策略测试器用历史 tick 验证。
class="type">ulong pos_ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Get the ticket of the position if (pos_ticket > class="num">0){ class=class="str">"cmt">//--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ class=class="str">"cmt">//--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ class=class="str">"cmt">//--- Check if the position matches the symbol class="type">ENUM_POSITION_TYPE pos_type = (class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">//--- Get the position type if (pos_type == POSITION_TYPE_BUY){ class=class="str">"cmt">//--- Check if the position is a buy position class="type">class="kw">double profit_loss = PositionGetDouble(POSITION_PROFIT); class=class="str">"cmt">//--- Get the profit/loss of the position if (profit_loss >= class="num">0){ class=class="str">"cmt">//--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); class=class="str">"cmt">//--- Close the profitable buy position } } } } } else if (sparam==obj_Btn_CLOSE_ALL_LOSS.Name()){ class=class="str">"cmt">//--- Check if the Close All Loss button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_LOSS.Name()); class=class="str">"cmt">//--- Log the button click event for (class="type">int i = PositionsTotal() -class="num">1; i >= class="num">0; i--){ class=class="str">"cmt">//--- Loop through all positions class="type">ulong pos_ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Get the ticket of the position if (pos_ticket > class="num">0){ class=class="str">"cmt">//--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ class=class="str">"cmt">//--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ class=class="str">"cmt">//--- Check if the position matches the symbol class="type">class="kw">double profit_loss = PositionGetDouble(POSITION_PROFIT); class=class="str">"cmt">//--- Get the profit/loss of the position if (profit_loss < class="num">0){ class=class="str">"cmt">//--- Check if the position is at a loss
◍ 一键平仓的逻辑分支怎么写
在 MT5 面板里做批量平仓,核心不是调交易函数,而是先把按钮事件和持仓遍历拆清楚。上面这段处理了三种点击:平亏损单、平所有盈利单、平挂单,每一种都先用 Print 把按钮名打进日志,方便你事后在专家面板里核对到底是哪颗键被触发。 平亏损仓的入口是判断 sparam 等于亏损按钮名,随后倒序跑 PositionsTotal()。倒序很关键——正序删仓位会让索引错位,MT5 里 PositionGetTicket(i) 在循环中途平仓后 total 会变小,漏单概率显著上升。 盈利单分支里用 PositionGetDouble(POSITION_PROFIT) 取浮盈,判断 >=0 才平。注意这里把零浮盈也归为可平,若你想严格只平正收益,改成 >0 即可,参数一行的差别。 挂单分支换成了 OrdersTotal() 和 OrderGetTicket(),因为挂单不在持仓池里。OrderSelect 之后同样比对 _Symbol,避免把其他图表上的 XAUUSD 挂单误删——外汇和贵金属多图表同开时这是高频坑。
obj_Trade.PositionClose(pos_ticket); class=class="str">"cmt">//--- Close the losing position } } } } } }else if (sparam==obj_Btn_CLOSE_ALL_PROFIT.Name()){ class=class="str">"cmt">//--- Check if the Close All Profit button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_ALL_PROFIT.Name()); class=class="str">"cmt">//--- Log the button click event for (class="type">int i = PositionsTotal() -class="num">1; i >= class="num">0; i--){ class=class="str">"cmt">//--- Loop through all positions class="type">ulong pos_ticket = PositionGetTicket(i); class=class="str">"cmt">//--- Get the ticket of the position if (pos_ticket > class="num">0){ class=class="str">"cmt">//--- Check if the position ticket is valid if (PositionSelectByTicket(pos_ticket)){ class=class="str">"cmt">//--- Select the position by ticket if (PositionGetString(POSITION_SYMBOL)==_Symbol){ class=class="str">"cmt">//--- Check if the position matches the symbol class="type">class="kw">double profit_loss = PositionGetDouble(POSITION_PROFIT); class=class="str">"cmt">//--- Get the profit/loss of the position if (profit_loss >= class="num">0){ class=class="str">"cmt">//--- Check if the position is profitable obj_Trade.PositionClose(pos_ticket); class=class="str">"cmt">//--- Close the profitable position } } } } } }else if (sparam==obj_Btn_CLOSE_PENDING.Name()){ class=class="str">"cmt">//--- Check if the Close Pending button is clicked Print("OBJECT CLICKED = ",obj_Btn_CLOSE_PENDING.Name()); class=class="str">"cmt">//--- Log the button click event for (class="type">int i = OrdersTotal() -class="num">1; i >= class="num">0; i--){ class=class="str">"cmt">//--- Loop through all pending orders class="type">ulong order_ticket = OrderGetTicket(i); class=class="str">"cmt">//--- Get the ticket of the order if (order_ticket > class="num">0){ class=class="str">"cmt">//--- Check if the order ticket is valid if (OrderSelect(order_ticket)){ class=class="str">"cmt">//--- Select the order by ticket if (OrderGetString(ORDER_SYMBOL)==_Symbol){ class=class="str">"cmt">//--- Check if the order matches the symbol
挂单删除与图表重绘的收口动作
在挂单逻辑走完之后,若条件不再满足,直接调用 obj_Trade.OrderDelete(order_ticket) 把对应的挂单撤掉,避免残留限价单在行情反转时被动成交。 删除动作放在内层条件判断里,ticket 来自前面查到的 order_ticket 变量;MT5 里 pending order 的删除不需要额外校验账户状态,但高频调用仍建议加错误码判断。 最后用 ChartRedraw(0) 对当前图表(图表ID 0 即当前)做一次强制重绘,让对象与订单标记即时刷新,不至于看到旧图形。外汇与贵金属杠杆高,撤单逻辑出错可能留下暴露仓位,建议在策略测试器里先跑一遍验证。
obj_Trade.OrderDelete(order_ticket); class=class="str">"cmt">//--- Delete the pending order } } } } } ChartRedraw(class="num">0); }
「别急着下结论」
这套带实时刷新和一键平仓的 MQL5 面板,把持仓监控与下单动作压进了同一个界面,确实让盯盘动线短了一截。附件 CONTROL_PANEL_PART_2.mq5 约 74 KB,在 MT5 里编译后能看到按钮点击直接触发 OrderSend,账户净值与占用保证金每 tick 重绘,省掉手动切窗口的成本。 不过外汇和贵金属本身是高杠杆高风险品种,面板再顺手也只是执行层工具,不替你判断方向。真要验证价值,自己把 ZIP 里的文件拖进 MetaEditor 跑一遍,看在 EURUSD 跳空时段按钮响应有没有延迟,比看任何说明都实在。