English Deutsch
preview
MQL5での取引戦略の自動化(第44回):スイングハイ/ローのブレイクによる性格の変化(CHoCH)検出

MQL5での取引戦略の自動化(第44回):スイングハイ/ローのブレイクによる性格の変化(CHoCH)検出

MetaTrader 5トレーディングシステム |
27 0
Allan Munene Mutiiria
Allan Munene Mutiiria

はじめに

前回の記事(第43回)では、MetaQuotes Language 5 (MQL5)で適応型線形回帰チャネル戦略を開発しました。この戦略では、回帰線と偏差バンドを計算し、十分な傾きのときのみ動作、偏差に応じて自動的に拡張または再作成、通常モードと逆モードをサポート、チャネル内のブレイクアウトでエントリー、中間線クロスで決済、ポジション数制限、塗りつぶしゾーンの可視化(ラベル、矢印付き)などを実現しました。第44回では、スイングハイやスイングローのブレイクを用いた性格の変化(CHoCH)検出システムを開発します。

本システムはバーをスキャンし、スイングハイやスイングローを識別してHH/LH/LL/HLでラベル付けし、トレンド方向を判定します。反転を示すブレイク時に取引をおこない、ダウントレンドでは高値上抜けで買い、アップトレンドでは安値下抜けで売りをおこないます。バーごとまたはティックごとのモード、固定取引レベルとリスクリワード比、取引制限、トレーリングストップ、アイコンやラベル、ブレイクライン、動的フォントなどの視覚的表示もサポートします。本記事では以下のトピックを扱います。

  1. 性格の変化(CHoCH)戦略の理解
  2. MQL5での実装
  3. バックテスト
  4. 結論

最終的に、CHoCH反転を検出して取引する機能的なMQL5戦略を、カスタマイズ可能なスキャン、リスク管理、明確な視覚フィードバック付きで構築できます。それでは始めましょう。


性格の変化(CHoCH)戦略の理解

CHoCHはプライスアクションの概念で、最近のスイングハイやスイングローを価格がブレイクすることで、既存トレンドとは逆の動きを示す反転の可能性を知らせます。スイングハイは周囲のバーより高い位置、スイングローは周囲より低いポイントとして特定します。前回のスイングと比較して、高値はHH (higher high)またはLH (lower high)、安値はLL (lower low)またはHL (higher low)とラベル付けします。

HH/HLの連続は上昇トレンド、LH/LLは下降トレンドを示します。CHoCHは、下降トレンド中に最近のスイングハイを価格が上抜けした場合(強気の反転)や、上昇トレンド中に最近のスイングローを下抜けした場合(弱気の反転)に発生します。これは、買い手または売り手が主導権を握ったことを示す「変化」です。LHまたはLLで示される下降トレンドでは、最近のスイングハイを価格が終値で上抜けした場合に強気のCHoCHが発動し、買いエントリーをおこないます。ストップロスはブレイクレベルの下に設定します。逆に上昇トレンド(HHまたはHL)では、最近のスイングローを終値で下抜けした場合に弱気のCHoCHが発動し、売りエントリーをおこないます。ストップロスは上に、テイクプロフィットは下方向に設定します。

本戦略の概要は次の通りです。各新しいローソク足を中心にバーをスキャンし、スイングハイやスイングローをHH/LH/LL/HLとしてラベル付けします。そして、ラベルの連続から現在のトレンドを判定し、ダウントレンドでは高値上抜けでCHoCH買い、アップトレンドでは安値下抜けでCHoCH売りを発動します。保有ポジション数は制限され、固定ポイント取引には調整可能なリスクリワード比が適用されます。さらに、利益が閾値に達した後にはポイントベースのトレーリングストップをオプションで設定できます。スイングには色付きアイコンやラベルを表示し、CHoCHブレイクには矢印付きのラインやテキストで視覚的に示します。チャートのスケール変更にも対応するため、フォントサイズは動的に調整されます。視覚的には、次のようなフレームワークで実現されます。

CHoCHフレームワーク


MQL5での実装

MQL5でプログラムを作成するには、まずMetaEditorを開き、ナビゲーターで[Experts]フォルダを探します。[新規]タブをクリックして指示に従い、ファイルを作成します。ファイルが作成されたら、コーディング環境で、まずプログラム全体で使用する入力パラメータグローバル変数をいくつか宣言する必要があります。

//+------------------------------------------------------------------+
//|                                                     CHoCH EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"

#include <Trade/Trade.mqh>

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
CTrade obj_Trade;                                                 //--- Trade object
int   object_code     = 174;                                      //--- Object code
int   current_font_size = 10;                                     //--- Current font size
long  magic_number    = 123456789;                                //--- Magic number

