English Deutsch
preview
MQL5で自己最適化エキスパートアドバイザーを構築する(第6回):ストップアウト防止

MQL5で自己最適化エキスパートアドバイザーを構築する(第6回):ストップアウト防止

MetaTrader 5 |
223 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

市場価格の将来的な変動を正確に予測できたにもかかわらず、損失でポジションをクローズしてしまうことがあります。取引の世界では、これを一般的に「ストップアウトされた」と表現します。この問題の根本的な原因は、価格が直線的かつ予測可能な動きをしないという点にあります。 

図1では、EURUSD通貨ペアの1時間ごとの価格変動のスナップショットを示しています。白い破線の縦線は、それぞれその日の取引開始と終了を示しています。この日、価格が下落すると確信していたトレーダーは、実際に将来的な価格の動きを正しく予測していたことになります。しかし残念ながら、価格はその後下落する前に一時的に大きく上昇しており、図1で赤くハイライトされたゾーンの範囲内にストップロスを設定していた場合、トレーダーは正しく予測していたにもかかわらず損失でポジションを閉じる結果となっていたはずです。

スクリーンショット1

図1:トレーダーが通常ストップアウトする状況

これまで多くのトレーダーたちが、この「ストップアウトされる」問題に対して様々な解決策を提案してきました。しかし、これからの議論を通じて明らかになるように、その多くは本質的な解決策とは言えません。最もよく挙げられる対処法は、単純に「ストップロスを広げる」というものです。 

これは、特にボラティリティの高い取引日において、ストップロスの幅を広げることでストップアウトを防ごうとする考え方です。しかし、これは本質的には望ましくないアドバイスです。なぜなら、この方法は、似たような取引に対して一貫性のないリスクを取る習慣をトレーダーに植え付けてしまい、明確なルールのない判断を促すからです。 

もう一つよく聞かれるのが、「エントリー前に確証を待つべきだ」という意見です。しかし、私たちが扱っているこの問題においては、これも有効な手段とは言えないでしょう。確証を待つという行為は、単にストップアウトされるタイミングを遅らせているだけで、本質的な問題解決にはつながらないという結果になることが多いのです。 

要するに、ストップアウトの問題は、トレーダーが健全なリスク管理を実行することを困難にし、同時に取引全体の利益性を損なう要因にもなっています。また、それ以外にも多くのトレーダーにとって深刻な懸念を引き起こす原因となっています。 

したがって私たちは、読者の皆さんに対し、勝ちトレードでストップアウトされる頻度を最小限に抑えるための、より合理的で明確なルールを提供することを目的としています。 

私たちの提案する解決策は、これまで一般的に挙げられてきた助言とは異なるアプローチを取ります。そして、「ストップロスを広げる」といった助言とは対照的に、ストップロスのサイズを固定するといった、健全な取引習慣の構築を促すものとなっています。 



取引戦略の概要

本戦略は平均回帰型の取引戦略であり、サポートとレジスタンスの水準にテクニカル分析を組み合わせて構成されます。まず最初に、前日の高値と安値を基準にして、当日の注目すべき価格帯をマークします。そして、現在の取引日において前日の高値または安値がブレイクされるかどうかを観察します。たとえば、当日の新たな高値によって前日の高値が更新された場合、私たちは価格が平均に回帰する(戻る)と仮定して、ショートポジション(売り)を取るチャンスを探ります。具体的なショートポジションのエントリーシグナルは、価格が前日の高値を上抜けて確定した後に、さらに移動平均線の上で価格がクローズしたときに明確になります。 

図2:取引戦略の動作



バックテスト期間の概要

提案した取引戦略の変更がどれほど効果的であるかを分析するには、まず変更内容を比較・検証するための固定された期間が必要です。本稿では、2022年1月1日から2025年1月1日までの期間をテスト期間として採用します。この検証対象期間は図1でハイライトされています。なお、図中ではEURUSDの月足チャートを観察しています。

図3:バックテストを実行する期間

実際のテストはM30時間枠で実行されます。図2では、テストに使用する対象の市場(EURUSD)と、前述したテスト期間がハイライトされています。これらの設定は本記事を通して一貫して使用する固定条件となるため、ここであらかじめ説明しておく必要があります。今後おこなうすべてのテストでは、この時間枠と期間設定を変更せずに使用します。また、私たちの検証に沿って作業を進めたい場合は、EURUSDを選択することを推奨しますが、ご自身の取引スタイルに合った他の銘柄でも構いません。

図4:バックテスト期間

加えて、[実際のティックに基づいたすべてのティック]を選択してください。これにより、過去の市場動向を最も正確に再現するエミュレーションが可能になります。なお、この設定を選択すると、使用しているブローカーから関連するティックデータが取得されます。そのため、ネットワーク環境によってはデータのダウンロードに時間がかかることがありますので、あらかじめご注意ください。

図5:バックテストに使用する口座設定


MQL5の始め方

これで本日のバックテスト期間について理解できたので、まずは比較対象となる基準値を確立しましょう。この基準を上回ることが、今後の戦略改善の目的となります。はじめに、サポートとレジスタンスのブレイクアウトを狙う取引戦略を実装するための取引アプリケーションを構築していきます。まず、取引ライブラリをインポートします。

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

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <Trade/Trade.mqh>
CTrade Trade;

システム定数を定義します。これらの定数は、すべてのテストにおいてアプリケーションの動作を明確かつ一貫して制御するために役立ちます。

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define MA_PERIOD 14                //--- Moving Average Period
#define MA_TYPE   MODE_EMA          //--- Type of moving average we have
#define MA_PRICE PRICE_CLOSE        //---- Applied Price of Moving Average
#define TF_1 PERIOD_D1              //--- Our time frame for technical analysis
#define TF_2 PERIOD_M30             //--- Our time frame for managing positions
#define VOL 0.1                     //--- Our trading volume
#define SL_SIZE  1e3 * _Point       //--- The size of our stop loss

また、昨日の注目すべき価格レベルを管理するために、いくつかのグローバル変数も必要となります。

//+------------------------------------------------------------------+
//| Our global variables                                             |
//+------------------------------------------------------------------+
int ma_handler,system_state;
double ma[];
double bid,ask,yesterday_high,yesterday_low;
const string last_high = "LAST_HIGH";
const string last_low = "LAST_LOW";

アプリケーションが初めて読み込まれたら、すべてのテクニカル指標を設定します。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   setup();
//---
   return(INIT_SUCCEEDED);
  }

アプリケーションが不要になった場合は、使用しなくなったテクニカル指標を解放します。

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

更新された価格を受け取った場合は、それを保存し、インジケーターの値も再計算します。

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

それでは、イベントサイクル内で呼び出している各関数を定義していきましょう。まず、update関数は2つの異なる時間枠で動作します。一部の処理やルーチンは1日に1回だけ実行すればよく、他の処理はもっと短い間隔でおこなう必要があります。この役割分担は、あらかじめ定義したシステム定数TF_1(日足)とTF_2(30分足)によって管理されています。たとえば、前日の高値と安値を取得する処理は1日1回で十分です。一方で、ポジションの検索などは30分足の新しいローソク足が形成されるたびに実行する必要があります。

//+------------------------------------------------------------------+
//| Perform our update routines                                      |
//+------------------------------------------------------------------+
void update()
  {
//--- Daily procedures
     {
      static datetime time_stamp;
      datetime current_time = iTime(Symbol(),TF_1,0);
      if(time_stamp != current_time)
        {
         yesterday_high = iHigh(Symbol(),TF_1,1);
         yesterday_low = iLow(Symbol(),TF_1,1);
         //--- Mark yesterday's levels
         ObjectDelete(0,last_high);
         ObjectDelete(0,last_low);
         ObjectCreate(0,last_high,OBJ_HLINE,0,0,yesterday_high);
         ObjectCreate(0,last_low,OBJ_HLINE,0,0,yesterday_low);
        }
     }
//--- M30 procedures
     {
      static datetime time_stamp;
      datetime current_time = iTime(Symbol(),TF_2,0);
      if(time_stamp != current_time)
        {
         time_stamp = current_time;
         //--- Get updated prices
         bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
         ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
         //--- Update our technical indicators
         CopyBuffer(ma_handler,0,0,1,ma);
         //--- Check for a setup
         if(PositionsTotal()==0)  find_setup();
        }
     }
  }

このアプリケーションは、たった1つのテクニカル指標のみに依存しています。そのため、セットアップ関数の定義は非常にシンプルなものとなります。

//+------------------------------------------------------------------+
//| Custom functions                                                 |
//+------------------------------------------------------------------+
void setup(void)
  {
   ma_handler    = iMA(Symbol(),TF_2,MA_PERIOD,0,MA_TYPE,MA_PRICE);
  };

ポジションを建てる条件は、現在の価格の極値が前日に観測した反対側の極値を突破した場合に満たされます。さらに、前日の価格水準をブレイクしたことに加え、価格と移動平均線との関係から追加の確認を得ることも重視します。

