English Deutsch
preview
プライスアクション分析ツールキットの開発(第50回):MQL5でのRVGI、CCI、SMA Confluenceエンジンの開発

プライスアクション分析ツールキットの開発(第50回):MQL5でのRVGI、CCI、SMA Confluenceエンジンの開発

MetaTrader 5 |
147 0
Christian Benjamin
Christian Benjamin

内容


はじめに

市場が買われすぎや売られすぎの状態に達したタイミングを見極めることは、取引において最も難しい課題の一つです。熟練したトレーダーでさえ、強いトレンドのピークと反転の直前を正確に特定するのは簡単ではありません。チャート分析に何時間も費やし、トレンドの継続を期待していたにもかかわらず、モメンタムがすでに変化していたことに気づくのが遅すぎることも多々あります。特にプライスアクションを学んでいる段階のトレーダーにとって、この不確実性は混乱や感情的な判断につながり、規律ある取引を妨げます。私自身、長年のチャート分析でこのパターンを何度も観察してきました。これはスキル不足ではなく、市場は完璧にバランスの取れた動きをするわけではないからです。市場は騙しシグナル、急激なスパイク、誤解を招くプルバックを発生させ、最も規律あるトレーダーさえも罠にかけます。この現実が、連載「プライスアクション分析ツールキットの開発」を作るきっかけとなりました。このプロジェクトは、市場の解釈を簡素化し、感情的な介入を減らし、チャート分析に秩序をもたらすことを目的としています。

当初からの目標は、厳密かつ実用的なものでした。価格の動きを、テスト可能で自動化でき、一貫して適用できる測定可能な要素に分解することです。真の反転を特定する、ブレイクアウトを確認する、弱いモメンタムをフィルタリングする、といった課題は、すべて体系的なルールを作るための機会になります。そのルールはコードで再現可能であり、実際のマーケット環境でも堅牢でなければなりません。トレーダーが直面する共通の難しさは開発プロセスにも現れ、その共通の経験が継続的な改善を促します。現代では、自動化が明確なアドバンテージを提供します。MQL5は、観察結果を決定論的ロジックに翻訳する手段を与えてくれます。インジケーターハンドル、バッファ読み取り、バー終値のチェックは疲れ知らずで実行されます。よく設計されたエキスパートアドバイザー(EA)は、客観的分析をおこない規律を維持し、トレーダーは実行やリスク管理に集中できるようになります。

今回紹介するのは、不確実な市場に明確さをもたらす分析セットアップです。この戦略は、RVGI、CCI(14)、SMA(30)の3要素を統合しています。これらはそれぞれ異なる役割を持ちます。RVGIは平滑化されたモメンタムシグナルを提供し、CCIは買われすぎと売られすぎの極値を検出し、SMAは構造的なコンテキストとトレンドバイアスを示します。これらの要素が揃うと、システムは方向性を確認し、反転を検証し、早期ブレイクアウトの可能性を特定する単一の一貫したシグナルを生成します。

記事の残りでは、意思決定フローとMQL5での実装について説明します。概念からコードへの明確な対応関係を示し、インジケーターハンドル、シグナルロジック、可視化、OnInitOnTickなどのEAライフサイクルを網羅します。目標は単なるインジケーターの提供ではなく、価格を知的に読み、ノイズを減らし、取引の自信を高める構造化された意思決定エンジンを作ることです。このツールキットは、長年の観察を抽出し、リアルな取引環境でのプライスアクション分析を簡素化・改善するための適応型システムとしてまとめています。



戦略ロジックと設計フレームワーク

すべての分析ツールは明確な目的から始まります。本システムの主目的は、トレンドのモメンタムが衰え始め、反転の兆しが現れる重要な局面(エネルギーの枯渇と転換点)を特定することです。これを実現するには、市場の微妙なエネルギー変化、方向性、タイミングを捉えるフレームワークが必要です。そのために、戦略は、モメンタム認識(RVGI)、市場状況測定(CCI)、トレンドフィルタリング(SMA)という、3つの異なるが相互に連携した要素を統合しています。これらを組み合わせることで、市場のリズムを読み取り、解釈する同期メカニズムが形成され、エネルギー状態、過熱度、方向性バイアスを包括的に把握できます。

1. モメンタム認識:RVGIの役割 

RVGI (Relative Vigor Index)は、バーの終値がレンジ内のどの位置で閉じたかを比較し、その関係を時間軸で平滑化することで、価格変動の持続的な力を測定します。上昇トレンドでは終値が高値付近に集まり、RVGIは上昇します。下降トレンドでは終値が安値付近に集まり、RVGIは下降します。この戦略では、RVGIは最初の確認層、つまりモメンタムの鼓動として機能し、市場のエネルギーが反転を支持しているかを示します。RVGIのメインラインがシグナルラインをきれいにクロスすることは、モメンタム方向の変化を示すシグナルとなります。バー確定時のクロスのみを使用することで、バー内部のノイズを回避しています。たとえば価格が新高値を更新しているのにRVGIが下落している場合は、現在の動きが弱まっている警告となり、CCIやSMAによる確認に備えたセットアップが整います。このように、RVGI単独では取引を発動せず、モメンタムの変化を検証する役割を果たし、CCIとSMAで判断される反転の信頼性を高めます。

2. 市場状況測定:CCIの機能

