DoEasy 函数库中的时间序列(第五十七部分):指标缓冲区数据对象·进阶篇
指标数据属性的三数组封装
在 MT5 自定义指标的类结构里,把数据属性按类型拆成三块连续内存,是后续高效读写的底层约定。整数、双精度、字符串各占一个定长数组,下标直接对应枚举值,避免用 map 带来的额外开销。 下面这段声明把周期描述串和三类属性数组一并铺开:m_long_prop 装整数属性,长度由 IND_DATA_PROP_INTEGER_TOTAL 决定;m_double_prop 装实数属性;m_string_prop 装字符串属性。周期描述 m_period_description 单独留作可读文本。 IndexProp 是两个重载,作用只有一件——把 DOUBLE 和 STRING 枚举偏移进各自数组的正确下标。减掉前面整数和双精度数组的总长,就能从全局枚举映射到局部下标,调用方完全无感。 SetProperty / GetProperty 同样按类型重载:整数直接按下标写,双精度和字符串先过 IndexProp 再落位。这种写法在回测中访问 10 万根 bar 的属性时,比运行时查表快出一个数量级,外汇与贵金属行情高频刷新下更显必要,但杠杆交易本身高风险,参数错配可能放大亏损。 SupportProperty 默认全返回 true,派生类可只覆写自己不支持的那一项,省掉调用前的类型判断。
class="type">class="kw">string m_period_description; class=class="str">"cmt">// Timeframe class="type">class="kw">string description class="type">long m_long_prop[IND_DATA_PROP_INTEGER_TOTAL]; class=class="str">"cmt">// Integer properties class="type">class="kw">double m_double_prop[IND_DATA_PROP_DOUBLE_TOTAL]; class=class="str">"cmt">// Real properties class="type">class="kw">string m_string_prop[IND_DATA_PROP_STRING_TOTAL]; class=class="str">"cmt">// String properties class=class="str">"cmt">//--- Return the index of the array the object&class="macro">#x27;s(class="num">1) class="type">class="kw">double and(class="num">2) class="type">class="kw">string properties are located at class="type">int IndexProp(ENUM_IND_DATA_PROP_DOUBLE class="kw">property) class="kw">const { class="kw">return(class="type">int)class="kw">property-IND_DATA_PROP_INTEGER_TOTAL; } class="type">int IndexProp(ENUM_IND_DATA_PROP_STRING class="kw">property) class="kw">const { class="kw">return(class="type">int)class="kw">property-IND_DATA_PROP_INTEGER_TOTAL-IND_DATA_PROP_DOUBLE_TOTAL; } class="kw">public: class=class="str">"cmt">//--- Set(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties of indicator data class="type">void SetProperty(ENUM_IND_DATA_PROP_INTEGER class="kw">property,class="type">long value) { this.m_long_prop[class="kw">property]=value; } class="type">void SetProperty(ENUM_IND_DATA_PROP_DOUBLE class="kw">property,class="type">class="kw">double value){ this.m_double_prop[this.IndexProp(class="kw">property)]=value; } class="type">void SetProperty(ENUM_IND_DATA_PROP_STRING class="kw">property,class="type">class="kw">string value){ this.m_string_prop[this.IndexProp(class="kw">property)]=value; } class=class="str">"cmt">//--- Return(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string class="kw">property of indicator data from the properties array class="type">long GetProperty(ENUM_IND_DATA_PROP_INTEGER class="kw">property) class="kw">const { class="kw">return this.m_long_prop[class="kw">property]; } class="type">class="kw">double GetProperty(ENUM_IND_DATA_PROP_DOUBLE class="kw">property) class="kw">const { class="kw">return this.m_double_prop[this.IndexProp(class="kw">property)]; } class="type">class="kw">string GetProperty(ENUM_IND_DATA_PROP_STRING class="kw">property) class="kw">const { class="kw">return this.m_string_prop[this.IndexProp(class="kw">property)]; } class=class="str">"cmt">//--- Return the flag of the object supporting this class="kw">property class="kw">virtual class="type">bool SupportProperty(ENUM_IND_DATA_PROP_INTEGER class="kw">property) { class="kw">return true; } class="kw">virtual class="type">bool SupportProperty(ENUM_IND_DATA_PROP_DOUBLE class="kw">property) { class="kw">return true; }
◍ 指标数据对象的内部接口与构造
在 MT5 自定义指标框架里,CDataInd 这类数据容器负责把「某品种、某周期、某根 K 线」上的指标状态打包成可排序、可比对的对象。它暴露的一组 Set 方法,本质是把散落的指标属性写进统一的属性槽位,方便后续在列表里按字段检索。 看下面这段接口声明,SupportProperty 直接返回 true,意味着字符串类指标属性在该对象上全部被放行;GetObject 则返回自身指针,让外部调用者能原地修改而不必拷贝。 [CODE] virtual bool SupportProperty(ENUM_IND_DATA_PROP_STRING property) { return true; } //--- Return itself CDataInd *GetObject(void) { return &this; } //--- Set (1) symbol, timeframe and time for the object, (2) indicator type, (3) number of buffers, (4) number of data buffer, //--- (5) ID, (6) data value, (7) name, (8) indicator short name void SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); void SetIndicatorType(const ENUM_INDICATOR type) { this.SetProperty(IND_DATA_PROP_INDICATOR_TYPE,type); } void SetBufferNum(const int num) { this.SetProperty(IND_DATA_PROP_IND_BUFFER_NUM,num); } void SetIndicatorID(const int id) { this.SetProperty(IND_DATA_PROP_IND_ID,id); } void SetBufferValue(const double value) { this.SetProperty(IND_DATA_PROP_BUFFER_VALUE,value); } void SetIndicatorName(const string name) { this.SetProperty(IND_DATA_PROP_IND_NAME,name); } void SetIndicatorShortname(const string shortname) { this.SetProperty(IND_DATA_PROP_IND_SHORTNAME,shortname); } //--- Compare CDataInd objects with each other by all possible properties (for sorting the lists by a specified object property) virtual int Compare(const CObject *node,const int mode=0) const; //--- Compare CDataInd objects with each other by all properties (to search equal objects) bool IsEqual(CDataInd* compared_data) const; //--- Constructors CDataInd(){;} CDataInd(const ENUM_INDICATOR ind_type, [/CODE] 逐行拆一下关键行:SupportProperty 返回 true 表示接受所有字符串型指标属性;GetObject 回传 this 指针,外部拿到后能直接调 Set 系列改属性。SetIndicatorType 到 SetIndicatorShortname 这七个 inline 方法,分别把指标类型、缓冲区数、ID、缓冲值、名称、短名写进对应的枚举属性槽,比如 IND_DATA_PROP_BUFFER_VALUE 存的就是那根 K 线上的指标数值(double 型)。 Compare 带 mode 参数,是按属性排序用的虚函数,默认 mode=0 一般对应基础比较逻辑;IsEqual 则拿另一个 CDataInd 指针做全属性比对,用来在列表里查重。两个构造函数中,无参版留空,带 ENUM_INDICATOR 参数的重载为后续按类型初始化留了入口。 开 MT5 在 MetaEditor 里搜 CDataInd 能看到完整声明,把上面代码贴进自定义类的公有段,就能在 EA 里用 SetBufferValue 把实时指标值灌进对象、再用 IsEqual 做跨品种信号去重。外汇与贵金属杠杆高,这类对象级比对只解决代码结构问题,不预示任何方向。
class="kw">virtual class="type">bool SupportProperty(ENUM_IND_DATA_PROP_STRING class="kw">property) { class="kw">return true; } class=class="str">"cmt">//--- Return itself CDataInd *GetObject(class="type">void) { class="kw">return &this; } class=class="str">"cmt">//--- Set(class="num">1) symbol, timeframe and time for the object, (class="num">2) indicator type, (class="num">3) number of buffers, (class="num">4) number of data buffer, class=class="str">"cmt">//--- (class="num">5) ID, (class="num">6) data value, (class="num">7) name, (class="num">8) indicator class="type">class="kw">short name class="type">void SetSymbolPeriod(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,class="kw">const class="type">class="kw">datetime time); class="type">void SetIndicatorType(class="kw">const ENUM_INDICATOR type) { this.SetProperty(IND_DATA_PROP_INDICATOR_TYPE,type); } class="type">void SetBufferNum(class="kw">const class="type">int num) { this.SetProperty(IND_DATA_PROP_IND_BUFFER_NUM,num); } class="type">void SetIndicatorID(class="kw">const class="type">int id) { this.SetProperty(IND_DATA_PROP_IND_ID,id); } class="type">void SetBufferValue(class="kw">const class="type">class="kw">double value) { this.SetProperty(IND_DATA_PROP_BUFFER_VALUE,value); } class="type">void SetIndicatorName(class="kw">const class="type">class="kw">string name) { this.SetProperty(IND_DATA_PROP_IND_NAME,name); } class="type">void SetIndicatorShortname(class="kw">const class="type">class="kw">string shortname) { this.SetProperty(IND_DATA_PROP_IND_SHORTNAME,shortname); } class=class="str">"cmt">//--- Compare CDataInd objects with each other by all possible properties(for sorting the lists by a specified object class="kw">property) class="kw">virtual class="type">int Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const; class=class="str">"cmt">//--- Compare CDataInd objects with each other by all properties(to search equal objects) class="type">bool IsEqual(CDataInd* compared_data) class="kw">const; class=class="str">"cmt">//--- Constructors CDataInd(){;} CDataInd(class="kw">const ENUM_INDICATOR ind_type,
「从指标事件里直接抠出周期和数值」
在 MT5 的指标事件回调里,系统传进来的不是一个裸数值,而是一组描述「这条数据从哪来」的上下文。上面这段接口声明定义了事件触发时你能拿到的核心参数:指标 ID、缓冲区编号、品种名、时间周期和具体时间。
真正方便的是封装好的访问方法。Time() 直接返回该 bar 的起始时间,Timeframe() 给出 ENUM_TIMEFRAMES 周期枚举,IndicatorType() 告诉你触发的是哪类指标,BufferNum() 和 IndicatorID() 则定位到具体缓冲区和实例。PriceValue() 才是你写策略最常用的一行——它返回该缓冲区在当前事件下的数值。
写 EA 时不用自己解析事件结构体,直接 double v = data.PriceValue(); 就能拿到值,再配合 data.Timeframe() 判断是不是在 H1 上触发。外汇与贵金属杠杆高、滑点随机,这类取值只解决「拿到数」,不代表信号一定有效,回测前先在策略测试器跑一遍不同点差环境。
class="kw">const class="type">int ind_id, class="kw">const class="type">int buffer_num, class="kw">const class="type">class="kw">string symbol, class="kw">const ENUM_TIMEFRAMES timeframe, class="kw">const class="type">class="kw">datetime time); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Methods of simplified access to object properties | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Return(class="num">1) bar period start time, (class="num">2) timeframe, (class="num">3) indicator type, (class="num">4) number of buffers, (class="num">5) buffer number, (class="num">6) indicator ID class="type">class="kw">datetime Time(class="type">void) class="kw">const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(IND_DATA_PROP_TIME); } ENUM_TIMEFRAMES Timeframe(class="type">void) class="kw">const { class="kw">return (ENUM_TIMEFRAMES)this.GetProperty(IND_DATA_PROP_PERIOD); } ENUM_INDICATOR IndicatorType(class="type">void) class="kw">const { class="kw">return (ENUM_INDICATOR)this.GetProperty(IND_DATA_PROP_INDICATOR_TYPE); } class="type">int BufferNum(class="type">void) class="kw">const { class="kw">return (ENUM_INDICATOR)this.GetProperty(IND_DATA_PROP_IND_BUFFER_NUM); } class="type">int IndicatorID(class="type">void) class="kw">const { class="kw">return (ENUM_INDICATOR)this.GetProperty(IND_DATA_PROP_IND_ID); } class=class="str">"cmt">//--- Return the price of indicator buffer data class="type">class="kw">double PriceValue(class="type">void) class="kw">const { class="kw">return this.GetProperty(IND_DATA_PROP_BUFFER_VALUE); } class=class="str">"cmt">//--- Return(class="num">1) data symbol, (class="num">2) name, (class="num">3) indicator class="type">class="kw">short name
指标数据对象的属性读取与跨周期定位
在 MT5 自定义指标类里,CIndicatorData 对象封装了当前图表的品种、指标名、短名与小数位等基础属性。直接调用 Symbol()、IndicatorName()、IndicatorShortName() 和 Digits() 即可拿到对应字符串或整数,无需再走全局函数二次查询。 跨周期对齐是实盘常用的动作。Index() 方法内部调用 iBarShift,把对象所属 K 线时间映射到指定周期(PERIOD_CURRENT 会回落到当前图表 Period())的 bar 索引;若时间不在该周期历史内,返回 -1。 属性自省方面,GetPropertyDescription 提供了整数、双精度、字符串三类枚举的描述文本,IndicatorTypeDescription() 则转调全局函数给出指标类型说明。Print(false) 只打印对象支持的属性,Print(true) 输出全部属性,方便在专家日志里排查配置漂移。 外汇与贵金属波动剧烈、杠杆风险高,跨周期索引错位可能导致信号延迟,建议先在策略测试器用历史数据验证 Index() 的返回值边界。
class="type">class="kw">string Symbol(class="type">void) class="kw">const { class="kw">return this.GetProperty(IND_DATA_PROP_SYMBOL); } class="type">class="kw">string IndicatorName(class="type">void) class="kw">const { class="kw">return this.GetProperty(IND_DATA_PROP_IND_NAME); } class="type">class="kw">string IndicatorShortName(class="type">void) class="kw">const { class="kw">return this.GetProperty(IND_DATA_PROP_IND_SHORTNAME); } class=class="str">"cmt">//--- Return bar index on the specified timeframe the object bar time falls into class="type">int Index(class="kw">const ENUM_TIMEFRAMES timeframe) class="kw">const { class="kw">return ::iBarShift(this.Symbol(),(timeframe==PERIOD_CURRENT ? ::Period() : timeframe),this.Time()); } class=class="str">"cmt">//--- Return Digits set for the object class="type">int Digits(class="type">void) class="kw">const { class="kw">return this.m_digits; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Description of properties of the object - indicator data | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//--- Return description of object&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string class="kw">property class="type">class="kw">string GetPropertyDescription(ENUM_IND_DATA_PROP_INTEGER class="kw">property); class="type">class="kw">string GetPropertyDescription(ENUM_IND_DATA_PROP_DOUBLE class="kw">property); class="type">class="kw">string GetPropertyDescription(ENUM_IND_DATA_PROP_STRING class="kw">property); class=class="str">"cmt">//--- Return indicator type description class="type">class="kw">string IndicatorTypeDescription(class="type">void) class="kw">const { class="kw">return ::IndicatorTypeDescription(this.IndicatorType()); } class=class="str">"cmt">//--- Display the description of object properties in the journal(full_prop=true - all properties, class="kw">false - supported ones only) class="type">void Print(class="kw">const class="type">bool full_prop=class="kw">false);
◍ 指标数据对象的比较与判等逻辑
在自建指标数据容器时,CDataInd 类用一套统一接口处理对象间的排序与判等,避免每次都比对所有字段写一堆 if。Compare() 按 mode 参数分流:小于 IND_DATA_PROP_INTEGER_TOTAL 走整型比较,中间段走双精度比较,再往后走字符串比较,返回 1 / -1 / 0 三态,直接兼容 CObject 派生类的集合排序。 IsEqual() 则是穷举式判等:先扫整型属性 0 到 BAR_PROP_INTEGER_TOTAL,再偏移双精度与字符串区段,任意一项不等立即 return false,全过才 return true。注意它起手用 beg=0, end=BAR_PROP_INTEGER_TOTAL,若你自定义整型属性总数与此宏不一致,拷贝这段代码会漏比或越界。 外汇与贵金属行情下这类对象常用于多周期指标快照比对,杠杆与滑点放大了微小数值差异,双精度比较建议自行加容差,不要直接依赖原版等于逻辑。
class=class="str">"cmt">//--- Display a class="type">class="kw">short description of the object in the journal class="kw">virtual class="type">void PrintShort(class="type">void); class=class="str">"cmt">//--- }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+-------------------------------------------------------------------+ class=class="str">"cmt">//| Compare CDataInd objects with each other by the specified class="kw">property| class=class="str">"cmt">//+-------------------------------------------------------------------+ class="type">int CDataInd::Compare(class="kw">const CObject *node,class="kw">const class="type">int mode=class="num">0) class="kw">const { class="kw">const CDataInd *obj_compared=node; class=class="str">"cmt">//--- compare integer properties of two objects if(mode<IND_DATA_PROP_INTEGER_TOTAL) { class="type">long value_compared=obj_compared.GetProperty((ENUM_IND_DATA_PROP_INTEGER)mode); class="type">long value_current=this.GetProperty((ENUM_IND_DATA_PROP_INTEGER)mode); class="kw">return(value_current>value_compared ? class="num">1 : value_current<value_compared ? -class="num">1 : class="num">0); } class=class="str">"cmt">//--- compare real properties of two objects else if(mode<IND_DATA_PROP_DOUBLE_TOTAL+IND_DATA_PROP_INTEGER_TOTAL) { class="type">class="kw">double value_compared=obj_compared.GetProperty((ENUM_IND_DATA_PROP_DOUBLE)mode); class="type">class="kw">double value_current=this.GetProperty((ENUM_IND_DATA_PROP_DOUBLE)mode); class="kw">return(value_current>value_compared ? class="num">1 : value_current<value_compared ? -class="num">1 : class="num">0); } class=class="str">"cmt">//--- compare class="type">class="kw">string properties of two objects else if(mode<IND_DATA_PROP_DOUBLE_TOTAL+IND_DATA_PROP_INTEGER_TOTAL+IND_DATA_PROP_STRING_TOTAL) { class="type">class="kw">string value_compared=obj_compared.GetProperty((ENUM_IND_DATA_PROP_STRING)mode); class="type">class="kw">string value_current=this.GetProperty((ENUM_IND_DATA_PROP_STRING)mode); class="kw">return(value_current>value_compared ? class="num">1 : value_current<value_compared ? -class="num">1 : class="num">0); } class="kw">return class="num">0; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Compare CDataInd objects with each other by all properties | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CDataInd::IsEqual(CDataInd *compared_obj) class="kw">const { class="type">int beg=class="num">0, end=BAR_PROP_INTEGER_TOTAL; for(class="type">int i=beg; i<end; i++) { ENUM_IND_DATA_PROP_INTEGER prop=(ENUM_IND_DATA_PROP_INTEGER)i; if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) class="kw">return class="kw">false; } beg=end; end+=IND_DATA_PROP_DOUBLE_TOTAL; for(class="type">int i=beg; i<end; i++) { ENUM_IND_DATA_PROP_DOUBLE prop=(ENUM_IND_DATA_PROP_DOUBLE)i; if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) class="kw">return class="kw">false; } beg=end; end+=IND_DATA_PROP_STRING_TOTAL; for(class="type">int i=beg; i<end; i++) { ENUM_IND_DATA_PROP_STRING prop=(ENUM_IND_DATA_PROP_STRING)i; if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) class="kw">return class="kw">false; } class="kw">return true; } class=class="str">"cmt">//+------------------------------------------------------------------+
「给指标对象绑定品种周期并打印自检」
把数据指标挂到具体品种和周期上,靠的是 SetSymbolPeriod 这个方法。它一次性写入三个属性:基准时间、交易品种字符串、以及 ENUM_TIMEFRAMES 周期枚举,后续该对象取价就锁定在这组上下文里。 Print 方法负责把对象属性铺到 MT5 Experts 日志里。整数、双精度、字符串三类属性分三段循环打印,full_prop 为 false 时只输出对象实际支持的属性,避免刷屏;设为 true 才会把全部枚举项都列出来。 PrintShort 则只吐一行极简信息:指标短名加缓冲区编号和索引。实测日志里能看到类似「AMA(EURUSD,H1) [Buffer 0, Index 0]」和「Examples\Custom Moving Average.ex5(EURUSD,H1) [Buffer 0, Index 1]」这样的输出,说明同一品种周期下不同加载实例的索引是递增分配的。 开 MT5 自己写一个 CDataInd 派生类,挂 EURUSD 的 H1 后调一次 PrintShort,就能确认你的实例索引有没有和已有指标撞车。外汇与贵金属杠杆高,这类底层绑定错误可能在回测和实盘切换时引发取价错位,需自行验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Set symbol, timeframe and object bar start time | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CDataInd::SetSymbolPeriod(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,class="kw">const class="type">class="kw">datetime time) { this.SetProperty(IND_DATA_PROP_TIME,time); this.SetProperty(IND_DATA_PROP_SYMBOL,symbol); this.SetProperty(IND_DATA_PROP_PERIOD,timeframe); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Display object properties in the journal | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CDataInd::Print(class="kw">const class="type">bool full_prop=class="kw">false) { ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_BEG)," (",this.IndicatorShortName(),") ============="); class="type">int beg=class="num">0, end=IND_DATA_PROP_INTEGER_TOTAL; for(class="type">int i=beg; i<end; i++) { ENUM_IND_DATA_PROP_INTEGER prop=(ENUM_IND_DATA_PROP_INTEGER)i; if(!full_prop && !this.SupportProperty(prop)) class="kw">continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("------"); beg=end; end+=IND_DATA_PROP_DOUBLE_TOTAL; for(class="type">int i=beg; i<end; i++) { ENUM_IND_DATA_PROP_DOUBLE prop=(ENUM_IND_DATA_PROP_DOUBLE)i; if(!full_prop && !this.SupportProperty(prop)) class="kw">continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("------"); beg=end; end+=IND_DATA_PROP_STRING_TOTAL; for(class="type">int i=beg; i<end; i++) { ENUM_IND_DATA_PROP_STRING prop=(ENUM_IND_DATA_PROP_STRING)i; if(!full_prop && !this.SupportProperty(prop)) class="kw">continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_END)," (",this.IndicatorShortName(),") =============\n"); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Display a class="type">class="kw">short description of the object in the journal | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CDataInd::PrintShort(class="type">void) { ::Print( this.IndicatorShortName(), " [",CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_BUFFER)," ",this.BufferNum(), ", ",CMessage::Text(MSG_SYM_STATUS_INDEX)," ",this.Index(this.Timeframe()),"]" ); } class=class="str">"cmt">//+------------------------------------------------------------------+