Русский Español
preview
Gerenciamento de riscos (Parte 4): Conclusão dos métodos-chave da classe

Gerenciamento de riscos (Parte 4): Conclusão dos métodos-chave da classe

MetaTrader 5Exemplos |
85 0
Niquel Mendoza
Niquel Mendoza


Introdução

Dando continuidade ao artigo anterior sobre gerenciamento de riscos, explicaremos as variáveis principais e alguns métodos iniciais do nossa classe especializada. Desta vez, o foco está em finalizar os métodos necessários para verificar se os limites máximos de prejuízo ou lucro definidos foram ultrapassados. Além disso, apresentaremos duas abordagens dinâmicas de controle do risco por operação.


Resumo do Artigo

Iniciaremos este artigo com a otimização de algumas funções, bem como do construtor e do destrutor da classe. Em seguida, definiremos novas estruturas, enumerações e constantes principais. Depois, implementaremos as funções de gerenciamento das posições abertas pelo EA, incluindo mecanismos especiais para detectar o excedente dos valores máximos de lucro ou prejuízo estabelecidos. Por fim, como complemento, adicionaremos métodos voltados ao gerenciamento dinâmico do risco por operação.


Novas Enumerações e Estruturas

Comecemos definindo as novas estruturas, enumerações e constantes que utilizaremos ao longo do artigo.

1. Risco Dinâmico por Operação

Como mencionado anteriormente, adicionei um novo conceito: o risco dinâmico por operação.

O que ele representa?

O risco dinâmico por operação, como o nome indica, não é um valor fixo, mas uma grandeza que varia conforme o lucro ou o prejuízo em relação ao saldo inicial definido. Isso pode ser especialmente útil para proteger a conta de negociação. Por exemplo, se o saldo diminuir até certa porcentagem do saldo original, é possível ajustar automaticamente o risco para minimizar perdas adicionais. Isso significa que, a cada vez que determinado nível é atingido, o risco por operação é recalculado e modificado.

Vejamos um exemplo prático apresentado na tabela. Suponhamos que o saldo inicial da conta seja de 10 000 USD.

Escolhemos os seguintes valores de limiar para alteração do risco: 3%, 5% e 7%.

Condição Novo percentual
  Se o saldo diminuir em 3% (300 USD, ou seja, abaixo de 9700 USD)  O risco é ajustado para 0,7%
  Se o saldo diminuir em 5% (500 USD, ou seja, abaixo de 9500 USD)  O risco é ajustado para 0,5%
  Se o saldo diminuir em 7% (700 USD, ou seja, abaixo de 9300 USD)  O risco é ajustado para 0,25%

Observações importantes sobre os parâmetros e o funcionamento do risco dinâmico:

  • Todos os valores mencionados neste artigo são parâmetros totalmente configuráveis e podem ser modificados conforme as necessidades e preferências do usuário. Isso inclui tanto as porcentagens que definem quando o risco deve ser alterado quanto o saldo inicial ao qual essas porcentagens são aplicadas.

O saldo inicial pode ser determinado de duas formas:

  • Conta do tipo PropFirm (por exemplo, FTMO): se essa opção for selecionada através do parâmetro de gerenciamento de risco (profitm_ftmo), o saldo inicial deve ser informado manualmente por meio do parâmetro input nas configurações do EA. Esse saldo permanece fixo durante toda a operação, ou seja, não muda com o tempo e é um valor estático definido pelo usuário no início da negociação.

  • Conta pessoal: se esse tipo de conta for escolhido (personal_account), o saldo inicial será determinado automaticamente pelo EA ao chamar a função AccountInfoDouble(), que retorna o saldo atual da conta no momento da inicialização do sistema. Nesse caso, o saldo será dinâmico e refletirá exatamente o valor atual dos fundos disponíveis na conta do usuário.
