English Deutsch
preview
プライスアクション分析ツールキットの開発(第13回):RSIセンチネルツール

プライスアクション分析ツールキットの開発(第13回):RSIセンチネルツール

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

内容


はじめに

ダイバージェンスとは、モメンタムやオシレーターといったテクニカル指標の動きが価格の動きと乖離する現象を指す、テクニカル分析の概念です。基本的に、価格が新たな高値または安値をつけたにもかかわらず指標が同じ動きを示さないとき、それはトレンドの弱まりを示しており、反転やモメンタムの変化を予兆する可能性があります。RSIダイバージェンスは、潜在的な市場の反転を見つけるためのシンプルな方法です。価格が一方向に動いているのに対し、RSIが逆方向に動いている場合、それはトレンドの変化を示している可能性があります。しかし、これらのシグナルを手作業でチャートから探すのは時間がかかり、ミスも起こりやすくなります。そこで、自動化が役に立ちます。

この記事では、RSIダイバージェンスシグナルを自動的に検出するMQL5のエキスパートアドバイザー(EA)を構築します。このEAは、これらのシグナルをチャート上に明確な矢印で表示し、簡単な要約を添えることで、現在の状況をすぐに把握できるようにします。初心者でも熟練トレーダーでも、このツールを使えば、取引前に検証可能な取引機会を見つけることができ、手作業で何時間も分析する必要がなくなります。それでは、このRSI Divergence EAがどのように取引プロセスを簡素化できるかを見ていきましょう。


戦略の概要

RSIダイバージェンスの理解

RSIダイバージェンスは、RSIの動きが資産価格と異なる方向に進んだときに発生し、価格モメンタムの変化を示唆します。RSIと価格の動きの食い違いは、市場の反転やトレンドの継続を予測するために、トレーダーが注目する重要なシグナルです。通常、RSIは価格の勢いに追随し、現在のトレンドを確認する役割を果たします。しかし、ダイバージェンスが現れると、そのダイバージェンス自体が、価格とRSIの間に矛盾を示します。この矛盾はしばしば大きな値動きの前触れとなります。こうしたシグナルを早期に察知することは、エントリーやエグジットのタイミングを見極める上で非常に重要です。

RSIダイバージェンスには、主に次の2種類があります。

1. 通常のRSIダイバージェンス

通常のRSIダイバージェンスは、一般的にトレンド反転のシグナルとされます。通常のRSIダイバージェンスは、現在のトレンドの勢いが弱まり、反転の可能性が高まっていることを示します。

  • 通常の強気RSIダイバージェンス

価格がより低い安値を形成する一方で、RSIがより高い安値を形成する場合に発生します。これは、価格が下落しているにもかかわらず、モメンタムはすでに上向きに転じている可能性を示しており、上昇トレンドへの反転を示唆するものです。

強気のダイバージェンス

図1:強気ダイバージェンス

  • 通常の弱気RSIダイバージェンス

価格がより高い高値を形成する一方で、RSIがより低い高値を形成する場合に発生します。価格は上昇しているものの、RSIが示す勢いの弱まりが、近い将来の下落を示唆しています。

売りシグナル

図2:弱気ダイバージェンス

2. 隠れたRSIダイバージェンス

隠れたRSIダイバージェンスは、反転の予兆ではなく、トレンドの継続を示すシグナルとして解釈されます。RSIと価格が一時的に乖離していても、現在のトレンドにまだ勢いがあることを示すシグナルです。

  • 隠れた強気RSIダイバージェンス:上昇トレンドにおいて、価格がより高い安値を形成し、RSIがより低い安値を形成する場合、調整は一時的なものであり、上昇トレンドが継続する可能性が高いことを示しています。

隠れた強気

図3:隠れた強気ダイバージェンス

  • 隠れた弱気RSIダイバージェンス:下降トレンドにおいて、価格がより低い高値を形成し、RSIがより高い高値を形成する場合、下降トレンドの勢いが継続していることを示し、下落が続く可能性が高いことを示唆しています。

弱気ダイバージェンス

図4:隠れた弱気ダイバージェンス

以下は、RSIダイバージェンスのタイプ間の主な違いをまとめた表です。

RSIダイバージェンスタイプ プライスアクション RSIアクション シグナルの種類 期待
通常の強気ダイバージェンス 安値更新(LL) 高値切り上げ(HL) 反転上昇シグナル 下落トレンドから上昇トレンドへ
通常の弱気ダイバージェンス 高値更新(HH)  高値切り下げ(LH) 反転下落シグナル 上昇トレンドから下落トレンドへ
隠れた強気ダイバージェンス 高値切り下げ(HL) 安値更新(LL) 上昇継続シグナル 上昇トレンド継続
隠れた弱気ダイバージェンス 安値切り上げ(LH) 高値更新(HH) 下落継続シグナル 下落トレンド継続

まとめとして、このEAは、定義された過去検証期間(ルックバック期間)における価格とRSIの両方のデータを継続的にスキャンし、その動きの不一致、すなわちRSIダイバージェンスを検出します。

以下がEAの主な動作内容です。

1. データの収集と準備

EAは直近のバーからRSI値と対応する価格データ(安値、高値、終値、時間)を取得します。これにより、分析は常に最新かつ完全な情報に基づいておこなわれます。

2. スイングポイントの特定

次に、価格とRSIデータの両方で局所的なスイング高値とスイング安値を判別します。これらのスイングポイントがダイバージェンス分析の基準点となります。
3.通常のダイバージェンスの検出

  • 通常の強気ダイバージェンス:価格がより低い安値を形成し、RSIがより高い安値を形成する場合、下降トレンドの勢いが弱まっており、上昇反転の可能性を示します。
  • 通常の弱気ダイバージェンス:価格がより高い高値を形成し、RSIがより低い高値を形成する場合、上昇トレンドの勢いが衰え、終焉に近づいていることを示します。

4. 隠れたダイバージェンスの検出
  • 隠れた強気ダイバージェンス:上昇トレンド中に価格がより高い安値を形成し、RSIがより低い安値を形成する場合、一時的な調整であり、上昇トレンドが継続していることを示します。
  • 隠れた弱気ダイバージェンス:下降トレンド中に価格がより低い高値を形成し、RSIがより高い高値を形成する場合、下降トレンドが継続していることを示します。

5. 視覚的およびログによるシグナル生成

