English Русский Deutsch
preview
Candlestick Trend Constraintモデルの構築(第9回):マルチ戦略エキスパートアドバイザー(II)

Candlestick Trend Constraintモデルの構築(第9回):マルチ戦略エキスパートアドバイザー(II)

MetaTrader 5 | 22 1月 2025, 13:37
157 0
Clemence Benjamin
Clemence Benjamin

内容


はじめに

20世紀にリチャード・ドンチャンは、金融市場の研究を通じてトレンドフォロー戦略を確立し、後にそれがドンチャンチャネル(ドンチャンチャネル)へと発展しました。彼の研究については以前の記事で簡単に触れましたが、今回は彼の理論に基づいた戦略の実装に焦点を当てていきます。さまざまな情報源によると、ドンチャンチャネルはその枠組みの中で複数の戦略を包含していると考えられています。ドンチャンチャネルに関する豊富な文献は、この理論が現代の取引においても依然として有効であることを示唆しています。ドンチャンチャネル戦略を統合することで、Trend Constraint EAの機会を拡大し、収益性と多様な市場環境への適応力を高めることを目指します。

ドンチャンチャネルに基づく有名な戦略には、ブレイクアウト戦略、クロール戦略、ミーンリバージョン戦略などがあります。Rayner Teoのような著名なトレーダーも、これらのチャネルを効果的に活用する方法を学ぶための教育コンテンツを提供しています。

ンチャンチャネルはMetaTrader 5プラットフォームで無料のインジケーターとして利用可能であり、この点が本プロジェクトにおいて大きな利点となります。このアクセスを活用することで、インジケーターのソースコードを利用し、その構造に対するより深い理解を得ることができ、Trend Constraint EAへの適用が容易になります。コードが複雑化していく中で、私たちは新しい戦略を独立して開発し、メインプログラムに統合する前にその調整をおこないます。今後のセグメントでは、理論の理解をさらに深め、アルゴリズムを一層強化していきます。


ドンチャンチャネルとは

ドンチャンチャネルは、テクニカル分析における指標で、上バンド、中バンド、下バンドの3本の線から成り、価格の動きの中で高値と安値を示すために使用されます。前述のように、トレンドフォロー取引の先駆者であるRichard Donchianによる功績が大きいです。以下は、3本のラインの概要です。
  • 上バンド:この線は、指定した期間(例えば過去20期間)の最高値を示します。
  • 下バンド:この線は、同じ指定期間の最低値を示します。
  • ミドルバンド:上バンドと下バンドの平均として計算されることが多く、基準点として使われることがあります。
以下は、MetaTrader 5でのドンチャンチャネルの適用例です。


ドンチャンチャネル

ドンチャンチャネルライン


MetaTrader 5でドンチャンチャネルにアクセスする

ドンチャンチャネルには、通常、下の画像に示すように、ナビゲーターウィンドウの[Indicators]フォルダからアクセスします。

MetaTrade 5でドンチャンチャネルにアクセスする

MetaTrader 5ナビゲーター

アクセスしたら、下の画像のように、ドンチャンチャネルを使用するチャート上にドラッグできます。この例では、Volatility 150 (1s)指数のデフォルトチャンネル設定を使用しています。

チャートにドンチャンチャネルを追加する

MetaTrader 5のチャートにドンチャンチャネルを追加する

このインジケーターをチャートに適用する理由は、プライスアクションとチャンネルの関係を分析することです。これは、アルゴリズム開発を始める前に、エントリールールを理解する助けとなります。次に、MetaEditor 5でインジケーターのソースコードにアクセスする方法を解説します。


MetaEditorでドンチャンチャネルのソースコードにアクセスする

MetaEditor 5でソースファイルを編集するには、ナビゲータウィンドウを開き、MetaTrader 5プラットフォームと同様に[Free Indicators]タブからインジケーターを見つけます。ただし、ここで扱うのはコンパイル済みバージョンではなく、ソースファイルである点が重要な違いです。ファイルをダブルクリックするとコードが表示されます。手順をより分かりやすくするため、以下の画像も併せてご参照ください。

MetaEditorのドンチャンチャネル

MetaEditorでDonchain Channelのソースコードにアクセスする


Trend Constraint EAにおけるドンチャンチャネル戦略の実装

新しいツールを取り入れる際のルールを確立し、議論を形成する指針となるパラメータを明確にすることは非常に重要です。私たちは常に、最初に制約条件を定義します。例えば、D1のローソク足が強気の場合のみ買い、D1のローソク足が弱気の場合のみ売る、といった具合です。このルールを念頭に置きつつ、まずは制約条件を特定した上で、市場のトレンドに沿ったチャンネルのセットアップから得られる注文機会を模索します。 具体的には、強気のD1シナリオにおいて以下に注目します。

  1. 価格がチャンネルの下限にタッチした場合、高い勝率で買い注文を発生させる。
  2. 中程度の勝率を伴うミドルラインでのリバウンドを狙う。
  3. 勝率は低いものの、上限のブレイクアウトを活用する。

これら3つの戦略について、以下の画像で説明しています。

Donchian Channel by Benjc Trade Advisor

ドンチャンチャンネル戦略

このプレゼンテーションでは、ブレイクアウト手法を使用します。これは、市場価格がチャンネルの外側の境界を超えて終値した場合の状況を監視するものです。それでは、インジケーターのソースコードを確認し、関連するバッファを特定していきましょう。


インジケーターソースコードのプレビュー