//+------------------------------------------------------------------+
//| Enums                                                            |
//+------------------------------------------------------------------+
enum TradeMode {                                                  // Define trade mode enum
   PerBar,                                                        // Per Bar
   PerTick                                                        // Per Tick
};

enum TrailingTypeEnum {                                           // Define enum for trailing stop types
   Trailing_None   = 0,                                           // None
   Trailing_Points = 2                                            // By Points
};

//+------------------------------------------------------------------+
//| Input Parameters                                                 |
//+------------------------------------------------------------------+
input group "EA GENERAL SETTINGS"
input double inpLot             = 0.01;                           // Lotsize
input int    sl_pts             = 300;                            // Stop Loss Points
input int    tp_pts             = 300;                            // Take Profit Points
input double r2r_ratio          = 
1 ;                              // Risk : Reward Ratio
input int    totalTrades        = 1;                              // Total Possible Open Trades
input color  def_clr_up         = clrBlue;                        // Swing High Color
input color  def_clr_down       = clrRed;                         // Swing Low Color
input int    ext_bars           = 5;                              // CHoCH Scan Length in Bars
input bool   prt                = true;                           // Print Statements
input int    width              = 2;                              // Width
input TradeMode trade_mode      = PerBar;                         // Trade Mode
input TrailingTypeEnum TrailingType = Trailing_None;              // Trailing Stop Type
input double Trailing_Stop_Pips = 30.0;                           // Trailing Stop in Pips (for Points type)
input double Min_Profit_To_Trail_Pips = 50.0;                     // Min Profit to Start Trailing in Pips

実装は、まずTradeライブラリを#include <Trade/Trade.mqh>でインクルードすることから始めます。これにより、注文やポジション操作をおこなうためのCTradeクラスが利用可能になります。次に、いくつかのグローバル変数を宣言します。obj_Tradeは取引操作をおこなうためのCTradeのインスタンスです。object_codeはビジュアル表示で使用する特定のWingdings記号として174に設定し、current_font_sizeは動的テキストサイズ用に初期値10で設定します。magic_numberは123456789に設定し、プログラムの取引を一意に識別します。これらは必要に応じて入力パラメータとして設定することも可能です。さらに、ユーザー設定用に2つの列挙型を定義します。TradeMode列挙型は、バークローズ時にブレイクを検出するPerBarと、リアルタイムのティックベースで検出するPerTickを提供します。TrailingTypeEnum列挙型では、トレーリングストップを無効にするTrailing_Noneと、ポイントベースのトレーリングを有効にするTrailing_Pointsを指定できます。

入力パラメータは、「EA GENERAL SETTINGS」の下にグループ化して、プロパティダイアログで整理して表示されるようにします。ここには、ロットサイズ用のinpLot、ストップロス・テイクプロフィット距離をポイント単位で指定するsl_ptsとtp_pts、リスクリワード倍率のr2r_ratio(SLを基準にTP計算に適用、必要に応じて調整可能)、同時未決ポジション数を制限するtotalTrades、スイングハイとスイングローの色指定用def_clr_up(青)とdef_clr_down(赤)、CHoCH検出用のスキャン長さext_bars、ログ出力のON/OFFを切り替えるprt、ビジュアルのライン幅width、バーまたはティックでの取引モードを指定するtrade_mode、トレーリングタイプを指定するTrailingType、トレーリング距離Trailing_Stop_Pips、およびトレーリング開始前の最小利益閾値Min_Profit_To_Trail_Pipsが含まれます。これで、プログラムをモジュール化するためのヘルパー関数を定義できる準備が整いました。

//+------------------------------------------------------------------+
//| Update font sizes function                                       |
//+------------------------------------------------------------------+
void UpdateFontSizes() {
   long scale = 0;                                                //--- Init scale
   if (ChartGetInteger(0, CHART_SCALE, 0, scale)) {               //--- Get scale
      current_font_size = (int)(7 + scale * 0.7);                 //--- Calc font size
      if (current_font_size < 6) current_font_size = 6;           //--- Min font size
      if (current_font_size > 15) current_font_size = 15;         //--- Max font size
      for (int i = ObjectsTotal(0, -1, -1) - 1; i >= 0; i--) {    //--- Iterate objects
         string name = ObjectName(0, i, -1, -1);                  //--- Get name
         long type = ObjectGetInteger(0, name, OBJPROP_TYPE);     //--- Get type
         if (type == OBJ_TEXT) {                                  //--- Check text
            ObjectSetInteger(0, name, OBJPROP_FONTSIZE, current_font_size); //--- Set font size
         }
      }
      ChartRedraw(0);                                             //--- Redraw chart
   }
}

//+------------------------------------------------------------------+
//| High function                                                    |
//+------------------------------------------------------------------+
double high(int index) {
   return (iHigh(_Symbol,_Period,index));                         //--- Return high
}

