DoEasy 函数库中的图形(第八十九部分):标准图形对象编程。 基本功能(基础篇)
📘

DoEasy 函数库中的图形(第八十九部分):标准图形对象编程。 基本功能(基础篇)

第 1/3 篇

◍ 给标准图形对象写底层接口

DoEasy 函数库这一轮把标准图形对象(趋势线、矩形、箭头等)的创建与属性读写收进统一类结构,目的是让 EA 和指标在调用时不再重复写 ObjectCreate / ObjectSetInteger 那套散装代码。 原文示例发布于 2021 年 2 月 21 日 13:41,截至文档统计有 1471 次查看、8 条讨论,说明这类基础封装在中低频策略开发者里需求稳定。 在 MT5 里验证很简单:新建一个 EA,用库里的图形基类画一条水平线,再改一次颜色与宽度,若代码量比原生 API 少一半以上,这套封装就值得接进你自己的系统。外汇与贵金属图表对象受价格跳空影响,对象锚点可能偏移,实盘前务必在周末 tick 模拟里跑一遍。

为什么要从程序里接管标准图形对象

现在这套函数库只能盯着终端图表上已有的标准图形对象,能发现它们被删掉或者某些参数被改了。但如果你想在 EA 或脚本里主动画一个、改一个、删一个自定义图形对象,目前还做不到——这个短板直接卡住了更复杂玩法。 一旦能从代码层创建并跟踪标准图形对象,就能拼出任意复杂度、任意嵌套、轴点数量可控的复合图形对象。本文先补齐“从零创建标准图形对象”的基础编程能力,后续文章再叠加上层复合结构。 上一篇文章里埋了个逻辑错:轴点超过两个的对象,属性变化就跟踪不到。这次顺手修掉,同时把多维动态数组类单独抽成文件,让它真正成为函数库里可独立调用的单元。外汇与贵金属图表对象受价格跳空影响,对象锚点可能瞬间偏移,实盘使用前建议在 MT5 策略测试器里用历史数据验证轴点跟踪是否稳定。

「给衍生图形对象补齐属性支持与类重构」

做函数库图形对象扩展时,衍生类必须依赖基准对象暴露的属性接口,才能正常搜索、排序以及在终端日志打印。以斐波那契弧为例,在 GStdFiboArcObj.mqh 里要手动加入对 GRAPH_OBJ_PROP_LEVELVALUE 这一实数型属性的支持,扩展、通道、扇形等六个文件照同样逻辑改一遍。 水平线类 GStdHLineObj.mqh 构造只用到价格,所以 SupportProperty 里要把整数型的 Pivot point time 标志删掉;竖线类 GStdVLineObj.mqh 只用到时间,实数型属性整体清空,返回 false。 之前有个逻辑坑:用两个轴点建对象、或级别超过两个时,往数组末尾加指针的方法会阻断轴点和级别修改。根因是外部建好一个对象、传同一指针进数组多次——改一个属性,所有指针引用的同一实例全变,导致第三到第五个参考点实数值没法单独跟踪。修复办法是在 AddQuantity() 里按指定数量新建属性对象实例塞进数组,而不是复用外部指针。 三维动态数组类(整数/实数/字符串)原先散在 XDimArray.mqh,现在抽成 Properties.mqh 通用工具。以 long 型为例,新建数据对象方法如下: [CODE] //+------------------------------------------------------------------+

//Return 'true' if an object supports a passed real property, otherwise return 'false'

//+------------------------------------------------------------------+ bool CGStdFiboArcObj::SupportProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property) { switch((int)property) { //--- Supported properties case GRAPH_OBJ_PROP_SCALE : case GRAPH_OBJ_PROP_PRICE : case GRAPH_OBJ_PROP_LEVELVALUE : return true; //--- Other properties are not supported //--- Default is 'false' default: break; } return false; } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+

//Return 'true' if an object supports a passed real property, otherwise return 'false'

