English
preview
プライスアクション分析ツールキットの開発(第43回):ローソク足の確率とブレイクアウト

プライスアクション分析ツールキットの開発(第43回):ローソク足の確率とブレイクアウト

MetaTrader 5 |
16 0
Christian Benjamin
Christian Benjamin

内容



はじめに

本記事では、十字線、包み足、ピンバーという3つの代表的なローソク足形状について確率分析をおこない、異なる通貨ペアや金融商品において、それらが価格変動にどのような影響を与えるかを測定します。これらのローソク足は、トレンドの継続または反転を示唆することが多いとされています。たとえば、下降トレンド中に強気のピンバーや強気の包み足が出現した場合、上方向への反転の可能性を示すことがあります。逆に、上昇トレンド中では、弱気のピンバーや弱気の包み足が下落への反転に先行することがあります。十字線は一般的に迷いを表します。強いトレンドの途中では、一時的な停滞の後にトレンド継続を示す場合があり、重要な支持線または抵抗線付近や大きな値動きの後では、反転の兆候となることもあります。一方、レンジ相場や出来高の文脈がない状況で形成された十字線は、多くの場合中立的な意味合いに留まります。

多くの市場参加者が直面する問題の一つが、早すぎるエントリーです。たとえば、強気のピンバーを確認してすぐに買いを入れたものの、市場が反転せず、結果的に不利なポジションを抱えてしまうケースです。同じローソク足形状であっても、銘柄や時間足によって反応は異なり、特定のパターン後に高いフォロー率を示す市場も存在します。このEAは「確率分析ツール」として設計されており、過去のバーをスキャンし、特定の通貨ペアおよび時間足において、各パターンがどの程度の頻度で継続、反転、またはブレイクアウトの成功につながるかを定量化します。この実証的な視点により、プライスアクションを用いる取引者は、自身が取引する市場において、どのパターンが反転や継続のシグナルとして信頼性が高いのかを把握できます。

上図を詳しく見ると、Aでは強気の包み足が形成され、その後価格が上昇しており、期待通りの結果が確認できます。Bでは十字線の後に弱気のピンバーが出現し、市場は売り手有利に進行しており、弱気シグナルが有効であったことを示しています。同様に、Cでは弱気の包み足が売りを示唆し、期待通りの下落が発生しています。

Dでは支持線付近で十字線が形成され、強気反転の可能性が示唆されています。この場合、続くピンバーは色だけで判断するのではなく、ヒゲの長さや位置関係を考慮して解釈する必要があります。最後にEでは、弱気の包み足が失敗し、価格が下落せずに上昇しており、これらのパターンが確率的なものであり、誤ったシグナルを生む可能性があることを示しています。確率分析ツールとして提示することで、各ローソク足形状が特定の通貨ペアおよび時間足において、どの程度の頻度で反転、継続、または失敗に至るのかを定量的に把握することが可能になります。



戦略概要

ここで理解していただきたいのは、このツールの背後にある考え方です。まずコンセプトを説明し、ロジックを追いやすいようにフローチャートを示します。その後、実装セクションでコーディングの詳細を解説します。

最初に、ツールは、選択された銘柄と時間足について、分析対象として指定された直近の価格バーを取得します。具体的には、各ローソク足の始値、高値、安値、終値に加え、そのバーにおける出来高(取引活動量)を読み込みます。同時に、チャート表示の準備をおこない、すべてのカウンタを初期化して、毎回の実行が新しい状態から始まるようにします。これは、分析結果のパーセンテージが、実際に取引で使用することを想定した新鮮な履歴データを正確に反映するためです。

次に、ツールは過去のバーを一つずつ確認し、それが追跡対象である3種類のローソク足形状(ピンバー、包み足、十字線)のいずれかに該当するかを判定します。簡単に言うと、ピンバーは実体が小さくヒゲが長い形状、包み足は1本のローソク足の実体が直前のローソク足の実体を完全に包み込む形状、十字線は実体が非常に小さい形状です。ツールがこれらの条件に一致するバーを検出すると、その時点を該当パターンの発生として記録します。

各パターンの発生について、ツールは次の情報を記録します。すなわち、パターンが発生した事実、発生した時刻、そしてそのバーにおける出来高です。その後、この発生に対して3つの結果を検証します。1つ目は、直後に確定した次のバーが、そのパターンが示唆する方向に動いたかどうかです。2つ目は、ユーザーが設定した次のN本のバーの中で、価格がその方向に一度でも動いたかどうかです。3つ目は、価格がパターンの高値または安値を、あらかじめ設定されたピップ数だけ突破し、そのブレイクアウトが次のバーで確認されたかどうかです。これらの判定はすべて「はい/いいえ」で記録され、それぞれに対応する出来高も保存されます。

