English Deutsch
preview
カスタムインジケータワークショップ(第1回):MQL5でSupertrendインジケータを構築する

カスタムインジケータワークショップ(第1回):MQL5でSupertrendインジケータを構築する

MetaTrader 5インディケータ |
13 0
Chacha Ian Maroa
Chacha Ian Maroa

はじめに

想像してみてください。時は2022年初頭。暗号資産の冬相場が本格化し、ビットコインはついに20,000ドルを下回りました。市場はパニック状態で、フォーラムは絶望的な投稿で溢れ、ポートフォリオは真っ赤に染まり、多くのトレーダーは恐怖で身動きが取れなくなるか、底値で慌てて投げ売りしていました。

しかし、静かなチャート上で、一本の太い線が落ち着いたまま赤から緑へと反転し、価格の下から寄り添うように推移しながら、まるでこう語りかけているようでした。「落ち着いて。下降トレンドは終わっています。」その線こそがSupertrendインジケータだったのです。

多くのトレーダーがノイズに振り回されている中で、このインジケータは明確で曖昧さのないメッセージを提示しました。「トレンドが転換した」という事実です。そしてそれを信じたトレーダーたちは、単に混乱を回避しただけでなく、その後の大きなトレンドそのものを捉えることになりました。

シンプルで長年使われ続けているにもかかわらず、Supertrendは今なお最も誤解されやすく、そして正しく実装されていないインジケータの一つです。多くの実装はリペイントを起こしたり、ライブ市場で不安定な挙動を示したり、あるいは内部ロジックが不透明なまま提供されています。その結果として開発者側には繰り返し同じ課題が発生します。トレーダーはSupertrendベースのツールを求め続けている一方で、信頼でき、拡張性があり、かつドキュメント化された実装は驚くほど少ないという問題です。

本記事はその問題を解決することを目的としています。Supertrendをブラックボックスとして扱うのではなく、第一原理から非リペイントのSupertrendインジケータをMQL5で設計・構築します。明確さ、正確さ、そして実務に耐える設計を意識して実装します。このインジケータは内部バッファを開発者向けに明確に公開し、単なる可視化ツールではなく、EAや取引戦略、さらなる拡張の基盤として利用できる設計とします。

同時に、MQL5開発者を志す学習者にとっても、インジケータ設計のクリーンアーキテクチャ、適切なバッファ管理、そしてライブ相場で安全に計算するための手法といった実践的なスキルを学ぶ機会になります。これらはSupertrendに限らず、あらゆるカスタムインジケータ開発に応用できる重要なスキルです。

ここから始まるのは連載「カスタムインジケータワークショップ」の第一回です。この連載では、既存のインジケータを単なる利用対象として扱うのではなく、分解し、再構築し、より良い形へと作り直していきます。既製ツールの固定パラメータや不透明なロジックに依存するのではなく、自分自身で完全に制御できるインジケータを作ることを目指します。MQL5で実装することで、その内部構造を深く理解することができます。

それではまず、Supertrendとは何か、そしてそれを正しく実装することがなぜ重要なのかから始めていきます。


Supertrendインジケータとは何か

MQL5コードを一行でも書く前に、Supertrendインジケータが実際に何を意味しているのか、そして何を意味していないのかを正しく理解することが重要です。

SupertrendインジケータはOlivier Sebanによって考案され、その後、TradingView上で実装・共有されましたそれ以来、特に暗号資産、FX、インデックス市場において、リテールトレード環境全体で最も広く使われるトレンドフォロー系ツールの一つへと成長しています。

その本質は「ボラティリティベースのトレンドインジケータ」です。価格方向を予測したり、反転を先読みしたり、天井や底を当てようとするものではありません。そうではなく、より単純で、しかし実務的にははるかに重要な問いに答えます。「現在のボラティリティを前提とした場合、今マーケットは上昇トレンドなのか、それとも下降トレンドなのか?」です。これを実現するために、Supertrendは以下の要素を組み合わせます。

  • ボラティリティを測定するためのATR
  • 価格の中間値(ミッドポイント)参照
  • トレンド変化に応じて上下を反転するダイナミックなトレーリングバンド

価格がトレーリングバンドの上にある限り、インジケータは強気トレンドとみなし、ラインを価格の下側に描画します。一方で、終値がそのバンドを下抜けした場合、仮定は反転し、ラインは価格の上側へ移動し、弱気トレンドを示します。

この「価格の上か下かへの単一の反転」こそが、Supertrendの明確性の源泉です。オシレーションは存在せず、競合するシグナルもなく、解釈の余地もほとんどありません。一本の線。一つの方向。ひとつの状態遷移だけです。このシンプルさにより、Supertrendはトレンドフォロー戦略、トレーリングストップシステム、アルゴリズム判断ロジックにおいて特に人気を得ています。ただし、多くのオンライン議論は、トレード戦略、パラメータ最適化、パフォーマンス比較といったテーマに集中しています。これらは重要ではあるものの、本ワークショップの主目的ではありません。

より深い理論的な説明に興味がある読者には、Investopediaがこのインジケータの理論と使用方法について、客観的で分かりやすい概要を提供しています。


私たちが作るSupertrendの見た目と挙動

コードに触れる前に、まず「何を作るのか」「それがチャート上でどのように振る舞うべきか」を明確にしておくことが重要です。Supertrendインジケータは完全にATRに依存して、そのトレンドラインの描画位置を決定します。ここで車輪の再発明をするのではなく、MetaTrader 5がすでに高度に最適化されたATRインジケータを提供している点を活用します。本カスタムSupertrendは、ATR値を取得するためにインジケータハンドルを使用し、ボラティリティ計算ではなくトレンドロジックと可視化に集中できる設計とします。

このインジケータはカスタムMQL5インジケータとして実装され、メインチャートウィンドウに直接描画されます。チャートのキャンバスはコントラストと視認性を最大化するため、白背景を使用します。任意の時点において、このインジケータは相場状態を表す単一の内部状態を保持します。その状態は強気または弱気の2つのいずれかのみです。

強気環境では、SeaGreen色の細い線が価格の下側に描画されます。このラインは、各ローソク足の中間価格(ミッドポイント)からATRにユーザー定義の乗数を掛けた値を差し引くことで計算されます。これにより、ボラティリティの変化に応じて動的に価格へ追従するトレーリングバンドになります。同時に、チャート上のすべてのローソク足は同じSeaGreenカラーで表示されます。これにより、市場が上昇トレンドであることが即座に視覚的に確認できるようになります。

弱気環境では挙動は反転します。細い黒色のラインが価格の上側に描画され、トレンドが変化するまで維持されます。同時に、すべてのローソク足も黒色で表示されます。価格がトレーリングバンドを逆方向にブレイクした瞬間にトレンド状態は反転し、インジケータはそれに応じて更新されます。

