6.5 图表对象与交易面板

第 42 节 / 共 43 节 练习题 2 组 课程:MQL4 教程与 MT4 EA 编程
图表对象是画在行情图上的“控件”:文字、矩形、按钮、趋势线、箭头。交易面板就是一组对象:背景矩形、标题文字、几个按钮、状态标签。对象本身不产生交易,点击事件要经过 OnChartEvent 才能进入你的代码。

对象的生命周期

  1. 创建:ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0)。0 表示当前图表,name 必须唯一。
  2. 设置:ObjectSetInteger 设置坐标、大小、颜色、层级;ObjectSetString 设置文字。
  3. 刷新:ChartRedraw() 立即重绘。不调用也能在下一次刷新时显示,但面板操作最好立即反馈。
  4. 删除:OnDeinit 里 ObjectsDeleteAll(0, 前缀),或 ObjectDelete 逐个删除。

对象名建议统一加前缀,例如 pnl_。删除时按前缀批量清理,不会误删其他指标画的线。重名对象重复创建会失败,所以 OnInit 里可以先 ObjectDelete 再创建,或直接用唯一名字。

坐标与角落

图表对象的坐标有两种锚定方式:按价格/时间锚定(会随图表缩放移动),或按角落像素锚定(面板常用)。OBJPROP_CORNER 可选左上、右上、左下、右下;再配合 OBJPROP_XDISTANCE、OBJPROP_YDISTANCE、OBJPROP_XSIZE、OBJPROP_YSIZE。面板放右上角时,x 从右边缘往左算,y 从上边缘往下算。

按钮还要设置 OBJPROP_SELECTABLE=false,否则用户拖动图表时会把按钮拖走。OBJPROP_HIDDEN=true 可以让对象不出现在对象列表里,界面更干净。

点击事件:OnChartEvent

点击按钮不会进入 OnTick,而是进入 OnChartEvent,id 为 CHARTEVENT_OBJECT_CLICK,sparam 是被点击对象的名字。处理顺序是:先判断 id,再判断 sparam,再执行动作,最后把 OBJPROP_STATE 设回 false。按钮的“按下”状态不会自动复位,忘记复位会一直显示凹陷。

完整示例:三键面板

下面的代码创建背景、三个按钮,并在 OnChartEvent 里处理买入、卖出、全平。全平部分留了注释,调用 6.2 的 CloseAllByMagic 即可。

#property strict
#define PANEL_PREFIX "pnl_"
 
void CreateButton(string name, string text, int x, int y, color bg) {
    string obj = PANEL_PREFIX + name;
    ObjectCreate(0, obj, OBJ_BUTTON, 0, 0, 0);
    ObjectSetInteger(0, obj, OBJPROP_CORNER, CORNER_RIGHT_UPPER);
    ObjectSetInteger(0, obj, OBJPROP_XDISTANCE, x);
    ObjectSetInteger(0, obj, OBJPROP_YDISTANCE, y);
    ObjectSetInteger(0, obj, OBJPROP_XSIZE, 80);
    ObjectSetInteger(0, obj, OBJPROP_YSIZE, 26);
    ObjectSetInteger(0, obj, OBJPROP_BGCOLOR, bg);
    ObjectSetInteger(0, obj, OBJPROP_SELECTABLE, false);
    ObjectSetString(0, obj, OBJPROP_TEXT, text);
}
 
int OnInit() {
    ObjectCreate(0, PANEL_PREFIX + "bg", OBJ_RECTANGLE_LABEL, 0, 0, 0);
    ObjectSetInteger(0, PANEL_PREFIX + "bg", OBJPROP_CORNER, CORNER_RIGHT_UPPER);
    ObjectSetInteger(0, PANEL_PREFIX + "bg", OBJPROP_XDISTANCE, 10);
    ObjectSetInteger(0, PANEL_PREFIX + "bg", OBJPROP_YDISTANCE, 30);
    ObjectSetInteger(0, PANEL_PREFIX + "bg", OBJPROP_XSIZE, 180);
    ObjectSetInteger(0, PANEL_PREFIX + "bg", OBJPROP_YSIZE, 130);
    ObjectSetInteger(0, PANEL_PREFIX + "bg", OBJPROP_BGCOLOR, clrWhiteSmoke);
    ObjectSetInteger(0, PANEL_PREFIX + "bg", OBJPROP_BACK, false);
 
    CreateButton("btnBuy", "买入", 80, 45, clrPaleGreen);
    CreateButton("btnSell", "卖出", 80, 80, clrMistyRose);
    CreateButton("btnClose","全平", 80, 115, clrGainsboro);
    ChartRedraw();
    return INIT_SUCCEEDED;
}
 
