Interação entre o MetaTrader 5 e MATLAB

Andrey Emelyanov | 31 dezembro, 2013

Introdução

Meu primeiro artigo Interação entre o MetaTrader 4 e máquina MATLAB (Máquina virtual MATLAB) foi observado pela comunidade MQL. Alguns leitores (1Q2W3E4R5T) foram inclusive capazes de mover esse projeto do Borland para VS2008. Mas o tempo voa com crueldade e (triste mas verdadeiro) o MetaTrader 4 está desaparecendo, dando espaço ao seu sucessor MetaTrader 5 com MQL5, que introduziu ponteiros e memória dinâmica.

Graças à essas inovações, temos a oportunidade de escrever uma biblioteca universal de interação com a máquina virtual MATLAB e unir as bibliotecas geradas pelo MATLAB diretamente com o MetaTrader 5. Este artigo abrange tais funcionalidades. Este artigo continua logicamente o anterior e abrange de maneira mais ampla o problema de interação entre o MetaTrader 5 e o MATLAB.

Para fazer a finalidade deste artigo mais compreensível à leitores despreparados o dividiremos em três partes: teoria, referência e prática. A teoria abrangerá os tipos de dados usados no MQL5 e MATLAB assim como a sua conversão mútua. Em referência aprenderemos as estruturas linguísticas e sintaxe das funções, necessárias para criar um DLL. E em prática analisaremos as "armadilhas" desta interação.

Leitores experientes podem pular a teoria e referência e começar pela prática. Outros são encorajados a ler a teoria e referência e apenas depois disso continuar com a prática. Vale a pena também ler livros mencionados na seção "Literatura".

1. Teoria

1.1 Tipos de dados no MATLAB e MQL5

1.1.1 Tipos de dados simples

Vamos continuar.

Primeiramente precisamos nos familiarizar com os mundos internos do MQL5 e METALAB. Depois de uma inspeção superficial de tipos de variáveis concluímos que elas são quase idênticas:

MQL5
Tamanho em bytes
Valor mínimo
Valor máximo
MATLAB
char
1
-128
127
Banco de dados int8/char
uchar
1
0
255
Banco de dados int8/char
bool
1
0(falso)
1(verdadeiro)
Lógica do banco de dados
curto
2
-32768
32767
Banco de dados int16
não curto
2
0
65535
Banco de dados int16
int
4
-2147483648
2147483647
Banco de dados int32
uint
4
0
4294967295
Banco de dados int32
longo 8
-9223372036854775808
9223372036854775807 Banco de dados int64
não longo 8
0
18446744073709551615
Banco de dados int64
flutuação 4
1.175494351e-38
3.402823466e+38
Banco de dados simples
duplo
8
2.225073858507201e-308
1.7976931348623158e+308
Banco de dados duplo

Tabela 1. Tipos de dados no MATLAB e MQL5

Há uma diferença principal: as variáveis no MQL5 podem ser simples ou compostas (complexas) e no MATLAB todas as variáveis são multidimensionais (complexas) - exemplo matriz. Você deve sempre lembrar desta diferença!

1.1.2 Tipos de dados complexos

No MQL5 existem 4 tipos de dados complexos: bancos de dados, séries e classes. Tipos de dados complexos são um conjunto de vários tipos de dados simples, combinados no bloco de memória de certo comprimento. Quando trabalhar com tais dados você deve sempre saber ou o tamanho do bloco de memória em bytes ou o número de elementos (exceto as classes). Temos interesse somente em bancos de dados e séries, pois submeter classes e estruturas MQL5 no MATLAB não faz sentido.

Quando passar bancos de dados de qualquer tipo você precisa saber: tipo (dimensão) e número de elementos usando a função ArraySize(). Deve ser dada uma atenção especial na indexação do MetaTrader 5 - normalmente ele está de trás pra frente (exemplo, o primeiro elemento contém dados mais recentes que o seguinte). Verifique isto usando a função ArrayIsSeries(). E o MATLAB tem a seguinte indexação: o primeiro elemento contém os dados mais antigos que o seguinte - então você deve "inverter" os seus bancos de dados antes de mandá-los para o MATLAB, se a flag AS_SERIES = VERDADEIRO. Baseado no acima, concordamos com o seguinte:

Mas esta não é a única limitação quando trabalhar com bancos de dados. Condo você trabalha com bancos de dados multidimensionais ou matrizes para ser mais específico, especialmente a partir do MATLAB, introduzimos a restrição para não mais de 2 bancos de dados dimensionais. Aqui a flag AS_SERIES não pode ser VERDADEIRA e então tais bancos de dados não serão "invertidos".

Não esqueça que as séries no MQL5 não são bancos de dados com elementos do tipo char. Então quando passar séries encontrará um pequeno problema: no MQL5 as séries são codificadas usando Unicode e o MATLAB usa codificação ANSI. Então antes de passar uma série, ela deve ser convertida em banco de dados de caracteres ANSI usando a função StringToCharArray(). E vice-versa, quando você tiver um banco de dados de caracteres do MATLAB, converta-o usando a função CharArrayToString(). Para evitar confusão, concorde: grave todas as séries em programas MQL5 usando Unicode, sem bancos de dados do tipo char.

1.2 Comparação dos tipos de dados do MQL5 e MATLAB

Para reduzir a quantidade de funções e simplificar o algoritmo de linguagem, reduziremos a quantidade de tipos por meio de conversão automática, que não deve afetar a integridade dos dados. A tabela a seguir ilustra a regra de conversão de tipos de dados do MQL5 para o MATLAB:

MQL5
Equivalente do MATLAB
char
uchar
Banco de dados char
bool
Lógica do banco de dados
short
ushort
int
uint
Banco de dados int32
long
ulong
Banco de dados int64*
float
double
Banco de dados duplo
série
Banco de dados char, usando as funções StringToCharArray() <=> CharArrayToString().

