DoEasy 函数库中的其他类(第七十一部分):图表对象集合事件(基础篇)
📘

DoEasy 函数库中的其他类(第七十一部分):图表对象集合事件(基础篇)

第 1/3 篇

「给图表对象事件建一个集合类」

DoEasy 函数库在 2021-07-12 发布第七十一部分更新,核心动作是把图表对象相关事件收进一个独立的集合类里管理,而不是散落在各窗口回调中。 该版本示例帖当日浏览量为 1352,说明这类底层事件封装对实盘脚本编写者确有刚需:当你要在 MT5 里同时监控多个对象(趋势线、矩形、文本)的创建、移动、删除,原生 OnChartEvent 容易写成一坨 switch-case。 把事件抽象成集合后,外部调用只需遍历集合拿对象状态,逻辑更薄。外汇与贵金属图表对象受报价跳空影响大,这类封装能降低漏事件概率,但高频行情下仍可能丢事件,需自行压测。

◍ 事件功能要处理手动与程序化操控的差异

上篇已经把图表对象属性、指标窗口的开闭和指标增删改的自动更新跑通了。这一篇要给图表窗口里的图表对象、窗口对象和指标对象都加上事件功能,目的是在这些对象发生变化时,往控制程序的图表发一个自定义事件。 这里有个容易踩的坑:手动操控图表是逐对象进行的,一次只动一个;但如果在计时器一次 tick 里用代码批量改好几个对象,事件很可能被错误定义,最糟的情况是连“哪个对象变了”都识别错。稳妥做法是只记录最后一个事件。 我写代码时会预留批量改参数的接口,但不会去微调、测试那种程序化并发处理多个图表元素的情况。外汇和贵金属市场高波动、高杠杆,这类底层事件逻辑若误判,可能让自动化策略在极端行情下反应错位。如果后续库的用户真有并发需求,我再单独返工处理。

给已删指标留后路:窗口索引与只读列表指针

在 DoEasy 函数库里追踪图表事件,难点不在捕获,而在对象被删之后还能反查。MQL5 的图表子窗口和指标一旦移除,原生 API 很难再拿到它们原先所在的窗口索引,所以作者在 ChartWnd.mqh 的 CWndInd 类私密段加了整型变量存子窗口索引,并配套了 set/get 方法,构造时也强制传入该索引。 为了不在每次需要历史对象时遍历自定义列表,作者在 ChartObjCollection 集合类里声明了 CArrayObj 类型的已删除图表、已删除窗口、已变更指标三个列表,并把指针只读式传给每个图表/窗口/指标对象。整数型属性枚举因此从 66 扩到 67,排序标准也加了按窗口索引一项。 这套指针传递有硬约束:集合外不能删列表里的对象、不能重置列表、不能改指针指向,本质上当只读引用用。好处是任意一个图表对象都能直接读全局已删记录,不必层层回调。 顺带修了个资源泄漏误区:之前每次读完指标数据就 ReleaseIndicator,其实句柄应留到析构才释放。现在 CWndInd 析构里循环遍历窗口指标列表,取对象存的句柄并释放再移除对象,避免反复建句柄。 验证路径:开 MT5 进 \MQL5\Include\DoEasy\Objects\Chart\ChartWnd.mqh,搜 m_wnd_index 变量和类析构里的 ReleaseIndicator 循环,对照改前提交看句柄生命周期差异;外汇/贵金属图表多品种多窗口下,误删句柄会拖慢 EA,属高频实盘隐患。

「图表对象与集合的本地化消息枚举」