すべての「はい/いいえ」の結果と出来高は、ピンバー、包み足、十字線ごとに個別に集計されます。各パターンについて、ツールは以下の合計値を保持します。発生回数の総数、次のバーで成功した回数、N本以内で成功した回数、ブレイクアウトの発生回数、そのうち確認された回数、さらにパターンが出現したバーの出来高合計と、成功したケースの出来高合計です。これらを分けて管理することで、特定の銘柄と時間足において、各パターンがどのような挙動を示すかを定量的に評価できます。
最も基本的な表示方法では、これらのカウントを分かりやすいパーセンテージに変換します。計算式は次の通りです。
  • 次バー成功率 = (次のバーで成功した回数 ÷ 総発生回数) × 100
  • N本以内成功率 = (N本以内で成功した回数 ÷ 総発生回数) × 100
  • ブレイクアウト成功率 = (確認されたブレイクアウト数 ÷ ブレイクアウト総数) × 100
例:ピンバーが50回検出され、そのうち15回で次のバーが想定通りに動いた場合、15 ÷ 50 = 0.30、すなわち次バー成功率は30%となります。場合によっては、単純な回数ではなく、取引活動量を重視して結果を評価したいこともあります。その場合、ツールは発生回数の代わりに出来高を合計して計算します。計算式は次の通りです。
  • 次バー成功率(出来高加重)=(次バーで成功したパターンの出来高÷全パターンの出来高合計)×100
  • N本以内成功率(出来高加重)=(N本以内で成功したパターンの出来高÷全パターンの出来高合計)×100
  • ブレイクアウト%(出来高加重)=(確認されたブレイクアウトの出来高÷全パターンの出来高合計)×100

例:50回のピンバーの出来高合計が10,000で、そのうち成功した15回の出来高合計が3,000であった場合、3,000 ÷ 10,000 = 0.30、すなわち出来高加重の次バー成功率は30%となります

最後に、ツールは各パターンの数値をダッシュボードに表示し、必要に応じてCSVファイルへ書き出します。通常、各パターンについて、総発生回数、次バー成功率、N本以内成功率、ブレイクアウト回数、ブレイクアウト成功率、そして有効にしていれば出来高加重の指標が表示されます。もし特定のパターンが一度も発生しなかった場合は、計算不能な指標について0%または「N/A」が表示されます。実務的なポイントは明確です。両方の視点を確認することです。単純なパーセンテージは「どれくらいの頻度で起きたか」を示し、出来高加重のパーセンテージは「その結果がどれだけ取引活動に裏付けられていたか」を示します。


実装

Candlestick Probability EAは、主要なローソク足パターン(ピンバー、包み足、十字線)を検出し、それらの過去における挙動を定量化し、結果をチャート上およびファイルとして提示するために設計された、コンパクトでありながら多機能なツールです。このEAは実践的な調査と迅速な視覚的検証を目的としており、ローソク足パターンの認識を、戦略設計や裁量判断に活用できる、測定可能で再現性のある統計情報へと変換します。

以下では、MQL5による実装を構造的に解説し、システム構成、入力パラメータ、統計モデル、可視化、そしてユーザーインターフェースについて説明します。

設定および入力パラメータ

ファイルの冒頭には、簡潔かつ十分にコメントされた入力パラメータ群が配置されており、ユーザーはコードを編集することなくEAの挙動を設定できます。これらのパラメータは、分析対象期間(LookbackBars)、ルックアヘッド期間(LookAheadBars)、ブレイクアウトの閾値(BreakoutPipsBreakoutConfirmBars)、時間足(TF)、表示およびユーザーインターフェースに関するフラグ(ShowPatternMarkersShowControlButtons)、さらにエクスポートや重み付けに関するオプション(ExportCSVUseVolumeWeighting)などを網羅しています。 これらの制御項目をあらかじめ公開しておくことで、迅速な試行錯誤が可能になるだけでなく、結果を調整・比較する際にどのパラメータが重要であるかを明確に示す役割も果たします。

//--- Inputs, user-configurable parameters
input int    LookbackBars        = 2000;       // how many bars to scan
input int    LookAheadBars       = 5;          // how many bars forward to look for follow-up
input int    BreakoutPips        = 5;          // breakout threshold in pips
input int    BreakoutConfirmBars = 1;          // bars after breakout used for confirmation
input ENUM_TIMEFRAMES TF        = PERIOD_CURRENT;
input bool   ExportCSV          = false;
input string CSVFileName        = "PatternStats.csv";

input bool   ShowPatternMarkers = true;
input int    MaxMarkers         = 500;
input int    MarkerFontSize     = 12;
input int    MarkerOffsetPips   = 3;
input int    MarkerStackWindow  = 2;

input bool   UseVolumeWeighting = true;
input bool   ShowControlButtons = true;

パターン統計データモデル

各ローソク足パターンに関する指標は、単一のPatternStats構造体に集約されています。この構造体には、パターンの発生回数、次バー一致数およびルックアヘッド期間内での一致数、ブレイクアウト回数と確認済みブレイクアウト成功数、出来高の累計値、ならびに出来高加重指標が格納されます。このように指標を整理することで、集計処理をモジュール化でき、分析の中核ループを変更することなく、新しい統計指標やパターンを容易に追加できる設計となっています。

struct PatternStats
  {
   string name;
   int    total;
   int    nextSameDir;
   int    withinLookAheadSame;
   int    breakouts;
   int    breakoutSuccess;
   double volTotal;
   double weightedNextSameDir;
   double weightedWithin;
   double weightedBreakouts;
   double weightedBreakoutSuccess;
  };

