English Deutsch
preview
プライスアクション分析ツールキットの開発(第21回):Market Structure Flip Detector Tool

プライスアクション分析ツールキットの開発(第21回):Market Structure Flip Detector Tool

MetaTrader 5トレーディングシステム |
97 1
Christian Benjamin
Christian Benjamin

内容


はじめに

このEAは取引における最も難しい課題の一つである「騙しの反転シグナル」に対処します。乱高下する価格変動は単純なピボット検出を狂わせ、トレーダーを騙しの動き(ホイップソー)に巻き込んでしまいます。Market Structure Flip Detectorは、ATR (Average True Range)を柔軟なバー数フィルターに変換することでこれを解決します。小さな値動きを無視し、有効な高値と安値のみを捉え、より高値更新が高値切り下げに変わったときには下降反転を、より安値更新が安値切り上げに変わったときには上昇反転を検出します。このEAを使うことで、以下のことが理解できます。

  • ATRを、荒れた相場では拡大し、落ち着いた相場では収縮する深みのある指標に変換する方法
  • 候補となる高値や安値の前後のバーを正確に一定数スキャンしてピボットを確認する手法
  • トレンドのバイアスを維持し、前回の構造が破られた後にのみ反転が発生するようにする方法
  • チャート上に矢印やピボットラベルを表示し、反転の回数やタイミングを追跡するライブ統計パネルでシグナルを注釈付けする方法

最終的に、市場のノイズを排除し、真の反転が発生した際に音声やプッシュ通知を通じて、最もクリーンでルールベースな反転シグナルのみを届ける堅牢なEAを得ることができます。


このツールが重要な理由

騙しの反転シグナルは、乱高下する相場におけるピボットベースのシグナルの半数以上を占めており、頻繁なホイップソーや期待値の低下を引き起こします。そこで、スイングポイントのフィルターを、1978年にワイルダーが単純な高値-安値の範囲よりも正確にボラティリティを測定するために導入したATRに連動させることで、ピボットに必要な「深さ」を動的に調整します。TRがボラティリティの急増時に上昇するとフィルターは広がり、小さく不規則な値動きを無視します。一方、市場が落ち着くとフィルターは狭まり、正確な反転を迅速に捉えます。

バーあたりのTR (True Range)は次のように定義されます。

TRの式


ATRはTRのn期間単純移動平均です(デフォルトn = 14)

ATRをバーカウントの深さdに変換するには、次の操作を実行します。

ATR

ここで、Pointは最小価格増分、mはATR乗数、fは緩和係数です。 

下降反転は、まず高値更新が発生した後、次のスイングハイがその直前のピークを下回った場合に発生します。上昇反転はその逆で、まず安値更新が発生した後、次のスイングローが直前のボトムを上回ったときに起こります。リターンが平均0、分散を「ボラティリティの2乗」とする正規分布に従うと仮定した場合、「2×深さ設定+1」本の足の中で任意の1本が最高値(または最安値)となる確率は、単純に「1 ÷(2×深さ+1)」となります。この深さの設定を市場のボラティリティに連動させることで、シグナルの信頼性を高めることができます。具体的には、ATRをπ/2の平方根(√(π/2))で割ることで、実効的なボラティリティを見積もり、それをもとに深さを決定します。これにより、ノイズ(誤シグナル)を約5%前後に抑えるような入力設定が可能になります。

TradeStationのリサーチによると、ATRをベースにしたピボットウィンドウは、ノイズによる無駄な取引を約40%削減し、S&P 500の過去5年のデータにおいて純利益を約22%向上させたとの結果が出ています。QuantifiedStrategies.comでは、EURUSDおよびES先物におけるバックテストで、ATRフィルターを適用したピボット手法が、勝率を約35%から58%に改善し、平均リスクリワード比も約1.1から1.8に向上したと報告されています。また、TradingViewのコミュニティでも、ATRウィンドウを用いたピボットツールが、特に1時間足(1H)および4時間足(4H)チャートで、機関投資家のオーダーフローブレイクと高い相関を示すという意見が多数寄せられています。


行動計画の概要

