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

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

MetaTrader 5トレーディングシステム |
50 0
Chacha Ian Maroa
Chacha Ian Maroa

はじめに

Supertrendインジケータを用いた手動トレンドトレードは、理論的にはシンプルですが、実践では非常に難しいものです。Supertrendラインが反転し、価格がその方向を確認すると、売買の判断は明確になります。しかし実際の市場では、その明確さは往々にして最悪のタイミングで現れます。エントリーが遅れ、感情が介入し、本来なら単純なエントリーが躊躇や過剰な思考へと変わってしまいます。

シグナルを「見ること」と「実行すること」の間にあるこのギャップこそが、自動化の価値が生まれる領域です。ただしそれは分析を置き換えるためではなく、実行を高速かつ一貫性のあるものにするためです。Supertrendインジケータはこの役割に適しています。トレンド状態を明確に定義し、その状態が変化する瞬間を正確に示すからです。ただし不足しているのは、それらのシグナルを取引へと変換するための、分かりやすく柔軟な実装方法です。

本記事では、その仕組みを構築することに焦点を当てます。Supertrendインジケータからのシグナルを受け取り、確定したトレンド変化が発生した瞬間に即座に取引を実行するMQL5エキスパートアドバイザー(EA)を設計します。ロジックは明確かつ透明に保たれ、EAのすべての判断は明確な市場条件に紐づけられます。

さらに本記事では、単なる取引ロジックにとどまらず、開発プロセス全体を記録します。シグナル検出からポジション管理、リスク制御、ヒストリカルテストまでを実践的に解説し、各ステップを実践的な観点から説明します。最終的には完全に機能するSupertrend EAを構築し、さらに重要な点として、継続的な研究や拡張に利用できる再利用可能なフレームワークを手にすることになります。


Supertrendシグナルロジックの理解

Supertrendインジケータは常に、上昇または下降のいずれかの状態をとります。上昇状態では、Supertrendラインは価格の下にプロットされ、トレーリングサポートとして機能します。下降状態では、この線は価格の上にプロットされ、トレーリングレジスタンスとして機能します。シグナルは、この状態が一方から他方へと遷移したときにのみ生成されます。

上昇シグナルは、直近で確定したローソク足の終値がSupertrendの上側バンドより上で引けたときに発生します。

上昇シグナル

このクローズは、下降状態が終了し、インジケータが上昇状態へとフリップしたことを確認するものです。この時点で上側バンドは無効となり、価格の下側に下側バンドが形成されます。この単一の遷移が、1つの確定した買いシグナルに相当します。

下降シグナルも同様のロジックの逆になります。

下降シグナル

直近の確定ローソク足がSupertrendのローワーバンドを下抜けてクローズした場合、上昇状態は終了し、インジケータは下降へとフリップします。このとき下側バンドは無効となり、上側バンドが価格の上に現れます。この遷移が、1つの確定した売りシグナルを意味します。

ここで重要なのは、シグナルは確定したローソク足のみに基づいて評価されるという点です。EAは未確定のバーを解釈することはありません。ローソク足のクローズを待つことで、Supertrendの状態が確定し、実運用環境においてリペイントやシグナルの不安定性が発生しないことを保証します。

このアプローチにより、各Supertrendシグナルは明確かつ決定論的であり、トレンド変化ごとに一度だけ発生します。継続中のトレンド内でシグナルが繰り返されることはなく、市場状態に曖昧さも存在しません。この明確なシグナル定義が、EA全体のロジックの基盤となります。


取引ルールと設計上の決定

Supertrendのシグナルロジックが明確になったことで、次のステップは、そのシグナルに対してEAが実際の市場環境でどのように反応すべきかを決定することです。この設計の目的は複雑化ではなく、制御性の確保にあります。すべての取引判断は、柔軟性を維持しながらも、明確性と安全性を損なわないように構造化されています。

上昇シグナルが発生した場合、EAはそれを下降状態から上昇状態への確定的な遷移として扱います。この時点で最初にはたすべき責任は、ポジションの方向をシグナルに合わせることです。もしこのEAインスタンスによって開かれた売りポジションが存在する場合、それは直ちにクローズされます。逆方向のポジションを保持することは、インジケータの状態と矛盾し、結果の歪みを引き起こします。整合性が回復した後、買い取引が許可されている場合には、即座に成行買い注文が実行されます。

下降シグナルに対しても同様のロジックが適用されます。Supertrendのローワーバンドを明確に下抜けてクローズした場合、それは下降状態への遷移を意味します。この場合、まずEAが保有するロングポジションはすべてクローズされます。その後、売り取引が許可されている場合には、成行売り注文が発注されます。この一連の処理により、EAは常に現在のSupertrend状態と一致して動作し、矛盾するポジションを持つことはありません。

リスク管理は固定ルールではなく、設定可能な構成要素として扱われます。EAではストップロスの有効・無効を完全に切り替えることができます。この設計により、純粋なシグナルベースの出口を好むトレーダーと、あらかじめリスク上限を設定したいトレーダーの両方に対応できます。

ストップロスが有効な場合、その配置方法は2つの論理モデルから選択できます。1つ目は構造ベースの方法です。ロングポジションでは直近で確定したローソク足の安値にストップロスを設定し、ショートポジションでは同じく高値に設定します。この方法はリスクを直近の価格構造に直接結び付けるものです。2つ目はボラティリティベースの方法であり、この場合は直近のローソク足におけるSupertrendのバンド値を使用し、インジケータ自体を動的な保護ラインとして活用します。

方向性の制御も本設計の重要な要素です。EAはロング専用、ショート専用、または双方向モードで起動することができます。これにより裁量的なバイアスと自動売買を共存させることが可能になります。上位の市場分析で特定方向が有利と判断される場合、その方向のみに取引を制限しつつ、執行とリスク管理は一貫してEAに任せることができます。

ポジションサイズの決定も同様に柔軟に設計されています。固定ロットモードでは、すべての取引が市場状況に関係なく一定の取引量で実行されます。一方、自動モードでは口座残高の一定割合に基づいてロットサイズが計算されます。これにより、口座の成長または縮小に応じてリスクが自動的にスケーリングされ、手動調整の必要がなくなります。

これらの設計思想を組み合わせることで、構造的でありながら適応性の高い取引システムが形成されます。シグナル自体は常に明確かつ客観的であり、実行・リスク・エクスポージャーはすべて透明な設定層によって制御されます。このようにロジックと設計を分離することで、EAは実運用においても実験用途においても扱いやすく、さらに拡張性の高いものになります。 


