English Deutsch
preview
MQL5で自己最適化エキスパートアドバイザーを構築する(第13回):行列分解を用いた制御理論の簡単な入門

MQL5で自己最適化エキスパートアドバイザーを構築する(第13回):行列分解を用いた制御理論の簡単な入門

MetaTrader 5 |
90 2
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

金融市場では、事前に計画や予測を立てることが難しい状況が頻繁に発生します。投資家心理はしばしば脆弱であり、国際情勢や突発的なニュースに応じて急速に変化し得ます。そのため、過去には利益が出ていたように見える取引戦略が、リアルタイムの市場では容易に失敗してしまうのです。

このような取引アプリケーションで見られる問題には数多くの理由がありますが、重要な点の1つは、システムが開発・展開されるとその挙動が固定され、通常は人間の介入なしに変更できないということです。つまり、戦略が失敗を糧に改善する機会を持たず、同じ誤りを繰り返す危険を抱えているということです。

図1:金融市場で取引アプリケーションを展開する際に一般的に用いられる設定

この繰り返し発生する問題に対してはさまざまな解決策が提案されてきましたが、その中でも特に有望なのが制御理論です。制御理論は主に、動的かつ混沌とした環境で動作するシステムの挙動を修正し、定められた目標に向けて再調整することを目的としています。

戦略のパフォーマンスを市場との相互作用を記録し、観察するフィードバックコントローラに継続的に入力することで、戦略の挙動と市場の結果との関係を近似的にモデル化できる可能性があります。このコントローラは、勝ちトレードと負けトレードの双方に関連する主要なパターンの識別を目指します。もしそのような構造が存在し、そこから学習できるのであれば、理論上は、混沌として絶えず変化する市場環境の中でも、フィードバックコントローラがシステムのダイナミクスを修正し、収益性を高める方向へ誘導できるはずです。 

このアプローチにより、図1に示した従来の構造から、戦略の展開構造は大きく変化します。図2では、制御理論で用いられる簡潔な表記法を示し、市場からの入力を「M」、戦略の出力を「S」として示します。

図2:市場入力「M」と戦略出力「S」を用いた再定義

これまで行列分解の議論では、主に将来の価格水準やテクニカル指標の変化を予測するための回帰モデルや分類モデルの構築に注目してきました。しかし本記事では、制御理論、とりわけ閉ループフィードバックコントローラに焦点を当てます。本分野は、基礎理論をトレーダーのニーズに合わせて拡張可能であるにもかかわらず、数値主導の取引アプリケーションでは見落とされがちです。

私たちの目的は、フィードバックコントローラが取引システムに対して貴重な、精緻な制御を提供できることを示すことにあります。慎重に構成されたコントローラは、システムを修正し、より収益性の高い取引軌道へと導くことができます。

私たちはまず、高値と安値の2本の移動平均を採用したベースラインとなる取引戦略から出発しました。両方の移動平均は同一の期間を共有し、実質的に移動平均チャネルを形成します。価格がチャネルを上抜ければロング、下抜ければショートという単純な戦略です。このベースラインの収益性が、フィードバックコントローラによって上回るべき基準となります。私たちの取引戦略は以下の図3に示されています。

図3:EURUSD日足チャートによる戦略の可視化

フィードバックコントローラは、介入を開始する前にまず90日間、戦略のパフォーマンスを観察しました。最初の90日間は入力を加えず、観察のみをおこないます。この90日という期間は調整可能なパラメータであり、適切な値が事前に分かっているわけではありません。私たちは、外国為替市場を支配する金融機関のビジネスサイクルを想定して便宜上90日を採用しましたが、読者は自由に変更できます。

この期間中、コントローラは市場価格、口座残高、エクイティ、インジケーター値、ポジション種別など主要な変数を記録しました。これらの観測データは「スナップショット」と呼ぶ行列に保存します。次に、これらのスナップショットが時間とともにどのように変化したかを学習する線形モデルを適合させました。つまり最初の89個のスナップショットが次の89個にどのように写像されたかを学習し、時間経過に伴う戦略状態の遷移を理解させたということです。

90日後、線形システムはすべてのシステム状態を踏まえて次の取引が利益になるかどうかを予測しました。不採算が予測された場合には、状況が改善すると予測されるまで取引を一時停止します。

