English Русский 中文 Español 日本語
preview
Gerenciamento de riscos (Parte 1): Fundamentos da construção de uma classe de gerenciamento de riscos

Gerenciamento de riscos (Parte 1): Fundamentos da construção de uma classe de gerenciamento de riscos

MetaTrader 5Exemplos |
91 0
Niquel Mendoza
Niquel Mendoza



Introdução 

Neste artigo, analisaremos o que é gerenciamento de riscos no trading e sua importância na negociação automática. Começaremos pelos conceitos básicos, estabelecendo a base para entender como um gerenciamento de riscos adequado pode determinar a diferença entre sucesso e fracasso nos mercados financeiros. 

Em seguida, criaremos passo a passo uma classe em MQL5, que implementa um gerenciamento de riscos completo, permitindo controlar aspectos essenciais, como tamanho do lote, perdas máximas e lucro esperado.


O que é gerenciamento de riscos 

O gerenciamento de riscos é um princípio fundamental de qualquer estratégia de negociação. Seu objetivo principal é monitorar e controlar as posições abertas, garantindo que as perdas não ultrapassem os limites definidos pelo trader, como limites diários, semanais ou gerais.

Além disso, o gerenciamento de riscos permite determinar o tamanho adequado do lote para cada operação com base nas regras e preferências do usuário. Isso não apenas protege o capital, mas também otimiza a eficiência da estratégia, garantindo que as operações estejam alinhadas ao perfil de risco estabelecido.

De modo geral, um gerenciamento de riscos competente não só reduz a probabilidade de perdas catastróficas, como também assegura uma abordagem disciplinada na tomada de decisões financeiras sensatas.


Importância para a negociação automática  

O gerenciamento de riscos desempenha um papel decisivo na negociação automática, pois funciona como um sistema de controle que impede erros custosos, como excesso de operações ou exposição a riscos desnecessários. No contexto de bots de negociação, em que as decisões são tomadas de forma totalmente automática, um gerenciamento de riscos adequado garante uma execução disciplinada e eficiente das estratégias.

Isso é especialmente valioso em situações como a seleção para financiamento, pois o cumprimento rigoroso dos limites de perdas diárias, semanais ou gerais pode se tornar um fator decisivo para o sucesso ou o fracasso. O gerenciamento de riscos permite estabelecer com precisão essas barreiras, protegendo o capital do usuário e otimizando suas atividades em um ambiente competitivo.

Além disso, isso ajuda o bot a agir de maneira mais estratégica, estabelecendo limites claros para evitar excesso de operações ou a aceitação de riscos desproporcionais. Graças ao cálculo automático dos tamanhos de lote e à limitação das perdas por operação, o gerenciamento de riscos não apenas protege o capital, mas também proporciona ao trader tranquilidade, pois ele sabe que seu bot opera dentro de limites controlados e seguros.


Conceitos introdutórios de gerenciamento de riscos 

Antes de iniciar a programação, é importante entender as principais variáveis e conceitos que utilizaremos no gerenciamento de riscos. Esses conceitos são fundamentais para desenvolver um sistema eficaz, que proteja o capital do usuário e garanta uma negociação controlada. A seguir, analisaremos cada um deles:

1. Perda máxima diária

É o limite máximo de perda que o bot pode acumular em um único dia (24 horas). Se esse limite for alcançado, o bot provavelmente fechará todas as posições abertas e suspenderá qualquer atividade de negociação até o próximo dia. Essa abordagem ajuda a evitar que uma sequência de operações malsucedidas cause impacto significativo no capital.

2. Perda máxima semanal

Funciona de forma semelhante à perda diária, mas aplicada ao intervalo de uma semana. Se o bot ultrapassar o limite definido, ele encerrará as operações até o início da semana seguinte. Esse parâmetro é ideal para impedir perdas significativas em horizontes temporais mais longos.

3. Perda máxima geral

É o limite absoluto de perdas, atingido o qual é ativada uma estratégia especial de recuperação. Ela pode incluir a reduçãao dos tamanhos dos lotes e uma negociação mais cautelosa, com o objetivo de recuperar gradualmente o capital perdido. Esse conceito permite controlar o risco total da conta.

4. Perda máxima por operação

Define a perda máxima que pode ser permitida em uma única operação. Esse valor é crucial, pois permite calcular automaticamente o tamanho ideal do lote para cada negociação conforme o nível de risco que o usuário está disposto a assumir.

5. Lucro diário, semanal e geral

Essas variáveis registram o lucro acumulado em diferentes períodos de tempo. Essas métricas são úteis para avaliar a eficiência do bot e ajustar estratégias com base nos resultados obtidos.


Criação do arquivo incluível e descrição do plano 

Nesta seção, começaremos a programar o arquivo incluível.

1. Na parte superior da nossa plataforma Metatrader, encontramos o item "IDE" e clicamos nele:

 IDE-1

2. No canto superior esquerdo do MetaEditor, selecionamos a aba "Arquivo", depois "Novo", e deve aparecer algo como o seguinte:

IDE-2

3. Selecionamos "Include" e clicamos em "Avançar":

 IDE-3

4. Configuramos o arquivo incluível, definindo para ele um nome e o autor:

IDE-4

Com isso, a criação do arquivo é concluída, embora este seja apenas o começo. Agora descreverei detalhadamente o plano de funcionamento do sistema de gerenciamento de riscos.

A seguir é apresentada uma representação esquemática de como o gerenciamento de riscos irá funcionar:

Map-1

Seção
Descrição   Frequência de execução
1. Variáveis de configuração do cálculo Nesse estágio inicial, executado apenas uma vez, são definidas todas as variáveis necessárias para calcular perdas e lucros.

As principais tarefas incluem:
  • definir o número mágico para identificar operações específicas do EA;
  • estabelecer o saldo inicial, algo especialmente relevante ao operar em contas financiadas ou em prop firms;
  • indicar os valores percentuais de risco aplicáveis e decidir se a perda será calculada em dinheiro ou como percentual do saldo/capital.
Se for escolhida a opção por percentual, o usuário deve indicar o valor-base ao qual o percentual será aplicado (por exemplo, saldo total, equity, lucro total ou margem livre).
Executado apenas uma vez ou ao configurar o EA.
2.
Cálculo de lucros e perdas
Neste estágio, calcula-se o estado atual de lucros e perdas da conta. Inclui:
  • o cálculo do total de perdas acumuladas;
  • o registro de lucros por dia, semana ou por operação;
  • a comparação das perdas acumuladas com os limites definidos na seção anterior.
Esse processo é executado periodicamente conforme a necessidade do usuário.
Executado diariamente, na abertura de uma operação, ou semanalmente — conforme as configurações.
3.
Verificação em tempo real
No modo em tempo real, o EA realiza uma verificação constante em cada tick, para garantir que as perdas atuais não ultrapassem os limites definidos.

Se algum dos valores de perda for excedido, o EA fechará imediatamente todas as posições abertas, para impedir novas perdas.
A cada tick (processo em tempo real).

Com tudo o que foi exposto, passamos à criação das primeiras funções.


Criação de funções para calcular o tamanho do lote 

Antes de começar o desenvolvimento da classe, é necessário primeiro criar as funções que permitirão calcular o lote.

Cálculo do lote ideal

Para determinar o lote ideal, precisamos inicialmente calcular o tamanho total do lote, isto é, o máximo que nossa conta pode comprar ou vender. Esse cálculo é baseado no conhecimento da margem necessária para negociar um lote na moeda da conta. Depois de obter esse valor, dividimos a margem livre da conta pela margem inicial exigida, ajustamos o resultado por arredondamento e assim obtemos o lote máximo permitido para nossa conta.

Requisitos preliminares

Antes de iniciar os cálculos, é necessário determinar a margem exigida para um lote de qualquer símbolo. Neste exemplo, o símbolo utilizado será o ouro, porém esse processo se aplica a qualquer outro símbolo financeiro.

O objetivo principal é obter uma base confiável para o cálculo eficiente dos lotes, adaptada ao saldo e à margem disponível em nossa conta de negociação.

 MARGEM-1

Como podemos ver, a margem inicial aproximada para comprar um lote de ouro é de 1 326 USD. Portanto, para calcular o lote máximo permitido, basta dividir a margem livre disponível na conta pela margem inicial exigida. Essa relação pode ser representada da seguinte forma:

MARGEM-2

Margem livre:

  • Este é o capital disponível em sua conta que pode ser utilizado para abrir novas operações. No MetaTrader, ele é calculado da seguinte maneira:

MARGEM-3

Cálculo do preço para qualquer tipo de ordem
Agora que sabemos como calcular o tamanho máximo do lote, o próximo passo será implementar essa funcionalidade programaticamente. No entanto, antes de fazer isso, precisamos determinar o preço pelo qual a ordem será executada. Para isso, criaremos uma função chamada PriceByOrderType, que calculará e retornará o preço correspondente conforme o tipo de ordem.

double PriceByOrderType(const string symbol, const ENUM_ORDER_TYPE order_type, double DEVIATION = 100, double STOP_LIMIT = 50)

Parâmetros:

  1. symbol — o símbolo de negociação (por exemplo, "EURUSD") no qual a ordem será executada,
  2. order_type — o tipo de ordem, baseado no enumerador ENUM_ORDER_TYPE,
  3. DEVIATION — o desvio permitido em pontos,
  4. STOP_LIMIT — a distância em pontos para ordens do tipo STOP_LIMIT.

Passo 1. Criação das variáveis necessárias

Primeiro, declararemos as variáveis que armazenarão a quantidade de dígitos do símbolo, o valor de um ponto e os preços atuais (bid e ask) na estrutura MqlTick.

int     digits=0; 
double  point=0; 
MqlTick tick={}; 

Passo 2. Atribuição de valores às variáveis

Utilizamos funções para obter informações sobre o símbolo, como quantidade de dígitos, valor do ponto e preços atuais.

Obtenção do valor SYMBOL_POINT:
ResetLastError(); 
if(!SymbolInfoDouble(symbol, SYMBOL_POINT, point)) 
  { 
   Print("SymbolInfoDouble() failed. Error ", GetLastError()); 
   return 0; 
  } 

Obtenção do valor SYMBOL_DIGITS:

long value=0; 
if(!SymbolInfoInteger(symbol, SYMBOL_DIGITS, value)) 
  { 
   Print("SymbolInfoInteger() failed. Error ", GetLastError()); 
   return 0; 
  } 
digits=(int)value; 

Obtenção dos preços atuais do símbolo:

if(!SymbolInfoTick(symbol, tick)) 
  { 
   Print("SymbolInfoTick() failed. Error ", GetLastError()); 
   return 0; 
  } 

Passo 3. Cálculo do preço com base no tipo de ordem