//+------------------------------------------------------------------+
//| Check if we have any trading setups                              |
//+------------------------------------------------------------------+
void find_setup(void)
  {
   if(iHigh(Symbol(),TF_2,1) < yesterday_low)
     {
         if(iClose(Symbol(),TF_2,1) < ma[0])
            {
               Trade.Buy(VOL,Symbol(),ask,(bid - (SL_SIZE)),(bid + (SL_SIZE)));
            }
     }

   if(iLow(Symbol(),TF_2,1) > yesterday_high)
     {
         if(iClose(Symbol(),TF_2,1) > ma[0])
            {
              Trade.Sell(VOL,Symbol(),bid,(ask + (SL_SIZE)),(ask - (SL_SIZE)));
            }
     }
  }

EAを使用していない場合は、不要になったシステムリソースを解放する必要があります。

//+------------------------------------------------------------------+
//| Free resources we are no longer using up                         |
//+------------------------------------------------------------------+
void release(void)
  {
   IndicatorRelease(ma_handler);
  }

最後に、プログラムの実行サイクルの終わりに、先ほど定義したシステム定数を削除します。

//+------------------------------------------------------------------+
//| Undefine the system constants we created                         |
//+------------------------------------------------------------------+
#undef TF_1
#undef TF_2
#undef VOL
#undef SL_SIZE
#undef MA_PERIOD
#undef MA_PRICE
#undef MA_TYPE

現在の取引戦略によって得られたエクイティカーブは安定していません。システムの残高は時間の経過とともに下落傾向を示しています。私たちが望むのは、たまに下落することはあっても、長期的には増加傾向を維持する戦略です。そこで、ポジションを建てるためのルールは一定に保ちつつ、ストップロスにかかる可能性が高い取引を除外することを試みます。この課題は間違いなく難しいものですが、何もしないよりは、少しでも解決策を試すことが重要です。

図6:現在の取引戦略によって生成されたエクイティカーブ

取引戦略の詳細な結果を分析すると、3年間のバックテスト期間中に1000ドル以上の損失を出していることが確認できます。これは決して好ましい結果とは言えません。さらに、平均損失および最大損失が、平均利益および最大利益を上回っているため、将来的なパフォーマンスに対してもネガティブな見通しが立ちます。したがって、現時点の戦略のままでは実際の資金を使った取引に適用することは望ましくありません。

図7:取引戦略によって得られた詳細な結果の分析


ベースラインの改善

私たちのストップアウト防止戦略の基盤は、これまでの議論の中で得られたある観察に基づいています。過去の議論を振り返りたい読者は、関連情報をこちらでいつでもご覧ください。要約すると、MetaTrader 5の200以上の異なる銘柄を対象に分析した結果、価格そのものよりも移動平均線というテクニカル指標のほうが予測しやすい傾向が一貫して見られました。 

この観察を活かし、将来の移動平均線の値がストップロスレベルを超えるかどうかを予測できるようにします。コンピュータが移動平均線がストップロスに達すると予測した場合、その期間中は新たな取引をおこなわないようにします。逆に、ストップロスに到達しないと判断すれば、取引を許可します。 

これが私たちの解決策の核心であり、最初から最後まで明確に定義され、健全な原則と客観的な論理に基づいています。さらに詳しく言えば、ストップロスを避けるだけでなく、取引をおこなう際には、移動平均線が利益確定水準に達すると予測されることも条件に加えるべきです。なぜなら、利益確定の見込みがなければ誰も取引をしたがらないからです。

まずは、MetaTrader 5の端末から関連する市場データを取得するために、MQL5スクリプトを用意するところから始めましょう。 

//+------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2020, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs

//--- Define our moving average indicator
#define MA_PERIOD 14                //--- Moving Average Period
#define MA_TYPE   MODE_EMA          //--- Type of moving average we have
#define MA_PRICE PRICE_CLOSE        //---- Applied Price of Moving Average

//--- Our handlers for our indicators
int ma_handle;

//--- Data structures to store the readings from our indicators
double ma_reading[];

//--- File name
string file_name = Symbol() + " Stop Out Prevention Market Data.csv";

//--- Amount of data requested
input int size = 3000;

//+------------------------------------------------------------------+
//| Our script execution                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
//---Setup our technical indicators
   ma_handle = iMA(_Symbol,PERIOD_M30,MA_PERIOD,0,MA_TYPE,MA_PRICE);

//---Set the values as series
   CopyBuffer(ma_handle,0,0,size,ma_reading);
   ArraySetAsSeries(ma_reading,true);

//---Write to file
   int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");

   for(int i=size;i>=1;i--)
     {
      if(i == size)
        {
         FileWrite(file_handle,"Time","Open","High","Low","Close","MA 14");
        }

      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_reading[i]);
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+


Pythonでデータを分析する

スクリプトを選択した市場に適用した後、Pythonのライブラリを使用して財務データの分析を開始できます。私たちの目標は、移動平均インジケーターの将来の値を予測するのに役立つニューラルネットワークを構築し、損失トレードを回避できる可能性を高めることです。 

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

次に、端末から抽出したデータを読み込みます。

data = pd.read_csv("EURUSD Stop Out Prevention Market Data.csv")
data

データにラベルを付けます。

LOOK_AHEAD = 48
data['Target'] = data['MA 14'].shift(-LOOK_AHEAD)
data.dropna(inplace=True)
data.reset_index(drop=True,inplace=True)

バックテストと重複する期間を削除します。

#Let's entirely drop off the last 2 years of data
data.iloc[-((48 * 365 * 2) + (48 * 31 * 2) + (48 * 14) - (3)):,:]

バックテスト期間の観測データを含まない新しいデータで元の市場データを上書きします。

#Let's entirely drop off the last 2 years of data
_ = data.iloc[-((48 * 365 * 2) + (48 * 31 * 2) + (48 * 14) - (3)):,:]
data = data.iloc[:-((48 * 365 * 2) + (48 * 31 * 2) + (48 * 14) - (3)),:]
data

次に、機械学習ライブラリを読み込みます。

from sklearn.neural_network import MLPRegressor
from sklearn.model_selection import train_test_split,TimeSeriesSplit,cross_val_score
モデルを迅速に交差検証できるように、時系列分割オブジェクトを作成します。
tscv = TimeSeriesSplit(n_splits=5,gap=LOOK_AHEAD)

入力とターゲットを指定します。

X = data.columns[1:-1]
y = data.columns[-1:]
新しいモデルの学習とテストのために、データを半分に分割します。

train , test = train_test_split(data,test_size=0.5,shuffle=False)

学習用およびテスト用のデータを正規化およびスケーリングできるように準備します。

train_X = train.loc[:,X]
train_y = train.loc[:,y]

test_X = test.loc[:,X]
test_y = test.loc[:,y]

Zスコアのパラメータを計算します。

mean_scores = train_X.mean()
std_scores = train_X.std()
モデルの入力データを正規化します。
train_X = ((train_X - mean_scores) / std_scores)
test_X = ((test_X - mean_scores) / std_scores)

ディープニューラルネットワークの最適な学習回数を見つけるために、直線探索を実行したいと考えています。2の累乗を用いて、2の0乗から2の14乗まで順に繰り返し処理をおこないます。

MAX_POWER = 15
results = pd.DataFrame(index=["Train","Test"],columns=[np.arange(0,MAX_POWER)])

手元のデータにディープニューラルネットワークモデルを適合させるために必要な最適な学習回数を見積もるための、forループを定義します。

#Classical Inputs
for i in np.arange(0,MAX_POWER):
    print(i)
    model = MLPRegressor(hidden_layer_sizes=(5,10,4,2),solver="adam",activation="relu",max_iter=(2**i),early_stopping=False)
    results.iloc[0,i] = np.mean(np.abs(cross_val_score(model,train_X.loc[:,:],train_y.values.ravel(),cv=tscv)))
    results.iloc[1,i] = np.mean(np.abs(cross_val_score(model,test_X.loc[:,:],test_y.values.ravel(),cv=tscv)))
    results
 01234567891011121314 
 Train19675.49249619765.297106
19609.764419511.588484
19859.734807
19942.30371
18831.617167
10703.554068
 4930.771654
1639.952482
1389.6150522938.371438
1.5367652.193895
30.553918
 Test13171.51913714113.25299414428.15920313649.15752513655.64306612919.77334611472.7707295878.96456411293.444345
3788.388634 2545.3684193599.3640282240.598518
1041.641869 882.696622

データを可視化してみると、モデルから最適な出力を得るためには最大の反復回数を使用する必要があることがわかります。ただし、読者は私たちの検索手法が早期に終了してしまった可能性があることにも寛容であるべきです。つまり、もし2の14乗を超えるイテレーション数を試していれば、さらに良い結果が得られた可能性もあるということです。しかし、これらのモデルの学習には計算コストがかかるため、検索は2の14乗を超えて実行されることはありませんでした。

plt.title("Neural Network RMSE Forecasting 14 Period MA")
plt.ylabel("5 CV RMSE")
plt.xlabel("Training Iterations As Powers of 2")
plt.grid()
sns.lineplot(np.array(results.iloc[1,:]).transpose())
plt.axhline(results.min(1)[1],linestyle='--',color='red')
plt.axvline(14,linestyle='--',color='red')

