English
preview
MQL5で自己最適化エキスパートアドバイザーを構築する(第17回):アンサンブルインテリジェンス

MQL5で自己最適化エキスパートアドバイザーを構築する(第17回):アンサンブルインテリジェンス

MetaTrader 5 |
25 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

すべてのアルゴリズム取引戦略は、その複雑さに関係なく、構築や維持が困難です。これは初心者と専門家の双方に共通する課題です。初心者は移動平均クロスオーバー戦略の期間を調整し続けることに苦労し、専門家もまたディープニューラルネットワークの重みを絶えず調整することに悩まされています。どちらの立場にも共通する本質的な問題があります。

機械学習モデルは脆弱であり、実際のライブ取引環境では崩壊してしまうことが少なくありません。その不透明で複雑な設計は、パフォーマンスのボトルネックの診断や特定をさらに困難にします。一方で、人間主導の戦略は比較的堅牢であることが多いものの、開始時には手動での設定を必要とし、その作業はアプローチによっては非常に手間がかかります。本記事では、教師ありモデルと人間の直感が相互に補完し合い、それぞれの限界をより迅速に克服するアンサンブルフレームワークを提案します。

この目的を達成するために、戦略と統計モデルが同じ4つのテクニカル指標を共有するよう設計しました。移動平均チャネル戦略を選択し、同じ指標に基づいてリッジ回帰モデルを適合させました。これにより、システム全体として収益性のある構成を迅速に特定できるようになりました。

テクニカル指標は、人間の直感と教師ありモデルの両方に対して集中管理を提供します。従来型の戦略と統計モデルの両方が一致した場合にのみポジションを取るようにすることで、それぞれ単独では収益性のなかった2つのシステムから、収益性のある結果を生み出しました。これがこのアンサンブルフレームワークの動機です。本戦略では、どちらか一方を個別に修正するよりも、両者を組み合わせたほうが、より迅速かつ一貫して改善される傾向が見られます。

このアプローチでは、統計モデルが戦略と同じテクニカル指標から学習するため、アンサンブル(スタッキング)をより実践的なものにし、本来であれば確立に時間のかかる安定した構成を見つけやすくします。この集中管理により、両コンポーネントに影響を与える少数のテクニカル指標だけを設定すればよくなり、基礎となる期間に関係なく、どの移動平均が重要かを迅速に把握できます。移動平均の期間の調整は一切必要ありませんでした。本記事では、説明目的のために選択した期間のままでは初期戦略は収益性がなかったことを示しますが、それでも調整は不要でした。システムは自ら学習し、自己修正していきます。


取引戦略の可視化

この戦略は、2つの移動平均指標に基づいています。それぞれ高値と安値の価格データに適用されます。これらのインジケーターはチャネルを形成し、その間の幅は市場のボラティリティに応じて決まります。戦略の概略図を図1に示します。この戦略では、価格がチャネルの上限を上抜けた場合に買いシグナルを出し、逆に下限を下抜けた場合に売りシグナルを出します。図1の右上にある白い枠内を見ると、短時間のうちに反対方向の2つのシグナルが生成されていることが分かります。つまり、この戦略には目に見えるノイズが存在しています。

図1

図1:移動平均チャネル戦略に基づく取引機会の特定

このようにノイズはあるものの、この戦略は市場全体のトレンドを把握するうえで、一定の有効性を持つ指標として機能します。

図2

図2:ノイズはあるが、全体としては妥当な戦略に見える


ベースラインパフォーマンスの確立

まず最初に、テスト全体を通して固定するシステム定数を定義します。 

//+------------------------------------------------------------------+
//|                                                           EI.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 SYSTEM_TF PERIOD_D1
#define MA_SHIFT 0
#define MA_TYPE MODE_EMA
#define ATR_PERIOD 14
#define PADDING 2

次に、演習に必要なヘルパーライブラリをいくつか読み込みます。

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

CTrade Trade;
TradeInfo *TradeHelper;

ほとんどのアプリケーションではグローバル変数が必要です。このシステムでも、テクニカル指標の値や時間を追跡するためにグローバル変数を使用します。

//+------------------------------------------------------------------+
//| Define global variables                                          |
//+------------------------------------------------------------------+
int    ma_h_handler,ma_l_handler,atr_handler;
double ma_h[],ma_l[],atr[];
MqlDateTime tc,ts;

次に、入力値として20を定義します。この値を変更する場合は、スクリプト全体で一貫して変更する必要があります。