ダイバージェンスが検出されると、通常・隠れ問わず、EAはチャート上に矢印やラベルを使って視覚的に表示し、さらにシグナルの詳細をログに記録して分析やバックテストに役立てます。詳細な処理については、以下の「コード解説」セクションをご覧ください。


MQL5コード

//+--------------------------------------------------------------------+
//|                                                RSI Divergence.mql5 |
//|                                 Copyright 2025, Christian Benjamin |
//|                                               https://www.mql5.com |
//+--------------------------------------------------------------------+
#property copyright "2025, MetaQuotes Software Corp."
#property link      "https://www.mql5.com/ja/users/lynnchris"
#property version   "1.0"
#property strict

//---- Input parameters
input int    InpRSIPeriod             = 14;    // RSI period
input int    InpSwingLeft             = 1;     // Bars to the left for swing detection (relaxed)
input int    InpSwingRight            = 1;     // Bars to the right for swing detection (relaxed)
input int    InpLookback              = 100;   // Number of bars to scan for divergence
input int    InpEvalBars              = 5;     // Bars after which to evaluate a signal
input int    InpMinBarsBetweenSignals = 1;     // Minimum bars between same-type signals (allows frequent re-entry)
input double InpArrowOffset           = 3.0;   // Arrow offset (in points) for display
input double InpMinSwingDiffPct       = 0.05;  // Lower minimum % difference to qualify as a swing
input double InpMinRSIDiff            = 1.0;   // Lower minimum difference in RSI between swing points

// Optional RSI threshold filter for bullish divergence (disabled by default)
input bool   InpUseRSIThreshold       = false; // If true, require earlier RSI swing to be oversold for bullish divergence
input double InpRSIOversold           = 30;    // RSI oversold level
input double InpRSIOverbought         = 70;    // RSI overbought level (if needed for bearish)

//---- Global variables
int      rsiHandle;         // Handle for the RSI indicator
double   rsiBuffer[];       // Buffer for RSI values
double   lowBuffer[];       // Buffer for low prices
double   highBuffer[];      // Buffer for high prices
double   closeBuffer[];     // Buffer for close prices
datetime timeBuffer[];       // Buffer for bar times
int      g_totalBars = 0;   // Number of bars in our copied arrays
datetime lastBarTime = 0;   // Time of last closed bar

//---- Structure to hold signal information
struct SignalInfo
  {
   string            type;         // e.g. "RegBearish Divergence", "HiddenBullish Divergence"
   int               barIndex;     // Bar index where the signal was generated
   datetime          signalTime;   // Time of the signal bar
   double            signalPrice;  // Price used for the signal (swing high for bearish, swing low for bullish)
  };

SignalInfo signals[];  // Global array to store signals

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   rsiHandle = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE);
   if(rsiHandle == INVALID_HANDLE)
     {
      Print("Error creating RSI handle");
      return(INIT_FAILED);
     }
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(rsiHandle != INVALID_HANDLE)
      IndicatorRelease(rsiHandle);
   EvaluateSignalsAndPrint();
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
// Process once per new closed candle (using bar1's time)
   datetime currentBarTime = iTime(_Symbol, _Period, 1);
   if(currentBarTime == lastBarTime)
      return;
   lastBarTime = currentBarTime;

//--- Copy RSI data
   ArrayResize(rsiBuffer, InpLookback);
   ArraySetAsSeries(rsiBuffer, true);
   if(CopyBuffer(rsiHandle, 0, 0, InpLookback, rsiBuffer) <= 0)
     {
      Print("Error copying RSI data");
      return;
     }

//--- Copy price and time data
   ArrayResize(lowBuffer, InpLookback);
   ArrayResize(highBuffer, InpLookback);
   ArrayResize(closeBuffer, InpLookback);
   ArraySetAsSeries(lowBuffer, true);
   ArraySetAsSeries(highBuffer, true);
   ArraySetAsSeries(closeBuffer, true);
   if(CopyLow(_Symbol, _Period, 0, InpLookback, lowBuffer) <= 0 ||
      CopyHigh(_Symbol, _Period, 0, InpLookback, highBuffer) <= 0 ||
      CopyClose(_Symbol, _Period, 0, InpLookback, closeBuffer) <= 0)
     {
      Print("Error copying price data");
      return;
     }

   ArrayResize(timeBuffer, InpLookback);
   ArraySetAsSeries(timeBuffer, true);
   if(CopyTime(_Symbol, _Period, 0, InpLookback, timeBuffer) <= 0)
     {
      Print("Error copying time data");
      return;
     }

   g_totalBars = InpLookback;

//--- Identify swing lows and swing highs
   int swingLows[];
   int swingHighs[];
   int startIndex = InpSwingLeft;
   int endIndex   = g_totalBars - InpSwingRight;
   for(int i = startIndex; i < endIndex; i++)
     {
      if(IsSignificantSwingLow(i, InpSwingLeft, InpSwingRight))
        {
         ArrayResize(swingLows, ArraySize(swingLows) + 1);
         swingLows[ArraySize(swingLows) - 1] = i;
        }
      if(IsSignificantSwingHigh(i, InpSwingLeft, InpSwingRight))
        {
         ArrayResize(swingHighs, ArraySize(swingHighs) + 1);
         swingHighs[ArraySize(swingHighs) - 1] = i;
        }
     }

//--- Bearish Divergence (using swing highs)
   if(ArraySize(swingHighs) >= 2)
     {
      ArraySort(swingHighs); // ascending order: index 0 is most recent
      int recent   = swingHighs[0];
      int previous = swingHighs[1];

      // Regular Bearish Divergence: Price makes a higher high while RSI makes a lower high
      if(highBuffer[recent] > highBuffer[previous] &&
         rsiBuffer[recent] < rsiBuffer[previous] &&
         (rsiBuffer[previous] - rsiBuffer[recent]) >= InpMinRSIDiff)
        {
         Print("Regular Bearish Divergence detected at bar ", recent);
         DisplaySignal("RegBearish Divergence", recent);
        }
      // Hidden Bearish Divergence: Price makes a lower high while RSI makes a higher high
      else
         if(highBuffer[recent] < highBuffer[previous] &&
            rsiBuffer[recent] > rsiBuffer[previous] &&
            (rsiBuffer[recent] - rsiBuffer[previous]) >= InpMinRSIDiff)
           {
            Print("Hidden Bearish Divergence detected at bar ", recent);
            DisplaySignal("HiddenBearish Divergence", recent);
           }
     }

