English Русский Español Português
preview
取引におけるニューラルネットワーク:ハイブリッドグラフシーケンスモデル(最終部)

取引におけるニューラルネットワーク:ハイブリッドグラフシーケンスモデル(最終部)

MetaTrader 5トレーディングシステム |
10 0
Dmitriy Gizlyk
Dmitriy Gizlyk

はじめに

前回の記事では、統合されたGSM+グラフシーケンスフレームワークの理論的側面を検討しました。このフレームワークは、複数のアーキテクチャ的解法の利点を組み合わせた革新的なデータ処理手法です。GSM++は、グラフトークン化局所ノードエンコーディング大域依存関係エンコーディングという3つの主要な段階で構成されます。この構造により、グラフ構造データの処理効率が向上し、金融や時系列および構造化データ分析を伴う複雑なタスクにおける分析能力が強化されます。

GSM++の重要な要素のひとつが階層型グラフトークン化です。これにより、複雑なデータをコンパクトなシーケンシャル表現に変換できます。GSM++で使用されるトークン化手法は、元データのトポロジー特性および時間的特性を保持するため、特徴量抽出の精度を大幅に向上させます。さらに、このアプローチは大規模データセットの解析時に計算コストを削減することにも寄与し、処理速度と分析の深さのバランスを提供します。タスクに応じて分析の詳細レベルを調整できるため、この手法は非常に柔軟で汎用性があります。

局所ノードエンコーディングは、個々のグラフ要素レベルで情報を処理する際に重要な役割を果たします。従来の分析手法では、情報の冗長性が問題となり、計算負荷が増大したり、パターン検出が困難になったりします。しかし、適応型エンコーディング機構を用いることで、最も重要なノード特徴量を抽出し、後続の分析層へ効率的に伝達できます。これにより、不要な情報量を減らし、ノード間の局所的な関係性を正確に識別する能力が向上します。さらに、局所エンコーディングは、ソースデータの変化に動的に適応できるため、突発的な変動が予測精度に大きな影響を与える金融市場において特に重要です。

分析能力は、リカレント系モデルとTransformerを組み合わせたハイブリッドエンコーダによってさらに強化されます。この手法は両方の利点を活用します。リカレント系モデルはイベントの順序を捉えて時系列を効率的に処理し、Self-Attentionを備えたTransformerは、順序に依存しない複雑な依存関係を効果的に検出します。この組み合わせにより、モデル精度が向上するだけでなく、市場ダイナミクスに対するロバスト性も高まります。さらに、ハイブリッドエンコーダはさまざまなシナリオに適応可能で、タスク要件に応じて予測精度と計算効率のバランスを調整できます。

GSM++

前回の記事の実践セクションでは、MQL5を用いてGSM++フレームワークの独自実装を開始しました。金融データの高い変動性を考慮し、フレームワーク著者が提案する階層的類似性に基づくクラスタリング(HAC)は使用せず、学習可能な混合トークン化モジュールを採用しました。これにより、実際の市場データを扱う際のモデルの柔軟性と適応性が大幅に向上します。

実装されたMixture of Tokenization (MoT)アルゴリズムは、1本のバーに対して4種類の異なるトークンを用いることで、より詳細な市場データ分析を可能にします。今回の実験では、以下のアプローチでソースデータをエンコードしています。

  • ノードのトークン化:各バーを個別の分析要素として扱い、個々の特性を評価し、後続の展開に影響を与える重要パラメータを特定します。
  • エッジトークン化:隣接バー間の依存関係を分析し、短期相関や近未来の変化予測に有用なトレンドを検出します。
  • サブグラフのトークン化:バーのグループを解析し、戦略的予測に重要な複雑な構造や安定パターンを特定します。
  • 単一のユニタリ系列のサブグラフトークン化:単変量シーケンスとその相互依存性を詳細に分析し、データ内の隠れたパターンを抽出します。