この時点で、以下のスクリーンショットを確認してください。これはライブチャート上でのインジケータの最終的な外観を示しています。ご覧の通り、追加のインジケータや解釈を必要とせずにトレンド状態が直感的に把握できます。一本の線。一つの色。一つの方向。

Supertrendの動作例

このビジュアル設計は意図的なものです。Supertrendラインとローソク足の両方を一貫して同じ色で表示することで、曖昧さを排除し、認知負荷を軽減します。トレーダーまたは自動売買システムは、迷うことなく現在の支配的トレンドを即座に識別できます。
デフォルトでは、このインジケータはチャートの時間足に基づいて計算および描画されます。この段階では挙動の予測可能性と透明性を保つため、マルチタイムフレームロジックは導入されません。
Supertrendの感度を制御するために、以下の2つのユーザー入力パラメータを公開します。
  • atrPeriod:ATRの計算期間を定義します
  • atrMultiplier:Supertrendラインが価格にどれだけ近く追従するかを制御します

最後に、そして最も重要な点として、この実装は非リペイントです。一度バーが確定しトレンド判断が確定した場合、それが過去に遡って変更されることはありません。これにより、本インジケータは裁量分析だけでなく、エキスパートアドバイザー、シグナル生成、さらには商用トレードシステムにも適用可能なものとなります。


MQL5でSupertrendを実装する

ここから先は、いよいよ本格的に手を動かすフェーズに入ります。このセクションでは、SupertrendのコンセプトをMQL5で動作するカスタムインジケータへと変換していきます。

進める前に、読者はいくつかの基本的な前提条件を満たしている必要があります。まず、MQL5プログラミングの基本的な理解がすでにあることが前提です。また、MetaTrader 5デスクトップターミナルの操作に慣れており、インジケータをチャートへ追加する方法を理解している必要があります。さらに、MetaEditorの使用に慣れており、新規ファイルの作成、コードのコンパイル、エラーの確認、必要に応じたデバッグができることも前提となります。これらに問題がなければ、次へ進む準備は整っています。

このインジケータの完成版ソースコードは本記事に添付されています。最も効果的に学習するために、コードを実際に自分で書きながら進めることを強く推奨します。自分でコードを書き、それを提供されたソースと比較することは、インジケータ開発を習得するうえで最も効果的な学習方法の一つです。途中でエラーが発生した場合でも、添付されている実装と照らし合わせることで確認できます。

初期インジケータ構造の作成

MetaEditorを開き、新しい空のカスタムインジケータファイルを作成します。ファイル名はsupertrend.mq5とし、次に示すソースコードをそのファイルに貼り付けます。

//+------------------------------------------------------------------+
//|                                                   supertrend.mq5 |
//|          Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian |
//|                          https://www.mql5.com/ja/users/chachaian |
//+------------------------------------------------------------------+

#property copyright "Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian"
#property link      "https://www.mql5.com/ja/users/chachaian"
#property version   "1.00"

//+------------------------------------------------------------------+
//| User input variables                                             |
//+------------------------------------------------------------------+
input group "Information"
input int32_t atrPeriod     = 10;
input double  atrMultiplier = 1.5;            

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;

//+------------------------------------------------------------------+
//| Indicator buffers                                                |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(){

   //--- To configure the chart's appearance
   if(!ConfigureChartAppearance()){
      Print("Error while configuring chart appearance", GetLastError());
      return INIT_FAILED;
   }   
   
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,
                const int32_t 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 int32_t &spread[])
{

   //--- This block is executed when the indicator is initially attached to a chart
   if(prev_calculated == 0){
   }
   
   //--- This block is executed on every new bar open
   if(prev_calculated != rates_total && prev_calculated != 0){
   }
   
   return rates_total;
}

//+------------------------------------------------------------------+
//| Deinitiatialization function                                     |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){

   //---
    
}

//--- CUSTOM UTILITY FUNCTIONS
//+------------------------------------------------------------------+
//| This function configures the chart's appearance.                 |
//+------------------------------------------------------------------+
bool ConfigureChartAppearance()
{
   if(!ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhite)){
      Print("Error while setting chart background, ", GetLastError());
      return false;
   }
   
   if(!ChartSetInteger(0, CHART_SHOW_GRID, false)){
      Print("Error while setting chart grid, ", GetLastError());
      return false;
   }
   
   if(!ChartSetInteger(0, CHART_MODE, CHART_LINE)){
      Print("Error while setting chart mode, ", GetLastError());
      return false;
   }

   if(!ChartSetInteger(0, CHART_COLOR_FOREGROUND, clrBlack)){
      Print("Error while setting chart foreground, ", GetLastError());
      return false;
   }

   if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BULL, clrWhite)){
      Print("Error while setting bullish candles color, ", GetLastError());
      return false;
   }
      
   if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, clrWhite)){
      Print("Error while setting bearish candles color, ", GetLastError());
      return false;
   }
   
   if(!ChartSetInteger(0, CHART_COLOR_CHART_UP, clrSeaGreen)){
      Print("Error while setting bearish candles color, ", GetLastError());
      return false;
   }
   
   if(!ChartSetInteger(0, CHART_COLOR_CHART_DOWN, clrBlack)){
      Print("Error while setting bearish candles color, ", GetLastError());
      return false;
   }
   
   return true;
}

//+------------------------------------------------------------------+

このファイルは、完全なSupertrendインジケータの基盤となるものです。一見するとコード量が多く見えるかもしれませんが、これは意図的な設計であり、コードを整理された状態に保ち、可読性を高め、将来的な拡張を容易にするための構造になっています。それでは、このコードをセクションごとに順番に見ていきましょう。

ファイルプロパティとメタデータ

まず最上部では、インジケータのメタデータを定義します。

//+------------------------------------------------------------------+
//|                                                   supertrend.mq5 |
//|          Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian |
//|                          https://www.mql5.com/ja/users/chachaian |
//+------------------------------------------------------------------+

#property copyright "Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian"
#property link      "https://www.mql5.com/ja/users/chachaian"
#property version   "1.00"

これには、ファイル名、作者情報、バージョン番号、参照リンクが含まれます。これらのプロパティはMetaTraderターミナル内に表示され、ユーザーがインジケータ、その作成者、そして作成者や配布元を識別するのに役立ちます。このセクションはロジックには影響しませんが、特に将来的にインジケータを公開したり商用として販売したりする可能性がある場合には、プロとしての開発では重要な慣行です。

ユーザー入力変数

次に、ユーザー入力パラメータを定義します。これらはトレーダーがインジケータの設定ウィンドウから直接変更できる値です。

//+------------------------------------------------------------------+
//| User input variables                                             |
//+------------------------------------------------------------------+
input group "Information"
input int32_t atrPeriod     = 10;
input double  atrMultiplier = 1.5; 

