プライスアクション分析ツールキットの開発(第39回):MQL5でBOSとChoCHの検出を自動化する
はじめに
連載記事「プライスアクション分析ツールキットの開発」第39回にようこそ。今回は、プライスアクショントレーダーがチャートをただ眺めて構造変化を見逃してしまうのではなく、構造の変化を明確に読み取れるようにする実用的なMQL5システムを構築します。目標はただ1つ、フラクタルピボットと構造ルールを、実運用と過去検証のどちらでも信頼できる非リペイント型シグナルに変換することです。
フラクタルピボットを局所的で信頼できるアンカーとして利用し、2種類の補完的なシグナルを検出します。1つ目はChoCH (Change of Character)で、上昇トレンドが高値更新に失敗したり、下降トレンドが安値更新に失敗したりすることで、相場がこれまでのバイアスを失いつつあることを示す早期警告です。2つ目は BOS (Break of Structure)で、価格が前のスイングハイまたはスイングローを終値で明確に突破したときにバイアス転換を確定させるシグナルです。ChoCHが前兆、BOSが確定と捉えると理解しやすいでしょう。
フラクタルとChoCH/BOSを組み合わせることで、分析のための明確で非リペイント型のアンカー、反転のより早い警告、そして多時間足での構造把握の精度向上(特に下位足のノイズ除去)が得られます。これらのルールは自動化、ログ記録、バックテストが容易で、EA化にも非常に適しています。
本記事では、アルゴリズム設計からMQL5での完全な実装まで順を追って解説します。確定バーを用いたフラクタルスキャン、メモリ安全なフラクタル保存、永続オブジェクトの安全な描画、そして確定したBOS/ChoCHをログや通知(デスクトップ、モバイル、サウンド)するイベント処理などを扱います。読み終える頃には、実運用レベルの検出システムをコンパイルし、テストし、配備できる状態になっているはずです。
まず戦略ロジックを説明し、その後 MQL5 による実装、アラート、ログと通知設定、テスト手順と結果、最後にまとめへと進みます。以下に目次を示します。
戦略ロジック
このセクションでは、本システムの中核となる戦略ロジックを順に説明します。本手法は、フラクタル指標と、そこから導出する構造信号に基づいています。以前の記事ではフラクタルを用いたブレイクアウト型のトレンド戦略を解説しましたが、フラクタルは応用範囲が広く、単なるブレイクアウトにとどまらず、価格構造分析に必要な信頼性の高い支点として機能します。本記事ではその特性を用いて、ChoCH (Change of Character)とBOS (Break of Structure)を検出します。
フラクタルピボットとは、中央の足の高値(または安値)が、左右対称に配置された一定数の足よりも高い(または低い)ときに形成される局所的な反転点を指します。実務上は、窓幅を「g_length = 2*p + 1」としたとき、中央の足の高値がその窓内の全高値以上であれば高値フラクタル、安値が窓内の全安値以下であれば安値フラクタルとなります。フラクタルが一貫した非リペイント型のアンカーとなる理由は、検出に窓全体の確定バーが必要だからです。
Break of Structure (BOS)は、直近の市場構造に対する明確な突破を意味し、過去のスイングハイを終値で上抜いた場合は強気BOS、スイングローを終値で下抜いた場合は弱気BOSとなります。BOSは、市場の勢いおよび短中期のバイアスが突破方向へ転換したことを示す確定信号であり、トレーダーが方向性にコミットする際の根拠となります。
Change of Character (ChoCH)は、より早い段階で市場のバイアス変化を示す予兆的信号です。典型的には、上昇相場で高値更新に失敗した場合や、下降相場で安値更新に失敗した場合などが該当します。ChoCHは警告と捉えるべきで、その後の値動きに確信が伴えばBOSにつながることが多く、逆張り準備や保有ポジションの調整に役立ちます。
重要なのは、リペイントを防ぐため、すべての判定を確定バーのみでおこなうことです。具体的には、保存されたフラクタル価格に対し、prevCloseやcurCloseといった確定終値を用いて比較し、未確定の形成中バーを対象にBOSやChoCHを宣言することは決してありません。確定バーに基づく判定により、シグナルは再現性があり、バックテストでも一貫した結果を得られます。
アルゴリズムは意図的に簡潔かつ決定的です。確定バーから信頼できるフラクタル支点を検出し、終値がその支点を明確に越えたときのみ判定し、一度確定した突破には一回だけ印を付けて描画し、一件のログと通知だけを生成します。この確定バー方式により、リペイントの発生を避け、再現性と検証性を備えたシグナルを生成できます。- 高レベルの流れ