//--- Bullish Divergence (using swing lows)
   if(ArraySize(swingLows) >= 2)
     {
      ArraySort(swingLows); // ascending order: index 0 is most recent
      int recent   = swingLows[0];
      int previous = swingLows[1];

      // Regular Bullish Divergence: Price makes a lower low while RSI makes a higher low
      if(lowBuffer[recent] < lowBuffer[previous] &&
         rsiBuffer[recent] > rsiBuffer[previous] &&
         (rsiBuffer[recent] - rsiBuffer[previous]) >= InpMinRSIDiff)
        {
         // Optionally require the earlier swing's RSI be oversold
         if(!InpUseRSIThreshold || rsiBuffer[previous] <= InpRSIOversold)
           {
            Print("Regular Bullish Divergence detected at bar ", recent);
            DisplaySignal("RegBullish Divergence", recent);
           }
        }
      // Hidden Bullish Divergence: Price makes a higher low while RSI makes a lower low
      else
         if(lowBuffer[recent] > lowBuffer[previous] &&
            rsiBuffer[recent] < rsiBuffer[previous] &&
            (rsiBuffer[previous] - rsiBuffer[recent]) >= InpMinRSIDiff)
           {
            Print("Hidden Bullish Divergence detected at bar ", recent);
            DisplaySignal("HiddenBullish Divergence", recent);
           }
     }
  }

//+------------------------------------------------------------------------+
//| IsSignificantSwingLow: Determines if the bar at 'index' is a swing low |
//+------------------------------------------------------------------------+
bool IsSignificantSwingLow(int index, int left, int right)
  {
   double currentLow = lowBuffer[index];
// Check left side for a local minimum condition
   for(int i = index - left; i < index; i++)
     {
      if(i < 0)
         continue;
      double pctDiff = MathAbs((lowBuffer[i] - currentLow) / currentLow) * 100.0;
      if(lowBuffer[i] < currentLow && pctDiff > InpMinSwingDiffPct)
         return false;
     }
// Check right side for a local minimum condition
   for(int i = index + 1; i <= index + right; i++)
     {
      if(i >= g_totalBars)
         break;
      double pctDiff = MathAbs((lowBuffer[i] - currentLow) / currentLow) * 100.0;
      if(lowBuffer[i] < currentLow && pctDiff > InpMinSwingDiffPct)
         return false;
     }
   return true;
  }

//+--------------------------------------------------------------------------+
//| IsSignificantSwingHigh: Determines if the bar at 'index' is a swing high |
//+--------------------------------------------------------------------------+
bool IsSignificantSwingHigh(int index, int left, int right)
  {
   double currentHigh = highBuffer[index];
// Check left side for a local maximum condition
   for(int i = index - left; i < index; i++)
     {
      if(i < 0)
         continue;
      double pctDiff = MathAbs((currentHigh - highBuffer[i]) / currentHigh) * 100.0;
      if(highBuffer[i] > currentHigh && pctDiff > InpMinSwingDiffPct)
         return false;
     }
// Check right side for a local maximum condition
   for(int i = index + 1; i <= index + right; i++)
     {
      if(i >= g_totalBars)
         break;
      double pctDiff = MathAbs((currentHigh - highBuffer[i]) / currentHigh) * 100.0;
      if(highBuffer[i] > currentHigh && pctDiff > InpMinSwingDiffPct)
         return false;
     }
   return true;
  }

//+------------------------------------------------------------------+
//| DisplaySignal: Draws an arrow on the chart and records the signal|
//+------------------------------------------------------------------+
void DisplaySignal(string signalText, int barIndex)
  {
// Prevent duplicate signals on the same bar (or too close)
   for(int i = 0; i < ArraySize(signals); i++)
     {
      if(StringFind(signals[i].type, signalText) != -1)
         if(MathAbs(signals[i].barIndex - barIndex) < InpMinBarsBetweenSignals)
            return;
     }

// Update a "LatestSignal" label for regular signals.
   if(StringFind(signalText, "Reg") != -1)
     {
      string labelName = "LatestSignal";
      if(ObjectFind(0, labelName) == -1)
        {
         if(!ObjectCreate(0, labelName, OBJ_LABEL, 0, 0, 0))
           {
            Print("Failed to create LatestSignal label");
            return;
           }
         ObjectSetInteger(0, labelName, OBJPROP_CORNER, 0);
         ObjectSetInteger(0, labelName, OBJPROP_XDISTANCE, 10);
         ObjectSetInteger(0, labelName, OBJPROP_YDISTANCE, 20);
         ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrWhite);
        }
      ObjectSetString(0, labelName, OBJPROP_TEXT, signalText);
     }

// Create an arrow object for the signal.
   string arrowName = "Arrow_" + signalText + "_" + IntegerToString(barIndex);
   if(ObjectFind(0, arrowName) < 0)
     {
      int arrowCode = 0;
      double arrowPrice = 0.0;
      color arrowColor = clrWhite;
      double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);

      if(StringFind(signalText, "Bullish") != -1)
        {
         arrowCode  = 233; // Wingdings up arrow
         arrowColor = clrLime;
         arrowPrice = lowBuffer[barIndex] - (InpArrowOffset * point);
        }
      else
         if(StringFind(signalText, "Bearish") != -1)
           {
            arrowCode  = 234; // Wingdings down arrow
            arrowColor = clrRed;
            arrowPrice = highBuffer[barIndex] + (InpArrowOffset * point);
           }

      if(!ObjectCreate(0, arrowName, OBJ_ARROW, 0, timeBuffer[barIndex], arrowPrice))
        {
         Print("Failed to create arrow object ", arrowName);
         return;
        }
      ObjectSetInteger(0, arrowName, OBJPROP_COLOR, arrowColor);
      ObjectSetInteger(0, arrowName, OBJPROP_ARROWCODE, arrowCode);
     }

// Record the signal for evaluation.
   SignalInfo sig;
   sig.type = signalText;
   sig.barIndex = barIndex;
   sig.signalTime = timeBuffer[barIndex];
   if(StringFind(signalText, "Bullish") != -1)
      sig.signalPrice = lowBuffer[barIndex];
   else
      sig.signalPrice = highBuffer[barIndex];
   ArrayResize(signals, ArraySize(signals) + 1);
   signals[ArraySize(signals) - 1] = sig;

   UpdateSignalCountLabel();
  }