したがって、フィードバックコントローラの役割は、システム「S」の出力を観察し、システムの挙動(FS)を修正して収益性を取り戻すための新たな制御関数「F」を学習することにあります。これにより、戦略が市場「M」のみに直接影響される状況を回避できます。90日間の観察期間が終了すると、戦略はもはや市場「M」だけに依存して制御されるのではなく、市場からの入力とフィードバックコントローラからの提案「FS + M」の組み合わせによって制御されるようになります。

図4:フィードバックコントローラーが展開後に取引アプリケーションの挙動をどのように変化させるかの可視化

5年間にわたるEUR/USDの日次データを用いて両システムをバックテストしました。その結果、フィードバックコントローラはシステムの収益性を82%向上させました。もともとのベースライン戦略の利益は134ドルでしたが、フィードバックコントローラを追加することで利益は245ドルまで増加しました。さらに、改善されたシステムは取引回数を減らしながらこの利益を達成しています。ポジション数は180から152に減少し、取引活動は15%減りました。これは、リスクを抑えつつ利益を拡大できたことを意味し、取引システムにとって極めて望ましい特徴です。

また、全体的なリスクも低減されました。ベースラインシステムの総損失は–1,092ドルでしたが、フィードバック制御システムでは–838ドルへと縮小し、粗利益はほぼ同等に維持されました。これもまた非常に好ましい結果です。

ベースラインシステムのシャープ比は0.34でしたが、フィードバックコントローラ導入後は0.68に向上し、100%の改善となりました。EUR/USDのような難しい市場においてこれは注目すべき成果です。勝ちトレードの割合も53.89%から57.24%へと6%上昇しました。その結果、負けトレードの割合は同じ幅だけ減少し、フィードバックコントローラが勝ち負けを分ける支配的パターンの学習に成功したことが示されました。最後に、期待収益は0.75から1.61に増加し、114%の改善となりました。

フィードバックコントローラが数値駆動型の取引アプリケーションにおいて重要な役割を果たし得ることは明らかです。適切に構成されれば、戦略が同じ誤りを繰り返すのを防ぐために、的確に介入する方法を自ら学習します。スナップショット的な観測データのみからコントローラを構築するこの手法は「システム同定」と呼ばれます。 

これらのアルゴリズムの系譜は、もともと流体力学に関心を持つエンジニアによって開発されました。航空機の翼を構成する部品の制御を設計する場面で、乱流の影響を予測し、補正するための明確な公式が存在しなかったためです。必然的に、システムの挙動とそれを引き起こす入力の観測から、最適な制御入力を学習する方法が求められたのです。 

本記事では線形モデルに基づいてシステムを構築したため、このアプローチはより正確には「線形システム同定」と呼ばれます。ここで示した結果は、フィードバックコントローラにさらなる役割を与え、将来的には非線形システム同定の探索へと踏み出す強い動機となります。それでは始めましょう。


MQL5を始める

私たちの多くの取引アプリケーションと同様に、まずは重要なシステム定義を設定するところから始めます。ベースライン戦略では、共有する移動平均期間を指定する1つのシステム定義だけが必要です。 

//+------------------------------------------------------------------+
//|                                         Closed Loop Feedback.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 definitions                                               |
//+------------------------------------------------------------------+
#define MA_PERIOD 10

システム設計の次の重要な側面は、グローバル変数の設定です。この特定のベースラインでは、依存するテクニカル指標に関連付けられた少数のグローバル変数のみが必要です。具体的には、2つの移動平均インジケーターのハンドラと、ストップロスの設定に使用するATRの別のハンドラを用意します。これらのインジケーターにはそれぞれ専用のバッファも必要です。

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    ma_h_handler,ma_l_handler,atr_handler;
double ma_h[],ma_l[],atr[];

私たちがここで扱う取引アプリケーションのほとんどには依存関係があります。というのも、すべてのコードを毎回ゼロから書くわけではないためです。このアプリケーションでは、標準の取引ライブラリに加えて、2つのカスタムライブラリを利用します。1つは新しいローソク足の形成を追跡するためのライブラリ、もう1つはBid価格やAsk価格といった主要な価格情報を取得するためのライブラリです。 

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

CTrade      Trade;
Time        *DailyTimeHandler;
TradeInfo   *TradeInfoHandler;