図8:ディープニューラルネットワークモデルの最適な学習反復回数を探索した結果

モデルの学習が完了したので、これからONNX形式へのエクスポートの準備をおこないます。

import onnx
import skl2onnx 
from skl2onnx.common.data_types import FloatTensorType  

推定した最適な学習反復回数を使用してモデルを適合する準備をします。

model = MLPRegressor(hidden_layer_sizes=(5,10,4,2),solver="adam",activation="relu",max_iter=(2**14),early_stopping=False)

データセット全体のZスコアを読み込みます。

mean_scores = data.loc[:,X].mean()
std_scores = data.loc[:,X].std()

mean_scores.to_csv("EURUSD StopOut Mean.csv")
std_scores.to_csv("EURUSD StopOut Std.csv")

データセット全体を変換します。

data[X] = ((data.loc[:,X] - mean_scores) / std_scores)

テストの日付を除く、保有するすべてのデータに基づいてモデルを適合させます。

model.fit(data.loc[:,X],data.loc[:,'Target'].values.ravel())

モデルの入力形状を指定します。

initial_types = [("float_input",FloatTensorType([1,5]))]

モデルをONNX形式に変換する準備をします。

model_proto = skl2onnx.convert_sklearn(model,initial_types=initial_types,target_opset=12)

モデルをONNXファイルとして保存します。

onnx.save(model_proto,"EURUSD StopOut Prevention Model.onnx")


洗練されたバージョンの戦略を構築する

新しく洗練された取引戦略の構築を始めましょう。まずは、先ほど作成したONNXモデルを読み込みます。

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

//+------------------------------------------------------------------+
//| Resources                                                        |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD StopOut Prevention Model.onnx" as uchar onnx_model_buffer[];

このバージョンのアプリケーションでは、いくつかの追加のシステム定数を作成します。

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define MA_PERIOD 14                 //--- Moving Average Period
#define MA_TYPE   MODE_EMA           //--- Type of moving average we have
#define MA_PRICE PRICE_CLOSE         //---- Applied Price of Moving Average
#define TF_1 PERIOD_D1               //--- Our time frame for technical analysis
#define TF_2 PERIOD_M30              //--- Our time frame for managing positions
#define VOL 0.1                      //--- Our trading volume
#define SL_SIZE  1e3 * _Point        //--- The size of our stop loss
#define SL_ADJUSTMENT 1e-5 * _Point  //--- The step size for our trailing stop
#define ONNX_MODEL_INPUTS 5          //---- Total model inputs for our ONNX model

さらに、グローバルZスコアを配列に読み込む必要があります。

//+------------------------------------------------------------------+
//| Our global variables                                             |
//+------------------------------------------------------------------+
int     ma_handler,system_state;
double  ma[];
double  mean_values[ONNX_MODEL_INPUTS]  = {1.157641086508574,1.1581085911361018,1.1571729541088953,1.1576420747040126,1.157640521193191};
double  std_values[ONNX_MODEL_INPUTS]   = {0.04070388112283021,0.040730761156963606,0.04067819202368064,0.040703752648947544,0.040684857239172416};
double  bid,ask,yesterday_high,yesterday_low;
const   string last_high = "LAST_HIGH";
const   string last_low  = "LAST_LOW";
long    onnx_model;
vectorf model_forecast = vectorf::Zeros(1);

ONNXモデルを使用する前に、まずモデルを適切に設定し、正しく構成されているかどうかを確認する必要があります。

//+------------------------------------------------------------------+
//| Prepare the resources our EA requires                            |
//+------------------------------------------------------------------+
bool setup(void)
  {
   onnx_model = OnnxCreateFromBuffer(onnx_model_buffer,ONNX_DEFAULT);

   if(onnx_model == INVALID_HANDLE)
     {
      Comment("Failed to create ONNX model: ",GetLastError());
      return(false);
     }

   ulong input_shape[] = {1,ONNX_MODEL_INPUTS};
   ulong output_shape[] = {1,1};

   if(!OnnxSetInputShape(onnx_model,0,input_shape))
     {
      Comment("Failed to set ONNX model input shape: ",GetLastError());
      return(false);
     }

   if(!OnnxSetOutputShape(onnx_model,0,output_shape))
     {
      Comment("Failed to set ONNX model output shape: ",GetLastError());
      return(false);
     }

   ma_handler    = iMA(Symbol(),TF_2,MA_PERIOD,0,MA_TYPE,MA_PRICE);

   if(ma_handler == INVALID_HANDLE)
     {
      Comment("Failed to load technical indicator: ",GetLastError());
      return(false);
     }

   return(true);
  };