// Example declarations
PatternStats pinbar, engulfing, dojiStats;

初期化(OnInit)

起動時の処理はOnInit()内で決定論的に実行されます。コードではResetStats()を用いてすべての統計データをゼロに初期化し、各PatternStatsインスタンスに可読性の高い名称を割り当てます。あわせて、バーごとのマーカー管理用カウンタを準備し、対話機能が有効な場合には制御ボタンを作成します。その後、EventSetTimer()によって短いイベントタイマーを開始し、Analyze()を即座に一度呼び出します。これにより、EAをチャートに適用した直後から分析結果が表示され、再起動時にも挙動が一貫し、ライブ運用中においても安全かつ再現性のある動作が保証されます。

int OnInit()
  {
   // reset stats and name patterns
   ResetStats();
   pinbar.name     = "Pinbar";
   engulfing.name  = "Engulfing";
   dojiStats.name  = "Doji";

   // prepare marker counts buffer
   ArrayResize(markerCounts, 64);
   for(int i=0;i<ArraySize(markerCounts);i++) markerCounts[i]=0;

   // create UI if requested
   if(ShowControlButtons) CreateControlButtons();

   // run periodically to detect new bars
   EventSetTimer(5);

   // initial analysis
   Analyze();
   return(INIT_SUCCEEDED);
  }

クリーンな終了(OnDeinit)

OnDeinit()では、確実な終了処理が実装されています。タイマーを停止し、マーカー、ボタン、ダッシュボード要素を含む、EAが作成したすべてのチャートオブジェクトを削除します。さらに、チャートの再描画を強制することで、表示上の残存物を完全に除去します。このように慎重なクリーンアップをおこなうことで、不要な表示の残留を防ぎ、EAを削除した後も他のツールやテンプレートに影響を与えないことが保証されます。

void OnDeinit(const int reason)
  {
   EventKillTimer();
   DeleteAllOurObjects();   // removes only objects with EA prefixes
   ChartRedraw();
  }

リアクティブ更新(OnTimer、OnChartEvent)

チャートの応答性は、軽量なタイマー処理とチャートイベント処理によって制御されています。OnTimer()は新しいバーの到来を検出し、不要な処理を避けるため、必要な場合にのみAnalyze()を呼び出します。一方、OnChartEvent()はボタンのクリック操作を監視し、各検出ストリームの有効と無効を切り替えます。制御ボタンがクリックされると、対応する有効フラグが更新され、ボタンラベルが現在の統計値を反映した内容に即座に更新されます。その後、分析処理が再実行されるため、ユーザー操作の結果がただちに画面上の統計情報に反映されます。

void OnTimer()
  {
   static datetime lastTime = 0;
   datetime t = iTime(_Symbol, TF, 1); // previous completed bar
   if(t == lastTime) return;
   lastTime = t;
   Analyze();
  }

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   if(id == CHARTEVENT_OBJECT_CLICK && StringFind(sparam, "Btn_") == 0)
     {
      if(StringCompare(sparam, "Btn_Pinbar") == 0) { enabledPinbar = !enabledPinbar; UpdateButtonLabel("Btn_Pinbar", pinbar, enabledPinbar); Analyze(); }
      if(StringCompare(sparam, "Btn_Engulfing") == 0) { enabledEngulfing = !enabledEngulfing; UpdateButtonLabel("Btn_Engulfing", engulfing, enabledEngulfing); Analyze(); }
      if(StringCompare(sparam, "Btn_Doji") == 0) { enabledDoji = !enabledDoji; UpdateButtonLabel("Btn_Doji", dojiStats, enabledDoji); Analyze(); }
     }
  }

パターン検出関数

各パターンの判定ルールは、IsPinbar(shift)、IsEngulfing(shift)、IsDoji(shift)といった、簡潔でパラメータ化されたヘルパー関数として実装されています。これらの関数は、必要最小限のバー情報のみを参照し、PinbarBodyPct、PinbarTailPct、DojiBodyPctなどの設定可能な閾値を適用して判定をおこないます。各関数は、方向性を示す値(+1、−1、0)を返す設計となっており、これにより後続の集計処理が単純化されると同時に、優先順位付けや複合的なパターンロジックの実装も容易になります。

int IsPinbar(int shift)
  {
   if(IsDoji(shift)) return 0;
   double o = iOpen(_Symbol, TF, shift), c = iClose(_Symbol, TF, shift);
   double h = iHigh(_Symbol, TF, shift), l = iLow(_Symbol, TF, shift);
   double range = h - l;
   if(range <= 0) return 0;
   double body = MathAbs(c - o);
   if(body > PinbarBodyPct * range) return 0;
   double upperTail = h - MathMax(o, c);
   double lowerTail = MathMin(o, c) - l;
   if(lowerTail >= PinbarTailPct * range && upperTail <= (1.0 - PinbarTailPct) * range) return +1;
   if(upperTail >= PinbarTailPct * range && lowerTail <= (1.0 - PinbarTailPct) * range) return -1;
   return 0;
  }

