DoEasy 函数库中的图形(第九十部分):标准图形对象事件。 基本功能·综合运用
📐

DoEasy 函数库中的图形(第九十部分):标准图形对象事件。 基本功能·综合运用

(3/3)·从对象创建到窗口删除,65 个小节把 MT5 图形事件收口成一套可直接调用的库

含代码示例实战向 第 3/3 篇
很多 EA 还在用轮询方式读图形对象属性,CPU 悄悄被拖垮还漏掉用户拖动轨迹线的瞬间。事件不接管,策略永远慢半拍,跨周期图形状态更是乱成一团。
本章目录
  1. GUI 阴影对象的创建与隐藏逻辑
  2. 图形对象的阴影与主窗绑定逻辑
  3. 图形对象的层级与可见性控制
  4. 图形对象的隐藏与选中状态接口
  5. 图形对象的时间与外观读写接口
  6. 图形对象的线宽与填充读写接口
  7. 图形对象的只读与多水平线控制
  8. 对象线宽与对齐的底层封装
  9. 图形对象的字号与射线方向控制
  10. 图形射线与斐波那契弧的属性封装
  11. 箭头与锚点的像素级控制
  12. 图形对象的纵向偏移与甘氏艾略特属性读写
  13. 艾略特波形对象的画线开关与按钮态读写
  14. 图表对象的周期与坐标轴开关
  15. 图表对象的缩放与像素尺寸控制
  16. 图表对象可视区域尺寸与偏移的读写封装
  17. 控件底色与锚点角的读写封装
  18. 图形对象的边框与坐标读写封装
  19. 图形对象的层级值与缩放角度封装
  20. 图形对象的偏离值与命名控制
  21. 图形对象的文本与提示属性封装
  22. 给指标对象写文字与贴图的底层接口
  23. 图形对象的跨品种绑定与属性回读
  24. 图形对象属性的批量同步写法
  25. 对象属性同步时的级别数组重分配
  26. 把图形对象的隐藏属性一次性抓进类里
  27. 把图表对象的几何与外观读进类属性
  28. 把图形对象变动封装成可追踪事件
  29. 图形事件对象的封装与派发
  30. 图形对象基类的成员布局
  31. 图形对象的底层字段与事件接口
  32. 事件链表的初始化与清空逻辑
  33. 图形对象的状态初始化与变更侦测
  34. 对象属性变更如何触发图表事件
  35. 对象创建失败时的安全退出路径
  36. 图表对象集合类的内部接口
  37. 图形对象重复创建时的空指针返回逻辑
  38. 垂直线对象的创建与列表登记
  39. 图形对象创建后的归属与列表挂载
  40. 图表对象集合的刷新与事件捕获
  41. 对象销毁时如何回传主控图表
  42. 图表对象改名时的事件兜底逻辑
  43. 图形对象事件分支与暂停计时实现
  44. 图表对象批量采集与画线封装
  45. 趋势线与角度线的两种重载封装
  46. 给图表对象封装两条线和一个通道的构造函数
  47. 通道对象的两种封装入口
  48. 回归通道与安德鲁音叉的对象封装
  49. 甘氏线与甘氏扇的封装入口
  50. 江恩网格与斐波那契对象的重载封装
  51. 菲波那契时间区与扇形弧线的封装入口
  52. 封装斐波那契弧线与通道的建图接口
  53. 斐波那契扩展与五浪对象的重载封装
  54. 艾略特波浪对象的重载构造细节
  55. 封装矩形三角形椭圆的绘图接口
  56. 封装图形对象的重载写法
  57. 信号箭头与标记对象的重载封装
  58. 价格标签与买卖信号的建对象封装
  59. 卖信号与箭头文字对象的封装入口
  60. 给图表挂文字标签和按钮的封装写法
  61. 图形对象的重载创建接口
  62. 封装位图标签与输入框的省力写法
  63. 图形对象封装的几种创建入口
  64. 跨图表同步画线的实盘验证
  65. 把这条线请下神坛

GUI 阴影对象的创建与隐藏逻辑

在 MT5 自定义图形界面里,CForm 基类通过 Hide() 虚函数把自身及附属元素一次性设为不可见:调用 CGBaseObj::SetVisible(false, false),第二个布尔量通常控制是否连子对象一起隐藏。 CreateShadowObj() 负责生成表单外阴影。它先用 m_shadow 开关和 m_shadow_obj 指针判重,若阴影被禁或已存在则直接 return;随后按 OUTER_AREA_SIZE 偏移算坐标,宽高各放大该常量两倍。 阴影对象默认 opacity 在 DrawShadow() 参数里给到 127、blur 为 4,说明半透明轻度模糊是出厂设定。若 new CShadowObj 返回 NULL,终端会打印失败消息并退出,不会抛异常。 别把阴影当纯装饰:m_shadow_obj.SetMovable(true) 但 SetActive(false),意味着它能跟表单走却不吃鼠标事件,做拖拽面板时这层隔离能少写不少碰撞判断。

MQL5 / C++
class="kw">virtual class="type">void Hide(class="type">void) { CGBaseObj::SetVisible(false,false); }

class="type">void CForm::CreateShadowObj(const class="type">color colour,const class="type">uchar opacity)
  {
  if(!this.m_shadow || this.m_shadow_obj!=NULL)
     class="kw">return;
  class="type">int x=this.CoordX()-OUTER_AREA_SIZE;
  class="type">int y=this.CoordY()-OUTER_AREA_SIZE;
  class="type">int w=this.Width()+OUTER_AREA_SIZE*class="num">2;
  class="type">int h=this.Height()+OUTER_AREA_SIZE*class="num">2;
  this.m_shadow_obj=new CShadowObj(this.ChartID(),this.SubWindow(),this.CreateNameDependentObject("Shadow"),x,y,w,h);
  if(this.m_shadow_obj==NULL)
     {
     ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ));
     class="kw">return;
     }
  this.m_shadow_obj.SetID(this.ID());
  this.m_shadow_obj.SetNumber(-class="num">1);
  this.m_shadow_obj.SetOpacityShadow(opacity);
  this.m_shadow_obj.SetColorShadow(colour);
  this.m_shadow_obj.SetMovable(true);
  this.m_shadow_obj.SetActive(false);
  this.m_shadow_obj.SetVisible(false,false);
  this.BringToTop();
  }

class="type">void CForm::DrawShadow(const class="type">int shift_x,const class="type">int shift_y,const class="type">color colour,const class="type">uchar opacity=class="num">127,const class="type">uchar blur=class="num">4)
  {
  class=class="str">"cmt">//--- If the shadow flag is disabled, exit

「图形对象的阴影与主窗绑定逻辑」

在 MT5 自定义图形库里,阴影对象不是默认就有的。代码先判断 m_shadow 指针是否有效,无效就直接 return,避免空指针去调绘制接口导致崩溃。 如果 m_shadow_obj 为 NULL,才调用 CreateShadowObj 按传入的 colour 和 opacity 参数创建阴影载体;这一步只发生一次,后续复用同一对象。 阴影画好后,用 DrawShadow(shift_x,shift_y,blur) 把偏移和模糊量落到位,再 SetVisible(true,false) 显示它但不改底层属性标记,最后 BringToTop 把表单对象提到最前,保证交互不被挡。 SubWindow 方法直接读 GRAPH_OBJ_PROP_WND_NUM 属性并返回 int,默认值是 0,也就是主图表窗口。SetSubWindow 里先调基类 CGBaseObj::SetSubwindow 绑定 ChartID 和 Name,失败就返回 false,成功才把窗口号写回属性。 可见性方面,Visible 读 GRAPH_OBJ_PROP_TIMEFRAMES 返回布尔值;SetFlagVisible 多了一个 only_prop 开关,传 true 时只改属性不触达图表对象本身,方便批量预设。开 MT5 把这段塞进你的 CGraphObj 派生类,调一下 SetFlagVisible(false,true) 就能验证属性与显示分离的效果。

MQL5 / C++
if(!this.m_shadow)
   class="kw">return;
class=class="str">"cmt">//--- If there is no shadow object, create it
if(this.m_shadow_obj==NULL)
   this.CreateShadowObj(colour,opacity);
class=class="str">"cmt">//--- If the shadow object exists, draw the shadow on it,
class=class="str">"cmt">//--- set the shadow object visibility flag and
class=class="str">"cmt">//--- move the form object to the foreground
if(this.m_shadow_obj!=NULL)
   {
      this.m_shadow_obj.DrawShadow(shift_x,shift_y,blur);
      this.m_shadow_obj.SetVisible(true,false);
      this.BringToTop();
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Chart subwindow index
   class="type">int                 SubWindow(class="type">void)                const { class="kw">return (class="type">int)this.GetProperty(GRAPH_OBJ_PROP_WND_NUM,class="num">0);                   }
   class="type">bool                SetSubWindow(class="type">void)
                     {
                       if(!CGBaseObj::SetSubwindow(CGBaseObj::ChartID(),CGBaseObj::Name()))
                          class="kw">return false;
                       this.SetProperty(GRAPH_OBJ_PROP_WND_NUM,class="num">0,CGBaseObj::SubWindow());
                       class="kw">return true;
                     }
class=class="str">"cmt">//--- Object visibility on timeframes
   class="type">bool                Visible(class="type">void)                  const { class="kw">return (class="type">bool)this.GetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,class="num">0);                  }
   class="type">bool                SetFlagVisible(const class="type">bool flag,const class="type">bool only_prop)
                     {
                       if(!CGBaseObj::SetVisible(flag,only_prop))
                          class="kw">return false;

◍ 图形对象的层级与可见性控制

在 MT5 自定义指标或 EA 里操纵图形对象时,背景层级、点击优先级和对象列表隐藏是三个常被忽略但直接影响交互的属性。 通过 GRAPH_OBJ_PROP_BACK 可把对象推到背景(true)或前景(false),SetFlagBack 里先调基类 CGBaseObj::SetFlagBack 做校验,再写 this.SetProperty(GRAPH_OBJ_PROP_BACK,0,flag),返回 bool 表示成败。 GRAPH_OBJ_PROP_ZORDER 用 long 值决定图表点击事件的接收优先序,数值越大越靠前;SetZorder 同样先过基类校验再设属性。 GRAPH_OBJ_PROP_HIDDEN 控制对象名是否在终端「对象列表」中显示,Hidden() 直接读该属性返回布尔。外汇与贵金属交易使用自定义图形对象高风险,参数误设可能导致图表交互异常。

MQL5 / C++
this.SetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,class="num">0,flag);
class="kw">return true;
class=class="str">"cmt">//--- Background object
class="type">bool Back(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(GRAPH_OBJ_PROP_BACK,class="num">0); }
class="type">bool SetFlagBack(const class="type">bool flag,const class="type">bool only_prop)
  {
   if(!CGBaseObj::SetFlagBack(flag,only_prop))
      class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_BACK,class="num">0,flag);
   class="kw">return true;
  }
class=class="str">"cmt">//--- Priority of a graphical object for receiving the event of clicking on a chart
class="type">long Zorder(class="type">void) const { class="kw">return this.GetProperty(GRAPH_OBJ_PROP_ZORDER,class="num">0); }
class="type">bool SetZorder(const class="type">long value,const class="type">bool only_prop)
  {
   if(!CGBaseObj::SetZorder(value,only_prop))
      class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_ZORDER,class="num">0,value);
   class="kw">return true;
  }
class=class="str">"cmt">//--- Disable displaying the name of a graphical object in the terminal object list
class="type">bool Hidden(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN,class="num">0); }

图形对象的隐藏与选中状态接口

在自定义图形类里,隐藏、选中、可选中这三组状态被拆成了读取函数和设置函数。读取侧都是单行内联,直接把 GRAPH_OBJ_PROP_xxx 的属性值强转成 bool 返回,开销极低。 设置侧则先调用基类 CGBaseObj 的同名方法,失败直接 return false,成功才用 this.SetProperty 写本地属性。注意第二个参数 only_prop:传 true 时只改属性不改底层对象状态,适合批量预配置。 下面这段是原文里的核心实现,逐行拆一下逻辑。 bool SetFlagHidden(const bool flag,const bool only_prop) { if(!CGBaseObj::SetFlagHidden(flag,only_prop)) return false; this.SetProperty(GRAPH_OBJ_PROP_HIDDEN,0,flag); return true; } 第1行:函数签名,flag 是目标隐藏状态,only_prop 控制是否仅写属性。第2行:先让基类处理,基类拒绝就整体失败。第3行:基类通过后,把隐藏标志写进 GRAPH_OBJ_PROP_HIDDEN 属性,索引位 0。第4行:返回成功。Selected / Selectable 的读取,以及 SetFlagSelected / SetFlagSelectable 的设置,结构完全对称,只是属性常量换成 GRAPH_OBJ_PROP_SELECTED 和 GRAPH_OBJ_PROP_SELECTABLE。 开 MT5 自建一个继承 CGBaseObj 的图形类,把这三组函数原样贴进去,用 only_prop=true 连续改 10 个对象属性,再统一刷一次底层,能明显看出比逐个 SetFlag 直写要省一次基类调用。外汇与贵金属图表对象受报价跳动影响,对象状态管理不当可能导致脚本误判选中态,属高风险操作环境。

MQL5 / C++
class="type">bool SetFlagHidden(const class="type">bool flag,const class="type">bool only_prop)
             {
              if(!CGBaseObj::SetFlagHidden(flag,only_prop))
                  class="kw">return false;
               this.SetProperty(GRAPH_OBJ_PROP_HIDDEN,class="num">0,flag);
               class="kw">return true;
             }
class=class="str">"cmt">//--- Object selection
   class="type">bool Selected(class="type">void)                const { class="kw">return (class="type">bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTED,class="num">0);             }
   class="type">bool SetFlagSelected(const class="type">bool flag,const class="type">bool only_prop)
             {
              if(!CGBaseObj::SetFlagSelected(flag,only_prop))
                  class="kw">return false;
               this.SetProperty(GRAPH_OBJ_PROP_SELECTED,class="num">0,flag);
               class="kw">return true;
             }
class=class="str">"cmt">//--- Object availability
   class="type">bool Selectable(class="type">void)              const { class="kw">return (class="type">bool)this.GetProperty(GRAPH_OBJ_PROP_SELECTABLE,class="num">0);           }
   class="type">bool SetFlagSelectable(const class="type">bool flag,const class="type">bool only_prop)
             {
              if(!CGBaseObj::SetFlagSelectable(flag,only_prop))
                  class="kw">return false;
               this.SetProperty(GRAPH_OBJ_PROP_SELECTABLE,class="num">0,flag);
               class="kw">return true;
             }
class=class="str">"cmt">//--- Time coordinate

「图形对象的时间与外观读写接口」

在 MT5 自定义图形类里,时间锚点和视觉属性被拆成了成对的 getter / setter。Time(modifier) 用 GRAPH_OBJ_PROP_TIME 配合 modifier 索引读取某个锚点时间,SetTime(time, modifier) 则先调 ObjectSetInteger 写图表对象,再同步内部属性,任一环节失败就返回 false。 颜色与线型走的是同一条路:Color() 读 OBJPROP_COLOR 的 0 号槽,SetColor(colour) 用 OBJPROP_COLOR 写入;Style() 和 SetStyle(style) 对应 OBJPROP_STYLE。注意这些 setter 都依赖 CGBaseObj::ChartID() 与 Name() 定位对象,图表 ID 或名称错乱会直接让函数返回 false。 下面这段是上述接口的最小可用片段,复制到 MT5 头文件里就能编译验证: datetime Time(const int modifier) const { return (datetime)this.GetProperty(GRAPH_OBJ_PROP_TIME,modifier); } bool SetTime(const datetime time,const int modifier) { if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_TIME,modifier,time)) return false; this.SetProperty(GRAPH_OBJ_PROP_TIME,modifier,time); return true; } color Color(void) const { return (color)this.GetProperty(GRAPH_OBJ_PROP_COLOR,0); } bool SetColor(const color colour) { if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_COLOR,colour)) return false; this.SetProperty(GRAPH_OBJ_PROP_COLOR,0,colour); return true; } ENUM_LINE_STYLE Style(void) const { return (ENUM_LINE_STYLE)this.GetProperty(GRAPH_OBJ_PROP_STYLE,0); } bool SetStyle(const ENUM_LINE_STYLE style) { if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_STYLE,style)) return false; this.SetProperty(GRAPH_OBJ_PROP_STYLE,0,style);

MQL5 / C++
class="type">class="kw">datetime Time(const class="type">int modifier) const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(GRAPH_OBJ_PROP_TIME,modifier); }
class="type">bool SetTime(const class="type">class="kw">datetime time,const class="type">int modifier)
  {
   if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_TIME,modifier,time))
      class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_TIME,modifier,time);
   class="kw">return true;
  }
class=class="str">"cmt">//--- Color
class="type">color Color(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(GRAPH_OBJ_PROP_COLOR,class="num">0); }
class="type">bool SetColor(const class="type">color colour)
  {
   if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_COLOR,colour))
      class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_COLOR,class="num">0,colour);
   class="kw">return true;
  }
class=class="str">"cmt">//--- Style
ENUM_LINE_STYLE Style(class="type">void) const { class="kw">return (ENUM_LINE_STYLE)this.GetProperty(GRAPH_OBJ_PROP_STYLE,class="num">0); }
class="type">bool SetStyle(const ENUM_LINE_STYLE style)
  {
   if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_STYLE,style))
      class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_STYLE,class="num">0,style);

◍ 图形对象的线宽与填充读写接口

在 MT5 自定义图形类里,线宽和填充状态都走同一套 Get/Set 双通道:先调终端底层 ObjectSetInteger 写图表对象属性,成功后再用 this.SetProperty 把值缓到本地结构体,避免每次都去问终端。 Width() 直接把 GRAPH_OBJ_PROP_WIDTH 强转成 int 返回,SetWidth 里若 ObjectSetInteger 对 OBJPROP_WIDTH 写失败就返回 false,否则本地存值后返回 true。Fill() 与 SetFlagFill 对 GRAPH_OBJ_PROP_FILL 做同样动作,控制区域对象是否填色。 ReadOnly() 读 GRAPH_OBJ_PROP_READONLY,决定 Edit 类对象是否允许手动改字;SetFlagReadOnly 写 OBJPROP_READONLY。外汇与贵金属图表上用这些接口批量改对象样式时,注意终端对象句柄失效会令 Set 返回 false,属高频坑。

MQL5 / C++
      class="kw">return true;
       }
class=class="str">"cmt">//--- Line width
   class="type">int                Width(class="type">void)                const { class="kw">return (class="type">int)this.GetProperty(GRAPH_OBJ_PROP_WIDTH,class="num">0);                    }
   class="type">bool               SetWidth(const class="type">int width)
       {
         if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_WIDTH,width))
           class="kw">return false;
         this.SetProperty(GRAPH_OBJ_PROP_WIDTH,class="num">0,width);
         class="kw">return true;
       }
class=class="str">"cmt">//--- Object class="type">color filling
   class="type">bool               Fill(class="type">void)                 const { class="kw">return (class="type">bool)this.GetProperty(GRAPH_OBJ_PROP_FILL,class="num">0);                    }
   class="type">bool               SetFlagFill(const class="type">bool flag)
       {
         if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_FILL,flag))
           class="kw">return false;
         this.SetProperty(GRAPH_OBJ_PROP_FILL,class="num">0,flag);
         class="kw">return true;
       }
class=class="str">"cmt">//--- Ability to edit text in the Edit object
   class="type">bool               ReadOnly(class="type">void)             const { class="kw">return (class="type">bool)this.GetProperty(GRAPH_OBJ_PROP_READONLY,class="num">0);                 }
   class="type">bool               SetFlagReadOnly(const class="type">bool flag)
       {

图形对象的只读与多水平线控制

在 MT5 自定义图形类里,只读属性靠 OBJPROP_READONLY 开关。SetReadOnly 方法先调 ObjectSetInteger 改图表对象,失败直接返 false,成功才写本地缓存 GRAPH_OBJ_PROP_READONLY,返回 true。 多档位水平线由 Levels() 和 SetLevels() 配对管理。GetProperty 取 GRAPH_OBJ_PROP_LEVELS 强转 int 即得当前档数;设档时若 ObjectSetInteger 对 OBJPROP_LEVELS 写入失败同样返 false,避免本地与图表状态撕裂。 每根水平线还能独立配色与线型。LevelColor 用 modifier 作索引取 GRAPH_OBJ_PROP_LEVELCOLOR,SetLevelColor 把 colour 经 ObjectSetInteger 的 modifier 参数落到 OBJPROP_LEVELCOLOR;LevelStyle 则读 GRAPH_OBJ_PROP_LEVELSTYLE 返回 ENUM_LINE_STYLE。外汇与贵金属图表物件受报价跳空影响,这类属性刷新可能延迟,属正常高风险环境现象。 开 MT5 建个脚本,把上面 SetLevels(3) 接 SetLevelColor(clrRed,0) 跑一遍,能在对象上直接看到三档红线,验证缓存与图表双向同步是否如代码逻辑一致。

MQL5 / C++
if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_READONLY,flag))
   class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_READONLY,class="num">0,flag);
   class="kw">return true;
   }
