Integração da MetaTrader 5 e Python: recebendo e enviando dados

4 abril 2019, 10:30
Maxim Dmitrievsky
0
1 721

Por que integrar o MQL5 e o Python?

O vasto processamento de dados requer ferramentas extensas e muitas vezes está além do ambiente seguro de um único aplicativo. Linguagens de programação especializadas são usadas para processar e analisar dados, estatísticas e aprendizado de máquina. Uma das principais linguagens de programação para processamento de dados é o Python. Uma solução muito eficaz é usar o poder da linguagem e incluir bibliotecas para o desenvolvimento de sistemas de negociação.

Existem diferentes soluções para implementar a interação de dois ou mais programas. Sockets são uma das soluções mais rápidas e flexíveis.

Um socket de rede é o ponto final da comunicação entre processos através de uma rede de computadores. A biblioteca padrão MQL5 inclui um grupo de funções Socket, que fornecem uma interface de baixo nível para trabalhar na Internet. Esta é uma interface comum para diferentes linguagens de programação, já ela que usa chamadas do sistema no nível do sistema operacional.

A troca de dados entre os preços é implementada em TCP/IP (Transmission Control Protocol/Internet Protocol). Assim, os processos podem interagir em um único computador e em uma rede local ou na Internet.

Para estabelecer uma conexão, é necessário criar e inicializar um servidor TCP ao qual o processo do cliente se conectará. Uma vez concluída a interação dos processos, a conexão deve ser fechada forçadamente. Os dados em uma troca TCP é um fluxo de bytes.

Ao criar um servidor, nós precisamos associar um socket a um ou mais hosts (endereços IP) e a uma porta não utilizada. Se a lista de hosts não estiver definida ou for especificada como "0.0.0.0", o socket escutará todos os hosts. Se você especificar "127.0.0.1" ou "localhost", a conexão será possível somente dentro do "loop interno", ou seja, somente dentro de um computador.

Como apenas o cliente está disponível no MQL5, nós criaremos um servidor em Python.


Criação de um servidor socket em Python

O objetivo do artigo não é ensinar os conceitos básicos de programação em Python. Assume-se, portanto, que o leitor esteja familiarizado com essa linguagem. 

Nós usaremos a versão 3.7.2 e o pacote embutido socket. Por favor, leia a documentação relacionada para mais detalhes.

Nós vamos escrever um programa simples que irá criar um servidor socket e receber as informações necessárias do cliente (o programa em MQL5), manipulá-lo e enviar o resultado de volta. Esse parece ser o método de interação mais eficiente. Suponha que nós precisamos usar uma biblioteca de aprendizado de máquina, como por exemplo scikit learn, que calculará a regressão linear usando preços e retornar as coordenadas, com base nos quais uma linha pode ser desenhada no terminal MetaTrader 5. Este é um exemplo simples. No entanto, essa interação também pode ser usada para treinar uma rede neural, para enviar à ela dados do terminal (cotações), aprender e retornar o resultado para o terminal.

Vamos criar o programa socketserver.py e importar as bibliotecas descritas acima:

import socket, numpy as np
from sklearn.linear_model import LinearRegression

Agora nós podemos continuar a criação de uma classe responsável pela manipulação do socket:

class socketserver:
    def __init__(self, address = '', port = 9090):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.address = address
        self.port = port
        self.sock.bind((self.address, self.port))
        self.cummdata = ''
        
    def recvmsg(self):
        self.sock.listen(1)
        self.conn, self.addr = self.sock.accept()
        print('connected to', self.addr)
        self.cummdata = ''

        while True:
            data = self.conn.recv(10000)
            self.cummdata+=data.decode("utf-8")
            if not data:
                break    
            self.conn.send(bytes(calcregr(self.cummdata), "utf-8"))
            return self.cummdata
            
    def __del__(self):
        self.sock.close()

Ao criar um objeto de classe, o construtor obtém o nome do host (endereço IP) e o número da porta. Então o objeto sock é criado, que é associado ao endereço e porta sock.bind().

O método recvmsg escuta a conexão de entrada sock.listen(1). Quando uma conexão nova do cliente chega, o servidor aceita ela self.sock.accept().

Em seguida, o servidor espera em um loop infinito por uma mensagem de entrada do cliente, que chega como um fluxo de bytes. Como o comprimento da mensagem não é conhecido antecipadamente, o servidor recebe essa mensagem em partes, digamos 1 Kbytes por vez, até que toda a mensagem seja lida self.conn.recv(10000). Os partes recebidas dos dados são convertidas em uma string data.decode("utf-8") e é adicionada ao resto da string summdata.

Depois de todos os dados terem sido recebidos (if not data:), o servidor envia ao cliente uma string contendo as coordenadas mais à direita e mais à esquerda da linha de regressão calculada. A string é convertida preliminarmente em uma array de bytes conn.send(bytes(calcregr(self.cummdata),"utf-8")).

No final, o método retorna a string recebida do cliente. Ele pode ser usado para a visualização de cotações recebidas, entre outros.

Um destrutor fecha o socket quando a execução do programa em Python é concluída.

Por favor, note que esta não é a única forma possível de implementação da classe. Como alternativa, você pode separar os métodos para receber e enviar mensagens e usá-las de maneiras diferentes em diferentes momentos no tempo. Eu descrevi apenas a tecnologia básica para criar uma conexão. Você pode implementar suas próprias soluções.

Vamos considerar com mais detalhes o método de aprendizagem de regressão linear dentro da implementação atual:

def calcregr(msg = ''):
    chartdata = np.fromstring(msg, dtype=float, sep= ' ') 
    Y = np.array(chartdata).reshape(-1,1)
    X = np.array(np.arange(len(chartdata))).reshape(-1,1)
        
    lr = LinearRegression()
    lr.fit(X, Y)
    Y_pred = lr.predict(X)
    type(Y_pred)
    P = Y_pred.astype(str).item(-1) + ' ' + Y_pred.astype(str).item(0)
    print(P)
    return str(P)