//+------------------------------------------------------------------+
//| UpdateSignalCountLabel: Updates a label showing signal counts    |
//+------------------------------------------------------------------+
void UpdateSignalCountLabel()
  {
   int regCount = 0, hidCount = 0;
   for(int i = 0; i < ArraySize(signals); i++)
     {
      if(StringFind(signals[i].type, "Reg") != -1)
         regCount++;
      else
         if(StringFind(signals[i].type, "Hidden") != -1)
            hidCount++;
     }
   string countText = "Regular Signals: " + IntegerToString(regCount) +
                      "\nHidden Signals: " + IntegerToString(hidCount);
   string countLabel = "SignalCount";
   if(ObjectFind(0, countLabel) == -1)
     {
      if(!ObjectCreate(0, countLabel, OBJ_LABEL, 0, 0, 0))
        {
         Print("Failed to create SignalCount label");
         return;
        }
      ObjectSetInteger(0, countLabel, OBJPROP_CORNER, 0);
      ObjectSetInteger(0, countLabel, OBJPROP_XDISTANCE, 10);
      ObjectSetInteger(0, countLabel, OBJPROP_YDISTANCE, 40);
      ObjectSetInteger(0, countLabel, OBJPROP_COLOR, clrYellow);
     }
   ObjectSetString(0, countLabel, OBJPROP_TEXT, countText);
  }

//+--------------------------------------------------------------------+
//| EvaluateSignalsAndPrint: After backtesting, prints signal accuracy |
//+--------------------------------------------------------------------+
void EvaluateSignalsAndPrint()
  {
   double closeAll[];
   int totalBars = CopyClose(_Symbol, _Period, 0, WHOLE_ARRAY, closeAll);
   if(totalBars <= 0)
     {
      Print("Error copying complete close data for evaluation");
      return;
     }
   ArraySetAsSeries(closeAll, true);

   int totalEvaluated = 0, regTotal = 0, hidTotal = 0;
   int regEval = 0, hidEval = 0;
   int regCorrect = 0, hidCorrect = 0;

   for(int i = 0; i < ArraySize(signals); i++)
     {
      int evalIndex = signals[i].barIndex - InpEvalBars;
      if(evalIndex < 0)
         continue;
      double evalClose = closeAll[evalIndex];

      if(StringFind(signals[i].type, "Bullish") != -1)
        {
         if(StringFind(signals[i].type, "Reg") != -1)
           {
            regTotal++;
            regEval++;
            if(evalClose > signals[i].signalPrice)
               regCorrect++;
           }
         else
            if(StringFind(signals[i].type, "Hidden") != -1)
              {
               hidTotal++;
               hidEval++;
               if(evalClose > signals[i].signalPrice)
                  hidCorrect++;
              }
         totalEvaluated++;
        }
      else
         if(StringFind(signals[i].type, "Bearish") != -1)
           {
            if(StringFind(signals[i].type, "Reg") != -1)
              {
               regTotal++;
               regEval++;
               if(evalClose < signals[i].signalPrice)
                  regCorrect++;
              }
            else
               if(StringFind(signals[i].type, "Hidden") != -1)
                 {
                  hidTotal++;
                  hidEval++;
                  if(evalClose < signals[i].signalPrice)
                     hidCorrect++;
                 }
            totalEvaluated++;
           }
     }

   double overallAccuracy = (totalEvaluated > 0) ? (double)(regCorrect + hidCorrect) / totalEvaluated * 100.0 : 0.0;
   double regAccuracy = (regEval > 0) ? (double)regCorrect / regEval * 100.0 : 0.0;
   double hidAccuracy = (hidEval > 0) ? (double)hidCorrect / hidEval * 100.0 : 0.0;

   Print("----- Backtest Signal Evaluation -----");
   Print("Total Signals Generated: ", ArraySize(signals));
   Print("Signals Evaluated: ", totalEvaluated);
   Print("Overall Accuracy: ", DoubleToString(overallAccuracy, 2), "%");
   Print("Regular Signals: ", regTotal, " | Evaluated: ", regEval, " | Accuracy: ", DoubleToString(regAccuracy, 2), "%");
   Print("Hidden Signals:  ", hidTotal, " | Evaluated: ", hidEval, " | Accuracy: ", DoubleToString(hidAccuracy, 2), "%");
  }
//+------------------------------------------------------------------+


コード解説

1. ヘッダー情報と入力パラメータ

スクリプトの最上部には、コードに関する重要な情報を示す明確なヘッダーがあります。

ファイルおよび著者情報


ヘッダーにはファイル名(RSI Divergence.mql5)、著作権表示、作者のプロフィールリンクが記載されています。これにより適切な帰属が保証され、ユーザーが更新や追加ドキュメントを確認する際の参考となります。

バージョン管理とコンパイルディレクティブ

#propertyディレクティブにより、バージョン番号や厳格なコンパイルルール(#property strict)などの重要なプロパティが設定されます。これにより開発および配布時の一貫性が保たれ、エラーの発生を抑制します。続いて、入力パラメータのセクションはカスタマイズに不可欠です。これらのパラメータを変更することで、コアコードを触らずにダイバージェンス検出の挙動を細かく調整できます。主な項目は以下の通りです。

RSIおよびスイング検出パラメータ

  • InpRSIPeriod:RSIインジケーターの期間を設定します。
  • InpSwingLeftInpSwingRight :スイングポイント検出時に左右それぞれ何本のバーを考慮するかを定義します。これらを調整することで、スイング検出の厳しさを変更できます。

ダイバージェンスおよびシグナル評価設定

  • InpLookback:過去何本分のバーをスキャンしてダイバージェンスを検出するかを決めます。
  • InpEvalBars:シグナルの成功判定を行うまでに待つバー数を指定します。
  • InpMinBarsBetweenSignals:類似シグナル間に最小限のバー間隔を設け、重複シグナルを避けるための設定です。

表示のカスタマイズ

  • InpArrowOffset:チャート上のスイングポイントから矢印を何ポイントずらすかを設定し、視認性を向上させます。