* Com este tipo de conversão há uma perda de precisão. Nós não a usaremos mas você pode usar tal conversão em seus programas.

Tabela 2. Comparação dos tipos de dados do MQL5 e MATLAB

Agora você já está familiarizado com os tipos de dados usados no MQL5 e MATLAB. Você sabe quais "armadilhas" o esperam na passagem de dados e como contorná-las completamente. Você ainda precisa conhecer a máquina MATLAB API e se familiarizar com o Compilador 4 MATLAB.

2. Referência da máquina MATLAB API, referência do Compilador 4 MATLAB e referência da biblioteca de entrada/saída do C++

Esta seção introduz a você as funções mais importantes da máquina MATLAB API, recursos do compilador 4 MATLAB e número de funções úteis da biblioteca de entrada/saída padrão do C++. então, vamos começar.

2.1 Funções da máquina MATLAB API e MCR

Máquina MATLAB - é uma interface externa que aciona outros programas para usar a área de trabalho do MATLAB. Ela fornece um trabalho completamente funcional de todos os pacotes MATLAB sem nenhuma restrição.

Embora não tenha sido dito na documentação, mas em termos de programador de sistema - é apenas uma máquina virtual assim como PHP, MySQL, etc. Que suporta uma forma simples mas relativamente rápida de trocas de dados entre o MetaTrader4/5 e o MATLAB.

Este método de conectar programas externos com o pacote do MATLAB é recomendado pelos desenvolvedores. a interface consiste de seis funções:

Engine *pEng = engOpen(NULL) — esta função chama a área de trabalho do MATLAB, o parâmetro é sempre NULL e retorna um ponteiro para o descritor da área de trabalho.

int exitCode = engClose(Engine *pEng) — esta função fecha a área de trabalho e retorna o número de usuários restantes da área de trabalho do MATLAB, onde:

mxArray *mxVector = mxCreateDoubleMatrix(int m, int n, int ComplexFlag) — esta função cria uma variável (matriz) da área de trabalho do MATLAB e retorna um ponteiro para a variável (matriz) onde:

void = mxDestroyArray(mxArray *mxVector) — esta função destrói a matriz do MATLAB, ela é necessária para limpar a memória onde:
int = engPutVariable(Engine *pEng, char *Name, mxArray *mxVector) — esta função envia a variável para a área de trabalho. Você só deve criar variáveis do tipo mxArray, mas também mandá-las para o MATLAB onde:
mxArray *mxVector = engGetVariable(Engine *pEng, char *Name) — esta função recebe a variável da área de trabalho - o inverso da função anterior. Somente variáveis do tipo mxArray são aceitas onde:
double *p = mxGetPr(mxArray *mxVector) — esta função recebe o ponteiro para os valores dos bancos de dados, ela é usada para copiar os dados juntamente com memcpy() (consulte a biblioteca de entrada/saída padrão do C++ 2.3) onde:
int = engEvalString(Engine *pEng, char *Command) — esta função envia comandos para a área de trabalho do MATLAB onde:

Você provavelmente notou que a máquina MATLAB API permite que você crie uma estrutura mxArray somente do tipo duplo. Mas esta restrição não afeta as suas possibilidades, mas afetará o algoritmo de sua biblioteca.

MCR (instância MCR) — é a biblioteca especial do pacote MATLAB que aciona a execução de aplicações/bibliotecas públicas em modo independente gerados pelo ambiente MATLAB em qualquer computador. Note que mesmo que você tenha um pacote completo do MATLAB, você ainda precisa instalar a biblioteca MCR executando o arquivo MCRInstaller.ex, localizado na pasta \Toolbox\compiler\deploy\win32. Então, antes de qualquer função de biblioteca pública criada pelo ambiente MATLAB, você precisa chamar a função de inicialização do MCR.

bool = mclInitializeApplication(const char **option, int count) – retorna VERDADEIRO se a inicialização do MCR for bem sucedida, ou então FALSO se:

No final do trabalho da biblioteca pública você deve chamar:
bool = mclTerminateApplication(void) — retorna VERDADEIRO se o MCR foi fechado com sucesso.

2.2 Compilador MATLAB 4

O Compilador MATLAB permite que você crie o seguinte a partir das funções M:

O Compilador suporta a maioria dos comandos e pacotes do MATLAB, mas não todos. Uma lista completa de restrições pode ser encontrada no website do MATLAB. Este método permite que você crie um "pacote independente de software" do MetaTrader 5 e MATLAB mas encontraste com a máquina MATLAB, requer um programador bem treinado e conhecimento profundo de compilação.

O Compilador MATLAB requer pelo menos um dos seguintes compiladores C/C++:

O compilador MATLAB 4 em contraste com os seus predecessores, gera somente o código de interface (empacotador), exemplo não traduz as funções M em binário ou código C/C++ mas ele cria um arquivo especial baseado na tecnologia do Arquivo de tecnologia de componente (CTF) que inclui integrações de vários pacotes requeridos para o suporte das funções M. O Compilador MATLAB também encripta este arquivo com uma chave exclusiva de 1024 bits (não repetida).

Agora vamos considerar o algoritmo do trabalho do Compilador MATLAB 4, já que a ignorância deste tópico levará a muitos erros estúpidos no momento da compilação:

  1. Análise de dependências — neste estágio determine todas as funções, arquivos MEX e arquivos P nos quais a compilação das funções M dependem.
  2. Criação do arquivo - o arquivo CTF é criado, ele é encriptado e comprimido.
  3. Geração do código de objeto do empacotador - neste estágio todos os códigos fonte são criados, necessários para o componente:
    • Código da interface C/C++ para as funções M especificados na linha de comando (NameFile_main.c).
    • Arquivo de componente (NameFile_component.dat), que contém todas as informações necessárias para executar o código M (incluindo as chaves de criptografia e caminhos, gravados no arquivo CTF).
  4. Tradução C/C++. Neste estágio os arquivos de código fonte C/C++ são compilados em arquivos de objetos.
  5. Ligação. O estágio final da construção do projeto.

