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

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

MetaTrader 5Testador |
56 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 (IV), expliquei sobre um pequeno cuidado que deveremos tomar, ao usar o SQLite que se encontra dentro do MetaTrader 5. Apesar de que, pode ser que tal coisa venha a ser modificada no futuro. Mas na dúvida você deve sempre primeiro testar, antes de realmente começar a fazer uso do SQL. Isto quando for fazer uso do SQLite que se encontra presente no MetaTrader 5. De qualquer maneira, ali mostrei como você deveria proceder, a fim de conseguir adicionar o mecanismo de pesquisa. Isto para que dentro do código MQL5, você pudesse de fato fazer uso pleno do SQL. A fim de conseguir obter os resultados quando for usar o comando SELECT FROM do SQL.

Mas ficou faltando falar da última função que precisamos implementar. Esta é a função DatabaseReadBind. E como para entender ela adequadamente é algo que exigirá um pouco mais de explicações. Ficou decidido que isto seria feito, não naquele artigo anterior. Mas sim neste daqui. Então como o assunto será relativamente longo. Vamos direto ao próximo tópico.


Implementando a função de leitura das pesquisas

A implementação da função responsável por ler os dados reportados pelo SQL, é algo bastante simples e direto. Mas existe o detalhe que você nunca deverá se esquecer. O resultado que será retornado pela função de leitura, é justamente o resultado da execução anterior da chamada ExecRequestOfData. Então esta deverá sempre preceder a chamada para ler os dados reportados.

Porém, aqui se abre um parêntese. Mas para entender melhor isto. É preciso de fato primeiro ver o código da classe. Assim sendo o código, completo da classe C_DB_SQL, pode ser visto logo abaixo. Lembrando que tal código é voltado a fazer uso do SQL, que se encontra embutido no MetaTrader 5. Para outros casos de uso do SQL, este código deverá ser modificado a fim de promover as devidas funcionalidades.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Service Graphics\Support\C_Array.mqh"
005. //+------------------------------------------------------------------+
006. class C_DB_SQL
007. {
008.     private    :
009.         C_Array    m_Arr;
010.         int        m_handleDB,
011.                    m_Request;
012. //+------------------------------------------------------------------+
013.         void Convert(const char &buff[], const int size)
014.         {
015.             string sz0 = "";
016.             bool b0, b1, bs1, bs2, bc0, bc1, bc;
017.             int nLine = 1;
018.             
019.             b0 = b1 = bs1 = bs2 = bc0 = bc1 = bc = false;
020.             for (int count = 0, nC0 = nLine; count < size; count++)
021.             {
022.                 switch (buff[count])
023.                 {
024.                     case '\t':
025.                         sz0 += (bs1 || bs2 ? "\t" : "");
026.                         break;
027.                     case '\n':
028.                         nC0++;
029.                     case '\r':
030.                         bc0 = false;
031.                         break;
032.                     case ';':
033.                         b0 = (bs1 || bs2 || bc0 || bc1 ? b0 : true);
034.                     default:
035.                         switch (buff[count])
036.                         {
037.                             case '"':
038.                                 bs1 = (bs2 || bc0 || bc1 ? bs1 : !bs1);
039.                                 break;
040.                             case '\'':
041.                                 bs2 = (bs1 || bc0 || bc1 ? bs2 : !bs2);
042.                                 break;
043.                         }
044.                         if (((count + 1) < size) && (!bs1) && (!bs2))
045.                         {
046.                             if (bc = ((buff[count] == '-') && (buff[count + 1] == '-'))) bc0 = true;
047.                             if (bc = ((buff[count] == '/') && (buff[count + 1] == '*'))) bc1 = true;
048.                             if (bc = ((buff[count] == '*') && (buff[count + 1] == '/'))) bc1 = false;
049.                             if (bc)
050.                             {
051.                                 count += 1;
052.                                 bc = false;
053.                                 continue;
054.                             }
055.                         }
056.                         if (!(bc0 || bc1))
057.                         {
058.                             if ((!b1) && (buff[count] > ' '))
059.                             {
060.                                 b1 = true;
061.                                 nLine = nC0;
062.                             }
063.                             sz0 += (b1 ? StringFormat("%c", buff[count]) : "");
064.                         }
065.                 }
066.                 if (b0)
067.                 {
068.                     m_Arr.Add(sz0, nLine);
069.                     sz0 = "";
070.                     b0 = b1 = false;
071.                 }
072.             }
073.         }
074. //+------------------------------------------------------------------+
075.         const string ExecSQL(void)
076.         {
077.             string szCmd;
078.             
079.             for (int count = 0, nLine; count >= 0; count++)
080.             {
081.                 szCmd = m_Arr.At(count, nLine);
082.                 if (nLine < 0) break;
083.                 if (!ExecCommandSQL(szCmd))
084.                     return StringFormat("Execution of line %d of the SQL script failed...", nLine);
085.             }
086.             
087.             return NULL;
088.         }
089. //+------------------------------------------------------------------+
090.     public    :
091. //+------------------------------------------------------------------+
092.         C_DB_SQL(const string szFileName = ":memory:")
093.         {
094.             m_handleDB = DatabaseOpen(szFileName, DATABASE_OPEN_CREATE | DATABASE_OPEN_READWRITE);
095.             m_Request = INVALID_HANDLE;
096.         }
097. //+------------------------------------------------------------------+
098.         ~C_DB_SQL()
099.         {
100.             DatabaseClose(m_handleDB);
101.         }
102. //+------------------------------------------------------------------+
103.         const string ExecResourceSQL(const string szResource)
104.         {
105.             char buff[];
106.             int size;
107.             
108.             ArrayResize(buff, size = StringLen(szResource));
109.             StringToCharArray(szResource, buff);
110.             Convert(buff, size);
111.             ArrayFree(buff);
112.             
113.             return ExecSQL();
114.         }
115. //+------------------------------------------------------------------+
116.         const string ExecScriptSQL(const string szFileName)
117.         {
118.             int file, size;
119.             char buff[];
120.             
121.             if ((file = FileOpen(szFileName, FILE_READ | FILE_BIN)) == INVALID_HANDLE)
122.                 return StringFormat("Unable to open script file: %s", szFileName);
123.             ArrayResize(buff, size = (int) FileSize(file));
124.             FileReadArray(file, buff);                
125.             FileClose(file);
126.             Convert(buff, size);
127.             ArrayFree(buff);
128.             
129.             return ExecSQL();
130.         }
131. //+------------------------------------------------------------------+
132.         bool ExecCommandSQL(const string szCmd)
133.         {
134.             return (m_handleDB == INVALID_HANDLE ? false : DatabaseExecute(m_handleDB, szCmd));
135.         }
136. //+------------------------------------------------------------------+
137.         bool ExecRequestOfData(const string szCmd)
138.         {
139.             if (m_Request != INVALID_HANDLE) DatabaseFinalize(m_Request);
140.             return ((m_Request = DatabasePrepare(m_handleDB, szCmd)) != INVALID_HANDLE);
141.         }
142. //+------------------------------------------------------------------+
143.         template <typename T> bool GetRegisterOfRequest(T &stObject, const bool Finish = true)
144.         {
145.             if (!DatabaseReadBind(m_Request, stObject))
146.             {
147.                 if (Finish)
148.                 {
149.                     DatabaseFinalize(m_Request);
150.                     m_Request = INVALID_HANDLE;
151.                 }
152.                 return false;
153.             }
154.             
155.             return true;
156.         }
157. //+------------------------------------------------------------------+
158. };
159. //+------------------------------------------------------------------+

