多銘柄多期間指標のDRAW_ARROW描画タイプ
内容
はじめに
多銘柄多期間指標の話題を続けます。本連載の前回の記事では、多銘柄多期間指標を取り上げました。今回の記事では、多指標クラスを矢印指標で動作するように修正します。
矢印指標は、その描画バッファでデータが常時利用可能ではないことを意味します。この値は、矢印が表示されているバッファに存在しますが、バッファに空の値が設定されていることもあります。通常はEMPTY_VALUEですが、空とみなされチャートに表示されない任意の値をバッファに設定することも可能です。これには次の関数を使用します。
PlotIndexSetDouble(buffer_index,PLOT_EMPTY_VALUE,empty_value);
ここで、buffer_indexは空に設定されたバッファインデックスであり、empty_valueはそのバッファに設定される「空の値」です。
バッファがギャップのあるデータで満たされる複数期間の指標では、矢印のない空の値の存在を考慮する必要があり、空でない値がすでに入力されているチャートバーにこれらの空の値を入力しないようにします。そうでなければ、先に配置された矢印は新しい空の値によって消去されます。これは、下位時間枠で計算された指標のデータが上位時間枠にコピーされた場合に当てはまります。
例を挙げてみます。М15バーがM5チャートに重ねられています。
ここではM5チャートのフラクタルを見ていますが、これはM15チャートのバーに取り付ける必要があります。
- 最初のバーM15 (I)については、バー3に位置する上部フラクタルを設定する必要があります。このフラクタルは、3つのうちの最後であるため、いずれにしてもM15で表示されます。
- 2番目のバーM15 (II)では、バー2に位置するフラクタルを設定する必要があります。このフラクタルは、バー3の空の値をコピーしない場合にのみM15で表示されます。M15バーには、上部と下部の2つのフラクタルがあります。M5では、バー2に下部フラクタルがあり、バー3に上部フラクタルがあるためです。
- M15の3番目のバー(III)では、バー1のトップフラクタルを設定する必要があります。このフラクタルは、バー2と3の空の値をコピーしない場合にのみM15で表示されます。
したがって、現在のチャートのバーの値を追跡し、それが空であれば、下位時間枠のバッファから任意の値をコピーする必要があります。しかし、値が空でなくなった場合は、空でない値もコピーすればよいのです。この場合、上位時間枠のチャートバーには、上位チャート期間のバーの一部である下位時間枠のバーの最後のフラクタルの値が含まれます。なぜ最初のバーではなく、最後のバーなのでしょうか。最後の値の方が現在時刻に近いので、より適切だと思います。
上位時間枠で計算された指標のデータを下位時間枠のチャートにコピーする場合、矢印はチャートのゼロまたは最初のバーではなく、例えば2本目、3本目、またはそれ以上のバーに配置できることを考慮する必要があります。現在多指標クラスでおこなわれているように、最初のバーとゼロのバーのデータだけをコピーすると、チャート上に矢印は表示されません。これらは単にインデックスが高いバーに配置されているため、指標の計算アルゴリズムでは認識できません。
ここでは、インデックス2のバー上にフラクタルが形成されていることがわかります。ゼロと最初のバーだけをコピーすれば、このフラクタルはプログラムからは見えません。
したがって、どの指標がどのバーに矢印を置くかを正確に知ることはできないので、多指標のクラスでは、検索と値のコピーがどのバーから始まるかを明示的に示す変数を導入する必要があります。現時点では、このクラスは現在のティックの最初のバーとゼロバーのみをコピーします。したがって、フラクタルは見ることができません。フラクタルはインデックス2のバーから始まります。
カスタム指標を最初の指標として使用する場合、フラクタル指標のように2本目のバーだけでなく、他のどのバーにも矢印を置くこと ができます。例えば、これがカスタムフラクタルの指標で、異なる次元のフラクタル、例えば3-X-3のフラクタルが検索される場合、矢印はインデックス3(フラクタルが再描画される場合)または4(フラクタルが再描画されない場合)のバーに配置することができます。
標準的な指標については、バーインデックスが既知であるため、明示的に表示します。カスタム指標については、クラスコンストラクタのパラメータで検索開始バーを指定できるようにします。
多指標クラスの改善
クラスメソッドでデータをコピーするときは、ローカルの一時配列を使用しました。これらの配列のために毎回メモリを再割り当てする必要がないように、グローバル配列にしましょう。
多銘柄多期間指標の基本クラス CIndMSTF のprivateセクションで、新しい配列を宣言し、protectセクションで、現在のティックで指標を計算するためのバーの数を格納するための変数を宣言します。
//+------------------------------------------------------------------+ //| 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
クラスのpublicセクションに、現在のティックで指標を計算するのに必要なバーの数を設定するメソッドを記述します。
//--- 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)
メソッドは必要な数のバーを受け取ります。新しい変数には値が設定され、配列のサイズはこの変数の値と等しく設定されます。
このため、配列のサイズが正常に変更されれば、データコピーメソッドで毎回配列を再インストールする必要はなくなります。ここで配列のサイズを変更できない場合、この場合、コピーメソッド内のデータコピー関数は、配列のサイズを必要な量に変更しようとします。これも失敗すると、メソッドはコピーのエラーメッセージを表示します。
したがって、クラスのコンストラクタか、(失敗した場合は)コピーメソッドで、配列に必要なサイズを一度設定します。そして、必要なサイズが設定された配列を使用することで、配列のために常にメモリを再割り当てする手間を省くことができます。
クラスコンストラクタで、デフォルト値を持つメソッドを呼び出し、データ(最初とゼロ)をコピーします。大半の指標では、2本のバーと等しいです。
//+------------------------------------------------------------------+ //| 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); }
ここで、指定されたバッファの配列のデータをコピーするメソッドで、新しい変数にセットされた値を使用してコピーされるデータ量を確認します。以前はハードコードされた2本のバーがありました。
//+------------------------------------------------------------------+ //| 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; }
指定されたバッファのすべての配列のデータをコピーするメソッドでは、以前は2本のバーのコピーがハードコードされていました。さらに、バッファ配列のソースと目標のインデックスも明示的に設定されました。
//+------------------------------------------------------------------+ //| 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; }
現在のティックに対する操作を担当するコードブロック内の計算部バッファからオブジェクトバッファにデータを充填するメソッドにおいて、2本のバーの代わりに、変数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のバーから始まります。言い換えれば、指標を計算するには、各ティックで3本のバーを更新する必要があります。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); } };
基本クラスでは、この量はデフォルトで2バーに設定されています。ここでは、量を3本のバーに再定義し、それに伴って一時的な配列の寸法も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は矢印も適用されるので、そのような指標を使用することができます。フラクタル指標が2つであるのに対して、描画されるバッファは1つです。1つのバッファは上のフラクタル図形を描くのに使用され、もう1つのバッファは下のフラクタル図形を描くのに使用されます。2つのバッファに矢印を描くように指標のコードを修正し、TestMSTFFractals.mq5という名前で保存します。
1つ目は現在のチャート期間からフラクタルを描画し、2つ目は設定で指定された銘柄とチャート期間で計算されたFractals指標からフラクタルを描画します。また、フラクタル指標のデータを表示するために、本連載ではすべての指標でデータパネルを使用します。
//+------------------------------------------------------------------+ //| 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チャートの3本のバーがあります。M15チャートのバー上に表示されるのは、これらのフラクタルです。この指標は、多銘柄多期間指標として作成されたためです。
結論
この記事では、DRAW_ARROW描画スタイルを持つ多銘柄多期間指標のクラスを作成し、その動作をテストしました。次回以降は、チャート上のデータの区切りを伴う残りの描画スタイルに焦点を当てます。さらに、まだ検討されていない残りの標準指標を完成させ、多銘柄多期間指標のトピックを締めくくります。
この記事で使用されているすべてのファイル(多指標クラス、パネルクラス、指標ファイル)は、独立したテストと使用のために記事に添付されています。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/14105
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索