このエキスパートアドバイザー(EA)は、現在のATRを使って「深さウィンドウ」を動的に調整することで、市場のノイズを除去します。市場が荒れているときはウィンドウを広く、静かなときは狭く設定し、そのウィンドウ内で各確定足の高値・安値が近隣バーと比較してスイングポイントかどうかを判断します。EAは直近2つのスイングハイとスイングローを記憶し、価格構造の変化を追跡するシンプルなバイアスフラグを保持しています。このフラグは、新たな高値が前回の高値を上回った場合に上昇、新たな安値が前回の安値を下回った場合に下降へと切り替わります。そして、市場構造に反転が見られると、つまり、上昇状態において最新の高値が前回の高値を下回った場合(下降反転)、あるいは下降状態で最新の安値が前回の安値を上回った場合(上昇反転)に、EAはチャート上に色付きの矢印を描画し、両方のピボットにラベルを付けます。同時に、ライブの統計パネルを更新し、必要に応じてサウンド通知やプッシュアラートを発します。この手法により、「高値更新からの高値切り下げ」や「安値更新からの安値切り上げ」といった、本質的な市場構造の反転のみを明確に視覚化することが可能になります。

下降反転

上昇トレンドは、価格が連続して2回、高値を更新したときに確認されます。その後、EAは、前回のスイングハイを下回るスイングハイが出現するかどうかを監視します。スイングハイとは、ATRをもとに算出されたウィンドウ内で最も高い地点として定義されます。「上昇状態」にあるなかでこの低いスイングハイを検出すると、EAは該当するバーに「LH」(Lower High、高値切り下げ)というラベル付きの赤い矢印を表示します。この視覚的なサインに加えて、EAはアラートを発し、下降反転として記録します。これは、市場において売り手の影響力が強まり始めている兆候とみなされます。

下降反転

上昇反転

下降トレンドは、価格が連続して2回、安値を更新したときに確立されます。その後、EAは、前回のスイングローを上回るスイングローが出現するかどうかを監視します。スイングローは、ATRに基づくウィンドウ内で最も低いポイントとして定義されます。「下降状態」にあるなかでこの高いスイングローを検出すると、EAは該当するバーに「HL」(Higher Low、安値切り上げ)というラベル付きの緑の矢印を表示します。加えて、アラートを発し、上昇反転として記録します。これは、買い手の関心が市場に戻りつつある可能性を示すシグナルと解釈されます。

上昇反転


エキスパートアドバイザーの構造

MQL5ファイルの冒頭では、まず#property行を使って基本情報を設定します。ここではまず厳格なコンパイル(#property strict)を有効にすることで、コンパイラが安全でないキャストや非推奨の関数呼び出しを検出できるようにしています。また、著作権情報、関連リンク、バージョン番号などのタグも記述します。これにより、コードを読む人が誰が書いたのか、どこで詳細情報を確認できるのか、どのバージョンなのかを簡単に把握できます。これらの#property行はプログラムのロジックには影響しませんが、ファイルに明確な「アイデンティティ」を与えるためのラベルとして重要な役割を果たします。

#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/ja/users/lynnchris"
#property version   "1.0"
#property strict

次に、トレーダーが調整する可能性があるすべての設定項目を明示します。最初の3つの入力は、ATRを使ってピボットを検出する方法に関わるものです。

  • InpAtrPeriod:ATRの計算に使うバー数を設定します。短い期間(例:7)は反応が速くなりますが、ノイズを拾いやすくなります。長い期間(例:21)ではスパイクが平滑化されますが、反応が遅くなります。
  • InpAtrMultiplier:ATRをスイング幅に変換する際の倍率を指定します。値が1.0の場合、価格が1ATR以上変動しない限り、ピボットとしては認識されません。これを1.5や2.0に上げると、フィルターがより厳しくなります。
  • InpAtrLoosenFactor:上記のスイング幅を0〜1の範囲で縮小する係数です。たとえば0.5なら必要な動きが半分になり、ボラティリティが低い局面でも早めにピボットが出現します。

続いて、チャートのレイアウトに関する設定です。

  • InpAutoShift:新しいローソク足が追加されるたびに、右側に余白を自動確保します。
  • InpShiftBars:右側に確保する空白バーの数を定義します(デフォルトは5本)。

このシンプルな余白のおかげで、矢印、ラベル、統計パネルがプライスアクションに重ならず、視認性が保たれます。

最後に、2種類のアラートメソッドを提供します。

  • InpEnableSound:反転が発生したときにWAVファイルを再生します。
  • InpSoundFile:使用するWAVファイル名を指定します(MetaTrader 5のSoundsフォルダにあるファイル)。
  • InpEnablePush:反転時にMetaTrader 5のモバイルアプリにプッシュ通知を送信します。

これらのオプションにより、デスクトップで音を鳴らす、スマホで通知を受け取る、またはその両方という好みに応じた通知スタイルが選べます。

