Gerenciamento de riscos (Parte 3): Criação da classe principal de gerenciamento de riscos
- Introdução
- Planejamento e concepção do sistema de gerenciamento de riscos
- Definição de constantes, enumerações e estruturas
- Declaração de variáveis-chave para a gerenciamento de riscos
- Criação do construtor, destrutor e métodos de inicialização
- Métodos para definição de valores de lucro e prejuízo
- Cálculo do volume do lote e do stop-loss com base no risco por operação
- Funções para obtenção de valores de lucro, prejuízo e nível de risco
- Eventos disparados no início de um novo dia e de uma nova semana
- Considerações finais
Introdução
Damos continuidade à nossa série de artigos dedicada ao desenvolvimento de um sistema de gerenciamento de riscos. No artigo anterior criamos a interface, aplicando os conceitos estudados na primeira parte. Agora, na terceira parte da série, concentraremos nossos esforços em finalizar a estrutura principal da classe de gerenciamento de riscos.
Neste artigo criaremos uma classe que nos permitirá atribuir valores de lucro e prejuízo, estabelecendo assim a base para o cálculo e o acompanhamento dos lucros (profits). Dessa forma, daremos um passo importante rumo à criação de um sistema de gerenciamento de riscos robusto e funcional.

Neste diagrama é apresentado um plano estruturado para o desenvolvimento e o planejamento da gerenciamento de riscos em nosso sistema.
Definições, estruturas e enumerações
A primeira coisa que faremos será definir as estruturas e enumerações necessárias. Elas serão utilizadas para armazenar informações-chave, como o lucro acumulado e os prejuízos acumulados, além de facilitar o gerenciamento dos dados no sistema.Criação da classe CRiskManagement
Em seguida desenvolveremos a classe principal responsável pela gerenciamento de riscos — CRiskManagement. Essa classe centralizará todos os cálculos e processos relacionados ao controle de riscos, garantindo uma implementação organizada e eficiente.Funções principais: atribuição e obtenção de valores
Depois implementaremos as funções necessárias para atribuir e obter valores, de modo a possibilitar a atualização e a consulta das informações de lucro e prejuízo. Além disso, nesta etapa definiremos o construtor e o destrutor da classe, para o gerenciamento adequado de memória e a correta inicialização dos dados.Funções acionadas por eventos
Por fim, desenvolveremos funções que serão executadas em momentos chave, como no início de um novo dia ou de uma nova semana. Essas funções serão úteis para o recálculo dos profits, o ajuste dos riscos e a reinicialização das variáveis acumuladas em determinados períodos, garantindo o acompanhamento adequado da performance ao longo do tempo.
Agora que definimos o plano de ação, comecemos pelo mais fundamental.
Definição de constantes, enumerações e estruturas
Antes de iniciar a implementação do código, organizaremos toda a lógica relacionada à gerenciamento de riscos em um arquivo separado chamado "Risk_Management.mqh" (o arquivo base que criamos). Dessa forma, manteremos um código mais estruturado e modular.
De acordo com nosso esquema de gerenciamento de riscos, o primeiro passo será criar determinadas estruturas, enumerações e constantes. Esses elementos serão essenciais para diversas funções, especialmente aquelas que realizam o cálculo e o controle de valores como perda máxima diária, perda semanal ou perda por operação.
Definição de constantes (#define)
Para simplificar a gerenciamento de riscos e melhorar a legibilidade do código, definiremos algumas constantes fundamentais:
1. NOT_MAGIC_NUMBER
Essa constante será utilizada como valor inicial no construtor da classe. Sua função é indicar que não será utilizado um magic number específico, permitindo que a gerenciamento de riscos seja aplicada a todas as operações sem restrição a um identificador particular. Isso pode ser útil quando se deseja implementar a gerenciamento de riscos no contexto da conta inteira durante operações reais.
#define NOT_MAGIC_NUMBER 0 //Not Magic Number
2.Flags (FLAGS) para o fechamento de operações
Para tornar a função de fechamento de posição mais dinâmica, utilizaremos flags em vez de criar diversas funções separadas para fechar operações com lucro, com prejuízo ou ambas. Esses flags nos permitirão simplificar o código e melhorar sua reutilização.
#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
Enumerações
1. Método de cálculo: valor fixo ou percentualPara começar, precisaremos de uma enumeração que permita definir como serão calculadas as perdas máximas e os lucros no âmbito da estratégia. Existem duas abordagens principais:
Fixo (money) — o usuário define um valor específico em dinheiro para determinar a perda ou o lucro máximos. Esse valor permanece constante, independentemente do saldo da conta.
- Exemplo: se o limite máximo de perda diária for definido como 1000 USD, esse limite permanecerá inalterado enquanto o sistema estiver em funcionamento.
Dinâmico (percentage) — em vez de um valor fixo, o usuário escolhe um percentual que será aplicado sobre um parâmetro de controle da conta, como o saldo ou a equity.
- Exemplo: se for definido um limite de 2% do saldo, o tamanho máximo da perda será ajustado conforme o saldo da conta. Se o saldo aumentar, o limite de perda cresce proporcionalmente; se o saldo diminuir, o limite também é reduzido.
No código isso é representado pela seguinte enumeração:
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. Aplicação do percentual: sobre qual parâmetro o risco será calculado
Se o método escolhido for o cálculo por percentual, é necessário definir sobre qual valor da conta esse percentual será aplicado. Para isso são considerados quatro principais parâmetros:
- Saldo (Balance) — o percentual é aplicado sobre o saldo total da conta.
- Lucro líquido (ganancianeta) — é calculado com base no lucro acumulado desde a criação da conta.
- Margem livre (free_margin) — é calculada com base no capital disponível para abertura de novas operações.
- Equity (equity) — representa o saldo ajustado pelas operações em aberto (lucro ou prejuízo flutuante).
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. Tipo de gerenciamento de riscos: conta pessoal ou prop-firma (FTMO)
Nas partes anteriores mencionamos que essa estrutura de gerenciamento de riscos pode ser aplicada tanto a contas pessoais quanto a contas de prop-firmas, especialmente a FTMO. Entretanto, na FTMO o cálculo da perda máxima diária é mais dinâmico e segue uma lógica diferente.
Mais adiante, ao analisarmos as estruturas, explicaremos detalhadamente como a FTMO realiza esse cálculo. Por ora, criaremos a enumeração que permitirá selecionar o tipo de conta para a aplicação das regras de gerenciamento de riscos.
enum ENUM_MODE_RISK_MANAGEMENT //enumeration to define the type of risk management { propfirm_ftmo, //Prop Firm FTMO personal_account // Personal Account };
4. Qual tipo de lote será utilizado
Nem todos os traders preferem trabalhar com lote dinâmico; por isso, daremos ao usuário a possibilidade de escolher o tipo de lote desejado. Essa escolha não altera a lógica principal da classe de gerenciamento de riscos, mas é fundamental no desenvolvimento do Expert Advisor, pois é necessário permitir que o usuário opere com lote fixo ou dinâmico.
Para isso, criaremos a seguinte enumeração:
enum ENUM_LOTE_TYPE //lot type { Dinamico,//Dynamic Fijo//Fixed };
Ao abrir uma operação, podemos definir a escolha do lote da seguinte forma:
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");
Nesse trecho de código, se o usuário selecionar o lote dinâmico (Dinamico), a função GetLote calculará o tamanho do lote com base no risco por operação. Caso contrário, se for selecionado o lote fixo (Fijo), o Expert Advisor utilizará o valor padrão atribuído à variável de lote (lote).
Posteriormente criaremos a função GetLote para obter corretamente o lote dinâmico.
5. Como o lote será calculado quando for dinâmico
Se o usuário escolher operar com lote dinâmico, é necessário determinar como seu tamanho será calculado. Para isso, criaremos uma enumeração que definirá se o cálculo será baseado exclusivamente no risco por operação ou se também será ajustado considerando o stop-loss.
Cálculo baseado apenas no risco por operação:
O lote é determinado exclusivamente com base no percentual de risco por operação. Nesse caso, não é necessário especificar um valor para o stop-loss.Cálculo baseado no risco por operação e no stop-loss
Nesse método, além de calcular o lote com base no risco por operação, o valor é ajustado considerando o stop-loss. Isso é importante porque, caso se defina um percentual fixo de risco sem vínculo com o stop-loss, o tamanho do lote pode se tornar inadequado.Exemplo:
Suponhamos que queremos arriscar 1% de uma conta de $1000, o que equivale a um risco máximo de $10. Se nossa estratégia possui uma relação risco/retorno de 1:2 e utilizamos um stop-loss de 100 pontos em EUR/USD (em que cada ponto vale 0,00001 para corretoras de 5 dígitos), poderíamos erroneamente calcular um lote de 0,02.- Se a operação for encerrada com prejuízo pelo stop-loss de 100 pontos, perderíamos apenas $2, e não os $10 previstos.
- Se a operação terminar em lucro, obteríamos apenas $4, em vez dos $20 esperados pela relação 1:2.
Para solucionar esse problema, ajustamos o tamanho do lote de modo que, com o stop-loss especificado, o prejuízo máximo corresponda exatamente a 1% da conta. Isso garante que, caso a operação atinja o take profit com relação 1:2, o lucro será de 2%.
A função GetIdealLot, apresentada na parte anterior, realiza automaticamente essa correção.
void GetIdealLot(double& nlot, double glot, double max_risk_per_operation, double& new_risk_per_operation, long StopLoss)
Essa função ajusta o tamanho do lote garantindo que o risco por operação permaneça dentro do limite definido.
Para representar esses dois métodos de cálculo, definimos a seguinte enumeração: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. };
Estruturas:
As estruturas desempenham um papel fundamental em nosso sistema de gerenciamento de riscos. Como mencionamos anteriormente, lucro e prejuízo são calculados com base em um de dois métodos: direto (valor fixo) ou dinâmico (percentual). No caso de utilizar o método baseado em percentual, ele deve ser aplicado a um valor de referência dentro da conta.
Para isso, criaremos uma estrutura que conterá todas as informações necessárias para determinar prejuízo ou lucro.
- value — representa o valor do prejuízo ou lucro.
- assigned_percentage — indica o percentual atribuído no caso de cálculo dinâmico.
- mode_calculation_risk — define o método de cálculo do risco por operação (direto ou dinâmico).
- percentage_applied_to — variável de enumeração que determina a qual métrica da conta o percentual será aplicado ao utilizar o método dinâmico.
No código isso é representado da seguinte forma:
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 };
Com essa estrutura podemos definir as seguintes variantes de lucro e prejuízo:
| Nome | Descrição | Reinício |
|---|---|---|
| Perda Máxima Diária (MDL) | É a perda máxima permitida durante um único dia. | Reinicia diariamente para 0. |
| Perda Máxima (ML) | É a perda total máxima que a conta pode atingir ao longo de toda sua existência. | Não reinicia e não é redefinida. |
| Perda Máxima por Operação Geral (GMLPO) | Equivale ao risco por operação definido pelo usuário, usado para calcular o tamanho adequado do lote. | Recalculada a cada fechamento de operação ou no início de um novo dia. |
| Perda Máxima Semanal (MWL) | É a perda máxima permitida durante uma semana. Opcional, pois não é um requisito obrigatório em prop-firmas. | Reinicia semanalmente. |
| Perda Máxima Líquida por Operação (NMLPO) | Gerada apenas quando a função que calcula o lote com base no risco por operação e no stop-loss é chamada. | Recalculada a cada chamada da função de obtenção do lote baseado em risco e stop-loss. |
| Lucro Máximo Diário | É o lucro máximo que o robô ou a conta pode obter em um único dia. | Reinicia diariamente. |
Particularidades da perda máxima diária na FTMO
Quando adaptamos esse sistema de gerenciamento de riscos para contas comuns e contas de prop-firmas como a FTMO, surgem algumas diferenças importantes. Na FTMO, a perda máxima diária é extremamente dinâmica, pois pode aumentar se houver lucro durante o dia:
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.
Como se pode ver na observação retirada diretamente do site da FTMO, a perda máxima diária pode variar dependendo do lucro obtido durante o dia.
Por exemplo, se no início do dia seu limite máximo de perda for $10.000 e, nesse mesmo dia, você obtiver lucro, esse lucro será somado aos $10.000 iniciais, aumentando assim o limite permitido de perda. Portanto, à medida que a lucratividade aumenta, o valor que você pode arriscar também será ajustado.
Declaração de variáveis-chave para a gerenciamento de riscos
Nesta seção definiremos a classe CRiskManagement, que será responsável pela gerenciamento do risco por operação.
//+------------------------------------------------------------------+ //| Class CRisk Management | //+------------------------------------------------------------------+ class CRiskManagemet final
Inclusão de bibliotecas
Antes de definir a classe, incluiremos a biblioteca CTrade.mqh para permitir o gerenciamento de operações, como o fechamento de posições:
#include <Trade/Trade.mqh>1. Ponteiro para CTrade
Como primeira variável, criaremos um ponteiro para a classe CTrade, o que nos permitirá interagir com posições abertas:
private: //--- CTrade class pointer to be able to work with open positions CTrade *trade;
2. Variáveis principais
Definiremos três variáveis principais:
- account_profit (double) — armazena o lucro líquido da conta desde 1971.01.01 (a menor data possível no tipo datetime);
- StopLoss (long) — armazena o valor do stop-loss em pontos, utilizado no cálculo do tamanho do lote;
- batch (double) — variável interna que armazena o último lote calculado.
Código:
//-- Main variables double account_profit; long StopLoss; double lote;
3. Variáveis gerais
- magic_number (ulong) — armazena o magic number utilizado para a gerenciamento do risco relacionado a um conjunto específico de operações. Também pode ser igual a 0, o que permite utilizá-lo em contas pessoais.
- mode_risk_management (ENUM_MODE_RISK_MANAGEMENT) — define o tipo de gerenciamento de riscos que será aplicado — para contas comuns ou para prop-firmas (como a FTMO).
Código:
//--- General variables ENUM_MODE_RISK_MANAGEMENT mode_risk_managemet; ulong magic_number;
4. Variáveis específicas para contas de prop-firmas (FTMO)
Em contas de prop-firmas, as regras de gerenciamento de riscos são aplicadas ao saldo inicial, e não ao saldo atual. Por exemplo, a perda máxima diária e a perda máxima total geralmente são calculadas como uma porcentagem do saldo inicial (normalmente 10%).
Para considerar essa particularidade, adicionaremos uma variável para armazenar o saldo inicial, que o usuário deverá definir manualmente:
//--- Variables to work with anchoring tests double account_balance_propfirm;
5. Variável para o prejuízo máximo estimado na próxima operação (NMLPO)
Essa variável armazenará o prejuízo máximo projetado para a próxima operação. Ela não é obrigatória, mas pode ser útil para prever quanto o robô perderá caso a posição atinja o stop-loss.
Ela será calculada apenas quando a função GetLote() for chamada.
//--- Variables to store the values of the maximum losses double nmlpo;
6. Variáveis para gerenciamento de lucro e prejuízo
A estrutura Loss_Profit definida anteriormente será utilizada para armazenar tanto o valor do prejuízo quanto o método de cálculo e a forma de aplicação do percentual.
Definiremos cinco variáveis principais:
//--- variables that store percentages and enumeration, which will be used for the subsequent calculation of losses
Loss_Profit mdl, mwl, ml, gmlpo, mdp;Onde:
- mdl — perda máxima diária (Max Daily Loss);
- mwl — perda máxima semanal;
- ml — perda máxima total (Max Loss);
- gmlpo — perda máxima por operação;
- mdp — lucro máximo diário.
7. Variáveis para obtenção de lucro (diário, semanal e total)
Para monitorar o desempenho do robô/Expert Advisor, adicionaremos variáveis que armazenarão dados de lucro, permitindo emitir alertas quando limites de perda forem atingidos ou quando medidas de segurança precisarem ser tomadas.
Precisaremos de:
- um horário de referência, a partir do qual o lucro será calculado,
- variáveis para armazenar os ganhos.
Código:
//--- 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;
Onde:
- daily_profit — lucro obtido no dia,
- weekly_profit — lucro obtido na semana,
- gross_profit — lucro total desde o início da gerenciamento de riscos,
- last_weekly_time — última data registrada para o cálculo do lucro semanal,
- last_day_time — última data registrada para o cálculo do lucro diário,
- init_time — data de início da gerenciamento de riscos.
Criação do construtor, destrutor e métodos de inicialização
Agora que definimos as variáveis principais, implementaremos as funções fundamentais da classe CRiskManagement.
Construtor
O construtor será responsável por inicializar variáveis essenciais, como o magic number, o modo de gerenciamento de riscos e o saldo da conta de prop-firma (quando aplicável).
A declaração do construtor será a seguinte:
CRiskManagemet(ulong magic_number_ = NOT_MAGIC_NUMBER,ENUM_MODE_RISK_MANAGEMENT mode_risk_management_ = personal_account, double account_propfirm_balance=0);
Na implementação do construtor atribuímos valores às variáveis, inicializamos o objeto CTrade e calculamos o lucro líquido da conta a partir de 1º de janeiro de 1972. Também configuramos as marcações de tempo necessárias para o acompanhamento dos lucros.
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'; }
Observação: se o usuário não fornecer um magic number, será exibido um aviso informando que o sistema considerará todas as operações abertas pelo usuário.
Destrutor
O destrutor da classe ficará responsável por liberar a memória alocada para o objeto CTrade quando uma instância de CRiskManagement for destruída.
CRiskManagemet::~CRiskManagemet()
{
delete trade;
}
Metodos de inicialização
Para facilitar a configuração e atualização da gerenciamento de riscos, criaremos funções especializadas que permitirão:
- definir o stop-loss,
- configurar os parâmetros de lucro e prejuízo.
1. Funções para definir valores das estruturas de prejuízo
Definiremos métodos que permitirão atribuir valores às variáveis principais utilizadas no cálculo de lucro e prejuízo.
//--- 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. Atribuição de percentuais às estruturas de prejuízo
O método seguinte atribui valores percentuais ou monetários às estruturas correspondentes.
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. Inicialização dos modos de cálculo do risco
Esse método define o modo de cálculo para cada estrutura. Se o usuário escolher o método baseado em dinheiro (money), o valor correspondente será atribuído diretamente.
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. Atribuição dos parâmetros aplicados à conta
Esse método define como os percentuais configurados serão aplicados à conta.
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. Funções de definição do stop-loss
Para definir o stop-loss utilizaremos dois métodos simples para atribuir valor à variável StopLoss:
- Método 1: o usuário informa diretamente a quantidade de pontos de stop-loss.
- Método 2: o usuário define a distância entre o stop-loss e o ponto de entrada, que em seguida será convertida em pontos (utilizando a função programada na primeira parte do artigo).
//--- 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_; }
Métodos para definição de valores de lucro e prejuízo
Nesta seção desenvolveremos os métodos necessários para atribuir os valores de lucro e prejuízo em nosso sistema de gerenciamento de riscos.
1. Função geral para atribuição de valores
Para garantir uma gerenciamento de riscos flexível e adaptável, criaremos uma função geral que calculará o valor correspondente com base no percentual aplicado a diferentes métricas da conta.
Definição da função:
a função principal que utilizaremos será a seguinte:
//--- General function to assign values to loss variables double GetValorWithApplied(const ENUM_APPLIED_PERCENTAGES applied,const double percentage_);
Essa função receberá dois parâmetros principais:
- applied — define a qual métrica da conta o percentual será aplicado (saldo, margem livre, equity etc.).
- percentage_ — o percentual que será aplicado à métrica selecionada.
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; }Explicação do código
- Gestão de contas de prop-firmas:
- Se a conta pertence a uma prop-firma (como a FTMO) e o percentual aplicado não se refere ao limite de lucro diário (mdp.assigned_percentage) nem ao prejuízo máximo por operação (gmlpo.assigned_percentage), o cálculo será realizado com base no saldo inicial da conta da prop-firma.
- Cálculo com base em diferentes métricas da conta:
- Balance — aplica o percentual ao saldo total da conta.
- ganancianeta — aplica o percentual ao lucro líquido da conta. Se o lucro líquido for negativo ou igual a zero, uma mensagem de erro será exibida.
- free_margin — aplica o percentual à margem livre. Se a margem livre for negativa, a função retorna 0.
- equity — aplica o percentual ao valor atual da equity da conta.
- Tratamento de erros:
- Se uma métrica inválida for passada no parâmetro applied, a função exibirá uma mensagem de erro e retornará 0.
2. Criação das funções para configurar as variáveis de lucro e prejuízo
Para uma gerenciamento eficiente de lucro e prejuízo, criaremos um conjunto de funções que nos permitirá atribuir valores de forma estruturada. Ao todo serão definidas seis funções: cinco delas para configurar os prejuízos principais e uma para calcular o lote ideal com base no Stop Loss.
1. Funções de atribuição de valores
O método de atribuição dos valores de lucro e prejuízo segue a seguinte lógica:
- Se o modo de cálculo for "money", então é mantido o valor previamente atribuído.
- Se o cálculo não estiver no modo "money", utiliza-se a função GetValorWithApplied para determinar o valor com base no percentual atribuído e no parâmetro de aplicação correspondente.
- Se o percentual atribuído for igual a 0, considera-se que esse tipo de prejuízo não será utilizado e, portanto, recebe o valor 0.
2. Funções de atribuição
As funções a seguir implementam a lógica descrita acima para cada tipo de prejuízo:
//--- 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); }
Cálculo do volume do lote e do stop-loss com base no risco por operação
Nesta seção implementaremos duas funções principais para calcular com precisão o tamanho do lote e o stop-loss, garantindo que cada operação esteja alinhada ao nível de risco que estamos dispostos a assumir.
1. Função para cálculo do lote
Para o cálculo do lote utilizaremos as funções desenvolvidas na primeira parte do artigo, garantindo que o tamanho resultante esteja em conformidade com nossas regras predefinidas de risco.
A função que nos permitirá obter o lote adequado é a seguinte:
//--- Get the lot double GetLote(const ENUM_ORDER_TYPE order_type, const ENUM_GET_LOT mode_get_lot);Parâmetros da função:
- order_type — especifica o tipo de ordem (compra, venda, stops, limits, stop-limits etc.).
- mode_get_lot — especifica o método de cálculo do lote (explicado anteriormente na seção de enumerações).
Objetivo da função
Essa função nos ajudará a calcular o tamanho ideal do lote com base em duas abordagens diferentes:
- de acordo com o stop-loss e o risco máximo permitido por operação,
- diretamente com base apenas no risco máximo por operação.
Agora vejamos como esse cálculo é implementado no código.
//+-----------------------------------------------------------------------------------------------+ //| 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; }
Modo 1. Cálculo do lote com base no stop-loss e no risco máximo por operação
Quando o usuário seleciona GET_LOT_BY_STOPLOSS_AND_RISK_PER_OPERATION, o sistema calcula o lote com base no risco por operação e depois o ajusta considerando o stop-loss, de modo que, caso a operação seja perdedora, o prejuízo corresponda ao risco previamente definido.
- O lote máximo permitido é determinado pela função GetMaxLote(order_type).
- O lote é ajustado com SetNMPLO(this.lot, MaxLot), garantindo que os limites da conta não sejam excedidos.
- É exibida uma mensagem informativa indicando o prejuízo máximo esperado caso a operação atinja o stop-loss.
Modo 2. Cálculo do lote com base no risco máximo por operação
Quando o usuário seleciona outro modo de cálculo (por exemplo, GET_LOT_BY_RISK_PER_OPERATION), o sistema determina o lote de forma mais direta:
- A função GetLotByRiskPerOperation(this.gmlpo.value, order_type) calcula o tamanho do lote com base no risco máximo permitido por operação.
- Esse método é mais simples e pode ser útil para traders que não utilizam um stop-loss fixo e operam o risco de forma mais dinâmica.
2. Função de cálculo do stop-loss
Como já desenvolvemos funções auxiliares nas seções anteriores, esta função será relativamente simples, pois reutiliza tais funções para realizar um cálculo eficiente do stop-loss.
A função que nos permitirá obter o stop-loss ideal é a seguinte:
long GetSL(const ENUM_ORDER_TYPE type, double DEVIATION = 100, double STOP_LIMIT = 50);Parâmetros da função:
- type — indica o tipo de ordem: pode ser ordem de compra, venda, stop, limit, stop-limit etc.
- DEVIATION (opcional) — representa a tolerância de desvio na execução da ordem, com valor padrão de 100 pontos.
- STOP_LIMIT (opcional) — representa a distância em pontos para ordens do tipo STOP_LIMIT.
Esses parâmetros garantem um cálculo dinâmico e adaptável do stop-loss para diferentes condições de mercado.
Implementação da função:
//+----------------------------------------------------------------------------------+ //| 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); }
- Como essa função funciona
- Define uma variável lot, que armazenará o tamanho do lote utilizado no cálculo do stop-loss.
- Chama a função CalculateSL(), passando como parâmetros:
- o tipo da ordem (type), para que o stop-loss seja calculado corretamente conforme seja compra ou venda;
- o risco máximo permitido por operação (this.gmlpo.value), que determina um stop-loss coerente com nossa gerenciamento de riscos;
- a variável lot, que será atualizada de acordo com o tamanho de lote utilizado;
- o desvio permitido (DEVIATION), que oferece certa flexibilidade na execução da ordem;
- STOP_LIMIT — a distância em pontos para ordens STOP_LIMIT.
- Ao final, a função retorna o stop-loss calculado em pontos.
Funções para obtenção dos valores de lucro e prejuízo
Nesta seção são descritas as funções responsáveis por obter as principais métricas de lucro e prejuízo na estratégia. Essas funções são declaradas como inline e const para garantir eficiência e impedir alterações indesejadas nas variáveis da classe.
1. Funções para obtenção dos valores máximos de lucro e prejuízo
As funções seguintes retornam o valor armazenado nas variáveis que registram o lucro máximo e o prejuízo máximo alcançados em diferentes períodos:
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; }
Cada função fornece os dados correspondentes sobre o comportamento das operações em cada nível de análise.
2. Funções de obtenção do lucro diário, semanal e total
Essas funções retornam o valor do lucro em distintos períodos de tempo, permitindo avaliar a performance do sistema.
//--- 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.Função para o fechamento de operações abertas de acordo com o magic number e os flags
Essa função é destinada ao fechamento de todas as posições abertas que atendam a determinados critérios. Ela é baseada em um sistema de flags, que permite especificar quais operações devem ser encerradas:
- somente lucrativas,
- somente perdedoras,
- ambas as categorias (todas as operações).
void CloseAllPositions(int flags = FLAG_CLOSE_ALL_LOSS | FLAG_CLOSE_ALL_PROFIT);
- Um flag é um número inteiro que representa condições específicas para o fechamento de posições. Diferentes flags podem ser combinados utilizando operadores bitwise (|).
#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
Aqui os flags são representados como potências de 2.
Cada flag corresponde a um valor que pode ser ativado individualmente ou em combinação:
| Flag | Valor (decimal) | Valor (binário) | Finalidade |
|---|---|---|---|
| FLAG_CLOSE_ALL_PROFIT | 2 | 00000010 | Fecha apenas posições lucrativas. |
| FLAG_CLOSE_ALL_LOSS | 4 | 00000100 | Fecha apenas posições perdedoras. |
2. Função de fechamento de posições
Essa função percorre todas as posições abertas e as encerra conforme os flags ativados.
3. Percorrendo todas as posições abertas
for(int i = PositionsTotal() - 1; i >= 0; i--)
- PositionsTotal() retorna o número total de posições abertas.
- O loop percorre as posições em ordem inversa (da última para a primeira) para evitar erros ao fechar posições em tempo real.
4. Obtendo o ticket da posição
ulong position_ticket = PositionGetTicket(i);
- Cada posição possui um ticket único que a identifica.
5. Selecionando a posição
if (!PositionSelectByTicket(position_ticket)) continue;
- PositionSelectByTicket(position_ticket) tenta selecionar a posição com base no ticket.
- Se a seleção falhar, o código avança imediatamente para a próxima posição usando continue.
6. Verificação do magic number
ulong magic = PositionGetInteger(POSITION_MAGIC); if (magic != this.magic_number && this.magic_number != NOT_MAGIC_NUMBER) continue;
- Obtém-se o magic number da posição.
- Se o magic number da posição for diferente do esperado e a negociação com qualquer ordem não estiver permitida (NOT_MAGIC_NUMBER), essa posição é ignorada.
7. Obtendo o lucro da posição
double profit = PositionGetDouble(POSITION_PROFIT);
- POSITION_PROFIT retorna o lucro atual da operação, que pode ser positivo (lucro) ou negativo (prejuízo).
8. Verificação dos flags e fechamento das operações
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); }
- O operador & é utilizado para verificar se um flag está ativado.
- Se FLAG_CLOSE_ALL_PROFIT estiver ativado e a operação for lucrativa, ela é encerrada.
- Se FLAG_CLOSE_ALL_LOSS estiver ativado e a operação for perdedora, ela é encerrada.
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); } } }
Eventos disparados no início de um novo dia e de uma nova semana
Para concluir este artigo, programaremos as últimas funções, que serão executadas mediante determinados eventos. Em particular, criaremos duas funções fundamentais:
- evento diário — executado no início de cada novo dia,
- evento semanal — executado no início de cada nova semana.
Esses eventos ajudarão a estruturar a gerenciamento de lucro e prejuízo, garantindo a atualização correta dos valores.
Evento diário
Todos os dias é necessário recalcular e definir os valores de todas as perdas máximas e do lucro máximo, e zerar o lucro diário acumulado. Também exibiremos os valores de lucro e prejuízo no log para fins de monitoramento.//+------------------------------------------------------------------+ //| 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)); }
- São chamadas todas as funções necessárias para configurar os valores de lucro e prejuízo.
- A variável daily_profit é reiniciada para iniciar um novo dia sem carregar lucros ou prejuízos do dia anterior.
- O timestamp do último dia registrado é atualizado usando iTime(_Symbol, PERIOD_D1, 0).
- Os valores dos prejuízos são enviados ao log para monitoramento do sistema.
Evento semanal
O evento semanal registrará o início de uma nova semana e zerará o lucro acumulado da semana anterior.//+------------------------------------------------------------------+ //| Function that runs every new week | //+------------------------------------------------------------------+ void CRiskManagemet::OnNewWeek(void) { this.last_weekly_time = iTime(_Symbol,PERIOD_W1,0); this.weekly_profit = 0; }
- A variável last_weekly_time é atualizada com o horário de abertura da nova semana.
- A variável weekly_profit é zerada para evitar acumular lucro ou prejuízo da semana anterior.
Detecção automática de novos eventos (dia/semana/período personalizado)
Para disparar automaticamente essas funções no Expert Advisor, é possível verificar mudanças no período utilizando uma variável do tipo datetime. Isso permite identificar o início de um novo dia, semana ou qualquer outro timeframe (H1, H12 etc.).
Exemplo de detecção de um novo dia:
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 } }
Exemplo de detecção de uma nova semana:
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); } }
Explicação:
- Uma variável datetime armazena o horário registrado anteriormente.
- A cada tick (OnTick), o horário atual é comparado com o valor armazenado em prev_time ou prev_week_time.
- Se houver diferença, significa que um novo dia ou semana começou, e então a função OnNewDay() ou OnNewWeek() é chamada.
- A variável prev_time ou prev_week_time é atualizada com o novo valor.
Considerações finais
Neste artigo desenvolvemos a primeira parte da classe de gerenciamento de riscos CRiskManagement. Nesta etapa inicial, nosso foco principal foi a atribuição dos valores de lucro máximo e de prejuízos máximos, criando assim a base para uma gerenciamento de riscos estruturada.
Embora a classe ainda não esteja pronta para uso em ambiente operacional, já podemos observar como esses valores são internamente atribuídos e processados dentro do sistema.
No próximo artigo concluiremos a implementação dessa classe, adicionando funções essenciais que permitirão:
- verificar se os limites máximos de prejuízo foram ultrapassados e tomar as medidas adequadas;
- incluir novos eventos para melhorar o controle de riscos;
- criar funções específicas, como a atualização automática do limite máximo de perda diária em contas do tipo Prop Firm, como as da FTMO.
Graças a essas melhorias, a classe CRiskManagement estará pronta para gerenciar de forma eficiente os limites de risco e se adaptar a diferentes condições de mercado.
Arquivos utilizados/aperfeiçoados neste artigo:
| Nome do arquivo | Tipo | Descrição |
|---|---|---|
| Risk_Management.mqh | .mqh (arquivo incluído) | Arquivo principal que contém as funções gerais e a implementação da classe CRiskManagement, responsável pela gerenciamento de riscos no sistema. Neste arquivo são definidas, desenvolvidas e expandidas todas as funções relacionadas ao controle de lucro e prejuízo. |
Traduzido do espanhol pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/es/articles/17249
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Automatizando Estratégias de Negociação em MQL5 (Parte 3): O Sistema Zone Recovery RSI para Gestão Dinâmica de Operações
Simulação de mercado: A união faz a força (I)
Do básico ao intermediário: Sobrecarga de operadores (II)
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso