DoEasyライブラリの時系列(第39部): ライブラリに基づいた指標 - データイベントと時系列イベントの準備
内容
概念
これまでにおこなったすべての作業は、EAとスクリプトのみに関連しており、決して指標には関連していませんでした。ただし、時系列は、指標のさまざまな計算のデータソースとしてアクティブに使用することもできます。ですから、それらについても検討する必要があります。
EAとは異なり、指標は完全に異なるアーキテクチャを備えています。各指標は、それが起動された単一の銘柄の単一のストリームで実行されます。つまり、同じ銘柄の複数のチャートで異なる指標を起動した場合、これらすべてのチャートが属する同じ銘柄スレッドで実行されます。したがって、指標の1つにアーキテクチャの欠陥がある場合、銘柄スレッド全体の速度が低下します。この場合、同じスレッドで動作している残りのすべての指標は、「低速」指標を待っている間フリーズします。
指標を操作するときに履歴データを待機している間の遅延を回避するために、ターミナルは要求されたデータを順次返す機能を備えています。履歴データのロードをアクティブ化する関数は、待機せずに関数結果をすぐに返します。
Copy 関数を使用して任意の銘柄の任意の時系列のデータを要求するとき、ターミナルが履歴データを送信すると指標とEAの動作は異なります。
指標からデータを要求するときに、要求された時系列がまだ構築されていない場合、またはサーバからダウンロードする必要があるが、ロード/構築自体が開始されている場合、関数はすぐに-1を返します。
EAまたはスクリプトからデータを要求するときに、端末にローカルに適切なデータがない場合はサーバからのダウンロードが開始され、ローカルからデータを構築できる場合が履歴はまだ準備ができていない場合は、必要な時系列の構築が始まります。関数は、タイムアウトの期限が切れるまでに準備ができる量を返しますが、履歴のダウンロードは続行され、関数は次の同様の要求中にさらに多くのデータを返します。
したがって、EAからデータを要求すると、端末はデータのダウンロードを開始します(ローカルで要求されたデータがまだないか、十分でない場合)。タイムアウトの期限が切れると、関数は、履歴のダウンロードが完了するのを待つ瞬間にすでに存在する履歴の量を返します。端末は、要求された履歴をすぐに提供しようとします。ローカルデータが不十分な場合、必要な量のダウンロードを試みます。
その間、プログラムはデータがダウンロードされるのを待ちます。
指標の場合、待つことができないので、ターミナルはそれが持っているものを送信します(または何もないことを報告します)。ローカル履歴がない、または初めのデータリクエストで足りないと、ダウンロードが開始されます。ここでは、システムがタイムアウトする前にデータがダウンロードされるのを待つことはありません。
現在の状況では、プログラムは次のティックより前に計算部を終了するはずです。新しいティックでの指標のOnCalculate()ハンドラでは、データは部分的または完全に読み込まれていて、計算に使用できる可能性があります。ここではプログラムアルゴリズムをスムーズに実行するのにどれだけのデータで足りるのかを決定します。
さらに、指標は、独自のデータ(銘柄と期間が起動されるデータ)をダウンロードしようとするべきではありません。した場合、そのような要求は競合につながる可能性があります。ターミナルサブシステムは、指標のようなデータをダウンロードします。OnCalculate()ハンドラのrates_total変数とprev_calculated変数の値とステータスに関するすべてのデータを提供します。
これらの最小要件に基づいて、時系列を操作するためにいくつかのクラスを調整し、指標での計算に必要なデータの正しい初期読み込みを調整する必要があります。
本稿では、作成済みのクラスを調整し、プログラムで使用されているすべての時系列の正しい初期データ読み込みを調整し、リアルタイム更新中にすべての使用された時系列のイベントを制御プログラムチャートに送信します 。
指標の操作、時系列イベントの作成のためのクラスの改善
まず、Datas.mqhファイルに新しいメッセージを追加しましょう — メッセージインデックス:
MSG_LIB_SYS_FAILED_PREPARING_SYMBOLS_ARRAY, // Failed to prepare array of used symbols. Error MSG_LIB_SYS_FAILED_GET_SYMBOLS_ARRAY, // Failed to get array of used symbols. MSG_LIB_SYS_ERROR_EMPTY_PERIODS_STRING, // Error. The string of predefined periods is empty and is to be used
...
//--- CBar MSG_LIB_TEXT_BAR_FAILED_GET_BAR_DATA, // Failed to receive bar data MSG_LIB_TEXT_BAR_FAILED_DT_STRUCT_WRITE, // Failed to write time to time structure MSG_LIB_TEXT_BAR_FAILED_GET_SERIES_DATA, // Failed to receive timeseries data
...
MSG_LIB_TEXT_TS_TEXT_SYMBOL_TERMINAL_FIRSTDATE, // The very first date in history by a symbol in the client terminal MSG_LIB_TEXT_TS_TEXT_CREATED_OK, // successfully created MSG_LIB_TEXT_TS_TEXT_NOT_CREATED, // not created MSG_LIB_TEXT_TS_TEXT_IS_SYNC, // synchronized MSG_LIB_TEXT_TS_TEXT_ATTEMPT, // Attempt: MSG_LIB_TEXT_TS_TEXT_WAIT_FOR_SYNC, // Waiting for data synchronization ... }; //+------------------------------------------------------------------+
新しく追加したインデックスに対応するメッセージテキストも追加します。
{"Не удалось подготовить массив используемых символов. Ошибка ","Failed to create an array of used symbols. Error "}, {"Не удалось получить массив используемых символов","Failed to get array of used symbols"}, {"Ошибка. Строка предопределённых периодов пустая, будет использоваться ","Error. String of predefined periods is empty, the Period will be used: "},
...
{"Не удалось получить данные бара","Failed to get bar data"}, {"Не удалось записать время в структуру времени","Failed to write time to datetime structure"}, {"Не удалось получить данные таймсерии","Failed to get timeseries data"},
...
{"Самая первая дата в истории по символу в клиентском терминале","Very first date in history of symbol in client terminal"}, {"создана успешно","created successfully"}, {"не создана","not created"}, {"синхронизирована","synchronized"}, {"Попытка: ","Attempt: "}, {"Ожидание синхронизации данных ...","Waiting for data synchronization ..."}, }; //+---------------------------------------------------------------------+
\MQL5\Include\DoEasy\Objects\BaseObj.mqhにあるすべてのライブラリオブジェクトのCBaseObj基本オブジェクトのクラスコンストラクタで、 m_available変数の初期化を変更しました 。作成中、CBaseObj基本オブジェクトから派生したすべてのオブジェクトは、「使用済み」ステータス(true)でプログラムで作業するための可用性プロパティを備えています。以前は、「未使用」がfalseステータスに初期化するときに値がインストールされていました。
//--- Constructor CBaseObj() : m_program((ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE)), m_global_error(ERR_SUCCESS), m_log_level(LOG_LEVEL_ERROR_MSG), m_chart_id_main(::ChartID()), m_chart_id(::ChartID()), m_folder_name(DIRECTORY), m_sound_name(""), m_name(__FUNCTION__), m_type(0), m_use_sound(false), m_available(true), m_first_start(true) {} }; //+------------------------------------------------------------------+
オブジェクトで検出されたイベントのフラグを設定するメソッドの名前は、 \MQL5\Include\DoEasy\Objects\BaseObj.mqhにあるすべてのCBaseObjExtライブラリオブジェクトの拡張基本オブジェクトのクラスで変更されました。
//--- Set/return the occurred event flag to the object data void SetEventFlag(const bool flag) { this.m_is_event=flag; }
以前は、このメソッドはSetEvent()と呼ばれていました。これは、SetEventがイベントの存在のシグナルフラグを設定するのではなく、イベントの作成、設定、送信などを意味する場合があるため、混乱を招く可能性があります。
したがって、メソッドを適用するクラスのファイルも変更されました。SetEvent()メソッドの呼び出しは、SetEventFlag()に置き換えられました。添付ファイルで詳細を確認してください。
指標では取引機能が無効になっているため、取引オブジェクトクラスに変更を加えます。
\MQL5\Include\DoEasy\Objects\Trade\TradeObj.mqh のクロスプラットフォーム取引オブジェクトクラスで、すべての取引グメソッドの最初に、プログラムタイプの確認を入力します。これが指標またはサービスの場合は、メソッドを終了して、trueを返します。
//+------------------------------------------------------------------+ //| Open a position | //+------------------------------------------------------------------+ bool CTradeObj::OpenPosition(const ENUM_POSITION_TYPE type, const double volume, const double sl=0, const double tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const ulong deviation=ULONG_MAX, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { if(this.m_program==PROGRAM_INDICATOR || this.m_program==PROGRAM_SERVICE) return true; ::ResetLastError();
...
//+------------------------------------------------------------------+ //| Close a position | //+------------------------------------------------------------------+ bool CTradeObj::ClosePosition(const ulong ticket, const string comment=NULL, const ulong deviation=ULONG_MAX) { if(this.m_program==PROGRAM_INDICATOR || this.m_program==PROGRAM_SERVICE) return true; ::ResetLastError();
...
//+------------------------------------------------------------------+ //| Close a position partially | //+------------------------------------------------------------------+ bool CTradeObj::ClosePositionPartially(const ulong ticket, const double volume, const string comment=NULL, const ulong deviation=ULONG_MAX) { if(this.m_program==PROGRAM_INDICATOR || this.m_program==PROGRAM_SERVICE) return true; ::ResetLastError();
...
//+------------------------------------------------------------------+ //| Close a position by an opposite one | //+------------------------------------------------------------------+ bool CTradeObj::ClosePositionBy(const ulong ticket,const ulong ticket_by) { if(this.m_program==PROGRAM_INDICATOR || this.m_program==PROGRAM_SERVICE) return true; ::ResetLastError();
...
//+------------------------------------------------------------------+ //| Modify a position | //+------------------------------------------------------------------+ bool CTradeObj::ModifyPosition(const ulong ticket,const double sl=WRONG_VALUE,const double tp=WRONG_VALUE) { if(this.m_program==PROGRAM_INDICATOR || this.m_program==PROGRAM_SERVICE) return true; ::ResetLastError();
...
//+------------------------------------------------------------------+ //| Set an order | //+------------------------------------------------------------------+ bool CTradeObj::SetOrder(const ENUM_ORDER_TYPE type, const double volume, const double price, const double sl=0, const double tp=0, const double price_stoplimit=0, const ulong magic=ULONG_MAX, const string comment=NULL, const datetime expiration=0, const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { if(this.m_program==PROGRAM_INDICATOR || this.m_program==PROGRAM_SERVICE) return true; ::ResetLastError();
...
//+------------------------------------------------------------------+ //| Remove an order | //+------------------------------------------------------------------+ bool CTradeObj::DeleteOrder(const ulong ticket) { if(this.m_program==PROGRAM_INDICATOR || this.m_program==PROGRAM_SERVICE) return true; ::ResetLastError();
...
//+------------------------------------------------------------------+ //| Modify an order | //+------------------------------------------------------------------+ bool CTradeObj::ModifyOrder(const ulong ticket, const double price=WRONG_VALUE, const double sl=WRONG_VALUE, const double tp=WRONG_VALUE, const double price_stoplimit=WRONG_VALUE, const datetime expiration=WRONG_VALUE, const ENUM_ORDER_TYPE_TIME type_time=WRONG_VALUE, const ENUM_ORDER_TYPE_FILLING type_filling=WRONG_VALUE) { if(this.m_program==PROGRAM_INDICATOR || this.m_program==PROGRAM_SERVICE) return true; ::ResetLastError();
\MQL5\Include\DoEasy\Trading.mqhのライブラリのメイン指標クラスのすべての同名の指標メソッドに同じ変更が加えられました。
このような方法で取引メソッドを終了すると、無効になっているプログラムで取引関数を呼び出すことができず、ライブラリエラーの処理を妨げるメソッドの実行が成功します。
次に、時系列オブジェクトのクラスに直接影響を与えた変更を考えてみましょう。
バーオブジェクトクラスでは、バーオブジェクトの作成中に履歴データを受信するときにエラーが発生した場合に、クラスコンストラクターから表示されるテキストを少し変更しました。表示されるテキストには、バーオブジェクトが作成されるコンストラクタ番号、銘柄、および時系列の時間枠も含まれています。
最初のフォームコンストラクタでは、 データ取得エラーの確認と時間構造への書き込み時間が別々のブロックに設定されています。
//+------------------------------------------------------------------+ //| Constructor 1 | //+------------------------------------------------------------------+ CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index) { this.m_type=COLLECTION_SERIES_ID; MqlRates rates_array[1]; this.SetSymbolPeriod(symbol,timeframe,index); ::ResetLastError(); //--- If ailed to get the requested data by index 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,index,1,rates_array)<1) { int err_code=::GetLastError(); ::Print ( DFUN,"(1) ",symbol," ",TimeframeDescription(timeframe)," ", CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_GET_BAR_DATA),". ", CMessage::Text(MSG_LIB_SYS_ERROR)," ",CMessage::Text(err_code)," ", CMessage::Retcode(err_code) ); MqlRates err={0}; rates_array[0]=err; } ::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)," ", 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]); } //+------------------------------------------------------------------+ //| Constructor 2 | //+------------------------------------------------------------------+ CBar::CBar(const string symbol,const ENUM_TIMEFRAMES timeframe,const int index,const MqlRates &rates) { this.m_type=COLLECTION_SERIES_ID; this.SetSymbolPeriod(symbol,timeframe,index); ::ResetLastError(); //--- If failed to set time to the time structure, display the error message, //--- create and fill the structure with zeros, set the bar properties from this structure and exit if(!::TimeToStruct(rates.time,this.m_dt_struct)) { int err_code=::GetLastError(); ::Print ( DFUN,"(2) ",symbol," ",TimeframeDescription(timeframe)," ", CMessage::Text(MSG_LIB_TEXT_BAR_FAILED_DT_STRUCT_WRITE),". ", CMessage::Text(MSG_LIB_SYS_ERROR)," ",CMessage::Text(err_code)," ", CMessage::Retcode(err_code) ); MqlRates err={0}; this.SetProperties(err); return; } //--- Set the bar properties this.SetProperties(rates); } //+------------------------------------------------------------------+
これらのアクションにより、バーオブジェクトの作成エラーが発生した場合に、より多くのデータが提供されます。
OnCalculate()ハンドラが提供する時系列配列を使用して、現在の期間銘柄のバーの数とその値に関するデータを要求する必要があるため、これらの配列と値をライブラリクラスに渡す必要があります。
これには、 \MQL5\Include\DoEasy\Defines.mqhに構造体を作成します。この構造は、現在の時系列に対して計算されたすべての必要なデータをライブラリの時系列に渡すために使用される変数を格納するものです。
//+------------------------------------------------------------------+ //| Structures | //+------------------------------------------------------------------+ struct SDataCalculate { int rates_total; // size of input time series int prev_calculated; // number of handled bars at the previous call int begin; // where significant data start double price; // current array value for calculation MqlRates rates; // Price structure } rates_data; //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Search and sorting data | //+------------------------------------------------------------------+
ご覧のように、構造体には、指標のOnCalculate()ハンドラの実装のためにライブラリにデータを渡すために必要なすべてのフィールドが含まれています。
1番目のフォームのハンドラ
int OnCalculate( const int rates_total, // price[] array size const int prev_calculated, // number of handled bars at the previous call const int begin, // index number in the price[] array meaningful data starts from const double& price[] // array of values for calculation );
rates_total, prev_calculated, begin and price variable structures are used.
2番目のフォームのハンドラ
int OnCalculate( const int rates_total, // size of input time series const int prev_calculated, // number of handled bars at the previous call const datetime& time{}, // Time array const double& open[], // Open array const double& high[], // High array const double& low[], // Low array const double& close[], // Close array const long& tick_volume[], // Tick Volume array const long& volume[], // Real Volume array const int& spread[] // Spread array );
rates_totalおよびprev_calculated変数構造体とともにMqlRatesレート構造体が配列値の格納に使用されます。
現在の構造の実装は、単一のバーの値のみをライブラリに渡すのに適しています。
\MQL5\Include\DoEasy\Objects\Series\Series.mqhCSeriesクラスでは、銘柄と時間枠を設定するメソッドにサーバ日付設定フラグを追加します。
//--- Set (1) symbol, (2) timeframe, (3) symbol and timeframe, (4) amount of applied timeseries data void SetSymbol(const string symbol,const bool set_server_date=false); void SetTimeframe(const ENUM_TIMEFRAMES timeframe,const bool set_server_date=false);
デフォルトではフラグはオフになっています。これにより、メソッドの呼び出し時にサーバの日付を設定できなくなります。これは、サーバの日付を設定するメソッドを呼び出し、フラグのステータスが最初にチェックされるためです。
//+------------------------------------------------------------------+ //| Set a symbol | //+------------------------------------------------------------------+ void CSeries::SetSymbol(const string symbol,const bool set_server_date=false) { if(this.m_symbol==symbol) return; this.m_symbol=(symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_new_bar_obj.SetSymbol(this.m_symbol); if(set_server_date) this.SetServerDate(); } //+------------------------------------------------------------------+ //| Set a timeframe | //+------------------------------------------------------------------+ void CSeries::SetTimeframe(const ENUM_TIMEFRAMES timeframe,const bool set_server_date=false) { if(this.m_timeframe==timeframe) return; this.m_timeframe=(timeframe==PERIOD_CURRENT ? (ENUM_TIMEFRAMES)::Period() : timeframe); this.m_new_bar_obj.SetPeriod(this.m_timeframe); this.m_period_description=TimeframeDescription(this.m_timeframe); if(set_server_date) this.SetServerDate(); } //+------------------------------------------------------------------+
これは、銘柄と時間枠を同時に設定するメソッドを呼び出すときに、サーバの日付が複数回リセットされるのを防ぐために行われました。
//+------------------------------------------------------------------+ //| Set a symbol and timeframe | //+------------------------------------------------------------------+ void CSeries::SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES timeframe) { if(this.m_symbol==symbol && this.m_timeframe==timeframe) return; this.SetSymbol(symbol); this.SetTimeframe(timeframe,true); } //+------------------------------------------------------------------+
ここでは、銘柄設定メソッドが最初に呼び出され(フラグが無効)次に、有効なフラグで時間枠を設定するメソッドが、時間枠設定メソッドからサーバ日付を設定するメソッドを呼び出します。
時系列データを更新するメソッドは、配列の完全なリストではなく、OnCalculate()ハンドラデータの新しい構造を渡します。
//--- (1) Create and (2) update the timeseries list int Create(const uint required=0); void Refresh(SDataCalculate &data_calculate); //--- Create and send the "New bar" event to the control program chart void SendEvent(void);
したがって、Refresh()メソッドの実装は、配列ではなく構造体データへのアクセスを備えています。
//+------------------------------------------------------------------+ //| Update timeseries list and data | //+------------------------------------------------------------------+ void CSeries::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 index this.m_list_series.Sort(SORT_BY_BAR_INDEX); //--- 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,0); 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 object from the list by the terminal timeseries index (zero bar) CBar *bar=this.GetBarBySeriesIndex(0); //--- 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]); } //+------------------------------------------------------------------+
2つの時系列オブジェクトを比較する仮想メソッドを介して、時系列による時系列オブジェクトのリストの検索を実行できるようになりました。
//--- Comparison method to search for identical timeseries objects by timeframe virtual int Compare(const CObject *node,const int mode=0) const { const CSeries *compared_obj=node; return(this.Timeframe()>compared_obj.Timeframe() ? 1 : this.Timeframe()<compared_obj.Timeframe() ? -1 : 0); } //--- Constructors CSeries(void); CSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0); }; //+------------------------------------------------------------------+
このメソッドは、比較された2つの時系列オブジェクト(現在のものとメソッドに渡されたもの)の「timeframe」プロパティを比較し、等しい場合はゼロを返します。
CObject標準ライブラリの基本オブジェクトから派生したさまざまなオブジェクトを検索および並び替えするための類似のメソッドのロジックについてはすでに検討しました。メソッドは、標準ライブラリのベースオブジェクトで仮想メソッドとして定義されています。したがって、子孫オブジェクトに実装する必要があります。メソッドが等しい場合はゼロを返し、現在のオブジェクトの比較対象プロパティの値が比較対象オブジェクトのプロパティ値より大きい/小さい場合は1 / -1を返します。
履歴データを返す関数への最初のアクセスは、ローカルに存在しない/不十分な場合にデータのダウンロードをアクティブにするため、メソッドの最初に必要な履歴データへのアクセスを追加(単純に現在のバー日付を要求)します 必要なデータの量を設定します。これにより、必要なデータのダウンロードが開始されます(ローカルにデータがない場合)。
//+------------------------------------------------------------------+ //| Set the number of required data | //+------------------------------------------------------------------+ bool CSeries::SetRequiredUsedData(const uint required,const uint rates_total) { this.m_required=(required<1 ? SERIES_DEFAULT_BARS_COUNT : required); //--- Launch downloading historical data if(this.m_program!=PROGRAM_INDICATOR || (this.m_program==PROGRAM_INDICATOR && (this.m_symbol!=::Symbol() || this.m_timeframe!=::Period()))) { datetime array[1]; ::CopyTime(this.m_symbol,this.m_timeframe,0,1,array); } //--- Set the number of available timeseries bars
単一の銘柄のすべての時系列のリストを格納するオブジェクト(CTimeSeriesクラス)を作成したときに、このオブジェクトが常にターミナルで可能なすべての時間枠の完全なセットを備えたリストを持つようにしました。時系列リストはすぐにリストに追加されます。ただし、必要な場合にのみ作成されます。必要な時系列へのポインタへのアクセスは、オフセット1のENUM_TIMEFRAMES列挙内のリスト時間枠インデックス位置に対応する定数インデックスによって実行されました(記事に記載)。
これは、リスト内の必要な時系列オブジェクトへのポインタへのアクセスを高速化するために行われました。しかし、ポインタへの即時アクセスにはテスターの問題が伴うことがわかります。ビジュアルテスターは、プログラムで実際に使用されたかどうか、および時系列リストが作成されたかどうかに関係なく、すべての時間枠のチャートを作成しました。
さらに、プログラムの操作中にチャートの期間を切り替えると、別の問題が発生します。以前に作成されたリストは再作成されず、プログラムは存在しないオブジェクトのイベントの追跡を再開して、それらを他のものに置き換えます。
隠れたエラーのさらなる蓄積とその原因の長時間の検索を回避するために、実際に使用および作成された時系列リストへのポインタを、すべての使用された時間枠の時系列リストを格納するCTimeSeriesクラスオブジェクトにのみ格納することにしました。言い換えると、各チャート期間の各時系列へのポインタは、プログラムがその使用の必要性を明示的に示し、そのような時系列オブジェクトが物理的に作成される場合にのみ、リストに追加されます。
\MQL5\Include\DoEasy\Objects\Series\TimeSeries.mqhを開いて必要な改善を加えます。
単一銘柄の時系列のクラスは、すべてのライブラリオブジェクトの拡張基本オブジェクトのクラスから派生します。
これは、CBaseObjExtクラスのイベント機能を使用できるようにするためにおこなわれます。
//+------------------------------------------------------------------+ //| Symbol timeseries class | //+------------------------------------------------------------------+ class CTimeSeries : public CBaseObjExt {
時系列インデックスを時間枠名でリストに返すメソッドは、クラスのprivateセクションで単に宣言されるようになりました。
//+------------------------------------------------------------------+ class CTimeSeries : public CBaseObjExt { private: string m_symbol; // Timeseries symbol CNewTickObj m_new_tick; // "New tick" object CArrayObj m_list_series; // List of timeseries by timeframes datetime m_server_firstdate; // The very first date in history by a server symbol datetime m_terminal_firstdate; // The very first date in history by a symbol in the client terminal //--- Return (1) the timeframe index in the list and (2) the timeframe by the list index int IndexTimeframe(const ENUM_TIMEFRAMES timeframe); ENUM_TIMEFRAMES TimeframeByIndex(const uchar index) const { return TimeframeByEnumIndex(uchar(index+1)); } //--- Set the very first date in history by symbol on the server and in the client terminal void SetTerminalServerDate(void) { this.m_server_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,::Period(),SERIES_SERVER_FIRSTDATE); this.m_terminal_firstdate=(datetime)::SeriesInfoInteger(this.m_symbol,::Period(),SERIES_TERMINAL_FIRSTDATE); } public:
メソッドがクラス本体の外部に実装されました。
//+------------------------------------------------------------------+ //| Return the timeframe index in the list | //+------------------------------------------------------------------+ int CTimeSeries::IndexTimeframe(const ENUM_TIMEFRAMES timeframe) { const CSeries *obj=new CSeries(this.m_symbol,timeframe); if(obj==NULL) return WRONG_VALUE; this.m_list_series.Sort(); int index=this.m_list_series.Search(obj); delete obj; return index; } //+------------------------------------------------------------------+
メソッドは時間枠を受け取ります。時間枠の時系列へのポインタが返されます。
次に、一時的な時系列オブジェクトを必要な時間枠で作成します。
時系列オブジェクトのリストに並び替えリストフラグを設定し、時系列が一時的オブジェクト時間枠と等しいリストで時系列オブジェクトインデックスを取得します。
そのようなオブジェクトがリストに存在する場合は、そのインデックスが受信されます。それ以外の場合は、 WRONG_VALUE(-1)になります。
一時オブジェクトを削除して、取得したインデックスを返します。
Create()メソッドとCreateAll()メソッドの代わりに、指定された時系列をリストに追加するメソッドと指定された時系列オブジェクトを作成するメソッドを宣言し、時系列リストを更新するメソッドは配列の完全なリストの代わりに、パラメータ値とOnCalculate()配列の構造を受け取るようになります。
//--- (1) Add the specified timeseries list to the list and create (2) the specified timeseries list bool AddSeries(const ENUM_TIMEFRAMES timeframe,const uint required=0); bool CreateSeries(const ENUM_TIMEFRAMES timeframe,const uint required=0); //--- Update (1) the specified timeseries list and (2) all timeseries lists void Refresh(const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate); void RefreshAll(SDataCalculate &data_calculate); //--- Compare CTimeSeries 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 CTimeSeries(void){;} CTimeSeries(const string symbol); }; //+------------------------------------------------------------------+
クラスコンストラクタから時系列リストを作成するループを削除します。
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTimeSeries::CTimeSeries(const string symbol) : m_symbol(symbol) { this.m_list_series.Clear(); this.m_list_series.Sort(); for(int i=0;i<21;i++) { ENUM_TIMEFRAMES timeframe=this.TimeframeByIndex((uchar)i); CSeries *series_obj=new CSeries(this.m_symbol,timeframe); this.m_list_series.Add(series_obj); } this.SetTerminalServerDate(); this.m_new_tick.SetSymbol(this.m_symbol); this.m_new_tick.Refresh(); } //+------------------------------------------------------------------+
これで、プログラムのOnInit()ハンドラで使用される時系列の配列を作成した後に、必要な時系列が作成されます。プログラムで使用されるチャート期間の数を変更すると、EAが指標の再初期化または再作成を引き起こし、使用される時系列オブジェクトのリストの完全な再作成および将来のそれらの正しい計算が行われます。
すべての使用された時系列SetRequiredAllUsedData()の履歴の深さを設定し、適用されたすべての時系列SyncAllData()の同期フラグを返すメソッドでは、すべての可能な時間枠の合計数のループ
//+------------------------------------------------------------------+ //| Set the history depth of all applied symbol timeseries | //+------------------------------------------------------------------+ bool CTimeSeries::SetRequiredAllUsedData(const uint required=0,const int rates_total=0) { if(this.m_symbol==NULL) { ::Print(DFUN,CMessage::Text(MSG_LIB_TEXT_TS_TEXT_FIRST_SET_SYMBOL)); return false; } bool res=true; for(int i=0;i<21;i++) { CSeries *series_obj=this.m_list_series.At(i); if(series_obj==NULL) continue; res &=series_obj.SetRequiredUsedData(required,rates_total); } return res; } //+------------------------------------------------------------------+
を、リスト内のリアルタイムシリーズオブジェクトの数のループで置き換えます。
int total=this.m_list_series.Total(); for(int i=0;i<total;i++)
これで、リストは実際に作成された時系列オブジェクトで構成され、ループは実際の数に従って実行されます。
指定された時系列オブジェクトをリストに追加するメソッドを実装します。
//+------------------------------------------------------------------+ //| Add the specified timeseries list to the list | //+------------------------------------------------------------------+ bool CTimeSeries::AddSeries(const ENUM_TIMEFRAMES timeframe,const uint required=0) { bool res=false; CSeries *series=new CSeries(this.m_symbol,timeframe,required); if(series==NULL) return res; this.m_list_series.Sort(); if(this.m_list_series.Search(series)==WRONG_VALUE) res=this.m_list_series.Add(series); if(!res) delete series; series.SetAvailable(true); return res; } //+------------------------------------------------------------------+
このメソッドは、銘柄時系列リストに追加される時系列チャート期間を受け取ります。
値がメソッドに渡される時間枠を特徴とする時系列オブジェクトを作成します。時系列リストの並び替えリストフラグを設定して、新しく作成されたものと等しい時系列オブジェクトをリストから検索します。
リストにそのようなオブジェクトが含まれていない場合(検索により-1が返されます)、作成された時系列オブジェクトをリストに追加します。
そうでない場合は、作成されたオブジェクトを削除します。そのような時系列オブジェクトはすでにリストに含まれているためです。プログラムで時系列を使用するフラグを設定し、時系列をリストに追加した結果を返します。
正常に追加できた場合はtrue、できなかった場合はfalseを返します。
ライブラリは、ライブラリのさまざまなオブジェクトに発生するイベントを送信するために、すべてのライブラリオブジェクトの拡張オブジェクトのイベント機能を備えています。第16部および第17部では、ライブラリのイベントを扱う際の原則と論理を検討しました。
つまり、CBaseObjライブラリの基本オブジェクト(現在はCBaseObjExt)から派生した各オブジェクトには、単一のティックまたは単一のタイマー反復でのプログラム操作の1つのループ内でオブジェクトに発生する可能性のあるすべてのイベントを登録するリストがあります。
オブジェクトイベントを識別すると、発生したイベントのフラグが設定されます。次に、コレクションオブジェクトのリストをコレクションクラスで表示できます。次に、フラグがリストでチェックされます。イベントフラグが有効になっているオブジェクトが見つかった場合、これらのオブジェクトのコレクションクラスは、イベントフラグが有効になっているすべてのオブジェクトイベントのリストを受け取り、リストからすべてのイベントを制御プログラムチャートに送信します。
プログラム自体は、すべての着信イベントを処理する機能を備えています。テスターでは、すべてのイベントはティックによって処理されます。テスター以外では、OnChartEvent()ハンドラで処理されます。
単一の銘柄CTimeSeriesのすべての時系列の検討対象オブジェクトクラスで、そのすべての時系列リストのイベントを定義するのに最適な場所は、指定されたRefresh()時系列を更新するメソッドとすべての銘柄timeseries RefreshAll()を更新するメソッドです。
時系列リストを更新するメソッドの実装を検討してみましょう。
//+------------------------------------------------------------------+ //| Update a specified timeseries list | //+------------------------------------------------------------------+ void CTimeSeries::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 CSeries *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(0),series_obj.Timeframe(),series_obj.Symbol())) this.m_is_event=true; } } //+------------------------------------------------------------------+ //| Update all timeseries lists | //+------------------------------------------------------------------+ void CTimeSeries::RefreshAll(SDataCalculate &data_calculate) { //--- Reset the flags indicating the necessity to set the first date in history on the server and in the terminal //--- and the timeseries event flag, and clear the list of all timeseries events bool upd=false; this.m_is_event=false; this.m_list_events.Clear(); //--- In the loop by the list of all used timeseries, int total=this.m_list_series.Total(); for(int i=0;i<total;i++) { //--- get the next timeseries object by the loop index CSeries *series_obj=this.m_list_series.At(i); if(series_obj==NULL || !series_obj.IsAvailable() || series_obj.DataTotal()==0) continue; //--- update the timeseries list series_obj.Refresh(data_calculate); //--- 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 flag indicating the necessity to set the first date in history on the server and in the terminal upd=true; //--- add the "New bar" event to the list of timeseries events //--- in case of successful addition, set the event flag for the timeseries if(this.EventAdd(SERIES_EVENTS_NEW_BAR,series_obj.Time(0),series_obj.Timeframe(),series_obj.Symbol())) this.m_is_event=true; } } //--- if the flag indicating the necessity to set the first date in history on the server and in the terminal is enabled, //--- set the values of the first date in history on the server and in the terminal if(upd) this.SetTerminalServerDate(); } //+------------------------------------------------------------------+
ここではすべてのメソッドコード文字列にコメントを付けたので、すべてが明確になるはずです。ご質問がある場合は、下のコメント欄でお気軽にお問い合わせください。
これにより、1つの銘柄のすべての時系列のオブジェクトのCTimeSeriesクラスが完成します。
次のクラスは、銘柄timeseriesオブジェクトのCTimeSeriesCollectionコレクションクラスです。プログラムで使用される各銘柄のすべての時系列を格納するすべてのオブジェクトからイベントのリストを取得するのは「責任がある」ため、イベント機能も備えている必要があります。
\MQL5\Include\DoEasy\Collections\TimeSeriesCollection.mqhを開いてすべてのライブラリオブジェクトの拡張基本クラスから派生させます。
//+------------------------------------------------------------------+ //| Symbol timeseries collection | //+------------------------------------------------------------------+ class CTimeSeriesCollection : public CBaseObjExt {
クラスのpublicセクションで、指定された銘柄のすべての時系列のオブジェクトを返すためのメソッドと指定された銘柄と期間の時系列オブジェクトを返すためのメソッドの2つを宣言します。
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 CTimeSeries *GetTimeseries(const string symbol); CSeries *GetSeries(const string symbol,const ENUM_TIMEFRAMES timeframe);
クラス本体の外側で実装しましょう。
以下は、指定された銘柄の時系列オブジェクトを作成するメソッドです。
//+------------------------------------------------------------------+ //| Return the timeseries object of the specified symbol | //+------------------------------------------------------------------+ CTimeSeries *CTimeSeriesCollection::GetTimeseries(const string symbol) { int index=this.IndexTimeSeries(symbol); if(index==WRONG_VALUE) return NULL; CTimeSeries *timeseries=this.m_list.At(index); return timeseries; } //+------------------------------------------------------------------+
ここで、第37部で検討したIndexTimeSeries()メソッドを使用して銘柄に名前を付けるために時系列オブジェクトのインデックスを取得します 。、取得されたインデックスは、リストから時系列オブジェクトを取得するために使用されます。リストからインデックスまたはオブジェクトを取得できなかった場合、NULLが返されます。それ以外の場合は、リスト内の要求されたオブジェクトへのポインタを取得します。
以下は、指定された銘柄/期間の時系列オブジェクトを返すメソッドです。
//+------------------------------------------------------------------+ //| Return the timeseries object of the specified symbol/period | //+------------------------------------------------------------------+ CSeries *CTimeSeriesCollection::GetSeries(const string symbol,const ENUM_TIMEFRAMES timeframe) { CTimeSeries *timeseries=this.GetTimeseries(symbol); if(timeseries==NULL) return NULL; CSeries *series=timeseries.GetSeries(timeframe); return series; } //+-----------------------------------------------------------------------+
ここでは、時系列オブジェクトをGetTimeseries()メソッド(上記で考察)メソッドに渡された銘柄によって取得します。
取得した時系列オブジェクトから、指定した時間枠で時系列リストを取得し、取得した時系列オブジェクトへのポインタを返します。
時系列オブジェクトのGetSeries()メソッドは、上記のIndexTimeframe()メソッドを使用して必要な時系列を返しますが、CTimeSeries時系列オブジェクトのGetSeries()メソッドは次のようになります。
CSeries *GetSeries(const ENUM_TIMEFRAMES timeframe) { return this.m_list_series.At(this.IndexTimeframe(timeframe)); }
クラスのpublicセクションで時系列を作成するための3つのメソッドを削除して、指定した銘柄の指定した時系列を作成するためのメソッドを1つだけ残します。
//--- Create (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols, //--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols bool CreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0); bool CreateSeries(const ENUM_TIMEFRAMES timeframe,const uint required=0); bool CreateSeries(const string symbol,const uint required=0); bool CreateSeries(const uint required=0); //--- Update (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols, //--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols and (5) all timeseries except for the current symbol
これまでのところ、削除された3つのメソッドは冗長であるようです。そこで、代わりに指定された時系列を再作成するメソッド、空の時系列を返すメソッド、部分的に入力された時系列を返すメソッドの3つの新しいメソッドを宣言しましょう。
//--- (1) Create and (2) re-create a specified timeseries of a specified symbol bool CreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0); bool ReCreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0); //--- Return (1) an empty, (2) partially filled timeseries CSeries *GetSeriesEmpty(void); CSeries *GetSeriesIncompleted(void);
時系列を再作成する必要があるのはなぜでしょうか。ライブラリを初期化し、すべての銘柄のすべての適用された時系列を作成するとき、履歴データのダウンロードを開始する関数を使用します。何度も言ったように、プログラムが指標であり、プログラムが起動された銘柄と時間枠を参照している場合、競合が引き起こされる可能性があります。したがって、そのような状況はスキップされます。完了してOnCalculate()ハンドラに入ると、最初に作成された時系列を修正し、(初期化中にスキップされた)空のものを取得し、OnCalculate()の rates_total変数からのデータを使用して再作成します。
OnCalculate()から時系列配列データを取得する代わりに、時系列更新メソッドがデータ構造体を受け取ります。時系列オブジェクトからイベントを取得して銘柄時系列コレクションのすべてのオブジェクトのイベントリストに追加するためのメソッドを宣言します。
//--- Update (1) the specified timeseries of the specified symbol, (2) all timeseries of all symbols void Refresh(const string symbol,const ENUM_TIMEFRAMES timeframe,SDataCalculate &data_calculate); void Refresh(SDataCalculate &data_calculate); //--- Get events from the timeseries object and add them to the list bool SetEvents(CTimeSeries *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); //--- Constructor CTimeSeriesCollection(); }; //+------------------------------------------------------------------+
空の時系列を返すメソッドと部分入力された時系列を返すメソッドを実装します。
//+------------------------------------------------------------------+ //|Return the empty (created but not filled with data) timeseries | //+------------------------------------------------------------------+ CSeries *CTimeSeriesCollection::GetSeriesEmpty(void) { //--- In the loop by the timeseries object list int total_timeseries=this.m_list.Total(); for(int i=0;i<total_timeseries;i++) { //--- get the next object of all symbol timeseries by the loop index CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL || !timeseries.IsAvailable()) continue; //--- get the list of timeseries objects from the object of all symbol timeseries CArrayObj *list_series=timeseries.GetListSeries(); if(list_series==NULL) continue; //--- in the loop by the symbol timeseries list int total_series=list_series.Total(); for(int j=0;j<total_series;j++) { //--- get the next timeseries CSeries *series=list_series.At(j); if(series==NULL || !series.IsAvailable()) continue; //--- if the timeseries has no bar objects, //--- return the pointer to the timeseries if(series.DataTotal()==0) return series; } } return NULL; } //+------------------------------------------------------------------+ //| Return partially filled timeseries | //+------------------------------------------------------------------+ CSeries *CTimeSeriesCollection::GetSeriesIncompleted(void) { //--- In the loop by the timeseries object list int total_timeseries=this.m_list.Total(); for(int i=0;i<total_timeseries;i++) { //--- get the next object of all symbol timeseries by the loop index CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL || !timeseries.IsAvailable()) continue; //--- get the list of timeseries objects from the object of all symbol timeseries CArrayObj *list_series=timeseries.GetListSeries(); if(list_series==NULL) continue; //--- in the loop by the symbol timeseries list int total_series=list_series.Total(); for(int j=0;j<total_series;j++) { //--- get the next timeseries CSeries *series=list_series.At(j); if(series==NULL || !series.IsAvailable()) continue; //--- if the timeseries has bar objects, //--- but their number is not equal to the requested and available one for the symbol, //--- return the pointer to the timeseries if(series.DataTotal()>0 && series.AvailableUsedData()!=series.DataTotal()) return series; } } return NULL; } //+------------------------------------------------------------------+
各メソッド文字列はコメント化されています。空および部分入力された時系列のチェックを除いて、メソッドは同様です。
メソッドは、検索条件を満たす最初の次の時系列を返します。これは、後続の各ティック(OnCalculateに入る)で可能なすべての空または部分入力された時系列を連続的に取得するために行われました。これは、指標の不十分なデータを正しく処理するためのMetaQuotesの推奨事項に対応しています。ハンドラを終了し、次のティックでデータの存在を確認します。
指定された銘柄の指定された時系列を作成するためのメソッドを実装します。
//+------------------------------------------------------------------+ //| Create the specified timeseries of the specified symbol | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::CreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0) { CTimeSeries *timeseries=this.GetTimeseries(symbol); if(timeseries==NULL) return false; if(!timeseries.AddSeries(timeframe,required)) return false; if(!timeseries.SyncData(timeframe,required,rates_total)) return false; return timeseries.CreateSeries(timeframe,required); } //+------------------------------------------------------------------+
このメソッドは、単一の銘柄の時系列オブジェクト(指定されたチャート期間の新しい時系列)にデータを追加します。
このメソッドは、 銘柄と必要な時系列の期間を受け取ります。
時系列オブジェクトを取得して指定されたチャート期間の新しい時系列を追加します。
銘柄/期間データをリクエストし、時系列に必要なデータ量を設定します。
以前のすべてのアクションが成功した場合、新しい時系列を作成し、それにデータを追加した結果を返します。
以前の記事では、これらすべての方法を検討しました。ここでは、必要な銘柄/期間の時系列を作成する新しいロジックを紹介しました。ロジックは、第37部で説明されているものとは異なります。
指定された銘柄の指定された時系列を再作成するためのメソッドを実装します。
//+------------------------------------------------------------------+ //| Re-create a specified timeseries of a specified symbol | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::ReCreateSeries(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0) { CTimeSeries *timeseries=this.GetTimeseries(symbol); if(timeseries==NULL) return false; if(!timeseries.SyncData(timeframe,rates_total,required)) return false; return timeseries.CreateSeries(timeframe,required); } //+------------------------------------------------------------------+
ここではすべてがまったく同じで、違いは1つだけです。それは、時系列はすでに作成されているため、すべての銘柄時系列のオブジェクトに新しい時系列を追加するステップはスキップされることです。
時系列オブジェクトからイベントを受け取り、時系列コレクションイベントのリストに追加するメソッドを実装します。
//+------------------------------------------------------------------+ //| Get events from the timeseries object and add them to the list | //+------------------------------------------------------------------+ bool CTimeSeriesCollection::SetEvents(CTimeSeries *timeseries) { //--- Set the flag of successfully adding an event to the list and //--- get the list of symbol timeseries object events bool res=true; CArrayObj *list=timeseries.GetListEvents(); if(list==NULL) return false; //--- In the loop by the obtained list of events, int total=list.Total(); for(int i=0;i<total;i++) { //--- get the next event by the loop index and CEventBaseObj *event=timeseries.GetEvent(i); if(event==NULL) continue; //--- add the result of adding the obtained event to the flag value //--- from the symbol timeseries list to the timeseries collection list res &=this.EventAdd(event.ID(),event.LParam(),event.DParam(),event.SParam()); } //--- Return the result of adding events to the list return res; } //+------------------------------------------------------------------+
このメソッドは、銘柄時系列オブジェクトへのポインタを受け取ります。そのすべてのイベントは、オブジェクトイベントリストによってループ内の時系列コレクションイベントのリストに追加されます。
指定された銘柄の指定された時系列を更新し、そのイベントを時系列コレクションイベントのリストに追加するメソッドを実装します。
//+------------------------------------------------------------------+ //| 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 CTimeSeries *timeseries=this.GetTimeseries(symbol); if(timeseries==NULL) return; //--- If there is no new tick on the timeseries object symbol, exit if(!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); } //+------------------------------------------------------------------+
すべての銘柄のすべての時系列を更新し、それらのイベントを時系列コレクションイベントのリストに追加するメソッドを実装します。
//+------------------------------------------------------------------+ //| Update all timeseries of all symbols | //+------------------------------------------------------------------+ void CTimeSeriesCollection::Refresh(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(); //--- In the loop by all symbol timeseries objects in the collection, int total=this.m_list.Total(); for(int i=0;i<total;i++) { //--- get the next symbol timeseries object CTimeSeries *timeseries=this.m_list.At(i); if(timeseries==NULL) continue; //--- if there is no new tick on a timeseries symbol, move to the next object in the list if(!timeseries.IsNewTick()) continue; //--- Update all symbol timeseries timeseries.RefreshAll(data_calculate); //--- If the event flag enabled for the symbol timeseries object, //--- 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); } } //+------------------------------------------------------------------+
これらのメソッドはすべて詳細にコメントされており、ロジックは理解しやすいものです。
これで、現在の段階でのすべての時系列クラスの作業は完了です。
ここで、プログラムから時系列コレクションを使用するためのCEngine ライブラリのメインオブジェクト(\MQL5\Include\DoEasy\Engine.mqh)を改善しましょう。
クラスのprivateセクションで一次停止オブジェクトを宣言します。
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
クラスのpublicセクションで、時系列コレクションにイベントが存在するかどうかのフラグを返すメソッドを追加します。
//--- Return the (1) hedge account, (2) working in the tester, (3) account event, (4) symbol event and (5) trading event flag bool IsHedge(void) const { return this.m_is_hedge; } bool IsTester(void) const { return this.m_is_tester; } bool IsAccountsEvent(void) const { return this.m_accounts.IsEvent(); } bool IsSymbolsEvent(void) const { return this.m_symbols.IsEvent(); } bool IsTradeEvent(void) const { return this.m_events.IsEvent(); } bool IsSeriesEvent(void) const { return this.m_time_series.IsEvent(); }
このメソッドは、時系列コレクションオブジェクトのIsEvent()メソッド操作の結果を返します。
指標のOnCalculate()ハンドラからの配列データは、現在の時系列データを処理するための時系列更新メソッドに送信する必要があるため、タイマーとティックイベント処理メソッドにOnCalculate()配列データ構造体をを渡すようにするとともに、 Calculateイベントを処理するメソッドを宣言します。
//--- (1) Timer, (2) NewTick event handler and (3) Calculate event handler void OnTimer(SDataCalculate &data_calculate); void OnTick(SDataCalculate &data_calculate,const uint required=0); int OnCalculate(SDataCalculate &data_calculate,const uint required=0);
クラスのpublicセクションで、時系列イベントリストを返すメソッドを追加します。
//--- Return (1) the timeseries collection and (2) the list of timeseries from the timeseries collection and (3) the list of timeseries events CTimeSeriesCollection *GetTimeSeriesCollection(void) { return &this.m_time_series; } CArrayObj *GetListTimeSeries(void) { return this.m_time_series.GetList(); } CArrayObj *GetListSeriesEvents(void) { return this.m_time_series.GetListEvents(); }
このメソッドは、GetListEvents()時系列コレクションメソッドを使用して、字形れ鵜tコレクションイベントのリストへのポインタを返します。
クラスのpublicセクションには、さまざまな時系列を作成するための4つのメソッドがあります。まだ必要のない3つのメソッドを一時的に削除しましょう。
//--- Create (1) the specified timeseries of the specified symbol, (2) the specified timeseries of all symbols, //--- (3) all timeseries of the specified symbol and (4) all timeseries of all symbols bool SeriesCreate(const string symbol,const ENUM_TIMEFRAMES timeframe,const uint required=0) { return this.m_series.CreateSeries(symbol,timeframe,required); } bool SeriesCreate(const ENUM_TIMEFRAMES timeframe,const uint required=0) { return this.m_series.CreateSeries(timeframe,required); } bool SeriesCreate(const string symbol,const uint required=0) { return this.m_series.CreateSeries(symbol,required); } bool SeriesCreate(const uint required=0) { return this.m_series.CreateSeries(required); }
そして、それらを使用されたすべてのコレクション銘柄のすべての時系列を作成するためのメソッドの宣言に置き換えます。また、指定した時系列を再作成するメソッドを記述して、サーバとの時系列の同期をリクエストするメソッドを宣言します。
//--- Create (1) the specified timeseries of the specified symbol and (2) all used timeseries of all used symbols bool SeriesCreate(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0) { return this.m_time_series.CreateSeries(symbol,timeframe,rates_total,required); } bool SeriesCreateAll(const string &array_periods[],const int rates_total=0,const uint required=0); //--- Re-create a specified timeseries of a specified symbol bool SeriesReCreate(const string symbol,const ENUM_TIMEFRAMES timeframe,const int rates_total=0,const uint required=0) { return this.m_time_series.ReCreateSeries(symbol,timeframe,rates_total,required); } //--- Synchronize timeseries data with the server void SeriesSync(SDataCalculate &data_calculate,const uint required=0);
また、時系列コレクションを更新するための4つのメソッドもあります。
指定された時系列を更新するメソッドとすべてのコレクション時系列を更新するメソッドの2つのみを残します。
//--- Update (1) the specified timeseries of the specified symbol, (2) 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(SDataCalculate &data_calculate) { this.m_time_series.Refresh(data_calculate); }
メソッドには変数とOnCalculate()配列のデータを特徴とする構造体は、OnCalculate()配列値の代わりに渡されます。
指定された銘柄の時系列オブジェクトへのポインタを返すメソッド、指定された時系列オブジェクトへのポインタを返すメソッド、空の時系列へのポインタを返すメソッド、部分的に入力された時系列を返すメソッドの4つの新しいメソッドを追加しましょう。
//--- Return (1) the timeseries object of the specified symbol and (2) the timeseries object of the specified symbol/period CTimeSeries *SeriesGetTimeseries(const string symbol) { return this.m_time_series.GetTimeseries(symbol); } CSeries *SeriesGetSeries(const string symbol,const ENUM_TIMEFRAMES timeframe) { return this.m_time_series.GetSeries(symbol,timeframe); } //--- Return (1) an empty, (2) partially filled timeseries CSeries *SeriesGetSeriesEmpty(void) { return this.m_time_series.GetSeriesEmpty(); } CSeries *SeriesGetSeriesIncompleted(void) { return this.m_time_series.GetSeriesIncompleted(); }
メソッドは、上記で検討した時系列コレクションの同じ名前のメソッドを返した結果を返します。
必要なすべてのコレクションへのポインタを取引クラスに渡すTradingOnInit()メソッドは、すべてのコレクションクラスの必要な初期化がメソッド内で実行されるために、より適切な CollectionOnInit()に名前変更されました 。
クラス本体のコードの最後に、一時停止オブジェクトを操作するためのメソッドを含むブロックを追加します。
//--- Set the new (1) pause countdown start time and (2) pause in milliseconds void PauseSetTimeBegin(const ulong time) { this.m_pause.SetTimeBegin(time); } void PauseSetWaitingMSC(const ulong pause) { this.m_pause.SetWaitingMSC(pause); } //--- Return (1) the time passed from the pause countdown start in milliseconds, (2) waiting completion flag //--- (3) pause countdown start time, (4) pause in milliseconds ulong PausePassed(void) const { return this.m_pause.Passed(); } bool PauseIsCompleted(void) const { return this.m_pause.IsCompleted(); } ulong PauseTimeBegin(void) const { return this.m_pause.TimeBegin(); } ulong PauseTimeWait(void) const { return this.m_pause.TimeWait(); } //--- Return the description (1) of the time passed till the countdown starts in milliseconds, //--- (2) pause countdown start time, (3) pause in milliseconds string PausePassedDescription(void) const { return this.m_pause.PassedDescription(); } string PauseTimeBeginDescription(void) const { return this.m_pause.TimeBeginDescription(); } string PauseWaitingMSCDescription(void) const { return this.m_pause.WaitingMSCDescription(); } string PauseWaitingSECDescription(void) const { return this.m_pause.WaitingSECDescription(); } //--- Launch the new pause countdown void Pause(const ulong pause_msc,const datetime time_start=0) { this.PauseSetWaitingMSC(pause_msc); this.PauseSetTimeBegin(time_start*1000); while(!this.PauseIsCompleted() && !IsStopped()){} } //--- Constructor/destructor CEngine(); ~CEngine();
Pauseクラスについては、第30部で説明されています。このクラスは、指標では機能しない Sleep()関数の代わりに一時停止を挿入するためのものです。
これらのメソッドから呼び出されるすでに説明されているCPauseクラスメソッドに加えて、パラメータを事前に初期化せずに新しい待機待機を開始できるようにする別のPause()メソッドを追加しました。すべてのパラメータがメソッドに渡され、メソッドは入力として渡されたミリ秒単位の一時停止の完了を待機します。これらのメソッドは、指標の一時停止を整理するプログラムで役立ちます。
この一時停止オブジェクトは、Sleep()関数と同様に、指標が起動されたメインスレッドを遅延させることに注意してください。
この一時停止は、必要に応じて指標に適用する必要があります。<br1 />
CEngineクラスのタイマーが再配置されました。以前は、テスター内かどうかに関係なく、各ハンドラが機能する場所をテスターで確認していましたが、すべてのコレクションの各ハンドラがこのようなチェックを実行する必要があり、これは不合理でした。
作業がどこ(テスター外またはテスター内)で行われたかを最初に確認するようになりました。次に、すべてのコレクションの処理がブロック内で実行されます(非テスターおよびテスター)。
//+------------------------------------------------------------------+ //| CEngine timer | //+------------------------------------------------------------------+ void CEngine::OnTimer(SDataCalculate &data_calculate) { //--- If this is not a tester, work with collection events by timer if(!this.IsTester()) { //--- Timer of the collections of historical orders and deals, as well as of market orders and positions int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID); CTimerCounter* cnt1=this.m_list_counters.At(index); if(cnt1!=NULL) { //--- If unpaused, work with the order, deal and position collections events if(cnt1.IsTimeDone()) this.TradeEventsControl(); } //--- Account collection timer index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID); CTimerCounter* cnt2=this.m_list_counters.At(index); if(cnt2!=NULL) { //--- If unpaused, work with the account collection events if(cnt2.IsTimeDone()) this.AccountEventsControl(); } //--- Timer 1 of the symbol collection (updating symbol quote data in the collection) index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID1); CTimerCounter* cnt3=this.m_list_counters.At(index); if(cnt3!=NULL) { //--- If the pause is over, update quote data of all symbols in the collection if(cnt3.IsTimeDone()) this.m_symbols.RefreshRates(); } //--- Timer 2 of the symbol collection (updating all data of all symbols in the collection and tracking symbl and symbol search events in the market watch window) index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID2); CTimerCounter* cnt4=this.m_list_counters.At(index); if(cnt4!=NULL) { //--- If the pause is over if(cnt4.IsTimeDone()) { //--- update data and work with events of all symbols in the collection this.SymbolEventsControl(); //--- When working with the market watch list, check the market watch window events if(this.m_symbols.ModeSymbolsList()==SYMBOLS_MODE_MARKET_WATCH) this.MarketWatchEventsControl(); } } //--- Trading class timer index=this.CounterIndex(COLLECTION_REQ_COUNTER_ID); CTimerCounter* cnt5=this.m_list_counters.At(index); if(cnt5!=NULL) { //--- If unpaused, work with the list of pending requests if(cnt5.IsTimeDone()) this.m_trading.OnTimer(); } //--- Timeseries collection timer index=this.CounterIndex(COLLECTION_TS_COUNTER_ID); CTimerCounter* cnt6=this.m_list_counters.At(index); if(cnt6!=NULL) { //--- If unpaused, work with the timeseries list if(cnt6.IsTimeDone()) this.SeriesRefresh(data_calculate); } } //--- If this is a tester, work with collection events by tick else { //--- work with events of collections of orders, deals and positions by tick this.TradeEventsControl(); //--- work with events of collections of accounts by tick this.AccountEventsControl(); //--- update quote data of all collection symbols by tick this.m_symbols.RefreshRates(); //--- work with events of all symbols in the collection by tick this.SymbolEventsControl(); //--- work with the list of pending orders by tick this.m_trading.OnTimer(); //--- work with the timeseries list by tick this.SeriesRefresh(data_calculate); } } //+------------------------------------------------------------------+
ハンドラはよりコンパクトになり、よりわかりやすいロジックを備えています。また、不必要な繰り返しチェックが軽減されました。
以下は、空の時系列データをサーバと同期し、空の時系列を再作成するメソッドです。
//+------------------------------------------------------------------+ //| Synchronize timeseries data with the server | //+------------------------------------------------------------------+ void 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 CSeries *series=this.SeriesGetSeriesEmpty(); 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(0,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(); } } } //--- Delete all comments else { ::Comment(""); ::ChartRedraw(::ChartID()); } } //+------------------------------------------------------------------+
このメソッドは、使用された任意の時系列の履歴データ(チャートの任意の銘柄と期間)を正しくロードするための基礎となります。
このメソッドは、時系列コレクションから最初の満たされていない時系列を受け取ります。これは、1ティック前にデータがなかったことを意味します。時系列のデータとサーバーのデータを同期する試みはすぐに実行されます。失敗した場合は、次のティックまでメソッドを終了します。データが同期されている場合、時系列は再作成されます—履歴から利用可能なすべての(ただし、要求された数量を超えない)バーで埋められます。
プロセスはすべてのティックで実行されます。次の空の時系列を取得し、同期して空の時系列がなくなるまで再作成します。
以下は、NewTickおよびCalculateイベントハンドラの実装です。
//+------------------------------------------------------------------+ //| 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 this.SeriesSync(data_calculate,required); //--- end } //+------------------------------------------------------------------+ //| 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 this.SeriesSync(data_calculate,required); //--- return rates_total return data_calculate.rates_total; } //+------------------------------------------------------------------+
両方のメソッドで空の時系列を再作成するメソッドが呼び出されます。
メソッド自体は、ライブラリに基づいて同じ名前のプログラムハンドラから呼び出されます。
使用されているすべての銘柄の適用されたすべての時系列を作成するためのメソッドの実装です。
//+------------------------------------------------------------------+ //| Create all applied timeseries of all used symbols | //+------------------------------------------------------------------+ bool CEngine::SeriesCreateAll(const string &array_periods[],const int rates_total=0,const uint required=0) { //--- Set the flag of successful creation of all timeseries of all symbols bool res=true; //--- Get the list of all used symbols CArrayObj* list_symbols=this.GetListAllUsedSymbols(); if(list_symbols==NULL) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_GET_SYMBOLS_ARRAY)); return false; } //--- In the loop by the total number of symbols for(int i=0;i<list_symbols.Total();i++) { //--- get the next symbol object CSymbol *symbol=list_symbols.At(i); if(symbol==NULL) { ::Print(DFUN,"index ",i,": ",CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ)); continue; } //--- In the loop by the total number of used timeframes, int total_periods=::ArraySize(array_periods); for(int j=0;j<total_periods;j++) { //--- create the timeseries object of the next symbol. //--- Add the timeseries creation result to the res variable ENUM_TIMEFRAMES timeframe=TimeframeByDescription(array_periods[j]); res &=this.SeriesCreate(symbol.Name(),timeframe,rates_total,required); } } //--- Return the result of creating all timeseries for all symbols return res; } //+------------------------------------------------------------------+
このメソッドは、使用されているすべての銘柄のリストを作成した後、プログラムの初期化中に呼び出されます。
メソッドは、初期化中に作成された配列を受け取ります。配列には、使用されているグラフ期間の名前と時系列を作成するためのパラメータ(現在の時系列バーの数(指標のみの場合—rates_total)と作成された時系列に必要な履歴の深さ(デフォルトは1000ですが、銘柄のBars()値以下で、指標のrates_total以下)が含まれます。
現在、これらはすべて時系列を操作するために必要な改善です。
指標での時系列とそのイベントのテスト
指標で時系列コレクションクラスの動作をテストするには、ターミナル指標ディレクトリ\MQL5\Indicators\TestDoEasy\に新しいフォルダーを作成します。新しいサブフォルダPart39\を作成し、中に新しい指標TestDoEasyPart39.mq5を作成します。
ここでは何も描画しないので、描画された指標バッファーの数と種類はこれまでのところ問題ではありません。ただし、 DRAW_LINE描画タイプの描画バッファーを2つ設定して、将来使用できるようにしました。
必要な銘柄と時間枠を設定するために必要な指標の入力、およびその他のいくつかの入力は、前の記事で説明したテストEAから取得されました。それはこのように見えます。
//+------------------------------------------------------------------+ //| TestDoEasyPart39.mq5 | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Engine.mqh> //--- enums //--- defines //--- structures //--- properties #property indicator_chart_window #property indicator_buffers 2 #property indicator_plots 2 //--- plot Label1 #property indicator_label1 "Label1" #property indicator_type1 DRAW_LINE #property indicator_color1 clrRed #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- plot Label2 #property indicator_label2 "Label2" #property indicator_type2 DRAW_LINE #property indicator_color2 clrGreen #property indicator_style2 STYLE_SOLID #property indicator_width2 1 //--- indicator buffers double Buffer1[]; double Buffer2[]; //--- 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 bool InpUseSounds = true; // Use sounds //--- 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() { //--- indicator buffers mapping SetIndexBuffer(0,Buffer1,INDICATOR_DATA); SetIndexBuffer(1,Buffer2,INDICATOR_DATA); //--- Set indicator global variables prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"; testing=engine.IsTester(); ZeroMemory(rates_data); //--- Initialize DoEasy library OnInitDoEasy(); //--- Check and remove remaining indicator graphical objects if(IsPresentObectByPrefix(prefix)) ObjectsDeleteAll(0,prefix); //--- Check playing a standard sound using macro substitutions engine.PlaySoundByDescription(SND_OK); //--- Wait for 600 milliseconds engine.Pause(600); engine.PlaySoundByDescription(SND_NEWS); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
指標のOnDeinit()ハンドラは、前の記事で説明したテストEAから取得されます。
//+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Remove indicator graphical objects by an object name prefix ObjectsDeleteAll(0,prefix); Comment(""); } //+------------------------------------------------------------------+
EAからOnTimer()およびOnChartEvent()ハンドラも取得してみましょう。
//+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { //--- Launch the library timer (only not in the tester) if(!MQLInfoInteger(MQL_TESTER)) engine.OnTimer(rates_data); } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- If working in the tester, exit if(MQLInfoInteger(MQL_TESTER)) return; //--- Handling mouse events if(id==CHARTEVENT_OBJECT_CLICK) { //--- Handling pressing the buttons in the panel if(StringFind(sparam,"BUTT_")>0) PressButtonEvents(sparam); } //--- Handling DoEasy library events if(id>CHARTEVENT_CUSTOM-1) { OnDoEasyEvent(id,lparam,dparam,sparam); } } //+------------------------------------------------------------------+
配列の構造と変数データを指標の最初のと2番目のOnCalculate()フォームから埋めるための2つの関数を作成します。
//+------------------------------------------------------------------+ //| Copy data from the first OnCalculate() form to the structure | //+------------------------------------------------------------------+ void CopyData(SDataCalculate &data_calculate, const int rates_total, const int prev_calculated, const int begin, const double &price[]) { //--- Get the array indexing flag as in the timeseries. If failed, //--- set the indexing direction for the array as in the timeseries bool as_series_price=ArrayGetAsSeries(price); if(!as_series_price) ArraySetAsSeries(price,true); //--- Copy the array zero bar to the OnCalculate() SDataCalculate data structure data_calculate.rates_total=rates_total; data_calculate.prev_calculated=prev_calculated; data_calculate.begin=begin; data_calculate.price=price[0]; //--- Return the array's initial indexing direction if(!as_series_price) ArraySetAsSeries(price,false); } //+------------------------------------------------------------------+ //| Copy data from the second OnCalculate() form to the structure | //+------------------------------------------------------------------+ void CopyData(SDataCalculate &data_calculate, 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[]) { //--- Get the array indexing flags as in the timeseries. If failed, //--- set the indexing direction or the arrays as in the timeseries bool as_series_time=ArrayGetAsSeries(time); if(!as_series_time) ArraySetAsSeries(time,true); bool as_series_open=ArrayGetAsSeries(open); if(!as_series_open) ArraySetAsSeries(open,true); bool as_series_high=ArrayGetAsSeries(high); if(!as_series_high) ArraySetAsSeries(high,true); bool as_series_low=ArrayGetAsSeries(low); if(!as_series_low) ArraySetAsSeries(low,true); bool as_series_close=ArrayGetAsSeries(close); if(!as_series_close) ArraySetAsSeries(close,true); bool as_series_tick_volume=ArrayGetAsSeries(tick_volume); if(!as_series_tick_volume) ArraySetAsSeries(tick_volume,true); bool as_series_volume=ArrayGetAsSeries(volume); if(!as_series_volume) ArraySetAsSeries(volume,true); bool as_series_spread=ArrayGetAsSeries(spread); if(!as_series_spread) ArraySetAsSeries(spread,true); //--- Copy the arrays' zero bar to the OnCalculate() SDataCalculate data structure data_calculate.rates_total=rates_total; data_calculate.prev_calculated=prev_calculated; data_calculate.rates.time=time[0]; data_calculate.rates.open=open[0]; data_calculate.rates.high=high[0]; data_calculate.rates.low=low[0]; data_calculate.rates.close=close[0]; data_calculate.rates.tick_volume=tick_volume[0]; data_calculate.rates.real_volume=(#ifdef __MQL5__ volume[0] #else 0 #endif); data_calculate.rates.spread=(#ifdef __MQL5__ spread[0] #else 0 #endif); //--- Return the arrays' initial indexing direction if(!as_series_time) ArraySetAsSeries(time,false); if(!as_series_open) ArraySetAsSeries(open,false); if(!as_series_high) ArraySetAsSeries(high,false); if(!as_series_low) ArraySetAsSeries(low,false); if(!as_series_close) ArraySetAsSeries(close,false); if(!as_series_tick_volume) ArraySetAsSeries(tick_volume,false); if(!as_series_volume) ArraySetAsSeries(volume,false); if(!as_series_spread) ArraySetAsSeries(spread,false); } //+------------------------------------------------------------------+
DoEasyライブラリイベントを処理する機能をテストEAから移動します。
//+------------------------------------------------------------------+ //| Handling DoEasy library events | //+------------------------------------------------------------------+ void OnDoEasyEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { int idx=id-CHARTEVENT_CUSTOM; //--- Retrieve (1) event time milliseconds, (2) reason and (3) source from lparam, as well as (4) set the exact event time ushort msc=engine.EventMSC(lparam); ushort reason=engine.EventReason(lparam); ushort source=engine.EventSource(lparam); long time=TimeCurrent()*1000+msc; //--- Handling symbol events if(source==COLLECTION_SYMBOLS_ID) { CSymbol *symbol=engine.GetSymbolObjByName(sparam); if(symbol==NULL) return; //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol int digits=(idx<SYMBOL_PROP_INTEGER_TOTAL ? 0 : symbol.Digits()); //--- Event text description string id_descr=(idx<SYMBOL_PROP_INTEGER_TOTAL ? symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_INTEGER)idx) : symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_DOUBLE)idx)); //--- Property change text value string value=DoubleToString(dparam,digits); //--- Check event reasons and display its description in the journal if(reason==BASE_EVENT_REASON_INC) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_DEC) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_MORE_THEN) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_LESS_THEN) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_EQUALS) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } } //--- Handling account events else if(source==COLLECTION_ACCOUNT_ID) { CAccount *account=engine.GetAccountCurrent(); if(account==NULL) return; //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol int digits=int(idx<ACCOUNT_PROP_INTEGER_TOTAL ? 0 : account.CurrencyDigits()); //--- Event text description string id_descr=(idx<ACCOUNT_PROP_INTEGER_TOTAL ? account.GetPropertyDescription((ENUM_ACCOUNT_PROP_INTEGER)idx) : account.GetPropertyDescription((ENUM_ACCOUNT_PROP_DOUBLE)idx)); //--- Property change text value string value=DoubleToString(dparam,digits); //--- Checking event reasons and handling the increase of funds by a specified value, //--- In case of a property value increase if(reason==BASE_EVENT_REASON_INC) { //--- Display an event in the journal Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); //--- if this is an equity increase if(idx==ACCOUNT_PROP_EQUITY) { //--- Get the list of all open positions for the current symbol CArrayObj* list_positions=engine.GetListMarketPosition(); list_positions=CSelect::ByOrderProperty(list_positions,ORDER_PROP_SYMBOL,Symbol(),EQUAL); //--- Select positions with the profit exceeding zero list_positions=CSelect::ByOrderProperty(list_positions,ORDER_PROP_PROFIT_FULL,0,MORE); if(list_positions!=NULL) { //--- Sort the list by profit considering commission and swap list_positions.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the position index with the highest profit int index=CSelect::FindOrderMax(list_positions,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list_positions.At(index); if(position!=NULL) { //--- Get a ticket of a position with the highest profit and close the position by a ticket engine.ClosePosition(position.Ticket()); } } } } } //--- Other events are simply displayed in the journal if(reason==BASE_EVENT_REASON_DEC) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_MORE_THEN) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_LESS_THEN) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_EQUALS) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } } //--- Handling market watch window events else if(idx>MARKET_WATCH_EVENT_NO_EVENT && idx<SYMBOL_EVENTS_NEXT_CODE) { //--- Market Watch window event string descr=engine.GetMWEventDescription((ENUM_MW_EVENT)idx); string name=(idx==MARKET_WATCH_EVENT_SYMBOL_SORT ? "" : ": "+sparam); Print(TimeMSCtoString(lparam)," ",descr,name); } //--- Handling timeseries events else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE) { //--- "New bar" event if(idx==SERIES_EVENTS_NEW_BAR) { Print(TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam)); } } //--- Handling trading events else if(idx>TRADE_EVENT_NO_EVENT && idx<TRADE_EVENTS_NEXT_CODE) { //--- Get the list of trading events CArrayObj *list=engine.GetListAllOrdersEvents(); if(list==NULL) return; //--- get the event index shift relative to the end of the list //--- in the tester, the shift is passed by the lparam parameter to the event handler //--- outside the tester, events are sent one by one and handled in OnChartEvent() int shift=(testing ? (int)lparam : 0); CEvent *event=list.At(list.Total()-1-shift); if(event==NULL) return; //--- Accrue the credit if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CREDIT) { Print(DFUN,event.TypeEventDescription()); } //--- Additional charges if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CHARGE) { Print(DFUN,event.TypeEventDescription()); } //--- Correction if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CORRECTION) { Print(DFUN,event.TypeEventDescription()); } //--- Enumerate bonuses if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BONUS) { Print(DFUN,event.TypeEventDescription()); } //--- Additional commissions if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION) { Print(DFUN,event.TypeEventDescription()); } //--- Daily commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_DAILY) { Print(DFUN,event.TypeEventDescription()); } //--- Monthly commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY) { Print(DFUN,event.TypeEventDescription()); } //--- Daily agent commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY) { Print(DFUN,event.TypeEventDescription()); } //--- Monthly agent commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY) { Print(DFUN,event.TypeEventDescription()); } //--- Interest rate if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_INTEREST) { Print(DFUN,event.TypeEventDescription()); } //--- Canceled buy deal if(event.TypeEvent()==TRADE_EVENT_BUY_CANCELLED) { Print(DFUN,event.TypeEventDescription()); } //--- Canceled sell deal if(event.TypeEvent()==TRADE_EVENT_SELL_CANCELLED) { Print(DFUN,event.TypeEventDescription()); } //--- Dividend operations if(event.TypeEvent()==TRADE_EVENT_DIVIDENT) { Print(DFUN,event.TypeEventDescription()); } //--- Accrual of franked dividend if(event.TypeEvent()==TRADE_EVENT_DIVIDENT_FRANKED) { Print(DFUN,event.TypeEventDescription()); } //--- Tax charges if(event.TypeEvent()==TRADE_EVENT_TAX) { Print(DFUN,event.TypeEventDescription()); } //--- Replenishing account balance if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_REFILL) { Print(DFUN,event.TypeEventDescription()); } //--- Withdrawing funds from balance if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL) { Print(DFUN,event.TypeEventDescription()); } //--- Pending order placed if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_PLASED) { Print(DFUN,event.TypeEventDescription()); } //--- Pending order removed if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_REMOVED) { Print(DFUN,event.TypeEventDescription()); } //--- Pending order activated by price if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED) { Print(DFUN,event.TypeEventDescription()); } //--- Pending order partially activated by price if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position opened if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED) { Print(DFUN,event.TypeEventDescription()); } //--- Position opened partially if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed by an opposite one if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_POS) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed by StopLoss if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_SL) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed by TakeProfit if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_TP) { Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by a new deal (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET) { Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by activating a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING) { Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by partial market order execution (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by activating a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by a new deal (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET) { Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by partial execution of a market order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by activating a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING) { Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by partial activation of a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed partially if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position partially closed by an opposite one if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed partially by StopLoss if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed partially by TakeProfit if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP) { Print(DFUN,event.TypeEventDescription()); } //--- StopLimit order activation if(event.TypeEvent()==TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order and StopLoss price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_SL) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order and TakeProfit price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_TP) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order, StopLoss and TakeProfit price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_SL_TP) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order's StopLoss and TakeProfit price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_SL_TP) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order's StopLoss if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_SL) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order's TakeProfit if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_TP) { Print(DFUN,event.TypeEventDescription()); } //--- Changing position's StopLoss and TakeProfit if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_SL_TP) { Print(DFUN,event.TypeEventDescription()); } //--- Changing position StopLoss if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_SL) { Print(DFUN,event.TypeEventDescription()); } //--- Changing position TakeProfit if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_TP) { Print(DFUN,event.TypeEventDescription()); } } } //+------------------------------------------------------------------+
以下は、EAからテスターでライブラリイベントを操作する関数です。
//+------------------------------------------------------------------+ //| Working with events in the tester | //+------------------------------------------------------------------+ void EventsHandling(void) { //--- If a trading event is present if(engine.IsTradeEvent()) { //--- Number of trading events occurred simultaneously int total=engine.GetTradeEventsTotal(); for(int i=0;i<total;i++) { //--- Get the next event from the list of simultaneously occurred events by index CEventBaseObj *event=engine.GetTradeEventByIndex(i); if(event==NULL) continue; long lparam=i; double dparam=event.DParam(); string sparam=event.SParam(); OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } //--- If there is an account event if(engine.IsAccountsEvent()) { //--- Get the list of all account events occurred simultaneously CArrayObj* list=engine.GetListAccountEvents(); if(list!=NULL) { //--- Get the next event in a loop int total=list.Total(); for(int i=0;i<total;i++) { //--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) continue; //--- Send an event to the event handler long lparam=event.LParam(); double dparam=event.DParam(); string sparam=event.SParam(); OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } //--- If there is a symbol collection event if(engine.IsSymbolsEvent()) { //--- Get the list of all symbol events occurred simultaneously CArrayObj* list=engine.GetListSymbolsEvents(); if(list!=NULL) { //--- Get the next event in a loop int total=list.Total(); for(int i=0;i<total;i++) { //--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) continue; //--- Send an event to the event handler long lparam=event.LParam(); double dparam=event.DParam(); string sparam=event.SParam(); OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } //--- If there is a timeseries collection event if(engine.IsSeriesEvent()) { //--- Get the list of all timeseries events occurred simultaneously CArrayObj* list=engine.GetListSeriesEvents(); if(list!=NULL) { //--- Get the next event in a loop int total=list.Total(); for(int i=0;i<total;i++) { //--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) continue; //--- Send an event to the event handler long lparam=event.LParam(); double dparam=event.DParam(); string sparam=event.SParam(); OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } } //+------------------------------------------------------------------+
取引パネルのボタンを操作するためにEA関数を再配置する必要はありません。ただし、指標のボタンを使用できるように小規模の変更をいくつか加えてみましょう(2つのボタンを実装する必要があります)。
//+------------------------------------------------------------------+ //| Return the button status | //+------------------------------------------------------------------+ bool ButtonState(const string name) { return (bool)ObjectGetInteger(0,name,OBJPROP_STATE); } //+------------------------------------------------------------------+ //| Set the button status | //+------------------------------------------------------------------+ void ButtonState(const string name,const bool state) { ObjectSetInteger(0,name,OBJPROP_STATE,state); //--- Button 1 if(name=="BUTT_1") { if(state) ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'220,255,240'); else ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'240,240,240'); } //--- Button 2 if(name=="BUTT_2") { if(state) ObjectSetInteger(0,name,OBJPROP_BGCOLOR,C'255,220,90'); 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)); //--- If the button is pressed if(ButtonState(button_name)) { //--- If button 1 is pressed if(button=="BUTT_1") { } //--- If button 2 is pressed else if(button=="BUTT_2") { } //--- Wait for 1/10 of a second engine.Pause(100); //--- "Unpress" the button (if this is neither a trailing button, nor the buttons enabling pending requests) ButtonState(button_name,false); //--- re-draw the chart ChartRedraw(); } //--- Not pressed else { //--- button 1 if(button=="BUTT_1") { ButtonState(button_name,false); } //--- button 2 if(button=="BUTT_2") { ButtonState(button_name,false); } //--- re-draw the chart ChartRedraw(); } } //+------------------------------------------------------------------+
ご覧のとおり、ほとんどのEA関数は調整の必要なく指標で使用できます。これは、EAおよび指標からライブラリを操作するために必要なすべての関数をライブラリインクルードファイルに移動する必要があることを示唆しています。しかし、これは後で行われます。現在、指標の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: | //+------------------------------------------------------------------+ //--- Arrange resource-saving indicator calculations //--- Set OnCalculate arrays as timeseries ArraySetAsSeries(open,true); ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArraySetAsSeries(close,true); ArraySetAsSeries(tick_volume,true); ArraySetAsSeries(volume,true); ArraySetAsSeries(spread,true); //--- Setting buffer arrays as timeseries ArraySetAsSeries(Buffer1,true); ArraySetAsSeries(Buffer2,true); //--- Check for the minimum number of bars for calculation if(rates_total<2 || Point()==0) return 0; //--- Check and calculate the number of calculated bars int limit=rates_total-prev_calculated; if(limit>1) { limit=rates_total-1; ArrayInitialize(Buffer1,EMPTY_VALUE); ArrayInitialize(Buffer2,EMPTY_VALUE); } //--- Prepare data for(int i=limit; i>=0 && !IsStopped(); i--) { // the code for preparing indicator calculation buffers } //--- Calculate the indicator for(int i=limit; i>=0 && !IsStopped(); i--) { Buffer1[i]=high[i]; Buffer2[i]=low[i]; } //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+
ご覧のとおり、ライブラリ操作に関連するすべてのものは、OnCalculate()ハンドラの小さなコードブロックに収まります。実際、EAの違いは、CopyData()関数を使用してOnCalculate()から現在の配列データの価格構造を入力することですが、その他はすべてEAでの作業とまったく同じです。指標がが銘柄チャートで起動された場合はライブラリはタイマーで機能し、指標がテスターで起動された場合はOnCalculate()でティックによって起動されます。
OnCalculate()計算部分の指標バッファーにhigh []およびlow []配列データを入力します。
完全な指標コードは、以下に添付されているファイルで確認できます。
指標をコンパイルし、長期間使用していなかった銘柄チャートで起動し(事前の設定で現在の銘柄を使用するように設定している間)、指定した時間枠リストを使用することを選択します。未使用の長い銘柄で指標を起動すると、欠落しているデータをダウンロードして、ジャーナルとチャートでそのことを通知します。
ここでは、次の空の時系列が同期され、新しいティックごとに作成されていることがわかります。以下のエントリが操作ログに表示されています。
Account 8550475: Artyom Trishkin (MetaQuotes Software Corp.) 10425.23 USD, 1:100, Hedge, MetaTrader 5 demo --- Initializing "DoEasy" library --- Working with the current symbol only: "USDCAD" Working with the specified timeframe list: "M1" "M5" "M15" "M30" "H1" "H4" "D1" "W1" "MN1" USDCAD symbol timeseries: - Timeseries "USDCAD" M1: Requested: 1000, Actual: 0, Created: 0, On the server: 0 - Timeseries "USDCAD" M5: Requested: 1000, Actual: 0, Created: 0, On the server: 0 - Timeseries "USDCAD" M15: Requested: 1000, Actual: 0, Created: 0, On the server: 0 - Timeseries "USDCAD" M30: Requested: 1000, Actual: 0, Created: 0, On the server: 0 - Timeseries "USDCAD" H1: Requested: 1000, Actual: 0, Created: 0, On the server: 0 - Timeseries "USDCAD" H4: Requested: 1000, Actual: 0, Created: 0, On the server: 0 - Timeseries "USDCAD" D1: Requested: 1000, Actual: 0, Created: 0, On the server: 0 - Timeseries "USDCAD" W1: Requested: 1000, Actual: 0, Created: 0, On the server: 0 - Timeseries "USDCAD" MN1: Requested: 1000, Actual: 0, Created: 0, On the server: 0 Library initialization time: 00:00:01.406 "USDCAD" M1 timeseries created successfully: - Timeseries "USDCAD" M1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5001 "USDCAD" M5 timeseries created successfully: - Timeseries "USDCAD" M5: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5741 "USDCAD" M15 timeseries created successfully: - Timeseries "USDCAD" M15: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5247 "USDCAD" M30 timeseries created successfully: - Timeseries "USDCAD" M30: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5123 "USDCAD" H1 timeseries created successfully: - Timeseries "USDCAD" H1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 6257 "USDCAD" H4 timeseries created successfully: - Timeseries "USDCAD" H4: Requested: 1000, Actual: 1000, Created: 1000, On the server: 6232 "USDCAD" D1 timeseries created successfully: - Timeseries "USDCAD" D1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 5003 "USDCAD" W1 timeseries created successfully: - Timeseries "USDCAD" W1: Requested: 1000, Actual: 1000, Created: 1000, On the server: 1403 "USDCAD" MN1 timeseries created successfully: - Timeseries "USDCAD" MN1: Requested: 1000, Actual: 323, Created: 323, On the server: 323 New bar on USDCAD M1: 2020.03.19 12:18 New bar on USDCAD M1: 2020.03.19 12:19 New bar on USDCAD M1: 2020.03.19 12:20 New bar on USDCAD M5: 2020.03.19 12:20
ここで、リクエストされたすべての時系列がライブラリの初期化時に作成されたことがわかります。ただし、データがないため、データが入力されていません。要求されたデータへの最初のアクセス中に、端末によるデータのダウンロードが開始されました。後続の各ティックの到着時に、別の空の時系列オブジェクトを受け取り、そのデータをサーバーと同期して、時系列オブジェクトに要求された数量の棒データを入力しました。MN1で実際に使用できるのは323バーだけです。それらのすべてが時系列リストに追加されました。
次に、同じ設定でテスターのビジュアルモードで指標を起動します。
テスターは、使用されているすべての時間枠に必要なすべての履歴をロードします。ライブラリは、現在のものを除くすべての時系列を作成することを通知します。現在の銘柄と期間の時系列は、OnCalculate()の最初のエントリで正常に再作成されます。テスターの一時停止を解除すると、使用された時系列の「新しいバー」イベントがテスターでどのようにトリガーされるかを確認できます。
すべてが期待どおりに機能します。
次の段階
次回の記事では、指標の時系列を使用して作業を続け、チャートに情報を表示するために作成された時系列を使用してテストします。
現在のバージョンのライブラリのすべてのファイルは、テスト用EAファイルと一緒に以下に添付されているので、テストするにはダウンロードしてください。
質問や提案はコメント欄にお願いします。
シリーズのこれまでの記事:
DoEasyライブラリの時系列(第35部): バーオブジェクトと銘柄の時系列リスト
DoEasyライブラリの時系列(第36部): すべての使用銘柄期間の時系列オブジェクト
DoEasyライブラリの時系列(第37部): すべての使用銘柄期間の時系列オブジェクトDoEasyライブラリの時系列(第38部): 時系列コレクション-リアルタイムの更新とプログラムからのデータへのアクセス
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/7724
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索