在 MQL5 中创建交互式图形用户界面(第 2 部分):添加控制和响应(基础篇)
📘

在 MQL5 中创建交互式图形用户界面(第 2 部分):添加控制和响应(基础篇)

第 1/3 篇

正文

在 MQL5 中创建交互式图形用户界面(第 2 部分):添加控制和响应 MetaTrader 5 — 交易 | 19 二月 2025, 09:08

  • 171

5 Allan Munene Mutiiria

◍ 从定格快照到可响应面板

上一节里我们搭出的 GUI 面板只是图形元素的静态组合——一次定格在时间里的快照,不响应任何鼠标或价格事件。这种迭代版本在 MT5 里跑起来就是一块死板贴图,用户点上去没反馈,行情变了它也不动。 本篇要做的第一件事就是解冻这张快照。核心思路是改用相对定位:组件不再写死像素坐标,而是按父容器比例排布,这样面板缩放或重排时元素能自己找位置。配合可点击、可编辑控件,界面才真正具备响应性。 实时数据是交易面板的命门。我们会直接拉 SymbolInfoTick 拿买卖价,用 OnTick 驱动面板重绘,确保报价延迟控制在一次 tick 内(通常 < 100ms,取决于经纪商推送频率)。 另一块是组件移动性:让特定控件能被鼠标拖拽。底层靠捕获 CHARTEVENT_OBJECT_DRAG 事件,重写坐标绑定逻辑,用户体验会从「看板」变成「操作台」。外汇与贵金属波动剧烈,这类实时面板仅作辅助,实盘决策仍须自担高风险。

「七处交互组件的自动化落点」

面板要做自动化的地方一共七个,先理清边界再写代码才不会乱。第一处是关闭按钮:单击后必须销毁全部面板子元素,不能留残留对象。第二处是仓位管理按钮,点“盈利”标签就平掉所有浮盈持仓,点其他标签对应平掉指定类订单。 第三处是交易量组件,单击实体弹出下拉,让用户选手数档位而不是盲填。第四处是交易按钮旁的增加/减少钮,只改编辑框数值、不接收键盘;第五处则是编辑框本身要能捕获直接键入的值,这两处互补。 第六处悬停效果:鼠标进按钮区域时控件放大,离开即复位默认尺寸,纯视觉反馈。第七处是每个分时 tick 把报价刷成实时值,否则面板数字会滞后。 外汇与贵金属杠杆高,面板只是执行界面,平仓逻辑出错会瞬间放大回撤概率,实盘前务必在 MT5 策略测试器用模拟品种走一遍这七步。

用 OnTick 与 OnChartEvent 把面板跑起来

MT5 的报价驱动靠 OnTick,它是 void 型内置函数,报价每次跳动都会被调用,不返回任何值。价格刷新逻辑全塞在这里:用 ObjectSetString 改按钮标签文本,窗口 ID 填 0 或 ChartID(),目标对象名如 LABEL_SELL_PRICE,属性用 OBJPROP_TEXT。 bid 是 double,标签要 string,直接强转会在编译期报“从数字到字符串的隐式转换”警告。更坑的是精度:XAUUSD 报价 11.77900 强转后变成 11.779,末尾两个零被吞,视觉上从 5 位变 3 位。用 DoubleToString(bid, _Digits) 才稳,_Digits 自动取当前品种小数位(黄金是 3,省略则默认 8)。点差是 int,直接转串无精度损失,也可用 IntegerToString。 [CODE] void OnTick() { ObjectSetString(0, "LABEL_SELL_PRICE", OBJPROP_TEXT, DoubleToString(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits)); ObjectSetString(0, "LABEL_BUY_PRICE", OBJPROP_TEXT, DoubleToString(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits)); ObjectSetString(0, "LABEL_SPREAD", OBJPROP_TEXT, (string)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD)); } [/CODE] 用户交互交给 OnChartEvent,它捕获鼠标、按键、对象点击等 11 类事件。参数 id 是事件枚举,lparam 长整型(如按键码),dparam 双精度(如鼠标位),sparam 字符串(如对象名)。对象点击时 id 为 CHARTEVENT_OBJECT_CLICK,用 if 比对后从 sparam 拿对象名做分支。 平仓按钮逻辑:先 ObjectGetInteger 存 originalColor,ObjectSetInteger 改红,再用 PositionsTotal 配合 PositionGetTicket 遍历平仓——注意只平当前品种,AUDUSD 仓位不会被 EA 关联的 XAUUSD 碰掉。挂单用 OrdersTotal 同样迭代删。平仓后 ObjectSetInteger 还原颜色。 [CODE] #include <Trade/Trade.mqh> CTrade obj_Trade; // ... if(id == CHARTEVENT_OBJECT_CLICK && sparam == "BTN_CLOSE") { long originalColor = ObjectGetInteger(0, "BTN_CLOSE", OBJPROP_COLOR); ObjectSetInteger(0, "BTN_CLOSE", OBJPROP_COLOR, clrRed); for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionSelect(ticket) && PositionGetString(POSITION_SYMBOL) == _Symbol) obj_Trade.PositionClose(ticket); } ObjectSetInteger(0, "BTN_CLOSE", OBJPROP_COLOR, originalColor); } [/CODE] 盈利单平仓只多一层判断:profit_or_loss = PositionGetDouble(POSITION_PROFIT) 大于 0 才平。交易量加减按钮用 trade_lots += 0.01 改编辑框,DoubleToString 精度写 2。每次加完逻辑就编译跑一遍,确认面板响应再叠下一段,外汇贵金属杠杆高,误平或漏平都可能放大亏损。