input int    InpAtrPeriod       = 14;      // How many bars for ATR
input double InpAtrMultiplier   = 1.0;     // Scale ATR into bar‑depth
input double InpAtrLoosenFactor = 0.5;     // Optional: loosen the swing filter
input bool   InpAutoShift       = true;    // Push bars left for visibility
input int    InpShiftBars       = 5;       // Number of bars for right margin
input bool   InpEnableSound     = true;    // Play a sound on flip
input string InpSoundFile       = "alert.wav";
input bool   InpEnablePush      = false;   // Send push notifications

EAが起動すると、OnInit関数が最初に実行されます。まず、InpAutoShiftが有効になっているかどうかをチェックし、有効である場合はChartSetInteger関数を使ってCHART_SHIFTにInpShiftBarsの値を設定します。これにより、新しいローソク足が追加されるたびにチャートが左に押し出され、矢印やラベルなどの注釈表示用スペースが右側に確保されます。次に、MetaTraderの組み込みATRインジケーターのハンドルを取得するためにiATR関数を呼び出し、その結果をatrHandleに保存します。ハンドルが無効だった場合はINIT_FAILEDを返して初期化を即座に中断します。その後、panelNameという名前のOBJ_LABELを作成し、チャートの左上コーナーに固定表示します。このラベルは、横方向・縦方向にそれぞれ10ピクセルだけオフセットされ、フォントサイズは10、文字色は黄色に設定されます。最後に、ATRデータへのアクセスと統計パネルの初期化が正常に完了したことを示すためにINIT_SUCCEEDEDを返します。これにより、OnTick関数内での処理を安心して開始できる状態が整います。

int OnInit()
{
   if(InpAutoShift)
      ChartSetInteger(0, CHART_SHIFT, InpShiftBars);

   atrHandle = iATR(_Symbol, _Period, InpAtrPeriod);
   if(atrHandle == INVALID_HANDLE)
      return INIT_FAILED;

   ObjectCreate(0, panelName, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, panelName, OBJPROP_CORNER,    CORNER_LEFT_UPPER);
   ObjectSetInteger(0, panelName, OBJPROP_XDISTANCE, 10);
   ObjectSetInteger(0, panelName, OBJPROP_YDISTANCE, 10);
   ObjectSetInteger(0, panelName, OBJPROP_FONTSIZE,  10);
   ObjectSetInteger(0, panelName, OBJPROP_COLOR,     clrYellow);

   return INIT_SUCCEEDED;
}

EAを削除したり、MetaTrader 5を終了したりすると、OnDeinit関数が呼び出されます。この関数では、チャート上に表示されたすべての矢印とテキストオブジェクトを削除し、統計パネルのラベルを消去し、ATRのハンドルを解放します。これにより、チャートの不要な表示物を残さず、リソースも適切に開放されます。

void OnDeinit(const int reason)
{
   ObjectsDeleteAll(0, -1, OBJ_ARROW);
   ObjectsDeleteAll(0, -1, OBJ_TEXT);
   ObjectDelete(0, panelName);
   if(atrHandle != INVALID_HANDLE)
      IndicatorRelease(atrHandle);
}

OnTickルーチンでは、すべての価格更新ごとにピボット判定ロジックが走ってしまわないように、static datetime lastBarを使ってガードをかけます。まず、iTime(..., 1)を呼び出して直近で確定済みのローソク足の開始時刻(thisBar)を取得し、それをlastBarと比較します。もし同じであれば、まだ同じ足の途中だと判断し、処理を中断してそのまま戻ります。thisBarが前回と異なった場合、新しいバーが確定したとみなし、lastBarを更新して次の処理に進みます。

次に、CopyBuffer関数を使ってATRハンドルから最新のATR値のみを取得します。この呼び出しに失敗した場合は、無効なデータで処理を続けないように即座に中断します。ATRの取得に成功した場合、この値を使ってスイングの「深さ」を計算します。これは、ATRの価格単位をバー数に変換するもので、ATR値を最小価格単位(SYMBOL_POINT)で割り、さらにInpAtrMultiplierとInpAtrLoosenFactorを掛け合わせて調整します。そして、MathMax(..., 1)を用いて最小でも1本以上の深さを保証します。こうして得られる「深さ」は、ボラティリティが高まると自動的に拡大し(=より大きな動きでのみピボットを検出)、市場が落ち着くと縮小(=小さな動きでもピボット検出)する、動的に変化する柔軟なパラメータです。その後、この深さをピボット検出用の関数に渡して処理を続行します。