EAの基盤構築

このセクションでは、EAのボイラープレート(雛形)を構築し、その構造を理解することに焦点を当てます。以降のすべての実装はこの基盤の上に積み重ねられるため、この段階を正しくおこなうことが重要です。

前提条件と環境設定

この段階では、MQL5プログラミング言語の基本を理解していることを前提とします。また、MetaTrader 5 プラットフォームの基本操作、たとえばチャートへのプログラムの適用、入力パラメータの設定、ストラテジーテスターやナビゲーターといった主要モジュールの操作にも慣れていることを想定します。

MetaEditor 5の基本的な使用経験も同様に重要です。これには、新規ソースファイルの作成、コードのコンパイル、コンパイラメッセージの確認などが含まれます。これらのスキルは、スムーズな開発プロセスのために不可欠です。

このEAは、前回の記事で開発したSupertrendインジケータに依存しています。学習プロセスの一貫性を保つため、インジケータの完全なソースコードはsupertrend.mq5というファイル名で本記事にも再度添付されています。このファイルをダウンロードし、ローカル環境でコンパイルしてから先に進むことが重要です。

インジケータのソースファイルは、デフォルトディレクトリ(.../MQL5/Indicators)に配置する必要があります。デフォルトのディレクトリ構造を使用することが強く推奨されます。これによりファイルパス関連の問題を回避でき、EAが実行時にインジケータを正しく参照・読み込みできるようになります。

インジケータソースファイルの配置が完了したら、MetaEditorで開き、コンパイルし、エラーがないことを確認します。このステップは非常に重要です。なぜなら、EAはこのインジケータから直接シグナルを読み取るため、インジケータが存在しない、またはコンパイルされていない場合、EAは明確なエラーを出さずに失敗するか、予測不能な挙動を示す可能性があるためです。

また、EAの完全なソースファイルsupertrendExpert.mq5も添付されています。このファイルをダウンロードし、参照用として開いておくことを推奨します。進行中の実装と完成済みのコードを比較することは、プログラム構造や処理フローを学ぶ上で非常に効果的な方法です。

EAボイラープレートの作成

環境の準備が整ったら、EAの骨組みを作成します。MetaEditorで新しい空のEAソースファイルを作成し、任意の名前を付けます。そのファイルに、以下のボイラープレートコードを貼り付けます。これは今後のすべての開発の基礎となる構造です。

//+------------------------------------------------------------------+
//|                                             supertrendExpert.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"

//+------------------------------------------------------------------+
//| Standard Libraries                                               |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>

//+------------------------------------------------------------------+
//| Custom Enumerations                                              |
//+------------------------------------------------------------------+
enum ENUM_TRADE_DIRECTION  
{ 
   ONLY_LONG, 
   ONLY_SHORT, 
   TRADE_BOTH 
};

enum ENUM_LOT_SIZE_INPUT_MODE 
{ 
   MODE_MANUAL, 
   MODE_AUTO 
};

enum ENUM_STOP_LOSS_MODE
{
   SL_MODE_NONE,
   SL_MODE_SWING_EXTREME,
   SL_MODE_SUPERTREND_VOLATILITY
};

//+------------------------------------------------------------------+
//| User input variables                                             |
//+------------------------------------------------------------------+
input group "Information"
input ulong magicNumber         = 254700680002;                 
input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;

input group "Supertrend configuration parameters"
input int32_t atrPeriod         = 10;
input double  atrMultiplier     = 1.5;

input group "Trade and Risk Management"
input ENUM_TRADE_DIRECTION direction        = ONLY_LONG;
input ENUM_STOP_LOSS_MODE stopLossMode      = SL_MODE_SWING_EXTREME; 
input ENUM_LOT_SIZE_INPUT_MODE lotSizeMode  = MODE_AUTO;
input double riskPerTradePercent            = 1.0;
input double positionSize                   = 0.1;

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
//--- Create a CTrade object to handle trading operations
CTrade Trade;

//--- Bid and Ask
double   askPrice;
double   bidPrice;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   //---  Assign a unique magic number to identify trades opened by this EA
   Trade.SetExpertMagicNumber(magicNumber);

   return(INIT_SUCCEEDED);
}

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

   //--- Notify why the program stopped running
   Print("Program terminated! Reason code: ", reason);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

   //--- Retrieve current market prices for trade execution
   askPrice      = SymbolInfoDouble (_Symbol, SYMBOL_ASK);
   bidPrice      = SymbolInfoDouble (_Symbol, SYMBOL_BID);  
}

//--- UTILITY FUNCTIONS

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

この初期構造では、まだ取引の実行もインジケータデータの読み取りもおこないません。その目的は、設定オプションを定義し、プログラムのライフサイクル関数を確立し、今後追加されるロジックのための基盤を整えることにあります。

ファイルヘッダーとプロパティ

//+------------------------------------------------------------------+
//|                                             supertrendExpert.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内に表示され、特に複数のシステムを同時に稼働させる場合に、このEAを他と区別するのに役立ちます。これらは直接的に取引動作には影響しませんが、明確性、保守性、配布の観点から重要です。

標準ライブラリのインクルード

//+------------------------------------------------------------------+
//| Standard Libraries                                               |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>

Tradeライブラリを含めることで、CTradeクラスにアクセスできるようになります。このクラスは、低レベルの取引リクエストを分かりやすく安全なメソッドにまとめることで、取引の実行および管理を簡素化します。 CTradeクラスは後の実装で使用され、組み込みの取引関数を通じて注文執行やポジション管理を担当します。

カスタム列挙型

カスタム列挙型は、重要な設定領域における選択肢を制御された形で定義するためのものです。

//+------------------------------------------------------------------+
//| Custom Enumerations                                              |
//+------------------------------------------------------------------+
enum ENUM_TRADE_DIRECTION  
{ 
   ONLY_LONG, 
   ONLY_SHORT, 
   TRADE_BOTH 
};

enum ENUM_LOT_SIZE_INPUT_MODE 
{ 
   MODE_MANUAL, 
   MODE_AUTO 
};

enum ENUM_STOP_LOSS_MODE
{
   SL_MODE_NONE,
   SL_MODE_SWING_EXTREME,
   SL_MODE_SUPERTREND_VOLATILITY
};