MQL5 / C++
class="type">void OnTick()
  {
   ObjectSetString(class="num">0, "LABEL_SELL_PRICE", OBJPROP_TEXT, DoubleToString(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits));
   ObjectSetString(class="num">0, "LABEL_BUY_PRICE", OBJPROP_TEXT, DoubleToString(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits));
   ObjectSetString(class="num">0, "LABEL_SPREAD", OBJPROP_TEXT, (class="type">class="kw">string)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD));
  }

class="macro">#include <Trade/Trade.mqh>
CTrade obj_Trade;
class=class="str">"cmt">// ...
if(id == CHARTEVENT_OBJECT_CLICK && sparam == "BTN_CLOSE")
  {
   class="type">long originalColor = ObjectGetInteger(class="num">0, "BTN_CLOSE", OBJPROP_COLOR);
   ObjectSetInteger(class="num">0, "BTN_CLOSE", OBJPROP_COLOR, clrRed);
   for(class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--)
     {
      class="type">class="kw">ulong ticket = PositionGetTicket(i);
      if(ticket > class="num">0 && PositionSelect(ticket) && PositionGetString(POSITION_SYMBOL) == _Symbol)
         obj_Trade.PositionClose(ticket);
     }
   ObjectSetInteger(class="num">0, "BTN_CLOSE", OBJPROP_COLOR, originalColor);
  }

◍ 让面板跟手:悬停高亮与拖拽联动的实现细节

启用鼠标追踪后,MT5 会通过 CHARTEVENT_MOUSE_MOVE 把坐标和状态推给 EA。先用 ChartGetInteger 取鼠标 X/Y 与 state,再拿 ObjectGetInteger 读按钮的 XDistance、YDistance、XSize、YSize,类型转成 int 备用。 悬停判定是四边界交集:mouse_X 在 [左边界, 左边界+宽] 内且 mouse_Y 在 [上边界, 上边界+高] 内才置 isMouseInside=true。用静态变量 prevMouseInside 比对状态跳变,只在「刚进」或「刚出」时改标签颜色并 ChartRedraw,避免每帧重画。实测编译后按钮移入即反白、移出复原。 拖拽则另设静态 movingState。左键从未按到按下(prevMouseClickState==false 且 mouse_State==1)且坐标落在拖拽图标四边界内时,记录初始鼠标点与对象距离,movingState=true;移动中用 ChartSetInteger 关掉图表横向滚动,按当前 mouse_X/Y 重算距离并刷新。 外汇与贵金属面板交互改造属高风险操作,参数误写可能导致下单异常。鼠标松开(mouse_State==0)必须把 movingState 复位、重开滚动,否则组件会「粘」在光标上乱跑。把其他面板元素套同一套距离增量逻辑后,编译可见全部组件随拖拽图标同步位移。

「把卖价买价点差刷进图表标签」

在 MT5 的 EA 里,OnTick 是价格每跳驱动的主循环。想把当前报价实时显示到自建的图表对象上,核心就是每次 tick 用 ObjectSetString 重写标签文本。 下面这段是可直接拷进 EA 的报价刷新片段,注意 Spread() 返回的是整数点差,必须用 IntegerToString 转文本,否则编译报错: void OnTick(){ // --- Update price quotes --- // 卖价标签写当前 Bid,_Digits 保证小数位跟随品种 ObjectSetString(0, LABEL_SELL_PRICE, OBJPROP_TEXT, DoubleToString(Bid(), _Digits)); // 买价标签写当前 Ask ObjectSetString(0, LABEL_BUY_PRICE, OBJPROP_TEXT, DoubleToString(Ask(), _Digits)); // 点差按钮写整数点差文本 ObjectSetString(0, BTN_SPREAD, OBJPROP_TEXT, IntegerToString(Spread())); } 常见坑是把 Bid() 直接塞进 ObjectSetString 而不转字符串,早期写法如 ObjectSetString(0, LABEL_SELL_PRICE, OBJPROP_TEXT, Bid()) 在严格编译下通不过;用 (string)Bid() 强制转换能跑,但会丢掉 _Digits 精度,EURUSD 可能显示 1.0945 而非 1.09452。 外汇和贵金属杠杆高、点差随流动性跳变,实盘前先在策略测试器用不同品种验证标签刷新频率与视觉遮挡,避免对象挡住 K 线。

MQL5 / C++
class="type">void OnTick(){
   class=class="str">"cmt">// --- Update price quotes ---
   class=class="str">"cmt">// Set the text of the "SELL PRICE" label to the current Bid price
   ObjectSetString(class="num">0, LABEL_SELL_PRICE, OBJPROP_TEXT, DoubleToString(Bid(), _Digits));
   class=class="str">"cmt">// Set the text of the "BUY PRICE" label to the current Ask price
   ObjectSetString(class="num">0, LABEL_BUY_PRICE, OBJPROP_TEXT, DoubleToString(Ask(), _Digits));
   class=class="str">"cmt">// Set the text of the "SPREAD" button to the current spread value
   ObjectSetString(class="num">0, BTN_SPREAD, OBJPROP_TEXT, IntegerToString(Spread()));
}

常见问题

先放标签显示买卖价和点差,再加按钮和编辑框,用图表事件把点击映射到交易函数即可。
在行情跳动函数里取卖价买价算点差,只更新标签文本,避免重画整个面板。
小布可扫描当前品种页的面板控件状态,提示哪个按钮没绑事件或标签没刷新。
在图表事件里判鼠标坐标命中控件就改背景色,命中标题区就记录偏移量随鼠标移动重绘。
给点差标签设阈值,超过就用红字显示,配合行情函数实时比对就行。