デフォルトのドンチャンチャネルインジケーターは、MetaTrader 5プラットフォームで利用可能です。MetaEditor 5では、先に説明した方法で直接アクセスすることもできます。

//+------------------------------------------------------------------+
//|                                             Donchian Channel.mq5 |
//|                              Copyright 2009-2024, MetaQuotes Ltd |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright   "2009-2024, MetaQuotes Ltd"
#property link        "http://www.mql5.com"
#property description "Donchian Channel"
//---
#property indicator_chart_window
#property indicator_buffers 3
#property indicator_plots   3
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrBlue
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrGray
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrRed
//--- labels
#property indicator_label1  "Upper Donchian"
#property indicator_label2  "Middle Donchian"
#property indicator_label3  "Lower Donchian"

//--- input parameter
input int  InpDonchianPeriod=20;    // period of the channel
input bool InpShowLabel     =true;  // show price of the level

//--- indicator buffers
double    ExtUpBuffer[];
double    ExtMdBuffer[];
double    ExtDnBuffer[];

//--- unique prefix to identify indicator objects
string ExtPrefixUniq;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- define buffers
   SetIndexBuffer(0, ExtUpBuffer);
   SetIndexBuffer(1, ExtMdBuffer);
   SetIndexBuffer(2, ExtDnBuffer);

//--- set a 1-bar offset for each line
   PlotIndexSetInteger(0, PLOT_SHIFT, 1);
   PlotIndexSetInteger(1, PLOT_SHIFT, 1);
   PlotIndexSetInteger(2, PLOT_SHIFT, 1);

//--- indicator name
   IndicatorSetString(INDICATOR_SHORTNAME, "Donchian Channel");
//--- number of digits of indicator value
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);

//--- prepare prefix for objects
   string number=StringFormat("%I64d", GetTickCount64());
   ExtPrefixUniq=StringSubstr(number, StringLen(number)-4);
   ExtPrefixUniq=ExtPrefixUniq+"_DN";
   Print("Indicator \"Donchian Channels\" started, prefix=", ExtPrefixUniq);

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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 &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- if the indicator has previously been calculated, start from the bar preceding the last one
   int start=prev_calculated-1;

//--- if this is the first calculation of the indicator, then move by InpDonchianPeriod bars form the beginning
   if(prev_calculated==0)
      start=InpDonchianPeriod+1;

//--- calculate levels for all bars in a loop
   for(int i=start; i<rates_total; i++)
     {
      //--- get max/min values for the last InpDonchianPeriod bars
      int    highest_bar_index=ArrayMaximum(high, i-InpDonchianPeriod+1, InpDonchianPeriod);
      int    lowest_bar_index=ArrayMinimum(low, i-InpDonchianPeriod+1, InpDonchianPeriod);;
      double highest=high[highest_bar_index];
      double lowest=low[lowest_bar_index];

      //--- write values into buffers
      ExtUpBuffer[i]=highest;
      ExtDnBuffer[i]=lowest;
      ExtMdBuffer[i]=(highest+lowest)/2;
     }

//--- draw labels on levels
   if(InpShowLabel)
     {
      ShowPriceLevels(time[rates_total-1], rates_total-1);
      ChartRedraw();
     }

//--- succesfully calculated
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- delete all our graphical objects after use
   Print("Indicator \"Donchian Channels\" stopped, delete all objects with prefix=", ExtPrefixUniq);
   ObjectsDeleteAll(0, ExtPrefixUniq, 0, OBJ_ARROW_RIGHT_PRICE);
   ChartRedraw(0);
  }
//+------------------------------------------------------------------+
//|  Show prices' levels                                             |
//+------------------------------------------------------------------+
void ShowPriceLevels(datetime time, int last_index)
  {
   ShowRightPrice(ExtPrefixUniq+"_UP", time, ExtUpBuffer[last_index], clrBlue);
   ShowRightPrice(ExtPrefixUniq+"_MD", time, ExtMdBuffer[last_index], clrGray);
   ShowRightPrice(ExtPrefixUniq+"_Dn", time, ExtDnBuffer[last_index], clrRed);
  }
//+------------------------------------------------------------------+
//| Create or Update "Right Price Label" object                      |
//+------------------------------------------------------------------+
bool ShowRightPrice(const string name, datetime time, double price, color clr)
  {
   if(!ObjectCreate(0, name, OBJ_ARROW_RIGHT_PRICE, 0, time, price))
     {
      ObjectMove(0, name, 0, time, price);
      return(false);
     }

//--- make the label size adaptive
   long scale=2;
   if(!ChartGetInteger(0, CHART_SCALE, 0, scale))
     {
      //--- output an error message to the Experts journal
      Print(__FUNCTION__+", ChartGetInteger(CHART_SCALE) failed, error = ", GetLastError());
     }
   int width=scale>1 ? 2:1;  // if chart scale > 1, then label size = 2

   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
   ObjectSetInteger(0, name, OBJPROP_BACK, false);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, name, OBJPROP_SELECTED, false);
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
   ObjectSetInteger(0, name, OBJPROP_ZORDER, 0);

   return(true);
  }
//+------------------------------------------------------------------+

