English Русский Deutsch
preview
Multibot no MetaTrader (Parte II): Modelo dinâmico aprimorado

Multibot no MetaTrader (Parte II): Modelo dinâmico aprimorado

MetaTrader 5Exemplos | 17 julho 2024, 10:50
54 0
Evgeniy Ilin
Evgeniy Ilin

Conteúdo


Introdução

No último artigo, fui inspirado por algumas das soluções mais populares do Mercado e consegui criar minha própria versão de um modelo desse tipo. Mas, considerando alguns dos projetos nos quais estou trabalhando, essa solução não é ideal. Além disso, ainda apresenta uma série de limitações e inconveniências associadas à arquitetura geral de tal modelo. Esse modelo pode ser suficiente para a grande maioria das soluções medianas, mas não para as minhas. O segundo ponto muito importante é o fato de que, do ponto de vista de um potencial comprador de EA e usuário final, eu pessoalmente gostaria de ver a máxima simplicidade e o mínimo de configurações. Idealmente, tal EA não deveria exigir reotimização e outras manipulações por parte do usuário. Afinal, pago não apenas por uma solução funcional, mas, mais importante, por uma interface o mais amigável possível e compreensível para todos.


Conceito de modelo dinâmico

Com base no EA anterior, vamos lembrar por que tal modelo foi criado. O objetivo principal era a capacidade de lançar um EA com configurações diferentes para cada par "instrumento - período". Isso era necessário para não lançar um EA em cada gráfico separadamente. Isso simplificou a gestão de tal sistema e sua configuração, porque quanto mais cópias do mesmo EA em um terminal, maior a probabilidade de erro potencial do usuário. Além disso, poderiam surgir conflitos entre esses EAs devido, por exemplo, à ordem dos números mágicos ou por outros motivos baseados no fator humano. Vamos primeiro descrever os prós e contras do primeiro modelo. Depois farei o mesmo para o novo modelo, para que as diferenças e vantagens da nova abordagem sejam claramente visíveis.

