Python与MT5通讯实验报告

Python与MT5通讯采用socket方式来实现。我是第一次涉及到这方面的编程,完全不具备最基本的常识,通过查阅大量的资料,充实了基础知识,又一次武装了自己的大脑~~

文中两个实验程序的代码都有非常详细的注释,记录了在学习过程中查询到的每条指令的功能和用法。本文作为日后开发借鉴资料,同时也给各位程序员提供参考。

socket通讯采用TCPIP协议,需要两个参数,一个是服务器地址,一个是通讯端口。

通讯端口 port 的范围是 0-65535,其中 0-1023 是预留给系统和一些常用服务的,例如 80 是 HTTP 服务的端口,25 是 SMTP 服务的端口。1024-49151 是注册端口,可以被 IANA 分配给一些服务,但不建议用户自定义使用。49152-65535 是动态端口,可以被用户自定义使用。如果不指定端口,系统会自动分配一个可用的动态端口。

根据 Python 的 socket 文档,如果想使用系统自动分配的动态端口,可以在创建 socket 对象后,调用 bind 方法时传入 0 作为端口参数。例如:

self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
self.sock.bind((self.address, 0))

这样,系统会选择一个可用的动态端口,并绑定到 socket 对象上。如果想获取这个端口,可以调用 getsockname 方法,它会返回一个元组,包含地址和端口。例如:


self.address, self.port = self.sock.getsockname()

客户端要想获取这个端口,就需要服务器提供一种方式来通知客户端,例如通过广播或者其他服务。或者,客户端可以先连接到一个固定的端口,然后服务器再告诉客户端动态端口的信息。

以下实验的两个程序需要传输中英文混写字符串,要注意编码问题:

● py中接受发送命令中要指定“gb2312”而不是“utf-8”。发送字符串时,如果字符串是中英文混写,有 n 个中文字,要在字符串后面追加 n*2 个空格;如果第一个是中文字符,还需要在前面添加一个空格。全英文字符则不要做以上处理。

● mq5中接受信息命令要指定“CP_ACP”。

查看计算机当前支持的编码:打开PowerShell,输入 chcp

936 = GB2312 简体中文,我的电脑是这个配置。

65001 = UTF-8 Unicode 编程常用。

socketserver_test.py(服务器)

"""
描述:本程序是socket服务器,与MT5连接,交换数据
作者:老易 QQ:921795 手机微信:13974863868
版本:1.0
日期:2023/6/2
"""

import socket # 导入 socket 通讯模块

# 预定义服务器地址(本地)和端口
server_ip = "localhost" # 本地 IP 地址,也可以用 172.0.0.1
server_port = 56765 # 端口号

class SocketServer:
    """
    socket服务器类
    """
    def __init__(self, address='', port=0):
        """
        构造方法,创建一个socket,初始化对象属性,例如地址、端口
        :param address:
        :param port:
        """
        self.addr = None # 一个元组,表示客户端的地址,包括 IP 地址和端口号,例如 (‘127.0.0.1’, 12345)
        self.conn = None # 一个 socket 对象,表示与客户端建立的连接,可以用来接收和发送数据
        self.cummdata = '' # 一个字符串,表示累积接收到的客户端发送的数据,每次调用 recvmsg 方法时会清空并重新赋值
        self.address = address # 服务器地址
        self.port = port # 服务器端口
        # 创建一个 socket,IPv4 = AF_INET,SOCK_STREAM = 使用 TCP 传输控制协议
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 绑定 socket 对象到一个地址(host, port)
        self.sock.bind((self.address, self.port))

    def recvmsg(self):
        """
        接收发送消息
        :return:接收到的消息
        """
        # 开始监听 TCP 连接请求
        self.sock.listen(1)
        """
        接受 TCP 客户端的连接,并返回一个新的 socket 对象和客户端的地址,分别赋值给 self.conn 和 self.addr。
        这样,服务器就可以通过 self.conn 来与客户端进行通信,而 self.addr 可以用来显示客户端的信息。
        """
        self.conn, self.addr = self.sock.accept()
        print('连接到:', self.addr, self.conn)
        self.cummdata = ''

        while True:
            # 接收TCP数据,返回一个字符串
            data = self.conn.recv(10000) # 最大数据量是10000字节
            # print(type(data), data)
            # 将接收到的数据解码为 gb2312 而不是 utf-8 编码的字符串,并追加到 self.cummdata 变量中
            self.cummdata += data.decode("gb2312")
            # 判断 data 变量是否为空,如果为空,就跳出 while 循环,结束接收数据的过程
            if not data:
                break
            # 发送TCP数据,返回发送的字节数
            # 如果字符串是中英文混写,有 n 个中文字,要在字符串后面追加 n*2 个空格;如果第一个是中文字符,还需要在前面添加一个空格。
            # 全英文字符则不要做以上处理。
            sendstr = " 老易 is 老易        "
            # bytes函数是Python的一个内置函数,可以对不同类型的数据进行字节化。encode函数只能对字符串进行编码,但是使用encode函数更常见和直观。
            self.conn.send(sendstr.encode("utf-8"))
            # self.conn.send(bytes(sendstr, "utf-8"))
            print('这是从python发送出的信息', sendstr, len(sendstr))
            # print('这是从mt5接收到的信息', self.cummdata)
            return self.cummdata

    def __del__(self):
        """
        析构方法,用于在 SocketServer 类的实例对象被销毁时执行一些清理操作,例如关闭 socket 对象
        :return:
        """
        self.sock.close()