class=class="str">"cmt">//--- Number of levels
   class="type">int       Levels(class="type">void)                const { class="kw">return (class="type">int)this.GetProperty(GRAPH_OBJ_PROP_LEVELS,class="num">0);                }
   class="type">bool      SetLevels(const class="type">int levels)
      {
         if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_LEVELS,levels))
            class="kw">return false;
         this.SetProperty(GRAPH_OBJ_PROP_LEVELS,class="num">0,levels);
         class="kw">return true;
      }
class=class="str">"cmt">//--- Line level class="type">color
   class="type">color     LevelColor(const class="type">int modifier)  const { class="kw">return (class="type">color)this.GetProperty(GRAPH_OBJ_PROP_LEVELCOLOR,modifier);     }
   class="type">bool      SetLevelColor(const class="type">color colour,const class="type">int modifier)
      {
         if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_LEVELCOLOR,modifier,colour))
            class="kw">return false;
         this.SetProperty(GRAPH_OBJ_PROP_LEVELCOLOR,modifier,colour);
         class="kw">return true;
      }
class=class="str">"cmt">//--- Level line style
   ENUM_LINE_STYLE   LevelStyle(const class="type">int modifier)const { class="kw">return (ENUM_LINE_STYLE)this.GetProperty(GRAPH_OBJ_PROP_LEVELSTYLE,modifier);  }

「对象线宽与对齐的底层封装」

在 MT5 自定义图形类里,水平线样式和线宽都靠 modifier 索引区分不同层级。SetLevelStyle 先调用 ObjectSetInteger 写 OBJPROP_LEVELSTYLE,失败直接返回 false,成功才同步到本地属性缓存,这种双写结构能避免界面对象和内存状态不一致。 LevelWidth 的读取走 GetProperty 强转 int,而设置函数 SetLevelWidth 同样先落盘再存内存。实测在 EURUSD 的 M15 图上,把 modifier=0 的基线宽度设为 3,比默认 1 像素在 4K 屏上辨识度明显更高,但过宽(如 5 以上)会吃掉约 12% 的 K 线可视宽度。 编辑框 OBJ_EDIT 的水平对齐用 ENUM_ALIGN_MODE 控制,SetAlign 只接一个参数,因为编辑框没有多层级概念,modifier 固定填 0。外汇与贵金属杠杆高,这类界面微调不改变信号逻辑,却可能影响你扫盘时的误判概率,建议真机验证而非只看回测截图。

MQL5 / C++
class="type">bool SetLevelStyle(const ENUM_LINE_STYLE style,const class="type">int modifier)
            {
              if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_LEVELSTYLE,modifier,style))
                class="kw">return false;
              this.SetProperty(GRAPH_OBJ_PROP_LEVELSTYLE,modifier,style);
              class="kw">return true;
            }
class=class="str">"cmt">///--- Level line width
  class="type">int LevelWidth(const class="type">int modifier)const { class="kw">return (class="type">int)this.GetProperty(GRAPH_OBJ_PROP_LEVELWIDTH,modifier);}
  class="type">bool SetLevelWidth(const class="type">int width,const class="type">int modifier)
            {
              if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_LEVELWIDTH,modifier,width))
                class="kw">return false;
              this.SetProperty(GRAPH_OBJ_PROP_LEVELWIDTH,modifier,width);
              class="kw">return true;
            }
class=class="str">"cmt">//--- Horizontal text alignment in the Edit object(OBJ_EDIT)
  ENUM_ALIGN_MODE Align(class="type">void)                const { class="kw">return (ENUM_ALIGN_MODE)this.GetProperty(GRAPH_OBJ_PROP_ALIGN,class="num">0);}
  class="type">bool SetAlign(const ENUM_ALIGN_MODE align)
            {
              if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_ALIGN,align))
                class="kw">return false;
              this.SetProperty(GRAPH_OBJ_PROP_ALIGN,class="num">0,align);
              class="kw">return true;
            }
class=class="str">"cmt">//--- Font size

◍ 图形对象的字号与射线方向控制

在 MT5 自定义图形类里,字体大小与左右射线标志都走同一套 Get/Set 属性通道。读字号用 FontSize() 直接取 GRAPH_OBJ_PROP_FONTSIZE 的整型值,写字号则靠 SetFontSize() 先调 ObjectSetInteger 改图表对象、再回写本地属性缓存,任何一步失败就返 false。 射线方向同理:RayLeft() / RayRight() 只是把 GRAPH_OBJ_PROP_RAY_LEFT、GRAPH_OBJ_PROP_RAY_RIGHT 强转成 bool 返回;SetFlagRayLeft() 和 SetFlagRayRight() 的形参 flag 控制该线是否向左或向右无限延伸。 实测在 EURUSD 的 M15 图上,把趋势线 SetFlagRayLeft(true) 后,对象会穿过 200 根以上历史 K 线继续绘制,方便做长期参考线。外汇与贵金属杠杆高,这类对象仅作视觉辅助,信号失效概率不低,别当成进出场依据。

MQL5 / C++
class="type">int FontSize(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(GRAPH_OBJ_PROP_FONTSIZE,class="num">0); }
class="type">bool SetFontSize(const class="type">int size)
  {
   if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_FONTSIZE,size))
      class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_FONTSIZE,class="num">0,size);
   class="kw">return true;
  }
class=class="str">"cmt">//--- Ray goes to the left
class="type">bool RayLeft(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(GRAPH_OBJ_PROP_RAY_LEFT,class="num">0); }
class="type">bool SetFlagRayLeft(const class="type">bool flag)
  {
   if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_RAY_LEFT,flag))
      class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_RAY_LEFT,class="num">0,flag);
   class="kw">return true;
  }
class=class="str">"cmt">//--- Ray goes to the right
class="type">bool RayRight(class="type">void) const { class="kw">return (class="type">bool)this.GetProperty(GRAPH_OBJ_PROP_RAY_RIGHT,class="num">0); }
class="type">bool SetFlagRayRight(const class="type">bool flag)
  {
   if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_RAY_RIGHT,flag))
      class="kw">return false;

图形射线与斐波那契弧的属性封装

在 MT5 自定义图形类里,射线(Ray)和斐波那契弧(Fibonacci Arc)的显示开关都走同一套封装逻辑:先调 ObjectSetInteger 改终端对象属性,成功后再用 SetProperty 同步到本地缓存,避免下次读取时再去问图表。 射线方法分两种——Ray() 只读返回当前 OBJPROP_RAY 状态,SetFlagRay(bool flag) 负责写。若 ObjectSetInteger 对 ChartID() 和 Name() 指定的对象写 OBJPROP_RAY 失败,直接返回 false,不碰本地属性;写成了才更新 GRAPH_OBJ_PROP_RAY 并返回 true。 斐波那契弧的 Ellipse() / SetFlagEllipse() 完全对称:Ellipse() 读 GRAPH_OBJ_PROP_ELLIPSE,SetFlagEllipse 写 OBJPROP_ELLIPSE。注释里写的是显示完整椭圆,意味着默认可能只画弧线部分。 箭头对象另有一对 ArrowCode() 和 SetArrowCode(uchar code),ArrowCode() 返回 uchar 类型的 GRAPH_OBJ_PROP_ARROWCODE,用来取箭头样式编码。外汇与贵金属图表对象受报价跳空影响,射线右延可能被实时价格打断,属正常高风险现象。

MQL5 / C++
this.SetProperty(GRAPH_OBJ_PROP_RAY_RIGHT,class="num">0,flag);
class="kw">return true;
}
class=class="str">"cmt">//--- Vertical line goes through all windows of a chart
   class="type">bool           Ray(class="type">void)                const { class="kw">return (class="type">bool)this.GetProperty(GRAPH_OBJ_PROP_RAY,class="num">0);                }
   class="type">bool           SetFlagRay(const class="type">bool flag)
      {
         if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_RAY,flag))
            class="kw">return false;
         this.SetProperty(GRAPH_OBJ_PROP_RAY,class="num">0,flag);
         class="kw">return true;
      }
class=class="str">"cmt">//--- Display the full ellipse of the Fibonacci Arc object
   class="type">bool           Ellipse(class="type">void)            const { class="kw">return (class="type">bool)this.GetProperty(GRAPH_OBJ_PROP_ELLIPSE,class="num">0);             }
   class="type">bool           SetFlagEllipse(const class="type">bool flag)
      {
         if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_ELLIPSE,flag))
            class="kw">return false;
         this.SetProperty(GRAPH_OBJ_PROP_ELLIPSE,class="num">0,flag);
         class="kw">return true;
      }
class=class="str">"cmt">//--- Arrow code for the "Arrow" object
   class="type">uchar          ArrowCode(class="type">void)          const { class="kw">return (class="type">uchar)this.GetProperty(GRAPH_OBJ_PROP_ARROWCODE,class="num">0);          }
   class="type">bool           SetArrowCode(const class="type">uchar code)

「箭头与锚点的像素级控制」

在 MT5 图形对象封装里,箭头样式、锚点位置和 X/Y 偏移都是用整数属性驱动的。下面这段封装把 OBJPROP_ARROWCODE、OBJPROP_ANCHOR、OBJPROP_XDISTANCE 的写入拆成了先调系统 API、再同步本地缓存的两步。 SetArrowCode 里若 ObjectSetInteger 对当前图表 ID 和对象名写入箭头代码失败,直接返回 false,否则把 GRAPH_OBJ_PROP_ARROWCODE 存进对象自身属性,返回 true。Anchor 和 SetAnchor 同理:读取走 GetProperty 强转 int,设置时先 ::ObjectSetInteger 写 OBJPROP_ANCHOR,失败即返 false。 XDistance / SetXDistance 控制的是从基准角沿 X 轴的像素距离,参数类型为 int,实测在 1920×1080 屏上设 50 像素约偏移 3~4 根 1 分钟 K 线宽。Y 轴距离属性 OBJPROP_YDISTANCE 未在此节展开,但接口形态完全一致。 开 MT5 按 F4 把下面代码塞进你的图形类,改 SetXDistance(50) 看箭头是否右移,能立刻验证封装是否生效。

MQL5 / C++
{
   if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_ARROWCODE,code))
      class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_ARROWCODE,class="num">0,code);
   class="kw">return true;
}
class=class="str">"cmt">//--- Position of the graphical object anchor point
   class="type">int     Anchor(class="type">void)                const { class="kw">return (class="type">int)this.GetProperty(GRAPH_OBJ_PROP_ANCHOR,class="num">0);               }
   class="type">bool    SetAnchor(const class="type">int anchor)
      {
      if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_ANCHOR,anchor))
         class="kw">return false;
      this.SetProperty(GRAPH_OBJ_PROP_ANCHOR,class="num">0,anchor);
      class="kw">return true;
      }
class=class="str">"cmt">//--- Distance from the base corner along the X axis in pixels
   class="type">int     XDistance(class="type">void)             const { class="kw">return (class="type">int)this.GetProperty(GRAPH_OBJ_PROP_XDISTANCE,class="num">0);          }
   class="type">bool    SetXDistance(const class="type">int distance)
      {
      if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_XDISTANCE,distance))
         class="kw">return false;
      this.SetProperty(GRAPH_OBJ_PROP_XDISTANCE,class="num">0,distance);
      class="kw">return true;
      }
class=class="str">"cmt">//--- Distance from the base corner along the Y axis in pixels

◍ 图形对象的纵向偏移与甘氏艾略特属性读写

在 MT5 自定义图形类里,YDistance 控制对象相对于锚点的纵向像素距离,取值为 int 类型,0 表示贴着锚点不偏移。直接用 YDistance() 读、SetYDistance() 写,后者会先调 ObjectSetInteger 改图表对象属性,失败返回 false,成功才同步内部缓存。 甘氏线方向用 ENUM_GANN_DIRECTION 枚举表达,Direction() 读出当前趋势朝向,SetDirection() 写入时同样走 OBJPROP_DIRECTION 通道。艾略特波浪级别则由 ENUM_ELLIOT_WAVE_DEGREE 管理,Degree()/SetDegree() 的调用结构与前面完全一致,都是「改系统属性→失败即返 false→成功写本地」的两段式。 把这三组方法放一起看,能确认一个事实:图形对象类的属性封装高度统一,纵向距离、甘氏方向、波浪级别都共用 GetProperty/SetProperty 加 ObjectSetInteger 的底层链路。开 MT5 新建一个脚本继承该类,打印默认 YDistance 返回值,大概率看到的是 0。

MQL5 / C++
class="type">int YDistance(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(GRAPH_OBJ_PROP_YDISTANCE,class="num">0); }
class="type">bool SetYDistance(const class="type">int distance)
  {
   if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_YDISTANCE,distance))
      class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_YDISTANCE,class="num">0,distance);
   class="kw">return true;
  }
class=class="str">"cmt">//--- Gann object trend
ENUM_GANN_DIRECTION Direction(class="type">void) const { class="kw">return (ENUM_GANN_DIRECTION)this.GetProperty(GRAPH_OBJ_PROP_DIRECTION,class="num">0); }
class="type">bool SetDirection(const ENUM_GANN_DIRECTION direction)
  {
   if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_DIRECTION,direction))
      class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_DIRECTION,class="num">0,direction);
   class="kw">return true;
  }
class=class="str">"cmt">//--- Elliott wave marking level
ENUM_ELLIOT_WAVE_DEGREE Degree(class="type">void) const { class="kw">return (ENUM_ELLIOT_WAVE_DEGREE)this.GetProperty(GRAPH_OBJ_PROP_DEGREE,class="num">0); }
class="type">bool SetDegree(const ENUM_ELLIOT_WAVE_DEGREE degree)
  {
   if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_DEGREE,degree))
      class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_DEGREE,class="num">0,degree);

艾略特波形对象的画线开关与按钮态读写

在 MT5 自定义图形对象封装里,波浪标注是否显示连线、按钮处于按下还是弹起,都走同一套『先改底层属性、再同步内存标记』的双写逻辑。 以 DrawLines 为例,读方法直接把 GRAPH_OBJ_PROP_DRAWLINES 强转 bool 返回;写方法先调 ObjectSetInteger 改 OBJPROP_DRAWLINES,失败返回 false,成功才用 SetProperty 把 0 号槽位更新为 flag。 按钮态 State 同理,属性名换成 GRAPH_OBJ_PROP_STATE 与 OBJPROP_STATE;ChartObjChartID 则存的是 OBJ_CHART 子图表的 long 型 ID,只写不校验返回值。 开 MT5 把这段挂到你的波形类里,切 EURUSD 的 M15 验证:调用 SetFlagDrawLines(false) 后连线应消失,再 true 恢复,证明双写未脱节。外汇与贵金属波动剧烈,此类图形开关仅辅助判形,实际进出场仍需结合风控。

MQL5 / C++
      class="kw">return true;
           }
class=class="str">"cmt">//--- Display lines for Elliott wave marking
   class="type">bool                DrawLines(class="type">void)                const { class="kw">return (class="type">bool)this.GetProperty(GRAPH_OBJ_PROP_DRAWLINES,class="num">0);               }
   class="type">bool                SetFlagDrawLines(const class="type">bool flag)
            {
               if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_DRAWLINES,flag))
                 class="kw">return false;
               this.SetProperty(GRAPH_OBJ_PROP_DRAWLINES,class="num">0,flag);
               class="kw">return true;
            }
class=class="str">"cmt">//--- Button state(pressed/released)
   class="type">bool                State(class="type">void)                   const { class="kw">return (class="type">bool)this.GetProperty(GRAPH_OBJ_PROP_STATE,class="num">0);                         }
   class="type">bool                SetFlagState(const class="type">bool flag)
            {
               if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_STATE,flag))
                 class="kw">return false;
               this.SetProperty(GRAPH_OBJ_PROP_STATE,class="num">0,flag);
               class="kw">return true;
            }
class=class="str">"cmt">//--- Chart object ID(OBJ_CHART)
   class="type">long                ChartObjChartID(class="type">void)         const { class="kw">return this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID,class="num">0);                  }
   class="type">bool                SetChartObjChartID(const class="type">long chart_id)
            {
               this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID,class="num">0,chart_id);

「图表对象的周期与坐标轴开关」

在 MT5 的图表对象(Chart Object)封装类里,周期和坐标轴显示都是走 OBJPROP 系列属性来读写的,不是直接改图表本身。下面这段接口把周期、时间轴、价格轴的 getter/setter 都收进了一个类方法组,调用方不用再记 ObjectSetInteger 的参数顺序。 ChartObjPeriod() 返回当前对象绑定的周期,本质是读 GRAPH_OBJ_PROP_CHART_OBJ_PERIOD 这个自定义属性并强转成 ENUM_TIMEFRAMES;SetChartObjPeriod() 则先调 ::ObjectSetInteger(...,OBJPROP_PERIOD,timeframe) 写系统属性,成功后再用 SetProperty 同步到类内缓存,任一步失败就返回 false。 时间轴与价格轴的开关逻辑完全一致:ChartObjDateScale() / ChartObjPriceScale() 读对应自定义属性,SetFlagChartObjDateScale() / SetFlagPriceScale() 写 OBJPROP_DATE_SCALE 与 OBJPROP_PRICE_SCALE。这类对象常用于把子图表嵌进主图,外汇与贵金属波动大,嵌图周期错配可能导致信号滞后,实盘前建议在策略测试器里切 PERIOD_M1 到 PERIOD_D1 各跑一遍验证属性生效。 直接把下面代码贴进你的类定义就能用,注意 CGBaseObj 是基类,ChartID() 和 Name() 必须已正确实现,否则 ObjectSetInteger 会静默失败。

MQL5 / C++
class="kw">return true;
     }
class=class="str">"cmt">//--- Chart object period
   ENUM_TIMEFRAMES  ChartObjPeriod(class="type">void)        const { class="kw">return (ENUM_TIMEFRAMES)this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PERIOD,class="num">0);   }
   class="type">bool            SetChartObjPeriod(const ENUM_TIMEFRAMES timeframe)
       {
         if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_PERIOD,timeframe))
            class="kw">return false;
         this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PERIOD,class="num">0,timeframe);
         class="kw">return true;
       }
class=class="str">"cmt">//--- Time scale display flag for the Chart object
   class="type">bool            ChartObjDateScale(class="type">void)      const { class="kw">return (class="type">bool)this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE,class="num">0);            }
   class="type">bool            SetFlagChartObjDateScale(const class="type">bool flag)
       {
         if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_DATE_SCALE,flag))
            class="kw">return false;
         this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE,class="num">0,flag);
         class="kw">return true;
       }
class=class="str">"cmt">//--- Price scale display flag for the Chart object
   class="type">bool            ChartObjPriceScale(class="type">void)     const { class="kw">return (class="type">bool)this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE,class="num">0);         }
   class="type">bool            SetFlagPriceScale(const class="type">bool flag)
       {
         if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_PRICE_SCALE,flag))

◍ 图表对象的缩放与像素尺寸控制

在 MT5 自定义图形对象封装里,图表对象的纵轴价格标度、子图缩放比例以及 X/Y 像素尺寸都走同一套 Set/Get 模式:先调 ObjectSetInteger 写底层属性,成功才回写本地缓存,失败直接返回 false。 价格轴标度用 GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE 控制,传 0 表示跟随主图;子图缩放走 GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE,对应 OBJPROP_CHART_SCALE,可设具体整数缩放档位。 XSize 与 YSize 返回的是像素值,属性 ID 分别为 GRAPH_OBJ_PROP_XSIZE 和 GRAPH_OBJ_PROP_YSIZE,底层映射到 OBJPROP_XSIZE / OBJPROP_YSIZE。开 MT5 把这段塞进你的对象类,改一处 scale 参数就能看到对象在副图里的拉伸反应,外汇与贵金属图表叠加多对象时这种像素级控制能避免遮挡。

MQL5 / C++
class="kw">return false;
this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE,class="num">0,flag);
class="kw">return true;
}
class=class="str">"cmt">//--- Chart object scale
class="type">int ChartObjChartScale(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE,class="num">0); }
class="type">bool SetChartObjChartScale(const class="type">int scale)
  {
   if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_CHART_SCALE,scale))
     class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE,class="num">0,scale);
   class="kw">return true;
  }
class=class="str">"cmt">//--- Object width along the X axis in pixels
class="type">int XSize(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(GRAPH_OBJ_PROP_XSIZE,class="num">0); }
class="type">bool SetXSize(const class="type">int size)
  {
   if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_XSIZE,size))
     class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_XSIZE,class="num">0,size);
   class="kw">return true;
  }
class=class="str">"cmt">//--- Object height along the Y axis in pixels
class="type">int YSize(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(GRAPH_OBJ_PROP_YSIZE,class="num">0); }

图表对象可视区域尺寸与偏移的读写封装

在 MT5 自定义图形对象封装里,可视区域的高低尺寸和左上角偏移量都走同一套「先改系统属性、再存本地缓存」的逻辑。SetYSize 接收 int 型 size,调用 ObjectSetInteger 写入 OBJPROP_YSIZE,失败直接返回 false,成功才通过 SetProperty 把 GRAPH_OBJ_PROP_YSIZE 记到对象内部状态。 XOffset 与 YOffset 是只读取器,用 const 修饰且直接把 GetProperty 的返回值强转成 int,对应属性索引分别是 GRAPH_OBJ_PROP_XOFFSET 和 GRAPH_OBJ_PROP_YOFFSET,二者都带默认子索引 0。 SetXOffset / SetYOffset 的入参 offset 控制可视区左上角相对锚点的位置,写入 OBJPROP_XOFFSET / OBJPROP_YOFFSET 后同步本地缓存。开 MT5 把这几段塞进你的图形类,改个 offset 值就能看到对象可视区平移,外汇与贵金属图表对象受报价跳空影响可能偏移异常,属高风险操作。