void OnChartEvent(const int id, const long &lparam,
const double &dparam, const string &sparam) {
    if(id != CHARTEVENT_OBJECT_CLICK) return;
 
    if(sparam == PANEL_PREFIX + "btnBuy") {
        ObjectSetInteger(0, sparam, OBJPROP_STATE, false);
        if(IsTradeAllowed())
            OrderSend(Symbol(), OP_BUY, 0.10, Ask, 3, 0, 0,
            "panel", 20261102, 0, clrBlue);
        ChartRedraw();
    }
    else if(sparam == PANEL_PREFIX + "btnSell") {
        ObjectSetInteger(0, sparam, OBJPROP_STATE, false);
        if(IsTradeAllowed())
            OrderSend(Symbol(), OP_SELL, 0.10, Bid, 3, 0, 0,
            "panel", 20261102, 0, clrRed);
        ChartRedraw();
    }
    else if(sparam == PANEL_PREFIX + "btnClose") {
        ObjectSetInteger(0, sparam, OBJPROP_STATE, false);
        // 这里调用 6.2 的 CloseAllByMagic(20261102)
    }
}
 
void OnDeinit(const int reason) {
    ObjectsDeleteAll(0, PANEL_PREFIX);
    ChartRedraw();
}

注意:面板只负责“收集意图”,真正的风控、手数计算、错误处理应该放在统一的下单函数里。否则按钮越多,重复代码越多。

刷新频率

面板上的余额、浮盈、状态文字不需要每个 Tick 都更新。可以在 OnTimer 里每秒刷新一次,减少 ChartRedraw 次数。频繁重绘会让图表在低配机器上明显卡顿。状态没变时跳过刷新,是一个简单有效的优化。

常见错误

  • OnDeinit 不删对象:切换参数后旧按钮残留,和新按钮重名,点击无反应。
  • 按钮状态不复位:点击后一直显示按下,用户以为没生效。
  • 对象名重复:多个图表用同一个名字,后创建的失败。名字加图表 ID 或前缀。
  • 用价格坐标画面板:图表一缩放,面板就跑到屏幕外。面板用角落像素坐标。
  • 在 OnChartEvent 里做重活:事件回调应快速返回,复杂计算放到 OnTimer 或 OnTick。

小结

面板 = 对象 + 事件 + 清理。创建时统一命名,设置时用角落坐标,点击时判断 sparam,退出时按前缀删除。把这四步做成模板,你就能给任何 EA 加一个不添乱的交易面板。

常用对象类型

  • OBJ_LABEL:单行文字,适合标题、状态、数值。用 OBJPROP_TEXT 改内容,用 OBJPROP_FONTSIZE 改字号。
  • OBJ_BUTTON:可点击按钮,点击时触发 CHARTEVENT_OBJECT_CLICK。适合买入、卖出、全平、切换参数。
  • OBJ_RECTANGLE_LABEL:矩形面板背景,支持背景色、边框色、圆角(部分版本)。适合给文字和按钮做底板。
  • OBJ_EDIT:输入框,用户可编辑,适合手数、止损点数等参数。读取时用 ObjectGetString(..., OBJPROP_TEXT)。

对象类型决定了可用属性。给 OBJ_LABEL 设置 OBJPROP_XSIZE 通常无效,给 OBJ_BUTTON 设置 OBJPROP_FONTSIZE 则可能生效。写代码前先查清类型支持的属性,比反复试错快。

事件参数怎么用

OnChartEvent 有四个参数:id 是事件类型,lparam 是长整数参数,dparam 是浮点参数,sparam 是字符串参数。对象点击事件里,sparam 是对象名;图表拖拽、键盘事件里,参数含义不同。不要把所有事件都当成点击处理,先判断 id,再读对应参数。

按钮点击后要复位 OBJPROP_STATE,否则按钮保持按下状态。如果按钮是“切换”语义(例如开启/关闭某种模式),可以用 state 表示开关,但要在状态文字里明确告诉用户当前是开还是关。

面板状态管理

面板上的余额、状态、按钮可用性都来自 EA 的内部状态。不要让面板成为“唯一状态源”:按钮点击只触发动作,动作的结果写回全局变量,再由刷新函数统一更新面板。这样即使面板对象被删除重建,状态也不会丢。