//+------------------------------------------------------------------+
//| Input varaibles                                                  |
//+------------------------------------------------------------------+
input group "Technical Indicators"
input int MA_PERIOD = 20;//Moving average period

アプリケーションが初期化されるとき、必要なテクニカル指標を読み込み、必要なクラスインスタンスを生成します。また、現在時刻を記録しておきます。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Setup our technical indicators
   ma_h_handler = iMA(Symbol(),SYSTEM_TF,MA_PERIOD,MA_SHIFT,MA_TYPE,PRICE_HIGH);
   ma_l_handler = iMA(Symbol(),SYSTEM_TF,MA_PERIOD,MA_SHIFT,MA_TYPE,PRICE_LOW);
   atr_handler = iATR(Symbol(),SYSTEM_TF,ATR_PERIOD);
   TradeHelper = new TradeInfo(Symbol(),SYSTEM_TF);

//--- Mark the time
   TimeLocal(tc);
   TimeLocal(ts);
//---
   return(INIT_SUCCEEDED);
  }

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

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   delete TradeHelper;
   IndicatorRelease(ma_h_handler);
   IndicatorRelease(ma_l_handler);
   IndicatorRelease(atr_handler);
  }

このアプリケーションは1時間ごとにルーチンタスクを実行します。これは、専用のMqlDateTimeオブジェクトを使用して時間の経過を確認することで実現します。時間が更新された場合、テクニカル指標のバッファを更新し、現在の終値を取得します。その後、取引シグナルを確認します。終値が移動平均チャネルを上抜けた場合は上昇シグナルとして買いポジションを取り、下抜けた場合は売りポジションを取ります。

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

   if(ts.hour != tc.hour)
     {
      if(PositionsTotal()==0)
        {
         //--- Update the time
         TimeLocal(tc);

         //--- Update the indicator buffer
         CopyBuffer(ma_h_handler,0,0,1,ma_h);
         CopyBuffer(ma_l_handler,0,0,1,ma_l);
         CopyBuffer(atr_handler,0,0,1,atr);

         //--- Check if the current price is above or below the channel
         double c = iClose(Symbol(),SYSTEM_TF,0);

         if(c > ma_h[0])
            Trade.Buy(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetAsk(),TradeHelper.GetBid()-(atr[0]*PADDING),TradeHelper.GetBid()+(atr[0]*PADDING));

         else
            if(c < ma_l[0])
               Trade.Sell(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetBid(),TradeHelper.GetAsk()+(atr[0]*PADDING),TradeHelper.GetAsk()-(atr[0]*PADDING));
        }
     }
  }
//+------------------------------------------------------------------+

最後に、事前に定義したすべてのシステム定数の定義を解除します。

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef SYSTEM_TF
#undef MA_SHIFT
#undef MA_TYPE
#undef ATR_PERIOD
#undef PADDING

ベンチマークアプリケーションを読み込み、テスト期間を設定します。演習では、2022年1月から2025年1月までの3年以上にわたる日足EURUSDデータを使用しました。

図3:アプリケーションのベースラインパフォーマンスを確立するためのテスト期間の選択

ランダム遅延と実ティックに基づくモデリング設定を組み合わせることで、ライブ取引に近い最良の結果を得ることができます。

図4:アプリケーションテスト条件の選択

また、ユーザーが期間を指定できるようにしており、検証に積極的に参加できる設計にしています。ただし、今回設定した初期値20は任意に選択したものです。

図5:任意の期間を選択して評価できる現在の設定

導入部で説明した通り、期間20は収益性がありませんでした。しかし後ほど示すように、統計モデルはこの結果から学習し、テクニカル指標の期間を何度も最適化し直すことなくノイズを適切に除去できるようになります。

図6:本取引アプリケーションのエクイティカーブは安定性に欠け、信頼性も十分とは言えない

今回得られたベンチマーク結果は、先に観察したエクイティカーブと一致しています。すなわちマイナスであり、通常であればこのアイデアを放棄すべき状況です。しかし本記事では、このシステムを維持したまま検証を続けます。

図7:ベンチマークの詳細統計から、まだ改善の余地があることが分かる


過去の市場データの取得

次に、過去の市場データを取得し、CSVファイルとして書き出すスクリプトを作成します。このデータを用いて、EURUSD市場に基づく統計モデルを構築します。

//+------------------------------------------------------------------+
//|                                                      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 5                 //--- Moving Average Period
#define MA_TYPE   MODE_SMA          //--- Type of moving average we have
#define HORIZON   5                 //--- Forecast horizon

