MQL5交易工具(第二部分):为交互式交易助手添加动态视觉反馈·综合运用
(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 个值如何随对象移动而变化,验证读取逻辑是否脱钩。
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 的初值,能确认每个矩形在按下瞬间的锚点距离。外汇与贵金属图表上这类手动面板交互延迟可能随品种波动放大,属高风险操作环境,参数需实盘验证。
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。外汇与贵金属杠杆高,这类边界约束能减少误拖导致的异常挂单。
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 把这段塞进鼠标事件回调即可看效果。
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 跑一遍拖拽逻辑。
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) 必须放在坐标写完之后,否则屏幕上的矩形和水平线会出现一帧错位,肉眼可见的闪烁在剥头皮策略里可能干扰判断。
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 拼出订单类型、手数(保留两位)和入场价,界面信息随拖拽同步刷新。
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,建议直接把这段抄进你的脚本里跑一遍。
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,让背景和表头等强制置于前景。外汇与贵金属交易界面高杠杆高风险,这类自定义面板仅作辅助,实盘前请在策略测试器里跑一遍确认无对象泄漏。
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 的面板重绘函数,切周期时若按钮颜色不乱跳,就说明复位链路通了。
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 微偏差在高杠杆下会放大成实际滑点风险。
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 边缘就拖不动,作者侧疑似边界拦截未放开——真要自用,先在自己图表复现这个卡点再决定跟不跟。