Agora, quando você estiver familiarizado com o comportamento do algoritmo do Compilador MATLAB você tem que aprender mais sobre as chaves para ter um plano de ações detalhado quando usar o compilador (mcc).

Chave
Propósito
um nome de arquivo
A adição do arquivo ao arquivo, determina quais arquivos serão adicionados ao arquivo CTF
l
Macro, que gera uma biblioteca de funções
N
Limpa todos os caminhos, exceto o conjunto mínimo necessário de diretórios.
p
Adicionar um caminho de tradução de acordo com o procedimento. Necessita a chave -N
R -nojvm
Cancela a opção MCR (Tempo de execução do componente MATLAB, consulte ajuda do MATLAB)
W
Gerencia a criação dos empacotadores de função
lib
Cria as funções de inicialização e finalização
main
Cria o envelope POSIX da função main()
T
Especifica o estágio de saída
codegen
Cria o código empacotador para a aplicação independente
compile:exe
O mesmo da codegen
compile:lib
Cria o código empacotador para DLL público
link:exe
O mesmo da compile:exe mais a ligação
link:lib
O mesmo da compile:exe mais a ligação

Tabela 3. Chaves do Compilador mcc Matlab (versão 4)

A tabela 3 contem as chaves básicas que podem ser úteis na solução dos problemas típicos. Para mais ajuda, use os comandos help mcc ou doc mcc do MATLAB.

Temos que nos familiarizar com o ligador do MATLAB, abaixo estão as chaves principais (mbuild):

Chave
Propósito
-setup
No modo interativo, definição do arquivo de opções do compilador para o uso como padrão nas chamadas futuras do mbuild
-g
Cria o programa com a informação de depuração. Anexo do DEBUGFLAGS no final do arquivo.
-O
Otimização do código de objeto

Tabela 4. Chaves do ligador mbuild Matlab (versão 4)

A Tabela 4 lista as chaves principais. Para mais informações use os comandos help mbuild ou doc mbuild.

2.3 Biblioteca de entrada/saída padrão

O uso da biblioteca de entrada/saída padrão fornece a cópia correta dos dados. O seu uso evitará que você cometa erros "estúpidos" que surgem durante a fase de projeto do programa (por exemplo: muitos programadores iniciantes copiam somente o ponteiro no bloco de memória em vez de copiar o bloco de memória inteiro). Da biblioteca de entrada/saída inteira estamos interessados em somente uma função:

void *pIn = memcpy(void *pIn, void *pOut, int nSizeByte) – esta função copia (clona) a variável/banco de dados de pOut para pIn com tamanho de nSizeByte bytes, onde:

3. Prática

Agora terminamos com a teoria e podemos continuar com a realização da interação do MetaTrader 5 e MATLAB.

Conforme você provavelmente imaginou, isto será feito de duas maneiras: usando a máquina virtual MATLAB e usando as bibliotecas geradas pelo Compilador MATLAB. Primeiramente, considere uma maneira simples, rápida e versátil de interação — através da máquina MATLAB.

Esta parte do artigo deve ser lida do início ao fim, pois, não importando a aparente diferença entre os métodos de interação, eles têm uma filosofia e sintaxe familiar de construção de linguagem, e aprender algo novo é mais fácil com exemplos fáceis.

3.1 Desenvolvimento da biblioteca universal da interação entre o MetaTrader 5 e a máquina MATLAB

Este método de interação não pode ser chamado de elegante e rápido mas é o mais confiável e cobre o pacote MATLAB inteiro. é claro, devemos mencionar a velocidade do desenvolvimento do modelo final. A essência do desenvolvimento é escrever um empacotador de biblioteca universal para a interação do MetaTrader 4/5 e máquina MATLAB. Depois disso o MetaTrader 4/8 série/indicador/expert pode gerenciar a área de trabalho virtual do MATLAB. E o algoritmo matemático inteiro pode ser gravado no programa MQL como séries, então você pode usá-lo para proteger a sua propriedade intelectual (para mais detalhes consulte o artigo "Protejam-se, Programadores!"). Ele também pode ser gravado em arquivos separados das funções M ou funções P na pasta \MQL5\Libraries.

Possíveis áreas de aplicação de tal interação:

Vamos continuar. Eu espero que você tenha lido as seções 1.1 Tipos de dados no MATLAB e MQL5, 1.2 Comparação dos tipos de dados do MQL5 e MATLAB, 2.1 Máquina MATLAB API e funções MCR e 2.3 Biblioteca de entrada/saída padrão C++, pois não iremos parar e analisá-las novamente. Leia cuidadosamente o esquema de bloco a seguir que ilustra o algoritmo da biblioteca futura:

Figura 1. Esquema de bloco do algoritmo da biblioteca

Figura 1. Esquema de bloco do algoritmo da biblioteca

Como visto na Figura 1, a biblioteca consiste e três blocos principais. Considere os seus propósitos:

Agora, vamos lidar com algoritmos. Começaremos com o bloco MQL5. O leitor atencioso já notou que isso focará na implementação do que está escrito na seção Tipos de dados no MATLAB e MQL5. Se você a perdeu você dificilmente entenderá porque tudo isso é necessário.

O algoritmo das funções mlInput é quase idêntico. Vamos discutir o seu trabalho usando a função mlInputDouble() que fornece a inserção de variáveis do tipo duplo para a máquina virtual MATLAB.

Aqui está o protótipo:

bool mlInputDouble(double &array[],int sizeArray, string NameArray), onde:

Algoritmo:

  1. Converta a série NameArray em banco de dados char usando a função StringToCharArray().
  2. Verifique o tipo de indexação usando a função ArrayIsSeries(). Se o tipo de indexação for normal — passe o valor para a função mlxInputDouble().
    Indexação ELSE do banco de dados timeseries:
    "Inverta" o banco de dados e passe o valor para a função mlxInputDouble().
  3. Termine a função, passe o valor retornado para a função mlxInputDouble().

O algoritmo da função mlGet é também quase idêntico. Vamos discutir o seu trabalho usando a função mlGetDouble() que fornece a inserção de variáveis do tipo duplo a partir da máquina virtual MATLAB.

O protótipo:

int mlGetDouble(double &array[],int sizeArray, string NameArray), onde:

Algoritmo:

  1. Converta a série NameArray em banco de dados char usando a função StringToCharArray().
  2. Encontre o tamanho do banco de dados usando a função mlxGetSizeOfName().
    • SE o tamanho for MAIOR QUE ZERO, atribua o banco de dados recipiente de tamanho necessário usando a função ArrayResize(), pegue os dados da mlxGetDouble(), retorne o tamanho do banco de dados.
    • SE o tamanho for ZERO, retorne o erro, exemplo valor nulo.

é isso! As funções mlGetInt() e mlGetLogical() produzem uma com versão de "sombra" dos tipos double ->; int/bool. Para esse propósito essas funções criam um buffer de memória temporária em seus corpos. Esta é uma medida forçada, pois infelizmente o MATLAB API não permite criar estruturas mxArray para tipos de dados diferentes de double. Entretanto, isto não significa que o MATLAB opera exclusivamente com os tipos double.

O bloco C++ é muito mais fácil - ele deve fornecer a tradução dos dados do tipo double para a estrutura mxArray. Isto é feito usando as funções mxCreateDoubleMatrix(), mxGetPr() e memcpy(). Então, usando a função engPutVariable() ela passa dados pela máquina virtual MATLAB e para extrair os dados ela usa a função engGetVariable(). Novamente, preste atenção nas funções com prefixos Int e Logical — como visto no esquema de bloco, elas não interagem diretamente com o MATLAB, mas usam as funções mlxInputDouble/mlxGetDouble e mlxInputChar(). O algoritmo com o seu comportamento é simples: chamada da função mlxInputDouble/mlxGetDouble — valores de entrada/saída como double(!) e envie o comando "sombra" do MATLAB para converter os tipos de dados através da função mlxInputChar().

O bloco da máquina MATLAB é ainda mais fácil. Ele fornece somente funções matemáticas. O seu comportamento depende dos seus comandos e suas funções m/p.

Agora, quando todos os "detalhes" do projeto estiverem esclarecidos, é hora de lidar com a construção do projeto.

Qualquer construção começa com a criação da biblioteca principal — em nosso caso é o bloco C/C++. Para esse propósito, em qualquer editor de texto ANSI (Notepad, Bred, etc.) crie um arquivo com a extensão DEF. é desejável que o nome deste arquivo consiste de caracteres Latinos sem espaços e pontuações, de outra forma você "ouvirá" muitas "palavras" de elogio de seu compilador... Este arquivo fornece a permanência das suas funções. Se este arquivo estiver ausente,o compilador C/C++ inventará os seus próprios "nomes exóticos" para funções de exortação.

Este arquivo contém: LIBRARY — palavra de controle, LibMlEngine — nome da biblioteca, e EXPORTS — segunda palavra de controle, então aparecem os nomes das funções. Como você provavelmente sabia, os nomes das funções de exportação não podem ter espaços e pontuação. Aqui está o texto do arquivo DllUnit.def do arquivo MATLABEngine.zip:

LIBRARY LibMlEngine
EXPORTS
mlxClose
mlxInputChar
mlxInputDouble
mlxInputInt
mlxInputLogical
mlxGetDouble
mlxGetInt
mlxGetLogical
mlxGetSizeOfName
mlxOpen

Então temos o primeiro arquivo do projeto. Agora abra o Windows Explorer e vá para a pasta '\Extern\include'. Copie o arquivo engine.h (arquivo cabeçalho da máquina virtual MATLAB) para a pasta, onde o seu projeto for construído (se você não fizer isso, você terá que especificar manualmente o caminho para o arquivo no estágio de compilação).

Agora é hora de criar o bloco C++. Nós não incluiremos o código fonte inteiro do programa no artigo, pois esse arquivo pode ser encontrado em MATLABEngine.zip assim como em DllUnit.cpp e é bem comentado. Note que é melhor criar funções usando a convenção __stdcall — exemplo os parâmetros passam pela pilha e a função limpa a pilha. Este padrão é "nativo" no API do Win32/64.

Considere como declarar uma função:

extern "C" __declspec(dllexport) __stdcall Function( )

  1. extern "C" __declspec(dllexport) — diz ao compilador C++ que a função é externa.
  2. — tipo da variável retornada, pode ser: void, bool, int, double, tipos compostos (conhecidas não somente para Dll, mas também para a chamada do programa) e ponteiros.
  3. __stdcall — declaração sobre a passagem de parâmetros para a função e volta, é padrão para o API Win32/64.
  4. Funcion — seu nome de função.
  5. — tipo e nome da variável de entrada, o número máximo de variáveis é 64.

Este tópico é coberto em detalhes no artigo Como trocar dados: Um DLL para o MQL5 em 10 minutos.

Construção de bloco C/C++: para isso você precisa incluir a biblioteca de entrada/saída padrão e adicionar ao projeto os seguintes arquivos (em seu compilador: Project->Add Project):

  1. DllUnit.def
  2. Na pasta \Extern\lib\\\, onde:
    — Pasta principal do MATLAB.
    — tanto a pasta win32 para SO 32-bit, ou win64 para SO 64-bit.
    — a pasta "borland" para a versão do Borland C/C++. 5-6, a pasta "microsoft" para o Microsoft Visual C++:
    • libeng.lib
    • libmx.lib

