English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
MQL5でトレンドを見つけるいくつかの方法

MQL5でトレンドを見つけるいくつかの方法

MetaTrader 5トレーディング | 7 10月 2015, 15:34
3 253 0
Dmitriy Skub
Dmitriy Skub

導入

どんなトレーダーであっても、「トレンドは友達、トレンドに従え」というルールを知っていますが、ほとんどすべての人が、トレンドとは何かという点について独自の考えを持っています。ほとんどすべてのトレーダーが、トレンドに反するトレードを行って身を滅ぼした人の恐ろしい話を、聞いたり読んだりしています。

あらゆるトレーダーが、所定の時点におけるトレンドを正確に見つけるための多くの機会を提供することができます。これはおそらく、誰もが探している聖杯です。本稿では、トレンドを見つけるいくつかの方法を検討していきます。それはより正確に言えば-MQL5を用いてトレンドを見つけるいくつかの伝統的な方法をプログラムする方法、です。


1トレンドとは何か、なぜそれを知るのか

まず、トレンドの一般的な概念を公式化してみましょう。

トレンドとは-マーケットにおける価格変動の長期的傾向(方向)です。トレンドのこの一般的な定義から、次の結果が導かれます:

  • トレンドの方向は、価格の時系列を検討するタイムフレームに依存する。
  • 価格変動の方向は、トレンドを認識する時系列分析を始めるレファレンスポイントに依存する。

この概念を解説します。

図1トレンド分析

図1トレンド分析

この図を見ると、2005年末から2006年5月までの上昇トレンドを概観することができます(チャート上の緑色の矢印)。しかし、もし価格チャートをより小さな単位で検討すると、2006年2月にはトレンドは明らかに下降し(チャート上の赤色の矢印)、1月にはほぼ全般的に、価格が平行線上をたどりました(黄色の矢印)。

したがって、トレンドを特定する前に、どのタイムフレームに注目するかを決定する必要があります。トレードにとって、タイムフレームとは第一に、オープンからクローズまで、マーケットポジションをホールドする期間を決定します。加えて、トレンドは保護的なストップと期待されるクローズ、またトレード操作の頻度にも依存します。

本稿の目的は、新規トレーダーが、メタトレーダー5プラットフォームで提供されているトレンド検知ツールを賢く使えるように、手助けすることです。本稿はまた、プロセスを自動化する簡単なインディケータを書くための基礎知識を提供することも目指しています。究極のゴールは、自動トレーディングにこれらのインディケータを用いる、シンプルなエキスパートを記述することです。

分かりやすいように、Forexマーケットで最も流動的な手段である日足チャート(ターミナル上のD1タイムフレーム)-EURUSD、を検討します。そのようなタイムフレームでポジションをホールドする時間は、数日から数か月の間で変化させることができます。したがって、目的としては-数百、数千ものポイントを取得し、保護的なストップロスを数百ポイントの距離で配置すること、です。

一般的には、以下に述べるすべてを、あらゆるタイムフレームで用いることができます。しかし、チャートのタイムフレームが小さいほど、ニュースや、主要参加者のマーケットへの投機、そしてその他のファクターがトレードに与えるインパクトはより大きなノイズ要素を持ち、マーケットのボラティリティに影響することは、念頭においてください。

トレンドが長くなるほど、そのトレンドがシフトしにくいことを考え合わせるならば、トレンドにもとづいてトレードを行うと、マネーを失うより得ることが多くなる傾向にあります。そこで、価格チャートでトレンドを見つける方法を理解しなければなりません。本稿ではこれを議論していきます。


2トレンドをどうやって見つけるか

以下は、トレンドの見分け方として知られるいくつかの方法です。

  1. 移動平均
  2. ジグザグのピーク
  3. ADXインディケータ
  4. NRTR
  5. 平均足(ローソク足)の色

これらすべての方法に注目しつつ、長所と欠点を検討していきます。では、同じ取引履歴で、これらを比べてみましょう。


2.1. 移動平均を用いたトレンド検知

おそらく、トレンドを見つけるもっとも簡単な方法とは-移動平均を用いること、です。テクニカル分析の最初のツールの1つである移動平均は、今なお異なるバリエーションで用いられ、多くのインディケータの土台となっています。トレーダーは、1つの移動平均と、「ファン」と呼ばれることがあるそれらのセットの両方を用います。

1つの移動平均を用いる場合の、単純なルールを公式化してみましょう:
  • 所定のタイムフレームで、バーの終値が移動平均より大きい場合、トレンドは上昇します
  • 所定のタイムフレームで、バーの終値が移動平均より小さい場合、トレンドは下降します

この場合、バーの終値を用いて、価格が移動平均付近で上下に変動(いわゆる「バウンス」)する際に「偽の」トレンド変動の数を減らします。

この方法について、解説します。

図2移動平均を用いたトレンド同定

図2移動平均を用いたトレンド同定

ここでは、EURUSD、D1チャートと、その終値に形成された(チャート上の赤線)、期間200の単純な移動平均を用います。図の下の方に、特別に開発されたトレンドインディケータ-MATrendDetector、が見られます。このトレンドの傾向は、ゼロを軸とするインディケータヒストグラムのポジションによって示されます。+1は、上昇トレンドに対応します。-1は、下降トレンドです。これと、本稿で用いる他のインディケータについて、以下さらに議論を進めます。

ご覧のとおり、バーが移動平均より上/下で終了する際、価格がしばしば反対方向に変わります。つまりこの方法は、たくさんの偽りのシグナルをもたらすということです。それが、エキスパートやインディケータの利用が、非常に「おおまかな」トレンドフィルタとしてごく限られている理由です。


2.2. 3つの移動平均を用いたトレンド検知

移動平均を用いたトレンド検知の質を向上させるためには、どうすれば良いでしょうか?例えば、異なる期間の2つ以上の移動平均を使うことができます。そして、あらゆる数(1つ以上)の移動平均を異なる期間で用いるトレンド検知のルールは、次のようになります:

  • 所定のタイムフレームにおいて、すべての移動平均がバーの終了時に昇順に正しくプロットされていれば、トレンドは上昇します
  • 所定のタイムフレームにおいて、すべての移動平均がバーの終了時に降順に正しくプロットされていれば、トレンドは下降します

ここでは、次の用語を用いています:

  • 正しい昇順-すべての移動平均は、より大きい期間の他の移動平均よりも大きくなければなりません。
  • 正しい降順-すべての移動平均は、より大きい期間の他の移動平均よりも小さくなければなりません。

このような「平均の正しい順序」はまた、平均ファンの上昇/下降オープニング、と呼ばれます。

この方法について、解説します。

図3いくつかの移動平均を用いたトレンド検知

図3いくつかの移動平均を用いたトレンド検知

ここでは、EURUSD、D1チャートと、その終値に形成された、期間200(濃い赤線)、50(中くらいの濃さの黄線)および21(薄い紫線)の単純な移動平均を用います。

図の下のほうに、特別に開発されたトレンドインディケータ-FanTrendDetectorが見られます。このトレンドの傾向は、ゼロを軸とするインディケータヒストグラムのポジションによって示されます。+1は、上昇トレンドに対応します。-1は、下降トレンドです。ヒストグラム値がゼロに等しければ、トレンドが検出されないことを意味します。比較のために、MATrendDetectorインディケータも用います。

トレンド変動の偽りの警告数が、明らかに減少しています。しかし、トレンド検知の遅延が増えています。これは、理にかなったことです-すべての移動平均を「正しい」順序でラインナップするまでには時間を要するからです。何がより良く何がそうでないかは-これらの方法を用いるトレーディングシステムによります。

