English Deutsch
preview
MQL5でゾーン回復マーチンゲール戦略を開発する

MQL5でゾーン回復マーチンゲール戦略を開発する

MetaTrader 5トレーディング | 14 8月 2024, 11:37
50 0
Kikkih25
Kikkih25

はじめに

この記事では、MetaTrader 5 (MT5)用のMetaQuotes言語5(MQL5)で、ゾーン回復マーチンゲールFX取引戦略のエキスパートアドバイザー(EA)を順を追って作成していきます。ゾーン回復マーチンゲール戦略は、負けポジションに対抗することを目的とした一般的な戦略であり、負けポジションを打ち消す、取引量がわずかに多い反対側のポジションを建てます。基本的にトレンドフォロー戦略で、相場の方向は気にせず、ある時点で相場が下降または上昇トレンドになり、最終的に特定の目標にヒットすることを期待します。MQL5では、このシステムについて議論するだけでなく、自動化することもできます。

このジャーニーでは以下のトピックを取り上げます。

  1. ゾーン回復戦略の定義
  2. ゾーン回復戦略の説明
  3. MQL5での実装
  4. バックテスト結果
  5. 結論


ゾーン回復戦略の定義

ゾーン回復取引戦略は、FX取引で主に使用される洗練された手法で、損失をコントロールし、軽減します。トレーダーが市場が変動すると予想する正確な価格帯を判定することが、この手法の核となる考え方です。トレーダーは、取引が不利に動き、あらかじめ決められた損失ゾーンに達したときに損失でポジションをクローズするのとは対照的に、回復ゾーンを構築するために一連の反対売買を開始します。理想的には、これらの取引は、回復ゾーンに戻る動きによって、ポジション全体がブレイクイーブン、あるいは利益で決済されるように配置されるべきです。

この手法は主にヘッジと平均化に重点を置いています。市場が最初の取引に逆行した場合、トレーダーは同サイズの反対ポジションを作り、ヘッジとして機能させます。相場が下降し続けた場合、エントリ価格を平均化するために、重要な間隔で新しい取引を開始します。この取引は、市場が平均に戻ったときに、回復取引による利益の合計が最初の取引による損失と等しくなるように設計されています。このアプローチでは、リスク管理戦略を明確にする必要があります。というのも、保有資産を積み上げる可能性があるため、必要証拠金が高くなり、不安定な市場環境にさらされる可能性があるからです。

ゾーン回復戦略は、相場の方向性を正確に予測することなく、負け取引を成功取引に変えることができるのが大きな利点です。これにより、トレーダーは市場の乱高下や反転から利益を得ることができ、不利なトレンドを反発のチャンスに変えることができます。

ゾーン回復の取引方法は、リスク管理戦略と市場力学を十分に理解した熟練トレーダーに最も適しています。これはトレーダーの道具箱の中で強力な道具であり、価格変動が頻繁に起こる不安定な市場で特に役立ちます。失敗した取引を挽回し、利益を上げる能力はありますが、その複雑さと関連する危険性から、熟慮した計画と計算された実行が必要となります。


ゾーン回復戦略の説明

市場分析に基づき、ゾーン回復の取引方法は、最初のポジションを建てることから始まります。市場が上昇すると信じて買いポジションを建てたトレーダーを想像してください。上昇トレンドからお金を稼ぐことが第一の目標です。このトレーダーは、市場がプラスに動き、価格が所定の利益目標まで上昇した場合、ポジションを決済し、利益を確保します。複雑な戦略を使用しなくても、このシンプルな戦略で市場の有利な動きから利益を得ることができます。

一方、ゾーン回復方式では、市場が当初のロングポジションに反して動き、あらかじめ決められた損失ポイントに達した場合、損失軽減メカニズムが起動します。この時点で、トレーダーは買いポジションを損切りするのではなく、より大きなロットサイズ、通常は前のロングポジションの2倍のサイズで売りポジションを建てます。最初の取引による損失と新しいポジションから得られる可能性のある利益を相殺することで、この反対取引はヘッジを確立しようとします。市場固有の振動を利用し、この戦略は反転、あるいは少なくとも所定のレンジ内での安定を予想します。

相場が進むにつれて、売りたてポジションに目を光らせます。最初の買いポジションと多めの売りポジションの複合的な影響により、理想的には、市場が下落を続け、別の所定のポイントに達した場合、損益分岐点または利益状況となります。その後、両方のポジションを決済し、より大きな次の取引で得た利益で最初の取引の損失を相殺することができます。全ポジションが回復ゾーン内で利益を上げて決済されることを保証するために、この戦略では正確な計算とタイミングが必要となります。


MQL5での実装

MQL5で、ゾーン回復レベル(通常は4つ)を可視化するEAを作成するには、まずレベルを定義する必要があります。取引システムを可視化する上で極めて重要であるため、できるだけ早い段階で定義しておくのです。これはキーワード#defineを使用することで実現できます。#defineはMQL5に内蔵されているディレクティブで、定数にニーモニック名を割り当てるのに使用することができます。これは以下の通りです。