Uma pergunta comum como esta pode surgir: "Eu tenho uma versão diferente de compilador ou nenhum compilador na lista! (Muito raramente não existirão tais arquivos)". Vamos ver como criar manualmente uma biblioteca pública. Consideraremos como é feito no Visual C++ e no Borland C++:

  1. Em FAR para a pasta \Bin\, onde:
    — Pasta principal do MATLAB.
    — tanto a pasta win32 para SO 32-bit, ou win64 para SO 64-bit.
  2. Para o Borland C++ insira: implib libeng.lib libeng.dll. O mesmo para libmx.dll.
  3. Para o Visual C++ insira: lib libeng.dll. O mesmo para libmx.dll.
  4. Se outro compilador: qualquer compilador de qualquer linguagem de programação deve ter este utilitário - Gerenciador de biblioteca, normalmente este é um programa de console \bin\*lib*.exe.

Aliás, eu esqueci de avisá-lo - não tente criar uma LIB 64-bit para um compilador 32-bit. Primeiramente descubra se há um suporte de endereçamento de 64-bit na ajuda do compilador. Se não, ou procure pelo DLL MATLAB 32-bit ou escolha outro compilador C/C++. Dando atenção à compilação, depois que conseguimos uma biblioteca, que deve ser colocada na pasta terminal_folder\MQL5\Libraries.

Agora começamos com o bloco MQL. Execute o MetaEditor, clique em "Novo" e faça como segue nas figuras:

Figura 2. Assistente MQL5: Criar biblioteca

Figura 2. Assistente MQL5: Criar biblioteca

Figura 3. Assistente MQL5: Propriedades gerais da biblioteca

Figura 3. Assistente MQL5: Propriedades gerais da biblioteca

Agora, quando o assistente do MQL5 criou um modelo, continue com a sua edição:

1. Descreva importar funções:

//+------------------------------------------------------------------+
//| DECLARATION OF IMPORTED FUNCTIONS                                 |
//+------------------------------------------------------------------+
#import "LibMlEngine.dll"
void   mlxClose(void);                        //void – means: don't pass any parameters!
bool   mlxOpen(void);                         //void – means: don't pass and don't receive any parameters!
bool   mlxInputChar(char &CharArray[]);       //char& CharArray[] – means: pass a reference!
bool   mlxInputDouble(double &dArray[],
                      int sizeArray,
                      char &CharNameArray[]);
bool   mlxInputInt(double &dArray[],
                   int sizeArray,
                   char &CharNameArray[]);
bool   mlxInputLogical(double &dArray[],
                       int sizeArray,
                       char &CharNameArray[]);
int    mlxGetDouble(double &dArray[],
                    int sizeArray,
                    char &CharNameArray[]);
int    mlxGetInt(double &dArray[],
                 int sizeArray,
                 char &CharNameArray[]);
int    mlxGetLogical(double &dArray[],
                     int sizeArray,
                     char &CharNameArray[]);
int    mlxGetSizeOfName(char &CharNameArray[]);
#import    

Note que no MQL5 você pode passar os "ponteiros" de duas formas:

  • void NameArray[] ; // Este método de passagem pelo banco de dados permite somente dados de leitura. Entretanto, se você tentar usar esta referência para "editar o seu conteúdo", você receberá um erro de acesso de memória (no melhor caso para você, o MetaTrader 5 cuidará silenciosamente do erro na estrutura SEH mas NÃO ESCREVEMOS uma estrutura SEH, então podemos até perder a razão do erro).
  • void& NameArray[] ; // Este método de passagem permite que você leia e edite o conteúdo dos bancos de dados mas você deve manter o tamanho do banco de dados.

Se a função não aceitar ou não passar os parâmetros, sempre especifique o tipo void.

2. Nós não descrevemos todas as funções do bloco MQL, pois você pode achar o código fonte MatlabEngine.mq5 no MATLABEngine.zip.

Assim, consideraremos os detalhes da declaração e definição das funções externas no MQL5.

bool mlInputChar(string array)export
{
//... body of function
}

Como visto no exemplo, a declaração e definição da função são combinadas. Neste caso, declaramos uma função chamada mlInputChar() como externa (exportação), que retorna o valor do tipo bool e aceita a série do banco de dados como parâmetro. Agora compile...

Agora que completamos o último bloco da biblioteca e o compilamos, é hora de testá-lo em condições reais.

Para fazer isso escreva um script de teste simples (ou pegue-o do arquivo MATLABEngine.zip: TestMLEngine.mq5).

O código do Script é simples e bem comentado:

#property copyright "2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com/pt"
#property version   "1.00"
#import "MatlabEngine.ex5"
bool mlOpen(void);
void mlClose(void);
bool mlInputChar(string array);
bool mlInputDouble(double &array[],
                   int sizeArray,
                   string NameArray);
bool mlInputInt(int &array[],
                int sizeArray,
                string NameArray);
int mlGetDouble(double &array[],
                string NameArray);
int mlGetInt(int &array[],
             string NameArray);
bool mlInputLogical(bool &array[],
                    int sizeArray,
                    string NameArray);
int mlGetLogical(bool &array[],
                 string NameArray);
