Русский Español Português
preview
Risk Management (Part 3): Building the Main Class for Risk Management

Risk Management (Part 3): Building the Main Class for Risk Management

MetaTrader 5Examples |
547 0
Niquel Mendoza
Niquel Mendoza


Introduction

We continue our series of articles, in which we develop a risk management system. In the previous article, we created the interface by applying the concepts learned in the first part. Now, in the third part of the series, we will focus on completing the core structure of the risk management class.

In this article, we will create a class that allows us to assign values to losses and profits, laying the foundation for profit calculation and tracking. This represents a key step toward building a robust and functional risk management system.

Risk Management Design and Planning

The following framework outlines a structured plan for designing and planning risk management in our system:

  1. Definitions, Structures, and Enumerations
    The first step is to define the necessary structures and enumerations. These are essential for storing key information, such as cumulative losses and profits, and for facilitating data handling within the system.

  2. Creating the CRiskManagement Class
    Next, we will develop the main class responsible for risk management CRiskManagement. This central class will consolidate all calculations and processes related to risk control, ensuring an organized and efficient implementation.

  3. Basic Functions: Assignment and Retrieval of Values
    We will then implement functions to assign and retrieve values, enabling us to update and query losses and profits. At this stage, we will also define the class constructor and destructor to properly manage memory and data initialization.

  4. Event-Driven Functions
    Finally, we will develop functions triggered at key moments, such as the start of a new day or week. These functions will be useful for recalculating profits, adjusting risk, and reinitializing accumulated variables over certain periods, ensuring proper performance tracking over time.

With this plan in place, we will start building from the ground up.


Definition of Constants, Enumerations, and Structures

Before implementing the code, we will organize all risk management logic into a separate file called Risk_Management.mqh (the base file we created). This keeps the code more structured and modular.

Following our risk management framework, the first step is to define the required structures, enumerations, and constants. These elements are essential for multiple functions, primarily those responsible for calculating and controlling values such as daily, weekly, or per-trade maximum loss.

Definition of Constants (#define)

To facilitate risk management and improve code readability, we will define several key constants:

1. NOT_MAGIC_NUMBER

This constant will be used as an initialization value in the class constructor. Its purpose is to indicate that a specific magic number will not be used; instead, risk management will apply to all trades without restriction to a particular identifier. This is useful when implementing risk management for the full account in a live trading environment.

#define NOT_MAGIC_NUMBER 0 //Not Magic Number 

2.Flags for Closing Trades

To make the position-closing function more dynamic, we will use flags instead of creating multiple separate functions to close trades with profit, loss, or both. These flags simplify the code and enhance reusability.

#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

Enumerations

1. Calculation Method: Fixed Amount or Percentage

We need an enumeration to determine how maximum losses and profits are calculated within the strategy. There are two main approaches:

  • Fixed (money): The user sets a specific monetary amount to define the maximum loss or profit. This value remains constant regardless of account balance.

    • Example: A daily maximum loss of $1,000 will remain unchanged while the system is running.
  • Dynamic (percentage): Instead of a fixed value, the user selects a percentage applied to a reference parameter within the account, such as balance or equity.

    • Example: If the limit is 2% of the balance, the maximum loss varies with the account balance. If the balance grows, the loss limit increases proportionally; if the balance decreases, the limit decreases.

This is represented in code with the following enumeration.

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. Percentage Application: Reference Parameter for Risk Calculation

If the calculation method is percentage-based, we must define the account value to which this percentage will apply. We consider four key options:

  • Balance - applied to total account balance.
  • Net Profit (ganancianeta) - based on cumulative profit since account creation.
  • Free Margin - calculated on capital available for opening new trades.
  • Equity - represents balance adjusted for open position profits or losses.
This is represented in code with the following enumeration.
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. Risk Management Type: Personal Account or Prop Firm (FTMO)

As mentioned in previous chapters, this system can be applied to both personal accounts and Prop Firms, specifically FTMO. FTMO has a more dynamic approach to daily maximum loss calculation.

Later, when analyzing structures, we will explain in detail how FTMO handles this calculation. For now, we will create an enumeration allowing the user to select the account type for risk management.

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

4. Select Lot Type

Not all traders prefer to trade with a dynamic lot. We will allow the user to choose the lot type. This choice does not affect the core logic of the risk management class but is important for developing an EA, as the user should be able to trade with either a fixed or dynamic lot.

We will create the following enumeration:

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

When opening a trade, lot selection can be conditioned as follows:

 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");

In this code, if the user selects a dynamic lot (Dinamico), the GetLote function will calculate the lot size based on the risk per trade. If the user selects a fixed lot (Fijo), the EA will use the default value assigned to the lot variable (lote).

Later, we will create the GetLot function to properly retrieve the dynamic lot.

5. Dynamic Lot Calculation Method

If the user chooses a dynamic lot, we need to define how its size is calculated. We will create an enumeration to specify whether the calculation is based solely on per-trade risk or adjusted according to stop loss.

  • Calculation based only on risk per trade:
    Lot size is determined based solely on the per-trade risk percentage. In this case, there is no need to specify a specific value for stop-loss.

  • Calculation based on risk and stop loss
    Lot size is calculated based on per-trade risk and adjusted according to the stop loss. This is important because if you set a fixed percentage of risk without reference to stop-loss, you may receive an inappropriate lot size.

    Example:
    Let's say we want to risk 1% of a $1000 account, which equates to a maximum risk of $10. Our strategy uses a 1:2 risk-reward ratio and a 100-point stop loss on EUR/USD (5-digit broker), a naive calculation might yield a lot of 0.02.

    • If the trade hits stop loss, the actual loss would be $2 instead of $10.
    • If the trade hits take profit, the actual gain would be $4 instead of $20.

    To solve this problem, we adjust the lot size so that for a given stop-loss, the maximum loss would be exactly 1% of the account. This ensures that if the deal reaches take profit at a ratio of 1:2, the profit will be 2%.

    The function GetIdealLot (introduced in a previous article) automatically adjusts the lot/

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

It adjust the lot so that the risk per trades stays within the specified limit.

To represent these two calculation methods, we define the following enumeration:
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.
 };

