English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
O método ideal para calcular o volume da posição total pelo número mágico especificado

O método ideal para calcular o volume da posição total pelo número mágico especificado

MetaTrader 5Sistemas de negociação | 24 janeiro 2014, 09:29
4 836 0
Dmitry Fedoseev
Dmitry Fedoseev

Introdução

A plataforma MetaTrader 5 permite o trabalho paralelo de vários Consultores Especialistas com um símbolo. É simples - apenas abra vários gráficos e anexe os Consultores Especialistas a eles. Seria bom se cada Consultor Especialista trabalhasse independentemente dos outros Consultores Especialistas, trabalhando com o mesmo símbolo (não há problema para os Consultores Especialistas trabalharem com símbolos diferentes).

Primeiramente, será permitido a um Consultor Especialista negociar em total conformidade com seu desempenho de teste e otimização no examinador de estratégia. As condições para abrir a posição podem depender do tamanho ou da falta da posição já aberta. Se vários Consultores Especialistas trabalharem com o mesmo símbolo, eles afetarão uns aos outros.

O segundo e, talvez, mais importante fator é permitir que Expert Advisors usem diferentes sistemas de gerenciamento de dinheiro, dependendo das estratégias de negócio implementadas nos Expert Advisors. E, por último - a possibilidade de monitorar os resultados de cada Consultor Especialista e desligá-lo se necessário.

1. O princípio geral do cálculo do volume da posição

Quando você abre uma ordem, você pode marcá-la com um número mágico através da especificação do valor da variável mágica na estrutura do MqlTradeRequest, passada para função OrderSend(). Quando a ordem é executada, a negociação também é marcada com o número mágico da ordem. Além disso, analisando as negociações no histórico, podemos ver negociações abertas por Consultores Especialistas diferentes.

O método de cálculo da posição total é bem simples: por exemplo, se você executa uma ordem de compra com volume 0.1, então outra pessoa compra 0.1 e vende 0.1, o volume da posição total será igual a 0.1+0.1-0.1=+0.1. Adicionamos os volumes das ordens de compra e subtraímos os volumes das ordens de venda e temos o volume da posição total.

É importante começar os cálculos quando o volume da posição total é igual a 0. O primeiro e mais óbvio ponto é o momento da abertura da conta. Em outras palavras, você pode solicitar todo o histórico de negociação da conta usando a função HistorySelect() com o primeiro parâmetro, igual a 0 (o menor tempo possível) e o valor do segundo parâmetro TimeCurrent() (o tempo recente conhecido de um servidor):

HistorySelect(0,TimeCurrent()); // load all history

Então, passe por todo o histórico, do começo ao fim, adicionando volumes das negociações de compra e subtraindo os volumes das negociações de venda para cada negociação com o número mágico especificado. Essa também é uma solução, mas na prática, o histórico das negociações pode ser bem grande. Isso pode afetar consideravelmente a velocidade de um Consultor Especialista, especialmente durante a avaliação e a otimização, devido a impossibilidade do uso prático de tal Consultor Especialista. Precisamos encontrar o último momento no histórico das negociações, quando o volume da posição líquida total era igual a zero.

Para fazer isso, temos que passar por todo o histórico primeiro e encontrar o último ponto, quando o volume da posição líquida total era zero. Encontrando esse ponto, nós o salvamos em uma certa variável (tempo de posição fixo). Depois, o Consultor Especialista passará pelo histórico das negociações a partir do tempo do ponto salvo. Uma solução melhor é salvar esse ponto em uma variável global da plataforma do cliente ao invés da variável de um Consultor Especialista, porque nesse caso ele será eliminado quando o Consultor Especialista for separado.

Nesse caso, mesmo quando o Consultor Especialista é lançado você precisa carregar o histórico mínimo necessário, ao invés de todo o histórico das negociações. Existem muitos Consultores Especialistas que podem negociar no mesmo símbolo, então compartilharemos esta variável global (com o tempo armazenado do ponto recente de volume zero) com todos os Consultores Especialistas.

Vamos divergir do tema principal e considerar o uso das variáveis globais da plataforma do cliente, que permitem que vários Consultores Especialistas trabalhem com o mesmo símbolo (talvez com parâmetros diferentes), e evitar a coincidência dos nomes criados por diferentes instâncias de Consultores Especialistas.

