English Русский 中文 Español Deutsch Português
preview
多通貨エキスパートアドバイザーの開発(第14回):リスクマネージャーにおける適応型ボリューム変更

多通貨エキスパートアドバイザーの開発(第14回):リスクマネージャーにおける適応型ボリューム変更

MetaTrader 5トレーディング | 6 2月 2025, 12:47
58 0
Yuriy Bykov
Yuriy Bykov

はじめに

本連載の以前の記事の一つで、リスク管理について取り上げ、基本的な機能を備えたリスクマネージャークラスを開発しました。このクラスでは、最大日次損失レベルと最大全体損失レベルを設定でき、それらのレベルに達すると取引が停止し、すべてのポジションがクローズされます。日次損失の上限に達した場合、取引は翌日に再開されましたが、全体の損失上限に達した場合は、それ以降取引は再開されませんでした。

ご記憶のとおり、リスクマネージャーの開発において考えられる改良点として、ポジションサイズのよりスムーズな調整(たとえば、制限の半分を超えた場合にポジションを半減する)、およびボリュームのより「賢い」回復(たとえば、損失がポジション削減レベルを超えた場合のみ回復させる)といった点が挙げられました。さらに、最大目標利益のパラメータを追加することも可能です。このパラメータに達すると取引が停止しますが、個人口座での取引にはあまり有用ではないかもしれません。しかし、プロップトレーディング会社の口座では需要が高いでしょう。計画した利益レベルに到達すると、通常は別の口座でのみ取引を継続できるからです。

また、リスクマネージャーの改良案の一つとして、時間ベースの取引制限の導入にも言及しましたが、本記事ではこのトピックには触れず、将来に持ち越したいと思います。今回は、リスクマネージャーに適応型ボリューム変更を実装し、それがどのようなメリットをもたらすかを検証してみましょう。


基本ケース

前回の記事で使用したEAを活用し、リスクマネージャーのパラメータを処理できる機能を追加します。さらに、いくつかの小さな改良も加えますが、これについては後述します。まずは、変更の結果を評価するために使用するEAのパラメータを整理しましょう。

まず、テストEAで使用する取引戦略の単一インスタンスの構成を確定します。最適化された3つの銘柄と3つの時間枠(合計9パターン)それぞれにおいて、passes_パラメータに第2段階の最適化後の最良のパスIDを設定します。各IDは取引戦略の単一インスタンス16個から成る正規化グループを示しており、最終的なグループには合計144個の戦略インスタンスが含まれます。これらは16戦略×9グループに分類されます。ただし、最終グループには正規化係数を適用しないため、正規化されません。

次に、固定取引残高10,000ドルと、スケーリング係数1に対する標準的な最大想定ドローダウン10%を設定します。このスケーリング係数を1から10の範囲で変更してみます。それに伴い、最大許容ドローダウンも増加しますが、リスクマネージャーを用いて10%を超えないように制御します。

具体的には、リスクマネージャーを有効化し、最大全体損失を10,000ドルの10%、つまり1,000ドルに設定します。さらに、最大日次損失はその半分の500ドルに設定します。残高が増加しても、これらの2つのパラメータは変更しません。

以上の設定に基づいて、すべての入力値を調整しましょう。

図1:テストEA入力(元のリスクマネージャー)


2021年~2022年の期間で最適化を実行し、ポジションサイズのスケーリング乗数(scale_)の異なる値に対してEAがどのように機能するかを確認しましょう。次の結果が得られます。

図2:オリジナルのリスクマネージャーを使用してテストEAのscale_パラメータを最適化した結果


結果はscale_パラメータ値の昇順でソートされます。つまり、行が下にいくほど、EAが使用するポジションサイズが大きくなります。ある臨界値を超えると、最終的な結果は1000ドルをわずかに超える損失になることがはっきりとわかります。

テスト期間中、さまざまな深さのドローダウンが発生しました。ドローダウンによってエクイティレベルが9000ドルを下回らなかったパスでは、期間の終了まで取引が継続されました。一方で、ドローダウン中に資金レベルが9000ドルを下回ったパスでは、その時点で取引が停止し、その後再開されませんでした。こうしたパスでは、約1000ドルの損失が発生しました。この損失が計算値をわずかに超えているのは、EAの動作モードが新しい分足が確定したタイミングでのみ処理を行う仕様であるためと考えられます。つまり、指定した損失額に正確に到達した瞬間から、リスクマネージャーが確認してすべてのポジションをクローズするまでの間に、価格がわずかに変動する時間があったためです。