Structures:

Structures play a key role in our risk management system. As mentioned, losses and profits are calculated either directly (fixed amount) or dynamically (percentage). If using the percentage method, it applies to a reference value in the account.

We will create a structure containing all necessary information to define a loss or profit.

  • value - represents the loss or profit amount.
  • assigned_percentage - indicates the assigned percentage if the calculation is dynamic.
  • mode_calculation_risk - defines the per-trade risk calculation method (direct or dynamic).
  • percentage_applied_to - enumeration specifying which account metric the percentage applies to when using the dynamic method.

This is represented in code as:

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
 };

Using this structure, we can identify the following profit and loss options:

Name  Description Reset
Maximum Daily Loss (MDL) The maximum loss allowed in a single day. Resets to 0 daily.
Maximum Loss (ML) The maximum total loss that an account can reach over its lifetime. Does not reset.
Maximum Loss Per Trade (GMLPO) Similar to the per-trade risk defined by the user; used to estimate the appropriate lot size. Recalculated each time a trade is closed or at the start of a new day.
Maximum Weekly Loss (MWL) The maximum loss allowed within a week. Optional, as it is not required for prop firms. Resets weekly.
Net Maximum Loss Per Trade (NMLPO) Generated only when calling the function that calculates the lot based on per-trade risk and stop loss. Recalculated each time the risk-and-stop-loss-based lot retrieval function is called.
Maximum Daily Profit The maximum profit the bot or account can achieve in a single day.  Resets daily.

Particularities of Maximum Daily Loss in FTMO

When adapting this risk management system for regular accounts and prop firms such as FTMO, some key differences arise. In FTMO, the Maximum Daily Loss is highly dynamic because it can increase if profits are generated during the day.

For example, in the case of an FTMO Challenge with the initial account balance of $200,000, the Max Daily Loss limit is $10,000. If you happen to lose $8,000 in your closed trades, your account must not decline more than $2,000 this day. It must also not go -$2,000 in your open floating losses. The limit is inclusive of commissions and swaps.

Vice versa, if you profit $5,000 in one day, then you can afford to lose $15,000, but not more than that. Once again, be reminded that your Maximum Daily Loss counts your open trades as well. For example, if in one day, you have closed trades with a loss of $6,000 and then you open a new trade that goes into a floating loss of some -$5,700 but ends up positive in the end, unfortunately, it is already too late. In one moment, your daily loss was -$11,700 on the equity, which is more than the permitted loss of $10,000.

FTMO Maximum Daily Loss

As noted directly from FTMO's guidelines, the maximum daily loss can vary depending on the profits achieved throughout the day.