これらのトークンは、R-MATフレームワークを応用したAttention Pooling機構で統合されます。この手法により、モデルは最も重要な特徴量に集中し、不要なデータを排除することができ、意思決定の精度が大幅に向上します。Attention Poolingの主な利点は、複雑なデータ構造を効率的に処理し、重要な特徴量を強調しつつノイズの影響を最小化できる点です。

この手法を実装するため、CNeuronMoTオブジェクトを作成し、基底機能をCNeuronMHAttentionPoolingから継承しました。これにより、Attention Poolingアルゴリズムを効率的に適用可能です。このモジュラー設計により、モデルの適応性が向上し、市場データの処理能力が高まり、価格変動分析や予測精度の向上につながります。アルゴリズム取引において非常に有用なツールとなります。

次のデータ処理段階は局所ノードエンコーディングです。実装では、以前に開発した適応型特徴量平滑化モジュールを使用します。ノード適応型特徴量平滑化(Node-Adaptive Feature Smoothing, NAFS)は、グラフ構造および個々のノード特性の両方を考慮して、より情報量の多いノード埋め込みを生成します。このアプローチでは、異なるノードが異なる平滑化レベルを必要とする場合があると仮定しています。これにより、各ノードをその近傍文脈に応じて適応的に処理可能です。NAFSは、低次および高次平滑化の組み合わせを適用し、グラフ内の局所および大域依存関係の両方を効果的に捉えます。

NAFSは、アンサンブル特徴量集約法を採用しており、ノイズに対するロバスト性を高め、エンコーディングの信頼性を向上させます。NAFSモジュールの主な利点は次のとおりです。

  • 重要な特徴量を強調しつつ、ノイズを排除する柔軟なデータフィルタリング
  • 大規模グラフや高頻度市場データの解析時に重要な計算コストの最適化
  • 平滑化パラメータの動的な調整により、変化する状況に適応可能
  • 詳細な分析と高度な一般化機能のバランスの取れた組み合わせによってモデル精度を向上

GSM++の最終的なコア要素はハイブリッドエンコーダです。フレームワークの著者は、MambaモジュールとTransformerを組み合わせることを提案しています。本記事の実装でもこの概念に従いますが、さらに改良を加え、MambaChimeraに、TransformerHidformerに置き換えました。

Chimeraは二次元状態空間モデル(2D-SSM)を採用し、時間軸とグラフトポロジーに関連する追加次元の両方で依存関係をモデル化します。このアプローチにより、複雑な市場関係の分析能力が大幅に拡張されます。Chimeraの利点は以下の通りです。

  • 二次元依存関係エンコーディングにより、隠れた市場パターンの検出と予測精度を向上
  • モデル表現力の向上により、複雑な非線形資産関係の深い分析を実現
  • 動的な市場変化への適応能力により、進化する条件に迅速に対応

Hidformerは二重ストリームアーキテクチャを特徴とし、従来のTransformerと異なり、入力データの処理を2つの経路に分けます。1つのエンコーダが時間的依存関係を分析し、もう1つが市場データの周波数成分を処理します。この設計により、より正確な市場動向のモデル化が可能です。Hidformerの主な利点は以下の通りです。

  • 時間解析と周波数解析を分離することで、市場トレンド予測の精度を向上
  • 時間エンコーダではリカレントアテンション機構、周波数エンコーダでは線形アテンション機構を使用し、計算コストを削減し効率を向上

このように、GSM++におけるChimeraHidformerの統合により、依存関係エンコーディングの精度を高め、市場ノイズの影響を最小化し、分析予測の信頼性を向上させることが可能となります。


SSMモジュールの調整

Chimeraフレームワークを用いて構築したモデルのテスト中、ポジション保持期間が長くなることが観測されました。当時、このモデルは短期変動を無視して長期トレンドのみを捉えているのではないかと考えられました。この制限に対処するため、以前実装したオブジェクトをわずかに修正し、追加の二次元状態空間モデル(2D-SSM)を内部に組み込むことにしました。更新されたアルゴリズムはCNeuronChimeraPlusオブジェクトに実装されており、その構造は以下の通りです。