int mlGetSizeOfName(string strName);
#import
void OnStart()
  {
// Dynamic buffers for MATLAB output
   double dTestOut[];
   int    nTestOut[];
   bool   bTestOut[];
// Variables for MATLAB input
   double dTestIn[] = {   1,     2,    3,     4};
   int    nTestIn[] = {   9,    10,   11,    12};
   bool   bTestIn[] = {true, false, true, false};
   int nSize=0;
// Variables names and command line
   string strComm="clc; clear all;"; // command line - clear screen and variables
   string strA     = "A";            // variable A
   string strB     = "B";            // variable B
   string strC     = "C";            // variable C
/*
   ** 1. RUNNING DLL
   */
   if(mlOpen()==true)
     {
      printf("MATLAB has been loaded");
     }
   else
     {
      printf("Matlab ERROR! Load error.");
      mlClose();
      return;
     }
/*
   ** 2. PASSING THE COMMAND LINE
   */
   if(mlInputChar(strComm)==true)
     {
      printf("Command line has been passed into MATLAB");
     }
   else printf("ERROR! Passing the command line error");
/*
   ** 3. PASSING VARIABLE OF THE DOUBLE TYPE
   */
   if(mlInputDouble(dTestIn,ArraySize(dTestIn),strA)==true)
     {
      printf("Variable of the double type has been passed into MATLAB");
     }
   else printf("ERROR! When passing string of the double type");
/*
   ** 4. GETTING VARIABLE OF THE DOUBLE TYPE
   */
   if((nSize=mlGetDouble(dTestOut,strA))>0)
     {
      int ind=0;
      printf("Variable A of the double type has been got into MATLAB, with size = %i",nSize);
      for(ind=0; ind<nsize; ind++)="" {="" printf("A = %g",dTestOut[ind]);
        }
     }
   else printf("ERROR! Variable of the double type double hasn't ben got");
/*
   ** 5. PASSING VARIABLE OF THE INT TYPE
   */
   if(mlInputInt(nTestIn,ArraySize(nTestIn),strB)==true)
     {
      printf("Variable of the int type has been passed into MATLAB");
     }
   else printf("ERROR! When passing string of the int type");
/*
   ** 6. GETTING VARIABLE OF THE INT TYPE
   */
   if((nSize=mlGetInt(nTestOut,strB))>0)
     {
      int ind=0;
      printf("Variable B of the int type has been got into MATLAB, with size = %i",nSize);
      for(ind=0; ind<nsize; ind++)="" {="" printf("B = %i",nTestOut[ind]);
        }
     }
   else printf("ERROR! Variable of the int type double hasn't ben got");
/*
   ** 7. PASSING VARIABLE OF THE BOOL TYPE
   */
   if(mlInputLogical(bTestIn,ArraySize(bTestIn),strC)==true)
     {
      printf("Variable of the bool type has been passed into MATLAB");
     }
   else printf("ERROR! When passing string of the bool type");
/*
   ** 8. GETTING VARIABLE OF THE BOOL TYPE
   */
   if((nSize=mlGetLogical(bTestOut,strC))>0)
     {
      int ind=0;
      printf("Variable C of the bool type has been got into MATLAB, with size = %i",nSize);
      for(ind=0; ind<nsize; ind++)="" {="" printf("C = %i",bTestOut[ind]);
        }
     }
   else printf("ERROR! Variable of the bool type double hasn't ben got");
/*
   ** 9. ENDING WORK
   */
   mlClose();
  }

Como visto no script, estamos inserindo valores e então recebendo valores. Entretanto, em contraste com o MetaTrader 4, onde precisávamos saber o tamanho do buffer no estágio de projeto, no MetaTrader 5 não é necessário, pois usamos os buffers dinâmicos.

Agora que você finalmente entendeu a máquina virtual MATLAB, você pode começar a usar a construção de DLL no ambiente MATLAB.

3.2 Normas técnicas de construção/uso de DLL gerado pelo Compilador MATLAB 4

Na seção anterior você aprendeu a criar uma biblioteca para interação universal com o pacote MATLAB. Entretanto, este método tem uma desvantagem - ele requer o pacote MATLAB de usuário final. Esta restrição cria um número de dificuldades na distribuição do produto de software acabado. Por isso que o pacote matemático do MATLAB tem um compilador incorporado, que permite que você crie "aplicações independentes" que não dependem do pacote MATLAB. Vamos dar uma olhada nele.

Por exemplo, considere um indicador simples - média de movimento (SMA). Atualize-o levemente adicionando um Filtro de rede neural (GRNN), que permite suavizar "ruído branco" (explosões aleatórias). Nomeie o novo indicador como NeoSMA e o filtro como GRNNFilter.

Então temos duas funções M, as quais queremos criar um DLL que pode ser chamado pelo MetaTrader 5.

Agora lembre-se que o MetaTrader 5 procura por DLLs nas seguintes pastas:

Então, coloque em um desses diretórios duas funções M (NeoSMA.m e GRNNFilter.m) onde construiremos o DLL. Eu chamei sua atenção para este fato de colocação pois isto não é feito por acidente. O leitor atento já conhece o recurso do compilador MATLAB - ele preserva os caminhos quando está compilando (consulte "2.2 Compilador MATLAB 4").

Antes de você começar a compilar o projeto, você deve configurar o compilador. Para fazer isso, siga esses passos:

  1. Na linha de comando do MATLAB insira: mbuild -setup;
  2. Pressione 'y' para confirmar a procura de compiladores C/C++ compatíveis instalados no seu sistema;
  3. Escolha o compilador padrão Lcc-win32 C;
  4. Pressione 'y' para confirmar o compilador selecionado.

Figura 4. Compilando o projeto

Figura 4. Compilando o projeto


Agora estamos prontos para mover para o processo de compilação das funções M.

Para isso insira:

mcc -N -W lib:NeoSMA -T link:lib NeoSMA.m GRNNFilter.m

Explique as chaves:

-N — omitir todos os caminhos desnecessários
-W lib:NeoSMA — diz ao compilador que NeoSMA é o nome da biblioteca
-T link:lib — diz ao compilador para criar uma biblioteca pública com ligação
NeoSMA.m e GRNNFilter.m — nomes das funções M

Agora vamos ver o que o compilador criou:

Então vamos manusear o DLL, precisamente com a sua estrutura interna. ele consiste de (somente funções básicas):

  1. Função principal de qualquer DLL - BOOL WINAPI (DllMain(), que (de acordo com a especificação da Microsoft) manipula eventos que ocorrem no DLL: Carregamento de DLL no espaço de endereçamento do processo, criando um fluxo novo, excluindo o fluxo e descarregando Dll da memória.
  2. Funções de serviço da inicialização/desinicialização de DLL: BOOL Initialize(void)/void Terminate(void) — são necessários para iniciar/descarregar o ambiente Math Work antes de usar as funções de biblioteca no final de seu uso.
  3. Funções M exportadas – void mlf(int , mxArray **, mxArray *, ...), onde:
    • — número de variáveis retornadas (não confunda com o tamanho do banco de dados, etc.).
    • mxArray ** — endereço da estrutura mxArray onde os resultados do trabalho das funções M serão retornados.
    • mxArray * — ponteiro para a estrutura mxArray da variável de entrada da função M.

Como você pode ver, as funções M exportadas contém endereços e ponteiros para a estrutura mxArray e você não pode chamar essas funções diretamente a partir do MetaTrade 5, pois ele não reconhece esse tipo de dados. Nós não descreveremos a estrutura mxArray no MetaTrader 5, pois os desenvolvedores do MATLAB não garantem que isso não será modificado com o tempo, mesmo dentro da mesma versão do produto, então você deve criar um adaptador de DLL simples.

O seu esquema de bloco está mostrado abaixo:

Figura 5. Esquema de bloco do adaptador DLL

Figura 5. Esquema de bloco do adaptador DLL

É bem similar ao lado direito do DLL para a máquina MATLAB, então não analisaremos o seu algoritmo e continuaremos diretamente ao código. Para fazer isso, crie dois arquivos pequenos em seu compilador C/C++:

nSMA.cpp (do DllMatlab.zip):

#include 
#include 
/* Include MCR header file and library header file */
#include "mclmcr.h"
#include "NEOSMA.h"
/*---------------------------------------------------------------------------
** DLL Global Functions (external)
*/
extern "C" __declspec(dllexport) bool __stdcall IsStartSMA(void);
extern "C" __declspec(dllexport) bool __stdcall nSMA(double *pY,  int  nSizeY,
                                                     double *pIn, int nSizeIn,
                                                     double   dN, double dAd);
/*---------------------------------------------------------------------------
** Global Variables
*/
mxArray *TempY;
mxArray *TempIn;
mxArray *TempN;
mxArray *TempAd;
bool bIsNeoStart;
//---------------------------------------------------------------------------
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
    switch(reason)
    {
        case DLL_PROCESS_ATTACH:
         bIsNeoStart = false;
         TempY  = 0;   //Nullify pointers to buffers
         TempN  = 0;
         TempIn = 0;
         TempAd = 0;
         break;
        case DLL_PROCESS_DETACH:
         NEOSMATerminate();
         //Delete old data before exiting from Dll
         if(TempY  != NULL) mxDestroyArray(TempY);
         if(TempN  != NULL) mxDestroyArray(TempN);
         if(TempIn != NULL) mxDestroyArray(TempIn);
         if(TempAd != NULL) mxDestroyArray(TempAd);
         mclTerminateApplication();
    }
    return 1;
}
//---------------------------------------------------------------------------
bool __stdcall IsStartSMA(void)
{
 if(bIsNeoStart == false)
 {
  if(!mclInitializeApplication(NULL,0) )
  {
   MessageBoxA(NULL, (LPSTR)"Can't start MATLAB MCR!",
               (LPSTR) "MATLAB DLL: ERROR!", MB_OK|MB_ICONSTOP);
   return false;
  }else
   {
    bIsNeoStart = NEOSMAInitialize();
   };
 };
 return bIsNeoStart;
}
//---------------------------------------------------------------------------
bool __stdcall nSMA(double *pY, int nSizeY, double *pIn, int nSizeIn, double dN, double dAd)
{
   /*
   ** Create buffers
   */
   if(TempN == NULL){ TempN = mxCreateDoubleMatrix(1, 1, mxREAL);}
   else
   {
     mxDestroyArray(TempN);
     TempN= mxCreateDoubleMatrix(1, 1, mxREAL);
   };
   if(TempIn == NULL){ TempIn = mxCreateDoubleMatrix(1, nSizeIn, mxREAL);}
   else
   {
     mxDestroyArray(TempIn);
     TempIn= mxCreateDoubleMatrix(1, nSizeIn, mxREAL);
   };
   if(TempAd == NULL){ TempAd = mxCreateDoubleMatrix(1, 1, mxREAL);}
   else
   {
     mxDestroyArray(TempAd);
     TempAd= mxCreateDoubleMatrix(1, 1, mxREAL);
   };
   /*
   ** Creating data for processing
   */
   memcpy((char *)mxGetPr(TempIn), (char *) pIn, (nSizeIn)*8);
   memcpy((char *)mxGetPr(TempN), (char *) &dN, 8);
   memcpy((char *)mxGetPr(TempAd), (char *) &dAd, 8);
   /*
   ** Send and receive a response from the m-function
   */
   if(mlfNeoSMA(1, (mxArray **)TempY, (mxArray *)TempIn, (mxArray *)TempN
      , (mxArray *)TempAd) == false) return false;
   /*
   ** Return calculated vector from the m-function and clear buffers
   */
   memcpy((char *) pY, (char *)mxGetPr(TempY), (nSizeY)*8);
   mxDestroyArray((mxArray *)TempY);  TempY  = 0;
   mxDestroyArray((mxArray *)TempN);  TempN  = 0;
   mxDestroyArray((mxArray *)TempIn); TempIn = 0;
   mxDestroyArray((mxArray *)TempAd); TempAd = 0;
   return true;
}

nSMA.def (do DllMatlab.zip):

LIBRARY nnSMA
EXPORTS
IsStartSMA
nSMA


Construa o projeto em seu compilador C/C++: para isso você precisa incluir a biblioteca de entrada/saída padrão e adicionar ao projeto os seguintes arquivos (em seu compilador: Project->Add Project):

  1. nSMA.def
  2. Na pasta \Extern\lib\\\, onde:
    — Pasta principal do MATLAB.
    — tanto a pasta win32 para SO 32-bit, ou win64 para SO 64-bit.
    — a pasta "borland" para a versão do Borland C/C++. 5-6, a pasta "microsoft" para o Microsoft Visual C++ (eu tenho os arquivos para a versão 6):
    • libmx.lib
    • mclmcr.lib
  3. NeoSMA.lib — crie manualmente (consulte 3.1 Desenvolvimento da biblioteca universal da interação entre o MetaTrader 5 e a máquina MATLAB).

A última coisa que quero lhe dizer nesta seção é sobre os arquivos necessários quando mover o projeto para outro computador, onde não há MATLAB instalado.

Aqui está uma lista dos arquivos e caminhos na máquina alvo:

Muitos programadores avançados já adivinharam que é aconselhável usar um programa instalador (SETUP). Existem muitos na internet incluindo produtos grátis.

Agora temos que testar este DLL no MetaTrader 5. Para fazer isso criaremos um script simples (TestDllMatlab.mq5 do DllMatlab.zip):

#property copyright "2010, MetaQuotes Software Corp."
#property link      "nav_soft@mail.ru"
#property version   "1.00"
#import "nnSMA.dll"
bool  IsStartSMA(void);
bool  nSMA(double &pY[],
           int nSizeY,
           double &pIn[],
           int nSizeIn,
           double dN,
           double dAd);
#import
datetime    Time[];    // dynamic array of time coordinates
double      Price[];   // dynamic array of price
double      dNeoSma[]; // dynamic array of price
void OnStart()
  {
   int ind=0;
// run Dll
   if(IsStartSMA()==true)
     {
      //--- create and fill arrays
      CopyTime(Symbol(),0,0,301,Time);   // time array + 1
      ArraySetAsSeries(Time,true);       // get the time chart
      CopyOpen(Symbol(),0,0,300,Price);  // price array
      ArraySetAsSeries(Price,true);      // get the open prices
      ArrayResize(dNeoSma,300,0);        // reserve space for function response
                                         // get data
      if(nSMA(dNeoSma,300,Price,300,1,2)==false) return;
      // specify array orientation
      ArraySetAsSeries(dNeoSma,true);
      // plot data on chart
      for(ind=0; ind<ArraySize(dNeoSma);ind++)
        {
         DrawPoint(IntegerToString(ind,5,'-'),Time[ind],dNeoSma[ind]);
        }
     }
  }
//+------------------------------------------------------------------+
void DrawPoint(string NamePoint,datetime x,double y)
  {  // 100% ready. Plotar dados no gráfico. Desenhando usando setas.
// Main properties of chart object
   ObjectCreate(0,NamePoint,OBJ_ARROW,0,0,0);
   ObjectSetInteger(0, NamePoint, OBJPROP_TIME, x);        // time coordinate x
   ObjectSetDouble(0, NamePoint, OBJPROP_PRICE, y);        // price coordinate y
// Additional properties of chart object
   ObjectSetInteger(0, NamePoint, OBJPROP_WIDTH, 0);       // line width
   ObjectSetInteger(0, NamePoint, OBJPROP_ARROWCODE, 173); // arrow type
   ObjectSetInteger(0, NamePoint, OBJPROP_COLOR, Red);     // arrow color
  }
//+------------------------------------------------------------------+

Conclusão

Então, você sabe criar uma biblioteca universal para a interação do MetaTrader 5 e MATLAB e como conectar o DLL construído no ambiente MATLAB. Mas ainda há interfaces da interação do MetaTrader 5 e MATLAB à serem descritas, mas isto está além do escopo deste artigo. O tópico deste artigo está abrangido com detalhes. Eu escolhi as maneiras mais eficazes de interação, que não necessitam um tipo especial de "adaptadores". Embora você possa seguir "outro caminho" como a tecnologia .NET - Como exportar cotas do MetaTrader 5 para as aplicações .NET usando serviços WCF.

Muitos leitores podem ter uma dúvida: Qual método escolher? A resposta é simples - ambos, pois durante o projeto/depuração do modelo matemático a velocidade não é necessária. Mas você precisará a potência total do MATLAB sem "custos especias de produção" para a programação. A máquina MATLAB ajudará aqui, é claro. Entretanto, quando o modelo matemático é depurado e pronto para o uso, você precisará de velocidade, multi-tarefa (trabalho do indicador e/ou sistema de comércio em vários gráficos de preços) - aqui sem dúvida você precisará de um DLL, construído no ambiente MATLAB.

Mas nada disso o obriga a segui-lo. Cada um dará a sua própria resposta para essa pergunta, confiando primeiramente na proporção de "custo de programação" na escala do projeto (número de indicador e/ou usuários do sistema de comércio). Não faz sentido criar DLL no ambiente MATLAB para um ou dois usuários (é mais fácil instalar o MATLAB em dois computadores).

Muitos leitores, que não são familiarizados com o MATLAB provavelmente tem uma dúvida: Porque tudo isso? O MQL5 já tem funções matemáticas! a resposta é que o uso do MATLAB permite que você implemente facilmente as suas idéias matemáticas aqui estão apenas uma lista parcial de possibilidades:

Então, tudo em suas mãos, e não esqueça: "A matemática sempre foi a rainha das ciências", e o pacote MATLAB — é a sua calculadora científica.

Literatura

  1. Ajuda integrada do MATLAB
  2. Ajuda integrada do MQL5
  3. Jeffrey Richter. Aplicações de programação para o Microsoft Windows