void OnTick()
{
   static datetime lastBar=0;
   datetime thisBar = iTime(_Symbol,_Period,1);
   if(thisBar == lastBar) return;
   lastBar = thisBar;

   double atrBuf[];
   if(CopyBuffer(atrHandle, 0, 1, 1, atrBuf) <= 0) return;
   double atr = atrBuf[0];

   int depth = MathMax(1,
      int(atr / SymbolInfoDouble(_Symbol, SYMBOL_POINT)
          * InpAtrMultiplier * InpAtrLoosenFactor));
   // … pivot checks follow …
}

深さが定義されたら、ピボットを検証するために2つのシンプルなループ処理を実行します。「IsSwingHigh(1, depth)」は、指定した深さの範囲内で、どの足の高値も候補バーの高値を上回っていないことを確認します。IsSwingLowはこれと反対に、安値について同様の条件をチェックします。新しいスイングハイまたはスイングローを検出した場合、lastHighをprevHighにシフトし(安値についても同様)、その時点のタイムスタンプを記録します。現在と直前のピボット両方を追跡することで、*その後の比較処理(例:高値の更新か否か、安値の切り上げか否か)が可能になります。

bool newHigh = IsSwingHigh(1, depth);
bool newLow  = IsSwingLow (1, depth);
double h = iHigh(_Symbol,_Period,1), l = iLow(_Symbol,_Period,1);

if(newHigh)
{
   prevHigh     = lastHigh;
   prevHighTime = lastHighTime;
   lastHigh     = h;
   lastHighTime = thisBar;
}
if(newLow)
{
   prevLow      = lastLow;
   prevLowTime  = lastLowTime;
   lastLow      = l;
   lastLowTime  = thisBar;
}

ピボットが取得できたら、現在のトレンドバイアスを示すstructStateを更新します。 高値が更新された場合はstateを1に設定し、次に下降反転が起こる可能性があることを示します。逆に、安値が更新された場合はstateを2に設定し、次に上昇反転が起こる可能性があることを示します。次に、実際に転換が発生したかをチェックします。stateが1(上昇状態)のときに、新しい高値が前回の高値を下回った場合、それは弱気転換です。stateが2(下降状態)のときに、新しい安値が前回の安値を上回った場合、それは強気転換です。転換が検出されたら、チャート描画や通知用の関数を呼び出し、関連するカウンタをインクリメントします。

// Update bias
if(newHigh && prevHigh>0 && lastHigh > prevHigh) structState = 1;
if(newLow  && prevLow>0  && lastLow  < prevLow) structState = 2;

// Bearish flip
if(newHigh && structState==1 && lastHigh < prevHigh)
{
   PlotArrow(...);  PlotLabel(...);  Notify(...);
   if(countBear>0) sumBearInterval += (lastHighTime - prevLowTime)/60.0;
   countBear++;
}

// Bullish flip
if(newLow && structState==2 && lastLow > prevLow)
{
   PlotArrow(...);  PlotLabel(...);  Notify(...);
   if(countBull>0) sumBullInterval += (lastLowTime - prevHighTime)/60.0;
   countBull++;
}

チャート上の描画処理は、PlotArrowとPlotLabelの2つのヘルパー関数に集約されています。これにより、コードの効率性と可読性の両方を確保しています。各関数の冒頭ではまず、「ObjectFind(0, name)」を使って指定した名前のオブジェクトがすでに存在するかを確認します。この関数の処理時間は、チャート上のオブジェクト数に対してO(n)となりますが、1バーごとに1回程度のチェックであれば、現代のマシンでは十分高速です。オブジェクトがまだ存在しない場合(ObjectFind関数が「–1」を返す)、ObjectCreateを使って一度だけオブジェクトを生成します。オブジェクトの種類は、PlotArrowでは矢印、PlotLabelではラベルを選択します。

次に、各オブジェクトのプロパティを設定します。矢印(PlotArrow)では、OBJPROP_ARROWCODEを用いて表示する記号を指定します(例:Wingdingsフォントのコード234は赤い下向き矢印など)。また、OBJPROP_COLORによって矢印の色を定義します。ラベル(PlotLabel)では、OBJPROP_TEXTに「LH」や「HL」などの文字列を設定し、必要に応じて位置のオフセットやフォントサイズも指定します。このように、ObjectCreate関数を毎回呼ばないようにすることで、長時間稼働した際に同一オブジェクトが大量に生成されてしまう問題(パフォーマンスの劣化やメモリ肥大)を未然に防ぎます。さらにこの設計により、各ピボットマーカーには一貫性のある識別名が付けられるため、後でOBJPROP_ZORDER(描画優先度)を変更したり、条件付きで削除したりする場合でも、対象のオブジェクトを確実に指定して操作することができます。他のチャート要素に誤って干渉する心配がありません。

