English Русский 中文 Español Português
preview
リスク管理(第3回):リスク管理のメインクラスの構築

リスク管理(第3回):リスク管理のメインクラスの構築

MetaTrader 5 |
9 1
Niquel Mendoza
Niquel Mendoza


はじめに

リスク管理システムの開発についての連載を継続します。前回の記事では、第1回で学んだ概念を応用してインターフェースを作成しました。今回、第3回では、リスク管理クラスのコア構造を完成させることに焦点を当てます。

本記事では、損益に値を割り当てることが可能なクラスを作成します。これにより、利益計算および追跡の基礎が構築されます。これは、堅牢で機能的なリスク管理システムを構築するための重要なステップです。

リスク管理の設計と計画

以下のフレームワークは、システム内でのリスク管理を設計および計画するための体系的な手順を示しています。

  1. 定義、構造体、列挙型
    最初のステップは、必要な構造体と列挙型を定義することです。これらは、累積損失や利益などの重要情報を格納し、システム内でのデータ処理を円滑にするために不可欠です。

  2. CRiskManagementクラスの作成
    次に、リスク管理を担当するメインクラスCRiskManagementを開発します。この中心クラスは、リスク管理に関するすべての計算とプロセスを統合し、整理された効率的な実装を可能にします。

  3. 基本関数:値の割り当てと取得
    続いて、値を割り当てたり取得したりする関数を実装します。これにより、損失や利益を更新および参照できるようになります。この段階で、クラスのコンストラクタとデストラクタも定義し、メモリやデータの初期化を適切に管理します。

  4. イベント駆動型関数
    最後に、新しい日や週の開始などの重要なタイミングでトリガーされる関数を開発します。これらの関数は、利益の再計算、リスクの調整、特定期間における累積変数の再初期化に役立ち、時間経過に伴う適切なパフォーマンス追跡を可能にします。

この計画を基に、ゼロからの構築を開始します。


定数、列挙型、構造体の定義

コードを実装する前に、すべてのリスク管理ロジックを別ファイルRisk_Management.mqh(以前作成したベースファイル)に整理します。これにより、コードをより構造化され、モジュール化された状態に保つことができます。

リスク管理フレームワークに従い、最初のステップとして、必要な構造体、列挙型、定数を定義します。これらの要素は、特に日次、週次、または1取引あたりの最大損失などの値を計算および管理する関数で不可欠です。