ここでは2つの入力パラメータを公開しています。ATR期間はボラティリティの測定方法を制御し、ATRマルチプライヤーはSupertrendラインが価格変動に対してどの程度敏感に追従するかを制御します。これら2つのパラメータにより、ユーザーはコードに触れることなくインジケータの挙動を調整できます。インジケータが拡張されていく過程でも、このセクションはすべてのユーザー設定可能なパラメータの定義場所として維持されます。

グローバル変数

グローバル変数セクションでは、複数の関数間で共有される値を定義します。現時点では、時間足を表す変数のみを定義しており、デフォルトでは現在のチャートの時間足が使用されます。

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;
今後の開発が進むにつれて、このセクションにはインジケータハンドル、トレンド状態の追跡、および計算に必要となるその他の共有値が追加されていきます。

インジケータバッファ

//+------------------------------------------------------------------+
//| Indicator buffers                                                |
//+------------------------------------------------------------------+

このセクションは現時点では意図的に空のままにしています。記事の後半では、ローソク足データ、Supertrendバンド、そしてトレンド状態を保持するために使用するすべてのバッファをここで宣言およびバインドします。バッファの宣言をこの場所に集約しておくことで、コード全体の構造が追いやすくなり、他の箇所が不要に散らかることを防ぐことができます。

OnInit関数

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(){

   //--- To configure the chart's appearance
   if(!ConfigureChartAppearance()){
      Print("Error while configuring chart appearance", GetLastError());
      return INIT_FAILED;
   }   
   
   return INIT_SUCCEEDED;
}

OnInit関数は、インジケータがチャートに追加された際に一度だけ実行されます。その主な役割は、初期化と設定処理です。

今回の実装では、最初におこなう処理として ConfigureChartAppearanceユーティリティ関数を呼び出し、チャートの外観を設定します。この設定処理が何らかの理由で失敗した場合、インジケータの初期化は安全に中断されます。

チャート外観の設定

ConfigureChartAppearance関数は、Supertrendインジケータのためにチャートキャンバスを準備します。

//+------------------------------------------------------------------+
//| This function configures the chart's appearance.                 |
//+------------------------------------------------------------------+
bool ConfigureChartAppearance()
{
   if(!ChartSetInteger(0, CHART_COLOR_BACKGROUND, clrWhite)){
      Print("Error while setting chart background, ", GetLastError());
      return false;
   }
   
   if(!ChartSetInteger(0, CHART_SHOW_GRID, false)){
      Print("Error while setting chart grid, ", GetLastError());
      return false;
   }
   
   if(!ChartSetInteger(0, CHART_MODE, CHART_LINE)){
      Print("Error while setting chart mode, ", GetLastError());
      return false;
   }

   if(!ChartSetInteger(0, CHART_COLOR_FOREGROUND, clrBlack)){
      Print("Error while setting chart foreground, ", GetLastError());
      return false;
   }

   if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BULL, clrWhite)){
      Print("Error while setting bullish candles color, ", GetLastError());
      return false;
   }
      
   if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, clrWhite)){
      Print("Error while setting bearish candles color, ", GetLastError());
      return false;
   }
   
   if(!ChartSetInteger(0, CHART_COLOR_CHART_UP, clrSeaGreen)){
      Print("Error while setting bearish candles color, ", GetLastError());
      return false;
   }
   
   if(!ChartSetInteger(0, CHART_COLOR_CHART_DOWN, clrBlack)){
      Print("Error while setting bearish candles color, ", GetLastError());
      return false;
   }
   
   return true;
}

白背景を設定し、グリッドを非表示にし、ローソク足の色を調整し、さらに強気・弱気の値動きが視覚的に明確に区別できるようにします。これにより、Supertrendラインと色分けされたローソク足がはっきりと際立つ、クリーンで高コントラストな表示環境が構築されます。

各設定は防御的に適用されます。もしチャートの変更処理のいずれかが失敗した場合、その関数はエラーを報告し、処理を停止します。これによりデバッグが容易になり、サイレントフェイル(エラーが表面化しない失敗)を防ぐことができます。

OnCalculate関数

OnCalculate関数は、最終的なインジケータ計算がすべて実行される中核部分です。この関数は新しい価格データが利用可能になるたびに呼び出されます。

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,
                const int32_t 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 int32_t &spread[])
{

   //--- This block is executed when the indicator is initially attached to a chart
   if(prev_calculated == 0){
   }
   
   //--- This block is executed on every new bar open
   if(prev_calculated != rates_total && prev_calculated != 0){
   }
   
   return rates_total;
}

現時点では、2つの論理シナリオを分離して扱います。1つ目は、インジケータがチャートに初めて追加されたときに発生するケースです。

   //--- This block is executed when the indicator is initially attached to a chart
   if(prev_calculated == 0){
   }

2つ目は、新しいバーが形成された際に発生するケースです。

   //--- This block is executed on every new bar open
   if(prev_calculated != rates_total && prev_calculated != 0){
   }

この構造により、初期化ロジックと継続的な計算ロジックを明確に分離することができます。これは特に、Supertrendのような非リペイント型インジケータにおいて重要になります。

OnDeinit関数

//+------------------------------------------------------------------+
//| Deinitiatialization function                                     |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){

   //---
    
}

OnDeinit関数は、インジケータがチャートから削除された際に呼び出されます。現時点では中身は空ですが、後で必要になった場合に適切なクリーンアップ処理を追加できるように存在しています。今後、インジケータハンドルや動的オブジェクトを追加していくにつれて、この関数はリソース解放のために重要な役割を持つようになります。

ユーティリティ関数セクション

最後に、すべてのヘルパー関数およびユーティリティ関数はファイルの下部にまとめて配置します。

//--- CUSTOM UTILITY FUNCTIONS

---

この構造により、メインのインジケータロジックは読みやすく保たれ、再利用可能な機能は一箇所に集約されます。インジケータが拡張されるにつれて、追加のユーティリティ関数もこのセクションに整理され、コアロジックを煩雑にすることなく管理できるようになります。

ATRインジケータの初期化

前述の通り、SupertrendインジケータはATRに基づいています。MetaTrader 5にはすでに信頼性が高く十分にテストされたATRインジケータが標準搭載されているため、これを一から再実装する必要はありません。その代わり、ターミナル内で計算されたATR値を直接取得し、ロジックに統合します。

これを実現するために、まず2つのグローバル変数が必要になります。1つ目はインジケータハンドルです。このハンドルは、カスタムインジケータとターミナル内で動作する組み込みATRインジケータとの間のライブ接続を表します。2つ目は動的配列で、MetaTraderによって計算されたATR値を格納するために使用されます。

これらはグローバルスコープで以下のように宣言します。

int atrHandle;
double atrValues [];

atrHandleはATRインジケータへの参照を保持し、atrValues[]はターミナルから取得したATR値を格納します。これらをグローバルに宣言することで、初期化時および計算処理中のどちらでもATR値へアクセスできるようになります。

ATRインジケータハンドルの作成