#define ZONE_H "ZH"            // Define a constant for the high zone line name
#define ZONE_L "ZL"            // Define a constant for the low zone line name
#define ZONE_T_H "ZTH"         // Define a constant for the target high zone line name
#define ZONE_T_L "ZTL"         // Define a constant for the target low zone line name

ここでは、ゾーン境界の定数を定義します。ZONE_Hを「ZH」(Zone High)、ZONE_Lを「ZL」(Zone Low)、ZONE_T_Hを「ZTH」(Zone Target High)、ZONE_T_Lを「ZTL」(Zone Target Low)とします。この定数は、システムにおけるそれぞれのレベルを表しています。

定義が終わったら、取引ポジションを建てる必要があります。ポジションを建てる最も簡単な方法は、取引インスタンスを含めることであり、通常はポジション専用の別のファイルを含めることで実現します。includeディレクティブを使用して、取引操作のための関数を含む取引ライブラリをインクルードします。

#include <Trade/Trade.mqh>
CTrade obj_trade;

まず、インクルードしたいファイルがインクルードフォルダに含まれていることを示すために角括弧を使用し、Tradeフォルダを指定します。次に通常のスラッシュまたはバックスラッシュを使用し、目標ファイル名(この場合はTrade.MQH)を指定します。CTradeは取引操作を処理するためのクラスで、obj_tradeはこのクラスのインスタンスであり、通常はCTradeクラスから生成されるポインタオブジェクトで、クラスのメンバー変数へのアクセスを提供します。

その後、ポジションを建てるためのシグナルを生成する制御ロジックが必要です。私たちの場合はRSI(相対力指標)を使用していますが、ご自分で適切と考えるものを使用してください。

int rsi_handle;                // Handle for the RSI indicator
double rsiData[];              // Array to store RSI data
int totalBars = 0;             // Variable to keep track of the total number of bars

rsi_handleはRSI (Relative Strength Index)指標のリファレンスを格納し、OnInit関数で初期化され、EAがRSI値を取得できるようにします。rsiData配列は、CopyBufferを使用して取得されたこれらのRSI値を格納し、RSI閾値に基づいて売買シグナルを決定するために使用されます。totalBars変数は、チャート上のバーの合計数を追跡し、取引ロジックが新しいバーごとに1回のみ実行されることを保証し、1つのバー内で複数回実行されることを防ぎます。これらの変数を組み合わせることで、EAは適切な執行タイミングを維持しながら、RSI値に基づいて売買シグナルを生成することができます。

最後に、指標値を定義した後、指標シグナル発生レベルとゾーンレベルを定義します。

double overBoughtLevel = 70.0; // Overbought level for RSI
double overSoldLevel = 30.0;   // Oversold level for RSI
double zoneHigh = 0;           // Variable to store the high zone price
double zoneLow = 0;            // Variable to store the low zone price
double zoneTargetHigh = 0;     // Variable to store the target high zone price
double zoneTargetLow = 0;      // Variable to store the target low zone price

ここでもoverBoughtLevelとoverSoldLevelという2つのdouble変数を定義し、それぞれ70と30に初期化します。これらは、シグナル生成のための極端なレベルとして機能します。さらに、zoneHigh、zoneLow、zoneTradegetHigh、zoneTargetLowの4つのdoubleデータ型変数を追加定義し、それらをゼロに初期化します。これらは後のコードで、回復の設定レベルを保持します。

ここまでで、システムにとって重要なグローバル変数をすべて定義しました。これで、EAが初期化されるたびに呼び出されるOnInitイベントハンドラに自由に移行できます。この例では、後にさらなる分析のためにデータをコピーする指標ハンドルを初期化する必要があります。指標を初期化するには、組み込み関数を使用し、正しいパラメータを与えてハンドルを返します。 

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Initialize the RSI indicator
   rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE);
   //--- Return initialization result
   return(INIT_SUCCEEDED);
  }

EA初期化解除イベントハンドラでは、指標のデータを解放し、保存されているデータも解放する必要があります。これは、指標をコンピュータのメモリから解放してリソースを節約するためです。指標が再び使用されることはなく、使用される場合は、指標のハンドルとデータがEAの初期化時に作成されます。 

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   //--- Remove RSI indicator from memory
   IndicatorRelease(rsi_handle);
   ArrayFree(rsiData); // Free the RSI data array
  }

次にOnTickイベントハンドラに移りますが、これは気配値の変化であるティックごとに呼び出される関数です。これはまた私たちの心臓の関数または部分です。取引戦略を成功させるための重要なコードスニペットがすべて含まれているからです。ポジションを建てるので、Ask価格とBid価格を定義し、分析目的で最新の値を使用できるようにする必要があります。 

   double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);

銘柄情報をdoubleデータ型として使用することで、Ask価格とBid価格を定義すると、さらなる分析のために最新の価格相場を取得するのに役立ちます。使用する関数は、2つのバリアントを含むオーバーロード関ですが、ここでは2つのパラメータを取る最初のものを使用します。最初のパラメータは銘柄です。_Symbolを使用して現在のチャート上の銘柄を自動的に取得し 、SYMBOL_ASKを使用してdouble型の列挙を取得します。