このケースでは、期間平均値はどのようにも選択されていませんが、本稿の著者も含めて、トレーダーの間でこれは広く用いられています。移動平均のセットとその数を選択することで、特定の通貨ペアについて、このトレンド検知の特徴を向上させることができます。


2.3. ジグザグインディケータの最大、最少を用いたトレンド検知

それでは、テクニカル分析の伝統的な視点によるトレンド分析へと進みましょう。すなわち、チャールズダウの以下のような理論を用います:

  • 価格チャートのすべての次の局所最大値がそれより前の局所最大値より大きく、それに続く各局所最小値もそれより前の局所最小値より大きければ、トレンドは上昇します
  • 価格チャート上で連続して起こる各局所最小値が前の局所最小値より小さく、それに続く各局所最大値もそれより前の局所最大値より小さければ、トレンドは下降します

ジグザグインディケータの頂点により、局所最大値、最小値を探すことができます。

この方法について、解説します。

図4ジグザグインディケータを用いたトレンド検知

図4ジグザグインディケータを用いたトレンド検知

ここでは、EURUSD、D1チャートと以下のパラメータ:ExtDepth = 5ExtDeviation = 5ExtBackstep = 3、をともなうジグザグを用います。

図の下の方に、特別に開発されたトレンドインディケータ- ZigZagTrendDetectorが見られます。

この方法の主な欠点は-極値がすでに形成されているかどうかをリアルタイムで理解することができないこと、です。履歴上では、極値はたいへん見つけやすく、どこに形成されているかを理解することができます。しかし、リアルタイムの価格変動では、形成された極値が突然消滅し、再度現れる場合があります。これを理解するため、いずれのエキスパートのビジュアルモードでもプロットするジグザグラインを少し見てみます。

この欠点により、この方法は、トレードでは実際に用いることができません。しかし、履歴データのテクニカル分析でパターンを発見し、さまざまなトレーディングシステムの質を評価する上ではたいへん役に立ちます。


2.4. ADXインディケータを用いたトレンド検知

以下に示す方法は、ADX(平均方向移動インデックス)インディケータを用いたトレンド分析です。このインディケータは、トレンドの方向を検知するだけでなく、その強さを評価するためにも用いられます。これが、ADXインディケータのたいへん有益な特徴です。主要なADXラインで決定されるトレンドの強さとは-価値が20より大きい(一般に認められるレベル、必ずしもその時点での最良とは限らない)場合、トレンドは十分に強い、というものです。

トレンドの方向は、+DIと-DIラインにより相互に決定されます。このインディケータは、3つのすべてのラインを指数平均で平衡化して用いるため、トレンド変動に対応した遅延が生じます。

トレンド検知のルールを公式化してみましょう:

  • +DIラインが-DIラインより高ければ、トレンドは上昇します
  • +DIラインが-DIラインより低ければ、トレンドは下降します

この場合、ADXトレンドラインは、トレンド検知には用いられません。このインディケータの偽りのシグナルを減らす必要があります。トレンドが弱い(ADXが20より小さい)場合には、より強くなるまで待って、その後で、そのトレンドでトレードを開始するのが最良です。

この方法について、解説します。

図5ADXインディケータを用いたトレンド同定

図5ADXインディケータを用いたトレンド同定

ここでは、EURUSD、D1チャートと、以下のパラメータ:PeriodADX = 21(濃い青線-ADXトレンド値の強さ、薄い緑線- +DI値、薄い赤線--DI値)、を持つADXインディケータを用います。

図の下のほうに、特別に開発されたトレンドインディケータ- ADXTrendDetectorが見られます。比較のため、上方のADXTrendDetectorインディケータのチャート(クリムゾン)ではトレンド強度フィルターが無効(ADXTrendLevel = 0)になっており、下方のチャート(青)ではこれが有効になっています(ADXTrendLevel = 20)。

トレンドの方向を探す際、トレンド強度フィルタをオンにすると、いわゆる「バウンス」の部分は割愛されます。実際の作業では、このフィルタを用いることが望まれます。外部パラメータを、マーケットの現状(フラット、レンジ、トレンド)に即しつつ、通貨ペアの動きの本質に応じて上手く選択すると、インディケータの質的向上を実現することができます。

一般的に、このインディケータは、入力フィルタとしてのトレンド追跡用トレーディングシステムを構築するための優れた機会を提供します。


2.5. NRTRインディケータを用いたトレンド検知

以下のトレンド検知方法では-NRTR(Nick Rypock Trailing Reverse)インディケータ、を用います。このインディケータは常に、到達した価格の極値-上昇トレンドでのより低い価格と、下降トレンドでのより高い価格、から一定距離をおいて配置されます。このインディケータのアイディアは-主要トレンドに対する小さな調整的な動きを無視し、主要トレンドに反する動きがあるレベルを超えると、トレンドの方向が変更したことを合図する、というものです。

この記述から、トレンドの方向を検知するルールが分かります:

  • インディケータラインがバー終了時に上昇トレンドに対応すれば-トレンドは上昇します
  • インディケータラインがバー終了時に下降トレンドに対応すれば-トレンドは下降します

価格変動についての偽りのトレンド反転の影響を減らすため、終値を用いてNRTRラインポジションをチェックします。

この方法について、解説します。

図6NRTRインディケータを用いたトレンド同定

図6NRTRインディケータを用いたトレンド同定

この大きい青ドットが上昇トレンドに、大きい赤ドットは下降トレンドに対応しています。チャートの下方に、後で述べるNRTRTrendDetectorが表示されています。


2.6. 3つの平均足(ローソク足)を用いたトレンド検知

トレンド検知のもう1つの定評ある方法は、平均足(ローソク足)を用いる方法です。平均足チャートとは、日本のローソク足チャートを修正したものです。これらの値は、その前のローソクで部分的に平均化されています。

この方法について、解説します。

図7平均足(ローソク足)の色によるトレンド検知

図7平均足(ローソク足)の色によるトレンド検知

見てわかるように、この方法でも、価格が横ばいに変動する場合に「偽の」シグナルから逃れることはできません。しかし、さらに悪いことに、このインディケータは、最後のバーだけでなく、最後から2番目のバーも再描画することができます。すなわち、エントリ時のシグナルが、次のバーでは反転するかもしれないのです。これは、ローソク足の色が決定される際に2つのバーが分析されることが原因です、したがってこの方法を、他の補助的なシグナルと併用して使うことが推奨されています。


3トレンドインディケータ

それでは、トレンドインディケータを作成してみましょう。


3.1. 移動平均にもとづくトレンドインディケータ

これは、移動平均にもとづいてトレンドを決定する最も簡単な方法としての、最も簡単なインディケータです。どのようなパーツで構成されているかを、見てみましょう。このインディケータの完全なソースコードは、本稿添付のMATrendDetector.MQ5ファイルに含まれています。

インディケータプログラムの先頭には、ライブラリに接続してさまざまな移動平均を計算する行が含まれます。このライブラリはクライアントターミナルに同梱されており、インストール直後にすぐ使うことができます。以下がそのプログラム行です:

#include <MovingAverages.mqh>

そこから、簡単な移動平均を計算する関数を1つ用います:

double SimpleMA(const int position, const int period, const double &price[])

ここで、入力パラメータを定義します:

  • positionprice[]配列の初期インデックスで、ここから計算を開始します。
  • period-移動平均の期間であり、0より大きくなければなりません。
  • price[]-インディケータをチャートに配置する際に指定される価格レンジを含む配列。デフォルトでは、Close[]にはバーの終値が用いられています。

