
古典的な戦略を再構築する(第12回):EURUSDブレイクアウト戦略
この記事では、MQL5で取引戦略を一緒に構築していきます。ブレイクアウト取引戦略を実装し、それを反復的に改善してその潜在能力を最大限に引き出します。戦略の詳細についていくつか議論していきましょう。
私たちはEURUSDペアに焦点を当て、その動きをH1時間枠で取引します。ブレイクアウト戦略では、まずEURUSDペアで現在提供されている高値と安値を記録します。時間が経過すると、記録した最初の高値と安値に基づいて形成されたチャネルの外で価格レベルが完全に開閉するのを待ちます。
これが発生すると、取引戦略は市場が特定の方向に動き続ける可能性が高いというバイアスを形成します。ただし、この時点ではポジションを取るわけではありません。バイアスが確認された段階でポジションに入ります。価格が完全に開き、最初のチャネルを突破したローソク足の極値を超えて閉じると、チャネルを上回っていればロングポジションを開き、そうでなければショートポジションを開きます。
ここまでで、私たちの指定したシステムは取引の数が多すぎます。収益性の低い取引を除外するために、強さや弱さの他の指標を設定する必要があります。移動平均は市場のトレンドを素早く識別するのに役立ちます。
私たちのシステムは、まず現在市場で提供されている価格を監視し、次に価格がどの方向にチャネルを突破するか、またその突破が後続の価格変動によってサポートされているかを観察します。観察したブレイクアウトが後続の価格変動と一致する場合、その後、移動平均を用いて注文実行のタイミングを図ります。
短期移動平均が長期移動平均を上回っていればロングポジションを好み、その逆の場合はショートポジションを取ります。すべての取引はATRインジケーターを用いて更新され、ストップロスとテイクプロフィットが計算されます。
2020年1月1日から2024年11月30日までの期間に、H1時間枠で取引戦略をテストします。
テクニカル指標は以下のように設定されます。
- 短期移動平均:終値に適用される5期間指数移動平均
- 長期移動平均:終値に適用される60期間指数移動平均
- Average True Range:14期間ATRインジケーター
図1:ブレイクアウト取引アプリケーションの初期状態
しばらくすると、価格レベルがついにチャネルの外側で開閉します。この極値が私たちのバイアスとなり、市場が従うと予測する方向になります。もしその後、価格レベルがバイアスの反対側で閉じると、バイアスが確認されます。それ以外の場合は、取引はおこないません。
図2:取引アプリケーションが市場のバイアスを発見した
価格レベルが私たちのバイアスを確認した場合、市場でポジションを開く自信を持つことができます。私たちの戦略は最初はトレンドフォローです。したがって、価格がチャネルを上抜けた場合、買いの機会を探します。
図3:バイアスが確認された後にポジションが開かれる
MQL5の始め方
私たちの取引アプリケーションは、取引ロジックと基本的なテクニカル分析の概念を組み合わせて作られています。コードに含まれる主要な要素を強調してみましょう。
システムの部分 | 意図した目的 |
---|---|
定数とパラメータ | 移動平均の期間、ロットサイズ、ストップロスとテイクプロフィットの幅など、すべてのテストで一貫性を持たせるために、取引アルゴリズムのいくつかの側面を固定します。 |
グローバル変数 | これらの変数はコードの異なる部分で使用され、使用する際には常に同じ値を指し示すことが重要です。アプリケーション内のグローバル変数のいくつかには、チャネルの高値と安値、市場が従うと信じる方向(バイアス)、その他のテクニカル指標の値などがあります。 |
また、市場の状態を追跡するために取引アプリケーションで必要なその他の重要な変数も定義する必要があります。重要な変数に慣れていきましょう。
変数 | 意図した目的 |
---|---|
Bias | 価格が動いている方向を表します。トレンドが強気の場合は値1、トレンドが弱気の場合は値-1が許可されます。それ以外の場合は0に設定されます。 |
Moving averages | 短期移動平均(ma_f)と長期移動平均(ma_s)によってトレンドが決まります。ma_f[0]>ma_s[0]、かつ価格(c)が短期移動平均線を上回っている場合、買い注文を出します。それ以外の場合、ma_f[0]<ma_s[0]、かつ価格が長期移動平均を下回っている場合、売り注文を出します。 |
Breakout | チャネルレベル(上限または下限)を突破すると、移動方向(バイアス)が設定されます。 |
Breakout levels | ブレイクアウトレベルは、市場が今後どの方向に進み続けると私たちが考えるかを教えてくれます。市場が上限を上回ると、感情は強気になります。 |
Signal confirmation | シグナルの確認がなければ、取引はおこなわれません。ブレイクアウト後に市場がその方向を維持すれば、シグナルが確認されます。確認が失われた場合、ポジションを調整またはクローズすることができます。 |
Order management | おこなう取引は、現在市場で観察されているバイアスによって異なります。上昇トレンド(bias == 1)の場合、コマンド「Trade.Buy(vol,Symbol(),ask,channel_low,0,"VolatilityDoctorAI");」が送信されます。それ以外の場合、下降トレンド(bias==-1)の場合は、コマンド「Trade.Sell(vol, Symbol(), bid, channel_high, 0, "Volatility Doctor AI");」が送信されます。 |
Stop loss | 最初は買いの場合はchannel_low、売りの場合はchannel_highに設定され、その後ATR値を使用して更新されます。 |
これで、戦略の構成要素の概念的なレイアウトができたので、一緒に取引戦略の構築を始めましょう。まず、取引アプリケーションの詳細を指定する必要があります。
//+------------------------------------------------------------------+ //| MTF Channel 2.mq5 | //| Gamuchirai Zororo Ndawana | //| https://www.mql5.com/ja/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com/ja/gamuchiraindawa" #property version "1.00"
次に、取引ライブラリをロードします。
//+------------------------------------------------------------------+ //| Library | //+------------------------------------------------------------------+ #include <Trade/Trade.mqh> CTrade Trade;
一部のテクニカル指標の期間など、取引アプリケーションの定数を定義します。
//+------------------------------------------------------------------+ //| Constants | //+------------------------------------------------------------------+ const int ma_f_period = 5; //Slow MA const int ma_s_period = 60; //Slow MA
ここで、エンドユーザーが調整できる入力を定義しましょう。テクニカル指標を固定しているため、エンドユーザーは多数のパラメータに圧倒されることはありません。
//+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input group "Money Management" input int lot_multiple = 5; //Lot Multiple input int atr_multiple = 5; //ATR Multiple
グローバル変数は、ほとんどのプログラムで使用します。
//+------------------------------------------------------------------+ //| Global varaibles | //+------------------------------------------------------------------+ double channel_high = 0; double channel_low = 0; double o,h,l,c; int bias = 0; double bias_level = 0; int confirmation = 0; double vol,bid,ask,initial_sl; int atr_handler,ma_fast,ma_slow; double atr[],ma_f[],ma_s[]; double bo_h,bo_l;
取引アプリケーションが初めて読み込まれると、テクニカル指標を読み込み、その他の必要な市場データを準備するための特殊な関数が呼び出されます。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- setup(); //--- return(INIT_SUCCEEDED); }
エキスパートアドバイザー(EA)を使用しなくなった場合は、使用しなくなったリソースを解放する必要があります。
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- IndicatorRelease(atr_handler); IndicatorRelease(ma_fast); IndicatorRelease(ma_slow); }
更新された価格を受け取るたびに、グローバル変数を更新し、新しい取引機会を確認します。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- If we have positions open if(PositionsTotal() > 0) manage_setup(); //--- Keep track of time static datetime timestamp; datetime time = iTime(Symbol(),PERIOD_CURRENT,0); if(timestamp != time) { //--- Time Stamp timestamp = time; if(PositionsTotal() == 0) find_setup(); } }
次の関数は、テクニカル指標を読み込み、市場データを取得する役割を担います。
//+---------------------------------------------------------------+ //| Load our technical indicators and market data | //+---------------------------------------------------------------+ void setup(void) { channel_high = iHigh(Symbol(),PERIOD_M30,1); channel_low = iLow(Symbol(),PERIOD_M30,1); vol = lot_multiple * SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN); ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high); ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low); atr_handler = iATR(Symbol(),PERIOD_CURRENT,14); ma_fast = iMA(Symbol(),PERIOD_CURRENT,ma_f_period,0,MODE_EMA,PRICE_CLOSE); ma_slow = iMA(Symbol(),PERIOD_CURRENT,ma_s_period,0,MODE_EMA,PRICE_CLOSE); }
戦略が初めて読み込まれると、市場で提供されている現在の高値と安値がマークされます。こうすることで、今後観察するすべての価格をコンテキストとともに観察でき、最初に到着したときに見た初期価格レベルと比較することができます。
//+---------------------------------------------------------------+ //| Update channel | //+---------------------------------------------------------------+ void update_channel(double new_high, double new_low) { channel_high = new_high; channel_low = new_low; ObjectDelete(0,"Channel High"); ObjectDelete(0,"Channel Low"); ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high); ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low); }
ポジションを開いている場合は、それに応じてストップロスとテイクプロフィットの値を更新する必要があります。リスク設定が市場の現在のボラティリティレベルに関係するように、ATRの倍数を使用してリスク設定を調整します。
//+---------------------------------------------------------------+ //| Manage setup | //+---------------------------------------------------------------+ void manage_setup(void) { bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); CopyBuffer(atr_handler,0,0,1,atr); Print("Managing Position"); if(PositionSelect(Symbol())) { Print("Position Found"); initial_sl = PositionGetDouble(POSITION_SL); } if(bias == 1) { Print("Position Buy"); double new_sl = (ask - (atr[0] * atr_multiple)); Print("Initial: ",initial_sl,"\nNew: ",new_sl); if(initial_sl < new_sl) { Trade.PositionModify(Symbol(),new_sl,0); Print("DONE"); } } if(bias == -1) { Print("Position Sell"); double new_sl = (bid + (atr[0] * atr_multiple)); Print("Initial: ",initial_sl,"\nNew: ",new_sl); if(initial_sl > new_sl) { Trade.PositionModify(Symbol(),new_sl,0); Print("DONE"); } } }
ポジションがない場合は、先に概説したルールに従って取引の機会を特定します。価格が見つかる最初のチャネルから抜け出す強力な価格変動を観察しようとしていることを思い出してください。その後、価格レベルが同じ方向に動き続け、先ほど作成したオープンチャネルを越えなければ、取引を実行するのに十分な自信が得られます。
//+---------------------------------------------------------------+ //| Find Setup | //+---------------------------------------------------------------+ void find_setup(void) { //--- We are updating the system o = iOpen(Symbol(),PERIOD_CURRENT,1); h = iHigh(Symbol(),PERIOD_CURRENT,1); l = iLow(Symbol(),PERIOD_CURRENT,1); c = iClose(Symbol(),PERIOD_CURRENT,1); bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); CopyBuffer(atr_handler,0,0,1,atr); CopyBuffer(ma_fast,0,0,1,ma_f); CopyBuffer(ma_slow,0,0,1,ma_s); //--- If we have no market bias if(bias == 0) { //--- Our bias is bullish if ( (o > channel_high) && (h > channel_high) && (l > channel_high) && (c > channel_high) ) { bias = 1; bias_level = h; bo_h = h; bo_l = l; mark_bias(h); } //--- Our bias is bearish if ( (o < channel_low) && (h < channel_low) && (l < channel_low) && (c < channel_low) ) { bias = -1; bias_level = l; bo_h = h; bo_l = l; mark_bias(l); } } //--- Is our bias valid? if(bias != 0) { //--- Our bearish bias has been violated if ( (o > channel_high) && (h > channel_high) && (l > channel_high) && (c > channel_high) && (bias == -1) ) { forget_bias(); } //--- Our bullish bias has been violated if ( (o < channel_low) && (h < channel_low) && (l < channel_low) && (c < channel_low) && (bias == 1) ) { forget_bias(); } //--- Our bullish bias has been violated if ( ((o < channel_high) && (c > channel_low)) ) { forget_bias(); } //--- Check if we have confirmation if((confirmation == 0) && (bias != 0)) { //--- Check if we are above the bias level if ( (o > bias_level) && (h > bias_level) && (l > bias_level) && (c > bias_level) && (bias == 1) ) { confirmation = 1; } //--- Check if we are below the bias level if ( (o < bias_level) && (h < bias_level) && (l < bias_level) && (c < bias_level) && (bias == -1) ) { confirmation = 1; } } } //--- Check if our confirmation is still valid if(confirmation == 1) { //--- Our bias is bullish if(bias == 1) { //--- Confirmation is lost if we fall beneath the breakout level if ( (o < bias_level) && (h < bias_level) && (l < bias_level) && (c < bias_level) ) { confirmation = 0; } } //--- Our bias is bearish if(bias == -1) { //--- Confirmation is lost if we rise above the breakout level if ( (o > bias_level) && (h > bias_level) && (l > bias_level) && (c > bias_level) ) { confirmation = 0; } } } //--- Do we have a setup? if((confirmation == 1) && (bias == 1)) { if(ma_f[0] > ma_s[0]) { if(c > ma_f[0]) { Trade.Buy(vol,Symbol(),ask,channel_low,0,"Volatility Doctor AI"); initial_sl = channel_low; } } } if((confirmation == 1) && (bias == -1)) { if(ma_f[0] < ma_s[0]) { if(c < ma_s[0]) { Trade.Sell(vol,Symbol(),bid,channel_high,0,"Volatility Doctor AI"); initial_sl = channel_high; } } } Comment("O: ",o,"\nH: ",h,"\nL: ",l,"\nC:",c,"\nC H: ",channel_high,"\nC L:",channel_low,"\nBias: ",bias,"\nBias Level: ",bias_level,"\nConfirmation: ",confirmation,"\nMA F: ",ma_f[0],"\nMA S: ",ma_s[0]); }
価格レベルが最初に設定したチャネルを突破したらと、チャネルを突破したローソク足によって作られた極値をマークします。その極値が私たちのバイアスレベルとなります。
//+---------------------------------------------------------------+ //| Mark our bias levels | //+---------------------------------------------------------------+ void mark_bias(double f_level) { ObjectCreate(0,"Bias",OBJ_HLINE,0,0,f_level);the }
最後に、価格レベルが以前に突破した後に取引チャネル内に戻った場合、古いチャネルは無効であるとみなし、チャネルの新しい位置をブレイクアウトローソク足によって作成されたレベルに更新します。
//+---------------------------------------------------------------+ //| Forget our bias levels | //+---------------------------------------------------------------+ void forget_bias() { update_channel(bo_h,bo_l); bias = 0; bias_level = 0; confirmation = 0; ObjectDelete(0,"Bias"); } //+------------------------------------------------------------------+
これで、ブレイクアウト取引戦略をバックテストする準備が整いました。私はこのアプリケーションに「MTF Channel 2」という名前を付けました。これは、「Multiple Time Frame Channel」の略です。H1時間枠でEURUSD銘柄を選択しました。テストの日程は先ほど指定した日程と同じです。読者は、これら3つの特定の設定が3つのテストすべてで固定されていることに気付くでしょう。
図4:最初のバックテストに使用された最初の設定バッチ
これらは設定したすべてのパラメータではありません。実際の取引シナリオを模倣するためにランダムな遅延設定を選択しました。これにより、経験する遅延が異なる場合があります。また、実際のティックに基づいてテストをモデル化することを選択し、実際の取引の体験に忠実に近いものを得るようにしました。
図5:戦略をテストするために選択された2番目の設定
EAで使用する設定はすべてのテストで同じに固定します。これらの設定を同じに保つことで、より良い取引ルールを選択することによって引き起こされる収益性を明確に分離するのに役立ちます。
図6:資金管理設定
戦略を実際に見てみましょう。下の図7では、スクリーンショットの右側に、アプリケーションが決定を下すために使用している内部変数が表示されています。すべての取引は、確認が1に設定されている場合にのみ実行されることに注意してください。
図7:EURUSDペアでの取引戦略のバックテスト
残念ながら、戦略が損失を生んでいたことがわかります。これは改善の余地があることを示しています。
図8:バックテストに関連するグラフを表示する
先ほどおこなったテストの詳細を見てみましょう。戦略は合計53件の取引を識別し、そのうち70%が不利益でした。シャープレシオは負の値であり、これらは良くないパフォーマンス指標です。
一方で、平均利益は平均損失よりも大きいという良い点があります。では、どのようにしてより良いパフォーマンスを達成できるか見ていきましょう。私たちは、総損失と平均損失に対するコントロールを強化し、同時に平均利益と利益のある取引の割合を最大化したいと考えています。
図9:バックテストの詳細
最初の結果を改善する
バックテストを見ていると、EAが同じミスを繰り返しているのを見ていて非常にイラつきました。損失のほとんどは、価格が単なる一時的な変動であるにもかかわらず、すべての条件を満たしてしまったために取引をしてしまったことが原因です。この問題を解決する唯一の方法は、市場の弱い動きと強い動きをより正確に区別できる条件を選ぶことです。
その一つの方法として、EURとUSDのパフォーマンスを共通のベンチマークに対して比較する方法があります。ここではGBPを使います。ポジションを開く前に、EURGBPとGBPUSDのペアの動きが一致しているかを確認します。つまり、チャートでEURUSDが強い強気トレンドを示している場合、EURGBPも同じ方向に動いていることを確認し、さらにGBPUSDも強気トレンドであることを期待します。
言い換えれば、もしEURUSDの価格がユーロがドルよりも高くなる方向に動いているなら、その信頼性を高めるために、ユーロがGBPに対しても上昇し、同時にドルがGBPに対して下落していることを確認する必要があります。この3つの通貨ペアにおける動きが一致すれば、偽のブレイクアウトを識別する助けになると考えています。3つの市場すべてに同時に影響を及ぼす変動は、利益を得られる非常に強力な動きである可能性が高いと考えています。
この変更を実装するために、元の取引戦略にいくつかのコードを追加します。まず、EURGBPとGBPUSDの価格を追跡するための新しいグローバル変数を作成します。また、これらの通貨ペアの動向を追跡するために、技術指標も適用する必要があります。
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ double channel_high = 0; double channel_low = 0; double o,h,l,c; int bias = 0; double bias_level = 0; int confirmation = 0; double vol,bid,ask,initial_sl; int atr_handler,ma_fast,ma_slow; double atr[],ma_f[],ma_s[]; double bo_h,bo_l; int last_trade_state,current_state; int eurgbp_willr, gbpusd_willr; string symbols[] = {"EURGBP","GBPUSD"};
EAを初めて読み込むときは、ベンチマーク銘柄で発生しているプライスアクションを追跡するために、いくつかの追加手順を実行する必要があります。これらの更新はsetup関数に実装されます。
//+---------------------------------------------------------------+ //| Load our technical indicators and market data | //+---------------------------------------------------------------+ void setup(void) { //--- Select the symbols we need SymbolSelect("EURGBP",true); SymbolSelect("GBPUSD",true); //--- Reset our last trade state last_trade_state = 0; //--- Mark the current high and low channel_high = iHigh("EURUSD",PERIOD_M30,1); channel_low = iLow("EURUSD",PERIOD_M30,1); ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high); ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low); //--- Our trading volums vol = lot_multiple * SymbolInfoDouble("EURUSD",SYMBOL_VOLUME_MIN); //--- Our technical indicators atr_handler = iATR("EURUSD",PERIOD_CURRENT,14); eurgbp_willr = iWPR(symbols[0],PERIOD_CURRENT,wpr_period); gbpusd_willr = iWPR(symbols[1],PERIOD_CURRENT,wpr_period); ma_fast = iMA("EURUSD",PERIOD_CURRENT,ma_f_period,0,MODE_EMA,PRICE_CLOSE); ma_slow = iMA("EURUSD",PERIOD_CURRENT,ma_s_period,0,MODE_EMA,PRICE_CLOSE); }
同様に、この取引アプリケーションが使用されなくなった場合、リリースする追加のテクニカル指標がいくつかあります。
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- IndicatorRelease(eurgbp_willr); IndicatorRelease(gbpusd_willr); IndicatorRelease(atr_handler); IndicatorRelease(ma_fast); IndicatorRelease(ma_slow); }
OnTick関数は同じままです。ただし、呼び出される関数は変更されます。まず、チャネルを更新するたびに、フォローしている市場の3つのチャネルを更新する必要があります。1つはEURUSD、2つ目はEURGBP、最後はGBPUSDです。
//+---------------------------------------------------------------+ //| Update channel | //+---------------------------------------------------------------+ void update_channel(double new_high, double new_low) { channel_high = new_high; channel_low = new_low; ObjectDelete(0,"Channel High"); ObjectDelete(0,"Channel Low"); ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high); ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low); }
プログラムの大部分は同じままですが、最も重要な変更点は、取引アプリケーションが取引を実行するかどうかを決定する前に、他の2つの市場をチェックするようになったことです。もしEUR/USDで見られるブレイクアウトが真の強さに支えられているというファンダメンタルズの信頼が得られた場合、ポジションを取ることにします。これらの更新は、find_setup関数に反映されます。
また、この関数が、ブレイクアウト戦略アプリケーションの前バージョンでは定義されていなかった新しい関数を呼び出していることにもお気づきかもしれません。追加された確認機能は、2つのベンチマーク市場をチェックし、基本的な取引条件が満たされているかどうかを確認します。
//+---------------------------------------------------------------+ //| Find Setup | //+---------------------------------------------------------------+ void find_setup(void) { //--- I have omitted code pieces that were unchanged //--- Do we have a setup? if((confirmation == 1) && (bias == 1) && (current_state != last_trade_state)) { if(ma_f[0] > ma_s[0]) { if(c > ma_f[0]) { if(additional_confirmation(1)) { Trade.Buy(vol,"EURUSD",ask,channel_low,0,"Volatility Doctor"); initial_sl = channel_low; last_trade_state = 1; } } } } if((confirmation == 1) && (bias == -1) && (current_state != last_trade_state)) { if(ma_f[0] < ma_s[0]) { if(c < ma_s[0]) { if(additional_confirmation(-1)) { Trade.Sell(vol,"EURUSD",bid,channel_high,0,"Volatility Doctor"); initial_sl = channel_high; last_trade_state = -1; } } } } }
この関数は、市場のノイズと真の強さを見分けるのに役立つはずです。関連する他の市場で確認をおこなうことで、常に最も有力な取引を選択できるようにしたいと考えています。
//+---------------------------------------------------------------+ //| Check for true strength | //+---------------------------------------------------------------+ bool additional_confirmation(int flag) { //--- Do we have additional confirmation from our benchmark pairs? //--- Record the average change in the EURGBP and GBPUSD Market vector eurgbp_willr_f = vector::Zeros(1); vector gbpusd_willr_f = vector::Zeros(1); eurgbp_willr_f.CopyIndicatorBuffer(eurgbp_willr,0,0,1); gbpusd_willr_f.CopyIndicatorBuffer(gbpusd_willr,0,0,1); if((flag == 1) && (eurgbp_willr_f[0] > -50) && (gbpusd_willr_f[0] < -50)) return(true); if((flag == -1) && (eurgbp_willr_f[0] < -50) && (gbpusd_willr_f[0] > -50)) return(true); Print("EURGBP WPR: ",eurgbp_willr_f[0],"\nGBPUSD WPR: ",gbpusd_willr_f[0]); return(false); }
このバージョンのアプリケーションは「MTF EURUSD Channel」と名付けられます。最初に作成したバージョンはより汎用的で、ターミナル内の他のシンボルでも簡単に取引できる設計でした。しかし今回のバージョンでは、EURGBPとGBPUSDをベンチマークとして使用するため、より専門的な構成となっており、EURUSDペアの取引に特化しています。読者の皆さんは、今回のテスト条件が最初のテストとすべて同一であることにお気づきになるでしょう。このバックテストは、最初のテストと同様に、2020年1月1日から2024年11月30日までの期間、同じ時間足を用いて実施されます。
図10:EURUSDチャネルブレイクアウト戦略のバックテスト用の最初の設定
こちらの手順に沿って設定を進める場合は、[Modelling]オプションを[Every tick based on real ticks]に設定すると、インターネット接続の状況によっては時間がかかる可能性があることにご注意ください。MT5ターミナルがブローカーから詳細なデータを取得して、できる限り現実に近い市場の動きを再現しようとするためです。そのため、処理が完了するまでに数分かかることがありますが、驚かずにお待ちください。処理中にコンピュータの電源を切ったりしないようにご注意ください。
図11:2番目の設定は、最初のテストで使用した設定と同じにする必要がある
ロット倍率を1に設定するということは、すべての取引が最小ロットサイズでおこなわれるという意味です。もし私たちのシステムが最小ロットサイズでも利益を出せるようになれば、ロット倍率を上げることでその利益をさらに増やすことができます。しかし、システムが最小ロットで利益を出せない場合、ロットサイズを増やしても何の意味もありません。
図12:アプリケーションの動作を制御するために使用するパラメータ
これで、履歴データに基づいて取引システムがどのように機能するかを確認できるようになりました。なお、このバージョンのシステムでは一度に3つの市場を監視します。まず、EURUSDペアを常に追跡し、そこからバイアスを取得します。
図13:EURUSDペアで動作しているシステム
ポジションを開くことができるのは、以下の図14と15に示すように、EURGBPとGBPUSDのペアが反対方向に推移しているのを確認した場合のみです。ウィリアムズパーセントレンジを使用して、2つの市場のトレンドを判断します。WPRが50レベルを上回っている場合、トレンドは強気であるとみなします。
図14:最初の確認ペア、GBPUSD
この例では、EURUSDを購入する取引機会が見つかりました。この機会を特定できたのは、2つの市場におけるWPR(Williams Percent Range)の値が50レベルを挟んで反対側に位置していたからです。このような不均衡は、市場が大きく変動する前兆である可能性が高く、ブレイクアウト戦略にとって理想的な状況となります。
図15:2番目のベンチマークペア
下の図9は、シミュレーション取引口座の残高が時間の経過とともにどのように変化するかを示しています。 私たちの目標は、戦略が失敗する理由を深く理解し、その弱点を改善することです。
図16:時間の経過に伴う口座残高をグラフ化する
残念ながら、システムに加えた変更により、取引アプリケーションの収益性が低下しました。平均損失と利益は同じ額だけ増加しました。そして、利益の出る取引の割合はわずかに減少しました。
図17:バックテストの詳細な結果
改善への最後の試み
私たちは、最も重要なポイントである「収益性」の改善に失敗しました。市場に対して自分たちの見解を押し付けるのではなく、コンピューター自身に、私たちよりも優れた移動平均の活用方法を学ばせることにします。そもそも、「効果的な取引」に対する私たちの考え方には、少なからずバイアスがかかっています。
そこで、終値と移動平均との関係性をコンピューターに学習させれば、これまでのような反応的な取引スタイルとは異なり、次に起こるであろう動きを予測して、自律的にルールを作りながら取引を行えるようになります。
その第一歩として、過去の市場データを抽出するためのスクリプトを作成しました。取引したい銘柄のチャートにこのスクリプトをドラッグ&ドロップするだけで、データの取得が始まります。このスクリプトは、過去の市場データを収集するとともに、当戦略に必要な2本の移動平均も、当アプリケーションで使用している形式と同じフォーマットで取り出してくれます。
//+------------------------------------------------------------------+ //| ProjectName | //| Copyright 2020, CompanyName | //| http://www.companyname.net | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com/ja/users/gamuchiraindawa" #property version "1.00" #property script_show_inputs //+------------------------------------------------------------------+ //| Script Inputs | //+------------------------------------------------------------------+ input int size = 100000; //How much data should we fetch? //+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int ma_f_handler,ma_s_handler; double ma_f_reading[],ma_s_reading[]; //+------------------------------------------------------------------+ //| On start function | //+------------------------------------------------------------------+ void OnStart() { //--- Load indicator ma_s_handler = iMA(Symbol(),PERIOD_CURRENT,60,0,MODE_EMA,PRICE_CLOSE); ma_f_handler = iMA(Symbol(),PERIOD_CURRENT,5,0,MODE_EMA,PRICE_CLOSE); //--- Load the indicator values CopyBuffer(ma_f_handler,0,0,size,ma_f_reading); CopyBuffer(ma_s_handler,0,0,size,ma_s_reading); ArraySetAsSeries(ma_f_reading,true); ArraySetAsSeries(ma_s_reading,true); //--- File name string file_name = "Market Data " + Symbol() +" MA Cross" + " As Series.csv"; //--- Write to file int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,","); for(int i= size;i>=0;i--) { if(i == size) { FileWrite(file_handle,"Time","Open","High","Low","Close","MA 5","MA 60"); } else { FileWrite(file_handle,iTime(Symbol(),PERIOD_CURRENT,i), iOpen(Symbol(),PERIOD_CURRENT,i), iHigh(Symbol(),PERIOD_CURRENT,i), iLow(Symbol(),PERIOD_CURRENT,i), iClose(Symbol(),PERIOD_CURRENT,i), ma_f_reading[i], ma_s_reading[i] ); } } //--- Close the file FileClose(file_handle); } //+------------------------------------------------------------------+
Pythonでデータを分析する
CSV形式で市場データが取得できたので、いよいよAIモデルの構築に取りかかることができます。このモデルによって、偽のブレイクアウトを予測し、それらを回避できるようになることを目指します。
import pandas as pd import numpy as np from sklearn.model_selection import TimeSeriesSplit,cross_val_score from sklearn.linear_model import Ridge from sklearn.metrics import mean_squared_error import matplotlib.pyplot as plt import seaborn as sns
先ほど抽出した市場データを読み取ります。データフレーム内の[Time]列に注目してください。最後のエントリの日付は2019年4月18日になっているのがわかります。これは意図的におこなっています。これまでのテストの開始日がいずれも2020年1月1日であったことを思い出してください。つまり、モデルにテストの「答え」をすべて与えるような真似はしていないということです。
#Define the forecast horizon look_ahead = 24 #Read in the data data = pd.read_csv('Market Data EURUSD MA Cross As Series.csv') #Drop the last 4 years data = data.iloc[:(-24 * 365 * 4),:] data.reset_index(drop=True,inplace=True) #Label the data data['Target'] = data['Close'].shift(-look_ahead) data['MA 5 Target'] = data['MA 5'].shift(-look_ahead) data['MA 5 Close Target'] = data['Target'] - data['MA 5 Target'] data['MA 60 Target'] = data['MA 60'].shift(-look_ahead) data['MA 60 Close Target'] = data['Target'] - data['MA 60 Target'] data.dropna(inplace=True) data.reset_index(drop=True,inplace=True) data
図18:過去の市場データ
EURUSD市場で、依然として移動平均で価格を予測する方が容易であるかをテストしてみましょう。この仮説をテストするために、30個の同一のニューラルネットワークを訓練し、3つのターゲットを順番に予測します。まず、未来の価格、5期間移動平均、そして60期間移動平均を予測します。すべてのターゲットは24ステップ先の未来を予測します。最初に、価格を直接予測する精度を記録します。
#Classical error classical_error = [] epochs = 1000 for i in np.arange(0,30): model = MLPRegressor(hidden_layer_sizes=(10,4),max_iter=epochs,early_stopping=False,solver='lbfgs') classical_error.append(np.mean(np.abs(cross_val_score(model,data.loc[:,['Open','High','Low','Close']],data.loc[:,'Target'],cv=tscv,scoring='neg_mean_squared_error'))))
次に、5期間の移動平均を予測した精度を記録します。
#MA Cross Over error ma_5_error = [] for i in np.arange(0,30): model = MLPRegressor(hidden_layer_sizes=(10,4),max_iter=epochs,early_stopping=False,solver='lbfgs') ma_5_error.append(np.mean(np.abs(cross_val_score(model,data.loc[:,['Open','High','Low','Close','MA 5']],data.loc[:,'MA 5 Target'],cv=tscv,scoring='neg_mean_squared_error'))))
最後に、60期間の移動平均を予測した精度を記録します。
#New error ma_60_error = [] for i in np.arange(0,30): model = MLPRegressor(hidden_layer_sizes=(10,4),max_iter=10000,early_stopping=False,solver='lbfgs') ma_60_error.append(np.mean(np.abs(cross_val_score(model,data.loc[:,['Open','High','Low','Close','MA 60']],data.loc[:,'MA 60 Target'],cv=tscv,scoring='neg_mean_squared_error'))))
結果をプロットすると、下の図12からわかるように、60期間の移動平均を予測するとシステムで最も多くの誤差が発生し、5期間の移動平均を予測すると価格を直接予測するよりも誤差が少なくなります。
plt.plot(classical_error) plt.plot(ma_5_error) plt.plot(ma_60_error) plt.legend(['OHLC','MA 5 ','MA 60']) plt.axhline(np.mean(classical_error),color='blue',linestyle='--') plt.axhline(np.mean(ma_5_error),color='orange',linestyle='--') plt.axhline(np.mean(ma_60_error),color='green',linestyle='--') plt.grid() plt.ylabel('Cross Validated Error') plt.xlabel('Iteration') plt.title('Comparing Different The Error Associated With Different Targets') plt.show()
図19:さまざまなターゲットに関連付けられたエラーの視覚化
ここで、取引アプリケーション用のモデルをエクスポートしてみます。必要なライブラリをインポートします。
import onnx from skl2onnx import convert_sklearn from skl2onnx.common.data_types import FloatTensorType from sklearn.neural_network import MLPRegressor
必要なモデルを指定します。このタスクでは2つのモデルを使用します。短期移動平均は予測しやすいため、単純なリッジモデルを使用して予測します。しかし、60期間の移動平均は困難であることが判明しました。したがって、ニューラルネットワークを使用して長期移動平均を予測します。
ma_5_model = Ridge() ma_5_model.fit(data[['Open','High','Low','Close','MA 5']],data['MA 5 Target']) ma_5_height_model = Ridge() ma_5_height_model.fit(data[['Open','High','Low','Close','MA 5']],data['MA 5 Close Target']) ma_60_model = Ridge() ma_60_model.fit(data[['Open','High','Low','Close','MA 60']],data['MA 60 Target']) ma_60_height_model = Ridge() ma_60_height_model.fit(data[['Open','High','Low','Close','MA 60']],data['MA 60 Close Target'])
ONNXへのエクスポートを準備します。
initial_type = [('float_input', FloatTensorType([1, 5]))] ma_5_onx = convert_sklearn(ma_5_model, initial_types=initial_type, target_opset=12 ) ma_5_height_onx = convert_sklearn(ma_5_height_model, initial_types=initial_type, target_opset=12 ) ma_60_height_onx = convert_sklearn(ma_60_height_model, initial_types=initial_type, target_opset=12 ) ma_60_onx = convert_sklearn(ma_60_model, initial_types=initial_type, target_opset=12 )
ONNX形式で保存します。
onnx.save(ma_5_onx,'eurchf_ma_5_model.onnx') onnx.save(ma_60_onx,'eurchf_ma_60_model.onnx') onnx.save(ma_5_height_onx,'eurusd_ma_5_height_model.onnx') onnx.save(ma_60_height_onx,'eurusd_ma_60_height_model.onnx')
MQL5の最終アップデート
新しく作成したモデルを適用して、市場の偽ブレイクアウトをフィルタリングできるかどうか確認してみましょう。最初におこなうべき更新は、先ほど作成したONNXモデルをインポートすることです。
//+------------------------------------------------------------------+ //| MTF Channel 2.mq5 | //| Gamuchirai Zororo Ndawana | //| https://www.mql5.com/ja/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com/ja/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| ONNX Resources | //+------------------------------------------------------------------+ #resource "\\Files\\eurusd_ma_5_model.onnx" as const uchar eurusd_ma_5_buffer[]; #resource "\\Files\\eurusd_ma_60_model.onnx" as const uchar eurusd_ma_60_buffer[]; #resource "\\Files\\eurusd_ma_5_height_model.onnx" as const uchar eurusd_ma_5_height_buffer[]; #resource "\\Files\\eurusd_ma_60_height_model.onnx" as const uchar eurusd_ma_60_height_buffer[];
次に、モデルに関連付けられたいくつかの新しい変数を作成する必要があります。
//+------------------------------------------------------------------+ //| Global varaibles | //+------------------------------------------------------------------+ int bias = 0; int state = 0; int confirmation = 0; int last_cross_over_state = 0; int atr_handler,ma_fast,ma_slow; int last_trade_state,current_state; long ma_5_model; long ma_60_model; long ma_5_height_model; long ma_60_height_model; double channel_high = 0; double channel_low = 0; double o,h,l,c; double bias_level = 0; double vol,bid,ask,initial_sl; double atr[],ma_f[],ma_s[]; double bo_h,bo_l; vectorf ma_5_forecast = vectorf::Zeros(1); vectorf ma_60_forecast = vectorf::Zeros(1); vectorf ma_5_height_forecast = vectorf::Zeros(1); vectorf ma_60_height_forecast = vectorf::Zeros(1);
初期化ルーチンを拡張して、ONNXモデルが自動的に設定されるようにします。
//+---------------------------------------------------------------+ //| Load our technical indicators and market data | //+---------------------------------------------------------------+ void setup(void) { //--- Reset our last trade state last_trade_state = 0; //--- Mark the current high and low channel_high = iHigh("EURUSD",PERIOD_M30,1); channel_low = iLow("EURUSD",PERIOD_M30,1); ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high); ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low); //--- Our trading volums vol = lot_multiple * SymbolInfoDouble("EURUSD",SYMBOL_VOLUME_MIN); //--- Our technical indicators atr_handler = iATR("EURUSD",PERIOD_CURRENT,14); ma_fast = iMA("EURUSD",PERIOD_CURRENT,ma_f_period,0,MODE_EMA,PRICE_CLOSE); ma_slow = iMA("EURUSD",PERIOD_CURRENT,ma_s_period,0,MODE_EMA,PRICE_CLOSE); //--- Setup our ONNX models //--- Define our ONNX model ulong input_shape [] = {1,5}; ulong output_shape [] = {1,1}; //--- Create the model ma_5_model = OnnxCreateFromBuffer(eurusd_ma_5_buffer,ONNX_DEFAULT); ma_60_model = OnnxCreateFromBuffer(eurusd_ma_60_buffer,ONNX_DEFAULT); ma_5_height_model = OnnxCreateFromBuffer(eurusd_ma_5_height_buffer,ONNX_DEFAULT); ma_60_height_model = OnnxCreateFromBuffer(eurusd_ma_60_height_buffer,ONNX_DEFAULT); //--- Store our models in a list long onnx_models[] = {ma_5_model,ma_5_height_model,ma_60_model,ma_60_height_model}; //--- Loop over the models and set them up for(int i = 0; i < 4; i++) { if(onnx_models[i] == INVALID_HANDLE) { Comment("Failed to load AI module correctly: Invalid handle"); } //--- Validate I/O if(!OnnxSetInputShape(onnx_models[i],0,input_shape)) { Comment("Failed to set input shape correctly: Wrong input shape ",GetLastError()," Actual shape: ",OnnxGetInputCount(ma_5_model)); } if(!OnnxSetOutputShape(onnx_models[i],0,output_shape)) { Comment("Failed to load AI module correctly: Wrong output shape ",GetLastError()," Actual shape: ",OnnxGetOutputCount(ma_5_model)); } } }
システムが使用されなくなった場合は、使用しなくなったリソースを解放する必要があります。
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Free the resources we don't need IndicatorRelease(atr_handler); IndicatorRelease(ma_fast); IndicatorRelease(ma_slow); OnnxRelease(ma_5_model); OnnxRelease(ma_5_height_model); OnnxRelease(ma_60_model); OnnxRelease(ma_60_height_model); }
価格が更新される際の唯一の大きな違いは、AIモデルからの予測も取得しようとする点です。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Keep track of time static datetime timestamp; datetime time = iTime(Symbol(),PERIOD_CURRENT,0); if(timestamp != time) { //--- Time Stamp timestamp = time; //--- Update system variables update(); //--- Make a new prediction model_predict(); if(PositionsTotal() == 0) { state = 0; find_setup(); } } //--- If we have positions open if(PositionsTotal() > 0) manage_setup(); }
MQL5でONNXモデルから予測を取得する関数を定義する必要があります。
//+------------------------------------------------------------------+ //| Get a prediction from our model | //+------------------------------------------------------------------+ void model_predict(void) { //--- Moving average inputs float a = (float) ma_f[0]; float b = (float) ma_s[0]; //--- Price quotes float op = (float) iOpen("EURUSD",PERIOD_H1,0); float hi = (float) iHigh("EURUSD",PERIOD_H1,0); float lo = (float) iLow("EURUSD",PERIOD_H1,0); float cl = (float) iClose("EURUSD",PERIOD_H1,0); //--- ONNX inputs vectorf fast_inputs = {op,hi,lo,cl,a}; vectorf slow_inputs = {op,hi,lo,cl,b}; Print("Fast inputs: ",fast_inputs); Print("Slow inputs: ",slow_inputs); //--- Inference OnnxRun(ma_5_model,ONNX_DATA_TYPE_FLOAT,fast_inputs,ma_5_forecast); OnnxRun(ma_5_height_model,ONNX_DATA_TYPE_FLOAT,fast_inputs,ma_5_height_forecast); OnnxRun(ma_60_model,ONNX_DEFAULT,slow_inputs,ma_60_forecast); OnnxRun(ma_60_height_model,ONNX_DATA_TYPE_FLOAT,fast_inputs,ma_60_height_forecast); }
最後におこなった変更は、戦略が取引を選択する方法に影響します。戦略では、単に真っ先に取引するのではなく、価格と移動平均の間で学習した関係に基づいて取引をおこなうようになりました。取引アプリケーションは、市場のバイアスに反する場合でも、売買できる柔軟性を備えています。
有効なセットアップで新しい関数が呼び出されていることに注意してください。この関数は、ブレイクアウト条件がtrueの場合にtrueを返すだけです。
//+---------------------------------------------------------------+ //| Find a setup | //+---------------------------------------------------------------+ void find_setup(void) { //--- I have skipped parts of the code that remained the same if(valid_setup()) { //--- Both models are forecasting rising prices if((c < (ma_60_forecast[0] + ma_60_height_forecast[0])) && (c < (ma_5_forecast[0] + ma_5_height_forecast[0]))) { if(last_trade_state != 1) { Trade.Buy(vol,"EURUSD",ask,0,0,"Volatility Doctor"); initial_sl = channel_low; last_trade_state = 1; last_cross_over_state = current_state; } } //--- Both models are forecasting falling prices if((c > (ma_60_forecast[0] + ma_60_height_forecast[0])) && (c > (ma_5_forecast[0] + ma_5_height_forecast[0]))) { if(last_trade_state != -1) { Trade.Sell(vol,"EURUSD",bid,0,0,"Volatility Doctor"); initial_sl = channel_high; last_trade_state = -1; last_cross_over_state = current_state; } } }
チャネルを突破したかどうかを確認します。ある場合、関数はtrueを返し、そうでない場合はfalseを返します。
//+---------------------------------------------------------------+ //| Do we have a valid setup? | //+---------------------------------------------------------------+ bool valid_setup(void) { return(((confirmation == 1) && (bias == -1) && (current_state != last_cross_over_state)) || ((confirmation == 1) && (bias == 1) && (current_state != last_cross_over_state))); }
ここまでで、バックテストに指定する設定についてご理解いただけたと思います。繰り返しになりますが、取引ルールに加えた変更に関連する収益性の変化を分離できるように、これらの設定を一貫して維持することが重要です。
図20:最後の取引戦略をバックテストするために使用する設定の一部
私たちのモデルは2019年までのデータでのみ訓練されており、テストは2020年から始まることを思い出してください。したがって、これは過去にこのシステムを設計していた場合に実際に何が起こっていたかを忠実にシミュレーションしていることになります。
図21:最後の取引戦略をバックテストするために使用する2番目の設定
繰り返しますが、設定は3つのテストすべてで同じです。
図22:最後のテストでアプリケーションを制御するために使用する設定
これで、モデルベースのブレイクアウト取引アプリケーションがEURUSDで実際に動作しているのを確認できます。モデルを訓練するときに、このデータはモデルに表示されなかったことを思い出してください。
図23:ブレイクアウト戦略の最終的なモデルベースバージョンが実行される
下の図23から、当初からモデルが抱えていた特徴的な負の傾斜をようやく修正することができ、収益性が向上していることがわかります。
図24:新しいモデルベースの戦略をテストしたバックテストの結果
私たちの目標は、平均利益を増やし、負け取引の割合を減らすことでしたが、それは確かに達成されました。最初のテストでは総損失が498ドル、2回目では403ドル、そして今回のテストでは298ドルまで減少しました。一方で、総利益は最初のテストでは378ドル、今回の最終テストでは341ドルと、ほぼ同水準を維持しています。つまり、今回の改善により、粗利益を保ちつつ、粗損失を確実に減らすことができたのです。最初のシステムでは、全取引の70%が損失に終わっていましたが、新しいシステムでは、不採算だった取引は55%にまで改善されています。
図25:モデルベースの戦略による詳細なバックテスト結果
結論
ブレイクアウトは、1日の中でも最も有利に取引できる可能性を秘めたタイミングです。しかし、それを正しく見極めるのは決して簡単なことではありません。この記事では、ブレイクアウト戦略を一から構築し、収益性を高めるためにいくつかのフィルターを追加する作業を一緒におこなってきました。とはいえ、ブレイクアウト戦略がEURUSD市場において必ずしも最適とは限らず、この市場には別のアプローチが必要になるかもしれません。真に有効なブレイクアウト戦略を構築するには、今回ご紹介した内容以上の時間と労力が求められるでしょう。それでも、ここで紹介したアイデアやアプローチは、皆さんが成功を目指す過程で、きっと参考になるはずです。
ファイル名 | 詳細 |
---|---|
MQL5 EURUSD AI | EURUSD市場のモデルを構築するために使用したJupyterノートブック |
EURUSD MA 60 Model | 60期間の移動平均を予測するために使用されるONNXモデル |
EURUSD MA 60 Height Model | 将来の終値と将来の60MAの差を予測するために使用されるONNXモデル |
EURUSD MA 5 Model | 5期間の移動平均を予測することを目的としたONNXモデル |
EURUSD MA 5 Height Model | 将来の終値と将来の5MAの差を予測するために使用されるONNXモデル |
MTF Channel 2 | ブレイクアウト戦略の最初の実装 |
MTF Channel 2 EURUSD | ベンチマークペアからの確認を使用したブレイクアウト戦略の2番目の実装 |
MTF Channel 2 EURUSD AI | モデルベースのブレイクアウト戦略の3番目の実装 |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/16569




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