これらの違いはほとんどのケースでは軽微であり、パラメータの上限をわずかに引き下げるか、予定どおりリスクマネージャーの動作ロジックを変更することで無視できる範囲です。ただし、唯一懸念されるのは、scale_= 5.5のケースです。この場合、すべてのポジションをクローズした後の損失が計算上の損失額を20%以上上回り、約1234ドルに達しました。

さらに分析するために、最初のパス(scale_= 1.0)のバランスとエクイティ曲線のグラフを確認してみましょう。その他のパスについては、オープンされるポジションサイズが異なるだけなので、残高およびエクイティの推移は基本的に同じ形状になります。唯一の違いは、ポジションサイズの増加に伴い、グラフの振れ幅が垂直方向に拡大する点です。

図3: テストEAのscale_ = 1.0パスの結果(元のリスクマネージャーを使用)


リスクマネージャーなしの結果と比較してみましょう。

図4: テストEAのscale_ = 1.0パスの結果(リスクマネージャーなし)


リスクマネージャーを使用しない場合、全体の利益は数パーセント増加し、エクイティの最大ドローダウンは変わりませんでした。これは、戦略をグループ化して正規化すること、およびその後の組み合わせによる運用が良好な結果をもたらすことを示唆しています。実際、リスクマネージャーが1日の損失上限を超えた際にポジションをクローズする必要があったのは、2年間でわずか3回程度でした。

次に、リスクマネージャーを使用し、scale_= 3の設定で実行したパスの結果を見てみましょう。利益は約50%増加しましたが、ドローダウンも3倍に拡大しました。

図5: テストEAのscale_ = 3.0パスの結果(元のリスクマネージャーを使用)


しかし、ドローダウンが絶対額で約3,000ドルに達したにもかかわらず、リスクマネージャーは1日あたりのドローダウンを500ドル以内に抑えるよう機能していました。つまり、リスクマネージャーがすべてのポジションをクローズし、翌日の初めに再びオープンする日が数日間続いたということです。結果として、前回の最高エクイティ額から現在の資産は最終的に3,000ドルの赤字に転落しましたが、各日の初めにおける最大エクイティまたは資金に対するドローダウンは1日ごとに500ドルを超えることはありませんでした。ただし、全体の損失には上限があるため、ポジションサイズを大きくするのはリスクが伴います。今回のケースでは、大きなドローダウンがテスト期間の開始後しばらく経ってから発生したことが幸運でした。残高が増加する時間があったため、最大全体損失の許容額も増えました。初期設定では最大損失は1,000ドルとされ、この金額は初期残高を基準に計算されていました。もし、テスト期間がドローダウン3,000ドル発生の直前に開始されていた場合、全体の損失制限を超えてしまい、取引は強制停止されていたでしょう。

開始時点の影響を考慮するため、リスクマネージャーのパラメータを変更し、全体の損失上限を「初期残高」ではなく、「直近の最大残高またはエクイティ」を基準に計算するようにします。ただし、この機能はまだ実装されていないため、まずリスクマネージャーのコードに変更を加える必要があります。


CVirtualRiskManagerのアップグレード

リスクマネージャークラスには、かなり多くの変更が予定されています。それらの多くは同じメソッドの変更を伴うため、各機能ごとの編集を明確に分けるのが難しく、説明がやや複雑になります。そのため、最もシンプルな部分から順に、すでに完成したバージョンのコードについて説明していきます。


列挙型の更新

クラスの説明の前に、後で使用するいくつかの列挙型を宣言しましたが、これらの列挙型の構成を少し拡張します。たとえば、リスクマネージャーの状態を示すENUM_RM_STATE列挙型には、次の2つの新しい状態が追加されます。

  • RM_STATE_RESTORE:新しい日次期間が開始された後、ポジションサイズが完全に復元されるまでの状態。以前は、新しい日が始まるとすぐにポジションサイズを復元していたため、この状態は発生していませんでした。現在では、すぐに復元するのではなく、価格が再度有利な開場値に戻った後にポジションサイズを復元するオプションがあります。この点については後で詳しく説明します。

  • RM_STATE_OVERALL_PROFIT:指定された利益に達した後の状態。このイベントが発生した後、取引は停止します。

また、以前のENUM_RM_CALC_LIMIT列挙型を、日次損失、全体損失、全体利益の3つの個別の列挙型に変更しました。これらの列挙型の値は、以下の2つの項目を決定するために使用されます。

  • パラメータで渡された数値をどのように使用するか:絶対値として使用するのか、それとも日次レベルまたは最高の残高や資金の値からのパーセンテージとして使用するのか
  • しきい値レベルをどの値からカウントするか(日次レベル、または最高残高または資金の値)

これらのオプションは列挙値のコメントに示されています。

