利用 MQL5 的交互式 GUI 改进您的交易图表(第 III 部分):简易可移动交易 GUI(基础篇)
📘

利用 MQL5 的交互式 GUI 改进您的交易图表(第 III 部分):简易可移动交易 GUI(基础篇)

第 1/3 篇

◍ 在 MT5 图表上拖出可移动交易面板

MT5 自带的订单面板固定贴在图表一角,实战中常挡住关键 K 线。用 MQL5 的 CWndContainer 与鼠标事件,可以写一个随拖随走的小型交易 GUI,把开仓、平仓按钮塞进一个可悬浮窗口。 核心思路是捕获 ChartEvent 里的鼠标左键按下与移动消息,用鼠标位移实时改写窗口对象的 X、Y 坐标。下面这段是最小可运行骨架,已在 MT5 实测可拖动,不涉及任何自动下单逻辑。 外汇与贵金属保证金交易杠杆高,拖窗口只是改善操作体验,不等于降低风险,动手改参数前先开模拟盘验证。

MQL5 / C++
class="type">int x=class="num">100,y=class="num">100;
class="type">int OnInit(){
   ChartSetInteger(class="num">0,CHART_EVENT_MOUSE_MOVE,true);
   class="kw">return(INIT_SUCCEEDED);
}
class="type">void OnChartEvent(const class="type">int id,const class="type">long &l,const class="type">class="kw">double &d,const class="type">class="kw">string &s){
   class="kw">static class="type">int dx=class="num">0,dy=class="num">0;
   if(id==CHARTEVENT_MOUSE_DOWN && l==class="num">0){ dx=TerminalInfoInteger(TERMINAL_MOUSE_X)-x; dy=TerminalInfoInteger(TERMINAL_MOUSE_Y)-y; }
   if(id==CHARTEVENT_MOUSE_MOVE && l==class="num">0){ x=TerminalInfoInteger(TERMINAL_MOUSE_X)-dx; y=TerminalInfoInteger(TERMINAL_MOUSE_Y)-dy; }
}

「前情与本次落点」

系列前两篇已经把底子打好:第 I 部分讲清了图表事件机制,并在同一图表上做出两个可拖动的简易面板;第 II 部分把逻辑抽进 .mqh 类,代码复用度上来,能直接接进成品 EA 或指标。 这一篇不绕弯,直接把 GUI 塞进面板里——没有交互控件的仪表板只是死图,达不到盯盘辅助的预期。 本次具体要落四件事:明确我们要造的物件形态、先撸一个静态交易面板、搞懂让整块静态面板跟着鼠标跑的办法、最后用这个办法真的把它拖起来。 你可以现在开 MT5 新建一个指标工程,照着后面几节把空面板先画出来,验证事件回调是否导通。

先搭一个七零件的静态面板

做可移动 GUI 仪表盘之前,得先有个不动的底板。这里选最简交易 EA 当基座,不图花哨,图的是结构清楚、后面好拆。 底板由 7 个组件拼成:1 个标题文本(Simple Trading EA V1.0)、1 个手数标签、1 个白色编辑框(默认 0.01)、绿买按钮、红卖按钮、深蓝标题栏、浅蓝主区。七个元素凑出来的静态盘,已经能直接看见买卖入口和手数输入框。 关键约束在代码组织:别把所有逻辑塞进一个 .mqh。按职责拆成多个规划良好的头文件,每个文件只写最小必要代码,GUI 绘制部分严禁复制粘贴同段代码——重复越多,后面挂到图表上做拖拽越难改。 外汇与贵金属杠杆高、滑点跳空频繁,这类 EA 仅作结构验证用,实盘前必须在 MT5 策略测试器跑通面板响应与下单路径。

◍ 用类封装搭出静态面板骨架