上記のカスタムコードは、ドンチャンチャンネルを実装しています。このコードでは、指定した期間内の最高値を表す上側のチャンネルライン、同じ期間内の最安値を表す下側のチャンネルライン、そして上側と下側のラインの平均を計算した中央ラインの3つのラインを算出し、表示します。このインジケーターは、潜在的なブレイクアウトポイントを視覚化するよう設計されており、チャンネル期間やチャート上に価格ラベルを表示するオプションなど、カスタマイズ可能な入力パラメータを備えています。コードには、インジケーターバッファやプロパティを設定するための初期化関数、チャート上の各バーに対応するチャンネルラインを更新する計算ループ、グラフィカルオブジェクトおよび価格ラベルを管理するための関数が含まれています。全体として、このインジケーターはトレーダーに、過去の価格水準に基づいてトレンドや潜在的な取引機会を特定するためのツールを提供します。

簡単に理解できるよう、上記のコードスニペットを分割し、次のセクションでさらなる開発を進めるために使用するバッファを特定していきます。

バッファの宣言

ExtUpBuffer[]ExtMdBuffer[]ExtDnBuffer[]の3つのバッファが宣言されており、それぞれ上、中、下のドンチャンチャネルの値を格納します。

double ExtUpBuffer[];
double ExtMdBuffer[];
double ExtDnBuffer[];

OnInitでのバッファ設定

SetIndexBuffer関数はチャートのプロット(線)をバッファにリンクし、チャート上に描画更新できるようにします。

SetIndexBuffer(0, ExtUpBuffer);
SetIndexBuffer(1, ExtMdBuffer);
SetIndexBuffer(2, ExtDnBuffer);

OnCalculateでのバッファ値計算

このコードは、定義された期間の最高値、最安値、平均値を計算し、各バーのそれぞれのバッファに格納します。

for(int i=start; i<rates_total; i++)
{
   //--- calculate highest and lowest for the Donchian period
   int highest_bar_index = ArrayMaximum(high, i-InpDonchianPeriod+1, InpDonchianPeriod);
   int lowest_bar_index  = ArrayMinimum(low, i-InpDonchianPeriod+1, InpDonchianPeriod);
   double highest = high[highest_bar_index];
   double lowest  = low[lowest_bar_index];

   //--- assign values to buffers
   ExtUpBuffer[i] = highest;
   ExtDnBuffer[i] = lowest;
   ExtMdBuffer[i] = (highest + lowest) / 2;
}

買いシグナルを生成する際、この戦略では上側バッファ(ExtUpBuffer)を使用し、価格が上側のドンチャンラインを上回ってクローズした場合に買いをトリガーします。逆に、売りシグナルは下側バッファ(ExtDnBuffer)を使用し、価格が下側のドンチャンラインを下回ってクローズした場合にトリガーされます。さらに、ミドルチャンネル(ExtMdBuffer)はフィルタとして機能し、価格がミドルチャンネルの上にある場合にのみ買い取引を許可することで、より強い上昇トレンドを示すよう戦略を洗練させることが可能です。この内容を踏まえ、EAの開発を進める準備が整ったと確信しています。


コード開発

ドンチャンチャネルがビルトインインジケーターとして利用可能であるため、インジケーターのバッファに焦点を当て、取引実行のシグナルを生成するEAを開発するプロセスが簡素化されます。前述の通り、分かりやすさを重視して、まずドンチャンチャネルに基づいたEAを開発し、その後Trend Constraint EAと統合を進めます。本日は、ドンチャンチャンネルを用いたブレイクアウト戦略に焦点を当てます。ブレイクアウトの条件はシンプルで、価格がチャンネルの極端なバンドを超えて引けた際に発生します。さまざまな戦略については、先ほどの画像で詳しく説明していますので、そちらも併せてご参照ください。

最初のステップとして、下図のようにMetaEditor 5で新規ファイルを作成します。このブレイクアウト戦略に主眼を置くため、ファイル名は「BreakoutEA」としました。

新しいEA

MetaEditorで新しいEAプログラムを開始する

私は、このEA構築プロセスを大きく5つのセグメントに分割しています。最初にEAを起動する際は、基本的なテンプレートから作業を開始し、他の部分にはチェックを入れません。そのテンプレートの下で、最終的に統合される重要な構成要素について説明します。

この基本テンプレートには、#property strictディレクティブのような必須プロパティが含まれています。このディレクティブは、コンパイラがデータ型を正しく使用していることを保証し、型の不一致による潜在的なプログラミングエラーを防ぐのに役立ちます。さらに重要な点として、取引業務を効率的に管理するためのツールを提供する取引ライブラリも含まれています。これらの手順は、開発プロセスの強固な基盤を築く上で不可欠です。

//+------------------------------------------------------------------+
//|                                                   BreakoutEA.mq5 |
//|                                Copyright 2024, Clemence Benjamin |
//|             https://www.mql5.com/ja/users/billionaire2024/seller |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Clemence Benjamin"
#property link      "https://www.mql5.com/ja/users/billionaire2024/seller"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

1. エキスパートアドバイザーの初期化

初期化セグメントでは、EAの入力パラメータが定義されます。これらのパラメータを使用することで、EAを自分の好みに合わせて設定することができます。重要な入力には、ドンチャンチャネルの期間、リスクとリターンの比率、取引のロットサイズ、ストップロスとテイクプロフィットのピップ値が含まれます。

// Input parameters
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
input double pipsToTakeProfit = 30;    // Take profit in pips

// Indicator handle storage
int handle;
string indicatorKey;