//--- Our handlers for our indicators
int ma_handle,ma_o_handle,ma_h_handle,ma_l_handle;

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

//--- File name
string file_name = Symbol() + " Detailed Market Data As Series Moving Average.csv";

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

//+------------------------------------------------------------------+
//| Our script execution                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
   int fetch = size + (HORIZON * 2);
//---Setup our technical indicators
   ma_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_CLOSE);
   ma_o_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_OPEN);
   ma_h_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_HIGH);
   ma_l_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_LOW);

//---Set the values as series
   CopyBuffer(ma_handle,0,0,fetch,ma_reading);
   ArraySetAsSeries(ma_reading,true);
   CopyBuffer(ma_o_handle,0,0,fetch,ma_o_reading);
   ArraySetAsSeries(ma_o_reading,true);
   CopyBuffer(ma_h_handle,0,0,fetch,ma_h_reading);
   ArraySetAsSeries(ma_h_reading,true);
   CopyBuffer(ma_l_handle,0,0,fetch,ma_l_reading);
   ArraySetAsSeries(ma_l_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",
                   //--- OHLC
                   "True Open",
                   "True High",
                   "True Low",
                   "True Close",
                   //--- MA OHLC
                   "True MA O",
                   "True MA H",
                   "True MA L",
                   "True MA C"
                  );
        }

      else
        {
         FileWrite(file_handle,
                   iTime(_Symbol,PERIOD_CURRENT,i),
                   //--- OHLC
                   iClose(_Symbol,PERIOD_CURRENT,i),
                   iOpen(_Symbol,PERIOD_CURRENT,i),
                   iHigh(_Symbol,PERIOD_CURRENT,i),
                   iLow(_Symbol,PERIOD_CURRENT,i),
                   //--- MA OHLC
                   ma_o_reading[i],
                   ma_h_reading[i],
                   ma_l_reading[i],
                   ma_reading[i]
                  );
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef HORIZON
#undef MA_PERIOD
#undef MA_TYPE
//+------------------------------------------------------------------+


Pythonでの過去市場データ分析

まず、標準的なPythonライブラリを読み込みます。
#Load the libraries we need
import pandas as pd
import numpy as np

次に、先ほどCSVとして書き出した市場データを読み込みます。

#Read in the data
data = pd.read_csv("../EURUSD Detailed Market Data As Series Moving Average.csv")
data

予測期間を定義します。

#Define the forecast horizon
HORIZON = 20

バックテスト期間と重複する履歴データをすべて削除します。

#Drop the dates that overlap with the back test
data = data.iloc[:-(365*3),:]
_ = data.iloc[-(365*3):,:]

市場データにラベル付けを行い、終値が将来、移動平均のどちら側に位置すると予測されるかをモデル化できるようにします。これは、人間の直感の観点から見たときに、本戦略を駆動する要素となるものです。

#Label the data
data['Target H'] = data['Close'].shift(-HORIZON) - data['MA H'].shift(-HORIZON)
data['Target L'] = data['Close'].shift(-HORIZON) - data['MA L'].shift(-HORIZON)

#Drop missing rows
data = data.iloc[:-HORIZON,:]

ONNXライブラリを読み込みます。ONNX(Open Neural Network Exchangeの略)は、機械学習モデルを学習環境の依存関係を引き継ぐことなく構築およびデプロイできるようにするオープンソースのライブラリです。

from sklearn.linear_model import Ridge
import onnx
from skl2onnx import convert_sklearn 
from skl2onnx.common.data_types import FloatTensorType

モデルをすべての学習用データに適合させます。

model = Ridge(alpha=1e-3)
model.fit(data.iloc[:,1:-2],data.loc[:,['Target H','Target L']])

ONNXモデルの入力と出力の形状を定義します。

initial_types = [('float_input',FloatTensorType([1,8]))]
final_types = [('float_output',FloatTensorType([1,2]))]

ONNXモデルをONNXプロトタイプとして保存します。

onnx_proto = convert_sklearn(model=model,initial_types=initial_types,final_types=final_types,target_opset=12)

ONNXプロトタイプをファイルとしてドライブに保存します。 

onnx.save(onnx_proto,'EURUSD MA R.onnx')


ベースラインを上回る

ここでは、変更が加えられたコード部分のみに焦点を当て、それ以外の変更されていない部分は省略します。最初の変更点は、ONNXモデルに関連付けられた2つの新しいシステム定数を定義することです。

//+------------------------------------------------------------------+
//|                                                           EI.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 ONNX_FEATURES 8
#define ONNX_TARGETS 2

ここで、ONNXモデルを読み込みましょう。

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD MA R.onnx" as const uchar onnx_buffer[];

その後、先ほど定義したバッファからONNXモデルを作成し、モデルの入力および出力の形状を設定します。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   onnx_model = OnnxCreateFromBuffer(onnx_buffer,ONNX_DATA_TYPE_FLOAT);

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

   ulong input_shape[]  = {1,ONNX_FEATURES};
   ulong output_shape[] = {1,ONNX_TARGETS};

   onnx_inputs = vectorf::Zeros(ONNX_FEATURES);
   onnx_output = vectorf::Zeros(ONNX_TARGETS);

   if(!OnnxSetInputShape(onnx_model,0,input_shape))
     {
      Print("Failed to define ONNX input shape: ",GetLastError());
      return(INIT_FAILED);
     }

   if(!OnnxSetOutputShape(onnx_model,0,output_shape))
     {
      Print("Failed to define ONNX output shape: ",GetLastError());
      return(INIT_FAILED);
     }

//--- Mark the time
   TimeLocal(tc);
   TimeLocal(ts);
//---
   return(INIT_SUCCEEDED);
  }