//+------------------------------------------------------------------+
//| Low function                                                     |
//+------------------------------------------------------------------+
double low(int index) {
   return (iLow(_Symbol,_Period,index));                          //--- Return low
}

//+------------------------------------------------------------------+
//| Close function                                                   |
//+------------------------------------------------------------------+
double close(int index) {
   return (iClose(_Symbol,_Period,index));                        //--- Return close
}

//+------------------------------------------------------------------+
//| Time function                                                    |
//+------------------------------------------------------------------+
datetime time(int index) {
   return (iTime(_Symbol,_Period,index));                         //--- Return time
}

//+------------------------------------------------------------------+
//| Draw swing point                                                 |
//+------------------------------------------------------------------+
void drawSwingPoint(string objName,datetime time,double price,int arrCode,
   color clr,int direction,string label) {
   UpdateFontSizes();                                             //--- Update font sizes
   if (ObjectFind(0,objName) < 0) {                               //--- Check no object
      // Draw icon as OBJ_TEXT with Wingdings
      string iconName = objName + "_icon";                        //--- Icon name
      ObjectCreate(0,iconName,OBJ_TEXT,0,time,price);             //--- Create icon
      ObjectSetString(0,iconName,OBJPROP_FONT,"Wingdings");       //--- Set font
      ObjectSetInteger(0,iconName,OBJPROP_FONTSIZE,current_font_size); //--- Set size
      ObjectSetString(0,iconName,OBJPROP_TEXT,CharToString((uchar)arrCode)); //--- Set text
      ObjectSetInteger(0,iconName,OBJPROP_COLOR,clr);             //--- Set color
      if (direction == 1){
         ObjectSetInteger(0,iconName,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER);   //--- Set anchor
      }
      else if (direction == -1){
         ObjectSetInteger(0,iconName,OBJPROP_ANCHOR,ANCHOR_RIGHT_LOWER);   //--- Set anchor
      }
      // Draw text label
      string txt = label;                                         //--- Set text
      string objNameDescr = objName + txt;                        //--- Descr name
      ObjectCreate(0,objNameDescr,OBJ_TEXT,0,time,price);         //--- Create descr
      ObjectSetString(0,objNameDescr,OBJPROP_FONT,"Arial");       //--- Set font
      ObjectSetInteger(0,objNameDescr,OBJPROP_COLOR,clr);         //--- Set color
      ObjectSetInteger(0,objNameDescr,OBJPROP_FONTSIZE,current_font_size); //--- Set size
      ObjectSetString(0,objNameDescr,OBJPROP_TEXT,txt);           //--- Set text
      if (direction == 1){
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_LEFT_UPPER); //--- Set anchor
      }
      else if (direction == -1){
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER); //--- Set anchor
      }
   }
   ChartRedraw(0);                                                //--- Redraw chart
}

//+------------------------------------------------------------------+
//| Draw break level                                                 |
//+------------------------------------------------------------------+
void drawBreakLevel(string objName,datetime time1,double price1,
   datetime time2,double price2,color clr,int direction) {
   UpdateFontSizes();                                              //--- Update font sizes
   if (ObjectFind(0,objName) < 0) {                                //--- Check no object
      ObjectCreate(0,objName,OBJ_ARROWED_LINE,0,time1,price1,time2,price2); //--- Create arrowed line
      ObjectSetInteger(0,objName,OBJPROP_TIME,0,time1);            //--- Set time1
      ObjectSetDouble(0,objName,OBJPROP_PRICE,0,price1);           //--- Set price1
      ObjectSetInteger(0,objName,OBJPROP_TIME,1,time2);            //--- Set time2
      ObjectSetDouble(0,objName,OBJPROP_PRICE,1,price2);           //--- Set price2
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr);               //--- Set color
      ObjectSetInteger(0,objName,OBJPROP_WIDTH,width);             //--- Set width
      string txt = "CHoCH";                                        //--- Set text
      string objNameDescr = objName + txt;                         //--- Descr name
      ObjectCreate(0,objNameDescr,OBJ_TEXT,0,time2,price2);        //--- Create descr
      ObjectSetInteger(0,objNameDescr,OBJPROP_COLOR,clr);          //--- Set color
      ObjectSetInteger(0,objNameDescr,OBJPROP_FONTSIZE,current_font_size); //--- Set size
      if (direction > 0) {                                         //--- Check positive
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER); //--- Set anchor
         ObjectSetString(0,objNameDescr,OBJPROP_TEXT, " " + txt);  //--- Set text
      }
      if (direction < 0) {                                         //--- Check negative
         ObjectSetInteger(0,objNameDescr,OBJPROP_ANCHOR,ANCHOR_RIGHT_LOWER); //--- Set anchor
         ObjectSetString(0,objNameDescr,OBJPROP_TEXT, " " + txt);  //--- Set text
      }
   }
   ChartRedraw(0);                                                 //--- Redraw chart
}