// Possible risk manager states
enum ENUM_RM_STATE {
   RM_STATE_OK,            // Limits are not exceeded 
   RM_STATE_DAILY_LOSS,    // Daily limit is exceeded
   RM_STATE_RESTORE,       // Recovery after daily limit
   RM_STATE_OVERALL_LOSS,  // Overall limit exceeded
   RM_STATE_OVERALL_PROFIT // Overall profit reached
};

// Possible methods for calculating limits
enum ENUM_RM_CALC_DAILY_LOSS {
   RM_CALC_DAILY_LOSS_MONEY_BB,    // [$] to Daily Level
   RM_CALC_DAILY_LOSS_PERCENT_BB,  // [%] from Base Balance to Daily Level
   RM_CALC_DAILY_LOSS_PERCENT_DL   // [%] from/to Daily Level
};

// Possible methods for calculating general limits
enum ENUM_RM_CALC_OVERALL_LOSS {
   RM_CALC_OVERALL_LOSS_MONEY_BB,           // [$] to Base Balance
   RM_CALC_OVERALL_LOSS_MONEY_HW_BAL,       // [$] to HW Balance
   RM_CALC_OVERALL_LOSS_MONEY_HW_EQ_BAL,    // [$] to HW Equity or Balance
   RM_CALC_OVERALL_LOSS_PERCENT_BB,         // [%] from/to Base Balance
   RM_CALC_OVERALL_LOSS_PERCENT_HW_BAL,     // [%] from/to HW Balance
   RM_CALC_OVERALL_LOSS_PERCENT_HW_EQ_BAL   // [%] from/to HW Equity or Balance
};

// Possible methods for calculating overall profit
enum ENUM_RM_CALC_OVERALL_PROFIT {
   RM_CALC_OVERALL_PROFIT_MONEY_BB,           // [$] to Base Balance
   RM_CALC_OVERALL_PROFIT_PERCENT_BB,         // [%] from/to Base Balance
};


クラス記述

CVirtualRiskManagerクラスのprotectedセクションに新しいプロパティとメソッドを追加しました。publicセクションに関しては、変更はありません。追加された文字列は緑色で強調表示されます。

//+------------------------------------------------------------------+
//| Risk management class (risk manager)                             |
//+------------------------------------------------------------------+
class CVirtualRiskManager : public CFactorable {
protected:
// Main constructor parameters
   bool              m_isActive;             // Is the risk manager active?

   double            m_baseBalance;          // Base balance

   ENUM_RM_CALC_DAILY_LOSS   m_calcDailyLossLimit; // Method of calculating the maximum daily loss
   double            m_maxDailyLossLimit;          // Parameter of calculating the maximum daily loss
   double            m_closeDailyPart;             // Threshold part of the daily loss

   ENUM_RM_CALC_OVERALL_LOSS m_calcOverallLossLimit;  // Method of calculating the maximum overall loss
   double            m_maxOverallLossLimit;           // Parameter of calculating the maximum overall loss
   double            m_closeOverallPart;              // Threshold part of the overall loss

   ENUM_RM_CALC_OVERALL_PROFIT m_calcOverallProfitLimit; // Method for calculating maximum overall profit
   double            m_maxOverallProfitLimit;            // Parameter for calculating the maximum overall profit

   double            m_maxRestoreTime;             // Waiting time for the best entry on a drawdown
   double            m_lastVirtualProfitFactor;    // Initial best drawdown multiplier


// Current state
   ENUM_RM_STATE     m_state;                // State
   double            m_lastVirtualProfit;    // Profit of open virtual positions at the moment of loss limit 
   datetime          m_startRestoreTime;     // Start time of restoring the size of open positions

// Updated values
   double            m_balance;              // Current balance
   double            m_equity;               // Current equity
   double            m_profit;               // Current profit
   double            m_dailyProfit;          // Daily profit
   double            m_overallProfit;        // Overall profit
   double            m_baseDailyBalance;     // Daily basic balance
   double            m_baseDailyEquity;      // Daily base balance
   double            m_baseDailyLevel;       // Daily base level
   double            m_baseHWBalance;        // balance High Watermark
   double            m_baseHWEquityBalance;  // equity or balance High Watermark
   double            m_virtualProfit;        // Profit of open virtual positions

// Managing the size of open positions
   double            m_baseDepoPart;         // Used (original) part of the overall balance
   double            m_dailyDepoPart;        // Multiplier of the used part of the overall balance by daily loss
   double            m_overallDepoPart;      // Multiplier of the used part of the overall balance by overall loss

// Protected methods
   double            DailyLoss();            // Maximum daily loss
   double            OverallLoss();          // Maximum overall loss