2. Usando as variáveis globais da plataforma do cliente

A linguagem do MQL5 tem a função MQLInfoString() que permite obter informações diferentes sobre um programa MQL5.

Para obter informação sobre o nome do arquivo, ative esta função com o identificador MQL_PROGRAM_NAME:

MQLInfoString(MQL_PROGRAM_NAME); // Expert Advisor name

Portanto, começamos os nomes das variáveis globais com o nome de um Consultor Especialista. Um Expert Advisor pode trabalhar com diversos símbolos; isso significa que precisamos adicionar o nome de um símbolo (Symbol). Consultores Especialistas podem trabalhar com o mesmo símbolo, mas prazos diferentes (com configurações diferentes), para esses casos temos que usar o número mágico. Portanto, adicionamos também o número mágico.

Por exemplo, se o Consultor Especialista tem um número mágico, armazenado na variável Magic_N, nós o adicionamos ao nome da variável global.

O nome de todas as variáveis globais aparecem a seguir:

gvp=MQLInfoString(MQL_PROGRAM_NAME)+"_"+_Symbol+"_"+IntegerToString(Magic_N)+"_"; // name of an Expert Advisor and symbol name 
                                                                            // and its magic number

Onde gvp (prefixo da variável global) - é uma variável sequencial, definida na seção de variáveis comuns.

Gostaria de esclarecer a terminologia para evitar a confusão das variáveis globais, como elas são usadas na programação (variáveis globais são visíveis dentro de todas as funções, variáveis locais das funções são visíveis apenas dentro da função).

Mas aqui temos um caso diferente - o termo "variáveis globais" corresponde às variáveis globais da plataforma do cliente (variáveis especiais, armazenadas em um arquivo, estão disponíveis pelas funções GlobalVariable...()). Quando falarmos sobre as variáveis globais (como elas são usadas na programação), usaremos o termo "variáveis comuns". O termo variáveis locais corresponderão às variáveis locais.

As variáveis globais são úteis porque elas salvam seus valores após a desinicialização de um Expert Advisor (reinício do Expert Advisor, terminal do cliente, computador), mas no modo de teste é necessário limpar todas as variáveis (ou um passo anterior à otimização). As variáveis globais, usadas nas operações reais devem ser separadas das variáveis globais criadas em teste, é necessário apagá-las após o teste. Mas você não deve modificar ou apagar as variáveis globais criadas pelo Consultor Especialista.

Usando a função AccountInfoInteger() e ativando-a com o identificador ACCOUNT_TRADE_MODE, você pode distinguir o modo atual: teste, conta demo ou conta real.

Vamos adicionar um prefixo às variáveis globais: "d" - quando trabalhar com contas demo, "r" - quando trabalhar com contas reais, "t" - quando trabalhar com o examinador de estratégia:

gvp=MQLInfoString(MQL_PROGRAM_NAME)+"_"+_Symbol+"_"+IntegerToString(Magic_N)+"_"; // name of an Expert Advisor, symbol name
                                                                            // and the Expert Advisor magic number
if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_DEMO))
  {
   gvp=gvp+"d_"; // demo account
  }
if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_REAL)
  {
   gvp=gvp+"r_"; // real
  }
if(MQLInfoInteger(MQL_TESTER))
  {
   gvp=gvp+"t_"; // testing
  } 

A função deve ser ativada a partir da função OnInit() do Consultor Especialista.

Como mencionado acima, as variáveis globais devem ser apagadas quando em teste, em outras palavras, precisamos adicionar a função que apaga as variáveis globais na função OnDeinit() de um Consultor Especialista:

void fDeleteGV()
  {
   if(MQLInfoInteger(MQL_TESTER)) // Testing mode
     {
      for(int i=GlobalVariablesTotal()-1;i>=0;i--) // Check all global variables (from the end, not from the begin)
        {
         if(StringFind(GlobalVariableName(i),gvp,0)==0) // search for the specified prefix
           {
            GlobalVariableDel(GlobalVariableName(i)); // Delete variable
           }
        }
     }
  }

