English
preview
Candlestick Trend Constraintモデルの構築(第10回):戦略的ゴールデンクロスとデスクロス(EA)

Candlestick Trend Constraintモデルの構築(第10回):戦略的ゴールデンクロスとデスクロス(EA)

MetaTrader 5 | 22 4月 2025, 10:19
27 0
Clemence Benjamin
Clemence Benjamin

はじめに

本記事では、戦略的に設計されたゴールデンクロスおよびデッドクロス戦略をTrend Constraint EAに統合し、長年にわたって信頼されてきた移動平均クロスオーバー手法の可能性を最大限に引き出す方法をご紹介します。私たちの目的は、これらの戦略を自動化することで、アルゴリズム取引におけるトレンドフォロー機能を強化し、精度・一貫性・取引システムとの互換性を確保することです。

多くのトレーダーは、強い上昇または下降トレンドを的確に見極め、それに基づいた取引機会を活かすことに課題を抱えています。ゴールデンクロスは上昇の勢い、デッドクロスは下降トレンドの始まりを示すシグナルとして、手動取引でも有効性が証明されていますが、自動化されていない場合にはシグナルの見逃しやタイミングのズレが生じがちです。

ゴールデンクロスおよびデッドクロス戦略を自動化されたEAに組み込むことで、トレーダーは他のトレンドフォロー戦略のような制約に縛られることなく、反転のシグナルを体系的かつ即時に実行できるようになります。これらのクロスオーバー戦略は、日々の市場センチメントに依存せず、独立して市場の転換を検出・対応するため、重要な反転ポイントをいち早く捉えることが可能です。その結果、広範なトレンド戦略との整合性を保ちつつ、取引の応答性とパフォーマンスが大幅に向上します。

主な内容

  1. ゴールデンクロスとデスクロス戦略について
  2. トレンドフォローのより広い文脈における関連性を分析する
  3. トレンド制約条件への適応
  4. MQL5を使用した戦略の実装
  5. 初期戦略テスト
  6. Trend Constraintにおける新しい戦略の統合
  7. テストと最適化の結果
  8. 結論


ゴールデンクロスとデスクロス戦略について

ゴールデンクロスとデスクロス戦略は、移動平均線のクロスオーバーに基づいて潜在的な強気または弱気の市場動向を示すために使用されるテクニカル分析の極めて重要な概念です。ゴールデンクロスは、短期の移動平均(通常は50日移動平均)が長期の移動平均(多くの場合200日移動平均)を上回ったときに発生します。この出来事は強い強気のシグナルとみなされ、長期的な上昇傾向が始まっている可能性を示唆しています。投資家やトレーダーは、価格の上昇を期待して、これを株式を購入または保有する機会とみなすかもしれません。 

逆に、デスクロスは、短期移動平均が長期移動平均を下回り、潜在的な弱気トレンドを示唆する反対のシナリオです。これは、市場が持続的な下落に陥る可能性があるという警告と見られています。デスクロスは、市場の状況が弱まることを示唆するため、投資家に売却または空売りを促したり、少なくとも注意を促したりする可能性があります。これら2つのシグナルは、直接的な取引決定だけでなく、より広範な市場感情分析にも使用されますが、絶対確実なものではないため、傾向を確認するには他の指標や分析形式と組み合わせて使用する必要があります。


トレンドフォロー戦略の文脈における関連性の分析

トレンドフォロー戦略のより広い文脈において、ゴールデンクロスとデッドクロスは、市場トレンドの開始や潜在的な反転を識別するための基本的かつ重要なツールとして位置づけられます。トレンドフォローとは、市場価格の持続的な動きを捉え、確立されたトレンドの方向に沿って取引することで利益を得ることを目的とした戦略です。ここでは、ゴールデンクロスとデッドクロスがこの戦略体系においてどのような役割を果たすのかを整理します。

1.トレンドの識別:これらのクロスオーバーの主な役割は、新たなトレンドの始まりを明確に示すことです。ゴールデンクロスは上昇トレンドの始動を示し、トレンドフォロワーにロングポジションの構築や保有ポジションの拡大を促します。一方、デッドクロスは下降トレンドへの転換を示唆し、ロングポジションの解消やショートの検討を促します。
2.トレンドの確認:これらのシグナルは、トレンドの存在を確認するための補完的な役割も果たします。トレンドフォロワーは、ゴールデンクロスやデッドクロスを、価格の動き、出来高、モメンタム系のインジケーターなどと併用することで、トレンドの信頼性を高めることができます。このようなマルチインジケーターによる確認手法は、誤シグナルを減らし、トレンドの持続期間を最大限に活かすために非常に有効です。
3.リスク管理:トレンドフォロー戦略においては、トレンドの急な反転や停滞に備えたリスク管理が不可欠です。ゴールデンクロスやデッドクロスは、ストップロスの設定やポジションサイズの調整に活用できます。たとえば、ゴールデンクロスの発生後に価格が移動平均線を再び下回った場合、損切りラインを引き上げたり、保有ポジションを縮小する判断材料となります。
4.長期的な視点:これらのシグナルは、通常50日と200日といった比較的長期の移動平均線を用いるため、短期的なノイズを排除しつつ、大きなトレンドの流れに焦点を当てることができます。この長期志向のアプローチは、トレンドフォロー戦略の本質とも一致しており、安定した取引判断につながります。
5.適応性:従来は50日移動平均線と200日移動平均線の組み合わせが一般的ですが、この戦略はさまざまな時間枠や市場状況に合わせて柔軟に調整可能です。動きの速い市場では、トレーダーはより短い期間を使用して、より迅速なシグナルを得ることがあります。一方で、より安定した市場では、小さな調整による振り回し(whipsaw)を回避するために、より長い期間を使用することが好まれるかもしれません。