取引方向の列挙型は、EAがロング、ショート、またはその両方で取引するかを決定します。これにより、ロジック自体を変更することなく、裁量的なフィルタリングをおこなうことができます。ロットサイズの入力モードは、ポジションサイズを固定にするか、リスクに基づいて自動計算するかを決定します。ストップロスモードは、保護的なストップの扱い方を定義し、無効化する場合や、価格構造またはSupertrendの値に基づいて設定する場合などを含みます。列挙型を使用することで入力パラメータが明確になり、不正な設定を防ぐことができます。

ユーザー入力変数

入力変数は、EAの設定パネルを通じて、すべての設定可能な挙動をユーザーに公開します。

//+------------------------------------------------------------------+
//| User input variables                                             |
//+------------------------------------------------------------------+
input group "Information"
input ulong magicNumber         = 254700680002;                 
input ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT;

input group "Supertrend configuration parameters"
input int32_t atrPeriod         = 10;
input double  atrMultiplier     = 1.5;

input group "Trade and Risk Management"
input ENUM_TRADE_DIRECTION direction        = ONLY_LONG;
input ENUM_STOP_LOSS_MODE stopLossMode      = SL_MODE_SWING_EXTREME; 
input ENUM_LOT_SIZE_INPUT_MODE lotSizeMode  = MODE_AUTO;
input double riskPerTradePercent            = 1.0;
input double positionSize                   = 0.1;

Information Group

magicNumber

この値は、このEAインスタンスによって開かれたすべてのポジションを一意に識別するために使用されます。これにより、同一口座内で他のEAや手動トレードによって開かれたポジションと、このEA自身の取引を区別することができます。 

timeframe

この設定は、EAがSupertrendシグナルを読み取る時間足を決定します。EAが低時間足チャートにアタッチされている場合でも、より高い時間足のシグナルに基づいて取引することが可能です。これにより、コードを変更することなくマルチタイムフレーム戦略を実現できます。

Supertrend構成パラメータ

atrPeriod

Supertrendインジケータで使用されるATRの期間を定義します。値を小さくすると価格変動に対して敏感になり、値を大きくするとノイズが減少し、シグナルが滑らかになります。EAはインジケータから直接シグナルを読み取るため、この値はインジケータ側の設定と一致している必要があり、不一致があるとシグナルのずれが発生します。

atrMultiplier

価格からSupertrendバンドまでの距離を制御します。倍率を大きくするとバンドが広がり、シグナルは減少しトレンドは長くなります。逆に小さくするとバンドが狭くなり、シグナル頻度は増加します。この設定はエントリーおよびエグジット頻度に直接影響します。

Trade and Risk Management

direction

この入力は、EAが実行可能なトレード方向を制御します。ONLY_LONGはロングのみ、ONLY_SHORTはショートのみ、TRADE_BOTHは双方向取引を許可します。これは市場に強い方向判断があると判断される場合に有効で、EAの動作をその前提に合わせることができます。

stopLossMode

ストップロスの有無および設定方法を定義します。SL_MODE_NONEではストップロスは設定されず、ポジションは反対シグナルによってのみ決済されます。SL_MODE_SWING_EXTREMEでは、直近で確定したローソク足の高値または安値にストップロスを設定し、価格構造に基づいたリスク管理をおこないます。SL_MODE_SUPERTREND_VOLATILITYでは、Supertrendバンドを基準としてストップロスを配置し、ボラティリティに応じて動的に調整されます。

lotSizeMode

ポジションサイズの計算方法を決定します。MANUAL_MODEでは固定ロットサイズを使用し、AUTO_MODEでは口座残高とリスク割合に基づいて自動計算をおこないます。これにより、口座の増減に応じてポジションサイズを自動的にスケーリングできます。

riskPerTradePercent

この値は自動ロット計算時のみ使用されます。1回のトレードで口座残高の何%をリスクにさらすかを定義します。EAはこの値とストップロス距離を組み合わせてロットサイズを計算し、常に一定のリスク水準を維持します。

positionSize

手動ロットサイズモード時にのみ使用される値です。ボラティリティやストップロス距離に関係なく、すべての取引はこの固定ロットサイズで実行されます。

グローバル変数

グローバル変数は、複数の関数間で共有する必要がある値を保持するために使用されます。

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
//--- Create a CTrade object to handle trading operations
CTrade Trade;

//--- Bid and Ask
double   askPrice;
double   bidPrice;

CTradeオブジェクトは一度だけ生成され、その後すべての取引処理で再利用されます。Bid価格とAsk価格は、銘柄情報への繰り返しアクセスを避け、実行ロジックを整理された状態に保つために保存されます。この段階では、インジケータハンドルやシグナルバッファはまだ宣言されていません。これらは後のセクションで導入されます。

プログラムのライフサイクル関数

OnInit関数は、EAがチャートに読み込まれたときに一度だけ呼び出されます。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   //---  Assign a unique magic number to identify trades opened by this EA
   Trade.SetExpertMagicNumber(magicNumber);

   return(INIT_SUCCEEDED);
}

ここでは、トレードオブジェクトにマジックナンバーを割り当てます。この一行により、EAの動作期間全体を通して取引の所有権が正しく管理されるようになります。

OnDeinit関数は、EAがチャートから取り外されたとき、またはターミナルが終了したときに呼び出されます。

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

   //--- Notify why the program stopped running
   Print("Program terminated! Reason code: ", reason);

}

現時点では、シャットダウンの理由を出力するだけのシンプルな処理になっています。後の段階では、リソースの解放処理もここでおこなうようになります。

OnTick関数は、新しい価格情報が到着したときに実行されます。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

   //--- Retrieve current market prices for trade execution
   askPrice      = SymbolInfoDouble (_Symbol, SYMBOL_ASK);
   bidPrice      = SymbolInfoDouble (_Symbol, SYMBOL_BID); 
   
}

現段階では、Bid価格とAsk価格を更新するのみです。これは意図的な設計です。取引ロジックは段階的に追加することで、挙動を予測しやすくし、デバッグを容易にしています。

この基盤が整ったことで、次のセクションではSupertrendインジケータのハンドルを導入し、実際のシグナル駆動型エントリー処理の実装へと進みます。


Supertrendシグナル検出ロジックの実装