厳密にいえば各ティックで生成される最新の価格相場を取得した後、準備は完了です。続行して、EAのゾーン範囲とゾーン目標を定義するだけです。これらをdoubleデータ型の変数として定義し、_Point変数と掛け合わせます。この変数には、相場通貨における現在の銘柄のポイントサイズが格納されています。

   double zoneRange = 200*_Point;
   double zoneTarget = 400*_Point;

最後に、回復ポジションを正しく建てるためのユーティリティ変数が必要です。今回はそのうちの4つを定義します。LastDirectionは最後に建てたポジションの種類を保持し、買い注文と売り注文が交互に出るようにします。売りは-1、買いは+1とするつもりですが、これは任意の値であり、好きに変更したり実装したりできます。次に、回復ロットが使用され、ゼロに初期化されます。その機能は、ゾーン回復システムがまだ動作している場合、計算された回復ポイントを保持して保存し、次の取引量を見失わないようにすることです。それでも、isBuyDoneとisSellDoneという2つのブール変数があり、一度に複数のポジションが建てられないようにするためのフラグを格納します。変数はすべて静的で、自分たちで個別に更新しない限り更新されないようになっています。これは、staticキーワードで宣言されたローカル変数が、関数のライフタイムを通じてその値を保持するからです。したがって、次のOnTick関数を呼び出すたびに、ローカル変数には前回の呼び出し時の値が格納されます。

   static int lastDirection = 0; //-1 = sell, 1 = buy
   static double recovery_lot = 0.0;
   static bool isBuyDone = false, isSellDone = false;

すべてのユーティリティ変数を定義した後、ポジションを建てて、そこから後にゾーン回復ロジックを実装します。ユーザーの好みに応じて完全に変更可能なRSI指標を使用してこれを実現するつもりです。そのため、先に進んでエントリテクニックを適用してください。さて、リソースを節約するために、ティックごとではなく、ローソク足ごとに指標からデータを取得したいと思います。これは次のように達成できます。

   int bars = iBars(_Symbol,PERIOD_CURRENT);
   if (totalBars == bars) return;
   totalBars = bars;

ここでは、整数変数barsを定義し、それをチャート上の現在のバーの数で初期化します。この初期化には、iBars組み込み関数を使用し、銘柄と期間の2つの引数を取ります。そして、以前に定義したバーの数と現在のバーの数が等しいかどうかを確認します。等しければ、まだ現在のバー上にいるので、戻ります。つまり、操作を中断し、呼び出し元のプログラムに制御を返します。2つの変数が一致しなければ、新しいローソク足に移行しているので、続行できます。そこで、totalBarsの値を現在のバーに更新し、次のティックでtotalBars変数の値が更新されるようにします。 

ゾーン回復が効果を発揮し、ポジションを1つだけ建てて管理するためには、インスタンスごとにポジションを1つ建てる必要があります。したがって、ポジションの数が1より大きい場合は、他のポジションを追加する必要はなく、早めに戻るだけです。これもまた以下の通りです。 

このポイントに戻らなければ、まだポジションを持っていないので、ポジションを建てることができます。そこで、指標ハンドルからデータをコピーし、さらに分析するために指標データ配列に格納します。これはCopyBuffer関数によって実現されます。 

   if (PositionsTotal() > 0) return;
   if (!CopyBuffer(rsi_handle,0,1,2,rsiData)) return;

ご覧のように、CopyBuffer関数は整数を返すオーバーロード関数です。セキュリティ上の理由から、if文を使用して要求されたデータが返ってくるかどうかを確認します。返ってこなければ、返すデータが足りないということになり、それ以上の分析はできません。この関数が何をするのかを見てみましょう。引数は5つです。1つ目はデータをコピーする指標ハンドル、2つ目は指標のバッファ番号です。この場合は0ですが、使用している指標によって完全に異なる可能性があります。3つ目は開始位置、つまりデータをコピーするバーのインデックスです。ここでは、チャート上の現在のバーの前のバーから開始することを示すために1を使用しています。4つ目はカウント(保存するデータ数)です。詳細な分析はしないので、ここでは2つで十分です。最後に、検索されたデータストレージの目標配列を提供します。