これらの変数を準備した上で、次にOnInit関数内でATRインジケータを初期化します。

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(){

   ...
   
   //--- Initialize the ATR indicator
   atrHandle = iATR(_Symbol, timeframe, atrPeriod);
   if(atrHandle == INVALID_HANDLE){
      Print("Error while initializing the ATR indicator ", GetLastError());
      return(INIT_FAILED);
   }    
   
   return INIT_SUCCEEDED;
}

これはiATR関数を使用しておこないます。この関数はMetaTraderに対して、どの銘柄を使用するか、どの時間足を参照するか、そしてATR計算において何期間を使用するかを指定します。今回の実装では、現在の銘柄、現在の時間足、そしてユーザーが入力したATR期間を使用します。

iATRが正常に呼び出された場合、有効なハンドルが返されます。何らかの問題が発生した場合は無効なハンドルが返されます。そのため、ここでは返り値を即座にチェックします。もしハンドルが無効であれば、エラーメッセージを出力し、インジケータの読み込みを停止します。これにより、有効なATRデータなしでロジックが実行されることを防ぎ、後の誤った計算結果を回避できます。

この時点で、ATRインジケータはバックグラウンドで静かに動作しており、MetaTraderは必要に応じてATR値を提供できる状態になっています。

ATRインジケータの解放

作成されたすべてのインジケータハンドルは、インジケータがチャートから削除される際に必ず解放する必要があります。これはOnDeinit関数内で処理されます。

//+------------------------------------------------------------------+
//| Deinitiatialization function                                     |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){

   //--- Release ATR
   if(atrHandle != INVALID_HANDLE){
      IndicatorRelease(atrHandle);
   }    
}

ここでは、ATRハンドルが有効かどうかを確認しています。有効である場合は、IndicatorReleaseを使用して解放します。この処理はメモリ管理において非常に重要です。これにより、Supertrendインジケータが不要になった際に、MetaTraderがATRインジケータによって使用されていたリソースを確実に解放できるようになります。MetaTrader自体もリソース管理は優れていますが、ハンドルを明示的に解放することはベストプラクティスであり、コードをクリーンかつプロフェッショナルに保つために推奨されます。

コンパイル警告の解消

この時点でインジケータをコンパイルすると、いくつかの警告が表示されます。これらの警告は想定されたものであり、まだ必要なプロパティが定義されていないことを示しています。最初の警告は、インジケータの描画ウィンドウが指定されていないというものです。MetaTraderでは、インジケータはどこに描画されるかを明示的に宣言する必要があります。

表示形式には大きく2種類があります。一部のインジケータは移動平均ボリンジャーバンドのように価格チャート上に直接描画されます。一方で、RSIATRのようにチャート下部のサブウィンドウに表示されるものもあります。

Supertrendは価格に追従するオーバーレイ型インジケータであるため、メインチャート上に表示する必要があります。これを明示するために、以下のディレクティブを追加します。

#property indicator_chart_window

これを追加することで、最初の警告は消えます。

グラフィカルプロットの理解

次の警告は、インジケータプロットが定義されていないことを示しています。グラフィカルプロットとは、MetaTraderがインジケータバッファのデータを使ってチャート上に描画する視覚要素のことです。各プロットは、ライン、ヒストグラム、カラー付きローソク足などの1つの視覚要素を表します。MQL5では最大18種類のプロットタイプがサポートされており、インジケータデータの可視化方法に柔軟性が提供されています。各プロットは必ず明示的に宣言する必要があり、ターミナルはそれに基づいて必要な視覚要素を準備します。

今回のSupertrendインジケータでは、3つのプロットを使用する予定です。1つはカスタムカラーのローソク足用で、残り2つはSupertrendライン用です。このため、以下のディレクティブを追加します。

#property indicator_plots 3

これにより2つ目の警告も解消されます。

インジケータバッファとその目的

プロット数を定義した後、MetaTraderは次に「インジケータのバッファサイズが不十分である」という警告を表示します。インジケータバッファとは、後にチャートへ描画されるデータを格納するための特別な配列です。各バッファは1つのデータストリーム(例:始値、ライン値、カラーインデックスなど)を保持します。MetaTraderはこれらのバッファを自動的に読み取り、その内容をもとに対応するプロットを描画します。

今回のSupertrendインジケータは、カスタムローソク足データ、Supertrendバンド、内部トレンド状態など複数のデータストリームを扱うため、事前に十分なバッファを確保する必要があります。そのため、使用するバッファの総数を以下のように宣言します。

#property indicator_buffers 8

これにより、MetaTraderは最大8個のインジケータバッファを割り当てるようになります。このディレクティブを追加することで、すべてのコンパイル警告は消えます。

インジケータバッファ配列の宣言

バッファ数を定義した後は、実際にインジケータバッファとして機能する配列を宣言します。これらの配列はdouble型であり、グローバルスコープで宣言する必要があります。MetaTraderはチャート上のバー数に応じて自動的にサイズを調整するため、これらは動的配列として宣言します。カスタムローソク足データ、Supertrendバンド、および内部トレンド状態のためのバッファをここで定義します。

//+------------------------------------------------------------------+
//| Indicator buffers                                                |
//+------------------------------------------------------------------+
double bufCandleOpen[];
double bufCandleHigh[];
double bufCandleLow[];
double bufCandleClose[];
double bufCandleTrendColor[];

double bufSupertrendUpperBand[];
double bufSupertrendLowerBand[];

double bufTrendState[];

この段階では、配列の宣言のみをおこなっています。しかし、宣言しただけでは不十分です。次のステップでは、各配列をOnInit関数内で特定のバッファインデックスにバインドし、MetaTraderがそれらを正式なインジケータバッファとして認識できるようにします。 

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(){

   ...
   
   //--- Bind arrays to indicator buffers
   SetIndexBuffer(0, bufCandleOpen, INDICATOR_DATA);
   SetIndexBuffer(1, bufCandleHigh, INDICATOR_DATA);
   SetIndexBuffer(2, bufCandleLow, INDICATOR_DATA);
   SetIndexBuffer(3, bufCandleClose, INDICATOR_DATA);
   SetIndexBuffer(4, bufCandleTrendColor, INDICATOR_COLOR_INDEX);
   SetIndexBuffer(5, bufSupertrendUpperBand, INDICATOR_DATA);
   SetIndexBuffer(6, bufSupertrendLowerBand, INDICATOR_DATA);
   SetIndexBuffer(7, bufTrendState, INDICATOR_DATA);    
   
   return INIT_SUCCEEDED;
}

この段階ですでに、すべてのインジケータバッファを宣言し、正しくバインドする準備が整っています。これらのバッファは、ローソク足、Supertrendバンド、トレンド状態を格納するためのストレージコンテナとして機能します。しかし現時点では、まだ意味のあるデータは含まれていません。構造化された方法でデータを格納するまでは、チャート上に何も描画されません。