この開発段階では、構造や設定からシグナル検出へと重点が移ります。このEAはSupertrendインジケータのシグナルを直接もとに取引をおこなう設計であるため、インジケータ自体がアルゴリズムの中核要素となります。EAはSupertrendを内部で再現するのではなく、元のインジケータから直接シグナルを読み取り、それに自動的に反応します。このアプローチにより、チャート上でトレーダーが視覚的に確認している内容とロジックの整合性が保たれ、手動実行の必要もなくなります。

EA内でのSupertrendインジケータの利用

最初のステップは、resourceディレクティブを使用してSupertrendインジケータをEA実行ファイルに埋め込むことです。

#resource "\\Indicators\\supertrend.ex5"

この行は、コンパイラに対してSupertrendインジケータを最終的なEAファイル内に組み込むよう指示します。その結果、EAは実行時に外部インジケータファイルへ依存しなくなります。これにより、配布・テスト・デプロイのすべてが大幅に簡素化され、単一の実行ファイルだけで運用が可能になります。EAが読み込まれる際、インジケータは内部的に既に利用可能な状態となり、追加の設定なしで初期化できます。

インジケータデータのグローバルストレージの宣言

Supertrendインジケータをプログラム的に扱うために、インジケータへの参照と出力を保持する変数を定義します。

//--- Supertrend values 
int    supertrendIndicatorHandle;
double upperBandValues[];
double lowerBandValues[];

インジケータハンドルは、バックグラウンドで動作するSupertrendインスタンスへの一意の参照として機能します。このハンドルがなければ、EAはインジケータと通信することも、そのデータを取得することもできません。upperBandValuesとlowerBandValuesの配列は、インジケータのバッファ値を格納するための動的コンテナです。これらのバンドは現在のSupertrendレベルを表しており、トレンド状態を判断するうえで重要な要素となります。過去と現在の値を保持することで、トレンド転換の検出が可能になります。

Supertrendインジケータの初期化

初期化関数内で、SupertrendインジケータはiCustomを使用して生成されます。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   ...

   // Initialize the Supertrend Indicator
   supertrendIndicatorHandle = iCustom(_Symbol, timeframe, "::Indicators\\supertrend.ex5", atrPeriod, atrMultiplier);
   if(supertrendIndicatorHandle == INVALID_HANDLE){
      Print("Error while initializing the Supertrend indicator", GetLastError());
      return(INIT_FAILED);
   }

   return(INIT_SUCCEEDED);
}

この呼び出しは、埋め込まれたインジケータを読み込み、選択された銘柄と時間足に適用し、ビジュアルインジケータと同じパラメータを渡します。これにより、EAが常に視覚的に表示されているものと完全に一致した設定からシグナルを取得できるようになります。iCustomが返すハンドルは、インジケータが正常に初期化されたかどうかを確認するためのものです。もしハンドルが無効であれば、シグナル検出が不可能になるためEAは処理を続行できません。そのため初期化に失敗した場合は、EAを安全に終了させる設計になっています。これにより、未定義状態でシステムが動作することを防ぎます。

インジケータ配列における時系列順序の使用

デフォルトでは、MQL5の配列は昇順でデータを格納し、古い値が先頭に配置されます。しかし、取引ロジックにおいては最新の値を頻繁に参照するため、この構造は扱いづらくなります。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   ...

   //--- Treat the following arrays as timeseries (index 0 becomes the most recent bar)
   ArraySetAsSeries(upperBandValues, true);
   ArraySetAsSeries(lowerBandValues, true);

   return(INIT_SUCCEEDED);
}

これらの配列を時系列として扱うことで、インデックス0は常に直近で確定したバーを指すようになります。インデックス1はその一つ前のバーを表し、その後も同様に続きます。この並びはMQL5におけるマーケットデータの参照方法と一致しているため、シグナルロジックがより直感的になり、エラーも発生しにくくなります。

インジケータバッファ値の読み取り

インジケータが初期化され、データ格納用の配列が準備できたら、次のステップは最新のSupertrend値を取得することです。UpdateSupertrendBandValues関数は、インジケータバッファからデータをEAの配列へコピーする役割を持ちます。

//--- UTILITY FUNCTIONS

//+------------------------------------------------------------------+
//| Fetches recent Supertrend upper and lower band values            |
//+------------------------------------------------------------------+
void UpdateSupertrendBandValues(){

   //--- Get a few Supertrend upper band values
   int copiedUpper = CopyBuffer(supertrendIndicatorHandle, 5, 0, 5, upperBandValues);
   if(copiedUpper == -1)
   {
      Print("Error while copying Supertrend upper band values: ", GetLastError());
      return;
   }

   //--- Get a few Supertrend lower band values
   int copiedLower = CopyBuffer(supertrendIndicatorHandle, 6, 0, 5, lowerBandValues);
   if(copiedLower == -1)
   {
      Print("Error while copying Supertrend lower band values: ", GetLastError());
      return;
   }
   
   if(copiedUpper < 5 || copiedLower < 5){
      Print("Insufficient Supertrend indicator data!");
      return;
   }   
}

内部的には、CopyBufferはインジケータのハンドルを使用して特定のバッファを要求します。バッファインデックスは、どの内部データストリームにアクセスするかを識別するために使われます。一定数の最新データがコピーされることで、EAはトレンド転換を検出するために必要な十分な過去情報を常に保持できます。各コピー操作の後にはエラーチェックが実行されます。もしインジケータがデータを返さない場合や、想定より少ない値しか返さない場合は、関数は早期に終了します。これにより、EAが不完全または無効なデータに基づいて判断を下すことが防がれます。この関数が正常に完了すると、EAは現在のSupertrend状態を反映した上側バンドおよび下側バンドの同期された値を保持することになります。

新しいバーの検出

トレード判断は、新しいローソク足が形成されたタイミングでのみ評価されます。このロジックを支援するために、以下のカスタム関数を定義します。

//+------------------------------------------------------------------+
//| Function to check if there's a new bar on a given chart timeframe|
//+------------------------------------------------------------------+
bool IsNewBar(string symbol, ENUM_TIMEFRAMES tf, datetime &lastTm){

   datetime currentTm = iTime(symbol, tf, 0);
   if(currentTm != lastTm){
      lastTm       = currentTm;
      return true;
   }  
   return false;
}

この仕組みにより、毎ティックごとの無駄な実行が防がれ、シグナルが確定したバーのデータのみに基づいて処理されるようになります。IsNewBar関数は、現在のバーの開始時刻と、グローバルスコープで定義された保存用タイムスタンプ変数を比較することで、新しいバーの発生を判定します。

//--- To help track new bar open
datetime lastBarOpenTime;