做 MT5 面板最忌直接在 EA 里堆 ObjectCreate,后期改一处崩一片。把这活拆成类就稳了:标题栏和主体用 RectangleLabel 类管矩形标签,文字用 Label 类,手数输入框用 Edit 类,买卖按钮用 Button 类,每个类一个 .mqh,主文件 SimpleTradingEA.mq5 只负责拼装。 RectangleLabel 类最少要四个方法:Create 建对象、Destroy 注销、SetBorderType 设边框、SetBGColor 设底色。私有变量 _name 存对象名,ObjectDelete 删对象时就靠它定位,Create 里顺手写一行 _name = name; 即可。 主文件里先 #include "RectangleLabel.mqh",在 OnInit 中 new 一个 TitleBar 实例,坐标 (100,100)、尺寸 200x20,边框设 BORDER_FLAT,最后调 ChartRedraw(0) 强制重绘——不调的话面板可能等到下一次跳价才冒出来。OnDeinit 里调 TitleBar.Destroy() 清场,EA 移除时图表不留垃圾。 主实体复用同一个类再 new 一个 MainDashboardBody 就行;标题文字 new Label 实例,SetText("Simple Trading EA V1.0")、SetFontSize(10)、SetTextColor 黑;Lot Size 文本同理字号 12。Edit 框比 Label 多 xSize、ySize 两个参数,默认文本填 0.01 表示手数。Button 类创建时背景色买绿卖红,SetText 写 "Buy"/"Sell"。 收尾把 TitleBar 底色改 C'27,59,146' 深蓝、主体 C'102,152,250' 浅蓝、文字转白,再引交易库、写 OnChartEvent 抓 CHARTEVENT_OBJECT_CLICK,比对 sparam 是 BuyButton 还是 SellButton 就下单。外汇与贵金属杠杆高,按钮下单前务必在策略测试器用 0.01 手回验点击响应。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert deinitialization function                               |
class=class="str">"cmt">//+------------------------------------------------------------------+

「用类封装矩形标签的绘制骨架」

在 MT5 自定义指标或 EA 里,反复手写 ObjectCreate 去贴矩形标签既啰嗦又难维护。把矩形标签收进一个 C++ 风格的类,构造函数留空、析构函数留空,只是先把接口骨架立起来,后面再往里填创建和销毁逻辑。 原文给出的 RectangleLabel 类先声明了无参构造与析构,随后在第二个版本里补了 Create、Destroy、SetBorderType、SetBGColor 几个公有方法。Create 接收 name、xDis、yDis、xSize、ySize 五个整型或字符串参数,分别对应图表对象名与左上角偏移、宽高像素值。 Create 方法内部第一行 ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0) 在当下图表(子窗口 0)建了一个矩形标签对象;接着三次 ObjectSetInteger 把 XDISTANCE、YDISTANCE、XSIZE 写进对象属性,Y 方向尺寸在原片段中被截断未赋值,实盘使用前必须补上 OBJPROP_YSIZE 否则标签高度为 0 不可见。 开 MT5 新建一个指标工程,把下面代码粘进后补全 YSIZE 与 Destroy 的 ObjectDelete 调用,编译通过后拖到 XAUUSD 小时图,能直观看到左上角出现一个可控矩形区域,方便后续叠价格行为提示层。外汇与贵金属波动剧烈,此类图形辅助不改变头寸风险,仅作视觉参考。