Dependendo do tipo de ordem, retornamos o preço correspondente, utilizando a construção switch:

switch(order_type) 
  { 
   case ORDER_TYPE_BUY              :  return(tick.ask); 
   case ORDER_TYPE_SELL             :  return(tick.bid); 
   case ORDER_TYPE_BUY_LIMIT        :  return(NormalizeDouble(tick.ask - DEVIATION * point, digits)); 
   case ORDER_TYPE_SELL_LIMIT       :  return(NormalizeDouble(tick.bid + DEVIATION * point, digits)); 
   case ORDER_TYPE_BUY_STOP         :  return(NormalizeDouble(tick.ask + DEVIATION * point, digits)); 
   case ORDER_TYPE_SELL_STOP        :  return(NormalizeDouble(tick.bid - DEVIATION * point, digits)); 
   case ORDER_TYPE_BUY_STOP_LIMIT   :  return(NormalizeDouble(tick.ask + DEVIATION * point - STOP_LIMIT * point, digits)); 
   case ORDER_TYPE_SELL_STOP_LIMIT  :  return(NormalizeDouble(tick.bid - DEVIATION * point + STOP_LIMIT * point, digits)); 
   default                          :  return(0); 
  } 

Aqui está a implementação final da função:

double PriceByOrderType(const string symbol, const ENUM_ORDER_TYPE order_type, double DEVIATION = 100, double STOP_LIMIT = 50) 
  {
   int     digits=0; 
   double  point=0; 
   MqlTick tick={}; 

//--- we get the Point value of the symbol
   ResetLastError(); 
   if(!SymbolInfoDouble(symbol, SYMBOL_POINT, point)) 
     { 
      Print("SymbolInfoDouble() failed. Error ", GetLastError()); 
      return 0; 
     } 

//--- we get the Digits value of the symbol
   long value=0; 
   if(!SymbolInfoInteger(symbol, SYMBOL_DIGITS, value)) 
     { 
      Print("SymbolInfoInteger() failed. Error ", GetLastError()); 
      return 0; 
     } 
   digits=(int)value; 

//--- we get the latest prices of the symbol
   if(!SymbolInfoTick(symbol, tick)) 
     { 
      Print("SymbolInfoTick() failed. Error ", GetLastError()); 
      return 0; 
     } 

//--- Depending on the type of order, we return the price
   switch(order_type) 
     { 
      case ORDER_TYPE_BUY              :  return(tick.ask); 
      case ORDER_TYPE_SELL             :  return(tick.bid); 
      case ORDER_TYPE_BUY_LIMIT        :  return(NormalizeDouble(tick.ask - DEVIATION * point, digits)); 
      case ORDER_TYPE_SELL_LIMIT       :  return(NormalizeDouble(tick.bid + DEVIATION * point, digits)); 
      case ORDER_TYPE_BUY_STOP         :  return(NormalizeDouble(tick.ask + DEVIATION * point, digits)); 
      case ORDER_TYPE_SELL_STOP        :  return(NormalizeDouble(tick.bid - DEVIATION * point, digits)); 
      case ORDER_TYPE_BUY_STOP_LIMIT   :  return(NormalizeDouble(tick.ask + DEVIATION * point - STOP_LIMIT * point, digits)); 
      case ORDER_TYPE_SELL_STOP_LIMIT  :  return(NormalizeDouble(tick.bid - DEVIATION * point + STOP_LIMIT * point, digits)); 
      default                          :  return(0); 
     } 
  } 

Além disso, será necessária uma função para obter o tipo de ordem a mercado com base no tipo de ordem, algo que é feito de forma simples:

ENUM_ORDER_TYPE MarketOrderByOrderType(ENUM_ORDER_TYPE type) 
  { 
   switch(type) 
     { 
      case ORDER_TYPE_BUY  : case ORDER_TYPE_BUY_LIMIT  : case ORDER_TYPE_BUY_STOP  : case ORDER_TYPE_BUY_STOP_LIMIT  : 
        return(ORDER_TYPE_BUY); 
      case ORDER_TYPE_SELL : case ORDER_TYPE_SELL_LIMIT : case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : 
        return(ORDER_TYPE_SELL); 
     } 
   return(WRONG_VALUE); 
  }

Cálculo do lote máximo
A função GetMaxLot calcula o tamanho máximo do lote que pode ser aberto com base na margem livre disponível e no tipo de ordem especificado. Trata-se de uma ferramenta fundamental para o gerenciamento de riscos, pois garante que as operações respeitem os requisitos de margem estabelecidos pela corretora.

1. Criação dos parâmetros da função

A função recebe os seguintes parâmetros:

double GetMaxLote(ENUM_ORDER_TYPE type, double DEVIATION = 100, double STOP_LIMIT = 50)
  • Type — define o tipo de ordem, como ORDER_TYPE_BUY ou ORDER_TYPE_SELL. Esse parâmetro é essencial para o cálculo correto do preço e da margem.
  • DEVIATION — indica o desvio permitido em pontos para ordens pendentes. O valor padrão é 100.
  • STOP_LIMIT — representa a distância em pontos para ordens do tipo STOP_LIMIT. O valor padrão é 50.

2. Inicialização das variáveis necessárias