   void              UpdateProfit();         // Update current profit values
   void              UpdateBaseLevels();     // Updating daily base levels

   void              CheckLimits();          // Check for excess of permissible losses
   bool              CheckDailyLossLimit();     // Check for excess of the permissible daily loss
   bool              CheckOverallLossLimit();   // Check for excess of the permissible overall loss
   bool              CheckOverallProfitLimit(); // Check if the specified profit has been achieved

   void              CheckRestore();         // Check the need for restoring the size of open positions
   bool              CheckDailyRestore();       // Check if the daily multiplier needs to be restored
   bool              CheckOverallRestore();     // Check if the overall multiplier needs to be restored

   double            VirtualProfit();        // Determine the profit of open virtual positions
   double            RestoreVirtualProfit(); // Determine the profit of open virtual positions to restore

   void              SetDepoPart();          // Set the values of the used part of the overall balance

public:
   ...
};

m_closeDailyPartおよびm_closeOverallPartプロパティを使用すると、ポジションサイズをよりスムーズに調整できます。これらの使用方法は似ており、唯一の違いは各プロパティが参照する制限(「日次」または「全体」)です。たとえば、m_closeDailyPart = 0.5 に設定すると、損失が日次制限の半分に達した時点でポジションサイズが半分になります。損失がさらに拡大し、日次制限の残りの半分に達した場合、ポジションサイズ(すでに半分になっている)はさらに半分になります。

ポジションサイズの縮小は、m_dailyDepoPartおよびm_overallDepoPartプロパティを変更することによっておこなわれます。これらの値は取引に使用する全体残高の割合を設定する方法に使用され、式において乗数として機能します。したがって、いずれかを半分にすると、全体の取引量も半分になります。

//+------------------------------------------------------------------+
//| Set the value of the used part of the overall balance            |
//+------------------------------------------------------------------+
void CVirtualRiskManager::SetDepoPart() {
   CMoney::DepoPart(m_baseDepoPart * m_dailyDepoPart * m_overallDepoPart);
}

関数で使用されるm_baseDepoPartプロパティには、取引に使用する残高の元のサイズの値が含まれています。

m_maxRestoreTimeおよびm_lastVirtualProfitFactorプロパティは、ポジションサイズを復元する可能性を判断するために使用されます。

最初のプロパティは、仮想利益が負でない場合でもサイズが復元されるまでの時間を分単位で指定します。つまり、この時間を過ぎると、EAは仮想ポジションに対応する実際の市場ポジションを再度開きます。この時、実際のポジションがクローズされている間に価格が正しい方向に動き、仮想利益が制限に達したときよりも大きくなった場合でも開かれます。それまでは、仮想ポジションの利益が時間の経過とともに変動する推定値よりも小さい場合にのみボリュームの回復がおこなわれます。

2番目のプロパティは、新しい日次期間の開始時に仮想ポジションの損失が、制限に達した時点での仮想ポジションの損失より何倍大きくなければならないかを決定する乗数を指定します。これにより、ポジションサイズが直ちに復元されます。たとえば、このパラメータの値が1の場合、1日の初めにサイズが回復するのは、仮想ポジションが同じドローダウンを維持しているか、制限に達した最後の時点と比較してさらに深いドローダウンに陥った場合のみです。

また、制限に到達したときは、ドローダウンが設定された制限を超えた場合だけでなく、たとえばm_closeDailyPartが0.5の場合、1日の制限の半分に達したときにもトリガーされたと見なされる点にも注目する必要があります。


仮想ポジションの利益を計算する

ポジションの部分的なクローズとその後のポジションサイズの復元を適用する場合、戦略に従ってオープンされたすべてのポジションがまだオープンのままである場合、その時点での浮動利益または損失がいくらになるかを正しく判断する必要があります。したがって、オープン仮想ポジションの現在の利益を計算する方法がリスクマネージャークラスに追加されました。ドローダウン制限に達してもポジションは閉じられないため、仮想ポジションに対応するオープン市場ポジションのおおよその利益をいつでも判断できます。この計算値は、手数料やスワップを考慮していないため、実際の状況と完全に一致するわけではありませんが、私たちの目的には十分な精度です。

前回は、このメソッドを使用して計算された結果がその後のアクションを決定することはありませんでしたが、今回は必要になります。加えて、仮想ポジションの利益を正確に計算するために、いくつかの小さな修正が加えられました。重要なのは、1つの仮想ポジションの利益を求めるために使用されるメソッドCMoney::Profit()が、計算に残高使用乗数(CMoney::DepoPart())を適用している点です。もしこの乗数を減少させた場合、仮想利益の計算は、元々のサイズではなく、縮小後のポジションサイズに基づいておこなわれます。