取引設定を見つける手順が若干変わります。まず、モデルから予測を取得します。その後、ポジションの開始と終了の条件は同じままです。ただし、これらの条件が満たされていることに加えて、満たされるべき新しい条件も確認します。 

//+------------------------------------------------------------------+
//| Check if we have any trading setups                              |
//+------------------------------------------------------------------+
void find_setup(void)
  {
   if(!model_predict())
     {
      Comment("Failed to get a forecast from our model");
      return;
     }

   if((iHigh(Symbol(),TF_2,1) < yesterday_low) && (iHigh(Symbol(),TF_2,2) < yesterday_low))
     {
      if(iClose(Symbol(),TF_2,1) > ma[0])
        {
         check_buy();
        }
     }

   if((iLow(Symbol(),TF_2,1) > yesterday_high) && (iLow(Symbol(),TF_2,2) > yesterday_high))
     {
      if(iClose(Symbol(),TF_2,1) < ma[0])
        {
         check_sell();
        }
     }
  }

新たに指定する条件は、ロングポジションとショートポジションの両方に適用されます。まず、移動平均の予測値が現在利用可能な移動平均インジケーターの現在の値よりも大きいかどうかを確認します。加えて、将来予想される移動平均インジケーターの値が、現在の価格の値よりも大きいかどうかも確認したいと思います。

これは、コンピュータがトレンドが一方向に継続すると予想していることを意味します。最後に、移動平均がストップロスの下にとどまるとコンピュータが予想しているかどうかをチェックします。これらすべての条件が満たされた場合、直ちに市場でポジションを取ります。

//+------------------------------------------------------------------+
//| Check if we have a valid buy setup                               |
//+------------------------------------------------------------------+
void check_buy(void)
  {
   if((model_forecast[0] > ma[0]) && (model_forecast[0] > iClose(Symbol(),TF_2,0)))
     {
      if(model_forecast[0] > (bid - (SL_SIZE)))
         Trade.Buy(VOL,Symbol(),ask,(bid - (SL_SIZE)),(bid + (SL_SIZE)));
     }
  }

ショートポジションを開くための条件は、ロングポジションに指定した条件と同じですが、順序は逆になります。

//+------------------------------------------------------------------+
//| Check if we have a valid sell setup                              |
//+------------------------------------------------------------------+
void check_sell(void)
  {
   if((model_forecast[0] < ma[0]) && (model_forecast[0] < iClose(Symbol(),TF_2,0)))
     {
      if(model_forecast[0] < (ask + (SL_SIZE)))
         Trade.Sell(VOL,Symbol(),bid,(ask + (SL_SIZE)),(ask - (SL_SIZE)));
     }
  }

ポジションを持った後も継続して監視し続ける必要があります。ストップロス更新関数は、呼び出し方によって2つの役割を果たします。この関数はフラグパラメータを受け取り、その値によって動作が変わります。フラグが0に設定されている場合は、単にストップレベルをより利益が出る価格へ引き上げるチャンスを探します。一方、フラグが1に設定されている場合は、まずモデルから新しい予測を取得し、移動平均の将来値が現在のストップロスレベルを超える可能性があるかどうかを確認します。 

移動平均がストップロスを超えることが予想され、なおかつ利益が見込める動きになる場合は、移動平均がピークに達すると予想されるレベルまでストップロスを調整します。そうでなく、取引が始値を下回ると予想される場合は、利益の可能性が低い取引に対してはリスクを抑えるようにコンピュータに指示します。

