多交易品种多周期指标中的 DRAW_ARROW 绘图类型
目录
概述
我们继续讨论多交易品种多周期指标。本系列的上一篇文章介绍了多交易品种多周期指标。在本文中,我们将修改多指标类,使其可以与箭头指标一起使用。
箭头指标意味着其绘图缓冲区中的数据并非始终可用。该值存在于显示箭头的缓冲区中,而在其他时候,缓冲区中包含的是为其设置的空值。通常是 EMPTY_VALUE,但也可以将缓冲区设置为"空"且不会显示在图表上的任何值。可以使用这个函数:
PlotIndexSetDouble(buffer_index,PLOT_EMPTY_VALUE,empty_value);
其中,buffer_index 是要设置为空的缓冲区索引,而 empty_value 则是将为该缓冲区设置的 "空值"。
在多周期指标中,缓冲区充满了有间隙的数据,因此需要考虑到没有箭头的空值的存在,不要将这些空值输入到已经输入了非空值的图表柱中。否则,先前放置的箭头将被一个新的空值擦除。如果将在较低时间框架内计算的指标数据复制到较高时间框架内,就会出现这种情况。
让我举一个例子。М15柱形叠加在M5图表上:
这里我们看到的是 M5 图表的分形,需要将其安装到 M15 图表的柱形图上。
- 对于第一个柱 M15 (I),我们需要设置位于第 3 个柱的上分形 - 该分形在任何情况下都会在 M15 上出现,因为它是三个柱中的最后一个柱。
- 对于第二个柱 M15 (II),您需要设置位于第 2 个柱的分形 - 只有在不复制第 3 个柱的空值的情况下,该分形才会在 M15 上显示,因为它将取代下分形的值。M15 柱形将有两个分形 - 一个上分形和一个下分形,因为 M5 柱形的第 2 个柱有一个下分形,第 3 个柱有一个上分形。
- 对于 M15 的第三个柱形(III),我们需要设置第 1 个柱形图的顶部分形 - 只有在不复制第 2 个柱形和第 3 个柱形中的空值的情况下,这个分形才会在 M15 上显示,因为它们会取代分形值。
因此,我们需要跟踪当前图表柱形上的值,如果它是空的,我们就可以从较低时间框架的缓冲区中复制任何值。但如果值不再为空,那么我们只需复制非空值即可。在这种情况下,较高时间框架的图表条形图包含较低时间框架柱形中最后一个分形的值,这些分形是较高图表周期柱形的一部分。为什么是最后一个柱,而不是第一个柱?我认为最后一个值更有意义,因为它更接近当前时间。
在将较高时间框架计算的指标数据复制到较低时间框架的图表时,我们需要考虑到箭头可以不放在图表的零柱或第一个柱上,而是放在第二个柱、第三个柱或更高的一个柱上。如果只复制一号和零号柱的数据,就像现在在多指标类中所做的那样,那么图表上就不会显示箭头 - 它们只是位于索引较高的柱形上,因此指标计算算法根本无法看到它们:
在这里,我们可以看到分形是在索引为 2 的柱形上形成的。如果我们只复制零号和一号柱,那么程序将无法看到这个分形。
因此,由于我们无法准确知道哪个指标将箭头放在哪个柱形上,因此有必要为多指标类引入一个变量,在该变量中我们将明确指出搜索和复制值是从哪个柱形开始的。目前,该类只复制当前分时的第一个和第零个柱。因此,它无法看到分形 - 数值从索引为 2 的柱形开始。
如果使用自定义指标作为初始指标,那么它可以在任何其他柱形上放置箭头,而不是像分形指标那样,只能在第二个柱形上放置箭头。例如,如果这是一个自定义分形指标,需要搜索不同维度的分形,如 3-X-3,那么箭头可以放在索引为 3(如果分形被重绘)或 4(如果分形未被重绘)的柱形上。
对于标准指标,我们将明确指出柱形索引,因为它是已知的。对于自定义指标,我们将在类构造函数参数中指定搜索起始柱。
完善多指标类
在类方法中复制数据时,我们使用了本地临时数组。让我们将它们设为全局数组,这样就不必每次都为这些数组重新分配内存了。
在多交易品种多周期指标的 CIndMSTF 基类的私有部分中声明新数组,而在受保护部分中声明一个变量,用于存储计算当前分时指标的柱数:
//+------------------------------------------------------------------+ //| Base class of the multi-symbol multi-period indicator | //+------------------------------------------------------------------+ class CIndMSTF : public CObject { private: ENUM_PROGRAM_TYPE m_program; // Program type ENUM_INDICATOR m_type; // Indicator type ENUM_TIMEFRAMES m_timeframe; // Chart timeframe string m_symbol; // Chart symbol int m_handle; // Indicator handle int m_id; // Identifier bool m_success; // Successful calculation flag ENUM_ERR_TYPE m_type_err; // Calculation error type string m_description; // Custom description of the indicator string m_name; // Indicator name string m_parameters; // Description of indicator parameters double m_array_data[]; // Temporary array for copying data datetime m_array_time[]; // Temporary array for copying time protected: ENUM_IND_CATEGORY m_category; // Indicator category MqlParam m_param[]; // Array of indicator parameters string m_title; // Title (indicator name + description of parameters) SBuffer m_buffers[]; // Indicator buffers int m_digits; // Digits in indicator values int m_limit; // Number of bars required to calculate the indicator on the current tick int m_rates_total; // Number of available bars for indicator calculation int m_prev_calculated; // Number of calculated bars on the previous indicator call uint m_bars_to_calculate; // Number of bars required to calculate the indicator on the current tick
在类的公有部分,编写一个方法来设置计算当前分时指标所需的柱数:
//--- Returns amount of calculated data int Calculated(void) const { return ::BarsCalculated(this.m_handle); } //--- Set the number of bars required to calculate the indicator on the current tick void SetBarsToCalculate(const int bars) { this.m_bars_to_calculate=bars; if(this.m_array_data.Size()!=this.m_bars_to_calculate) { ::ArrayResize(this.m_array_data,this.m_bars_to_calculate); ::ArrayResize(this.m_array_time,this.m_bars_to_calculate); } } //--- Virtual method returning the type of object (indicator)
该方法会得到所需的柱数。新变量的值会被设置好,而数组的大小会被设置为等于该变量的值。
因此,如果成功更改了数组的大小,在数据复制方法中就不再需要每次都重新分配数组。如果无法在此更改数组的大小,在这种情况下,复制方法中的数据复制函数会尝试将数组的大小改为所需的大小。如果这也失败,则方法会显示复制错误信息。
因此,我们只需在类构造函数中或复制方法中(如果不成功)设置一次数组所需的大小。然后,我们使用设置了所需大小的数组,这样就不用不断为数组重新分配内存了。
在类的构造函数中,使用默认值调用方法复制数据(第一和零号柱)。对于绝大多数指标而言,它等于两个柱形:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CIndMSTF::CIndMSTF(const ENUM_INDICATOR type,const uint buffers,const string symbol,const ENUM_TIMEFRAMES timeframe) { //--- Start the timer ::ResetLastError(); if(!::EventSetTimer(1)) ::PrintFormat("%s: EventSetTimer failed. Error %lu",__FUNCTION__,::GetLastError()); //--- Set properties to the values passed to the constructor or to default values this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE); this.m_type=type; this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe); this.m_handle=INVALID_HANDLE; this.m_digits=::Digits(); this.m_success=true; this.m_type_err=ERR_TYPE_NO_ERROR; //--- Set the size of the buffer structure array to the number of indicator buffers ::ResetLastError(); if(::ArrayResize(this.m_buffers,buffers)!=buffers) ::PrintFormat("%s: Buffers ArrayResize failed. Error %lu"__FUNCTION__,::GetLastError()); //--- Set the "empty" value for each buffer by default (can be changed it later) for(int i=0;i<(int)this.m_buffers.Size();i++) this.SetBufferInitValue(i,EMPTY_VALUE); //--- Set initial values of variables involved in resource-efficient calculation of the indicator this.m_prev_calculated=0; this.m_limit=0; this.m_rates_total=::Bars(this.m_symbol,this.m_timeframe); this.SetBarsToCalculate(2); //--- If the indicator is calculated on non-current chart, request data from the required chart //--- (the first access to data starts data pumping) datetime array[]; if(symbol!=::Symbol() || timeframe!=::Period()) ::CopyTime(this.m_symbol,this.m_timeframe,0,this.m_rates_total,array); }
在类的析构函数中,释放为临时数组分配的内存:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CIndMSTF::~CIndMSTF() { //--- Delete timer ::EventKillTimer(); //--- Release handle of the indicator ::ResetLastError(); if(this.m_handle!=INVALID_HANDLE && !::IndicatorRelease(this.m_handle)) ::PrintFormat("%s: %s, handle %ld IndicatorRelease failed. Error %ld",__FUNCTION__,this.Title(),m_handle,::GetLastError()); //--- Free up the memory of buffer arrays for(int i=0;i<(int)this.BuffersTotal();i++) { ::ArrayFree(this.m_buffers[i].array0); ::ArrayFree(this.m_buffers[i].array1); ::ArrayFree(this.m_buffers[i].array2); ::ArrayFree(this.m_buffers[i].array3); ::ArrayFree(this.m_buffers[i].color_indexes); } //--- Free up the memory of temporary arrays ::ArrayFree(this.m_array_data); ::ArrayFree(this.m_array_time); }
现在,在复制指定缓冲区数组数据的方法中,使用新变量中设置的值检查正在复制的数据量。以前的硬编码值是两个柱:
//+------------------------------------------------------------------+ //| Copy data of the specified array of the specified buffer | //+------------------------------------------------------------------+ bool CIndMSTF::CopyArray(const uint buff_num,const uint array_num,const int to_copy,double &array[]) { ::ResetLastError(); //--- Copy either the last two bars to 'array' or all available historical data from indicator's calculation part array to buffer array of indicator object int copied=0; if(to_copy==this.m_bars_to_calculate) { switch(array_num) { case 0 : case 1 : case 2 : case 3 : copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(), -this.m_buffers[buff_num].Shift(),to_copy,array); break; case 4 : copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom()+1,-this.m_buffers[buff_num].Shift(),to_copy,array); break; default : break; } } else { switch(array_num) { case 0 : copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(), -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array0); break; case 1 : copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(), -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array1); break; case 2 : copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(), -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array2); break; case 3 : copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom(), -this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].array3); break; case 4 : copied=::CopyBuffer(this.m_handle,this.m_buffers[buff_num].BufferFrom()+1,-this.m_buffers[buff_num].Shift(),to_copy,this.m_buffers[buff_num].color_indexes); break; default : break; } } //--- If copied successfully if(copied>0) return true; //--- If not all data is copied //--- If CopyBuffer returned -1, this means the start of historical data downloading //--- print a message about this to the log if(copied==WRONG_VALUE) ::PrintFormat("%s::%s: Start downloading data by %s/%s. Waiting for the next tick...",__FUNCTION__,this.Title(),this.m_symbol,this.TimeframeDescription()); //--- In any other case, not all data has been copied yet //--- print a message about this to the log else ::PrintFormat("%s::%s: Not all data was copied. Data available: %lu, total copied: %ld",__FUNCTION__,this.Title(),this.m_rates_total,copied); return false; }
在复制指定缓冲区内所有数组数据的方法中,复制两个柱形的工作以前是使用硬编码的。此外,还明确设置了缓冲数组的源索引和目标索引 :
//+------------------------------------------------------------------+ //| Copy data of all arrays of the specified buffer | //+------------------------------------------------------------------+ bool CIndMSTF::CopyArrays(const uint buff_num,const int to_copy) { bool res=true; double array[2]; if(to_copy==2) { switch(this.BufferDrawType(buff_num)) { //--- One buffer case DRAW_LINE : case DRAW_HISTOGRAM : case DRAW_ARROW : case DRAW_SECTION : res=this.CopyArray(buff_num,0,to_copy,array); if(res) { this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-1]=array[1]; this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-2]=array[0]; } return res; //--- Two buffers case DRAW_HISTOGRAM2 : case DRAW_ZIGZAG : case DRAW_FILLING :
而现在,复制数据的数量是无法预先知道的。它被设置在一个新变量中。因此,我们将检查变量值,同时按复制 的数据量 循环复制 数据。
我们将根据循环索引计算源数组和目标数组的索引。 同时,我们现在已经声明了全局临时数组,因此不再需要本地数组:
//+------------------------------------------------------------------+ //| Copy data of all arrays of the specified buffer | //+------------------------------------------------------------------+ bool CIndMSTF::CopyArrays(const uint buff_num,const int to_copy) { bool res=true; if(to_copy==this.m_bars_to_calculate) { int total=(int)this.m_array_data.Size(); switch(this.BufferDrawType(buff_num)) { //--- One buffer case DRAW_LINE : case DRAW_HISTOGRAM : case DRAW_ARROW : case DRAW_SECTION : res=this.CopyArray(buff_num,0,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i]; } return res; //--- Two buffers case DRAW_HISTOGRAM2 : case DRAW_ZIGZAG : case DRAW_FILLING : res=this.CopyArray(buff_num,0,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i]; } return res; //--- Four buffers case DRAW_BARS : case DRAW_CANDLES : res=this.CopyArray(buff_num,0,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array2[this.DataTotal(buff_num,2)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array3[this.DataTotal(buff_num,3)-(total-i)]=this.m_array_data[i]; } return res; //--- One buffer + color buffer case DRAW_COLOR_LINE : case DRAW_COLOR_HISTOGRAM : case DRAW_COLOR_ARROW : case DRAW_COLOR_SECTION : res=this.CopyArray(buff_num,0,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,4)-(total-i)]=this.m_array_data[i]; } return res; //--- Two buffers + color buffer case DRAW_COLOR_HISTOGRAM2 : case DRAW_COLOR_ZIGZAG : res=this.CopyArray(buff_num,0,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,4)-(total-i)]=this.m_array_data[i]; } return res; //--- Four buffers + color buffer case DRAW_COLOR_BARS : case DRAW_COLOR_CANDLES : res=this.CopyArray(buff_num,0,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array0[this.DataTotal(buff_num,0)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array1[this.DataTotal(buff_num,1)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array2[this.DataTotal(buff_num,2)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].array3[this.DataTotal(buff_num,3)-(total-i)]=this.m_array_data[i]; } res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data); if(res) { for(int i=0;i<total;i++) this.m_buffers[buff_num].color_indexes[this.DataTotal(buff_num,4)-(total-i)]=this.m_array_data[i]; } return res; //---DRAW_NONE default: break; } } else { switch(this.BufferDrawType(buff_num)) { //--- One buffer case DRAW_LINE : case DRAW_HISTOGRAM : case DRAW_ARROW : case DRAW_SECTION : return this.CopyArray(buff_num,0,to_copy,this.m_array_data); //--- Two buffers case DRAW_HISTOGRAM2 : case DRAW_ZIGZAG : case DRAW_FILLING : res =this.CopyArray(buff_num,0,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data); return res; //--- Four buffers case DRAW_BARS : case DRAW_CANDLES : res =this.CopyArray(buff_num,0,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data); return res; //--- One buffer + color buffer case DRAW_COLOR_LINE : case DRAW_COLOR_HISTOGRAM : case DRAW_COLOR_ARROW : case DRAW_COLOR_SECTION : res =this.CopyArray(buff_num,0,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data); return res; //--- Two buffers + color buffer case DRAW_COLOR_HISTOGRAM2 : case DRAW_COLOR_ZIGZAG : res =this.CopyArray(buff_num,0,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data); return res; //--- Four buffers + color buffer case DRAW_COLOR_BARS : case DRAW_COLOR_CANDLES : res =this.CopyArray(buff_num,0,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,1,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,2,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,3,to_copy,this.m_array_data); res &=this.CopyArray(buff_num,4,to_copy,this.m_array_data); return res; //---DRAW_NONE default: break; } } return false; }
在负责当前分时操作的代码块中,使用计算部分缓冲区的数据填充对象缓冲区的方法中,复制 m_bars_to_calculate 变量中设置的柱数,而不是两个柱:
//--- If calculated m_limit is less than or equal to 1, this means either opening of a new bar (m_limit==1) or current tick (m_limit==0) //--- In this case, it is necessary to calculate the number of bars specified in the m_bars_to_calculate variable - the current one and the rest if(this.m_limit<=1) { //--- In a loop over the number of indicator buffers for(int i=0;i<total;i++) { //--- If this is the opening of a new bar and resizing the indicator buffer failed, if(this.m_limit==1 && !this.BufferResize(i,this.m_rates_total)) { //--- add 'false' to the m_success variable and return 'false' //--- Here, an error message will be printed to log from the BufferResize method this.m_success=false; return false; } //--- If failed to copy bars in the amount of m_bars_to_calculate from the indicator's calculation part buffer, ::ResetLastError(); if(!this.CopyArrays(i,this.m_bars_to_calculate)) { //--- report this via the log, add 'false' to the m_success variable and return 'false' ::PrintFormat("%s::%s: CopyBuffer(%lu) failed. Error %lu",__FUNCTION__,this.Title(),i,::GetLastError()); this.m_success &=false; } } //--- If the loop is followed by errors, return 'false' if(!this.m_success) { this.m_type_err=ERR_TYPE_NO_DATA; return false; } //--- Success this.m_type_err=ERR_TYPE_NO_ERROR; this.m_success=true; return true; } //--- Undefined 'limit' option - return 'false' return false; }
将类缓冲区中的数据填入传递数组的方法现已完全重新设计。它考虑到了将数据从较低的时间框架复制到较高的时间框架、从较高的时间框架复制到较低的时间框架的细微差别,以及在本文开头讨论过的当前分时上复制的数据量。该方法的逻辑已被完全重写并注释:
//+------------------------------------------------------------------+ //| Fills the passed plot array with data from the class buffer | //+------------------------------------------------------------------+ bool CIndMSTF::DataToBuffer(const string symbol_to,const ENUM_TIMEFRAMES timeframe_to,const uint buffer_num,const uint array_num,const int limit,double &buffer[]) { //--- Set the success flag this.m_success=true; //--- Get the indexing direction of the buffer array passed to the method and, //--- if non-timeseries indexing, set timeseries indexing bool as_series=::ArrayGetAsSeries(buffer); if(!as_series) ::ArraySetAsSeries(buffer,true); //--- Set the symbol name and timeframe value passed to the method string symbol=(symbol_to=="" || symbol_to==NULL ? ::Symbol() : symbol_to); ENUM_TIMEFRAMES timeframe=(timeframe_to==PERIOD_CURRENT ? ::Period() : timeframe_to); //--- If this is the first launch or history changes, initialize the buffer array passed to the method if(limit>1 && this.m_limit>1) { ::PrintFormat("%s::%s First start, or historical data has been changed. Initialize Buffer(%lu)",__FUNCTION__,this.Title(),buffer_num); ::ArrayInitialize(buffer,this.BufferInitValue(buffer_num)); } //--- Set the value of the loop counter (no more than the maximum number of bars in the terminal on the chart) int count=(limit<=1 ? (int)this.m_bars_to_calculate : ::fmin(::TerminalInfoInteger(TERMINAL_MAXBARS),limit)); //--- In a loop from the zero bar to the value of the loop counter for(int i=0;i<count;i++) { //--- If the chart timeframe matches the class object timeframe, fill the buffer directly from the class object array if(timeframe==::Period() && this.m_timeframe==::Period()) buffer[i]=this.GetData(buffer_num,array_num,i); //--- Otherwise, if the chart timeframe is not equal to the timeframe of the class object else { //--- If the chart timeframe is lower than the timeframe of the class object, if(timeframe<this.m_timeframe) { //--- If this is historical data (a bar with an index greater than m_bars_to_calculate-1) if(i>(int)this.m_bars_to_calculate-1) { //--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to ::ResetLastError(); if(::CopyTime(symbol,timeframe,i,1,this.m_array_time)!=1) { //--- If there is no data in the terminal, move on if(::GetLastError()==4401) continue; //--- Error in obtaining existing data - return false this.m_success &=false; return false; } //--- Using time of bar of current chart timeframe, find the corresponding bar index of the chart period in the class object buffer array int bar=::iBarShift(this.m_symbol,this.m_timeframe,this.m_array_time[0]); if(bar==WRONG_VALUE) { this.m_success &=false; continue; } //--- in the indicator buffer at the loop index, write the value obtained from the calculation part buffer buffer[i]=this.GetData(buffer_num,array_num,bar); } //--- If this is the current (zero) bar or a bar with an index less than or equal to m_bars_to_calculate-1 else { //--- Get the time of bars by symbol/timeframe of the class object in the amount of m_bars_to_calculate if(::CopyTime(this.m_symbol,this.m_timeframe,0,this.m_bars_to_calculate,this.m_array_time)!=this.m_bars_to_calculate) { this.m_success=false; return false; } //--- Get the number of bars of the current chart period contained in the chart period of the class object int num_bars=::PeriodSeconds(this.m_timeframe)/::PeriodSeconds(timeframe); if(num_bars<1) num_bars=1; //--- In a loop through the array of received bar times for(int j=0;j<(int)this.m_array_time.Size();j++) { //--- Get the corresponding bar on the chart by the time of the bar in the buffer array of the class object int barN=::iBarShift(symbol,timeframe,this.m_array_time[j]); if(barN==WRONG_VALUE) { this.m_success &=false; return false; } //--- Calculate the final bar on the chart to copy data from the buffer of the calculation part of the indicator int end=barN-num_bars; if(end<0) end=-1; //--- In a loop from barN to end, copy data from the buffer array of the calculation part to the indicator buffer being drawn on the chart for(int n=barN;n>end;n--) buffer[n]=this.GetData(buffer_num,array_num,this.m_bars_to_calculate-1-j); } } } //--- If the chart timeframe is higher than the timeframe of the class object, else if(timeframe>this.m_timeframe) { //--- Get the number of bars of the class object chart period contained in the bar of the current chart period int num_bars=::PeriodSeconds(timeframe)/::PeriodSeconds(this.m_timeframe); //--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to ::ResetLastError(); if(::CopyTime(symbol,timeframe,i,1,this.m_array_time)!=1) { //--- If there is no data in the terminal, move on if(::GetLastError()==4401) continue; //--- Error in obtaining existing data - return false this.m_success &=false; return false; } //--- Using time of bar of current chart timeframe, find the corresponding bar index of the chart period in the class object buffer array //--- This bar will be the first of several bars of the class object in the amount of num_bars included in the chart bar int bar=::iBarShift(this.m_symbol,this.m_timeframe,this.m_array_time[0]); if(bar==WRONG_VALUE) { this.m_success &=false; continue; } //--- Calculate the index of the last bar to copy in the loop int end=bar-num_bars; if(end<0) end=-1; //--- In a loop from the first to the last bar included in the bar of the older chart period for(int j=bar;j>end;j--) { //--- Find out which time of this class the bar of the current chart timeframe, corresponding to the loop index, belongs to ::ResetLastError(); if(::CopyTime(this.m_symbol,this.m_timeframe,j,1,this.m_array_time)!=1) { //--- If there is no data in the terminal, move on if(::GetLastError()==4401) continue; //--- Error in obtaining existing data - return false this.m_success &=false; return false; } //--- Get the bar number on the chart corresponding to the time of the buffer array of the calculation part by j loop index int barN=::iBarShift(symbol,timeframe,this.m_array_time[0]); //--- Get the data of the j bar in the buffer array of the calculation part and the data of barN contained in the chart indicator buffer double value_data=this.GetData(buffer_num,array_num,j); double value_buff=buffer[barN]; //--- If the bar on the chart has an empty value, then write data from the buffer array of the calculation part into it. Or //--- if there is already data on the chart, then we write new data to this bar only if the data contains a non-empty value. if(value_buff==this.BufferInitValue(buffer_num) || (value_buff!=this.BufferInitValue(buffer_num) && value_data!=this.BufferInitValue(buffer_num))) buffer[barN]=value_data; } } } } //--- Set initial indexing of the buffer array passed to the method ::ArraySetAsSeries(buffer,as_series); //--- Successful return true; }
多交易品种多周期指标的基本对象类已准备就绪。
现在让我们最终完成后续类。
在所有标准指标中,只有比尔. 威廉姆斯的指标以箭头形式绘制,并带有断点。(抛物线 SAR也以箭头形式绘制,但在图表上没有断点)。
分形指标的绘制从索引为 2 的柱形开始。换句话说,要计算该指标,我们需要在每个分时更新三个柱形:2、1 和 0。
在指标类构造函数中,将计算柱数设为 3:
//+------------------------------------------------------------------+ //| Fractals indicator class | //+------------------------------------------------------------------+ class CIndFractals : public CIndMSTF { public: //--- Constructor CIndFractals(const string symbol,const ENUM_TIMEFRAMES timeframe) : CIndMSTF(IND_FRACTALS,2,symbol,timeframe) { // Buffer indexes: 0 - UPPER_LINE, 1 - LOWER_LINE //--- Create description of parameters //--- If non-current chart symbol or period, their descriptions are added to parameters bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); string param=(current ? "" : StringFormat("(%s)",symbol_period)); //--- Write description of parameters, indicator name, its description, title and category this.SetParameters(param); this.SetName("Fractals"); this.SetDescription("Fractals"); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_WILLIAMS; //--- Description of UPPER_LINE and LOWER_LINE line buffers this.SetBufferDescription(UPPER_LINE,this.m_title+" Up"); this.SetBufferDescription(LOWER_LINE,this.m_title+" Down"); //--- Set drawing style for buffers 0 and 1 and the numbers of calculation part data buffers this.SetBufferDrawType(0,DRAW_ARROW,UPPER_LINE); this.SetBufferDrawType(1,DRAW_ARROW,LOWER_LINE); //--- Set default colors for buffers 0 and 1 this.SetBufferColorToIndex(UPPER_LINE,0,clrGray); this.SetBufferColorToIndex(LOWER_LINE,0,clrGray); //--- Set the number of bars required to calculate the indicator on the current tick this.SetBarsToCalculate(3); } };
在基类中,该值默认设置为两个柱。在这里,我们将数量重新定义为三个柱,并相应地将临时数组的大小也增加到三。
在自定义指标类中,事先并不知道需要多少个柱来计算当前分时。这意味着我们不能像在分形指标类的构造函数中那样,事先指定所需的柱数。在这里,我们必须在类构造函数参数中设置这个数字,并通过输入参数进行传递和设置:
//+------------------------------------------------------------------+ //| Custom indicator class | //+------------------------------------------------------------------+ class CIndCustom : public CIndMSTF { public: //--- Constructor CIndCustom(const string symbol,const ENUM_TIMEFRAMES timeframe, const string path, // path to the indicator (for example, "Examples\\MACD.ex5") const string name, // name of the custom indicator const uint buffers, // number of indicator buffers const uint bars_to_calculate, // number of bars required to calculate the indicator on the current tick const MqlParam ¶m[] // array of custom indicator parameters ) : CIndMSTF(IND_CUSTOM,buffers,symbol,timeframe) { //--- If an empty array of parameters is passed, print this to log int total=(int)param.Size(); if(total==0) ::PrintFormat("%s Error. Passed an empty array",__FUNCTION__); //--- If the array is not empty and its size is increased by 1 (the first string parameter must contain the indicator name) ResetLastError(); if(total>0 && ::ArrayResize(this.m_param,total+1)==total+1) { //--- Reset data in the array and enter name (path to file and name of .ex5 file) ::ZeroMemory(this.m_param); //--- name of the custom indicator this.m_param[0].type=TYPE_STRING; this.m_param[0].string_value=path; //--- fill the array of indicator parameters for(int i=0;i<total;i++) { this.m_param[i+1].type=param[i].type; this.m_param[i+1].double_value=param[i].double_value; this.m_param[i+1].integer_value=param[i].integer_value; this.m_param[i+1].string_value=param[i].string_value; } //--- Create description of parameters //--- If non-current chart symbol or period, their descriptions are added to parameters bool current=(this.Symbol()==::Symbol() && this.Timeframe()==::Period()); string symbol_period=(current ? "" : ::StringFormat("%s,%s",this.Symbol(),this.TimeframeDescription())); string param=(current ? "" : StringFormat("(%s)",symbol_period)); //--- Write description of parameters, indicator name, its description, title and category this.SetParameters(param); this.SetName(name); this.SetDescription(name); this.m_title=this.Name()+this.Parameters(); this.m_category=IND_CATEGORY_CUSTOM; //--- Write a description of the first line buffer this.SetBufferDescription(0,this.m_title); //--- Set the number of bars required to calculate the indicator on the current tick this.SetBarsToCalculate(bars_to_calculate); } } };
在创建新自定义指标对象的方法中,我们还需要在指标集合类中指定计算指标的柱数:
int AddNewVolumes(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_APPLIED_VOLUME applied_volume=VOLUME_TICK); int AddNewCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const string path, // path to the indicator (for example, "Examples\\MACD.ex5") const string name, // name of custom indicator (for example, "Custom MACD") const uint buffers, // number of buffers const uint bars_to_calculate,// number of bars required to calculate the indicator on the current tick const MqlParam ¶m[]); // Array of parameters //--- Timer
而在方法执行代码中,我们需要将该变量的值传递给自定义指标类的构造函数:
//+------------------------------------------------------------------+ //| Add a custom indicator to the collection | //+------------------------------------------------------------------+ int CMSTFIndicators::AddNewCustom(const string symbol,const ENUM_TIMEFRAMES timeframe,const string path,const string name,const uint buffers,const uint bars_to_calculate,const MqlParam ¶m[]) { //--- Create a new indicator object. If there is an error, add a journal message and return INVALID_HANDLE CIndCustom *ind_obj=new CIndCustom(symbol,timeframe,path,name,buffers,bars_to_calculate,param); if(ind_obj==NULL) { ::PrintFormat("%s: Error. Failed to create %s custom indicator object",__FUNCTION__,name); return INVALID_HANDLE; } //--- Return the result of adding the created indicator object to the collection list return this.AddNewIndicator(ind_obj,__FUNCTION__); }
现在,多交易品种多周期指标类应与箭头指标和包含缓冲区间隙数据的指标(DRAW_ARROW, DRAW_COLOR_ARROW, DRAW_SECTION, DRAW_ZIGZAG, DRAW_COLOR_SECTION 和 DRAW_COLOR_ZIGZAG 绘图样式的指标)一起使用。
测试
要进行测试,我可以使用之前创建的任何指标,例如抛物线 SAR,因为它也使用了箭头。它有一个绘制缓冲区,而分形指标有两个。一个缓冲区用于绘制上方部分的分形,第二个缓冲区用于绘制下方部分的分形。让我们修改指标代码,以便在两个缓冲区中绘制箭头,并将其保存为 TestMSTFFractals.mq5。
该指标将同时在图表上显示两个分形指标的数据 - 一个将绘制当前图表周期的分形,另一个将绘制根据设置中指定的交易品种和图表周期计算的分形指标的分形。此外,为了显示分形指标的数据,我们将在本系列文章的所有指标中使用数据面板:
//+------------------------------------------------------------------+ //| TestMSTFFractals.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_chart_window #property indicator_buffers 4 #property indicator_plots 4 //--- enums //--- plot FractalsUp1 #property indicator_label1 "FractalsUp1" #property indicator_type1 DRAW_ARROW #property indicator_color1 clrGray #property indicator_width1 1 //--- plot FractalsDown1 #property indicator_label2 "FractalsDown1" #property indicator_type2 DRAW_ARROW #property indicator_color2 clrGray #property indicator_width2 1 //--- plot FractalsUp2 #property indicator_label3 "FractalsUp2" #property indicator_type3 DRAW_ARROW #property indicator_color3 clrDodgerBlue #property indicator_width3 1 //--- plot FractalsDown2 #property indicator_label4 "FractalsDown2" #property indicator_type4 DRAW_ARROW #property indicator_color4 clrDodgerBlue #property indicator_width4 1 //--- includes #include <IndMSTF\IndMSTF.mqh> #include <Dashboard\Dashboard.mqh> //--- input parameters input string InpSymbol = NULL; /* Symbol */ // Moving average symbol input ENUM_TIMEFRAMES InpTimeframe = PERIOD_CURRENT; /* Timeframe */ // Moving average timeframe input uchar InpArrowShift1 = 10; /* Senior period Arrow VShift */ // Shift the arrows vertically for the higher period input uchar InpArrowShift2 = 10; /* Junior period Arrow VShift */ // Shift the arrows vertically for the lower period input bool InpAsSeries = true; /* As Series flag */ // Timeseries flag of indicator buffer arrays //--- indicator buffers double BufferFractalsUp1[]; double BufferFractalsDn1[]; double BufferFractalsUp2[]; double BufferFractalsDn2[]; //--- global variables int handle_fractals1; int handle_fractals2; CMSTFIndicators indicators; // An instance of the indicator collection object //--- variables for the panel CDashboard *panel=NULL; // Pointer to the panel object int mouse_bar_index; // Index of the bar the data is taken from //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set a timer with an interval of 1 second EventSetTimer(1); //--- Assign the BufferFractalsUp1 and BufferFractalsDn1 arrays to the plot buffers 0 and 1, respectively SetIndexBuffer(0,BufferFractalsUp1,INDICATOR_DATA); SetIndexBuffer(1,BufferFractalsDn1,INDICATOR_DATA); //--- Assign the BufferFractalsUp2 and BufferFractalsDn2 arrays to the plot buffers 2 and 3, respectively SetIndexBuffer(2,BufferFractalsUp2,INDICATOR_DATA); SetIndexBuffer(3,BufferFractalsDn2,INDICATOR_DATA); //--- Define the symbol code from the Wingdings font to draw in PLOT_ARROW PlotIndexSetInteger(0,PLOT_ARROW,217); PlotIndexSetInteger(1,PLOT_ARROW,218); PlotIndexSetInteger(2,PLOT_ARROW,217); PlotIndexSetInteger(3,PLOT_ARROW,218); //--- Set the vertical shift of the arrows PlotIndexSetInteger(0,PLOT_ARROW_SHIFT,-InpArrowShift1); PlotIndexSetInteger(1,PLOT_ARROW_SHIFT, InpArrowShift1); PlotIndexSetInteger(2,PLOT_ARROW_SHIFT,-InpArrowShift2); PlotIndexSetInteger(3,PLOT_ARROW_SHIFT, InpArrowShift2); //--- Set the timeseries flags for the indicator buffer arrays (for testing, to see that there is no difference) ArraySetAsSeries(BufferFractalsUp1,InpAsSeries); ArraySetAsSeries(BufferFractalsDn1,InpAsSeries); ArraySetAsSeries(BufferFractalsUp2,InpAsSeries); ArraySetAsSeries(BufferFractalsDn2,InpAsSeries); //--- Create two indicators of the same type //--- The first one is calculated on the current chart symbol/period, the second - on those specified in the settings handle_fractals1=indicators.AddNewFractals(NULL,PERIOD_CURRENT); handle_fractals2=indicators.AddNewFractals(InpSymbol,InpTimeframe); //--- If failed to create indicator handles, return initialization error if(handle_fractals1==INVALID_HANDLE || handle_fractals2==INVALID_HANDLE) return INIT_FAILED; //--- Set descriptions for indicator lines from buffer descriptions of calculation part of created indicators indicators.SetPlotLabelFromBuffer(0,handle_fractals1,0); indicators.SetPlotLabelFromBuffer(1,handle_fractals1,1); indicators.SetPlotLabelFromBuffer(2,handle_fractals2,0); indicators.SetPlotLabelFromBuffer(3,handle_fractals2,1); //--- Dashboard //--- Create the panel int width=311; panel=new CDashboard(1,20,20,width,264); if(panel==NULL) { Print("Error. Failed to create panel object"); return INIT_FAILED; } //--- Set font parameters panel.SetFontParams("Calibri",9); //--- Display the panel with the "Symbol, Timeframe description" header text panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7)); //--- Create a table with ID 0 to display bar data in it panel.CreateNewTable(0); //--- Draw a table with ID 0 on the panel background panel.DrawGrid(0,2,20,6,2,18,width/2-2); //--- Create a table with ID 1 to display the data of indicator 1 panel.CreateNewTable(1); //--- Get the Y2 table coordinate with ID 0 and //--- set the Y1 coordinate for the table with ID 1 int y1=panel.TableY2(0)+22; //--- Draw a table with ID 1 on the panel background panel.DrawGrid(1,2,y1,2,2,18,width/2-2); //--- Create a table with ID 2 to display the data of indicator 2 panel.CreateNewTable(2); //--- Get the Y2 coordinate of the table with ID 1 and //--- set the Y1 coordinate for the table with ID 2 int y2=panel.TableY2(1)+3; //--- Draw a table with ID 2 on the background of the dashboard panel.DrawGrid(2,2,y2,3,2,18,width/2-2); //--- Initialize the variable with the index of the mouse cursor bar mouse_bar_index=0; //--- Display the data of the current bar on the panel DrawData(mouse_bar_index,TimeCurrent()); //--- Successful initialization return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Delete the timer EventKillTimer(); //--- If the panel object exists, delete it if(panel!=NULL) delete panel; //--- Delete all comments Comment(""); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- Number of bars for calculation int limit=rates_total-prev_calculated; //--- If limit > 1, then this is the first calculation or change in the history if(limit>1) { //--- specify all the available history for calculation limit=rates_total-1; /* // If the indicator has any buffers that display other calculations (not multi-indicators), // initialize them here with the "empty" value set for these buffers */ } //--- Calculate all created multi-symbol multi-period indicators if(!indicators.Calculate()) return 0; //--- Display the bar data under cursor (or current bar if cursor is outside the chart) on the dashboard DrawData(mouse_bar_index,time[mouse_bar_index]); //--- From buffers of calculated indicators, output data to indicator buffers if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals1,0,0,limit,BufferFractalsUp1)) return 0; if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals1,1,0,limit,BufferFractalsDn1)) return 0; if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals2,0,0,limit,BufferFractalsUp2)) return 0; if(!indicators.DataToBuffer(NULL,PERIOD_CURRENT,handle_fractals2,1,0,limit,BufferFractalsDn2)) return 0; //--- return value of prev_calculated for the next call return(rates_total); } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { //--- Call the indicator collection timer indicators.OnTimer(); } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handling the panel //--- Call the panel event handler panel.OnChartEvent(id,lparam,dparam,sparam); //--- If the cursor moves or a click is made on the chart if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK) { //--- Declare the variables to record time and price coordinates in them datetime time=0; double price=0; int wnd=0; //--- If the cursor coordinates are converted to date and time if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- write the bar index where the cursor is located to a global variable mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time); //--- Display the bar data under the cursor on the panel DrawData(mouse_bar_index,time); } } //--- If we received a custom event, display the appropriate message in the journal if(id>CHARTEVENT_CUSTOM) { //--- Here we can implement handling a click on the close button on the panel PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam); } } //+------------------------------------------------------------------+ //| Display data from the specified timeseries index to the panel | //+------------------------------------------------------------------+ void DrawData(const int index,const datetime time) { //--- Declare the variables to receive data in them MqlRates rates[1]; //--- Exit if unable to get the bar data by the specified index if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1) return; //--- Set font parameters for bar and indicator data headers int size=0; uint flags=0; uint angle=0; string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,9,FW_BOLD); panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6); panel.DrawText("Indicators data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6); //--- Set font parameters for bar and indicator data panel.SetFontParams(name,9); //--- Display the data of the specified bar in table 0 on the panel panel.DrawText("Date", panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_DATE), panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90); panel.DrawText("Time", panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_MINUTES), panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90); panel.DrawText("Open", panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()), panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90); panel.DrawText("High", panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()), panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90); panel.DrawText("Low", panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()), panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90); panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()), panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90); //--- Output the data of buffer 0 of indicator 1 from the specified bar into table 1 panel.DrawText(indicators.Title(handle_fractals1)+" Up", panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2); double value10=indicators.GetData(handle_fractals1,0,0,index); string value_str10=(value10!=EMPTY_VALUE ? DoubleToString(value10,indicators.Digits(handle_fractals1)) : " "); panel.DrawText(value_str10,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,110); //--- Output the data of buffer 1 of indicator 1 from the specified bar into table 1 panel.DrawText(indicators.Title(handle_fractals1)+" Down", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2); double value11=indicators.GetData(handle_fractals1,1,0,index); string value_str11=(value11!=EMPTY_VALUE ? DoubleToString(value11,indicators.Digits(handle_fractals1)) : " "); panel.DrawText(value_str11,panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clrNONE,110); //--- Output the data of buffer 0 of indicator 2 from the specified bar into table 2 panel.DrawText(indicators.Title(handle_fractals2)+" Up", panel.CellX(2,0,0)+2, panel.CellY(2,0,0)+2); double value20=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_fractals2,0,0,index); string value_str20=(value20!=EMPTY_VALUE ? DoubleToString(value20,indicators.Digits(handle_fractals2)) : " "); panel.DrawText(value_str20,panel.CellX(2,0,1)+2,panel.CellY(2,0,1)+2,clrNONE,110); //--- Output the data of buffer 1 of indicator 2 from the specified bar into table 2 panel.DrawText(indicators.Title(handle_fractals2)+" Down", panel.CellX(2,1,0)+2, panel.CellY(2,1,0)+2); double value21=indicators.GetDataTo(Symbol(),PERIOD_CURRENT,handle_fractals2,1,0,index); string value_str21=(value21!=EMPTY_VALUE ? DoubleToString(value21,indicators.Digits(handle_fractals2)) : " "); panel.DrawText(value_str21,panel.CellX(2,1,1)+2,panel.CellY(2,1,1)+2,clrNONE,110); //--- Redraw the chart to immediately display all changes on the panel ChartRedraw(ChartID()); } //+------------------------------------------------------------------+
编译指标并在 M1 图表上运行,然后将图表周期切换为 M5 和 M15:
我们可以看到,M1 图表上的指标缓冲区已被为 M5 设计的指标数据填满。
同时,当切换到 M15 时,我们可以看到为 M5 设计的指标的所有分形是如何绘制在 M15 柱形上的。同时,它们似乎不应出现在 M15 图表的每个柱形上。但事实并非如此。在 M15 图表的每个柱形中,有三个来自 M5 图表的柱形,其中包含分形。正是这些分形显示在 M15 图表的柱形上,因为这正是创建该指标的目的 - 成为一个多交易品种多周期指标。
结论
在本文中,我们使用 DRAW_ARROW 绘图样式创建并测试了多交易品种多周期指标类的操作。在随后的文章中,我们将重点介绍其余涉及图表数据分离的绘制样式。此外,我们还将完成尚未考虑的其余标准指标,以结束多交易品种多周期指标这一主题。
文章中使用的所有文件(多指标类、面板类和指标文件)都附在文章中,可供独立测试和使用。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/14105