Vantagens da implementação do risco dinâmico por operação:
  • reduz significativamente o risco de "queimar" a conta ou perder todo o capital de negociação,
  • aumenta a segurança geral da operação ao diminuir a exposição a séries prolongadas de perdas.

    Desvantagens do risco dinâmico:

    • o processo de recuperação após uma sequência de perdas ou uma retração significativa pode ser mais lento devido à redução preventiva da exposição. Embora essa recuperação ocorra de forma mais lenta, ela é mais segura e controlada.

    Explicação detalhada da estrutura necessária para a implementação do risco dinâmico

    Para que o gerenciamento e o ajuste do risco dinâmico por operação funcionem corretamente, é necessário acessar e modificar a propriedade chamada assigned_percentage dentro da variável nomeada gmlpo. Para realizar essas alterações automáticas com base no saldo, deve-se criar uma estrutura contendo dois elementos principais:

    • o saldo específico no qual a mudança de risco será ativada (por exemplo, quando o saldo cair para 9700 USD a partir de um saldo inicial de 10 000 USD, conforme mostrado na tabela).
    • a nova porcentagem de risco que deve ser aplicada ao saldo ao atingir o nível especificado (por exemplo, alterar o risco para 0,7% neste caso específico).

      Inicialmente, poderia parecer suficiente criar uma estrutura simples com duas variáveis separadas do tipo double, mas essa solução não é adequada. Em vez disso, utilizaremos dois arrays separados dentro da estrutura.

      Por que usar arrays em vez de variáveis separadas

      Usamos arrays no lugar de variáveis individuais porque precisamos classificar facilmente vários valores relacionados. A necessidade de ordenação surge devido ao método específico que aplicaremos para gerenciar o risco dinâmico de forma eficiente e rápida. Em particular, vamos utilizar a função ArraySort para ordenar os saldos nos quais será ativada a alteração do risco (balance_to_activate_the_risk[]).

      Por que é necessário usar um método de ordenação como o ArraySort

      A principal razão é que estamos implementando um método sem laços, projetado para verificar continuamente se o saldo atual ultrapassou determinados limites a fim de ajustar o risco. Essa abordagem sem laços foi escolhida para otimizar o desempenho e a velocidade do sistema, o que é especialmente importante quando as verificações são frequentes (por exemplo, a cada tick ou após o fechamento de cada operação).

      Se os valores não forem corretamente ordenados em ordem crescente — podem ocorrer problemas sérios. Vejamos um exemplo prático para esclarecer essa situação.

      Suponhamos que inicialmente definimos as seguintes porcentagens:

      • primeira porcentagem de alteração: 3% (saldo de ativação – 9700 USD)
      • segunda porcentagem de alteração: 7% (saldo de ativação – 9300 USD)
      • terceira porcentagem de alteração: 5% (saldo de ativação – 9500 USD)

        Como se pode perceber, esses valores estão desordenados (a segunda porcentagem é maior que a terceira). O problema da falta de ordenação correta surge porque o nosso método sem laços utiliza uma variável inteira (semelhante a um contador) para rastrear o estado atual do risco.

        Vejamos o que acontece:

        • Quando o saldo inicial diminui 3% (até 9700 USD), o contador aumenta em uma unidade, ajustando o risco para 0,7%.
        • Depois, quando o saldo diminui para o nível seguinte (7%), atingindo 9300 USD, o contador é incrementado novamente, ignorando o valor intermediário (5% correspondente ao saldo de 9500 USD).
        • Esse valor intermediário (9500 USD) permanece sem uso, gerando confusão e sérios erros nos cálculos, pois o risco dinâmico deixa de ser ajustado corretamente.

          Além disso, se a conta começar a se recuperar a partir do nível mais baixo (neste caso, incorretamente do valor de 5%), para retornar ao valor anterior ela teria de ultrapassar 9300, o que está errado. Mas como o método original não considerava a ordem corretamente, o processo de recuperação também não funcionaria de maneira adequada, gerando erros adicionais.

          Por essas razões, a ordenação correta é fundamental para garantir o funcionamento ideal do método sem laços. A estrutura mais simples que propomos inicialmente é a seguinte:

          struct Dynamic_gmlpo
          {
             double balance_to_activate_the_risk[];
             double risk_to_be_adjusted[];
          };

          No entanto, embora essa estrutura seja simples, ainda existe uma limitação importante. Como esses dois arrays representam pares de chave-valor (saldo — porcentagem de risco ajustada), é essencial manter essa correspondência intacta durante a ordenação. É exatamente nesse ponto que entra em ação a estrutura CHashMap.

          A implementação com CHashMap permite associar diretamente cada saldo específico ao respectivo percentual de risco. Durante a ordenação do array principal (dos saldos), o vínculo com o risco correspondente é automaticamente preservado. Assim, garante-se precisão total em todas as operações e cálculos.

          Portanto, o que implementamos até agora é uma solução inicial simples que utiliza dois arrays separados, facilitando temporariamente o uso do método ArraySort. No entanto, para uma implementação mais robusta e precisa, especialmente se for necessário manipular várias combinações de chave-valor de forma ordenada e eficiente, recomenda-se o uso posterior do CHashMap. Essa estrutura assegura o vínculo correto de cada saldo ao seu respectivo risco ajustado, evitando erros potenciais durante a ordenação e a consulta de valores dinâmicos.

          A seguir, o artigo detalhará como implementar corretamente essa solução com CHashMap, apresentando exemplos práticos e explicações passo a passo.

          Verificação do saldo

          Dando continuidade à análise das enumerações necessárias para a correta implementação do risco dinâmico, devemos adicionar duas opções adicionais que definirão com clareza o momento em que a verificação do saldo será realizada. Essa verificação é fundamental, pois é ela que determina o instante exato em que deve ser avaliado se o saldo atual caiu abaixo dos valores percentuais previamente definidos, ativando assim a alteração do risco dinâmico.

          Existem dois tipos principais de verificação:

          1. Verificação a cada tick do mercado: neste método, a verificação é executada continuamente, a cada movimento de preço (tick) do mercado. Essa opção é altamente precisa, pois monitora constantemente se o patrimônio líquido (saldo atual) caiu abaixo de um determinado valor limite. No entanto, há uma desvantagem significativa: a comparação constante com o patrimônio líquido atual pode causar situações incômodas ou ineficientes.

            Por exemplo, suponha que o saldo inicial seja de 10 000 USD e que o primeiro nível de ativação do risco ocorra quando o saldo atingir 9 700 USD. Se o patrimônio líquido oscilar entre 9701 e 9699, o mecanismo de redefinição dinâmica do risco será acionado e desativado repetidamente, o que não apenas é inconveniente, mas também gera uma carga desnecessária sobre os recursos do sistema devido à alta frequência dessa verificação.

          2. Verificação no fechamento das operações: o segundo método executa a verificação apenas no momento em que uma operação é encerrada, e não a cada tick. Essa opção é mais eficiente em termos de uso de recursos do sistema, já que o saldo é verificado apenas em momentos específicos. No entanto, pode ser menos precisa, pois a checagem ocorre apenas no fechamento das operações e pode não levar em conta flutuações intermediárias relevantes que poderiam ter ativado o ajuste do risco dinâmico de forma oportuna.

          Para facilitar a escolha entre esses dois métodos, definiremos claramente ambos no código por meio de uma enumeração:

          //---
          enum ENUM_REVISION_TYPE
           {
            REVISION_ON_CLOSE_POSITION, //Check GMLPO only when closing positions
            REVISION_ON_TICK //Check GMLPO on all ticks
           };
          

          Dessa forma, oferecemos ao usuário uma escolha explícita sobre como e quando realizar a verificação do saldo, permitindo que ele configure o comportamento do risco dinâmico conforme suas necessidades e preferências individuais, equilibrando desempenho e precisão do sistema.

          Modo GMLPO

          Para controlar o modo em que o risco dinâmico por operação (GMLPO) é aplicado, utilizaremos uma enumeração especial que oferece três opções claramente distintas. A seguir, explicarei em detalhes o propósito de cada uma dessas opções e o motivo pelo qual decidi implementá-las mediante uma enumeração.

          Inicialmente, para definir os percentuais específicos que determinavam quando o risco deveria ser alterado, bem como para indicar os novos valores de risco, eram utilizadas cadeias de texto (string) inseridas pelo usuário. Nesse método original, o usuário precisava digitar manualmente em uma única string de entrada (string input) os valores percentuais negativos do saldo que ativariam a mudança de risco, e em outra string os novos valores de risco a serem aplicados. Embora essa abordagem fosse funcional, ela apresentava uma limitação importante: as strings não podiam ser automaticamente otimizadas usando a função de otimização do EA. Isso tornava o processo de configuração e teste de diferentes cenários lento, pouco prático e bastante cansativo.

          Para superar essas limitações, decidi implementar uma enumeração que permite escolher facilmente entre três modos claramente definidos, oferecendo flexibilidade e praticidade durante o processo de otimização:

          1. DYNAMIC_GMLPO_FULL_CUSTOM: este modo é totalmente personalizável e permite ao usuário definir manualmente vários níveis de ativação do risco e os respectivos novos valores percentuais. Embora esse modo mantenha o uso de cadeias de texto, ele concede ao usuário a liberdade de especificar quantas alterações desejar, proporcionando máxima flexibilidade à custa da impossibilidade de otimização automática.

          2. DYNAMIC_GMLPO_FIXED_PARAMETERS: este modo simplifica significativamente a configuração do risco dinâmico, limitando o número máximo de alterações possíveis a quatro. Aqui, o usuário define diretamente os percentuais negativos do saldo e seus correspondentes percentuais de risco por meio de parâmetros numéricos, o que torna o processo de otimização muito mais fácil. Essa opção oferece um equilíbrio entre flexibilidade de configuração e eficiência nos testes e otimizações automáticas.

          3. NO_DYNAMIC_GMLPO: este último modo desativa completamente a função de risco dinâmico. É ideal para usuários que preferem manter um risco fixo durante toda a negociação sem ajustes dinâmicos baseados nas oscilações do saldo.

          A enumeração correspondente a esses modos é apresentada a seguir:

          //---
          enum ENUM_OF_DYNAMIC_MODES_OF_GMLPO
           {
            DYNAMIC_GMLPO_FULL_CUSTOM, //Customisable dynamic risk per operation
            DYNAMIC_GMLPO_FIXED_PARAMETERS,//Risk per operation with fixed parameters
            NO_DYNAMIC_GMLPO //No dynamic risk for risk per operation
           };

          Essa implementação baseada em enumeração garante clareza, facilidade de uso e a capacidade de otimizar diferentes configurações de forma simples, permitindo encontrar rapidamente a melhor solução consoante as preferências e estratégias individuais do usuário.

          Controle de operações

          Para aprimorar ainda mais nosso sistema de gerenciamento de riscos, adicionaremos uma funcionalidade especial que permitirá monitorar com precisão todas as posições abertas na conta, independentemente de terem sido abertas manualmente pelo usuário ou automaticamente pelo robô de negociação.

          No caso das posições abertas pelo EA, também será possível identificar claramente se um determinado ticket corresponde ao número mágico atribuído a esse EA. Isso é especialmente útil quando é necessário saber exatamente quantas posições estão abertas por aquele EA específico e distinguir facilmente essas posições de outras que foram abertas manualmente.

          Para alcançar esse objetivo, definiremos uma estrutura simples, mas eficiente, que armazenará as informações principais de cada posição:

          struct Positions
           {
            ulong              ticket; //position ticket
            ENUM_POSITION_TYPE type; //position type
           };

          Excedente de lucro ou prejuízo máximo

          Agora adicionaremos uma enumeração separada que indicará quais critérios serão considerados para determinar se o valor máximo de lucro ou prejuízo previamente estabelecido foi ultrapassado.

          A função responsável por realizar essa verificação retornará true quando as condições especificadas forem atendidas conforme o critério selecionado.

          A enumeração consistirá em três opções claramente distintas:

          //--- Mode to check if a maximum loss or gain has been exceeded
          enum MODE_SUPERATE 
           {
            EQUITY, //Only Equity
            CLOSE_POSITION, //Only for closed positions
            CLOSE_POSITION_AND_EQUITY//Closed positions and equity
           };
          1. EQUITY:
            Esta opção avalia exclusivamente o patrimônio líquido atual da conta (ou seja, o saldo em tempo real considerando posições abertas e fechadas). Ela não leva em conta o lucro ou o prejuízo das operações já encerradas no dia atual. A função indicará o excedente do lucro ou prejuízo máximo somente se o patrimônio líquido em tempo real ultrapassar diretamente o limite definido.

          2. CLOSED_POSITIONS:
            Este método considera apenas os lucros ou prejuízos das posições encerradas durante o dia corrente. Ele ignora completamente as posições abertas e o patrimônio líquido atual. Assim, o excedente do limite é determinado exclusivamente pelo resultado acumulado das operações fechadas.

          3. CLOSED_POSITION_AND_EQUITY:
            Este é o método mais completo e preciso, pois combina os dois anteriores. A função avalia simultaneamente o lucro ou prejuízo das posições encerradas no dia e o patrimônio líquido atual em tempo real. Isso significa que é analisado o resultado total do dia, garantindo uma avaliação mais rigorosa e detalhada do excedente dos limites estabelecidos.

          Ao utilizar essa enumeração dentro do sistema de gerenciamento de riscos, garantimos ao usuário flexibilidade na escolha do método de verificação dos limites, permitindo adaptar-se facilmente a diferentes estratégias e níveis de precisão necessários para o controle de risco.

          Defines

          Dando continuidade à definição das novas variáveis, é importante estabelecer algumas constantes usando a diretiva #define.

          Primeiro, definiremos um prefixo com define para facilitar a identificação das operações e mensagens geradas pelo nosso EA. Esse prefixo pode incluir o nome do EA, tornando-o facilmente reconhecível nos logs ou comentários gerados pelo sistema. Por exemplo, neste caso, utilizaremos:

          #define EA_NAME "CRiksManagement | " //Prefix

          Além disso, será necessário definir algumas constantes (flags) que utilizaremos posteriormente para o controle preciso tanto das posições abertas quanto das ordens pendentes. Essas flags permitirão identificar rapidamente o tipo de operação (compra, venda, ordens limitadas, ordens stop, etc.), o que facilitará o gerenciamento eficiente do controle de riscos, o fechamento de posições e a execução de solicitações específicas relacionadas ao estado atual do mercado e das nossas posições.

          A seguir estão os defines correspondentes:

          //--- positions
          #define  FLAG_POSITION_BUY 2
          #define  FLAG_POSITION_SELL 4
          
          //--- orders
          #define FLAG_ORDER_TYPE_BUY             1
          #define FLAG_ORDER_TYPE_SELL            2
          #define FLAG_ORDER_TYPE_BUY_LIMIT       4
          #define FLAG_ORDER_TYPE_SELL_LIMIT      8
          #define FLAG_ORDER_TYPE_BUY_STOP        16
          #define FLAG_ORDER_TYPE_SELL_STOP       32
          #define FLAG_ORDER_TYPE_BUY_STOP_LIMIT  64
          #define FLAG_ORDER_TYPE_SELL_STOP_LIMIT 128
          #define FLAG_ORDER_TYPE_CLOSE_BY        256

          Essas constantes garantirão uma implementação mais clara e eficiente nas funções futuras, simplificando significativamente a leitura, manutenção e escalabilidade do código do EA.


          Melhorias no Construtor e nas Funções Gerais

          Iniciaremos a otimização do gerenciamento de riscos pelo construtor da classe CRiskManagement. Agora que o risco dinâmico por operação foi incorporado, várias melhorias importantes foram adicionadas ao código:

          Em primeiro lugar, foi introduzido de forma explícita o tipo de lote utilizado (type_get_lot) e o parâmetro associado ao saldo inicial (account_propfirm_balance), o que é especialmente útil quando se utiliza uma conta do tipo PropFirm. Além disso, é possível notar que a definição EA_NAME será constantemente exibida nos comentários informativos gerados pelas funções principais da classe. Isso facilita a identificação imediata dessas funções nos logs do terminal.

          A implementação aprimorada do construtor é apresentada da seguinte forma:

          //+------------------------------------------------------------------+
          //| Constructor                                                      |
          //+------------------------------------------------------------------+
          CRiskManagemet::CRiskManagemet(bool mdp_strict_, ENUM_GET_LOT type_get_lot_, 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(EA_NAME, " (Warning) No magic number has been chosen, taking into account all the magic numbers and the user's trades");
             }
          //---
            this.mdp_is_strict = mdp_strict_;
            this.type_get_lot = type_get_lot_;
          //---
            this.account_balance_propfirm = account_propfirm_balance ;
            trade = new CTrade();
            trade.SetExpertMagicNumber(this.magic_number);
            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.ActivateDynamicRiskPerOperation = false;
          //---
            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';
          //---
            this.positions_open = false;
            this.curr_profit = 0;
            UpdateProfit();
            
          //---
             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_number_ || magic_number_ == NOT_MAGIC_NUMBER)
               {
                this.positions_open = true;
                Positions new_pos;
                new_pos.type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
                new_pos.ticket = position_ticket;
                ExtraFunctions::AddArrayNoVerification(open_positions, new_pos);
               }
             }  
          }

          Foram adicionadas novas variáveis importantes, tais como:

          • curr_profit armazena o lucro atual, facilitando o monitoramento contínuo dos resultados;
          • ActivateDynamicRiskPerOperation variável lógica que define se o risco dinâmico será utilizado durante a execução do EA;
          • mdp_is_strict variável que determina se o gerenciamento de riscos seguirá rigorosamente o controle definido por mdp;
          • type_get_lot variável responsável por armazenar o tipo de lote em uso.
          Além disso, foi adicionado um laço que coleta todas as posições abertas associadas ao número mágico ou, caso este não seja definido, adiciona-as ao array de posições sob gerenciamento de risco.

          Melhorias no destrutor

          O destrutor também recebeu uma série de melhorias relevantes, especialmente no que diz respeito ao gerenciamento correto da memória dinâmica. Agora é realizada uma verificação do ponteiro da classe CTrade por meio da função CheckPointer, garantindo que sua liberação ocorra apenas se o ponteiro for realmente dinâmico, evitando assim erros potenciais durante o processo de desalocação de memória.

          A versão otimizada do destrutor é apresentada a seguir:

          //+------------------------------------------------------------------+
          //| Destructor                                                       |
          //+------------------------------------------------------------------+
          CRiskManagemet::~CRiskManagemet()
           {
            if(CheckPointer(trade) == POINTER_DYNAMIC) delete trade;
            ArrayFree(this.open_positions);
            ArrayFree(this.dynamic_gmlpos.balance_to_activate_the_risk);
            ArrayFree(this.dynamic_gmlpos.risk_to_be_adjusted);
           }

          O uso adequado da função CheckPointer assegura que o ponteiro trade seja liberado corretamente e apenas quando necessário. Além disso, com o uso da função ArrayFree, a memória utilizada pelos arrays pertencentes à classe é liberada de forma eficiente, garantindo uma gestão de memória apropriada e tornando o funcionamento do EA, que incorpora esse sistema de gerenciamento de riscos, mais estável e eficiente.

          Melhorias gerais

          Implementei uma série de alterações importantes voltadas para o fortalecimento global do sistema de gerenciamento de riscos e para a prevenção de possíveis falhas no funcionamento do EA. A seguir, essas melhorias são descritas em detalhes.

          1. Verificações ao obter o volume do lote e o stop loss (SL)

          Foram adicionadas verificações essenciais para garantir que o valor do risco por operação (gmlpo.assigned_percentage) não seja inválido ou igual a zero. Essas verificações permitem identificar erros críticos em tempo hábil e exibir mensagens informativas ao usuário, facilitando a correção rápida de configurações incorretas.

          O critério principal é a verificação direta de gmlpo.assigned_percentage. Se esse valor for menor ou igual a zero, uma mensagem crítica é exibida no console, e a função é encerrada retornando valores seguros, evitando assim o funcionamento incorreto do EA.

            if(gmlpo.assigned_percentage <= 0)
             {
              PrintFormat("%s Critical error, the value of gmlpo.value(%.2f) is invalid", EA_NAME, this.gmlpo.value);
              return 0;
             }

          Exemplo na função GetSL:

          //+----------------------------------------------------------------------------------+
          //| 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)
           {
             if(gmlpo.assigned_percentage <= 0)
             {
              PrintFormat("%s Critical error, the value of gmlpo.value(%.2f) is invalid", EA_NAME, this.gmlpo.value);
              return 0;
             }
           
            double lot;
            return CalculateSL(type, this.gmlpo.value, lot, DEVIATION, STOP_LIMIT);
           }
          

          Exemplo na função GetLote:

          //+-----------------------------------------------------------------------------------------------+
          //| 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)
           {
            if(gmlpo.assigned_percentage <= 0)
             {
              PrintFormat("%s Critical error, the value of gmlpo.value(%.2f) is invalid", EA_NAME, this.gmlpo.value);
              this.lote = 0.00;
              return this.lote;
             }
             
          //---
            if(this.type_get_lot == GET_LOT_BY_STOPLOSS_AND_RISK_PER_OPERATION)
             {
              double MaxLote = GetMaxLote(order_type);
              SetNMPLO(this.lote, MaxLote);
              PrintFormat("%s Maximum loss in case the next operation fails %.2f ", EA_NAME, this.nmlpo);
             }
            else
             {
              this.lote = GetLotByRiskPerOperation(this.gmlpo.value, order_type);
             }
          
          //---
            return this.lote;
           }

          2. Melhorias na função de atribuição de parâmetros (SetEnums)

          Essa função é fundamental e deve ser obrigatoriamente executada no evento OnInit. Agora ela inclui uma verificação adicional que assegura a correta atribuição de valores monetários ou percentuais, dependendo da escolha do usuário. Essa verificação impede a atribuição de valores incorretos ou negativos, especialmente ao usar o modo de valor fixo (money) como critério.

          Implementação aprimorada da função SetEnums:

          //+----------------------------------------------------------------------------------------+
          //| Function to set how losses or gains are calculated,                                    |
          //| by percentage applied to (balance, equity, free margin or net profit) or simply money. |
          //+----------------------------------------------------------------------------------------+
          //Note: This method is mandatory, it must be executed in the OnInit event.
          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_;
          //-- En caso se haya escojido el modo dinero, asignamos la variable que guarda el dinero o porcentage alas varialbes correspondientes
            if(this.gmlpo.mode_calculation_risk  == money)
             {
              this.gmlpo.value = this.gmlpo.percentage_applied_to;
              this.ActivateDynamicRiskPerOperation = false;
             }
            else
              this.gmlpo.value = 0;
              
            this.mdp.value  = this.mdp.mode_calculation_risk  == money ? (this.mdp.percentage_applied_to > 0 ? this.mdp.percentage_applied_to : 0) : 0;
            this.mdl.value  = this.mdl.mode_calculation_risk  == money ? (this.mdl.percentage_applied_to > 0 ? this.mdl.percentage_applied_to : 0) : 0;
            this.ml.value  = this.ml.mode_calculation_risk  == money   ? (this.ml.percentage_applied_to  > 0 ? this.ml.percentage_applied_to  : 0) : 0;
            this.mwl.value  = this.mwl.mode_calculation_risk  == money ? (this.mwl.percentage_applied_to > 0 ? this.mwl.percentage_applied_to : 0) : 0;
           }


          Novas Variáveis da Classe

          Dando continuidade às melhorias, adicionaremos variáveis que permitirão um controle mais preciso e eficiente das operações abertas.

          Primeiro, definiremos um array especial para armazenar informações sobre todas as posições abertas na conta, tanto as abertas manualmente pelo usuário quanto as abertas automaticamente pelo EA:

            //---
            Positions          open_positions[];    
          

          Além disso, adicionaremos duas variáveis principais:

          • Uma variável lógica (booleana) chamada positions_open, que indicará se há posições abertas no mercado. Essa variável desempenha um papel importante na otimização de desempenho, pois evita verificações desnecessárias quando não há posições ativas:

            //--- Boolean variable to check if there are any open operations by the EA or user
            bool               positions_open;

          • Uma variável do tipo double chamada curr_profit, que armazenará o lucro ou prejuízo acumulado em tempo real. Essa variável facilita o cálculo rápido do estado atual das operações:

            //--- Variable to store the current profit of the EA or user
            double             curr_profit;

          Risco dinâmico por operação

          Para o gerenciamento eficaz do risco dinâmico por operação (dinâmico GMLPO), é necessário adicionar e definir claramente algumas variáveis específicas dentro da classe. Essas variáveis garantirão um controle eficiente, permitindo ajustar automaticamente o risco conforme os parâmetros definidos pelo usuário. A seguir, é detalhado o propósito de cada uma delas:

          1. Estrutura para armazenamento de saldos e riscos dinâmicos

          Utilizaremos uma estrutura personalizada chamada Dynamic_gmlpo, que conterá dois arrays dinâmicos:

            Dynamic_gmlpo      dynamic_gmlpos;

          Essa estrutura permite armazenar vários níveis específicos de saldo juntamente com seus respectivos percentuais de risco, facilitando assim o gerenciamento do risco dinâmico.

          2. Variável para definir o tipo de verificação do risco dinâmico

          Definiremos uma variável do tipo enum chamada revision_type, que permitirá ao usuário escolher como as verificações do risco dinâmico serão executadas (a cada tick ou no fechamento das posições):

            ENUM_REVISION_TYPE revision_type;

          3. Variável lógica (booleana) para ativar ou desativar o risco dinâmico

          Essa variável lógica armazenará a decisão de usar ou não o risco dinâmico por operação:

            bool               ActivateDynamicRiskPerOperation;

          4. Variável para armazenar o índice atual do risco dinâmico

          Para manter um controle preciso sobre a mudança do nível de risco dinâmico, é usada uma variável de índice que indica em qual elemento do array dinâmico nos encontramos no momento:

            int                index_gmlpo;

          5. Variável para armazenar o saldo base (inicial)

          Essa variável guarda o saldo inicial ou de referência definido pelo usuário, ao qual serão aplicadas posteriormente as porcentagens configuradas para a ativação do risco dinâmico:

            double             chosen_balance;

          6. Variável que indica o próximo saldo-alvo para a alteração do risco por operação (em direção positiva)

          Essa variável armazena o próximo nível de saldo que deve ser ultrapassado para que o percentual de risco seja ajustado dinamicamente:

            double             NewBalanceToOvercome;

          7. Variável lógica para prevenir erros de estouro de índice fora do intervalo permitido

          Essa variável lógica indica se o saldo mínimo permitido definido pelo usuário (ou o percentual negativo máximo) foi atingido. Se o valor da variável se tornar true, o incremento do índice será interrompido, evitando que o limite máximo permitido seja ultrapassado:

            bool               TheMinimumValueIsExceeded;

          8. Variável para armazenar o percentual inicial de risco por operação

          Essa variável mantém o valor percentual inicial definido para o risco por operação. Ela é usada principalmente para restaurar o valor original quando o saldo se recupera após atingir níveis inferiores:

            double             gmlpo_percentage;

          Essas variáveis permitem implementar de forma eficiente um mecanismo robusto, seguro e fácil de gerenciar, garantindo clareza e controle preciso do risco dinâmico por operação em diversos cenários de negociação.

          Variável para controle do lucro máximo diário

          Para um controle mais rigoroso do lucro diário, introduziremos a variável lógica mdp_is_strict. Essa variável ajudará o sistema de gerenciamento de riscos a determinar se o cálculo do lucro máximo diário deve ou não ser ajustado levando em conta as perdas do dia.

          • Se mdp_is_strict for igual a true: o lucro máximo diário será considerado atingido apenas quando todas as perdas anteriores tiverem sido recuperadas e, a partir daí, for alcançado o nível-alvo original. Por exemplo, se o lucro máximo diário for de 50 USD e durante o dia houver uma perda de 20 USD, será necessário obter um total de 70 USD (recuperar os 20 USD perdidos mais 50 USD de lucro líquido) para que o sistema reconheça o atingimento do objetivo.
          • Se mdp_is_strict for igual a false: as perdas diárias não afetam o cálculo do lucro máximo. Nesse caso, se o alvo de lucro for de 50 USD e tiver ocorrido uma perda de 40 USD, bastará lucrar mais 10 USD (recuperando 40 USD de perdas mais 10 USD de lucro líquido) para atingir o lucro máximo diário.

            bool               mdp_is_strict;

          Variável para o tipo de lote

          Para simplificar a funcionalidade de atribuição de lotes, modificaremos a função GetBatch() de modo que não seja mais necessário especificar manualmente o tipo de lote. Em vez disso, o tipo de lote será inicializado diretamente no construtor ou poderá ser alterado por meio de funções adicionais que desenvolveremos posteriormente. Essa abordagem garante uma configuração mais direta e reduz o risco de erros decorrentes da inserção manual de parâmetros.

            ENUM_GET_LOT       type_get_lot;  
          

          Com essas melhorias, esperamos aumentar a eficiência e a precisão tanto no gerenciamento de riscos quanto na atribuição de lotes dentro da nossa plataforma de negociação.


          Gerenciamento de Posições Abertas

          Nesta parte, concentraremos nossa atenção no controle correto de todas as posições abertas, seja pelo número mágico do EA, seja pelo próprio usuário. Para isso, criaremos várias funções úteis e de fácil entendimento, que permitirão monitorar todas as operações abertas a qualquer momento.

          1. Função para verificar a existência de um ticket no array interno (open_positions)

          A função TheTicketExists tem como objetivo verificar rapidamente se um determinado ticket está contido em nosso array interno de posições abertas (open_positions). Essa operação é particularmente importante quando é necessário confirmar se uma posição já está sendo gerenciada ou se é preciso realizar alguma ação adicional.

          A lógica é simples: percorremos o array e comparamos cada elemento com o ticket passado como argumento. Se uma correspondência for encontrada, retornamos true, caso contrário, retornamos false.

          Declaração:

            bool               TheTicketExists(const ulong ticket); //Check if a ticket is in the operations array

          Implementação:

          //+------------------------------------------------------------------+
          //| Function to check if the ticket is in the array                  |
          //+------------------------------------------------------------------+
          bool CRiskManagemet::TheTicketExists(const ulong ticket)
           {
            for(int i = 0; i < this.GetPositionsTotal() ; i++)
              if(this.open_positions[i].ticket == ticket)
                return true;
                
            return false;
           }

          2. Função para obter o número total de posições abertas

          A função GetPositionsTotal simplesmente retorna o número de elementos presentes no array interno open_positions. Ela permite determinar de forma rápida e fácil quantas posições estão sendo gerenciadas em tempo real.

          Declaração e implementação:

            inline int         GetPositionsTotal() const { return (int)this.open_positions.Size(); } //Get the total number of open positions

          3. Função para obter o número total de posições com flags

          A função GetPositions utiliza um sistema de flags para oferecer maior flexibilidade na contagem de posições abertas de acordo com critérios específicos, como o tipo de operação (compra ou venda). Para isso, o tipo de posição (ENUM_POSITION_TYPE) é convertido em valores compatíveis com flags binárias (geralmente potências de 2 — por exemplo, 2, 4, 8 etc.).

          A lógica consiste em percorrer todas as posições abertas e verificar cada uma delas em relação às flags especificadas, incrementando o contador a cada correspondência encontrada.

          Implementação:

          //+------------------------------------------------------------------+
          //| Function to obtain the number of open positions                  |
          //+------------------------------------------------------------------+
          int CRiskManagemet::GetPositions(int flags) const
          {
          int count = 0;
          
           for(int i = 0; i < ArraySize(this.open_positions) ; i++)
            {
              if(this.open_positions[i].type == POSITION_TYPE_BUY && (flags & FLAG_POSITION_BUY) != 0 )
              {
               count++;
              }
              else if(this.open_positions[i].type == POSITION_TYPE_SELL && (flags & FLAG_POSITION_SELL) != 0 )
              {
               count++;
              }
            } 
          
          return count;
          }

          4. Função para verificar a existência de posições abertas atualmente

          Por fim, a função ThereAreOpenOperations oferece uma maneira rápida e eficiente de saber se o nosso EA está atualmente gerenciando posições abertas. Ela simplesmente retorna o valor da variável lógica interna (positions_open).

          Declaração e implementação:

            inline bool        ThereAreOpenOperations() const { return this.positions_open; } //Check if there are any open operations

          5. Funções auxiliares adicionais

          Além das funções que fazem parte diretamente da classe CRiskManagement, serão criadas funções externas adicionais para facilitar a execução de tarefas específicas, como o fechamento de ordens com base em flags.

          5.1 Função para fechamento de ordens pendentes com base em flags

          Antes de fechar ordens com base em determinados critérios, é necessário converter os tipos de ordens (ENUM_ORDER_TYPE) em flags binárias compatíveis. Esse passo é fundamental para evitar erros durante a execução de operações bit a bit (&).

          Abaixo é apresentada uma função simples que converte um tipo específico de ordem em sua flag correspondente.

          Se for passado um valor inválido ou genérico (WRONG_VALUE), a função retorna uma combinação de todas as flags:

          // Converts an order type to its corresponding flag
          int OrderTypeToFlag(ENUM_ORDER_TYPE type)
           {
            if(type == ORDER_TYPE_BUY)
              return FLAG_ORDER_TYPE_BUY;
            else
              if(type == ORDER_TYPE_SELL)
                return FLAG_ORDER_TYPE_SELL;
              else
                if(type == ORDER_TYPE_BUY_LIMIT)
                  return FLAG_ORDER_TYPE_BUY_LIMIT;
                else
                  if(type == ORDER_TYPE_SELL_LIMIT)
                    return FLAG_ORDER_TYPE_SELL_LIMIT;
                  else
                    if(type == ORDER_TYPE_BUY_STOP)
                      return FLAG_ORDER_TYPE_BUY_STOP;
                    else
                      if(type == ORDER_TYPE_SELL_STOP)
                        return FLAG_ORDER_TYPE_SELL_STOP;
                      else
                        if(type == ORDER_TYPE_BUY_STOP_LIMIT)
                          return FLAG_ORDER_TYPE_BUY_STOP_LIMIT;
                        else
                          if(type == ORDER_TYPE_SELL_STOP_LIMIT)
                            return FLAG_ORDER_TYPE_SELL_STOP_LIMIT;
                          else
                            if(type == ORDER_TYPE_CLOSE_BY)
                              return FLAG_ORDER_TYPE_CLOSE_BY;
                              
            return (FLAG_ORDER_TYPE_BUY | FLAG_ORDER_TYPE_SELL | FLAG_ORDER_TYPE_BUY_LIMIT |
                    FLAG_ORDER_TYPE_SELL_LIMIT | FLAG_ORDER_TYPE_BUY_STOP | FLAG_ORDER_TYPE_SELL_STOP |
                    FLAG_ORDER_TYPE_BUY_STOP_LIMIT | FLAG_ORDER_TYPE_SELL_STOP_LIMIT | FLAG_ORDER_TYPE_CLOSE_BY);
           }
          

          A função principal percorre todas as ordens existentes, fechando aquelas que correspondem às flags especificadas:

          // Close all orders that match the flags in `flags`
          void CloseAllOrders(int flags, CTrade &obj_trade, ulong magic_number_ = NOT_MAGIC_NUMBER)
           {
            ResetLastError();
            for(int i = OrdersTotal() - 1; i >= 0; i--)
             {
              ulong ticket = OrderGetTicket(i);
              
              if(OrderSelect(ticket))
               {
                ENUM_ORDER_TYPE type_order = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
                ulong magic = OrderGetInteger(ORDER_MAGIC);
                int bandera = OrderTypeToFlag(type_order);
                if((bandera & flags) != 0 && (magic == magic_number_ || magic_number_ == NOT_MAGIC_NUMBER))
                 {
                  if(type_order == ORDER_TYPE_BUY || type_order == ORDER_TYPE_SELL)
                    obj_trade.PositionClose(ticket);
                  else
                    obj_trade.OrderDelete(ticket);
                 }
               }
              else
               {
                PrintFormat("Error selecting order %d, last error %d", ticket, GetLastError());
               }
             }
           }

          5.2 Funções para obter o número total de posições abertas

          Também é adicionada uma função externa simples para contar o número total de posições abertas, sem depender exclusivamente da classe CRiskManagement. Primeiramente, é necessário converter os tipos de posições (ENUM_POSITION_TYPE) em flags compatíveis.

          5.2.1 Função para converter ENUM_POSITION_TYPE em uma flag válida

          int PositionTypeToFlag(ENUM_POSITION_TYPE type)
           {
            if(type == POSITION_TYPE_BUY)
              return FLAG_POSITION_BUY;
            else
              if(type == POSITION_TYPE_SELL)
                return FLAG_POSITION_SELL;
                
            return FLAG_POSITION_BUY | FLAG_POSITION_SELL;
           }

          5.2.2 Função para obter o número total de posições abertas com base em flags

          Essa função percorre todas as posições existentes e contabiliza apenas aquelas que correspondem às flags especificadas:

          //---
          int GetPositions(int flags = FLAG_POSITION_BUY | FLAG_POSITION_SELL, ulong magic_number_ = NOT_MAGIC_NUMBER)
           {
            int counter = 0;
            for(int i = PositionsTotal() - 1; i >= 0; i--)
             {
              ulong position_ticket = PositionGetTicket(i);
              if(!PositionSelectByTicket(position_ticket))
                continue; // Si la selección falla, pasa a la siguiente posición
              ulong position_magic = PositionGetInteger(POSITION_MAGIC);
              ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
              // Check if the position type matches the flags
              if((flags & PositionTypeToFlag(type)) != 0 &&
                 (position_magic == magic_number_ || magic_number_ == NOT_MAGIC_NUMBER))
               {
                counter++;
               }
             }
            return counter;
           }


          Métodos de Controle de Excesso de Limites

          Agora definiremos várias funções que permitem determinar se ultrapassamos o limite máximo de prejuízo ou o limite máximo de lucro diário.

          Começaremos desenvolvendo duas funções principais: uma destinada a verificar o excedente de prejuízo e outra a confirmar o alcance do lucro máximo esperado.

          Cada um desses modos está encapsulado em uma função que, dependendo da opção selecionada, informará se a meta foi atingida (retornando o valor true) ou se ainda há caminho a percorrer (retornando o valor false). Por exemplo, no modo "CLOSED_POSITIONS", é essencial saber qual lucro já foi obtido, pois ele deve ser comparado com os limites definidos de prejuízo máximo ou lucro — sejam eles diários ou semanais.

          Vejamos como isso pode ser implementado no código:

          //+------------------------------------------------------------------+
          //| Boolean function to check if a loss was overcome                 |
          //+------------------------------------------------------------------+
          bool CRiskManagemet::IsSuperated(double profit_, double loss_, const MODE_SUPERATE mode) const
           {
            if(loss_ <= 0 || !this.positions_open)
              return false; //if loss is zero return false (the loss is not being used)
          //---
            if(mode == EQUITY) //---
             {
              if(this.curr_profit * -1 > loss_)
                return true;
             }
            else
              if(mode == CLOSE_POSITION)
               {
                if(profit_ * -1 > loss_)
                  return true;
               }
              else
                if(mode == CLOSE_POSITION_AND_EQUITY)
                 {
                  double new_loss = profit_ < 0 ? loss_ - MathAbs(profit_) : loss_;
                  if(this.curr_profit * -1 > new_loss)
                    return true;
                 }
          
            return false;
           }

          Análise dos modos:

          1. EQUITY: nesse modo, comparamos diretamente o lucro atual (equity menos o saldo da conta). Se esse valor negativo ultrapassar o limite de prejuízo estabelecido, significa que o limite foi excedido.

          2. CLOSE_POSITION: nesse modo, é analisado o lucro obtido apenas nas posições já encerradas, e, para fins de comparação correta, o resultado é multiplicado por -1.

          3. CLOSE_POSITION_AND_EQUITY: esse modo é um pouco mais complexo. Aqui, o limite máximo de prejuízo é ajustado levando em consideração o lucro atual. Se o dia for negativo e o lucro estiver abaixo de zero, esse valor é subtraído do limite de prejuízo permitido. Caso o prejuízo atual ultrapasse o valor ajustado, entende-se que o limite também foi excedido.

          Funções especializadas para lucro máximo

          Assim como os prejuízos, também precisamos de uma forma de verificar se o lucro máximo esperado foi ultrapassado. Para isso, criaremos uma função especial que garantirá que nossas operações não causem uma alteração indesejada no valor da meta de lucro diário (mdp).

          O código dessa função é apresentado a seguir:

          //+------------------------------------------------------------------+
          //| Function to check if the maximum profit per day was exceeded     |
          //+------------------------------------------------------------------+
          bool CRiskManagemet::MDP_IsSuperated(const MODE_SUPERATE mode) const
           {
            if(this.mdp.value  <= 0 || !this.positions_open)
              return false; //if loss is zero return false (the loss is not being used)
          //---
            if(mode == EQUITY) //---
             {
              if(this.curr_profit > this.mdp.value)
                return true;
             }
            else
              if(mode == CLOSE_POSITION)
               {
                if(this.daily_profit > this.mdp.value)
                  return true;
               }
              else
                if(mode == CLOSE_POSITION_AND_EQUITY)
                 {
                  double new_mdp = this.daily_profit > 0 ? this.mdp.value - this.daily_profit : (this.mdp_is_strict == false ? this.mdp.value : this.mdp.value + (this.daily_profit * -1));
                  if(this.curr_profit > new_mdp)
                    return true;
                 }
          //---
            return false;
           }

          Detalhes do funcionamento da função:

          1. EQUITY: aqui, o lucro atual (curr_profit) é comparado diretamente com o valor definido para o lucro diário máximo (mdp). Se o lucro atual for maior, a meta é considerada atingida.

          2. CLOSE_POSITION: neste modo, verifica-se se o lucro obtido apenas nas posições fechadas durante o dia (daily_profit) excede o valor de mdp.

          3. CLOSE_POSITION_AND_EQUITY: este é o caso mais complexo e inclui duas situações distintas:

            • Se o lucro diário for positivo, simplesmente subtraímos esse valor de mdp para definir uma nova meta ajustada.
            • Se o lucro diário for negativo e a política de gerenciamento de lucro for rigorosa (mdp_is_strict), somamos o valor absoluto do prejuízo diário a mdp, para compensar as perdas antes que o objetivo possa ser considerado atingido. Caso a política não seja rigorosa, o valor original de mdp é mantido, e as perdas do dia são ignoradas.

          Essa função garante um controle preciso sobre as metas de lucro, assegurando que, mesmo em dias de alta volatilidade, seja possível avaliar corretamente se atingimos ou superamos nossas expectativas financeiras.

          Perda máxima em contas PropFirm

          Dando continuidade ao tema do gerenciamento de perdas, é importante destacar uma característica específica das contas do tipo PropFirm, especialmente em plataformas como a FTMO. Nesses tipos de conta, o limite máximo de perda é fixo, pois ele não muda e permanece constante desde o início do teste. Por exemplo, se começarmos com uma conta de 10 000 USD, o limite máximo de perda será de 9 000 USD. Isso significa que, se em qualquer momento o patrimônio líquido da conta cair abaixo desse valor, a possibilidade de obter financiamento será automaticamente perdida.

          Para simplificar o controle desse limite e evitar complicações adicionais, implementaremos um método especial que verificará se o limite máximo de perda foi alcançado ou ultrapassado:

            //--- Function to check if the maximum loss has been exceeded in a PropFirm account of the FTMO type
            inline bool        IsSuperatedMLPropFirm() const { return (this.ml.value == 0 || !this.positions_open) ? false : AccountInfoDouble(ACCOUNT_EQUITY) < (account_balance_propfirm - (this.ml.value)); }

          A lógica é simples e direta: se não houver posições abertas ou se a variável responsável por controlar o limite máximo de perda (ml) for igual a zero, a verificação não é executada, e a função retorna false. Por outro lado, se houver posições abertas e a variável ml tiver um valor atribuído, a função comparará o patrimônio líquido atual (equity) com a diferença entre o saldo inicial do teste e o valor do limite máximo de perda permitido.

          Funções de verificação

          Além do método descrito acima, criaremos várias funções práticas que permitirão determinar rapidamente se diferentes níveis pré-definidos de lucro ou prejuízo foram alcançados ou ultrapassados. Essas funções funcionarão como chamadas simplificadas (wrappers) para o método geral IsSuperated:

            //--- functions to verify if the established losses were exceeded
            inline bool        ML_IsSuperated(const MODE_SUPERATE mode)   const  {return this.mode_risk_managemet == personal_account ? IsSuperated(this.gross_profit, this.ml.value, mode) : IsSuperatedMLPropFirm();  }
            inline bool        MWL_IsSuperated(const MODE_SUPERATE mode)  const  {return IsSuperated(this.weekly_profit, this.mwl.value, mode);    }
            inline bool        MDL_IsSuperated(const MODE_SUPERATE mode)  const  {return IsSuperated(this.daily_profit, this.mdl.value, mode);     }
            inline bool        GMLPO_IsSuperated()                        const  {return IsSuperated(0, this.gmlpo.value, EQUITY);                 }
            inline bool        NMLPO_IsSuperated()                        const  {return IsSuperated(0, this.nmlpo, EQUITY);                       }
            bool               MDP_IsSuperated(const MODE_SUPERATE mode)  const;

          Essas funções oferecem um meio simples e eficiente de avaliar condições específicas relacionadas a perdas diárias, semanais e assim por diante.

          Máxima perda diária dinâmica na FTMO

          Um aspecto fundamental do gerenciamento de riscos em contas FTMO é compreender que a perda máxima diária não é um valor fixo, mas sim dinâmico. Esse parâmetro muda conforme o lucro acumulado ao longo do dia. Em outras palavras, quanto maior o lucro obtido durante o dia, maior será o limite permitido para eventuais perdas — o que significa que esse limite é variável e não permanece constante.

          Abaixo está apresentada a função que atualiza o valor do prejuízo máximo diário com base no lucro obtido:

            //--- Update Loss (only if ftmo propfirm FTMO is selected)
            inline void        UpdateDailyLossFTMO() {  this.mdl.value += this.daily_profit > 0 ? this.daily_profit : 0; 
                                                        PrintFormat("%s The maximum loss per operation has been modified, its new value: %.2f",EA_NAME,this.mdl.value); }

          Essa função deve ser incluída no método que processa as transações do EA, ou seja, dentro do evento OnTradeTransaction.

          //+------------------------------------------------------------------+
          //| TradeTransaction function                                        |
          //+------------------------------------------------------------------+
          void OnTradeTransaction(const MqlTradeTransaction& trans,
                                  const MqlTradeRequest& request,
                                  const MqlTradeResult& result)


          Gerenciamento de Eventos

          Agora passaremos aos novos "eventos" que serão executados no contexto das operações do EA.

          OnTradeTransaction

          O método OnTradeTransaction desempenha um papel fundamental no gerenciamento dinâmico de riscos, pois é acionado em qualquer evento relacionado às operações de negociação da conta — como a abertura ou fechamento de posições, modificações em ordens existentes ou até mesmo depósitos na conta.

          A função possui três parâmetros importantes:

          • trans contém informações detalhadas sobre a transação atual,
          • request inclui os dados dos pedidos de negociação (pendentes ou recém-executados),
          • result apresenta os resultados obtidos após o processamento desses pedidos.

            Para o gerenciamento de riscos adequado, o foco principal deve estar nas informações contidas em trans, pois é nesse parâmetro que se encontram os dados exatos sobre o tipo de transação e a operação associada a ela.

            Existem vários tipos de transações de negociação definidos na enumeração: 

            ENUM_TRADE_TRANSACTION_TYPE

            Tipos de transações:

            Identificador
            Descrição
             TRADE_TRANSACTION_ORDER_ADD
            Adição de uma nova ordem aberta.
             TRADE_TRANSACTION_ORDER_UPDATE Atualização de uma ordem aberta. Essas alterações incluem não apenas edições explícitas feitas pelo terminal do cliente ou pelo servidor de negociação, mas também mudanças de estado durante o processo de execução (por exemplo, transição de ORDER_STATE_STARTED para ORDER_STATE_PLACED ou de ORDER_STATE_PLACED para ORDER_STATE_PARTIAL, entre outras).
             TRADE_TRANSACTION_ORDER_DELETE Remoção de uma ordem da lista de ordens abertas. Uma ordem pode ser removida após a execução correspondente ou seu cancelamento, sendo movida para o histórico.
             TRADE_TRANSACTION_DEAL_ADD Adição de uma negociação (deal) ao histórico. Ocorre como resultado da execução de uma ordem ou de uma operação que envolva o saldo da conta.
             TRADE_TRANSACTION_DEAL_UPDATE Atualização de uma negociação (deal) no histórico. Pode ocorrer quando uma negociação previamente executada é modificada no servidor. Por exemplo, caso tenha sido ajustada em um sistema de negociação externo (como uma bolsa), para o qual foi encaminhada pela corretora.
             TRADE_TRANSACTION_DEAL_DELETE Remoção de uma negociação (deal) do histórico. Pode acontecer de uma negociação já executada ser removida no servidor. Como no caso de exclusão por parte de um sistema externo (por exemplo, uma bolsa de valores).
             TRADE_TRANSACTION_HISTORY_ADD Adição de uma ordem ao histórico, como resultado de sua execução ou cancelamento.
               TRADE_TRANSACTION_HISTORY_UPDATE Atualização de uma ordem presente no histórico. Esse tipo de transação foi incluído para permitir a expansão de funcionalidades no lado do servidor.
               TRADE_TRANSACTION_HISTORY_DELETE Remoção de uma ordem do histórico. Esse tipo de transação foi incluído para permitir a expansão de funcionalidades no lado do servidor.
             TRADE_TRANSACTION_POSITION Alteração de uma posição que não esteja relacionada à execução de uma negociação (deal). Esse tipo de transação indica que uma posição foi modificada diretamente pelo servidor de negociação. Uma posição pode ser alterada em termos de volume, preço de abertura, bem como nos níveis de Stop Loss e Take Profit. As informações sobre essas modificações são transmitidas pela estrutura MqlTradeTransaction através do manipulador OnTradeTransaction. Alterações de posição (adição, modificação ou exclusão) resultantes da execução de uma negociação (deal) não geram posteriormente uma transação do tipo TRADE_TRANSACTION_POSITION.
             TRADE_TRANSACTION_REQUEST Notificação de que uma solicitação de negociação foi processada pelo servidor e o resultado desse processamento já foi recebido. Para transações desse tipo, dentro da estrutura MqlTradeTransaction, é necessário analisar apenas o campo type (tipo da transação). Para obter informações adicionais, deve-se examinar os parâmetros request e result da função OnTradeTransaction.

            No entanto, o tipo de transação que realmente nos interessa é o seguinte:

            • TRADE_TRANSACTION_DEAL_ADD — indica que uma nova transação foi adicionada ao histórico (seja o fechamento de uma operação ou a confirmação da abertura de uma posição).

            Criação da função OnTradeTransactionEvent

            Em seguida, definiremos uma função especial para gerenciar esse evento de forma clara e eficiente:

              void               OnTradeTransactionEvent(const MqlTradeTransaction& trans);

            A função exigirá apenas o parâmetro trans, que contém todas as informações necessárias sobre a transação atual.

            A estrutura básica da nossa função começará verificando se o tipo de transação corresponde a TRADE_TRANSACTION_DEAL_ADD e selecionando previamente a negociação a partir do histórico:

            //+------------------------------------------------------------------+
            //| OnTradeTransaction Event                                         |
            //+------------------------------------------------------------------+
            void CRiskManagemet::OnTradeTransactionEvent(const MqlTradeTransaction &trans)
             {
              HistoryDealSelect(trans.deal);
              
              if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
               {
                ENUM_DEAL_ENTRY entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(trans.deal, DEAL_ENTRY);
                ulong position_magic = (ulong)HistoryDealGetInteger(trans.deal, DEAL_MAGIC);
                bool is_select = PositionSelectByTicket(trans.position);
            
                if(entry == DEAL_ENTRY_IN && is_select && (this.magic_number == position_magic || this.magic_number == NOT_MAGIC_NUMBER))
                 {
                  Print(EA_NAME, " New position opened with ticket: ", trans.position);
                  this.positions_open = true;   
                  
                  Positions new_pos;
                  new_pos.type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
                  new_pos.ticket = trans.position;
                  
                  AddArrayNoVerification(open_positions, new_pos);
                  return;
                 }
            
                if(entry == DEAL_ENTRY_OUT && TheTicketExists(trans.position) == true  && !is_select)
                 {
                  Print(EA_NAME, " Position with ticket ", trans.position, " has been closed");
                  DeleteTicket(trans.position);
            
                  //---
                  if(this.revision_type == REVISION_ON_CLOSE_POSITION)
                    CheckAndModifyThePercentageOfGmlpo();
            
                  //---
                  if(GetPositionsTotal() == 0)
                    this.positions_open = false;
            
                  //---
                  UpdateProfit();
            
                  //---
                  if(this.mode_risk_managemet == propfirm_ftmo)
                    UpdateDailyLossFTMO();
            
                  SetGMLPO();
                  Print(StringFormat("%-6s| %.2f", "GMLPO", this.gmlpo.value));
                 }
               }
               
             }

            Dentro dessa função, trataremos dois cenários principais:

            • Abertura de posições: confirmamos que a posição foi realmente aberta (is_select) e que seu número mágico coincide com o especificado antes de incluí-la em nossa lista interna (open_positions).

            • Fechamento de posições: verificamos que a posição foi efetivamente encerrada (não pode mais ser selecionada) e que ela ainda está presente em nossa lista de posições gerenciadas. Nesse caso, removemos o ticket do registro, atualizamos variáveis-chave como o lucro acumulado e o estado geral das posições abertas, além de ajustar dinamicamente o limite máximo de perda diária caso o modo FTMO esteja sendo utilizado.

            Dessa forma, o sistema pode reagir antecipadamente a cada transação registrada, garantindo um gerenciamento de riscos claro, eficiente e dinâmico.

            OnTickEvent

            O evento OnTick é um dos mais importantes no funcionamento de um EA, pois é acionado a cada novo tick do mercado.

            No nosso caso específico, ele será usado para atualizar o lucro acumulado total de todas as posições abertas, filtrando-as pelo número mágico, caso este esteja definido. Se o número mágico não estiver configurado, a atualização será realizada para todas as posições abertas, independentemente do valor de magic.

            A definição básica do método será a seguinte:

              void               OnTickEvent();

            Essa função será executada apenas se houver posições abertas, evitando assim cálculos desnecessários.

            A estrutura da função é implementada da seguinte forma:

            //+------------------------------------------------------------------+
            //| Function to execute in OnTick                                    |
            //+------------------------------------------------------------------+
            void CRiskManagemet::OnTickEvent(void)
             {
              if(!positions_open)
                return;
            //---
              GetPositionsProfit();
            
            //---
              if(this.revision_type == REVISION_ON_TICK)
                CheckAndModifyThePercentageOfGmlpo();
             }
            

            Explicação detalhada do processo:
            1. Atualização do lucro total:

              • o método GetPositionsProfit() é responsável por obter e atualizar as informações sobre o lucro ou prejuízo atual das posições sob gerenciamento. Isso garante que tenhamos sempre dados atualizados e precisos sobre o desempenho geral das operações abertas.

            2. Verificação e modificação dinâmica do GMLPO:

              • se selecionarmos a opção REVIEW_ON_TICK, o EA verificará continuamente, a cada novo tick, se algum limite de lucro ou prejuízo previamente definido foi ultrapassado, ajustando dinamicamente o risco permitido por operação. Isso permite regular o tamanho das posições abertas (nível de exposição ao mercado) em tempo real e aumenta significativamente a eficiência do gerenciamento de riscos.


            Funções para risco dinâmico e manipulação do array de posições

            Nesta seção, implementaremos as funções principais que permitirão trabalhar de forma eficaz com o risco dinâmico e executar tarefas práticas relacionadas aos arrays de posições. Essas ferramentas serão úteis em várias etapas da nossa estratégia de gerenciamento de riscos.

            Função para conversão de string em tipo de dado

            Essa função utiliza templates para se adaptar facilmente a diferentes tipos de dados simples (com exceção de classes ou estruturas complexas). Seu principal objetivo é converter uma string em um tipo de dado específico, facilitando o processamento dinâmico de informações em tempo real.

            Implementação:

            template <typename S>
            void StringToType(string token, S &value, ENUM_DATATYPE type)
             {
              if(StringLen(token) == 0)
               {
                Print("Error: String is empty.");
                return;
               }
              switch(type)
               {
                case TYPE_BOOL:
                  value = (S)(StringToInteger(token) != 0);  // Convertir a bool
                  break;
                case TYPE_CHAR:
                  value = (S)((char)StringToInteger(token));  // Convertir a char
                  break;
                case TYPE_UCHAR:
                  value = (S)((uchar)StringToInteger(token));  // Convertir a uchar
                  break;
                case TYPE_SHORT:
                  value = (S)((short)StringToInteger(token));  // Convertir a short
                  break;
                case TYPE_USHORT:
                  value = (S)((ushort)StringToInteger(token));  // Convertir a ushort
                  break;
                case TYPE_COLOR:
                  value = (S)((color)StringToInteger(token));  // Convertir a color
                  break;
                case TYPE_INT:
                  value = (S)(StringToColor(token));  // Convertir a int
                  break;
                case TYPE_UINT:
                  value = (S)((uint)StringToInteger(token));  // Convertir a uint
                  break;
                case TYPE_DATETIME:
                  value = (S)(StringToTime(token));  // Convertir a datetime
                  break;
                case TYPE_LONG:
                  value = (S)((long)StringToInteger(token));  // Convertir a long
                  break;
                case TYPE_ULONG:
                  value = (S)((ulong)StringToInteger(token));  // Convertir a ulong
                  break;
                case TYPE_FLOAT:
                  value = (S)((float)StringToDouble(token));  // Convertir a float
                  break;
                case TYPE_DOUBLE:
                  value = (S)(StringToDouble(token));  // Convertir a double
                  break;
                case TYPE_STRING:
                  value = (S)(token);  // Mantener como string
                  break;
                default:
                  Print("Error: Unsupported data type in ConvertToType.");
                  break;
               }
             }   
            

            Essa função será especialmente importante ao lidar com o risco dinâmico, pois muitos parâmetros virão como strings de texto e precisarão ser convertidos em variáveis numéricas para uso posterior.

            Função para converter uma string de texto em um array de tipos simples

            De forma semelhante, essa função utiliza a flexibilidade dos templates para simplificar o processo de conversão de uma string de texto em um array do tipo desejado. A função executa as seguintes etapas:

            • divide a string original usando um delimitador específico (por padrão, uma vírgula “,”);
            • armazena cada elemento obtido em um array temporário;
            • converte cada elemento individual para o tipo de dado especificado e os atribui ao array de destino.
              Implementação:
              //---
              template <typename S>
              void StringToArray(S &array_receptor[], string cadena, ENUM_DATATYPE type_data, ushort separator = ',')
               {
                string result[];
                int num = StringSplit(cadena, separator, result);
                ArrayResize(array_receptor, ArraySize(result));
                for(int i = 0; i < ArraySize(array_receptor) ; i++)
                 {
                  S value;
                  StringToType(result[i], value, type_data);
                  array_receptor[i]  = value;
                 }
               }

              Por exemplo, em situações práticas relacionadas ao risco dinâmico, podemos receber dados no formato “5.0,4.5,3.0”, que serão automaticamente convertidos em um array do tipo double. Isso simplifica muito o gerenciamento de parâmetros dinâmicos dentro do nosso sistema.

              Funções avançadas para manipulação de arrays

              A seguir estão apresentadas várias funções úteis que facilitam o gerenciamento eficiente de arrays, especialmente quando trabalhamos com estratégias dinâmicas e estruturas complexas no contexto do gerenciamento de riscos.

              Função para remover vários elementos de um array por índice

              Essa função permite remover simultaneamente vários elementos de um array, aumentando significativamente a eficiência e a legibilidade do código. A lógica principal consiste em ordenar previamente os índices dos elementos que desejamos excluir e, em seguida, copiar apenas os elementos que precisam ser mantidos para um novo índice do array original.

              Implementação:

              //---
              template <typename T>
              void RemoveMultipleIndexes(T &arr[], int &indexes_to_remove[])
               {
                int oldSize = ArraySize(arr);
                int removeSize = ArraySize(indexes_to_remove);
                if(removeSize == 0 || oldSize == 0)
                  return;
              // Ordenamos los índices para garantizar eficiencia al recorrerlos
                ArraySort(indexes_to_remove);
                int writeIndex = 0, readIndex = 0, removeIndex = 0;
                while(readIndex < oldSize)
                 {
                  if(removeIndex < removeSize && readIndex == indexes_to_remove[removeIndex])
                   {
                    removeIndex++;
                   }
                  else
                   {
                    arr[writeIndex] = arr[readIndex];
                    writeIndex++;
                   }
                  readIndex++;
                 }
                ArrayResize(arr, writeIndex);
               }

              Função para adicionar elementos a um array

              A próxima função permite adicionar facilmente novos elementos a um array de qualquer tipo, adaptando-se automaticamente ao tipo de dado.

              Implementação:

              //---
              template <typename X>
              void AddArrayNoVerification(X &array[], const X &value)
               {
                ArrayResize(array, array.Size() + 1);
                array[array.Size() - 1] = value;
               }

              É simples: aumentamos o tamanho do array em uma unidade e atribuímos o novo valor ao último elemento.

              Função especializada para remover um elemento com base em um ticket

              Essa função foi projetada especificamente para arrays compostos por estruturas que contenham o campo “ticket”. Ela permite remover um elemento específico com base nesse identificador único.

              Implementação:

              //---
              template<typename T>
              bool RemoveIndexFromAnArrayOfPositions(T &array[], const ulong ticket)
               {
                int size = ArraySize(array);
                int index = -1;
              // Search index and move elements in a single loop
                for(int i = 0; i < size; i++)
                 {
                  if(array[i].ticket == ticket)
                   {
                    index = i;
                   }
                  if(index != -1 && i < size - 1)
                   {
                    array[i] = array[i + 1]; // Move the elements
                   }
                 }
                if(index == -1)
                  return false;
              // Reducir el tamaño del array
                if(size > 1)
                  ArrayResize(array, size - 1);
                else
                  if(size <= 1)
                    ArrayFree(array);
                    
                return true;
               }

              É importante observar que essa função exige obrigatoriamente que a estrutura inclua um campo chamado “ticket”. Caso tentemos utilizá-la em uma estrutura que não possua esse elemento, ocorrerá um erro:

              'ticket' - undeclared identifier        
              in template 'bool RemoveIndexFromAnArrayOfPositions(T&[],const ulong)' specified with [T=Message]      
              see template instantiation 'ExtraFunctions::RemoveIndexFromAnArrayOfPositions<Message>' 
              1 errors, 0 warnings            
              

              Neste exemplo, a estrutura “Message” não contém o campo “ticket”.

              Função adicional para repetição de uma string

              Por fim, temos uma função útil que gera uma sequência de texto repetida o número de vezes desejado. Isso é especialmente conveniente para imprimir tabelas ou criar divisões visuais de informações.

              Implementação:

              string StringRepeat(string str, int count)
               {
                string result = "";
                for(int i = 0; i < count; i++)
                  result += str;
              
                return result;
               } 
              

              Por exemplo, ao chamar 

              StringRepeat("-", 10) мы получим "----------".

              Essas funções avançadas simplificam significativamente o trabalho com arrays e melhoram a legibilidade do código, fornecendo ferramentas eficientes e versáteis para o gerenciamento dinâmico e preciso de riscos dentro de nossos sistemas de negociação.


              Construção do Risco Dinâmico

              Finalmente, chegamos à parte mais importante a implementação do risco dinâmico. Para isso, utilizaremos duas funções principais que simplificarão consideravelmente seu processamento e sua capacidade de adaptação.

              Antes de começar, é necessário incluir a seguinte biblioteca:

              #include <Generic\HashMap.mqh>

              Essa biblioteca nos ajudará a organizar e processar corretamente os dados relacionados ao risco dinâmico.

              Função de inicialização do risco dinâmico

              Seguindo o conceito apresentado nas partes anteriores deste estudo, nosso sistema de risco dinâmico será baseado em uma estrutura que contém dois arrays do tipo double: um para armazenar os novos valores de risco a serem aplicados e outro para definir os níveis específicos de saldo ou percentuais que ativarão essas alterações.

              Devido às limitações da linguagem MQL5, não é possível passar arrays do tipo double diretamente como parâmetros do EA. Para contornar essa limitação, utilizaremos strings de texto separadas por vírgulas, que serão posteriormente convertidas em arrays numéricos com o auxílio das funções implementadas anteriormente.

              A função principal de inicialização do risco dinâmico será responsável por converter essas strings em arrays numéricos, verificar a ausência de duplicatas e ordenar corretamente os valores obtidos.

              A declaração da função será a seguinte:

                void               SetDynamicGMLPO(string percentages_to_activate, string risks_to_be_applied, ENUM_REVISION_TYPE revision_type_);

              A rotina interna funcionará da seguinte maneira:

              1. Verificação do uso do risco dinâmico

              Antes de executar qualquer operação, verificamos se o percentual GMLPO foi definido corretamente:

               if(this.gmlpo.assigned_percentage == 0)
                  return;
                  
                if(this.gmlpo.mode_calculation_risk == money)
                {
                this.ActivateDynamicRiskPerOperation = false;
                Print(EA_NAME, __FUNCTION__, "::'Money' mode is not valid for dynamic risk, change it to 'Percentage %' or change the group mode to 'No dynamic risk for risk per operation' ");
                return;
                } 

              2. Definição do tipo de verificação selecionado

                this.revision_type = revision_type_;
              

              3. Conversão de strings em arrays numéricos

              Utilizando as funções criadas anteriormente, convertemos as strings em arrays do tipo double:

              //---
                ExtraFunctions::StringToArray(this.dynamic_gmlpos.balance_to_activate_the_risk, percentages_to_activate, TYPE_DOUBLE, ',');
                ExtraFunctions::StringToArray(this.dynamic_gmlpos.risk_to_be_adjusted, risks_to_be_applied, TYPE_DOUBLE, ',');

              4. Verificação dos arrays resultantes

              //---
                if(this.dynamic_gmlpos.risk_to_be_adjusted.Size() < 1 && this.dynamic_gmlpos.balance_to_activate_the_risk.Size() < 1)
                 {
                  Print(EA_NAME, __FUNCTION__, "::Critical error: the size of the array is less than 1");
                  this.ActivateDynamicRiskPerOperation = false;
                  return;
                 }
                if(this.dynamic_gmlpos.risk_to_be_adjusted.Size() != this.dynamic_gmlpos.balance_to_activate_the_risk.Size())
                 {
                  Print(EA_NAME, __FUNCTION__, "::Critical error the double arrays for the risk due to dynamic operation are not equal");
                  this.ActivateDynamicRiskPerOperation = false;
                  return;
                 }
              
                Print(EA_NAME, " Arrays before revision");
                PrintArrayAsTable(dynamic_gmlpos.balance_to_activate_the_risk, "Negative percentages to modify the risk", "balance");
                PrintArrayAsTable(dynamic_gmlpos.risk_to_be_adjusted, "Risk to be adjusted", "new risk");

              Em seguida, é necessário garantir que ambos os arrays tenham o mesmo comprimento e não estejam vazios. Caso contrário, o risco dinâmico será desativado para evitar erros.

              5. Limpeza e preparação da estrutura final

              Por fim, limpamos a estrutura HashMap, selecionamos o saldo de referência apropriado com base no tipo de conta (FTMO ou pessoal) e preparamos um array auxiliar para eventuais índices duplicados ou inválidos:

               balanceRiskMap.Clear();
                this.chosen_balance = this.mode_risk_managemet == propfirm_ftmo ? this.account_balance_propfirm  : AccountInfoDouble(ACCOUNT_BALANCE);
                int indexes_to_remove[];

              Com essa função, garantimos uma configuração sólida e estável do risco dinâmico, totalmente adaptada às necessidades específicas de cada conta ou estratégia de negociação, assegurando um controle de risco preciso e eficiente.

              6. Laço para adicionar elementos válidos ao HashMap

              Em seguida, é implementado o ciclo principal, responsável por adicionar ao HashMap apenas os elementos válidos, garantindo assim uma gestão ordenada e eficiente do risco dinâmico. Um elemento é considerado inválido nas seguintes situações:

              • se o percentual que ativa o risco for menor ou igual a zero;
              • se o novo valor de risco for menor ou igual a zero;
              • se o elemento já estiver presente no HashMap (duplicado).

                Quando um elemento inválido é detectado, ele é temporariamente adicionado ao array indexes_to_remove para posterior exclusão. Ao avaliar ambos os arrays (balance_to_activate_the_risk e risk_to_be_adjusted), basta que um dos valores do par seja incorreto para que a dupla inteira seja descartada, garantindo a integridade e a consistência dos dados.

                Implementação do ciclo:

                //---
                  for(int i = 0 ; i < ArraySize(dynamic_gmlpos.balance_to_activate_the_risk) ; i++)
                   {
                
                    if(dynamic_gmlpos.balance_to_activate_the_risk[i] <= 0)
                     {
                      Print(EA_NAME, " (Warning) The percentage value that will be exceeded to modify the risk is 0 or less than this (it will not be taken into account)");
                      ExtraFunctions::AddArrayNoVerification(indexes_to_remove, i);
                      continue;
                     }
                
                    if(dynamic_gmlpos.risk_to_be_adjusted[i] <= 0)
                     {
                      Print(EA_NAME, " (Warning) The new percentage to which the field is modified is 0 or less than this (it will not be taken into account)");
                      ExtraFunctions::AddArrayNoVerification(indexes_to_remove, i);
                      continue;
                     }
                
                    if(balanceRiskMap.ContainsKey(dynamic_gmlpos.balance_to_activate_the_risk[i]) == false)
                      balanceRiskMap.Add(dynamic_gmlpos.balance_to_activate_the_risk[i], dynamic_gmlpos.risk_to_be_adjusted[i]);
                    else
                      ExtraFunctions::AddArrayNoVerification(indexes_to_remove, i);
                   }

                Por exemplo, suponhamos que temos os seguintes arrays:

                • [1, 3, 5]
                • [0.1, 0.3, 0.0]

                  Nesse caso, o último par (5 - 0,0) será considerado inválido, pois o valor de risco ajustado é zero; portanto, o elemento “5” será posteriormente removido do array correspondente.

                  7. Remoção de duplicados e elementos inválidos

                  Após a identificação dos elementos inválidos ou duplicados, passamos à etapa de remoção. Além disso, reorganizamos o array principal balance_to_activate_the_risk para garantir a devida ordem e consistência dos dados:

                  //---
                    ExtraFunctions::RemoveMultipleIndexes(dynamic_gmlpos.balance_to_activate_the_risk, indexes_to_remove);
                    ArraySort(dynamic_gmlpos.balance_to_activate_the_risk);
                    ArrayResize(dynamic_gmlpos.risk_to_be_adjusted, ArraySize(dynamic_gmlpos.balance_to_activate_the_risk)); 
                   
                  

                  O ajuste do tamanho do array risk_to_be_adjusted é necessário para manter a coerência entre os dois arrays após a remoção.

                  8. Ajuste e conversão dos valores de risco

                  Em seguida, ajustamos e convertemos os valores percentuais armazenados em balance_to_activate_the_risk em valores monetários específicos, baseados no saldo selecionado da conta. O array risk_to_be_adjusted é então atualizado com os valores correspondentes:

                  //---
                    for(int i = 0 ; i < ArraySize(dynamic_gmlpos.balance_to_activate_the_risk) ; i++)
                     {
                      double value;
                      balanceRiskMap.TryGetValue(this.dynamic_gmlpos.balance_to_activate_the_risk[i], value);
                      dynamic_gmlpos.risk_to_be_adjusted[i] = value;
                      dynamic_gmlpos.balance_to_activate_the_risk[i] =  this.chosen_balance - (this.chosen_balance * (dynamic_gmlpos.balance_to_activate_the_risk[i] / 100.0));
                     }

                  9. Inicialização final do risco dinâmico

                  Por fim, inicializamos as variáveis necessárias para garantir que o risco dinâmico esteja pronto para operar corretamente:

                  //---
                    this.index_gmlpo = 0;
                    this.ActivateDynamicRiskPerOperation = true;
                    this.TheMinimumValueIsExceeded = false;
                    this.NewBalanceToOvercome = 0.00;
                    Print(EA_NAME, " Arrays ready: ");
                    PrintArrayAsTable(dynamic_gmlpos.balance_to_activate_the_risk, "Negative percentages to modify the risk", "balance");
                    PrintArrayAsTable(dynamic_gmlpos.risk_to_be_adjusted, "Risk to be adjusted", "new risk");

                  Dessa forma, a configuração dinâmica do risco torna-se precisa, eficiente e constantemente adaptável aos resultados atuais da nossa estratégia de negociação.

                  Função completa:

                  //+------------------------------------------------------------------+
                  //| Function to set dynamic risks per operation                      |
                  //+------------------------------------------------------------------+
                  void CRiskManagemet::SetDynamicGMLPO(string percentages_to_activate, string risks_to_be_applied, ENUM_REVISION_TYPE revision_type_)
                   {
                    if(this.gmlpo.assigned_percentage <= 0)
                      return;
                    
                    if(this.gmlpo.mode_calculation_risk == money)
                    {
                    this.ActivateDynamicRiskPerOperation = false;
                    Print(EA_NAME, __FUNCTION__, "::'Money' mode is not valid for dynamic risk, change it to 'Percentage %' or change the group mode to 'No dynamic risk for risk per operation' ");
                    return;
                    }
                    
                  //---
                    this.revision_type = revision_type_;
                  
                  //---
                    ExtraFunctions::StringToArray(this.dynamic_gmlpos.balance_to_activate_the_risk, percentages_to_activate, TYPE_DOUBLE, ',');
                    ExtraFunctions::StringToArray(this.dynamic_gmlpos.risk_to_be_adjusted, risks_to_be_applied, TYPE_DOUBLE, ',');
                  
                  //---
                    if(this.dynamic_gmlpos.risk_to_be_adjusted.Size() < 1 || this.dynamic_gmlpos.balance_to_activate_the_risk.Size() < 1)
                     {
                      Print(EA_NAME, __FUNCTION__, "::Critical error: the size of the array is less than 1");
                      this.ActivateDynamicRiskPerOperation = false;
                      return;
                     }
                    if(this.dynamic_gmlpos.risk_to_be_adjusted.Size() != this.dynamic_gmlpos.balance_to_activate_the_risk.Size())
                     {
                      Print(EA_NAME, __FUNCTION__, "::Critical error the double arrays for the risk due to dynamic operation are not equal");
                      this.ActivateDynamicRiskPerOperation = false;
                      return;
                     }
                  
                    Print(EA_NAME, " Arrays before revision");
                    PrintArrayAsTable(dynamic_gmlpos.balance_to_activate_the_risk, "Negative percentages to modify the risk", "balance");
                    PrintArrayAsTable(dynamic_gmlpos.risk_to_be_adjusted, "Risk to be adjusted", "new risk");
                  
                  //---
                    balanceRiskMap.Clear();
                    this.chosen_balance = this.mode_risk_managemet == propfirm_ftmo ? this.account_balance_propfirm  : AccountInfoDouble(ACCOUNT_BALANCE);
                    int indexes_to_remove[];
                  
                  //---
                    for(int i = 0 ; i < ArraySize(dynamic_gmlpos.balance_to_activate_the_risk) ; i++)
                     {
                  
                      if(dynamic_gmlpos.balance_to_activate_the_risk[i] <= 0)
                       {
                        Print(EA_NAME, " (Warning) The percentage value that will be exceeded to modify the risk is 0 or less than this (it will not be taken into account)");
                        ExtraFunctions::AddArrayNoVerification(indexes_to_remove, i);
                        continue;
                       }
                  
                      if(dynamic_gmlpos.risk_to_be_adjusted[i] <= 0)
                       {
                        Print(EA_NAME, " (Warning) The new percentage to which the field is modified is 0 or less than this (it will not be taken into account)");
                        ExtraFunctions::AddArrayNoVerification(indexes_to_remove, i);
                        continue;
                       }
                  
                      if(balanceRiskMap.ContainsKey(dynamic_gmlpos.balance_to_activate_the_risk[i]) == false)
                        balanceRiskMap.Add(dynamic_gmlpos.balance_to_activate_the_risk[i], dynamic_gmlpos.risk_to_be_adjusted[i]);
                      else
                        ExtraFunctions::AddArrayNoVerification(indexes_to_remove, i);
                     }
                  
                  //---
                    ExtraFunctions::RemoveMultipleIndexes(dynamic_gmlpos.balance_to_activate_the_risk, indexes_to_remove);
                    ArraySort(dynamic_gmlpos.balance_to_activate_the_risk);
                    ArrayResize(dynamic_gmlpos.risk_to_be_adjusted, ArraySize(dynamic_gmlpos.balance_to_activate_the_risk));
                  
                  //---
                    for(int i = 0 ; i < ArraySize(dynamic_gmlpos.balance_to_activate_the_risk) ; i++)
                     {
                      double value;
                      balanceRiskMap.TryGetValue(this.dynamic_gmlpos.balance_to_activate_the_risk[i], value);
                      dynamic_gmlpos.risk_to_be_adjusted[i] = value;
                      dynamic_gmlpos.balance_to_activate_the_risk[i] =  this.chosen_balance - (this.chosen_balance * (dynamic_gmlpos.balance_to_activate_the_risk[i] / 100.0));
                     }
                  
                  //---
                    this.index_gmlpo = 0;
                    this.ActivateDynamicRiskPerOperation = true;
                    this.TheMinimumValueIsExceeded = false;
                    this.NewBalanceToOvercome = 0.00;
                    Print(EA_NAME, " Arrays ready: ");
                    PrintArrayAsTable(dynamic_gmlpos.balance_to_activate_the_risk, "Negative percentages to modify the risk", "balance");
                    PrintArrayAsTable(dynamic_gmlpos.risk_to_be_adjusted, "Risk to be adjusted", "new risk");
                   }

                  Explicação clara e detalhada da função de modificação dinâmica da porcentagem de risco (GMLPO)

                  A seguir, explicaremos passo a passo a função CheckAndModifyThePercentageOfGmlpo, que permite gerenciar dinamicamente o risco por operação, ajustando-o automaticamente conforme os níveis de patrimônio líquido (equity) atingidos na conta de negociação.

                  1. Verificação inicial

                  A função começa verificando se o gerenciamento dinâmico de risco está ativado. Caso esteja desativado, a execução é encerrada imediatamente.

                  if(!this.ActivateDynamicRiskPerOperation)
                     return;

                  2. Verificação do saldo atual

                  Em seguida, a função obtém o patrimônio líquido atual da conta (o valor real da conta, considerando lucros e perdas flutuantes). Esse valor é comparado com o saldo previamente selecionado (chosen_balance).

                  Se o patrimônio líquido atual for maior que o saldo escolhido e o próximo nível-alvo de saldo ainda não tiver sido atingido, a função não faz nenhuma modificação e encerra sua execução.

                  double account_equity = AccountInfoDouble(ACCOUNT_EQUITY);
                  
                  if(account_equity > this.chosen_balance && this.NewBalanceToOvercome != this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo])
                     return;

                  3. Ajuste do risco quando o patrimônio líquido diminui

                  Se o patrimônio líquido cair abaixo dos níveis definidos, o sistema identifica o próximo nível inferior de saldo.

                  • Quando é detectado que o saldo caiu abaixo de um determinado nível:

                    1. é determinado o novo percentual de risco correspondente a esse nível;
                    2. as variáveis internas são atualizadas para refletir o novo nível de risco.
                    if(this.TheMinimumValueIsExceeded == false)
                     {
                      if(account_equity < this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo])
                       {
                        PrintFormat("%s The risk percentage per operation has been modified because the value of %.2f was exceeded", EA_NAME, this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo]);
                  
                        while(IsStopped() == false && index_gmlpo < (int)this.dynamic_gmlpos.balance_to_activate_the_risk.Size())
                         {
                          if(index_gmlpo < (int)this.dynamic_gmlpos.balance_to_activate_the_risk.Size() - 1)
                           {
                            if(this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo]  > account_equity
                               && account_equity > this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo + 1])
                             {
                              this.NewBalanceToOvercome = this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo];
                              this.gmlpo.assigned_percentage = this.dynamic_gmlpos.risk_to_be_adjusted[index_gmlpo];
                              index_gmlpo ++;
                              PrintFormat("%s The new balance to overcome: %.2f", EA_NAME, NewBalanceToOvercome);
                              break;
                             }
                           }
                          else
                            if(index_gmlpo == this.dynamic_gmlpos.balance_to_activate_the_risk.Size() -  1)
                             {
                              if(this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo]  > account_equity)
                               {
                                this.NewBalanceToOvercome = this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo];
                                this.gmlpo.assigned_percentage = this.dynamic_gmlpos.risk_to_be_adjusted[index_gmlpo];
                                this.TheMinimumValueIsExceeded = true;
                                PrintFormat("%s The new balance to overcome: %.2f", EA_NAME, NewBalanceToOvercome);
                                PrintFormat("%s The minimum value %.2f was exceeded", EA_NAME, this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo]);
                                break;
                               }
                             }
                  
                          index_gmlpo++;
                         }
                  
                        PrintFormat("%s The new risk per operation %.2f", EA_NAME, this.gmlpo.assigned_percentage);
                        SetGMLPO();
                       }
                     }

                  4. Restauração do risco quando o patrimônio líquido volta a crescer


                  Se posteriormente o patrimônio líquido aumentar novamente e ultrapassar o nível-alvo anteriormente atingido:

                  • o sistema ajusta novamente o risco por operação, restaurando os níveis anteriores conforme definidos nos arrays.

                    if(this.NewBalanceToOvercome > 0.00)
                     {
                      if(account_equity > this.NewBalanceToOvercome)
                       {
                        PrintFormat("%s Equity %.2f exceeded balance to shift risk to %.2f", EA_NAME, account_equity, NewBalanceToOvercome);
                        while(!IsStopped() && index_gmlpo > 0)
                         {
                          if(index_gmlpo > 0)
                           {
                            if(this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo]  < account_equity
                               && account_equity < this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo - 1])
                             {
                              break;
                             }
                  
                            this.index_gmlpo--;
                           }
                         }
                  
                        this.TheMinimumValueIsExceeded = false;
                        if(this.index_gmlpo == 0)
                         {
                          Print(EA_NAME, " Excellent, the balance has been positively exceeded");
                          PrintFormat("%s The risk percentage per operation has been modified because the value of %.2f was exceeded", EA_NAME, this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo]);
                          this.gmlpo.assigned_percentage = this.gmlpo_percentage;
                          this.NewBalanceToOvercome = 0.00;
                  
                          PrintFormat("%s The new risk per operation %.2f", EA_NAME, this.gmlpo.assigned_percentage);
                          SetGMLPO();
                         }
                        else
                          if(index_gmlpo > 0)
                           {
                            Print(EA_NAME, " Excellent, the balance has been positively exceeded");
                            PrintFormat("%s The risk percentage per operation has been modified because the value of %.2f was exceeded", EA_NAME, this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo]);
                            this.NewBalanceToOvercome  = this.dynamic_gmlpos.balance_to_activate_the_risk[index_gmlpo - 1];
                            this.gmlpo.assigned_percentage = this.dynamic_gmlpos.risk_to_be_adjusted[index_gmlpo - 1];
                  
                            PrintFormat("%s The new risk per operation %.2f", EA_NAME, this.gmlpo.assigned_percentage);
                            PrintFormat("%s The new balance to overcome: %.2f", EA_NAME, NewBalanceToOvercome);
                            SetGMLPO();
                           }
                       }
                     }


                  Considerações Finais

                  Ao longo deste ciclo de artigos, analisamos passo a passo como desenvolver um sistema de gerenciamento de riscos completo e confiável. Nesta parte final, focamos na consolidação de todos os detalhes e apresentamos um conceito avançado e extremamente útil, pois o risco dinâmico por operação. Esse recurso permite ajustar automaticamente o nível de risco com base nos resultados reais da conta, o que é um fator essencial para preservar o capital e aumentar a eficiência do trader.

                  Espero sinceramente que este material seja útil, especialmente para aqueles que estão começando sua jornada no fascinante mundo da programação em MQL5.

                  Na próxima parte, dedicada ao gerenciamento de riscos, daremos um passo adiante e aprenderemos na prática como aplicar tudo o que foi estudado, integrando este sistema avançado de gerenciamento ao robô de negociação.

                  Para isso, utilizaremos o indicador Order Blocks, desenvolvido anteriormente:

                  Além disso, teremos a oportunidade de observar de forma prática as vantagens concretas de um gerenciamento de riscos eficaz em comparação com operações realizadas sem qualquer controle. Por fim, configuraremos parâmetros específicos que simplificarão consideravelmente o uso diário dessas ferramentas avançadas em qualquer estratégia automatizada.


                  Arquivos utilizados/melhorados 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 pelo gerenciamento de riscos no sistema. Neste arquivo são definidas, desenvolvidas e ampliadas todas as funções relacionadas ao controle de lucros e perdas.


                  Traduzido do espanhol pela MetaQuotes Ltd.
                  Artigo original: https://www.mql5.com/es/articles/17508

                  Arquivos anexados |
                  Risk_Management.mqh (126.71 KB)
                  Automatização de estratégias de trading com MQL5 (Parte 13): Criação de um algoritmo de negociação para o padrão "Cabeça e Ombros" Automatização de estratégias de trading com MQL5 (Parte 13): Criação de um algoritmo de negociação para o padrão "Cabeça e Ombros"
                  Neste artigo, automatizaremos o padrão "Cabeça e Ombros" em MQL5. Analisaremos sua arquitetura, implementaremos um EA para sua detecção e negociação, e testaremos os resultados no histórico. Esse processo revela um algoritmo de negociação prático, que pode ser aprimorado.
                  Automatizando Estratégias de Negociação em MQL5 (Parte 3): O Sistema Zone Recovery RSI para Gestão Dinâmica de Operações Automatizando Estratégias de Negociação em MQL5 (Parte 3): O Sistema Zone Recovery RSI para Gestão Dinâmica de Operações
                  Neste artigo, criamos um Sistema EA Zone Recovery RSI em MQL5, utilizando sinais de RSI para acionar operações e uma estratégia de recuperação para gerenciar perdas. Implementamos uma classe "ZoneRecovery" para automatizar as entradas de operações, a lógica de recuperação e o gerenciamento de posições. O artigo conclui com insights de backtesting para otimizar a performance e aprimorar a eficácia do EA.
                  Desenvolvimento de um sistema de monitoramento de entradas de swing (EA) Desenvolvimento de um sistema de monitoramento de entradas de swing (EA)
                  À medida que o ano se aproxima do fim, traders de longo prazo costumam refletir sobre o histórico do mercado para analisar seu comportamento e tendências, visando projetar potenciais movimentos futuros. Neste artigo, exploraremos o desenvolvimento de um Expert Advisor (EA) de monitoramento de entradas de longo prazo usando MQL5. O objetivo é abordar o desafio das oportunidades de negociação de longo prazo perdidas devido ao trading manual e à ausência de sistemas automatizados de monitoramento. Usaremos um dos pares mais negociados como exemplo para estruturar e desenvolver nossa solução de forma eficaz.
                  Gerenciamento de riscos (Parte 3): Criação da classe principal de gerenciamento de riscos Gerenciamento de riscos (Parte 3): Criação da classe principal de gerenciamento de riscos
                  Neste artigo começaremos a criação da classe principal de gerenciamento de riscos, que será o elemento chave para o controle de riscos no sistema. Vamos nos concentrar na construção das bases, na definição das principais estruturas, variáveis e funções. Além disso, implementaremos os métodos necessários para atribuir valores de lucro máximo e prejuízo máximo, estabelecendo assim o alicerce do gerenciamento de riscos.