アプリケーションの使用が終了した場合は、不要となったONNXモデルを解放する必要があります。

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

これまで定義したインジケーターの更新に加えて、ONNXモデルに渡す現在の市場データも必要です。このモデルは、終値が移動平均チャネルの境界からどれだけ乖離するかを予測します。正の値は強気、負の値は弱気を示します。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(ts.hour != tc.hour)
     {
      if(PositionsTotal()==0)
        {
         onnx_inputs[0] = (float) iOpen(Symbol(),SYSTEM_TF,0);
         onnx_inputs[1] = (float) iHigh(Symbol(),SYSTEM_TF,0);
         onnx_inputs[2] = (float) iLow(Symbol(),SYSTEM_TF,0);
         onnx_inputs[3] = (float) iClose(Symbol(),SYSTEM_TF,0);
         onnx_inputs[4] = (float) ma_o[0];
         onnx_inputs[5] = (float) ma_h[0];
         onnx_inputs[6] = (float) ma_l[0];
         onnx_inputs[7] = (float) ma_c[0];

         if(OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,onnx_inputs,onnx_output))
           {
            //--- Check if the current price is above or below the channel
            Print("Forecast: ",onnx_output);
            double c = iClose(Symbol(),SYSTEM_TF,0);
	    
            if((c > ma_h[0]) && (onnx_output[0]>0) && (onnx_output[1]>0))
               Trade.Buy(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetAsk(),TradeHelper.GetBid()-(atr[0]*PADDING),TradeHelper.GetBid()+(atr[0]*PADDING));

            else
               if((c < ma_l[0]) && (onnx_output[0]<0) && (onnx_output[1]<0))
                  Trade.Sell(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetBid(),TradeHelper.GetAsk()+(atr[0]*PADDING),TradeHelper.GetAsk()-(atr[0]*PADDING));
           }

         else
           {
            Print("Failed to obtain a prediction from our ONNX model: ",GetLastError());
           }
        }
     }
  }
//+------------------------------------------------------------------+

その後、ONNXモデルに対応するために追加した新しい定義を解除します。

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef ONNX_FEATURES
#undef ONNX_TARGETS
//+------------------------------------------------------------------+

次に、今回新たに構築したアプリケーションの新バージョンを、前回と同じテスト期間で検証します。

図8:一貫性を保つために、最初のテストと同じ日付範囲を選択する

ご覧のとおり、アプリケーションは依然として収益化には至っていません。しかし、詳細な統計をもう少し詳しく確認してみましょう。

図9:アプリケーションはまだ利益圏に到達していない

総純利益は-96ドルから-62ドルへと改善していますが、依然として改善の余地は大きく残されています。

図10:今回の詳細結果からも、現時点では本戦略の妥当性について十分な信頼を置ける段階には至っていないことが分かる


追加改善

前回のローソク足パターンの議論を活かし、統計モデルの別の学習パートナーを導入します。高値と安値の価格データは、移動平均インジケーターのテクニカル入力となります。前回詳しく取り上げた陽の包み足を活用し、統計モデルとは独立して良好なパフォーマンスを引き出す方法を観察しました。