Atualmente não é possível interromper o teste no MetaTrader 5, em outras palavras, a execução da função OnDeinit() não é garantida, no entanto ela pode aparecer no futuro. Não sabemos se a função OnDeinit() será executada após a interrupção do examinador de estratégia, portanto, apagamos as variáveis globais no início do funcionamento do Consultor Especialista - dentro da função OnInit().

Obteremos o seguinte código das funções OnInit() e OnDeinit():

int OnInit()
  {
   fCreateGVP(); // Creating a prefix for the names of global variables of the client terminal
   fDeleteGV();  // Delete global variables when working in Tester
   return(0);
  }

void OnDeinit(const int reason)
  {
   fDeleteGV();  // Delete global variables when working in tester
  }

Também podemos simplificar o uso de variáveis globais, criando as funções com nomes curtos para a criação de variáveis globais (em vez de GlobalVariableSet (gvp+...).

A função para configurar o valor da variável global:

void fGVS(string aName,double aValue)
  {
   GlobalVariableSet(gvp+aName,aValue);
  }

A função para obter o valor da variável global:

double fGVG(string aName)
  {
   return(GlobalVariableGet(gvp+aName));
  }

A função para apagar a variável global:

void fGVD(string aName)
  {
   GlobalVariableDel(gvp+aName);
  }

Discutimos as variáveis globais, mas isso não é tudo.

Precisamos fornecer uma possibilidade para criar variáveis globais para um símbolo, e fornecer suas diferentes operações na conta e no examinador de estratégia. Os nomes dessas variáveis globais não devem depender do nome e do número mágico de um Consultor Especialista.

Vamos definir uma outra variável para um prefixo da variável global nomeada "Commom_gvp". Então ao trabalhar com uma conta, ela terá o valor "COMMON" e terá o mesmo valor que uma variável gvp ao trabalhar com o examinador de estratégia (para apagar a variável depois ou antes do processo de backtesting de estratégia).

Por último, a função para preparar os prefixos das varáveis globais tem a seguinte forma:

void fCreateGVP()
  {
   gvp=MQLInfoString(MQL_PROGRAM_NAME)+"_"+_Symbol+"_"+IntegerToString(Magic_N)+"_";
   Commom_gvp="COMMOM_"; // Prefix for common variables for all Expert Advisors
   if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_DEMO)
     {
      gvp=gvp+"d_";
     }
   if(AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_REAL)
     {
      gvp=gvp+"r_";
     }
   if(MQLInfoInteger(MQL_TESTER))
     {
      gvp=gvp+"t_";
      Commom_gvp=gvp; // To be used in tester, the variables with such a prefix 
                      // will be deleted after the testing
     }
  }

Alguém deve achar que os prefixos das variáveis globais incluem informação extra - a separação das contas reais e demo, e o prefixo "t" quando em teste, embora isto pudesse ser feito apenas através da adição do char "t" que indica que nosso Consultor Especialista está trabalhando no examinador de estratégia. Mas fiz desta forma. Não sabemos o futuro e as coisas que podem ser necessárias para analisar o trabalho dos Consultores Especialistas.

Como dizem: quem guarda, tem.

As funções apresentadas acima indicam que a plataforma do cliente trabalha com uma conta, não existe mudança de conta durante seu trabalho. É proibido mudar a conta durante o trabalho de um Consultor Especialista. Certamente, se necessário, esse problema pode ser resolvido através da adição de um número de conta aos nomes das variáveis globais.

Outra observação muito importante! A extensão do nome da variável global é limitada a 63 símbolos. Devido a esse fato, não dê nomes longos a seus Consultores Especialistas.

Finalizamos as variáveis globais, agora é o momento para considerarmos o tópico principal deste artigo - o cálculo do volume da posição através de um número mágico especificado.

3. Calculando o volume de uma posição

Primeiro, vamos checar se existe uma variável global com a informação sobre a última vez que a posição do volume foi zero usando a função GlobalVariableCheck() (para simplificar, se não existe uma posição aberta, chamamos de caso de "posição zero").

Se existe tal variável - vamos carregar o histórico das negociações começando a partir do tempo armazenado na variável, caso contrário, carregaremos o histórico completo.

if(GlobalVariableCheck(Commom_gvp+sSymbol+"_HistStTm")) // Saved time of a "zero" total position
  {
   pLoadHistoryFrom=(datetime)GlobalVariableGet(Commom_gvp+pSymbol+"_HistStTm"); // initial date setting 
                                                                             // select only the history needed
  }
