📘

「用套接字把 MT5 变成行情中继站」

「用套接字把 MT5 变成行情中继站」

在 MT5 里做市场模拟,最容易被忽略的一环是进程间通信。终端本身不暴露底层 socket 接口,但借助 MQL5 的 Socket 系列函数,我们可以让 EA 直接对外发起 TCP 连接,把报价或自定义信号推给外部程序,也可以反过来接收指令。 实测在 MetaTrader 5 build 4410 上,SocketCreate 返回的句柄在策略测试器里同样有效,这意味着你能在历史回测中模拟「实时外部数据注入」的场景,而不必等实盘才验证链路。 下面这段代码演示了最基础的连接建立与关闭,重点看超时参数和错误码处理——很多连不上不是防火墙,是 SocketConnect 的 timeout 给得太短。

MQL5 / C++
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 原在线位写“离线”提示,让你不开日志也能肉眼确认服务状态。外汇/贵金属跨进程桥接涉及实时报价,断连可能造成滑点,请先在模拟账户验证。

MQL5 / C++
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 异常可能造成信号丢失,属于高风险链路,建议先在模拟环境跑通再上实盘。

MQL5 / C++
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 账户验证连通性。

MQL5 / C++
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 那边读不到数据。 外汇和贵金属杠杆高、滑点大,这类桥接方案只是通道,不解决信号本身的胜率问题,接入前先在模拟账户跑通再上真仓。

MQL5 / C++
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 只能靠图表手工做」那道心理门槛。

常见问题

用本地套接字在交易终端和 Excel 之间建一条中继通道,终端发报价、Excel 收数据,避免手动抄价。
在 Excel 写触发单元格,通过桥接进程把指令发回交易终端执行,注意外汇贵金属波动大、先模拟验证。
小布可接管这类桥接与校验流程,你只需维护 Excel 信号格,由它完成通道握手和指令中转,降低人工漏单概率。
多半是握手校验没过或通道超时,检查两端端口占用与校验字段格式,重发一次初始化包通常能恢复。
独立进程隔离了表格卡顿的影响,崩溃可自重启;建议把底层类方法封装好,方便以后换数据源。