if(OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,onnx_inputs,onnx_output))
  {
   //--- Check if the current price is above or below the channel
   Print("Forecast: ",onnx_output);
   double c = iClose(Symbol(),SYSTEM_TF,0);

   //--- Check for any bullish engulfing candle sticks
   if((onnx_output[0]>0) && (onnx_output[1]>0) && (iHigh(Symbol(),PERIOD_CURRENT,1) > iHigh(Symbol(),PERIOD_CURRENT,2)) && (iLow(Symbol(),PERIOD_CURRENT,1) < iLow(Symbol(),PERIOD_CURRENT,2)))
      Trade.Buy(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetAsk(),TradeHelper.GetBid()-(atr[0]*PADDING),TradeHelper.GetBid()+(atr[0]*PADDING));
            
   //--- Check for any bearish engulfing candle sticks
   else
      if((onnx_output[0]<0) && (onnx_output[1]<0) && (iHigh(Symbol(),PERIOD_CURRENT,1) > iHigh(Symbol(),PERIOD_CURRENT,2)) && (iLow(Symbol(),PERIOD_CURRENT,1) < iLow(Symbol(),PERIOD_CURRENT,2)))
         Trade.Sell(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetBid(),TradeHelper.GetAsk()+(atr[0]*PADDING),TradeHelper.GetAsk()-(atr[0]*PADDING));
  }    

それでは、この改良版アプリケーションを用いて、同じ期間で再度テストを実行してみましょう。

図11:改善版の戦略を同じ3年間のテストウィンドウで実行した結果

結果を見ると、以前は達成できなかった収益レベルに到達していることが分かります。アプリケーションは、プラスの勢いが見られるようになっています。しかし、詳細なバックテスト結果も確認してみましょう。

図12:改善版アプリケーションのエクイティカーブは、定量的にも一定の信頼性が確認できる

今回のテストでは、総純利益が126.58ドルに達しましたが、3年間で建てられたショートポジションはわずか9回に留まっています。これは理想的とは言えず、まだ活用しきれていない潜在的な改善余地があることを示唆しています。

図13:詳細分析からも、エントリーの分布についてさらに最適化が必要であることが分かる


最後の試み

ここで、モデルをさらに改善する最終段階に進みます。まず、モデルに重要な調整を加えます。テクニカル指標を複数ステップ先まで予測するように設定し、それぞれの指標を1ステップ先とHORIZON(20ステップ)先でモデル化することで、合計4つのターゲットを設定します。

#Label the data
data['Target H'] = data['MA H'].shift(-1)
data['Target L'] = data['MA L'].shift(-1)

data['Target H 2'] = data['MA H'].shift(-HORIZON)
data['Target L 2'] = data['MA L'].shift(-HORIZON)

#Drop missing rows
data = data.iloc[:-HORIZON,:]

次に、リッジ回帰モデルをアルファ値0.001で適合させます。この値は重要でない係数をゼロに近づける速度を決定し、モデルを重要なパラメータに集中させます。その後、モデルをデータに適合させます。

model = Ridge(alpha=1e-3)
model.fit(data.iloc[:,1:-4],data.loc[:,['Target H','Target L','Target H 2','Target L 2']])

モデルの入力と出力形状も定義します。

initial_types = [('float_input',FloatTensorType([1,8]))]
final_types = [('float_output',FloatTensorType([1,4]))]

最後に、ONNXモデルをファイルに保存します。

onnx.save(onnx_proto,'EURUSD MA MFH R.onnx')


MQL5での改善点の実装

ここで、MQL5において改良版を実装します。まず、ONNXモデルの出力サイズを変更します。

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define ONNX_TARGETS 4

次に、新しく更新したマルチステップ予測モデルを読み込みます。

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD MA MFH R.onnx" as const uchar onnx_buffer[];

これにより、モデルから新しい予測を取得し、人間の直感と比較できます。終値の移動平均が始値より高ければ市場は強気と判断し、ONNXモデルが高値と安値の両方の移動平均が上昇すると予測した場合、買いポジションを取ります。逆に売り条件も同様に判定します。

 if(OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,onnx_inputs,onnx_output))
           {
            //--- Check if the current price is above or below the channel
            Print("Forecast: ",onnx_output);
            double c = iClose(Symbol(),SYSTEM_TF,0);

            if((ma_o[0]<ma_c[0]) && (onnx_output[0]<onnx_output[2]) && (onnx_output[1]<onnx_output[3]))
               Trade.Buy(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetAsk(),TradeHelper.GetBid()-(atr[0]*PADDING),TradeHelper.GetBid()+(atr[0]*PADDING));

            else
               if((ma_o[0]>ma_c[0]) && (onnx_output[0]>onnx_output[2]) && (onnx_output[1]>onnx_output[3]))
                  Trade.Sell(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetBid(),TradeHelper.GetAsk()+(atr[0]*PADDING),TradeHelper.GetAsk()-(atr[0]*PADDING));
           }