int IsEngulfing(int shift)
  {
   int totalBars = iBars(_Symbol, TF);
   if(totalBars <= shift + 1) return 0;
   double o1 = iOpen(_Symbol, TF, shift), c1 = iClose(_Symbol, TF, shift);
   double o2 = iOpen(_Symbol, TF, shift + 1), c2 = iClose(_Symbol, TF, shift + 1);
   double body1 = c1 - o1, body2 = c2 - o2;
   if(body1 > 0 && body2 < 0 && o1 <= c2 && c1 >= o2) return +1;
   if(body1 < 0 && body2 > 0 && o1 >= c2 && c1 <= o2) return -1;
   return 0;
  }

bool IsDoji(int shift)
  {
   double o = iOpen(_Symbol, TF, shift), c = iClose(_Symbol, TF, shift);
   double h = iHigh(_Symbol, TF, shift), l = iLow(_Symbol, TF, shift);
   double range = h - l;
   if(range <= 0) return false;
   return (MathAbs(c - o) <= DojiBodyPct * range);
  }

中核分析ループ(Analyze)

設定されたルックバック期間を一度直線的に走査する処理が、EAの中核を形成します。銘柄ごとのポイント・ピップ換算は事前に一度計算され、各シフトに対してパターン検出関数が呼び出されます。検出されたパターンはUpdateStatsOnPattern()を通じて集計され、必要に応じてスタックされたマーカーが描画されます。このループを単一パスにすることで、結果の決定論的な再現性とCPU負荷の予測可能性が確保されています。

void Analyze()
  {
   ResetStats();
   int totalBars = iBars(_Symbol, TF);
   if(totalBars < 3) return;
   int maxIndex = MathMin(totalBars - 1, LookbackBars);
   if(maxIndex < 2) return;

   // pip scaling
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   double factor = (digits == 5 || digits == 3) ? 10.0 : 1.0;
   double breakoutPoints = BreakoutPips * point * factor;

   ArrayResize(markerCounts, MathMax(3, maxIndex + 3));
   for(int i=0;i<ArraySize(markerCounts);i++) markerCounts[i]=0;

   if(ShowPatternMarkers) DeletePatternObjects();

   int drawn=0;
   for(int shift=2; shift<=maxIndex; shift++)
     {
      int eng = IsEngulfing(shift);
      int pin = IsPinbar(shift);
      bool doj = IsDoji(shift);
      bool matched = false;

      if(eng != 0 && enabledEngulfing)
        {
         UpdateStatsOnPattern(engulfing, shift, eng, LookAheadBars, breakoutPoints);
         if(ShowPatternMarkers && drawn < MaxMarkers) { DrawPatternOnChartWithStacking(shift, "Engulfing", eng, MarkerFontSize, MarkerOffsetPips, factor); drawn++; }
         matched = true;
        }
      if(!matched && pin != 0 && enabledPinbar)
        {
         UpdateStatsOnPattern(pinbar, shift, pin, LookAheadBars, breakoutPoints);
         if(ShowPatternMarkers && drawn < MaxMarkers) { DrawPatternOnChartWithStacking(shift, "Pinbar", pin, MarkerFontSize, MarkerOffsetPips, factor); drawn++; }
         matched = true;
        }
      if(!matched && doj && enabledDoji)
        {
         UpdateStatsOnPattern(dojiStats, shift, 0, LookAheadBars, breakoutPoints);
         if(ShowPatternMarkers && drawn < MaxMarkers) { DrawPatternOnChartWithStacking(shift, "Doji", 0, MarkerFontSize, MarkerOffsetPips, factor); drawn++; }
        }
     }

   if(ExportCSV) SaveToCSV();
   DrawDashboard();
   DrawSummaryHeaderAndLabel();

   if(ShowControlButtons)
     {
      UpdateButtonLabel("Btn_Pinbar", pinbar, enabledPinbar);
      UpdateButtonLabel("Btn_Engulfing", engulfing, enabledEngulfing);
      UpdateButtonLabel("Btn_Doji", dojiStats, enabledDoji);
     }

   ChartRedraw();
  }

統計の更新(UpdateStatsOnPattern)

パターンが認識されると、包括的な測定処理が開始されます。この処理では、発生カウンタを増加させ、出来高を累積し、直後のバーおよび設定されたルックアヘッド期間内で同方向への動きを確認します。また、BreakoutPipsを用いてパターンの極値付近でのブレイクアウトを検出し、BreakoutConfirmBars経過後の確認も評価します。生のカウントと出来高加重カウンタの両方が保持されるため、結果は単純な頻度ベースでも、市場参加量に基づく評価でも解釈できる設計となっています。

