メタトレーダー5における検証の原則

2 10月 2015, 14:40
MetaQuotes
0
4 231

ストラテジーテスターはなぜ必要か

自動トレーディングのアイディアは、トレーディングロボットが週7日、1日24時間ノンストップで動作することができるという点で魅力的です。ロボットは疲れたり、疑ったり、恐れたりすることなく、心理的問題とは完全に無縁でいられます。トレーデイングルールを明確に形式化し、アルゴリズムで実行するだけで準備は十分で、後はロボットが疲れることなく働いてくれます。しかしまず、次の2つの重要な条件が満たされているかを確認する必要があります:

  • エキスパートアドバイザがトレーディングシステムのルールにしたがってトレード運用を行っているかどうか;
  • EAで実行されるトレーディングストラテジーが、過去の履歴で収益を示してしているか。

これらの問いに答えるために、メタトレーダー5クライアントターミナルに含まれるストラテジーテスターに注目してみましょう。


ティック生成モジュールの選択

エキスパートアドバイザはMQL5で記述され、外部イベントに応答するたびに実行されるプログラムです。EAは、あらかじめ定義されたイベントに対応する関数(イベントハンドラ)を備えています。

EAの主なイベントはNewTickイベント(価格変更)であり、EAを検証するために、ティックシーケンスを生成する必要があります。メタトレーダー5クライアントターミナルのストラテジーテスターによって実行されるティック生成には、3つのモードがあります:

  • イベントティック
  • 1分足OHLC (分足バー付きOHLC価格)
  • 始値オンリー

ベーシックかつ最も詳細なのが「全ティック」モードです。他の2つは、このベーシックなモードを単純化したもので、「全ティック」モードと対比して記述します。違いを理解するため、これら3つのモードすべてを考えてみましょう。


「全ティック」

金融手段の相場履歴データが、分足バーパッケージの形でトレーディングサーバからメタトレーダー5クライアントターミナルに転送されます。 リクエストの発生と必要とされるタイムフレームの構築に関する詳細な情報は、MQL5リファレンスのデータアクセスを調整するというセクションから入手できます。

価格履歴の最小エレメントは分足バーで、そこから価格の4つの値について情報を得ることができます:

  • 始値-分足バーが開始した時の価格。
  • 高値-分足バーで達成された最大値。
  • 安値-分足バーで達成された最小値。
  • 終値-バーが終了した時の価格。

新しい分足バーは、分が切り替わった時点(秒数0)ではオープンされず、ティックが起こった時-少なくとも1つのポイントにおける価格変更の際、にオープンされます。この図では、2011.01.10 00:00にオープンした新しいトレード週の最初の分足バーを示しています。週末の間であっても、配信されるニュースに対応して為替レートは変動するため、チャートに見られるような金曜と月曜の価格ギャップは、よくあることです。

図1金曜と月曜の価格ギャップ

このバーからは、分足バーが2011年1月10日の00時00分にオープンしたことがわかりますが、秒については分かりません。00:00:12または00:00:36(日付が変わってから12秒あるいは36秒後)、もしくは、同じ分の間でそれ以外の時刻にオープンしたのかもしれません。しかし、新規分足バーの開始時刻におけるEURUSDの始値が1.28940であったことは分かります。

当該分足バーの終値に対応して、ティックを受け取ったのが何秒であったのか、についても分かりません。分かることは1つ-分足バーの最後の終値、のみです。この時、価格は1.28958でした。高値と安値が出現した時刻についても同様に不明ですが、最大価格と最小価格のレベルがそれぞれ、1.28958と1.28940であったことはわかります。

トレーディングストラテジーを検証するには、エキスパートアドバイザの動作をシミュレーションする対象となる、ティックのシーケンスが必要です。したがって、すべての分足バーについて、価格が明確である4つのコントロールポイントを把握します。1つのバーに4ティックしか含まれていない場合、それは検証を行うのに十分な情報ではありますが、通常はティック量は4より大きくなります。

したがって、始値、高値、安値、終値の間に起こったティックに対するコントロールポイントも、追加して生成する必要があります。「全ティック」というティック生成モードの原則は、メタトレーダー5ターミナルのストラテジーテスターにおけるティック生成のアルゴリズム、という記事に記されています。その中の図を1つ、以下に示します。

図2ティック生成アルゴリズム

「全ティック」モードで検証する際、EAのOnTick()関数はすべてのコントロールポイントで呼び出されます。各コントロールポイントが、生成されたシーケンスからのティックとなります。EAはシミュレートされたティックの時刻と価格を、オンラインで動作しているときと同じように受け取ります。

重要:「全ティック」検証モードは最も正確ですが、同時に、最も時間がかかります。ほとんどのトレーディングストラテジーを最初に検証する際は、通常は、他の2つの検証モードのいずれかを用いれば十分です。


「1分足」

「全ティック」モードは3つのモードの中で最も正確ですが、同時に最も遅いモードです。OnTick()ハンドラはすべてのティックで実行され、ティック量がたいへん大きい場合もあります。バー全体の価格変動のティックシーケンスが重要ではないストラテジーのために、より速く、より大まかにシミュレーションを行うモード-「1分足OHLC」モードがあります。

「1分足OHLC」モードでは、ティックシーケンスは分足バーのOHLC 価格ごとにのみ構築され、生成されるコントロールポイントがたいへん少なくなります-したがって、検証時間も短くなります。OnTick ()関数の起動はすべてのコントロールポイントで実行され、OHLC分足バーの価格ごとに構築されます。

始値、高値、安値、終値の間のティックが追加生成されないため、始値が決定された後の価格変動の決定方法が硬直であるように見えます。このため、検証している残高の上昇グラフを示す「検証の聖杯」を作成することができます。

そのような聖杯の例がコードベース-Grr-alに示されています。

図3OHLC価格の特徴を用いるGrr-alエキスパートアドバイザ