期間14の商品チャネル指数(CCI, Commodity Channel Index)は、価格が統計的平均からどれだけ離れているかを測定します。典型価格と移動平均を平均偏差でスケーリングして比較することで、価格が通常レンジから大きく乖離している局面を明確に示します。CCIは、買われすぎと売られすぎの極端状態を把握する警告灯として機能します。+100以上は買われすぎ、−100以下は売られすぎを示します。これらの極端な値自体が即時反転を保証するわけではありませんが、モメンタムが反転すれば反転が起こりやすいゾーンを示します。

タイミングと確認の観点では、CCIは単独トリガーではなく状況確認のために使用します。「市場は反対方向の動きを考慮すべきほど十分に動いたか?」という問いを立てるイメージです。RVGIなどのモメンタムツールと組み合わせることで、CCIは文脈を提供します。RVGIモメンタムが衰える中でCCIが+100付近の正値を示す場合は天井形成の可能性が高まります。深い負のCCIで下向きモメンタムが弱まる場合は反発のセットアップを示唆します。実際の運用では、このシステムはCCIを「エネルギー枯渇のゲート」として扱います。有効な強気反転を認めるには、CCIが−100に到達またはそれを下回った後、再び−100を上抜けする必要があります。有効な弱気反転の場合は、CCIが+100に到達またはそれを上回った後、再び+100を下抜けする必要があります。この「極端値からの回帰」チェックにより、価格がまだ極端値付近にある状態でのエントリーを防ぎ、シグナルの質を高めます。

この戦略では、CCIを単独で使用することはありません。RVGIのモメンタムとSMAで定義されたコンテキストと整合している必要があります。これらを組み合わせることで、SMAが構造的バイアスを定義し、RVGIがモメンタム変化を確認し、CCIが価格の極端値の枯渇を確認するという、3層の確認が成立します。この二重確認(モメンタムの反転+CCIの極端値からの回帰)により、騙しシグナルを大幅に減らし、プロットされる各反転シグナルの信頼性を向上させます。

bool CheckReversalSignal(int shift, bool &isBuy)
{
   // defensive
   if(iBars(_Symbol,_Period) <= shift + InpCCI_Period + InpRVI_Smooth + 4) return(false);

   // CCI read
   double cciNow = cci_at(shift, InpCCI_Period);
   double cciPrev = cci_at(shift + 1, InpCCI_Period);

   // RVI read
   double rviMainNow = rvi_main_at(shift);
   double rviMainPrev = rvi_main_at(shift + 1);
   double rviSigNow = rvi_signal_at(shift);
   double rviSigPrev = rvi_signal_at(shift + 1);

   // SMA & price
   double smaVal = /* read or compute SMA */;
   double price = EA_Close(shift);
   bool priceAboveSMA = (price > smaVal);
   bool priceBelowSMA = (price < smaVal);

   // flags
   bool cciBuy = (cciPrev <= CCI_LOWER) && (cciNow > CCI_LOWER);
   bool cciSell = (cciPrev >= CCI_UPPER) && (cciNow < CCI_UPPER);
   bool rviUp = (rviMainPrev <= rviSigPrev) && (rviMainNow > rviSigNow);
   bool rviDown = (rviMainPrev >= rviSigPrev) && (rviMainNow < rviSigNow);

   // final combination (mean-reversion style)
   if(priceBelowSMA && cciBuy && rviUp)  { isBuy = true; return(true); }
   if(priceAboveSMA && cciSell && rviDown){ isBuy = false; return(true); }
   return(false);
}

3. トレンドフィルタリング:SMA(30)による指針

30期間の単純移動平均(SMA)は、この分析フレームワークの構造的バックボーンとして機能します。市場のノイズを平滑化し、価格の基礎的な方向性を明らかにします。より広い視点では、SMAは動的な均衡ラインとして機能します。価格が一貫してSMAの上にある場合、市場は強気環境と見なされ、逆にSMAの下で継続的に取引される場合は弱気環境を示します。SMAは単純ながらも、トレンドコンテキストの基準点として強力です。

トレンド定義に加え、SMA(30)はタイミングと確認にも重要な役割を果たします。トレンド中、価格が平均から大きく乖離すると、モメンタムが過剰である可能性を示す枯渇ゾーンとして機能します。価格がSMAに戻る動きは、調整や潜在的な反転の始まりを示すことが多く、このSMA周りの自然な周期が、RVGIとCCIによるより深い分析の土台となります。

戦略的には、SMAを用いることで、市場の動きを拡張フェーズと反転フェーズという2つの実行可能な状態に分類できます。拡張フェーズでは、価格がSMAと整合しモメンタムとともに動いている場合、逆トレンド取引は避け、エネルギー枯渇の確認を待ちます。反転フェーズでは、価格がSMAの上下に大きく乖離し、モメンタムが衰え始めると、反転シグナルの準備をおこないます。現在の設定では、SMA(30)がこれらの重要な境界を定義します。価格がSMAの上にある場合は弱気反転を監視し、CCIが+100を上抜け、RVGIが下降クロスしたときに確認します。逆に価格がSMAの下にある場合は強気反転を予測し、CCIが−100以下まで下がり、RVGIが上方クロスしたときに確認します。

これにより、三層確認モデルが構築されます。

  • SMA(30)が構造的コンテキストと価格バイアスを定義する
  • CCI(14)が過熱・過冷却の市場極値を特定する
  • RVGIがクロスによるモメンタム変化を確認する