ヘルパー関数として、まずUpdateFontSizes関数を実装します。この関数は、現在のチャートスケールに応じてテキストサイズを動的に調整し、ズームインやズームアウトの際にもラベルが読みやすくなるようにします。scaleを0で初期化し、ChartGetInteger関数とCHART_SCALEを使ってチャートのスケール値を取得します。取得に成功した場合、current_font_sizeを「7 + スケールの70%」で計算し、極端なサイズにならないよう6から15の範囲に制限します。次に、ObjectsTotalを使ってチャート上のすべてのオブジェクトを逆順にループし(すべてのウィンドウ・タイプを対象にするため-1を指定)、ObjectNameで名前、ObjectGetIntegerとOBJPROP_TYPEでタイプを取得します。OBJ_TEXTオブジェクトであれば、ObjectSetIntegerOBJPROP_FONTSIZEを用いてフォントサイズをcurrent_font_sizeに更新し、ChartRedrawでチャートを再描画します。

次に、バーのデータに簡単にアクセスできるラッパー関数を作成します。highは現在の銘柄と期間の指定インデックスの高値をiHighで返し、lowはiLow、closeはiClose、timeはiTimeで始値時刻を返します。これにより、スイング検出や描画の際に繰り返し関数を呼ぶ手間を省けます。さらに、検出したスイングハイやスイングローを可視化するdrawSwingPoint関数を定義します。まずUpdateFontSizesを呼んで現在のサイズを反映させ、次にObjectFindで指定された名前のオブジェクトが存在するかを確認します。存在しなければ、WingdingsアイコンをOBJ_TEXTとして作成し、名前は_iconを付加します。指定された時間と価格に配置し、フォントは「Wingdings」、サイズはcurrent_font_size、テキストはarrCodeからCharToStringで取得、色はclr、アンカーは安値の場合は右上(方向1)、高値の場合は右下(方向-1)に設定します。

その後、テキストラベル自体を作成します。ラベルには「HH」や「LL」など指定された文字列を使用し、別のOBJ_TEXTオブジェクトとして作成します。フォントはArial、色とサイズはアイコンと同じ、アンカーは方向に応じて左上または左下に設定します。最後にチャートを再描画します。Wingdingsコードが初めての場合は、MQL5用のWingdingsコードを参照すると分かりやすいです。

MQL5 WINGDINGS

続いて、CHoCHブレイクを示すdrawBreakLevel関数も実装します。まずUpdateFontSizesを呼び、オブジェクトが存在しない場合に限り、OBJ_ARROWED_LINEを作成します。線はtime1・price1からtime2・price2まで引き、座標はOBJPROP_TIMEとOBJPROP_PRICEで明示的に指定します。線の色と幅は指定通りに設定します。その後、OBJ_TEXTで「CHoCH」のテキストラベルをtime2・price2に作成し、色とcurrent_font_sizeを適用、方向が正の場合は右上、負の場合は右下にアンカーを設定します。ラベルの前にスペースを加えて間隔を調整します。これらの関数を用意すれば、実装を開始する準備が整います。まず初期化時にマジックナンバーを設定し、既存ラベルがあれば更新します。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   obj_Trade.SetExpertMagicNumber(magic_number);                  //--- Set magic number
   UpdateFontSizes();                                             //--- Update font sizes
   return(INIT_SUCCEEDED);                                        //--- Return success
}