この関数が、移動平均の計算値を返します。

次のテキスト部分には、スクリーン上にインディケータを表示する初期設定が含まれています:

//---------------------------------------------------------------------
#property indicator_separate_window
//---------------------------------------------------------------------
#property indicator_applied_price       PRICE_CLOSE
#property indicator_minimum             -1.4
#property indicator_maximum             +1.4
//---------------------------------------------------------------------
#property indicator_buffers             1
#property indicator_plots               1
//---------------------------------------------------------------------
#property indicator_type1               DRAW_HISTOGRAM
#property indicator_color1              Black
#property indicator_width1              2
//---------------------------------------------------------------------

以下のパラメータが設定されます:

  • #property indicator_separate_windowは、メタトレーダー5ターミナルに個々のウィンドウにインディケータチャートを表示するよう指示します。
  • #property indicator_applied_price PRICE_CLOSE-デフォルトの価格タイプ。
  • #property indicator_minimum -1.4-インディケータウィンドウに表示される垂直軸の最小値。
  • #property indicator_maximum +1.4-インディケータウィンドウに表示される垂直軸の最大値。

最後の2つのパラメータによって、インディケータチャートを固定スケールで設定することができます。これがなぜ可能かというと、ここではインディケータの最小値と最大値が-1と+1(当該値を含む)であることが分かっているからです。これにより、ウィンドウの境界やインディケータタイトルがウィンドウ内で重ならないよう、チャートの見映えを良くすることができます。

  • #property indicator_buffers 1-インディケータ計算用のバッファ数。ここでは、1つのみを用います。
  • #property indicator_plots 1-インディケータにおけるグラフィックの連続数。ここでは、スクリーンには1つのチャートのみを表示します。
  • #property indicator_type1 DRAW_HISTOGRAM-ヒストグラムとしてインディケータチャートを表示。
  • #property indicator_color1 Black-インディケータチャートのデフォルト色。
  • #property indicator_width1 2-インディケータチャートの線幅。この場合、ヒストグラム列の幅に相当。

次は、インディケータの動作中、インディケータの配置時またはそれ以降に変更することができる、インディケータの外部パラメータを入力するパートです:

input int   MAPeriod = 200;

ここでは、1つのパラメータ-移動平均期間の値、しかありません。

インディケータの次の重要なパートは-インディケータがチャート上で動作している間にさまざまなイベントを処理する関数、です。

まず、初期化関数-OnInit()、が登場します。これは、インディケータのロード直後に呼び出されます。このインディケータは、以下のようになっています:

void OnInit()
{
  SetIndexBuffer( 0, TrendBuffer, INDICATOR_DATA );
  PlotIndexSetInteger( 0, PLOT_DRAW_BEGIN, MAPeriod );
}

SetIndexBuffer()関数では、インディケータバッファの1つを用いてトレンドの値TrendBuffer[]を格納するため、前に宣言した配列をバインドします。ここではインディケータバッファを1つしか用いないため、そのインデックスは0に等しくなります。

PlotIndexSetInteger() 関数では、初期バーの数
をインディケータウィンドウに表示せずに設定します。

単純移動平均を、その期間より小さい数のバーについて計算することは数学的に不可能なため、バーの数を、移動平均期間と同一に設定します。

次が、インディケータを再計算する必要についてのイベントを処理する関数-OnCalculate()、です:

int OnCalculate(const int _rates_total, 
                const int _prev_calculated,
                const int _begin, 
                const double& _price[ ] )
{
  int  start, i;

//   If number of bars on the screen is less than averaging period, calculations can't be made:
  if( _rates_total < MAPeriod )
  {
    return( 0 );
  }

//  Determine the initial bar for indicator buffer calculation:
  if( _prev_calculated == 0 )
  {
    start = MAPeriod;
  }
  else
  {
    start = _prev_calculated - 1;
  }

//      Loop of calculating the indicator buffer values:
  for( i = start; i < _rates_total; i++ )
  {
    TrendBuffer[ i ] = TrendDetector( i, _price );
  }

  return( _rates_total );
}

この関数は、インディケータ初期化後に初めて、さらに価格データが変更されるたびに呼び出されます。例えば、インディケータが計算されるシンボルに関して、新しいティックが現れる場合を考えます。これを詳しく検討してみましょう。

まず、チャート上に十分な数のバーがあるかどうかを確認します-移動平均期間に足りなければ、計算は行われず、この関数はreturn演算子で終了します。 operator. バーの数が計算のために十分にあれば、インディケータの計算拠点となる初期バーを決定します。これは、価格ティックごとにすべてのインディケータを再計算しないようにするために行われます。

ここでは、ターミナルで提供されている仕組みを用います。ハンドラ関数を呼び出すごとに、_prev_calculated関数アーギュメントの値-OnCalculate()関数の以前の呼び出しで処理されたバーの数、をチェックします。これがゼロであれば、インディケータのすべての値を再計算します。そうでなければ、インデックス_prev_calculated - 1、となっている最後のバーのみを再計算します。

インディケータバッファ値の計算ループは、for演算子で実行されます-本例では、トレンド検知機能TrendDetectorを、再計算されたインディケータバッファごとに呼び出しています。したがって、この関数のみを無効にすることで、トレンドの方向を計算する別のアルゴリズムを実行することができます。このケースでは、インディケータの他のパートは変更されていません(外部パラメータの変更は可能です)。

トレンド検知本体の関数-TrendDetector、について検討してみましょう。

int TrendDetector(int _shift, const double& _price[])
{
  double  current_ma;
  int     trend_direction = 0;

  current_ma = SimpleMA(_shift, MAPeriod, _price);

  if(_price[_shift] > current_ma)
  {
    trend_direction = 1;
  }
  else if(_price[_shift] < current_ma)
  {
    trend_direction = -1;
  }

  return(trend_direction);
}

この関数は以下のタスクを実行します:

  • _shiftアーギュメントで設定されたバーから開始して、単純移動平均を計算する。これには、ライブラリ関数SimpleMAを用います。
  • このバー上の価格の値を、移動平均の値と比較します。
  • 価格の値が移動平均の値より大きい場合は1を返し、そうでない場合、もし価格の値が移動平均より小さい場合は-1を返し、それ以外の場合はゼロを返します。

関数がゼロを返した場合には、トレンドは検出されなかったことを意味します。

インディケータの作業結果は、図2図3で見ることができます。


3.2. 移動平均「ファン」にもとづくトレンドインディケータ

さてそれでは、このインディケータにもとづいて、移動平均「ファン」を用いてトレンドを検知するより複雑なインディケータを、どのように作成することができるかを見ていきます。

このインディケータの完全なソースコードは、本稿に添付のFanTrendDetector.MQ5ファイルに含まれています。

このインディケータが前のものと異なる点は、次の通りです:

  • 3つの移動平均の期間は外部パラメータで設定される:
input int MA1Period = 200; // period value of senior moving average
input int MA2Period = 50;  // period value of medium moving average
input int MA3Period = 21;  // period value of junior moving average
  • 別のTrendDetector関数:
int TrendDetector(int _shift, const double& _price[])
{
  double  current_ma1, current_ma2, current_ma3;
  int     trend_direction = 0;

  current_ma1 = SimpleMA(_shift, MA1Period, _price);
  current_ma2 = SimpleMA(_shift, MA2Period, _price);
  current_ma3 = SimpleMA(_shift, MA3Period, _price);

  if(current_ma3 > current_ma2 && current_ma2 > current_ma1)
  {
    trend_direction = 1;
  }
  else if(current_ma3 < current_ma2 && current_ma2 < current_ma1)
  {
    trend_direction = -1;
  }

  return(trend_direction);
}

この関数は、移動平均をif...else演算子で相互に、またそれらの順序を比較して、正しい順序で配置されているかをチェックします。平均が昇順に配置されていれば1を返し-これは上昇トレンドを意味します。平均が降順に配置されていれば-1を返し-これは下降トレンドを意味します。ifブロックでチェックされる両条件が偽であれば、ゼロを返します(トレンドが検出されなかったことを意味します)。この関数には2つの入力アーギュメント-分析されるバーのバッファ内におけるシフト、および価格の連続をともなうバッファ自身、があります。

他のインディケータ部分は、前のものと同様です。


3.3. ジグザグインディケータにもとづくトレンドインディケータ

ジグザグの断片を用いて極値を決定し、チャールズダウにしたがってトレンドの方向を見つけるインディケータを考えてみましょう。このインディケータの完全なソースコードは、本稿に添付のZigZagTrendDetector.MQ5ファイルに含まれています。

外部変数が、外部インディケータであるZigZagのパラメータ値とともに割り当てられます:

//---------------------------------------------------------------------
//  External parameters:
//---------------------------------------------------------------------
input int   ExtDepth = 5;
input int   ExtDeviation = 5;
input int   ExtBackstep = 3;
//---------------------------------------------------------------------

このインディケータの重要な違いとは-インディケータバッファの数、です。ここでは、ディスプレイバッファの他に2つの計算用バッファを用います。したがって、インディケータコードの設定を適切に変更しました:

#property indicator_buffers  3

そして、2つのバッファを追加します。それらは外部インディケータZigZagから取得する極値を格納します:

double ZigZagHighs[];  // zigzag's upper turnarounds
double ZigZagLows[];   // zigzag's lower turnarounds

インディケータ初期化イベントハンドラを変更する必要もあります-これら2つの追加バッファを計算バッファとして設定します:

//  Buffers to store zigzag's turnarounds
SetIndexBuffer(1, ZigZagHighs, INDICATOR_CALCULATIONS);
SetIndexBuffer(2, ZigZagLows, INDICATOR_CALCULATIONS);

また、OnCalculate関数の計算モードにおいて、ジグザグ断片を読み込んでこれらのバッファに提供しなければなりません。これを、以下のように行います:

//  Copy upper and lower zigzag's turnarounds to buffers:
  CopyBuffer(indicator_handle, 1, 0, _rates_total - _prev_calculated, ZigZagHighs);
  CopyBuffer(indicator_handle, 2, 0, _rates_total - _prev_calculated, ZigZagLows);

//  Loop of calculating the indicator buffer values:
  for(i = start; i < _rates_total; i++)
  {
    TrendBuffer[i] = TrendDetector(i);
  }

TrendDetector関数はこのようになります:

//---------------------------------------------------------------------
//  Determine the current trend direction:
//---------------------------------------------------------------------
//  Returns:
//    -1 - Down trend
//    +1 - Up trend
//     0 - trend is not defined
//---------------------------------------------------------------------
double    ZigZagExtHigh[2];
double    ZigZagExtLow[2];
//---------------------------------------------------------------------
int TrendDetector(int _shift)
{
  int    trend_direction = 0;

//  Find last four zigzag's turnarounds:
  int    ext_high_count = 0;
  int    ext_low_count = 0;
  for(int i = _shift; i >= 0; i--)
  {
    if(ZigZagHighs[i] > 0.1)
    {
      if(ext_high_count < 2)
      {
        ZigZagExtHigh[ext_high_count] = ZigZagHighs[i];
        ext_high_count++;
      }
    }
    else if(ZigZagLows[i] > 0.1)
    {
      if(ext_low_count < 2)
      {
        ZigZagExtLow[ext_low_count] = ZigZagLows[i];
        ext_low_count++;
      }
    }

//  If two pairs of extrema are found, break the loop:
    if(ext_low_count == 2 && ext_high_count == 2)
    {
      break;
    }
  }

//  If required number of extrema is not found, the trend can't be determined:
  if(ext_low_count != 2 || ext_high_count != 2)
  {
    return(trend_direction);
  }

//  Check Dow's condition fulfillment:
  if(ZigZagExtHigh[0] > ZigZagExtHigh[1] && ZigZagExtLow[0] > ZigZagExtLow[1])
  {
    trend_direction = 1;
  }
  else if(ZigZagExtHigh[0] < ZigZagExtHigh[1] && ZigZagExtLow[0] < ZigZagExtLow[1])
  {
    trend_direction = -1;
  }

  return(trend_direction);
}

ここで、最後の4つのジグザグの極値を検索します。検索は、履歴にさかのぼって行われることに注意してください。これが、forループの各検索の繰り返しにおいてインデックスが減少し、ゼロになる理由です。極値が見つかった場合、ダウの法則にしたがったトレンド定義の一貫性のため、相互に比較されます。極値の位置としては2つの可能性があります-上昇トレンドと下降トレンドです。これらの変数は、if...else演算子でチェックされます。


3.4. ADXインディケータにもとづくトレンドインディケータ

ADXインディケータを用いたADXTrendDetectorトレンドインディケータを考えてみます。このインディケータの完全なソースコードは、本稿に添付のADXTrendDetector.MQ5ファイルに含まれています。外部パラメータが、外部インディケータであるADXの値とともに割り当てられます:

//---------------------------------------------------------------------
//      External parameters
//---------------------------------------------------------------------
input int  PeriodADX     = 14;
input int  ADXTrendLevel = 20;

TrendDetector関数はこのようになります:

//---------------------------------------------------------------------
//  Determine the current trend direction:
//---------------------------------------------------------------------
//  Returns:
//    -1 - Down trend
//    +1 - Up trend
//     0 - trend is not defined
//---------------------------------------------------------------------
int TrendDetector(int _shift)
{
  int     trend_direction = 0;
  double  ADXBuffer[ 1 ];
  double  PlusDIBuffer[ 1 ];
  double  MinusDIBuffer[ 1 ];

//  Copy ADX indicator values to buffers:
  CopyBuffer(indicator_handle, 0, _shift, 1, ADXBuffer);
  CopyBuffer(indicator_handle, 1, _shift, 1, PlusDIBuffer);
  CopyBuffer(indicator_handle, 2, _shift, 1, MinusDIBuffer);

//  If ADX value is considered (trend strength):
  if(ADXTrendLevel > 0)
  {
    if(ADXBuffer[0] < ADXTrendLevel)
    {
      return(trend_direction);
    }
  }

//  Check +DI and -DI positions relative to each other:
  if(PlusDIBuffer[0] > MinusDIBuffer[0])
  {
    trend_direction = 1;
  }
  else if(PlusDIBuffer[0] < MinusDIBuffer[0])
  {
    trend_direction = -1;
  }

  return( trend_direction );
}

CopyBuffer()を用いると、外部インディケータADXから、_shiftアーギュメントで与えられるバーの数に応じて、必要なインディケータバッファの値を得ることができます。次に、相互に関連する+DIと-DIの行の位置を分析します。必要があれば、トレンドの強さを考慮します-定義されているよりも弱い場合、トレンドは検出されません。


3.5. NTRTインディケータにもとづくトレンドインディケータ

NRTRにもとづくNRTRTrendDetectorトレンドインディケータのストラクチャーは、前に紹介したものと類似しています。このインディケータの完全なソースコードは、本稿に添付のNRTRTrendDetector.MQ5ファイルに含まれています。

第1の違いは-外部パラメータのブロック、です:

//---------------------------------------------------------------------
//      External parameters:
//---------------------------------------------------------------------
input int     ATRPeriod =  40;    // ATR period, in bars
input double  Koeff     = 2.0;    // Coefficient of ATR value change   
//---------------------------------------------------------------------

第2の違いは-トレンドの方向を検知するTrendDetector関数、です:

//---------------------------------------------------------------------
//      Determine the current trend direction:
//---------------------------------------------------------------------
//  Returns:
//    -1 - Down trend
//    +1 - Up trend
//     0 - trend is not defined
//---------------------------------------------------------------------
int TrendDetector(int _shift)
{
  int     trend_direction = 0;
  double  Support[1];
  double  Resistance[1];

//      Copy NRTR indicator values to buffers::
  CopyBuffer(indicator_handle, 0, _shift, 1, Support);
  CopyBuffer(indicator_handle, 1, _shift, 1, Resistance);

//  Check values of indicator lines:
  if(Support[0] > 0.0 && Resistance[0] == 0.0)
  {
    trend_direction = 1;
  }
  else if(Resistance[0] > 0.0 && Support[0] == 0.0)
  {
    trend_direction = -1;
  }

  return( trend_direction );
}

ここで、インデックスが0および1である、外部インディケータNRTRの2つのバッファから値を読み込みます。上昇トレンドが存在する場合はSupportバッファの値はゼロとは異なり、下降トレンドが存在する場合はResistanceバッファの値はゼロとは異なります。


3.6. 平均足(ローソク足)にもとづくトレンドインディケータ

さて、平均足(ローソク足)を用いるトレンドインディケータを考えてみましょう。

このケースでは、外部インディケータは呼び出さずにローソクを独自に計算します。これによりインディケータのパフォーマンスが向上し、CPUをより重要なタスクのために解放することができます。このインディケータの完全なソースコードは、本稿に添付のHeikenAshiTrendDetector.MQ5ファイルに含まれています。


平均足インディケータは外部パラメータの設定を仮定していないため、input演算子のブロックを削除することができます。主な変更箇所は、インディケータ再計算のハンドラ部分です。ここで、代わりのハンドラ変数を用いて、現在のチャートのすべての価格配列にアクセスできるようにします。

OnCalculate()関数はこのようになります:

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& TickVolume[],
              const long& Volume[], 
              const int& Spread[])
{
  int     start, i;
  double  open, close, ha_open, ha_close;

//  Determine the initial bar for indicator buffer calculation:
  if(_prev_calculated == 0)
  {
    open = Open[0];
    close = Close[0];
    start = 1;
  }
  else
  {
    start = _prev_calculated - 1;
  }

//  Loop of calculating the indicator buffer values:
  for(i = start; i < _rates_total; i++)
  {
//  Heiken Ashi candlestick open price:
    ha_open = (open + close) / 2.0;

//  Heiken Ashi candlestick close price:
    ha_close = (Open[i] + High[i] + Low[i] + Close[i]) / 4.0;

    TrendBuffer[i] = TrendDetector(ha_open, ha_close);

    open = ha_open;
    close = ha_close;
  }

  return(_rates_total);
}

平均足の色を決定するには2つの価格-始値と終値、のみが必要であり、したがってそれらのみをカウントします。

トレンドの方向をTrendDetector関数の呼び出しで決定した後は、平均足(ローソク足)の現在価格を中間変数openおよびcloseに保存します。TrendDetector関数は、たいへんシンプルです。これをOnCalculateに挿入することもできますが、今後のアルゴリズム開発の場合を考えてより汎用的にするため、また煩雑になるため、この関数は保留とします。この関数は以下の通りです:

int TrendDetector(double _open, double _close)
{
  int    trend_direction = 0;

  if(_close > _open)         // if candlestick is growing, then it is the up trend
  {
    trend_direction = 1;
  }
  else if(_close < _open)     // if candlestick is falling, then it is the down trend
  {
    trend_direction = -1;
  }

  return(trend_direction);
}

関数の引数は、平均足(ローソク足)の2つの価格-始値と終値、であり、これらにもとづいてその方向性が決定されます。


4エキスパートにおけるトレンド検知の使用例

異なるインディケータを用いるエキスパートアドバイザを作成しましょう。トレンド検知の異なる方法を用いたエキスパートの結果を比較するのは、興味深いことです。まず、デフォルトパラメータの結果をチェックし、それらを調整して最良のものを探します。

この場合、エキスパートアドバイザの作成目的は-トレンド検知の方法を正確性とスピードにおいて比較すること、です。したがって、すべてのエキスパートアドバイザの作成原則を公式化してみましょう:

  • トレンドが下降から上昇に、または未定義から上昇に変わる際に、買いポジションがオープンします
  • トレンドが上昇から下降に、または未定義から下降に変わる際に、売りポジションがオープンします
  • トレンドの方向がリバースもしくは未定義に変わる際に、ポジションをクローズします
  • 新規バーがオープンする場合(対応するシグナルがある場合)、エキスパートアドバイザはポジションをオープン/クローズしなけばなりません

ここで作成したすべてのトレンドインディケータは、トレンドの方向に関する必要なデータを格納する、ゼロインデックスのインディケータバッファを含みます。エキスパートアドバイザでこれを用いて、ポジションをオープン/クローズするためにシグナルを取得します。

トレーディング関数が必要なため、メタトレーダー5と合わせてインストールされている対応ライブラリをインクルードしました。このライブラリはCTradeクラスと、ポジションやオーダーとともに動作するいくつかのメソッドを含みます。これにより、トレーディング関数によるルーチン処理を単純化することができます。このライブラリは、以下の行に含まれています:

#include <Trade\Trade.mqh>

そこから、2つのメソッド:position openingとclosing、を用います。最初のメソッドにより、所定の方向とボリュームのポジションをオープンすることができます:

PositionOpen(const string symbol, 
             ENUM_ORDER_TYPE order_type,
             double volume, double price,
             double sl, double tp, const string comment )

入力引数は以下となります:

  • symbol-トレード手段名、例えば「EURUSD」など。
  • order_type-ポジションオープニングの方向、ショートやロングなど。
  • volume-ロット内にオープンしたポジションのボリューム、例えば、0.10。
  • price-始値。
  • sl-ストップロス価格。
  • tp-テイクプロフィット価格。
  • comment-ポジションがトレードターミナルに表示されている際に現れるコメント。

第2のメソッドにより、ポジションをクローズすることができます:

PositionClose( const string symbol, ulong deviation )

入力引数は以下となります:

  • symbol-トレード手段名、例えば「EURUSD」など。
  • deviation-ポジションクローズの際に許可されている現在価格からの最大乖離(ポイント)

MATrendDetectorインディケータを用いたエキスパートアドバイザのストラクチャを詳細に見てみましょう。このエキスパートアドバイザの完全なソースコードは、本稿に添付のMATrendExpert.MQ5ファイルに含まれています。このエキスパートの最初の主要ブロックは-外部パラメータの設定ブロックです。

input double Lots = 0.1;
input int    MAPeriod = 200;

エキスパートアドバイザのLotsパラメータ-ポジションがオープンされる際に用いられるロットサイズ。トレンド検知の異なる方法の比較結果を取得するために、マネー管理をともなわないパーマネントロットを用います。他のすべての外部パラメータは、前に議論したトレンドインディケータで用いられています。そのリストおよび目的は、対応するインディケータと完全に同一です。

エキスパートアドバイザの第2の重要なブロックは-エキスパートアドバイザ初期化のイベントハンドラです。