これらのバッファにデータを格納するために、ここからSupertrendアルゴリズムを実装していきます。MQL5の多くのインジケータと同様に、コアとなる計算ロジックはOnCalculate関数内に配置されます。この関数は、ターミナルに新しい価格データが到着するたびに実行されます。

私たち目的は2つあります。第一に、価格とATRに基づいてSupertrend値を計算することです。第二に、検出されたトレンドに応じてローソク足を色分けすることです。強気のローソク足はSeaGreen、弱気のローソク足はで表示されます。ローソク足の色分けをサポートするために、シンプルなカラーパレットを定義します。

#property indicator_color1 clrBlack, clrSeaGreen

このカラーバッファは数値インデックスを使用して色と対応付けられます。値0は黒に対応し、値1はSeaGreenに対応します。

ヘルパー関数によるコード重複の回避

ローソク足を構築する際、同じバッファ代入処理が繰り返し登場します。このロジックをインラインで記述すると、すぐにコードの重複が発生し、可読性が低下します。これを避けるために、2つのヘルパー関数を定義します。最初の関数は、強気バー用のローソク足バッファを埋め、同時に強気のカラ―インデックスを設定します。

//+---------------------------------------------------------------------------------------------------+
//| Populates candle buffers for a bullish bar and assigns the bullish trend color at the given index |
//+---------------------------------------------------------------------------------------------------+
void SetBullishCandle(int index, double &open[], double &high[], double &low[], double &close[]){
   bufCandleOpen [index]  = open [index];
   bufCandleHigh [index]  = high [index];
   bufCandleLow  [index]  = low  [index];
   bufCandleClose[index]  = close[index];
   bufCandleTrendColor[index] = 1;
}

この関数内では、指定されたバーの始値・高値・安値・終値がインジケータのローソク足バッファへコピーされます。その後、トレンドカラー用バッファが1に設定され、これは強気カラーに対応します。2つ目の関数も同様に処理しますが、対象が弱気ローソク足である点が異なります。

//+---------------------------------------------------------------------------------------------------+
//| Populates candle buffers for a bearish bar and assigns the bearish trend color at the given index |
//+---------------------------------------------------------------------------------------------------+
void SetBearishCandle(int index, double &open[], double &high[], double &low[], double &close[]){
   bufCandleOpen [index]  = open [index];
   bufCandleHigh [index]  = high [index];
   bufCandleLow  [index]  = low  [index];
   bufCandleClose[index]  = close[index];
   bufCandleTrendColor[index] = 0;
}

唯一の違いは、カラーバッファが0に設定される点であり、これは弱気ローソク足を示します。これら2つの関数により、ローソク足の構築と色分けを一貫しておこなうことができ、同時にメインのSupertrendロジックを読みやすくコンパクトに保つことができます。

価格配列を安全に扱う

OnCalculate関数は、始値・高値・安値・終値といった価格データの配列を提供します。これらの配列は定数として扱われるため、カスタム関数へ参照渡しすることはできません。この制約を回避するために、OnCalculate内でこれらの配列のローカルコピーを作成します。

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int32_t rates_total,
   ...
{

   //--- Temporary buffers used to store price data for rendering custom colored candles
   double sOpen [];
   double sHigh [];
   double sLow  [];
   double sClose[];

   ...
   
   return rates_total;
}

これらの配列はワーキングバッファとして機能します。価格データをこれらの配列にコピーし、元の価格データ系列を変更することなく、安全にヘルパー関数へ渡します。

インジケータ初期追加時の初期計算

インジケータが初めてチャートに追加されたとき、prev_calculatedパラメータは0に設定されます。これは、過去の計算結果が存在せず、利用可能なすべての過去データを処理する必要があることを示しています。この状態は次の条件で検出します。

if(prev_calculated == 0)

このブロック内では、まずすべてのインジケータバッファをEMPTY_VALUEで初期化します。

   //--- This block is executed when the indicator is initially attached to a chart
   if(prev_calculated == 0){
   
      //--- Start with clean buffers at first calculation
      ArrayInitialize(bufCandleOpen, EMPTY_VALUE);
      ArrayInitialize(bufCandleHigh, EMPTY_VALUE);
      ArrayInitialize(bufCandleLow,  EMPTY_VALUE);
      ArrayInitialize(bufCandleClose, EMPTY_VALUE);
      ArrayInitialize(bufSupertrendUpperBand, EMPTY_VALUE);
      ArrayInitialize(bufSupertrendLowerBand, EMPTY_VALUE);
   }

MQL5において EMPTY_VALUE は、そのインデックスに何も描画しないようターミナルへ指示するための特別な値です。この挙動はSupertrendバンドにとって非常に有用です。なぜなら、トレンド方向に応じて常に一方のバンドのみが表示される設計となっているためです。

トレンド状態の定義と初期化

Supertrendロジックはトレンド方向に依存しています。任意のバーにおいて、トレンドは強気、弱気、またはまだ確定していない状態のいずれかになります。これらの状態を明確に表現するために、以下の3つの定数を定義します。

const double TREND_BULLISH =  1.0;
const double TREND_BEARISH =  0.0;
const double TREND_NEUTRAL = -1.0;

これらの値は専用のトレンド状態バッファに格納されます。計算を開始する前に、このバッファ全体をニュートラル状態で初期化します。これにより、アルゴリズムは最初の時点ではトレンドが存在しないことを正しく認識できます。

過去データの検証と価格データのコピー

初期計算ブロック内では引き続き、ターミナルから取得した価格配列をローカルのワーキングバッファへコピーします。

   //--- This block is executed when the indicator is initially attached to a chart
   if(prev_calculated == 0){
   
      ...
      
      //--- Copy price data into local working buffers to safely manipulate candle values without altering the original price arrays
      ArrayCopy(sOpen, open);
      ArrayCopy(sHigh, high);
      ArrayCopy(sLow, low);
      ArrayCopy(sClose, close);
              
   }

次に、ATRを計算するために十分な過去データが存在するかどうかを検証します。

   //--- This block is executed when the indicator is initially attached to a chart
   if(prev_calculated == 0){
   
      ...
      
      //--- Ensure sufficient historical data is available to calculate the ATR period.
      if(rates_total < atrPeriod){
         Print("Insufficient bars to initialize Supertrend: ATR period requires more historical data.");
         return rates_total;
      }             
   }

SupertrendはATRに依存するため、少なくともatrPeriodバーがないと先に進むことができません。データが不足している場合は、処理を早期に終了し、より多くのバーが読み込まれるまで待ちます。

ATR値の取得

SupertrendバンドはATR値から算出されます。CopyBufferを使用してATRデータ全体を取得します。

   //--- This block is executed when the indicator is initially attached to a chart
   if(prev_calculated == 0){
   
      ...
      
      //--- Get all ATR values
      int numberOfCopiedATRValues = CopyBuffer(atrHandle, 0, 0, rates_total, atrValues);
      if(numberOfCopiedATRValues == -1){
         Print("Error while copying ATR values: ", GetLastError());
         return rates_total;
      }            
   }