しかし、トレンドフォローにおけるゴールデンクロスとデッドクロスの関連性は、以下のように批判的に分析することもできます。

  • 遅れ:最大の批判の1つは、移動平均線に伴う遅れです。これらのクロスオーバーが発生する時点では、トレンドの重要な部分がすでに展開済みである可能性があり、これらのシグナルのみに依存した取引では、利益機会が減少するおそれがあります。
  • 偽シグナル:トレンドフォロー戦略には、誤ったブレイクアウトや時期尚早なトレンド反転というリスクが本質的に伴います。ゴールデンクロスおよびデッドクロスも例外ではなく、したがって、これらの指標は他の分析手法や確認用シグナルと組み合わせた、より広範な戦略の一部として使用するべきです。
  • 市場環境:その有効性は、市場のコンディションによって大きく左右されます。トレンドが明確な相場では、これらのシグナルは非常に効果的である一方、レンジ相場や荒れた市場環境では、多くの誤ったシグナルを引き起こす可能性があります。


トレンド制約条件への適応

長期的な戦略において、過度な制約を課すことはその潜在能力を制限する可能性があります。たとえば、日足の市場センチメントが弱気であるというシナリオを考えてみましょう。同じ日の中で、短期の時間枠においてゴールデンクロスのシグナルが出現した場合、それは反転の可能性を示唆し、市場が強気に転じる兆候となるかもしれません。このような反転が発生すると、他の制約された戦略も新たに生まれたトレンドセンチメントに適応し、市場の新しい方向性に沿うようになります。

このようなダイナミクスを活かすために、デスクロスおよびゴールデンクロス戦略をTrend Constraint EAに独立したモジュールとして組み込むことが可能です。これにより、EAは新たに出現したトレンドと一致する反転エントリーを捉えることで、パフォーマンスを最大化することができます。

このアプローチにより、以下が実現されます。

  1. 時間枠を超えた柔軟性:EAは短期的な反転(例:ゴールデンクロス)を検出し、それをより広範な市場の動きと整合させることで、適応性を高めます。
  2. エントリポイントの改善:トレンド感情の変化を早期に特定することで、EAは戦略的なポジションを取ることができ、市場の変化への対応の遅れを最小限に抑えることができます。
  3. 戦略間の相乗効果:ゴールデンクロス戦略とデスクロス戦略を個別に統合することで、EAは主要なトレンド追従メカニズムを無効にすることなく、それぞれの強みを活用できます。

この二重層アプローチを採用することで、Trend Constraint EAは長期的な収益性を維持しながら市場の反転を効果的に乗り越える能力を獲得します。では、次の段階に進み、この戦略を実装して統合しましょう。


MQL5を使用した戦略の実装

私の開発アプローチでは、マルチ戦略EAを作成するときは常に、最初に各戦略を独立したEAとして設計します。これにより、単一の統合コードベースに統合する前に、集中的なテストと改良が可能になります。この方法に従って、まず戦略的ゴールデンEAとデスクロスEAを個別に分解して開発してから、メインのExpertAdvisorに統合します。

いつものように、デスクトップからMetaEditor5アプリケーションを開くか、MetaTrader5ターミナルでF4キーを押して直接起動します。以下に概説する開発手順に従ってください。初心者の場合は、スキルを強化し、理解を深めるために、コピーして貼り付けるのではなく、手動でコードを入力することをお勧めします。

ここではゴールデンクロスとデスクロスから始めます。

ヘッダーとメタデータ

これはプログラムの最上部の部分であり、明確さと専門性を確保するためにメタデータを定義することから始まります。メタデータには、著作権の詳細、説明、バージョン番号など、プログラムがStrategicGolden&DeathCrossEAであることが示されます。このステップは、プロジェクトを整理し、MetaTrader5にデプロイされたときに作業が適切に帰属されることを保証するのに役立ちます。このようにメタデータを設定することで、EAの専門的で十分に文書化された基盤が確立されます。

//+------------------------------------------------------------------+ 
//|                       Strategic Golden & Death Cross EA.mq5      |
//|                           Copyright 2024, Clemence Benjamin      |
//|        https://www.mql5.com/ja/users/billionaire2024/seller      |
//+------------------------------------------------------------------+
#property copyright "Clemence Benjamin"
#property description "GOLDEN AND DEATH CROSS"
#property version "1.0"

インクルードとトレードオブジェクトの初期化

取引の実行を簡素化するために、#include<Trade\Trade.mqh>を使用してMQL5取引ライブラリをインクルードします。このライブラリは強力な取引機能へのアクセスを提供します。CTradeクラスをtradeとしてインスタンス化し、EAがこれらのルーチンを手動で実装することなく、ポジションの購入、販売、クローズなどの操作を処理できるようにします。これにより複雑さが軽減され、コードの信頼性が向上します。この設定により、戦略に集中し、注文の実行には取引管理ライブラリを頼ることができます。

#include<Trade\Trade.mqh>;
CTrade trade;

入力パラメータ

まず、EAを柔軟かつカスタマイズ可能にするために、ユーザーが調整可能なパラメータを定義します。

以下は例です。

  • LotSizeを使用すると、取引量を制御できます。
  • Slippageは、実行中に許容される価格の偏差を指定します。
  • FastEMAPeriodとSlowEMAPeriodは、ゴールデンクロスとデスクロス戦略のバックボーンを形成する移動平均の期間を定義します。