São declaradas quatro variáveis do tipo double e um enumerador ORDER_TYPE, que serão utilizados nos cálculos:

   //--- Set variables
   double VOLUME = 1.0; //Initial volume size
   ENUM_ORDER_TYPE new_type = MarketOrderByOrderType(type); 
   double price = PriceByOrderType(_Symbol, type, DEVIATION, STOP_LIMIT); // Price for the given order type
   double volume_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); // Volume step for the symbol
   double margin = EMPTY_VALUE; // Required margin, initialized as empty

3. Cálculo da margem necessária para um lote

Para determinar a margem exigida para abrir um lote nas condições de mercado atuais, utiliza-se a função OrderCalcMargin. Em caso de falha, uma mensagem de erro é exibida e retorna-se 0:

ResetLastError(); 
if (!OrderCalcMargin(new_type, _Symbol, VOLUME, price, margin)) 
  { 
   Print("OrderCalcMargin() failed. Error ", GetLastError()); 
   return 0; // Exit the function if margin calculation fails
  } 

4. Cálculo do tamanho máximo do lote

Para calcular o tamanho máximo do lote, utiliza-se a fórmula apresentada acima. Isso inclui dividir a margem livre pela margem exigida, normalizar o resultado de acordo com o passo de volume permitido e arredondar para baixo, evitando erros:

double result = MathFloor((AccountInfoDouble(ACCOUNT_MARGIN_FREE) / margin) / volume_step) * volume_step; 

5. Retorno do resultado

Por fim, é retornado o tamanho máximo de lote calculado:

return result; // Return the maximum lot size

Função completa:

double GetMaxLote(ENUM_ORDER_TYPE type, double DEVIATION = 100, double STOP_LIMIT = 50) 
  { 
   //--- Set variables
   double VOLUME = 1.0; // Initial volume size
   ENUM_ORDER_TYPE new_type = MarketOrderByOrderType(type); 
   double price = PriceByOrderType(_Symbol, type, DEVIATION, STOP_LIMIT); // Price for the given order type
   double volume_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);    // Volume step for the symbol
   double margin = EMPTY_VALUE; // Required margin, initialized as empty
  
   //--- Get margin for one lot
   ResetLastError(); 
   if (!OrderCalcMargin(new_type, _Symbol, VOLUME, price, margin)) 
     { 
      Print("OrderCalcMargin() failed. Error ", GetLastError()); 
      return 0; // Exit the function if margin calculation fails
     }
   //--- Calculate the maximum lot size
   double result = MathFloor((AccountInfoDouble(ACCOUNT_MARGIN_FREE) / margin) / volume_step) * volume_step; 
   return result; // Return the maximum lot size
  }


Criação de funções para obter o lucro 

Após concluir o desenvolvimento das funções para calcular o tamanho máximo do lote, o próximo passo é desenvolver funções que calculem o lucro desde uma data específica até o momento atual. Isso é fundamental, pois, na avaliação a cada tick, precisamos determinar se a variável de perda máxima foi ultrapassada. Para isso, dependemos das variáveis que armazenam informações sobre o lucro. Por exemplo, para verificar se a perda máxima diária foi excedida, é necessário conhecer o lucro acumulado do dia e também a equity atual.

O cálculo do lucro atual será realizado utilizando as funções disponíveis para trabalhar com o histórico de ordens e transações. Isso nos permitirá obter dados precisos e atualizados sobre lucros e perdas em um determinado período.

Descrição detalhada da função

1. Inicialização de variáveis e limpeza de erros:

double total_net_profit = 0.0; // Initialize the total net profit
ResetLastError(); // Reset any previous errors
  • total_net_profit — é inicializada com o valor 0,0, indicando que o lucro líquido ainda não foi calculado.
  • ResetLastError — garante a limpeza de quaisquer erros anteriores que possam ter ocorrido no código antes do início da execução.

2. Verificação da data inicial (start_date):

if((start_date > 0 || start_date != D'1971.01.01 00:00'))

Esta linha verifica se a data indicada como start_date é válida, pois ela não pode ser uma data inválida padrão, como 1971.01.01, ou uma data nula. Se a data for válida, o código prossegue com a seleção do histórico de negociações.

3. Seleção do histórico de negociações:

if(!HistorySelect(start_date, TimeCurrent())) 
{
   Print("Error when selecting orders: ", _LastError); 
   return 0.0; // Exit if unable to select the history
}
  • HistorySelect — seleciona o histórico de negociações desde a data indicada (start_date) até o momento atual (TimeCurrent).
  • Se a seleção do histórico falhar, uma mensagem de erro é exibida e a função termina retornando 0.

4. Obtenção do número total de negociações:

int total_deals = HistoryDealsTotal(); // Get the total number of deals in history

  • HistoryDealsTotal — retorna a quantidade total de negociações no histórico, permitindo determinar quantas negociações precisam ser processadas.

5. Iteração por todas as negociações:

for(int i = 0; i < total_deals; i++)
{
   ulong deal_ticket = HistoryDealGetTicket(i); // Retrieve the deal ticket
  • Nesta etapa, é iniciado um laço for que percorre todas as negociações do histórico.
  • HistoryDealGetTicket — obtém o ticket único da negociação na posição i, necessário para acessar os detalhes completos daquela negociação.

6. Filtragem das negociações do tipo "balance":

if(HistoryDealGetInteger(deal_ticket, DEAL_TYPE) == DEAL_TYPE_BALANCE) continue;

Se o tipo da negociação for balance (ajuste de balanço, não sendo uma operação real), ela é ignorada, e o laço continua para a próxima.

7. Obtenção dos detalhes da negociação:

ENUM_DEAL_ENTRY deal_entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal_ticket, DEAL_ENTRY); // Get deal entry type
long deal_close_time_long = HistoryDealGetInteger(deal_ticket, DEAL_TIME);                    // Get deal close time (as long)
datetime deal_close_time = (datetime)deal_close_time_long;                                    // Explicit conversion to datetime
ulong position_id = HistoryDealGetInteger(deal_ticket, DEAL_POSITION_ID);                     // Get the position ID
  • deal_entry — determina se a negociação foi uma entrada ou saída, útil para identificar se a operação se refere à abertura ou ao fechamento.
  • deal_close_time — o horário de fechamento da negociação. Para facilitar o processamento, ele é convertido para o tipo datetime.
  • position_id — o identificador da posição associada à negociação, usado para verificar o magic number.

8. Filtragem por data e tipo de negociação:

if(deal_close_time >= start_date && (deal_entry == DEAL_ENTRY_OUT || deal_entry == DEAL_ENTRY_IN))

A condição garante que apenas negociações cujo horário de fechamento seja maior ou igual a start_date, e que sejam negociações de entrada ou saída válidas, sejam consideradas.

9. Filtragem de negociações por Magic Number e tipo de inclusão:

if((HistoryDealGetInteger(deal_ticket, DEAL_MAGIC) == specific_magic || specific_magic == GetMagic(position_id)) 
   || include_all_magic == true)

  • HistoryDealGetInteger — obtém o magic number da negociação.
  • Se o magic number da operação corresponde ao especificado (specific_magic), ou se todas as negociações foram autorizadas para inclusão (quando include_all_magic é true), então o cálculo do lucro líquido da negociação é realizado.

10. Cálculo do lucro líquido por negociação:

double deal_profit = HistoryDealGetDouble(deal_ticket, DEAL_PROFIT);         // Retrieve profit from the deal
double deal_commission = HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION); // Retrieve commission
double deal_swap = HistoryDealGetDouble(deal_ticket, DEAL_SWAP);             // Retrieve swap fees
                  
double deal_net_profit = deal_profit + deal_commission + deal_swap;          // Calculate net profit for the deal
total_net_profit += deal_net_profit;                                         // Add to the total net profit
  • deal_profit — obtém o lucro da negociação.
  • deal_commission — obtém a comissão da negociação.
  • deal_swap — obtém o swap da negociação, isto é, a taxa de juros ou custo pelo carregamento da posição para o dia seguinte.

O lucro líquido da negociação é calculado como a soma desses três valores e adicionado a total_net_profit.

11. Retorno do lucro líquido total:

return NormalizeDouble(total_net_profit, 2); // Return the total net profit rounded to 2 decimals

Ao final, é retornado o lucro líquido total, arredondado para 2 casas decimais com a ajuda de NormalizeDouble, garantindo que o valor possua o formato correto para uso posterior.

Função completa:

double GetNetProfitSince(bool include_all_magic, ulong specific_magic, datetime start_date)
{
   double total_net_profit = 0.0; // Initialize the total net profit
   ResetLastError();              // Reset any previous errors

   // Check if the start date is valid
   if((start_date > 0 || start_date != D'1971.01.01 00:00'))
   {   
      // Select the order history from the given start date to the current time
      if(!HistorySelect(start_date, TimeCurrent())) 
      {
         Print("Error when selecting orders: ", _LastError); 
         return 0.0; // Exit if unable to select the history
      }

      int total_deals = HistoryDealsTotal(); // Get the total number of deals in history
  
      // Iterate through all deals
      for(int i = 0; i < total_deals; i++)
      {
         ulong deal_ticket = HistoryDealGetTicket(i); // Retrieve the deal ticket

         // Skip balance-type deals
         if(HistoryDealGetInteger(deal_ticket, DEAL_TYPE) == DEAL_TYPE_BALANCE) continue;            

         ENUM_DEAL_ENTRY deal_entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal_ticket, DEAL_ENTRY); // Get deal entry type
         long deal_close_time_long = HistoryDealGetInteger(deal_ticket, DEAL_TIME);                    // Get deal close time (as long)
         datetime deal_close_time = (datetime)deal_close_time_long;                                    // Explicit conversion to datetime
         ulong position_id = HistoryDealGetInteger(deal_ticket, DEAL_POSITION_ID);                     // Get the position ID

         // Check if the deal is within the specified date range and is a valid entry/exit deal
         if(deal_close_time >= start_date && (deal_entry == DEAL_ENTRY_OUT || deal_entry == DEAL_ENTRY_IN))
         {             
            // Check if the deal matches the specified magic number or if all deals are to be included
            if((HistoryDealGetInteger(deal_ticket, DEAL_MAGIC) == specific_magic || specific_magic == GetMagic(position_id)) 
               || include_all_magic == true)
            {
               double deal_profit = HistoryDealGetDouble(deal_ticket, DEAL_PROFIT);         // Retrieve profit from the deal
               double deal_commission = HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION); // Retrieve commission
               double deal_swap = HistoryDealGetDouble(deal_ticket, DEAL_SWAP);             // Retrieve swap fees
               
               double deal_net_profit = deal_profit + deal_commission + deal_swap; // Calculate net profit for the deal
               total_net_profit += deal_net_profit; // Add to the total net profit
            }
         }
      }
   }
     
   return NormalizeDouble(total_net_profit, 2); // Return the total net profit rounded to 2 decimals
}

Função adicional para obter o magic number de uma ordem:

ulong GetMagic(const ulong ticket)
{
HistoryOrderSelect(ticket);
return HistoryOrderGetInteger(ticket,ORDER_MAGIC); 
} 


Verificação na prática com a criação de um script simples com arquivo incluível 

Agora vamos criar uma função que converte uma distância absoluta em unidades de pontos do símbolo atual. Essa conversão é essencial no trading, pois os pontos são a unidade padrão utilizada para calcular níveis de preço, stops e objetivos.

Fórmula matemática
A fórmula para calcular a distância em pontos é simples:

ЭКСТРА-1

Onde:

  • dist — é a distância absoluta que desejamos converter,
  • pointSize — é o tamanho de um ponto do instrumento financeiro (por exemplo, 0,0001 para EUR/USD).

Representação da fórmula no código
Para implementar essa fórmula em MQL5, precisamos realizar as seguintes etapas:

  1. Obter o tamanho do ponto (pointSize).

    Utilizamos a função SymbolInfoDouble para obter o valor do ponto do símbolo atual. O parâmetro _Symbol representa o símbolo no qual o script está sendo executado, e SYMBOL_POINT retorna o tamanho do ponto.

  2. double pointSize = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
    
  3. Dividir a distância pelo tamanho do ponto e converter para um número inteiro.

    Para calcular a quantidade de pontos, dividimos a distância (dist) pelo tamanho do ponto (pointSize). Em seguida, convertemos o resultado para um valor inteiro usando int, pois pontos são sempre números inteiros.

  4. return (int)(dist / pointSize);
    

Função completa
Abaixo apresentamos a função em sua forma final:

int DistanceToPoint(double dist)
{
  double pointSize = SymbolInfoDouble(_Symbol, SYMBOL_POINT); // Get the point size for the current symbol
  return (int)(dist / pointSize); // Calculate and return the distance in points 
}

Para aplicar na prática tudo o que foi exposto neste artigo, criaremos dois scripts.

Agora criaremos duas funções importantes: uma para calcular o lote ideal com base na perda permitida por operação, e outra para calcular o stop loss ideal em pontos do símbolo com base no lote especificado e no risco por operação.  

Função para calcular o lote ideal com base no risco por operação
A função GetIdealLot calcula o tamanho ideal do lote (nlot) levando em consideração a perda máxima permitida por operação e a distância até o stop loss (StopLoss). Isso garante que cada operação cumpra o limite de risco definido pelo usuário.

void GetIdealLot(
    double& nlot,                     // Calculated ideal lot
    double glot,                      // Gross Lot (max lot accorsing to the balance)
    double max_risk_per_operation,    // Maximum allowed risk per trade (in account currency)
    double& new_risk_per_operation,   // Calculated risk for the adjusted lot (in account currency)
    long StopLoss                     // Stop Loss distance (in points)
)

Descrição dos parâmetros:

  1. nlot — este será o lote ideal, ajustado pela função.
  2. glot — este é o lote máximo possível que pode ser aberto utilizando todos os fundos disponíveis na conta.
  3. max_risk_per_operation — representa o risco máximo permitido por operação, expresso na moeda da conta.
  4. new_risk_per_operation — indica o risco real da operação ajustada com base no lote calculado (nlot), mostrando quanto será perdido se o preço atingir o stop loss.
  5. StopLoss — a distância até o stop loss em pontos.

1. Verificação inicial

A função verifica que o valor de "StopLoss" é maior que 0, pois um stop loss inválido tornaria impossível o cálculo correto do risco.

if(StopLoss <= 0)
{
    Print("[ERROR SL] Stop Loss distance is less than or equal to zero, now correct the stoploss distance: ", StopLoss);
    nlot = 0.0; 
    return;   
}

2. Inicialização das variáveis

São inicializados os valores necessários para os cálculos posteriores:

  • spread — o spread atual do símbolo.
  • tick_value — o valor de um tick, que indica quanto vale a menor variação de preço na moeda da conta.
  • step — o incremento mínimo permitido no tamanho do lote.
new_risk_per_operation = 0;  // Initialize the new risk
long spread = (long)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

3. Cálculo do risco atual (rpo)

O risco atual por operação (rpo) é calculado utilizando a fórmula:

RISCO-1

No código:

double rpo = (glot * (spread + 1 + (StopLoss * tick_value)));

4. Verificação do risco máximo

A função avalia se o risco atual (rpo) ultrapassa o risco máximo permitido por operação (max_risk_per_operation):

Caso 1. O risco excede o valor máximo permitido

  • O tamanho do lote é ajustado proporcionalmente ao risco máximo permitido.
  • O lote ajustado é arredondado para o incremento permitido mais próximo (step).
  • Um novo risco é calculado de acordo com esse lote corrigido.
if(rpo > max_risk_per_operation)
{
    double new_lot = (max_risk_per_operation / rpo) * glot;
    new_lot = MathFloor(new_lot / step) * step;
    new_risk_per_operation = new_lot * (spread + 1 + (StopLoss * tick_value));
    nlot = new_lot; 
}

Caso 2. O risco está dentro do limite permitido

  • Se o risco atual não ultrapassar o limite estabelecido, os valores originais são mantidos:
else
{
    new_risk_per_operation = rpo; // Current risk
    nlot = glot;                  // Gross lot
}

Por fim, criaremos a função final para calcular o stop loss com base no prejuízo máximo permitido por operação e no volume de lote definido pelo usuário:

long GetSL(const ENUM_ORDER_TYPE type , double risk_per_operation , double lot) 
{
 long spread = (long)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
 double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
 double result = ((risk_per_operation/lot)-spread-1)/tick_value;
 
return long(MathRound(result));
}  

Descrição dos parâmetros:

  1. type — o tipo de ordem (compra ou venda), embora nesta função ele não seja usado diretamente.
  2. risk_per_operation — a perda máxima permitida por operação, expressa na moeda da conta.
  3. lot — o tamanho do lote definido pelo usuário.

Lógica passo a passo

1. Fórmula básica:

A fórmula original de cálculo do risco por operação (rpo) é a seguinte:

RISCO-1

Nesta função, expressaremos o stop loss de forma a calcular seu valor com base em rpo, no tamanho do lote e em outros fatores.

2. Expressando o stop loss:

  • Ambos os lados da equação são divididos pelo valor do lote:

RISCO-2

  • Subtraímos o spread e 1 de ambos os lados da equação:

RISCO-3

  • Dividimos por tick_value para isolar o StopLoss:

RISCO-4

Implementação no código
A fórmula acima é aplicada diretamente nos cálculos dentro da função:

double result = ((risk_per_operation / lot) - spread - 1) / tick_value;
  • risk_per_operation/lot — calcula o risco por unidade de lote,
  • - spread - 1 — subtrai o spread e uma margem adicional,
  • /tick_value — converte o resultado para pontos dividindo pelo valor de um tick.

O resultado é arredondado e convertido para o tipo long, para corresponder ao formato exigido:

return long(MathRound(result));

Finalmente, criaremos dois scripts para calcular o lote ideal e o stop loss (sl) ideal de acordo com o risco definido para a operação. Ambos os scripts utilizam uma lógica simples, porém eficaz, para automatizar esses cálculos com base no saldo da conta e nos parâmetros fornecidos pelo usuário.

Primeiro script: cálculo do lote ideal
Esse script calculará o tamanho ideal do lote com base no percentual de risco por operação, no stop loss (sl) especificado em pontos e no tipo de ordem.

  1. Propriedades do script:

    • #property strict garante que o código siga regras rígidas de compilação.
    • #property script_show_inputs permite que o usuário insira parâmetros por meio da interface gráfica.
  2. Parâmetros de entrada (Inputs):

input double percentage_risk_per_operation = 1.0; //Risk per operation in %
input long   sl = 600; //Stops Loss in points
input ENUM_ORDER_TYPE Order_Type = ORDER_TYPE_BUY; //Order Type

Cálculo do risco por operação:

A fórmula calcula o valor em dólares que pode ser colocado em risco em uma única operação, com base no percentual definido:

double risk_per_operation = ((percentage_risk_per_operation/100.0) * AccountInfoDouble(ACCOUNT_BALANCE));

Chamada da função de cálculo do lote ideal:

GetIdealLot(new_lot, GetMaxLote(Order_Type), risk_per_operation, new_risk_per_operation, sl);

Mensagens ao usuário: informações detalhadas sobre os valores calculados, como o lote ideal e o risco ajustado, são exibidas no console e no gráfico.

//+------------------------------------------------------------------+
//|                             Get Lot By Risk Per Trade and SL.mq5 |
//|                                                        Your name |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+ 
#property copyright "Your name"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict
#property script_show_inputs

input double percentage_risk_per_operation = 1.0; // Risk per operation in %
input long   sl = 600; // Stop Loss in points
input ENUM_ORDER_TYPE Order_Type = ORDER_TYPE_BUY; // Order Type

#include <Risk Management.mqh>

//+------------------------------------------------------------------+
//| Main script function                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
   // Calculate the maximum allowable risk per operation in account currency
   double risk_per_operation = ((percentage_risk_per_operation / 100.0) * AccountInfoDouble(ACCOUNT_BALANCE));
   
   // Print input and calculated risk details
   Print("Risk Per operation: ", risk_per_operation);
   Print("SL in points: ", sl);
   Print("Order type: ", EnumToString(Order_Type));
   
   double new_lot;
   double new_risk_per_operation;
   
   // Calculate the ideal lot size
   GetIdealLot(new_lot, GetMaxLote(Order_Type), risk_per_operation, new_risk_per_operation, sl);
   
   // Check if the lot size is valid
   if (new_lot <= 0)
     {
      Print("The stop loss is too large or the risk per operation is low. Increase the risk or decrease the stop loss.");
     }
   else
     {
      // Display calculated values
      Print("Ideal Lot: ", new_lot);
      Print("Maximum loss with SL: ", sl, " | Lot: ", new_lot, " is: ", new_risk_per_operation);
      Comment("Ideal Lot: ", new_lot);
     }
   
   Sleep(1000);
   Comment(" ");
  }
//+------------------------------------------------------------------+

Segundo script: cálculo do SL ideal
Esse script calcula o stop loss em pontos com base no lote especificado pelo usuário e no risco máximo permitido por operação.

input double percentage_risk_per_operation = 1.0; //Risk per operation in %
input double Lot = 0.01; //lot
input ENUM_ORDER_TYPE Order_Type = ORDER_TYPE_BUY; //Order Type

Cálculo do sl ideal: a função get sl é usada para determinar o stop loss em pontos:

long new_sl = GetSL(Order_Type, risk_per_operation, Lot);

Verificação do resultado: se o valor calculado de sl for inválido (new_sl menor ou igual a 0), o usuário recebe uma notificação correspondente.