これらを組み合わせることで、各シグナルは構造、モメンタム、エネルギー枯渇のバランスに基づいて生成されます。SMAは単なる移動平均線ではなく、システム全体の軸となり、タイミング、方向、確信度の同期を助けます。RVGIとCCIと組み合わせることで、通常は静的なトレンドフィルタを、市場リズムに適応し、反転検出精度を向上させる動的意思決定フレームワークに変えています。

統合シグナルの形成

このシステムの真価は、3つの要素の同期にあります。シグナルは、モメンタム(RVGI)、市場状況(CCI)、トレンド(SMA)の3層すべてが整合したときのみ有効と見なされます。

  • 強気のセットアップ:RVGIが上方クロス、CCIが売られすぎレベルから上昇、価格がSMA(30)の下にある場合

  • 弱気のセットアップ:RVGIが下向きにクロス、CCIが買われすぎレベルから下降、価格がSMA(30)の上にある場合

この三重確認ロジックにより、ほとんどの騙しシグナルが排除され、実際に市場のエネルギーが収束するポイントだけに焦点を当てます。量より質を重視し、頻繁で信頼性の低いエントリーではなく、一貫性のある高確率取引を目指します。この構造化されたアプローチにより、トレーダーは市場ダイナミクスを正確に解釈でき、感情的な反応を減らし、体系的な意思決定を促進できます。



MQL5での実装

基本フレームワークの設定

まず、EAの基本構造を構築します。ヘッダコメントセクションを設け、著作権情報、バージョン番号、関連リソースへのリンクを明記します。これにより、コードを整理された状態に保ち、プロフェッショナルな構成を維持できます。次に、#propertyディレクティブ(#property copyright、#property link、#property versionなど)を使用して、MetaTraderがスクリプトを正しく認識できるようメタデータを定義します。この構造を設定することで、コードは十分にドキュメント化され、共有や後のメンテナンスが容易になります。

//+------------------------------------------------------------------+
//|                                       RVGI_CCI_SMA_Panel_EA.mq5  |
//|                             Copyright 2025, Christian Benjamin.  |
//|                                            https://www.mql5.com  |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

ユーザー入力の定義

次に、ユーザー入力パラメータを追加して、システムを柔軟かつ適応可能にします。これらの入力は非常に重要で、コードを書き換えることなくシステムを調整できるようにします。たとえば、InpSMA_Periodは、単純移動平均(SMA)が何本のバーを考慮するかを設定するもので、トレンド検出に影響します。同様に、InpCCI_Periodは、買われすぎ・売られすぎを検出するCommodity Channel Indexのルックバック期間を制御します。InpRVI_Smoothは、RVGIの平均化に使用するバー数を設定し、市場ノイズを平滑化してモメンタムをより正確に反映します。

また、ATRに関するパラメータも用意しています。InpATR_PeriodはATR計算に使用するバー数を定義し、InpATR_MultiplierはATRをスケーリングしてストップロスやテイクプロフィットの設定に使用します。さらに、InpUseATRStopのようなトグルを使うことで、ATRベースでストップを設定するか、スイングの高値と安値を基準にするかを選択できます。これらの入力により、システムの感度やリスク管理を自在に調整でき、異なる銘柄や時間足に最適化することが可能になります。

// User inputs
input int     InpSMA_Period        = 30;      // SMA period
input int     InpCCI_Period        = 14;      // CCI period
input int     InpRVI_Smooth        = 4;       // RVI smoothing
input int     InpATR_Period        = 14;      // ATR period
input double  InpATR_Multiplier    = 1.5;     // ATR multiplier for stops
input bool    InpUseATRStop        = true;    // Use ATR for stops
input int     InpSL_SwingBars      = 10;      // Swing lookback
input double  InpTarget1_ATR       = 1.0;     // Target 1 ATR multiple
input double  InpTarget2_ATR       = 2.0;     // Target 2 ATR multiple
input int     InpSignalLookback    = 1;       // Bars to look back
input int     InpCheckIntervalMs   = 500;     // Check interval in ms

インジケーターハンドルと変数の初期化

入力パラメータを設定した後、インジケーターハンドルを保持する内部変数を宣言します。たとえば、SMA用にはhSMA、ATR用にはhATRです。これらはまだ作成されていないことを示すために、初期値としてINVALID_HANDLEを設定します。その後、OnInit()関数内で、iMA()(SMA用)やiATR()(ATR用)といった関数を使用して、実際のインジケーターハンドルを作成します。

// Declare handles
int hSMA = INVALID_HANDLE;
int hATR = INVALID_HANDLE;

// Inside OnInit()
hSMA = iMA(_Symbol, _Period, InpSMA_Period, 0, MODE_SMA, PRICE_CLOSE);
hATR = iATR(_Symbol, _Period, InpATR_Period);

この設定により、各ティックごとにインジケーターを手動で再計算する必要がなくなり、インジケーターバッファに効率的にアクセスできるため、パフォーマンスが向上します。また、lastSignalledShift(最後にシグナルを出したバーを追跡)、lastCheckMs(市場条件を評価する頻度の管理)、lastSignalText、lastSignalTimeといった変数も初期化します。これらの変数は、重複シグナルの防止や評価頻度の制御に役立ち、システムの応答性を維持しつつCPU負荷を抑えることができます。

// Internal variables
int lastSignalledShift = -1;
uint lastCheckMs = 0;
string lastSignalText = "none";
datetime lastSignalTime = 0;

ヘルパー関数の作成によるコードの明確化