void UpdateStatsOnPattern(PatternStats &s, int shift, int direction, int lookAhead, double breakoutPoints)
  {
   s.total++;
   double vol = (double)iVolume(_Symbol, TF, shift);
   if(vol <= 0) vol = 1.0;
   s.volTotal += vol;

   // next-bar direction
   if(shift - 1 >= 0)
     {
      double nO = iOpen(_Symbol, TF, shift - 1), nC = iClose(_Symbol, TF, shift - 1);
      int nDir = (nC > nO) ? +1 : (nC < nO) ? -1 : 0;
      if(direction == 0)
        {
         if(nDir != 0) { s.nextSameDir++; s.weightedNextSameDir += vol; }
        }
      else
        {
         if(nDir == direction) { s.nextSameDir++; s.weightedNextSameDir += vol; }
        }
     }

   // within lookahead
   bool anySame = false; double weightedAny = 0.0;
   int totalBars = iBars(_Symbol, TF);
   for(int k=1; k<=lookAhead; k++)
     {
      int idx = shift - k;
      if(idx < 1 || idx > totalBars - 1) break;
      double o = iOpen(_Symbol, TF, idx), c = iClose(_Symbol, TF, idx);
      double v = (double)iVolume(_Symbol, TF, idx); if(v <= 0) v = 1.0;
      int d = (c > o) ? +1 : (c < o) ? -1 : 0;
      if(direction == 0)
        {
         if(d != 0) { anySame = true; weightedAny += v; break; }
        }
      else
        {
         if(d == direction) { anySame = true; weightedAny += v; break; }
        }
     }
   if(anySame) { s.withinLookAheadSame++; s.weightedWithin += (weightedAny > 0.0 ? weightedAny : vol); }

   // breakout detection and confirmation
   double highP = iHigh(_Symbol, TF, shift), lowP = iLow(_Symbol, TF, shift);
   bool breakout = false;
   if(shift - 1 >= 0)
     {
      double nextHigh = iHigh(_Symbol, TF, shift - 1), nextLow = iLow(_Symbol, TF, shift - 1);
      if(direction == +1 && nextHigh >= highP + breakoutPoints) breakout = true;
      else if(direction == -1 && nextLow <= lowP - breakoutPoints) breakout = true;
      else if(direction == 0 && (nextHigh >= highP + breakoutPoints || nextLow <= lowP - breakoutPoints)) breakout = true;
     }

   if(breakout)
     {
      s.breakouts++;
      s.weightedBreakouts += vol;
      int confirmShift = shift - 1 - BreakoutConfirmBars;
      if(confirmShift >= 0 && confirmShift <= totalBars - 1)
        {
         double cO = iOpen(_Symbol, TF, confirmShift), cC = iClose(_Symbol, TF, confirmShift);
         double cVol = (double)iVolume(_Symbol, TF, confirmShift); if(cVol <= 0) cVol = 1.0;
         int cDir = (cC > cO) ? +1 : (cC < cO) ? -1 : 0;
         bool confirmed = false;
         if(direction == +1 && cC >= highP + breakoutPoints) confirmed = true;
         else if(direction == -1 && cC <= lowP - breakoutPoints) confirmed = true;
         else if(direction == 0 && (cC >= highP + breakoutPoints || cC <= lowP - breakoutPoints)) confirmed = true;
         if(confirmed && (direction == 0 ? cDir != 0 : cDir == direction))
           {
            s.breakoutSuccess++;
            s.weightedBreakoutSuccess += cVol;
           }
        }
     }
  }

パーセンテージ計算とCSVエクスポート

生のカウントや加重合計を読みやすいパーセンテージに変換する処理は、ComputePct()に集約されています。UseVolumeWeightingが有効な場合は出来高加重パーセンテージが返され、無効の場合は単純な頻度パーセンテージが使用されます。ExportCSVがtrueの場合、SaveToCSV()はヘッダ付きのタブ区切りファイルを出力し、各パターンごとに1行が記録されます。これにより、Python、R、またはスプレッドシートツールでのオフライン分析が簡単かつ再現可能になります。

double ComputePct(double weightedValue, PatternStats &s, int simpleCount, int total)
  {
   if(UseVolumeWeighting)
     {
      if(s.volTotal <= 0.0) return 0.0;
      return 100.0 * weightedValue / s.volTotal;
     }
   else
     {
      if(total <= 0) return 0.0;
      return 100.0 * simpleCount / total;
     }
  }

void SaveToCSV()
  {
   int handle = FileOpen(CSVFileName, FILE_WRITE|FILE_CSV|FILE_ANSI, '\t');
   if(handle == INVALID_HANDLE) { Print("Failed to open CSV: ", CSVFileName); return; }
   FileWrite(handle, "Pattern","Total","Next%","Within%","Breakouts","Success%","VolTotal","W_Next%","W_Within%","W_Breakouts%","W_Success%");
   WriteStatsLine(handle, pinbar);
   WriteStatsLine(handle, engulfing);
   WriteStatsLine(handle, dojiStats);
   FileClose(handle);
  }

void WriteStatsLine(int handle, PatternStats &s)
  {
   double nextPct   = ComputePct(s.weightedNextSameDir, s, s.nextSameDir, s.total);
   double withinPct = ComputePct(s.weightedWithin, s, s.withinLookAheadSame, s.total);
   double successPct = s.breakouts > 0 ? 100.0 * s.breakoutSuccess / s.breakouts : 0.0;
   double wBreakoutsPct = s.volTotal > 0.0 ? 100.0 * s.weightedBreakouts / s.volTotal : 0.0;
   double wSuccessPct   = s.weightedBreakouts > 0.0 ? 100.0 * s.weightedBreakoutSuccess / s.weightedBreakouts : 0.0;
   FileWrite(handle, s.name, s.total, DoubleToString(nextPct,1), DoubleToString(withinPct,1),
             s.breakouts, DoubleToString(successPct,1), DoubleToString(s.volTotal,0),
             DoubleToString(nextPct,1), DoubleToString(withinPct,1), DoubleToString(wBreakoutsPct,1), DoubleToString(wSuccessPct,1));
  }