この図は、EA検証における大変魅力的なグラフを示しています。どのようにして得られたのでしょうか?分足バーで4つの価格を把握し、最初が始値、最後が終値であることが分かっています。その間に高値と安値があり、それらの出現するシーケンスは不明ですが、高値は始値と等しいかより大きい(また、安値は始値と等しいかより小さい)ことは分かっています。

つまり、始値を受け取った時刻を決定すれば十分で、それから次のティックを分析して高値または安値がその前に起こっていないことを確認すればよいのです。価格が始値より小さい場合は、そのティックは安値であってそこで買い注文を行い、その次のティックは高値に対応することになり、そこで買いをクローズして売りをオープンするわけです。次が最後のティックで終値となり、取引をクローズします。

この価格の後に始値より大きい価格を持つティックを受け取る場合、その取引のシーケンスは反転されています。分足バーをこの「チート」モードで処理し、次のバーを待ちます。

そのようなEAを過去の取引について検証する際はすべてがスムーズに行われますが、オンラインで起動すると、真実が反転し始めます-バランスラインは一定ですが、下向きとなります。このトリックを経験するには、単純に、EAを「全ティック」モードで実行する必要があります。

注意:EAの大まかな検証モード(「1分足OHLC」と「始値オンリー」)による検証結果が良好すぎる場合は、必ず「全ティック」モードで検証してください。


「始値オンリー」

このモードでは、ティックは検証用に選択されたタイムフレームのOHLC価格にもとづいて生成されます。エキスパートアドバイザのOnTick()関数は、バー開始時に始値においてのみ実行されます。この特徴によって(特により大きいタイムフレームで検証を行う場合に)、特定の価格と異なる価格においてストップレベルとペンディングが起こるかもしれません。その代わり、エキスパートアドバイザの評価テストを手早く実行することが可能です。

「始値オンリー」モードのティック生成の例外となるのが、期間W1とMN1です: これらのタイムフレームでは、ティックは週や月のOHLC価格ではなく、各日のOHLC価格で生成されます。

エキスパートアドバイザを、EURUSDのH1について「始値オンリー」モードで検証すると仮定します。この場合、ティック(コントロールポイント)の合計数は、4×検証期間内の1時間足バーの数、のみとなります。しかし、OnTick()ハンドラは1時間足バーの開始時のみ呼び出されます。正しい検証に必要なチェックは、(EAから「非表示」となっている)他のティックについて行われます。
  • マージン要求の計算;
  • ストップロスとテイクプロフィットレベルの発生;
  • ペンディングオーダーの発生;
  • 期限切れペンディングオーダーの削除。

オープンポジションやペンディングオーダーがない場合は、これらのチェックを非表示のティックで行う必要はなく、処理スピードが非常に向上するかもしれません。「始値オンリー」モードは、取引がバーのオープン時のみで、ペンディングオーダーや、ストップロス、テイクプロフィットオーダーを用いないストラテジーを検証するのに適しています。そうしたストラテジーのクラス用に、テストに必要な正確性が確保されています。

EAの例として、標準パッケージから移動平均エキスパートアドバイザを使ってみましょう。これはどのモードでも検証が可能です。このEAのロジックは、すべての意思決定がバーのオープン時に行われ、取引はペンディングオーダーを用いることなく即時に行われる、として構築されています。

EAの検証を、EURUSDのH1、期間2010.01.09から2010.31.12について実行し、グラフを比べてみます。この図は、3つのモードすべての検証レポートから、残高グラフを示しています。


図4標準パッケージに含まれるMoving Average.mq5 EAの検証グラフは、検証モードに依存していません(画像をクリックすると拡大されます)

見てわかるとおり、異なる検証モードのグラフは、標準パッケージに含まれる移動平均EAとまったく同一となっています。

「始値オンリー」モードにはいくつかの制約があります:

  • ランダム遅延実行モードは使用できません。
  • 検証されるエキスパートアドバイザでは、検証/最適化に用いられるタイムフレームより小さいもののデータにはアクセスできません。例えば、検証/最適化を期間H1について実行する場合は、H2, H3, H4などのデータにはアクセスできますが、M30, M20, M10などのデータにはアクセスできません。加えて、アクセスできるより大きいタイムフレームは、検証するタイムフレームの倍数である必要があります。例えば、M20の検証を実行する場合、M30のデータにはアクセスできませんが、H1のデータにはアクセスできます。このような制約は、検証/最適化中に生成されるバーのうち、より小さい、もしくは倍数ではないタイムフレームのデータを取得できないことと関係しています。
  • 他のタイムフレームのデータへのアクセス制限は、データがエキスパートアドバイザで用いられている他のシンボルについても適用されます。この場合、各シンボルへの制約は、検証/最適化中にアクセスされる最初のタイムフレームに依存します。EURUSD、H1の検証中に、エキスパートアドバイザがGBPUSD、M20のデータにアクセスすると仮定します。この場合、エキスパートアドバイザはEURUSDのH1、H2などと同様に、GBPUSDのM20、H1、H2なども使うことができます。

注意:「始値オンリー」モードは検証時間は最速ですが、すべてのトレーディングストラテジーに適しているわけではありません。トレーディングシステムの特徴に合わせて、望ましい検証モードを選択するようにします。

ティック生成モードに関するセクションの結論として、EURUSDの異なるティック生成モード、期間2011.01.11 21:00:00-2011.01.11 21:30:00の2つのM15バーについて、ビジュアルに比較して考えてみましょう。

ティックはWriteTicksFromTester.mq5というEAを用いて異なるファイルにセーブされ、これらのファイル名の最後はfilenamEveryTick、filenameOHLC、そしてfilenameOpenPriceという入力パラメータで設定されます。

図5WriteTicksFromTesterというエキスパートアドバイザのために、ティックの開始および終了日(変数startおよびend)を特定できます

3つのティックシーケンスを含む3つのファイル(「全ティック」「1分足OHLC」および「始値オンリー」の各モードに対応)を取得するには、EAをそれぞれ対応するモードで個別に実行し、3回起動されました。その後、これら3つのファイルからのデータが、インディケータTicksFromTester.mq5を用いてチャートに表示されました。インディケータのコードは、本稿に添付されています。