input double LotSize = 1.0;            // Trade volume (lots)
input int Slippage = 20;               // Slippage in points
input int TimerInterval = 10000;       // Timer interval in seconds
input double TakeProfitPips = 100;     // Take Profit in pips
input double StopLossPips = 50;        // Stop Loss in pips
input int FastEMAPeriod = 50;          // Fast EMA period (default 50)
input int SlowEMAPeriod = 200;         // Slow EMA period (default 200)

これらのパラメータを設定することで、ユーザーは特定の取引条件に応じてEAを調整できるようになります。

初期化とタイマーの設定

EAが適切に初期化されていることを確認しましょう。OnInit()関数では、長期EMAを計算するのに十分なバーが存在するかどうかを確認するチェックを追加します。存在しない場合、EAはエラーをログに記録し、実行を停止します。これにより、十分なデータが利用可能な場合にのみ戦略が実行されるようになります。

int OnInit()
{
   //--- create timer
   EventSetTimer(TimerInterval);

   //--- Check if there are enough bars for EMA calculation
   if(Bars(_Symbol,PERIOD_CURRENT)<SlowEMAPeriod)
   {
      Print("Not enough bars for EMA calculation");
      return(INIT_FAILED);
   }
   return(INIT_SUCCEEDED);
}

さらに、EventSetTimer()を使用してOnTimer()関数を定期的に呼び出し、取引ロジックを実行します。OnDeinit()関数は、EAが削除されたときにタイマーが非アクティブになり、リソースが解放されることを保証します。

void OnDeinit(const int reason)
{
   //--- delete timer
   EventKillTimer();
}

OnTimer()の取引ロジック

ここで、OnTimer()関数内の戦略の核心を見ていきましょう。

EMAの計算

まずiMA()を用いて短期EMAと長期EMAのハンドルを作成し、CopyBuffer()を用いてその値を取得します。これらのEMAは、エントリーシグナルを検出するために不可欠です。

int fastEMAHandle = iMA(_Symbol, PERIOD_CURRENT, FastEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
int slowEMAHandle = iMA(_Symbol, PERIOD_CURRENT, SlowEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);

double fastEMAArray[], slowEMAArray[];
CopyBuffer(fastEMAHandle, 0, 0, 2, fastEMAArray);
CopyBuffer(slowEMAHandle, 0, 0, 2, slowEMAArray);

市場データの取得

次に、ストップロスとテイクプロフィットの正確な計算を確実にするために、売り、買い、ポイントサイズなどの重要な市場データを取得します。

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

エントリーシグナル

ここでは、売買取引を開始するための条件を定義します。ゴールデンクロス(短期EMAが長期EMAを上抜ける)は買いのシグナルとなり、デスクロス(短期EMAが長期EMAを下抜ける)は売りのシグナルとなります。

if(fastEMAArray[0] > slowEMAArray[0] && fastEMAArray[1] <= slowEMAArray[1]) // Death Cross 
{
   double sl = NormalizeDouble(ask + StopLossPips * point, _Digits);
   trade.Sell(LotSize, _Symbol, ask, sl);
}
else if(fastEMAArray[0] < slowEMAArray[0] && fastEMAArray[1] >= slowEMAArray[1]) // Golden Cross
{
   double sl = NormalizeDouble(bid - StopLossPips * point, _Digits);
   trade.Buy(LotSize, _Symbol, bid, sl);
}

エグジットシグナル

ここで反対のシグナルが現れると既存のポジションがクローズされることを確認しましょう。これにより、変化する市場状況に合わせた戦略を維持できます。

if((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && fastEMAArray[0] < slowEMAArray[0]) ||
   (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && fastEMAArray[0] > slowEMAArray[0]))
{
   trade.PositionClose(PositionGetInteger(POSITION_TICKET));
}

エラー処理

現在、取引実行やポジションのクローズに関する問題をログに記録するためのエラー処理ルーチンが追加されています。これによりデバッグが容易になり、スムーズなパフォーマンスが保証されます。

if(!trade.Sell(LotSize, _Symbol, ask, sl))
{
   Print("Sell order error: ", GetLastError());
}

完全なコードは次のとおりです。

//+------------------------------------------------------------------+ 
//|                          Golden & Death Cross Strategy.mq5       |
//|                           Copyright 2024, Clemence Benjamin      |
//|        https://www.mql5.com/ja/users/billionaire2024/seller      |
//+------------------------------------------------------------------+

#property copyright "Clemence Benjamin"
#property description "GOLDEN AND DEATH CROSS"
#property version "1.0"


//+------------------------------------------------------------------+
//| Includes                                                         |
//+------------------------------------------------------------------+
#include<Trade\Trade.mqh>;
CTrade trade;

//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
input double LotSize = 1.0;            // Trade volume (lots)
input int Slippage = 20;               // Slippage in points
input int TimerInterval = 1000;          // Timer interval in seconds
input double StopLossPips = 1500;        // Stop Loss in pips
input int FastEMAPeriod = 50;          // Fast EMA period (default 50)
input int SlowEMAPeriod = 200;         // Slow EMA period (default 200)

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- create timer
   EventSetTimer(TimerInterval);
   
   //--- Check if there are enough bars to calculate the EMA
   if(Bars(_Symbol,PERIOD_CURRENT)<SlowEMAPeriod)
     {
      Print("Not enough bars for EMA calculation");
      return(INIT_FAILED);
     }
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   //--- delete timer
   EventKillTimer();
  }