- 新しいバーが確定するのを待ち、そのバーの時刻を処理の起点とする。
- 「g_length = 2*p + 1」の左右対称の窓の中央にフラクタルが形成されているか判定する。
- 検出した場合は、そのフラクタル(時刻と価格)を保存し、「marked = false」とする。
- 以後、新しいバーが確定するごとに、prevCloseとcurCloseを保存済みフラクタルの価格と比較し、終値による突破を判定する。
- 突破が発生した場合(確定バーでの突破)、該当フラクタルに印を付け、水平線またはトレンドラインと固定ラベルを描画し、ログと通知を一回だけ発行する。
- 配列が無制限に肥大化しないよう、古いフラクタルを定期的に削除する。
コア擬似コード(簡潔版)
if new_closed_bar(): ScanForFractals() // detect & append new fractal anchors PruneFractals(maxKeepBars) // remove very old anchors for each fractal in stored_fractals: if not fractal.marked and crossed(prevClose, curClose, fractal.price): DrawBreak(fractal) LabelAndLog(fractal) fractal.marked = true
MQL5での実装
ヘッダとメタデータ
ファイル冒頭では、ファイル名、作成者、著作権、参考リンクなどの基本情報を簡潔なコメントとして記述します。これらの情報は、バージョン管理や、後日ファイルを共有したり参照したりする際に重要な手掛かりとなります。続いて記述される#propertyディレクティブは、コンパイル動作を制御する設定です。特に「#property strict」は厳格な型検査とAPI検証を適用し、開発初期の段階で微細な不具合を検出しやすくする効果があります。#include <stdlib.mqh>ディレクティブは一般的な補助処理を集めた標準ヘルパー群を取り込み、主要コードの記述量を削減し、保守性を高める役割を果たします。読者の環境にこのライブラリが存在しない場合は、自身で用意するか、この行を削除してコンパイルエラーを回避する必要があります。
//+------------------------------------------------------------------+ //| Fractal Reaction System.mq5| //| Copyright 2025, Christian Benjamin.| //| https://www.mql5.com/ja/users/lynnchris| //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Christian Benjamin." #property link "https://www.mql5.com/ja/users/lynnchris" #property version "1.0" #property strict #include <stdlib.mqh>
ユーザー入力パラメータ
入力ブロックでは、再コンパイルをおこなわずにEAの動作を調整できる各種パラメータを公開します。AutoDetectLengthは、チャートの時間軸に応じて適切なフラクタル長を自動選択する機能を提供し、LengthInputは手動で値を指定したい場合に使用します。なお、フラクタル検出には奇数の窓幅(例:3、5、7)が必要で、中央に単一の足を置き、その両側に対称の足が並ぶ構造を確保します。ShowBullやShowBearといった表示設定、およびBullColorやBearColorといった配色設定は、視認性向上と判読速度の向上に寄与し、複数チャートを監視する際にも役立ちます。
HorizontalRightBarsとHorizontalLeftExtendは、描画される水準線や傾斜線をどの程度右側および左側へ延長するかを決定し、その水準の有効性を視覚的に把握しやすくします。DebugModeは、開発や検証時に診断用の詳細ログを有効化するための設定です。さらに、MaxFractalHistoryBarsは保持する過去フラクタル数の上限を定め、長時間稼働時のメモリ肥大化を防止します。
// User-configurable inputs input bool AutoDetectLength = false; input int LengthInput = 5; input bool ShowBull = true; input color BullColor = clrLime; input bool ShowBear = true; input color BearColor = clrRed; input int HorizontalRightBars = 0; input int HorizontalLeftExtend = 3; input bool DebugMode = false; input int MaxFractalHistoryBars = 2000;
グローバル変数とデータ構造体
グローバル変数は、実行時の状態管理と永続的な内部保存を担います。g_chart_idは、描画対象のチャート識別番号を保持し、すべての描画物が必ず正しいチャートに紐付くようにします。g_lengthとp_halfは、それぞれフラクタルの窓幅とその半幅を表し、初期化時に一度だけ計算し、その後は使い回します。ea_digits、ea_point、ea_point_pipsは、ブローカーや銘柄ごとの価格精度差を吸収し、価格オフセットやラベル配置を一貫させるための正規化情報です。本システムは、強気フラクタルと弱気フラクタルを並列配列(*_time[]、*_price[]、*_marked[])として保持します。時刻情報と価格が各フラクタルを識別し、markedは同一フラクタルを二重処理しないための印です。最後に、os_stateは市場の構造バイアスを保持する変数で、0が中立、1が強気、-1が弱気を表します。
// Internal globals long g_chart_id; int g_length; int p_half; int ea_digits; double ea_point, ea_point_pips; datetime bull_time[]; double bull_price[]; bool bull_marked[]; datetime bear_time[]; double bear_price[]; bool bear_marked[]; int os_state = 0; // 0: none, 1: bullish, -1: bearish
初期化(OnInit)
OnInit ()は初期化処理と入力値の検証を担当します。処理の中では、まずチャート識別番号を取得し、フラクタル長を自動判定または利用者指定のいずれかに基づいて決定します。さらに、最小値を下回らないように補正し、必ず奇数となるよう調整します。続いてp_halfを計算し、銘柄の価格精度情報を読み込み、正確な描画をおこなうためのポイント値とピップ値を導出します。フラクタル関連配列は、初期状態として空にリセットされます。DebugModeが有効な場合は、主要な初期化値を含む開始メッセージを出力し、実行開始前に設定内容が正しく反映されていることを確認しやすくします。
int OnInit() { g_chart_id = ChartID(); // Determine fractal length if(AutoDetectLength) { if(_Period <= PERIOD_H1) g_length = 5; else if(_Period <= PERIOD_H4) g_length = 7; else if(_Period <= PERIOD_D1) g_length = 9; else g_length = 11; } else { g_length = LengthInput; } // Ensure odd length >=3 if(g_length < 3) g_length = 5; if((g_length % 2) == 0) g_length++; p_half = g_length / 2; // Get symbol info ea_digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); ea_point = Point(); ea_point_pips = ea_point; if(ea_digits == 3 || ea_digits == 5) ea_point_pips = Point() * 10.0; // Clear fractal arrays ArrayResize(bull_time,0); ArrayResize(bull_price,0); ArrayResize(bull_marked,0); ArrayResize(bear_time,0); ArrayResize(bear_price,0); ArrayResize(bear_marked,0); if(DebugMode) PrintFormat("EA INIT: AutoDetect=%s LengthInput=%d g_length=%d p_half=%d chart=%d", AutoDetectLength ? "true" : "false", LengthInput, g_length, p_half, g_chart_id); return(INIT_SUCCEEDED); }
クリーンアップ(OnDeinit)
OnDeinit ()は、EAがチャートから削除される際の後処理を担当します。この関数では、CleanupObjectsByPrefix()を呼び出し、一定の接頭辞で識別されるEA作成物(水準線、傾斜線、ラベルなど)を一括削除します。この一貫した後片付け処理により、不要物がチャート上に残留して視認性を損なったり、後続の分析や別ツールの使用を妨げたりする事態を防ぎます。これは、実運用やデモ環境における品質確保のためにも重要な配慮です。
void OnDeinit(const int reason) { CleanupObjectsByPrefix("CHB_"); }
メインループ(OnTick)
OnTick ()は、決定的な動作を得るために「確定バーごとに一度だけ実行する」方式を採用しています。関数は、直近の確定バーの時刻を確認し、前回実行時から時刻が変化していない場合は処理をおこなわず即座に終了します。新しい確定バーが検出された場合、OnTick()は3つの主要処理を呼び出します。第一に、ScanForFractals()を実行して、新たに確定したフラクタルを検出します。第二に、PruneFractalsを実行して、設定された履歴保持数を超える古いフラクタルを削除します。第三に、ProcessFractalCrossesを実行し、価格が保存済みフラクタル水準を終値で越えたかどうかを評価します。この処理順序により効率性が保たれ、すべての検出と判定を確定バーに基づいて実施できます。これは、バックテストの再現性と、実運用における信頼性のどちらにおいても極めて重要です。
void OnTick() { static datetime last_checked = 0; datetime t = iTime(_Symbol, _Period, 1); if(t == last_checked) return; last_checked = t; ScanForFractals(); PruneFractals(MaxFractalHistoryBars); ProcessFractalCrosses(); }
フラクタル検出(ScanForFractals)
ScanForFractals ()は、まず必要な履歴データが十分に存在するかを確認し、そのうえでフラクタル窓の中央に位置するp_halfシフトの足を検査します。この中央足がフラクタル判定の対象となります。関数内部ではIsFractalHighAtShift ()およびIsFractalLowAtShift ()を呼び出し、中央足が高値フラクタルまたは安値フラクタルの条件を満たすかどうかを判定します。どちらのヘルパー関数も、中央足とその左右対称の近傍足との間で厳密な比較をおこないます。有効なフラクタルが検出され、さらにその時刻が既存記録と重複していない場合(重複防止はタイムスタンプでおこないます)、該当する配列に時刻と価格を新規追加し、後続の水準突破判定に参加できるよう「未処理(未マーク)」状態として保存します。
void ScanForFractals() { int bars = iBars(_Symbol, _Period); if(bars <= g_length) return; int centerShift = p_half; if(centerShift >= bars) return; // High fractal detection if(IsFractalHighAtShift(centerShift)) { datetime t_fr = (datetime)iTime(_Symbol, _Period, centerShift); double p_fr = iHigh(_Symbol, _Period, centerShift); // Store if new bool exists = false; for(int i=0;i<ArraySize(bull_time);i++) if(bull_time[i]==t_fr) exists = true; if(!exists) { int n = ArraySize(bull_time); ArrayResize(bull_time, n+1); ArrayResize(bull_price, n+1); ArrayResize(bull_marked, n+1); bull_time[n] = t_fr; bull_price[n] = p_fr; bull_marked[n] = false; if(DebugMode) PrintFormat("FRAC_BULL DETECTED: t=%s price=%G", TimeToString(t_fr, TIME_DATE|TIME_SECONDS), p_fr); } } // Low fractal detection if(IsFractalLowAtShift(centerShift)) { datetime t_fr = (datetime)iTime(_Symbol, _Period, centerShift); double p_fr = iLow(_Symbol, _Period, centerShift); // Store if new bool exists = false; for(int i=0;i<ArraySize(bear_time);i++) if(bear_time[i]==t_fr) exists = true; if(!exists) { int n = ArraySize(bear_time); ArrayResize(bear_time, n+1); ArrayResize(bear_price, n+1); ArrayResize(bear_marked, n+1); bear_time[n] = t_fr; bear_price[n] = p_fr; bear_marked[n] = false; if(DebugMode) PrintFormat("FRAC_BEAR DETECTED: t=%s price=%G", TimeToString(t_fr, TIME_DATE|TIME_SECONDS), p_fr); } } }
フラクタル検証ヘルパー
IsFractalHighAtShift ()とIsFractalLowAtShift ()は、フラクタル定義に従い、中央足を中心とした左右対称の窓内を順に確認します。近傍足のいずれかが中央足の優位性を損なう場合、あるいは窓全体のデータが揃っていない場合は、関数はfalseを返します。この厳密な検証により、早期の誤判定や偽フラクタルの生成を防ぎ、履歴データの先頭や時間足変更後のインデックスエラーを回避できます。大量データを扱う場合は、CopyHigh/CopyLowを使って過去の価格系列を一括取得することで、バーごとのAPI呼び出しを削減し、処理効率を向上させることが推奨されます。
bool IsFractalHighAtShift(int shift) { int bars = iBars(_Symbol,_Period); int p = p_half; if(shift < 0 || shift >= bars) return false; double center = iHigh(_Symbol,_Period,shift); for(int k=-p; k<=p; k++) { if(k == 0) continue; int s = shift + k; if(s < 0 || s >= bars) return false; // incomplete window if(iHigh(_Symbol,_Period,s) > center) return false; } return true; }
ヘルパー:低フラクタルをチェック
bool IsFractalLowAtShift(int shift) { int bars = iBars(_Symbol,_Period); int p = p_half; if(shift < 0 || shift >= bars) return false; double center = iLow(_Symbol,_Period,shift); for(int k=-p; k<=p; k++) { if(k == 0) continue; int s = shift + k; if(s < 0 || s >= bars) return false; if(iLow(_Symbol,_Period,s) < center) return false; } return true; }
クロス処理(ProcessFractalCrosses)
ProcessFractalCrosses ()は、保存されたフラクタル水準を実際のエントリー信号に変換する役割を果たします。関数は、直近の確定済み終値がいずれかのフラクタル価格を越えたかどうかを判定します。処理は保守的な確定バー方式でおこなわれます。具体的には、前回確定バーの終値prevCloseと直近確定バーの終値curCloseを比較し、CrossedOver()またはCrossedUnder()を用いてクロス判定をおこないます。未処理のフラクタルでクロスが検出された場合、EAは一意のオブジェクトタグを付与し、市場構造状態os_stateに基づいてBOS (Break of Structure)またはChoCH (Change of Character)として分類します。必要に応じてブレイクラインやラベルを描画し、os_stateを更新するとともに、該当フラクタルを処理済みとしてマークし、アラートを発行します。このフラクタルごとの一度きりの処理により、動作が決定的となり、バックテストにも適した構造となっています。
void ProcessFractalCrosses() { double prevClose = iClose(_Symbol, _Period, 2); double curClose = iClose(_Symbol, _Period, 1); datetime curTime = (datetime)iTime(_Symbol, _Period, 1); // Process bullish fractals for(int i=0; i<ArraySize(bull_time); i++) { if(bull_marked[i]) continue; double level = bull_price[i]; if(CrossedOver(prevClose, curClose, level)) { datetime fr_time = bull_time[i]; string tag = "CHB_BULL_" + IntegerToString((int)fr_time); bool isChoCH = (os_state == -1); string niceName = isChoCH ? "Bull ChoCH" : "Bull BOS"; if(ShowBull) { DrawBreak(tag, fr_time, level, curTime, true); CreateAnchoredLabel(tag + "_lbl", niceName, fr_time, level + 3*ea_point, BullColor); } os_state = 1; bull_marked[i] = true; string msg = StringFormat("%s detected: %s %s at %s price=%s", niceName, _Symbol, TimeframeToString(_Period), TimeToString(curTime, TIME_DATE|TIME_MINUTES), DoubleToString(level, ea_digits)); EmitLogAlert(msg); } } // Process bearish fractals for(int i=0; i<ArraySize(bear_time); i++) { if(bear_marked[i]) continue; double level = bear_price[i]; if(CrossedUnder(prevClose, curClose, level)) { datetime fr_time = bear_time[i]; string tag = "CHB_BEAR_" + IntegerToString((int)fr_time); bool isChoCH = (os_state == 1); string niceName = isChoCH ? "Bear ChoCH" : "Bear BOS"; if(ShowBear) { DrawBreak(tag, fr_time, level, curTime, false); CreateAnchoredLabel(tag + "_lbl", niceName, fr_time, level - 3*ea_point, BearColor); } os_state = -1; bear_marked[i] = true; string msg = StringFormat("%s detected: %s %s at %s price=%s", niceName, _Symbol, TimeframeToString(_Period), TimeToString(curTime, TIME_DATE|TIME_MINUTES), DoubleToString(level, ea_digits)); EmitLogAlert(msg); } } }
アラート(EmitLogAlert)
EmitLogAlert ()は、通知処理を一元化する関数です。常にメッセージをエキスパートログに出力し、監査や後日の確認に役立てます。必要に応じて、ポップアップ通知を表示したり、MetaTraderモバイルクライアントにプッシュ通知を送信したり、サウンドファイルを再生することも可能です。このマルチチャネル通知により、トレーダーは作業中の環境に関わらず、重要なイベントをタイムリーに把握できます。プッシュ通知を利用する場合は、あらかじめターミナルにMetaQuotes IDが設定されていることを確認してください。
void EmitLogAlert(const string msg) { Print(msg); if(EnableAlerts) Alert(msg); if(EnableNotifications) SendNotification(msg); if(EnableSound && StringLen(AlertSoundFile) > 0) PlaySound(AlertSoundFile); }
可視化関数
DrawBreak ()、CreateTrendLine ()、およびCreateAnchoredLabel ()は、チャート描画を管理する関数です。DrawBreak()は、フラクタルおよびブレイク発生時刻に対応するバーインデックスをiBarShift()で算出し、古い側をHorizontalLeftExtend分だけ延長し、必要に応じて新しい側をHorizontalRightBarsに従って現在方向へシフトします。CreateTrendLine()は、指定した2つの時刻を結ぶアンカリング付きトレンドラインを作成し、視覚的スタイルを適用します。CreateAnchoredLabel ()は、指定した時刻と価格に説明テキストを配置し、判読性を確保するために若干のオフセットを与えます。いずれの作成処理も、SafeDelete()を用いて同名の競合オブジェクトを事前に削除してから新規作成をおこないます。これにより、チャートが散らかることなく、一貫性のある表示が維持されます。
void DrawBreak(const string tag, datetime fract_time, double fract_price, datetime break_time, bool bullish) { int barFr = iBarShift(_Symbol, _Period, fract_time, false); int barBreak = iBarShift(_Symbol, _Period, break_time, false); int bars = iBars(_Symbol, _Period); if(barFr == -1 || barBreak == -1) return; int older_shift = MathMax(barFr, barBreak); int newer_shift = MathMin(barFr, barBreak); // Extend left older_shift = MathMin(older_shift + HorizontalLeftExtend, bars - 1); // Extend right towards current bar if(HorizontalRightBars > 0) newer_shift = MathMax(newer_shift - HorizontalRightBars, 0); // Swap if necessary if(older_shift < newer_shift) { int tmp = older_shift; older_shift = newer_shift; newer_shift = tmp; } datetime tLeft = (datetime)iTime(_Symbol, _Period, older_shift); datetime tRight = (datetime)iTime(_Symbol, _Period, newer_shift); string lineName = tag + "_line"; CreateTrendLine(lineName, tLeft, fract_price, tRight, (bullish ? BullColor : BearColor), false); } void CreateAnchoredLabel(const string name, const string txt, datetime when, double price, color col) { SafeDelete(name); if(ObjectCreate(g_chart_id, name, OBJ_TEXT, 0, when, price)) { ObjectSetString(g_chart_id, name, OBJPROP_TEXT, txt); ObjectSetInteger(g_chart_id, name, OBJPROP_COLOR, (int)col); ObjectSetInteger(g_chart_id, name, OBJPROP_FONTSIZE, 10); ObjectSetInteger(g_chart_id, name, OBJPROP_BACK, false); ObjectMove(g_chart_id, name, 0, when, price); } } void CreateTrendLine(const string name, datetime tLeft, double price, datetime tRight, color col, bool dashed=false) { SafeDelete(name); if(ObjectCreate(g_chart_id, name, OBJ_TREND, 0, tLeft, price, tRight, price)) { ObjectSetInteger(g_chart_id, name, OBJPROP_COLOR, (int)col); ObjectSetInteger(g_chart_id, name, OBJPROP_WIDTH, 2); ObjectSetInteger(g_chart_id, name, OBJPROP_STYLE, dashed ? STYLE_DASH : STYLE_SOLID); ObjectSetInteger(g_chart_id, name, OBJPROP_BACK, false); ObjectSetInteger(g_chart_id, name, OBJPROP_SELECTABLE, false); } } void SafeDelete(const string name) { if(ObjectFind(g_chart_id, name) >= 0) ObjectDelete(g_chart_id, name); }
ユーティリティヘルパー
CrossedOver ()およびCrossedUnder ()は、確定バーにおけるクロス判定条件をカプセル化した関数です。CrossedOver()はprevClose <= level && curClose > levelの条件を判定し、CrossedUnder()はその逆の条件を判定します。
bool CrossedOver(double prevClose, double curClose, double level) { return (prevClose <= level && curClose > level); } bool CrossedUnder(double prevClose, double curClose, double level) { return (prevClose >= level && curClose < level); }
TimeframeToString ()は、時間足定数を人間に読みやすい文字列に変換する関数です。
string TimeframeToString(int period) { switch(period) { case PERIOD_M1: return "M1"; case PERIOD_M5: return "M5"; case PERIOD_M15: return "M15"; case PERIOD_M30: return "M30"; case PERIOD_H1: return "H1"; case PERIOD_H4: return "H4"; case PERIOD_D1: return "D1"; case PERIOD_W1: return "W1"; case PERIOD_MN1: return "MN1"; default: return IntegerToString(period); } }
CleanupObjectsByPrefix ()は、指定した接頭辞を共有するすべてのオブジェクトを削除する関数です。削除時にはオブジェクトリストを逆順に走査することで、インデックスのずれによるエラーを防ぎます。
void CleanupObjectsByPrefix(const string prefix) { long total = ObjectsTotal(g_chart_id); for(int i=total-1; i>=0; i--) { string name = ObjectName(g_chart_id, i); if(StringLen(name) >= StringLen(prefix) && StringSubstr(name, 0, StringLen(prefix)) == prefix) ObjectDelete(g_chart_id, name); } }
剪定(PruneFractals)
PruneFractals ()は、保存されたフラクタルのうち、MaxFractalHistoryBarsの閾値を超えた古いデータを削除することで、処理性能とメモリ安定性を維持します。処理は書き込みポインタを用いたインプレース方式で配列を圧縮し、その後サイズを調整するため、不要なメモリ割当を避けられます。ただし、削除処理中は保存済みフラクタルごとにiBarShift ()が呼び出されるため、保存上限を極端に大きく設定すると処理時間が増加する可能性があります。デフォルトとして2000バー程度に設定すると、履歴の十分なカバー範囲と実行効率のバランスが取れます。
void PruneFractals(int keepBars) { if(keepBars <=0) return; // Prune bullish fractals int nB = ArraySize(bull_time); if(nB > 0) { int write = 0; for(int i=0; i<nB; i++) { int sh = iBarShift(_Symbol, _Period, bull_time[i], false); if(sh != -1 && sh <= keepBars) { bull_time[write] = bull_time[i]; bull_price[write] = bull_price[i]; bull_marked[write] = bull_marked[i]; write++; } } if(write != nB) { ArrayResize(bull_time, write); ArrayResize(bull_price, write); ArrayResize(bull_marked, write); } } // Prune bearish fractals int nS = ArraySize(bear_time); if(nS > 0) { int write = 0; for(int i=0; i<nS; i++) { int sh = iBarShift(_Symbol, _Period, bear_time[i], false); if(sh != -1 && sh <= keepBars) { bear_time[write] = bear_time[i]; bear_price[write] = bear_price[i]; bear_marked[write] = bear_marked[i]; write++; } } if(write != nS) { ArrayResize(bear_time, write); ArrayResize(bear_price, write); ArrayResize(bear_marked, write); } } }
本EAは、確定バーにおける有効なフラクタルを識別し、効率的に保存したうえで、確定したクロスを検出してBOSおよびChoCHシグナルを生成し、明確なチャート注釈を描画し、多チャネルで通知を発行する、モジュール構造を持つプロフェッショナル向けシステムです。各モジュールは、可読性、再現性、保守性を重視して設計されています。実運用や教育目的で使用する場合は、まずデモ環境でテストをおこない、検証段階ではDebugModeを有効にすることを推奨します。
アラート、ログ、通知オプション
本EAは3つの出力チャネルを設定可能で、重要なイベントを見逃さないように設計されています。デスクトップのポップアップ、モバイルプッシュ通知、サウンドです。最大限にカバーしたい場合は併用し、静かな運用を希望する場合は個別に利用できます。
設定:
- EnableAlerts:Alert()によるデスクトップポップアップ(ターミナル起動中に即時表示)。アクティブな監視時に有効です。
- EnableNotifications:SendNotification ()によるモバイルプッシュ通知(ターミナルの[ツール]>[オプション]>[通知]でMetaQuotes IDを入力し、MetaTraderモバイルアプリで通知を有効にする必要があります)。
- EnableSound:PlaySound (ファイル名)でターミナル内のサウンドを再生します。サウンドファイルはターミナルのSoundsフォルダに存在し、ターミナルの音量がミュート解除されている必要があります。
推奨アラートメッセージ形式(明確で解析可能)は以下の通りです。
Bull BOS detected: EURUSD H1 at 2025.08.01 14:00 price=1.12345 fr_time=2025.08.01 12:00
銘柄、時間足、イベント種別、確定時刻、価格、オプションでフラクタル時刻を含めることで、後で照合しやすくなります。
プッシュ通知を有効にする場合は、ターミナル設定でMetaQuotes IDを入力し、モバイル端末で通知を有効にしてください。

サウンド通知を利用する場合は、WAVまたはMP3ファイルをターミナルのSoundsフォルダにコピーし、[オプション]>[イベント]でPlaySoundが正しく動作することを確認してください。同じフラクタルによる重複通知を防ぐため、*_marked[]配列を活用し、市場が不安定な場合は銘柄ごとに30〜120秒程度のクールダウン期間を設けることを検討してください。さらに、監査やトラブルシューティング目的で、イベントをCSVファイルに追記してログ化することも可能です。FILE_COMMONを使用するか、ターミナルのMQL5/Filesフォルダにファイルを置き、書き込み後はファイルを速やかに閉じてファイルロックを回避してください。
テストと結果
このセクションでは、システムのパフォーマンスを示します。過去のバックテストと実運用の両方で、設計目標に沿った動作が確認できました。バックテストの設定や指標、エクイティカーブ、サンプルトレードを紹介し、検出器が実際にどのように動作するかを視覚的に示します。また、実運用での短期的なパフォーマンスも、リアルタイムスクリーンショットとバックテスト結果の照合を交えて確認します。最後に、制限事項と今後の追加検証のための推奨ステップをまとめます。
EURUSDおよびStep IndexのH1時間足でバックテストを実施し、以下に結果を示します。付随するアニメーションGIFでは、ChoCHおよびBOSイベントが明確にラベル付けされ、視覚化されています。注釈は正確で、警告(ChoCH)から確定(BOS)までの流れを容易に追跡できます。
- Step Index H1:フラクタルピボット(アンカー)、ChoCH(高値更新失敗)、Bull BOS(確定終値による確認)が注釈付きで表示

- EURUSD H1:フラクタルピボット(アンカー)、ChoCH(高値更新失敗)、Bull BOS(確定終値による確認)が注釈付きで表示

さらに、リアルタイムでの動作確認もおこない、EAは期待通りに動作しました。以下のスクリーンショットでは、EAがChoCHおよびBOSイベントをリアルタイムで検出、ラベル付け、ログ出力している様子を示しています。デスクトップ通知およびチャートオブジェクトは、確定バー上に即座に表示されます。リアルタイム動作はバックテストシグナルと方向性が一致しており、スプレッドやスリッページといった通常の実市場効果のみが実行ログに現れています。
ライブデモ:Volatility 75 (1s) Index M1におけるリアルタイムChoCH警告およびBull BOS確認。
このリアルタイムスクリーンショットは、システムがフラクタルピボットを利用して早期のChoCH警告を示し、その後、非リペイント型のBOSマーカーで構造変化を確定する様子を示しています。これにより、検出器のリアルタイム動作が視覚的に確認できます。
ライブデモ:Step Index M1におけるリアルタイムChoCH警告とBull BOS確認
システムはまずBear ChoCHおよび連続するBear BOS水準で弱気局面を示し、その後、下落が失敗した局面でBull ChoCHを表示し、複数のBull BOSシグナルによって強気反転を確定します。これにより、システムの早期警告(ChoCH)と非リペイントによる確定(BOS)の挙動がリアルタイムで確認できます。
以下は、MetaTrader 5の[エキスパート]タブに出力されたログです。
2025.09.03 10 : 20 :58.856 Fractal Reaction System (Volatility 75 (1s) Index,M1) Alert: Bear BOS detected: Volatility 75 (1s) Index M1 at 2025.09.03 13:44 price=3446.40 2025.09.03 10 : 20 :58.856 Fractal Reaction System (Volatility 75 (1s) Index,M1) Bear BOS detected: Volatility 75 (1s) Index M1 at 2025.09.03 13:44 price=3446.73 2025.09.03 10 : 20 :58.856 Fractal Reaction System (Volatility 75 (1s) Index,M1) Alert: Bear BOS detected: Volatility 75 (1s) Index M1 at 2025.09.03 13:44 price=3446.73 2025.09.03 10 : 20 :58.894 Fractal Reaction System (Step Index,M1) Bear BOS detected: Step Index M1 at 2025.09.03 13:44 price=8233.6 2025.09.03 10 : 20 :58.894 Fractal Reaction System (Step Index,M1) Alert: Bear BOS detected: Step Index M1 at 2025.09.03 13:44 price=8233.6 2025.09.03 10 : 20 :58.896 Fractal Reaction System (Volatility 75 (1s) Index,M1) Alert: Bear BOS detected: Volatility 75 (1s) Index M1 at 2025.09.03 13:44 price=3447.86
過去のバックテスト(EURUSDおよびStep Index、H1時間足)およびリアルタイムテストの両方において、フラクタルベースの検出器は設計通りに動作しました。フラクタルピボットは安定した非リペイント型のアンカーを提供し、ChoCH警告はバイアス喪失を早期に示し、BOS確認は構造変化を確実に通知しました。バックテストでは、注釈付きGIF(警告から確定までのシーケンス)と一致する一貫したトレードイベントが生成され、リアルタイムスクリーンショットでも同様のパターンが確認され、実市場特有の影響(スプレッドやスリッページ)のみがログに現れました。
結論
Fractal Reaction Systemは、シンプルなフラクタルピボットを、信頼性の高い非リペイント型の市場構造シグナルへと変換する仕組みです。ChoCH (Change of Character)は早期警告として、BOS (Break of Structure)は転換を確定させるシグナルとして機能します。本EAはメモリ安全性を備え、再現性を確保するため確定バーのみを評価し、チャート上には永続的な構造オブジェクトを安全に描画します。また、すべての確定イベントをログおよびアラートで通知し、過去のバックテストとライブ環境の両方で一貫した動作を実現します。本システムの主な強みは、透明性(監査可能なイベント)、再現性(確定バーに基づくロジック)、実用性(デスクトップ/モバイル/サウンド通知およびログによる容易な照合)の3点にあります。
本ツールはシグナル検出器であり、完全な取引管理システムではありません。実運用では、スプレッド、スリッページ、約定条件といった要素が実際のパフォーマンスに影響を与えます。また、ChoCHは指示的な役割ではなく、状況を知らせるための情報シグナルとして設計されています。資金を投入する前に、使用する銘柄とブローカー設定でEAを必ず検証し、付属のバックテスト設定ファイル(.set)やイベントログを確認してください。そのうえで、リスクプロファイルに応じて、ポジションサイズ調整、高時間足フィルター、クールダウン機構の追加などを検討するとよいでしょう。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/19365
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
平均足を使ったプロフェッショナルな取引システムの構築(第1回):カスタムインジケーターの開発
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
機械学習の限界を克服する(第3回):不可逆的誤りに関する新たな視点
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索
こんにちは、
file 'C: \UsersAdministrator@AppData@Roaming@MetaQuotesTerminal@24F345EB9F291441AFE537834F9D8A19@MQL5@Include@stdlib_mq5.mqh' not found Fractal_Reaction_System.mq5
このファイルはどこで入手できますか?
クリス