//---------------------------------------------------------------------
//      Initialization event handler:
//---------------------------------------------------------------------
int OnInit()
{
//  Create external indicator handle for future reference to it:
  ResetLastError();
  indicator_handle = iCustom(Symbol(), PERIOD_CURRENT, "Examples\\MATrendDetector", MAPeriod);

// If initialization was unsuccessful, return nonzero code:
  if(indicator_handle == INVALID_HANDLE)
  {
    Print("MATrendDetector initialization error, Code = ", GetLastError());
    return(-1);
  }
  return(0);
}

ここでハンドルを作成してトレンドインディケータを参照し、作成が成功したら、ゼロコードに戻ります。インディケータハンドルの作成に失敗すると(例えば、インディケータがEX5フォーマットでコンパイルされていない場合など)、その旨のメッセージをプリントし、非ゼロコードに戻ります。この例では、エキスパートアドバイザは先の動作を停止してターミナルからアンロードされ、対応するメッセージがジャーナルに出力されます。

エキスパートアドバイザの次のブロックは-エキスパートアドバイザ初期化解除のイベントハンドラ、です。

//---------------------------------------------------------------------
//      Indicator deinitialization event handler:
//---------------------------------------------------------------------
void OnDeinit(const int _reason)
{
//  Delete indicator handle:
  if(indicator_handle != INVALID_HANDLE)
  {
    IndicatorRelease(indicator_handle);
  }
}

ここでインディケータハンドルは削除され、割り当てられたメモリが解放されます。

エキスパートアドバイザの初期化を解放するには、他にどんなアクションも必要とされません。

エキスパートアドバイザの次のメインブロックは-現在のシンボルによる新規ティックについてのイベントハンドラ、です。

//---------------------------------------------------------------------
//  Handler of event about new tick by the current symbol:
//---------------------------------------------------------------------
int    current_signal = 0;
int    prev_signal = 0;
bool   is_first_signal = true;
//---------------------------------------------------------------------
void OnTick()
{
//  Wait for beginning of a new bar:
  if(CheckNewBar() != 1)
  {
    return;
  }

//  Get signal to open/close position:
  current_signal = GetSignal();
  if(is_first_signal == true)
  {
    prev_signal = current_signal;
    is_first_signal = false;
  }

//  Select position by current symbol:
  if(PositionSelect(Symbol()) == true)
  {
//  Check if we need to close a reverse position:
    if(CheckPositionClose(current_signal) == 1)
    {
      return;
    }
  }

//  Check if there is the BUY signal:
  if(CheckBuySignal(current_signal, prev_signal) == 1)
  {
    CTrade  trade;
    trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, Lots, SymbolInfoDouble(Symbol(), SYMBOL_ASK ), 0, 0);
  }

//  Check if there is the SELL signal:
  if(CheckSellSignal(current_signal, prev_signal) == 1)
  {
    CTrade  trade;
    trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, Lots, SymbolInfoDouble(Symbol(), SYMBOL_BID ), 0, 0);
  }

//  Save current signal:
  prev_signal = current_signal;
}

エキスパートアドバイザで用いられる補助関数について、考えてみます。

まず、ここで用いるエキスパートアドバイザは、チャートで他の新規バーを開くためにシグナルをチェックする必要があります。このために、CheckNewBar関数が用いられます:

//---------------------------------------------------------------------
//  Returns flag of a new bar:
//---------------------------------------------------------------------
//  - if it returns 1, there is a new bar
//---------------------------------------------------------------------
int CheckNewBar()
{
  MqlRates  current_rates[1];

  ResetLastError();
  if(CopyRates(Symbol(), Period(), 0, 1, current_rates)!= 1)
  {
    Print("CopyRates copy error, Code = ", GetLastError());
    return(0);
  }

  if(current_rates[0].tick_volume>1)
  {
    return(0);
  }

  return(1);
}

新規バーが存在するかどうかは、ティックボリュームの値により決定されます。新規バーをオープンする際、そのボリュームはゼロに初期化されます(クォータが存在していなかったため)。新規バーが現れると、そのサイズが1になります。

この関数は、1つのエレメントで構成される、MqlRatesストラクチャのcurrent_rates[]配列を作成し、また現在価格とボリュームの情報をそこにコピーして、その後、ティックボリューム値をチェックします。

ここで用いる現在のシンボルによる新規ティックのイベントハンドラでは、この関数を以下のように用います:

//  Wait for beginning of a new bar:
if(CheckNewBar()!= 1)
{
  return;
}

そこで新規バーが開き、現在のトレンドの方向についてのシグナルを取得することができます。これを、以下のように行います:

//  Get signal to open/close position:
  current_signal = GetSignal();
  if(is_first_signal == true)
  {
    prev_signal = current_signal;
    is_first_signal = false;
  }

トレンド変動を追跡する必要があるため、前のバー上のトレンドの値を記憶する必要があります。上記のコード部ではこのために、prev_signal変数を用いています。また、これが最初のシグナルである(以前にまだシグナルが存在していない)ことを合図するフラグも、用いなければなりません。これがis_first_signal変数となります。このフラグがtrueの値を持つ場合、prev_signal変数を初期値へと初期化します。

ここで、インディケータから取得された現在のトレンドの方向を返すGetSignal関数を用います。これは、以下のようになります:

//---------------------------------------------------------------------
//      Get signal to open/close position:
//---------------------------------------------------------------------
int GetSignal()
{
  double    trend_direction[1];

//  Get signal from trend indicator:
  ResetLastError();
  if(CopyBuffer(indicator_handle, 0, 0, 1, trend_direction) != 1)
  {
    Print("CopyBuffer copy error, Code = ", GetLastError());
    return(0);
  }

  return((int)trend_direction[0]);
}

トレンドインディケータはゼロバッファから、1つのエレメントで構成される配列trend_directionにコピーされます。そしてこの配列エレメントの値を、関数へ返します。また、コンパイラの警告を避けるため、doubleタイプがintタイプに指定されます。

新しいポジションをオープンする前に、以前にオープンしている逆のポジションをクローズする必要があります。同じ方向についても、すでにオープンしているポジションがないかチェックする必要があります。これらすべては、以下のコード部で行われます:

//  Select position by current symbol:
  if(PositionSelect(Symbol()) == true)
  {
//  Check if we need to close a reverse position:
    if(CheckPositionClose(current_signal) == 1)
    {
      return;
    }
  }

ポジションにアクセスするには、まずそれを選択する必要があります-これは現在のシンボルにPositionSelect() 関数を用いて行われます。この関数が真を返す場合は、ポジションが存在してその選択に成功しているため、それを操作することができます。

逆ポジションをクローズするには、CheckPositionClose関数が用いられます:

//---------------------------------------------------------------------
//  Check if we need to close position:
//---------------------------------------------------------------------
//  Returns:
//    0 - no open position
//    1 - position already opened in signal's direction
//---------------------------------------------------------------------
int CheckPositionClose(int _signal)
{
  long    position_type = PositionGetInteger(POSITION_TYPE);

  if(_signal == 1)
  {
//  If there is the BUY position already opened, then return:
    if(position_type == (long)POSITION_TYPE_BUY)
    {
      return(1);
    }
  }

  if(_signal==-1)
  {
//  If there is the SELL position already opened, then return:
    if( position_type == ( long )POSITION_TYPE_SELL )
    {
      return(1);
    }
  }

//  Close position:
  CTrade  trade;
  trade.PositionClose(Symbol(), 10);

  return(0);
}

まず、ポジションがトレンド方向にオープンしているかどうかをチェックします。その場合、関数は1を返し、現在のポジションはクローズされません。逆のトレンド方向にオープンしている場合は、クローズする必要があります。これは、上記PositionCloseメソッドで行われます。オープンポジションがない場合は、ゼロを返します。

