
MQL5で自己最適化エキスパートアドバイザーを構築する(第5回):自己適応型取引ルール
テクニカル指標は、明確に定義された取引シグナルを提供するため、アプリケーションの迅速な開発を可能にし、ほとんどのアルゴリズム取引アプリケーションにとって不可欠なツールとなっています。しかし残念ながら、トレーダーはしばしば、テクニカル指標の標準的な使用ルールが通用しない、敵対的な市場状況に直面します。
たとえば、相対力指数(RSI)は、特定の市場における価格変動の強さを測定するために使用されます。RSIの値が30未満の場合に買いエントリーを設定し、70を超えた場合に売りエントリーを設定するのが、トレーダーにとってのベストプラクティスとして広く知られています。
下の図1では、20期間のRSIを米ドル建ての金の終値(日足)に適用しています。予想されるいずれのRSI水準(30以下または70以上)も、3か月という長期間にわたって一度も観測されなかったことにお気づきでしょう。チャートに描かれたトレンドラインを見ると、2019年9月から2019年12月まで、金価格は明確な下降トレンドにあったことがわかります。
しかし、より注意深く観察すると、この下降トレンドの期間中において、RSIインジケーターはショートポジションのエントリーに必要とされるシグナルを示さなかったことに気づくはずです。
図1:市場環境により指標が想定通りに動作せず、意図しない結果を招く可能性
読者の中には、「期間を調整すれば、標準的な結果が得られるはずだ」と合理的に考える方もいるかもしれません。しかし、期間の調整が常に有効な解決策となるとは限りません。たとえば、複数の銘柄を対象とした分析に関心のあるコミュニティメンバーの中には、特定のクロスマーケットパターンを最も的確に捉えるために、あらかじめ決められた期間を使用しているケースもあり、そのような場合には期間を固定せざるを得ません。
また、1つの市場のみを取引している場合、RSIの期間を長くすると問題がさらに深刻化することを理解しておくべきです。反対に、期間を短くすると、市場のノイズ過敏に反応してしまい、取引シグナルの全体的な精度が低下するおそれがあります。
要するに、この問題に安易に対処すると、取引アプリケーションが意図しない動作を引き起こし、チャンスを逃したことによる利益の損失や、終了条件が満たされないことによる想定以上の市場エクスポージャーといったリスクにつながる可能性があります。
方法論の概要
市場の履歴データに応じて取引ルールを柔軟に適応させることができるように、アプリケーションを設計する必要があるのは明白です。本記事の後半では、私たちが提案するソリューションについて詳細に解説します。現時点では、このアプローチを簡潔に要約するならば、「特定の市場における『強い値動き』を識別するためには、まず『平均的な値動き』を正しく把握する必要がある」という論理に基づいています。この考え方に基づいて、取引アプリケーションを、平均を上回る値動きのみを対象とするように設定したり、さらに踏み込んで、平均の2倍あるいは3倍の動きに限定してシグナルを検出するように調整することが可能になります。
取引シグナルを市場の平均的な値動きに対して相対的に判断するように設定することで、長期間にわたりシグナルが発生しないという事態を効果的に回避でき、RSIの計算期間を延々と最適化し続ける必要もなくなると期待されます。
MQL5の始め方
このガイドでは、MetaTrader 5のストラテジーテスターを使用して、戦略のベースラインとなるパフォーマンスを確立するところから始めます。ここで構築する戦略は、読者が自分自身の取引戦略に置き換えるための足場として位置づけられています。本稿では、主要なサポートラインまたはレジスタンスラインをブレイクした際にエントリーするブレイクアウト戦略を、MQL5で構築します。RSIは、ブレイクが有効に機能したことを確認するための判断材料として使用します。また、収益性の変化が取引判断の改善によるものであることを明確にするために、すべてのアプリケーションにおいてストップロスとテイクプロフィットの値は固定しておきます。
有効なサポートラインやレジスタンスラインの定義にはさまざまな見解がありますが、本ガイドでは、サポートラインは過去に価格が反転し上昇に転じた「安値」の水準を指し、レジスタンスラインは強い下落トレンドを生じさせた「高値」の水準であるとシンプルに考えます。これらの価格水準がどの程度の期間、有効であるかは、取引セッションの開始時点では不明です。
これらの水準がブレイクされると、通常は相場のボラティリティが一時的に高まります。RSIは、実際にブレイクアウトが発生しているかを判断する際のガイドとして活用します。サポートラインを下抜けした際にロングポジションを取り、逆にレジスタンスラインを上抜けした場合にのみショートポジションを取る設計です。
サポートおよびレジスタンスラインの特定には、現在の価格と5営業日前の価格との比較を用います。
図2:XAGUSDのサポートとレジスタンスのレベル
議論の一部のパラメータは固定とします。そのため、同じ情報を繰り返さないように、ここで固定パラメータについて説明します。今回の検討では、米ドル建て銀(XAGUSD)の日足価格を対象に、サポートおよびレジスタンスレベルでのブレイクアウト取引をおこないます。戦略の検証には、2017年1月1日から2025年1月28日までの15分足(M15)過去データを用います。
図3:バックテストに使用する期間
また、バックテスト時には[ランダム遅延]の設定を使用します。これは実際の取引環境に近い状況を再現するためです。さらに、ブローカーから取得した実際の市場データを用いてテストをおこなうため、[実ティックに基づく全てのティック]モードを選択しています。
図4:バックテストにおける固定設定(その2)
これで準備が整ったので、MQL5を使ったベースラインアプリケーションの構築に集中しましょう。まず、会話の中で変更しない重要なシステム定数を定義することから始めます。これらの定数は、ストップロスやテイクプロフィットの幅、使用する時間足、取引するポジションサイズなど、取引アプリケーションの動作を制御するためのパラメータです。
//+------------------------------------------------------------------+ //| Self Adapting Trading Rules.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/ja/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/ja/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| System constants | //+------------------------------------------------------------------+ #define RSI_PERIOD 10 //The period for our RSI indicator #define RSI_PRICE PRICE_CLOSE //The price level our RSI should be applied to #define ATR_SIZE 1.5 //How wide should our Stop loss be? #define ATR_PERIOD 14 //The period of calculation for our ATR indicator #define TF_1 PERIOD_D1 //The primary time frame for our trading application #define TF_2 PERIOD_M15 //The secondary time frame for our trading application #define VOL 0.1 //Our trading volume
ここで、ポジションの管理に役立つ取引ライブラリをインポートします。
//+------------------------------------------------------------------+ //| Libraries we need | //+------------------------------------------------------------------+ #include <Trade/Trade.mqh> CTrade Trade;
次に、いくつかの重要なグローバル変数を定義する必要があります。
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int rsi_handler,atr_handler; double rsi[],atr[]; double support,resistance;
MetaTrader 5のアプリケーションは主にターミナル内で発生するイベントによって動作します。各ターミナルイベントが発生すると、それに応じて呼び出されるカスタムメソッドを作成しています。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- setup(); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- release(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- update(); } //+------------------------------------------------------------------+
インジケーターが初めて読み込まれた際に、まずテクニカル指標を設定し、その後、前週に観測された最高値と最安値を基にサポートおよびレジスタンスレベルを設定します。
//+------------------------------------------------------------------+ //| User defined methods | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Initialize our system variables | //+------------------------------------------------------------------+ void setup(void) { //Load our technical indicators atr_handler = iATR(Symbol(),TF_1,ATR_PERIOD); rsi_handler = iRSI(Symbol(),TF_2,RSI_PERIOD,RSI_PRICE); resistance = iHigh(Symbol(),TF_1,5); support = iLow(Symbol(),TF_1,5); }
取引アプリケーションが使用されなくなった場合は、使用しなくなったテクニカル指標を解放する必要があります。
//+------------------------------------------------------------------+ //| Let go of resources we are no longer consuming | //+------------------------------------------------------------------+ void release(void) { //Free up resources we are not using IndicatorRelease(atr_handler); IndicatorRelease(rsi_handler); }
更新関数は2つの部分に分かれています。前半は1日1回実行すべきルーチンタスクを処理し、後半はより短い時間足で頻繁に実行する必要があるタスクを処理します。これにより、突然の異常な市場の動きに対応しやすくなります。
//+------------------------------------------------------------------+ //| Update our system variables and look for trading setups | //+------------------------------------------------------------------+ void update(void) { //Update our system variables //Some duties must be performed periodically on the higher time frame { static datetime time_stamp; datetime current_time = iTime(Symbol(),TF_1,0); //Update the time if(time_stamp != current_time) { time_stamp = current_time; //Update indicator readings CopyBuffer(rsi_handler,0,0,1,rsi); CopyBuffer(atr_handler,0,0,1,atr); //Update our support and resistance levels support = iLow(Symbol(),TF_1,5); resistance = iHigh(Symbol(),TF_1,5); ObjectDelete(0,"Support"); ObjectDelete(0,"Resistance"); ObjectCreate(0,"Suppoprt",OBJ_HLINE,0,0,support); ObjectCreate(0,"Resistance",OBJ_HLINE,0,0,resistance); } } //While other duties need more attention, and must be handled on lower time frames. { static datetime time_stamp; datetime current_time = iTime(Symbol(),TF_2,0); double bid,ask; //Update the time if(time_stamp != current_time) { time_stamp = current_time; bid=SymbolInfoDouble(Symbol(),SYMBOL_BID); ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK); //Check if we have broken either extreme if(PositionsTotal() == 0) { //We are looking for opportunities to sell if(iLow(Symbol(),TF_2,0) > resistance) { if(rsi[0] > 70) Trade.Sell(VOL,Symbol(),bid,(ask + (ATR_SIZE * atr[0])),(ask - (ATR_SIZE * atr[0]))); } //We are looking for opportunities to buy if(iHigh(Symbol(),TF_2,0) < support) { if(rsi[0] < 30) Trade.Buy(VOL,Symbol(),ask,(bid - (ATR_SIZE * atr[0])),(bid + (ATR_SIZE * atr[0]))); } } } } }
最後に、アプリケーションの始めに定義したシステム定数を未定義にします。
//+------------------------------------------------------------------+ //| Undefine the system constants | //+------------------------------------------------------------------+ #undef RSI_PERIOD #undef RSI_PRICE #undef ATR_PERIOD #undef ATR_SIZE #undef TF_1 #undef TF_2 #undef VOL //+------------------------------------------------------------------+
全体として、これが現在のコードベースの外観です。
//+------------------------------------------------------------------+ //| Self Adapting Trading Rules.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/ja/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/ja/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| System constants | //+------------------------------------------------------------------+ #define RSI_PERIOD 10 //The period for our RSI indicator #define RSI_PRICE PRICE_CLOSE //The price level our RSI should be applied to #define ATR_SIZE 1.5 //How wide should our Stop loss be? #define ATR_PERIOD 14 //The period of calculation for our ATR indicator #define TF_1 PERIOD_D1 //The primary time frame for our trading application #define TF_2 PERIOD_M15 //The secondary time frame for our trading application #define VOL 0.1 //Our trading volume //+------------------------------------------------------------------+ //| Libraries we need | //+------------------------------------------------------------------+ #include <Trade/Trade.mqh> CTrade Trade; //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int rsi_handler,atr_handler; double rsi[],atr[]; double support,resistance; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- setup(); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- release(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- update(); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| User defined methods | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Initialize our system variables | //+------------------------------------------------------------------+ void setup(void) { //Load our technical indicators atr_handler = iATR(Symbol(),TF_1,ATR_PERIOD); rsi_handler = iRSI(Symbol(),TF_2,RSI_PERIOD,RSI_PRICE); resistance = iHigh(Symbol(),TF_1,5); support = iLow(Symbol(),TF_1,5); } //+------------------------------------------------------------------+ //| Let go of resources we are no longer consuming | //+------------------------------------------------------------------+ void release(void) { //Free up resources we are not using IndicatorRelease(atr_handler); IndicatorRelease(rsi_handler); } //+------------------------------------------------------------------+ //| Update our system variables and look for trading setups | //+------------------------------------------------------------------+ void update(void) { //Update our system variables //Some duties must be performed periodically on the higher time frame { static datetime time_stamp; datetime current_time = iTime(Symbol(),TF_1,0); //Update the time if(time_stamp != current_time) { time_stamp = current_time; //Update indicator readings CopyBuffer(rsi_handler,0,0,1,rsi); CopyBuffer(atr_handler,0,0,1,atr); //Update our support and resistance levels support = iLow(Symbol(),TF_1,5); resistance = iHigh(Symbol(),TF_1,5); ObjectDelete(0,"Support"); ObjectDelete(0,"Resistance"); ObjectCreate(0,"Suppoprt",OBJ_HLINE,0,0,support); ObjectCreate(0,"Resistance",OBJ_HLINE,0,0,resistance); } } //While other duties need more attention, and must be handled on lower time frames. { static datetime time_stamp; datetime current_time = iTime(Symbol(),TF_2,0); double bid,ask; //Update the time if(time_stamp != current_time) { time_stamp = current_time; bid=SymbolInfoDouble(Symbol(),SYMBOL_BID); ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK); //Check if we have broken either extreme if(PositionsTotal() == 0) { //We are looking for opportunities to sell if(iLow(Symbol(),TF_2,0) > resistance) { if(rsi[0] > 70) Trade.Sell(VOL,Symbol(),bid,(ask + (ATR_SIZE * atr[0])),(ask - (ATR_SIZE * atr[0]))); } //We are looking for opportunities to buy if(iHigh(Symbol(),TF_2,0) < support) { if(rsi[0] < 30) Trade.Buy(VOL,Symbol(),ask,(bid - (ATR_SIZE * atr[0])),(bid + (ATR_SIZE * atr[0]))); } } } } } //+------------------------------------------------------------------+ //| Undefine the system constants | //+------------------------------------------------------------------+ #undef RSI_PERIOD #undef RSI_PRICE #undef ATR_PERIOD #undef ATR_SIZE #undef TF_1 #undef TF_2 #undef VOL //+------------------------------------------------------------------+
それでは、このアルゴリズムが過去の市場データでどれほどの性能を発揮するかを評価してみましょう。MetaTrader 5ターミナルを起動し、先ほど一緒に開発したアプリケーションを選択します。テスト期間や設定については、このセクションの冒頭でご案内していますのでご参照ください。
図5:MetaTrader 5でバックテストを開始する
このエキスパートアドバイザーには入力パラメータはありません。アプリケーションを読み込み、テスト期間を設定したら、MetaTrader 5のストラテジーテスターを起動します。以下の図6は、RSIの古典的な取引ルールに基づいて得られたエクイティカーブです。
図6:古典的なRSI取引戦略に従って作成されたエクイティカーブ
図7は、初回テストにおけるRSIベースの取引アプリケーションのパフォーマンスサマリーを示しています。総純利益は588.60ドルで、シャープレシオは0.85でした。これらの結果は現時点で有望であり、次の取引アプリケーションの改良に向けた目標となります。
図7:バックテストから得られた結果の詳細な要約
初期結果の改善
インジケーターの非標準的な出力が引き起こす可能性のある問題に対処するための提案された解決策に到達しました。従来の70や30という固定レベルを、市場の過去のパフォーマンスに基づいて最適に選ばれたレベルに置き換えるためには、まず対象市場でインジケーターが示す出力の範囲を観察する必要があります。次に、インジケーターの最大値と最小値を示す2本の平行線の中間線を引きます。この中間線が新しい基準点となります。なお、RSIの従来の中間点は50です。
新しい基準点を求めた後、それぞれのRSI値と基準点との差の絶対値を記録し、それらの平均を計算して、その市場における「平均的な動き」の推定値を出します。以降、取引アプリケーションには、この平均的な動きの2倍以上のRSI変化があった場合のみポジションを取るように指示します。この新しいパラメータによってアプリケーションの感度を調整できますが、例えば100など大きすぎる値を設定すると、一切取引をおこなわなくなってしまいます。
これらの変更をアプリケーションに反映させるには、次の修正をおこなう必要があります。
提案する変更項目 | 目的・意図 |
---|---|
新しいシステム定数の作成 | アプリケーション初期バージョンの多くのパラメータを固定しつつ、新たに必要となるいくつかのパラメータを追加する |
ユーザー定義メソッドの変更 | 先に作成したカスタム関数を拡張し、新たに求められる機能を実装できるようにする |
まず最初に、必要な新しいシステム定数を作成しましょう。計算に使用するバーの本数を動的に決定する必要がありますが、これは、利用可能なバーの総数のほぼ半分に相当する整数として定義される、新しいシステム定数「BARS」が担います。さらに、RSIの変化が平均の2倍以上の場合に注目することにしたため、動きの強さの閾値を表す新たな定数「BIG_SIZE」も設定しました。
最後に、サポートおよびレジスタンスレベルは、現在の価格と1週間前(5営業日前)の価格を比較して算出します。
//+---------------------------------------------------------------+ //| System constants | //+---------------------------------------------------------------+ #define RSI_PERIOD 10 //The period for our RSI indicator #define RSI_PRICE PRICE_CLOSE //The price level our RSI should be applied to #define ATR_SIZE 1.5 //How wide should our Stop loss be? #define ATR_PERIOD 14 //The period of calculation for our ATR indicator #define TF_1 PERIOD_D1 //The primary time frame for our trading application #define TF_2 PERIOD_M15 //The secondary time frame for our trading application #define VOL 0.1 //Our trading volume #define BARS (int) MathFloor((iBars(Symbol(),TF_2) / 2)) //How many bars should we use for our calculation? #define BIG_SIZE 2 //How many times bigger than the average move should the observed change be? #define SUPPORT_PERIOD 5 //How far back into the past should we look to find our support and resistance levels?
次に、ポジションを建てる条件を変更します。固定されたインジケーターのレベルではなく、インジケーターのメモリバッファから算出された動的なレベルを基準にエントリーをおこなう点にご注目ください。
//While other duties need more attention, and must be handled on lower time frames. { static datetime time_stamp; datetime current_time = iTime(Symbol(),TF_2,0); double bid,ask; //Update the time if(time_stamp != current_time) { time_stamp = current_time; bid=SymbolInfoDouble(Symbol(),SYMBOL_BID); ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK); //Copy the rsi readings into a vector vector rsi_vector = vector::Zeros(BARS); rsi_vector.CopyIndicatorBuffer(rsi_handler,0,1,BARS); //Let's see how far the RSI tends to deviate from its centre double rsi_midpoint = ((rsi_vector.Max() + rsi_vector.Min()) / 2); vector rsi_growth = MathAbs(rsi_vector - rsi_midpoint); //Check if we have broken either extreme if(PositionsTotal() == 0) { //We are looking for opportunities to sell if(iLow(Symbol(),TF_2,0) > resistance) { if((rsi[0] - rsi_midpoint) > (rsi_growth.Mean() * BIG_SIZE)) Trade.Sell(VOL,Symbol(),bid,(ask + (ATR_SIZE * atr[0])),(ask - (ATR_SIZE * atr[0]))); } //We are looking for opportunities to buy if(iHigh(Symbol(),TF_2,0) < support) { if((rsi[0] - rsi_midpoint) < (-(rsi_growth.Mean() * BIG_SIZE))) Trade.Buy(VOL,Symbol(),ask,(bid - (ATR_SIZE * atr[0])),(bid + (ATR_SIZE * atr[0]))); } } } }
最後に、作成した新しいシステム定数を未定義にする必要があります。
//+------------------------------------------------------------------+ //| Undefine the system constants | //+------------------------------------------------------------------+ #undef RSI_PERIOD #undef RSI_PRICE #undef ATR_PERIOD #undef ATR_SIZE #undef TF_1 #undef TF_2 #undef VOL #undef BARS #undef BIG_SIZE #undef SUPPORT_PERIOD //+------------------------------------------------------------------+
これで、RSIに対する新しい動的取引ルールをテストする準備が整いました。テストを開始する前に、ストラテジーテスターで適切なバージョンのEAを選択していることを確認してください。なお、日付設定はこの記事の冒頭で述べたものと同じです。
図8:テストに適したバージョンの取引アプリケーションを選択する
新しいバージョンのRSI取引アルゴリズムによって得られたエクイティカーブは、最初に得られた結果と似たようなものに見えます。しかし、どのような違いが生まれたのかを明確に把握するために、結果の詳細なサマリーを分析してみましょう。
図9:動的ルール取引アルゴリズムによって生成されたエクイティカーブ
初回のテストでは、136回の取引で合計純利益が588.60ドルでした。新しい戦略では121回の取引で703.20ドルの利益を上げました。つまり、利益率は約19.5%向上し、一方で取引回数は約11%減少しています。これは、新しいシステムがインジケーターの従来のルールと比べて明確な優位性をもたらしていることを示しています。
図10:新しい取引戦略のパフォーマンスをまとめた詳細な結果
結論
今回紹介したソリューションは、MetaTrader 5ターミナルを用いて取引アプリケーションの感度を細かく調整するための設計パターンを提供します。複数の銘柄を分析するトレーダーにとっては、異なる市場間での価格変動の強さを客観的かつ合理的に比較できる新たな視点となり、不意のバグによる思わぬ損失リスクを軽減する助けとなるでしょう。
さらに、従来の固定されたRSIの30・70レベルを、市場の過去データに基づき最適化された動的なレベルに置き換えるこのアルゴリズムは、標準的な指標レベルの発生を待つだけの一般的なトレーダーに対して、明確な競争優位をもたらす可能性があります。
添付ファイル | 詳細 |
---|---|
Self Adapting Trading Rules | 静的かつ固定されたルールを備えた当取引アプリケーションのベンチマーク版 |
Self Adapting Trading Rules V2 | 利用可能な市場データに基づいてルールを適応させる、当取引アプリケーションの改良版 |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/17049
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索