preview
Simulação de mercado: Iniciando o SQL no MQL5 (II)

Simulação de mercado: Iniciando o SQL no MQL5 (II)

MetaTrader 5Testador |
86 0
Daniel Jose
Daniel Jose

Introdução

Olá pessoal, e sejam bem-vindos a mais um artigo da série sobre como construir um sistema de replay/simulação.

No artigo anterior Simulação de mercado: Iniciando o SQL no MQL5 (I), começamos a fazer uso do SQL junto ao código de MQL5. Apesar de muitos imaginarem que podemos usar tranquilamente códigos em SQL dentro de outros códigos. Isto normalmente não se aplica. Devido ao fato, de que um código SQL, será sempre colocado dentro de um executável, como sendo uma string. E este fato de colocar o código SQL como sendo uma string, apesar de não ser problemático, para pequenos trechos de código. Podem sim ser algo que nos causará muitos transtornos e uma baita de uma dor de cabeça.

Isto por que, se durante a transcrição do código SQL para dentro de uma string, você eventualmente digitar algo incorreto. Não irá perceber que fez isto. O motivo é que não tem uma forma simples e eficaz de analisar visualmente se a string, que no caso seria o código em SQL, está de fato correta. Já que a sintaxe do código, quando colocada em uma string, não fica destacada em cores diferentes.

Este tipo de coisa, pode nos provocar muitos incômodos. Eu mesmo já passei por maus bocados, tentando entender por que um código SQL, não estava funcionando corretamente dentro de um executável. E quando fui analisar o código com muita calma, acabei me deparando com um pequeno erro de digitação. Este pequeno erro, não permitia que o SQL, conseguisse fazer as ligações entre os bancos de dados que era esperado. Foi um bom tempo perdido, simplesmente tentando entender o motivo do erro. E quando o mesmo foi encontrado, a sensação foi de que todo aquele tempo poderia ter sido melhor utilizado em outra coisa. Desde então, quando vou fazer códigos que são mais complexos de serem feitos. Faço as coisas de uma maneira totalmente diferente.

E esta é a proposta deste artigo. Mostrar a você, caro leitor, uma alternativa para poder usar códigos SQL, junto com um executável criado em MQL5. Acreditem, se você for realmente fazer uso do SQL no MetaTrader 5. Aconselho a você de fato pensar nesta alternativa que irei mostrar. Você pode até adaptar ela a outras necessidades suas. Porém, se você for fazer algo realmente elaborado, usando SQL. Sugiro que não tente colocar o código diretamente em strings no executável. As chances de algo dar errado, e depois você perder um bom tempo tentando entender o porquê de estar dado errado é enorme. Mas se o código é curto e simples. Não vejo motivos para preocupações.


Começando a usar scripts SQL

Bem, para que possamos começar a usar scripts em SQL, dentro do executável feito em MQL5. A primeira coisa a ser feita é entender a sintaxe do SQL. Se você estudar como os comandos em SQL são montados, irá notar que todos terminam em um ponto virgula ( ; ). Bem isto já nos dá uma boa ideia de qual deverá ser o limitador que deveremos procurar. Isto a fim de conseguir capturar todo o comando de uma linha do SQL.

Aqui mora uma questão curiosa. Apesar de sempre digitarmos os comandos em SQL, como sendo múltiplas linhas. Ele na verdade, é composto apenas de uma longa string de uma única linha. Onde iniciamos o comando no começo de uma linha e o terminamos justamente no ponto virgula ( ; ). Mas se você fizer isto, na prática poderá ficar muito confuso ao tentar ler o comando. Já que ele em alguns casos pode ser bastante extenso. Apesar de tudo, para o SQL, isto pouco importa, ele irá sempre ver o comando como sendo composto de apenas uma única linha. Isto já nos dá uma boa ideia do que precisamos fazer.

A próxima coisa a ser pensada, é com relação a questão dos comentários. Bem, não é incomum haver diversos comentários em um script de SQL. Tais comentários ajudam bastante, quando o script é longo ou contém muitos pontos, nos quais temos que ter alguma atenção. Bem, como não queremos gastar tempo, enviando coisas que o SQL não usará, por serem comentários. Vamos ter que nos livrar deles em algum momento, antes de enviar o comando para o SQL executar.

Mesmo por que, o comentário de fim de linha, onde usamos um duplo traço ( -- ), é algo simples de lidar. Porém, o comentário de múltiplas linhas é algo no qual pode nos dar algum trabalho para resolver. Isto por que, dentro deste comentário, pode haver palavras que o SQL, poderá interpretar como sendo comandos a serem executados. E não queremos que isto aconteça. Então iremos precisar lidar com este tipo de coisa, obrigatoriamente.