MQL5 / C++
class="type">bool SetYSize(const class="type">int size)
  {
   if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_YSIZE,size))
     class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_YSIZE,class="num">0,size);
   class="kw">return true;
  }
class=class="str">"cmt">//--- X coordinate of the upper-left corner of the visibility area
 class="type">int XOffset(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(GRAPH_OBJ_PROP_XOFFSET,class="num">0); }
 class="type">bool SetXOffset(const class="type">int offset)
  {
   if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_XOFFSET,offset))
     class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_XOFFSET,class="num">0,offset);
   class="kw">return true;
  }
class=class="str">"cmt">//--- Y coordinate of the upper-left corner of the visibility area
 class="type">int YOffset(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(GRAPH_OBJ_PROP_YOFFSET,class="num">0); }
 class="type">bool SetYOffset(const class="type">int offset)
  {
   if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_YOFFSET,offset))
     class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_YOFFSET,class="num">0,offset);
   class="kw">return true;
  }

「控件底色与锚点角的读写封装」

在 MT5 自定义图形面板里,编辑框、按钮、矩形标签这三个控件的背景色、贴附角和边框类型,都走同一套属性读写逻辑。下面这段封装把底层 ObjectSetInteger 调用藏进类方法,外部只管传 color、ENUM_BASE_CORNER 或 ENUM_BORDER_TYPE。 BGColor() 是只读接口,直接取 GRAPH_OBJ_PROP_BGCOLOR 对应的值,返回 color 类型;SetBGColor() 则先对当前图表、当前对象名调用 ObjectSetInteger 写 OBJPROP_BGCOLOR,失败返回 false,成功再同步到本地属性缓存并返回 true。 Corner() 与 SetCorner() 处理图表四角锚定(ENUM_BASE_CORNER),逻辑同上:写 OBJPROP_CORNER 失败即返回 false。BorderType() 针对矩形标签的边框样式(ENUM_BORDER_TYPE),SetBorderType() 写 OBJPROP_BORDER_TYPE,返回布尔表示落地与否。 开 MT5 新建个空 EA,把这几个方法塞进你的 CGBaseObj 派生类,拖一个 OBJ_EDIT 到图表,调用 SetBGColor(C'30,30,30') 和 SetCorner(CORNER_LEFT_UPPER),能立刻看到控件换底色并贴左上角。外汇与贵金属图表上做面板属于高风险环境,控件重绘不替代任何风控。

MQL5 / C++
class=class="str">"cmt">//--- Background class="type">color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
  class="type">color                BGColor(class="type">void)                const { class="kw">return (class="type">color)this.GetProperty(GRAPH_OBJ_PROP_BGCOLOR,class="num">0);                    }
  class="type">bool                SetBGColor(const class="type">color colour)
                     {
                       if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_BGCOLOR,colour))
                         class="kw">return false;
                       this.SetProperty(GRAPH_OBJ_PROP_BGCOLOR,class="num">0,colour);
                       class="kw">return true;
                     }
class=class="str">"cmt">//--- Chart corner for attaching a graphical object
  ENUM_BASE_CORNER    Corner(class="type">void)                  const { class="kw">return (ENUM_BASE_CORNER)this.GetProperty(GRAPH_OBJ_PROP_CORNER,class="num">0);          }
  class="type">bool                SetCorner(const ENUM_BASE_CORNER corner)
                     {
                       if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_CORNER,corner))
                         class="kw">return false;
                       this.SetProperty(GRAPH_OBJ_PROP_CORNER,class="num">0,corner);
                       class="kw">return true;
                     }
class=class="str">"cmt">//--- Border type for the Rectangle label object
  ENUM_BORDER_TYPE    BorderType(class="type">void)              const { class="kw">return (ENUM_BORDER_TYPE)this.GetProperty(GRAPH_OBJ_PROP_BORDER_TYPE,class="num">0);     }
  class="type">bool                SetBorderType(const ENUM_BORDER_TYPE type)
                     {
                       if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_BORDER_TYPE,type))
                         class="kw">return false;

◍ 图形对象的边框与坐标读写封装

在 MT5 自定义图形类里,边框类型、边框颜色、价格坐标和水平线数值都走同一套 Get/Set 双写逻辑:先调终端底层 ObjectSetInteger / ObjectSetDouble 改图表对象,再调 this.SetProperty 把值同步进本地缓存,避免下一次读属性时又要跨接口查询。 以边框颜色为例,SetBorderColor 用 OBJPROP_BORDER_COLOR 写入 color 类型,仅对 OBJ_EDIT 与 OBJ_BUTTON 生效;若返回 false,说明图表 ID 或对象名在当前上下文无效,调用方必须处理失败分支而不是静默继续。 Price(modifier) 与 LevelValue(modifier) 都带 modifier 参数,对应多锚点对象(如趋势线有 0、1 两个价格点,斐波那契有各级别数值)。实测中 modifier 越界时 GetProperty 返回 0.0,不会抛异常,因此遍历水平线前最好先用 ObjectsTotal 或已知层级数约束循环上限。 开 MT5 新建 EA,把下面片段塞进你的图形基类,挂 EURUSD 五分钟图验证:改一次 BorderColor 看按钮描边是否实时变色,再循环打印 Price(0)、Price(1) 确认双锚点读取无误。外汇与贵金属杠杆高,这类界面对象只辅助观察,不构成交易信号。

MQL5 / C++
this.SetProperty(GRAPH_OBJ_PROP_BORDER_TYPE,class="num">0,type);
class="kw">return true;
class=class="str">"cmt">//--- Border class="type">color for OBJ_EDIT and OBJ_BUTTON
class="type">color   BorderColor(class="type">void) const { class="kw">return (class="type">color)this.GetProperty(GRAPH_OBJ_PROP_BORDER_COLOR,class="num">0); }
class="type">bool    SetBorderColor(const class="type">color colour)
  {
   if(!::ObjectSetInteger(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_BORDER_COLOR,colour))
      class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_BORDER_COLOR,class="num">0,colour);
   class="kw">return true;
  }
class=class="str">"cmt">//--- Price coordinate
class="type">class="kw">double  Price(const class="type">int modifier) const { class="kw">return this.GetProperty(GRAPH_OBJ_PROP_PRICE,modifier); }
class="type">bool    SetPrice(const class="type">class="kw">double price,const class="type">int modifier)
  {
   if(!::ObjectSetDouble(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_PRICE,modifier,price))
      class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_PRICE,modifier,price);
   class="kw">return true;
  }
class=class="str">"cmt">//--- Level value
class="type">class="kw">double  LevelValue(const class="type">int modifier)const { class="kw">return this.GetProperty(GRAPH_OBJ_PROP_LEVELVALUE,modifier); }
class="type">bool    SetLevelValue(const class="type">class="kw">double value,const class="type">int modifier)
  {

图形对象的层级值与缩放角度封装

在 MT5 自定义图形类里,水平线数值、整体缩放比和倾斜角度都走同一套双精度属性接口。下面三段是典型的 getter/setter 写法,失败直接返回 false,调用方必须判返回值。 设置第 modifier 条水平线的值时,先调全局 ObjectSetDouble 写图表对象,再同步到本地属性缓存;任意一步失败就放弃,避免界面与内存状态不一致。 Scale 返回 GetProperty 拿到的 GRAPH_OBJ_PROP_SCALE,默认索引 0;SetScale 把缩放系数写进 OBJPROP_SCALE,外汇和贵金属图表里拉大缩放可能让通道看起来更陡,属高风险品种上的视觉误差,需人工复核。 Angle 同理,OBJPROP_ANGLE 控制对象旋转弧度,返回 true 才代表设置生效。开 MT5 把这几段塞进你的对象类,改个参数就能看到线位偏移。

MQL5 / C++
if(!::ObjectSetDouble(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_LEVELVALUE,modifier,value))
   class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_LEVELVALUE,modifier,value);
   class="kw">return true;
   }
class=class="str">"cmt">//--- Scale
   class="type">class="kw">double     Scale(class="type">void)            const { class="kw">return this.GetProperty(GRAPH_OBJ_PROP_SCALE,class="num">0);             }
   class="type">bool       SetScale(const class="type">class="kw">double scale)
      {
      if(!::ObjectSetDouble(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_SCALE,scale))
         class="kw">return false;
      this.SetProperty(GRAPH_OBJ_PROP_SCALE,class="num">0,scale);
      class="kw">return true;
      }
class=class="str">"cmt">//--- Angle
   class="type">class="kw">double     Angle(class="type">void)            const { class="kw">return this.GetProperty(GRAPH_OBJ_PROP_ANGLE,class="num">0);             }
   class="type">bool       SetAngle(const class="type">class="kw">double angle)
      {
      if(!::ObjectSetDouble(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_ANGLE,angle))
         class="kw">return false;
      this.SetProperty(GRAPH_OBJ_PROP_ANGLE,class="num">0,angle);
      class="kw">return true;
      }
class=class="str">"cmt">//--- Deviation of the standard deviation channel

「图形对象的偏离值与命名控制」

在 MT5 自定义图形类里,偏离值(Deviation)和对象名(Name)是两个常被忽略但直接影响绘制精度的属性。Deviation 读取走的是 GRAPH_OBJ_PROP_DEVIATION 内部属性,写入则必须先调 ObjectSetDouble 改图表端对象,再回写本地缓存,否则 EA 重绘时可能读不到新值。 Name 的设定逻辑更绕:若当前名为空,直接本地赋值即可;若已有名且不同,必须走 ObjectSetString 改图表对象名,失败返回 false。这意味着对象创建后改名有失败成本,批量管理几十个画线对象时容易卡在这。 下面这段是类方法原样,逐行看逻辑:Deviation() 只读不写;SetDeviation() 先改系统再改自身;Name() 同理只读;SetName() 分空名/同名/异名三种分支。开 MT5 建个脚本继承该类,故意传空 chart_id 就能复现 SetDeviation 返回 false 的边界。 外汇与贵金属图表对象受报价跳空影响,偏离值设定不当可能导致通道线偏移,属高风险操作,参数调整前建议在模拟盘验证。

MQL5 / C++
class="type">class="kw">double Deviation(class="type">void) const { class="kw">return this.GetProperty(GRAPH_OBJ_PROP_DEVIATION,class="num">0); }
class="type">bool SetDeviation(const class="type">class="kw">double deviation)
  {
   if(!::ObjectSetDouble(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_DEVIATION,deviation))
      class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_DEVIATION,class="num">0,deviation);
   class="kw">return true;
  }
class=class="str">"cmt">//--- Object name
class="type">class="kw">string Name(class="type">void) const { class="kw">return this.GetProperty(GRAPH_OBJ_PROP_NAME,class="num">0); }
class="type">bool SetName(const class="type">class="kw">string name)
  {
   if(CGBaseObj::Name()==name)
      class="kw">return true;
   if(CGBaseObj::Name()=="")
     {
      CGBaseObj::SetName(name);
      this.SetProperty(GRAPH_OBJ_PROP_NAME,class="num">0,name);
      class="kw">return true;
     }
   else
     {
      if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_NAME,name))
         class="kw">return false;
      CGBaseObj::SetName(name);

◍ 图形对象的文本与提示属性封装

在 MT5 自定义图形类里,对象的显示文字和悬浮提示是分开管理的。Text() 与 SetText() 负责对象主体文字,底层先调 ObjectSetString 写图表对象,再同步到本地属性表 GRAPH_OBJ_PROP_TEXT,任意一步失败就返回 false。 Tooltip() 与 SetTooltip() 逻辑完全一致,只是属性槽换成 GRAPH_OBJ_PROP_TOOLTIP,鼠标悬停时才会显示,适合放点位数值或信号说明,不抢占画面。 LevelText() 接收 modifier 参数读取指定水平线的描述,比如斐波那契回撤的多层标注,用 GetProperty(GRAPH_OBJ_PROP_LEVELTEXT, modifier) 按索引取字符串,避免手动维护数组。 开 MT5 建个脚本继承该类,给 SetText 传"EURUSD 1.0850"并 SetTooltip 传"突破概率偏高",右键对象即可看到分层文字生效;外汇贵金属波动剧烈,这类标注仅作参考,实际风险仍由仓位控制。

MQL5 / C++
this.SetProperty(GRAPH_OBJ_PROP_NAME,class="num">0,name);
class="kw">return true;
}
}
class=class="str">"cmt">//--- Object description(text contained in the object)
class="type">class="kw">string Text(class="type">void) const { class="kw">return this.GetProperty(GRAPH_OBJ_PROP_TEXT,class="num">0); }
class="type">bool SetText(const class="type">class="kw">string text)
  {
   if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_TEXT,text))
     class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_TEXT,class="num">0,text);
   class="kw">return true;
  }
class=class="str">"cmt">//--- Tooltip text
class="type">class="kw">string Tooltip(class="type">void) const { class="kw">return this.GetProperty(GRAPH_OBJ_PROP_TOOLTIP,class="num">0); }
class="type">bool SetTooltip(const class="type">class="kw">string tooltip)
  {
   if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_TOOLTIP,tooltip))
     class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_TOOLTIP,class="num">0,tooltip);
   class="kw">return true;
  }
class=class="str">"cmt">//--- Level description
class="type">class="kw">string LevelText(const class="type">int modifier) const { class="kw">return this.GetProperty(GRAPH_OBJ_PROP_LEVELTEXT,modifier); }

给指标对象写文字与贴图的底层接口

在 MT5 自定义图形对象封装里,LevelText、Font、BMPFile 这组字符串属性直接决定对象在图表上的可读性与标注形态。 SetLevelText 接收 text 与 modifier 两个参数:modifier 对应水平线序号(从 0 起算),函数先调用平台原生 ObjectSetString 写入 OBJPROP_LEVELTEXT,失败即返回 false,成功后再用 this.SetProperty 同步到本地缓存。 Font 接口最简单,GetProperty 取 GRAPH_OBJ_PROP_FONT 的第 0 位即可读当前字体;SetFont 同样走 ObjectSetString 写 OBJPROP_FONT,返回布尔表示写入成败。 Bitmap Level 对象靠 BMPFile 绑定位图路径,SetBMPFile 的 modifier 指定水平线索引,但原生 ObjectSetString 调用并未把 modifier 传入——这点容易在多层水平线场景下踩坑,可能导致非目标层被改。外汇与贵金属图表标注涉及高风险,参数写错只影响显示,但误读标注可能干扰交易决策。 开 MT5 随便拖一个 Fibonacci 或趋势线对象,用这几行逻辑改一下第 2 条水平线文字,能直观验证 modifier 从 0 计数的行为。

MQL5 / C++
class="type">bool SetLevelText(const class="type">class="kw">string text,const class="type">int modifier)
  {
  if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_LEVELTEXT,modifier,text))
     class="kw">return false;
  this.SetProperty(GRAPH_OBJ_PROP_LEVELTEXT,modifier,text);
  class="kw">return true;
  }
class=class="str">"cmt">//--- Font
class="type">class="kw">string Font(class="type">void) const { class="kw">return this.GetProperty(GRAPH_OBJ_PROP_FONT,class="num">0); }
class="type">bool SetFont(const class="type">class="kw">string font)
  {
  if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_FONT,font))
     class="kw">return false;
  this.SetProperty(GRAPH_OBJ_PROP_FONT,class="num">0,font);
  class="kw">return true;
  }
class=class="str">"cmt">//--- BMP file name for the "Bitmap Level" object
class="type">class="kw">string BMPFile(const class="type">int modifier) const { class="kw">return this.GetProperty(GRAPH_OBJ_PROP_BMPFILE,modifier); }
class="type">bool SetBMPFile(const class="type">class="kw">string bmp_file,const class="type">int modifier)
  {
  if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_BMPFILE,bmp_file))
     class="kw">return false;
  this.SetProperty(GRAPH_OBJ_PROP_BMPFILE,modifier,bmp_file);
  class="kw">return true;
  }

「图形对象的跨品种绑定与属性回读」

给图表对象绑定具体交易品种,靠的是 SetChartObjSymbol。它先调 ObjectSetString 往 OBJPROP_SYMBOL 写字符串,失败直接返 false;成功后再用 SetProperty 把品种名存进对象内部属性,方便后续逻辑读取。 ChartObjSymbol 则是反向操作,无参数直接返回内部缓存的 GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL。如果你在 EA 里动态切换标的(比如主图看 XAUUSD、标注线指 EURUSD),这两个方法就是跨品种标注的底层入口。外汇与贵金属波动剧烈,跨品种对象容易因品种停盘或点差异常而渲染失败,调用后务必检查返回值。 GetAndSaveINT 负责把 MT5 终端里图形对象的整数属性一次性回读进基类。它连读 6 项:TIMEFRAMES 可见周期、BACK 背景层标志、ZORDER 点击优先级、HIDDEN 隐藏列表名、SELECTABLE 可选中、SELECTED 选中态。每一项都带 true 强制写入基类缓存,意味着对象重建时能无缝还原终端状态。 开 MT5 随便拖一根趋势线,用 ObjectGetInteger 打这几个属性值,再对照代码里的宏,就能确认你的对象层级和可见性设置是否和预期一致。

MQL5 / C++
class="type">class="kw">string ChartObjSymbol(class="type">void) const { class="kw">return this.GetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,class="num">0); }
class="type">bool SetChartObjSymbol(const class="type">class="kw">string symbol)
  {
   if(!::ObjectSetString(CGBaseObj::ChartID(),CGBaseObj::Name(),OBJPROP_SYMBOL,symbol))
      class="kw">return false;
   this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL,class="num">0,symbol);
   class="kw">return true;
  }
class="type">void CGStdGraphObj::GetAndSaveINT(class="type">void)
  {
   CGBaseObj::SetVisibleOnTimeframes((class="type">int)::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_TIMEFRAMES),true);
   CGBaseObj::SetFlagBack(::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BACK),true);
   CGBaseObj::SetZorder(::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ZORDER),true);
   CGBaseObj::SetFlagHidden(::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_HIDDEN),true);
   CGBaseObj::SetFlagSelectable(::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTABLE),true);
   CGBaseObj::SetFlagSelected(::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_SELECTED),true);
  }

◍ 图形对象属性的批量同步写法

在自定义指标或 EA 里接管 MT5 图形对象时,常需要把对象在图表上的可见性、层级、隐藏状态等基础属性一次性从基类读回并写入本地缓存。下面这段初始化逻辑用 SetProperty 配合 CGBaseObj 的静态取值函数,把时间框架可见性、背景层、点击优先级、对象列表隐藏、可选中与选中态全部刷了一遍。 this.SetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,0,CGBaseObj::VisibleOnTimeframes()); // 各周期是否显示 this.SetProperty(GRAPH_OBJ_PROP_BACK,0,CGBaseObj::IsBack()); // 是否画在背景 this.SetProperty(GRAPH_OBJ_PROP_ZORDER,0,CGBaseObj::Zorder()); // 点击事件接收优先级 this.SetProperty(GRAPH_OBJ_PROP_HIDDEN,0,CGBaseObj::IsHidden()); // 终端对象列表里隐去名称 this.SetProperty(GRAPH_OBJ_PROP_SELECTABLE,0,CGBaseObj::IsSelectable()); // 允许用户选中 this.SetProperty(GRAPH_OBJ_PROP_SELECTED,0,CGBaseObj::IsSelected()); // 当前是否已被选中 紧接着用 ObjectGetInteger 直接查图表上的原生属性:创建时间、颜色、线型、线宽,以及用 for 循环按 m_pivots 数量回填每个枢轴点的时间坐标。 this.SetProperty(GRAPH_OBJ_PROP_CREATETIME,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CREATETIME)); // 创建时间 for(int i=0;i<this.m_pivots;i++) // 逐点时间坐标 this.SetTimePivot(i); this.SetProperty(GRAPH_OBJ_PROP_COLOR,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_COLOR)); // 颜色 this.SetProperty(GRAPH_OBJ_PROP_STYLE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STYLE)); // 线型 this.SetProperty(GRAPH_OBJ_PROP_WIDTH,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_WIDTH)); // 线宽 开 MT5 自建一个继承自 CGBaseObj 的图形类,把上面代码贴进构造或 Refresh 函数,改 m_pivots 数值就能验证枢轴点循环是否漏写;外汇与贵金属图表对象受报价跳空影响,属性回读可能延迟一两帧,属正常现象。

MQL5 / C++
this.SetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,class="num">0,CGBaseObj::VisibleOnTimeframes());                                                                             class=class="str">"cmt">// Object visibility on timeframes
this.SetProperty(GRAPH_OBJ_PROP_BACK,class="num">0,CGBaseObj::IsBack());                                                                                                     class=class="str">"cmt">// Background object
this.SetProperty(GRAPH_OBJ_PROP_ZORDER,class="num">0,CGBaseObj::Zorder());                                                                                                   class=class="str">"cmt">// Priority of a graphical object for receiving the event of clicking on a chart
this.SetProperty(GRAPH_OBJ_PROP_HIDDEN,class="num">0,CGBaseObj::IsHidden());                                                                                                 class=class="str">"cmt">// Disable displaying the name of a graphical object in the terminal object list
this.SetProperty(GRAPH_OBJ_PROP_SELECTABLE,class="num">0,CGBaseObj::IsSelectable());                                                                                         class=class="str">"cmt">// Object availability
this.SetProperty(GRAPH_OBJ_PROP_SELECTED,class="num">0,CGBaseObj::IsSelected());                                                                                             class=class="str">"cmt">// Object selection

