
MQL5で古典的な戦略を再構築する(後編):FTSE100と英国債
現代の投資家がAIを取引戦略に組み込む方法は無限にある可能性があります。個々の投資家が、どの戦略に資金を託すかを決める前に、各戦略を十分に分析する時間を持つことはほとんどありません。本連載では、取引環境におけるAIの適用可能性の広がりを探ります。私たちの目標は、特定の投資家プロファイルに適した戦略を見つけるための支援をすることです。
取引戦略の概要
FTSE100種総合株価指数(FTSE100)は、ロンドン証券取引所(LSE)に上場している上位100社の業績を追跡する、世界的に認知された指数です。この指数は1984年に1000ポイントの基準値で作成され、現在では約8000ポイントで取引されています。指数に含まれる企業は時価総額に比例して加重されており、そのため、大企業の方が小企業よりも市場に対して大きな影響力を持っています。
先進国のすべての政府は自国通貨建ての債務を発行しており、英国政府も例外ではありません。英国債は英国政府の債務証券であり、LSEにも上場されています。英国債には、2つの異なるタイプの固定収入証券があります。最初のタイプは従来の国債であり、国債販売の大部分を占めています。この従来型国債は、保有者に対して満期まで固定クーポンを支払い、満期時に最終的なクーポンと元本が投資家に返済されます。
2つ目のタイプの英国債は、インデックス連動型国債です。この国債には固定クーポンレートがなく、債券から受け取る配当は変動し、国債を保有している間に発生した総インフレを投資家に補償します。これらのインデックス連動型国債は、キャッシュフローストリームが予測できないため、固定型国債ほど人気が高くありません。
債券の利回りと需要には逆相関があります。特定の債券が投資家の間で高い需要を持つと、その債券に関連する利回りは低下し、逆に債券のパフォーマンスが悪い場合は、投資家の関心を引くためにその利回りは上昇します。
通常、株式市場のパフォーマンスが悪化しているとき、投資家はリスクの高い株式市場から資金を引き上げ、国債などのより安全な証券に投資する傾向があります。逆に、株式市場への信頼を取り戻すと、安全な国債から資金を引き上げ、株式市場に投資する傾向があります。この行動の主な理由は、期待される収益の差にあります。平均的に、株式市場は債券市場よりも高い収益を提供するため、より魅力的です。しかし、株式市場はより大きなリスクを伴うため、投資家は市場の弱さに非常に敏感です。
方法論の概要
2つの市場に関する独自の取引ルールを学習するAI搭載のエキスパートアドバイザー(EA)を構築しました。通常、投資家として私たちは自分に魅力的な戦略に偏りがちですが、アルゴリズム学習アプローチを採用することで、プログラムが収集したデータに基づいてすべての決定を下していることを確信できます。
金融時系列データは、ノイズが多いことで有名です。ノイズの多いデータセットを扱う場合、より単純なモデルでもニューラルネットワークと同等のパフォーマンスを発揮できることはよく知られています。そこで、行列とベクトルのAPIを使用して、MQL5で線形回帰モデルを最初から実装しました。入力データのスケールが異なっており、英国債とUK100銘柄のスケールが異なるため、モデルを適合させる前に入力データを標準化してスケーリングしました。疑似逆ソリューションを使用して、モデルパラメータを計算しました。
さらに、テクニカル分析を使用してAIモデルを誘導したため、テクニカル指標も同じ結論に至った場合にのみ、システムは取引を開始します。最後に、AIシステムを使用して、潜在的な反転を検出し、ポジションをクローズするタイミングを決定することもできました。以前の記事で、線形モデルをゼロから開発したときに、ユーザーの1人からモデルの出力が許容範囲を超えているとのコメントがありました。この記事では、モデルの切片の列を追加し、データを標準化してスケーリングすることで、その問題を解決しようとしました。
MQL5での実装
まず、EAのすべての部分がどのように組み合わされるかを把握するために、MQL5でスクリプトを作成しました。最初に、アプリケーションの入力を定義する必要があります。取得するデータの量と、予測する未来の長さを定義しなければなりません。取得するデータが多ければ多いほど、アプリケーションの計算負荷が最終的に高くなることを忘れないでください。したがって、ここではエンドユーザーがリソースで処理できる範囲のデータを取得できるようにし、バランスを取ることにしています。さらに、市場の状況は常に変化することを考慮する必要があります。利用可能なすべてのデータを取得しても、パフォーマンスが向上しない場合もあります。
最近では、市場価格が従来の公開取引システムとは異なり、アルゴリズムによって提供されるようになっています。過去の市場動向に基づいて市場価格を研究しても、再現できない可能性があるため、意味がないこともあります。したがって、私たちは最新のデータのみに焦点を絞っています。
//+------------------------------------------------------------------+ //| UK100 Gilts.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" #property script_show_inputs //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input int fetch = 20; //How much data should we fetch? input int look_ahead = 20; //How far into the future should we forecast?
まず、スクリプト全体で必要となる変数を定義します。
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- Global variables we will need inside our expert advisor matrix coefficients = matrix::Zeros(1,9); vector mean_values = vector::Zeros(8); vector std_values = vector::Zeros(8); vector intercept = vector::Ones(fetch); matrix input_matrix = matrix::Zeros(9,fetch); matrix gilts_data,uk100_data,target;
次に、必要なデータを取得してみます。入力データはlook_aheadステップ数だけ遅れて取得され、ターゲットデータは入力データより先に取得されていることに注目してください。
//--- First we will fetch the market data gilts_data.CopyRates("UKGB_Z4",PERIOD_CURRENT,COPY_RATES_OHLC,1+look_ahead,fetch); uk100_data.CopyRates("UK100",PERIOD_CURRENT,COPY_RATES_OHLC,1+look_ahead,fetch); target.CopyRates("UK100",PERIOD_CURRENT,COPY_RATES_CLOSE,1,fetch);
次に入力行列を作成します。入力行列の最初の列は数字の1で、入力の取得と同じ回数だけ繰り返されます。これは、この1のエントリが線形モデルのバイアス項の計算に単純に乗算されるためです。その後、英国債とFTSE 100に関連するデータを入力します。
//--- Fill in the input matrix input_matrix.Row(intercept,0); input_matrix.Row(gilts_data.Row(0),1); input_matrix.Row(gilts_data.Row(1),2); input_matrix.Row(gilts_data.Row(2),3); input_matrix.Row(gilts_data.Row(3),4); input_matrix.Row(uk100_data.Row(0),5); input_matrix.Row(uk100_data.Row(1),6); input_matrix.Row(uk100_data.Row(2),7); input_matrix.Row(uk100_data.Row(3),8);
取得したデータを確認して、データのサイズが適切であること、および取得する行列がこれから実行する操作に適合していることを確認します。行列のサイズが適切でない場合、計算は失敗し、実行時にエラーが返されることに注意してください。
//--- Display the data fetched Print("Input matrix: "); Print("Rows: ",input_matrix.Rows()," Columns: ",input_matrix.Cols()); Print(input_matrix); Print("Target: "); Print("Rows: ",target.Rows()," Columns: ",target.Cols()); Print(target); Print("UK100: "); Print("Rows: ",uk100_data.Rows()," Columns: ",uk100_data.Cols()); Print(uk100_data); Print("GILTS: "); Print("Rows: ",gilts_data.Rows()," Columns: ",gilts_data.Cols()); Print(gilts_data);
入力データをスケーリングして正規化するには、処理する各列の平均値と標準偏差を計算する必要があります。機械学習を初めて学んだときは、データのスケーリングや標準化がいつ必要になるのか、理解できていませんでした。しかし、時間が経つにつれて、入力データが異なるスケールにある場合にはスケーリングが必要だという経験則を学びました。たとえば、英国債市場データの範囲は100台ですが、FTSE100市場データの範囲は8000台であり、このような場合にはスケーリングが必要となります。なお、ターゲットデータについてはスケーリングは一切必要ありません。
//--- Calculate the scaling values mean_values = input_matrix.Mean(1); std_values = input_matrix.Std(1); Print("Mean values: "); Print(mean_values); Print("Std values: "); Print(std_values);
ここで、入力データを正規化するためには、まず各列の平均値を減算し、その後、標準偏差で割ります。この前処理手順は、モデルが効果的に学習するために重要です。特に、今回のケースのように、モデル入力が異なるスケールである場合には、この処理を実行することが必要不可欠です。
//--- Normalizing and scaling our input data for(int i = 0; i < 8; i++) { //--- Extract the vector vector temp = input_matrix.Row(i + 1); //--- Scale the data temp = ((temp - mean_values[i+1]) / std_values[i+1]); //--- Write the data back input_matrix.Row(temp,i+1); } //--- Finished normalizing the data Print("Finished normalizing the data."); Print(input_matrix);
次に、モデルパラメータ、つまり線形モデルの係数を計算します。これは、ターゲットデータと入力データの疑似逆行列を行列乗算することでおこないます。これにより、行列の各列に対応する1つの係数値を持つ1×nの行列が返されます。各係数値の符号は、傾き、つまり入力変数が増加することに対してターゲットが増加するのか減少するのかを示します。
//--- Now we can calculate our coefficient values coefficients = target.MatMul(input_matrix.PInv()); Print("Coefficient values"); Print(coefficients);
モデルから予測を得るには、まず現在の入力データ値を取得し、それをスケーリングしてから、線形回帰式を適用してモデルの予測を取得する必要があります。
//--- Now we can obtain a forecast from our model gilts_data.CopyRates("UKGB_Z4",PERIOD_CURRENT,COPY_RATES_OHLC,0,1); uk100_data.CopyRates("UK100",PERIOD_CURRENT,COPY_RATES_OHLC,0,1); //--- Scale our inputs gilts_data[0,0] = ((gilts_data[0,0] - mean_values[1]) / std_values[1]); gilts_data[1,0] = ((gilts_data[1,0] - mean_values[2]) / std_values[2]); gilts_data[2,0] = ((gilts_data[2,0] - mean_values[3]) / std_values[3]); gilts_data[3,0] = ((gilts_data[3,0] - mean_values[4]) / std_values[4]); uk100_data[0,0] = ((uk100_data[0,0] - mean_values[5]) / std_values[5]); uk100_data[1,0] = ((uk100_data[1,0] - mean_values[6]) / std_values[6]); uk100_data[2,0] = ((uk100_data[2,0] - mean_values[7]) / std_values[7]); uk100_data[3,0] = ((uk100_data[3,0] - mean_values[8]) / std_values[8]); Print("Normalized inputs: "); Print(gilts_data); Print(uk100_data); double forecast = ( (1 * coefficients[0,0]) + (gilts_data[0,0] * coefficients[0,1]) + (gilts_data[1,0] * coefficients[0,2]) + (gilts_data[2,0] * coefficients[0,3]) + (gilts_data[3,0] * coefficients[0,4]) + (uk100_data[0,0] * coefficients[0,5]) + (gilts_data[1,0] * coefficients[0,6]) + (gilts_data[2,0] * coefficients[0,7]) + (gilts_data[3,0] * coefficients[0,8]) ); //--- Give our predictions Comment("Model forecast: ",forecast);
図1:スクリプト
図2:スクリプトの入力
図3:モデルの予測
MQL5でEAを構築する
ここまでくれば、EAを作り始める準備は整っっています。アプリケーションのビルドを開始するために、まずポジションを管理できるように取引ライブラリをインポートします。
//+------------------------------------------------------------------+ //| UK100 Gilts.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" //+------------------------------------------------------------------+ //|Libraries we need | //+------------------------------------------------------------------+ #include <Trade/Trade.mqh> //Trade class CTrade Trade; //Initialize the class
次に、エンドユーザーがどの程度のデータを取得し、どの程度先の未来を予測するかを決定できるように、アプリケーションの入力を設定します。
//+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input int fetch = 20; //How much data should we fetch? input int look_ahead = 20; //How far into the future should we forecast?
また、プログラム全体で使われるグローバル変数も必要です。
//+------------------------------------------------------------------+ //| Global vairables | //+------------------------------------------------------------------+ matrix coefficients = matrix::Zeros(1,9); vector mean_values = vector::Zeros(8); vector std_values = vector::Zeros(8); vector intercept = vector::Ones(fetch); matrix input_matrix = matrix::Zeros(9,fetch); matrix gilts_data,uk100_data,target; double willr_buffer[],rsi_buffer[]; int willr_handler,rsi_handler; double forecast,bid,ask; int model_forecast = 0; int state = 0;
訓練データを取得し、データの標準化とスケーリングに必要なスケーリング値を取得する関数を定義します。配列の代わりに行列を使っていることに注目してください。これは、MQL5 APIに含まれる特殊な行列関数とベクトル関数を利用するためです。
//+------------------------------------------------------------------+ //| Let us fetch our training data | //+------------------------------------------------------------------+ void fetch_training_data(void) { //--- First we will fetch the market data gilts_data.CopyRates("UKGB_Z4",PERIOD_CURRENT,COPY_RATES_OHLC,1+look_ahead,fetch); uk100_data.CopyRates("UK100",PERIOD_CURRENT,COPY_RATES_OHLC,1+look_ahead,fetch); target.CopyRates("UK100",PERIOD_CURRENT,COPY_RATES_CLOSE,1,fetch); //--- Fill in the input matrix input_matrix.Row(intercept,0); input_matrix.Row(gilts_data.Row(0),1); input_matrix.Row(gilts_data.Row(1),2); input_matrix.Row(gilts_data.Row(2),3); input_matrix.Row(gilts_data.Row(3),4); input_matrix.Row(uk100_data.Row(0),5); input_matrix.Row(uk100_data.Row(1),6); input_matrix.Row(uk100_data.Row(2),7); input_matrix.Row(uk100_data.Row(3),8); //--- Display the data fetched Print("Input matrix: "); Print("Rows: ",input_matrix.Rows()," Columns: ",input_matrix.Cols()); Print(input_matrix); Print("Target: "); Print("Rows: ",target.Rows()," Columns: ",target.Cols()); Print(target); Print("UK100: "); Print("Rows: ",uk100_data.Rows()," Columns: ",uk100_data.Cols()); Print(uk100_data); Print("GILTS: "); Print("Rows: ",gilts_data.Rows()," Columns: ",gilts_data.Cols()); Print(gilts_data); //--- Calculate the scaling values mean_values = input_matrix.Mean(1); std_values = input_matrix.Std(1); Print("Mean values: "); Print(mean_values); Print("Std values: "); Print(std_values); } //+------------------------------------------------------------------+
スケーリング値ができたので、実際に入力データをスケーリングして標準化する関数を定義してみましょう。
//+------------------------------------------------------------------+ //| Let us scale and standardize the training data | //+------------------------------------------------------------------+ void scale_training_data(void) { //--- Normalizing and scaling our input data for(int i = 0; i < 8; i++) { //--- Extract the vector vector temp = input_matrix.Row(i + 1); //--- Scale the data temp = ((temp - mean_values[i+1]) / std_values[i+1]); //--- Write the data back input_matrix.Row(temp,i+1); } //--- Finished normalizing the data Print("Finished normalizing the data."); Print(input_matrix); }
訓練データのスケーリングと標準化が完了したら、モデルのパラメータを計算する準備が整います。
//+------------------------------------------------------------------+ //| Calculate coefficient values | //+------------------------------------------------------------------+ void calculate_coefficient_values(void) { //--- Now we can calculate our coefficient values coefficients = target.MatMul(input_matrix.PInv()); Print("Coefficient values"); Print(coefficients); }
モデルから予測をフェッチする関数も必要です。手順は簡単で、まず市場データを取得し、データをスケーリングし、最後に線形回帰式を適用して予測を得ます。
//+------------------------------------------------------------------+ //| Fetch a forecast from our model | //+------------------------------------------------------------------+ void fetch_forecast(void) { //--- Now we can obtain a forecast from our model gilts_data.CopyRates("UKGB_Z4",PERIOD_CURRENT,COPY_RATES_OHLC,0,1); uk100_data.CopyRates("UK100",PERIOD_CURRENT,COPY_RATES_OHLC,0,1); //--- Scale our inputs gilts_data[0,0] = ((gilts_data[0,0] - mean_values[1]) / std_values[1]); gilts_data[1,0] = ((gilts_data[1,0] - mean_values[2]) / std_values[2]); gilts_data[2,0] = ((gilts_data[2,0] - mean_values[3]) / std_values[3]); gilts_data[3,0] = ((gilts_data[3,0] - mean_values[4]) / std_values[4]); uk100_data[0,0] = ((uk100_data[0,0] - mean_values[5]) / std_values[5]); uk100_data[1,0] = ((uk100_data[1,0] - mean_values[6]) / std_values[6]); uk100_data[2,0] = ((uk100_data[2,0] - mean_values[7]) / std_values[7]); uk100_data[3,0] = ((uk100_data[3,0] - mean_values[8]) / std_values[8]); Print("Normalized inputs: "); Print(gilts_data); Print(uk100_data); //--- Calculate the model's prediction forecast = ( (1 * coefficients[0,0]) + (gilts_data[0,0] * coefficients[0,1]) + (gilts_data[1,0] * coefficients[0,2]) + (gilts_data[2,0] * coefficients[0,3]) + (gilts_data[3,0] * coefficients[0,4]) + (uk100_data[0,0] * coefficients[0,5]) + (gilts_data[1,0] * coefficients[0,6]) + (gilts_data[2,0] * coefficients[0,7]) + (gilts_data[3,0] * coefficients[0,8]) ); //--- Store the model's prediction if(forecast < iClose("UK100",PERIOD_CURRENT,0)) { model_forecast = -1; } if(forecast > iClose("UK100",PERIOD_CURRENT,0)) { model_forecast = 1; } //--- Give the user feedback Comment("Model forecast: ",forecast); }
最後に、現在の市場データとテクニカル指標データを取得する関数も必要です。
//+------------------------------------------------------------------+ //| This function will fetch current market data | //+------------------------------------------------------------------+ void update_market_data(void) { //--- Market prices bid = SymbolInfoDouble("UK100",SYMBOL_BID); ask = SymbolInfoDouble("UK100",SYMBOL_ASK); //--- Technical indicators CopyBuffer(rsi_handler,0,0,1,rsi_buffer); CopyBuffer(willr_handler,0,0,1,willr_buffer); }
また、テクニカル指標の読みに対してAIモデルの予測を検証する2つの関数も必要です。テクニカル分析と定量分析の両方が同じ方向を向いているときにのみ、高確率のセットアップにエントリするつもりです。
//+------------------------------------------------------------------+ //| This function will check if we have oppurtunities to buy | //+------------------------------------------------------------------+ void check_bullish_sentiment(void) { if((willr_buffer[0] > -20) && (rsi_buffer[0] > 70)) { Trade.Buy(0.2,"UK100",ask,ask-5,ask+5,"UK100 Gilts AI"); state = 1; } } //+------------------------------------------------------------------+ //| This function will check if we have oppurtunities to sell | //+------------------------------------------------------------------+ void check_bearish_sentiment(void) { if((willr_buffer[0] < -80) && (rsi_buffer[0] <370)) { Trade.Sell(0.2,"UK100",ask,ask-5,ask+5,"UK100 Gilts AI"); state = -1; } }
それだけでなく、EAの初期化手順を定義する準備もできました。まず、訓練データを取得し、そこから訓練データをスケーリングして標準化する必要があります。その後、係数の値を計算し、最後にテクニカル指標を設定して、テクニカル指標が有効であることを検証します。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Fetch the training data fetch_training_data(); //--- Scale the training data scale_training_data(); //--- Calculate the coefficients calculate_coefficient_values(); //--- Setup the indicators rsi_handler = iRSI("UK100",PERIOD_CURRENT,20,PRICE_CLOSE); willr_handler = iWPR("UK100",PERIOD_CURRENT,30); //--- Validate the technical indicators if((rsi_handler == INVALID_HANDLE) || (willr_handler == INVALID_HANDLE)) { Comment("Failed to load indicators. ",GetLastError()); return(INIT_FAILED); } //--- Everything went well return(INIT_SUCCEEDED); }
EAがチャートから削除されるたびに、使用しなくなったリソースが解放されます。テクニカル分析で使用した指標を外し、さらにEAもチャートから外します。
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Free up the resoruces we don't need IndicatorRelease(willr_handler); IndicatorRelease(rsi_handler); ExpertRemove(); }
最後に、価格が更新されるたびに、まず市場データを更新し、次にAIモデルから予測を取得します。ポジションがない場合は、ポジションを建てる前に、テクニカル指標がAIモデルと一致しているかどうかを確認します。ポジションが建っている場合は、AIモデルがポジションに対して反転を予測しているかどうかを確認します。モデルが反転を予測している場合は、ポジションをクローズします。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Fetch updated market data update_market_data(); //--- Fetch a forecast from our model fetch_forecast(); //--- Check for a position we can open if(PositionsTotal() == 0) { if(model_forecast == 1) { check_bullish_sentiment(); } else if(model_forecast == -1) { check_bearish_sentiment(); } } //--- Check for a reversal else if(PositionsTotal() > 0) { if(model_forecast != state) { Alert("Reversal detected by our AI system! Closing all positions now."); Trade.PositionClose("UK100"); } } } //+------------------------------------------------------------------+
図4:AIシステムの動作
結論
本記事では、MQL5を使用してAIガイド付きEAを構築する方法について紹介しました。このアプローチの最大の利点は、アプリケーションを再調整することなく、時間枠を変更できる点です。一方、ONNXを使用してソリューションを実装する場合、取引する時間枠ごとに少なくとも1つのONNXモデルが必要になります。現在のソリューションは自己最適化されており、さまざまな条件下で動作することができます。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/15771





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