Além do mais existe um outro problema aqui. Você deve saber que no SQL temos strings. E caso tenhamos os indicadores de comentário dentro de uma string, eles não deverão ser vistos como iniciadores ou finalizadores de comentários. Neste caso deveremos fazer um tratamento diferente para este tipo de situação. Então temos aqui, algo que pode ser um pouco confuso, para quem esteja começando em programação. Por este motivo, vamos fazer as coisas de uma maneira um pouco mais fácil de você, que esteja começando, consiga de fato entender. Vamos deixar a questão dos comentários de lado por hora. Então neste ponto, os scripts em SQL não deverão contar com comentários. Isto para não complicar o que iremos fazer em MQL5.

Para começar, vamos fazer o seguinte: No artigo anterior, criamos uma classe para poder lidar com o SQL. Tal classe ainda está no começo de seu desenvolvimento. Porém, aproveitaremos disto, para podermos criar uma outra classe. Esta será responsável por lidar com o script. Mas como iremos fazer isto? Parece ser algo muito complicado. Na verdade, se você tem acompanhado meus artigos. Já deve ter notado, que sempre tento fazer as coisas da maneira o mais simples, quanto for possível ser feito. E aqui não será diferente. Então a primeira coisa, que faremos. Será separar a classe do SQL do arquivo principal. Isto é facilmente conseguido conforme mostrado abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. class C_DB_SQLite
05. {
06.     private   :
07.         int    m_handleDB;
08.     public    :
09. //+------------------------------------------------------------------+
10.         C_DB_SQLite(const string szFileName)
11.             :m_handleDB(INVALID_HANDLE)
12.         {
13.             if ((m_handleDB = DatabaseOpen(szFileName, DATABASE_OPEN_CREATE | DATABASE_OPEN_READWRITE)) == INVALID_HANDLE)
14.             {
15.                 Print("Unable to create or open the file ", szFileName);
16.                 return;
17.             }
18.         }
19. //+------------------------------------------------------------------+
20.         ~C_DB_SQLite()
21.         {
22.             DatabaseClose(m_handleDB);
23.             Print("Closing Database...");
24.         }
25. //+------------------------------------------------------------------+
26.         bool Command(const string szRequestSQL)
27.         {
28.             bool ret = (m_handleDB == INVALID_HANDLE ? false : DatabaseExecute(m_handleDB, szRequestSQL));
29.             Print("Request execution: ", (ret ? "Success" : "Failed"), "...");
30.             return ret;
31.         }
32. //+------------------------------------------------------------------+
33. };
34. //+------------------------------------------------------------------+

Código fonte de C_DB_SQLite.mqh

Este código que você pode ver logo acima, é justamente o que existia dentro do arquivo principal. Ou seja, removemos do arquivo de script em MQL5, o código responsável pela classe. E criamos um arquivo de cabeçalho chamado C_DB_SQLite.mqh, e o salvamos na pasta: Include\Market Replay\SQL. Isto irá garantir que qualquer código que viermos a criar, e que necessite usar os mesmos princípios de trabalho no SQL. Poderá fazer isto, usando este arquivo de cabeçalho. Bem, assim sendo, o antigo script em MQL5, foi modificado e ficou como mostrado abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Basic script for SQL database written in MQL5"
04. #property version   "1.00"
05. #property script_show_inputs
06. //+------------------------------------------------------------------+
07. #include <Market Replay\SQL\C_DB_SQLite.mqh>
08. //+------------------------------------------------------------------+
09. input string user01 = "DataBase01";        //FileName
10. //+------------------------------------------------------------------+
11. void OnStart()
12. {
13.     C_DB_SQLite *DB;
14.     
15.     DB = new C_DB_SQLite(user01 + ".sqlite");
16.     
17.     (*DB).Command("PRAGMA FOREIGN_KEYS = ON;");    
18.     (*DB).Command("CREATE TABLE IF NOT EXISTS tb_Symbols"
19.                   "("
20.                     "id PRIMARY KEY,"
21.                     "symbol NOT NULL UNIQUE"
22.                   ");");
23.     (*DB).Command("CREATE TABLE IF NOT EXISTS tb_Quotes"
24.                       "("
25.                    "of_day NOT NULL,"
26.                    "price NOT NULL,"
27.                    "fk_id NOT NULL,"
28.                    "FOREIGN KEY (fk_id) REFERENCES tb_Symbols(id)"
29.                  ");");    
30.     delete DB;
31. }
32. //+------------------------------------------------------------------+

Código fonte do Script em MQL5

Repare que agora temos uma include na linha sete. Esta faz com que o código do arquivo de cabeçalho, possa ser acessado pelo arquivo de script em MQL5, como se aquele mesmo código no arquivo de cabeçalho estivesse aqui. Neste arquivo principal. E é desta maneira, que vamos fazendo as coisas usando diversos arquivos de cabeçalho. Observe nesta mesma linha sete, onde estou dizendo que o arquivo de cabeçalho deverá estar. Note que é justamente o mesmo local que informei a pouco. Claro que não precisei dizer o nome do diretório Include. Isto por que, por padrão o MQL5, irá procurar os arquivos de cabeçalho no diretório chamado Include.

