初心者からエキスパートへ:市場構造を認識したRSI取引
内容
はじめに
安定した取引成績への道は、多くの場合、断片的で遅行するシグナルへの依存によって阻まれています。一般的な取引教育では、ブレイクアウト取引のためのチャネルや、買われすぎと売られすぎの反転を狙うRSIオシレーターといったツールが重視されます。これらは基礎的なツールではあるものの、よく知られた欠点を抱えています。ブレイクアウトは失敗しやすく、リテストが発生しないことも多く、またRSIは、取引資金が耐えられる時間よりもはるかに長く、極端な水準に留まり続けることがあります。このような反応的なアプローチは、誤ったシグナルや機会損失を招き、結果として最適なモメンタムが始まった後に取引へ参加する状況を生み出します。
本記事では、プライスアクションの理解と初期モメンタムの確認を組み合わせることで、これらの問題を克服する構造化手法を紹介します。私たちは市場構造、特にトレンドチャネルを、単なるブレイクアウト境界として扱う従来の見方から脱却します。代わりに、それをモメンタムシグナルの強度と有効性を判断するための基盤となる文脈として捉えます。
本記事の焦点は具体的な手法にあります。RSIを単独の反転予測ツールとして使用するのではなく、確立された市場構造の枠組みの中で機能する確認エンジンとして用います。価格チャネルをアルゴリズムで検出し、重要な構造レベルにおいてRSIによる初期確認を求めることで、従来のブレイクアウト+リテスト手法よりも早く、かつ安全性の高いエントリーを、高い確率で特定することを目指します。
さらに、本記事では理論的な取引アイデアと実行可能な優位性との間のギャップを埋めるため、MQL5プログラミング言語を活用します。構造やシグナルを手作業で判定する作業は時間がかかるだけでなく、主観的になりがちです。そこで以下を実現する自動化取引システムの構築を解説します。
- アルゴリズムによる有効なトレンドチャネルの検出
- チャネル構造の文脈におけるRSIの挙動の論理的な解釈
- 事前に定義されたルールに基づいた取引の実行と管理
まず、一般的な手動チャネル取引とその典型的なパターンを詳細に分析し、プライスアクションの中核となる原則を整理します。次に、堅牢なチャネル検出アルゴリズムの構築へと進みます。本記事の要は、この構造分析とフィルタリングされたRSIダイナミクスを統合し、高信頼度のシグナルを生成する点にあります。最後に、これらすべてのロジックをMQL5による自動取引システムのプロトタイプとしてまとめ、市場理論からアルゴリズム実装までの実践的な道筋を示します。
概念と代表的な構造の理解
強気と弱気のフラグは、強いトレンドの中で発生する一時的な調整局面を示します。従来は単純なトレンド継続パターンとして扱われてきましたが、その本質的な価値は、チャネル内部に現れる特有のモメンタム挙動にあります。合理的に見えるブレイクアウト+リテスト手法は、結果としてその後の値動きの大部分を体系的に取り逃がしてしまいます。
本記事の手法では、構造とモメンタムの強力なコンフルエンスを用いて、トレンドの根底にある強さが再び表出する調整局面の正確な瞬間を狙います。
信頼性の高いフラグパターンの構造
強気フラグ:強い上昇トレンドの中で形成されます。最初に、急峻でほぼ垂直に近い価格上昇(初動のインパルスムーブ)によるフラグポールが現れ、その後、下向きに傾いた、または横ばいの保ち合いチャネルが続きます。このパターンが有効とみなされるのは、保ち合い局面での押し戻しが、フラグポールの起点を下回らない場合に限られます。

強気フラグのセットアップ
弱気フラグ:下降トレンドにおける強気フラグの鏡像となるパターンです。急激な下落によるフラグポールの形成後、上向きに傾いた、または横ばいの保ち合いチャネルが続きます。

弱気フラグのセットアップ
ブレイクアウトの先へ:ダイバージェンスという優位性
一般的なブレイクアウト手法では、価格がチャネルを抜け、その後に境界線をリテストするのを待ってからエントリーします。これに対し、本記事の手法は、ブレイクアウトが発生する前の段階で、逆方向への動きにおけるモメンタムの減衰を捉えることで、より早く、かつ期待値の高いエントリーを狙います。その判断は、パターンにおける重要な価格水準で発生するRSIのレギュラーダイバージェンスによっておこなわれます。
強気フラグの場合
構造的な前提条件:価格は下向きに傾いたチャネルの内部を推移しながら、安値切り下げ(LL)を連続して形成します。
ダイバージェンスのシグナル(本手法におけるエントリー判断のコンフルエンス):価格がチャネル下限のサポート付近で3回目または4回目の安値切り下げ(LL)を形成する一方で、RSIは安値切り上げ(HL)を形成します。このダイバージェンスは、保ち合い局面における下落方向の売りモメンタムが、論理的なサポート水準において枯渇しつつあることを示します。その結果、より大きな上位足の上昇トレンドが再開する準備が整った状態であると判断できます。
弱気フラグの場合
構造的な前提条件:価格は上向きに傾いたチャネルの内部を推移しながら、高値切り上げ(HH)を連続して形成します。
ダイバージェンスのシグナル(本手法におけるエントリー判断のコンフルエンス):価格がチャネル上限のレジスタンス付近で新たな高値(HH)を付ける一方で、RSIは高値切り下げ(LH)を形成します。これは、戻し局面における上昇方向の買いモメンタムが、論理的なレジスタンス水準で失速していることを示しています。その結果、より大きな下降トレンドが継続する準備が整ったと判断できます。