コードを整理し、管理しやすく、再利用可能にするため、ヘルパー関数を作成します。たとえば、make_object_name()は接頭辞とタイムスタンプを連結してチャートオブジェクトのユニーク名を生成します。複数のグラフィカルオブジェクトを作成する場合に、上書きされないようにするために必須です。同様に、format_time_dt()はdatetime値を人間が読みやすい文字列に変換し、ラベルやコメントを理解しやすくします。さらに、EA_Close()、EA_Open()、EA_High()、 EA_Low()、EA_Time()のようなラッパー関数を作成します。これらは内部で標準のiClose()やiOpen()などを呼び出しますが、パラメータ指定を簡略化しています。このラッパーにより、価格データへのアクセスがコード全体で一貫して簡単になり、エラーが減り可読性が向上します。

// Generate unique object names
string make_object_name(const string prefix, datetime t)
{
    return prefix + IntegerToString((int)t);
}

// Format time for display
string format_time_dt(const datetime t)
{
    return(TimeToString(t, TIME_DATE | TIME_SECONDS));
}

// Wrappers for data retrieval
double EA_Close(int shift) { return(iClose(_Symbol, _Period, shift)); }
double EA_Open(int shift)  { return(iOpen(_Symbol, _Period, shift)); }
double EA_High(int shift)  { return(iHigh(_Symbol, _Period, shift)); }
double EA_Low(int shift)   { return(iLow(_Symbol, _Period, shift)); }
datetime EA_Time(int shift) { return(iTime(_Symbol, _Period, shift)); }

RVGIインジケーターの計算

ここから、コアのモメンタムインジケーターであるRVGIの実装に焦点を当てます。まず、rvi_raw_at_shift()関数を使用して、直近4本のバーの終値と始値の加重和を計算します。この関数は、中央のバーにより大きな重みを与えることで、最近の市場活動を強調し、現在のモメンタムを捉えやすくしています。生値を正規化するため、各バーの高値-安値の値幅で割ることで、指標をスケール非依存にしています。さらに、分母が小さすぎる場合はゼロを返すチェックを入れることで、ゼロ除算を防ぎ安定性を維持します。

// Raw RVGI calculation
double rvi_raw_at_shift(int shift)
{
    if(iBars(_Symbol, _Period) <= shift + 3)
        return 0.0;
        
    double num0 = EA_Close(shift) - EA_Open(shift);
    double num1 = EA_Close(shift + 1) - EA_Open(shift + 1);
    double num2 = EA_Close(shift + 2) - EA_Open(shift + 2);
    double num3 = EA_Close(shift + 3) - EA_Open(shift + 3);
    double num = num0 + 2.0 * num1 + 2.0 * num2 + num3;

    double den0 = EA_High(shift) - EA_Low(shift);
    double den1 = EA_High(shift + 1) - EA_Low(shift + 1);
    double den2 = EA_High(shift + 2) - EA_Low(shift + 2);
    double den3 = EA_High(shift + 3) - EA_Low(shift + 3);
    double den = den0 + 2.0 * den1 + 2.0 * den2 + den3;

    if(MathAbs(den) < DBL_EPSILON)
        return 0.0;

    return num / den;
}

次に、rvi_main_at()を作成します。これは、生のRVGI値を指定したバー数(通常は4本)で平滑化し、市場ノイズを低減してより信頼性の高いモメンタムラインを生成します。

// Smoothed RVGI (main line)
double rvi_main_at(int shift)
{
    int n = MathMax(1, InpRVI_Smooth);
    double sum = 0.0;
    for(int i=0; i<n; i++)
    {
        sum += rvi_raw_at_shift(shift + i);
    }
    return sum / n;
}

これは、平滑化ウィンドウ内の生値を平均化することでおこないます。その後、rvi_signal_at()がこの処理をさらに進め、メインのRVGIラインを再度平滑化してシグナルラインを生成します。シグナルラインは、モメンタムの変化を示すクロスオーバーを検出するために不可欠です。これらの関数を組み合わせることで、堅牢かつ応答性の高いモメンタムインジケーターが構築され、潜在的な反転を早期に特定するのに役立ちます。

// Signal line (smoothed RVGI)
double rvi_signal_at(int shift)
{
    int n = MathMax(1, InpRVI_Smooth);
    double sum = 0.0;
    for(int i=0; i<n; i++)
    {
        sum += rvi_main_at(shift + i);
    }
    return sum / n;
}

CCI計算の実装

次に、CCIを手動で計算するcci_at()関数を実装します。まず、指定されたルックバック期間内の各バーについて、以下の式で典型価格を計算します。この典型価格を期間分合計し、その平均値を求めます。次に、各典型価格が平均からどれだけ離れているかを表す 平均偏差を計算します。現在の典型価格がこの平均からどれだけ離れているかを適切にスケーリングすることで、CCIの値を求めます。この手動計算により、インジケーターの精度と一貫性を確保でき、パラメータの調整も容易になります。CCIは、市場がどちらの方向にも過度に伸びているかを検出するのに役立ち、エントリーやイグジットのタイミングを判断する上で重要な指標となります。