//+------------------------------------------------------------------+
//| Update our stop loss                                             |
//+------------------------------------------------------------------+
void update_sl(int flag)
  {
   //--- First find our open position
   if(PositionSelect(Symbol()))
     {
      double current_sl = PositionGetDouble(POSITION_SL);
      double current_tp = PositionGetDouble(POSITION_TP);
      double open_price = PositionGetDouble(POSITION_PRICE_OPEN);

      //--- Flag 0 means we just want to push the stop loss and take profit forward if its possible
      if(flag == 0)
        {
         //--- Buy Setup
         if(current_tp > current_sl)
           {
            if((bid - SL_SIZE) > current_sl)
               Trade.PositionModify(Symbol(),(bid - SL_SIZE),(bid + SL_SIZE));
           }

         //--- Sell setup
         if(current_tp < current_sl)
           {
            if((ask + SL_SIZE) < current_sl)
               Trade.PositionModify(Symbol(),(ask + SL_SIZE),(ask - SL_SIZE));
           }
        }

      //--- Flag 1 means we want to check if the stop loss may be hit soon, and act accordingly
      if(flag == 1)
        {
         model_predict();

         //--- Buy setup
         if(current_tp > current_sl)
           {
            
            if(model_forecast[0] < current_sl)
              {
               if((model_forecast[0] > ma[0]) && (model_forecast[0] > yesterday_low))
                  Trade.PositionModify(Symbol(),model_forecast[0],current_tp);
              }

            if(model_forecast[0] < open_price)
               Trade.PositionModify(Symbol(),model_forecast[0] * 1.5,current_tp);
           }

         //--- Sell setup
         if(current_tp < current_sl)
           {
            if(model_forecast[0] > current_sl)
              {
               if((model_forecast[0] < ma[0]) && (model_forecast[0] < yesterday_high))
                  Trade.PositionModify(Symbol(),model_forecast[0],current_tp);
              }

            if(model_forecast[0] > open_price)
               Trade.PositionModify(Symbol(),model_forecast[0] * 0.5,current_tp);
           }
        }
     }
  }

更新手順は、ストップロス更新関数を呼び出すために若干変更されています。

//+------------------------------------------------------------------+
//| Perform our update routines                                      |
//+------------------------------------------------------------------+
void update()
  {
//--- Daily procedures
     {
      static datetime time_stamp;
      datetime current_time = iTime(Symbol(),TF_1,0);
      if(time_stamp != current_time)
        {
         yesterday_high = iHigh(Symbol(),TF_1,1);
         yesterday_low = iLow(Symbol(),TF_1,1);
         //--- Mark yesterday's levels
         ObjectDelete(0,last_high);
         ObjectDelete(0,last_low);
         ObjectCreate(0,last_high,OBJ_HLINE,0,0,yesterday_high);
         ObjectCreate(0,last_low,OBJ_HLINE,0,0,yesterday_low);
        }
     }
//--- M30 procedures
     {
      static datetime time_stamp;
      datetime current_time = iTime(Symbol(),TF_2,0);
      if(time_stamp != current_time)
        {
         time_stamp = current_time;
         //--- Get updated prices
         bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
         ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
         //--- Update our technical indicators
         CopyBuffer(ma_handler,0,0,1,ma);
         //--- Check for a setup
         if(PositionsTotal()==0)
            find_setup();

         //--- Check for a setup
         if(PositionsTotal() > 0)
            update_sl(1);
        }
     }
//--- Per tick procedures
     {
      //--- These function calls can become expensive and may slow down the speed of your back tests
      //--- Be thoughtful when placing any function calls in this scope
      update_sl(0);
     }
  }

ニューラルネットワークモデルから予測を取得する専用の関数も必要です。まず入力データを浮動小数点ベクトル型に準備し、その後入力を標準化してから、モデルから予測を取得します。

//+------------------------------------------------------------------+
//| Get a forecast from our deep neural network                      |
//+------------------------------------------------------------------+
bool model_predict(void)
  {
   double ma_input[] = {0};
   CopyBuffer(ma_handler,0,1,1,ma_input);
   vectorf model_inputs =
     {
      (float) iOpen(Symbol(),TF_2,1),
      (float) iHigh(Symbol(),TF_2,1),
      (float) iLow(Symbol(),TF_2,1),
      (float) iClose(Symbol(),TF_2,1),
      (float) ma_input[0]
     };

   for(int i = 0; i < ONNX_MODEL_INPUTS;i++)
     {
      model_inputs[i] = (float)((model_inputs[i] - mean_values[i]) / std_values[i]);
     }

   if(!OnnxRun(onnx_model,ONNX_DEFAULT,model_inputs,model_forecast))
     {
      Comment("Failed to obtain forecast: ",GetLastError());
      return(false);
     }

   Comment(StringFormat("Expected MA Value: %f",model_forecast[0]));
   return(true);
  }

最後に、アプリケーションの使用が終了した際には、インジケーターとONNXモデルを解放します。

//+------------------------------------------------------------------+
//| Free resources we are no longer using up                         |
//+------------------------------------------------------------------+
void release(void)
  {
   OnnxRelease(onnx_model);
   IndicatorRelease(ma_handler);
  }
//+------------------------------------------------------------------+

新しく改良した取引アルゴリズムによって生成されたエクイティカーブを分析すると、最初の戦略実装で見られた特徴的な負の傾きが修正されていることがすぐにわかります。現在の戦略は、時折の下落はあるものの、全体としては上昇傾向を示しており、これは戦略の初期状態よりも望ましい結果です。