class CNeuronChimeraPlus    :  public CNeuronChimera
  {
protected:
   CNeuron2DSSMOCL    cSSMPlus;
   CLayer             cDiscretizationPlus;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   //---
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronChimeraPlus(void) {};
                    ~CNeuronChimeraPlus(void) {};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint window_in, uint window_out, uint units_in, uint units_out,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override  const   {  return defNeuronChimeraPlus; }
   //---
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau);
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   //---
   virtual bool      Clear(void) override;
  };

新しいオブジェクトの構造からわかるように、以前作成したCNeuronChimeraオブジェクトを完全に書き換えたわけではありません。むしろ親クラスとして利用し、以前実装した機能をすべて継承しています。ただし、3つ目の2D-SSMモジュールと対応するデータ投影ブロックを追加するため、通常の仮想メソッド群をオーバーライドする必要があります。新しく宣言されたオブジェクトおよび継承オブジェクトの初期化はInitメソッドでおこなわれ、パラメータ構造は親クラスの対応メソッドから完全に継承されています。

bool CNeuronChimeraPlus::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                              uint window_in, uint window_out, uint units_in, uint units_out,
                              ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronChimera::Init(numOutputs, myIndex, open_cl, window_in, window_out, units_in,
                                                      units_out, optimization_type, batch))
      return false;

メソッド内では、まず親クラスの対応メソッドを呼び出します。これにより、パラメータ検証や継承コンポーネントの初期化メカニズムが既に実行されます。

親クラスメソッドが正常に実行された後、新たに宣言されたオブジェクトを初期化します。ここで追加される主要なコンポーネントのひとつが、追加の二次元状態空間(2D-SSM)モジュールです。

なお、親クラスのメソッドでは既に2つの2D-SSMモジュールが初期化されており、それぞれ特定のタスクを実行しています。1つは指定された結果次元内で動作し、空間的依存関係の標準的なエンコーディングを提供します。もう1つは拡張された特徴量空間を使用し、分析要素間のより複雑で多階層の関係を捉えます。

モデルの汎化能力を高め、金融データ処理精度を向上させるため、追加の2D-SSMモジュールは既存モジュールとは異なり、指定された特徴量空間内で動作しつつも、時間軸方向に拡張投影をおこないます。このアーキテクチャにより、時系列および空間的に分布した市場データのより精密な分析が可能になります。

   int index = 0;
   if(!cSSMPlus.Init(0, index, OpenCL, window_in, window_out, units_in, 2 * units_out, optimization, iBatch))
      return false;

次に、結果を指定されたサブスペースに投影する必要があります。このとき、時間軸方向の投影は即座にはおこなえないため、内部で小規模なオブジェクト列を組み立てる必要があります。

まず、作成するオブジェクトへのポインタを格納するための動的配列とローカル変数を準備します。

   CNeuronTransposeOCL *transp = NULL;
   CNeuronConvOCL      *conv = NULL;
   cDiscretizationPlus.Clear();
   cDiscretizationPlus.SetOpenCL(OpenCL);

まず、データを必要な形式に変換できるデータ転置オブジェクトを作成します。

   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, 2 * units_out, window_out, optimization, iBatch) ||
      !cDiscretizationPlus.Add(transp))
     {
      delete transp;
      return false;
     }

次に、指定された時間次元に沿って畳み込みデータ投影層を追加します。

   index++;
   conv = new CNeuronConvOCL();
   if(!conv ||
      !conv.Init(0, index, OpenCL, 2 * units_out, 2 * units_out, units_out, window_out, 1, optimization, iBatch) ||
      !cDiscretizationPlus.Add(conv))
     {
      delete conv;
      return false;
     }
   conv.SetActivationFunction(None);

その後、別の転置オブジェクトを使用して、データは元の表現に戻されます。

   index++;
   transp = new CNeuronTransposeOCL();
   if(!transp ||
      !transp.Init(0, index, OpenCL, window_out, units_out, optimization, iBatch) ||
      !cDiscretizationPlus.Add(transp))
     {
      delete transp;
      return false;
     }
   transp.SetActivationFunction((ENUM_ACTIVATION)conv.Activation());