double cci_at(int shift, int period)
{
    int totalBars = iBars(_Symbol, _Period);
    if(totalBars <= shift + period - 1)
        return 0.0; // Not enough bars

    double sumTP = 0.0;
    for(int k=0; k<period; k++)
    {
        int idx = shift + k;
        double tp = (EA_High(idx) + EA_Low(idx) + EA_Close(idx)) / 3.0;
        sumTP += tp;
    }
    double smaTP = sumTP / period;

    double meanDev = 0.0;
    for(int k=0; k<period; k++)
    {
        int idx = shift + k;
        double tp = (EA_High(idx) + EA_Low(idx) + EA_Close(idx)) / 3.0;
        meanDev += MathAbs(tp - smaTP);
    }
    meanDev /= period;

    if(meanDev < DBL_EPSILON)
        return 0.0;

    double tp_current = (EA_High(shift) + EA_Low(shift) + EA_Close(shift)) / 3.0;
    double cci = (tp_current - smaTP) / (0.015 * meanDev);
    return cci;
}

ATRデータへのアクセス

次に、get_atr_current()を実装します。この関数は、ATRハンドルhATRを使用して最新のATR値を取得します。CopyBuffer()を用いて直近のATRを読み取り、もしハンドルが無効、またはデータが取得できない場合はゼロを返します。この値は重要で、現在の市場ボラティリティに応じた適応型のストップロスやテイクプロフィットレベルを設定することが可能になります。ATRを使用することで、損切りや利確のレベルが狭すぎたり広すぎたりすることを防ぎ、適切なリスクリワードバランスを維持できます。

double get_atr_current()
{
    if(hATR == INVALID_HANDLE)
        return 0.0;

    double tmp[];
    if(CopyBuffer(hATR, 0, 0, 1, tmp) <= 0)
        return 0.0;

    return tmp[0];
}

取引レベルの構築

すべてのインジケーターデータが揃ったら、Levels構造体を定義し、建値、ストップロス、2つのテイクプロフィットターゲットに加え、取引方向やタイムスタンプなどの追加情報を保持します。build_levels()関数は、これらのレベルを動的に計算します。まず、エントリーは現在の終値に設定されます。ATRベースのストップが有効な場合は、ATRにユーザー定義の倍率を掛けてストップロスおよびTPレベルを算出します。ATRが利用できない場合や無効の場合は、ルックバック期間内のスイングの安値と高値を検索してストップレベルを設定し、安全のためにバッファを加えます。この方法により、取引レベルは現在の市場環境に適応し、リスクと潜在的リワードのバランスを維持することができます。

struct Levels
{
    double entry;
    double stop;
    double tp1;
    double tp2;
    datetime time;
    bool isBuy;
};

Levels build_levels(bool isBuy, int shift)
{
    Levels L;
    L.entry = EA_Close(shift);
    L.time = EA_Time(shift);
    L.isBuy = isBuy;

    double atr = get_atr_current();
    if(atr <= 0.0)
        atr = SymbolInfoDouble(_Symbol, SYMBOL_POINT) * 10;

    if(InpUseATRStop)
    {
        L.stop = isBuy ? L.entry - atr * InpATR_Multiplier : L.entry + atr * InpATR_Multiplier;
    }
    else
    {
        // Swing low/high search
        double extremePrice = isBuy ? EA_Low(shift) : EA_High(shift);
        for(int i=1; i<=InpSL_SwingBars; i++)
        {
            double v = isBuy ? EA_Low(shift + i) : EA_High(shift + i);
            if(v == WRONG_VALUE)
                continue;
            if(isBuy && v < extremePrice)
                extremePrice = v;
            if(!isBuy && v > extremePrice)
                extremePrice = v;
        }
        // Add buffer
        double buffer = SymbolInfoDouble(_Symbol, SYMBOL_POINT) * 5;
        L.stop = isBuy ? extremePrice - buffer : extremePrice + buffer;
    }

    // Targets
    L.tp1 = isBuy ? L.entry + atr * InpTarget1_ATR : L.entry - atr * InpTarget1_ATR;
    L.tp2 = isBuy ? L.entry + atr * InpTarget2_ATR : L.entry - atr * InpTarget2_ATR;

    return L;
}

シグナルとレベルの可視化

シグナルを明確に伝えるため、draw_signal_objects()を実装します。この関数はチャート上にグラフィカル要素を作成します。エントリーポイントを示す矢印、ストップロスおよびTPレベルを示す水平線、正確な価格を表示するラベルです。各オブジェクトには、接頭辞とタイムスタンプを組み合わせた一意の名前を付け、複数のシグナルが生成されても衝突しないようにします。色、線の太さ、矢印の形状もカスタマイズして見やすさを向上させます。オブジェクト作成後はChartRedraw()を呼び出し、チャートを即座に更新します。この視覚的フィードバックにより、シグナルやレベルを一目で確認でき、手動管理も容易になります。