図6メタトレーダー5ターミナルのストラテジーテスターにおける、3つの異なるモードでのティックシーケンス

デフォルトでは、MQL5言語のすべてのファイル操作は「ファイルサンドボックス」内で行われ、検証中、EAは自身の「ファイルサンドボックス」にのみアクセスします。インディケータとEAが、検証中に1つのフォルダにあるファイルの作業を行うために、FILE_COMMONフラグを用いました。EAからのコード例を示します:

//--- open the file
   file=FileOpen(filename,FILE_WRITE|FILE_CSV|FILE_COMMON,";");
//--- check file handle
   if(file==INVALID_HANDLE)
     {
      PrintFormat("Error in opening of file %s for writing. Error code=%d",filename,GetLastError());
      return;
     }
   else
     {
      PrintFormat("The file will be created in %s folder",TerminalInfoString(TERMINAL_COMMONDATA_PATH));
     }

インディケータ中のデータを読み込むためにも、flag FILE_COMMONフラグを使用しました。これにより、フォルダからフォルダへのファイルの手動転送を避けることができました。

//--- open the file
   int file=FileOpen(fname,FILE_READ|FILE_CSV|FILE_COMMON,";");
//--- check file handle
   if(file==INVALID_HANDLE)
     {
      PrintFormat("Error in open of file %s for reading. Error code=%d",fname,GetLastError());
      return;
     }
   else
     {
      PrintFormat("File will be opened from %s",TerminalInfoString(TERMINAL_COMMONDATA_PATH));
     }


スプレッドのシミュレーション

売りと買いの価格差は、スプレッドと呼ばれます。検証中、スプレッドはモデル化されませんが、過去の履歴データから取得されています。履歴データ内でスプレッドが0以下の場合は、要求された履歴データの時刻におけるスプレッドが、検証エージェントによって用いられます。

ストラテジーテスターでは、スプレッドは常にフローティングと見なされます。これは、SymbolInfoInteger(symbol、 SYMBOL_SPREAD_FLOAT)で常に真を返します。

加えて、履歴データはティック値とトレーディング量を含みます。データの保存と検索のために、MqlRatesという特別なストラクチャを用います:

struct MqlRates
  {
   datetime time;         // opening bar time
   double   open;         // opening price Open
   double   high;         // the highest price High
   double   low;          // the lowest price Low
   double   close;        // the closing price Close
   long     tick_volume;  // the tick volume
   int      spread;       // spread
   long     real_volume;  // market volume 
  };


クライアントターミナルのグローバル変数

検証中、クライアントターミナルのグローバル変数もエミューレートされますが、F3ボタンを用いてターミナルで確認できる現在のターミナルのグローバル変数とは連動していません。このことは、検証中、グローバル変数によるすべてのターミナル操作はクライアントターミナルの外(検証エージェント)で行われることを意味します。


検証中のインディケータの計算

リアルタイムモードでは、インディケータの値はティックごとに計算されます。ストラテジーテスターでは、インディケータを計算するための費用対効果の高いモデルを採用しました-インディケータは、EAの実行前にのみ再計算されます。つまり、インディケータの再計算は、OnTick()、OnTrade()およびOnTimer()関数の呼び出し前に行われるということです。

特定のイベントハンドラの呼び出しがあるかどうかは問題ではなく、ハンドルがiCustom()関数もしくはIndicatorCreate()関数で作成されるすべてのインディケータが、イベントハンドラの呼び出し前に再計算されます。

結果として、「全ティック」モードでの検証の際、インディケータの計算はOnTick()関数の呼び出し前に行われます。

EAにおいて、EventSetTimer()関数を用いてタイマーがオンになっている場合、インディケータは、OnTimer()ハンドラの各呼び出し前に再計算されます。したがって、適切な方法で書かれていないインディケータの使用によって、検証時間が大きく増加する可能性があります。


検証中のローディング履歴

検証されるシンボルの履歴は、検証プロセスの開始前に同期され、トレードサーバからターミナルによってロードされます。初回は、後でリクエストしないように、ターミナルはすべての利用可能なシンボルの履歴をロードします。その後は、新たなデータのみがロードされます。

検証エージェントは検証開始直後に、クライアントターミナルから検証されるシンボルの履歴を受け取ります。検証プロセスに他の手法(例えば、マルチカレンシーエキスパートアドバイザ)が用いられている場合、検証エージェントは、そのようなデータの最初の呼び出し時に、クライアントターミナルから必要な履歴をリクエストします。ターミナル内の履歴データが利用可能であれば、それが即座に検証エージェントへ渡されます。データが利用できない場合は、ターミナルがサーバからリクエストしてダウンロードし、検証エージェントに渡します。

追加手段のデータも、トレード操作のクロスレートを計算するために必要です。例えば、通貨USDで預入されたEURCHFについてのストラテジーを検証する際、ストラテジーはそれらのシンボルを直接呼び出して用いていませんが、検証エージェントは、初回のトレード操作処理前にEURUSDとUSDCHFの履歴データをクライアントターミナルからリクエストします。

マルチカレンシーストラテジーを検証する前に、クライアントターミナルのすべての必要な履歴データをダウンロードすることを推奨します。これは、検証/最適化が、必要とされるデータのダウンロードのために遅くなるのを避けるのに役立ちます。履歴は、例えば、適切なチャートを開いてそれを履歴の最初にスクロールすることで、ダウンロードが可能です。MQL5リファレンスのデータアクセスを最適化するというセクションに、履歴をターミナルに強制的にロードする例が掲載されています。

ターミナルは、履歴をトレードサーバから1回のみ、最初にエージェントが検証されるシンボルの履歴をターミナルからリクエストした際にロードします。履歴は、トラフィックを減らすため圧縮フォーマットでロードされます。

その次に、検証エージェントがターミナルから履歴を圧縮フォーマットで受け取ります。次回の検証では、前回の起動以降はテスターが必要なデータを利用できるため、ターミナルから履歴をロードすることはありません。


