988 lines
80 KiB
MQL5
988 lines
80 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| RiskManagement.mqh |
|
|
//| Copyright 2025, Niquel Mendoza. |
|
|
//| https://www.mql5.com/es/users/nique_372/news |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2025, Niquel Mendoza."
|
|
#property link "https://www.mql5.com/es/users/nique_372/news"
|
|
#property strict
|
|
|
|
#ifndef RISK_MANAGEMENT_GEN_MQH
|
|
#define RISK_MANAGEMENT_GEN_MQH
|
|
|
|
#include "Modificators.mqh"
|
|
|
|
/*
|
|
ADVERTENCIA al momeneto de añadir items a esta clase, esta si son dinamicos las eliminara cuidado, como un modificador.
|
|
WARNING when adding items to this class, if they are dynamic, it will remove them carefully, as a modifier.
|
|
*/
|
|
|
|
//open_positions
|
|
//+-----------------------------------------------------------------+
|
|
//| Clase Base CRiskManagement |
|
|
//+------------------------------------------------------------------+
|
|
class CRiskManagemet : public CRiskManagemetBase
|
|
{
|
|
protected:
|
|
//---
|
|
CGetLote* get_lote;
|
|
|
|
//---
|
|
double chosen_balance;
|
|
|
|
//--- Extra Variables
|
|
ENUM_GET_LOT type_get_lot;
|
|
ulong last_deal_ticket; //The last deal ticket (it can be from a position opening deal or a position closing deal)
|
|
ulong last_position_ticket;
|
|
|
|
//-- main variables
|
|
long StopLoss; //StopLoss
|
|
double lote; //Last recorded lot
|
|
|
|
//--- variables to store the values of the maximum losses
|
|
double nmlpo; //last expected loss
|
|
bool is_init; //flag indicating if it is init
|
|
|
|
//--- variables that store percentages and enumeration, which will be used for the subsequent calculation of losses
|
|
CLossProfitManager* loss_profits_manager;
|
|
bool isCachedAddLossProfit[LOSS_PROFIT_COUNT];
|
|
|
|
//--- Modifiers
|
|
CManagerModificator modificators;
|
|
bool isCachedModifier[LOSS_PROFIT_COUNT];
|
|
|
|
//--- general function to assign values to loss variables
|
|
virtual double GetValorWithApplied(double percentage_, ENUM_TYPE_LOSS_PROFIT type, ENUM_APPLIED_PERCENTAGES applied) const override;
|
|
|
|
|
|
//--- Update profits function
|
|
virtual void UpdateProfit(const ROnOpenClosePosition &pos, bool actulizar_profits);
|
|
virtual void OnOpenTrade() { };
|
|
virtual void OnCloseTrade() { };
|
|
|
|
//---
|
|
virtual FuncionLossProfitSuperate SuperateModeToFunctionProfit(ENUM_TYPE_LOSS_PROFIT type, MODE_SUPERATE mode) const;
|
|
virtual FuncionLossProfitSuperate SuperateModeToFunctionLoss(ENUM_TYPE_LOSS_PROFIT type, MODE_SUPERATE mode) const;
|
|
|
|
|
|
|
|
public:
|
|
//--- Constructor and Destructor
|
|
CRiskManagemet(ulong _magic, ENUM_GET_LOT type_get_lot_);
|
|
~CRiskManagemet();
|
|
|
|
//--- Set
|
|
void EndAddProfitLoss();
|
|
virtual void SetGeneralParameters(MqlParam ¶ms[]) = 0;
|
|
void SetLote(CGetLote* lote_ptr);
|
|
inline void SetStopLoss(double dist_open_sl) { this.StopLoss = (long)ConvertPriceToPoints(_Symbol, dist_open_sl); }
|
|
inline void SetStopLoss(long _sl_point_) { this.StopLoss = _sl_point_; }
|
|
|
|
//--- Get
|
|
inline CLossProfit* operator[](const ENUM_TYPE_LOSS_PROFIT _type) { return loss_profits_manager[_type]; };
|
|
inline CLossProfit* operator[](const int index) { return loss_profits_manager[index]; };
|
|
|
|
//--- Add
|
|
void AddModificator(CExtraModifications* modificator); // Modificator pointer
|
|
void AddModificator(CExtraModifications &modificator); // Modificator instance by reference
|
|
void AddLoss(double _percentage, ENUM_APPLIED_PERCENTAGES _applied, ENUM_RISK_CALCULATION_MODE _mode, ENUM_TYPE_LOSS_PROFIT type, MODE_SUPERATE sop_mode = CLOSE_POSITION_AND_EQUITY,
|
|
bool is_dynamic = false, string percentages_to_activate = NULL, string risks_to_be_applied = NULL); // Loss
|
|
void AddProfit(double _percentage, ENUM_APPLIED_PERCENTAGES _applied, ENUM_RISK_CALCULATION_MODE _mode, ENUM_TYPE_LOSS_PROFIT type, bool is_strict_,
|
|
MODE_SUPERATE sop_mode = CLOSE_POSITION_AND_EQUITY, bool is_dynamic = false, string percentages_to_activate = NULL, string risks_to_be_applied = NULL); // Profit
|
|
|
|
//--- Functions inherited from CAccountGestor
|
|
void OnNewProfit(const ROnOpenClosePosition &profit) override;
|
|
virtual void OnOpenClosePosition(const ROnOpenClosePosition &pos) override;
|
|
void OnNewDay(datetime init_time) override;
|
|
void OnNewWeek(datetime init_time) override;
|
|
void OnNewMonth(datetime init_time) override;
|
|
void OnWithdrawalDeposit(double value) override;
|
|
|
|
//--- Get the lot using GMPLO and type_get_lot
|
|
double GetLote(ENUM_ORDER_TYPE type, double entry_price, ulong deviation, ulong stop_limit);
|
|
|
|
//--- Obtain the SL by risk per operation
|
|
long GetSL(ENUM_ORDER_TYPE type, double entry_price, ulong deviation, ulong stop_limit);
|
|
|
|
//--- Functions for working with the get lot size mod
|
|
void TypeGetLot(ENUM_GET_LOT new_get_mode) { this.type_get_lot = new_get_mode; }
|
|
inline ENUM_GET_LOT TypeGetLot() const { return this.type_get_lot; }
|
|
|
|
//--- Magic number
|
|
inline ulong MagicNumber() const { return this.magic_number; }
|
|
|
|
|
|
//--- Risk management type
|
|
virtual inline ENUM_MODE_RISK_MANAGEMENT ModeRiskManagement() const = 0;
|
|
};
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CRiskManagemet::CRiskManagemet(ulong _magic, ENUM_GET_LOT type_get_lot_)
|
|
: CRiskManagemetBase(_magic)
|
|
{
|
|
this.chosen_balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
|
|
MathSrand(GetTickCount());
|
|
|
|
vector ramdom = vector::Random(1, -100, 100);
|
|
c = (int8_t)ramdom[0];
|
|
|
|
for(int i = 0 ; i < LOSS_PROFIT_COUNT; i++)
|
|
{
|
|
isCachedAddLossProfit[i] = false;
|
|
isCachedModifier[i] = false;
|
|
}
|
|
|
|
this.is_init = false;
|
|
|
|
//---
|
|
this.last_position_ticket = INVALID_TICKET;
|
|
this.last_deal_ticket = INVALID_TICKET;
|
|
|
|
//---
|
|
this.type_get_lot = type_get_lot_;
|
|
loss_profits_manager = new CLossProfitManager();
|
|
|
|
//---
|
|
AddLogger(loss_profits_manager);
|
|
AddLogger(modificators);
|
|
|
|
//---
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong position_ticket = PositionGetTicket(i);
|
|
if(!PositionSelectByTicket(position_ticket))
|
|
continue;
|
|
|
|
ulong position_magic = PositionGetInteger(POSITION_MAGIC);
|
|
ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
if(position_magic == _magic || _magic == NOT_MAGIC_NUMBER)
|
|
{
|
|
Position new_pos;
|
|
new_pos.type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
new_pos.ticket = position_ticket;
|
|
new_pos.profit = GetTotalPositionProfitNoCurrent(new_pos.ticket);
|
|
new_pos.magic = _magic;
|
|
AddArrayNoVerification2(open_positions, new_pos, RISK_MANAGEMENT_RESERVE_POS)
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
CRiskManagemet::~CRiskManagemet()
|
|
{
|
|
CleanItems("RiskManagement");
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
FuncionLossProfitSuperate CRiskManagemet::SuperateModeToFunctionProfit(ENUM_TYPE_LOSS_PROFIT type, MODE_SUPERATE mode) const
|
|
{
|
|
const static FuncionLossProfitSuperate functions[3] = {RiskMNormal_IsSuperatedProfit_Equity, RiskMNormal_IsSuperatedProfit_Close, RiskMNormal_IsSuperatedProfit_CloseAndEquity};
|
|
return functions[int(mode)];
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
FuncionLossProfitSuperate CRiskManagemet::SuperateModeToFunctionLoss(ENUM_TYPE_LOSS_PROFIT type, MODE_SUPERATE mode) const
|
|
{
|
|
const static FuncionLossProfitSuperate functions[3] = {RiskMNormal_IsSuperatedLoss_Equity, RiskMNormal_IsSuperatedLoss_Close, RiskMNormal_IsSuperatedLoss_CloseAndEquity};
|
|
return functions[int(mode)];
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| SetLote function |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManagemet::SetLote(CGetLote *lote_ptr)
|
|
{
|
|
if(CheckPointer(lote_ptr) == POINTER_INVALID)
|
|
{
|
|
LogError("El puntero a CGetlote* es invalido", FUNCION_ACTUAL);
|
|
return;
|
|
}
|
|
|
|
//---
|
|
if(CheckPointer(get_lote)) // is not equal to 0 (POINTER_INVALID)
|
|
{
|
|
if(CheckPointer(get_lote) == POINTER_DYNAMIC)
|
|
delete get_lote;
|
|
|
|
RemoveLogger(get_lote);
|
|
AddLogger(lote_ptr);
|
|
this.get_lote = lote_ptr;
|
|
return;
|
|
}
|
|
else //Si es null
|
|
{
|
|
AddLogger(lote_ptr);
|
|
this.get_lote = lote_ptr;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Function that will be executed once all the |
|
|
//| Maximum profit and loss have been added |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManagemet::EndAddProfitLoss()
|
|
{
|
|
if(is_init)
|
|
return;
|
|
|
|
LogInfo(StringFormat("The total size between maximum profit and loss is: %d", loss_profits_manager.SizeValids()), FUNCION_ACTUAL);
|
|
loss_profits_manager.SetValues();
|
|
this.is_init = true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Function to add a maximum loss |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManagemet::AddLoss(double _percentage, ENUM_APPLIED_PERCENTAGES _applied, ENUM_RISK_CALCULATION_MODE _mode, ENUM_TYPE_LOSS_PROFIT type, MODE_SUPERATE sop_mode = CLOSE_POSITION_AND_EQUITY,
|
|
bool is_dynamic = false, string percentages_to_activate = NULL, string risks_to_be_applied = NULL)
|
|
{
|
|
//---
|
|
if(_percentage <= 0.00)
|
|
{
|
|
LogWarning(StringFormat("Invalid percentage %.2f for lost profit %s, will not be added to the main array", _percentage, EnumToString(type)), FUNCION_ACTUAL);
|
|
return;
|
|
}
|
|
|
|
//---
|
|
if(_percentage > 100.00 && _mode == percentage)
|
|
{
|
|
LogWarning(StringFormat("Percentage %.2f for %s exceeds 100%%, limiting to 100%%", _percentage, EnumToString(type)), FUNCION_ACTUAL);
|
|
_percentage = 100.00;
|
|
}
|
|
|
|
//---
|
|
const int index = int(type);
|
|
if(index < 3)
|
|
{
|
|
LogError(StringFormat("Invalid maximum loss type: %s (index: %d)", EnumToString(type), index), FUNCION_ACTUAL);
|
|
return;
|
|
}
|
|
|
|
//---
|
|
if(isCachedAddLossProfit[index])
|
|
{
|
|
LogWarning(StringFormat("Maximum loss %s already exists, ignoring duplicate", EnumToString(type)), FUNCION_ACTUAL);
|
|
return;
|
|
}
|
|
|
|
CLossProfit* new_loss;
|
|
new_loss = new CLossProfit(false, dynamic_cast<CRiskManagemetBase*>(&this));
|
|
|
|
|
|
//---
|
|
new_loss.SetProperties(_percentage, _applied, _mode, type, SuperateModeToFunctionLoss(type, sop_mode));
|
|
loss_profits_manager.Add(new_loss);
|
|
|
|
if(is_dynamic == true && percentages_to_activate != NULL && risks_to_be_applied != NULL)
|
|
{
|
|
if(!isCachedModifier[index])
|
|
{
|
|
new_loss.SetDynamic(percentages_to_activate, risks_to_be_applied, this.chosen_balance);
|
|
LogInfo(StringFormat("Dynamic maximum loss %s successfully configured", EnumToString(type)), FUNCION_ACTUAL);
|
|
}
|
|
else
|
|
{
|
|
LogError(StringFormat("Maximum loss %s already has an active modifier", EnumToString(type)), FUNCION_ACTUAL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogInfo(StringFormat("Maximum loss %s added: %.2f%%", EnumToString(type), _percentage), FUNCION_ACTUAL);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Function to add a maximum gain |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManagemet::AddProfit(double _percentage, ENUM_APPLIED_PERCENTAGES _applied, ENUM_RISK_CALCULATION_MODE _mode, ENUM_TYPE_LOSS_PROFIT type, bool is_strict_,
|
|
MODE_SUPERATE sop_mode = CLOSE_POSITION_AND_EQUITY, bool is_dynamic = false, string percentages_to_activate = NULL, string risks_to_be_applied = NULL)
|
|
{
|
|
//---
|
|
if(_percentage <= 0.00)
|
|
{
|
|
LogWarning(StringFormat("Invalid percentage %.2f for maximum gain %s, will not be added to the main array", _percentage, EnumToString(type)), FUNCION_ACTUAL);
|
|
return;
|
|
}
|
|
|
|
//---
|
|
if(_percentage > 100.00 && _mode == percentage)
|
|
{
|
|
LogWarning(StringFormat("Percentage %.2f for %s exceeds 100%%, limiting to 100%%", _percentage, EnumToString(type)), FUNCION_ACTUAL);
|
|
_percentage = 100.00;
|
|
}
|
|
|
|
//---
|
|
const int index = int(type);
|
|
|
|
if(index >= 3)
|
|
{
|
|
LogError(StringFormat("Invalid maximum gain type: %s (index: %d)", EnumToString(type), index), FUNCION_ACTUAL);
|
|
return;
|
|
}
|
|
|
|
//---
|
|
if(isCachedAddLossProfit[index])
|
|
{
|
|
LogWarning(StringFormat("Maximum gain %s already exists, ignoring duplicate", EnumToString(type)), FUNCION_ACTUAL);
|
|
return;
|
|
}
|
|
|
|
CLossProfit* new_profit;
|
|
new_profit = new CLossProfit(false, dynamic_cast<CRiskManagemetBase*>(&this));
|
|
|
|
|
|
//---
|
|
new_profit.SetProperties(_percentage, _applied, _mode, type, SuperateModeToFunctionProfit(type, sop_mode), is_strict_);
|
|
loss_profits_manager.Add(new_profit);
|
|
|
|
if(is_dynamic == true && percentages_to_activate != NULL && risks_to_be_applied != NULL)
|
|
{
|
|
if(!isCachedModifier[index])
|
|
{
|
|
new_profit.SetDynamic(percentages_to_activate, risks_to_be_applied, this.chosen_balance);
|
|
LogInfo(StringFormat("Maximum dynamic gain %s successfully set", EnumToString(type)), FUNCION_ACTUAL);
|
|
}
|
|
else
|
|
{
|
|
LogError(StringFormat("Maximum gain %s already has an active modifier", EnumToString(type)), FUNCION_ACTUAL);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogInfo(StringFormat("Maximum gain %s added: %.2f%%", EnumToString(type), _percentage), FUNCION_ACTUAL);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Function to add a risk modifier |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManagemet::AddModificator(CExtraModifications *modificator)
|
|
{
|
|
//--- Check pointer
|
|
if(!CheckPointer(modificator))
|
|
{
|
|
LogError("The risk modifier is null-invalid", FUNCION_ACTUAL);
|
|
return;
|
|
}
|
|
|
|
//--- Property to be modified is invalid ?
|
|
const ENUM_TYPE_LOSS_PROFIT loss_or_profit = modificator.MaximumProfitOrLossAModify();
|
|
if(loss_or_profit == WRONG_VALUE)
|
|
{
|
|
LogError("The risk modifier does not modify any risk.", FUNCION_ACTUAL);
|
|
return;
|
|
}
|
|
|
|
//--- Property to modify is "empty" ?
|
|
if(loss_profits_manager[loss_or_profit].IsEmpty())
|
|
{
|
|
LogError(StringFormat("The property to be modified %s is not defined", EnumToString(loss_or_profit)), FUNCION_ACTUAL);
|
|
return;
|
|
}
|
|
|
|
//--- Is the loss_or_profit already dynamic ?
|
|
if(loss_profits_manager[loss_or_profit].IsDynamicMode())
|
|
{
|
|
LogError(StringFormat("The property to be modified %s is already dynamic", EnumToString(loss_or_profit)), FUNCION_ACTUAL);
|
|
return;
|
|
}
|
|
|
|
//---
|
|
const int index = int(loss_or_profit);
|
|
|
|
if(isCachedModifier[index])
|
|
{
|
|
LogWarning(StringFormat("The property to be modified %s is already being modified", EnumToString(loss_or_profit)), FUNCION_ACTUAL);
|
|
return;
|
|
}
|
|
|
|
//---
|
|
modificator.SetPointer(loss_profits_manager[loss_or_profit]);
|
|
ModfierInitInfo init;
|
|
init.balance = this.chosen_balance;
|
|
init.magic = this.magic_number;
|
|
modificator.OnInitModifier(init);
|
|
|
|
LogCaution(StringFormat("The modifier '%s' is being added\nThis will modify the property '%s'", modificator.Name(), loss_profits_manager[loss_or_profit].Name()), FUNCION_ACTUAL);
|
|
|
|
//--- Add
|
|
modificators.AddItemFast(modificator); // We use the fast version (we already did all the checkpointer checks etc.)
|
|
|
|
//---
|
|
isCachedModifier[index] = true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Function to add a risk modifier |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManagemet::AddModificator(CExtraModifications & modificator)
|
|
{
|
|
AddModificator(&modificator);
|
|
}
|
|
|
|
//+----------------------------------------------------------------------------------+
|
|
//| Get the ideal stop loss based on a specified lot and the maximum loss per trade |
|
|
//+----------------------------------------------------------------------------------+
|
|
long CRiskManagemet::GetSL(ENUM_ORDER_TYPE type, double entry_price, ulong deviation, ulong stop_limit)
|
|
{
|
|
if(loss_profits_manager[LP_GMLPO].IsEmpty())
|
|
{
|
|
LogCriticalError("GMLPO not configured: Stop Loss cannot be calculated", FUNCION_ACTUAL);
|
|
Remover();
|
|
return 0;
|
|
}
|
|
|
|
double lot;
|
|
long sl = get_lote.CalculateSL(type, loss_profits_manager[LP_GMLPO].GetValue(), entry_price, lot, deviation, stop_limit);
|
|
//If there is an error or the SL is invalid, it is printed in CGetLote
|
|
return sl;
|
|
}
|
|
|
|
//+-----------------------------------------------------------------------------------------------+
|
|
//| Function to obtain the ideal lot based on the maximum loss per operation and the stop loss |
|
|
//+-----------------------------------------------------------------------------------------------+
|
|
double CRiskManagemet::GetLote(ENUM_ORDER_TYPE type, double entry_price, ulong deviation, ulong stop_limit)
|
|
{
|
|
if(loss_profits_manager[LP_GMLPO].IsEmpty())
|
|
{
|
|
LogCriticalError("GMLPO not configured: unable to calculate lot size", FUNCION_ACTUAL);
|
|
Remover();
|
|
this.lote = 0.00;
|
|
return this.lote;
|
|
}
|
|
if(this.type_get_lot == GET_LOT_BY_STOPLOSS_AND_RISK_PER_OPERATION)
|
|
{
|
|
double MaxLote = get_lote.GetMaxLote(type, entry_price, deviation, stop_limit); // We do not check max batch data that is done in GetLote... of CGetLote
|
|
this.lote = get_lote.GetLoteByRiskPerOperationAndSL(MaxLote, loss_profits_manager[LP_GMLPO].GetValue(), this.nmlpo, this.StopLoss);
|
|
}
|
|
else
|
|
this.lote = get_lote.GetLoteByRiskPerOperation(type, loss_profits_manager[LP_GMLPO].GetValue(), entry_price, deviation, stop_limit); // If it is less than the minimum batch, a message is printed in CGetLote
|
|
|
|
return this.lote;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Function to check if the maximum profit per day was exceeded |
|
|
//+------------------------------------------------------------------+
|
|
inline bool RiskMNormal_IsSuperatedProfit_Close(double profit_, double value_, bool strict, double extra_value)
|
|
{
|
|
return (profit_ > value_);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
inline bool RiskMNormal_IsSuperatedProfit_CloseAndEquity(double profit_, double value_, bool strict, double extra_value)
|
|
{
|
|
double new_mdp = profit_ > 0 ? value_ - profit_ : (!strict ? value_ : value_ + (profit_ * -1));
|
|
return (account_status_curr_profit > new_mdp);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
inline bool RiskMNormal_IsSuperatedProfit_Equity(double profit_, double value_, bool strict, double extra_value)
|
|
{
|
|
return (account_status_curr_profit > value_);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Boolean function to check if a loss was overcome |
|
|
//+------------------------------------------------------------------+
|
|
inline bool RiskMNormal_IsSuperatedLoss_Close(double profit_, double value_, bool strict, double extra_value)
|
|
{
|
|
return (profit_ * -1 > value_);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
inline bool RiskMNormal_IsSuperatedLoss_CloseAndEquity(double profit_, double value_, bool strict, double extra_value)
|
|
{
|
|
double new_loss = profit_ < 0 ? value_ - fabs(profit_) : value_;
|
|
return (account_status_curr_profit * -1 > new_loss);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
inline bool RiskMNormal_IsSuperatedLoss_Equity(double profit_, double value_, bool strict, double extra_value)
|
|
{
|
|
return (account_status_curr_profit * -1 > value_);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Function to update profits |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManagemet::UpdateProfit(const ROnOpenClosePosition &pos, bool actulizar_profits)
|
|
{
|
|
//--- Update profits
|
|
if(actulizar_profits)
|
|
{
|
|
this.monthly_profit += pos.position.profit;
|
|
this.daily_profit += pos.position.profit;
|
|
this.weekly_profit += pos.position.profit;
|
|
this.gross_profit += pos.position.profit;
|
|
|
|
if(IsInfoLogEnabled())
|
|
{
|
|
FastLog(FUNCION_ACTUAL, INFO_TEXT, "New profits:");
|
|
Print("Daily Profit: ", this.daily_profit);
|
|
Print("Gross profit: ", this.gross_profit);
|
|
Print("Weekly Profit: ", this.weekly_profit);
|
|
Print("Monthly Profit: ", this.monthly_profit);
|
|
}
|
|
}
|
|
|
|
//---
|
|
this.account_profit = pos.account_profit_total; // Set Account total profit
|
|
|
|
//---
|
|
loss_profits_manager[LP_MDL].SetProfit(pos.account_profit_diario, this.c);
|
|
loss_profits_manager[LP_MDP].SetProfit(pos.account_profit_diario, this.c);
|
|
loss_profits_manager[LP_MWL].SetProfit(pos.account_profit_semanal, this.c);
|
|
loss_profits_manager[LP_MWP].SetProfit(pos.account_profit_semanal, this.c);
|
|
loss_profits_manager[LP_MML].SetProfit(pos.account_profit_mensual, this.c);
|
|
loss_profits_manager[LP_MMP].SetProfit(pos.account_profit_mensual, this.c);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Initial profit update |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManagemet::OnNewProfit(const ROnOpenClosePosition &profit)
|
|
{
|
|
//--- Update profits (SetProfit checks if it's empty, no need to do it here)
|
|
loss_profits_manager[LP_MDL].SetProfit(profit.account_profit_diario, this.c);
|
|
loss_profits_manager[LP_MDP].SetProfit(profit.account_profit_diario, this.c);
|
|
loss_profits_manager[LP_MWL].SetProfit(profit.account_profit_semanal, this.c);
|
|
loss_profits_manager[LP_MWP].SetProfit(profit.account_profit_semanal, this.c);
|
|
loss_profits_manager[LP_MML].SetProfit(profit.account_profit_mensual, this.c);
|
|
loss_profits_manager[LP_MMP].SetProfit(profit.account_profit_mensual, this.c);
|
|
loss_profits_manager[LP_ML].SetProfit(profit.account_profit_total, this.c);
|
|
|
|
//---Dates
|
|
this.last_day_time = iTime(_Symbol, PERIOD_D1, 0);
|
|
this.last_weekly_time = iTime(_Symbol, PERIOD_W1, 0);
|
|
this.last_monthly_time = iTime(_Symbol, PERIOD_MN1, 0);
|
|
this.init_trade_time = this.magic_number != NOT_MAGIC_NUMBER ? TimeCurrent() : D'1972.01.01 00:00';
|
|
|
|
//--- Set internal profits
|
|
this.account_profit = GetNetProfitSince(true, this.magic_number, D'1972.01.01 00:00');
|
|
this.daily_profit = GetNetProfitSince((this.magic_number == NOT_MAGIC_NUMBER ? true : false), this.magic_number, this.last_day_time);
|
|
this.weekly_profit = GetNetProfitSince((this.magic_number == NOT_MAGIC_NUMBER ? true : false), this.magic_number, this.last_weekly_time);
|
|
this.gross_profit = GetNetProfitSince((this.magic_number == NOT_MAGIC_NUMBER ? true : false), this.magic_number, this.init_trade_time);
|
|
this.monthly_profit = GetNetProfitSince((this.magic_number == NOT_MAGIC_NUMBER ? true : false), this.magic_number, this.last_monthly_time);
|
|
|
|
//--- Info
|
|
if(IsInfoLogEnabled())
|
|
{
|
|
FastLog(FUNCION_ACTUAL, INFO_TEXT, "New profits:");
|
|
Print("Daily Profit: ", this.daily_profit);
|
|
Print("Gross profit: ", this.gross_profit);
|
|
Print("Weekly Profit: ", this.weekly_profit);
|
|
Print("Monthly Profit: ", this.monthly_profit);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| OnTradeTransaction Event |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManagemet::OnOpenClosePosition(const ROnOpenClosePosition &pos)
|
|
{
|
|
if(pos.deal_entry_type == DEAL_ENTRY_IN && (this.magic_number == pos.position.magic || this.magic_number == NOT_MAGIC_NUMBER))
|
|
{
|
|
LogInfo(StringFormat("Position %I64u added to internal tickets", pos.position.ticket), FUNCION_ACTUAL);
|
|
|
|
//--- Assign tickets so child classes have access to them
|
|
this.last_deal_ticket = pos.deal_ticket;
|
|
this.last_position_ticket = pos.position.ticket;
|
|
|
|
//--- Activate the flag
|
|
this.positions_open = true;
|
|
|
|
//--- Execute the virtual function
|
|
OnOpenTrade();
|
|
|
|
//--- If there are modifiers then execute the OnOpenPosition function
|
|
if(modificators.Size() > 0)
|
|
{
|
|
ModifierOnOpenCloseStruct new_mod;
|
|
|
|
// Position
|
|
new_mod.position = pos.position;
|
|
|
|
// Profits
|
|
new_mod.profit_diario = this.daily_profit;
|
|
new_mod.profit_mensual = this.monthly_profit;
|
|
new_mod.profit_semanal = this.weekly_profit;
|
|
new_mod.profit_total = this.gross_profit;
|
|
|
|
// Deal
|
|
new_mod.deal_profit = pos.deal_profit;
|
|
new_mod.deal_ticket = pos.deal_ticket;
|
|
new_mod.deal_entry_type = pos.deal_entry_type;
|
|
new_mod.deal_reason = pos.deal_reason;
|
|
modificators.OnOpenPosition(new_mod);
|
|
}
|
|
//--- Add the operation to positions opened by the Magic
|
|
AddArrayNoVerification2(open_positions, pos.position, RISK_MANAGEMENT_RESERVE_POS);
|
|
return;
|
|
}
|
|
|
|
if(pos.deal_entry_type != DEAL_ENTRY_OUT)
|
|
return;
|
|
|
|
if(RemoveIndexFromAnArrayOfPositions(this.open_positions, pos.position.ticket, RISK_MANAGEMENT_RESERVE_POS))
|
|
{
|
|
//--- Logs
|
|
LogCaution(StringFormat("Position with ticket %I64u has been closed", pos.position.ticket), FUNCION_ACTUAL);
|
|
LogInfo(StringFormat("Total number of positions for magic number %u is: %d", this.magic_number, this.GetPositionsTotal()), FUNCION_ACTUAL);
|
|
|
|
//--- Set the last tickets
|
|
this.last_deal_ticket = pos.deal_ticket;
|
|
this.last_position_ticket = pos.position.ticket;
|
|
|
|
//--- Update internal profits
|
|
UpdateProfit(pos, true);
|
|
|
|
//--- Update risks (dynamic)
|
|
loss_profits_manager.CheckDynamic();
|
|
|
|
//--- If there are no more positions, deactivate the flag
|
|
if(GetPositionsTotal() == 0)
|
|
this.positions_open = false;
|
|
|
|
//--- Execute the function that runs every time a position closes
|
|
OnCloseTrade();
|
|
|
|
//--- If there are modifiers, execute OnOpenClosePosition
|
|
if(modificators.Size() > 0)
|
|
{
|
|
ModifierOnOpenCloseStruct new_mod;
|
|
|
|
// Profit
|
|
new_mod.profit_diario = this.daily_profit;
|
|
new_mod.profit_mensual = this.monthly_profit;
|
|
new_mod.profit_semanal = this.weekly_profit;
|
|
new_mod.profit_total = this.gross_profit;
|
|
|
|
// Position
|
|
new_mod.position = pos.position;
|
|
|
|
// Deal
|
|
new_mod.deal_profit = pos.deal_profit;
|
|
new_mod.deal_ticket = pos.deal_ticket;
|
|
new_mod.deal_entry_type = pos.deal_entry_type;
|
|
new_mod.deal_reason = pos.deal_reason;
|
|
modificators.OnClosePosition(new_mod);
|
|
}
|
|
|
|
//--- Set the new GMLPO value
|
|
loss_profits_manager[LP_GMLPO].Set(); // Empty check is done internally
|
|
}
|
|
else // In case a position closes but it's not from the current bot, we need to update profits (Possibly it was closed by the trader or another bot)
|
|
{
|
|
//--- Udpate profit
|
|
UpdateProfit(pos, false);
|
|
|
|
//--- Set GMLPO
|
|
loss_profits_manager[LP_GMLPO].Set(); // Empty check is done internally
|
|
}
|
|
|
|
// Partials are not necessary since we did that to ensure trans.position profit also counts the partial close, the other class already does that.
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Function that runs every new day |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManagemet::OnNewDay(datetime init_time)
|
|
{
|
|
this.daily_profit = 0.00;
|
|
this.last_day_time = init_time;
|
|
modificators.OnNewDay();
|
|
|
|
//--- MDL
|
|
if(loss_profits_manager[LP_MDL].Set()) // If the value could be set (means it is not empty)
|
|
loss_profits_manager[LP_MDL].SetProfit(0.00, this.c); // Reset profit
|
|
|
|
//--- MDP
|
|
if(loss_profits_manager[LP_MDP].Set()) // If the value could be set (means it is not empty)
|
|
loss_profits_manager[LP_MDP].SetProfit(0.00, this.c); // Reset profit
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Function that runs every new week |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManagemet::OnNewWeek(datetime init_time)
|
|
{
|
|
this.last_weekly_time = init_time;
|
|
this.weekly_profit = 0.00;
|
|
|
|
//--- MWL
|
|
if(loss_profits_manager[LP_MWL].Set()) // If the value could be set (means it is not empty)
|
|
loss_profits_manager[LP_MWL].SetProfit(0.00, this.c); // Reset profit
|
|
|
|
//--- MWP
|
|
if(loss_profits_manager[LP_MWP].Set()) // If the value could be set (means it is not empty)
|
|
loss_profits_manager[LP_MWP].SetProfit(0.00, this.c); // Reset profit
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Function that runs every new month |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManagemet::OnNewMonth(datetime init_time)
|
|
{
|
|
this.last_monthly_time = init_time;
|
|
this.monthly_profit = 0.00;
|
|
|
|
//--- MML
|
|
if(loss_profits_manager[LP_MML].Set()) // If the value could be set (means it is not empty)
|
|
loss_profits_manager[LP_MML].SetProfit(0.00, this.c); // Reset profit
|
|
|
|
//--- MMP
|
|
if(loss_profits_manager[LP_MMP].Set()) // If the value could be set (means it is not empty)
|
|
loss_profits_manager[LP_MMP].SetProfit(0.00, this.c); // Reset profit
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Function to obtain the value of a loss or gain based |
|
|
//| on the applied value (balance, free margin, equity, net profit) |
|
|
//+------------------------------------------------------------------+
|
|
double CRiskManagemet::GetValorWithApplied(double percentage_, ENUM_TYPE_LOSS_PROFIT type, ENUM_APPLIED_PERCENTAGES applied) const
|
|
{
|
|
switch(applied)
|
|
{
|
|
case Balance:
|
|
return NormalizeDouble((percentage_ / 100.0) * AccountInfoDouble(ACCOUNT_BALANCE), 2);
|
|
case ganancianeta:
|
|
{
|
|
if(this.account_profit <= 0)
|
|
{
|
|
LogError(StringFormat("The total profit of the account which is %+.2f is invalid or negative", this.account_profit), FUNCION_ACTUAL);
|
|
return 0;
|
|
}
|
|
else
|
|
return NormalizeDouble((percentage_ / 100.0) * this.account_profit, 2);
|
|
}
|
|
case free_margin:
|
|
{
|
|
if(AccountInfoDouble(ACCOUNT_MARGIN_FREE) <= 0)
|
|
{
|
|
LogError(StringFormat("Free margin of %+.2f is invalid", AccountInfoDouble(ACCOUNT_MARGIN_FREE)), FUNCION_ACTUAL);
|
|
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:
|
|
LogFatalError(StringFormat("It was not found that: %s be part of the allowed enumeration", EnumToString(applied)), FUNCION_ACTUAL);
|
|
}
|
|
//---
|
|
return 0.00;
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
//--- Define dangerous, if disabled it could increase the risk strongly, for safety reasons do not comment on it
|
|
#define RISK_MANAGEMENT_UPDATE_CHOSEN_BALANCE_DEPOSIT_WITDRAWAL
|
|
|
|
//---
|
|
void CRiskManagemet::OnWithdrawalDeposit(double value) override
|
|
{
|
|
// Function that is executed every time a device or profit occurs
|
|
//--- Uodate
|
|
#ifdef RISK_MANAGEMENT_UPDATE_CHOSEN_BALANCE_DEPOSIT_WITDRAWAL
|
|
this.chosen_balance += value;
|
|
loss_profits_manager.SetNewChossenBalanceForDynamics(chosen_balance, true);
|
|
#else
|
|
loss_profits_manager.SetValuesAndDynamic();
|
|
#endif
|
|
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Clase de gestion de riesgo normal |
|
|
//+------------------------------------------------------------------+
|
|
class CRiskManagemetPersonal : public CRiskManagemet
|
|
{
|
|
public:
|
|
CRiskManagemetPersonal(ulong _magic, ENUM_GET_LOT _get)
|
|
: CRiskManagemet(_magic, _get) { }
|
|
|
|
inline ENUM_MODE_RISK_MANAGEMENT ModeRiskManagement() const override final { return risk_mode_personal_account; }
|
|
void SetGeneralParameters(MqlParam ¶ms[]) override { } // No extra parameters required
|
|
};
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Clase de gestion de riesgo para propfirms que usen riesgo |
|
|
//| Diario dinamico |
|
|
//+------------------------------------------------------------------+
|
|
//---
|
|
class CRiskManagemetPropFirm : public CRiskManagemet
|
|
{
|
|
private:
|
|
double account_balance_propfirm; // Balance incial del challenge
|
|
bool m_update_loss; // Bandera que indica si se aumentara el MDL
|
|
|
|
void UpdateDailyLossFTMO();
|
|
double GetValorWithApplied(double percentage_, ENUM_TYPE_LOSS_PROFIT type, ENUM_APPLIED_PERCENTAGES applied) const override;
|
|
FuncionLossProfitSuperate SuperateModeToFunctionLoss(ENUM_TYPE_LOSS_PROFIT type, MODE_SUPERATE mode) const override;
|
|
void OnCloseTrade() override;
|
|
|
|
public:
|
|
CRiskManagemetPropFirm(ulong _magic, ENUM_GET_LOT type_get_lot_)
|
|
: CRiskManagemet(_magic, type_get_lot_), m_update_loss(true) { }
|
|
|
|
|
|
//--- General
|
|
double GetExtraValueForLossProfit() override { return account_balance_propfirm; }
|
|
void SetGeneralParameters(MqlParam ¶ms[]) override;
|
|
inline double GetAccountBalancePropFirm() const { return account_balance_propfirm; }
|
|
|
|
//--- Actulizacion del MDL
|
|
inline bool UpdateLoss() const { return m_update_loss; }
|
|
void UpdateLoss(bool new_val) { m_update_loss = new_val;}
|
|
|
|
//---
|
|
inline ENUM_MODE_RISK_MANAGEMENT ModeRiskManagement() const override final { return risk_mode_propfirm_dynamic_daiy_loss; }
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
inline bool RiskMPropFirmFN_FTMO_IsSuperatedLoss(double profit_, double value_, bool strict, double extra_value)
|
|
{
|
|
return AccountInfoDouble(ACCOUNT_EQUITY) < (extra_value - value_);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
FuncionLossProfitSuperate CRiskManagemetPropFirm::SuperateModeToFunctionLoss(ENUM_TYPE_LOSS_PROFIT type, MODE_SUPERATE mode) const
|
|
{
|
|
if(type == LP_ML)
|
|
return RiskMPropFirmFN_FTMO_IsSuperatedLoss;
|
|
|
|
return CRiskManagemet::SuperateModeToFunctionLoss(type, mode);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManagemetPropFirm::OnCloseTrade(void)
|
|
{
|
|
if(m_update_loss)
|
|
UpdateDailyLossFTMO();
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManagemetPropFirm::SetGeneralParameters(MqlParam & params[]) override
|
|
{
|
|
account_balance_propfirm = params[0].double_value;
|
|
this.chosen_balance = account_balance_propfirm;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManagemetPropFirm::UpdateDailyLossFTMO()
|
|
{
|
|
double prev = loss_profits_manager[LP_MDL].GetValue();
|
|
prev += this.daily_profit > 0.00 ? this.daily_profit : 0;
|
|
loss_profits_manager[LP_MDL].SetValue(prev, this.c);
|
|
LogCaution(StringFormat("The maximum loss per operation has been modified, its new value: %.2f", loss_profits_manager[LP_MDL].GetValue()), FUNCION_ACTUAL);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
double CRiskManagemetPropFirm::GetValorWithApplied(double percentage_, ENUM_TYPE_LOSS_PROFIT type, ENUM_APPLIED_PERCENTAGES applied) const override
|
|
{
|
|
if(type == LP_ML || type == LP_MDL)
|
|
return account_balance_propfirm * (percentage_ / 100.0);
|
|
|
|
return CRiskManagemet::GetValorWithApplied(percentage_, type, applied);
|
|
//---
|
|
return 0.00;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Clase manager para poder obtener un puntero |
|
|
//| En base al tipo de gestion de riesgo |
|
|
//+------------------------------------------------------------------+
|
|
class CRiskPointer
|
|
{
|
|
private:
|
|
RiskParams parametros[];
|
|
ulong magic;
|
|
ENUM_GET_LOT type_get_lot;
|
|
|
|
public:
|
|
CRiskPointer(ulong _magic, ENUM_GET_LOT _Get);
|
|
void SetPropirm(double account_balance_propfirm);
|
|
CRiskManagemet* GetRiskPointer(ENUM_MODE_RISK_MANAGEMENT mode);
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Obtener el puntero de CRiskManagement |
|
|
//+------------------------------------------------------------------+
|
|
CRiskManagemet* CRiskPointer::GetRiskPointer(ENUM_MODE_RISK_MANAGEMENT mode)
|
|
{
|
|
CRiskManagemet *new_risk = NULL;
|
|
|
|
//---
|
|
switch(mode)
|
|
{
|
|
case risk_mode_personal_account:
|
|
{
|
|
new_risk = new CRiskManagemetPersonal(this.magic, this.type_get_lot);
|
|
break;
|
|
}
|
|
case risk_mode_propfirm_dynamic_daiy_loss:
|
|
{
|
|
new_risk = new CRiskManagemetPropFirm(this.magic, this.type_get_lot);
|
|
|
|
if(ArraySize(parametros[1].params) < 1)
|
|
{
|
|
Print(__FUNCTION__, ": Critical error | The parameter array for prop firm mode has not been populated.");
|
|
Remover();
|
|
break;
|
|
}
|
|
|
|
new_risk.SetGeneralParameters(parametros[1].params);
|
|
break;
|
|
}
|
|
default:
|
|
Print(__FUNCTION__, ": Critical error | The type: ", EnumToString(mode), " It is invalid");
|
|
break;
|
|
}
|
|
|
|
//---
|
|
if(CheckPointer(new_risk) == POINTER_INVALID)
|
|
{
|
|
Print(__FUNCTION__, ": Critical error | The pointer to CRiskManagement* is invalid");
|
|
return NULL;
|
|
}
|
|
|
|
//---
|
|
return new_risk;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CRiskPointer::CRiskPointer(ulong _magic, ENUM_GET_LOT _Get)
|
|
{
|
|
this.magic = _magic;
|
|
this.type_get_lot = _Get;
|
|
|
|
ArrayResize(parametros, 2);
|
|
|
|
parametros[0].mode = risk_mode_personal_account;
|
|
parametros[1].mode = risk_mode_propfirm_dynamic_daiy_loss;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Para setear el modo de gestion de riesgo propfirm |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskPointer::SetPropirm(double account_balance_propfirm)
|
|
{
|
|
ArrayFree(parametros[1].params);
|
|
ArrayResize(parametros[1].params, 2);
|
|
parametros[1].params[0].double_value = account_balance_propfirm;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
#endif
|
|
//+------------------------------------------------------------------+
|