//---
   return true;
  }

これにより内部オブジェクトの初期化が完了し、メソッドは呼び出しプログラムに論理値を返して終了します。

次に、フィードフォワードパスアルゴリズムをfeedForwardメソッドで実装します。親クラスの対応メソッド出力にはデータ正規化が含まれることに注意が必要です。この操作によりデータ分布が変化するため、追加された情報ストリームの非正規化出力と正規化出力を単純に合算すると、一方の経路に予測できないバイアスが生じる可能性があります。これを防ぐため、feedForwardメソッドは完全に書き換えます。

bool CNeuronChimeraPlus::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   for(uint i = 0; i < caSSM.Size(); i++)
     {
      if(!caSSM[i].FeedForward(NeuronOCL))
         return false;
     }
   if(!cSSMPlus.FeedForward(NeuronOCL))
      return false;

このメソッドでは、入力データオブジェクトのポインタを受け取り、内部2D-SSMモデルの同名メソッドに渡します。これらのモデルは3つの異なる投影で結果を生成します。

次に、内部の離散化オブジェクトを用いて、状態空間モデルの出力を比較可能な形式に変換します。

   if(!cDiscretization.FeedForward(caSSM[1].AsObject()))
      return false;
   CNeuronBaseOCL *inp = NeuronOCL;
   CNeuronBaseOCL *current = NULL;
   for(int i = 0; i < cDiscretizationPlus.Total(); i++)
     {
      current = cDiscretizationPlus[i];
      if(!current ||
         !current.FeedForward(inp))
         return false;
      inp = current;
     }

さらに、残差接続経路に沿った入力データの投影を取得します。

   inp = NeuronOCL;
   for(int i = 0; i < cResidual.Total(); i++)
     {
      current = cResidual[i];
      if(!current ||
         !current.FeedForward(inp))
         return false;
      inp = current;
     }

最後に、4つの情報ストリームを合算します。この段階でのみ値の正規化が実施されます。

   inp = cDiscretizationPlus[-1];
   if(!SumAndNormilize(caSSM[0].getOutput(), cDiscretization.getOutput(), Output, 1, false, 0, 0, 0, 1) ||
      !SumAndNormilize(Output, inp.getOutput(), Output, 1, false, 0, 0, 0, 1) ||
      !SumAndNormilize(Output, current.getOutput(), Output, cDiscretization.GetFilters(), true, 0, 0, 0, 1))
      return false;
//---
   return true;
  }

実行された処理の論理結果を呼び出し元プログラムに返し、メソッドの実行を完了します。

私たちの仕事の次の段階は、バックプロパゲーションアルゴリズムの構築です。これは、calcInputGradientsupdateInputWeightsの2つのメソッドで実装されます。前者は参加オブジェクト間で誤差勾配を分配し、後者はモデルの学習可能パラメータを調整します。この段階では、フィードフォワードメソッドとは異なり、親クラスの機能を利用できます。

勾配分配メソッドは、同じ入力データオブジェクトへのポインタを受け取ります。このオブジェクトには、入力データがモデル出力に与える影響を反映した誤差勾配の値が格納されている必要があります。