//+------------------------------------------------------------------+
//| Expert timer function                                            |
//+------------------------------------------------------------------+
void OnTimer()
{
   bool hasPosition = PositionSelect(_Symbol);
   
   int fastEMAHandle = iMA(_Symbol, PERIOD_CURRENT, FastEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
   int slowEMAHandle = iMA(_Symbol, PERIOD_CURRENT, SlowEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);

   if(fastEMAHandle < 0 || slowEMAHandle < 0)
   {
      Print("Failed to create EMA handles. Error: ", GetLastError());
      return;
   }

   double fastEMAArray[], slowEMAArray[];
   if(CopyBuffer(fastEMAHandle, 0, 0, 2, fastEMAArray) <= 0 ||
      CopyBuffer(slowEMAHandle, 0, 0, 2, slowEMAArray) <= 0)
   {
      Print("Failed to copy EMA data. Error: ", GetLastError());
      return;
   }

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

   if(!hasPosition)
   {
      if(fastEMAArray[0] > slowEMAArray[0] && fastEMAArray[1] <= slowEMAArray[1]) // Death Cross 
      {
         
         double sl = NormalizeDouble(ask + StopLossPips * point, _Digits);
         if(!trade.Sell(LotSize, _Symbol, ask, sl ))
            Print("Buy order error ", GetLastError());
         else
            Print("Buy order opened with TP ", " and SL ", StopLossPips, " pips");
      }
      else if(fastEMAArray[0] < slowEMAArray[0] && fastEMAArray[1] >= slowEMAArray[1]) // Golden Cross
      {
         
         double sl = NormalizeDouble(bid - StopLossPips * point, _Digits);
         if(!trade.Buy(LotSize, _Symbol, bid, sl ))
            Print("Sell order error ", GetLastError());
         else
            Print("Sell order opened with TP ",  " and SL ", StopLossPips, " pips");
      }
   }
   else
   {
     if((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && fastEMAArray[0] < slowEMAArray[0]) ||
         (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && fastEMAArray[0] > slowEMAArray[0]))
    {
         ulong ticket = PositionGetInteger(POSITION_TICKET);
         if(!trade.PositionClose(ticket))
            Print("Failed to close position (Ticket: ", ticket, "). Error: ", GetLastError());
         else  
            Print("Position closed : ", ticket);
      }
   }
}

//+------------------------------------------------------------------+


初期戦略テスト

コードを正常にコンパイルしたら、ストラテジーテスターを使用して戦略のパフォーマンスをテストする準備が整いました。以下は、戦略をTrend Constraint EAに統合する前のテスト結果の画像です。

ストラテジーテスター設定.PNG

ストラテジーテスターの設定Boom 500指数

入力設定

入力設定:Boom 500指数

Boom 500指数 M5:ゴールデンクロス&デスクロス戦略のテスト

初期テストでは注文実行ロジックはスムーズに動作していますが、出口戦略を改善する必要があります。ストラテジーテスターで手動で観察したところ、利益を確保できたはずなのに、最終的には小さな利益、あるいは損失で終了したポジションが多数あることに気付きました。これは、大幅なドローダウンの後に反転によってクロスオーバーが発生し、利益が制限されたために発生しました。市場統合の過程では、多くの誤ったクロスオーバーにより損失が発生しました。このような大きなドローダウンを回避するには、出口戦略の強化が必要です。


Trend Constraintにおける新しい戦略の統合

最後に、以前に開発した戦略を統合して、マルチ戦略EAを作成するという目標に到達しました。既存の戦略を思い出すには、第9回を再度参照してください。すでに統合されている戦略のリストは次のとおりです。

  • トレンドフォロー
  • ドンチャンチャネルブレイクアウト
  • ダイバージェンス戦略

本日は、4番目の戦略を追加します。この戦略は、先ほどご説明したとおり、他の条件に依存せず独立して動作するため、すべての反転のチャンスを捉えることができます。この実装のために、既存のTrend Constraint EAに、新しいゴールデンクロスおよびデッドクロス戦略を有効にするためのブール型スイッチを追加します。加えて、他のコードセクションも、メインコード内の適切な関数へリファクタリングをおこないます。

また、メインプログラム内にすでに存在している他の用語との衝突を避けるため、ゴールデンクロスおよびデッドクロス戦略に関連する変数には、固有のプレフィックスを付けました。例えば、LotSizeGDC_LotSize=1.0:のように変更し、可読性と明確性を高め、混乱を避けるようにしています。

以下は、エラーのない完全なコードです。新しく追加された部分と変更された部分は、理解しやすく明確にするために明確に強調表示されています。

//+------------------------------------------------------------------+
//|                                      Trend Constraint Expert.mq5 |
//|                                Copyright 2024, Clemence Benjamin |
//|             https://www.mql5.com/ja/users/billionaire2024/seller |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Clemence Benjamini"
#property link      "https://www.mql5.com/ja/users/billionaire2024/seller"
#property version   "1.03"

#include <Trade\Trade.mqh>
CTrade trade;

// Input parameters for controlling strategies
input bool UseTrendFollowingStrategy = false;   // Enable/Disable Trend Following Strategy
input bool UseBreakoutStrategy = false;         // Enable/Disable Breakout Strategy
input bool UseDivergenceStrategy = false;       // Enable/Disable Divergence Strategy
input bool UseGoldenDeathCrossStrategy = true;  // Enable/Disable Golden/Death Cross Strategy

// Input parameters for Golden/Death Cross Strategy
input double GDC_LotSize = 1.0;            // Trade volume (lots) for Golden Death Cross
input int GDC_Slippage = 20;               // Slippage in points for Golden Death Cross
input int GDC_TimerInterval = 1000;        // Timer interval in seconds for Golden Death Cross
input double GDC_StopLossPips = 1500;      // Stop Loss in pips for Golden Death Cross
input int GDC_FastEMAPeriod = 50;          // Fast EMA period for Golden Death Cross
input int GDC_SlowEMAPeriod = 200;         // Slow EMA period for Golden Death Cross

int GDC_fastEMAHandle, GDC_slowEMAHandle;  // Handles for EMA indicators in Golden Death Cross

// Global variables
double prevShortMA, prevLongMA;

// Input parameters for Trend Constraint Strategy
input int    RSI_Period = 14;            // RSI period
input double RSI_Overbought = 70.0;     // RSI overbought level
input double RSI_Oversold = 30.0;       // RSI oversold level
input double Lots = 0.1;                // Lot size
input double StopLoss = 100;            // Stop Loss in points
input double TakeProfit = 200;          // Take Profit in points
input double TrailingStop = 50;         // Trailing Stop in points
input int    MagicNumber = 12345678;    // Magic number for the Trend Constraint EA
input int    OrderLifetime = 43200;     // Order lifetime in seconds (12 hours)

// Input parameters for Breakout Strategy
input int InpDonchianPeriod = 20;       // Period for Donchian Channel
input double RiskRewardRatio = 1.5;     // Risk-to-reward ratio
input double LotSize = 0.1;             // Default lot size for trading
input double pipsToStopLoss = 15;       // Stop loss in pips for Breakout
input double pipsToTakeProfit = 30;     // Take profit in pips for Breakout

// Input parameters for Divergence Strategy
input int DivergenceMACDPeriod = 12;    // MACD Fast EMA period
input int DivergenceSignalPeriod = 9;   // MACD Signal period
input double DivergenceLots = 1.0;      // Lot size for Divergence trades
input double DivergenceStopLoss = 300;   // Stop Loss in points for Divergence
input double DivergenceTakeProfit = 500; // Take Profit in points for Divergence
input int DivergenceMagicNumber = 87654321;     // Magic number for Divergence Strategy
input int DivergenceLookBack = 8;       // Number of periods to look back for divergence
input double profitLockerPoints  = 20;  // Number of profit points to lock

// Indicator handle storage
int rsi_handle;                         
int handle;                             // Handle for Donchian Channel
int macd_handle;

double ExtUpBuffer[];                   // Upper Donchian buffer
double ExtDnBuffer[];                   // Lower Donchian buffer
double ExtMacdBuffer[];                 // MACD buffer
double ExtSignalBuffer[];               // Signal buffer
int globalMagicNumber;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    prevShortMA = 0.0;
    prevLongMA = 0.0;
    // Initialize RSI handle
    rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE);
    if (rsi_handle == INVALID_HANDLE)
    {
        Print("Failed to create RSI indicator handle. Error: ", GetLastError());
        return INIT_FAILED;
    }

    // Create a handle for the Donchian Channel
    handle = iCustom(_Symbol, PERIOD_CURRENT, "Free Indicators\\Donchian Channel", InpDonchianPeriod);
    if (handle == INVALID_HANDLE)
    {
        Print("Failed to load the Donchian Channel indicator. Error: ", GetLastError());
        return INIT_FAILED;
    }

    // Initialize MACD handle for divergence
    globalMagicNumber = DivergenceMagicNumber;
    macd_handle = iMACD(_Symbol, PERIOD_CURRENT, DivergenceMACDPeriod, 26, DivergenceSignalPeriod, PRICE_CLOSE);
    if (macd_handle == INVALID_HANDLE)
    {
        Print("Failed to create MACD indicator handle for divergence strategy. Error: ", GetLastError());
        return INIT_FAILED;
    }
    
    
    if(UseGoldenDeathCrossStrategy)
{
    // Check if there are enough bars to calculate the EMA
    if(Bars(_Symbol, PERIOD_CURRENT) < GDC_SlowEMAPeriod)
    {
        Print("Not enough bars for EMA calculation for Golden Death Cross");
        return INIT_FAILED;
    }
    GDC_fastEMAHandle = iMA(_Symbol, PERIOD_CURRENT, GDC_FastEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
    GDC_slowEMAHandle = iMA(_Symbol, PERIOD_CURRENT, GDC_SlowEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
    if(GDC_fastEMAHandle < 0 || GDC_slowEMAHandle < 0)
    {
        Print("Failed to create EMA handles for Golden Death Cross. Error: ", GetLastError());
        return INIT_FAILED;
    }
}

    // Resize arrays for MACD buffers
    ArrayResize(ExtMacdBuffer, DivergenceLookBack);
    ArrayResize(ExtSignalBuffer, DivergenceLookBack);

    Print("Trend Constraint Expert initialized.");
    return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    IndicatorRelease(rsi_handle);
    IndicatorRelease(handle);
    IndicatorRelease(macd_handle);
    Print("Trend Constraint Expert deinitialized.");
}

//+------------------------------------------------------------------+
//| Check Golden/Death Cross Trading Logic                           |
//+------------------------------------------------------------------+
void CheckGoldenDeathCross()
{
    double fastEMAArray[2], slowEMAArray[2];
    double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
    double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
    double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

    if(CopyBuffer(GDC_fastEMAHandle, 0, 0, 2, fastEMAArray) <= 0 ||
       CopyBuffer(GDC_slowEMAHandle, 0, 0, 2, slowEMAArray) <= 0)
    {
        Print("Failed to copy EMA data for Golden Death Cross. Error: ", GetLastError());
        return;
    }

    bool hasPosition = PositionSelect(_Symbol);

    if(!hasPosition)
    {
        if(fastEMAArray[0] > slowEMAArray[0] && fastEMAArray[1] <= slowEMAArray[1]) // Death Cross 
        {
            double sl = NormalizeDouble(ask + GDC_StopLossPips * point, _Digits);
            if(!trade.Sell(GDC_LotSize, _Symbol, ask, sl ))
                Print("Sell order error for Golden Death Cross ", GetLastError());
            else
                Print("Sell order opened for Golden Death Cross with SL ", GDC_StopLossPips, " pips");
        }
        else if(fastEMAArray[0] < slowEMAArray[0] && fastEMAArray[1] >= slowEMAArray[1]) // Golden Cross
        {
            double sl = NormalizeDouble(bid - GDC_StopLossPips * point, _Digits);
            if(!trade.Buy(GDC_LotSize, _Symbol, bid, sl ))
                Print("Buy order error for Golden Death Cross ", GetLastError());
            else
                Print("Buy order opened for Golden Death Cross with SL ", GDC_StopLossPips, " pips");
        }
    }
    else
    {
        if((PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && fastEMAArray[0] < slowEMAArray[0]) ||
           (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && fastEMAArray[0] > slowEMAArray[0]))
        {
            ulong ticket = PositionGetInteger(POSITION_TICKET);
            if(!trade.PositionClose(ticket))
                Print("Failed to close position for Golden Death Cross (Ticket: ", ticket, "). Error: ", GetLastError());
            else  
                Print("Position closed for Golden Death Cross: ", ticket);
        }
    }
}

//+------------------------------------------------------------------+
//| Check Trend Following Strategy                                   |
//+------------------------------------------------------------------+
void CheckTrendFollowing()
{
   if (PositionsTotal() >= 2) return; // Ensure no more than 2 orders from this strategy

    double rsi_value;
    double rsi_values[];
    if (CopyBuffer(rsi_handle, 0, 0, 1, rsi_values) <= 0)
    {
        Print("Failed to get RSI value. Error: ", GetLastError());
        return;
    }
    rsi_value = rsi_values[0];

    double ma_short = iMA(_Symbol, PERIOD_CURRENT, 50, 0, MODE_EMA, PRICE_CLOSE);
    double ma_long = iMA(_Symbol, PERIOD_CURRENT, 200, 0, MODE_EMA, PRICE_CLOSE);

    bool is_uptrend = ma_short > ma_long;
    bool is_downtrend = ma_short < ma_long;

    if (is_uptrend && rsi_value < RSI_Oversold)
    {
        double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
        double stopLossPrice = currentPrice - StopLoss * _Point;
        double takeProfitPrice = currentPrice + TakeProfit * _Point;

        // Corrected Buy method call with 6 parameters
        if (trade.Buy(Lots, _Symbol, 0, stopLossPrice, takeProfitPrice, "Trend Following Buy"))
        {
            Print("Trend Following Buy order placed.");
        }
    }
    else if (is_downtrend && rsi_value > RSI_Overbought)
    {
        double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double stopLossPrice = currentPrice + StopLoss * _Point;
        double takeProfitPrice = currentPrice - TakeProfit * _Point;

        // Corrected Sell method call with 6 parameters
        if (trade.Sell(Lots, _Symbol, 0, stopLossPrice, takeProfitPrice, "Trend Following Sell"))
        {
            Print("Trend Following Sell order placed.");
        }
    }
}

//+------------------------------------------------------------------+
//| Check Breakout Strategy                                          |
//+------------------------------------------------------------------+
void CheckBreakoutTrading()
{
    if (PositionsTotal() >= 2) return; // Ensure no more than 2 orders from this strategy

    ArrayResize(ExtUpBuffer, 2);
    ArrayResize(ExtDnBuffer, 2);

    if (CopyBuffer(handle, 0, 0, 2, ExtUpBuffer) <= 0 || CopyBuffer(handle, 2, 0, 2, ExtDnBuffer) <= 0)
    {
        Print("Error reading Donchian Channel buffer. Error: ", GetLastError());
        return;
    }

    double closePrice = iClose(_Symbol, PERIOD_CURRENT, 0);
    double lastOpen = iOpen(_Symbol, PERIOD_D1, 1);
    double lastClose = iClose(_Symbol, PERIOD_D1, 1);

    bool isBullishDay = lastClose > lastOpen;
    bool isBearishDay = lastClose < lastOpen;

    if (isBullishDay && closePrice > ExtUpBuffer[1])
    {
        double stopLoss = closePrice - pipsToStopLoss * _Point;
        double takeProfit = closePrice + pipsToTakeProfit * _Point;
        if (trade.Buy(LotSize, _Symbol, 0, stopLoss, takeProfit, "Breakout Buy") > 0)
        {
            Print("Breakout Buy order placed.");
        }
    }
    else if (isBearishDay && closePrice < ExtDnBuffer[1])
    {
        double stopLoss = closePrice + pipsToStopLoss * _Point;
        double takeProfit = closePrice - pipsToTakeProfit * _Point;
        if (trade.Sell(LotSize, _Symbol, 0, stopLoss, takeProfit, "Breakout Sell") > 0)
        {
            Print("Breakout Sell order placed.");
        }
    }
}

//+------------------------------------------------------------------+
//| Check Divergence Trading                                         |
//+------------------------------------------------------------------+
bool CheckBullishRegularDivergence()
{
    double priceLow1 = iLow(_Symbol, PERIOD_CURRENT, 2);
    double priceLow2 = iLow(_Symbol, PERIOD_CURRENT, DivergenceLookBack);
    double macdLow1 = ExtMacdBuffer[2];
    double macdLow2 = ExtMacdBuffer[DivergenceLookBack - 1];

    return (priceLow1 < priceLow2 && macdLow1 > macdLow2);
}

bool CheckBullishHiddenDivergence()
{
    double priceLow1 = iLow(_Symbol, PERIOD_CURRENT, 2);
    double priceLow2 = iLow(_Symbol, PERIOD_CURRENT, DivergenceLookBack);
    double macdLow1 = ExtMacdBuffer[2];
    double macdLow2 = ExtMacdBuffer[DivergenceLookBack - 1];

    return (priceLow1 > priceLow2 && macdLow1 < macdLow2);
}

bool CheckBearishRegularDivergence()
{
    double priceHigh1 = iHigh(_Symbol, PERIOD_CURRENT, 2);
    double priceHigh2 = iHigh(_Symbol, PERIOD_CURRENT, DivergenceLookBack);
    double macdHigh1 = ExtMacdBuffer[2];
    double macdHigh2 = ExtMacdBuffer[DivergenceLookBack - 1];

    return (priceHigh1 > priceHigh2 && macdHigh1 < macdHigh2);
}

bool CheckBearishHiddenDivergence()
{
    double priceHigh1 = iHigh(_Symbol, PERIOD_CURRENT, 2);
    double priceHigh2 = iHigh(_Symbol, PERIOD_CURRENT, DivergenceLookBack);
    double macdHigh1 = ExtMacdBuffer[2]; 
    double macdHigh2 = ExtMacdBuffer[DivergenceLookBack - 1];

    return (priceHigh1 < priceHigh2 && macdHigh1 > macdHigh2);
}

void CheckDivergenceTrading()
{
    if (!UseDivergenceStrategy) return;

    // Check if no position is open or if less than 3 positions are open
    int openDivergencePositions = CountOrdersByMagic(DivergenceMagicNumber);
    if (openDivergencePositions == 0 || openDivergencePositions < 3)
    {
        int barsAvailable = Bars(_Symbol, PERIOD_CURRENT);
        if (barsAvailable < DivergenceLookBack * 2)
        {
            Print("Not enough data bars for MACD calculation.");
            return;
        }

        int attempt = 0;
        while(attempt < 6)
        {
            if (CopyBuffer(macd_handle, 0, 0, DivergenceLookBack, ExtMacdBuffer) > 0 &&
                CopyBuffer(macd_handle, 1, 0, DivergenceLookBack, ExtSignalBuffer) > 0)
                break; 
            
            Print("Failed to copy MACD buffer, retrying...");
            Sleep(1000);
            attempt++;
        }
        if(attempt == 6)
        {
            Print("Failed to copy MACD buffers after ", attempt, " attempts.");
            return;
        }

        if(TimeCurrent() == iTime(_Symbol, PERIOD_CURRENT, 0))
        {
            Print("Skipping trade due to incomplete bar data.");
            return;
        }

        double currentClose = iClose(_Symbol, PERIOD_CURRENT, 0);
        double dailyClose = iClose(_Symbol, PERIOD_D1, 0);
        double dailyOpen = iOpen(_Symbol, PERIOD_D1, 0);
        bool isDailyBullish = dailyClose > dailyOpen;
        bool isDailyBearish = dailyClose < dailyOpen;

        // Only proceed with buy orders if D1 is bullish
        if (isDailyBullish)
        {
            if ((CheckBullishRegularDivergence() && ExtMacdBuffer[0] > ExtSignalBuffer[0]) ||
                CheckBullishHiddenDivergence())
            {
                ExecuteDivergenceOrder(true);
            }
        }

        // Only proceed with sell orders if D1 is bearish
        if (isDailyBearish)
        {
            if ((CheckBearishRegularDivergence() && ExtMacdBuffer[0] < ExtSignalBuffer[0]) ||
                CheckBearishHiddenDivergence())
            {
                ExecuteDivergenceOrder(false);
            }
        }
    }
    else
    {
        Print("Divergence strategy: Maximum number of positions reached.");
    }
}

void ExecuteDivergenceOrder(bool isBuy)
{
    // Ensure the magic number is set for the trade
    trade.SetExpertMagicNumber(DivergenceMagicNumber);
    
    double currentPrice = isBuy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
    double stopLossPrice = isBuy ? currentPrice - DivergenceStopLoss * _Point : currentPrice + DivergenceStopLoss * _Point;
    double takeProfitPrice = isBuy ? currentPrice + DivergenceTakeProfit * _Point : currentPrice - DivergenceTakeProfit * _Point;

    if (isBuy)
    {
        if (trade.Buy(DivergenceLots, _Symbol, 0, stopLossPrice, takeProfitPrice, "Divergence Buy"))
        {
            Print("Divergence Buy order placed.");
        }
    }
    else
    {
        if (trade.Sell(DivergenceLots, _Symbol, 0, stopLossPrice, takeProfitPrice, "Divergence Sell"))
        {
            Print("Divergence Sell order placed.");
        }
    }
}

int CountOrdersByMagic(int magic)
{
    int count = 0;
    for (int i = 0; i < PositionsTotal(); i++)
    {
        ulong ticket = PositionGetTicket(i);
        if (PositionSelectByTicket(ticket))
        {
            if (PositionGetInteger(POSITION_MAGIC) == magic)
            {
                count++;
            }
        }
    }
    return count;
}

//+------------------------------------------------------------------+
//| Profit Locking Logic                                             |
//+------------------------------------------------------------------+
void LockProfits()
{
    for (int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if (PositionSelectByTicket(ticket))
        {
            double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            double currentProfit = PositionGetDouble(POSITION_PROFIT);
            double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
            
            // Convert profit to points
            double profitPoints = MathAbs(currentProfit / _Point);

            // Check if profit has exceeded 100 points
            if (profitPoints >= 100)
            {
                double newStopLoss;
                
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                    newStopLoss = entryPrice + profitLockerPoints * _Point; // 20 points above entry for buys
                }
                else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
                {
                    newStopLoss = entryPrice - profitLockerPoints * _Point; // 20 points below entry for sells
                }
                else
                {
                    continue; // Skip if not a buy or sell position
                }

                // Modify stop loss only if the new stop loss is more protective
                double currentStopLoss = PositionGetDouble(POSITION_SL);
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                    if (currentStopLoss < newStopLoss || currentStopLoss == 0)
                    {
                        if (trade.PositionModify(ticket, newStopLoss, PositionGetDouble(POSITION_TP)))
                        {
                            Print("Profit locking for buy position: Stop Loss moved to ", newStopLoss);
                        }
                    }
                }
                else // POSITION_TYPE_SELL
                {
                    if (currentStopLoss > newStopLoss || currentStopLoss == 0)
                    {
                        if (trade.PositionModify(ticket, newStopLoss, PositionGetDouble(POSITION_TP)))
                        {
                            Print("Profit locking for sell position: Stop Loss moved to ", newStopLoss);
                        }
                    }
                }
            }
        }
    }
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    if (UseTrendFollowingStrategy)
        CheckTrendFollowing();
    if (UseBreakoutStrategy)
        CheckBreakoutTrading();
    if (UseDivergenceStrategy)
        CheckDivergenceTrading();
    if(UseGoldenDeathCrossStrategy)
        CheckGoldenDeathCross();

}