OnInitイベントハンドラでは、プログラムが読み込まれたりチャートにアタッチされた際に一度だけ実行されます。まず、obj_Tradeに対してSetExpertMagicNumberを使い、マジックナンバーを設定します。これにより、すべての取引に一意の識別子が付与されます。次に、UpdateFontSizesを呼んで、現在のチャートスケールに基づいたテキストサイズを初期化します。最後に、INIT_SUCCEEDEDを返して、初期化が正常に完了したことを示します。とてもシンプルです。次のステップは、スイングポイントを使ったトレンドの検出です。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick() {
   static bool isNewBar = false;                                  //--- New bar flag
   int currBars = iBars(_Symbol,_Period);                         //--- Get current bars
   static int prevBars = currBars;                                //--- Previous bars
   if (prevBars == currBars) {                                    //--- Check same bars
      isNewBar = false;                                           //--- Set not new
   } else if (prevBars != currBars) {                             //--- Check new bars
      isNewBar = true;                                            //--- Set new bar
      prevBars = currBars;                                        //--- Update prev
   }
   const int length = ext_bars;                                   //--- Set length
   const int limit = ext_bars;                                    //--- Set limit
   int right_index, left_index;                                   //--- Indices
   bool isSwingHigh = true, isSwingLow = true;                    //--- Swing flags
   static double current_swing_high = -1.0, current_swing_low = -1.0; //--- Current swings
   static datetime swing_high_time = 0, swing_low_time = 0;       //--- Swing times
   static int current_trend = 0;                                  //--- Current trend (1 up, -1 down, 0 unknown)
   int curr_bar = limit;                                          //--- Set curr bar
   if (isNewBar) {                                                //--- Check new bar
      UpdateFontSizes();                                          //--- Update font sizes
      for (int j=1; j<=length; j++) {                             //--- Iterate length
         right_index = curr_bar - j;                              //--- Calc right
         left_index = curr_bar + j;                               //--- Calc left
         if ( (high(curr_bar) <= high(right_index)) || (high(curr_bar) < high(left_index)) ) { //--- Check not high
            isSwingHigh = false;                                  //--- Set not high
         }
         if ( (low(curr_bar) >= low(right_index)) || (low(curr_bar) > low(left_index)) ) { //--- Check not low
            isSwingLow = false;                                   //--- Set not low
         }
      }
      if (isSwingHigh) {                                          //--- Check swing high
         double new_high = high(curr_bar);                        //--- Get new high
         string label = "H";                                      //--- Init label
         color clr = def_clr_up;                                  //--- Set color
         if (current_swing_high > 0) {                            //--- Check existing high
            if (new_high > current_swing_high) {                  //--- Check higher
               label = "HH";                                      //--- Set HH
               current_trend = 1;                                 //--- Set up trend
            } else {                                              //--- Lower
               label = "LH";                                      //--- Set LH
               clr = def_clr_down;                                //--- Set down color
               current_trend = -1;                                //--- Set down trend
            }
         }
         if (prt) {                                               //--- Check print
            Print("SWING HIGH @ BAR INDEX ",curr_bar," of High: ",new_high, " Label: ",label); //--- Log high
         }
         drawSwingPoint(TimeToString(time(curr_bar)),time(curr_bar),new_high,object_code,clr,-1,label); //--- Draw high
         current_swing_high = new_high;                           //--- Update high
         swing_high_time = time(curr_bar);                        //--- Update time
      }
      if (isSwingLow) {                                           //--- Check swing low
         double new_low = low(curr_bar);                          //--- Get new low
         string label = "L";                                      //--- Init label
         color clr = def_clr_down;                                //--- Set color
         if (current_swing_low > 0) {                             //--- Check existing low
            if (new_low < current_swing_low) {                    //--- Check lower
               label = "LL";                                      //--- Set LL
               current_trend = -1;                                //--- Set down trend
            } else {                                              //--- Higher
               label = "HL";                                      //--- Set HL
               clr = def_clr_up;                                  //--- Set up color
               current_trend = 1;                                 //--- Set up trend
            }
         }
         if (prt) {                                               //--- Check print
            Print("SWING LOW @ BAR INDEX ",curr_bar," of Low: ",new_low, " Label: ",label); //--- Log low
         }
         drawSwingPoint(TimeToString(time(curr_bar)),time(curr_bar),new_low,object_code,clr,1,label); //--- Draw low
         current_swing_low = new_low;                             //--- Update low
         swing_low_time = time(curr_bar);                         //--- Update time
      }
   }
}

OnTick関数では、静的変数isNewBarとprevBarsを使って新しいバーの発生を確認します。まず、iBarsを使って現在の銘柄と期間の総バー数をcurrBarsに取得し、これをprevBarsと比較します。バー数が変わらなければisNewBarをfalseに設定し、バー数が増えていればisNewBarをtrueに設定してprevBarsをcurrBarsに更新します。これにより、スイングスキャンのような重い計算は、完成したバーごとに一度だけ実行されます。次に、入力パラメータext_barsをlengthとlimitに設定し、左右のチェック用インデックスを宣言します。ブールフラグisSwingHighとisSwingLowをtrueで初期化し、最近のスイング高値・安値やその時間、そして現在のトレンド(1=上昇、-1=下降、0=不明)を追跡するための静的グローバル変数を用意します。スキャン対象バーcurr_barは通常ウィンドウ内で最も古いバーであるlimitに固定します。