この処理が失敗した場合は、無効なデータを扱ってしまうことを避けるため、処理を中断します。

過去のデータに基づくSupertrendの算出

すべての前提条件が満たされた後、過去のバーを順番に走査し、Supertrendの値を計算していきます。

   //--- This block is executed when the indicator is initially attached to a chart
   if(prev_calculated == 0){
   
      ...
      
      //--- Iterate through historical price data to calculate Supertrend values starting from the first valid ATR window.
      for(int i = atrPeriod - 1; i < rates_total - 1; i++){
         
         //--- Calculate the current bar midpoint and derive the raw Supertrend upper and lower bands using the ATR multiplier
         double barMidpoint    = (high[i] + low[i]) / 2.0;
         double upperBandValue = barMidpoint + atrValues[i] * atrMultiplier;
         double lowerBandValue = barMidpoint - atrValues[i] * atrMultiplier;
         
         //--- Initialize the first valid Supertrend bands once enough ATR data is available and skip further processing for this bar
         if(i == (atrPeriod - 1)){
            bufSupertrendUpperBand[atrPeriod - 1] = upperBandValue;
            bufSupertrendLowerBand[atrPeriod - 1] = lowerBandValue;
            continue;
         }
         
         //--- Handle initial trend resolution when no prior trend has been established
         if(bufTrendState[i - 1] == TREND_NEUTRAL){
         
            if(close[i] > bufSupertrendUpperBand[i - 1] || close[i] < bufSupertrendLowerBand[i - 1]){
               
               if(close[i] > bufSupertrendUpperBand[i - 1]){
                  bufTrendState[i] = TREND_BULLISH;
                  SetBullishCandle(i, sOpen, sHigh, sLow, sClose);
                  bufSupertrendUpperBand[i] = EMPTY_VALUE;
                  bufSupertrendLowerBand[i] = lowerBandValue;
               }
               
               if(close[i] < bufSupertrendLowerBand[i - 1]){
                  bufTrendState[i] = TREND_BEARISH;
                  SetBearishCandle(i, sOpen, sHigh, sLow, sClose);
                  bufSupertrendLowerBand[i] = EMPTY_VALUE;
                  bufSupertrendUpperBand[i] = upperBandValue;
               }
               
            }else{
            
               if(upperBandValue < bufSupertrendUpperBand[i - 1]){
                  bufSupertrendUpperBand[i] = upperBandValue;
               }else{
                  bufSupertrendUpperBand[i] = bufSupertrendUpperBand[i - 1];
               }
               
               if(lowerBandValue > bufSupertrendLowerBand[i - 1]){
                  bufSupertrendLowerBand[i] = lowerBandValue;
               }else{
                  bufSupertrendLowerBand[i] = bufSupertrendLowerBand[i - 1];
               }    
            }           
         }
         
         //--- Maintain or invalidate the bullish trend based on price interaction with the lower Supertrend band
         if(bufTrendState[i - 1] == TREND_BULLISH){
            if(close[i] < bufSupertrendLowerBand[i - 1]){
               bufTrendState[i] = TREND_BEARISH;
               SetBearishCandle(i, sOpen, sHigh, sLow, sClose);
               bufSupertrendLowerBand[i] = EMPTY_VALUE;
               bufSupertrendUpperBand[i] = upperBandValue;
            }else{
               if(lowerBandValue > bufSupertrendLowerBand[i - 1]){
                  bufSupertrendLowerBand[i] = lowerBandValue;
                  SetBullishCandle(i, sOpen, sHigh, sLow, sClose);
                  bufTrendState[i] = TREND_BULLISH;
               }else{
                  bufSupertrendLowerBand[i] = bufSupertrendLowerBand[i - 1];
                  SetBullishCandle(i, sOpen, sHigh, sLow, sClose);
                  bufTrendState[i] = TREND_BULLISH;
               }
            }
         }
         
         //--- Maintain or invalidate the bearish trend based on price interaction with the upper Supertrend band
         if(bufTrendState[i - 1] == TREND_BEARISH){
            if(close[i] > bufSupertrendUpperBand[i - 1]){
               bufTrendState[i] = TREND_BULLISH;
               SetBullishCandle(i, sOpen, sHigh, sLow, sClose);
               bufSupertrendUpperBand[i] = EMPTY_VALUE;
               bufSupertrendLowerBand[i] = lowerBandValue;
            }else{
               if(upperBandValue < bufSupertrendUpperBand[i - 1]){
                  bufSupertrendUpperBand[i] = upperBandValue;
                  SetBearishCandle(i, sOpen, sHigh, sLow, sClose);
                  bufTrendState[i] = TREND_BEARISH;
               }else{
                  bufSupertrendUpperBand[i] = bufSupertrendUpperBand[i - 1];
                  SetBearishCandle(i, sOpen, sHigh, sLow, sClose);
                  bufTrendState[i] = TREND_BEARISH;
               }
            }
         }   
      }              
   }

ループはatrPeriod - 1から開始します。これはATRが有効になる最初のインデックスです。バーごとにミッドポイント価格を計算し、ATRマルチプライヤーを用いて生の上側・下側のSupertrendバンドを導出します。最初の有効バーでは両バンドを初期化し、それ以降のロジックはスキップします。この時点からトレンド判定処理が開始されます。

前回のトレンドがニュートラルの場合、価格と直前のバンドの関係によって強気または弱気トレンドのどちらが成立するかが決定されます。一度トレンドが確立されると、一方のバンドのみが有効となり、反対側のバンドは EMPTY_VALUE によって非表示になります。

前回トレンドが強気の場合、価格が下側バンドを下抜けると弱気への反転が発生します。それ以外の場合は強気トレンドが継続し、下側バンドは調整または前回値を引き継ぎます。弱気ケースも同様のロジックで逆方向に処理されます。終値が上側バンドを上抜けると強気への反転が発生し、継続時は上側バンドが引き締められるか維持されます。各ステップにおいて、ローソク足はヘルパー関数を用いて構築・色分けされ、視覚的一貫性が確保されます。

Supertrendのリアルタイム更新

