![DoEasy 函数库中的时间序列(第五十八部分):指标缓冲区数据的时间序列](https://c.mql5.com/2/49/doeasy_058_600x314.jpg)
DoEasy 函数库中的时间序列(第五十八部分):指标缓冲区数据的时间序列
内容目录
概述
关于操控时间序列的主题总结,诸如组织存储、针对存储在指标缓冲区中的数据进行搜索和分类,如此即可在程序里利用函数库创建指标值,并进一步据其执行分析。
构造指标数据时间序列的概念类似于构造时间序列集合的概念:为每个指标创建一个列表,以便存储其所有缓冲区的所有数据。 指标缓冲区列表中的数据量应与指标计算所依的相应时间序列的数据量对应。 函数库的所有集合类的一般概念,能够轻松地在相应的集合中找到必要的数据。 在今天创建的类中,也可分别完成同样功能。
更进一步,我将创建一些类,依据存储在函数库中的所有数据进行分析。 这将提供一个非常灵活的工具,如此即可全方位统计研究任何函数库集合的数据。
改进库类
指标缓冲区数据的集合类会自动更新所有依据当前柱线创建的指标数据;每当出现一个新的柱线时,它会在指标缓冲区创建新数据,并将其放置到集合中。
在 NewBarObj.mqh 文件里我们已拥有了为时间序列集合而创建的 “New bar” 类。
它存储在函数库目录中的时间序列类文件夹 \MQL5\Include\DoEasyPart57\Objects\Series\ 当中。
现在,还需要跟踪指标缓冲区数据集合类中的新柱线。
因此,将其移动到服务函数和函数库类的文件夹 \MQL5\Include\DoEasy\Services\ 当中。
删除先前位置的文件。
鉴于 “Timeseries” 类会用到 “New bar” 类,因此在 “Timeseries” 类文件 \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh 中的包含文件模块里必须更改旧的类文件路径:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Services\Select.mqh" #include "NewBarObj.mqh" #include "Bar.mqh" //+------------------------------------------------------------------+
指向新的路径:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Services\Select.mqh" #include "..\..\Services\NewBarObj.mqh" #include "Bar.mqh" //+------------------------------------------------------------------+
在文件里 \MQL5\Include\DoEasy\Data.mqh 加入新的消息索引:
MSG_LIB_SYS_ERROR_FAILED_GET_PRICE_ASK, // Failed to get Ask price. Error MSG_LIB_SYS_ERROR_FAILED_GET_PRICE_BID, // Failed to get Bid price. Error MSG_LIB_SYS_ERROR_FAILED_GET_TIME, // Failed to get time. Error MSG_LIB_SYS_ERROR_FAILED_OPEN_BUY, // Failed to open Buy position. Error
...
//--- CDataInd MSG_LIB_TEXT_IND_DATA_IND_BUFFER_NUM, // Indicator buffer number MSG_LIB_TEXT_IND_DATA_BUFFER_VALUE, // Indicator buffer value //--- CSeriesDataInd MSG_LIB_TEXT_METHOD_NOT_FOR_INDICATORS, // The method is not intended to handle indicator programs MSG_LIB_TEXT_IND_DATA_FAILED_GET_SERIES_DATA, // Failed to get indicator data timeseries MSG_LIB_TEXT_IND_DATA_FAILED_GET_CURRENT_DATA, // Failed to get current data of indicator buffer MSG_LIB_SYS_FAILED_CREATE_IND_DATA_OBJ, // Failed to create indicator data object MSG_LIB_TEXT_IND_DATA_FAILED_ADD_TO_LIST, // Failed to add indicator data object to list }; //+------------------------------------------------------------------+
和与新添加的索引相对应的消息文本:
{"Could not get Ask price. Error "}, {"Could not get Bid price. Error "}, {"Could not get time. Error "}, {"Failed to open a Buy position. Error "},
...
{"Indicator buffer number"}, {"Indicator buffer value"}, {"The method is not intended for working with indicator programs"}, {"Failed to get indicator data timeseries"}, {"Failed to get the current data of the indicator buffer"}, {"Failed to create indicator data object"}, {"Failed to add indicator data object to the list"}, }; //+---------------------------------------------------------------------+
从今天开始,我将创建一个新集合,我们来为其设置集合 ID。 除此之外,为新集合计时器创建参数常量,因为指标缓冲区数据将在计时器中自动更新。
将新数据添加到文件 \MQL5\Include\DoEasy\Defines.mqh 里:
//--- Parameters of the timeseries collection timer #define COLLECTION_TS_PAUSE (64) // Timeseries collection timer pause in milliseconds #define COLLECTION_TS_COUNTER_STEP (16) // Timeseries timer counter increment #define COLLECTION_TS_COUNTER_ID (6) // Timeseries timer counter ID //--- Parameters of the timer of indicator data timeseries collection #define COLLECTION_IND_TS_PAUSE (64) // Pause of the timer of indicator data timeseries collection in milliseconds #define COLLECTION_IND_TS_COUNTER_STEP (16) // Increment of indicator data timeseries timer counter #define COLLECTION_IND_TS_COUNTER_ID (7) // ID of indicator data timeseries timer counter //--- 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 #define COLLECTION_INDICATORS_DATA_ID (0x7782) // Indicator data collection list ID
为了在其集合中搜索指标缓冲区数据,必须另外按指标句柄搜索数据(因为数据将与该指标关联,并且对于搜索数据,如果不使用与指标的隶属关系的确切 ID,感觉会很奇怪)。
需为指标数据的整数型属性添加新属性,然后将此类型属性的数量从 5 增加到 6:
//+------------------------------------------------------------------+ //| Integer properties of indicator data | //+------------------------------------------------------------------+ enum ENUM_IND_DATA_PROP_INTEGER { IND_DATA_PROP_TIME = 0, // Start time of indicator data bar period IND_DATA_PROP_PERIOD, // Indicator data period (timeframe) IND_DATA_PROP_INDICATOR_TYPE, // Indicator type IND_DATA_PROP_IND_BUFFER_NUM, // Indicator data buffer number IND_DATA_PROP_IND_ID, // Indicator ID IND_DATA_PROP_IND_HANDLE, // Indicator handle }; #define IND_DATA_PROP_INTEGER_TOTAL (6) // Total number of indicator data integer properties #define IND_DATA_PROP_INTEGER_SKIP (0) // Number of indicator data properties not used in sorting //+------------------------------------------------------------------+
鉴于我们添加了一个新的整数型属性,故需将其添加到可能的排序条件列表之中,以便能够依据该属性进行搜索和排序:
//+------------------------------------------------------------------+ //| Possible criteria for indicator data sorting | //+------------------------------------------------------------------+ #define FIRST_IND_DATA_DBL_PROP (IND_DATA_PROP_INTEGER_TOTAL-IND_DATA_PROP_INTEGER_SKIP) #define FIRST_IND_DATA_STR_PROP (IND_DATA_PROP_INTEGER_TOTAL-IND_DATA_PROP_INTEGER_SKIP+IND_DATA_PROP_DOUBLE_TOTAL-IND_DATA_PROP_DOUBLE_SKIP) enum ENUM_SORT_IND_DATA_MODE { //--- Sort by integer properties SORT_BY_IND_DATA_TIME = 0, // Sort by bar period start time of indicator data SORT_BY_IND_DATA_PERIOD, // Sort by indicator data period (timeframe) SORT_BY_IND_DATA_INDICATOR_TYPE, // Sort by indicator type SORT_BY_IND_DATA_IND_BUFFER_NUM, // Sort by indicator data buffer number SORT_BY_IND_DATA_IND_ID, // Sort by indicator ID SORT_BY_IND_DATA_IND_HANDLE, // Sort by indicator handle //--- Sort by real properties SORT_BY_IND_DATA_BUFFER_VALUE = FIRST_IND_DATA_DBL_PROP, // Sort by indicator data value //--- Sort by string properties SORT_BY_IND_DATA_SYMBOL = FIRST_IND_DATA_STR_PROP, // Sort by indicator data symbol SORT_BY_IND_DATA_IND_NAME, // Sort by indicator name SORT_BY_IND_DATA_IND_SHORTNAME, // Sort by indicator short name }; //+------------------------------------------------------------------+
存储在集合中的指标缓冲区数据由上一篇文章中创建的指标缓冲区数据类表示。
现在,将其加进(在文件 \MQL5\Include\DoEasy\Objects\Indicators\DataInd.mqh 中)指标句柄的安装方法,在该类的对象中存储了缓冲区数据。 还有,在类构造函数里添加传递指标句柄的参数,以及其缓冲区的数值。 这样即可在对象创建期间为这些参数设置数值:
//--- Set (1) symbol, timeframe and time for the object, (2) indicator type, (3) number of buffers, (4) number of data buffer, //--- (5) ID, (6) handle, (7) data value, (8) name, (9) 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 SetIndicatorHandle(const int handle) { this.SetProperty(IND_DATA_PROP_IND_HANDLE,handle); } 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 property of data object ) 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, const int ind_handle, const int ind_id, const int buffer_num, const string symbol, const ENUM_TIMEFRAMES timeframe, const datetime time, const double value);
在简化访问对象属性的方法模块中,加入一个返回指标句柄的方法,并重命名了返回指标缓冲区数据数值的方法(之前的方法称为 PriceValue()):
//+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- Return (1) bar period start time, (2) timeframe, (3) indicator type, //--- (4) number of buffers, (5) buffer number, (6) ID, (7) indicator handle datetime Time(void) const { return (datetime)this.GetProperty(IND_DATA_PROP_TIME); } ENUM_TIMEFRAMES Timeframe(void) const { return (ENUM_TIMEFRAMES)this.GetProperty(IND_DATA_PROP_PERIOD); } ENUM_INDICATOR IndicatorType(void) const { return (ENUM_INDICATOR)this.GetProperty(IND_DATA_PROP_INDICATOR_TYPE); } int BufferNum(void) const { return (ENUM_INDICATOR)this.GetProperty(IND_DATA_PROP_IND_BUFFER_NUM); } int IndicatorID(void) const { return (ENUM_INDICATOR)this.GetProperty(IND_DATA_PROP_IND_ID); } int IndicatorHandle(void) const { return (ENUM_INDICATOR)this.GetProperty(IND_DATA_PROP_IND_HANDLE); } //--- Return the value of indicator buffer data double BufferValue(void) const { return this.GetProperty(IND_DATA_PROP_BUFFER_VALUE); }
在类构造函数的主体中,设置创建新对象时传递给它的新属性数值:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CDataInd::CDataInd(const ENUM_INDICATOR ind_type, const int ind_handle, const int ind_id, const int buffer_num, const string symbol, const ENUM_TIMEFRAMES timeframe, const datetime time, const double value) { this.m_type=COLLECTION_INDICATORS_DATA_ID; this.m_digits=(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS)+1; this.m_period_description=TimeframeDescription(timeframe); this.SetSymbolPeriod(symbol,timeframe,time); this.SetIndicatorType(ind_type); this.SetIndicatorHandle(ind_handle); this.SetBufferNum(buffer_num); this.SetIndicatorID(ind_id); this.SetBufferValue(value); } //+------------------------------------------------------------------+
在返回整数型属性说明的方法中,添加显示指标句柄说明的代码模块:
//+------------------------------------------------------------------+ //| Return description of object's integer property | //+------------------------------------------------------------------+ string CDataInd::GetPropertyDescription(ENUM_IND_DATA_PROP_INTEGER property) { return ( property==IND_DATA_PROP_TIME ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS) ) : property==IND_DATA_PROP_PERIOD ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_TIMEFRAME)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.m_period_description ) : property==IND_DATA_PROP_INDICATOR_TYPE ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_TYPE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.IndicatorTypeDescription() ) : property==IND_DATA_PROP_IND_BUFFER_NUM ? CMessage::Text(MSG_LIB_TEXT_IND_DATA_IND_BUFFER_NUM)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==IND_DATA_PROP_IND_ID ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_ID)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==IND_DATA_PROP_IND_HANDLE ? CMessage::Text(MSG_LIB_TEXT_IND_TEXT_HANDLE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : "" ); } //+------------------------------------------------------------------+
指标缓冲区数据的时间序列类
函数库中的所有集合类都是基于指向标准库 CObject 类及其衍生类实例的动态指针数组的类而创建的。 指标缓冲区数据的集合类也不例外。
该类允许在 CArrayObj 对象列表中存储一个指标的缓冲区数据对象,以便获取与时间序列柱线开立时间相对应的任何指标缓冲区的数据。 自然地,该列表能够根据存储在其内的对象属性自动进行更新、搜索和排序。
在文件夹 \MQL5\Include\DoEasy\Objects\Indicators\ 下的文件 SeriesDataInd.mqh 里创建一个新类 CSeriesDataInd。
类对象必须派生自函数库所有对象的基准对象 CBaseObj。
分析类主体的所有变量和方法:
//+------------------------------------------------------------------+ //| SeriesDataInd.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 "..\..\Services\NewBarObj.mqh" #include "DataInd.mqh" //+------------------------------------------------------------------+ //| “Indicator data list” class | //+------------------------------------------------------------------+ class CSeriesDataInd : public CBaseObj { private: ENUM_TIMEFRAMES m_timeframe; // Timeframe ENUM_INDICATOR m_ind_type; // Indicator type string m_symbol; // Symbol string m_period_description; // Timeframe string description int m_ind_handle; // Indicator handle int m_ind_id; // Indicator ID int m_buffers_total; // Number of indicator buffers uint m_amount; // Amount of applied timeseries data uint m_required; // Required amount of applied timeseries data uint m_bars; // Number of bars in history by symbol and timeframe bool m_sync; // Synchronized data flag CArrayObj m_list_data; // Indicator data list CNewBarObj m_new_bar_obj; // "New bar" object //--- Create a new indicator data object, return pointer to it CDataInd *CreateNewDataInd(const int buffer_num,const datetime time,const double value); public: //--- Return (1) oneself, (2) the full list of indicator data CSeriesDataInd *GetObject(void) { return &this; } CArrayObj *GetList(void) { return &this.m_list_data; } //--- Return the list of indicator data by selected (1) double, (2) integer and (3) string property fitting a compared condition CArrayObj *GetList(ENUM_IND_DATA_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByIndicatorDataProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_IND_DATA_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByIndicatorDataProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_IND_DATA_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByIndicatorDataProperty(this.GetList(),property,value,mode); } //--- Return indicator data object by (1) time, (2) bar number and buffer number CDataInd *GetIndDataByTime(const int buffer_num,const datetime time); CDataInd *GetIndDataByBar(const int buffer_num,const uint shift); //--- Set (1) symbol, (2) timeframe, (3) symbol and timeframe, (4) amount of applied timeseries data, //--- (5) handle, (6) ID, (7) number of indicator buffers void SetSymbol(const string symbol); void SetTimeframe(const ENUM_TIMEFRAMES timeframe); void SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe); bool SetRequiredUsedData(const uint required); void SetIndHandle(const int handle) { this.m_ind_handle=handle; } void SetIndID(const int id) { this.m_ind_id=id; } void SetIndBuffersTotal(const int total) { this.m_buffers_total=total; } //--- Return (1) symbol, (2) timeframe, number of (3) used and (4) requested timeseries data, //--- (5) number of bars in timeseries, (6) handle, (7) ID, (8) number of indicator buffers string Symbol(void) const { return this.m_symbol; } ENUM_TIMEFRAMES Timeframe(void) const { return this.m_timeframe; } ulong AvailableUsedData(void) const { return this.m_amount; } ulong RequiredUsedData(void) const { return this.m_required; } ulong Bars(void) const { return this.m_bars; } int IndHandle(void) const { return this.m_ind_handle; } int IndID(void) const { return this.m_ind_id; } int IndBuffersTotal(void) const { return this.m_buffers_total; } bool IsNewBar(const datetime time) { return this.m_new_bar_obj.IsNewBar(time); } bool IsNewBarManual(const datetime time) { return this.m_new_bar_obj.IsNewBarManual(time); } //--- Return real list size int DataTotal(void) const { return this.m_list_data.Total(); } //--- Save the new bar time during the manual time management void SaveNewBarTime(const datetime time) { this.m_new_bar_obj.SaveNewBarTime(time); } //--- (1) Create, (2) update the indicator data list int Create(const uint required=0); void Refresh(void); //--- Return data of specified indicator buffer by (1) open time, (2) bar index double BufferValue(const int buffer_num,const datetime time); double BufferValue(const int buffer_num,const uint shift); //--- Constructors CSeriesDataInd(void){;} CSeriesDataInd(const int handle, const ENUM_INDICATOR ind_type, const int ind_id, const int buffers_total, const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0); }; //+------------------------------------------------------------------+
在对象中,函数库所有对象的固有已知方法均可见,还有一组存储对象参数的类成员变量。
在类的公开部分中,声明简化访问对象属性,以及从外部安装的方法。
我们来看一下类方法的实现。
为了创建对象,需向参数类构造函数传递所有属性值:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CSeriesDataInd::CSeriesDataInd(const int handle, const ENUM_INDICATOR ind_type, const int ind_id, const int buffers_total, const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0) : m_bars(0), m_amount(0),m_required(0),m_sync(false) { //--- To the object set indicator data collection list ID this.m_type=COLLECTION_INDICATORS_DATA_ID; //--- Clear the list and set for it the flag of the list sorted by time this.m_list_data.Clear(); this.m_list_data.Sort(SORT_BY_IND_DATA_TIME); //--- Set values for all object variables this.SetSymbolPeriod(symbol,timeframe); this.m_sync=this.SetRequiredUsedData(required); this.m_period_description=TimeframeDescription(this.m_timeframe); this.m_ind_handle=handle; this.m_ind_type=ind_type; this.m_ind_id=ind_id; this.m_buffers_total=buffers_total; } //+------------------------------------------------------------------+
为了给对象设置指标缓冲区数据的时间序列集合的 ID,清除存储 CDataInd 类对象的列表,并为该列表设置已排序的标志。 这些对象的最佳排序方式是按时间排序:如此可确保列表中的位置与指标物理缓冲区中的数据位置相对应。
设置品种和时间帧的方法无需注释:
//+------------------------------------------------------------------+ //| Set a symbol | //+------------------------------------------------------------------+ void CSeriesDataInd::SetSymbol(const string symbol) { if(this.m_symbol==symbol) return; this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); } //+------------------------------------------------------------------+ //| Set a timeframe | //+------------------------------------------------------------------+ void CSeriesDataInd::SetTimeframe(const ENUM_TIMEFRAMES timeframe) { if(this.m_timeframe==timeframe) return; this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe); } //+------------------------------------------------------------------+ //| Set a symbol and timeframe | //+------------------------------------------------------------------+ void CSeriesDataInd::SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe) { this.SetSymbol(symbol); this.SetTimeframe(timeframe); } //+------------------------------------------------------------------+
设置指标缓冲区数据的所需数量的方法:
//+------------------------------------------------------------------+ //| Set the amount of necessary data | //+------------------------------------------------------------------+ bool CSeriesDataInd::SetRequiredUsedData(const uint required) { //--- If this is indicator program - report and leave if(this.m_program==PROGRAM_INDICATOR) { ::Print(CMessage::Text(MSG_LIB_TEXT_METHOD_NOT_FOR_INDICATORS)); return false; } //--- Set the necessary amount of data - if less than 1 is passed use by default (1000), or else - the passed value this.m_required=(required<1 ? SERIES_DEFAULT_BARS_COUNT : required); //--- Launch download of historical data (relevant for the “cold” start) datetime array[1]; ::CopyTime(this.m_symbol,this.m_timeframe,0,1,array); //--- Set the number of available timeseries bars this.m_bars=(uint)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_BARS_COUNT); //--- If succeeded to set the number of available history bars, set the amount of data in the list: if(this.m_bars>0) { //--- if zero 'required' value is passed, //--- use either the default value (1000 bars) or the number of available history bars - the least one of them //--- if non-zero 'required' value is passed, //--- use either the 'required' value or the number of available history bars - the least one of them this.m_amount=(required==0 ? ::fmin(SERIES_DEFAULT_BARS_COUNT,this.m_bars) : ::fmin(required,this.m_bars)); return true; } return false; } //+------------------------------------------------------------------+
为了在指标里操作,需其他类已可用,因此立即检查启动程序的类型。 如果这是指标,则显示一条消息,并返回 false。 在第三十五篇文章里创建时间序列类时,已分析过类似方法的操作。
创建新指标数据对象的方法:
//+------------------------------------------------------------------+ //| Create a new indicator data object, return the pointer | //+------------------------------------------------------------------+ CDataInd* CSeriesDataInd::CreateNewDataInd(const int buffer_num,const datetime time,const double value) { CDataInd* data_ind=new CDataInd(this.m_ind_type,this.m_ind_handle,this.m_ind_id,buffer_num,this.m_symbol,this.m_timeframe,time,value); return data_ind; } //+------------------------------------------------------------------+
创建一个 CDataInd 类的新对象,并返回指向新创建对象的指针;如果尚未创建对象,则返回 NULL。
指标数据时间序列表的创建方法:
//+------------------------------------------------------------------+ //| Create indicator data timeseries list | //+------------------------------------------------------------------+ int CSeriesDataInd::Create(const uint required=0) { //--- If this is indicator program - report and leave if(this.m_program==PROGRAM_INDICATOR) { ::Print(CMessage::Text(MSG_LIB_TEXT_METHOD_NOT_FOR_INDICATORS)); return false; } //--- If the required history depth is not set for the list yet, //--- display the appropriate message and return zero, if(this.m_amount==0) { ::Print(DFUN,this.m_symbol," ",TimeframeDescription(this.m_timeframe),": ",CMessage::Text(MSG_LIB_TEXT_BAR_TEXT_FIRS_SET_AMOUNT_DATA)); return 0; } //--- otherwise, if the passed 'required' value exceeds zero and is not equal to the one already set, //--- while the passed ‘required’ value is less than the available bar number, //--- set the new value of the required history depth for the list else if(required>0 && this.m_amount!=required && required<this.m_bars) { //--- If failed to set a new value, return zero if(!this.SetRequiredUsedData(required)) return 0; } //--- For the data[] array we are to receive historical data to, //--- set the flag of direction like in the timeseries, //--- clear the list of indicator data objects and set the flag of sorting by timeseries bar time double data[]; ::ArraySetAsSeries(data,true); this.m_list_data.Clear(); this.m_list_data.Sort(SORT_BY_IND_DATA_TIME); ::ResetLastError(); int err=ERR_SUCCESS; //--- In a loop by the number of indicator data buffers for(int i=0;i<this.m_buffers_total;i++) { //--- Get historical data of i buffer of indicator to data[] array starting from the current bar in the amount of m_amount, //--- if failed to get data, display the appropriate message and return zero int copied=::CopyBuffer(this.m_ind_handle,i,0,this.m_amount,data); if(copied<1) { err=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_IND_DATA_FAILED_GET_SERIES_DATA)," ",this.m_symbol," ",TimeframeDescription(this.m_timeframe),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)); return 0; } //--- In the loop by amount of required data for(int j=0;j<(int)this.m_amount;j++) { //--- Get time of j bar ::ResetLastError(); datetime time=::iTime(this.m_symbol,this.m_timeframe,j); //--- If failed to get time //--- display the appropriate message with the error description in the journal and move on to the next loop iteration by data (j) if(time==0) { err=::GetLastError(); ::Print( DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TIME),CMessage::Text(err),CMessage::Retcode(err)); continue; } //--- If failed to create a new indicator data object from i buffer //--- display the appropriate message with the error description in the journal and move on to the next loop iteration by data (j) CDataInd* data_ind=CreateNewDataInd(i,time,data[j]); if(data_ind==NULL) { err=::GetLastError(); ::Print ( DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_IND_DATA_OBJ)," ",::TimeToString(time),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err) ); continue; } //--- If failed to add a new indicator data object to list //--- display the appropriate message with the error description in the journal, remove data object //--- and move on to the next loop iteration by data if(!this.m_list_data.Add(data_ind)) { err=::GetLastError(); ::Print ( DFUN,CMessage::Text(MSG_LIB_TEXT_IND_DATA_FAILED_ADD_TO_LIST)," ",::TimeToString(time),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err) ); delete data_ind; continue; } } } //--- Return the size of the created list of indicator data objects return this.m_list_data.Total(); } //+------------------------------------------------------------------+
方法的所有逻辑在其清单中都有详细描述。 该方法将数据从所有的指标缓冲区里复制到数组。 在这些数据基础上,它创建新的指标数据对象 CDataInd,并将它们放到集合列表中。
更新指标数据集合列表的方法:
//+------------------------------------------------------------------+ //| Update indicator data timeseries list and data | //+------------------------------------------------------------------+ void CSeriesDataInd::Refresh(void) { //--- If this is indicator program - report and leave if(this.m_program==PROGRAM_INDICATOR) { ::Print(CMessage::Text(MSG_LIB_TEXT_METHOD_NOT_FOR_INDICATORS)); return; } //--- If indicator data timeseries is not used, exit if(!this.m_available) return; double data[1]; //--- Get the current bar time int err=ERR_SUCCESS; ::ResetLastError(); datetime time=::iTime(this.m_symbol,this.m_timeframe,0); //--- If failed to get time //--- display the appropriate message with the error description in the journal and exit if(time==0) { err=::GetLastError(); ::Print( DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TIME),CMessage::Text(err),CMessage::Retcode(err)); return; } //--- Set the flag of sorting the data list by time this.m_list_data.Sort(SORT_BY_IND_DATA_TIME); //--- If a new bar is present on a symbol and period if(this.IsNewBarManual(time)) { //--- create new indicator data objects by the number of buffers and add them to the list end for(int i=0;i<this.m_buffers_total;i++) { //--- Get data of the current bar of indicator’s i buffer to data[] array, //--- if failed to get data, display the appropriate message and exit int copied=::CopyBuffer(this.m_ind_handle,i,0,1,data); if(copied<1) { err=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_IND_DATA_FAILED_GET_CURRENT_DATA)," ",this.m_symbol," ",TimeframeDescription(this.m_timeframe),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)); return; } //--- Create a new data object of indicator’s i buffer CDataInd* data_ind=CreateNewDataInd(i,time,data[0]); if(data_ind==NULL) return; //--- If the created object was not added to the list - remove this object and exit from the method if(!this.m_list_data.InsertSort(data_ind)) { delete data_ind; return; } } //--- if the size of indicator data timeseries has ecxeeded the requested number of bars, remove the earliest data object if(this.m_list_data.Total()>(int)this.m_required) this.m_list_data.Delete(0); //--- save the new bar time as the previous one for the subsequent new bar check this.SaveNewBarTime(time); } //--- Get the list of indicator data objects by the time of current bar start CArrayObj *list=CSelect::ByIndicatorDataProperty(this.GetList(),IND_DATA_PROP_TIME,time,EQUAL); //--- In the loop, by the number of indicator buffers get indicator data objects from the list by loop index for(int i=0;i<this.m_buffers_total;i++) { //--- If failed to get object from the list - exit CDataInd *data_ind=this.GetIndDataByTime(i,time); if(data_ind==NULL) return; //--- Copy data of indicator’s i buffer from current bar int copied=::CopyBuffer(this.m_ind_handle,i,0,1,data); if(copied<1) { err=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_IND_DATA_FAILED_GET_CURRENT_DATA)," ",this.m_symbol," ",TimeframeDescription(this.m_timeframe),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)); return; } //--- Add received value of indicator’s i buffer to indicator data object data_ind.SetBufferValue(data[0]); } } //+------------------------------------------------------------------+
方法的操作逻辑在其清单中进行了详细描述。 该方法将更新基于当前柱线的所有指标缓冲区数据。 当创建新柱线时,它会为当前柱线创建新对象,并将它们添加到集合列表之中。 当集合列表的大小超过所需值,则应从列表中删除最旧的对象。
按时间和缓冲区编号返回指标数据对象的方法:
//+------------------------------------------------------------------+ //| Return indicator data object by time and buffer number | //+------------------------------------------------------------------+ CDataInd* CSeriesDataInd::GetIndDataByTime(const int buffer_num,const datetime time) { CArrayObj *list=GetList(IND_DATA_PROP_TIME,time,EQUAL); list=CSelect::ByIndicatorDataProperty(list,IND_DATA_PROP_IND_BUFFER_NUM,buffer_num,EQUAL); return list.At(0); } //+------------------------------------------------------------------+
此处:获取包含与指定时间相对应的指标数据对象的列表,
然后过滤收到的列表,目的是仅在其中保留指定的指标缓冲区对象。
该列表仅包含一个对象。 将其返回。
依据柱线和缓冲区编号返回指标数据对象的方法:
//+------------------------------------------------------------------+ //| Return indicator data object by bar and buffer number | //+------------------------------------------------------------------+ CDataInd* CSeriesDataInd::GetIndDataByBar(const int buffer_num,const uint shift) { datetime time=::iTime(this.m_symbol,this.m_timeframe,(int)shift); if(time==0) return NULL; return this.GetIndDataByTime(buffer_num,time); } //+------------------------------------------------------------------+
此处:依据指定的索引获取柱线开立时间,然后调用上述研究过的方法返回收到的对象。
按时间返回指定的指标缓冲区数据的方法:
//+------------------------------------------------------------------+ //| Return data of specified indicator buffer by time | //+------------------------------------------------------------------+ double CSeriesDataInd::BufferValue(const int buffer_num,const datetime time) { CDataInd *data_ind=this.GetIndDataByTime(buffer_num,time); return(data_ind==NULL ? EMPTY_VALUE : data_ind.BufferValue()); } //+------------------------------------------------------------------+
此处:利用 GetIndDataByTime() 方法获取数据对象,并返回写在对象缓冲区里的数值,如果未收到对象,则返回“空值”。
依据柱线索引返回指定指标缓冲区数据的方法:
//+------------------------------------------------------------------+ //| Return data of specified indicator buffer by bar index | //+------------------------------------------------------------------+ double CSeriesDataInd::BufferValue(const int buffer_num,const uint shift) { CDataInd *data_ind=this.GetIndDataByBar(buffer_num,shift); return(data_ind==NULL ? EMPTY_VALUE : data_ind.BufferValue()); } //+------------------------------------------------------------------+
此处:利用 GetIndDataByBar() 方法获取数据对象,并返回写在对象缓冲区里的数值,如果未收到对象,则返回“空值”。
程序中创建的所有指标均应移至第五十四篇文章里创建的指标对象集合。 每个此类对象均含有标准或自定义指标的所有数据。 不过,必须补充一些数据。 如此即可始终知道依据该类对象描述的指标所拥有缓冲区的数量,为此必须添加一个存储指标缓冲区数量的变量。 它与自定义指标尤其相关,因为您无法提前知道所要绘制的缓冲区数量。 另外,我必须添加指标缓冲器数据集合列表,而该类刚刚创建。 在这种情况下,指标对象还要存储其所有缓冲区的数据列表。 从它们当中可以接收每个指标缓冲器的每根柱线上的数据。
打开指标对象类文件 \MQL5\Include\DoEasy\Objects\Indicators\IndicatorDE.mqh,并对其进行所有必要的改进:
//+------------------------------------------------------------------+ //| 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" #include "..\..\Objects\Indicators\SeriesDataInd.mqh" //+------------------------------------------------------------------+ //| Abstract indicator class | //+------------------------------------------------------------------+ class CIndicatorDE : public CBaseObj { protected: MqlParam m_mql_param[]; // Array of indicator parameters CSeriesDataInd m_series_data; // Timeseries object of indicator buffer data 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 string m_ind_type_description; // Indicator type description int m_buffers_total; // Total number of indicator buffers //--- 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: //--- 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 &mql_params[]); public: //--- Default constructor CIndicatorDE(void){;} //--- Destructor ~CIndicatorDE(void); //--- Set buffer's (1) integer, (2) real and (3) string property 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 buffer’s (1) integer, (2) real and (3) string property 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 property 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 equal indicator objects) bool IsEqual(CIndicatorDE* compared_obj) const; //--- Set indicator’s (1) group, (2) empty value of buffers, (3) name, (4) short name, (5) ID 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); } void SetID(const int id) { this.SetProperty(INDICATOR_PROP_ID,id); } void SetBuffersTotal(const int buffers_total) { this.m_buffers_total=buffers_total; } //--- Return indicator’s (1) status, (2) group, (3) timeframe, (4) type, (5) handle, (6) ID, //--- (7) empty value of buffers, (8) name, (9) short name, (10) symbol, (11) number of buffers 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); } ENUM_INDICATOR TypeIndicator(void) const { return (ENUM_INDICATOR)this.GetProperty(INDICATOR_PROP_TYPE); } int Handle(void) const { return (int)this.GetProperty(INDICATOR_PROP_HANDLE); } int ID(void) const { return (int)this.GetProperty(INDICATOR_PROP_ID); } 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); } int BuffersTotal(void) const { return this.m_buffers_total; } //--- Return description of (1) type, (2) status, (3) group, (4) timeframe, (5) empty value of indicator, (6) parameter of m_mql_param array string GetTypeDescription(void) const { return m_ind_type_description; } string GetStatusDescription(void) const; string GetGroupDescription(void) const; string GetTimeframeDescription(void) const; string GetEmptyValueDescription(void) const; string GetMqlParamDescription(const int index) const; //--- Return indicator data timeseries CSeriesDataInd *GetSeriesData(void) { return &this.m_series_data; } //--- 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 (1) a short description, (2) description of indicator object parameters in the journal (implementation in the descendants) virtual void PrintShort(void) {;} virtual void PrintParameters(void) {;} //--- Return data of specified buffer from specified bar (1) by index, (2) by bar time double GetDataBuffer(const int buffer_num,const int index); double GetDataBuffer(const int buffer_num,const datetime time); }; //+------------------------------------------------------------------+
为了令该类看到指标数据集合类的对象,我在包含文件部分中指定了它的文件 SeriesDataInd.mqh。
在该类的保护区域中,声明指标数据集合类 m_series_data 的对象。
m_buffers_total 变量将存储需绘制指标缓冲区的总数。 所有这些缓冲区的数据对象将存储在指标缓冲区数据集合当中。
方法 SetBuffersTotal() 和 BuffersTotal() 设置/返回需绘制的指标缓冲区的总数。
方法 GetSeriesData() 返回一个指向指标缓冲区数据集合的指针。
现在,打开指标集合类文件 \MQL5\Include\DoEasy\Collections\IndicatorsCollection.mqh,并对其进行必要的改进。
在指标创建期间,会创建抽象指标类的对象,并明确声明与所创建指标类型相对应的所有数据。 然后,将该指标对象添加到集合列表之中。 当利用 AddIndicatorToList() 方法将所创建指标对象添加到集合当中时,会另行设置指标的必要参数,从而对其进行精确标识。
在该方法中添加指定指标绘制缓冲区数量和缓冲区所需的数据量:
//--- (1) Create, (2) add to collection list a new indicator object and set an ID for it CIndicatorDE *CreateIndicator(const ENUM_INDICATOR ind_type,MqlParam &mql_param[],const string symbol_name=NULL,const ENUM_TIMEFRAMES period=PERIOD_CURRENT); int AddIndicatorToList(CIndicatorDE *indicator,const int id,const int buffers_total,const uint required=0);
在创建自定义指标的方法中,还要传递绘制缓冲区总数:
int CreateCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id,const int buffers_total, ENUM_INDICATOR_GROUP group, MqlParam &mql_param[]);
声明按指定时间返回指向指标缓冲区时间序列的数据对象指针的方法,以及创建、更新、和从指标缓冲区数据集合中返回数据的方法:
//--- Return (1) indicator object by its ID, (2) the data object of indicator buffer timeseries by time CIndicatorDE *GetIndByID(const uint id); CDataInd *GetDataIndObj(const uint ind_id,const int buffer_num,const datetime time); //--- Display (1) the complete and (2) short collection description in the journal void Print(void); void PrintShort(void); //--- Create (1) timeseries of specified indicator data, (2) all timeseries used of all collection indicators bool SeriesCreate(CIndicatorDE *indicator,const uint required=0); bool SeriesCreateAll(const uint required=0); //--- Update buffer data of all indicators void SeriesRefreshAll(void); void SeriesRefresh(const int ind_id); //--- Return by bar (1) time, (2) number the value of the buffer specified by indicator ID double GetBufferValue(const uint ind_id,const int buffer_num,const datetime time); double GetBufferValue(const uint ind_id,const int buffer_num,const uint shift); //--- Constructor CIndicatorsCollection(); }; //+------------------------------------------------------------------+
在 AddIndicatorToList() 方法的实现中,添加设置指标缓冲区数量的设定,并创建其时间序列:
//+------------------------------------------------------------------+ //| Add a new indicator object to collection list | //+------------------------------------------------------------------+ int CIndicatorsCollection::AddIndicatorToList(CIndicatorDE *indicator,const int id,const int buffers_total,const uint required=0) { //--- If invalid indicator is passed to the object - return INVALID_HANDLE if(indicator==NULL) return INVALID_HANDLE; //--- If such indicator is already in the list int index=this.Index(indicator); if(index!=WRONG_VALUE) { //--- Remove the earlier created object, get indicator object from the list and return indicator handle delete indicator; indicator=this.m_list.At(index); } //--- If indicator object is not in the list yet else { //--- If failed to add indicator object to the list - display a corresponding message, //--- remove object and return INVALID_HANDLE if(!this.m_list.Add(indicator)) { ::Print(CMessage::Text(MSG_LIB_SYS_FAILED_ADD_IND_TO_LIST)); delete indicator; return INVALID_HANDLE; } } //--- If indicator is successfully added to the list or is already there... //--- If indicator with specified ID (not -1) is not in the list - set ID if(id>WRONG_VALUE && !this.CheckID(id)) indicator.SetID(id); //--- Set the total number of buffers and create data timeseries of all indicator buffers indicator.SetBuffersTotal(buffers_total); this.SeriesCreate(indicator,required); //--- Return handle of a new indicator added to the list return indicator.Handle(); } //+------------------------------------------------------------------+
由于现在指标缓冲区的总数也传递给 AddIndicatorToList() 方法,因此在创建指标对象的所有方法中,必须加入传递缓冲区编号值。 对于标准指标,其确切编号是已知的;而对于自定义指标,编号需在其创建方法中传递。
所有该类的方法都已进行了更改。 我们只研究其中一些。
加速器指标的创建方法:
//+------------------------------------------------------------------+ //| Create a new indicator object Accelerator Oscillator | //| and place it to the collection list | //+------------------------------------------------------------------+ int CIndicatorsCollection::CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id) { //--- AC indicator possesses no parameters - resize the array of parameter structures ::ArrayResize(this.m_mql_param,0); //--- Create indicator object CIndicatorDE *indicator=this.CreateIndicator(IND_AC,this.m_mql_param,symbol,timeframe); //--- Return indicator handle received as a result of adding the object to collection list return this.AddIndicatorToList(indicator,id,1); } //+------------------------------------------------------------------+
当调用把指标对象添加到集合列表的方法时,我还额外指定此指标拥有一个绘制缓冲区。
创建平均方向走势指数指标的方法:
//+------------------------------------------------------------------+ //| Create new indicator object | //| Average Directional Movement Index | //| and place it to the collection list | //+------------------------------------------------------------------+ int CIndicatorsCollection::CreateADX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id,const int adx_period=14) { //--- Add required indicator parameters to the array of parameter structures ::ArrayResize(this.m_mql_param,1); this.m_mql_param[0].type=TYPE_INT; this.m_mql_param[0].integer_value=adx_period; //--- Create indicator object CIndicatorDE *indicator=this.CreateIndicator(IND_ADX,this.m_mql_param,symbol,timeframe); //--- Return indicator handle received as a result of adding the object to collection list return this.AddIndicatorToList(indicator,id,3); } //+------------------------------------------------------------------+
当调用把指标对象添加到集合列表的方法时,我还额外指定此指标拥有三个绘制缓冲区。
创建自定义指标的方法:
//+------------------------------------------------------------------+ //| Create a new object - custom indicator | //| and place it to the collection list | //+------------------------------------------------------------------+ int CIndicatorsCollection::CreateCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id, const int buffers_total, ENUM_INDICATOR_GROUP group, MqlParam &mql_param[]) { //--- Create indicator object CIndicatorDE *indicator=this.CreateIndicator(IND_CUSTOM,mql_param,symbol,timeframe); if(indicator==NULL) return INVALID_HANDLE; //--- Set a group for indicator object indicator.SetGroup(group); //--- Return indicator handle received as a result of adding the object to collection list return this.AddIndicatorToList(indicator,id,buffers_total); } //+------------------------------------------------------------------+
在方法的输入变量中,传递指标的绘制缓冲区数量值,而在调用把指标对象添加到集合列表的方法时,将指定的指标绘制缓冲区数量传递给方法。
按时间返回指向指标缓冲区时间序列的数据对象指针的方法:
//+------------------------------------------------------------------+ //| Return data object of indicator buffer timeseries by time | //+------------------------------------------------------------------+ CDataInd *CIndicatorsCollection::GetDataIndObj(const uint ind_id,const int buffer_num,const datetime time) { CIndicatorDE *indicator=this.GetIndByID(ind_id); if(indicator==NULL) return NULL; CSeriesDataInd *buffers_data=indicator.GetSeriesData(); if(buffers_data==NULL) return NULL; return buffers_data.GetIndDataByTime(buffer_num,time); } //+------------------------------------------------------------------+
此处:依据集合 ID 获取集合中的指标对象,
获取指向其缓冲区数据的集合列表指针,
按时间序列柱线开立时间返回指定缓冲区的数据。
创建指定指标的数据时间序列的方法:
//+------------------------------------------------------------------+ //| Create data timeseries of the specified indicator | //+------------------------------------------------------------------+ bool CIndicatorsCollection::SeriesCreate(CIndicatorDE *indicator,const uint required=0) { if(indicator==NULL) return false; CSeriesDataInd *buffers_data=indicator.GetSeriesData(); if(buffers_data!=NULL) { buffers_data.SetSymbolPeriod(indicator.Symbol(),indicator.Timeframe()); buffers_data.SetIndHandle(indicator.Handle()); buffers_data.SetIndID(indicator.ID()); buffers_data.SetIndBuffersTotal(indicator.BuffersTotal()); buffers_data.SetRequiredUsedData(required); } return(buffers_data!=NULL ? buffers_data.Create(required)>0 : false); } //+------------------------------------------------------------------+
此处:方法接收指向指标对象的指针和需要创建的指标缓冲区数据柱线数量。
从指标对象获取指向其缓冲区数据集合的指针,
设置数据集合的所有必要参数,并
返回请求数量的指标缓冲区数据的创建结果。
创建所有集合指标使用的所有时间序列的方法:
//+------------------------------------------------------------------+ //| Create all timeseries used of all collection indicators | //+------------------------------------------------------------------+ bool CIndicatorsCollection::SeriesCreateAll(const uint required=0) { bool res=true; for(int i=0;i<m_list.Total();i++) { CIndicatorDE *indicator=m_list.At(i); if(!this.SeriesCreate(indicator,required)) res&=false; } return res; } //+------------------------------------------------------------------+
此处:按集合中指标对象的总数进行循环,获取指向其他指标对象的指针,并利用上述方法把据其缓冲区数据创建的时间序列结果加进 res 变量。 直至循环完成后,返回变量 res 的值。 如果至少一个缓冲区数据时间序列未能创建,则此变量所含值为 false。
更新所有集合指标缓冲区数据的方法:
//+------------------------------------------------------------------+ //| Update buffer data of all indicators | //+------------------------------------------------------------------+ void CIndicatorsCollection::SeriesRefreshAll(void) { for(int i=0;i<m_list.Total();i++) { CIndicatorDE *indicator=m_list.At(i); if(indicator==NULL) continue; CSeriesDataInd *buffers_data=indicator.GetSeriesData(); if(buffers_data==NULL) continue; buffers_data.Refresh(); } } //+------------------------------------------------------------------+
此处:按集合中指标对象的总数进行循环,获取指向其他指标对象的指针,获取指向其缓冲区时间序列的数据对象指针,并调用 Refresh() 方法更新时间序列。
更新指定指标缓冲区数据的方法:
//+------------------------------------------------------------------+ //| Update buffer data of the specified indicator | //+------------------------------------------------------------------+ void CIndicatorsCollection::SeriesRefresh(const int ind_id) { CIndicatorDE *indicator=this.GetIndByID(ind_id); if(indicator==NULL) return; CSeriesDataInd *buffers_data=indicator.GetSeriesData(); if(buffers_data==NULL) return; buffers_data.Refresh(); } //+------------------------------------------------------------------+
该方法接收所需指标的 ID。 使用 GetIndByID() 方法获取指向所需指标的指针,获取其缓冲区数据的时间序列对象,并调用 Refresh() 方法更新时间序列。
按时间或柱线索引返回指定指标缓冲区数值的方法:
//+------------------------------------------------------------------+ //| Return buffer value by bar time | //| specified by indicator ID | //+------------------------------------------------------------------+ double CIndicatorsCollection::GetBufferValue(const uint ind_id,const int buffer_num,const datetime time) { CIndicatorDE *indicator=GetIndByID(ind_id); if(indicator==NULL) return EMPTY_VALUE; CSeriesDataInd *series=indicator.GetSeriesData(); return(series!=NULL && series.DataTotal()>0 ? series.BufferValue(buffer_num,time) : EMPTY_VALUE); } //+------------------------------------------------------------------+ //| Return by bar number the buffer value | //| specified by indicator ID | //+------------------------------------------------------------------+ double CIndicatorsCollection::GetBufferValue(const uint ind_id,const int buffer_num,const uint shift) { CIndicatorDE *indicator=GetIndByID(ind_id); if(indicator==NULL) return EMPTY_VALUE; CSeriesDataInd *series=indicator.GetSeriesData(); return(series!=NULL && series.DataTotal()>0 ? series.BufferValue(buffer_num,shift) : EMPTY_VALUE); } //+------------------------------------------------------------------+
方法与其它相同,除了在第一个方法中传递柱线开立时间,而在第二个方法中则传递柱线索引。
获取指向集合中所需指标的指针,获取指向缓冲区数据的集合对象指针,并利用重载的方法 BufferValue() 返回指定缓冲区的数值。
为了将函数库类与“外部世界”连接,要用到函数库的主类 CEngine。 它位于文件 \MQL5\Include\DoEasy\Engine.mqh 当中,在其内可找到从程序访问所有函数库方法的方法。
在类的公开部分中添加处理指标缓冲区数据集合的方法://--- Return (1) the indicator collection, (2) the indicator list from the collection CIndicatorsCollection *GetIndicatorsCollection(void) { return &this.m_indicators; } CArrayObj *GetListIndicators(void) { return this.m_indicators.GetList(); } //--- Create all timeseries used of all collection indicators bool IndicatorSeriesCreateAll(void) { return this.m_indicators.SeriesCreateAll();} //--- Update buffer data of all indicators void IndicatorSeriesRefreshAll(void) { this.m_indicators.SeriesRefreshAll(); } void IndicatorSeriesRefresh(const int ind_id) { this.m_indicators.SeriesRefresh(ind_id);} //--- Return the value of the specified buffer by (1) time, (2) number of the bar specified by indicator ID double IndicatorGetBufferValue(const uint ind_id,const int buffer_num,const datetime time) { return this.m_indicators.GetBufferValue(ind_id,buffer_num,time); } double IndicatorGetBufferValue(const uint ind_id,const int buffer_num,const uint shift) { return this.m_indicators.GetBufferValue(ind_id,buffer_num,shift); }
所有这些方法会调用已添加到上述指标集合类中的相应方法。
在类构造函数中,创建一个新计时器,从而更新指标缓冲区数据集合:
//+------------------------------------------------------------------+ //| CEngine constructor | //+------------------------------------------------------------------+ CEngine::CEngine() : m_first_start(true), m_last_trade_event(TRADE_EVENT_NO_EVENT), m_last_account_event(WRONG_VALUE), m_last_symbol_event(WRONG_VALUE), m_global_error(ERR_SUCCESS) { this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif; this.m_is_tester=::MQLInfoInteger(MQL_TESTER); this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE); this.m_name=::MQLInfoString(MQL_PROGRAM_NAME); this.m_list_counters.Sort(); this.m_list_counters.Clear(); this.CreateCounter(COLLECTION_ORD_COUNTER_ID,COLLECTION_ORD_COUNTER_STEP,COLLECTION_ORD_PAUSE); this.CreateCounter(COLLECTION_ACC_COUNTER_ID,COLLECTION_ACC_COUNTER_STEP,COLLECTION_ACC_PAUSE); this.CreateCounter(COLLECTION_SYM_COUNTER_ID1,COLLECTION_SYM_COUNTER_STEP1,COLLECTION_SYM_PAUSE1); this.CreateCounter(COLLECTION_SYM_COUNTER_ID2,COLLECTION_SYM_COUNTER_STEP2,COLLECTION_SYM_PAUSE2); this.CreateCounter(COLLECTION_REQ_COUNTER_ID,COLLECTION_REQ_COUNTER_STEP,COLLECTION_REQ_PAUSE); this.CreateCounter(COLLECTION_TS_COUNTER_ID,COLLECTION_TS_COUNTER_STEP,COLLECTION_TS_PAUSE); this.CreateCounter(COLLECTION_IND_TS_COUNTER_ID,COLLECTION_IND_TS_COUNTER_STEP,COLLECTION_IND_TS_PAUSE); ::ResetLastError(); #ifdef __MQL5__ if(!::EventSetMillisecondTimer(TIMER_FREQUENCY)) { ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError()); this.m_global_error=::GetLastError(); } //---__MQL4__ #else if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY)) { ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError()); this.m_global_error=::GetLastError(); } #endif //--- } //+------------------------------------------------------------------+
在类的计时器当中,添加按计时器和即时报价操控指标缓冲区数据的时间序列的代码模块(在测试器里):
//+------------------------------------------------------------------+ //| CEngine timer | //+------------------------------------------------------------------+ void CEngine::OnTimer(SDataCalculate &data_calculate) { //--- If this is not a tester, work with collection events by timer if(!this.IsTester()) { //--- Timer of the collections of historical orders and deals, as well as of market orders and positions int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID); CTimerCounter* cnt1=this.m_list_counters.At(index); if(cnt1!=NULL) { //--- If unpaused, work with the order, deal and position collections events if(cnt1.IsTimeDone()) this.TradeEventsControl(); } //--- Account collection timer index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID); CTimerCounter* cnt2=this.m_list_counters.At(index); if(cnt2!=NULL) { //--- If unpaused, work with the account collection events if(cnt2.IsTimeDone()) this.AccountEventsControl(); } //--- Timer 1 of the symbol collection (updating symbol quote data in the collection) index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID1); CTimerCounter* cnt3=this.m_list_counters.At(index); if(cnt3!=NULL) { //--- If the pause is over, update quote data of all symbols in the collection if(cnt3.IsTimeDone()) this.m_symbols.RefreshRates(); } //--- Timer 2 of the symbol collection (updating all data of all symbols in the collection and tracking symbl and symbol search events in the market watch window) index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID2); CTimerCounter* cnt4=this.m_list_counters.At(index); if(cnt4!=NULL) { //--- If the pause is over if(cnt4.IsTimeDone()) { //--- update data and work with events of all symbols in the collection this.SymbolEventsControl(); //--- When working with the market watch list, check the market watch window events if(this.m_symbols.ModeSymbolsList()==SYMBOLS_MODE_MARKET_WATCH) this.MarketWatchEventsControl(); } } //--- Trading class timer index=this.CounterIndex(COLLECTION_REQ_COUNTER_ID); CTimerCounter* cnt5=this.m_list_counters.At(index); if(cnt5!=NULL) { //--- If unpaused, work with the list of pending requests if(cnt5.IsTimeDone()) this.m_trading.OnTimer(); } //--- Timeseries collection timer index=this.CounterIndex(COLLECTION_TS_COUNTER_ID); CTimerCounter* cnt6=this.m_list_counters.At(index); if(cnt6!=NULL) { //--- If the pause is over, work with the timeseries list (update all except the current one) if(cnt6.IsTimeDone()) this.SeriesRefreshAllExceptCurrent(data_calculate); } //--- Timer of timeseries collection of indicator buffer data index=this.CounterIndex(COLLECTION_IND_TS_COUNTER_ID); CTimerCounter* cnt7=this.m_list_counters.At(index); if(cnt7!=NULL) { //--- If the pause is over, work with the timeseries list of indicator data (update all except for the current one) if(cnt7.IsTimeDone()) this.IndicatorSeriesRefreshAll(); } } //--- If this is a tester, work with collection events by tick else { //--- work with events of collections of orders, deals and positions by tick this.TradeEventsControl(); //--- work with events of collections of accounts by tick this.AccountEventsControl(); //--- update quote data of all collection symbols by tick this.m_symbols.RefreshRates(); //--- work with events of all symbols in the collection by tick this.SymbolEventsControl(); //--- work with the list of pending orders by tick this.m_trading.OnTimer(); //--- work with the timeseries list by tick this.SeriesRefresh(data_calculate); //--- work with the timeseries list of indicator buffers by tick this.IndicatorSeriesRefreshAll(); } } //+------------------------------------------------------------------+
这些就是目前的改进。
测试
为了执行测试,我们借用来自上一篇文章中的 EA,并
将其保存到新文件夹 \MQL5\Experts\TestDoEasy\Part58\ 之下,新命名为 TestDoEasyPart58.mq5。
遵照上次 EA 中相同的方式执行测试:四个指标,其中两个是标准指标,两个是自定义指标。
不同之处在于,在先前的 EA 中,我立即在其中创建了所有类的对象,而现在,我将用所创建的指标缓冲区数据集合类的对象,和在函数库中创建的那些对象。
从全局区域删除指向指标数据对象的指针:
//--- Pointers to indicator data objects CDataInd *data_ma1_0=NULL; CDataInd *data_ma1_1=NULL; CDataInd *data_ma2_0=NULL; CDataInd *data_ma2_1=NULL; CDataInd *data_ama1_0=NULL; CDataInd *data_ama1_1=NULL; CDataInd *data_ama2_0=NULL; CDataInd *data_ama2_1=NULL; //+------------------------------------------------------------------+
在 EA 的 OnInit() 处理程序里,添加已创建自定义指标缓冲区的数量:
//--- Create indicators ArrayResize(param_ma1,4); //--- Name of indicator 1 param_ma1[0].type=TYPE_STRING; param_ma1[0].string_value="Examples\\Custom Moving Average.ex5"; //--- Calculation period param_ma1[1].type=TYPE_INT; param_ma1[1].integer_value=13; //--- Horizontal shift param_ma1[2].type=TYPE_INT; param_ma1[2].integer_value=0; //--- Smoothing method param_ma1[3].type=TYPE_INT; param_ma1[3].integer_value=MODE_SMA; //--- Create indicator 1 engine.GetIndicatorsCollection().CreateCustom(NULL,PERIOD_CURRENT,MA1,1,INDICATOR_GROUP_TREND,param_ma1); ArrayResize(param_ma2,5); //--- Name of indicator 2 param_ma2[0].type=TYPE_STRING; param_ma2[0].string_value="Examples\\Custom Moving Average.ex5"; //--- Calculation period param_ma2[1].type=TYPE_INT; param_ma2[1].integer_value=13; //--- Horizontal shift param_ma2[2].type=TYPE_INT; param_ma2[2].integer_value=0; //--- Smoothing method param_ma2[3].type=TYPE_INT; param_ma2[3].integer_value=MODE_SMA; //--- Calculation price param_ma2[4].type=TYPE_INT; param_ma2[4].integer_value=PRICE_OPEN; //--- Create indicator 2 engine.GetIndicatorsCollection().CreateCustom(NULL,PERIOD_CURRENT,MA2,1,INDICATOR_GROUP_TREND,param_ma2);
在 EA 的 OnDeinit() 的处理程序中,把删除已创建指标对象的代码取消:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Remove EA graphical objects by an object name prefix ObjectsDeleteAll(0,prefix); Comment(""); //--- Remove created data objects of MA indicators if(CheckPointer(data_ma1_0)==POINTER_DYNAMIC) delete data_ma1_0; if(CheckPointer(data_ma1_1)==POINTER_DYNAMIC) delete data_ma1_1; if(CheckPointer(data_ma2_0)==POINTER_DYNAMIC) delete data_ma2_0; if(CheckPointer(data_ma2_1)==POINTER_DYNAMIC) delete data_ma2_1; //--- Remove created data objects of AMA indicators if(CheckPointer(data_ama1_0)==POINTER_DYNAMIC) delete data_ama1_0; if(CheckPointer(data_ama1_1)==POINTER_DYNAMIC) delete data_ama1_1; if(CheckPointer(data_ama2_0)==POINTER_DYNAMIC) delete data_ama2_0; if(CheckPointer(data_ama2_1)==POINTER_DYNAMIC) delete data_ama2_1; //--- Deinitialize library engine.OnDeinit(); } //+------------------------------------------------------------------+
与以前的 EA 相比,现在的 OnTick() 处理程序变得更简洁了:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Handle the NewTick event in the library engine.OnTick(rates_data); //--- If work in tester if(MQLInfoInteger(MQL_TESTER)) { engine.OnTimer(rates_data); // Work in timer PressButtonsControl(); // Button press control engine.EventsHandling(); // Work with events } //--- Get indicator values by their IDs from data collections of their buffers from bars 0 and 1 double ma1_value0=engine.IndicatorGetBufferValue(MA1,0,0), ma1_value1=engine.IndicatorGetBufferValue(MA1,0,1); double ma2_value0=engine.IndicatorGetBufferValue(MA2,0,0), ma2_value1=engine.IndicatorGetBufferValue(MA2,0,1); double ama1_value0=engine.IndicatorGetBufferValue(AMA1,0,0), ama1_value1=engine.IndicatorGetBufferValue(AMA1,0,1); double ama2_value0=engine.IndicatorGetBufferValue(AMA2,0,0), ama2_value1=engine.IndicatorGetBufferValue(AMA2,0,1); //--- Display the values of indicator buffers to comment on the chart from data objects Comment ( "ma1(1)=",DoubleToString(ma1_value1,6),", ma1(0)=",DoubleToString(ma1_value0,6)," ", "ma2(1)=",DoubleToString(ma2_value1,6),", ma2(0)=",DoubleToString(ma2_value0,6),", \n", "ama1(1)=",DoubleToString(ama1_value1,6),", ama1(0)=",DoubleToString(ama1_value0,6)," ", "ama1(1)=",DoubleToString(ama2_value1,6),", ama1(0)=",DoubleToString(ama2_value0,6) ); //--- If the trailing flag is set if(trailing_on) { TrailingPositions(); // Trailing positions TrailingOrders(); // Trailing of pending orders } } //+------------------------------------------------------------------+
编译 EA,并在图表上启动它,设置为仅使用当前品种和当前时间帧。 在图表的注释中,将显示所有已创建指标的第一号和零号(当前)柱线的数据:
为了更清晰起见,在图表上绘制了含有相同设置的相同指标 - 图表注释里的信息和数据窗口(Ctrl+D)中的指标数据,且与当前柱线更新数值相匹配。
下一步是什么?
下一篇文章将开始准备创建处理即时报价和市场深度的类。
下面附有当前函数库版本的所有文件,以及 MQL5 的测试 EA 文件。 您可以下载它们,并测试所有内容。
请在文章的评论中留下您的评论、问题和建议。
该系列中的先前文章:
DoEasy 函数库中的时间序列(第三十五部分):柱线对象和品种时间序列列表
DoEasy 函数库中的时间序列(第三十六部分):所有用到的品种周期的时间序列对象
DoEasy 函数库中的时间序列(第三十七部分):时间序列集合 - 按品种和周期的时间序列数据库
DoEasy 函数库中的时间序列(第三十八部分):时间序列集合 - 实时更新以及从程序访问数据
DoEasy 函数库中的时间序列(第三十九部分):基于函数库的指标 - 准备数据和时间序列事件
DoEasy 函数库中的时间序列(第四十部分):基于函数库的指标 - 实时刷新数据
DoEasy 函数库中的时间序列(第四十一部分):多品种多周期指标样品
DoEasy 函数库中的时间序列(第四十二部分):抽象指标缓冲区对象类
DoEasy 函数库中的时间序列(第四十三部分):指标缓冲区对象类
DoEasy 函数库中的时间序列(第四十四部分):指标缓冲区对象集合类
DoEasy 函数库中的时间序列(第四十五部分):多周期指标缓冲区
DoEasy 函数库中的时间序列(第四十六部分):多周期、多品种指标缓冲区
DoEasy 函数库中的时间序列(第四十七部分):多周期、多品种标准指标
DoEasy 函数库中的时间序列(第四十八部分):在单一子窗口里基于一个缓冲区的多周期、多品种指标
DoEasy 函数库中的时间序列(第四十九部分):多周期、多品种、多缓冲区标准指标
DoEasy 函数库中的时间序列(第五十部分):多周期、多品种带位移的标准指标
DoEasy 函数库中的时间序列(第五十一部分):复合多周期、多品种标准指标
DoEasy 函数库中的时间序列(第五十二部分):多周期、多品种单缓冲区标准指标的跨平台性质
DoEasy 函数库中的时间序列(第五十三部分):抽象基准指标类
DoEasy 函数库中的时间序列(第五十四部分):抽象基准指标类的衍生类
DoEasy 函数库中的时间序列(第五十五部分):指标集合类
DoEasy 函数库中的时间序列(第五十六部分):自定义指标对象,从集合中的指标对象获取数据
DoEasy 函数库中的时间序列(第五十七·部分):指标缓冲区数据对象
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/8787
![开发自适应算法 (第二部分): 提高效率](https://c.mql5.com/2/41/50_percents__2.png)
![开发自适应算法(第一部分):寻找基本模式](https://c.mql5.com/2/41/50_percents__1.png)
![神经网络变得轻松(第八部分):关注机制](https://c.mql5.com/2/48/Neural_networks_made_easy_008.png)