// Expert initialization function
int OnInit() {
    // Create a unique key for the indicator based on the symbol and period
    indicatorKey = StringFormat("%s_%d", Symbol(), InpDonchianPeriod);
    
    // Load the Donchian Channel indicator
    handle = iCustom(Symbol(), Period(), "Free Indicators\\Donchian Channel", InpDonchianPeriod);
    
    // Check if the indicator loaded successfully
    if (handle == INVALID_HANDLE) {
        Print("Failed to load the indicator. Error: ", GetLastError());
        return INIT_FAILED;
    }
    return INIT_SUCCEEDED;
}

取引銘柄と定義された期間に基づいて一意のキーが作成され、インジケーターの各インスタンスを確実に区別できるようにします。iCustom()関数は、MetaTraderディレクトリ内のパス(Free Indicators\\Donchian Channel) を指定して、Donchian Channelインジケーターをロードするために使用されます。ロードに失敗した場合(INVALID_HANDLEで示される)、エラーメッセージが表示され、初期化に失敗します。その結果、必要なインジケーターデータがない状態では、EAの実行が停止します。インジケーターはルートフォルダには存在しないため、保存場所を正確に指定することが重要です。保存場所を指定しない場合、以下のようなエラーが発生します。ほとんどの場合、インジケーターのロードに失敗すると、EAは正常に実行されません。

//Typical Journal log when the EA fails to locate an indicator in the root indicators storage.
2024.10.20 08:49:04.117 2022.01.01 00:00:00   cannot load custom indicator 'Donchian Channel' [4802]
2024.10.20 08:49:04.118 2022.01.01 00:00:00   indicator create error in 'DonchianEA.mq5' (1,1)
2024.10.20 08:49:04.118 OnInit critical error
2024.10.20 08:49:04.118 tester stopped because OnInit failed

2. クリーンアップと初期化

クリーンアップセグメントは、EAが使用したリソースを解放する役割を担います。これは、EAが削除される際やMetaTrader 5がシャットダウンする際に呼び出されるOnDeinit()関数内で実行されます。この関数では、IndicatorRelease()を使用してインジケーターハンドルが確実に解放されることを確認します。リソースを徹底的にクリーンアップすることは、メモリリークを防ぎ、プラットフォーム全体のパフォーマンスを維持するために非常に重要です。

// Expert deinitialization function
void OnDeinit(const int reason) {
    // Release the indicator handle to free up resources
    IndicatorRelease(handle);
}

3. 主な実行ロジック

主な実行ロジックはOnTick()関数にあり、市場のティックまたは価格変動があるたびにトリガーされます。この関数では、PositionsTotal()関数を使用して、現在開かれているポジションがあるかどうかを確認します。ポジションが開かれていない場合、プログラムは別の関数を呼び出して取引条件を評価します。この構造により、一度に複数の取引が開かれることを防ぎ、過剰取引を避けることができます。

// Main execution function with block-based control
void OnTick() {
    // Check if any positions are currently open
    if (PositionsTotal() == 0) {
        CheckTradingConditions();
    }
}

4. 取引条件の評価

このセグメントでは、EAは市場状況をドンチャンチャネルの上下バンドに照らし合わせてチェックします。インジケーターバッファのサイズは、最新のデータに対応するように変更されます。CopyBuffer()関数は、ドンチャンチャネルから最新の値を取得します。

// Check trading conditions based on indicator buffers
void CheckTradingConditions() {
    double ExtUpBuffer[], ExtDnBuffer[];  // Buffers for upper and lower Donchian bands

    // Resize buffers to hold the latest data

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

    // Get the latest values from the Donchian Channel
    if (CopyBuffer(handle, 0, 0, 2, ExtUpBuffer) <= 0 || CopyBuffer(handle, 2, 0, 2, ExtDnBuffer) <= 0) {
        Print("Error reading indicator buffer. Error: ", GetLastError());
        return;
    }

    // Get the close price of the current candle
    double closePrice = iClose(Symbol(), Period(), 0);

    // Buy condition: Closing price is above the upper Donchian band
    if (closePrice > ExtUpBuffer[1]) {
        double stopLoss = closePrice - pipsToStopLoss * _Point; // Calculate stop loss
        double takeProfit = closePrice + pipsToTakeProfit * _Point; // Calculate take profit
        OpenBuy(LotSize, stopLoss, takeProfit);
    }

    // Sell condition: Closing price is below the lower Donchian band
    if (closePrice < ExtDnBuffer[1]) {
        double stopLoss = closePrice + pipsToStopLoss * _Point; // Calculate stop loss
        double takeProfit = closePrice - pipsToTakeProfit * _Point; // Calculate take profit
        OpenSell(LotSize, stopLoss, takeProfit);
    }
}

現在の終値を取得します。これは取引シグナルを評価するために重要です。取引条件は、終値が上限バンドを超えれば買い注文が発注され、下限バンドを下回れば売り注文が発注されるように定義されています。ストップロスとテイクプロフィットのレベルは、ユーザーが定義したピップ値に基づいて計算され、リスクを効果的に管理します。

5. 発注関数

注文発注関数は、売買取引の実行を担当します。各関数はCTradeクラスのメソッドを使用して取引を試みるため、取引管理が簡素化されます。取引の実行後、プログラムは注文が成功したかどうかを確認します。失敗した場合は、エラーメッセージが表示され、トレーダーに失敗を通知します。これらの関数は取引ロジックをカプセル化し、前述の条件に基づいて注文を発注するための明確なインターフェイスを提供します。

