Usando Pseudo-modelos como Alternativa para Modelos C++

14 fevereiro 2014, 15:17
Nikolay Demko
0
783

Introdução

A questão da implementação de modelos como um padrão da linguagem foi destacado por muitas vezes no fórum mql5.com. Como eu enfrentei a parede da recusa dos desenvolvedores do MQL5, meu interesse na implementação de modelos utilizando métodos personalizados começou a crescer. O resultado de meus estudos é apresentado neste artigo.


Um pouco da história de C e C++

Desde o início, a linguagem C foi desenvolvida para oferecer a possibilidade de realizar tarefas do sistema. Os criadores da linguagem C não implementaram um modelo abstrato de ambiente de execução da linguagem, eles só implementaram recursos para as necessidades dos programadores de sistema. Primeiro de tudo, aqueles são os métodos de trabalho direto com a memória, as construções da estrutura de controlamento e de gestão de módulos de aplicações.

Na verdade, nada mais foi incluso na linguagem, todas as outras coisas foram levadas para a biblioteca de tempo de execução. é por isso que algumas pessoas mal-intencionadas, às vezes, se referem à linguagem C como a montadora estruturada. Mas o que quer que eles digam, a abordagem parece ser muito bem sucedida. Devido a isso, chegou-se a um novo nível de relação entre a simplicidade e o poder da linguagem.

Assim, a C aparece como uma linguagem universal para programação do sistema. Mas ela não fica dentro desses limites. No final dos anos 80, impulsionando a Fortran à parte da liderança, a C ganhou uma grande popularidade entre os programadores de todo o mundo e tornou-se amplamente utilizada em diferentes aplicações. Uma contribuição significativa para a sua popularidade foi feita pela distribuição de Unix (e assim a linguagem C) nas universidades, onde uma nova geração de programadores foram educados.

Mas se tudo é tão claro, então por que todas as outras linguagens ainda são utilizadas e o que apoia a sua existência? O calcanhar de Aquiles da linguagem C está se tronando de muito baixo nível para os problemas manifestados nos anos 90. Este problema tem dois aspectos.

Na primeira mão, a linguagem contém também os meios de baixo nível: em primeiro lugar, é o trabalho com a memória e aritmética local. Assim, uma alteração no número de bits do processador provoca uma série de problemas para muitas aplicações de C. Por outro lado, existe a falta de meios de alto nível em C - tipos abstratos de dados e objetos, polimorfismo e manipulação das excepções. Assim, em aplicações C uma técnica de implementação de uma tarefa muitas vezes domina o seu lado material.

As primeiras tentativas para corrigir essas desvantagens foram feitas no início dos anos 80. Naquela época, Bjarne Stroustrup na AT & T Bell Labs começou a desenvolver a extensão da linguagem C chamada de "C com classes". O estilo de desenvolvimento correspondeu com o espírito da criação da linguagem C em si - a adição de características diferentes para tornar o trabalho de determinados grupos de pessoas mais conveniente.

A principal inovação em C++ é o mecanismo de classes que dá a possibilidade de utilização de novos tipos de dados. Um programador descreve a representação interna do objeto de classe e o conjunto dos métodos de função para acessar essa representação. Um dos principais objetivos da criação do C++ foi aumentar a proporção da reutilização do código já escrito.

A inovação da linguagem C++ não consiste em apenas introdução de classes. Ela tem implementado mecanismo de manipulação estrutural de exceções (a falta dela complica o desenvolvimento de aplicações à prova de falhas), o mecanismo de modelos e muitas outras coisas. Assim, a principal linha de desenvolvimento da linguagem foi dirigida a prolongar suas possibilidades através da introdução de novas construções de alto nível com a manutenção da total compatibilidade com ANSI С.


Modelo como um Mecanismo de Substituição de Macro

Para entender como implementar um modelo em MQL5, você precisa entender como eles funcionam em C++.

Vamos ver a definição.

Os modelos são uma característica da linguagem de programação C++, que permite que as funções e classes operem com tipos genéricos. Isso permite que uma função ou classe trabalhe em muitos tipos de dados diferentes, sem serem reescritas para cada um.