この変数は最後に処理されたバーを追跡します。起動時にはゼロに初期化されるため、最初に検出されるバーは必ず新規バーとして扱われます。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   ...

   //--- Initialize global variables
   lastBarOpenTime = 0;

   return(INIT_SUCCEEDED);
}

ゼロ初期化によってクリーンな開始状態が保証され、最初の評価サイクルが失われることを防ぎます。バーの時刻が変化すると、この関数は保存された値を更新し、新しいバーが形成されたことを通知します。この仕組みにより、シグナル検出は各ローソク足につき一度だけ実行され、バックテストとライブ取引の両方で一貫性が保たれます。

強気・弱気Supertrendシグナルの識別

シグナル検出は、Supertrendのバンドがバーごとにどのように切り替わるかによって決定されます。上昇シグナルは、直近で確定したバーでローワーバンドが有効になり、その一つ前のバーではアッパーバンドが有効だった場合に発生します。この遷移は、価格が下降状態から上昇トレンドへ移行したことを示します。

強気シグナルの検出関数は、直近のバンド値を確認し、想定通りの有効データが存在するかをチェックすることで、この条件を判定します。

//+--------------------------------------------------------------------------------+
//| Detects a bullish Supertrend signal when price confirms an upward trend change |
//+--------------------------------------------------------------------------------+
bool IsSupertrendBullishSignal(){

   if(lowerBandValues[1] != EMPTY_VALUE && upperBandValues[2] != EMPTY_VALUE){
      return true;
   }
   
   return false;
}

弱気シグナルの検出ロジックも同様の構造ですが逆方向です。弱気シグナルは、直前のバーで下側バンドが有効で、直近の確定バーで上側バンドが有効になった場合に検出されます。これにより、下降トレンドへの転換が確認されます。

//+---------------------------------------------------------------------------------+
//| Detects a bearish Supertrend signal when price confirms a downward trend change |
//+---------------------------------------------------------------------------------+
bool IsSupertrendBearishSignal(){

   if(upperBandValues[1] != EMPTY_VALUE && lowerBandValues[2] != EMPTY_VALUE){
      return true;
   }
   
   return false;
}

両者を個別の関数として分離することで、ロジックは明確で読みやすくなり、検証もしやすくなります。

ロジックのテスト検証

トレード実行に進む前に、シグナルロジックの妥当性を確認する必要があります。これは、新しいバーが形成された際にティック関数内でログを出力することでおこないます。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

   ...
   
   //--- Run this block only when a new bar is detected on the selected timeframe
   if(IsNewBar(_Symbol, timeframe, lastBarOpenTime)){
   
      UpdateSupertrendBandValues();
      
      if(IsSupertrendBullishSignal()){   
         Print("Bullish Signal");
      }
      
      if(IsSupertrendBearishSignal()){
         Print("Bearish Signal");
      }      
   }   
}

新しいバーごとにSupertrendの値を更新し、両方の条件を評価します。トレンド転換が発生するとジャーナルにメッセージが表示されます。テスト中はトレンドの変化に応じて、これらのメッセージが交互に出力されるようになります。

ジャーナルテストログ

この挙動が確認できることで、EAがSupertrendの転換を正しく解釈し、検出ロジックが正常に動作していることが証明されます。

ここまで到達した時点で、EAはインジケータのデータを読み取り、市場構造の変化を追跡し、トレンド転換を確実に識別できる状態になっています。


トレードおよびポジション管理ロジックの構築

シグナル検出が完了したため、ここからはトレードフェーズに移行します。この段階では、Supertrendシグナルを制御された市場アクションへと変換することに焦点を当てます。目的はシンプルかつ明確であり、EAは常に現在のトレンド方向と一致したポジションのみを保有する必要があります。トレンドが変化した場合には、新規エントリーを検討する前に、逆方向のポジションを即座にクローズしなければなりません。

この動作を実現するために、まず既存ポジションの確認、決済処理、ポジションサイズ計算、適応型ストップロスの算出、そして最終的な注文実行といった各要素を順番に構築します。これらのコンポーネントは段階的に導入され、最終的にメイントレーディングループの中で統合されます。

既存の買いポジションの確認

最初に導入するユーティリティ関数は、非常に単純ですが重要な問いに答えるものです。このEAはすでに買いポジションを保有しているかどうか、という点です。

//+------------------------------------------------------------------+
//| To verify whether this EA currently has an active buy position.  |                                 |
//+------------------------------------------------------------------+
bool IsThereAnActiveBuyPosition(ulong magic){
   
   for(int i = PositionsTotal() - 1; i >= 0; i--){
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0){
         Print("Error while fetching position ticket ", GetLastError());
         continue;
      }else{
         if(PositionGetInteger(POSITION_MAGIC) == magic && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){
            return true;
         }
      }
   }
   
   return false;
}

このロジックは、取引口座内で現在保有されているすべてのポジションを走査することで動作します。各ポジションについて、EAはチケット番号を取得し、2つの条件を確認します。第一に、そのポジションがこのEAインスタンスによるものであること(マジックナンバーによる判定)。第二に、そのポジションタイプが買いポジションであることです。該当するポジションが見つかった時点で関数はtrueを返します。ループが最後まで一致するポジションを見つけられなかった場合はfalseを返します。この関数は重要な設計ルールを強制するために存在しており、EAが同一方向に複数ポジションを重ねて保有することを防ぎます。

既存の売りポジションの確認

次に、アクティブな売りポジションを検出するための第2の関数を定義します。

//+------------------------------------------------------------------+
//| To verify whether this EA currently has an active sell position. |                                 |
//+------------------------------------------------------------------+
bool IsThereAnActiveSellPosition(ulong magic){
   
   for(int i = PositionsTotal() - 1; i >= 0; i--){
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0){
         Print("Error while fetching position ticket ", GetLastError());
         continue;
      }else{
         if(PositionGetInteger(POSITION_MAGIC) == magic && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){
            return true;
         }
      }
   }
   
   return false;
}

この関数の内部ロジックは、前の関数と完全に同じです。唯一の違いは、確認するポジションタイプが異なる点です。買いポジションではなく、このEAによって保有されている売りポジションを検索します。構造も目的も前の関数と同一であるため、この関数で新しい概念が導入されることはありません。単に反対方向の取引を対象に加えることで、ポジション管理ロジックを完成させる役割を果たします。この2つの関数を組み合わせることで、EAは現在保有しているポジションの状況を完全に把握できるようになります。

