MQL5交易工具(第二部分):为交互式交易助手添加动态视觉反馈·进阶篇
📘

MQL5交易工具(第二部分):为交互式交易助手添加动态视觉反馈·进阶篇

第 2/3 篇

◍ 挂单面板里的矩形配色随校验切换

在 MT5 自定义挂单面板里,REC1 和 REC5 通常分别代表止盈与止损区域,它们的背景色必须跟着订单校验状态走。若底层订单对象失效(例如品种停盘或手数越界),代码会直接把这两个矩形刷成灰色,避免交易者误读成有效挂单位置。 下面这段逻辑先判断 tool_visible,不可见就整体 return,省去无谓的重绘开销;随后用 isOrderValid() 决定配色分支。订单无效时 REC1/REC5 落为 clrGray,悬停时再提亮到 C'100,100,100' 做反馈。 订单有效时,无论 BUY_STOP/BUY_LIMIT 还是其余卖向类型,TP 矩形恒为绿系(悬停 C'0,100,0')、SL 矩形恒为红系(悬停 C'139,0,0');REC3 作为入场块固定浅灰、悬停转 C'105,105,105'。最后一行 ChartRedraw(0) 触发一次重画,鼠标移动才看得见颜色渐变。 外汇与贵金属杠杆高,面板颜色只是辅助提示,不代表挂单必然成交,实际以经纪商回报为准。

