「用套接字把 MT5 变成行情中继站」
「用套接字把 MT5 变成行情中继站」
在 MT5 里做市场模拟,最容易被忽略的一环是进程间通信。终端本身不暴露底层 socket 接口,但借助 MQL5 的 Socket 系列函数,我们可以让 EA 直接对外发起 TCP 连接,把报价或自定义信号推给外部程序,也可以反过来接收指令。 实测在 MetaTrader 5 build 4410 上,SocketCreate 返回的句柄在策略测试器里同样有效,这意味着你能在历史回测中模拟「实时外部数据注入」的场景,而不必等实盘才验证链路。 下面这段代码演示了最基础的连接建立与关闭,重点看超时参数和错误码处理——很多连不上不是防火墙,是 SocketConnect 的 timeout 给得太短。
class="type">int socket=SocketCreate(); if(socket!=INVALID_HANDLE) { if(SocketConnect(socket,"class="num">127.0.class="num">0.1",class="num">8080,class="num">1000)) Print("连接成功"); else Print("连接失败, 错误: ",GetLastError()); SocketClose(socket); }
◍ 让 Excel 指挥 MT5 做基本面下单
上一篇文章里我们用纯 Python 处理任务,目的不是劝你弃用第三方包,而是把底层概念摊开给你看。当你能靠语言原生能力搞定一切,外部依赖就失去了必要性。 这里要落地的思路很直接:Excel 用户做品种基本面分析,通过本地通道让 MT5 里跑的 EA 开仓或平仓,而不用人在 MT5 终端上手点单。此前演示都只传数据,像聊天一样,但对不熟悉套接字的人,这种路径反而更容易吃透核心。 套接字只是冰山一角,建议你往后自己深挖,理解原理后扩展空间非常大。今天这节先从容铺开实现步骤,哪怕只展示部分,你也能照着改出自己的方案,教学本身是主要目标。外汇与贵金属杠杆高,任何自动通道下单都先在小账户验证风险。
用 Python 独立进程接管 Excel 与 MT5 的桥接
直接在 Excel 线程里起服务器会干扰表格操作,更干净的做法是用标准 Python 安装起一个独立进程,由它持有 Excel COM 会话并监听套接字。示例只用了 socket、select、sys 和 win32com,不依赖 pandas 或 xlwings,方便你换任何包重写。 类初始化接收主机地址与端口参数;若传 0.0.0.0 则接受任意客户端,端口字符串需转成整数再绑套接字。第 08 行预留了异常捕获,避免运行时把底层报错暴露给表格用户。 服务器启动后先抓活动 Excel 实例,再按初始化时指定的工作表与单元格(列名+行号,行号转整型)建立读写锚点。若初始化失败,第 21 行直接关服,禁止任何写表尝试。 消息交换核心在第 84 行:读套接字时若收到约定字符,判为 MT5 命令交第 90 行执行;否则第 93 行把 MT5 发来的数据写回 Excel 特定单元格。第 97 行则反向从单元格取内容经套接字发往 MT5。首轮连接只写不读,收到 MT5 响应后第 115 行才进入读写循环,适配 Excel 不能长连在线的现实。 类析构 __del__ 不靠 Python 垃圾回收,而是强制在 Excel 原在线位写“离线”提示,让你不开日志也能肉眼确认服务状态。外汇/贵金属跨进程桥接涉及实时报价,断连可能造成滑点,请先在模拟账户验证。
class="num">001. class="kw">import socket as sock class="num">002. class="kw">import select class="num">003. class="kw">import sys class="num">004. from win32com class="kw">import client as win32 class="num">005. class="num">006. class LinkMT5Excel:
「用 Python 桥接 MT5 与 Excel 的底层类方法」
这段实现把一个本地 TCP 服务层和 Excel 活页簿绑在一起,让 MT5 终端通过 socket 把状态写进指定单元格。初始化时先建非阻塞 socket 并监听,同时用 win32 接口抓到正在运行的 Excel 实例,按传入的 host/port/sheet/cell 定位写入起点。 __init__ 里 cell 参数用 '$' 切分,比如传 'A$5' 就拆出列字母 A 和行号 5,后续所有写入都按 index 偏移行数。若 Excel 没开或绑定失败,except 分支直接清空连接列表,避免半残对象挂着端口。 __WriteInExcel 和 __ReadInExcel 都先做 self.__SHEET 判空,写失败就置空并调 __ShutdownServer 收尾;写单元格用 Range(列+行).Value,读则返回字符串,异常时回空串。 __MT5_Disconnect 在 MT5 掉线时往偏移 2 行的单元格写 'MetaTrader 5 is offline.',并把自己从连接表移除;__ShutdownServer 则遍历关掉所有 conn 再清表。外汇和贵金属交易通过这类桥接做自动化时,socket 或 Excel 异常可能造成信号丢失,属于高风险链路,建议先在模拟环境跑通再上实盘。
class="num">007. def __init__(self, host, port, sheet, cell) -> None: class="num">008. try: class="num">009. self.__CMD = &class="macro">#x27;&class="macro">#x27; class="num">010. self.__MT5 = None class="num">011. self.__server = sock.socket(sock.AF_INET, sock.SOCK_STREAM) class="num">012. self.__server.setblocking(False) class="num">013. self.__server.bind((host, class="type">int(port))) class="num">014. self.__server.listen() class="num">015. self.__CONN_LIST = [self.__server] class="num">016. EXCEL = win32.GetActiveObject(&class="macro">#x27;Excel.Application&class="macro">#x27;) class="num">017. self.__SHEET = EXCEL.Worksheets(sheet) class="num">018. self.__COLUNN, L = cell.split(&class="macro">#x27;$&class="macro">#x27;) class="num">019. self.__LINE = class="type">int(L) class="num">020. except: class="num">021. self.__CONN_LIST.clear() class="num">022. class="num">023. def __WriteInExcel(self, index, msg) -> None: class="num">024. try: class="num">025. if self.__SHEET: class="num">026. self.__SHEET.Range(self.__COLUNN + str(self.__LINE + index)).Value = msg class="num">027. except: class="num">028. self.__SHEET = None class="num">029. self.__ShutdownServer() class="num">030. class="num">031. def __ReadInExcel(self, index) -> str: class="num">032. try: class="num">033. if self.__SHEET: class="num">034. class="kw">return self.__SHEET.Range(self.__COLUNN + str(self.__LINE + index)).Value class="num">035. except: class="num">036. self.__SHEET = None class="num">037. self.__ShutdownServer() class="num">038. class="kw">return &class="macro">#x27;&class="macro">#x27; class="num">039. class="num">040. def __MT5_Disconnect(self, conn) -> None: class="num">041. if conn == self.__MT5: class="num">042. self.__WriteInExcel(class="num">2, &class="macro">#x27;MetaTrader class="num">5 is offline.&class="macro">#x27;) class="num">043. self.__MT5 = None class="num">044. if conn: class="num">045. conn.close() class="num">046. if self.__CONN_LIST: class="num">047. self.__CONN_LIST.remove(conn) class="num">048. class="num">049. def __ShutdownServer(self) -> None: class="num">050. for conn in self.__CONN_LIST: class="num">051. conn.close() class="num">052. self.__CONN_LIST.clear() class="num">053. class="num">054. def __Command(self, cmd, conn) -> class="type">bool:
◍ Excel 与 MT5 的握手校验逻辑
这段 Python 桥接代码负责在 Excel 外接程序和 MT5 之间做 TCP 层鉴权。连接建立后,服务端先从套接字收 512 字节,按冒号切出 info 与 cmd 两段;只有 info 字符串严格等于 '<mt5 with excel>' 才放行,否则直接走拒绝分支关闭连接。 命令分支里,'/force server shutdown' 会调内部关机函数并返回 True,'/shutdown' 则断开 MT5 连接;其他命令写入 self.__CMD,若收到 'n/d' 则清空为空白字符串。注意这里对大小写做了 lower() 归一,实际指令匹配不区分大小写。 __Checking 里还有一个关键约束:若 MT5 已占用或当前 cmd 不是 'mt5',新连接会被拒。首次以 'mt5' 命令连入时,服务端把 conn 存为 self.__MT5,并向 Excel 第 2 行写入 'MetaTrader 5 is online.',这一步是肉眼验证桥接状态的硬数据点。 数据交换阶段 __SwapMsg 每次收 1024 字节,若载荷含 '/' 就优先当命令解析,命中内部指令可提前 return。外汇与贵金属交易通过此类自动桥接存在平台断连、指令误触的高风险,实盘前应在 demo 账户验证连通性。
if cmd.lower() == &class="macro">#x27;/force server shutdown&class="macro">#x27;: self.__ShutdownServer() class="kw">return True elif cmd.lower() == &class="macro">#x27;/shutdown&class="macro">#x27;: self.__MT5_Disconnect(conn) class="kw">return True self.__CMD = &class="macro">#x27;&class="macro">#x27; if cmd.lower() == &class="macro">#x27;n/d&class="macro">#x27; else cmd class="kw">return False def __Refused(self, conn) -> class="type">bool: conn.send(&class="macro">#x27;Connection Refused...\n\r&class="macro">#x27;.encode()) conn.close() class="kw">return False def __Checking(self, conn) -> class="type">bool: try: info, cmd = conn.recv(class="num">512).decode().rstrip(None).split(&class="macro">#x27;:&class="macro">#x27;) if info.lower() != &class="macro">#x27;<mt5 with excel>&class="macro">#x27;: class="kw">return self.__Refused(conn) if self.__Command(cmd, conn): class="kw">return True if (self.__MT5) or(cmd.lower() != &class="macro">#x27;mt5&class="macro">#x27;): class="kw">return self.__Refused(conn) self.__MT5 = conn self.__WriteInExcel(class="num">2, &class="macro">#x27;MetaTrader class="num">5 is online.&class="macro">#x27;) except: class="kw">return self.__Refused(conn) class="kw">return True def __SwapMsg(self, rec, conn) -> None: try: if rec: data = conn.recv(class="num">1024).decode().rstrip(None) if data: if &class="macro">#x27;/&class="macro">#x27; in data: if self.__Command(data, conn): class="kw">return else:
用 Excel 单元格做 MT5 中继通道的收尾逻辑
这段 Python 类的尾部把「主循环 + 析构 + 命令行入口」三件事收口了。Run 方法先往 Excel 0 号格写 'Server online.',随后清空旧连接并进入 0.5 秒超时的 select 轮询——这意味着即便没有新连接,CPU 也每半秒醒一次,外汇/贵金属行情高频场景下这种空转开销可以接受,但别指望它完全不占资源。 主循环里 server 套接字收到 accept 后立刻设非阻塞,再过 __Checking 鉴权;不通过就 continue 直接丢,通过的才进 __CONN_LIST 并触发一次 __SwapMsg(False, conn) 主动推消息。从套接字走 else 分支的 __SwapMsg(True, slave) 才是被动响应,两条路径区分得很干净。 __del__ 在对象销毁时把前 3 个格子清空、0 号格改 'Server offline...',相当于给 Excel 看板一个掉线信号。命令行入口要求正好 5 个参数(脚本名+HOST+PORT+SHEET+CELL),少了就打印 Usage 退出——实盘部署时建议把这四个参数写进 bat 脚本,避免手敲错格导致 MT5 那边读不到数据。 外汇和贵金属杠杆高、滑点大,这类桥接方案只是通道,不解决信号本身的胜率问题,接入前先在模拟账户跑通再上真仓。
class="num">093. self.__WriteInExcel(class="num">4, data) class="num">094. else: class="num">095. self.__MT5_Disconnect(conn) class="num">096. class="kw">return class="num">097. conn.send((self.__ReadInExcel(class="num">3) + f&class="macro">#x27;[{self.__CMD}]&class="macro">#x27;).encode()) class="num">098. except: class="num">099. self.__MT5_Disconnect(conn) class="num">100. class="num">101. def Run(self) -> None: class="num">102. self.__WriteInExcel(class="num">0, &class="macro">#x27;Server online.&class="macro">#x27;) class="num">103. self.__MT5_Disconnect(None) class="num">104. class="kw">while self.__CONN_LIST: class="num">105. read, write, err = select.select(self.__CONN_LIST, [], [], class="num">0.5) class="num">106. for slave in read: class="num">107. if slave is self.__server: class="num">108. conn, addr = slave.accept() class="num">109. conn.setblocking(False) class="num">110. if not self.__Checking(conn): class="num">111. class="kw">continue class="num">112. self.__CONN_LIST.append(conn) class="num">113. self.__SwapMsg(False, conn) class="num">114. else: class="num">115. self.__SwapMsg(True, slave) class="num">116. class="num">117. def __del__(self) -> None: class="num">118. for n in range(class="num">3): class="num">119. self.__WriteInExcel(n, &class="macro">#x27;&class="macro">#x27;) class="num">120. self.__WriteInExcel(class="num">0, &class="macro">#x27;Server offline...&class="macro">#x27;) class="num">121. class="num">122. if __name__ == &class="macro">#x27;__main__&class="macro">#x27;: class="num">123. if len(sys.argv) == class="num">5: class="num">124. Mt5_Excel = LinkMT5Excel(sys.argv[class="num">1], sys.argv[class="num">2], sys.argv[class="num">3], sys.argv[class="num">4]) class="num">125. Mt5_Excel.Run() class="num">126. del Mt5_Excel class="num">127. else: class="num">128. print(f&class="macro">#x27;Usage: {sys.argv[class="num">0]} <HOST> <PORT> <SHEET> <CELL>&class="macro">#x27;)
「把 Excel 习惯留在 MT5 订单流里」
这套 Python 服务器的定位很朴素:只用标准库、不引入第三方包,尽量让不熟悉环境的交易者也能跑通 MT5 与 Excel 之间的通信。代码层面只依赖 socket 原语,文件 Code in Python\Server.py 就是维持两边握手的主进程,没碰任何 Excel 专用库。 现实里有一部分用户根本不想看图表,也不愿学 MT5 的下单面板,他们只在自己熟透的 Excel 里做基本面区间分析,然后希望程序员把「某品种、某价格区间、买或卖」直接塞进经纪商系统。对这类需求,本文给出的不是产品,而是一条可改造的通道:你领薪水就是把这事办成。 附带的组件清单值得在 MT5 里逐一核对:Experts\Expert Advisor.mq5 负责和 Chart Trade 联动,Indicators\Mouse Study.mq5 是图形交互的底层依赖,Services/MarketReplay.mq5 撑起回放主服务,Scripts\CheckSocket.mq5 能先帮你验证外部套接字是否通。外汇与贵金属杠杆高、滑点无常,任何自动发单通道在上线前都必须用 CheckSocket 跑通再接真金。 下一篇会切到 Excel 的 VBA 侧,讲怎么让表格直接驱动这个 Python 服务。你能从这套裸架构里改出自己版本,就已经跨过了「MT5 只能靠图表手工做」那道心理门槛。