void PlotArrow(string name, datetime t, double price, int code, color c)
{
   if(ObjectFind(0, name) < 0)
   {
      ObjectCreate(0, name, OBJ_ARROW, 0, t, price);
      ObjectSetInteger(0, name, OBJPROP_ARROWCODE, code);
      ObjectSetInteger(0, name, OBJPROP_COLOR, c);
   }
}
// PlotLabel is identical, but creates OBJ_TEXT and sets OBJPROP_TEXT.

各バーの後に、panelNameラベルを再構築して次の内容を表示します。

  • 現在のピボットの深さ
  • 上昇反転と下降反転の合計
  • 反転間の平均時間(分単位)(少なくとも2回反転した場合)

これにより、選択したATRパラメータのもとで、市場構造の転換(ブレイク)がどれほど頻繁に起こっているかを即座に把握できます。

string txt = StringFormat("Depth: %d\nBull Flips: %d\nBear Flips: %d",
                          depth, countBull, countBear);
if(countBull>1)
   txt += "\nAvg HL Int: " + DoubleToString(sumBullInterval/(countBull-1),1) + "m";
if(countBear>1)
   txt += "\nAvg LH Int: " + DoubleToString(sumBearInterval/(countBear-1),1) + "m";

ObjectSetString(0, panelName, OBJPROP_TEXT, txt);

最後に、Notify(msg)関数は、すべてのアラート手段を一か所にまとめています。MetaTrader 5のポップアップ通知として必ずAlert(msg)を呼び出し、設定に応じて音声通知(PlaySound)やプッシュ通知(SendNotification)をオプションで実行します。このように通知処理を一元管理することで、将来的にメール通知やWebhook連携を追加するのも簡単になります。

void Notify(string msg)
{
   Alert(msg);
   if(InpEnableSound) PlaySound(InpSoundFile);
   if(InpEnablePush)  SendNotification(msg);
}


ソースコードリスト

//+------------------------------------------------------------------+
//|                                 Market Structure Flip Detector EA|
//|                                   Copyright 2025, MetaQuotes Ltd.|
//|                           https://www.mql5.com/ja/users/lynnchris|
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/ja/users/lynnchris"
#property version   "1.0"
#property strict

//--- user inputs
input int    InpAtrPeriod       = 14;      // ATR lookback
input double InpAtrMultiplier   = 1.0;     // ATR to swing depth factor (lower = looser)
input double InpAtrLoosenFactor = 0.5;     // Loosen factor for ATR confirmation (0-1)
input bool   InpAutoShift       = true;    // Auto-enable chart right shift
input int    InpShiftBars       = 5;       // Bars for right margin
input bool   InpEnableSound     = true;
input string InpSoundFile       = "alert.wav";
input bool   InpEnablePush      = false;

//--- global vars
string   panelName = "FlipPanel";
int      atrHandle;
int      structState = 0;
double   prevHigh=0, lastHigh=0;
datetime prevHighTime=0, lastHighTime=0;
double   prevLow=0, lastLow=0;
datetime prevLowTime=0, lastLowTime=0;
int      countBull=0, countBear=0;
double   sumBullInterval=0, sumBearInterval=0;

//+------------------------------------------------------------------+
int OnInit()
  {
   if(InpAutoShift)
      ChartSetInteger(0, CHART_SHIFT, InpShiftBars);

   atrHandle = iATR(_Symbol, _Period, InpAtrPeriod);
   if(atrHandle == INVALID_HANDLE)
      return(INIT_FAILED);

   ObjectCreate(0, panelName, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, panelName, OBJPROP_CORNER,    CORNER_LEFT_UPPER);
   ObjectSetInteger(0, panelName, OBJPROP_XDISTANCE, 10);
   ObjectSetInteger(0, panelName, OBJPROP_YDISTANCE, 10);
   ObjectSetInteger(0, panelName, OBJPROP_FONTSIZE,  10);
   ObjectSetInteger(0, panelName, OBJPROP_COLOR,     clrYellow);
   ObjectSetInteger(0, panelName, OBJPROP_BACK,      false);
   ObjectSetInteger(0, panelName, OBJPROP_ZORDER,    1);

   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ObjectsDeleteAll(0, -1, OBJ_ARROW);
   ObjectsDeleteAll(0, -1, OBJ_TEXT);
   ObjectDelete(0, panelName);
   if(atrHandle != INVALID_HANDLE)
      IndicatorRelease(atrHandle);
  }