指標ハンドルからデータを取得した後、取引目的、つまりシグナル生成のためにデータを使用します。まず、買いのシグナルを探します。これは、if文を使用して買いポジションを開くことで実現します。 

   if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel){
      obj_trade.Buy(0.01);

保存された配列のインデックス1のデータが定義された売られすぎのレベルより小さく、インデックス0のデータが売られすぎのレベルより大きければ、RSIラインと売られすぎのレベルがクロスオーバーしたことになり、買いシグナルが出たことになります。次に、取引オブジェクトとドット演算子を利用して、CTradeクラスに含まれる買いメソッドにアクセスします。この例では、出来高0.01の買いポジションを建て、ストップロスやテイクプロフィットなどの残りのパラメータは無視します。ストップ ロス レベルでポジションをクローズする必要のないx=zone回復戦略を実装しているため、これらのパラメータを使用するとシステムが意図したとおりに動作しなくなるためです。

しかし、ゾーンの回復レベルを設定するには、そのプロパティにアクセスするためにポジションのチケットが必要です。チケットを取得するには、前回建てたポジションの結果順を使用します。チケットを取得した後、それが0より大きい(ポジションを建てるのに成功したことを示す)ことを確認しチケットによってポジションを選択します。チケットで選択できる場合は、ポジションのプロパティを取得できますが、私たちが関心があるのは始値だけです。始値から次のようにポジションを設定します。買いポジションの建値はゾーン高値であり、ゾーン安値を得るには、ゾーン高値からゾーン範囲を引くだけです。ゾーン目標の高値と安値については、それぞれゾーン高値にゾーン目標を加算し、ゾーン安値からゾーン目標を減算するだけです。最後に、正確を期すため、値を記号の桁数に正規化すれば完了です。

      ulong pos_ticket = obj_trade.ResultOrder();
      if (pos_ticket > 0){
         if (PositionSelectByTicket(pos_ticket)){
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            zoneHigh = NormalizeDouble(openPrice,_Digits);
            zoneLow = NormalizeDouble(zoneHigh - zoneRange,_Digits);
            zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget,_Digits);
            zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget,_Digits);

ここまでで、レベルを設定することはできますが、チャート上には表示されません。これを実現するために、定義された4つのレベルをチャート上で可視化するために使用する関数を作成します。

            drawZoneLevel(ZONE_H,zoneHigh,clrGreen,2);
            drawZoneLevel(ZONE_L,zoneLow,clrRed,2);
            drawZoneLevel(ZONE_T_H,zoneTargetHigh,clrBlue,3);
            drawZoneLevel(ZONE_T_L,zoneTargetLow,clrBlue,3);

ここでは、levelName、price、CLR、widthの4つの入力パラメータ(引数)を取る単純なvoid関数を作成します。ObjectCreate組み込み関数を使用して、チャートの長さ全体に渡る水平線を作成し、指定された時間と価格に添付します。最後に、ObjectSetIntegerを使用して、一意性を保つためのオブジェクトの色と、見え方を調整しやすくするための幅を設定します。 

void drawZoneLevel(string levelName, double price, color clr, int width) {
   ObjectCreate(0, levelName, OBJ_HLINE, 0, TimeCurrent(), price); // Create a horizontal line object
   ObjectSetInteger(0, levelName, OBJPROP_COLOR, clr); // Set the line color
   ObjectSetInteger(0, levelName, OBJPROP_WIDTH, width); // Set the line width
}

最後に、買いポジションを建てたことを示すために、最後の方向性を1に設定し、次の回復ボリュームを最初のボリュームに乗数定数(この場合は2)を掛けたものに設定し、最後にisBuyDoneフラグをtrueに、isSellDoneフラグをfalseに設定します。 

            lastDirection = 1;
            recovery_lot = 0.01*2;
            isBuyDone = true; isSellDone = false;

ポジションを建てて、ゾーンの回復レベルを設定するための完全なコードは以下の通りです。 

   //--- Check for oversold condition and open a buy position
   if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel) {
      obj_trade.Buy(0.01); // Open a buy trade with 0.01 lots
      ulong pos_ticket = obj_trade.ResultOrder();
      if (pos_ticket > 0) {
         if (PositionSelectByTicket(pos_ticket)) {
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            zoneHigh = NormalizeDouble(openPrice, _Digits); // Set the high zone price
            zoneLow = NormalizeDouble(zoneHigh - zoneRange, _Digits); // Set the low zone price
            zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price
            zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price
            drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line
            drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line
            drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line
            drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line
            
            lastDirection = 1;       // Set the last direction to buy
            recovery_lot = 0.01 * 2; // Set the initial recovery lot size
            isBuyDone = true;        // Mark buy trade as done
            isSellDone = false;      // Reset sell trade flag
         }
      }
   }

売りポジションを建ててゾーンの回復レベルを設定するためには、制御ロジックは残りますが、以下のように逆の条件となります。

   else if (rsiData[1] > overBoughtLevel && rsiData[0] < overBoughtLevel) {
      obj_trade.Sell(0.01); // Open a sell trade with 0.01 lots
      ulong pos_ticket = obj_trade.ResultOrder();
      if (pos_ticket > 0) {
         if (PositionSelectByTicket(pos_ticket)) {
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            zoneLow = NormalizeDouble(openPrice, _Digits); // Set the low zone price
            zoneHigh = NormalizeDouble(zoneLow + zoneRange, _Digits); // Set the high zone price
            zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price
            zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price
            drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line
            drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line
            drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line
            drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line
            
            lastDirection = -1;      // Set the last direction to sell
            recovery_lot = 0.01 * 2; // Set the initial recovery lot size
            isBuyDone = false;       // Reset buy trade flag
            isSellDone = true;       // Mark sell trade as done
         }
      }
   }

ここで、売りシグナルの条件が満たされているかどうかを確認し、満たされていれば即座に売りポジションを建てます。次に、そのチケットを取得し、そのチケットを使用してポジションの始値を取得します。売りポジションなので、その価格はゾーン安値となり、ゾーン高値を求めるには、ゾーン高値にゾーン範囲を足すだけです。同様に、ゾーン高値にゾーン目標を加算してゾーン目標高値とし、ゾーン安値からゾーン目標を減算してゾーン目標安値とします。可視化するために、再び関数を使用して4つのレベルを描します。最後に、ユーティリティ変数を設定します。

ここまでで、提示されたシグナルに基づいてポジションを建て、ゾーン回復システムを設定することができました。以下はそれを可能にする完全なコードです。

void OnTick()
  {
   int bars = iBars(_Symbol,PERIOD_CURRENT);
   if (totalBars == bars) return;
   totalBars = bars;
   
   if (PositionsTotal() > 0) return;
   
   if (!CopyBuffer(rsi_handle,0,1,2,rsiData)) return;
   
   //--- Check for oversold condition and open a buy position
   if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel) {
      obj_trade.Buy(0.01); // Open a buy trade with 0.01 lots
      ulong pos_ticket = obj_trade.ResultOrder();
      if (pos_ticket > 0) {
         if (PositionSelectByTicket(pos_ticket)) {
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            zoneHigh = NormalizeDouble(openPrice, _Digits); // Set the high zone price
            zoneLow = NormalizeDouble(zoneHigh - zoneRange, _Digits); // Set the low zone price
            zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price
            zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price
            drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line
            drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line
            drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line
            drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line
            
            lastDirection = 1;       // Set the last direction to buy
            recovery_lot = 0.01 * 2; // Set the initial recovery lot size
            isBuyDone = true;        // Mark buy trade as done
            isSellDone = false;      // Reset sell trade flag
         }
      }
   }
   //--- Check for overbought condition and open a sell position
   else if (rsiData[1] > overBoughtLevel && rsiData[0] < overBoughtLevel) {
      obj_trade.Sell(0.01); // Open a sell trade with 0.01 lots
      ulong pos_ticket = obj_trade.ResultOrder();
      if (pos_ticket > 0) {
         if (PositionSelectByTicket(pos_ticket)) {
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            zoneLow = NormalizeDouble(openPrice, _Digits); // Set the low zone price
            zoneHigh = NormalizeDouble(zoneLow + zoneRange, _Digits); // Set the high zone price
            zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price
            zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price
            drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line
            drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line
            drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line
            drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line
            
            lastDirection = -1;      // Set the last direction to sell
            recovery_lot = 0.01 * 2; // Set the initial recovery lot size
            isBuyDone = false;       // Reset buy trade flag
            isSellDone = true;       // Mark sell trade as done
         }
      }
   }
}   

ゾーン回復システムを設定することはできますが、それを監視し、目標水準に達したらゾーンから脱出するか、あらかじめ設定した水準に達したらそれぞれのポジションを建てるようにする必要があります。このロジックは、ローソク足のループが制限される前に実装されなければなりません。まず、目標レベルにヒットする条件を確認し、次にゾーンレベルにヒットする条件を確認します。では参りましょう。

         if (zoneTargetHigh > 0 && zoneTargetLow > 0){
            if (bid > zoneTargetHigh || bid < zoneTargetLow){
               obj_trade.PositionClose(_Symbol);
               deleteZoneLevels();
               ...
            }
         }

ここでは、目標レベルがゼロ以上であるというロジックを持つことで、ゾーン回復システムが設定されていることを確認します。もしそうなら、bid値が目標高値を上回るか、目標安値を下回るか確認します。これは、ゾーン回復の目的が達成されたため、ゾーン回復を問題なく破壊できることを示しています。したがって、関数deleteZoneLevelsによってゾーンレベルを削除します。使用する関数は、何も返す必要がないためvoid型であり、組み込みのObjectDelete関数は、チャートインデックスとオブジェクト名の2つの引数を取り、レベルを削除するために実装されています。

void deleteZoneLevels(){
   ObjectDelete(0,ZONE_H);
   ObjectDelete(0,ZONE_L);
   ObjectDelete(0,ZONE_T_H);
   ObjectDelete(0,ZONE_T_L);
}

ポジションをクローズするには、この時点でいくつかのポジションが存在する可能性があるため、すべてのポジションを考慮し、個別に削除するループを使用します。これは以下のコードで実現できます。

               for (int i = PositionsTotal()-1; i >= 0; i--){
                  ulong ticket = PositionGetTicket(i);
                  if (ticket > 0){
                     if (PositionSelectByTicket(ticket)){
                        obj_trade.PositionClose(ticket);
                     }
                  }
               }

すべてのポジションをクローズし、レベルを削除した後、システムをデフォルトにリセットしました。

               //closed all, reset all
               zoneHigh=0;zoneLow=0;zoneTargetHigh=0;zoneTargetLow=0;
               lastDirection=0;
               recovery_lot = 0;