[/CODE] 代码逐行拆解:函数声明返回 bool,入参是双精度属性枚举。switch 把枚举转 int 做分支;GRAPH_OBJ_PROP_SCALE 比例、GRAPH_OBJ_PROP_PRICE 价格、GRAPH_OBJ_PROP_LEVELVALUE 级别值三个 case 直接 return true,表明弧线对象支持这仨实数属性;其余走 default 跳出,函数尾 return false。高亮那行就是本节新增的级别值支持。 抽象标准图形对象类 gstdgraphhobj.mqh 里把属性对象移到公开区,加了析构函数释放;设标志的方法改名避免和基准类 SetDrawLines() 重名导致提示混乱。检查属性变化的方法也简化了:按属性数组第二维大小循环,单值属性维大小为 1,多值锚点等于点数,一个循环覆盖全部。 可编程建的标准图形对象 ID 规定 1–10000,手动建的从 10001 起,阈值写进 Defines.mqh。外汇和贵金属图表对象用这套库做自动标注时波动剧烈,实操前请在 MT5 策略测试器用历史数据验证对象 ID 冲突概率。

MQL5 / C++
class="type">bool CGStdFiboArcObj::SupportProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE class="kw">property)
  {
   class="kw">switch((class="type">int)class="kw">property)
     {
      class=class="str">"cmt">//--- Supported properties
      case GRAPH_OBJ_PROP_SCALE       :
      case GRAPH_OBJ_PROP_PRICE       :
      case GRAPH_OBJ_PROP_LEVELVALUE  : class="kw">return true;
      class=class="str">"cmt">//--- Other properties are not supported
      class=class="str">"cmt">//--- Default is &class="macro">#x27;class="kw">false&class="macro">#x27;
      class="kw">default: class="kw">break;
     }
   class="kw">return class="kw">false;
  }

◍ 对象属性支持判定的代码差异

在 MT5 自定义图形对象封装里,不同类别对属性枚举的支持范围差异很大,直接决定了你能用代码改哪些视觉与锚点参数。 下面这段是标准 Pitchfork(叉形线)对象对双精度属性的支持判定,只放行价格与水平值两类,其余一律返回 false。 [CODE] bool CGStdPitchforkObj::SupportProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property) { switch((int)property) { //--- Supported properties case GRAPH_OBJ_PROP_PRICE : case GRAPH_OBJ_PROP_LEVELVALUE : return true; //--- Other properties are not supported //--- Default is 'false' default: break; } return false; } [/CODE] 逐行看:函数入参是双精度属性枚举;switch 里仅 GRAPH_OBJ_PROP_PRICE 与 GRAPH_OBJ_PROP_LEVELVALUE 走 return true,意味着叉形线只能被代码调整价格与层级数值;default 跳出后函数尾返回 false,未列属性全不支持。 水平线对象(CGStdHLineObj)对整型属性的支持清单则长得多,从 ID、类型、所属图表到颜色、线宽共 19 个 case 返回 true,但注意 GRAPH_OBJ_PROP_TIME 虽在列却对水平线无意义——水平线锚定的是价格而非时间。 [CODE] bool CGStdHLineObj::SupportProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property) { switch((int)property) { //--- Supported properties case GRAPH_OBJ_PROP_ID : case GRAPH_OBJ_PROP_TYPE : case GRAPH_OBJ_PROP_ELEMENT_TYPE : case GRAPH_OBJ_PROP_GROUP : case GRAPH_OBJ_PROP_BELONG : case GRAPH_OBJ_PROP_CHART_ID : case GRAPH_OBJ_PROP_WND_NUM : case GRAPH_OBJ_PROP_NUM : case GRAPH_OBJ_PROP_CREATETIME : case GRAPH_OBJ_PROP_TIMEFRAMES : case GRAPH_OBJ_PROP_BACK : case GRAPH_OBJ_PROP_ZORDER : case GRAPH_OBJ_PROP_HIDDEN : case GRAPH_OBJ_PROP_SELECTED : case GRAPH_OBJ_PROP_SELECTABLE : case GRAPH_OBJ_PROP_TIME : case GRAPH_OBJ_PROP_COLOR : case GRAPH_OBJ_PROP_STYLE : case GRAPH_OBJ_PROP_WIDTH : return true; //--- Other properties are not supported //--- Default is 'false' default: break; } return false; } [/CODE] 竖线对象(CGStdVLineObj)对双精度属性直接整体返回 false,说明它不暴露任何可改的双精度参数,想调粗细颜色得走整型接口。 [CODE] bool CGStdVLineObj::SupportProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property) { return false; } [/CODE] 开 MT5 新建一个叉形线,用 ObjectSetDouble 去改非价格类双精度属性会静默失败;这和水平线能改 19 项整型参数形成对照,写 EA 前先核对对应类的 SupportProperty 能少踩坑。外汇与贵金属图表对象受报价跳空影响,这类属性限制可能在极端行情下让锚点更新滞后,属正常风险。

MQL5 / C++
class="type">bool CGStdPitchforkObj::SupportProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE class="kw">property)
  {
   class="kw">switch((class="type">int)class="kw">property)
     {
      class=class="str">"cmt">//--- Supported properties
      case GRAPH_OBJ_PROP_PRICE       :
      case GRAPH_OBJ_PROP_LEVELVALUE  : class="kw">return true;
      class=class="str">"cmt">//--- Other properties are not supported
      class=class="str">"cmt">//--- Default is &class="macro">#x27;class="kw">false&class="macro">#x27;
      class="kw">default: class="kw">break;
     }
   class="kw">return class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return &class="macro">#x27;true&class="macro">#x27; if an object supports a passed                     |
class=class="str">"cmt">//| integer class="kw">property, otherwise class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;                       |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CGStdHLineObj::SupportProperty(ENUM_GRAPH_OBJ_PROP_INTEGER class="kw">property)
  {
   class="kw">switch((class="type">int)class="kw">property)
     {
      class=class="str">"cmt">//--- Supported properties
      case GRAPH_OBJ_PROP_ID       :
      case GRAPH_OBJ_PROP_TYPE     :
      case GRAPH_OBJ_PROP_ELEMENT_TYPE :
      case GRAPH_OBJ_PROP_GROUP    :
      case GRAPH_OBJ_PROP_BELONG   :
      case GRAPH_OBJ_PROP_CHART_ID :
      case GRAPH_OBJ_PROP_WND_NUM  :
      case GRAPH_OBJ_PROP_NUM      :
      case GRAPH_OBJ_PROP_CREATETIME :
      case GRAPH_OBJ_PROP_TIMEFRAMES :
      case GRAPH_OBJ_PROP_BACK     :
      case GRAPH_OBJ_PROP_ZORDER   :
      case GRAPH_OBJ_PROP_HIDDEN   :
      case GRAPH_OBJ_PROP_SELECTED :
      case GRAPH_OBJ_PROP_SELECTABLE :
      case GRAPH_OBJ_PROP_TIME     :
      case GRAPH_OBJ_PROP_COLOR    :
      case GRAPH_OBJ_PROP_STYLE    :
      case GRAPH_OBJ_PROP_WIDTH    : class="kw">return true;
      class=class="str">"cmt">//--- Other properties are not supported
      class=class="str">"cmt">//--- Default is &class="macro">#x27;class="kw">false&class="macro">#x27;
      class="kw">default: class="kw">break;
     }
   class="kw">return class="kw">false;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Return &class="macro">#x27;true&class="macro">#x27; if an object supports a passed                     |
class=class="str">"cmt">//| real class="kw">property, otherwise class="kw">return &class="macro">#x27;class="kw">false&class="macro">#x27;                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CGStdVLineObj::SupportProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE class="kw">property)
  {
   class="kw">return class="kw">false;
  }

批量塞对象的容错写法

在自定义数组容器里,经常需要一次性往尾部追加 N 个相同类型的对象。下面这段逻辑用 total 控制循环次数,每次调用 this.Add(object) 把指针挂进列表,任何一次失败就记日志并把结果位清零,但不中断循环。 res 初始为 true,循环中用 res &= false 做累积与运算——只要有一轮 Add 返回 false,最终函数就会返回 false,调用方据此判断整批插入是否完全成功。这种写法比抛异常更适合 MT5 脚本环境,不会因为单个对象失败直接崩掉 EA。 CDataUnitLong *CreateData(...) 则是 CDimLong 类的私有工厂方法,负责按传入的 long 值 new 出一个数据单元,默认填 0。开 MT5 把这段塞进你自己的 CArrayObj 派生类,改 total 参数就能验证批量追加的边界表现,外汇与贵金属品种上跑这类容器需注意 Tick 稀疏时的对象生命周期高风险。

MQL5 / C++
class="type">bool AddQuantity(class="kw">const class="type">class="kw">string source,class="kw">const class="type">int total,CObject *object)
  {
   class="type">bool res=true;
   for(class="type">int i=class="num">0;i<total;i++)
     {
      if(!this.Add(object))
        {
         CMessage::ToLog(source,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
         res &=class="kw">false;
         class="kw">continue;
        }
     }
   class="kw">return res;
  }

class CDimLong : class="kw">public CArrayObj
 {
class="kw">private:
   CDataUnitLong   *CreateData(class="kw">const class="type">class="kw">string source,class="kw">const class="type">long value=class="num">0)
     {

「长整型数据对象的批量挂接逻辑」

在 MT5 自定义类里,长整型数据单元通常用 CDataUnitLong 承载。单对象创建时先 new 出来,空指针就往日志抛 MSG_LIB_SYS_FAILED_CREATE_LONG_DATA_OBJ,否则把入参 value 写进 data.Value 再返回指针——这一步决定了上层调用方拿到的到底是可用对象还是 NULL。 批量追加走 AddQuantity,它接收 sourcetotal 和带默认 0 的 value。内部 for 循环按 total 次数反复调用 this.CreateData(DFUN, value) 造对象,任意一次造失败就把 res 与 false 做位与并 continue,不会中断整轮添加。 这种写法意味着:你传 total=100 而内存只够前 97 个,函数仍返回 false 且数组里静默少了 3 个单元。外汇与贵金属行情Tick密集,批量建对象前最好先测一下可用内存,否则回测里看不出、实盘才可能崩。

MQL5 / C++
class=class="str">"cmt">//--- Create a new class="type">long data object
CDataUnitLong *data=new CDataUnitLong();
class=class="str">"cmt">//--- If failed to create an object, inform of that in the journal
if(data==NULL)
   ::Print(source,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_LONG_DATA_OBJ));
class=class="str">"cmt">//--- Otherwise, set the value passed to the method for the object
else
   data.Value=value;
class=class="str">"cmt">//--- Return the pointer to the object or NULL
class="kw">return data;
}
class=class="str">"cmt">//--- Get class="type">long data object from the array
class=class="str">"cmt">//--- Add the specified number of cells with data to the end of the array
class="type">bool AddQuantity(class="kw">const class="type">class="kw">string source,class="kw">const class="type">int total,class="kw">const class="type">long value=class="num">0)
   {
   class=class="str">"cmt">//--- Declare the variable for storing the result of adding objects to the list
   class="type">bool res=true;
   class=class="str">"cmt">//--- in the list by the number of added objects passed to the method
   for(class="type">int i=class="num">0;i<total;i++)
      {
      class=class="str">"cmt">//--- Create a new class="type">long data object
      CDataUnitLong *data=this.CreateData(DFUN,value);
      class=class="str">"cmt">//--- If failed to create an object, inform of that and move on to the next iteration
      if(data==NULL)
         {
         res &=class="kw">false;
         class="kw">continue;
         }

常见问题

多半是循环里没做名称查重和存在性判断。先检测对象是否已存在再创建,并对失败操作打日志,能避免静默漏建。
用对象遍历接口按类型枚举,再把句柄绑到自己的底层类里。这样后续移动、改色、删对象都能用代码统一控制。
可以。小布能扫描当前品种页的图形对象,列出属性不全或类支持异常的标注,你直接按提示补代码或手动修正即可。
常因时间或刻度值越界、或容器下标类型不匹配。用有符号长整型接收、循环前先清容器并做边界断言,容错会稳很多。
不必全量重写。在基类补属性判定分支,衍生类只覆写差异部分,就能补齐支持又不动原结构。