チャート可視化、マーカー、および積み上げ表示

視覚的な指標はDrawPatternOnChartWithStacking()によって描画されます。この関数は、パターンが発生したバーにコンパクトなテキスト矢印やシンボルを配置し、縦方向に積み上げることでマーカー同士の重なりを回避します。積み上げ処理は、バーごとのmarkerCounts[]配列とMarkerStackWindowパラメータによって階層管理されます。マーカーは色分けされており、意図的に目立たないデザインとなっているため、価格動向を隠すことなく、直感的に状況を把握できるようになっています。

void DrawPatternOnChartWithStacking(int shift, string patternName, int direction, int fontSize, int offsetPips, double factor)
  {
   string name = "Pattern_" + patternName + "_" + IntegerToString(shift);
   if(ObjectFind(0, name) != -1) ObjectDelete(0, name);

   datetime t = iTime(_Symbol, TF, shift);
   double highP = iHigh(_Symbol, TF, shift), lowP = iLow(_Symbol, TF, shift);
   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   double offset = offsetPips * point * factor;
   double priceBase = (highP + lowP) / 2.0;
   if(direction > 0) priceBase = lowP - offset;
   else if(direction < 0) priceBase = highP + offset;

   int idx = shift;
   if(idx >= ArraySize(markerCounts)) ArrayResize(markerCounts, idx + 5);
   int stack = markerCounts[idx]++;
   double stackOffset = (stack / MarkerStackWindow) * offset * 1.2;
   if(direction > 0) priceBase -= stackOffset; else priceBase += stackOffset;

   string arrow = (direction>0 ? "▲" : direction<0 ? "▼" : "◆");
   string text = arrow + " " + StringSubstr(patternName, 0, 3);
   ObjectCreate(0, name, OBJ_TEXT, 0, t, priceBase);
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, 0);
   ObjectSetInteger(0, name, OBJPROP_BACK, 1);
   ObjectSetInteger(0, name, OBJPROP_COLOR, (direction>0 ? clrLime : direction<0 ? clrRed : clrLightBlue));
  }

ダッシュボードと概要ラベル

DrawDashboard()によって生成されるチャート上のコンパクトなダッシュボードには、Pattern、Total、Next%、Within%、Breakouts、Success%といった標準的な列が表示されます。また、DrawSummaryHeaderAndLabel()は左上にサマリーを作成し、迅速な状況確認を可能にします。オブジェクト名の接頭辞を一貫して使用することで、不要になった際にEAがこれらのUI要素を確実にクリーンアップできる設計になっています。

void DrawDashboard()
  {
   string rectName = "Dash_Background";
   if(ObjectFind(0, rectName) != -1) ObjectDelete(0, rectName);
   ObjectCreate(0, rectName, OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(0, rectName, OBJPROP_CORNER, 0);
   ObjectSetInteger(0, rectName, OBJPROP_XDISTANCE, 8);
   ObjectSetInteger(0, rectName, OBJPROP_YDISTANCE, 70);
   ObjectSetInteger(0, rectName, OBJPROP_XSIZE, 660);
   ObjectSetInteger(0, rectName, OBJPROP_YSIZE, 80);
   ObjectSetInteger(0, rectName, OBJPROP_BACK, 1);
   ObjectSetInteger(0, rectName, OBJPROP_COLOR, clrDarkSlateGray);

   // header and rows omitted for brevity, see DrawDashboard in main code for full cell logic
  }

void DrawSummaryHeaderAndLabel()
  {
   string header = "SummaryHeader";
   if(ObjectFind(0, header) != -1) ObjectDelete(0, header);
   ObjectCreate(0, header, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, header, OBJPROP_CORNER, 0);
   ObjectSetInteger(0, header, OBJPROP_XDISTANCE, 10);
   ObjectSetInteger(0, header, OBJPROP_YDISTANCE, 12);
   ObjectSetInteger(0, header, OBJPROP_FONTSIZE, 11);
   ObjectSetString(0, header, OBJPROP_TEXT, "Candlestick Pattern Stats Summary");
   ObjectSetInteger(0, header, OBJPROP_COLOR, clrBlue);

   string sum = "SummaryLabel";
   if(ObjectFind(0, sum) != -1) ObjectDelete(0, sum);
   ObjectCreate(0, sum, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, sum, OBJPROP_CORNER, 0);
   ObjectSetInteger(0, sum, OBJPROP_XDISTANCE, 10);
   ObjectSetInteger(0, sum, OBJPROP_YDISTANCE, 30);
   ObjectSetInteger(0, sum, OBJPROP_FONTSIZE, 9);
   ObjectSetInteger(0, sum, OBJPROP_BACK, 1);
   ObjectSetString(0, sum, OBJPROP_TEXT, FormatStatsForLabel(pinbar) + "\n" + FormatStatsForLabel(engulfing) + "\n" + FormatStatsForLabel(dojiStats));
  }

制御ボタンと動的ラベル

自動サイズ調整された制御ボタンはCreateAutoSizedButton()で作成され、UpdateButtonLabel()によってリアルタイムで更新されます。各ボタンには、短い識別子、ON/OFF状態、現在のNext%およびSucc%の指標が表示されます。EstimateButtonWidth()によりラベルの切り捨てが防止され、ボタンの色は有効/無効状態を反映して変化します。これにより、ユーザーはどのパターンを統計に反映させるかを対話的に調整できます。

void CreateAutoSizedButton(string name, int x, int y, string text)
  {
   if(ObjectFind(0, name) != -1) ObjectDelete(0, name);
   ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0);
   ObjectSetInteger(0, name, OBJPROP_CORNER, 1);
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
   ObjectSetString(0, name, OBJPROP_FONT, ButtonFont);
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, ButtonFontSize);
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   int w = EstimateButtonWidth(text, ButtonFontSize);
   int h = ButtonFontSize + ButtonPaddingY * 2;
   ObjectSetInteger(0, name, OBJPROP_XSIZE, w);
   ObjectSetInteger(0, name, OBJPROP_YSIZE, h);
   ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clrWhite);
  }