else
 {
   GlobalVariableSet(Commom_gvp+sSymbol+"_HistStTm",0);
 }
if(!HistorySelect(sLoadHistoryFrom,TimeCurrent())) // Load the necessary part of the deal history
  { 
   return(false);
  } 

Depois, definimos o volume da posição líquida total para um símbolo:

double CurrentVolume=fSymbolLots(pSymbol);

O volume de uma posição é determinado usando a função fSymbolLots().

Existem várias maneiras para se obter o volume de uma posição: por exemplo, pode ser usada a função PositionSelect(). Se a função retornar falsa, significa que não existe posição (seu volume é igual a zero). Se a função retornar verdadeira, o volume pode ser obtido usando a função PositionGetDouble() com o identificador POSITION_VOLUME. O tipo de posição (compra ou venda) é determinado usando a função PositionGetInteger() com o identificador POSITION_TYPE. A função retorna um valor positivo para posições longas e negativo para posições curtas.

A função completa aparece a seguir:

double fSymbolLots(string aSymbol)
  {
   if(PositionSelect(aSymbol,1000)) // the position has been selected successfully, so it exists
     {
      switch(PositionGetInteger(POSITION_TYPE)) // It returns the positive or negative value dependent on the direction
        {
         case POSITION_TYPE_BUY:
            return(NormalizeDouble(PositionGetDouble(POSITION_VOLUME),2));
            break;
         case POSITION_TYPE_SELL:
            return(NormalizeDouble(-PositionGetDouble(POSITION_VOLUME),2));
            break;
        }
     }
   else
     {
      return(0);
     }
  }

Alternativamente, você pode determinar o volume da posição total de um símbolo pelo loop através de todas as posições, o número das posições é determinado pela função PositionsTotal(). Depois, encontre o símbolo necessário usando a função PositionGetSymbol() e determine o volume e direção da posição (a função PositionGetDouble() com identificador POSITION_VOLUME e função PositionGetInteger() com identificador POSITION_TYPE).

Nesse caso, a função pronta terá a forma:

double fSymbolLots(string aSymbol)
  {
   double TmpLots=0;
   for(int i=0;i<PositionsTotal();i++) // Go through all positions
     {
      if(PositionGetSymbol(i)==aSymbol) // we have found a position with specified symbol
        {
         TmpLots=PositionGetDouble(POSITION_VOLUME);
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
           {
            TmpLots*=-1; // the sign is dependent on the position type           }
         break;
        }
     }
   TmpLots=NormalizeDouble(TmpLots,2);
   return(TmpLots);
  }

Após a determinação do volume atual, passaremos pelo histórico das negociações do fim para o início, até que a soma dos volumes seja igual ao volume.

A extensão do histórico das negociações selecionado é determinada usando a função HistoryDealsTotal(), a entrada é determinada para cada negociação usando a função HistoryDealGetTicket(), os dados da negociação são extraídos usando a função HistoryDealGetInteger() (o identificador para o tipo de negociação DEAL_TYPE) e a HistoryDealGetDouble() (o identificador para o volume da negociação DEAL_VOLUME):

double Sum=0; 
int FromI=0;
int FromTicket=0;
for(int i=HistoryDealsTotal()-1;i>=0;i--) // go through all the deals from the end to the beginning 
  {
   ulong ticket=HistoryDealGetTicket(i); // Get ticket of the deal
   if(ticket!=0)
     {
      switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) // We add or subtract the volume depending on deal direction
        {
         case DEAL_TYPE_BUY:
            Sum+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
            Sum=NormalizeDouble(Sum,2);
            break;
         case DEAL_TYPE_SELL:
            Sum-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
            Sum=NormalizeDouble(Sum,2);
            break;
        }
      if(CurrentVolume==Sum) // all the deals has scanned
        {
         sLoadHistoryFrom=HistoryDealGetInteger(ticket,DEAL_TIME); // Save the time of a "zero" position
         GlobalVariableSet(Commom_gvp+aSymbol+"_HistStTm",sLoadHistoryFrom);
         FromI=i; // Save the index
         break;
        }
     }
  }