これは、最後の方向と回復ロット以外に、ゾーンレベルと目標をゼロに設定することで達成されます。これらは静的変数なので、手動でリセットする必要があります。動的変数については、自動的に更新されることが多いので必要ありません。

目的が達成された後、回復システムを破壊する完全なコードは以下の通りです。 

   //--- Close all positions if the bid price is outside target zones
   if (zoneTargetHigh > 0 && zoneTargetLow > 0) {
      if (bid > zoneTargetHigh || bid < zoneTargetLow) {
         obj_trade.PositionClose(_Symbol); // Close the current position
         deleteZoneLevels();               // Delete all drawn zone levels
         for (int i = PositionsTotal() - 1; i >= 0; i--) {
            ulong ticket = PositionGetTicket(i);
            if (ticket > 0) {
               if (PositionSelectByTicket(ticket)) {
                  obj_trade.PositionClose(ticket); // Close positions by ticket
               }
            }
         }
         //--- Reset all zone and direction variables
         zoneHigh = 0;
         zoneLow = 0;
         zoneTargetHigh = 0;
         zoneTargetLow = 0;
         lastDirection = 0;
         recovery_lot = 0;
      }
   }

回復のポジションを建てるためには、ゾーンレベルがゼロを超えた時点で、システムがまだ機能しているかどうかを確認する必要があります。もしそうなら、変数lots_recを宣言してそれを設定します。使用する取引口座はマイクロロット口座なので、精度を上げるために小数点以下3桁に正規化します。この値は、使用している口座の種類によって変わることがあります。たとえば、標準口座を使用している場合、その最小ロットは1であるため、小数点以下の桁数を取り除くために、値は0になります。ほとんどの場合、小数点以下は2桁ですが、0.001の口座タイプがあるため、ロットを小数点以下3桁に四捨五入するため、値は3桁になります。

   if (zoneHigh > 0 && zoneLow > 0){   
      double lots_Rec = 0;
      lots_Rec = NormalizeDouble(recovery_lot,2);
      ...
   }

そして、買値がゾーンの高値を上回るかどうかを確認し、前回のisBuyDoneフラグがfalseであるか、最後の方向値がゼロ未満であれば、回復買いポジションを建てます。ポジションを建てた後、lastDirectionを1に設定します。これは、前に建てたポジションが買いであることを意味し、回復ロットを計算し、次の回復ポジション呼び出しで使用するためにrecovery_lot変数に格納します。次に、isBuyDoneフラグをtrueに設定し、isSellDoneをfalseに設定して、買いポジションがすでに開かれていることを示します。 

  if (bid > zoneHigh) {
         if (isBuyDone == false || lastDirection < 0) {
            obj_trade.Buy(lots_Rec); // Open a buy trade
            
            lastDirection = 1;       // Set the last direction to buy
            recovery_lot = recovery_lot * 2; // Double the recovery lot size
            isBuyDone = true;        // Mark buy trade as done
            isSellDone = false;      // Reset sell trade flag
         }
      }

そうでない場合、買値がゾーン安値を下回れば、それぞれ図のように回復売りポジションを建てます。

else if (bid < zoneLow) {
         if (isSellDone == false || lastDirection > 0) {
            obj_trade.Sell(lots_Rec); // Open a sell trade
            
            lastDirection = -1;      // Set the last direction to sell
            recovery_lot = recovery_lot * 2; // Double the recovery lot size
            isBuyDone = false;       // Reset buy trade flag
            isSellDone = true;       // Mark sell trade as done
         }
      }
   }

現在、回復ポジションを建てる完全なコードは以下の通りです。

   //--- Check if price is within defined zones and take action
   if (zoneHigh > 0 && zoneLow > 0) {   
      double lots_Rec = NormalizeDouble(recovery_lot, 2); // Normalize the recovery lot size to 2 decimal places
      if (bid > zoneHigh) {
         if (isBuyDone == false || lastDirection < 0) {
            obj_trade.Buy(lots_Rec); // Open a buy trade
            
            lastDirection = 1;       // Set the last direction to buy
            recovery_lot = recovery_lot * 2; // Double the recovery lot size
            isBuyDone = true;        // Mark buy trade as done
            isSellDone = false;      // Reset sell trade flag
         }
      } else if (bid < zoneLow) {
         if (isSellDone == false || lastDirection > 0) {
            obj_trade.Sell(lots_Rec); // Open a sell trade
            
            lastDirection = -1;      // Set the last direction to sell
            recovery_lot = recovery_lot * 2; // Double the recovery lot size
            isBuyDone = false;       // Reset buy trade flag
            isSellDone = true;       // Mark sell trade as done
         }
      }
   }

これが、私たちがこれまでに達成したマイルストーンです。

   現在のマイルストーン

ゾーン回復システムの自動化に必要な完全なコードは以下の通りです。

//+------------------------------------------------------------------+
//|                                                MARTINGALE EA.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

//--- Define utility variables for later use