刷新函数应该幂等:同样的状态重复调用,结果一致;状态没变时跳过 ChartRedraw,减少闪烁。面板文字变化频繁时,可以只更新变化的那个对象,而不是删除重建整个面板。

坐标与高 DPI

MT4 在不同显示器缩放下,图表像素坐标可能变化。用角落锚定 + XDISTANCE/YDISTANCE 比用价格坐标稳定;但高 DPI 下按钮可能显得偏小。可以在 OnInit 里根据 `TerminalInfoInteger(TERMINAL_SCREEN_DPI)` 调整尺寸(部分版本支持),或者干脆把按钮做大一点,保证可点击。

面板不要遮挡关键价格区域。右上角是常用位置,但如果图表右上角有均线标签,可以改到左上角或右下角。上线前在 100%、125%、150% 三种缩放下各看一遍。

面板不是下单入口

按钮点击只是“用户意图”,不是“交易指令”。真正的下单必须经过统一的风控与执行函数:检查交易权限、点差、手数、止损距离、最大持仓、当日亏损,再决定发不发。把 OrderSend 直接写在 OnChartEvent 里,短期看很方便,长期会导致“面板能下单、策略不能下单、两套逻辑不一致”。

面板还应显示“为什么不能下单”。例如交易被禁用时,状态栏显示“交易不允许”,而不是点了没反应。用户看到原因,才不会反复点击。

对象命名与清理

对象名在图表内必须唯一。建议用“EA 前缀 + 模块 + 名称”,例如 `pnl_bg`、`pnl_btnBuy`、`pnl_lblStatus`。清理时按前缀 ObjectsDeleteAll(0, "pnl_"),不会误删其他指标的趋势线。若同一图表挂多个 EA,前缀里再加魔术号,避免互相覆盖。

OnDeinit 的 reason 参数可以区分卸载原因:REASON_REMOVE、REASON_RECOMPILE、REASON_CHARTCHANGE、REASON_PARAMETERS 等。参数改变会触发卸载重载,清理逻辑必须能重复执行,不能假设对象只创建一次。

可访问性与可读性

按钮文字要短、对比度要高,深色背景配浅色文字或反过来。面板不要用大面积高饱和色,容易遮挡行情。状态文字用“已买入 0.10 手”而不是“OK”,让用户一眼知道发生了什么。重要操作(全平)可以加二次确认:第一次点击把按钮文字改成“确认全平”,第二次点击才执行。

移动端或小屏幕不在 MT4 桌面端的考虑范围内,但高 DPI 和窄图表窗口要考虑。按钮宽度至少 70 像素,高度至少 24 像素;面板总宽不要超过图表宽度的三分之一,避免遮挡 K 线。

交互演示:交易面板:对象创建与点击事件

先创建背景、标题和三个按钮,再模拟点击“买入”:OnChartEvent 收到 sparam,检查权限,下单,更新状态并复位按钮。

提示:动画默认暂停。点「播放」连续演示,点「单步」自己控制节奏;桌面端也可以用空格播放、方向键单步、R 重置。

练习题

  • 6.5 图表对象与交易面板 选择题
    1.图表按钮被点击时,EA 收到的回调是? (2分) [选择]
    A. OnTick
    B. OnTimer
    C. OnChartEvent
    D. OnCalculate
    2.面板对象退出时应如何清理? (2分) [选择]
    A. 不用清理,终端会自动删除
    B. 在 OnDeinit 中按前缀 ObjectsDeleteAll
    C. 在 OnTick 中 ObjectDelete
    D. 重启终端
    3.按钮点击后 OBJPROP_STATE 会自动复位为 false。 (2分) [判断]
  • 6.5 图表对象与交易面板 编程实战(AI 批改)
    1.【实战】写一个带买入按钮的面板 (10分) [MQL4自动批改]
    写一个 EA:OnInit 创建名为 pnl_bg 的矩形背景(右上角,宽 180 高 130),再创建名为 pnl_btnBuy 的按钮(文字“买入”,宽 80 高 26);OnChartEvent 中判断 CHARTEVENT_OBJECT_CLICK 且 sparam=="pnl_btnBuy",先将按钮状态复位,再在 IsTradeAllowed 时 OrderSend 买入 0.10 手;OnDeinit 用 ObjectsDeleteAll(0,"pnl_") 清理。必须出现 ObjectCreate、OBJ_BUTTON、OnChartEvent、ObjectsDeleteAll。