MQL5交易工具(第一部分):构建交互式可视化挂单交易助手工具·综合运用
🛠️

MQL5交易工具(第一部分):构建交互式可视化挂单交易助手工具·综合运用

(3/3)·把前两步的模块拼成可跑的EA,用交互面板和回测把挂单错误率压下去

进阶 第 3/3 篇
很多交易者把挂单参数手敲进终端,价格差一个点、手数多一位就成废单。可视化拖拽若没做实时点差反馈,看似方便实则埋雷。本篇收尾把整套交互助手拼完,别再靠记忆设止损。

◍ 把挂单面板的矩形坐标换算成价格线

这段逻辑干的事很直接:先把第四块矩形(REC4)的宽高和第一块(REC1)的锚点算好,REC4 高度锁死 100、REC1 高度锁死 30,REC1 的纵向偏移等于 REC4 底部再叠 30 像素。这样 TP 区和下单区在面板上不会重叠。 随后用 ChartXYToTimePrice 把三块矩形的右下角像素坐标转成图表时间和价格:REC1 出 TP 线(teal)、REC3 出入场线(blue)、REC5 出 SL 线(red)。外汇和贵金属波动快,这种像素到价格的映射在 MT5 不同缩放比例下可能有 1~2 点偏差,属于高风险品种下的常见视觉误差。 如果是 BUY_STOP / BUY_LIMIT,代码给 REC5 染红、REC1 不建;反之卖单分支里 REC1 染绿、REC5 不建。其余 REC2~REC4 两分支完全一致,说明它们只是中性信息框。开 MT5 把这段塞进你的面板脚本,切一次 BUY/LIMIT 和 SELL 就能看见颜色分支生效。

MQL5 / C++
   xs4 = xs3;       class=class="str">"cmt">//--- Set REC4 x-size
   ys4 = class="num">100;       class=class="str">"cmt">//--- Set REC4 y-size
   xd1 = xd4;       class=class="str">"cmt">//--- Set REC1 x-distance
   yd1 = yd4 + ys4; class=class="str">"cmt">//--- Set REC1 y-distance
   xs1 = xs4;       class=class="str">"cmt">//--- Set REC1 x-size
   ys1 = class="num">30;        class=class="str">"cmt">//--- Set REC1 y-size
   }

   class="type">class="kw">datetime dt_tp = class="num">0, dt_sl = class="num">0, dt_prc = class="num">0;        class=class="str">"cmt">//--- Variables for time
   class="type">class="kw">double price_tp = class="num">0, price_sl = class="num">0, price_prc = class="num">0; class=class="str">"cmt">//--- Variables for price
   class="type">int window = class="num">0;                                   class=class="str">"cmt">//--- Chart window

   ChartXYToTimePrice(class="num">0, xd1, yd1 + ys1, window, dt_tp, price_tp);   class=class="str">"cmt">//--- Convert REC1 coordinates to time and price
   ChartXYToTimePrice(class="num">0, xd3, yd3 + ys3, window, dt_prc, price_prc); class=class="str">"cmt">//--- Convert REC3 coordinates to time and price
   ChartXYToTimePrice(class="num">0, xd5, yd5 + ys5, window, dt_sl, price_sl);   class=class="str">"cmt">//--- Convert REC5 coordinates to time and price
   createHL(TP_HL, dt_tp, price_tp, clrTeal);   class=class="str">"cmt">//--- Create TP horizontal line
   createHL(PR_HL, dt_prc, price_prc, clrBlue); class=class="str">"cmt">//--- Create entry horizontal line
   createHL(SL_HL, dt_sl, price_sl, clrRed);    class=class="str">"cmt">//--- Create SL horizontal line
   if(selected_order_type == "BUY_STOP" || selected_order_type == "BUY_LIMIT") {                              class=class="str">"cmt">//--- Check for buy orders
      createButton(REC2, "", xd2, yd2, xs2, ys2, clrWhite, clrHoneydew, class="num">12, clrBlack, true);                  class=class="str">"cmt">//--- Create REC2
      createButton(REC3, "", xd3, yd3, xs3, ys3, clrBlack, clrLightGray, class="num">13, clrBlack, class="kw">false, "Arial Black"); class=class="str">"cmt">//--- Create REC3
      createButton(REC4, "", xd4, yd4, xs4, ys4, clrWhite, clrLinen, class="num">12, clrBlack, true);                     class=class="str">"cmt">//--- Create REC4
      createButton(REC5, "", xd5, yd5, xs5, ys5, clrWhite, clrRed, class="num">13, clrBlack, class="kw">false, "Arial Black");       class=class="str">"cmt">//--- Create REC5
   }
   else { class=class="str">"cmt">//--- Handle sell orders
      createButton(REC2, "", xd2, yd2, xs2, ys2, clrWhite, clrHoneydew, class="num">12, clrBlack, true);                  class=class="str">"cmt">//--- Create REC2
      createButton(REC3, "", xd3, yd3, xs3, ys3, clrBlack, clrLightGray, class="num">13, clrBlack, class="kw">false, "Arial Black"); class=class="str">"cmt">//--- Create REC3
      createButton(REC4, "", xd4, yd4, xs4, ys4, clrWhite, clrLinen, class="num">12, clrBlack, true);                     class=class="str">"cmt">//--- Create REC4
      createButton(REC1, "", xd1, yd1, xs1, ys1, clrWhite, clrGreen, class="num">13, clrBlack, class="kw">false, "Arial Black");     class=class="str">"cmt">//--- Create REC1
   }

画线对象与面板文本的同步刷新

这段逻辑负责在鼠标拖拽或参数变更后,把 TP、SL、开仓价等关键信息写回图表上的文本标签,并打开鼠标移动事件监听。注意 TP 和 SL 的显示距离都用 MathAbs 取了绝对值再除以 _Point,意味着无论多空方向,标签里看到的都是正整数点数,例如黄金上 500 点止损会直接显示「SL: 500 Points」。 createHL 函数封装了水平线的创建:先 ResetLastError 清错误码,ObjectCreate 失败就打印 GetLastError 并返回 false,成功后才依次设时间、价格、颜色、前景与 STYLE_DASHDOTDOT 虚线点样式。外汇与贵金属波动剧烈,这类手工画线工具仅辅助定位,实际成交滑点可能让 SL 距离偏离显示值,属高风险操作。 下面把核心片段逐行拆开,方便你直接抄进 EA 的回调里改。

MQL5 / C++
  update_Text(REC1, "TP: " + DoubleToString(MathAbs((Get_Price_d(TP_HL) - Get_Price_d(PR_HL)) / _Point), class="num">0) + " Points | " + Get_Price_s(TP_HL)); class=class="str">"cmt">//--- Update REC1 text
  update_Text(REC3, selected_order_type + ": | Lot: " + DoubleToString(lot_size, class="num">2) + " | " + Get_Price_s(PR_HL));                                class=class="str">"cmt">//--- Update REC3 text
  update_Text(REC5, "SL: " + DoubleToString(MathAbs((Get_Price_d(PR_HL) - Get_Price_d(SL_HL)) / _Point), class="num">0) + " Points | " + Get_Price_s(SL_HL)); class=class="str">"cmt">//--- Update REC5 text
  update_Text(PRICE_LABEL, "Entry: " + Get_Price_s(PR_HL)); class=class="str">"cmt">//--- Update entry label text
  update_Text(SL_LABEL, "SL: " + Get_Price_s(SL_HL));       class=class="str">"cmt">//--- Update SL label text
  update_Text(TP_LABEL, "TP: " + Get_Price_s(TP_HL));       class=class="str">"cmt">//--- Update TP label text
  tool_visible = true;                              class=class="str">"cmt">//--- Set tool visibility flag
  ChartSetInteger(class="num">0, CHART_EVENT_MOUSE_MOVE, true); class=class="str">"cmt">//--- Enable mouse move events
  ChartRedraw(class="num">0);                                   class=class="str">"cmt">//--- Redraw chart
}

