Integração de um EA em MQL e bancos de dados (SQL Server, .NET e C#)
Introdução. EAs em MQL e bancos de dados
Nos fóruns, ocasionalmente há perguntas sobre como integrar o trabalho com bancos de dados a EAs escritos em MQL5. O interesse neste tópico não é surpreendente. Os bancos de dados são muito bons como meio para salvar dados. Ao contrário dos logs de terminal, os dados não desaparecem dos bancos de dados. Eles são fáceis de classificar e filtrar. Através do banco de dados, o EA pode enviar as informações, por exemplo, certos comandos. E o mais importante é que os dados obtidos podem ser tanto analisados de diferentes ângulos quanto processados estatisticamente. Por exemplo, a fim de descobrir o lucro médio e o lucro total de um tempo especificado para cada par de moedas, basta escrever uma solicitação numa linha, o que leva literalmente um minuto. Agora imagine quanto tempo leva calculá-lo manualmente usando o histórico da conta no terminal de negociação.
Infelizmente, não existem meios regulares para interagir com os servidores do banco de dados nos terminais MetaTrader. O problema é resolvido apenas através da importação de funções a partir de arquivos DLL. Embora a tarefa não seja fácil, é realizável.
Eu tive que fazer isso mais de uma vez, e neste artigo eu compartilho minha experiência. Como exemplo, descreverei a organização da interação entre EAs em MQL5 e o servidor de banco de dados Microsoft SQL Server. Para criar o arquivo DLL a partir do qual os EAs importarão funções para trabalhar com o banco de dados, foram usadas a plataforma Microsoft.NET e a linguagem de programação C#. No artigo, descrevo detalhadamente a criação de um projeto, a preparação de um arquivo DLL, bem como a importação de funções dele para um EA programado em MQL5. O código do EA de exemplo é muito simples. Para compilá-lo em MQL4, você precisa fazer alterações mínimas.
Trabalho preparatório
Para trabalhar, precisamos do seguinte:
- MetaTrader 5 instalado e conta de negociação ativa. Podemos usar não apenas uma conta de demonstração, mas também uma conta real — usando um EA de testes, não arriscamos nosso depósito.
- Uma instância instalada de um servidor de banco de dados Microsoft SQL Server. Podemos usar o servidor de banco de dados em outro computador conectando-nos a ele pela rede. Podemos baixar do site da Microsoft e instalar a versão gratuita do Express Edition — para a maioria dos usuários, suas limitações são insignificantes. Ele pode ser baixado neste site: https://www.microsoft.com/pt-br/sql-server/sql-server-editions-express. Às vezes, a Microsoft altera os links para seu site, portanto, se um link direto não funcionar, basta digitar em qualquer mecanismo de pesquisa uma frase como "Baixar o SQL Server Express". Se você estiver instalando o SQL Server pela primeira vez, podem surgir algumas dificuldades de instalação. Em particular, em versões mais antigas do sistema operacional, ele pode solicitar a instalação de componentes adicionais (em especial, PowerShell e .NET 4.5). Também às vezes há um conflito entre o SQL Server e o VS C++ 2017, neste caso, o instalador pede restaurar o C++. Isso pode ser feito através de "Painel de controle", "Programas", "Programas e componentes", "VS C++ 2017", "Alterar", "Recuperar". Problemas são pouco frequentes e fáceis de resolver.
- Ambiente de desenvolvimento usando .NET e C #. Eu uso o Microsoft Visual Studio (ele também tem uma versão gratuita), portanto, vou dar exemplos para ele. Podemos usar um ambiente de desenvolvimento diferente e até outra linguagem de programação. Mas, nesse caso, teremos de pensar por conta própria como implementar os exemplos dados em nosso ambiente e no idioma escolhido.
- Ferramenta para exportar funções de arquivos .net DLL para código não gerenciado. EAs em MQL não sabem como trabalhar com código gerenciado .NET. Por esse motivo, o arquivo DLL resultante precisará ser especialmente preparado, fornecendo a capacidade de exportar funções. Na web, são descritas várias maneiras de fazer isso. Eu usei o pacote "UnmanagedExports" criado pelo programador Robert Giesecke. Se você estiver usando o Microsoft Visual Studio versão 2012 ou superior, poderá adicioná-lo ao projeto diretamente do menu do ambiente de desenvolvimento. Vou falar mais tarde como fazer isso.
Além de instalar os programas necessários, precisamos ter em mente mais uma coisa. Por vários motivos, o pacote "UnmanagedExports" não pode funcionar se, nas configurações de idioma do seu computador, estiver selecionado "Russo (Rússia)" como o idioma para programas que não suportam Unicode. Pode haver problemas com outros idiomas, se não for "Inglês (EUA)". Para instalá-lo, abra o painel de controle. Encontre a aba "Regional and Language Standards", a partir daí vá para a aba "Advanced". Na aba "Idioma dos programas que não suportam Unicode", clique em "Alterar idioma do sistema ...". Se estiver definido como "Inglês (EUA)", tudo está bem. Se alguma outra coisa estiver definida, mude para "Inglês (EUA)" e reinicie o computador.
Se isso não for feito, quando o projeto for compilado durante a execução do script "UnmanagedExports", nos arquivos ".il" serão gerados os erros de sintaxe. Eles não podem ser consertados. Mesmo que seu projeto seja completamente simples e não haja erros no código em C#, os erros ainda aparecerão nos arquivos ".il", e você não poderá exportar funções do projeto para código não gerenciado.
Isso se aplica somente a aplicativos de 64 bits. Os de 32 bits podem ser processados por outros meios para os quais não precisa ser alterado o idioma do sistema. Por exemplo, o programa DllExporter.exe é adequado, ele pode ser baixado usando o link: https://www.codeproject.com/Articles/37675/Simple-Method-of-DLL-Export-without-C-CLI.
Alterar o idioma do sistema tornará alguns aplicativos inoperantes. Infelizmente, vamos ter de lidar com esse inconveniente, mas não por muito tempo. Alterar o idioma só é necessário ao compilar o projeto. Após a compilação bem-sucedida, o idioma pode ser alterado novamente.
Criando um arquivo DLL
Abrimos o Visual Studio e criamos um novo projeto Visual C#, escolhendo "Class Library" como seu tipo. Vamos chamá-lo de MqlSqlDemo. Nas propriedades do projeto, na seção "Build", precisamos configurar a plataforma de destino ("Platform target"). Aqui precisamos substituir "Any CPU" por "x64" (na configuração Debug e na configuração Release). Isto é devido aos recursos de exportação de funções para código não gerenciado — eles definitivamente devem indicar o tipo de processador.
Definimos a versão do .NET framework como 4.5. Geralmente já está selecionado por padrão.
Ao criar um projeto nele imediatamente, o arquivo "Class1.cs" é automaticamente adicionado, contendo a classe "Class1". Mudamos o nome do arquivo, da classe para "MqlSqlDemo.cs" e "MqlSqlDemo", respectivamente. Funções que serão exportadas do arquivo DLL só podem ser estáticas — novamente, isso é necessário a fim de exportar para código não gerenciado.
Estritamente falando, também podem ser exportadas funções não estáticas. Mas para isso precisamos usar ferramentas C++/CLI, que não consideramos neste artigo.
Como todas as funções em nossa classe devem ser estáticas, também faz sentido tornar a classe em si estática. Nesse caso, se, para alguma função, o modificador “static” for esquecido, isso será imediatamente determinado quando o projeto for compilado. Obtemos a seguinte descrição de classe:
{
// ...
}
Agora precisamos configurar as dependências do projeto (seção "References" no "Solution Explorer"). Removemos de lá tudo que for desnecessário, deixando apenas "System" e "System.Data".
Agora adicionamos o pacote "UnmanagedExports".
A descrição do pacote pode ser encontrada no site do autor: https://sites.google.com/site/robertgiesecke/Home/uploads/unmanagedexports.
É mais conveniente adicioná-lo através do gerenciador de pacotes NuGet. As instruções de como adicionar podem ser encontradas no site do NuGet: https://www.nuget.org/packages/UnmanagedExports
Precisamos apenas de uma destas instruções:
Install-Package UnmanagedExports -Version 1.2.7
No menu Visual Studio, selecionamos a seção "Tools" — "NuGet Package Manager" — "Package Manager Console". Abaixo será aberta a linha de comando. É necessário inserir nela a instrução copiada "Install-Package UnmanagedExports -Version 1.2.7" e pressionar a tecla "Enter". O gerenciador de pacotes se conectará à Internet um momento e fará o download do pacote, adicionando-o ao projeto e exibindo o seguinte:
PM> Install-Package UnmanagedExports -Version 1.2.7 Installing 'UnmanagedExports 1.2.7'. Successfully installed 'UnmanagedExports 1.2.7'. Adding 'UnmanagedExports 1.2.7' to MqlSqlDemo. Successfully added 'UnmanagedExports 1.2.7' to MqlSqlDemo. PM>
Isso significa que o pacote foi adicionado com sucesso.
Depois disso, podemos prosseguir diretamente a escrita do código no arquivo de descrição de classe MqlSqlDemo.cs.
Configuramos o espaço de nomes usado.
- Visual Studio adiciona coisas de mais. Removemos tudo da seção "using", exceto "using System;".
- Agora adicionamos "using System.Data;" — daqui serão tomadas as classes para trabalhar com bancos de dados.
- Adicionamos "using System.Data.SqlClient;" — aqui são contidas as classes para trabalhar especificamente com o banco de dados SQL Server.
- Adicionamos "using System.Runtime.InteropServices;" — aqui são contidos os atributos para interagir com código não gerenciado.
- Adicionamos "using RGiesecke.DllExport;" — daqui tomaremos o atributo para marcar as funções exportadas.
using System; using System.Data; using System.Data.SqlClient; using System.Runtime.InteropServices; using RGiesecke.DllExport;
Adicionamos as variáveis necessárias. As variáveis na classe estática também podem ser estáticas somente. Vamos precisar de objetos para trabalhar com o banco de dados, nomeadamente do objeto de conexão e do objeto de comando:
private static SqlConnection conn = null; private static SqlCommand com = null;
Também precisamos de uma linha através da qual transmitiremos mensagens detalhadas sobre os resultados das funções:
private static string sMessage = string.Empty;
Declaramos duas constantes com valores 0 e 1 — elas servirão como valores de retorno para a maioria das funções. Se bem-sucedido, as funções retornarão 0, se falharem, 1. Isso tornará o código mais compreensível.
public const int iResSuccess = 0; public const int iResError = 1;
Agora passamos a tratar das funções.
Existem limitações para funções que serão exportadas a fim de serem usadas em MQL5.
- As funções, como mencionado, devem ser estáticas.
- Não se podem usar classes genéricas de coleções (espaço de nomes System.Collections.Generic). Embora não haja problemas compilando com elas, na etapa de execução podem ocorrer erros inexplicáveis. Essas classes podem ser usadas em outras funções que não serão exportadas, mas é melhor não usá-las. Podemos usar matrizes habituais. Nosso projeto é escrito apenas para fins informativos, portanto, não terá esse tipo de classes (como, a propósito, matrizes).
Em nosso projeto de demonstração, transferiremos apenas dados simples — números ou cadeias de caracteres. Também seria teoricamente possível transferir valores do tipo boolean que em sua representação interna também são inteiros. Mas os valores desses números podem ser interpretados de maneira diferente se usados sistemas diferentes (MQL e .NET). Isso leva a erros. Portanto, nos limitamos a três tipos de dados, isto é, int, string e double. Os valores de booleano, se necessário, devem ser transferidos como int.
Em projetos reais, você pode transferir estruturas de dados complexas, mas, para organizar o trabalho com o SQL Server, não precisa fazer isso.
Para trabalhar com o banco de dados, primeiro precisamos estabelecer uma conexão. Para fazer isso, usamos a função CreateConnection. A função aceitará um parâmetro, nomeadamente uma string com parâmetros para se conectar ao banco de dados SQL Server. Ele retornará um inteiro indicando se foi possível estabelecer uma conexão. Após a conexão bem-sucedida, retornaremos iResSuccess, isto é, 0. Se mal-sucedido — iResError, ou seja, 1. Informações mais detalhadas sobre o erro serão inseridas na linha de mensagem — sMessage.
Veja o que acontece:
[DllExport("CreateConnection", CallingConvention = CallingConvention.StdCall)] public static int CreateConnection( [MarshalAs(UnmanagedType.LPWStr)] string sConnStr) { // Limpamos a linha de mensagem: sMessage = string.Empty; // Se já houver conexão, fechamos e mudamos // cadeia de conexão para uma nova, se não - // recriamos os objetos de conexão e os comandos: if (conn != null) { conn.Close(); conn.ConnectionString = sConnStr; } else { conn = new SqlConnection(sConnStr); com = new SqlCommand(); com.Connection = conn; } // Tentamos abrir uma conexão: try { conn.Open(); } catch (Exception ex) { // Por algum motivo, a conexão não foi aberta. // Colocamos as informações de erro na linha de mensagens: sMessage = ex.Message; // Liberamos recursos e redefinimos objetos: com.Dispose(); conn.Dispose(); conn = null; com = null; // Erro: return iResError; } // Tudo correu bem, a conexão está aberta: return iResSuccess; }
Antes da descrição, cada função exportada é marcada com o atributo DllExport. Ele está no espaço de nomes RGiesecke.DllExport importado a partir da montagem RGiesecke.DllExport.Metadata. Uma montagem é incluída automaticamente nas dependências do projeto quando o gerenciador de pacotes NuGet instala o pacote UnmanagedExports. Para este atributo precisam ser transferidos dois parâmetros:
- o nome da função sob o qual será exportada. Ela será chamada com este nome por programas externos a partir de DLL, incluindo o MetaTrader 5. Podemos tornar o nome da função exportada igual ao nome da função no código — CreateConnection;
- o segundo parâmetro indica qual mecanismo de chamada de função será usado. Para todas as nossas funções, será adequado CallingConvention.StdCall.
Reparemos no atributo [MarshalAs(UnmanagedType.LPWStr)]. Ele fica na frente do parâmetro da linha de conexão ConnStringIn que a função aceita. Este atributo indica como a linha deve ser transmitida. No momento da escrita deste texto, o MetaTrader 5 e o MetaTrader 4 estavam trabalhando com linhas unicode — UnmanagedType.LPWStr.
Ao chamar a função, na linha de mensagem, pode permanecer o texto que descreve o erro na tentativa de conexão anterior, portanto, no início da função, a linha é limpa. Além disso, a função pode ser chamada quando a conexão anterior ainda não está fechada. Por isso, primeiro, verificamos se há objetos e comandos de conexão. Se houverem, podemos fechar a conexão e usar os objetos novamente. Se não, será preciso criar novos objetos.
O método de conexão usado Open não retorna nenhum resultado. Por conseguinte, a única maneira descobrir se a conexão foi bem-sucedida é pegando exceções. Em caso de erro, liberamos os recursos, redefinimos os objetos, inserimos as informações na linha de mensagens e retornamos iResError. Se tudo correr bem, retornamos iResSuccess.
Se a conexão não puder ser aberta, o robô deverá ler a mensagem escrita na linha sMessage, a fim de descobrir o motivo da falha. Para fazer isso, adicionamos a função GetLastMessage. Ela retornará uma linha com a mensagem:
[DllExport("GetLastMessage", CallingConvention = CallingConvention.StdCall)] [return: MarshalAs(UnmanagedType.LPWStr)] public static string GetLastMessage() { return sMessage; }
Como a função de configuração de conexão, essa função também é rotulada com o atributo de exportação DllExport. A atributo [return: MarshalAs(UnmanagedType.LPWStr)] mostra como o resultado de retorno deve ser transferido. Como o resultado é uma linha, também é necessário transferí-la para o MetaTrader 5 em Unicode. Por essa razão, aqui também usamos UnmanagedType.LPWStr.
Após abrir a conexão, podemos começar a trabalhar com o banco de dados. Adicionamos a capacidade de realizar solicitações ao banco de dados. A função ExecuteSql vai se encarregar disso:
[DllExport("ExecuteSql", CallingConvention = CallingConvention.StdCall)] public static int ExecuteSql( [MarshalAs(UnmanagedType.LPWStr)] string sSql) { // Limpamos a linha de mensagem: sMessage = string.Empty; // Primeiro precisamos verificar se a conexão está estabelecida. if (conn == null) { // A conexão ainda não está aberta. // Notificamos sobre o erro e retornamos o sinalizador do erro: sMessage = "Connection is null, call CreateConnection first."; return iResError; } // A conexão está pronta, tentamos executar o comando. try { com.CommandText = sSql; com.ExecuteNonQuery(); } catch (Exception ex) { // Erro ao executar o comando. // Colocamos as informações de erro na linha de mensagens: sMessage = ex.Message; // Retornamos o sinalizador de erro: return iResError; } // Tudo correu bem, portanto, devolvemos o sinalizador de execução bem-sucedida: return iResSuccess; }
O texto da solicitação é transferido para a função como um parâmetro. Antes de executar a solicitação, verificamos se a conexão está aberta. Da mesma maneira que na função de abertura de conexão, no caso de execução bem-sucedida, a função retorna iResSuccess, no caso de erro, iResError. Para obter informações mais detalhadas sobre as causas do erro, precisamos usar a função GetLastMessage. Usando a função ExecuteSql, podemos executar quaisquer solicitações — gravar, excluir, alterar dados. Podemos até trabalhar com a estrutura do banco de dados. Mas, infelizmente, ele não permite ler os dados, porque a função não retorna um resultado e nunca armazena a os dados lidos. A solicitação será executada, mas não será possível ver o que foi lido. Portanto, adicionamos mais duas funções para ler os dados.
A primeira função é projetada para ler um único inteiro numa tabela de banco de dados.
[DllExport("ReadInt", CallingConvention = CallingConvention.StdCall)] public static int ReadInt( [MarshalAs(UnmanagedType.LPWStr)] string sSql) { // Limpamos a linha de mensagem: sMessage = string.Empty; // Primeiro precisamos verificar se a conexão está estabelecida. if (conn == null) { // A conexão ainda não está aberta. // Notificamos sobre o erro e retornamos o sinalizador do erro: sMessage = "Connection is null, call CreateConnection first."; return iResError; } // Variável para obter o resultado de retorno: int iResult = 0; // A conexão está pronta, tentamos executar o comando. try { com.CommandText = sSql; iResult = (int)com.ExecuteScalar(); } catch (Exception ex) { // Erro ao executar o comando. // Colocamos as informações de erro na linha de mensagens: sMessage = ex.Message; } // Retornamos o resultado: return iResult; }
A implementação da leitura de dados é muito mais difícil do que apenas executar comandos. Esta função é muito simplificada e usa a função ExecuteScalar da classe SqlCommand. Ela retorna o valor da primeira coluna da primeira linha retornada pela solicitação. Consequentemente, a solicitação SQL transferida pelo parâmetro deve ser formada de forma que o conjunto de dados retornado contenha linhas e um inteiro na primeira coluna. Além disso, a função deve, de alguma forma, retornar o número lido. Por isso, seu resultado não será mais uma mensagem sobre a execução bem-sucedida. Para entender se era possível executar a consulta e ler os dados, será necessário, em qualquer caso, analisar a última mensagem chamando GetLastMessage. Se a última mensagem estiver vazia, não houve erro e os dados foram lidos. Se algo estiver escrito, ocorreu um erro e os dados não puderam ser lidos.
A segunda função também lê um valor no banco de dados, mas de um tipo diferente - não um inteiro, mas, sim, uma string. As cadeias de caracteres podem ser lidas da mesma maneira que números, a diferença é apenas no tipo do resultado retornado. Como a função retorna uma string, é necessário marcá-la com um atributo [return: MarshalAs(UnmanagedType.LPWStr)]. Aqui está o código desta função:
[DllExport("ReadString", CallingConvention = CallingConvention.StdCall)] [return: MarshalAs(UnmanagedType.LPWStr)] public static string ReadString( [MarshalAs(UnmanagedType.LPWStr)] string sSql) { // Limpamos a linha de mensagem: sMessage = string.Empty; // Primeiro precisamos verificar se a conexão está estabelecida. if (conn == null) { // A conexão ainda não está aberta. // Notificamos sobre o erro e retornamos o sinalizador do erro: sMessage = "Connection is null, call CreateConnection first."; return string.Empty; } // Variável para obter o resultado de retorno: string sResult = string.Empty; // A conexão está pronta, tentamos executar o comando. try { com.CommandText = sSql; sResult = com.ExecuteScalar().ToString(); } catch (Exception ex) { // Erro ao executar o comando. // Colocamos as informações de erro na linha de mensagens: sMessage = ex.Message; } // Retornamos o resultado: return sResult; }
Para um projeto de demonstração, essa leitura de dados será suficiente. Para um EA real, ela pode não ser necessária, uma vez que, para os EAs, é mais importante armazenar dados no banco de dados para análise posterior. Se ainda for preciso ler os dados, essas funções podem ser usadas, uma vez que elas são bastante eficientes. No entanto, às vezes, é preciso ler muitas linhas numa tabela contendo várias colunas.
Para fazer isso, podemos proceder de duas maneiras. Podemos retornar estruturas de dados complexas a partir de uma função (esse caminho não é adequado para MQL4). Também podemos declarar uma variável estática de classe DataSet em nossa classe. Ao ler, precisaremos carregar dados do banco de dados nesse DataSet e, depois, com outras funções, ler dados, uma célula por vez, usando única chamada de função. Essa abordagem é implementada no projeto mencionado abaixo HerdOfRobots. Ele pode ser estudado em detalhes no código do projeto e, para não aumentar o tamanho do artigo, não discutiremos a leitura de dados de várias linhas.
Após concluir o trabalho com o banco de dados, a conexão precisará ser fechada, liberando os recursos usados. A função CloseConnection é destinada para isso:
[DllExport("CloseConnection", CallingConvention = CallingConvention.StdCall)] public static void CloseConnection() { // Primeiro precisamos verificar se a conexão está estabelecida. if (conn == null) // A conexão ainda não está aberta - significa que também não precisamos fechá-la: return; // Conexão está aberta - devemos fechá-la: com.Dispose(); com = null; conn.Close(); conn.Dispose(); conn = null; }
Esta função simples não recebe parâmetros e não retorna um resultado.
Todas as funções necessárias estão prontas. Compilamos o projeto.
Como as funções precisarão ser usadas não de outros aplicativos .NET, mas, sim, do MetaTrader (que não usa .NET), a compilação ocorrerá em duas etapas. Na primeira etapa, tudo é feito da mesma maneira que para todos os projetos .NET. Uma é criada compilação normal e, em seguida, é processada pelo pacote UnmanagedExports. Compilada a montagem, começa o trabalho do pacote. Primeiro, é executado o descompilador IL. Ele analisa o conjunto resultante em código IL. Em seguida, o código IL é alterado, pois são removidas referências para os atributos DllExport e são adicionadas instruções para exportar as funções marcadas com esse atributo. Depois disso, o arquivo com o código IL é recompilado e escrito em vez da DLL original.
Todas essas ações são realizadas automaticamente. Mas, como mencionado acima, se o idioma russo for selecionado nas configurações do sistema operacional para programas que não suportam Unicode, ao tentar compilar um arquivo com um código IL modificado, o pacote UnmanagedExports provavelmente gerará um erro e não poderá fazer nada.
Se nenhuma mensagem de erro aparecer durante a compilação, tudo correu bem e a DLL resultante pode ser usada em EAs. Além disso, se a DLL for processada com sucesso, o UnmanagedExports adicionará mais dois arquivos com as extensões ".exp" e ".lib" (no nosso caso, eles serão "MqlSqlDemo.exp" e "MqlSqlDemo.lib"). Não precisamos deles, mas, como eles existem, o trabalho de UnmanagedExports foi completado com sucesso.
Deve-se notar que o projeto de demonstração tem uma limitação muito significativa, isto é, ele permite execute apenas um EA - que trabalha a partir do banco de dados - num terminal MetaTrader. Acontece que todos os EAs usam uma instância da DLL carregada. Como nossa classe é feita estática, ela será a mesma para todos os EAs iniciados. As variáveis também serão comuns. Se forem executados vários EAs, todos usarão a mesma conexão e um objeto de comando para todos. Se vários EAs tentarem usar esses objetos ao mesmo tempo simultaneamente, poderão surgir problemas.
Mas esse projeto será o suficiente para explicar os princípios de trabalho e testar a conexão com o banco de dados. Agora temos um arquivo DLL com funções. Podemos começar a escrever o EA em MQL5.
Criando um Expert Advisor em MQL5
Fazemos um EA simples em MQL5. Seu código também pode ser compilado no editor MQL4, se alterarmos sua extensão de "mq5" para "mq4". O EA é necessário apenas para demonstrar um trabalho bem-sucedido com o banco de dados, portanto, não será executada nenhuma operação de negociação.
Iniciamos o MetaEditor, clicamos no botão "Criar". Deixamos o item "EA ('Modelo')" e clicamos em "Avançar". Especificamos o nome "MqlSqlDemo". Além disso, adicionamos um parâmetro — "ConnectionString" do tipo "string". Esta será uma cadeia de conexão indicando como se conectar ao seu servidor de banco de dados. Por exemplo, o valor inicial para ela pode ser especificado como:
Server=localhost;Database=master;Integrated Security=True
Esta cadeia de conexão permite se conectar a um servidor de banco de dados sem nome ("Default Instance") instalado no mesmo computador no qual o MetaTrader está sendo executado. O nome de usuário e a senha não são necessários ao mesmo tempo, pois é usada a autorização da conta do Windows.
Se você baixou o SQL Server Express e o instalou no computador, durante a instalação sem alterar os parâmetros, o SQL Server será uma "instância nomeada". Ele receberá o nome "SQLEXPRESS". Ele terá uma cadeia de conexão diferente:
Server=localhost\\SQLEXPRESS;Database=master;Integrated Security=True
Ao adicionar um parâmetro de string ao modelo do EA, há um limite no tamanho da string. Uma cadeia de conexão mais longa (por exemplo, para o servidor nomeado "SQLEXPRESS") pode não caber. Mas não há problema com isso, uma vez que o valor do parâmetro nessa etapa pode ser deixado vazio. Depois, ao editar o código do EA, podemos dar a ele qualquer valor . Também podemos definir a string de conexão necessária ao iniciar o EA.
Clicamos em "Avançar". Nenhuma função precisa ser adicionada, logo, na próxima tela deixamos todas as marcas de seleção desmarcadas. Novamente, clicamos em "Avançar" e obtemos o código inicial gerado do EA.
O EA é necessário apenas para demonstrar conexão com o banco de dados e trabalhar com ele. Para fazer isso, basta apenas a função de inicialização — OnInit. Os excertos de código das funções restantes — OnDeinit e OnTick — podem ser removidas imediatamente.
Como resultado, temos o seguinte:
//+------------------------------------------------------------------+ //| MqlSqlDemo.mq5 | //| Copyright 2018, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #property strict //--- input parameters input string ConnectionString = "Server=localhost\\SQLEXPRESS;Database=master;Integrated Security=True"; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); }
Atenção: ao se conectar a uma instância nomeada (no nosso caso, "SQLEXPRESS"), é necessário repetir o caractere "\" duas vezes: "localhost\\SQLEXPRESS". Isso é necessário ao adicionar um parâmetro no modelo do EA e no código. Se o caractere for especificado apenas uma vez, o compilador decidirá que a string contém a sequência 'Escape' (caractere especial) "\S" e, concluída a compilação, informará que ela não é reconhecida.
Mas se o robô já compilado for transferido para o gráfico, em seus parâmetros haverá apenas um caractere "\", apesar do fato de haver dois deles especificados no código. Na verdade, ao compilar, todas as sequências Escape nas strings são convertidas nos caracteres correspondentes. A sequência "\\" é convertida num único caractere "\"; e os usuários (que não precisam trabalhar com o código) podem ver a string usual. Portanto, se você definir a cadeia de conexão não no código, mas, sim, quando iniciar o EA, apenas um caractere "\" será especificado na cadeia de conexão:
Server=localhost\SQLEXPRESS;Database=master;Integrated Security=True
Agora, adicionamos funcionalidade ao excerto de código do EA. Primeiro, precisamos importar as funções a partir da DLL criada para trabalhar com o banco de dados. Adicionamos a seção de importação antes da função OnInit. As funções importadas são descritas quase da mesma maneira que são declaradas no código em C#, só precisamos remover todos os modificadores e atributos:
// Descrição de funções importadas. #import "MqlSqlDemo.dll" // Função de abertura de conexão: int CreateConnection(string sConnStr); // Função de leitura da última mensagem: string GetLastMessage(); // Função de execução de comandos SQL: int ExecuteSql(string sSql); // Função de leitura de inteiro: int ReadInt(string sSql); // Função de leitura de string: string ReadString(string sSql); // Função de encerramento de conexão: void CloseConnection(); // Concluindo a importação: #import
Para melhor clareza do código, vamos declarar constantes dos resultados da execução de funções. Como na DLL, isso será 0 para execução bem-sucedida e 1 para erro:
// Execução bem-sucedida da função: #define iResSuccess 0 // Erro ao executar a função: #define iResError 1
Agora podemos adicionar chamadas de funções que trabalharão com o banco de dados à função de inicialização OnInit. Aqui está como ficará:
int OnInit() { // Tentamos abrir a conexão: if (CreateConnection(ConnectionString) != iResSuccess) { // Conexão falhou. // Escrevemos a mensagem e concluímos o trabalho: Print("Erro ao abrir a conexão. ", GetLastMessage()); return(INIT_FAILED); } Print("Conexão com o banco de dados estabelecida."); // Conexão estabelecida com sucesso. // Tentamos executar as solicitações. // Criamos uma tabela e registramos os dados nela: if (ExecuteSql( "create table DemoTest(DemoInt int, DemoString nvarchar(10));") == iResSuccess) Print("Tabela criada no banco de dados."); else Print("Não foi possível criar tabela. ", GetLastMessage()); if (ExecuteSql( "insert into DemoTest(DemoInt, DemoString) values(1, N'Test');") == iResSuccess) Print("Dados registrados na tabela."); else Print("Não foi possível registrar dados na tabela. ", GetLastMessage()); // Procedemos à leitura de dados. Lemos um inteiro no banco de dados: int iTestInt = ReadInt("select top 1 DemoInt from DemoTest;"); string sMessage = GetLastMessage(); if (StringLen(sMessage) == 0) Print("Número lido no banco de dados: ", iTestInt); else // Não foi possível ler o número. Print("Não foi possível ler o número no banco de dados. ", GetLastMessage()); // Agora lemos a string: string sTestString = ReadString("select top 1 DemoString from DemoTest;"); sMessage = GetLastMessage(); if (StringLen(sMessage) == 0) Print("String lida no banco de dados: ", sTestString); else // Não foi possível ler a string. Print("Não foi possível ler a string no banco de dados. ", GetLastMessage()); // Como a tabela não é mais necessária, pode ser excluída. if (ExecuteSql("drop table DemoTest;") != iResSuccess) Print("Não foi possível excluir a tabela. ", GetLastMessage()); // Trabalho concluído, encerramos a conexão: CloseConnection(); // Concluímos a inicialização: return(INIT_SUCCEEDED); }
Compilamos o EA. Isso é tudo, o EA de testes está pronto. Agora, podemos iniciá-lo. Antes de iniciar, você precisa adicionar uma DLL à pasta de bibliotecas do perfil do MetaTrader que você usa. Iniciamos o MetaTrader, no menu "Arquivo", selecionamos "Abrir diretório de dados". Abrimos a pasta "MQL5" (no caso do MetaTrader 4, será a pasta "MQL4") — pasta "Libraries". Colocamos lá o arquivo da nossa DLL — MqlSqlDemo.dll. O EA neste momento já deve estar compilado e disponível para uso. Naturalmente, a inicialização de EAs e a importação de funções de uma DLL devem ser permitidas nas configurações do MetaTrader 5; caso contrário, a inicialização do EA é concluída imediatamente com um erro.
Iniciamos o EA alterando os dados da cadeia de conexão nos parâmetros para os parâmetros de acesso ao nosso servidor de banco de dados. Se tudo for feito corretamente, o EA registrará as seguintes entradas:
2018.07.10 20:36:22.187 MqlSqlDemo (EURUSD,H1) Tabela criada no banco de dados.
2018.07.10 20:36:22.427 MqlSqlDemo (EURUSD,H1) Dados registrados na tabela.
2018.07.10 20:36:22.569 MqlSqlDemo (EURUSD,H1) Número lido no banco de dados: 1
2018.07.10 20:36:22.586 MqlSqlDemo (EURUSD,H1) String lida no banco de dados: Test
Conexão ao banco de dados, execução de comandos SQL, registro e leitura de dados — tudo bem-sucedido.
Fim do artigo
A solução completa para o Visual Studio está no arquivo MqlSqlDemo.zip anexado ao artigo. O pacote "UnmanagedExports" já vem instalado. O EA de teste MqlSqlDemo.mq5 e sua variante para MQL4 também estão na pasta "MQL" anexada.
A abordagem descrita neste artigo é funcional. Com base nos princípios descritos acima, são criados aplicativos que permitem trabalhar com milhares e até dezenas de milhares de EAs simultaneamente. Tudo tem sido testado muitas vezes e funciona até agora.
O arquivo DLL e o EA criados no contexto deste artigo são apenas destinados ao aprendizado e à leitura. Podemos usar a DLL em projetos reais. No entanto, devido às suas limitações, talvez ela não nos seja útil mais tarde. Se quisermos adicionar a capacidade para trabalhar com o banco de dados ao nosso EA, o mais provável é que precisemos de mais recursos. Nesse caso, necessitaremos modificar o código por conta própria, guiando-nos por artigos de exemplo. Se você tiver dúvidas, deixe seu comentário, o envie uma mensagem para meu Skype cansee378 ou através da página de contato do meu site: http://life-warrior.org/contact.
Se não termos tempo ou vontade de lidar com código em C #, podemos baixar um projeto pronto. O programa gratuito de código aberto HerdOfRobots é implementado com os mesmos princípios descritos no artigo. O pacote de instalação, juntamente com o programa, inclui arquivos prontos com funções importadas para MetaTrader 5 e MetaTrader 4. Essas bibliotecas têm muito mais recursos. Por exemplo, elas permitem executar até 63 EAs num terminal, conectá-los a diferentes bancos de dados, ler dados de tabelas uma linha por vez e registrar valores de data/hora no banco de dados.
O próprio programa HerdOfRobots fornece recursos convenientes para monitorar EAs - que se conectam ao banco de dados - e para analisar os dados que eles registram. O pacote inclui um guia detalhando sobre todos os aspectos do trabalho. Também anexei ao artigo o arquivo de instalação do programa — SetupHerdOfRobots.zip. Se você quiser ver o código do programa usado para se conectar ao banco de dados do projeto MqlToSql64 (MqlToSql para MT4), então você pode usar os recursos avançados mencionados em meus projetos —o código pode ser baixado em repositórios abertos:
https://bitbucket.org/CanSeeThePain/herdofrobots
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2895
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso