知っておくべきMQL5ウィザードのテクニック(第80回):TD3強化学習で一目均衡表とADX-Wilderのパターンを使用する
はじめに
アルゴリズム取引の領域は、静的なインジケーターベース戦略から、市場状況の変化に適応する動的手法へと進化しています。その中で最も有望な手法の一つが強化学習(RL: Reinforcement Learning)です。前回までに、プロトタイプとしてウィザードで組み立てたEAにおける教師あり学習および強化学習の実装も検討済みです。従来の機械学習手法が予測や分類をおこなうのに対し、強化学習はエージェントが環境と相互作用しながら学習することを可能にします。取引においては、価格の特徴を継続的に観察し、買い、売り、ホールドといった行動を選択し、利益や損失という形で報酬を受け取ることを意味します。
RLアルゴリズムの中でも、TD3は金融分野への応用において有力な候補として注目されています。TD3は連続行動空間向けに設計されており、ポジションサイズやタイミングが二値的ではなく細かい制御を必要とする取引問題に適しています。その前身であるDDPGと比較すると、TD3は、複数のCriticネットワークを使用すること、ターゲット行動にノイズを加えて平滑化すること、さらに方策の更新を遅延させることで、一時的な変動への過学習を防ぐことなどの重要な安定化手法を導入しています。
本記事の主な目的は、Pythonで学習したTD3モデルをMQL5のEAフレームワークに統合し、プロトタイピングに活用する方法を示すことです。具体的には、ONNX形式にエクスポートしたTD3のActorネットワークをカスタムシグナルクラス内でラップして利用する手法を紹介します。これらのシグナルクラスは、その後MQL5ウィザードを用いて自動売買ロボットに組み込まれます。
より実践的な観点を補強するため、記事の最後ではMetaTraderのストラテジーテスターでおこなったフォワードテストを確認します。分析対象は、以前第74回でさらに検討するために選択した3つのシグナルパターン(Pattern_0、Pattern_1、Pattern_5)の各レポートです。
取引における強化学習の背景
強化学習(RL)は、エージェントが環境と相互作用し、行動を取り、その行動に対する報酬から学習するという、シンプルながらも強力なサイクルに基づいています。取引にこれを応用するには、RLの各構成要素がどのような役割を持つかを整理すると理解しやすくなります。
- エージェント:ここでは、買い、売り、ホールドの判断をおこなう取引アルゴリズムを指します。
- 環境:金融市場を表し、価格履歴、指標、派生特徴量などで構成されます。
- 状態:現在の市場状況を数値的に表現したものです。今回の例では、Pattern_0、Pattern_1、Pattern_5の指標値を用いています。以前使用していたバイナリ形式の入力ではなく、より連続的な形式を試しています。
- 行動:取引の判断です。離散的に「ロング/ショート/フラット」とする場合もあれば、連続的にポジションサイズや数量を決定する場合もあります(短期売りは負の値、ロングは正の値など)。本記事では離散的な形式を使用しています。
- 報酬:行動の良し悪しを評価する数値信号です。取引では通常、損益に基づいて計算されますが、ドローダウンや取引コストでリスク調整されることもあります。
このサイクルを繰り返すことで、エージェントは様々な戦略を試し、長期的な累積報酬の最大化を目指します。回帰や分類タスクのように固定されたデータセットで問題が定義されるわけではなく、RLは連続的な意思決定に適しています。今日の行動が明日の結果に影響を与えるという特徴があります。
金融市場には特有の課題があります。それは、市場が非定常的であるという点です。たとえば、ルールが変わらないボードゲームとは異なり、市場のダイナミクスは新規参加者や規制、世界的なイベントによって常に変化します。このため、TD3のようなオフポリシーRL手法は魅力的です。リプレイバッファに蓄積された多様な市場状態から学習でき、SARSAやPPOのようなオンポリシー手法より柔軟に適応できます。
もうひとつの重要な要素は、決定論的方策と確率論的方策の区別です。TD3などの決定論的方策は、各状態を特定の行動に直接マッピングするため、計算効率が高く、低遅延が求められる取引システムへの導入が容易です。裁定取引などで有用です。対照的に、確率論的方策は確率分布から行動をサンプリングするため、探索性の高い環境では有効ですが、注文執行において一貫性が求められる場合にはあまり適していません。
さらに、RLでは探索と活用のバランスという概念も導入されます(これまでのRLに関する記事でも触れています)。取引においては、これは新しい戦略を試す探索と、収益性の高いアプローチを継続する活用のバランスとして現れます。TD3では、学習中に方策に制御されたノイズを加えることで、エージェントがバリエーションを試すことを保証しつつ、「無謀な」行動に逸脱することを防ぎます。
まとめると、RLは取引に対して合理的なフレームワークを提供します。市場を観察し、判断し、利益や損失を経験するという反復プロセスは、エージェント-環境-報酬のループとよく対応しています。TD3を用いることで、これらの考えをさらに発展させ、非揮発性の連続行動アルゴリズムに適用し、Pythonでの学習とMQL5での「実運用」をつなぐことが可能になります。
TD3の場合
トレーダーによるRLの初期実験では、連続行動問題においてDDPG (Deep Deterministic Policy Gradient)が迅速に主要アルゴリズムとして確立されました。DDPGは、Actor-Criticフレームワークとニューラルネットワークを組み合わせることで、ポートフォリオ配分やポジションスケーリングのような環境で決定論的方策を実現しました。しかし、DDPGには2つの重大な弱点がありました。ひとつはQ値の過大評価バイアス、もうひとつは頻繁な方策更新による不安定性です。金融時系列データのように予測不可能性が常態である場合、これらの欠点はシミュレーションでは良好なパフォーマンスを示す一方、実運用では崩壊する傾向があります。
こうした課題を克服するために登場したのがTD3 (Twin Delayed Deep Deterministic Policy Gradient)です。TD3は、DDPGの弱点を補いつつ、その強みを保持するよう設計されています。特に取引に有益な3つの革新があります。それぞれ見ていきましょう。
- 二重Critic:多くのRLアルゴリズムでは、行動価値を推定する際にCriticネットワークを1つだけ使用します。TD3では2つ用います。各状態-行動ペアに対して、2つのCriticがそれぞれ価値推定をおこない、アルゴリズムはその最小値を採用します。この控えめで保守的な手法により、過大評価バイアスが大幅に軽減されます。取引の観点では、リスクの高い取引局面に対して過剰に楽観的にならず、より慎重で安定した意思決定を促します。
- ターゲット方策の平滑化:金融市場はノイズに満ちており、ランダムな価格ジャンプ、偽のブレイクアウト、短期的なボラティリティスパイクが頻発します。TD3では、Criticの更新時にターゲット行動へガウスノイズを加え、その大きさを一定範囲内に制限することで、この問題に対処します。この平滑化により、Criticネットワークは小さな変動に過敏にならない価値関数を学習し、単一のティック異常に過剰反応せず、より堅牢なパターンに基づいて行動する方策を実現します。
- 方策更新の遅延:DDPGでは、ActorネットワークとCriticネットワークは同時に更新されます。しかしTD3では、意図的に遅延を導入しています。方策(Actorネットワーク)の更新頻度をCriticより低くすることで、Actorは学習途中で不安定な信号を追いかけるのではなく、十分に学習されたCriticに導かれることになります。実務上、この遅延はより安定した改善と、突発的で非典型的な取引パターンの減少につながります。
これら3つの要素を組み合わせることで、TD3はRLアルゴリズムの中でも安定性と信頼性に優れた手法となります。金融市場では、ドローダウン管理やリスク調整後リターンが重要であり、他人の資金でのレバレッジ取引が拡大している状況では、TD3の保守的なバイアスは大きな利点です。他のアルゴリズム(PPOやSACなど)は一般的なRLタスクでは人気ですが、確率的で離散的サンプリングに依存しているため、連続的なリアルタイム取引環境ではやや複雑で扱いにくくなります。
さらに、TD3が好まれるもうひとつの理由は、ONNXへのエクスポート対応です。状態を行動(取引判断)にマッピングするActorネットワークだけをエクスポートすれば、トレーダー向けの予測に利用できます。これは軽量でポータブルな設定であり、実行速度が非常に重要なMQL5環境への組み込みに最適です。MQL5で直接学習するのは非効率でリソースを大量に消費します。理想的なアプローチは、負荷のかかる学習はPythonでおこない、MQL5が推論と取引実行を担当する方法です。
まとめると、TD3はTwin Critics、平滑化ターゲット、遅延更新を組み合わせることで、多くの金融市場のランダム性を乗り越えるための安定性を提供します。決定論的設計により、実行時に一貫した判断が可能であり、ONNXによる最小限の要件でMQL5のEAへのシームレスな展開が可能です。したがって、TD3は単なるDDPGの学術的改善にとどまらず、取引戦略においてより堅牢なツールと言えます。
TD3モデルの学習
TD3エージェントの学習フェーズは、完全にPython上でおこなわれます。PyTorchなどのライブラリを活用することで、膨大な金融データを扱う際に必要な柔軟性と計算効率を確保できます。学習プロセスは、大きく分けて環境設計、リプレイバッファ管理、ハイパーパラメータチューニング、学習ループの4つの要素で構成されます。
強化学習において、環境はエージェントとの相互作用ルールを定義します。取引のシナリオでは、過去の市場データを行形式で整形した環境を構築します。各行が1タイムステップに対応し、価格の変化量や一目均衡表のスパン、ADX値などの指標が状態ベクトル(Actorネットワークへの入力)として使用されます。行動は、TD3のActorネットワークが推奨する取引判断であり、通常は[-1,1]の範囲でスケーリングされます。報酬は、エージェントを長期的に利益の出る戦略へ導くための非常に重要な指標です。報酬は、Actorネットワークのバックプロパゲーションにおけるデルタ(勾配)推定を通じて学習を導く役割も担います。本記事での場合、シンプルですが効果的な選択は次のとおりです。
ここで
- ytは市場の方向性の変化
- txn_costは行動変化に応じたペナルティ
古い環境経験を破棄するのではなく、すべて大規模なリプレイバッファに保存します。学習時には、エージェントがこのバッファからランダムにミニバッチをサンプリングし、ネットワークの更新に利用します。この方法により、金融データ特有の時間的相関を排除でき、学習の安定化と汎化性能の向上が可能になります。
取引においては、これは、エージェントが過去のさまざまな市場状況、たとえばトレンド相場の期間や逆行相場のような激しい値動きの期間から学習でき、直近のデータに制約されないことを意味します。
TD3には、その性能に強く影響を与えるさまざまなハイパーパラメータが存在します。特に金融市場のような環境では、その影響は顕著です。代表的なものとして、まずバッチサイズがあります。バッチサイズを大きくすると勾配推定はより平滑になりますが、その分メモリ使用量が増加する傾向があります。次に、割引率(γ)があります。これは、エージェントが即時的な利益だけでなく、長期的な利益もほぼ同等に評価するようにするためのパラメータであり、特にスイングトレードのような取引スタイルにおいて重要な要素です。さらに、ソフトアップデート率(τ)があります。これは、ターゲットネットワークがオンラインネットワークをどの程度の速度で追従するかを制御するもので、更新を緩やかにすることで学習の不安定化を防ぎます。また、方策ノイズおよびノイズ制限も重要です。これらは、Criticネットワークの更新時にターゲット行動へ注入されるランダム性を制御するためのものであり、単一の外れ値に方策が引きずられることを防ぐ役割を果たします。最後に、方策遅延があります。これは、Actorネットワークの更新頻度をCriticネットワークよりも低く保つことで、方策更新を安定させ、段階的で信頼性の高い方策改善を実現します。
これら各ハイパーパラメータに値を割り当てる作業は、科学的な知見と経験的な判断の両面を要します。ただし実務においては、まずTD3に関する論文で推奨されているデフォルト値から始め、その後、取引データの特性やパフォーマンスを確認しながら、最も効果的な設定へと調整していくのが一般的です。学習ループはエポック単位で構成され、エージェントは過去の市場データの各行を1ステップとして順に反復処理していきます。各ステップにおいて、まず状態を観測し、データセットから特徴量を抽出します。これらの特徴量は、一目均衡表およびADXのシグナルで構成され、1つのベクトルとしてまとめられます。次に、Actorネットワークが行動を出力します。学習中は探索を目的として、この行動にはノイズが付加されます。その後、この行動を環境に適用し、報酬を計算するとともに、次の状態へとインデックスを進めます。続いて、状態、行動、報酬、次状態からなる遷移(トランジション)を、前述した形式のタプルとしてリプレイバッファに保存します。最後に、一定の間隔でリプレイバッファからミニバッチをサンプリングし、Criticネットワークを更新します。Actorネットワークについても学習をおこないますが、これは遅延された頻度で更新されます。
この学習処理のPythonによる実装は、以下に示すPythonソースコードのスニペットにまとめられています。
if __name__ == "__main__": # Example dummy data; replace with your real x_data, y_data arrays T = 5000 state_dim = 6 action_dim = 1 #----------------------------MT5 Connections------------------------- if not mt5.initialize(login = 5040189685, server = "MetaQuotes-Demo", password = "JmYp-u4v"): print("initialize() failed, error code =",mt5.last_error()) quit() # set start and end dates for history data from datetime import timedelta, datetime #end_date = datetime.now() end_date = datetime(2024, 7, 1, 0) start_date = datetime(2023, 7, 1, 0) # print start and end dates print("data start date =",start_date) print("data end date =",end_date) # get rates eurusd_rates = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_H4, start_date, end_date) # create dataframe df = pd.DataFrame(eurusd_rates) df = Ichimoku(df) df = ADX_Wilder(df) index = 5 scaler = 1 x_data, drops = GetFeatures(index, scaler, df) y_data = GetStates(df, drops) ## min_rows = min(len(x_data), len(y_data)) min_rows = min(x_data.shape[0], y_data.shape[0]) x_data = x_data[:min_rows] y_data = y_data[:min_rows] print(" x rows =",x_data.shape[0]) print(" y rows =",y_data.shape[0]) # Example usage (your style) state_dim = x_data.shape[1] # Example state dimension action_dim = y_data.shape[1] # Example action dimension agent = TD3Agent(state_dim, action_dim) # alias to TD3Agent # Load the environment env = CustomDataEnv(x_data, y_data, txn_cost=TXN_COST) epochs = 10 # Training loop for epoch in range(epochs): s_np = env.reset() for row in range(x_data.shape[0]): # Build torch state state = torch.tensor(s_np, dtype=torch.float32) # Policy action (with exploration noise) action = agent.act(state, noise_scale=0.1) next_state, reward, done, info = env.step(action) agent.replay_buffer.add(state, action, reward, next_state, done) # Periodic learning if row % BATCH_SIZE == 0: agent.learn(batch_size=BATCH_SIZE) # Advance s_np = next_state if done: env.render() s_np = env.reset() print(f"Epoch {epoch+1}/{epochs} done. Buffer size={agent.replay_buffer.size}") # Export actor ONNX post-training os.makedirs("exports", exist_ok=True) out_path = agent.export_actor_onnx(os.path.join("exports",str(index)+"_"+str(scaler)+"_actor.onnx")) print(f"Exported trained actor to: {out_path}")
十分なエポック数を経た後(上記のコード例では10エポックを使用しています)、TD3のActorネットワークは、状態から行動への安定した対応関係を学習し、形成するようになります。この段階では、Actorネットワークのみをエクスポートします。Actorネットワークは、環境における意思決定の中核を担う重要なコンポーネントです。一方で、Criticネットワークは、主にActorネットワークのバックプロパゲーションを誘導する役割を担うため、Python側に残します。エクスポートされたActorネットワークは、ONNXモデルとしてMQL5上にデプロイでき、より決定論的な取引シグナルを提供します。この分離構成は非常に効果的であり、計算コストの高い学習処理はPythonが担当し、MQL5ではほとんどオーバーヘッドなくリアルタイム推論を実行できます。
システムの知能は、まさにこの学習段階で組み込まれます。学習が完了した後、次のステップとしてActorネットワークを互換性のあるONNX形式でエクスポートします。ONNXは、Pythonの柔軟性とMQL5の取引環境とを接続する橋渡しの役割を果たします。 この構成により、エージェントは過剰な取引を回避するよう学習できます。 TD3のようなオフポリシー型アルゴリズムにおける重要な中核要素の一つが、リプレイバッファです。各インタラクションは、以下のような単純なタプルとして生成されます。
![]()
TD3をONNXにエクスポートする
Python上でTD3エージェントの学習が完了した後におこなうのが、ActorネットワークをMetaTraderへデプロイするためのエクスポート処理です。これを実現する最も実用的な方法が、Open Neural Network Exchange (ONNX)形式を利用することです。ONNXはフレームワーク非依存のモデル形式を提供します。これにより、PyTorchで学習をおこない、その後MetaTraderに備わるONNXランタイムのバインディングを通じて、効率的に推論を実行できます。
では、なぜActorネットワークのみをエクスポートするのでしょうか。これは、TD3、ひいては多くの強化学習アルゴリズムにおいて、Actorが提示された現在の状態に対して必要な行動を生成する役割を担っているためです。Criticネットワークは学習時にのみ必要であり、ライブ環境での推論には不要です。Actorネットワークのみをエクスポートすることで、実行時のフットプリントを最小限に抑えられるだけでなく、MQL5上で学習処理をおこなおうとする不要な複雑さも回避できます。以下に、PythonからActorネットワークをエクスポートする際のコードを示します。
# -------------------- ONNX Export -------------------- def export_actor_onnx(self, path: str): self.actor.eval() dummy = torch.zeros(1, self.state_dim, dtype=torch.float32, device=DEVICE) torch.onnx.export( self.actor, dummy, path, export_params=True, opset_version=17, do_constant_folding=True, input_names=["state"], output_names=["action"], dynamic_axes=None ) return path
上記のエクスポート関数のコードでは、入力はstate(状態)として定義され、出力はaction(行動)として指定されています。出力の形状は、出力層のサイズに合わせて[1, action_dim]となっています。入力層および出力層はいずれも、データ型float32のテンソルです。また、opset_version=17を指定することで、MQL5の実装を含む最新のONNXランタイムとの互換性を確保しています。ONNXファイルをMQL5へ移行する前には、エクスポート結果を必ずPython上で検証することが重要です。この検証では、ファイルが正しく生成されているかを確認するだけでなく、入力層および出力層の形状を読み取り、想定通りであることをチェックします。これは、MQL5上でONNXリソースを初期化する際の重要な前提条件となります。
ONNXファイルの正当性が確認され、学習済みTD3の方策がMQL5のEAに取り込まれると、モデルの意思決定能力は具体的な形を取り、MetaTraderの環境内で取引シグナルとして出力されるようになります。
MQL5におけるONNX
TD3のActorネットワークのエクスポートが完了した後、次におこなうのがMetaTrader 5へのインポートです。これまでの記事でも述べてきたとおり、MQL5にはONNXランタイムが標準で組み込まれており、Pythonで学習したニューラルネットワークを、取引中にネイティブに実行できます。この仕組みにより、強化学習エージェントは、EA内において、従来のシグナルプロバイダーと同様に動作させることが可能になります。MQL5では、ONNXモデルをリソースとして初期化します。具体的には、カスタムシグナルクラスのOnInit()関数内で、以下のようにONNXリソースを設定します。
#resource "Python/0_1_actor.onnx" as uchar __81_0[] #resource "Python/1_1_actor.onnx" as uchar __81_1[] #resource "Python/5_1_actor.onnx" as uchar __81_5[] #include <SRI\PipeLine.mqh> #define __PATTERNS 3 //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CSignalRL_Ichimoku_ADXWilder::CSignalRL_Ichimoku_ADXWilder(void) : m_pattern_0(50), m_pattern_1(50), m_pattern_5(50) //m_patterns_usage(255) { //--- initialization of protected data // //-----omitted source // //--- create model from static buffer m_handles[0] = OnnxCreateFromBuffer(__81_0, ONNX_DEFAULT); m_handles[1] = OnnxCreateFromBuffer(__81_1, ONNX_DEFAULT); m_handles[2] = OnnxCreateFromBuffer(__81_5, ONNX_DEFAULT); // //-----omitted source // }
これにより、シグナルパターン0、1、5に対応する3つのエクスポート済みモデルが、ハンドルを通じて読み込まれ、取引時に推論呼び出しが可能な状態になります。各シグナルパターンに対応するTD3のActorネットワークは、学習時と同一の入力形状を前提としています。これは[1, state_dim]であり、データ型はfloat32のテンソルです。そのため、初期化を完了させるには、ValidationSettings()関数内で検証チェックをおこないます。これは次のように実装されます。
//+------------------------------------------------------------------+ //| Validation settings protected data. | //+------------------------------------------------------------------+ bool CSignalRL_Ichimoku_ADXWilder::ValidationSettings(void) { //--- validation settings of additional filters if(!CExpertSignal::ValidationSettings()) return(false); //--- initial data checks const ulong _out_shape[] = {1, 1}; for(int i = 0; i < __PATTERNS; i++) { // Set input shapes int _in = 3; if(i == __PATTERNS - 1) { _in = 7; } const ulong _in_shape[] = {1, _in}; if(!OnnxSetInputShape(m_handles[i], ONNX_DEFAULT, _in_shape)) { Print("OnnxSetInputShape error ", GetLastError(), " for feature: ", i); return(false); } // Set output shapes if(!OnnxSetOutputShape(m_handles[i], 0, _out_shape)) { Print("OnnxSetOutputShape error ", GetLastError(), " for feature: ", i); return(false); } } //--- ok return(true); }
入力として提供するデータが、これらのONNXモデルが期待する形状と一致しない場合、初期化は失敗します。また、出力の形状も同様に確認する必要があります。モデルが初期化され、正しく検証を通過できるようになれば、推論の実行は比較的簡単です。これについては、以下に示すRunModel()で対応しています。
//+------------------------------------------------------------------+ //| Forward Feed Network, to Get Forecast State. | //+------------------------------------------------------------------+ double CSignalRL_Ichimoku_ADXWilder::RunModel(int Index, ENUM_POSITION_TYPE T, vectorf &X) { vectorf _y(1); _y.Fill(0.0); //Print(" x: ", __FUNCTION__, X); ResetLastError(); if(!OnnxRun(m_handles[Index], ONNX_NO_CONVERSION, X, _y)) { printf(__FUNCSIG__ + " failed to get y forecast, err: %i", GetLastError()); return(double(_y[0])); } //printf(__FUNCSIG__ + " pre y is: " + DoubleToString(_y[0], 5)); if(T == POSITION_TYPE_BUY && _y[0] > 0.5f) { _y[0] = 2.0f * (_y[0] - 0.5f); } else if(T == POSITION_TYPE_SELL && _y[0] < 0.5f) { _y[0] = 2.0f * (0.5f - _y[0]); } //printf(__FUNCSIG__ + " post y is: ", DoubleToString(_y[0], 5)); return(double(_y[0])); }
このようなRLモデルの出力は非常にカスタマイズ可能です。たとえば、モデルが複数の行動を出力する場合(位置サイズ、予測、方向などが出力される場合)、出力ベクトルも同様に多次元となり、この場合は2次元になります。しかし、私たちは1次元の出力を使用しており、出力される浮動小数点値は[0,1]の範囲にあることが期待されています。これは、この単一出力を取引操作に変換する必要があることを意味します。そのため、次の簡略化した解釈を採用しています。
- Actionが0.5以上の場合は、買いポジションを新規で開くか保持する
- Actionが0.5未満の場合は、売りポジションを新規で開くか保持する
なお、買いと売りの間に中立ゾーンは設けていませんが、これは読者が買いと売りの間にミラーリングバッファを設けることで検討できる設定です。たとえば、このバッファを0.2とすると、何もしないゾーンは-0.2f〜0.2fの範囲になります。このような閾値の設定は、ゼロ付近の小さな変動に対してEAが過剰に取引するのを防ぐのに役立ちます。もしEAが固定されていない連続的なポジションサイズで最適化される場合、行動の大きさをブローカーの許容範囲内でロットサイズにスケーリングすることも可能です。
データ前処理の重要性は増しています。Pythonのscikit-learnモジュールは、ネットワークやモデルにデータを入力する前に、正規化や標準化をおこなうことが非常に重要であることを示しています。学習時にPythonで前処理をおこなった場合、推論をおこなう前に、入力データに同じスケーリングを正確に適用することが重要です。取引でmin-maxスケーリングや標準スケーリング、ロバストスケーリングなどを使用した場合は、それらとまったく同じ変換をRunModel()を呼び出す前に適用する必要があります。スケーリングが一致しない場合、Actorネットワークの挙動は予測不能になります。
Direction()関数の内部では、重要なLongCondition()とShortCondition()関数を呼び出します。この関数内での実際の処理の流れは、まず最近のバーから特徴量ベクトルを構築し、その特徴量を学習時の変換(今回の場合は標準スケーラー)に合わせてスケーリングします。その後、これらの特徴量をONNXモデルに入力し、モデルの出力行動を買い/売りの判断に変換します。最後に、MQL5のカスタムシグナルの開閉閾値を用いて取引を実行します。
この一連の流れによって、TD3ActorネットワークはEAの意思決定の中枢となります。MQL5プラットフォームでは、ウィザードを利用してEAを組み立てることができ、トレーダーはモジュール式のコードブロックから自動売買ロボットのプロトタイプを作成することが可能です。新規読者向けのガイドはこちらとこちらにあります。本連載で説明した通り、これらのブロックは主にカスタムシグナルクラスを特徴としており、通常はRSIや移動平均などのインジケーターを使用します。このシグナルブロックは、マネーマネジメントやトレーリングストップのコードブロックと独立したクラスとして組み合わせて使用されます。私たちがRLをこのエコシステムに統合する際には、カスタムシグナルクラスの形でおこなわれ、MQL5ウィザードによって認識されるようになっています。
このクラス内のロングおよびショート条件に対する実装は、以下の通りです。
//+------------------------------------------------------------------+ //| "Voting" that price will grow. | //+------------------------------------------------------------------+ int CSignalRL_Ichimoku_ADXWilder::LongCondition(void) { int result = 0, results = 0; vectorf _x; //--- if the model 0 is used if(((m_patterns_usage & 0x01) != 0)) { matrixf _raw = IsPattern_0(); matrixf _fitted(_raw.Rows(), _raw.Cols()); if(m_pipeline.FitTransformPipeline(_raw, _fitted)) { _x = _fitted.Row(0); //Print(" buy x: ", __FUNCTION__, _x); double _y = RunModel(0, POSITION_TYPE_BUY, _x); if(_y > 0.0) { result += m_pattern_0; results++; } } } //--- if the model 1 is used if(((m_patterns_usage & 0x02) != 0)) { matrixf _raw = IsPattern_1(); matrixf _fitted(_raw.Rows(), _raw.Cols()); if(m_pipeline.FitTransformPipeline(_raw, _fitted)) { _x = _fitted.Row(0); double _y = RunModel(1, POSITION_TYPE_BUY, _x); if(_y > 0.0) { result += m_pattern_1; results++; } } } //--- if the model 5 is used if(((m_patterns_usage & 0x20) != 0)) { matrixf _raw = IsPattern_5(); matrixf _fitted(_raw.Rows(), _raw.Cols()); if(m_pipeline.FitTransformPipeline(_raw, _fitted)) { _x = _fitted.Row(0); double _y = RunModel(2, POSITION_TYPE_BUY, _x); if(_y > 0.0) { result += m_pattern_5; results++; } } } //--- return the result //if(result > 0)printf(__FUNCSIG__+" result is: %i",result); if(results > 0 && result > 0) { return(int(round(result / results))); } return(0); } //+------------------------------------------------------------------+ //| "Voting" that price will fall. | //+------------------------------------------------------------------+ int CSignalRL_Ichimoku_ADXWilder::ShortCondition(void) { int result = 0, results = 0; vectorf _x; //--- if the model 0 is used if(((m_patterns_usage & 0x01) != 0)) { matrixf _raw = IsPattern_0(); matrixf _fitted(_raw.Rows(), _raw.Cols()); if(m_pipeline.FitTransformPipeline(_raw, _fitted)) { _x = _fitted.Row(0); //Print(" sell x: ", __FUNCTION__, _x); double _y = RunModel(0, POSITION_TYPE_SELL, _x); if(_y > 0.0) { result += m_pattern_0; results++; } } } //--- if the model 1 is used if(((m_patterns_usage & 0x02) != 0)) { matrixf _raw = IsPattern_1(); matrixf _fitted(_raw.Rows(), _raw.Cols()); if(m_pipeline.FitTransformPipeline(_raw, _fitted)) { _x = _fitted.Row(0); double _y = RunModel(1, POSITION_TYPE_SELL, _x); if(_y > 0.0) { result += m_pattern_1; results++; } } } //--- if the model 5 is used if(((m_patterns_usage & 0x20) != 0)) { matrixf _raw = IsPattern_5(); matrixf _fitted(_raw.Rows(), _raw.Cols()); if(m_pipeline.FitTransformPipeline(_raw, _fitted)) { _x = _fitted.Row(0); double _y = RunModel(2, POSITION_TYPE_SELL, _x); if(_y > 0.0) { result += m_pattern_5; results++; } } } //--- return the result //if(result > 0)printf(__FUNCSIG__+" result is: %i",result); if(results > 0 && result > 0) { return(int(round(result / results))); } return(0); }
テスト結果
RLアルゴリズムがどれだけ学習上で有望であっても、理論上優れていても、最終的には非常に重要なハードルをクリアする必要があります。それが「フォワードテスト」です。MetaTraderのストラテジーテスターは、ライブ環境をある程度シミュレートする合理的な環境を提供しており、EAを未検証のデータにさらしつつ、実行ルール、スプレッド、注文処理などを考慮できます。過去の記事で説明した通り、バックテストではEAのパラメータを学習データや価格に最適化しますが、フォワードテストでは、同じパラメータが未知の価格データでも同様のパフォーマンスを発揮できるかを単純に確認することになります。
今回の記事では、以前第74回で扱った3種類のシグナルパターン(0、1、5)それぞれについて、別々のテストを行いました。各パターンには別々のActorネットワークがあり、Pythonで学習をおこなった後、ONNXでエクスポートしています。3つのネットワークはすべて、通貨ペアEUR/USDの4時間足を対象に学習をおこない、学習期間は2023年7月1日から2024年7月1日までです。フォワードウォーク期間は2024年7月1日から2025年7月1日まで設定しました。前回の記事から引き続きパイプラインを使用していますが、ネットワークへの入力ベクトルにはいくつか変更を加えています。今回はActorネットワークへの入力としてバイナリ形式を使用していません。過去に、たとえばシグナルに教師あり学習を追加してEAのシグナルをより体系化することを検討した際には、入力ベクトルは純粋に0と1のみで構成されていました。各次元に1がある場合、そのシグナルパターンに対して重要な指標がすべて満たされていることを示していました。
しかし、今回の記事では、探索の一環として、またパイプラインの力を活用する試みとして、再び浮動小数点値や複数のダブル値を持つベクトルを入力として使用しています。そのため、入力ベクトルは確認したインジケーターの大きさを追跡する形になり、前回の記事のように固定サイズ(例えば2次元)ではなくなっています。以下に示す結果は、過去にバイナリ入力ベクトルでシグナルパターン5のみを使用したときのフォワードウォークの可能性を示唆する結果とは大きく異なっています。
Pattern_0


