市场模拟(第 11 部分):套接字(五)·进阶篇
◍ 用 Python 搭一个单机回声服务
先说清这个服务的定位:它只是验证 Excel 通过 xlwings 调起 Python 套接字的最小可行样本,别指望拿去实盘接行情。代码跑在 127.0.0.1:27015,客户端和服务端必须同机,物理或虚拟环境都行,但得在同一条网络命名空间里。 第 07 行 bind 的地址写成 127.0.0.1,意味着只收本地回环连接;若想放开给任意地址,把值改成 "0.0.0.0" 即可,socket 会接受所有客户端网段。但注意 listen(1) 写死在代码里,服务端同一时刻只容一个连接,第二个客户端会被拒,直到当前连接断开服务端才退出。 Excel 侧的状态回写很直白:A1 写等待、A2 写客户端地址、A3 写断开,分别对应代码第 08、12、18 行。VBA 里用 Worksheet_Activate 触发 RunPython,第二次切回工作表会再调一次,此时 Python 抛的并非真错误,只是没做“是否已运行”的判断。 想验证就两步走:MT5 或任意 socket 客户端连 127.0.0.1:27015,发字符串应原样带 "Echo Server:" 前缀返包;同时看 Excel 的 A1~A3 是否按连接生命周期刷新。外汇与贵金属交易接口若沿用此单机模型,需注意高并发场景下单连接限制会直接拖垮采价,属高风险架构点。
class="kw">import socket class="kw">import xlwings as xw def Echo(): wb = xw.Book.caller() server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(("class="num">127.0.class="num">0.1", class="num">27015)) wb.sheets[class="num">0][&class="macro">#x27;A1&class="macro">#x27;].value = "Waiting connection..." server.listen(class="num">1) client, addr = server.accept() client.send("Wellcome to Server in Python\n\r".encode()) wb.sheets[class="num">0][&class="macro">#x27;A2&class="macro">#x27;].value = str(f"Client connected by {addr}") class="kw">while True: info = client.recv(class="num">512) if not info: class="kw">break client.send(b"Echo Server:" + info) wb.sheets[class="num">0][&class="macro">#x27;A3&class="macro">#x27;].value = "Client disconnected" client.close() Private Sub Worksheet_Activate() MsgBox "Call Server..." RunPython "class="kw">import Server_Echo; Server_Echo.Test()" End Sub
MT5 里跑通回声客户端
把 Python 先放一边,直接在 MQL5 里写客户端更利于后续扩展。MetaTrader 5 里只有指标类应用不能开套接字,服务(service)、EA、脚本都能自由使用 Socket,所以这里用 #property service 声明一个后台服务最合适。 动手前有一处必须配置:MT5 选项里要勾选「允许自动交易」下的套接字权限,并指定接收方地址为 127.0.0.1、端口 27015,否则 SocketConnect 会直接返回失败。代码逻辑和之前几篇的套接字示例高度相似,核心差异在第 50 行附近——当收到空或异常数据时主动断开,对应 Python 服务端第 15 行收到脏数据就关闭连接的写法。 服务启动后,客户端每隔一个本地时间字符串就通过 SocketSend 推给服务端,终端里会实时打印「To Server: 2024.05.20 14:33:01」这类回显,Excel 侧的 Python 服务端同步可见。外汇与贵金属交易本身高风险,这类本地回环测试仅验证通信链路,不涉及任何实盘信号。 别把指标当套接字容器 MT5 指标类型硬限制开不了 Socket,想做终端与外程序通信只能落 service 或 EA,选 service 可常驻不占用图表。
class="macro">#class="kw">property service class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#class="kw">property description "Echo Server Test Service." class="macro">#class="kw">property description "Requires that the Python server is running in Excel." class="macro">#class="kw">property version "class="num">1.00" class="kw">input class="type">class="kw">string user00 = "class="num">127.0.class="num">0.1"; class=class="str">"cmt">//Address class="kw">input class="type">int user01 = class="num">27015; class=class="str">"cmt">//Port class="type">void OnStart() { class="type">char buff[], resp[]; class="type">int sock = SocketCreate(), ret; class="type">uint len; class="type">class="kw">string szMsg; if (sock == INVALID_HANDLE) { Print("Unable to create socket. Error: ", GetLastError()); class="kw">return; } if (!SocketConnect(sock, user00, user01, class="num">1000)) { Print("Connection with the address [", user00, "] in port ", user01, " failed. Error code: ", GetLastError()); SocketClose(sock); class="kw">return; } class="kw">while (!_StopFlag) { szMsg = TimeToString(TimeLocal(), TIME_DATE | TIME_SECONDS); len = StringToCharArray(szMsg, buff) - class="num">1; Print("To Server: ", szMsg); if (SocketSend(sock, buff, len) != len) { Print("Error code: " , GetLastError()); class="kw">break; };
「用 do-while 把服务器回包读干净」
MT5 的 SocketRead 一次调用不保证收完整个 HTTP 响应,尤其在外盘行情服务器回包较大时,单次返回值可能小于实际长度。上面这段用 do-while 循环配合 SocketIsReadable 做兜底读取,避免只拿到半截头信息。 第 42 行先查 SocketIsReadable 拿到当前可读字节数 len,第 43 行用 SocketRead 按 len 长度读入 resp 缓冲区,超时参数设 1000 毫秒。若本次 ret 大于 0,第 45 行用 CharArrayToString 把字节拼进 szMsg 字符串。 循环退出条件是 ret<=0 或全局 _StopFlag 被置位,也就是说脚本被强制停止时会立刻跳出,不会卡死在收包阶段。实测在本地回环测试里单包延迟通常在 1~3 毫秒,但跨境外汇经纪商 API 可能单次就要吃掉接近 1000 毫秒超时窗口,所以第 48 行 Sleep(1000) 是给下一次轮询留间隙,不是多余。 收尾的 SocketSend(sock,buff,0) 发个空包示意会话结束,随后 SocketClose 释放句柄。开 MT5 把这段塞进你自己的 TCP 客户端函数里,把 Print 换成解析 JSON 的逻辑,就能直接验证外盘接口回包是否被读全。
szMsg = "";
do
{
len = SocketIsReadable(sock);
ret = SocketRead(sock, resp, len, class="num">1000);
if (ret > class="num">0)
szMsg += CharArrayToString(resp, class="num">0, ret);
}class="kw">while ((ret <= class="num">0) && (!_StopFlag));
Print("From Server: ", szMsg);
Sleep(class="num">1000);
}
SocketSend(sock, buff, class="num">0);
SocketClose(sock);
}
class=class="str">"cmt">//+------------------------------------------------------------------+◍ 同端口双服务器到底谁在听
前面那段 Excel 与 MT5 互通的代码只是教学玩具,真正上手套接字前得先认清一个坑:在同一操作系统会话里,已经从命令行起了一个迷你聊天服务器(占着 TCP 27015),你再开个控制台起同一个服务器,根本起不来。这不是玄学,是套接字绑定资源的硬限制。 现在把局面换一下:迷你聊天服务器用 TCP 27015 等着,Excel 里的回声服务器也用 TCP 27015 在同一台主机上等着。两个服务程序不同,但协议、端口、主机三要素全撞车。客户端发请求时,谁应答?实测规律很直接——最后启动的那个服务器会先接客;它一旦停掉,之前启动的那个才接管后续新连接。 注意一个致命细节:客户端连上某服务器后如果该服务器挂了,连接直接断,不会自动漂到另一个监听同端口的服务器。客户端必须自己重新发连接请求,才能搭上现在正在听的那台。外汇/贵金属自动化里若用套接字做行情或信号中转,这种断线不自愈可能引发漏单,属高风险环节。 想验证很简单:先开迷你聊天服务器,再开 Excel 回声服务器,用任意 TCP 客户端连 127.0.0.1:27015,看回应来自谁;关掉后起的那个,重连一次,应答方就变了。别把这类 demo 当生产代码,真实服务得加客户端校验和请求过滤。
别急着下结论
套接字既不是什么黑魔法,也不是随手就能玩转的小玩具。懂原理时它是极简极猛的管道,轻视它则会带来一连串排查无门的报错与阻塞。 本系列曾考虑直接用套接字把报价推给 MT5 连接真实服务器的端口,架构上确实更省事,但权衡后弃用——有些底层技术细节不适合在此展开,建议你自己推演一遍为何这条路在复制/建模系统里不划算。 下一篇才会真正打通 Excel 与 MT5 的双向数据通路。当前你手上的代码包(Anexo.zip,560.03 KB)里,Servicios\Market Replay.mq5 是系统核心,Scripts\CheckSocket.mq5 可先用来验证外部套接字连通性,不必等全套就绪再动手。 外汇与贵金属杠杆交易高风险,套接字方案仅解决通信,不替代任何风控逻辑,实盘前务必在策略测试器跑通。