システムが初期化されたら、まずカスタム定義クラスの新しいインスタンスを作成します。インジケーターの新しいインスタンスも作成します。 

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   DailyTimeHandler = new Time(Symbol(),PERIOD_D1);
   TradeInfoHandler = new TradeInfo(Symbol(),PERIOD_D1);
   ma_h_handler = iMA(Symbol(),PERIOD_D1,MA_PERIOD,0,MODE_EMA,PRICE_HIGH);
   ma_l_handler = iMA(Symbol(),PERIOD_D1,MA_PERIOD,0,MODE_EMA,PRICE_LOW);
   atr_handler      = iATR(Symbol(),PERIOD_D1,14);
//---
   return(INIT_SUCCEEDED);
  }

アプリケーションが使用されなくなったら、メモリを効率的に管理するために作成した動的オブジェクトを削除し、リソースを安全に管理するために使用されなくなったインジケーターを解放します。

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

新しい価格レベルが受信されると、OnTickハンドラが呼び出されます。このハンドラはカスタムライブラリを使って、新しい日足が形成されたかどうかを確認します。新しいローソク足が検出された場合、バッファに保存されているインジケーターの値を更新し、現在の終値を記録します。ポジションがない場合は、取引ルールを適用して売買を判断します。終値が移動平均チャネルの上限を上回っていればロングポジションを、下限を下回っていればショートポジションを開始します。 

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(DailyTimeHandler.NewCandle())
     {
      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);

      double c = iClose(Symbol(),PERIOD_D1,0);

      if(PositionsTotal() == 0)
        {
         if(c > ma_h[0])
            Trade.Buy(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetAsk(),(TradeInfoHandler.GetBid()-(atr[0]*2)),(TradeInfoHandler.GetBid()+(atr[0]*2)),"");

         if(c < ma_l[0])
            Trade.Sell(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetBid(),(TradeInfoHandler.GetAsk()+(atr[0]*2)),(TradeInfoHandler.GetAsk()-(atr[0]*2)),"");
        }
     }
  }

最後に、すべての操作が完了したら、アプリケーションの開始時に作成したシステム定義を未定義にしてクリーンアップします。以上が、私たちの取引システムのベースライン版の概要です。

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

次に、履歴データを使用してアプリケーションのテストを開始できます。このテストでは、5年間のEUR/USD日次データを選択し、ベースラインアプリケーションを適用します。

図5:5年間のEUR/USD市場データに基づいた取引アプリケーションのバックテスト

テストをより現実的にするために、ランダム遅延も導入します。実際の市場状況は予測不可能であり、遅延によってその不確実性をシミュレートできるためです。

図6:分析のための現実的なバックテスト条件の選択

そこから、アプリケーションの詳細なパフォーマンス概要を生成できます。この記事の冒頭では、バックテスト結果の概要をすでに紹介しました。ここでは、システムのシャープ比が0.34と低く、平均的な負けトレードが平均的な勝ちトレードよりも大きいことがわかります。公平を期すために付け加えると、改良版アプリケーションにおいても負けトレードの平均は依然として勝ちトレードの平均よりわずかに大きいままでした。しかし、この閉ループフィードバックシステムは、この差をしっかり縮小することに成功しています。

図7:取引アプリケーションのパフォーマンスをまとめた詳細統計

さらに、ベースラインシステムのエクイティカーブを確認すると、確かに上昇傾向は見られます。しかし一貫性に欠け、数カ月にわたってレンジに留まり続けることが多く、利益と損失を周期的に繰り返すため、戦略が長期間足踏みしてしまう場面が目立ちます。まさにこの不安定な挙動こそ、フィードバックコントローラを適用してシステムの振る舞いを修正しようとしている点です。

図8:取引アプリケーションによって生成されたエクイティカーブは形状が不安定であり、望ましくない


初期結果の改善

これで、フィードバックコントローラを用いて初期結果を改善する準備が整いました。不要な重複を避けるため、変更されていないコード部分は省略し、主にアプリケーションの初期バージョンに加えられた修正に焦点を当てます。

ご覧の通り、アプリケーションに必要なシステム定義の数は増えています。初期アプリケーションではシステム定義は1つのみでしたが、更新版では4つの定義に依存します。これらの新しい定義は、(1)フィードバックコントローラーがオンラインになる前に収集する観測値の総数、(2)追跡する特徴量の数(この例では取引戦略のパフォーマンスを捉える12個の特徴量)、(3)現在保有しているポジションの種類を管理するベクトルに関連しています。

//+------------------------------------------------------------------+
//|                                         Closed Loop Feedback.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 definitions                                               |
//+------------------------------------------------------------------+
#define MA_PERIOD    10
#define OBSERVATIONS 90
#define FEATURES     12
#define ACCOUNT_STATES 3

アプリケーションが拡張されるにつれて、グローバル変数の構造も複雑化します。スナップショットを保存する行列、線形システムからの予測を記録するベクトル、そしてシステムが観察段階からライブ取引に移行する準備が整ったかどうかを判定するブールフラグが必要になります。

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    ma_h_handler,ma_l_handler,atr_handler,scenes,b_matrix_scenes;
double ma_h[],ma_l[],atr[];
matrix snapshots,OB_SIGMA,OB_VT,OB_U,b_vector,b_matrix;
vector S,prediction;
vector account_state;
bool predict,permission;

初期化時には、いくつかの前処理がおこなわれます。最初の数ステップは、テクニカル指標を設定するおなじみの手順です。その後、スナップショット行列を12行90列で初期化します。この段階では、すべての値がゼロに設定されます。「許可」フラグはtrueで初期化され、システムは取引の許可を持った状態で開始されます。しかし、90日間の観察期間が終了すると、このフラグはfalseに切り替わり、「予測」フラグがtrueに設定されます。この時点で、システムはもはや無条件で取引を行わず、アクションを実行する前に必ず線形モデルからの承認を得る必要があります。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   DailyTimeHandler  = new Time(Symbol(),PERIOD_D1);
   TradeInfoHandler  = new TradeInfo(Symbol(),PERIOD_D1);
   ma_h_handler      = iMA(Symbol(),PERIOD_D1,MA_PERIOD,0,MODE_EMA,PRICE_HIGH);
   ma_l_handler      = iMA(Symbol(),PERIOD_D1,MA_PERIOD,0,MODE_EMA,PRICE_LOW);
   atr_handler       = iATR(Symbol(),PERIOD_D1,14);
   snapshots         = matrix::Ones(FEATURES,OBSERVATIONS);
   scenes            = 0;
   b_matrix_scenes   = 0;
   account_state     = vector::Zeros(3);
   b_matrix          = matrix::Zeros(1,1);
   prediction        = vector::Zeros(2);
   predict           = false;
   permission        = true;
//---
   return(INIT_SUCCEEDED);
  }

OnTickハンドラは、ベースラインシステム以降、大幅に変更されています。重要な変更点のひとつは、口座状態が3要素のベクトルに格納されるようになったことです。このベクトルはワンホットエンコーディングを用いており、買いポジションが開いている場合は最初の要素が1に、売りポジションが開いている場合は2番目の要素が1に、ポジションが開かれていない場合は3番目の要素が1に設定されます。これにより、線形システムに対して構造化されたカテゴリ情報が提供されます。

取引ロジック自体は従来と同じで、終値が移動平均チャネルの上限を上回る場合は買いを検討し、下限を下回る場合は売りを検討します。最初の90日間、予測フラグがfalseの間は、システムは無条件でエントリーすることができます。その後は、すべての取引判断に線形システムによる検証が必要になります。必要な観測数が収集されると、スナップショット行列は新しいデータに対応してリサイズされ、特定された線形システムによって取引判断がフィルタリングされた上で市場に投入されます。 

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(DailyTimeHandler.NewCandle())
     {
      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);
      double c = iClose(Symbol(),PERIOD_D1,0);

      if(PositionsTotal() == 0)
        {
         account_state = vector::Zeros(ACCOUNT_STATES);

         if(c > ma_h[0])
           {
            if(!predict)
              {
               if(permission)
                  Trade.Buy(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetAsk(),(TradeInfoHandler.GetBid()-(atr[0]*2)),(TradeInfoHandler.GetBid()+(atr[0]*2)),"");
              }

            account_state[0] = 1;
           }

         else
            if(c < ma_l[0])
              {
               if(!predict)
                 {
                  if(permission)
                     Trade.Sell(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetBid(),(TradeInfoHandler.GetAsk()+(atr[0]*2)),(TradeInfoHandler.GetAsk()-(atr[0]*2)),"");
                 }

               account_state[1] = 1;
              }

            else
              {
               account_state[2] = 1;
              }
        }

      if(scenes < OBSERVATIONS)
        {
         take_snapshots();
        }

      else
        {
         matrix temp;
         temp.Assign(snapshots);
         snapshots = matrix::Ones(FEATURES,scenes+1);

         //--- The first row is the intercept and must be full of ones
         for(int i=0;i<FEATURES;i++)
            snapshots.Row(temp.Row(i),i);

         take_snapshots();
         fit_snapshots();

         predict = true;
         permission = false;
        }
      scenes++;
     }
  }