在 MT5 的图表管理封装里,模板保存、指标增删、子窗口变动这些动作都需要对应的提示文本,代码用枚举常量加双语字符串表来落地。 下面这段枚举定义了图表对象层级的关键事件 ID,高亮部分是运行时最常触发的指标与窗口类消息: MSG_CHART_OBJ_TEMPLATE_SAVED, // Chart template saved MSG_CHART_OBJ_TEMPLATE_APPLIED, // Template applied to chart MSG_CHART_OBJ_INDICATOR_ADDED, // Indicator added MSG_CHART_OBJ_INDICATOR_REMOVED, // Indicator removed MSG_CHART_OBJ_INDICATOR_CHANGED, // Indicator changed MSG_CHART_OBJ_WINDOW_ADDED, // Subwindow added MSG_CHART_OBJ_WINDOW_REMOVED, // Subwindow removed //--- CChartObjCollection MSG_CHART_COLLECTION_TEXT_CHART_COLLECTION, // Chart collection MSG_CHART_COLLECTION_ERR_FAILED_CREATE_CHART_OBJ, // Failed to create a new chart object MSG_CHART_COLLECTION_ERR_FAILED_ADD_CHART, // Failed to add a chart object to the collection MSG_CHART_COLLECTION_ERR_CHARTS_MAX, // Cannot open new chart. Number of open charts at maximum MSG_CHART_COLLECTION_CHART_OPENED, // Chart opened MSG_CHART_COLLECTION_CHART_CLOSED, // Chart closed }; 紧随其后的字符串数组把俄文界面词与英文做了映射,绿色高亮行对应前面指标和图表开关事件: {"Шаблон графика сохранён","Chart template saved"}, {"Шаблон применён к графику","Template applied to the chart"}, {"Добавлен индикатор","Added indicator"}, {"Удалён индикатор","Removed indicator"}, {"Изменён индикатор","Changed indicator"}, {"Добавлено подокно","Added subwindow"}, {"Удалено подокно","Removed subwindow"}, //--- CChartObjCollection {"Коллекция чартов","Chart collection"}, {"Не удалось создать новый объект-чарт","Failed to create new chart object"}, {"Не удалось добавить объект-чарт в коллекцию","Failed to add chart object to collection"}, {"Нельзя открыть новый график, так как количество открытых графиков уже максимальное","You cannot open a new chart, since the number of open charts is already maximum"}, {"Открыт график","Open chart"}, {"Закрыт график","Closed chart"}, }; 开 MT5 切到俄文终端时,若你写的 EA 调用了 CChartObjCollection,这些字符串就是用户实际看到的回显;改英文表不改枚举,界面可能错位。外汇与贵金属图表多开时,MSG_CHART_COLLECTION_ERR_CHARTS_MAX 会真实弹错,属终端硬限制,非代码 bug。

MQL5 / C++
  MSG_CHART_OBJ_TEMPLATE_SAVED,                    class=class="str">"cmt">// Chart class="kw">template saved
  MSG_CHART_OBJ_TEMPLATE_APPLIED,                   class=class="str">"cmt">// Template applied to chart

  MSG_CHART_OBJ_INDICATOR_ADDED,                    class=class="str">"cmt">// Indicator added
  MSG_CHART_OBJ_INDICATOR_REMOVED,                  class=class="str">"cmt">// Indicator removed
  MSG_CHART_OBJ_INDICATOR_CHANGED,                  class=class="str">"cmt">// Indicator changed
  MSG_CHART_OBJ_WINDOW_ADDED,                       class=class="str">"cmt">// Subwindow added
  MSG_CHART_OBJ_WINDOW_REMOVED,                     class=class="str">"cmt">// Subwindow removed
class=class="str">"cmt">//--- CChartObjCollection
  MSG_CHART_COLLECTION_TEXT_CHART_COLLECTION,       class=class="str">"cmt">// Chart collection
  MSG_CHART_COLLECTION_ERR_FAILED_CREATE_CHART_OBJ, class=class="str">"cmt">// Failed to create a new chart object
  MSG_CHART_COLLECTION_ERR_FAILED_ADD_CHART,        class=class="str">"cmt">// Failed to add a chart object to the collection
  MSG_CHART_COLLECTION_ERR_CHARTS_MAX,              class=class="str">"cmt">// Cannot open new chart. Number of open charts at maximum

  MSG_CHART_COLLECTION_CHART_OPENED,                class=class="str">"cmt">// Chart opened
  MSG_CHART_COLLECTION_CHART_CLOSED,                class=class="str">"cmt">// Chart closed

};
class=class="str">"cmt">//+------------------------------------------------------------------+
  {"Шаблон графика сохранён","Chart class="kw">template saved"},
  {"Шаблон применён к графику","Template applied to the chart"},

  {"Добавлен индикатор","Added indicator"},
  {"Удалён индикатор","Removed indicator"},
  {"Изменён индикатор","Changed indicator"},

  {"Добавлено подокно","Added subwindow"},
  {"Удалено подокно","Removed subwindow"},

class=class="str">"cmt">//--- CChartObjCollection
  {"Коллекция чартов","Chart collection"},
  {"Не удалось создать новый объект-чарт","Failed to create new chart object"},
  {"Не удалось добавить объект-чарт в коллекцию","Failed to add chart object to collection"},
  {"Нельзя открыть новый график, так как количество открытых графиков уже максимальное","You cannot open a new chart, since the number of open charts is already maximum"},

  {"Открыт график","Open chart"},
  {"Закрыт график","Closed chart"},

};

◍ 把图表事件与整数属性做成可枚举常量