弱気フラグにおけるダイバージェンスとチャネルのコンフルエンス
このコンフルエンスが取引の質を変える理由
このダイバージェンスに基づくロジックは、単純なブレイクアウトルールと、基本的なRSIの買われ過ぎと売られ過ぎシグナルの双方に共通する根本的な欠陥を直接的に解消します。
- 「落ちるナイフをつかむ」取引を防ぐ:チャネルのサポート付近でRSIが売られ過ぎを示すこと自体は珍しくありませんが、それだけでは信頼できる買いシグナルにはなりません。一方、同じサポート水準でRSIの強気ダイバージェンスが発生していれば、売り圧力が弱まりつつあることの証拠となります。これにより、単なる「反発しやすい価格帯」が、高確率な「反転ポイント」へと変わります。
- 優れたリスク管理を可能にする:チャネル境界でのダイバージェンスを根拠としたエントリーでは、論理的に非常にタイトなストップロスを設定できます(ダイバージェンスに対応する直近のスイング安値/高値のすぐ外側)。その結果、ブレイクアウト後のエントリーと比べて、リスクリワード比は大幅に改善されます。
- より早く、かつ高い確証を伴うエントリーを狙う:価格が実際にブレイクアウトを完了するのを待つのではなく(その過程で値動きの最も有利な部分を逃すことも多い)、ブレイクアウトに先行し、それを予測するモメンタムの警告サインに基づいて行動します。
要するに、私たちはもはや単なる幾何学的なパターンを取引しているのではありません。そのパターン内部で発生しているモメンタム枯渇の証拠を取引しているのであり、その決定的な根拠としてダイバージェンスを用いています。
この明確なルールベースの概念は、アルゴリズムへの変換に非常に適しています。次のセクションでは、これらのフラグ構造を検出し、その境界で発生する重要なRSIダイバージェンスを識別するための具体的な計算ルールを定義します。 これが、MQL5エキスパートアドバイザー(EA)の中核ロジックとなります。
市場の設計図を分解し、より高い明瞭性、タイミング、規律をもって取引するための体系的プロセスを構築していきましょう。
実装
取引アイデアを堅牢な自動化システムへと正しく変換するためには、体系的かつモジュール化されたアプローチが不可欠です。RSIダイバージェンス検出とチャネル構造による確認を組み合わせる手法は、理論上は単純に見えるかもしれませんが、実際の実装には相当な技術的複雑さが伴います。この複雑さを効果的に管理するため、システムは相互に補完し合う2つの独立したモジュールとして設計されています。- RSIダイバージェンスインジケーター:価格チャート上でダイバージェンスパターンを検出し、視覚的に表示する独立したテクニカルインジケーター
- 等間隔チャネルEA:市場構造をリアルタイムで検出し、等間隔チャネルを自動的に描画する取引補助EA
このモジュール化された設計には、いくつかの戦略的利点があります。各コンポーネントを個別に開発、検証、最適化できるため、統合前に十分な信頼性を確保できます。トレーダーは戦略に応じて、どちらか一方のみを使用することも、両者を組み合わせて判断精度を高めることもできます。最終的には、これらのモジュールをシームレスに統合することで、構造検出、シグナル確認、自動執行を備えた統合取引システムを構築できます。
実装は段階的に進めていきます。まず、RSIダイバージェンスインジケーターをステップごとに構築し、ピボット検出、ダイバージェンス判定、視覚的シグナル表示のロジックを詳しく解説します。次に、チャネル配置EAがどのように価格構造を解析し、等間隔チャネルを特定して描画するかを示します。これら2つのツールが連携して動作することで、トレーダーは確認されたチャネル構造内で発生するダイバージェンスを即座に把握でき、主観的になりがちなテクニカル分析を、機械的かつルールベースの取引手法へと昇華させることができます。
以下のセクションでは、個別コンポーネントの開発から統合システムの展開まで、実装の全工程を順を追って説明し、堅牢なアルゴリズム基盤の上に構築された実務レベルの取引ツールキットを提供します。
RSIダイバージェンス検出インジケーター
セクション1:インジケーターフレームワークの構築と設定
RSIダイバージェンス検出インジケーターの基盤は、MetaTrader 5における適切なインジケーターフレームワークの設定から始まります。まず、取引プラットフォームとの連携方法を定義する必須のインジケータープロパティを設定します。1行目から22行目にかけての#propertyディレクティブでは、著作権情報、バージョン管理、そして最も重要な表示に関する設定が構成されています。
indicator_separate_windowの宣言により、RSIの計算結果はメインチャートとは独立した専用サブウィンドウに表示され、視認性の低下を防ぎます。このインジケーターでは、計算用として7つのデータバッファを確保しつつ、実際に表示するプロットは3つのみに限定しています。このように、計算バッファと表示出力を分離することで、メモリ効率を保ちながら、整理された視覚表現を実現しています。
色とスタイルの設定(11~17行目)では、直感的に理解しやすい視覚表現を採用しています。RSIラインにはDodgerBlue、高値ピボットにはオレンジ、安値ピボットにはDeepSkyBlueを使用し、異なる価格構造ポイントを即座に識別できるようにしています。
//+------------------------------------------------------------------+ //| RSIDivergenceDetector.mq5 | //| Copyright 2025, MetaQuotes Ltd| //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property description "RSI Divergence Detector with Main Chart Arrows" #property description "Shows divergence arrows on main chart, stores pivot values" #property indicator_separate_window #property indicator_buffers 7 #property indicator_plots 3 #property indicator_color1 clrDodgerBlue // RSI line #property indicator_color2 clrOrange // RSI High Pivots #property indicator_color3 clrDeepSkyBlue // RSI Low Pivots #property indicator_width1 2 #property indicator_width2 3 #property indicator_width3 3 #property indicator_label1 "RSI" #property indicator_label2 "RSI High Pivot" #property indicator_label3 "RSI Low Pivot"
セクション2:ユーザー設定と入力パラメータ
実務向け取引ツールでは、自動化とユーザー制御のバランスが重要です。このインジケーターでは、これを包括的な入力パラメーターシステム(25~42行目)によって実現しています。inputキーワードにより、ユーザーが設定ダイアログ上で変更可能な変数を作成できます。これにより、トレーダーは自分の取引スタイルに応じて検出アルゴリズムをカスタマイズできます。
RSI計算のコア部分(期間や価格ソース)や、ピボット検出感度をInpPivotStrengthで、さらにダイバージェンスのフィルタリング条件を制御することが可能です。また、ブール型のフラグであるInpShowRegularとInpShowHiddenにより、トレーダーは自分の取引戦略に応じて通常のダイバージェンスと隠れダイバージェンスの表示を切り替えることができます。
特に重要なのは、InpRequireRSIBreakパラメータ(40行目)です。これはRSIが前回のピボットレベルを実際に突破した場合にのみダイバージェンスをシグナルする確認ロジックを実装しており、早すぎるシグナル発生を防ぎ、信頼性を高める役割を持ちます。
//--- Input parameters input int InpRSIPeriod = 14; // RSI Period input ENUM_APPLIED_PRICE InpRSIPrice = PRICE_CLOSE; // RSI Applied Price input int InpPivotStrength = 3; // Pivot Strength (bars on each side) input double InpOverbought = 70.0; // Overbought Level input double InpOversold = 30.0; // Oversold Level input bool InpShowRegular = true; // Show Regular Divergences input bool InpShowHidden = true; // Show Hidden Divergences input color InpBullishColor = clrLimeGreen; // Bullish divergence arrow color input color InpBearishColor = clrRed; // Bearish divergence arrow color input int InpArrowSize = 3; // Arrow size on chart input bool InpAlertOnDivergence = true; // Alert on divergence input bool InpSendNotification = false; // Send notification input bool InpRequireRSIBreak = true; // Require RSI to break pivot line input double InpMinDivergenceStrength = 2.0; // Minimum RSI divergence strength input int InpMaxPivotDistance = 100; // Max bars between pivots for divergence input double InpArrowOffsetPct = 0.3; // Arrow offset percentage (0.3 = 30%)
セクション3:データ構造設計とメモリ管理
44行目から67行目では、カスタム構造体RSI_PIVOTを用いてコアとなるデータアーキテクチャを実装しています。この構造体は重要な設計上の決定であり、異なる種類のデータを混ぜた単純な配列に頼るのではなく、各ピボットポイントに関する関連情報をすべてカプセル化した専用のデータ型を作成しています。
各RSI_PIVOTインスタンスは、バーインデックス、RSI値、対応する価格レベル、タイムスタンプ、ピボットタイプ(高値/安値)、検出強度、確認ステータスを保持します。
コンストラクタ(59〜62行目)は適切な初期化を保証し、未初期化変数による一般的なプログラミングエラーを防ぎます。また、rsiPivots[]という動的配列でこれらの構造体を管理し、pivotCountで現在有効なエントリ数を追跡します。この手法により、データの整合性を保ちながら効率的なメモリ使用が可能となり、複雑なインデックス管理をおこなうことなくピボットデータの追加、削除、検索を容易におこなうことができます。
//--- Indicator buffers double BufferRSI[]; double BufferRSIHighPivot[]; double BufferRSILowPivot[]; double BufferRSIHigh[]; double BufferRSILow[]; double BufferPivotHigh[]; double BufferPivotLow[]; //--- Global variables int rsiHandle; datetime lastAlertTime; string indicatorPrefix = "RSI_DIV_"; //--- Structures for storing pivot data struct RSI_PIVOT { int barIndex; double value; double price; datetime time; bool isHigh; int strength; bool isConfirmed; // Constructor to initialize values RSI_PIVOT() : barIndex(-1), value(0.0), price(0.0), time(0), isHigh(false), strength(0), isConfirmed(false) {} }; RSI_PIVOT rsiPivots[]; int pivotCount = 0;
セクション4:初期化とリソースのセットアップ
OnInit()関数(70〜125行目)は、インジケーターがロードされる際の重要な初期設定フェーズを担当します。74行目から80行目では、SetIndexBuffer()を用いて計算バッファとインジケーターの表示システムを接続しています。
各バッファには明確な役割が割り当てられています。BufferRSIは計算されたRSI値を格納し、BufferRSIHighPivotとBufferRSILowPivotはピボットポイントの視覚的マーカーを保持します。
83行目から94行目では描画挙動を設定しています。メインのRSIラインはDRAW_LINEで描画され、ピボットポイントは文字コード159(四角記号)を用いたDRAW_ARROWで描画されます。103行目では組み込みRSIインジケーターへのハンドルをiRSI()で作成し、計算済みのRSI値に効率的にアクセスできるようにしています。
112行目から115行目では買われ過ぎ/売られ過ぎレベルの参照線を設定し、121行目ではCleanChartObjects()を呼び出して、前回のインジケーター実行で残ったグラフィック要素を削除し、クリーンな状態でスタートできるようにしています。
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0, BufferRSI, INDICATOR_DATA); SetIndexBuffer(1, BufferRSIHighPivot, INDICATOR_DATA); SetIndexBuffer(2, BufferRSILowPivot, INDICATOR_DATA); SetIndexBuffer(3, BufferRSIHigh, INDICATOR_DATA); SetIndexBuffer(4, BufferRSILow, INDICATOR_DATA); SetIndexBuffer(5, BufferPivotHigh, INDICATOR_DATA); SetIndexBuffer(6, BufferPivotLow, INDICATOR_DATA); //--- Set plot properties PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_LINE); PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_ARROW); PlotIndexSetInteger(2, PLOT_DRAW_TYPE, DRAW_ARROW); //--- Set arrow codes for RSI pivot points PlotIndexSetInteger(1, PLOT_ARROW, 159); // Square dot for high pivots PlotIndexSetInteger(2, PLOT_ARROW, 159); // Square dot for low pivots //--- Set empty values for arrow buffers PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE); //--- Set indicator labels IndicatorSetString(INDICATOR_SHORTNAME, "RSI Divergence (" + string(InpRSIPeriod) + ")"); IndicatorSetInteger(INDICATOR_DIGITS, 2); //--- Create RSI handle rsiHandle = iRSI(_Symbol, _Period, InpRSIPeriod, InpRSIPrice); if(rsiHandle == INVALID_HANDLE) { Print("Failed to create RSI handle"); return(INIT_FAILED); } //--- Set overbought/oversold levels IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, InpOversold); IndicatorSetDouble(INDICATOR_LEVELVALUE, 1, InpOverbought); IndicatorSetInteger(INDICATOR_LEVELCOLOR, 0, clrSilver); IndicatorSetInteger(INDICATOR_LEVELCOLOR, 1, clrSilver); IndicatorSetInteger(INDICATOR_LEVELSTYLE, 0, STYLE_DOT); IndicatorSetInteger(INDICATOR_LEVELSTYLE, 1, STYLE_DOT); lastAlertTime = 0; //--- Clean any existing objects from previous runs CleanChartObjects(); return(INIT_SUCCEEDED); }
セクション5:コア計算エンジンとデータ処理
OnCalculate()関数(128〜169行目)は、メインの処理ループとして機能し、各ティックやバーの更新時に呼び出されます。まず130行目でデータを検証し、有意な計算をおこなうのに十分な過去データが存在することを確認します。
133行目から142行目では、インテリジェントな再計算ロジックを実装しています。最初の実行時(prev_calculated == 0)はピボット配列を初期化し、カウンタをリセットします。それ以外の場合は、前回の続きから処理を再開することで、冗長な計算を避け、パフォーマンスを最適化します。145行目では、RSIハンドルからCopyBuffer()を使用してRSI値を取得しています。これは、MetaTrader 5のテクニカル指標APIを適切に利用したデータ取得の例です。
その後、関数は3つの主要処理ステップを順序立てて実行します。RSIピボットの検出、ダイバージェンスの検出、古いデータのクリーンアップです。このモジュール化されたアプローチにより、処理の責務が明確になりつつ、効率的なデータフローを維持できます。
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { if(rates_total < InpRSIPeriod + 10) return(0); int start; if(prev_calculated == 0) { start = InpRSIPeriod + InpPivotStrength; ArrayResize(rsiPivots, rates_total); pivotCount = 0; CleanChartObjects(); } else { start = prev_calculated - 1; } //--- Get RSI values if(CopyBuffer(rsiHandle, 0, 0, rates_total, BufferRSI) <= 0) return(0); //--- Find RSI pivots FindRSIPivots(rates_total, start, high, low, time); //--- Detect divergences and draw arrows on main chart DetectDivergences(rates_total, start, high, low, close, time); //--- Clean old pivot data CleanOldPivots(rates_total); return(rates_total); }
セクション6:ピボット検出アルゴリズムの実装
FindRSIPivots()(172〜218行目)は、スイングポイント(ピボット)を特定するコアロジックを実装しています。175行目から176行目では、表示バッファから前回のピボットマーカーをクリアし、常に現在の関連ピボットのみが表示されるようにしています。
178行目から216行目のループでは、各バーを順に処理し、ピボット検出の条件を適用します。IsHighPivot()およびIsLowPivot()ヘルパー関数(221〜272行目)が実際の判定ロジックを担います。高値ピボットとして認定されるには、対象バーのRSI値が指定されたバー数(InpPivotStrengthで制御)よりも両側で高くなければなりません。
有効なピボットが見つかった場合(183〜215行目)、新しいRSI_PIVOT構造体に関連データを格納します。格納する情報は、参照用のバーインデックス、比較用のRSI値、ダイバージェンス分析用の価格レベル、追跡用のタイムスタンプ、そしてピボットタイプの分類です。isConfirmedフラグは初期値としてfalseに設定され、ダイバージェンス検出ロジックが有効なシグナルに貢献したピボットを適宜マークできるようにしています。
//+------------------------------------------------------------------+ //| Find RSI pivots function | //+------------------------------------------------------------------+ void FindRSIPivots(int rates_total, int start, const double &high[], const double &low[], const datetime &time[]) { // Clear pivot buffers ArrayFill(BufferRSIHighPivot, start, rates_total - start, EMPTY_VALUE); ArrayFill(BufferRSILowPivot, start, rates_total - start, EMPTY_VALUE); for(int i = start; i < rates_total - InpPivotStrength; i++) { //--- Check for RSI high pivot if(IsHighPivot(i, BufferRSI, InpPivotStrength)) { if(pivotCount < ArraySize(rsiPivots)) { rsiPivots[pivotCount].barIndex = i; rsiPivots[pivotCount].value = BufferRSI[i]; rsiPivots[pivotCount].price = high[i]; rsiPivots[pivotCount].time = time[i]; rsiPivots[pivotCount].isHigh = true; rsiPivots[pivotCount].strength = InpPivotStrength; rsiPivots[pivotCount].isConfirmed = false; pivotCount++; BufferRSIHighPivot[i] = BufferRSI[i]; } } //--- Check for RSI low pivot if(IsLowPivot(i, BufferRSI, InpPivotStrength)) { if(pivotCount < ArraySize(rsiPivots)) { rsiPivots[pivotCount].barIndex = i; rsiPivots[pivotCount].value = BufferRSI[i]; rsiPivots[pivotCount].price = low[i]; rsiPivots[pivotCount].time = time[i]; rsiPivots[pivotCount].isHigh = false; rsiPivots[pivotCount].strength = InpPivotStrength; rsiPivots[pivotCount].isConfirmed = false; pivotCount++; BufferRSILowPivot[i] = BufferRSI[i]; } } } }
セクション7:ピボット検出のためのヘルパー関数
IsHighPivot()およびIsLowPivot()関数は、RSI値におけるスイングポイント(ピボット)を特定する正確なロジックを実装しています。これらの関数は、トレーダーが一般的に「ピボット強度」と呼ぶ判定方法を具現化したものです。
高値ピボットとして認定されるバー(221〜246行目)では、そのRSI値が指定された左側のバー数より高く、かつ指定された右側のバー数よりも高い必要があります。
この両方向の検証により、一時的な変動ではなく、真の局所的な最大値/最小値を検出することが保証されます。強度パラメータは感度を制御しており、値を大きくすると確認バー数が増え、ピボットの数は減りますが信頼性は高まります。これらの関数は単純なブール値を返し、メインのピボット検出ループに渡されます。これにより、検出ロジックとデータ管理がきれいに分離されていることが示されています。
//+------------------------------------------------------------------+ //| Check if bar is a high pivot | //+------------------------------------------------------------------+ bool IsHighPivot(int index, double &buffer[], int strength) { if(index < strength || index >= ArraySize(buffer) - strength) return false; double pivotValue = buffer[index]; // Check left side for(int i = 1; i <= strength; i++) { if(buffer[index - i] > pivotValue) return false; } // Check right side for(int i = 1; i <= strength; i++) { if(buffer[index + i] > pivotValue) return false; } return true; } //+------------------------------------------------------------------+ //| Check if bar is a low pivot | //+------------------------------------------------------------------+ bool IsLowPivot(int index, double &buffer[], int strength) { if(index < strength || index >= ArraySize(buffer) - strength) return false; double pivotValue = buffer[index]; // Check left side for(int i = 1; i <= strength; i++) { if(buffer[index - i] < pivotValue) return false; } // Check right side for(int i = 1; i <= strength; i++) { if(buffer[index + i] < pivotValue) return false; } return true; }
セクション8:ダイバージェンス検出ロジックとシグナル生成
DetectDivergences()関数(275〜373行目)は、インジケーターの知能的中核を担う部分です。まず277行目で十分なピボットデータがあるかを確認した後、ネストされたループ構造(280〜372行目)で全ての可能なピボットペアを比較します。
283行目では実用的なフィルタを適用しています。ピボット同士の距離がInpMaxPivotDistanceを超える場合は無視されます。距離が離れすぎたピボット同士は、しばしば異なる市場フェーズを表すためです。次に、ロジックは2つの主要な分岐に分かれます。高値ピボットの比較は弱気ダイバージェンス(287〜322行目)、低値ピボットの比較は強気ダイバージェンス(323〜372行目)です。
各ダイバージェンスタイプに対して、レギュラーと隠れダイバージェンスの両方を確認します。具体的な価格/RSIの関係条件は、専用のCheck...Divergence()関数にカプセル化されています。有効なダイバージェンスが見つかると、弱気の場合は294行目から298行目、強気の場合は330行目から334行目で、現在の価格レンジからのパーセンテージベースのオフセットを使って矢印の表示位置を計算します。これにより、矢印がプライスアクションを隠すことなく明確に表示されます。
//+------------------------------------------------------------------+ //| Detect divergences between price and RSI | //+------------------------------------------------------------------+ void DetectDivergences(int rates_total, int start, const double &high[], const double &low[], const double &close[], const datetime &time[]) { if(pivotCount < 4) return; // Check all pivot pairs for divergence for(int i = pivotCount - 1; i >= 0; i--) { for(int j = i - 1; j >= 0; j--) { // Skip if pivots are too far apart if(rsiPivots[i].barIndex - rsiPivots[j].barIndex > InpMaxPivotDistance) continue; // Check if both are high pivots if(rsiPivots[i].isHigh && rsiPivots[j].isHigh) { // Check for regular bearish divergence if(InpShowRegular && CheckBearishDivergence(rsiPivots[i], rsiPivots[j], rates_total)) { // Check RSI break confirmation if required if(!InpRequireRSIBreak || CheckRSIBreak(rsiPivots[j], rsiPivots[i], rates_total, true)) { double arrowPrice = high[rsiPivots[i].barIndex]; double range = high[rsiPivots[i].barIndex] - low[rsiPivots[i].barIndex]; double offset = range * InpArrowOffsetPct; DrawChartArrow(rsiPivots[i].barIndex, time[rsiPivots[i].barIndex], arrowPrice + offset, false, "Bearish"); TriggerAlert("Regular Bearish Divergence detected!", time[rsiPivots[i].barIndex]); // Mark as confirmed to avoid duplicate signals rsiPivots[i].isConfirmed = true; rsiPivots[j].isConfirmed = true; } } // Check for hidden bearish divergence else if(InpShowHidden && CheckHiddenBearishDivergence(rsiPivots[i], rsiPivots[j])) { if(!InpRequireRSIBreak || CheckRSIBreak(rsiPivots[j], rsiPivots[i], rates_total, true)) { double arrowPrice = high[rsiPivots[i].barIndex]; double range = high[rsiPivots[i].barIndex] - low[rsiPivots[i].barIndex]; double offset = range * InpArrowOffsetPct; DrawChartArrow(rsiPivots[i].barIndex, time[rsiPivots[i].barIndex], arrowPrice + offset, false, "Bearish(H)"); TriggerAlert("Hidden Bearish Divergence detected!", time[rsiPivots[i].barIndex]); rsiPivots[i].isConfirmed = true; rsiPivots[j].isConfirmed = true; } } } // Check if both are low pivots else if(!rsiPivots[i].isHigh && !rsiPivots[j].isHigh) { // Check for regular bullish divergence if(InpShowRegular && CheckBullishDivergence(rsiPivots[i], rsiPivots[j], rates_total)) { // Check RSI break confirmation if required if(!InpRequireRSIBreak || CheckRSIBreak(rsiPivots[j], rsiPivots[i], rates_total, false)) { double arrowPrice = low[rsiPivots[i].barIndex]; double range = high[rsiPivots[i].barIndex] - low[rsiPivots[i].barIndex]; double offset = range * InpArrowOffsetPct; DrawChartArrow(rsiPivots[i].barIndex, time[rsiPivots[i].barIndex], arrowPrice - offset, true, "Bullish"); TriggerAlert("Regular Bullish Divergence detected!", time[rsiPivots[i].barIndex]); rsiPivots[i].isConfirmed = true; rsiPivots[j].isConfirmed = true; } } // Check for hidden bullish divergence else if(InpShowHidden && CheckHiddenBullishDivergence(rsiPivots[i], rsiPivots[j])) { if(!InpRequireRSIBreak || CheckRSIBreak(rsiPivots[j], rsiPivots[i], rates_total, false)) { double arrowPrice = low[rsiPivots[i].barIndex]; double range = high[rsiPivots[i].barIndex] - low[rsiPivots[i].barIndex]; double offset = range * InpArrowOffsetPct; DrawChartArrow(rsiPivots[i].barIndex, time[rsiPivots[i].barIndex], arrowPrice - offset, true, "Bullish(H)"); TriggerAlert("Hidden Bullish Divergence detected!", time[rsiPivots[i].barIndex]); rsiPivots[i].isConfirmed = true; rsiPivots[j].isConfirmed = true; } } } } } }
セクション9:ダイバージェンスの検証と確認関数
CheckBearishDivergence()、CheckBullishDivergence()、CheckHiddenBearishDivergence()、およびCheckHiddenBullishDivergence()関数(376〜445行目)は、それぞれのダイバージェンスタイプの正確な数学的定義を実装しています。
各関数は、パラメータの検証、価格関係の確認、RSI関係の確認、強度の検証、確認ステータスの検証という、一貫した手順で構成されています。
たとえば、CheckBearishDivergence()(376〜395行目)は次の条件を満たす必要があります。1) 新しいピボットの価格が古いピボットより高いこと、2) 新しいピボットのRSI値が古いピボットより低いこと(少なくともInpMinDivergenceStrength分)、3) 絶対値の差が最小強度条件を満たしていること、4) いずれのピボットも、他のダイバージェンスで既に確認されていないことこれらの厳密な条件により、誤検知を防ぎ、有意なダイバージェンスのみがアラートを発生させるようにしています。
//+------------------------------------------------------------------+ //| Check for regular bearish divergence | //+------------------------------------------------------------------+ bool CheckBearishDivergence(RSI_PIVOT &pivot1, RSI_PIVOT &pivot2, int rates_total) { // pivot1 is newer, pivot2 is older if(pivot1.barIndex <= pivot2.barIndex) return false; if(pivot1.barIndex >= rates_total - 1 || pivot2.barIndex >= rates_total - 1) return false; // Price makes higher high bool priceHigher = pivot1.price > pivot2.price; // RSI makes lower high bool rsiLower = pivot1.value < pivot2.value - InpMinDivergenceStrength; // Ensure there's enough divergence strength bool enoughStrength = MathAbs(pivot2.value - pivot1.value) >= InpMinDivergenceStrength; // Not already confirmed bool notConfirmed = !pivot1.isConfirmed && !pivot2.isConfirmed; return priceHigher && rsiLower && enoughStrength && notConfirmed; } //+------------------------------------------------------------------+ //| Check for regular bullish divergence | //+------------------------------------------------------------------+ bool CheckBullishDivergence(RSI_PIVOT &pivot1, RSI_PIVOT &pivot2, int rates_total) { // pivot1 is newer, pivot2 is older if(pivot1.barIndex <= pivot2.barIndex) return false; if(pivot1.barIndex >= rates_total - 1 || pivot2.barIndex >= rates_total - 1) return false; // Price makes lower low bool priceLower = pivot1.price < pivot2.price; // RSI makes higher low bool rsiHigher = pivot1.value > pivot2.value + InpMinDivergenceStrength; // Ensure there's enough divergence strength bool enoughStrength = MathAbs(pivot1.value - pivot2.value) >= InpMinDivergenceStrength; // Not already confirmed bool notConfirmed = !pivot1.isConfirmed && !pivot2.isConfirmed; return priceLower && rsiHigher && enoughStrength && notConfirmed; } //+------------------------------------------------------------------+ //| Check for hidden bearish divergence | //+------------------------------------------------------------------+ bool CheckHiddenBearishDivergence(RSI_PIVOT &pivot1, RSI_PIVOT &pivot2) { // Price makes lower high bool priceLower = pivot1.price < pivot2.price; // RSI makes higher high bool rsiHigher = pivot1.value > pivot2.value + InpMinDivergenceStrength; // Not already confirmed bool notConfirmed = !pivot1.isConfirmed && !pivot2.isConfirmed; return priceLower && rsiHigher && notConfirmed; } //+------------------------------------------------------------------+ //| Check for hidden bullish divergence | //+------------------------------------------------------------------+ bool CheckHiddenBullishDivergence(RSI_PIVOT &pivot1, RSI_PIVOT &pivot2) { // Price makes higher low bool priceHigher = pivot1.price > pivot2.price; // RSI makes lower low bool rsiLower = pivot1.value < pivot2.value - InpMinDivergenceStrength; // Not already confirmed bool notConfirmed = !pivot1.isConfirmed && !pivot2.isConfirmed; return priceHigher && rsiLower && notConfirmed; }
セクション10:RSIブレイクの確認とシグナル検証
CheckRSIBreak()関数(448〜478行目)は、追加の検証レイヤーを実装しています。これは、ダイバージェンスを確認する前に、RSIが実際に前のピボットレベルを突破したことを確認するものです。
この関数は、モメンタムが実際に変化する前に発生する早すぎるダイバージェンスシグナルという一般的な問題に対応しています。456行目から458行目では、新しいピボットの後に10本のバーのルックバックウィンドウを設定し、この間にブレイク条件を監視します。
弱気ダイバージェンスの場合(464〜467行目)、RSIが古いピボットの値を下回ったかを確認します。強気ダイバージェンスの場合(469〜472行目)、RSIが古いピボットの値を上回ったかを確認します。この確認ステップにより、インジケーターは単なるパターン検出器からモメンタム変化の検証器へと進化し、シグナルの信頼性が大幅に向上します。
//+------------------------------------------------------------------+ //| Check if RSI has broken the previous pivot level | //+------------------------------------------------------------------+ bool CheckRSIBreak(RSI_PIVOT &olderPivot, RSI_PIVOT &newerPivot, int rates_total, bool isBearish) { // For bearish: Check if RSI has broken below the older pivot's value // For bullish: Check if RSI has broken above the older pivot's value if(newerPivot.barIndex >= rates_total - 1) return false; // Look for break in recent bars after newer pivot int lookbackBars = 10; int startBar = newerPivot.barIndex + 1; int endBar = MathMin(rates_total - 1, newerPivot.barIndex + lookbackBars); for(int i = startBar; i <= endBar; i++) { if(isBearish) { // Bearish: RSI should break below older pivot value if(BufferRSI[i] < olderPivot.value) return true; } else { // Bullish: RSI should break above older pivot value if(BufferRSI[i] > olderPivot.value) return true; } } return false; }
セクション11:視覚的シグナル表示とチャート管理
DrawChartArrow()関数(481〜529行目)は、メインの価格チャート上でのダイバージェンスシグナルの視覚表示を担当します。483行目から484行目では、タイムスタンプを用いてユニークなオブジェクト名を生成し、各矢印とラベルのペアを独立して管理できるようにしています。487行目から488行目では、同じ名前の既存オブジェクトを削除し、インジケーターの再計算時に重複が発生しないようにしています。
その後、シグナルの方向に応じて処理が分岐します。強気シグナルはOBJ_ARROW_BUYオブジェクト(491〜505行目)を使用し、弱気シグナルはOBJ_ARROW_SELLオブジェクト(507〜522行目)を使用します。各矢印にはスタイル設定が可能で、色はInpBullishColor / InpBearishColor、サイズはInpArrowSizeから取得されます。また、ダイバージェンスタイプを示すテキストラベルも含まれます。
CleanChartObjects()関数(532〜542行目)は、インジケーターが作成したすべてのオブジェクトを体系的に削除し、チャートの表示を常に整理された状態に保ちます。
//+------------------------------------------------------------------+ //| Draw arrow on main chart | //+------------------------------------------------------------------+ void DrawChartArrow(int barIndex, datetime time, double price, bool isBullish, string labelText) { string arrowName = indicatorPrefix + "Arrow_" + IntegerToString(time); string labelName = indicatorPrefix + "Label_" + IntegerToString(time); // Remove existing objects with same name ObjectDelete(0, arrowName); ObjectDelete(0, labelName); if(isBullish) { // Draw bullish arrow (up arrow) if(!ObjectCreate(0, arrowName, OBJ_ARROW_BUY, 0, time, price)) { Print("Failed to create arrow object: ", GetLastError()); return; } ObjectSetInteger(0, arrowName, OBJPROP_COLOR, InpBullishColor); ObjectSetInteger(0, arrowName, OBJPROP_WIDTH, InpArrowSize); ObjectSetInteger(0, arrowName, OBJPROP_BACK, false); // Draw label if(!ObjectCreate(0, labelName, OBJ_TEXT, 0, time, price)) return; ObjectSetString(0, labelName, OBJPROP_TEXT, labelText); ObjectSetInteger(0, labelName, OBJPROP_COLOR, InpBullishColor); ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8); } else { // Draw bearish arrow (down arrow) if(!ObjectCreate(0, arrowName, OBJ_ARROW_SELL, 0, time, price)) { Print("Failed to create arrow object: ", GetLastError()); return; } ObjectSetInteger(0, arrowName, OBJPROP_COLOR, InpBearishColor); ObjectSetInteger(0, arrowName, OBJPROP_WIDTH, InpArrowSize); ObjectSetInteger(0, arrowName, OBJPROP_BACK, false); // Draw label if(!ObjectCreate(0, labelName, OBJ_TEXT, 0, time, price)) return; ObjectSetString(0, labelName, OBJPROP_TEXT, labelText); ObjectSetInteger(0, labelName, OBJPROP_COLOR, InpBearishColor); ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8); } ObjectSetInteger(0, labelName, OBJPROP_ANCHOR, ANCHOR_CENTER); ObjectSetInteger(0, labelName, OBJPROP_BACK, false); }
セクション12:クリーンアップ関数とアラートシステム
CleanChartObjects()(532〜542行目)およびOnDeinit()関数(572〜582行目)は、重要なリソース管理を担当します。ピボットのクリーンアップロジックでは、最新500個のピボットのみを保持し、それより古いデータは破棄することで、長時間のチャート分析中にメモリが膨張するのを防ぎます。インジケーターが削除される際、OnDeinit()はIndicatorRelease()を使用してRSIインジケーターハンドルを解放し、チャート上のすべてのオブジェクトをクリーンアップします。
TriggerAlert()関数は、高度な通知システムを実装しており、シグナル認識と通知疲れの管理を両立させます。554行目では、ユーザーが設定したアラート有効/無効の選択を尊重します。
557行目ではlastAlertTimeを用いた時間フィルタリングをおこない、同じバーに対する繰り返しアラートを防止します。条件が満たされると、562行目で標準のMetaTrader 5アラート(音付き)を発動し、565行目から568行目では、設定が有効な場合にプラットフォーム通知も送信されます。
//+------------------------------------------------------------------+ //| Clean chart objects | //+------------------------------------------------------------------+ void CleanChartObjects() { int total = ObjectsTotal(0, 0, -1); for(int i = total - 1; i >= 0; i--) { string name = ObjectName(0, i, 0, -1); if(StringFind(name, indicatorPrefix, 0) != -1) { ObjectDelete(0, name); } } } //+------------------------------------------------------------------+ //| Clean old pivot data | //+------------------------------------------------------------------+ void CleanOldPivots(int rates_total) { if(pivotCount > 500) // Keep last 500 pivots maximum { int newCount = 250; for(int i = 0; i < newCount; i++) { rsiPivots[i] = rsiPivots[pivotCount - newCount + i]; } pivotCount = newCount; } } //+------------------------------------------------------------------+ //| Trigger alert function | //+------------------------------------------------------------------+ void TriggerAlert(string message, datetime time) { if(!InpAlertOnDivergence) return; // Avoid repeated alerts for same bar if(time <= lastAlertTime) return; lastAlertTime = time; // Play sound Alert(message + " at ", TimeToString(time, TIME_DATE|TIME_MINUTES)); // Send notification if enabled if(InpSendNotification) SendNotification("RSI Divergence: " + Symbol() + " " + StringSubstr(EnumToString(_Period), 7) + " - " + message + " at " + TimeToString(time)); } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Delete RSI handle if(rsiHandle != INVALID_HANDLE) IndicatorRelease(rsiHandle); // Clean up chart objects CleanChartObjects(); }
記事末尾には完全なソースコードが添付されています。RSI Divergence Detectorのアーキテクチャと実装を詳しく確認し、テクニカル指標が確立されたので、次に取引システムの第2のコンポーネントに進みます。Equidistant Channel Auto-Placement EA(等間隔チャネル自動配置EA)の開発です。この自動化ツールは、ダイバージェンス検出を補完し、価格構造パターンを特定することで、最終的に統合されたアルゴリズム取引ソリューションを構築します。実装後には、両プロジェクトの包括的なテスト結果を共有し、今後の開発およびシステム統合の戦略的ロードマップを示します。
Equidistant Channel Auto-Placement EA
このEAは、精密に設計された列挙型システムと、包括的なユーザー設定可能パラメータによって堅牢な基盤を構築します。ENUM_CHANNEL_TYPE列挙型は、上昇チャネルと下降チャネルの意味的区別を明確にし、従来の強気/弱気用語を置き換え、取引セットアップに即した表現を提供します。上昇チャネル(高値の切り上げ)は売りの機会を示し、下降チャネル(安値の切り下げ)は買いの機会を示します。この用語のシフトは重要な設計上の決定であり、抽象的なテクニカル概念ではなく、実際の取引行動に沿ったチャネルの理解を促します。
セクション1:アーキテクチャ基盤とユーザー設定フレームワーク
入力パラメータシステムは、オートメーションとユーザー制御のバランスを取った高度な設定インターフェイスを提供します。それぞれのパラメータはチャネル検出アルゴリズムで明確な役割を持ちます。LookbackBarsは履歴データの範囲を定義し、SwingStrengthはスイングポイント検出の感度を制御します。一方、MinTouchesPerLineは各チャネルラインに複数回価格が触れるという新しい検証条件を導入し、偽シグナルの発生を大幅に減らします。TouchTolerancePipsパラメータは、わずかな価格のぶれを考慮し、そうでなければ真のチャネル構造を無効化してしまう可能性のある微小な価格変動を許容します。
enum ENUM_CHANNEL_TYPE { CHANNEL_NONE, CHANNEL_RISING, // Higher lows - Sell setups (formerly bullish) CHANNEL_FALLING // Lower highs - Buy setups (formerly bearish) }; input bool EnableRisingChannels = true; input bool EnableFallingChannels = true; input int LookbackBars = 150; input int SwingStrength = 2; input bool ShowChannel = true; input color RisingChannelColor = clrRed; input color FallingChannelColor = clrLimeGreen; input int ChannelWidth = 1; input int MinChannelLengthBars = 15; input double MinChannelHeightPct = 0.5; input bool AlertOnNewChannel = true; input int MinTouchesPerLine = 2; input double TouchTolerancePips = 5.0; input int MaxExtensionBars = 50; input bool ExtendLeft = true; input bool ExtendRight = false;
セクション2:状態管理と初期化アーキテクチャ
このEAは、チャネルの持続性やアラートのタイミングを追跡するグローバル変数を用いた高度な状態管理システムを実装しています。これは、過去の実装で問題となった点に対応するものです。lastAlertTime変数は、テスト中に確認された連続アラートの問題を防ぐためのインテリジェントなアラート制御(スロットリング)を導入しています。channelStartBar変数とchannelEndBar変数はメモリシステムを構築し、新しいチャネル形成と既存チャネルの持続を区別できるようにしています。これは、1つのチャネル表示動作を維持する上で重要な機能です。
初期化関数では、実務レベルのリソース管理手法が示されています。起動時に既存のチャートオブジェクトをクリーンアップすることで、チャート画面の乱雑化を防止しています。一方、初期化解除関数は最小限の介入の原則に従い、描画済みチャネルをユーザー分析のためにそのまま残します。必要に応じて自動クリーンアップをおこなうオプションもコメントで示されており、自動化とユーザー制御のバランスを考慮した設計となっています。
datetime lastBarTime = 0; string currentChannelName = ""; string channelPrefix = "SmartCh_"; bool channelFound = false; ENUM_CHANNEL_TYPE currentChannelType = CHANNEL_NONE; datetime lastAlertTime = 0; int channelStartBar = -1; int channelEndBar = -1; int OnInit() { DeleteAllChannels(); Print("Smart Single Channel EA initialized"); return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { // Optional: Delete channel on exit // DeleteAllChannels(); }
セクション3:インテリジェントなイベント駆動型処理
OnTick()関数は、応答性と計算効率のバランスを考慮した最適化されたイベント駆動型アーキテクチャを実装しています。この関数ではiTime()を用いた新しいバー検出機構を確立しており、意味のある価格変動が発生した場合のみ処理をおこなうことで、価格の横ばい時の無駄な計算を防止しています。さらに、革新的なスロットリングロジックにより、チャネルの確認を3バーごとにおこなう制御された処理頻度が導入されています。この設計により、アラートの頻度を大幅に減らしつつ、チャネル検出のタイミングは逃しません。
このスロットリング手法は、テスト時に確認された連続アラート問題に直接対応しており、EAを単なる過剰反応の騒がしいシステムから、意味のあるチャネル形成が発生したときのみ信号を提供する実務向け取引ツールへと変えています。コア処理をFindAndDrawSingleChannel()に委譲するモジュラーアーキテクチャは、単一責任の原則に従っており、イベント処理とビジネスロジックの実装を明確に分離しています。
void OnTick() { datetime currentBarTime = iTime(_Symbol, _Period, 0); if(currentBarTime <= lastBarTime) return; lastBarTime = currentBarTime; int barShift = iBarShift(_Symbol, _Period, currentBarTime); if(barShift % 3 != 0) return; FindAndDrawSingleChannel(); }
セクション4:コアチャネル検出および検証ロジック
FindAndDrawSingleChannel()関数は、EAの高度な意思決定エンジンを実装しており、複数の検出アルゴリズムを統合するとともに、洗練された検証基準を適用します。この関数は、上昇チャネルと下降チャネルの両方を並列で評価するアプローチを示しており、ユーザー設定に基づく条件付き実行を可能にしています。このアーキテクチャにより、包括的な市場分析をおこないつつ、無効化された検出経路の早期終了によって計算効率を維持することができます。
チャネル選択ロジックは、複数基準による意思決定アルゴリズムを実装しており、新しさ(直近性)と検証の強度の両方を考慮します。スコアリングシステムは、現在価格との近接性、検証の堅牢性、最小タッチ数の充足の3つの要素に基づきチャネルの優先度を決定します。この多次元評価により、高品質で十分に検証されたチャネルのみが表示優先度を得ることが保証されます。
新しいチャネル検出ロジックでは、高度な状態比較を導入しており、チャネルの存在、タイプの変更、構造の変化、時間的クールダウンの4つの検証基準に基づいて冗長なアラートを防ぎます。この包括的アプローチにより、連続アラート問題を解消しつつ、真に意味のある市場構造の変化に対して敏感に反応できます。
void FindAndDrawSingleChannel() { bool risingFound = false; int risePoint1 = -1, risePoint2 = -1; double riseSlope = 0; int riseTouchCount = 0; if(EnableRisingChannels) risingFound = FindRisingChannel(risePoint1, risePoint2, riseSlope, riseTouchCount); bool fallingFound = false; int fallPoint1 = -1, fallPoint2 = -1; double fallSlope = 0; int fallTouchCount = 0; if(EnableFallingChannels) fallingFound = FindFallingChannel(fallPoint1, fallPoint2, fallSlope, fallTouchCount); bool drawChannel = false; int point1 = -1, point2 = -1; double slope = 0; ENUM_CHANNEL_TYPE newType = CHANNEL_NONE; int touchCount = 0; if(risingFound && fallingFound) { if(risePoint1 > fallPoint1 || riseTouchCount > fallTouchCount + 1) { drawChannel = true; point1 = risePoint1; point2 = risePoint2; slope = riseSlope; newType = CHANNEL_RISING; touchCount = riseTouchCount; } else { drawChannel = true; point1 = fallPoint1; point2 = fallPoint2; slope = fallSlope; newType = CHANNEL_FALLING; touchCount = fallTouchCount; } } else if(risingFound && riseTouchCount >= MinTouchesPerLine * 2) { drawChannel = true; point1 = risePoint1; point2 = risePoint2; slope = riseSlope; newType = CHANNEL_RISING; touchCount = riseTouchCount; } else if(fallingFound && fallTouchCount >= MinTouchesPerLine * 2) { drawChannel = true; point1 = fallPoint1; point2 = fallPoint2; slope = fallSlope; newType = CHANNEL_FALLING; touchCount = fallTouchCount; } if(drawChannel && touchCount >= MinTouchesPerLine * 2) { bool isNewChannel = (!channelFound) || (currentChannelType != newType) || (MathAbs(point1 - channelStartBar) > 10) || (TimeCurrent() - lastAlertTime > 3600); if(isNewChannel) { DeleteAllChannels(); if(DrawChannel(point1, point2, slope, newType)) { channelFound = true; currentChannelType = newType; channelStartBar = point1; channelEndBar = point2; if(AlertOnNewChannel && (TimeCurrent() - lastAlertTime > 3600)) { string typeStr = (newType == CHANNEL_RISING) ? "Rising (Sell Setup)" : "Falling (Buy Setup)"; string message = StringFormat("%s channel detected on %s %s. %d touches confirmed.", typeStr, Symbol(), PeriodToString(_Period), touchCount); Alert(message); lastAlertTime = TimeCurrent(); } } } } else { if(channelFound && !IsChannelStillValid()) { DeleteAllChannels(); channelFound = false; currentChannelType = CHANNEL_NONE; } } }
セクション5:タッチ検証を伴う高度なチャネル検出
FindRisingChannel()およびFindFallingChannel()関数は、単純なスイングポイント検出を超えた高度なパターン認識アルゴリズムを実装しています。これらの関数は、ルックバック期間内のすべてのスイングポイント組み合わせを評価する二重ループ構造を採用しており、複数の検証フィルターを適用することで、本物のチャネル構造を識別します。
検証基準は、市場マイクロストラクチャへの実務レベルの注意力を示しており、チャネルの傾き方向が正しいことと、チャネルの長さ要件を通じて最小限の構造的整合性を確保しています。傾き検証により、意味のあるトレンド情報を欠くほぼ水平な構造の検出が防止されます。
CountChannelTouches()によって実装された革新的なタッチ検証システムは、従来のチャネル検出器に対するEAの最も重要な進歩を表しています。チャネルの両境界に対する複数回の価格接触を要求することで、検出されたチャネルが単なる数学的アーティファクトではなく、市場の実際の動きによってテストされ検証されたことを保証します。スコアリングアルゴリズムは、直近性、タッチの頻度、チャネルの高さを組み合わせて複合スコアを算出し、最も重要なチャネル構造を客観的に特定します。
bool FindRisingChannel(int &point1, int &point2, double &slope, int &touchCount) { int swingLows[]; FindSwingLows(swingLows, SwingStrength, LookbackBars); if(ArraySize(swingLows) < 2) return false; int bestPoint1 = -1, bestPoint2 = -1; double bestScore = -1; int bestTouches = 0; for(int i = 0; i < ArraySize(swingLows) - 1; i++) { for(int j = i + 1; j < ArraySize(swingLows); j++) { int low1 = swingLows[i]; int low2 = swingLows[j]; if(iLow(NULL, 0, low1) <= iLow(NULL, 0, low2)) continue; if(MathAbs(low1 - low2) < MinChannelLengthBars) continue; double low1Price = iLow(NULL, 0, low1); double low2Price = iLow(NULL, 0, low2); double barDiff = MathAbs(low1 - low2); slope = (low1Price - low2Price) / barDiff; if(slope < 0.00005) continue; double channelHeight = CalculateChannelHeight(low1, low2, true); int touches = CountChannelTouches(low1, low2, slope, low2Price, channelHeight, true); double recencyScore = 100.0 - (low1 * 100.0 / LookbackBars); double touchScore = touches * 25.0; double heightScore = (channelHeight / SymbolInfoDouble(_Symbol, SYMBOL_POINT)) / 100.0; double totalScore = recencyScore + touchScore + heightScore; if(totalScore > bestScore && touches >= MinTouchesPerLine * 2) { bestScore = totalScore; bestPoint1 = low1; bestPoint2 = low2; bestTouches = touches; } } } if(bestScore > 0) { point1 = bestPoint1; point2 = bestPoint2; slope = (iLow(NULL, 0, bestPoint1) - iLow(NULL, 0, bestPoint2)) / MathAbs(bestPoint1 - bestPoint2); touchCount = bestTouches; return true; } return false; }
セクション6:チャネル幾何学の数学的基礎
CalculateChannelHeight()およびCountChannelTouches()関数は、価格データを検証済みチャネル構造に変換する数学的コアを実装しています。これらの関数は、金融時系列におけるチャネル検出の幾何学的課題に対応する高度なアルゴリズム思考を示しています。
CalculateChannelHeight()は二重の目的を持つアルゴリズムを実装しており、計算された基準線からの最大価格乖離を測定することで経験的な高さ計算をおこなうと同時に、パーセンテージベースの閾値を用いて最小構造要件を適用します。このアプローチにより、経験的観察と理論的要件がバランスされ、取引に意味のあるチャネルサイズが保証されます。
CountChannelTouches()は現実の価格挙動において正確なラインタッチが稀であることを考慮した許容範囲ベースの検証を導入しています。この許容範囲の計算は、銘柄固有のスケーリングに実務レベルの配慮を示し、異なる通貨ペアやピップ値でも一貫した挙動を確保します。二重ループ構造により、チャネル両境界でのタッチを個別に検証し、チャネルの整合性に関する詳細な診断情報を提供します。
double CalculateChannelHeight(int point1, int point2, bool isRising) { double maxHeight = 0; int startBar = MathMin(point1, point2); int endBar = MathMax(point1, point2); double price1 = (isRising) ? iLow(NULL, 0, point1) : iHigh(NULL, 0, point1); double price2 = (isRising) ? iLow(NULL, 0, point2) : iHigh(NULL, 0, point2); double slope = (price1 - price2) / (point1 - point2); double intercept = price1 - slope * point1; for(int bar = startBar; bar <= endBar; bar++) { double currentPrice = (isRising) ? iHigh(NULL, 0, bar) : iLow(NULL, 0, bar); double baseLinePrice = slope * bar + intercept; double deviation = MathAbs(currentPrice - baseLinePrice); if(deviation > maxHeight) maxHeight = deviation; } double minHeight = (isRising) ? iLow(NULL, 0, startBar) * MinChannelHeightPct / 100.0 : iHigh(NULL, 0, startBar) * MinChannelHeightPct / 100.0; return MathMax(maxHeight, minHeight); } int CountChannelTouches(int point1, int point2, double slope, double basePrice, double height, bool isRising) { int touches = 0; int startBar = MathMin(point1, point2); int endBar = MathMax(point1, point2); double tolerance = TouchTolerancePips * SymbolInfoDouble(_Symbol, SYMBOL_POINT) * 10; for(int bar = startBar; bar <= endBar; bar++) { double currentPrice = (isRising) ? iLow(NULL, 0, bar) : iHigh(NULL, 0, bar); double baseLinePrice = slope * (bar - point2) + basePrice; if(MathAbs(currentPrice - baseLinePrice) <= tolerance) touches++; } for(int bar = startBar; bar <= endBar; bar++) { double currentPrice = (isRising) ? iHigh(NULL, 0, bar) : iLow(NULL, 0, bar); double parallelLinePrice = slope * (bar - point2) + basePrice + (isRising ? height : -height); if(MathAbs(currentPrice - parallelLinePrice) <= tolerance) touches++; } return touches; }
セクション7:スイングポイント検出エンジン
FindSwingLows()およびFindSwingHighs()関数は、左右の価格動作を同時に検証する対称的検証アプローチを用いた堅牢なスイングポイント検出を実装しています。これらの関数は、チャネル検出の基礎層を形成しており、信頼性の高い構造分析を確保するために慎重な実装が求められます。
両関数は、比較演算子を逆にした同一のアルゴリズムパターンを使用しており、コード再利用と論理の明確さを両立しています。検証ループは厳密な「ピークと谷」検出アルゴリズムを実装しており、スイングポイントは左右両側の指定バー数より高い(または低い)必要があり、SwingStrengthパラメータによって制御されます。この二方向の確認により、検出されたスイングポイントが一時的な変動ではなく、真の局所極値であることが保証されます。
配列管理パターンは、MQL5における実務レベルのメモリ管理を示しており、有効なスイングポイントが検出されるたびに配列を動的に拡張します。最後におこなわれるArraySort()操作により、最新のバーから古いバーへの時系列順に整列され、チャネル検出アルゴリズムでの効率的な後続処理を可能にします。
void FindSwingLows(int &swingPoints[], int strength, int lookback) { ArrayResize(swingPoints, 0); for(int i = strength; i < MathMin(lookback, Bars(NULL, 0) - strength); i++) { bool isSwingLow = true; double currentLow = iLow(NULL, 0, i); for(int left = 1; left <= strength && isSwingLow; left++) if(iLow(NULL, 0, i - left) < currentLow) isSwingLow = false; if(isSwingLow) for(int right = 1; right <= strength && isSwingLow; right++) if(iLow(NULL, 0, i + right) < currentLow) isSwingLow = false; if(isSwingLow) { int size = ArraySize(swingPoints); ArrayResize(swingPoints, size + 1); swingPoints[size] = i; } } ArraySort(swingPoints); }
セクション8:制御付き拡張による専門的なチャネル可視化
DrawChannel()関数は、高度なグラフィック表現を実装し、知的な拡張管理を組み込むことで、テストで指摘されたチャネルの過剰描画問題に直接対応しています。この関数は、視認性とチャート空間の管理のバランスを考慮した構造化されたチャネル描画アプローチを採用しています。
この関数は、数学的なチャネルパラメータを視覚要素に変換する専門的な座標計算を示しています。拡張ロジックでは、境界管理が制御されており、ExtendLeftは過去のコンテキストを提供し、ExtendRightは意図的に無効化されています。これは、チャネルが「現在価格から過剰に遠くまで描画される」というテストフィードバックへの直接的な対応です。
チャネル描画の実装では、MT5の組み込みチャネルオブジェクトではなく、基準線と平行線を個別のトレンドラインオブジェクトとして使用しています。この設計判断により、視覚プロパティと拡張挙動の細かな制御が可能になります。ラベル配置のロジックは、チャネル境界に対して適切な価格水準に説明文を配置することで、視覚的階層への配慮を示しています。
bool DrawChannel(int point1, int point2, double slope, ENUM_CHANNEL_TYPE type) { if(!ShowChannel) return false; DeleteAllChannels(); datetime time1 = iTime(NULL, 0, point1); datetime time2 = iTime(NULL, 0, point2); double price1, price2; color channelColor; string channelLabel; bool isRising = (type == CHANNEL_RISING); if(isRising) { price1 = iLow(NULL, 0, point1); price2 = iLow(NULL, 0, point2); channelColor = RisingChannelColor; channelLabel = "Rising Channel (Sell)"; } else { price1 = iHigh(NULL, 0, point1); price2 = iHigh(NULL, 0, point2); channelColor = FallingChannelColor; channelLabel = "Falling Channel (Buy)"; } double channelHeight = CalculateChannelHeight(point1, point2, isRising); int extensionBars = MathMin(MaxExtensionBars, MathAbs(point1 - point2) / 2); datetime extendedTime1 = time1; datetime extendedTime2 = time2; if(ExtendLeft) { int extendBack = MathMin(extensionBars, point2); extendedTime2 = iTime(NULL, 0, point2 - extendBack); } if(ExtendRight) { int extendForward = MathMin(extensionBars, Bars(NULL, 0) - point1 - 1); extendedTime1 = iTime(NULL, 0, point1 + extendForward); } currentChannelName = channelPrefix + "Base"; ObjectCreate(0, currentChannelName, OBJ_TREND, 0, extendedTime2, price2, time1, price1); ObjectSetInteger(0, currentChannelName, OBJPROP_COLOR, channelColor); ObjectSetInteger(0, currentChannelName, OBJPROP_WIDTH, ChannelWidth); ObjectSetInteger(0, currentChannelName, OBJPROP_RAY_RIGHT, false); ObjectSetInteger(0, currentChannelName, OBJPROP_RAY_LEFT, ExtendLeft); ObjectSetInteger(0, currentChannelName, OBJPROP_BACK, true); string parallelName = channelPrefix + "Parallel"; double parallelPrice1 = price1 + (isRising ? channelHeight : -channelHeight); double parallelPrice2 = price2 + (isRising ? channelHeight : -channelHeight); ObjectCreate(0, parallelName, OBJ_TREND, 0, extendedTime2, parallelPrice2, time1, parallelPrice1); ObjectSetInteger(0, parallelName, OBJPROP_COLOR, channelColor); ObjectSetInteger(0, parallelName, OBJPROP_WIDTH, ChannelWidth); ObjectSetInteger(0, parallelName, OBJPROP_STYLE, STYLE_DASH); ObjectSetInteger(0, parallelName, OBJPROP_RAY_RIGHT, false); ObjectSetInteger(0, parallelName, OBJPROP_RAY_LEFT, ExtendLeft); ObjectSetInteger(0, parallelName, OBJPROP_BACK, true); string labelName = channelPrefix + "Label"; ObjectCreate(0, labelName, OBJ_TEXT, 0, time1, isRising ? price1 + channelHeight * 1.1 : price1 - channelHeight * 1.1); ObjectSetString(0, labelName, OBJPROP_TEXT, channelLabel); ObjectSetInteger(0, labelName, OBJPROP_COLOR, channelColor); ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8); ObjectSetInteger(0, labelName, OBJPROP_BACK, true); return true; }
セクション9:チャネル持続性の検証とリソース管理
IsChannelStillValid()およびDeleteAllChannels()関数は、EAが長時間の市場セッションでも安定して動作するための重要なシステムメンテナンスおよび検証ロジックを実装しています。これらの関数は、チャネルの時間的妥当性とリソース管理という2つの主要な運用上の懸念に対応します。
IsChannelStillValid()は、簡略化されたが効果的なチャネルブレイク検出アルゴリズムを実装しており、最近の価格変動を監視して、確立されたチャネル境界からの重大な逸脱を検出します。この関数はパーセンテージベースの閾値を使用して潜在的なチャネルブレイクを識別し、感度と堅牢性の実用的なバランスを提供します。保守的なアプローチにより、通常の価格変動による誤ったチャネル無効化を減らしつつ、真の構造的ブレイクへの応答性を維持します。
DeleteAllChannels()は、EAの名前接頭辞を持つチャートオブジェクトを体系的に巡回して削除することで、プロフェッショナルなオブジェクト管理を実現しています。逆反復パターンにより、反復中のオブジェクト削除が安全におこなえるようになっており、コレクションを巡回しながら変更する際の重要な詳細をカバーしています。接頭辞ベースのフィルタリングにより、他のチャートオブジェクトへの干渉を防ぎ、複数ツール併用環境への配慮が示されています。
bool IsChannelStillValid() { if(!channelFound) return false; int recentBars = 10; bool isRising = (currentChannelType == CHANNEL_RISING); for(int i = 0; i < recentBars; i++) { double high = iHigh(NULL, 0, i); double low = iLow(NULL, 0, i); if(isRising) { if(low > iLow(NULL, 0, channelStartBar) * 1.01) return false; } else { if(high < iHigh(NULL, 0, channelStartBar) * 0.99) return false; } } return true; } void DeleteAllChannels() { int total = ObjectsTotal(0); for(int i = total - 1; i >= 0; i--) { string name = ObjectName(0, i); if(StringFind(name, channelPrefix) == 0) ObjectDelete(0, name); } currentChannelName = ""; }
テスト
テストは、指標をライブチャート上に直接展開し、その動作を観察する形でおこなわれました。システム構造は、RSIオシレーターを専用のサブウィンドウに分離しつつ、メイン価格チャートと指標ウィンドウの両方でシグナルを明確に表示することに成功しています。
ダイバージェンスアラートを受け取ると、トレーダーは即座に構造分析をおこない、サポートとなるチャネル形成を特定できます。これにより、手動取引のためのマルチファクター意思決定フレームワークを構築することが可能です。このアプローチは、自動的なパターン検出と裁量による構造検証を組み合わせています。
包括的な入力パラメータシステムにより、ユーザーは検出アルゴリズムを最適化したり、アラート閾値を調整したり、表示を変更して各自の取引手法や市場状況に合わせたりすることができます。

RSIDivergenceDetectorの展開
以下のスクリーンショットは、ストラテジーテスターでテスト中のEquidistant Channel Auto-Placement EAを示しており、チャネル構造を正確に識別して描画できていることを確認できます。RSIダイバージェンス検出器と組み合わせることで、取引シグナルをこれらの構造的形成と照合でき、マルチ確認フレームワークとして信頼性の高い取引判断をサポートします。

EquidistantChannel自動配置EAをストラテジーテスターでテストする
結論
RSIダイバージェンスシグナルと確立された市場構造、特にチャネル境界とのコンフルエンスは、高確率の取引セットアップを特定するための強力なフレームワークを生み出します。本記事では、RSIダイバージェンスパターンの検出と、インテリジェントな等間隔チャネルの自動配置という、2つの補完的なテクニカル分析コンポーネントの自動化に成功しました。
これらのツールは同じチャート上で相互干渉することなく連携して動作します。RSIダイバージェンス検出器はカスタム指標として機能し、Equidistant Channel Auto-PlacementはEAとして動作します。モジュラー開発アプローチにより、各コンポーネントを焦点を絞って実装してテストしつつ、責務の明確な分離を維持することができました。
現在の独立モジュールは即時の有用性を提供しますが、自然な次のステップは、これらの機能を統合し、自動取引ロジックと組み合わせた統一型取引システムを開発することです。将来の発展に向けた非常に魅力的な展望です。
詳細なコード解説と実装手順は、プロフェッショナルなMQL5プログラミング、取引システムアーキテクチャ、アルゴリズム検証手法に関する実践的な洞察を提供しています。両プロジェクトの完全なソースファイルは、さらなる学習やカスタマイズのために添付資料で提供されています。
コメント欄では、引き続き議論や質問、建設的なフィードバックを歓迎します。
重要な学び
| 重要な学び | 説明 |
|---|---|
| 1. モジュラーシステムアーキテクチャ | 複雑なシステムを独立してテスト可能なモジュール(インジケーター+EA)に分割し、後で統合できるようにする。 |
| 2. アラート制御のための状態管理 | 時間ベースのアラート制御(lastAlertTime)を実装し、連続アラートや通知疲れを防ぐ。 |
| 3. 検証優先の設計パターン | パターンをシグナルする前に、複数の価格タッチや確認ブレイクを要求し、信頼性を優先する。 |
| 4. スマートなオブジェクトライフサイクル管理 | 一意の名前接頭辞と体系的なクリーンアップ(OnDeinit)を使用して、チャート上のオブジェクトの蓄積を防ぐ。 |
| 5.パフォーマンス最適化されたイベント処理 | 新規バー検出と処理のスロットリングを実装し、応答性と計算効率のバランスを取る。 |
添付ファイル
| ソースファイル名 | バージョン | 説明 |
|---|---|---|
| RSIDivergenceDetector.mq5 | 1.00 | カスタムRSIオシレーター。レギュラー/隠れダイバージェンスを検出し、ピボット値を保存。メインチャートに買い/売り矢印を明確に表示し、アラートやRSIブレイク確認を設定可能。 |
| EquidistantChannelAuto-Placement.mq5 | 1.00 | インテリジェントなEA。単一の有効な等間隔チャネルを自動検出して描画し、タッチ検証、制御された延長、スマートなアラート制御を備え、上昇(売り)および下降(買い)セットアップに対応します。 |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/20554
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
MQL5 MVCパラダイムにおけるテーブルのビューおよびコントローラーコンポーネント:サイズ変更可能な要素
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
機械学習を用いたトレンド取引戦略の開発
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索