Español
preview
Управление рисками (Часть 3): Создание основного класса для управления рисками

Управление рисками (Часть 3): Создание основного класса для управления рисками

MetaTrader 5Примеры |
79 0
Niquel Mendoza
Niquel Mendoza


Введение

Продолжаем нашу серию статей, посвященную разработке системы управления рисками. В предыдущей статье мы создали интерфейс, применяя концепции, изученные в первой части. Теперь, в третьей части серии, мы сосредоточимся на завершении основной структуры класса по управлению рисками.

В этой статье мы создадим класс, который позволит нам присваивать значения прибыли и убыткам, тем самым закладывая основу для расчета и отслеживания профитов (profits). Таким образом мы сделаем важный шаг на пути к созданию надежной и функциональной системы управления рисками.

План управления рисками

На этой схеме представлен структурированный план разработки и планирования управления рисками в нашей системе.

  1. Определения, структуры и перечисления
    Первое, что мы сделаем, это определим необходимые структуры и перечисления. Они будут необходимы для хранения ключевой информации, такой как накопленная прибыль и убытки, а также для упрощения управления данными в системе.

  2. Создание класса CRiskManagement
    Затем мы разработаем основной класс, отвечающий за управление рисками — CRiskManagement. Этот класс будет централизовать все вычисления и процессы, связанные с контролем рисков, обеспечивая организованную и эффективную реализацию.

  3. Основные функции: присвоение и получение значений
    Далее мы реализуем функции, необходимые для присвоения и получения значений, чтобы получить возможность обновлять и запрашивать информацию о прибыли и убытках. Кроме того, на этом этапе мы определим конструктор и деструктор класса для корректного управления памятью и инициализацией данных.

  4. Функции, активируемые событиями
    Наконец, мы разработаем функции, которые будут выполняться в ключевые моменты, например, в начале нового дня или недели. Эти функции будут полезны для пересчета профитов, корректировки рисков и переинициализации накопленных переменных в определенные периоды, обеспечивая надлежащее отслеживание результативности с течением времени.

Теперь, когда мы определили план действий, начнем с самого основного.


Определение констант, перечислений и структур

Прежде чем приступить к реализации кода, мы организуем всю логику, связанную с управлением рисками, в отдельном файле под названием «Risk_Management.mqh» (базовый файл, который мы создали). Таким образом, мы сохраним более структурированный и модульный код.

В соответствии с нашей схемой управления рисками, первым шагом будет создание определенных структур, перечислений и констант. Эти элементы будут иметь важное значение для множества функций, в первую очередь тех, которые отвечают за вычисление и контроль таких значений, как максимальный дневной убыток, недельный убыток или убыток на сделку.