Código de C_DB_SQL.mqh

Ok. Este é o código completo da classe. Mas já que apenas uma pequena parte dele, não foi ainda comentada. Ou melhor, não havia sido mostrada anteriormente. Não irei abordar as funções ou pontos já comentados em outros artigos. Aqui vamos focar apenas na função presente na linha 143. Ou seja, a função GetRegisterOfRequest. A primeira pergunta, é esta seria uma pergunta justa a ser feita é: Por que esta função é declarada desta maneira? Esta é uma parte um pouco, quanto complicada de ser explicada. Isto usando apenas o MQL5.

Para entender isto, em detalhes, seria preciso de fato explicar um conceito, muito complicado que existe na linguagem C/C++. Este conceito envolve o uso de ponteiros do tipo void. E sim, podemos declarar e usar um ponteiro do tipo void. Não apenas de tipos comuns, como muitos imaginam ser possível. Porém, como este conceito é extremamente complicado de explicar em um artigo. Irei tentar mostrar a você, caro leitor, por que esta função está sendo declarada desta maneira. Isto evitando ao máximo entrar na linguagem C/C++.

Para começar, aqui temos uma dependência de tipo. Não na declaração da função, que está sendo feita na linha 143. E sim na função DatabaseReadBind. Esta dependência, nos força a utilizar um certo tipo de dado, para que o compilador MQL5, não gere uma avalanche de erros ao tentar compilar o código. Muitos programadores, quando vão usar, ou tentar usar a função DatabaseReadBind, não a colocam em uma chamada como estou fazendo aqui. O motivo disto, é justamente por eles não entenderem a dependência de tipo gerada aqui.

Então o que eles, normalmente fazem? Eles criam algum procedimento, ou função. Onde a pesquisa será feita. O resultado reportado pelo SQL analisado. E por fim eles retornam o valor normalmente, ao chamador. Ok. Este tipo de abordagem funciona, e é bastante adequada para casos bastante específicos. Em outros casos, os programadores simplesmente usam, e isto quando usam, a função DatabaseReadBind, dentro do corpo do código principal.

Mas por que eles fazem uso de tal abordagem? Novamente o motivo, é por que existe uma dependência de tipo. E esta não é tão simples de se lidar, aqui no MQL5. Porém quando usamos o C/C++ a coisa muda um pouco de figura. Visto que neste caso, podemos usar um ponteiro do tipo void. Imagino que você ainda não tem entendido, esta questão. Então vamos olhar como a função DatabaseReadBind é declarada no MQL5. Você pode ver isto olhando logo abaixo.

bool  DatabaseReadBind( 
   int    request,           // manipulador da consulta criada no DatabasePrepare 
   void&  struct_object      // referência para a estrutura para leitura do registro 
   );

Isto que você está vendo, pode ser observado na documentação da função, dentro do MQL5. Talvez para você isto não faça muito sentido. Mas para um programador C/C++, esta declaração faz todo sentido. Então, sem entrar em detalhes sobre C/C++, o que temos aqui é o seguinte: A função deverá receber dois parâmetros. O primeiro parâmetro é um valor do tipo inteiro, que representa o manipulador que é retornado pela função DatabasePrepare. Falamos sobre isto no artigo anterior. Já o segundo parâmetro é que é o grande detalhe aqui. Este parâmetro é um ponteiro. Mas um ponteiro do tipo void. Em tese ele poderia ser de qualquer tipo. Já que o tipo void não específica o tipo esperado.

