内容
- 概念
- 改善时间序列类
- "新即时报价"类和数据更新
- 测试
- 下一步是什么?
概念
之前,有关创建任意品种和图表周期时间序列的文章里,我们曾创建过一个完整的时间序列集合类,其中包含程序中用到的所有品种,并为了快速搜索和排序,学习如何运用历史数据填充时间序列。
如此工具将令我们能够按各种组合搜索和比较历史价格数据。 但我们还要考虑刷新当前数据,针对每个所用品种,应在每次新的即时报价来临时完成。
即使是最简单的版本,也能够令我们在程序的 OnTimer() 毫秒处理程序中刷新所有时间序列。 然而,这会引发一个问题,即是否应该始终依据计时器的计数器来精准刷新时间序列数据。 毕竟,新的即时报价到达后,程序中的数据就会变化。 不管新的即时报价到达,简单地更新数据都是错误的 — 就性能而言,这不合理。
虽然我们总是知道,在当前品种图表上的 EA OnTick() 应对程序、或指标的 OnCalculate() 中有新的即时报价到达,但对于程序里用到的其他品种,若没有启动 EA 或指标,则无法跟踪这些品种的价格变化。 此任务需要在 EA 中或在指标中跟踪必要的事件。
在此,满足当前函数库需求的最简单方法是将之前的即时报价时间与当前即时报价时间进行比较。 如果先前的即时报价时间与当前时间不同,则认为程序跟踪的品种有新的即时报价已经到达,但这并非“原生”(该程序是在另一个品种的图表上启动)。
在开发“新即时报价”类,以及为程序中用到的所有时间序列进行实时刷新之前,我们来略微改进一下现有的类。
改善时间序列类
首先, Datas.mqh 文件接收函数库的新消息索引:
//--- CTimeSeries MSG_LIB_TEXT_TS_TEXT_FIRST_SET_SYMBOL, // First, set a symbol using SetSymbol() MSG_LIB_TEXT_TS_TEXT_IS_NOT_USE, // Timeseries is not used. Set the flag using SetAvailable() MSG_LIB_TEXT_TS_TEXT_UNKNOWN_TIMEFRAME, // Unknown timeframe
以及与新添加的索引相对应的消息文本:
{"Сначала нужно установить символ при помощи SetSymbol()","First you need to set Symbol using SetSymbol()"}, {"Таймсерия не используется. Нужно установить флаг использования при помощи SetAvailable()","Timeseries not used. Need to set usage flag using SetAvailable()"}, {"Неизвестный таймфрейм","Unknown timeframe"},
所有函数库对象的基准对象 CBaseObj 类含有两个变量:
//+------------------------------------------------------------------+ //| Base object class for all library objects | //+------------------------------------------------------------------+ class CBaseObj : public CObject { protected: ENUM_LOG_LEVEL m_log_level; // Logging level ENUM_PROGRAM_TYPE m_program; // Program type bool m_first_start; // First launch flag bool m_use_sound; // Flag of playing the sound set for an object bool m_available; // Flag of using a descendant object in the program int m_global_error; // Global error code long m_chart_id_main; // Control program chart ID long m_chart_id; // Chart ID string m_name; // Object name string m_folder_name; // Name of the folder storing CBaseObj descendant objects string m_sound_name; // Object sound file name int m_type; // Object type (corresponds to the collection IDs) public:
m_chart_id_main 变量存储控制程序图表 ID — 这是启动程序的品种图表。 该图表将获取所有在函数库集合和对象中注册的事件。
m_chart_id 存储从 CBaseObj 类派生的对象所关联的图表 ID。 此属性尚未在任何地方使用。 至于时间就要等到以后了。
由于我们在 m_chart_id 之后添加了 m_chart_id_main 变量,故所有消息都将发送到 m_chart_id 变量中设置的图表 ID。 我已经修复了这一点。 现在,所有当前图表 ID 都在 m_chart_id_main 变量中设置。 从函数库向控制程序图表发送消息的所有类均已修改 — 所有 “m_chart_id” 均已替换为 “m_chart_id_main”。
来自 \MQL5\Include\DoEasy\Objects\Events\ 文件夹中的所有事件类,以及 AccountsCollection.mqh,EventsCollection.mqh 和 SymbolsCollection.mqh 集合类,这些文件均已修改。
您可以在附件中查看所有修改。
若要显示来自时间序列集合中指定的柱线条数据,在 \MQL5\Include\DoEasy\Objects\Series\Bar.mqh 里给 CBar 类的柱线参数添加文本描述。
在包含对象属性描述的代码模块中,声明创建柱线参数文本描述的方法:
//+------------------------------------------------------------------+ //| Descriptions of bar object properties | //+------------------------------------------------------------------+ //--- Get description of a bar's (1) integer, (2) real and (3) string properties string GetPropertyDescription(ENUM_BAR_PROP_INTEGER property); string GetPropertyDescription(ENUM_BAR_PROP_DOUBLE property); string GetPropertyDescription(ENUM_BAR_PROP_STRING property); //--- Return the bar type description string BodyTypeDescription(void) const; //--- Send description of bar properties to the journal (full_prop=true - all properties, false - only supported ones) void Print(const bool full_prop=false); //--- Display a short bar description in the journal virtual void PrintShort(void); //--- Return the (1) short name and (2) description of bar object parameters virtual string Header(void); string ParameterDescription(void); //--- }; //+------------------------------------------------------------------+
在类主体之外,实现创建柱线参数文本描述的方法,并修改在日志中显示柱线简要描述的方法实现:
//+------------------------------------------------------------------+ //| Return the description of the bar object parameters | //+------------------------------------------------------------------+ string CBar::ParameterDescription(void) { int dg=(this.m_digits>0 ? this.m_digits : 1); return ( ::TimeToString(this.Time(),TIME_DATE|TIME_MINUTES|TIME_SECONDS)+", "+ "O: "+::DoubleToString(this.Open(),dg)+", "+ "H: "+::DoubleToString(this.High(),dg)+", "+ "L: "+::DoubleToString(this.Low(),dg)+", "+ "C: "+::DoubleToString(this.Close(),dg)+", "+ "V: "+(string)this.VolumeTick()+", "+ (this.VolumeReal()>0 ? "R: "+(string)this.VolumeReal()+", " : "")+ this.BodyTypeDescription() ); } //+------------------------------------------------------------------+ //| Display a short bar description in the journal | //+------------------------------------------------------------------+ void CBar::PrintShort(void) { ::Print(this.Header(),": ",this.ParameterDescription()); } //+------------------------------------------------------------------+
在此,我只简单地从日志显示柱线参数的方法里删除了参数描述代码,并将其放在返回文本消息的新方法之中。 在日志中显示柱线参数时,显示的是由柱线对象简称及其参数组成的复合消息,其文本描述现已改至新方法 ParameterDescription() 中生成。
为了更新“非本名”时间序列(不是程序启动时的时间序列),我们决定创建“新即时报价”类,并仅在 “ New tick” 事件到来时为程序中用到的每个品种刷新数据 。
"新即时报价"类和数据更新
在 \MQL5\Include\DoEasy\Objects\ 里,创建 Ticks\ 文件夹,其内文件 NewTickObj.mqh 包含派生自所有 CBaseObj 函数库基准对象的 CNewTickObj 类(其文件包含在类文件中),并填充必要的数据:
//+------------------------------------------------------------------+ //| NewTickObj.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 tick" class | //+------------------------------------------------------------------+ class CNewTickObj : public CBaseObj { private: MqlTick m_tick; // Structure of the current prices MqlTick m_tick_prev; // Structure of the current prices during the previous check string m_symbol; // Object symbol bool m_new_tick; // New tick flag public: //--- Set a symbol void SetSymbol(const string symbol) { this.m_symbol=symbol; } //--- Return the new tick flag bool IsNewTick(void); //--- Update price data in the tick structure and set the "New tick" event flag if necessary void Refresh(void) { this.m_new_tick=this.IsNewTick(); } //--- Constructors CNewTickObj(void){;} CNewTickObj(const string symbol); }; //+------------------------------------------------------------------+
m_tick 变量存储最后到达的即时报价数据。
m_tick_prev 变量存储前一次即时报价的数据。
m_symbol 变量存储需要跟踪其新即时报价的品种。
新即时报价标志保存在 m_new_tick 变量,稍后会用到。
对于当前的函数库需求,品种产生的 “New tick” 事件由 IsNewTick() 方法来定义:
//+------------------------------------------------------------------+ //| Return the new tick flag | //+------------------------------------------------------------------+ bool CNewTickObj::IsNewTick(void) { //--- If failed to get the current prices to the tick structure, return 'false' if(!::SymbolInfoTick(this.m_symbol,this.m_tick)) return false; //--- If this is the first launch, copy data of the obtained tick to the previous tick data //--- reset the first launch flag and return 'false' if(this.m_first_start) { this.m_tick_prev=this.m_tick; this.m_first_start=false; return false; } //--- If the time of a new tick is not equal to the time of a tick during the previous check - //--- copy data of the obtained tick to the previous tick data and return 'true' if(this.m_tick.time_msc!=this.m_tick_prev.time_msc) { this.m_tick_prev=this.m_tick; return true; } //--- In all other cases, return 'false' return false; } //+------------------------------------------------------------------+
该类含有两个已定义的构造函数:
- 默认没有参数的构造函数可在另一个类中定义“新即时报价”对象。 在这种情况下,利用 SetSymbol() 类方法为 CNewTickObj 类对象设置品种,并为其定义“新即时报价”事件。
- 参数型构造函数通过 new 运算符创建类对象。 在这种情况下,在创建对象时可立即为所创建对象指定品种。
//+------------------------------------------------------------------+ //| Parametric constructor CNewTickObj | //+------------------------------------------------------------------+ CNewTickObj::CNewTickObj(const string symbol) : m_symbol(symbol) { //--- Reset the structures of the new and previous ticks ::ZeroMemory(this.m_tick); ::ZeroMemory(this.m_tick_prev); //--- If managed to get the current prices to the tick structure, //--- copy data of the obtained tick to the previous tick data and reset the first launch flag if(::SymbolInfoTick(this.m_symbol,this.m_tick)) { this.m_tick_prev=this.m_tick; this.m_first_start=false; } } //+------------------------------------------------------------------+
这是新即时报价对象的整个类。 该思路很简单:在即时报价结构里设置价格,比较新到达即时报价与前一个的时间。
如果这些时间不相等,则新的即时报价已到来。
在 EA 中可能会忽略即时报价,但在此这并不重要。 我们能够在计时器中的“非本名”品种上跟踪新的即时报价,从而仅在新即时报价到达时才刷新数据,而不是由计时器不断地进行更新。
如果一个指标可以跟踪所有批量到达的即时报价,则应在 OnCalculate() 应对程序中完成指标所在品种当前时间序列数据的刷新。 在计时器中跟踪“非本名”品种的新即时报价(在 OnOnCalculate() 中无法接收“非本名”品种的新即时报价事件),因此,仅跟踪“非本名”品种的新旧即时报价之间的时间差就足够了,从而能够及时刷新时间序列数据。
令 CSeries 时间序列对象能将其 “New bar” 事件发送到控制程序。 这会令我们能够从程序中的任何时间序列里获取此类事件,并对其进行响应。
在 Defines.mqh 清单文件的末尾,添加新的枚举,以及可能的时间序列对象事件的列表:
//+------------------------------------------------------------------+ //| List of possible timeseries events | //+------------------------------------------------------------------+ enum ENUM_SERIES_EVENT { SERIES_EVENTS_NO_EVENT = SYMBOL_EVENTS_NEXT_CODE, // no event SERIES_EVENTS_NEW_BAR, // "New bar" event }; #define SERIES_EVENTS_NEXT_CODE (SERIES_EVENTS_NEW_BAR+1) // Code of the next event after the "New bar" event //+------------------------------------------------------------------+
在此,我们只有两个时间序列事件状态:“无事件”和“新柱线”事件。 我们需要这些枚举常量,从而能够按指定属性搜索柱线集合列表(CSeries 时间序列)中的柱线对象。
鉴于时间序列对象是在函数库计时器里刷新的,因此将刷新时间序列对象集合的定时器参数,与时间序列集合列表 ID 一起添加到 Defines.mqh 清单文件中:
//--- Trading class timer parameters #define COLLECTION_REQ_PAUSE (300) // Trading class timer pause in milliseconds #define COLLECTION_REQ_COUNTER_STEP (16) // Trading class timer counter increment #define COLLECTION_REQ_COUNTER_ID (5) // Trading class timer counter ID //--- Parameters of the timeseries collection timer #define COLLECTION_TS_PAUSE (32) // Timeseries collection timer pause in milliseconds #define COLLECTION_TS_COUNTER_STEP (16) // Account timer counter increment #define COLLECTION_TS_COUNTER_ID (6) // Timeseries timer counter ID //--- Collection list IDs #define COLLECTION_HISTORY_ID (0x777A) // Historical collection list ID #define COLLECTION_MARKET_ID (0x777B) // Market collection list ID #define COLLECTION_EVENTS_ID (0x777C) // Event collection list ID #define COLLECTION_ACCOUNT_ID (0x777D) // Account collection list ID #define COLLECTION_SYMBOLS_ID (0x777E) // Symbol collection list ID #define COLLECTION_SERIES_ID (0x777F) // Timeseries collection list ID //--- Data parameters for file operations
在创建 CEngine 函数库基准对象时,我们已研究过集合计时器参数,而在重新编排函数库结构时,已讲述过集合 ID 的作用。
因为时间序列对象是一个列表,其内包含指向属于该列表的柱线对象指针,故应立即将时间序列集合 ID 分配给柱线对象。
再次打开 \MQL5\Include\DoEasy\Objects\Series\Bar.mqh,然后将对象类型添加到两个构造函数当中:
//+------------------------------------------------------------------+ //| Constructor 1 | //+------------------------------------------------------------------+ CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { this.m_type=COLLECTION_SERIES_ID; MqlRates rates_array[1]; this.SetSymbolPeriod(symbol,timeframe,index); ::ResetLastError(); //--- If failed to write bar data to the MqlRates array by index or set the time to the time structure, //--- display an error message, create and fill the structure with zeros, and write it to the rates_array array if(::CopyRates(symbol,timeframe,index,1,rates_array)<1 || !::TimeToStruct(rates_array[0].time,this.m_dt_struct)) { int err_code=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_BAR_DATA),". ",CMessage::Text(MSG_LIB_SYS_ERROR)," ",CMessage::Text(err_code)," ",CMessage::Retcode(err_code)); MqlRates err={0}; rates_array[0]=err; } //--- Set the bar properties this.SetProperties(rates_array[0]); } //+------------------------------------------------------------------+ //| Constructor 2 | //+------------------------------------------------------------------+ CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const MqlRates &rates) { this.m_type=COLLECTION_SERIES_ID; this.SetSymbolPeriod(symbol,timeframe,index); ::ResetLastError(); //--- If failed to set time to the time structure, display the error message, //--- create and fill the structure with zeros, set the bar properties from this structure and exit if(!::TimeToStruct(rates.time,this.m_dt_struct)) { int err_code=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_BAR_DATA),". ",CMessage::Text(MSG_LIB_SYS_ERROR)," ",CMessage::Text(err_code)," ",CMessage::Retcode(err_code)); MqlRates err={0}; this.SetProperties(err); return; } //--- Set the bar properties this.SetProperties(rates); } //+------------------------------------------------------------------+
现在,我们来改进 \MQL5\Include\DoEasy\Objects\Series\Series.mqh 里的 CSeries 时间序列对象类。
在类的公开部分中,声明将事件发送到控制程序图表的新方法:
//--- (1) Create and (2) update the timeseries list int Create(const uint required=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); //--- Create and send the "New bar" event to the control program chart void SendEvent(void); //--- Return the timeseries name string Header(void); //--- Display (1) the timeseries description and (2) the brief timeseries description in the journal void Print(void); void PrintShort(void); //--- Constructors CSeries(void); CSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0); }; //+------------------------------------------------------------------+
在类清单的末尾,实现声明的方法:
//+------------------------------------------------------------------+ //| Create and send the "New bar" event | //| to the control program chart | //+------------------------------------------------------------------+ void CSeries::SendEvent(void) { ::EventChartCustom(this.m_chart_id_main,SERIES_EVENTS_NEW_BAR,this.Time(0),this.Timeframe(),this.Symbol()); } //+------------------------------------------------------------------+
在此,我们创建并将事件发送到控制程序图表。 该事件包括:
事件接收者的图表 ID,
事件 ID (新柱线),
发送新柱线的创立时间,作为一个 long 型的事件参数,
将发生事件图表的时间帧作为 double 型事件参数发送,且
发送品种名称(发生事件的时间序列)作为 string 型参数。
在时间序列数据同步方法中添加校验代表程序中使用时间序列的标志:
//+------------------------------------------------------------------+ //|Synchronize symbol and timeframe data with server data | //+------------------------------------------------------------------+ bool CSeries::SyncData(const uint required,const uint rates_total) { //--- If the timeseries is not used, notify of that and exit if(!this.m_available) { ::Print(DFUN,this.m_symbol," ",TimeframeDescription(this.m_timeframe),": ",CMessage::Text(MSG_LIB_TEXT_TS_TEXT_IS_NOT_USE)); return false; } //--- If managed to obtain the available number of bars in the timeseries
换句话说,如果未设置程序中时间序列使用情况的标志,则无需对其进行同步。 在未设置使用标志的情况下,当我们需要时间序列时,也可能会出现这种情况。 因此,发送相应的消息到日志。
在时间序列创建方法里实现相同的检查:
//+------------------------------------------------------------------+ //| Create the timeseries list | //+------------------------------------------------------------------+ int CSeries::Create(const uint required=0) { //--- If the timeseries is not used, notify of that and return zero if(!this.m_available) { ::Print(DFUN,this.m_symbol," ",TimeframeDescription(this.m_timeframe),": ",CMessage::Text(MSG_LIB_TEXT_TS_TEXT_IS_NOT_USE)); return 0; } //--- If the required history depth is not set for the list yet,
该类中修改了按时间序列索引返回柱线对象的方法。 以前,该方法如下所示:
//+------------------------------------------------------------------+ //| Return the bar object by index in the timeseries | //+------------------------------------------------------------------+ CBar *CSeries::GetBarBySeriesIndex(const uint index) { CArrayObj *list=this.GetList(BAR_PROP_INDEX,index); return(list==NULL || list.Total()==0 ? NULL : list.At(0)); } //+------------------------------------------------------------------+
换句话说,已创建含有必要柱线副本的新列表,且该副本已返回。 如果我们只想接收所请求的柱线数据,这就足够了,但是如果我们需要更改柱线属性,则此方法不起作用,因为所有修改只针对柱线副本的属性,而非原始对象的属性。
由于我们希望当前柱线在新的即时报价达时实时刷新,故此我修改方法,返回柱线集合列表中原始柱线对象的位置指针,而非列表副本中的柱线对象:
//+------------------------------------------------------------------+ //| Return the bar object by index in the timeseries | //+------------------------------------------------------------------+ CBar *CSeries::GetBarBySeriesIndex(const uint index) { CBar *tmp=new CBar(this.m_symbol,this.m_timeframe,index); if(tmp==NULL) return NULL; this.m_list_series.Sort(SORT_BY_BAR_INDEX); int idx=this.m_list_series.Search(tmp); delete tmp; CBar *bar=this.m_list_series.At(idx); return(bar!=NULL ? bar : NULL); } //+------------------------------------------------------------------+
在此,我们利用当前时间序列对象图表的品种和周期,和传递给方法的柱线索引创建临时柱线对象。 在已按柱线索引排序的时间序列列表中搜索同一对象,图表时间序列中的柱线索引是必不可少的。 搜索相同时间序列索引的柱线时,我们获取其在列表中的索引(该索引用于获取指向列表中柱线对象的指针),并将指针返回。
现在,该方法返回时间序列列表中原始柱线对象的指针。 可以在实时数据刷新期间进行更改。
现在改进了 CTimeSeries 时间序列对象类,可在定义此类事件时跟踪新的即时报价并刷新数据。 类对象是单个品种所有用到的图表周期的时间序列集合。 这意味着该对象是“新即时报价”类对象的最佳位置,因为按照 CTimeSeries 时间序列对象品种获取新的即时报价会启动该对象所有周期的 CSeries 时间序列对象进行数据刷新。
将“新即时报价”对象类文件包含到时间序列对象类文件之中。 在类的私密部分,定义“新即时报价”类对象。
在类的公开部分中,添加返回当前时间序列对象品种上新即时报价标志的方法:
//+------------------------------------------------------------------+ //| 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" #include "..\Ticks\NewTickObj.mqh" //+------------------------------------------------------------------+ //| Symbol timeseries class | //+------------------------------------------------------------------+ class CTimeSeries : public CBaseObj { private: string m_symbol; // Timeseries symbol CNewTickObj m_new_tick; // "New tick" object CArrayObj m_list_series; // List of timeseries by timeframes datetime m_server_firstdate; // The very first date in history by a server symbol datetime m_terminal_firstdate; // The very first date in history by a symbol in the client terminal //--- Return (1) the timeframe index in the list and (2) the timeframe by the list index char IndexTimeframe(const ENUM_TIMEFRAMES timeframe) const { return IndexEnumTimeframe(timeframe)-1; } ENUM_TIMEFRAMES TimeframeByIndex(const uchar index) const { return TimeframeByEnumIndex(uchar(index+1)); } //--- Set the very first date in history by symbol on the server and in the client terminal void SetTerminalServerDate(void) { this.m_server_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,::Period(),SERIES_SERVER_FIRSTDATE); this.m_terminal_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,::Period(),SERIES_TERMINAL_FIRSTDATE); } public: //--- Return (1) oneself, (2) the full list of timeseries, (3) specified timeseries object and (4) timeseries object by index CTimeSeries *GetObject(void) { return &this; } CArrayObj *GetListSeries(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 SetRequiredUsedData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0); bool SetRequiredAllUsedData(const uint required=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 required=0,const int rates_total=0); bool SyncAllData(const uint required=0,const int rates_total=0); //--- Return the very first date in history by symbol (1) on the server, (2) in the client terminal and (3) the new tick flag datetime ServerFirstDate(void) const { return this.m_server_firstdate; } datetime TerminalFirstDate(void) const { return this.m_terminal_firstdate; } bool IsNewTick(void) { return this.m_new_tick.IsNewTick(); } //--- Create (1) the specified timeseries list and (2) all timeseries lists bool Create(const ENUM_TIMEFRAMES timeframe,const uint required=0); bool CreateAll(const uint required=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); //--- Compare CTimeSeries objects (by symbol) virtual int Compare(const CObject *node,const int mode=0) const; //--- Display (1) description and (2) short symbol timeseries description in the journal void Print(const bool created=true); void PrintShort(const bool created=true); //--- Constructors CTimeSeries(void){;} CTimeSeries(const string symbol); }; //+------------------------------------------------------------------+
IsNewTick() 方法返回来自 m_new_tick “新即时报价”对象的新即时报价请求数据的结果。
为了让“新即时报价”类对象知道需返回数据的品种,我们应在类构造函数中为“新即时报价”类对象设置品种,并立即刷新数据读取当前报价:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTimeSeries::CTimeSeries(const string symbol) : m_symbol(symbol) { this.m_list_series.Clear(); this.m_list_series.Sort(); for(int i=0;i<21;i++) { ENUM_TIMEFRAMES timeframe=this.TimeframeByIndex((uchar)i); CSeries *series_obj=new CSeries(this.m_symbol,timeframe); this.m_list_series.Add(series_obj); } this.SetTerminalServerDate(); this.m_new_tick.SetSymbol(this.m_symbol); this.m_new_tick.Refresh(); } //+------------------------------------------------------------------+
我们现在去检查返回数据同步标志的方法中时间序列的使用标志。 如果该标志未选中,则程序里将不会用到该时间序列,且不会处理它:
//+------------------------------------------------------------------+ //| Return the flag of data synchronization | //| with the server data | //+------------------------------------------------------------------+ bool CTimeSeries::SyncData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0) { if(this.m_symbol==NULL) { ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_TEXT_FIRST_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; } if(!series_obj.IsAvailable()) return false; return series_obj.SyncData(required,rates_total); } //+------------------------------------------------------------------+ //| Return the flag of data synchronization | //| of all timeseries with the server data | //+------------------------------------------------------------------+ bool CTimeSeries::SyncAllData(const uint required=0,const int rates_total=0) { if(this.m_symbol==NULL) { ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_TEXT_FIRST_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 || !series_obj.IsAvailable()) continue; res &=series_obj.SyncData(required,rates_total); } return res; } //+------------------------------------------------------------------+
在时间序列创建方法中强制设置时间序列使用标志:
//+------------------------------------------------------------------+ //| Create a specified timeseries list | //+------------------------------------------------------------------+ bool CTimeSeries::Create(const ENUM_TIMEFRAMES timeframe,const uint required=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.RequiredUsedData()==0) { ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_TEXT_FIRS_SET_AMOUNT_DATA)); return false; } series_obj.SetAvailable(true); return(series_obj.Create(required)>0); } //+------------------------------------------------------------------+ //| Create all timeseries lists | //+------------------------------------------------------------------+ bool CTimeSeries::CreateAll(const uint required=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.RequiredUsedData()==0) continue; series_obj.SetAvailable(true); res &=(series_obj.Create(required)>0); } return res; } //+------------------------------------------------------------------+
在时间序列刷新方法中添加代码(如果在其中检测到“新柱线”事件),利用上面研究的 CSeries 时间序列对象的 SendEvent() 方法向控制程序图表发送有关该事件的消息:
//+------------------------------------------------------------------+ //| 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); if(series_obj.IsNewBar(time)) { series_obj.SendEvent(); this.SetTerminalServerDate(); } } //+------------------------------------------------------------------+ //| 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) { bool upd=false; 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); if(series_obj.IsNewBar(time)) { series_obj.SendEvent(); upd &=true; } } if(upd) this.SetTerminalServerDate(); } //+------------------------------------------------------------------+
我们来改进 \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh 中的 CTimeSeriesCollection 时间序列集合类。
将时间序列集合列表类型设置为 CLassObj 类类型。
为此,包含 CListObj 类文件,然后将集合列表类型从 CArrayObj 更改为 CListObj :
//+------------------------------------------------------------------+ //| TimeSeriesCollection.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 "ListObj.mqh" #include "..\Objects\Series\TimeSeries.mqh" #include "..\Objects\Symbols\Symbol.mqh" //+------------------------------------------------------------------+ //| Symbol timeseries collection | //+------------------------------------------------------------------+ class CTimeSeriesCollection : public CObject { private: CListObj m_list; // List of applied symbol timeseries //--- Return the timeseries index by symbol name int IndexTimeSeries(const string symbol); public:
在该类的公开部分,声明按图表时间序列索引返回指定时间序列柱线的方法,返回指定时间序列创建新柱线标志的方法,和刷新不属于当前品种的时间序列方法:
//--- Return the flag of data synchronization with the server data of the (1) specified timeseries of the specified symbol, //--- (2) the specified timeseries of all symbols, (3) all timeseries of the specified symbol and (4) all timeseries of all symbols bool SyncData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0); bool SyncData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0); bool SyncData(const string symbol,const uint required=0,const int rates_total=0); bool SyncData(const uint required=0,const int rates_total=0); //--- Return the bar of the specified timeseries of the specified symbol of the specified position CBar *GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true); //--- Return the flag of opening a new bar of the specified timeseries of the specified symbol bool IsNewBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time=0); //--- Create (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols, //--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols void RefreshOther(void); //--- Display (1) the complete and (2) short collection description in the journal void Print(const bool created=true); void PrintShort(const bool created=true); //--- Constructor CTimeSeriesCollection(); }; //+------------------------------------------------------------------+
在类构造函数中,为时间序列对象列表设置时间序列集合 ID:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTimeSeriesCollection::CTimeSeriesCollection() { this.m_list.Clear(); this.m_list.Sort(); this.m_list.Type(COLLECTION_SERIES_ID); } //+------------------------------------------------------------------+
实现按时间序列索引和新柱线事件从指定时间序列列表里返回柱线对象的方法:
//+-----------------------------------------------------------------------+ //| Return the bar of the specified timeseries | //| of the specified symbol of the specified position | //| from_series=true - by the timeseries index, false - by the list index | //+-----------------------------------------------------------------------+ CBar *CTimeSeriesCollection::GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true) { //--- Get the timeseries object index in the timeseries collection list by a symbol name int idx=this.IndexTimeSeries(symbol); if(idx==WRONG_VALUE) return NULL; //--- Get the pointer to the timeseries object from the collection list of timeseries objects by the obtained index CTimeSeries *timeseries=this.m_list.At(idx); if(timeseries==NULL) return NULL; //--- Get the specified timeseries from the symbol timeseries object by the specified timeframe CSeries *series=timeseries.GetSeries(timeframe); if(series==NULL) return NULL; //--- Depending on the from_series flag, return the pointer to the bar //--- either by the chart timeseries index or by the bar index in the timeseries list return(from_series ? series.GetBarBySeriesIndex(index) : series.GetBarByListIndex(index)); } //+------------------------------------------------------------------+ //| Return new bar opening flag | //| for a specified timeseries of a specified symbol | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::IsNewBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time=0) { //--- Get the timeseries object index in the timeseries collection list by a symbol name int index=this.IndexTimeSeries(symbol); if(index==WRONG_VALUE) return false; //--- Get the pointer to the timeseries object from the collection list of timeseries objects by the obtained index CTimeSeries *timeseries=this.m_list.At(index); if(timeseries==NULL) return false; //--- Get the specified timeseries from the symbol timeseries object by the specified timeframe CSeries *series=timeseries.GetSeries(timeframe); if(series==NULL) return false; //--- Return the result of checking the new bar of the specified timeseries return series.IsNewBar(time); } //+------------------------------------------------------------------+
实现更新除当前品种以外的所有时间序列的方法:
//+------------------------------------------------------------------+ //| Update all timeseries except the current symbol | //+------------------------------------------------------------------+ void CTimeSeriesCollection::RefreshOther(void) { int total=this.m_list.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; if(timeseries.Symbol()==::Symbol() || !timeseries.IsNewTick()) continue; timeseries.RefreshAll(); } } //+------------------------------------------------------------------+
循环遍历所有时间序列对象列表,获取下一个时间序列对象。 如果对象品种等于程序挂载的图表品种,则略过该时间序列对象。
此方法以及下面所述的时间序列刷新方法,拥有检查新即时报价标记的功能。 如果没有新的即时报价,则跳过该时间序列,且其数据不会更新:
//+------------------------------------------------------------------+ //| Update the specified timeseries of the specified symbol | //+------------------------------------------------------------------+ void CTimeSeriesCollection::Refresh(const string symbol,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) { int index=this.IndexTimeSeries(symbol); if(index==WRONG_VALUE) return; CTimeSeries *timeseries=this.m_list.At(index); if(timeseries==NULL) return; if(!timeseries.IsNewTick()) return; timeseries.Refresh(timeframe,time,open,high,low,close,tick_volume,volume,spread); } //+------------------------------------------------------------------+ //| Update the specified timeseries of all symbols | //+------------------------------------------------------------------+ void CTimeSeriesCollection::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) { int total=this.m_list.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; if(!timeseries.IsNewTick()) continue; timeseries.Refresh(timeframe,time,open,high,low,close,tick_volume,volume,spread); } } //+------------------------------------------------------------------+ //| Update all timeseries of the specified symbol | //+------------------------------------------------------------------+ void CTimeSeriesCollection::Refresh(const string symbol, 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) { int index=this.IndexTimeSeries(symbol); if(index==WRONG_VALUE) return; CTimeSeries *timeseries=this.m_list.At(index); if(timeseries==NULL) return; if(!timeseries.IsNewTick()) return; timeseries.RefreshAll(time,open,high,low,close,tick_volume,volume,spread); } //+------------------------------------------------------------------+ //| Update all timeseries of all symbols | //+------------------------------------------------------------------+ void CTimeSeriesCollection::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) { int total=this.m_list.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; if(!timeseries.IsNewTick()) continue; timeseries.RefreshAll(time,open,high,low,close,tick_volume,volume,spread); } } //+------------------------------------------------------------------+
最后一步是针对 CEngine 函数库主对象类的文件进行必要的改进。
打开位于 \MQL5\Include\DoEasy\Engine.mqh 里的类文件。
在类的私密部分,基于函数库声明存储程序类型的变量:
//+------------------------------------------------------------------+ //| Library basis class | //+------------------------------------------------------------------+ class CEngine { private: CHistoryCollection m_history; // Collection of historical orders and deals CMarketCollection m_market; // Collection of market orders and deals CEventsCollection m_events; // Event collection CAccountsCollection m_accounts; // Account collection CSymbolsCollection m_symbols; // Symbol collection CTimeSeriesCollection m_series; // Timeseries collection CResourceCollection m_resource; // Resource list CTradingControl m_trading; // Trading management object CArrayObj m_list_counters; // List of timer counters int m_global_error; // Global error code bool m_first_start; // First launch flag bool m_is_hedge; // Hedge account flag bool m_is_tester; // Flag of working in the tester bool m_is_market_trade_event; // Account trading event flag bool m_is_history_trade_event; // Account history trading event flag bool m_is_account_event; // Account change event flag bool m_is_symbol_event; // Symbol change event flag ENUM_TRADE_EVENT m_last_trade_event; // Last account trading event int m_last_account_event; // Last event in the account properties int m_last_symbol_event; // Last event in the symbol properties ENUM_PROGRAM_TYPE m_program; // Program type
在该类的公开部分,声明处理 NewTick EA 事件的方法:
//--- (1) NewTick event timer and (2) handler void OnTimer(void); void OnTick(void);
在同一公开部分中,声明按图表时间序列索引返回指定品种指定时间序列的柱线对象的方法,和返回指定品种指定时间序列创立新柱线标志的方法:
//--- Create (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols, //--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols bool SeriesCreate(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0) { return this.m_series.CreateSeries(symbol,timeframe,required); } bool SeriesCreate(const ENUM_TIMEFRAMES timeframe,const uint required=0) { return this.m_series.CreateSeries(timeframe,required); } bool SeriesCreate(const string symbol,const uint required=0) { return this.m_series.CreateSeries(symbol,required); } bool SeriesCreate(const uint required=0) { return this.m_series.CreateSeries(required); } //--- Return the bar of the specified timeseries of the specified symbol of the specified position CBar *SeriesGetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true) { return this.m_series.GetBar(symbol,timeframe,index,from_series); } //--- Return the flag of opening a new bar of the specified timeseries of the specified symbol bool SeriesIsNewBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time=0) { return this.m_series.IsNewBar(symbol,timeframe,time); } //--- Update (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols, //--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols
在同一个类中,声明依据指定品种、时间序列和其在图表时间序列的位置(柱线索引)返回标准柱线属性的方法:
//--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) Time, (6) TickVolume, //--- (7) RealVolume, (8) Spread of the specified bar of the specified symbol of the specified timeframe double SeriesOpen(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); double SeriesHigh(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); double SeriesLow(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); double SeriesClose(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); datetime SeriesTime(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); long SeriesTickVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); long SeriesRealVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); int SeriesSpread(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); //--- Set the following for the trading classes: //--- (1) correct filling policy, (2) filling policy, //--- (3) correct order expiration type, (4) order expiration type, //--- (5) magic number, (6) comment, (7) slippage, (8) volume, (9) order expiration date, //--- (10) the flag of asynchronous sending of a trading request, (11) logging level, (12) number of trading attempts
在类构造函数中,设置正在运行的程序类型,并为时间序列集合计时器创建计数器:
//+------------------------------------------------------------------+ //| 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_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); ::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 //--- } //+------------------------------------------------------------------+
在该函数库的 OnTimer() 应对程序中,添加操控时间序列集合计时器的功能(已删除过多的代码):
//+------------------------------------------------------------------+ //| CEngine timer | //+------------------------------------------------------------------+ void CEngine::OnTimer(void) { //--- Timer of the collections of historical orders and deals, as well as of market orders and positions //... //--- Account collection timer //... //--- Timer 1 of the symbol collection (updating symbol quote data in the collection) //... //--- 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) //... //--- Trading class timer //... //--- Timeseries collection timer index=this.CounterIndex(COLLECTION_TS_COUNTER_ID); if(index>WRONG_VALUE) { CTimerCounter* counter=this.m_list_counters.At(index); if(counter!=NULL) { //--- If this is not a tester if(!this.IsTester()) { //--- If the pause is over, work with the timeseries list (except for the current symbol timeseries) if(counter.IsTimeDone()) this.m_series.RefreshOther(); } //--- In case of the tester, work with the timeseries list by tick (except for the current symbol timeseries) else this.m_series.RefreshOther(); } } } //+------------------------------------------------------------------+
在创建 CEngine 函数库主对象时已考虑到操控与集合计时器计数器及其自身的交互。 其他所有内容在代码注释中均有所讲述。
请记住,计时器仅处理品种与程序挂载图表品种不匹配的时间序列。
在计时器中,当为“非本名”品种注册“新即时报价”事件时,我们将刷新时间序列。 所以,这些就是我们在计时器中要检测的事件。
从 EA 的 OnTick() 应对程序中启动的 OnTick() 方法刷新当前品种时间序列:
//+------------------------------------------------------------------+ //| NewTick event handler | //+------------------------------------------------------------------+ void CEngine::OnTick(void) { //--- If this is not a EA, exit if(this.m_program!=PROGRAM_EXPERT) return; //--- Update the current symbol timeseries this.SeriesRefresh(NULL,PERIOD_CURRENT); } //+------------------------------------------------------------------+
接收指定时间序列指定柱线的主要属性的方法实现:
//+------------------------------------------------------------------+ //| Return the specified bar's Open | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ double CEngine::SeriesOpen(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { CBar *bar=this.m_series.GetBar(symbol,timeframe,index); return(bar!=NULL ? bar.Open() : 0); } //+------------------------------------------------------------------+ //| Return the specified bar's High | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ double CEngine::SeriesHigh(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { CBar *bar=this.m_series.GetBar(symbol,timeframe,index); return(bar!=NULL ? bar.High() : 0); } //+------------------------------------------------------------------+ //| Return the specified bar's Low | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ double CEngine::SeriesLow(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { CBar *bar=this.m_series.GetBar(symbol,timeframe,index); return(bar!=NULL ? bar.Low() : 0); } //+------------------------------------------------------------------+ //| Return the specified bar's Close | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ double CEngine::SeriesClose(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { CBar *bar=this.m_series.GetBar(symbol,timeframe,index); return(bar!=NULL ? bar.Close() : 0); } //+------------------------------------------------------------------+ //| Return the specified bar's Time | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ datetime CEngine::SeriesTime(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { CBar *bar=this.m_series.GetBar(symbol,timeframe,index); return(bar!=NULL ? bar.Time() : 0); } //+------------------------------------------------------------------+ //| Return the specified bar's TickVolume | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ long CEngine::SeriesTickVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { CBar *bar=this.m_series.GetBar(symbol,timeframe,index); return(bar!=NULL ? bar.VolumeTick() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return the specified bar's RealVolume | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ long CEngine::SeriesRealVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { CBar *bar=this.m_series.GetBar(symbol,timeframe,index); return(bar!=NULL ? bar.VolumeReal() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return the specified bar's Spread | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ int CEngine::SeriesSpread(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { CBar *bar=this.m_series.GetBar(symbol,timeframe,index); return(bar!=NULL ? bar.Spread() : INT_MIN); } //+------------------------------------------------------------------+
这里的一切都很简单:按时间序列品种和时间帧从图表时间序列的指定索引接收柱线对象 (0-当前柱线),然后返回相应的柱线属性。
这些都是当前需要的所有改进,为程序中用到的时间序列创建自动刷新价格数据,发送事件至控制程序图表,并在程序中从所创建时间序列里接收数据。
测试
我们按如下方式执行测试:
为三个品种创建当前时间帧的三个时间序列,从时间序列集合对象(CTimeSeriesCollection)获取零号索引的柱线对象(CBar 类),并利用返回柱线对象简称+说明的方法在图表注释中显示柱线数据。 第二行注释将包含零号柱线的相似格式数据。 在此情况下,利用 CEngine 函数库主对象的方法生成数据,该方法返回指定时间帧指定品种指定柱线的数据。
数据将在测试器中实时更新,并在启动 EA 的图表上更新。
我们还将实现从 CSeries 类对象接收事件的处理,发送“新柱线”事件至控制程序图表,并观察在品种图表上启动的程序如何接收这些事件。
为了执行测试,我们将利用上一篇文章中的 EA,将其保存在 \MQL5\Experts\TestDoEasy\Part38\ 之下,命名为 TestDoEasyPart38.mq5 。
按以下方式检查 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 } //--- Handle the NewTick event in the library engine.OnTick(); //--- If the trailing flag is set if(trailing_on) { TrailingPositions(); // Trailing positions TrailingOrders(); // Trailing of pending orders } //--- Bet the zero bar of the current timeseries CBar *bar=engine.SeriesGetBar(NULL,PERIOD_CURRENT,0); if(bar==NULL) return; //--- Create a string of parameters of the current bar similar to the one //--- displayed by the bar object description: //--- bar.Header()+": "+bar.ParameterDescription() string parameters= (TextByLanguage("Бар \"","Bar \"")+Symbol()+"\" "+TimeframeDescription((ENUM_TIMEFRAMES)Period())+"[0]: "+TimeToString(bar.Time(),TIME_DATE|TIME_MINUTES|TIME_SECONDS)+ ", O: "+DoubleToString(engine.SeriesOpen(NULL,PERIOD_CURRENT,0),Digits())+ ", H: "+DoubleToString(engine.SeriesHigh(NULL,PERIOD_CURRENT,0),Digits())+ ", L: "+DoubleToString(engine.SeriesLow(NULL,PERIOD_CURRENT,0),Digits())+ ", C: "+DoubleToString(engine.SeriesClose(NULL,PERIOD_CURRENT,0),Digits())+ ", V: "+(string)engine.SeriesTickVolume(NULL,PERIOD_CURRENT,0)+ ", Real: "+(string)engine.SeriesRealVolume(NULL,PERIOD_CURRENT,0)+ ", Spread: "+(string)engine.SeriesSpread(NULL,PERIOD_CURRENT,0) ); //--- Display the data received from the bar object in the first line of the chart comment, //--- while the second line contains the methods of receiving timeseries price data Comment(bar.Header(),": ",bar.ParameterDescription(),"\n",parameters); } //+------------------------------------------------------------------+
这一切都很简单:代码块是操控 DoEasy 函数库时的标准模板。 当前的实现在每次即时报价上调用函数库的 NewTick 事件应对程序(当前,它为所创建时间序列执行刷新)。 跳过所有缺失的时间序列(已声明但未由 Create() 方法创建的时间序列)(未经函数库更新)。 将来,将需要从 EA 的 OnTick() 应对程序中调用此方法来刷新当前时间序列数据。
接下来,我们从当前品种和周期时间序列中接收柱线对象,创建一个含有所获柱线数据描述的字符串,并在注释中显示两行:
利用柱线对象方法显示第一行,
第二行包含由函数库主对象方法获取并返回的数据。
OnInitDoEasy() 函数库初始化函数代码块稍微进行了修改,可为所有用到的品种创建时间序列:
//--- Implement displaying the list of used timeframes only for MQL5 - MQL4 has no ArrayPrint() function #ifdef __MQL5__ if(InpModeUsedTFs!=TIMEFRAMES_MODE_CURRENT) ArrayPrint(array_used_periods); #endif //--- Create timeseries of all used symbols CArrayObj *list_timeseries=engine.GetListTimeSeries(); if(list_timeseries!=NULL) { int total=list_timeseries.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=list_timeseries.At(i); int total_periods=ArraySize(array_used_periods); for(int j=0;j<total_periods;j++) { ENUM_TIMEFRAMES timeframe=TimeframeByDescription(array_used_periods[j]); timeseries.SyncData(timeframe); timeseries.Create(timeframe); } } } //--- Check created timeseries - display descriptions of all created timeseries in the journal //--- (true - only created ones, false - created and declared ones) engine.GetTimeSeriesCollection().PrintShort(true); // Short descriptions //engine.GetTimeSeriesCollection().Print(true); // Full descriptions
在这里,我们获取所有时间序列的列表,并循环遍历时间序列列表,按循环索引获取下一个时间序列对象。 然后按所用时间帧数量循环,在同步时间序列和历史数据后创建所需时间序列列表。
在应对函数库事件的 OnDoEasyEvent() 函数中,添加处理时间序列事件的代码块(冗余代码已被删除):
//+------------------------------------------------------------------+ //| Handling DoEasy library events | //+------------------------------------------------------------------+ void OnDoEasyEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { int idx=id-CHARTEVENT_CUSTOM; //--- Retrieve (1) event time milliseconds, (2) reason and (3) source from lparam, as well as (4) set the exact event time ushort msc=engine.EventMSC(lparam); ushort reason=engine.EventReason(lparam); ushort source=engine.EventSource(lparam); long time=TimeCurrent()*1000+msc; //--- Handling symbol events //... //--- Handling account events //... //--- Handling market watch window events //... //--- Handling timeseries events else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE) { //--- "New bar" event if(idx==SERIES_EVENTS_NEW_BAR) { Print(TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam)); } } //--- Handling trading events //... } //+------------------------------------------------------------------+
此处,如果获取的事件 ID 存在于时间序列事件 ID 列表内,且如果这是“新柱线”事件,则在终端日志里显示有关消息。
编译 EA,并按以下方式设置其参数:
- 设置 Mode of used symbols list 以便使用指定的品种列表,
- 在 List of used symbols (comma - separator), 只留下三个品种,其中之一是 EURUSD 和
- 在 Mode of used timeframes list, 选择仅使用当前时间帧,例如:
在图表上启动 EA。 一段时间后,日志将显示当前品种图表所用品种“新柱线”事件消息:
New bar on EURUSD M5: 2020.03.11 12:55 New bar on EURAUD M5: 2020.03.11 12:55 New bar on AUDUSD M5: 2020.03.11 12:55 New bar on EURUSD M5: 2020.03.11 13:00 New bar on AUDUSD M5: 2020.03.11 13:00 New bar on EURAUD M5: 2020.03.11 13:00
在可视测试器模式下,在设置中所选品种之一的图表上启动 EA,例如在 EURUSD 上,并查看图表注释中的零号柱线数据如何变化:
如我们所见,两行包含以不同方式获得的数据,接收相同的零号柱线属性值,且在每次即时报价上实时更新。
下一步是什么?
在下一篇文章中,我们将修复本文完成后发现的一些缺陷,并继续扩展操控时间序列的概念,即作为指标一部分操控时间序列的函数库功能。
以下附件是函数库当前版本的所有文件,以及测试 EA 文件,供您测试和下载。
请您在评论中留下问题和建议。
返回内容目录
该系列中的先前文章:
DoEasy 函数库中的时间序列(第三十五部分):柱线对象和品种时间序列列表
DoEasy 函数库中的时间序列(第三十六部分):所有用到的品种周期的时间序列对象
DoEasy 函数库中的时间序列(第三十七部分):时间序列集合 - 按品种和周期的时间序列数据库