Porém o arquivo de cabeçalho, não precisa estar obrigatoriamente ali. Mas neste caso, não irei entrar em detalhes para não lhe confundir. Então dê preferência de deixar as coisas nos locais corretos. Um pequeno detalhe: O que acabei de explicar, pode parecer bobagem e algo trivial para muitos. Porém, houveram pessoas que não estavam conseguindo entender, como fazer para que o código viesse a ser compilado. Tentei explicar a elas. Mas não sei se elas de fato entenderam.

Mas como este código aqui é bastante simples. Usei ele para mostrar como seria o funcionamento, dos tais arquivos de cabeçalho. Me desculpem se pareci obvio e trivial. Mas quero que todos entendam o que está acontecendo. Já que decidi não disponibilizar os arquivos no anexo dos artigos. E quero que aqueles que são entusiasta, saibam como conseguir obter o código completo e posicionar adequadamente os arquivos, a fim de obter o executável final.

Muito bem, agora já temos a base da estrutura que precisamos. Isto para que possamos pensar em alguma forma de trabalhar com os scripts em SQL. Estando eles salvos em um arquivo a parte. Um fato importante a ser mencionado. Apesar de que a ideia, aqui venha a ser, de inicialmente colocar o script em SQL em um arquivo a parte. Você pode usando diretivas de compilação, a fim de embutir o arquivo, contendo o script em SQL, dentro do executável final. Criado aqui no MQL5. Tal coisa ao meu ver, é bem mais adequada, do que usar strings, contendo código SQL, diretamente no código MQL5. Mas antes de vermos como fazer, caso você não saiba como fazer. Vamos primeiro ver, como iremos lidar com o script em SQL. A fim de que o código em MQL5 faça uso dele. Para começar, vamos criar uma nova classe, conforme mostrado abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "C_DB_SQLite.mqh"
05. //+------------------------------------------------------------------+
06. class C_ScriptSQL : private C_DB_SQLite
07. {
08.     private   :
09.     public    :
10. //+------------------------------------------------------------------+
11.         C_ScriptSQL(const string szFileScript, const string szFileDataBase)
12.             :C_DB_SQLite(szFileDataBase)
13.             {
14.             }
15. //+------------------------------------------------------------------+
16.         ~C_ScriptSQL()
17.             {
18.             }
19. //+------------------------------------------------------------------+
20.         bool Execute(void)
21.             {
22.                 return false;
23.             }
24. //+------------------------------------------------------------------+
25. };
26. //+------------------------------------------------------------------+

Código Inicial do arquivo C_ScriptSQL.mqh

Ok. Vamos ver o que temos aqui inicialmente. Observe na linha quatro que temos uma include sendo declarada. Esta estará nos dizendo que estaremos incluindo o arquivo C_DB_SQLite.mqh, neste arquivo de cabeçalho, que chamaremos de C_ScriptSQL. Agora preste atenção ao seguinte fato. É algo sútil, porém importante. Note que o nome C_DB_SQLite.mqh, se encontra entre aspas duplas. Isto indica, que este arquivo C_ScriptSQL, que estamos criando, deverá estar no mesmo diretório que o arquivo C_DB_SQLite.mqh.

Sempre que você encontrar em um código MQL5, ou C/C++. A mesma coisa vista nesta linha quatro. Você deverá pensar, em colocar ou procurar o arquivo atual em uma posição relativa ao arquivo indicado na include. Ou seja, podemos de alguma forma navegar entre pastas diferentes, apenas dizendo isto na include. Então este arquivo C_ScriptSQL.mqh, deverá ser salvo na pasta Include\Makart Replay\SQL. Que é a mesma pasta do arquivo C_DB_SQLite.mqh. Se isto for feito desta maneira, o código será compilado perfeitamente.

Agora vamos ver, algumas coisas que podem parecer um pouco complicadas. Note que na linha seis, estamos declarando a classe C_ScriptSQL e esta estará herdando de maneira privativa a classe C_DB_SQLite. O motivo de estamos fazendo isto, será melhor compreendido depois. Por conta desta herança, precisamos criar um método a fim de inicializar o constructor da classe C_DB_SQLite. Por isto, temos a linha 11, onde declaramos o constructor da classe C_ScriptSQL. Observe que estaremos recebendo dois parâmetros aqui. Então a fim de inicializar o contructor de C_DB_SQLite, usamos a linha 12 e passamos um dos parâmetros para o constructor da classe C_DB_SQLite, para que a classe seja inicializada. O outro parâmetro usaremos depois.

Como quero já deixar a classe pre montada, estou na linha 16 declarando o destructor da mesma. E na linha 20 um procedimento que usaremos no código principal. Mas como este procedimento deverá retornar algum valor, já declaro na linha 22 que o valor retornado é falso. Ou seja, caso eu venha a experimentar o código, antes mesmo de ele estar concluído. Garanto que qualquer procedimento, que precise retornar algum valor, retorne sempre um valor falso.