void UpdateButtonLabel(string name, PatternStats &s, bool enabled)
  {
   if(ObjectFind(0, name) == -1) return;
   string onoff = enabled ? "ON" : "OFF";
   double nextPct = ComputePct(s.weightedNextSameDir, s, s.nextSameDir, s.total);
   double successPct = s.breakouts > 0 ? 100.0 * s.breakoutSuccess / s.breakouts : 0.0;
   string shortName = (StringCompare(s.name, "Pinbar") == 0) ? "Pin" : (StringCompare(s.name,"Engulfing")==0) ? "Eng" : "Doj";
   string txt = shortName + ": " + onoff + "  Next% " + DoubleToString(nextPct,1) + "  Succ% " + DoubleToString(successPct,1);
   ObjectSetString(0, name, OBJPROP_TEXT, txt);
   ObjectSetInteger(0, name, OBJPROP_XSIZE, EstimateButtonWidth(txt, ButtonFontSize));
   if(enabled) { ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clrWhite); ObjectSetInteger(0, name, OBJPROP_COLOR, clrBlack); }
   else { ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clrLightGray); ObjectSetInteger(0, name, OBJPROP_COLOR, clrDimGray); }
  }

int EstimateButtonWidth(const string txt, const int fontSize)
  {
   int len = StringLen(txt);
   double charW = fontSize * 0.75;
   int base = (int)MathCeil(len * charW) + ButtonPaddingX * 2 + 10;
   if(base < 100) base = 100;
   return base;
  }

管理処理およびオブジェクト管理

EAが作成するすべてのチャートオブジェクトには、OBJ_PREFIX_PATTERN、OBJ_PREFIX_DASH、OBJ_PREFIX_BTNなどの決定論的な接頭辞が付与されます。対応する削除関数は、これらの接頭辞に一致するオブジェクトのみを削除するため、ユーザーが作成した無関係なオブジェクトを誤って削除するリスクを最小化できます。

void DeleteAllOurObjects()
  {
   int total = ObjectsTotal(0);
   for(int i = total - 1; i >= 0; i--)
     {
      string nm = ObjectName(0, i);
      if(StringFind(nm, "Pattern_") == 0 || StringFind(nm, "Dash_") == 0 || StringFind(nm, "Btn_") == 0
         || StringCompare(nm, "SummaryLabel") == 0 || StringCompare(nm, "SummaryHeader") == 0)
         ObjectDelete(0, nm);
     }
  }


テスト

このセクションでは、EAをStep IndexおよびBoom 300 Indexで評価します。これらの銘柄は、ボラティリティ特性が異なる合成指数を用いることで、ツールの堅牢性を検証する目的で選定されています。

  • Step Index

上図は、Step Indexにおけるツールのパフォーマンスを示しています。EAをチャートに適用すると、左上にコンパクトなダッシュボードが作成され、パターン統計が集約表示されます。また、左下にはチャート上の可視化を制御するトグルボタン群が配置されます。ボタンをクリックすると、対応するパターンストリームを無効化または有効化でき、ローソク足ラベルが非表示または再表示されるため、チャートを順に確認しながらパターン識別を視覚的に検証できます。トグル操作は、特定のパターンタイプに集中して分析したい場合に便利です。

例として示された画面では、ダッシュボードはピンバーの成功率を40.2%、包み足を31.7%と報告しています。十字線は65.1%ですが、この分析では十字線は中立シグナルとして扱います。十字線の表面的な強さは主にブレイクアウトフィルタリングによるもので、方向性のバイアスに基づくものではないためです。したがって、十字線の結果は単純な強気や弱気のシグナルとしてではなく、この銘柄におけるトレンド継続やブレイクアウトの解消傾向を示すものとして解釈するのが適切です。