図9:改良された新しいバージョンの予防停止アルゴリズムによって生成された利益曲線

さらに詳しく調査してみると、新しい戦略が収益を上げていることがわかります。初期バージョンの戦略では約1,000ドルの損失が出ていましたが、現在のバージョンでは1,000ドルを少し超える利益が出ています。これは大きな改善です。初期のシャープレシオは-0.39でしたが、新しいシャープレシオは0.79です。読者の方も、平均利益トレードが98ドルから130ドルに増加し、平均損失トレードは102ドルから63ドルに減少していることに気づくでしょう。これは、平均利益の成長ペースが平均損失を大きく上回っていることを示しており、 このバージョンの取引戦略を使用することに対して、前向きな期待を抱かせる結果となっています。

とはいえ、大きな進歩があった一方で、「ストップアウトされる」問題は依然として難しい課題です。実際に、すべてのポジションのうち約60%が損失トレードだったという事実からも、それは明らかです。すべての損失トレードを完全に排除することは難しい課題ですが、今回は特に大きくて不採算なトレードをうまくフィルタリングすることには成功しました。

図10:新しいストップアウト防止アルゴリズムを使用して得られた結果の詳細な分析



結論

本記事では、利益の出るトレードでストップアウトされてしまうという、長年にわたる問題に対するひとつの解決策を読者と共に検討してきました。この問題は、成功する取引の核心にあり、完全に解決されることはないかもしれません。新たな解決策を導入するたびに、それに伴う新たな脆弱性が戦略に入り込む可能性があります。しかし、この記事を通して、読者はストップロス水準を管理するための、より定量的なフレームワークを得ることができたはずです。不要なドローダウンを引き起こす取引を特定し、排除することは、あらゆる取引戦略において極めて重要な要素です。 

ファイル名ファイルの説明
Baseline Model.mq5優れたパフォーマンスを目標とした当社独自の取引戦略
Stop Out Prevention Model.mq5ディープニューラルネットワークを活用した、私たちの洗練された取引戦略
EURUSD Stop Out Moving Average Model.ipynbMetaTrader 5端末から抽出した金融データを分析するために使用したJupyter Notebook
EURUSD Stop Out Prevention Model.onnx私たちのディープニューラルネットワーク
Fetch Data MA.mq5必要な市場データを取得するために使用したMQL5スクリプト

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

MQL5での取引戦略の自動化(第8回):バタフライハーモニックパターンを用いたエキスパートアドバイザーの構築 MQL5での取引戦略の自動化(第8回):バタフライハーモニックパターンを用いたエキスパートアドバイザーの構築
この記事では、バタフライハーモニックパターンを検出するためのMQL5エキスパートアドバイザー(EA)を構築します。ピボットポイントを特定し、フィボナッチレベルを検証してパターンを確認します。次に、チャート上にパターンを可視化し、確認された際には自動的に取引を実行します。
プライスアクション分析ツールキットの開発(第14回):Parabolic Stop and Reverseツール プライスアクション分析ツールキットの開発(第14回):Parabolic Stop and Reverseツール
プライスアクション分析にテクニカルインジケーターを取り入れることは、非常に有効なアプローチです。これらのインジケーターは、反転や押し戻しの重要なレベルを示すことが多く、市場の動きを把握する上での貴重な手がかりとなります。本記事では、パラボリックSARインジケーターを用いてシグナルを生成する自動ツールをどのように開発したかを紹介します。
MQL5での取引戦略の自動化(第9回):アジアブレイクアウト戦略のためのエキスパートアドバイザーの構築 MQL5での取引戦略の自動化(第9回):アジアブレイクアウト戦略のためのエキスパートアドバイザーの構築
この記事では、アジアブレイクアウト戦略のためのエキスパートアドバイザー(EA)をMQL5で構築します。セッション中の高値と安値を計算し、移動平均によるトレンドフィルタリングをおこないます。また、動的なオブジェクトスタイリング、ユーザー定義の時間入力、堅牢なリスク管理も実装します。最後に、プログラムの精度を高めるためのバックテストおよび最適化手法を紹介します。
MQL5での取引戦略の自動化(第7回):動的ロットスケーリングを備えたグリッド取引EAの構築 MQL5での取引戦略の自動化(第7回):動的ロットスケーリングを備えたグリッド取引EAの構築
この記事では、動的なロットスケーリングを採用したMQL5のグリッドトレーディングエキスパートアドバイザー(EA)を構築します。戦略の設計、コードの実装、バックテストのプロセスについて詳しく解説します。最後に、自動売買システムを最適化するための重要な知見とベストプラクティスを共有します。