Pattern_1


Pattern_5


上記のレポートから、入力特徴量の形式を0と1のバイナリから、より連続的な形式に変更したことが、パフォーマンスに悪影響を与えたことが分かります。この変更は、最近導入したデータ前処理を活用するためにおこなったものであり、この前処理は重要であると考えられます。ただし、本稿での前処理の実装は最適とは言えない可能性があるため、今後の記事ではこの点を改善できるかどうか検討していきたいと思います。
結論
TD3をPythonで学習させ、ONNXを通してエクスポートし、MQL5にカスタムシグナルクラスとして統合できることを示しました。ただし、本記事でのアプローチはやや非正統的です。なぜなら、強化学習を監督学習のように扱い、推論や実際の取引時には学習やバックプロパゲーションをおこなわないためです。Pythonでのシミュレーションや学習時にはおこないますが、RLは通常、ライブ環境でも継続して学習がおこなわれることを期待されます。
シグナルパターン0、1、5に対しておこなったフォワードテストでは、バイナリ入力から連続特徴量への移行が問題を引き起こす可能性があることが分かりました。特にPattern_5のみがわずかに有望な結果を示しました。結果は混在していましたが、このことは研究の反復的な性質を示しています。すなわち、後続のテストを通じて改善を重ねることで、より堅牢な取引システムに近づくことができるということです。今後の記事では、前処理パイプラインの最適化、より洗練された特徴量選択、およびさらに精緻なフォワードウォークの検討を進めていきたいと考えています。
| 名前 | 説明 |
|---|---|
| WZ-81.mq5 | ヘッダに参照ファイルを示すウィザード組み立てEA |
| SignalWZ-81.mqh | ウィザードアセンブリで使用されるカスタムシグナルクラスファイル |
| 0-1-actor.onnx | エクスポート済みPattern_0ネットワーク |
| 1-1-actor.onnx | Pattern_1ネットワーク |
| 5-1-actor.onnx | Pattern_5ネットワーク |
| PipeLine.mqh | さまざまな変換タイプのクラス |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/19627
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
サイクルベースの取引システム(DPO)の構築と最適化の方法
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
MQL 標準ライブラリエクスプローラー(第1回):CTrade、CiMA、CiATRによる紹介
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索