Bem, mas o que esta classe C_ScriptSQL irá de fato fazer? A ideia aqui, é que esta classe receba o nome do arquivo de script SQL, e no nome do banco de dados. E execute o script em cima do banco de dados. Talvez nos possibilitando retornar algum valor, caso o script esteja fazendo alguma pesquisa. Mas por hora, a ideia é remover o código que você pode observar, ao ver o código principal em MQL5. Tal código SQL pode ser observado, ao se olhar o script em MQL5.

Como existem muitas coisas a serem feitas. Devemos dividir os problemas em tarefas. E implementar a solução aos poucos. Tarefa por tarefa, até obter o que realmente precisamos. Assim sendo, a primeira tarefa a ser feita é remover o script SQL do código principal feito em MQL5. E salvar o script SQL em um arquivo no disco. Por conta que o MQL5, por segurança, não nos permite acessar arquivos fora do diretório MQL5, precisamos salvar o arquivo em um ponto acessível e simples de acessar. Eu por simplicidade, irei deixar ele no diretório MQL5\Files. Ou seja, no mesmo diretório que estaremos usando para guardar o nosso banco de dados, por padrão. Então salvaremos o arquivo com um nome. Mas para diferenciar ele, vamos usar a extensão .SQL. E assim nasce o arquivo visto abaixo.

01. PRAGMA FOREIGN_KEYS = ON;
02. 
03. CREATE TABLE IF NOT EXISTS tb_Symbols
04. (
05.     id PRIMARY KEY,
06.     symbol NOT NULL UNIQUE
07. );
08. 
09. CREATE TABLE IF NOT EXISTS tb_Quotes
10. (
11.     of_day NOT NULL,
12.     price NOT NULL,
13.     fk_id NOT NULL,
14.     FOREIGN KEY (fk_id) REFERENCES tb_Symbols(id)
15. );

Código fonte do script em SQL

Na imagem abaixo, você pode ver ele sendo destacado, junto a sua localização.

Beleza. Já temos o que precisamos para iniciar. Observe o nome que eu coloquei no arquivo de script SQL. Pois iremos usar isto em breve. Agora observe o que eu havia dito, onde cada comando termina em ( ; ) no script SQL. Então vamos implementar o sistema a fim de conseguir executar exatamente o script SQL mostrado acima. Não iremos neste momento nos preocupar com os comentários. Já que como você pode ver, não existe nenhum no script SQL. Assim o código a ser colocado no arquivo C_ScriptSQL, pode ser visto abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. //+------------------------------------------------------------------+
04. #include "C_DB_SQLite.mqh"
05. #include "..\Service Graphics\Support\C_Array.mqh"
06. //+------------------------------------------------------------------+
07. class C_ScriptSQL : private C_DB_SQLite
08. {
09.     private    :
10.         C_Array    m_Arr;
11.         char       m_Buff[];
12.         int        m_Size;
13. //+------------------------------------------------------------------+
14.         void Convert(void)
15.         {
16.             string sz0 = "";
17.             bool b0 = false, b1 = false;
18.             int nLine = 1;
19.             
20.             for (int count = 0, nC0 = nLine; count < m_Size; count++)
21.             {
22.                 switch (m_Buff[count])
23.                 {
24.                     case '\t':
25.                         break;
26.                     case '\n':
27.                         nC0++;
28.                     case '\r':
29.                         break;
30.                     case ';':
31.                         b0 = true;
32.                     default:
33.                         if ((!b1) && (m_Buff[count] > ' '))
34.                         {
35.                             b1 = true;
36.                             nLine = nC0;
37.                         }
38.                         sz0 += (b1 ? StringFormat("%c", m_Buff[count]) : "");
39.                 }
40.                 if (b0)
41.                 {
42.                     m_Arr.Add(sz0, nLine);
43.                     sz0 = "";
44.                     b0 = b1 = false;
45.                 }
46.             }
47.         }
48. //+------------------------------------------------------------------+
49.     public    :
50. //+------------------------------------------------------------------+
51.         C_ScriptSQL(const string szFileScript, const string szFileDataBase)
52.             :C_DB_SQLite(szFileDataBase),
53.              m_Size(-1)
54.         {
55.             int handle;
56.                 
57.             if ((handle = FileOpen(szFileScript, FILE_READ | FILE_BIN)) == INVALID_HANDLE)
58.             {
59.                 Print("Unable to open script file: ", szFileScript);
60.                 return;
61.             }
62.             ArrayResize(m_Buff, m_Size = (int) FileSize(handle));
63.             FileReadArray(handle, m_Buff);
64.             FileClose(handle);
65.             Convert();
66.         }
67. //+------------------------------------------------------------------+
68.         ~C_ScriptSQL()
69.         {
70.             ArrayFree(m_Buff);
71.         }
72. //+------------------------------------------------------------------+
73.         bool Execute(void)
74.         {
75.             int nLine;
76.             string szInfo;
77.                 
78.             for (int c = 0; c >= 0; c++)
79.             {
80.                 szInfo = m_Arr.At(c, nLine);
81.                 if (nLine > 0)
82.                     Print(nLine, ">>", szInfo);
83.                 else break;
84.             }
85.             return false;
86.         }
87. //+------------------------------------------------------------------+
88. };
89. //+------------------------------------------------------------------+