Определение констант (#define)

Для упрощения управления рисками и улучшения читаемости кода мы определим несколько ключевых констант:

1. NOT_MAGIC_NUMBER

Эта константа будет использоваться, как инициализирующее значение в конструкторе класса. Ее назначение — указать, что не будет использоваться конкретный magic number, а управление рисками будет применяться ко всем сделкам без ограничений по какому-либо конкретному идентификатору. Это может быть полезно, если требуется реализовать управление рисками в отношении всего счета в условиях реальной торговли.

#define NOT_MAGIC_NUMBER 0 //Not Magic Number 

2.Флаги (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

Перечисления

1. Метод расчета: фиксированная сумма или процент

Для начала нам понадобится перечисление, которое позволит определить, как будут рассчитываться максимальные убытки и прибыль в рамках стратегии. Существует два основных подхода:

  • Фиксированный (money) — пользователь устанавливает конкретную сумму в деньгах для определения максимальной прибыли или убытка. Это значение остается постоянным, независимо от баланса счета.

    • Пример: если установлено максимальное дневное ограничение убытка в размере 1000 USD, этот лимит останется неизменным, пока система работает.
  • Динамический (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. Применение процента: по какому параметру будет рассчитываться риск

Если выбран метод расчет по процентам, необходимо определить, к какому значению счета этот процент будет применяться. Для этого рассматриваются четыре основных варианта:

  • Баланс (Balance) — процент применяется к общему балансу счета.
  • Чистая прибыль (ganancianeta) — рассчитывается на основе накопленной прибыли с момента создания счета.
  • Свободная маржа (free_margin) — рассчитывается на основе капитала, доступного для открытия новых сделок.
  • Эквити (equity) — отражает баланс, скорректированный с учетом прибыли или убытков по открытым позициям.
В коде это представлено с помощью следующего перечисления:
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. Какой тип лота будет использоваться

Не все трейдеры предпочитают работать с динамическим лотом, поэтому мы дадим возможность пользователю выбирать желаемый тип лота. Этот выбор не повлияет на основную логику класса управления рисками, но он важен при разработке советника, поскольку необходимо предоставить пользователю возможность торговать фиксированным или динамическим лотом.

Для этого создадим следующее перечисление:

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 рассчитает размер лота на основе риска на сделку. Напротив, если он выберет фиксированный лот (Fijo), советник будет использовать значение по умолчанию, присвоенное переменной лота (lote).

Позже мы создадим функцию GetLote для корректного получения динамического лота.

5. Как будет рассчитываться лот, если он динамический

Если пользователь выбирает торговлю с динамическим лотом, необходимо определить, как будет рассчитываться его размер. Для этого мы создадим перечисление, которое определит, будет ли расчет основан исключительно на риске на сделку, или он также будет корректироваться с учетом стоп-лосса.

  • Расчет, основанный только на риске на сделку:
    Лот определяется, исходя исключительно из процента риска на сделку. В этом случае нет необходимости задавать конкретное значение для стоп-лосса.

  • Расчет, основанный на риске на сделку и стоп-лоссе
    В этом методе, помимо расчета лота на основе риска на сделку, он корректируется с учетом стоп-лосса. Это важно, так как если задать фиксированный процент риска без привязки к стоп-лоссу, можно получить неподходящий размер лота.

    Пример:
    Предположим, мы хотим рискнуть 1% от счета в $1000, что соответствует максимальному риску в $10. Если наша стратегия имеет соотношение риск/прибыль 1:2, и мы используем стоп-лосс в 100 пунктов по EUR/USD (где каждый пункт стоит 0,00001 для 5-значного брокера), мы могли бы ошибочно рассчитать лот в 0,02.

    • Если сделка закроется с убытком по стоп-лоссу в 100 пунктов, мы потеряем всего $2 вместо предусмотренных $10.
    • Если сделка окажется прибыльной, мы заработаем всего $4 вместо ожидаемых $20 при соотношении 1:2.

    Чтобы решить эту проблему, мы корректируем размер лота так, чтобы при заданном стоп-лоссе максимальный убыток составлял ровно 1% от счета. Это гарантирует, что если сделка дойдет до тейк-профита при соотношении 1:2, прибыль составит 2%.

    Функция GetIdealLot, представленная в предыдущей статье, выполняет эту корректировку автоматически.

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

Эта функция корректирует размер лота так, чтобы риск на сделку оставался в пределах заданного лимита.

Чтобы представить эти два метода расчета, мы определяем следующее перечисление:
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 — представляет собой сумму убытка или прибыли.
  • assigned_percentage — указывает назначенный процент в случае динамического расчета.
  • mode_calculation_risk — определяет метод расчета риска на сделку (прямой или динамический).
  • 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)Это максимальный убыток, допустимый в течение одного дня.Ежедневно сбрасывается в 0.
Максимальный убыток (ML)Это максимальный общий убыток, которого может достичь счет за все время существования.Не сбрасывается и не перезапускается.
Максимальный убыток по сделке (GMLPO)Аналогичен определяемому пользователем риску на сделку, используется для оценки подходящего размера лота.Пересчитывается каждый раз при закрытии сделки или в начале нового дня.
Максимальный недельный убыток (MWL) Это максимальный убыток, допустимый за неделю. Опционален, так как не является обязательным требованием в проп-компаниях.Перезапускается еженедельно.
Максимальный чистый убыток по сделке (NMLPO)Генерируется только при вызове функции, которая рассчитывает лот на основе риска на сделку и стоп-лосса.Пересчитывается каждый раз при вызове функции получения лота, основанного на риске и стоп-лоссе.
Максимальная дневная прибыльЭто максимальная прибыль, которую бот или счет может получить за один день. Перезапускается ежедневно.

Особенности максимального дневного убытка в FTMO

Когда мы адаптируем эту систему управления рисками для обычных счетов и проп-компаний, таких как FTMO, выявляются некоторые ключевые различия. В FTMO максимальный дневной убыток является супердинамическим, так как он может увеличиваться, если в течение дня есть прибыль:

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 Máxima pérdida diaria

Как видно из примечания, взятого непосредственно с сайта FTMO, максимальный дневной убыток может варьироваться в зависимости от прибыли, полученной в течение дня.

Например, если в начале дня ваш максимально допустимый убыток равен $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. Основные переменные

Мы определим три основные переменные:

  • account_profit (double) — хранит чистую прибыль счета с момента 1971.01.01 (минимальная дата в типе datetime);
  • StopLoss (long)  — сохраняет уровень стоп-лосса в пунктах, используемый для расчета размера лота;
  • batch (double) — внутренняя переменная, которая хранит последний рассчитанный лот.

Код:

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

3. Общие переменные

  1. magic_number (ulong) — хранит magic number, используемый для управления риском, связанным с определенным набором сделок. Он также может быть равен 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)

Эта переменная будет хранить предполагаемый максимальный убыток для следующей сделки. Она не является обязательной, но может быть полезна для прогнозирования того, сколько потеряет бот, если позиция достигнет уровня Stop Loss.

Она будет рассчитана только при вызове функции GetLote()

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

6. Переменные для управления прибылью и убытками

Определенная ранее структура Loss_Profit будет использоваться для хранения как величины убытка, так и метода его расчета и способа применения процента.

Определим пять ключевых переменных:

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

Где:

  • mdl — максимальный дневной убыток (Max Daily Loss);
  • mwl — максимальный недельный убыток;
  • ml — максимальный общий убыток (Max Loss);
  • gmlpo — максимальный убыток на сделку;
  • mdp — максимальная дневная прибыль.

7. Переменные для получения прибыли (дневной, недельной и общей)

Для мониторинга эффективности бота/советника мы добавим переменные, в которых будут храниться данные о прибыли, и которые позволят выдавать оповещения о достижении лимитов убытка или принятии мер безопасности.

Нам понадобится:

  • контрольное время, от которого рассчитывается прибыль,
  • переменные для хранения прибыли.

Код:

  //--- 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 и рассчитаем чистую прибыль счета, начиная с 1 января 1972 года. Также мы установим соответствующие метки времени для отслеживания прибыли.

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

Примечание: если пользователь не задает magic number, будет выведено предупреждение о том, что система будет учитывать все сделки, открытые пользователем.

Деструктор

Деструктор класса будет отвечать за освобождение памяти, выделенной объекту CTrade, при удалении экземпляра CRiskManagement.

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. Инициализация режимов расчета риска

Этот метод устанавливает режим расчета для каждой структуры. Если пользователь выбирает режим расчета на основе денег (money), соответствующее значение присваивается напрямую.

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. Функции установки стоп-лосса

Для установки стоп-лосса мы будем использовать 2 простых метода для присвоения значения переменной StopLoss:

  • Метод 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_);