マルチカレンシー検証

ストラテジーテスターでは、複数シンボルでトレードを行うストラテジーの検証も可能です。そのようなEAは慣習的に、マルチカレンシーエキスパートアドバイザと呼ばれます。なぜなら本来、以前のプラットフォームでは、検証は単一シンボルでのみ実行されていたからです。メタトレーダー5ターミナルのストラテジーテスターでは、すべての利用可能なシンボルのトレーディングをモデル化することができます。

テスターはシンボルデータの初回呼び出し時に、使用されたシンボルの履歴をクライアントターミナルから(トレードサーバからではありません!)自動的にロードします。

検証エージェントは検証開始時に、インディケータの計算用に不足している履歴のみを、その履歴へ必要なデータを付加するため少しのマージンとともにダウンロードします。タイムフレームがD1以下の場合、ダウンロードされる履歴の最小量は1年分です。

したがって、期間2010.11.01-2010.12.01(1か月間)の検証をM15(各バーが15分足)で実行する場合、ターミナルは、2010年全体の当該手法の履歴をリクエストされることになります。週足のタイムフレームの場合には、100バーの履歴をリクエストすることになり、これはおよそ2年分に相当します(1年を52週と換算)。毎月のタイムフレームの検証のためには、エージェントは8年分の履歴をリクエストすることになります(12か月×8年で96か月分)。

必要なバーがない場合は、検証開始日が過去から現在へ自動的にシフトされ、必要なバーの蓄積が検証前に行われるように調整されます。

検証中、「マーケットウォッチ」も同様にエミュレートされ、これによりシンボルの情報を取得することができます。

デフォルトでは、検証前には、ストラテジーテスターの「マーケットウォッチ」には1つのシンボル-検証が実行されているというシンボル、のみが存在します。すべての必要なシンボルは、参照される際に、ストラテジーテスター(ターミナルではありません!)の「マーケットウォッチ」に自動的に接続されます。

マルチカレンシーエキスパートアドバイザの検証開始前には、ターミナルの「マーケットウォッチ」での検証に必要なシンボルを選択し、必要なデータをロードする必要があります。「外部」シンボルの初回呼び出し時には、検証エージェントとクライアントターミナル間で履歴が自動的に同期されます。「外部」シンボルとは、検証実行対象外のシンボルです。

「対象外」のシンボルデータの参照は、次のようなケースで起こります:

「対象外」シンボルの初回呼び出し時には、検証プロセスは停止し、シンボル/タイムフレーム用の履歴がターミナルから検証エージェントへダウンロードされます。同時に、このシンボル用のティックシーケンスの生成も行われます。

選択されたティック生成モードにしたがって、個々のシンボル用に、独立したティックシーケンスが生成されます。また、OnInit()ハンドラのSymbolSelect()を呼び出して、必要な履歴を明示的にリクエストすることもできます-履歴のダウンロードはエキスパートアドバイザのテスト直前に行われます。

したがって、メタトレーダー5クライアントターミナルにおいて、マルチカレンシー検証を実行するためのいかなる追加作業も必要とされません。クライアントターミナルで、適切なシンボルのチャートを開くだけです。必要とされるシンボルについて、履歴がそのデータを含む場合には、トレーディングサーバから自動でアップロードされます。


ストラテジーテスターにおける時刻のシミュレーション

検証時に、ローカル時刻TimeLocal()は、サーバ時刻TimeTradeServer()に常に等しくなります。また、サーバ時刻はGMT時間に対応する時刻-TimeGMT()、に常に等しくなります。このように、これらすべての関数は、検証中、同じ時刻を示します。

サーバに接続していない場合には、GMT、ローカル、サーバの各時刻の差を設けないようになっています。接続の有無にかかわらず、検証結果は常に同一であるべきです。サーバ時刻に関する情報はローカルには保存されず、サーバから取得されます。


ストラテジーテスターにおけるOnTimer()関数

MQL5では、タイマーイベントを扱うことができます。検証モードにかかわらず、OnTimer()ハンドラ―の呼び出しが行われます。

これは、検証が「始値オンリー」モード、H4で実行され、かつEAが1秒ごとにタイマーを呼び出す設定を備えている場合、各H4バーの呼び出し時にOnTick() handlerハンドラが1回呼び出され、またOnTimer()ハンドラが14400回(3600秒×4時間)呼び出されます。EAの検証時間の増加量は、EAのロジックに依存します。

検証時間の依存性をタイマーに与えられた頻度からチェックするため、トレーディング操作を含まない簡単なEAを記述しました。

//--- input parameters
input int      timer=1;              // timer value, sec
input bool     timer_switch_on=true; // timer on
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- run the timer if  timer_switch_on==true
   if(timer_switch_on)
     {
      EventSetTimer(timer);
     }
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- stop the timer
   EventKillTimer();
  }
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
// take no actions, the body of the handler is empty
  }
//+------------------------------------------------------------------+

検証時間の測定は、タイマーパラメータの値にしたがって行われます(タイマーイベントの周期性)。取得したデータでは、タイマー期間の関数として検証時間をプロットします。

図7タイマー期間の関数としての検証時間

EventSetTimer(タイマー)関数の初期化中、他の条件が同一であれば、パラメータタイマーが小さいほどOnTimer()ハンドラの呼び出し間隔(期間)が小さく、また検証時間Tが大きいことがはっきり分かります。


ストラテジーテスターのSleep()関数

グラフの上での作業中、Sleep()関数を用いると、EAやスクリプトがmql5-プログラムの実行を一時中断することができます。これは、リクエスト時にまだ用意されていないデータをリクエストし、それが準備できるまで待たなければならない際に役立ちます。Sleep()関数の詳細な使用例は、データアクセス管理のセクションで参照できます。