この段階で、システムは関連する特徴量の完全なスナップショットを取得します。前日の始値、高値、安値、終値、テクニカル指標の状態、口座情報、そしてワンホットエンコードされた口座状態ベクトルです。これらの変数を含めることで、システムは戦略と市場との関係をモデル化することを学習できます。私たちの関心は単なる将来の価格予測にとどまらず、取引口座の将来残高を予測することにまで広がっています。そして、もしこの関係が存在し学習可能であれば、フィードバックコントローラは人間の介入なしに戦略の挙動を調整することを学習できるはずです。

//+------------------------------------------------------------------+
//| Record the current state of our system                           |
//+------------------------------------------------------------------+
void take_snapshots(void)
  {
   snapshots[1,scenes] = iOpen(Symbol(),PERIOD_D1,1);
   snapshots[2,scenes] = iHigh(Symbol(),PERIOD_D1,1);
   snapshots[3,scenes] = iLow(Symbol(),PERIOD_D1,1);
   snapshots[4,scenes] = iClose(Symbol(),PERIOD_D1,1);
   snapshots[5,scenes] = AccountInfoDouble(ACCOUNT_BALANCE);
   snapshots[6,scenes] = AccountInfoDouble(ACCOUNT_EQUITY);
   snapshots[7,scenes] = ma_h[0];
   snapshots[8,scenes] = ma_l[0];
   snapshots[9,scenes] = account_state[0];
   snapshots[10,scenes] = account_state[1];
   snapshots[11,scenes] = account_state[2];
  }
//+------------------------------------------------------------------+

次に、2つの行列を用いて線形システムを適合させます。Xは入力、yはターゲットです。これは複数の結果を同時に予測するマルチ出力システムです。y行列は1タイムステップ分オフセットされ、入力は前の89個のスナップショットに対応し、出力はその後の89個のスナップショットに対応します。観測が増えるにつれて、このループは経過したシーンの総数を追跡することで、自然に増大するデータセットに適応します。

ここでいうシーンとは、連続する2つのスナップショット間の時間を指します。本記事ではシステムのスナップショットを毎日取得しているため、シーンの数は読者の好みに応じて調整可能です。擬似逆行列(MQL5のPInv()関数)を用いて、Xからyへの写像の最適係数を求めます。この関数の詳細は本連載のこれまでの記事で説明していますが、PInv()関数の重要性を理解していない読者は、こちらの議論を参照すると良いでしょう。PInv()は私たちが使用する強力なツールです。

適合後、システムはスナップショット、入力とターゲット、学習済み係数、そして予測値を出力します。これらの予測はリアルタイムで解釈されます。モデルが口座残高の増加を予測した場合、取引の許可が与えられます。買いポジションを検討中にモデルが移動平均高値の上昇モメンタムを予測した場合も許可されます。同様に、モデルが移動平均安値の下降モメンタムを予測し、売りポジションが検討される場合も許可されます。それ以外の場合、システムは取引の許可を保留します。

最後に、許可が与えられ、かつポジションが開かれていない場合、システムは希望する取引を実行します。プログラムは、現在の残高、予測残高、そして取引許可の有無を出力します。この手順を経ることで、戦略のパフォーマンス変化を確認するために必要な改良が完了します。コードは、コントローラに「いつ買うか」「いつ売るか」を明示的に指示するものではなく、蓄積された観察から導き出した推論に基づき、コントローラが実行できる一連のアクションを定義しています。