#define ZONE_H "ZH"            // Define a constant for the high zone line name
#define ZONE_L "ZL"            // Define a constant for the low zone line name
#define ZONE_T_H "ZTH"         // Define a constant for the target high zone line name
#define ZONE_T_L "ZTL"         // Define a constant for the target low zone line name

//--- Include trade instance class

#include <Trade/Trade.mqh>     // Include the trade class for trading functions
CTrade obj_trade;              // Create an instance of the CTrade class for trading operations

//--- Declare variables to hold indicator data

int rsi_handle;                // Handle for the RSI indicator
double rsiData[];              // Array to store RSI data
int totalBars = 0;             // Variable to keep track of the total number of bars

double overBoughtLevel = 70.0; // Overbought level for RSI
double overSoldLevel = 30.0;   // Oversold level for RSI
double zoneHigh = 0;           // Variable to store the high zone price
double zoneLow = 0;            // Variable to store the low zone price
double zoneTargetHigh = 0;     // Variable to store the target high zone price
double zoneTargetLow = 0;      // Variable to store the target low zone price

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- Initialize the RSI indicator
   rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, 14, PRICE_CLOSE);
   //--- Return initialization result
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   //--- Remove RSI indicator from memory
   IndicatorRelease(rsi_handle);
   ArrayFree(rsiData); // Free the RSI data array
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   //--- Retrieve the current Ask and Bid prices
   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double zoneRange = 200 * _Point;       // Define the range for the zones
   double zoneTarget = 400 * _Point;      // Define the target range for the zones
   
   //--- Variables to track trading status
   static int lastDirection = 0;          // -1 = sell, 1 = buy
   static double recovery_lot = 0.0;      // Lot size for recovery trades
   static bool isBuyDone = false, isSellDone = false; // Flags to track trade completion
   
   //--- Close all positions if the bid price is outside target zones
   if (zoneTargetHigh > 0 && zoneTargetLow > 0) {
      if (bid > zoneTargetHigh || bid < zoneTargetLow) {
         obj_trade.PositionClose(_Symbol); // Close the current position
         deleteZoneLevels();               // Delete all drawn zone levels
         for (int i = PositionsTotal() - 1; i >= 0; i--) {
            ulong ticket = PositionGetTicket(i);
            if (ticket > 0) {
               if (PositionSelectByTicket(ticket)) {
                  obj_trade.PositionClose(ticket); // Close positions by ticket
               }
            }
         }
         //--- Reset all zone and direction variables
         zoneHigh = 0;
         zoneLow = 0;
         zoneTargetHigh = 0;
         zoneTargetLow = 0;
         lastDirection = 0;
         recovery_lot = 0;
      }
   }

   //--- Check if price is within defined zones and take action
   if (zoneHigh > 0 && zoneLow > 0) {   
      double lots_Rec = NormalizeDouble(recovery_lot, 2); // Normalize the recovery lot size to 2 decimal places
      if (bid > zoneHigh) {
         if (isBuyDone == false || lastDirection < 0) {
            obj_trade.Buy(lots_Rec); // Open a buy trade
            
            lastDirection = 1;       // Set the last direction to buy
            recovery_lot = recovery_lot * 2; // Double the recovery lot size
            isBuyDone = true;        // Mark buy trade as done
            isSellDone = false;      // Reset sell trade flag
         }
      } else if (bid < zoneLow) {
         if (isSellDone == false || lastDirection > 0) {
            obj_trade.Sell(lots_Rec); // Open a sell trade
            
            lastDirection = -1;      // Set the last direction to sell
            recovery_lot = recovery_lot * 2; // Double the recovery lot size
            isBuyDone = false;       // Reset buy trade flag
            isSellDone = true;       // Mark sell trade as done
         }
      }
   }
   
   //--- Update bars and check for new bars
   int bars = iBars(_Symbol, PERIOD_CURRENT);
   if (totalBars == bars) return; // Exit if no new bars
   totalBars = bars; // Update the total number of bars
   
   //--- Exit if there are open positions
   if (PositionsTotal() > 0) return;
   
   //--- Copy RSI data and check for oversold/overbought conditions
   if (!CopyBuffer(rsi_handle, 0, 1, 2, rsiData)) return;
   
   //--- Check for oversold condition and open a buy position
   if (rsiData[1] < overSoldLevel && rsiData[0] > overSoldLevel) {
      obj_trade.Buy(0.01); // Open a buy trade with 0.01 lots
      ulong pos_ticket = obj_trade.ResultOrder();
      if (pos_ticket > 0) {
         if (PositionSelectByTicket(pos_ticket)) {
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            zoneHigh = NormalizeDouble(openPrice, _Digits); // Set the high zone price
            zoneLow = NormalizeDouble(zoneHigh - zoneRange, _Digits); // Set the low zone price
            zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price
            zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price
            drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line
            drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line
            drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line
            drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line
            
            lastDirection = 1;       // Set the last direction to buy
            recovery_lot = 0.01 * 2; // Set the initial recovery lot size
            isBuyDone = true;        // Mark buy trade as done
            isSellDone = false;      // Reset sell trade flag
         }
      }
   }
   //--- Check for overbought condition and open a sell position
   else if (rsiData[1] > overBoughtLevel && rsiData[0] < overBoughtLevel) {
      obj_trade.Sell(0.01); // Open a sell trade with 0.01 lots
      ulong pos_ticket = obj_trade.ResultOrder();
      if (pos_ticket > 0) {
         if (PositionSelectByTicket(pos_ticket)) {
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            zoneLow = NormalizeDouble(openPrice, _Digits); // Set the low zone price
            zoneHigh = NormalizeDouble(zoneLow + zoneRange, _Digits); // Set the high zone price
            zoneTargetHigh = NormalizeDouble(zoneHigh + zoneTarget, _Digits); // Set the target high zone price
            zoneTargetLow = NormalizeDouble(zoneLow - zoneTarget, _Digits); // Set the target low zone price
            drawZoneLevel(ZONE_H, zoneHigh, clrGreen, 2); // Draw the high zone line
            drawZoneLevel(ZONE_L, zoneLow, clrRed, 2); // Draw the low zone line
            drawZoneLevel(ZONE_T_H, zoneTargetHigh, clrBlue, 3); // Draw the target high zone line
            drawZoneLevel(ZONE_T_L, zoneTargetLow, clrBlue, 3); // Draw the target low zone line
            
            lastDirection = -1;      // Set the last direction to sell
            recovery_lot = 0.01 * 2; // Set the initial recovery lot size
            isBuyDone = false;       // Reset buy trade flag
            isSellDone = true;       // Mark sell trade as done
         }
      }
   }
}