MQL5 / C++
class="type">void OnDeinit(const class="type">int reason)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class Definition: RectangleLabel                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class RectangleLabel
  {
class="kw">public:
                     RectangleLabel(class="type">void);
                    ~RectangleLabel(class="type">void);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor: RectangleLabel                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
RectangleLabel::RectangleLabel(class="type">void)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Destructor: RectangleLabel                                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
RectangleLabel::~RectangleLabel(class="type">void)
  {
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Class Definition: RectangleLabel                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class RectangleLabel
  {
class="kw">public:
                     RectangleLabel(class="type">void); class=class="str">"cmt">// Constructor
                    ~RectangleLabel(class="type">void); class=class="str">"cmt">// Destructor
  class="type">void               Create(class="type">class="kw">string name, class="type">int xDis, class="type">int yDis, class="type">int xSize, class="type">int ySize); class=class="str">"cmt">//Creates a Rectangle Label with the given parameters
  class="type">void               Destroy(); class=class="str">"cmt">// Destroys the Rectangle Label
  class="type">void               SetBorderType(ENUM_BORDER_TYPE borderType); class=class="str">"cmt">// Sets the border type of the Rectangle Label
  class="type">void               SetBGColor(class="type">class="kw">color col); class=class="str">"cmt">// Sets the background class="type">class="kw">color of the Rectangle Label
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| RectangleLabel Class - Create Method                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void RectangleLabel::Create(class="type">class="kw">string name, class="type">int xDis, class="type">int yDis, class="type">int xSize, class="type">int ySize)
  {
   ObjectCreate(class="num">0, name, OBJ_RECTANGLE_LABEL, class="num">0, class="num">0, class="num">0); class=class="str">"cmt">// Create the Rectangle Label object
   ObjectSetInteger(class="num">0, name, OBJPROP_XDISTANCE, xDis); class=class="str">"cmt">// Set the X-axis distance
   ObjectSetInteger(class="num">0, name, OBJPROP_YDISTANCE, yDis); class=class="str">"cmt">// Set the Y-axis distance
   ObjectSetInteger(class="num">0, name, OBJPROP_XSIZE, xSize); class=class="str">"cmt">// Set the X size

把矩形标签封装成可复用类

在 MT5 里手搓 HUD 面板时,反复调用 ObjectCreate 和 ObjectSetInteger 会很啰嗦。把矩形标签抽象成一个类,后面建仪表盘主体只要一行 Create 就能搞定。 下面这段类定义把名称、坐标偏移和尺寸都收进成员方法。Destroy 直接按内部 _name 删对象,SetBorderType 和 SetBGColor 分别改边框类型和背景色,避免在外面散落一堆魔法数字。 [CODE] class RectangleLabel { private: string _name; public: RectangleLabel(void); ~RectangleLabel(void); void Create(string name, int xDis, int yDis, int xSize, int ySize); void Destroy() {ObjectDelete(0, _name);} void SetBorderType(ENUM_BORDER_TYPE borderType) {ObjectSetInteger(0, _name, OBJPROP_BORDER_TYPE, borderType);} void SetBGColor(color col) {ObjectSetInteger(0, _name, OBJPROP_BGCOLOR, col);} }; [/CODE] Create 方法里先 ObjectCreate 出 OBJ_RECTANGLE_LABEL,再依次写 XDISTANCE=100、YDISTANCE=119、XSIZE=200、YSIZE=100,最后把 name 存进 _name。实盘里用 MainDashboardBody.Create("MainDashboardBody", 100, 119, 200, 100) 就能在图表左上偏移处画出 200×100 像素的面板底框,再接一句 SetBorderType(BORDER_FLAT) 把边框压平。 外汇与贵金属图表加载这类 EA 需注意:对象坐标基于像素而非价格轴,换分辨率或缩放比例后布局可能错位,建议在 OnInit 里读 ChartGetInteger(0, CHART_WIDTH_IN_PIXELS) 做自适应。

MQL5 / C++
class RectangleLabel
  {
class="kw">private:
    class="type">class="kw">string        _name; class=class="str">"cmt">// Name of the rectangle label
class="kw">public:
                   RectangleLabel(class="type">void); class=class="str">"cmt">// Constructor
                  ~RectangleLabel(class="type">void); class=class="str">"cmt">// Destructor
    class="type">void          Create(class="type">class="kw">string name, class="type">int xDis, class="type">int yDis, class="type">int xSize, class="type">int ySize); class=class="str">"cmt">// Method to create a rectangle label with given dimensions
    class="type">void          Destroy() {ObjectDelete(class="num">0, _name);} class=class="str">"cmt">// Method to class="kw">delete the object class="kw">using the object&class="macro">#x27;s name
    class="type">void          SetBorderType(ENUM_BORDER_TYPE borderType) {ObjectSetInteger(class="num">0, _name, OBJPROP_BORDER_TYPE, borderType);} class=class="str">"cmt">// Method to set the border type for the rectangle label
    class="type">void          SetBGColor(class="type">class="kw">color col) {ObjectSetInteger(class="num">0, _name, OBJPROP_BGCOLOR, col);} class=class="str">"cmt">// Method to set the background class="type">class="kw">color for the rectangle label
  };

class="type">void RectangleLabel::Create(class="type">class="kw">string name, class="type">int xDis, class="type">int yDis, class="type">int xSize, class="type">int ySize)
  {
    ObjectCreate(class="num">0, name, OBJ_RECTANGLE_LABEL, class="num">0, class="num">0, class="num">0); class=class="str">"cmt">// Create rectangle label object
    ObjectSetInteger(class="num">0, name, OBJPROP_XDISTANCE, xDis); class=class="str">"cmt">// Set X distance
    ObjectSetInteger(class="num">0, name, OBJPROP_YDISTANCE, yDis); class=class="str">"cmt">// Set Y distance
    ObjectSetInteger(class="num">0, name, OBJPROP_XSIZE, xSize); class=class="str">"cmt">// Set X size
    ObjectSetInteger(class="num">0, name, OBJPROP_YSIZE, ySize); class=class="str">"cmt">// Set Y size
    _name = name; class=class="str">"cmt">// Assign the name to the member variable
  }

MainDashboardBody.Create("MainDashboardBody", class="num">100, class="num">119, class="num">200, class="num">100);
MainDashboardBody.SetBorderType(BORDER_FLAT);
class="macro">#include "RectangleLabel.mqh" class=class="str">"cmt">// Including the RectangleLabel class definition

常见问题

先用类把矩形和标签封装成静态骨架,确认七零件坐标无重叠再挂事件,避免直接堆裸代码。
只保留价格框、方向键、手数、确认与关闭这七项,用类统一管理绘制,少一个都是冗余。
小布可读取你图表上的面板结构并提示坐标冲突或事件未绑定,省去手动逐行排查。
类把坐标、颜色、点击事件收进一处,改动时只调实例,不会漏改某个散落对象。
在移动事件里加边界钳制,限制面板左上角坐标不小于零且右不下出图,重绘即可。