私たちが求めているのは、初期のポジションサイズに基づいた利益ですので、計算をおこなう前に一時的に初期の残高利用乗数を復元します。その後、仮想ポジションの利益を計算し、計算後にSetDepoPart()メソッドを呼び出して、現在の残高使用乗数を再設定します。

//+------------------------------------------------------------------+
//| Determine the profit of open virtual positions                   |
//+------------------------------------------------------------------+
double CVirtualRiskManager::VirtualProfit() {
// Access the receiver object
   CVirtualReceiver *m_receiver = CVirtualReceiver::Instance();

// Set the initial balance usage multiplier
   CMoney::DepoPart(m_baseDepoPart);

   double profit = 0;

// Find the profit sum for all virtual positions
   FORI(m_receiver.OrdersTotal(), profit += CMoney::Profit(m_receiver.Order(i)));

// Restore the current balance usage multiplier
   SetDepoPart();

   return profit;
}


ハイウォーターマーク

より良い分析のために、最初の口座残高だけでなく、たとえば最後に達成された残高の最大値から最大損失レベルを計算する機能を追加したいと考えています。そのため、常に更新する必要があるプロパティのリストにm_baseHWBalancem_baseHWEquityBalanceを追加しました。UpdateProfit()メソッドでは、全体の利益が基本残高ではなく、残高または資本の最高値に対して計算されるかどうかをチェックしながら、その計算を追加しました。

//+------------------------------------------------------------------+
//| Updating current profit values                                   |
//+------------------------------------------------------------------+
void CVirtualRiskManager::UpdateProfit() {
// Current equity
   m_equity = AccountInfoDouble(ACCOUNT_EQUITY);

// Current balance
   m_balance = AccountInfoDouble(ACCOUNT_BALANCE);

// Maximum balance (High Watermark)
   m_baseHWBalance = MathMax(m_balance, m_baseHWBalance);

// Maximum balance or equity (High Watermark)
   m_baseHWEquityBalance = MathMax(m_equity, MathMax(m_balance, m_baseHWEquityBalance));

// Current profit
   m_profit = m_equity - m_balance;

// Current daily profit relative to the daily level
   m_dailyProfit = m_equity - m_baseDailyLevel;

// Current overall profit relative to base balance
   m_overallProfit = m_equity - m_baseBalance;

// If we take the overall profit relative to the highest balance,
   if(m_calcOverallLossLimit       == RM_CALC_OVERALL_LOSS_MONEY_HW_BAL
         || m_calcOverallLossLimit == RM_CALC_OVERALL_LOSS_PERCENT_HW_BAL) {
      // Recalculate it
      m_overallProfit = m_equity - m_baseHWBalance;
   }

// If we take the overall profit relative to the highest balance or equity,
   if(m_calcOverallLossLimit       == RM_CALC_OVERALL_LOSS_MONEY_HW_EQ_BAL
         || m_calcOverallLossLimit == RM_CALC_OVERALL_LOSS_PERCENT_HW_EQ_BAL) {
      // Recalculate it
      m_overallProfit = m_equity - m_baseHWEquityBalance;
   }

// Current profit of virtual open positions
   m_virtualProfit = VirtualProfit();

   ...
}


checkLimitメソッド

このメソッドにも変更が加えられました。コードをいくつかの補助メソッドに分割したため、現在は次のようになっています。

//+------------------------------------------------------------------+
//| Check loss limits                                                |
//+------------------------------------------------------------------+
void CVirtualRiskManager::CheckLimits() {
   if(false
         || CheckDailyLossLimit()     // Check daily limit
         || CheckOverallLossLimit()   // Check overall limit
         || CheckOverallProfitLimit() // Check overall profit
     ) {
      // Remember the current level of virtual profit
      m_lastVirtualProfit = m_virtualProfit;

      // Notify the recipient about changes
      CVirtualReceiver::Instance().Changed();
   }
}

1日損失制限をチェックするメソッドでは、まず、m_closeDailyPartパラメータで定義された1日の制限またはその指定された部分に達したかどうかを確認します。もし達していた場合、その後、全体残高の使用済み部分の乗数を日次損失分だけ減少させます。すでに十分に小さくなっている場合は、完全にリセットします。その後、全体残高の使用済み部分の値を設定し、リスクマネージャーを達成した日次損失の状態に切り替えます。