初期計算が完了した後は、新しいバーが形成されるたびにOnCalculate が再び呼び出されます。この場合、 prev_calculated0でもなくrates_totalとも等しくありません。

   //--- This block is executed on every new bar open
   if(prev_calculated != rates_total && prev_calculated != 0){
   
      //--- Copy price data into local working buffers to safely manipulate candle values without altering the original price arrays
      ArrayCopy(sOpen, open);
      ArrayCopy(sHigh, high);
      ArrayCopy(sLow, low);
      ArrayCopy(sClose, close);
      
      //--- Start with clean buffers
      bufSupertrendLowerBand[rates_total - 1] = EMPTY_VALUE;
      bufSupertrendLowerBand[rates_total - 2] = EMPTY_VALUE;
      bufSupertrendUpperBand[rates_total - 1] = EMPTY_VALUE;
      bufSupertrendUpperBand[rates_total - 2] = EMPTY_VALUE;
   
      //--- Get all ATR values
      int numberOfCopiedATRValues = CopyBuffer(atrHandle, 0, 0, rates_total, atrValues);
      if(numberOfCopiedATRValues == -1){
         Print("Error while copying ATR values: ", GetLastError());
         return rates_total;
      }
      
      //--- Define indices for the most recently closed bar and the bar immediately preceding it
      int elapsedBarIndex      = rates_total - 2;
      int priorElapsedBarIndex = rates_total - 3;
      
      //--- Compute the Supertrend midpoint and derive the upper and lower bands using the ATR multiplier
      double barMidpoint    = (high[elapsedBarIndex] + low[elapsedBarIndex]) / 2.0;
      double upperBandValue = barMidpoint + atrValues[elapsedBarIndex] * atrMultiplier;
      double lowerBandValue = barMidpoint - atrValues[elapsedBarIndex] * atrMultiplier;
      
      //--- Handle Supertrend continuation or reversal logic when the previous bar was bullish
      if(bufTrendState[priorElapsedBarIndex] == TREND_BULLISH){
         if(close[elapsedBarIndex] < bufSupertrendLowerBand[priorElapsedBarIndex]){
            bufTrendState[elapsedBarIndex] = TREND_BEARISH;
            SetBearishCandle(elapsedBarIndex, sOpen, sHigh, sLow, sClose);
            bufSupertrendUpperBand[elapsedBarIndex] = upperBandValue;
            bufSupertrendLowerBand[elapsedBarIndex] = EMPTY_VALUE;
         }else{
            if(lowerBandValue > bufSupertrendLowerBand[priorElapsedBarIndex]){
               bufSupertrendLowerBand[elapsedBarIndex] = lowerBandValue;
               bufSupertrendUpperBand[elapsedBarIndex] = EMPTY_VALUE;
               SetBullishCandle(elapsedBarIndex, sOpen, sHigh, sLow, sClose);
               bufTrendState[elapsedBarIndex] = TREND_BULLISH;
            }else{
               bufSupertrendLowerBand[elapsedBarIndex] = bufSupertrendLowerBand[priorElapsedBarIndex];
               bufSupertrendUpperBand[elapsedBarIndex] = EMPTY_VALUE;
               SetBullishCandle(elapsedBarIndex, sOpen, sHigh, sLow, sClose);
               bufTrendState[elapsedBarIndex] = TREND_BULLISH;
            }
         }
      }
      
      //--- Handle Supertrend continuation or reversal logic when the previous bar was bearish
      if(bufTrendState[priorElapsedBarIndex] == TREND_BEARISH){
         if(close[elapsedBarIndex] > bufSupertrendUpperBand[priorElapsedBarIndex]){
            bufTrendState[elapsedBarIndex] = TREND_BULLISH;
            SetBullishCandle(elapsedBarIndex, sOpen, sHigh, sLow, sClose);
            bufSupertrendLowerBand[elapsedBarIndex] = lowerBandValue;
            bufSupertrendUpperBand[elapsedBarIndex] = EMPTY_VALUE;
         }else{
            if(upperBandValue < bufSupertrendUpperBand[priorElapsedBarIndex]){
               bufSupertrendUpperBand[elapsedBarIndex] = upperBandValue;
               bufSupertrendLowerBand[elapsedBarIndex] = EMPTY_VALUE;
               SetBearishCandle(elapsedBarIndex, sOpen, sHigh, sLow, sClose);
               bufTrendState[elapsedBarIndex] = TREND_BEARISH;
            }else{
               bufSupertrendUpperBand[elapsedBarIndex] = bufSupertrendUpperBand[priorElapsedBarIndex];
               bufSupertrendLowerBand[elapsedBarIndex] = EMPTY_VALUE;
               SetBearishCandle(elapsedBarIndex, sOpen, sHigh, sLow, sClose);
               bufTrendState[elapsedBarIndex] = TREND_BEARISH;
            }
         }
      }   
   }

このブロックは、直近で確定したバーのみを更新し、リペイントを防止します。まず価格データを再度ローカルバッファへコピーし、その後、直近2本のSupertrendバンド値をクリアして、古い描画データが残らないようにします。ATR値は再取得され、重要な2つのインデックスが特定されます。すなわち、直近で確定したバーと、その1本前のバーです。これらのインデックスを用いて新しいSupertrendバンドを計算し、履歴の処理と同じ継続・反転ロジックを適用します。更新対象を最新バーのみに限定することで、計算効率を維持しつつ結果の安定性を確保します。

グラフィックプロットの描画設定

この時点で、すべてのインジケータバッファは正しく定義されており、Supertrendロジックも完全に実装されています。しかし、インジケータをチャートに追加しても、まだ何も表示されません。これは想定された挙動です。MQL5では、値の計算とグラフィック描画は独立した責務として扱われます。バッファには有効なデータが格納されていますが、ターミナル側はまだそれを描画するための設定を認識していません。インジケータを可視化するためには、グラフィックプロットを明示的に設定する必要があります。

グラフィックプロットはPlotIndexSetIntegerなどの関数を用いて設定します。この設定は通常、OnInit関数内でおこない、インジケータバッファを各プロットインデックスにバインドした直後に配置します。

色付きローソク足プロットの設定

最初のプロットは、トレンド方向に応じて動的に色が変化するローソク足を描画するものです。このプロットは DRAW_COLOR_CANDLES モードを使用します。OnInit内では、以下のように最初のプロットを設定します。

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(){

   ...
   
   //--- Configure Graphic Plots   
   PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_COLOR_CANDLES);
   PlotIndexSetDouble (0, PLOT_EMPTY_VALUE, EMPTY_VALUE);   
   
   return INIT_SUCCEEDED;
}

ここでは、プロットインデックス0が最初のインジケータプロットを指します。描画タイプを DRAW_COLOR_CANDLES に設定することで、ターミナルに対して最初の5つのバッファをローソク足データとして扱うよう指示します。この描画モードでは、特定の順序に従った5つのバッファ(Open、High、Low、Close、カラーインデックス)が必要になります。EMPTY_VALUEの設定により、その値がEMPTY_VALUEとしてマークされたローソク足バッファは描画されません。これにより、ロジックが明示的に値をセットした場合のみローソク足を選択的に描画できます。

最初のプロットにおけるローソク足バッファの命名

複数のバッファを1つのローソク足プロットに正しく関連付けるため、以下のプロパティディレクティブでラベルを定義します。

#property indicator_label1 "SupertrendOpen;SupertrendHigh;SupertrendLow;SupertrendClose"

このディレクティブは、最初のプロットにおけるローソク足データバッファに名前を割り当てるものです。順序は非常に重要です。ターミナルはこの順序に従って各バッファを対応するローソク足構成要素へマッピングします。これにより、MQL5はこれらのバッファ群を個別のグラフィカル要素ではなく、1つのカラー付きローソク足プロットとして認識します。また、データウィンドウでインジケータバッファを確認する際の可読性も向上します。