isNewBarがtrueであれば、まずUpdateFontSizesを呼んでビジュアルを更新します。その後、1からlengthまでループしてcurr_barが真のスイングかどうかを検証します。高値の場合は右側(新しいバー)と左側(古いバー)の両方の高値を超えているか確認し、条件を満たさない場合はisSwingHighをfalseに設定します。安値の場合も同様に、周囲の安値より低いかを確認し、条件を満たさなければisSwingLowをfalseにします。isSwingHighがtrueのままであれば、バーの高値をnew_highに格納し、ラベルは「H」、色はdef_clr_upに設定します。以前のcurrent_swing_highが存在する場合は比較し、高ければラベルを「HH」としてcurrent_trendを1(上昇)に設定し、低ければ「LH」として色をdef_clr_downに変更、トレンドを-1(下降)に設定します。prtが有効であれば、スイングの詳細をログに記録し、drawSwingPointを呼んでバーの時刻文字列、時間、価格、object_code、色、方向-1(高値用)、ラベルを渡して描画します。最後にcurrent_swing_highとswing_high_timeを新しい値に更新します。スイングローの検出も同様の手順で処理します。コンパイルすると、次の結果が得られます。

スイングポイント検出

スイングポイントが確定すれば、トレンド反転時のCHoCHをスキャンし、チャート上にマーキングしてリアルタイムで取引することが可能です。次は、強気のCHoCHのシナリオから始めます。

double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get ask
double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get bid
bool buy_break = (trade_mode == PerTick) ? (Bid > current_swing_high) : (Bid > current_swing_high && close(1) > current_swing_high); //--- Check buy break
bool sell_break = (trade_mode == PerTick) ? (Ask < current_swing_low) : (Ask < current_swing_low && close(1) < current_swing_low); //--- Check sell break
if (current_trend == -1 && current_swing_high > 0 && buy_break) { //--- Check up CHoCH
   if (prt) {                                                   //--- Check print
      Print("CHoCH UP NOW");                                    //--- Log up CHoCH
   }
   int swing_H_index = 0;                                       //--- Init index
   for (int i=0; i<=length*2+1000; i++) {                       //--- Iterate search
      double high_sel = high(i);                                //--- Get high
      if (high_sel == current_swing_high) {                     //--- Check match
         swing_H_index = i;                                     //--- Set index
         if (prt) {                                             //--- Check print
            Print("BREAK HIGH @ BAR ",swing_H_index);           //--- Log break
         }
         break;                                                 //--- Break loop
      }
   }
   drawBreakLevel(TimeToString(time(0)),swing_high_time,current_swing_high, 
   time(0+1),current_swing_high,def_clr_up,-1);                 //--- Draw break level
   current_swing_high = -1.0;                                   //--- Reset high
   //--- Open Buy
   double trade_lots = NormalizeDouble(inpLot, 2);              //--- Normalize lots
   double SL_Buy = NormalizeDouble(Bid-sl_pts*r2r_ratio*_Point,_Digits); //--- Calc buy SL
   double TP_Buy = NormalizeDouble(Bid+tp_pts*_Point,_Digits);  //--- Calc buy TP
   if (PositionsTotal() < totalTrades) {                        //--- Check positions limit
      obj_Trade.Buy(trade_lots,_Symbol,Ask,SL_Buy,TP_Buy,"CHoCH Up BUY"); //--- Open buy
   }
   return;                                                      //--- Return
}

ここでは、強気のCHoCHのブレイクアウト検出と取引実行を処理します。まず、SymbolInfoDoubleSYMBOL_ASK を使って現在のAsk価格を取得し、銘柄の小数桁数に合わせて正規化してAskに格納します。同様に、Bid価格もSYMBOL_BIDで取得してBidに正規化します。次に、trade_modeに基づいてbuy_breakを定義します。PerTickの場合はBidがcurrent_swing_highを超えたかを確認し、PerBarの場合はBidが上回ることに加え、前バーの終値(close(1)による)も上回っていることを確認します。sell_breakも同様ですが、条件は逆になります。現在のトレンドが下降(current_trend == -1)で、current_swing_highが0より大きく、かつbuy_breakが成立していれば、強気のCHoCHが検出されたことになります。prtが有効であれば、ログに「CHoCH UP NOW」と記録します。続いて、このスイングハイの正確なバーインデックスを特定するため、スキャン長さの2倍+1000バーまでループし、各high(i)とcurrent_swing_highを比較します。一致した場合はインデックスをswing_H_indexに格納し、prtが有効であればブレイクバーをログに記録してループを抜けます。

drawBreakLevelを呼び出して、現在の時間文字列、swing_high_timeでのcurrent_swing_highから1バー先の同価格まで線を描画し、色はdef_clr_up、方向は-1に設定します。その後、次のスイング検出のためにcurrent_swing_highを-1.0にリセットします。取引では、ロット数を小数2桁に正規化してtrade_lotsに格納します。買いのストップロスは「Bid - sl_pts * r2r_ratio * _Point」、テイクプロフィットは「Bid + tp_pts * _Point」で計算し正規化します。取引レベルの設定方法は任意で、リスクリワード比を使うか固定レベルを使用するかは自由に選択可能です。ここでは2つのオプションを入力として用意しており、どちらを使うかはユーザー次第です。最後に、総ポジション数がtotalTrades未満であれば、obj_Trade.Buyを使ってtrade_lots、銘柄、Ask、SL、TP、コメント「CHoCH Up BUY」で買いポジションを開きます。その後、同ティックでの追加処理を避けるため早期にリターンします。システムを実行すると、次の結果が得られます。