//+------------------------------------------------------------------+
//| Fit our linear model to our collected snapshots                  |
//+------------------------------------------------------------------+
void fit_snapshots(void)
  {
   matrix X,y;
   X.Reshape(FEATURES,scenes);
   y.Reshape(FEATURES-1,scenes);

   for(int i=0;i<scenes;i++)
     {
      X[0,i] = snapshots[0,i];
      X[1,i] = snapshots[1,i];
      X[2,i] = snapshots[2,i];
      X[3,i] = snapshots[3,i];
      X[4,i] = snapshots[4,i];
      X[5,i] = snapshots[5,i];
      X[6,i] = snapshots[6,i];
      X[7,i] = snapshots[7,i];
      X[8,i] = snapshots[8,i];
      X[9,i] = snapshots[9,i];
      X[10,i] = snapshots[10,i];
      X[11,i] = snapshots[11,i];

      y[0,i] = snapshots[1,i+1];
      y[1,i] = snapshots[2,i+1];
      y[2,i] = snapshots[3,i+1];
      y[3,i] = snapshots[4,i+1];
      y[4,i] = snapshots[5,i+1];
      y[5,i] = snapshots[6,i+1];
      y[6,i] = snapshots[7,i+1];
      y[7,i] = snapshots[8,i+1];
      y[8,i] = snapshots[9,i+1];
      y[9,i] = snapshots[10,i+1];
      y[10,i] = snapshots[11,i+1];
     }

//--- Find optimal solutions
   b_vector = y.MatMul(X.PInv());
   Print("Day Number: ",scenes+1);
   Print("Snapshot");
   Print(snapshots);
   Print("Input");
   Print(X);
   Print("Target");
   Print(y);
   Print("Coefficients");
   Print(b_vector);
   Print("Prediciton");
   Print(y.Col(scenes-1));
   prediction = b_vector.MatMul(snapshots.Col(scenes-1));

   if(prediction[4] > AccountInfoDouble(ACCOUNT_BALANCE))
      permission = true;

   else
      if((account_state[0] == 1) && (prediction[6] > ma_h[0]))
         permission = true;

      else
         if((account_state[1] == 1) && (prediction[7] < ma_l[0]))
            permission = true;

         else
            permission = false;

   if(permission)
     {
      if(PositionsTotal() == 0)
        {
         if(account_state[0] == 1)
            Trade.Buy(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetAsk(),(TradeInfoHandler.GetBid()-(atr[0]*2)),(TradeInfoHandler.GetBid()+(atr[0]*2)),"");

         else
            if(account_state[1] == 1)
               Trade.Sell(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetBid(),(TradeInfoHandler.GetAsk()+(atr[0]*2)),(TradeInfoHandler.GetAsk()-(atr[0]*2)),"");
        }
     }

   Print("Current Balabnce: ",AccountInfoDouble(ACCOUNT_BALANCE)," Predicted Balance: ",prediction[4]," Permission: ",permission);
  }
//+------------------------------------------------------------------+

このフレームワークが完成したことで、以前と同じ履歴条件下でアプリケーションをバックテストする準備が整いました。冗長性を避けるため、ここではテストパラメータを再度述べませんが、図6で使用したものと同一であることに注意してください。 

図9:取引戦略の改良版をテストする準備

詳細な分析により、ベースラインシステムとフィードバックコントローラシステムの間には明確な差があることがわかります。純利益は大幅に増加した一方で、粗利益はほぼ変わっていません。これは、コントローラが優れたスキルを用いて損失トレードを直接ターゲットにしていることを示しています。シャープ比は大幅に向上し、利益トレードと損失トレードの平均サイズはほぼ同等となり、ベースラインと比べて顕著な改善が見られました。 

図10:取引アプリケーションによって得られた結果の詳細分析が示す改善点

資産曲線はボラティリティの低下と、より強く一貫した上昇傾向を示しています。これは、クローズドループフィードバックコントローラを取引戦略に導入することで達成したかった理想的な結果です。

図11:改訂された取引戦略によって生成された資産曲線は時間の経過とともにボラティリティが低くなってより安定したリターンを示す

バックテスト中にシステムが取得したスナップショットのスクリーンショットも添付しました。残念ながら、12個すべての特徴量を1つのスクリーンショットに収めることはできませんでした。しかし、スナップショット行列の最後の行には、シミュレートされた取引戦略の開始時と同じ500ドルの残高が含まれていることが確認できます(図6参照)。その後、市場状況と取引戦略の意思決定プロセスによって、口座のエクイティと残高がどのように変化するかを追跡します。

図12:戦略を監視および修正するために追跡しているシステムのパフォーマンススナップショット

出力操作ログのスナップショットによって、この動作が確認できます。たとえば、1645日目には、線形システムが損失を予測したため取引をブロックし、許可フラグがfalseに設定されました。 

図13:線形システムコントローラが不利な市場状況を分析し、取引戦略に対して取引許可を拒否した例