//+------------------------------------------------------------------+
void OnTick()
  {
   static datetime lastBar=0;
   datetime thisBar = iTime(_Symbol,_Period,1);
   if(thisBar==lastBar)
      return;
   lastBar=thisBar;

   double atrBuf[];
   if(CopyBuffer(atrHandle,0,1,1,atrBuf)<=0)
      return;
   double atr = atrBuf[0];

// loosen ATR confirmation by InpAtrLoosenFactor (0-1)
   double rawDepth = atr/SymbolInfoDouble(_Symbol,SYMBOL_POINT)*InpAtrMultiplier;
   int depth = MathMax(1, (int)(rawDepth * InpAtrLoosenFactor));

   bool newHigh=false,newLow=false;
   double h=iHigh(_Symbol,_Period,1), l=iLow(_Symbol,_Period,1);

   if(IsSwingHigh(1,depth))
     {
      prevHigh = lastHigh;
      prevHighTime = lastHighTime;
      lastHigh = h;
      lastHighTime = thisBar;
      newHigh = true;
     }
   if(IsSwingLow(1,depth))
     {
      prevLow = lastLow;
      prevLowTime = lastLowTime;
      lastLow = l;
      lastLowTime = thisBar;
      newLow = true;
     }

   double off = SymbolInfoDouble(_Symbol,SYMBOL_POINT)*10;

// Bearish Flip: Lower High after a Higher High
   if(newHigh && structState==1 && prevHigh>0 && lastHigh<prevHigh)
     {
      // signal arrow and label at current LH
      PlotArrow("Bear_"+IntegerToString((int)lastHighTime), lastHighTime, lastHigh, 234, clrRed);
      PlotLabel("LH_"+IntegerToString((int)lastHighTime), lastHighTime, lastHigh+off, "LH", clrRed);
      // label the previous LH used for comparison
      PlotLabel("PrevLH_"+IntegerToString((int)prevHighTime), prevHighTime, prevHigh+off, "LH_prev", clrRed);
      Notify("Bearish Flip (LH) at "+TimeToString(lastHighTime,TIME_DATE|TIME_MINUTES));
      if(countBear>0)
         sumBearInterval += (lastHighTime-prevLowTime)/60.0;
      countBear++;
     }

// Bullish Flip: Higher Low after a Lower Low
   if(newLow && structState==2 && prevLow>0 && lastLow>prevLow)
     {
      // signal arrow and label at current HL
      PlotArrow("Bull_"+IntegerToString((int)lastLowTime), lastLowTime, lastLow, 233, clrLime);
      PlotLabel("HL_"+IntegerToString((int)lastLowTime), lastLowTime, lastLow-off, "HL", clrLime);
      // label the previous HL used for comparison
      PlotLabel("PrevHL_"+IntegerToString((int)prevLowTime), prevLowTime, prevLow-off, "HL_prev", clrLime);
      Notify("Bullish Flip (HL) at "+TimeToString(lastLowTime,TIME_DATE|TIME_MINUTES));
      if(countBull>0)
         sumBullInterval += (lastLowTime-prevHighTime)/60.0;
      countBull++;
     }

// update structure state
   if(newHigh && prevHigh>0 && lastHigh>prevHigh)
      structState = 1;
   if(newLow  && prevLow>0  && lastLow <prevLow)
      structState = 2;

// update panel stats
   string txt = "Depth: "+IntegerToString(depth)+"\n";
   txt += "Bull Flips: "+IntegerToString(countBull)+"\n";
   txt += "Bear Flips: "+IntegerToString(countBear);
   if(countBull>1)
      txt += "\nAvg HL Int: "+DoubleToString(sumBullInterval/(countBull-1),1)+"m";
   if(countBear>1)
      txt += "\nAvg LH Int: "+DoubleToString(sumBearInterval/(countBear-1),1)+"m";
   ObjectSetString(0, panelName, OBJPROP_TEXT, txt);
  }

//+------------------------------------------------------------------+
bool IsSwingHigh(int shift,int depth)
  {
   double p = iHigh(_Symbol,_Period,shift);
   for(int i=shift-depth; i<=shift+depth; i++)
      if(i>=0 && iHigh(_Symbol,_Period,i) > p)
         return false;
   return true;
  }

//+------------------------------------------------------------------+
bool IsSwingLow(int shift,int depth)
  {
   double p = iLow(_Symbol,_Period,shift);
   for(int i=shift-depth; i<=shift+depth; i++)
      if(i>=0 && iLow(_Symbol,_Period,i) < p)
         return false;
   return true;
  }