トレンド転換時のポジション決済

既存ポジションを検出できるようになったら、次はそれらを適切に決済する仕組みが必要になります。この段階ではEAはテイクプロフィットを使用しないため、市場のトレンドが反転した際にはポジションを手動で決済しなければなりません。この処理を実現するために、次のカスタム関数を定義します。

//+------------------------------------------------------------------+
//| To close all position with a specified magic number              |   
//+------------------------------------------------------------------+
void ClosePositionsByMagic(ulong magic) {
    
    for (int i = PositionsTotal() - 1; i >= 0; i--) {
        ulong ticket = PositionGetTicket(i);
        if (PositionSelectByTicket(ticket)) {
            if (PositionGetInteger(POSITION_MAGIC) == magic) {
                ulong positionType = PositionGetInteger(POSITION_TYPE);
                double volume = PositionGetDouble(POSITION_VOLUME);
                if (positionType == POSITION_TYPE_BUY) {
                    Trade.PositionClose(ticket);
                } else if (positionType == POSITION_TYPE_SELL) {
                    Trade.PositionClose(ticket);
                }
            }
        }
    }    
}

この関数は、現在保有されているすべてのポジションを順番に走査し、EAのマジックナンバーと一致するポジションのみを対象とします。これにより、手動で開かれたポジションや他のEAによるポジションには一切影響を与えません。一致した各ポジションについて、EAはそれが買いポジションか売りポジションかを確認し、CTradeオブジェクトを使用して決済要求を送信します。この関数は、ポジション管理におけるリセット機構として機能します。トレンドが変化するたびに、EAは古いトレンドに基づくポジションをすべて解消し、その後で新しいエントリーを検討します。

リスク割合に基づくポジションサイズの計算

自動ロットサイズ機能を実現するため、口座リスクに基づいてロットサイズを計算する関数を導入します。

//+------------------------------------------------------------------+
//| Calculates position size based on a fixed percentage risk of the account balance |
//+------------------------------------------------------------------+
double CalculatePositionSizeByRisk(double stopDistance){
   double amountAtRisk = (riskPerTradePercent / 100.0) * AccountInfoDouble(ACCOUNT_BALANCE);
   double contractSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_CONTRACT_SIZE);
   double volume       = amountAtRisk / (contractSize * stopDistance);
   return NormalizeDouble(volume, 2);
}

ロットサイズの計算は、まず1回のトレードで許容する損失額を求めることから始まります。この金額は、口座残高とユーザーが設定したリスク割合から算出されます。次に、取引対象銘柄のコントラクトサイズとストップロスまでの距離を考慮します。これにより、固定的な前提ではなく、実際の市場エクスポージャーを反映した取引数量が計算されます。最後に、算出された取引数量は、有効な取引精度に合わせて正規化されます。返される値は、市場のボラティリティに応じてリスクが適切に調整されたポジションサイズを表します。この関数により、EAは異なる銘柄、口座残高、ストップロス距離に対しても、手動で再調整することなくポジションサイズを自動的に最適化できます。

適応型ストップロスレベルの計算

トレードを開始する前に、EAはストップロスをどこに配置するのが適切かを判断する必要があります。ユーザーがストップロスを設定しない場合でも、この価格水準はリスク計算に必要となります。この関数は、ポジションサイズ計算関数の直後に追加してください。

//+-----------------------------------------------------------------------------------------------+
//| Calculates the appropriate stop loss price based on position type and selected stop loss mode |
//+-----------------------------------------------------------------------------------------------+
double CalculateAdaptiveStopLossPrice(ENUM_POSITION_TYPE positionType){
   
   double stopLossPrice = 0.000000;
   
   if(positionType == POSITION_TYPE_BUY){
   
      if(stopLossMode == SL_MODE_SWING_EXTREME){
         stopLossPrice = NormalizeDouble(iLow(_Symbol, timeframe, 1), Digits());
      }
      
      if(stopLossMode == SL_MODE_SUPERTREND_VOLATILITY){
         stopLossPrice = NormalizeDouble(lowerBandValues[1], Digits());
      }
      
      if(stopLossMode == SL_MODE_NONE){
         stopLossPrice = NormalizeDouble(lowerBandValues[1], Digits());
      }      
   }
   
   if(positionType == POSITION_TYPE_SELL){
   
      if(stopLossMode == SL_MODE_SWING_EXTREME){
         stopLossPrice = NormalizeDouble(iHigh(_Symbol, timeframe, 1), Digits());
      }
      
      if(stopLossMode == SL_MODE_SUPERTREND_VOLATILITY){
         stopLossPrice = NormalizeDouble(upperBandValues[1], Digits());
      }
      
      if(stopLossMode == SL_MODE_NONE){
         stopLossPrice = NormalizeDouble(upperBandValues[1], Digits());
      }   
   }   
   return stopLossPrice;  
}

この関数は、予定しているポジションタイプを受け取り、選択されたストップロスモードに応じてストップロス価格を決定します。買いポジションの場合、スイング高値・安値方式では直前に確定したローソク足の安値を使用し、ボラティリティベース方式では直前のSupertrendの下側バンドを使用します。売りポジションの場合は、このロジックを逆に適用します。つまり、直前に確定したローソク足の高値、またはSupertrendの上側バンドをストップロスとして使用します。ストップロスを使用しない設定が選択されている場合でも、この関数はSupertrendに基づく価格を返します。これは意図的な設計です。自動ロットサイズの計算にはリスク距離が必要であり、その基準としてはボラティリティに基づく価格が最も安定しているためです。

新規買いポジション

必要な補助機能がすべて揃ったところで、次に買いポジションを発注するための関数を定義します。

//+------------------------------------------------------------------+
//| Function to open a market buy position                           |
//+------------------------------------------------------------------+
bool OpenBuy(double entryPrice, double stopLoss, double lotSize){
   
   if(lotSizeMode == MODE_AUTO){
      lotSize = CalculatePositionSizeByRisk(entryPrice - stopLoss);
   }
   
   if(stopLossMode == SL_MODE_NONE){
      if(!Trade.Buy(lotSize, _Symbol, entryPrice, 0.000000, 0.000000)){
         Print("Error while executing a market buy order: ", GetLastError());
         Print(Trade.ResultRetcode());
         Print(Trade.ResultComment());
         return false;
      }
   }else{
      if(!Trade.Buy(lotSize, _Symbol, entryPrice, stopLoss, 0.000000)){
         Print("Error while executing a market buy order: ", GetLastError());
         Print(Trade.ResultRetcode());
         Print(Trade.ResultComment());
         return false;
      }
   }
   return true;
}

