MQL5での取引戦略の自動化(第44回):スイングハイ/ローのブレイクによる性格の変化(CHoCH)検出
はじめに
前回の記事(第43回)では、MetaQuotes Language 5 (MQL5)で適応型線形回帰チャネル戦略を開発しました。この戦略では、回帰線と偏差バンドを計算し、十分な傾きのときのみ動作、偏差に応じて自動的に拡張または再作成、通常モードと逆モードをサポート、チャネル内のブレイクアウトでエントリー、中間線クロスで決済、ポジション数制限、塗りつぶしゾーンの可視化(ラベル、矢印付き)などを実現しました。第44回では、スイングハイやスイングローのブレイクを用いた性格の変化(CHoCH)検出システムを開発します。
本システムはバーをスキャンし、スイングハイやスイングローを識別してHH/LH/LL/HLでラベル付けし、トレンド方向を判定します。反転を示すブレイク時に取引をおこない、ダウントレンドでは高値上抜けで買い、アップトレンドでは安値下抜けで売りをおこないます。バーごとまたはティックごとのモード、固定取引レベルとリスクリワード比、取引制限、トレーリングストップ、アイコンやラベル、ブレイクライン、動的フォントなどの視覚的表示もサポートします。本記事では以下のトピックを扱います。
最終的に、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ブレイクには矢印付きのラインやテキストで視覚的に示します。チャートのスケール変更にも対応するため、フォントサイズは動的に調整されます。視覚的には、次のようなフレームワークで実現されます。

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オブジェクトであれば、ObjectSetIntegerとOBJPROP_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コードを参照すると分かりやすいです。

続いて、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のブレイクアウト検出と取引実行を処理します。まず、SymbolInfoDoubleとSYMBOL_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でもおこなう必要があります。以下にアプローチを示します。
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のセットアップを検出できるようになりました。残る作業は、相場が有利に動いた際に利益を最大化するためのトレーリングストップを追加することだけです。それを実装すれば完了です。
//+------------------------------------------------------------------+ //| 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を呼んで全てのテキストオブジェクトを更新し、ユーザー操作に応じてビジュアルがシームレスに適応するようにします。コンパイルすると、次の結果が得られます。

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

バックテストレポート

結論
本記事ではMQL5で性格の変化(CHoCH)検出システムを開発しました。このシステムは、バーをスキャンしてスイングハイとスイングローを特定しラベル付けすることでトレンドを判定し、反転を示すブレイクで取引を発動します。バー単位およびティック単位でのモードに対応し、固定取引ラベル、未決ポジションの制限、オプションでポイントベースのトレーリングストップもサポートします。さらに、スイングは色付きアイコンやラベルで可視化され、CHoCHのブレイクは矢印付きのラインやテキストで表示されます。チャートスケールの変更時にはフォントサイズが動的に更新され、デバッグ用にプリントログも記録可能です。
免責条項:本記事は教育目的のみを意図したものです。取引には重大な財務リスクが伴い、市場の変動によって損失が生じる可能性があります。本プログラムを実際の市場で運用する前に、十分なバックテストと慎重なリスク管理が不可欠です。
このCHoCH戦略により、スイングブレイクによる反転を検出してプライスアクションシグナルに基づいた取引が可能になります。今後の最適化や戦略改善にも対応できる準備が整っています。取引をお楽しみください。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20355
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
プライスアクション分析ツールキットの開発(第53回):サポート・レジスタンスゾーン発見のためのPattern Density Heatmap
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
MQL5におけるARIMA予測指標
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索