bool CNeuronChimeraPlus::calcInputGradients(CNeuronBaseOCL *NeuronOCL)
  {
   if(!CNeuronChimera::calcInputGradients(NeuronOCL))
      return false;

標準的な方法とは異なり、このポインタの有効性はチェックされず、同名の親クラスメソッドに即座に渡されます。親クラスのメソッドではすでに制御点や、3つの継承された情報ストリーム(2つの2D-SSMと残差接続経路)への誤差勾配分配アルゴリズムが実装されています。

次に、勾配は追加された経路にのみ伝播されます。まず、後続オブジェクトから受け取った誤差勾配を、追加された離散化モデルの最終層の活性化関数の微分で調整します。

   CNeuronBaseOCL *current = cDiscretizationPlus[-1];
   if(!current ||
      !DeActivation(current.getOutput(), current.getGradient(), Gradient, current.Activation()))
      return false;

調整された値は、その後、離散化ブロックを逆方向に通過させます。そのために、要素を後ろから順に反復処理し、各オブジェクトの対応するメソッドを順番に呼び出します。

   for(int i = cDiscretizationPlus.Total() - 2; i >= 0; i--)
     {
      current = cDiscretizationPlus[i];
      if(!current ||
         !current.calcHiddenGradients(cDiscretizationPlus[i + 1]))
         return false;
     }

次に、誤差勾配が二次元状態空間モデルを通じて伝播されます。

   if(!cSSMPlus.calcHiddenGradients(current.AsObject()))
      return false;

最後に、勾配は入力データのレベルに戻されます。入力データオブジェクトのバッファには、継承された3つの情報ストリームを通じて伝播された勾配がすでに含まれています。したがって、データを保持するために、誤差勾配バッファへのポインタが一時的に置き換えられ、新しく計算された値が保持されます。

   current = cResidual[0];
   CBufferFloat *temp = NeuronOCL.getGradient();
   if(!NeuronOCL.SetGradient(current.getGradient(), false) ||
      !NeuronOCL.calcHiddenGradients(cSSMPlus.AsObject()) ||
      !SumAndNormilize(temp, NeuronOCL.getGradient(), temp, 1, false, 0, 0, 0, 1) ||
      !NeuronOCL.SetGradient(temp, false))
      return false;
//---
   return true;
  }

次に、二次元状態空間モデルから入力データレベルへ誤差勾配を伝播させます。その後、得られた値を以前に蓄積されていた値と合算します。

データバッファへのポインタは元の状態に戻されます。

これで、拡張されたChimeraモジュールのアルゴリズムの確認は完了ですCNeuronChimeraPlusクラスおよびそのすべてのメソッドの完全なコードは、添付ファイルにて確認できます。


ハイブリッドデコーダの構築

アップグレードされたChimeraモジュールの構築が完了した後は、ハイブリッドエンコーダの開発に進みます。前述したように、私たちの実装ではこのエンコーダはChimeraモジュールとHidformerブロックで構成されていますHidformerオブジェクトは、解析対象システムの状態データを入力として受け取り、エージェントの行動のテンソルを出力します。この文脈では、私たちの新しいオブジェクトを「ハイブリッドデコーダ」と呼ぶ方がより正確かもしれません。オブジェクトの構造は以下の通りです。

class CNeuronHypridDecoder :  public CNeuronHidformer
  {
protected:
   CNeuronChimeraPlus   cChimera;
   //---
   virtual bool      feedForward(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      updateInputWeights(CNeuronBaseOCL *NeuronOCL) override;
   virtual bool      calcInputGradients(CNeuronBaseOCL *NeuronOCL) override;

public:
                     CNeuronHypridDecoder(void){};
                    ~CNeuronHypridDecoder(void){};
   //---
   virtual bool      Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                          uint window, uint window_key, uint units_count,
                          uint heads, uint layers, uint stack_size, uint nactions,
                          ENUM_OPTIMIZATION optimization_type, uint batch);
   //---
   virtual int       Type(void) override   const   {  return defNeuronHypridDecoder; }
   //---
   virtual bool      Save(int const file_handle) override;
   virtual bool      Load(int const file_handle) override;
   //---
   virtual bool      WeightsUpdate(CNeuronBaseOCL *source, float tau) override;
   virtual void      SetOpenCL(COpenCLMy *obj) override;
   //---
   virtual bool      Clear(void) override;
  };

この構造では、内部オブジェクトとして修正済みのChimeraモジュールのみが宣言されており、そのアルゴリズムは前述の通りです。CNeuronHidformerオブジェクトは親クラスとして使用されており、これにより機能の重複を避け、追加のインスタンスを明示的に作成することなく、既に実装されたメソッドや構造を効率的に再利用できます。それでもなお、通常の仮想メソッドはオーバーライドする必要があります。

