
「新規バー」イベントハンドラ
はじめに
インディケータやエキスパートの著者は常に実行時間に関してコンパクトなコードを書くことに関心を寄せてきました。別の角度からこの問題にアプローチすることが可能です。本稿のこの巾広いトピックは、すでに解決されているかに見える問題を取り上げます。新規バーのチェックです。計算ループを限定するというのはひじょうによく使われる方法です。というのもすべての計算やトレーディング処理はチャート上で新規バーが生成される間に実行されるからです。よってお話することは
- 新規バーを検出する方法
- 既存の新規バー検出アルゴリズムの欠点
- 新規バー検索の汎用メソッド作成
- このメソッドの適用法
- このイベントのNewBarイヴェントとハンドラ - OnNewBar().
新規バー検出方法
新規バーの検出方法について受け入れ可能な解決法がいくつかあります。たとえば、それらはExpert Advisorsでの制約と検証、インディケータの経済的計算原則といった記事やここでご覧いただけます。ところで、これら資料で学習されることをお薦めします。そうすると私のお話することが理解しやすくなるでしょう。
これら資料は現在終了していないバーの開始時刻追跡の原理を取り入れています。これは簡単で信頼できる方法です。他にも新規バーの検出手法はあります。
たとえば、カスタムインディケータにおいて、この目的のため、OnCalculate()関数の入力パラメータを2つ使うことができます。それは、rates_total およびprev_calculatedです。この手法の限界は、基本的に現在チャートとインディケータだけで新規バーの検出だけに使用することができる、という事実です。別の期間やシンボルについて新規バーを見つけたいと思ったら、それ以上の技術が必要です。
または、たとえば、Tick Volume = 1、あるいはすべてのバー価格が等しくOpen = High = Low = Closeのとき、最初のティックで新規バーをとらえようとすることができます。これらの手法は検証にたいしてはよく使用されますが、実際のトレーディングではしばしば誤作動します。これは、最初と二度目のティックの間の時間が生成されたバーをとらえるのに十分ではないことがあるからです。これは特に強いマーケットの動き、またはインターネットの質が悪いときに顕著です。
TimeCurrent() 関数を基にした新規バー検出方法があります。ところで、現在使用中のチャートについて新規バーを検出する必要があるなら、それはよい方法です。それは本稿の最後で使います。
ご近所の方に訊くこともできますよ。「新規バー、ある?」という感じで。訊かれた方はなんと答えるのでしょうか?現在終了していないバーのオープン時刻追跡の原理を選ぶのはやめましょう。シンプルさと信頼性はほんとうに実証済みですから。
開始ポイント
新規バー検出に関しては前述の資料もそう悪くなさそうです。が。。
この「が。。」が何か理解するために、スタートポイント(またはプロトタイプ)として新規バーを検出するのに『Expert Advisorsの制約と検証』稿からシンプルでうまく動作する関数を抜粋します。それがこちらです。
//+------------------------------------------------------------------+ //| Returns true if a new bar has appeared for a symbol/period pair | //+------------------------------------------------------------------+ bool isNewBar() { //--- memorize the time of opening of the last bar in the static variable static datetime last_time=0; //--- current time datetime lastbar_time=SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE); //--- if it is the first call of the function if(last_time==0) { //--- set the time and exit last_time=lastbar_time; return(false); } //--- if the time differs if(last_time!=lastbar_time) { //--- memorize the time and return true last_time=lastbar_time; return(true); } //--- if we passed to this line, then the bar is not new; return false return(false); }
このプロトタイプ関数は実際動作していますし、生きる権利を十分持っています。が。。
プロトタイプ関数の分析
私は自分の(もちろん)素晴らしい最高中の最高のExpert Advisorのソースコードにこの関数をコピーしました。動作しませんでした。で、調べました。以下にこの関数についての考えを記します。
関数ヘッダ。 すべてを関数のヘッダから始めます。
bool isNewBar()
関数ヘッダは気に入りました。たいへんシンプルで、直感的、パラメータに渡される必要はありません。 将来この形式で使うのはよいかもしれません。
呼び出し回数の制限。 ヘッダに続いて、静的変数を初期化する最初の記述です。
//--- memorize the time of opening of the last bar in the static variable static datetime last_time=0;
すべてたいへんうまくいっているようです。が。。
問題は、静的 変数を採り入れることです。ヘルプトピックによると
静的変数はプログラム実行の瞬間から存在し、指定された OnInit() 関数が呼ばれるとき一度だけ初期化されます。初期値が指定されていなけば、静的ストレージクラスの変数はゼロの初期値を取っています。
ローカル変数静的キーワードで宣言され、関数のライフタイム中その値を保持します。そのようなローカル変数は、次の関数の呼び出しに前回の呼び出し中に持っていた値を含んでいます。
このプロトタイプ関数をある場所から呼ぶと、それから必要なものを入手します。しかしたとえば、この関数を使いたい場合、再び同じ計算ループの別の場所でそれは常にfalseを返します。これはバーがないことを意味します。そしてこれは常に真実であるとは限りません。静的変数はこの場合、プロトタイプ関数呼び出数に人工的に制限を設けます。
ユニバーサリティの疑問。 プロトタイプ関数内で続く記述は以下のようなものです。
//--- current time datetime lastbar_time=SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);
直近の未完了バーの開始時刻を得るのに、 SERIES_LASTBAR_DATEモディファイアを伴う SeriesInfoInteger() 関数が使われるのは理論にかなっています。
われわれのプロトタイプ関数 isNewBar() は、もともとシンプルな発想のもので、デフォルトではそれはトレーディングインスツルメントと現在チャート期間を使用します。これは現在チャート上でのみ新規バーを検出したいなら役に立ちます。しかし、現在チャートだけでない期間およびインスツルメントを使うならどうすればよいでしょうか。それ以上に複雑なチャートをもっていたらどうなるのでしょうか?たとえば、Renkoまたは Kagiをプロットしようと思ったらどうなるのでしょうか。
能力の欠如がかなり制限を与えます。後で、それを直す方法を取り上げます。
エラー処理。 SeriesInfoInteger() 関数をみていきます。チャートがまだできていないとき実行すると、それは何を返すと思いますか?たとえば、こんな状況がおこるのではないでしょうか。みなさんの Expert Advisorまたはインディケータをチャートにアタッチして期間かシンボルを変えようと思った、または端末を再起動するとき。 時系列の更新中は何が起こるでしょうか?たまたまヘルプトピックにそのような警告があります。
データ使用可能性
HCCフォーマットや 準備済みのHC フォーマットにあるデータさえ、チャートに表示したりMQL5プログラムで絶対的に使えるということを必ずしも意味するものではありません。
MQL5プログラムから価格データやインディケータ値にアクセスするときは、ある時間に利用可能であることや、ある時間から使用を開始するすることを保証するものではないことを覚えておく必要があります。これは、システムリソースを保存するためには、mql5プログラムに必要なデータのフルコピーがMetaTrader 5に保管されず、ただ端末データベースへの直接アクセスだけが可能であるという事実によります。
すべての時間枠についての価格履歴はHCCフォーマットの共通データから構築され、すべてのサーバーからのデータ更新は 前時間枠のデータ更新およびインディケータの再計算につながるのです。このことにより、同じデータが一瞬前にはアクセス可能であったとしても、そのデータへのアクセスは拒否される可能性があります。
では、この関数は何を返すのでしょうか?この不確定要素を避けるため、1本前の未完了バーの開始時刻のクエリーエラーをとらえることを始める必要があります。
初期化の可能性 では、先に進みます。以下に挙げるプロトタイプ関数の記述を考察します。
//--- if it is the first call of the function if(last_time==0) { //--- set the time and exit last_time=lastbar_time; return(false); }
ここではすべてが間違いなくうまくいっています。が、ビミョウです。「ヘルプ」からの上述内容はお解りですね。「静的変数はプログラム実行の瞬間から存在し、指定されたOnInit() 関数が呼ばれるとき一度だけ初期化される。」というものでした。では、もし last_time 変数を初期化するのにもっと時間が必要だとしたら?もっと正確に言うと、最初の呼び出し状態を人工的に作りたければどうしますか?もしくはその他の状況を作りたければ?答えがわかっていれば質問は簡単です。それ以上は後にお話しします。
バー数 プロトタイプ関数の次にくるのは以下のコードです。
//--- if the time differs if(last_time!=lastbar_time) { //--- memorize the time and return true last_time=lastbar_time; return(true); }
おわかりですね。私のようなプログラマは if オペレータがクライアント端末とストラテジーテスタを「驚かす」べく、それをすることができるのです。実際は理論的に過去は常に現在に劣る、です。それは、 last_time < lastbar_timeです。たまたまのプログラムエラーにより、タイムマシンを手に入れた、またはもっと正確には、逆が起こったのです。すなわち、lastbar_time < last_time となったのです。驚きましたね!一般的には、そのような時間のパラドックスは簡単に見つけられますし、エラーメッセージにもすぐに表示されます。
しかし、不幸中の幸い。私の「タイムマシン」を見ていて気付いたのです。isNewBar() の呼び出し中に現れるバーは一つだけではないのです。チャート期間が短いほど、関数呼び出しの間に複数バーが発生する可能性は高まるのです。これには多くの理由があります。長い計算時間から始まり、サーバーと一時的に接続がきれて終了する、というもの。新規バーのシグナルだけを受け取るのではなく、複数バーのシグナルも受け取る機会、というのは確かに使えます。
われわれのプロトタイプ関数は以下のように終了します。
//--- if we passed to this line, then the bar is not new; return false return(false);
そうです。この行まで飛ばしたら、バーは新規ではないのです。
新しいisNewBar()関数の作成
ここからおもしろいことを始めます。見つかった弱点を克服していくのです。私はすこし謙虚すぎました。この項を『新しいisNewBar()関数の作成』と呼ぶなんて。実はもっとかっこいいことをやるのです。
まずは、数々の関数呼び出しから制約を取り除くことから始めます。
まず心に浮かんだことは、『インディケータの経済的計算原理』稿、またはここ isNewBar に出ているNewBar() と同じ名前の関数を使うことができる、ということです。それは、複数のlast_time値を関数本体に保存する配列をインクルードするため、別の場所からの isNewBar() 関数呼び出しのカウンタを置く、などです。もちろんこれらはすべて動作しているバージョンであり、実装できるものです。でも、想像してみてください。複数通貨対応Expert Advisorを12種の通貨ペアで動作するようにプログラムしているとしたら。考慮すべき、また混乱しないようにしなければならない細かいことがたくさんありませんか?
ではどうすればよいのでしょうか?答えはここにあります!
オブジェクト指向プログラミングが美しいのは、オブジェクトまたはクラスのインスタンスが同じクラスの他のインスタンスから独立して「それぞれが生きている」からです。CisNewBarクラスの作成を始めます。そしてExpert Advisorやインディケータのどこでもなんどでもこのクラスのインスタンスを作成することができるようになるのです。そしておのおののインスタンスに「各自の命を生きて」もらいましょう。
初めにやることはそれです。
class CisNewBar { protected: datetime m_lastbar_time; // Time of opening last bar public: void CisNewBar(); // CisNewBar constructor //--- Methods of detecting new bars: bool isNewBar(); // First type of request for new bar }; bool CisNewBar::isNewBar() { //--- here is the definition of static variable //--- here will be the rest of method's code ... //--- if we've reached this line, then the bar is not new; return false return(false); }
isNewBar() 関数だったものは、いまではメソッドとなりました。今、静的変数last_time は存在しません。その代り、保護クラス変m_lastbar_timeがあります。静的変数をisNewBar() メソッドに残してきてしまったら、すべての努力は水の泡です。なぜなら、isNewBar() 関数でまた前と同じ問題を抱えることになるからです。これが静的変数の性質です。
今、最後のバーがクラスの保護変数m_lastbar_timeに保存され、それぞれのクラスインスタンスにはこの変数についてのメモリが割り当てられます。これで呼び出し回数に関する制約は取り除くことができました。それはプロトタイプ関数にあったものです。今や、MQLプログラムの異なる場所でなんどでも好きなだけ各場所に対してクラスインスタンスを作成しながらisNewBar()メソッドを呼ぶことができます。
これはわれわれの成功です。では、ユニバーサリティに関して作業をします。われわれの新規クラスに何かを付け加える前に、ひとつすばらしい考えにみなさんを導きたいと思います。
何か考えてみましょう。われわれがしたいことは? 新規バーについてシグナルを取得したいのです。そのやり方は? そこで、です。最終ティック(または最終モーメント)の現在未完成バーの開始時刻が、前回ティック(または前回モーメント)にある現在未完成バーの開始時刻よりも大きいと、新規バーが形成されます。ややこしい文ですが、これは正しいのです。ポイントは、時刻の比較が必要だということです。ということで、現在未完了バーの開始時刻newbar_time を isNewBar() メソッドに私のが合理的であろう、と判断しました。メソッドのヘッダは以下です。
bool isNewBar(datetime newbar_time)
まだ、どこでnewbar_time を取得するのか訊かないでください。たぶんもうお解りのことと思います。これはのちほどということで。
ところで、時刻をisNewBar() メソッドに渡すことで、新規バーを検出するためのひじょうに柔軟性に富んだツールを手に入れます。あらゆるタイプのトレーディングツールを得た今、すべての標準チャート期間を取り扱うことができるのです。それでシンボル名や期間サイズに依存することはなくなりました。
規格外チャートも使うことができます。たとえば、tick candlesticks、Renko 、 Kagi チャートをプロットしていると、そこのバー開始時刻は実際決して標準チャート期間の時刻と一致することはありません。そこでなくてはならないのが、われわれの関数です。
これで、多用途性についてはもういいですね。CisNewBarクラスについてわれわれの考え方で補完しましょう。
class CisNewBar { protected: datetime m_lastbar_time; // Time of opening last bar uint m_retcode; // Result code of detecting new bar int m_new_bars; // Number of new bars string m_comment; // Comment of execution public: void CisNewBar(); // CisNewBar constructor //--- Methods of detecting new bars: bool isNewBar(datetime new_Time); // First type of request for new bar }; //+------------------------------------------------------------------+ //| First type of request for new bar | //| INPUT: newbar_time - time of opening (hypothetically) new bar | //| OUTPUT: true - if new bar(s) has(ve) appeared | //| false - if there is no new bar or in case of error | //| REMARK: no. | //+------------------------------------------------------------------+ bool CisNewBar::isNewBar(datetime newbar_time) { //--- Initialization of protected variables m_new_bars = 0; // Number of new bars m_retcode = 0; // Result code of detecting new bar: 0 - no error m_comment =__FUNCTION__+" Successful check for new bar"; //--- //--- Just to be sure, check: is the time of (hypothetically) new bar m_newbar_time less than time of last bar m_lastbar_time? if(m_lastbar_time>newbar_time) { // If new bar is older than last bar, print error message m_comment=__FUNCTION__+" Synchronization error: time of previous bar "+TimeToString(m_lastbar_time)+ ", time of new bar request "+TimeToString(newbar_time); m_retcode=-1; // Result code of detecting new bar: return -1 - synchronization error return(false); } //--- //--- if it's the first call if(m_lastbar_time==0) { m_lastbar_time=newbar_time; //--- set time of last bar and exit m_comment =__FUNCTION__+" Initialization of lastbar_time = "+TimeToString(m_lastbar_time); return(false); } //--- //--- Check for new bar: if(m_lastbar_time<newbar_time) { m_new_bars=1; // Number of new bars m_lastbar_time=newbar_time; // remember time of last bar return(true); } //--- //--- if we've reached this line, then the bar is not new; return false return(false); }
われわれのクラスのソースコードを見ると、おそらくランタイムエラーを考慮したこと、また、新規バー数を格納する変数も導入したことにもにお気づきでしょう。
すべてうまくいっていますね。が、この汎用メソッドisNewBar(datetime newbar_time)には一つ大きな欠点があるのです。それは、つねにエキスパートやインディケータのソースコード内で(仮に)newbar_timeの時刻計算を気にかけないといけないということです。
さいわいにも、時として、このクラスの新規追加メソッドとしてこの関数を信頼することでみなさんの生活をシンプルにすることができます。われわれのプロトタイプ関数で標準期間とシンボルについてSERIES_LASTBAR_DATEモディファイアを伴ってSeriesInfoInteger()関数の第二バージョンを使いこれを行うことができます。 そ例外では遺伝的メソッドを使用します。以下、取得したものです。
//+------------------------------------------------------------------+ //| Second type of request for new bar | //| INPUT: no. | //| OUTPUT: m_new_bars - Number of new bars | //| REMARK: no. | //+------------------------------------------------------------------+ int CisNewBar::isNewBar() { datetime newbar_time; datetime lastbar_time=m_lastbar_time; //--- Request time of opening last bar: ResetLastError(); // Set value of predefined variable _LastError as 0 if(!SeriesInfoInteger(m_symbol,m_period,SERIES_LASTBAR_DATE,newbar_time)) { // If request has failed, print error message: m_retcode=GetLastError(); // Result code of detecting new bar: write value of variable _LastError m_comment=__FUNCTION__+" Error when getting time of last bar opening: "+IntegerToString(m_retcode); return(0); } //--- //---Next use first type of request for new bar, to complete analysis: if(!isNewBar(newbar_time)) return(0); //---Correct number of new bars: m_new_bars=Bars(m_symbol,m_period,lastbar_time,newbar_time)-1; //--- If we've reached this line - then there is(are) new bar(s), return their number: return(m_new_bars); }
ここまでで手にしたものは何でしょうか?標準期間として、1本前の未完了バーの開始時刻を決める必要はありません。プロトタイプ関数に対してシンプルな呼び出しとそれの欠点なしの方法をしてきました。その上別のメリットも得ました。エラーコード、ランタイムコメント、新規バー数です。
まだ何か必要でしょうか?答えは、はい、です。最後に必要なもの、初期化です。そのために、クラスコンストラクタといくつかの設定メソッドを使用します。以下がわれわれのクラスコンストラクタです。:
//+------------------------------------------------------------------+ //| CisNewBar constructor. | //| INPUT: no. | //| OUTPUT: no. | //| REMARK: no. | //+------------------------------------------------------------------+ void CisNewBar::CisNewBar() { m_retcode=0; // Result code of detecting new bar m_lastbar_time=0; // Time of opening last bar m_new_bars=0; // Number of new bars m_comment=""; // Comment of execution m_symbol=Symbol(); // Symbol name, by default - symbol of current chart m_period=Period(); // Chart period, by default - period of current chart }
そして以下が設定メソッドです。:
//--- Methods of initializing protected data: void SetLastBarTime(datetime lastbar_time){m_lastbar_time=lastbar_time; } void SetSymbol(string symbol) {m_symbol=(symbol==NULL || symbol=="")?Symbol():symbol; } void SetPeriod(ENUM_TIMEFRAMES period) {m_period=(period==PERIOD_CURRENT)?Period():period; }
クラスコンストラクタのお陰で、シンボルと現在チャートの初期化を気にする必要はありません。プロトタイプ関数同様、それらは初期設定にあるのです。が、別のシンボルやチャート期間を使いたければ、独自に作成した設定メソッドで可能です。また、SetLastBarTime(datetime lastbar_time) を用いて『初期コール』の状況を再度作成することができます。
最後に、いくつかGet-メソッドを作成して、Expert Advisorのクラスとインディケータからデータを取得します。
//--- Methods of access to protected data: uint GetRetCode() const {return(m_retcode); } // Result code of detecting new bar datetime GetLastBarTime() const {return(m_lastbar_time);} // Time of opening last bar int GetNewBars() const {return(m_new_bars); } // Number of new bars string GetComment() const {return(m_comment); } // Comment of execution string GetSymbol() const {return(m_symbol); } // Symbol name ENUM_TIMEFRAMES GetPeriod() const {return(m_period); } // Chart period
これでmql5プログラム内に必要な情報は出そろいました。ここで、CisNewBarクラスの作成はおしまいです。
このクラスのソースコード全文は、添付のLib CisNewBar.mqhファイルにあります。
CisNewBarクラス使用例
これまで作成してきたものの細かな点をすべて習得するため、クラス使用例を見ていくことをご提案します。きっとメリットだけでなく、デメリットもあることでしょう。
例1 まず、"Limitations and Verifications in Expert Advisors" 稿にあるisNewBar() 関数に対して、そっくりなExpert Advisorを作成します。:
//+------------------------------------------------------------------+ //| Example1NewBar.mq5 | //| Copyright 2010, Lizar | //| Lizar-2010@mail.ru | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, Lizar" #property link "Lizar-2010@mail.ru" #property version "1.00" #include <Lib CisNewBar.mqh> CisNewBar current_chart; // instance of the CisNewBar class: current chart //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- if(current_chart.isNewBar()>0) { PrintFormat("New bar: %s",TimeToString(TimeCurrent(),TIME_SECONDS)); } }
同じぺアと期間で両方の Expert Advisorsを実行します。できたものを確認します。
まず、どちらのExpert Advisorsも新規バーについて同期的に報告しています。そして、静かになり、4分後にはもう新規バーの存在を知らせています。(この時刻は1とマークされています。)それはOKです。数分間インターネットから離れて何が起こるか見てみたのです。数個のバーが作成されたのにもかかわらず、この情報は入手していません。新しいExpert Advisorでは、この欠点は修正することができます。なぜなら、 我々が作成した isNewBar()メソッドにより可能となっているからです。
次にチャート期間を M2と変更しました。Expert Advisorsの反応は異なるものでした。CheckLastBarが2分ごとに新規バーの報告を始め、 Example1NewBarが、期間変更はなかったかのように毎分新規バーのことを告げます。(2としてマークしました)
現在のチャートインスタンスはExpert Advisorがチャートにアタッチされるときにクラスコンストラクタによって初期化されたのです。すでにチャートにアタッチされているExpert Advisorの期間を変更するとクラスコンストラクタは起動せず、Expert Advisor はM1期間で動作を続けます。これは、われわれのクラスインスタンスは自立していて、環境変化に影響されないことを物語っています。それはメリットでありデメリットでもあります。すべてはタスクによります。
このExpert AdvisorをCheckLastBarのように使用するには、保護クラス変数である m_symbolおよびm_periodを OnInit()関数内で初期化する必要があります。 ではそれをやってみます。
例2 このExpert Advisorに追加を行い、そのパフォーマンスを CheckLastBarと比較します。Expert Advisorのソースコードは Example2NewBar.mq5ファイルとして添付されています。Expert Advisorsを同じペアと期間でチャート上で実行します。前回と同じようにそれらに同じ障害を作成します。できたものを確認します。
前回のようにExpert Advisorsは最初に新規バーについて同期的報告をします。それから、数分間インターネット接続を切りました。そして接続をしました。新しいExpert Advisorは新規バーの報告をしただけではなく、新規バー数についても報告をしました。(1としてマークしました。) インディケータやエキスパートの多くにとっては、この数値は未計算のバーの数量を意味します。よって、コストを意識した再計算アルゴリズムへのうまい土台ができたわけです。
次にチャート期間を M2と変更しました。例1とは違い、Expert Advisorは同期的に動作します。(2とマークしました)OnInit()関数内の保護クラス変数the m_symbolおよび m_periodが役に立ったのです!シンボルを変えると(3としてマーク)、Expert Advisorsはまた同じように動作します。
例 3 われわれのCisNewBarクラスで、エラー追跡の可能性も盛り込みました。Expert Advisor はエラー追跡の必要などないと設計されるかもしれません。可能性だけを考えないでください。エラーが起こりうるという状況を人工的に作ってみて、やってみるのです。このため、Expert Advisorのソースコードにほんの少し補足します。 ( Example3NewBar.mq5 ファイル)
それから?いつものようにExample3NewBarを分チャート上で実行します。それから、Expert Advisorが依頼する前、端末は時系列を構築するための時刻を持たないという状況を期待してチャートのインスツルメントの変更を始めます。通常は、クライアント端末を悩ませ、何が起こるか見るのですが。。。
何度か試してやっとわれわれのExpert Advisorはエラーを出しました。
これで自信をもってランタイムエラーが出せるといえます。それをどう処理するか、はお好みしだいです。ただ、このエラーは何度か追跡したことにご注意ください。ダウンロードが完了し、チャートが形作られたら、Expert Advisor はわれわれが1つだけバーを見落としたと示しました。
ところでExpert Advisorのソースコードを見た方々は、isNewBar() メソッドがゼロ以下の値を返すときだけエラー確認をするのが合理的だとお解りのことと思います。
注意:この実験中に、チャート期間を変更する際、チャート期間を小さいものから大きなものへ変更するとき、同期エラーが発生します。これは、バー開始時刻(たとえば)H1がM1よりも59ケースで早いためです。このエラーを避けるため、チャート期間を切り替えるときは、SetLastBarTime (datetime lastbar_time) メソッドを用いて OnInit()関数でm_lastbar_time変数を適切に初期化する必要があります。
例 4 この例ではExpert Advisorのタスクを複雑にしてみます。3種類の通貨ペアを取ります。:EURUSDをM1で、GBPUSD をM1で、USDJPY を M2で。最初のペアを伴うチャートが現在使用のものです。そしてそこで新規バーを注意して見ます。二番目のペアでは、Expert Advisorを起動してから作成されたバー数を計算します。最初のペアが新規バーの存在についてのシグナルを送るまで数えます。そして、三番目のペアでは、常に(EURUSDに関してバーが出現するとき)保護クラス変数m_lastbar_time protected の初期化を行います。Expert Advisorのソースコードは Example4NewBar.mq5ファイルとして添付されています。
この例を作成することでわれわれのクラスCisNewBar が複数通貨対応で動作することを確認したいのです。では、始めます。。結果を見ましょう。
結果から疑問が浮かびます。火に油を注ぎ、ストラテジーテスタでまさしくこの時間間隔を実行します。ストラテジーテスタ結果
ここから『10の間違い探し』ゲームです。デモアカウントでのExpert Advisorの奇妙な動作に加え、デモアカウントとストラテジーテスタには差があることが明らかです。しかもその差は相当なものです。正しい方法での比較はExpert Advisorの欠点を明らかにするだけでなく、それを除くことにもつながります。発生理由は分析しないと思いますが、どのように起こったか、Expert Advisorの何を修正すべきかは分析していきます。
例 5 例の中で、新規バー検出のもっとも汎用性に富んだ方法ははっきりと使っていませんでした。それは isNewBar(datetime newbar_time)です。このために『MQL5へのティックインディケータの作成』稿よりティックろうそく足を取り上げます。そしてバー開始時刻を保存するためのバッファを追加します(ファイル TickColorCandles v2.00.mq5参照)。新規チークろうそく足の時刻について伝えるだけのごく短いExpert Advisorを書きます。 (Example5NewBar.mq5ファイル参照)
#property copyright "Copyright 2010, Lizar" #property link "Lizar-2010@mail.ru" #property version "1.00" #include <Lib CisNewBar.mqh> CisNewBar newbar_ind; // instance of the CisNewBar class: detect new tick candlestick int HandleIndicator; // indicator handle //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int OnInit() { //--- Get indicator handle: HandleIndicator=iCustom(_Symbol,_Period,"TickColorCandles v2.00",16,0,""); if(HandleIndicator==INVALID_HANDLE) { Alert(" Error when creating indicator handle, error code: ",GetLastError()); Print(" Incorrect initialization of Expert Advisor. Trade is not allowed."); return(1); } //--- Attach indicator to chart: if(!ChartIndicatorAdd(ChartID(),1,HandleIndicator)) { Alert(" Error when attaching indicator to chart, error code: ",GetLastError()); return(1); } //--- If you passed until here, initialization was successful Print(" Successful initialization of Expert Advisor. Trade is allowed."); return(0); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- double iTime[1]; //--- Get time of opening last unfinished tick candlestick: if(CopyBuffer(HandleIndicator,5,0,1,iTime)<=0) { Print(" Failed to get time value of indicator. "+ "\nNext attempt to get indicator values will be made on the next tick.",GetLastError()); return; } //--- Detect the next tick candlestick: if(newbar_ind.isNewBar((datetime)iTime[0])) { PrintFormat("New bar. Opening time: %s Time of last tick: %s", TimeToString((datetime)iTime[0],TIME_SECONDS), TimeToString(TimeCurrent(),TIME_SECONDS)); } }
ろうそく足開始ティック時刻の取得方法は確かにお分かりですね。簡単ですよね?フォルダにインディケータと、Expert Advisorを入れ、コンパイルし、Expert Advisor実行しました。動作します。結果はこの通りです。
『新規バー』イベントハンドラ
本稿も終わりに近づいてきました。ここではもう一つみなさんと共有したい考えがあります。 Forum (ロシア語)に、標準的な 『新規バー』イベントハンドラがあればいいだろうな、というのがありました。おそらく開発者は一度はこの考えに至るのでしょうが、たぶんちがうでしょう。しかしMQL5の美しい点は、もっともすぐれた考えをエレガントにシンプルに導入することができることです。
『新規バー』イベントハンドラ (または NewBar) をほしいと思えば、作るのです!特に、このイベントを簡単にやってしまうことができます。われわれのクラスを使うのです。われわれのエキスパートがどう見えるかです。(新規バーイベントハンドラOnNewBar()を備えて)
#property copyright "Copyright 2010, Lizar" #property link "Lizar-2010@mail.ru" #property version "1.00" #include "OnNewBar.mqh" // Here is the secret of launching the "new bar" event handler //+------------------------------------------------------------------+ //| New bar event handler function | //+------------------------------------------------------------------+ void OnNewBar() { PrintFormat("New bar: %s",TimeToString(TimeCurrent(),TIME_SECONDS)); }
いいですね。われわれのExpert Advisorはきわめてシンプルです。このハンドラは新規バーについてのストリングを印刷します。これがするのは以上です。NewBarイベントを追跡する方法とハンドラの実行方法を理解するためにはOnNewBar.mqhファイルを参照ください。
#property copyright "Copyright 2010, Lizar" #property link "Lizar@mail.ru" #include <Lib CisNewBar.mqh> CisNewBar current_chart; // instance of the CisNewBar class: current chart //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- int period_seconds=PeriodSeconds(_Period); // Number of seconds in current chart period datetime new_time=TimeCurrent()/period_seconds*period_seconds; // Time of bar opening on current chart if(current_chart.isNewBar(new_time)) OnNewBar(); // When new bar appears - launch the NewBar event handler }
ご覧のように、ここにもなにも複雑なことはありません。ですが、すこしご注意いただきたいことがあります。
第一 ご存じのとおりバー開始時刻の計算にTimeCurrent()関数を使用し、われわれのクラスから新規バーイベントチェックの最初の方法を使います。ここにひとつよい追加があります。それは、そのような手法は、SERIES_LASTBAR_DATEモディファイアと共にSeriesInfoInteger()を使うとき、エラー処理を要求しない、という時事にあります。われわれにとって、それは重要なことです。というのも、われわれのOnNewBar() ハンドラはできる限り信頼できるからです。
第二 バー開始時刻の計算にTimeCurrent()関数を使用するのはもっとも速い方法です。エラーコントロールなしでも、SeriesInfoInteger()関数を同じ目的で使うとスピードは劣ります。
われわれのハンドラの結果です。
おわりに
資料提供としては、新規バー検出に関してよい分析ができました。新規バー検出に関する既存手法のメリットとデメリットをあぶりだしました。われわれが持っていたものを基にCisNewBarクラスを作成し、プログラミングに追加コストを加えることなく、ほとんどあらゆるタスクで『新規バー』イベントをとらえることができました。 同時に、以前の解決法から不都合な部分をかなり取り除きました。
これら例により、自分達が見つけ出した手法のメリット、デメリットも理解できました。正しい作業に関して、特別な注意が複数通貨モードに要求されます。確認されている不足については完全な分析と、その解決策を作成することが必要です。
『新規バー』イベントハンドラの作成は、単一通貨対応Expert Advisorsにのみ適しています。われわれはこの目的のため、最も信頼性の高い、速い方法を学ぶ必要があります。先に進んで、複数通貨対応新規バーイベントハンドラの作成を手掛けることができます。が、この話題は別の記事に譲ります。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/159





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索