DoEasy 函数库中的图形(第九十一部分):标准图形对象事件。 对象名称更改历史记录(基础篇)
📘

DoEasy 函数库中的图形(第九十一部分):标准图形对象事件。 对象名称更改历史记录(基础篇)

第 1/3 篇

给标准图形对象挂上改名事件钩子

在 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 对象状态管理本身复杂,实盘使用前建议在策略测试器用历史图表验证内存与日志表现。

MQL5 / C++
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% 的调试输出代码。外汇与贵金属图表对象随价格波动频繁重绘,这类事件捕获对复盘高风险行情下的画线逻辑很有用。

MQL5 / C++
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 头文件里编译就能看到结构。

MQL5 / C++
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 在动你的画线。不过自动化对象管理本身不改变行情方向,相关操作须清醒认识杠杆交易的高风险。

MQL5 / C++
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));
  }

常见问题

在标准图形对象类里挂一个改名事件钩子,触发时把对象ID、旧名、新名写进日志,打开对应品种页即可回看。
警报按旧对象名匹配,改名后匹配失效;需在改名事件里同步更新警报引用的对象标识。
可以,小布能监听对象变更记忆并推送改名提醒,你不用自己写钩子也能知道哪条线被动过。
把事件文本做多语消息映射,按终端语言取对应字符串,避免硬编码中文或英文。
至少暴露获取对象ID、读名称历史、写日志三个接口,外部脚本才能低成本拉出改名记录。