内部オブジェクトは静的に宣言されているため、新クラスのコンストラクタおよびデストラクタは空のままです。これらの宣言済みおよび継承されたオブジェクトの初期化は、Initメソッド内でおこなわれます。

bool CNeuronHypridDecoder::Init(uint numOutputs, uint myIndex, COpenCLMy *open_cl,
                                uint window, uint window_key, uint units_count,
                                uint heads, uint layers, uint stack_size, uint nactions,
                                ENUM_OPTIMIZATION optimization_type, uint batch)
  {
   if(!CNeuronHidformer::Init(numOutputs, myIndex, open_cl, window_key, window_key, nactions,
                             heads, layers, stack_size, nactions, optimization_type, batch))
      return false;

メソッドのパラメータには、作成されるオブジェクトのアーキテクチャを一意に定義する定数が含まれています。パラメータ構造は親クラスの同名メソッドから完全に継承されている点に注意が必要です。しかし、親クラスのメソッドを呼び出す際には、受け取った値がそのまま渡されるわけではありません。これは、親クラスのフィードフォワードメソッドが、外部プログラムからの生データではなく、内部Chimeraモジュールの出力を受け取ることを前提としているためです。したがって、継承された親クラスオブジェクトの初期化時には、内部モジュールの結果次元が入力データとして指定されます。このとき、特徴量次元は内部状態ベクトルの値に設定され、シーケンス長はエージェントの行動空間と一致します。つまり、Chimeraモジュールは潜在状態テンソルを出力し、その各行がエージェント行動の個別要素に対応するトークンを表しています。

親クラスのメソッドが正常に実行された後、同名のChimeraモジュールのメソッドが呼び出され、入力データ次元と所望の出力テンソル次元が指定されます。

   if(!cChimera.Init(0, 0, OpenCL, window, window_key, units_count, nactions, optimization, iBatch))
      return false;
//---
   return true;
  }

メソッドはその後、論理値として実行結果を返し、処理を完了します。

オブジェクト初期化メソッドのアルゴリズムは非常にシンプルであることに気づかれたかもしれません。同様の単純さは他のメソッドにも当てはまります。たとえば、feedForwardメソッドは入力データオブジェクトへのポインタを受け取り、それを直ちに同名のChimeraモジュールのメソッドに渡します。

bool CNeuronHypridDecoder::feedForward(CNeuronBaseOCL *NeuronOCL)
  {
   if(!cChimera.FeedForward(NeuronOCL))
      return false;
   return CNeuronHidformer::feedForward(cChimera.AsObject());
  }

その後、結果は同名の親クラスメソッドに渡され、論理的な結果は呼び出し元プログラムに返されます。そしてメソッドを完了します。

このクラスの残りのメソッドは独立して確認可能であり、完全なコードは添付ファイルに含まれています。


モデルアーキテクチャ

GSM++フレームワークの各構成ブロックの構築が完了した後、次は完全なモデルアーキテクチャの組み立てに進みます。この段階では、単一のモデルであるActorを学習させます。アーキテクチャの詳細な説明はCreateDescriptionsメソッドで提供されています。