MQL5 não tem modelos, mas isso não significa que é impossível usar um estilo de programação com modelos. O mecanismo de modelos na linguagem C++ é, na verdade, um mecanismo sofisticado de geração de macro profundamente incorporado na linguagem. Em outras palavras, quando um programador usa um molde, o compilador determina o tipo de dados no qual a função correspondente é chamada, e não onde ela é declarada.

Os modelos foram introduzidos em C++ para diminuir a quantidade de códigos escritos por programadores. Mas você não deve esquecer que um código digitado em um teclado por um programador não é o mesmo para o que foi criado pelo compilador. O mecanismo de moldes em si não resultou na diminuição do tamanho dos programas, ele só diminuiu o tamanho do seu código fonte. é por isso que o principal problema resolvido usando modelos está diminuindo o código digitado pelos programadores.

Uma vez que o código da máquina é gerado durante a compilação, os programadores usuais não veem se o código de uma função é gerado uma ou várias vezes. Durante a compilação de código modelo, o código da função é gerado tantas vezes quantos forem os tipos em que o modelo foi utilizado. Basicamente, um modelo é prioritário na estágio da compilação.

O segundo aspecto da introdução de modelos em C++ é a alocação de memória. O questão é que a memória na linguagem C é determinada de maneira estática. Para fazer essa alocação mais flexível, um modelo que define o tamanho da memória para matrizes é usado. Mas este aspecto já foi implementado pelos desenvolvedores de MQL4 sob a forma de matrizes dinâmicas, e também tem sido feito em MQL5 sob a forma de objetos dinâmicos.

Assim, só o problema da substituição dos tipos permanece sem solução. Os criadores de MQL5 recusados de resolvê-lo, referem que o uso do mecanismo de substituição do modelo iria permitir a fratura do compilador, o que levaria a aparição de um decompilador.

Bem, eles conhecem isso melhor. E nós temos apenas uma escolha - implementar esse paradigma em uma forma personalizada.

Primeiro de tudo, deixe-me fazer uma observação de que nós não vamos mudar o compilador ou alterar as normas da linguagem. Sugiro mudar a abordagem para os próprios modelos. Se não podemos criar modelos em estágio de compilação, isso não significa que não estamos autorizados a escrever o código da máquina. Sugiro mover o uso de moldes a partir da parte de geração do código binário para o lado, no qual o código de texto está escrito. Vamos chamar essa abordagem de "pseudo-modelos".


Pseudo-modelos

Um pseudo-modelo tem suas vantagens e desvantagens, em comparação com um modelo de C++. As desvantagens incluem manipulações adicionais com o movimento de arquivos. As vantagens incluem possibilidades mais flexíveis do que o determinado pelas normas da linguagem. Vamos passar das palavras aos atos.

Para usar pseudo-modelos, precisamos de um análogo do pré-processador. Vamos usar o script dos "modelos" para esta finalidade. Aqui estão os requisitos gerais para o script: ele deve ler um arquivo especificado (mantendo a estrutura de dados), encontrar um modelo e substituí-lo por tipos especificados.

Aqui eu preciso fazer uma observação. Uma vez que vamos usar o mecanismo predominante ao invés dos modelos, o código vai ser reescrito tantas vezes quantos forem os tipos que devem ser substituídos. Em outras palavras, a substituição será realizada em todo o código fornecido para a análise. Depois, o código vai ser reescrito várias vezes pelo script, cada vez que criar uma nova substituição. Assim, podemos compreender o slogan "trabalho manual realizado por máquinas".


Desenvolvendo o Código do Script

Vamos determinar as variáveis​de entrada necessárias:

  1. Nome de um arquivo para ser processado.
  2. Uma variável para armazenar o tipo de dado a ser substituído.
  3. Nome de um modelo que irá ser usado em vez de tipos reais de dados.
input string folder="Example templat";//name of file for processing

input string type="long;double;datetime;string"
                 ;//names of custom types, separator ";"
