
古典的な戦略を再構築する(第14回):高確率セットアップ
これまでの議論では、移動平均のクロスオーバー戦略を再構築する方法として、2つの移動平均インジケーターの期間を固定することで、戦略に含まれる遅延をある程度制御できることを示しました。さらに、1つの移動平均を始値に、もう1つを終値に適用することで、従来のクロスオーバー戦略よりもはるかに感度の高いバージョンを得ることができました。 この新しいフレームワークによって、従来の戦略では得られなかった一定の保証が得られます。まだその議論を読んでいない読者の方は、以前のその記事がこちらからご覧になれます。
本日はさらに進んで、この再構築した移動平均クロスオーバー戦略から、より高い生産性を引き出すことに意味があるのかを探っていきます。移動平均クロスオーバー戦略とEURUSD市場との関係を慎重にモデル化することにより、私たちの戦略が優れて機能する市場環境と、逆に困難を極める市場環境の違いを明らかにできることを期待しています。最終的な目標は、不利な市場環境を検出した際に取引を停止することを学習する、取引戦略を構築することです。
取引戦略の概要
私たちのコミュニティの多くのメンバーの間では、「高確率セットアップ」を積極的に探して取引するべきだという考えが広く受け入れられています。しかし、実際に「高確率の取引セットアップ」とは何かについての正式な定義はほとんど存在しません。特定の取引セットアップに関連する確率をどのように経験的に測定すればよいのでしょうか。誰に尋ねるかによって、その識別方法や活用方法についての定義は異なるでしょう。
本記事では、これらの問題に対処するために、旧来の定義から脱却し、根拠に基づいた数値的な定義に依拠するアルゴリズム的枠組みを提案します。これにより、取引戦略が自動的に、かつ一貫性を持って、そうした高確率のセットアップを特定し、利益を上げられるようにすることを目的としています。
私たちが望むのは、自身の取引戦略と、取引対象として選んだ銘柄との関係をモデル化することです。そのためにはまず、MetaTrader 5端末を用いて、市場を完全に記述する市場データと、取引戦略を構成するすべてのパラメータを取得します。
その後、統計モデルを構築し、戦略が利益を生むシグナルを生成するのか、あるいは損失につながる可能性が高いシグナルを生成しているのかを分類します。
このモデルによって算出された確率が、その特定のシグナルに関連付けられる「確率」となります。こうして、「高確率セットアップ」について、より科学的かつ実証的な観点から語ることができるようになります。それは証拠と関連する市場データに基づいた論理的アプローチです。
この枠組みは、取引戦略に「目的意識」を持たせ、戦略自身が有利だと見込むアクションのみを実行するよう明示的に指示することを可能にします。私たちは、アルゴリズム取引戦略に必要な構成要素を形式化し始めており、それは戦略自身が自らの行動のもっともらしい結果を推定しようとする試みに他なりません。これは、強化学習の理念を、教師あり学習的アプローチで実現しようとする試みとして正しく位置づけられるでしょう。
MQL5を始める
本日の課題は、自分たちの取引戦略と、取引したい銘柄との関係性を学ぶことに焦点を当てています。この目標を達成するために、4つの主要な価格データ(始値、高値、安値、終値)の変化と、2つの移動平均インジケーターの変化を取得します。
ここで注意すべき点として、データにラベル付けするためには、2つのインジケーターの元の実数値と終値の値も必要になります。最終的に、10列から成るCSVファイルにデータを書き出し、その後、当該銘柄における自分たちの戦略との関係性を学習していきます。各ステップにおいて、この結果を、同じモデルが市場価格を直接予測しようとしたときのパフォーマンスと比較します。
これにより、どのターゲット(予測対象)がより学習しやすいかが分かります。私たちのアプローチの魅力は、どちらのターゲットが予測しやすいかに関わらず、両者ともに「価格がどこへ向かうか」という情報を提供してくれるという点にあります。
/+-------------------------------------------------------------------+ //| 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 3 //--- Moving Average Period #define MA_TYPE MODE_SMA //--- Type of moving average we have #define HORIZON 10 //--- Our handlers for our indicators int ma_handle,ma_o_handle; //--- Data structures to store the readings from our indicators double ma_reading[],ma_o_reading[]; //--- File name string file_name = Symbol() + " Reward Modelling.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); //---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); //---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","True Close","True MA C","True MA O","Open","High","Low","Close","MA Close 2","MA Open 2"); } else { FileWrite(file_handle, iTime(_Symbol,PERIOD_CURRENT,i), iClose(_Symbol,PERIOD_CURRENT,i), ma_reading[i], ma_o_reading[i], iOpen(_Symbol,PERIOD_CURRENT,i) - iOpen(_Symbol,PERIOD_CURRENT,(i + HORIZON)), iHigh(_Symbol,PERIOD_CURRENT,i) - iHigh(_Symbol,PERIOD_CURRENT,(i + HORIZON)), iLow(_Symbol,PERIOD_CURRENT,i) - iLow(_Symbol,PERIOD_CURRENT,(i + HORIZON)), iClose(_Symbol,PERIOD_CURRENT,i) - iClose(_Symbol,PERIOD_CURRENT,(i + HORIZON)), ma_reading[i] - ma_reading[(i + HORIZON)], ma_o_reading[i] - ma_o_reading[(i + HORIZON)] ); } } //--- Close the file FileClose(file_handle); } //+------------------------------------------------------------------+
データの分析
まず、市場データをJupyter Notebookに読み込み、戦略のパフォーマンスの数値分析を実行できるようにします。
import pandas as pd
どれだけ先の利益や損失を予測するかを定義します。
HORIZON = 10
データを読み込み、必要な列を追加します。最初の列「Target」は従来のマーケットリターンであり、この場合は10日間のEURUSDのマーケットリターンです。「Class」列は、強気の日には1、それ以外は0です。 3番目の「Action」列は、私たちの取引戦略が引き起こしたアクションを示しており、1は買い、-1は売りを意味します。「Reward」列は、「Target」列と「Action」列の要素ごとの積として計算されます。この掛け算は、次の条件を満たす場合にのみ正の報酬を生み出します。
- Actionが「-1」で、Targetが0未満(戦略が売りを選び、その後価格が下落した)
- Actionが「1」で、Targetが0を超えた(戦略が買いを選び、その後価格が上昇した)
data = pd.read_csv("..\EURUSD Reward Modelling.csv") data['Target'] = 0 data['Class'] = 0 data['Action'] = 0 data['Reward'] = 0 data['Trade Signal'] = 0
ここで、従来のターゲットであるEURUSD終値の10日間の変化を入力します。
data['Target'] = data['True Close'].shift(-HORIZON) - data['True Close'] data.dropna(inplace=True)
クラスにラベルを付けて、グラフ内で強気の日と弱気の日を素早く区別できるようにする必要があります。
data.loc[data['Target'] > 0,'Class'] = 1
ここで、私たちの戦略がどのようなアクションをとったかを記入してみましょう。
data.loc[data['True MA C'] > data['True MA O'],'Action'] = 1 data.loc[data['True MA C'] < data['True MA O'],'Action'] = -1
そして、それらの行動をとった場合の私たちの戦略による利益または損失です。
data['Reward'] = data['Target'] * data['Action']
ここで、行動を起こすべきか待つべきかを示す取引シグナルを入力しましょう。
data.loc[((data['Target'] < 0) & (data['Action'] == -1)),'Trade Signal'] = 1 data.loc[((data['Target'] > 0) & (data['Action'] == 1)),'Trade Signal'] = 1
データを確認してみましょう。 市場の動きを明確に分類するのが難しいことがすぐに分かります。オレンジの点は本来取るべきだった売買シグナルを、青の点は無視すべきだったシグナルを表しています。この散布図でオレンジと青の点が重なって見えるという事実は、数学的に見ても、ほぼ同じ条件下で利益の出るシグナルと損失を生むシグナルが形成される可能性があることを示しています。私たち人間では気づけない、あるいは非常に多くの労力を必要とするような微細な違いや共通点を、統計モデルであれば見つけ出せるかもしれません。
import numpy as np import seaborn as sns import matplotlib.pyplot as plt sns.scatterplot(data=data,x='MA Close 2',y='MA Open 2',hue='Trade Signal') plt.title('Analyzing How Well Moving Average Cross Overs Separate The Market') plt.grid()
図1:移動平均のクロスオーバーでは価格の動きをうまく分類できない傾向が見られる
新しいターゲットが従来のターゲットよりも予測しやすいかどうかを簡単に評価してみましょう。
from sklearn.linear_model import RidgeClassifier from sklearn.model_selection import TimeSeriesSplit,cross_val_score
線形モデルは、信頼性が高く、かつ低コストで近似を得られる点で特に有用です。ここでは、時系列分割オブジェクトを作成して、線形分類器の交差検証をおこないましょう。
tscv = TimeSeriesSplit(n_splits=5,gap=HORIZON)
model = RidgeClassifier()
scores = []
まずは従来のターゲットでモデルを交差検証し、その後、私たちの戦略によって生成される損益を推定する新しいターゲットでモデルを検証します。今回は分類タスクであるため、スコアリング指標は「accuracy」に設定してください。
scores.append(np.mean(np.abs(cross_val_score(model,data.iloc[:,4:-5],data.loc[:,'Class'],cv=tscv,scoring='accuracy')))) scores.append(np.mean(np.abs(cross_val_score(model,data.iloc[:,4:-5],data.loc[:,'Trade Signal'],cv=tscv,scoring='accuracy'))))
バーグラフで結果を可視化すると一目瞭然です。戦略の損益を予測するモデルの方が、市場そのものを直接予測しようとするモデルよりも優れた結果を示しています。市場を直接予測するモデルは、50%の正解率を下回っており、一方で新しいターゲットに基づいたモデルは、かろうじてではありますが、50%の基準を超える結果を出しています。
sns.barplot(scores,color='black') plt.axhline(np.max(scores),linestyle=':',color='red') plt.title('Forcasting Market Returns vs Forecasting Strategy Reward') plt.ylabel('Percentage Accuracy Levels %') plt.xlabel('0: Market Return Forecast | 1: Strategy Profit/Loss Forecast')
図2:線形モデルは、戦略の損益を予測する方が有効である可能性を示唆している
私たちは、得られたパフォーマンスの正確な向上率を計算できます。その結果、市場を直接予測するよりも、戦略と市場の関係を予測することで約7.6%の精度向上が得られていることがわかりました。
scores = (((scores / scores[0]) - 1) * 100) scores[1]
7.595993322203687
バックテスト期間と重複するすべてのデータを削除して、バックテストが実際の市場状況の実質的なシミュレーションを表すようにします。
#Drop all the data that overlaps with your backtest period data = data.iloc[:-((365 * 4) + (30 * 5) + 17),:] data
図3:データフレームの日付がバックテストの対象となる日付と重ならないようにする
線形モデルは、戦略によって生み出される損益を予測するほうが、価格を直接予測するよりも有効である可能性を示してくれました。しかし、取引のバックテストでは、より柔軟な学習モデルを使用して、モデルができる限り多くの有益な情報を捉えられるようにします。
from sklearn.ensemble import GradientBoostingRegressor model = GradientBoostingRegressor()
入力とターゲットにラベルを付けます。
X = ['Open','High','Low','Close','MA Close 2','MA Open 2'] y = 'Trade Signal'
モデルを適合します。
model.fit(data.loc[:,X],data.loc[:,y])
モデルをONNXにエクスポートする準備をします。
import onnx from skl2onnx import convert_sklearn from skl2onnx.common.data_types import FloatTensorType
モデルの入力サイズを定義します。
initial_types = [("float input",FloatTensorType([1,6]))]
モデルを保存します。
onnx_proto = convert_sklearn(model,initial_types=initial_types,target_opset=12) onnx.save(onnx_proto,"EURUSD Reward Model.onnx")
MQL5の始め方
これで、取引アプリケーションの開発を開始する準備が整いました。統計モデルの学習手順に対する変更の効果を評価するために、2つのバージョンの戦略を構築します。両バージョンの取引アルゴリズムは、同一の条件下でバックテストを実施します。重複した情報を避けるために、まずはこれらの条件について理解を深めておきましょう。
最初の重要な設定は銘柄です。先述の通り、この例ではEURUSDペアを取引します。日足で、2020年1月1日から2025年4月1日までの5年間にわたり取引をおこないます。これにより、アプリケーションの有効性を十分に検証できる長期間のデータを扱うことができます。図3を思い出してください。2019年12月29日以降の市場データは実質的に消去しました。
図4:両方のバージョンの取引戦略のバックテストに必要な日付
最後に、市場をモデル化する際の条件は、実際の取引の予測不可能な性質を模倣するように設定します。そのため、遅延はランダムに設定し、すべてのティックは実際のティックデータに基づくものとすることで、より現実的な市場の再現を目指しています。
図5:市場の状況を模倣するために使用する設定は非常に重要である
これまで提案してきた学習手法の改良点が有効かどうかを検証するために、バックテスト可能なアプリケーションの構築を始めましょう。まずは、高確率セットアップを識別するために新たに考案したモデリング手法を使わずに、戦略のパフォーマンスを測定します。要するに、最初のバージョンの取引戦略は、移動平均のクロスオーバーが発生した際に裁量的に取引をおこなう、従来のシンプルな戦略を適用するだけです。これにより、提案する報酬モデリング戦略と比較するためのベンチマークを得ることができます。取引ライブラリをインポートすることから始めます。
//+------------------------------------------------------------------+ //| Reward Modelling.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 we need | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> CTrade Trade;
便利なシステム定数を定義します。これらの定数は、MQL5スクリプトとPythonスクリプトの両方で使用している他の定数と一致しています。
//+------------------------------------------------------------------+ //| System constants | //+------------------------------------------------------------------+ #define MA_PERIOD 3 //--- Moving Average Period #define MA_TYPE MODE_SMA //--- Type of moving average we have #define HORIZON 10 //--- How far into the future we should forecast
グローバル変数とテクニカル指標を設定します。
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int fetch = HORIZON + 1; //+------------------------------------------------------------------+ //| Technical indicators | //+------------------------------------------------------------------+ int ma_handle,ma_o_handle; double ma_reading[],ma_o_reading[]; int position_timer;
各イベントハンドラには、それがトリガーされた際に呼び出される対応するメソッドがペアとして紐づけられています。このような設計スタイルを採用することで、将来的に新しいアイデアが浮かんだ際にも、コードベースの保守性や拡張性を高く保つことができます。イベントハンドラ内に直接コードを書いてしまうと、変更時に多くのコードを注意深く読み解く必要があり、意図せず何かを壊してしまうリスクが高まります。一方、私たちの設計パターンでは、開発者は既存の処理の上に自身の関数呼び出しを追加するだけで、簡単に機能を拡張できます。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- if(!setup()) return(INIT_FAILED); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- release(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- update(); } //+------------------------------------------------------------------+
それでは、各メソッドについて順番に見ていきましょう。最初に設定する関数は、テクニカル指標の読み込みと、ポジションタイマーのリセットを担当する関数です。このポジションタイマーは、システム定数であるHORIZENが指定された期間、各取引を保持し続けるために必要となります。
//+------------------------------------------------------------------+ //| Setup the system | //+------------------------------------------------------------------+ bool setup(void) { //---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); position_timer = 0; return(true); }
releaseメソッドは、使用していないテクニカル指標を単に解放するだけです。MQL5では、自分で使用したものを後始末するのが良い習慣です。
//+------------------------------------------------------------------+ //| Release system variables we are no longer using | //+------------------------------------------------------------------+ void release(void) { IndicatorRelease(ma_handle); IndicatorRelease(ma_o_handle); return; }
updateメソッドは現在の価格レベルを取得し、それらをインジケータバッファにコピーします。さらに、現在のポジションがどれくらいの時間開かれているかを追跡し、適切なタイミングでクローズできるようにします。
//+------------------------------------------------------------------+ //| Update system parameters | //+------------------------------------------------------------------+ void update(void) { //--- Time stamps static datetime time_stamp; datetime current_time = iTime(Symbol(),PERIOD_D1,0); //--- We are on a new day if(time_stamp != current_time) { time_stamp = current_time; if(PositionsTotal() == 0) { //--- Copy indicator values CopyBuffer(ma_handle,0,0,fetch,ma_reading); CopyBuffer(ma_o_handle,0,0,fetch,ma_o_reading); //---Set the values as series ArraySetAsSeries(ma_reading,true); ArraySetAsSeries(ma_o_reading,true); find_setup(); position_timer = 0; } //--- Forecasts are only valid for HORIZON days if(PositionsTotal() > 0) { position_timer += 1; } //--- Otherwise close the position if(position_timer == HORIZON) Trade.PositionClose(Symbol()); } return; }
そして最後に、find_setup関数です。私たちのセットアップは、移動平均のクロスオーバーが発生したときに識別されます。始値が終値を上回ってクロスした場合は、ショートシグナルとして登録されます。そうでない場合は、ロングシグナルとなります。
//+------------------------------------------------------------------+ //| Find a trading oppurtunity | //+------------------------------------------------------------------+ void find_setup(void) { double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID) , ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); double vol = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN); vector ma_o,ma_c; ma_o.CopyIndicatorBuffer(ma_o_handle,0,0,1); ma_c.CopyIndicatorBuffer(ma_handle,0,0,1); if(ma_o[0] > ma_c[0]) { Trade.Sell(vol,Symbol(),ask,0,0,""); } if(ma_o[0] < ma_c[0]) { Trade.Buy(vol,Symbol(),bid,0,0,""); } return; }
先ほど定義したシステム定数を未定義にすることを忘れないでください。
//+------------------------------------------------------------------+ //| Undefine system constatns | //+------------------------------------------------------------------+ #undef HORIZON #undef MA_PERIOD #undef MA_TYPE
直前に、バックテストで使用する設定についてはすでに説明しました。私たちの目的は、開発した統計モデリング手法を用いずに、戦略の有効性を観察することです。エキスパートアドバイザー(EA)を読み込み、図4および図5で説明した設定を使ってバックテストを開始します。
図6:ベンチマークの確立
裁量取引戦略は利益を上げてはいるものの、その利益はわずかです。平均利益と平均損失の差はわずか0.9ドルで、全取引のうち利益が出ているのはわずか51%にすぎません。これはあまり心強い結果とは言えません。シャープレシオは0.62であり、システムを見直すことで改善の余地があるかもしれません。
図7:裁量取引戦略のパフォーマンスの詳細な分析
このバージョンのトレーディング戦略によって生成された残高曲線および資産曲線を分析すると、その欠陥は一目瞭然です。戦略は不安定で変動が大きく、実際、バックテスト開始から4年後の2024年2月には、ほぼ初期の残高に戻ってしまっています。2020年後半から始まり、2024年まで4年間も続いた損失の連鎖から抜け出すのに苦しみました。これはアルゴリズムトレーダーとして、魅力的とは言えない結果です。
図8:裁量的取引戦略によって生成された損益曲線を視覚化する
EAパフォーマンスの向上
さて、次に取引戦略を改善しましょう。アプリケーションに、人間の思考プロセスのように、自分の行動の結果を考慮してから決断する能力を持たせます。
まずは、ONNXモデルをシステムのファイルディレクトリからリソースとしてアプリケーションにインポートすることから始めます。
//+------------------------------------------------------------------+ //| System resources | //+------------------------------------------------------------------+ #resource "\\Files\\EURUSD Reward Model.onnx" as uchar onnx_proto[];
モデルを活用するために、いくつかの追加のグローバル変数が必要になります。主に、モデルハンドラを表す変数と、取得すべきデータ量を示す変数が必要です。
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ long onnx_model; int fetch = HORIZON + 1;
次に、setup関数でONNXモデルを設定する必要があります。以下のコード例では、アプリケーションの両バージョンで変更のないコード部分はあえて省略しています。ONNXモデルはバッファから生成し、モデルの検証をおこない、その後に入力サイズと出力サイズをそれぞれ指定しています。途中のいずれかのステップで失敗した場合は、初期化処理を完全に中止します。
//+------------------------------------------------------------------+ //| Setup the system | //+------------------------------------------------------------------+ bool setup(void) { //---Omitted code that hasn't changed //--- Setup the ONNX model onnx_model = OnnxCreateFromBuffer(onnx_proto,ONNX_DEFAULT); //--- Validate the ONNX model if(onnx_model == INVALID_HANDLE) { Comment("Failed to create ONNX model"); return(false); } //--- Register the ONNX model I/O parameters ulong input_shape[] = {1,6}; ulong output_shape[] = {1,1}; if(!OnnxSetInputShape(onnx_model,0,input_shape)) { Comment("Failed to set input shape"); return(false); } if(!OnnxSetOutputShape(onnx_model,0,output_shape)) { Comment("Failed to set output shape"); return(false); } return(true); }
さらに、アプリケーションを終了するときには、もはや使用していないシステムリソースを解放する必要があります。
//+------------------------------------------------------------------+ //| Release system variables we are no longer using | //+------------------------------------------------------------------+ void release(void) { //--- Omitted code segments that haven't changed OnnxRelease(onnx_model); return; }
アプリケーションには、取引をおこなうかどうか判断する前に、まず取引モデルから予測を取得するよう指示します。モデルの予測値が0.5を超えていれば、戦略によって生成されたシグナルが利益をもたらすとアルゴリズムが期待していることを意味し、取引を許可します。そうでなければ、不利な市場環境が自然に解消するのを待ちます。
//+------------------------------------------------------------------+ //| Find a trading oppurtunity | //+------------------------------------------------------------------+ void find_setup(void) { //--- Skipped parts of the code base that haven't changed //--- Prepare the model's inputs vectorf model_input(6); model_input[0] = (float)(iOpen(_Symbol,PERIOD_CURRENT,0) - iOpen(_Symbol,PERIOD_CURRENT,(HORIZON))); model_input[1] = (float)(iHigh(_Symbol,PERIOD_CURRENT,0) - iHigh(_Symbol,PERIOD_CURRENT,(HORIZON))); model_input[2] = (float)(iLow(_Symbol,PERIOD_CURRENT,0) - iLow(_Symbol,PERIOD_CURRENT,(HORIZON))); model_input[3] = (float)(iClose(_Symbol,PERIOD_CURRENT,0) - iClose(_Symbol,PERIOD_CURRENT,(HORIZON))); model_input[4] = (float)(ma_reading[0] - ma_reading[(HORIZON)]); model_input[5] = (float)(ma_o_reading[0] - ma_o_reading[(HORIZON)]); //--- Prepare the model's output vectorf model_output(1); //--- We failed to run the model if(!OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,model_input,model_output)) Comment("Failed to obtain a forecast"); //--- Everything went fine else { Comment("Forecast: ",model_output[0]); //--- Our model forecasts that our strategy is likely to be profitable if(model_output[0] > 0.5) { if(ma_o[0] > ma_c[0]) { Trade.Sell(vol,Symbol(),ask,0,0,""); } if(ma_o[0] < ma_c[0]) { Trade.Buy(vol,Symbol(),bid,0,0,""); } } } return; }
図4と5に指定された設定を使用してバックテストでアプリケーションを実行し、2つの戦略を公平に比較します。
図9:利益と損失をモデル化した改良版取引戦略による第2回バックテストの結果
裁量ベンチマークで同じ戦略を適用した際のシャープレシオは0.62でしたが、現在の改良版では1.07に上昇し、これは72%の増加を示しています。新しいアルゴリズムで定義された「高確率セットアップ」戦略に従った場合、総純利益は117.13ドルから162.75ドルへ38%増加しました。負けトレードの割合は48.78%から40.48%へ17%減少しています。さらに、戦略によっておこなわれた総取引数は164回から126回に減少しました。つまり、新しいシステムは旧システムの取引数の76%でありながら38%多くの利益を上げており、リスクを抑えつつより大きなリターンを得られているため、以前よりも効果的であると言えます。
図10:新しい取引戦略のパフォーマンスの詳細な要約
改良版取引戦略の残高曲線と資産曲線を読者に示し、その下に元の戦略によって生成された曲線も配置しました。これにより、読者は前後にスクロールすることなく両者を比較できます。最初のバックテスト年が終わる前に、元の戦略はほぼ損益分岐点に戻っていたのに対し、新しい戦略はその期間をうまく乗り切っていることが明確に分かります。
興味深いことに、両方の戦略とも2022年5月から12月の期間にはパフォーマンスに苦戦している点が見られます。これは特に不安定な市場環境の兆候かもしれず、効果的に対処するにはさらなる工夫が必要だと考えられます。
図11:行動の結果を考慮する能力を持つ新しい取引戦略によって生成された損益曲線
図12:元の取引戦略による資産曲線(新しい結果との比較用にコピー)
結論
この記事を読んだ読者は、教師あり統計モデルを用いてアルゴリズム取引に取り組む新しい方法を学びました。読者は自身の取引戦略と取引対象の市場との関係をモデル化するために必要な知識を身につけています。これにより、市場を直接予測しようとする一般的な参加者に対して競争優位を得ることができるのです。私たちは、市場を直接予測することが必ずしも最良の選択肢ではないことを示しました。
ファイル名 | ファイルの説明 |
---|---|
Reward Modelling Benchmark.mq5 | 行動の結果を考慮しようとしない、従来版の取引戦略。すべての取引機会を均等に重み付けし、各取引は必ず利益を生むものと想定します。 |
Reward Modelling.mq5 | 取引をおこなう前にその行動の結果を明示的に予測しようとする、改良版の取引戦略 |
EURUSD Reward Model.onnx | 戦略によって生成されたシグナルが利益をもたらす可能性を推定するONNX統計モデル |
Reward Modelling.ipynb | 過去の市場データを分析し、統計モデルを適合させるために使用したJupyter Notebook |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/17756
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。





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