Mas, este ponteiro irá de fato receber uma referência a uma posição da memória. E não um endereço, como seria o natural. Isto por que ponteiros normalmente se referem a uma posição da memória. Mas quando usamos o carácter e comercial (&) antes do nome, estamos indicando que faremos uma referência a uma variável. E não uma posição. Se você de fato deseja entender isto, procure saber como usar ponteiros em C/C++. Já que é algo, que muitas vezes exige diversos capítulos de livro voltados a ensinar C/C++. Pois o assunto é de fato bastante complexo e denso.

Porém, pela questão do MQL5, não nos permitir usar ponteiros da mesma forma que usamos em C/C++. Precisamos de um pequeno macete, a fim de conseguir acesso a função DatabaseReadBind. Isto como visto no código da classe C_DB_SQL. Tal macete, é justamente fazer uso de um sistema de conversão de tipos. Onde dizemos a função que ela poderá receber qualquer tipo, e este deverá ser convertido, internamente pelo compilador no tipo adequado. Aparentemente isto seria algo muito complicado e de difícil solução. Mas felizmente o MQL5, nos dá a possibilidade de algo presente no C/C++, que é a declaração:

template <typename T >

Aqui mora um detalhe. T pode ser qualquer coisa, mas normalmente usamos o T como um padrão no momento da codificação. O detalhe que justamente esta declaração que vem antes do nome da função. Diz ao compilador que ele deverá promover a correta conversão a fim se solucionar o problema de tipagem. Agora observe o seguinte: O primeiro parâmetro da função GetRegisterOfRequest é justamente o T. Já que não podemos usar o tipo void, esta declaração desta forma substitui a forma de fazer as coisas, como seria feita em C/C++.

Ok. Mas ainda não consegui compreender esta lógica. Olhando a documentação do MQL5, a respeito da função DatabaseReadBind, vemos que lá é indicado que o segundo parâmetro deverá ser uma estrutura. E até nos exemplos, onde é demonstrado o uso desta função, temos também o uso de uma estrutura sendo colocada no segundo parâmetro. Então não consegui entender por que desta complicação de usar template <typename T >, e esta declaração bizarra na linha 143. Por que simplesmente não passar uma estrutura diretamente para esta função. Por que complicar as coisas desta maneira?

De fato, meu caro leitor. Concordo plenamente com a sua forma de ver e enxergar as coisas. E você de fato está correto. Poderíamos simplesmente passar uma estrutura como sendo um parâmetro da função. Assim deixaríamos a declaração da linha 143 muito mais simples. Porém, agora lhe pergunto: Quantos campos e que tipo de campos esta estrutura deverá ter? Se você conseguir me responder esta questão, de forma a generalizar as coisas. Com toda a certeza irei modelar a chamada da linha 143 de uma outra forma.

Mas, porém, toda via e entretanto. Tal estrutura jamais será de fato construída de forma a generalizar todas as situações. Isto por que, mesmo você conhecendo o número de campos presentes no banco de dados. Mais hora ou menos hora, este número de campos poderá mudar. E se isto ocorrer, toda a construção irá ter que ser refeita aqui no código em MQL5. Por conta disto, muitos usam a função DatabaseReadBind, de uma forma diferente da que estou mostrando.

Esta função que estou mostrando, cuja finalidade é retornar para o nosso código em MQL5, o que foi reportado pelo SQL. É de fato uma função genérica. E em breve você entenderá o porquê. De qualquer forma, na linha 145, faremos a chamada e dependendo do retorno teremos duas situações totalmente diferente, acontecendo aqui. A primeira situação é quando a função DatabaseReadBind, consegue ler algum registro reportado pelo SQL. Neste caso, a linha 155 será executada. Mas pode acontecer de não haver mais registros dentro do bloco. Então iremos ter duas situações diferentes. Porém ambas estarão dentro do bloco de erro no retorno da função DatabaseReadBind.

Observe que por padrão, a função GetRegisterOfRequest, tem seu segundo parâmetro como sendo um true, ou seja, um valor verdadeiro. Quando o teste na linha 145 falhar, iremos verificar na linha 147 se o valor do segundo parâmetro é verdadeiro ou não. Se ele não foi modificado pelo chamador, teremos a execução da função DatabaseFinish na linha 149. E para completar na linha 150, modificamos o valor da variável m_Request, indicando que não há mais registros disponíveis. Sendo necessário uma nova pesquisa no banco de dados. Porém se o chamador, informar que o valor do parâmetro Finish é falso, o teste na linha 147 falhará. Deixando as coisas como estão. Isto será útil para o que veremos depois no código principal. Mas independentemente disto, teremos como retorno um valor falso. Isto por conta da linha 152.

Com isto encerramos totalmente a parte referente a base sobre a classe C_DB_SQL. Agora todo e qualquer código poderá usar ela depois. Isto no nosso sistema de replay/simulador. Mas antes de fazermos isto, precisamos verificar se tudo está funcionando conforme esperado. E para fazer isto, vamos a um novo tópico. Para que possamos separar as coisas de forma mais adequada.


Testando a classe C_DB_SQL para uso no SQLite do MetaTrader 5