定数の定義(#define)

リスク管理を容易にし、コードの可読性を向上させるため、いくつかの重要な定数を定義します。

1. NOT_MAGIC_NUMBER

この定数はクラスのコンストラクタで初期化値として使用されます。特定のマジックナンバーを使用しないことを示す目的があります。つまり、リスク管理を特定の識別子に制限せず、すべての取引に適用できるようにします。これは、実取引環境で口座全体にリスク管理を適用する場合に便利です。

#define NOT_MAGIC_NUMBER 0 //Not Magic Number 

2. 取引終了のフラグ

利益を出している取引、損失を出している取引、またはそれらの両方を決済するために、複数の関数を作成する代わりにフラグを使用します。これにより、コードが簡潔になり、再利用性が向上します。

#define FLAG_CLOSE_ALL_PROFIT 2 //Flag indicating to close only operations with profit
#define FLAG_CLOSE_ALL_LOSS   4 //Flag indicating to close only operations without profit

列挙型

1. 計算方法:固定金額または割合

戦略内で最大損失や利益をどのように計算するかを決定する列挙型が必要です。主に以下の2つのアプローチがあります。

  • 固定(money):ユーザーが最大損失または利益を具体的な金額で設定します。この値は口座残高に関係なく一定です。

    • 例:日次最大損失を$1,000に設定した場合、システム稼働中はこの値は変わりません。
  • 動的(percentage):固定値の代わりに、ユーザーが口座残高やエクイティなどの基準パラメータに対して適用される割合を選択します。

    • 例:残高の2%を制限として設定した場合、最大損失は口座残高に応じて変動します。残高が増加すれば損失制限も増加し、残高が減少すれば制限も減少します。

この計算方法は、以下の列挙型で表現されます。

enum ENUM_RISK_CALCULATION_MODE //enumeration to define the types of calculation of the value of maximum profits and losses
 {
  money, //Money
  percentage //Percentage %
 };

2. 割合適用:リスク計算の基準パラメータ

計算方法が割合ベースの場合、この割合を適用する口座の値を定義する必要があります。主に4つの重要なオプションがあります。

  • 残高:口座残高の合計に適用されます。
  • 純利益(ganancianeta):口座開設以降の累積利益に基づきます。
  • 余剰証拠金:新規取引のために使用可能な資本に基づき計算されます。
  • エクイティ:未決済ポジションの損益を反映した調整後の残高を表します。
この計算方法は、以下の列挙型で表現されます。
enum ENUM_APPLIED_PERCENTAGES //enumeration to define the value to which the percentages will be applied
 {
  Balance, //Balance
  ganancianeta,//Net profit
  free_margin, //Free margin
  equity //Equity
 };

3. リスク管理の種類:個人口座またはプロップファーム(FTMO)

前の章で述べたように、このシステムは個人口座およびプロップファーム、特にFTMOに適用可能です。FTMOは日次最大損失の計算に対してより動的なアプローチを採用しています。

後ほど構造体の解析時に、FTMOがどのようにこの計算を処理するかを詳細に説明します。現時点では、ユーザーがリスク管理をおこなう口座タイプを選択できるように、列挙型を作成します。

enum ENUM_MODE_RISK_MANAGEMENT //enumeration to define the type of risk management
 {
  propfirm_ftmo, //Prop Firm FTMO
  personal_account // Personal Account
 };

4. ロットタイプの選択

すべてのトレーダーが動的ロットで取引することを好むわけではありません。ユーザーがロットタイプを選択できるようにします。この選択はリスク管理クラスのコアロジックには影響しませんが、エキスパートアドバイザー(EA)を開発する上では重要です。ユーザーが固定ロットまたは動的ロットのいずれでも取引できるようにする必要があります。

以下の列挙型を作成します。

enum ENUM_LOTE_TYPE //lot type
  {
   Dinamico,//Dynamic
   Fijo//Fixed
  };

取引を開始する際、ロットの選択は次のように条件付けできます。

 trade.Sell( (Lote_Type == Dinamico ? risk.GetLote(ORDER_TYPE_SELL,GET_LOT_BY_ONLY_RISK_PER_OPERATION) : lote), _Symbol,tick.bid,sl,tp,"EA Sell");

このコードでは、ユーザーが動的ロット(Dinamico)を選択した場合、GetLote関数が1取引あたりのリスクに基づいてロットサイズを計算します。ユーザーが固定ロット(Fijo)を選択した場合、EAはlote変数に割り当てられたデフォルト値を使用します。

後ほど、動的ロットを正確に取得するためのGetLote関数を作成します。

5. 動的ロットの計算方法

ユーザーが動的ロットを選択した場合、ロットサイズの計算方法を定義する必要があります。ここでは、計算が1取引あたりのリスクのみに基づくか、ストップロスに応じて調整するかを指定する列挙型を作成します。

  • 1取引あたりのリスクのみで計算
    ロットサイズは1取引あたりのリスク割合のみで決定されます。この場合、ストップロスの具体的な値を指定する必要はありません。

  • リスクとストップロスに基づく計算
    ロットサイズは1取引あたりのリスクに基づき、ストップロスに応じて調整されます。これは重要です。なぜなら、ストップロスを考慮せずに固定リスク割合を設定すると、適切でないロットサイズが計算される可能性があるからです。

    例:
    $1,000の口座で1%リスク(最大リスク$10)を許容するとします。戦略ではリスクリワードレシオ1:2、EUR/USDで100ポイントのストップロス(5桁ブローカー)を使用する場合、単純計算ではロット0.02が得られるかもしれません。

    • 取引がストップロスにヒットした場合、実際の損失は$2となり、$10ではありません。
    • 取引がテイクプロフィットにヒットした場合、実際の利益は$4となり、$20ではありません。

    この問題を解決するため、ロットサイズを調整し、ストップロスに基づく最大損失が口座の1%に正確に一致するようにします。これにより、リスクリワードレシオ1:2でテイクプロフィットに達した場合、利益が2%となります。

    以前の記事で紹介したGetIdealLot関数は、ロットを自動調整します。

void GetIdealLot(double& nlot, double glot, double max_risk_per_operation, double& new_risk_per_operation, long StopLoss)

1取引あたりのリスクが指定の範囲内に収まるようにします。

これら2つの計算方法を表すために、次の列挙型を定義します。
enum ENUM_GET_LOT
 {
  GET_LOT_BY_ONLY_RISK_PER_OPERATION, //Obtain the lot for the risk per operation
  GET_LOT_BY_STOPLOSS_AND_RISK_PER_OPERATION //Obtain and adjust the lot through the risk per operation and stop loss respectively.
 };

構造体

構造体はリスク管理システムで重要な役割を果たします。損失や利益は固定金額(直接)または動的(割合)で計算されます。割合方式の場合、口座内の基準値に適用されます。

損失や利益を定義するために必要な情報をすべて含む構造体を作成します。

  • value:損失または利益の金額
  • assignment_percentage:割り当てられた割合(動的計算時)
  • mode_calculation_risk:1取引あたりのリスク計算方法(直接または動的)
  • percentage_applied_to:割合適用対象の口座指標を指定する列挙型

これはコードでは次のように表されます。

struct Loss_Profit
 {
  double             value; //value
  double             assigned_percentage; //percentage to apply
  ENUM_RISK_CALCULATION_MODE mode_calculation_risk; //risk calculation method
  ENUM_APPLIED_PERCENTAGES percentage_applied_to; //percentage applied to
 };

この構造体により、以下の損益オプションを識別できます。

名前  説明 リセット
最大日次損失(MDL) 1日あたり許容される最大損失 毎日0にリセット
最大損失(ML) 口座が存続期間中に到達できる最大総損失 リセットなし
1取引あたり最大損失(GMLPO) ユーザー定義の1取引あたりリスクに類似、適切なロットサイズ算出に使用 取引終了時または新しい日開始時に再計算
最大週次損失(MWL) 1週間で許容される最大損失(プロップファームでは必要ないため任意) 週次リセット
ネット1取引あたり最大損失(NMLPO) 1取引あたりのリスクとストップロスに基づくロット計算関数呼び出し時に生成 リスクとストップロスに基づくロット取得関数が呼び出されるたびに再計算
最大日次利益 1日あたりボットまたは口座が達成できる最大利益  毎日リセット

FTMOにおける最大日次損失の特殊性

個人口座とFTMOのようなプロップファームにリスク管理システムを適用する場合では、いくつかの重要な違いがあります。FTMOでは、最大日次損失が非常に動的です。日中に利益が出た場合、この値は増加することがあります。

例:FTMOチャレンジで初期口座残高$200,000の場合、最大日次損失は$10,000です。決済済みの取引で$8,000の損失を出した場合、その日の口座残高は$2,000以上減少できません。未決済ポジションの含み損も-$2,000を超えてはいけません。手数料やスワップも含まれます。

逆に、1日で$5,000の利益を出した場合、損失許容額は$15,000まで増加します。しかし、それ以上は許可されません。最大日次損失には、未決済取引も含まれることに注意してください。たとえば、決済済みの取引で$6,000の損失があり、その後新規取引が一時的に-$5,700の含み損になったとしても、最終的に利益が出た場合でも、一時的に-$11,700となり、許容損失$10,000を超えてしまいます。

FTMOの最大日次損失

FTMOのガイドラインによれば、最大日次損失は日中の利益に応じて変動します。

例:1日の開始時に許容最大損失$10,000で、同日に利益が発生した場合、その利益は初期$10,000に加算され、許容損失マージンが増加します。したがって、利益が積み重なると、リスク可能額もそれに応じて調整されます。


リスク管理の主要変数の宣言

このセクションでは、取引のリスク管理を担当するCRiskManagementクラスを定義します。

//+------------------------------------------------------------------+
//|                  Class CRisk Management                          |
//+------------------------------------------------------------------+
class CRiskManagemet final

ライブラリをインクルードする

クラスを定義する前に、ポジション決済などの取引管理をおこなうため、CTrade.mqhライブラリをインクルードします。

#include <Trade/Trade.mqh>

1. CTradeへのポインタ

最初の変数として、CTradeクラスへのポインタを作成します。これにより、保有中のポジションとやり取りできるようになります。

private:
  //--- CTrade class pointer to be able to work with open positions
  CTrade             *trade;

2. 主要変数

以下の3つの主要変数を定義します。

  • account_profit (double):1972.01.01(datetime型での最小日付)以降の口座の純利益を保持します。
  • StopLoss (long):ロットサイズの計算に使用されるストップロスの水準をポイント単位で保持します。
  • lote (double):最後に計算されたロットサイズを保持する内部変数です。

以下がコードです。

  //-- Main variables
  double             account_profit;
  long               StopLoss;
  double             lote;

3. 共通変数

  1. magic_number (ulong):特定の取引グループに関連付けられたリスクを管理するために使用されるマジックナンバーを保持します。この値は0に設定することも可能で、その場合は個人口座でも使用できます。
  2. mode_risk_management (ENUM_MODE_RISK_MANAGEMENT):適用されるリスク管理のタイプを定義します。通常の口座用か、プロップファーム(例:FTMO)用かを指定します。

以下がコードです。

  //--- General variables
  ENUM_MODE_RISK_MANAGEMENT mode_risk_managemet;
  ulong              magic_number;

4. プロップファーム口座用の専用変数(FTMO)

プロップファーム口座では、リスク管理ルールは運用中の残高ではなく、初期残高に対して適用されます。たとえば、最大日次損失や最大総損失は、通常、初期残高の一定割合(多くの場合10%)として計算されます。

この特性に対応するため、初期残高を保持する変数を追加します。この値はユーザーが手動で設定する必要があります。

  //--- Variables to work with anchoring tests
  double             account_balance_propfirm;

5. 次回取引における想定最大損失用変数(NMLPO)

この変数は、次の取引で想定される最大損失を保持します。必須ではありませんが、ポジションがストップロスに到達した場合に、ロボットがどの程度の損失を被るかを事前に把握するために有用です。

この値はGetLote()関数が呼び出された場合にのみ計算されます。

//--- Variables to store the values ​​of the maximum losses
  double             nmlpo;

6. 損益管理用変数

前述のLoss_Profit構造体は、損失や利益の金額、計算方法、および割合の適用方法を格納するために使用されます。

以下の5つの主要変数を定義します。

  //--- variables that store percentages and enumeration, which will be used for the subsequent calculation of losses
  Loss_Profit        mdl, mwl, ml, gmlpo, mdp;

ここで

  • mdl:最大日次損失
  • mwl:最大週次損失
  • ml:最大総損失
  • gmlpo:1取引あたりの最大損失
  • mdp:最大日次利益

7. 利益管理用変数(日次、週次、合計)

ロボット/EAのパフォーマンスを監視するため、利益データを保持し、損失制限に達した場合や安全措置が発動された際にアラートを出せるようにする変数を追加します。

必要なのは以下のとおりです。

  • 利益計算の基準となる時刻
  • 利益を保存するための変数

以下がコードです。

  //--- Variables to store the profit and the time from which they will be obtained
  double             daily_profit, weekly_profit, gross_profit;
  datetime           last_weekly_time, last_day_time, init_time;

ここで

  • daily_profit:当日に得られた利益
  • weekly_profit:当週に得られた利益
  • gross_profit:リスク管理開始以降の累計利益
  • last_weekly_time:週次利益計算のために最後に記録された日時
  • last_day_time:日次利益計算のために最後に記録された日時
  • init_time:リスク管理の開始日時



コンストラクタ、デストラクタ、初期化メソッドの作成

主要な変数を定義したので、次にCRiskManagementクラスの主要な関数を実装します。 

コンストラクタ 

コンストラクタは、マジックナンバー、リスク管理モード、そして必要に応じてプロップファーム口座残高などの主要な変数を初期化する役割を担います。

コンストラクタの宣言は以下のとおりです。

CRiskManagemet(ulong magic_number_ = NOT_MAGIC_NUMBER,ENUM_MODE_RISK_MANAGEMENT mode_risk_management_ = personal_account, double account_propfirm_balance=0);

コンストラクタの実装では、各変数に値を代入し、CTradeオブジェクトを初期化し、1972年1月1日からの口座の純利益を計算します。また、利益を追跡するための適切なタイムスタンプを設定します。

CRiskManagemet::CRiskManagemet(ulong magic_number_ =  NOT_MAGIC_NUMBER,ENUM_MODE_RISK_MANAGEMENT mode_risk_management_ = personal_account, double account_propfirm_balance =0)
 {
  if(magic_number_ == NOT_MAGIC_NUMBER)
   {
    Print("| Warning | No magic number has been chosen, taking into account all the magic numbers and the user's trades");
   }
//---
  this.account_balance_propfirm = account_propfirm_balance ;
  trade = new CTrade();
  this.account_profit = GetNetProfitSince(true,this.magic_number,D'1972.01.01 00:00');
  this.magic_number = magic_number_;
  this.mode_risk_managemet = mode_risk_management_;
//---
  this.last_day_time = iTime(_Symbol,PERIOD_D1,0);
  this.last_weekly_time = iTime(_Symbol,PERIOD_W1,0);
  this.init_time =magic_number_ != NOT_MAGIC_NUMBER ? TimeCurrent() : D'1972.01.01 00:00';
 }

注意:ユーザーがマジックナンバーを指定しない場合、システムはユーザーが開いたすべての取引を考慮する旨の警告が表示されます。

デストラクタ

クラスのデストラクタは、CRiskManagementインスタンスが削除される際に、CTradeオブジェクトに割り当てられたメモリを解放する役割を担います。

CRiskManagemet::~CRiskManagemet()
 {
  delete trade;
 }

初期化メソッド 

リスク管理の設定および更新を簡単にするために、以下を可能にする特別な関数を作成します。

  • ストップロスを設定する
  • 損益パラメータを設定する

1. 損失構造値を設定するための関数

損益計算に必要な主要な変数に値を割り当てることを可能にするメソッドを定義します。

  //--- Functions to assign values ​​to variables for subsequent calculation of losses
  void               SetPorcentages(double percentage_or_money_mdl, double percentage_or_money_mwl,double percentage_or_money_gmlpo, double percentage_or_money_ml, double percentage_or_money_mdp_);    

  void               SetEnums(ENUM_RISK_CALCULATION_MODE mode_mdl_, ENUM_RISK_CALCULATION_MODE mode_mwl_, ENUM_RISK_CALCULATION_MODE mode_gmlpo_, ENUM_RISK_CALCULATION_MODE mode_ml_,
                             ENUM_RISK_CALCULATION_MODE mode_mdp_);  

  void               SetApplieds(ENUM_APPLIED_PERCENTAGES applied_mdl_,  ENUM_APPLIED_PERCENTAGES  applied_mwl_, ENUM_APPLIED_PERCENTAGES applied_gmlpo_,  ENUM_APPLIED_PERCENTAGES  applied_ml_,
                                ENUM_APPLIED_PERCENTAGES applied_mdp_); 

 2. 損失構造への割合の割り当て

以下のメソッドは、対応する構造体に対して割合または金額の値を割り当てます。

void CRiskManagemet::SetPorcentages(double percentage_or_money_mdl,double percentage_or_money_mwl,double percentage_or_money_gmlpo,double percentage_or_money_ml,double percentage_or_money_mdp_)
 {
  this.gmlpo.assigned_percentage = percentage_or_money_gmlpo;
  this.mdl.assigned_percentage = percentage_or_money_mdl;
  this.ml.assigned_percentage = percentage_or_money_ml;
  this.mdp.assigned_percentage = percentage_or_money_mdp_;
  this.mwl.assigned_percentage = percentage_or_money_mwl;
 }

3. リスク計算モードの初期化

このメソッドは、各構造体の計算モードを設定します。ユーザーが金額ベースの計算モードを選択した場合、対応する値が直接割り当てられます。

void CRiskManagemet::SetEnums(ENUM_RISK_CALCULATION_MODE mode_mdl_,ENUM_RISK_CALCULATION_MODE mode_mwl_,ENUM_RISK_CALCULATION_MODE mode_gmlpo_,ENUM_RISK_CALCULATION_MODE mode_ml_,ENUM_RISK_CALCULATION_MODE mode_mdp_)
 {
  this.gmlpo.mode_calculation_risk = mode_gmlpo_;
  this.mdl.mode_calculation_risk  = mode_mdl_;
  this.mdp.mode_calculation_risk  = mode_mdp_;
  this.ml.mode_calculation_risk  = mode_ml_;
  this.mwl.mode_calculation_risk  = mode_mwl_;
//-- If the money mode has been chosen, assign the variable that stores the money or percentage to the corresponding variables.
  this.gmlpo.value = this.gmlpo.mode_calculation_risk  == money ? this.gmlpo.value : 0;
  this.mdp.value  = this.mdp.mode_calculation_risk  == money ? this.mdp.value : 0;
  this.mdl.value  = this.mdl.mode_calculation_risk  == money ? this.mdl.value : 0;
  this.ml.value  = this.ml.mode_calculation_risk  == money ? this.ml.value : 0;
  this.mwl.value  = this.mwl.mode_calculation_risk  == money ? this.mwl.value : 0;
 }

4. 口座に適用されるパラメータの割り当て

このメソッドは、設定された割合が口座にどのように適用されるかを決定します。

void CRiskManagemet::SetApplieds(ENUM_APPLIED_PERCENTAGES applied_mdl_,ENUM_APPLIED_PERCENTAGES applied_mwl_,ENUM_APPLIED_PERCENTAGES applied_gmlpo_,ENUM_APPLIED_PERCENTAGES applied_ml_,ENUM_APPLIED_PERCENTAGES applied_mdp_)
 {
  this.gmlpo.percentage_applied_to = applied_gmlpo_;
  this.mdl.percentage_applied_to  = applied_mdl_;
  this.mdp.percentage_applied_to  = applied_mdp_;
  this.mwl.percentage_applied_to  = applied_mwl_;
  this.ml.percentage_applied_to  = applied_ml_;
 }

5. ストップロス設定関数

ストップロスを設定するために、StopLoss変数に値を割り当てる簡単な2つの方法を使用します。

  • 方法1:ユーザーが直接ストップロスポイント数を設定する
  • 方法2:ユーザーがストップロスとエントリーポイントの距離を定義し、その後ポイントに変換する(記事の前半でプログラムした関数を使用) 

  //--- Function to set the "StopLoss" variable, in points or distance
  inline void        SetStopLoss(double dist_open_sl) {     this.StopLoss = DistanceToPoint(dist_open_sl);  }
  inline void        SetStopLoss(long _sl_point_)     {     this.StopLoss = _sl_point_;                     }



損益に値を割り当てるメソッド

このセクションでは、リスク管理システムで損益に値を割り当てるために必要な方法を開発します。

1. 値を割り当てるための一般関数

柔軟で適応的なリスク管理を実現するために、口座のさまざまな指標に適用される割合に基づいて適切な値を計算する一般関数を作成します。

関数定義

使用する主要な関数は以下の通りです。

  //--- General function to assign values ​​to loss variables
  double             GetValorWithApplied(const ENUM_APPLIED_PERCENTAGES applied,const double percentage_);

この関数は2つの主要なパラメータを取ります。

  1. applied:割合をどの口座指標に適用するかを定義します(残高、余剰証拠金、エクイティなど)。
  2. percentage_:選択した指標に適用するパーセント
これらの値に基づき、関数は対応する金額を計算します。
double CRiskManagemet::GetValorWithApplied(const ENUM_APPLIED_PERCENTAGES applied,const double percentage_)
 {
  if(this.mode_risk_managemet == propfirm_ftmo && percentage_ != this.mdp.assigned_percentage && percentage_ != this.gmlpo.assigned_percentage)
    return this.account_balance_propfirm * (percentage_/100.0);
  switch(applied)
   {
    case Balance:
      return NormalizeDouble((percentage_/100.0) * AccountInfoDouble(ACCOUNT_BALANCE),2);
    case ganancianeta:
     {
      if(this.account_profit <= 0)
       {
        PrintFormat("The total profit of the account which is %+.2f is invalid or negative",this.account_profit);
        return 0;
       }
      else
        return NormalizeDouble((percentage_/100.0) * this.account_profit,2);
     }
    case free_margin:
     {
      if(AccountInfoDouble(ACCOUNT_MARGIN_FREE) <= 0)
       {
        PrintFormat("free margin of %+.2f is invalid",AccountInfoDouble(ACCOUNT_MARGIN_FREE));
        return 0;
       }
      else
        return NormalizeDouble((percentage_/100.0) * AccountInfoDouble(ACCOUNT_MARGIN_FREE),2);
     }
    case equity:
      return NormalizeDouble((percentage_/100.0) * AccountInfoDouble(ACCOUNT_EQUITY),2);
    default:
      Print("Critical Error | It was not found that: ", EnumToString(applied), " be part of the allowed enumeration");
   }
  return 0;
 }
コードの説明
    • プロップファーム口座の管理
      • 口座がプロップファーム(例:FTMO)に属しており、適用される割合が「1日あたりの最大損失(mdp.assigned_percentage)」や「1取引あたりの最大損失(gmlpo.assigned_percentage)」に関連しない場合、計算はプロップファームの口座残高に基づいておこなわれます。
    • さまざまな口座指標に基づく計算
      • Balance:口座残高全体に対して割合を適用します。
      • ganancianeta(純利益):口座の純利益に対して割合を適用します。利益が負または0の場合、エラーメッセージを表示します。
      • free_margin(余剰証拠金):余剰証拠金に対して割合を適用します。余剰証拠金が負の場合、関数は0を返します。
      • equity(エクイティ):口座の現在のエクイティに対して割合を適用します。
    • エラー処理
      • 無効な指標がapplied関数に渡された場合、関数はエラーメッセージを出力し、0を返します。

    2. 損益変数を設定する関数の作成

    損益を効率的に管理するために、構造化された方法で値を割り当てる一連の関数を作成します。メインの損失パラメータを設定するための5つの関数と、ストップロス基づいて理想的なロットサイズを計算する1つの関数の、合計で6つの関数を定義します。

    1. 値割り当て関数

    損益の値を割り当てる方法は、次のロジックに基づいています。

    1. 計算モードがmoneyの場合、以前に割り当てられた値を保持します。
    2. 計算モードがmoneyでない場合、GetValorWithApplied関数を使用して、割り当てられた割合と対応する適用パラメータに基づき値を決定します。
    3. 割り当てられた割合が0の場合、その損失は使用されていないとみなし、値0を割り当てます。

    2. 割り当て関数

    以下の関数は、上記のロジックを各損失タイプに対して実装します。

      //--- Functions to assign values ​​to internal variables
      void               SetMDL()   {this.mdl.value = this.mdl.mode_calculation_risk == money ? this.mdl.value           : (this.mdl.assigned_percentage > 0 ? GetValorWithApplied(this.mdl.percentage_applied_to,mdl.assigned_percentage) : 0);          }
      void               SetMWL()   {this.mwl.value  = this.mwl.mode_calculation_risk  == money ? this.mwl.value         : (this.mwl.assigned_percentage > 0 ? GetValorWithApplied(this.mwl.percentage_applied_to,mwl.assigned_percentage) : 0);          }
      void               SetML()    {this.ml.value  = this.ml.mode_calculation_risk == money ? this.ml.value             : (this.ml.assigned_percentage > 0 ? GetValorWithApplied(this.ml.percentage_applied_to,ml.assigned_percentage): 0);              }
      void               SetGMLPO() {this.gmlpo.value  = this.gmlpo.mode_calculation_risk  == money ? this.gmlpo.value   : (this.gmlpo.assigned_percentage  > 0 ? GetValorWithApplied(this.gmlpo.percentage_applied_to,gmlpo.assigned_percentage) : 0);  }
      void               SetMDP()   {this.mdp.value  = this.mdp.mode_calculation_risk == money ? this.mdp.value          : (this.mdp.assigned_percentage > 0 ? GetValorWithApplied(this.mdp.percentage_applied_to,mdp.assigned_percentage) : 0);          }
      void               SetNMPLO(double& TLB_new, double tlb) { GetIdealLot(TLB_new,tlb,this.gmlpo.value,this.nmlpo,this.StopLoss); }


    1取引あたりのリスクに基づくロットサイズとストップロスの計算

    このセクションでは、各取引が許容するリスクレベルに沿うように、ロットサイズとストップロスを正確に計算するための2つの重要な関数を実装します。

    1. ロットサイズを計算する関数

    ロットサイズを計算するために、最初の記事で開発した関数を使用し、ロットがあらかじめ定義したリスクルールに従うようにします。

    適切なロットサイズを取得する関数は以下の通りです。

      //--- Get the lot
      double             GetLote(const ENUM_ORDER_TYPE order_type, const ENUM_GET_LOT mode_get_lot);
    パラメータは次の通りです。
    1. order_type:注文タイプを指定します(Buy、Sell、Stop、Limit、Stop-Limitなど)。
    2. mode_get_lot:ロット計算方法を指定します(前述の列挙型セクションで説明)。

    関数の目的

    この関数は、以下の2つの方法で理想的なロットサイズを計算するのに役立ちます。

    • ストップロスと取引ごとの最大リスクに基づく計算
    • 取引ごとの最大リスクに直接基づく計算

    次に、コード内で計算がどのように実装されているかを見ていきます。

    //+-----------------------------------------------------------------------------------------------+
    //| Function to obtain the ideal lot based on the maximum loss per operation and the stop loss    |
    //+-----------------------------------------------------------------------------------------------+
    double CRiskManagemet::GetLote(const ENUM_ORDER_TYPE order_type, const ENUM_GET_LOT mode_get_lot)
     {
      if(mode_get_lot == GET_LOT_BY_STOPLOSS_AND_RISK_PER_OPERATION)
       {
        double MaxLote = GetMaxLote(order_type);
        SetNMPLO(this.lote,MaxLote);
        PrintFormat("Maximum loss in case the next operation fails %.2f ", this.nmlpo);
       }
      else
       {
        this.lote = GetLotByRiskPerOperation(this.gmlpo.value,order_type);
       }
      return this.lote;
     }

    モード1:ストップロスと取引ごとの最大リスクに基づくロット計算

    ユーザーがGET_LOT_BY_STOPLOSS_AND_RISK_PER_OPERATIONを選択した場合、システムは取引ごとのリスクに基づきロットサイズを計算し、ストップロスに応じて調整します。これにより、取引が損失になった場合でも、実際の損失が定義されたリスクに一致するようにします。

    1. 許容される最大ロットはGetMaxLote(order_type)関数で取得し、
    2. SetNMPLO(this.lot, MaxLot)を使って口座上限を超えないようにロットを調整します。
    3. また、取引がストップロスに到達した場合の想定最大損失を示す情報メッセージも出力されます。

    モード2:取引ごとの最大リスクに基づくロット計算

    ユーザーが別の計算モード(例:GET_LOT_BY_RISK_PER_OPERATION)を選択した場合、システムはより直接的にロットサイズを調整します。

    1. GetLotByRiskPerOperation(this.gmlpo.value, order_type)関数により、取引ごとの最大リスクに基づいてロットサイズを決定します。
    2. この方法はシンプルで、固定ストップロスに依存せず、リスクをより動的に調整するトレーダーに有用です。

    2. ストップロスを計算する関数

    前のセクションでヘルパー関数を開発済みのため、この関数は比較的シンプルです。既存の関数を再利用して効率的にストップロスを計算します。

    理想的なストップロスを取得する関数は以下の通りです。

    long GetSL(const ENUM_ORDER_TYPE type, double DEVIATION = 100, double STOP_LIMIT = 50);

    パラメータは次の通りです。
    1. type:注文タイプを指定します(Buy、Sell、Stop、Limit、Stop-Limitなど)。
    2. DEVIATION(オプション):許容される実行のズレ(デフォルト100ポイント)を表します。
    3. STOP_LIMIT(オプション):STOP_LIMIT型注文における距離(ポイント単位)を表します。

    これらのパラメータにより、ストップロス計算は動的で、市場のさまざまな状況に適応可能になります。

    関数の実装

    //+----------------------------------------------------------------------------------+
    //| Get the ideal stop loss based on a specified lot and the maximum loss per trade  |
    //+----------------------------------------------------------------------------------+
    long CRiskManagemet::GetSL(const ENUM_ORDER_TYPE type, double DEVIATION = 100, double STOP_LIMIT = 50)
     {
      double lot;
      return CalculateSL(type,this.gmlpo.value,lot,DEVIATION,STOP_LIMIT);
     }

    • この関数の動作

    1. ストップロス計算の結果となるロットサイズを格納するlot変数を定義します。
    2. 次にCalculateSL()関数を呼び出し、以下のパラメータを渡します。
      • type(注文タイプ):買い注文または売り注文に応じてストップロスを正しく計算する
      • this.gmlpo.value(取引ごとの最大リスク):ストップロスがリスク管理ルールに沿うようにする
      • lot:使用中のロットサイズに基づき更新される
      • DEVIATION:許容されるズレで、注文実行に一定の柔軟性を提供する
      • STOP_LIMIT:STOP_LIMIT型注文における距離(ポイント単位)
    3. 最後に、関数はストップロス値をポイント単位で返します。


    損益値を取得する関数

    このセクションでは、取引システム内で主要な損益指標を取得する関数について説明します。これらの関数はinlineおよびconstとして宣言されており、効率性を確保するとともに、クラス変数が意図せず変更されることを防ぎます。

    1. 最大損益を取得する関数


    以下の関数は、さまざまな期間で達成された最大利益および損失を追跡する変数に格納された値を返します。

      inline double      GetML()    const { return this.ml.value;    }
      inline double      GetMWL()   const { return this.mwl.value;   }
      inline double      GetMDL()   const { return this.mdl.value;   }
      inline double      GetGMLPO() const { return this.gmlpo.value; }
      inline double      GetNMLPO() const { return this.nmlpo; }
      inline double      GetMDP()   const { return this.mdp.value;   }

    各関数は、さまざまな分析レベルでの取引パフォーマンスに関する関連データを提供します。

    2. 日次、週次、総利益を取得する関数


    これらの関数は、異なる期間における利益を返し、システムのパフォーマンス評価を可能にします。

      //--- Obtain only profits:
      inline double      GetGrossProfit()  const { return this.gross_profit;   }
      inline double      GetWeeklyProfit() const { return this.weekly_profit;  }
      inline double      GetDailyProfit()  const { return this.daily_profit;   }

    3. マジックナンバーとフラグに基づいてポジションを決済する関数


    この関数は、特定の条件を満たすポジションをすべて決済するために設計されています。フラグシステムに基づいており、決済するポジションを以下のように指定できます。

    • 利益のあるポジションのみ
    • 損失のあるポジションのみ
    • 両方(すべてのポジション)

    void CloseAllPositions(int flags = FLAG_CLOSE_ALL_LOSS | FLAG_CLOSE_ALL_PROFIT);

    • Flags:ポジションを決済する条件を表す整数で、複数のフラグをビット単位のOR演算子(|)で組み合わせることができます。
    1. フラグの説明
    #define FLAG_CLOSE_ALL_PROFIT 2 //Flag indicating to close only operations with profit
    #define FLAG_CLOSE_ALL_LOSS   4 //Flag indicating to close only operations without profit

    フラグは2の累乗として定義されており、

    それぞれ独立して有効化するか、組み合わせて使用することができます。

     フラグ   値(10進数) 値(2進数)
    用途
    FLAG_CLOSE_ALL_PROFIT   2  00000010
    利益が出ている取引のみを決済
    FLAG_CLOSE_ALL_LOSS     4  00000100 損失が出ている取引のみを決済

    2. ポジションを決済する関数

    この関数は、すべてのポジションをループ処理し、アクティブなフラグに応じて決済します。

    3. すべてのポジションを反復処理する

    for(int i = PositionsTotal() - 1; i >= 0; i--)

    • PositionsTotal()はポジションの総数を返します。
    • 逆順でループすることで、リアルタイムでポジションを閉じる際のエラーを回避します。

    4. ポジション識別子(チケット)の取得

    ulong position_ticket = PositionGetTicket(i);

    • 各ポジションには一意のチケットがあり、識別に使用されます。

    5. ポジション選択

    if (!PositionSelectByTicket(position_ticket))
    
        continue;

    • PositionSelectByTicket(position_ticket)は、取得したチケットでポジションを選択しようとします。
    • 選択に失敗した場合は、continueにより次のポジションに移ります。

    6. マジックナンバーの確認

    ulong magic = PositionGetInteger(POSITION_MAGIC);
    
    if (magic != this.magic_number && this.magic_number != NOT_MAGIC_NUMBER)
    
        continue;

    • ポジションのマジックナンバーを取得します。
    • 期待したマジックナンバーと異なる場合、またはすべての注文が許可されていない場合(NOT_MAGIC_NUMBER)、そのポジションは無視されます。

    7. ポジションの利益取得

    double profit = PositionGetDouble(POSITION_PROFIT);

    • POSITION_PROFITは取引の現在の利益を返します。正の場合は利益、負の場合は損失です。

    8. フラグの確認と取引の終了

    if ((flags & FLAG_CLOSE_ALL_PROFIT) != 0 && profit > 0) {  
    
        trade.PositionClose(position_ticket);  
    }  
    
    else if ((flags & FLAG_CLOSE_ALL_LOSS) != 0 && profit < 0) {  
    
        trade.PositionClose(position_ticket);  
    } 

    • &演算子を使い、フラグが有効かどうかを確認します。
    • FLAG_CLOSE_ALL_PROFITが有効で、ポジションが利益を出している場合、ポジションを決済します。
    • FLAG_CLOSE_ALL_LOSSが有効で、ポジションが損失を出している場合、ポジションを決済します。
    9. 完全なコード
    void CRiskManagemet::CloseAllPositions(int flags = FLAG_CLOSE_ALL_LOSS | FLAG_CLOSE_ALL_PROFIT)
     {
      for(int i = PositionsTotal() - 1; i >= 0; i--)
       {
        ulong position_ticket = PositionGetTicket(i);
        if(!PositionSelectByTicket(position_ticket))
          continue; // If you don't select the position, continue
        double profit = PositionGetDouble(POSITION_PROFIT);
        // Check flags before closing the position
        if((flags & FLAG_CLOSE_ALL_PROFIT) != 0 && profit > 0) // Close only profit positions
         {
          trade.PositionClose(position_ticket);
         }
        else
          if((flags & FLAG_CLOSE_ALL_LOSS) != 0 && profit < 0) // Close only losing positions
           {
            trade.PositionClose(position_ticket);
           }
       }
     }
    


    新しい日および新しい週に対するスケジュールイベント

    記事の締めくくりとして、特定のイベント時に実行される最終的な関数を実装します。具体的には、以下の2つの重要な関数を作成します。

    1. 日次イベント:新しい1日の開始時に実行されます。
    2. 週次イベント:新しい1週間の開始時に実行されます。

    これらのイベントは、損失と利益を構造的に管理し、各値が正しく更新されることを保証します。

    日次イベント

    毎日、すべての最大損失および最大利益の値を再計算して設定し、あわせて累積された日次利益をゼロにリセットする必要があります。また、監視目的として、損失および利益の値をログに出力します。
    //+------------------------------------------------------------------+
    //| Function that runs every new day                                 |
    //+------------------------------------------------------------------+
    void CRiskManagemet::OnNewDay(void)
     {
      SetMWL();
      SetMDL();
      SetML();
      SetMDP();
      SetGMLPO();
      this.daily_profit = 0;
      this.last_day_time = iTime(_Symbol,PERIOD_D1,0);
      Print(" New day ");
      Print(StringFormat("%-6s| %s", "Losses", "Loss"));
      Print(StringFormat("%-6s| %.2f", "MDP", this.mdp.value));
      Print(StringFormat("%-6s| %.2f", "MWL", this.mwl.value));
      Print(StringFormat("%-6s| %.2f", "ML", this.ml.value));
      Print(StringFormat("%-6s| %.2f", "MDl", this.mdl.value));
      Print(StringFormat("%-6s| %.2f", "GMLPO", this.gmlpo.value));
     }

    • 損失および利益の値を設定するために必要なすべての関数が呼び出されます。
    • daily_profit変数は、前日の損益を持ち越さずに新しい1日を開始するため、ゼロにリセットされます。
    • 最後に記録された日のタイムスタンプは、iTime(_Symbol, PERIOD_D1, 0)を使用して保存されます。
    • システムの動作を監視するため、損失および利益の値がログに出力されます。

    週次イベント

    週次イベントは、新しい週の開始を記録し、前週から累積された利益をゼロにリセットする役割を担います。
    //+------------------------------------------------------------------+
    //| Function that runs every new week                                |
    //+------------------------------------------------------------------+
    void CRiskManagemet::OnNewWeek(void)
     {
      this.last_weekly_time = iTime(_Symbol,PERIOD_W1,0);
      this.weekly_profit = 0;
     }

    • last_weekly_time変数は、新しい週の始値時刻で更新されます。
    • weekly_profit変数は、前週の損益が累積されないようにゼロにリセットされます。

    新しいイベントの検出(日/週/カスタム期間)

    これらの関数をEA内で自動的に実行するために、datetime変数を使用して時間足が変更されたかどうかを確認します。これにより、新しい日、新しい週、またはその他の時間足(H1、H12など)を検出できます。

    以下は、新しい日を検出する例です。

    datetime prev_time = 0;
    
    void OnTick()
    {
      if(prev_time != iTime(_Symbol, PERIOD_D1, 0))
      {
        Print("New day detected");
        prev_time = iTime(_Symbol, PERIOD_D1, 0);
        OnNewDay(); // Call the corresponding function
      }
    }
    

    以下は、新しい週を検出する例です。

    datetime prev_week_time = 0;
    
    void OnTick()
    {
      if(prev_week_time != iTime(_Symbol, PERIOD_W1, 0))
      {
        Print("New week detected")
       //Call the corresponding function
        prev_week_time = iTime(_Symbol, PERIOD_W1, 0);
      }
    }

    説明

    • 以前の時刻を格納するためのdatetime変数を定義します。
    • 各ティック(OnTick)で、現在の時刻とprev_timeまたはprev_week_timeに格納されている値を比較します。
    • 値が変化していれば、新しい日または新しい週が始まったことを示しており、OnNewDay()またはOnNewWeek()関数が呼び出されます。
    • 最後に、prev_timeまたはprev_week_time変数は新しい値で更新されます。


    結論

    本記事では、CRiskManagementクラスのリスク管理機能の第1部を開発しました。この初期段階では、主に最大損失および最大利益に値を割り当てることに重点を置き、構造化されたリスク管理の基盤を確立しました。

    このクラスはまだ実際の取引環境での使用準備は整っていませんが、システム内でこれらの値がどのように割り当てられ、管理されるかは確認できます。

    次回の記事では、クラスの実装を完成させ、以下の重要な機能を追加します。

    • 最大損失制限を超えたかどうかをチェックし、対応するアクションを実行する。
    • リスク管理をさらに改善するための新しいイベントを追加する。
    • FTMOなどのプロップファーム口座向けに、最大日次損失の自動更新など、特定の機能を作成する。

    これらの改善により、CRiskManagementクラスは、効率的にリスク制限を管理し、さまざまな市場状況に適応できるようになります。

    以下は、本記事で使用/改善されたファイルです。

    ファイル名 種類 説明 
     Risk_Management.mqh   .mqh(インクルードファイル) CRiskManagementクラスの実装を含む主要ファイルで、システム内のリスク管理を担当します。このファイルは、損益管理に関連するすべての関数を定義、開発、拡張しています。


    MetaQuotes Ltdによりスペイン語から翻訳されました。
    元の記事: https://www.mql5.com/es/articles/17249

    添付されたファイル |
    最後のコメント | ディスカッションに移動 (1)
    Pedro-2006
    Pedro-2006 | 27 1月 2026 において 17:10
    トレード方法のレッスンに参加したい
    取引におけるニューラルネットワーク:ハイブリッドグラフシーケンスモデル(最終部) 取引におけるニューラルネットワーク:ハイブリッドグラフシーケンスモデル(最終部)
    引き続き、異なるアーキテクチャの利点を統合し、高い分析精度と計算リソースの効率的な配分を実現するハイブリッドグラフシーケンスモデル(GSM++)を検討します。これらのモデルは、隠れたパターンを効果的に識別し、市場ノイズの影響を低減して予測精度を向上させます。
    初級から中級まで:イベント(II) 初級から中級まで:イベント(II)
    この記事では、すべてを必ずしも特定の方法で実装する必要がないことを見ていきます。問題解決には複数のアプローチが存在します。本記事を正しく理解するには、前回の記事で説明された概念を把握していることが前提となります。ここで提示する内容はあくまで学習目的のものであり、最終的なアプリケーションとして利用することを目的としたものではありません。
    エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法 エラー 146 (「トレードコンテキスト ビジー」) と、その対処方法
    この記事では、MT4において複数のEAの衝突をさける方法を扱います。ターミナルの操作、MQL4の基本的な使い方がわかる人にとって、役に立つでしょう。
    初心者からエキスパートへ:MQL5リスク強制EAによる取引規律の自動化 初心者からエキスパートへ:MQL5リスク強制EAによる取引規律の自動化
    多くのトレーダーにとって、口座が破綻する最大の要因は、リスクルールを理解していることと、それを一貫して守ることの間にあるギャップです。感情による判断の上書き、リベンジトレード、あるいは単純な見落としによって、どれほど優れた戦略であっても容易に崩壊してしまいます。本記事では、リスク強制エキスパートアドバイザー(Risk Enforcement EA)を開発することで、MetaTrader 5プラットフォームを、あなたの取引ルールを一切の例外なく執行する揺るぎない監督者へと変えていきます。ディスカッションにぜひご参加ください。