Quando encontramos este ponto, armazenamos o tempo dentro da variável global a qual será usada futuramente quando o histórico das negociações for carregado (o índice da negociação no histórico é armazenado na variável FromI).

Antes da negociação com um índice FromI a posição total no símbolo era igual a zero.

Agora vamos do FromI para o fim do histórico e contamos o volume das negociações com o número mágico especificado:

static double sVolume=0;
static ulong sLastTicket=0;
for(int i=FromI;i<HistoryDealsTotal();i++) // from the first deal until the end
  {
   ulong ticket=HistoryDealGetTicket(i);   // Get deal ticket
   if(ticket!=0)
     {
      if(HistoryDealGetString(ticket,DEAL_SYMBOL)==aSymbol) // Specified symbol
        {
         long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC);
         if(PosMagic==aMagic || aMagic==-1) // Specified magic
           {
            switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) // add or subtract the deal volumes 
                                                       // depending on the deal type
              {
               case DEAL_TYPE_BUY:
                  sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  sLastTicket=ticket;
                  break;
               case DEAL_TYPE_SELL:
                  sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  sLastTicket=ticket;
                  break;
              }
           }
        }
     }
  } 

Após o fim de um loop teremos o volume de uma posição atual através do número mágico especificado, a entrada de uma última negociação com o número mágico especificado será armazenada na variável sLastTicket, após a execução da negociação o volume total de uma posição com o número mágico especificado será igual a sVolume. O trabalho preliminar da função está terminado.

As variáveis sLoadHistoryFrom, sLastTicket e sVolume são definidas como estáticas (elas armazenam seus valores após a conclusão da função), esses valores serão usados futuramente para cada ativação de função.

Temos o tempo (o ponto de início do histórico das negociações), a entrada da negociação, após sua execução o volume da posição total (com símbolo especificado) terá o valor atual.

Devido ao tempo da posição de volume zero, basta passar pelo histórico, do tempo atual para o tempo salvo, e efetuar a soma dos volumes de negociação e salvar o volume e a entrada da última negociação.

Assim, o cálculo da posição total do Consultor Especialista é o processamento das últimas negociações:

if(!HistorySelect(sLoadHistoryFrom,TimeCurrent())) // Request for the deals history up to the current time
  {
   return(false);
  }
for(int i=HistoryDealsTotal()-1;i>=0;i--) // Loop from the end
  {
   ulong ticket=HistoryDealGetTicket(i); // Get ticke
   if(ticket!=0)
     {
      if(ticket==sLastTicket) // We have found the already calculated deal, save the ticket and break
        {
         sLastTicket=HistoryDealGetTicket(HistoryDealsTotal()-1);
         break;
        }
      switch(HistoryDealGetInteger(ticket,DEAL_TYPE)) // Add or subtract deal volume depending on deal type      
        {
         case DEAL_TYPE_BUY:
            sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
            break;
         case DEAL_TYPE_SELL:
            sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
            break;
        }
     }
  }

O algoritmo da função pode ser apresentado da seguinte forma:


A função completa:

bool fGetPositionVolume(string aSymbol,int aMagic,double aVolume)
  {
   static bool FirstStart=false;
   static double sVolume=0;
   static ulong sLastTicket=0;
   static datetime sLoadHistoryFrom=0;
   // First execution of function when Expert Advisor has started
   if(!FirstStart)
     {
      if(GlobalVariableCheck(Commom_gvp+aSymbol+"_HistStTm"))
        {
         sLoadHistoryFrom=(datetime)GlobalVariableGet(Commom_gvp+aSymbol+"_HistStTm");
        }
      else
        {
         GlobalVariableSet(Commom_gvp+aSymbol+"_HistStTm",0);
        }
      if(!HistorySelect(sLoadHistoryFrom,TimeCurrent())) // Return if unsuccessful, 
                                                      // we will repeat on the next tick
        {
         return(false);
        }
      double CurrentVolume=fSymbolLots(aSymbol); // Total volume
      double Sum=0;
      int FromI=0;
      int FromTicket=0;
      // Search the last time when position volume was equal to zero
      for(int i=HistoryDealsTotal()-1;i>=0;i--)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket!=0)
           {
            switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
              {
               case DEAL_TYPE_BUY:
                  Sum+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  Sum=NormalizeDouble(Sum,2);
                  break;
               case DEAL_TYPE_SELL:
                  Sum-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  Sum=NormalizeDouble(Sum,2);
                  break;
              }
            if(CurrentVolume==Sum)
              {
               sLoadHistoryFrom=HistoryDealGetInteger(ticket,DEAL_TIME);
               GlobalVariableSet(Commom_gvp+aSymbol+"_HistStTm",sLoadHistoryFrom);
               FromI=i;
               break;
              }
           }
        }
      // Calculate the volume of position with specified magic number and symbol
      for(int i=FromI;i<HistoryDealsTotal();i++)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket!=0)
           {
            if(HistoryDealGetString(ticket,DEAL_SYMBOL)==aSymbol)
              {
               long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC);
               if(PosMagic==aMagic || aMagic==-1)
                 {
                  switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
                    {
                     case DEAL_TYPE_BUY:
                        sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                        sLastTicket=ticket;
                        break;
                     case DEAL_TYPE_SELL:
                        sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                        sLastTicket=ticket;
                        break;
                    }
                 }
              }
           }
        }
      FirstStart=true;
     }

   // Recalculate the volume of a position (with specified symbol and magic)
   // for the deals, after the zero position time
   if(!HistorySelect(sLoadHistoryFrom,TimeCurrent()))
     {
      return(false);
     }
   for(int i=HistoryDealsTotal()-1;i>=0;i--)
     {
      ulong ticket=HistoryDealGetTicket(i);
      if(ticket!=0)
        {
         if(ticket==sLastTicket)
           {
            sLastTicket=HistoryDealGetTicket(HistoryDealsTotal()-1);
            break;
           }
         switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
           {
            case DEAL_TYPE_BUY:
               sVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
               break;
            case DEAL_TYPE_SELL:
               sVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
               break;
           }
        }
     }
   aVolume=NormalizeDouble(sVolume,2);;
   return(true);
  }

O símbolo e o número mágico são passados para a função, que retorna o volume da posição. Ele retorna verdadeiro se bem sucedido e falso no caso contrário.

Quando bem sucedido, ele devolve o volume necessário para a variável aVolume, passada para a função pela referência. Variáveis estáticas, definidas na função, não permitem usar esta função com parâmetros diferentes (símbolo e número mágico).

No caso do MQL4, este problema poderia ser resolvido através da criação de uma cópia dessa função com um nome diferente e chamá-la pelo outro par "símbolo-mágico" ou definir as variáveis FirstStart, sVolume, sLastTicket, sLoadHistoryFrom como variáveis comuns - para cada par "símbolo-mágico" e passá-las para dentro da função.

Isso também pode ser implementado no MQL5 da mesma forma, mas o MQL5 tem uma característica muito mais prática - as classes, é o caso onde o uso das classes é satisfatório. Quando as classes são usadas, é necessário criar uma instância de classe para cada par de número de símbolo-mágico, os dados serão armazenados em cada instância de classe.

Vamos definir uma classe de volume da posição. Todas as variáveis, definidas como estáticas dentro da função serão definidas como privadas, não as usaremos diretamente a partir do Consultor Especialista, exceto a variável Volume. Mas precisaremos dela apenas após a execução da função do cálculo do volume. Também definimos o símbolo e as variáveis mágicas - é impraticável passá-los para dentro da função, faça apenas uma vez quando inicializar a instância de classe.

A classe terá duas funções públicas: a função de inicialização e a função para cálculo do volume da posição, e uma função privada para determinar o volume total da posição:

class PositionVolume
  {
private:
   string            pSymbol;
   int               pMagic;
   bool              pFirstStart;
   ulong             pLastTicket;
   double            pVolume;
   datetime         pLoadHistoryFrom;
   double            SymbolLots();
public:
   void Init(string aSymbol,int aMagic)
     {
      pSymbol=aSymbol;
      pMagic=aMagic;
      pFirstStart=false;
      pLastTicket=0;
      pVolume=0;
     }
   bool              GetVolume(double  &aVolume);
  }; 