For example, if at the start of the day your allowed maximum loss is $10,000 and you generate a profit on the same day, this profit is added to the initial $10,000, increasing your permissible loss margin. Thus, as profits accumulate, the amount you can risk also adjusts accordingly.


Declaration of Key Variables for Risk Management

In this section, we will define the CRiskManagement class, responsible for managing the risk of trades.

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

Including Libraries

Before defining the class, we'll include the CTrade.mqh library to be able to manage trades, such as closing positions:

#include <Trade/Trade.mqh>

1. Pointer to CTrade

As the first variable, we create a pointer to the CTrade class, which will allow us to interact with open positions:

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

2. Key variables

We define three main variables:

  • account_profit (double) - stores the account's net profit since 1971.01.01 (minimum date in datetime type);
  • StopLoss (long) - maintains the level of stop-loss in points used to calculate the lot size;
  • batch (double) - an internal variable that stores the last calculated lot.

Code:

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

3. Common variables

  1. magic_number (ulong) - stores the magic number used to manage the risk associated with a given set of trades. It can also be equal to 0, which allows it to be used on personal accounts.
  2. mode_risk_management (ENUM_MODE_RISK_MANAGEMENT) - defines the type of risk management that will be applied: for regular accounts or prop companies (e.g. FTMO).

Code:

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

4. Specific variables for prop firm accounts (FTMO)

In prop firm accounts, risk management rules apply to the opening balance, not the running balance. For example, the maximum daily loss and maximum total loss are typically calculated as a percentage of the opening balance (usually 10%).

To account for this feature, we will add a variable to store the initial balance, which the user will have to set manually:

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

5. Variable for expected maximum loss on the next trade (NMLPO)

This variable will store the estimated maximum loss for the next trade. It is not mandatory, but can be useful for predicting how much the robot will lose if the position reaches the stop loss level.

It will be calculated only with the GetLote() call.

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

6. Variables for profit and loss management

The previously defined Loss_Profit structure will be used to store the amount of the loss, the method for calculating it, and the way the percentage is applied.

Let's define five key variables:

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

where:

  • mdl - maximum daily loss;
  • mwl - maximum weekly loss;
  • ml - maximum total loss;
  • gmlpo - maximum loss per trade;
  • mdp - maximum daily profit.

7. Variables for profit (daily, weekly and total)

To monitor the robot/Expert Advisor performance, we will add variables that will store profit data and will allow for alerts to be issued when loss limits are reached or when security measures are taken.

We will need:

  • reference time from which profit is calculated,
  • variables to store profit.

Code:

  //--- 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;

where:

  • daily_profit - profit received during the day,
  • weekly_profit - profit received during the week,
  • gross_profit - total profit since the beginning of risk management,
  • last_weekly_time - the last recorded date for calculating weekly profit,
  • last_day_time - the last recorded date for calculating daily profit,
  • init_time - the start date of risk management.



Creation of Constructor, Destructor, and Initialization Methods

Now that we have defined the key variables, we will implement the main functions of the CRiskManagement class. 

Constructor 

The constructor will be responsible for initializing key variables such as the magic number, risk management mode, and the prop company account balance (if applicable).

The constructor declaration is as follows:

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

In the constructor implementation, we assign values to variables, initialize the CTrade object, and calculate the account's net profit starting from January 1, 1972. We also set appropriate timestamps to track profits.

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';
 }

Note: If the user does not specify a magic number, a warning will be displayed that the system will take into account all trades opened by the user.

Destructor

The class destructor will be responsible for freeing the memory allocated to the CTrade object when the CRiskManagement instance is deleted.

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

Initialization Methods 

To simplify the risk management setup and update, we will create special functions that will allow you to:

  • set stop loss,
  • set up profit and loss parameters.

1. Functions for Setting Loss Structure Values

We define methods that will allow us to assign values to key variables for profit and loss calculations.

  //--- 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. Assigning Percent to Loss Structures

The following method assigns percentage or monetary values to the corresponding structures.

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. Initialization of Risk Calculation Modes

This method sets the calculation mode for each structure. If the user selects the money-based calculation mode, the corresponding value is assigned directly.

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. Assigning Parameters Applied to the Account

This method determines how the configured percents will be applied to the account.

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. Stop-Loss Setting Functions

To set the stop loss, we will use 2 simple methods to assign a value to the StopLoss variable:

  • Method 1: The user directly sets the number of stop loss points.
  • Method 2: The user defines the distance between the stop loss and the entry point, after which it is converted into points (using the function we programmed in the first part of the article). 

  //--- 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_;                     }