検証プロセスを、Sleep()の呼び出しによって遅らせることはできません。Sleep()の呼び出し時には、生成されたティックは指定された遅延時間の中で「実行され」ていて、その結果、ペンディングやストップオーダーなどのトリガとなる可能性があります。Sleep()の呼び出し後、ストラテジーテスターでシミュレートされる時刻は、Sleep関数のパラメータで指定されたインターバル分だけ増加します。

Sleep()関数を実行した結果、ストラテジーテスターの現在時刻が検証期間を超えた場合には、「検証中に無限スリープループが見つかりました」というエラーメッセージを受け取ります。このエラーを受け取る場合、検証結果が否定されるわけではなく、すべての計算は完全ボリュームで実行されて(取引や減退の数など)、検証結果はターミナルに渡されます。

Sleep()関数はOnDeinit()内では動作しません。なぜなら呼び出し後、検証時間が検証間隔の範囲を確実に超えてしまうからです。

図7メタトレーダー5ターミナルのストラテジーテスターでSleep()関数を使うスキーム

図8メタトレーダー5ターミナルのストラテジーテスターでSleep()関数を使うスキーム



数学的計算上の最適化問題のためにストラテジーテスターを使う

メタトレーダー5ターミナルのテスターは、トレーディングストラテジーの検証だけでなく、数学的計算にも用いることができます。このためには、「数学的計算」モードを選択する必要があります:

この場合、3つの関数:OnInit()、OnTester()、OnDeinit()、のみが呼び出されます。「数学的計算」モードではティックの生成や履歴のダウンロードは行われません。

ストラテジーテスターは、終了日よりも後の開始日を指定した際にも「数学的計算」モードで動作します。

数学的問題の解決のためにテスターを用いる際は、履歴のアップロードやティックの生成は行われません。

メタトレーダー5ストラテジーテスターで解決する典型的な数学的問題-多くの変数を持つ関数の極致を探す

これを解決するために以下が必要となります:

  • 関数値の計算は、OnTester()関数に配置する必要があります;
  • 関数のパラメータはエキスパートアドバイザの入力変数として定義されなければなりません;

EAをコンパイルし、「ストラテジーテスター」ウィンドウを開きます。「パラメータの入力」タブで必要な入力変数を選択し、関数の各変数についてスタート、ストップ、そしてステップの各値を指定することで、パラメータ値のセットを定義します。

最適化のタイプ-「遅いが完全なアルゴリズム」(パラメータ領域の完全検索)または「速いジェネリックベースのアルゴリズム」、を選択します。関数の極値を簡単に検索するためには速い最適化を選択するほうが好ましいですが、変数のセット全体の値を計算したい場合には遅い最適化を用いるのがベストです。


「数学的計算」モードを選択し、「スタート」ボタンを用いて最適化の手順を実行します。最適化時は、ストラテジーテスターは関数の最大値を検索することに注意してください。部分的な最小値を探すためには、OnTester関数で計算された関数値の逆数を返します:

return(1/function_value);

function_valueはゼロでないことをチェックする必要があります。 そうでなければ、ゼロで割るという致命的エラーとなってしまうからです。

他にも方法があり、こちらの方がより便利で、かつ最適化の結果を歪めません。これは本稿の読者から提案されたものです:

return(-function_value);

このオプションではfunction_valueがゼロであるかどうかのチェックを必要とせず、最適化結果の3D表現は同じ形となりますが、オリジナルの反転になります。

例として、sink()関数を提示します:

この関数の極値を探すためのEAコードは、OnTester()内に配置されます:

//+------------------------------------------------------------------+
//|                                                         Sink.mq5 |
//|                        Copyright 2011, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- input parameters
input double   x=-3.0; // start=-3, step=0.05, stop=3
input double   y=-3.0; // start=-3, step=0.05, stop=3
//+------------------------------------------------------------------+
//| Tester function                                                  |
//+------------------------------------------------------------------+
double OnTester()
  {
//---
   double sink=MathSin(x*x+y*y);
//---
   return(sink);
  }
//+------------------------------------------------------------------+
最適化を実行し、2Dグラフの形で最適化の結果を確認します。

図9 2Dグラフによるsink (x*x+y*y)関数の完全な最適化の結果

与えられたパラメータのペア(x, y)がより良いものであれば、色がより飽和します。sink()の式の形を見ると予測できたことですが、値が(0,0)を中心とした同心円を形成しています。3Dグラフでは、sink()関数は単一のグローバルな極値を持たないことがわかります:

Sink関数の3Dグラフ


「始値オンリー」モードにおけるバーの同期

メタトレーダー5クライアントターミナルのテスターによって、いわゆる「マルチカレンシー」EAのチェックができるようになります。マルチカレンシーEA-これは、2以上のシンボルについて取引するEAです。

複数のシンボルについてトレードを行うストラテジーの検証のために、テスターには、追加でいくつかの技術的要求が課されます。

  • これら複数シンボルへのティックの生成;
  • これらのシンボル用のインディケータ値の計算;
  • これらのシンボルへのマージン要求の計算;
  • すべてのトレーディングシンボル用に生成されるティックシーケンスの同期;

ストラテジーテスターは、選択されたトレーディングモードに応じて、各手段ごとにティックシーケンスを生成、実行します。同時に、各シンボルの新規バーを、他のシンボルでどのようにオープンするかにかかわらず、オープンします。これは、マルチカレンシーEAを検証する際に(しばしば)起こることですが、1つの手段では新規バーがすでにオープンしているが、他ではしていない、ということを意味します。したがって検証においては、あらゆることが現実同様に起こります。

テスターによる本来の履歴のシミュレーションでは、「全ティック」や「1分足OHLC」検証モードを用いる限りは、何の問題も起こりません。これらのモードでは、1つのローソク足のために十分なティックが生成されて、異なるシンボルからのバーの同期が行われるまで待つことができます。しかし、トレーディング手段に関してバーの同期が必須であるとしたら、「始値オンリー」モードのマルチカレンシーストラテジーはどのように検証したらよいでしょうか?このモードでは、EAはバーのオープン時に相当する1つのティックについてのみ呼び出されます。