void draw_signal_objects(const Levels &L)
{
    string baseName = make_object_name("RVGI_SIG_", L.time);

    // Draw arrow
    string arrowName = baseName + (L.isBuy ? "_BUY_ARR" : "_SELL_ARR");
    if(ObjectFind(0, arrowName) >= 0)
        ObjectDelete(0, arrowName);
    ObjectCreate(0, arrowName, OBJ_ARROW, 0, L.time, L.entry);
    ObjectSetInteger(0, arrowName, OBJPROP_ARROWCODE, L.isBuy ? 233 : 234);
    ObjectSetInteger(0, arrowName, OBJPROP_COLOR, L.isBuy ? InpArrowUpColor : InpArrowDownColor);
    ObjectSetInteger(0, arrowName, OBJPROP_WIDTH, 2);
    ObjectSetInteger(0, arrowName, OBJPROP_BACK, true);

    // Stop line
    string slName = baseName + "_SL";
    if(ObjectFind(0, slName) >= 0)
        ObjectDelete(0, slName);
    ObjectCreate(0, slName, OBJ_HLINE, 0, 0, L.stop);
    ObjectSetDouble(0, slName, OBJPROP_PRICE, L.stop);
    ObjectSetInteger(0, slName, OBJPROP_COLOR, InpSL_Color);
    ObjectSetString(0, slName, OBJPROP_TEXT, "SL: " + DoubleToString(L.stop, _Digits));

    // TP1 line
    string tp1Name = baseName + "_TP1";
    if(ObjectFind(0, tp1Name) >= 0)
        ObjectDelete(0, tp1Name);
    ObjectCreate(0, tp1Name, OBJ_HLINE, 0, 0, L.tp1);
    ObjectSetDouble(0, tp1Name, OBJPROP_PRICE, L.tp1);
    ObjectSetInteger(0, tp1Name, OBJPROP_COLOR, InpTP_Color);
    ObjectSetString(0, tp1Name, OBJPROP_TEXT, "TP1: " + DoubleToString(L.tp1, _Digits));

    // TP2 line
    string tp2Name = baseName + "_TP2";
    if(ObjectFind(0, tp2Name) >= 0)
        ObjectDelete(0, tp2Name);
    ObjectCreate(0, tp2Name, OBJ_HLINE, 0, 0, L.tp2);
    ObjectSetDouble(0, tp2Name, OBJPROP_PRICE, L.tp2);
    ObjectSetInteger(0, tp2Name, OBJPROP_COLOR, InpTP_Color);
    ObjectSetString(0, tp2Name, OBJPROP_TEXT, "TP2: " + DoubleToString(L.tp2, _Digits));

    ChartRedraw();
}

リアルタイムデータパネルの作成

さらに、draw_panel()を追加して、チャート上で素早く状況を把握できるようにします。この関数は、SMA、RVGI、CCI、ATRなどの現在のインジケーター値を取得します。インジケーターハンドルが有効であればバッファから、無効であれば手動計算で取得します。取得したデータは、見やすい文字列に整形してComment()で表示します。このパネルにより、市場状況やシステムの状態を即座に把握でき、別画面に切り替えることなく判断や進行中のパフォーマンス監視が可能になります。

void draw_panel()
{
    string txt = "RVGI + CCI + SMA Panel\n";

    // SMA
    double smaValue = 0;
    if(hSMA != INVALID_HANDLE)
    {
        double tmp[];
        if(CopyBuffer(hSMA, 0, 0, 1, tmp) > 0)
            smaValue = tmp[0];
        else
            smaValue = 0; // fallback
    }
    else
    {
        // manual calculation fallback
        double sum=0;
        int cnt=0;
        for(int i=0; i<InpSMA_Period; i++)
        {
            if(iBars(_Symbol, _Period) <= i)
                break;
            sum += EA_Close(i);
            cnt++;
        }
        if(cnt>0)
            smaValue = sum / cnt;
    }
    txt += "SMA(" + IntegerToString(InpSMA_Period) + "): " + DoubleToString(smaValue, _Digits) + "\n";

    // RVGI & CCI
    double rviM = rvi_main_at(1);
    double rviS = rvi_signal_at(1);
    double cciV = cci_at(1, InpCCI_Period);
    txt += "RVI: " + DoubleToString(rviM,5) + " sig: " + DoubleToString(rviS,5) + "\n";
    txt += "CCI: " + DoubleToString(cciV,2) + "\n";

    // ATR
    double atr = get_atr_current();
    txt += "ATR: " + DoubleToString(atr, _Digits) + "\n";

    // Last signal info
    txt += "Last signal: " + lastSignalText;
    if(lastSignalTime != 0)
        txt += " " + TimeToString(lastSignalTime, TIME_DATE | TIME_SECONDS);

    Comment(txt);
}

市場状況の評価とシグナル生成

コアの意思決定エンジンは、check_for_signals()に実装されています。GetTickCount()を使ったスロットリングを導入し、評価の頻度を制御することでCPU負荷の過剰発生を防ぎます。まず、ルックバック設定に基づいてシフトを決定し、分析に十分なバーが読み込まれているかを確認します。その後、インジケーターハンドルが有効であればバッファから、無効であれば再計算して常に最新の値を取得します。