オプションのRSI閾値フィルター

  • InpUseRSIThresholdと共に、InpRSIOversoldおよびInpRSIOverboughtは追加のフィルタリング機能を提供します。これにより、例えば強気ダイバージェンスの場合は、初期のRSIスイングが売られすぎ領域にあることを条件とすることが可能です(ユーザーがこのフィルターを有効にした場合)。

//+------------------------------------------------------------------+
//|                                           RSI Divergence.mql5    |
//|                               Copyright 2025, Christian Benjamin |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2025, MetaQuotes Software Corp."
#property link      "https://www.mql5.com/ja/users/lynnchris"
#property version   "1.0"
#property strict

//---- Input parameters
input int    InpRSIPeriod             = 14;    // RSI period
input int    InpSwingLeft             = 1;     // Bars to the left for swing detection (relaxed)
input int    InpSwingRight            = 1;     // Bars to the right for swing detection (relaxed)
input int    InpLookback              = 100;   // Number of bars to scan for divergence
input int    InpEvalBars              = 5;     // Bars after which to evaluate a signal
input int    InpMinBarsBetweenSignals = 1;     // Minimum bars between same-type signals (allows frequent re-entry)
input double InpArrowOffset           = 3.0;   // Arrow offset (in points) for display
input double InpMinSwingDiffPct       = 0.05;  // Lower minimum % difference to qualify as a swing
input double InpMinRSIDiff            = 1.0;   // Lower minimum difference in RSI between swing points

// Optional RSI threshold filter for bullish divergence (disabled by default)
input bool   InpUseRSIThreshold       = false; // If true, require earlier RSI swing to be oversold for bullish divergence
input double InpRSIOversold           = 30;    // RSI oversold level
input double InpRSIOverbought         = 70;    // RSI overbought level (if needed for bearish)

2. インジケータの初期化

この部分では、RSIインジケーターの初期化をおこないます。OnInit()関数は、銘柄、時間足、そしてユーザーが指定したRSI期間などのパラメータを用いてRSIインジケーターのハンドルを作成します。このステップは非常に重要で、以降のすべての処理は有効なRSIハンドルを取得し、そのデータを利用できることが前提となっています。

  • iRSI関数は必要なパラメータとともに呼び出されます。
  • ハンドル作成に失敗した場合に備えたエラーハンドリングが実装されています。
  • 初期化処理により、インジケーターがデータ取得と分析の準備が整った状態となります。
int OnInit()
{
   // Create the RSI indicator handle with the specified period
   rsiHandle = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE);
   if(rsiHandle == INVALID_HANDLE)
   {
      Print("Error creating RSI handle");
      return(INIT_FAILED);
   }
   return(INIT_SUCCEEDED);
}

3. 新しいローソク足ごとのデータ取得

OnTick関数内では、処理を開始する前に新しいローソク足が確定したかどうかを確認します。これにより、常に確定済みのデータを使って分析をおこなうことが保証されます。次に、設定可能な遡及期間(ルックバック期間)にわたり、RSI値、安値、高値、終値、時間データの配列をコピーします。配列を「シリーズ」として設定することで、最新のバーがインデックス0にくるようにデータの並び順を維持します。

  • 未確定のデータを処理しないよう、新しい確定ローソク足を待ちます。
  • CopyBuffer、CopyLow、CopyHigh、CopyClose、CopyTimeといった関数でRSIおよび価格データを取得します。
  • ArraySetAsSeriesを使用することで、時系列解析に適した正しいデータ順序を保ちます。

void OnTick()
{
   // Process only once per new closed candle by comparing bar times
   datetime currentBarTime = iTime(_Symbol, _Period, 1);
   if(currentBarTime == lastBarTime)
      return;
   lastBarTime = currentBarTime;
   
   // Copy RSI data for a given lookback period
   ArrayResize(rsiBuffer, InpLookback);
   ArraySetAsSeries(rsiBuffer, true);
   if(CopyBuffer(rsiHandle, 0, 0, InpLookback, rsiBuffer) <= 0)
   {
      Print("Error copying RSI data");
      return;
   }
   
   // Copy price data (lows, highs, closes) and time data for analysis
   ArrayResize(lowBuffer, InpLookback);
   ArrayResize(highBuffer, InpLookback);
   ArrayResize(closeBuffer, InpLookback);
   ArraySetAsSeries(lowBuffer, true);
   ArraySetAsSeries(highBuffer, true);
   ArraySetAsSeries(closeBuffer, true);
   if(CopyLow(_Symbol, _Period, 0, InpLookback, lowBuffer) <= 0 ||
      CopyHigh(_Symbol, _Period, 0, InpLookback, highBuffer) <= 0 ||
      CopyClose(_Symbol, _Period, 0, InpLookback, closeBuffer) <= 0)
   {
      Print("Error copying price data");
      return;
   }
   
   ArrayResize(timeBuffer, InpLookback);
   ArraySetAsSeries(timeBuffer, true);
   if(CopyTime(_Symbol, _Period, 0, InpLookback, timeBuffer) <= 0)
   {
      Print("Error copying time data");
      return;
   }
   
   g_totalBars = InpLookback;
   
   // (Further processing follows here...)
}

4.スイング検出(スイングローおよびスイングハイの検出)

ダイバージェンスを検出する前に、まず重要なスイングポイントを特定する必要があります。2つの補助関数であるIsSignificantSwingLowIsSignificantSwingHighが、局所的な最安値および最高値を見つけるために使用されます。これらの関数は、指定された範囲内でバーの安値または高値を隣接するバーと比較し、パーセンテージ差が設定された閾値を満たしているかを確認します。
  • 関数は現在のバーの左右両方をチェックします。
  • パーセンテージ差を計算し、重要なスイングのみを検出します。
  • このフィルタリングによりノイズが軽減され、ダイバージェンス分析が意味のある市場の動きに集中できるようになります。
bool IsSignificantSwingLow(int index, int left, int right)
{
   double currentLow = lowBuffer[index];
   // Check left side for local minimum condition
   for(int i = index - left; i < index; i++)
   {
      if(i < 0) continue;
      double pctDiff = MathAbs((lowBuffer[i] - currentLow) / currentLow) * 100.0;
      if(lowBuffer[i] < currentLow && pctDiff > InpMinSwingDiffPct)
         return false;
   }
   // Check right side for local minimum condition
   for(int i = index + 1; i <= index + right; i++)
   {
      if(i >= g_totalBars) break;
      double pctDiff = MathAbs((lowBuffer[i] - currentLow) / currentLow) * 100.0;
      if(lowBuffer[i] < currentLow && pctDiff > InpMinSwingDiffPct)
         return false;
   }
   return true;
}

