今天,我们将创建可容纳程序中所有品种的时间序列的集合对象,每个都包含一个品种的特定时间帧数据。 结果就是,我们得到一个对象,其中包含每个品种的每个时间序列给定数量的所有柱线数据。
时间序列集合能存储程序中所有品种,以及设定的所有时间帧的所有必要历史数据。
此外,该集合允许为每个品种的每个时间帧分别设置所需的数据。
由于时间序列集合功能的说明过于庞大,故在下一篇文章中将实现其实时更新,以及从中获取所有可能的数据。
大多数函数库对象都是从所有库对象的基础对象派生出的,严格说是从 构建 MQL5 标准库的基类派生出的。
随着函数库需求的增长,函数库基准对象 CBaseObj 类也变得越发庞大。 如今,若我们从中继承新对象,那么它们也会继承其他根本不必要的方法。
若要解决此问题,我们来把基准对象类切分为两个:
因此,需要基本属性和方法的对象可从 CBaseObj 派生,而需要事件功能的对象则可从 CBaseObjExt 派生。
首先,简单地把 \MQL5\Include\DoEasy\Objects\BaseObj.mqh 中的 CBaseObj 基准对象类重命名为 CBaseObjExt,并在 \MQL5\Include\DoEasy\Engine.mqh 中编译 CEngine 函数库主对象的文件,此刻会导致大量编译错误(因为我们重命名了函数库基准对象类)。
遍历所有指示缺少 CBaseObj 类的错误列表,并将所有 “CBaseObj” 字符串的实例替换为 “CBaseObjExt”。 用正确的基准对象类名称重新编译应该会成功。
现在,在基准对象类清单里,添加新类 CBaseObj,它是从 MQL5 函数库基准对象派生而来,并在基准对象的新类里可看到从 CBaseObjExt 类搬运来的所有变量和方法。 CBaseObjExt 类则继承自 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: //--- (1) Set, (2) return the error logging level void SetLogLevel(const ENUM_LOG_LEVEL level) { this.m_log_level=level; } ENUM_LOG_LEVEL GetLogLevel(void) const { return this.m_log_level; } //--- (1) Set and (2) return the chart ID of the control program void SetMainChartID(const long id) { this.m_chart_id_main=id; } long GetMainChartID(void) const { return this.m_chart_id_main; } //--- (1) Set and (2) return chart ID void SetChartID(const long id) { this.m_chart_id=id; } long GetChartID(void) const { return this.m_chart_id; } //--- (1) Set the sub-folder name, (2) return the folder name for storing descendant object files void SetSubFolderName(const string name) { this.m_folder_name=DIRECTORY+name; } string GetFolderName(void) const { return this.m_folder_name; } //--- (1) Set and (2) return the name of the descendant object sound file void SetSoundName(const string name) { this.m_sound_name=name; } string GetSoundName(void) const { return this.m_sound_name; } //--- (1) Set and (2) return the flag of playing descendant object sounds void SetUseSound(const bool flag) { this.m_use_sound=flag; } bool IsUseSound(void) const { return this.m_use_sound; } //--- (1) Set and (2) return the flag of using the descendant object in the program void SetAvailable(const bool flag) { this.m_available=flag; } bool IsAvailable(void) const { return this.m_available; } //--- Return the global error code int GetError(void) const { return this.m_global_error; } //--- Return the object name string GetName(void) const { return this.m_name; } //--- Return an object type virtual int Type(void) const { return this.m_type; } //--- Constructor CBaseObj() : m_program((ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE)), m_global_error(ERR_SUCCESS), m_log_level(LOG_LEVEL_ERROR_MSG), m_chart_id_main(::ChartID()), m_chart_id(::ChartID()), m_folder_name(DIRECTORY), m_sound_name(""), m_name(__FUNCTION__), m_type(0), m_use_sound(false), m_available(true), m_first_start(true) {} }; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Extended base object class for all library objects | //+------------------------------------------------------------------+ #define CONTROLS_TOTAL (10) class CBaseObjExt : public CBaseObj { 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 MqlTick m_tick; // Tick structure for receiving quote data double m_hash_sum; // Object data hash sum double m_hash_sum_prev; // Object data hash sum during the previous check int m_digits_currency; // Number of decimal places in an account currency bool m_is_event; // Object event flag int m_event_code; // Object event code int m_event_id; // Event ID (equal to the object property value) //--- Data for storing, controlling and returning tracked properties: //--- [Property index][0] Controlled property increase value //--- [Property index][1] Controlled property decrease value //--- [Property index][2] Controlled property value level //--- [Property index][3] Property value //--- [Property index][4] Property value change //--- [Property index][5] Flag of a property change exceeding the increase value //--- [Property index][6] Flag of a property change exceeding the decrease value //--- [Property index][7] Flag of a property increase exceeding the control level //--- [Property index][8] Flag of a property decrease being less than the control level //--- [Property index][9] Flag of a property value being equal to the control level long m_long_prop_event[][CONTROLS_TOTAL]; // The array for storing object's integer properties values and controlled property change values double m_double_prop_event[][CONTROLS_TOTAL]; // The array for storing object's real properties values and controlled property change values long m_long_prop_event_prev[][CONTROLS_TOTAL]; // The array for storing object's controlled integer properties values during the previous check double m_double_prop_event_prev[][CONTROLS_TOTAL]; // The array for storing object's controlled real properties values during the previous check //--- Return (1) time in milliseconds, (2) milliseconds from the MqlTick time value long TickTime(void) const { return #ifdef __MQL5__ this.m_tick.time_msc #else this.m_tick.time*1000 #endif ; } ushort MSCfromTime(const long time_msc) const { return #ifdef __MQL5__ ushort(this.TickTime()%1000) #else 0 #endif ; } //--- return the flag of the event code presence in the event object bool IsPresentEventFlag(const int change_code) const { return (this.m_event_code & change_code)==change_code; } //--- Return the number of decimal places of the account currency int DigitsCurrency(void) const { return this.m_digits_currency; } //--- Returns the number of decimal places in the 'double' value int GetDigits(const double value) const; //--- Set the size of the array of controlled (1) integer and (2) real object properties bool SetControlDataArraySizeLong(const int size); bool SetControlDataArraySizeDouble(const int size); //--- Check the array size of object properties bool CheckControlDataArraySize(bool check_long=true); //--- Check the list of object property changes and create an event void CheckEvents(void); //--- (1) Pack a 'ushort' number to a passed 'long' number long UshortToLong(const ushort ushort_value,const uchar to_byte,long &long_value); protected: //--- (1) convert a 'ushort' value to a specified 'long' number byte long UshortToByte(const ushort value,const uchar to_byte) const; public: //--- Set the value of the pbject property controlled (1) increase, (2) decrease, (3) control level template<typename T> void SetControlledValueINC(const int property,const T value); template<typename T> void SetControlledValueDEC(const int property,const T value); template<typename T> void SetControlledValueLEVEL(const int property,const T value); //--- Return the set value of the controlled (1) integer and (2) real object properties increase long GetControlledLongValueINC(const int property) const { return this.m_long_prop_event[property][0]; } double GetControlledDoubleValueINC(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][0]; } //--- Return the set value of the controlled (1) integer and (2) real object properties decrease long GetControlledLongValueDEC(const int property) const { return this.m_long_prop_event[property][1]; } double GetControlledDoubleValueDEC(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][1]; } //--- Return the specified control level of object's (1) integer and (2) real properties long GetControlledLongValueLEVEL(const int property) const { return this.m_long_prop_event[property][2]; } double GetControlledDoubleValueLEVEL(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][2]; } //--- Return the current value of the object (1) integer and (2) real property long GetPropLongValue(const int property) const { return this.m_long_prop_event[property][3]; } double GetPropDoubleValue(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][3]; } //--- Return the change value of the controlled (1) integer and (2) real object property long GetPropLongChangedValue(const int property) const { return this.m_long_prop_event[property][4]; } double GetPropDoubleChangedValue(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][4]; } //--- Return the flag of an (1) integer and (2) real property value change exceeding the increase value long GetPropLongFlagINC(const int property) const { return this.m_long_prop_event[property][5]; } double GetPropDoubleFlagINC(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][5]; } //--- Return the flag of an (1) integer and (2) real property value change exceeding the decrease value long GetPropLongFlagDEC(const int property) const { return this.m_long_prop_event[property][6]; } double GetPropDoubleFlagDEC(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][6]; } //--- Return the flag of an (1) integer and (2) real property value increase exceeding the control level long GetPropLongFlagMORE(const int property) const { return this.m_long_prop_event[property][7]; } double GetPropDoubleFlagMORE(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][7]; } //--- Return the flag of an (1) integer and (2) real property value decrease being less than the control level long GetPropLongFlagLESS(const int property) const { return this.m_long_prop_event[property][8]; } double GetPropDoubleFlagLESS(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][8]; } //--- Return the flag of an (1) integer and (2) real property being equal to the control level long GetPropLongFlagEQUAL(const int property) const { return this.m_long_prop_event[property][9]; } double GetPropDoubleFlagEQUAL(const int property) const { return this.m_double_prop_event[property-this.m_long_prop_total][9]; } //--- Reset the variables of (1) tracked and (2) controlled object data (can be reset in the descendants) void ResetChangesParams(void); virtual void ResetControlsParams(void); //--- Add the (1) object event and (2) the object event reason to the list bool EventAdd(const ushort event_id,const long lparam,const double dparam,const string sparam); bool EventBaseAdd(const int event_id,const ENUM_BASE_EVENT_REASON reason,const double value); //--- Set/return the occurred event flag to the object data void SetEvent(const bool flag) { this.m_is_event=flag; } bool IsEvent(void) const { return this.m_is_event; } //--- Return (1) the list of events, (2) the object event code and (3) the global error code CArrayObj *GetListEvents(void) { return &this.m_list_events; } int GetEventCode(void) const { return this.m_event_code; } //--- Return (1) an event object and (2) a base event by its number in the list CEventBaseObj *GetEvent(const int shift=WRONG_VALUE,const bool check_out=true); CBaseEvent *GetEventBase(const int index); //--- Return the number of (1) object events int GetEventsTotal(void) const { return this.m_list_events.Total(); } //--- Update the object data to search for changes (Calling from the descendants: CBaseObj::Refresh()) virtual void Refresh(void); //--- Return an object event description string EventDescription(const int property, const ENUM_BASE_EVENT_REASON reason, const int source, const string value, const string property_descr, const int digits); //--- Data location in the magic number int value //----------------------------------------------------------- // bit 32|31 24|23 16|15 8|7 0| //----------------------------------------------------------- // byte | 3 | 2 | 1 | 0 | //----------------------------------------------------------- // data | uchar | uchar | ushort | //----------------------------------------------------------- // descr |pend req id| id2 | id1 | magic | //----------------------------------------------------------- //--- Set the ID of the (1) first group, (2) second group, (3) pending request to the magic number value void SetGroupID1(const uchar group,uint &magic) { magic &=0xFFF0FFFF; magic |= uint(this.ConvToXX(group,0)<<16); } void SetGroupID2(const uchar group,uint &magic) { magic &=0xFF0FFFFF; magic |= uint(this.ConvToXX(group,1)<<16); } void SetPendReqID(const uchar id,uint &magic) { magic &=0x00FFFFFF; magic |= (uint)id<<24; } //--- Convert the value of 0 - 15 into the necessary uchar number bits (0 - lower, 1 - upper ones) uchar ConvToXX(const uchar number,const uchar index) const { return((number>15 ? 15 : number)<<(4*(index>1 ? 1 : index))); } //--- Return (1) the specified magic number, the ID of (2) the first group, (3) second group, (4) pending request from the magic number value ushort GetMagicID(const uint magic) const { return ushort(magic & 0xFFFF); } uchar GetGroupID1(const uint magic) const { return uchar(magic>>16) & 0x0F; } uchar GetGroupID2(const uint magic) const { return uchar((magic>>16) & 0xF0)>>4; } uchar GetPendReqID(const uint magic) const { return uchar(magic>>24) & 0xFF; } //--- Constructor CBaseObjExt(); }; //+------------------------------------------------------------------+
现在,在所有函数库对象的新基准对象类中声明了三个新的类成员变量:
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 string m_sound_name; // Object sound file name
还添加了设置和返回变量值的相应方法:
//--- (1) Set and (2) return the name of the descendant object sound file void SetSoundName(const string name) { this.m_sound_name=name; } string GetSoundName(void) const { return this.m_sound_name; } //--- (1) Set and (2) return the flag of playing descendant object sounds void SetUseSound(const bool flag) { this.m_use_sound=flag; } bool IsUseSound(void) const { return this.m_use_sound; } //--- (1) Set and (2) return the flag of using the descendant object in the program void SetAvailable(const bool flag) { this.m_available=flag; } bool IsAvailable(void) const { return this.m_available; }
衍生对象的声音文件名允许设置(SetSoundName())或接收(GetSoundName())对象声音文件名,如果需要按条件播放声音,这可控制对象属性。
除了可以给对象分配名称这一事实之外,还可以启用/禁用(SetUseSound())播放文件的权限,并获得所设置播放文件的权限标记(IsUseSound())。
那么,“衍生对象正在使用的标志”实际上是什么,为什么我们需要设置和获取它?
例如,我们有某品种的 М5、М30、Н1 和 D1 时间序列对象。 但是在某个时间点,我们不想处理 M5 时间序列。 通过启用/禁用标志,我们可以调整监管事件函数库的必要性,例如 M5 时间序列的新创柱线。
所有函数库对象的基准对象中都存在这样的标记,这令我们可以灵活地控制此类对象的属性状态是否需要处理。 换言之,如果需要在程序中处理和使用对象,则设置标志。 如果您不再需要这样做,则删除该标志。
自然而然,类的构造函数也已被修改 — 移至新类的变量已被删除,且新类中的所有变量均被初始化。 请从附件里参阅修改的细节。
现在,以下类要自 CBaseObjExt 扩展基准对象派生:
在改进已创建的时间序列对象类之前,我们将必要的数据添加到 Datas.mqh 文件之中 — 新的宏替换指定分隔符,以便切分程序输入里的品种和时间帧列表字符串,时间帧操作模式的枚举,以及与所声明索引相对应的新消息索引和消息文本:
//+------------------------------------------------------------------+ //| Datas.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" //+------------------------------------------------------------------+ //| Macro substitutions | //+------------------------------------------------------------------+ #define INPUT_SEPARATOR (",") // Separator in the inputs string #define TOTAL_LANG (2) // Number of used languages //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Modes of working with symbols | //+------------------------------------------------------------------+ enum ENUM_SYMBOLS_MODE { SYMBOLS_MODE_CURRENT, // Work with the current symbol only SYMBOLS_MODE_DEFINES, // Work with the specified symbol list SYMBOLS_MODE_MARKET_WATCH, // Work with the Market Watch window symbols SYMBOLS_MODE_ALL // Work with the full symbol list }; //+------------------------------------------------------------------+ //| Mode of working with timeframes | //+------------------------------------------------------------------+ enum ENUM_TIMEFRAMES_MODE { TIMEFRAMES_MODE_CURRENT, // Work with the current timeframe only TIMEFRAMES_MODE_LIST, // Work with the specified timeframe list TIMEFRAMES_MODE_ALL // Work with the full timeframe list }; //+------------------------------------------------------------------+ MSG_LIB_SYS_ERROR_EMPTY_SYMBOLS_STRING, // Error. Predefined symbols string empty, to be used MSG_LIB_SYS_FAILED_PREPARING_SYMBOLS_ARRAY, // Failed to prepare array of used symbols. Error MSG_LIB_SYS_ERROR_EMPTY_PERIODS_STRING, // Error. The string of predefined periods is empty and is to be used MSG_LIB_SYS_FAILED_PREPARING_PERIODS_ARRAY, // Failed to prepare array of used periods. Error MSG_LIB_SYS_INVALID_ORDER_TYPE, // Invalid order type:
...
//--- 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 MSG_LIB_TEXT_TS_REQUIRED_HISTORY_DEPTH, // Requested history depth MSG_LIB_TEXT_TS_ACTUAL_DEPTH, // Actual history depth MSG_LIB_TEXT_TS_AMOUNT_HISTORY_DATA, // Created historical data MSG_LIB_TEXT_TS_HISTORY_BARS, // Number of history bars on the server MSG_LIB_TEXT_TS_TEXT_SYMBOL_TIMESERIES, // Symbol timeseries MSG_LIB_TEXT_TS_TEXT_TIMESERIES, // Timeseries MSG_LIB_TEXT_TS_TEXT_REQUIRED, // Requested MSG_LIB_TEXT_TS_TEXT_ACTUAL, // Actual MSG_LIB_TEXT_TS_TEXT_CREATED, // Created MSG_LIB_TEXT_TS_TEXT_HISTORY_BARS, // On the server MSG_LIB_TEXT_TS_TEXT_SYMBOL_FIRSTDATE, // The very first date by a period symbol MSG_LIB_TEXT_TS_TEXT_SYMBOL_LASTBAR_DATE, // Time of opening the last bar by period symbol MSG_LIB_TEXT_TS_TEXT_SYMBOL_SERVER_FIRSTDATE, // The very first date in history by a server symbol MSG_LIB_TEXT_TS_TEXT_SYMBOL_TERMINAL_FIRSTDATE, // The very first date in history by a symbol in the client terminal }; //+------------------------------------------------------------------+
...
{"Ошибка. Строка предопределённых символов пустая, будет использоваться ","Error. String of predefined symbols is empty, the Symbol will be used: "}, {"Не удалось подготовить массив используемых символов. Ошибка ","Failed to create an array of used symbols. Error "}, {"Ошибка. Строка предопределённых периодов пустая, будет использоваться ","Error. String of predefined periods is empty, the Period will be used: "}, {"Не удалось подготовить массив используемых периодов. Ошибка ","Failed to create an array of used periods. Error "}, {"Неправильный тип ордера: ","Invalid order type: "},...
{"Сначала нужно установить символ при помощи SetSymbol()","First you need to set the Symbol using SetSymbol()"}, {"Неизвестный таймфрейм","Unknown timeframe"}, {"Не удалось получить объект-таймсерию ","Failed to get timeseries object "}, {"Запрошенная глубина истории: ","Required history depth: "}, {"Фактическая глубина истории: ","Actual history depth: "}, {"Создано исторических данных: ","Total historical data created: "}, {"Баров истории на сервере: ","Server history Bars number: "}, {"Таймсерия символа","Symbol time series"}, {"Таймсерия","Timeseries"}, {"Запрошено","Required"}, {"Фактически","Actual"}, {"Создано","Created"}, {"На сервере","On server"}, {"Самая первая дата по символу-периоду","The very first date for the symbol-period"}, {"Время открытия последнего бара по символу-периоду","Open time of the last bar of the symbol-period"}, {"Самая первая дата в истории по символу на сервере","The very first date in the history of the symbol on the server"}, {"Самая первая дата в истории по символу в клиентском терминале","The very first date in the history of the symbol in the client terminal"}, }; //+---------------------------------------------------------------------+
我们准备了所有必要的数据,从而改进之前开发的时间序列类,并创建所有时间序列的集合。
我们针对单个 CSeries 时间帧来改进品种时间序列对象类。
在该类的私密部分当中,添加四个新变量和一个为时间序列设置日期的方法:
//+------------------------------------------------------------------+ //| Timeseries class | //+------------------------------------------------------------------+ class CSeries : public CBaseObj { private: ENUM_TIMEFRAMES m_timeframe; // Timeframe string m_symbol; // Symbol string m_period_description; // Timeframe string description datetime m_firstdate; // The very first date by a period symbol at the moment datetime m_lastbar_date; // Time of opening the last bar by period symbol 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_series; // Timeseries list CNewBarObj m_new_bar_obj; // "New bar" object //--- Set the very first date by a period symbol at the moment and the new time of opening the last bar by a period symbol void SetServerDate(void) { this.m_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_FIRSTDATE); this.m_lastbar_date=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_LASTBAR_DATE); } public:
当构造函数创建类对象时,并在方法中为时间序列对象设置时间帧之时,应立即将时间序列图表周期的说明写到 m_period_description 变量当中。 如此做是为了避免从函数库的 DELib.mqh 服务函数文件中不断访问 TimeframeDescription() 函数 — 该函数在时间帧字符串描述中查找来自 ENUM_TIMEFRAMES 枚举中的子字符串,从而妨碍了执行。 因此,最好在构造对象时立即执行“慢速”函数,以防数据不刷新,或偶尔因来自程序的请求而更新。
调用 SeriesInfoInteger() 函数,参数为 SERIES_FIRSTDATE 属性 ID,获取的该品种周期第一个日期的时刻保存在 m_firstdate 变量之中。
调用 SeriesInfoInteger() 函数,参数为 SERIES_LASTBAR_DATE 属性 ID,获取的该品种周期最后一个日期的时刻保存在 m_lastbar_date 变量之中。
仅在创建类对象,或在新创柱线上刷新数据,以及为时间序列对象设置新的品种或时间帧时,才调用 SetServerDate() 方法设置这两个变量。
m_required 变量存储所需(最后请求)已用时间序列数据的数量。 当请求必要的时间序列柱线数量时,可能会发现服务器上的请求数据量不足以创建时间序列。 在这种情况下,创建的时间序列的数量等于服务器上可用历史记录的数量。 该变量始终存储最后请求的数据量,无论实际获取和创建的数据多寡。 考虑到我们所用的“请求数据”概念,在类中所包含的原方法名称 “Amount” 已被修改 — 现已将其替换为 “Required”。
在类的公开部分中,还添加了新方法:
public: //--- Return (1) oneself and (2) the timeseries list CSeries *GetObject(void) { return &this; } 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 SetRequiredUsedData(const uint required,const uint rates_total); //--- Return (1) symbol, (2) timeframe, number of (3) used and (4) requested timeseries data, //--- (5) number of bars in the timeseries, (6) the very first date, (7) time of opening the last bar by a symbol period, //--- new bar flag with (8) automatic and (9) manual time management 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; } datetime FirstDate(void) const { return this.m_firstdate; } datetime LastBarDate(void) const { return this.m_lastbar_date; } 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); //--- (1) Set and (2) return the sound of a sound file of the "New bar" timeseries event void SetNewBarSoundName(const string name) { this.m_new_bar_obj.SetSoundName(name); } string NewBarSoundName(void) const { return this.m_new_bar_obj.GetSoundName(); } //--- 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 required,const uint rates_total); //--- (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); //--- 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); }; //+------------------------------------------------------------------+
GetObject() 方法将指向整个时间序列对象的指针返回给控制程序。 它能够获取整个时间序列对象,并在自定义程序中操控它。
RequiredUsedData() 方法将上述 m_required 变量的值返回给调用程序。
FirstDate() 和 LastBarDate() 方法返回上述的 m_firstdate 和 m_lastbar_date 变量的值。
SetNewBarSoundName() 方法设置 CNewBarObj “新创柱线"对象的声音文件名,该对象是时间序列对象的一部分。
NewBarSoundName() 方法返回分配给 CNewBarObj “新创柱线”对象的声音文件名,该对象是时间序列对象的一部分。
该方法允许将声音分配给任何时间序列对象。 当检测到“新创柱线”事件时,将播放声音。
Header() 方法创建并返回时间序列对象的简称:
//+------------------------------------------------------------------+ //| Return the timeseries name | //+------------------------------------------------------------------+ string CSeries::Header(void) { return CMessage::Text(MSG_LIB_TEXT_TS_TEXT_TIMESERIES)+" \""+this.m_symbol+"\" "+this.m_period_description; } //+------------------------------------------------------------------+
时间序列的字符串描述将按如下形式从方法返回
Timeseries "SYMBOL" TIMEFRAME_DESCRIPTION
例如:
Timeseries "AUDUSD" M15
Print() 方法在日志中显示完整的时间序列描述:
//+------------------------------------------------------------------+ //| Display the timeseries description in the journal | //+------------------------------------------------------------------+ void CSeries::Print(void) { string txt= ( CMessage::Text(MSG_LIB_TEXT_TS_REQUIRED_HISTORY_DEPTH)+(string)this.RequiredUsedData()+", "+ CMessage::Text(MSG_LIB_TEXT_TS_ACTUAL_DEPTH)+(string)this.AvailableUsedData()+", "+ CMessage::Text(MSG_LIB_TEXT_TS_AMOUNT_HISTORY_DATA)+(string)this.DataTotal()+", "+ CMessage::Text(MSG_LIB_TEXT_TS_HISTORY_BARS)+(string)this.Bars() ); ::Print(this.Header(),": ",txt); } //+------------------------------------------------------------------+
按以下形式将时间序列数据打印到日志中
HEADER: HISTORY_DEPTH: XXXX, ACTUAL_DEPTH: XXXX, AMOUNT_HISTORY_DATA: XXXX, HISTORY_BARS: XXXX
例如:
Timeseries "AUDUSD" W1: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 1400 Timeseries "AUDUSD" MN1: Requested history depth: 1000, Actual history depth: 322, Historical data created: 322, History bars on the server: 322
PrintShort() 方法在日志中显示时间序列的简述:
//+------------------------------------------------------------------+ //| Display a short timeseries description in the journal | //+------------------------------------------------------------------+ void CSeries::PrintShort(void) { string txt= ( CMessage::Text(MSG_LIB_TEXT_TS_TEXT_REQUIRED)+": "+(string)this.RequiredUsedData()+", "+ CMessage::Text(MSG_LIB_TEXT_TS_TEXT_ACTUAL)+": "+(string)this.AvailableUsedData()+", "+ CMessage::Text(MSG_LIB_TEXT_TS_TEXT_CREATED)+": "+(string)this.DataTotal()+", "+ CMessage::Text(MSG_LIB_TEXT_TS_TEXT_HISTORY_BARS)+": "+(string)this.Bars() ); ::Print(this.Header(),": ",txt); } //+------------------------------------------------------------------+
按以下形式将时间序列数据打印到日志中
HEADER: REQUIRED: XXXX, ACTUAL: XXXX, CREATED: XXXX, HISTORY_BARS: XXXX
例如:
Timeseries "USDJPY" W1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2562 Timeseries "USDJPY" MN1: Requested: 1000, Actual: 589, Created: 589, On the server: 589
在这两个类构造函数里添加保存时间帧描述和设置时间序列日期:
//+------------------------------------------------------------------+ //| Constructor 1 (current symbol and period timeseries) | //+------------------------------------------------------------------+ CSeries::CSeries(void) : m_bars(0),m_amount(0),m_required(0),m_sync(false) { this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE); this.m_list_series.Clear(); this.m_list_series.Sort(SORT_BY_BAR_INDEX); this.SetSymbolPeriod(NULL,(ENUM_TIMEFRAMES)::Period()); this.m_period_description=TimeframeDescription(this.m_timeframe); this.SetServerDate(); } //+------------------------------------------------------------------+ //| Constructor 2 (specified symbol and period timeseries) | //+------------------------------------------------------------------+ CSeries::CSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0) : m_bars(0), m_amount(0),m_required(0),m_sync(false) { this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE); this.m_list_series.Clear(); this.m_list_series.Sort(SORT_BY_BAR_INDEX); this.SetSymbolPeriod(symbol,timeframe); this.m_sync=this.SetRequiredUsedData(required,0); this.m_period_description=TimeframeDescription(this.m_timeframe); this.SetServerDate(); } //+------------------------------------------------------------------+
在品种设置方法中,添加对同一品种的检查和设置时间序列日期:
//+------------------------------------------------------------------+ //| Set a symbol | //+------------------------------------------------------------------+ void CSeries::SetSymbol(const string symbol) { if(this.m_symbol==symbol) return; this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_new_bar_obj.SetSymbol(this.m_symbol); this.SetServerDate(); } //+------------------------------------------------------------------+
在此,如果将对象中用到的品种传递给方法,则无需任何设置 — 仅需退出方法即可。
在时间帧设置方法中,添加对相同时间帧的检查和设置时间序列日期:
//+------------------------------------------------------------------+ //| Set a timeframe | //+------------------------------------------------------------------+ void CSeries::SetTimeframe(const ENUM_TIMEFRAMES timeframe) { if(this.m_timeframe==timeframe) return; this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe); this.m_new_bar_obj.SetPeriod(this.m_timeframe); this.m_period_description=TimeframeDescription(this.m_timeframe); this.SetServerDate(); } //+------------------------------------------------------------------+
在此,如果将对象中用到的时间帧传递给方法,则无需任何设置 — 仅需离开方法即可。
我们来修改设置交易品种和时间帧的方法:
//+------------------------------------------------------------------+ //| Set a symbol and timeframe | //+------------------------------------------------------------------+ void CSeries::SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe) { if(this.m_symbol==symbol && this.m_timeframe==timeframe) return; this.SetSymbol(symbol); this.SetTimeframe(timeframe); } //+------------------------------------------------------------------+
此处,如果将相同的品种和时间帧传递给该方法,则无需更改 —仅需离开方法即可。
接着,调用设置品种和时间帧的方法。
在为时间序列设置所需历史深度的方法中,保存请求的历史深度:
//+------------------------------------------------------------------+ //| Set the number of required data | //+------------------------------------------------------------------+ bool CSeries::SetRequiredUsedData(const uint required,const uint rates_total) { this.m_required=(required==0 ? SERIES_DEFAULT_BARS_COUNT : required); //--- Set the number of available timeseries bars this.m_bars=(uint) ( //--- If this is an indicator and the work is performed on the current symbol and timeframe, //--- add the rates_total value passed to the method, //--- otherwise, get the number from the environment this.m_program==PROGRAM_INDICATOR && this.m_symbol==::Symbol() && this.m_timeframe==::Period() ? rates_total : ::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_BARS_COUNT) ); //--- If managed to set the number of available history, 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; } //+------------------------------------------------------------------+
如果传递所需的零值,则请求历史记录的数量采用在 Define.mqh 中 SERIES_DEFAULT_BARS_COUNT 宏替换指定的默认数量(1000 根),否则 - 采用 'required' 传递的值。
刷新时间序列的方法会收到在程序中所用时间序列的检查结果。 如果时间序列尚未使用,则无需刷新:
//+------------------------------------------------------------------+ //| Update timeseries list and data | //+------------------------------------------------------------------+ void CSeries::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) { //--- If the timeseries is not used, exit if(!this.m_available) return; MqlRates rates[1]; //--- Set the flag of sorting the list of bars by index this.m_list_series.Sort(SORT_BY_BAR_INDEX); //--- If a new bar is present on a symbol and period, if(this.IsNewBarManual(time)) { //--- create a new bar object and add it to the end of the list CBar *new_bar=new CBar(this.m_symbol,this.m_timeframe,0); if(new_bar==NULL) return; if(!this.m_list_series.Add(new_bar)) { delete new_bar; return; } //--- Write the very first date by a period symbol at the moment and the new time of opening the last bar by a period symbol this.SetServerDate(); //--- if the timeseries exceeds the requested number of bars, remove the earliest bar if(this.m_list_series.Total()>(int)this.m_required) this.m_list_series.Delete(0); //--- save the new bar time as the previous one for the subsequent new bar check this.SaveNewBarTime(time); } //--- Get the index of the last bar in the list and the object bar by the index int index=this.m_list_series.Total()-1; CBar *bar=this.m_list_series.At(index); //--- if the work is performed in an indicator and the timeseries belongs to the current symbol and timeframe, //--- copy price parameters (passed to the method from the outside) to the bar price structure int copied=1; if(this.m_program==PROGRAM_INDICATOR && this.m_symbol==::Symbol() && this.m_timeframe==::Period()) { rates[0].time=time; rates[0].open=open; rates[0].high=high; rates[0].low=low; rates[0].close=close; rates[0].tick_volume=tick_volume; rates[0].real_volume=volume; rates[0].spread=spread; } //--- otherwise, get data to the bar price structure from the environment else copied=::CopyRates(this.m_symbol,this.m_timeframe,0,1,rates); //--- If the prices are obtained, set the new properties from the price structure for the bar object if(copied==1) bar.SetProperties(rates[0]); } //+------------------------------------------------------------------+
此外,如果在时间序列中出现新创柱线,则刷新时间序列日期。
这些是类中的主要变化。 在此,对于方法名称的微小变化我不想多说。 您可以在附件中找到完整的类清单。
至此,针对 CSeries 类的现阶段工作完成。
我们来终结包含一个品种所有可能图表周期 CSeries 对象的 CTimeSeries 类。
该类清单所提供的 IndexTimeframe() 和 TimeframeByIndex() 方法,用于接收列表中的时间帧索引,按列表索引存储相应图表的时间序列和时间帧周期。 这些方法非常特别,因为它们基于一个事实:最小可能时间帧的索引(PERIOD_M1)包含在列表的零号索引之中。 在 ENUM_TIMEFRAMES 枚举里,由于零索引包含 PERIOD_CURRENT 常量,因此 М1 的索引已定义为等于一。 换言之,所有索引的偏移相对于零加 1。
我们需要一些函数返回在 ENUM_TIMEFRAMES 枚举内定义的图表周期常数索引,反之亦然,需要按图表周期返回其在枚举内的常数。 我们将在服务函数文件中创建相应的函数 — IndexEnumTimeframe() 和 TimeframeByEnumIndex(),而在 CTimeSeries 类清单里,删除 IndexTimeframe() 和 TimeframeByIndex() 方法的实现,并在类主体添加对 IndexEnumTimeframe() 和 TimeframeByEnumIndex() 函数的调用,且偏移量加一。
将三个函数添加到服务函数的 DELib.mqh 文件当中:
//+------------------------------------------------------------------+ //| Return the timeframe index in the ENUM_TIMEFRAMES enumeration | //+------------------------------------------------------------------+ char IndexEnumTimeframe(ENUM_TIMEFRAMES timeframe) { int statement=(timeframe==PERIOD_CURRENT ? Period() : timeframe); switch(statement) { case PERIOD_M1 : return 1; case PERIOD_M2 : return 2; case PERIOD_M3 : return 3; case PERIOD_M4 : return 4; case PERIOD_M5 : return 5; case PERIOD_M6 : return 6; case PERIOD_M10 : return 7; case PERIOD_M12 : return 8; case PERIOD_M15 : return 9; case PERIOD_M20 : return 10; case PERIOD_M30 : return 11; case PERIOD_H1 : return 12; case PERIOD_H2 : return 13; case PERIOD_H3 : return 14; case PERIOD_H4 : return 15; case PERIOD_H6 : return 16; case PERIOD_H8 : return 17; case PERIOD_H12 : return 18; case PERIOD_D1 : return 19; case PERIOD_W1 : return 20; case PERIOD_MN1 : return 21; default : Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_TEXT_UNKNOWN_TIMEFRAME)); return WRONG_VALUE; } } //+------------------------------------------------------------------+ //| Return the timeframe by the ENUM_TIMEFRAMES enumeration index | //+------------------------------------------------------------------+ ENUM_TIMEFRAMES TimeframeByEnumIndex(const uchar index) { if(index==0) return(ENUM_TIMEFRAMES)Period(); switch(index) { case 1 : return PERIOD_M1; case 2 : return PERIOD_M2; case 3 : return PERIOD_M3; case 4 : return PERIOD_M4; case 5 : return PERIOD_M5; case 6 : return PERIOD_M6; case 7 : return PERIOD_M10; case 8 : return PERIOD_M12; case 9 : return PERIOD_M15; case 10 : return PERIOD_M20; case 11 : return PERIOD_M30; case 12 : return PERIOD_H1; case 13 : return PERIOD_H2; case 14 : return PERIOD_H3; case 15 : return PERIOD_H4; case 16 : return PERIOD_H6; case 17 : return PERIOD_H8; case 18 : return PERIOD_H12; case 19 : return PERIOD_D1; case 20 : return PERIOD_W1; case 21 : 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; } } //+------------------------------------------------------------------+ //| Return the timeframe by its description | //+------------------------------------------------------------------+ ENUM_TIMEFRAMES TimeframeByDescription(const string timeframe) { return ( timeframe=="M1" ? PERIOD_M1 : timeframe=="M2" ? PERIOD_M2 : timeframe=="M3" ? PERIOD_M3 : timeframe=="M4" ? PERIOD_M4 : timeframe=="M5" ? PERIOD_M5 : timeframe=="M6" ? PERIOD_M6 : timeframe=="M10" ? PERIOD_M10 : timeframe=="M12" ? PERIOD_M12 : timeframe=="M15" ? PERIOD_M15 : timeframe=="M20" ? PERIOD_M20 : timeframe=="M30" ? PERIOD_M30 : timeframe=="H1" ? PERIOD_H1 : timeframe=="H2" ? PERIOD_H2 : timeframe=="H3" ? PERIOD_H3 : timeframe=="H4" ? PERIOD_H4 : timeframe=="H6" ? PERIOD_H6 : timeframe=="H8" ? PERIOD_H8 : timeframe=="H12" ? PERIOD_H12 : timeframe=="D1" ? PERIOD_D1 : timeframe=="W1" ? PERIOD_W1 : timeframe=="MN1" ? PERIOD_MN1 : PERIOD_CURRENT ); } //+------------------------------------------------------------------+
在 CTimeSeries 类文件中,从清单中删除 IndexTimeframe() 和 TimeframeByIndex() 方法的实现:
//+------------------------------------------------------------------+
//| 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;
}
}
//+------------------------------------------------------------------+
//| 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;
}
}
//+------------------------------------------------------------------+
现在,我们要从服务函数的 DELib.mqh 文件中替换这些方法:
//--- 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)); }
由于时间序列的类列表包含所有可能的图表周期,因此零号列表索引包含 M1 图表周期的时间序列。 ENUM_TIMEFRAMES 枚举在零号索引中包含 PERIOD_CURRENT,而 М1 位于一号索引 — 因此,我们需要将索引值偏移一位,以便获得其在列表中的正确索引。 这正是我们在这里所做的。
在类的私密部分增加两个类成员变量,用于设置服务器和终端上历史记录中的第一个日期,以及在变量中设置这些日期值的方法:
//+------------------------------------------------------------------+ //| Symbol timeseries class | //+------------------------------------------------------------------+ class CTimeSeries : public CBaseObj { private: string m_symbol; // Timeseries symbol 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:
调用 SeriesInfoInteger() 函数,参数为 SERIES_SERVER_FIRSTDATE 属性 ID,获得服务器上该品种历史记录中的第一个日期,并存储在 m_server_firstdate 变量之中。
调用 SeriesInfoInteger() 函数,参数为 SERIES_TERMINAL_FIRSTDATE 属性 ID,获得终端上该品种历史记录中的第一个日期,并存储在 m_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 and (2) in the client terminal datetime ServerFirstDate(void) const { return this.m_server_firstdate; } datetime TerminalFirstDate(void) const { return this.m_terminal_firstdate; } //--- 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); }; //+------------------------------------------------------------------+
GetObject() 方法返回指向类对象的指针。 它能够接收品种时间序列类对象,并在自定义程序中操控它。
ServerFirstDate() 和 TerminalFirstDate() 方法返回上面讨论的 m_server_firstdate 和 m_terminal_firstdate 的变量值。
Compare() 虚拟方法允许按品种名称比较两个时间序列对象:
//+------------------------------------------------------------------+ //| Compare CTimeSeries objects | //+------------------------------------------------------------------+ int CTimeSeries::Compare(const CObject *node,const int mode=0) const { const CTimeSeries *compared_obj=node; return(this.Symbol()>compared_obj.Symbol() ? 1 : this.Symbol()<compared_obj.Symbol() ? -1 : 0); } //+------------------------------------------------------------------+
如果所比较的两个时间序列对象的品种相等,则该方法返回零。 否则,返回 +/- 1。 该方法已在标准库的 CObject 类中声明,并应在其后代中重新定义。
Print() 方法在日志中显示所有品种时间序列的完整描述:
//+------------------------------------------------------------------+ //| Display descriptions of all symbol timeseries in the journal | //+------------------------------------------------------------------+ void CTimeSeries::Print(const bool created=true) { ::Print(CMessage::Text(MSG_LIB_TEXT_TS_TEXT_SYMBOL_TIMESERIES)," ",this.m_symbol,": "); for(int i=0;i<this.m_list_series.Total();i++) { CSeries *series=this.m_list_series.At(i); if(series==NULL || (created && series.DataTotal()==0)) continue; series.Print(); } } //+------------------------------------------------------------------+
日志以相应的格式显示所有已创建(created=true),或已创建并声明(created=false)品种时间序列的列表,例如
created=true:
GBPUSD symbol timeseries: Timeseries "GBPUSD" M1: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 6296 Timeseries "GBPUSD" M5: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 3921 Timeseries "GBPUSD" M15: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 3227 Timeseries "GBPUSD" M30: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 3053 Timeseries "GBPUSD" H1: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 6187 Timeseries "GBPUSD" H4: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 5298 Timeseries "GBPUSD" D1: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 5288 Timeseries "GBPUSD" W1: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 1398 Timeseries "GBPUSD" MN1: Requested history depth: 1000, Actual history depth: 321, Historical data created: 321, History bars on the server: 321
created=false:
GBPUSD symbol timeseries: Timeseries "GBPUSD" M1: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 6296 Timeseries "GBPUSD" M2: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 5483 Timeseries "GBPUSD" M3: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 4616 Timeseries "GBPUSD" M4: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 4182 Timeseries "GBPUSD" M5: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 3921 Timeseries "GBPUSD" M6: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 3748 Timeseries "GBPUSD" M10: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 3401 Timeseries "GBPUSD" M12: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 3314 Timeseries "GBPUSD" M15: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 3227 Timeseries "GBPUSD" M20: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 3140 Timeseries "GBPUSD" M30: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 3053 Timeseries "GBPUSD" H1: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 6187 Timeseries "GBPUSD" H2: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 5047 Timeseries "GBPUSD" H3: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 5031 Timeseries "GBPUSD" H4: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 5298 Timeseries "GBPUSD" H6: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 6324 Timeseries "GBPUSD" H8: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 6301 Timeseries "GBPUSD" H12: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 0, History bars on the server: 5762 Timeseries "GBPUSD" D1: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 5288 Timeseries "GBPUSD" W1: Requested history depth: 1000, Actual history depth: 1000, Historical data created: 1000, History bars on the server: 1398 Timeseries "GBPUSD" MN1: Requested history depth: 1000, Actual history depth: 321, Historical data created: 321, History bars on the server: 321
Print Short() 方法在日志中显示所有品种时间序列的简要描述:
//+-------------------------------------------------------------------+ //| Display short descriptions of all symbol timeseries in the journal| //+-------------------------------------------------------------------+ void CTimeSeries::PrintShort(const bool created=true) { ::Print(CMessage::Text(MSG_LIB_TEXT_TS_TEXT_SYMBOL_TIMESERIES)," ",this.m_symbol,": "); for(int i=0;i<this.m_list_series.Total();i++) { CSeries *series=this.m_list_series.At(i); if(series==NULL || (created && series.DataTotal()==0)) continue; series.PrintShort(); } } //+------------------------------------------------------------------+
日志以相应的格式显示所有已创建(created=true),或已创建并声明(created=false)品种时间序列的列表,例如
created=true:
USDJPY symbol timeseries: Timeseries "USDJPY" M1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2880 Timeseries "USDJPY" M5: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3921 Timeseries "USDJPY" M15: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3227 Timeseries "USDJPY" M30: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3053 Timeseries "USDJPY" H1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5095 Timeseries "USDJPY" H4: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5023 Timeseries "USDJPY" D1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5305 Timeseries "USDJPY" W1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2562 Timeseries "USDJPY" MN1: Requested: 1000, Actual: 589, Created: 589, On the server: 589
created=false:
USDJPY symbol timeseries: Timeseries "USDJPY" M1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2880 Timeseries "USDJPY" M2: Requested: 1000, Actual: 1000, Created: 0, On the server: 3608 Timeseries "USDJPY" M3: Requested: 1000, Actual: 1000, Created: 0, On the server: 4616 Timeseries "USDJPY" M4: Requested: 1000, Actual: 1000, Created: 0, On the server: 4182 Timeseries "USDJPY" M5: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3921 Timeseries "USDJPY" M6: Requested: 1000, Actual: 1000, Created: 0, On the server: 3748 Timeseries "USDJPY" M10: Requested: 1000, Actual: 1000, Created: 0, On the server: 3401 Timeseries "USDJPY" M12: Requested: 1000, Actual: 1000, Created: 0, On the server: 3314 Timeseries "USDJPY" M15: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3227 Timeseries "USDJPY" M20: Requested: 1000, Actual: 1000, Created: 0, On the server: 3140 Timeseries "USDJPY" M30: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3053 Timeseries "USDJPY" H1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5095 Timeseries "USDJPY" H2: Requested: 1000, Actual: 1000, Created: 0, On the server: 5047 Timeseries "USDJPY" H3: Requested: 1000, Actual: 1000, Created: 0, On the server: 5031 Timeseries "USDJPY" H4: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5023 Timeseries "USDJPY" H6: Requested: 1000, Actual: 1000, Created: 0, On the server: 6390 Timeseries "USDJPY" H8: Requested: 1000, Actual: 1000, Created: 0, On the server: 6352 Timeseries "USDJPY" H12: Requested: 1000, Actual: 1000, Created: 0, On the server: 5796 Timeseries "USDJPY" D1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5305 Timeseries "USDJPY" W1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2562 Timeseries "USDJPY" MN1: Requested: 1000, Actual: 589, Created: 589, On the server: 589
类的构造函数已得到设置时间序列日期:
//+------------------------------------------------------------------+ //| 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(); } //+------------------------------------------------------------------+
当检测到已更新时间序列的“新创柱线”事件时,指定的时间序列更新方法提供为时间序列设置日期的功能:
//+------------------------------------------------------------------+ //| 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)) 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)) upd &=true; } if(upd) this.SetTerminalServerDate(); } //+------------------------------------------------------------------+
不过,这里我们应当仅刷新一次日期 — 若列表里任何已更新时间序列中检测到“新创柱线”事件时(有 21 个时间序列,我们不想将相同的日期设置 21 次)。 所以,在遍历所有时间序列时若遇到“新创柱线”事件发生,需要更新日期的标志设置为 true 。 直至循环完成后,如果已启用标志,则更新日期。
CTimeSeries 类的改进至此完毕。 我们在此忽略次要的调整。 您可以在附件中看到所有这些文件。
当前,我们有三个类,其中包含创建时间序列集合的所有必要数据:
现在,我们来创建时间序列集合,它是程序里用到的每个品种的时间序列列表集合(3)。
时间序列集合将由指向 CObject 类实例及其衍生类对象指针( CTimeSeries 类的指针)的动态数组构成。
在 \MQL5\Include\DoEasy\Collections\ 函数库文件夹里,创建 CTimeSeriesCollection 类的文件 TimeSeriesCollection.mqh。
基类对象是构造 CObject 标准库的基准对象。
我们来看一下类清单:
//+------------------------------------------------------------------+ //| 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 "..\Objects\Series\TimeSeries.mqh" #include "..\Objects\Symbols\Symbol.mqh" //+------------------------------------------------------------------+ //| Symbol timeseries collection | //+------------------------------------------------------------------+ class CTimeSeriesCollection : public CObject { private: CArrayObj m_list; // List of applied symbol timeseries //--- Return the timeseries index by symbol name int IndexTimeSeries(const string symbol); public: //--- Return (1) oneself and (2) the timeseries list CTimeSeriesCollection *GetObject(void) { return &this; } CArrayObj *GetList(void) { return &this.m_list; } //--- Create the symbol timeseries list collection bool CreateCollection(const CArrayObj *list_symbols); //--- Set the flag of using (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 SetAvailable(const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag=true); void SetAvailable(const ENUM_TIMEFRAMES timeframe,const bool flag=true); void SetAvailable(const string symbol,const bool flag=true); void SetAvailable(const bool flag=true); //--- Get the flag of using (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 IsAvailable(const string symbol,const ENUM_TIMEFRAMES timeframe); bool IsAvailable(const ENUM_TIMEFRAMES timeframe); bool IsAvailable(const string symbol); bool IsAvailable(void); //--- Set the history depth of (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 SetRequiredUsedData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0); bool SetRequiredUsedData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0); bool SetRequiredUsedData(const string symbol,const uint required=0,const int rates_total=0); bool SetRequiredUsedData(const uint required=0,const int rates_total=0); //--- 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); //--- 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 CreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0); bool CreateSeries(const ENUM_TIMEFRAMES timeframe,const uint required=0); bool CreateSeries(const string symbol,const uint required=0); bool CreateSeries(const uint required=0); //--- 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 void 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); 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 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); 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); //--- 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(); }; //+------------------------------------------------------------------+
此刻,该类是时间序列对象的列表,并含有创建/设置参数、按品种和时间帧更新必要时间序列的方法:
CObject 类对象的指针数组 m_list 包含 CTimeSeries 类对象的指针。 这是一个列表,我们从中可以获取所需操控的时间序列的数据。
IndexTimeSeries() 方法按品种名称返回时间序列索引,允许按品种名称访问所需的 CTimeSeries 对象:
//+------------------------------------------------------------------+ //| Return the timeseries index by symbol name | //+------------------------------------------------------------------+ int CTimeSeriesCollection::IndexTimeSeries(const string symbol) { CTimeSeries *tmp=new CTimeSeries(symbol); if(tmp==NULL) return WRONG_VALUE; this.m_list.Sort(); int index=this.m_list.Search(tmp); delete tmp; return index; } //+------------------------------------------------------------------+
依据传递给方法的品种值创建一个新的临时时间序列对象,为 m_list 列表设置已排序标志,并调用 Search() 方法获取对象在列表中的索引值。
确保删除临时时间序列对象,并返回获得的索引。
如果这个指定品种的对象不在列表中,则该方法返回 -1,否则返回 — 检测到的对象索引值。
GetObject() 方法将指向时间序列集合对象的指针返回给控制程序。 它能够获取整个集合对象,并在自定义程序中操控它。
GetList() 方法返回指向 CTimeSeries 时间序列集合列表的指针。 它能够获取所有品种的时间序列列表,并在自定义程序中操控它。
CreateCollection() 方法创建一个空的时间序列对象集合:
//+------------------------------------------------------------------+ //| Create the symbol timeseries collection list | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::CreateCollection(const CArrayObj *list_symbols) { //--- If an empty list of symbol objects is passed, exit if(list_symbols==NULL) return false; //--- Get the number of symbol objects in the passed list int total=list_symbols.Total(); //--- Clear the timeseries collection list this.m_list.Clear(); //--- In a loop by all symbol objects for(int i=0;i<total;i++) { //--- get the next symbol object CSymbol *symbol_obj=list_symbols.At(i); //--- if failed to get a symbol object, move on to the next one in the list if(symbol_obj==NULL) continue; //--- Create a new timeseries object with the current symbol name CTimeSeries *timeseries=new CTimeSeries(symbol_obj.Name()); //--- If failed to create the timeseries object, move on to the next symbol in the list if(timeseries==NULL) continue; //--- Set the sorted list flag for the timeseries collection list this.m_list.Sort(); //--- If the object with the same symbol name is already present in the timeseries collection list, remove the timeseries object if(this.m_list.Search(timeseries)>WRONG_VALUE) delete timeseries; //--- if failed to add the timeseries object to the collection list, remove the timeseries object else if(!this.m_list.Add(timeseries)) delete timeseries; } //--- Return the flag indicating that the created collection list has a size greater than zero return this.m_list.Total()>0; } //+-----------------------------------------------------------------------+
每个方法代码在其清单中均伴有注释。
之前针对程序里所有用到品种创建的列表将传递给该方法,并依列表循环创建 CTimeSeries 时间序列对象。 立即为其指定品种名称。 由此,我们得到了与程序里用到的品种数量一致的时间序列集合列表。 已创建时间序列对象的所有剩余数据保持为空。 它应该单独设置。
这样做是为了令函数库始终含有空时间序列集合列表,在数量上该列表应等于需操控而指定的品种数量。 时间帧,及其时间序列对象(用户将在程序中操控)在下一步或需要的时候进行设置。
最好在程序的 OnInit() 响应程序里,或在程序启动后立即创建它们,因为创建大量时间序列会消耗一些时间,尤其是在程序“冷”启动的情况下。
四个重载的 SetAvailable() 方法用于设置标志,表示程序需要针对指定的时间序列进行操作。
为指定品种的指定时间序列甚至标志的方法:
//+-----------------------------------------------------------------------+ //|Set the flag of using the specified timeseries of the specified symbol | //+-----------------------------------------------------------------------+ void CTimeSeriesCollection::SetAvailable(const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag=true) { int index=this.IndexTimeSeries(symbol); if(index==WRONG_VALUE) return; CTimeSeries *timeseries=this.m_list.At(index); if(timeseries==NULL) return; CSeries *series=timeseries.GetSeries(timeframe); if(series==NULL) return; series.SetAvailable(flag); } //+------------------------------------------------------------------+
该方法接收品种、时间帧,并为与时间帧品种相对应的时间序列设置标志。
首先,利用上述的 IndexTimeSeries() 方法,按品种得到其在 m_list 列表中的 CTimeSeries 时间序列索引。 再从列表中按索引获取时间序列。 调用上一篇文章中所述的 GetSeries() 方法,从获得的时间序列对象中,提取所需的指定图表周期的 CSeries 时间序列,并调用 CBaseObj 类的 SetAvailable() 方法为其设置传递给方法的标志。
该方法可针对所有品种的指定时间序列设置标志:
//+------------------------------------------------------------------+ //|Set the flag of using the specified timeseries of all symbols | //+------------------------------------------------------------------+ void CTimeSeriesCollection::SetAvailable(const ENUM_TIMEFRAMES timeframe,const bool flag=true) { int total=this.m_list.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; CSeries *series=timeseries.GetSeries(timeframe); if(series==NULL) continue; series.SetAvailable(flag); } } //+------------------------------------------------------------------+
该方法接收针对所有品种的指定时间序列需设置的时间帧和标志。
遍历所有品种时间序列列表的循环中,按循环索引获取下一个 CTimeSeries 时间序列。 利用我们在上一篇文章中研究过的的 GetSeries() 方法,从获得的时间序列对象中,提取指定图表周期的指定 CSeries 时间序列。 并利用 CBaseObj 类的 SetAvailable() 方法设置传递给方法的标志 。
为指定品种,设置使用所有时间序列的标志的方法:
//+------------------------------------------------------------------+ //|Set the flag of using all timeseries of the specified symbol | //+------------------------------------------------------------------+ void CTimeSeriesCollection::SetAvailable(const string symbol,const bool flag=true) { int index=this.IndexTimeSeries(symbol); if(index==WRONG_VALUE) return; CTimeSeries *timeseries=this.m_list.At(index); if(timeseries==NULL) return; CArrayObj *list=timeseries.GetListSeries(); if(list==NULL) return; int total=list.Total(); for(int i=0;i<total;i++) { CSeries *series=list.At(i); if(series==NULL) continue; series.SetAvailable(flag); } } //+------------------------------------------------------------------+
该方法接收品种和所需设置的标志,可用于指定品种的所有时间序列。
首先,利用上述的 IndexTimeSeries() 方法,按品种在 m_list 列表中获取 CTimeSeries 时间序列的索引。 再按索引从列表中获取 CTimeSeries 时间序列。 利用 GetListSeries() 方法,从获取的时间序列对象中,提取所有 CSeries 时间序列的完整列表。 循环遍历获取列表,从中获取下一个 CSeries 时间序列,并利用 CBaseObj类的 SetAvailable() 方法为其设置传递给方法的标志。
设置标志的方法,可用于所有品种的所有时间序列:
//+------------------------------------------------------------------+ //| Set the flag of using all timeseries of all symbols | //+------------------------------------------------------------------+ void CTimeSeriesCollection::SetAvailable(const bool flag=true) { int total=this.m_list.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; CArrayObj *list=timeseries.GetListSeries(); if(list==NULL) continue; int total_series=list.Total(); for(int j=0;j<total_series;j++) { CSeries *series=list.At(j); if(series==NULL) continue; series.SetAvailable(flag); } } } //+--------------------------------------------------------------------+
该方法接收要为所有品种的所有时间序列额外设置的标志。
循环遍历时间序列列表,从中按循环索引获取下一个 CTimeSeries 时间序列对象。 从获取的对象里,利用 GetListSeries() 方法提取所有 CSeries 时间序列的列表。 循环遍历 CSeries 时间序列列表,从中按循环索引获取下一个时间序列,并利用 CBaseObj 类的 SetAvailable() 方法设置传递给该方法的标志。
四个方法返回指定所有或指定的时间序列的标志:
//+-------------------------------------------------------------------------+ //|Return the flag of using the specified timeseries of the specified symbol| //+-------------------------------------------------------------------------+ bool CTimeSeriesCollection::IsAvailable(const string symbol,const ENUM_TIMEFRAMES timeframe) { int index=this.IndexTimeSeries(symbol); if(index==WRONG_VALUE) return false; CTimeSeries *timeseries=this.m_list.At(index); if(timeseries==NULL) return false; CSeries *series=timeseries.GetSeries(timeframe); if(series==NULL) return false; return series.IsAvailable(); } //+------------------------------------------------------------------+ //| Return the flag of using the specified timeseries of all symbols | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::IsAvailable(const ENUM_TIMEFRAMES timeframe) { bool res=true; int total=this.m_list.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; CSeries *series=timeseries.GetSeries(timeframe); if(series==NULL) continue; res &=series.IsAvailable(); } return res; } //+------------------------------------------------------------------+ //| Return the flag of using all timeseries of the specified symbol | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::IsAvailable(const string symbol) { bool res=true; int index=this.IndexTimeSeries(symbol); if(index==WRONG_VALUE) return false; CTimeSeries *timeseries=this.m_list.At(index); if(timeseries==NULL) return false; CArrayObj *list=timeseries.GetListSeries(); if(list==NULL) return false; int total=list.Total(); for(int i=0;i<total;i++) { CSeries *series=list.At(i); if(series==NULL) continue; res &=series.IsAvailable(); } return res; } //+------------------------------------------------------------------+ //| Return the flag of using all timeseries of all symbols | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::IsAvailable(void) { bool res=true; int total=this.m_list.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; CArrayObj *list=timeseries.GetListSeries(); if(list==NULL) continue; int total_series=list.Total(); for(int j=0;j<total_series;j++) { CSeries *series=list.At(j); if(series==NULL) continue; res &=series.IsAvailable(); } } return res; } //+--------------------------------------------------------------------+
这些方法的工作方式与上述我为时间序列设置标志的方法类似。 区别在于,利用初始状态为 true 的 res 局部变量,获取所有时间序列的 "collective" 标志,在方法中返回所用多个时间序列的公共标志。 循环遍历 CSeries 时间序列,每个所检测时间序列的标志状态被写入变量。 如果时间序列中至少有一个等于 false ,则在变量中设置 false 标志。 循环遍历所有时间序列完成后,将从方法中返回变量值。
有四个方法可以一次性为指定时间序列或所有时间序列设置所需历史记录深度:
//+--------------------------------------------------------------------------+ //|Set the history depth for the specified timeseries of the specified symbol| //+--------------------------------------------------------------------------+ bool CTimeSeriesCollection::SetRequiredUsedData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0) { int index=this.IndexTimeSeries(symbol); if(index==WRONG_VALUE) return false; CTimeSeries *timeseries=this.m_list.At(index); if(timeseries==NULL) return false; CSeries *series=timeseries.GetSeries(timeframe); if(series==NULL) return false; return series.SetRequiredUsedData(required,rates_total); } //+------------------------------------------------------------------+ //| Set the history depth of the specified timeseries of all symbols | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::SetRequiredUsedData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0) { bool res=true; int total=this.m_list.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; CSeries *series=timeseries.GetSeries(timeframe); if(series==NULL) continue; res &=series.SetRequiredUsedData(required,rates_total); } return res; } //+------------------------------------------------------------------+ //| Set the history depth for all timeseries of the specified symbol | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::SetRequiredUsedData(const string symbol,const uint required=0,const int rates_total=0) { bool res=true; int index=this.IndexTimeSeries(symbol); if(index==WRONG_VALUE) return false; CTimeSeries *timeseries=this.m_list.At(index); if(timeseries==NULL) return false; CArrayObj *list=timeseries.GetListSeries(); if(list==NULL) return false; int total=list.Total(); for(int i=0;i<total;i++) { CSeries *series=list.At(i); if(series==NULL) continue; res &=series.SetRequiredUsedData(required,rates_total); } return res; } //+------------------------------------------------------------------+ //| Set the history depth for all timeseries of all symbols | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::SetRequiredUsedData(const uint required=0,const int rates_total=0) { bool res=true; int total=this.m_list.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; CArrayObj *list=timeseries.GetListSeries(); if(list==NULL) continue; int total_series=list.Total(); for(int j=0;j<total_series;j++) { CSeries *series=list.At(j); if(series==NULL) continue; res &=series.SetRequiredUsedData(required,rates_total); } } return res; } //+------------------------------------------------------------------+
这些方法的工作方式与返回时间序列使用情况标志的方法类似。 它们返回利用 SetRequiredUsedData() 方法得到的 CSeries 时间序列对象的数据请求数量的结果。 因此,为多个 CSeries 时间序列设置历史深度时,此处也用到公用标志。
四种方法返回指定或所有时间序列的同步标志:
//+------------------------------------------------------------------+ //| Return the flag of data synchronization with the server data | //| for a specified timeseries of a specified symbol | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::SyncData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0) { int index=this.IndexTimeSeries(symbol); if(index==WRONG_VALUE) return false; CTimeSeries *timeseries=this.m_list.At(index); if(timeseries==NULL) return false; CSeries *series=timeseries.GetSeries(timeframe); if(series==NULL) return false; return series.SyncData(required,rates_total); } //+------------------------------------------------------------------+ //| Return the flag of data synchronization with the server data | //| for a specified timeseries of all symbols | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::SyncData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0) { bool res=true; int total=this.m_list.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; CSeries *series=timeseries.GetSeries(timeframe); if(series==NULL) continue; res &=series.SyncData(required,rates_total); } return res; } //+------------------------------------------------------------------+ //| Return the flag of data synchronization with the server data | //| for all timeseries of a specified symbol | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::SyncData(const string symbol,const uint required=0,const int rates_total=0) { bool res=true; int index=this.IndexTimeSeries(symbol); if(index==WRONG_VALUE) return false; CTimeSeries *timeseries=this.m_list.At(index); if(timeseries==NULL) return false; CArrayObj *list=timeseries.GetListSeries(); if(list==NULL) return false; int total=list.Total(); for(int i=0;i<total;i++) { CSeries *series=list.At(i); if(series==NULL) continue; res &=series.SyncData(required,rates_total); } return res; } //+------------------------------------------------------------------+ //| Return the flag of data synchronization with the server data | //| for all timeseries of all symbols | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::SyncData(const uint required=0,const int rates_total=0) { bool res=true; int total=this.m_list.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; CArrayObj *list=timeseries.GetListSeries(); if(list==NULL) continue; int total_series=list.Total(); for(int j=0;j<total_series;j++) { CSeries *series=list.At(j); if(series==NULL) continue; res &=series.SyncData(required,rates_total); } } return res; } //+------------------------------------------------------------------+
这些方法的工作方式与上述方法相似。 它们利用 CSeries 类的 SyncData() 方法检查时间序列同步,并返回结果。
用于创建指定或所有时间序列的四种方法。
创建指定品种的指定时间序列的方法:
//+------------------------------------------------------------------+ //| Create the specified timeseries of the specified symbol | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::CreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0) { int index=this.IndexTimeSeries(symbol); if(index==WRONG_VALUE) return false; CTimeSeries *timeseries=this.m_list.At(index); if(timeseries==NULL) return false; return timeseries.Create(timeframe,required); } //+------------------------------------------------------------------+
该方法接收创建时间序列的品种。 时间序列周期也一并传递给该方法。
按品种名称,利用 IndexTimeSeries() 方法获取时间序列在列表中的索引。 在利用得到的索引,从列表中获取 CTimeSeries 时间序列,并返回利用 CTimeSeries 类的 Create() 方法创建指定时间序列的结果 。
创建所有品种指定时间序列的方法:
//+------------------------------------------------------------------+ //| Create the specified timeseries of all symbols | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::CreateSeries(const ENUM_TIMEFRAMES timeframe,const uint required=0) { bool res=true; int total=this.m_list.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; res &=timeseries.Create(timeframe,required); } return res; } //+------------------------------------------------------------------+
该方法接收要为所有品种创建时间序列的时间帧。
循环遍历所有对象时间序列的所有对象,从中按循环索引提取下一个 CTimeSeries 时间序列对象。 利用 CTimeSeries 类的 Create() 方法添加创建指定时间序列的结果保存到 res 变量。 循环完成后,返回为所有品种创建指定时间序列的结果。 如果至少一个时间序列未能创建,则结果为 false 。
创建指定品种的所有时间序列的方法:
//+------------------------------------------------------------------+ //| Create all timeseries of the specified symbol | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::CreateSeries(const string symbol,const uint required=0) { int index=this.IndexTimeSeries(symbol); if(index==WRONG_VALUE) return false; CTimeSeries *timeseries=this.m_list.At(index); if(timeseries==NULL) return false; return timeseries.CreateAll(required); } //+------------------------------------------------------------------+
该方法接收创建所有时间序列所对应的品种。
按品种名称,利用 IndexTimeSeries() 方法获取时间序列在列表中的索引。 利用得到的索引,从列表中提取 CTimeSeries 时间序列,并返回利用 CTimeSeries 类的 CreateAll() 方法创建所有时间序列的结果。
创建所有品种的所有时间序列的方法:
//+------------------------------------------------------------------+ //| Create all timeseries of all symbols | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::CreateSeries(const uint required=0) { bool res=true; int total=this.m_list.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; res &=timeseries.CreateAll(required); } return res; } //+------------------------------------------------------------------+
该方法仅接收所创建历史数量的深度(与上述所有方法一样)。 默认情况下传递零。 这意味着采用 Defines.mqh 文件中的 SERIES_DEFAULT_BARS_COUNT 宏替换设置的 1000 条历史深度。
循环遍历时间序列列表,从中按循环索引获取下一个 CTimeSeries 时间序列对象。 利用 CreateAll() 方法为当前 CTimeSeries 对象品种创建所有时间序列,并将结果标志返回到 res 变量 。
循环完成后,返回为所有品种创建所有时间序列的结果。 如果至少一个时间序列未能创建,则结果为 false。
更新所有时间序列或指定品种的指定时间序列的四种方法:
//+------------------------------------------------------------------+ //| 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; 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; 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; 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; timeseries.RefreshAll(time,open,high,low,close,tick_volume,volume,spread); } } //+------------------------------------------------------------------+
在此,我们以一种或另一种方式(所有方式已在前面予以讲述)获得所需的 CTimeSeries 时间序列对象,并更新所有 CTimeSeries 类的时间序列,调用 Refresh() 方法更新指定的单个时间序列,或 RefreshAll() 方法更新全部。
来自时间序列数组的当前数据会被传递给所有方法。 这对于处理当前品种当前周期的指标很有必要。 在其余情况下,所传递的数值无关紧要。 因此,其默认值设置为 0。
该方法将完整的集合列表反馈到日志:
//+------------------------------------------------------------------+ //| Display complete collection description to the journal | //+------------------------------------------------------------------+ void CTimeSeriesCollection::Print(const bool created=true) { int total=this.m_list.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; timeseries.Print(created); } } //+------------------------------------------------------------------+
该方法接收标志,其代表在日志中必须仅显示所创建时间序列。
循环遍历时间序列对象列表,从中提取下一个 TimeSeries 时间序列对象,并调用同名方法,该方法操作结果已在前面讲述。 结果就是,在日志中可以找到所有集合品种的所有当前时间序列的数据。
该方法将集合列表简要反馈到日志:
//+------------------------------------------------------------------+ //| Display the short collection description in the journal | //+------------------------------------------------------------------+ void CTimeSeriesCollection::PrintShort(const bool created=true) { int total=this.m_list.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; timeseries.PrintShort(created); } } //+------------------------------------------------------------------+
该方法接收标志,其代表在日志中必须仅显示所创建时间序列。
循环遍历时间序列对象列表,从中提取下一个 TimeSeries 时间序列对象,并调用同名方法,该方法操作结果已在前面讲述。 结果就是,在日志中以简要形式提供了所有集合品种的所有当前时间序列的数据。
时间序列集合类的当前版本已准备就绪。 请参阅附件中的完整类清单。
现在,我们需要为所创建时间序列集合安排外部访问,并为所创建时间序列便捷地设置参数。
任何程序都可以从 CEngine 函数库主对象访问函数库方法。
将访问时间序列集合的函数添加到 CEngine 类文件之中。
打开 \MQL5\Include\DoEasy\ 函数库目录中的 Engine.mqh 文件,并进行必要的修改。
由于之前版本里, CTimeSeries 类文件已包含在 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\TimeSeries.mqh" //+------------------------------------------------------------------+
并包含时间序列集合类的文件:
//+------------------------------------------------------------------+ //| Engine.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 "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 "Collections\TimeSeriesCollection.mqh" #include "TradingControl.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
在该类的公开部分,修改为所用品种设置列表的方法实现:
//--- Set the list of (1) used symbols bool SetUsedSymbols(const string &array_symbols[]) { return this.m_symbols.SetUsedSymbols(array_symbols);}
在当前的实现中,该方法调用为品种集合类设置品种列表的同名方法。 在此,最方便的是在品种集合中设置品种列表之后,立即基于创建的品种集合列表创建时间序列集合。
在此处仅保留方法声明:
//--- Set the list of used symbols in the symbol collection and create the collection of symbol timeseries bool SetUsedSymbols(const string &array_symbols[]);
并在类的主体之外编写其实现:
//+------------------------------------------------------------------+ //| Set the list of used symbols in the symbol collection | //| and create the symbol timeseries collection | //+------------------------------------------------------------------+ bool CEngine::SetUsedSymbols(const string &array_symbols[]) { bool res=this.m_symbols.SetUsedSymbols(array_symbols); CArrayObj *list=this.GetListAllUsedSymbols(); if(list==NULL) return false; res&=this.m_series.CreateCollection(list); return res; } //+------------------------------------------------------------------+
此处,声明 res 变量,并依据在品种集合中设置品种列表方法的结果对其进行初始化。
接着,从品种集合类中提取所需品种的列表。 如果列表尚未创建,则返回 false,否则,将基于品种集合列表创建时间序列集合列表的结果添加到 res 变量。
返回来自方法的最终结果。
在类的公开部分中添加处理时间序列集合的方法:
//--- Return the list of pending requests CArrayObj *GetListPendingRequests(void) { return this.m_trading.GetListRequests(); } //--- Return (1) the timeseries collection and (2) the list of timeseries from the timeseries collection CTimeSeriesCollection *GetTimeSeriesCollection(void) { return &this.m_series; } CArrayObj *GetListTimeSeries(void) { return this.m_series.GetList(); } //--- Set the flag of using (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 SeriesSetAvailable(const string symbol,const ENUM_TIMEFRAMES timeframe,const bool flag=true) { this.m_series.SetAvailable(symbol,timeframe,flag);} void SeriesSetAvailable(const ENUM_TIMEFRAMES timeframe,const bool flag=true) { this.m_series.SetAvailable(timeframe,flag); } void SeriesSetAvailable(const string symbol,const bool flag=true) { this.m_series.SetAvailable(symbol,flag); } void SeriesSetAvailable(const bool flag=true) { this.m_series.SetAvailable(flag); } //--- Set the history depth of (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 SeriesSetRequiredUsedData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0) { return this.m_series.SetRequiredUsedData(symbol,timeframe,required,rates_total);} bool SeriesSetRequiredUsedData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0) { return this.m_series.SetRequiredUsedData(timeframe,required,rates_total); } bool SeriesSetRequiredUsedData(const string symbol,const uint required=0,const int rates_total=0) { return this.m_series.SetRequiredUsedData(symbol,required,rates_total); } bool SeriesSetRequiredUsedData(const uint required=0,const int rates_total=0) { return this.m_series.SetRequiredUsedData(required,rates_total); } //--- 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 SeriesSyncData(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0) { return this.m_series.SyncData(symbol,timeframe,required,rates_total); } bool SeriesSyncData(const ENUM_TIMEFRAMES timeframe,const uint required=0,const int rates_total=0) { return this.m_series.SyncData(timeframe,required,rates_total); } bool SeriesSyncData(const string symbol,const uint required=0,const int rates_total=0) { return this.m_series.SyncData(symbol,required,rates_total); } bool SeriesSyncData(const uint required=0,const int rates_total=0) { return this.m_series.SyncData(required,rates_total); } //--- 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); } //--- 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 void SeriesRefresh(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) { this.m_series.Refresh(symbol,timeframe,time,open,high,low,close,tick_volume,volume,spread); } void SeriesRefresh(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) { this.m_series.Refresh(timeframe,time,open,high,low,close,tick_volume,volume,spread); } void SeriesRefresh(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) { this.m_series.Refresh(symbol,time,open,high,low,close,tick_volume,volume,spread); } void SeriesRefresh(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) { this.m_series.Refresh(time,open,high,low,close,tick_volume,volume,spread); }
GetTime SeriesCollection() 方法将指向时间序列集合对象的指针返回到调用程序。 它允许接收程序中指向集合的指针,并有效地操控它。
GetListTimeSeries() 方法将指向集合时间序列列表的指针返回到调用程序。 它允许接收指向 CTimeSeries 时间序列对象列表的指针,并有效地操控它。
SeriesSetAvailable() 重载方法可访问我们前面已研究的 CTimeSeriesCollection 时间序列集合类的 SetAvailable() 方法。
SeriesSetRequiredUsedData() 重载方法可访问我们前面已研究的 CTimeSeriesCollection 时间序列集合类的 SetRequiredUsedData() 方法。
SeriesSyncData() 重载方法可访问我们前面已研究的 CTimeSeriesCollection 时间序列集合类的 SyncData() 方法。
SeriesCreate() 重载方法可访问我们前面已研究的 CTimeSeriesCollection 时间序列集合类的 CreateSeries() 方法。
SeriesRefresh() 重载方法可访问我们前面已研究的 CTimeSeriesCollection 时间序列集合类的 Refresh() 方法。
所有这些,都是为测试新的时间序列集合类而进行的必要任务。
我们利用上一篇文章的 EA 来测试创建和填充时间序列集合。 将其保存在新文件夹 \MQL5\Experts\TestDoEasy\Part37\ 之中,并命名为 TestDoEasyPart37.mq5。
为了选择操控品种的模式,我们在 Datas.mqh 文件中已有枚举。
//+------------------------------------------------------------------+ //| Modes of working with symbols | //+------------------------------------------------------------------+ enum ENUM_SYMBOLS_MODE { SYMBOLS_MODE_CURRENT, // Work with the current symbol only SYMBOLS_MODE_DEFINES, // Work with the specified symbol list SYMBOLS_MODE_MARKET_WATCH, // Work with the Market Watch window symbols SYMBOLS_MODE_ALL // Work with the full symbol list }; //+------------------------------------------------------------------+
EA 拥有输入变量,可令我们选择与之配套操控的品种:
sinput ENUM_SYMBOLS_MODE InpModeUsedSymbols = SYMBOLS_MODE_CURRENT; // Mode of used symbols list
根据变量值的不同,将在 OnInitDoEasy() EA 函数的初始化期间创建品种数组,并将其传递给函数库。 然后在h函数库中创建品种列表。
我们需要执行相同的操作来选择并创建 EA 时间帧的列表。
在 Datas.mqh 文件中创建新的枚举,以便选择操作模式和品种图表周期:
//+------------------------------------------------------------------+ //| Datas.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" //+------------------------------------------------------------------+ //| Macro substitutions | //+------------------------------------------------------------------+ #define INPUT_SEPARATOR (",") // Separator in the inputs string #define TOTAL_LANG (2) // Number of used languages //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Modes of working with symbols | //+------------------------------------------------------------------+ enum ENUM_SYMBOLS_MODE { SYMBOLS_MODE_CURRENT, // Work with the current symbol only SYMBOLS_MODE_DEFINES, // Work with the specified symbol list SYMBOLS_MODE_MARKET_WATCH, // Work with the Market Watch window symbols SYMBOLS_MODE_ALL // Work with the full symbol list }; //+------------------------------------------------------------------+ //| Mode of working with timeframes | //+------------------------------------------------------------------+ enum ENUM_TIMEFRAMES_MODE { TIMEFRAMES_MODE_CURRENT, // Work with the current timeframe only TIMEFRAMES_MODE_LIST, // Work with the specified timeframe list TIMEFRAMES_MODE_ALL // Work with the full timeframe list }; //+------------------------------------------------------------------+
服务函数文件提供函数,来为所用函数库品种准备数组:
//+------------------------------------------------------------------+ //| Prepare the symbol array for a symbol collection | //+------------------------------------------------------------------+ bool CreateUsedSymbolsArray(const ENUM_SYMBOLS_MODE mode_used_symbols,string defined_used_symbols,string &used_symbols_array[]) { //--- When working with the current symbol if(mode_used_symbols==SYMBOLS_MODE_CURRENT) { //--- Write the name of the current symbol to the only array cell ArrayResize(used_symbols_array,1); used_symbols_array[0]=Symbol(); return true; } //--- If working with a predefined symbol set (from the defined_used_symbols string) else if(mode_used_symbols==SYMBOLS_MODE_DEFINES) { //--- Set a comma as a separator string separator=","; //--- Replace erroneous separators with correct ones if(StringFind(defined_used_symbols,";")>WRONG_VALUE) StringReplace(defined_used_symbols,";",separator); if(StringFind(defined_used_symbols,":")>WRONG_VALUE) StringReplace(defined_used_symbols,":",separator); if(StringFind(defined_used_symbols,"|")>WRONG_VALUE) StringReplace(defined_used_symbols,"|",separator); if(StringFind(defined_used_symbols,"/")>WRONG_VALUE) StringReplace(defined_used_symbols,"/",separator); if(StringFind(defined_used_symbols,"\\")>WRONG_VALUE) StringReplace(defined_used_symbols,"\\",separator); if(StringFind(defined_used_symbols,"'")>WRONG_VALUE) StringReplace(defined_used_symbols,"'",separator); if(StringFind(defined_used_symbols,"-")>WRONG_VALUE) StringReplace(defined_used_symbols,"-",separator); if(StringFind(defined_used_symbols,"`")>WRONG_VALUE) StringReplace(defined_used_symbols,"`",separator); //--- Delete as long as there are spaces while(StringFind(defined_used_symbols," ")>WRONG_VALUE && !IsStopped()) StringReplace(defined_used_symbols," ",""); //--- As soon as there are double separators (after removing spaces between them), replace them with a separator while(StringFind(defined_used_symbols,separator+separator)>WRONG_VALUE && !IsStopped()) StringReplace(defined_used_symbols,separator+separator,separator); //--- If a single separator remains before the first symbol in the string, replace it with a space if(StringFind(defined_used_symbols,separator)==0) StringSetCharacter(defined_used_symbols,0,32); //--- If a single separator remains after the last symbol in the string, replace it with a space if(StringFind(defined_used_symbols,separator)==StringLen(defined_used_symbols)-1) StringSetCharacter(defined_used_symbols,StringLen(defined_used_symbols)-1,32); //--- Remove all redundant things to the left and right #ifdef __MQL5__ StringTrimLeft(defined_used_symbols); StringTrimRight(defined_used_symbols); //--- __MQL4__ #else defined_used_symbols=StringTrimLeft(defined_used_symbols); defined_used_symbols=StringTrimRight(defined_used_symbols); #endif //--- Prepare the array ArrayResize(used_symbols_array,0); ResetLastError(); //--- divide the string by separators (comma) and add all found substrings to the array int n=StringSplit(defined_used_symbols,StringGetCharacter(separator,0),used_symbols_array); //--- if nothing is found, display the appropriate message (working with the current symbol is selected automatically) if(n<1) { string err= (n==0 ? DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_ERROR_EMPTY_STRING)+Symbol() : DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_FAILED_PREPARING_SYMBOLS_ARRAY)+(string)GetLastError() ); Print(err); return false; } } //--- If working with the Market Watch window or the full list else { //--- Add the (mode_used_symbols) working mode to the only array cell ArrayResize(used_symbols_array,1); used_symbols_array[0]=EnumToString(mode_used_symbols); } return true; } //+------------------------------------------------------------------+
我们应该已有了类似的函数,来为所用时间帧准备数组。 很明显,在两个类似的函数中,我们会得到相同的代码块(在提供的清单中,以颜色突出显示)。
我们将此代码块移至一个单独的函数中:
//+------------------------------------------------------------------+ //| Prepare the passed string of parameters | //+------------------------------------------------------------------+ int StringParamsPrepare(string defined_used,string separator,string &array[]) { //--- Replace erroneous separators with correct ones if(separator!=";" && StringFind(defined_used,";")>WRONG_VALUE) StringReplace(defined_used,";",separator); if(separator!=":" && StringFind(defined_used,":")>WRONG_VALUE) StringReplace(defined_used,":",separator); if(separator!="|" && StringFind(defined_used,"|")>WRONG_VALUE) StringReplace(defined_used,"|",separator); if(separator!="/" && StringFind(defined_used,"/")>WRONG_VALUE) StringReplace(defined_used,"/",separator); if(separator!="\\"&& StringFind(defined_used,"\\")>WRONG_VALUE) StringReplace(defined_used,"\\",separator); if(separator!="'" && StringFind(defined_used,"'")>WRONG_VALUE) StringReplace(defined_used,"'",separator); if(separator!="-" && StringFind(defined_used,"-")>WRONG_VALUE) StringReplace(defined_used,"-",separator); if(separator!="`" && StringFind(defined_used,"`")>WRONG_VALUE) StringReplace(defined_used,"`",separator); //--- Delete as long as there are spaces while(StringFind(defined_used," ")>WRONG_VALUE && !IsStopped()) StringReplace(defined_used," ",""); //--- As soon as there are double separators (after removing spaces between them), replace them with a separator while(StringFind(defined_used,separator+separator)>WRONG_VALUE && !IsStopped()) StringReplace(defined_used,separator+separator,separator); //--- If a single separator remains before the first symbol in the string, replace it with a space if(StringFind(defined_used,separator)==0) StringSetCharacter(defined_used,0,32); //--- If a single separator remains after the last symbol in the string, replace it with a space if(StringFind(defined_used,separator)==StringLen(defined_used)-1) StringSetCharacter(defined_used,StringLen(defined_used)-1,32); //--- Remove all redundant things to the left and right #ifdef __MQL5__ StringTrimLeft(defined_used); StringTrimRight(defined_used); //--- __MQL4__ #else defined_used=StringTrimLeft(defined_used); defined_used=StringTrimRight(defined_used); #endif //--- Prepare the array ArrayResize(array,0); ResetLastError(); //--- divide the string by separators (comma), write all detected substrings into the array and return the number of obtained substrings return StringSplit(defined_used,StringGetCharacter(separator,0),array); } //+------------------------------------------------------------------+
在这种情况下,为所用品种创建数组的函数如下所示:
//+------------------------------------------------------------------+ //| Prepare the symbol array for a symbol collection | //+------------------------------------------------------------------+ bool CreateUsedSymbolsArray(const ENUM_SYMBOLS_MODE mode_used_symbols,string defined_used_symbols,string &used_symbols_array[]) { //--- When working with the current symbol if(mode_used_symbols==SYMBOLS_MODE_CURRENT) { //--- Write the name of the current symbol to the only array cell ArrayResize(used_symbols_array,1); used_symbols_array[0]=Symbol(); return true; } //--- If working with a predefined symbol set (from the defined_used_symbols string) else if(mode_used_symbols==SYMBOLS_MODE_DEFINES) { //--- Set comma as a separator (defined in the Datas.mqh file, page 11) string separator=INPUT_SEPARATOR; int n=StringParamsPrepare(defined_used_symbols,separator,used_symbols_array); //--- if nothing is found, display the appropriate message (working with the current symbol is selected automatically) if(n<1) { int err_code=GetLastError(); string err= (n==0 ? DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_ERROR_EMPTY_SYMBOLS_STRING)+Symbol() : DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_FAILED_PREPARING_SYMBOLS_ARRAY)+(string)err_code+": "+CMessage::Text(err_code) ); Print(err); return false; } } //--- If working with the Market Watch window or the full list else { //--- Add the (mode_used_symbols) working mode to the only array cell ArrayResize(used_symbols_array,1); used_symbols_array[0]=EnumToString(mode_used_symbols); } //--- All is successful return true; } //+------------------------------------------------------------------+
移动到单独函数中的代码块将被准备参数字符串的函数所替代。
我们创建一个函数,以类似的方式为程序中所用时间帧准备数组:
//+------------------------------------------------------------------+ //| Prepare the array of timeframes for the timeseries collection | //+------------------------------------------------------------------+ bool CreateUsedTimeframesArray(const ENUM_TIMEFRAMES_MODE mode_used_periods,string defined_used_periods,string &used_periods_array[]) { //--- If working with the current chart period, set the current timeframe flag if(mode_used_periods==TIMEFRAMES_MODE_CURRENT) { ArrayResize(used_periods_array,1,21); used_periods_array[0]=TimeframeDescription((ENUM_TIMEFRAMES)Period()); return true; } //--- If working with a predefined set of chart periods (from the defined_used_periods string) else if(mode_used_periods==TIMEFRAMES_MODE_LIST) { //--- Set comma as a separator (defined in the Datas.mqh file, page 11) string separator=INPUT_SEPARATOR; //--- Fill in the array of parameters from the string with predefined timeframes int n=StringParamsPrepare(defined_used_periods,separator,used_periods_array); //--- if nothing is found, display the appropriate message (working with the current period is selected automatically) if(n<1) { int err_code=GetLastError(); string err= (n==0 ? DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_ERROR_EMPTY_PERIODS_STRING)+TimeframeDescription((ENUM_TIMEFRAMES)Period()) : DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_FAILED_PREPARING_PERIODS_ARRAY)+(string)err_code+": "+CMessage::Text(err_code) ); Print(err); //--- Set the current period to the array ArrayResize(used_periods_array,1,21); used_periods_array[0]=TimeframeDescription((ENUM_TIMEFRAMES)Period()); return false; } } //--- If working with the full list of timeframes, fill in the array with strings describing all timeframes else { ArrayResize(used_periods_array,21,21); for(int i=0;i<21;i++) used_periods_array[i]=TimeframeDescription(TimeframeByEnumIndex(uchar(i+1))); } //--- All is successful return true; } //+------------------------------------------------------------------+
准备参数字符串的函数于此以类似的方式调用。 相应的代码与 CreateUsedSymbolsArray() 和 CreateUsedTimeframesArray() 函数相同。
DELib.mqh 文件还含有显示时间(以毫秒为单位)的函数:
//+------------------------------------------------------------------+ //| Return time with milliseconds | //+------------------------------------------------------------------+ string TimeMSCtoString(const long time_msc) { return TimeToString(time_msc/1000,TIME_DATE|TIME_MINUTES|TIME_SECONDS)+"."+IntegerToString(time_msc%1000,3,'0'); } //+------------------------------------------------------------------+
该函数始终按以下格式显示时间:YYYY.MM.DD HH:MM:SS.MSC
我们增加选择时间显示格式的函数:
//+------------------------------------------------------------------+ //| Return time with milliseconds | //+------------------------------------------------------------------+ string TimeMSCtoString(const long time_msc,int flags=TIME_DATE|TIME_MINUTES|TIME_SECONDS) { return TimeToString(time_msc/1000,flags)+"."+IntegerToString(time_msc%1000,3,'0'); } //+------------------------------------------------------------------+
现在,可以设置时间显示格式+毫秒。
将所用时间帧的选择添加到 EA 文件输入模块之中:
sinput ENUM_SYMBOLS_MODE InpModeUsedSymbols = SYMBOLS_MODE_CURRENT; // Mode of used symbols list sinput string InpUsedSymbols = "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY"; // List of used symbols (comma - separator) sinput ENUM_TIMEFRAMES_MODE InpModeUsedTFs = TIMEFRAMES_MODE_LIST; // Mode of used timeframes list sinput string InpUsedTFs = "M1,M5,M15,M30,H1,H4,D1,W1,MN1"; // List of used timeframes (comma - separator) sinput bool InpUseSounds = true; // Use sounds
InpModeUsedTFs 允许选择时间帧运用模式
选择第二种模式时,程序将采用 InpUsedTFs 变量输入指定的字符串所对应的时间帧列表。
从 EA 全局变量模块中,删除声明 CTimeSeries 类对象的变量。 因为它不再需要。 现在,通过访问 engine 函数库对象,来执行对时间序列的访问。
//--- global variables CEngine engine; CTimeSeries timeseries; SDataButt butt_data[TOTAL_BUTT];
全局变量的同一模块接收新数组,程序所用时间帧名称将被写入其内:
//--- global variables CEngine engine; SDataButt butt_data[TOTAL_BUTT]; string prefix; double lot; double withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal); ushort magic_number; uint stoploss; uint takeprofit; uint distance_pending; uint distance_stoplimit; uint distance_pending_request; uint bars_delay_pending_request; uint slippage; bool trailing_on; bool pressed_pending_buy; bool pressed_pending_buy_limit; bool pressed_pending_buy_stop; bool pressed_pending_buy_stoplimit; bool pressed_pending_close_buy; bool pressed_pending_close_buy2; bool pressed_pending_close_buy_by_sell; bool pressed_pending_sell; bool pressed_pending_sell_limit; bool pressed_pending_sell_stop; bool pressed_pending_sell_stoplimit; bool pressed_pending_close_sell; bool pressed_pending_close_sell2; bool pressed_pending_close_sell_by_buy; bool pressed_pending_delete_all; bool pressed_pending_close_all; bool pressed_pending_sl; bool pressed_pending_tp; double trailing_stop; double trailing_step; uint trailing_start; uint stoploss_to_modify; uint takeprofit_to_modify; int used_symbols_mode; string array_used_symbols[]; string array_used_periods[]; bool testing; uchar group1; uchar group2; double g_point; int g_digits; //+------------------------------------------------------------------+
从 EA 的 OnInit() 应对程序中,删除之前测试保留的创建两个时间序列的代码:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Calling the function displays the list of enumeration constants in the journal //--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity //EnumNumbersTest(); //--- Set EA global variables prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"; testing=engine.IsTester(); for(int i=0;i<TOTAL_BUTT;i++) { butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i); butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i); } lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0)); magic_number=InpMagic; stoploss=InpStopLoss; takeprofit=InpTakeProfit; distance_pending=InpDistance; distance_stoplimit=InpDistanceSL; slippage=InpSlippage; trailing_stop=InpTrailingStop*Point(); trailing_step=InpTrailingStep*Point(); trailing_start=InpTrailingStart; stoploss_to_modify=InpStopLossModify; takeprofit_to_modify=InpTakeProfitModify; distance_pending_request=(InpDistancePReq<5 ? 5 : InpDistancePReq); bars_delay_pending_request=(InpBarsDelayPReq<1 ? 1 : InpBarsDelayPReq); g_point=SymbolInfoDouble(NULL,SYMBOL_POINT); g_digits=(int)SymbolInfoInteger(NULL,SYMBOL_DIGITS); //--- Initialize random group numbers group1=0; group2=0; srand(GetTickCount()); //--- Initialize DoEasy library OnInitDoEasy(); //--- Check and remove remaining EA graphical objects if(IsPresentObects(prefix)) ObjectsDeleteAll(0,prefix); //--- Create the button panel if(!CreateButtons(InpButtShiftX,InpButtShiftY)) return INIT_FAILED; //--- Set trailing activation button status ButtonState(butt_data[TOTAL_BUTT-1].name,trailing_on); //--- Reset states of the buttons for working using pending requests for(int i=0;i<14;i++) { ButtonState(butt_data[i].name+"_PRICE",false); ButtonState(butt_data[i].name+"_TIME",false); } //--- 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); } //+------------------------------------------------------------------+
从 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); } } } //+------------------------------------------------------------------+
在 OnInitDoEasy() 函数库初始化函数中,增加创建并显示 EA 中所用时间帧的列表,并在日志中显示函数库初始化时间。
以下是完整的清单,其中修改的部分以颜色突出显示,且代码伴随注释,能更好地理解:
//+------------------------------------------------------------------+ //| Initializing DoEasy library | //+------------------------------------------------------------------+ void OnInitDoEasy() { //--- Check if working with the full list is selected used_symbols_mode=InpModeUsedSymbols; if((ENUM_SYMBOLS_MODE)used_symbols_mode==SYMBOLS_MODE_ALL) { int total=SymbolsTotal(false); string ru_n="\nКоличество символов на сервере "+(string)total+".\nМаксимальное количество: "+(string)SYMBOLS_COMMON_TOTAL+" символов."; string en_n="\nNumber of symbols on server "+(string)total+".\nMaximum number: "+(string)SYMBOLS_COMMON_TOTAL+" symbols."; string caption=TextByLanguage("Внимание!","Attention!"); string ru="Выбран режим работы с полным списком.\nВ этом режиме первичная подготовка списков коллекций символов и таймсерий может занять длительное время."+ru_n+"\nПродолжить?\n\"Нет\" - работа с текущим символом \""+Symbol()+"\""; string en="Full list mode selected.\nIn this mode, the initial preparation of lists of symbol collections and timeseries can take a long time."+en_n+"\nContinue?\n\"No\" - working with the current symbol \""+Symbol()+"\""; string message=TextByLanguage(ru,en); int flags=(MB_YESNO | MB_ICONWARNING | MB_DEFBUTTON2); int mb_res=MessageBox(message,caption,flags); switch(mb_res) { case IDNO : used_symbols_mode=SYMBOLS_MODE_CURRENT; break; default: break; } } //--- Set the counter start point to measure the approximate library initialization time ulong begin=GetTickCount(); Print(TextByLanguage("--- Инициализация библиотеки \"DoEasy\" ---","--- Initializing the \"DoEasy\" library ---")); //--- Fill in the array of used symbols CreateUsedSymbolsArray((ENUM_SYMBOLS_MODE)used_symbols_mode,InpUsedSymbols,array_used_symbols); //--- Set the type of the used symbol list in the symbol collection and fill in the list of symbol timeseries engine.SetUsedSymbols(array_used_symbols); //--- Displaying the selected mode of working with the symbol object collection in the journal string num= ( used_symbols_mode==SYMBOLS_MODE_CURRENT ? ": \""+Symbol()+"\"" : TextByLanguage(". Количество используемых символов: ",". The number of symbols used: ")+(string)engine.GetSymbolsCollectionTotal() ); Print(engine.ModeSymbolsListDescription(),num); //--- Implement displaying the list of used symbols only for MQL5 - MQL4 has no ArrayPrint() function #ifdef __MQL5__ if(InpModeUsedSymbols!=SYMBOLS_MODE_CURRENT) { string array_symbols[]; CArrayObj* list_symbols=engine.GetListAllUsedSymbols(); for(int i=0;i<list_symbols.Total();i++) { CSymbol *symbol=list_symbols.At(i); if(symbol==NULL) continue; ArrayResize(array_symbols,ArraySize(array_symbols)+1,1000); array_symbols[ArraySize(array_symbols)-1]=symbol.Name(); } ArrayPrint(array_symbols); } #endif //--- Set used timeframes CreateUsedTimeframesArray(InpModeUsedTFs,InpUsedTFs,array_used_periods); //--- Display the selected mode of working with the timeseries object collection string mode= ( InpModeUsedTFs==TIMEFRAMES_MODE_CURRENT ? TextByLanguage("Работа только с текущим таймфреймом: ","Work only with the current Period: ")+TimeframeDescription((ENUM_TIMEFRAMES)Period()) : InpModeUsedTFs==TIMEFRAMES_MODE_LIST ? TextByLanguage("Работа с заданным списком таймфреймов:","Work with a predefined list of Periods:") : TextByLanguage("Работа с полным списком таймфреймов:","Work with the full list of all Periods:") ); Print(mode); //--- 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(); int total=list_timeseries.Total(); for(int i=0;i<total;i++) { CTimeSeries *timeseries=list_timeseries.At(i); if(list_timeseries==NULL) continue; int total_periods=ArraySize(array_used_periods); for(int j=0;j<total_periods;j++) { ENUM_TIMEFRAMES timeframe=TimeframeByDescription(array_used_periods[j]); engine.SeriesSyncData(timeseries.Symbol(),timeframe); engine.SeriesCreate(timeseries.Symbol(),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(false); // Full descriptions //--- Create resource text files engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_01",TextByLanguage("Звук упавшей монетки 1","Falling coin 1"),sound_array_coin_01); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_02",TextByLanguage("Звук упавших монеток","Falling coins"),sound_array_coin_02); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_03",TextByLanguage("Звук монеток","Coins"),sound_array_coin_03); engine.CreateFile(FILE_TYPE_WAV,"sound_array_coin_04",TextByLanguage("Звук упавшей монетки 2","Falling coin 2"),sound_array_coin_04); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_01",TextByLanguage("Звук щелчка по кнопке 1","Button click 1"),sound_array_click_01); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_02",TextByLanguage("Звук щелчка по кнопке 2","Button click 2"),sound_array_click_02); engine.CreateFile(FILE_TYPE_WAV,"sound_array_click_03",TextByLanguage("Звук щелчка по кнопке 3","Button click 3"),sound_array_click_03); engine.CreateFile(FILE_TYPE_WAV,"sound_array_cash_machine_01",TextByLanguage("Звук кассового аппарата","Cash machine"),sound_array_cash_machine_01); engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_green",TextByLanguage("Изображение \"Зелёный светодиод\"","Image \"Green Spot lamp\""),img_array_spot_green); engine.CreateFile(FILE_TYPE_BMP,"img_array_spot_red",TextByLanguage("Изображение \"Красный светодиод\"","Image \"Red Spot lamp\""),img_array_spot_red); //--- Pass all existing collections to the trading class engine.TradingOnInit(); //--- Set the default magic number for all used symbols engine.TradingSetMagic(engine.SetCompositeMagicNumber(magic_number)); //--- Set synchronous passing of orders for all used symbols engine.TradingSetAsyncMode(false); //--- Set the number of trading attempts in case of an error engine.TradingSetTotalTry(InpTotalAttempts); //--- Set correct order expiration and filling types to all trading objects engine.TradingSetCorrectTypeExpiration(); engine.TradingSetCorrectTypeFilling(); //--- Set standard sounds for trading objects of all used symbols engine.SetSoundsStandart(); //--- Set the general flag of using sounds engine.SetUseSounds(InpUseSounds); //--- Set the spread multiplier for symbol trading objects in the symbol collection engine.SetSpreadMultiplier(InpSpreadMultiplier); //--- Set controlled values for symbols //--- Get the list of all collection symbols CArrayObj *list=engine.GetListAllUsedSymbols(); if(list!=NULL && list.Total()!=0) { //--- In a loop by the list, set the necessary values for tracked symbol properties //--- By default, the LONG_MAX value is set to all properties, which means "Do not track this property" //--- It can be enabled or disabled (by setting the value less than LONG_MAX or vice versa - set the LONG_MAX value) at any time and anywhere in the program /* for(int i=0;i<list.Total();i++) { CSymbol* symbol=list.At(i); if(symbol==NULL) continue; //--- Set control of the symbol price increase by 100 points symbol.SetControlBidInc(100000*symbol.Point()); //--- Set control of the symbol price decrease by 100 points symbol.SetControlBidDec(100000*symbol.Point()); //--- Set control of the symbol spread increase by 40 points symbol.SetControlSpreadInc(400); //--- Set control of the symbol spread decrease by 40 points symbol.SetControlSpreadDec(400); //--- Set control of the current spread by the value of 40 points symbol.SetControlSpreadLevel(400); } */ } //--- Set controlled values for the current account CAccount* account=engine.GetAccountCurrent(); if(account!=NULL) { //--- Set control of the profit increase to 10 account.SetControlledValueINC(ACCOUNT_PROP_PROFIT,10.0); //--- Set control of the funds increase to 15 account.SetControlledValueINC(ACCOUNT_PROP_EQUITY,15.0); //--- Set profit control level to 20 account.SetControlledValueLEVEL(ACCOUNT_PROP_PROFIT,20.0); } //--- Get the end of the library initialization time counting and display it in the journal ulong end=GetTickCount(); Print(TextByLanguage("Время инициализации библиотеки: ","Library initialization time: "),TimeMSCtoString(end-begin,TIME_MINUTES|TIME_SECONDS)); } //+------------------------------------------------------------------+
这些就是测试 EA 的所有改进。
编译并启动它,在参数中指定当前所用的品种和时间帧。
日志中显示以下消息l:
--- Initializing "DoEasy" library --- Working with the current symbol only: "EURUSD" Working with the current timeframe only: H4 EURUSD symbol timeseries: Timeseries "EURUSD" H4: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5330 Library initialization time: 00:00:00.141
配置采用当前品种和设置中的指定时间帧列表(该列表含有主要时间帧)。
日志中显示以下消息:
--- Initializing "DoEasy" library --- Working with the current symbol only: "EURUSD" Working with the specified timeframe list: "M1" "M5" "M15" "M30" "H1" "H4" "D1" "W1" "MN1" EURUSD symbol timeseries: Timeseries "EURUSD" M1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3286 Timeseries "EURUSD" M5: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3566 Timeseries "EURUSD" M15: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3109 Timeseries "EURUSD" M30: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2894 Timeseries "EURUSD" H1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5505 Timeseries "EURUSD" H4: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5330 Timeseries "EURUSD" D1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5087 Timeseries "EURUSD" W1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2564 Timeseries "EURUSD" MN1: Requested: 1000, Actual: 590, Created: 590, On the server: 590 Library initialization time: 00:00:00.032
配置采用当前品种和完整的时间帧列表.
日志中显示以下消息:
--- Initializing "DoEasy" library --- Working with the current symbol only: "EURUSD" Working with the full list of timeframes: "M1" "M2" "M3" "M4" "M5" "M6" "M10" "M12" "M15" "M20" "M30" "H1" "H2" "H3" "H4" "H6" "H8" "H12" "D1" "W1" "MN1" EURUSD symbol timeseries: Timeseries "EURUSD" M1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3390 Timeseries "EURUSD" M2: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5626 Timeseries "EURUSD" M3: Requested: 1000, Actual: 1000, Created: 1000, On the server: 4713 Timeseries "EURUSD" M4: Requested: 1000, Actual: 1000, Created: 1000, On the server: 4254 Timeseries "EURUSD" M5: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3587 Timeseries "EURUSD" M6: Requested: 1000, Actual: 1000, Created: 1000, On the server: 4805 Timeseries "EURUSD" M10: Requested: 1000, Actual: 1000, Created: 1000, On the server: 4035 Timeseries "EURUSD" M12: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3842 Timeseries "EURUSD" M15: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3116 Timeseries "EURUSD" M20: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3457 Timeseries "EURUSD" M30: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2898 Timeseries "EURUSD" H1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5507 Timeseries "EURUSD" H2: Requested: 1000, Actual: 1000, Created: 1000, On the server: 6303 Timeseries "EURUSD" H3: Requested: 1000, Actual: 1000, Created: 1000, On the server: 6263 Timeseries "EURUSD" H4: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5331 Timeseries "EURUSD" H6: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5208 Timeseries "EURUSD" H8: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5463 Timeseries "EURUSD" H12: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5205 Timeseries "EURUSD" D1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5087 Timeseries "EURUSD" W1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2564 Timeseries "EURUSD" MN1: Requested: 1000, Actual: 590, Created: 590, On the server: 590 Library initialization time: 00:00:00.094
配置采用指定的品种列表,并设置了三个品种 EURUSD,AUDUSD,EURAUD。 另外,配置采用指定的时间帧列表(在列表中指定了主要时间帧)。
日志中显示以下消息:
--- Initializing "DoEasy" library --- Working with predefined symbol list. The number of used symbols: 3 "AUDUSD" "EURUSD" "EURAUD" Working with the specified timeframe list: "M1" "M5" "M15" "M30" "H1" "H4" "D1" "W1" "MN1" AUDUSD symbol timeseries: Timeseries "AUDUSD" M1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3394 Timeseries "AUDUSD" M5: Requested: 1000, Actual: 1000, Created: 1000, On the server: 4024 Timeseries "AUDUSD" M15: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3262 Timeseries "AUDUSD" M30: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3071 Timeseries "AUDUSD" H1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5104 Timeseries "AUDUSD" H4: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5026 Timeseries "AUDUSD" D1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5289 Timeseries "AUDUSD" W1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 1401 Timeseries "AUDUSD" MN1: Requested: 1000, Actual: 323, Created: 323, On the server: 323 EURAUD symbol timeseries: Timeseries "EURAUD" M1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3393 Timeseries "EURAUD" M5: Requested: 1000, Actual: 1000, Created: 1000, On the server: 4025 Timeseries "EURAUD" M15: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3262 Timeseries "EURAUD" M30: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3071 Timeseries "EURAUD" H1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5104 Timeseries "EURAUD" H4: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5026 Timeseries "EURAUD" D1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 4071 Timeseries "EURAUD" W1: Requested: 1000, Actual: 820, Created: 820, On the server: 820 Timeseries "EURAUD" MN1: Requested: 1000, Actual: 189, Created: 189, On the server: 189 EURUSD symbol timeseries: Timeseries "EURUSD" M1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3394 Timeseries "EURUSD" M5: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3588 Timeseries "EURUSD" M15: Requested: 1000, Actual: 1000, Created: 1000, On the server: 3116 Timeseries "EURUSD" M30: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2898 Timeseries "EURUSD" H1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5507 Timeseries "EURUSD" H4: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5331 Timeseries "EURUSD" D1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5087 Timeseries "EURUSD" W1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 2564 Timeseries "EURUSD" MN1: Requested: 1000, Actual: 590, Created: 590, On the server: 590 Library initialization time: 00:00:00.266
如我们所见,所需的时间序列是根据 EA 设置中指定的品种和时间帧创建的。 时间序列的创建时间取决于 EA 的启动(冷启/热启),以及之前是否使用了选定的品种及其时间帧。
在下一篇文章中,我们将创建实时更新已创建时间序列的功能,并为所有用到的时间序列发送有关“新创柱线”事件的消息给控制程序,以及从现有时间序列接收所需数据。
以下附件是函数库当前版本的所有文件,以及测试 EA 文件,供您测试和下载。
请您在评论中留下问题和建议。
返回内容目录
该系列中的先前文章:
DoEasy 函数库中的时间序列(第三十五部分):柱线对象和品种时间序列列表
DoEasy 函数库中的时间序列(第三十六部分):所有用到的品种周期的时间序列对象
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程