1つ例を挙げて解説します:あるEURUSDのEAを検証していて、EURUSDで新しく時間単位のローソク足がオープンした場合、「始値オンリー」モードでの検証では、NewTickイベントが検証期間中のバーオープン時に対応するという事実については、容易に理解することができます。しかし、EAで用いられているUSDJPYシンボルについて、新規のローソク足がオープンしたという保証はありません。

通常の状況であれば、OnTick()関数の作業を完了し、次のティックでUSDJPYの新しいバーに異常がないかをチェックすれば十分です。しかし「始値オンリー」モードで検証する際には他のティックがないため、マルチカレンシーEAの検証に適していないように思われます。しかしそうではありません-メタトレーダー5のテスターは現実の世界と同様に動作することを忘れてはなりません。つまり、Sleep()関数を用いて、新しいバーが他のシンボルで開くまで待つことができるのです!

このEAコード、Synchronize_Bars_Use_Sleep.mq5では、「始値オンリー」モードにおけるバーの同期の例を示しています:

//+------------------------------------------------------------------+
//|                                   Synchronize_Bars_Use_Sleep.mq5 |
//|                        Copyright 2011, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//--- input parameters
input string   other_symbol="USDJPY";
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- check symbol
   if(_Symbol==other_symbol)
     {
      PrintFormat("You have to specify the other symbol in input parameters or select other symbol in Strategy Tester!");
      //--- forced stop testing
      return(-1);
     }
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- static variable, used for storage of last bar time
   static datetime last_bar_time=0;
//--- sync flag
   static bool synchonized=false;

//--- if static variable isn't initialized
   if(last_bar_time==0)
     {
      //--- it's first call, save bar time and exit
      last_bar_time=(datetime)SeriesInfoInteger(_Symbol,Period(),SERIES_LASTBAR_DATE);
      PrintFormat("The last_bar_time variable is initialized with value %s",TimeToString(last_bar_time));
     }

//--- get open time of the last bar of chart symbol
   datetime curr_time=(datetime)SeriesInfoInteger(Symbol(),Period(),SERIES_LASTBAR_DATE);
//--- if times aren't equal
   if(curr_time!=last_bar_time)
     {
      //--- save open bar time to the static variable
      last_bar_time=curr_time;
      //--- not synchronized
      synchonized=false;
      //--- print message
      PrintFormat("A new bar has appeared on symbol %s at %s",_Symbol,TimeToString(TimeCurrent()));
     }
//--- open time of the other symbol's bar
   datetime other_time;

//--- loop until the open time of other symbol become equal to curr_time
   while(!(curr_time==(other_time=(datetime)SeriesInfoInteger(other_symbol,Period(),SERIES_LASTBAR_DATE)) && !synchonized))
     {
      PrintFormat("Waiting 5 seconds..");
      //--- wait 5 seconds and call SeriesInfoInteger(other_symbol,Period(),SERIES_LASTBAR_DATE)
      Sleep(5000);
     }
//--- bars are synchronized
   synchonized=true;
   PrintFormat("Open bar time of the chart symbol %s: is %s",_Symbol,TimeToString(last_bar_time));
   PrintFormat("Open bar time of the symbol %s: is %s",other_symbol,TimeToString(other_time));
//--- TimeCurrent() is not useful, use TimeTradeServer()
   Print("The bars are synchronized at ",TimeToString(TimeTradeServer(),TIME_SECONDS));
  }
//+------------------------------------------------------------------+

EAの最後の行に注目してください。ここで、同期の事実が確立した時点の現在時刻を表示します:

Print("The bars synchronized at ",TimeToString(TimeTradeServer(),TIME_SECONDS));

現在時刻を表示するために、TimeCurrent ()ではなくTimeTradeServer ()関数を用いました。TimeCurrent()関数は最後にティックした時刻を返し、これはSleep()を用いた後も変わることがありません。「始値オンリー」モードのEAを実行すると、バーの同期についてのあるメッセージに気づくでしょう。

最後のティックが到着した時刻ではなく、サーバの現在時刻を取得する必要がある場合は、TimeTradeServer()関数をTimeCurrent()の代わりに用います。

バーの同期方法は他にもあります-タイマーを使用する方法、です。そのようなEAの例、Synchronize_Bars_Use_OnTimer.mq5が、本稿に添付されています。


テスターにおけるIndicatorRelease()関数

1つの検証が完了した後、その金融商品のチャートが自動的にオープンし、EAで用いられた検証済みの取引とインディケータが表示されます。これにより、エントリポイントと退場ポイントを視覚的にチェックし、それらをインディケータの値と比較することができます。

注意:検証後に自動的にオープンするチャート上のインディケータは、 検証完了後に新たに計算されています。たとえこれらのインディケータが、検証されたEAで用いられていた場合であってもです。

しかし場合によっては、プログラマーが、インディケータがトレーディングアルゴリズムに関与していたという情報を表示すべきではないと考えるかもしれません。例えば、EAのコードについては、ソースコードの提供なしに、実行可能ファイルとしてレンタルや販売が行われます。この目的において、IndicatorRelease()関数が適しています。

ターミナルが、tester.tplという名前のテンプレートをクライアントターミナルのdirectory/profiles/templatesに設定する場合、これは、オープンされるチャートにも適用されることになります。このファイルがない時は、デフォルトのテンプレートが適用されます。(default.tpl)

IndicatorRelease()関数は本来、インディケータの計算部分が不要になった場合に解放することを目的としています。各ティックがインディケータの計算を呼び出すことから、これにより、メモリとCPUのリソースを節約することができます。第2の目的は、1つの検証を実行した後、検証チャート上にインディケータを表示することを禁止することです。

検証後、チャート上のインディケータ表示を禁止するには、IndicatorRelease()を、OnDeinit()ハンドラにあるインディケータのハンドルとともに呼び出します。OnDeinit()関数は常に、検証チャートの完了後ならびに表示前に呼び出されます。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   bool hidden=IndicatorRelease(handle_ind);
   if(hidden) Print("IndicatorRelease() successfully completed");
   else Print("IndicatorRelease() returned false. Error code ",GetLastError());
  }