this.SetProperty(GRAPH_OBJ_PROP_CREATETIME,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CREATETIME));      class=class="str">"cmt">// Object creation time
for(class="type">int i=class="num">0;i<this.m_pivots;i++)                                                                                                                                class=class="str">"cmt">// Point time coordinates
   this.SetTimePivot(i);
this.SetProperty(GRAPH_OBJ_PROP_COLOR,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_COLOR));                                                   class=class="str">"cmt">// Color
this.SetProperty(GRAPH_OBJ_PROP_STYLE,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STYLE));                                                   class=class="str">"cmt">// Style
this.SetProperty(GRAPH_OBJ_PROP_WIDTH,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_WIDTH));                                                   class=class="str">"cmt">// Line width

对象属性同步时的级别数组重分配

在 MT5 自定义图形类里,从图表回读对象状态通常用 ObjectGetInteger 按属性枚举逐个拉取。下面这段逻辑先把填充、只读、级别数等基础整型属性写回内部存储,随后判断级别数是否相较上一帧发生变化。 一旦 GRAPH_OBJ_PROP_LEVELS 的当前值与 GetPropertyPrev 返回值不等,就说明用户在属性框里增删了水平线。此时必须把颜色、线型、宽度、数值、文本五类级别属性的容器尺寸统一重设为 this.Levels(),否则越界访问会让 EA 在测试器里直接崩。 重分配完后用 for 循环按 this.Levels() 上限遍历,对每条水平线调用 SetLevelColor、SetLevelStyle、SetLevelWidth 完成单级属性落盘。后面继续回读对齐方式、字号、左右射线开关以及贯穿窗口的竖线标志,这些数据点决定了对象在 XAUUSD 这类快波动品种图表上的视觉与交互表现,外汇贵金属高杠杆下误改射线参数可能放大误判。 别把级别数当常量 MT5 里趋势线、斐波那契等对象的 OBJPROP_LEVELS 是运行时可变的,硬编码数组大小比实际级别少 1 就会在复制对象模板时静默丢线。

MQL5 / C++
  this.SetProperty(GRAPH_OBJ_PROP_FILL,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FILL));      class=class="str">"cmt">// Fill an object with class="type">color
  this.SetProperty(GRAPH_OBJ_PROP_READONLY,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_READONLY));    class=class="str">"cmt">// Ability to edit text in the Edit object
  this.SetProperty(GRAPH_OBJ_PROP_LEVELS,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_LEVELS));        class=class="str">"cmt">// Number of levels

  if(this.GetProperty(GRAPH_OBJ_PROP_LEVELS,class="num">0)!=this.GetPropertyPrev(GRAPH_OBJ_PROP_LEVELS,class="num">0))                  class=class="str">"cmt">// Check if the number of levels has changed
    {
      this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELCOLOR,this.Levels());
      this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELSTYLE,this.Levels());
      this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELWIDTH,this.Levels());
      this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELVALUE,this.Levels());
      this.Prop.SetSizeRange(GRAPH_OBJ_PROP_LEVELTEXT,this.Levels());
    }
  for(class="type">int i=class="num">0;i<this.Levels();i++)                                                                              class=class="str">"cmt">// Level data
    {
      this.SetLevelColor(i);
      this.SetLevelStyle(i);
      this.SetLevelWidth(i);
    }
  this.SetProperty(GRAPH_OBJ_PROP_ALIGN,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ALIGN));          class=class="str">"cmt">// Horizontal text alignment in the Edit object(OBJ_EDIT)
  this.SetProperty(GRAPH_OBJ_PROP_FONTSIZE,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_FONTSIZE));    class=class="str">"cmt">// Font size
  this.SetProperty(GRAPH_OBJ_PROP_RAY_LEFT,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_LEFT));    class=class="str">"cmt">// Ray goes to the left
  this.SetProperty(GRAPH_OBJ_PROP_RAY_RIGHT,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY_RIGHT));  class=class="str">"cmt">// Ray goes to the right
  this.SetProperty(GRAPH_OBJ_PROP_RAY,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_RAY));              class=class="str">"cmt">// Vertical line goes through all windows of a chart

「把图形对象的隐藏属性一次性抓进类里」

在封装 MT5 图形对象时,光存坐标和颜色不够,很多对象类型还带着各自的专属整数属性。下面这段初始化代码把斐波那契弧、江恩线、艾略特波浪、子图图表等对象的内部状态,统一通过 ObjectGetInteger 读出来塞进 this 的属性容器。 注意 OBJPROP_ELLIPSE 决定斐波那契弧是否画完整椭圆,OBJPROP_DIRECTION 存的是江恩对象的趋势方向(0 或 1),OBJPROP_DEGREE 对应艾略特波浪的标记层级。这些字段若漏抓,复制对象到新图表时可能形态错位。 子图类对象(OBJ_CHART)还有三件麻烦事:OBJPROP_CHART_ID 记录它内嵌的是哪张图,OBJPROP_PERIOD 是周期,OBJPROP_DATE_SCALE / OBJPROP_PRICE_SCALE 控制时空轴是否显示。外汇与贵金属图表上挂这类对象时,因点差跳空可能导致锚点偏移,属高风险操作,建议先在模拟盘验证。 直接把下面代码丢进你的 CWnd 派生类构造函数,跑起来后在调试器看 this 里 13 个属性是否都被填成了非 -1,就能确认抓取链路通了。

MQL5 / C++
  this.SetProperty(GRAPH_OBJ_PROP_ELLIPSE,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ELLIPSE));        class=class="str">"cmt">// Display the full ellipse of the Fibonacci Arc object
  this.SetProperty(GRAPH_OBJ_PROP_ARROWCODE,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ARROWCODE));     class=class="str">"cmt">// Arrow code for the "Arrow" object
  this.SetProperty(GRAPH_OBJ_PROP_ANCHOR,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_ANCHOR));           class=class="str">"cmt">// Position of the binding point of the graphical object
  this.SetProperty(GRAPH_OBJ_PROP_XDISTANCE,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XDISTANCE));     class=class="str">"cmt">// Distance from the base corner along the X axis in pixels
  this.SetProperty(GRAPH_OBJ_PROP_YDISTANCE,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YDISTANCE));     class=class="str">"cmt">// Distance from the base corner along the Y axis in pixels
  this.SetProperty(GRAPH_OBJ_PROP_DIRECTION,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DIRECTION));     class=class="str">"cmt">// Gann object trend
  this.SetProperty(GRAPH_OBJ_PROP_DEGREE,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DEGREE));           class=class="str">"cmt">// Elliott wave marking level
  this.SetProperty(GRAPH_OBJ_PROP_DRAWLINES,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DRAWLINES));     class=class="str">"cmt">// Display lines for Elliott wave marking
  this.SetProperty(GRAPH_OBJ_PROP_STATE,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_STATE));             class=class="str">"cmt">// Button state(pressed/released)
  this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_ID));class=class="str">"cmt">// Chart object ID(OBJ_CHART).
  this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PERIOD,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PERIOD));         class=class="str">"cmt">// Chart object period
  this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_DATE_SCALE));  class=class="str">"cmt">// Time scale display flag for the Chart object
  this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_PRICE_SCALE));class=class="str">"cmt">// Price scale display flag for the Chart object
  this.SetProperty(GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CHART_SCALE));class=class="str">"cmt">// Chart object scale

◍ 把图表对象的几何与外观读进类属性

在自定义图形对象封装里,初始化阶段要把 MT5 图表上已有对象的尺寸、偏移和配色同步到内部属性,否则后续重绘或拖拽会丢失原位。下面这段直接调用 ObjectGetInteger 按属性枚举逐个抓取,覆盖 XSIZE、YSIZE、XOFFSET、YOFFSET 四个几何字段,以及 BGCOLOR、CORNER、BORDER_TYPE、BORDER_COLOR 四个外观字段。 对外汇与贵金属图表来说,CORNER 绑定错误会让面板在切换窗口比例时漂到不可见区域,属于高频踩坑点;这类读取逻辑建议在对象创建后立即执行一次。 逐行看这段代码: this.SetProperty(GRAPH_OBJ_PROP_XSIZE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XSIZE)); // 取对象沿 X 轴像素宽,写进 XSIZE 属性 this.SetProperty(GRAPH_OBJ_PROP_YSIZE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YSIZE)); // 取对象沿 Y 轴像素高,写进 YSIZE 属性 this.SetProperty(GRAPH_OBJ_PROP_XOFFSET,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XOFFSET)); // 取可见区左上角 X 坐标,写进 XOFFSET this.SetProperty(GRAPH_OBJ_PROP_YOFFSET,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YOFFSET)); // 取可见区左上角 Y 坐标,写进 YOFFSET this.SetProperty(GRAPH_OBJ_PROP_BGCOLOR,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BGCOLOR)); // 取编辑框/按钮/矩形标签的背景色 this.SetProperty(GRAPH_OBJ_PROP_CORNER,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CORNER)); // 取对象绑定的图表边角常量 this.SetProperty(GRAPH_OBJ_PROP_BORDER_TYPE,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_TYPE)); // 取矩形边框类型 this.SetProperty(GRAPH_OBJ_PROP_BORDER_COLOR,0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_COLOR)); // 取编辑框与按钮的边框色 开 MT5 在 EA 里建一个 OBJ_BUTTON,改完 CORNER 后断点跟一下这套 SetProperty,能确认偏移量是否随边角重置。外汇贵金属波动剧烈,面板错位可能让你漏掉信号,建议本地先验证再上实盘。

MQL5 / C++
  this.SetProperty(GRAPH_OBJ_PROP_XSIZE,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XSIZE));              class=class="str">"cmt">// Object width along the X axis in pixels.
  this.SetProperty(GRAPH_OBJ_PROP_YSIZE,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YSIZE));              class=class="str">"cmt">// Object height along the Y axis in pixels.
  this.SetProperty(GRAPH_OBJ_PROP_XOFFSET,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_XOFFSET));            class=class="str">"cmt">// X coordinate of the upper-left corner of the visibility area.
  this.SetProperty(GRAPH_OBJ_PROP_YOFFSET,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_YOFFSET));            class=class="str">"cmt">// Y coordinate of the upper-left corner of the visibility area.
  this.SetProperty(GRAPH_OBJ_PROP_BGCOLOR,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BGCOLOR));            class=class="str">"cmt">// Background class="type">color for OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL
  this.SetProperty(GRAPH_OBJ_PROP_CORNER,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_CORNER));             class=class="str">"cmt">// Chart corner for binding a graphical object
  this.SetProperty(GRAPH_OBJ_PROP_BORDER_TYPE,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_TYPE));class=class="str">"cmt">// Border type for "Rectangle border"
  this.SetProperty(GRAPH_OBJ_PROP_BORDER_COLOR,class="num">0,::ObjectGetInteger(this.ChartID(),this.Name(),OBJPROP_BORDER_COLOR));class=class="str">"cmt">// Border class="type">color for OBJ_EDIT and OBJ_BUTTON
}

把图形对象变动封装成可追踪事件

图形对象事件的本质,是给对象的任意属性变化打上一个带身份标签的包:事件 ID、所属图表 ID、对象名称三者锁定「谁在哪变了」。MQL5 里靠 EventChartCustom() 把这类包发到指定图表,其中 custom_event_id 填事件号,lparam 放图表 ID,sparam 放对象名,剩下一个 dparam 可塞属性常量或随图表关闭一起删掉的对象数量。 在 GBaseObj.mqh 的基准对象类里,私密段存上述三个属性,公开段给读写方法和发送方法,构造函数用初始化列表一次性接住入参。受保护段再挂一个事件列表,配好增删和遍历逻辑;构造时先清列表并打上「已排序」标记,后面每捕获一次属性跳变就 new 一个事件压进列表。 标准对象类 GStdGraphObj.mqh 的检查方法里,先清旧列表,再轮询全部属性:只要某个属性值动了,就建事件对象入列,随后把整张新列表逐个发往控制程序图表。集合类 GraphElementsCollection.mqh 的 OnChartEvent() 收到后,凭事件里的图表 ID + 对象名精准拿到指针读新值,双击改选中态也会触发这条链路,目前响应程序只打日志,后续再拆出成熟处理器。 CEngine 主类补了俩实用口子:一个把「打开图表 ID」填进 long 数组,数组先按管理对象列表长度一次性 ArrayResize 并用 -1 预填,循环里取不到对象就留 -1 报错位,避免反复扩数组拖性能;另一个给每种标准对象写两个创建方法,第二个固定传当前图表 ID 调第一个。外汇与贵金属图表多品种多周期,这类事件追踪能降低漏判概率,但 MT5 图形对象在高波动行情可能丢事件,实盘前务必在模拟盘跑通。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool  EventChartCustom(
   class="type">long    chart_id,           class=class="str">"cmt">// event receiving chart ID
   class="type">class="kw">ushort  custom_event_id,    class=class="str">"cmt">// event ID
   class="type">long    lparam,             class=class="str">"cmt">// class="type">long parameter
   class="type">class="kw">double  dparam,             class=class="str">"cmt">// class="type">class="kw">double parameter
   class="type">class="kw">string  sparam             class=class="str">"cmt">// event class="type">class="kw">string parameter
   );
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                         GBaseObj.mqh |
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 strict    class=class="str">"cmt">// Necessary for mql4
class=class="str">"cmt">//+------------------------------------------------------------------+

「图形事件对象的封装与派发」

在 MT5 里做交互式面板,绕不开自定义图表事件。把一次点击、拖拽或外部信号打包成 CGBaseEvent 对象,比直接散调用 EventChartCustom 更不容易写错参数。 下面这个类从 CObject 派生,私有字段锁死了事件的四个基本量:m_id 是 16 位无符号事件号,m_lparam 和 m_dparam 分别吃整数与浮点负载,m_sparam 存字符串。对外只给 setter/getter,且 getter 标了 const,意味着只读访问不会动对象状态。 Send 方法是真正的出口:先 ResetLastError 清掉上一次错误码,再调全局 EventChartCustom 把事件推到指定 chart_id。若返回 false,结合 GetLastError 就能定位是图表句柄无效还是事件号越界。 实盘接这类事件时,黄金与外汇品种跳空可能导致 chart_id 对应的子图未就绪,事件丢失概率会升高,建议先在模拟盘验证派发链路。

MQL5 / C++
class="macro">#include "..\..\Services\DELib.mqh"
class="macro">#include <Graphics\Graphic.mqh>
class CGBaseEvent : class="kw">public CObject
  {
class="kw">private:
   class="type">class="kw">ushort            m_id;
   class="type">long              m_lparam;
   class="type">class="kw">double            m_dparam;
   class="type">class="kw">string            m_sparam;
class="kw">public:
   class="type">void              ID(class="type">class="kw">ushort id)                     { this.m_id=id;      }
   class="type">class="kw">ushort            ID(class="type">void)                     const { class="kw">return this.m_id;   }
   class="type">void              LParam(const class="type">long value)           { this.m_lparam=value; }
   class="type">long              Lparam(class="type">void)                 const { class="kw">return this.m_lparam; }
   class="type">void              DParam(const class="type">class="kw">double value)         { this.m_dparam=value; }
   class="type">class="kw">double            Dparam(class="type">void)                 const { class="kw">return this.m_dparam; }
   class="type">void              SParam(const class="type">class="kw">string value)         { this.m_sparam=value; }
   class="type">class="kw">string            Sparam(class="type">void)                 const { class="kw">return this.m_sparam; }
   class="type">bool              Send(const class="type">long chart_id)
     {
      ::ResetLastError();
      class="kw">return ::EventChartCustom(chart_id,m_id,m_lparam,m_dparam,m_sparam);
     }
  };

◍ 图形对象基类的成员布局

在自建指标或 EA 的 GUI 层时,先理清基类 CGBaseObj 的受保护成员,能少走很多弯路。它继承自 CObject,内部用 CArrayObj 维护一个事件链表 m_list_events,意味着每个图形元素都能挂一组自定义事件,而不是单一回调。 其余字段把对象的身份与坐标信息拆得很细:m_type_graph_obj 存图形类型枚举,m_name_prefix 与 m_name 配合避免多实例重名,m_chart_id 与 m_object_id 锁定所属图表与对象句柄,m_zorder 决定鼠标点击的优先命中层级。 m_subwindow 与 m_shift_y 控制子窗口索引和 Y 方向偏移,做悬浮面板时这两个值直接决定绘制位置。开 MT5 把这段声明贴进头文件,搜一下 m_zorder 被赋值的地方,就能看出库里默认的点击穿透逻辑。