void check_for_signals()
{
    uint now = (uint)GetTickCount();
    if(lastCheckMs != 0 && (now - lastCheckMs) < (uint)InpCheckIntervalMs)
        return; // Throttle

    lastCheckMs = now;
    int shift = InpSignalLookback;

    // Verify enough bars loaded
    if(iBars(_Symbol, _Period) <= shift + 6)
        return;

    // Retrieve SMA
    double smaVal = 0;
    if(hSMA != INVALID_HANDLE)
    {
        double tmp[];
        if(CopyBuffer(hSMA, 0, shift, 1, tmp) > 0)
            smaVal = tmp[0];
    }
    else
    {
        // fallback manual SMA
        double sum=0;
        int cnt=0;
        for(int i=shift; i<shift + InpSMA_Period; i++)
        {
            if(iBars(_Symbol, _Period) <= i)
                break;
            sum += EA_Close(i);
            cnt++;
        }
        if(cnt > 0)
            smaVal = sum / cnt;
        else
            return; // Not enough data
    }

    // Get indicator values
    double rviMainNow = rvi_main_at(shift);
    double rviMainPrev = rvi_main_at(shift + 1);
    double rviSigNow = rvi_signal_at(shift);
    double rviSigPrev = rvi_signal_at(shift + 1);
    double cciNow = cci_at(shift, InpCCI_Period);
    double cciPrev = cci_at(shift + 1, InpCCI_Period);
    double price = EA_Close(shift);

    // Trend and momentum analysis
    bool priceAboveSMA = (price > smaVal);
    bool priceBelowSMA = (price < smaVal);
    bool rviCrossUp = (rviMainPrev <= rviSigPrev) && (rviMainNow > rviSigNow);
    bool rviCrossDown = (rviMainPrev >= rviSigPrev) && (rviMainNow < rviSigNow);
    bool cciOverbought = (cciNow >= 100);
    bool cciOversold = (cciNow <= -100);

    // Generate signals based on conditions
    if(priceAboveSMA && cciOversold && rviCrossUp)
    {
        // Buy signal
        if(shift != lastSignalledShift)
        {
            Levels L = build_levels(true, shift);
            draw_signal_objects(L);
            lastSignalledShift = shift;
            lastSignalText = "BUY @ " + DoubleToString(price, _Digits);
            lastSignalTime = EA_Time(shift);
            // Send alerts if enabled...
        }
    }
    else if(priceBelowSMA && cciOverbought && rviCrossDown)
    {
        // Sell signal
        if(shift != lastSignalledShift)
        {
            Levels L = build_levels(false, shift);
            draw_signal_objects(L);
            lastSignalledShift = shift;
            lastSignalText = "SELL @ " + DoubleToString(price, _Digits);
            lastSignalTime = EA_Time(shift);
            // Send alerts if enabled...
        }
    }

    // Update panel info
    draw_panel();
}

次に、インジケーターシグナルを解析します。RVGIがシグナルラインを上抜けまたは下抜けしているか(モメンタムの変化を示す)を確認し、CCIが買われすぎまたは売られすぎゾーンにあるかを評価します。また、現在の価格をSMAと比較してトレンドを判断します。これらの条件がすべて揃った場合(例えば、RVGIが上向きクロス、CCIが売られすぎ、価格がSMAの下にある場合)、買いシグナルを生成します。逆の条件では売りシグナルを発動します。重複シグナルを避けるため、最後にシグナルを出したバーを追跡します。新しいシグナルが確認されると、取引レベルを生成し、チャートに可視化し、内部変数を更新します。有効になっている場合はアラートや通知も送信し、状況を常に把握できるようにします。

EAライフサイクル管理とリソースのクリーンアップ

最後に、OnInit()内でSMAとATRのインジケーターハンドルを作成し、エラーをチェックしつつ変数を初期化します。また、情報パネルを描画して、ステータスを素早く確認できるようにします。

// In OnInit()
int OnInit()
{
    hSMA = iMA(_Symbol, _Period, InpSMA_Period, 0, MODE_SMA, PRICE_CLOSE);
    hATR = iATR(_Symbol, _Period, InpATR_Period);
    // Initialize variables
    lastSignalledShift = -1;
    lastCheckMs = 0;
    lastSignalText = "none";
    lastSignalTime = 0;

    draw_panel();
    Print("EA initialized");
    return(INIT_SUCCEEDED);
}

OnDeinit()では、IndicatorRelease()を使ってインジケーターハンドルを解放し、リソースを適切に開放します。OnTick()では、システムを常時稼働させるために単純にcheck_for_signals()を呼び出します。この構造により、EAは効率的で応答性が高く、運用中も安定して管理された状態を維持できます。

// In OnDeinit()
void OnDeinit(const int reason)
{
    if(hSMA != INVALID_HANDLE)
        IndicatorRelease(hSMA);
    if(hATR != INVALID_HANDLE)
        IndicatorRelease(hATR);
}

// In OnTick()
void OnTick()
{
    check_for_signals();
}



テストと結果

システム実装後、実際の市場環境での有効性を評価するため、徹底的なテストを実施しました。結果から、統合されたインジケーターとレベルが信頼できるエントリーシグナルを生成し、チャート上で視覚的に確認できることが明確になり、透明性と検証の容易さが保証されました。

視覚的確認とシグナル精度

最初に確認できた成果は、チャート上での正確なシグナル表示です。RVGI、CCI、SMAの条件がロジック通りに揃った瞬間に、買いと売りの矢印が正確に生成されました。たとえば、RVGIが上向きクロス、CCIが売られすぎ領域に入り、価格がSMAの下にある場合、緑色の上矢印が表示され、強力な買いシグナルを示します。これらのシグナルには、エントリー、ストップロス、テイクプロフィットのレベルも明確にマークされ、システムの分析を瞬時に確認できます。

買いシグナル:GBPUSD M1

売りシグナル:Volatility 75 (1s) Index M1


インジケーターデータの妥当性

次に注目すべき成果は、シグナル時のインジケーター値の整合性です。詳細データを見ると、RVGIは上昇中でモメンタム増加を確認しています。CCIは売られすぎ領域(−100以下)にあり、市場が下方に過剰拡張して反転準備が整っていることを示します。同時に価格はSMAの下にあり、強気のセットアップに一致しています。この多層確認により、シグナルが堅実なテクニカル根拠に基づくことが確認でき、偽シグナルの可能性を低減しました。

取引執行と収益性

