MQL5交易工具(第二部分):为交互式交易助手添加动态视觉反馈·综合运用
🎛️

MQL5交易工具(第二部分):为交互式交易助手添加动态视觉反馈·综合运用

(3/3)·从可拖拽面板到悬停高亮与订单预校验,把交互摩擦压到最低

新手友好 第 3/3 篇

接上篇,我们继续深挖这套交易助手的收尾工程。前面两篇铺垫了挂单逻辑与基础结构,这一篇把界面响应和参数校验补齐,让工具真正好用而不是只是能跑。

「拖拽结束与矩形参数实时读取」

鼠标按键释放后,先把 panel_dragging 标记置 false,再用 ChartSetInteger(0, CHART_MOUSE_SCROLL, true) 恢复图表滚轮缩放。若不恢复,用户拖完面板会发现图表无法滚动,体验直接断档。 紧接着在 tool_visible 为真时,批量抓取 REC1、REC2、REC3 三个矩形的坐标与尺寸。以 REC1 为例:OBJPROP_XDISTANCE 取左边距像素、OBJPROP_YDISTANCE 取上边距像素、OBJPROP_XSIZE / OBJPROP_YSIZE 取宽高。这三组共 12 个整数,是后续计算价格区间与触发信号的原始输入。 别把正态当圣经:这里读的是屏幕像素距离,不是时间轴坐标。若直接在 OnChartEvent 里用,需配合 ChartXYToTimePrice 转换;只拿 XDISTANCE 做判断会随图表拉伸而漂移。 让小布替你跑这套:开 MT5 挂上带三个矩形对象的 EA,在鼠标松开断点处打印 XD_R1 与 YS_R3,拖一次面板就能看到 12 个值如何随对象移动而变化,验证读取逻辑是否脱钩。