下降トレンド用Supertrendバンドの設定

次のプロットでは、下降トレンド時に表示される上側のSupertrendバンドを描画します。このバンドはシンプルなラインとして表示されます。設定は以下のようにおこないます。

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(){

   ...
   
   //--- Configure Graphic Plots
   
   ...
      
   PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_LINE);
   PlotIndexSetInteger(1, PLOT_LINE_STYLE, STYLE_SOLID);
   PlotIndexSetInteger(1, PLOT_LINE_WIDTH, 1);
   PlotIndexSetInteger(1, PLOT_LINE_COLOR, clrBlack);
   PlotIndexSetDouble (1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetString (1, PLOT_LABEL, "downtrend");  
   
   return INIT_SUCCEEDED;
}

プロットインデックス1は、2番目のインジケータプロットを指します。これはラインとして定義され、スタイルは実線、太さは1ピクセルに設定されます。色は弱気条件と視覚的に一致させるためにに設定します。EMPTY_VALUEの設定はここでも重要な役割を持ちます。バッファがEMPTY_VALUEを含む場合、そのバーではラインは描画されません。これにより、下降トレンド時のみ下降バンドが表示され、それ以外の状況では非表示にすることができます。プロットラベルは、このラインがデータウィンドウおよび凡例にどのように表示されるかを定義します。

上昇トレンド用Supertrendバンドの設定

上昇バンドも下降バンドと同様の方法で設定しますが、異なる点はプロットインデックス、色、およびラベルのみです。

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(){

   ...
   
   //--- Configure Graphic Plots
   
   ...
      
   PlotIndexSetInteger(2, PLOT_DRAW_TYPE, DRAW_LINE);
   PlotIndexSetInteger(2, PLOT_LINE_STYLE, STYLE_SOLID);
   PlotIndexSetInteger(2, PLOT_LINE_WIDTH, 1);
   PlotIndexSetInteger(2, PLOT_LINE_COLOR, clrSeaGreen);
   PlotIndexSetDouble (2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetString (2, PLOT_LABEL, "uptrend"); 
   
   return INIT_SUCCEEDED;
}

このプロットは、強気トレンド時に下側のSupertrendバンドを表示します。ロジックは下降トレンド用の設定と同一であるため、描画ルールも同じものが適用されます。トレンド状態に応じて、常に一方のバンドのみが表示されるようになっています。

インジケータの全般プロパティ設定

最後に、ターミナル上でのインジケータの表示方法に影響する一般的なプロパティを設定します。

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(){

   ...
   
   //--- General indicator configurations
   IndicatorSetString(INDICATOR_SHORTNAME, "Supertrend Indicator");
   IndicatorSetInteger(INDICATOR_DIGITS, Digits()); 
   
   return INIT_SUCCEEDED;
}

ショートネームは、インジケータがチャート上およびインジケータ一覧に表示される際の名称を定義します。複数のインジケータを同時に追加する場合でも可読性を維持するため、簡潔にしておくことが望まれます。digits設定は、すべてのプロット値がシンボルの価格精度に従うことを保証します。これは、異なる小数桁を持つシンボルを扱う場合でも一貫性を保つために重要です。

これらのプロット設定が完了したことで、ターミナルはデータの描画方法を正しく理解できるようになります。トレンドに応じて色付けされたローソク足が表示され、必要な場合のみSupertrendバンドが描画され、EMPTY_VALUEによって不要な描画は適切に抑制されます。この時点でSupertrendインジケータは完全に機能し、視覚的にも正しく、かつ非リペイントな状態になっています。インジケータロジック、バッファ管理、描画設定が一体となり、整合性の取れたシステムとして動作します。


テスト

この段階では、インジケータはエラーなくコンパイルされ、チャート上に正しく描画されるはずです。あとは任意の銘柄および時間足に適用することで、色分けされたローソク足とSupertrendバンドがリアルタイムの市場変化にどのように反応するかを観察できます。

以下は、完成したSupertrendインジケータをゴールドの4時間足チャートに追加したスクリーンショットです。 

Supertrend

これにより、計算ロジックと描画設定の両方が意図通りに動作していることが確認できます。


結論

この記事では、理論の段階を越え、実際に動作するものを構築しました。MQL5で、ライブ市場において安定して動作し、リペイントせず、内部ロジックをクリーンかつ構造的に公開する、完全に機能するSupertrendインジケータを設計・実装しました。これはデモや簡易的な実装ではありません。トレードや分析に即座に使用できる実用的なツールであり、またEAや商用システムの構成要素としても利用できるものです。

さらに重要なのは、成果はインジケータそのものだけではないという点です。その過程で私たちは、健全なインジケータアーキテクチャ、規律あるバッファ管理、ライブデータの安全な取り扱い、そして計算と描画の明確な分離といった原則を実践しました。これらはSupertrendに限らず、すべての本格的なMQL5インジケータに共通する中核的なスキルです。これらの基礎を理解すれば、より高度でカスタマイズされたツールの構築は格段に取り組みやすくなります。

自らインジケータを実装することで、私たちは多くのトレードツールに付きまとう「ブラックボックス性」を取り除きました。挙動、視覚表現、パフォーマンスをすべて制御できるようになり、インジケータがリアルタイムでどのように動作しているのかを正確に理解できるようになりました。この理解と確信こそが、インジケータの「利用者」と「制作者」を分けるものです。

これにより、Custom Indicator Workshopシリーズの第一回は完了となります。今後の回でも同様のアプローチを取り、広く使われているインジケータを分解し、意図と明確性を持って再構築していきます。目的は変わりません。ブラックボックスを減らし、より優れたツールを作り、日々参照するチャートへの理解を深めることです。 

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

添付されたファイル |
supertrend.mq5 (17.83 KB)
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
初心者からエキスパートへ:流動性ゾーンインジケータの開発 初心者からエキスパートへ:流動性ゾーンインジケータの開発
流動性ゾーンの広がりとブレイクアウトレンジの大きさは、リテストが発生する確率に大きな影響を与える重要な変数です。本ディスカッションでは、これらの比率を組み込んだインジケータを開発するための完全なプロセスについて解説します。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
口座ダイナミクスの追跡:MQL5による残高、エクイティ、含み損益の可視化 口座ダイナミクスの追跡:MQL5による残高、エクイティ、含み損益の可視化
カスタムMT5インジケーターを作成し、全ディール履歴を処理して、開始残高、残高、エクイティ、および含み損益を連続曲線として描画します。このインジケーターはバーごとに更新され、複数銘柄にまたがるポジションを追跡し、ローカルキャッシュを利用することで外部依存を回避します。これを使用することで、エクイティと残高の乖離、実現損益と含み損益の関係、そしてリスクを取ったタイミングを分析できます。