Código de C_ScriptSQL.mqh

Veja que o código já cresceu um bocado. Mas aqui temos quase tudo que precisamos, já implementado. Mas vamos dar uma rápida passada para entender o que temos aqui. Na linha cinco, estamos dizendo que precisamos usar um arquivo de cabeçalho. Este arquivo existe originalmente no código do replay/simulador. Você deve procurar nos artigos mais antigos desta sequência este arquivo de cabeçalho. Se você tem acompanhado os artigos, e feito as atualizações no código, conforme eu venho mostrando. Não precisará se preocupar, pois você já terá o arquivo correto, que precisamos.

Muito bem, nas linhas 10 a 12 temos algumas variáveis globais, porém privativas a esta classe. Já na linha 14, temos um procedimento, cujo objetivo é transcrever o conteúdo presente na variável m_Buff para uma array de linhas. Este procedimento é algo bastante interessante, já que ele irá fazer todo o trabalho pesado para nós. Basicamente aqui, iremos varrer o buffer, carácter por carácter, a fim de capturar uma linha de comando em SQL.

Note que cada uma das case estará testando a presença de algum carácter. Na linha 24, testamos o carácter de tabulação. Este carácter simplesmente será ignorado. Pelo menos neste momento. Já na linha 26, testamos a presença do carácter de nova linha. Quando este carácter é encontrado, temos um incremento na contagem da linha, que é feita na linha 27. Observe que na linha 28, analisamos a presença do carácter de retorno de carro.

Ambos carácteres, tanto o de retorno de carro, quanto o de nova linha tem um tratamento comum, ou seja, eles são encerrados na linha 29. Porém, apenas o de nova linha, recebe um tratamento diferenciado, pois ele incrementa a contagem de linhas. Se o editor que você utilizar, para criar o script SQL, não usar o carácter de nova linha, o que é algo raro de acontecer. Você deverá, usar o carácter de retorno de carro para incrementar a contagem de linhas. De qualquer maneira, acredito não ser preciso mudar este ponto.

Já na linha 30, temos exatamente o carácter que esperávamos para indicar o término de um comando SQL. Observe que ali, usamos uma variável para indicar que um comando foi encontrado. Mas como este mesmo carácter, também deverá estar presente, quando formos enviar o comando para o SQL. Não encerramos com um break, seu bloco. Assim ele entrará no bloco default. Este bloco, irá tratar qualquer carácter não tratado anteriormente. Então vamos entender o que está acontecendo aqui.

Na linha 33, testamos se foi encontrado algum carácter diferente do carácter espaço. Isto por que não precisamos de espaços no começo do comando SQL. Quando um carácter diferente de espaço for encontrado, iremos marcar isto, na linha 35. E logo em seguida, anotar no número da linha onde o comando SQL está iniciando.

Já na linha 38, fazemos a captura do carácter presente no buffer. Mas existe um detalhe. A string de comando, somente receberá algum carácter, se já houver algum presente no comando. Caso contrário, ele irá aguardar até que algum carácter venha a surgir. Para finalizar, na linha 40, quando tivermos a indicação de que um comando foi completamente capturado, teremos o armazenamento do mesmo. E a reinicialização, a fim de procurar o próximo comando SQL.

Já o constructor, assim como o destructor, tem códigos bastante simples ao meu ver. Não necessitando assim que eu os comente de forma especial. No entanto, o código na linha 73, contém algumas coisas que merecem alguma explicação. Então vamos a elas. Observe que na linha 78 temos um laço for bastante estranho. Isto por que ele começa a sua contagem em zero e vai incrementando. Porém ele não é infinito, como muitos poderiam imaginar. Ele se encerra, no momento que a variável ficar negativa. Isto de fato irá ocorrer em algum momento, já que o tipo da variável é uma inteira com sinal.

Mas antes que isto de fato venha a ocorrer, temos na linha 80, a forma de reaver os comandos que armazenamos anteriormente. Note que na linha 81, verificamos se a linha indicada é negativa. Quando isto ocorrer, a linha 83 será executada, finalizando assim o laço da linha 78. Caso tenhamos algo armazenado, iremos imprimir ele na linha 82. E é assim que a mágica funciona.

Note que se ao contrário de imprimir no terminal o comando, o enviássemos para a classe C_DB_SQLite, poderíamos neste ponto, executar o comando colocado no arquivo de script SQL. Algo realmente muito prático. Mas antes de vermos isto ocorrendo, vamos ver como ficou o código principal em MQL5. Este pode ser visto abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Basic script for SQL database written in MQL5"
04. #property version   "1.00"
05. #property script_show_inputs
06. //+------------------------------------------------------------------+
07. #include <Market Replay\SQL\C_ScriptSQL.mqh>
08. //+------------------------------------------------------------------+
09. input string user01 = "DataBase01";       //Database File Name
10. input string user02 = "Script 01.sql";    //SQL Script File Name
11. //+------------------------------------------------------------------+
12. void OnStart()
13. {
14.     C_ScriptSQL *SQL;
15.     
16.     SQL = new C_ScriptSQL(user02, user01);
17.     
18.     (*SQL).Execute();
19.     
20.     delete SQL;
21. }
22. //+------------------------------------------------------------------+