Prós do modelo estático (base):

  1. Configuração individual de cada instrumento - período (EA virtual em um gráfico virtual).
  2. Código mais simples e rápido (executará mais rápido).
  3. A prevalência da abordagem (amplamente representada no mercado e provou sua viabilidade).

    Contras do modelo estático (base):

    1. Complexidade da configuração e alta probabilidade de erro (longas cadeias de strings nas configurações).
    2. Possibilidades limitadas de expandir a funcionalidade.
    3. Incapacidade de adicionar ou remover robôs virtuais sem reconfiguração manual.

      Entendendo esses dados, podemos ver que a solução é muito limitada, embora difundida. É possível conceber uma solução muito mais interessante. Além disso, integrar meu EA na minha solução, que realiza otimização dinâmica independente fora do terminal MetaTrader, me levou a uma solução aprimorada. Vamos começar com o diagrama que revela a essência do novo modelo e nos ajuda a entender melhor suas vantagens em comparação com a versão anterior.

      uso do novo modelo

      Aqui, por favor, preste atenção ao elemento inferior esquerdo contendo CLOSING. O modelo prevê salvar as configurações nas quais a última posição foi aberta no gráfico virtual correspondente. Este é um mecanismo de proteção especial contra mudanças frequentes de configurações e quebra de estratégia. Se abrimos posições de acordo com alguma lógica, estamos interessados em fechá-las de acordo com a mesma lógica, e só depois disso podemos mudar a configuração para uma mais recente. Caso contrário, poderíamos acabar com uma bagunça absoluta. Sim, isso nem sempre acontece, mas é melhor bloquear esses momentos imediatamente no nível da lógica geral.

      O novo modelo utiliza as capacidades e a estrutura de criação de diretórios de trabalho do MetaTrader 4 e MetaTrader 5 juntamente com o sistema de arquivos. Em outras palavras, gerenciamos nosso modelo usando arquivos de texto que podem conter as configurações necessárias para cada par de negociação virtual. Isso garante a máxima facilidade de configuração e reestruturação dinâmica do EA durante a operação, sem a necessidade de reconfiguração manual. Mas isso não é tudo. Isso também proporciona uma série de benefícios que listarei abaixo.

      Prós do modelo dinâmico (novo):

      1. Cada instrumento-período é configurado usando arquivos de texto independentemente do terminal de negociação.
      2. Leitura dinâmica de configurações de pastas e abertura ou fechamento automático de gráficos virtuais junto com seus EAs.
      3. É possível sincronizar automaticamente com a Web API (requer via porta 443).
      4. A configuração ocorre usando a pasta comum do terminal (*\Common\Files).
      5. O EA líder é necessário para sincronizar todos os terminais em uma máquina com a API. Ele também pode potencialmente funcionar em várias máquinas através de uma pasta compartilhada na rede local.
      6. É possível integrar com programas externos que também podem gerenciar esses arquivos, por exemplo, criá-los ou excluí-los, bem como alterar as configurações.
      7. É possível criar um par de modelos pagos e gratuitos. Se cortarmos a conexão com a API, podemos projetar isso como uma versão demo que exige uma versão paga para funcionar de forma eficaz.

                Contras do modelo dinâmico (novo):

                1. Trabalhando através do sistema de arquivos

                A desvantagem aqui também é muito condicional, porque a única alternativa é a integração web ou a comunicação usando alguns outros métodos (por exemplo, através da RAM), mas neste caso nosso trunfo é que não usamos bibliotecas adicionais, e nosso código se torna multiplataforma (adequado tanto para EAs MQL4 quanto MQL5). Graças a isso, esses EAs cumprem os requisitos do Mercado e podem ser facilmente colocados à venda, se desejado. Claro, você terá que adicionar algumas coisas e ajustá-las de acordo com suas necessidades, mas isso será extremamente fácil de fazer usando minhas percepções fornecidas aqui.

                Vamos dar uma olhada no funcionamento interno de um EA assim, bem como considerar como podemos representar visual e esquematicamente as coisas que mencionei acima. Acho que isso pode ser feito usando o seguinte diagrama:

                estrutura simplificada de manuseio de modelo

                Toda essa esquema funciona de forma bastante simples, com a ajuda de dois temporizadores independentes, cada um responsável por suas próprias tarefas. O primeiro temporizador carrega as configurações da API, enquanto o segundo lê as mesmas configurações na memória do EA. Além disso, duas setas são mostradas, simbolizando a possibilidade de criar e alterar essas configurações manualmente ou automatizar esse processo usando um aplicativo de terceiros ou outro código. Teoricamente, este modelo pode ser controlado a partir de qualquer outro código, como de outro EA ou script do MetaTrader 4 e MetaTrader 5.

                Após ler as configurações, é determinado se essas configurações são novas, ou se há novas configurações na pasta, ou talvez tenhamos excluído algumas delas. Se a comparação mostrar que não há alterações nos arquivos, o modelo continua a funcionar, mas se algo mudou, então seria muito mais correto reiniciar todo o código com a nova configuração e continuar trabalhando. Por exemplo, se tivéssemos um EA que funciona apenas com o gráfico em que opera, teríamos que fazer tudo isso manualmente dentro do terminal. No entanto, neste caso, todo o controle ocorre completamente automaticamente dentro do modelo, sem necessidade de intervenção manual.


                Configurações básicas de modelo dinâmico

                Agora, podemos passar para o código e analisar seus pontos principais. Deixe-me primeiro mostrar o conjunto mínimo de entradas, que, na minha opinião, são suficientes para gerenciar negociações usando a lógica bar-a-bar. Mas antes de fazer isso, é necessário mencionar as importantes paradigmas que esse padrão usa para entender melhor seu código no futuro.

                • Operações de negociação e cálculos de sinais de entrada ocorrem quando um novo bar é aberto (este ponto é calculado automaticamente com base nos ticks para cada gráfico virtual separadamente).
                • Apenas uma posição pode ser aberta em um gráfico por vez (EURUSD M1 e EURUSD M5 são considerados gráficos diferentes).
                • Uma nova posição em um gráfico separado não pode ser aberta até que a anterior seja fechada (considero essa estrutura a mais simples e correta, pois todos os tipos de compras adicionais e médias são já pesos adicionais em certa medida).

                Agora, levando em conta essas regras e restrições, precisamos entender que podemos modificar esse código para trabalhar com médias ou martingale, ou, por exemplo, para lidar com ordens pendentes. Você pode modificar sua estrutura, se necessário. Agora vamos ver nosso conjunto mínimo de parâmetros que nos permitirá gerenciar negociações.

                //+------------------------------------------------------------------+
                //|                         main variables                           |
                //+------------------------------------------------------------------+
                input bool bToLowerE=false;//To Lower Symbol
                input string SymbolPrefixE="";//Symbol Prefix
                input string SymbolPostfixE="";//Symbol Postfix
                
                input string SubfolderE="folder1";//Subfolder In Files Folder
                input bool bCommonReadE = true;//Read From Common Directory
                
                input bool bWebSyncE = false;//Sync with API
                input string SignalDirectoryE = "folder1";//Signal Name(Folder)
                input string ApiDomen = "https://yourdomen.us";//API DOMEN (add in terminal settings!)
                
                input bool bInitLotControl=true;//Auto Lot
                input double DeltaBarPercent=1.5;//Middle % of Delta Equity Per M1 Bar (For ONE! Bot)
                input double DepositDeltaEquityE=100.0;//Deposit For ONE! Bot
                
                input bool bParallelTradingE=true;//Parallel Trading
                
                input int SLE=0;//Stop Loss Points
                input int TPE=0;//Take Profit Points

                Neste exemplo, dividi as variáveis em diferentes blocos com base em características semelhantes. Como podemos ver, não há muitas variáveis. O primeiro bloco é projetado para lidar com diferentes estilos de nomeação de instrumentos para diferentes corretores. Por exemplo, se o seu corretor tiver "EURUSD", então todas as variáveis deste bloco devem permanecer como estão agora, mas outras opções também são possíveis, por exemplo:

                • eurusd
                • EURUSDt
                • tEURUSD
                • _EURUSD
                • eurusd_

                E assim por diante. Não vou continuar, acho que você pode descobrir sozinho. Claro, um bom modelo deve lidar com a maioria dessas variações.

                O segundo subbloco nos informa de qual pasta estamos lendo os arquivos. Além disso, estamos carregando dados da API na mesma pasta. Se especificarmos o nome correto da pasta, o MetaTrader criará o subdiretório correspondente e trabalhará com ele. Se não especificarmos, todos os arquivos estarão localizados na raiz. Um recurso interessante é fornecido pela pasta compartilhada do terminal: se ativarmos esta opção, podemos combinar não apenas vários EAs rodando dentro do MetaTrader 5, mas também todos os terminais, independentemente de estarem rodando no MetaTrader 4 ou MetaTrader 5. Dessa forma, ambos os modelos poderão trabalhar identicamente usando as mesmas configurações, garantindo a máxima integração e sincronização.

                Como você já deve ter adivinhado, o terceiro subbloco habilita/desabilita o temporizador de sincronização com sua API, se houver uma. É importante notar que a comunicação com a API é realizada usando apenas a função WebRequest, que funciona tanto no MQL4 quanto no MQL5. A única limitação aqui é que sua API deve rodar na porta 443. No entanto, no MQL5 esse método foi expandido e tem a capacidade de se conectar através de uma porta diferente. No entanto, abandonei essa ideia na minha API para garantir que meus modelos e soluções construídos sobre ela sejam multiplataforma.

                Construí a API de forma que o nome do sinal fosse também o diretório do arquivo. Dessa forma, posso me conectar a diferentes sinais conhecendo seus nomes. Podemos desconectar do sinal antigo e conectar ao novo a qualquer momento. Claro, não é possível baixar os próprios arquivos dessa forma, mas fiz isso de forma diferente. Recebo JSON com o conteúdo do arquivo e, em seguida, crio-o eu mesmo a partir do código do próprio modelo. Isso requer métodos adicionais para extrair os dados da sua string JSON, mas pessoalmente isso não criou problemas para mim, porque o MQL5 nos permite fazer isso facilmente. Claro, antes de usar nossa API, precisaremos de qualquer domínio e adicioná-lo à lista de conexões permitidas nas configurações do MetaTrader.

                O quarto bloco é muito importante, pois controla os riscos e volumes de entrada. Claro, podemos configurar volumes para cada instrumento-período separadamente usando os mesmos arquivos de texto, mas decidi fazer configurações automáticas usando os dados de volatilidade do par de negociação utilizado. Essa abordagem também envolve um aumento proporcional automático nos volumes junto com o crescimento do nosso saldo - isso é chamado de lote automático.

                O quinto bloco consiste em apenas uma variável. Por padrão, todos os EAs virtuais que trabalham dentro do modelo negociam independentemente e não prestam atenção às posições abertas por EAs que estão em outros gráficos virtuais. Esta variável garante que apenas uma posição pode ser aberta em um EA, outros esperam até que seja fechada e só depois podem abrir a sua própria. Em alguns casos, esse estilo de negociação pode ser extremamente útil.

                O último (quinto) bloco contém apenas configurações de stop. Se os configurarmos para zero, então negociamos sem eles. No sexto bloco, que ainda não existe, você pode adicionar seus parâmetros, se necessário.


                Regras para nomear arquivos com configurações e diretivas aplicadas

                Gostaria de começar com os principais métodos que carregam configurações de nossos arquivos, e como eles são lidos, bem como como esses métodos encontram exatamente os arquivos que precisamos. Para garantir que tais arquivos sejam lidos corretamente, primeiro precisamos introduzir regras para nomeá-los que sejam simples e compreensíveis. Primeiro precisamos encontrar esta diretiva em nosso modelo:

                //+------------------------------------------------------------------+
                //|                     your bot unique name                         |
                //+------------------------------------------------------------------+
                #define BotNick "dynamictemplate" //bot

                Esta diretiva define um apelido de bot. Absolutamente tudo acontece sob este apelido:

                1. Nomeação de arquivos recém-criados,
                2. Leitura de arquivos
                3. Criação de variáveis globais do terminal,
                4. Leitura de variáveis globais do terminal,
                5. Outros.

                Nosso modelo aceitará apenas arquivos "**** dynamictemplate.txt" como suas configurações. Em outras palavras, definimos a primeira regra para nomear arquivos com configurações - o nome do arquivo antes de sua extensão deve sempre terminar em "dynamictemplate". Você é livre para mudar esse nome para qualquer outro que desejar. Assim, se você criar dois EAs com apelidos diferentes, eles ignorarão com segurança as configurações de seu "irmão" e trabalharão apenas com seus próprios arquivos.

                Próxima está outra diretiva similar que alcança o mesmo objetivo:

                //+------------------------------------------------------------------+
                //|               unique shift for difference of EA                  |
                //+------------------------------------------------------------------+
                #define MagicHelp 0 //bot magic shift

                A única diferença é que esta diretiva garante a distinção entre diferentes EAs no nível do número mágico das ordens, assim como nos arquivos, para que dois ou mais EAs não fechem as ordens de outros EAs, se você decidir usar vários EAs dentro de uma conta de negociação. Da mesma forma, ao criar o próximo EA, também devemos alterar esse número juntamente com a alteração do apelido. Apenas não o faça muito grande, é melhor apenas adicionar um a esse número toda vez que criarmos um novo EA com base nesse modelo.

                Em seguida, passamos para a parte do nome do arquivo que informará ao nosso EA para qual gráfico ele se destina. Mas primeiro, vamos dar uma olhada neste array:

                //+------------------------------------------------------------------+
                //|                        applied symbols                           |
                //+------------------------------------------------------------------+
                string III[] = { 
                   "EURUSD",
                   "GBPUSD",
                   "USDJPY",
                   "USDCHF",
                   "USDCAD",
                   "AUDUSD",
                   "NZDUSD",
                   "EURGBP",
                   "EURJPY",
                   "EURCHF",
                   "EURCAD",
                   "EURAUD",
                   "EURNZD",
                   "GBPJPY",
                   "GBPCHF",
                   "GBPCAD",
                   "GBPAUD",
                   "GBPNZD",
                   "CHFJPY",
                   "CADJPY",
                   "AUDJPY",
                   "NZDJPY",
                   "CADCHF",
                   "AUDCHF",
                   "NZDCHF",
                   "AUDCAD",
                   "NZDCAD",
                   "AUDNZD",
                   "USDPLN",
                   "EURPLN",
                   "USDMXN",
                   "USDZAR",
                   "USDCNH",
                   "XAUUSD",
                   "XAGUSD",
                   "XAUEUR"
                };

                Este array tem uma função muito importante de filtragem de arquivos. Em outras palavras, o modelo carregará apenas aqueles gráficos e as configurações correspondentes que estão presentes nesta lista de símbolos. Aqui somos obrigados a fixar mais algumas regras que funcionam igualmente tanto para nomear arquivos com configurações quanto para ajustar a lista especificada.

                1. Todos os nomes dos instrumentos são convertidos para maiúsculas.
                2. Todos os sufixos e prefixos dos instrumentos são removidos, restando apenas os nomes verdadeiros dos instrumentos.

                Vamos olhar o exemplo agora. Suponha que o nome do símbolo "EURUSD" tenha a seguinte aparência no seu corretor: "eurusd_". Isso significa que você ainda nomeia o arquivo de configurações usando "EURUSD", mas adicionalmente faz o seguinte nas configurações:

                //+------------------------------------------------------------------+
                //|                        symbol correction                         |
                //+------------------------------------------------------------------+
                input bool bToLowerE=true;//To Lower Symbol
                input string SymbolPrefixE="";//Symbol Prefix
                input string SymbolPostfixE="_";//Symbol Postfix

                Em outras palavras, você sinaliza ao modelo que dentro do EA nossos nomes serão convertidos para o original convertendo os nomes para minúsculas e adicionando o sufixo apropriado. Isso não acontecerá com frequência, pois os corretores geralmente seguem o esquema clássico de nomeação em maiúsculas sem usar prefixos ou sufixos, simplesmente porque não faz sentido. Mas ainda assim essa opção não está excluída.

                Por enquanto, descobrimos apenas como nomear o arquivo para que o modelo entenda que esta é a configuração necessária do "BotNick", e como combiná-la com o instrumento de negociação correto. Ainda precisamos combinar a configuração com um período específico do gráfico. Para isso, introduzi a seguinte regra:

                • Após o nome do arquivo, coloque um espaço e escreva o equivalente desse período em minutos.
                • A faixa de período do gráfico permitida é M1... H4.

                Acho importante listar especificamente os períodos que estão nesta faixa. Era muito importante para mim que todos esses períodos fossem representados tanto no MetaTrader 4 quanto no MetaTrader 5, este foi o principal motivo para a escolha dos períodos. Outro motivo muito importante foi que períodos muito altos acima de "H4" não são geralmente usados em negociações automáticas de barras. Em qualquer caso, não vi tais exemplos, então a escolha recaiu sobre os seguintes períodos:

                • M1 - 1 minuto
                • M5 - 5 minutos
                • M15 - 15 minutos
                • M30 - 30 minutos
                • H1 - 60 minutos
                • H4 - 240 minutos

                Esses períodos são bastante suficientes. Além disso, para cada um desses períodos é muito fácil calcular seu equivalente em minutos, e esses números são muito fáceis de lembrar. Agora podemos mostrar a estrutura geral do nome do arquivo, de acordo com a qual você criará configurações manualmente ou automaticamente usando código de terceiros ou sua API. Primeiro, vamos dar uma olhada no esboço geral:

                • "INSTRUMENT" + " " + "PERIOD IN MINUTES" + " " + BotNick + ".txt"

                Além disso, vamos dar uma olhada na estrutura que o modelo usará para duplicar os arquivos destinados a fechar posições:

                • "CLOSING" + " " + "INSTRUMENT" + " " + "PERIOD IN MINUTES" + " " + BotNick + ".txt"

                Como você pode ver, esses dois arquivos diferem apenas na adição de "CLOSING" e no espaço separador subsequente. Todas as linhas de sinal no nome são, é claro, separadas por um único espaço para que o interpretador do modelo possa reconhecer e extrair esses marcadores do nome do arquivo. Assim, se uma configuração pertence a um gráfico específico é determinado apenas por seu nome. Vamos agora dar uma olhada em alguns exemplos de configurações que seguem essa regra:

                • EURUSD 15 dynamictemplate.txt
                • GBPUSD 240 dynamictemplate.txt
                • EURCHF 60 dynamictemplate.txt
                • CLOSING GBPUSD 240 dynamictemplate.txt

                Obviamente, isso é suficiente para um exemplo. Por favor, preste atenção ao último nome. Este arquivo será copiado com base em "GBPUSD 240 dynamictemplate.txt" e colocado na pasta do terminal exato em que o EA fez a cópia. Isso é feito para evitar múltiplas escritas nos mesmos arquivos por diferentes, mas idênticos, EAs em vários terminais. Se desativarmos a opção de leitura da pasta compartilhada do terminal, arquivos regulares também serão escritos lá. Isso pode ser necessário se precisarmos configurar cada EA específico no terminal correspondente com seu próprio número independente de configurações. Deixarei vários arquivos ao lado dos templates como exemplo, para que fique mais claro com um exemplo específico e para que você possa experimentá-los, movendo-os para diferentes pastas. Com isso, conclui-se a consideração dos aspectos gerais do uso das configurações.


                Métodos para ler arquivos e criá-los

                Para dominar totalmente a funcionalidade do template, é aconselhável entender como funciona a leitura e escrita de arquivos. Isso, em última análise, nos permitirá começar a usá-los não apenas como marcadores de instrumentos-períodos aos quais queremos anexar robôs virtuais, mas também para personalizar individualmente cada um deles, se desejado. Para isso, podemos começar a considerar o seguinte método.

                //+------------------------------------------------------------------+
                //|                 used for configuration settings                  |
                //+------------------------------------------------------------------+
                bool QuantityConfiguration()
                {
                    FilesGrab(); // Determine the names of valid files
                    
                    // Check if there are changes in the configuration settings (either add or delete)
                    if (bNewConfiguration())
                    {
                        return true;
                    }     
                    return false;
                }

                Este método determina se o conjunto de arquivos em nosso diretório de trabalho foi atualizado. Se sim, então usamos este método como um sinal para reiniciar todos os gráficos e EAs, a fim de adicionar novos ou remover períodos de instrumentos desnecessários. Agora vamos dar uma olhada no método FilesGrab.

                //+------------------------------------------------------------------+
                //|   reads all files and forms a list of instruments and periods    |
                //+------------------------------------------------------------------+
                void FilesGrab()
                   {
                   string file;
                   string tempsubfolder= SubfolderE == "" ? ""  : SubfolderE + "\\"; // SubfolderE is the path to the specific subfolder
                   // Returns the handle of the first found file with the specified characteristics, based on whether CommonReadE is True or False
                   long total_files = !bCommonReadE? FileFindFirst(tempsubfolder+"*"+BotNick+".txt", file) :FileFindFirst(tempsubfolder+"*"+BotNick+".txt", file,FILE_COMMON);
                   if(total_files > 0)
                      {
                         ArrayResize(SettingsFileNames,0); // Clear the array from previous values if there are files to be read
                         do
                         {
                            int second_space = StringFind(file, " ", StringFind(file, " ") + 1); // Searches for the index of the second space in the file's name
                            if(second_space > 0) 
                            {
                                string filename = StringSubstr(file, 0, second_space); // Extracts the string/characters from the filename up to the second space
                                ArrayResize(SettingsFileNames, ArraySize(SettingsFileNames) + 1); // Increases the size of the array by one
                                SettingsFileNames[ArraySize(SettingsFileNames) - 1] = filename; // Adds the new filename into the existing array
                            }
                         }
                         while(FileFindNext(total_files, file)); // Repeat for all the files        
                         FileFindClose(total_files); // Close the file handle to free resources
                      }
                   }  

                Este método realiza uma coleta preliminar dos nomes dos arquivos que se relacionam com nosso EA em algo como "EURUSD 60". Em outras palavras, ele deixa apenas a parte do nome que mais tarde será analisada como um par instrumento-período. A leitura desses arquivos, no entanto, não ocorre aqui, mas dentro de cada EA virtual separadamente. Mas para fazer isso, primeiro precisamos analisar a própria string em um símbolo e um período. Isso é precedido por vários pontos. Um deles é o seguinte.

                //+------------------------------------------------------------------+
                //|                        symbol validator                          |
                //+------------------------------------------------------------------+
                bool AdaptDynamicArrays()
                {
                    bool RR=QuantityConfiguration();
                    // If a new configuration of files is detected (new files, changed order, etc.)
                    if (RR)
                    {
                        // Read the settings (returns the count)
                        int Readed = ArraySize(SettingsFileNames); 
                        int Valid =0;
                
                        // Only valid symbol name needs to be populated (filenames are taken from already prepared array)
                        ArrayResize(S, Readed);
                     
                        for ( int j = 0; j < Readed; j++ )
                        {
                            for ( int i = 0; i < ArraySize(III); i++ )
                            {
                                // check the symbol to valid
                                if ( III[i] == BasicNameToSymbol(SettingsFileNames[j]) )
                                {
                                    S[Valid++]=SettingsFileNames[j];
                                    break; // stop the loop
                                }
                            }
                        } 
                        //resize S with the actual valid quantity
                        ArrayResize(S, Valid);
                        return true;
                    }
                    return false;
                }

                Este método é muito importante para descartar as configurações (gráficos) que não estão presentes em nossa lista de instrumentos permitidos. O ponto é, em última análise, adicionar todos os gráficos que foram filtrados pela lista no array "S", preparando-o para uso posterior no código para criar objetos gráficos virtuais.

                Um ponto importante também é a reserva de configurações, que ocorre constantemente à medida que as configurações básicas são lidas. Se uma posição de configuração básica foi aberta, então paramos a reserva de configurações periódicas. O arquivo de backup com o prefixo "CLOSING" é sempre salvo no diretório atual do terminal.

                //+------------------------------------------------------------------+
                //|         сopy settings from the main file to a CLOSING file       |
                //+------------------------------------------------------------------+
                void SaveCloseSettings()
                   {
                   string FileNameString=Charts[chartindex].BasicName;
                   bool bCopied;
                   string filenametemp;
                   string filename="";
                   long handlestart;   
                   
                   //Checking if SubfolderE doesn't exist, if yes, assign tempsubfolder to be an empty string
                   string tempsubfolder= SubfolderE == "" ? ""  : SubfolderE + "\\"; 
                
                   //Find the first file in the subfolder according to bCommonReadE and assign the result to handlestart 
                   if (bCommonReadE) handlestart=FileFindFirst(tempsubfolder+"*",filenametemp,FILE_COMMON);
                   else handlestart=FileFindFirst(tempsubfolder+"*",filenametemp);
                   
                   //Check if the start of our found file name matches FileNameString 
                   if ( StringSubstr(filenametemp,0,StringLen(FileNameString)) == FileNameString )
                      {
                      //if yes, complete the file's path 
                      filename=tempsubfolder+filenametemp;
                      }
                     //keep finding the next file while conditions are aligned  
                   while ( FileFindNext(handlestart,filenametemp) )
                      {
                      //if found file's name matches FileNameString then add found file's name to the path
                      if ( StringSubstr(filenametemp,0,StringLen(FileNameString)) == FileNameString )
                         {
                         filename=tempsubfolder+filenametemp;
                         break;
                         }
                      }   
                   //if handlestart is not INVALID_HANDLE then close the handle to release the resources after the search
                   if (handlestart != INVALID_HANDLE) FileFindClose(handlestart); 
                
                   //Perform file copy operation and notice if it was successful 
                   if ( bCommonReadE ) bCopied=FileCopy(filename,FILE_COMMON,tempsubfolder+"CLOSING "+FileNameString+".txt",FILE_REWRITE|FILE_TXT|FILE_ANSI);
                   else bCopied=FileCopy(filename,0,tempsubfolder+"CLOSING "+FileNameString+".txt",FILE_REWRITE|FILE_TXT|FILE_ANSI);
                   }

                Aqui vale esclarecer que a configuração de backup funciona de tal maneira que, por exemplo, quando o template é reiniciado ou lido novamente, os dados serão lidos a partir dele. Isso só é possível com uma posição aberta que corresponda a essa configuração. Se uma instância específica de um EA virtual não tiver posições abertas, então o template é sincronizado com a configuração geral.


                Criação de gráficos e EAs virtuais

                Em seguida, recuperamos todos os dados de lá no próximo método, enquanto simultaneamente criamos os gráficos virtuais necessários.

                //+------------------------------------------------------------------+
                //|                      creates chart objects                       |
                //+------------------------------------------------------------------+
                void CreateCharts()
                   {
                   bool bAlready;
                   int num=0;
                   string TempSymbols[];
                   string Symbols[];
                   ArrayResize(TempSymbols,ArraySize(S)); // Resize TempSymbols array to the size of S array
                   for (int i = 0; i < ArraySize(S); i++) // Populate TempSymbols array with empty strings
                      {
                      TempSymbols[i]="";
                      }
                   for (int i = 0; i < ArraySize(S); i++) // Count the required number of unique trading instruments
                      {
                      bAlready=false;
                      for (int j = 0; j < ArraySize(TempSymbols); j++)
                         {
                         if ( S[i] == TempSymbols[j] ) // If any symbol is already present in TempSymbols from S, then it's not unique
                            {
                            bAlready=true;
                            break;
                            }
                         }
                      if ( !bAlready ) // If the symbol is not found in TempSymbols i.e., it is unique, add it to TempSymbols
                         {
                         for (int j = 0; j < ArraySize(TempSymbols); j++)
                            {
                            if ( TempSymbols[j] == "" )
                               {
                               TempSymbols[j] = S[i];
                               break;
                               }
                            }
                         num++; // Increments num if a unique element is added          
                         }
                      }      
                   ArrayResize(Symbols,num); // Resize the Symbols array to the size of the num
                
                   for (int j = 0; j < ArraySize(Symbols); j++) // Now that the Symbols array has the appropriate size, populate it
                      {
                      Symbols[j]=TempSymbols[j];
                      } 
                   ArrayResize(Charts,num); // Resize Charts array to the size of num
                
                   int tempcnum=0;
                   tempcnum=1000; // Sets all charts to a default of 1000 bars
                   Chart::TCN=tempcnum; 
                   for (int j = 0; j < ArraySize(Charts); j++)
                      {
                      Charts[j] = new Chart();
                      Charts[j].lastcopied=0; // Initializes the array position where the last copy of the chart was stored
                      Charts[j].BasicName=Symbols[j]; 
                      ArrayResize(Charts[j].CloseI,tempcnum+2); // Resizes the CloseI array to store closing price of each bar
                      ArrayResize(Charts[j].OpenI,tempcnum+2); // Resizes the OpenI array for opening prices
                      ArrayResize(Charts[j].HighI,tempcnum+2); // HighI array for high price points in each bar
                      ArrayResize(Charts[j].LowI,tempcnum+2); // LowI array for low price points of each bar
                      ArrayResize(Charts[j].TimeI,tempcnum+2); // TimeI array is resized to store time of each bar
                      string vv = BasicNameToSymbol(Charts[j].BasicName); 
                      StringToLower(vv);
                      // Append prefix and postfix to the basic symbol name to get the specific symbol of the financial instrument 
                      Charts[j].CurrentSymbol = SymbolPrefixE +  (!bToLowerE ? BasicNameToSymbol(Charts[j].BasicName) : vv) + SymbolPostfixE;
                      Charts[j].Timeframe = BasicNameToTimeframe(Charts[j].BasicName); // Extracts the timeframe from the basic name string
                      }
                   ArrayResize(Bots,ArraySize(S)); // Resize Bots array to the size of S array
                   }

                Este método foca na criação de uma coleção de instrumentos-períodos não repetidos, com base na qual são criados objetos dos gráficos correspondentes. Além disso, por favor, observe que o tamanho do array de barras para cada gráfico é definido para um pouco mais de 1000 barras. Acredito que isso seja mais do que suficiente para implementar a maioria das estratégias. Se algo acontecer, podemos alterar essa quantidade conforme necessário. Agora vamos consolidar o material com um método que cria objetos de EA virtuais.

                //+------------------------------------------------------------------+
                //|              attaching all virtual robots to charts              |
                //+------------------------------------------------------------------+
                void CreateInstances()
                   {
                   // iterating over the S array
                   for (int i = 0; i < ArraySize(S); i++)
                      {
                      // iterating over the Charts array
                      for (int j = 0; j < ArraySize(Charts); j++)
                         {
                         // checking if the BasicName of current Chart matches with the current item in S array
                         if ( Charts[j].BasicName == S[i] )
                            {
                            // creating a new Bot instance with indices i, j and assigning it to respective position in Bots array
                            Bots[i] = new BotInstance(i,j);
                            break;
                            } 
                         }
                      }
                   }

                Aqui são criados EAs virtuais e anexados aos gráficos correspondentes, armazenando o ID deste gráfico "j" dentro do EA, para que no futuro saibamos de qual gráfico obter dados dentro do EA virtual. Quanto à estrutura interna dessas duas classes, mencionei isso no artigo anterior. De muitas maneiras, o novo código é semelhante a ele, exceto por algumas mudanças insignificantes.


                Leitura dinâmica e reconfiguração de EAs virtuais

                Obviamente, esta seção é extremamente importante para nós, pois representa metade do conceito do novo template. Afinal, criar gráficos e EAs virtuais é apenas metade do trabalho. Parece que entendemos a recriação de gráficos virtuais em um nível mínimo, mas isso não é suficiente. É aconselhável descobrir como pegar novas configurações instantaneamente e reconfigurar o EA sem interferir no terminal de negociação. Para resolver esse problema, é utilizado um simples temporizador, conforme ilustrado nos diagramas no início do artigo.

                //+------------------------------------------------------------------+
                //|             we will read the settings every 5 minutes +          |
                //+------------------------------------------------------------------+
                bool bReadTimer()
                   {
                   if (  TimeCurrent() - LastTime > 5*60 + int((double(MathRand())/32767.0) * 60) )
                      {
                      LastTime=TimeCurrent();
                      int orders=OrdersG();
                      bool bReaded=false;
                      if (orders == 0)  bReaded = ReadSettings(false,Charts[chartindex].BasicName);//reading a regular file
                      else bReaded = ReadSettings(true,Charts[chartindex].BasicName);//reading file to close position
                      if (orders == 0 && bReaded) SaveCloseSettings();//save settings for closing position
                      return bReaded;
                      }
                   return false;
                   }

                Como você pode ver, o temporizador é acionado a cada "5" minutos, o que significa que novos arquivos não serão captados instantaneamente. No entanto, na minha opinião, este intervalo é suficiente para garantir dinamismo. Se isso não for suficiente para você, pode reduzi-lo para até um segundo. A única coisa que você deve entender é que o uso frequente de operações de arquivo não é encorajado e essa abordagem deve ser evitada sempre que possível. Neste código, preste atenção ao método ReadSettings. Ele lê o arquivo necessário (para cada EA virtual separadamente) e depois reconfigura o EA após a leitura. O método é projetado de tal forma que pode ler as configurações gerais (se não houver posições abertas no EA virtual selecionado) ou pausar a atualização das configurações e esperar até que a posição seja fechada de acordo com as configurações pelas quais esta posição foi criada.

                //+------------------------------------------------------------------+
                //|                        reading settings                          |
                //+------------------------------------------------------------------+
                bool BotInstance::ReadSettings(bool bClosingFile,string Path)
                   {
                   string FileNameString=Path;
                   int Handle0x;
                   string filenametemp;
                   string filename="";
                   long handlestart;
                            
                   string tempsubfolder= SubfolderE == "" ? ""  : SubfolderE + "\\"; 
                        
                   if (!bClosingFile)//reading a regular file
                      {
                      if (!bCommonReadE)
                         {
                         handlestart=FileFindFirst(tempsubfolder+"*",filenametemp);         
                         int SearchStart=0;
                         
                         if ( StringSubstr(filenametemp,SearchStart,StringLen(FileNameString)) == FileNameString )
                            {
                            filename=tempsubfolder+filenametemp;
                            }
                         if (filename != filenametemp || filename == "")
                            {
                            while ( FileFindNext(handlestart,filenametemp) )
                               {
                               if ( StringSubstr(filenametemp,SearchStart,StringLen(FileNameString)) == FileNameString )
                                  {
                                  filename=tempsubfolder+filenametemp;
                                  break;
                                  }
                               }         
                            }
                         if (handlestart != INVALID_HANDLE) FileFindClose(handlestart);// Release resources after search
                         
                         if (filename != "")
                            {
                            Handle0x=FileOpen(filename,FILE_READ|FILE_SHARE_READ|FILE_TXT|FILE_ANSI);
                            
                            if ( Handle0x != INVALID_HANDLE )//if the file exists
                               {
                               FileSeek(Handle0x,0,SEEK_SET);
                               ulong size = FileSize(Handle0x);
                               string str = "";
                               for(ulong i = 0; i < size; i++)
                                  {
                                     str += FileReadString(Handle0x);
                                  }
                               if (str != "" && str != PrevReaded)
                                  {
                                  FileSeek(Handle0x,0,SEEK_SET);
                                  //read the required parameters
                                  ReadFileStrings(Handle0x);
                                  //                     
                                  FileClose(Handle0x);
                                  LastRead = TimeCurrent();
                                  RestartParams();
                                  }
                               else
                                  {
                                  FileClose(Handle0x);
                                  }
                               return true;
                               }
                            else
                               {
                               return false;
                               }         
                            }         
                         }
                      else
                         {
                         handlestart=FileFindFirst(tempsubfolder+"*",filenametemp,FILE_COMMON);   
                         int SearchStart=0;
                         
                         if ( StringSubstr(filenametemp,SearchStart,StringLen(FileNameString)) == FileNameString )
                            {
                            filename=tempsubfolder+filenametemp;
                            }
                         if (filename != filenametemp || filename == "")
                            {
                            while ( FileFindNext(handlestart,filenametemp) )
                               {
                               if ( StringSubstr(filenametemp,SearchStart,StringLen(FileNameString)) == FileNameString )
                                  {
                                  filename=tempsubfolder+filenametemp;
                                  break;
                                  }
                               }         
                            }
                         if (handlestart != INVALID_HANDLE) FileFindClose(handlestart);// Release resources after search
                         
                         if (filename != "")
                            {
                            Handle0x=FileOpen(filename,FILE_READ|FILE_SHARE_READ|FILE_TXT|FILE_ANSI|FILE_COMMON);
                            
                            if ( Handle0x != INVALID_HANDLE )//if the file exists
                               {
                               FileSeek(Handle0x,0,SEEK_SET);
                               ulong size = FileSize(Handle0x);
                               string str = "";
                               for(ulong i = 0; i < size; i++)
                                  {
                                     str += FileReadString(Handle0x);
                                  }
                               if (str != "" && str != PrevReaded)
                                  {
                                  FileSeek(Handle0x,0,SEEK_SET);
                                  //read the required parameters
                                  ReadFileStrings(Handle0x);
                                  //      
                                  FileClose(Handle0x);
                                  LastRead = TimeCurrent();
                                  RestartParams();
                                  }
                               else
                                  {
                                  FileClose(Handle0x);
                                  }
                               return true;
                               }
                            else
                               {
                               return false;
                               }         
                            }         
                         }
                      }         
                   else//reading a file to close a position
                      {
                      handlestart=FileFindFirst(tempsubfolder+"*",filenametemp);   
                      int SearchStart=8;//when the line starts with "CLOSING "
                      
                      if ( StringLen(filenametemp) >= (8 + StringLen(FileNameString)) && StringSubstr(filenametemp,0,8) == "CLOSING " 
                      && StringSubstr(filenametemp,SearchStart,StringLen(FileNameString)) == FileNameString )
                         {
                         filename=tempsubfolder+filenametemp;
                         }
                      if (filename != filenametemp || filename == "")
                         {
                         while ( FileFindNext(handlestart,filenametemp) )
                            {
                            if ( StringLen(filenametemp) >= (8 + StringLen(FileNameString)) && StringSubstr(filenametemp,0,8) == "CLOSING " 
                            && StringSubstr(filenametemp,SearchStart,StringLen(FileNameString)) == FileNameString )
                               {
                               filename=tempsubfolder+filenametemp;
                               break;
                               }
                            }         
                         }
                      if (handlestart != INVALID_HANDLE) FileFindClose(handlestart);// Release resources after search
                      
                      if (filename != "")
                         {
                         Handle0x=FileOpen(filename,FILE_READ|FILE_SHARE_READ|FILE_TXT|FILE_ANSI);
                         
                         if ( Handle0x != INVALID_HANDLE )//if the file exists
                            {
                            FileSeek(Handle0x,0,SEEK_SET);
                            ulong size = FileSize(Handle0x);
                            string str = "";
                            for(ulong i = 0; i < size; i++)
                               {
                                  str += FileReadString(Handle0x);
                               }
                            if (str != "" && str != PrevReaded)
                               {
                               PrevReaded=str;
                               FileSeek(Handle0x,0,SEEK_SET);
                               //read the required parameters
                               ReadFileStrings(Handle0x);
                               //
                               FileClose(Handle0x);
                               LastRead = TimeCurrent();
                               RestartParams();                  
                               }
                            else
                               {
                               FileClose(Handle0x);
                               }
                            return true;
                            }
                         else
                            {
                            return false;
                            }         
                         }         
                      }         
                   return false;   
                   }

                Primeiramente, quero enfatizar que este método é especificamente projetado para ler dois tipos de arquivos. Dependendo do marcador bClosingFile passado, é lido ou a configuração geral ou a configuração "para fechamento". Cada leitura de arquivo consiste em várias etapas:

                1. Comparação do conteúdo do arquivo lido anteriormente com o atual;
                2. Se o conteúdo for diferente, lemos nossas configurações atualizadas;
                3. Reiniciamos nosso EA virtual com novas configurações, se necessário.

                O método é construído de tal forma que a limpeza de recursos e outras ações já estão planejadas. Nós só precisamos implementar o próximo método, que é chamado no método anterior. Eu tentei fazer tudo de forma a poupar você do incômodo dessas operações de arquivo, para que você possa se concentrar o máximo possível em escrever exatamente o código de leitura que você precisa. A leitura é realizada aqui.

                //+------------------------------------------------------------------+
                //|               read settings from file line by line               |
                //+------------------------------------------------------------------+
                void BotInstance::ReadFileStrings(int handle)
                   {
                   //FileReadString(Handle,0);
                   
                   }

                Não há necessidade de abrir ou fechar o arquivo aqui. Tudo o que você precisa fazer é ler o arquivo linha por linha e adicionar corretamente o que você leu nas variáveis apropriadas. Para isso, podemos usar tanto variáveis temporárias quanto escrever imediatamente todos os dados nas variáveis que você usará como configurações para sua estratégia. Mas eu recomendaria preencher as configurações já neste método, que foi projetado para esses fins.

                //+------------------------------------------------------------------+
                //|                function to prepare new parameters                |
                //+------------------------------------------------------------------+
                void BotInstance::RestartParams() 
                {
                   //additional code
                
                   //
                   MagicF=SmartMagic(BasicNameToSymbol(Charts[chartindex].BasicName), Charts[chartindex].Timeframe);
                   CurrentSymbol=Charts[chartindex].CurrentSymbol;
                   m_trade.SetExpertMagicNumber(MagicF);
                }

                Não há necessidade de mexer nas últimas três strings, pois são obrigatórias. O mais interessante ali é o método SmartMagic, que é projetado para atribuir automaticamente números mágicos a cada um dos EAs virtuais. Tudo o que precisamos saber nesta fase é que precisamos escrever essa lógica para reatribuir as configurações do EA um pouco mais acima - no bloco vazio. Se necessário, também podemos trabalhar na recriação dos indicadores e tudo mais que ainda estiver lá.


                Geração automática de números mágicos

                Sem nos afastarmos do método anterior, quero revelar imediatamente um método para gerar IDs de ordem únicos que garantam negociação independente para todos os EAs virtuais dentro do template. Para isso, utilizei o seguinte método. 

                Eu atribuo um passo de "10000", por exemplo, entre os números mágicos mais próximos. Para cada instrumento, primeiro registro seu número mágico preliminar, por exemplo, "10000" ou "70000". No entanto, isso não é suficiente, pois o instrumento também tem um período. Portanto, adiciono mais um número a este número mágico intermediário. 

                A maneira mais simples é adicionar o equivalente em minutos desses períodos, assim como na estrutura de leitura de arquivos. É assim que é feito.

                //+------------------------------------------------------------------+
                //|              Smart generation of magical numbers                 |
                //|    (each instrument-period has its own fixed magic number)       |
                //+------------------------------------------------------------------+
                int BotInstance::SmartMagic(string InstrumentSymbol,ENUM_TIMEFRAMES InstrumentTimeframe)
                {
                   // initialization
                   int magicbuild=0;
                   
                   // loop through the array
                   for ( int i=0; i<ArraySize(III); i++ )
                   {
                      // check the symbol to assign a magic number
                      if ( III[i] == InstrumentSymbol )
                      {
                          magicbuild=MagicHelp+(i+1)*10000;
                          break; // stop the loop
                      }
                   }  
                   
                   // add identifier for time frame    
                   magicbuild+=InstrumentTimeframe;      
                   return magicbuild;
                }

                É aqui que nosso deslocamento adicional de números mágicos aparece para tornar os conjuntos de números mágicos únicos, mesmo entre diferentes EAs dentro do terminal.

                //+------------------------------------------------------------------+
                //|               unique shift for difference of EA                  |
                //+------------------------------------------------------------------+
                #define MagicHelp 0 //bot magic shift [0...9999]

                Em geral, tudo é bastante simples. O grande passo entre os números mágicos oferece uma quantidade bastante grande de opções de deslocamento, mais do que suficiente para criar o número necessário de EAs. A única condição para usar essa estrutura é não exceder o número "9999". Além disso, precisamos garantir que o deslocamento não coincida com nossos equivalentes de períodos em minutos, pois nesse caso pode haver coincidências nos números mágicos de dois templates diferentes. Para não pensar nessas opções, podemos simplesmente fazer um deslocamento um pouco maior que "240", por exemplo, "241", "2412", "2413", "241*N".

                Para resumir esta abordagem, podemos ver que essa estrutura nos livra completamente da configuração de números mágicos, que era um dos objetivos intermediários não mencionados desta solução. A única desvantagem é a impossibilidade de conectar dois ou mais EAs virtuais independentes, pois seus números mágicos acabarão coincidindo, levando eventualmente à interação dessas estratégias e, como resultado, à quebra de sua lógica. Na verdade, não sei quem poderia precisar de um movimento tão exótico. Além disso, isso não se encaixa na funcionalidade originalmente pretendida. Talvez eu adicione isso no próximo artigo, se alguém estiver interessado.


                Sistema de normalização de volume

                Se eu tiver um modelo que seja simples e fácil de personalizar, então é extremamente importante escolher o método correto para definir os volumes de negociação. É muito interessante que as conclusões finais dos meus artigos sobre teoria da probabilidade, especificamente deste artigo, me ajudaram a realizar uma equalização simples e eficaz dos volumes. Decidi equalizar os volumes, assumindo que a duração média e o tamanho do valor absoluto do resultado financeiro final da posição devem ser semelhantes, o que significa que a única solução correta para essa equalização deve ser baseada nas seguintes considerações.

                A taxa média de aumento ou diminuição do patrimônio do gráfico final de negociação deve ser fornecida igualmente por cada um dos EAs (instrumento-período independente) incluídos na montagem final. Todos os valores necessários devem ser calculados sem usar dados daqueles períodos de instrumento que não aparecem na lista dos nossos gráficos virtuais. Os volumes devem ser distribuídos não apenas proporcionalmente ao número de EAs, mas também proporcionalmente ao depósito (lote automático).

                Para isso, é importante introduzir imediatamente as seguintes definições e equações. Para começar, implementei os seguintes parâmetros para ajustar os riscos:

                • DeltaBarPercent - porcentagem do DepositDeltaEquity,
                • DepositDeltaEquity - depósito de um robô para calcular seu delta de patrimônio aceitável para um bar de M1 com uma posição aberta.

                Os termos podem parecer obscuros no início. Então, deixe-me esclarecer. Por conveniência, especificamos um depósito com o qual um EA virtual separado trabalha e indicamos em percentagem que parte deste depósito deve aumentar ou diminuir (na forma de nosso patrimônio) se abríssemos uma posição e houvesse um movimento do ponto alto do bar "M1" para o ponto baixo ou vice-versa.

                O objetivo do nosso código é selecionar automaticamente os volumes de entrada levando em consideração nossos requisitos. Para isso, precisaremos de quantidades matemáticas adicionais e equações baseadas nelas. Não farei conclusões, apenas fornecerei para explicar o código:

                • "Mb" - tamanho médio dos bars em pontos no intervalo de histórico selecionado do tamanho "bars" no gráfico de trabalho do EA,
                • "Mb1" - tamanho médio dos bars em pontos no intervalo de histórico selecionado do tamanho "bars" no gráfico de trabalho do EA recalculado para M1,
                • "Kb" - relação de conexão entre o tamanho médio dos bars do gráfico atual e seu equivalente "M1",
                • "T" - período do gráfico selecionado reduzido para um equivalente de minuto (assim como tínhamos em nossos arquivos),
                • "BasisI" - aumento ou diminuição média necessária na linha de patrimônio na moeda do depósito para o tamanho médio da vela M1 no gráfico do instrumento selecionado,
                • "Basisb" - aumento ou diminuição média real na linha de patrimônio na moeda do depósito para o tamanho médio da vela M1 no gráfico do instrumento selecionado para um comércio de tamanho "1" lote,
                • "Lot" - lote selecionado (volume).

                Agora que listei todas as quantidades usadas no cálculo, vamos começar a analisar e compreender isso. Para calcular o lote necessário, antes de tudo, devemos entender como é feita a relação entre os tamanhos dos bars em prazos mais altos em relação a "M1". A seguinte equação ajudará aqui.

                Fator de escala para ir para M1

                Este é exatamente o termo que permite, sem carregar dados de "M1", calcular a mesma característica, embora no período apresentado do gráfico virtual com o qual o EA virtual selecionado trabalha. Após multiplicar por esse fator, obtemos quase os mesmos dados como se os calculássemos no período "M1". Este é o primeiro passo a ser feito. O método para calcular este valor será assim:

                //+------------------------------------------------------------------+
                //|       timeframe to average movement adjustment coefficient       |
                //+------------------------------------------------------------------+
                double PeriodK(ENUM_TIMEFRAMES tf)
                   {
                   double ktemp;
                   switch(tf)
                      {
                      case  PERIOD_H1:
                          ktemp = MathSqrt(1.0/60.0);
                          break;
                      case  PERIOD_H4:
                          ktemp = MathSqrt(1.0/240.0);
                          break;
                      case PERIOD_M1:
                          ktemp = 1.0;
                          break;
                      case PERIOD_M5:
                          ktemp = MathSqrt(1.0/5.0);
                          break;
                      case PERIOD_M15:
                          ktemp = MathSqrt(1.0/15.0);
                          break;
                      case PERIOD_M30:
                          ktemp = MathSqrt(1.0/30.0);
                          break;
                      default: ktemp = 0;
                      }
                   return ktemp;
                   }

                Agora, é claro, precisamos entender qual valor estamos adaptando para "M1". Aqui está.

                Em outras palavras, calculamos o tamanho médio das velas em pontos no gráfico com o qual o EA virtual trabalha. Após calcular o valor, devemos convertê-lo usando o valor anterior dessa forma.

                Ambas essas ações ocorrem no seguinte método.

                //+------------------------------------------------------------------+
                //|     average candle size in points for M1 for the current chart   |
                //+------------------------------------------------------------------+
                double CalculateAverageBarPoints(Chart &Ch)
                   {
                   double SummPointsSize=0.0;
                   double MaxPointSize=0.0;
                   for (int j = 0; j < ArraySize(Ch.HighI); j++)
                      {
                      if (Ch.HighI[j]-Ch.LowI[j] > MaxPointSize) MaxPointSize= Ch.HighI[j]-Ch.LowI[j];
                      }
                        
                   for (int j = 0; j < ArraySize(Ch.HighI); j++)
                      {
                      if (Ch.HighI[j]-Ch.LowI[j] > 0) SummPointsSize+=(Ch.HighI[j]-Ch.LowI[j]);
                      else SummPointsSize+=MaxPointSize;
                      }  
                   SummPointsSize=(SummPointsSize/ArraySize(Ch.HighI))/Ch.ChartPoint;
                   return PeriodK(Ch.Timeframe)*SummPointsSize;//return the average size of candles reduced to a minute using the PeriodK() adjustment function
                   }

                Agora podemos usar o valor resultante, reduzido para M1, para calcular a variável "Basisb". Deve ser calculado assim.

                O tamanho do tick é a quantidade de mudança no patrimônio de uma posição aberta com um volume de "1" lote quando o preço se move por "1" ponto. Se multiplicarmos isso pelo tamanho médio de uma vela de minuto, obteremos o valor da mudança no patrimônio para uma posição com um único lote, levando em consideração que o tamanho do movimento se tornou igual ao tamanho médio do bar de minuto. Em seguida, devemos calcular o valor restante de "BasisI", e isso é feito assim.

                O percentual e o depósito nele são exatamente os parâmetros de controle, com os quais exigimos a base que precisamos. Tudo o que resta a fazer é selecionar uma proporção de proporcionalidade para que as bases sejam iguais, e esta proporção será nosso lote final.

                Todas as operações descritas são realizadas no seguinte método.

                //+------------------------------------------------------------------+
                //|    calculate the optimal balanced lot for the selected chart     |
                //+------------------------------------------------------------------+
                double OptimalLot(Chart &Ch)
                   {
                   double BasisDX0 =  (DeltaBarPercent/100.0) * DepositDeltaEquityE;
                   double DY0=CalculateAverageBarPoints(Ch)*SymbolInfoDouble(Ch.CurrentSymbol,SYMBOL_TRADE_TICK_VALUE);
                   return BasisDX0/DY0;
                   }

                Dessa forma, equalizamos efetivamente os volumes de todos os instrumentos para uma contribuição igual ao patrimônio de todos os EAs virtuais. Isso resulta em algo como um modo de lote fixo, como se o tivéssemos configurado separadamente para cada EA, mas neste caso nos livramos dessa necessidade. Este é um sistema de controle de risco completamente diferente, adaptado ao comércio diversificado. Usando-o, você terá que fazê-lo de qualquer maneira se desejar alcançar a estabilidade ótima da curva de lucro e se livrar dessa rotina. De fato, equilibrar vários EAs é o momento mais sensível. Acredito que aqueles que fizeram coisas semelhantes apreciarão essa decisão. De qualquer forma, se este método de balanceamento não lhe convier, você sempre pode escrever suas configurações nos arquivos apropriados e modificar este sistema.


                Lote Automático

                O lote automático pode ser aumentado proporcionalmente ao depósito. Para entender como, introduzirei as seguintes notações:

                • - Lot - lote normalizado (equilibrado) para um EA virtual específico;
                • - AutoLot - recalculado para o depósito "Lot" no modo de lote automático (precisamos recebê-lo quando o lote automático está ativado;
                • - DepositPerOneBot - parte do depósito atual (parte do Deposit), que pode ser controlada por apenas um dos EAs virtuais;
                • - DepositDeltaEquity - depósito, contra o qual realizamos a normalização (lotes balanceados);
                • - Deposit - nosso banco atual;
                • - BotQuantity - número atual de EAs virtuais negociando dentro do multibot.

                Então você pode escrever o que nosso "AutoLot" é igual a:

                • AutoLot = Lot * (DepositPerOneBot / DepositDeltaEquity).

                Assim, no caso do volume normalizado usual, ignoramos nosso depósito e aceitamos que o seguinte depósito seja alocado para um EA virtual - DepositDeltaEquity. Mas no caso do lote automático, este depósito não é real, e devemos mudar proporcionalmente os volumes normalizados para que nossos riscos se adaptem ao depósito real. No entanto, precisa ser adaptado levando em consideração que um EA virtual representa apenas uma parte do depósito.

                • DepositPerOneBot = Deposit / BotQuantity.

                É assim que o lote automático funciona no meu modelo. Acredito que este método é bastante conveniente e proporciona o ajuste necessário para a íngreme curva de crescimento exponencial. Você pode encontrar o código-fonte no anexo. Vamos ver o resultado do ajuste adequado desses valores.

                Lote automático com volumes normalizados

                Por favor, observe que a curva de lucro neste modo se parecerá aproximadamente como mostrado aqui, desde que as configurações sejam corretas e o sinal inicial seja lucrativo. Esta curva será mais suave e se assemelhará mais a uma curva exponencial quanto maior for o fator de lucro em sua estratégia e quanto mais negociações ela tiver em sua área de teste. Isso é exatamente o que a diversificação alcança muito bem. Nosso modelo contém todos os fundamentos para maximizar esse efeito. Além disso, preste atenção ao carregamento do depósito: é sua suavidade e uniformidade que indicam indiretamente a normalização correta e a subsequente escalada da carga atual do depósito. Tirei este exemplo do meu produto baseado no modelo mencionado.


                Sincronização com API

                Esta funcionalidade é opcional e pode ser facilmente desativada ou completamente removida do modelo, mas pessoalmente achei muito útil para meu produto. Como mencionado anteriormente, a sincronização também é acionada através do temporizador.

                //+------------------------------------------------------------------+
                //|            used to read the settings every 5 minutes +           |
                //+------------------------------------------------------------------+
                void DownloadTimer()
                {
                    // Check if the passed time from the last download time is more than 5 minutes
                    if (  TimeCurrent() - LastDownloadTime > 5*60 + int((double(MathRand())/32767.0) * 60) )
                    {
                        // Set the last download time to the current time
                        LastDownloadTime=TimeCurrent();
                        // Download files again
                        DownloadFiles();
                     }
                } 

                Agora vamos analisar o método principal DownloadFiles.

                //+------------------------------------------------------------------+
                //|       used to download control files if they isn't present       |
                //+------------------------------------------------------------------+
                void DownloadFiles()
                {
                    string Files[];
                    // Initialize the response code by getting files from the signal directory
                    int res_code=GetFiles(SignalDirectoryE,Files); 
                
                    // Check if the list of files is successfully got
                    if (res_code == 200)
                    {
                        // Proceed if there is at least one file in the server
                        if (ArraySize(Files) > 0)
                        {
                            // Download each file individually
                            for (int i = 0; i < ArraySize(Files); i++)
                            {
                                string FileContent[];
                                // Get the content of the file
                                int resfile =  GetFileContent(SignalDirectoryE,Files[i],FileContent);
                
                                // Check if the file content is successfully got
                                if (resfile == 200)
                                {
                                    // Write the file content in our local file
                                    WriteData(FileContent,Files[i]);
                                }
                            }
                        }
                    }
                }

                Eu organizei toda a estrutura de tal forma que o primeiro passo é acessar a API para descobrir a lista completa de arquivos que estão na pasta especificada em nosso servidor. Isso distribuirá as configurações. O nome da pasta é SignalDirectoryE. Este também é o nome do sinal de acordo com minha ideia. Após receber a lista de arquivos, cada um deles é baixado separadamente. Na minha opinião, essa lógica de construção é muito conveniente. Dessa forma, podemos criar muitos sinais (pastas) e alternar entre eles a qualquer momento. Como fazer e organizar isso fica a seu critério. Minha tarefa é fornecer funcionalidade pronta para conexão fácil. Agora vamos examinar o modelo de método que obtém uma lista de nomes de arquivos de nosso servidor.

                //+------------------------------------------------------------------+
                //|              getting the list of files into an array             |
                //+------------------------------------------------------------------+
                int GetFiles(string directory,string &fileArray[])
                   {
                   //string for getting a list of files in the form of JSON via GET to API 
                   string urlList = ApiDomen+"/filelist/"+directory;//URL
                   char message[];//Body of the request
                   string headers = "Password_key: " + key;// We form the headers of the request
                   string resultheaders = "";//returning headers          
                   string cookie = "";//cookies
                   int timeout = 1500;//waiting for a response when requesting a file or json
                   char result[];
                   
                   // We send a GET request to the server to receive JSON with a list of files
                   int res_code =  WebRequest("GET", urlList, headers, timeout, message, result, resultheaders);
                   bool rez = extractFiles(CharArrayToString(result),fileArray);
                   if (rez) return res_code;
                   else return 400;
                   }

                Tudo o que você precisa fazer aqui é formar sua string "URL" da mesma maneira. As partes mais importantes aqui são as seguintes strings:

                • filelist
                • Password_key

                A primeira string é uma das funções do sua API. Você pode mudar seu nome se quiser. Você terá várias funções desse tipo. Por exemplo, para fornecer as seguintes operações:

                1. Carregar arquivos de configurações para sua API (do seu aplicativo ou programa individual).
                2. Limpar arquivos de configurações na sua API (do seu aplicativo ou programa individual).
                3. Carregar listas de arquivos de um diretório existente (do seu EA).
                4. Carregar conteúdo de arquivo (do seu EA).

                Pode haver outras funções que você precise, mas especificamente, apenas as duas últimas serão necessárias no EA. A segunda string é um dos "headers". Você também precisará formá-lo com base no que você passa para a API. Como regra geral, tudo o que você precisa é uma chave de acesso para evitar que alguém invada. Mas você pode adicionar mais informações se precisar transmitir dados adicionais. A string recebida do servidor deve ser analisada aqui.

                //+------------------------------------------------------------------+
                //|                  get file list from JSON string                  |
                //+------------------------------------------------------------------+
                bool extractFiles(string json, string &Files[])
                   {
                
                   return false;
                   }

                Recebemos JSON e o analisamos para extrair os nomes. Infelizmente, não há um código de análise universal. Cada caso é individual. Pessoalmente, não considero escrever o código de análise muito difícil. É claro que é bom ter bibliotecas apropriadas, mas pessoalmente prefiro escrever o máximo possível de código por conta própria. Agora vamos analisar um método semelhante que obtém o conteúdo do arquivo.

                //+------------------------------------------------------------------+
                //|                    getting the file content                      |
                //+------------------------------------------------------------------+
                int GetFileContent(string directory,string filename,string &OutContent[])
                   {
                   //string for getting a file content in the form of JSON via GET to API 
                   string urlList = ApiDomen+"/file_content/"+directory+"/"+filename;//
                   char message[];// Body of the request
                   string headers = "Password_key: " + key;// We form the headers of the request
                   string resultheaders = "";//returning headers             
                   string cookie = "";//cookies
                   int timeout = 1500;//waiting for a response when requesting a file or json
                   char result[];
                   
                   // We send a GET request to the server to receive JSON with a file content
                   int res_code =  WebRequest("GET", urlList, headers, timeout, message, result, resultheaders);
                   bool rez = extractContent(CharArrayToString(result),OutContent);
                   if (rez) return res_code;
                   else return 400;
                   } 

                Tudo é exatamente igual ao anterior, exceto que agora obtemos não uma lista de arquivos, mas uma lista de strings dentro do arquivo. É claro, o processo de analisar o JSON com o conteúdo do arquivo, linha por linha, é realizado por uma lógica separada no método seguinte, que tem o mesmo propósito que seu "irmão" - "extractFiles".

                //+------------------------------------------------------------------+
                //|   read the contents of the file from JSON each line separately   |
                //+------------------------------------------------------------------+ 
                bool extractContent(string json, string &FileLines[])
                   {
                   
                   return false;
                   }

                Claro, você não precisa seguir exatamente o que eu digo. Eu já tenho um produto construído exatamente assim. Usar um exemplo de trabalho específico me parece muito mais fácil para entender tudo isso. Após receber o conteúdo do arquivo, você pode escrevê-lo com segurança, linha por linha, usando o seguinte método, que já está integrado na lógica do modelo.

                //+-----------------------------------------------------------------------+
                //|    fill the file with its lines, which are all contained in data      |
                //|  with a new line separator, and save in the corresponding directory   |
                //+-----------------------------------------------------------------------+
                void WriteData(string &data[],string FileName)
                   {
                   int fileHandle;
                   string tempsubfolder= SubfolderE == "" ? ""  : SubfolderE + "\\"; 
                   
                   if (!bCommonReadE)
                      {
                      fileHandle = FileOpen(tempsubfolder+FileName,FILE_REWRITE|FILE_WRITE|FILE_TXT|FILE_ANSI);
                      }
                   else
                      {
                      fileHandle = FileOpen(tempsubfolder+FileName,FILE_REWRITE|FILE_WRITE|FILE_TXT|FILE_ANSI|FILE_COMMON);
                      } 
                      
                   if(fileHandle != INVALID_HANDLE) 
                       {
                       FileSeek(fileHandle,0,SEEK_SET);
                       for (int i=0; i < ArraySize(data) ; i++)
                          {
                          FileWriteString(fileHandle,data[i]+"\r\n");
                          }
                       FileClose(fileHandle);
                       }
                   }
                
                

                ssim é como um arquivo é criado pelo seu nome. E é claro, seu conteúdo também é escrito nele na forma de strings separadas. Esta é uma solução completamente adequada para arquivos pequenos. Não notei nenhum atraso durante seu funcionamento.


                Método geral com lógica de negociação

                Eu mencionei esse problema no artigo anterior, mas quero destacá-lo novamente e lembrar que este método funciona quando uma nova barra é aberta em cada EA virtual. Podemos considerá-lo como um manipulador do tipo OnTick, mas no nosso caso será, é claro, OnBar. A propósito, não há esse manipulador em MQL5. Ele funciona um pouco diferente do que gostaríamos, mas isso não tem realmente um impacto significativo na negociação por barra, então este é o menor dos nossos problemas.

                //+------------------------------------------------------------------+
                //|      the main trading function of individual robot instance      |
                //+------------------------------------------------------------------+
                void BotInstance::Trade() 
                {
                   //data access
                   
                   //Charts[chartindex].CloseI[0]//current bar (zero bar is current like in mql4)
                   //Charts[chartindex].OpenI[0]
                   //Charts[chartindex].HighI[0]
                   //Charts[chartindex].LowI[0]
                   //Charts[chartindex]. ???
                   
                   //close & open
                   
                   //CloseBuyF();
                   //CloseSellF();
                   //BuyF();
                   //SellF();   
                
                   // Here we can include operations such as closing the buying position, closing selling position and opening new positions.
                   // Other information from the chart can be used for making our buying/selling decisions.
                   
                   // Here is a simple trading logic example
                   if ( Charts[chartindex].CloseI[1] > Charts[chartindex].OpenI[1] )
                   {
                      CloseBuyF();
                      SellF();
                   }
                   if ( Charts[chartindex].CloseI[1] < Charts[chartindex].OpenI[1] )
                   {
                      CloseSellF();
                      BuyF();
                   }      
                } 

                Eu implementei alguma lógica básica dentro do modelo para que você possa construir o seu próprio usando-o como exemplo. Eu aconselho adicionar sua própria lógica e variáveis ao lado deste método na classe BotInstance, para evitar confusão. ecomendo construir sua lógica usando métodos e variáveis que você usará no método principal de negociação (Trade).


                Interface gráfica

                O modelo, assim como a versão anterior, contém um exemplo de uma interface de usuário simples, cujo esquema de cores e conteúdo podem ser alterados. Esta interface é idêntica para ambos os modelos: tanto para o MetaTrader 4 quanto para o MetaTrader 5, e ela foi melhorada visualmente.

                GUI

                Perguntas indicam lugares onde você pode adicionar dados adicionais ou remover blocos desnecessários. Isso é muito fácil de fazer. Existem dois métodos para trabalhar com a interface: CreateSimpleInterface e UpdateStatus. Eles são muito simples. Não mostrarei eles em ação. Você pode encontrá-los pelos respectivos nomes.

                Adicionei três campos muito úteis a esta interface. Se você olhar para as últimas três strings, verá seu "corredor de números mágicos reservados", que é relevante para a configuração atual que você está usando. Se deletarmos ou adicionarmos arquivos de configuração, então, consequentemente, este corredor se estreitará ou se expandirá. Além disso, precisamos proteger de alguma forma diferentes EAs de conflitos, e este campo nos ajudará com isso. Os dois campos restantes sinalizam a última vez que alguma das configurações foi lida e o momento da última sincronização com nossa API, desde que a sincronização ocorra.


                Conclusão

                Neste artigo, chegamos a um modelo de template mais conveniente e funcional, que, entre outras coisas, é adequado para expansão e modificação futuras. O código ainda está longe de ser perfeito, e há muito a ser otimizado e corrigido, mas mesmo levando tudo isso em conta, tenho várias ideias claramente formadas sobre o que adicionar lá e para que finalidade. Para o próximo artigo, pretendo virtualizar as posições e, com base nesses dados, obter um otimizador cruzado de moedas que otimizará cada um de nossos EAs individualmente e gerará arquivos prontos com configurações para nossas estratégias.

                O otimizador cruzado de moedas nos permitirá otimizar simultaneamente todos os instrumentos-períodos virtuais. Ao mesclar todas essas configurações, obteremos uma curva de lucro melhor e mais segura, com riscos reduzidos. Considero a diversificação automática e a melhoria do lucro uma prioridade absoluta para futuras melhorias. Como resultado, gostaria de obter um complemento básico sobre qualquer estratégia que permita extrair o máximo de lucro dela, mantendo ao mesmo tempo máxima funcionalidade e conveniência para o usuário final. Isso deveria ser algo como um exoesqueleto para nosso sinal de negociação.

                Links

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

                Arquivos anexados |
                DynamicTemplate.zip (45.56 KB)
                Rede neural na prática: Pseudo Inversa (II) Rede neural na prática: Pseudo Inversa (II)
                Por conta do fato, de que estes artigos visam a didática. E não para mostrar como implementar esta ou aquela funcionalidade. Vamos fazer algo um pouco diferente aqui. Em vez de mostrar como implementar a fatoração para conseguir a inversa de uma matriz. Vamos focar em como fatorar a pseudo inversa. O motivo é que não faz sentido, mostrar como fatorar algo de forma genérica. Se podemos fazer a mesma coisa de forma especializada. E melhor, será algo que você, conseguirá entender muito mais do por que as coisas serem como são. Então vamos ver por que um hardware aparece depois de um tempo, em substituição a um software.
                Operações baseadas em ângulos para traders Operações baseadas em ângulos para traders
                Este artigo abordará operações baseadas em ângulos. Vamos examinar métodos para construir ângulos e utilizá-los no trading.
                Do básico ao intermediário: Variáveis (I) Do básico ao intermediário: Variáveis (I)
                Muitos programadores em inicio tem muitas dificuldades em compreender, por que seus códigos não funcionam como eles esperam. Existem muitos detalhes que torna um código de fato funcional. Não é somente digitar toda uma série de funções e operação que faz um código funciona. Então que tão aprender da maneira correta, como se cria um código real. Ao invés de ficar copiando e colando partes de código encontrados aqui e ali? O conteúdo exposto aqui, visa e tem como objetivo, pura e simplesmente a didática. De modo algum deve ser encarado como sendo, uma aplicação cuja finalidade não venha a ser o aprendizado e estudo dos conceitos mostrados.
                Usando algoritmos de otimização para configurar parâmetros de EA em tempo real Usando algoritmos de otimização para configurar parâmetros de EA em tempo real
                O artigo discute os aspectos práticos do uso de algoritmos de otimização para encontrar os melhores parâmetros de EA em tempo real, bem como a virtualização das operações de negociação e da lógica do EA. O artigo pode ser usado como instrução para implementar algoritmos de otimização em um EA.