DoEasy 函数库中的图形(第九十部分):标准图形对象事件。 基本功能·综合运用
(3/3)·从对象创建到窗口删除,65 个小节把 MT5 图形事件收口成一套可直接调用的库
- GUI 阴影对象的创建与隐藏逻辑
- 图形对象的阴影与主窗绑定逻辑
- 图形对象的层级与可见性控制
- 图形对象的隐藏与选中状态接口
- 图形对象的时间与外观读写接口
- 图形对象的线宽与填充读写接口
- 图形对象的只读与多水平线控制
- 对象线宽与对齐的底层封装
- 图形对象的字号与射线方向控制
- 图形射线与斐波那契弧的属性封装
- 箭头与锚点的像素级控制
- 图形对象的纵向偏移与甘氏艾略特属性读写
- 艾略特波形对象的画线开关与按钮态读写
- 图表对象的周期与坐标轴开关
- 图表对象的缩放与像素尺寸控制
- 图表对象可视区域尺寸与偏移的读写封装
- 控件底色与锚点角的读写封装
- 图形对象的边框与坐标读写封装
- 图形对象的层级值与缩放角度封装
- 图形对象的偏离值与命名控制
- 图形对象的文本与提示属性封装
- 给指标对象写文字与贴图的底层接口
- 图形对象的跨品种绑定与属性回读
- 图形对象属性的批量同步写法
- 对象属性同步时的级别数组重分配
- 把图形对象的隐藏属性一次性抓进类里
- 把图表对象的几何与外观读进类属性
- 把图形对象变动封装成可追踪事件
- 图形事件对象的封装与派发
- 图形对象基类的成员布局
- 图形对象的底层字段与事件接口
- 事件链表的初始化与清空逻辑
- 图形对象的状态初始化与变更侦测
- 对象属性变更如何触发图表事件
- 对象创建失败时的安全退出路径
- 图表对象集合类的内部接口
- 图形对象重复创建时的空指针返回逻辑
- 垂直线对象的创建与列表登记
- 图形对象创建后的归属与列表挂载
- 图表对象集合的刷新与事件捕获
- 对象销毁时如何回传主控图表
- 图表对象改名时的事件兜底逻辑
- 图形对象事件分支与暂停计时实现
- 图表对象批量采集与画线封装
- 趋势线与角度线的两种重载封装
- 给图表对象封装两条线和一个通道的构造函数
- 通道对象的两种封装入口
- 回归通道与安德鲁音叉的对象封装
- 甘氏线与甘氏扇的封装入口
- 江恩网格与斐波那契对象的重载封装
- 菲波那契时间区与扇形弧线的封装入口
- 封装斐波那契弧线与通道的建图接口
- 斐波那契扩展与五浪对象的重载封装
- 艾略特波浪对象的重载构造细节
- 封装矩形三角形椭圆的绘图接口
- 封装图形对象的重载写法
- 信号箭头与标记对象的重载封装
- 价格标签与买卖信号的建对象封装
- 卖信号与箭头文字对象的封装入口
- 给图表挂文字标签和按钮的封装写法
- 图形对象的重载创建接口
- 封装位图标签与输入框的省力写法
- 图形对象封装的几种创建入口
- 跨图表同步画线的实盘验证
- 把这条线请下神坛
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),意味着它能跟表单走却不吃鼠标事件,做拖拽面板时这层隔离能少写不少碰撞判断。
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) 就能验证属性与显示分离的效果。
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() 直接读该属性返回布尔。外汇与贵金属交易使用自定义图形对象高风险,参数误设可能导致图表交互异常。
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 直写要省一次基类调用。外汇与贵金属图表对象受报价跳动影响,对象状态管理不当可能导致脚本误判选中态,属高风险操作环境。
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);
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,属高频坑。
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) 跑一遍,能在对象上直接看到三档红线,验证缓存与图表双向同步是否如代码逻辑一致。
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。外汇与贵金属杠杆高,这类界面微调不改变信号逻辑,却可能影响你扫盘时的误判概率,建议真机验证而非只看回测截图。
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 线继续绘制,方便做长期参考线。外汇与贵金属杠杆高,这类对象仅作视觉辅助,信号失效概率不低,别当成进出场依据。
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,用来取箭头样式编码。外汇与贵金属图表对象受报价跳空影响,射线右延可能被实时价格打断,属正常高风险现象。
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) 看箭头是否右移,能立刻验证封装是否生效。
{
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。
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 恢复,证明双写未脱节。外汇与贵金属波动剧烈,此类图形开关仅辅助判形,实际进出场仍需结合风控。
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 会静默失败。
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 参数就能看到对象在副图里的拉伸反应,外汇与贵金属图表叠加多对象时这种像素级控制能避免遮挡。
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 值就能看到对象可视区平移,外汇与贵金属图表对象受报价跳空影响可能偏移异常,属高风险操作。
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),能立刻看到控件换底色并贴左上角。外汇与贵金属图表上做面板属于高风险环境,控件重绘不替代任何风控。
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) 确认双锚点读取无误。外汇与贵金属杠杆高,这类界面对象只辅助观察,不构成交易信号。
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 把这几段塞进你的对象类,改个参数就能看到线位偏移。
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 的边界。 外汇与贵金属图表对象受报价跳空影响,偏离值设定不当可能导致通道线偏移,属高风险操作,参数调整前建议在模拟盘验证。
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 传"突破概率偏高",右键对象即可看到分层文字生效;外汇贵金属波动剧烈,这类标注仅作参考,实际风险仍由仓位控制。
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 计数的行为。
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 打这几个属性值,再对照代码里的宏,就能确认你的对象层级和可见性设置是否和预期一致。
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 数值就能验证枢轴点循环是否漏写;外汇与贵金属图表对象受报价跳空影响,属性回读可能延迟一两帧,属正常现象。
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 就会在复制对象模板时静默丢线。
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,就能确认抓取链路通了。
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,能确认偏移量是否随边角重置。外汇贵金属波动剧烈,面板错位可能让你漏掉信号,建议本地先验证再上实盘。
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 图形对象在高波动行情可能丢事件,实盘前务必在模拟盘跑通。
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 对应的子图未就绪,事件丢失概率会升高,建议先在模拟盘验证派发链路。
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 被赋值的地方,就能看出库里默认的点击穿透逻辑。
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 之前被误删。
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() 就能验证空链起点。
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() 的返回按自己的对象类型收窄,就能避免无谓的事件风暴。外汇与贵金属市场波动剧烈,这类对象状态监控仅用于辅助,实际下单仍属高风险行为。
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 实例。
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_event 由 m_delta_graph_obj 强转得到,供 NewObjects()、IsEvent() 两个 const 接口对外暴露增量与事件标志。
实盘加载这类库时,建议故意传一个非法对象名观察日志,确认 NULL 分支与 Add 失败分支都能按预期回收资源,外汇与贵金属图表对象多、刷图频繁,内存泄漏倾向放大风险。
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 以上,写循环时建议加节流。
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,说明扩展坐标在基础对象创建时并非必填。
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 与时间位生成竖线。外汇与贵金属图表上挂这类标记线属高风险辅助手段,信号仅作概率参考。 下面这段是上述逻辑的核心代码片段,逐行看能直接抄进自己的图形管理类。
::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 并报警,后者删对象防内存泄漏——外汇与贵金属图表高频刷新下,这类泄漏可能让终端变卡甚至掉线,属高风险操作环境。
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 调用频次验证开销。
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 在分时图挂两个指标窗口,手动关掉一个验证事件是否触发。
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 里用三个文本对象互改名称实测一次。
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 清理,可能让指标残留引用已关图表,引发后续数组越界报错。
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() 用当前图。垂直、水平、趋势线都这么包一层,调用时少传一个参数就能画在本图,多传就能跨图。 外汇和贵金属品种波动剧烈,这类自动画线仅作参考辅助,实际进出场仍要结合价格行为自行判断,杠杆交易风险偏高。
::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 下触及次数,再决定哪条线值得挂单。
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 像素。
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。
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 参数,否则对象可能落到主图造成视觉错位。外汇与贵金属波动大,这类画线仅作结构参考,价格触碰通道边界后反向或突破均属概率事件,需结合仓位控制。
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 可能更贴合波动。外汇与贵金属杠杆高,甘氏线仅作时空参照,破线不意味趋势必然反转。
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 上画出完全失真的网格,实盘前务必在策略测试器用历史数据核对。
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,就能在图上秒出时间区,省去手动拖拽。
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 成员去真正落笔。
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)
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 上手动画一次验证锚点对齐。
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 前缀下。外汇与贵金属波动大,这类对象仅作结构标注,实际下单仍须自担高风险。
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 参数,才能定出形状。
…
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 环境)。外汇与贵金属波动剧烈,这类标注只作视觉辅助,不代表任何方向判断。
{ 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 时箭头会直接钉在主图买价位置;外汇与贵金属杠杆高,图形只是辅助,信号失效概率始终存在,别拿箭头当进场保证。
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)),确认箭头出现在主图而非子窗。外汇与贵金属杠杆高,图形信号只作辅助,进场仍须自担风险。
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 坐标。 外汇与贵金属波动剧烈,这类图形对象仅作辅助标记,信号触发不代表价格必然反转,实盘前请在策略测试器用历史数据验证绘制逻辑与性能开销。
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 验证锚点偏移是否符合预期。
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 重名导致隐形丢失。外汇与贵金属图表叠加这类对象时波动跳空可能引发锚点漂移,属于高风险操作,参数需先在模拟盘验证。
{ 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 只管交互不承担信号,实盘前请在策略测试器里先点一遍控件确认无报错。
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,属高风险操作。
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 里复跑确认。
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 里敲几行回调,把‘捕获’变成‘可用’。