//+------------------------------------------------------------------+
//| Check daily loss limit                                           |
//+------------------------------------------------------------------+
bool CVirtualRiskManager::CheckDailyLossLimit() {
// If daily loss is reached and positions are still open
   if(m_dailyProfit < -DailyLoss() * (1 - m_dailyDepoPart * (1 - m_closeDailyPart))
      && CMoney::DepoPart() > 0) {

      // Reduce the multiplier of the used part of the overall balance by the daily loss
      m_dailyDepoPart *= (1 - m_closeDailyPart);

      // If the multiplier is already too small,
      if(m_dailyDepoPart < 0.05) {
         // Set it to 0
         m_dailyDepoPart = 0;
      }

      // Set the value of the used part of the overall balance
      SetDepoPart();
    
      ... 

      // Set the risk manager to the achieved daily loss state
      m_state = RM_STATE_DAILY_LOSS;

      return true;
   }

   return false;
}

全体損失限度を確認するメソッドも同様に機能します。唯一の違いは、全体損失に対する全体残高の使用部分の乗数がゼロになった場合にのみ、リスクマネージャーが達成された全体損失の状態に切り替わる点です。

//+------------------------------------------------------------------+
//| Check the overall loss limit                                     |
//+------------------------------------------------------------------+
bool CVirtualRiskManager::CheckOverallLossLimit() {
// If overall loss is reached and positions are still open
   if(m_overallProfit < -OverallLoss() * (1 - m_overallDepoPart * (1 - m_closeOverallPart))
         && CMoney::DepoPart() > 0) {
      // Reduce the multiplier of the used part of the overall balance by the overall loss
      m_overallDepoPart *= (1 - m_closeOverallPart);

      // If the multiplier is already too small,
      if(m_overallDepoPart < 0.05) {
         // Set it to 0
         m_overallDepoPart = 0;

         // Set the risk manager to the achieved overall loss state
         m_state = RM_STATE_OVERALL_LOSS;
      }

      // Set the value of the used part of the overall balance
      SetDepoPart();
      
      ...

      return true;
   }

   return false;
}

指定された利益が達成されたかどうかの確認はさらに簡単です。達成された場合、対応する乗数をリセットし、リスクマネージャーを達成された全体利益の状態に設定します。

//+------------------------------------------------------------------+
//| Check if the specified profit has been achieved                  |
//+------------------------------------------------------------------+
bool CVirtualRiskManager::CheckOverallProfitLimit() {
// If overall loss is reached and positions are still open
   if(m_overallProfit > m_maxOverallProfitLimit && CMoney::DepoPart() > 0) {
      // Reduce the multiplier of the used part of the overall balance by the overall loss
      m_overallDepoPart = 0;

      // Set the risk manager to the achieved overall profit state
      m_state = RM_STATE_OVERALL_PROFIT;

      // Set the value of the used part of the overall balance
      SetDepoPart();
      
      ... 

      return true;
   }

   return false;
}

これらの3つのメソッドはすべて、対応する制限に達したかどうかという問いに対するブール値を返します。もし達成された場合、現在の仮想利益レベルをCheckLimits()メソッドに保存し、ポジション構成の変更について市場ポジション量の受信者に通知します。

日次ベースライン更新メソッドには、毎日の利益の再計算と、もし以前に日次損失限度に達していた場合の回復状態への切り替えを追加しました。

//+------------------------------------------------------------------+
//| Update daily base levels                                         |
//+------------------------------------------------------------------+
void CVirtualRiskManager::UpdateBaseLevels() {
// Update balance, funds and base daily level
   m_baseDailyBalance = m_balance;
   m_baseDailyEquity = m_equity;
   m_baseDailyLevel = MathMax(m_baseDailyBalance, m_baseDailyEquity);

   m_dailyProfit = m_equity - m_baseDailyLevel;

   ...

// If the daily loss level was reached earlier, then
   if(m_state == RM_STATE_DAILY_LOSS) {
      // Switch to the state of restoring the sizes of open positions
      m_state = RM_STATE_RESTORE;

      // Remember restoration start time
      m_startRestoreTime = TimeCurrent();
   }
}


ポジションサイズの復元

また、これまでこのタスクを1つのメソッドで実行していたものを、複数のメソッドに分割しました。回復が必要かどうかを確認するメソッドは、最上位で呼び出されます。

//+------------------------------------------------------------------+
//| Check the need for restoring the size of open positions          |
//+------------------------------------------------------------------+
void CVirtualRiskManager::CheckRestore() {
// If we need to restore the state to normal, then
   if(m_state == RM_STATE_RESTORE) {
      // Check the possibility of restoring the daily loss multiplier to normal 
      bool dailyRes = CheckDailyRestore();

      // Check the possibility of restoring the overall loss multiplier to normal
      bool overallRes = CheckOverallRestore();

      // If at least one of them has recovered,
      if(dailyRes || overallRes) {
         
...

         // Set the value of the used part of the overall balance
         SetDepoPart();

         // Notify the recipient about changes
         CVirtualReceiver::Instance().Changed();

         // If both multipliers are restored to normal,
         if(dailyRes && overallRes) {
            // Set normal state
            m_state = RM_STATE_OK;
         }
      }
   }
}

