MQL5-JSON-API:用Socket+JSON让MT5交易与Python无缝对接

发布时间:2026/10/18 15:51:50
MQL5-JSON-API:用Socket+JSON让MT5交易与Python无缝对接 简介MQL5的JSON通信接口是一套面向MetaTrader 5平台开发者的开源工具包主要用于打通交易终端与外部系统之间的实时数据通道。它围绕市场价格数据即引号数据的获取、解析与转发完整展示了基于JSON格式、Socket套接字以及ZeroMQ底层消息库的API设计思路适合有一定MQL5编程基础、希望搭建自动化交易或数据服务模块的开发者参考学习。压缩包共含11个文件整体大小约49KB其中有多个mqh头文件用于封装JSON解析和错误控制逻辑两个mq5示例一个指标、一个智能交易用于演示请求报价、处理响应等核心流程同时附有md格式的说明文档、清理脚本以及许可证文件目录结构清晰便于按阅读顺序逐层理解。这套资源已被368人学习下载属于典型的实战型示例代码。通过阅读源码可以掌握JSON对象的编码与解码方法、套接字连接的建立与维护、ZeroMQ消息模式在MQL5中的具体应用并能基于自带示例快速改造出自己的实时报价推送模块或历史数据查询工具从而显著降低MT5生态与外部系统集成时的开发门槛。1. MQL5-JSON-API 到底解决什么问题把行情、交易和历史数据变成 JSON 接口做量化的人迟早会撞上一个尴尬策略在外部程序里跑但交易执行和数据源都在 MT5 里两边隔着一堵墙。MQL5-JSON-API 的核心思路很直接——把 MQL5 端的数据查询、K线拉取、下单改单全部包装成 JSON 格式的请求通过 TCP Socket 或 ZeroMQ 暴露给外部程序Python、Node.js、Java 都能用同一套协议消费这个 trading-api。它解决的是外部程序怎么安全可控地驱动 MT5 执行交易、读取历史数据这个老问题。拿到这套资源之后不需要再手搓通信协议把封装好的报价与交易层拿来改参数就能用。新手跑通连接即可练手熟手可以替换消息推送层换成自己的消息总线。2. MQL5-JSON-API 架构选型为什么是 Socket JSON 而不是 WebRequest先交代清楚底层选择后面所有排错才有依据。MQL5 留给开发者的外部通信接口一直很有限官方的 WebRequest 看似能用实际做项目会被三条硬约束卡死这也是 MQL5-JSON-API 没有走官方接口、而是在 MT5 端直接开 Socket 的根本原因。理解了这几条你才知道什么时候该改配置、什么时候该换通信层。2.1 WebRequest 的三道坎白名单、短连接、单向通道第一道坎是白名单。WebRequest 只能访问在工具-选项-智能交易系统里手动登记过的 URL交易图表上还要勾选允许 WebRequest。域名每次变更哪怕只是端口从 80 改到 8080都要进配置改一遍、重启终端。对自动化部署来说这基本等于让运维天天做手工活。第二道坎是短连接。WebRequest 每次请求都是一开一合服务端响应完就断开连接状态不保留。行情推送、服务端主动向 MT5 下发指令这种场景完全做不了。有人用循环 WebRequest 去轮询一个远端命令队列但一秒钟轮询一次已经是极限轮询太密会把终端主线程堵住太疏又损失实时性两难。第三道坎是单向通道。MQL5 侧永远只能作为 HTTP 客户端外部程序没法主动推数据过来。换到交易场景就是这样外部策略判断出该开仓可它只能等着 MT5 过来取命令。MQL5-JSON-API 的做法正好相反MT5 端运行后主动监听本地端口外部程序作为客户端连接过来主动权回到外部一侧。实际部署时我一般绑定 127.0.0.1再让本机的 Python 服务连它。外部程序和 MT5 不在同一台机器时才绑内网网卡并加一层 token 校验避免端口裸奔。2.2 请求与响应模型id、method、params 三个字段必须定死协议字段决定后续对接是否省心。我拆过几份 MQL5-JSON-API 的实现能直接落地的版本协议上都有一个共同的三段式结构id、method、params。id 是外部程序自增生成的请求编号。这一项不能省因为 MT5 端单线程处理消息多个请求并发进来时响应顺序和请求顺序没法保证一致。外部程序拿到响应必须先读 id才知道对应哪次请求。我习惯用整数序列号起始 1000出问题时对着日志按 id 就能串起完整调用链。method 是动作名get_rates、get_tick、get_positions、order_send、order_close 是常见路由。method 直接决定 params 的解析分支相当于 RPC 路由表。命名上建议统一小写加下划线避免大小写问题成为外部程序的兼容性坑。params 是参数对象内容随 method 变化。get_rates 需要 symbol、timeframe、start、countorder_send 需要 symbol、volume、action、type、price、sl、tp。字段类型在文档里写清楚服务端再做一次类型校验比靠外部程序自觉要可靠得多。响应结构同样要统一。我被问得最多的问题是为什么响应里有时有 result、有时有 error混乱的根源是两边写协议时没有约定成功和失败的形状。这里建议定死为要么 result 非空、要么 error 非空不能两个同时给。{ id: 1001, result: { status: ok, rates: [] }, error: null }error 字段也不要只放一段人话最好是一个带 code 和 message 的对象。外部程序可以根据 code 判断是否重试message 留给人在排查时看。成功的 result 里 status 字段可以省略直接把 result 本身当作状态即可。2.3 ZeroMQ 的适用边界什么时候值得多引一个 DLL外部程序只有一个、交互都是请求-响应时纯 TCP Socket 完全够用代码量最小部署也最简单。但一旦出现一个 MT5 对多个外部客户端或MT5 要主动往外推数据这两类需求手写 Socket 就有点吃力了。连接管理、消息帧边界、断线重连全要自己实现这也是 MQL5-JSON-API 的 ZeroMQ 版本存在的价值。用 ZeroMQ 封装时核心交易链路和数据查询建议走 REQ/REP外部发请求、MT5 回复一问一答语义清晰。实时行情广播走 PUB/SUBMT5 作为 PUB 端发布 JSON 消息外部按主题订阅。最常用的主题可以按品种和周期拆比如 XAUUSD.tick、EURUSD.M1订阅端想收什么就订阅什么不需要把全部数据推给所有客户端。选型上还有一个容易被忽略的成本ZeroMQ 在 MQL5 里要借助外部 DLL这会引入运行库依赖和终端允许 DLL 导入的开关。如果你的部署环境是托管服务器且不允许加载 DLL那就只能回归纯 Socket 方案把多客户端分发写在业务层。我先讲清这个边界是因为见过不少团队在纯 Socket 能解决的情况下硬上 ZeroMQ最后反而被 DLL 依赖卡在生产环境外面。注意ZeroMQ 版本和纯 Socket 版本在 MQL5-JSON-API 内部是两套不同的接入层业务层可以复用。迁移成本主要在连接层协议层不需要动。3. 实战搭建从 Socket 初始化到历史数据与交易指令收发原理说完直接看怎么把服务端跑起来。MQL5-JSON-API 的代码组织大体分三层连接层负责 Socket 的创建、监听、读取协议层负责 JSON 的序列化和解析业务层把 method 映射到 MQL5 原生调用。下面按这三层拆开。3.1 MQL5 端 Socket 初始化与消息循环MQL5 没有异步 Socket消息循环通常落在定时器里。用 OnInit 创建监听套接字int g_listener INVALID_HANDLE; int OnInit() { g_listener SocketCreate(); if(g_listener INVALID_HANDLE) { Print(SocketCreate error: , GetLastError()); return INIT_FAILED; } // 端口要和外部客户端约定好建议避开特权端口 if(!SocketBind(g_listener, 5555)) { Print(SocketBind error: , GetLastError()); return INIT_FAILED; } if(!SocketListen(g_listener)) { Print(SocketListen error: , GetLastError()); return INIT_FAILED; } EventSetTimer(1); // 每秒触发一次OnTimer用来accept和读数据 return INIT_SUCCEEDED; }SocketCreate 返回一个句柄失败时 GetLastError() 是具体错误码。SocketBind 第二参数是端口号同一台机器跑多个 EA 实例时端口要做成输入参数不要硬编码。EventSetTimer(1) 把 OnTimer 设成每秒执行一次这个频率对非高频消息够用也不会抢行情线程的时间片。核心循环放在 OnTimer 里用 SocketAccept 接受新连接再循环读取数据避免一次 SocketReceive 只读到半个 JSONvoid OnTimer() { int client SocketAccept(g_listener); if(client ! INVALID_HANDLE) { Print(Client connected, handle, client); g_client client; // 单客户端版本多客户端见第5章 } if(g_client ! INVALID_HANDLE) { ProcessSocketInput(); } } void ProcessSocketInput() { uchar buffer[2048]; uint timeout 10; int received SocketReceive(g_client, buffer, sizeof(buffer), timeout); if(received 0) { string message CharArrayToString(buffer, 0, received, CP_UTF8); // 这里按method分发到具体处理函数 string response HandleRequest(message); char response_data[]; int length StringToCharArray(response, response_data, 0, WHOLE_ARRAY, CP_UTF8) - 1; SocketSend(g_client, response_data, length); } }SocketReceive 的 timeout 单位是毫秒返回 0 表示超时小于 0 才是出错别把超时当成断开。CharArrayToString 用 CP_UTF8 是因为 JSON 内容里可能带非 ASCII 字符。发送响应时 StringToCharArray 会带上结尾符减掉 1 避免多出一个 \0这是很多人卡过的小坑。但这段代码只处理了单客户端而且读一次就解析一次TCP 半包问题仍然存在。放到真实场景中ProcessSocketInput 需要改进成维护接收缓冲区、累积字节直到遇到换行符再解析。这个处理放到第四章讲先把主干功能走通。3.2 历史数据查询CopyRates 的参数与时间戳处理外部程序最常调用的方法就是拉历史数据。CopyRates 能把 K 线直接复制到 MqlRates 数组里但参数细节决定返回质量。bool HandleGetRates(const string symbol, string timeframe, datetime start_time, int count, string response) { ENUM_TIMEFRAMES tf StringToTimeframe(timeframe); MqlRates rates[]; ArraySetAsSeries(rates, true); // 结束时间取当前报价时间start_time由外部传字符串转datetime datetime stop_time TimeCurrent(); int copied CopyRates(symbol, tf, start_time, stop_time, rates); if(copied 0) { response BuildError(-1, CopyRates failed); return false; } // 限制返回条数防止一次拉太多卡住终端 int use_count MathMin(count, copied); response BuildRatesResponse(rates, use_count); return true; }CopyRates 参数顺序是品种、周期、起始位置、结束位置、输出数组。start_time 是 datetime 类型外部传进来的是字符串需要先用 StringToTime 转。用 TimeCurrent() 作为结束时间是拉最近一段K线的常见做法它取的是终端维护的最新报价时间。ArraySetAsSeries(rates, true) 让数组下标 0 对应最新一根K线外部客户端处理时会少绕一个弯。序列化时我只保留最小必要字段time、open、high、low、close、tick_volume。tick_volume 是 tick 成交量不是真实交易量部分品种会有 real_volume但实盘账户经常拿不到所以默认不给避免外部程序拿到空字段后产生误判。time 字段直接用 Unix 秒不带时区信息时区换算放在外部展示层。3.3 交易指令下发JSON 字段和 MqlTradeRequest 的映射交易接口是整个 API 里最敏感的部分字段映射必须准确。从 JSON 请求到 MqlTradeRequest常见字段对照如下。JSON 字段类型MqlTradeRequest 映射说明actionstringreq.actiondeal 对应 TRADE_ACTION_DEALsymbolstringreq.symbol品种名必须精确匹配typestringreq.typebuy/sell 映射到 ORDER_TYPE_BUY/SELLvolumedoublereq.volume注意最小手数限制pricedoublereq.price建议显式传服务端只做差值校验sl/tpdoublereq.sl / req.tp止损止盈价magiclongreq.magic用于策略识别交易来源请求 JSON 示例{ id: 2001, method: order_send, params: { symbol: XAUUSD, action: deal, type: buy, volume: 0.01, price: 2045.5, sl: 2030.0, tp: 2060.0, magic: 20250101 } }MQL5 端收到后填充结构体的核心代码如下bool HandleOrderSend(JsonObject params, string response) { MqlTradeRequest req {}; MqlTradeResult res {}; req.action TRADE_ACTION_DEAL; req.symbol params[symbol]; req.volume params[volume].ToDouble(); req.type params[type] sell ? ORDER_TYPE_SELL : ORDER_TYPE_BUY; req.price params[price].ToDouble(); req.sl params[sl].ToDouble(); req.tp params[tp].ToDouble(); req.magic params[magic].ToLong(); req.deviation 20; // 滑点容忍度单位是点数 bool ok OrderSend(req, res); if(!ok || res.retcode ! TRADE_RETCODE_DONE) { response BuildError(res.retcode, ReturnCodeMessage(res.retcode)); return false; } response BuildOrderResult(res); return true; }MqlTradeRequest 必须用 {} 做零初始化否则残留字段会带来诡异报错。price 字段我特意要求外部显式传参而不是服务端继续用 Ask/Bid因为快速行情下服务端现取价很容易和外部策略逻辑不一致。retcode 是交易服务器返回的结果码OrderSend 返回 true 只代表请求已受理不能认为已经成交最终状态以 TRADE_RETCODE_DONE10009为准。如果启用算法交易成交结果还会通过 OnTradeTransaction 异步通知过来。JSON API 的请求-响应模式通常不会主动推送异步结果外部程序要做状态确认就轮询 get_positions 或 get_deals。4. 避坑指南MQL5-JSON-API 使用中五个典型故障与排查下面五条来自实际跑通和修过的部署环境按现象-原因-解决展开。遇到类似症状可以按顺序排查。4.1 现象客户端连接成功但收不到完整响应外部程序 SocketConnect 返回成功发送请求后 MT5 日志显示收到了但响应 JSON 被截断多请求几次后数据开始错位。原因SocketReceive 一次只返回当前可读的字节数TCP 会把一条消息拆成好几分片。单次读就做解析必然遇到半包。另一个常见来源是 MQL5 端发送时多带上一个字符串结尾符。解决接收端做缓冲拼接按消息边界切分。实现成本最低的办法是协议约定每条 JSON 以换行符 \n 结束。服务端循环 SocketReceive把新数据追进缓冲区直到末尾出现 \n 才截取解析。发送端记得去掉 StringToCharArray 产生的结尾符。这个约定也是很多 MQL5-JSON-API 默认采用的消息分帧方式。4.2 现象拉一年 M1 历史数据时 MT5 终端卡死外部请求把 start_time 设到一年前没有限制返回条数MT5 终端界面直接无响应几十秒。原因CopyRates 是同步阻塞调用MQL5 程序跑在终端主线程上一次拉几万根K线会占据大量行情处理时间。解决服务端对单次请求条数做硬限制比如最多 5000 根。需要更长历史时外部客户端分页拉取以上一批最后一根K线时间作为下一次的 start_time。服务端最好再加一层简单的日期缓存重复请求直接命中避免反复打接口。4.3 现象外部程序解析出来的K线时间比图表少 8 小时Python 端打印 K线时间是凌晨 02:00MT5 图表同一根K线显示 10:00。原因CopyRates 返回的 datetime 是 Unix 时间戳本质是 UTC 时间。MT5 图表按经纪商时区显示不同平台有时差两边就对不上。解决协议层统一时间基准。API 返回的 time 字段一律用 UTC Unix 秒外部展示层做时区换算。请求里如果允许传入日期字符串同样要求 UTC 字符串并在文档里写明。最怕的是同一个请求里 start_time 用本地时间、响应又是 UTC对账到凌晨必乱。4.4 现象ZeroMQ 版 API 加载失败客户端连接超时换到 ZeroMQ 封装版本后MT5 端似乎正常外部客户端连接超时日志里有 DLL 加载失败痕迹。原因八成是终端没有开启允许 DLL 导入或者 libzmq 的依赖库缺失、dll 位置放错。解决先在工具-选项-智能交易系统勾选允许 DLL 导入。再把 dll 和 MQL5 脚本放在同一发布目录注意主 dll 的依赖项要一起带过去。部署到新机器时优先选静态编译版本少背运行库环境债。4.5 现象请求体里带特殊字符就解析失败外部传参数带引号、反斜杠或中文MT5 端返回 parse error纯英文数字则正常。原因手写 JSON 解析器没有正确处理转义和 UTF-8一遇到嵌套引号、反斜杠就崩。部分实现为了省事用 StringFind 找大括号做解析经不起揉搓。解决不要自己写 JSON 解析器用成熟库替换。资源包里一般会带 JSON 解析库放在 Include 目录正确引用即可。MQL5 端读取 Socket 数据统一用 CP_UTF8避免中文品种名乱码。我接手任何 MQL5 JSON 资源时第一件事就是确认解析库版本把它换成长期验证过的实现。5. 进阶改造从单连接升级到多客户端与行情推送把基础链路跑通之后单客户端模式在实盘里很快就不够用。策略程序、风控脚本、手工监控面板都要连同一台 MT5需要把接入层从单连接升级成能区分客户端身份的服务模式。5.1 多客户端接入连接句柄按客户端 ID 管理纯 Socket 实现里把全局唯一的 g_client 换成结构体数组每个客户端保存套接字句柄和自己的 id。MQL5 无法直接用自定义类存连接我一般用两个平行数组管理int g_clients[]; string g_client_ids[]; void OnTimer() { // 每轮先接受所有新连接再逐个处理已有连接 int new_client SocketAccept(g_listener); if(new_client ! INVALID_HANDLE) { int size ArraySize(g_clients); ArrayResize(g_clients, size 1); ArrayResize(g_client_ids, size 1); g_clients[size] new_client; g_client_ids[size] client_ IntegerToString(size 1); } for(int i 0; i ArraySize(g_clients); i) { if(g_clients[i] ! INVALID_HANDLE) ReadAndDispatch(g_clients[i], g_client_ids[i]); } }MQL5 数组不能直接删除中间元素客户端断开后把句柄置为 INVALID_HANDLE数组长度只增不减。一次交易会话通常也就几个客户端内存增长量可以接受。真正重要的是连接假死清理MQL5 端没有内置空闲检测要靠在协议里约定心跳。外部客户端 10 秒发一次 ping服务端记录每个客户端上次活跃时间超过 30 秒没有消息就断开清理。多客户端还要考虑响应怎么回到正确的连接。每个连接是独立 Socket请求和响应天然隔离但派生出的异步推送成交回报、风控告警要按客户端订阅项分发这就要在客户端连接握手阶段登记它的订阅列表而不是广播给所有人。5.2 ZeroMQ PUB/SUB 行情推送快照与增量结合实时行情是最容易把 MT5 拖垮的地方。每来一个 tick 就序列化一条 JSON 推送终端主线程根本忙不过来。我的做法是订阅 Tick 后用缓冲区聚合成批固定每 200 毫秒推一批增量void OnTick() { MqlTick tick; SymbolInfoTick(_Symbol, tick); // 先把tick攒到批量缓冲区攒满100条或满200ms再推 g_tick_buffer[g_tick_count] tick; if(GetTickCount() - g_last_push_ts 200 || g_tick_count 100) { PushTickBatch(); } }外部客户端第一次订阅时先用 get_tick 拿一次当前快照之后接收增量自行拼接出连续状态。推送消息的 JSON 只带订阅主题和价格变化比如快照消息{ topic: XAUUSD.tick, type: snapshot, data: { time: 1700000000, price: 2045.5, volume: 100 } }增量消息只带变化字段{ topic: XAUUSD.tick, type: delta, data: { time: 1700000005, price: 2045.6 } }频率上200 毫秒一批对大多数策略足够也能把消息量控制在每秒几十条以内不会堵塞外部程序的消息队列。PUB/SUB 下 ZeroMQ 自己会做断线重连和消息积压但积压不是无限容忍的。外部客户端消费不过来时内存会持续膨胀要在服务端设定发送缓冲区上限超阈值时丢弃最旧的批量消息。5.3 心跳与安全校验长连接最容易忽略的一层长连接跑起来后的隐患是连接假死。外部程序崩溃后 TCP 连接可能还开着MQL5 端不知道反复写入后句柄就开始报错。我在协议里固定增加一组心跳方法外部每 10 秒发一次 ping服务端回 pong同时记录每个客户端最后一次收到消息的时间。超过 30 秒无消息的客户端直接断开。对多客户端数组遍历时标记为超时的连接立即释放句柄。安全校验要前置。MQL5-JSON-API 暴露的是交易能力如果机器被扫描到 5555 端口又没校验等于把下单权限送人。我采用 init 握手外部连接建立后第一条消息必须是 auth 方法并带 token验证通过才把连接标记为可用其余 method 一律返回 auth error。token 放在外部配置文件中不写进代码仓库。6. 一个验证技巧用 Python 脚本对整套 API 做端到端检查接入这套 MQL5-JSON-API 之后第一件事不是写业务代码而是用一个小脚本把整条链路从头到尾验证一遍确认连接、请求、解析、交易通道都通。我习惯把这个脚本保存成 verify_api.py每次改完 MQL5 端代码就跑一遍作为回归测试的基础。脚本逻辑非常简单建立 Socket发送一个 get_tick 请求断言返回的 JSON 里 id 和请求一致、error 为 null再检查结果里包含 symbol 和 price 字段。如果服务端配置了 init 握手先发送 auth 再发业务请求import json import socket def rpc(sock, method_id, method, params): ### 构造JSON请求末尾加\n作为消息边界 request { id: method_id, method: method, params: params } sock.sendall((json.dumps(request) \n).encode(utf-8)) # 循环接收直到收到完整的一行 response while not response.endswith(\n): response sock.recv(4096).decode(utf-8) return json.loads(response.strip()) sock socket.create_connection((127.0.0.1, 5555), timeout5) # 先握手再查实时报价 print(rpc(sock, 1, auth, {token: test_token})) result rpc(sock, 2, get_tick, {symbol: XAUUSD}) print(result) sock.close()发送方在 JSON 后追加换行符和服务端约定的消息边界保持一致接收端循环读直到缓冲区以换行结尾。拿到结果后重点核对三件事id 是否原样返回、error 是否为 null、result 内字段名是否和文档一致。这个脚本可以继续扩展成回归测试把 get_rates、get_positions、order_send 依次执行把每步的响应打印出来。我第一次用类似脚本接入时发现 token 流程就占了一半问题先握手再请求的逻辑一旦跑通后续隐患少了很多。order_send 的验证一定要在模拟账号上跑把价格、手数、品种核对无误后再切真实账户。就算这样首次实盘我仍然会挂只读监控不放开自动交易权限跑一个完整交易时段确认返回码和持仓变化一致。从那以后我每次部署 MQL5-JSON-API 到新环境都强制走一遍这个端到端脚本先验证再谈业务功能。这套资源本身把连接、协议、交易封装都做好了但环境差异永远存在验证脚本就是环境差异的照妖镜。拿到资源包以后先从这一段开始希望这个习惯能帮你在接入时省下几个通宵排查的时间。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询