この関数は、まず自動ロットサイズ機能が有効になっているかどうかを確認します。有効な場合は、エントリー価格とストップロス価格の差に基づいてロットサイズを計算します。次に、ストップロスをブローカーへ送信するかどうかを判断します。ストップロスモードが無効になっている場合は、ストップロスを設定せずに注文を発注します。それ以外の場合は、計算されたストップロス価格を注文情報に含めます。すべての注文処理はCTradeオブジェクトによって実行されます。注文が失敗した場合は、実行上の問題を診断できるよう、詳細なエラー情報がログに記録されます。注文が正常に実行された場合はtrueが返され、取引が正しく発注されたことを示します。

新規売りポジション

売り注文を実行する関数は、買い注文の実行関数の直後に追加します。

//+------------------------------------------------------------------+
//| Function to open a market sell position                          |
//+------------------------------------------------------------------+
bool OpenSel(double entryPrice, double stopLoss , double lotSize){
   
   if(lotSizeMode == MODE_AUTO){
      lotSize = CalculatePositionSizeByRisk(stopLoss - entryPrice);
   }
   
   if(stopLossMode == SL_MODE_NONE){
      if(!Trade.Sell(lotSize, _Symbol, entryPrice, 0.000000, 0.000000)){
         Print("Error while executing a market sell order: ", GetLastError());
         Print(Trade.ResultRetcode());
         Print(Trade.ResultComment());
         return false;
      }
   }else{
      if(!Trade.Sell(lotSize, _Symbol, entryPrice, stopLoss, 0.000000)){
         Print("Error while executing a market sell order: ", GetLastError());
         Print(Trade.ResultRetcode());
         Print(Trade.ResultComment());
         return false;
      }
   }
   
   return true;
}

この関数のロジックは、買い注文の実行関数と完全に対称になっています。異なるのは、取引方向とストップロス距離の計算方法だけです。このような対称的な構成にすることで、トレードロジック全体の一貫性が保たれ、保守もしやすくなります。また、一方の関数に加えた改善を、もう一方にも容易に反映させることができます。

OnTickで全体を統合する

これですべてのトレード用ユーティリティ関数が揃いました。次に、OnTick関数を更新します。ここで初めて、シグナル検出とトレード実行が結び付けられます。先ほどまで使用していたPrintによるテスト用コードを削除し、OnTick関数を次の実装に置き換えます。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){

   //--- Retrieve current market prices for trade execution
   askPrice      = SymbolInfoDouble (_Symbol, SYMBOL_ASK);
   bidPrice      = SymbolInfoDouble (_Symbol, SYMBOL_BID); 
   
   //--- Run this block only when a new bar is detected on the selected timeframe
   if(IsNewBar(_Symbol, timeframe, lastBarOpenTime)){
   
      UpdateSupertrendBandValues();
      
      if(IsSupertrendBullishSignal()){   
         if(IsThereAnActiveSellPosition(magicNumber)){
            ClosePositionsByMagic(magicNumber);
            Sleep(50);
         }
         if(direction == TRADE_BOTH || direction == ONLY_LONG){
            OpenBuy(askPrice, CalculateAdaptiveStopLossPrice(POSITION_TYPE_BUY), positionSize);
         }
      }
      
      if(IsSupertrendBearishSignal()){
         if(IsThereAnActiveBuyPosition(magicNumber)){
            ClosePositionsByMagic(magicNumber);
            Sleep(50);
         }
         if(direction == TRADE_BOTH || direction == ONLY_SHORT){
            OpenSel(bidPrice, CalculateAdaptiveStopLossPrice(POSITION_TYPE_SELL), positionSize);
         }
      }      
   }   
}

新しいバーが形成されるたびに、EAはSupertrendの値を更新し、現在のトレンド方向を評価します。上昇シグナルが検出された場合は、まず保有しているすべての売りポジションを決済します。その後、選択された取引方向の設定で買い取引が許可されている場合に限り、新規の買いポジションを発注します。下降シグナルについても同様の処理を逆方向でおこないます。この構成により、取引方向の一貫性が保証されます。EAは相反するポジションを同時に保有することはなく、検出されたトレンドに逆らった取引をおこなうこともありません。

チャート表示の設定

テストへ進む前に、最後に利便性を高める機能を追加します。チャートの表示を自動的に設定することで、EAが正しく動作しているかを視覚的に確認しやすくなります。チャート設定用の関数は、ソースファイルの末尾付近に配置してください。

//+------------------------------------------------------------------+
//| 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_CANDLES)){
      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, clrSeaGreen)){
      Print("Error while setting bullish candles color, ", GetLastError());
      return false;
   }
      
   if(!ChartSetInteger(0, CHART_COLOR_CANDLE_BEAR, clrBlack)){
      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;
}

この関数では、チャートの背景色、ローソク足の色、グリッドの表示・非表示、およびチャート表示モードを設定します。これらの設定により視認性とコントラストが向上し、トレードの動作をより容易に確認できるようになります。いずれかのチャート設定に失敗した場合は、エラー内容をログに記録し、初期化を中止します。これにより、問題が気付かれないまま動作を続けることを防ぎ、EAの挙動を予測可能なものに保ちます。

最後に、このチャート設定処理を OnInit 関数内で有効化します。

//+------------------------------------------------------------------+
//| Expert 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);
}

EAをチャートに適用すると同時に、この設定処理が実行され、チャートの表示環境が自動的に整えられます。これで、EAの開発は完了です。システムはトレンドの検出、ポジション管理、リスク計算、注文執行、そして見やすいトレード画面の提供まで、一連の機能を備えています。


テストとパフォーマンス評価

トレードロジックが完成したので、次のステップは、過去の市場データを用いてExpert Advisorがどのように動作するかを検証することです。実際の市場環境におけるパフォーマンス、安定性、およびリスク特性を評価するため、体系的なバックテストを実施しました。

テストは、XAUUSD(ゴールド) を対象に、1時間足(H1)で実施しました。検証期間は2025年1月1日から2025年12月31日までの1年間です。この期間にはトレンド相場とレンジ相場の両方が含まれており、さまざまな市場環境をバランスよく評価できます。