Código do script MQL5

Ao executar este arquivo no terminal MetaTrader 5. Você irá obter como resposta o que é visto na imagem abaixo.

Note que temos exatamente o número da linha onde o comando SQL se inicia, sendo informado. Assim como também o comando que foi capturado pelo sistema. Ou seja, já temos o que queremos, podendo assim enviar os comandos para a classe C_DB_SQLite, em vez de imprimir eles no terminal. Mas antes vamos ver como tratar a questão dos comentários no arquivo de script SQL. Para que possamos tratar os comentários permitidos em SQL. Precisamos voltar o procedimento Convert presente na classe C_ScriptSQL, e adicionar além de modificar algumas coisas ali.

Basicamente o problema que temos em mãos, é saber quando estamos dentro ou não de uma string. Parece algo simples. Porém quando o assunto é SQL, a coisa complica um pouco. Isto por que o SQL, permite usar tanto, aspas simples ( ' ) quando aspas duplas ( " ). E não tem como termos certeza sobre se o script estará usando um ou outro modelo para as strings. Então precisamos cobrir ambos. Mas não é bem está a parte mais complicada. Ela na verdade é outra. Mas vamos ver como o código ficou, a fim de conseguir cumprir o objetivo desejado. Isto pode ser visto logo abaixo.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "C_DB_SQLite.mqh"
005. #include "..\Service Graphics\Support\C_Array.mqh"
006. //+------------------------------------------------------------------+
007. class C_ScriptSQL : private C_DB_SQLite
008. {
009.     private    :
010.         C_Array     m_Arr;
011.         char        m_Buff[];
012.         int         m_Size;
013. //+------------------------------------------------------------------+
014.         void Convert(void)
015.         {
016.             string sz0 = "";
017.             bool b0, b1, bs1, bs2, bc0, bc1, bc;
018.             int nLine = 1;
019.             
020.             b0 = b1 = bs1 = bs2 = bc0 = bc1 = bc = false;
021.             for (int count = 0, nC0 = nLine; count < m_Size; count++)
022.             {
023.                 switch (m_Buff[count])
024.                 {
025.                     case '\t':
026.                         sz0 += (bs1 || bs2 ? "\t" : "");
027.                         break;
028.                     case '\n':
029.                         nC0++;
030.                     case '\r':
031.                         bc0 = false;
032.                         break;
033.                     case ';':
034.                         b0 = (bs1 || bs2 || bc0 || bc1 ? b0 : true);
035.                     default:
036.                         switch (m_Buff[count])
037.                         {
038.                             case '"':
039.                                 bs1 = (bs2 || bc0 || bc1 ? bs1 : !bs1);
040.                                 break;
041.                             case '\'':
042.                                 bs2 = (bs1 || bc0 || bc1 ? bs2 : !bs2);
043.                                 break;
044.                         }
045.                         if (((count + 1) < m_Size) && (!bs1) && (!bs2))
046.                         {
047.                             if (bc = ((m_Buff[count] == '-') && (m_Buff[count + 1] == '-'))) bc0 = true;
048.                             if (bc = ((m_Buff[count] == '/') && (m_Buff[count + 1] == '*'))) bc1 = true;
049.                             if (bc = ((m_Buff[count] == '*') && (m_Buff[count + 1] == '/'))) bc1 = false;
050.                             if (bc)
051.                             {
052.                                 count += 1;
053.                                 bc = false;
054.                                 continue;
055.                             }
056.                         }
057.                         if (!(bc0 || bc1))
058.                         {
059.                             if ((!b1) && (m_Buff[count] > ' '))
060.                             {
061.                                 b1 = true;
062.                                 nLine = nC0;
063.                             }
064.                             sz0 += (b1 ? StringFormat("%c", m_Buff[count]) : "");
065.                         }
066.                 }
067.                 if (b0)
068.                 {
069.                     m_Arr.Add(sz0, nLine);
070.                     sz0 = "";
071.                     b0 = b1 = false;
072.                 }
073.             }
074.         }
075. //+------------------------------------------------------------------+
076.     public    :
077. //+------------------------------------------------------------------+
078.         C_ScriptSQL(const string szFileScript, const string szFileDataBase)
079.             :C_DB_SQLite(szFileDataBase),
080.              m_Size(-1)
081.         {
082.             int handle;
083.                 
084.             if ((handle = FileOpen(szFileScript, FILE_READ | FILE_BIN)) == INVALID_HANDLE)
085.             {
086.                 Print("Unable to open script file: ", szFileScript);
087.                 return;
088.             }
089.             ArrayResize(m_Buff, m_Size = (int) FileSize(handle));
090.             FileReadArray(handle, m_Buff);
091.             FileClose(handle);
092.             Convert();
093.         }
094. //+------------------------------------------------------------------+
095.         ~C_ScriptSQL()
096.         {
097.             ArrayFree(m_Buff);
098.         }
099. //+------------------------------------------------------------------+
100.         bool Execute(void)
101.         {
102.             int nLine;
103.             string szInfo;
104.                 
105.             for (int c = 0; c >= 0; c++)
106.             {
107.                 szInfo = m_Arr.At(c, nLine);
108.                 if (nLine > 0)
109.                     Print(nLine, ">>", szInfo);
110.                 else break;
111.             }
112.             return false;
113.         }
114. //+------------------------------------------------------------------+
115. };
116. //+------------------------------------------------------------------+

Código fonte de C_ScriptSQL.mqh

Note que o código é muito mais complicado. Por isto mostrei antes como fazer as coisas. Pois entender este código para tratar os comentários é muito mais confuso, se você não entender o código anterior. Observe que foram adicionadas muito mais variáveis. E que agora o carácter de tabulação, que antes era ignorado, já não será ignorado em todos os casos. Apenas em casos específicos, ou seja, quando não estamos em uma string. Quando estivermos, o carácter de tabulação será anotado.

Também foi necessário adicionarmos, algo que você pode ver na linha 31. Onde quando é encontrada uma nova linha revertemos o valor usando para indicar que estamos em um comentário. Agora veja, que testamos se estamos em um comentário ou em uma string quando encontramos um carácter ponto virgula ( ; ). Isto para evitar falsas finalizações de comando SQL. Observe também, que na linha 36, adicionamos um switch. Este tem como função cobrir as aspas. Mas por que não deixei isto no corpo principal? O motivo, é que se este tratamento fosse feito no corpo principal, teríamos um trabalho extra para manter o script SQL intacto.

Mas o que realmente quero chamar a sua atenção, caro leitor, é para o código presente na linha 45. Pois é ali, que começamos a analisar a presença de comentários no código SQL. Cada uma das linhas testa uma condição de comentário. E todas elas, tem algo em comum que é a variável bc. Quando esta variável for verdadeira, indica que temos um comentário, e que qualquer coisa, deverá ser ignorada. Então vamos ver como as coisas funcionam aqui. Na linha 47, verificamos a presença de um traço duplo ( -- ) que em SQL indica um comentário até o fim da linha corrente. Na linha 48, verificamos a indicação de que teremos um comentário de muitas linhas. Já na linha 49, verificamos se existe a indicação de final de um comentário de muitas linhas.

Tais verificações são todas muito simples. Porém note que sempre iremos ignorar tais verificações quando estamos em uma string. Assim como também iremos ignorar a verificação de uma string, quando estamos em um comentário. Isto basicamente faz com que o script seja lido, e limpo de qualquer coisa que não seja um comando. Experimente criar scripts SQL, contendo comentários e strings. Execute o código do script MQL5, no MetaTrader 5 a fim de verificar se está tudo conforme o esperado. Observe cada um dos detalhes, a fim de garantir que de fato, o código esteja sendo corretamente capturado por esta rotina presente na classe C_ScriptSQL. Isto por que, uma vez que tal coisa esteja corretamente sendo feita. Você poderá mudar o código presente na linha 100 pelo código visto logo abaixo. Isto fará com que o script, em vez de ser mostrado no terminal do MetaTrader 5, seja de fato executado. Desta maneira concluímos o nosso objetivo.

099. //+------------------------------------------------------------------+
100.         bool Execute(void)
101.         {
102.             int nLine;
103.             string szInfo;
104.                 
105.             for (int c = 0; c >= 0; c++)
106.             {
107.                 szInfo = m_Arr.At(c, nLine);
108.                 if (nLine < 0) return c > 0;
109.                 if (!this.Command(szInfo)) return false;
110.             }
111.             return false;
112.         }
113. //+------------------------------------------------------------------+

Fragmento de C_ScriptSQL.mqh

Não se esqueça também de atualizar o arquivo principal, para o código visto abaixo.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Basic script for SQL database written in MQL5"
04. #property version   "1.00"
05. #property script_show_inputs
06. //+------------------------------------------------------------------+
07. #include <Market Replay\SQL\C_ScriptSQL.mqh>
08. //+------------------------------------------------------------------+
09. input string user01 = "DataBase01";       //Database File Name
10. input string user02 = "Script 01.sql";    //SQL Script File Name
11. //+------------------------------------------------------------------+
12. void OnStart()
13. {
14.     C_ScriptSQL *SQL;
15.     
16.     SQL = new C_ScriptSQL(user02, user01);
17.     
18.     Print("Result of executing the SQL script: ", (*SQL).Execute() ? "Success" : "Failed", "...");
19.     
20.     delete SQL;
21. }
22. //+------------------------------------------------------------------+

Código fonte do script MQL5

Assim como também, se deseja fazer muitos testes com o script SQL. Atualize ele para o que é visto no código abaixo.

01. PRAGMA FOREIGN_KEYS = ON;
02. 
03. DROP TABLE IF EXISTS tb_Symbols;
04. DROP TABLE IF EXISTS tb_Quotes;
05. 
06. CREATE TABLE IF NOT EXISTS tb_Symbols
07. (
08.     id PRIMARY KEY,
09.     symbol NOT NULL UNIQUE
10. );
11. 
12. CREATE TABLE IF NOT EXISTS tb_Quotes
13. (
14.     of_day NOT NULL,
15.     price NOT NULL,
16.     fk_id NOT NULL,
17.     FOREIGN KEY (fk_id) REFERENCES tb_Symbols(id)
18. );

Código em SQL


Considerações finais

Neste artigo, mostrei como você usando um pouco de conhecimento em MQL5 e em SQL. Pode conseguir fazer com que os códigos em SQL venham a ser executados via script. Ou melhor dizendo, você pode usar um arquivo que contém o código SQL, e fornecendo as devidas informações, ter a execução do exato código contido neste arquivo de script SQL.

É bem verdade, que muitos podem considerar usar um arquivo externo como sendo um problema. Isto devido ao fato de que você sempre precisará fornecer para o cliente o arquivo SQL. E terá de confiar, no fato de que o cliente, não venha por ventura mexer ou modificar o código presente no arquivo SQL. Isto de fato é um problema se você não sabe exatamente como lidar com algumas coisas. Mas, mesmo que você tenha gostado do que viu aqui, e venha ficar preocupado pelo fato de que alguém possa mexer ou modificar o código a ser executado pelo SQL. Isto de fato, não deve ser algo no qual você deverá se preocupar.

Se você não sabe como embutir este arquivo, contendo o código SQL no executável criado pelo MQL5. Não perca o próximo artigo. Pois irei mostrar como fazer isto. Além é claro, veremos ainda mais coisas sobre como trabalhar com o SQL via MQL5.

Arquivo Descrição
Experts\Expert Advisor.mq5
Demonstra a interação entre o Chart Trade e o Expert Advisor (É necessário o Mouse Study para interação)
 Indicators\Chart Trade.mq5 Cria a janela para configuração da ordem a ser enviada (É necessário o Mouse Study para interação)
 Indicators\Market Replay.mq5 Cria os controles para interação com o serviço de replay/simulador (É necessário o Mouse Study para interação)
 Indicators\Mouse Study.mq5 Permite interação entre os controles gráficos e o usuário (Necessário tanto para operar o replay simulador, quanto no mercado real)
 Services\Market Replay.mq5 Cria e mantém o serviço de replay e simulação de mercado (Arquivo principal de todo o sistema)
 Code VS C++\Servidor.cpp Cria e mantém um soquete servidor criado em C++ (Versão Mini Chat)
 Code in Python\Server.py Cria e mantém um soquete em python para comunicação entre o MetaTrader 5 e o Excel
 Indicators\Mini Chat.mq5 Permite implementar um mini chat via indicador (Necessário uso de um servidor para funcionar)
 Experts\Mini Chat.mq5 Permite implementar um mini chat via Expert Advisor (Necessário uso de um servidor para funcionar)
 Scripts\SQLite.mq5 Demonstra uso de script SQL por meio do MQL5
 Files\Script 01.sql Demonstra a criação de uma tabela simples, com chave estrangeira
 Files\Script 02.sql Demonstra a adição de valores em uma tabela
Arquivos anexados |
Anexo.zip (571.71 KB)
Redes neurais em trading: Agente multimodal com ferramentas complementares (FinAgent) Redes neurais em trading: Agente multimodal com ferramentas complementares (FinAgent)
Apresentamos o framework do agente multimodal para negociação financeira FinAgent, projetado para analisar dados de diferentes tipos que refletem a dinâmica do mercado e padrões históricos de negociação.
Redes neurais em trading: Agente com memória multinível (Conclusão) Redes neurais em trading: Agente com memória multinível (Conclusão)
Damos continuidade ao desenvolvimento do framework FinMem, que utiliza abordagens de memória multinível, imitando os processos cognitivos humanos. Isso permite que o modelo não apenas processe dados financeiros complexos de forma eficiente, mas também se adapte a novos sinais, aumentando significativamente a precisão e a efetividade das decisões de investimento em mercados altamente dinâmicos.
Analisamos o código binário dos preços no mercado (Parte I): Um novo olhar sobre a análise técnica Analisamos o código binário dos preços no mercado (Parte I): Um novo olhar sobre a análise técnica
Este artigo apresenta uma abordagem inovadora para a análise técnica, baseada na conversão dos movimentos de preço em código binário. O autor mostra como diferentes aspectos do comportamento do mercado - desde movimentos simples de preço até padrões complexos - podem ser codificados em sequências de zeros e uns.
Modelos polinomiais no trading Modelos polinomiais no trading
Este artigo é dedicado aos polinômios ortogonais. Seu uso pode se tornar a base para uma análise mais precisa e eficaz das informações do mercado, permitindo que o trader tome decisões mais fundamentadas.