// Open a buy order
void OpenBuy(double lotSize, double stopLoss, double takeProfit) {
    // Attempt to open a buy order
    if (trade.Buy(lotSize, Symbol(), 0, stopLoss, takeProfit, "Buy Order")) {
        Print("Buy order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
    } else {
        Print("Failed to open buy order. Error: ", GetLastError());
    }
}

// Open a sell order
void OpenSell(double lotSize, double stopLoss, double takeProfit) {
    // Attempt to open a sell order
    if (trade.Sell(lotSize, Symbol(), 0, stopLoss, takeProfit, "Sell Order")) {
        Print("Sell order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
    } else {
        Print("Failed to open sell order. Error: ", GetLastError());
    }
}

私たちのドンチャンチャネルBreakout EAは、現在、完全に開発され、準備万端です。

//+----------------------------------------------------------------------+
//|                                                       BreakoutEA.mq5 |
//|                                    Copyright 2024, Clemence Benjamin |
//|                 https://www.mql5.com/ja/users/billionaire2024/seller |
//+----------------------------------------------------------------------+
#property copyright "Copyright 2024, Clemence Benjamin"
#property link      "https://www.mql5.com/ja/users/billionaire2024/seller"
#property version   "1.00"
#property strict
#include <Trade\Trade.mqh> // Include the trade library

// Input parameters
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
input double pipsToTakeProfit = 30;    // Take profit in pips

// Indicator handle storage
int handle;
string indicatorKey;
double ExtUpBuffer[];  // Upper Donchian buffer
double ExtDnBuffer[];  // Lower Donchian buffer

// Trade instance
CTrade trade;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    indicatorKey = StringFormat("%s_%d", Symbol(), InpDonchianPeriod); // Create a unique key for the indicator
    handle = iCustom(Symbol(), Period(), "Free Indicators\\Donchian Channel", InpDonchianPeriod);
    if (handle == INVALID_HANDLE)
    {
        Print("Failed to load the indicator. Error: ", GetLastError());
        return INIT_FAILED;
    }
    return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    // Release the indicator handle
    IndicatorRelease(handle);
}

//+------------------------------------------------------------------+
//| Main execution function with block-based control                 |
//+------------------------------------------------------------------+
void OnTick()
{
    // Check if any positions are currently open
    if (PositionsTotal() == 0)
    {
        CheckTradingConditions();
    }
}

//+------------------------------------------------------------------+
//| Check trading conditions based on indicator buffers              |
//+------------------------------------------------------------------+
void CheckTradingConditions()
{
    // Resize buffers to get the latest data
    ArrayResize(ExtUpBuffer, 2);
    ArrayResize(ExtDnBuffer, 2);

    // Get the latest values from the Donchian Channel
    if (CopyBuffer(handle, 0, 0, 2, ExtUpBuffer) <= 0 || CopyBuffer(handle, 2, 0, 2, ExtDnBuffer) <= 0)
    {
        Print("Error reading indicator buffer. Error: ", GetLastError());
        return;
    }

    // Get the close price of the current candle
    double closePrice = iClose(Symbol(), Period(), 0);

    // Buy condition: Closing price is above the upper Donchian band
    if (closePrice > ExtUpBuffer[1])
    {
        double stopLoss = closePrice - pipsToStopLoss * _Point; // Calculate stop loss
        double takeProfit = closePrice + pipsToTakeProfit * _Point; // Calculate take profit
        OpenBuy(LotSize, stopLoss, takeProfit);
    }

    // Sell condition: Closing price is below the lower Donchian band
    if (closePrice < ExtDnBuffer[1])
    {
        double stopLoss = closePrice + pipsToStopLoss * _Point; // Calculate stop loss
        double takeProfit = closePrice - pipsToTakeProfit * _Point; // Calculate take profit
        OpenSell(LotSize, stopLoss, takeProfit);
    }
}

//+------------------------------------------------------------------+
//| Open a buy order                                                 |
//+------------------------------------------------------------------+
void OpenBuy(double lotSize, double stopLoss, double takeProfit)
{
    if (trade.Buy(lotSize, Symbol(), 0, stopLoss, takeProfit, "Buy Order"))
    {
        Print("Buy order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
    }
    else
    {
        Print("Failed to open buy order. Error: ", GetLastError());
    }
}

//+------------------------------------------------------------------+
//| Open a sell order                                                |
//+------------------------------------------------------------------+
void OpenSell(double lotSize, double stopLoss, double takeProfit)
{
    if (trade.Sell(lotSize, Symbol(), 0, stopLoss, takeProfit, "Sell Order"))
    {
        Print("Sell order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
    }
    else
    {
        Print("Failed to open sell order. Error: ", GetLastError());
    }
}

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

メインのTrend Constraintコードに組み込む前に、Breakout EAをテストしてみましょう。全体的な目的に沿った制約ロジックをまだ実装していないため、これは最終版ではないことに注意することが重要です。

チャートにBreakout EAを追加する

EAリスト上で右クリックし、[Test]を選択してテスターウィンドウを開きます。そこから、テスト用のBreakout EAを選択して設定することができます。以下のパフォーマンスをご覧ください。

On Tester

Breakout EAがストラテジーテスターで機能する

素晴らしいことです。注文を成功させることができました。この基盤を活用して、収益性を高め、不必要な取引を排除することができます。これにより、次の段階の重要性が際立ちます。次の段階では、より確率の低い取引を排除するために制約を組み込むことが求められます。


Trend Constraint EAへの組み込み

2つのEAコードを統合するには、双方の機能を組み合わせる必要があり、共有されている機能は最終的なEAの主要な部分となります。さらに、それぞれのEAが持つ独自の機能によって、統合されたコードの全体的なサイズと機能が拡張されます。例えば、Trend Constraint EAとBreakout EAの両方に共通するプロパティがあり、それらを1つのプログラムに組み込みます。以下のコードスニペットを参照してください。

// We merge it to one
#property strict
#include <Trade\Trade.mqh>  // Include the trade library

さて、組み込みましょう。 2つ以上の戦略を1つのEAに統合する際、全体的な取引ロジックの中心となる主な機能には、OnInit()OnTick()、およびポジション管理機能(OpenBuy()OpenSell()など)が含まれます。これらの関数はEAの核となり、それぞれインジケーターの初期化、リアルタイム市場分析、注文発注を処理します。

一方で、Trend Constraint EAのDonchian Channelブレイクアウト戦略と移動平均線付きRSIトレンドフォロー戦略の機能は、OnTick()関数内で個別の条件として組み込まれ、既存のプログラムを拡張します。このEAは、ドンチャンチャネルからのブレイクアウトシグナルとRSIおよび移動平均からのトレンドシグナルを同時に評価するため、市場の状況に対してより包括的に反応することが可能になります。

これらの独立した機能を統合することで、EAは意思決定能力を強化し、さまざまな市場ダイナミクスに適応する、より強固な取引戦略が実現します。

1. 初期化関数

初期化関数(OnInit())は、EAが採用する両方の取引戦略に必要なインジケーターを設定するため、非常に重要です。この関数は、EAが最初にロードされる際に呼び出され、取引操作を開始する前にすべての重要なコンポーネントが準備できていることを確認します。RSI(相対力指数)およびドンチャンチャネルインジケーターを初期化します。これらのインジケーターのいずれかが適切に初期化できなかった場合、関数はエラーステータスを返し、取引システムの実行を停止し、潜在的な市場リスクを回避します。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    // 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");
        return INIT_FAILED;
    }

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

    return INIT_SUCCEEDED;
}

2. 主な実行ロジック

主な実行ロジックはOnTick()関数で処理され、この関数は市場のティックが発生するたびに呼び出されます。この関数はEAの中核として機能し、変化する市場環境に応じて様々な取引戦略を評価する役割を担います。トレンドフォロー戦略とブレイクアウト戦略をチェックする関数を順番に呼び出します。さらに、期限切れの注文のチェックも含まれており、EAは期限切れのポジションが有効なまま残らないように管理し、効果的にリスクをコントロールすることができます。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    // Execute both strategies independently on each tick
    CheckTrendConstraintTrading();
    CheckBreakoutTrading();
    CheckOrderExpiration(); // Check for expired Trend Following orders
}