今回のテストでは、取引方向を買い(ロング)のみに制限しました。つまり、EAは上昇相場でのみ取引をおこない、下降シグナルはすべて無視します。ポジションサイズは自動計算とし、各トレードで口座残高の1%をリスクにさらす設定としました。また、ストップロスを有効にし、直前に確定したローソク足の高値・安値を基準として設定することで、一貫性のある客観的なリスク管理を実現しています。

このテストを完全に再現できるように、2つの補助ファイルを添付しています。1つ目のconfigurations.iniにはストラテジーテスターの環境設定が含まれ、2つ目のparameters.setにはテスト時に使用したEAの入力パラメータが保存されています。これらを使用することで、手作業で設定を再現することなく、同一条件でバックテストを実行できます。

テスト結果は非常に良好なものでした。

テストレポート

初期資金 10,000米ドルから開始し、システムは21,380.79米ドルの純利益を達成しました。これは、1年間で200%を超える収益率に相当します。勝率は34.92%と比較的低いものの、エクイティカーブはテスト期間を通して滑らかで安定した推移を示しました。

エクイティカーブ

急激な資産の減少や不規則な変動は見られず、ドローダウンが適切に抑えられ、規律あるリスク管理が実践されていることが分かります。添付したエクイティカーブの画像からも、この特徴を視覚的に確認できます。資産は着実に増加し、ドローダウンは限定的であり、全体としては高い勝率ではなくリスクリワード比の優位性によって収益を積み重ねるシステムであることが分かります。

もちろん、検証はここで終わりではありません。本EAは、さらなる研究と検証をおこなえるよう、入力パラメータを柔軟に変更できるよう設計されています。銘柄、時間足、リスク水準、ストップロス方式、取引方向フィルターなど、さまざまな項目を変更して検証することができます。これらの設定を変更することで、異なる前提条件や市場環境における戦略の特性を詳しく分析できます。

ぜひ、ご自身でもバックテストをおこない、初期設定にとらわれずさまざまな条件を試してみてください。得られた気付きや改善案、予想外の結果などは、ぜひ記事のコメント欄で共有してください。知見を共有することで、より深い理解や新たな改良案の発見につながるでしょう。


結論

本記事では、Supertrendインジケータを利用した実用的で柔軟なEAを構築する一連の流れを解説しました。最終的に得られるのは、単に動作するコードではありません。手動による発注を不要とし、明確な売買ルールを徹底し、市場の挙動を規律ある再現可能な方法で研究できる実用的なリサーチツールです。

シグナルの解釈から注文執行、リスク管理、バックテストに至るまで、Supertrendベースの自動売買システムを段階的に構築しました。完成したEAは無償で利用でき、設定変更にも対応しているため、そのまま使用することも、新たなアイデアを検証するための土台として拡張することも可能です。また、ストップロス方式、ポジションサイズの計算方法、取引方向フィルターなどをソースコードを書き換えることなく変更できるため、実際の運用だけでなく、体系的な戦略研究にも適しています。

さらに、本記事のもう一つの重要な成果は、開発プロセスそのものを詳細に記録した点です。カスタムインジケータとEAを連携させる方法、インジケータバッファを安全に読み取る方法、新しい市場データへ適切に反応する方法、そしてシグナルを実際の売買へ結び付ける方法など、MQL5開発における重要な技術を実践的に解説しました。これらの知識はSupertrendに限らず、多くのEA開発に応用できます。

インジケータを利用したEA開発をこれから始める方にとっては、実践的な入門資料として役立つでしょう。一方で、経験豊富な開発者にとっても、最適化や機能拡張、他のロジックとの組み合わせをおこなうための堅牢なベースとして活用できます。いずれの場合でも、本記事が継続的に優位性のあるトレード戦略を研究していくための確かな出発点となれば幸いです。

以下の表は、本記事に添付されている各ファイルと、その役割をまとめたものです。



ファイル名 説明
1 supertrend.mq5 トレンド方向とボラティリティに基づく価格バンドを計算するSupertrendインジケータのソースコードです。EAはこのインジケータの値を直接読み取り、売買シグナルを生成します。
2 supertrendExpert.mq5 Supertrendシグナル、リスク管理ルール、およびユーザー設定に基づいて売買を自動実行するEAのソースコードです。
3 configurations.ini ストラテジーテスターの環境設定ファイルです。本記事で使用したバックテスト環境をそのまま再現できます。
4 parameters.set バックテストで使用したEAの入力パラメータを保存したファイルです。リスク設定、取引方向、ストップロス方式などの設定を一括で読み込めます。

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

添付されたファイル |
supertrend.mq5 (17.92 KB)
configurations.ini (1.38 KB)
parameters.set (1.26 KB)
MQL5標準ライブラリエクスプローラー(第7回):CCanvasによるインタラクティブなポジションラベル表示 MQL5標準ライブラリエクスプローラー(第7回):CCanvasによるインタラクティブなポジションラベル表示
MQL5標準ライブラリに含まれるCCanvasを使用して、ポジション情報を可視化するツールの構築方法を解説します。このプロジェクトを通して、標準ライブラリの各種モジュールを扱うスキルを高めるとともに、ライブチャート上で保有ポジションを視覚的に確認・操作できる実用的なツールを作成します。ぜひ最後までお読みいただき、議論にもご参加ください。
ラリー・ウィリアムズの『市場の秘密』(第10回):スマッシュデー反転パターンの自動化 ラリー・ウィリアムズの『市場の秘密』(第10回):スマッシュデー反転パターンの自動化
ルールベースのエキスパートアドバイザー(EA)を構築し、動的なリスク管理、ブレイクアウト確認ロジック、そして「常に1ポジションのみ保有する」売買ルールを組み込みます。読者は、MetaTrader 5のストラテジーテスターと付属のソースコードを用いてバックテストを実施し、結果を再現するとともに、各パラメータがパフォーマンスへ与える影響を検証できます。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
MetaTrader 5機械学習の設計図(第7回):散発的な実験から再現可能な結果へ MetaTrader 5機械学習の設計図(第7回):散発的な実験から再現可能な結果へ
本連載の最新回では、個々の機械学習手法の解説から一歩進み、多くのクオンツトレーダーを悩ませている「リサーチの混沌(Research Chaos)」という問題に焦点を当てます。本記事では、場当たり的なノートブックでの実験から脱却し、再現性・追跡可能性・効率性を備えた、本番運用レベルのパイプラインへ移行する方法について説明します。