Grande parte da nossa classe C_DB_SQL, já foi testada. Restando agora de fato testar a parte referente a pesquisas. E conseguir de alguma forma entender o que o SQL estará nos reportando quando uma pesquisa for feita.

Para tentar explicar isto de uma forma conveniente. E que ao mesmo tempo seja facilmente compreendida por todos. Principalmente para você, caro leitor e entusiasta. Eu vou mostrar um fragmento do código principal. Mostrar o resultado obtido. Explicar o que está acontecendo e por que aquele resultado foi obtido. E mais no final do artigo, colocarei o código na íntegra para que você possa experimentar localmente o que estaremos fazendo aqui.

Tendo entendido esta parte, vamos ao primeiro teste. Ele será feito, usando o seguinte fragmento mostrado abaixo.

18. //+------------------------------------------------------------------+
19. void SELECT_Type_01(void)
20. {
21.     struct st
22.     {
23.         int id;
24.         string symbol;
25.     }stLocal;
26. 
27.     Print("Executing type 1 data request...");
28.     if ((*SQL).ExecRequestOfData("SELECT * FROM tb_Symbols"))
29.     {
30.         Print("Request: OK...");
31.         for (int c = 0; (*SQL).GetRegisterOfRequest(stLocal, false); c++)
32.             Print(" Resq ", c, " ==> ", stLocal.id, " - ", stLocal.symbol);
33.         Print("Reload...");
34.         for (int c = 0; (*SQL).GetRegisterOfRequest(stLocal); c++)
35.             Print(" Resq ", c, " ==> ", stLocal.id, " - ", stLocal.symbol);
36.     };
37.     Print("Finish type 1...");
38. }
39. //+------------------------------------------------------------------+

Fragmento do arquivo principal

A numeração das linhas segue exatamente o que você verá no código completo. Então você pode estudar com calma cada fragmento. O resultado da execução deste fragmento é visto na imagem animação logo abaixo.

Note que é algo bem simples e direto. Observe que na linha 21 declaramos uma estrutura. Note que dentro desta estrutura, teremos duas variáveis. Você NÃO as deve ver desta forma. Não pense que são variáveis, pois isto irá lhe confundir tornando difícil entender outras coisas depois. Pense nelas como sendo campos, ou colunas dentro da resposta vinda do SQL. Então temos uma estrutura, que deverá ser pensada como sendo uma linha de resposta do SQL. Nesta linha teremos dois campos esperados.