//+------------------------------------------------------------------+
void PlotArrow(string nm,datetime t,double price,int code,color c)
  {
   if(ObjectFind(0,nm) < 0)
     {
      ObjectCreate(0, nm, OBJ_ARROW, 0, t, price);
      ObjectSetInteger(0, nm, OBJPROP_ARROWCODE, code);
      ObjectSetInteger(0, nm, OBJPROP_COLOR, c);
      ObjectSetInteger(0, nm, OBJPROP_WIDTH, 2);
     }
  }

//+------------------------------------------------------------------+
void PlotLabel(string nm,datetime t,double price,string txt,color c)
  {
   if(ObjectFind(0,nm) < 0)
     {
      ObjectCreate(0, nm, OBJ_TEXT, 0, t, price);
      ObjectSetString(0, nm, OBJPROP_TEXT, txt);
      ObjectSetInteger(0, nm, OBJPROP_COLOR, c);
      ObjectSetInteger(0, nm, OBJPROP_FONTSIZE, 10);
     }
  }

//+------------------------------------------------------------------+
void Notify(string msg)
  {
   Alert(msg);
   if(InpEnableSound)
      PlaySound(InpSoundFile);
   if(InpEnablePush)
      SendNotification(msg);
  }
//+------------------------------------------------------------------+


パフォーマンス成果

以下では、実際の市場環境とバックテストの両方におけるテストの結果を概説します。

実際の市場環境

実際の市場環境

上のチャートでは、EAが最初に「LH_prev」とラベル付けされたスイング高値を検出しています。これは、連続する2つの高値更新があり、上昇構造が確立されたことを示しています。数本後に、前回のピークを超えないスイングハイが検出されます。この上昇トレンド内の高値引き下げは、EAが赤い矢印と「LH」ラベルを該当バーに描画するトリガーとなります。この下降反転のシグナルは、買い勢力の勢いが崩れたことを示し、下降トレンドの始まりを警告します。

実際の市場環境のGIF画像

以下は、EURUSDの実際の市場環境でのEAの動作を示すGIFです。1分足が確定するごとに、EAは連続する安値を追跡し、前回の谷を上回るスイングロー(安値の引き上げ)を見つけます。その安値の引き上げが現れ他場合、緑色の「HL」矢印をチャートに表示し、上昇反転を示します。同時に、画面上部のヘッダーパネルが更新され、ここでは「12回の強気転換」「1回の弱気転換」、および「平均HL間隔108.0分」といった最新の集計が表示されます。この動画は、下降構造から上昇への潜在的な転換を非常にわかりやすく示しています。

実際の市場環境での結果

バックテスト

以下は、複数の時間枠にわたるステップ・インデックス分析の結果をまとめた表です。「ポジティブシグナル」とは、その後市場が示された方向へ一定期間継続的に動いたシグナルを指します。

5分足の時間軸

シグナルの種類 シグナル総数 ポジティブシグナル 勝率
売り 56 39 70%
買い 53 44 83%

15分足の時間軸

シグナルの種類 シグナル総数 ポジティブシグナル 勝率
売り 7 5 71%
買い 14 9 64%

Market Structure Flip Detectorの分析結果から、このツールは特に短期時間軸において一貫して有益なシグナルを生成していることが確認されました。売りシグナルにおいては成功率が70%以上と高く、このツールの有効性を裏付けています。この成果は、プライスアクション分析の自動化における大きな前進であり、低遅延かつ完全にシステマティックな取引ツールキットの実現に一歩近づいたと言えるでしょう。



結論

このツールを実際の市場条件とバックテストの両方で開発およびテストした結果、分析では、このツールが一貫して強力なパフォーマンスを発揮し、特に短い時間枠でのスキャルピングで大きな利益を生み出すことが明らかになりました。ただし、どんな自動ツールでも同様に、実際に取引をおこなう前には他の手法やインジケーターによる追加の確認をおこなうことが重要です。また、このツールをさまざまな通貨ペアでテストすることも非常に重要です。そうすることで、どの市場環境・銘柄で最も良い成果を上げられるかを特定できます。さらに、入力パラメータ(ATR期間や倍率など)を調整しながらテストを進めることで、パフォーマンスの最適化も可能です。

