MetaTrader 5 与 Python 的集成:接收和发送数据

Maxim Dmitrievsky | 3 四月, 2019

为什么要把 MQL5 与 Python 集成?

全方位的数据处理需要大量工具,并且经常超出单一应用程序的功能沙箱。 专用编程语言正在用于处理和分析数据,统计和机器学习。 Python 是数据处理的主要编程语言之一。 一个非常有效的解决方案是利用语言的力量并包含函数库来开发交易系统。

在两个或更多个程序之间实现交互存在众多不同的解决方案。 套接字是最快速、最灵活的解决方案之一。

网络套接字是计算机网络上进程间通信的端点。 MQL5 标准库包含一组 Socket 函数,这些函数为在互联网上操作提供了一个低层接口。 这是不同编程语言的通用接口,因为它在操作系统级别进行系统调用。

价格之间的数据交换是通过 TCP/IP(传输控制协议/互联网协议)实现的。 因此,进程可以在单个计算机内,以及通过局域网或互联网进行交互。

若要建立连接,必须创建并初始化 TCP 服务器,以便客户端进程连接。 一旦交互进程完成,则连接必须强制关闭。 TCP 交换中的数据是字节流。

创建服务器时,我们需要将套接字与一个或多个主机(IP 地址)的未使用端口相关联。 如果未设置主机列表,或将其指定为 “0.0.0.0”,则套接字将监听所有主机。 如果指定 “127.0.0.1” 或 “localhost”,则只能在 “内部循环” 内连接,即仅在一台计算机内监听。

由于在 MQL5 中只提供客户端,我们利用 Python 创建一个服务器。


利用 Python 创建套接字服务器

本文的目的并非教授 Python 编程的基础知识。 因此,假定读者熟悉这种语言。 

我们将使用 3.7.2 版本的内置 socket 软件包。 更详细信息,请阅读相关文档。

我们将编写一个简单的程序,它创建一个套接字服务器,并从客户端(MQL5 程序)接收必要的信息,处理它并发回结果。 这似乎是最有效的交互方法。 假设我们需要使用机器学习函数库,例如 scikit learn,它将计算价格的线性回归并返回坐标,根据这些坐标可以在 MetaTrader 5 终端中绘制一条线。 这是基本的示例。 然而,这种交互也可以用于训练神经网络,用于从终端发送数据(报价),学习并将结果返回给终端。

我们来创建 socketserver.py 程序并导入上述函数库:

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

现在我们可以继续创建一个负责处理套接字操作的类:

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()

创建类的对象时,构造函数将获取主机名(IP 地址)和端口编号。 然后创建 sock 对象,该对象与地址和端口 sock.bind() 相关联。

recvmsg 方法监听传入连接 sock.listen(1)。 当传入的客户端连接到达时,服务器会接受它 self.sock.accept()。

然后,服务器在无限循环中等待传入的客户端消息,该消息抵达时是为数位流。 由于事先不知道消息长度,服务器会分帧接收该消息,比如一次 1K 字节,直到读取整个消息 self.conn.recv(10000)。 接收到的数据片段将会被转换为字符串 data.decode(“utf-8”),并添加到字符串 summdata 的其余部分。

一旦收到所有数据(如果不是数据 :),服务器就会向客户端发送一个字符串,其中包含计算出的回归线的最右边和最左边的坐标。 该字符串初步转换为字节数组 conn.send(bytes(calcregr(self.cummdata),"utf-8"))

在末尾,该方法返回从客户端收到的字符串。 它可以用于所接收报价的可视化,亦或其它。

一旦 Python 程序执行完毕,析构函数就会关闭套接字。

请注意,这并非该类唯一可能的实现。 替代方案,您可以分离接收和发送消息的方法,并在不同的时间点以不同的方式使用它。 我只是描述创建连接的基本技术。 您可以实现自己的方案。

我们来更详尽地考察当前实现中的线性回归学习方法:

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)

接收到的字节流转换为 utf-8 字符串,然后由 calcregr(msg ='') 方法接受。 由于字符串包含若干由空格分隔的价格序列(在客户端中实现),因此它将被转换至 float 类型的 NumPy 数组。 之后价格数组会被转换为一列(数据接收格式为 sclearn)Y = np.array(chartdata).reshape(-1,1)模型的预测变量是线性时间(一个数值序列; 其大小等于训练样本的长度)X = np.array(np).arange(len(chartdata))).reshape(-1,1)

接下来是训练和模型预测,而回归线的第一个和最后一个值(线段的边缘)被写入 “P” 变量,转换为字符串,并以字节形式传递给客户端。

现在,我们只需要创建类对象,并在循环中调用 recvmsg() 方法:

serv = socketserver('127.0.0.1', 9090)

while True:  
    msg = serv.recvmsg()


利用 MQL5 创建套接字客户端

我们创建一个简单的智能交易系统,它可以连接到服务器,传递指定数量的近期收盘价,得到反馈的回归线坐标,并将其绘制在图表上。 

socksend() 函数将数据传递给服务器:

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); 
  }

它接收字符串,转换为字节数组,并发送到服务器。

socketreceive() 函数监听端口。 一旦收到服务器响应后,该函数将其作为字符串返回:

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;
  }

最后一个函数 drawlr() 接收一个字符串,其中写入左、右线坐标,然后将字符串解析为字符串数组,并在图表上绘制线性回归线:

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)); 
     }
  

该函数在 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()); }

测试 MQL5-Python 客户端 - 服务器应用程序

为了运行该应用程序,您需要安装 Python 解释器。 您可以从 官方网站 下载。

然后运行服务器应用程序 socketserver.py。 它创建一个套接字,并监听来自 MQL5 程序 socketclientEA.mq5 的新连接。

连接成功后,连接过程和回归线价格将显示在程序窗口中。 价格会反馈给客户端:



连接活动和回归线价格也会在 MetaTrader 5 终端中显示。 回归线也会在图表上绘制,并在每次新的即时报价来临时进一步更新:

我们已考研究通过套接字连接实现两个程序的直接交互。 与此同时,MetaQuotes 已开发了一个 Python 软件包,允许直接从终端接收数据。 更多有关详细信息,请参阅与 在 MetaTrader 中使用 Python 相关的论坛讨论(俄语版,请酌情使用自动翻译选项)。</ s0>

我们创建一个脚本来演示如何从终端接收报价。

使用 MetaTrader 5 Python API 获取并分析报价

首先,您需要安装 MetaTrader5 Python 模块(Python 讨论摘要在此)。 

pip install MetaTrader5

将其导入程序,并初始化与终端的连接:

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

# 初始化 MT5 连接 
MT5Initialize()
MT5WaitForTerminal()

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

之后创建所需品种列表,并从终端连续请求每个货币对的收盘价发至 pandas 数据帧:

# 需要绘制相关矩阵货币创建清单
sym = ['EURUSD','GBPUSD','USDJPY','USDCHF','AUDUSD','GBPJPY']

# 将数据复制到数据帧
d = pd.DataFrame()
for i in sym:
     rates = MT5CopyRatesFromPos(i, MT5_TIMEFRAME_M1, 0, 1000)
     d[i] = [y.close for y in rates]

现在我们可以断开与终端的连接,然后计算相关矩阵,并在屏幕上显示货币对价格变化的百分比:

# 逆初始化 MT5 连接
MT5Shutdown()

# 比较百分比变化
rets = d.pct_change()

# 计算相关性
corr = rets.corr()

# 绘制相关性矩阵
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()

我们可以从上面的热图中看到 GBPUSD 与 GBPJPY 之间的良好相关性。 然后我们可以通过导入 statsmodels 函数库来测试协整:

# 导入 statsmodels 进行协整检验
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])

两个货币对之间的关系可以显示为 Z 分值:

# 绘制 z-分值变换
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()



利用 Plotly 函数库可视化市场数据

通常需要按照便利的形式可视化报价。 这可以利用 Plotly 函数库来实现,该函数库还允许以交互式 .html 格式保存图表。

我们下载 EURUSD 报价,并将其显示在烛条图表中:

# -*- coding: utf-8 -*-
"""
创建于 Thu Mar 14 16:13:03 2019

@author: dmitrievsky
"""
from MetaTrader5 import *
from datetime import datetime
import pandas as pd
# 初始化 MT5 连接 
MT5Initialize()
MT5WaitForTerminal()

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

# 将数据复制到 pandas 数据帧
stockdata = pd.DataFrame()
rates = MT5CopyRatesFromPos("EURUSD", MT5_TIMEFRAME_M1, 0, 5000)
# 逆初始化 MT5 连接
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)

也可以下载并显示出价和要价历史的任意深度:

# -*- coding: utf-8 -*-
"""
创建于 Thu Mar 14 16:13:03 2019

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

# 初始化 MT5 连接 
MT5Initialize()
MT5WaitForTerminal()

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

# 将数据复制到列表
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]

# 逆初始化 MT5 连接
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)


结束语

在本文中,我们研究了终端与利用 Python 编写的程序之间实现通信的选项,通过套接字并直接使用 MetaQuotes 的专用库。 不幸的是,MetaTrader 5 中当前实现的客户端套接字不适合在策略测试程序中运行,因此没有执行完整的测试,以及测量解决方案性能。 我们等待套接字功能的进一步更新。