DoEasy 函数库中的图形(第九十部分):标准图形对象事件。 基本功能(基础篇)
给标准图形对象挂上事件钩子
在 DoEasy 函数库里,标准图形对象(水平线、趋势线、矩形这类)现在能直接抛出事件,不用交易者再写轮询去判断对象有没有被拖拽或删除。这意味着 EA 可以监听对象变动并即时响应,比如线被挪动就重算支撑阻力。 原文示例标注于 2022 年 2 月 28 日发布,累计浏览 1572 次,说明这类底层封装在实际跑 MT5 的人里关注度不算低,但也不算爆款——属于硬核自用型功能。外汇和贵金属波动大、滑点频繁,用对象事件驱动下单前,务必在策略测试器里先验证事件触发延迟。 要验证这套机制,开 MT5 把库里标准图形对象事件类挂到一个空 EA 上,手动拖一根趋势线,看日志是否打出对象 ID 与变更类型。能跑通,再谈后续封装。
「用事件钩子盯住图形对象的变化」
在 MT5 里手动拖一条趋势线,程序若想实时知道它的价位变了,最笨的办法是每帧去读对象属性——这其实是资源密集型操作,官方文档也明确提示过频繁轮询会拖慢终端。 更好的落点是 OnChartEvent():终端本身就把图形对象的增删改都封装成了事件推给 EA。我们要做的,是在自己的函数库里补一层,让程序精确知道“哪个对象、哪个属性”动了,而不是瞎猜。 这一节先把对象级事件定下来:新建、改属性、重命名、删除、随图表窗口一起删除。这五类会直接进 OnChartEvent()。下一篇才逐个写处理器,现在只是把骨架立住。 重命名被单独拎出来,是因为它底层等价于“删旧对象+建新对象+改属性”一连串动作;拆成独立事件能省掉上层逻辑的判断分支。删除事件目前只能报“删了几个、叫什么名”,若连图表窗口一起关,暂时只知道被删对象的数量,具体属性暂不回溯——作者判断暂无强需求。
◍ 给图形对象事件追踪补上底层钩子
想在 MT5 里实时跟踪图形对象的选中、取消选中,光靠双击动作不够,得先在事件响应程序里注册新的事件 ID。当前图表直接加 ID 就能监听,跨图表则必须改事件控制指标,让新开的图表窗口自动挂上监听——否则你在副图上的对象操作主图指标根本收不到信号。 具体落地要动几个文件:EventControl.mq5 发新事件 ID;Data.mqh 加消息索引和文本;Defines.mqh 删掉多余的「对象重新定位事件」,因为属性更改枚举已覆盖;再补一个删除图形对象/图表的常数。注意枚举里下一个事件代码要跟着最后一个枚举值自动算,手改魔法数字迟早出 bug。 类构造上有个坑:CGStdGraphObj 构造函数从物理对象读属性写进类变量,但原有 Set 方法会无条件调 ObjectSetXXX 写回图表。图表同步慢,重复写是浪费。解决办法是在 CGBaseObj 的 Set 方法加 bool only_prop 参数——true 只写类变量,false 才先写图形对象再写变量。改完所有图形对象基类方法都照这个逻辑走,避免过度访问慢函数。 标准图形对象类里把设属性的方法全改成返回 bool,不然父类 bool 子类 void 在重载时提示会乱。以设子窗口索引为例:用已存的图表 ID 和对象名调基类方法,失败返 false,成功填属性返 true。设全周期可见性的方法也多接一个参数指定「仅属性还是连对象带属性」,逻辑一致。其余整数型属性接收方法同理,先写基类再填衍生类,避免取属性时读不到图表数据。 下面这段是事件控制指标的骨架,跨图表发事件时 InpChartSRC/InpChartDST 填图表 ID 即可验证:
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EventControl.mq5 | class=class="str">"cmt">//| Copyright class="num">2021, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2021, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property indicator_chart_window class="macro">#class="kw">property indicator_plots class="num">0 class=class="str">"cmt">//--- input parameters input class="type">long InpChartSRC = class="num">0; input class="type">long InpChartDST = class="num">0; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- indicator shortname IndicatorSetString(INDICATOR_SHORTNAME,"EventSend_From#"+(class="type">class="kw">string)InpChartSRC+"_To#"+(class="type">class="kw">string)InpChartDST); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Custom indicator iteration function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(const class="type">int rates_total,
图表事件转发与对象控制指示器
在自定义指标里,OnCalculate 直接返回 rates_total 即可,系统用 prev_calculated 判断增量计算边界,省去手动维护柱数状态的麻烦。 OnChartEvent 负责把用户操作透传出去:当 id 等于 CHARTEVENT_OBJECT_CHANGE、CHARTEVENT_OBJECT_DRAG 或 CHARTEVENT_OBJECT_CLICK 时,调用 EventChartCustom 把事件转发到目标图表(InpChartDST),并带上源图表 ID(InpChartSRC)、数值 dparam 与字符串 sparam。这样跨图表联动画线、拖拽锚点就能被另一端的 EA 或指标捕获。 CGraphElementsCollection 的错误码暴露了内部链路:MSG_GRAPH_OBJ_ADDED_EVN_CTRL_INDICATOR 表示控制事件指示器已成功加载到图表,而 MSG_GRAPH_OBJ_FAILED_ADD_EVN_OBJ 意味着图形对象的事件对象没进列表。开 MT5 挂上这类指标后,从 Experts 日志搜这些前缀,能直接定位是建对象失败还是加指示器失败。
const class="type">int prev_calculated, const class="type">class="kw">datetime &time[], const class="type">class="kw">double &open[], const class="type">class="kw">double &high[], const class="type">class="kw">double &low[], const class="type">class="kw">double &close[], const class="type">long &tick_volume[], const class="type">long &volume[], const class="type">int &spread[]) { class="kw">return rates_total; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ChartEvent function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam) { class=class="str">"cmt">//--- if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG || id==CHARTEVENT_OBJECT_CLICK) { EventChartCustom(InpChartDST,(class="type">class="kw">ushort)id,InpChartSRC,dparam,sparam); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- CGraphElementsCollection MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST, class=class="str">"cmt">// Failed to get the list of newly added objects MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST, class=class="str">"cmt">// Failed to remove a graphical object from the list MSG_GRAPH_OBJ_CREATE_EVN_CTRL_INDICATOR, class=class="str">"cmt">// Indicator for controlling and sending events created MSG_GRAPH_OBJ_FAILED_CREATE_EVN_CTRL_INDICATOR, class=class="str">"cmt">// Failed to create the indicator for controlling and sending events MSG_GRAPH_OBJ_ADDED_EVN_CTRL_INDICATOR, class=class="str">"cmt">// Indicator for controlling and sending events successfully added to the chart MSG_GRAPH_OBJ_FAILED_ADD_EVN_CTRL_INDICATOR, class=class="str">"cmt">// Failed to add the indicator for controlling and sending events MSG_GRAPH_OBJ_CLOSED_CHARTS, class=class="str">"cmt">// Chart windows closed: MSG_GRAPH_OBJ_OBJECTS_ON_CLOSED_CHARTS, class=class="str">"cmt">// Objects removed together with charts: MSG_GRAPH_OBJ_FAILED_CREATE_EVN_OBJ, class=class="str">"cmt">// Failed to create the event object for a graphical object MSG_GRAPH_OBJ_FAILED_ADD_EVN_OBJ, class=class="str">"cmt">// Failed to add the event object to the list
「图形对象事件枚举与提示文本映射」
在 MT5 的自定义图形对象事件框架里,系统用一组枚举值区分对象生命周期动作:新建、属性变更、重命名、删除,以及随图表关闭被连带清除。代码里还单独列了 MOVE 事件,说明对象被拖拽位移也会触发独立回调,不只是属性改写。 配套的多语言字符串表把每个技术状态翻成可读提示,例如「New graphic object created」对应新建、「Graphic object deleted」对应删除。若图表被关,会同时输出「Closed chart windows: 」和「Objects removed with them: 」两条计数,便于回溯资源释放是否干净。 实操上,开 MT5 新建 EA 把下面枚举抄进工程,挂一个监控指标,就能在对象被手动拖动或改名时收到事件,而不是轮询 ObjectGetInteger。外汇与贵金属图表对象变动频繁,这类事件驱动写法能降低 CPU 占用,但信号滑点和高波动下的对象竞态仍属高风险场景,需自行压测。
enum ENUM_GRAPH_OBJ_EVENT { GRAPH_OBJ_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE,class=class="str">"cmt">// No event GRAPH_OBJ_EVENT_CREATE, class=class="str">"cmt">// "Creating a new graphical object" event GRAPH_OBJ_EVENT_CHANGE, class=class="str">"cmt">// "Changing graphical object properties" event GRAPH_OBJ_EVENT_MOVE, class=class="str">"cmt">// "Moving graphical object" event GRAPH_OBJ_EVENT_RENAME, class=class="str">"cmt">// "Renaming graphical object" event
◍ 图形对象事件枚举与背景标志设置
在 MT5 自定义图形对象封装里,事件类型用枚举 ENUM_GRAPH_OBJ_EVENT 管理,从 CHART_OBJ_EVENTS_NEXT_CODE 起算,依次覆盖创建、属性变更、重命名、删除,以及随图表窗口一起被移除的 GRAPH_OBJ_EVENT_DEL_CHART。 末尾用宏 GRAPH_OBJ_EVENTS_NEXT_CODE 定义为最后一个事件码加 1,给后续扩展事件留出了连续编号空间,避免和平台内建对象事件撞码。 SetFlagBack 方法控制对象是否置于背景层:传入 flag 设 OBJPROP_BACK,only_prop 为 true 时只改内存成员 m_back 不碰图表,为 false 才真正调用 ObjectSetInteger 下发到图表。 调用前先 ResetLastError,失败则走 CMessage::ToLog 把错误码写日志;外汇与贵金属图表对象交互属高频操作,图形事件回调异常可能漏掉关键价位提示,实盘前建议在策略测试器里跑一遍删除与换图事件。
enum ENUM_GRAPH_OBJ_EVENT { GRAPH_OBJ_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE,class=class="str">"cmt">// No event GRAPH_OBJ_EVENT_CREATE, class=class="str">"cmt">// "Creating a new graphical object" event GRAPH_OBJ_EVENT_CHANGE, class=class="str">"cmt">// "Changing graphical object properties" event GRAPH_OBJ_EVENT_RENAME, class=class="str">"cmt">// "Renaming graphical object" event GRAPH_OBJ_EVENT_DELETE, class=class="str">"cmt">// "Removing graphical object" event GRAPH_OBJ_EVENT_DEL_CHART, class=class="str">"cmt">// "Removing a graphical object together with the chart window" event }; class="macro">#define GRAPH_OBJ_EVENTS_NEXT_CODE(GRAPH_OBJ_EVENT_DEL_CHART+class="num">1) class=class="str">"cmt">// The code of the next event after the last graphical object event code class="type">bool SetFlagBack(const class="type">bool flag,const class="type">bool only_prop) { ::ResetLastError(); if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_BACK,flag)) || only_prop) { this.m_back=flag; class="kw">return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); class="kw">return false; }