テストと最適化の結果

Trend Constraint EAを起動

Trend Constraint EA:デフォルト設定でチャートに追加

戦略ビジュアライザー

ゴールデンクロスとデスクロス戦略は、ストラテジーテスターの他の戦略の一部として視覚化されます。


結論

EAコードは継続的に拡張可能ですが、その結果、処理がより複雑になり、リソースの消費も増大する可能性があります。これは、リソース管理技術の高度化が求められていることを示しています。こうした複雑性に対応する手段として、AIモデルの統合は非常に有効であり、高い効果が期待できます。本プロジェクトでは、反転のチャンスを捉えるための最も一般的な戦略の一つである「ゴールデンクロスおよびデッドクロス戦略」を、Trend Constraint EAに統合することに成功しました。

第1回から今回までにわたる基礎構築により、このEAの土台が形成されました。しかし、最適な結果を得るためには、各種設定の最適化や特定の構造の見直しをおこない、EAモデルをさらに洗練させる必要があります。このようなアプローチにより、本EAは教育および実験用途として非常に価値のあるツールとなります。なお、本EAは収益性を保証するものではなく、あくまで教育および研究を目的としています。

今後の記事では、これまでに構築した戦略のさらなる改良に加え、機械学習技術の導入によって、EAの機能とパフォーマンスの向上を目指していく予定です。