毎日の乗数と全体の乗数を復元する必要があるかどうかを確認する補助メソッドは現在同じように動作していますが、将来的にはその動作が変更される可能性があります。現在のところ、仮想利益の現在の値が希望する水準を下回っているかどうかを確認しています。もしそうであれば、現在の価格で実際のポジションを再開することが私たちにとって有益なので、そのサイズを復元する必要があります。

//+------------------------------------------------------------------+
//| Check if the daily multiplier needs to be restored               |
//+------------------------------------------------------------------+
bool CVirtualRiskManager::CheckDailyRestore() {
// If the current virtual profit is less than the one desired for recovery,
   if(m_virtualProfit <= RestoreVirtualProfit()) {
      // Restore the daily loss multiplier
      m_dailyDepoPart = 1.0;
      return true;
   }

   return false;
}

//+------------------------------------------------------------------+
//| Check if the overall multiplier needs to be restored             |
//+------------------------------------------------------------------+
bool CVirtualRiskManager::CheckOverallRestore() {
// If the current virtual profit is less than the one desired for recovery,
   if(m_virtualProfit <= RestoreVirtualProfit()) {
      // Restore the overall loss multiplier
      m_overallDepoPart = 1.0;
      return true;
   }

   return false;
}

RestoreVirtualProfit()メソッドは、仮想利益の希望レベルを計算します。2つのパラメータを使用した単純な線形補間を採用しました。時間が経過するほど、実際のポジションを再開することに同意する収益性のレベルは低くなります。

//+------------------------------------------------------------------+
//| Determine the profit of virtual positions for recovery           |
//+------------------------------------------------------------------+
double CVirtualRiskManager::RestoreVirtualProfit() {
// If the maximum recovery time is not specified,
   if(m_maxRestoreTime == 0) {
      // Return the current value of the virtual profit
      return m_virtualProfit;
   }

// Find the elapsed time since the start of recovery in minutes
   double t = (TimeCurrent() - m_startRestoreTime) / 60.0;

// Return the calculated value of the desired virtual profit
// depending on the time elapsed since the start of recovery
   return m_lastVirtualProfit * m_lastVirtualProfitFactor * (1 - t / m_maxRestoreTime);
}

現在のフォルダのVirtualRiskManager.mqhファイルに変更を保存します。


検証

まず、日次および合計制限に達したときにポジションを完全にクローズすることを指定するパラメータを使用してEAを起動します。結果は図3と同じになるはずです。

図6: 元のリスクマネージャーと同じ設定、scale_ = 1.0でのEA結果

ドローダウンの結果は完全に一致していますが、利益と正規化された平均年間利益(OnTesterの結果)については、結果が予想よりもわずかに多くなっています。これは励みにはなります。以前実装したものが壊れていないということです。

さて、0.5の値で日次および全体の制限の一部に達した場合、ポジションを適応的にクローズするように設定したときに何が起こるかを見てみましょう。

図7: scale_ = 1.0、rmCloseDailyPart_ = 0.5、rmCloseOverallPart_ = 0.5でのEA結果

ドローダウンはわずかに縮小し、その結果、標準化された平均年間利益がわずかに増加しました。それでは、最適な価格でポジションサイズの復元を試してみましょう。パラメータで、ドローダウン中の最適なエントリーまでの待機時間を1440分(1日)に設定し、初期の最適ドローダウン乗数を1.5に設定します。これらは直感的に決めた値です。

図8: scale_ = 1.0、rmCloseDailyPart_ = 0.5、rmCloseOverallPart_ = 0.5、rmMaxRestoreTime_ = 1440、rmLastVirtualProfitFactor_ = 1.5でのEA結果

標準化された平均年間利益の結果は、再び数パーセントポイント改善しました。このメカニズムを実装するための努力が正当であることが証明されたようです。

さて、前回の起動以降、リスクマネージャーのパラメータを変更せずに、オープンポジションのサイズを3倍に増やしてみましょう。これにより、リスクマネージャーがより頻繁にトリガーされることになります。タスクにどのように対処するか見てみましょう。

図9: scale_ = 3.0、rmCloseDailyPart_ = 0.5、rmCloseOverallPart_ = 0.5、rmMaxRestoreTime_ = 1440、rmLastVirtualProfitFactor_ = 1.5でのEA結果