Эта функция будет принимать два ключевых параметра:

  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), и применяемый процент не относится к максимальному дневному убытку (mdp.assigned_percentage) или максимальному убытку на сделку (gmlpo.assigned_percentage), расчет выполняется на основе баланса счета проп-компании.
    • Расчет на основе различных метрик счета:
      • Balance — применяет процент к общему балансу счета.
      • ganancianeta — применяет процент к чистой прибыли счета. Если прибыль отрицательная или равна нулю, выводится сообщение об ошибке.
      • free_margin — применяет процент к свободной марже. Если свободная маржа отрицательная, функция возвращает 0.
      • equity — применяет процент к текущему значению эквити счета.
    • Обработка ошибок:
      • Если в функцию applied передана недопустимая метрика, функция выведет сообщение об ошибке и вернет 0.

    2. Создание функций для настройки переменных прибыли и убытков

    Для эффективного управления прибылью и убытками мы создадим набор функций, которые позволят нам структурированно присваивать значения. Всего будет определено шесть функций: пять из них — для настройки основных убытков и одна — для расчета идеального лота на основе Stop Loss.

    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. Функция для расчета лота

    Для расчета лота мы воспользуемся функциями, разработанными в первой статье, чтобы убедиться, что объем лота соответствует нашим предустановленным правилам риска.

    Функция, которая позволит нам получить подходящий лот, следующая:

      //--- Get the lot
      double             GetLote(const ENUM_ORDER_TYPE order_type, const ENUM_GET_LOT mode_get_lot);
    Параметры функции:
    1. order_type — указывает тип ордера (покупка, продажа, стопы, лимиты, стоп-лимиты и т. д.).
    2. mode_get_lot — указывает метод расчета лота (который мы объяснили в разделе перечислений).

    Цель функции

    Эта функция поможет нам рассчитать идеальный размер лота, основываясь на двух разных подходах:

    • в зависимости от стоп-лосса и максимального риска на сделку,
    • непосредственно на основе максимального риска на сделку.

    Теперь давайте посмотрим, как расчет реализуется в коде.

    //+-----------------------------------------------------------------------------------------------+
    //| 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 — указывает тип ордера: это может быть ордер на покупку, продажу, стоп, лимит, стоп-лимит и т. д.
    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.Функция для закрытия открытых сделок согласно магическому номеру и флагам


    Эта функция предназначена для закрытия всех открытых позиций, которые соответствуют определенным критериям. Она основана на системе флагов (flags), которая позволяет указать, какие сделки должны быть закрыты:

    • только прибыльные,
    • только убыточные,
    • обе категории (все сделки).

    void CloseAllPositions(int flags = FLAG_CLOSE_ALL_LOSS | FLAG_CLOSE_ALL_PROFIT);

    • Флаг — это целое число, представляющее условия закрытия позиций. Различные флаги можно комбинировать с помощью побитовых операторов (|).
    1. Объяснение флагов (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

    Здесь флаги представлены как степени числа 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. Получение идентификатора (ticket) позиции

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


    События, срабатывающие в начале нового дня и новой недели

    В завершение этой статьи мы запрограммируем последние функции, которые будут выполняться при определенных событиях. В частности, создадим две ключевые функции:

    1. ежедневное событие — выполняется в начале каждого нового дня,
    2. еженедельное событие — выполняется в начале каждой новой недели.

    Эти события помогут структурированно управлять прибылью и убытками, обеспечивая корректное обновление значений.

    Ежедневное событие

    Каждый день необходимо пересчитывать и устанавливать значения всех максимальных убытков и прибыли, а накопленную дневную прибыль обнулять. Мы также будем выводить значения прибыли и убытков в журнал для мониторинга.
    //+------------------------------------------------------------------+
    //| 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 сбрасывается, чтобы начать новый день без переноса прибыли или убытков из предыдущего.
    • Метка времени последнего зарегистрированного дня сохраняется с помощью 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. На этом начальном этапе наше основное внимание было уделено присвоению значений максимальной прибыли и убыткам, что создает основу для структурированного управления рисками.

    Хотя класс еще не готов к использованию в рабочей среде, мы уже можем видеть, как внутри системы эти значения назначаются и обрабатываются.

    В следующей статье мы завершим реализацию этого класса, добавив ключевые функции, которые позволят:

    • проверять, не превышены ли максимальные убытки, и принимать соответствующие меры;
    • включать новые события для улучшения управления рисками;
    • создавать специальные функции, такие как автоматическое обновление максимального дневного убытка на счетах типа Prop Firm, таких как FTMO.

    Благодаря этим улучшениям, класс CRiskManagement будет готов эффективно управлять лимитами риска и адаптироваться к различным рыночным условиям.

    Файлы, использованные/улучшенные в этой статье:

    Имя файлаТипОписание 
     Risk_Management.mqh  .mqh (включаемый файл)Основной файл, содержащий общие функции и реализацию класса CRiskManagement, отвечающего за управление рисками в системе. В этом файле определяются, разрабатываются и расширяются все функции, связанные с управлением прибылью и убытками.

    Перевод с испанского произведен MetaQuotes Ltd.
    Оригинальная статья: https://www.mql5.com/es/articles/17249

    Прикрепленные файлы |
    От новичка до эксперта: Создание анимированного советника для новостей в MQL5 (V) — Система напоминаний о событиях От новичка до эксперта: Создание анимированного советника для новостей в MQL5 (V) — Система напоминаний о событиях
    В этом обсуждении мы рассмотрим дополнительные усовершенствования, поскольку интегрируем усовершенствованную логику оповещения о событиях в экономическом календаре, отображаемых советником «Заголовки новостей». Это усовершенствование имеет решающее значение — оно гарантирует, что пользователи будут получать своевременные уведомления за короткое время до ключевых предстоящих событий. Присоединяйтесь к этой дискуссии, чтобы узнать больше.
    От новичка до эксперта: Создание анимированного советника для новостей в MQL5 (IV) - Анализ рынка локально размещенными моделями с использованием ИИ От новичка до эксперта: Создание анимированного советника для новостей в MQL5 (IV) - Анализ рынка локально размещенными моделями с использованием ИИ
    В сегодняшнем обсуждении мы рассмотрим, как самостоятельно размещать модели искусственного интеллекта с открытым исходным кодом и использовать их для получения информации о рынке. Это является частью наших постоянных усилий по расширению советника «Заголовки новостей» путем внедрения раздела «Анализ искусственного интеллекта» (AI Insights), который превращает советник в мультиинтеграционный вспомогательный инструмент. Обновленный советник предназначен для информирования трейдеров о событиях календаря, последних финансовых новостях, технических индикаторах, а теперь и о перспективах рынка, генерируемых искусственным интеллектом, тем самым, предлагая своевременную, разнообразную и интеллектуальную поддержку при принятии торговых решений. Присоединяйтесь к разговору, в ходе которого мы рассмотрим практические стратегии интеграции и то, как MQL5 может взаимодействовать с внешними ресурсами для создания мощного и интеллектуального торгового рабочего терминала.
    Упрощаем торговлю на новостях (Часть 6): Совершаем сделки (III) Упрощаем торговлю на новостях (Часть 6): Совершаем сделки (III)
    В этой статье будет реализована сортировка новостей для отдельных новостных событий на основе их идентификаторов. Кроме того, предыдущие запросы SQL будут улучшены для предоставления дополнительной информации или сокращения времени выполнения запроса. Код, созданный в предыдущих статьях, станет работоспособным.
    Переходим на MQL5 Algo Forge (Часть 2): Работа с несколькими репозиториями Переходим на MQL5 Algo Forge (Часть 2): Работа с несколькими репозиториями
    Рассмотрим один из возможных подходов к организации хранения исходного кода проекта в публичном репозитории. Используя распределение по различным веткам, создадим удобные и понятные правила для развития проекта.