はじめに戻る

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

添付されたファイル |
MQL5取引ツールキット(第5回):ポジション関数による履歴管理EX5ライブラリの拡張 MQL5取引ツールキット(第5回):ポジション関数による履歴管理EX5ライブラリの拡張
エクスポート可能なEX5関数を作成して、過去のポジションデータを効率的にクエリおよび保存する方法を解説します。このステップバイステップのガイドでは、直近にクローズされたポジションの主要なプロパティを取得するモジュールを開発し、HistoryManagement EX5ライブラリを拡張していきます。対象となるプロパティには、純利益、取引時間、ピップ単位でのストップロスやテイクプロフィット、利益値、その他多くの重要な情報が含まれます。
ログレコードをマスターする(第2回):ログのフォーマット処理 ログレコードをマスターする(第2回):ログのフォーマット処理
この記事では、ライブラリ内でログフォーマッターを作成し、適用する方法について詳しく解説します。フォーマッターの基本構造から実践的な実装例まで幅広く取り上げます。この記事を読み終える頃には、ライブラリ内でログを整形するために必要な知識を習得し、その裏側で何がどのように動作しているのかを理解できるようになります。
DiscordとMetaTrader 5の統合:リアルタイム通知機能を備えたトレーディングボットの構築 DiscordとMetaTrader 5の統合:リアルタイム通知機能を備えたトレーディングボットの構築
この記事では、MetaTrader 5とDiscordサーバーを統合し、どこからでもリアルタイムで取引通知を受信する方法について解説します。Discordへのアラート配信を有効にするための、MetaTrader 5側およびDiscord側の設定方法を詳しく説明します。また、このような通知ソリューションでWebRequestやWebhookを利用する際に生じるセキュリティ上の注意点についても取り上げます。
古典的な戦略を再構築する(第13回):移動平均線のクロスオーバーにおける遅延の最小化 古典的な戦略を再構築する(第13回):移動平均線のクロスオーバーにおける遅延の最小化
移動平均クロスオーバーは、私たちのコミュニティにおけるトレーダーの間で広く知られている戦略ですが、その基本的な仕組みは誕生以来ほとんど変化していません。本稿では、この戦略に存在する“遅延”を最小限に抑えることを目的とした、わずかながらも重要な改良について紹介します。元の戦略を愛用しているトレーダーの方々にも、今回ご紹介する洞察をもとに、戦略の見直しを検討していただければ幸いです。同一の期間を持つ2つの移動平均を使用することで、戦略の根本的な原則を損なうことなく、遅延を大幅に削減することが可能になります。