Methods for Assigning Values to Losses and Profits

In this section, we will develop the methods needed to assign values to profit and loss in our risk management system.

1. General Function for Assigning Values

To ensure flexible and adaptive risk management, we will create a general function that will calculate the appropriate value based on the percentage applied to various account metrics.

Function definition:

The main function we will be using is the following:

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

This function will take two key parameters:

  1. applied - defines which account metric the percentage will be applied to (balance, free margin, equity, etc.).
  2. percentage_ - the percentage to be applied to the selected metric.
Depending on these values, the function will calculate the corresponding amount.
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;
 }
Code explanation
    • Managing Prop Firm Accounts:
      • If the account belongs to a prop firm (e.g. FTMO), and the percentage applied does not refer to the maximum daily loss (mdp.assigned_percentage) or the maximum loss per trade (gmlpo.assigned_percentage), the calculation is based on the prop firm's account balance.
    • Calculation based on various account metrics:
      • Balance - percentage applied to total account balance.
      • ganancianeta - percentage applied to the account's net profit. If the profit is negative or zero, an error message is displayed.
      • free_margin - percentage applied to the free margin. If the free margin is negative, the function returns 0.
      • equity - percentage applied to the account's current equity.
    • Error handling:
      • If an invalid metric is passed to the applied function, the function will print an error message and return 0.

    2. Creating functions to configure loss and profit variables

    To efficiently manage losses and profits, we will create a set of functions that allow us to assign values in a structured manner. In total, we will define six functions: five dedicated to configuring the main loss parameters, and one to calculate the ideal lot size based on the Stop Loss.

    1. Value Assignment Functions

    The method for assigning loss and profit values is based on the following logic:

    1. If the calculation mode is money, the previously assigned value is retained.
    2. If the calculation is not in money mode, the GetValorWithApplied function is used to determine the value based on the assigned percentage and the corresponding application parameter.
    3. If the assigned percentage is 0, the loss is considered unused and, therefore, a value of 0 is assigned.

    2. Assignment Functions

    The following functions implement the logic described above for each type of loss:

      //--- 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); }


    Calculation of Lot Size and Stop Loss Based on Per-Trade Risk

    In this section, we will implement two essential functions to accurately calculate the lot size and stop loss, ensuring that each trade aligns with the level of risk we are willing to assume.

    1. Function to Calculate Lot Size

    To calculate the lot size, we will use the functions developed in the first article, ensuring that the lot complies with our predefined risk rules.

    The function that allows us to obtain the appropriate lot size is:

      //--- Get the lot
      double             GetLote(const ENUM_ORDER_TYPE order_type, const ENUM_GET_LOT mode_get_lot);
    Its parameters:
    1. order_type - specifies the order type (buy, sell, stop, limit, stop-limit, etc.).
    2. mode_get_lot - indicates the lot calculation method (explained earlier in the enumerations section).

    Purpose of the function

    This function helps calculate the ideal lot size using two different approaches:

    • based on the stop loss and maximum risk per trade,
    • based directly on the maximum risk per trade.

    Now, let's see how the calculation is implemented in the code.

    //+-----------------------------------------------------------------------------------------------+
    //| 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;
     }

    Mode 1. Lot Calculation Based on Stop Loss and Maximum Per-Trade Risk

    When the user selects GET_LOT_BY_STOPLOSS_AND_RISK_PER_OPERATION, the system calculates the lot size based on per-trade risk and then adjusts it according to the stop loss, ensuring that if the trade results in a loss, the actual loss matches the defined risk.

    1. The maximum allowed lot is obtained using the GetMaxLote(order_type) function.
    2. The lot size is adjusted using SetNMPLO(this.lot, MaxLot) to prevent exceeding account limits.
    3. An informational message is printed indicating the expected maximum loss if the trade reaches the stop loss.

    Mode 2. Lot Calculation Based on Maximum Per-Trade Risk

    When the user selects a different calculation mode (for example, GET_LOT_BY_RISK_PER_OPERATION), the system adjusts the lot size in a more direct manner:

    1. The GetLotByRiskPerOperation(this.gmlpo.value, order_type) function determines the lot size based on the maximum risk we are willing to assume for the trade.
    2. This method is simpler and may be useful for traders who do not rely on a fixed stop loss, instead adjusting their risk more dynamically.

    2. Function to Calculate the Stop Loss

    Since we have already developed auxiliary functions in previous sections, this function will be relatively simple, as we will reuse those functions to calculate the stop loss efficiently.

    The function that helps us obtain the ideal stop loss is:

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

    Its parameters:
    1. type - specifies the order type: buy, sell, stop, limit, stop-limit, etc.
    2. DEVIATION (optional - represents the allowed execution deviation, with a default value of 100 points.
    3. STOP_LIMIT (optional) - represents the distance in points for STOP_LIMIT-type orders.

    These parameters ensure that the stop loss calculation is dynamic and adaptable to different market conditions.

    Function implementation:

    //+----------------------------------------------------------------------------------+
    //| 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);
     }

    • How this function works

    1. It defines the variable lot, which will store the lot size resulting from the stop loss calculation.
    2. It calls the CalculateSL() function, passing the following parameters:
      • order type (type), so that the stop loss is calculated correctly for buys or sells;
      • maximum per-trade risk (this.gmlpo.value), so that the stop loss is consistent with risk management rules;
      • lot - it will be updated based on the lot size in use;
      • DEVIATION - the allowed deviation, providing certain flexibility in order execution;
      • STOP_LIMIT - represents the distance in points for STOP_LIMIT orders.
    3. Finally, the function returns the stop loss value in points.


    Functions to Retrieve Loss and Profit Values

    This section describes the functions responsible for retrieving key loss and profit metrics within the trading system. These functions are declared as inline and const to ensure efficiency and prevent unintended modifications to class variables.

    1. Functions to Retrieve Maximum Losses and Profits


    The following functions return the values stored in variables that track the maximum profit and loss reached over different periods:

      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;   }

    Each function provides relevant data regarding trading performance at different levels of analysis.

    2. Functions to Get Daily, Weekly, and Total Profit


    These functions return profits over different time periods, allowing for performance evaluation of the system.

      //--- 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. Function to Close Open Positions Based on Magic Number and Flags


    This function is designed to close all open positions that meet specific criteria. It is based on a flag system that allows specifying which trades to close:

    • only profitable,
    • only losing,
    • Both types (all trades).

    void CloseAllPositions(int flags = FLAG_CLOSE_ALL_LOSS | FLAG_CLOSE_ALL_PROFIT);

    • Flags - an integer representing the conditions under which positions will be closed. Multiple flags can be combined using bitwise OR operators (|).
    1. Explanation of Flags
    #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

    The flags are defined as powers of two.

    Each flag represents a value that can be activated independently or in combination:

     Flag   Value (decimal) Value (binary)
    Purpose
    FLAG_CLOSE_ALL_PROFIT   2  00000010
    Closes only profitable trades.
    FLAG_CLOSE_ALL_LOSS     4  00000100 Closes only losing trades.

    2. Function to Close Positions

    This function iterates through all open positions and closes them according to the active flags.

    3. Iterating Through All Open Positions

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

    • PositionsTotal() returns the total number of open positions.
    • The loop iterates through positions in reverse order (from last to first) to avoid errors when closing positions in real time.

    4. Retrieving the Position Identifier (Ticket)

    ulong position_ticket = PositionGetTicket(i);

    • Each position has a unique ticket that identifies it.

    5. Position Selection

    if (!PositionSelectByTicket(position_ticket))
    
        continue;

    • PositionSelectByTicket(position_ticket) пытается выбрать позицию по полученному тикету.
    • If selection fails, 'continue' moves to the next position.

    6. Checking the Magic Number

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

    • The magic number of the position is retrieved.
    • If the position has a magic number different from the expected one and operating on any order is not allowed (NOT_MAGIC_NUMBER), the position is ignored.

    7. Retrieving the Position Profit

    double profit = PositionGetDouble(POSITION_PROFIT);

    • POSITION_PROFIT returns the current profit of the trade. This value can be positive (profit) or negative (loss).

    8. Checking Flags and Closing Trades

    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);  
    } 

    • The & operator is used to check whether a flag is enabled.
    • If FLAG_CLOSE_ALL_PROFIT is active and the position is profitable, it is closed.
    • If FLAG_CLOSE_ALL_LOSS is active and the position is losing, it is closed.
    9. Full Code
    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);
           }
       }
     }
    


    Scheduled Events for New Day and New Week

    To conclude this article, we will implement the final functions that execute during specific events. In particular, we will create two key functions:

    1. Daily Event - executed at the beginning of each new day.
    2. Weekly Event - executed at the start of each new week.

    These events help manage losses and profits in a structured manner, ensuring that values are updated correctly.

    Daily Event

    Each day, it is necessary to recalculate and set the values for all maximum losses and gains, as well as reset the accumulated daily profit to zero. We will also print loss and profit values to the log for monitoring purposes.
    //+------------------------------------------------------------------+
    //| 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));
     }

    • All required functions to set loss and profit values are called.
    • The daily_profit variable is reset to zero to start a new day without carrying over previous gains or losses.
    • The timestamp of the last recorded day is stored using iTime(_Symbol, PERIOD_D1, 0).
    • Loss and profit values are printed to the log to monitor system behavior.

    Weekly Event

    The weekly event is responsible for registering the start of a new week and resetting the accumulated profit from the previous week to zero.
    //+------------------------------------------------------------------+
    //| Function that runs every new week                                |
    //+------------------------------------------------------------------+
    void CRiskManagemet::OnNewWeek(void)
     {
      this.last_weekly_time = iTime(_Symbol,PERIOD_W1,0);
      this.weekly_profit = 0;
     }

    • The last_weekly_time variable is updated with the opening time of the new week.
    • The weekly_profit variable is reset to zero to prevent accumulation of gains or losses from the previous week.

    Detection of New Events (Day/Week/Custom Period)

    To execute these functions automatically within an Expert Advisor (EA), we can check whether the time period has changed using a datetime variable. This allows us to detect a new day, week, or any other timeframe (H1, H12, etc.).

    Example of new day detection:

    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
      }
    }
    

    Example of new week detection:

    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);
      }
    }

    Explanation:

    • A datetime variable is defined to store the previously recorded time.
    • On each tick (OnTick), the current time is compared with the value stored in prev_time or prev_week_time.
    • If the value has changed, it indicates that a new day or week has started, and the OnNewDay() or OnNewWeek() function is called.
    • The prev_time or prev_week_time variable is updated with the new value.


    Conclusion

    In this article, we have developed the first part of the CRiskManagement risk management class. At this initial stage, our primary focus has been on assigning values to maximum losses and profits, thereby establishing the foundation for structured risk control.

    Although the class is not yet ready for use in a live trading environment, we can already see how these values are assigned and managed within the system.

    In the next article, we will complete the implementation of this class by adding key functions that will allow us to:

    • check whether maximum loss limits have been exceeded and execute the corresponding actions;
    • add new events to further improve risk management;
    • create specific functions, such as the automatic updating of maximum daily loss for Prop Firm–type accounts, such as FTMO.

    With these improvements, the CRiskManagement class will be ready to efficiently manage risk limits and adapt to different market conditions.

    Files used/improved in this article:

    File name Type Description 
     Risk_Management.mqh   .mqh (include file) The main file containing common functions and the implementation of the CRiskManagement class, which is responsible for risk management in the system. This file defines, develops, and extends all functions related to profit and loss management.


    Translated from Spanish by MetaQuotes Ltd.
    Original article: https://www.mql5.com/es/articles/17249

    Attached files |
    MQL5 Trading Tools (Part 13): Creating a Canvas-Based Price Dashboard with Graph and Stats Panels MQL5 Trading Tools (Part 13): Creating a Canvas-Based Price Dashboard with Graph and Stats Panels
    In this article, we develop a canvas-based price dashboard in MQL5 using the CCanvas class to create interactive panels for visualizing recent price graphs and account statistics, with support for background images, fog effects, and gradient fills. The system includes draggable and resizable features via mouse event handling, theme toggling between dark and light modes with dynamic color adjustments, and minimize/maximize controls for efficient chart space management.
    Price Action Analysis Toolkit Development (Part 56): Reading Session Acceptance and Rejection with CPI Price Action Analysis Toolkit Development (Part 56): Reading Session Acceptance and Rejection with CPI
    This article presents a session-based analytical framework that combines time-defined market sessions with the Candle Pressure Index (CPI) to classify acceptance and rejection behavior at session boundaries using closed-candle data and clearly defined rules.
    Features of Experts Advisors Features of Experts Advisors
    Creation of expert advisors in the MetaTrader trading system has a number of features.
    Larry Williams Market Secrets (Part 7): An Empirical Study of the Trade Day of the Week Concept Larry Williams Market Secrets (Part 7): An Empirical Study of the Trade Day of the Week Concept
    An empirical study of Larry Williams’ Trade Day of the Week concept, showing how time-based market bias can be measured, tested, and applied using MQL5. This article presents a practical framework for analyzing win rates and performance across trading days to improve short-term trading systems.