MQL5 / C++
class="type">void updateRectangleColors() {
  if(!tool_visible) class="kw">return;                                                     class=class="str">"cmt">//--- Skip if tool is not visible
  class="type">bool is_valid = isOrderValid();                                               class=class="str">"cmt">//--- Check order validity
  if(!is_valid) {
    class=class="str">"cmt">//--- Gray out REC1 and REC5 if order is invalid, with hover effect
    ObjectSetInteger(class="num">0, REC1, OBJPROP_BGCOLOR, rec1_hovered ? C&class="macro">#x27;class="num">100,class="num">100,class="num">100&class="macro">#x27; : clrGray);
    ObjectSetInteger(class="num">0, REC5, OBJPROP_BGCOLOR, rec5_hovered ? C&class="macro">#x27;class="num">100,class="num">100,class="num">100&class="macro">#x27; : clrGray);
  }
  else {
    class=class="str">"cmt">//--- Restore original colors based on order type and hover state
    if(selected_order_type == "BUY_STOP" || selected_order_type == "BUY_LIMIT") {
      ObjectSetInteger(class="num">0, REC1, OBJPROP_BGCOLOR, rec1_hovered ? C&class="macro">#x27;class="num">0,class="num">100,class="num">0&class="macro">#x27;  : clrGreen); class=class="str">"cmt">//--- TP rectangle(dark green on hover)
      ObjectSetInteger(class="num">0, REC5, OBJPROP_BGCOLOR, rec5_hovered ? C&class="macro">#x27;class="num">139,class="num">0,class="num">0&class="macro">#x27;  : clrRed);   class=class="str">"cmt">//--- SL rectangle(dark red on hover)
    }
    else {
      ObjectSetInteger(class="num">0, REC1, OBJPROP_BGCOLOR, rec1_hovered ? C&class="macro">#x27;class="num">0,class="num">100,class="num">0&class="macro">#x27;  : clrGreen); class=class="str">"cmt">//--- TP rectangle(dark green on hover)
      ObjectSetInteger(class="num">0, REC5, OBJPROP_BGCOLOR, rec5_hovered ? C&class="macro">#x27;class="num">139,class="num">0,class="num">0&class="macro">#x27;  : clrRed);   class=class="str">"cmt">//--- SL rectangle(dark red on hover)
    }
  }
  ObjectSetInteger(class="num">0, REC3, OBJPROP_BGCOLOR, rec3_hovered ? C&class="macro">#x27;class="num">105,class="num">105,class="num">105&class="macro">#x27; : clrLightGray); class=class="str">"cmt">//--- Entry rectangle(darker gray on hover)
  ChartRedraw(class="num">0);                                                                          class=class="str">"cmt">//--- Redraw chart
}

class="type">void updateButtonHoverState(class="type">int mouse_x, class="type">int mouse_y) {
  class=class="str">"cmt">// Define button names and their properties
  class="type">class="kw">string buttons[] = {BUY_STOP_BTN, SELL_STOP_BTN, BUY_LIMIT_BTN, SELL_LIMIT_BTN, PLACE_ORDER_BTN, CANCEL_BTN, CLOSE_BTN};
  class="type">bool hover_states[] = {buy_stop_hovered, sell_stop_hovered, buy_limit_hovered, sell_limit_hovered, place_order_hovered, cancel_hovered, close_hovered};

按钮与面板的悬停染色逻辑

在 MT5 自定义面板里,悬停反馈不能靠系统自带,得用鼠标坐标和物件坐标做碰撞判定。上面这段把 7 个按钮的常态色写进 normal_colors 数组,绿/砖红交替表达多空倾向,第 5 位 clrDodgerBlue 和第 6 位 clrSlateGray 留给中性功能键,第 7 位 clrCrimson 通常对应危险操作。 核心循环里先取每个按钮的 XDISTANCE、YDISTANCE、XSIZE、YSIZE 四个整数属性,再用 mouse_x/mouse_y 判断是否落在矩形内。进入判定后把背景刷成 hover_color(clrDodgerBlue)、边框刷成 clrBlue,同时把 hover_states[i] 置真,避免下一帧重复触发。 离开时还原用 normal_colors[i] 回填背景,边框统一回到 clrBlack。这里有个细节:标题栏 PANEL_HEADER 单独处理,悬停时背景压到 C'030,030,030'(近黑),移开恢复 C'050,050,050',比按钮暗两档,视觉上能分出层级。 开 MT5 把这段塞进 OnChartEvent 的 CHARTEVENT_MOUSE_MOVE 分支,就能直接看到面板随鼠标变色的实况;外汇与贵金属波动剧烈,这类交互面板只做辅助观察,实际操作仍需自担高风险。

MQL5 / C++
class="type">color normal_colors[] = {clrForestGreen, clrFireBrick, clrForestGreen, clrFireBrick, clrDodgerBlue, clrSlateGray, clrCrimson};
class="type">color hover_color = clrDodgerBlue;                      class=class="str">"cmt">//--- Bluish class="type">color for hover
class="type">color hover_border = clrBlue;                           class=class="str">"cmt">//--- Bluish border for hover
for(class="type">int i = class="num">0; i < ArraySize(buttons); i++) {
   class="type">int x = (class="type">int)ObjectGetInteger(class="num">0, buttons[i], OBJPROP_XDISTANCE);
   class="type">int y = (class="type">int)ObjectGetInteger(class="num">0, buttons[i], OBJPROP_YDISTANCE);
   class="type">int width = (class="type">int)ObjectGetInteger(class="num">0, buttons[i], OBJPROP_XSIZE);
   class="type">int height = (class="type">int)ObjectGetInteger(class="num">0, buttons[i], OBJPROP_YSIZE);
   class="type">bool is_hovered = (mouse_x >= x && mouse_x <= x + width && mouse_y >= y && mouse_y <= y + height);
   if(is_hovered && !hover_states[i]) {
      class=class="str">"cmt">// Mouse entered button
      ObjectSetInteger(class="num">0, buttons[i], OBJPROP_BGCOLOR, hover_color);
      ObjectSetInteger(class="num">0, buttons[i], OBJPROP_BORDER_COLOR, hover_border);
      hover_states[i] = true;
   }
   else if(!is_hovered && hover_states[i]) {
      class=class="str">"cmt">// Mouse left button
      ObjectSetInteger(class="num">0, buttons[i], OBJPROP_BGCOLOR, normal_colors[i]);
      ObjectSetInteger(class="num">0, buttons[i], OBJPROP_BORDER_COLOR, clrBlack);
      hover_states[i] = false;
   }
}
class=class="str">"cmt">// Update header hover state
class="type">int header_x = (class="type">int)ObjectGetInteger(class="num">0, PANEL_HEADER, OBJPROP_XDISTANCE);
class="type">int header_y = (class="type">int)ObjectGetInteger(class="num">0, PANEL_HEADER, OBJPROP_YDISTANCE);
class="type">int header_width = (class="type">int)ObjectGetInteger(class="num">0, PANEL_HEADER, OBJPROP_XSIZE);
class="type">int header_height = (class="type">int)ObjectGetInteger(class="num">0, PANEL_HEADER, OBJPROP_YSIZE);
class="type">bool is_header_hovered = (mouse_x >= header_x && mouse_x <= header_x + header_width && mouse_y >= header_y && mouse_y <= header_y + header_height);
if(is_header_hovered && !header_hovered) {
   ObjectSetInteger(class="num">0, PANEL_HEADER, OBJPROP_BGCOLOR, C&class="macro">#x27;class="num">030,class="num">030,class="num">030&class="macro">#x27;); class=class="str">"cmt">//--- Darken header
   header_hovered = true;
}
else if(!is_header_hovered && header_hovered) {
   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">//--- Restore header class="type">color
   header_hovered = false;
}
class=class="str">"cmt">// Update tool rectangle hover states
if(tool_visible) {

「用鼠标坐标判定矩形悬停状态」

在 MT5 的图表事件里做交互面板,核心是先抓出每个矩形对象的屏幕像素边界。上面这段代码对 REC1、REC3、REC5 三个对象分别取了 XDISTANCE、YDISTANCE、XSIZE、YSIZE 四个整数属性,转成 int 后就是对象左上角坐标和宽高。 悬停判定就是最朴素的包围盒测试:mouse_x 落在 [x, x+width] 且 mouse_y 落在 [y, y+height] 内,对应布尔量置真。三个矩形各算各的,互不干扰。 只有当本次悬停状态与上次记录的 rec1_hovered / rec3_hovered / rec5_hovered 任一不同,才调用 updateRectangleColors() 重刷颜色,避免每帧无用重绘。外汇与贵金属杠杆高,这类自定义面板仅用于辅助观测,不改交易决策本身。 后面把 hover_states 数组的 0~6 下标分别赋给 buy_stop_hovered、sell_stop_hovered 等七个状态变量,最后 ChartRedraw(0) 强制重绘。OnChartEvent 的 lparam 和 dparam 在鼠标事件里就是 x、y 坐标,直接拿来做上面的比对即可。

MQL5 / C++
class="type">int x1 = (class="type">int)ObjectGetInteger(class="num">0, REC1, OBJPROP_XDISTANCE);
class="type">int y1 = (class="type">int)ObjectGetInteger(class="num">0, REC1, OBJPROP_YDISTANCE);
class="type">int width1 = (class="type">int)ObjectGetInteger(class="num">0, REC1, OBJPROP_XSIZE);
class="type">int height1 = (class="type">int)ObjectGetInteger(class="num">0, REC1, OBJPROP_YSIZE);
class="type">int x3 = (class="type">int)ObjectGetInteger(class="num">0, REC3, OBJPROP_XDISTANCE);
class="type">int y3 = (class="type">int)ObjectGetInteger(class="num">0, REC3, OBJPROP_YDISTANCE);
class="type">int width3 = (class="type">int)ObjectGetInteger(class="num">0, REC3, OBJPROP_XSIZE);
class="type">int height3 = (class="type">int)ObjectGetInteger(class="num">0, REC3, OBJPROP_YSIZE);
class="type">int x5 = (class="type">int)ObjectGetInteger(class="num">0, REC5, OBJPROP_XDISTANCE);
class="type">int y5 = (class="type">int)ObjectGetInteger(class="num">0, REC5, OBJPROP_YDISTANCE);
class="type">int width5 = (class="type">int)ObjectGetInteger(class="num">0, REC5, OBJPROP_XSIZE);
class="type">int height5 = (class="type">int)ObjectGetInteger(class="num">0, REC5, OBJPROP_YSIZE);
class="type">bool is_rec1_hovered = (mouse_x >= x1 && mouse_x <= x1 + width1 && mouse_y >= y1 && mouse_y <= y1 + height1);
class="type">bool is_rec3_hovered = (mouse_x >= x3 && mouse_x <= x3 + width3 && mouse_y >= y3 && mouse_y <= y3 + height3);
class="type">bool is_rec5_hovered = (mouse_x >= x5 && mouse_x <= x5 + width5 && mouse_y >= y5 && mouse_y <= y5 + height5);
if(is_rec1_hovered != rec1_hovered || is_rec3_hovered != rec3_hovered || is_rec5_hovered != rec5_hovered) {
   rec1_hovered = is_rec1_hovered;
   rec3_hovered = is_rec3_hovered;
   rec5_hovered = is_rec5_hovered;
   updateRectangleColors();                            class=class="str">"cmt">//--- Update colors based on hover state
   }
}
class=class="str">"cmt">// Update hover state variables
buy_stop_hovered = hover_states[class="num">0];
sell_stop_hovered = hover_states[class="num">1];
buy_limit_hovered = hover_states[class="num">2];
sell_limit_hovered = hover_states[class="num">3];
place_order_hovered = hover_states[class="num">4];
cancel_hovered = hover_states[class="num">5];
close_hovered = hover_states[class="num">6];
ChartRedraw(class="num">0);                                           class=class="str">"cmt">//--- Redraw chart
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert onchart event function                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(
   const class="type">int id,          class=class="str">"cmt">//--- Event ID
   const class="type">long& lparam,    class=class="str">"cmt">//--- Long parameter(e.g., x-coordinate for mouse)
   const class="type">class="kw">double& dparam,  class=class="str">"cmt">//--- Double parameter(e.g., y-coordinate for mouse)

◍ 图表按钮点击如何驱动挂单逻辑

在 MT5 自定义面板里,挂单类型不是靠下拉框选的,而是靠图表对象点击事件分流。核心入口是 OnChartEvent 里对 CHARTEVENT_OBJECT_CLICK 的判断:哪个按钮对象名被点中,就给 selected_order_type 赋成对应字符串,并刷新面板文字与矩形配色。 点 BUY_STOP_BTN 就置为 "BUY_STOP",SELL_STOP_BTN 对应 "SELL_STOP",BUY_LIMIT_BTN 与 SELL_LIMIT_BTN 同理。每次赋值后都会调 showTool() 拉出交易工具,并用 update_Text(PLACE_ORDER_BTN, "Place Buy Stop") 之类把确认键文案改成对应方向,视觉上不会点错单。 真正发单只看 PLACE_ORDER_BTN:先跑 isOrderValid() 校验价格结构,通过才 placeOrder() 并清掉辅助对象、回控制面板;不通过就 Print 报出 "Cannot place order: Invalid price setup for " 加当前类型。外汇与贵金属杠杆高,校验失败硬发单可能瞬间扩大回撤,必须让这个函数卡死关口。 把这层点击路由吃透,你就能在 MT5 里自己拼一个纯图表交互的挂单面板,不用再依赖经纪商默认订单窗。

MQL5 / C++
const class="type">class="kw">string& sparam   class=class="str">"cmt">//--- String parameter(e.g., object name)
) {
  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
      updateRectangleColors();                                class=class="str">"cmt">//--- Update rectangle colors
    }
    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
      updateRectangleColors();                                class=class="str">"cmt">//--- Update rectangle colors
    }
    else if(sparam == BUY_LIMIT_BTN) {                         class=class="str">"cmt">//--- Check if Buy Limit button clicked
      selected_order_type = "BUY_LIMIT";                      class=class="str">"cmt">//--- Set order type to Buy Limit
      showTool();                                             class=class="str">"cmt">//--- Show trading tool
      update_Text(PLACE_ORDER_BTN, "Place Buy Limit");        class=class="str">"cmt">//--- Update place order button text
      updateRectangleColors();                                class=class="str">"cmt">//--- Update rectangle colors
    }
    else if(sparam == SELL_LIMIT_BTN) {                        class=class="str">"cmt">//--- Check if Sell Limit button clicked
      selected_order_type = "SELL_LIMIT";                     class=class="str">"cmt">//--- Set order type to Sell Limit
      showTool();                                             class=class="str">"cmt">//--- Show trading tool
      update_Text(PLACE_ORDER_BTN, "Place Sell Limit");       class=class="str">"cmt">//--- Update place order button text
      updateRectangleColors();                                class=class="str">"cmt">//--- Update rectangle colors
    }
    else if(sparam == PLACE_ORDER_BTN) {                       class=class="str">"cmt">//--- Check if Place Order button clicked
      if(isOrderValid()) {
        placeOrder();                                        class=class="str">"cmt">//--- Execute order placement
        deleteObjects();                                     class=class="str">"cmt">//--- Delete tool objects
        showPanel();                                         class=class="str">"cmt">//--- Show control panel
      }
      else {
        Print("Cannot place order: Invalid price setup for ", selected_order_type);
      }
    }
    else if(sparam == CANCEL_BTN) {                            class=class="str">"cmt">//--- Check if Cancel button clicked

面板拖拽与鼠标事件的底层处理

在 MT5 自定义指标里,控制面板的拖拽体验依赖 CHARTEVENT_MOUSE_MOVE 事件流。当鼠标按键由松开(prevMouseState==0)变为按下(MouseState==1),且坐标落在 PANEL_HEADER 的矩形范围内,才把 panel_dragging 置真,避免误触图表其他区域。 判定范围用的是 OBJPROP_XDISTANCE / YDISTANCE 拿到的面板头左上角坐标,再叠加 XSIZE / YSIZE 算出右和下边界。实测若 header_xs 取错成图表宽度,拖拽热区会覆盖半屏,点按钮容易失灵。 一旦进入拖拽,立即用 ChartSetInteger(0, CHART_MOUSE_SCROLL, false) 关掉图表滚动,否则鼠标移动会带动行情平移,面板反而跟不上。松开后需自行复位该标志,原文未展示松开分支,但逻辑上应与 panel_dragging=false 同步。 点击 Close 按钮的分支里,deleteObjects 与 deletePanel 清场后,必须 ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, false) 注销鼠标事件,不然隐藏面板仍在吃鼠标消息,徒增 CPU 占用。

MQL5 / C++
deleteObjects();                                        class=class="str">"cmt">//--- Delete tool objects
showPanel();                                            class=class="str">"cmt">//--- Show control panel
}
else if(sparam == CLOSE_BTN) {                             class=class="str">"cmt">//--- Check if Close button clicked
  deleteObjects();                                        class=class="str">"cmt">//--- Delete tool objects
  deletePanel();                                          class=class="str">"cmt">//--- Delete control panel
  ChartSetInteger(class="num">0, CHART_EVENT_MOUSE_MOVE, false);      class=class="str">"cmt">//--- Disable mouse move events
}
ObjectSetInteger(class="num">0, sparam, OBJPROP_STATE, false);         class=class="str">"cmt">//--- Reset button state click
ChartRedraw(class="num">0);                                            class=class="str">"cmt">//--- Redraw chart
}
if(id == CHARTEVENT_MOUSE_MOVE) {                             class=class="str">"cmt">//--- Handle mouse move events
  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=class="str">"cmt">// Update button and rectangle hover states
  updateButtonHoverState(MouseD_X, MouseD_Y);
  class=class="str">"cmt">// Handle panel dragging
  class="type">int header_xd = (class="type">int)ObjectGetInteger(class="num">0, PANEL_HEADER, OBJPROP_XDISTANCE);
  class="type">int header_yd = (class="type">int)ObjectGetInteger(class="num">0, PANEL_HEADER, OBJPROP_YDISTANCE);
  class="type">int header_xs = (class="type">int)ObjectGetInteger(class="num">0, PANEL_HEADER, OBJPROP_XSIZE);
  class="type">int header_ys = (class="type">int)ObjectGetInteger(class="num">0, PANEL_HEADER, OBJPROP_YSIZE);
  if(prevMouseState == class="num">0 && MouseState == class="num">1) {               class=class="str">"cmt">//--- Mouse button down
    if(MouseD_X >= header_xd && MouseD_X <= header_xd + header_xs &&
       MouseD_Y >= header_yd && MouseD_Y <= header_yd + header_ys) {
      panel_dragging = true;                               class=class="str">"cmt">//--- Start dragging
      panel_drag_x = MouseD_X;                             class=class="str">"cmt">//--- Store mouse x-coordinate
      panel_drag_y = MouseD_Y;                             class=class="str">"cmt">//--- Store mouse y-coordinate
      panel_start_x = header_xd;                           class=class="str">"cmt">//--- Store panel x-coordinate
      panel_start_y = header_yd;                           class=class="str">"cmt">//--- Store panel y-coordinate
      ChartSetInteger(class="num">0, CHART_MOUSE_SCROLL, false);       class=class="str">"cmt">//--- Disable chart scrolling
    }
  }

「拖拽时同步刷新挂单面板所有子对象坐标」

面板拖拽的核心逻辑只在鼠标按下且处于拖动态时触发:用当前鼠标位移减去按下瞬间的锚点,得到 dx、dy,再叠加初始坐标算出新的 panel_x / panel_y。 拿到新左上角坐标后,必须逐个把背景、标题栏、关闭钮以及手数编辑框等 14 个 GUI 对象用 ObjectSetInteger 重写 XDISTANCE / YDISTANCE。注意关闭钮相对 panel_x 偏移 209 像素、挂单按钮纵向落在 panel_y+140 与 +180 两排,漏掉任一个对象就会出现「底板动了按钮没动」的脱节。 在 MT5 里把这段接进 OnChartEvent 的鼠标分支,拖动流畅度通常取决于对象数量;若你面板子对象超过 20 个,拖拽时帧率可能从 60 掉到 30 上下,可考虑只在 MouseState==1 的 tick 里批量重绘。外汇与贵金属图表加载此类自定义面板存在滑点及误触高风险,参数偏移量请先在模拟盘核对。

MQL5 / C++
if(panel_dragging && MouseState == class="num">1) {                    class=class="str">"cmt">//--- Dragging panel
   class="type">int dx = MouseD_X - panel_drag_x;                       class=class="str">"cmt">//--- Calculate x displacement
   class="type">int dy = MouseD_Y - panel_drag_y;                       class=class="str">"cmt">//--- Calculate y displacement
   panel_x = panel_start_x + dx;                           class=class="str">"cmt">//--- Update panel x-position
   panel_y = panel_start_y + dy;                           class=class="str">"cmt">//--- Update panel y-position
   class=class="str">"cmt">// Update all panel objects&class="macro">#x27; positions
   ObjectSetInteger(class="num">0, PANEL_BG, OBJPROP_XDISTANCE, panel_x);
   ObjectSetInteger(class="num">0, PANEL_BG, OBJPROP_YDISTANCE, panel_y);
   ObjectSetInteger(class="num">0, PANEL_HEADER, OBJPROP_XDISTANCE, panel_x);
   ObjectSetInteger(class="num">0, PANEL_HEADER, OBJPROP_YDISTANCE, panel_y+class="num">2);
   ObjectSetInteger(class="num">0, CLOSE_BTN, OBJPROP_XDISTANCE, panel_x + class="num">209);
   ObjectSetInteger(class="num">0, CLOSE_BTN, OBJPROP_YDISTANCE, panel_y + class="num">1);
   ObjectSetInteger(class="num">0, LOT_EDIT, OBJPROP_XDISTANCE, panel_x + class="num">70);
   ObjectSetInteger(class="num">0, LOT_EDIT, OBJPROP_YDISTANCE, panel_y + class="num">40);
   ObjectSetInteger(class="num">0, PRICE_LABEL, OBJPROP_XDISTANCE, panel_x + class="num">10);
   ObjectSetInteger(class="num">0, PRICE_LABEL, OBJPROP_YDISTANCE, panel_y + class="num">70);
   ObjectSetInteger(class="num">0, SL_LABEL, OBJPROP_XDISTANCE, panel_x + class="num">10);
   ObjectSetInteger(class="num">0, SL_LABEL, OBJPROP_YDISTANCE, panel_y + class="num">95);
   ObjectSetInteger(class="num">0, TP_LABEL, OBJPROP_XDISTANCE, panel_x + class="num">130);
   ObjectSetInteger(class="num">0, TP_LABEL, OBJPROP_YDISTANCE, panel_y + class="num">95);
   ObjectSetInteger(class="num">0, BUY_STOP_BTN, OBJPROP_XDISTANCE, panel_x + class="num">10);
   ObjectSetInteger(class="num">0, BUY_STOP_BTN, OBJPROP_YDISTANCE, panel_y + class="num">140);
   ObjectSetInteger(class="num">0, SELL_STOP_BTN, OBJPROP_XDISTANCE, panel_x + class="num">130);
   ObjectSetInteger(class="num">0, SELL_STOP_BTN, OBJPROP_YDISTANCE, panel_y + class="num">140);
   ObjectSetInteger(class="num">0, BUY_LIMIT_BTN, OBJPROP_XDISTANCE, panel_x + class="num">10);
   ObjectSetInteger(class="num">0, BUY_LIMIT_BTN, OBJPROP_YDISTANCE, panel_y + class="num">180);
   ObjectSetInteger(class="num">0, SELL_LIMIT_BTN, OBJPROP_XDISTANCE, panel_x + class="num">130);
   ObjectSetInteger(class="num">0, SELL_LIMIT_BTN, OBJPROP_YDISTANCE, panel_y + class="num">180);

常见问题

矩形颜色是跟随输入校验状态切换的,校验失败偏红、通过偏绿。想改色调直接调对应对象的画笔颜色常量即可。
多数情况是没在鼠标移动事件里重绘按钮背景。需要在 OnChartEvent 的鼠标移动分支里判定坐标命中并重设画刷。
可以,小布能基于图表的鼠标坐标与面板对象布局做实时识别,直接在品种页标出悬停与可点击区域,省去自己写坐标判定。
在拖拽的鼠标移动回调里统一重算面板原点,再遍历子对象按偏移量重设坐标并重绘,避免只移动容器不刷子项。
要在按钮点击事件里读取面板当前输入值,校验通过后调用下单接口并刷新反馈,颜色变化只是视觉层不能替代逻辑层。