
DoEasy 函数库中的时间序列(第四十七部分):多周期、多品种标准指标
内容
概述
我相信,每个人都可以从交付的常规终端中了解标准指标。 这些指标用当前品种/周期图表来显示相同品种/周期的数据。
我将在本文里着手实现的功能是创建自定义指标的能力,在当前品种/周期图表上基于所有标准指标计算指定品种/周期数据,并显示数据。
在本文中,我将研究创建必要的方法,以便基于标准 AC(加速振荡器)指标创建自定义指标。 所有方法均可由其他标准指标调用,尽管需要进行微小修改 - 我将在随后的文章中实现它们。
我们为缓冲区对象添加新属性,以便创建和标识能够操控标准指标数据的缓冲区对象:
- 一个指标的多个缓冲区的标识符能够用来标识和选择这些缓冲区所属的单个标准指标的所有缓冲区对象。 一个自定义指标可按不同参数调用若干个相同的标准指标(当基于若干标准指标创建复杂的自定义指标时)。 该标识符能够按其所属标准指标来定义每个用到的缓冲区对象。
- 按指标句柄使用缓冲区 — 计算标准指标的每个缓冲区对象都应含有所创建标准指标的句柄,以便操控属于该指标的任何缓冲区对象。
- 按指标类型使用缓冲区 — 在此处指定指标类型,值来自 ENUM_INDICATOR 指标类型枚举。 这也允许按所属标准指标类型的缓冲区对象来定义和选择缓冲区对象。
- 按指标名称使用缓冲区 — 按标准指标的名称应用缓冲区对象,以便显示存储在此处的说明。
除了创建操控标准指标数据的数据库之外,我还略微改进了“新柱线”对象和时间序列类,从而可跟踪跳过的历史柱线,并将“跳过的柱线”事件发送到程序中。
如果出现连接断开,启用/禁用睡眠模式,以及其他需要一定时间才能恢复的异常事件,我们可以看到程序恢复运行后,函数库的数据库中某些历史记录柱线被跳过。 我们来创建跟踪跳过柱线数量,并将“跳过柱线”事件发送到程序的方法,从而用户能够在其程序中处理这种情况。
改进函数库类
首先,我们在 \MQL5\Include\DoEasy\Datas.mqh 里添加所要显示的消息数据。
添加消息 IDs:
MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_PLOT, // Index of the next drawn buffer MSG_LIB_TEXT_BUFFER_TEXT_ID, // Indicator buffer ID MSG_LIB_TEXT_BUFFER_TEXT_IND_HANDLE, // Handle of an indicator using a buffer MSG_LIB_TEXT_BUFFER_TEXT_IND_TYPE, // Type of an indicator using a buffer MSG_LIB_TEXT_BUFFER_TEXT_TIMEFRAME, // Buffer (timeframe) data period MSG_LIB_TEXT_BUFFER_TEXT_STATUS, // Buffer status MSG_LIB_TEXT_BUFFER_TEXT_TYPE, // Buffer type MSG_LIB_TEXT_BUFFER_TEXT_ACTIVE, // Active MSG_LIB_TEXT_BUFFER_TEXT_ARROW_CODE, // Arrow code MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SHIFT, // The vertical shift of the arrows MSG_LIB_TEXT_BUFFER_TEXT_DRAW_BEGIN, // The number of initial bars that are not drawn and values in DataWindow MSG_LIB_TEXT_BUFFER_TEXT_DRAW_TYPE, // Graphical construction type MSG_LIB_TEXT_BUFFER_TEXT_SHOW_DATA, // Display construction values in DataWindow MSG_LIB_TEXT_BUFFER_TEXT_SHIFT, // Indicator graphical construction shift by time axis in bars MSG_LIB_TEXT_BUFFER_TEXT_LINE_STYLE, // Line style MSG_LIB_TEXT_BUFFER_TEXT_LINE_WIDTH, // Line width MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SIZE, // Arrow size MSG_LIB_TEXT_BUFFER_TEXT_COLOR_NUM, // Number of colors MSG_LIB_TEXT_BUFFER_TEXT_COLOR, // Drawing color MSG_LIB_TEXT_BUFFER_TEXT_EMPTY_VALUE, // Empty value for plotting where nothing will be drawn MSG_LIB_TEXT_BUFFER_TEXT_SYMBOL, // Buffer symbol MSG_LIB_TEXT_BUFFER_TEXT_LABEL, // Name of the graphical indicator series displayed in DataWindow MSG_LIB_TEXT_BUFFER_TEXT_IND_NAME, // Name of an indicator using a buffer
和对应于所加 IDs 的文本消息:
{"Индекс следующего по счёту рисуемого буфера","Index of the next drawable buffer"}, {"Идентификатор буферов индикатора","Indicator Buffer Id"}, {"Хэндл индикатора, использующего буфер","Indicator handle that uses buffer"}, {"Тип индикатора, использующего буфер","Indicator type that uses buffer"}, {"Период данных буфера (таймфрейм)","Buffer data Period (Timeframe)"}, {"Статус буфера","Buffer status"}, {"Тип буфера","Buffer type"}, {"Активен","Active"}, {"Код стрелки","Arrow code"}, {"Смещение стрелок по вертикали","Vertical shift of arrows"}, {"Количество начальных баров без отрисовки и значений в DataWindow","Number of initial bars without drawing and values in DataWindow"}, {"Тип графического построения","Type of graphical construction"}, {"Отображение значений построения в окне DataWindow","Display construction values in DataWindow"}, {"Сдвиг графического построения индикатора по оси времени в барах","Shift of indicator plotting along time axis in bars"}, {"Стиль линии отрисовки","Drawing line style "}, {"Толщина линии отрисовки","Thickness of drawing line"}, {"Размер значка стрелки","Arrow icon size"}, {"Количество цветов","Number of colors"}, {"Цвет отрисовки","Index of buffer containing drawing color"}, {"Пустое значение для построения, для которого нет отрисовки","Empty value for plotting, for which there is no drawing"}, {"Символ буфера","Buffer Symbol"}, {"Имя индикаторной графической серии, отображаемое в окне DataWindow","Name of indicator graphical series to display in DataWindow"}, {"Наименование индикатора, использующего буфер","Name of indicator that uses buffer"}, {"Индикаторный буфер с типом графического построения","Indicator buffer with graphic plot type"}, {"Неправильно указано количество буферов индикатора (#property indicator_buffers)","Number of indicator buffers incorrect (#property indicator_buffers)"}, {"Достигнуто максимально возможное количество индикаторных буферов","Maximum number of indicator buffers reached"},
在 \MQL5\Include\DoEasy\Defines.mqh 中为当前任务设置所有必要附属数据。
在“宏替换”部分中,更改存储默认交易尝试数值的常量名称,令其能提供更多信息:
//+------------------------------------------------------------------+ //| Macro substitutions | //+------------------------------------------------------------------+ //--- Describe the function with the error line number #define DFUN_ERR_LINE (__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Page " : ", Line ")+(string)__LINE__+": ") #define DFUN (__FUNCTION__+": ") // "Function description" #define COUNTRY_LANG ("Russian") // Country language #define END_TIME (D'31.12.3000 23:59:59') // End date for account history data requests #define TIMER_FREQUENCY (16) // Minimal frequency of the library timer in milliseconds #define TOTAL_TRADE_TRY (5) // Default number of trading attempts #define IND_COLORS_TOTAL (64) // Maximum possible number of indicator buffer colors #define IND_BUFFERS_MAX (512) // Maximum possible number of indicator buffers //--- Standard sounds
以前,该常量名为 TOTAL_TRY,但没有提供信息。 由于我们可能还有其他常量来指定尝试次数,因此在常量名称里附加归属某个动作(此处为 “TRADE” — 含有交易尝试的隶属关系)的关联性会提供更多信息。 当为其他“尝试次数”添加新的常量时,它可避免更改常量名称的必要。
在可能的时间序列事件的枚举里添加新事件:
//+------------------------------------------------------------------+ //| List of possible timeseries events | //+------------------------------------------------------------------+ enum ENUM_SERIES_EVENT { SERIES_EVENTS_NO_EVENT = SYMBOL_EVENTS_NEXT_CODE, // no event SERIES_EVENTS_NEW_BAR, // "New bar" event SERIES_EVENTS_MISSING_BARS, // "Bars skipped" event }; #define SERIES_EVENTS_NEXT_CODE (SERIES_EVENTS_MISSING_BARS+1) // Code of the next event after the "Bars skipped" event //+------------------------------------------------------------------+
相应地,下一个事件的代码现在基于基于新常数。
我曾提过往缓冲区对象里添加新属性。 我们在缓冲区对象的整数型和字符串型属性的枚举中设置它们:
//+------------------------------------------------------------------+ //| Buffer integer properties | //+------------------------------------------------------------------+ enum ENUM_BUFFER_PROP_INTEGER { BUFFER_PROP_INDEX_PLOT = 0, // Plotted buffer serial number BUFFER_PROP_STATUS, // Buffer status (by drawing style) from the ENUM_BUFFER_STATUS enumeration BUFFER_PROP_TYPE, // Buffer type (from the ENUM_BUFFER_TYPE enumeration) BUFFER_PROP_TIMEFRAME, // Buffer period data (timeframe) BUFFER_PROP_ACTIVE, // Buffer usage flag BUFFER_PROP_DRAW_TYPE, // Graphical construction type (from the ENUM_DRAW_TYPE enumeration) BUFFER_PROP_ARROW_CODE, // Arrow code for DRAW_ARROW style BUFFER_PROP_ARROW_SHIFT, // The vertical shift of the arrows for DRAW_ARROW style BUFFER_PROP_LINE_STYLE, // Line style BUFFER_PROP_LINE_WIDTH, // Line width BUFFER_PROP_DRAW_BEGIN, // The number of initial bars that are not drawn and values in DataWindow BUFFER_PROP_SHOW_DATA, // Flag of displaying construction values in DataWindow BUFFER_PROP_SHIFT, // Indicator graphical construction shift by time axis in bars BUFFER_PROP_COLOR_INDEXES, // Number of colors BUFFER_PROP_COLOR, // Drawing color BUFFER_PROP_INDEX_BASE, // Base data buffer index BUFFER_PROP_INDEX_NEXT_BASE, // Index of the array to be assigned as the next indicator buffer BUFFER_PROP_INDEX_NEXT_PLOT, // Index of the next drawn buffer BUFFER_PROP_ID, // ID of multiple buffers of a single indicator BUFFER_PROP_IND_HANDLE, // Handle of an indicator using a buffer BUFFER_PROP_IND_TYPE, // Type of an indicator using a buffer BUFFER_PROP_NUM_DATAS, // Number of data buffers BUFFER_PROP_INDEX_COLOR, // Color buffer index }; #define BUFFER_PROP_INTEGER_TOTAL (23) // Total number of integer bar properties #define BUFFER_PROP_INTEGER_SKIP (2) // Number of buffer properties not used in sorting //+------------------------------------------------------------------+ //| Buffer real properties | //+------------------------------------------------------------------+ enum ENUM_BUFFER_PROP_DOUBLE { BUFFER_PROP_EMPTY_VALUE = BUFFER_PROP_INTEGER_TOTAL, // Empty value for plotting where nothing will be drawn }; #define BUFFER_PROP_DOUBLE_TOTAL (1) // Total number of real buffer properties #define BUFFER_PROP_DOUBLE_SKIP (0) // Number of buffer properties not used in sorting //+------------------------------------------------------------------+ //| Buffer string properties | //+------------------------------------------------------------------+ enum ENUM_BUFFER_PROP_STRING { BUFFER_PROP_SYMBOL = (BUFFER_PROP_INTEGER_TOTAL+BUFFER_PROP_DOUBLE_TOTAL), // Buffer symbol BUFFER_PROP_LABEL, // Name of the graphical indicator series displayed in DataWindow BUFFER_PROP_IND_NAME, // Name of an indicator using a buffer }; #define BUFFER_PROP_STRING_TOTAL (3) // Total number of string buffer properties //+------------------------------------------------------------------+
将整数型属性的总数从 20 增加到 23,并将字符串型属性的数目从 2 增加到 3。
鉴于我们添加了新属性,因此还需要添加按这些属性进行排序和选择的功能。
在可能的排序标准枚举中添加新的缓冲区对象排序类型:
//+------------------------------------------------------------------+ //| Possible buffer sorting criteria | //+------------------------------------------------------------------+ #define FIRST_BUFFER_DBL_PROP (BUFFER_PROP_INTEGER_TOTAL-BUFFER_PROP_INTEGER_SKIP) #define FIRST_BUFFER_STR_PROP (BUFFER_PROP_INTEGER_TOTAL-BUFFER_PROP_INTEGER_SKIP+BUFFER_PROP_DOUBLE_TOTAL-BUFFER_PROP_DOUBLE_SKIP) enum ENUM_SORT_BUFFER_MODE { //--- Sort by integer properties SORT_BY_BUFFER_INDEX_PLOT = 0, // Sort by the plotted buffer serial number SORT_BY_BUFFER_STATUS, // Sort by buffer drawing style (status) from the ENUM_BUFFER_STATUS enumeration SORT_BY_BUFFER_TYPE, // Sort by buffer type (from the ENUM_BUFFER_TYPE enumeration) SORT_BY_BUFFER_TIMEFRAME, // Sort by the buffer data period (timeframe) SORT_BY_BUFFER_ACTIVE, // Sort by the buffer usage flag SORT_BY_BUFFER_DRAW_TYPE, // Sort by graphical construction type (from the ENUM_DRAW_TYPE enumeration) SORT_BY_BUFFER_ARROW_CODE, // Sort by the arrow code for DRAW_ARROW style SORT_BY_BUFFER_ARROW_SHIFT, // Sort by the vertical shift of the arrows for DRAW_ARROW style SORT_BY_BUFFER_LINE_STYLE, // Sort by the line style SORT_BY_BUFFER_LINE_WIDTH, // Sort by the line width SORT_BY_BUFFER_DRAW_BEGIN, // Sort by the number of initial bars that are not drawn and values in DataWindow SORT_BY_BUFFER_SHOW_DATA, // Sort by the flag of displaying construction values in DataWindow SORT_BY_BUFFER_SHIFT, // Sort by the indicator graphical construction shift by time axis in bars SORT_BY_BUFFER_COLOR_INDEXES, // Sort by a number of attempts SORT_BY_BUFFER_COLOR, // Sort by the drawing color SORT_BY_BUFFER_INDEX_BASE, // Sort by the basic data buffer index SORT_BY_BUFFER_INDEX_NEXT_BASE, // Sort by the index of the array to be assigned as the next indicator buffer SORT_BY_BUFFER_INDEX_NEXT_PLOT, // Sort by the index of the next drawn buffer SORT_BY_BUFFER_ID, // Sort by ID of multiple buffers of a single indicator SORT_BY_BUFFER_IND_HANDLE, // Sort by handle of an indicator using a buffer SORT_BY_BUFFER_IND_TYPE, // Sort by type of an indicator using a buffer //--- Sort by real properties SORT_BY_BUFFER_EMPTY_VALUE = FIRST_BUFFER_DBL_PROP, // Sort by the empty value for plotting where nothing will be drawn //--- Sort by string properties SORT_BY_BUFFER_SYMBOL = FIRST_BUFFER_STR_PROP, // Sort by the buffer symbol SORT_BY_BUFFER_LABEL, // Sort by the name of the graphical indicator series displayed in DataWindow SORT_BY_BUFFER_IND_NAME, // Sort by name of an indicator using a buffer }; //+------------------------------------------------------------------+
为了检测跳过的柱线(例如,在连接断开后),我们需要稍微改进 \MQL5\Include\DoEasy\Objects\Series\NewBarObj.mqh 中的“新柱线”对象类。 我们需要做的就是添加两个 “新柱线”事件之间的柱数计数。 该值超过 1 表示历史记录柱线已被跳过,或在服务器上根本不存在(尚未考虑过这种情况)。
在类的私密部分中,添加四个新的类成员变量,用于存储前一次“新柱线”事件的时间,便于手动和自动时间管控,以及存储两个“新柱线”事件之间的秒数和柱线计数
//+------------------------------------------------------------------+ //| "New bar" object class | //+------------------------------------------------------------------+ class CNewBarObj : public CBaseObj { 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 datetime m_prev_new_bar_time; // Previous new bar time for auto time management datetime m_prev_new_bar_time_manual; // Previous new bar time for manual time management long m_seconds_between; // Number of seconds between two "New bar" events int m_bars_between; // Number of bars between two "New bar" events //--- Return the current bar data datetime GetLastBarDate(const datetime time); public:
在该类的公开部分,重命名设置和返回对象时间范围的方法(以前曾用 Period,但用 Timeframe 来存储时间帧更具参考性),并添加返回新声明变量值的方法:
public: //--- Set (1) symbol and (2) timeframe void SetSymbol(const string symbol) { this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); } void SetTimeframe(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 Timeframe(void) const { return this.m_timeframe; } //--- Return (1) new bar time, (2) previous new bar time, number of (3) seconds, (4) number of bars between the two last events datetime TimeNewBar(void) const { return this.m_new_bar_time; } datetime TimePrevNewBar(void) const { return this.m_prev_new_bar_time; } long SecondsBetweenNewBars(void) const { return this.m_seconds_between; } int BarsBetweenNewBars(void) const { return this.m_bars_between; } //--- 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); }; //+------------------------------------------------------------------+
在参数型类构造函数的初始化清单里,设置秒数和柱线数的初始值,而其他 新变量在构造函数主体中初始化为零:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CNewBarObj::CNewBarObj(const string symbol,const ENUM_TIMEFRAMES timeframe) : m_symbol(symbol), m_timeframe(timeframe), m_seconds_between(0), m_bars_between(0) { this.m_prev_new_bar_time=this.m_prev_new_bar_time_manual=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(tm<=0) return false; //--- If the previous and current time are equal to zero, this is the first launch if(this.m_prev_time+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 less than the current bar open time, this is a new bar if(this.m_prev_time>0 && this.m_prev_time<tm) { this.m_prev_new_bar_time=this.m_prev_time; this.m_seconds_between=tm-m_prev_time; this.m_bars_between=int(this.m_seconds_between/::PeriodSeconds(this.m_timeframe)); //--- 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; } //+------------------------------------------------------------------+
在返回新柱线标志的方法中,在手动管理的情况下,无需计算数据。 跳过柱线上的数据始终会自动计算。 不过,在该方法当中,若是手动管理的情况下我们会保存前一个“新柱线”的时间,并且修复为新柱线分配时间的错误(以前 ,在自动时间管理期间,时间已保存在变量中):
//+------------------------------------------------------------------+ //| 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(tm<=0) return false; //--- If the previous and current time are equal to zero, this is the first launch if(this.m_prev_time_manual+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 less than the current bar open time, this is a new bar if(this.m_prev_time_manual>0 && this.m_prev_time_manual<tm) { this.m_prev_new_bar_time_manual=this.m_prev_time_manual; //--- 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_manual=tm; return true; } //--- in other cases, return 'false' return false; } //+------------------------------------------------------------------+
我们经常可以在终端日志中看到有关函数接收历史柱线错误的记录。 发生这种情况是因为某个品种即使在某个时间帧上没有历史数据,该函数库也会查看整个历史记录。 显示相应的记录,然后系统移至下一个历史柱线。 这样做是为了在操控时间序列时能够调试函数库方法。 即使获取历史数据时出错,但确无必要查看,我将删除这些记录。 为此,Bar 对象类的 \MQL5\Include\DoEasy\Objects\Series\Bar.mqh 应该接收另一个无参数构造函数:
//+------------------------------------------------------------------+ //| Bar class | //+------------------------------------------------------------------+ class CBar : public CBaseObj { 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 time, (2) bar object parameters void SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); 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 datetime time,const string source); CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const MqlRates &rates); //+------------------------------------------------------------------+
按品种创建时间序列列表时,我们将用构造函数来创建属于指定品种时间序列的新柱线对象。 以前,参数化构造函数尝试自行为新创建柱线对象在历史记录中检索需要的数据,且在检索历史记录发生错误时,将调试记录发送到日志。 一个没有参数的简单构造函数将创建一个空的柱线对象,创建成功后,您需要用该数据填充数据。 这会发生在 CSeriesDE 类方法当中。
我们来研究 \MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqh 中需修改的的类清单。
在该类的公开部分,将添加方法,返回指向属于该类时间序列的“新柱线”类对象的指针:
//+------------------------------------------------------------------+ //| Timeseries class | //+------------------------------------------------------------------+ class CSeriesDE : public CBaseObj { private: ENUM_TIMEFRAMES m_timeframe; // Timeframe string m_symbol; // Symbol string m_period_description; // Timeframe string description datetime m_firstdate; // The very first date by a period symbol at the moment datetime m_lastbar_date; // Time of opening the last bar by period symbol uint m_amount; // Amount of applied timeseries data uint m_required; // Required amount of applied timeseries data uint m_bars; // Number of bars in history by symbol and timeframe bool m_sync; // Synchronized data flag CArrayObj m_list_series; // Timeseries list CNewBarObj m_new_bar_obj; // "New bar" object //--- Set the very first date by a period symbol at the moment and the new time of opening the last bar by a period symbol void SetServerDate(void) { this.m_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_FIRSTDATE); this.m_lastbar_date=(datetime)::SeriesInfoInteger(this.m_symbol,this.m_timeframe,SERIES_LASTBAR_DATE); } public: //--- Return (1) itself, (2) timeseries list, (3) timeseries "New bar" object CSeriesDE *GetObject(void) { return &this; } CArrayObj *GetList(void) { return &m_list_series; } CNewBarObj *GetNewBarObj(void) { return &this.m_new_bar_obj; } //--- Return the list of bars by selected (1) double, (2) integer and (3) string property fitting a compared condition
鉴于我们现在已有两个时间序列事件(“新建柱线”和“跳过柱线”),创建时间序列事件,并将其发送到控制程序图表的方法应加以改进。 在方法声明中,添加输入参数,我们将在其中传递要创建和发送的时间序列事件:
//--- Create and send the timeseries event to the control program chart void SendEvent(ENUM_SERIES_EVENT event);
改进位于类主体外部的方法:
//+------------------------------------------------------------------+ //| Create and send the timeseries event | //| to the control program chart | //+------------------------------------------------------------------+ void CSeriesDE::SendEvent(ENUM_SERIES_EVENT event) { if(event==SERIES_EVENTS_NEW_BAR) { int index=CSelect::FindBarMax(this.GetList(),BAR_PROP_TIME); CBar *bar=this.m_list_series.At(index); if(bar==NULL) return; ::EventChartCustom(this.m_chart_id_main,SERIES_EVENTS_NEW_BAR,bar.Time(),this.Timeframe(),this.Symbol()); } else if(event==SERIES_EVENTS_MISSING_BARS) { ::EventChartCustom(this.m_chart_id_main,SERIES_EVENTS_MISSING_BARS,this.m_new_bar_obj.BarsBetweenNewBars(),this.Timeframe(),this.Symbol()); } } //+------------------------------------------------------------------+
在此,取决于传递给方法的值,我们创建必要的事件,并将其发送到控制程序图表。 如果创建了“跳过柱线”事件,则在 EventChartCustom() 函数的 lparam 参数中传递需跳过的历史柱线数量。
为了滤除日志中不必要的历史数据接收错误,我们需要开发一种在时间序列中按时间返回柱线对象的方法:
//+------------------------------------------------------------------+ //| Return the bar object by time in the timeseries | //+------------------------------------------------------------------+ CBar *CSeriesDE::GetBar(const datetime time) { CBar *obj=new CBar(); if(obj==NULL) return NULL; obj.SetSymbolPeriod(this.m_symbol,this.m_timeframe,time); this.m_list_series.Sort(SORT_BY_BAR_TIME); int index=this.m_list_series.Search(obj); delete obj; return this.m_list_series.At(index); } //+------------------------------------------------------------------+
从现在开始,我们在 CBar 类中有了无参数构造函数,我们将用该构造函数创建一个新的柱线对象,从而搜索所需的柱线。
在此,我们只需创建一个临时的空柱线对象,然后设置所需的交易品种,时间帧和柱线时间。
其余的很简单:按时间对柱线对象列表进行排序,然后在列表中搜索其数据与我们的临时柱线对象所设数据相匹配的对象。
Search() 方法返回列表中得到的对象索引,而 At() 方法按索引返回指向对象的指针。 如果未找到对象,则索引的值为 -1,而 At() 返回 NULL 。
现在,\MQL5\Include\DoEasy\Objects\Series\TimeSeriesDE.mqh 中所有现有 CTimeSeriesDE 类时间序列的方法均得以更新,能够检测新柱线事件,以及柱线跳过事件。
我们来改进两种更新时间序列的方法,添加定义“跳过柱线”事件代码块:
//+------------------------------------------------------------------+ //| Update a specified timeseries list | //+------------------------------------------------------------------+ void CTimeSeriesDE::Refresh(const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate) { //--- Reset the timeseries event flag and clear the list of all timeseries events this.m_is_event=false; this.m_list_events.Clear(); //--- Get the timeseries from the list by its timeframe CSeriesDE *series_obj=this.m_list_series.At(this.IndexTimeframe(timeframe)); if(series_obj==NULL || series_obj.DataTotal()==0 || !series_obj.IsAvailable()) return; //--- Update the timeseries list series_obj.Refresh(data_calculate); datetime time= ( this.m_program==PROGRAM_INDICATOR && series_obj.Symbol()==::Symbol() && series_obj.Timeframe()==(ENUM_TIMEFRAMES)::Period() ? data_calculate.rates.time : series_obj.LastBarDate() ); //--- If the timeseries object features the New bar event if(series_obj.IsNewBar(time)) { //--- send the "New bar" event to the control program chart series_obj.SendEvent(SERIES_EVENTS_NEW_BAR); //--- set the values of the first date in history on the server and in the terminal this.SetTerminalServerDate(); //--- add the "New bar" event to the list of timeseries events //--- in case of successful addition, set the event flag for the timeseries if(this.EventAdd(SERIES_EVENTS_NEW_BAR,time,series_obj.Timeframe(),series_obj.Symbol())) this.m_is_event=true; //--- Check skipped bars int missing=series_obj.GetNewBarObj().BarsBetweenNewBars(); if(missing>1) { //--- send the "Bars skipped" event to the control program chart series_obj.SendEvent(SERIES_EVENTS_MISSING_BARS); //--- add the "Bars skipped" event to the list of timeseries events this.EventAdd(SERIES_EVENTS_MISSING_BARS,missing,series_obj.Timeframe(),series_obj.Symbol()); } } } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Update all timeseries lists | //+------------------------------------------------------------------+ void CTimeSeriesDE::RefreshAll(SDataCalculate &data_calculate) { //--- Reset the flags indicating the necessity to set the first date in history on the server and in the terminal //--- and the timeseries event flag, and clear the list of all timeseries events bool upd=false; this.m_is_event=false; this.m_list_events.Clear(); //--- In the loop by the list of all used timeseries, int total=this.m_list_series.Total(); for(int i=0;i<total;i++) { //--- get the next timeseries object by the loop index CSeriesDE *series_obj=this.m_list_series.At(i); if(series_obj==NULL || !series_obj.IsAvailable() || series_obj.DataTotal()==0) continue; //--- update the timeseries list series_obj.Refresh(data_calculate); datetime time= ( this.m_program==PROGRAM_INDICATOR && series_obj.Symbol()==::Symbol() && series_obj.Timeframe()==(ENUM_TIMEFRAMES)::Period() ? data_calculate.rates.time : series_obj.LastBarDate() ); //--- If the timeseries object features the New bar event if(series_obj.IsNewBar(time)) { //--- send the "New bar" event to the control program chart, series_obj.SendEvent(SERIES_EVENTS_NEW_BAR); //--- set the flag indicating the necessity to set the first date in history on the server and in the terminal upd=true; //--- add the "New bar" event to the list of timeseries events //--- in case of successful addition, set the event flag for the timeseries if(this.EventAdd(SERIES_EVENTS_NEW_BAR,time,series_obj.Timeframe(),series_obj.Symbol())) this.m_is_event=true; //--- Check skipped bars int missing=series_obj.GetNewBarObj().BarsBetweenNewBars(); if(missing>1) { //--- send the "Bars skipped" event to the control program chart series_obj.SendEvent(SERIES_EVENTS_MISSING_BARS); //--- add the "Bars skipped" event to the list of timeseries events this.EventAdd(SERIES_EVENTS_MISSING_BARS,missing,series_obj.Timeframe(),series_obj.Symbol()); } } } //--- if the flag indicating the necessity to set the first date in history on the server and in the terminal is enabled, //--- set the values of the first date in history on the server and in the terminal if(upd) this.SetTerminalServerDate(); } //+------------------------------------------------------------------+
在定义“新柱线”事件时,我们调用并将“新柱线”事件传递给先前修改过的方法来创建新的时间序列事件。 如果有缺失的柱线,也会创建相应的事件。
在 \MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqh 中所有时间序列对象的 CTimeSeriesCollection 集合类的公开部分,添加重新创建所有时间序列的方法声明:
//--- (1) Create, (2) re-create a specified timeseries of a specified symbol, (3) re-create all timeseries bool CreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0); bool ReCreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0); bool ReCreateSeriesAll(const int rates_total=0,const uint required=0); //--- Return (1) an empty, (2) partially filled timeseries
我们在类主体之外编写其实现:
//+------------------------------------------------------------------+ //| Re-create all timeseries | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::ReCreateSeriesAll(const int rates_total=0,const uint required=0) { //--- In the loop by all symbol timeseries objects in the collection, int total=this.m_list.Total(); for(int i=0;i<total;i++) { //--- get the next symbol timeseries object CTimeSeriesDE *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; //--- Get the list of all symbol timeseries CArrayObj *list=timeseries.GetListSeries(); if(list==NULL) continue; //--- In a loop by all symbol timeseries int total_series=list.Total(); for(int j=0;j<total_series;j++) { //--- Get the next timeseries CSeriesDE *series=list.At(j); if(series==NULL) continue; //--- check timeseries synchronization and re-create it if(!series.SyncData(required,rates_total)) return false; if(series.Create(required)==0) return false; } } return true; } //+------------------------------------------------------------------+
该方法简单地重新创建集合中所有可用的时间序列。 到目前为止,该方法尚未在任何地方应用,但若有必要重新创建现有时间序列集合,则它会很有用。 例如,当程序用到多个品种/周期时,也许需要定义跳过大量的柱线。 在这种情况下,比之定义每个时间序列中跳过的柱线数,并分别重新创建每个时间序列,调用一个方法来重新创建所有集合时间序列要容易得多。 甚至,只有在恢复与服务器的连接,或在新柱线时才会发生这种情况。
我已经完成了所有准备步骤,略微改善了时间序列和柱线处理。 现在该开始创建操控标准指标的方法了。
操控标准指标的方法
首先,我们改进 \MQL5\Include\DoEasy\Objects\Indicators\Buffer.mqh 中的抽象缓冲区对象类。
在该类的公开部分,添加用于设置 并返回四个新缓冲区对象属性的方法:
//--- Set (1) the arrow code, (2) vertical shift of arrows, (3) symbol, (4) timeframe, (5) buffer activity flag //--- (6) drawing type, (7) number of initial bars without drawing, (8) flag of displaying construction values in DataWindow, //--- (9) shift of the indicator graphical construction along the time axis, (10) line style, (11) line width, //--- (12) total number of colors, (13) one drawing color, (14) color of drawing in the specified color index, //--- (15) drawing colors from the color array, (16) empty value, (17) name of the graphical series displayed in DataWindow virtual void SetArrowCode(const uchar code) { return; } virtual void SetArrowShift(const int shift) { return; } void SetSymbol(const string symbol) { this.SetProperty(BUFFER_PROP_SYMBOL,symbol); } void SetTimeframe(const ENUM_TIMEFRAMES timeframe) { this.SetProperty(BUFFER_PROP_TIMEFRAME,timeframe); } void SetActive(const bool flag) { this.SetProperty(BUFFER_PROP_ACTIVE,flag); } void SetDrawType(const ENUM_DRAW_TYPE draw_type); void SetDrawBegin(const int value); void SetShowData(const bool flag); void SetShift(const int shift); void SetStyle(const ENUM_LINE_STYLE style); void SetWidth(const int width); void SetColorNumbers(const int number); void SetColor(const color colour); void SetColor(const color colour,const uchar index); void SetColors(const color &array_colors[]); void SetEmptyValue(const double value); virtual void SetLabel(const string label); void SetID(const int id) { this.SetProperty(BUFFER_PROP_ID,id); } void SetIndicatorHandle(const int handle) { this.SetProperty(BUFFER_PROP_IND_HANDLE,handle); } void SetIndicatorType(const ENUM_INDICATOR type) { this.SetProperty(BUFFER_PROP_IND_TYPE,type); } void SetIndicatorName(const string name) { this.SetProperty(BUFFER_PROP_IND_NAME,name); } //--- Return (1) the serial number of the drawn buffer, (2) bound array index, (3) color buffer index, //--- (4) index of the first free bound array, (5) index of the next drawn buffer, (6) buffer data period, (7) buffer status, //--- (8) buffer type, (9) buffer usage flag, (10) arrow code, (11) arrow shift for DRAW_ARROW style, //--- (12) number of initial bars that are not drawn and values in DataWindow, (13) graphical construction type, //--- (14) flag of displaying construction values in DataWindow, (15) indicator graphical construction shift along the time axis, //--- (16) drawing line style, (17) drawing line width, (18) number of colors, (19) drawing color, number of buffers for construction //--- (20) set empty value, (21) buffer symbol, (22) name of the indicator graphical series displayed in DataWindow int IndexPlot(void) const { return (int)this.GetProperty(BUFFER_PROP_INDEX_PLOT); } int IndexBase(void) const { return (int)this.GetProperty(BUFFER_PROP_INDEX_BASE); } int IndexColor(void) const { return (int)this.GetProperty(BUFFER_PROP_INDEX_COLOR); } int IndexNextBaseBuffer(void) const { return (int)this.GetProperty(BUFFER_PROP_INDEX_NEXT_BASE); } int IndexNextPlotBuffer(void) const { return (int)this.GetProperty(BUFFER_PROP_INDEX_NEXT_PLOT); } ENUM_TIMEFRAMES Timeframe(void) const { return (ENUM_TIMEFRAMES)this.GetProperty(BUFFER_PROP_TIMEFRAME); } ENUM_BUFFER_STATUS Status(void) const { return (ENUM_BUFFER_STATUS)this.GetProperty(BUFFER_PROP_STATUS); } ENUM_BUFFER_TYPE TypeBuffer(void) const { return (ENUM_BUFFER_TYPE)this.GetProperty(BUFFER_PROP_TYPE); } bool IsActive(void) const { return (bool)this.GetProperty(BUFFER_PROP_ACTIVE); } uchar ArrowCode(void) const { return (uchar)this.GetProperty(BUFFER_PROP_ARROW_CODE); } int ArrowShift(void) const { return (int)this.GetProperty(BUFFER_PROP_ARROW_SHIFT); } int DrawBegin(void) const { return (int)this.GetProperty(BUFFER_PROP_DRAW_BEGIN); } ENUM_DRAW_TYPE DrawType(void) const { return (ENUM_DRAW_TYPE)this.GetProperty(BUFFER_PROP_DRAW_TYPE); } bool IsShowData(void) const { return (bool)this.GetProperty(BUFFER_PROP_SHOW_DATA); } int Shift(void) const { return (int)this.GetProperty(BUFFER_PROP_SHIFT); } ENUM_LINE_STYLE LineStyle(void) const { return (ENUM_LINE_STYLE)this.GetProperty(BUFFER_PROP_LINE_STYLE); } int LineWidth(void) const { return (int)this.GetProperty(BUFFER_PROP_LINE_WIDTH); } int ColorsTotal(void) const { return (int)this.GetProperty(BUFFER_PROP_COLOR_INDEXES); } color Color(void) const { return (color)this.GetProperty(BUFFER_PROP_COLOR); } int BuffersTotal(void) const { return (int)this.GetProperty(BUFFER_PROP_NUM_DATAS); } double EmptyValue(void) const { return this.GetProperty(BUFFER_PROP_EMPTY_VALUE); } string Symbol(void) const { return this.GetProperty(BUFFER_PROP_SYMBOL); } string Label(void) const { return this.GetProperty(BUFFER_PROP_LABEL); } int ID(void) const { return (int)this.GetProperty(BUFFER_PROP_ID); } int IndicatorHandle(void) const { return (int)this.GetProperty(BUFFER_PROP_IND_HANDLE); } ENUM_INDICATOR IndicatorType(void) const { return (ENUM_INDICATOR)this.GetProperty(BUFFER_PROP_IND_TYPE); } string IndicatorName(void) const { return this.GetProperty(BUFFER_PROP_IND_NAME); } int IndicatorBarsCalculated(void) const { return ::BarsCalculated((int)this.GetProperty(BUFFER_PROP_IND_HANDLE));}
在类构造函数中,为新属性设置默认值:
//+------------------------------------------------------------------+ //| Closed parametric constructor | //+------------------------------------------------------------------+ CBuffer::CBuffer(ENUM_BUFFER_STATUS buffer_status, ENUM_BUFFER_TYPE buffer_type, const uint index_plot, const uint index_base_array, const int num_datas, const uchar total_arrays, const int width, const string label) { this.m_type=COLLECTION_BUFFERS_ID; this.m_act_state_trigger=true; this.m_total_arrays=total_arrays; //--- Save integer properties this.m_long_prop[BUFFER_PROP_STATUS] = buffer_status; this.m_long_prop[BUFFER_PROP_TYPE] = buffer_type; this.m_long_prop[BUFFER_PROP_ID] = WRONG_VALUE; this.m_long_prop[BUFFER_PROP_IND_HANDLE] = INVALID_HANDLE; this.m_long_prop[BUFFER_PROP_IND_TYPE] = WRONG_VALUE; ENUM_DRAW_TYPE type= ( !this.TypeBuffer() || !this.Status() ? DRAW_NONE : this.Status()==BUFFER_STATUS_FILLING ? DRAW_FILLING : ENUM_DRAW_TYPE(this.Status()+8) ); this.m_long_prop[BUFFER_PROP_DRAW_TYPE] = type; this.m_long_prop[BUFFER_PROP_TIMEFRAME] = PERIOD_CURRENT; this.m_long_prop[BUFFER_PROP_ACTIVE] = true; this.m_long_prop[BUFFER_PROP_ARROW_CODE] = 0x9F; this.m_long_prop[BUFFER_PROP_ARROW_SHIFT] = 0; this.m_long_prop[BUFFER_PROP_DRAW_BEGIN] = 0; this.m_long_prop[BUFFER_PROP_SHOW_DATA] = (buffer_type>BUFFER_TYPE_CALCULATE ? true : false); this.m_long_prop[BUFFER_PROP_SHIFT] = 0; this.m_long_prop[BUFFER_PROP_LINE_STYLE] = STYLE_SOLID; this.m_long_prop[BUFFER_PROP_LINE_WIDTH] = width; this.m_long_prop[BUFFER_PROP_COLOR_INDEXES] = (this.Status()>BUFFER_STATUS_NONE ? (this.Status()!=BUFFER_STATUS_FILLING ? 1 : 2) : 0); this.m_long_prop[BUFFER_PROP_COLOR] = clrRed; this.m_long_prop[BUFFER_PROP_NUM_DATAS] = num_datas; this.m_long_prop[BUFFER_PROP_INDEX_PLOT] = index_plot; this.m_long_prop[BUFFER_PROP_INDEX_BASE] = index_base_array; this.m_long_prop[BUFFER_PROP_INDEX_COLOR] = this.GetProperty(BUFFER_PROP_INDEX_BASE)+ (this.TypeBuffer()!=BUFFER_TYPE_CALCULATE ? this.GetProperty(BUFFER_PROP_NUM_DATAS) : 0); this.m_long_prop[BUFFER_PROP_INDEX_NEXT_BASE] = index_base_array+this.m_total_arrays; this.m_long_prop[BUFFER_PROP_INDEX_NEXT_PLOT] = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? index_plot+1 : index_plot); //--- Save real properties this.m_double_prop[this.IndexProp(BUFFER_PROP_EMPTY_VALUE)] = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? EMPTY_VALUE : 0); //--- Save string properties this.m_string_prop[this.IndexProp(BUFFER_PROP_SYMBOL)] = ::Symbol(); this.m_string_prop[this.IndexProp(BUFFER_PROP_LABEL)] = (this.TypeBuffer()>BUFFER_TYPE_CALCULATE ? label : NULL); this.m_string_prop[this.IndexProp(BUFFER_PROP_IND_NAME)] = NULL; //--- If failed to change the size of the indicator buffer array, display the appropriate message indicating the string
这些新属性的值将属于缓冲区对象,不能与标准指标一起操控。 如果我们创建的一个缓冲对象属于标准指标,则创建时函数库将填充这些参数(稍后将实现)。
在返回缓冲区整数型属性描述的方法中,添加显示新整数型属性的描述 :
//+------------------------------------------------------------------+ //| Return description of a buffer's integer property | //+------------------------------------------------------------------+ string CBuffer::GetPropertyDescription(ENUM_BUFFER_PROP_INTEGER property) { return ( property==BUFFER_PROP_INDEX_PLOT ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_PLOT)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_STATUS ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_STATUS)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetStatusDescription() ) : property==BUFFER_PROP_TYPE ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_TYPE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetTypeBufferDescription() ) : property==BUFFER_PROP_TIMEFRAME ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_TIMEFRAME)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetTimeframeDescription() ) : property==BUFFER_PROP_ACTIVE ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ACTIVE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetActiveDescription() ) : property==BUFFER_PROP_DRAW_TYPE ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_DRAW_TYPE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetDrawTypeDescription() ) : property==BUFFER_PROP_ARROW_CODE ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_CODE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_ARROW_SHIFT ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SHIFT)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_LINE_STYLE ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LINE_STYLE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetLineStyleDescription() ) : property==BUFFER_PROP_LINE_WIDTH ? (this.Status()==BUFFER_STATUS_ARROW ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ARROW_SIZE) : CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LINE_WIDTH))+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_DRAW_BEGIN ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_DRAW_BEGIN)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_SHOW_DATA ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SHOW_DATA)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetShowDataDescription() ) : property==BUFFER_PROP_SHIFT ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SHIFT)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_COLOR_INDEXES ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_COLOR_NUM)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_INDEX_COLOR ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_COLOR)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_INDEX_BASE ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_BASE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_INDEX_NEXT_BASE ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_BASE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_INDEX_NEXT_PLOT ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_INDEX_NEXT_PLOT)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_ID ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_ID)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_IND_HANDLE ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_HANDLE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_IND_TYPE ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_TYPE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_NUM_DATAS ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_NUM_DATAS)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BUFFER_PROP_COLOR ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_COLOR)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.GetColorsDescription() ) : "" ); } //+------------------------------------------------------------------+
在返回缓冲区字符串型属性描述的方法中,添加显示新字符串型属性的描述 :
//+------------------------------------------------------------------+ //| Return description of a buffer's string property | //+------------------------------------------------------------------+ string CBuffer::GetPropertyDescription(ENUM_BUFFER_PROP_STRING property) { return ( property==BUFFER_PROP_SYMBOL ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_SYMBOL)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.Symbol() ) : property==BUFFER_PROP_LABEL ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_LABEL)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(this.Label()==NULL || this.Label()=="" ? CMessage::Text(MSG_LIB_PROP_NOT_SET) : "\""+this.Label()+"\"") ) : property==BUFFER_PROP_IND_NAME ? CMessage::Text(MSG_LIB_TEXT_BUFFER_TEXT_IND_NAME)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(this.IndicatorName()==NULL || this.IndicatorName()=="" ? CMessage::Text(MSG_LIB_PROP_NOT_SET) : "\""+this.IndicatorName()+"\"") ) : "" ); } //+------------------------------------------------------------------+
我们来修改设置空值和图形系列名称的方法。 以前,计算缓冲区并未设置初值。 我们这样做,以便仅为计算缓冲区的情况下为缓冲区对象的属性设置初值。
对于绘制缓冲区,应同时为对象和缓冲区属性设置数值:
//+------------------------------------------------------------------+ //| Set the "empty" value for construction | //| without drawing | //+------------------------------------------------------------------+ void CBuffer::SetEmptyValue(const double value) { this.SetProperty(BUFFER_PROP_EMPTY_VALUE,value); if(this.TypeBuffer()!=BUFFER_TYPE_CALCULATE) ::PlotIndexSetDouble((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_EMPTY_VALUE,value); } //+------------------------------------------------------------------+ //| Set the indicator graphical series name | //+------------------------------------------------------------------+ void CBuffer::SetLabel(const string label) { this.SetProperty(BUFFER_PROP_LABEL,label); if(this.TypeBuffer()!=BUFFER_TYPE_CALCULATE) ::PlotIndexSetString((int)this.GetProperty(BUFFER_PROP_INDEX_PLOT),PLOT_LABEL,label); } //+------------------------------------------------------------------+
按时间序列索引返回值的方法中,添加索引值小于零的控制逻辑:
//+------------------------------------------------------------------+ //| Return the value from the specified timeseries index | //| of the specified data buffer array | //+------------------------------------------------------------------+ double CBuffer::GetDataBufferValue(const uint buffer_index,const int series_index) const { int correct_buff_index=this.GetCorrectIndexBuffer(buffer_index); int data_total=this.GetDataTotal(correct_buff_index); if(data_total==0 || series_index<0) return this.EmptyValue(); int data_index=((int)series_index<data_total ? (int)series_index : data_total-1); return this.DataBuffer[correct_buff_index].Array[data_index]; } //+------------------------------------------------------------------+ //| Return the color index value from the specified timeseries index | //| of the specified color buffer array | //+------------------------------------------------------------------+ int CBuffer::GetColorBufferValueIndex(const int series_index) const { int data_total=this.GetDataTotal(0); if(data_total==0 || series_index<0) return WRONG_VALUE; int data_index=((int)series_index<data_total ? (int)series_index : data_total-1); return(this.ColorsTotal()==1 ? 0 : (int)this.ColorBufferArray[data_index]); } //+------------------------------------------------------------------+ //| Return the color value from the specified timeseries index | //| of the specified color buffer array | //+------------------------------------------------------------------+ color CBuffer::GetColorBufferValueColor(const int series_index) const { int data_total=this.GetDataTotal(0); if(data_total==0 || series_index<0) return clrNONE; int color_index=this.GetColorBufferValueIndex(series_index); return(color_index>WRONG_VALUE ? (color)this.ArrayColors[color_index] : clrNONE); } //+------------------------------------------------------------------+
因此,若给该方法传递了错误的索引,则该方法退出执行,并同时返回“空”值,该值对于每个方法都是不同的。
现在,我们来改进位于 \MQL5\Include\DoEasy\Objects\Indicators\BufferCalculate.mqh 里的计算缓冲区对象类。
方法返回缓冲区对象支持实数型和字符串型属性的标志,若返回 false — 即,计算缓冲区不支持该类型的属性。 我们令其支持所有这些属性。 在返回对象支持整数型属性标志的方法中,为计算缓冲区对象添加新的可支持整数型属性:
//+------------------------------------------------------------------+ //| Return 'true' if a buffer supports a passed | //| integer property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_INTEGER property) { if( property==BUFFER_PROP_INDEX_PLOT || property==BUFFER_PROP_STATUS || property==BUFFER_PROP_TYPE || property==BUFFER_PROP_INDEX_BASE || property==BUFFER_PROP_ID || property==BUFFER_PROP_IND_HANDLE || property==BUFFER_PROP_IND_TYPE || property==BUFFER_PROP_INDEX_NEXT_BASE ) return true; return false; } //+------------------------------------------------------------------+ //| Return 'true' if a buffer supports a passed | //| real property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_DOUBLE property) { return true; } //+------------------------------------------------------------------+ //| Return 'true' if a buffer supports a passed | //| string property, otherwise return 'false' | //+------------------------------------------------------------------+ bool CBufferCalculate::SupportProperty(ENUM_BUFFER_PROP_STRING property) { return true; } //+------------------------------------------------------------------+
计算缓冲区对象支持所有实数型和字符串型属性,大部分是一种快速的临时解决方案,以便创建操控标准指标缓冲区的方法。 稍后,我将从可支持属性列表里删除其中一些。
标准指标的指标缓冲区的整体处理安排在 \MQL5\Include\DoEasy\Collections\BuffersCollection.mqh 中的指标缓冲区的 CBuffersCollection 集合类之中。
今天,我将创建和维护 AC(加速振荡器)标准指标的多品种、多周期指标缓冲区。 在随后文章中,我将基于测试的功能添加创建和操控其他标准指标的能力。
所有操控标准指标的缓冲区对象均可获得 ID,从而令我们能够找到所需的缓冲区,并对其进行操控。
在类的公开部分,声明返回含有该 ID 的缓冲区对象列表的方法:
//+------------------------------------------------------------------+ //| Collection of indicator buffers | //+------------------------------------------------------------------+ class CBuffersCollection : public CObject { private: CListObj m_list; // Buffer object list CTimeSeriesCollection *m_timeseries; // Pointer to the timeseries collection object //--- Return the index of the (1) last, (2) next drawn and (3) basic buffer int GetIndexLastPlot(void); int GetIndexNextPlot(void); int GetIndexNextBase(void); //--- Create a new buffer object and place it to the collection list bool CreateBuffer(ENUM_BUFFER_STATUS status); //--- Get data of the necessary timeseries and bars for working with a single buffer bar, and return the number of bars int GetBarsData(CBuffer *buffer,const int series_index,int &index_bar_period); public: //--- Return (1) itself, (2) timeseries list, (3) indicator buffer list (featuring the ID of belonging to an indicator) CBuffersCollection *GetObject(void) { return &this; } CArrayObj *GetList(void) { return &this.m_list; } CArrayObj *GetListBuffersWithID(void);
我们在类主体之外编写其实现:
//+------------------------------------------------------------------+ //| Return the list of indicator buffers | //| (featuring the ID of belonging to an indicator) | //+------------------------------------------------------------------+ CArrayObj *CBuffersCollection::GetListBuffersWithID(void) { CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_ID,WRONG_VALUE,NO_EQUAL); return list; } //+------------------------------------------------------------------+
此处一切都很简单:利用 CSelect 类,获取 ID 值不等于 -1 的缓冲区对象列表,并返回指向所获列表的指针。
如果成功获得该列表,则它将含有 ID 不等于 -1 的所有缓冲区对象。 这意味着该列表将包含所有为标准指标创建的缓冲区对象,包括针对任何标准指标类型的计算和绘制缓冲区对象。
为了搜索属于特定指标的缓冲区对象,应按标准指标类型,ID 和缓冲区类型在列表里进行排序。
声明创建缓冲区对象处理标准指标的方法,将其添加到类的公开部分:
//--- Create the new buffer (1) "Drawing with arrows", (2) "Line", (3) "Sections", (4) "Histogram from the zero line", //--- (5) "Histogram on two indicator buffers", (6) "Zigzag", (7) "Color filling between two levels", //--- (8) "Display as bars", (9) "Display as candles", calculated buffer bool CreateArrow(void) { return this.CreateBuffer(BUFFER_STATUS_ARROW); } bool CreateLine(void) { return this.CreateBuffer(BUFFER_STATUS_LINE); } bool CreateSection(void) { return this.CreateBuffer(BUFFER_STATUS_SECTION); } bool CreateHistogram(void) { return this.CreateBuffer(BUFFER_STATUS_HISTOGRAM); } bool CreateHistogram2(void) { return this.CreateBuffer(BUFFER_STATUS_HISTOGRAM2); } bool CreateZigZag(void) { return this.CreateBuffer(BUFFER_STATUS_ZIGZAG); } bool CreateFilling(void) { return this.CreateBuffer(BUFFER_STATUS_FILLING); } bool CreateBars(void) { return this.CreateBuffer(BUFFER_STATUS_BARS); } bool CreateCandles(void) { return this.CreateBuffer(BUFFER_STATUS_CANDLES); } bool CreateCalculate(void) { return this.CreateBuffer(BUFFER_STATUS_NONE); } //--- Create a multi-symbol multi-period indicator int CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE); int CreateAD(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id=WRONG_VALUE); int CreateADX(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id=WRONG_VALUE); int CreateADXWilder(const string symbol,const ENUM_TIMEFRAMES timeframe,const int adx_period,const int id=WRONG_VALUE); int CreateAlligator(const string symbol,const ENUM_TIMEFRAMES timeframe, const int jaw_period, const int jaw_shift, const int teeth_period, const int teeth_shift, const int lips_period, const int lips_shift, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateAMA(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ama_period, const int fast_ma_period, const int slow_ma_period, const int ama_shift, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateAO(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE); int CreateATR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE); int CreateBearsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE); int CreateBands(const string symbol,const ENUM_TIMEFRAMES timeframe, const int bands_period, const int bands_shift, const double deviation, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateBullsPower(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE); int CreateCCI(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateChaikin(const string symbol,const ENUM_TIMEFRAMES timeframe, const int fast_ma_period, const int slow_ma_period, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_VOLUME applied_volume, const int id=WRONG_VALUE); int CreateDEMA(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const int ma_shift, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateDeMarker(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE); int CreateEnvelopes(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const int ma_shift, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_PRICE applied_price, const double deviation, const int id=WRONG_VALUE); int CreateForce(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_VOLUME applied_volume, const int id=WRONG_VALUE); int CreateFractals(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE); int CreateFrAMA(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const int ma_shift, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateGator(const string symbol,const ENUM_TIMEFRAMES timeframe, const int jaw_period, const int jaw_shift, const int teeth_period, const int teeth_shift, const int lips_period, const int lips_shift, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateIchimoku(const string symbol,const ENUM_TIMEFRAMES timeframe, const int tenkan_sen, const int kijun_sen, const int senkou_span_b, const int id=WRONG_VALUE); int CreateBWMFI(const string symbol,const ENUM_TIMEFRAMES timeframe, const ENUM_APPLIED_VOLUME applied_volume, const int id=WRONG_VALUE); int CreateMomentum(const string symbol,const ENUM_TIMEFRAMES timeframe, const int mom_period, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateMFI(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const ENUM_APPLIED_VOLUME applied_volume, const int id=WRONG_VALUE); int CreateMA(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const int ma_shift, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateOsMA(const string symbol,const ENUM_TIMEFRAMES timeframe, const int fast_ema_period, const int slow_ema_period, const int signal_period, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateMACD(const string symbol,const ENUM_TIMEFRAMES timeframe, const int fast_ema_period, const int slow_ema_period, const int signal_period, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateOBV(const string symbol,const ENUM_TIMEFRAMES timeframe, const ENUM_APPLIED_VOLUME applied_volume, const int id=WRONG_VALUE); int CreateSAR(const string symbol,const ENUM_TIMEFRAMES timeframe, const double step, const double maximum, const int id=WRONG_VALUE); int CreateRSI(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateRVI(const string symbol,const ENUM_TIMEFRAMES timeframe,const int ma_period,const int id=WRONG_VALUE); int CreateStdDev(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const int ma_shift, const ENUM_MA_METHOD ma_method, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateStochastic(const string symbol,const ENUM_TIMEFRAMES timeframe, const int Kperiod, const int Dperiod, const int slowing, const ENUM_MA_METHOD ma_method, const ENUM_STO_PRICE price_field, const int id=WRONG_VALUE); int CreateTEMA(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const int ma_shift, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateTriX(const string symbol,const ENUM_TIMEFRAMES timeframe, const int ma_period, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateWPR(const string symbol,const ENUM_TIMEFRAMES timeframe,const int calc_period,const int id=WRONG_VALUE); int CreateVIDYA(const string symbol,const ENUM_TIMEFRAMES timeframe, const int cmo_period, const int ema_period, const int ma_shift, const ENUM_APPLIED_PRICE applied_price, const int id=WRONG_VALUE); int CreateVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id=WRONG_VALUE);
每种特定的标准指标类型都将调用其自身的方法来创建相应的指标,和必要的缓冲区对象。
例如,此处,我将实现处理 AC 指标。 我们在类主体之外编写创建 AC 指标及其缓冲区的方法:
//+------------------------------------------------------------------+ //| Create multi-symbol multi-period AC | //+------------------------------------------------------------------+ int CBuffersCollection::CreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id=WRONG_VALUE) { //--- Create the indicator handle and set the default ID int handle=::iAC(symbol,timeframe); int identifier=(id==WRONG_VALUE ? IND_AC : id); if(handle!=INVALID_HANDLE) { //--- Create the histogram buffer from the zero line this.CreateHistogram(); //--- Get the last created (drawn) buffer object and set all the necessary parameters to it CBuffer *buff=this.GetLastCreateBuffer(); buff.SetSymbol(symbol); buff.SetTimeframe(timeframe); buff.SetID(identifier); buff.SetIndicatorHandle(handle); buff.SetIndicatorType(IND_AC); buff.SetShowData(true); buff.SetLabel("AC("+symbol+","+TimeframeDescription(timeframe)+")"); buff.SetIndicatorName("Accelerator Oscillator"); //--- Create a calculated buffer storing standard indicator data this.CreateCalculate(); //--- Get the last created (calculated) buffer object and set all the necessary parameters to it buff=this.GetLastCreateBuffer(); buff.SetSymbol(symbol); buff.SetTimeframe(timeframe); buff.SetID(identifier); buff.SetIndicatorHandle(handle); buff.SetIndicatorType(IND_AC); buff.SetEmptyValue(EMPTY_VALUE); buff.SetLabel("AC("+symbol+","+TimeframeDescription(timeframe)+")"); buff.SetIndicatorName("Accelerator Oscillator"); } return handle; } //+------------------------------------------------------------------+
如您所见,这里的一切都很简单。 如果将 -1 作为 ID 传递,则该 ID 等于标准指标类型常量。 如果创建指标成功(其句柄不等于 INVALID_HANDLE ),则创建绘图类型为“从零轴开始的直方图”的缓冲区对象,并使用 GetLastCreateBuffer() 方法返回指向最后创建的缓冲区的指针(该方法将在以后研究),从而获取指向直方图缓冲区对象的指针,并因其标识作为绘制标准 AC 指标数据的缓冲区,而设置所需的参数。
接下来,针对计算缓冲区也执行相同的操作。 在计算缓冲区中,写入依据其句柄访问指标时获得的 AC 指标数据。 在缓冲区对象属性中设置所创建指标的句柄。 无论是绘制还是计算缓冲区,都是如此,即我们可以获取这些缓冲区的任何对象,按对象中设置的句柄访问指标,并操控指标。
添加使用必要缓冲区对象创建 AC 指标的方法实现,来观察实现不同类型标准指标的方法之区别:
//+------------------------------------------------------------------+ //| Create multi-symbol multi-period AD | //+------------------------------------------------------------------+ int CBuffersCollection::CreateAD(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume,const int id=WRONG_VALUE) { //--- Create the indicator handle and set the default ID int handle=::iAD(symbol,timeframe,applied_volume); int identifier=(id==WRONG_VALUE ? IND_AD : id); if(handle!=INVALID_HANDLE) { //--- Create the line buffer this.CreateLine(); //--- Get the last created (drawn) buffer object and set all the necessary parameters to it CBuffer *buff=this.GetLastCreateBuffer(); buff.SetSymbol(symbol); buff.SetTimeframe(timeframe); buff.SetID(identifier); buff.SetIndicatorHandle(handle); buff.SetIndicatorType(IND_AD); buff.SetShowData(true); buff.SetLabel("AD("+symbol+","+TimeframeDescription(timeframe)+")"); buff.SetIndicatorName("Accumulation/Distribution"); //--- Create a calculated buffer storing standard indicator data this.CreateCalculate(); //--- Get the last created (calculated) buffer object and set all the necessary parameters to it buff=this.GetLastCreateBuffer(); buff.SetSymbol(symbol); buff.SetTimeframe(timeframe); buff.SetID(identifier); buff.SetIndicatorHandle(handle); buff.SetIndicatorType(IND_AD); buff.SetEmptyValue(EMPTY_VALUE); buff.SetLabel("AD("+symbol+","+TimeframeDescription(timeframe)+")"); buff.SetIndicatorName("Accumulation/Distribution"); } return handle; } //+------------------------------------------------------------------+
差异很小,并且主要与绘制缓冲区类型、标准指标类型、图形系列名称和指标名称有关。 在其他类型的标准指标中,还会有不同数量的绘制和计算缓冲区对象(如果需要)来处理标准指标。
在声明创建标准指标的方法之后,直接添加其余方法的声明:
//--- Prepare calculated buffer data of the specified standard indicator int PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy); //--- Clear buffer data of the specified standard indicator by the timeseries index void ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index); //--- Set the values for the current chart to the specified standard indicator buffer by the timeseries index according to the buffer object period/symbol bool SetDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index,const datetime series_time,const char color_index=WRONG_VALUE); //--- Return the buffer (1) by the graphical series name, (2) by timeframe, //--- (3) by Plot index, (4) by object index in the collection list, (5) the last created one, //--- list of buffers (6) by ID, (7) by standard indicator type, (8) by type and ID CBuffer *GetBufferByLabel(const string plot_label); CBuffer *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe); CBuffer *GetBufferByPlot(const int plot_index); CBuffer *GetBufferByListIndex(const int index_list); CBuffer *GetLastCreateBuffer(void); CArrayObj *GetListBufferByID(const int id); CArrayObj *GetListBufferByIndType(const ENUM_INDICATOR indicator_type); CArrayObj *GetListBufferByTypeID(const ENUM_INDICATOR indicator_type,const int id);
所有已声明的方法都在注释中命名。 我们研究在类主体之外实现它们。
返回最后创建的缓冲区对象的方法:
//+------------------------------------------------------------------+ //| Return the last created buffer | //+------------------------------------------------------------------+ CBuffer *CBuffersCollection::GetLastCreateBuffer(void) { return this.m_list.At(this.m_list.Total()-1); } //+------------------------------------------------------------------+
该方法只是返回指向缓冲对象的指针,该指针是缓冲对象列表中的最后一个。
该方法按 ID 返回缓冲区对象的列表:
//+------------------------------------------------------------------+ //| Return the list of buffers by ID | //+------------------------------------------------------------------+ CArrayObj *CBuffersCollection::GetListBufferByID(const int id) { CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_ID,id,EQUAL); return list; } //+------------------------------------------------------------------+
获取其 ID 等于传递给方法 参数值的缓冲区对象列表。
返回指向所获取列表的指标。
该方法按标准指标类型返回缓冲区对象的列表:
//+------------------------------------------------------------------+ //| Return the list of buffers by the standard indicator type | //+------------------------------------------------------------------+ CArrayObj *CBuffersCollection::GetListBufferByIndType(const ENUM_INDICATOR indicator_type) { CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_IND_TYPE,indicator_type,EQUAL); return list; } //+------------------------------------------------------------------+
获取其标准指标类型 等于传递给方法参数值的缓冲区对象列表。
返回指向从方法所获列表的指针。
该方法按标准指标类型和 ID 返回缓冲区对象的列表:
//+------------------------------------------------------------------+ //| Return the list of buffers by type and ID | //+------------------------------------------------------------------+ CArrayObj *CBuffersCollection::GetListBufferByTypeID(const ENUM_INDICATOR indicator_type,const int id) { CArrayObj *list=this.GetListBufferByIndType(indicator_type); list=CSelect::ByBufferProperty(list,BUFFER_PROP_ID,id,EQUAL); return list; } //+------------------------------------------------------------------+
首先,获取其属性中含有指定标准指标类型的缓冲区对象的列表。 接下来,针对所获缓冲区对象列表,按属性内含有指定 ID 进行排序。
从该方法返回指向结果列表的指针。
该方法返回属于某个标准指标的缓冲区对象列表:
//+------------------------------------------------------------------+ //| Return the list of indicator buffers | //| (featuring the ID of belonging to an indicator) | //+------------------------------------------------------------------+ CArrayObj *CBuffersCollection::GetListBuffersWithID(void) { CArrayObj *list=CSelect::ByBufferProperty(this.GetList(),BUFFER_PROP_ID,WRONG_VALUE,NO_EQUAL); return list; } //+------------------------------------------------------------------+
从缓冲区对象的集合列表中,获取其 ID 属性不等于 -1 的对象列表。
返回指向从方法所获列表的指针。
函数库的主要对象类 CEngine 是程序与函数库之间的链接。
我们在类文件中进行必要的改进 \MQL5\Include\DoEasy\Engine.mqh。
在类的公开部分中添加重新创建所有时间序列的方法:
//--- Re-create (1) the specified timeseries of the specified symbol, (2) all collection timeseries bool SeriesReCreate(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0) { return this.m_time_series.ReCreateSeries(symbol,timeframe,rates_total,required); } bool SeriesReCreateAll(const int rates_total=0,const uint required=0) { return this.m_time_series.ReCreateSeriesAll(rates_total,required); }
该方法简单地返回我在上面添加的同名时间序列集合方法的结果。
在类的公开部分里,添加返回指定时间序列柱线数量的方法:
//--- Return (1) an empty, (2) partially filled timeseries CSeriesDE *SeriesGetSeriesEmpty(void) { return this.m_time_series.GetSeriesEmpty(); } CSeriesDE *SeriesGetSeriesIncompleted(void) { return this.m_time_series.GetSeriesIncompleted(); } //--- Return the umber of bars of the timeseries of the specified symbol/period int SeriesGetBarsTotal(const string symbol,const ENUM_TIMEFRAMES timeframe);
在类主体之外实现方法:
//+---------------------------------------------------------------------------+ //| Return the umber of bars of the timeseries of the specified symbol/period | //+---------------------------------------------------------------------------+ int CEngine::SeriesGetBarsTotal(const string symbol,const ENUM_TIMEFRAMES timeframe) { CSeriesDE *series=this.SeriesGetSeries(symbol,timeframe); if(series==NULL) return WRONG_VALUE; return (int)series.Bars(); } //+------------------------------------------------------------------+
从时间序列集合类中获取指定的时间序列,并返回时间序列柱线数量。
在前面,我已有了返回最后创建的缓冲区的方法:
//--- Return the buffer by (1) the graphical series name, (2) timeframe, (3) Plot index, (4) collection list and (5) the last one in the list CBuffer *GetBufferByLabel(const string plot_label) { return this.m_buffers.GetBufferByLabel(plot_label); } CBuffer *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe) { return this.m_buffers.GetBufferByTimeframe(timeframe);} CBuffer *GetBufferByPlot(const int plot_index) { return this.m_buffers.GetBufferByPlot(plot_index); } CBuffer *GetBufferByListIndex(const int index_list) { return this.m_buffers.GetBufferByListIndex(index_list);} CBuffer *GetLastBuffer(void);
和其实现:
//+------------------------------------------------------------------+ //| Return the last indicator buffer | //| in the indicator buffer collection list | //+------------------------------------------------------------------+ CBuffer *CEngine::GetLastBuffer(void) { CArrayObj *list=this.GetListBuffers(); if(list==NULL) return NULL; return list.At(list.Total()-1); } //+------------------------------------------------------------------+
从类清单里删除方法实现,用新方法替换其声明:
//--- Return the buffer by (1) the graphical series name, (2) timeframe, (3) Plot index, (4) collection list and (5) the last one in the list CBuffer *GetBufferByLabel(const string plot_label) { return this.m_buffers.GetBufferByLabel(plot_label); } CBuffer *GetBufferByTimeframe(const ENUM_TIMEFRAMES timeframe) { return this.m_buffers.GetBufferByTimeframe(timeframe);} CBuffer *GetBufferByPlot(const int plot_index) { return this.m_buffers.GetBufferByPlot(plot_index); } CBuffer *GetBufferByListIndex(const int index_list) { return this.m_buffers.GetBufferByListIndex(index_list);} CBuffer *GetLastCreateBuffer(void) { return this.m_buffers.GetLastCreateBuffer(); }
该方法返回上面研究过的同名缓冲区集合类方法的结果。
在类的公开部分中,添加了一个创建 AC 标准指标和其操控缓冲区的方法:
//--- Create the new buffer (1) "Drawing with arrows", (2) "Line", (3) "Sections", (4) "Histogram from the zero line", //--- (5) "Histogram on two indicator buffers", (6) "Zigzag", (7) "Color filling between two levels", //--- (8) "Display as bars", (9) "Display as candles", calculated buffer bool BufferCreateArrow(void) { return this.m_buffers.CreateArrow(); } bool BufferCreateLine(void) { return this.m_buffers.CreateLine(); } bool BufferCreateSection(void) { return this.m_buffers.CreateSection(); } bool BufferCreateHistogram(void) { return this.m_buffers.CreateHistogram(); } bool BufferCreateHistogram2(void) { return this.m_buffers.CreateHistogram2(); } bool BufferCreateZigZag(void) { return this.m_buffers.CreateZigZag(); } bool BufferCreateFilling(void) { return this.m_buffers.CreateFilling(); } bool BufferCreateBars(void) { return this.m_buffers.CreateBars(); } bool BufferCreateCandles(void) { return this.m_buffers.CreateCandles(); } bool BufferCreateCalculate(void) { return this.m_buffers.CreateCalculate(); } //--- The methods of creating standard indicators and buffer objects for them bool BufferCreateAC(const string symbol,const ENUM_TIMEFRAMES timeframe,const int id) { return(this.m_buffers.CreateAC(symbol,timeframe,id)!=INVALID_HANDLE); } //--- Initialize all drawn buffers by a (1) specified value, (2) empty value set for the buffer object
该方法返回我在上面已研究过的指标缓冲区集合类中创建同名 AC 指标的方法结果。 随后的文章将添加创建其他标准指标的方法。
实现为标准指标准备计算缓冲区数据的方法(仅到目前为止适用于 AC):
//+------------------------------------------------------------------+ //| Prepare the calculated buffer data | //| of the specified standard indicator | //+------------------------------------------------------------------+ int CBuffersCollection::PreparingDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int total_copy) { CArrayObj *list=this.GetListBufferByTypeID(std_ind,id); list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL); if(list==NULL || list.Total()==0) return 0; CBufferCalculate *buffer=NULL; int copies=WRONG_VALUE; switch((int)std_ind) { case IND_AC : buffer=list.At(0); if(buffer==NULL) return 0; copies=buffer.FillAsSeries(buffer.IndicatorHandle(),0,0,total_copy); return copies; case IND_AD : break; case IND_ADX : break; case IND_ADXW : break; case IND_ALLIGATOR : break; case IND_AMA : break; case IND_AO : break; case IND_ATR : break; case IND_BANDS : break; case IND_BEARS : break; case IND_BULLS : break; case IND_BWMFI : break; case IND_CCI : break; case IND_CHAIKIN : break; case IND_DEMA : break; case IND_DEMARKER : break; case IND_ENVELOPES : break; case IND_FORCE : break; case IND_FRACTALS : break; case IND_FRAMA : break; case IND_GATOR : break; case IND_ICHIMOKU : break; case IND_MA : break; case IND_MACD : break; case IND_MFI : break; case IND_MOMENTUM : break; case IND_OBV : break; case IND_OSMA : break; case IND_RSI : break; case IND_RVI : break; case IND_SAR : break; case IND_STDDEV : break; case IND_STOCHASTIC : break; case IND_TEMA : break; case IND_TRIX : break; case IND_VIDYA : break; case IND_VOLUMES : break; case IND_WPR : break; default: break; } return 0; } //+------------------------------------------------------------------+
按指标类型和 ID 获取缓冲区对象列表,仅将计算缓冲区保留在获取的列表之中,从列表提取第一个(仅用于 AC)计算缓冲区,用指定的数据量填充计算缓冲区,并返回从指标句柄成功复制到计算缓冲区的数据量。
实现按指定索引清除标准指标计算缓冲区数据的方法(到目前为止仅适用于 AC):
//+------------------------------------------------------------------+ //| Clear buffer data of the specified standard indicator | //| by the timeseries index | //+------------------------------------------------------------------+ void CBuffersCollection::ClearDataBufferStdInd(const ENUM_INDICATOR std_ind,const int id,const int series_index) { CArrayObj *list=this.GetListBufferByID(id); if(list==NULL) return; list=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL); if(list.Total()==0) return; CBuffer *buffer=NULL; switch((int)std_ind) { case IND_AC : buffer=list.At(0); if(buffer==NULL) return; buffer.SetBufferValue(0,series_index,buffer.EmptyValue()); break; case IND_AD : break; case IND_ADX : break; case IND_ADXW : break; case IND_ALLIGATOR : break; case IND_AMA : break; case IND_AO : break; case IND_ATR : break; case IND_BANDS : break; case IND_BEARS : break; case IND_BULLS : break; case IND_BWMFI : break; case IND_CCI : break; case IND_CHAIKIN : break; case IND_DEMA : break; case IND_DEMARKER : break; case IND_ENVELOPES : break; case IND_FORCE : break; case IND_FRACTALS : break; case IND_FRAMA : break; case IND_GATOR : break; case IND_ICHIMOKU : break; case IND_MA : break; case IND_MACD : break; case IND_MFI : break; case IND_MOMENTUM : break; case IND_OBV : break; case IND_OSMA : break; case IND_RSI : break; case IND_RVI : break; case IND_SAR : break; case IND_STDDEV : break; case IND_STOCHASTIC : break; case IND_TEMA : break; case IND_TRIX : break; case IND_VIDYA : break; case IND_VOLUMES : break; case IND_WPR : break; default: break; } } //+------------------------------------------------------------------+
该方法的工作方式与数据准备方法相似。 但取代从指标句柄复制数据到计算缓冲区,此处要指定缓冲区对象的绘制缓冲区索引,以便为其设置空值。
实现提取任何交易品种/时间帧的标准指标数据,并填充当前图表上绘制缓冲区的方法(到目前为止仅适用于 AC):
//+------------------------------------------------------------------+ //| Set values for the current chart to the specified buffer | //| of the standard indicator by the timeseries index according to | //| the buffer object symbol/period | //+------------------------------------------------------------------+ bool CBuffersCollection::SetDataBufferStdInd(const ENUM_INDICATOR ind_type,const int id,const int series_index,const datetime series_time,const char color_index=WRONG_VALUE) { //--- Get the list of buffer objects with ID CArrayObj *list=this.GetListBufferByTypeID(ind_type,id); if(list==NULL) return false; //--- Get the list of drawn objects with ID CArrayObj *list_data=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_DATA,EQUAL); list_data=CSelect::ByBufferProperty(list_data,BUFFER_PROP_IND_TYPE,ind_type,EQUAL); //--- Get the list of calculated buffers with ID CArrayObj *list_calc=CSelect::ByBufferProperty(list,BUFFER_PROP_TYPE,BUFFER_TYPE_CALCULATE,EQUAL); list_calc=CSelect::ByBufferProperty(list_calc,BUFFER_PROP_IND_TYPE,ind_type,EQUAL); //--- Exit if any of the lists is empty if(list_data.Total()==0 || list_calc.Total()==0) return false; //--- Declare the necessary objects and variables CBuffer *buffer_data=NULL; CBuffer *buffer_calc=NULL; int index_period=0; int series_index_start=0; int num_bars=1,index=0; datetime time_period=0; double value0=EMPTY_VALUE, value1=EMPTY_VALUE; //--- Depending on the standard indicator type switch((int)ind_type) { case IND_AC : //--- Get drawn and calculated buffer objects buffer_data=list_data.At(0); buffer_calc=list_calc.At(0); if(buffer_calc==NULL || buffer_data==NULL || buffer_calc.GetDataTotal(0)==0) return false; //--- Find the bar index corresponding to the current bar start time index_period=::iBarShift(buffer_calc.Symbol(),buffer_calc.Timeframe(),series_time,true); if(index_period==WRONG_VALUE || index_period>buffer_calc.GetDataTotal()-1) return false; //--- Get the value by the index from the indicator buffer value0=buffer_calc.GetDataBufferValue(0,index_period); if(buffer_calc.Symbol()==::Symbol() && buffer_calc.Timeframe()==::Period()) { series_index_start=series_index; num_bars=1; } else { //--- Get the bar time the bar with the index_period index falls into on the calculated buffer period and symbol time_period=::iTime(buffer_calc.Symbol(),buffer_calc.Timeframe(),index_period); if(time_period==0) return false; //--- Get the appropriate current chart bar series_index_start=::iBarShift(::Symbol(),::Period(),time_period,true); if(series_index_start==WRONG_VALUE) return false; //--- Calculate the number of bars on the current chart which should be filled with calculated buffer data num_bars=::PeriodSeconds(buffer_calc.Timeframe())/::PeriodSeconds(PERIOD_CURRENT); if(num_bars==0) num_bars=1; } //--- Take values to calculate colors value1=(series_index_start+num_bars>buffer_data.GetDataTotal()-1 ? value0 : buffer_data.GetDataBufferValue(0,series_index_start+num_bars)); //--- In the loop by the number of bars in num_bars, fill in the drawn buffer with the calculated buffer value taken by the index_period index //--- and set the color of the drawn buffer depending on the value0 and value1 values ratio for(int i=0;i<num_bars;i++) { index=series_index_start-i; buffer_data.SetBufferValue(0,index,value0); buffer_data.SetBufferColorIndex(index,uchar(value0>value1 ? 0 : value0<value1 ? 1 : 2)); } break; case IND_AD : break; case IND_ADX : break; case IND_ADXW : break; case IND_ALLIGATOR : break; case IND_AMA : break; case IND_AO : break; case IND_ATR : break; case IND_BANDS : break; case IND_BEARS : break; case IND_BULLS : break; case IND_BWMFI : break; case IND_CCI : break; case IND_CHAIKIN : break; case IND_DEMA : break; case IND_DEMARKER : break; case IND_ENVELOPES : break; case IND_FORCE : break; case IND_FRACTALS : break; case IND_FRAMA : break; case IND_GATOR : break; case IND_ICHIMOKU : break; case IND_MA : break; case IND_MACD : break; case IND_MFI : break; case IND_MOMENTUM : break; case IND_OBV : break; case IND_OSMA : break; case IND_RSI : break; case IND_RVI : break; case IND_SAR : break; case IND_STDDEV : break; case IND_STOCHASTIC : break; case IND_TEMA : break; case IND_TRIX : break; case IND_VIDYA : break; case IND_VOLUMES : break; case IND_WPR : break; default: break; } return true; } //+------------------------------------------------------------------+
与 AC 数据计算有关的整个方法逻辑在注释中进行了详细描述。
在类主体的最后,声明两个处理函数库事件的方法:
public: //--- Create and return the composite magic number from the specified magic number value, the first and second group IDs and the pending request ID uint SetCompositeMagicNumber(ushort magic_id,const uchar group_id1=0,const uchar group_id2=0,const uchar pending_req_id=0); //--- Handling DoEasy library events void OnDoEasyEvent(const int id,const long &lparam,const double &dparam,const string &sparam); //--- Working with events in the tester void EventsHandling(void); }; //+------------------------------------------------------------------+
以前,我曾用与新声明的方法同名的函数来处理自定义程序中的函数库事件。 我们将这些函数从一个程序传递到了另一个程序,且未经任何修改。 这表明可将这些处理程序转移到函数库当中,而在程序中,我们可以简单地接收已发生事件的标志(接收标志,以及事件标志本身,以及在自定义程序中处理事件的能力将在稍后实现)。
我已将这些函数从测试指标移至 CEngine 类清单,利用它们来实现上述声明的方法:
//+------------------------------------------------------------------+ //| Handling DoEasy library events | //+------------------------------------------------------------------+ void CEngine::OnDoEasyEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { int idx=id-CHARTEVENT_CUSTOM; //--- Retrieve (1) event time milliseconds, (2) reason and (3) source from lparam, as well as (4) set the exact event time ushort msc=this.EventMSC(lparam); ushort reason=this.EventReason(lparam); ushort source=this.EventSource(lparam); long time=::TimeCurrent()*1000+msc; //--- Handling symbol events if(source==COLLECTION_SYMBOLS_ID) { CSymbol *symbol=this.GetSymbolObjByName(sparam); if(symbol==NULL) return; //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol int digits=(idx<SYMBOL_PROP_INTEGER_TOTAL ? 0 : symbol.Digits()); //--- Event text description string id_descr=(idx<SYMBOL_PROP_INTEGER_TOTAL ? symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_INTEGER)idx) : symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_DOUBLE)idx)); //--- Property change text value string value=::DoubleToString(dparam,digits); //--- Check event reasons and display its description in the journal if(reason==BASE_EVENT_REASON_INC) { ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_DEC) { ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_MORE_THEN) { ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_LESS_THEN) { ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_EQUALS) { ::Print(DFUN,symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } } //--- Handling account events else if(source==COLLECTION_ACCOUNT_ID) { CAccount *account=this.GetAccountCurrent(); if(account==NULL) return; //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol int digits=int(idx<ACCOUNT_PROP_INTEGER_TOTAL ? 0 : account.CurrencyDigits()); //--- Event text description string id_descr=(idx<ACCOUNT_PROP_INTEGER_TOTAL ? account.GetPropertyDescription((ENUM_ACCOUNT_PROP_INTEGER)idx) : account.GetPropertyDescription((ENUM_ACCOUNT_PROP_DOUBLE)idx)); //--- Property change text value string value=::DoubleToString(dparam,digits); //--- Checking event reasons and handling the increase of funds by a specified value, //--- Display an event in the journal if(reason==BASE_EVENT_REASON_INC) { ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_DEC) { ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_MORE_THEN) { ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_LESS_THEN) { ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_EQUALS) { ::Print(DFUN,account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } } //--- Handling market watch window events else if(idx>MARKET_WATCH_EVENT_NO_EVENT && idx<SYMBOL_EVENTS_NEXT_CODE) { //--- Market Watch window event string descr=this.GetMWEventDescription((ENUM_MW_EVENT)idx); string name=(idx==MARKET_WATCH_EVENT_SYMBOL_SORT ? "" : ": "+sparam); Print(TimeMSCtoString(lparam)," ",descr,name); } //--- Handling timeseries events else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE) { //--- "New bar" event if(idx==SERIES_EVENTS_NEW_BAR) { ::Print(DFUN,TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam)); CArrayObj *list=this.m_buffers.GetListBuffersWithID(); if(list!=NULL) { int total=list.Total(); for(int i=0;i<total;i++) { CBuffer *buff=list.At(i); if(buff==NULL) continue; string symbol=sparam; ENUM_TIMEFRAMES timeframe=(ENUM_TIMEFRAMES)dparam; if(buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE) continue; if(buff.Symbol()==symbol && buff.Timeframe()==timeframe ) { CSeriesDE *series=this.SeriesGetSeries(symbol,timeframe); if(series==NULL) continue; int count=::fmin(buff.GetDataTotal(),buff.IndicatorBarsCalculated()); this.m_buffers.PreparingDataBufferStdInd(buff.IndicatorType(),buff.ID(),count); } } } } //--- "Bars skipped" event if(idx==SERIES_EVENTS_MISSING_BARS) { ::Print(DFUN,TextByLanguage("Пропущены бары на ","Missed bars on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",(string)lparam); } } //--- Handling trading events else if(idx>TRADE_EVENT_NO_EVENT && idx<TRADE_EVENTS_NEXT_CODE) { //--- Get the list of trading events CArrayObj *list=this.GetListAllOrdersEvents(); if(list==NULL) return; //--- get the event index shift relative to the end of the list //--- in the tester, the shift is passed by the lparam parameter to the event handler //--- outside the tester, events are sent one by one and handled in OnChartEvent() int shift=(this.IsTester() ? (int)lparam : 0); CEvent *event=list.At(list.Total()-1-shift); if(event==NULL) return; //--- Accrue the credit if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CREDIT) { ::Print(DFUN,event.TypeEventDescription()); } //--- Additional charges if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CHARGE) { ::Print(DFUN,event.TypeEventDescription()); } //--- Correction if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CORRECTION) { ::Print(DFUN,event.TypeEventDescription()); } //--- Enumerate bonuses if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BONUS) { ::Print(DFUN,event.TypeEventDescription()); } //--- Additional commissions if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION) { ::Print(DFUN,event.TypeEventDescription()); } //--- Daily commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_DAILY) { ::Print(DFUN,event.TypeEventDescription()); } //--- Monthly commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY) { ::Print(DFUN,event.TypeEventDescription()); } //--- Daily agent commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY) { ::Print(DFUN,event.TypeEventDescription()); } //--- Monthly agent commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY) { ::Print(DFUN,event.TypeEventDescription()); } //--- Interest rate if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_INTEREST) { ::Print(DFUN,event.TypeEventDescription()); } //--- Canceled buy deal if(event.TypeEvent()==TRADE_EVENT_BUY_CANCELLED) { ::Print(DFUN,event.TypeEventDescription()); } //--- Canceled sell deal if(event.TypeEvent()==TRADE_EVENT_SELL_CANCELLED) { ::Print(DFUN,event.TypeEventDescription()); } //--- Dividend operations if(event.TypeEvent()==TRADE_EVENT_DIVIDENT) { ::Print(DFUN,event.TypeEventDescription()); } //--- Accrual of franked dividend if(event.TypeEvent()==TRADE_EVENT_DIVIDENT_FRANKED) { ::Print(DFUN,event.TypeEventDescription()); } //--- Tax charges if(event.TypeEvent()==TRADE_EVENT_TAX) { ::Print(DFUN,event.TypeEventDescription()); } //--- Replenishing account balance if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_REFILL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Withdrawing funds from balance if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Pending order placed if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_PLASED) { ::Print(DFUN,event.TypeEventDescription()); } //--- Pending order removed if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_REMOVED) { ::Print(DFUN,event.TypeEventDescription()); } //--- Pending order activated by price if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED) { ::Print(DFUN,event.TypeEventDescription()); } //--- Pending order partially activated by price if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position opened if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position opened partially if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED_PARTIAL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position closed if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position closed by an opposite one if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_POS) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position closed by StopLoss if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_SL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position closed by TakeProfit if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_TP) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by a new deal (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by activating a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by partial market order execution (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by activating a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by a new deal (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET) { ::Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by partial execution of a market order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by activating a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING) { ::Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by partial activation of a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position closed partially if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position partially closed by an opposite one if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position closed partially by StopLoss if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Position closed partially by TakeProfit if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP) { ::Print(DFUN,event.TypeEventDescription()); } //--- StopLimit order activation if(event.TypeEvent()==TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing order price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing order and StopLoss price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_SL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing order and TakeProfit price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_TP) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing order, StopLoss and TakeProfit price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_SL_TP) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing order's StopLoss and TakeProfit price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_SL_TP) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing order's StopLoss if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_SL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing order's TakeProfit if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_TP) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing position's StopLoss and TakeProfit if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_SL_TP) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing position StopLoss if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_SL) { ::Print(DFUN,event.TypeEventDescription()); } //--- Changing position TakeProfit if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_TP) { ::Print(DFUN,event.TypeEventDescription()); } } } //+------------------------------------------------------------------+ //| Working with events in the tester | //+------------------------------------------------------------------+ void CEngine::EventsHandling(void) { //--- If a trading event is present if(this.IsTradeEvent()) { //--- Number of trading events occurred simultaneously int total=this.GetTradeEventsTotal(); for(int i=0;i<total;i++) { //--- Get the next event from the list of simultaneously occurred events by index CEventBaseObj *event=this.GetTradeEventByIndex(i); if(event==NULL) continue; long lparam=i; double dparam=event.DParam(); string sparam=event.SParam(); this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } //--- If there is an account event if(this.IsAccountsEvent()) { //--- Get the list of all account events occurred simultaneously CArrayObj* list=this.GetListAccountEvents(); if(list!=NULL) { //--- Get the next event in a loop int total=list.Total(); for(int i=0;i<total;i++) { //--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) continue; //--- Send an event to the event handler long lparam=event.LParam(); double dparam=event.DParam(); string sparam=event.SParam(); this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } //--- If there is a symbol collection event if(this.IsSymbolsEvent()) { //--- Get the list of all symbol events occurred simultaneously CArrayObj* list=this.GetListSymbolsEvents(); if(list!=NULL) { //--- Get the next event in a loop int total=list.Total(); for(int i=0;i<total;i++) { //--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) continue; //--- Send an event to the event handler long lparam=event.LParam(); double dparam=event.DParam(); string sparam=event.SParam(); this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } //--- If there is a timeseries collection event if(this.IsSeriesEvent()) { //--- Get the list of all timeseries events occurred simultaneously CArrayObj* list=this.GetListSeriesEvents(); if(list!=NULL) { //--- Get the next event in a loop int total=list.Total(); for(int i=0;i<total;i++) { //--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) continue; //--- Send an event to the event handler long lparam=event.LParam(); double dparam=event.DParam(); string sparam=event.SParam(); this.OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } } //+------------------------------------------------------------------+
在开发测试 EA 时,我已经在开始的函数库讲述文章中研究了这些函数(现在是 CEngine 类方法)。 方法清单清晰地表明,几乎每个事件都伴有日志记录。 相应地,可在全局可见性范围内创建事件标志的列表,并简单地设置所需的标志。 在自定义程序中,更容易为每个已激活标志实现处理程序。 我将稍后再做。
因此,我们无需在每个自定义程序中指定这些处理程序。
该类具有从指标调用的 calculate 事件处理程序。 如果处理程序返回的值等于零,则意味着指标中使用的所有时间序列尚未构造。 指标返回码为 0 则应退出 OnCalculate(),这意味着等待下一个即时报价,并表示未计算任何数据。
鉴于我要添加标准指标的处理,因此必须确保所创建指标经过计算。
为了找到已计算的数据量,我们可以用 BarsCalculated() 函数返回指标已计算的数据量。 如果尚无数据经过计算,则函数返回 -1。
在处理 'calculate' 事件的方法中加入缓冲区集合中所有已创建标准指标已成功计算的检查 :
//+------------------------------------------------------------------+ //| Calculate event handler | //+------------------------------------------------------------------+ int CEngine::OnCalculate(SDataCalculate &data_calculate,const uint required=0) { //--- If this is not an indicator, exit if(this.m_program!=PROGRAM_INDICATOR) return 0; //--- Re-create empty timeseries //--- If at least one of the timeseries is not synchronized, return zero if(!this.SeriesSync(data_calculate,required)) return 0; //--- Update the timeseries of the current symbol (not in the tester) and //--- return either 0 (in case there are empty timeseries), or rates_total if(!this.IsTester()) this.SeriesRefresh(NULL,data_calculate); int res=(this.SeriesGetSeriesEmpty()==NULL ? data_calculate.rates_total : 0); //--- Check the amount of calculated standard indicator data CArrayObj *list=m_buffers.GetListBuffersWithID(); if(list!=NULL) { //--- In a loop by the number of buffers having an ID int total=list.Total(); for(int i=0;i<total;i++) { //--- get the next calculated buffer using the standard indicator CBuffer *buff=list.At(i); if(buff==NULL || buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorHandle()==INVALID_HANDLE) continue; //--- if the indicator data is not calculated yet, return zero if(buff.IndicatorBarsCalculated()==WRONG_VALUE) return 0; } } return res; } //+------------------------------------------------------------------+
方法清单中说明了处理所创建指标数据的逻辑。
在当前文章里作为函数库修订版的最后一点,我打算把当前图表周期添加到已用时间帧列表当中。<br0 /> 函数库服务函数文件 E:\MetaQuotes\MetaTrader 5\MQL5\Include\DoEasy\Services\DELib.mqh 含有准备已用时间帧列表的函数。 如果在程序设置中未指定当前图表周期,则函数库不会为其创建时间序列。 但我们的任务需要持续的时间序列。
我们添加代码模块来改进 CreateUsedTimeframesArray() 函数,从而在已用时间帧列表中指定当前图表周期:
//+------------------------------------------------------------------+ //| Prepare the array of timeframes for the timeseries collection | //+------------------------------------------------------------------+ bool CreateUsedTimeframesArray(const ENUM_TIMEFRAMES_MODE mode_used_periods,string defined_used_periods,string &used_periods_array[]) { //--- If working with the current chart period, fill the array with the current timeframe description string if(mode_used_periods==TIMEFRAMES_MODE_CURRENT) { ArrayResize(used_periods_array,1,21); used_periods_array[0]=TimeframeDescription((ENUM_TIMEFRAMES)Period()); return true; } //--- If working with a predefined set of chart periods (from the defined_used_periods string) else if(mode_used_periods==TIMEFRAMES_MODE_LIST) { //--- Set comma as a separator (defined in the Datas.mqh file, page 11) string separator=INPUT_SEPARATOR; //--- Fill in the array of parameters from the string with predefined timeframes int n=StringParamsPrepare(defined_used_periods,separator,used_periods_array); //--- if nothing is found, display the appropriate message (working with the current period is selected automatically) if(n<1) { int err_code=GetLastError(); string err= (n==0 ? DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_ERROR_EMPTY_PERIODS_STRING)+TimeframeDescription((ENUM_TIMEFRAMES)Period()) : DFUN_ERR_LINE+CMessage::Text(MSG_LIB_SYS_FAILED_PREPARING_PERIODS_ARRAY)+(string)err_code+": "+CMessage::Text(err_code) ); Print(err); //--- Set the current period to the array ArrayResize(used_periods_array,1,21); used_periods_array[0]=TimeframeDescription((ENUM_TIMEFRAMES)Period()); return false; } } //--- If working with the full list of timeframes, fill in the array with strings describing all timeframes else { ArrayResize(used_periods_array,21,21); for(int i=0;i<21;i++) used_periods_array[i]=TimeframeDescription(TimeframeByEnumIndex(uchar(i+1))); } //--- Add the current chart timeframe to the list of used periods bool f=false; for(int i=0;i<ArraySize(used_periods_array);i++) { if(used_periods_array[i]==TimeframeDescription((ENUM_TIMEFRAMES)Period())) { f=true; break; } } //--- If the list of used periods features no timeframe of the current chart if(!f) { //--- Increase the array of used periods by 1 and add the current chart period to it ArrayResize(used_periods_array,ArraySize(used_periods_array)+1); used_periods_array[ArraySize(used_periods_array)-1]=TimeframeDescription((ENUM_TIMEFRAMES)Period()); } //--- All is successful return true; } //+------------------------------------------------------------------+
至此,函数库类的改进完毕。
现在到了测试已开发的多品种、多周期加速振荡器标准指标的时候了。
测试
为了执行测试,我将借助来自前篇文章的指标,并将其保存在 \MQL5\Indicators\TestDoEasy\Part47\,命名为 TestDoEasyPart47.mq5。
计算标准 AC 振荡器指标时,在指标设置中我们需要指定目标交易品种和时间帧。 该指标将在当前图表子窗口中显示数据。
指标头部如下:
//+------------------------------------------------------------------+ //| TestDoEasyPart47.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> //--- properties #property indicator_separate_window #property indicator_buffers 3 #property indicator_plots 1 //--- classes //--- enums //--- defines //--- structures //--- input variables sinput string InpUsedSymbols = "GBPUSD"; // Used symbol (one only) sinput ENUM_TIMEFRAMES InpPeriod = PERIOD_M30; // Used chart period //--- sinput bool InpUseSounds = true; // Use sounds //--- indicator buffers //--- global variables ENUM_SYMBOLS_MODE InpModeUsedSymbols= SYMBOLS_MODE_DEFINES; // Mode of used symbols list ENUM_TIMEFRAMES_MODE InpModeUsedTFs = TIMEFRAMES_MODE_LIST; // Mode of used timeframes list string InpUsedTFs; // List of used timeframes CEngine engine; // CEngine library main object string prefix; // Prefix of graphical object names int min_bars; // The minimum number of bars for the indicator calculation int used_symbols_mode; // Mode of working with symbols string array_used_symbols[]; // The array for passing used symbols to the library string array_used_periods[]; // The array for passing used timeframes to the library //+------------------------------------------------------------------+
为计算 AC 指标,仅需指定一个品种和图表周期。
在 OnInit() 处理程序中,创建标准的 AC 指标,其参数在输入中指定,其 ID(等于 1),以及操控缓冲区:
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Write the name of the working timeframe selected in the settings to the InpUsedTFs variable InpUsedTFs=TimeframeDescription(InpPeriod); //--- Initialize DoEasy library OnInitDoEasy(); //--- Set indicator global variables prefix=engine.Name()+"_"; //--- calculate the number of bars of the current period fitting in the maximum used period //--- Use the obtained value if it exceeds 2, otherwise use 2 int num_bars=NumberBarsInTimeframe(InpPeriod); min_bars=(num_bars>2 ? num_bars : 2); //--- Check and remove remaining indicator graphical objects if(IsPresentObectByPrefix(prefix)) ObjectsDeleteAll(0,prefix); //--- Create the button panel //--- Check playing a standard sound using macro substitutions engine.PlaySoundByDescription(SND_OK); //--- Wait for 600 milliseconds engine.Pause(600); engine.PlaySoundByDescription(SND_NEWS); //--- indicator buffers mapping //--- Create all the necessary buffer objects for constructing AO engine.BufferCreateAC(InpUsedSymbols,InpPeriod,1); //--- Check the number of buffers specified in the 'properties' block if(engine.BuffersPropertyPlotsTotal()!=indicator_plots) Alert(TextByLanguage("Внимание! Значение \"indicator_plots\" должно быть ","Attention! Value of \"indicator_plots\" should be "),engine.BuffersPropertyPlotsTotal()); if(engine.BuffersPropertyBuffersTotal()!=indicator_buffers) Alert(TextByLanguage("Внимание! Значение \"indicator_buffers\" должно быть ","Attention! Value of \"indicator_buffers\" should be "),engine.BuffersPropertyBuffersTotal()); //--- Create the color array and set non-default colors to all buffers within the collection color array_colors[]={clrGreen,clrRed,clrGray}; engine.BuffersSetColors(array_colors); //--- Display short descriptions of created indicator buffers engine.BuffersPrintShort(); //--- Set the short name for the indicator and bit depth IndicatorSetString(INDICATOR_SHORTNAME,"AC("+InpUsedSymbols+","+TimeframeDescription(InpPeriod)+")"); IndicatorSetInteger(INDICATOR_DIGITS,(int)SymbolInfoInteger(InpUsedSymbols,SYMBOL_DIGITS)+2); //--- Successful return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
在 OnCalculate() 里,首先准备 AC 指标的计算缓冲区数据。 接下来,在主指标循环中,提取来自 AC 指标计算缓冲区的数据来填充当前图表上的绘制缓冲区:
//+------------------------------------------------------------------+ //| OnCalculate code block for working with the library: | //+------------------------------------------------------------------+ //--- Pass the current symbol data from OnCalculate() to the price structure and set the "as timeseries" flag to the arrays CopyDataAsSeries(rates_total,prev_calculated,time,open,high,low,close,tick_volume,volume,spread); //--- Check for the minimum number of bars for calculation if(rates_total<min_bars || Point()==0) return 0; //--- Handle the Calculate event in the library //--- If the OnCalculate() method of the library returns zero, not all timeseries are ready - leave till the next tick if(engine.0) return 0; //--- If working in the tester if(MQLInfoInteger(MQL_TESTER)) { engine.OnTimer(rates_data); // Working in the library timer engine.EventsHandling(); // Working with library events } //+------------------------------------------------------------------+ //| OnCalculate code block for working with the indicator: | //+------------------------------------------------------------------+ //--- Check and calculate the number of calculated bars //--- If limit = 0, there are no new bars - calculate the current one //--- If limit = 1, a new bar has appeared - calculate the first and the current ones //--- limit > 1 means the first launch or changes in history - the full recalculation of all data int limit=rates_total-prev_calculated; //--- Recalculate the entire history if(limit>1) { limit=rates_total-1; engine.BuffersInitPlots(); engine.BuffersInitCalculates(); } //--- Prepare data int bars_total=engine.SeriesGetBarsTotal(InpUsedSymbols,InpPeriod); int total_copy=(limit<min_bars ? min_bars : fmin(limit,bars_total)); //--- Fill in the calculated buffer with AO data CArrayObj *list=engine.GetBuffersCollection().GetListBuffersWithID(); if(list!=NULL) { for(int i=0;i<list.Total();i++) { CBuffer *buff=list.At(i); if(buff==NULL || buff.TypeBuffer()==BUFFER_TYPE_DATA || buff.IndicatorType()==WRONG_VALUE) continue; CSeriesDE *series=engine.SeriesGetSeries(buff.Symbol(),buff.Timeframe()); if(series==NULL) return 0; ulong used_data=series.AvailableUsedData(); int copied=engine.GetBuffersCollection().PreparingDataBufferStdInd(IND_AC,1,(int)used_data); if(copied<(int)used_data) return 0; } } //--- Calculate the indicator CBar *bar=NULL; // Bar object for defining the candle direction uchar color_index=0; // Color index to be set for the buffer depending on the candle direction //--- Main calculation loop of the indicator for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--) { engine.GetBuffersCollection().SetDataBufferStdInd(IND_AC,1,i,time[i]); } //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+
这就是在当前图表上显示任何品种/时间帧的标准 AC 指标时,我们需要计算的全部内容。
标准指标数据准备模块将得到改进(由于只是为了验证概念而开发的,因此在当前实现中不是最佳选择),在后续文章中会将其转移到函数库之中。
完整的指标代码在下面的文件中提供。
在指标设置中指定 GBPUSD M5 之后,编译指标,并在 EURUSD M1 上启动它,这意味着在当前 EURUSD 分钟图表上显示 AC 指标数据(依据 GBPUSD M5 计算):
为了比较,还打开了一个 GBPUSD M5 标准 AC 指标图表。
下一步是什么?
在下篇文章中,我将继续开发多品种、多周期的标准指标。
以下附件是函数库当前版本的所有文件,以及测试 EA 文件,供您测试和下载。
将您的问题、评论和建议留在评论中。
请记住,此处我已经为 MetaTrader 5 开发了 MQL5 测试指标。
附件仅适用于 MetaTrader 5。 当前函数库版本尚未在 MetaTrader 4 里进行测试。
在开发和测试指标缓冲区的功能之后,我将尝试在 MetaTrader 4 中实现一些 MQL5 特性。
该系列中的先前文章:
DoEasy 函数库中的时间序列(第三十五部分):柱线对象和品种时间序列列表DoEasy 函数库中的时间序列(第三十六部分):所有用到的品种周期的时间序列对象
DoEasy 函数库中的时间序列(第三十七部分):时间序列集合 - 按品种和周期的时间序列数据库
DoEasy 函数库中的时间序列(第三十八部分):时间序列集合 - 实时更新以及从程序访问数据
DoEasy 函数库中的时间序列(第三十九部分):基于函数库的指标 - 准备数据和时间序列事件
DoEasy 函数库中的时间序列(第四十部分):基于函数库的指标 - 实时刷新数据
DoEasy 函数库中的时间序列(第四十一部分):多品种多周期指标样品
DoEasy 函数库中的时间序列(第四十二部分):抽象指标缓冲区对象类
DoEasy 函数库中的时间序列(第四十三部分):指标缓冲区对象类
DoEasy 函数库中的时间序列(第四十四部分):指标缓冲区对象集合类
DoEasy 函数库中的时间序列(第四十五部分):多周期指标缓冲区
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/8207
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.



Artem,你好!
感谢您对 kodobase 的贡献和您的工作!
我的理解是,这不是一个模板,我可以在其中输入我需要的任何符号和时间?
它只适用于 MT 的内置指标?
自定义指标 呢?
我非常希望有一个可以输入任何指标(包括自定义指标)的模板。
谢谢!
Artem,你好!
感谢您对 kodobase 的贡献和所做的工作!
我的理解是,这不是一个模板,我可以在其中输入我需要的任何符号和时间?
它只适用于 MT 中的内置指标?
自定义指标 呢?
我希望有一个可以插入任何指标(包括自定义指标)的模板。
谢谢!
该库不是模板,其目的是帮助您为 MetaTrader 开发自己的程序。
本文介绍如何使用该库创建多周期多字符自定义指标,该指标显示符号和周期与当前图表符号/周期不同的标准指标(以 AC 为例)的数据。其他标准指标将在后续文章中介绍。
本部分主要介绍标准指标。
若要使用第三方(非借助库创建)自定义指标,则需要采用稍有不同的方法。这将在后续文章中介绍。
感谢您帮助新人