bool CreateDescriptions(CArrayObj *&actor)
  {
//---
   CLayerDescription *descr;
//---
   if(!actor)
     {
      actor = new CArrayObj();
      if(!actor)
         return false;
     }

このメソッドは、モデルアーキテクチャを記述するオブジェクト列を格納するための動的配列へのポインタを受け取ります。まず、ポインタの有効性を確認します。必要に応じて新しいオブジェクトインスタンスを作成します。

次に、入力データ層の記述を作成します。ここでも通常通り、十分なサイズを持つ全結合層が使用されます。

//--- Actor
   actor.Clear();
//--- Input layer
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBaseOCL;
   int prev_count = descr.count = (HistoryBars * BarDescr);
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

モデルはターミナルからの生データを受け取ります。初期の前処理はモデル側でおこなわれます。この目的のために、入力データのばらつきのある値を標準化するバッチ正規化層が適用されます。

//--- layer 1
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronBatchNormOCL;
   descr.count = prev_count;
   descr.batch = 1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

その後、混合トークン化モジュールが続きます。

//--- layer 2
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronMoT;
   descr.window = BarDescr;
   descr.count = HistoryBars;
   descr.batch = 1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

次に、S3モジュールを使用して、最適なトークンの順序付け方法を学習します。このモジュールは、各要素の相互依存関係や全体データ構造内での重要性を考慮しながら、最適な要素の並びを特定します。

//--- layer 3
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronS3;
   descr.count = HistoryBars;
   descr.window = BarDescr;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

処理済みのデータはその後、NAFSモジュールによって実装された局所ノードエンコーダに渡されます。

//--- layer 4
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronNAFS;
   descr.count = HistoryBars;
   descr.window = BarDescr;
   descr.window_out = BarDescr;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

エージェントの行動テンソルは、前述のアルゴリズムで説明したハイブリッドデコーダモジュールによって生成されます。

//--- layer 5
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronHypridDecoder;
//--- Windows
     {
      int temp[] = {BarDescr, 120, NActions};      //Window, Stack Size, N Actions
      if(ArrayCopy(descr.windows, temp) < int(temp.Size()))
         return false;
     }
   descr.count = HistoryBars;
   descr.window_out = 32;
   descr.step = 4;                                  // Heads
   descr.layers = 3;
   descr.batch = 1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }

ここで重要なのは、私たちがエージェントのために開発したアーキテクチャは、環境状態の解析のみに焦点を当てているという点です。しかし、これは包括的なリスク評価には不十分です。なぜなら、モデルは利用可能な資産やそれが意思決定に与える影響を考慮していないからです。

この問題を解決するために、アーキテクチャには以前検討したモデルからリスク管理ブロックが組み込まれています。

//--- layer 6
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronMacroHFTvsRiskManager;
//--- Windows
     {
      int temp[] = {3, 15, NActions, AccountDescr}; //Window, Stack Size, N Actions, Account Description
      if(ArrayCopy(descr.windows, temp) < int(temp.Size()))
         return false;
     }
   descr.count = 10;
   descr.window_out = 16;
   descr.step = 4;                              // Heads
   descr.batch = 1e4;
   descr.activation = None;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }
//--- layer 7
   if(!(descr = new CLayerDescription()))
      return false;
   descr.type = defNeuronConvOCL;
   descr.count = NActions / 3;
   descr.window = 3;
   descr.step = 3;
   descr.window_out = 3;
   descr.activation = SIGMOID;
   descr.optimization = ADAM;
   if(!actor.Add(descr))
     {
      delete descr;
      return false;
     }
//---
   return true;
  }

アーキテクチャ記述を作成した後、メソッドは呼び出し元プログラムに論理値として実行結果を返します。

アーキテクチャ記述メソッドの完全なコードは添付ファイルに含まれています。添付ファイルには、以前の作業からコピーした学習およびテスト用スクリプトも含まれており、独立して確認可能です。 


テスト

私たちは、GSMフレームワークの提案手法を独自に解釈して実装する大規模な作業を完了しました。現在、重要な段階に達しています。実際の過去データを用いて、実装したソリューションの有効性を評価する段階です。

ここで注意すべきは、エージェントの最終ニューラル層が、私たちのHidformerフレームワーク実装で使用したアーキテクチャをほぼ忠実に再現している点です。同じリスク管理モジュール構造が適用されており、ハイブリッドデコーダ出力にはCNeuronHidformerオブジェクトが使用されています。このアーキテクチャの類似性により、新モデルの性能をHidformerフレームワークと比較することが妥当となります。

公平な比較のため、両モデルはHidformerの学習に使用したのと同じデータセットで学習させました。次の点を思い出してください。

  • 学習セットは、2024年全期間のEURUSD M1過去データで構成されています。
  • 分析対象の指標パラメータはすべてデフォルト値で、追加の最適化はおこなわず、外部要因の影響を排除しています。
  • 学習済みモデルのテストは2025年1月の履歴データで実施され、他のパラメータはすべて変更せず、客観的な比較を保証しています。

