DoEasyライブラリの時系列(第40部): ライブラリに基づいた指標 - 実時間でのデータ更新
Artyom Trishkin | 19 8月, 2020
内容
概念
前の記事では、指標で動作するDoEasyライブラリに注目しました。このタイプのプログラムでは、指標でのリソースを節約する計算の一部の機能と、同じチャートで起動された指標で現在の銘柄とチャート期間のデータを取得する際の制限により、時系列の構築と更新に少し異なるアプローチが必要です。
履歴データの正しいリクエストと読み込みは達成したので、指標で使用されるすべての時系列からのすべてのデータを実時間で更新する機能を開発する必要があります(指標は複数期間であり、起動された指定されたチャート時間枠から操作のデータを受け取ると想定します)。
指標バッファデータを作成する際に、ループを指定した履歴データの深さを持つバーから現在の(ゼロ)バーに移動しました。ここで最も簡単な解決策は、ループインデックスによってデータを取得することです。データは既にライブラリの時系列オブジェクトに作成されているため、インデックスでデータを受け取ることができます。ただし、これは静的データを構築する場合にのみ機能します。時系列リストの実時間でのデータ更新中に、バー番号によるインデックス作成の問題に直面します。時系列リストに新しいバーを追加すると、新しいバーはゼロ(現在)のバーになるため、新しいバーのインデックスは0になります。時系列の以前のすべてのバーのインデックスは1ずつ増加されます。したがって、チャートで新しいバーが開かれるたびに、この新しく表示されたバーを時系列リストに追加し、更新された時系列リスト内の他のすべてのバーの数を1つ増やす必要があります。
これは非常に非現実的です。代わりに、時系列リストのバー時間の操作を適用します。時系列リストの各バーが開く時間は常に同じです。ライブラリ時系列コレクション内の任意のバーへの参照は、バー時間で始まります。また、バー番号によるインデックス付けも残します。ただし、バー番号はバーのプロパティからではなく、受け取る必要があります。代わりに、時系列インデックスによってバーを要求すると、要求されたインデックスによってバー時間が計算され、計算されたバー時間によって必要なバーが時系列リストから受信され、使用されます。
時系列クラスの改善
\MQL5\Include\DoEasy\Defines.mqhで、バーオブジェクトの整数プロパティの列挙からバーインデックスプロパティを削除します。
//+------------------------------------------------------------------+ //| Bar integer properties | //+------------------------------------------------------------------+ enum ENUM_BAR_PROP_INTEGER { BAR_PROP_INDEX = 0, // Bar index in timeseries BAR_PROP_TYPE, // Bar type (from the ENUM_BAR_BODY_TYPE enumeration)
バー時間プロパティと置き換えてバーオブジェクトの整数プロパティの数を1減らします(14から13)。
//+------------------------------------------------------------------+ //| Bar integer properties | //+------------------------------------------------------------------+ enum ENUM_BAR_PROP_INTEGER { BAR_PROP_TIME = 0, // Bar period start time BAR_PROP_TYPE, // Bar type (from the ENUM_BAR_BODY_TYPE enumeration) BAR_PROP_PERIOD, // Bar period (timeframe) BAR_PROP_SPREAD, // Bar spread BAR_PROP_VOLUME_TICK, // Bar tick volume BAR_PROP_VOLUME_REAL, // Bar exchange volume BAR_PROP_TIME_DAY_OF_YEAR, // Bar day serial number in a year BAR_PROP_TIME_YEAR, // A year the bar belongs to BAR_PROP_TIME_MONTH, // A month the bar belongs to BAR_PROP_TIME_DAY_OF_WEEK, // Bar week day BAR_PROP_TIME_DAY, // Bar day of month (number) BAR_PROP_TIME_HOUR, // Bar hour BAR_PROP_TIME_MINUTE, // Bar minute }; #define BAR_PROP_INTEGER_TOTAL (13) // Total number of integer bar properties #define BAR_PROP_INTEGER_SKIP (0) // Number of bar properties not used in sorting //+------------------------------------------------------------------+
したがって、可能なバーの並べ替え基準の列挙では、インデックスによる並べ替えを削除し、バーの時間による並べ替えで置き換えます。
//+------------------------------------------------------------------+ //| Possible bar sorting criteria | //+------------------------------------------------------------------+ #define FIRST_BAR_DBL_PROP (BAR_PROP_INTEGER_TOTAL-BAR_PROP_INTEGER_SKIP) #define FIRST_BAR_STR_PROP (BAR_PROP_INTEGER_TOTAL-BAR_PROP_INTEGER_SKIP+BAR_PROP_DOUBLE_TOTAL-BAR_PROP_DOUBLE_SKIP) enum ENUM_SORT_BAR_MODE { //--- Sort by integer properties SORT_BY_BAR_TIME = 0, // Sort by bar period start time SORT_BY_BAR_TYPE, // Sort by bar type (from the ENUM_BAR_BODY_TYPE enumeration) SORT_BY_BAR_PERIOD, // Sort by bar period (timeframe) SORT_BY_BAR_SPREAD, // Sort by bar spread SORT_BY_BAR_VOLUME_TICK, // Sort by bar tick volume SORT_BY_BAR_VOLUME_REAL, // Sort by bar exchange volume SORT_BY_BAR_TIME_DAY_OF_YEAR, // Sort by bar day number in a year SORT_BY_BAR_TIME_YEAR, // Sort by a year the bar belongs to SORT_BY_BAR_TIME_MONTH, // Sort by a month the bar belongs to SORT_BY_BAR_TIME_DAY_OF_WEEK, // Sort by a bar week day SORT_BY_BAR_TIME_DAY, // Sort by a bar day SORT_BY_BAR_TIME_HOUR, // Sort by a bar hour SORT_BY_BAR_TIME_MINUTE, // Sort by a bar minute //--- Sort by real properties SORT_BY_BAR_OPEN = FIRST_BAR_DBL_PROP, // Sort by bar open price SORT_BY_BAR_HIGH, // Sort by the highest price for the bar period SORT_BY_BAR_LOW, // Sort by the lowest price for the bar period SORT_BY_BAR_CLOSE, // Sort by a bar close price SORT_BY_BAR_CANDLE_SIZE, // Sort by a candle price SORT_BY_BAR_CANDLE_SIZE_BODY, // Sort by a candle body size SORT_BY_BAR_CANDLE_BODY_TOP, // Sort by a candle body top SORT_BY_BAR_CANDLE_BODY_BOTTOM, // Sort by a candle body bottom SORT_BY_BAR_CANDLE_SIZE_SHADOW_UP, // Sort by candle upper wick size SORT_BY_BAR_CANDLE_SIZE_SHADOW_DOWN, // Sort by candle lower wick size //--- Sort by string properties SORT_BY_BAR_SYMBOL = FIRST_BAR_STR_PROP, // Sort by a bar symbol }; //+------------------------------------------------------------------+
CBarクラスを\MQL5\Include\DoEasy\Objects\Series\Bar.mqhでバーの時間を使用するように再構築します。
以前は、SetSymbolPeriod()メソッドは、指定された銘柄、チャート期間、バーオブジェクトのバーインデックスを設定していました。インデックスはバーの時間に置き換えられました。
//--- 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);
メソッドの実装を修正しましょう。
//+------------------------------------------------------------------+ //| Set bar symbol, timeframe and index | //+------------------------------------------------------------------+ void CBar::SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { this.SetProperty(BAR_PROP_TIME,time); this.SetProperty(BAR_PROP_SYMBOL,symbol); this.SetProperty(BAR_PROP_PERIOD,timeframe); this.m_digits=(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS); this.m_period_description=TimeframeDescription(timeframe); } //+------------------------------------------------------------------+
最初のパラメトリッククラスコンストラクタは、バーインデックスの代わりにCBarクラスコンストラクタが呼び出されたバー時間を受け取って、より多くのデータを取得できるようになりました。バーオブジェクトの作成が呼び出されるクラスメソッドの説明を渡すために使用される変数を追加します。
//--- 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);
コンストラクタの実装を修正します。インデックスの代わりに、バー時間を使用して、コンストラクタが呼び出されたクラスメソッドを指定する変数を履歴データの取得エラーを説明するテキストに追加します。
//+------------------------------------------------------------------+ //| Constructor 1 | //+------------------------------------------------------------------+ CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time,const string source) { this.m_type=COLLECTION_SERIES_ID; MqlRates rates_array[1]; this.SetSymbolPeriod(symbol,timeframe,time); ::ResetLastError(); //--- If failed to get the requested data by time and write bar data to the MqlRates array, //--- display an error message, create and fill the structure with zeros, and write it to the rates_array array if(::CopyRates(symbol,timeframe,time,1,rates_array)<1) { int err_code=::GetLastError(); ::Print ( DFUN,"(1)-> ",source,symbol," ",TimeframeDescription(timeframe)," ",::TimeToString(time),": ", CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_BAR_DATA),". ", CMessage::Text(MSG_LIB_SYS_ERROR),"> ",CMessage::Text(err_code)," ", CMessage::Retcode(err_code) ); //--- Set the requested bar time to the structure with zero fields MqlRates err={0}; err.time=time; rates_array[0]=err; } ::ResetLastError(); //--- If failed to set time to the time structure, display the error message if(!::TimeToStruct(rates_array[0].time,this.m_dt_struct)) { int err_code=::GetLastError(); ::Print ( DFUN,"(1) ",symbol," ",TimeframeDescription(timeframe)," ",::TimeToString(time),": ", CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_DT_STRUCT_WRITE),". ", CMessage::Text(MSG_LIB_SYS_ERROR),"> ",CMessage::Text(err_code)," ", CMessage::Retcode(err_code) ); } //--- Set the bar properties this.SetProperties(rates_array[0]); } //+------------------------------------------------------------------+
履歴データを取得するときに表示されるエラーのメッセージにsource変数値を追加すると、クラスとそのメソッドを見つけて、新しいバーオブジェクトを作成する試みが行われます。
また、2番目のパラメトリックコンストラクタはバーインデックスの代わりにバー時間を適用します。
//+------------------------------------------------------------------+ //| Constructor 2 | //+------------------------------------------------------------------+ CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const MqlRates &rates) { this.m_type=COLLECTION_SERIES_ID; this.SetSymbolPeriod(symbol,timeframe,rates.time); ::ResetLastError(); //--- If failed to set time to the time structure, display the error message, //--- create and fill the structure with zeros, set the bar properties from this structure and exit if(!::TimeToStruct(rates.time,this.m_dt_struct)) { int err_code=::GetLastError(); ::Print ( DFUN,"(2) ",symbol," ",TimeframeDescription(timeframe)," ",::TimeToString(rates.time),": ", CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_DT_STRUCT_WRITE),". ", CMessage::Text(MSG_LIB_SYS_ERROR),"> ",CMessage::Text(err_code)," ", CMessage::Retcode(err_code) ); //--- Set the requested bar time to the structure with zero fields MqlRates err={0}; err.time=rates.time; this.SetProperties(err); return; } //--- Set the bar properties this.SetProperties(rates); } //+------------------------------------------------------------------+
クラスのpublicセクションのバーオブジェクトプロパティに簡単にアクセスするためのブロックで、Period()メソッドをTimeframe()に名前変更して、すでに削除されたバープロパティを返すIndex()メソッドを削除します。
//+------------------------------------------------------------------+ //| Methods of simplified access to bar object properties | //+------------------------------------------------------------------+ //--- Return the (1) type, (2) period, (3) spread, (4) tick, (5) exchange volume, //--- (6) bar period start time, (7) year, (8) month the bar belongs to //--- (9) week number since the year start, (10) week number since the month start //--- (11) day, (12) hour, (13) minute ENUM_BAR_BODY_TYPE TypeBody(void) const { return (ENUM_BAR_BODY_TYPE)this.GetProperty(BAR_PROP_TYPE); } ENUM_TIMEFRAMES Timeframe(void) const { return (ENUM_TIMEFRAMES)this.GetProperty(BAR_PROP_PERIOD); } int Spread(void) const { return (int)this.GetProperty(BAR_PROP_SPREAD); } long VolumeTick(void) const { return this.GetProperty(BAR_PROP_VOLUME_TICK); } long VolumeReal(void) const { return this.GetProperty(BAR_PROP_VOLUME_REAL); } datetime Time(void) const { return (datetime)this.GetProperty(BAR_PROP_TIME); } long Year(void) const { return this.GetProperty(BAR_PROP_TIME_YEAR); } long Month(void) const { return this.GetProperty(BAR_PROP_TIME_MONTH); } long DayOfWeek(void) const { return this.GetProperty(BAR_PROP_TIME_DAY_OF_WEEK); } long DayOfYear(void) const { return this.GetProperty(BAR_PROP_TIME_DAY_OF_YEAR); } long Day(void) const { return this.GetProperty(BAR_PROP_TIME_DAY); } long Hour(void) const { return this.GetProperty(BAR_PROP_TIME_HOUR); } long Minute(void) const { return this.GetProperty(BAR_PROP_TIME_MINUTE); } long Index(void) const { return this.GetProperty(BAR_PROP_INDEX); }
Index()メソッドは、存在しないバーオブジェクトプロパティを返す代わりに、バー時間ごとに計算された値を返します。
//--- Return bar symbol string Symbol(void) const { return this.GetProperty(BAR_PROP_SYMBOL); } //--- Return bar index on the specified timeframe the bar time falls into int Index(const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT) const { return ::iBarShift(this.Symbol(),(timeframe>PERIOD_CURRENT ? timeframe : this.Timeframe()),this.Time()); } //+------------------------------------------------------------------+
メソッドは、iBarShift()関数によって計算されたメソッド入力で指定された時間枠の現在の時系列のバーインデックスを返します。
バーオブジェクトの短い名を返すメソッドで、デフォルト値のPERIOD_CURRENTを使用して新しく記述されたメソッドを呼び出します。これは、バーオブジェクトが属する時系列のインデックスを返します。
//+------------------------------------------------------------------+ //| Return the bar object short name | //+------------------------------------------------------------------+ string CBar::Header(void) { return ( CMessage::Text(MSG_LIB_TEXT_BAR)+" \""+this.GetProperty(BAR_PROP_SYMBOL)+"\" "+ TimeframeDescription((ENUM_TIMEFRAMES)this.GetProperty(BAR_PROP_PERIOD))+"["+(string)this.Index()+"]" ); } //+------------------------------------------------------------------+
バーオブジェクトの整数プロパティの説明を返すメソッドから、バーインデックスの説明を返すブロックを削除します。
//+------------------------------------------------------------------+ //| Return the description of the bar integer property | //+------------------------------------------------------------------+ string CBar::GetPropertyDescription(ENUM_BAR_PROP_INTEGER property) { return ( property==BAR_PROP_INDEX ? CMessage::Text(MSG_LIB_TEXT_BAR_INDEX)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BAR_PROP_TYPE ? CMessage::Text(MSG_ORD_TYPE)+
代わりに、バー時間を返すコードブロックを設定します(以下は完全なメソッドコードです)。
//+------------------------------------------------------------------+ //| Return the description of the bar integer property | //+------------------------------------------------------------------+ string CBar::GetPropertyDescription(ENUM_BAR_PROP_INTEGER property) { return ( property==BAR_PROP_TIME ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::TimeToString(this.GetProperty(property),TIME_DATE|TIME_MINUTES|TIME_SECONDS) ) : property==BAR_PROP_TYPE ? CMessage::Text(MSG_ORD_TYPE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.BodyTypeDescription() ) : property==BAR_PROP_PERIOD ? CMessage::Text(MSG_LIB_TEXT_BAR_PERIOD)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.m_period_description ) : property==BAR_PROP_SPREAD ? CMessage::Text(MSG_LIB_TEXT_BAR_SPREAD)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BAR_PROP_VOLUME_TICK ? CMessage::Text(MSG_LIB_TEXT_BAR_VOLUME_TICK)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BAR_PROP_VOLUME_REAL ? CMessage::Text(MSG_LIB_TEXT_BAR_VOLUME_REAL)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==BAR_PROP_TIME_YEAR ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_YEAR)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.Year() ) : property==BAR_PROP_TIME_MONTH ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_MONTH)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+MonthDescription((int)this.Month()) ) : property==BAR_PROP_TIME_DAY_OF_YEAR ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY_OF_YEAR)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)::IntegerToString(this.DayOfYear(),3,'0') ) : property==BAR_PROP_TIME_DAY_OF_WEEK ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY_OF_WEEK)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+DayOfWeekDescription((ENUM_DAY_OF_WEEK)this.DayOfWeek()) ) : property==BAR_PROP_TIME_DAY ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_DAY)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)::IntegerToString(this.Day(),2,'0') ) : property==BAR_PROP_TIME_HOUR ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_HOUR)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)::IntegerToString(this.Hour(),2,'0') ) : property==BAR_PROP_TIME_MINUTE ? CMessage::Text(MSG_LIB_TEXT_BAR_TIME_MINUTE)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)::IntegerToString(this.Minute(),2,'0') ) : "" ); } //+------------------------------------------------------------------+
これでバーオブジェクトクラスの変更は完了です。
標準ライブラリクラスのリストをよく見ると、MQL5\Include\Indicators\には、Series.mqhとTimeSeries.mqhの2つのファイルがあります。
ライブラリには、同じ名前のクラスファイルも含まれていますが、これは誤りです。2つのクラスの名前を変更しましょう。DE(DoEasy)をクラス名とファイル名に追加します。また、これらのファイルおよびクラスへのアクセスが発生する場所でも名前を変更します。これらの変更はSeries.mqh(SeriesDE.mqhおよびCSeriesDEクラスに名前変更)、TimeSeries.mqh(TimeSeriesDE.mqhおよびCTimeSeriesDEクラスに名前変更)、TimeSeriesCollection.mqh(名前変更されたクラスの両方に適用)の3つのファイルに影響を与えます。これらすべてのファイルとそのクラスを順番に検討してみましょう。
Series.mqhファイルは\MQL5\Include\DoEasy\Objects\Series\SeriesDE.mqhとして保存され、クラス名も適切に変更されます。
//+------------------------------------------------------------------+ //| Timeseries class | //+------------------------------------------------------------------+ class CSeriesDE : public CBaseObj { private:
したがって、クラスオブジェクトを返すメソッドには新しいクラスタイプがあります。
public: //--- Return (1) oneself and (2) the timeseries list CSeriesDE *GetObject(void) { return &this; }
GetBarBySeriesIndex時系列のように、インデックスによってバーオブジェクトを返すpublicメソッドの名前は、GetBar()変更されました。同じ種類のメソッドをもう1つ追加して、バーオブジェクトを時系列で開いた時間によって返します。
//--- Return the bar object by (1) a real index in the list, (2) an index as in the timeseries, (3) time and (4) the real list size CBar *GetBarByListIndex(const uint index); CBar *GetBar(const uint index); CBar *GetBar(const datetime time); int DataTotal(void) const { return this.m_list_series.Total(); }
したがって、時間とインデックスによってバーオブジェクトを返すための2つのオーバーロードメソッドがあります。
以下は、開いた時間によってバーオブジェクトを返すメソッドの実装です。
//+------------------------------------------------------------------+ //| Return the bar object by time in the timeseries | //+------------------------------------------------------------------+ CBar *CSeriesDE::GetBar(const datetime time) { CBar *obj=new CBar(this.m_symbol,this.m_timeframe,time,DFUN_ERR_LINE); if(obj==NULL) return NULL; this.m_list_series.Sort(SORT_BY_BAR_TIME); int index=this.m_list_series.Search(obj); delete obj; CBar *bar=this.m_list_series.At(index); return bar; } //+------------------------------------------------------------------+
メソッドは、適切なバーオブジェクトを見つけて返すために使用される時間を受け取ります。
現在の時系列の一時的なバーオブジェクトを作成し、timeプロパティをメソッドに渡されたものと同じにします。
バーオブジェクトのリストを時間で並び替えるフラグを設定して、timeプロパティがメソッドに渡されたものと同じであるバーオブジェクトをリストで検索します 。
検索により、リスト内のバーインデックスが返されます。失敗した場合は-1が返されます。
一時的なバーオブジェクトを削除して取得したインデックスによってリストから必要なバーを取得します。インデックスがゼロ未満の場合、CArrayObjクラスのAt()メソッドはNULLを返します。
オブジェクトが時間までに見つかった場合は、メソッドからオブジェクトバーを返すか、NULLを介します。
以下は、インデックスによってバーオブジェクトを返すメソッドの実装です。
//+------------------------------------------------------------------+ //| Return the bar object by index as in the timeseries | //+------------------------------------------------------------------+ CBar *CSeriesDE::GetBar(const uint index) { datetime time=::iTime(this.m_symbol,this.m_timeframe,index); if(time==0) return NULL; return this.GetBar(time); } //+------------------------------------------------------------------+
メソッドは必要なバーインデックスを受け取ります。
iTime()関数を使用して、インデックスでバー時間を取得し、上述のGetBar()メソッド操作の結果を返します。これは、バーオブジェクトを取得した時間によって返します。
publicクラスセクションで、インデックスによってメインバーのプロパティを返すメソッドと共に、バーの時間によって同じプロパティを返すメソッドを宣言します。
//--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) time, (6) tick volume, (7) real volume, (8) bar spread by index double Open(const uint index,const bool from_series=true); double High(const uint index,const bool from_series=true); double Low(const uint index,const bool from_series=true); double Close(const uint index,const bool from_series=true); datetime Time(const uint index,const bool from_series=true); long TickVolume(const uint index,const bool from_series=true); long RealVolume(const uint index,const bool from_series=true); int Spread(const uint index,const bool from_series=true); //--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) time, (6) tick volume, (7) real volume, (8) bar spread by index double Open(const datetime time); double High(const datetime time); double Low(const datetime time); double Close(const datetime time); datetime Time(const datetime time); long TickVolume(const datetime time); long RealVolume(const datetime time); int Spread(const datetime time);
宣言されたメソッドの実装は後で検討されます。
同じパブリッククラスセクションで、メソッドに渡された配列に、指定され多時系列オブジェクトデータを書き込むことを許可するメソッドを宣言します。
//--- (1) Create and (2) update the timeseries list int Create(const uint required=0); void Refresh(SDataCalculate &data_calculate); //--- Copy the specified double property of the timeseries to the array //--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array bool CopyToBufferAsSeries(const ENUM_BAR_PROP_DOUBLE property,double &array[],const double empty=EMPTY_VALUE); //--- Create and send the "New bar" event to the control program chart void SendEvent(void);
時系列データを指標バッファーに一度に書き込む必要があるとします。バーオブジェクトには、整数と実数の両方のさまざまなプロパティを含めることができます。このメソッドを使用して、任意のバーオブジェクトの実数プロパティを配列に書き込むことができます。すべてのデータは、時系列配列に書き込む場合と同じ方法で配列に書き込まれます。リストの最後にある時系列オブジェクトに格納されている現在のバーデータは、受信者配列のゼロインデックスに書き込まれます。つまり、書き込みは逆方向に行われます。
実装を見てみましょう。
//+------------------------------------------------------------------+ //| Copy the specified double property of the timeseries to the array| //+------------------------------------------------------------------+ bool CSeriesDE::CopyToBufferAsSeries(const ENUM_BAR_PROP_DOUBLE property,double &array[],const double empty=EMPTY_VALUE) { //--- Get the number of bars in the timeseries list int total=this.m_list_series.Total(); if(total==0) return false; //--- If a dynamic array is passed to the method and its size is not equal to that of the timeseries list, //--- set the new size of the passed array equal to that of the timeseries list if(::ArrayIsDynamic(array) && ::ArraySize(array)!=total) if(::ArrayResize(array,total,this.m_required)==WRONG_VALUE) return false; //--- In the loop from the very last timeseries list element (from the current bar) int n=0; for(int i=total-1;i>WRONG_VALUE && !::IsStopped();i--) { //--- get the next bar object by the loop index, CBar *bar=this.m_list_series.At(i); //--- calculate the index, based on which the bar property is saved to the passed array n=total-1-i; //--- write the value of the obtained bar property using the calculated index //--- if the bar is not received or the property is equal to zero, write the value passed to the method as "empty" to the array array[n]=(bar==NULL ? empty : (bar.GetProperty(property)>0 && bar.GetProperty(property)<EMPTY_VALUE ? bar.GetProperty(property) : empty)); } return true; } //+------------------------------------------------------------------+
ご覧のとおり、受信者配列のインデックスは、ソース配列の最後の値が受信者配列のゼロセルに入るように計算されています。したがって、時系列リスト(要求されたバープロパティ)は、銘柄チャート上のバーの番号順に配列(指標バッファなど)に書き込まれますが、時系列リストのバーオブジェクトは逆の順序で配置され、最新の時刻(現在のバー)はリストの最後にあります。これにより、コピーされた時系列の時間枠がチャートの時間枠と一致する場合、時系列リストから指数バッファーにすべてのバーのプロパティをすばやくコピーできます。チャートの時間枠に対して、メソッドを使用して時系列をバッファにコピーします。
両方のクラスコンストラクタで、時系列リストをバー時間で並び替えるフラグを設定します。
//+------------------------------------------------------------------+ //| Constructor 1 (current symbol and period timeseries) | //+------------------------------------------------------------------+ CSeriesDE::CSeriesDE(void) : m_bars(0),m_amount(0),m_required(0),m_sync(false) { this.m_list_series.Clear(); this.m_list_series.Sort(SORT_BY_BAR_TIME); this.SetSymbolPeriod(NULL,(ENUM_TIMEFRAMES)::Period()); this.m_period_description=TimeframeDescription(this.m_timeframe); } //+------------------------------------------------------------------+ //| Constructor 2 (specified symbol and period timeseries) | //+------------------------------------------------------------------+ CSeriesDE::CSeriesDE(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0) : m_bars(0), m_amount(0),m_required(0),m_sync(false) { this.m_list_series.Clear(); this.m_list_series.Sort(SORT_BY_BAR_TIME); this.SetSymbolPeriod(symbol,timeframe); this.m_sync=this.SetRequiredUsedData(required,0); this.m_period_description=TimeframeDescription(this.m_timeframe); } //+------------------------------------------------------------------+
時系列リストを作成する方法では、インデックスによる並べ替えを時間による並べ替えに置き換え、バーオブジェクト作成エラーの場合に表示されるテキストおよび時系列リストに追加する際に発生したエラーを補完します。
//+------------------------------------------------------------------+ //| Create the timeseries list | //+------------------------------------------------------------------+ int CSeriesDE::Create(const uint required=0) { //--- If the required history depth is not set for the list yet, //--- display the appropriate message and return zero, if(this.m_amount==0) { ::Print(DFUN,this.m_symbol," ",TimeframeDescription(this.m_timeframe),": ",CMessage::Text(MSG_LIB_TEXT_BAR_TEXT_FIRS_SET_AMOUNT_DATA)); return 0; } //--- otherwise, if the passed 'required' value exceeds zero and is not equal to the one already set, //--- while being lower than the available bar number, //--- set the new value of the required history depth for the list else if(required>0 && this.m_amount!=required && required<this.m_bars) { //--- If failed to set a new value, return zero if(!this.SetRequiredUsedData(required,0)) return 0; } //--- For the rates[] array we are to receive historical data to, //--- set the flag of direction like in the timeseries, //--- clear the bar object list and set the flag of sorting by bar index MqlRates rates[]; ::ArraySetAsSeries(rates,true); this.m_list_series.Clear(); this.m_list_series.Sort(SORT_BY_BAR_TIME); ::ResetLastError(); //--- Get historical data of the MqlRates structure to the rates[] array starting from the current bar in the amount of m_amount, //--- if failed to get data, display the appropriate message and return zero int copied=::CopyRates(this.m_symbol,this.m_timeframe,0,(uint)this.m_amount,rates),err=ERR_SUCCESS; if(copied<1) { err=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_SERIES_DATA)," ",this.m_symbol," ",TimeframeDescription(this.m_timeframe),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)); return 0; } //--- Historical data is received in the rates[] array //--- In the rates[] array loop, for(int i=0; i<copied; i++) { //--- create a new bar object out of the current MqlRates structure by the loop index ::ResetLastError(); CBar* bar=new CBar(this.m_symbol,this.m_timeframe,rates[i]); if(bar==NULL) { ::Print ( DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_BAR_OBJ)," ",this.Header()," ",::TimeToString(rates[i].time),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(::GetLastError()) ); continue; } //--- If failed to add bar object to the list, //--- display the appropriate message with the error description in the journal if(!this.m_list_series.Add(bar)) { err=::GetLastError(); ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_ADD_TO_LIST)," ",bar.Header()," ",::TimeToString(rates[i].time),". ", CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(err),CMessage::Retcode(err)); } } //--- Return the size of the created bar object list return this.m_list_series.Total(); } //+------------------------------------------------------------------+
リストと時系列データを更新する方法も少し更新されました。
//+------------------------------------------------------------------+ //| Update timeseries list and data | //+------------------------------------------------------------------+ void CSeriesDE::Refresh(SDataCalculate &data_calculate) { //--- If the timeseries is not used, exit if(!this.m_available) return; MqlRates rates[1]; //--- Set the flag of sorting the list of bars by time this.m_list_series.Sort(SORT_BY_BAR_TIME); //--- If a new bar is present on a symbol and period, if(this.IsNewBarManual(data_calculate.rates.time)) { //--- create a new bar object and add it to the end of the list CBar *new_bar=new CBar(this.m_symbol,this.m_timeframe,this.m_new_bar_obj.TimeNewBar(),DFUN_ERR_LINE); if(new_bar==NULL) return; if(!this.m_list_series.InsertSort(new_bar)) { delete new_bar; return; } //--- Write the very first date by a period symbol at the moment and the new time of opening the last bar by a period symbol this.SetServerDate(); //--- if the timeseries exceeds the requested number of bars, remove the earliest bar if(this.m_list_series.Total()>(int)this.m_required) this.m_list_series.Delete(0); //--- save the new bar time as the previous one for the subsequent new bar check this.SaveNewBarTime(data_calculate.rates.time); } //--- Get the bar index with the maximum time (zero bar) and bar object from the list by the obtained index int index=CSelect::FindBarMax(this.GetList(),BAR_PROP_TIME); CBar *bar=this.m_list_series.At(index); if(bar==NULL) return; //--- if the work is performed in an indicator and the timeseries belongs to the current symbol and timeframe, //--- copy price parameters (passed to the method from the outside) to the bar price structure int copied=1; if(this.m_program==PROGRAM_INDICATOR && this.m_symbol==::Symbol() && this.m_timeframe==(ENUM_TIMEFRAMES)::Period()) { rates[0].time=data_calculate.rates.time; rates[0].open=data_calculate.rates.open; rates[0].high=data_calculate.rates.high; rates[0].low=data_calculate.rates.low; rates[0].close=data_calculate.rates.close; rates[0].tick_volume=data_calculate.rates.tick_volume; rates[0].real_volume=data_calculate.rates.real_volume; rates[0].spread=data_calculate.rates.spread; } //--- otherwise, get data to the bar price structure from the environment else copied=::CopyRates(this.m_symbol,this.m_timeframe,0,1,rates); //--- If the prices are obtained, set the new properties from the price structure for the bar object if(copied==1) bar.SetProperties(rates[0]); } //+------------------------------------------------------------------+
ここでは、リストの並び替えも時間によって設定されるようになっています。新しいバーオブジェクトを作成するとき、新しいバーを開くことを定義するときにのみ新しいバーをリストに追加するので、「新しいバー」オブジェクトからクラスコンストラクタにバー時間を渡します 。「新しいバー」オブジェクトにはすでにバーが開いた時間を含みます。それをコンストラクタに渡します。さらに、新しいバーオブジェクトが作成されるメソッドの説明をコンストラクタに渡します。新しいバーオブジェクトが作成できなかった場合、メッセージはCSeriesDE::RefreshメソッドとCBarクラスコンストラクタの呼び出し元のコード文字列を含むコンストラクタから操作ログに送信されます。
時系列リストから最新(現在)のバーを取得するには、時系列リスト内のすべてのバーオブジェクトの最大時間でそれを見つけます。これを行うには、まずCSelectクラスのFindBarMax()メソッドを使用して、最大時間のバーオブジェクトインデックスを見つけます。取得したインデックスを使用して、リストから最後のバーを取り出します。そのバーが現在のバーになります。何らかの理由で現在のバーインデックスを取得できない場合、インデックス値は-1になります。負のインデックスの場合にAt()メソッドを使用してリスト要素を受信するとNULLを取得します。実際にnullの場合は、更新メソッドを終了してください。
以下は、時間によってメインバーオブジェクトのプロパティを返すメソッドです。
//+------------------------------------------------------------------+ //| Return bar's Open by time | //+------------------------------------------------------------------+ double CSeriesDE::Open(const datetime time) { CBar *bar=this.GetBar(time); return(bar!=NULL ? bar.Open() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar's High by time | //+------------------------------------------------------------------+ double CSeriesDE::High(const datetime time) { CBar *bar=this.GetBar(time); return(bar!=NULL ? bar.High() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar's Low by time | //+------------------------------------------------------------------+ double CSeriesDE::Low(const datetime time) { CBar *bar=this.GetBar(time); return(bar!=NULL ? bar.Low() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar's Close by time | //+------------------------------------------------------------------+ double CSeriesDE::Close(const datetime time) { CBar *bar=this.GetBar(time); return(bar!=NULL ? bar.Close() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar time by time | //+------------------------------------------------------------------+ datetime CSeriesDE::Time(const datetime time) { CBar *bar=this.GetBar(time); return(bar!=NULL ? bar.Time() : 0); } //+------------------------------------------------------------------+ //| Return bar tick volume by time | //+------------------------------------------------------------------+ long CSeriesDE::TickVolume(const datetime time) { CBar *bar=this.GetBar(time); return(bar!=NULL ? bar.VolumeTick() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar real volume by time | //+------------------------------------------------------------------+ long CSeriesDE::RealVolume(const datetime time) { CBar *bar=this.GetBar(time); return(bar!=NULL ? bar.VolumeReal() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return bar spread by time | //+------------------------------------------------------------------+ int CSeriesDE::Spread(const datetime time) { CBar *bar=this.GetBar(time); return(bar!=NULL ? bar.Spread() : WRONG_VALUE); } //+------------------------------------------------------------------+
すべてが同じように機能します。
timeseriesリストからバーオブジェクトを時間ごとに取得し、バーオブジェクトの受信エラーを考慮して適切なプロパティの値を返します。
制御プログラムチャートで「新しいバー」イベントを作成して送信するメソッドも、現在のバーオブジェクトを時間までに取得する必要性を考慮して改善されています。
//+------------------------------------------------------------------+ //| Create and send the "New bar" event | //| to the control program chart | //+------------------------------------------------------------------+ void CSeriesDE::SendEvent(void) { 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()); } //+------------------------------------------------------------------+
Refresh()メソッドと同様に、ここでは時系列リストから現在のバーオブジェクトを取得し、カスタムイベントを制御プログラムチャートに送信するときにバーの時間をlparamパラメータに渡します。
これで時系列クラスは完成です。次に、単一の銘柄のすべての時系列のクラスを改善しましょう。
前述のように、CTimeSerirsクラスは、標準ライブラリの同名クラスとの競合を引き起こす可能性があります。そのため、名前をCTimeSerirsDEに変更しました。クラスリスト内で、CTimeSerirs文字列のすべてのインスタンスをCTimeSerirsDEおよびCSerirsからCSerirsDEに置き換えました。詳細な説明を掘り下げるかわりに、次の簡単な例を検討します。
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "SeriesDE.mqh" #include "..\Ticks\NewTickObj.mqh" //+------------------------------------------------------------------+ //| Symbol timeseries class | //+------------------------------------------------------------------+ class CTimeSeriesDE : public CBaseObjExt { private:
クラスのpublicセクションで、指定された時系列のバーの指定された実数プロパティを渡された配列にコピーするメソッドを宣言します。
//--- Copy the specified double property of the specified timeseries to the array //--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array bool CopyToBufferAsSeries(const ENUM_TIMEFRAMES timeframe, const ENUM_BAR_PROP_DOUBLE property, double &array[], const double empty=EMPTY_VALUE); //--- Compare CTimeSeriesDE objects (by symbol) virtual int Compare(const CObject *node,const int mode=0) const; //--- Display (1) description and (2) short symbol timeseries description in the journal void Print(const bool created=true); void PrintShort(const bool created=true); //--- Constructors CTimeSeriesDE(void){;} CTimeSeriesDE(const string symbol); }; //+------------------------------------------------------------------+
CSeriesDEクラスを改善しながら、このメソッドを上記で検討しました。メソッドを実装しましょう。
//+------------------------------------------------------------------+ //| Copy the specified double property of the specified timeseries | //+------------------------------------------------------------------+ bool CTimeSeriesDE::CopyToBufferAsSeries(const ENUM_TIMEFRAMES timeframe, const ENUM_BAR_PROP_DOUBLE property, double &array[], const double empty=EMPTY_VALUE) { CSeriesDE *series=this.GetSeries(timeframe); if(series==NULL) return false; return series.CopyToBufferAsSeries(property,array,empty); } //+------------------------------------------------------------------+
ここではすべてが単純です。まず、指定された時間枠で必要な時系列を取得し、次に取得した時系列オブジェクトからメソッド呼び出しの結果を返します。
時系列インデックスを返すメソッドのすべての銘柄時系列のリストで、検索用に指定された時間枠の検証を実装します。
//+------------------------------------------------------------------+ //| Return the timeframe index in the list | //+------------------------------------------------------------------+ int CTimeSeriesDE::IndexTimeframe(const ENUM_TIMEFRAMES timeframe) { const CSeriesDE *obj=new CSeriesDE(this.m_symbol,(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe)); if(obj==NULL) return WRONG_VALUE; this.m_list_series.Sort(); int index=this.m_list_series.Search(obj); delete obj; return index; } //+------------------------------------------------------------------+
検索用の一時オブジェクトを作成するときは、指定された時間枠を確認し、それがCURRENT_PERIODの場合は、検索に現在の時間枠を使用してください。
指定された時系列リストを更新する方法では、イベントリストに新しいイベントを追加するときに、data_calculate構造体からの新しいバーの開始時間をlparamパラメータ値として使用します。
//+------------------------------------------------------------------+ //| 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); //--- If the timeseries object features the New bar event if(series_obj.IsNewBar(data_calculate.rates.time)) { //--- send the "New bar" event to the control program chart series_obj.SendEvent(); //--- 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,series_obj.Time(data_calculate.rates.time),series_obj.Timeframe(),series_obj.Symbol())) this.m_is_event=true; } } //+------------------------------------------------------------------+
これでCTimeSeriesDEクラスは完成です。すべての銘柄のすべての時系列のオブジェクトのコレクションオブジェクトのCRime SeriesCollectionクラスに移動します。
現在、名前変更された2つのクラス(CSeriesDEとCTimeSerirsDE)があります。CTimeSeriesCollectionクラスのコード内で、CTimeSerirs文字列のすべてのインスタンスをCTimeSerirsDEに、CSerirsをCSerirsDEに置き換えます。
詳細な説明を掘り下げるかわりに、次の簡単な例を検討します。
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Objects\Series\TimeSeriesDE.mqh" #include "..\Objects\Symbols\Symbol.mqh" //+------------------------------------------------------------------+ //| Symbol timeseries collection | //+------------------------------------------------------------------+ class CTimeSeriesCollection : public CBaseObjExt { private: CListObj m_list; // List of applied symbol timeseries //--- Return the timeseries index by symbol name int IndexTimeSeries(const string symbol); public: //--- Return (1) oneself and (2) the timeseries list CTimeSeriesCollection *GetObject(void) { return &this; } CArrayObj *GetList(void) { return &this.m_list; } //--- Return (1) the timeseries object of the specified symbol and (2) the timeseries object of the specified symbol/period CTimeSeriesDE *GetTimeseries(const string symbol); CSeriesDE *GetSeries(const string symbol,const ENUM_TIMEFRAMES timeframe); //--- Create the symbol timeseries list collection
クラスのpublicセクションで、3つの新しいメソッドを宣言します。
これらは、指定された銘柄の指定された時系列のバーオブジェクトをバーの開始時間によって返すメソッド別の時系列のバーの開始時間に対応する単一のタイムシリーズのバーオブジェクトをバーインデックスとバー時間で返す2つのメソッドです。
//--- Return the bar object of the specified timeseries of the specified symbol of the specified position (1) by index, (2) by time //--- bar object of the first timeseries corresponding to the bar open time on the second timeseries (3) by index, (4) by time CBar *GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true); CBar *GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime bar_time); CBar *GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const int index, const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT); CBar *GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const datetime first_bar_time, const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT);
また、publicセクションでさらに2つのメソッドを宣言します。これらは、指定した銘柄のすべての時系列を更新するメソッドと、指定した銘柄の指定した時系列の指定したdoubleプロパティを配列にコピーするメソッドです。
//--- Update (1) the specified timeseries of the specified symbol, (2) all timeseries of the specified symbol, (3) all timeseries of all symbols void Refresh(const string symbol,const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate); void Refresh(const string symbol,SDataCalculate &data_calculate); void Refresh(SDataCalculate &data_calculate); //--- Get events from the timeseries object and add them to the list bool SetEvents(CTimeSeriesDE *timeseries); //--- Display (1) the complete and (2) short collection description in the journal void Print(const bool created=true); void PrintShort(const bool created=true); //--- Copy the specified double property of the specified timeseries of the specified symbol to the array //--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array bool CopyToBufferAsSeries(const string symbol,const ENUM_TIMEFRAMES timeframe, const ENUM_BAR_PROP_DOUBLE property, double &array[], const double empty=EMPTY_VALUE); //--- Constructor CTimeSeriesCollection(); }; //+------------------------------------------------------------------+
指定されたポジションの指定された銘柄の指定された時系列のバーオブジェクトを時間によって返すメソッドを実装します。
//+------------------------------------------------------------------+ //| Return the bar object of the specified timeseries | //| of the specified symbol of the specified position by time | //+------------------------------------------------------------------+ CBar *CTimeSeriesCollection::GetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime bar_time) { CSeriesDE *series=this.GetSeries(symbol,timeframe); if(series==NULL) return NULL; return series.GetBar(bar_time); } //+------------------------------------------------------------------+
このメソッドは、時系列の銘柄と時間枠を渡します。そこから、指定された開始時間のバーを取得する必要があります。
指定された銘柄と時間枠を持つ時系列オブジェクトを取得し、 取得された時系列から取得したバーオブジェクトをバー時間で返します。
バーが取得できなかった場合はNULLを返します。
2番目の時系列のバーの開始時間に対応するインデックスによって、最初の時系列のバーオブジェクトを返すメソッドを実装します。
//+------------------------------------------------------------------+ //| Return the bar object of the first timeseries by index | //| corresponding to the bar open time on the second timeseries | //+------------------------------------------------------------------+ CBar *CTimeSeriesCollection::GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const int index, const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT) { CBar *bar_first=this.GetBar(symbol_first,timeframe_first,index); if(bar_first==NULL) return NULL; CBar *bar_second=this.GetBar(symbol_second,timeframe_second,bar_first.Time()); return bar_second; } //+------------------------------------------------------------------+
このメソッドは、最初のチャートの銘柄と時間枠、最初のチャートのバーインデックス、2番目のチャートの銘柄と期間を受け取ります。
指定されたインデックスによって最初の銘柄期間の時系列から最初のバーオブジェクトを取得し、最初に取得されたバーの時間までに2番目の銘柄期間の2番目のバーオブジェクトを取得して返します。
このメソッドでは、指定された最初のチャート銘柄期間のインデックスで指定されたバーのポジションを、2番目に指定されたチャート期間銘柄のバーのポジションと開始時間で一致させることができます。
メリットは何でしょうか。例として、M15チャート上のすべてのН1バーにすばやくマークを付けることができます。
現在の銘柄、М15チャート期間、チャート上のインデックスによるバーのポジション(たとえば、指標計算ループインデックス)によるバーの位置、現在の銘柄、およびН1期間をメソッドに渡すだけです。 このメソッドは、現在の銘柄チャートとН1期間からバーオブジェクトを返します。その期間には、最初に指定されたバーを開いた時間が含まれます。
2番目の時系列のバーの開始時間に対応する時間で、最初の時系列のバーオブジェクトを返すメソッドを実装します。
//+------------------------------------------------------------------+ //| Return the bar object of the first timeseries by time | //| corresponding to the bar open time on the second timeseries | //+------------------------------------------------------------------+ CBar *CTimeSeriesCollection::GetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const datetime first_bar_time, const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT) { CBar *bar_first=this.GetBar(symbol_first,timeframe_first,first_bar_time); if(bar_first==NULL) return NULL; CBar *bar_second=this.GetBar(symbol_second,timeframe_second,bar_first.Time()); return bar_second; } //+------------------------------------------------------------------+
このメソッドは、上で説明したインデックスによってバーオブジェクトを受け取る方法に似ています。ここでは、時系列のバーインデックスの代わりに、指定された最初の時系列に開始時刻が設定されます。
お気づきかもしれませんが、どちらのメソッドも両方のチャートの期間と記号を受け取ります。これは、メソッドが、時系列で指定されたポジションにある最初の期間銘柄のバーオブジェクトに対応する任意の期間銘柄からバーオブジェクトを取得できることを意味します。これにより、任意の期間銘柄の2つのバーを簡単に照合して、任意のバーオブジェクトプロパティで比較できます。
指定された銘柄の指定された時系列を更新するメソッドに「非ネイティブ銘柄」に対する確認を追加します。
//+------------------------------------------------------------------+ //| Update the specified timeseries of the specified symbol | //+------------------------------------------------------------------+ void CTimeSeriesCollection::Refresh(const string symbol,const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate) { //--- Reset the flag of an event in the timeseries collection and clear the event list this.m_is_event=false; this.m_list_events.Clear(); //--- Get the object of all symbol timeseries by a symbol name CTimeSeriesDE *timeseries=this.GetTimeseries(symbol); if(timeseries==NULL) return; //--- If a symbol is non-native and there is no new tick on the timeseries object symbol, exit if(symbol!=::Symbol() && !timeseries.IsNewTick()) return; //--- Update the required object timeseries of all symbol timeseries timeseries.Refresh(timeframe,data_calculate); //--- If the timeseries has the enabled event flag, //--- get events from symbol timeseries, write them to the collection event list //--- and set the event flag in the collection if(timeseries.IsEvent()) this.m_is_event=this.SetEvents(timeseries); } //+------------------------------------------------------------------+
このプロパティが何故必要なのかと言えば、ライブラリタイマーの現在の期間銘柄に属していないすべての時系列を更新します。プログラムが起動される銘柄に属する時系列は、プログラムのStart、NewTick、またはCalculateイベントハンドラーから更新する必要があります。タイマーの現在の銘柄の新しいティックイベントを回避するには(現在の銘柄時系列はティックによって更新されます)、時系列銘柄が現在の銘柄と一致するかどうかを確認し、時系列が現在の銘柄に属さない場合にのみ「新しいティック」時系列イベントを確認します 。
指定された銘柄のすべての時系列を更新するメソッドを実装します。
//+------------------------------------------------------------------+ //| Update all timeseries of the specified symbol | //+------------------------------------------------------------------+ void CTimeSeriesCollection::Refresh(const string symbol,SDataCalculate &data_calculate) { //--- Reset the flag of an event in the timeseries collection and clear the event list this.m_is_event=false; this.m_list_events.Clear(); //--- Get the object of all symbol timeseries by a symbol name CTimeSeriesDE *timeseries=this.GetTimeseries(symbol); if(timeseries==NULL) return; //--- If a symbol is non-native and there is no new tick on the timeseries object symbol, exit if(symbol!=::Symbol() && !timeseries.IsNewTick()) return; //--- Update all object timeseries of all symbol timeseries timeseries.RefreshAll(data_calculate); //--- If the timeseries has the enabled event flag, //--- get events from symbol timeseries, write them to the collection event list //--- and set the event flag in the collection if(timeseries.IsEvent()) this.m_is_event=this.SetEvents(timeseries); } //+------------------------------------------------------------------+
メソッドロジックの各文字列はコードコメントに記述されているので、ここですべてが明確になることを願っています。
メソッドに渡された配列に、指定された時系列オブジェクトの指定された実際のバーデータを書き込むメソッドを実装します。
//+------------------------------------------------------------------+ //| Copy the specified double property to the array | //| for a specified timeseries of a specified symbol | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::CopyToBufferAsSeries(const string symbol,const ENUM_TIMEFRAMES timeframe, const ENUM_BAR_PROP_DOUBLE property, double &array[], const double empty=EMPTY_VALUE) { CSeriesDE *series=this.GetSeries(symbol,timeframe); if(series==NULL) return false; return series.CopyToBufferAsSeries(property,array,empty); } //+------------------------------------------------------------------+
CSeriesDEクラスを改善しながら、メソッドを上記で検討しました。
ここでは、単純に指定された銘柄と期間によって必要な時系列オブジェクトを取得し、取得した時系列の同じ名前のメソッドを呼び出した結果を返します。
これで、時系列コレクションクラスの作業は完了です。
次に、ライブラリベースのプログラムから新しく作成されたメソッドへのアクセスを提供する必要があります。このようなアクセスは、CEngineライブラリのメインオブジェクトによって提供されます。
\MQL5\Include\DoEasy\Engine.mqhを開いて、CSerirs文字列のすべてのインスタンスをCSerirsDEに、CTimeSerirsをCTimeSerirsDEに置き換えます。
クラスのprivateセクションで、プログラム名を格納するためのクラスメンバー変数を宣言します。
//+------------------------------------------------------------------+ //| Library basis class | //+------------------------------------------------------------------+ class CEngine { private: CHistoryCollection m_history; // Collection of historical orders and deals CMarketCollection m_market; // Collection of market orders and deals CEventsCollection m_events; // Event collection CAccountsCollection m_accounts; // Account collection CSymbolsCollection m_symbols; // Symbol collection CTimeSeriesCollection m_time_series; // Timeseries collection CResourceCollection m_resource; // Resource list CTradingControl m_trading; // Trading management object CPause m_pause; // Pause object CArrayObj m_list_counters; // List of timer counters int m_global_error; // Global error code bool m_first_start; // First launch flag bool m_is_hedge; // Hedge account flag bool m_is_tester; // Flag of working in the tester bool m_is_market_trade_event; // Account trading event flag bool m_is_history_trade_event; // Account history trading event flag bool m_is_account_event; // Account change event flag bool m_is_symbol_event; // Symbol change event flag ENUM_TRADE_EVENT m_last_trade_event; // Last account trading event int m_last_account_event; // Last event in the account properties int m_last_symbol_event; // Last event in the symbol properties ENUM_PROGRAM_TYPE m_program; // Program type string m_name; // Program name
クラスコンストラクタで、変数にプログラム名を割り当てます。
//+------------------------------------------------------------------+ //| CEngine constructor | //+------------------------------------------------------------------+ CEngine::CEngine() : m_first_start(true), m_last_trade_event(TRADE_EVENT_NO_EVENT), m_last_account_event(WRONG_VALUE), m_last_symbol_event(WRONG_VALUE), m_global_error(ERR_SUCCESS) { this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif; this.m_is_tester=::MQLInfoInteger(MQL_TESTER); this.m_program=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE); this.m_name=::MQLInfoString(MQL_PROGRAM_NAME); ...
クラスのpublicセクションで、指定されたポジションの指定された銘柄の指定された時系列のバーオブジェクトをバー時間で返すメソッド、
インデックスと時間によって、2番目の時系列のバーの開始時間に対応する1番目の時系列のバーオブジェクトを返す2つのメソッド、
指定された銘柄のすべての時系列を更新するメソッド、
時間によってバーの基本プロパティを返すメソッド、
指定された銘柄の指定された時系列の指定されたdoubleプロパティを配列にコピーするメソッド、
ライブラリベースのプログラムの名前を返すメソッドを追加します。
//--- Return the bar object of the specified timeseries of the specified symbol of the specified position (1) by index, (2) by time CBar *SeriesGetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const bool from_series=true) { return this.m_time_series.GetBar(symbol,timeframe,index,from_series); } CBar *SeriesGetBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { return this.m_time_series.GetBar(symbol,timeframe,time); } //--- Return the bar object of the first timeseries corresponding to the bar open time on the second timeseries (1) by index, (2) by time CBar *SeriesGetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const int index, const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT) { return this.m_time_series.GetBarSeriesFirstFromSeriesSecond(symbol_first,timeframe_first,index,symbol_second,timeframe_second); } CBar *SeriesGetBarSeriesFirstFromSeriesSecond(const string symbol_first,const ENUM_TIMEFRAMES timeframe_first,const datetime time, const string symbol_second=NULL,const ENUM_TIMEFRAMES timeframe_second=PERIOD_CURRENT) { return this.m_time_series.GetBarSeriesFirstFromSeriesSecond(symbol_first,timeframe_first,time,symbol_second,timeframe_second); } //--- Return the flag of opening a new bar of the specified timeseries of the specified symbol bool SeriesIsNewBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time=0) { return this.m_time_series.IsNewBar(symbol,timeframe,time); } //--- Update (1) the specified timeseries of the specified symbol, (2) all timeseries of the specified symbol, (3) all timeseries of all symbols void SeriesRefresh(const string symbol,const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate) { this.m_time_series.Refresh(symbol,timeframe,data_calculate); } void SeriesRefresh(const string symbol,SDataCalculate &data_calculate) { this.m_time_series.Refresh(symbol,data_calculate); } void SeriesRefresh(SDataCalculate &data_calculate) { this.m_time_series.Refresh(data_calculate); } //--- Return (1) the timeseries object of the specified symbol and (2) the timeseries object of the specified symbol/period CTimeSeriesDE *SeriesGetTimeseries(const string symbol) { return this.m_time_series.GetTimeseries(symbol); } CSeriesDE *SeriesGetSeries(const string symbol,const ENUM_TIMEFRAMES timeframe) { return this.m_time_series.GetSeries(symbol,timeframe); } //--- 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 (1) Open, (2) High, (3) Low, (4) Close, (5) Time, (6) TickVolume, //--- (7) RealVolume, (8) Spread of the bar, specified by index, of the specified symbol of the specified timeframe double SeriesOpen(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); double SeriesHigh(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); double SeriesLow(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); double SeriesClose(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); datetime SeriesTime(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); long SeriesTickVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); long SeriesRealVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); int SeriesSpread(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index); //--- Return (1) Open, (2) High, (3) Low, (4) Close, (5) Time, (6) TickVolume, //--- (7) RealVolume, (8) Spread of the bar, specified by time, of the specified symbol of the specified timeframe double SeriesOpen(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); double SeriesHigh(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); double SeriesLow(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); double SeriesClose(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); datetime SeriesTime(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); long SeriesTickVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); long SeriesRealVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); int SeriesSpread(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time); //--- Copy the specified double property of the specified timeseries of the specified symbol to the array //--- Regardless of the array indexing direction, copying is performed the same way as copying to a timeseries array bool SeriesCopyToBufferAsSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const ENUM_BAR_PROP_DOUBLE property, double &array[],const double empty=EMPTY_VALUE) { return this.m_time_series.CopyToBufferAsSeries(symbol,timeframe,property,array,empty);}
...
//--- Return the program name string Name(void) const { return this.m_name; }
クラス本体で実装が設定されているすべてのメソッドは、上記で検討したTimeSeriesCollection時系列のコレクションの同じ名前のメソッドを呼び出した結果を返します。
時間によってバーの基本プロパティを返すメソッドを実装します。
//+------------------------------------------------------------------+ //| Return Open of the specified bar by time | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ double CEngine::SeriesOpen(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time); return(bar!=NULL ? bar.Open() : 0); } //+------------------------------------------------------------------+ //| Return High of the specified bar by time | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ double CEngine::SeriesHigh(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time); return(bar!=NULL ? bar.High() : 0); } //+------------------------------------------------------------------+ //| Return Low of the specified bar by time | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ double CEngine::SeriesLow(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time); return(bar!=NULL ? bar.Low() : 0); } //+------------------------------------------------------------------+ //| Return Close of the specified bar by time | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ double CEngine::SeriesClose(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time); return(bar!=NULL ? bar.Close() : 0); } //+------------------------------------------------------------------+ //| Return Time of the specified bar by time | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ datetime CEngine::SeriesTime(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time); return(bar!=NULL ? bar.Time() : 0); } //+------------------------------------------------------------------+ //| Return TickVolume of the specified bar by time | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ long CEngine::SeriesTickVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time); return(bar!=NULL ? bar.VolumeTick() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return RealVolume of the specified bar by time | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ long CEngine::SeriesRealVolume(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time); return(bar!=NULL ? bar.VolumeReal() : WRONG_VALUE); } //+------------------------------------------------------------------+ //| Return Spread of the specified bar by time | //| of the specified symbol of the specified timeframe | //+------------------------------------------------------------------+ int CEngine::SeriesSpread(const string symbol,const ENUM_TIMEFRAMES timeframe,const datetime time) { CBar *bar=this.m_time_series.GetBar(symbol,timeframe,time); return(bar!=NULL ? bar.Spread() : INT_MIN); } //+------------------------------------------------------------------+
ここではすべてが簡単です。
時系列銘柄と期間、および時系列で要求されたバーを開いた時間を指定してGetBar()メソッドを使用して時系列コレクションクラスからバーオブジェクトを取得し 、受信エラーを考慮して、 取得されたバーの適切なプロパティの値を返します。
現在の銘柄のNewTickイベントハンドラに現在の銘柄のすべての時系列の更新を追加します。
//+------------------------------------------------------------------+ //| NewTick event handler | //+------------------------------------------------------------------+ void CEngine::OnTick(SDataCalculate &data_calculate,const uint required=0) { //--- If this is not a EA, exit if(this.m_program!=PROGRAM_EXPERT) return; //--- Re-create empty timeseries and update the current symbol timeseries this.SeriesSync(data_calculate,required); this.SeriesRefresh(NULL,data_calculate); //--- end } //+------------------------------------------------------------------+
同期試行の直後に、EA内の現在の銘柄に適用されたすべての時系列を更新できるようになります。それにより、ライブラリタイマーで現在の銘柄の時系列の更新を待つ必要がなくなり、新しいティックが現在の銘柄に到着した後にタイマーのデータ更新が呼び出されると、データの非同期が発生することがあります。
現在の銘柄の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 data_calculate.rates_total; //--- 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 and return rates_total this.SeriesRefresh(NULL,data_calculate); return data_calculate.rates_total; } //+------------------------------------------------------------------+
OnTick()ハンドラとの違いは次のとおりです。現在の銘柄に適用されたすべての時系列が同期されるまで、メソッドはゼロを返します。これにより、OnCalculate()ハンドラに履歴データを完全に再計算する必要性に関する指標が通知されます。
したがって、すべての時系列のデータを同期するメソッドは、ブール値を返すはずです。
//+------------------------------------------------------------------+ //| Synchronize timeseries data with the server | //+------------------------------------------------------------------+ bool CEngine::SeriesSync(SDataCalculate &data_calculate,const uint required=0) { //--- If the timeseries data is not calculated, try re-creating the timeseries //--- Get the pointer to the empty timeseries CSeriesDE *series=this.SeriesGetSeriesEmpty(); //--- If there is an empty timeseries if(series!=NULL) { //--- Display the empty timeseries data as a chart comment and try synchronizing the timeseries with the server data ::Comment(series.Header(),": ",CMessage::Text(MSG_LIB_TEXT_TS_TEXT_WAIT_FOR_SYNC)); ::ChartRedraw(::ChartID()); //--- if the data has been synchronized if(series.SyncData(required,data_calculate.rates_total)) { //--- if managed to re-create the timeseries if(this.m_time_series.ReCreateSeries(series.Symbol(),series.Timeframe(),data_calculate.rates_total)) { //--- display the chart comment and the journal entry with the re-created timeseries data ::Comment(series.Header(),": OK"); ::ChartRedraw(::ChartID()); Print(series.Header()," ",CMessage::Text(MSG_LIB_TEXT_TS_TEXT_CREATED_OK),":"); series.PrintShort(); return true; } } //--- Data is not yet synchronized or failed to re-create the timeseries return false; } //--- There are no empty timeseries - all is synchronized, delete all comments else { ::Comment(""); ::ChartRedraw(::ChartID()); return true; } return false; } //+------------------------------------------------------------------+
これで、CEngineクラスが完成しました。
次に、これらすべてが指標でどのように機能するかを確認します。単一の指標でいくつかの異なる時系列を使用し、他の時系列から最初のバーの境界内にある別のバーのデータに対応する単一のバーデータを取得できるため、最初に頭に浮かぶのは、 現在のチャートに他の時間枠からのバーのOHLCラインを表示する指標を作成することです。
複数期間指標の作成とテスト
テストを実行するには、前の記事のEAを使用して、\MQL5\Indicators\TestDoEasy\Part40\ TestDoEasyPart40.mq5として保存します。
標準の利用可能なチャート期間の数で21時系列を使用できます。設定には、使用される時間枠の標準セットが備わっていますが、チャートには、設定で選択された使用される時間枠に対応するボタンが表示されます。指標バッファ用の過剰なコードを回避するには、構造体配列を使用して、ターミナルに存在する各チャート期間にバッファを割り当てるだけです。
チャートのバッファラインと指標データウィンドウでのそのデータの可視性は、適切なボタンを有効/無効にすることで有効/無効になります。2つのバッファ(描画および計算)が各時間枠に割り当てられます。計算されたバッファにより、対応する時系列の中間データを保存できます。ただし、現在の実装では、計算されたバッファは使用されません。42のすべてのバッファ(21が描画され、21が計算されたバッファ)の書き込みを回避するために、各時間枠のパラメータを格納する構造を作成します。
- 描画された指標バッファによって割り当てられた配列
- 計算された指標バッファによって割り当てられた配列
- バッファーID(データがバッファーによって表示される時系列の時系列)
- 描画されたバッファ配列に関連する指標バッファーのインデックス
- 計算されたバッファ配列に関連する指標バッファーのインデックス
- 指標でバッファを使用するフラグ(ボタンが押されている/押されていない)
- チャートボタンによるバッファ表示を有効/無効にする前に指標にバッファを表示するフラグ
指標の設定により、各時間枠を使用する必要があるかどうか、したがって、どの時系列を選択するかを決定できます。選択した時系列に従ってプロットされたチャートボタンを使用すると、チャート上の対応する指標バッファの表示を有効または無効にできます。ボタンによって表示が有効/無効になるまで指標にバッファを表示するフラグにより、適切なボタンが押されたときにのみ、チャート上のバッファデータの削除または表示を決定できます。
各指標バッファのすべてのパラメータを設定します(プログラムで設定することもできますが、現在の方法の方が高速です)。
//+------------------------------------------------------------------+ //| TestDoEasyPart40.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_chart_window #property indicator_buffers 43 #property indicator_plots 21 //--- plot M1 #property indicator_label1 " M1" #property indicator_type1 DRAW_LINE #property indicator_color1 clrGray #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- plot M2 #property indicator_label2 " M2" #property indicator_type2 DRAW_LINE #property indicator_color2 clrGray #property indicator_style2 STYLE_SOLID #property indicator_width2 1 //--- plot M3 #property indicator_label3 " M3" #property indicator_type3 DRAW_LINE #property indicator_color3 clrGray #property indicator_style3 STYLE_SOLID #property indicator_width3 1 //--- plot M4 #property indicator_label4 " M4" #property indicator_type4 DRAW_LINE #property indicator_color4 clrGray #property indicator_style4 STYLE_SOLID #property indicator_width4 1 //--- plot M5 #property indicator_label5 " M5" #property indicator_type5 DRAW_LINE #property indicator_color5 clrGray #property indicator_style5 STYLE_SOLID #property indicator_width5 1 //--- plot M6 #property indicator_label6 " M6" #property indicator_type6 DRAW_LINE #property indicator_color6 clrGray #property indicator_style6 STYLE_SOLID #property indicator_width6 1 //--- plot M10 #property indicator_label7 " M10" #property indicator_type7 DRAW_LINE #property indicator_color7 clrGray #property indicator_style7 STYLE_SOLID #property indicator_width7 1 //--- plot M12 #property indicator_label8 " M12" #property indicator_type8 DRAW_LINE #property indicator_color8 clrGray #property indicator_style8 STYLE_SOLID #property indicator_width8 1 //--- plot M15 #property indicator_label9 " M15" #property indicator_type9 DRAW_LINE #property indicator_color9 clrGray #property indicator_style9 STYLE_SOLID #property indicator_width9 1 //--- plot M20 #property indicator_label10 " M20" #property indicator_type10 DRAW_LINE #property indicator_color10 clrGray #property indicator_style10 STYLE_SOLID #property indicator_width10 1 //--- plot M30 #property indicator_label11 " M30" #property indicator_type11 DRAW_LINE #property indicator_color11 clrGray #property indicator_style11 STYLE_SOLID #property indicator_width11 1 //--- plot H1 #property indicator_label12 " H1" #property indicator_type12 DRAW_LINE #property indicator_color12 clrGray #property indicator_style12 STYLE_SOLID #property indicator_width12 1 //--- plot H2 #property indicator_label13 " H2" #property indicator_type13 DRAW_LINE #property indicator_color13 clrGray #property indicator_style13 STYLE_SOLID #property indicator_width13 1 //--- plot H3 #property indicator_label14 " H3" #property indicator_type14 DRAW_LINE #property indicator_color14 clrGray #property indicator_style14 STYLE_SOLID #property indicator_width14 1 //--- plot H4 #property indicator_label15 " H4" #property indicator_type15 DRAW_LINE #property indicator_color15 clrGray #property indicator_style15 STYLE_SOLID #property indicator_width15 1 //--- plot H6 #property indicator_label16 " H6" #property indicator_type16 DRAW_LINE #property indicator_color16 clrGray #property indicator_style16 STYLE_SOLID #property indicator_width16 1 //--- plot H8 #property indicator_label17 " H8" #property indicator_type17 DRAW_LINE #property indicator_color17 clrGray #property indicator_style17 STYLE_SOLID #property indicator_width17 1 //--- plot H12 #property indicator_label18 " H12" #property indicator_type18 DRAW_LINE #property indicator_color18 clrGray #property indicator_style18 STYLE_SOLID #property indicator_width18 1 //--- plot D1 #property indicator_label19 " D1" #property indicator_type19 DRAW_LINE #property indicator_color19 clrGray #property indicator_style19 STYLE_SOLID #property indicator_width19 1 //--- plot W1 #property indicator_label20 " W1" #property indicator_type20 DRAW_LINE #property indicator_color20 clrGray #property indicator_style20 STYLE_SOLID #property indicator_width20 1 //--- plot MN1 #property indicator_label21 " MN1" #property indicator_type21 DRAW_LINE #property indicator_color21 clrGray #property indicator_style21 STYLE_SOLID #property indicator_width21 1 //--- classes
ご覧のとおり、バッファの数は43に設定されており、描画されたバッファの数は21に設定されています。描画された各バッファに1つの計算されたバッファを追加することにしたので、結果は21 + 21 = 42になります。余分なバッファはどこから来るのでしょうか。これは、time[] OnCalculate()配列から時間通りにデータを格納するために必要です。time[]配列がOnCalculate()ハンドラの可視性スコープ内にのみ存在する一方、一部の関数はインデックスによるバーの時間を必要とするため、現在の時間枠の各バーの時間データを持つ最も簡単な解決策は、time[]配列を指標の計算されたバッファの1つに保存することです。これが、もう1つのバッファを設定した理由です。
この指標は、始値、高値、安値、終値の4つのバー価格を表示する機能を提供します。バーオブジェクトには、他の実数プロパティがあります。
- バーの始め値(Open)
- バー期間の最高値(High)
- バー期間の最安値(Low)
- バー決済価格(Close)
- ローソク足サイズ
- ローソク足実体サイズ
- ローソク足実体の上限
- ローソク足実体の下限
- ローソク足上髭サイズ
- ローソク足下髭サイズ
したがって、設定で列挙値(ENUM_BAR_PROP_DOUBLE)を使用することはできません。表示の設定で選択できるENUM_BAR_PROP_DOUBLEバーオブジェクトの実際のプロパティの列挙プロパティと一致する必要なプロパティを備えた別の列挙を作成して、使用可能なチャート期間の合計量でマクロ置換を設定します。
//--- classes //--- enums enum ENUM_BAR_PRICE { BAR_PRICE_OPEN = BAR_PROP_OPEN, // Bar Open BAR_PRICE_HIGH = BAR_PROP_HIGH, // Bar High BAR_PRICE_LOW = BAR_PROP_LOW, // Bar Low BAR_PRICE_CLOSE = BAR_PROP_CLOSE, // Bar Close }; //--- defines #define PERIODS_TOTAL (21) // Total amount of available chart periods //--- structures
次に、1つの時系列(チャート期間)に割り当てられた1つの描画バッファと1つの計算バッファのデータ構造を作成します。
//--- structures struct SDataBuffer { private: int m_buff_id; // Buffer ID (timeframe) int m_buff_data_index; // The index of the indicator buffer related to the Data[] array int m_buff_tmp_index; // The index of the indicator buffer related to the Temp[] array bool m_used; // The flag of using the buffer in the indicator bool m_show_data; // The flag of displaying the buffer on the chart before enabling/disabling its display public: double Data[]; // The array assigned as INDICATOR_DATA by the indicator buffer double Temp[]; // The array assigned as INDICATOR_CALCULATIONS by the indicator buffer //--- Set indices for the drawn and calculated buffers assigned to the timeframe void SetIndex(const int index) { this.m_buff_data_index=index; this.m_buff_tmp_index=index+PERIODS_TOTAL; } //--- Methods of setting and returning values of the private structure members void SetID(const int id) { this.m_buff_id=id; } void SetUsed(const bool flag) { this.m_used=flag; } void SetShowData(const bool flag) { this.m_show_data=flag; } int IndexDataBuffer(void) const { return this.m_buff_data_index; } int IndexTempBuffer(void) const { return this.m_buff_tmp_index; } int ID(void) const { return this.m_buff_id; } bool IsUsed(void) const { return this.m_used; } bool GetShowDataFlag(void) const { return this.m_show_data; } void Print(void); }; //--- Display structure data to the journal void SDataBuffer::Print(void) { ::Print ( "Buffer[",this.IndexDataBuffer(),"], ID: ",(string)this.ID(), " (",TimeframeDescription((ENUM_TIMEFRAMES)this.ID()), "), temp buffer index: ",(string)this.IndexTempBuffer(), ", used: ",this.IsUsed() ); } //--- input variables
構造は、単一の時間枠で作業するためのすべてのデータを格納することです。使用される指標の各時間枠に個別の構造体が割り当てられます。適切な構造体の配列は、そのための最も最適なソリューションです。指標バッファを定義するためのブロックで作成しましょう。
指標の入力を書き込みます。
//--- input variables /*sinput*/ENUM_SYMBOLS_MODE InpModeUsedSymbols= SYMBOLS_MODE_CURRENT; // Mode of used symbols list /*sinput*/string InpUsedSymbols = "EURUSD,AUDUSD,EURAUD,EURCAD,EURGBP,EURJPY,EURUSD,GBPUSD,NZDUSD,USDCAD,USDJPY"; // List of used symbols (comma - separator) sinput ENUM_TIMEFRAMES_MODE InpModeUsedTFs = TIMEFRAMES_MODE_LIST; // Mode of used timeframes list sinput string InpUsedTFs = "M1,M5,M15,M30,H1,H4,D1,W1,MN1"; // List of used timeframes (comma - separator) sinput ENUM_BAR_PRICE InpBarPrice = BAR_PRICE_OPEN; // Applied bar price sinput bool InpShowBarTimes = false; // Show bar time comments sinput uint InpControlBar = 1; // Control bar sinput uint InpButtShiftX = 0; // Buttons X shift sinput uint InpButtShiftY = 10; // Buttons Y shift sinput bool InpUseSounds = true; // Use sounds //--- indicator buffers
これはすべて、各記事に提供するテストEAと指標に似ています。単一の銘柄での作業をテストするので、銘柄設定のsinput修飾子をコメントアウトして、変数が指標入力であることを示します(sinput修飾子は、変数のパラメータ最適化が無効であることを示します)。したがって、SYMBOLS_MODE_CURRENT値がInpModeUsedSymbols変数に割り当てられている間、これらのパラメータを設定で選択することはできません。現在の銘柄でのみ機能します。
InpShowBarTimes変数は、チャートのコメントの表示/非表示を許可します。テストされた時系列のチャート上の同じ時刻のバーと一致する現在のチャート期間にバーを表示します。InpControlBar変数は、チャートのコメントを介して値を追跡できるバーのインデックスを指定するために使用されます。
最後に、指標バッファとグローバル変数を記述します。
//--- indicator buffers SDataBuffer Buffers[PERIODS_TOTAL]; // Array of the indicator buffer data structures assigned to the timeseries double BufferTime[]; // The calculated buffer for storing and passing data from the time[] array //--- global variables CEngine engine; // CEngine library main object string prefix; // Prefix of graphical object names bool testing; // Flag of working in the tester int used_symbols_mode; // Mode of working with symbols string array_used_symbols[]; // Array of used symbols string array_used_periods[]; // Array of used timeframes //+------------------------------------------------------------------+
ご覧のとおり、上記の構造体配列を指標バッファの定義として設定しました。指標を初期化するとき、データを構造体配列に割り当て、構造体配列を指標バッファにバインドします。時間を保存して指標関数に渡すためにここで計算されたバッファが定義されます。
指標のグローバル変数はコメントされており、かなり理解しやすいと思います。
指標のOnInit()ハンドラで、まず、設定で選択された時間枠に対応するボタンを備えたパネルを作成します。 次に、すべての指標バッファーを割り当て、すべての指標バッファパラメータを指標バッファ構造体の配列にある構造体に設定します。
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set indicator global variables prefix=engine.Name()+"_"; testing=engine.IsTester(); ZeroMemory(rates_data); //--- Initialize DoEasy library OnInitDoEasy(); //--- Check and remove remaining indicator graphical objects if(IsPresentObectByPrefix(prefix)) ObjectsDeleteAll(0,prefix); //--- Create the button panel if(!CreateButtons(InpButtShiftX,InpButtShiftY)) return INIT_FAILED; //--- 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 //--- In the loop by the total number of available timeframes, for(int i=0;i<PERIODS_TOTAL;i++) { //--- get the next timeframe ENUM_TIMEFRAMES timeframe=TimeframeByEnumIndex(uchar(i+1)); //--- Bind the drawn indicator buffer by the buffer index equal to the loop index with the structure Data[] array SetIndexBuffer(i,Buffers[i].Data); //--- set "the empty value" for the Data[] buffer, //--- set the name of the graphical series displayed in the data window for the Data[] buffer //--- set the direction of indexing the Data[] drawn buffer as in the timeseries PlotIndexSetDouble(i,PLOT_EMPTY_VALUE,EMPTY_VALUE); PlotIndexSetString(i,PLOT_LABEL,"Buffer "+TimeframeDescription(timeframe)); ArraySetAsSeries(Buffers[i].Data,true); //--- Setting the drawn buffer according to the button status bool state=false; //--- Set the name of the button correspondign to the buffer with the loop index and its timeframe string name=prefix+"BUTT_"+TimeframeDescription(timeframe); //--- If not in the tester, while the chart features the button with the specified name, if(!engine.IsTester() && ObjectFind(ChartID(),name)==0) { //--- set the name of the terminal global variable for storing the button status string name_gv=(string)ChartID()+"_"+name; //--- if no global variable with such a name is found, create it set to 'false', if(!GlobalVariableCheck(name_gv)) GlobalVariableSet(name_gv,false); //--- get the button status from the terminal global variable state=GlobalVariableGet(name_gv); } //--- Set the values for all structure fields Buffers[i].SetID(timeframe); Buffers[i].SetIndex(i); Buffers[i].SetUsed(state); Buffers[i].SetShowData(state); //--- Set the button status ButtonState(name,state); //--- Depending on the button status, specify whether the buffer data should be displayed should be displayed in the data window PlotIndexSetInteger(i,PLOT_SHOW_DATA,state); //--- Bind the calculated indicator buffer by the buffer index from IndexTempBuffer() with the Temp[] array of the structure SetIndexBuffer(Buffers[i].IndexTempBuffer(),Buffers[i].Temp,INDICATOR_CALCULATIONS); //--- set the direction of indexing the Temp[] calculated buffer as in the timeseries ArraySetAsSeries(Buffers[i].Temp,true); } //--- Bind the calculated indicator buffer by the PERIODS_TOTAL*2 buffer index with the BufferTime[] array of the indicator SetIndexBuffer(PERIODS_TOTAL*2,BufferTime,INDICATOR_CALCULATIONS); //--- set the direction of indexing the BufferTime[] calculated buffer as in the timeseries ArraySetAsSeries(BufferTime,true); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
ここでは、構造体配列によって指標バッファがループインデックスにバインドされ、残りのパラメータが各構造体配列セルに格納されている各構造体に設定されているループのすべての文字列についてコメントしました。ご不明な点がございましたら、コメントでお気軽にお問い合わせください。
以下はボタン関数です。
//+------------------------------------------------------------------+ //| Create the buttons panel | //+------------------------------------------------------------------+ bool CreateButtons(const int shift_x=20,const int shift_y=0) { int total=ArraySize(array_used_periods); uint w=30,h=20,x=InpButtShiftX+1, y=InpButtShiftY+h+1; //--- In the loop by the amount of used timeframes for(int i=0;i<total;i++) { //--- create the name of the next button string butt_name=prefix+"BUTT_"+array_used_periods[i]; //--- create a new button with the offset by ((button width + 1) * loop index) if(!ButtonCreate(butt_name,x+(w+1)*i,y,w,h,array_used_periods[i],clrGray)) { Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),array_used_periods[i]); return false; } } ChartRedraw(0); return true; } //+------------------------------------------------------------------+ //| Create the button | //+------------------------------------------------------------------+ bool ButtonCreate(const string name,const int x,const int y,const int w,const int h,const string text,const color clr,const string font="Calibri",const int font_size=8) { if(ObjectFind(0,name)<0) { if(!ObjectCreate(0,name,OBJ_BUTTON,0,0,0)) { Print(DFUN,TextByLanguage("не удалось создать кнопку! Код ошибки=","Could not create button! Error code="),GetLastError()); return false; } ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,name,OBJPROP_HIDDEN,true); ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x); ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y); ObjectSetInteger(0,name,OBJPROP_XSIZE,w); ObjectSetInteger(0,name,OBJPROP_YSIZE,h); ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_LOWER); ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font_size); ObjectSetString(0,name,OBJPROP_FONT,font); ObjectSetString(0,name,OBJPROP_TEXT,text); ObjectSetInteger(0,name,OBJPROP_COLOR,clr); ObjectSetString(0,name,OBJPROP_TOOLTIP,"\n"); ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR,clrGray); return true; } return false; } //+------------------------------------------------------------------+ //| Set the terminal's global variable value | //+------------------------------------------------------------------+ bool SetGlobalVariable(const string gv_name,const double value) { //--- If the variable name length exceeds 63 symbols, return 'false' if(StringLen(gv_name)>63) return false; return(GlobalVariableSet(gv_name,value)>0); } //+------------------------------------------------------------------+ //| Return the button status | //+------------------------------------------------------------------+ bool ButtonState(const string name) { return (bool)ObjectGetInteger(0,name,OBJPROP_STATE); } //+------------------------------------------------------------------+ //| Return the button status by the timeframe name | //+------------------------------------------------------------------+ bool ButtonState(const ENUM_TIMEFRAMES timeframe) { string name=prefix+"BUTT_"+TimeframeDescription(timeframe); return ButtonState(name); } //+------------------------------------------------------------------+ //| Set the button status | //+------------------------------------------------------------------+ void ButtonState(const string name,const bool state) { ObjectSetInteger(0,name,OBJPROP_STATE,state); if(state) ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'220,255,240'); else ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'240,240,240'); } //+------------------------------------------------------------------+ //| Track the buttons' status | //+------------------------------------------------------------------+ void PressButtonsControl(void) { int total=ObjectsTotal(0,0); for(int i=0;i<total;i++) { string obj_name=ObjectName(0,i); if(StringFind(obj_name,prefix+"BUTT_")<0) continue; PressButtonEvents(obj_name); } } //+------------------------------------------------------------------+ //| Handle pressing the buttons | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { //--- Convert button name into its string ID string button=StringSubstr(button_name,StringLen(prefix)); //--- Create the button name for the terminal's global variable string name_gv=(string)ChartID()+"_"+prefix+button; //--- Get the button status (pressed/released). If not in the tester, //--- write the status to the button global variable (1 or 0) bool state=ButtonState(button_name); if(!engine.IsTester()) SetGlobalVariable(name_gv,state); //--- Get the timeframe from the button string ID and //--- the drawn buffer index by timeframe ENUM_TIMEFRAMES timeframe=TimeframeByDescription(StringSubstr(button,5)); int buffer_index=IndexBuffer(timeframe); //--- Set the button color depending on its status, //--- write its status to the buffer structure depending on the button status (used/not used) //--- initialize the buffer corresponding to the button timeframe by the buffer index received earlier ButtonState(button_name,state); Buffers[buffer_index].SetUsed(state); if(Buffers[buffer_index].GetShowDataFlag()!=state) { InitBuffer(buffer_index); BufferFill(buffer_index); Buffers[buffer_index].SetShowData(state); } //--- Here you can add additional handling of button pressing: //--- If the button is pressed if(state) { //--- If M1 button is pressed if(button=="BUTT_M1") { } //--- If button M2 is pressed else if(button=="BUTT_M2") { } //--- // Remaining buttons ... //--- } //--- Not pressed else { //--- M1 button if(button=="BUTT_M1") { } //--- M2 button if(button=="BUTT_M2") { } //--- // Remaining buttons ... //--- } //--- re-draw the chart ChartRedraw(); } //+------------------------------------------------------------------+
これらの関数はすべて非常にシンプルで単純ですが、一部の文字列はコメント化されています。
指標のOnCalculate()ハンドラを見てみましょう。
//+------------------------------------------------------------------+ //| 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[]) { //+------------------------------------------------------------------+ //| OnCalculate code block for working with the library: | //+------------------------------------------------------------------+ //--- Pass the current symbol data from OnCalculate() to the price structure CopyData(rates_data,rates_total,prev_calculated,time,open,high,low,close,tick_volume,volume,spread); //--- Handle the Calculate event in the library engine.OnCalculate(rates_data); //--- If working in the tester if(MQLInfoInteger(MQL_TESTER)) { engine.OnTimer(rates_data); // Working in the timer PressButtonsControl(); // Button pressing control EventsHandling(); // Working with events } //+------------------------------------------------------------------+ //| OnCalculate code block for working with the indicator: | //+------------------------------------------------------------------+ //--- Set OnCalculate arrays as timeseries ArraySetAsSeries(open,true); ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArraySetAsSeries(close,true); ArraySetAsSeries(time,true); ArraySetAsSeries(tick_volume,true); ArraySetAsSeries(volume,true); ArraySetAsSeries(spread,true); //--- Setting buffer arrays as timeseries //--- Check for the minimum number of bars for calculation if(rates_total<2 || Point()==0) return 0; //--- Display reference data on bar open time if(InpShowBarTimes) { string txt=""; int total=ArraySize(array_used_periods); //--- In the loop by the amount of used timeframes for(int i=0;i<total;i++) { //--- get the next timeframe, buffer index and timeseries object by timeframe ENUM_TIMEFRAMES timeframe=TimeframeByDescription(array_used_periods[i]); int buffer_index=IndexBuffer(timeframe); CSeriesDE *series=engine.SeriesGetSeries(NULL,timeframe); //--- If failed to get the timeseries or the buffer is not used (the button is released), move on to the next one if(series==NULL || !Buffers[buffer_index].IsUsed()) continue; //--- Get the reference bar from the timeseries list CBar *bar=series.GetBar(InpControlBar); if(bar==NULL) continue; //--- Collect data for the comment text string t1=TimeframeDescription((ENUM_TIMEFRAMES)Period()); string t2=TimeframeDescription(bar.Timeframe()); string t3=(string)InpControlBar; string t4=TimeToString(bar.Time()); string t5=(string)bar.Index((ENUM_TIMEFRAMES)Period()); //--- Set the comment text depending on the terminal language string tn=TextByLanguage ( "Бар на "+t1+", соответствующий бару "+t2+"["+t3+"] со временеи открытия "+t4+", расположен на баре "+t5, "The bar on "+t1+", corresponding to the "+t2+"["+t3+"] bar since the opening time of "+t4+", is located on bar "+t5 ); txt+=tn+"\n"; } //--- Display the comment on the chart Comment(txt); } //--- Check and calculate the number of calculated bars int limit=rates_total-prev_calculated; //--- Recalculate the entire history if(limit>1) { limit=rates_total-1; InitBuffersAll(); } //--- Prepare data //--- Calculate the indicator for(int i=limit; i>WRONG_VALUE && !IsStopped(); i--) { BufferTime[i]=(double)time[i]; CalculateSeries((ENUM_BAR_PROP_DOUBLE)InpBarPrice,i,time[i]); } //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+
「バーの時間コメントを表示」パラメータ(InpShowBarTimes変数)がtrueに設定されている場合、コードブロックは、InpControlBar ("ControlBar") 変数で指定された現在のチャートのバーにデータを表示し、それが使用されているすべてのタイムシリーズの時間枠のバーに一致することを示します。
計算されたlimit値が1を超える場合(履歴の変更により履歴全体を再描画する必要があることを意味)、limit を現在のチャートの履歴の開始に等しく設定し、すべての指標バッファを初期化する関数を呼び出します。
指標は、limit値(通常の状態では、1(新しいバー)またはゼロ(現在のバーを計算)からゼロまで計算されます。
指標のメイン計算ループで、time[]配列から計算された時間バッファを入力し(time[]配列が利用できない場合はインデックスによって時間を取得する他の指標関数には時間バッファが必要です)、使用されているすべてのインジケーターバッファーの単一のバーを計算する関数を呼び出します。
以下は、指標バッファを初期化する関数です。
//+------------------------------------------------------------------+ //| Initialize the timeseries and the appropriate buffers by index | //+------------------------------------------------------------------+ bool InitBuffer(const int buffer_index) { //--- Leave if the wrong index is passed if(buffer_index==WRONG_VALUE) return false; Initialize the variables using the "Not rendered" drawing style and disable the display in the data window int draw_type=DRAW_NONE; bool show_data=false; //--- If the buffer is used (button pressed) //--- Set the "Line" drawing style for variables and enable display in the data window if(Buffers[buffer_index].IsUsed()) { draw_type=DRAW_LINE; show_data=true; } //--- Set the drawing style and display in the data window for the buffer by its index PlotIndexSetInteger(Buffers[buffer_index].IndexDataBuffer(),PLOT_DRAW_TYPE,draw_type); PlotIndexSetInteger(Buffers[buffer_index].IndexDataBuffer(),PLOT_SHOW_DATA,show_data); //--- Initialize the calculated buffer using zero, while the drawn one is initialized using the "empty" value ArrayInitialize(Buffers[buffer_index].Temp,0); ArrayInitialize(Buffers[buffer_index].Data,EMPTY_VALUE); return true; } //+------------------------------------------------------------------+ //|Initialize the timeseries and the appropriate buffers by timeframe| //+------------------------------------------------------------------+ bool InitBuffer(const ENUM_TIMEFRAMES timeframe) { return InitBuffer(IndexBuffer(timeframe)); } //+------------------------------------------------------------------+ //| Initialize all timeseries and the appropriate buffers | //+------------------------------------------------------------------+ void InitBuffersAll(void) { //--- Initialize the next buffer in the loop by the total number of chart periods for(int i=0;i<PERIODS_TOTAL;i++) if(!InitBuffer(i)) continue; } //+------------------------------------------------------------------+
以下は、使用されているすべての指標バッファの単一の指定されたバーを計算する関数です(ボタンの押下)。
//+------------------------------------------------------------------+ //| Calculating a single bar of all active buffers | //+------------------------------------------------------------------+ void CalculateSeries(const ENUM_BAR_PROP_DOUBLE property,const int index,const datetime time) { //--- Get the next buffer in the loop by the total number of chart periods for(int i=0;i<PERIODS_TOTAL;i++) { //--- if the buffer is not used (the button is released), move on to the next one if(!Buffers[i].IsUsed()) continue; //--- get the timeseries object by the buffer timeframe CSeriesDE *series=engine.SeriesGetSeries(NULL,(ENUM_TIMEFRAMES)Buffers[i].ID()); //--- if the timeseries is not received //--- or the bar index passed to the function is beyond the total number of bars in the timeseries, move on to the next buffer if(series==NULL || index>series.GetList().Total()-1) continue; //--- get the bar object from the timeseries corresponding to the one passed to the bar time function on the current chart CBar *bar=engine.SeriesGetBarSeriesFirstFromSeriesSecond(NULL,PERIOD_CURRENT,time,NULL,series.Timeframe()); if(bar==NULL) continue; //--- get the specified property from the obtained bar and //--- call the function of writing the value to the buffer by i index double value=bar.GetProperty(property); SetBufferData(i,value,index,bar); } } //+------------------------------------------------------------------+
以下は、現在のチャートのいくつかのバーインデックスによってバーオブジェクトプロパティを指標バッファに書き込む関数です。
//+------------------------------------------------------------------+ //| Write data on a single bar to the specified buffer | //+------------------------------------------------------------------+ void SetBufferData(const int buffer_index,const double value,const int index,const CBar *bar) { //--- Get the bar index by its time falling within the time limits on the current chart int n=iBarShift(NULL,PERIOD_CURRENT,bar.Time()); //--- If the passed index on the current chart (index) is less than the calculated time of bar start on another timeframe if(index<n) //--- in the loop from the n bar on the current chart to zero while(n>WRONG_VALUE && !IsStopped()) { //--- fill in the n index buffer with the 'value' passed to the function (0 - EMPTY_VALUE) //--- and decrease the n value Buffers[buffer_index].Data[n]=(value>0 ? value : EMPTY_VALUE); n--; } //--- If the passed index on the current chart (index) is not less than the calculated time of bar start on another timeframe //--- Set 'value' for the buffer by the 'index' passed to the function (0 - EMPTY_VALUE) else Buffers[buffer_index].Data[index]=(value>0 ? value : EMPTY_VALUE); } //+------------------------------------------------------------------+
現在のチャートの別の時間枠からのバーデータを正しく表示するには、現在のチャートで指定されたローソク足(バー)期間の開始を見つけ、現在のチャートのすべてのバッファインデックスに別の期間のバーの値を入力します。これが関数の機能です。
時間枠をアクティブにするボタンを押すとき、適切な表示されたバッファに空の値を入力する(ボタンが離された場合)、または、ボタンで示されたバッファのすべてのデータを完全に再計算する必要があります(ボタンが押された場合)。バッファ初期化関数はデータを削除しますが、次の関数は指定された時系列データでバッファを埋めます。
//+------------------------------------------------------------------+ //| Fill in the entire buffer with historical data | //+------------------------------------------------------------------+ void BufferFill(const int buffer_index) { //--- Leave if the wrong index is passed if(buffer_index==WRONG_VALUE) return; //--- Leave if the buffer is not used (the button is released) if(!Buffers[buffer_index].IsUsed()) return; //--- Get the timeseries object by the buffer timeframe CSeriesDE *series=engine.SeriesGetSeries(NULL,(ENUM_TIMEFRAMES)Buffers[buffer_index].ID()); if(series==NULL) return; //--- If the buffer belongs to the current chart, copy the bar data from the timeseries to the buffer if(Buffers[buffer_index].ID()==Period()) series.CopyToBufferAsSeries((ENUM_BAR_PROP_DOUBLE)InpBarPrice,Buffers[buffer_index].Data,EMPTY_VALUE); //--- Otherwise, calculate each next timeseries bar and write it to the buffer in the loop by the number of the current chart bars else for(int i=rates_data.rates_total-1;i>WRONG_VALUE && !IsStopped();i--) CalculateSeries((ENUM_BAR_PROP_DOUBLE)InpBarPrice,i,(datetime)BufferTime[i]); } //+------------------------------------------------------------------+
完全な指標コードは、以下に添付されているファイルで提供されます。
このテスト指標はMQL5で開発されたことに留意してください。MQL4でも機能しますが、通常の方法では機能しません。適切なボタンを押すと、現在のチャート期間は表示されません。
さらに別の時間枠をアクティブにしたときにのみ表示されます。MetaTrader 4設定で非標準のチャート期間を設定すると、指標は同期を無限に待ちます。
また、一部のデータがターミナルデータウィンドウに誤って表示されます。すべての指標バッファが表示されます(計算されたものを含む)。これは、すべてのMQL5関数がMQL4で機能するわけではなく、対応するMQL4に置き換える必要があるためです。
さらに、指標はテスト目的で、つまり複数期間モードでの動作を確認するために作成されているため、MetaTrader 5の履歴データの変更も誤って処理する可能性があります。検出されたすべてのバグは、後続の記事で徐々に修正されます。ライブラリは、MetaTrader 5ですべての欠点が解消されてからMetaTrader 4指標に合わせて調整されます。
指標をコンパイルし、チャートで起動します。
ご覧のように、М15では、М5のデータバッファは、現在のチャートローソク足の3分の1の1つにおけるМ5バーの終値を示しています。これは、1つのМ15バーに3つのМ5バーが含まれており、M5バーの終値がМ15バーに表示されるので理解できます。
現在のチャート期間に時系列データを表示するパラメータを有効にして、テスターで指標を起動します。
次の段階
次回の記事では、指標でライブラリ時系列オブジェクトを処理する作業を続けます。
現在のバージョンのライブラリのすべてのファイルは、テスト用EAファイルと一緒に以下に添付されているので、テストするにはダウンロードしてください。
質問や提案はコメント欄にお願いします。
シリーズのこれまでの記事:
DoEasyライブラリの時系列(第35部): バーオブジェクトと銘柄の時系列リスト
DoEasyライブラリの時系列(第36部): すべての使用銘柄期間の時系列オブジェクト
DoEasyライブラリの時系列(第37部): すべての使用銘柄期間の時系列オブジェクトDoEasyライブラリの時系列(第38部): 時系列コレクション-リアルタイムの更新とプログラムからのデータへのアクセス
DoEasyライブラリの時系列(第39部): ライブラリに基づいた指標 - データイベントと時系列イベントの準備s