1つの検証完了後、チャート上のインディケータ表示を禁止するには、IndicatorRelease()関数をOnDeinit()ハンドラ内で用います。


テスターにおけるイベントハンドリング

EAにおいて、OnTick()ハンドラは、メタトレーダー5テスターでそれを履歴データに関する検証の対象にする上では、必須ではありません。EAが、以下の関数ハンドラの少なくとも1つを含んでいれば十分です:

  • OnTick()-新規ティックの到着に関するイベントハンドラ;
  • OnTrade()-トレーディングイベントハンドラ;
  • OnTimer()-タイマーからのシグナル到着に関するイベントハンドラ;
  • OnChartEvent ()-クライアント側のイベントに関するハンドラ。

EA内を検証する際、OnChartEvent()関数を用いてカスタムコントロールイベントを扱うことができますが、インディケータ内では、この関数をテスター内で呼び出すことができません。たとえインディケータがOnChartEvent()イベントハンドラを備え、このインディケータが検証されるEAで用いられるとしても、インディケータ自身はどんなカスタムイベントも受け取りません。

検証中、インディケータはEventChartCustom()関数を用いてカスタムイベントを生成し、EAはこのイベントをOnChartEvent()内で処理することができます。


検証エージェント

メタトレーダー5クライアントターミナルにおける検証は、検証エージェントを用いて実行されます。ローカルエージェントは、自動的に作成され有効化されます。ローカルオブジェクトのデフォルト数はコンピュータのコア数に対応します。

各検証エージェントはそれぞれが、クライアントターミナルに関連しないグローバル変数のコピーを備えています。ターミナル自身はディスパッチャーとして、ローカルとリモートのエージェントに配信を行います。与えられたパラメータによるEAの検証タスクの実行後、エージェントはターミナルに結果を返します。単一の検証では、1つのエージェントのみが用いられます。

エージェントは、ターミナルから受け取る履歴を、手段名別に個別フォルダに格納します。そのため、EURUSDの履歴はEURUSDという名前のフォルダに格納されます。加えて、手段に関する履歴はソース別にも別れています。履歴は、次のような構成で格納されます:

tester_catalog\Agent-IPaddress-Port\bases\name_source\history\symbol_name

例えば、サーバMetaQuotes-DemoからのEURUSDの履歴は、tester_catalog\Agent-127.0.0.1-3000\bases\MetaQuotes-Demo\EURUSD、というフォルダに格納されます。

検証の完了後、ローカルエージェントは、次の呼び出しが起動してすぐの時間を無駄にしないようスタンバイモードに切り変わり、5分間次のタスクを待ちます。待機時間が過ぎた後でのみ、ローカルエージェントはシャットダウンし、CPUメモリのロードを解放します。

検証が早く完了した場合は、ユーザ側(「キャンセル」ボタン)から、またクライアントターミナルの終了にともなって、すべてのローカルエージェントが作業を速やかに停止してメモリロードから解放されます。


ターミナルとエージェント間のデータ交換

検証を実行する際、クライアントターミナルはエージェントに、大量のパラメータブロックを送信する準備を行います。

  • 検証用の入力パラメータ(シミュレーションモード、検証のインターバル、手段、最適化基準など)
  • 「マーケットウォッチ」における、選択されたシンボルのリスト
  • 検証するシンボルの特定(コントラクトサイズ、ストップロスやテイクプロフィット設定に用いる、マーケットからの許容マージン、など)
  • 検証されるエキスパートアドバイザと入力パラメータ値
  • 追加ファイルに関する情報(ライブラリ、インディケータ、データファイル-# property tester_ ...
    tester_indicator 文字列 indicator_name.ex5」フォーマットのカスタムインディケータ名。検証を要するインディケータは、対応パラメータが定数列により設定されている場合は、iCustom()関数の呼び出しから自動で定義されます。他のすべてのケース(IndicatorCreate()関数が使われている場合や、インディケータ名の設定パラメータに非定数列が使われている場合)で、このプロパティがリクエストされます。
    tester_file 文字列 ダブルクォートで囲まれた(定数列としての)テスターのファイル名と拡張子の表示。指定されたファイル名が、テスターに渡されます。検証される入力ファイルは、必要なものがある場合には常に特定されなければなりません。
    tester_library 文字列 ダブルクォートで囲まれた、ライブラリ名とその拡張子。ライブラリは、拡張子がdllまたはex5となっています。 検証が必要なライブラリは自動的に定義されます。しかし、カスタム インディケータで用いられるライブラリの場合、どのようなものであってもこのプロパティが必要です。

パラメータの各ブロックにMD5ハッシュ形式のデジタル指紋が作成され、エージェントに送られます。MD5ハッシュは各セットごとにユニークであり、より多くの場合、そのボリュームは計算される情報量よりも少なくなります。

エージェントはハッシュブロックを受け取り、すでに持っている値と比較します。与えられたパラメータブロックの指紋がエージェント側に存在しない場合、もしくは受け取ったハッシュが既存のものと異なる場合は、エージェントはこのパラメータブロックをリクエストします。これにより、ターミナルとエージェントの間のトラフィックを減らすことができます。

検証後、エージェントはターミナルに、実行結果のすべて:受取収益、取引数、効率化係数、OnTester()関数など、を返し、それらは「テスト結果」「最適化結果」タブに示されます。

最適化中、ターミナルは検証タスクを小さなパッケージとしてエージェントに渡し、各パッケージはいくつかのタスク(各タスクとは、入力パラメータのセットをともなう1つの検証を意味します)を含みます。これにより、ターミナルとエージェントの間の交信時間を減らすことができます。

エージェントは、セキュリティ上、エージェントを実行するコンピュータが送られたデータを使用することができないように、ターミナルから得られるEX5ファイル(EA、インディケータ、ライブラリなど)をハードディスクに記録しません。DLLを含む他のすべてのファイルは、サンドボックス内に記録されます。リモートエージェントの場合、DLLを用いた検証を行うことができません。

検証結果は、必要な時に素早くアクセスできるように、ターミナルによって結果についての特別なキャッシュ(結果キャッシュ)に追加されます。パラメータの各セットに対し、ターミナルは、前回の実行ですでに利用できるようになっている結果について結果キャッシュを検索し、再実行を避けるようにします。そのようなパラメータセットを含む結果が見つからない場合に、エージェントに検証を実行するためのタスクが与えられます。

ターミナルとエージェント間のすべてのトラフィックは暗号化されています。


すべてのクライアントターミナルの共有フォルダを使う

すべての検証エージェントは互いに、またクライアントターミナルから独立しています:各エージェントには専用のフォルダがあり、個別にログが記録されます。加えて、エージェントの検証中に生じるすべてのファイル操作は、 agent_name/MQL5/Filesフォルダで発生します。しかし、ファイルオープン時にフラグFILE_COMMONを指定した場合は、ローカルエージェントとクライアントターミナル間の交信を、すべてのクライアントターミナル用の共有フォルダを通して実行することができます:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- the shared folder for all of the client terminals
   common_folder=TerminalInfoString(TERMINAL_COMMONDATA_PATH);
//--- draw out the name of this folder
   PrintFormat("Open the file in the shared folder of the client terminals %s", common_folder);
//--- open a file in the shared folder (indicated by FILE_COMMON flag)
   handle=FileOpen(filename,FILE_WRITE|FILE_READ|FILE_COMMON);
   ... further actions
//---
   return(0);
  }