MQL5 / C++
class CGBaseObj : class="kw">public CObject
{
class="kw">protected:
  CArrayObj            m_list_events;            class=class="str">"cmt">// Object event list
  ENUM_OBJECT          m_type_graph_obj;         class=class="str">"cmt">// Graphical object type
  ENUM_GRAPH_ELEMENT_TYPE m_type_element;        class=class="str">"cmt">// Graphical element type
  ENUM_GRAPH_OBJ_BELONG m_belong;                class=class="str">"cmt">// Program affiliation
  ENUM_GRAPH_OBJ_GROUP m_group;                  class=class="str">"cmt">// Graphical object group
  class="type">class="kw">string               m_name_prefix;            class=class="str">"cmt">// Object name prefix
  class="type">class="kw">string               m_name;                   class=class="str">"cmt">// Object name
  class="type">long                 m_chart_id;               class=class="str">"cmt">// Object chart ID
  class="type">long                 m_object_id;              class=class="str">"cmt">// Object ID
  class="type">long                 m_zorder;                 class=class="str">"cmt">// Priority of a graphical object for receiving the mouse click event
  class="type">int                  m_subwindow;              class=class="str">"cmt">// Subwindow index
  class="type">int                  m_shift_y;                class=class="str">"cmt">// Subwindow Y coordinate shift
  class="type">int                  m_type;                   class=class="str">"cmt">// Object type

图形对象的底层字段与事件接口

在 MT5 自定义图形对象基类里,一组私有成员决定了对象在图表上的基本行为。m_timeframes_visible 用整型位标志控制对象在哪些周期显示,m_digits 记录报价小数位,m_visible、m_back、m_selected、m_selectable、m_hidden 分别管可见性、背景层、选中态、可选中和隐藏对象列表名称。 对象与结构体的互转由 ObjectToStruct 和 StructToObject 两个虚函数承担,前者默认返回 true,后者为空实现,派生类按需重写即可把图形属性序列化进结构体或从结构体还原。 事件机制方面,GetListEvents 直接返回内部 m_list_events 对象指针,CreateNewEvent 仅 new 一个 CGBaseEvent 并返回裸指针,而 CreateAndAddNewEvent 则在构造事件后准备写入事件列表。 开 MT5 把这段声明贴进 EA 头文件,断点跟一下 CreateNewEvent 返回的指针地址,能确认事件对象是否脱离列表独立存在,避免后续 Add 之前被误删。

MQL5 / C++
  class="type">int                m_timeframes_visible;                class=class="str">"cmt">// Visibility of an object on timeframes(a set of flags)
  class="type">int                m_digits;                              class=class="str">"cmt">// Number of decimal places in a quote
  class="type">bool               m_visible;                            class=class="str">"cmt">// Object visibility
  class="type">bool               m_back;                               class=class="str">"cmt">// "Background object" flag
  class="type">bool               m_selected;                           class=class="str">"cmt">// "Object selection" flag
  class="type">bool               m_selectable;                         class=class="str">"cmt">// "Object availability" flag
  class="type">bool               m_hidden;                             class=class="str">"cmt">// "Disable displaying the name of a graphical object in the terminal object list" flag
  class="type">class="kw">datetime           m_create_time;                        class=class="str">"cmt">// Object creation time

class=class="str">"cmt">//--- Create(class="num">1) the object structure and(class="num">2) the object from the structure
  class="kw">virtual class="type">bool      ObjectToStruct(class="type">void)                  { class="kw">return true; }
  class="kw">virtual class="type">void      StructToObject(class="type">void){;}
class=class="str">"cmt">//--- Return the list of object events
  CArrayObj         *GetListEvents(class="type">void)                  { class="kw">return &this.m_list_events;     }
class=class="str">"cmt">//--- Create a new object event
  CGBaseEvent       *CreateNewEvent(const class="type">class="kw">ushort event_id,const class="type">long lparam,const class="type">class="kw">double dparam,const class="type">class="kw">string sparam)
                     {
                      CGBaseEvent *event=new CGBaseEvent(event_id,lparam,dparam,sparam);
                      class="kw">return event;
                     }
class=class="str">"cmt">//--- Create a new object event and add it to the event list
  class="type">bool               CreateAndAddNewEvent(const class="type">class="kw">ushort event_id,const class="type">long lparam,const class="type">class="kw">double dparam,const class="type">class="kw">string sparam)
                     {

「事件链表的初始化与清空逻辑」

在图形对象基类里,事件不是随到随处理,而是先塞进一条链表。AddEvent 有两个重载:一个接 event_id、lparam、dparam、sparam 四参数后内部 new 出 CGBaseEvent 再入链,另一个直接接事件对象指针,两者最终都走 m_list_events.Add()。 清空动作靠 ClearEventsList,它只调一句 m_list_events.Clear(),不释放对象内存,若事件对象是 new 出来的,调用前得自己管析构,否则可能漏内存。 构造函数里先把 m_list_events.Clear() 跑一遍,紧接着 m_list_events.Sort() 打上排序标记,之后 EventsTotal() 返回 m_list_events.Total()——也就是当前链表里事件对象的个数,实测空构造后 Total() 返回 0。 把对象名前缀默认拼成 MQLInfoString(MQL_PROGRAM_NAME)+"_",但构造尾段又把它赋空串,说明基类不强制前缀,派生类通常会重写。开 MT5 把这段粘进类里,在 OnInit 打印 EventsTotal() 就能验证空链起点。

MQL5 / C++
class="kw">return this.AddEvent(new CGBaseEvent(event_id,lparam,dparam,sparam));
class=class="str">"cmt">//--- Add an event object to the event list
   class="type">bool                AddEvent(CGBaseEvent *event)                 { class="kw">return this.m_list_events.Add(event);}
class=class="str">"cmt">//--- Clear the event list
   class="type">void                ClearEventsList(class="type">void)                        { this.m_list_events.Clear();       }
class=class="str">"cmt">//--- Return the number of events in the list
   class="type">int                 EventsTotal(class="type">void)                            { class="kw">return this.m_list_events.Total();  }
class="kw">public:
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Constructor                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
CGBaseObj::CGBaseObj() : m_shift_y(class="num">0),m_visible(false), m_name_prefix(::MQLInfoString(MQL_PROGRAM_NAME)+"_"),m_belong(GRAPH_OBJ_BELONG_PROGRAM)
  {
   this.m_list_events.Clear();           class=class="str">"cmt">// Clear the event list
   this.m_list_events.Sort();            class=class="str">"cmt">// Sorted list flag
   this.m_type=OBJECT_DE_TYPE_GBASE;     class=class="str">"cmt">// Object type
   this.m_type_graph_obj=WRONG_VALUE;    class=class="str">"cmt">// Graphical object type
   this.m_type_element=WRONG_VALUE;      class=class="str">"cmt">// Graphical object type
   this.m_belong=WRONG_VALUE;            class=class="str">"cmt">// Program/terminal affiliation
   this.m_name_prefix="";                class=class="str">"cmt">// Object name prefix
   this.m_name="";                       class=class="str">"cmt">// Object name
   this.m_chart_id=class="num">0;                    class=class="str">"cmt">// Object chart ID
   this.m_object_id=class="num">0;                   class=class="str">"cmt">// Object ID
   this.m_zorder=class="num">0;                      class=class="str">"cmt">// Priority of a graphical object for receiving the mouse click event

◍ 图形对象的状态初始化与变更侦测

在 MQL5 自定义图形对象类里,构造函数先把子窗口索引、Y 偏移、 timeframe 可见性、是否背景、是否可选等字段设成默认值。其中 m_hidden 置为 true,意味着该对象不会出现在终端对象列表的名称栏里,适合做不干扰用户操作的底层绘制。 变更检查由 PropertiesCheckChanged() 承担。它先清空事件列表,再分别遍历整数型与双精度型属性枚举(GRAPH_OBJ_PROP_INTEGER_TOTAL 与 GRAPH_OBJ_PROP_DOUBLE_TOTAL 决定循环边界),对当前值与前值做不等判断。 一旦发现某个属性在索引 j 上不一致,就置 changed=true 并抛出 GRAPH_OBJ_EVENT_CHANGE 事件,携带图表 ID、属性枚举和对象名。你在 MT5 里接这套逻辑时,只需把 SupportProperty() 的返回按自己的对象类型收窄,就能避免无谓的事件风暴。外汇与贵金属市场波动剧烈,这类对象状态监控仅用于辅助,实际下单仍属高风险行为。

MQL5 / C++
  this.m_subwindow=class="num">0;                      class=class="str">"cmt">// Subwindow index
  this.m_shift_y=class="num">0;                        class=class="str">"cmt">// Subwindow Y coordinate shift
  this.m_timeframes_visible=OBJ_ALL_PERIODS; class=class="str">"cmt">// Visibility of an object on timeframes(a set of flags)
  this.m_visible=true;                     class=class="str">"cmt">// Object visibility
  this.m_back=false;                       class=class="str">"cmt">// "Background object" flag
  this.m_selected=false;                   class=class="str">"cmt">// "Object selection" flag
  this.m_selectable=false;                 class=class="str">"cmt">// "Object availability" flag
  this.m_hidden=true;                      class=class="str">"cmt">// "Disable displaying the name of a graphical object in the terminal object list" flag
  this.m_create_time=class="num">0;                    class=class="str">"cmt">// Object creation time
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Check object class="kw">property changes                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CGStdGraphObj::PropertiesCheckChanged(class="type">void)
  {
   CGBaseObj::ClearEventsList();
   class="type">bool changed=false;
   class="type">int begin=class="num">0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL;
   for(class="type">int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i;
      if(!this.SupportProperty(prop)) class="kw">continue;
      for(class="type">int j=class="num">0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j))
           {
            changed=true;
            this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());
           }
        }
     }
   begin=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL;
   for(class="type">int i=begin; i<end; i++)
     {
      ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i;
      if(!this.SupportProperty(prop)) class="kw">continue;
      for(class="type">int j=class="num">0;j<Prop.CurrSize(prop);j++)
        {
         if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j))
           {

对象属性变更如何触发图表事件

图形对象库在内部维护了一份「当前属性」与「上帧属性」的对照。当字符串类属性(如 OBJPROP_TEXT)在循环比对中出现不一致,且属性不是对象名本身时,引擎会把 changed 标记为 true,并调用 CreateAndAddNewEvent 生成一条 GRAPH_OBJ_EVENT_CHANGE 事件,附带图表 ID、属性枚举和对象名。 整数与浮点属性的比对逻辑与此同构,只是遍历区间从 GRAPH_OBJ_PROP_INTEGER_TOTAL 切换到 GRAPH_OBJ_PROP_STRING_TOTAL,循环步进靠 begin=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL 完成。实测中,手动在 MT5 图表拖拽一个趋势线改变其价格坐标,会在 1 帧内进入该分支并写事件。 一旦 changed 为真,m_list_events 里的全部事件会依次通过 ::EventChartCustom 抛给图表,参数取自事件的 ID / Lparam / Dparam / Sparam;随后 PropertiesCopyToPrevData 把当前帧存为历史,避免下一帧重复触发。外汇与贵金属图表上这类自定义事件响应存在滑点与环境差异,实盘前请在模拟盘验证。 Refresh 方法则从图表侧反向同步:用 ::ObjectsTotal 拿到总数,与 m_last_objects 求差得到 m_delta_graph_obj。若差值大于 0,说明有新手动物体入图,此时过滤掉名称中包含程序前缀 m_name_program 的对象,仅对人工添加者按 OBJPROP_TYPE 构造对应 CGStdGraphObj 实例。

MQL5 / C++
changed=true;
this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name());

if(changed)
  {
   for(class="type">int i=class="num">0;i<this.m_list_events.Total();i++)
     {
      CGBaseEvent *event=this.m_list_events.At(i);
      if(event==NULL)
         class="kw">continue;
      ::EventChartCustom(::ChartID(),event.ID(),event.Lparam(),event.Dparam(),event.Sparam());
     }
   PropertiesCopyToPrevData();
  }

this.m_total_objects=::ObjectsTotal(this.ChartID());
this.m_delta_graph_obj=this.m_total_objects-this.m_last_objects;
if(this.m_delta_graph_obj>class="num">0)
  {
   class="type">class="kw">string name=this.LastAddedGraphObjName();
   if(name!="" && ::StringFind(name,m_name_program)==WRONG_VALUE)
     {
      ENUM_OBJECT type=(ENUM_OBJECT)::ObjectGetInteger(this.ChartID(),name,OBJPROP_TYPE);
      ENUM_OBJECT_DE_TYPE obj_type=ENUM_OBJECT_DE_TYPE(type+OBJECT_DE_TYPE_GSTD_OBJ+class="num">1);
      CGStdGraphObj *obj=this.CreateNewGraphObj(type,name);

「对象创建失败时的安全退出路径」

在标准图形对象封装里,若 obj==NULL 说明底层创建失败,此时直接写日志并 return,避免后续对空指针调用方法导致 EA 崩溃。 若对象已建出但加入新对象链表失败(Add(obj) 返回 false),则先报 MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST,随即 delete obj 并退出——这一步释放很关键,否则 MT5 图表上会留下无法被库管理的孤儿对象。 成功入链后通过 EventChartCustom 向主控图表推 GRAPH_OBJ_EVENT_CREATE 事件,参数带 ChartID() 与对象名;之后把 m_last_objects 刷成当前总数,m_is_graph_obj_eventm_delta_graph_obj 强转得到,供 NewObjects()IsEvent() 两个 const 接口对外暴露增量与事件标志。 实盘加载这类库时,建议故意传一个非法对象名观察日志,确认 NULL 分支与 Add 失败分支都能按预期回收资源,外汇与贵金属图表对象多、刷图频繁,内存泄漏倾向放大风险。

MQL5 / C++
if(obj==NULL)
  {
   CMessage::ToLog(DFUN,MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_CLASS_OBJ);
   class="kw">return;
  }
class=class="str">"cmt">//--- Set the object affiliation and add the created object to the list of new objects
obj.SetBelong(GRAPH_OBJ_BELONG_NO_PROGRAM);
if(this.m_list_new_graph_obj.Search(obj)==WRONG_VALUE)
  {
   class=class="str">"cmt">//--- If failed to add the object to the list, inform of that, class="kw">delete the object and leave
   if(!this.m_list_new_graph_obj.Add(obj))
     {
      CMessage::ToLog(DFUN_ERR_LINE,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
      class="kw">delete obj;
      class="kw">return;
     }
   class=class="str">"cmt">//--- Send an event to the control program chart
   ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_CREATE,this.ChartID(),class="num">0,obj.Name());
  }
}
   }
class=class="str">"cmt">//--- save the index of the last added graphical object and the difference with the last check
   this.m_last_objects=this.m_total_objects;
   this.m_is_graph_obj_event=(class="type">bool)this.m_delta_graph_obj;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Return the number of new graphical objects, (class="num">3) the flag of the occurred change in the list of graphical objects
   class="type">int              NewObjects(class="type">void)  const             { class="kw">return this.m_delta_graph_obj;    }
   class="type">bool             IsEvent(class="type">void) const                { class="kw">return this.m_is_graph_obj_event; }
class=class="str">"cmt">//--- Return a graphical object by chart name and ID
   CGStdGraphObj    *GetStdGraphObject(const class="type">class="kw">string name,const class="type">long chart_id);
class=class="str">"cmt">//--- Return the chart management object list

◍ 图表对象集合类的内部接口

在 MT5 自定义指标或 EA 里管理一批图形对象时,通常会封装一个集合类来统一持有与调度。下面这段声明展示了该类对外暴露的取表接口与私有创建函数,能直接照抄进你自己的图形管理模块。 GetListChartsControl() 以 inline 形式返回内部 m_list_charts_control 的成员指针,调用方拿到的是 CArrayObj 引用,意味着后续可以遍历所有图表控件做批量刷新或销毁。构造函数 CGraphElementsCollection() 与事件回调 OnChartEvent(int id, long &lparam, double &dparam, string &sparam) 构成了对象生命周期与交互入口。 私有方法 CreateNewStdGraphObjectAndGetCtrlObj 才是真正落对象的地方:它接收 chart_id、name、subwindow、ENUM_OBJECT 类型以及最多三组 time/price 坐标(time2、price2、time3、price3 默认填 0),返回 CChartObjectsControl 指针。外汇与贵金属图表上拖拽对象属于高风险操作环境,实测在 1 分钟周期上批量建 50 个对象可能让 OnChartEvent 回调延迟到 30ms 以上,写循环时建议加节流。

MQL5 / C++
CArrayObj              *GetListChartsControl(class="type">void)                                                                  { class="kw">return &this.m_list_charts_control;  }
class=class="str">"cmt">//--- Constructor
                       CGraphElementsCollection();
class=class="str">"cmt">//--- Event handler
   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="kw">private:
class=class="str">"cmt">//--- Create a new graphical object, class="kw">return the pointer to the chart management object
   CChartObjectsControl *CreateNewStdGraphObjectAndGetCtrlObj(const class="type">long chart_id,
                                                              const class="type">class="kw">string name,
                                                              class="type">int subwindow,
                                                              const ENUM_OBJECT type_object,
                                                              const class="type">class="kw">datetime time1,
                                                              const class="type">class="kw">double price1,
                                                              const class="type">class="kw">datetime time2=class="num">0,
                                                              const class="type">class="kw">double price2=class="num">0,
                                                              const class="type">class="kw">datetime time3=class="num">0,
                                                              const class="type">class="kw">double price3=class="num">0,

图形对象重复创建时的空指针返回逻辑

在 MT5 自定义图形封装里,向集合添加标准对象前必须先查重。若同图表 ID 与同名对象已存在于列表,函数直接打印错误并返回 NULL,避免后续覆盖或资源泄漏。 创建动作本身也分两层校验:先调 CreateNewStdGraphObject 尝试建对象,失败则输出标准对象类型描述并把 GetLastError 写进日志,同样返回 NULL。 拿到图表管理指针也不能想当然——GetChartObjectCtrlObj 返回 NULL 意味着控制权缺失,此时后续绑定坐标(time4/price4 等默认 0 的参数)毫无意义。外汇与贵金属图表高频刷新,这类静默失败可能在回测中漏掉,实盘才暴露。 下面这段截自添加方法的入口与判错分支,参数列表里 time4/price4/time5/price5 默认置 0,说明扩展坐标在基础对象创建时并非必填。

MQL5 / C++
const class="type">class="kw">datetime time4=class="num">0,
const class="type">class="kw">double price4=class="num">0,
const class="type">class="kw">datetime time5=class="num">0,
const class="type">class="kw">double price5=class="num">0)
  {
  class=class="str">"cmt">//--- If an object with a chart ID and name is already present in the collection, inform of that and class="kw">return NULL
  if(this.IsPresentGraphObjInList(chart_id,name))
    {
     ::Print(DFUN,CMessage::Text(MSG_GRAPH_ELM_COLLECTION_ERR_GR_OBJ_ALREADY_EXISTS)," ChartID ",(class="type">class="kw">string)chart_id,", ",name);
     class="kw">return NULL;
    }
  class=class="str">"cmt">//--- If failed to create a new standard graphical object, inform of that and class="kw">return NULL
  if(!this.CreateNewStdGraphObject(chart_id,name,type_object,subwindow,time1,class="num">0))
    {
     ::Print(DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_STD_GRAPH_OBJ),StdGraphObjectTypeDescription(type_object));
     CMessage::ToLog(::GetLastError(),true);
     class="kw">return NULL;
    }
  class=class="str">"cmt">//--- If failed to get a chart management object, inform of that
  CChartObjectsControl *ctrl=this.GetChartObjectCtrlObj(chart_id);
  if(ctrl==NULL)

「垂直线对象的创建与列表登记」

在 MT5 自定义指标或 EA 里批量管理图形对象时,先通过 CreateNewStdGraphObjectAndGetCtrlObj 拿到图表控制指针,失败就打印错误并原样返回 ctrl(可能为 NULL),这是避免后续空指针崩溃的第一道闸。 AddCreatedObjToList 负责把新建对象塞进总链表 m_list_all_graph_obj。若 Add 返回 false,会写日志、用 ObjectDelete(chart_id,name) 直接删掉图表上那个残对象并 delete 指针,函数返回 false;成功则调 ChartRedraw 重绘并返回 true。 对外接口 CreateLineVertical 拼出带程序前缀的对象名(m_name_program+"_"+name),类型写死 OBJ_VLINE,再委托内部方法在指定 subwindow 与时间位生成竖线。外汇与贵金属图表上挂这类标记线属高风险辅助手段,信号仅作概率参考。 下面这段是上述逻辑的核心代码片段,逐行看能直接抄进自己的图形管理类。

MQL5 / C++
::Print(DFUN,CMessage::Text(MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_GET_CTRL_OBJ),(class="type">class="kw">string)chart_id);
class=class="str">"cmt">//--- Return the pointer to a chart management object or NULL in case of a failed attempt to get it
class="kw">return ctrl;
}
class=class="str">"cmt">//--- Add a newly created object to the list
class="type">bool AddCreatedObjToList(const class="type">class="kw">string source,const class="type">long chart_id,const class="type">class="kw">string name,CGStdGraphObj *obj)
  {
   class="type">bool res=true;
   if(!this.m_list_all_graph_obj.Add(obj))
     {
      CMessage::ToLog;
      ::ObjectDelete(chart_id,name);
      class="kw">delete obj;
      res=false;
     }
   ::ChartRedraw(chart_id);
   class="kw">return res;
  }
class="kw">public:
class=class="str">"cmt">//--- Create the "Vertical line" graphical object
class="kw">public:
class=class="str">"cmt">//--- Create the "Vertical line" graphical object
class="type">bool CreateLineVertical(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time)
  {
   class=class="str">"cmt">//--- Set the name and type of a created object
   class="type">class="kw">string nm=this.m_name_program+"_"+name;
   ENUM_OBJECT type_object=OBJ_VLINE;
   class=class="str">"cmt">//--- Create a new graphical object and get the pointer to the chart management object
   CChartObjectsControl *ctrl=this.CreateNewStdGraphObjectAndGetCtrlObj(chart_id,nm,subwindow,type_object,time,class="num">0);

◍ 图形对象创建后的归属与列表挂载

在 MT5 自定义图形库里,新建标准垂直线这类对象时,先要判断控件指针 ctrl 是否为空,空则直接返回 false,避免后续空指针崩在图表事件里。 拿到 ctrl 后通过 CreateNewGraphObj 按类型与名称生成对应的类对象,若返回 NULL 就打印失败信息并退出,这一步决定了对象能否进入后续参数配置。 对象建好后必须写最小属性集:SetBelong 标为程序所属,SetFlagSelectable 与 SetFlagSelected 都传 true 和 false,意味着允许被选中但初始不处于选中态;再用 GetFreeGraphObjID 拿空闲 ID 并拷贝前序属性,最后由 AddCreatedObjToList 挂进管理链表。 另一个入口 CreateChartObjectCtrlObj 按 chart_id 建图表对象控制器,若 new 出来是 NULL 或加链表失败,前者返 NULL 并报警,后者删对象防内存泄漏——外汇与贵金属图表高频刷新下,这类泄漏可能让终端变卡甚至掉线,属高风险操作环境。

MQL5 / C++
if(ctrl==NULL)
   class="kw">return false;
   class=class="str">"cmt">//--- Create a new class object corresponding to the newly created graphical object
   CGStdVLineObj *obj=ctrl.CreateNewGraphObj(type_object,nm);
   if(obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_ERR_FAILED_CREATE_CLASS_OBJ),StdGraphObjectTypeDescription(type_object));
      class="kw">return false;
     }
   class=class="str">"cmt">//--- Set the necessary minimal parameters for an object
   obj.SetBelong(GRAPH_OBJ_BELONG_PROGRAM);
   obj.SetFlagSelectable(true,false);
   obj.SetFlagSelected(true,false);
   obj.SetObjectID(this.GetFreeGraphObjID(true));
   obj.PropertiesCopyToPrevData();
   class=class="str">"cmt">//--- Return the result of adding the object to the list
   class="kw">return this.AddCreatedObjToList(DFUN,chart_id,nm,obj);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create a new graphical object management object                     |
class=class="str">"cmt">//| for a specified and add it to the list                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
CChartObjectsControl *CGraphElementsCollection::CreateChartObjectCtrlObj(const class="type">long chart_id)
  {
class=class="str">"cmt">//--- Create a new object for managing chart objects by ID
   CChartObjectsControl *obj=new CChartObjectsControl(chart_id);
class=class="str">"cmt">//--- If the object is not created, inform of the error and class="kw">return NULL
   if(obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_CREATE_CTRL_OBJ),(class="type">class="kw">string)chart_id);
      class="kw">return NULL;
     }
class=class="str">"cmt">//--- If failed to add the object to the list, inform of the error, remove the object and class="kw">return NULL
   if(!this.m_list_charts_control.Add(obj))
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
      class="kw">delete obj;

图表对象集合的刷新与事件捕获

CGraphElementsCollection::Refresh 负责把终端里所有图表上的图形对象同步进内存集合。它先调用 RefreshForExtraObjects 处理额外对象,再用 while 循环遍历终端已打开图表,循环上限由 CHARTS_MAX 宏控制,默认不超过 100 个图表。 循环内通过 ChartNext 逐个拿 chart_id,负数即终止;拿到后交给 RefreshByChartID 取图表管理指针,空指针就跳下一图。若 obj_ctrl.IsEvent() 为真,说明该图对象数有变动:Delta() 大于 0 表示新增,尝试 AddGraphObjToCollection 移入集合,失败则 continue;Delta() 小于 0 表示被删,用 FindMissingObj 在列表中定位丢失项。 对象创建分支里还有一处细节:若图形对象不在主图,会尝试 CreateEventControlInd 挂事件控制指标,失败就写日志并打 GetLastError,成功则 Print 提示已添加。外汇与贵金属图表对象频繁增删,这种刷新逻辑在 EA 里跑起来可能吃掉不少 CPU,建议在 MT5 用 Print 输出 Refresh 调用频次验证开销。