3. モジュール式取引条件関数

トレンドフォロー戦略

この関数は、意思決定を進める前に、現在のポジションの有無を確認します。ポジションがない場合、現在のRSI値を取得し、市場のトレンドを判断するために短期および長期の移動平均を計算します。市場が上昇トレンドにあり、RSIが売られ過ぎの状態を示す場合、買い注文を出すことがあります。逆に、相場が下降トレンドにあり、RSIが買われ過ぎの状態を示す場合、売り注文を出す可能性があります。既存のポジションがある場合は、トレーリングストップ機能を使って管理します。

//+------------------------------------------------------------------+
//| Check and execute Trend Constraint EA trading logic              |
//+------------------------------------------------------------------+
void CheckTrendConstraintTrading()
{
    // Check if there are any positions open
    if (PositionsTotal() == 0)
    {
        // Get RSI value
        double rsi_value;
        double rsi_values[];
        if (CopyBuffer(rsi_handle, 0, 0, 1, rsi_values) <= 0)
        {
            Print("Failed to get RSI value");
            return;
        }
        rsi_value = rsi_values[0];

        // Calculate moving averages
        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);

        // Determine trend direction
        bool is_uptrend = ma_short > ma_long;
        bool is_downtrend = ma_short < ma_long;

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

            // Attempt to open a Buy order
            if (trade.Buy(Lots, _Symbol, 0, stopLossPrice, takeProfitPrice, "Trend Following Buy") > 0)
            {
                Print("Trend Following Buy order placed.");
            }
            else
            {
                Print("Error placing Trend Following Buy order: ", GetLastError());
            }
        }
        // Check for sell conditions
        else if (is_downtrend && rsi_value > RSI_Overbought)
        {
            double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            double stopLossPrice = currentPrice + StopLoss * _Point;
            double takeProfitPrice = currentPrice - TakeProfit * _Point;

            // Attempt to open a Sell order
            if (trade.Sell(Lots, _Symbol, 0, stopLossPrice, takeProfitPrice, "Trend Following Sell") > 0)
            {
                Print("Trend Following Sell order placed.");
            }
            else
            {
                Print("Error placing Trend Following Sell order: ", GetLastError());
            }
        }
    }
    else
    {
        // Implement Trailing Stop for open positions
        TrailingStopLogic();
    }
}

ブレイクアウト戦略関数

この関数は、ドンチャンチャネルインジケーターに基づいてブレイクアウト条件を評価するように設計されています。最新のデータを取り込むために必要なバッファサイズを変更し、潜在的なブレイクアウトの機会を確認します。この戦略では、特定の価格水準を超えることを探し、これが実現すると、市場での大きな価格変動を示唆する可能性があります。条件が満たされると、EAはそれに従って注文を実行します。