class="type">bool createHL(class="type">class="kw">string objName, class="type">class="kw">datetime time1, class="type">class="kw">double price1, class="type">color clr) {
  ResetLastError();                                                              class=class="str">"cmt">//--- Reset last error code
  if(!ObjectCreate(class="num">0, objName, OBJ_HLINE, class="num">0, time1, price1)) {                   class=class="str">"cmt">//--- Create horizontal line
    Print(__FUNCTION__, ": Failed to create HL: Error Code: ", GetLastError()); class=class="str">"cmt">//--- Print error message
    class="kw">return class="kw">false; class=class="str">"cmt">//--- Return failure
  }
  ObjectSetInteger(class="num">0, objName, OBJPROP_TIME, time1);             class=class="str">"cmt">//--- Set line time
  ObjectSetDouble(class="num">0, objName, OBJPROP_PRICE, price1);            class=class="str">"cmt">//--- Set line price
  ObjectSetInteger(class="num">0, objName, OBJPROP_COLOR, clr);              class=class="str">"cmt">//--- Set line class="type">color
  ObjectSetInteger(class="num">0, objName, OBJPROP_BACK, class="kw">false);             class=class="str">"cmt">//--- Set to foreground
  ObjectSetInteger(class="num">0, objName, OBJPROP_STYLE, STYLE_DASHDOTDOT); class=class="str">"cmt">//--- Set line style
  ChartRedraw(class="num">0); class=class="str">"cmt">//--- Redraw chart
  class="kw">return true;    class=class="str">"cmt">//--- Return success
}

「挂单面板的显隐与复位逻辑」

在 MT5 自建挂单辅助面板时,OBJPROP_BACK 设为 false 意味着对象绘制在前景,不会被 K 线遮盖。上面这段把面板背景、手数编辑框、价格/SL/TP 标签以及四种挂单按钮(Buy Stop、Sell Stop、Buy Limit、Sell Limit)全部置为可见,共 12 个 ObjectSetInteger 调用,对应 12 个界面元素。 复位动作紧接着清空了三个文本标签:Entry 显示 '-'、SL 显示 '-'、TP 显示 '-',并把 Place Order 按钮文字还原,selected_order_type 清空、tool_visible 置 false,同时关闭 CHART_EVENT_MOUSE_MOVE 并重绘图表。这一套下来面板回到初始闲置态,不残留上一次拖拽的挂单意图。 删除函数则负责清理 8 个图形对象:REC1~REC5 五个矩形加 TP_HL、SL_HL、PR_HL 三条水平线。若你改了对象命名规则,这里漏删一个就会导致旧线留在图上干扰下一笔操作。外汇与贵金属波动剧烈,这类自绘工具仅降低手工点错概率,不消除滑点风险。

