利用 MQL5 的交互式 GUI 改进您的交易图表(第 III 部分):简易可移动交易 GUI(基础篇)
◍ 在 MT5 图表上拖出可移动交易面板
MT5 自带的订单面板固定贴在图表一角,实战中常挡住关键 K 线。用 MQL5 的 CWndContainer 与鼠标事件,可以写一个随拖随走的小型交易 GUI,把开仓、平仓按钮塞进一个可悬浮窗口。 核心思路是捕获 ChartEvent 里的鼠标左键按下与移动消息,用鼠标位移实时改写窗口对象的 X、Y 坐标。下面这段是最小可运行骨架,已在 MT5 实测可拖动,不涉及任何自动下单逻辑。 外汇与贵金属保证金交易杠杆高,拖窗口只是改善操作体验,不等于降低风险,动手改参数前先开模拟盘验证。
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 手回验点击响应。
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 小时图,能直观看到左上角出现一个可控矩形区域,方便后续叠价格行为提示层。外汇与贵金属波动剧烈,此类图形辅助不改变头寸风险,仅作视觉参考。
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) 做自适应。
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