日付 ツール名  詳細 バージョン  アップデート  備考
01/10/24 ChartProjector 前日のプライスアクションをゴースト効果でオーバーレイするスクリプト 1.0 初回リリース ツール番号1
18/11/24 Analytical Comment 前日の情報を表形式で提供し、市場の将来の方向性を予測する 1.0 初回リリース ツール番号2
27/11/24 Analytics Master 2時間ごとに市場指標を定期的に更新  1.01 v.2 ツール番号3
02/12/24 Analytics Forecaster  Telegram統合により、2時間ごとに市場指標を定期的に更新 1.1 v.3 ツール番号4
09/12/24 Volatility Navigator ボリンジャーバンド、RSI、ATR指標を使用して市場の状況を分析するEA 1.0 初回リリース ツール番号5
19/12/24 Mean Reversion Signal Reaper  平均回帰戦略を用いて市場を分析し、シグナルを提供する  1.0  初回リリース  ツール番号6 
9/01/25  Signal Pulse  多時間枠分析ツール 1.0  初回リリース  ツール番号7 
17/01/25  Metrics Board  分析用のボタン付きパネル  1.0  初回リリース ツール番号8 
21/01/25 External Flow 外部ライブラリによる分析 1.0  初回リリース ツール番号9 
27/01/25 VWAP 出来高加重平均価格   1.3  初回リリース  ツール番号10 
02/02/25  Heikin Ashi  トレンドの平滑化と反転シグナルの識別  1.0  初回リリース  ツール番号11
04/02/25  FibVWAP  Python分析によるシグナル生成  1.0  初回リリース  ツール番号12
14/02/25  RSI DIVERGENCE  プライスアクションとRSIのダイバージェンス  1.0  初回リリース  ツール番号13 
17/02/25  Parabolic Stop and Reverse (PSAR)  PSAR戦略の自動化 1.0 初回リリース  ツール番号14
20/02/25  Quarters Drawerスクリプト チャートにクォーターレベルを描く  1.0  初回リリース  ツール番号15 
27/02/25  Intrusion Detector 価格がクォーターレベルに達したときに検出して警告する 1.0   初回リリース ツール番号16 
27/02/25  TrendLoom Tool  多時間枠分析パネル 1.0 初回リリース ツール番号17
11/03/25  Quarters Board  クォーターレベルを有効または無効にするボタン付きのパネル  1.0  初回リリース ツール番号18
26/03/25  ZigZag Analyzer  ジグザグインジケーターを使ったトレンドラインの描画  1.0  初回リリース  ツール番号19 
10/04/25  Correlation Pathfinder Pythonライブラリを使用して通貨の相関関係をプロットする 1.0 初回リリース  ツール番号20 
 23/04/25  Market Structure Flip Detector Tool 市場構造反転を検出する 1.0  初回リリース  ツール番号21

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

添付されたファイル |
Flip_Detector.mq5 (14.5 KB)
最後のコメント | ディスカッションに移動 (1)
Alexander Piechotta
Alexander Piechotta | 7 5月 2025 において 11:08
bool IsSwingHigh( int shift, int depth)
  {
   double p = iHigh ( _Symbol , _Period ,shift);
   for ( int i= shift-depth; i<=shift+depth; i++)
       if (i>= 0 && iHigh ( _Symbol , _Period ,i) > p)
         return false ;
   return true ;
  }

こんにちは、なぜint i=shift-depthと 書くのか理解できません

説明していただけますか?ありがとうございます。

取引におけるニューラルネットワーク:制御されたセグメンテーション 取引におけるニューラルネットワーク:制御されたセグメンテーション
この記事では、複雑なマルチモーダルインタラクション分析と特徴量理解の方法について説明します。
Metatrader 5のWebsockets — Windows APIを使用した非同期クライアント接続 Metatrader 5のWebsockets — Windows APIを使用した非同期クライアント接続
この記事では、MetaTraderプログラム向けに非同期のWebSocketクライアント接続を可能にするカスタムDLL(ダイナミックリンクライブラリ)の開発について解説します。
取引におけるニューラルネットワーク:相対エンコーディング対応Transformer 取引におけるニューラルネットワーク:相対エンコーディング対応Transformer
自己教師あり学習は、ラベル付けされていない大量のデータを分析する効果的な手段となり得ます。この手法の効率性は、モデルが金融市場特有の特徴に適応することで実現され、従来手法の有効性も向上します。本記事では、入力間の相対的な依存関係や関係性を考慮した新しいAttention(注意)機構を紹介します。
取引チャート上で双三次補間を用いたリソース駆動型画像スケーリングによる動的MQL5グラフィカルインターフェイスの作成 取引チャート上で双三次補間を用いたリソース駆動型画像スケーリングによる動的MQL5グラフィカルインターフェイスの作成
本記事では、取引チャート上で高品質な画像スケーリングを実現するために、双三次補間(バイキュービック補間)を使用した動的なMQL5グラフィカルインターフェイスについて解説します。カスタムオフセットによる動的な中央配置やコーナーアンカーなど、柔軟なポジショニングオプションも紹介します。