MQL5 / C++
ObjectSetInteger(class="num">0, PLACE_ORDER_BTN, OBJPROP_XDISTANCE, panel_x + class="num">10);
ObjectSetInteger(class="num">0, PLACE_ORDER_BTN, OBJPROP_YDISTANCE, panel_y + class="num">240);
ObjectSetInteger(class="num">0, CANCEL_BTN, OBJPROP_XDISTANCE, panel_x + class="num">130);
ObjectSetInteger(class="num">0, CANCEL_BTN, OBJPROP_YDISTANCE, panel_y + class="num">240);
ChartRedraw(class="num">0);                                                   class=class="str">"cmt">//--- Redraw chart
   }
   if(MouseState == class="num">0) {                                                class=class="str">"cmt">//--- Mouse button released
      if(panel_dragging) {
         panel_dragging = false;                                        class=class="str">"cmt">//--- Stop dragging
         ChartSetInteger(class="num">0, CHART_MOUSE_SCROLL, true);                  class=class="str">"cmt">//--- Re-enable chart scrolling
      }
   }
   if(tool_visible) {                                                   class=class="str">"cmt">//--- Handle tool movement
      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

矩形对象的尺寸抓取与鼠标按下初值缓存

在 MT5 自定义面板里批量读取图形对象属性时,用 ObjectGetInteger 配 OBJPROP_XDISTANCE / YDISTANCE / XSIZE / YSIZE 能一次性把 REC3~REC5 这组矩形的屏幕坐标与像素尺寸拉进整型变量。上面这段只示意外了 REC3、REC4、REC5 的读取,实际 REC1、REC2 在前文已用同样宏取过,五个矩形共 20 个整型槽位,足以覆盖拖拽重算所需的基准。 鼠标左键刚按下那一帧(prevMouseState==0 且 MouseState==1)且面板不在拖动态,代码会把 MouseD_X / MouseD_Y 以及各矩形的 XD/YD 初值分别存进 mlbDownXn、mlbDownYD_Rn 系列变量。这里加 !panel_dragging 判断很关键:若整块面板正被拖走,就不该把这些矩形当成独立命中目标,否则会出现错位吸附。 开 MT5 把这段塞进 OnChartEvent 的鼠标分支,断点看 mlbDownXD_R1~R5 的初值,能确认每个矩形在按下瞬间的锚点距离。外汇与贵金属图表上这类手动面板交互延迟可能随品种波动放大,属高风险操作环境,参数需实盘验证。

MQL5 / C++
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
if(prevMouseState == class="num">0 && MouseState == class="num">1 && !panel_dragging) {   class=class="str">"cmt">//--- Check for mouse button down, avoid dragging conflict
   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

◍ 拖拽矩形时的坐标快照与命中判定

在鼠标左键按下事件里,先把 REC4、REC5 的鼠标坐标和矩形距离变量存下来:mlbDownY4 记鼠标 Y,mlbDownXD_R4 / mlbDownYD_R4 记矩形左上角距离,REC5 同理用 mlbDownX5 / mlbDownY5 及对应的 XD_R5 / YD_R5。这组快照是后续拖动时计算偏移量的基准,缺了就会跳帧。 接着用三组独立 if 做命中测试:以 REC1 为例,判断 MouseD_X 是否在 [XD_R1, XD_R1+XS_R1]、MouseD_Y 是否在 [YD_R1, YD_R1+YS_R1] 区间内。命中后把 movingState_R1 置 true,并调 ChartSetInteger(0, CHART_MOUSE_SCROLL, false) 关掉图表滚动,避免拖矩形时画面乱跑。REC3、REC5 的判定逻辑完全一致,只是换了对应变量名。 进入 movingState_R1 分支后,先声明 bool canMove = false 作为移动合法性闸门。若当前选中的挂单类型是 BUY_STOP 或 BUY_LIMIT,再进一步要求 YD_R1 + YS_R1 < YD_R3——也就是止盈矩形底沿必须高于入场矩形(REC3),满足条件才把 canMove 翻成 true。外汇与贵金属杠杆高,这类边界约束能减少误拖导致的异常挂单。

MQL5 / C++
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
   ChartSetInteger(class="num">0, CHART_MOUSE_SCROLL, false);              class=class="str">"cmt">//--- Disable chart scrolling
}
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
   ChartSetInteger(class="num">0, CHART_MOUSE_SCROLL, false);              class=class="str">"cmt">//--- Disable chart scrolling
}
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
   ChartSetInteger(class="num">0, CHART_MOUSE_SCROLL, false);              class=class="str">"cmt">//--- Disable chart scrolling
}
}
if(movingState_R1) {                                                                           class=class="str">"cmt">//--- Handle REC1(TP) movement
   class="type">bool canMove = 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

「拖拽止盈框时的坐标换算与方向校验」

这段逻辑处理的是鼠标拖动矩形对象改 TP 位置时,MT5 图形对象属性的实时刷新。买单调 TP 用 REC2、卖单用 REC4,核心是先判断 YD_R1 与 YD_R3 的相对位置,保证止盈落在入场另一侧。 买单方面,当 YD_R1 < YD_R3 时 canMove 置真,说明 TP 在入口上方,此时用 ObjectSetInteger 改 REC1 的 YDISTANCE 跟随鼠标,REC2 的 YDISTANCE 与 YSIZE 按 YD_R1、YS_R1、YD_R3 重算,框出止盈区间。 卖单分支反过来:YD_R1 > YD_R3 + YS_R3 才允许移动,确保 TP 在入口下方;REC4 的 YSIZE 等于 YD_R1 减去 (YD_R3+YS_R3),把距离画成可视块。外汇与贵金属杠杆高,拖错方向可能把止损当止盈,实盘前先在策略测试器用历史 tick 验证。 canMove 通过后,ChartXYToTimePrice 把图表 X/Y 坐标转成时间价格,写回 TP_HL 水平线;最后 update_Text 用 MathAbs 算 TP 与 PR_HL 的point差并拼成『TP: N Points | 价格』,开 MT5 把这段塞进鼠标事件回调即可看效果。

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

拖拽止损矩形时的坐标约束

在挂单辅助面板里,REC5 代表止损(SL)拖拽区。鼠标移动时程序先判断 canMove 是否为真,避免把买挂单的止损拖到入场价上方、或把卖挂单的止损拖到入场价下方——这类违规操作在 MT5 里会直接拒单。 对于 BUY_STOP / BUY_LIMIT,只有当 YD_R5 > YD_R4 时才放行,此时用 mlbDownYD_R5 + MouseD_Y - mlbDownY5 重算 REC5 的 YDISTANCE,并同步把 REC4(止损到入场的区间块)的 YDISTANCE 与 YSIZE 跟着改。卖单分支反过来,要求 YD_R5 + YS_R5 < YD_R3,再相应调整 REC2。 这种按订单方向分开校验的做法,能让图表上的 SL 块永远落在合理侧。外汇与贵金属波动大、滑点频繁,拖错止损位置可能让风险失控,实盘前建议在策略测试器里用历史 tick 跑一遍拖拽逻辑。

MQL5 / C++
if(movingState_R5) {
   class="type">bool canMove = false;
   if(selected_order_type == "BUY_STOP" || selected_order_type == "BUY_LIMIT") {
      if(YD_R5 > YD_R4) {
         canMove = true;
         ObjectSetInteger(class="num">0, REC5, OBJPROP_YDISTANCE, mlbDownYD_R5 + MouseD_Y - mlbDownY5);
         ObjectSetInteger(class="num">0, REC4, OBJPROP_YDISTANCE, YD_R3 + YS_R3);
         ObjectSetInteger(class="num">0, REC4, OBJPROP_YSIZE, YD_R5 - (YD_R3 + YS_R3));
      }
   }
   else {
      if(YD_R5 + YS_R5 < YD_R3) {
         canMove = true;
         ObjectSetInteger(class="num">0, REC5, OBJPROP_YDISTANCE, mlbDownYD_R5 + MouseD_Y - mlbDownY5);
         ObjectSetInteger(class="num">0, REC2, OBJPROP_YDISTANCE, YD_R5 + YS_R5);
         ObjectSetInteger(class="num">0, REC2, OBJPROP_YSIZE, YD_R3 - (YD_R5 + YS_R5));
      }
   }
   if(canMove) {

◍ 拖拽止损线时同步刷新坐标与标签

在 MT5 图表上用鼠标拖动矩形区块调整止损位时,核心是先通过 ChartXYToTimePrice 把屏幕像素坐标反算回时间—价格坐标,再写回水平线对象。下面这段逻辑里,XD_R5 / YD_R5 是鼠标落点,YS_R5 做了纵向偏移补偿,window 固定为 0 表示主图窗口。 datetime dt_SL=0; double price_SL=0; int window=0; ChartXYToTimePrice(0, XD_R5, YD_R5+YS_R5, window, dt_SL, price_SL); ObjectSetInteger(0, SL_HL, OBJPROP_TIME, dt_SL); ObjectSetDouble(0, SL_HL, OBJPROP_PRICE, price_SL); update_Text(REC5, "SL: "+DoubleToString(MathAbs((Get_Price_d(PR_HL)-Get_Price_d(SL_HL))/_Point),0)+" Points | "+Get_Price_s(SL_HL)); update_Text(SL_LABEL, "SL: "+Get_Price_s(SL_HL)); 写回后 REC5 文本会显示止损距入场价的点数差,例如黄金 XAUUSD 在 1 点=0.01 报价下,拖动后可能直接看到「SL: 350 Points」这类反馈,方便即时评估风险。外汇与贵金属杠杆高,拖拽改 SL 只解决显示与挂单位置,实际成交仍受滑点影响。 若入场块 REC3 处于移动状态,则要把 REC1/REC2/REC4 的 XDISTANCE、YDISTANCE 按鼠标位移量同步偏移,保持区域联动不变形。位移计算用 mlbDownXD_R3 + MouseD_X - mlbDownX3,即「按下时坐标 + 当前鼠标增量 - 按下时鼠标坐标」,这套写法在 1920×1080 屏上拖拽延迟通常低于 16ms。 别让颜色更新漏掉 updateRectangleColors() 和 ChartRedraw(0) 必须放在坐标写完之后,否则屏幕上的矩形和水平线会出现一帧错位,肉眼可见的闪烁在剥头皮策略里可能干扰判断。

MQL5 / C++
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
}
   updateRectangleColors();                                                                    class=class="str">"cmt">//--- Update rectangle colors
   ChartRedraw(class="num">0);                                                                             class=class="str">"cmt">//--- Redraw chart
   }
   
   if(movingState_R3) { class=class="str">"cmt">//--- Handle REC3(Entry) movement
      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 把 REC4、REC5 的 XDISTANCE 和 YDISTANCE 按鼠标偏移量实时改写,图形才能跟着光标走。 随后声明 dt_PRC/dt_SL1/dt_TP1 与 price_PRC/price_SL1/price_TP1 六组变量,配合 window=0 指定主图窗口,用 ChartXYToTimePrice 把 REC3、REC5、REC1 的像素坐标转成具体时间和价格。 拿到时间价格后,ObjectSetInteger 改 OBJPROP_TIME、ObjectSetDouble 改 OBJPROP_PRICE,分别刷新入场线 PR_HL、止盈线 TP_HL、止损线 SL_HL。外汇与贵金属波动剧烈,这类拖拽测算出的 SL/TP 仅反映当下鼠标位置,实盘触发概率受滑点影响。 最后 update_Text 重算文字:REC1 显示 TP 距离入场线多少 Point(用 MathAbs 除 _Point 并 DoubleToString 取整)及具体价格;REC3 拼出订单类型、手数(保留两位)和入场价,界面信息随拖拽同步刷新。

MQL5 / C++
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
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

拖完价位后怎么刷新标签和面板

鼠标松开后,EA 要把 REC5 上的止损点数和价格文本实时算出来。下面这行把入场价与止损价的差除以 _Point 取绝对值,再用 DoubleToString 转成 0 位小数的「Points」字符串,外汇和贵金属点值随品种跳动,肉眼读差比手动算更稳。 update_Text(REC5, "SL: " + DoubleToString(MathAbs((Get_Price_d(PR_HL) - Get_Price_d(SL_HL)) / _Point), 0) + " Points | " + Get_Price_s(SL_HL)); 随后 Entry、SL、TP 三个标签各自调 update_Text 重写文字,再跑 updateRectangleColors 按多空状态换矩形颜色,ChartRedraw(0) 强制重绘——不重绘的话 MT5 上经常出现拖了线但标签晚半秒才跟上的鬼影。 MouseState == 0 即左键释放,把 movingState_R1/R3/R5 全置 false,并 ChartSetInteger(0, CHART_MOUSE_SCROLL, true) 把图表滚动交还用户。若忘了这步,面板拖完图表就锁死不能滚,体验直接崩。 控制面板本体用 OBJ_RECTANGLE_LABEL 画,宽 250、高 280,背景色 C'070,070,070'、白边、置顶(OBJPROP_BACK=false)。这种尺寸在 1920 宽屏约占 13% 横向像素,遮 K 线不多但够放三组价签。外汇贵金属波动快,面板刷新逻辑卡一顿都可能漏掉一根 pin bar,建议直接把这段抄进你的脚本里跑一遍。

MQL5 / C++
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
updateRectangleColors(); class=class="str">"cmt">//--- Update rectangle colors
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 = false;                          class=class="str">"cmt">//--- Disable REC1 movement
  movingState_R3 = false;                          class=class="str">"cmt">//--- Disable REC3 movement
  movingState_R5 = 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="type">void createControlPanel() {
  ObjectCreate(class="num">0, PANEL_BG, OBJ_RECTANGLE_LABEL, class="num">0, class="num">0, class="num">0);                   class=class="str">"cmt">//--- Create panel background rectangle
  ObjectSetInteger(class="num">0, PANEL_BG, OBJPROP_XDISTANCE, panel_x);                 class=class="str">"cmt">//--- Set background x-position
  ObjectSetInteger(class="num">0, PANEL_BG, OBJPROP_YDISTANCE, panel_y);                 class=class="str">"cmt">//--- Set background y-position
  ObjectSetInteger(class="num">0, PANEL_BG, OBJPROP_XSIZE, class="num">250);                         class=class="str">"cmt">//--- Set background width
  ObjectSetInteger(class="num">0, PANEL_BG, OBJPROP_YSIZE, class="num">280);                         class=class="str">"cmt">//--- Set background height
  ObjectSetInteger(class="num">0, PANEL_BG, OBJPROP_BGCOLOR, C&class="macro">#x27;class="num">070,class="num">070,class="num">070&class="macro">#x27;);            class=class="str">"cmt">//--- Set background class="type">color
  ObjectSetInteger(class="num">0, PANEL_BG, OBJPROP_BORDER_COLOR, clrWhite);             class=class="str">"cmt">//--- Set border class="type">color
  ObjectSetInteger(class="num">0, PANEL_BG, OBJPROP_BACK, false);                        class=class="str">"cmt">//--- Set background to foreground
}

◍ 面板对象的销毁与前置显示

下单面板的生命周期里,退出或刷新时要把所有图形对象清掉,否则 MT5 图表会残留按钮和标签。deletePanel() 用 ObjectDelete(0, 对象名) 逐个删除,从 PANEL_BG 到 CLOSE_BTN 共 14 个对象,最后用 ChartRedraw(0) 强制重绘让残留消失。 关闭按钮的创建值得单独看:createButton(CLOSE_BTN, CharToString(203), panel_x+209, panel_y+1, 40, 25, clrWhite, clrCrimson, 12, clrBlack, false, "Wingdings") 里 CharToString(203) 配合 Wingdings 字体,渲染出来是叉号图标,位置在面板右侧偏移 209 像素、宽 40 高 25。 显示面板时 showPanel() 不给对象赋值可见性,而是把 OBJPROP_BACK 设为 false,让背景和表头等强制置于前景。外汇与贵金属交易界面高杠杆高风险,这类自定义面板仅作辅助,实盘前请在策略测试器里跑一遍确认无对象泄漏。

MQL5 / C++
createButton(PANEL_HEADER,"",panel_x+class="num">2,panel_y+class="num">2,class="num">250-class="num">4,class="num">28-class="num">3,clrBlue,C&class="macro">#x27;class="num">050,class="num">050,class="num">050&class="macro">#x27;,class="num">12,C&class="macro">#x27;class="num">050,class="num">050,class="num">050&class="macro">#x27;,false);

createButton(CLOSE_BTN, CharToString(class="num">203), panel_x + class="num">209, panel_y + class="num">1, class="num">40, class="num">25, clrWhite, clrCrimson, class="num">12, clrBlack, false, "Wingdings"); class=class="str">"cmt">//--- Create close button
class=class="str">"cmt">//---
}
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, PANEL_HEADER);     class=class="str">"cmt">//--- Delete panel header
   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">//| Show control panel                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void showPanel() {
   class=class="str">"cmt">// Ensure panel is in foreground
   ObjectSetInteger(class="num">0, PANEL_BG,        OBJPROP_BACK, false); class=class="str">"cmt">//--- Show panel background
   ObjectSetInteger(class="num">0, PANEL_HEADER,    OBJPROP_BACK, false); class=class="str">"cmt">//--- Show panel header
   ObjectSetInteger(class="num">0, LOT_EDIT,        OBJPROP_BACK, false); class=class="str">"cmt">//--- Show lot edit field
   ObjectSetInteger(class="num">0, PRICE_LABEL,     OBJPROP_BACK, false); class=class="str">"cmt">//--- Show price label

「挂单面板的显隐与配色复位」

在自定义挂单面板初始化或刷新时,需要把所有交互控件从背景层拉到前景,否则它们会被K线覆盖导致点不到。上面这段用 ObjectSetInteger 把 SL/TP 标签、六大挂单与操作按钮的 OBJPROP_BACK 统一设为 false,相当于一次性「置顶」9 个图形对象。 紧接着要把鼠标悬停状态全部清零:buy_stop_hovered 到 header_hovered 共 8 个布尔变量归位,避免上一次悬停的高亮逻辑残留到新周期。外汇与贵金属波动快,这类状态没清干净,面板可能误触发订单按钮,属于典型的高风险交互 bug。 配色也要复位成基准色:Buy 类按钮背景 clrForestGreen、Sell 类 clrFireBrick、下单键 clrDodgerBlue、取消键 clrSlateGray,边框统一 clrBlack。直接开 MT5 把这段粘进 EA 的面板重绘函数,切周期时若按钮颜色不乱跳,就说明复位链路通了。

MQL5 / C++
ObjectSetInteger(class="num">0, SL_LABEL,           OBJPROP_BACK, false); class=class="str">"cmt">//--- Show SL label
ObjectSetInteger(class="num">0, TP_LABEL,           OBJPROP_BACK, false); class=class="str">"cmt">//--- Show TP label
ObjectSetInteger(class="num">0, BUY_STOP_BTN,       OBJPROP_BACK, false); class=class="str">"cmt">//--- Show Buy Stop button
ObjectSetInteger(class="num">0, SELL_STOP_BTN,      OBJPROP_BACK, false); class=class="str">"cmt">//--- Show Sell Stop button
ObjectSetInteger(class="num">0, BUY_LIMIT_BTN,      OBJPROP_BACK, false); class=class="str">"cmt">//--- Show Buy Limit button
ObjectSetInteger(class="num">0, SELL_LIMIT_BTN,     OBJPROP_BACK, false); class=class="str">"cmt">//--- Show Sell Limit button
ObjectSetInteger(class="num">0, PLACE_ORDER_BTN,    OBJPROP_BACK, false); class=class="str">"cmt">//--- Show Place Order button
ObjectSetInteger(class="num">0, CANCEL_BTN,         OBJPROP_BACK, false); class=class="str">"cmt">//--- Show Cancel button
ObjectSetInteger(class="num">0, CLOSE_BTN,          OBJPROP_BACK, false); class=class="str">"cmt">//--- Show Close button

class=class="str">"cmt">// Reset button hover states
buy_stop_hovered     = false;
sell_stop_hovered    = false;
buy_limit_hovered    = false;
sell_limit_hovered   = false;
place_order_hovered  = false;
cancel_hovered       = false;
close_hovered        = false;
header_hovered       = false;
class=class="str">"cmt">// Reset button colors
ObjectSetInteger(class="num">0, BUY_STOP_BTN,       OBJPROP_BGCOLOR,       clrForestGreen);
ObjectSetInteger(class="num">0, BUY_STOP_BTN,       OBJPROP_BORDER_COLOR,  clrBlack);
ObjectSetInteger(class="num">0, SELL_STOP_BTN,      OBJPROP_BGCOLOR,       clrFireBrick);
ObjectSetInteger(class="num">0, SELL_STOP_BTN,      OBJPROP_BORDER_COLOR,  clrBlack);
ObjectSetInteger(class="num">0, BUY_LIMIT_BTN,      OBJPROP_BGCOLOR,       clrForestGreen);
ObjectSetInteger(class="num">0, BUY_LIMIT_BTN,      OBJPROP_BORDER_COLOR,  clrBlack);
ObjectSetInteger(class="num">0, SELL_LIMIT_BTN,     OBJPROP_BGCOLOR,       clrFireBrick);
ObjectSetInteger(class="num">0, SELL_LIMIT_BTN,     OBJPROP_BORDER_COLOR,  clrBlack);
ObjectSetInteger(class="num">0, PLACE_ORDER_BTN,    OBJPROP_BGCOLOR,       clrDodgerBlue);
ObjectSetInteger(class="num">0, PLACE_ORDER_BTN,    OBJPROP_BORDER_COLOR,  clrBlack);
ObjectSetInteger(class="num">0, CANCEL_BTN,         OBJPROP_BGCOLOR,       clrSlateGray);
ObjectSetInteger(class="num">0, CANCEL_BTN,         OBJPROP_BORDER_COLOR,  clrBlack);

面板复位时的对象属性与状态清理

平仓按钮用深红底配黑边,面板头用 RGB(50,50,50) 的近黑底,这种高对比配色在 MT5 暗色模板下能降低误点概率。 复位逻辑里把 Entry、SL、TP 标签文本重写成占位符,清空已选订单类型并把工具可见性置否,等于把面板交还到初始空闲态。 最后两行很关键:重新打开 CHART_EVENT_MOUSE_MOVE 并强制 ChartRedraw,否则鼠标拖拽残留坐标可能让下一次下单锚点偏移几像素。外汇与贵金属波动剧烈,这类 UI 微偏差在高杠杆下会放大成实际滑点风险。

MQL5 / C++
  ObjectSetInteger(class="num">0, CLOSE_BTN,       OBJPROP_BGCOLOR,       clrCrimson);
  ObjectSetInteger(class="num">0, CLOSE_BTN,       OBJPROP_BORDER_COLOR,  clrBlack);
  ObjectSetInteger(class="num">0, PANEL_HEADER,    OBJPROP_BGCOLOR,       C&class="macro">#x27;class="num">050,class="num">050,class="num">050&class="macro">#x27;);
  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        = false;                        class=class="str">"cmt">//--- Hide tool
  ChartSetInteger(class="num">0, CHART_EVENT_MOUSE_MOVE, true);   class=class="str">"cmt">//--- Ensure mouse move events are enabled
  ChartRedraw(class="num">0);                                     class=class="str">"cmt">//--- Redraw chart
}

◍ 把工具请下神坛

这套挂单助手在 MT5 里补上了可拖动面板、悬停高亮和实时订单校验,本质是把人工盯盘时最容易手抖的那几步交给图形界面兜住。作者在自家风格下跑过一轮回测,59.4 KB 的 Trade_Assistant_GUI_Tool_Part_2.mq5 就是载体,但回测样本只覆盖其个人场景,不代表普适胜率。 外汇与贵金属杠杆高、滑点随机,任何 GUI 工具都只是降低操作摩擦,不替你判断方向。把它当扳手而不是预言机,开 MT5 加载 mq5 改两行参数,比信‘效率显著提高’这种空话有用。 评论区有人提到 TP/SL 贴着 entry 边缘就拖不动,作者侧疑似边界拦截未放开——真要自用,先在自己图表复现这个卡点再决定跟不跟。

把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到挂单参数与市价的偏离提示,你专注决策就好。

常见问题

通常会为每个按钮声明独立的布尔悬停变量,在 OnChartEvent 里比对光标坐标与对象边框,命中即置真并触发高亮重绘。
在鼠标移动事件里对计算出的新坐标做边界钳制,确保标题栏始终留在图表客户区内再提交重绘。
可以,小布的品种页已把入场、止损、止盈与实时报价的逻辑一致性做了可视化提示,不必自己写校验循环。
倾向在用户点击下单前就比对市价与止损距离,不满足最小止损或逻辑顺序时禁用放置按钮并给出颜色反馈。
界面交互本身不进回测,但订单生成与校验函数可抽离成无界面模块单独测,关于完整拆法见《MQL5交易工具·实战篇》。