
CatBoost機械学習モデルをトレンド追従戦略のフィルターとして活用する
はじめに
CatBoostは、定常的な特徴量に基づいて意思決定をおこなうことに特化した、強力なツリーベースの機械学習モデルです。XGBoostやRandom Forestといった他のツリーベースモデルも、堅牢性、複雑なパターンへの対応力、そして高い解釈性といった点で共通した特長を備えています。これらのモデルは、特徴量分析からリスク管理に至るまで、幅広い分野で活用されています。
本記事では、学習済みのCatBoostモデルを、従来型の移動平均クロスを用いたトレンドフォロー戦略のフィルターとして活用する手順を解説します。戦略構築の過程で直面しうる課題を取り上げながら、具体的な開発プロセスへの理解を深めることを目的としています。MetaTrader 5からのデータ取得、Pythonによる機械学習モデルの学習、そしてそれをMetaTrader 5のエキスパートアドバイザー(EA)へ統合するまでのワークフローをご紹介します。記事の終盤では、統計的検証を通じて戦略の有効性を確認し、現在のアプローチをもとにした今後の展望についても考察していきます。
直感
CTA(商品取引アドバイザー)戦略の開発においては、すべての戦略アイデアの背後に明確で直感的な説明があることが重要だというのが、業界で広く受け入れられている経験則です。これは、人が戦略アイデアを思いつく自然なプロセスでもあり、過学習を避ける手段としても有効です。この考え方は、機械学習モデルを用いる場合にも同様に重要です。ここでは、このアイデアの背後にある直感的な理由を説明します。
これが機能する理由
CatBoostモデルは、特徴量を入力として受け取り、それに基づいて各結果の確率を出力する決定木を構築します。このケースでは、勝ち(1)と負け(0)の二値分類問題としてモデルを訓練しています。モデルは、学習データにおける損失関数を最小化するように、決定木内の分岐ルールを調整していきます。モデルがサンプル外のデータでも一定の予測精度を示す場合、勝つ可能性が低い取引をフィルタリングするための手段として活用でき、その結果として全体的な収益性の向上につながる可能性があります。
私たちのような個人トレーダーにとって現実的な期待値は、訓練されたモデルが万能な存在となるわけではなく、あくまでランダムウォークをわずかに上回る程度の精度が得られれば十分ということです。もちろん、モデルの精度を高めるための方法は多く存在し、それについては後で触れますが、たとえ小さな改善であっても、取り組む価値のある重要な一歩となります。
バックボーン戦略の最適化
前のセクションでも述べたように、モデルによるパフォーマンス向上はあくまでわずかなものであるため、ベースとなる戦略(バックボーン戦略)自体に一定の収益性が備わっていることが非常に重要です。
また、この戦略では、以下の理由から十分な数のサンプルを生成できる必要があります。
- モデルは取引の一部を除外するため、残されたサンプル数が大数の法則に基づく統計的有意性を示すのに十分である必要があります。
- 学習データ(インサンプル)に対する損失関数を効果的に最小化するためにも、モデルの訓練に必要な十分なサンプル数が求められます。
本記事では、異なる期間の2つの移動平均線が交差したタイミングでエントリーし、価格が移動平均線の反対側に抜けたときにエグジットするという、実績のあるトレンドフォロー戦略を採用しています。つまり、トレンドに従って取引をおこなう戦略です。以下のMQL5コードは、この戦略を実装したEAです。
#include <Trade/Trade.mqh> //XAU - 1h. CTrade trade; input ENUM_TIMEFRAMES TF = PERIOD_CURRENT; input ENUM_MA_METHOD MaMethod = MODE_SMA; input ENUM_APPLIED_PRICE MaAppPrice = PRICE_CLOSE; input int MaPeriodsFast = 15; input int MaPeriodsSlow = 25; input int MaPeriods = 200; input double lott = 0.01; ulong buypos = 0, sellpos = 0; input int Magic = 0; int barsTotal = 0; int handleMaFast; int handleMaSlow; int handleMa; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { trade.SetExpertMagicNumber(Magic); handleMaFast =iMA(_Symbol,TF,MaPeriodsFast,0,MaMethod,MaAppPrice); handleMaSlow =iMA(_Symbol,TF,MaPeriodsSlow,0,MaMethod,MaAppPrice); handleMa = iMA(_Symbol,TF,MaPeriods,0,MaMethod,MaAppPrice); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { int bars = iBars(_Symbol,PERIOD_CURRENT); //Beware, the last element of the buffer list is the most recent data, not [0] if (barsTotal!= bars){ barsTotal = bars; double maFast[]; double maSlow[]; double ma[]; CopyBuffer(handleMaFast,BASE_LINE,1,2,maFast); CopyBuffer(handleMaSlow,BASE_LINE,1,2,maSlow); CopyBuffer(handleMa,0,1,1,ma); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double lastClose = iClose(_Symbol, PERIOD_CURRENT, 1); //The order below matters if(buypos>0&& lastClose<maSlow[1]) trade.PositionClose(buypos); if(sellpos>0 &&lastClose>maSlow[1])trade.PositionClose(sellpos); if (maFast[1]>maSlow[1]&&maFast[0]<maSlow[0]&&buypos ==sellpos)executeBuy(); if(maFast[1]<maSlow[1]&&maFast[0]>maSlow[0]&&sellpos ==buypos) executeSell(); if(buypos>0&&(!PositionSelectByTicket(buypos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ buypos = 0; } if(sellpos>0&&(!PositionSelectByTicket(sellpos)|| PositionGetInteger(POSITION_MAGIC) != Magic)){ sellpos = 0; } } } //+------------------------------------------------------------------+ //| Expert trade transaction handling function | //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { if (trans.type == TRADE_TRANSACTION_ORDER_ADD) { COrderInfo order; if (order.Select(trans.order)) { if (order.Magic() == Magic) { if (order.OrderType() == ORDER_TYPE_BUY) { buypos = order.Ticket(); } else if (order.OrderType() == ORDER_TYPE_SELL) { sellpos = order.Ticket(); } } } } } //+------------------------------------------------------------------+ //| Execute sell trade function | //+------------------------------------------------------------------+ void executeSell() { double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); bid = NormalizeDouble(bid,_Digits); trade.Sell(lott,_Symbol,bid); sellpos = trade.ResultOrder(); } //+------------------------------------------------------------------+ //| Execute buy trade function | //+------------------------------------------------------------------+ void executeBuy() { double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); ask = NormalizeDouble(ask,_Digits); trade.Buy(lott,_Symbol,ask); buypos = trade.ResultOrder(); }
バックボーン戦略を検証するには、次の点を考慮する必要があります。
- 十分なサンプル数があること(サンプルの頻度はタイムフレームやシグナルの制限に依存しますが、総サンプル数としては1000~10000件を推奨します。1回の取引が1サンプルです。)
- ある程度の収益性がすでに確認できていること、ただし高すぎないこと(プロフィットファクターが1~1.15程度であれば十分と考えられます。MetaTrader 5のテスターはスプレッドを考慮しているため、プロフィットファクターが1でも統計的な優位性があると判断できます。1.15を超える場合、その戦略単体で十分に機能している可能性が高く、複雑性を増すような追加のフィルターは不要かもしれません。)
- バックボーン戦略にあまり多くのパラメーターがないこと(機械学習モデルをフィルターとして使用することで戦略の複雑性はすでに高まっているため、バックボーン戦略自体はシンプルであることが望ましいです。フィルターが少なければ過剰適合のリスクも低く抑えられます。)
以下は、私がこの戦略を最適化するために行った取り組みです。
- 適切な時間枠の選定:異なる時間枠でコードを実行した結果、この戦略は上位の時間枠で最も効果を発揮することが分かりました。ただし、十分なサンプル数を確保するため、最終的には1時間足(1H)に落ち着きました。
- パラメータの最適化:長期移動平均と短期移動平均の期間をステップ幅5で最適化し、上記のコードにある設定値を得ました。
- エントリーの時点で、価格がある期間の移動平均をすでに上回っている(または下回っている)ことを条件とするフィルターを試しました。これは、すでにその方向にトレンドが出ていることを意味します。ただし重要なのは、こうしたフィルターも直感的な説明が可能であること、そしてデータスヌーピングなしで仮説を検証する必要があるという点です。最終的には、このルールを加えてもパフォーマンスに大きな改善が見られなかったため、戦略の複雑化を避ける目的でこのアイデアは破棄しました。
最後に、これはXAUUSD1時間枠、2004.1.1–2024.11.1でのテスト結果です。
データの取得
モデルを訓練するには、各取引時点での特徴量が必要であり、さらにその取引の結果も把握する必要があります。私が最も効率的かつ確実だと感じている方法は、対応する特徴量をすべて2次元配列に保存するEAを作成することです。そして取引結果については、バックテストのレポートをエクスポートするだけで取得できます。
まず、取引結果を取得するには、バックテスト結果画面で右クリックしてレポートを選択し、次のようにXMLで開きます。
次に、double配列をCSVに変換するには、この記事で説明されているCFileCSVクラスを使用します。
私たちは、バックボーン戦略スクリプトの上に次の手順で構築します。
1. mqhファイルをインクルードし、クラスオブジェクトを作成します。
#include <FileCSV.mqh>
CFileCSV csvFile;
2.保存するファイル名と、「index」およびその他のすべての特徴量名を含むヘッダーを宣言します。ここでの「index」は、テスターの実行中に配列インデックスを更新するためにのみ使用され、後でPythonで削除されます。
string fileName = "ML.csv"; string headers[] = { "Index", "Accelerator Oscillator", "Average Directional Movement Index", "Average Directional Movement Index by Welles Wilder", "Average True Range", "Bears Power", "Bulls Power", "Commodity Channel Index", "Chaikin Oscillator", "DeMarker", "Force Index", "Gator", "Market Facilitation Index", "Momentum", "Money Flow Index", "Moving Average of Oscillator", "MACD", "Relative Strength Index", "Relative Vigor Index", "Standard Deviation", "Stochastic Oscillator", "Williams' Percent Range", "Variable Index Dynamic Average", "Volume", "Hour", "Stationary" }; string data[10000][26]; int indexx = 0; vector xx;
3.すべての特徴量を計算してグローバル配列に格納するgetData()関数を記述します。この場合、時間、オシレーター、定常価格を特徴として使用します。この関数は、取引シグナルがあるたびに呼び出され、取引と一致するようになります。特徴量の選択については後述します。
//+------------------------------------------------------------------+ //| Execute get data function | //+------------------------------------------------------------------+ vector getData(){ //23 oscillators double ac[]; // Accelerator Oscillator double adx[]; // Average Directional Movement Index double wilder[]; // Average Directional Movement Index by Welles Wilder double atr[]; // Average True Range double bep[]; // Bears Power double bup[]; // Bulls Power double cci[]; // Commodity Channel Index double ck[]; // Chaikin Oscillator double dm[]; // DeMarker double f[]; // Force Index double g[]; // Gator double bwmfi[]; // Market Facilitation Index double m[]; // Momentum double mfi[]; // Money Flow Index double oma[]; // Moving Average of Oscillator double macd[]; // Moving Averages Convergence/Divergence double rsi[]; // Relative Strength Index double rvi[]; // Relative Vigor Index double std[]; // Standard Deviation double sto[]; // Stochastic Oscillator double wpr[]; // Williams' Percent Range double vidya[]; // Variable Index Dynamic Average double v[]; // Volume CopyBuffer(handleAc, 0, 1, 1, ac); // Accelerator Oscillator CopyBuffer(handleAdx, 0, 1, 1, adx); // Average Directional Movement Index CopyBuffer(handleWilder, 0, 1, 1, wilder); // Average Directional Movement Index by Welles Wilder CopyBuffer(handleAtr, 0, 1, 1, atr); // Average True Range CopyBuffer(handleBep, 0, 1, 1, bep); // Bears Power CopyBuffer(handleBup, 0, 1, 1, bup); // Bulls Power CopyBuffer(handleCci, 0, 1, 1, cci); // Commodity Channel Index CopyBuffer(handleCk, 0, 1, 1, ck); // Chaikin Oscillator CopyBuffer(handleDm, 0, 1, 1, dm); // DeMarker CopyBuffer(handleF, 0, 1, 1, f); // Force Index CopyBuffer(handleG, 0, 1, 1, g); // Gator CopyBuffer(handleBwmfi, 0, 1, 1, bwmfi); // Market Facilitation Index CopyBuffer(handleM, 0, 1, 1, m); // Momentum CopyBuffer(handleMfi, 0, 1, 1, mfi); // Money Flow Index CopyBuffer(handleOma, 0, 1, 1, oma); // Moving Average of Oscillator CopyBuffer(handleMacd, 0, 1, 1, macd); // Moving Averages Convergence/Divergence CopyBuffer(handleRsi, 0, 1, 1, rsi); // Relative Strength Index CopyBuffer(handleRvi, 0, 1, 1, rvi); // Relative Vigor Index CopyBuffer(handleStd, 0, 1, 1, std); // Standard Deviation CopyBuffer(handleSto, 0, 1, 1, sto); // Stochastic Oscillator CopyBuffer(handleWpr, 0, 1, 1, wpr); // Williams' Percent Range CopyBuffer(handleVidya, 0, 1, 1, vidya); // Variable Index Dynamic Average CopyBuffer(handleV, 0, 1, 1, v); // Volume //2 means 2 decimal places data[indexx][0] = IntegerToString(indexx); data[indexx][1] = DoubleToString(ac[0], 2); // Accelerator Oscillator data[indexx][2] = DoubleToString(adx[0], 2); // Average Directional Movement Index data[indexx][3] = DoubleToString(wilder[0], 2); // Average Directional Movement Index by Welles Wilder data[indexx][4] = DoubleToString(atr[0], 2); // Average True Range data[indexx][5] = DoubleToString(bep[0], 2); // Bears Power data[indexx][6] = DoubleToString(bup[0], 2); // Bulls Power data[indexx][7] = DoubleToString(cci[0], 2); // Commodity Channel Index data[indexx][8] = DoubleToString(ck[0], 2); // Chaikin Oscillator data[indexx][9] = DoubleToString(dm[0], 2); // DeMarker data[indexx][10] = DoubleToString(f[0], 2); // Force Index data[indexx][11] = DoubleToString(g[0], 2); // Gator data[indexx][12] = DoubleToString(bwmfi[0], 2); // Market Facilitation Index data[indexx][13] = DoubleToString(m[0], 2); // Momentum data[indexx][14] = DoubleToString(mfi[0], 2); // Money Flow Index data[indexx][15] = DoubleToString(oma[0], 2); // Moving Average of Oscillator data[indexx][16] = DoubleToString(macd[0], 2); // Moving Averages Convergence/Divergence data[indexx][17] = DoubleToString(rsi[0], 2); // Relative Strength Index data[indexx][18] = DoubleToString(rvi[0], 2); // Relative Vigor Index data[indexx][19] = DoubleToString(std[0], 2); // Standard Deviation data[indexx][20] = DoubleToString(sto[0], 2); // Stochastic Oscillator data[indexx][21] = DoubleToString(wpr[0], 2); // Williams' Percent Range data[indexx][22] = DoubleToString(vidya[0], 2); // Variable Index Dynamic Average data[indexx][23] = DoubleToString(v[0], 2); // Volume datetime currentTime = TimeTradeServer(); MqlDateTime timeStruct; TimeToStruct(currentTime, timeStruct); int currentHour = timeStruct.hour; data[indexx][24]= IntegerToString(currentHour); double close = iClose(_Symbol,PERIOD_CURRENT,1); double open = iOpen(_Symbol,PERIOD_CURRENT,1); double stationary = MathAbs((close-open)/close)*100; data[indexx][25] = DoubleToString(stationary,2); vector features(26); for(int i = 1; i < 26; i++) { features[i] = StringToDouble(data[indexx][i]); } //A lot of the times positions may not open due to error, make sure you don't increase index blindly if(PositionsTotal()>0) indexx++; return features; }
ここでチェックを追加したことに注意してください。
if(PositionsTotal()>0) indexx++;
これは、取引シグナルが発生しても、EAが市場のクローズ時間中に実行されているため、取引がおこなわれない場合があり、テスターは取引をおこなわないためです。
4.テストが終了すると、OnDeInit()が呼び出されてファイルを保存します。
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if (!SaveData) return; if(csvFile.Open(fileName, FILE_WRITE|FILE_ANSI)) { //Write the header csvFile.WriteHeader(headers); //Write data rows csvFile.WriteLine(data); //Close the file csvFile.Close(); } else { Print("File opening error!"); } }
このEAをストラテジーテスターで実行すると、/Tester/Agent-sth000ディレクトリに作成されたcsvファイルが生成されます。
データのクリーニングと調整
これで2つのデータファイルが完成しましたが、解決すべき根本的な問題がまだたくさん残っています。
1. バックテストレポートは乱雑で、.xlsx形式です。私たちが知りたいのは、各取引で勝ったかどうかだけです。
まず、取引結果のみを表示する行を抽出します。次のような内容が表示されるまで、XLSXファイルを下にスクロールする必要がある場合があります。
行番号を覚えておき、次のPythonコードに適用します。
import pandas as pd # Replace 'your_file.xlsx' with the path to your file input_file = 'ML2.xlsx' # Load the Excel file and skip the first {skiprows} rows df = pd.read_excel(input_file, skiprows=10757) # Save the extracted content to a CSV file output_file = 'extracted_content.csv' df.to_csv(output_file, index=False) print(f"Content has been saved to {output_file}.")
次に、この抽出されたコンテンツを次のコードに適用して、処理されたビンを取得します。勝ち取引は1になり、負け取引は0になります。
import pandas as pd # Load the CSV file file_path = 'extracted_content.csv' # Update with the correct file path if needed data = pd.read_csv(file_path) # Select the 'profit' column (assumed to be 'Unnamed: 10') and filter rows as per your instructions profit_data = data["Profit"][1:-1] profit_data = profit_data[profit_data.index % 2 == 0] # Filter for rows with odd indices profit_data = profit_data.reset_index(drop=True) # Reset index # Convert to float, then apply the condition to set values to 1 if > 0, otherwise to 0 profit_data = pd.to_numeric(profit_data, errors='coerce').fillna(0) # Convert to float, replacing NaN with 0 profit_data = profit_data.apply(lambda x: 1 if x > 0 else 0) # Apply condition # Save the processed data to a new CSV file with index output_csv_path = 'processed_bin.csv' profit_data.to_csv(output_csv_path, index=True, header=['bin']) print(f"Processed data saved to {output_csv_path}")
結果ファイルは次のようになります。
ビン | |
---|---|
0 | 1 |
1 | 0 |
2 | 1 |
3 | 0 |
4 | 0 |
5 | 1 |
すべての値が0の場合は、開始行が正しくないことが原因の可能性があります。開始行が偶数か奇数かを確認し、それに応じてPythonコードで変更してください。
2.特徴量データはCFileCSVクラスのためすべて文字列であり、コンマで区切られて1つの列にまとめられています。
次のPythonコードで目的を達成できます。
import pandas as pd # Load the CSV file with semicolon separator file_path = 'ML.csv' data = pd.read_csv(file_path, sep=';') # Drop rows with any missing or incomplete values data.dropna(inplace=True) # Drop any duplicate rows if present data.drop_duplicates(inplace=True) # Convert non-numeric columns to numerical format for col in data.columns: if data[col].dtype == 'object': # Convert categorical to numerical using label encoding data[col] = data[col].astype('category').cat.codes # Ensure all remaining columns are numeric and cleanly formatted for CatBoost data = data.apply(pd.to_numeric, errors='coerce') data.dropna(inplace=True) # Drop any rows that might still contain NaNs after conversion # Save the cleaned data to a new file in CatBoost-friendly format output_file_path = 'Cleaned.csv' data.to_csv(output_file_path, index=False) print(f"Data cleaned and saved to {output_file_path}")
最後に、このコードを使用して2つのファイルを結合し、将来単一のデータフレームとして簡単にアクセスできるようにします。
import pandas as pd # Load the two CSV files file1_path = 'processed_bin.csv' # Update with the correct file path if needed file2_path = 'Cleaned.csv' # Update with the correct file path if needed data1 = pd.read_csv(file1_path, index_col=0) # Load first file with index data2 = pd.read_csv(file2_path, index_col=0) # Load second file with index # Merge the two DataFrames on the index merged_data = pd.merge(data1, data2, left_index=True, right_index=True, how='inner') # Save the merged data to a new CSV file output_csv_path = 'merged_data.csv' merged_data.to_csv(output_csv_path) print(f"Merged data saved to {output_csv_path}")
2つのデータが正しく結合されたことを確認するには、先ほど作成した3つのCSVファイルをチェックして、最終的なインデックスが同じかどうかを確認します。もし同じなら、リラックスできます。
訓練モデル
機械学習の各側面の背後にある技術的な説明については、あまり深く掘り下げません。ただし、ML取引全体に興味がある方は、Marcos López de Prado著の「AdvancesinFinancialMachineLearning」をぜひ読んでみてください。
このセクションの目的は非常に明確です。
まず、pandasライブラリを使用して結合されたデータを読み取り、bin列をyとして、残りをXとして分割します。
data = pd.read_csv("merged_data.csv",index_col=0) XX = data.drop(columns=['bin']) yy = data['bin'] y = yy.values X = XX.values
次に、データを訓練用に80%、テスト用に20%に分割します。
それから訓練をします。分類器の各パラメータの詳細については、CatBoost Webサイトに記載されています。
from catboost import CatBoostClassifier from sklearn.ensemble import BaggingClassifier # Define the CatBoost model with initial parameters catboost_clf = CatBoostClassifier( class_weights=[10, 1], #more weights to 1 class cuz there's less correct cases iterations=20000, # Number of trees (similar to n_estimators) learning_rate=0.02, # Learning rate depth=5, # Depth of each tree l2_leaf_reg=5, bagging_temperature=1, early_stopping_rounds=50, loss_function='Logloss', # Use 'MultiClass' if it's a multi-class problem random_seed=RANDOM_STATE, verbose=1000, # Suppress output (set to a positive number if you want to see training progress) ) fit = catboost_clf.fit(X_train, y_train)
.cbmファイルを保存します。
catboost_clf.save_model('catboost_test.cbm')
残念ながら、まだ終わっていません。MetaTrader 5はONNX形式のモデルのみをサポートしているため、この記事の次のコードを使用してONNX形式に変換します。
model_onnx = convert_sklearn( model, "catboost", [("input", FloatTensorType([None, X.shape[1]]))], target_opset={"": 12, "ai.onnx.ml": 2}, ) # And save. with open("CatBoost_test.onnx", "wb") as f: f.write(model_onnx.SerializeToString())
統計的検定
.onnxファイルを取得したら、それをMQL5/Filesフォルダにドラッグします。先ほどデータを取得するために使用したEAの上に構築します。繰り返しになりますが、EAで.onnxモデルを初期化する手順についてはこちらの記事ですでに詳しく説明しているので、ここではエントリー基準を変更する方法に重点を置きます。
if (maFast[1]>maSlow[1]&&maFast[0]<maSlow[0]&&sellpos == buypos){ xx= getData(); prob = cat_boost.predict_proba(xx); if (prob[1]<max&&prob[1]>min)executeBuy(); } if(maFast[1]<maSlow[1]&&maFast[0]>maSlow[0]&&sellpos == buypos){ xx= getData(); prob = cat_boost.predict_proba(xx); Print(prob); if(prob[1]<max&&prob[1]>min)executeSell(); }
ここでは、getData()を呼び出してベクトル情報を変数xxに格納し、モデルに従って成功の確率を返します。範囲がどの程度になるかがわかるように、print文を追加しました。トレンド追従戦略の場合、精度が低く、取引ごとのリスク対報酬比が高いため、通常、モデルは0.5未満の確率を与えます。
成功確率が低い取引を除外するための閾値を追加し、コーディング部分は完了です。ではテストしてみましょう。
8対2の比率で分割したことを覚えているでしょうか。ここで、およそ2021.1.1~2024.11.1の未訓練データに対してサンプル外テストを実行します。
まず、0.05の確率閾値でインサンプルテストを実行し、適切なデータで訓練したことを確認します。結果はほぼ完璧になるはずです。
次に、ベースラインとして閾値なしでサンプル外テストを実行します。閾値を拡大すると、このベースライン結果をかなり上回る結果になると予想されます。
最後に、サンプル外テストを実施して、さまざまな閾値に対する収益性パターンを分析します。
以下は、閾値=0.05の結果です。
以下は、閾値=0.1の結果です。
以下は、閾値=0.2の結果です。
閾値が0.05の場合、モデルは元の取引の約半分を除外しましたが、これにより収益性が低下しました。予測変数が過剰適合し、訓練されたパターンに過度に適合し、訓練セットとテストセット間で共有される類似のパターンをキャプチャできないことを示している可能性があります。金融機械学習では、これはよくある問題です。ただし、閾値を0.1に増やすと、利益率は徐々に向上し、ベースラインを上回ります。
閾値0.2では、モデルは元の取引の約70%を除外しますが、残りの取引の全体的な品質は元の取引よりも大幅に収益性が高くなります。統計分析によると、この閾値の範囲内では、全体的な収益性は閾値と正の相関関係にあります。これは、取引に対するモデルの信頼度が高まるにつれて、全体的なパフォーマンスも向上し、好ましい結果となることを示唆しています。
モデルの精度が一貫していることを確認するために、Pythonで10倍の交差検証を実行しました。
{'score': array([-0.97148655, -1.25263677, -1.02043177, -1.06770248, -0.97339545, -0.88611439, -0.83877111, -0.95682533, -1.02443847, -1.1385681 ])}
各交差検証スコアの差はわずかであり、モデルの精度はさまざまな訓練期間とテスト期間にわたって一貫していることを示しています。
さらに、平均ログ損失スコアが約-1であるため、モデルのパフォーマンスは中程度に効果的であると考えられます。
モデルの精度をさらに向上させるには、次のアイデアを取り入れることができます。
1. 特徴量エンジニアリング
このように特徴量の重要度をプロットし、重要度の低いものを削除します。
特徴量を選択する場合、市場に関連するものであれば何でも可能ですが、ツリーベースのモデルでは固定値ルールを使用して入力を処理するため、データが定常であることを確認してください。
2.ハイパーパラメータの調整
先ほど説明した分類関数のパラメータを覚えているでしょうか。値のグリッドをループし、どの訓練パラメータが最良の交差検証スコアを生成するかをテストする関数を作成できます。
3.モデルの選択
さまざまな機械学習モデルやさまざまな種類の値を予測するために試すことができます。機械学習モデルは価格の予測には向いていないものの、ボラティリティの予測にはかなり優れていることが分かっています。さらに、隠れマルコフモデルは隠れたトレンドを予測するために広く使用されています。これらは両方とも、トレンド追従戦略の強力なフィルターになる可能性があります。
読者の皆さんには、添付のコードでこれらの方法を試してみて、パフォーマンスの向上に成功したかどうかをお知らせください。
結論
この記事では、トレンド追従戦略のためのCatBoost機械学習フィルターを開発するワークフロー全体を説明しました。途中で、機械学習戦略を研究する際に注意すべきさまざまな側面を強調しました。最後に、統計的テストを通じて戦略を検証し、現在のアプローチから拡張した将来の目標について話し合いました。
添付ファイルテーブル
ファイル名 | 使用法 |
---|---|
ML-Momentum Data.mq5 | 特徴量データを取得するEA |
ML-Momentum.mq5 | 最終実行EA |
CB2.ipynb | CatBoostモデルの訓練とテストのワークフロー |
handleMql5DealReport.py | 取引レポートから有用な行を抽出する |
getBinFromMql5.py | 抽出されたコンテンツからバイナリ結果を取得する |
clean_mql5_csv.py | MT5から抽出したCSVの特徴をクリーンアップする |
merge_data2.py | 特徴量と結果を1つのCSVに統合する |
OnnxConvert.ipynb | .cbmモデルを.onnx形式に変換する |
Classic Trend Following.mq5 | バックボーン戦略EA |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/16487





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