//+------------------------------------------------------------------+
//|      FUNCTION TO DRAW HORIZONTAL ZONE LINES                      |
//+------------------------------------------------------------------+

void drawZoneLevel(string levelName, double price, color clr, int width) {
   ObjectCreate(0, levelName, OBJ_HLINE, 0, TimeCurrent(), price); // Create a horizontal line object
   ObjectSetInteger(0, levelName, OBJPROP_COLOR, clr); // Set the line color
   ObjectSetInteger(0, levelName, OBJPROP_WIDTH, width); // Set the line width
}

//+------------------------------------------------------------------+
//|       FUNCTION TO DELETE DRAWN ZONE LINES                        |
//+------------------------------------------------------------------+

void deleteZoneLevels() {
   ObjectDelete(0, ZONE_H); // Delete the high zone line
   ObjectDelete(0, ZONE_L); // Delete the low zone line
   ObjectDelete(0, ZONE_T_H); // Delete the target high zone line
   ObjectDelete(0, ZONE_T_L); // Delete the target low zone line
}

これまでのところ、ゾーン回復FX取引システムは意図したとおりに自動化されています。以下に示すように、テストを進めてパフォーマンスを確認し、指定された目的が達成されているかどうかを確認します。

バックテスト結果

ストラテジーテスターでテストした結果は以下の通りです。

グラフ

   グラフ

結果

結果


結論

この記事では、MQL5で有名なゾーン回復マーチンゲール戦略を自動化するために実装する必要がある基本的なステップについて見てきました。ストラテジーの基本的な定義と説明をおこない、MQL5でどのように実装できるかを示した。トレーダーはこの知識をもとに、より複雑なゾーン回収システムを開発することができます。

免責条項:このコードは、ゾーン回復FX取引システムを作成するための基本を身につけることを目的としており、実証された結果は将来のパフォーマンスを保証するものではありません。ご自分の取引スタイルに合わせてシステムを作成し、最適化するための知識を慎重に実行してください。

この記事には、システム作成に向けたすべてのステップが定期的に掲載されています。より良い、完全に最適化されたゾーン回復システムを構築するための足がかりとして、お役立ていただければ幸いです。これらの例を示すために使用された例を提供するために、必要なファイルが添付されています。最適な結果を得るためには、コードを研究し、それを特定の戦略に適用することができるはずです。


MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/15067

添付されたファイル |
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
LSTMニューラルネットワークを用いた時系列予測の作成:価格の正規化と時間のトークン化 LSTMニューラルネットワークを用いた時系列予測の作成:価格の正規化と時間のトークン化
この記事では、日次レンジを使用して市場データを正規化し、市場予測を強化するためにニューラルネットワークを訓練する簡単な戦略を概説します。開発されたモデルは、既存のテクニカル分析の枠組みと組み合わせて、あるいは単独で、市場全体の方向性を予測するのに役立てることができます。この記事で概説した枠組みは、テクニカルアナリストであれば、手動と自動売買の両方の戦略に適したモデルを開発するために、さらに改良を加えることができます。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
データサイエンスと機械学習(第24回):通常のAIモデルによるFX時系列予測 データサイエンスと機械学習(第24回):通常のAIモデルによるFX時系列予測
外国為替市場において、過去を知らずに将来のトレンドを予測することは非常に困難です。過去の値を考慮して将来の予測をおこなうことができる機械学習モデルは非常に少ないです。この記事では、市場に勝つために古典的な(非時系列)人工知能モデルを使用する方法について説明します。