在前一篇文章中,我们开启了新系列的 DoEasy 函数库论述,并初试了 Bar 对象和其列表的创建。 相关于 MetaTrader 平台术语,我们为单一品种在单一时间帧之上创建了时间序列,并提取该时间序列的每根柱线数据为其填充。
相关于 DoEasy 函数库术语,我们为单一品种在单一时间帧之上创建了柱线对象集合。 现在,我们可以在集合内按柱线对象的任何属性,在所创建的集合中(在指定的集合历史深度之内)执行任何搜索和排序。 简言之,我们可以搜索时间序列柱线的各种参数,及其各种组合(稍后将添加各种柱线组合),并在所创建集合中查验新柱线创立,并用当前数据更新它。
这挺好,但仍有不足,因为我们可以在程序中使用多个时间帧和品种。 所以,我们可处置的品种时间序列集合与在程序中需要使用的品种时间序列在数量上应相等。
所创建时间序列集合可以胜任这一点 — 它是根据一对品种和时间帧创建的,这意味着我们可以根据需要为一个品种创建任意数量的集合。
将一个品种但不同时间帧的所有集合存储在一个对象中会很方便 — 品种时间序列对象。 然后,创建单个公用时间序列集合,来容纳不同品种及其时间帧的这些对象。
在可预见的未来,许多函数库类会需要了解它们正在运行的程序类型。 为此,以 MQL_PROGRAM_TYPE 指示符为参数调用 MQLInfoInteger) 函数。 在这种情况下,该函数将返回正在运行 mql5 程序的类型。
为避免在每个类中重复编写存储程序类型的变量,我们将在所有程序对象的基类中声明变量。 从基类派生的所有类都将拥有存储所执行程序类型的变量。
在 \MQL5\Include\DoEasy\Objects\BaseObj.mqh 中 CBaseObj 类的保护部分中,声明类成员变量,从而存储运行程序类型:
//+------------------------------------------------------------------+ //| Base object class for all library objects | //+------------------------------------------------------------------+ #define CONTROLS_TOTAL (10) class CBaseObj : public CObject { private: int m_long_prop_total; int m_double_prop_total; //--- Fill in the object property array template<typename T> bool FillPropertySettings(const int index,T &array[][CONTROLS_TOTAL],T &array_prev[][CONTROLS_TOTAL],int &event_id); protected: CArrayObj m_list_events_base; // Object base event list CArrayObj m_list_events; // Object event list ENUM_LOG_LEVEL m_log_level; // Logging level ENUM_PROGRAM_TYPE m_program; // Program type MqlTick m_tick; // Tick structure for receiving quote data
且在类构造函数中,为其赋值:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CBaseObj::CBaseObj() : m_global_error(ERR_SUCCESS), m_hash_sum(0),m_hash_sum_prev(0), m_is_event(false),m_event_code(WRONG_VALUE), m_chart_id_main(::ChartID()), m_chart_id(::ChartID()), m_folder_name(DIRECTORY), m_name(__FUNCTION__), m_long_prop_total(0), m_double_prop_total(0), m_first_start(true) { ::ArrayResize(this.m_long_prop_event,0,100); ::ArrayResize(this.m_double_prop_event,0,100); ::ArrayResize(this.m_long_prop_event_prev,0,100); ::ArrayResize(this.m_double_prop_event_prev,0,100); ::ZeroMemory(this.m_tick); this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE); this.m_digits_currency=(#ifdef __MQL5__ (int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS) #else 2 #endif); this.m_list_events.Clear(); this.m_list_events.Sort(); this.m_list_events_base.Clear(); this.m_list_events_base.Sort(); } //+------------------------------------------------------------------+
现在,所有自函数库对象的基类派生出的所有对象均“知道”它们正在运行的程序类型。
在 \MQL5\Include\DoEasy\Objects\Series\NewBarObj.mqh 清单里的 CNewBarObj 类中,包含基准对象类文件:
//+------------------------------------------------------------------+ //| NewBarObj.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 "..\..\Objects\BaseObj.mqh" //+------------------------------------------------------------------+ //| "New bar" object class | //+------------------------------------------------------------------+
从基准对象继承 “New bar” 对象:
//+------------------------------------------------------------------+ //| "New bar" object class | //+------------------------------------------------------------------+ class CNewBarObj : public CBaseObj { private:
并从清单中删除“所有提及”的程序类型变量 — 现在,程序类型移至 CBaseObj 里设置:
//+------------------------------------------------------------------+ //| "New bar" object class | //+------------------------------------------------------------------+ class CNewBarObj { private: ENUM_PROGRAM_TYPE m_program; // Program type string m_symbol; // Symbol ENUM_TIMEFRAMES m_timeframe; // Timeframe datetime m_new_bar_time; // New bar time for auto time management datetime m_prev_time; // Previous time for auto time management datetime m_new_bar_time_manual; // New bar time for manual time management datetime m_prev_time_manual; // Previous time for manual time management //--- Return the current bar data datetime GetLastBarDate(const datetime time); public: //--- Set (1) symbol and (2) timeframe void SetSymbol(const string symbol) { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); } void SetPeriod(const ENUM_TIMEFRAMES timeframe) { this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe); } //--- Save the new bar time during the manual time management void SaveNewBarTime(const datetime time) { this.m_prev_time_manual=this.GetLastBarDate(time); } //--- Return (1) symbol and (2) timeframe string Symbol(void) const { return this.m_symbol; } ENUM_TIMEFRAMES Period(void) const { return this.m_timeframe; } //--- Return the (1) new bar time datetime TimeNewBar(void) const { return this.m_new_bar_time; } //--- Return the new bar opening flag during the time (1) auto, (2) manual management bool IsNewBar(const datetime time); bool IsNewBarManual(const datetime time); //--- Constructors CNewBarObj(void) : m_program((ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE)), m_symbol(::Symbol()), m_timeframe((ENUM_TIMEFRAMES)::Period()), m_prev_time(0),m_new_bar_time(0), m_prev_time_manual(0),m_new_bar_time_manual(0) {} CNewBarObj(const string symbol,const ENUM_TIMEFRAMES timeframe); }; //+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CNewBarObj::CNewBarObj(const string symbol,const ENUM_TIMEFRAMES timeframe) : m_symbol(symbol),m_timeframe(timeframe) { this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE); this.m_prev_time=this.m_prev_time_manual=this.m_new_bar_time=this.m_new_bar_time_manual=0; } //+------------------------------------------------------------------+
在文章所附的文件里可找到校对后的全部类清单。
我们稍微修改前一篇文章中创建的 CSeries 类。
类自 CBaseObj 基准对象继承而来,而非 CObject,并从其中删除存储程序类型的变量://+------------------------------------------------------------------+ //| Timeseries class | //+------------------------------------------------------------------+ class CSeries : public CBaseObj { private: ENUM_PROGRAM_TYPE m_program; // Program type ENUM_TIMEFRAMES m_timeframe; // Timeframe string m_symbol; // Symbol uint m_amount; // 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_series; // Timeseries list CNewBarObj m_new_bar_obj; // "New bar" object public:
在该类的公开部分,声明设置品种和时间帧的方法,并完整实现返回可用数据总量的方法:
//+------------------------------------------------------------------+ //| Timeseries class | //+------------------------------------------------------------------+ class CSeries : public CBaseObj { private: ENUM_TIMEFRAMES m_timeframe; // Timeframe string m_symbol; // Symbol uint m_amount; // 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_series; // Timeseries list CNewBarObj m_new_bar_obj; // "New bar" object public: //--- Return the timeseries list CArrayObj *GetList(void) { return &m_list_series;} //--- Return the list of bars by selected (1) double, (2) integer and (3) string property fitting a compared condition CArrayObj *GetList(ENUM_BAR_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByBarProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_BAR_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByBarProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_BAR_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByBarProperty(this.GetList(),property,value,mode); } //--- Set (1) symbol, (2) timeframe, (3) symbol and timeframe, (4) amount of applied timeseries data void SetSymbol(const string symbol); void SetTimeframe(const ENUM_TIMEFRAMES timeframe); void SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe); bool SetAmountUsedData(const uint amount,const uint rates_total); //--- Return (1) symbol, (2) timeframe, (3) number of applied timeseries data, //--- (4) number of bars in the timeseries, the new bar flag with the (5) auto, (6) manual time management string Symbol(void) const { return this.m_symbol; } ENUM_TIMEFRAMES Timeframe(void) const { return this.m_timeframe; } uint AmountUsedData(void) const { return this.m_amount; } uint Bars(void) const { return this.m_bars; } 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 the bar object by index (1) in the list and (2) in the timeseries, as well as (3) the real list size CBar *GetBarByListIndex(const uint index); CBar *GetBarBySeriesIndex(const uint index); int DataTotal(void) const { return this.m_list_series.Total(); } //--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) time, (6) tick volume, (7) real volume, (8) bar spread by index double Open(const uint index,const bool from_series=true); double High(const uint index,const bool from_series=true); double Low(const uint index,const bool from_series=true); double Close(const uint index,const bool from_series=true); datetime Time(const uint index,const bool from_series=true); long TickVolume(const uint index,const bool from_series=true); long RealVolume(const uint index,const bool from_series=true); int Spread(const uint index,const bool from_series=true); //--- Save the new bar time during the manual time management void SaveNewBarTime(const datetime time) { this.m_new_bar_obj.SaveNewBarTime(time); } //--- Synchronize symbol and timeframe data with server data bool SyncData(const uint amount,const uint rates_total); //--- (1) Create and (2) update the timeseries list int Create(const uint amount=0); void Refresh(const datetime time=0, const double open=0, const double high=0, const double low=0, const double close=0, const long tick_volume=0, const long volume=0, const int spread=0); //--- Constructors CSeries(void); CSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint amount=0); }; //+------------------------------------------------------------------+
在类主体之外,实现设置品种和时间帧的方法:
//+------------------------------------------------------------------+ //| Set a symbol | //+------------------------------------------------------------------+ void CSeries::SetSymbol(const string symbol) { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_new_bar_obj.SetSymbol(this.m_symbol); } //+------------------------------------------------------------------+ //| Set a timeframe | //+------------------------------------------------------------------+ void CSeries::SetTimeframe(const ENUM_TIMEFRAMES timeframe) { this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe); this.m_new_bar_obj.SetPeriod(this.m_timeframe); } //+------------------------------------------------------------------+
传递给方法的参数值,如有必要需进行检查和调整。 然后将它们发送到变量。
此后,该值在 “New bar” 类对象予以赋值。
\MQL5\Include\DoEasy\Services\Select.mqh 中的 CSelect 类清单将包含 CSeries 类文件,而非 CBar 类的 Bar.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\Series.mqh" //+------------------------------------------------------------------+ //| Storage list | //+------------------------------------------------------------------+ CArrayObj ListStorage; // Storage object for storing sorted collection lists //+------------------------------------------------------------------+ //| Class for sorting objects meeting the criterion | //+------------------------------------------------------------------+ class CSelect {
至此,为所有品种时间序列创建对象的类一切就绪。
品种时间序列对象是什么? 在前一篇文章中,我们为单个品种创建了一个时间段的时间序列对象。 现在,该列表内的所有柱线对象都可以按任何柱线对象属性进行排序,任何柱线对象都可以按其任何属性进行检测,等等。 然而,程序通常需要用一个或若干个品种的历史周期进行分析。 品种时间序列对象包含一个品种所有可能时间帧的多个时间序列。 时间帧的数量可以等于 ENUM_TIMEFRAMES 枚举中所定义的终端可用图表周期的数量。
实际上,该对象是 CArrayObj 对象的指针数组,而其中对象是我在前一篇文章中所创建的品种时间序列列表。 反过来,它们也包含柱线对象。
在本文中,我们将创建所有品种时间序列的对象,从而允许:
其余的对象功能,会在后续文章中创建所有用到品种的所有时间序列对象时再添加。
与往常一样,我们首先为新类添加所需的所有消息 — 消息索引及其相应的文本。
在 \MQL5\Include\DoEasy\Datas.mqh 中的 Datas.mqh 文件里,添加新的消息索引:
MSG_LIB_TEXT_BAR_TEXT_FIRS_SET_AMOUNT_DATA, // First, we need to set the required amount of data using SetAmountUsedData() //--- CTimeSeries MSG_LIB_TEXT_TS_TEXT_FIRS_SET_SYMBOL, // First, set a symbol using SetSymbol() MSG_LIB_TEXT_TS_TEXT_UNKNOWN_TIMEFRAME, // Unknown timeframe MSG_LIB_TEXT_TS_FAILED_GET_SERIES_OBJ, // Failed to receive the timeseries object }; //+------------------------------------------------------------------+
及与新添加的索引相对应的消息文本:
{"Сначала нужно установить требуемое количество данных при помощи SetAmountUsedData()","First you need to set required amount of data using SetAmountUsedData()"}, {"Сначала нужно установить символ при помощи SetSymbol()","First you need to set Symbol using SetSymbol()"}, {"Неизвестный таймфрейм","Unknown timeframe"}, {"Не удалось получить объект-таймсерию ","Failed to get timeseries object "}, }; //+---------------------------------------------------------------------+
再 \MQL5\Include\DoEasy\Objects\Series\ 之下,创建 TimeSeries.mqh 文件,内含继承自标准库基准对象 CObject 的 CTimeSeries 类,且其与 Series.mqh 时间序列对象文件相关联。
在类主体里填充所需的内容,然后分别研究所有变量和方法:
//+------------------------------------------------------------------+ //| TimeSeries.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 "Series.mqh" //+------------------------------------------------------------------+ //| Timeseries class | //+------------------------------------------------------------------+ class CTimeSeries : public CObject { private: string m_symbol; // Timeseries symbol CArrayObj m_list_series; // List of timeseries by timeframes //--- Return (1) the timeframe index in the list and (2) the timeframe by index char IndexTimeframe(const ENUM_TIMEFRAMES timeframe) const; ENUM_TIMEFRAMES TimeframeByIndex(const uchar index) const; public: //--- Return (1) the full list of timeseries, (2) specified timeseries object and (3) timeseries object by index CArrayObj *GetList(void) { return &this.m_list_series; } CSeries *GetSeries(const ENUM_TIMEFRAMES timeframe) { return this.m_list_series.At(this.IndexTimeframe(timeframe)); } CSeries *GetSeriesByIndex(const uchar index) { return this.m_list_series.At(index); } //--- Set/return timeseries symbol void SetSymbol(const string symbol) { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); } string Symbol(void) const { return this.m_symbol; } //--- Set the history depth (1) of a specified timeseries and (2) of all applied symbol timeseries bool SetAmountUsedData(const ENUM_TIMEFRAMES timeframe,const uint amount=0,const int rates_total=0); bool SetAmountAllUsedData(const uint amount=0,const int rates_total=0); //--- Return the flag of data synchronization with the server data of the (1) specified timeseries, (2) all timeseries bool SyncData(const ENUM_TIMEFRAMES timeframe,const uint amount=0,const uint rates_total=0); bool SyncAllData(const uint amount=0,const uint rates_total=0); //--- Create (1) the specified timeseries list and (2) all timeseries lists bool SeriesCreate(const ENUM_TIMEFRAMES timeframe,const uint amount=0); bool SeriesCreateAll(const uint amount=0); //--- Update (1) the specified timeseries list and (2) all timeseries lists void Refresh(const ENUM_TIMEFRAMES timeframe, const datetime time=0, const double open=0, const double high=0, const double low=0, const double close=0, const long tick_volume=0, const long volume=0, const int spread=0); void RefreshAll(const datetime time=0, const double open=0, const double high=0, const double low=0, const double close=0, const long tick_volume=0, const long volume=0, const int spread=0); //--- Constructor CTimeSeries(void); }; //+------------------------------------------------------------------+
m_symbol 类成员变量存储一个品种名称,其为必须,表明此对象是为哪个品种创建时间序列、存储和处理。 随后,变量值会用在选择含该品种的时间序列对象。
指向 CObject 类实例的指针数组 m_list_series 旨在存储前一篇文章中创建的时间序列对象。 列表中的对象数量可以等于平台中所有可用时间帧的数量,并依照 ENUM_TIMEFRAMES 枚举中的列举顺序将它们排列在列表中。 反之,也可令我们确切地知道列表中每个时间序列对象的索引。 为了返回时间序列对象列表中的索引,创建了两个方法:
IndexTimeframe() 方法按时间帧值返回列表中的时间序列对象索引。
在类主体之外实现它:
//+------------------------------------------------------------------+ //| Return the timeframe index in the list | //+------------------------------------------------------------------+ char CTimeSeries::IndexTimeframe(ENUM_TIMEFRAMES timeframe) const { int statement=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe); switch(statement) { case PERIOD_M1 : return 0; case PERIOD_M2 : return 1; case PERIOD_M3 : return 2; case PERIOD_M4 : return 3; case PERIOD_M5 : return 4; case PERIOD_M6 : return 5; case PERIOD_M10 : return 6; case PERIOD_M12 : return 7; case PERIOD_M15 : return 8; case PERIOD_M20 : return 9; case PERIOD_M30 : return 10; case PERIOD_H1 : return 11; case PERIOD_H2 : return 12; case PERIOD_H3 : return 13; case PERIOD_H4 : return 14; case PERIOD_H6 : return 15; case PERIOD_H8 : return 16; case PERIOD_H12 : return 17; case PERIOD_D1 : return 18; case PERIOD_W1 : return 19; case PERIOD_MN1 : return 20; default : ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_TEXT_UNKNOWN_TIMEFRAME)); return WRONG_VALUE; } } //+------------------------------------------------------------------+
此处的一切都很清晰明了。 根据传递给该方法的时间帧,返回其在 ENUM_TIMEFRAMES 枚举中的序列号(以及相应的在 m_list_series 列表中的索引)。
TimeframeByIndex() 方法按列表中的时间序列对象索引返回时间帧。
在类主体之外实现它:
//+------------------------------------------------------------------+ //| Return a timeframe by index | //+------------------------------------------------------------------+ ENUM_TIMEFRAMES CTimeSeries::TimeframeByIndex(const uchar index) const { switch(index) { case 0 : return PERIOD_M1; case 1 : return PERIOD_M2; case 2 : return PERIOD_M3; case 3 : return PERIOD_M4; case 4 : return PERIOD_M5; case 5 : return PERIOD_M6; case 6 : return PERIOD_M10; case 7 : return PERIOD_M12; case 8 : return PERIOD_M15; case 9 : return PERIOD_M20; case 10 : return PERIOD_M30; case 11 : return PERIOD_H1; case 12 : return PERIOD_H2; case 13 : return PERIOD_H3; case 14 : return PERIOD_H4; case 15 : return PERIOD_H6; case 16 : return PERIOD_H8; case 17 : return PERIOD_H12; case 18 : return PERIOD_D1; case 19 : return PERIOD_W1; case 20 : return PERIOD_MN1; default : ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_DATAS),"... ",CMessage::Text(MSG_SYM_STATUS_INDEX),": ",(string)index); return WRONG_VALUE; } } //+------------------------------------------------------------------+
此方法与 IndexTimeframe() 互逆。 根据传递给该方法的索引,按其在 ENUM_TIMEFRAMES 枚举中的位置顺序返回相应的时间帧。
Getlist() 方法将所有时间序列的完整列表“按原样”返回给控制程序。 在程序中,您可以从获取的列表中选择必要的时间序列。
GetSeries() 方法依照来自 ENUM_TIMEFRAMES 枚举的所需时间序列的名称从 m_list_series 列表中返回指定的时间序列对象。 利用之前研究的 IndexTimeframe() 方法获取列表中的时间序列索引
GetSeriesByIndex() 方法依照 m_list_series 列表中的索引返回时间序列对象。
为指定时间序列设置历史深度的方法实现:
//+------------------------------------------------------------------+ //| Set a history depth of a specified timeseries | //+------------------------------------------------------------------+ bool CTimeSeries::SetAmountUsedData(const ENUM_TIMEFRAMES timeframe,const uint amount=0,const int rates_total=0) { if(this.m_symbol==NULL) { ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_TEXT_FIRS_SET_SYMBOL)); return false; } CSeries *series_obj=this.m_list_series.At(this.IndexTimeframe(timeframe)); return series_obj.SetAmountUsedData(amount,rates_total); } //+------------------------------------------------------------------+
方法接收需要设置历史深度的时间序列时间帧,所需的历史时间序列数据的大小(历史深度;如果为 0,则用 1000 根深度),以及当前时间序列柱线的数量(仅用于指标,针对当前品种和当前时间帧设置历史深度 — 将 rates_total 参数传递给 OnCalculate();在其他情况下,该参数并不重要)。
如果类对象尚未设置品种,则显示相应的消息,并返回 false。
按时间帧名称获得索引,并从列表获取索引处的被请求时间序列对象,然后调用我们在前一篇文章中所研究的时间序列对象类中的同名方法设置历史深度,并返回结果。
为所有用到的品种时间序列设置历史深度的方法实现:
//+------------------------------------------------------------------+ //| Set the history depth of all applied symbol timeseries | //+------------------------------------------------------------------+ bool CTimeSeries::SetAmountAllUsedData(const uint amount=0,const int rates_total=0) { if(this.m_symbol==NULL) { ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_TEXT_FIRS_SET_SYMBOL)); return false; } bool res=true; for(int i=0;i<21;i++) { CSeries *series_obj=this.m_list_series.At(i); if(series_obj==NULL) continue; res &=series_obj.SetAmountUsedData(amount,rates_total); } return res; } //+------------------------------------------------------------------+
方法接收所需历史时间序列数据大小(历史深度;如果为 0,则采用 1000 根柱线深度),以及当前时间序列的柱数(仅用于指标,针对当前品种和当前时间帧设置历史深度 — 将 rates_total 参数传递给 OnCalculate(),在其他情况下,该参数并不重要)。
如果类对象尚未设置品种,则显示相应的消息,并返回 false。
在循环里遍历所有存在时间帧的完整列表,依照循环索引从列表中获取下一个时间序列列表,然后调用前一篇文章中研究过的时间序列对象类同名方法将设置历史深度的结果写入本地 res 变量。 如果设置任何可用时间序列对象历史深度的方法之中至少其一返回了 false,则在变量中设置 false。
循环完成后,所有设置返回的结果写入 res变量。
返回指定时间序列数据与服务器同步数据标志的方法实现:
//+------------------------------------------------------------------+ //| Return the flag of data synchronization | //| with the server data | //+------------------------------------------------------------------+ bool CTimeSeries::SyncData(const ENUM_TIMEFRAMES timeframe,const uint amount=0,const uint rates_total=0) { if(this.m_symbol==NULL) { ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_TEXT_FIRS_SET_SYMBOL)); return false; } CSeries *series_obj=this.m_list_series.At(this.IndexTimeframe(timeframe)); if(series_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_FAILED_GET_SERIES_OBJ),this.m_symbol," ",TimeframeDescription(timeframe)); return false; } return series_obj.SyncData(amount,rates_total); } //+------------------------------------------------------------------+
方法接收需要同步标志的时间序列时间帧,所需的历史时间序列数据的大小(历史深度;如果为 0,则用 1000 根深度),以及当前时间序列柱线的数量(仅用于指标,针对当前品种和当前时间帧设置历史深度 — 将 rates_total 参数传递给 OnCalculate();在其他情况下,该参数并不重要)。
如果类对象尚未设置品种,则显示相应的消息,并返回 false。
按时间帧名称获得索引,并从列表获取索引处的被请求时间序列对象,然后调用我们在前一篇文章中所研究的时间序列对象类中的同名方法设置历史深度,并返回结果。
返回所有时间序列与服务器同步数据标志的方法实现:
//+------------------------------------------------------------------+ //| Return the flag of data synchronization | //| of all timeseries with the server data | //+------------------------------------------------------------------+ bool CTimeSeries::SyncAllData(const uint amount=0,const uint rates_total=0) { if(this.m_symbol==NULL) { ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_TEXT_FIRS_SET_SYMBOL)); return false; } bool res=true; for(int i=0;i<21;i++) { CSeries *series_obj=this.m_list_series.At(i); if(series_obj==NULL) continue; res &=series_obj.SyncData(amount,rates_total); } return res; } //+------------------------------------------------------------------+
方法接收所需历史时间序列数据大小(历史深度;如果为 0,则采用 1000 根柱线深度),以及当前时间序列的柱数(仅用于指标,针对当前品种和当前时间帧设置历史深度 — 将 rates_total 参数传递给 OnCalculate(),在其他情况下,该参数并不重要)。
如果类对象尚未设置品种,则显示相应的消息,并返回 false。
在所有存在时间帧的完整列表里循环,依照循环索引从列表中获取下一个时间序列列表,然后调用前一篇文章中研究过的时间序列对象类同名方法检查与服务器之间的数据同步,并将标志写入本地 res 变量。 如果检查时间序列对象同步的方法之中至少其一返回了 false,则在变量中设置 false。
循环完成后,返回 res 变量中设置的所有检查的结果。
创建指定时间序列列表的方法实现:
//+------------------------------------------------------------------+ //| Create a specified timeseries list | //+------------------------------------------------------------------+ bool CTimeSeries::Create(const ENUM_TIMEFRAMES timeframe,const uint amount=0) { CSeries *series_obj=this.m_list_series.At(this.IndexTimeframe(timeframe)); if(series_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_FAILED_GET_SERIES_OBJ),this.m_symbol," ",TimeframeDescription(timeframe)); return false; } if(series_obj.AmountUsedData()==0) { ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_TEXT_FIRS_SET_AMOUNT_DATA)); return false; } return(series_obj.Create(amount)>0); } //+------------------------------------------------------------------+
方法接收应创建的时间序列时间帧,以及所创建时间序列的历史深度(默认值为零 — 按预设历史深度,调用 SetAmountUsedData() 方法创建一个时间序列;如果该值超过零,且小于指定图表周期的可用时间序列柱线数量,则采用传递给该方法的历史深度创建)
按时间帧名称的索引获取必要的时间序列对象。 如果获取对象失败,或尚未为其设置历史深度,则显示相应的消息,并返回 false。
调用我们在前一篇文章中研究过的时间序列对象同名方法返回时间序列创建结果。 由于前一篇文章中论述的时间序列创建方法,返回加到时间序列列表中的柱线对象数量,而当前方法返回的是布尔值,因此加入列表的元素数量与“大于零”列表进行比较,并返回 true 或 false 结果就足够了。 这正是我们在此所做的。
创建所有时间序列列表的方法实现:
//+------------------------------------------------------------------+ //| Create all timeseries lists | //+------------------------------------------------------------------+ bool CTimeSeries::CreateAll(const uint amount=0) { bool res=true; for(int i=0;i<21;i++) { CSeries *series_obj=this.m_list_series.At(i); if(series_obj==NULL || series_obj.AmountUsedData()==0) continue; res &=(series_obj.Create(amount)>0); } return res; } //+------------------------------------------------------------------+
方法接收创建时间序列的历史深度(默认值为零 — 按预设历史深度,调用 SetAmountUsedData() 方法创建一个时间序列;如果该值超过零,则 小于指定图表周期的可用时间序列柱线数量,则采用传递给该方法的历史深度创建。
在所有时间帧列表里循环,按循环索引获取下一个时间序列对象。 如果获取对象失败,或尚未为其设置历史深度,则移至下一个时间帧。
本地 res 变量接收创建时间序列的结果,过程是调用我们在前一篇文章中研究过的时间序列对象类的同名方法,并表达为列表中添加的元素数量与“大于零”的比较结果。 如果创建时间序列对象的方法中至少其一返回 false,则该变量将设置为 false。
循环完成后,创建所有时间序列的返回结果写入 res变量。
刷新指定时间序列列表的方法实现:
//+------------------------------------------------------------------+ //| Update a specified timeseries list | //+------------------------------------------------------------------+ void CTimeSeries::Refresh(const ENUM_TIMEFRAMES timeframe, const datetime time=0, const double open=0, const double high=0, const double low=0, const double close=0, const long tick_volume=0, const long volume=0, const int spread=0) { CSeries *series_obj=this.m_list_series.At(this.IndexTimeframe(timeframe)); if(series_obj==NULL || series_obj.DataTotal()==0) return; series_obj.Refresh(time,open,high,low,close,tick_volume,volume,spread); } //+------------------------------------------------------------------+
方法接收所要更新的时间序列时间帧,和当前柱线的价格数据(仅用于指标,针对当前品种和当前时间帧更新数据 — 数据由价格数组传递给 OnCalculate();在其他情况下,传递的参数值无关紧要)。
按时间帧名称的索引获取必要的时间序列对象。 如果获取对象失败,或创建的时间帧历史记录的大小为零(未用或尚未调用 Create() 方法创建时间帧),则退出方法。
接着,调用我们在前一篇文章中研究过的时间序列对象的同名刷新方法。
刷新所有时间序列列表的方法实现:
//+------------------------------------------------------------------+ //| Update all timeseries lists | //+------------------------------------------------------------------+ void CTimeSeries::RefreshAll(const datetime time=0, const double open=0, const double high=0, const double low=0, const double close=0, const long tick_volume=0, const long volume=0, const int spread=0) { for(int i=0;i<21;i++) { CSeries *series_obj=this.m_list_series.At(i); if(series_obj==NULL || series_obj.DataTotal()==0) continue; series_obj.Refresh(time,open,high,low,close,tick_volume,volume,spread); } } //+------------------------------------------------------------------+
方法接收当前的柱线价格数据(仅适用于指标,针对当前品种和当前时间帧更新数据 — 将数据由价格数组传递给 OnCalculate();在其他情况下,传递的参数值无所谓)。
在所有时间帧列表里循环,按循环索引获取下一个时间序列对象。如果获取对象失败,或创建的时间帧历史记录的大小为零(未用或尚未调用 Create() 方法创建时间帧),则移至下一个时间帧。
接下来,调用 在前一篇文章中研究过的时间序列对象同名方法。
单一品种所有时间序列对象类的第一个版本已准备就绪。 类的当前功能足以用于测试单个品种的多个时间帧。 在未来,为多个品种创建公共的时间序列集合类之时,我们会对其重新优化。
在 \MQL5\Include\DoEasy\Engine.mqh 中的 CEngine 类文件里,把包含时间序列对象类文件的字符串:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Services\TimerCounter.mqh" #include "Collections\HistoryCollection.mqh" #include "Collections\MarketCollection.mqh" #include "Collections\EventsCollection.mqh" #include "Collections\AccountsCollection.mqh" #include "Collections\SymbolsCollection.mqh" #include "Collections\ResourceCollection.mqh" #include "TradingControl.mqh" #include "Objects\Series\Series.mqh" //+------------------------------------------------------------------+
替换为包含所有用到品种周期的时间序列对象文件:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Services\TimerCounter.mqh" #include "Collections\HistoryCollection.mqh" #include "Collections\MarketCollection.mqh" #include "Collections\EventsCollection.mqh" #include "Collections\AccountsCollection.mqh" #include "Collections\SymbolsCollection.mqh" #include "Collections\ResourceCollection.mqh" #include "TradingControl.mqh" #include "Objects\Series\TimeSeries.mqh" //+------------------------------------------------------------------+
现在,新创建的类在任意基于函数库的程序中可见。
为了测试操控单个品种在不同区间的时间序列,我们将利用前一篇文章的 EA,并将其保存在 \MQL5\Experts\TestDoEasy\Part36\,命名为 TestDoEasyPart36.mq5。
若要测试该类,请使用条件编译指令创建两个版本的测试 EA。
在 EA 全局变量模块中,保留时间序列类的单个对象。 由于 CSeries 类对象现在是 CTimeSeries 类对象的一部分,因此定义 CTimeSeries 类变量,来替代 CSeries 类。
删除一个 CSeries 对象:
//--- global variables CEngine engine; CSeries series; CSeries series_m1; SDataButt butt_data[TOTAL_BUTT];
另外,重命名第二个对象,并将其类型定义为 CTimeSeries:
//--- global variables CEngine engine; CTimeSeries timeseries; SDataButt butt_data[TOTAL_BUTT];
在 EA 的 OnInit() 处理程序的最后,根据指定的 TIMESERIES_ALL ID 的存在/不存在,编写创建时间序列的代码模块:
//--- Check playing a standard sound by macro substitution and a custom sound by description engine.PlaySoundByDescription(SND_OK); Sleep(600); engine.PlaySoundByDescription(TextByLanguage("Звук упавшей монетки 2","Falling coin 2")); //--- Set a symbol for created timeseries timeseries.SetSymbol(Symbol()); //#define TIMESERIES_ALL //--- Create two timeseries #ifndef TIMESERIES_ALL timeseries.SyncData(PERIOD_CURRENT,10); timeseries.Create(PERIOD_CURRENT); timeseries.SyncData(PERIOD_M15,2); timeseries.Create(PERIOD_M15); //--- Create all timeseries #else timeseries.SyncAllData(); timeseries.CreateAll(); #endif //--- Check created timeseries CArrayObj *list=timeseries.GetList(); Print(TextByLanguage("Данные созданных таймсерий:","Data of created timeseries:")); for(int i=0;i<list.Total();i++) { CSeries *series_obj=timeseries.GetSeriesByIndex((uchar)i); if(series_obj==NULL || series_obj.AmountUsedData()==0 || series_obj.DataTotal()==0) continue; Print( DFUN,i,": ",series_obj.Symbol()," ",TimeframeDescription(series_obj.Timeframe()), ": AmountUsedData=",series_obj.AmountUsedData(),", DataTotal=",series_obj.DataTotal(),", Bars=",series_obj.Bars() ); } Print(""); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
首先,确保为品种时间序列类设置品种名称。 接下来,我们将曾定义的 ID 注释掉,换为宏替换。 它的存在/不存在定义了要编译的代码的版本 — 其一创建两个时间序列(如果没有 ID),其二创建所有时间序列(如果有 ID)。
通常,依所有品种时间序列对象类的当前版本,我们需要以下内容来创建时间序列:
正常的话,设置历史深度和检查服务器同步的方法返回的结果应加以验证。 如果无法设置历史深度,或数据尚未与服务器同步,则该方法可以返回 false。
不过,我们现在可以跳过此检查,从而在当前品种上执行测试。 最有可能的是,整个数据均可用。 即便数据不可用,简单地不创建时间序列就可。 由于首次访问函数可能会重启 EA,加载初始历史记录数据,启动加载后,数据应在首次 EA 启动期间与服务器同步。
创建必要的时间序列后,在日志中显示所创建时间序列的完整列表,以便检查创建是否成功。
为此,得到所有时间序列的完整列表。 在循环中,从列表中得到下一个时间序列对象。 如果时间序列已被创建(具有历史深度,并填充了数据),则在日志中显示数据。
在 OnTick() 处理程序的末尾,插入更新所有已创建品种时间序列数据的代码模块:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- If working in the tester if(MQLInfoInteger(MQL_TESTER)) { engine.OnTimer(); // Working in the timer PressButtonsControl(); // Button pressing control EventsHandling(); // Working with events } //--- If the trailing flag is set if(trailing_on) { TrailingPositions(); // Trailing positions TrailingOrders(); // Trailing of pending orders } //--- Update created timeseries CArrayObj *list=timeseries.GetList(); for(int i=0;i<list.Total();i++) { CSeries *series_obj=timeseries.GetSeriesByIndex((uchar)i); if(series_obj==NULL || series_obj.DataTotal()==0) continue; series_obj.Refresh(); if(series_obj.IsNewBar(0)) { Print(TextByLanguage("Новый бар на ","New bar on "),series_obj.Symbol()," ",TimeframeDescription(series_obj.Timeframe())," ",TimeToString(series_obj.Time(0))); if(series_obj.Timeframe()==Period()) engine.PlaySoundByDescription(SND_NEWS); } } } //+------------------------------------------------------------------+
在此,从所有品种时间序列的对象中获取时间序列的列表。 在时间序列列表中循环,按循环索引获取下一个时间序列对象。 如果接收时间序列失败,或没有数据(柱线),则移至下一个时间帧的下一个时间序列。 如果得到时间序列对象,则刷新它。 If the new bar flag is set for the timeseries, display the appropriate message (also play the news.wav sound for the current period timeseries)
编译 EA(176 行含有 #define TIMESERIES_ALL 宏替换定义的字符串应该被注释掉)— 编译创建两个时间序列的 EA 版本。
在终端的 M30 图表里的启动它。 在日志中会显示有关两个已创建时间序列的参数。 一段时间后,出现有关在 M15 和 M30 图表上创立新柱线的记录:
Account 8550475: Artyom Trishkin (MetaQuotes Software Corp.) 10425.23 USD, 1:100, Hedge, Demo account MetaTrader 5 Work only with the current symbol. The number of symbols used: 1 Data of created timeseries: OnInit: 8: EURUSD M15: AmountUsedData=2, DataTotal=2, Bars=5000 OnInit: 10: EURUSD M30: AmountUsedData=10, DataTotal=10, Bars=5030 New bar on EURUSD M15 2020.02.20 20:45 New bar on EURUSD M15 2020.02.20 21:00 New bar on EURUSD M30 2020.02.20 21:00 New bar on EURUSD M15 2020.02.20 21:15 New bar on EURUSD M15 2020.02.20 21:30 New bar on EURUSD M30 2020.02.20 21:30
现在,取消 176 行定义 #define TIMESERIES_ALL 宏替换字符串的注释,并编译 EA — 生成采用默认值创建所有时间序列的 EA 版本。
在品种图表上启动它。 在日志中显示有关所有已创建时间序列的参数。 在一段时间后,会出现有关在已创建的时间序列图周期上创立新柱线的记录:
Account 8550475: Artyom Trishkin (MetaQuotes Software Corp.) 10425.23 USD, 1:100, Hedge, Demo account MetaTrader 5 Work only with the current symbol. The number of symbols used: 1 Data of created timeseries: OnInit: 0: EURUSD M1: AmountUsedData=1000, DataTotal=1000, Bars=5140 OnInit: 1: EURUSD M2: AmountUsedData=1000, DataTotal=1000, Bars=4010 OnInit: 2: EURUSD M3: AmountUsedData=1000, DataTotal=1000, Bars=3633 OnInit: 3: EURUSD M4: AmountUsedData=1000, DataTotal=1000, Bars=3445 OnInit: 4: EURUSD M5: AmountUsedData=1000, DataTotal=1000, Bars=3332 OnInit: 5: EURUSD M6: AmountUsedData=1000, DataTotal=1000, Bars=3256 OnInit: 6: EURUSD M10: AmountUsedData=1000, DataTotal=1000, Bars=3106 OnInit: 7: EURUSD M12: AmountUsedData=1000, DataTotal=1000, Bars=3068 OnInit: 8: EURUSD M15: AmountUsedData=1000, DataTotal=1000, Bars=5004 OnInit: 9: EURUSD M20: AmountUsedData=1000, DataTotal=1000, Bars=2993 OnInit: 10: EURUSD M30: AmountUsedData=1000, DataTotal=1000, Bars=5032 OnInit: 11: EURUSD H1: AmountUsedData=1000, DataTotal=1000, Bars=5352 OnInit: 12: EURUSD H2: AmountUsedData=1000, DataTotal=1000, Bars=6225 OnInit: 13: EURUSD H3: AmountUsedData=1000, DataTotal=1000, Bars=6212 OnInit: 14: EURUSD H4: AmountUsedData=1000, DataTotal=1000, Bars=5292 OnInit: 15: EURUSD H6: AmountUsedData=1000, DataTotal=1000, Bars=5182 OnInit: 16: EURUSD H8: AmountUsedData=1000, DataTotal=1000, Bars=5443 OnInit: 17: EURUSD H12: AmountUsedData=1000, DataTotal=1000, Bars=5192 OnInit: 18: EURUSD D1: AmountUsedData=1000, DataTotal=1000, Bars=5080 OnInit: 19: EURUSD W1: AmountUsedData=1000, DataTotal=1000, Bars=2562 OnInit: 20: EURUSD MN1: AmountUsedData=589, DataTotal=589, Bars=589 New bar on EURUSD M1 2020.02.20 21:41 New bar on EURUSD M1 2020.02.20 21:42 New bar on EURUSD M2 2020.02.20 21:42 New bar on EURUSD M3 2020.02.20 21:42 New bar on EURUSD M6 2020.02.20 21:42 New bar on EURUSD M1 2020.02.20 21:43 New bar on EURUSD M1 2020.02.20 21:44 New bar on EURUSD M2 2020.02.20 21:44 New bar on EURUSD M4 2020.02.20 21:44 New bar on EURUSD M1 2020.02.20 21:45 New bar on EURUSD M3 2020.02.20 21:45 New bar on EURUSD M5 2020.02.20 21:45 New bar on EURUSD M15 2020.02.20 21:45
在 M5 上以测试器的可视模式启动 EA:
首先,测试器下载所有时间帧的历史数据,然后 EA 显示所创建时间序列的数据。 然后将消息发送到日志,通知测试过程中,在创建的时间序列上创立新柱线。
创建单个品种时间序列的功能于此阶段,所有操作均按预期进行。
在下一篇文章中,我们将创建通用时间序列集合类,该类存储不同品种和时间帧所需的数据量。
以下附件是函数库当前版本的所有文件,以及测试 EA 文件,供您测试和下载。
请您在评论中留下问题和建议。
返回内容目录
该系列中的先前文章:
DoEasy 函数库中的时间序列(第三十五部分):柱线对象和品种时间序列列表
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...