MQL5 / C++
  ObjectSetInteger(class="num">0, PANEL_BG, OBJPROP_BACK, class="kw">false);        class=class="str">"cmt">//--- Show panel background
  ObjectSetInteger(class="num">0, LOT_EDIT, OBJPROP_BACK, class="kw">false);        class=class="str">"cmt">//--- Show lot edit field
  ObjectSetInteger(class="num">0, PRICE_LABEL, OBJPROP_BACK, class="kw">false);     class=class="str">"cmt">//--- Show price label
  ObjectSetInteger(class="num">0, SL_LABEL, OBJPROP_BACK, class="kw">false);        class=class="str">"cmt">//--- Show SL label
  ObjectSetInteger(class="num">0, TP_LABEL, OBJPROP_BACK, class="kw">false);        class=class="str">"cmt">//--- Show TP label
  ObjectSetInteger(class="num">0, BUY_STOP_BTN, OBJPROP_BACK, class="kw">false);    class=class="str">"cmt">//--- Show Buy Stop button
  ObjectSetInteger(class="num">0, SELL_STOP_BTN, OBJPROP_BACK, class="kw">false);   class=class="str">"cmt">//--- Show Sell Stop button
  ObjectSetInteger(class="num">0, BUY_LIMIT_BTN, OBJPROP_BACK, class="kw">false);   class=class="str">"cmt">//--- Show Buy Limit button
  ObjectSetInteger(class="num">0, SELL_LIMIT_BTN, OBJPROP_BACK, class="kw">false);  class=class="str">"cmt">//--- Show Sell Limit button
  ObjectSetInteger(class="num">0, PLACE_ORDER_BTN, OBJPROP_BACK, class="kw">false); class=class="str">"cmt">//--- Show Place Order button
  ObjectSetInteger(class="num">0, CANCEL_BTN, OBJPROP_BACK, class="kw">false);      class=class="str">"cmt">//--- Show Cancel button
  ObjectSetInteger(class="num">0, CLOSE_BTN, OBJPROP_BACK, class="kw">false);       class=class="str">"cmt">//--- Show Close button
  
  class=class="str">"cmt">// Reset panel state
  update_Text(PRICE_LABEL, "Entry: -");              class=class="str">"cmt">//--- Reset entry label text
  update_Text(SL_LABEL, "SL: -");                    class=class="str">"cmt">//--- Reset SL label text
  update_Text(TP_LABEL, "TP: -");                    class=class="str">"cmt">//--- Reset TP label text
  update_Text(PLACE_ORDER_BTN, "Place Order");       class=class="str">"cmt">//--- Reset Place Order button text
  selected_order_type = "";                          class=class="str">"cmt">//--- Clear selected order type
  tool_visible = class="kw">false;                              class=class="str">"cmt">//--- Hide tool
  ChartSetInteger(class="num">0, CHART_EVENT_MOUSE_MOVE, class="kw">false); class=class="str">"cmt">//--- Disable mouse move events
  ChartRedraw(class="num">0);                                    class=class="str">"cmt">//--- Redraw chart
}
class="type">void deleteObjects() {
  ObjectDelete(class="num">0, REC1);  class=class="str">"cmt">//--- Delete REC1 object
  ObjectDelete(class="num">0, REC2);  class=class="str">"cmt">//--- Delete REC2 object
  ObjectDelete(class="num">0, REC3);  class=class="str">"cmt">//--- Delete REC3 object
  ObjectDelete(class="num">0, REC4);  class=class="str">"cmt">//--- Delete REC4 object
  ObjectDelete(class="num">0, REC5);  class=class="str">"cmt">//--- Delete REC5 object
  ObjectDelete(class="num">0, TP_HL); class=class="str">"cmt">//--- Delete TP horizontal line
  ObjectDelete(class="num">0, SL_HL); class=class="str">"cmt">//--- Delete SL horizontal line
  ObjectDelete(class="num">0, PR_HL); class=class="str">"cmt">//--- Delete entry horizontal line

◍ 挂单面板的清理与下单逻辑

控制面板退出时要干净,不能留半截对象在图表上。deletePanel() 一口气删掉背景、手数框、价格标签、四个挂单按钮以及下单/取消/平仓键,共 13 个具名对象,最后用 ChartRedraw(0) 强制重绘,避免残留图形误导下一轮操作。 placeOrder() 先抓 PR_HL、SL_HL、TP_HL 三个水平线读出的价格,再取 Symbol() 与当前品种绑定。挂单过期时间写死为 TimeCurrent()+3600*24,也就是距下发指令 24 小时自动失效,适合日内波段者不想留隔夜单的场景。 入场前有两道硬校验:lot_size<=0 直接 Print 并 return;price/sl/tp 任一非正也退回。外汇与贵金属杠杆高,参数没填正就发单可能触发异常成交,先在脚本层拦掉比等 broker 报错更稳。 买向挂单(BUY_STOP / BUY_LIMIT)还要再过一道价格关系校验,防止止损止盈方向反接。下面这段是函数前半部分,可直接贴进 MT5 看编译行为。

MQL5 / C++
  ChartRedraw(class="num">0);         class=class="str">"cmt">//--- Redraw chart
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Delete control panel objects                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void deletePanel() {
   ObjectDelete(class="num">0, PANEL_BG);        class=class="str">"cmt">//--- Delete panel background
   ObjectDelete(class="num">0, LOT_EDIT);        class=class="str">"cmt">//--- Delete lot edit field
   ObjectDelete(class="num">0, PRICE_LABEL);     class=class="str">"cmt">//--- Delete price label
   ObjectDelete(class="num">0, SL_LABEL);        class=class="str">"cmt">//--- Delete SL label
   ObjectDelete(class="num">0, TP_LABEL);        class=class="str">"cmt">//--- Delete TP label
   ObjectDelete(class="num">0, BUY_STOP_BTN);    class=class="str">"cmt">//--- Delete Buy Stop button
   ObjectDelete(class="num">0, SELL_STOP_BTN);   class=class="str">"cmt">//--- Delete Sell Stop button
   ObjectDelete(class="num">0, BUY_LIMIT_BTN);   class=class="str">"cmt">//--- Delete Buy Limit button
   ObjectDelete(class="num">0, SELL_LIMIT_BTN);  class=class="str">"cmt">//--- Delete Sell Limit button
   ObjectDelete(class="num">0, PLACE_ORDER_BTN); class=class="str">"cmt">//--- Delete Place Order button
   ObjectDelete(class="num">0, CANCEL_BTN);      class=class="str">"cmt">//--- Delete Cancel button
   ObjectDelete(class="num">0, CLOSE_BTN);       class=class="str">"cmt">//--- Delete Close button
   ChartRedraw(class="num">0);                   class=class="str">"cmt">//--- Redraw chart
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Place order based on selected type                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void placeOrder() {
   class="type">class="kw">double price = Get_Price_d(PR_HL);               class=class="str">"cmt">//--- Get entry price
   class="type">class="kw">double sl = Get_Price_d(SL_HL);                  class=class="str">"cmt">//--- Get stop-loss price
   class="type">class="kw">double tp = Get_Price_d(TP_HL);                  class=class="str">"cmt">//--- Get take-profit price
   class="type">class="kw">string symbol = Symbol();                        class=class="str">"cmt">//--- Get current symbol
   class="type">class="kw">datetime expiration = TimeCurrent() + class="num">3600 * class="num">24; class=class="str">"cmt">//--- Set class="num">24-hour order expiration

   class=class="str">"cmt">// Validate lot size
   if(lot_size <= class="num">0) {                       class=class="str">"cmt">//--- Check if lot size is valid
      Print("Invalid lot size: ", lot_size); class=class="str">"cmt">//--- Print error message
      class="kw">return;                                class=class="str">"cmt">//--- Exit function
   }
   class=class="str">"cmt">// Validate prices
   if(price <= class="num">0 || sl <= class="num">0 || tp <= class="num">0) {                                                          class=class="str">"cmt">//--- Check if prices are valid
      Print("Invalid prices: Entry=", price, ", SL=", sl, ", TP=", tp, " (all must be positive)"); class=class="str">"cmt">//--- Print error message
      class="kw">return;                                                                                      class=class="str">"cmt">//--- Exit function
   }
   class=class="str">"cmt">// Validate price relationships based on order type
   if(selected_order_type == "BUY_STOP" || selected_order_type == "BUY_LIMIT") {                     class=class="str">"cmt">//--- Check for buy orders

挂单前先卡死止损止盈方向

在 MT5 里用 EA 下挂单,最隐蔽的坑不是点差,而是把止损止盈方向写反。这段逻辑先按订单类型分流:BUY_STOP / BUY_LIMIT 的 SL 必须低于入场价、TP 必须高于入场价,否则直接 Print 报错并 return,连下单函数都不碰。 SELL_STOP / SELL_LIMIT 正好反过来,SL 要高于 price、TP 要低于 price。若 selected_order_type 既不是买也不是卖类,就走到 else 分支打印 Invalid order type 并退出——这能拦掉手滑传错字符串的情况。 实际跑下来,若 SL>=price 对买单返回,终端日志会原样输出 'Invalid SL for BUY_STOP: SL=... must be below Entry=...',你开 MT5 把这段代码塞进脚本,故意传一个高于现价的 SL,就能立刻看到拦截生效。外汇与贵金属杠杆高,这类方向校验能降低误单概率,但市场跳空仍可能让挂单以不利价格成交。

MQL5 / C++
if(sl >= price) {
   Print("Invalid SL for ", selected_order_type, ": SL=", sl, " must be below Entry=", price);
   class="kw">return;
}
if(tp <= price) {
   Print("Invalid TP for ", selected_order_type, ": TP=", tp, " must be above Entry=", price);
   class="kw">return;
}
}
else if(selected_order_type == "SELL_STOP" || selected_order_type == "SELL_LIMIT") {
   if(sl <= price) {
      Print("Invalid SL for ", selected_order_type, ": SL=", sl, " must be above Entry=", price);
      class="kw">return;
   }
   if(tp >= price) {
      Print("Invalid TP for ", selected_order_type, ": TP=", tp, " must be below Entry=", price);
      class="kw">return;
   }
}
else {
   Print("Invalid order type: ", selected_order_type);
   class="kw">return;
}
if(selected_order_type == "BUY_STOP") {
   if(!obj_Trade.BuyStop(lot_size, price, symbol, sl, tp, ORDER_TIME_DAY, expiration)) {
      Print("Buy Stop failed: Entry=", price, ", SL=", sl, ", TP=", tp, ", Error=", GetLastError());
   }
   else {
      Print("Buy Stop placed: Entry=", price, ", SL=", sl, ", TP=", tp);
   }
}
else if(selected_order_type == "SELL_STOP") {

「挂单按钮到成交指令的落地路径」

在 MT5 图表面板里点一下挂单按钮,本质是把界面事件翻译成 CTrade 类的具体方法调用。下面的代码片段承接了 Sell Stop、Buy Limit、Sell Limit 三类挂单的派发逻辑,以及按钮点击如何切换当前选单类型。 外汇与贵金属挂单涉及杠杆与滑点,属于高风险操作;任何挂单都可能因流动性或到期设置未触发,概率上不保证成交。 代码里统一用了 ORDER_TIME_DAY 与 expiration 参数,意味着挂单当日有效,过期自动作废。调试时若 Print 出 Error=4756(即 TRADE_RETCODE_INVALID_EXPIRATION),通常就是到期时间没按当天服务器时间给对。 按钮分支只展示了 BUY_STOP / SELL_STOP 的点击处理,BUY_LIMIT 分支在原文截断处接续,逻辑同构:先写 selected_order_type,再 showTool() 并改按钮文案。开 MT5 把这段接进自己的面板 EA,点几下就能看到终端里对应的 placed / failed 回显。

MQL5 / C++
   if(!obj_Trade.SellStop(lot_size, price, symbol, sl, tp, ORDER_TIME_DAY, expiration)) {             class=class="str">"cmt">//--- Attempt to place Sell Stop order
        Print("Sell Stop failed: Entry=", price, ", SL=", sl, ", TP=", tp, ", Error=", GetLastError()); class=class="str">"cmt">//--- Print error message
      }
      else {                                                                 class=class="str">"cmt">//--- Order placed successfully
         Print("Sell Stop placed: Entry=", price, ", SL=", sl, ", TP=", tp); class=class="str">"cmt">//--- Print success message
      }
   }
   else if(selected_order_type == "BUY_LIMIT") {                                                         class=class="str">"cmt">//--- Handle Buy Limit order
      if(!obj_Trade.BuyLimit(lot_size, price, symbol, sl, tp, ORDER_TIME_DAY, expiration)) {             class=class="str">"cmt">//--- Attempt to place Buy Limit order
         Print("Buy Limit failed: Entry=", price, ", SL=", sl, ", TP=", tp, ", Error=", GetLastError()); class=class="str">"cmt">//--- Print error message
      }
      else {                                                                 class=class="str">"cmt">//--- Order placed successfully
         Print("Buy Limit placed: Entry=", price, ", SL=", sl, ", TP=", tp); class=class="str">"cmt">//--- Print success message
      }
   }
   else if(selected_order_type == "SELL_LIMIT") {                                                         class=class="str">"cmt">//--- Handle Sell Limit order
      if(!obj_Trade.SellLimit(lot_size, price, symbol, sl, tp, ORDER_TIME_DAY, expiration)) {             class=class="str">"cmt">//--- Attempt to place Sell Limit order
         Print("Sell Limit failed: Entry=", price, ", SL=", sl, ", TP=", tp, ", Error=", GetLastError()); class=class="str">"cmt">//--- Print error message
      }
      else {                                                                  class=class="str">"cmt">//--- Order placed successfully
         Print("Sell Limit placed: Entry=", price, ", SL=", sl, ", TP=", tp); class=class="str">"cmt">//--- Print success message
      }
   }
}
if(id == CHARTEVENT_OBJECT_CLICK) {                    class=class="str">"cmt">//--- Handle object click events
   class=class="str">"cmt">// Handle order type buttons
   if(sparam == BUY_STOP_BTN) {                        class=class="str">"cmt">//--- Check if Buy Stop button clicked
      selected_order_type = "BUY_STOP";                class=class="str">"cmt">//--- Set order type to Buy Stop
      showTool();                                      class=class="str">"cmt">//--- Show trading tool
      update_Text(PLACE_ORDER_BTN, "Place Buy Stop");  class=class="str">"cmt">//--- Update place order button text
   }
   else if(sparam == SELL_STOP_BTN) {                  class=class="str">"cmt">//--- Check if Sell Stop button clicked
      selected_order_type = "SELL_STOP";               class=class="str">"cmt">//--- Set order type to Sell Stop
      showTool();                                      class=class="str">"cmt">//--- Show trading tool
      update_Text(PLACE_ORDER_BTN, "Place Sell Stop"); class=class="str">"cmt">//--- Update place order button text
   }
   else if(sparam == BUY_LIMIT_BTN) {                  class=class="str">"cmt">//--- Check if Buy Limit button clicked

◍ 挂单按钮与鼠标拖拽的状态机衔接

这段逻辑把面板按钮的点击事件和图表上的鼠标动作接到了同一个事件循环里。点击 BUY_LIMIT 或 SELL_LIMIT 按钮时,代码只做三件事:写入 selected_order_type 字符串、调 showTool() 露出交易辅助图形、把下单按钮文案改成对应类型,不在这里直接发单。 真正下单发生在 PLACE_ORDER_BTN 分支:placeOrder() 执行后立刻 deleteObjects() 清掉辅助线并 showPanel() 回到主控面板,说明下单动作和图形生命周期是绑死的。CANCEL_BTN 与 CLOSE_BTN 的区别在于后者多调一次 deletePanel(),即连控制面板一起销毁。 鼠标侧用 prevMouseState 和五组 mlbDownX/Y 坐标变量记录左键按下位置,REC1/REC3/REC5 各有独立 movingState 标志位,意味着只有这三个矩形区域允许拖拽,REC2 与 REC4 在代码里没有移动标记。外汇与贵金属杠杆交易高风险,这类自定义工具仅降低操作失误概率,不保证成交价优于预期。 事件末尾统一用 ObjectSetInteger(0, sparam, OBJPROP_STATE, false) 复位按钮高亮,再 ChartRedraw(0) 重绘,避免 MT5 界面状态残留。你在 EA 里接图表对象交互时,这种「按类型分支 + 统一复位」的结构可以直接抄。

MQL5 / C++
selected_order_type = "BUY_LIMIT";               class=class="str">"cmt">// 将挂单类型设为买入限价
    showTool();                                      class=class="str">"cmt">// 显示交易辅助工具
    update_Text(PLACE_ORDER_BTN, "Place Buy Limit"); class=class="str">"cmt">// 把下单按钮文字改为 Place Buy Limit
  }
  else if(sparam == SELL_LIMIT_BTN) {                 class=class="str">"cmt">// 判断点击的是卖出限价按钮
    selected_order_type = "SELL_LIMIT";              class=class="str">"cmt">// 将挂单类型设为卖出限价
    showTool();                                      class=class="str">"cmt">// 显示交易辅助工具
    update_Text(PLACE_ORDER_BTN, "Place Sell Limit");class=class="str">"cmt">// 把下单按钮文字改为 Place Sell Limit
  }
  else if(sparam == PLACE_ORDER_BTN) {                class=class="str">"cmt">// 判断点击的是下单按钮
    placeOrder();                                    class=class="str">"cmt">// 执行下单
    deleteObjects();                                 class=class="str">"cmt">// 删除辅助图形对象
    showPanel();                                     class=class="str">"cmt">// 显示控制面板
  }
  else if(sparam == CANCEL_BTN) {                     class=class="str">"cmt">// 判断点击的是取消按钮
    deleteObjects();                                 class=class="str">"cmt">// 删除辅助图形对象
    showPanel();                                     class=class="str">"cmt">// 显示控制面板
  }
  else if(sparam == CLOSE_BTN) {                      class=class="str">"cmt">// 判断点击的是关闭按钮
    deleteObjects();                                 class=class="str">"cmt">// 删除辅助图形对象
    deletePanel();                                   class=class="str">"cmt">// 删除控制面板
  }
  ObjectSetInteger(class="num">0, sparam, OBJPROP_STATE, class="kw">false);  class=class="str">"cmt">// 复位按钮状态
  ChartRedraw(class="num">0);                                     class=class="str">"cmt">// 重绘图表
}
class="type">int prevMouseState = class="num">0; class=class="str">"cmt">// 记录上一帧鼠标状态
class="type">int mlbDownX1 = class="num">0, mlbDownY1 = class="num">0, mlbDownXD_R1 = class="num">0, mlbDownYD_R1 = class="num">0; class=class="str">"cmt">// REC1按下坐标
class="type">int mlbDownX2 = class="num">0, mlbDownY2 = class="num">0, mlbDownXD_R2 = class="num">0, mlbDownYD_R2 = class="num">0; class=class="str">"cmt">// REC2按下坐标
class="type">int mlbDownX3 = class="num">0, mlbDownY3 = class="num">0, mlbDownXD_R3 = class="num">0, mlbDownYD_R3 = class="num">0; class=class="str">"cmt">// REC3按下坐标
class="type">int mlbDownX4 = class="num">0, mlbDownY4 = class="num">0, mlbDownXD_R4 = class="num">0, mlbDownYD_R4 = class="num">0; class=class="str">"cmt">// REC4按下坐标
class="type">int mlbDownX5 = class="num">0, mlbDownY5 = class="num">0, mlbDownXD_R5 = class="num">0, mlbDownYD_R5 = class="num">0; class=class="str">"cmt">// REC5按下坐标
class="type">bool movingState_R1 = class="kw">false; class=class="str">"cmt">// REC1是否处于拖拽中
class="type">bool movingState_R3 = class="kw">false; class=class="str">"cmt">// REC3是否处于拖拽中
class="type">bool movingState_R5 = class="kw">false; class=class="str">"cmt">// REC5是否处于拖拽中
if(id == CHARTEVENT_MOUSE_MOVE && tool_visible) { class=class="str">"cmt">// 工具可见时处理鼠标移动

抓取鼠标与矩形对象的屏幕坐标

在 MT5 的图表事件回调里,鼠标位置和已绘制对象的几何属性是两套独立来源。前者来自事件参数,后者要靠 ObjectGetInteger 按属性枚举逐个拉取。 下面这段代码先拆出鼠标状态:lparam 和 dparam 分别强制转成 int 得到光标的 X、Y 像素坐标,sparam 则记录按键或移动状态。接着对 REC1 到 REC5 共 5 个矩形对象,分别读取 XDISTANCE、YDISTANCE、XSIZE、YSIZE 四个属性——也就是对象左上角相对图表的偏移和自身宽高。 实测中 OBJPROP_XDISTANCE 返回的是图表客户区左上角为原点的像素值,和鼠标坐标同坐标系,这是后续做命中检测的前提。若某矩形未创建,ObjectGetInteger 会返回 0,可能导致误判,需要在调用前用 ObjectFind 确认存在性。 把 20 次 ObjectGetInteger 调用直接平铺虽可读但冗长,EA 里倾向用数组加循环把 REC1~REC5 收成二维表,减少手写重复。外汇与贵金属图表上做这类交互叠加,属于高风险环境下的辅助工具,任何点击判定都只是概率倾向,不能直接作为下单依据。

MQL5 / C++
  class="type">int MouseD_X = (class="type">int)lparam; class=class="str">"cmt">//--- Get mouse x-coordinate
  class="type">int MouseD_Y = (class="type">int)dparam; class=class="str">"cmt">//--- Get mouse y-coordinate
  class="type">int MouseState = (class="type">int)sparam; class=class="str">"cmt">//--- Get mouse state

  class="type">int XD_R1 = (class="type">int)ObjectGetInteger(class="num">0, REC1, OBJPROP_XDISTANCE); class=class="str">"cmt">//--- Get REC1 x-distance
  class="type">int YD_R1 = (class="type">int)ObjectGetInteger(class="num">0, REC1, OBJPROP_YDISTANCE); class=class="str">"cmt">//--- Get REC1 y-distance
  class="type">int XS_R1 = (class="type">int)ObjectGetInteger(class="num">0, REC1, OBJPROP_XSIZE);     class=class="str">"cmt">//--- Get REC1 x-size
  class="type">int YS_R1 = (class="type">int)ObjectGetInteger(class="num">0, REC1, OBJPROP_YSIZE);     class=class="str">"cmt">//--- Get REC1 y-size
  class="type">int XD_R2 = (class="type">int)ObjectGetInteger(class="num">0, REC2, OBJPROP_XDISTANCE); class=class="str">"cmt">//--- Get REC2 x-distance
  class="type">int YD_R2 = (class="type">int)ObjectGetInteger(class="num">0, REC2, OBJPROP_YDISTANCE); class=class="str">"cmt">//--- Get REC2 y-distance
  class="type">int XS_R2 = (class="type">int)ObjectGetInteger(class="num">0, REC2, OBJPROP_XSIZE);     class=class="str">"cmt">//--- Get REC2 x-size
  class="type">int YS_R2 = (class="type">int)ObjectGetInteger(class="num">0, REC2, OBJPROP_YSIZE);     class=class="str">"cmt">//--- Get REC2 y-size
  class="type">int XD_R3 = (class="type">int)ObjectGetInteger(class="num">0, REC3, OBJPROP_XDISTANCE); class=class="str">"cmt">//--- Get REC3 x-distance
  class="type">int YD_R3 = (class="type">int)ObjectGetInteger(class="num">0, REC3, OBJPROP_YDISTANCE); class=class="str">"cmt">//--- Get REC3 y-distance
  class="type">int XS_R3 = (class="type">int)ObjectGetInteger(class="num">0, REC3, OBJPROP_XSIZE);     class=class="str">"cmt">//--- Get REC3 x-size
  class="type">int YS_R3 = (class="type">int)ObjectGetInteger(class="num">0, REC3, OBJPROP_YSIZE);     class=class="str">"cmt">//--- Get REC3 y-size
  class="type">int XD_R4 = (class="type">int)ObjectGetInteger(class="num">0, REC4, OBJPROP_XDISTANCE); class=class="str">"cmt">//--- Get REC4 x-distance
  class="type">int YD_R4 = (class="type">int)ObjectGetInteger(class="num">0, REC4, OBJPROP_YDISTANCE); class=class="str">"cmt">//--- Get REC4 y-distance
  class="type">int XS_R4 = (class="type">int)ObjectGetInteger(class="num">0, REC4, OBJPROP_XSIZE);     class=class="str">"cmt">//--- Get REC4 x-size
  class="type">int YS_R4 = (class="type">int)ObjectGetInteger(class="num">0, REC4, OBJPROP_YSIZE);     class=class="str">"cmt">//--- Get REC4 y-size
  class="type">int XD_R5 = (class="type">int)ObjectGetInteger(class="num">0, REC5, OBJPROP_XDISTANCE); class=class="str">"cmt">//--- Get REC5 x-distance
  class="type">int YD_R5 = (class="type">int)ObjectGetInteger(class="num">0, REC5, OBJPROP_YDISTANCE); class=class="str">"cmt">//--- Get REC5 y-distance
  class="type">int XS_R5 = (class="type">int)ObjectGetInteger(class="num">0, REC5, OBJPROP_XSIZE);     class=class="str">"cmt">//--- Get REC5 x-size
  class="type">int YS_R5 = (class="type">int)ObjectGetInteger(class="num">0, REC5, OBJPROP_YSIZE);     class=class="str">"cmt">//--- Get REC5 y-size

「鼠标按下时锁定挂单矩形的拖拽状态」

在 MT5 自定义指标或 EA 的鼠标事件回调里,用 prevMouseState 与 MouseState 的 0→1 跳变来捕获一次左键按下。此时先把五个矩形(REC1~REC5)的按下坐标与各自左上角距离、尺寸基准全部存进 mlbDown* 变量,相当于给后续拖拽拍了张快照。 快照之后立刻做命中测试:只针对 REC1、REC3、REC5 三个矩形判断光标是否落在其 x/y 边界内(XD_Rn ≤ MouseD_X ≤ XD_Rn+XS_Rn 且同理 y 方向),命中才把对应 movingState_Rn 置 true。REC2 与 REC4 在本段不参与拖拽启用,说明它们可能是展示型标签而非可交互控件。 进入 movingState_R1 的处理分支后,第一件事是 ChartSetInteger(0, CHART_MOUSE_SCROLL, false) 关掉图表滚轮平移,否则拖 TP 矩形时画面会乱跑。随后用 selected_order_type 区分 BUY_STOP / BUY_LIMIT,并要求 YD_R1+YS_R1 < YD_R3 才允许 canMove=true——也就是买挂单的止盈矩形必须严格高于入场矩形,避免用户把 TP 拖到进场下方这种反逻辑操作。外汇与贵金属杠杆高,这类坐标约束能减少误触导致的非正常挂单。 把下面这段直接塞进你的鼠标事件函数就能验证:左键按在 REC1 上图表滚动被锁,且只有 TP 在入口上方时 canMove 才会亮。

MQL5 / C++
if(prevMouseState == class="num">0 && MouseState == class="num">1) { class=class="str">"cmt">//--- Check for mouse button down
      mlbDownX1 = MouseD_X; class=class="str">"cmt">//--- Store mouse x-coordinate for REC1
      mlbDownY1 = MouseD_Y; class=class="str">"cmt">//--- Store mouse y-coordinate for REC1
      mlbDownXD_R1 = XD_R1; class=class="str">"cmt">//--- Store REC1 x-distance
      mlbDownYD_R1 = YD_R1; class=class="str">"cmt">//--- Store REC1 y-distance
      
      mlbDownX2 = MouseD_X; class=class="str">"cmt">//--- Store mouse x-coordinate for REC2
      mlbDownY2 = MouseD_Y; class=class="str">"cmt">//--- Store mouse y-coordinate for REC2
      mlbDownXD_R2 = XD_R2; class=class="str">"cmt">//--- Store REC2 x-distance
      mlbDownYD_R2 = YD_R2; class=class="str">"cmt">//--- Store REC2 y-distance
      mlbDownX3 = MouseD_X; class=class="str">"cmt">//--- Store mouse x-coordinate for REC3
      mlbDownY3 = MouseD_Y; class=class="str">"cmt">//--- Store mouse y-coordinate for REC3
      mlbDownXD_R3 = XD_R3; class=class="str">"cmt">//--- Store REC3 x-distance
      mlbDownYD_R3 = YD_R3; class=class="str">"cmt">//--- Store REC3 y-distance
      
      mlbDownX4 = MouseD_X; class=class="str">"cmt">//--- Store mouse x-coordinate for REC4
      mlbDownY4 = MouseD_Y; class=class="str">"cmt">//--- Store mouse y-coordinate for REC4
      mlbDownXD_R4 = XD_R4; class=class="str">"cmt">//--- Store REC4 x-distance
      mlbDownYD_R4 = YD_R4; class=class="str">"cmt">//--- Store REC4 y-distance
      mlbDownX5 = MouseD_X; class=class="str">"cmt">//--- Store mouse x-coordinate for REC5
      mlbDownY5 = MouseD_Y; class=class="str">"cmt">//--- Store mouse y-coordinate for REC5
      mlbDownXD_R5 = XD_R5; class=class="str">"cmt">//--- Store REC5 x-distance
      mlbDownYD_R5 = YD_R5; class=class="str">"cmt">//--- Store REC5 y-distance
      if(MouseD_X >= XD_R1 && MouseD_X <= XD_R1 + XS_R1 && class=class="str">"cmt">//--- Check if mouse is within REC1 bounds
         MouseD_Y >= YD_R1 && MouseD_Y <= YD_R1 + YS_R1) {
         movingState_R1 = true;                            class=class="str">"cmt">//--- Enable REC1 movement
      }
      if(MouseD_X >= XD_R3 && MouseD_X <= XD_R3 + XS_R3 && class=class="str">"cmt">//--- Check if mouse is within REC3 bounds
         MouseD_Y >= YD_R3 && MouseD_Y <= YD_R3 + YS_R3) {
         movingState_R3 = true;                            class=class="str">"cmt">//--- Enable REC3 movement
      }
      if(MouseD_X >= XD_R5 && MouseD_X <= XD_R5 + XS_R5 && class=class="str">"cmt">//--- Check if mouse is within REC5 bounds
         MouseD_Y >= YD_R5 && MouseD_Y <= YD_R5 + YS_R5) {
         movingState_R5 = true;                            class=class="str">"cmt">//--- Enable REC5 movement
      }
   }
   if(movingState_R1) {                                                                        class=class="str">"cmt">//--- Handle REC1(TP) movement
      ChartSetInteger(class="num">0, CHART_MOUSE_SCROLL, class="kw">false);                                           class=class="str">"cmt">//--- Disable chart scrolling
      class="type">bool canMove = class="kw">false;                                                                    class=class="str">"cmt">//--- Flag to check if movement is valid
      if(selected_order_type == "BUY_STOP" || selected_order_type == "BUY_LIMIT") {            class=class="str">"cmt">//--- Check for buy orders
         if(YD_R1 + YS_R1 < YD_R3) {                                                           class=class="str">"cmt">//--- Ensure TP is above entry for buy orders
            canMove = true;                                                                    class=class="str">"cmt">//--- Allow movement

◍ 拖拽止损框时的坐标换算与校验

在 MT5 自定义控件里拖动止损矩形(REC5 之前的逻辑处理的是 REC1/REC2/REC4 的 TP 拖拽),核心是先判断买卖方向再改对象属性。买单要求 TP 纵坐标 YD_R1 小于 YD_R3 + YS_R3,卖单则反过来要求 YD_R1 大于 YD_R3 + YS_R3,不满足就令 canMove 保持 false,避免把止损拖到入场同侧。 通过 ChartXYToTimePrice(0, XD_R1, YD_R1 + YS_R1, window, dt_TP, price_TP) 把屏幕坐标转成真实价格,再用 ObjectSetDouble(0, TP_HL, OBJPROP_PRICE, price_TP) 写回水平线。实测中若 YD_R1 与 YS_R1 合计超出图表可视区,price_TP 会返回 0,拖拽瞬间 TP 线消失,需自行加边界判断。 文本刷新用 update_Text 拼接 DoubleToString(MathAbs((Get_Price_d(TP_HL)-Get_Price_d(PR_HL))/_Point),0) 显示点值差,外汇与贵金属杠杆高、滑点大,这类手动拖框得出的止损距离仅作参考,实盘触发概率受流动性影响。

MQL5 / C++
ObjectSetInteger(class="num">0, REC1, OBJPROP_YDISTANCE, mlbDownYD_R1 + MouseD_Y - mlbDownY1); class=class="str">"cmt">//--- Update REC1 y-position
ObjectSetInteger(class="num">0, REC2, OBJPROP_YDISTANCE, YD_R1 + YS_R1);                       class=class="str">"cmt">//--- Update REC2 y-position
ObjectSetInteger(class="num">0, REC2, OBJPROP_YSIZE, YD_R3 - (YD_R1 + YS_R1));                 class=class="str">"cmt">//--- Update REC2 y-size
 } 
 }
 else {                                                                                   class=class="str">"cmt">//--- Handle sell orders
   if(YD_R1 > YD_R3 + YS_R3) {                                                           class=class="str">"cmt">//--- Ensure TP is below entry for sell orders
     canMove = true;                                                                    class=class="str">"cmt">//--- Allow movement
     ObjectSetInteger(class="num">0, REC1, OBJPROP_YDISTANCE, mlbDownYD_R1 + MouseD_Y - mlbDownY1); class=class="str">"cmt">//--- Update REC1 y-position
     ObjectSetInteger(class="num">0, REC4, OBJPROP_YDISTANCE, YD_R3 + YS_R3);                       class=class="str">"cmt">//--- Update REC4 y-position
     ObjectSetInteger(class="num">0, REC4, OBJPROP_YSIZE, YD_R1 - (YD_R3 + YS_R3));                 class=class="str">"cmt">//--- Update REC4 y-size
   }
 }
 
 if(canMove) {           class=class="str">"cmt">//--- If movement is valid
   class="type">class="kw">datetime dt_TP = class="num">0;  class=class="str">"cmt">//--- Variable for TP time
   class="type">class="kw">double price_TP = class="num">0; class=class="str">"cmt">//--- Variable for TP price
   class="type">int window = class="num">0;      class=class="str">"cmt">//--- Chart window
   
   ChartXYToTimePrice(class="num">0, XD_R1, YD_R1 + YS_R1, window, dt_TP, price_TP); class=class="str">"cmt">//--- Convert chart coordinates to time and price
   ObjectSetInteger(class="num">0, TP_HL, OBJPROP_TIME, dt_TP);                      class=class="str">"cmt">//--- Update TP horizontal line time
   ObjectSetDouble(class="num">0, TP_HL, OBJPROP_PRICE, price_TP);                   class=class="str">"cmt">//--- Update TP horizontal line price
   
   update_Text(REC1, "TP: " + DoubleToString(MathAbs((Get_Price_d(TP_HL) - Get_Price_d(PR_HL)) / _Point), class="num">0) + " Points | " + Get_Price_s(TP_HL)); class=class="str">"cmt">//--- Update REC1 text
   update_Text(TP_LABEL, "TP: " + Get_Price_s(TP_HL));                                                                                             class=class="str">"cmt">//--- Update TP label text
 }
 ChartRedraw(class="num">0); class=class="str">"cmt">//--- Redraw chart
 }

 if(movingState_R5) {                                                                        class=class="str">"cmt">//--- Handle REC5(SL) movement
   ChartSetInteger(class="num">0, CHART_MOUSE_SCROLL, class="kw">false);                                           class=class="str">"cmt">//--- Disable chart scrolling
   class="type">bool canMove = class="kw">false;                                                                    class=class="str">"cmt">//--- Flag to check if movement is valid

拖拽止损线的坐标换算与方向校验

在 MT5 脚本里用鼠标拖动止损矩形(REC5)时,买挂单和卖挂单的合法性判定是反着的。买类订单(BUY_STOP / BUY_LIMIT)要求 YD_R5 大于 YD_R4,即止损位置必须落在入场线下方;卖类订单则要求 YD_R5 + YS_R5 小于 YD_R3,止损须在入场线上方,否则 canMove 保持 false,拖动直接无效。 一旦通过方向校验,代码用 ObjectSetInteger 改写 REC5 的 YDISTANCE 跟随鼠标位移,同时重算 REC4(买)或 REC2(卖)的 YDISTANCE 与 YSIZE,让风险区域色块实时拉伸。这里 YS_R3、YS_R5 都是像素尺寸常量,修改它们能改变色块默认厚度。 真正把屏幕坐标变成交易价格的是 ChartXYToTimePrice:传入 XD_R5 与 YD_R5+YS_R5,回填 dt_SL 与 price_SL,再写进 SL_HL 水平线。外汇与贵金属杠杆高,这类拖拽改的是挂单风险参数,实盘前务必在策略测试器用历史 tick 验证坐标映射无偏移。 最后 update_Text 把「SL: N Points | 价格」刷到 REC5 和 SL_LABEL 上,ChartRedraw(0) 强制重绘。开 MT5 把这段粘进 EA 的鼠标事件分支,拖一下就能看见点值随位移实时跳动。

MQL5 / C++
if(selected_order_type == "BUY_STOP" || selected_order_type == "BUY_LIMIT") {            class=class="str">"cmt">//--- Check for buy orders
     if(YD_R5 > YD_R4) {                                                                   class=class="str">"cmt">//--- Ensure SL is below entry for buy orders
          canMove = true;                                                                    class=class="str">"cmt">//--- Allow movement
          ObjectSetInteger(class="num">0, REC5, OBJPROP_YDISTANCE, mlbDownYD_R5 + MouseD_Y - mlbDownY5); class=class="str">"cmt">//--- Update REC5 y-position
          ObjectSetInteger(class="num">0, REC4, OBJPROP_YDISTANCE, YD_R3 + YS_R3);                       class=class="str">"cmt">//--- Update REC4 y-position
          ObjectSetInteger(class="num">0, REC4, OBJPROP_YSIZE, YD_R5 - (YD_R3 + YS_R3));                 class=class="str">"cmt">//--- Update REC4 y-size
     }
}
else {                                                                                   class=class="str">"cmt">//--- Handle sell orders
     if(YD_R5 + YS_R5 < YD_R3) {                                                           class=class="str">"cmt">//--- Ensure SL is above entry for sell orders
          canMove = true;                                                                    class=class="str">"cmt">//--- Allow movement
          ObjectSetInteger(class="num">0, REC5, OBJPROP_YDISTANCE, mlbDownYD_R5 + MouseD_Y - mlbDownY5); class=class="str">"cmt">//--- Update REC5 y-position
          ObjectSetInteger(class="num">0, REC2, OBJPROP_YDISTANCE, YD_R5 + YS_R5);                       class=class="str">"cmt">//--- Update REC2 y-position
          ObjectSetInteger(class="num">0, REC2, OBJPROP_YSIZE, YD_R3 - (YD_R5 + YS_R5));                 class=class="str">"cmt">//--- Update REC2 y-size
     }
}

if(canMove) {           class=class="str">"cmt">//--- If movement is valid
     class="type">class="kw">datetime dt_SL = class="num">0;  class=class="str">"cmt">//--- Variable for SL time
     class="type">class="kw">double price_SL = class="num">0; class=class="str">"cmt">//--- Variable for SL price
     class="type">int window = class="num">0;      class=class="str">"cmt">//--- Chart window

     ChartXYToTimePrice(class="num">0, XD_R5, YD_R5 + YS_R5, window, dt_SL, price_SL); class=class="str">"cmt">//--- Convert chart coordinates to time and price
     ObjectSetInteger(class="num">0, SL_HL, OBJPROP_TIME, dt_SL);                      class=class="str">"cmt">//--- Update SL horizontal line time
     ObjectSetDouble(class="num">0, SL_HL, OBJPROP_PRICE, price_SL);                   class=class="str">"cmt">//--- Update SL horizontal line price

     update_Text(REC5, "SL: " + DoubleToString(MathAbs((Get_Price_d(PR_HL) - Get_Price_d(SL_HL)) / _Point), class="num">0) + " Points | " + Get_Price_s(SL_HL)); class=class="str">"cmt">//--- Update REC5 text
     update_Text(SL_LABEL, "SL: " + Get_Price_s(SL_HL));                                                                                             class=class="str">"cmt">//--- Update SL label text
}
ChartRedraw(class="num">0); class=class="str">"cmt">//--- Redraw chart

「拖拽矩形时同步刷新挂单位置」

在 REC3 被鼠标拖动的态里,先关掉图表横向滚动(CHART_MOUSE_SCROLL=false),否则边拖边滑会让坐标算偏。随后用按下时的基准距离加鼠标位移增量,把 REC1~REC5 共 5 个矩形的 XDISTANCE / YDISTANCE 一次性重算,实现整组对象跟手移动。 坐标只是像素,要变成能挂单的价位还得调 ChartXYToTimePrice。代码里取 REC3 下沿算入场价(PR_HL)、REC5 下沿算止损(SL_HL)、REC1 下沿算止盈(TP_HL),再写回对应水平线的 OBJPROP_TIME 与 OBJPROP_PRICE。 外汇与贵金属杠杆高,这类拖拽得出的 SL/TP 仅是基于屏幕坐标的参考位,实际成交受滑点影响,触发概率不等于确定性。开 MT5 把这段塞进鼠标事件回调,拖一下矩形就能看到三条水平线实时贴着价格走,比手填更不容易填错。

MQL5 / C++
if(movingState_R3) {                                                                  class=class="str">"cmt">//--- Handle REC3(Entry) movement
      ChartSetInteger(class="num">0, CHART_MOUSE_SCROLL, class="kw">false);                                     class=class="str">"cmt">//--- Disable chart scrolling
      ObjectSetInteger(class="num">0, REC3, OBJPROP_XDISTANCE, mlbDownXD_R3 + MouseD_X - mlbDownX3); class=class="str">"cmt">//--- Update REC3 x-position
      ObjectSetInteger(class="num">0, REC3, OBJPROP_YDISTANCE, mlbDownYD_R3 + MouseD_Y - mlbDownY3); class=class="str">"cmt">//--- Update REC3 y-position
      
      ObjectSetInteger(class="num">0, REC1, OBJPROP_XDISTANCE, mlbDownXD_R1 + MouseD_X - mlbDownX1); class=class="str">"cmt">//--- Update REC1 x-position
      ObjectSetInteger(class="num">0, REC1, OBJPROP_YDISTANCE, mlbDownYD_R1 + MouseD_Y - mlbDownY1); class=class="str">"cmt">//--- Update REC1 y-position
      
      ObjectSetInteger(class="num">0, REC2, OBJPROP_XDISTANCE, mlbDownXD_R2 + MouseD_X - mlbDownX2); class=class="str">"cmt">//--- Update REC2 x-position
      ObjectSetInteger(class="num">0, REC2, OBJPROP_YDISTANCE, mlbDownYD_R2 + MouseD_Y - mlbDownY2); class=class="str">"cmt">//--- Update REC2 y-position
      ObjectSetInteger(class="num">0, REC4, OBJPROP_XDISTANCE, mlbDownXD_R4 + MouseD_X - mlbDownX4); class=class="str">"cmt">//--- Update REC4 x-position
      ObjectSetInteger(class="num">0, REC4, OBJPROP_YDISTANCE, mlbDownYD_R4 + MouseD_Y - mlbDownY4); class=class="str">"cmt">//--- Update REC4 y-position
      ObjectSetInteger(class="num">0, REC5, OBJPROP_XDISTANCE, mlbDownXD_R5 + MouseD_X - mlbDownX5); class=class="str">"cmt">//--- Update REC5 x-position
      ObjectSetInteger(class="num">0, REC5, OBJPROP_YDISTANCE, mlbDownYD_R5 + MouseD_Y - mlbDownY5); class=class="str">"cmt">//--- Update REC5 y-position
      class="type">class="kw">datetime dt_PRC = class="num">0, dt_SL1 = class="num">0, dt_TP1 = class="num">0;        class=class="str">"cmt">//--- Variables for time
      class="type">class="kw">double price_PRC = class="num">0, price_SL1 = class="num">0, price_TP1 = class="num">0; class=class="str">"cmt">//--- Variables for price
      class="type">int window = class="num">0;                                     class=class="str">"cmt">//--- Chart window
      
      ChartXYToTimePrice(class="num">0, XD_R3, YD_R3 + YS_R3, window, dt_PRC, price_PRC); class=class="str">"cmt">//--- Convert REC3 coordinates to time and price
      ChartXYToTimePrice(class="num">0, XD_R5, YD_R5 + YS_R5, window, dt_SL1, price_SL1); class=class="str">"cmt">//--- Convert REC5 coordinates to time and price
      ChartXYToTimePrice(class="num">0, XD_R1, YD_R1 + YS_R1, window, dt_TP1, price_TP1); class=class="str">"cmt">//--- Convert REC1 coordinates to time and price
      ObjectSetInteger(class="num">0, PR_HL, OBJPROP_TIME, dt_PRC);    class=class="str">"cmt">//--- Update entry horizontal line time
      ObjectSetDouble(class="num">0, PR_HL, OBJPROP_PRICE, price_PRC); class=class="str">"cmt">//--- Update entry horizontal line price
      
      ObjectSetInteger(class="num">0, TP_HL, OBJPROP_TIME, dt_TP1);    class=class="str">"cmt">//--- Update TP horizontal line time
      ObjectSetDouble(class="num">0, TP_HL, OBJPROP_PRICE, price_TP1); class=class="str">"cmt">//--- Update TP horizontal line price
      
      ObjectSetInteger(class="num">0, SL_HL, OBJPROP_TIME, dt_SL1);    class=class="str">"cmt">//--- Update SL horizontal line time

◍ 拖拽结束与每跳刷新的对象同步

鼠标按键释放后,脚本把三个记录框的移动标志 movingState_R1/R3/R5 全部置 false,并重新打开图表滚动 CHART_MOUSE_SCROLL,避免拖完对象后图表被锁死。 在拖拽过程中,每帧都会用 ObjectSetDouble 把 SL 水平线价格刷新到 price_SL1,并调用 update_Text 重写 REC1/REC3/REC5 以及 Entry/SL/TP 三个标签。TP 与 SL 的显示点数用 MathAbs((Get_Price_d(...) - Get_Price_d(...)) / _Point) 算绝对点差,保留 0 位小数;手数则 DoubleToString(lot_size, 2) 固定两位小数。 写完文本立即 ChartRedraw(0) 强制重绘,否则 MT5 可能等到下一帧才更新,拖起来有粘滞感。 OnTick 里只做一件事:从 LOT_EDIT 编辑框读字符串,StringToDouble 转换,大于 0 才覆盖 lot_size。这意味着你在面板上改手数不需要重拖对象,下一跳自动生效。外汇与贵金属杠杆高,错设手数可能瞬间放大回撤,改完建议切到交易品种规格核对最小手数限制。 OnDeinit 调用 deleteObjects 与 deletePanel 清理所有自建图形,换周期或移除 EA 不会留下残影对象。

MQL5 / C++
   ObjectSetDouble(class="num">0, SL_HL, OBJPROP_PRICE, price_SL1); class=class="str">"cmt">//--- Update SL horizontal line price
   update_Text(REC1, "TP: " + DoubleToString(MathAbs((Get_Price_d(TP_HL) - Get_Price_d(PR_HL)) / _Point), class="num">0) + " Points | " + Get_Price_s(TP_HL)); class=class="str">"cmt">//--- Update REC1 text
   update_Text(REC3, selected_order_type + ": | Lot: " + DoubleToString(lot_size, class="num">2) + " | " + Get_Price_s(PR_HL));                                class=class="str">"cmt">//--- Update REC3 text
   update_Text(REC5, "SL: " + DoubleToString(MathAbs((Get_Price_d(PR_HL) - Get_Price_d(SL_HL)) / _Point), class="num">0) + " Points | " + Get_Price_s(SL_HL)); class=class="str">"cmt">//--- Update REC5 text
   update_Text(PRICE_LABEL, "Entry: " + Get_Price_s(PR_HL));                                                                                       class=class="str">"cmt">//--- Update entry label text
   update_Text(SL_LABEL, "SL: " + Get_Price_s(SL_HL));                                                                                             class=class="str">"cmt">//--- Update SL label text
   update_Text(TP_LABEL, "TP: " + Get_Price_s(TP_HL));                                                                                             class=class="str">"cmt">//--- Update TP label text

   ChartRedraw(class="num">0); class=class="str">"cmt">//--- Redraw chart
   }
   if(MouseState == class="num">0) {                            class=class="str">"cmt">//--- Check if mouse button is released
      movingState_R1 = class="kw">false;                       class=class="str">"cmt">//--- Disable REC1 movement
      movingState_R3 = class="kw">false;                       class=class="str">"cmt">//--- Disable REC3 movement
      movingState_R5 = class="kw">false;                       class=class="str">"cmt">//--- Disable REC5 movement
      ChartSetInteger(class="num">0, CHART_MOUSE_SCROLL, true); class=class="str">"cmt">//--- Enable chart scrolling
   }
   prevMouseState = MouseState;                     class=class="str">"cmt">//--- Update previous mouse state
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(const class="type">int reason) {
   deleteObjects(); class=class="str">"cmt">//--- Delete tool objects
   deletePanel();   class=class="str">"cmt">//--- Delete control panel objects
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick() {
   class=class="str">"cmt">// Update lot size from edit field
   class="type">class="kw">string lot_text = ObjectGetString(class="num">0, LOT_EDIT, OBJPROP_TEXT); class=class="str">"cmt">//--- Get lot size text from edit field
   class="type">class="kw">double new_lot = StringToDouble(lot_text);                    class=class="str">"cmt">//--- Convert lot size text to class="type">class="kw">double
   if(new_lot > class="num">0) lot_size = new_lot;                           class=class="str">"cmt">//--- Update lot size if valid
}

一点提醒

这套交互式挂单助手把图形面板和 placeOrder 逻辑绑在一起,本质是用 GUI 降低手动拖价误差,44.77 KB 的 mq5 文件在 MT5 里编译后就能直接挂常用品种试。 自定义空间主要在 createControlPanel 的参数区,改默认手数或挂单偏移量比重写整套框架更实际,外汇与贵金属波动剧烈,杠杆风险高,任何工具都不替代仓位约束。 后续若接风险管理模块,建议先拿模拟盘跑一周再看滑点表现,别一上来就切真仓。

让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到挂单面板与实时点差层,把重复劳动交给小布,你专注决策。

常见问题

多半是价格偏离市价超距、点差缓冲没留足,或面板对象ID常量重复。回测时先单键确认再放宽容差。
用Get_Price_d取double精度比对,渲染前做最小点数校验,重叠则禁止单键确认。
小布盯盘品种页已内置类似交互层与AIGC诊断,不必自己编译EA也能看实时挂单反馈。
贵金属跳空可能使止损限价单不成交,属高风险品种;工具仅降操作错,不消除市场风险,概率上仍可能滑点。