すべての必要なチェックと、既存のポジションへのアクションが行われると、今度は、新規シグナルの存在をチェックしなければなりません。これは、以下のコード部で行われます:

//  Check if there is the BUY signal:
if(CheckBuySignal(current_signal, prev_signal)==1)
{
  CTrade  trade;
  trade.PositionOpen(Symbol(), ORDER_TYPE_BUY, Lots, SymbolInfoDouble(Symbol(), SYMBOL_ASK), 0, 0);
}

買いシグナルがある場合は、所定ボリュームのロングポジションを、現在価格 SYMBOL_ASK でオープンします。すべてのポジションが逆のシグナルによりクローズされるため、テイクプロフィットとストップロスは用いられません。エキスパートアドバイザは「いつでもマーケットの中にあります」。

リアルトレードでは、例えばDCサーバとの接続が失われたり、その他の強制的な不可抗力の条件下にあるなど、予見できない状況においては、保護的なストップロスを用いることが推奨されています。

売りシグナルについても、すべて同様です:

//  Check if there is the SELL signal:
if(CheckSellSignal(current_signal, prev_signal) == 1)
{
  CTrade  trade;
  trade.PositionOpen(Symbol(), ORDER_TYPE_SELL, Lots, SymbolInfoDouble(Symbol(), SYMBOL_BID), 0, 0);
}

唯一の違いは、売り価格-SYMBOL_BID、のみです。

シグナルの存在は、買いの場合はCheckBuySignal関数で、売りの場合はCheckSellSignal関数でチェックすることができます。これらの関数はたいへんシンプルで、わかりやすいものです:

//---------------------------------------------------------------------
//  Check if signal has changed to BUY:
//---------------------------------------------------------------------
//  Returns:
//    0 - no signal
//    1 - there is the BUY signal
//---------------------------------------------------------------------
int CheckBuySignal(int _curr_signal, int _prev_signal)
{
//  Check if signal has changed to BUY:
  if((_curr_signal==1 && _prev_signal==0) || (_curr_signal==1 && _prev_signal==-1))
  {
    return(1);
  }

  return(0);
}

//---------------------------------------------------------------------
//  Check if there is the SELL signal:
//---------------------------------------------------------------------
//  Returns:
//    0 - no signal
//    1 - there is the SELL signal
//---------------------------------------------------------------------
int CheckSellSignal(int _curr_signal, int _prev_signal)
{
//  Check if signal has changed to SELL:
  if((_curr_signal==-1 && _prev_signal==0) || (_curr_signal==-1 && _prev_signal==1))
  {
    return(1);
  }

  return(0);
}

ここで、トレンドが逆方向に変わっているか、あるいはトレンドの方向が出現しているかをチェックします。これらのいずれかの条件が満たされると、関数がシグナルの存在を返します。

一般に、エキスパートアドバイザのそのようなスキームはたいへん汎用的なストラクチャを提供しているため、より複雑なアルゴリズムに対応するためのアップグレードや拡張が簡単にできます。

他のエキスパートアドバイザもまったく同様に構築されています。顕著な違いは、外部パラメータについてのみ存在します-それらは、用いられるトレンドインディケータに対応しなければならず、インディケータハンドルを作成する際に引数として渡されなければなりません。

履歴データに関する最初のエキスパートアドバイザの結果について、考えてみましょう。期間が01.04.2004から06.08.2010までのEURUSD取引履歴を、日足バーで用います。デフォルトパラメータを用いて、ストラテジーテスターのエキスパートアドバイザを実行すると、以下の結果が得られます:

図8MATrendDetectorインディケータを用いた、エキスパートアドバイザの検証結果

図8MATrendDetectorインディケータを用いた、エキスパートアドバイザの検証結果

ストラテジーテスターレポート
MetaQuotes-Demo (Build 302)

設定
エキスパート: MATrendExpert
シンボル: EURUSD
期間: Daily (2004.04.01 - 2010.08.06)
入力: Lots=0.100000
MAPeriod=200
仲介者: MetaQuotes Software Corp.
通貨: USD
当初預金: 10 000.00

結果
バー: 1649 ティック: 8462551
ネット利益合計: 3 624.59 グロス利益: 7 029.16 グロス損失: -3 404.57
プロフィットファクター: 2.06 ペイオフ見込: 92.94
リカバリーファクター: 1.21 シャープ係数: 0.14

残高ドローダウン:
残高ドローダウン(絶対): 2 822.83 残高ドローダウン(最高): 2 822.83 (28.23%) 残高ドローダウン(相対): 28.23% (2 822.83)
資産ドローダウン:
資産ドローダウン(絶対): 2 903.68 資産ドローダウン(最高): 2 989.93 (29.64%) 資産ドローダウン(相対): 29.64% (2 989.93)

トレード合計: 39 ショートトレード(勝率パーセント) 20 (20.00%) ロングトレード(勝率パーセント) 19 (15.79%)
取引合計: 78 収益トレード(全体に占めるパーセント): 7 (17.95%) 損失トレード(全体に占めるパーセント): 32 (82.05%)
最大収益トレード: 3 184.14 最大損失トレード(全体に占めるパーセント): -226.65
平均収益トレード: 1 004.17 平均損失トレード(全体に占めるパーセント): -106.39
最大連続利得(ドル): 4 (5 892.18) 最大連続損失(ドル): 27 (-2 822.83)
最大連続収益(カウント): 5 892.18 (4) 最大連続損失(カウント): -2 822.83 (27)
平均連続利得: 2 平均連続損失: 8

検証の初期から22.09.2004までのセクションを除けば、全体的には良い傾向です。このセクションが将来も繰り返される保証はありません。この期間のチャートを見ると、限られたレンジで横方向の動きが優勢であったことがわかります。このような条件下では、ここで紹介した簡単なトレンドエキスパートはそれほど役立ちませんでした。ここで、この期間の取引を表示した図を見てみます:

図9横方向の動きをともなうセクション

図9横方向の動きをともなうセクション

また、SMA200移動平均もチャートに表示します。

そこで、同じ間隔、デフォルトパラメータを使用するいくつかの移動平均を含むインディケータを用いた、より「高度な」エキスパートアドバイザがどのように見えるかを、確認してみましょう。

図10FanTrendDetectorインディケータを用いたエキスパートアドバイザの検証結果

図10FanTrendDetectorインディケータを用いたエキスパートアドバイザの検証結果

ストラテジーテスターレポート
MetaQuotes-Demo (Build 302)

設定
エキスパート: FanTrendExpert
シンボル: EURUSD
期間: Daily (2004.04.01 - 2010.08.06)
入力: Lots=0.100000
MA1Period=200
MA2Period=50
MA3Period=21
仲介者: MetaQuotes Software Corp.
通貨: USD
当初預金: 10 000.00

結果
バー: 1649 ティック: 8462551
ネット利益合計: 2 839.63 グロス利益: 5 242.93 グロス損失: -2 403.30
プロフィットファクター: 2.18 ペイオフ見込: 149.45
リカバリーファクター: 1.06 シャープ係数: 0.32

残高ドローダウン:
残高ドローダウン(絶対): 105.20 残高ドローダウン(最高): 1 473.65 (11.73%) 残高ドローダウン(相対): 11.73% (1 473.65)
資産ドローダウン:
資産ドローダウン(絶対): 207.05 資産ドローダウン(最高): 2 671.98 (19.78%) 資産ドローダウン(相対): 19.78% (2 671.98)