翌日、収益が見込まれるとシステムは許可を与え、取引を実行しました。全体として、改善されたアプリケーションは最初から最後までこのように動作します。これにより、通常の価格予測以外にも予測モデルの多様な活用例があることが示されます。学習したモデルを使えば、勝ちトレードと負けトレードを支配する構造が存在するかどうかを検証し、過去の誤りを繰り返さないように学習することができます。

図14:市場状況が戦略と整合している場合、フィードバックコントローラは取引継続の許可を与える



結論

結論として、フィードバックコントローラは、収益性が高いだけでなく適応性のある取引システムを構築する手段を提供します。これらのシステムは自らのパフォーマンスから学習することで、過去の失敗を繰り返さず、変化する市場環境でも効果を発揮し続けます。私たちの結果は、収益性、効率性、リスク軽減における明確な改善を示しており、制御理論が取引において実際に実用的な価値を持つことを証明しています。今後、この研究は線形モデルを超えて拡張し、市場行動のさらに豊富なパターンを捉えられる非線形フィードバックコントローラの探求につながるでしょう。これにより、不確実性に直面しても、システムの安定性と収益性をさらに高めることが可能になります。

しかし、線形システムを見直す前に、まだ価値のある改善点はいくつか存在します。単純な線形システムでこれらの改善効果を測定することは、将来構築する可能性のある非線形システムに対する信頼できるベンチマークを提供します。

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

添付されたファイル |
最後のコメント | ディスカッションに移動 (2)
Majeed Odubela
Majeed Odubela | 20 9月 2025 において 14:12
volatilityDoctor/Time..Trade インクルード・ファイルが添付されていません。この2つのインクルード・ファイルがないと、テストできません。
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 23 9月 2025 において 14:17
Majeed Odubela #:
volatilityDoctor/Time..Trade インクルード・ファイルが添付されていません。この2つのインクルード・ファイルがないと、あなたの投稿はテストできません。
Majeedあなたの経験についてお悔やみ申し上げます。

しかし、この記事は、関連するシリーズの大きなファミリーの一部であり、相互に構築されていることをご理解ください。

あなたが探しているクラスは、以前の記事で完全にゼロから構築され、添付されています。
チャート同期でテクニカル分析を簡単にする チャート同期でテクニカル分析を簡単にする
「Chart Synchronization for Easier Technical Analysis」は、単一の銘柄に対してすべてのチャート時間足でトレンドライン、四角形、インジケーターなどの一貫したグラフィックオブジェクトが表示されるようにするツールです。パン、ズーム、銘柄変更などの操作はすべての同期したチャートに反映されるため、トレーダーは複数の時間足で同じプライスアクションの文脈をシームレスに確認し、比較できます。
MQL5入門(第20回):ハーモニックパターンの基礎 MQL5入門(第20回):ハーモニックパターンの基礎
本記事では、ハーモニックパターンの基本、構造、そして取引での応用方法について解説します。フィボナッチリトレースメントやフィボナッチエクステンションについて学び、MQL5におけるハーモニックパターン検出の実装方法を理解することで、より高度な取引ツールやエキスパートアドバイザー(EA)を構築するための基礎を築くことができます。
MQL5での取引戦略の自動化(第28回):視覚的フィードバックによるプライスアクションバットハーモニックパターンの作成 MQL5での取引戦略の自動化(第28回):視覚的フィードバックによるプライスアクションバットハーモニックパターンの作成
本記事では、MQL5で弱気と強気の両方のバット(Bat)ハーモニックパターンを、ピボットポイントとフィボナッチ比率を用いて識別し、正確なエントリー、ストップロス、テイクプロフィットレベルを用いて取引を自動化するバットパターンシステムを開発し、チャートオブジェクトによる視覚的フィードバックを強化します。
共和分株式による統計的裁定取引(第3回):データベースのセットアップ 共和分株式による統計的裁定取引(第3回):データベースのセットアップ
本記事では、新しく作成したデータベースを更新するためのMQL5 Serviceのサンプル実装を紹介します。このデータベースはデータ分析や、共和分関係にある株式バスケットの取引に利用されます。データベース設計の根拠についても詳しく説明し、参照用としてデータディクショナリを文書化します。さらに、データベースの作成、スキーマ初期化、市場データ挿入のためのMQL5とPythonのスクリプトも提供します。