強気のCHoCH

画像からも分かる通り、強気のCHoCHを検出し、それに応じて取引が実行されます。同様の処理を、ロジックを反転させた弱気のCHoCHでもおこなう必要があります。以下にアプローチを示します。

else if (current_trend == 1 && current_swing_low > 0 && sell_break) { //--- Check down CHoCH
   if (prt) {                                                   //--- Check print
      Print("CHoCH DOWN NOW");                                  //--- Log down CHoCH
   }
   int swing_L_index = 0;                                       //--- Init index
   for (int i=0; i<=length*2+1000; i++) {                       //--- Iterate search
      double low_sel = low(i);                                  //--- Get low
      if (low_sel == current_swing_low) {                       //--- Check match
         swing_L_index = i;                                     //--- Set index
         if (prt) {                                             //--- Check print
            Print("BREAK LOW @ BAR ",swing_L_index);            //--- Log break
         }
         break;                                                 //--- Break loop
      }
   }
   drawBreakLevel(TimeToString(time(0)),swing_low_time,current_swing_low, 
   time(0+1),current_swing_low,def_clr_down,1);                 //--- Draw break level
   current_swing_low = -1.0;                                    //--- Reset low
   //--- Open Sell
   double trade_lots = NormalizeDouble(inpLot, 2);              //--- Normalize lots
   double SL_Sell = NormalizeDouble(Ask+sl_pts*r2r_ratio*_Point,_Digits); //--- Calc sell SL
   double TP_Sell = NormalizeDouble(Ask-tp_pts*_Point,_Digits); //--- Calc sell TP
   if (PositionsTotal() < totalTrades) {                        //--- Check positions limit
      obj_Trade.Sell(trade_lots,_Symbol,Bid,SL_Sell,TP_Sell,"CHoCH Down SELL"); //--- Open sell
   }
   return;                                                      //--- Return
}

ここでは、強気のシナリオと同じロジックを用いますが、条件を反転させることで弱気のCHoCHを検出し、チャート上にマーキングし、取引を実行します。コンパイルすると、次の結果が得られます。

弱気のCHoCH

これで、すべてのCHoCHのセットアップを検出できるようになりました。残る作業は、相場が有利に動いた際に利益を最大化するためのトレーリングストップを追加することだけです。それを実装すれば完了です。

//+------------------------------------------------------------------+
//| Apply Points Trailing Stop                                       |
//+------------------------------------------------------------------+
void ApplyPointsTrailing() {
   double point = _Point;                                            //--- Get point value
   for (int i = PositionsTotal() - 1; i >= 0; i--) {                 //--- Iterate positions reverse
      if (PositionGetTicket(i) > 0) {                                //--- Check valid ticket
         if (PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) { //--- Check symbol and magic
            double sl = PositionGetDouble(POSITION_SL);              //--- Get SL
            double tp = PositionGetDouble(POSITION_TP);              //--- Get TP
            double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); //--- Get open price
            ulong ticket = PositionGetInteger(POSITION_TICKET);      //--- Get ticket
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { //--- Check buy
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - Trailing_Stop_Pips * point, _Digits); //--- Calc new SL
               if (newSL > sl && SymbolInfoDouble(_Symbol, SYMBOL_BID) - openPrice > Min_Profit_To_Trail_Pips * point) { //--- Check conditions
                  obj_Trade.PositionModify(ticket, newSL, tp);       //--- Modify position
               }
            } else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { //--- Check sell
               double newSL = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + Trailing_Stop_Pips * point, _Digits); //--- Calc new SL
               if (newSL < sl && openPrice - SymbolInfoDouble(_Symbol, SYMBOL_ASK) > Min_Profit_To_Trail_Pips * point) { //--- Check conditions
                  obj_Trade.PositionModify(ticket, newSL, tp);       //--- Modify position
               }
            }
         }
      }
   }
}

//--- Call the function in the tick event handler
// Points trailing can run anytime
if (TrailingType == Trailing_Points && PositionsTotal() > 0) {      //--- Check trailing
   ApplyPointsTrailing();                                           //--- Apply trailing
}