Então na linha 28, tentamos requerer dados do banco de dados em SQL. Preste muita, mais muita atenção ao que está entre aspas duplas ("), pois este deverá ser o comando a ser executado pelo SQL. Você deverá digitar exatamente o que deseja que o SQL execute. Neste exemplo mais simples, estamos apenas querendo todos os valores presentes na tabela tb_Symbols. Apesar de tal forma de pesquisa não ser adequada na prática. Para nosso exemplo, ele é adequado. Já que nossa tabela é bem pequena e contem poucos registros.

Caso tenhamos sucesso, na nossa requisição, iremos primeiramente efetuar um laço for. Isto na linha 31. Agora preste atenção, ao fato de que, quem fará o laço ser encerrado, é justamente o fato de não haver mais registros sendo reportados. E não deixe de notar o fato de que, durante estas chamadas na linha 31, estamos dizendo a função GetRegisterOfRequest que não queremos o fechamento do bloco lido. Ou seja, quando não houver mais registros disponíveis, não fecharemos o bloco. Iremos manter a tabela de registros reportados ainda disponíveis.

Já na linha 32, imprimiremos no terminal o resultado que foi reportado. Na linha 33 indicamos no terminal, que faremos uma nova leitura dos dados reportados pelo SQL. Note que estamos fazendo praticamente a mesma coisa feita na linha 31. Porém desta vez, não informaremos absolutamente nada no segundo parâmetro da chamada GetRegisterOfRequest. Então no momento que o bloco for totalmente lido. Tudo que foi reportado pelo SQL será destruído. Precisando assim que façamos um novo requerimento.

A próxima forma de fazer as coisas é visto no fragmento abaixo:

39. //+------------------------------------------------------------------+
40. void SELECT_Type_02(void)
41. {
42.     struct st
43.     {
44.         string of_day;
45.         double price;
46.         string symbol;
47.     }stLocal;
48.     
49.     Print("Executing type 2 data request...");
50.     if ((*SQL).ExecRequestOfData("SELECT tq.of_day, tq.price, ts.symbol FROM tb_Quotes AS tq, tb_Symbols AS ts WHERE tq.fk_id = ts.id ORDER BY price DESC;"))
51.     {
52.         Print("Request: OK...");
53.         for (int c = 0; (*SQL).GetRegisterOfRequest(stLocal); c++)
54.             Print(" Resq ", c, " ==> ", stLocal.of_day, " { ", stLocal.price, " } ", stLocal.symbol);
55.     }
56.     Print("Finish type 2...");
57. }
58. //+------------------------------------------------------------------+

Fragmento do código principal

E o resultado é visto na animação abaixo:

Observe que aqui, temos algo um pouco diferente. Primeiramente, agora aqui na linha 42 temos a declaração de uma estrutura, que terá três campos. No caso anterior tínhamos dois campos por linha. Mas observe, que apesar de termos mais campos, ainda assim temos usamos um código muito parecido com o anterior. Porém, desta vez, quero que observe atentamente o que está sendo colocado como comando para o SQL. Isto na linha 50. Note que estamos fazendo uma combinação entre a tabela tb_Quotes e a tabela tb_Symbol. Isto de forma a criar uma certa apresentação do que existe de fato, no banco de dados.

Agora preste atenção na ordem que os campos estão sendo declarados no requerimento ao SQL. Isto é importante, já que esta mesma ordem, será respeitada pelo comando DatabaseReadBind. Isto no momento que você for ler os dados de resposta do SQL. Então se você trocar a ordem, seja no comando SQL, seja na declaração dos campos dentro da estrutura na linha 42, poderá ver coisas completamente inesperadas, sendo reportadas. Então tome cuidado com estes detalhes.

Agora temos um caso um pouco mais complicado. Isto à primeira vista. Mas não se deixe levar. Pois ele é tão simples quanto todos dos demais. Apenas estaremos fazendo um uso um pouco mais elaborado das funções presentes no MQL5. Estas cuja finalidade é promover um uso do SQL dentro do MQL5. Você pode ver o fragmento logo abaixo.

58. //+------------------------------------------------------------------+
59. void SELECT_Type_03(void)
60. {
61.     struct st0
62.     {
63.         double price;
64.         string symbol;
65.     }stLocal;
66.     struct st1
67.     {
68.         string tmp1,
69.                tmp2,
70.                date;
71.     }std;
72.     
73.     Print("Executing type 3 data request...");
74.     if ((*SQL).ExecRequestOfData("SELECT tq.price, ts.symbol, tq.of_day FROM tb_Quotes AS tq, tb_Symbols AS ts WHERE tq.fk_id = ts.id ORDER BY price DESC;"))
75.     {
76.         Print("Request: OK...");
77.         for (int c = 0; (*SQL).GetRegisterOfRequest(stLocal, false); c++)
78.             Print(" Resq ", c, " ==> { ", stLocal.price, " } ", stLocal.symbol);
79.         Print("Reload...");
80.         for (int c = 0; (*SQL).GetRegisterOfRequest(std); c++)
81.             Print(" Resq ", c, " ==> < ", std.date, " > ");
82.         
83.     }
84.     Print("Finish type 3...");
85. }
86. //+------------------------------------------------------------------+

Fragmento do código principal

E o resultado da execução deste fragmento é mostrado logo abaixo.

Até o momento, temos feitos uso de uma estrutura de linha onde haveria dois ou três campos. Mas como expliquei no tópico anterior. Podemos fazer muito mais do que isto. Podemos fazer com que o SQL, nos retorne mais campos do que precisamos. E depois de analisar adequadamente os campos que realmente queríamos. Podemos fazer uso do que o SQL já haverá nos reportado. E assim não precisaremos fazer uma nova pesquisa, apenas para procurar os dados que de fato queremos. Isto por que eles já vieram junto com a pesquisa anterior.

Aqui mora um detalhe que você precisará entender. Este é o fato de que, cada linha reportada pelo SQL, terá alguma relação com que estará em cada campo específico. Mas como você poderá experimentar neste exemplo. Estaremos fazendo a mesma pesquisa feita no fragmento anterior. Só que agora, o SQL receberá a ordens dos campos em uma outra sequência. É muito, mas muito importante você notar isto. O motivo é que, a estrutura declarada na linha 61 contém dois campos. Já a estrutura declarada na linha 66 contém três campos. E o SQL irá retornar para nos três campos. Isto por que o comando de pesquisa está dizendo para o SQL fazer isto.

Agora note que a estrutura st0 se intercala na estrutura st1. No entanto, o campo nomeado como date, não existe na estrutura st0. Sendo por isto ignorado no momento que formos buscar os dados reportados pelo SQL. Isto na linha 77, onde fazemos um laço a fim de buscar e imprimir no terminal os dados reportados pelo SQL.

No entanto, na linha 80, fazemos a mesma coisa. Só que desta vez, iremos ignorar os outros dados que o SQL nos reportou. Mostrando assim apenas o campo de nome date. Não existe grandes complicações aqui. É tudo uma questão de entender o que estamos fazendo. E o que o SQL está nos dizendo.

Você já deve estar achando que podemos fazer as coisas de qualquer maneira. Que independentemente do que faremos algo será obtido. No entanto, as coisas não são bem assim. para verificar isto, veja o próximo fragmento.

086. //+------------------------------------------------------------------+
087. void SELECT_Type_04(void)
088. {
089.     struct st
090.     {
091.         string of_day;
092.         double price;
093.         string symbol;
094.     }stLocal;
095.     
096.     Print("Executing type 4 data request...");
097.     if ((*SQL).ExecRequestOfData("SELECT tq.price, ts.symbol FROM tb_Quotes AS tq, tb_Symbols AS ts WHERE tq.fk_id = ts.id ORDER BY price DESC;"))
098.     {
099.         Print("Request: OK...");
100.         for (int c = 0; (*SQL).GetRegisterOfRequest(stLocal); c++)
101.             Print(" Resq ", c, " ==> ", stLocal.of_day, " { ", stLocal.price, " } ", stLocal.symbol);
102.     }
103.     Print("Finish type 4...");
104. }
105. //+------------------------------------------------------------------+

Fragmento do código principal

O resultado da execução deste fragmento acima pode ser visto logo a seguir. Sendo aparentemente algo estranho, devido ao fato de termos um resultado completamente inesperado.

Isto de fato é algo bastante estranho, você pode estar pensando. Mas não é assim tão estranho. Isto por que observe na declaração da linha 89, onde dizemos quais os campos que esperamos obter no requerimento ao SQL. Agora olhe na linha 97, o que estamos requerendo ao SQL. Observe que as informações não estão pareadas, entre o que esperamos como resultado e o que requisitamos ao SQL.

Aqui mora uma questão que novamente devemos recorrer a documentação do MQL5, a fim de entender o motivo pelo qual o resultado é o mostrado na animação. Então olhando a documentação, da função principal, presente na rotina GetRegisterOfRequest, que é a função DatabaseReadBind. Temos o seguinte trecho a ser destacado:

Observação

O número de campos na estrutura struct_object não pode exceder DatabaseColumnsCount(). Se o número de campos na estrutura struct_object for menor que o número de campos no registro, será realizada uma leitura parcial. Os dados restantes podem ser obtidos explicitamente usando as funções correspondentes DatabaseColumnText(), DatabaseColumnInteger() e assim por diante

Mas espere um pouco, não estamos usando DatabaseColumnsCount. Então o resultado não faz sentido. Bem, na verdade o resultado faz sentido. Já que no requerimento ao SQL estamos informando que queremos dois campos. Observe na linha 97, que estamos pedindo o campo price e o campo symbol, cada um vindo de uma tabela diferente. Como a função GetRegisterOfRequest, não faz uso de um método a fim de ajustar as coisas. Isto para que dois dos três campos indicados na estrutura sejam preenchidos. A função DatabaseReadBind, simplesmente ignora qualquer tentativa de colocar dados nos campos da estrutura. Assim sendo, o retorno é exatamente este que você pode ver na animação.

Podemos melhorar isto, mas por hora ficará assim. Já que ainda existe um outro caso a ser visto. Este pode ser observado no fragmento abaixo.

105. //+------------------------------------------------------------------+
106. void SELECT_Type_05(void)
107. {
108.     struct st
109.     {
110.         double price;
111.     }stLocal;
112.         
113.     Print("Executing type 5 data request...");
114.     if ((*SQL).ExecRequestOfData("SELECT tq.price FROM tb_Quotes AS tq, tb_Symbols AS ts WHERE tq.fk_id = (SELECT ts.id FROM tb_Symbols WHERE ts.symbol = 'PETR4') AND tq.of_day = '2023-07-11';"))
115.     {
116.         Print("Request: OK...");
117.         for (int c = 0; (*SQL).GetRegisterOfRequest(stLocal); c++)
118.             Print(" Resq ", c, " ==> { ", stLocal.price, " } ");
119.     }
120.     Print("Finish type 5...");
121. }
122. //+------------------------------------------------------------------+

Fragmento do código principal

Como resultado da execução temos exatamente o que é visto na animação abaixo.

Este é um caso um pouco mais extremo do uso do SQL. Onde de fato, forçamos o SQL a fazer exatamente algo que você possivelmente você pensaria que seria preciso fazer fora do SQL. Mas aqui estamos simplesmente fazendo uma pesquisa bem específica a fim de obter um dado registro de dentro do banco de dados. Observe muito atentamente a linha 114. Nela temos o comando que o SQL deverá executar. Observe que estamos pedindo a cotação, do ativo cujo símbolo é PETR4, e isto do dia 11-07-2023. Claro que a data segue o formato esperado pelo SQL. Mas creio que você neste ponto de leitura dos artigos, já tenha entendido como o SQL funciona.

E note que se você olhar dentro do banco de dados. Irá obter o mesmo resultado visto no terminal para o que foi requisitado ao SQL. Este tipo de coisa exige que tenhamos apenas um único campo a ser retornado. Observe como isto é declarado na linha 108.

Mas aqui pode surgir uma pergunta que é válida: Não poderíamos simplesmente usar a linha 110, desprezando a construção da estrutura vista na linha 108? Infelizmente não, meu caro leitor. Não da forma como a função GetRegisterOfRequest foi declarada. Isto por que se você tentar usar apenas a linha 110, desprezando a construção da estrutura, você na verdade não estaria chamando a leitura feita usando a função DatabaseReadBind. Você estaria usando algo diferente. Porém, como programadores, podemos gerar um código que consiga trabalhar com este tipo de situação. Assim como também lidar com a situação vista no fragmento anterior.

De qualquer maneira, você pode experimentar os pedidos, ou melhor dizendo, os comandos em SQL visto aqui, diretamente no MetaEditor. A fim de experimentar um pouco mais, sobre como de fato criar uma pesquisa orientada para obtenção de resultados como visto neste último fragmento. Mas não se esqueça de também experimentar a mesma coisa no código final a ser usado como aplicação do MetaTrader 5. O código completo, para que você possa experimentar as coisas, pode ser visto logo abaixo.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. #property description "Basic script for SQL database written in MQL5"
004. #property version   "1.00"
005. #property script_show_inputs
006. //+------------------------------------------------------------------+
007. #resource "\\Files\\Script 01.sql" as string SQL_Create
008. #resource "\\Files\\Script 02.sql" as string SQL_Insert
009. //+------------------------------------------------------------------+
010. #include <Market Replay\SQL\C_DB_SQL.mqh>
011. //+------------------------------------------------------------------+
012. enum eCall {eType_00, eType_01, eType_02, eType_03, eType_04, eType_05};
013. //+------------------------------------------------------------------+
014. input string user01 = "DataBase01";       //Database File Name
015. input ECall user02 = eType_01;            //Search type
016. //+------------------------------------------------------------------+
017. C_DB_SQL *SQL;
018. //+------------------------------------------------------------------+
019. void SELECT_Type_01(void)
020. {
021.     struct st
022.     {
023.         int id;
024.         string symbol;
025.     }stLocal;
026. 
027.     Print("Executing type 1 data request...");
028.     if ((*SQL).ExecRequestOfData("SELECT * FROM tb_Symbols"))
029.     {
030.         Print("Request: OK...");
031.         for (int c = 0; (*SQL).GetRegisterOfRequest(stLocal, false); c++)
032.             Print(" Resq ", c, " ==> ", stLocal.id, " - ", stLocal.symbol);
033.         Print("Reload...");
034.         for (int c = 0; (*SQL).GetRegisterOfRequest(stLocal); c++)
035.             Print(" Resq ", c, " ==> ", stLocal.id, " - ", stLocal.symbol);
036.     };
037.     Print("Finish type 1...");
038. }
039. //+------------------------------------------------------------------+
040. void SELECT_Type_02(void)
041. {
042.     struct st
043.     {
044.         string of_day;
045.         double price;
046.         string symbol;
047.     }stLocal;
048.     
049.     Print("Executing type 2 data request...");
050.     if ((*SQL).ExecRequestOfData("SELECT tq.of_day, tq.price, ts.symbol FROM tb_Quotes AS tq, tb_Symbols AS ts WHERE tq.fk_id = ts.id ORDER BY price DESC;"))
051.     {
052.         Print("Request: OK...");
053.         for (int c = 0; (*SQL).GetRegisterOfRequest(stLocal); c++)
054.             Print(" Resq ", c, " ==> ", stLocal.of_day, " { ", stLocal.price, " } ", stLocal.symbol);
055.     }
056.     Print("Finish type 2...");
057. }
058. //+------------------------------------------------------------------+
059. void SELECT_Type_03(void)
060. {
061.     struct st0
062.     {
063.         double price;
064.         string symbol;
065.     }stLocal;
066.     struct st1
067.     {
068.         string tmp1,
069.                  tmp2,
070.                  date;
071.     }std;
072.     
073.     Print("Executing type 3 data request...");
074.     if ((*SQL).ExecRequestOfData("SELECT tq.price, ts.symbol, tq.of_day FROM tb_Quotes AS tq, tb_Symbols AS ts WHERE tq.fk_id = ts.id ORDER BY price DESC;"))
075.     {
076.         Print("Request: OK...");
077.         for (int c = 0; (*SQL).GetRegisterOfRequest(stLocal, false); c++)
078.             Print(" Resq ", c, " ==> { ", stLocal.price, " } ", stLocal.symbol);
079.         Print("Reload...");
080.         for (int c = 0; (*SQL).GetRegisterOfRequest(std); c++)
081.             Print(" Resq ", c, " ==> < ", std.date, " > ");
082.         
083.     }
084.     Print("Finish type 3...");
085. }
086. //+------------------------------------------------------------------+
087. void SELECT_Type_04(void)
088. {
089.     struct st
090.     {
091.         string of_day;
092.         double price;
093.         string symbol;
094.     }stLocal;
095.     
096.     Print("Executing type 4 data request...");
097.     if ((*SQL).ExecRequestOfData("SELECT tq.price, ts.symbol FROM tb_Quotes AS tq, tb_Symbols AS ts WHERE tq.fk_id = ts.id ORDER BY price DESC;"))
098.     {
099.         Print("Request: OK...");
100.         for (int c = 0; (*SQL).GetRegisterOfRequest(stLocal); c++)
101.             Print(" Resq ", c, " ==> ", stLocal.of_day, " { ", stLocal.price, " } ", stLocal.symbol);
102.     }
103.     Print("Finish type 4...");
104. }
105. //+------------------------------------------------------------------+
106. void SELECT_Type_05(void)
107. {
108.     struct st
109.     {
110.         double price;
111.     }stLocal;
112.         
113.     Print("Executing type 5 data request...");
114.     if ((*SQL).ExecRequestOfData("SELECT tq.price FROM tb_Quotes AS tq, tb_Symbols AS ts WHERE tq.fk_id = (SELECT ts.id FROM tb_Symbols WHERE ts.symbol = 'PETR4') AND tq.of_day = '2023-07-11';"))
115.     {
116.         Print("Request: OK...");
117.         for (int c = 0; (*SQL).GetRegisterOfRequest(stLocal); c++)
118.             Print(" Resq ", c, " ==> { ", stLocal.price, " } ");
119.     }
120.     Print("Finish type 5...");
121. }
122. //+------------------------------------------------------------------+
123. const string ExecScripts(void)
124. {
125.     string szMsg = (*SQL).ExecResourceSQL(SQL_Create);
126.     if (szMsg != NULL) return szMsg;
127.     return (*SQL).ExecResourceSQL(SQL_Insert);
128. };
129. //+------------------------------------------------------------------+
130. void OnStart()
131. {
132.     string szMsg = NULL;
133.     
134.     SQL = new C_DB_SQL(user01);
135.     
136.     if (user02 == eType_00) szMsg = ExecScripts();
137.     Print(szMsg == NULL ? "Result of executing the SQL script: Success" : szMsg);
138.     
139.     if (szMsg == NULL) switch (user02)
140.     {
141.         case eType_01: SELECT_Type_01();    break;
142.         case eType_02:    SELECT_Type_02();    break;
143.         case eType_03: SELECT_Type_03(); break;
144.         case eType_04: SELECT_Type_04(); break;
145.         case eType_05: SELECT_Type_05(); break;
146.     }
147. 
148.     delete SQL;
149. }
150. //+------------------------------------------------------------------+

Código Principal em MQL5

Veja que tudo que foi explicado se encontra no código. Seguindo a mesma numeração das linhas. A única coisa, um pouco diferente, do código visto em artigos anteriores, é justamente a linha 139. Onde com base na variável declarada na linha 15, fazemos a seleção de qual será o tipo. Ou melhor, o fragmento que será chamado. Isto para que você tenha a mesma visão que foi feita em cada uma das animações.


Considerações finais

Neste artigo, mostrei como a função de leitura dos dados reportados por um comando ao SQL, podem ser lidos, quando fazemos uso do SQLite presente no MetaTrader 5. Se bem, que como você pode ter notado, durante a leitura do artigo, podemos melhorar ainda mais a nossa classe C_DB_SQL. Isto ainda focados em trabalhar com o mesmo SQLite do MetaTrader 5. As principais melhorias que podemos fazer são:

  • Permitir que quando fizemos um requerimento com um número inferior de colunas, possamos ter o preenchimento das colunas que fazem parte do código MQL5. Isto evitaria o desagradável resultado de não termos nenhuma informação sendo retornada pela chamada GetRegisterOfRequest.
  • Outra melhoria, seria em caso de uma pesquisa, cujo resultado, apenas e somente um único registro nos é de interesse. Como o que ocorreu no fragmento SELECT_Type_05. Pudéssemos não precisar declarar a variável de retorno dentro de uma estrutura. Podendo assim usar ela diretamente como uma declaração padrão e solta.

Apesar de tais melhorias serem de fato interessantes de serem implementadas. Não sei ainda ao certo se de fato mostrarei como fazer tal coisa. Já que durante a programação a aplicação em MQL5, podemos fazer as coisas de uma forma um pouco mais criteriosa. De qualquer forma, se ficar decidido que mostraremos como fazer tais modificações. A fim se dar um melhor suporte aos casos indicados nos fragmentos. Tal coisa será feita no próximo artigo. Mas independentemente disto, não deixe de ver o próximo artigo desta série. Pois a coisa fica mais interessante a cada dia que se passa.

ArquivoDescriçã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.mq5Cria a janela para configuração da ordem a ser enviada (É necessário o Mouse Study para interação)
Indicators\Market Replay.mq5Cria os controles para interação com o serviço de replay/simulador (É necessário o Mouse Study para interação)
Indicators\Mouse Study.mq5Permite 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.mq5Cria e mantém o serviço de replay e simulação de mercado (Arquivo principal de todo o sistema)
Code VS C++\Servidor.cppCria e mantém um soquete servidor criado em C++ ( Versão Mini Chat )
Code in Python\Server.pyCria e mantém um soquete em python para comunicação entre o MetaTrader 5 e o Excel
Indicators\Mini Chat.mq5Permite implementar um mini chat via indicador (Necessário uso de um servidor para funcionar)
Experts\Mini Chat.mq5Permite implementar um mini chat via Expert Advisor (Necessário uso de um servidor para funcionar)
Scripts\SQLite.mq5Demonstra uso de script SQL por meio do MQL5
Files\Script 01.sqlDemonstra a criação de uma tabela simples, com chave estrangeira
Files\Script 02.sqlDemonstra a adição de valores em uma tabela
Arquivos anexados |
Anexo.zip (571.71 KB)
Do básico ao intermediário: Eventos em Objetos (II) Do básico ao intermediário: Eventos em Objetos (II)
Neste artigo iremos ver como funciona os três últimos tipos de eventos que podem ser disparados por um objeto. Entender isto será algo muito divertido. Já que no final faremos algo que para muitos pode parecer um tanto quanto insanidade. Porém que é perfeitamente possível de ser feito, e tem um resultado bastante surpreendente.
Análise pós-fato da negociação: ajustando TrailingStop e novos stops no testador de estratégias Análise pós-fato da negociação: ajustando TrailingStop e novos stops no testador de estratégias
Seguimos com o tema da análise de negociações realizadas no testador de estratégias para melhorar a qualidade da negociação. Vamos verificar como o uso de diferentes métodos de trailing pode alterar os resultados de negociação já obtidos.
Algoritmo evolutivo de trading com aprendizado por reforço e extinção de estratégias não lucrativas (ETARE) Algoritmo evolutivo de trading com aprendizado por reforço e extinção de estratégias não lucrativas (ETARE)
Apresentamos um algoritmo de trading inovador que combina algoritmos evolutivos com aprendizado profundo por reforço para operar no mercado Forex. O algoritmo utiliza um mecanismo de extinção das estratégias ineficientes, com o objetivo de otimizar a estratégia de negociação.
Redes neurais em trading: Aprendizado dependente de contexto com memória (Conclusão) Redes neurais em trading: Aprendizado dependente de contexto com memória (Conclusão)
Estamos finalizando a implementação do framework MacroHFT para trading de alta frequência com criptomoedas, que utiliza aprendizado por reforço dependente de contexto e memória para se adaptar às condições dinâmicas do mercado. E para concluir este artigo, será realizado um teste com os métodos implementados utilizando dados históricos reais, a fim de avaliar sua eficácia.