最も重要な成果は、指定レベルでの取引が正確に実行されたことです。注文は視覚的なエントリーポイントで正確に発動し、ストップロスとテイクプロフィットはATRまたはスイング高値・安値に基づき正確に設定されました。取引は期待通りに動作し、市場が有利に動いた場合には効率的に利益を獲得しました。テスト期間中の例では、高確率エントリーの特定と取引管理の有効性が確認でき、収益性のある取引につながりました。

総合的な有効性

  • 統合インジケーター(RVGI、CCI、SMA)が一貫して信頼できるエントリーを提供し、視覚的にも確認可能
  • ストップおよびターゲット用に生成されたレベルは適切で、最適なリスクリワード管理が可能
  • システムの視覚およびデータ出力により、手動での検証や調整も容易で透明性が高い

これらの成果に基づき、特定の市場や時間足に最適化するために、インジケーター期間、ATR倍率、シグナル条件などのパラメータをさらに微調整することを推奨します。継続的なテストと検証により、精度向上、偽シグナルの削減、全体的な収益性の改善が期待できます。



結論

このシステムは、取引における最も難しい課題の一つである反転の信頼性の高い特定に明確さをもたらします。トレンド、モメンタム、エネルギー枯渇という3つの独立した確認条件を要求することで、価格構造、方向性エネルギー、市場の疲労が収束する瞬間を特定します。その結果、シグナルは少なくなりますが、それぞれが明確なテクニカル根拠を持ち、チャート上で測定可能な価値を示すものとなります。EAはそのまま使える万能ソリューションではなく、分析エンジンとして扱うべきです。セットアップを視覚化し、シグナルを記録し、アイデアを体系的にテストするために使用します。閉じたバー(closed-bar)ロジックで再現可能なバックテストをおこない、各シグナルを基礎となる市場構造に照らしてログし、パラメータを変更する前にアウトオブサンプルのパフォーマンスを確認してください。この規律により、カーブフィッティングを防ぎ、結果の信頼性を保持できます。

リスクは意図的に管理します。EAが提示するストップ(スイングベースまたはATRスケーリング)を基準ルールとして使用し、資本を守るためにポジションサイズを調整します。部分的な利確やトレーリングストップを用い、時間足やリスク許容度に応じて運用してください。将来的に実行モジュールを構築する場合でも、元のシグナルルールは維持し、オーダーロジックは同じ確認条件とリスクガードレールに従うよう設計してください。

最後に、このツールキットは観察から行動への橋渡しとして捉えます。プライスアクションを再現可能なルールに変換し、感情によるノイズを減らし、規律ある意思決定のための明確なフレームワークを提供します。これを用いて判断力を磨き、効果的な手法を記録し、研究用の分析エンジンから信頼できる取引ワークフローの一部へとシステムを進化させてください。

注意事項:完全なソースコードは本稿末尾に添付されています。クリックしてダウンロードし、MetaEditorで開いてコンパイルしてください。十分にテストし、取引条件や好みに応じてパラメータを調整してください。

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

添付されたファイル |
取引戦略の開発:擬似ピアソン相関アプローチ 取引戦略の開発:擬似ピアソン相関アプローチ
既存のインジケーターから新しいインジケーターを生成することは、取引分析を強化するための非常に強力な方法です。既存のインジケーターの出力を統合する数学的関数を定義することで、トレーダーは複数のシグナルを1つの効率的なツールにまとめたハイブリッドインジケーターを作成できます。本記事では、ピアソン相関関数を改良した「擬似ピアソン相関(PPC, Pseudo Pearson Correlation)」を用いて、3つのオシレーターから構築された新しいインジケーターを紹介します。PPCインジケーターは、オシレーター同士の動的な関係を数値化し、それを実践的な取引戦略に応用することを目的としています。
MQL5とデータ処理パッケージの統合(第6回):市場フィードバックとモデル適応の融合 MQL5とデータ処理パッケージの統合(第6回):市場フィードバックとモデル適応の融合
ライブ取引結果、ボラティリティの変化、流動性の変化といったリアルタイムの市場フィードバックを、適応型モデル学習とどのように統合するかに焦点を当てます。これにより、応答性が高く、自己改善を継続する取引システムを維持することを目指します。
MQL5でのAI搭載取引システムの構築(第6回):チャットの削除と検索機能の導入 MQL5でのAI搭載取引システムの構築(第6回):チャットの削除と検索機能の導入
連載第6回では、ChatGPT統合型エキスパートアドバイザー(EA)をさらに進化させ、サイドバーのインタラクティブな削除ボタン、大・小の履歴ポップアップ、新しい検索ポップアップを導入することで、トレーダーが永続的な会話履歴を効率的に管理および整理できるようにしました。これにより、チャートデータからのAI駆動のシグナルを維持しつつ、暗号化されたストレージに会話を安全に保存できます。
MQL5でのAI搭載取引システムの構築(第5回):チャットポップアップを備えた折りたたみ可能なサイドバーの追加 MQL5でのAI搭載取引システムの構築(第5回):チャットポップアップを備えた折りたたみ可能なサイドバーの追加
連載第5回では、ChatGPT統合型エキスパートアドバイザー(EA)に折りたたみ可能なサイドバーを追加し、ナビゲーションを改善します。これにより、大小の履歴ポップアップからチャットをスムーズに選択できるようになり、従来の複数行入力処理、暗号化されたチャットの保存機能、チャートデータからのAIによる取引シグナル生成も維持されます。