トレード合計: 19 ショートトレード(勝率パーセント) 8 (50.00%) ロングトレード(勝率パーセント) 11 (63.64%)
取引合計: 38 収益トレード(全体に占めるパーセント): 11 (57.89%) 損失トレード(全体に占めるパーセント): 8 (42.11%)
最大収益トレード: 1 128.30 最大損失トレード(全体に占めるパーセント): -830.20
平均収益トレード: 476.63 平均損失トレード(全体に占めるパーセント): -300.41
最大連続利得(ドル): 2 (1 747.78) 最大連続損失(ドル): 2 (-105.20)
最大連続収益(カウント): 1 747.78 (2) 最大連続損失(カウント): -830.20 (1)
平均連続利得: 2 平均連続損失: 1

かなり良くなりました!旧来のエキスパートが過去にあきらめてきた「課題」についてのセクションを見ると、この図は次のようになるでしょう:

図11横方向の動きをともなうセクションの、FanTrendExpertによる検証結果

図11横方向の動きをともなうセクションの、FanTrendExpertによる検証結果

これを図9と比較すると-トレンド変化の偽のアラームが減っていることが、明らかに分かります。しかし取引数は半分に減っており、これはきわめてロジカルな成り行きです。両方のエキスパートアドバイザの資産/残高曲線を分析すると、多くの取引が、最大利益を得る観点でいえば最適未満でクローズしていたことがわかります。したがって、次のエキスパートアドバイザアップグレードは-取引クローズのアルゴリズムの実装、です。しかしこれは、本稿の範囲を超えてしまいます。読者の皆さんが、独自にこれをできるかもしれません。


5エキスパートアドバイザの検証結果

今回紹介したエキスパートを検証しましょう。ペアEURUSD、タイムフレームD1で、1993年から2010年までの間に利用可能なすべての履歴レンジの結果を以下に示します。

図12MATrendExpertの検証

図12MATrendExpertの検証

図13FanTrendExpertの検証

図13FanTrendExpertの検証

図14ADXTrendExpert(ADXTrendLevel = 0)の検証

図14ADXTrendExpert(ADXTrendLevel = 0)の検証

図15ADXTrendExpert(ADXTrendLevel = 20)の検証

図15ADXTrendExpert(ADXTrendLevel = 20)の検証

図16NRTRTrendExpertの検証

図16NRTRTrendExpertの検証

図17平均足の検証

図17平均足の検証

検証結果について考えてみましょう。

先導役として、2つの最も一般的なエキスパートアドバイザ-移動平均と、移動平均「ファン」、があります。確かに、これらのエキスパートは単純に、過去の価格の連続を平衡化して用いることにより、トレンドに従ったルールに(そして、ゆえに価格に)最も近いものとなっています。今回は期間200というかなり「大量の」移動平均を用いるため、マーケットボラティティのインパクトはほぼ消滅しているように見えます。

エキスパートアドバイザからの取引数が少ないことは、欠点ではありません。なぜなら、200日のトレンドに従うと、ポジション保留時間が数か月にもわたる可能性があるからです。MATrendExpertがどのようにトレンド領域を変えるか、残高がどこで増加するか、フラットになるか(エキスパートの観点で)、マネーがどこで失われるか、興味深く検証できます。

ADXインディケータによるトレンド検知方法も、良好な結果を提供しています。PeriodADXの値が17へと多少変更されていますが、これによって、履歴の間でより統一的な結果が提示されています。トレンド強度によるフィルタ効果は、それほど有意義ではありません。おそらく、ADXTrendLevelパラメータを調整するか、もしくは現在のマーケットボラティリティに応じて動的に設定する必要があります。いくつかのドローダウン期間があり、したがって、残高曲線を同一にする追加作業が必要です。

NRTRインディケータは、デフォルトの設定を用いると、検証期間全体についても、ランダムに選択された長期インターバルの間でも、実際には収益性ゼロであったことを示しました。このことから、このトレンド検知方法が、ある程度まで安定したものであることがわかります。おそらく、パラメータの調整を行えばこのエキスパートアドバイザが有益なものとなるでしょう。すなわち、最適化が必要とされています。

平均足にもとづくエキスパートアドバイザは、明らかに有益ではありません。履歴については良さそうに見えますが、おそらくは、リアルタイムでは再描画を行うため検証結果が理想とかけ離れます。多分、このインディケータの平衡化されたバージョン-再描画をそれほど行わない平衡化平均足、を用いると、より良い結果が実現されるでしょう。

間違いなく、すべてのエキスパートアドバイザは、動的にストップレベルを引き出しターゲットレベルを作成するオープンポジションを実行するシステムからの恩恵を受けるでしょう。また、ドローダウンを最小に食い止め、長期的に利益を増やすことができるキャピタル管理システムの登場も待たれます。


結論

このように、トレンドを検知するコードを記述することは難しくありません。ここで重要なことは-市場原理に則り、有効かつ道理にかなったアイディア、です。そして、これらの法則がより重要になれば、こうした法則にもとづくトレーディングシステムについて、より自信を持つことができるでしょう-そのシステムは、ちょっと動かしただけですぐに不具合が出たりすることがないのです。

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

添付されたファイル |
experts.zip (9.35 KB)
indicators.zip (7.82 KB)
カスタムグラフィックコントロールパート2コントロールライブラリ カスタムグラフィックコントロールパート2コントロールライブラリ
「カスタムグラフィックコントロール」シリーズ第2弾となる本稿では、プログラム(エキスパートアドバイザ、スクリプト、インディケータ)とユーザ間のインタラクションにおいて起こる主要な問題を処理するコントロールライブラリを紹介します。このライブラリにはたいへん多くのクラス(CInputBox、CSpinInputBox、CCheckBox、CRadioGroup、CVSсrollBar、CHSсrollBar、CList、CListMS、 CComBox、CHMenu、CVMenu、CHProgress、CDialer、CDialerInputBox、CTable)と、それらの使用例が含まれています。
Meta Trader5がもたらす新たな機会 Meta Trader5がもたらす新たな機会
Meta Trader4は世界中のトレーダーから好評を博し、これ以上に望むものはないように思われていました。高い処理速度、安定性、インディケータ記述の広大な可能性、エキスパートアドバイザー、情報提供型トレーディングシステム、そして100以上の異なるブローカーから選択できることにより-このターミナルは他に類を見ないほどに優れたものでした。しかし時は流れ、今や、Meta Trader4かMeta Trader5かの選択を迫られる時代となりました。本稿では、時代が求めるこの第5世代ターミナルの主な違いについて述べます。
MQL5クラウドネットワークを使った速度アップ MQL5クラウドネットワークを使った速度アップ
お使いになられているパソコンのコア数はいくつでしょうか?トレーディングストラテジーの最適化のために使えるパソコンは何台あるでしょうか?ここではMQL5クラウドネットワークを使い、マウスをクリックするだけで世界中のコンピューターパワーを利用して計算を早くするための方法を紹介します。"時は金なり"ということわざは、近年より話題となってきました。重要な計算を何十時間もあるいは何日間も待つことはできませこん。
William Blauの指数MQL5におけるトレーディングシステムパート 1:インディケータ William Blauの指数MQL5におけるトレーディングシステムパート 1:インディケータ
本稿はWilliam Blau著"Momentum, Direction, and Divergence"に述べられるインディケータを紹介します。William Blau氏の手法により迅速に正確に価格曲線の変動を概算し、価格変動の傾向と変換点を判断し、価格ノイズを除去することができるようになりました。一方でまた、トレンドの終了と価格変動の逆転を示しながらマーケットの買いすぎ/売りすぎ状態、シグナルを検出することができます。