DoEasy 函数库中的图形(第九十一部分):标准图形对象事件。 对象名称更改历史记录(基础篇)
给标准图形对象挂上改名事件钩子
在 DoEasy 函数库里,标准图形对象现在能捕获名称被更改的事件了。这意味着当图表上的水平线、趋势线被重命名时,底层类会写入一条对象名称更改历史记录,而不是只保留当前名。 对做价格行为复盘的交易者来说,这套历史记录可以直接用来追溯自己什么时候把某条关键支撑线从「S1」改成了「Breakout_Level」,避免手动记笔记。MT5 上跑这套逻辑,本质是给图形对象事件补了一块之前缺失的监听。 原文示例发布于 2022 年 3 月 4 日,截至收录时该帖在源站有 2275 次查看、4 条讨论,说明这类底层对象事件封装在实际 EA 开发里需求并不小。外汇与贵金属图表对象操作同样适用,但需注意 MT5 自动交易存在滑点与断连风险,事件监听不等于执行兜底。
◍ 给图形对象装上变更记忆
现有函数库只能识别标准图形对象发生了事件,但无法告诉使用者具体哪个属性变了、变了多少。本文要补上的能力是:在事件触发时,同时回传事件 ID、对象名称、所属图表 ID,以及被改动的属性名,让调用方精确定位差异。 当图形对象从图表被删掉,对应类实例不会立刻销毁,而是进入“已删除对象列表”。这一点很关键——程序仍能读取它的全部属性,甚至可以把误删的对象还原回图表。 如果把“暂存已删除对象”的思路延伸到“暂存属性变更”,就能顺出一条对象变更历史链。只要保留连续的状态快照,任意时刻都能把对象回滚到此前某一态,或按序/乱序重放。这意味着技术分析工具可以自带“记忆”,下一篇要做的复合图形对象也会用到这套机制。 本篇先不铺开全属性记录,只用“对象名称”属性变更做验证:改名时在日志打出旧名与新名。后续文章再逐步把全部属性的记录与重放能力补齐。外汇与贵金属图表对象受价格跳变影响大,这类回放功能仅作调试与复盘辅助,实盘高风险,须自行验证。
「给图形对象装上重命名记忆」
在 DoEasy 库里给标准图形对象加“前名记忆”,思路是直接把旧名塞进基于多维动态数组的对象属性里。数组 0 号单元格存当前名,后面每重命名一次就在末尾追加一个旧名,于是类对象自身就带着完整改名轨迹,不用去翻终端历史。 抽象类 GStdGraphObj 公开区新增了两个方法:一个返回属性对象指针,一个在日志打印重命名历史。SetPrevName() 接收旧名,把名称属性数组 size+1,新单元格写入旧名,失败就打日志返 false。PrintRenameHistory() 先打当前名标题,再从索引 1 起倒序循环——因为 0 是现名、末位是上一代旧名,倒着数就能还原顺序。重命名三次的对象日志会列出四条记录,顺序一目了然。 集合类 GraphElementsCollection 同步改了事件跟踪。以前同时加多个对象只捕获一个添加事件,现在先建添加列表,再逐个发事件;删除时也按数量循环发事件,并把移除对象挪到“已删除列表”而非直接销毁,方便后续按图表 ID 提取还原。图表管理类 Refresh() 用 EventChartCustom() 的 dparam 传对象创建时间,能识别“删图表又还原”后时间归零的老对象。 这些改动让图形对象变成可记录状态的分析单元。外汇和贵金属图表上手动画的趋势线若被重命名或图表误删,用 CEngine 的新方法(返回已删除列表、返回属性数组大小)就能在 EA 里捞回历史参数,但 MT5 对象状态管理本身复杂,实盘使用前建议在策略测试器用历史图表验证内存与日志表现。
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_FAILED_ADD_OBJ_TO_DEL_LIST, class=class="str">"cmt">// Failed to set a graphical object to the list of removed objects MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_RNM_LIST, class=class="str">"cmt">// Failed to set a graphical object to the list of renamed objects 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_ALREADY_EXIST_EVN_CTRL_INDICATOR class=class="str">"cmt">// Indicator for controlling and sending events is already present on the chart
图形对象事件与多语消息映射
在 MT5 的图形对象管理模块里,系统用一组枚举常量跟踪对象生命周期:从新建、改属性、重命名到随图表关闭被连带删除,都对应独立事件标识。 下面这段消息表把俄文与英文成对写入,用于运行时弹窗或日志输出。注意高亮的三行——'Failed to place graphic object in the list of deleted objects'、'Property change history: ' 和 'The indicator for monitoring and sending events is already on the chart',它们分别对应删除列表写入失败、属性变更历史起点、监控指标重复加载三种边界状态。 如果你在 EA 里自己维护图形对象集合,直接复制这套枚举+双语文案结构,能在回测和实盘里少写 80% 的调试输出代码。外汇与贵金属图表对象随价格波动频繁重绘,这类事件捕获对复盘高风险行情下的画线逻辑很有用。
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 MSG_GRAPH_OBJ_GRAPH_OBJ_PROP_CHANGE_HISTORY, class=class="str">"cmt">// Property change history: MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CREATE, class=class="str">"cmt">// New graphical object created MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CHANGE, class=class="str">"cmt">// Changed the graphical object class="kw">property MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_RENAME, class=class="str">"cmt">// Graphical object renamed MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DELETE, class=class="str">"cmt">// Graphical object removed MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DEL_CHART, class=class="str">"cmt">// Graphical object removed together with the chart }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- CGraphElementsCollection {"Не удалось получить список вновь добавленных объектов","Failed to get the list of newly added objects"}, {"Не удалось изъять графический объект из списка","Failed to detach graphic object from the list"}, {"Не удалось поместить графический объект в список удалённых объектов","Failed to place graphic object in the list of deleted objects"}, {"Не удалось поместить графический объект в список переименованных объектов","Failed to place graphic object in the list of renamed objects"}, {"Создан индикатор контроля и отправки событий","An indicator for monitoring and sending events has been created"}, {"Не удалось создать индикатор контроля и отправки событий","Failed to create indicator for monitoring and sending events"}, {"Индикатор контроля и отправки событий успешно добавлен на график ","The indicator for monitoring and sending events has been successfully added to the chart "}, {"Не удалось добавить индикатор контроля и отправки событий на график ","Failed to add the indicator of monitoring and sending events to the chart "}, {"Индикатор контроля и отправки событий уже есть на графике","The indicator for monitoring and sending events is already on the chart"}, {"Закрыто окон графиков: ","Closed chart windows: "}, {"С ними удалено объектов: ","Objects removed with them: "}, {"Не удалось создать объект-событие для графического объекта","Failed to create event object for graphic object"}, {"Не удалось добавить объект-событие в список","Failed to add event object to list"}, {"История изменения свойства: ","Property change history: "}, {"Создан новый графический объект","New graphic object created"}, {"Изменено свойство графического объекта","Changed graphic object class="kw">property"}, {"Графический объект переименован","Graphic object renamed"}, {"Удалён графический объект","Graphic object deleted"}, {"Графический объект удалён вместе с графиком","The graphic object has been removed along with the chart"}, };
◍ 标准图形对象类的接口暴露方式
CGStdGraphObj 作为标准图形对象的基类,把自身指针和属性管理器对外暴露得很直接。GetObject() 返回 this 的地址,Properties() 返回内部 CProperties 实例指针,这意味着在 EA 或脚本里拿到对象后,可以绕过封装直接读写属性。 三个 SupportProperty 重载全部返回 true,覆盖整数、双精度、字符串三类枚举属性。也就是说基类默认声明自己支持全部属性,具体支不支持要看派生类怎么重写,调用前最好用 SupportProperty 探一下,别默认全能设。 Print 与 PrintShort 两个虚函数负责把对象信息打到终端:Print 的 full_prop 参数控制打全部属性还是仅受支持属性,PrintShort 则只出简短描述,dash 和 symbol 控制是否带分隔线与品种名。AnchorDescription 直接取 GRAPH_OBJ_PROP_ANCHOR 的第 0 位转成字符串,给出锚点说明。 Compare 按指定属性和模式比较两个对象,是给对象列表做排序用的。下面这段是类里最该动手验证的几行,粘到 MT5 头文件里编译就能看到结构。
CGStdGraphObj *GetObject(class="type">void) { class="kw">return &this; } CProperties *Properties(class="type">void) { class="kw">return this.Prop; } class=class="str">"cmt">//--- Return the flag of the object supporting this class="kw">property class="kw">virtual class="type">bool SupportProperty(ENUM_GRAPH_OBJ_PROP_INTEGER class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_GRAPH_OBJ_PROP_STRING class="kw">property) { class="kw">return true; } class=class="str">"cmt">//--- Get description of(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties class="type">class="kw">string GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_INTEGER class="kw">property); class="type">class="kw">string GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_DOUBLE class="kw">property); class="type">class="kw">string GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_STRING class="kw">property); class=class="str">"cmt">//--- Return the description of the graphical object anchor point position class="kw">virtual class="type">class="kw">string AnchorDescription(class="type">void) class="kw">const { class="kw">return (class="type">class="kw">string)this.GetProperty(GRAPH_OBJ_PROP_ANCHOR,class="num">0); } class=class="str">"cmt">//--- Display the description of the object properties in the journal(full_prop=true - all properties, class="kw">false - supported ones only - implemented in descendant classes) class="kw">virtual class="type">void Print(class="kw">const class="type">bool full_prop=class="kw">false,class="kw">const class="type">bool dash=class="kw">false); class=class="str">"cmt">//--- Display a class="type">class="kw">short description of the object in the journal class="kw">virtual class="type">void PrintShort(class="kw">const class="type">bool dash=class="kw">false,class="kw">const class="type">bool symbol=class="kw">false); class=class="str">"cmt">//--- Return the object class="type">class="kw">short name class="kw">virtual class="type">class="kw">string Header(class="kw">const class="type">bool symbol=class="kw">false); class=class="str">"cmt">//--- Display the history of renaming the object to the journal class="type">void PrintRenameHistory(class="type">void); class=class="str">"cmt">//--- Return the description of the(class="num">1) (ENUM_OBJECT) graphical object type class="kw">virtual class="type">class="kw">string TypeDescription(class="type">void) class="kw">const { class="kw">return CMessage::Text(MSG_GRAPH_STD_OBJ_ANY); } class=class="str">"cmt">//--- Compare CGStdGraphObj objects by a specified class="kw">property (to sort the list by an object class="kw">property) class="kw">virtual class="type">int Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const;
「对象改名历史怎么落进日志」
图形对象在 MT5 里不是只存当前名字,CGStdGraphObj 把每次改名都塞进同名属性的尺寸扩展区,靠 SetNamePrev 把旧名压进数组尾部。上面那段实现先给 GRAPH_OBJ_PROP_NAME 扩 1 个槽位,再把新名字写进倒数第一位,返回失败只可能是属性容器扩容没成功。 PrintRenameHistory 负责把这套历史倒序打印到专家日志:先打第 0 号原始名,再从 size-1 往 1 循环,输出每次改名的记录。下面这份实际日志显示,一个名为 M30 Vertical Line 24264_3 的竖线,最早叫 M30 Vertical Line 24264,之后连续被改了三次名,编号从 _1 累到 _3。 这种机制对外汇和贵金属图表脚本很有用——这类品种跳空和重连频繁,对象被重命名的概率不低,留痕能帮你回溯是哪个 EA 在动你的画线。不过自动化对象管理本身不改变行情方向,相关操作须清醒认识杠杆交易的高风险。
class=class="str">"cmt">//--- Compare CGStdGraphObj objects with each other by all properties(to search for equal objects) class="type">bool IsEqual(CGStdGraphObj* compared_req) class="kw">const; class=class="str">"cmt">//--- Set the object previous name class="type">bool SetNamePrev(class="kw">const class="type">class="kw">string name) { if(!this.Prop.SetSizeRange(GRAPH_OBJ_PROP_NAME,this.Prop.CurrSize(GRAPH_OBJ_PROP_NAME)+class="num">1)) class="kw">return class="kw">false; this.SetProperty(GRAPH_OBJ_PROP_NAME,this.Prop.CurrSize(GRAPH_OBJ_PROP_NAME)-class="num">1,name); class="kw">return true; } class=class="str">"cmt">//--- Default constructor CGStdGraphObj(){ this.m_type=OBJECT_DE_TYPE_GSTD_OBJ; m_group=WRONG_VALUE; } class=class="str">"cmt">//--- Destructor ~CGStdGraphObj() { if(this.Prop!=NULL) class="kw">delete this.Prop; } class="kw">protected: class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Display the history of renaming the object to the journal | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CGStdGraphObj::PrintRenameHistory(class="type">void) { ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_GRAPH_OBJ_PROP_CHANGE_HISTORY),this.GetPropertyDescription(GRAPH_OBJ_PROP_NAME),":"); class="type">int size=this.Properties().CurrSize(GRAPH_OBJ_PROP_NAME); ::Print(DFUN,"class="num">0: ",this.GetProperty(GRAPH_OBJ_PROP_NAME,class="num">0)); for(class="type">int i=size-class="num">1;i>class="num">0;i--) ::Print(DFUN,size-i,": ",this.GetProperty(GRAPH_OBJ_PROP_NAME,i)); }