//--- We added this incase we are manually re-scaling the chart
//+------------------------------------------------------------------+
//| Chart event function                                             |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   if (id == CHARTEVENT_CHART_CHANGE) {                             //--- Check chart change
      UpdateFontSizes();                                            //--- Update font sizes
   }
}

トレーリングについては、ApplyPointsTrailing関数を実装して、ポイントベースのトレーリングストップを管理します。有効になっている場合、価格が有利に動くにつれてストップロスを動的に調整します。まず、銘柄のポイント値を_Pointでpointに代入します。次に、PositionsTotal を使ってすべての未決ポジションを逆順にループし、安全に変更処理をおこないます。各チケットの有効性はPositionGetTicketで確認します。銘柄とmagic_numberが一致するポジションについては、PositionGetDoubleとPOSITION_SLでストップロス、POSITION_TPでテイクプロフィット、POSITION_PRICE_OPENでオープン価格、POSITION_TICKETでチケット番号を取得します。買いポジション(POSITION_TYPE_BUY)の場合は、新しいストップロスを「現在のBid − Trailing_Stop_Pips * point」と計算し、桁数に正規化します。もしこれが現在のSLよりもタイトで、かつ利益が「Min_Profit_To_Trail_Pips * point」を超えていれば、obj_Trade.PositionModifyで更新します。売りポジションの場合も同様の手順で処理します。

OnTick関数内では、TrailingTypeがTrailing_Pointsで、かつポジションが存在する場合(PositionsTotal)、毎ティックごとにApplyPointsTrailingを呼び出し、リアルタイムで保護を適用します。また、OnChartEvent関数も含め、スケール変更などのイベントに対応します。idがCHARTEVENT_CHART_CHANGEの場合、UpdateFontSizesを呼んで全てのテキストオブジェクトを更新し、ユーザー操作に応じてビジュアルがシームレスに適応するようにします。コンパイルすると、次の結果が得られます。

CHoCH GIF

この可視化から、構造のブレイクを検出、取引、管理できていることが分かります。これで目標は達成されました。残っている作業は、このプログラムのバックテストをおこなうことです。バックテストについては次のセクションで扱います。


バックテスト

徹底的なバックテストによって、次の結果が得られました。

バックテストグラフ

グラフ

バックテストレポート

レポート


結論

本記事ではMQL5で性格の変化(CHoCH)検出システムを開発しました。このシステムは、バーをスキャンしてスイングハイとスイングローを特定しラベル付けすることでトレンドを判定し、反転を示すブレイクで取引を発動します。バー単位およびティック単位でのモードに対応し、固定取引ラベル、未決ポジションの制限、オプションでポイントベースのトレーリングストップもサポートします。さらに、スイングは色付きアイコンやラベルで可視化され、CHoCHのブレイクは矢印付きのラインやテキストで表示されます。チャートスケールの変更時にはフォントサイズが動的に更新され、デバッグ用にプリントログも記録可能です。

免責条項:本記事は教育目的のみを意図したものです。取引には重大な財務リスクが伴い、市場の変動によって損失が生じる可能性があります。本プログラムを実際の市場で運用する前に、十分なバックテストと慎重なリスク管理が不可欠です。

このCHoCH戦略により、スイングブレイクによる反転を検出してプライスアクションシグナルに基づいた取引が可能になります。今後の最適化や戦略改善にも対応できる準備が整っています。取引をお楽しみください。

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

添付されたファイル |
CHoCH_EA.mq5 (46.57 KB)
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
プライスアクション分析ツールキットの開発(第53回):サポート・レジスタンスゾーン発見のためのPattern Density Heatmap プライスアクション分析ツールキットの開発(第53回):サポート・レジスタンスゾーン発見のためのPattern Density Heatmap
本記事では、パターン密度ヒートマップ(Pattern Density Heatmap)を紹介します。これは、繰り返し出現するローソク足パターンの検出結果を、統計的に有意なサポート・レジスタンスゾーンに変換するプライスアクションマッピングツールです。単一のシグナルを個別に扱うのではなく、EAは検出結果を固定価格レンジに集約し、密度をスコア化(必要に応じて直近の重み付けも可能)し、高い時間軸のデータと照合してレベルを確認します。その結果として得られるヒートマップは、市場が過去にどの価格レベルで反応したかを可視化し、売買のタイミング、リスク管理、戦略への信頼性向上に活用できます。あらゆる取引スタイルに対応可能です。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
MQL5におけるARIMA予測指標 MQL5におけるARIMA予測指標
本記事では、MQL5でARIMA予測インジケーターを実装する方法について説明します。ARIMAモデルがどのように予測を生成するのか、またそれが外国為替市場や株式市場全般にどのように適用できるのかを解説します。さらに、自己回帰(AR)とは何か、自己回帰モデルがどのように予測に利用されるのか、その仕組みについても説明します。