bool PositionVolume::GetVolume(double  &aVolume)
  {
   if(!pFirstStart)
     {
      if(GlobalVariableCheck(Commom_gvp+pSymbol+"_HistStTm"))
        {
         pLoadHistoryFrom=(datetime)GlobalVariableGet(Commom_gvp+pSymbol+"_HistStTm");
        }
      else
        {
         GlobalVariableSet(Commom_gvp+pSymbol+"_HistStTm",0);
        }
      if(!HistorySelect(pLoadHistoryFrom,TimeCurrent()))
        {
         return(false);
        }
      double CurrentVolume=fSymbolLots(pSymbol);
      double Sum=0;
      int FromI=0;
      int FromTicket=0;
      for(int i=HistoryDealsTotal()-1;i>=0;i--)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket!=0)
           {
            switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
              {
               case DEAL_TYPE_BUY:
                  Sum+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  Sum=NormalizeDouble(Sum,2);
                  break;
               case DEAL_TYPE_SELL:
                  Sum-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                  Sum=NormalizeDouble(Sum,2);
                  break;
              }
            if(CurrentVolume==Sum)
              {
               pLoadHistoryFrom=HistoryDealGetInteger(ticket,DEAL_TIME);
               GlobalVariableSet(Commom_gvp+pSymbol+"_HistStTm",pLoadHistoryFrom);
               FromI=i;
               break;
              }
           }
        }
      for(int i=FromI;i<HistoryDealsTotal();i++)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket!=0)
           {
            if(HistoryDealGetString(ticket,DEAL_SYMBOL)==pSymbol)
              {
               long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC);
               if(PosMagic==pMagic || pMagic==-1)
                 {
                  switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
                    {
                     case DEAL_TYPE_BUY:
                        pVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                        pLastTicket=ticket;
                        break;
                     case DEAL_TYPE_SELL:
                        pVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                        pLastTicket=ticket;
                        break;
                    }
                 }
              }
           }
        }
      pFirstStart=true;
     }
   if(!HistorySelect(pLoadHistoryFrom,TimeCurrent()))
     {
      return(false);
     }
   for(int i=HistoryDealsTotal()-1;i>=0;i--)
     {
      ulong ticket=HistoryDealGetTicket(i);
      if(ticket!=0)
        {
         if(ticket==pLastTicket)
           {
            break;
           }
         if(HistoryDealGetString(ticket,DEAL_SYMBOL)==pSymbol)
           {
            long PosMagic=HistoryDealGetInteger(ticket,DEAL_MAGIC);
            if(PosMagic==pMagic || pMagic==-1)
              {
               switch(HistoryDealGetInteger(ticket,DEAL_TYPE))
                 {
                  case DEAL_TYPE_BUY:
                     pVolume+=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                     break;
                  case DEAL_TYPE_SELL:
                     pVolume-=HistoryDealGetDouble(ticket,DEAL_VOLUME);
                     break;
                 }
              }
           }
        }
     }
   if(HistoryDealsTotal()>0)
     {
      pLastTicket=HistoryDealGetTicket(HistoryDealsTotal()-1);
     }
   pVolume=NormalizeDouble(pVolume,2);
   aVolume=pVolume;
   return(true);
  }

double PositionVolume::SymbolLots()
  {
   double TmpLots=0;
   for(int i=0;i<PositionsTotal();i++)
     {
      if(PositionGetSymbol(i)==pSymbol)
        {
         TmpLots=PositionGetDouble(POSITION_VOLUME);
         if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
           {
            TmpLots*=-1;
           }
         break;
        }
     }
   TmpLots=NormalizeDouble(TmpLots,2);
   return(TmpLots);
  }

Quando usar esta classe para cada par de número de símbolo-mágico, é necessário criar uma instância de classe:

PositionVolume PosVol11;
PositionVolume PosVol12;
PositionVolume PosVol21;
PositionVolume PosVol22;

Ela deve ser inicializada na função OnInit() de um Consultor Especialista, por exemplo:

PosVol11.Init(Symbol_1,Magic_1); 
PosVol12.Init(Symbol_1,Magic_2);
PosVol21.Init(Symbol_2,Magic_1); 
PosVol22.Init(Symbol_2,Magic_2);   

Depois disso, é possível obter o volume de uma posição pelo símbolo especificado e pelo número mágico. Vamos ativar a função GetVolume da instância de classe correspondente.