改良版アプリケーションを、同じテスト期間で実行します。

図14:今回の改良版を同じ3年の期間でテストする

ご覧のとおり、新しいアプリケーションのエクイティカーブはこれまでのすべてのバージョンよりもはるかに安定しており、上昇トレンドもより強く示されています。

図15:改良版のエクイティカーブは以前より上昇トレンドが強くなっている

さらに、新しいパフォーマンス水準は健全です。総利益は173.72ドルとなり、初期の-96ドルから大幅に改善しました。買いと売りのエントリー分布も適切になっています。

図16:改良版の詳細結果により、これまでの変更が反映されていることが分かる



結論

本日の解説はこれで終了です。今回の記事では、教師ありモデルに依存するアルゴリズム取引戦略の不安定さを制御する方法を示しました。従来の戦略と同じ指標と市場データに基づく統計モデルを構築し、両者を組み合わせることで、一方のパラメータ調整を省略し、より堅牢な最終戦略を作ることができました。読者は、この手法を自分の好きな指標にも応用可能です。


ファイル名  ファイルの説明
Fetch Data MA.mq5 MetaTrader 5ターミナルから過去の市場データを取得するために使用したMQL5スクリプト
EI Baseline.mq5  従来の移動平均チャネル戦略の収益性ベンチマークとして使用したアプリケーション
EI.mq5 ベンチマークを上回ることを目指した最初の試み。なお、このバージョンは収益化できていません。
EI 2.mq5  ベンチマークを上回ることに初めて成功したバージョン。ただし、このバージョンは買いポジションに偏った戦略でした。
EI 3.mq5 従来戦略を上回り、かつ比較的偏りの少ない取引をおこなった移動平均チャネル戦略の最良バージョン
MA Channel AI 3.ipynb  MQL5スクリプトで取得した過去の市場データを分析するために作成したJupyter Notebook

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

添付されたファイル |
Fetch_Data_MA.mq5 (3.69 KB)
EI_Baseline.mq5 (4.24 KB)
EI.mq5 (6.6 KB)
EI_2.mq5 (6.98 KB)
EI_3.mq5 (6.74 KB)
MQL5でのAI搭載取引システムの構築(第5回):チャットポップアップを備えた折りたたみ可能なサイドバーの追加 MQL5でのAI搭載取引システムの構築(第5回):チャットポップアップを備えた折りたたみ可能なサイドバーの追加
連載第5回では、ChatGPT統合型エキスパートアドバイザー(EA)に折りたたみ可能なサイドバーを追加し、ナビゲーションを改善します。これにより、大小の履歴ポップアップからチャットをスムーズに選択できるようになり、従来の複数行入力処理、暗号化されたチャットの保存機能、チャートデータからのAIによる取引シグナル生成も維持されます。
MQL5での取引戦略の自動化(第40回):カスタムレベルを使ったフィボナッチリトレースメント取引 MQL5での取引戦略の自動化(第40回):カスタムレベルを使ったフィボナッチリトレースメント取引
フィボナッチリトレースメント取引のためのMQL5エキスパートアドバイザー(EA)を構築します。日足の値幅またはルックバック配列を使用して、50%や61.8%といったカスタムレベルをエントリー用に計算し、終値と始値の比較に基づいて強気または弱気のセットアップを判断します。システムは、価格が各レベルをクロスした際に買いまたは売りをトリガーし、各レベルごとに最大取引回数を設定できます。また、新しいフィボナッチ計算時の任意決済、最小利益閾値到達後のポイントベースのトレーリングストップ、値幅に対する割合で設定されるストップロスとテイクプロバッファを備えています。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
MQL5取引ツール(第10回):視覚的なレベルとパフォーマンス指標を備えた戦略追跡システムの構築 MQL5取引ツール(第10回):視覚的なレベルとパフォーマンス指標を備えた戦略追跡システムの構築
移動平均線のクロスオーバーシグナルを検知し、長期移動平均線でフィルタリングした上で、利益確定(TP)や損切り(SL)をポイント単位で設定して取引をシミュレーションまたは実行し、結果をモニタリングするMQL5戦略トラッカーシステムを開発します。