bool IsSignificantSwingHigh(int index, int left, int right)
{
   double currentHigh = highBuffer[index];
   // Check left side for local maximum condition
   for(int i = index - left; i < index; i++)
   {
      if(i < 0) continue;
      double pctDiff = MathAbs((currentHigh - highBuffer[i]) / currentHigh) * 100.0;
      if(highBuffer[i] > currentHigh && pctDiff > InpMinSwingDiffPct)
         return false;
   }
   // Check right side for local maximum condition
   for(int i = index + 1; i <= index + right; i++)
   {
      if(i >= g_totalBars) break;
      double pctDiff = MathAbs((currentHigh - highBuffer[i]) / currentHigh) * 100.0;
      if(highBuffer[i] > currentHigh && pctDiff > InpMinSwingDiffPct)
         return false;
   }
   return true;
}

5. ダイバージェンス検出:弱気ダイバージェンスと強気ダイバージェンス

スイングポイントが特定されると、アルゴリズムは直近のスイングを比較してダイバージェンスを検出します。弱気ダイバージェンスの場合、コードは2つのスイングハイを見て、価格がより高い高値を形成している一方でRSIがより低い高値を示しているか(隠れた弱気ダイバージェンスの場合は逆)をチェックします。強気ダイバージェンスの場合は同様に2つのスイングローを比較します。オプションのRSI閾値設定により、初期のRSIが売られすぎ領域にあることを確認して強気シグナルの信頼性を高めることも可能です。

  • ダイバージェンス分析には直近の2つのスイングポイント(高値または安値)が使われます。
  • 通常と隠れたダイバージェンスの条件は明確に区別されています。
  • RSIの売られすぎ条件などのオプションパラメータが、シグナルの強度をさらに絞り込む役割を果たします。

// --- Bearish Divergence (using swing highs)
if(ArraySize(swingHighs) >= 2)
{
   ArraySort(swingHighs); // Ensure ascending order: index 0 is most recent
   int recent   = swingHighs[0];
   int previous = swingHighs[1];
   
   // Regular Bearish Divergence: Price makes a higher high while RSI makes a lower high
   if(highBuffer[recent] > highBuffer[previous] &&
      rsiBuffer[recent] < rsiBuffer[previous] &&
      (rsiBuffer[previous] - rsiBuffer[recent]) >= InpMinRSIDiff)
   {
      Print("Regular Bearish Divergence detected at bar ", recent);
      DisplaySignal("RegBearish Divergence", recent);
   }
   // Hidden Bearish Divergence: Price makes a lower high while RSI makes a higher high
   else if(highBuffer[recent] < highBuffer[previous] &&
           rsiBuffer[recent] > rsiBuffer[previous] &&
           (rsiBuffer[recent] - rsiBuffer[previous]) >= InpMinRSIDiff)
   {
      Print("Hidden Bearish Divergence detected at bar ", recent);
      DisplaySignal("HiddenBearish Divergence", recent);
   }
}

// --- Bullish Divergence (using swing lows)
if(ArraySize(swingLows) >= 2)
{
   ArraySort(swingLows); // Ensure ascending order: index 0 is most recent
   int recent   = swingLows[0];
   int previous = swingLows[1];
   
   // Regular Bullish Divergence: Price makes a lower low while RSI makes a higher low
   if(lowBuffer[recent] < lowBuffer[previous] &&
      rsiBuffer[recent] > rsiBuffer[previous] &&
      (rsiBuffer[recent] - rsiBuffer[previous]) >= InpMinRSIDiff)
   {
      // Optionally require the earlier RSI swing to be oversold
      if(!InpUseRSIThreshold || rsiBuffer[previous] <= InpRSIOversold)
      {
         Print("Regular Bullish Divergence detected at bar ", recent);
         DisplaySignal("RegBullish Divergence", recent);
      }
   }
   // Hidden Bullish Divergence: Price makes a higher low while RSI makes a lower low
   else if(lowBuffer[recent] > lowBuffer[previous] &&
           rsiBuffer[recent] < rsiBuffer[previous] &&
           (rsiBuffer[previous] - rsiBuffer[recent]) >= InpMinRSIDiff)
   {
      Print("Hidden Bullish Divergence detected at bar ", recent);
      DisplaySignal("HiddenBullish Divergence", recent);
   }
}

6. シグナルの表示と記録

ダイバージェンスが検出された際には、シグナルを視覚的に表示し、その詳細を後で評価できるように記録することが重要です。DisplaySignal関数は、強気・弱気シグナルに応じて異なる矢印コードや色を使いチャート上に矢印を表示するだけでなく、最新のシグナル情報を示すラベルも更新し、シグナルのメタデータをグローバル配列に保存します。この体系的な記録により、後から戦略のバックテストがが可能となります。

  • 類似のバーに対して既にシグナルが存在するかをチェックし、重複シグナルを防止しています。
  • 矢印やラベルといった視覚的な表示により、チャートの読みやすさが向上します。
  • シグナルはタイプ、バーインデックス、時間、価格などの詳細とともに保存され、後のパフォーマンス評価に役立ちます。

