本文开始函数库说明的新篇章,从而方便开发 MetaTrader 5/4 终端程序。 第一个系列(共 34 篇文章)专门讨论函数库对象及其互连的概念。 此概念是用来开发操控帐户的功能 — 当前状态和历史记录。
目前,该函数库具有以下功能:
我们还可以告诉函数它应该响应哪些事件,并发送有关受监视事件的通知。
此外,我们已实现了操控终端交易功能的能力。 我们还开发出了新型的交易请求 — 延后交易请求,它令我们能够在各种交易条件下创建程序行为逻辑,并提供创建新型挂单的全套功能。
所有这一切,以及更多内容均已在函数库探讨的前一个系列中得以创建和阐述。
函数库说明的第二个系列会探讨操控价格数据、品种图表、市场深度、指标、等等更多方面。 通常,文章的新系列将致力于开发函数库功能,从而能够快速访问、搜索、比较和排序任何价格数据数组,以及它们的存储对象和来源。
在文章前一个系列的第一篇当中,详细阐述了函数库对象分派、存储和操控的概念。 在本文中,我将简要地复述分派和存储函数库对象的基本原理。
任何一组同类数据都可以表示为含此数据类型固有属性的对象列表。 来自同类对象列表中的每个对象都拥有一套类似属性,尽管相同排序列表中的每个对象都含有不同的值。
存储一组同类对象的每个列表,都可以按列表内对象具有的任何属性进行排序。对象属性具有三个主要类型 — 整数型、实数型和字符串型属性。
在已排序列表中为这些对象排序,令我们能够快速定位任何对象,从其内获取任何数据,并在自定义程序中操控它们。几乎所有函数库对象都能够独立跟踪其自身的属性 — 即我们能为每个感兴趣的对象进行设置,以一定数额来修改它们的数值。 当对象属性按指定值进行修改时,被跟踪对象会发送消息至控制程序,通知已触及设定的跟踪阈值。
所有函数库对象的基础是交易平台提供的基准标准库对象,而对象列表(对象集合)是一个指向 CObject 类实例和其标准库后代的动态指针数组。
在本文中,我将创建 Bar 对象,存储品种图表上单根柱线的全部固有数据,以及单一品种和时间帧内所有柱线对象的列表。
品种图表的每根柱线都有一组在 MqlRates 结构中描述的参数:
除了柱线对象的主要属性外,我们还可以在创建对象时为其设置附加属性:
所有这些属性令我们能够查找范围内柱线和蜡烛的任意组合(存储在柱线列表之中)。 可以为柱线对象列表设置期望的存储数据范围。 默认情况下,我们将在列表中存储一千根柱线(如果少于一千根柱线,或指定了其他范围,则存储品种柱线的全部可用历史记录)。
我们来创建所有柱线对象的属性枚举。 为此,请打开存储在 \MQL5\Include\DoEasy\Defines.mqh 下的 Defines.mqh 文件,并添加柱线类型,整数型,实数型和字符串型柱线对象属性,并将柱线对象列表排序方法清单加到文件末尾:
//+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Info for working with serial data | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Bar type (candle body) | //+------------------------------------------------------------------+ enum ENUM_BAR_BODY_TYPE { BAR_BODY_TYPE_BULLISH, // Bullish bar BAR_BODY_TYPE_BEARISH, // Bearish bar BAR_BODY_TYPE_NULL, // Zero bar BAR_BODY_TYPE_CANDLE_ZERO_BODY, // Candle with a zero body }; //+------------------------------------------------------------------+ //| Bar integer properties | //+------------------------------------------------------------------+ enum ENUM_BAR_PROP_INTEGER { BAR_PROP_INDEX = 0, // Bar index in timeseries BAR_PROP_TYPE, // Bar type (from the ENUM_BAR_BODY_TYPE enumeration) BAR_PROP_PERIOD, // Bar period (timeframe) BAR_PROP_SPREAD, // Bar spread BAR_PROP_VOLUME_TICK, // Bar tick volume BAR_PROP_VOLUME_REAL, // Bar exchange volume BAR_PROP_TIME, // Bar period start time BAR_PROP_TIME_DAY_OF_YEAR, // Bar day serial number in a year BAR_PROP_TIME_YEAR, // A year the bar belongs to BAR_PROP_TIME_MONTH, // A month the bar belongs to BAR_PROP_TIME_DAY_OF_WEEK, // Bar week day BAR_PROP_TIME_DAY, // Bar day of month (number) BAR_PROP_TIME_HOUR, // Bar hour BAR_PROP_TIME_MINUTE, // Bar minute }; #define BAR_PROP_INTEGER_TOTAL (14) // Total number of integer bar properties #define BAR_PROP_INTEGER_SKIP (0) // Number of bar properties not used in sorting //+------------------------------------------------------------------+ //| Real bar properties | //+------------------------------------------------------------------+ enum ENUM_BAR_PROP_DOUBLE { //--- bar data BAR_PROP_OPEN = BAR_PROP_INTEGER_TOTAL, // Bar open price BAR_PROP_HIGH, // Highest price for the bar period BAR_PROP_LOW, // Lowest price for the bar period BAR_PROP_CLOSE, // Bar close price //--- candle data BAR_PROP_CANDLE_SIZE, // Candle size BAR_PROP_CANDLE_SIZE_BODY, // Candle body size BAR_PROP_CANDLE_BODY_TOP, // Candle body top BAR_PROP_CANDLE_BODY_BOTTOM, // Candle body bottom BAR_PROP_CANDLE_SIZE_SHADOW_UP, // Candle upper wick size BAR_PROP_CANDLE_SIZE_SHADOW_DOWN, // Candle lower wick size }; #define BAR_PROP_DOUBLE_TOTAL (10) // Total number of real bar properties #define BAR_PROP_DOUBLE_SKIP (0) // Number of bar properties not used in sorting //+------------------------------------------------------------------+ //| Bar string properties | //+------------------------------------------------------------------+ enum ENUM_BAR_PROP_STRING { BAR_PROP_SYMBOL = (BAR_PROP_INTEGER_TOTAL+BAR_PROP_DOUBLE_TOTAL), // Bar symbol }; #define BAR_PROP_STRING_TOTAL (1) // Total number of string bar properties //+------------------------------------------------------------------+ //| Possible bar sorting criteria | //+------------------------------------------------------------------+ #define FIRST_BAR_DBL_PROP (BAR_PROP_INTEGER_TOTAL-BAR_PROP_INTEGER_SKIP) #define FIRST_BAR_STR_PROP (BAR_PROP_INTEGER_TOTAL-BAR_PROP_INTEGER_SKIP+BAR_PROP_DOUBLE_TOTAL-BAR_PROP_DOUBLE_SKIP) enum ENUM_SORT_BAR_MODE { //--- Sort by integer properties SORT_BY_BAR_INDEX = 0, // Sort by bar index SORT_BY_BAR_TYPE, // Sort by bar type (from the ENUM_BAR_BODY_TYPE enumeration) SORT_BY_BAR_PERIOD, // Sort by bar period (timeframe) SORT_BY_BAR_SPREAD, // Sort by bar spread SORT_BY_BAR_VOLUME_TICK, // Sort by bar tick volume SORT_BY_BAR_VOLUME_REAL, // Sort by bar exchange volume SORT_BY_BAR_TIME, // Sort by bar period start time SORT_BY_BAR_TIME_DAY_OF_YEAR, // Sort by bar day number in a year SORT_BY_BAR_TIME_YEAR, // Sort by a year the bar belongs to SORT_BY_BAR_TIME_MONTH, // Sort by a month the bar belongs to SORT_BY_BAR_TIME_DAY_OF_WEEK, // Sort by a bar week day SORT_BY_BAR_TIME_DAY, // Sort by a bar day SORT_BY_BAR_TIME_HOUR, // Sort by a bar hour SORT_BY_BAR_TIME_MINUTE, // Sort by a bar minute //--- Sort by real properties SORT_BY_BAR_OPEN = FIRST_BAR_DBL_PROP, // Sort by bar open price SORT_BY_BAR_HIGH, // Sort by the highest price for the bar period SORT_BY_BAR_LOW, // Sort by the lowest price for the bar period SORT_BY_BAR_CLOSE, // Sort by a bar close price SORT_BY_BAR_CANDLE_SIZE, // Sort by a candle price SORT_BY_BAR_CANDLE_SIZE_BODY, // Sort by a candle body size SORT_BY_BAR_CANDLE_BODY_TOP, // Sort by a candle body top SORT_BY_BAR_CANDLE_BODY_BOTTOM, // Sort by a candle body bottom SORT_BY_BAR_CANDLE_SIZE_SHADOW_UP, // Sort by candle upper wick size SORT_BY_BAR_CANDLE_SIZE_SHADOW_DOWN, // Sort by candle lower wick size //--- Sort by string properties SORT_BY_BAR_SYMBOL = FIRST_BAR_STR_PROP, // Sort by a bar symbol }; //+------------------------------------------------------------------+
柱线类型可以是看涨、看跌和零。 由于蜡烛实体尺寸是定义各种蜡烛形态的参数之一,因此用单独的柱线类型属性来设置蜡烛实体类型。 如此,零类蜡烛实体尺寸不应视作等效于空白柱线。 零类柱线的所有四个 OHLC 价格均等于同一价格,而零类蜡烛可能有灯芯,在定义蜡烛的形状时要考虑所在位置和灯芯尺寸。
为了显示柱线属性和其他一些函数库消息的说明,我们将需要新的文本消息。
\MQL5\Include\DoEasy\Datas.mqh 中的 Datas.mqh 文件接收新消息的索引:
MSG_LIB_SYS_ERROR_CODE_OUT_OF_RANGE, // Return code out of range of error codes MSG_LIB_SYS_FAILED_CREATE_PAUSE_OBJ, // Failed to create the \"Pause\" object MSG_LIB_SYS_FAILED_CREATE_BAR_OBJ, // Failed to create the \"Bar\" object MSG_LIB_SYS_FAILED_SYNC_DATA, // Failed to synchronize data with the server
...
MSG_LIB_TEXT_TIME_UNTIL_THE_END_DAY, // Order lifetime till the end of the current day to be used MSG_LIB_TEXT_JANUARY, // January MSG_LIB_TEXT_FEBRUARY, // February MSG_LIB_TEXT_MARCH, // March MSG_LIB_TEXT_APRIL, // April MSG_LIB_TEXT_MAY, // May MSG_LIB_TEXT_JUNE, // June MSG_LIB_TEXT_JULY, // July MSG_LIB_TEXT_AUGUST, // August MSG_LIB_TEXT_SEPTEMBER, // September MSG_LIB_TEXT_OCTOBER, // October MSG_LIB_TEXT_NOVEMBER, // November MSG_LIB_TEXT_DECEMBER, // December MSG_LIB_TEXT_SUNDAY, // Sunday
...
MSG_LIB_TEXT_PEND_REQUEST_ADD_CRITERIONS, // Added pending request activation conditions //--- CBar MSG_LIB_TEXT_BAR_FAILED_GET_BAR_DATA, // Failed to receive bar data MSG_LIB_TEXT_BAR_FAILED_GET_SERIES_DATA, // Failed to receive timeseries data MSG_LIB_TEXT_BAR_FAILED_ADD_TO_LIST, // Could not add bar object to the list MSG_LIB_TEXT_BAR, // Bar MSG_LIB_TEXT_BAR_PERIOD, // Timeframe MSG_LIB_TEXT_BAR_SPREAD, // Spread MSG_LIB_TEXT_BAR_VOLUME_TICK, // Tick volume MSG_LIB_TEXT_BAR_VOLUME_REAL, // Exchange volume MSG_LIB_TEXT_BAR_TIME, // Period start time MSG_LIB_TEXT_BAR_TIME_YEAR, // Year MSG_LIB_TEXT_BAR_TIME_MONTH, // Month MSG_LIB_TEXT_BAR_TIME_DAY_OF_YEAR, // Day serial number in a year MSG_LIB_TEXT_BAR_TIME_DAY_OF_WEEK, // Week day MSG_LIB_TEXT_BAR_TIME_DAY, // Two months MSG_LIB_TEXT_BAR_TIME_HOUR, // Hour MSG_LIB_TEXT_BAR_TIME_MINUTE, // Minute MSG_LIB_TEXT_BAR_INDEX, // Index in timeseries MSG_LIB_TEXT_BAR_HIGH, // Highest price for the period MSG_LIB_TEXT_BAR_LOW, // Lowest price for the period MSG_LIB_TEXT_BAR_CANDLE_SIZE, // Candle size MSG_LIB_TEXT_BAR_CANDLE_SIZE_BODY, // Candle body size MSG_LIB_TEXT_BAR_CANDLE_SIZE_SHADOW_UP, // Candle upper wick size MSG_LIB_TEXT_BAR_CANDLE_SIZE_SHADOW_DOWN, // Candle lower wick size MSG_LIB_TEXT_BAR_CANDLE_BODY_TOP, // Candle body top MSG_LIB_TEXT_BAR_CANDLE_BODY_BOTTOM, // Candle body bottom MSG_LIB_TEXT_BAR_TYPE_BULLISH, // Bullish bar MSG_LIB_TEXT_BAR_TYPE_BEARISH, // Bearish bar MSG_LIB_TEXT_BAR_TYPE_NULL, // Zero bar MSG_LIB_TEXT_BAR_TYPE_CANDLE_ZERO_BODY, // Candle with a zero body MSG_LIB_TEXT_BAR_TEXT_FIRS_SET_AMOUNT_DATA, // First, we need to set the required amount of data using SetAmountUsedData() }; //+------------------------------------------------------------------+
以及与新添加的索引相对应的文本消息:
{"Код возврата вне заданного диапазона кодов ошибок","Out of range of error codes return code"}, {"Не удалось создать объект \"Пауза\"","Failed to create \"Pause\" object"}, {"Не удалось создать объект \"Бар\"","Failed to create \"Bar\" object"}, {"Не удалось синхронизировать данные с сервером","Failed to sync data with server"},
...
{"Будет использоваться время действия ордера до конца текущего дня","Order validity time until the end of the current day will be used"}, {"Январь","January"}, {"Февраль","February"}, {"Март","March"}, {"Апрель","April"}, {"Май","May"}, {"Июнь","June"}, {"Июль","July"}, {"Август","August"}, {"Сентябрь","September"}, {"Октябрь","October"}, {"Ноябрь","November"}, {"Декабрь","December"}, {"Воскресение","Sunday"},
...
{"Добавлены условия активации отложенного запроса","Pending request activation conditions added"}, {"Не удалось получить данные бара","Failed to get bar data"}, {"Не удалось получить данные таймсерии","Failed to get timeseries data"}, {"Не удалось добавить объект-бар в список","Failed to add bar object to list"}, {"Бар","Bar"}, {"Таймфрейм","Timeframe"}, {"Спред","Spread"}, {"Тиковый объём","Tick volume"}, {"Биржевой объём","Real volume"}, {"Время начала периода","Period start time"}, {"Год","Year"}, {"Месяц","Month"}, {"Порядковый номер дня в году","Sequence day number in a year"}, {"День недели","Day of week"}, {"День месяца","Day of month"}, {"Час","Hour"}, {"Минута","Minute"}, {"Индекс в таймсерии","Timeseries index"}, {"Наивысшая цена за период","Highest price for the period"}, {"Наименьшая цена за период","Lowest price for the period"}, {"Размер свечи","Candle size"}, {"Размер тела свечи","Candle body size"}, {"Размер верхней тени свечи","Candle upper shadow size"}, {"Размер нижней тени свечи","Candle lower shadow size"}, {"Верх тела свечи","Top of candle body"}, {"Низ тела свечи","Bottom of candle body"}, {"Бычий бар","Bullish bar"}, {"Медвежий бар","Bearish bar"}, {"Нулевой бар","Zero bar"}, {"Свеча с нулевым телом","Candle with zero body"}, {"Сначала нужно установить требуемое количество данных при помощи SetAmountUsedData()","First you need to set required amount of data using SetAmountUsedData()"}, }; //+---------------------------------------------------------------------+
位于 \MQL5\Include\DoEasy\Services\DELib.mqh 的服务函数文件 DELib.mqh 接收函数返回的月份名称和函数返回的时间帧说明:
//+------------------------------------------------------------------+ //| Return month names | //+------------------------------------------------------------------+ string MonthDescription(const int month) { return ( month==1 ? CMessage::Text(MSG_LIB_TEXT_JANUARY) : month==2 ? CMessage::Text(MSG_LIB_TEXT_FEBRUARY) : month==3 ? CMessage::Text(MSG_LIB_TEXT_MARCH) : month==4 ? CMessage::Text(MSG_LIB_TEXT_APRIL) : month==5 ? CMessage::Text(MSG_LIB_TEXT_MAY) : month==6 ? CMessage::Text(MSG_LIB_TEXT_JUNE) : month==7 ? CMessage::Text(MSG_LIB_TEXT_JULY) : month==8 ? CMessage::Text(MSG_LIB_TEXT_AUGUST) : month==9 ? CMessage::Text(MSG_LIB_TEXT_SEPTEMBER) : month==10 ? CMessage::Text(MSG_LIB_TEXT_OCTOBER) : month==11 ? CMessage::Text(MSG_LIB_TEXT_NOVEMBER) : month==12 ? CMessage::Text(MSG_LIB_TEXT_DECEMBER) : (string)month ); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Return timeframe description | //+------------------------------------------------------------------+ string TimeframeDescription(const ENUM_TIMEFRAMES timeframe) { return StringSubstr(EnumToString(timeframe),7); } //+------------------------------------------------------------------+
返回月份名称的函数接收一个月份编号,并根据它返回其文本描述。
返回时间帧名称的函数将接收一个时间帧。 接着,从时间帧相应枚举值的文本表示里提取子字符串,从位置 7 开始,直到字符串的末尾。 得到的结果作为文本返回。 例如,从 PERIOD_H1 小时时间帧的文本表示中提取出值 H1。
若要存储柱线对象类,请在函数库对象目录中创建一个新文件夹 \MQL5\Include\DoEasy\Objects\Series\。 顺便,在新文件夹中创建 CBar 类的 Bar.mqh 文件。
我们来研究类主体清单,及其方法的实现:
//+------------------------------------------------------------------+ //| Bar.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Services\DELib.mqh" //+------------------------------------------------------------------+ //| bar class | //+------------------------------------------------------------------+ class CBar : public CObject { private: MqlDateTime m_dt_struct; // Date structure int m_digits; // Symbol's digits value string m_period_description; // Timeframe string description long m_long_prop[BAR_PROP_INTEGER_TOTAL]; // Integer properties double m_double_prop[BAR_PROP_DOUBLE_TOTAL]; // Real properties string m_string_prop[BAR_PROP_STRING_TOTAL]; // String properties //--- Return the index of the array the bar's (1) double and (2) string properties are located at int IndexProp(ENUM_BAR_PROP_DOUBLE property) const { return(int)property-BAR_PROP_INTEGER_TOTAL; } int IndexProp(ENUM_BAR_PROP_STRING property) const { return(int)property-BAR_PROP_INTEGER_TOTAL-BAR_PROP_DOUBLE_TOTAL; } //--- Return the bar type (bullish/bearish/zero) ENUM_BAR_BODY_TYPE BodyType(void) const; //--- Calculate and return the size of (1) candle, (2) candle body, //--- (3) upper, (4) lower candle wick, //--- (5) candle body top and (6) bottom double CandleSize(void) const { return(this.High()-this.Low()); } double BodySize(void) const { return(this.BodyHigh()-this.BodyLow()); } double ShadowUpSize(void) const { return(this.High()-this.BodyHigh()); } double ShadowDownSize(void) const { return(this.BodyLow()-this.Low()); } double BodyHigh(void) const { return ::fmax(this.Close(),this.Open()); } double BodyLow(void) const { return ::fmin(this.Close(),this.Open()); } //--- Return the (1) year and (2) month the bar belongs to, (3) week day, //--- (4) bar serial number in a year, (5) day, (6) hour, (7) minute, int TimeYear(void) const { return this.m_dt_struct.year; } int TimeMonth(void) const { return this.m_dt_struct.mon; } int TimeDayOfWeek(void) const { return this.m_dt_struct.day_of_week; } int TimeDayOfYear(void) const { return this.m_dt_struct.day_of_year; } int TimeDay(void) const { return this.m_dt_struct.day; } int TimeHour(void) const { return this.m_dt_struct.hour; } int TimeMinute(void) const { return this.m_dt_struct.min; } public: //--- Set bar's (1) integer, (2) real and (3) string properties void SetProperty(ENUM_BAR_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; } void SetProperty(ENUM_BAR_PROP_DOUBLE property,double value){ this.m_double_prop[this.IndexProp(property)]=value; } void SetProperty(ENUM_BAR_PROP_STRING property,string value){ this.m_string_prop[this.IndexProp(property)]=value; } //--- Return (1) integer, (2) real and (3) string bar properties from the properties array long GetProperty(ENUM_BAR_PROP_INTEGER property) const { return this.m_long_prop[property]; } double GetProperty(ENUM_BAR_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)]; } string GetProperty(ENUM_BAR_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(property)]; } //--- Return the flag of the bar supporting the property virtual bool SupportProperty(ENUM_BAR_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_BAR_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_BAR_PROP_STRING property) { return true; } //--- Return itself CBar *GetObject(void) { return &this;} //--- Set (1) bar symbol, timeframe and index, (2) bar object parameters void SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); void SetProperties(const MqlRates &rates); //--- Compare CBar objects by all possible properties (for sorting the lists by a specified bar object property) virtual int Compare(const CObject *node,const int mode=0) const; //--- Compare CBar objects by all properties (to search for equal bar objects) bool IsEqual(CBar* compared_bar) const; //--- Constructors CBar(){;} CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const MqlRates &rates); //+------------------------------------------------------------------+ //| Methods of a simplified access to the order object properties | //+------------------------------------------------------------------+ //--- Return the (1) type, (2) period, (3) spread, (4) tick, (5) exchange volume, //--- (6) bar period start time, (7) year, (8) month the bar belongs to //--- (9) week number since the year start, (10) week number since the month start //--- (11) bar's day, (12) hour, (13) minute, (14) index ENUM_BAR_BODY_TYPE TypeBody(void) const { return (ENUM_BAR_BODY_TYPE)this.GetProperty(BAR_PROP_TYPE); } ENUM_TIMEFRAMES Period(void) const { return (ENUM_TIMEFRAMES)this.GetProperty(BAR_PROP_PERIOD); } int Spread(void) const { return (int)this.GetProperty(BAR_PROP_SPREAD); } long VolumeTick(void) const { return this.GetProperty(BAR_PROP_VOLUME_TICK); } long VolumeReal(void) const { return this.GetProperty(BAR_PROP_VOLUME_REAL); } datetime Time(void) const { return (datetime)this.GetProperty(BAR_PROP_TIME); } long Year(void) const { return this.GetProperty(BAR_PROP_TIME_YEAR); } long Month(void) const { return this.GetProperty(BAR_PROP_TIME_MONTH); } long DayOfWeek(void) const { return this.GetProperty(BAR_PROP_TIME_DAY_OF_WEEK); } long DayOfYear(void) const { return this.GetProperty(BAR_PROP_TIME_DAY_OF_YEAR); } long Day(void) const { return this.GetProperty(BAR_PROP_TIME_DAY); } long Hour(void) const { return this.GetProperty(BAR_PROP_TIME_HOUR); } long Minute(void) const { return this.GetProperty(BAR_PROP_TIME_MINUTE); } long Index(void) const { return this.GetProperty(BAR_PROP_INDEX); } //--- Return bar's (1) Open, (2) High, (3) Low, (4) Close price, //--- size of the (5) candle, (6) body, (7) candle top, (8) bottom, //--- size of the (9) candle upper, (10) lower wick double Open(void) const { return this.GetProperty(BAR_PROP_OPEN); } double High(void) const { return this.GetProperty(BAR_PROP_HIGH); } double Low(void) const { return this.GetProperty(BAR_PROP_LOW); } double Close(void) const { return this.GetProperty(BAR_PROP_CLOSE); } double Size(void) const { return this.GetProperty(BAR_PROP_CANDLE_SIZE); } double SizeBody(void) const { return this.GetProperty(BAR_PROP_CANDLE_SIZE_BODY); } double TopBody(void) const { return this.GetProperty(BAR_PROP_CANDLE_BODY_TOP); } double BottomBody(void) const { return this.GetProperty(BAR_PROP_CANDLE_BODY_BOTTOM); } double SizeShadowUp(void) const { return this.GetProperty(BAR_PROP_CANDLE_SIZE_SHADOW_UP); } double SizeShadowDown(void) const { return this.GetProperty(BAR_PROP_CANDLE_SIZE_SHADOW_DOWN); } //--- Return bar symbol string Symbol(void) const { return this.GetProperty(BAR_PROP_SYMBOL); } //+------------------------------------------------------------------+ //| Descriptions of bar object properties | //+------------------------------------------------------------------+ //--- Get description of a bar's (1) integer, (2) real and (3) string properties string GetPropertyDescription(ENUM_BAR_PROP_INTEGER property); string GetPropertyDescription(ENUM_BAR_PROP_DOUBLE property); string GetPropertyDescription(ENUM_BAR_PROP_STRING property); //--- Return the bar type description string BodyTypeDescription(void) const; //--- Send description of bar properties to the journal (full_prop=true - all properties, false - only supported ones) void Print(const bool full_prop=false); //--- Display a short bar description in the journal virtual void PrintShort(void); //--- Return the bar object short name virtual string Header(void); //--- }; //+------------------------------------------------------------------+
我们要研究类的内容,需刷新有关之前已阐述资料的记忆。
类的私密部分含有:
三个数组,分别存储相应的柱线对象属性 — 整数型、实数型和字符串型值。
计算对象属性位于相应数组中真实索引的方法。
计算并返回其他柱线对象属性的方法。
该类的公开部分含有:
将传递的对象属性值写入整数型、实数型和字符串型属性数组的方法。
从数组中返回所请求的整数型、实数型或字符串型属性值的方法。
虚拟方法返回每个属性是否由对象支持的标志。 这些方法应在柱线对象的衍生对象中实现,如果衍生对象不支持指定的属性,则应返回 false。 在 Bar 对象中,支持所有属性,且方法返回 true。
在第一篇文章当中,我们讨论了函数库对象的整体结构。 在此,我们将简要探究其余类方法的实现。
该类拥有三个构造函数:
1. 默认情况下,不带参数的构造函数用来简单声明类对象,并随后为已创建的对象设置所有必需的参数。
2. 第一个参数构造器接收三个参数 — 品种、时间帧和柱线索引。 基于这三个参数,它调用 CopyRates() 函数的第一种形式,从时间序列中提取单个柱线对象的所有属性:
//+------------------------------------------------------------------+ //| Constructor 1 | //+------------------------------------------------------------------+ CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { MqlRates rates_array[1]; this.SetSymbolPeriod(symbol,timeframe,index); ::ResetLastError(); //--- If failed to write bar data to the array by index or set the time to the time structure //--- display an error message, create and fill the structure with zeros, and write it to the rates_array array if(::CopyRates(symbol,timeframe,index,1,rates_array)<1 || !::TimeToStruct(rates_array[0].time,this.m_dt_struct)) { int err_code=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_BAR_DATA),". ",CMessage::Text(MSG_LIB_SYS_ERROR)," ",CMessage::Text(err_code)," ",CMessage::Retcode(err_code)); MqlRates err={0}; rates_array[0]=err; } //--- Set the bar properties this.SetProperties(rates_array[0]); } //+------------------------------------------------------------------+
创建柱线对象时,此构造函数立即从时间序列中提取一次数据。
3. 第二个参数构造器依据已就绪 MqlRates 结构数组创建柱线对象。
换言之,这意味着在循环中便利 MqlRates 结构数组,并依数组索引创建对象:
//+------------------------------------------------------------------+ //| Constructor 2 | //+------------------------------------------------------------------+ CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const MqlRates &rates) { this.SetSymbolPeriod(symbol,timeframe,index); ::ResetLastError(); //--- If failed to set time to the time structure, display the error message, //--- create and fill the structure with zeros, set the bar properties from this structure and exit if(!::TimeToStruct(rates.time,this.m_dt_struct)) { int err_code=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_BAR_DATA),". ",CMessage::Text(MSG_LIB_SYS_ERROR)," ",CMessage::Text(err_code)," ",CMessage::Retcode(err_code)); MqlRates err={0}; this.SetProperties(err); return; } //--- Set the bar properties this.SetProperties(rates); } //+------------------------------------------------------------------+
此处,除了品种、时间帧和索引之外,还要将指向 MqlRates 结构的链接传递给构造函数。 柱线对象需基于此数据创建。
虚拟方法 Compare() 按指定的属性比较两个对象。 它在标准库的 CObject 基准对象类中定义,且如果两值相等,则应返回零;如果比较值更高/更低,则应返回 1/-1。 标准库的 Search() 方法进行搜索和排序。 该方法应在衍生类中重新定义:
//+------------------------------------------------------------------+ //| Compare CBar objects by all possible properties | //+------------------------------------------------------------------+ int CBar::Compare(const CObject *node,const int mode=0) const { const CBar *bar_compared=node; //--- compare integer properties of two bars if(mode<BAR_PROP_INTEGER_TOTAL) { long value_compared=bar_compared.GetProperty((ENUM_BAR_PROP_INTEGER)mode); long value_current=this.GetProperty((ENUM_BAR_PROP_INTEGER)mode); return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0); } //--- compare real properties of two bars else if(mode<BAR_PROP_DOUBLE_TOTAL+BAR_PROP_INTEGER_TOTAL) { double value_compared=bar_compared.GetProperty((ENUM_BAR_PROP_DOUBLE)mode); double value_current=this.GetProperty((ENUM_BAR_PROP_DOUBLE)mode); return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0); } //--- compare string properties of two bars else if(mode<BAR_PROP_DOUBLE_TOTAL+BAR_PROP_INTEGER_TOTAL+BAR_PROP_STRING_TOTAL) { string value_compared=bar_compared.GetProperty((ENUM_BAR_PROP_STRING)mode); string value_current=this.GetProperty((ENUM_BAR_PROP_STRING)mode); return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0); } return 0; } //+------------------------------------------------------------------+
定义两个相似柱线对象的方法,它是比较两个柱线对象。 仅当两个比较对象的所有字段都相同时才返回 true:
//+------------------------------------------------------------------+ //| Compare CBar objects by all properties | //+------------------------------------------------------------------+ bool CBar::IsEqual(CBar *compared_bar) const { int beg=0, end=BAR_PROP_INTEGER_TOTAL; for(int i=beg; i<end; i++) { ENUM_BAR_PROP_INTEGER prop=(ENUM_BAR_PROP_INTEGER)i; if(this.GetProperty(prop)!=compared_bar.GetProperty(prop)) return false; } beg=end; end+=BAR_PROP_DOUBLE_TOTAL; for(int i=beg; i<end; i++) { ENUM_BAR_PROP_DOUBLE prop=(ENUM_BAR_PROP_DOUBLE)i; if(this.GetProperty(prop)!=compared_bar.GetProperty(prop)) return false; } beg=end; end+=BAR_PROP_STRING_TOTAL; for(int i=beg; i<end; i++) { ENUM_BAR_PROP_STRING prop=(ENUM_BAR_PROP_STRING)i; if(this.GetProperty(prop)!=compared_bar.GetProperty(prop)) return false; } return true; } //+------------------------------------------------------------------+
在时间序列中设置品种、时间帧和柱线对象索引的方法:
//+------------------------------------------------------------------+ //| Set bar symbol, timeframe and index | //+------------------------------------------------------------------+ void CBar::SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { this.SetProperty(BAR_PROP_INDEX,index); this.SetProperty(BAR_PROP_SYMBOL,symbol); this.SetProperty(BAR_PROP_PERIOD,timeframe); this.m_digits=(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS); this.m_period_description=TimeframeDescription(timeframe); } //+------------------------------------------------------------------+
除了设置上述三个属性之外,该方法还按 m_digits 变量值设置品种价格中的小数位数,并按 m_period_description 变量值设置时间帧文本描述。 创建柱线对象时,只需指定一次即可。
为柱线对象设置所有参数的方法简单地将传递给该方法的 MqlRates 结构中的值写入对象属性,并用相应的方法计算其他对象属性的参数:
//+------------------------------------------------------------------+ //| Set bar object parameters | //+------------------------------------------------------------------+ void CBar::SetProperties(const MqlRates &rates) { this.SetProperty(BAR_PROP_SPREAD,rates.spread); this.SetProperty(BAR_PROP_VOLUME_TICK,rates.tick_volume); this.SetProperty(BAR_PROP_VOLUME_REAL,rates.real_volume); this.SetProperty(BAR_PROP_TIME,rates.time); this.SetProperty(BAR_PROP_TIME_YEAR,this.TimeYear()); this.SetProperty(BAR_PROP_TIME_MONTH,this.TimeMonth()); this.SetProperty(BAR_PROP_TIME_DAY_OF_YEAR,this.TimeDayOfYear()); this.SetProperty(BAR_PROP_TIME_DAY_OF_WEEK,this.TimeDayOfWeek()); this.SetProperty(BAR_PROP_TIME_DAY,this.TimeDay()); this.SetProperty(BAR_PROP_TIME_HOUR,this.TimeHour()); this.SetProperty(BAR_PROP_TIME_MINUTE,this.TimeMinute()); //--- this.SetProperty(BAR_PROP_OPEN,rates.open); this.SetProperty(BAR_PROP_HIGH,rates.high); this.SetProperty(BAR_PROP_LOW,rates.low); this.SetProperty(BAR_PROP_CLOSE,rates.close); this.SetProperty(BAR_PROP_CANDLE_SIZE,this.CandleSize()); this.SetProperty(BAR_PROP_CANDLE_SIZE_BODY,this.BodySize()); this.SetProperty(BAR_PROP_CANDLE_BODY_TOP,this.BodyHigh()); this.SetProperty(BAR_PROP_CANDLE_BODY_BOTTOM,this.BodyLow()); this.SetProperty(BAR_PROP_CANDLE_SIZE_SHADOW_UP,this.ShadowUpSize()); this.SetProperty(BAR_PROP_CANDLE_SIZE_SHADOW_DOWN,this.ShadowDownSize()); //--- this.SetProperty(BAR_PROP_TYPE,this.BodyType()); } //+------------------------------------------------------------------+
显示所有柱线对象属性描述的方法:
//+------------------------------------------------------------------+ //| Display bar properties in the journal | //+------------------------------------------------------------------+ void CBar::Print(const bool full_prop=false) { ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_BEG)," (",this.Header(),") ============="); int beg=0, end=BAR_PROP_INTEGER_TOTAL; for(int i=beg; i<end; i++) { ENUM_BAR_PROP_INTEGER prop=(ENUM_BAR_PROP_INTEGER)i; if(!full_prop && !this.SupportProperty(prop)) continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("------"); beg=end; end+=BAR_PROP_DOUBLE_TOTAL; for(int i=beg; i<end; i++) { ENUM_BAR_PROP_DOUBLE prop=(ENUM_BAR_PROP_DOUBLE)i; if(!full_prop && !this.SupportProperty(prop)) continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("------"); beg=end; end+=BAR_PROP_STRING_TOTAL; for(int i=beg; i<end; i++) { ENUM_BAR_PROP_STRING prop=(ENUM_BAR_PROP_STRING)i; if(!full_prop && !this.SupportProperty(prop)) continue; ::Print(this.GetPropertyDescription(prop)); } ::Print("============= ",CMessage::Text(MSG_LIB_PARAMS_LIST_END)," (",this.Header(),") =============\n"); } //+------------------------------------------------------------------+
在三个循环中显示对象属性数组每个属性的描述。 如果不支持该属性,且若 full_prop 方法输入为 false(默认),则该属性不会显示在日志里。
在日志中显示柱线对象简短描述的方法:
//+------------------------------------------------------------------+ //| Display a short bar description in the journal | //+------------------------------------------------------------------+ void CBar::PrintShort(void) { int dg=(this.m_digits>0 ? this.m_digits : 1); string params= ( ::TimeToString(this.Time(),TIME_DATE|TIME_MINUTES|TIME_SECONDS)+", "+ "O: "+::DoubleToString(this.Open(),dg)+", "+ "H: "+::DoubleToString(this.High(),dg)+", "+ "L: "+::DoubleToString(this.Low(),dg)+", "+ "C: "+::DoubleToString(this.Close(),dg)+", "+ "V: "+(string)this.VolumeTick()+", "+ (this.VolumeReal()>0 ? "R: "+(string)this.VolumeReal()+", " : "")+ this.BodyTypeDescription() ); ::Print(this.Header(),": ",params); } //+------------------------------------------------------------------+
该方法采用以下格式显示柱线说明
Bar "SYMBOL" H4[INDEX]: YYYY.MM.DD HH:MM:SS, O: X.XXXXX, H: X.XXXXX, L: X.XXXXX, C: X.XXXXX, V: XXXX, BAR_TYPE
例如:
Bar "EURUSD" H4[6]: 2020.02.06 20:00:00, O: 1.09749, H: 1.09828, L: 1.09706, C: 1.09827, V: 3323, Bullish bar
显示柱线简称的方法:
//+------------------------------------------------------------------+ //| Return the bar object short name | //+------------------------------------------------------------------+ string CBar::Header(void) { return ( CMessage::Text(MSG_LIB_TEXT_BAR)+" \""+this.GetProperty(BAR_PROP_SYMBOL)+"\" "+ TimeframeDescription((ENUM_TIMEFRAMES)this.GetProperty(BAR_PROP_PERIOD))+"["+(string)this.GetProperty(BAR_PROP_INDEX)+"]" ); } //+------------------------------------------------------------------+
按以下格式显示柱线名称
Bar "SYMBOL" H4[INDEX]
例如:
Bar "EURUSD" H4[6]
返回柱线对象整数型属性描述的方法:
//+------------------------------------------------------------------+ //| Return the description of the bar integer property | //+------------------------------------------------------------------+ string CBar::GetPropertyDescription(ENUM_BAR_PROP_INTEGER property) { return ( property==BAR_PROP_INDEX ? CMessage::Text(MSG_LIB_TEXT_BAR_INDEX)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BAR_PROP_TYPE ? CMessage::Text(MSG_ORD_TYPE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.BodyTypeDescription() ) : property==BAR_PROP_PERIOD ? CMessage::Text(MSG_LIB_TEXT_BAR_PERIOD)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.m_period_description ) : property==BAR_PROP_SPREAD ? CMessage::Text(MSG_LIB_TEXT_BAR_SPREAD)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BAR_PROP_VOLUME_TICK ? CMessage::Text(MSG_LIB_TEXT_BAR_VOLUME_TICK)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BAR_PROP_VOLUME_REAL ? CMessage::Text(MSG_LIB_TEXT_BAR_VOLUME_REAL)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BAR_PROP_TIME ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS) ) : property==BAR_PROP_TIME_YEAR ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_YEAR)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.Year() ) : property==BAR_PROP_TIME_MONTH ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_MONTH)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+MonthDescription((int)this.Month()) ) : property==BAR_PROP_TIME_DAY_OF_YEAR ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY_OF_YEAR)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)::IntegerToString(this.DayOfYear(),3,'0') ) : property==BAR_PROP_TIME_DAY_OF_WEEK ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY_OF_WEEK)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+DayOfWeekDescription((ENUM_DAY_OF_WEEK)this.DayOfWeek()) ) : property==BAR_PROP_TIME_DAY ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)::IntegerToString(this.Day(),2,'0') ) : property==BAR_PROP_TIME_HOUR ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_HOUR)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)::IntegerToString(this.Hour(),2,'0') ) : property==BAR_PROP_TIME_MINUTE ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_MINUTE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)::IntegerToString(this.Minute(),2,'0') ) : "" ); } //+------------------------------------------------------------------+
该方法传递 整数型属性。 根据其值,返回其相应在 Datas.mqh 文件中设置的文本描述。
返回柱线对象实数型和字符串型属性描述的方法,这与返回柱线对象整数型属性描述的方法类似:
//+------------------------------------------------------------------+ //| Return the description of the bar's real property | //+------------------------------------------------------------------+ string CBar::GetPropertyDescription(ENUM_BAR_PROP_DOUBLE property) { int dg=(this.m_digits>0 ? this.m_digits : 1); return ( property==BAR_PROP_OPEN ? CMessage::Text(MSG_ORD_PRICE_OPEN)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==BAR_PROP_HIGH ? CMessage::Text(MSG_LIB_TEXT_BAR_HIGH)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==BAR_PROP_LOW ? CMessage::Text(MSG_LIB_TEXT_BAR_LOW)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==BAR_PROP_CLOSE ? CMessage::Text(MSG_ORD_PRICE_CLOSE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==BAR_PROP_CANDLE_SIZE ? CMessage::Text(MSG_LIB_TEXT_BAR_CANDLE_SIZE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==BAR_PROP_CANDLE_SIZE_BODY ? CMessage::Text(MSG_LIB_TEXT_BAR_CANDLE_SIZE_BODY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==BAR_PROP_CANDLE_SIZE_SHADOW_UP ? CMessage::Text(MSG_LIB_TEXT_BAR_CANDLE_SIZE_SHADOW_UP)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==BAR_PROP_CANDLE_SIZE_SHADOW_DOWN ? CMessage::Text(MSG_LIB_TEXT_BAR_CANDLE_SIZE_SHADOW_DOWN)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==BAR_PROP_CANDLE_BODY_TOP ? CMessage::Text(MSG_LIB_TEXT_BAR_CANDLE_BODY_TOP)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : property==BAR_PROP_CANDLE_BODY_BOTTOM ? CMessage::Text(MSG_LIB_TEXT_BAR_CANDLE_BODY_BOTTOM)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::DoubleToString(this.GetProperty(property),dg) ) : "" ); } //+------------------------------------------------------------------+ //| Return the description of the bar string property | //+------------------------------------------------------------------+ string CBar::GetPropertyDescription(ENUM_BAR_PROP_STRING property) { return(property==BAR_PROP_SYMBOL ? CMessage::Text(MSG_LIB_PROP_SYMBOL)+": \""+this.GetProperty(property)+"\"" : ""); } //+------------------------------------------------------------------+
返回柱线类型的方法:
//+------------------------------------------------------------------+ //| Return the bar type (bullish/bearish/zero) | //+------------------------------------------------------------------+ ENUM_BAR_BODY_TYPE CBar::BodyType(void) const { return ( this.Close()>this.Open() ? BAR_BODY_TYPE_BULLISH : this.Close()<this.Open() ? BAR_BODY_TYPE_BEARISH : (this.ShadowUpSize()+this.ShadowDownSize()==0 ? BAR_BODY_TYPE_NULL : BAR_BODY_TYPE_CANDLE_ZERO_BODY) ); } //+------------------------------------------------------------------+
此处的一切都很简单:如果收盘价超过开盘价,即为看涨柱线,如果收盘价低于开盘价,则为看跌柱线。 如果两个烛芯都等于零,则此为一根光头光脚的柱线,否则,此为一个零实体一字线蜡烛。
返回柱线说明的方法:
//+------------------------------------------------------------------+ //| Return the bar type description | //+------------------------------------------------------------------+ string CBar::BodyTypeDescription(void) const { return ( this.BodyType()==BAR_BODY_TYPE_BULLISH ? CMessage::Text(MSG_LIB_TEXT_BAR_TYPE_BULLISH) : this.BodyType()==BAR_BODY_TYPE_BEARISH ? CMessage::Text(MSG_LIB_TEXT_BAR_TYPE_BEARISH) : this.BodyType()==BAR_BODY_TYPE_CANDLE_ZERO_BODY ? CMessage::Text(MSG_LIB_TEXT_BAR_TYPE_CANDLE_ZERO_BODY) : CMessage::Text(MSG_LIB_TEXT_BAR_TYPE_NULL) ); } //+------------------------------------------------------------------+
根据柱线类型,该方法返回其在 Datas.mqh 文件中设置的文本描述。
Bar 对象类已准备就绪。 现在,我们可以为所需时间序列的每根必要柱线创建一个柱线对象。 不过,调用 CopyRates() 请求时间序列柱线,仅对象本身不能为我们提供多于接收常规数据的任何显著优势。
为了能够根据需要管理时间序列数据,我们要创建与所需数量、所需时间序列相对应的柱线对象的列表。 如此这般,我们就可以分析列表数据,并搜索分析所需的所有数据。
这意味着我们需要创建一个时间序列列表来存储柱线对象。
此外,我们需要检测创立新柱线,如此即可将另一个柱线对象添加到列表中,且始终拥有一个工具,当任何品种在任何时间帧上有新柱线创立时发出通知,无论其数量如何。
在创建存储柱线对象列表之前,要编写“新柱线”类,因为该类对象是柱线列表属性之一。
为了检测新创立柱线事件,要把当前柱线的创立时间与上一次的创立时间进行比较。 如果它们不匹配,则为创立一根新柱线。 在这种情况下,我们需要将新创立时间保存为上一次创立时间,以便后续进行比较:
NewBar = false; if(PrevTime != Time) { NewBar = true; PrevTime = Time; }
在这种情况下,新柱线事件将开放一次,而所有后续指令都会在新柱线上执行。
然而,有时我们需要在“新柱线”之后精确执行某些指令。 在所有指令完成之前,事件应保持相关性。 为达此目的,我们需要在新柱线出现之时,为前一个分配新值之前,执行所有应完成的指令:
NewBar = false; if(PrevTime != Time) { NewBar = true; // ... commands to be // ... executed // ... when a new bar appears PrevTime = Time; }
因此,第一选项可以作为独立函数执行,并返回新的柱线创立标志。 当前版本中的第二个选项应该是 OnTick() 处理程序的一部分,最好位于一开始,以便在新柱线出现时立即执行所有指令。 然后,它们将始终跟随所有执行的指令。
在最简单的情况下,这足以控制新柱线的创立。
不过,这还不足以满足函数库的需求 — 我们需要在两个描述的选项中为每个品种和时间帧的新柱线分别单独定义:
如果在当前品种和时间帧内请求数据,则不建议调用时间序列更新函数。 由于历史数据与指标是在同一线程中更新的,因此这种请求可能会引起冲突。 所以,请确保检查是否您要从当前品种或时间帧获取时间序列数据。 如果是,则采用另一种方法应对此类请求,而非调用 SeriesInfoInteger() 和其他返回序列数据的函数,调用该函数将启动历史记录的加载。
这样的方法是存在的,且非常简单:
指标的 OnCalculate() 处理程序参数已含有必要的变量:
int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[])
这些指标令您可以采用简单的计算来跟踪历史数据的变化:
在指标中,要将当前品种和时间帧的 time[] 数组中的柱线时间传递给类方法,以便定义新柱线,并为其指定时间。 在其他情况下,我们会在类方法中取得时间。
在 \MQL5\Include\DoEasy\Objects\Series\,创建新文件 NewBarObj.mqh,其内是 CNewBarObj 类:
//+------------------------------------------------------------------+ //| NewBarObj.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| "New bar" object class | //+------------------------------------------------------------------+ class CNewBarObj { private: string m_symbol; // Symbol ENUM_TIMEFRAMES m_timeframe; // Timeframe datetime m_new_bar_time; // New bar time for auto time management datetime m_prev_time; // Previous time for auto time management datetime m_new_bar_time_manual; // New bar time for manual time management datetime m_prev_time_manual; // Previous time for manual time management //--- Return the current bar data datetime GetLastBarDate(const datetime time); public: //--- Set (1) symbol and (2) timeframe void SetSymbol(const string symbol) { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); } void SetPeriod(const ENUM_TIMEFRAMES timeframe) { this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe); } //--- Save the new bar time during the manual time management void SaveNewBarTime(const datetime time) { this.m_prev_time_manual=this.GetLastBarDate(time); } //--- Return (1) symbol and (2) timeframe string Symbol(void) const { return this.m_symbol; } ENUM_TIMEFRAMES Period(void) const { return this.m_timeframe; } //--- Return the (1) new bar time datetime TimeNewBar(void) const { return this.m_new_bar_time; } //--- Return the new bar opening flag during the time (1) auto, (2) manual management bool IsNewBar(const datetime time); bool IsNewBarManual(const datetime time); //--- Constructors CNewBarObj(void) : m_symbol(::Symbol()), m_timeframe((ENUM_TIMEFRAMES)::Period()), m_prev_time(0),m_new_bar_time(0), m_prev_time_manual(0),m_new_bar_time_manual(0) {} CNewBarObj(const string symbol,const ENUM_TIMEFRAMES timeframe); }; //+------------------------------------------------------------------+
我想,这里的一切都很简单:
在类私密部分中声明的成员变量,存储品种和时间帧,其作用是为定义对象的 “New bar” 事件,
存储新柱线创立时间和前一根创立时间的变量,分别由自动和手动时间管理(我们已经讨论过为什么需要这样做)。
下面将予以研究的 GetLastBarDate() 方法返回新柱线创立时间。
在类的公开部分中声明并实现了 SaveNewBarTime() 方法,可将新柱线时间以手动时间管理保存为上一次时间。 它允许函数库用户在完成所有新操作后保存新柱线的时间。
其余的方法是不言自明的,故于此没有必要再赘述。
该类含有两个构造函数。 第一个构造函数没有参数。 在其初始化清单里需设置当前品种和时间帧,而所有新柱线的时间值,和先前柱线的创立时间均被重置。 创建此对象后,我们需要单独调用为所创建类对象设置必要品种和时间帧的方法。
第二个构造函数是参数化的。 将必要的品种和时间帧立即发送给它:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CNewBarObj::CNewBarObj(const string symbol,const ENUM_TIMEFRAMES timeframe) : m_symbol(symbol),m_timeframe(timeframe) { this.m_prev_time=this.m_prev_time_manual=this.m_new_bar_time=this.m_new_bar_time_manual=0; } //+------------------------------------------------------------------+
需传递给创建类对象的品种和时间帧参数,在其初始清单中设置。 然后在类主体中,将所有时间变量设置为零值。
方法返回自动时间管理期间新柱线的创立标记:
//+------------------------------------------------------------------+ //| Return new bar opening flag | //+------------------------------------------------------------------+ bool CNewBarObj::IsNewBar(const datetime time) { //--- Get the current bar time datetime tm=this.GetLastBarDate(time); //--- If the previous and current time are equal to zero, this is the first launch if(this.m_prev_time==0 && this.m_new_bar_time==0) { //--- set the new bar opening time, //--- set the previous bar time as the current one and return 'false' this.m_new_bar_time=this.m_prev_time=tm; return false; } //--- If the previous time is not equal to the current bar open time, this is a new bar if(this.m_prev_time!=tm) { //--- set the new bar opening time, //--- set the previous time as the current one and return 'true' this.m_new_bar_time=this.m_prev_time=tm; return true; } //--- in other cases, return 'false' return false; } //+------------------------------------------------------------------+
依据对象所设品种或时间帧,若有新创柱线时,该方法都会返回 true。
方法返回手动时间管理期间新创柱线的标记:
//+------------------------------------------------------------------+ //| Return the new bar opening flag during the manual management | //+------------------------------------------------------------------+ bool CNewBarObj::IsNewBarManual(const datetime time) { //--- Get the current bar time datetime tm=this.GetLastBarDate(time); //--- If the previous and current time are equal to zero, this is the first launch if(this.m_prev_time_manual==0 && this.m_new_bar_time_manual==0) { //--- set the new bar opening time, //--- set the previous bar time as the current one and return 'false' this.m_new_bar_time_manual=this.m_prev_time_manual=tm; return false; } //--- If the previous time is not equal to the current bar open time, this is a new bar if(this.m_prev_time_manual!=tm) { //--- set the new bar opening time and return 'true' //--- Save the previous time as the current one from the program using the SaveNewBarTime() method //--- Till the previous time is forcibly set as the current one from the program, //--- the method returns the new bar flag allowing the completion of all the necessary actions on the new bar. this.m_new_bar_time=tm; return true; } //--- in other cases, return 'false' return false; } //+------------------------------------------------------------------+
与自动时间管理不同,该方法不会将当前新创柱线时间覆盖前次柱线时间变量。 如此,检测到“新柱线”事件之后,就可以在每次新即时报价来临时发送新创柱线的标志,直到用户确定柱线新创时要执行的所有动作均已完成,且必须将新柱线创立时间保存为前次时间。
两种方法均需传递新柱线时间作为输入。 然后,调用 GetLastBarDate() 方法定义采用哪个时间:
//+------------------------------------------------------------------+ //| Return the current bar time | //+------------------------------------------------------------------+ datetime CNewBarObj::GetLastBarDate(const datetime time) { return ( ::MQLInfoInteger(MQL_PROGRAM_TYPE)==PROGRAM_INDICATOR && this.m_symbol==::Symbol() && this.m_timeframe==::Period() ? time : (datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_LASTBAR_DATE) ); } //+------------------------------------------------------------------+
如果这是一个指标, “新柱线”对象的品种和时间帧都与当前品种和时间帧一致,返回传递给该方法的时间(在指标中,时间出现在 OnCalculate() 参数中,即 time[] 数组 — 这是一个数组,应将该时间数组传递到定义新柱线的方法),否则调用 SeriesInfoInteger() 接收最后一次柱线创立时间 — 在这种情况下,可以传递任何值代替时间。
“新柱线”对象类已准备就绪,可以满足我们当前的需求。 现在,是时候创建柱线对象列表了。
柱线对象列表本质上是历史时间序列数据的片段,是 MqlRates 的一部分。 为什么我们需要单独的列表?
首先,我们能得到快速排序、搜索和比较功能。 其次,除了 MqlRates 结构字段外,在列表中存储的指定数量柱线对象,还提供了其他字段值,这些值在以后能简化不同蜡烛形式的搜索。
对于时间序列列表(柱线对象的列表),我们打算利用标准库的指向 CObject 类及其衍生实例的指针动态数组。 在本文中,我仅将实现柱线对象的列表,仅针对一对品种和时间帧存储时间序列的柱线。 在将来的文章中,我打算利用该列表作为基础,针对用户程序中用到的每个单一品种,按时间帧建立时间序列集合。 因此,我们将拥有多个相同类型的时间序列集合列表,在其中可以快速搜索必要的信息,从而与函数库用户可用的其他时间序列集合列表进行分析和比较。
柱线对象的每个列表都应含有用户定义数量的柱线对象(历史深度)。 默认情况下,所有列表的历史深度尺寸为 1000 根柱线。 在构造之前,每个列表都应考虑与交易服务器的数据同步。 每个时间序列的列表都应包含一个数值,该值指定可用的历史记录柱线数量。 此值由 Bars() 函数返回,无需参数。 如果返回零,则历史记录尚未同步。 在等待与服务器同步数据时,我们将进行若干次间隔时间很短的尝试。
在 Defines.mqh 文件中,创建宏替换,定义所应用历史记录深度,尝试与服务器同步历史记录的间隔毫秒数,和获取同步事件的尝试次数:
//--- Pending request type IDs #define PENDING_REQUEST_ID_TYPE_ERR (1) // Type of a pending request created based on the server return code #define PENDING_REQUEST_ID_TYPE_REQ (2) // Type of a pending request created by request //--- Timeseries parameters #define SERIES_DEFAULT_BARS_COUNT (1000) // Required default amount of timeseries data #define PAUSE_FOR_SYNC_ATTEMPTS (16) // Amount of pause milliseconds between synchronization attempts #define ATTEMPTS_FOR_SYNC (5) // Number of attempts to receive synchronization with the server //+------------------------------------------------------------------+ //| Structures | //+------------------------------------------------------------------+
为能快速搜索和排序集合列表,我们在 \MQL5\Include\DoEasy\Services\ 文件夹下的服务函数和 Select.mph 文件里选定 CSelect 类,并在其内创建了这些功能。
在柱线对象列表中添加搜索和排序的方法。
将 Bar 对象类文件包含到清单之中:
//+------------------------------------------------------------------+ //| Select.mqh | //| Copyright 2019, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2019, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include <Arrays\ArrayObj.mqh> #include "..\Objects\Orders\Order.mqh" #include "..\Objects\Events\Event.mqh" #include "..\Objects\Accounts\Account.mqh" #include "..\Objects\Symbols\Symbol.mqh" #include "..\Objects\PendRequest\PendRequest.mqh" #include "..\Objects\Series\Bar.mqh" //+------------------------------------------------------------------+
在类主体当中,添加按 Bar 对象属性进行搜索和排序的方法定义:
//+------------------------------------------------------------------+ //| Class for sorting objects meeting the criterion | //+------------------------------------------------------------------+ class CSelect { private: //--- Method for comparing two values template<typename T> static bool CompareValues(T value1,T value2,ENUM_COMPARER_TYPE mode); public: //+------------------------------------------------------------------+ //| Methods of working with orders | //+------------------------------------------------------------------+ //--- Return the list of orders with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByOrderProperty(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode); //--- Return the order index with the maximum value of the order's (1) integer, (2) real and (3) string properties static int FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property); static int FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property); static int FindOrderMax(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property); //--- Return the order index with the minimum value of the order's (1) integer, (2) real and (3) string properties static int FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_INTEGER property); static int FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_DOUBLE property); static int FindOrderMin(CArrayObj *list_source,ENUM_ORDER_PROP_STRING property); //+------------------------------------------------------------------+ //| Methods of working with events | //+------------------------------------------------------------------+ //--- Return the list of events with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByEventProperty(CArrayObj *list_source,ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode); //--- Return the event index with the maximum value of the event's (1) integer, (2) real and (3) string properties static int FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER property); static int FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE property); static int FindEventMax(CArrayObj *list_source,ENUM_EVENT_PROP_STRING property); //--- Return the event index with the minimum value of the event's (1) integer, (2) real and (3) string properties static int FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_INTEGER property); static int FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_DOUBLE property); static int FindEventMin(CArrayObj *list_source,ENUM_EVENT_PROP_STRING property); //+------------------------------------------------------------------+ //| Methods of working with accounts | //+------------------------------------------------------------------+ //--- Return the list of accounts with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByAccountProperty(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode); //--- Return the event index with the maximum value of the event's (1) integer, (2) real and (3) string properties static int FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property); static int FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property); static int FindAccountMax(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property); //--- Return the event index with the minimum value of the event's (1) integer, (2) real and (3) string properties static int FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_INTEGER property); static int FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_DOUBLE property); static int FindAccountMin(CArrayObj *list_source,ENUM_ACCOUNT_PROP_STRING property); //+------------------------------------------------------------------+ //| Methods of working with symbols | //+------------------------------------------------------------------+ //--- Return the list of symbols with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *BySymbolProperty(CArrayObj *list_source,ENUM_SYMBOL_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *BySymbolProperty(CArrayObj *list_source,ENUM_SYMBOL_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *BySymbolProperty(CArrayObj *list_source,ENUM_SYMBOL_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode); //--- Return the symbol index with the maximum value of the order's (1) integer, (2) real and (3) string properties static int FindSymbolMax(CArrayObj *list_source,ENUM_SYMBOL_PROP_INTEGER property); static int FindSymbolMax(CArrayObj *list_source,ENUM_SYMBOL_PROP_DOUBLE property); static int FindSymbolMax(CArrayObj *list_source,ENUM_SYMBOL_PROP_STRING property); //--- Return the symbol index with the minimum value of the order's (1) integer, (2) real and (3) string properties static int FindSymbolMin(CArrayObj *list_source,ENUM_SYMBOL_PROP_INTEGER property); static int FindSymbolMin(CArrayObj *list_source,ENUM_SYMBOL_PROP_DOUBLE property); static int FindSymbolMin(CArrayObj *list_source,ENUM_SYMBOL_PROP_STRING property); //+------------------------------------------------------------------+ //| Methods of working with pending requests | //+------------------------------------------------------------------+ //--- Return the list of pending requests with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *ByPendReqProperty(CArrayObj *list_source,ENUM_PEND_REQ_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByPendReqProperty(CArrayObj *list_source,ENUM_PEND_REQ_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByPendReqProperty(CArrayObj *list_source,ENUM_PEND_REQ_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode); //--- Return the pending request index with the maximum value of the order's (1) integer, (2) real and (3) string properties static int FindPendReqMax(CArrayObj *list_source,ENUM_PEND_REQ_PROP_INTEGER property); static int FindPendReqMax(CArrayObj *list_source,ENUM_PEND_REQ_PROP_DOUBLE property); static int FindPendReqMax(CArrayObj *list_source,ENUM_PEND_REQ_PROP_STRING property); //--- Return the pending request index with the minimum value of the order's (1) integer, (2) real and (3) string properties static int FindPendReqMin(CArrayObj *list_source,ENUM_PEND_REQ_PROP_INTEGER property); static int FindPendReqMin(CArrayObj *list_source,ENUM_PEND_REQ_PROP_DOUBLE property); static int FindPendReqMin(CArrayObj *list_source,ENUM_PEND_REQ_PROP_STRING property); //+------------------------------------------------------------------+ //| Methods of working with timeseries bars | //+------------------------------------------------------------------+ //--- Return the list of bars with one out of (1) integer, (2) real and (3) string properties meeting a specified criterion static CArrayObj *ByBarProperty(CArrayObj *list_source,ENUM_BAR_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByBarProperty(CArrayObj *list_source,ENUM_BAR_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode); static CArrayObj *ByBarProperty(CArrayObj *list_source,ENUM_BAR_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode); //--- Return the pending request index with the maximum value of the order's (1) integer, (2) real and (3) string properties static int FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_INTEGER property); static int FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_DOUBLE property); static int FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_STRING property); //--- Return the pending request index with the minimum value of the order's (1) integer, (2) real and (3) string properties static int FindBarMin(CArrayObj *list_source,ENUM_BAR_PROP_INTEGER property); static int FindBarMin(CArrayObj *list_source,ENUM_BAR_PROP_DOUBLE property); static int FindBarMin(CArrayObj *list_source,ENUM_BAR_PROP_STRING property); //--- }; //+------------------------------------------------------------------+
在类主体之外添加方法的具体实现:
//+------------------------------------------------------------------+ //| Methods of working with timeseries bar lists | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Return the list of bars with one integer | //| property meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByBarProperty(CArrayObj *list_source,ENUM_BAR_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); ListStorage.Add(list); int total=list_source.Total(); for(int i=0; i<total; i++) { CBar *obj=list_source.At(i); if(!obj.SupportProperty(property)) continue; long obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the list of bars with one real | //| property meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByBarProperty(CArrayObj *list_source,ENUM_BAR_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); ListStorage.Add(list); for(int i=0; i<list_source.Total(); i++) { CBar *obj=list_source.At(i); if(!obj.SupportProperty(property)) continue; double obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the list of bars with one string | //| property meeting the specified criterion | //+------------------------------------------------------------------+ CArrayObj *CSelect::ByBarProperty(CArrayObj *list_source,ENUM_BAR_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode) { if(list_source==NULL) return NULL; CArrayObj *list=new CArrayObj(); if(list==NULL) return NULL; list.FreeMode(false); ListStorage.Add(list); for(int i=0; i<list_source.Total(); i++) { CBar *obj=list_source.At(i); if(!obj.SupportProperty(property)) continue; string obj_prop=obj.GetProperty(property); if(CompareValues(obj_prop,value,mode)) list.Add(obj); } return list; } //+------------------------------------------------------------------+ //| Return the listed bar index | //| with the maximum integer property value | //+------------------------------------------------------------------+ int CSelect::FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_INTEGER property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CBar *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CBar *obj=list_source.At(i); long obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); long obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the listed bar index | //| with the maximum real property value | //+------------------------------------------------------------------+ int CSelect::FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_DOUBLE property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CBar *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CBar *obj=list_source.At(i); double obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); double obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the listed bar index | //| with the maximum string property value | //+------------------------------------------------------------------+ int CSelect::FindBarMax(CArrayObj *list_source,ENUM_BAR_PROP_STRING property) { if(list_source==NULL) return WRONG_VALUE; int index=0; CBar *max_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CBar *obj=list_source.At(i); string obj1_prop=obj.GetProperty(property); max_obj=list_source.At(index); string obj2_prop=max_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,MORE)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the listed bar index | //| with the minimum integer property value | //+------------------------------------------------------------------+ int CSelect::FindBarMin(CArrayObj* list_source,ENUM_BAR_PROP_INTEGER property) { int index=0; CBar *min_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CBar *obj=list_source.At(i); long obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); long obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the listed bar index | //| with the minimum real property value | //+------------------------------------------------------------------+ int CSelect::FindBarMin(CArrayObj* list_source,ENUM_BAR_PROP_DOUBLE property) { int index=0; CBar *min_obj=NULL; int total=list_source.Total(); if(total== 0) return WRONG_VALUE; for(int i=1; i<total; i++) { CBar *obj=list_source.At(i); double obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); double obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+ //| Return the listed bar index | //| with the minimum string property value | //+------------------------------------------------------------------+ int CSelect::FindBarMin(CArrayObj* list_source,ENUM_BAR_PROP_STRING property) { int index=0; CBar *min_obj=NULL; int total=list_source.Total(); if(total==0) return WRONG_VALUE; for(int i=1; i<total; i++) { CBar *obj=list_source.At(i); string obj1_prop=obj.GetProperty(property); min_obj=list_source.At(index); string obj2_prop=min_obj.GetProperty(property); if(CompareValues(obj1_prop,obj2_prop,LESS)) index=i; } return index; } //+------------------------------------------------------------------+
我们在前一个系列的第三篇文章中已经研究过类似的方法,故于此不再赘述。 如果您有任何疑问,请随时在评论中提问。
在 \MQL5\Include\DoEasy\Objects\Series\ 中,创建 CSeries 类的 Series.mqh 文件,并将 CSelect 类文件连接到该文件,还有新创建的 “New Bar” 和 “Bar” 对象类:
//+------------------------------------------------------------------+ //| Series.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Services\Select.mqh" #include "NewBarObj.mqh" #include "Bar.mqh" //+------------------------------------------------------------------+
现在,添加所有必需的类成员变量,并在类主体中声明类方法:
//+------------------------------------------------------------------+ //| Series.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Services\Select.mqh" #include "NewBarObj.mqh" #include "Bar.mqh" //+------------------------------------------------------------------+ //| Timeseries class | //+------------------------------------------------------------------+ class CSeries : public CObject { private: ENUM_PROGRAM_TYPE m_program; // Program type ENUM_TIMEFRAMES m_timeframe; // Timeframe string m_symbol; // Symbol uint m_amount; // Amount of applied timeseries data uint m_bars; // Number of bars in history by symbol and timeframe bool m_sync; // Synchronized data flag CArrayObj m_list_series; // Timeseries list CNewBarObj m_new_bar_obj; // "New bar" object public: //--- Return the timeseries list CArrayObj* GetList(void) { return &m_list_series;} //--- Return the list of bars by selected (1) double, (2) integer and (3) string property fitting a compared condition CArrayObj* GetList(ENUM_BAR_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByBarProperty(this.GetList(),property,value,mode); } CArrayObj* GetList(ENUM_BAR_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByBarProperty(this.GetList(),property,value,mode); } CArrayObj* GetList(ENUM_BAR_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL){ return CSelect::ByBarProperty(this.GetList(),property,value,mode); } //--- Set (1) symbol and timeframe and (2) the number of applied timeseries data void SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe); bool SetAmountUsedData(const uint amount,const uint rates_total); //--- Return (1) symbol, (2) timeframe, (3) number of applied timeseries data, //--- (4) number of bars in the timeseries, the new bar flag with the (5) auto, (6) manual time management string Symbol(void) const { return this.m_symbol; } ENUM_TIMEFRAMES Period(void) const { return this.m_timeframe; } uint AmountUsedData(void) const { return this.m_amount; } uint Bars(void) const { return this.m_bars; } bool IsNewBar(const datetime time) { return this.m_new_bar_obj.IsNewBar(time); } bool IsNewBarManual(const datetime time) { return this.m_new_bar_obj.IsNewBarManual(time); } //--- Return the bar object by index (1) in the list and (2) in the timeseries CBar *GetBarByListIndex(const uint index); CBar *GetBarBySeriesIndex(const uint index); //--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) time, (6) tick volume, (7) real volume, (8) bar spread by index double Open(const uint index,const bool from_series=true); double High(const uint index,const bool from_series=true); double Low(const uint index,const bool from_series=true); double Close(const uint index,const bool from_series=true); datetime Time(const uint index,const bool from_series=true); long TickVolume(const uint index,const bool from_series=true); long RealVolume(const uint index,const bool from_series=true); int Spread(const uint index,const bool from_series=true); //--- Save the new bar time during the manual time management void SaveNewBarTime(const datetime time) { this.m_new_bar_obj.SaveNewBarTime(time); } //--- Synchronize symbol data with server data bool SyncData(const uint amount,const uint rates_total); //--- (1) Create and (2) update the timeseries list int Create(const uint amount=0); void Refresh(const datetime time=0, const double open=0, const double high=0, const double low=0, const double close=0, const long tick_volume=0, const long volume=0, const int spread=0); //--- Constructors CSeries(void); CSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint amount=0); }; //+------------------------------------------------------------------+
接收列表的方法出现在所有对象集合类中,这些方法的开发过程在前一个系列的第三部分中进行了阐述。
我们来审阅已声明方法的清单,并分析其实现。
第一个类构造函数没有参数,可创建当前品种和时间帧的列表:
//+------------------------------------------------------------------+ //| Constructor 1 (current symbol and period timeseries) | //+------------------------------------------------------------------+ CSeries::CSeries(void) : m_bars(0),m_amount(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(),(ENUM_TIMEFRAMES)::Period()); } //+------------------------------------------------------------------+
时间序列当中可用的柱线数量,列表中存储的柱线数,以及与服务器同步数据的标志在初始化清单里被重置。
然后设置程序类型,清除柱线对象列表,并为其设置按柱线索引排序的标志。 再之后,为列表设置当前品种和时间帧。
创建柱线对象列表之后,请确保调用 SetAmountUsedData() 或 SyncData() 方法(其中包含 SetAmountUsedData() 方法)为其设置用到的柱线数量。 如果是指标,请确保将 rates_total 作为第二个参数传递给该方法。
该类的第二个构造函数含有三个输入(品种、时间帧和柱线对象列表大小),还有虑及按指定品种和时间帧创建列表,必要的历史深度:
//+------------------------------------------------------------------+ //| Constructor 2 (specified symbol and period timeseries) | //+------------------------------------------------------------------+ CSeries::CSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint amount=0) : m_bars(0), m_amount(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.SetAmountUsedData(amount,0); } //+------------------------------------------------------------------+
时间序列当中可用的柱线数量,列表中存储的柱线数,以及与服务器同步数据的标志在初始化清单里被重置。
然后设置程序类型,清除柱线对象列表,并为其设置按柱线索引排序的标志。
随后,为列表设置当前品种和时间帧。
最后,调用 SetAmountUsedData() 为柱线对象列表设置所需的柱线数量,以及设置同步标志。 该列表将从构造函数的 amount 输入接收指定的所需历史深度。
创建柱线对象列表之后,请确保调用程序中的 SyncData() 方法检查与服务器的同步。
设置品种和时间帧的方法:
//+------------------------------------------------------------------+ //| Set a symbol and timeframe | //+------------------------------------------------------------------+ void CSeries::SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe) { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe); this.m_new_bar_obj.SetSymbol(this.m_symbol); this.m_new_bar_obj.SetPeriod(this.m_timeframe); } //+------------------------------------------------------------------+
该方法传递一对品种和时间帧,并检查所传递值的有效性。 最终,会用当前品种和时间帧,或者传递给该方法的参数来设置 。 接下来,用新保存在变量中的品种和时间帧设置柱线列表的“新建条”对象。
为柱线对象列表设置所用时间序列数据数的方法:
//+------------------------------------------------------------------+ //| Set the number of required data | //+------------------------------------------------------------------+ bool CSeries::SetAmountUsedData(const uint amount,const uint rates_total) { //--- Set the number of available timeseries bars this.m_bars= ( //--- 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 : ::Bars(this.m_symbol,this.m_timeframe) ); //--- If managed to set the number of available history, set the amount of data in the list: if(this.m_bars>0) { //--- if zero 'amount' 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 'amount' value is passed, //--- use either the 'amount' value or the number of available history bars - the least one of them this.m_amount=(amount==0 ? ::fmin(SERIES_DEFAULT_BARS_COUNT,this.m_bars) : ::fmin(amount,this.m_bars)); return true; } return false; } //+------------------------------------------------------------------+
该方法接收柱线对象列表所需的数据量,和当前时间序列的柱线总数(针对指标)。
然后检查程序类型,并在 m_bars 变量里定义可用历史记录量的来源 — 即可来自传递给方法的参数值(用于当前品种和时间帧上的指标),亦或来自运行环境。 接下来,根据可用和所需历史记录的数量,为 m_amount 变量定义应设置的数值。
与服务器同步品种和时间帧数据的方法:
//+------------------------------------------------------------------+ //|Synchronize symbol and timeframe data with server data | //+------------------------------------------------------------------+ bool CSeries::SyncData(const uint amount,const uint rates_total) { //--- If managed to obtain the available number of bars in the timeseries //--- and return the size of the bar object list, return 'true' this.m_sync=this.SetAmountUsedData(amount,rates_total); if(this.m_sync) return true; //--- Data is not yet synchronized with the server //--- Create a pause object CPause *pause=new CPause(); if(pause==NULL) { ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_PAUSE_OBJ)); return false; } //--- Set the pause duration of 16 milliseconds (PAUSE_FOR_SYNC_ATTEMPTS) and initialize the tick counter pause.SetWaitingMSC(PAUSE_FOR_SYNC_ATTEMPTS); pause.SetTimeBegin(0); //--- Make five (ATTEMPTS_FOR_SYNC) attempts to obtain the available number of bars in the timeseries //--- and set the bar object list size int attempts=0; while(attempts<ATTEMPTS_FOR_SYNC && !::IsStopped()) { //--- If data is currently synchronized with the server if(::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_SYNCHRONIZED)) { //--- if managed to obtain the available number of bars in the timeseries //--- and set the size of the bar object list, break the loop this.m_sync=this.SetAmountUsedData(amount,rates_total); if(this.m_sync) break; } //--- Data is not yet synchronized. //--- If the pause of 16 ms is over if(pause.IsCompleted()) { //--- set the new start of the next waiting for the pause object //--- and increase the attempt counter pause.SetTimeBegin(0); attempts++; } } //--- Remove the pause object and return the m_sync value delete pause; return this.m_sync; } //+------------------------------------------------------------------+
方法逻辑已在代码注释中描述。 我相信,一切都很清晰明了。
清单中详细说明了创建和更新柱线对象列表的方法。 我们从整体探讨它们,以便不占用太多空间来描述它们。 如果您对这些方法有任何疑问,请随时在评论中提问:
//+------------------------------------------------------------------+ //| Create the timeseries list | //+------------------------------------------------------------------+ int CSeries::Create(const uint amount=0) { //--- If the required history depth is not set for the list yet, //--- display the appropriate message and return zero, if(this.m_amount==0) { ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_TEXT_FIRS_SET_AMOUNT_DATA)); return 0; } //--- otherwise, if the passed 'amount' value exceeds zero and is not equal to the one already set, //--- while being lower than the available bar number, //--- set the new value of the required history depth for the list else if(amount>0 && this.m_amount!=amount && amount<this.m_bars) { //--- If failed to set a new value, return zero if(!this.SetAmountUsedData(amount,0)) return 0; } //--- For the rates[] array we are to receive historical data to, //--- set the flag of direction like in the timeseries, //--- clear the bar object list and set the flag of sorting by bar index MqlRates rates[]; ::ArraySetAsSeries(rates,true); this.m_list_series.Clear(); this.m_list_series.Sort(SORT_BY_BAR_INDEX); ::ResetLastError(); //--- Get historical data of the MqlRates structure to the rates[] array starting from the current bar in the amount of m_amount, //--- if failed to get data, display the appropriate message and return zero int copied=::CopyRates(this.m_symbol,this.m_timeframe,0,this.m_amount,rates),err=ERR_SUCCESS; if(copied<1) { err=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_SERIES_DATA)," ",this.m_symbol," ",TimeframeDescription(this.m_timeframe),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)); return 0; } //--- Historical data is received in the rates[] array //--- In the rates[] array loop, for(int i=0; i<copied; i++) { //--- create a new bar object out of the current MqlRates structure by the loop index ::ResetLastError(); CBar* bar=new CBar(this.m_symbol,this.m_timeframe,i,rates[i]); if(bar==NULL) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_BAR_OBJ),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(::GetLastError())); continue; } //--- If failed to add bar object to the list, //--- display the appropriate message with the error description in the journal if(!this.m_list_series.Add(bar)) { err=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_ADD_TO_LIST)," ",bar.Header(),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)); } } //--- Return the size of the created bar object list return this.m_list_series.Total(); } //+------------------------------------------------------------------+ //| 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) { 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; } //--- if the specified timeseries size exceeds one bar, remove the earliest bar if(this.m_list_series.Total()>1) 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]); } //+------------------------------------------------------------------+
为了创建时间序列列表,首先,设置所需历史记录的大小,并调用 SyncData() 方法获取与服务器同步的标志。 接下来,调用 Create() 方法。 若要更新柱线对象列表数据,请确保在每次即时报价来临时调用 Refresh() 方法。 该方法自行检测新创立柱线,并将新创立柱线对象添加到时间序列列表。 从柱线对象列表中删除最早的柱线对象,因此列表大小始终保持在 SyncData() 方法设置的级别。
我们需要从时间序列列表中获取 Bar 对象,从而管理数据。 如果为时间序列列表设置了按索引排序的标记(SORT_BY_BAR_INDEX),则列表中柱线对象的顺序对应于时间序列中真实柱线的位置。 但是,若我们为列表设置了另一个排序标志,则列表中对象的顺序不再与时间序列中实际柱线的位置相对应 — 它们按属性的升序执行列表排列。 因此,我们有两种方法从柱线对象列表中选择对象:一种方法是按其在时间序列中的索引返回一个对象柱线;另一种方法是按其在柱线对象列表中的索引返回一个对象柱线。
我们来研究这两种方法。
按柱线对象列表中的索引返回柱线对象的方法:
//+------------------------------------------------------------------+ //| Return the bar object by index in the list | //+------------------------------------------------------------------+ CBar *CSeries::GetBarByListIndex(const uint index) { return this.m_list_series.At(this.m_list_series.Total()-index-1); } //+------------------------------------------------------------------+
将所需柱线对象的索引传递给该方法。 传递的索引应与时间序列方向相同:零索引表示列表中的最后一个对象。 然而,列表中的对象保存顺序从索引零到 list.Total()-1,即,为了获得列表中的最后的柱线,需按 list.Total()-1 索引请求它,而为了获得图表上最右边的柱线,需按索引 0(反向索引)进行请求。
因此,应以相应的方法重新计算索引:从列表大小中减去所传递索引值减 1,并遵照时间序列,根据索引方向返回已计算索引处的柱线对象。
按时间序列中的索引返回柱线对象的方法:
//+------------------------------------------------------------------+ //| Return the bar object by index in the timeseries | //+------------------------------------------------------------------+ CBar *CSeries::GetBarBySeriesIndex(const uint index) { CArrayObj *list=this.GetList(BAR_PROP_INDEX,index); return(list==NULL || list.Total()==0 ? NULL : list.At(0)); } //+------------------------------------------------------------------+
将所需柱线对象的索引传递给该方法。 传递的索引值应与时间序列方向相同。
若要获得与时间序列中相同的柱线索引处的对象,应以 BAR_PROP_INDEX 属性进行选择。 如果柱线对象列表中存在所需索引的柱线,则列表返回该单个对象。
如果没有这样的对象,则返回 NULL。 若发生错误,这两个方法都将返回 NULL。
按索引从柱线对象列表中返回柱线对象基本属性的方法:
//+------------------------------------------------------------------+ //| Return bar's Open by the index | //+------------------------------------------------------------------+ double CSeries::Open(const uint index,const bool from_series=true) { CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index)); return(bar!=NULL ? bar.Open() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar's High by the timeseries index or the list of bars | //+------------------------------------------------------------------+ double CSeries::High(const uint index,const bool from_series=true) { CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index)); return(bar!=NULL ? bar.High() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar's Low by the timeseries index or the list of bars | //+------------------------------------------------------------------+ double CSeries::Low(const uint index,const bool from_series=true) { CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index)); return(bar!=NULL ? bar.Low() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar's Close by the timeseries index or the list of bars | //+------------------------------------------------------------------+ double CSeries::Close(const uint index,const bool from_series=true) { CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index)); return(bar!=NULL ? bar.Close() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar time by the timeseries index or the list of bars | //+------------------------------------------------------------------+ datetime CSeries::Time(const uint index,const bool from_series=true) { CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index)); return(bar!=NULL ? bar.Time() : 0); } //+-------------------------------------------------------------------+ //|Return bar tick volume by the timeseries index or the list of bars | //+-------------------------------------------------------------------+ long CSeries::TickVolume(const uint index,const bool from_series=true) { CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index)); return(bar!=NULL ? bar.VolumeTick() : WRONG_VALUE); } //+--------------------------------------------------------------------+ //|Return bar real volume by the timeseries index or the list of bars | //+--------------------------------------------------------------------+ long CSeries::RealVolume(const uint index,const bool from_series=true) { CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index)); return(bar!=NULL ? bar.VolumeReal() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar spread by the timeseries index or the list of bars | //+------------------------------------------------------------------+ int CSeries::Spread(const uint index,const bool from_series=true) { CBar *bar=(from_series ? this.GetBarBySeriesIndex(index) : this.GetBarByListIndex(index)); return(bar!=NULL ? bar.Spread() : WRONG_VALUE); } //+------------------------------------------------------------------+
方法接收柱线索引和标志,该标志表示所请求索引对应于时间序列的索引方向(true)。
基于该标志值, 调用 GetBarBySeriesIndex() 或 GetBarByListIndex() 方法来获取柱线对象。 然后返回方法中所请求的属性值。
此处未研究 CSeries 类中其余设置或返回类成员变量值的方法。
为了验证我们在本文中所做的事情,我们需要让外部程序知道所创建的类。 为达此目的,只需将 CSeries 类文件包含到 CEngine 函数库主目标文件中:
//+------------------------------------------------------------------+ //| 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 "TradingControl.mqh" #include "Objects\Series\Series.mqh" //+------------------------------------------------------------------+
现在,我们可以在测试 EA 中定义 CSeries 类型的变量,从而按指定数量的柱线对象创建和使用时间序列列表(在当前实现中,这些列表仅用于测试)。 我们马上行动。
若要执行测试,请使用上一个系列最后一篇文章中的 EA,并将其保存在 \MQL5\Experts\TestDoEasy\Part35\ 目录下,命名为 TestDoEasyPart35 .mq5。
创建两个 CSeries 类的对象变量(用于测试)— M1(2 根柱线),和当前时间帧(10 根柱线)。 在 OnInit() 处理程序中设置所有必要的参数,并显示三个列表:
在 OnTick() 处理程序中,在每次即时报价来临时更新两个时间序列,并显示有关于这两个时间序列列表内两个时间序列上每次创立新柱线的记录 — М1 和当前时间。 除了日志记录,还会在当前时间序列创立新柱线时,播放标准的 “news.wav” 声音。
在 EA 的全局变量列表中,定义两个 CSeries 类型的变量 — 当前时间帧的时间序列列表变量,和 M1 时间序列列表的变量:
//+------------------------------------------------------------------+ //| TestDoEasyPart35.mq5 | //| 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" //--- includes #include <DoEasy\Engine.mqh> //--- enums enum ENUM_BUTTONS { BUTT_BUY, BUTT_BUY_LIMIT, BUTT_BUY_STOP, BUTT_BUY_STOP_LIMIT, BUTT_CLOSE_BUY, BUTT_CLOSE_BUY2, BUTT_CLOSE_BUY_BY_SELL, BUTT_SELL, BUTT_SELL_LIMIT, BUTT_SELL_STOP, BUTT_SELL_STOP_LIMIT, BUTT_CLOSE_SELL, BUTT_CLOSE_SELL2, BUTT_CLOSE_SELL_BY_BUY, BUTT_DELETE_PENDING, BUTT_CLOSE_ALL, BUTT_SET_STOP_LOSS, BUTT_SET_TAKE_PROFIT, BUTT_PROFIT_WITHDRAWAL, BUTT_TRAILING_ALL }; #define TOTAL_BUTT (20) //--- structures struct SDataButt { string name; string text; }; //--- input variables input ushort InpMagic = 123; // Magic number input double InpLots = 0.1; // Lots input uint InpStopLoss = 150; // StopLoss in points input uint InpTakeProfit = 150; // TakeProfit in points input uint InpDistance = 50; // Pending orders distance (points) input uint InpDistanceSL = 50; // StopLimit orders distance (points) input uint InpDistancePReq = 50; // Distance for Pending Request's activate (points) input uint InpBarsDelayPReq = 5; // Bars delay for Pending Request's activate (current timeframe) input uint InpSlippage = 5; // Slippage in points input uint InpSpreadMultiplier = 1; // Spread multiplier for adjusting stop-orders by StopLevel input uchar InpTotalAttempts = 5; // Number of trading attempts sinput double InpWithdrawal = 10; // Withdrawal funds (in tester) sinput uint InpButtShiftX = 0; // Buttons X shift sinput uint InpButtShiftY = 10; // Buttons Y shift input uint InpTrailingStop = 50; // Trailing Stop (points) input uint InpTrailingStep = 20; // Trailing Step (points) input uint InpTrailingStart = 0; // Trailing Start (points) input uint InpStopLossModify = 20; // StopLoss for modification (points) input uint InpTakeProfitModify = 60; // TakeProfit for modification (points) 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 bool InpUseSounds = true; // Use sounds //--- global variables CEngine engine; CSeries series; CSeries series_m1; 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 used_symbols; string array_used_symbols[]; bool testing; uchar group1; uchar group2; double g_point; int g_digits; //+------------------------------------------------------------------+
在 EA 的 OnInit() 处理程序中,为时间序列对象的两个变量设置必要的属性,并显示依据当前时间帧所创建柱线对象列表上的完整数据。 对于 М1,只需显示有关成功创建列表的消息:
//+------------------------------------------------------------------+ //| 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 the M1 timeseries object parameters series_m1.SetSymbolPeriod(Symbol(),PERIOD_M1); //--- If symbol and M1 timeframe data are synchronized if(series_m1.SyncData(2,0)) { //--- create the timeseries list of two bars (the current and previous ones), //--- if the timeseries is created, display the appropriate message in the journal int total=series_m1.Create(2); if(total>0) Print(TextByLanguage("Создана таймсерия М1 с размером ","Created timeseries M1 with size "),(string)total); } //--- Check filling price data on the current symbol and timeframe series.SetSymbolPeriod(Symbol(),(ENUM_TIMEFRAMES)Period()); //--- If symbol and the current timeframe data are synchronized if(series.SyncData(10,0)) { //--- create the timeseries list of ten bars (bars 0 - 9), //--- if the timeseries is created, display three lists: //--- 1. the list of bars sorted by candle size (from bars' High to Low) //--- 2. the list of bars sorted by bar indices (according to their sequence in the timeseries) //--- 3. the full list of all properties of the previous bar object (bar properties with the timeseries index of 1) int total=series.Create(10); if(total>0) { CArrayObj *list=series.GetList(); CBar *bar=NULL; //--- Display short properties of the bar list by the candle size Print("\n",TextByLanguage("Бары, сортированные по размеру свечи от High до Low:","Bars sorted by candle size from High to Low:")); list.Sort(SORT_BY_BAR_CANDLE_SIZE); for(int i=0;i<total;i++) { bar=series.GetBarByListIndex(i); if(bar==NULL) continue; bar.PrintShort(); } //--- Display short properties of the bar list by the timeseries index Print("\n",TextByLanguage("Бары, сортированные по индексу таймсерии:","Bars sorted by timeseries index:")); list.Sort(SORT_BY_BAR_INDEX); for(int i=0;i<total;i++) { bar=series.GetBarByListIndex(i); if(bar==NULL) continue; bar.PrintShort(); } //--- Display all bar 1 properties Print(""); list=CSelect::ByBarProperty(list,BAR_PROP_INDEX,1,EQUAL); if(list.Total()==1) { bar=list.At(0); bar.Print(); } } } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
在 OnTick() 处理程序中,在每次即时报价来临时更新 CSeries 类对象的时间序列列表,而在 “New bar” 事件期间,显示有关两个列表中每次事件的消息。 此外,在当前时间帧播放创立新柱线事件的声音:
//+------------------------------------------------------------------+ //| 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 } //--- Check the update of the current and M1 timeseries series.Refresh(); if(series.IsNewBar(0)) { Print("New bar on ",series.Symbol()," ",TimeframeDescription(series.Period())," ",TimeToString(series.Time(0))); engine.PlaySoundByDescription(SND_NEWS); } series_m1.Refresh(); if(series_m1.IsNewBar(0)) { Print("New bar on ",series_m1.Symbol()," ",TimeframeDescription(series_m1.Period())," ",TimeToString(series_m1.Time(0))); } } //+------------------------------------------------------------------+
编译 EA,并在品种图表上启动它。
首先,创建含两根柱线的 M1 时间序列列表伊始在日志里显示记录,然后显示按蜡烛尺寸在柱线列表里排序,接着, 它显示按时间序列中的柱线索引顺序为柱线列表排序,最后,则是时间序列中索引为 1 处柱线对象的所有属性值:
Account 15585535: Artyom Trishkin (MetaQuotes Software Corp.) 9999.40 USD, 1:100, Demo account MetaTrader 5 Work only with the current symbol. The number of symbols used: 1 Created timeseries M1 with size 2 Bars, sorted by size candle from High to Low: Bar "EURUSD" H1[2]: 2020.02.12 10:00:00, O: 1.09145, H: 1.09255, L: 1.09116, C: 1.09215, V: 2498, Bullish bar Bar "EURUSD" H1[3]: 2020.02.12 09:00:00, O: 1.09057, H: 1.09150, L: 1.09022, C: 1.09147, V: 1773, Bullish bar Bar "EURUSD" H1[1]: 2020.02.12 11:00:00, O: 1.09215, H: 1.09232, L: 1.09114, C: 1.09202, V: 1753, Bearish bar Bar "EURUSD" H1[9]: 2020.02.12 03:00:00, O: 1.09130, H: 1.09197, L: 1.09129, C: 1.09183, V: 1042, Bullish bar Bar "EURUSD" H1[4]: 2020.02.12 08:00:00, O: 1.09108, H: 1.09108, L: 1.09050, C: 1.09057, V: 581, Bearish bar Bar "EURUSD" H1[8]: 2020.02.12 04:00:00, O: 1.09183, H: 1.09197, L: 1.09146, C: 1.09159, V: 697, Bearish bar Bar "EURUSD" H1[5]: 2020.02.12 07:00:00, O: 1.09122, H: 1.09143, L: 1.09096, C: 1.09108, V: 591, Bearish bar Bar "EURUSD" H1[6]: 2020.02.12 06:00:00, O: 1.09152, H: 1.09159, L: 1.09121, C: 1.09122, V: 366, Bearish bar Bar "EURUSD" H1[7]: 2020.02.12 05:00:00, O: 1.09159, H: 1.09177, L: 1.09149, C: 1.09152, V: 416, Bearish bar Bar "EURUSD" H1[0]: 2020.02.12 12:00:00, O: 1.09202, H: 1.09204, L: 1.09181, C: 1.09184, V: 63, Bearish bar Bars, sorted by timeseries index: Bar "EURUSD" H1[9]: 2020.02.12 03:00:00, O: 1.09130, H: 1.09197, L: 1.09129, C: 1.09183, V: 1042, Bullish bar Bar "EURUSD" H1[8]: 2020.02.12 04:00:00, O: 1.09183, H: 1.09197, L: 1.09146, C: 1.09159, V: 697, Bearish bar Bar "EURUSD" H1[7]: 2020.02.12 05:00:00, O: 1.09159, H: 1.09177, L: 1.09149, C: 1.09152, V: 416, Bearish bar Bar "EURUSD" H1[6]: 2020.02.12 06:00:00, O: 1.09152, H: 1.09159, L: 1.09121, C: 1.09122, V: 366, Bearish bar Bar "EURUSD" H1[5]: 2020.02.12 07:00:00, O: 1.09122, H: 1.09143, L: 1.09096, C: 1.09108, V: 591, Bearish bar Bar "EURUSD" H1[4]: 2020.02.12 08:00:00, O: 1.09108, H: 1.09108, L: 1.09050, C: 1.09057, V: 581, Bearish bar Bar "EURUSD" H1[3]: 2020.02.12 09:00:00, O: 1.09057, H: 1.09150, L: 1.09022, C: 1.09147, V: 1773, Bullish bar Bar "EURUSD" H1[2]: 2020.02.12 10:00:00, O: 1.09145, H: 1.09255, L: 1.09116, C: 1.09215, V: 2498, Bullish bar Bar "EURUSD" H1[1]: 2020.02.12 11:00:00, O: 1.09215, H: 1.09232, L: 1.09114, C: 1.09202, V: 1753, Bearish bar Bar "EURUSD" H1[0]: 2020.02.12 12:00:00, O: 1.09202, H: 1.09204, L: 1.09181, C: 1.09184, V: 63, Bearish bar ============= The beginning of the event parameter list (Bar "EURUSD" H1[1]) ============= Timeseries index: 1 Type: Bearish bar Timeframe: H1 Spread: 1 Tick volume: 1753 Real volume: 0 Period start time: 2020.02.12 11:00:00 Sequence day number in the year: 042 Year: 2020 Month: February Day of week: Wednesday Day od month: 12 Hour: 11 Minute: 00 ------ Price open: 1.09215 Highest price for the period: 1.09232 Lowest price for the period: 1.09114 Price close: 1.09202 Candle size: 0.00118 Candle body size: 0.00013 Top of the candle body: 1.09215 Bottom of the candle body: 1.09202 Candle upper shadow size: 0.00017 Candle lower shadow size: 0.00088 ------ Symbol: "EURUSD" ============= End of the parameter list (Bar "EURUSD" H1[1]) =============
现在,我们在测试器内以可视化模式启动 EA,选择 М5,并查看测试器有关创立新柱线的日志消息:
正如我们所见,每隔五条消息就是关于在 М5 上创立一根新柱线,间歇是关于在 М1 上创立一根新柱线的消息。
在下一篇文章中,我们将创建柱线列表的集合类。
以下附件是函数库当前版本的所有文件,以及测试 EA 文件,供您测试和下载。
请您在评论中留下问题和建议。
返回内容目录
前一个系列的文章:
第一部分 概念,数据管理
第二部分 历史订单和成交集合
第三部分 市价单和仓位集合,安排搜索
第四部分 交易事件。 概念
第五部分 交易事件类和集合。 发送事件至程序
第六部分 净持账户事件
第七部分 StopLimit 订单激活事件,为订单和仓位修改事件准备功能
第八部分 订单和仓位修改事件
第九部分 与 MQL4 兼容性— 准备数据
第十部分 与 MQL4 兼容性 - 开仓和挂单激活事件
第十一部分 与 MQL4 兼容性 - 平仓事件
第十二部分 帐户对象类和帐户对象集合
第十三部分 帐户对象事件
第十四部分 品种对象
第十五部分 品种对象集合
第十六部分 品种集合事件
第十七部分 函数库对象的交互性
第十八部分 帐户与任何其他函数库对象的交互性
第十九部分 函数库消息类
第二十部分 创建和存储程序资源
第二十一部分 交易类 - 基本的跨平台交易对象
第二十二部分 交易类 - 基本交易类,限制验证
第二十三部分 交易类 - 基本交易类,有效参数验证
第二十四部分 交易类 - 基本交易类,自动纠正无效参数
第二十五部分 交易类 - 基本交易类,处理交易服务器返回的错误
第二十六部分 操控延后交易请求 - 首次实现(开仓)
第二十七部分 操控延后交易请求 - 下挂单
第二十八部分 操控延后交易请求 - 平单、删除和修改
第二十九部分 操控延后交易请求 - 请求对象类
第三十部分 延后交易请求 - 管理请求对象
第三十一部分 延后交易请求 - 在某些条件下开仓
第三十二部分 延后交易请求 - 在某些条件下挂单
第三十三部分 延后交易请求 - 在某些条件下平仓(全部、部分或由逆向)
第三十四部分 延后交易请求 - 在某些条件下删除/修改订单和持仓
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程