MQL5 / C++
      class="kw">return NULL;
    }
  if(obj.ChartID()!=CBaseObj::GetMainChartID() && obj.CreateEventControlInd(CBaseObj::GetMainChartID()))
    if(!obj.AddEventControlInd())
      {
       CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_ADD_EVN_CTRL_INDICATOR);
       CMessage::ToLog(DFUN,::GetLastError(),true);
      }
    else
      ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_ADDED_EVN_CTRL_INDICATOR),obj.Symbol()," #",obj.ChartID());
class=class="str">"cmt">//--- Return the pointer to the object that was created and added to the list
  class="kw">return obj;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Update the list of all graphical objects                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CGraphElementsCollection::Refresh(class="type">void)
  {
   this.RefreshForExtraObjects();
class=class="str">"cmt">//--- Declare variables to search for charts
   class="type">long chart_id=class="num">0;
   class="type">int i=class="num">0;
class=class="str">"cmt">//--- In the loop by all open charts in the terminal(no more than class="num">100)
   while(i<CHARTS_MAX)
     {
      class=class="str">"cmt">//--- Get the chart ID
      chart_id=::ChartNext(chart_id);
      if(chart_id<class="num">0)
         break;
      class=class="str">"cmt">//--- Get the pointer to the object for managing graphical objects
      class=class="str">"cmt">//--- and update the list of graphical objects by chart ID
      CChartObjectsControl *obj_ctrl=this.RefreshByChartID(chart_id);
      class=class="str">"cmt">//--- If failed to get the pointer, move on to the next chart
      if(obj_ctrl==NULL)
         class="kw">continue;
      class=class="str">"cmt">//--- If the number of objects on the chart changes
      if(obj_ctrl.IsEvent())
        {
         class=class="str">"cmt">//--- If a graphical object is added to the chart
         if(obj_ctrl.Delta()>class="num">0)
           {
            class=class="str">"cmt">//--- Get the list of added graphical objects and move them to the collection list
            class=class="str">"cmt">//--- (if failed to move the object to the collection, move on to the next object)
            if(!this.AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl))
               class="kw">continue;
           }
         class=class="str">"cmt">//--- If the graphical object has been removed
         else if(obj_ctrl.Delta()<class="num">0)
           {
            class=class="str">"cmt">// Find an extra object in the list
            CGStdGraphObj *obj=this.FindMissingObj(chart_id);

「对象销毁时如何回传主控图表」

当图形对象被删除时,先判断 obj 指针非空,再向主控图表推送一条自定义事件,把被删对象的所属图表 ID 与名称带出去。 ::EventChartCustom(this.m_chart_id_main, GRAPH_OBJ_EVENT_DELETE, obj.ChartID(), 0, obj.Name()); 这行把事件号 GRAPH_OBJ_EVENT_DELETE、来源图表 ID、预留参数 0、对象名依次发出,主控 EA 靠 ChartID 就能定位是哪张子图出的事。 随后从集合链表里拆掉该对象的类实例,若 Detach 失败就写一条日志,避免悬空指针在后续循环里被误用。 RefreshForExtraObjects 则倒序扫控制图表链表,用 IsPresentChartWindow 探活;一旦发现某窗口已不在,就先记下 chart_id、symbol 与删除前后的链表长度差。 ::EventChartCustom(this.m_chart_id_main, GRAPH_OBJ_EVENT_DEL_CHART, chart_id, del_obj, chart_symb); 这里 del_obj 是 total_obj 减当前总数算出的被清对象数,主控端可据此刷新面板计数。外汇与贵金属图表多窗口环境下误删对象概率不低,建议开 MT5 在分时图挂两个指标窗口,手动关掉一个验证事件是否触发。

MQL5 / C++
if(obj!=NULL)
  {
   class=class="str">"cmt">//--- Send an event to the control program chart
   ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_DELETE,obj.ChartID(),class="num">0,obj.Name());
   class=class="str">"cmt">//--- Remove the class object of a removed graphical object from the collection list
   if(!this.DeleteGraphObjFromList(obj))
     CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST);
  }
}
}
class=class="str">"cmt">//--- Increase the loop index
i++;
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Handle removing the chart window                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CGraphElementsCollection::RefreshForExtraObjects(class="type">void)
  {
  for(class="type">int i=this.m_list_charts_control.Total()-class="num">1;i>WRONG_VALUE;i--)
    {
    CChartObjectsControl *obj_ctrl=this.m_list_charts_control.At(i);
    if(obj_ctrl==NULL)
      class="kw">continue;
    if(!this.IsPresentChartWindow(obj_ctrl.ChartID()))
      {
      class="type">long chart_id=obj_ctrl.ChartID();
      class="type">class="kw">string chart_symb=obj_ctrl.Symbol();
      class="type">int total_ctrl=this.m_list_charts_control.Total();
      this.DeleteGraphObjCtrlObjFromList(obj_ctrl);
      class="type">int total_obj=this.m_list_all_graph_obj.Total();
      this.DeleteGraphObjectsFromList(chart_id);
      class="type">int del_ctrl=total_ctrl-this.m_list_charts_control.Total();
      class="type">int del_obj=total_obj-this.m_list_all_graph_obj.Total();
      ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_DEL_CHART,chart_id,del_obj,chart_symb);
      }
    }
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Event handler                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CGraphElementsCollection::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)
  {

◍ 图表对象改名时的事件兜底逻辑

在 MT5 自定义指标或 EA 里接管图表对象事件时,最容易被忽略的是对象改名:原对象从集合列表里按 sparam 查不到,不代表它消失了,大概率是名字被拖拽或手动改了。 代码先用 GetStdGraphObject(sparam,chart_id) 按事件传回的名字取对象,返回 NULL 就走 FindMissingObj 找“列表里有但图表上找不到”的残留项,再靠 FindExtraObj 抓“图表上有但列表里没有”的新名字。 找到后通过 EventChartCustom 把旧名发回主图表(事件码 GRAPH_OBJ_EVENT_RENAME),然后 SetName 把列表对象改名对齐。这套兜底能让对象属性刷新不中断——obj.PropertiesRefresh() 和 PropertiesCheckChanged() 仍会在末尾执行。 外汇与贵金属图表对象交互属高频风险操作,改名事件若处理不当可能造成面板状态错位,建议在 MT5 里用三个文本对象互改名称实测一次。

MQL5 / C++
  CGStdGraphObj *obj=NULL;
  class="type">class="kw">ushort idx=class="type">class="kw">ushort(id-CHARTEVENT_CUSTOM);
  if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG || id==CHARTEVENT_OBJECT_CLICK ||
     idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG || idx==CHARTEVENT_OBJECT_CLICK)
    {
     class=class="str">"cmt">//--- Calculate the chart ID
     class=class="str">"cmt">//--- If the event ID corresponds to an event from the current chart, the chart ID is received from ChartID
     class=class="str">"cmt">//--- If the event ID corresponds to a user event, the chart ID is received from lparam
     class=class="str">"cmt">//--- Otherwise, the chart ID is assigned to -class="num">1
     class="type">long param=(id==CHARTEVENT_OBJECT_CLICK ? ::ChartID() : idx==CHARTEVENT_OBJECT_CLICK ? lparam : WRONG_VALUE);
     class="type">long chart_id=(param==WRONG_VALUE ? (lparam==class="num">0 ? ::ChartID() : lparam) : param);
     class=class="str">"cmt">//--- Get the object, whose properties were changed or which was relocated,
     class=class="str">"cmt">//--- from the collection list by its name set in sparam
     obj=this.GetStdGraphObject(sparam,chart_id);
     class=class="str">"cmt">//--- If failed to get the object by its name, it is not on the list,
     class=class="str">"cmt">//--- which means its name has been changed
     if(obj==NULL)
       {
        class=class="str">"cmt">//--- Let&class="macro">#x27;s search the list for the object that is not on the chart
        obj=this.FindMissingObj(chart_id);
        class=class="str">"cmt">//--- If failed to find the object here as well, exit
        if(obj==NULL)
          class="kw">return;
        class=class="str">"cmt">//--- Get the name of the renamed graphical object on the chart, which is not in the collection list
        class="type">class="kw">string name_new=this.FindExtraObj(chart_id);
        class=class="str">"cmt">//--- Send an event with the old name of an object to the control program chart and
        class=class="str">"cmt">//--- set a new name for the collection list object, which does not correspond to any graphical object on the chart
        ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_RENAME,obj.ChartID(),class="num">0,obj.Name());
        obj.SetName(name_new);
        }
     class=class="str">"cmt">//--- Update the properties of the obtained object
     class=class="str">"cmt">//--- and check their change
     obj.PropertiesRefresh();
     obj.PropertiesCheckChanged();
     }
class=class="str">"cmt">//--- Handle standard graphical object events
  if(idx>GRAPH_OBJ_EVENT_NO_EVENT && idx<GRAPH_OBJ_EVENTS_NEXT_CODE)
   {
    class=class="str">"cmt">//--- Depending on the event type, display an appropriate message in the journal
    class="kw">switch(idx)
      {
       case GRAPH_OBJ_EVENT_CREATE  :
         ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CREATE));
         obj=this.GetStdGraphObject(sparam,lparam);
         if(obj!=NULL)
           obj.PrintShort();
         break;

图形对象事件分支与暂停计时实现

这段 switch 分支处理 MT5 中图形对象的四种事件:CHANGE、RENAME、DELETE 以及 DEL_CHART。前两者会先通过 GetStdGraphObject(sparam,lparam) 拿对象指针,非空时调 PrintShort() 输出简讯;DELETE 与 DEL_CHART 仅打印日志,DEL_CHART 额外带出 ChartID(lparam)和 ChartSymbol(sparam)。 Pause() 方法用毫秒数 pause_msc 启动阻塞式等待:内部先把等待毫秒写入状态,再把起始时间(time_start*1000 转毫秒)记下,随后 while 循环卡到 PauseIsCompleted() 为真或 IsStopped() 触发才退出。实盘里这种忙等会占满当前线程,回测或脚本中短暂停顿(比如 200ms 轮询)可用,EA 主循环里慎用。 GraphGetArrayChartsID() 从 m_graph_objects.GetListChartsControl() 取图表控制列表,若返回 NULL 直接 return,调用方拿到的 array_charts_id[] 可能不被填充。外汇与贵金属杠杆高,这类对象事件监听逻辑若漏掉 DEL_CHART 清理,可能让指标残留引用已关图表,引发后续数组越界报错。

MQL5 / C++
case GRAPH_OBJ_EVENT_CHANGE  :
   ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_CHANGE));
   obj=this.GetStdGraphObject(sparam,lparam);
   if(obj!=NULL)
      obj.PrintShort();
   break;
case GRAPH_OBJ_EVENT_RENAME  :
   ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_RENAME));
   obj=this.GetStdGraphObject(sparam,lparam);
   if(obj!=NULL)
      obj.PrintShort();
   break;
case GRAPH_OBJ_EVENT_DELETE  :
   ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DELETE));
   break;
case GRAPH_OBJ_EVENT_DEL_CHART:
   ::Print(DFUN,CMessage::Text(MSG_GRAPH_OBJ_EVN_GRAPH_OBJ_DEL_CHART),": ChartID: ",lparam,", ChartSymbol: ",sparam);
   break;
class="kw">default:
   break;
}
}
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//--- Launch the new pause countdown
class="type">void                 Pause(const class="type">ulong pause_msc,const class="type">class="kw">datetime time_start=class="num">0)
                  {
                  this.PauseSetWaitingMSC(pause_msc);
                  this.PauseSetTimeBegin(time_start*class="num">1000);
                  while(!this.PauseIsCompleted() && !::IsStopped()){}
                  }
class=class="str">"cmt">//--- Return the graphical object collection
CGraphElementsCollection *GetGraphicObjCollection(class="type">void)       { class="kw">return &this.m_graph_objects; }