想在 EA 或指标里监听 MT5 图表自身的变化,第一步是把散落的事件码收拢成一个枚举。下面这段把图表开关、窗口增减、指标增删与参数改动都列进 ENUM_CHART_OBJ_EVENT,首成员从 SIGNAL_MQL5_EVENTS_NEXT_CODE 起算,避免和系统事件撞码。 末尾用宏 CHART_OBJ_EVENTS_NEXT_CODE 取最后一个成员加 1,作为自定义后续事件的起始偏移。实战中如果你要挂自己的图形交互事件,就从这个数往下排,能降低事件 ID 冲突概率。 图表整数属性另起一个枚举 ENUM_CHART_PROP_INTEGER,CHART_PROP_ID 固定为 0,紧接的是 TIMEFRAME、SHOW、IS_OBJECT 等。注意 CHART_PROP_EVENT_MOUSE_WHEEL 与 CHART_PROP_EVENT_MOUSE_MOVE 两个开关:不打开它们,鼠标滚轮和移动消息就不会广播给图表上的 MQL5 程序,回测里常被人漏掉导致画线工具没反应。外汇与贵金属杠杆高,这类监听逻辑若误触发批量下单,风险会被放大,参数务必先在模拟盘验证。

MQL5 / C++
enum ENUM_CHART_OBJ_EVENT
  {
   CHART_OBJ_EVENT_NO_EVENT = SIGNAL_MQL5_EVENTS_NEXT_CODE, class=class="str">"cmt">// No event
   CHART_OBJ_EVENT_CHART_OPEN,                            class=class="str">"cmt">// "New chart opening" event
   CHART_OBJ_EVENT_CHART_CLOSE,                           class=class="str">"cmt">// "Chart closure" event
   CHART_OBJ_EVENT_CHART_WND_ADD,                         class=class="str">"cmt">// "Adding a new window on the chart" event
   CHART_OBJ_EVENT_CHART_WND_DEL,                         class=class="str">"cmt">// "Removing a window from the chart" event
   CHART_OBJ_EVENT_CHART_WND_IND_ADD,                     class=class="str">"cmt">// "Adding a new indicator to the chart window" event
   CHART_OBJ_EVENT_CHART_WND_IND_DEL,                     class=class="str">"cmt">// "Removing an indicator from the chart window" event
   CHART_OBJ_EVENT_CHART_WND_IND_CHANGE,                  class=class="str">"cmt">// "Changing indicator parameters in the chart window" event
  };
class="macro">#define CHART_OBJ_EVENTS_NEXT_CODE(CHART_OBJ_EVENT_CHART_WND_IND_CHANGE+class="num">1)   class=class="str">"cmt">// The code of the next event after the last chart event code
enum ENUM_CHART_PROP_INTEGER
  {
   CHART_PROP_ID = class="num">0,                                     class=class="str">"cmt">// Chart ID
   CHART_PROP_TIMEFRAME,                                  class=class="str">"cmt">// Chart timeframe
   CHART_PROP_SHOW,                                       class=class="str">"cmt">// Price chart drawing
   CHART_PROP_IS_OBJECT,                                  class=class="str">"cmt">// Chart object(OBJ_CHART) identification attribute
   CHART_PROP_BRING_TO_TOP,                               class=class="str">"cmt">// Show chart above all others
   CHART_PROP_CONTEXT_MENU,                               class=class="str">"cmt">// Enable/disable access to the context menu using the right click
   CHART_PROP_CROSSHAIR_TOOL,                             class=class="str">"cmt">// Enable/disable access to the Crosshair tool using the middle click
   CHART_PROP_MOUSE_SCROLL,                               class=class="str">"cmt">// Scroll the chart horizontally using the left mouse button
   CHART_PROP_EVENT_MOUSE_WHEEL,                          class=class="str">"cmt">// Send messages about mouse wheel events(CHARTEVENT_MOUSE_WHEEL) to all MQL5 programs on a chart
   CHART_PROP_EVENT_MOUSE_MOVE,                           class=class="str">"cmt">// Send messages about mouse button click and movement events(CHARTEVENT_MOUSE_MOVE) to all MQL5 programs on a chart
  };

图表属性枚举里藏着的事件与显示开关