void DisplaySignal(string signalText, int barIndex)
{
   // Prevent duplicate signals on the same or nearby bars
   for(int i = 0; i < ArraySize(signals); i++)
   {
      if(StringFind(signals[i].type, signalText) != -1)
         if(MathAbs(signals[i].barIndex - barIndex) < InpMinBarsBetweenSignals)
            return;
   }
     
   // Update a label for the latest regular signal
   if(StringFind(signalText, "Reg") != -1)
   {
      string labelName = "LatestSignal";
      if(ObjectFind(0, labelName) == -1)
      {
         if(!ObjectCreate(0, labelName, OBJ_LABEL, 0, 0, 0))
         {
            Print("Failed to create LatestSignal label");
            return;
         }
         ObjectSetInteger(0, labelName, OBJPROP_CORNER, 0);
         ObjectSetInteger(0, labelName, OBJPROP_XDISTANCE, 10);
         ObjectSetInteger(0, labelName, OBJPROP_YDISTANCE, 20);
         ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrWhite);
      }
      ObjectSetString(0, labelName, OBJPROP_TEXT, signalText);
   }
     
   // Create an arrow object to mark the signal on the chart
   string arrowName = "Arrow_" + signalText + "_" + IntegerToString(barIndex);
   if(ObjectFind(0, arrowName) < 0)
   {
      int arrowCode = 0;
      double arrowPrice = 0.0;
      color arrowColor = clrWhite;
      double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
          
      if(StringFind(signalText, "Bullish") != -1)
      {
         arrowCode  = 233; // Wingdings up arrow
         arrowColor = clrLime;
         arrowPrice = lowBuffer[barIndex] - (InpArrowOffset * point);
      }
      else if(StringFind(signalText, "Bearish") != -1)
      {
         arrowCode  = 234; // Wingdings down arrow
         arrowColor = clrRed;
         arrowPrice = highBuffer[barIndex] + (InpArrowOffset * point);
      }
          
      if(!ObjectCreate(0, arrowName, OBJ_ARROW, 0, timeBuffer[barIndex], arrowPrice))
      {
         Print("Failed to create arrow object ", arrowName);
         return;
      }
      ObjectSetInteger(0, arrowName, OBJPROP_COLOR, arrowColor);
      ObjectSetInteger(0, arrowName, OBJPROP_ARROWCODE, arrowCode);
   }
     
   // Record the signal details for later backtesting evaluation
   SignalInfo sig;
   sig.type = signalText;
   sig.barIndex = barIndex;
   sig.signalTime = timeBuffer[barIndex];
   if(StringFind(signalText, "Bullish") != -1)
      sig.signalPrice = lowBuffer[barIndex];
   else
      sig.signalPrice = highBuffer[barIndex];
   ArrayResize(signals, ArraySize(signals) + 1);
   signals[ArraySize(signals) - 1] = sig;
     
   UpdateSignalCountLabel();
}

初期化後のバックテスト評価

最後に、EXが初期化解除される際に、EvaluateSignalsAndPrint関数が呼び出されます。この関数は、記録されたすべてのシグナルを遡って評価し、シグナル発生時の価格とその数バー後の価格の動きを比較します。これにより、ダイバージェンス戦略のパフォーマンスに関する有益なフィードバックが得られます。

  • この関数は、過去の終値データを完全に取得します。
  • 各シグナルは、設定されたバー数(InpEvalBarsで指定)経過後に評価されます。
  • 正規のシグナルと隠れたシグナルの両方に対して、全体および個別の精度(正確性)指標が算出され、パフォーマンス検証に役立ちます。

void EvaluateSignalsAndPrint()
{
   double closeAll[];
   int totalBars = CopyClose(_Symbol, _Period, 0, WHOLE_ARRAY, closeAll);
   if(totalBars <= 0)
   {
      Print("Error copying complete close data for evaluation");
      return;
   }
   ArraySetAsSeries(closeAll, true);
   
   int totalEvaluated = 0, regTotal = 0, hidTotal = 0;
   int regEval = 0, hidEval = 0;
   int regCorrect = 0, hidCorrect = 0;
   
   for(int i = 0; i < ArraySize(signals); i++)
   {
      int evalIndex = signals[i].barIndex - InpEvalBars;
      if(evalIndex < 0)
         continue;
      double evalClose = closeAll[evalIndex];
      
      if(StringFind(signals[i].type, "Bullish") != -1)
      {
         if(StringFind(signals[i].type, "Reg") != -1)
         {
            regTotal++;
            regEval++;
            if(evalClose > signals[i].signalPrice)
               regCorrect++;
         }
         else if(StringFind(signals[i].type, "Hidden") != -1)
         {
            hidTotal++;
            hidEval++;
            if(evalClose > signals[i].signalPrice)
               hidCorrect++;
         }
         totalEvaluated++;
      }
      else if(StringFind(signals[i].type, "Bearish") != -1)
      {
         if(StringFind(signals[i].type, "Reg") != -1)
         {
            regTotal++;
            regEval++;
            if(evalClose < signals[i].signalPrice)
               regCorrect++;
         }
         else if(StringFind(signals[i].type, "Hidden") != -1)
         {
            hidTotal++;
            hidEval++;
            if(evalClose < signals[i].signalPrice)
               hidCorrect++;
         }
         totalEvaluated++;
      }
   }
     
   double overallAccuracy = (totalEvaluated > 0) ? (double)(regCorrect + hidCorrect) / totalEvaluated * 100.0 : 0.0;
   double regAccuracy = (regEval > 0) ? (double)regCorrect / regEval * 100.0 : 0.0;
   double hidAccuracy = (hidEval > 0) ? (double)hidCorrect / hidEval * 100.0 : 0.0;
     
   Print("----- Backtest Signal Evaluation -----");
   Print("Total Signals Generated: ", ArraySize(signals));
   Print("Signals Evaluated: ", totalEvaluated);
   Print("Overall Accuracy: ", DoubleToString(overallAccuracy, 2), "%");
   Print("Regular Signals: ", regTotal, " | Evaluated: ", regEval, " | Accuracy: ", DoubleToString(regAccuracy, 2), "%");
   Print("Hidden Signals:  ", hidTotal, " | Evaluated: ", hidEval, " | Accuracy: ", DoubleToString(hidAccuracy, 2), "%");
}


テストと結果

MetaEditorでEAのコンパイルが成功したら、チャート上にドラッグしてテストをおこないます。本番資金をリスクにさらさないよう、必ずデモ口座でテストしてください。テスト中にシグナルの確認を簡単にするために、チャートにRSIインジケーターを追加することもできます。これをおこなうには、MetaTrader 5の[インディケーター]タブを開き、「Panels」フォルダー内のRSIインジケーターを選択し、お好みのパラメータを設定します(EAで使用しているものと一致させてください)。以下のGIFでは、MetaTrader 5のチャート上にRSIインジケーターウィンドウを追加する手順を示しています。また、1分足時間枠上で確認された「通常のブル(買い)ダイバージェンス」シグナルも表示されています。

強気の収束

図5:インジケーター設定とテスト結果1

以下は、Boom 500で実施した別のテストです。価格アクションとRSIインジケーターの両方で確認され、売りシグナルを示しています。

テスト

図6:テスト結果2