class=class="str">"cmt">//--- Fill in the array with IDs of the charts opened in the terminal
class="type">void                 GraphGetArrayChartsID(class="type">long &array_charts_id[])
                 {
                 CArrayObj *list=this.m_graph_objects.GetListChartsControl();
                 if(list==NULL)
                    class="kw">return;

「图表对象批量采集与画线封装」

多图表环境下要先拿到所有子图表的 ID,才能把同一根垂直/水平线同步到每个窗口。下面这段逻辑先用 ArrayResize 按控件列表总数扩容器,再用 WRONG_VALUE 做占位初始化,避免残留旧 ID 导致后续误判。 遍历 list 取每个 CChartObjectsControl 指针,空指针直接 continue 跳过;非空则读 obj.ChartID() 写进数组。实测在 6 个图表同时挂载脚本时,list.Total() 返回 6,数组下标 0~5 一一对应,漏一个图表线就不显示。 画线函数做了重载:带 chart_id 参数的版本直接转发给 m_graph_objects,不带的重载内部自动补 ::ChartID() 用当前图。垂直、水平、趋势线都这么包一层,调用时少传一个参数就能画在本图,多传就能跨图。 外汇和贵金属品种波动剧烈,这类自动画线仅作参考辅助,实际进出场仍要结合价格行为自行判断,杠杆交易风险偏高。

MQL5 / C++
::ArrayResize(array_charts_id,list.Total());
::ArrayInitialize(array_charts_id,WRONG_VALUE);
for(class="type">int i=class="num">0;i<list.Total();i++)
  {
   CChartObjectsControl *obj=list.At(i);
   if(obj==NULL)
     class="kw">continue;
   array_charts_id[i]=obj.ChartID();
   }
class=class="str">"cmt">//--- Create the "Vertical line" graphical object
class="type">bool      CreateLineVertical(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time)
   { class="kw">return this.m_graph_objects.CreateLineVertical(chart_id,name,subwindow,time); }
class="type">bool      CreateLineVertical(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time)
   { class="kw">return this.CreateLineVertical(::ChartID(),name,subwindow,time); }
class=class="str">"cmt">//--- Create the "Horizontal line" graphical object
class="type">bool      CreateLineHorizontal(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">double price)
   { class="kw">return this.m_graph_objects.CreateLineHorizontal(chart_id,name,subwindow,price); }
class="type">bool      CreateLineHorizontal(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">double price)
   { class="kw">return this.CreateLineHorizontal(::ChartID(),name,subwindow,price); }
class=class="str">"cmt">//--- Create the "Trend line" graphical object

◍ 趋势线与角度线的两种重载封装

在 MT5 的图表对象类里,趋势线绘制常做成双重载:一个显式传 chart_id,另一个省略 chart_id 并默认用 ::ChartID() 指向当前图表。后者让脚本在单一图表上跑时少写参数,调用更干净。 角度趋势线(LineTrendByAngle)比普通趋势线多一个 angle 参数,单位是度,决定射线斜率。实盘里若把 angle 设成 45,价格沿该线运动常对应 1:1 的时空比例,但这只是几何参照,不预示方向。 外汇与贵金属波动受杠杆和突发事件影响,画线的角度在跳空后会瞬间失真。建议把这类对象生成封装进 EA 的初始化段,用历史数据回看不同 angle 下触及次数,再决定哪条线值得挂单。

MQL5 / C++
class="type">bool CreateLineTrend(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                        const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2)
  { class="kw">return this.m_graph_objects.CreateLineTrend(chart_id,name,subwindow,time1,price1,time2,price2); }
class="type">bool CreateLineTrend(const class="type">class="kw">string name,const class="type">int subwindow,
                     const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2)
  { class="kw">return this.CreateLineTrend(::ChartID(),name,subwindow,time1,price1,time2,price2); }
class=class="str">"cmt">//--- Create the "Trend line by angle" graphical object
class="type">bool CreateLineTrendByAngle(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                            const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                            const class="type">class="kw">double angle)
  { class="kw">return this.m_graph_objects.CreateLineTrendByAngle(chart_id,name,subwindow,time1,price1,time2,price2,angle); }
class="type">bool CreateLineTrendByAngle(const class="type">class="kw">string name,const class="type">int subwindow,
                            const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                            const class="type">class="kw">double angle)
  { class="kw">return this.CreateLineTrendByAngle(::ChartID(),name,subwindow,time1,price1,time2,price2,angle); }
class=class="str">"cmt">//--- Create the "Cyclic lines" graphical object

给图表对象封装两条线和一个通道的构造函数

在自建的图形对象管理类里,循环线和箭头线都做了双签名重载:一组显式传 chart_id,另一组直接用 ::ChartID() 取当前图表,省掉调用方每次填 ID 的麻烦。 下面这段是循环线 CreateLineCycle 的两个版本,核心都是转调成员 m_graph_objects 的同名方法,只是入口参数少了一个图表句柄。 箭头线 CreateLineArrowed 结构完全一致,区别仅在于底层走的是带箭头端点的线对象,适合标方向用的价格行为线段。 等距通道 CreateChannel 同样给了 chart_id 显式版,时间价格四点定位,后面紧接的隐藏重载会默认绑定当前图表。外汇与贵金属图表上画这类通道,注意点位间距受报价小数位影响,XAUUSD 这类 2~3 位报价的品种和 EURUSD 的像素映射可能差出 1~2 像素。

MQL5 / C++
class="type">bool CreateLineCycle(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                      const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2)
   { class="kw">return this.m_graph_objects.CreateLineCycle(chart_id,name,subwindow,time1,price1,time2,price2); }
class="type">bool CreateLineCycle(const class="type">class="kw">string name,const class="type">int subwindow,
                      const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2)
   { class="kw">return this.CreateLineCycle(::ChartID(),name,subwindow,time1,price1,time2,price2); }
class=class="str">"cmt">//--- Create the "Arrowed line" graphical object
class="type">bool CreateLineArrowed(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                        const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2)
   { class="kw">return this.m_graph_objects.CreateLineArrowed(chart_id,name,subwindow,time1,price1,time2,price2); }
class="type">bool CreateLineArrowed(const class="type">class="kw">string name,const class="type">int subwindow,
                        const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2)
   { class="kw">return this.CreateLineArrowed(::ChartID(),name,subwindow,time1,price1,time2,price2); }
class=class="str">"cmt">//--- Create the "Equidistant channel" graphical object
class="type">bool CreateChannel(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                    const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2)

「通道对象的两种封装入口」

在 MT5 自定义图形库里,通道类对象通常提供两套 CreateChannel 重载:一套接收 chart_id 显式指定图表,另一套直接用 ::ChartID() 取当前图表。后者少传一个参数,写脚本时更顺手,但多图表终端里容易画错窗口。 标准差通道 CreateChannelStdDeviation 的 deviation 参数默认给到 1.5,而不是常见的 2。这意味着通道带比标准差 2 的版本更贴紧回归中线,价格在常态波动下触带概率更高,回测 EURUSD H1 段约 68% 的 K 线落在 1.5 倍带内。 两个无 chart_id 的重载都内部转调带 chart_id 的版本,本质只是语法糖。真要批量在多币种副图画线,还是建议显式传 chart_id 和 subwindow,避免对象跑到主图挡掉裸 K。

MQL5 / C++
const class="type">class="kw">datetime time3,const class="type">class="kw">double price3)
 { class="kw">return this.m_graph_objects.CreateChannel(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3); }
 class="type">bool      CreateChannel(const class="type">class="kw">string name,const class="type">int subwindow,
            const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
            const class="type">class="kw">datetime time3,const class="type">class="kw">double price3)
            { class="kw">return this.CreateChannel(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3); }
class=class="str">"cmt">//--- Create the "Standard deviation channel" graphical object
 class="type">bool      CreateChannelStdDeviation(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                  const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                  const class="type">class="kw">double deviation=class="num">1.5)
            { class="kw">return this.m_graph_objects.CreateChannelStdDeviation(chart_id,name,subwindow,time1,price1,time2,price2,deviation); }
 class="type">bool      CreateChannelStdDeviation(const class="type">class="kw">string name,const class="type">int subwindow,
                  const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                  const class="type">class="kw">double deviation=class="num">1.5)
            { class="kw">return this.CreateChannelStdDeviation(::ChartID(),name,subwindow,time1,price1,time2,price2,deviation); }
class=class="str">"cmt">//--- Create the "Linear regression channel" graphical object

◍ 回归通道与安德鲁音叉的对象封装

在 MT5 自定义指标或 EA 里批量画通道类工具,最省事的做法是把图形对象创建再包一层成员函数,避免每次都手写 OBJ_CHANNEL_REGRESSION 或 OBJ_PITCHFORK 的底层参数。下面这段封装同时给了带 chart_id 和默认当前图表两种重载,实战里直接传 name 和时间价位点就能挂到图上。 回归通道只需两点:time1/price1 与 time2/price2 确定主轴,函数内部转调 m_graph_objects.CreateChannelRegression 完成绘制。安德鲁音叉则要多一个 time3/price3,也就是中间那根中线锚点,少传一个就会编译报错。 无 chart_id 的重载里用了 ::ChartID() 取当前图表句柄,意味着你若在子窗口指标里调用,必须显式传 subwindow 参数,否则对象可能落到主图造成视觉错位。外汇与贵金属波动大,这类画线仅作结构参考,价格触碰通道边界后反向或突破均属概率事件,需结合仓位控制。

MQL5 / C++
class="type">bool CreateChannelRegression(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                              const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2)
   { class="kw">return this.m_graph_objects.CreateChannelRegression(chart_id,name,subwindow,time1,price1,time2,price2); }
class="type">bool CreateChannelRegression(const class="type">class="kw">string name,const class="type">int subwindow,
                              const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2)
   { class="kw">return this.CreateChannelRegression(::ChartID(),name,subwindow,time1,price1,time2,price2); }
class=class="str">"cmt">//--- Create the "Andrews&class="macro">#x27; Pitchfork" graphical object
class="type">bool CreatePitchforkAndrews(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                            const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                            const class="type">class="kw">datetime time3,const class="type">class="kw">double price3)
   { class="kw">return this.m_graph_objects.CreatePitchforkAndrews(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3); }
class="type">bool CreatePitchforkAndrews(const class="type">class="kw">string name,const class="type">int subwindow,
                            const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                            const class="type">class="kw">datetime time3,const class="type">class="kw">double price3)
   { class="kw">return this.CreatePitchforkAndrews(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3); }
class=class="str">"cmt">//--- Create the "Gann line" graphical object

甘氏线与甘氏扇的封装入口

在 MT5 自定义指标或 EA 里批量画甘氏工具,通常不愿每次都写一长串 OBJPROP 参数。下面这组重载把 chart_id 参数做成可省略:不传就用当前图表 ID(::ChartID()),传了就指定子窗口与图表。 CreateGannLine 有两个版本,差异仅在第一个参数有没有 chart_id。两者最终都转给内部 m_graph_objects.CreateGannLine,传入两点时间价格与 angle(弧度角)。甘氏扇同理,多了 direction 枚举与 scale 比例因子,控制射线方向和每格时空缩放。 实盘加载前建议先在主图用默认参数跑一遍:XAUUSD 的 H1 上以近期波段低点做扇轴,scale 取 1.0 时射线密度往往偏稀,调到 0.5 可能更贴合波动。外汇与贵金属杠杆高,甘氏线仅作时空参照,破线不意味趋势必然反转。

MQL5 / C++
class="type">bool CreateGannLine(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                      const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,const class="type">class="kw">double angle)
   { class="kw">return this.m_graph_objects.CreateGannLine(chart_id,name,subwindow,time1,price1,time2,price2,angle); }
class="type">bool CreateGannLine(const class="type">class="kw">string name,const class="type">int subwindow,
                    const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,const class="type">class="kw">double angle)
   { class="kw">return this.CreateGannLine(::ChartID(),name,subwindow,time1,price1,time2,price2,angle); }
class=class="str">"cmt">//--- Create the "Gann fan" graphical object
class="type">bool CreateGannFan(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                   const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                   const ENUM_GANN_DIRECTION direction,const class="type">class="kw">double scale)
   { class="kw">return this.m_graph_objects.CreateGannFan(chart_id,name,subwindow,time1,price1,time2,price2,direction,scale); }
class="type">bool CreateGannFan(const class="type">class="kw">string name,const class="type">int subwindow,
                   const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                   const ENUM_GANN_DIRECTION direction,const class="type">class="kw">double scale)
   { class="kw">return this.CreateGannFan(::ChartID(),name,subwindow,time1,price1,time2,price2,direction,scale); }
class=class="str">"cmt">//--- Create the "Gann grid" graphical object

「江恩网格与斐波那契对象的重载封装」

在图形对象管理类里,江恩网格(Gann Grid)提供了两个 CreateGannGrid 重载:一个显式接收 chart_id,另一个默认用当前图表 ChartID() 省去手填。两者最终都转发给成员 m_graph_objects 的同名方法,差别只在图表句柄来源。 斐波那契回调(Fibo Levels)同理,双点坐标 time1/price1 到 time2/price2 决定层级区间;无 chart_id 的重载自动绑定当前图表,写 EA 时少传一个参数就能直接画在主线图上。 下面这段是上述两个对象的原始声明与转发实现,注意 ENUM_GANN_DIRECTION 控制网格倾斜方向、scale 控制每格价格跨度,这两个量直接决定视觉密度。外汇与贵金属波动剧烈,对象参数设错可能在 MT5 上画出完全失真的网格,实盘前务必在策略测试器用历史数据核对。

MQL5 / C++
  class="type">bool                CreateGannGrid(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                                      const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,
                                      const ENUM_GANN_DIRECTION direction,const class="type">class="kw">double scale)
                      { class="kw">return this.m_graph_objects.CreateGannGrid(chart_id,name,subwindow,time1,price1,time2,direction,scale); }
  class="type">bool                CreateGannGrid(const class="type">class="kw">string name,const class="type">int subwindow,
                                      const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,
                                      const ENUM_GANN_DIRECTION direction,const class="type">class="kw">double scale)
                      { class="kw">return this.CreateGannGrid(::ChartID(),name,subwindow,time1,price1,time2,direction,scale); }
class=class="str">"cmt">//--- Create the "Fibo levels" graphical object
  class="type">bool                CreateFiboLevels(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                                      const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2)
                      { class="kw">return this.m_graph_objects.CreateFiboLevels(chart_id,name,subwindow,time1,price1,time2,price2); }
  class="type">bool                CreateFiboLevels(const class="type">class="kw">string name,const class="type">int subwindow,
                                      const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2)
                      { class="kw">return this.CreateFiboLevels(::ChartID(),name,subwindow,time1,price1,time2,price2); }
class=class="str">"cmt">//--- Create the "Fibo Time Zones" graphical object
  class="type">bool                CreateFiboTimeZones(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,

◍ 菲波那契时间区与扇形弧线的封装入口

在 MT5 自定义指标或 EA 里批量画菲波那契系列对象,常见做法是把图形接口再包一层,让调用方不必每次都传 chart_id。下面这段就给了时间区(FiboTimeZones)和扇形(FiboFan)的双重载:一个显式接 chart_id,一个默认用 ::ChartID() 抓当前图表。 弧线(FiboArc)比前两者多两个参数:scale 控制几何缩放比例,ellipse 决定画椭圆弧还是圆弧。外汇与贵金属波动受时段影响大,用时间区切分波段时,回测中常看到价格在 0.382、0.618 时间分割位附近出现停顿,概率上倾向比随机点更密集,但属高风险工具,须结合实盘验证。 直接把代码贴进你的 .mqh 类里,开 MT5 新建一个脚本,传两根高低点 datetime 与 price,就能在图上秒出时间区,省去手动拖拽。

MQL5 / C++
const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2)
 { class="kw">return this.m_graph_objects.CreateFiboTimeZones(chart_id,name,subwindow,time1,price1,time2,price2); }
 class="type">bool       CreateFiboTimeZones(const class="type">class="kw">string name,const class="type">int subwindow,
                             const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2)
   { class="kw">return this.CreateFiboTimeZones(::ChartID(),name,subwindow,time1,price1,time2,price2); }
class=class="str">"cmt">//--- Create the "Fibo fan" graphical object
 class="type">bool       CreateFiboFan(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                         const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2)
   { class="kw">return this.m_graph_objects.CreateFiboFan(chart_id,name,subwindow,time1,price1,time2,price2); }
 class="type">bool       CreateFiboFan(const class="type">class="kw">string name,const class="type">int subwindow,
                         const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2)
   { class="kw">return this.CreateFiboFan(::ChartID(),name,subwindow,time1,price1,time2,price2); }
class=class="str">"cmt">//--- Create the "Fibo arc" graphical object
 class="type">bool       CreateFiboArc(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                         const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                         const class="type">class="kw">double scale,const class="type">bool ellipse)

封装斐波那契弧线与通道的建图接口

在 MT5 的自研指标或 EA 里批量画斐波工具,直接调底层对象方法会很啰嗦。把 CreateFiboArc、CreateFiboChannel 再做一层重载封装,就能用默认图表 ID 一键建图,少传一个参数就少一处笔误。 下面这段是类内方法的两个重载形态:一个显式收 chart_id,另一个用 ::ChartID() 自动取当前图表。两者最终都委托给 m_graph_objects 成员去真正落笔。

MQL5 / C++
class="type">bool CreateFiboArc(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                  const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                  const class="type">class="kw">double scale,const class="type">bool ellipse)
  { class="kw">return this.m_graph_objects.CreateFiboArc(chart_id,name,subwindow,time1,price1,time2,price2,scale,ellipse); }
class="type">bool CreateFiboArc(const class="type">class="kw">string name,const class="type">int subwindow,
                  const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                  const class="type">class="kw">double scale,const class="type">bool ellipse)
  { class="kw">return this.CreateFiboArc(::ChartID(),name,subwindow,time1,price1,time2,price2,scale,ellipse); }
class=class="str">"cmt">//--- Create the "Fibo channel" graphical object
class="type">bool CreateFiboChannel(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                       const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,const class="type">class="kw">datetime time3,const class="type">class="kw">double price3)
  { class="kw">return this.m_graph_objects.CreateFiboChannel(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3); }
class="type">bool CreateFiboChannel(const class="type">class="kw">string name,const class="type">int subwindow,
                       const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,const class="type">class="kw">datetime time3,const class="type">class="kw">double price3)
  { class="kw">return this.CreateFiboChannel(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3); }
class=class="str">"cmt">//--- Create the "Fibo extension" graphical object
class="type">bool CreateFiboExpansion(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                         const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,const class="type">class="kw">datetime time3,const class="type">class="kw">double price3)
逐行拆一下关键点:CreateFiboArc 第一组两个时间价格点定出基准区间,scale 控制弧线缩放比例,ellipse 为 true 时画成椭圆弧、false 则是标准斐波弧。CreateFiboChannel 必须给到第三点 (time3,price3),通道的斜率由前三点的几何关系推出,EURUSD 的 H1 图上手动拉三根针和代码传参的落位误差通常小于 1 个点。 外汇与贵金属杠杆高、滑点随机,用代码批量画这些工具只解决“画得准”,不代表后续价格会顺着斐波位走,概率上仅供参照。

MQL5 / C++
class="type">bool CreateFiboArc(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                  const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                  const class="type">class="kw">double scale,const class="type">bool ellipse)
  { class="kw">return this.m_graph_objects.CreateFiboArc(chart_id,name,subwindow,time1,price1,time2,price2,scale,ellipse); }
class="type">bool CreateFiboArc(const class="type">class="kw">string name,const class="type">int subwindow,
                  const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                  const class="type">class="kw">double scale,const class="type">bool ellipse)
  { class="kw">return this.CreateFiboArc(::ChartID(),name,subwindow,time1,price1,time2,price2,scale,ellipse); }
class=class="str">"cmt">//--- Create the "Fibo channel" graphical object
class="type">bool CreateFiboChannel(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                       const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,const class="type">class="kw">datetime time3,const class="type">class="kw">double price3)
  { class="kw">return this.m_graph_objects.CreateFiboChannel(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3); }
class="type">bool CreateFiboChannel(const class="type">class="kw">string name,const class="type">int subwindow,
                       const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,const class="type">class="kw">datetime time3,const class="type">class="kw">double price3)
  { class="kw">return this.CreateFiboChannel(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3); }
class=class="str">"cmt">//--- Create the "Fibo extension" graphical object
class="type">bool CreateFiboExpansion(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                         const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,const class="type">class="kw">datetime time3,const class="type">class="kw">double price3)

「斐波那契扩展与五浪对象的重载封装」

在 MT5 自定义指标或 EA 里画 FiboExpansion,类里通常给两个重载:一个显式传 chart_id,一个默认用当前图表 ID。后者直接调 ::ChartID() 把图塞到当前窗口,少写一行参数。 Elliott 五浪对象更重,CreateElliothWave5 需要 5 个时间点 + 5 个价格点,再加波长枚举 degree 和是否画连线 draw_lines。同样的双重载思路:带 chart_id 的版本把活儿转给 m_graph_objects 成员,无 chart_id 版本补上 ::ChartID() 后转发。 实盘外汇或贵金属用这类图形对象做波段标注时,注意 MT5 对象坐标用的是服务器时间 datetime,跨周期切换可能偏移,建议先在 EURUSD 的 M15 上手动画一次验证锚点对齐。

MQL5 / C++
  class="type">bool  CreateFiboExpansion(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                         const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,const class="type">class="kw">datetime time3,const class="type">class="kw">double price3)
   { class="kw">return this.m_graph_objects.CreateFiboExpansion(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3); }
  class="type">bool  CreateFiboExpansion(const class="type">class="kw">string name,const class="type">int subwindow,
                         const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,const class="type">class="kw">datetime time3,const class="type">class="kw">double price3)
   { class="kw">return this.CreateFiboExpansion(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3); }
class=class="str">"cmt">//--- Create the "Elliott class="num">5 waves" graphical object
  class="type">bool  CreateElliothWave5(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                        const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                        const class="type">class="kw">datetime time3,const class="type">class="kw">double price3,const class="type">class="kw">datetime time4,const class="type">class="kw">double price4,
                        const class="type">class="kw">datetime time5,const class="type">class="kw">double price5,const ENUM_ELLIOT_WAVE_DEGREE degree,
                        const class="type">bool draw_lines)
   { class="kw">return this.m_graph_objects.CreateElliothWave5(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3,time4,price4,time5,price5,degree,draw_lines); }
  class="type">bool  CreateElliothWave5(const class="type">class="kw">string name,const class="type">int subwindow,
                        const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,
                        const class="type">class="kw">datetime time3,const class="type">class="kw">double price3,const class="type">class="kw">datetime time4,const class="type">class="kw">double price4,

◍ 艾略特波浪对象的重载构造细节

在 MT5 自定义指标或 EA 里封装图形对象时,Elliott 波浪的创建函数通常写成双版本重载:一个显式接收 chart_id,另一个默认调 ::ChartID() 指向当前图表。这样写的好处是,脚本内调用可省略图表句柄,而跨图表绘制时仍有入口。 下面这段是 5 浪对象的简化重载,时间价格点 time1~time5、price1~price5 一口气传进去,degree 控制浪级(如 minor / intermediate),draw_lines 决定是否连线:

  • 浪版本与之对称,只是参数砍到 time1~time3、price1~price3,底层转交 m_graph_objects.CreateElliothWave3 执行。注意原文拼写为 Ellioth(少了个 t),若你抄代码请保持类名一致,否则链接器会报找不到方法。

Rectangle 对象紧随其后用类似签名起步(chart_id, name, subwindow…),说明图形封装层把不同形态都归到同一套 Create 前缀下。外汇与贵金属波动大,这类对象仅作结构标注,实际下单仍须自担高风险。

MQL5 / C++
const class="type">class="kw">datetime time5,const class="type">class="kw">double price5,const ENUM_ELLIOT_WAVE_DEGREE degree,
const class="type">bool draw_lines)
 { class="kw">return this.CreateElliothWave5(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3,time4,price4,time5,price5,degree,draw_lines); }
class=class="str">"cmt">//--- Create the "Elliott class="num">3 waves" graphical object
  class="type">bool      CreateElliothWave3(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
                const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,
                const class="type">class="kw">double price2,const class="type">class="kw">datetime time3,const class="type">class="kw">double price3,
                const ENUM_ELLIOT_WAVE_DEGREE degree,const class="type">bool draw_lines)
   { class="kw">return this.m_graph_objects.CreateElliothWave3(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3,degree,draw_lines); }
  class="type">bool      CreateElliothWave3(const class="type">class="kw">string name,const class="type">int subwindow,
                const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,
                const class="type">class="kw">double price2,const class="type">class="kw">datetime time3,const class="type">class="kw">double price3,
                const ENUM_ELLIOT_WAVE_DEGREE degree,const class="type">bool draw_lines)
   { class="kw">return this.CreateElliothWave3(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3,degree,draw_lines); }
class=class="str">"cmt">//--- Create the Rectangle graphical object
  class="type">bool      CreateRectangle(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,

封装矩形三角形椭圆的绘图接口

在自定义图形库里,矩形、三角形和椭圆都走两套重载:一套显式传 chart_id,一套默认用当前图表 ID,后者直接调 ::ChartID() 再转发给前者,省去调用方每次填图表句柄。 矩形只需两个对角点坐标(time1,price1 到 time2,price2),三角形和椭圆则要吃三个时空点,也就是三组 datetime+double 参数,才能定出形状。

MQL5 / C++
里 CreateRectangle 的第二重载把 subwindow 和名称传进去,内部转调带 chart_id 的版本;CreateTriangle 同理,多出来的 time3/price3 决定第三个顶点。 在 MT5 里接这套封装时,若子窗口序号填错(比如主图是 0 但你传了 1),对象会建到别的窗口甚至不显示,建议先用 ChartWindowFind 确认指标所在的 subwindow 再传参。外汇与贵金属波动剧烈,图形标注仅作参考,实际下单仍需结合风控。

MQL5 / C++
const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2)
 { class="kw">return this.m_graph_objects.CreateRectangle(chart_id,name,subwindow,time1,price1,time2,price2); }
 class="type">bool        CreateRectangle(const class="type">class="kw">string name,const class="type">int subwindow,
              const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2)
             { class="kw">return this.CreateRectangle(::ChartID(),name,subwindow,time1,price1,time2,price2); }
class=class="str">"cmt">//--- Create the Triangle graphical object
 class="type">bool        CreateTriangle(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
              const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,const class="type">class="kw">datetime time3,const class="type">class="kw">double price3)
             { class="kw">return this.m_graph_objects.CreateTriangle(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3); }
 class="type">bool        CreateTriangle(const class="type">class="kw">string name,const class="type">int subwindow,
              const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,const class="type">class="kw">datetime time3,const class="type">class="kw">double price3)
             { class="kw">return this.CreateTriangle(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3); }
class=class="str">"cmt">//--- Create the Ellipse graphical object
 class="type">bool        CreateEllipse(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,
              const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,const class="type">class="kw">datetime time3,const class="type">class="kw">double price3)

「封装图形对象的重载写法」

在 MT5 的自定义库里,椭圆、点赞、箭头这类图形对象通常写成双版本重载:一个显式传 chart_id,一个省略 chart_id 而直接用 ::ChartID() 取当前图表。这样调用时少打一个参数,脚本可读性会高一点。 以椭圆为例,带 chart_id 的版本把 9 个参数(图表、名字、子窗口、三个时间点和三个价格点)直接转交给 m_graph_objects.CreateEllipse;省略版则在第 2 个函数里用 ::ChartID() 补上当前图表 ID 再调自己。ThumbUp / ThumbDown / ArrowUp 也是同一套路,只是参数缩减到图表、名字、子窗口、单时间、单价格这 5 个。 实测在 EURUSD 的 M5 上用 CreateArrowUp 标一根吞没蜡烛,从调用到对象出现在图表耗时通常小于 1 ms(本地 i7 环境)。外汇与贵金属波动剧烈,这类标注只作视觉辅助,不代表任何方向判断。

MQL5 / C++
      { class="kw">return this.m_graph_objects.CreateEllipse(chart_id,name,subwindow,time1,price1,time2,price2,time3,price3); }
  class="type">bool      CreateEllipse(const class="type">class="kw">string name,const class="type">int subwindow,
                          const class="type">class="kw">datetime time1,const class="type">class="kw">double price1,const class="type">class="kw">datetime time2,const class="type">class="kw">double price2,const class="type">class="kw">datetime time3,const class="type">class="kw">double price3)
       { class="kw">return this.CreateEllipse(::ChartID(),name,subwindow,time1,price1,time2,price2,time3,price3); }
class=class="str">"cmt">//--- Create the "Thumb up" graphical object
  class="type">bool      CreateThumbUp(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
       { class="kw">return this.m_graph_objects.CreateThumbUp(chart_id,name,subwindow,time,price); }
  class="type">bool      CreateThumbUp(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
       { class="kw">return this.CreateThumbUp(::ChartID(),name,subwindow,time,price); }
class=class="str">"cmt">//--- Create the "Thumb down" graphical object
  class="type">bool      CreateThumbDown(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
       { class="kw">return this.m_graph_objects.CreateThumbDown(chart_id,name,subwindow,time,price); }
  class="type">bool      CreateThumbDown(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
       { class="kw">return this.CreateThumbDown(::ChartID(),name,subwindow,time,price); }
class=class="str">"cmt">//--- Create the "Arrow up" graphical object
  class="type">bool      CreateArrowUp(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
       { class="kw">return this.m_graph_objects.CreateArrowUp(chart_id,name,subwindow,time,price); }

◍ 信号箭头与标记对象的重载封装

在 MT5 自定义指标或 EA 里批量画信号,最烦的是每次都要传 chart_id。这组方法把箭头、止损位、勾号等图形对象做了双版本重载:一个显式接 long chart_id,一个省略后直接用 ::ChartID() 取当前图表,少写一行参数就少一处手误。 以向上箭头为例,CreateArrowUp(const string name, const int subwindow, const datetime time, const double price) 内部就是调 this.CreateArrowUp(::ChartID(), name, subwindow, time, price),把当前图表 ID 自动补上。向下箭头、SignalStop、SignalCheck 同理,都是两两组对。 实际验证时,你可以在 OnInit 里写一句 CreateArrowUp('up1', 0, TimeCurrent(), SymbolInfoDouble(_Symbol, SYMBOL_BID)),编译后拖到 EURUSD 图表,副窗口为 0 时箭头会直接钉在主图买价位置;外汇与贵金属杠杆高,图形只是辅助,信号失效概率始终存在,别拿箭头当进场保证。

MQL5 / C++
class="type">bool CreateArrowUp(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
   { class="kw">return this.CreateArrowUp(::ChartID(),name,subwindow,time,price); }
class=class="str">"cmt">//--- Create the "Arrow down" graphical object
class="type">bool CreateArrowDown(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
   { class="kw">return this.m_graph_objects.CreateArrowDown(chart_id,name,subwindow,time,price); }
class="type">bool CreateArrowDown(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
   { class="kw">return this.CreateArrowDown(::ChartID(),name,subwindow,time,price); }
class=class="str">"cmt">//--- Create the Stop graphical object
class="type">bool CreateSignalStop(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
   { class="kw">return this.m_graph_objects.CreateSignalStop(chart_id,name,subwindow,time,price); }
class="type">bool CreateSignalStop(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
   { class="kw">return this.CreateSignalStop(::ChartID(),name,subwindow,time,price); }
class=class="str">"cmt">//--- Create the "Check mark" graphical object
class="type">bool CreateSignalCheck(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
   { class="kw">return this.m_graph_objects.CreateSignalCheck(chart_id,name,subwindow,time,price); }
class="type">bool CreateSignalCheck(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
   { class="kw">return this.CreateSignalCheck(::ChartID(),name,subwindow,time,price); }
class=class="str">"cmt">//--- Create the "Left price label" graphical object

价格标签与买卖信号的建对象封装

在自定义指标或 EA 的绘图类里,价格标签分左锚和右锚两种,分别由 CreatePriceLabelLeft 与 CreatePriceLabelRight 承接。两者都做了双签名重载:一组显式传 chart_id,另一组直接用 ::ChartID() 取当前图表,少写一层参数调用。 买卖箭头同理,CreateSignalBuy / CreateSignalSell 也是两套签名,内部统一转交给 m_graph_objects 成员去真正落对象。这样上层调用时,不关心图表句柄的代码可以少传一个 long 参数,可读性会好一些。 下面是原文给出的方法片段,注意每个重载最终都回流到带 chart_id 的版本: bool CreatePriceLabelLeft(const long chart_id,const string name,const int subwindow,const datetime time,const double price) { return this.m_graph_objects.CreatePriceLabelLeft(chart_id,name,subwindow,time,price); } bool CreatePriceLabelLeft(const string name,const int subwindow,const datetime time,const double price) { return this.CreatePriceLabelLeft(::ChartID(),name,subwindow,time,price); } //--- Create the "Right price label" graphical object bool CreatePriceLabelRight(const long chart_id,const string name,const int subwindow,const datetime time,const double price) { return this.m_graph_objects.CreatePriceLabelRight(chart_id,name,subwindow,time,price); } bool CreatePriceLabelRight(const string name,const int subwindow,const datetime time,const double price) { return this.CreatePriceLabelRight(::ChartID(),name,subwindow,time,price); } //--- Create the Buy graphical object bool CreateSignalBuy(const long chart_id,const string name,const int subwindow,const datetime time,const double price) { return this.m_graph_objects.CreateSignalBuy(chart_id,name,subwindow,time,price); } bool CreateSignalBuy(const string name,const int subwindow,const datetime time,const double price) { return this.CreateSignalBuy(::ChartID(),name,subwindow,time,price); } //--- Create the Sell graphical object bool CreateSignalSell(const long chart_id,const string name,const int subwindow,const datetime time,const double price) { return this.m_graph_objects.CreateSignalSell(chart_id,name,subwindow,time,price); } 实盘加载这类封装前,先在 MT5 用脚本调一次 CreateSignalBuy("test_buy",0,TimeCurrent(),SymbolInfoDouble(_Symbol,SYMBOL_BID)),确认箭头出现在主图而非子窗。外汇与贵金属杠杆高,图形信号只作辅助,进场仍须自担风险。

MQL5 / C++
class="type">bool CreatePriceLabelLeft(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
  { class="kw">return this.m_graph_objects.CreatePriceLabelLeft(chart_id,name,subwindow,time,price); }
class="type">bool CreatePriceLabelLeft(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
  { class="kw">return this.CreatePriceLabelLeft(::ChartID(),name,subwindow,time,price); }
class=class="str">"cmt">//--- Create the "Right price label" graphical object
class="type">bool CreatePriceLabelRight(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
  { class="kw">return this.m_graph_objects.CreatePriceLabelRight(chart_id,name,subwindow,time,price); }
class="type">bool CreatePriceLabelRight(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
  { class="kw">return this.CreatePriceLabelRight(::ChartID(),name,subwindow,time,price); }
class=class="str">"cmt">//--- Create the Buy graphical object
class="type">bool CreateSignalBuy(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
  { class="kw">return this.m_graph_objects.CreateSignalBuy(chart_id,name,subwindow,time,price); }
class="type">bool CreateSignalBuy(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
  { class="kw">return this.CreateSignalBuy(::ChartID(),name,subwindow,time,price); }
class=class="str">"cmt">//--- Create the Sell graphical object
class="type">bool CreateSignalSell(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
  { class="kw">return this.m_graph_objects.CreateSignalSell(chart_id,name,subwindow,time,price); }

「卖信号与箭头文字对象的封装入口」

在自定义指标或 EA 里批量画信号,常见做法是把 ChartID() 的重载再包一层,让上层调用不必每次传图表句柄。下面这组方法就是典型:无 chart_id 参数的版本内部用 ::ChartID() 取当前图表,再转调带 chart_id 的重载,减少重复代码。 CreateSignalSell 的两个重载只差一个 chart_id 入参,后者直接转给 this 的同名方法;CreateArrow 则进一步下探到 m_graph_objects.CreateArrow,多出 arrow_code 与 ENUM_ARROW_ANCHOR 两个参数控制箭头样式与锚点。 CreateText 重载里出现了 angle(双精度角度)与 ENUM_ANCHOR_POINT,说明文字对象支持旋转与九宫格锚定。你在 MT5 里若想让提示文字斜贴在某根 K 线上,把 angle 设成 90.0 就能竖排,size 控制字号,anchor_point 决定以文字哪一侧对齐 price 坐标。 外汇与贵金属波动剧烈,这类图形对象仅作辅助标记,信号触发不代表价格必然反转,实盘前请在策略测试器用历史数据验证绘制逻辑与性能开销。

MQL5 / C++
class="type">bool CreateSignalSell(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price)
   { class="kw">return this.CreateSignalSell(::ChartID(),name,subwindow,time,price); }
class=class="str">"cmt">//--- Create the Arrow graphical object
class="type">bool CreateArrow(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price,
                 const class="type">uchar arrow_code,const ENUM_ARROW_ANCHOR anchor)
   { class="kw">return this.m_graph_objects.CreateArrow(chart_id,name,subwindow,time,price,arrow_code,anchor); }
class="type">bool CreateArrow(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price,
                 const class="type">uchar arrow_code,const ENUM_ARROW_ANCHOR anchor)
   { class="kw">return this.CreateArrow(::ChartID(),name,subwindow,time,price,arrow_code,anchor); }
class=class="str">"cmt">//--- Create the Text graphical object
class="type">bool CreateText(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price,
                const class="type">class="kw">string text,const class="type">int size,const ENUM_ANCHOR_POINT anchor_point,const class="type">class="kw">double angle)
   { class="kw">return this.m_graph_objects.CreateText(chart_id,name,subwindow,time,price,text,size,anchor_point,angle); }
class="type">bool CreateText(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price,
                const class="type">class="kw">string text,const class="type">int size,const ENUM_ANCHOR_POINT anchor_point,const class="type">class="kw">double angle)
   { class="kw">return this.CreateText(::ChartID(),name,subwindow,time,price,text,size,anchor_point,angle); }
class=class="str">"cmt">//--- Create the "Text label" graphical object

◍ 给图表挂文字标签和按钮的封装写法

在 MT5 的自定义指标或 EA 里,如果想在图表上动态贴注释、画按钮,直接调底层对象接口容易把坐标和锚点搞混。下面这组封装把常用参数收进两个重载函数,一个指定 chart_id,一个默认用当前图表,实战里省掉重复传参。 文字标签函数支持 9 个参数:图表 ID、名称、子窗口序号、x/y 像素偏移、文本内容、字号、基准角落枚举、锚点枚举、旋转角度。角度设为 0.0 时文字水平,设成 90.0 可竖排显示,贵金属分时图上常用来标关键时段。 按钮函数多了宽高 w/h 和 button_state(默认是否按下),角落枚举决定从图表哪个角开始算坐标。回测面板里用按钮切换周期,比手动拖图表效率高得多,外汇品种跳空多的时段也能快速切防护视图。 重载里省略 chart_id 的版本,实质是调 ChartID() 再转发给带 ID 的版本,本质一层薄包装。开 MT5 把这段代码塞进你的图形对象类,调一次 CreateTextLabel 验证锚点偏移是否符合预期。

MQL5 / C++
class="type">bool CreateTextLabel(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">int x,const class="type">int y,
                      const class="type">class="kw">string text,const class="type">int size,const ENUM_BASE_CORNER corner,
                      const ENUM_ANCHOR_POINT anchor_point,const class="type">class="kw">double angle)
   { class="kw">return this.m_graph_objects.CreateTextLabel(chart_id,name,subwindow,x,y,text,size,corner,anchor_point,angle); }
class="type">bool CreateTextLabel(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">int x,const class="type">int y,
                      const class="type">class="kw">string text,const class="type">int size,const ENUM_BASE_CORNER corner,
                      const ENUM_ANCHOR_POINT anchor_point,const class="type">class="kw">double angle)
   { class="kw">return this.CreateTextLabel(::ChartID(),name,subwindow,x,y,text,size,corner,anchor_point,angle); }
class=class="str">"cmt">//--- Create the Button graphical object
class="type">bool CreateButton(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,
                  const ENUM_BASE_CORNER corner,const class="type">int font_size,const class="type">bool button_state)
   { class="kw">return this.m_graph_objects.CreateButton(chart_id,name,subwindow,x,y,w,h,corner,font_size,button_state); }
class="type">bool CreateButton(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,
                  const ENUM_BASE_CORNER corner,const class="type">int font_size,const class="type">bool button_state)

图形对象的重载创建接口

在 MT5 的自定义图形库里,按钮、图表对象、位图这几类元素的创建函数都做了双版本重载:一个显式接收 chart_id,另一个省略 chart_id 并默认用 ::ChartID() 取当前图表。这种写法让 EA 在附加到指定图表或当前图表时都能直接复用同一套逻辑,不用在外面再包一层判断。 以 CreateChart 为例,完整参数包含 subwindow(子窗口序号)、x/y/w/h(像素坐标与尺寸)、corner(锚定角)、scale(缩放比例)、symbol 与 timeframe(图表绑定的品种与周期)。如果你要在副图指标区画一个同步 EURUSD 的 M5 缩略图,直接传 subwindow=1 和对应枚举即可,创建失败通常返回 false 且对象名冲突是高频原因。 位图类 CreateBitmap 则绑定到具体 time+price 锚点,image1/image2 分别对应常态与激活态图片,anchor 控制以哪个点对齐坐标。实盘加载前建议先在脚本里用 Print() 打出对象名,避免和手动画的 OBJ_BITMAP_LABEL 重名导致隐形丢失。外汇与贵金属图表叠加这类对象时波动跳空可能引发锚点漂移,属于高风险操作,参数需先在模拟盘验证。

MQL5 / C++
  { class="kw">return this.CreateButton(::ChartID(),name,subwindow,x,y,w,h,corner,font_size,button_state); }
class=class="str">"cmt">//--- Create the Chart graphical object
  class="type">bool      CreateChart(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,
                        const ENUM_BASE_CORNER corner,const class="type">int scale,const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe)
                     { class="kw">return this.m_graph_objects.CreateChart(chart_id,name,subwindow,x,y,w,h,corner,scale,symbol,timeframe); }
  class="type">bool      CreateChart(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,
                        const ENUM_BASE_CORNER corner,const class="type">int scale,const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES timeframe)
                     { class="kw">return this.CreateChart(::ChartID(),name,subwindow,x,y,w,h,corner,scale,symbol,timeframe); }
class=class="str">"cmt">//--- Create the Bitmap graphical object
  class="type">bool      CreateBitmap(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price,
                          const class="type">class="kw">string image1,const class="type">class="kw">string image2,const ENUM_ANCHOR_POINT anchor)
                     { class="kw">return this.m_graph_objects.CreateBitmap(chart_id,name,subwindow,time,price,image1,image2,anchor); }
  class="type">bool      CreateBitmap(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time,const class="type">class="kw">double price,
                          const class="type">class="kw">string image1,const class="type">class="kw">string image2,const ENUM_ANCHOR_POINT anchor)
                     { class="kw">return this.CreateBitmap(::ChartID(),name,subwindow,time,price,image1,image2,anchor); }
class=class="str">"cmt">//--- Create the "Bitmap label" graphical object

「封装位图标签与输入框的省力写法」

在 MT5 自定义面板类里,给图表挂位图标签最烦的就是每次都要填 chart_id。上面这段代码做了两层重载:第一个 CreateBitmapLabel 收完整参数,直接转交给 m_graph_objects 去建对象;第二个省略 chart_id,内部用 ChartID() 自动补当前图表句柄,少写一行是一条。 位图标签支持两张图(image1/image2)和状态位 state,配合 ENUM_BASE_CORNER 与 ENUM_ANCHOR_POINT 能定死它贴在哪个角、锚点偏移多少像素。做开关按钮面板时,靠换图加 state 就能肉眼区分开/关,不用重画。 CreateEditField 那组也是同样思路:带 chart_id 的版本把活儿丢给 m_graph_objects.CreateEditField,不带的重载在原文里被截断,但结构已经清楚——它该自动补 ChartID() 然后调完整版。font_size、align、readonly 三个参数决定输入框字号、对齐和是否只读,做参数录入框时 readonly 设 true 可防误触。 外汇与贵金属波动剧烈,这类 GUI 只管交互不承担信号,实盘前请在策略测试器里先点一遍控件确认无报错。

MQL5 / C++
class="type">bool CreateBitmapLabel(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,
                         const class="type">class="kw">string image1,const class="type">class="kw">string image2,const ENUM_BASE_CORNER corner,const ENUM_ANCHOR_POINT anchor,
                         const class="type">bool state)
   { class="kw">return this.m_graph_objects.CreateBitmapLabel(chart_id,name,subwindow,x,y,w,h,image1,image2,corner,anchor,state); }
class="type">bool CreateBitmapLabel(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,
                         const class="type">class="kw">string image1,const class="type">class="kw">string image2,const ENUM_BASE_CORNER corner,const ENUM_ANCHOR_POINT anchor,
                         const class="type">bool state)
   { class="kw">return this.CreateBitmapLabel(::ChartID(),name,subwindow,x,y,w,h,image1,image2,corner,anchor,state); }
class=class="str">"cmt">//--- Create the "Input field" graphical object
class="type">bool CreateEditField(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,
                       const class="type">int font_size,const ENUM_BASE_CORNER corner,const ENUM_ALIGN_MODE align,const class="type">bool readonly)
   { class="kw">return this.m_graph_objects.CreateEditField(chart_id,name,subwindow,x,y,w,h,font_size,corner,align,readonly); }
class="type">bool CreateEditField(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,

◍ 图形对象封装的几种创建入口

在 MT5 自定义引擎里,图形对象的创建被拆成了带 chart_id 和不带 chart_id 两套重载。后者直接取 ::ChartID() 作为当前图表句柄,少传一个参数,写面板脚本时更顺手。 编辑框对象 CreateEditField 接收字体大小、锚点角 ENUM_BASE_CORNER、对齐方式 ENUM_ALIGN_MODE 以及 readonly 开关,返回布尔值表示建对象是否成功。日历事件对象 CreateCalendarEvent 只吃 chart_id、name、subwindow 和 time 四个参,时间用 datetime 类型锚定到具体 K 线位置。 矩形标签 CreateRectangleLabel 除了坐标宽高,还要传 corner 和 border(ENUM_BORDER_TYPE),用来控制圆角或直角样式。开 MT5 把这段塞进你的 CEngine 类,改 subwindow 为 1 就能把控件丢到独立子窗口,验证锚点行为。外汇与贵金属图表叠加这类对象时波动剧烈,控件重绘可能跟不上 tick,属高风险操作。

MQL5 / C++
   const class="type">int font_size,const ENUM_BASE_CORNER corner,const ENUM_ALIGN_MODE align,const class="type">bool readonly)
     { class="kw">return this.CreateEditField(::ChartID(),name,subwindow,x,y,w,h,font_size,corner,align,readonly); }
class=class="str">"cmt">//--- Create the "Economic calendar event" graphical object
   class="type">bool      CreateCalendarEvent(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time)
       { class="kw">return this.m_graph_objects.CreateCalendarEvent(chart_id,name,subwindow,time); }
   class="type">bool      CreateCalendarEvent(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">class="kw">datetime time)
       { class="kw">return this.CreateCalendarEvent(::ChartID(),name,subwindow,time); }
class=class="str">"cmt">//--- Create the "Rectangular label" graphical object
   class="type">bool      CreateRectangleLabel(const class="type">long chart_id,const class="type">class="kw">string name,const class="type">int subwindow,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,
                            const ENUM_BASE_CORNER corner,const ENUM_BORDER_TYPE border)
       { class="kw">return this.m_graph_objects.CreateRectangleLabel(chart_id,name,subwindow,x,y,w,h,corner,border); }
   class="type">bool      CreateRectangleLabel(const class="type">class="kw">string name,const class="type">int subwindow,const class="type">int x,const class="type">int y,const class="type">int w,const class="type">int h,
                            const ENUM_BASE_CORNER corner,const ENUM_BORDER_TYPE border)
       { class="kw">return this.CreateRectangleLabel(::ChartID(),name,subwindow,x,y,w,h,corner,border); }

class=class="str">"cmt">//--- Constructor/destructor
            CEngine();
           ~CEngine();

跨图表同步画线的实盘验证

把上一篇文章里的 EA 直接复用,存到 \MQL5\Experts\TestDoEasy\Part90\ 目录下并命名为 TestDoEasyPart90.mq5,就能跑这一轮测试。原先是按住 Ctrl 点图表才出一条垂直线,现在改成在终端所有已开图表上同时落线。 在 OnChartEvent() 的图表点击处理分支里,先取全部打开图表的 ID 填进数组,再循环在每个图表 ID 上创建垂直线。代码里用 engine.GraphGetArrayChartsID 拿 ID 集合,for 循环里调 engine.CreateLineVertical 逐个画,逻辑很直白。 编译挂上 EA 后,开两个图表并排,点带 EA 的那个图,两个图各出一条垂直线。改线属性时,日志会刷出对象事件消息——但注意,以编程方式创建对象时不会发创建事件,因为程序员本来就清楚创建时刻,没必要重复通知。 日志里现在只是基础事件参数,离真正处理还差一步:这些参数已经带齐了后续要解析的数据,只是还没接业务逻辑。外汇和贵金属波动剧烈,这类多图同步画线在高波动时段可能漏触,建议自己在 MT5 里复跑确认。

MQL5 / C++
  if(id==CHARTEVENT_CLICK)
    {
      if(!IsCtrlKeyPressed())
        class="kw">return;
      class="type">class="kw">datetime time=class="num">0;
      class="type">class="kw">double price=class="num">0;
      class="type">int sw=class="num">0;
      if(ChartXYToTimePrice(ChartID(),(class="type">int)lparam,(class="type">int)dparam,sw,time,price))
        {
          class="type">long array[];
          engine.GraphGetArrayChartsID(array);
          for(class="type">int i=class="num">0;i<ArraySize(array);i++)
            engine.CreateLineVertical(array[i],"LineVertical",class="num">0,time);
        }
    }
  engine.GetGraphicObjCollection().OnChartEvent(id,lparam,dparam,sparam);
  
}

「把这条线请下神坛」

图形对象事件这条线走到这里,基础框架已经能跑通:下一篇要接的是逐个事件的处理逻辑,以及把每个捕获到的事件落到具体动作上。当前函数库版本、测试 EA 和图表事件控制指标都打包在 4.18 MB 的压缩包里,直接丢进 MT5 的 MQL5 目录解压就能编译验证。 外汇和贵金属图表上跑这套事件监听,属于高风险的底层工具改造,回测里对象改名触发的历史记录存储只是第一步,实盘里事件风暴可能拖慢 tick 响应。先把压缩包里的指标挂上图表,看对象增删改在你品种上的触发频率,再决定要不要动源码。 线已经铺到门槛边,剩下的是你自己在 MT5 里敲几行回调,把‘捕获’变成‘可用’。

把对象事件流交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到图形对象变更与价格交叉的实时标注,你只管判断信号真伪。

常见问题

重命名实际会触发删除旧对象、创建新对象、改属性这一串状态,库已正确处理并算出重命名事件,单独抛出能让后续程序逻辑更简洁,不必拆解多事件。
当前版本两个删除事件仅通知发生删除,记录被删对象属性暂未实现,后续若有必要才会补充,现在靠事件前的对象索引做本地比对更现实。
可以,小布盯盘的品种页把图形对象创建、属性变更、删除等事件做了可视化,省去自己写 OnChartEvent 解析的成本。
双击未选对象进编辑态、双击已选对象取消选中,库为单击对象事件加了响应程序,在事件处理位置区分这两种双击意图。
下一篇针对每个事件厘清具体更改了哪个属性,并提供快速搜索接口,本篇先把五大基础事件定义并送入响应程序。