/+-------------------------------------------------------------------+
//| Check and execute Breakout EA trading logic                      |
//+------------------------------------------------------------------+
void CheckBreakoutTrading()
{
    // Resize buffers to get the latest data
    ArrayResize(ExtUpBuffer, 2);
    ArrayResize(ExtDnBuffer, 2);

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

    // Get the close price of the current candle
    double closePrice = iClose(Symbol(), Period(), 0);
    
    // Get the daily open and close for the previous day
    double lastOpen = iOpen(Symbol(), PERIOD_D1, 1);
    double lastClose = iClose(Symbol(), PERIOD_D1, 1);

    // Determine if the last day was bullish or bearish
    bool isBullishDay = lastClose > lastOpen; // Bullish if close > open
    bool isBearishDay = lastClose < lastOpen; // Bearish if close < open

    // Check if there are any open positions before executing breakout strategy
    if (PositionsTotal() == 0) // Only proceed if no positions are open
    {
        // Buy condition: Closing price is above the upper Donchian band on a bullish day
        if (closePrice > ExtUpBuffer[1] && isBullishDay)
        {
            double stopLoss = closePrice - pipsToStopLoss * _Point; // Calculate stop loss
            double takeProfit = closePrice + pipsToTakeProfit * _Point; // Calculate take profit
            OpenBreakoutBuyOrder(stopLoss, takeProfit);
        }

        // Sell condition: Closing price is below the lower Donchian band on a bearish day
        if (closePrice < ExtDnBuffer[1] && isBearishDay)
        {
            double stopLoss = closePrice + pipsToStopLoss * _Point; // Calculate stop loss
            double takeProfit = closePrice - pipsToTakeProfit * _Point; // Calculate take profit
            OpenBreakoutSellOrder(stopLoss, takeProfit);
        }
    }
}


 ブレイクアウト取引の確認

この関数は、市場の状況を分析するために、ドンチャンチャネルインジケーターから最新の値を取得します。現在のローソク足の終値と、前日の始値および終値を取得し、これらの価格を比較することで、前日が強気か弱気かを判断します。その後、ブレイクアウト戦略を実行する前に、ポジションが開かれているかどうかを確認します。ポジションがない場合、買い注文(強気の日で終値が上限バンドを上回る場合)または売り注文(弱気の日で終値が下限バンドを下回る場合)を出す条件を確認します。注文を出す前に、それぞれの取引の損切りと利食いのレベルを計算します。

//+------------------------------------------------------------------+
//| Open a buy order for the Breakout strategy                       |
//+------------------------------------------------------------------+
void OpenBreakoutBuyOrder(double stopLoss, double takeProfit)
{
    if (trade.Buy(LotSize, _Symbol, 0, stopLoss, takeProfit, "Breakout Buy"))
    {
        Print("Breakout Buy order placed.");
    }
    else
    {
        Print("Error placing Breakout Buy order: ", GetLastError());
    }
}

//+------------------------------------------------------------------+
//| Open a sell order for the Breakout strategy                      |
//+------------------------------------------------------------------+
void OpenBreakoutSellOrder(double stopLoss, double takeProfit)
{
    if (trade.Sell(LotSize, _Symbol, 0, stopLoss, takeProfit, "Breakout Sell"))
    {
        Print("Breakout Sell order placed.");
    }
    else
    {
        Print("Error placing Breakout Sell order: ", GetLastError());
    }
}

4. 注文有効期限チェック

CheckOrderExpiration関数は、すべての未決済ポジションを確認し、指定された有効期限を超えたポジションを特定してクローズします。この機能は、新鮮な取引環境を維持し、リスクを効果的に管理し、古いポジションが戦略的に望ましい期間よりも長く開いたままになるのを防ぐために非常に重要です。この関数では、各ポジションのマジックナンバーをチェックして、それがトレンドフォロー戦略の一部であるかどうかを確認し、現在時刻とポジションのオープン時刻を比較して、ポジションをクローズする必要があるかどうかを判断します。

//+------------------------------------------------------------------+
//| Check for expired Trend Following orders                         |
//+------------------------------------------------------------------+
void CheckOrderExpiration()
{
    for (int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if (PositionSelectByTicket(ticket))
        {
            long magicNumber = PositionGetInteger(POSITION_MAGIC);

            // Check if it's a Trend Following position
            if (magicNumber == MagicNumber)
            {
                datetime openTime = (datetime)PositionGetInteger(POSITION_TIME);
                if (TimeCurrent() - openTime >= OrderLifetime)
                {
                    // Attempt to close the position
                    if (trade.PositionClose(ticket))
                    {
                        Print("Trend Following position closed due to expiration.");
                    }
                    else
                    {
                        Print("Error closing position due to expiration: ", GetLastError());
                    }
                }
            }
        }
    }
}

5. TrailingStopLogic

TrailingStopLogicメソッドは、トレーリングストップのルールに従って損切りレベルを調整することで、既存のポジションを管理します。ロングポジションの場合、現在の価格がトレーリングストップの閾値を超えた際にストップロスを上に移動させます。ショートポジションの場合、条件が整うとストップロスを引き下げます。このアプローチは、ストップロスを有利な価格変動に追随させることで利益を確保し、相場が反転した際の損失リスクを軽減します。