また、結果は独立した検証のためにCSV形式で出力することも可能です。CSVファイルを生成するには、EAの入力パラメータを開き、ExportCSV = trueに設定します。サマリーファイル名はCSVFileNameで変更できます。EAは概要CSVをターミナルのFilesフォルダに書き込み、オフラインでの分析に利用可能です。

input bool   ExportCSV          = true;
input string CSVFileName        = "PatternStats.csv";

  • Boom 300 Index

Boom 300 Indexにおける測定結果では、包み足の成功率が40.0%、ピンバーが42.1%、十字線が78.6%となっています。これらの数値は、この銘柄における十字線の発生が、EAのルール下でトレンド継続やブレイクアウト型の動きに従いやすいことを示しています。一方、包み足とピンバーは方向性の性能が比較的控えめであることがわかります。

Step IndexとBoom 300を比較すると、ピンバーのパフォーマンスは両銘柄でほぼ同等であり、このパターンの一定の堅牢性が示唆されます。しかし、包み足はBoom 300でStep Indexよりも明らかに良好な結果を示しており、EAのパラメータ設定およびサンプル期間内で、Boom 300において包み足シグナルはより高いフォロー率をもたらすことを意味しています。


結論

本記事では、複数の銘柄におけるローソク足パターンの発生確率を測定するMQL5 EAの構築手順を紹介しました。ピンバー、包み足、十字線の形成を検出し、パターンごとの統計を収集し、成功率を報告することで、各通貨ペアでどのパターンがポジティブなフォローにつながりやすいかを評価できることを示しました。本EAは、プライスアクションパターンとその実証的確率を可視化する実験的分析ツールとして提示しており、完成済みの自動売買ロボットとしてではなく、研究や検証の補助として使用することを想定しています。ユーザーは自身の時間軸や銘柄に合わせて、入力パラメータや検証手順を適用する必要があります。

十分なサンプルで堅牢性が確認された場合、このアプローチは、特定のパターンがライブ取引で典型的にどのように振る舞うかに対する現実的な期待値を設定するのに役立ちます。また、CSV監査ファイルを用いた再現可能なテストもサポートします。今後のエピソードでは本ツールの機能拡張を予定しており、改善提案や追加分析機能のリクエストも歓迎します。

他の記事もぜひご覧ください。

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

添付されたファイル |
MQL5でのAI搭載取引システムの構築(第3回):スクロール対応の単一スレッド型チャットUIへのアップグレード MQL5でのAI搭載取引システムの構築(第3回):スクロール対応の単一スレッド型チャットUIへのアップグレード
本記事では、MQL5で構築したChatGPT統合プログラムを、タイムスタンプ付きの会話履歴管理と動的スクロール機構を備えた、単一スレッド型チャット指向のUIへとアップグレードします。本システムはJSON解析を用いてマルチターンのメッセージを管理し、スクロールバー表示モードの切り替えやホバーエフェクトをサポートすることで、実装面と操作性の両面からユーザー体験を向上させます。
初心者からエキスパートへ:MQL5を使用したバックエンド操作モニター 初心者からエキスパートへ:MQL5を使用したバックエンド操作モニター
取引システムの内部動作を意識せずに、既製のソリューションをそのまま利用することは一見すると安心に思えますが、開発者にとっては必ずしもそうとは限りません。いずれアップデートや動作不良、あるいは予期しないエラーが発生し、その原因がどこにあるのかを正確に突き止め、迅速に診断して解決する必要に迫られます。本記事では、取引用エキスパートアドバイザー(EA)の裏側で通常どのような操作がおこなわれているのかを明らかにするとともに、MQL5を用いてバックエンド操作を表示し、記録するための専用カスタムクラスを開発します。これにより、開発者およびトレーダーの双方が、エラーの特定、挙動の監視、EAごとの診断情報に迅速にアクセスできるようになります。
知っておくべきMQL5ウィザードのテクニック(第81回): β-VAE推論学習で一目均衡表とADX-Wilderのパターンを利用する 知っておくべきMQL5ウィザードのテクニック(第81回): β-VAE推論学習で一目均衡表とADX-Wilderのパターンを利用する
本記事は第80回の続編です。前回は、強化学習フレームワーク下で一目均衡表とADXの組み合わせを検証しました。今回は焦点を推論学習に移します。一目均衡表とADXは前回も述べた通り補完的な指標ですが、今回は前回の記事で触れたパイプライン使用に関する結論を再検討します。推論学習には、変分オートエンコーダのβアルゴリズムを用います。また、MQL5ウィザードとの統合を目的として設計されたカスタムシグナルクラスの実装も継続します。
MQL5での取引戦略の自動化(第35回):ブレーカーブロック取引システムの作成 MQL5での取引戦略の自動化(第35回):ブレーカーブロック取引システムの作成
本記事では、MQL5でブレーカーブロック取引システムを作成します。本システムは、レンジ相場を識別し、ブレイクアウトを検出、スイングポイントでブレーカーブロックを検証した上で、リスクパラメータを定義してリテスト取引を実行します。また、オーダーブロックおよびブレーカーブロックを動的なラベルと矢印で可視化し、自動売買やトレーリングストップにも対応しています。