DLLの使用

最適化のスピードアップのため、ローカルだけでなくリモートのエージェントを用いることができます。このケースでは、リモートエージェントにいくつかの制約があります。まず、リモートエージェントは、Print()関数の実行結果をログに表示しません。これらは、ポジションのオープンやクローズについてのメッセージだからです。ログには最低限の情報が表示されるようにして、誤って記述されたEAが、リモートエージェントが動作するコンピュータにログメッセージをため込まないようになっています。

第2の制約として-EAの検証時にDLLの使用を禁止しています。セキュリティの関係上、リモートエージェント上でのDLLの呼び出しは固く禁止されています。ローカルエージェントでは、検証対象のEAでのDLLの呼び出しは、「DLLインポートの許可」による適切な許可がある場合のみ可能です。

図10mql5プログラムにおける「DLLインポートの許可」オプション

注意:EAから受信したファイル(スクリプト、インディケータ)の使用にDLL呼び出しの許可を必要とする際は、ターミナルの設定でこのオプションを許可するリスクについて認識しなければなりません。それは、EAがどのように用いられるか-検証のため、あるいはチャート上での実行のため、にかかわらず、然りです。


結論

本稿では、メタトレーダー5クライアントターミナルでのEAの検証を手早くマスターするのに役立つ基本事項や知識を紹介しています:

  • 3つのティック生成モード;
  • 検証中のインディケータ値の計算;
  • マルチカレンシーの検証;
  • 検証中の時間のシミュレーション;
  • ストラテジーテスターにおけるOnTimer()関数、OnSleep()関数およびIndicatorRelease()関数の動作;
  • DLL呼び出し時の検証エージェントの動作;
  • 全クライアントターミナル用共有フォルダの使用;
  • 「始値オンリー」モードでのバーの同期;
  • イベントハンドリング。

クライアントターミナルのストラテジーテスターの主な働きは、MQL5プログラマーが最小の労力で、求められるデータの正確性を確実にすることです。開発者は、あなたが過去の履歴データについてトレーディングストラテジーを検証するためだけにコードを書き直す必要がないよう、多くの工夫を重ねてきました。検証の基本事項を知っていれば十分で、そうすれば、正確に記述されたEAが、テスターでも、オンラインモードのチャート上であっても同様に、正しく動作するはずです。

MetaQuotes Software Corp.によってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/239

MQL5におけるリソースの使用 MQL5におけるリソースの使用
MQL5プログラムを使うと、ルーチンの演算を自動化するだけでなく、フル機能のグラフィック環境を作ることが可能です。真にインタラクティブなコントロールを作成する機能は、今や、伝統的なプログラミング言語のそれとほとんど同じくらい充実しています。MQL5でフル機能のスタンドアロンプログラムを書くことを望むなら、これらに含まれるリソースを使いましょう。リソースを使ったプログラムは、管理や配布をより簡単に行えます。
MQL5のExpert Advisorsのテストと最適化を行うためのガイド MQL5のExpert Advisorsのテストと最適化を行うためのガイド
ここでは、コードエラーを見つけ解決するための段階的な手順について説明します。またExpert Advisor(以下EA)への入力パラメータのテストと最適化の手順についても説明します。Meta Trader 5のクライアント端末のStrategy Testerの使い方がわかれば、ご自身のEAに最も適したシンボルや入力パラメータセットを見つけることができるようになります。
MQL5ウィザード:新バージョン MQL5ウィザード:新バージョン
本稿では、最新のMQL5ウィザードで利用できる新しい特徴について述べます。シグナルのアーキテクチャが変更され、さまざまなマーケットパターンにもどつくトレーディングロボットを作成することができるようになっています。本稿に含まれる例では、エキスパートアドバイザのインタラクティブな作成手順を説明しています。
メタトレーダー5クライアントターミナルにおける適応型トレーディングシステムとその使用 メタトレーダー5クライアントターミナルにおける適応型トレーディングシステムとその使用
本稿では、多くのストラテジーで構成され、それぞれが「バーチャル」トレードオプションを実行する、適応型システムの変形を提案します。リアルトレーディングは、その時点で最大利益となるストラテジーにしたがって行われます。オブジェクト指向アプローチ、データを扱うクラスや、標準ライブラリのトレードクラスのおかげで、システムのアーキテクチャはシンプルかつスケーラブルになりました;今や、数百のトレードストラテジーを含む適応型システムを、簡単に作成し、分析することができます。