//+------------------------------------------------------------------+
//|                         Get Sl by risk per operation and lot.mq5 |
//|                                                        Your name |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Your name"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict
#property script_show_inputs

input double percentage_risk_per_operation = 1.0; // Risk per operation in %
input double Lot = 0.01; // Lot size
input ENUM_ORDER_TYPE Order_Type = ORDER_TYPE_BUY; // Order Type

#include <Risk Management.mqh>

//+------------------------------------------------------------------+
//| Main script function                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
   // Calculate the maximum allowable risk per operation in account currency
   double risk_per_operation = ((percentage_risk_per_operation / 100.0) * AccountInfoDouble(ACCOUNT_BALANCE));
   
   // Print input and calculated risk details
   Print("Risk Per operation: ", risk_per_operation);
   Print("Lot size: ", Lot);
   Print("Order type: ", EnumToString(Order_Type));
   
   // Calculate the ideal stop loss
   long new_sl = GetSL(Order_Type, risk_per_operation, Lot);
   
   // Check if the SL is valid
   if (new_sl <= 0)
     {
      Print("The lot size is too high or the risk per operation is too low. Increase the risk or decrease the lot size.");
     }
   else
     {
      // Display calculated values
      Print("For lot: ", Lot, ", and risk: ", risk_per_operation, ", the ideal SL is: ", new_sl);
      Comment("Ideal SL: ", new_sl);
     }
   
   Sleep(1000);
   Comment(" ");
  }
//+------------------------------------------------------------------+

Agora, para aplicar o script na prática, utilizaremos ele para obter o tamanho ideal do lote com base no risco definido por operação. O teste será realizado no símbolo XAUUSD, que corresponde ao ouro.

 SCRIPT-RISCO-1

Com parâmetros como um stop loss de 200 pontos, risco por operação de 1,0% do saldo da conta e tipo de ordem definido como ORDER_TYPE_BUY, o resultado será o seguinte:

 SCRIPT-RISCO-2

O resultado exibido na aba "Experts" corresponde a um tamanho de lote de 0,01 com um stop loss de 200 pontos e um risco por operação de 3,81, que equivale a 1% do saldo da conta.



Considerações finais 

Concluímos a primeira parte desta série, na qual nos concentramos no desenvolvimento das funções principais que serão utilizadas na construção da classe gerenciamento de riscos. Essas funções são fundamentais para obter lucro e realizar cálculos adicionais. Na próxima parte, veremos como integrar tudo o que aprendemos à interface gráfica, utilizando as bibliotecas de elementos de controle do MQL5.

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

Desenvolvimento de sistemas de trading avançados ICT: Implementação de sinais no indicador Order Blocks Desenvolvimento de sistemas de trading avançados ICT: Implementação de sinais no indicador Order Blocks
Neste artigo você vai aprender como desenvolver um indicador Order Blocks baseado no volume do livro de ofertas (profundidade de mercado) e otimizá-lo usando buffers para melhorar a precisão. Com isso, concluímos a etapa atual do projeto e nos preparamos para as próximas, nas quais será implementada uma classe de gerenciamento de risco e um robô de negociação que utilizará os sinais gerados pelo indicador.
MQL5 Trading Toolkit (Parte 5): Expandindo a Biblioteca EX5 de Gerenciamento de Histórico com Funções de Posição MQL5 Trading Toolkit (Parte 5): Expandindo a Biblioteca EX5 de Gerenciamento de Histórico com Funções de Posição
Descubra como criar funções exportáveis em EX5 para consultar e salvar de forma eficiente dados históricos de posições. Neste guia passo a passo, ampliaremos a biblioteca EX5 de gerenciamento de histórico desenvolvendo módulos que recuperam propriedades-chave da posição fechada mais recentemente. Isso inclui lucro líquido, duração da negociação, stop loss em pips, take profit, valores de lucro e vários outros detalhes importantes.
Técnicas do MQL5 Wizard que você deve conhecer (Parte 51): Aprendizado por Reforço com SAC Técnicas do MQL5 Wizard que você deve conhecer (Parte 51): Aprendizado por Reforço com SAC
Soft Actor Critic é um algoritmo de Aprendizado por Reforço que utiliza 3 redes neurais. Uma rede ator e 2 redes críticas. Esses modelos de aprendizado de máquina são combinados em uma parceria mestre-escravo onde as redes críticas são modeladas para melhorar a precisão de previsão da rede ator. Ao mesmo tempo em que introduzimos ONNX nesta série, exploramos como essas ideias podem ser colocadas à prova como um sinal personalizado de um Expert Advisor montado pelo wizard.
Construindo um Modelo de Restrição de Tendência com Candlestick (Parte 10): Golden Cross e Death Cross Estratégicos (EA) Construindo um Modelo de Restrição de Tendência com Candlestick (Parte 10): Golden Cross e Death Cross Estratégicos (EA)
Você sabia que as estratégias Golden Cross e Death Cross, baseadas no cruzamento de médias móveis, são alguns dos indicadores mais confiáveis para identificar tendências de mercado de longo prazo? Um Golden Cross sinaliza uma tendência de alta quando uma média móvel mais curta cruza acima de uma média mais longa, enquanto o Death Cross indica uma tendência de baixa quando a média mais curta cruza abaixo. Apesar de sua simplicidade e eficácia, aplicar essas estratégias manualmente frequentemente leva a oportunidades perdidas ou negociações atrasadas.