以下の GIFでバックテストを使用して別のテストが実行され、いくつかのプラスの変化が示されました。よく見ると、隠れた継続シグナルと通常のシグナルの両方があることに気付くでしょう。ただし、肯定的な影響があるにもかかわらず、確認が不十分なため、いくつかのシグナルを除外する必要があります。

バックテスト

図7:テスト結果3


結論

このツールは、プライスアクションとの整合性が非常に高く、本連載の中核である「できる限り多くのプライスアクション分析ツールを作成する」という目的にしっかりと沿ったものとなっています。特に、RSIインジケーターがダイバージェンスからポジティブなシグナルを抽出し、プライスアクションと効果的に連携する点に非常に感心しました。実施したテストでは、有望な結果と前向きな傾向が確認できました。

とはいえ、次のステップとして、シグナル精度をさらに向上させるために、外部ライブラリを用いた正確なスイング識別機能の導入を検討する時期に来ていると考えています。このツールを使用する際は、しっかりとテストをおこない、自身の取引スタイルに合ったパラメータに調整することをお勧めします。また、生成された各シグナルは、エントリー前に必ずクロスチェックを行ってください。このツールは、あくまで市場を監視し、あなたの全体的な戦略を確認するためのサポートツールとして設計されています。

日付 ツール名  詳細 バージョン  アップデート  備考
01/10/24 ChartProjector 前日のプライスアクションをゴースト効果でオーバーレイするスクリプト 1.0 初回リリース Lynnchris Tool Chestの最初のツール
18/11/24 Analytical Comment 前日の情報を表形式で提供し、市場の将来の方向性を予測する 1.0 初回リリース Lynnchris Tool Chestの2番目のツール
27/11/24 Analytics Master 2時間ごとに市場指標を定期的に更新  1.01 v.2 Lynnchris Tool Chestの3番目のツール
02/12/24 Analytics Forecaster  Telegram統合により、2時間ごとに市場指標を定期的に更新 1.1 v.3 ツール番号4
09/12/24 Volatility Navigator ボリンジャーバンド、RSI、ATR指標を使用して市場の状況を分析するEA 1.0 初回リリース ツール番号5
19/12/24 Mean Reversion Signal Reaper  平均回帰戦略を用いて市場を分析し、シグナルを提供する  1.0  初回リリース  ツール番号6 
9/01/25  Signal Pulse  多時間枠分析ツール 1.0  初回リリース  ツール番号7 
17/01/25  Metrics Board  分析用のボタン付きパネル  1.0  初回リリース ツール番号8 
21/01/25 External Flow 外部ライブラリによる分析 1.0  初回リリース ツール番号9 
27/01/25 VWAP 出来高加重平均価格   1.3  初回リリース  ツール番号10 
02/02/25  Heikin Ashi  トレンドの平滑化と反転シグナルの識別  1.0  初回リリース  ツール番号11
04/02/25  FibVWAP  Python分析によるシグナル生成  1.0  初回リリース  ツール番号12
14/02/25  RSI DIVERGENCE  プライスアクションとRSIのダイバージェンス  1.0  初回リリース  ツール番号13 

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

添付されたファイル |
RSI_DIVERGENCE.mq5 (33.61 KB)
最後のコメント | ディスカッションに移動 (1)
linfo2
linfo2 | 18 2月 2025 において 20:05
クリスさん、もう一度、よく考え抜かれた有益な記事をありがとう。私が修正するためのテンプレートとして、とても感謝している。また、あなたの博学な思考と実践的な実装にも感謝しています。私の場合、EvaluateSignalsAndPrintに問題があり、関数がError Copying Compete close data for evaluationを返すと書かれています。最後のエラーは'4003'です。なぜWHOLE_ARRAY 変数を使用すると関数が失敗するのか、私には理解できません。copycloseの'WHOLE_ARRAY'をクローズしたバーのカウントに置き換えると、'Backtest Signal Evaluation'が返されます。ところで、私はGMT +13という奇妙なタイムゾーンにおり、ローカルとサーバーの日付と時刻に問題があることがあります。
MQL5での取引戦略の自動化(第7回):動的ロットスケーリングを備えたグリッド取引EAの構築 MQL5での取引戦略の自動化(第7回):動的ロットスケーリングを備えたグリッド取引EAの構築
この記事では、動的なロットスケーリングを採用したMQL5のグリッドトレーディングエキスパートアドバイザー(EA)を構築します。戦略の設計、コードの実装、バックテストのプロセスについて詳しく解説します。最後に、自動売買システムを最適化するための重要な知見とベストプラクティスを共有します。
MQL5で取引管理者パネルを作成する(第9回):コード編成(I)モジュール化 MQL5で取引管理者パネルを作成する(第9回):コード編成(I)モジュール化
本ディスカッションでは、MQL5プログラムをより小さく扱いやすいモジュールに分割する一歩を踏み出します。これらのモジュール化されたコンポーネントをメインプログラムに統合することで、構造が整理され保守性が向上します。この手法によりメインプログラムの構造が簡素化されるだけでなく、各コンポーネントを他のエキスパートアドバイザー(EA)やインジケーター開発にも再利用可能にします。モジュール設計を採用することで、将来的な機能拡張の基盤を確立し、私たちのプロジェクトだけでなく広く開発者コミュニティにも貢献できるものとなります。
プライスアクション分析ツールキットの開発(第14回):Parabolic Stop and Reverseツール プライスアクション分析ツールキットの開発(第14回):Parabolic Stop and Reverseツール
プライスアクション分析にテクニカルインジケーターを取り入れることは、非常に有効なアプローチです。これらのインジケーターは、反転や押し戻しの重要なレベルを示すことが多く、市場の動きを把握する上での貴重な手がかりとなります。本記事では、パラボリックSARインジケーターを用いてシグナルを生成する自動ツールをどのように開発したかを紹介します。
MQL5でカスタムキャンバスグラフィックを使用したケルトナーチャネルインジケーターの構築 MQL5でカスタムキャンバスグラフィックを使用したケルトナーチャネルインジケーターの構築
本記事では、MQL5を用いてカスタムキャンバスグラフィック付きのケルトナーチャネルインジケーターを構築します。移動平均の統合、ATRの計算、そして視覚的に強化されたチャート表示について詳しく解説します。また、インジケーターの実用性を評価するためのバックテスト手法についても取り上げ、実際の取引に役立つ洞察を提供します。