在 MT5 的图表属性体系里,有一批以 CHART_PROP_ 开头的枚举值,专门控制图表的事件广播与视觉元素。上面这段罗列了从对象创建/删除事件到各种显示线条、网格、成交量等共 25 个属性常量,它们都能作为 ChartGetInteger / ChartSetInteger 的第二个参数直接读写。 比如 CHART_PROP_EVENT_OBJECT_CREATE 与 CHART_PROP_EVENT_OBJECT_DELETE,开启后图表上任意 EA 或指标画的对象被增删,都会向同图所有 MQL5 程序广播 CHARTEVENT_OBJECT_CREATE / DELETE 事件,多程序协作时靠这个互通有无。 CHART_PROP_SCALEFIX_11 是 1:1 比例模式,价格点数和屏幕像素一一对应,做精确形态测量时比普通缩放更不容易看走样;CHART_PROP_SHOW_BID_LINE 和 CHART_PROP_SHOW_ASK_LINE 则决定买卖价水平线是否常驻。外汇与贵金属杠杆高、滑点随机,这类显示开关只影响观察效率,不改变任何成交概率。 打开 MT5 按 F4 进 MetaEditor,随便建个脚本用 ChartSetInteger(0, CHART_PROP_SHOW_ASK_LINE, true) 跑一下,就能直接验证这些属性是否如枚举注释所说即时生效。

MQL5 / C++
  CHART_PROP_EVENT_OBJECT_CREATE,                                                                     class=class="str">"cmt">// Send messages about the graphical object creation event(CHARTEVENT_OBJECT_CREATE) to all MQL5 programs on a chart
  CHART_PROP_EVENT_OBJECT_DELETE,                                                                     class=class="str">"cmt">// Send messages about the graphical object destruction event(CHARTEVENT_OBJECT_DELETE) to all MQL5 programs on a chart
  CHART_PROP_MODE,                                                                                    class=class="str">"cmt">// Type of the chart(candlesticks, bars or line(ENUM_CHART_MODE))
  CHART_PROP_FOREGROUND,                                                                              class=class="str">"cmt">// Price chart in the foreground
  CHART_PROP_SHIFT,                                                                                   class=class="str">"cmt">// Mode of shift of the price chart from the right border
  CHART_PROP_AUTOSCROLL,                                                                              class=class="str">"cmt">// The mode of automatic shift to the right border of the chart
  CHART_PROP_KEYBOARD_CONTROL,                                                                        class=class="str">"cmt">// Allow managing the chart using a keyboard
  CHART_PROP_QUICK_NAVIGATION,                                                                        class=class="str">"cmt">// Allow the chart to intercept Space and Enter key strokes to activate the quick navigation bar
  CHART_PROP_SCALE,                                                                                   class=class="str">"cmt">// Scale
  CHART_PROP_SCALEFIX,                                                                                class=class="str">"cmt">// Fixed scale mode
  CHART_PROP_SCALEFIX_11,                                                                             class=class="str">"cmt">// class="num">1:class="num">1 scale mode
  CHART_PROP_SCALE_PT_PER_BAR,                                                                        class=class="str">"cmt">// The mode of specifying the scale in points per bar
  CHART_PROP_SHOW_TICKER,                                                                             class=class="str">"cmt">// Display a symbol ticker in the upper left corner
  CHART_PROP_SHOW_OHLC,                                                                               class=class="str">"cmt">// Display OHLC values in the upper left corner
  CHART_PROP_SHOW_BID_LINE,                                                                           class=class="str">"cmt">// Display Bid value as a horizontal line on the chart
  CHART_PROP_SHOW_ASK_LINE,                                                                           class=class="str">"cmt">// Display Ask value as a horizontal line on a chart
  CHART_PROP_SHOW_LAST_LINE,                                                                          class=class="str">"cmt">// Display Last value as a horizontal line on a chart
  CHART_PROP_SHOW_PERIOD_SEP,                                                                         class=class="str">"cmt">// Display vertical separators between adjacent periods
  CHART_PROP_SHOW_GRID,                                                                               class=class="str">"cmt">// Display a grid on the chart
  CHART_PROP_SHOW_VOLUMES,                                                                            class=class="str">"cmt">// Display volumes on a chart
  CHART_PROP_SHOW_OBJECT_DESCR                                                                       class=class="str">"cmt">// Display text descriptions of objects

常见问题

手动操作触发的事件需区分用户来源,程序化创建要标记来源避免重复响应;集合类里对两种来源做分支判断即可。
用只读列表指针保留已删对象的弱引用,窗口索引存本地副本,访问前先判空,避免野指针崩溃。
小布可接管图表对象事件监听,实时提示删除或属性变更,并把差异推到盯盘面板,省去自己写集合类。
建本地化消息枚举,按客户端语言ID映射字符串,集合类取消息时统一走枚举不写死文本。
把事件与显示位抽成可枚举常量,用位运算读取对应位,避免直接记魔法数字,改配置只调枚举。