内容目录
- 概述
- 改进库类
- 抽象指标类
- 测试
- 下一步是什么?
概述
随着 DoEasy 函数库的开发,我们已有必要创建一个指标对象。 此类对象的存在将允许在程序里便捷地存储和使用已创建的所有指标。 指标对象构造的概念与主要函数库对象的概念没有不同,即:基准抽象对象及其后代依据其状态(对于指标 - 自定义和标准)阐明对象的从属关系。 我曾在最早的文章中谈及如何创建此类对象。
今天,创建基准抽象指标对象,并检查其创建结果。 在随后文章中,我将创建标准和自定义指标的对象。
除了遵循状态(标准和自定义)隶属关系之外,创建的每个指标对象都将按指标类型(分组)关联:
- 趋势指标
- 振荡器
- 成交量
- 箭头指标
因此,我们将能够在程序中按分组对指标进行排序。 我们不会在单独的分组中输入比尔·威廉姆斯的指标,因为它们每个都与指定的分组之一有从属关系。 因此,我认为没有必要输入另一个单独包括上面所列全部指标的分组。
改进库类
首先,在函数库里为指标对象添加必要的文本消息。
在文件 \MQL5\Include\DoEasy\Data.mqh 里加入新的消息索引:
//--- CBuffer //--- removed for the sake of space //--- ... //--- ... //--- ... MSG_LIB_TEXT_BUFFER_TEXT_STYLE_SOLID, // Solid line MSG_LIB_TEXT_BUFFER_TEXT_STYLE_DASH, // Dashed line MSG_LIB_TEXT_BUFFER_TEXT_STYLE_DOT, // Dotted line MSG_LIB_TEXT_BUFFER_TEXT_STYLE_DASHDOT, // Dot-dash line MSG_LIB_TEXT_BUFFER_TEXT_STYLE_DASHDOTDOT, // Dash - two dots //--- CIndicatorDE MSG_LIB_TEXT_IND_TEXT_STATUS, // Indicator status MSG_LIB_TEXT_IND_TEXT_STATUS_STANDART, // Standard indicator MSG_LIB_TEXT_IND_TEXT_STATUS_CUSTOM, // Custom indicator MSG_LIB_TEXT_IND_TEXT_TIMEFRAME, // Indicator timeframe MSG_LIB_TEXT_IND_TEXT_HANDLE, // Indicator handle MSG_LIB_TEXT_IND_TEXT_GROUP, // Indicator group MSG_LIB_TEXT_IND_TEXT_GROUP_TREND, // Trend indicator MSG_LIB_TEXT_IND_TEXT_GROUP_OSCILLATOR, // Oscillator MSG_LIB_TEXT_IND_TEXT_GROUP_VOLUMES, // Volumes MSG_LIB_TEXT_IND_TEXT_GROUP_ARROWS, // Arrow indicator MSG_LIB_TEXT_IND_TEXT_EMPTY_VALUE, // Empty value for plotting where nothing will be drawn: MSG_LIB_TEXT_IND_TEXT_SYMBOL, // Indicator symbol MSG_LIB_TEXT_IND_TEXT_NAME, // Indicator name MSG_LIB_TEXT_IND_TEXT_SHORTNAME, // Indicator short name }; //+------------------------------------------------------------------+
... 进而在同一文件中 - 与新添加索引相对应的文本消息:
{"Solid line"}, {"Broken line"}, {"Dotted line"}, {"Dash-dot line"}, {"Dash - two points"}, {"Indicator status"}, {"Standard indicator"}, {"Custom indicator"}, {"Indicator timeframe"}, {"Indicator handle"}, {"Indicator group"}, {"Trend indicator"}, {"Solid lineOscillator"}, {"Volumes"}, {"Arrow indicator"}, {"Empty value for plotting, for which there is no drawing"}, {"Indicator symbol"}, {"Indicator name"}, {"Indicator shortname"}, }; //+---------------------------------------------------------------------+
在文件 E:\MetaQuotes\MetaTrader 5\MQL5\Include\DoEasy\Defines.mqh 里,为函数库对象添加已变为标准的指标对象参数。。
鉴于所有这些对象最终都将存储在指标缓冲区集合列表之中,因此我们为它们引入自身的 ID:
//--- Collection list IDs #define COLLECTION_HISTORY_ID (0x777A) // Historical collection list ID #define COLLECTION_MARKET_ID (0x777B) // Market collection list ID #define COLLECTION_EVENTS_ID (0x777C) // Event collection list ID #define COLLECTION_ACCOUNT_ID (0x777D) // Account collection list ID #define COLLECTION_SYMBOLS_ID (0x777E) // Symbol collection list ID #define COLLECTION_SERIES_ID (0x777F) // Timeseries collection list ID #define COLLECTION_BUFFERS_ID (0x7780) // Indicator buffer collection list ID #define COLLECTION_INDICATORS_ID (0x7781) // Indicator collection list ID //--- Data parameters for file operations
在清单文件的末尾,添加所有必需的属性枚举,和集合列表中指标对象的排序条件:
//+------------------------------------------------------------------+ //| Data for working with indicators | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Abstract indicator status | //+------------------------------------------------------------------+ enum ENUM_INDICATOR_STATUS { INDICATOR_STATUS_STANDART, // Standard indicator INDICATOR_STATUS_CUSTOM, // Custom indicator }; //+------------------------------------------------------------------+ //| Indicator group | //+------------------------------------------------------------------+ enum ENUM_INDICATOR_GROUP { INDICATOR_GROUP_TREND, // Trend indicator INDICATOR_GROUP_OSCILLATOR, // Oscillator INDICATOR_GROUP_VOLUMES, // Volumes INDICATOR_GROUP_ARROWS, // Arrow indicator }; //+------------------------------------------------------------------+ //| Indicator integer properties | //+------------------------------------------------------------------+ enum ENUM_INDICATOR_PROP_INTEGER { INDICATOR_PROP_STATUS = 0, // Indicator status (from enumeration ENUM_INDICATOR_STATUS) INDICATOR_PROP_TIMEFRAME, // Indicator timeframe INDICATOR_PROP_HANDLE, // Indicator handle INDICATOR_PROP_GROUP, // Indicator group }; #define INDICATOR_PROP_INTEGER_TOTAL (4) // Total number of indicator integer properties #define INDICATOR_PROP_INTEGER_SKIP (0) // Number of indicator properties not used in sorting //+------------------------------------------------------------------+ //| Indicator real properties | //+------------------------------------------------------------------+ enum ENUM_INDICATOR_PROP_DOUBLE { INDICATOR_PROP_EMPTY_VALUE = INDICATOR_PROP_INTEGER_TOTAL,// Empty value for plotting where nothing will be drawn }; #define INDICATOR_PROP_DOUBLE_TOTAL (1) // Total number of real indicator properties #define INDICATOR_PROP_DOUBLE_SKIP (0) // Number of indicator properties not used in sorting //+------------------------------------------------------------------+ //| Indicator string properties | //+------------------------------------------------------------------+ enum ENUM_INDICATOR_PROP_STRING { INDICATOR_PROP_SYMBOL = (INDICATOR_PROP_INTEGER_TOTAL+INDICATOR_PROP_DOUBLE_TOTAL), // Indicator symbol INDICATOR_PROP_NAME, // Indicator name INDICATOR_PROP_SHORTNAME, // Indicator short name }; #define INDICATOR_PROP_STRING_TOTAL (3) // Total number of indicator string properties //+------------------------------------------------------------------+ //| Possible indicator sorting criteria | //+------------------------------------------------------------------+ #define FIRST_INDICATOR_DBL_PROP (INDICATOR_PROP_INTEGER_TOTAL-INDICATOR_PROP_INTEGER_SKIP) #define FIRST_INDICATOR_STR_PROP (INDICATOR_PROP_INTEGER_TOTAL-INDICATOR_PROP_INTEGER_SKIP+INDICATOR_PROP_DOUBLE_TOTAL-INDICATOR_PROP_DOUBLE_SKIP) enum ENUM_SORT_INDICATOR_MODE { //--- Sort by integer properties SORT_BY_INDICATOR_INDEX_STATUS = 0, // Sort by indicator status SORT_BY_INDICATOR_TIMEFRAME, // Sort by indicator timeframe SORT_BY_INDICATOR_HANDLE, // Sort by indicator handle SORT_BY_INDICATOR_GROUP, // Sort by indicator group //--- Sort by real properties SORT_BY_INDICATOR_EMPTY_VALUE = FIRST_INDICATOR_DBL_PROP,// Sort by the empty value for plotting where nothing will be drawn //--- Sort by string properties SORT_BY_INDICATOR_SYMBOL = FIRST_INDICATOR_STR_PROP, // Sort by indicator symbol SORT_BY_INDICATOR_NAME, // Sort by indicator name SORT_BY_INDICATOR_SHORTNAME, // Sort by indicator short name }; //+------------------------------------------------------------------+
此处无需解释。
抽象指标类
继续创建基准抽象指标类。
在文件夹 \MQL5\Include\DoEasy\Objects\Indicators\ 下名为 IndicatorDE.mqh 的文件里创建一个新类 CIndicatorDE。 鉴于在标准库里 CIndicator 类已经存在,所以我在类和文件名中添加了首字母缩写 DE(DoEasy)。
新类派生自函数库 CBaseObj,其为所有对象的基准对象类。
类主体包含设置和获取对象属性的方法,这些方法我们之前均已研究过。 只需查看它们的清单,并进一步分析一些类方法:
//+------------------------------------------------------------------+ //| Ind.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Services\Select.mqh" #include "..\..\Objects\BaseObj.mqh" //+------------------------------------------------------------------+ //| Abstract indicator class | //+------------------------------------------------------------------+ class CIndicatorDE : public CBaseObj { private: long m_long_prop[INDICATOR_PROP_INTEGER_TOTAL]; // Integer properties double m_double_prop[INDICATOR_PROP_DOUBLE_TOTAL]; // Real properties string m_string_prop[INDICATOR_PROP_STRING_TOTAL]; // String properties MqlParam m_mql_params[]; // Array of indicator parameters //--- Return the index of the array the buffer's (1) double and (2) string properties are actually located at int IndexProp(ENUM_INDICATOR_PROP_DOUBLE property) const { return(int)property-INDICATOR_PROP_INTEGER_TOTAL; } int IndexProp(ENUM_INDICATOR_PROP_STRING property) const { return(int)property-INDICATOR_PROP_INTEGER_TOTAL-INDICATOR_PROP_DOUBLE_TOTAL;} //--- Comare (1) structures MqlParam, (2) array of structures MqlParam between each other bool IsEqualMqlParams(MqlParam &struct1,MqlParam &struct2) const; bool IsEqualMqlParamArrays(MqlParam &compared_struct[]) const; protected: public: //--- Protected parametric constructor CIndicatorDE(ENUM_INDICATOR ind_type, string symbol, ENUM_TIMEFRAMES timeframe, ENUM_INDICATOR_STATUS status, ENUM_INDICATOR_GROUP group, string name, string shortname, MqlParam &indicator_params[]); public: //--- Default constructor CIndicatorDE(void){;} //--- Destructor ~CIndicatorDE(void); //--- Set buffer's (1) integer, (2) real and (3) string properties void SetProperty(ENUM_INDICATOR_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; } void SetProperty(ENUM_INDICATOR_PROP_DOUBLE property,double value) { this.m_double_prop[this.IndexProp(property)]=value; } void SetProperty(ENUM_INDICATOR_PROP_STRING property,string value) { this.m_string_prop[this.IndexProp(property)]=value; } //--- Return (1) integer, (2) real and (3) string buffer properties from the properties array long GetProperty(ENUM_INDICATOR_PROP_INTEGER property) const { return this.m_long_prop[property]; } double GetProperty(ENUM_INDICATOR_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)]; } string GetProperty(ENUM_INDICATOR_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(property)]; } //--- Return description of buffer's (1) integer, (2) real and (3) string properties string GetPropertyDescription(ENUM_INDICATOR_PROP_INTEGER property); string GetPropertyDescription(ENUM_INDICATOR_PROP_DOUBLE property); string GetPropertyDescription(ENUM_INDICATOR_PROP_STRING property); //--- Return the flag of the buffer supporting the property virtual bool SupportProperty(ENUM_INDICATOR_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_INDICATOR_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_INDICATOR_PROP_STRING property) { return true; } //--- Compare CIndicatorDE objects by all possible properties (for sorting the lists by a specified indicator object property) virtual int Compare(const CObject *node,const int mode=0) const; //--- Compare CIndicatorDE objects by all properties (to search for equal indicator objects) bool IsEqual(CIndicatorDE* compared_obj) const; //--- Set indicator’s (1) group, (2) empty value of buffers, (3) name, (4) short name void SetGroup(const ENUM_INDICATOR_GROUP group) { this.SetProperty(INDICATOR_PROP_GROUP,group); } void SetEmptyValue(const double value) { this.SetProperty(INDICATOR_PROP_EMPTY_VALUE,value); } void SetName(const string name) { this.SetProperty(INDICATOR_PROP_NAME,name); } void SetShortName(const string shortname) { this.SetProperty(INDICATOR_PROP_SHORTNAME,shortname); } //--- Return indicator’s (1) status, (2) group, (3) timeframe, (4) handle, (5) empty value of buffers, (6) name, (7) short name, (8) symbol ENUM_INDICATOR_STATUS Status(void) const { return (ENUM_INDICATOR_STATUS)this.GetProperty(INDICATOR_PROP_STATUS);} ENUM_INDICATOR_GROUP Group(void) const { return (ENUM_INDICATOR_GROUP)this.GetProperty(INDICATOR_PROP_GROUP); } ENUM_TIMEFRAMES Timeframe(void) const { return (ENUM_TIMEFRAMES)this.GetProperty(INDICATOR_PROP_TIMEFRAME); } int Handle(void) const { return (int)this.GetProperty(INDICATOR_PROP_HANDLE); } double EmptyValue(void) const { return this.GetProperty(INDICATOR_PROP_EMPTY_VALUE); } string Name(void) const { return this.GetProperty(INDICATOR_PROP_NAME); } string ShortName(void) const { return this.GetProperty(INDICATOR_PROP_SHORTNAME); } string Symbol(void) const { return this.GetProperty(INDICATOR_PROP_SYMBOL); } //--- Return description of indicator’s (1) status, (2) group, (3) timeframe, (4) empty value string GetStatusDescription(void) const; string GetGroupDescription(void) const; string GetTimeframeDescription(void) const; string GetEmptyValueDescription(void) const; //--- Display the description of indicator object properties in the journal (full_prop=true - all properties, false - supported ones only) void Print(const bool full_prop=false); //--- Display a short description of indicator object in the journal (implementation in the descendants) virtual void PrintShort(void) {;} }; //+------------------------------------------------------------------+
把封闭参数的类构造函数临时公开 — 今天,我们打算测试创建单个类对象是否能成功。 由此原因,构造函数必须为外部调用开放。
如您所见,所有类方法都在其逻辑中重复所有早先研究过的函数库对象。 因此,读者必须熟悉他们的目的和操作。 与所有其他的对象析构函数隐式创建且不可用相反,此处我添加了析构函数,因为创建的指标对象必须被析构。 我们来分析在类主体外部执行的方法如何实现。
在类析构函数里析构创建的指标对象:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CIndicatorDE::~CIndicatorDE(void) { ::IndicatorRelease(this.Handle()); } //+------------------------------------------------------------------+
封闭参数的类构造函数:
//+------------------------------------------------------------------+ //| Closed parametric constructor | //+------------------------------------------------------------------+ CIndicatorDE::CIndicatorDE(ENUM_INDICATOR ind_type, string symbol, ENUM_TIMEFRAMES timeframe, ENUM_INDICATOR_STATUS status, ENUM_INDICATOR_GROUP group, string name, string shortname, MqlParam &indicator_params[]) { //--- Set collection ID to the object this.m_type=COLLECTION_INDICATORS_ID; //--- If parameter array value passed to constructor is more than zero //--- fill in the array of object parameters with data from the array passed to constructor int count=::ArrayResize(m_mql_params,::ArraySize(indicator_params)); for(int i=0;i<count;i++) { this.m_mql_params[i].type=indicator_params[i].type; this.m_mql_params[i].double_value=indicator_params[i].double_value; this.m_mql_params[i].integer_value=indicator_params[i].integer_value; this.m_mql_params[i].string_value=indicator_params[i].string_value; } //--- Create indicator handle int handle=::IndicatorCreate(symbol,timeframe,ind_type,count,indicator_params); //--- Save integer properties this.m_long_prop[INDICATOR_PROP_STATUS] = status; this.m_long_prop[INDICATOR_PROP_GROUP] = group; this.m_long_prop[INDICATOR_PROP_TIMEFRAME] = timeframe; this.m_long_prop[INDICATOR_PROP_HANDLE] = handle; //--- Save real properties this.m_double_prop[this.IndexProp(INDICATOR_PROP_EMPTY_VALUE)]=EMPTY_VALUE; //--- Save string properties this.m_string_prop[this.IndexProp(INDICATOR_PROP_SYMBOL)] = (symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_string_prop[this.IndexProp(INDICATOR_PROP_NAME)] = name; this.m_string_prop[this.IndexProp(INDICATOR_PROP_SHORTNAME)]= shortname; } //+------------------------------------------------------------------+
创建指标对象的所有必需参数都传递给构造函数。
如果参数数组 MqlParam&indicator_params[] 含有零值,则在创建指标时 IndicatorCreate() 函数并不会取用必须包含在数组中的参数。
进而,简单地把传递给该方法的所有值保存在相应的对象属性字段之中。
为了在两个指标对象间进行比较,两个对象的所有字段均需进行排序和搜索,然后进行比较。 指标对象包含 MqlParam 结构的数组 — 它们也须相互比较。 它们将被逐元素比较。 为此,我们已有两种方法:其一将两个 MqlParam 结构彼此进行比较,以及比较两个结构数组的方法。
//--- Method to compare two MqlParam structures with each other:
//+------------------------------------------------------------------+ //| Compare MqlParam structures with each other | //+------------------------------------------------------------------+ bool CIndicatorDE::IsEqualMqlParams(MqlParam &struct1,MqlParam &struct2) const { bool res= (struct1.type!=struct2.type ? false : (struct1.type==TYPE_STRING && struct1.string_value==struct2.string_value) || (struct1.type<TYPE_STRING && struct1.type>TYPE_ULONG && struct1.double_value==struct2.double_value) || (struct1.type<TYPE_FLOAT && struct1.integer_value==struct2.integer_value) ? true : false ); return res; } //+------------------------------------------------------------------+
这很简单。
检查结构数据类型的字段,以及这些结构数据类型的比较结果是否不对应 - 返回 false。
如果数据类型为字符串,并且两个结构的数据相同 — 返回 true。
如果数据类型为实数型,且两个结构数据相同 — 返回 true。
如果数据类型为整数型,且两个结构的数据相同 — 返回 true。
在任何其他情况下 — 返回 false。
比较 MqlParam 结构数组的方法:
//+------------------------------------------------------------------+ //| Compare array of MqlParam structures with each other | //+------------------------------------------------------------------+ bool CIndicatorDE::IsEqualMqlParamArrays(MqlParam &compared_struct[]) const { int total=::ArraySize(this.m_mql_params); int size=::ArraySize(compared_struct); if(total!=size || total==0 || size==0) return false; for(int i=0;i<total;i++) { if(!this.IsEqualMqlParams(this.m_mql_params[i],compared_struct[i])) return false; } return true; } //+------------------------------------------------------------------+
如果两个数组的值不相等或它们当中的任何一个为零 — 返回 false。
然后,按数组中的结构量数循环比较两个数组的如下每个结构,如果它们不相等,则返回 false 。
成功检查两个数组中包含的所有结构后,返回 true。
按所有可能的属性比较 CIndicatorDE 对象的方法:
//+------------------------------------------------------------------+ //| Compare CIndicatorDE objects with each other | //| by all possible properties | //+------------------------------------------------------------------+ int CIndicatorDE::Compare(const CObject *node,const int mode=0) const { const CIndicatorDE *compared_obj=node; //--- compare integer properties of two indicators if(mode<INDICATOR_PROP_INTEGER_TOTAL) { long value_compared=compared_obj.GetProperty((ENUM_INDICATOR_PROP_INTEGER)mode); long value_current=this.GetProperty((ENUM_INDICATOR_PROP_INTEGER)mode); return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0); } //--- compare real properties of two indicators else if(mode<INDICATOR_PROP_INTEGER_TOTAL+INDICATOR_PROP_DOUBLE_TOTAL) { double value_compared=compared_obj.GetProperty((ENUM_INDICATOR_PROP_DOUBLE)mode); double value_current=this.GetProperty((ENUM_INDICATOR_PROP_DOUBLE)mode); return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0); } //--- compare string properties of two indicators else if(mode<INDICATOR_PROP_INTEGER_TOTAL+INDICATOR_PROP_DOUBLE_TOTAL+INDICATOR_PROP_STRING_TOTAL) { string value_compared=compared_obj.GetProperty((ENUM_INDICATOR_PROP_STRING)mode); string value_current=this.GetProperty((ENUM_INDICATOR_PROP_STRING)mode); return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0); } return 0; } //+------------------------------------------------------------------+
按所有属性比较 CIndicatorDE 对象的方法:
//+------------------------------------------------------------------+ //| Compare CIndicatorDE objects with each other by all properties | //+------------------------------------------------------------------+ bool CIndicatorDE::IsEqual(CIndicatorDE *compared_obj) const { if(!IsEqualMqlParamArrays(compared_obj.m_mql_params)) return false; int beg=0, end=INDICATOR_PROP_INTEGER_TOTAL; for(int i=beg; i<end; i++) { ENUM_INDICATOR_PROP_INTEGER prop=(ENUM_INDICATOR_PROP_INTEGER)i; if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; } beg=end; end+=INDICATOR_PROP_DOUBLE_TOTAL; for(int i=beg; i<end; i++) { ENUM_INDICATOR_PROP_DOUBLE prop=(ENUM_INDICATOR_PROP_DOUBLE)i; if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; } beg=end; end+=INDICATOR_PROP_STRING_TOTAL; for(int i=beg; i<end; i++) { ENUM_INDICATOR_PROP_STRING prop=(ENUM_INDICATOR_PROP_STRING)i; if(this.GetProperty(prop)!=compared_obj.GetProperty(prop)) return false; } return true; } //+------------------------------------------------------------------+
CIndicatorDE 对象的两种比较方法与其他函数库对象的同名方法在逻辑上是相同的,且我们已经反复研究过它们了。 在第二种方法(IsEqual)里的仅有区别 — 首先,调用比较对象的两个 MqlParam 结构数组的方法。 如果它们不相等,则对象不相等 - 返回 false。 进而,按对象的所有字段比较对象。
返回指标整数型,实数型和字符串型属性说明的方法:
//+------------------------------------------------------------------+ //| Return description of indicator's integer property | //+------------------------------------------------------------------+ string CIndicatorDE::GetPropertyDescription(ENUM_INDICATOR_PROP_INTEGER property) { return ( property==INDICATOR_PROP_STATUS ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_STATUS)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetStatusDescription() ) : property==INDICATOR_PROP_GROUP ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_GROUP)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetGroupDescription() ) : property==INDICATOR_PROP_TIMEFRAME ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_TIMEFRAME)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetTimeframeDescription() ) : property==INDICATOR_PROP_HANDLE ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_HANDLE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : "" ); } //+------------------------------------------------------------------+ //| Return description of indicator's real property | //+------------------------------------------------------------------+ string CIndicatorDE::GetPropertyDescription(ENUM_INDICATOR_PROP_DOUBLE property) { return ( property==INDICATOR_PROP_EMPTY_VALUE ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_EMPTY_VALUE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetEmptyValueDescription() ) : "" ); } //+------------------------------------------------------------------+ //| Return description of indicator's string property | //+------------------------------------------------------------------+ string CIndicatorDE::GetPropertyDescription(ENUM_INDICATOR_PROP_STRING property) { return ( property==INDICATOR_PROP_SYMBOL ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_SYMBOL)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.Symbol() ) : property==INDICATOR_PROP_NAME ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_NAME)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(this.Name()==NULL || this.Name()=="" ? CMessage::Text(MSG_LIB_PROP_NOT_SET) : "\""+this.Name()+"\"") ) : property==INDICATOR_PROP_SHORTNAME ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_SHORTNAME)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(this.ShortName()==NULL || this.ShortName()=="" ? CMessage::Text(MSG_LIB_PROP_NOT_SET) : "\""+this.ShortName()+"\"") ) : "" ); } //+------------------------------------------------------------------+
每个函数库对象都有相同的方法,我们早前也曾研究过它们。
其它显示各种指标对象属性说明的方法也与其他函数库对象中的同名方法相同。 因此,出于个人学习目的,我们简单地看一下它们的清单:
//+------------------------------------------------------------------+ //| Return indicator status description | //+------------------------------------------------------------------+ string CIndicatorDE::GetStatusDescription(void) const { return ( this.Status()==INDICATOR_STATUS_CUSTOM ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_STATUS_CUSTOM) : this.Status()==INDICATOR_STATUS_STANDART ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_STATUS_STANDART) : "Unknown" ); } //+------------------------------------------------------------------+ //| Return indicator group description | //+------------------------------------------------------------------+ string CIndicatorDE::GetGroupDescription(void) const { return ( this.Group()==INDICATOR_GROUP_TREND ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_GROUP_TREND) : this.Group()==INDICATOR_GROUP_OSCILLATOR ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_GROUP_OSCILLATOR) : this.Group()==INDICATOR_GROUP_VOLUMES ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_GROUP_VOLUMES) : this.Group()==INDICATOR_GROUP_ARROWS ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_GROUP_ARROWS) : "Any" ); } //+------------------------------------------------------------------+ //| Return description of timeframe used | //+------------------------------------------------------------------+ string CIndicatorDE::GetTimeframeDescription(void) const { string timeframe=TimeframeDescription(this.Timeframe()); return(this.Timeframe()==PERIOD_CURRENT ? CMessage::Text(MSG_LIB_TEXT_PERIOD_CURRENT)+" ("+timeframe+")" : timeframe); } //+------------------------------------------------------------------+ //| Return description of the set empty value | //+------------------------------------------------------------------+ string CIndicatorDE::GetEmptyValueDescription(void) const { double value=fabs(this.EmptyValue()); return(value<EMPTY_VALUE ? ::DoubleToString(this.EmptyValue(),(this.EmptyValue()==0 ? 1 : 8)) : (this.EmptyValue()>0 ? "EMPTY_VALUE" : "-EMPTY_VALUE")); } //+------------------------------------------------------------------+ //| Display indicator properties in the journal | //+------------------------------------------------------------------+ void CIndicatorDE::Print(const bool full_prop=false) { ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_BEG),": \"",this.GetStatusDescription(),"\" ============="); int beg=0, end=INDICATOR_PROP_INTEGER_TOTAL; for(int i=beg; i<end; i++) { ENUM_INDICATOR_PROP_INTEGER prop=(ENUM_INDICATOR_PROP_INTEGER)i; if(!full_prop && !this.SupportProperty(prop)) continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("------"); beg=end; end+=INDICATOR_PROP_DOUBLE_TOTAL; for(int i=beg; i<end; i++) { ENUM_INDICATOR_PROP_DOUBLE prop=(ENUM_INDICATOR_PROP_DOUBLE)i; if(!full_prop && !this.SupportProperty(prop)) continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("------"); beg=end; end+=INDICATOR_PROP_STRING_TOTAL; for(int i=beg; i<end; i++) { ENUM_INDICATOR_PROP_STRING prop=(ENUM_INDICATOR_PROP_STRING)i; if(!full_prop && !this.SupportProperty(prop)) continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("================== ",CMessage::Text(MSG_LIB_PARAMS_LIST_END),": \"",this.GetStatusDescription(),"\" ==================\n"); } //+------------------------------------------------------------------+
这是指标对象的完整组成。 很有可能我们会进一步改进它。 但就目前而言,检查对象的创建已足够了。
现在,我们应该能够有效地运用函数库中的新指标对象列表:对它们进行排序,并根据预设条件选择所需的指标。 为此,在文件 \MQL5\Include\DoEasy\Services\Select.mqh 中添加基准抽象指标的类文件
//+------------------------------------------------------------------+ //| Select.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include <Arrays\ArrayObj.mqh> #include "..\Objects\Orders\Order.mqh" #include "..\Objects\Events\Event.mqh" #include "..\Objects\Accounts\Account.mqh" #include "..\Objects\Symbols\Symbol.mqh" #include "..\Objects\PendRequest\PendRequest.mqh" #include "..\Objects\Series\SeriesDE.mqh" #include "..\Objects\Indicators\Buffer.mqh" #include "..\Objects\Indicators\IndicatorDE.mqh" //+------------------------------------------------------------------+
在类主体的末尾声明操控指标对象列表的方法:
//+------------------------------------------------------------------+ //| Methods of working with indicators | //+------------------------------------------------------------------+ //--- Return the list of indicators with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *ByIndicatorProperty(CArrayObj *list_source,ENUM_INDICATOR_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByIndicatorProperty(CArrayObj *list_source,ENUM_INDICATOR_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByIndicatorProperty(CArrayObj *list_source,ENUM_INDICATOR_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode); //--- Return the indicator index in the list with the maximum value of the indicator's (1) integer, (2) real and (3) string property static int FindIndicatorMax(CArrayObj *list_source,ENUM_INDICATOR_PROP_INTEGER property); static int FindIndicatorMax(CArrayObj *list_source,ENUM_INDICATOR_PROP_DOUBLE property); static int FindIndicatorMax(CArrayObj *list_source,ENUM_INDICATOR_PROP_STRING property); //--- Return the indicator index in the list with the minimum value of the indicator's (1) integer, (2) real and (3) string property static int FindIndicatorMin(CArrayObj *list_source,ENUM_INDICATOR_PROP_INTEGER property); static int FindIndicatorMin(CArrayObj *list_source,ENUM_INDICATOR_PROP_DOUBLE property); static int FindIndicatorMin(CArrayObj *list_source,ENUM_INDICATOR_PROP_STRING property); //--- }; //+------------------------------------------------------------------+
操控指标对象的所有方法也是绝对标准的,并且与早期在其他函数库对象集合列表里搜索和排序的设置方法相同。 它们都已研究过了。 我们简单地分析一下它们的实现,出于个人学习和复习研习的资料:
//+------------------------------------------------------------------+ //| Methods of working with indicator lists | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Return the list of indicators with one of integer | //| properties meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByIndicatorProperty(CArrayObj *list_source,ENUM_INDICATOR_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); ListStorage.Add(list); int total=list_source.Total(); for(int i=0; i<total; i++) { CIndicatorDE *obj=list_source.At(i); if(!obj.SupportProperty(property)) continue; long obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the list of indicators with one of real | //| properties meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByIndicatorProperty(CArrayObj *list_source,ENUM_INDICATOR_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); ListStorage.Add(list); for(int i=0; i<list_source.Total(); i++) { CIndicatorDE *obj=list_source.At(i); if(!obj.SupportProperty(property)) continue; double obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the list of indicators with one of string | //| properties meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByIndicatorProperty(CArrayObj *list_source,ENUM_INDICATOR_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); ListStorage.Add(list); for(int i=0; i<list_source.Total(); i++) { CIndicatorDE *obj=list_source.At(i); if(!obj.SupportProperty(property)) continue; string obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the indicator index in the list | //| with the maximum integer property value | //+------------------------------------------------------------------+ int CSelect::FindIndicatorMax(CArrayObj *list_source,ENUM_INDICATOR_PROP_INTEGER property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CIndicatorDE *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CIndicatorDE *obj=list_source.At(i); long obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); long obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the indicator index in the list | //| with the maximum real property value | //+------------------------------------------------------------------+ int CSelect::FindIndicatorMax(CArrayObj *list_source,ENUM_INDICATOR_PROP_DOUBLE property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CIndicatorDE *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CIndicatorDE *obj=list_source.At(i); double obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); double obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the indicator index in the list | //| with the maximum string property value | //+------------------------------------------------------------------+ int CSelect::FindIndicatorMax(CArrayObj *list_source,ENUM_INDICATOR_PROP_STRING property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CIndicatorDE *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CIndicatorDE *obj=list_source.At(i); string obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); string obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the indicator index in the list | //| with the minimum integer property value | //+------------------------------------------------------------------+ int CSelect::FindIndicatorMin(CArrayObj* list_source,ENUM_INDICATOR_PROP_INTEGER property) { int index=0; CIndicatorDE *min_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CIndicatorDE *obj=list_source.At(i); long obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); long obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the indicator index in the list | //| with the minimum real property value | //+------------------------------------------------------------------+ int CSelect::FindIndicatorMin(CArrayObj* list_source,ENUM_INDICATOR_PROP_DOUBLE property) { int index=0; CIndicatorDE *min_obj=NULL; int total=list_source.Total(); if(total== 0) return WRONG_VALUE; for(int i=1; i<total; i++) { CIndicatorDE *obj=list_source.At(i); double obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); double obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the indicator index in the list | //| with the minimum string property value | //+------------------------------------------------------------------+ int CSelect::FindIndicatorMin(CArrayObj* list_source,ENUM_INDICATOR_PROP_STRING property) { int index=0; CIndicatorDE *min_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CIndicatorDE *obj=list_source.At(i); string obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); string obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+
此刻,在我们的函数库指标是在 CBuffersCollection 指标缓冲区类集合内创建并使用。 我为指标对象创建了一个单独的类,反推,这些对象又被收集到其集合类当中。 可从此集合里访问指标对象。 同时,从今天开始,在 CBuffersCollection 类中进行操作,我们仅需创建指标对象,并检查其是否成功。
在后续的文章中,当创建抽象指标的衍生类时,我将其收集到集合当中,且在 CBuffersCollection 类中,我不会不直接与指标一起操作(如我们现在所做的那样),而是使用指标集合类。
今天,我们的目的是创建并检查指标对象创建过程这个事实。 因此,改进只会涉及一个缓冲区集合类的方法,即创建加速器(AD)指标的方法。
针对跨平台性质在创建指标对象方法里进行了修改;在日志中打印其数据,并立即删除该对象。 这些就是检查抽象指标对象创建所需要做的全部工作。
打开指标缓冲区集合类文件 \MQL5\Include\DoEasy\Collections\BuffersCollection.mqh,并进行必要的改进。
为了用 IndicatorCreate() 函数创建指标对象,需要将许多指标的必需参数传递给它。 这些参数是利用 MqlParam 数组传递的,该数组是专门为此目的而开发的。
在类的私密部分声明这个数组:
//+------------------------------------------------------------------+ //| Collection of indicator buffers | //+------------------------------------------------------------------+ class CBuffersCollection : public CObject { private: CListObj m_list; // Buffer object list CTimeSeriesCollection *m_timeseries; // Pointer to the timeseries collection object MqlParam m_mql_param[]; // Array of indicator parameters //--- Return the index of the (1) last, (2) next drawn and (3) basic buffer int GetIndexLastPlot(void); int GetIndexNextPlot(void); int GetIndexNextBase(void); //--- Create a new buffer object and place it to the collection list bool CreateBuffer(ENUM_BUFFER_STATUS status); //--- Get data of the necessary timeseries and bars for working with a single buffer bar, and return the number of bars int GetBarsData(CBuffer *buffer,const int series_index,int &index_bar_period); public:
由于经过检查,仅通过一种方法 CreateAC() 创建加速振荡器指标对象,故所有改进将仅涉及此方法。 所有其他用于创建指标缓冲区的方法,将会在接下来的文章中进行改进。
针对 MQL5 和针对 MQL4 的跨平台性质,将方法化分成分别的两个模块。 但与此同时,对于 MQL4,仅创建第二个缓冲区,用以显示第二种颜色(加速振荡器为双色)。 但我不会改变直方图的颜色,这实际上意味着在 MQL4 中会交替显示两个不同的缓冲区,在视觉上则是显示不同的指标颜色。 今天,我做了一件完全不同的事情。
在该方法的最开始,添加创建指标对象的代码,将所创建对象的数据输出到日志,并删除对象:
//+------------------------------------------------------------------+ //| Create multi-symbol multi-period AC | //+------------------------------------------------------------------+ int CBuffersCollection::CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE) { //--- To check it, create indicator object, print its data and remove it at once ::ArrayResize(this.m_mql_param,0); CIndicatorDE *indicator=new CIndicatorDE(IND_AC,symbol,timeframe,INDICATOR_STATUS_STANDART,INDICATOR_GROUP_OSCILLATOR,"Accelerator Oscillator","AC("+symbol+","+TimeframeDescription(timeframe)+")",this.m_mql_param); indicator.Print(); delete indicator; //--- Create indicator handle and set default ID int handle= #ifdef __MQL5__ ::iAC(symbol,timeframe) #else 0 #endif ; int identifier=(id==WRONG_VALUE ? IND_AC : id); color array_colors[3]={clrGreen,clrRed,clrGreen}; CBuffer *buff=NULL; if(handle!=INVALID_HANDLE) { //--- Create histogram buffer from the zero line this.CreateHistogram(); //--- Get the last created buffer object (drawn) and set to it all necessary parameters buff=this.GetLastCreateBuffer(); if(buff==NULL) return INVALID_HANDLE; buff.SetSymbol(symbol); buff.SetTimeframe(timeframe); buff.SetID(identifier); buff.SetIndicatorHandle(handle); buff.SetIndicatorType(IND_AC); buff.SetLineMode(INDICATOR_LINE_MODE_MAIN); buff.SetShowData(true); buff.SetLabel("AC("+symbol+","+TimeframeDescription(timeframe)+")"); buff.SetIndicatorName("Accelerator Oscillator"); buff.SetIndicatorShortName("AC("+symbol+","+TimeframeDescription(timeframe)+")"); #ifdef __MQL5__ buff.SetColors(array_colors); #else buff.SetColor(array_colors[0]); buff.SetIndicatorLineAdditionalNumber(0); #endif //--- MQL5 #ifdef __MQL5__ //--- Create calculated buffer, in which standard indicator data will be stored this.CreateCalculate(); //--- Get the last created buffer object (calculated) and set to it all necessary parameters buff=this.GetLastCreateBuffer(); if(buff==NULL) return INVALID_HANDLE; buff.SetSymbol(symbol); buff.SetTimeframe(timeframe); buff.SetID(identifier); buff.SetIndicatorHandle(handle); buff.SetIndicatorType(IND_AC); buff.SetLineMode(INDICATOR_LINE_MODE_MAIN); buff.SetEmptyValue(EMPTY_VALUE); buff.SetLabel("AC("+symbol+","+TimeframeDescription(timeframe)+")"); buff.SetIndicatorName("Accelerator Oscillator"); buff.SetIndicatorShortName("AC("+symbol+","+TimeframeDescription(timeframe)+")"); //--- MQL4 #else //--- Create histogram buffer from the zero line for buffer of the second color this.CreateHistogram(); //--- Get the last created buffer object (drawn) and set to it all necessary parameters buff=this.GetLastCreateBuffer(); if(buff==NULL) return INVALID_HANDLE; buff.SetSymbol(symbol); buff.SetTimeframe(timeframe); buff.SetID(identifier); buff.SetIndicatorHandle(handle); buff.SetIndicatorType(IND_AC); buff.SetLineMode(INDICATOR_LINE_MODE_MAIN); buff.SetShowData(true); buff.SetLabel("AC("+symbol+","+TimeframeDescription(timeframe)+")"); buff.SetIndicatorName("Accelerator Oscillator"); buff.SetIndicatorShortName("AC("+symbol+","+TimeframeDescription(timeframe)+")"); #ifdef __MQL5__ buff.SetColors(array_colors); #else buff.SetColor(array_colors[1]); buff.SetIndicatorLineAdditionalNumber(1); #endif #endif } return handle; } //+------------------------------------------------------------------+
由于加速振荡器标准指标不包含任何参数,因此在由 IndicatorCreate() 函数创建指标句柄时,我无需使用指标参数数组。
重置参数数组的大小。
创建一个新的指标对象,所需的所有数据已传递给其构造函数,
立即打印新创建的对象的数据(我不会检查对象的创建是否成功,因为这只是测试)。
并删除该对象 — 为了避免内存泄露。
在后续文章中,创建基准抽象指标的衍生对象,并将其放置到指标集合中之后,添加创建指标缓冲区的其余方法。 今天,即使是这样的检查也已足够了。
在依据缓冲对象品种/时间按时间序列索引为指定标准指标的缓冲设置当前图表数值的方法中,为了令工件能够实现跨平台性质,已在模块中针对 MQL5 添加了单独代码,且对于 MQL4 ,仅需针对单缓冲区标准指标(我不会再实现 MQL4 的代码,因为如今我并不需要它):
//+------------------------------------------------------------------+ //| Set values for the current chart to buffers of the specified | //| standard indicator by the timeseries index in accordance | //| with buffer object symbol/period | //+------------------------------------------------------------------+ bool CBuffersCollection::SetDataBufferStdInd(const ENUM_INDICATOR ind_type,const int id,const int series_index,const datetime series_time,const char color_index=WRONG_VALUE) { //--- Get the list of buffer objects by type and ID CArrayObj *list=this.GetListBufferByTypeID(ind_type,id); if(list==NULL || list.Total()==0) { ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NO_BUFFER_OBJ)); return false; } //--- Get the list of drawn buffers with ID CArrayObj *list_data=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL); list_data=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_TYPE,ind_type,EQUAL); //--- Get the list of calculated buffers with ID CArrayObj *list_calc=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL); list_calc=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_TYPE,ind_type,EQUAL); //--- Leave if any of the lists is empty if(list_data.Total()==0 #ifdef __MQL5__ || list_calc.Total()==0 #endif ) return false; //--- Declare necessary objects and variables CBuffer *buffer_data0=NULL,*buffer_data1=NULL,*buffer_data2=NULL,*buffer_data3=NULL,*buffer_data4=NULL,*buffer_tmp0=NULL,*buffer_tmp1=NULL; CBuffer *buffer_calc0=NULL,*buffer_calc1=NULL,*buffer_calc2=NULL,*buffer_calc3=NULL,*buffer_calc4=NULL; #ifdef __MQL4__ CBuffer *buff_add=NULL; #endif double value00=EMPTY_VALUE, value01=EMPTY_VALUE; double value10=EMPTY_VALUE, value11=EMPTY_VALUE; double value20=EMPTY_VALUE, value21=EMPTY_VALUE; double value30=EMPTY_VALUE, value31=EMPTY_VALUE; double value40=EMPTY_VALUE, value41=EMPTY_VALUE; double value_tmp0=EMPTY_VALUE,value_tmp1=EMPTY_VALUE; long vol0=0,vol1=0; int series_index_start=series_index,index_period=0, index=0,num_bars=1; uchar clr=0; //--- Depending on standard indicator type switch((int)ind_type) { //--- Single-buffer standard indicators case IND_AC : case IND_AD : case IND_AMA : case IND_AO : case IND_ATR : case IND_BEARS : case IND_BULLS : case IND_BWMFI : case IND_CCI : case IND_CHAIKIN : case IND_DEMA : case IND_DEMARKER : case IND_FORCE : case IND_FRAMA : case IND_MA : case IND_MFI : case IND_MOMENTUM : case IND_OBV : case IND_OSMA : case IND_RSI : case IND_SAR : case IND_STDDEV : case IND_TEMA : case IND_TRIX : case IND_VIDYA : case IND_VOLUMES : case IND_WPR : list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,0,EQUAL); buffer_data0=list.At(0); #ifdef __MQL5__ list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,0,EQUAL); buffer_calc0=list.At(0); #endif if(buffer_data0==NULL #ifdef __MQL5__ || buffer_calc0==NULL || buffer_calc0.GetDataTotal(0)==0 #endif ) return false; series_index_start=PreparingSetDataStdInd(buffer_data0,buffer_data1,buffer_data2,buffer_data3,buffer_data4, buffer_calc0,buffer_calc1,buffer_calc2,buffer_calc3,buffer_calc4, ind_type,series_index,series_time,index_period,num_bars, value00,value01,value10,value11,value20,value21,value30,value31,value40,value41); if(series_index_start==WRONG_VALUE) return false; //--- In a loop, by the number of bars in num_bars fill in the drawn buffer with a value from the calculated buffer taken by index_period index //--- and set the drawn buffer color depending on a proportion of value00 and value01 values for(int i=0;i<num_bars;i++) { index=series_index_start-i; buffer_data0.SetBufferValue(0,index,value00); if(ind_type!=IND_BWMFI) clr=(color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index); else { vol0=::iVolume(buffer_data0.Symbol(),buffer_data0.Timeframe(),index_period); vol1=::iVolume(buffer_data0.Symbol(),buffer_data0.Timeframe(),index_period+1); clr= ( value00>value01 && vol0>vol1 ? 0 : value00<value01 && vol0<vol1 ? 1 : value00>value01 && vol0<vol1 ? 2 : value00<value01 && vol0>vol1 ? 3 : 4 ); } #ifdef __MQL5__ buffer_data0.SetBufferColorIndex(index,clr); #else #endif } return true; //--- Multi-buffer standard indicators case IND_ENVELOPES : case IND_FRACTALS : list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,0,EQUAL); buffer_data0=list.At(0); list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,1,EQUAL); buffer_data1=list.At(0); list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,0,EQUAL); buffer_calc0=list.At(0); list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,1,EQUAL); buffer_calc1=list.At(0); if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0) return false; if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0) return false; series_index_start=PreparingSetDataStdInd(buffer_data0,buffer_data1,buffer_data2,buffer_data3,buffer_data4, buffer_calc0,buffer_calc1,buffer_calc2,buffer_calc3,buffer_calc4, ind_type,series_index,series_time,index_period,num_bars, value00,value01,value10,value11,value20,value21,value30,value31,value40,value41); if(series_index_start==WRONG_VALUE) return false; //--- In a loop, by the number of bars in num_bars fill in the drawn buffer with a value from the calculated buffer taken by index_period index //--- and set the drawn buffer color depending on a proportion of value00 and value01 values for(int i=0;i<num_bars;i++) { index=series_index_start-i; buffer_data0.SetBufferValue(0,index,value00); buffer_data1.SetBufferValue(1,index,value10); buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index); buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index); } return true; case IND_ADX : case IND_ADXW : case IND_BANDS : case IND_MACD : case IND_RVI : case IND_STOCHASTIC : case IND_ALLIGATOR : list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,0,EQUAL); buffer_data0=list.At(0); list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,1,EQUAL); buffer_data1=list.At(0); list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,2,EQUAL); buffer_data2=list.At(0); list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,0,EQUAL); buffer_calc0=list.At(0); list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,1,EQUAL); buffer_calc1=list.At(0); list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,2,EQUAL); buffer_calc2=list.At(0); if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0) return false; if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0) return false; if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(0)==0) return false; series_index_start=PreparingSetDataStdInd(buffer_data0,buffer_data1,buffer_data2,buffer_data3,buffer_data4, buffer_calc0,buffer_calc1,buffer_calc2,buffer_calc3,buffer_calc4, ind_type,series_index,series_time,index_period,num_bars, value00,value01,value10,value11,value20,value21,value30,value31,value40,value41); if(series_index_start==WRONG_VALUE) return false; //--- In a loop, by the number of bars in num_bars fill in the drawn buffer with a value from the calculated buffer taken by index_period index //--- and set the drawn buffer color depending on a proportion of value00 and value01 values for(int i=0;i<num_bars;i++) { index=series_index_start-i; buffer_data0.SetBufferValue(0,index,value00); buffer_data1.SetBufferValue(0,index,value10); buffer_data2.SetBufferValue(0,index,value20); buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index); buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index); buffer_data2.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value20>value21 ? 0 : value20<value21 ? 1 : 2) : color_index); } return true; case IND_ICHIMOKU : list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_TENKAN_SEN,EQUAL); buffer_data0=list.At(0); list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_KIJUN_SEN,EQUAL); buffer_data1=list.At(0); list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SENKOU_SPANA,EQUAL); buffer_data2=list.At(0); list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SENKOU_SPANB,EQUAL); buffer_data3=list.At(0); list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_CHIKOU_SPAN,EQUAL); buffer_data4=list.At(0); //--- Get the list of buffer objects which have ID of auxiliary line, and from it - buffer object with line number as 0 list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_ADDITIONAL,EQUAL); list=CSelect::ByBufferProperty(list,BUFFER_PROP_IND_LINE_ADDITIONAL_NUM,0,EQUAL); buffer_tmp0=list.At(0); //--- Get the list of buffer objects which have ID of auxiliary line, and from it - buffer object with line number as 1 list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_ADDITIONAL,EQUAL); list=CSelect::ByBufferProperty(list,BUFFER_PROP_IND_LINE_ADDITIONAL_NUM,1,EQUAL); buffer_tmp1=list.At(0); list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_TENKAN_SEN,EQUAL); buffer_calc0=list.At(0); list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_KIJUN_SEN,EQUAL); buffer_calc1=list.At(0); list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SENKOU_SPANA,EQUAL); buffer_calc2=list.At(0); list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_SENKOU_SPANB,EQUAL); buffer_calc3=list.At(0); list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,INDICATOR_LINE_MODE_CHIKOU_SPAN,EQUAL); buffer_calc4=list.At(0); if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0) return false; if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0) return false; if(buffer_calc2==NULL || buffer_data2==NULL || buffer_calc2.GetDataTotal(0)==0) return false; if(buffer_calc3==NULL || buffer_data3==NULL || buffer_calc3.GetDataTotal(0)==0) return false; if(buffer_calc4==NULL || buffer_data4==NULL || buffer_calc4.GetDataTotal(0)==0) return false; series_index_start=PreparingSetDataStdInd(buffer_data0,buffer_data1,buffer_data2,buffer_data3,buffer_data4, buffer_calc0,buffer_calc1,buffer_calc2,buffer_calc3,buffer_calc4, ind_type,series_index,series_time,index_period,num_bars, value00,value01,value10,value11,value20,value21,value30,value31,value40,value41); if(series_index_start==WRONG_VALUE) return false; //--- In a loop, by the number of bars in num_bars fill in the drawn buffer with a value from the calculated buffer taken by index_period index //--- and set the drawn buffer color depending on a proportion of value00 and value01 values for(int i=0;i<num_bars;i++) { index=series_index_start-i; buffer_data0.SetBufferValue(0,index,value00); buffer_data1.SetBufferValue(0,index,value10); buffer_data2.SetBufferValue(0,index,value20); buffer_data3.SetBufferValue(0,index,value30); buffer_data4.SetBufferValue(0,index,value40); buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index); buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10>value11 ? 0 : value10<value11 ? 1 : 2) : color_index); buffer_data2.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value20>value21 ? 0 : value20<value21 ? 1 : 2) : color_index); buffer_data3.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value30>value31 ? 0 : value30<value31 ? 1 : 2) : color_index); buffer_data4.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value40>value41 ? 0 : value40<value41 ? 1 : 2) : color_index); //--- Set values for indicator auxiliary lines depending on mutual position of Senkou Span A and Senkou Span B lines value_tmp0=buffer_data2.GetDataBufferValue(0,index); value_tmp1=buffer_data3.GetDataBufferValue(0,index); if(value_tmp0<value_tmp1) { buffer_tmp0.SetBufferValue(0,index,buffer_tmp0.EmptyValue()); buffer_tmp0.SetBufferValue(1,index,buffer_tmp0.EmptyValue()); buffer_tmp1.SetBufferValue(0,index,value_tmp0); buffer_tmp1.SetBufferValue(1,index,value_tmp1); } else { buffer_tmp0.SetBufferValue(0,index,value_tmp0); buffer_tmp0.SetBufferValue(1,index,value_tmp1); buffer_tmp1.SetBufferValue(0,index,buffer_tmp1.EmptyValue()); buffer_tmp1.SetBufferValue(1,index,buffer_tmp1.EmptyValue()); } } return true; case IND_GATOR : list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,0,EQUAL); buffer_data0=list.At(0); list=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_LINE_MODE,1,EQUAL); buffer_data1=list.At(0); list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,0,EQUAL); buffer_calc0=list.At(0); list=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_LINE_MODE,1,EQUAL); buffer_calc1=list.At(0); if(buffer_calc0==NULL || buffer_data0==NULL || buffer_calc0.GetDataTotal(0)==0) return false; if(buffer_calc1==NULL || buffer_data1==NULL || buffer_calc1.GetDataTotal(0)==0) return false; series_index_start=PreparingSetDataStdInd(buffer_data0,buffer_data1,buffer_data2,buffer_data3,buffer_data4, buffer_calc0,buffer_calc1,buffer_calc2,buffer_calc3,buffer_calc4, ind_type,series_index,series_time,index_period,num_bars, value00,value01,value10,value11,value20,value21,value30,value31,value40,value41); if(series_index_start==WRONG_VALUE) return false; //--- In a loop, by the number of bars in num_bars fill in the drawn buffer with a value from the calculated buffer taken by index_period index //--- and set the drawn buffer color depending on a proportion of value00 and value01 values for(int i=0;i<num_bars;i++) { index=series_index_start-i; buffer_data0.SetBufferValue(0,index,value00); buffer_data1.SetBufferValue(1,index,value10); buffer_data0.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value00>value01 ? 0 : value00<value01 ? 1 : 2) : color_index); buffer_data1.SetBufferColorIndex(index,color_index==WRONG_VALUE ? uchar(value10<value11 ? 0 : value10>value11 ? 1 : 2) : color_index); } return true; default: break; } return false; } //+------------------------------------------------------------------+
这些就是我们今天必须进行的修改。
测试
为了验证指标对象的创建,取用上一篇文章的测试指标,
将其保存在新文件夹 \MQL5\Indicators\TestDoEasy\Part53\ 之下,并新命名为 TestDoEasyPart53.mq5,并用 AC 指标替换 AD 指标的操作:
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Write the name of the working timeframe selected in the settings to InpUsedTFs variable InpUsedTFs=TimeframeDescription(InpPeriod); //--- Initialize DoEasy library OnInitDoEasy(); //--- Set indicator global variables prefix=engine.Name()+"_"; //--- Calculate the number of bars of the current period fitting in the maximum used period //--- Use the obtained value if it exceeds 2, otherwise use 2 int num_bars=NumberBarsInTimeframe(InpPeriod); min_bars=(num_bars>2 ? num_bars : 2); //--- Check and remove remaining indicator graphical objects if(IsPresentObectByPrefix(prefix)) ObjectsDeleteAll(0,prefix); //--- Create the button panel //--- Check playing a standard sound using macro substitutions engine.PlaySoundByDescription(SND_OK); //--- Wait for 600 milliseconds engine.Pause(600); engine.PlaySoundByDescription(SND_NEWS); //--- indicator buffers mapping //--- Create all necessary buffer objects to construct the selected standard indicator if(!engine.BufferCreateAC(InpUsedSymbols,InpPeriod,1)) { Print(TextByLanguage("Error. Indicator not created")); return INIT_FAILED; } //--- Check the number of buffers specified in the 'properties' block engine.CheckIndicatorsBuffers(indicator_buffers,indicator_plots); //--- Create the color array and set non-default colors to all buffers within the collection //--- (commented out since default colors are already set in methods of standard indicator creation) //--- (we can always set required colors either for all indicators like here or for each one individually) //color array_colors[]={clrGreen,clrRed,clrGray}; //engine.BuffersSetColors(array_colors); //--- Display short descriptions of created indicator buffers engine.BuffersPrintShort(); //--- Set a short name for the indicator, data capacity and levels string label=engine.BufferGetIndicatorShortNameByTypeID(IND_AC,1); IndicatorSetString(INDICATOR_SHORTNAME,label); SetIndicatorLevels(InpUsedSymbols,IND_AC); //--- Successful return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Remove indicator graphical objects by an object name prefix ObjectsDeleteAll(0,prefix); Comment(""); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //+------------------------------------------------------------------+ //| OnCalculate code block for working with the library: | //+------------------------------------------------------------------+ //--- Pass the current symbol data from OnCalculate() to the price structure and set the "as timeseries" flag to the arrays CopyDataAsSeries(rates_total,prev_calculated,time,open,high,low,close,tick_volume,volume,spread); //--- Check for the minimum number of bars for calculation if(rates_total<min_bars || Point()==0) return 0; //--- Handle the Calculate event in the library //--- If the OnCalculate() method of the library returns zero, not all timeseries are ready - leave till the next tick if(engine.OnCalculate(rates_data)==0) return 0; //--- If work in the tester if(MQLInfoInteger(MQL_TESTER)) { engine.OnTimer(rates_data); // Work in the library timer engine.EventsHandling(); // Work with library events } //+------------------------------------------------------------------+ //| OnCalculate code block for working with the indicator: | //+------------------------------------------------------------------+ //--- Check and calculate the number of calculated bars //--- If limit = 0, there are no new bars - calculate the current one //--- If limit = 1, a new bar has appeared - calculate the first and the current ones //--- If limit > 1 means the first launch or changes in history - the full recalculation of all data int limit=rates_total-prev_calculated; //--- Recalculate the entire history if(limit>1) { limit=rates_total-1; engine.BuffersInitPlots(); engine.BuffersInitCalculates(); } //--- Prepare data //--- Fill in calculated buffers of all created standard indicators with data int bars_total=engine.SeriesGetBarsTotal(InpUsedSymbols,InpPeriod); int total_copy=(limit<min_bars ? min_bars : fmin(limit,bars_total)); if(!engine.BufferPreparingDataAllBuffersStdInd()) return 0; //--- Calculate the indicator //--- Main calculation loop of the indicator for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--) { engine.GetBuffersCollection().SetDataBufferStdInd(IND_AC,1,i,time[i]); } //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+
这就是此刻我们验证测试指标创建新抽象指标对象类需要做的工作。
完整的指标代码在下面的文件中提供。
编译指标,并启动它。 “智能系统”日志栏显示所创建指标对象的数据:
Account 8550475: Artyom Trishkin (MetaQuotes Software Corp.) 10425.23 USD, 1:100, Hedge, Demo account MetaTrader 5 --- Initializing "DoEasy" library --- Working with the current symbol only. Number of used symbols: 1 "EURUSD" Working with the specified timeframe list: "H4" "H1" EURUSD symbol timeseries: - "EURUSD" H1 timeseries: Requested: 1000, Actually: 0, Created: 0, On the server: 0 - "EURUSD" H4 timeseries: Requested: 1000, Actually: 1000, Created: 1000, On the server: 6237 Time of library initializing: 00:00:00.156 ============= Beginning of the parameter list: "Standard indicator" ============= Indicator status: Standard indicator Indicator timeframe: H4 Indicator handle: 10 Indicator group: Oscillator ------ Empty value for plotting where nothing will be drawn: EMPTY_VALUE ------ Indicator symbol: EURUSD Indicator name: "Accelerator Oscillator" Indicator short name: "AC(EURUSD,H4)" ================== End of the parameter list "Standard indicator" ================== Buffer(P0/B0/C1): Histogram from the zero line EURUSD H4 Buffer[P0/B2/C2]: Calculated buffer "EURUSD" H1 timeseries created successfully: - "EURUSD" H1 timeseries: Requested: 1000, Actually: 1000, Created: 1000, On the server: 6256
下一步是什么?
在随后的文章中,我将开始为今天所创建的抽象指标基准对象创建衍生对象类。
以下是该含糊库当前版本的所有文件,以及 MQL5 的测试指标文件。 您可以下载它们,并测试所有内容。
请在文章的评论中留下您的评论、问题和建议。
返回内容目录
该系列中的先前文章:
DoEasy 函数库中的时间序列(第三十五部分):柱线对象和品种时间序列列表
DoEasy 函数库中的时间序列(第三十六部分):所有用到的品种周期的时间序列对象
DoEasy 函数库中的时间序列(第三十七部分):时间序列集合 - 按品种和周期的时间序列数据库
DoEasy 函数库中的时间序列(第三十八部分):时间序列集合 - 实时更新以及从程序访问数据
DoEasy 函数库中的时间序列(第三十九部分):基于函数库的指标 - 准备数据和时间序列事件
DoEasy 函数库中的时间序列(第四十部分):基于函数库的指标 - 实时刷新数据
DoEasy 函数库中的时间序列(第四十一部分):多品种多周期指标样品
DoEasy 函数库中的时间序列(第四十二部分):抽象指标缓冲区对象类
DoEasy 函数库中的时间序列(第四十三部分):指标缓冲区对象类
DoEasy 函数库中的时间序列(第四十四部分):指标缓冲区对象集合类
DoEasy 函数库中的时间序列(第四十五部分):多周期指标缓冲区
DoEasy 函数库中的时间序列(第四十六部分):多周期、多品种指标缓冲区
DoEasy 函数库中的时间序列(第四十七部分):多周期、多品种标准指标
DoEasy 函数库中的时间序列(第四十八部分):在单一子窗口里基于一个缓冲区的多周期、多品种指标
DoEasy 函数库中的时间序列(第四十九部分):多周期、多品种、多缓冲区标准指标
DoEasy 函数库中的时间序列(第五十部分):多周期、多品种带位移的标准指标
DoEasy 函数库中的时间序列(第五十一部分):复合多周期、多品种标准指标
DoEasy 函数库中的时间序列(第五十二部分):多周期、多品种单缓冲区标准指标的跨平台性质