元のリスクマネージャーを使用した同様の開始と比較すると、ここでもすべての指標で結果が改善されていることがわかります。前回の実行時と比較して、利益は約2倍に増加しましたが、ドローダウンは3倍に増加し、そのため標準化された平均年間利益の値は減少しました。しかし、取引日ごとにドローダウンはリスクマネージャーのパラメータで設定された値を超えることはありませんでした。

さらに、ポジションサイズをたとえば10倍に増加させた場合にどうなるのかを確認するのも興味深いです。

図10: scale_ = 3.0、rmCloseDailyPart_ = 0.5、rmCloseOverallPart_ = 0.5、rmMaxRestoreTime_ = 1440、rmLastVirtualProfitFactor_ = 1.5でのEA結果

ご覧の通り、このようなポジションサイズでは、リスクマネージャーは指定された最大総損失を維持できなくなり、取引が停止されました。つまり、リスクマネージャーは、取引戦略の癖に関係なく指定された最大ドローダウンを防ぐための万能ツールではありません。それは、取引戦略に必ず存在するべき資金管理ルールを補完するものに過ぎません。

最後に、遺伝的最適化を使用してリスクマネージャーの最適なパラメータを選定してみます。残念ながら、各パスには2年間のインターバルで約3分かかるため、最適化が完了するまでかなりの時間を待つ必要があります。最適化基準として、正規化された平均年間利益を計算するユーザー定義基準を選びます。しばらくすると、最適化結果を示すテーブルの上位は次のようになります。

図11:リスクマネージャーを使用したEA最適化の結果

ご覧の通り、適切なリスクマネージャーのパラメーターを選択することで、ドローダウンの増加から取引を保護できるだけでなく、取引結果も改善されました。異なるパス間での差は、追加の利益が20%に達しました。すでに見つかった最良のパラメータセットでさえ、直感的に選んだ値で図8に示された結果にはわずかに劣ります。さらに最適化をおこなうことで、この結果はおそらくもう少し改善できるでしょうが、その必要性は特に感じません。


結論

簡単にまとめます。成功する取引システムの重要なコンポーネントであるリスクマネージャーを改良し、より柔軟で、特定のニーズに合わせてカスタマイズできるようにしました。これにより、許容可能なドローダウンに関する確立された制限をより正確に遵守できるメカニズムを実現しました。

信頼性を高めるよう努めていますが、その機能は賢明に使用する必要があります。これは資本を保護するための極端な手段であることを認識する価値があります。したがって、リスクマネージャーが取引にまったく干渉しないか、非常に稀にしか干渉しないような取引パラメータを選択することが重要です。テスト結果からもわかるように、推奨されるポジションサイズを大幅に超えると、エクイティの変動が急激になり、リスクマネージャーでも指定されたドローダウンレベルを超えないように資金を守ることが間に合わない場合があります。

このリスクマネージャーの更新作業を進める中で、それをさらに改善するための新しいアイデアが浮かびました。しかし、あまりにも複雑にしすぎるのは良くないため、しかし、あまりにも複雑にしすぎるのは良くないため、しばらくリスクマネージャーに関する作業を脇に置き、次の部分ではより緊急なEA開発に戻ろうと思います。

ご精読ありがとうございました。またすぐにお会いしましょう。

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

添付されたファイル |
ニューラルネットワークが簡単に(第97回):MSFformerによるモデルの訓練 ニューラルネットワークが簡単に(第97回):MSFformerによるモデルの訓練
さまざまなモデルアーキテクチャの設計を検討する際、モデルの訓練プロセスには十分な注意が払われないことがよくあります。この記事では、そのギャップを埋めることを目指します。
初心者からプロまでMQL5をマスターする(第3回):複雑なデータ型とインクルードファイル 初心者からプロまでMQL5をマスターする(第3回):複雑なデータ型とインクルードファイル
これはMQL5プログラミングの主な側面を説明する連載の第3回目です。この記事では、前回の記事で触れなかった複雑なデータ型について説明します。具体的には、構造体、共用体、クラス、および「関数」データ型を扱います。また、#includeプリプロセッサディレクティブを使ってプログラムにモジュール性を加える方法についても解説します。
取引におけるニューラルネットワーク:時系列の区分線形表現 取引におけるニューラルネットワーク:時系列の区分線形表現
本記事は、これまでの公開記事とはやや異なる内容となっています。本記事では、時系列データの代替的な表現について解説します。時系列の区分的線形表現とは、小さな区間ごとに線形関数を用いて時系列データを近似する手法です。
季節性を利用した外国為替スプレッド取引 季節性を利用した外国為替スプレッド取引
この記事では、外国為替取引におけるスプレッド取引時に季節性要因を利用したレポートデータの生成および提供の可能性について検討します。