# 创建一个 SocketServer 类的实例对象,命名为 myserver,传入服务器地址和端口作为参数,初始化对象的属性和 socket 对象
myserver = SocketServer(server_ip, server_port)

# 开始一个无限循环,用于不断地接收客户端的连接请求和数据
while True:
    """
    调用 myserver 对象的 recvmsg 方法,接收客户端发送的数据,并将其赋值给 msg 变量。
    同时,向客户端发送一个字符串 ‘from python’ 作为回应
    """
    msg = myserver.recvmsg()
    if not msg:
        print('没有收到信息')
    else:
        print('接收来自MT5的信息:', msg)

socket.recv 方法的参数表示要接收的最大数据量,单位是字节。这个参数的值可以根据实际情况进行调整,但是有一些因素需要考虑,例如:

● 网络层的数据包大小,例如以太网的数据包大小约为 1500 字节,TCP 的数据包大小约为 64K 字节。如果接收缓冲区的大小小于数据包的大小,可能会导致数据分片或丢失。

● 系统调用的开销,例如每次调用 socket.recv 都会涉及一些内核操作,如果接收缓冲区的大小过小,可能会导致频繁的系统调用,影响性能。

● 应用层的协议设计,例如如果发送方和接收方有约定好的消息格式和长度,那么接收缓冲区的大小应该与之匹配,以便正确地解析消息。

一般来说,接收缓冲区的大小应该选择一个相对较小的 2 的幂次方,例如 40963。这样可以与硬件和网络的实际情况相匹配,也可以避免出现小尺寸的剩余数据。如果想要接收更大量的数据,可以多次调用 socket.recv 方法,直到读取完整个消息或者遇到错误为止。

程序运行后,控制台显示如下:

 socketclient_test.mq5(客户端MT5)

/*
描述:本程序是socket服务器,与MT5连接,交换数据
作者:老易 QQ:921795 手机微信:13974863868
版本:1.0
日期:2023/6/2
*/


int socket_handle; //socket句柄
// 预定义服务器地址(本地)和端口
string  server_ip = "localhost"; // 本地 IP 地址,也可以用 172.0.0.1
int     server_port = 56765; // 端口号
uint    server_timeout = 1000; // 延时,毫秒

string  str_send = ""; // 发送字符
string  str_receive = ""; // 接收字符

int OnInit()
{
//--- 创建一个socket连接
    socket_handle = SocketCreate(); // 创建成功,socket_handle=1

//--- 创建时间触发器,1秒
    EventSetTimer(1);
   
//---
    return(INIT_SUCCEEDED);
}

void OnTimer()
{
//--- 创建一个socket连接
    socket_handle = SocketCreate(); // 创建成功,socket_handle>=1
    
//--- 连接socket服务器
    if (   true
        && socket_handle!=INVALID_HANDLE
       )
    {
        if (   true
            && SocketConnect(socket_handle, server_ip, server_port, server_timeout) // 每秒连接一次
           )
        {
            str_send="123中文abc";
            str_receive = StringSend(socket_handle, str_send) ? StringRecv(socket_handle, server_timeout) : ""; 
Comment(TerminalInfoInteger(TERMINAL_CODEPAGE));
Print("接收来自python的信息: ",str_receive);
        }
    }

//--- 销毁socket连接
    SocketClose(socket_handle);
}

bool StringSend(int socket,string request) 
{
/*
发送信息
*/
    char req[];
    int  len=StringToCharArray(request,req)-1;
    if(len<0) return(false);
    return(SocketSend(socket,req,len)==len); 
}

string StringRecv(int socket, uint timeout) 
{ 
/*
接收信息
*/
// 定义一个缓冲区大小
    char    rsp[]; 
    string  result = ""; 
    uint    len;
    uint    timeout_check=GetTickCount()+timeout;

//--- 读取套接数据 
    do 
    { 
        len=SocketIsReadable(socket); 
        if(len) 
        { 
            int rsp_len=SocketRead(socket,rsp,len,timeout);
            if(rsp_len>0) 
            { 
                result+=CharArrayToString(rsp,0,rsp_len,CP_UTF8); 
            }
       } 
    } 
    while(GetTickCount()<timeout_check && !IsStopped()); 

//---
    return(result); 
}

void OnDeinit(const int reason)
{
//--- 销毁socket连接
    SocketClose(socket_handle);
    
//--- 销毁时间触发器
    EventKillTimer();
   
}

void OnTick()
{
//---
   
}

我发现MT5接收到python发送的字符,如果有中文,就会出现乱码。以下编写了一个函数来自动处理。

经过处理的字符被MT5接收后,str_receive变量获取的字符串已经自动将头尾空格去掉。

给字符串添加空格的函数

# 定义一个函数,接受一个中英文混写的字符串,返回一个前后加空格的字符串
def add_spaces(my_sct_string):
     """

     :param my_sct_string: 字符串
     :return: 整形后的字符串
     """
     # 定义一个变量,存储中文字符和标点符号的个数
     chinese_count = 0

     # 定义一个常量,存储中文标点符号的范围
     chinese_punctuation = u'\uFF01-\uFF5E\u0020-\u007E\u3000-\u303F'

     # 遍历字符串中的每个字符
     for char in my_sct_string:
        # 如果是中文或者中文标点符号,就增加中文字符和标点符号的个数
        if (char.isalpha() and not char.isascii()) or char in chinese_punctuation:
            chinese_count += 1

     # 如果字符串开头是中文或者中文标点符号,就在前面加一个空格
     if (my_sct_string[0].isalpha() and not my_sct_string[0].isascii()) or my_sct_string[0] in chinese_punctuation:
        my_sct_string = " " + my_sct_string

     # 如果字符串中有中文或者中文标点符号,就在后面添加中文字符和标点符号的两倍
     if chinese_count > 0:
         my_sct_string += " " * (chinese_count * 2)

     # 返回修改后的字符串
     return my_sct_string

# 测试一下函数
sct_string = "你好!hello!你好"
print("原始字符串是:{}".format(sct_string))
print("修改后的字符串是:{}".format(add_spaces(sct_string)))

运行后的结果,输出字符反选部分显示了空格数量:

 === 实验报告结束 ===

有朋友问我干这个做什么用?借此机会多说两句。

国内股票期货量化系统已经有很多了,虽然他们都用Python编程,但技术规范各不相同。这就意味着你在一个平台编好的程序,换个平台就要重新写代码。

国内这些量化平台的可视化回测、可视化交易功能都很弱,不像MT5的回测功能,可以在指定时间段内逐个报价(tick)跑历史行情,这可以让我们大大缩短策略开发周期。

在国内量化平台上,你可以注册模拟、实盘账号,可以做交易。

我做这个的目的就是想从国内量化平台获取行情数据、账户数据,在MT5中呈现出来,并利用MT5强大的回测功能(可以多品种、多时间周期聚合)快速完成策略开发。

我经历过MT4到MT5伪升级,非常痛苦,同一家公司开发出来的量化系统,居然在语言上不能兼容。

从那以后,我就在想,但凡有机会,就要搞一套不被软件商捆绑的量化方案出来。做一套不被国内量化平台、不被国外量化平台例如MT5在编程语言上捆绑的系统。

我知道工作量有多大,靠一个人几乎不可能完成,但我有愚公移山的精神,慢慢挖,总有一天能成功。人多力量大,我也热切盼望这一路有更多的同志。

路漫漫其修远兮,吾将上下而求索。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
MT5是MetaTrader 5的简称,是一种用于外汇交易的电子交易平台。而PythonMT5之间的通信是通过套接字(sockets)来实现的。具体来说,通过在MT5终端的Include文件夹中放置一个名为socketlib.mqh的文件,可以实现PythonMT5之间的通信。这个文件是基于Python 3.6的sockets库开发的,并且通过在Python中运行Server.py文件来建立连接。 使用PythonMT5通信的目的是为了实现从国内量化平台获取行情数据和账户数据,并在MT5中展示,并且利用MT5强大的回测功能进行策略开发。通过这种方式,可以实现多品种和多时间周期的聚合,以更快地完成策略的开发和测试。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [PythonMql5Sockets:Mql5和Python之间通过套接字进行通信](https://download.csdn.net/download/weixin_42109125/18835604)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [PythonMT5通讯实验报告](https://blog.csdn.net/yiwence/article/details/131082945)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值