//+------------------------------------------------------------------+
//| Manage trailing stops for open positions                         |
//+------------------------------------------------------------------+
void TrailingStopLogic()
{
    for (int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if (PositionSelectByTicket(ticket))
        {
            double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            double stopLoss = PositionGetDouble(POSITION_SL);

            // Update stop loss for long positions
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            {
                if (currentPrice - stopLoss > TrailingStop * _Point || stopLoss == 0)
                {
                    trade.PositionModify(ticket, currentPrice - TrailingStop * _Point, PositionGetDouble(POSITION_TP));
                }
            }
            // Update stop loss for short positions
            else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
            {
                if (stopLoss - currentPrice > TrailingStop * _Point || stopLoss == 0)
                {
                    trade.PositionModify(ticket, currentPrice + TrailingStop * _Point, PositionGetDouble(POSITION_TP));
                }
            }
        }
    }
}

6. クリーンアップ関数

OnDeinit()関数は、EAがチャートから削除された際に実行されるクリーンアップルーチンです。この関数は、割り当てられたリソースやインジケーターハンドルの解放を処理し、メモリリークや不正な参照が残らないことを保証します。EAが適切に初期化解除されたことを確認し、この操作をログに記録します。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    // Release indicators and handles
    IndicatorRelease(rsi_handle);
    IndicatorRelease(handle);
    Print("Expert deinitialized.");
}


テストと結果

下の画像は、新しい機能が正常に動作している私たちのストラテジーテスターの結果です。

Trend Constraint EA。Donchianブレイクアウトとトレンドフォロー

Trend Constraint EA:ドンチャンチャネルブレイクアウトとトレンドフォロー戦略


結論

このディスカッションでは、1つのEAに複数の戦略を組み込むことで、さまざまな市場環境に適応するという課題を取り上げました。当初、ブレイクアウトプロセスを効果的に管理するためのミニBreakout EAを開発し、それをメインのTrend Constraint EAに統合しました。この統合により、ブレイクアウト戦略をより上位の時間枠の市場センチメント、特にD1ローソク足分析に合わせることができ、過剰な取引執行を減少させることができました。

ストラテジーテスターで実行された各取引には、採用された戦略が明確に注釈され、その基礎となる仕組みを理解する上で透明性と明瞭性が提供されました。複雑な問題に効果的に取り組むためには、それを管理可能な小さな構成要素に分解し、ひとつひとつ段階を踏んで対処することが重要です。このアプローチを採ることで、ミニEAを開発し、それを統合するという形で戦略を1つにまとめることができました。

私たちの実装は可能性を示していますが、まだ改善の余地があり、未完成の状態です。このEAは教育ツールとして、異なる戦略がどのように連動し、より良い取引結果を生むかを示しています。提供されたEAを試してみて、ご自分の取引戦略に合うように修正することをお勧めします。アルゴリズム取引における複数の戦略を組み合わせる可能性を探求するためには、皆様からのフィードバックが不可欠です。

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

添付されたファイル |
BreakoutEA.mq5 (5.07 KB)
MQL5で取引管理者パネルを作成する(第5回):2要素認証(2FA) MQL5で取引管理者パネルを作成する(第5回):2要素認証(2FA)
本日は、現在開発中の取引管理パネルのセキュリティ強化について説明します。Telegram APIを統合し、2要素認証(2FA)を実現する新しいセキュリティ戦略にMQL5を実装する方法を探ります。このディスカッションでは、MQL5を活用してセキュリティ対策を強化する方法について貴重な洞察を得ることができます。さらに、MathRand関数の機能に焦点を当て、セキュリティフレームワーク内でどのように効果的に活用できるかを検討します。さらに詳しく知りたい方は、読み続けてください。
Connexusヘルパー(第5回):HTTPメソッドとステータスコード Connexusヘルパー(第5回):HTTPメソッドとステータスコード
この記事では、Web上でクライアントとサーバー間の重要な通信手段であるHTTPメソッドとステータスコードについて理解します。各メソッドの役割を理解することで、リクエストをより正確に制御できるようになり、サーバーに対して実行したいアクションを明確に伝えることができます。これにより、通信の効率が向上します。
Controlsクラスを使用してインタラクティブなMQL5ダッシュボード/パネルを作成する方法(第2回):ボタンの応答性の追加 Controlsクラスを使用してインタラクティブなMQL5ダッシュボード/パネルを作成する方法(第2回):ボタンの応答性の追加
この記事では、ボタンの応答性を有効にすることで、静的なMQL5ダッシュボードパネルをインタラクティブなツールへと変換することに焦点を当てます。GUIコンポーネントの機能を自動化し、ユーザーのクリックに適切に反応する方法を探究します。この記事の最後には、ユーザーのエンゲージメントと取引体験を向上させる動的なインターフェイスを構築します。
プライスアクション分析ツールキットの開発(第1回):チャートプロジェクター プライスアクション分析ツールキットの開発(第1回):チャートプロジェクター
このプロジェクトは、MQL5アルゴリズムを活用して、MetaTrader 5向けの包括的な分析ツールセットを開発することを目的としています。これらのツールは、スクリプトやインジケーターからAIモデルやエキスパートアドバイザー(EA)に至るまで幅広く、市場分析プロセスの自動化を実現します。場合によっては、これらのツールによって、高度な分析を人間の介入なしで実行し、適切なプラットフォームに結果を予測することも可能になります。どのようなチャンスも逃しません。一緒に強力な市場分析用カスタムツールチェストを構築するプロセスを探求していきましょう。まず、「チャートプロジェクター」と名付けたシンプルなMQL5プログラムを開発することから始めます。