O fluxo de bytes recebidos é convertido em uma string utf-8, que é então aceita pelo método calcregr(msg = ' '. Como a string contém uma sequência de preços separados por espaços (conforme implementado no cliente), ela é convertida em um array em NumPy do tipo float. Depois disso, o array de preços é convertido em uma coluna (o formato de recebimento de dados é sclearn) Y = np.array(chartdata).reshape(-1,1). O preditor para o modelo é o tempo linear (uma sequência de valores; seu tamanho é igual ao comprimento da amostra de treinamento) X = np.array(np.arange(len(chartdata))).reshape(-1,1)

Isso é seguido pelo treinamento e previsão do modelo, enquanto o primeiro e o último valor da linha (as bordas do segmento) são gravados na variável "P", convertidos em uma string e passados para o cliente em forma de byte.

Agora, nós precisamos apenas criar o objeto de classe e chamar o método recvmsg() em um loop:

serv = socketserver('127.0.0.1', 9090)

while True:  
    msg = serv.recvmsg()


Criação do socket cliente em MQL5

Vamos criar um Expert Advisor simples, que pode se conectar ao servidor, passar um número especificado de preços recentes de fechamento, recuperar as coordenadas da linha de regressão e desenhá-la no gráfico. 

A função socksend() irá passar os dados para o servidor:

bool socksend(int sock,string request) 
  {
   char req[];
   int  len=StringToCharArray(request,req)-1;
   if(len<0) return(false);
   return(SocketSend(sock,req,len)==len); 
  }

Ele recebe a string, converte em um array de bytes e envia para um servidor.

A função socketreceive() escuta na porta. Quando uma resposta do servidor é recebida, a função a retorna como uma string:

string socketreceive(int sock,int timeout)
  {
   char rsp[];
   string result="";
   uint len;
   uint timeout_check=GetTickCount()+timeout;
   do
     {
      len=SocketIsReadable(sock);
      if(len)
        {
         int rsp_len;
         rsp_len=SocketRead(sock,rsp,len,timeout);
         if(rsp_len>0) 
           {
            result+=CharArrayToString(rsp,0,rsp_len); 
           }
        }
     }
   while((GetTickCount()<timeout_check) && !IsStopped());
   return result;
  }

A última função drawlr() recebe uma string, na qual as coordenadas da linha esquerda e direita são escritas, então, ele analisa a string para um array de string e desenha a linha de regressão linear em um gráfico:

void drawlr(string points) 
  {
   string res[];
   StringSplit(points,' ',res);

   if(ArraySize(res)==2) 
     {
      Print(StringToDouble(res[0]));
      Print(StringToDouble(res[1]));
      datetime temp[];
      CopyTime(Symbol(),Period(),TimeCurrent(),lrlenght,temp);
      ObjectCreate(0,"regrline",OBJ_TREND,0,TimeCurrent(),NormalizeDouble(StringToDouble(res[0]),_Digits),temp[0],NormalizeDouble(StringToDouble(res[1]),_Digits)); 
     }
  

A função é implementada no manipulador da OnTick().

void OnTick() {
 socket=SocketCreate();
 if(socket!=INVALID_HANDLE) {
  if(SocketConnect(socket,"localhost",9090,1000)) {
   Print("Connected to "," localhost",":",9090);
         
   double clpr[];
   int copyed = CopyClose(_Symbol,PERIOD_CURRENT,0,lrlenght,clpr);
         
   string tosend;
   for(int i=0;i<ArraySize(clpr);i++) tosend+=(string)clpr[i]+" ";       
   string received = socksend(socket, tosend) ? socketreceive(socket, 10) : ""; 
   drawlr(recieved); }
   
  else Print("Connection ","localhost",":",9090," error ",GetLastError());
  SocketClose(socket); }
 else Print("Socket creation error ",GetLastError()); }

Testando a aplicação Cliente/Servidor em MQL5/Python

Para executar a aplicação, você precisa ter o interpretador Python instalado. Você pode baixá-lo do website oficial.

Em seguida, execute o aplicativo do servidor socketserver.py. Ele cria um socket e ouve novas conexões do programa socketclientEA.mq5 em MQL5.

Após uma conexão bem-sucedida, o processo de conexão e os preços da linha de regressão são exibidos na janela do programa. Os preços são enviados de volta para o cliente:



A atividade de conexão e os preços da linha de regressão também são exibidos no terminal MetaTrader 5. Além disso, a linha de regressão é desenhada em um gráfico e atualizada posteriormente a cada novo tick:

Nós consideramos a implementação da interação direta de dois programas através de uma conexão de socket. Ao mesmo tempo, a MetaQuotes desenvolveu um pacote em Python, que permite receber os dados diretamente do terminal. Para mais detalhes, consulte a discussão do fórum relacionada o uso do Python na MetaTrader (em russo, então use a opção de tradução automática).

Vamos criar um script para demonstrar como receber as cotações do terminal.

Obtendo e analisando as cotações usando a API MetaTrader 5 Python

Primeiro você precisa instalar o módulo MetaTrader5 Python (o resumo das discussões de Python está disponível aqui). 

pip install MetaTrader5

Importe-o para o programa e inicialize a conexão com o terminal:

from MetaTrader5 import *
from datetime import date
import pandas as pd 
import matplotlib.pyplot as plt 

# Initializing MT5 connection 
MT5Initialize()
MT5WaitForTerminal()

print(MT5TerminalInfo())
print(MT5Version())

Depois disso, crie a lista de símbolos desejada e, sucessivamente, solicite os preços de fechamento para cada par de moedas do terminal ao dataframe do pandas:

# Create currency watchlist for which correlation matrix is to be plotted
sym = ['EURUSD','GBPUSD','USDJPY','USDCHF','AUDUSD','GBPJPY']

# Copying data to dataframe
d = pd.DataFrame()
for i in sym:
     rates = MT5CopyRatesFromPos(i, MT5_TIMEFRAME_M1, 0, 1000)
     d[i] = [y.close for y in rates]

Agora nós podemos desconectar do terminal e, em seguida, representar os preços do par de moedas como variações percentuais calculando a matriz de correlação e exibindo-a na tela:

# Deinitializing MT5 connection
MT5Shutdown()

# Compute Percentage Change
rets = d.pct_change()

# Compute Correlation
corr = rets.corr()

# Plot correlation matrix
plt.figure(figsize=(10, 10))
plt.imshow(corr, cmap='RdYlGn', interpolation='none', aspect='auto')
plt.colorbar()
plt.xticks(range(len(corr)), corr.columns, rotation='vertical')
plt.yticks(range(len(corr)), corr.columns);
plt.suptitle('FOREX Correlations Heat Map', fontsize=15, fontweight='bold')
plt.show()

Nós podemos ver uma boa correlação entre GBPUSD e GBPJPY no mapa de calor acima. Em seguida, nós podemos testar a cointegração importando a biblioteca statmodels:

# Importing statmodels for cointegration test
import statsmodels
from statsmodels.tsa.stattools import coint

x = d['GBPUSD']
y = d['GBPJPY']
x = (x-min(x))/(max(x)-min(x))
y = (y-min(y))/(max(y)-min(y))

score = coint(x, y)
print('t-statistic: ', score[0], ' p-value: ', score[1])

A relação entre dois pares de moedas pode ser exibida como uma Z score:

# Plotting z-score transformation
diff_series = (x - y)
zscore = (diff_series - diff_series.mean()) / diff_series.std()

plt.plot(zscore)
plt.axhline(2.0, color='red', linestyle='--')
plt.axhline(-2.0, color='green', linestyle='--')

plt.show()



Visualizando os dados de mercado usando a biblioteca Plotly

Muitas vezes é necessário visualizar as cotações de uma forma conveniente. Isso pode ser implementado usando a biblioteca Plotly, que também permite salvar os gráficos no formato interativo .html.

Vamos baixar as cotações do EURUSD e exibi-las em um gráfico de velas:

# -*- coding: utf-8 -*-
"""
Created on Thu Mar 14 16:13:03 2019

@author: dmitrievsky
"""
from MetaTrader5 import *
from datetime import datetime
import pandas as pd
# Initializing MT5 connection 
MT5Initialize()
MT5WaitForTerminal()

print(MT5TerminalInfo())
print(MT5Version())

# Copying data to pandas data frame
stockdata = pd.DataFrame()
rates = MT5CopyRatesFromPos("EURUSD", MT5_TIMEFRAME_M1, 0, 5000)
# Deinitializing MT5 connection
MT5Shutdown()

stockdata['Open'] = [y.open for y in rates]
stockdata['Close'] = [y.close for y in rates]
stockdata['High'] = [y.high for y in rates]
stockdata['Low'] = [y.low for y in rates]
stockdata['Date'] = [y.time for y in rates]

import plotly.graph_objs as go
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot

trace = go.Ohlc(x=stockdata['Date'],
                open=stockdata['Open'],
                high=stockdata['High'],
                low=stockdata['Low'],
                close=stockdata['Close'])

data = [trace]
plot(data)

Também é possível fazer o download e exibir qualquer profundidade do histórico de Bid e Ask:

# -*- coding: utf-8 -*-
"""
Created on Thu Mar 14 16:13:03 2019

@author: dmitrievsky
"""
from MetaTrader5 import *
from datetime import datetime

# Initializing MT5 connection 
MT5Initialize()
MT5WaitForTerminal()

print(MT5TerminalInfo())
print(MT5Version())

# Copying data to list
rates = MT5CopyTicksFrom("EURUSD", datetime(2019,3,14,13), 1000, MT5_COPY_TICKS_ALL)
bid = [x.bid for x in rates]
ask = [x.ask for x in rates]
time = [x.time for x in rates]

# Deinitializing MT5 connection
MT5Shutdown()

import plotly.graph_objs as go
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
data = [go.Scatter(x=time, y=bid), go.Scatter(x=time, y=ask)]

plot(data)


Conclusão

Neste artigo, nós consideramos as opções para implementar a comunicação entre o terminal e um programa escrito em Python, via sockets e usando diretamente a biblioteca especializada da MetaQuotes. Infelizmente, a implementação atual do socket cliente na MetaTrader 5 não é adequada para a execução no Strategy Tester, portanto nenhum teste e medição completos do desempenho da solução foram realizados. Vamos aguardar por novas atualizações a respeito da funcionalidade socket.

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

Arquivos anexados |
Análise sintática MQL via ferramentas MQL Análise sintática MQL via ferramentas MQL

Este artigo descreve um pré-processador, um leitor e um analisador para examinar códigos fonte em MQL. A implementação em MQL está anexada ao artigo.

O poder do ZigZag (parte II). Exemplos de recebimento, processamento e exibição de dados O poder do ZigZag (parte II). Exemplos de recebimento, processamento e exibição de dados

Na primeira parte do artigo, eu descrevi um indicador ZigZag modificado e uma classe para receber os dados desses tipos de indicadores. Aqui, eu mostrarei como desenvolver indicadores baseados nessas ferramentas e escrever um EA para testes que apresentem operações de acordo com os sinais formados pelo indicador ZigZag. Como complemento, o artigo apresentará uma nova versão da biblioteca EasyAndFast para o desenvolvimento de interfaces gráficas do usuário.

Estudo de técnicas de análise de velas (Parte II): Busca automática de novos padrões Estudo de técnicas de análise de velas (Parte II): Busca automática de novos padrões

No artigo anterior, nós analisamos 14 padrões selecionados de uma grande variedade de formações de velas existentes. É impossível analisar todos os padrões um por um, portanto, outra solução foi encontrada. O novo sistema busca e testa novos padrões de velas com base nos tipos de velas conhecidos.

Criando interfaces gráficas para EAs e indicadores baseados no .Net Framework e C# Criando interfaces gráficas para EAs e indicadores baseados no .Net Framework e C#

Uma maneira simples e rápida de criar janelas gráficas usando o editor do Visual Studio, e integração no código MQL do EA. O artigo é destinado para um vasto público de leitores e não requer conhecimentos de C# e .Net.