string TEMPLAT="_XXX_";// template name

Para fazer script multiplicar apenas uma parte do código, defina os nomes dos marcadores. O marcador de abertura destina-se a indicar o início da parte a ser processada, e o marcador de fechamento - para indicar o seu fim.

Enquanto estive usando o script, enfrentei o problema de leitura dos marcadores.

Durante a análise, descobri que, quando está se formatando um documento em MetaEditor, um espaço ou tabulação (dependendo da situação), é frequentemente adicionada às linhas de comentário. O problema foi resolvido com a exclusão de espaços antes e depois de um símbolo importante na determinação dos marcadores. Este recurso é realizado como automático no script, mas existe uma observação.

Um nome de marcador não deve começar ou terminar com um espaço.

Um marcador de fechamento não é obrigatório e, se ele estiver ausente, o código será processado até ao final do arquivo. Mas tem de haver uma abertura. Uma vez que os nomes dos marcadores são constantes, eu uso o pré-processador diretivo #define ao invés das variáveis.

#define startread "//start point"
#define endread "//end point"

Para formar uma matriz de tipos, eu criei a função de anulamento ParserInputType(int i,string &type_d[],string text), que preenche as matrizes type_dates [] com valores usando o 'tipo' variável.

Uma vez que o script recebe o nome de um arquivo e marcadores, ele começa a ler o arquivo. Para salvar a formatação do documento, o script lê a informação linha por linha, poupando linhas encontradas na matriz.

Claro, você pode esvaziar em uma só variável, mas, neste caso, você vai perder a hifenização e o texto vai se transformar em uma linha interminável. é por isso que a função da leitura do arquivo usa a matriz de strings que muda seu tamanho a cada iteração de começar uma nova string.

//+------------------------------------------------------------------+
//| downloading file                                                 |
//+------------------------------------------------------------------+
void ReadFile()
  {
   string subfolder="Templates";
   int han=FileOpen(subfolder+"\\"+folder+".mqh",FILE_READ|FILE_SHARE_READ|FILE_TXT|FILE_ANSI,"\r"); 
   if(han!=INVALID_HANDLE)
     {
      string temp="";
      //--- scrolling file to the starting point
      do {temp=FileReadString(han);StringTrimLeft(temp);StringTrimRight(temp);}
      while(startread!=temp);

      string text=""; int size;
      //--- reading the file to the array until a break point or the end of the file
      while(!FileIsEnding(han))
        {
         temp=text=FileReadString(han);
         // deleting symbols of tabulation to check the end
         StringTrimLeft(temp);StringTrimRight(temp);
         if(endread==temp)break;
         // flushing data to the array
         if(text!="")
           {
            size=ArraySize(fdates);
            ArrayResize(fdates,size+1);
            fdates[size]=text;
           }
        }
      FileClose(han);
     }
   else
     {
      Print("File open failed"+subfolder+"\\"+folder+".mqh, error",GetLastError());
      flagnew=true;
     }
  }

Para maior comodidade de uso, o arquivo é aberto no modo FILE_SHARE_READ. Ele dá uma possibilidade de iniciar o script sem fechar o arquivo editado. A extensão do arquivo é especificada como 'mqh'. Assim, o script lê diretamente o texto do código que é armazenado no arquivo de inclusão. A questão é que um arquivo com a extensão 'mqh' é, na verdade, um arquivo de texto, você pode torná-lo certo, basta mudar o nome do arquivo em 'txt' e abrir o arquivo 'mqh' usando qualquer editor de texto.

No final da leitura, o comprimento da matriz é igual ao número de linhas entre os marcadores de início e fim.

O nome do arquivo aberto deve ter a extensão "templat", caso contrário, o arquivo inicial será substituído e todas as informações serão perdidas.

Agora vamos voltar para a análise de informações. A função que analisa e substitui a informação é chamada a partir da função de gravação de um arquivo nulo WriteFile(int contam). Comentários são determinados dentro da função.

void WriteFile(int count)
  {
   ...
   if(han!=INVALID_HANDLE)
     {
      if(flagnew)// if the file cannot be read
        {
         ...
        }
      else
        {// if the file exists
         ArrayResize(tempfdates,count);
         int count_type=ArraySize(type_dates);
         //--- the cycle rewrites the contents of the file for each type of the type_dates template
         for(int j=0;j<count_type;j++)
           {
            for(int i=0;i<count;i++) // copy data into the temporary array
               tempfdates[i]=fdates[i];
            for(int i=0;i<count;i++) // replace templates with types
               Replace(tempfdates,i,j);

            for(int i=0;i<count;i++)
               FileWrite(han,tempfdates[i]); // flushing array in the file
           }
        }
     ...
  }

Uma vez que os dados são substituídos em seu lugar e a matriz é alterada após a transformação, vamos trabalhar com uma cópia do mesmo. Aqui, definiremos o tamanho da matriz tempfdates[] usada para o armazenamento temporário de dados e preenchê-la de acordo com exemplo fdates[].

Em seguida, a substituição de moldes usando a função Replace() é executada. Os parâmetros da função são os seguintes: matriz a ser processada (em que a substituição do molde é realizada), o contador de linhas i (para mover dentro da matriz), e o contador de tipos j (para navegar através da matriz de tipos).

Como temos dois ciclos aninhados, o código fonte é impresso tantas vezes quantos forem os tipos especificados.

//+------------------------------------------------------------------+
//| replacing templates with types                                   |
//+------------------------------------------------------------------+
void Replace(string &temp_m[],int i,int j)
  {
   if(i>=ArraySize(temp_m))return;
   if(j<ArraySize(type_dates))
      StringReplac(temp_m[i],TEMPLAT,type_dates[j]);// replacing  templat with types   
  }

A função Replace() contém restrições (para evitar a chamada de um índice inexistente de uma matriz) e ele chama a função aninhada StringReplac(). Há uma razão pela qual o nome da função é semelhante à função padrão StringReplace, eles também têm o mesmo número de parâmetros.

Assim, através da adição de uma única letra "e", podemos alterar toda a lógica de substituição. A função padrão recebe o valor do exemplo 'find' e o substitui pela string especificada de 'replacement'. E a minha função não só substitui, mas analisa se há símbolos antes de 'find' (ou seja, verifica se 'find' é uma parte de uma palavra), e se houver, ele substitui 'find' com 'replacement', mas em letras maiores, normalmente é realizada como está. Portanto, além de definir tipos, você pode usá-los em nome de dados substituídos.


Inovações

Agora deixe-me contar sobre as inovações que foram adicionadas durante o uso. Eu já mencionei que houveram problemas de leitura de marcadores enquanto o script era usado.

O problema é resolvido pelo seguinte código dentro da função nula ReadFile():

      string temp="";
      //--- scrolling the file to the start point
      do {temp=FileReadString(han);StringTrimLeft(temp);StringTrimRight(temp);}
      while(startread!=temp);

O próprio ciclo foi implementado na versão anterior, mas o corte dos símbolos de tabulação usando as funções StringTrimLeft() e StringTrimRight() só apareceu na versão melhorada.

Além disso, as inovações incluem cortar a extensão "templat" do nome do arquivo de saída, logo, o arquivo de saída está pronto para ser utilizado. Ele é implementado utilizando a função de deleção de um exemplo especificado a partir de uma string especificada.

Código de função de deleção:

//+------------------------------------------------------------------+
//| Deleting the 'find' template from the 'text' string              |
//+------------------------------------------------------------------+
string StringDel(string text,const string find)
  {
   string str=text;
   StringReplace(str,find,"");
   return(str);
  }

O código que executa o corte de um nome de arquivo está localizado na função nula WriteFile(int count):

   string newfolder;
   if(flagnew)newfolder=folder;// if it is the first start, create an empty file of pre-template
   else newfolder=StringDel(folder," templat");// or create the output file according to the template

Além disso, o modo de preparação de um pré-modelo, é introduzido. Se o arquivo necessário não existe no diretório de Modelos/Arquivos, irá ser formado como um arquivo pré-modelo.

Exemplo:

//#define _XXX_ long
 
//this is the start point
 _XXX_
//this is the end point
   

O código que cria linhas está localizado na função nula WriteFile(int count):

      if(flagnew)// if the file couldn't be read
        {// fill the template file with the pre-template
         FileWrite(han,"#define "+TEMPLAT+" "+type_dates[0]);
         FileWrite(han," ");
         FileWrite(han,startread);
         FileWrite(han," "+TEMPLAT);
         FileWrite(han,endread);
         Print("Creating pre-template "+subfolder+"\\"+folder+".mqh");
        }

A execução do código é protegida pela flagnew variável global, que assume o valor 'true' se houve um erro de leitura do arquivo.

Enquanto estive usando o script, eu inclui um modelo adicional. O processo de ligação do segundo modelo é o mesmo. As funções que exigem alterações neles são colocadas mais próximas da função OnStart() para conexão de um modelo adicional. Uma rota para a conexão de novos modelos é usada. Assim, temos a possibilidade de conectar quantos modelos precisarmos. Agora vamos verificar o funcionamento.


Verificação de Funcionamento

Primeiro de tudo, vamos começar o script especificando todos os parâmetros necessários. Na janela que aparece, especifique o nome do arquivo "Exemple templat".

Preencha os campos dos tipos personalizados de dados usando o ';' separador.

Janela de começo do script

Assim que o botão "OK" for pressionado, o diretório de modelos é criado, ele contém o arquivo pré-modelo "Exemple templat.mqh".

Este evento é apresentado no diário com a mensagem:

Mensagens do diário

Vamos mudar o pré-modelo e iniciar o script novamente. Desta vez, o arquivo já existe no diretório de modelos (assim como o próprio diretório), é por isso que a mensagem sobre o erro de abertura do arquivo não será exibida. A reposição será realizada de acordo com o modelo especificado:

//this_is_the_start_point
 _XXX_ Value_XXX_;
//this_is_the_end_point

Abra o arquivo criado "Example.mqh" mais uma vez.

 long ValueLONG;
 double ValueDOUBLE;
 datetime ValueDATETIME;
 string ValueSTRING;

Como se pode ver, 4 linhas são feitas a partir de uma linha de acordo com o número de tipos que passamos como parâmetro. Agora escreva duas linhas a seguir no arquivo modelo:

//this_is_the_start_point
 _XXX_ Value_XXX_;
 _XXX_ Type_XXX_;
//this_is_the_end_point

O resultado demonstra a lógica da operação do script de uma maneira clara.

Primeiro de tudo, todo o código é reescrito com um tipo de dado, então o processamento de um outro tipo é realizado. Isto é feito até que todos os tipos sejam processados.

 long ValueLONG;
 long TypeLONG;
 double ValueDOUBLE;
 double TypeDOUBLE;
 datetime ValueDATETIME;
 datetime TypeDATETIME;
 string ValueSTRING;
 string TypeSTRING;

Agora, inclua o segundo molde no texto do exemplo.

//this_is_the_start_point
 _XXX_ Value_XXX_(_xxx_ ind){return((_XXX_)ind);};
 _XXX_ Type_XXX_(_xxx_ ind){return((_XXX_)ind);};

 //this_is_the_end_button

Resultado:

 long ValueLONG(int ind){return((long)ind);};
 long TypeLONG(int ind){return((long)ind);};
 
 double ValueDOUBLE(float ind){return((double)ind);};
 double TypeDOUBLE(float ind){return((double)ind);};
 
 datetime ValueDATETIME(int ind){return((datetime)ind);};
 datetime TypeDATETIME(int ind){return((datetime)ind);};
 
 string ValueSTRING(string ind){return((string)ind);};
 string TypeSTRING(string ind){return((string)ind);};

No último exemplo, eu intencionalmente coloquei um espaço após a última linha. Esse espaço demonstra onde o roteiro termina o processamento de um tipo e começa a processar outro. Em relação ao segundo modelo, podemos notar que o processamento de tipos é realizado de forma semelhante ao primeiro modelo. Se um tipo de correspondente não for encontrado para um tipo do primeiro modelo, nada é impresso.

Agora eu quero esclarecer a questão da depuração do código. Os exemplos dados são bastante simples para depuração. Durante a programação, você pode precisar depurar uma parte bem grande do código, e multiplicá-lo assim que ele estiver acabado. Para fazê-lo, há uma linha comentada reservada no pré-modelo: "//#define _XXX_ long".

Se você remover os comentários, o nosso modelo se tornará um tipo real. Em outras palavras, vamos dizer ao compilador como o modelo deve ser interpretado.

Infelizmente, não podemos depurar todos os tipos dessa forma. Mas podemos depurar um tipo e, em seguida, mudar o tipo de modelo em 'define', de modo que possamos depurar todos os tipos, um por um. Claro que, para a depuração, é preciso mover o arquivo para o diretório do arquivo chamado ou para o diretório de inclusão. Este é o inconveniente da depuração o qual mencionei antes, ao falar sobre as desvantagens dos pseudo-modelos.


Conclusão

Concluindo, eu quero dizer que, embora a ideia de usar o pseudo-modelos seja interessante e bastante produtiva, é apenas a ideia com um pequeno início de implementação. Embora o código descrito acima esteja funcionando e tenha economizado um monte de horas ao escrever o código para mim, muitas questões ainda estão abertas. Primeiro de tudo, é a questão do desenvolvimento dos padrões.

Meu script implementa a substituição de blocos dos modelos. Mas esta abordagem não é obrigatória. Você pode criar um analisador mais complexo que interpreta certas regras. Mas aqui é o começo. Espero uma grande discussão. A concepção prospera no conflito. Boa sorte!

Traduzido do russo pela MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/ru/articles/253

Arquivos anexados |
templates.mq5 (9.91 KB)
Usando Indicadores MetaTrader 5 com Estrutura de Aprendizado de Máquina ENCOG para Previsão das Séries Temporais Usando Indicadores MetaTrader 5 com Estrutura de Aprendizado de Máquina ENCOG para Previsão das Séries Temporais

Este artigo apresenta a conexão do MetaTrader 5 para ENCOG - Rede neural avançada e estrutura de aprendizado de máquina. Ele contém a descrição e implementação de um simples indicador de rede neural com base em indicadores técnicos padrão e um Expert Advisor baseado em um indicador neural. Todos os códigos fonte, binários compilados, DLLs e uma rede treinada exemplar estão ligados ao artigo.

Modelo de regressão universal para predição do preço do mercado Modelo de regressão universal para predição do preço do mercado

O preço de mercado é formado pelo estável equilíbrio entre demanda e fornecimento que, por sua vez, depende de uma variedade de fatores econômicos, políticos e psicológicos. As diferenças na natureza também como causas de influência destes fatores dificultam considerar diretamente todos os componentes. Este artigo estabelece uma tentativa de prever o preço de mercado, com base em um modelo de regressão elaborada.

O papel das distribuições estatísticas no trabalho de negociação O papel das distribuições estatísticas no trabalho de negociação

Este artigo é uma continuação lógica do meu artigo de Distribuições de probabilidade estatística em MQL5 que apresenta as classes para trabalhar com algumas distribuições estatísticas teóricas. Agora que temos uma base teórica, sugiro que devemos prosseguir diretamente para conjuntos de dados reais e tentar fazer algum uso informativo desta base.

Filtragem de Sinais com Base em Dados Estatísticos de Correlação de Preço Filtragem de Sinais com Base em Dados Estatísticos de Correlação de Preço

Existe alguma correlação entre o comportamento do preço passado e suas futuras tendências? Por que o preço repete hoje a característica de seu movimento do dia anterior? A estatística pode ser usada para prever a dinâmica de preço? Existe uma resposta, e é positiva. Se tiver alguma dúvida, então, este artigo é para você. Vou lhe dizer como criar um filtro de trabalho para um sistema de negócio no MQL5, revelando um padrão interessante nas mudanças de preço.