テスト結果を以下に示します。

テスト期間中、モデルは15回の取引を実行しました。M1時間軸での高頻度取引としては比較的少ない件数であり、この数値はベースラインのHidformerモデルよりも低くなっています。利益が出た取引はわずか7件で、勝率は46.67%となり、ベースラインの62.07%を下回っています。この結果から、ショートポジションの精度が低下していることがわかります。しかし、損失の規模はわずかに減少し、利益の出た取引の規模は相対的に増加しています。

ベースラインモデルの平均利益取引と損失取引の比率が1.6であった場合、新モデルではこの比率が4を超えています。これにより、テスト期間中の総利益はほぼ倍増し、利益係数も相応に増加しました。このことは、新アーキテクチャが損失最小化と利益最大化を優先していることを示唆しています。長期的には、より安定した金融結果につながる可能性があります。ただし、テスト期間が短く、取引件数も少ないため、モデルの長期的な性能について結論を出すことはできません。


結論

本記事では、市場データ解析のための高度な手法を統合したGSM++統一グラフシーケンス処理フレームワークを検討しました。このフレームワークの主な利点は、階層的トークン化、局所ノードエンコーディング、大域依存関係エンコーディングを組み込んだハイブリッドデータ表現にあります。この多層的アプローチにより、重要なパターンを効率的に抽出し、高度に情報量の多い埋め込みを形成することが可能となり、金融時系列予測において非常に重要な役割を果たします。

本記事の実践パートでは、提案されたアプローチをMQL5を用いて独自に実装しました。ここで重要なのは、本記事での実装と著者による元の手法との間には実質的な差異が存在する点です。したがって、すべてのテスト結果は実装したソリューションにのみ適用されます。

学習済みモデルは、アウトオブサンプルデータに対して利益を生み出す能力を示しました。ただし、期待する規模には達していません。これは実装した手法の潜在能力を示唆していますが、さらなる作業が必要であることも意味します。具体的には、より代表性のあるデータセットでの学習、包括的なテスト、解析対象指標およびそのパラメータの最適化などです。モデルは学習データ内のパターンを識別するものであり、新たにパターンを生成するわけではありません。


参照文献


記事で使用されているプログラム

# 名前 種類 詳細
1 Research.mq5 EA サンプル収集用EA
2 ResearchRealORL.mq5
EA
Real-ORL法を用いたサンプル収集用EA
3 Study.mq5 EA モデル学習用EA
4 Test.mq5 EA モデルテスト用EA
5 Trajectory.mqh クラスライブラリ システム状態とモデルアーキテクチャ記述構造
6 NeuroNet.mqh クラスライブラリ ニューラルネットワークを作成するためのクラスのライブラリ
7 NeuroNet.cl コードベース OpenCLプログラムコード

MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/17310

添付されたファイル |
MQL5.zip (2482.85 KB)
EAのサンプル EAのサンプル
一般的なMACDを使ったEAを例として、MQL4開発の原則を紹介します。
リスク管理(第3回):リスク管理のメインクラスの構築 リスク管理(第3回):リスク管理のメインクラスの構築
本記事では、システム内のリスクを管理するための重要な基盤となるコアのリスク管理クラスを作成し始めます。今回は、基礎の構築に焦点を当て、基本的な構造、変数、関数を定義します。加えて、最大損益値を設定するために必要なメソッドを実装し、リスク管理の土台を築きます。
エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
初級から中級まで:イベント(II) 初級から中級まで:イベント(II)
この記事では、すべてを必ずしも特定の方法で実装する必要がないことを見ていきます。問題解決には複数のアプローチが存在します。本記事を正しく理解するには、前回の記事で説明された概念を把握していることが前提となります。ここで提示する内容はあくまで学習目的のものであり、最終的なアプリケーションとして利用することを目的としたものではありません。