Se bem sucedida, ela retorna verdadeira e coloca o valor para a variável, passada através da referência como parâmetro da função:

double Vol11;
double Vol12;
double Vol21;
double Vol22;
PosVol11.GetVolume(Vol11);
PosVol12.GetVolume(Vol12);
PosVol21.GetVolume(Vol21);
PosVol22.GetVolume(Vol22);

Aqui, alguém pode dizer que acabou, mas ainda falta o teste de controle.

4. Teste de controle

Para testar o trabalho de uma função usamos um Consultor Especialista que trabalha simultaneamente com quatro posições:

  1. usando o indicador RSI com período 14 em EURUSD com número mágico 1;
  2. usando o indicador RSI com período 21 em EURUSD com número mágico 2;
  3. usando o indicador RSI com período 14 em GBPUSD com número mágico 1;
  4. usando o indicador RSI com período 21 em GBPUSD com número mágico 2.

O Consultor Especialista com número mágico 1 negociou 0.1 lote do volume, o Consultor Especialista com número mágico 2 negociou o volume igual a 0.2 lotes.

O volume de uma negociação é adicionado às variáveis do Consultor Especialista quando se executa uma negociação, antes e depois da negociação o volume de cada posição era determinado usando da função apresentada acima.

A função gera uma mensagem se houver erro no cálculo de volumes.

O código do Consultor Especialista pode ser encontrado no anexo do artigo (nome do arquivo: ePosVolTest.mq5).

Conclusão

Muitas funções são necessárias a um Consultor Especialista, e elas devem ser implementadas de forma útil para serem usadas em todas as etapas. Essas funções devem ser escritas em termos de melhor uso dos recursos computacionais.

O método de cálculo do volume da posição proposto neste artigo cumpre essas condições - ele carrega apenas o mínimo necessário do histórico das negociações quando iniciado. Ao trabalhar, ele recalcula o volume atual da posição usando as últimas negociações.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/125

Arquivos anexados |
eposvoltest.mq5 (17.98 KB)
Controlando o declive da curva de equilíbrio durante o trabalho de um Expert Advisor Controlando o declive da curva de equilíbrio durante o trabalho de um Expert Advisor
Encontrar regras para um sistema de negócio e programá-las em um Expert Advisor é metade do trabalho. De alguma forma, você precisa corrigir a operação do Expert Advisor conforme ele acumular os resultados da negociação. Este artigo descreve uma das abordagens, que permite melhorar a performance de um Expert Advisor pela criação de um feedback que mede o declive da curva de equilíbrio.
Como Encomendar um Robô de Negociação em MQL5 e MQL4 Como Encomendar um Robô de Negociação em MQL5 e MQL4
A "Freelance" é o maior serviço freelance para a encomenda de robôs de negociação em MQL4 e indicadores técnicos. Centenas de desenvolvedores profissionais estão prontos para desenvolver aplicativos de negociação personalizados para a plataforma MetaTrader 4/5.
Como criar rapidamente um Consultor Especialista para o Campeonato de Negociações Automáticas 2010 Como criar rapidamente um Consultor Especialista para o Campeonato de Negociações Automáticas 2010
A fim de desenvolver um especialista para participar no Automated Trading Championship 2010 (Campeonato de Negociações Automáticas 2010), vamos usar um modelo de um conselheiro especialista pronto. Até mesmo um programador MQL5 iniciante será capaz desta tarefa, porque para suas estratégias as classes básicas, funções e modelos já estão desenvolvidos. é suficiente escrever uma quantidade mínima de código para implementar sua ideia de negociação.
Escrevendo um Expert Advisor utilizando a abordagem de programação orientada a objeto do MQL5 Escrevendo um Expert Advisor utilizando a abordagem de programação orientada a objeto do MQL5
Este artigo foca na abordagem orientada a objeto para fazer o que fizemos no artigo "Guia passo a passo para escrever um Expert Advisor no MQL5 para iniciantes" - criando um simples Expert Advisor. A maior parte das pessoas acha que isso é difícil, mas quero assegurá-lo que quando você terminar de ler esse artigo, você será capaz de escrever o seu próprio consultor especialista que é baseado em orientação a objeto.