
简介面向MetaTrader 5平台开发者的MQL5-JSON-API实现源码包用于打通MT5与外部系统之间的JSON数据交互尤其聚焦报价数据实时tick、历史K线的获取与解析。压缩包共11个文件、约49KB核心为mqh头文件含JSON编解码、错误控制等基础库、mq5示例程序JsonAPI专家、JsonAPI指标、md说明文档、sh辅助脚本及license许可文件结构紧凑适合直接参考或嵌入自研交易工具。已有366人浏览学习。通过该资源可掌握套接字与ZeroMQ通信模式在MQL5中的落地写法理解交易API的请求/响应流程并借助附带文档快速定位关键函数与错误处理方法从而更高效地构建多数据源融合的自动化交易系统。 做MQL5开发这几年有一类需求几乎绕不开把EA或者指标里拿到的行情报价、订单状态、账户信息交给外部程序或者反过来让外部系统往MT5里推数据。前两年我处理这些对接靠的是文件读写和CSV代码写起来极其别扭字段一多就乱中文还可能乱码。后来被一个项目逼着完整梳理了一遍JSON方案顺手把整套逻辑沉淀成了通用的MQL5-JSON-API模块今天就把这套东西从设计思路到踩坑细节完整过一遍。这套方案的核心价值很简单用JSON格式统一MQL5程序和外部服务之间的数据交换通过HTTP请求把行情报价、K线数据、账户信息等内容变成结构化数据同时也能解析外部API下发的JSON指令。适合需要把MT5接入自有后台、数据看板、消息通知服务或者第三方行情源的开发者无论你写的是EA、脚本还是指标这套模块都能直接复用。1. MQL5里的JSON处理为什么值得单独做个模块1.1 没有JSON之前MQL5写数据对接有多痛苦很多人刚接触MQL5时会觉得奇怪C语言风格的语法连字典结构都这么难用处理JSON这种嵌套数据是不是得自己写解析器确实MQL5标准库本身没有提供完整的JSON解析方案不像Python或者JavaScript天生就带json.loads()和JSON.parse()。早期做数据交换最常见的做法是拼接字符串、约定分隔符、按行解析一个字段错了整个协议就崩排查起来异常痛苦。用CSV格式还存在几个绕不开的硬伤字段顺序必须严格一致解析端一改顺序就乱嵌套结构完全无法表达遇到内容里包含逗号或换行时更是灾难。而行情报价这种数据天然就是嵌套的——一个交易品种快照包含买价、卖价、时间戳、成交量某个字段还可能本身是个数组只有JSON能干净地表达这类结构这是我把方案定为JSON格式的最核心原因。1.2 行情数据用JSON表达长什么样才合理在设计MQL5-JSON-API的初期我先定义了行情报价数据在JSON里的标准结构这个结构后来一直沿用你可以直接参考{ type: quote, symbol: EURUSD, time: 1710000000, bid: 1.08452, ask: 1.08455, spread: 3, volume: 12.5, tickValue: 1.25 }这个结构本身没什么玄机但有几个设计细节值得展开。type字段用来区分消息类型同一个API接口可以承载行情、订单、账户状态等多种消息解析端第一步就是看这个字段做分发。time统一用Unix时间戳而不是MT5常用的datetime类型好处是JSON跨平台传递时不会有时区歧义外部服务无论是Java、Go还是Python解析都不用做二次转换。bid和ask这类价格字段全部用double直接存MQL5里DoubleToString()的精度控制放到序列化环节去做避免外部拿到一堆科学计数法字符串。有了标准结构之后模块的边界就清晰了一是把MT5的内置数据SymbolInfoTick、AccountInfoDouble等组装成JSON字符串二是解析外部下发的JSON转化成MQL5能直接用的结构体或者变量。这两块虽然方向相反但底层的JSON序列化与反序列化逻辑是共用的所以值得抽象成独立模块。2. 从零搭建序列化与反序列化模块2.1 引入JAson库先解决“能不能用”的问题MQL5社区里流传最广的JSON库是JAson由一名俄罗斯开发者维护Include/Json.mqh就是它。这个库封装了两个核心类CJAVal用于构建和遍历JSON节点CJsonSerializer做得较少但配合用也够用。我这里选择直接用CJAVal理由很实际——它同时覆盖了串行化和解析两件事一个类搞定不用引入太多依赖。安装方式很简单把Json.mqh放到MQL5/Include/目录在代码里#include Json.mqh就能用。如果你在Market或者论坛下载过其他版本注意确认文件里class CJAVal的声明版本差异主要是方法命名上的小改动核心API这些年一直保持稳定。下面这段代码展示了如何把所有字段手动写进JSON节点这是最直观的用法也能让你看清楚JSON序列化到底是怎么回事#property strict #include Json.mqh string BuildQuoteJson(string symbol) { MqlTick tick; if(!SymbolInfoTick(symbol, tick)) { Print(SymbolInfoTick failed: , GetLastError()); return ; } CJAVal root; root[type] quote; root[symbol] symbol; root[time] (long)tick.time; root[bid] tick.bid; root[ask] tick.ask; root[volume] tick.volume; return root.Serialize(); }注意root[time] (long)tick.time这一行的强转tick.time是datetime类型底层其实是uint直接赋值在某些编译器版本下可能有类型警告。转成long再赋值能保证时间戳在64位平台下的统一性也避免负数问题。2.2 解析端反向操作把JSON变回MQL5变量序列化解决了“发出去”的问题反序列化解决“收回来”的问题。解析的关键不是逐字节读而是把整个JSON字符串塞给CJAVal然后用运算符[]逐层取值。我自己写解析行情快照的完整代码是这个样子的bool ParseQuoteJson(string jsonStr, QuoteData quote) { CJAVal root; if(!root.Deserialize(jsonStr)) { Print(JSON deserialize failed); return false; } if(root[type].ToStr() ! quote) { Print(Not a quote message); return false; } quote.symbol root[symbol].ToStr(); quote.time (datetime)root[time].ToLong(); quote.bid root[bid].ToDouble(); quote.ask root[ask].ToDouble(); quote.volume root[volume].ToDouble(); return true; }这里有个从一次次踩坑中总结出来的经验在解析外部API返回时先判断外层类型再逐层取值并且每次取值用的转换函数要匹配。比如时间戳字段用.ToLong()再强转datetime价格字段用.ToDouble()字符串字段用.ToStr()。如果你混用比如拿.ToStr()去取价格字段JAson会返回空值或默认值而且不报错排查起来非常隐蔽。结构体QuoteData需要你自己定义放在模块的头文件里即可struct QuoteData { string symbol; datetime time; double bid; double ask; double volume; };2.3 数组和嵌套对象解析K线数据的关键写法行情快照是单层结构相对简单。但实际对接中更常用的是K线数组——外部API返回一段历史行情通常是一个数组每个元素里又嵌套对象。MQL5里没有原生的foreach遍历JSON数组处理起来有几个固定套路。假设外部返回的JSON长这样{ symbol: XAUUSD, period: M1, candles: [ {time: 1710000000, open: 2150.1, high: 2155.3, low: 2148.7, close: 2153.0}, {time: 1710000060, open: 2153.0, high: 2156.2, low: 2151.5, close: 2154.8} ] }解析代码bool ParseCandlesJson(string jsonStr, MqlRates rates[], int count) { CJAVal root; if(!root.Deserialize(jsonStr)) return false; CJAVal *candles root[candles]; if(candles NULL || candles.Size() 0) return false; count candles.Size(); ArrayResize(rates, count); for(int i 0; i count; i) { CJAVal *c candles[i]; rates[i].time (datetime)c[time].ToLong(); rates[i].open c[open].ToDouble(); rates[i].high c[high].ToDouble(); rates[i].low c[low].ToDouble(); rates[i].close c[close].ToDouble(); } return true; }这里的关键是root[candles]拿到的是CJAVal*指针而且candles[i]返回的也是指针。千万别漏了指针符号漏了之后编译能过但运行时大概率访问非法内存。另一个细节是candles.Size()拿到的是数组元素个数不是字节数所以ArrayResize(rates, count)直接用这个值即可。3. WebRequest请求链路与行情报价对接实现3.1 开启WebRequest权限和URL白名单这一关卡住无数人MQL5程序能否发出HTTP请求不是代码决定的是MT5终端的设置决定的。具体路径在MT5菜单栏“工具”-“选项”-“EA交易”勾选“允许WebRequest”然后在下面的URL列表里填入你要访问的API域名。这个白名单匹配的是域名前缀比如https://api.example.com填完点确定重启终端或者重新编译EA后生效。这个设置最容易出问题的地方在于URL白名单是你设置时终端里已加载的EA和脚本自动重取得如果在你填写白名单之前EA已经加载填完以后必须关闭图表上的EA再重新挂载一次否则WebRequest永远返回-1和错误码4014。这个问题我遇到不止一次每次排查半天最后发现是没重新挂载。3.2 同步请求与响应解析的完整代码流程WebRequest函数是同步阻塞的也就是说它会卡住当前EA的线程直到收到响应或超时。如果是数据同步类任务比如手动触发或者批量拉取同步问题不大如果是在OnTick或OnTimer里高频调用就必须自己做节流。我这里用一个通用函数封装完整的GET请求流程string HttpGetJson(string url, int timeout 5000) { char post[]; char result[]; string resultHeaders; ResetLastError(); int res WebRequest(GET, url, , timeout, post, result, resultHeaders); if(res -1) { Print(WebRequest error: , GetLastError(), , url, url); return ; } if(res ! 200) { Print(HTTP status: , res); return ; } return CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8); }CharArrayToString第三个参数不能省略WHOLE_ARRAY表示整个数组都转换最后那个CP_UTF8参数也很关键如果响应体里有中文或特殊字符不指定UTF-8很容易乱码。POST请求类似只是多一个请求体拼装string HttpPostJson(string url, string payload, int timeout 5000) { char post[]; char result[]; string resultHeaders; string headers Content-Type: application/json\r\n; StringToCharArray(payload, post, 0, StringLen(payload)); ResetLastError(); int res WebRequest(POST, url, headers, timeout, post, result, resultHeaders); if(res -1) { Print(WebRequest error: , GetLastError(), , url, url); return ; } return CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8); }注意StringToCharArray会默认在末尾加一个终止符\0如果你直接把它作为POST请求体发出去部分服务端会解析出错。所以第四个参数要传StringLen(payload)只转换实际内容长度不要带上结尾的\0。这个坑非常隐蔽我曾经排查了整整一个下午最后抓包才发现请求体末尾被塞了个空字节。3.3 超时、重试与错误处理策略行情API对接最怕的不是数据格式错误而是网络不稳定。WebRequest的timeout参数单位是毫秒我建议设成5000到10000之间。太短了容易误判超时太长了EA会长时间卡死行情来了也处理不了。超时后的重试不是简单循环请求而是要做退避。我常用的策略是第一次失败后等2秒重试第二次失败后等5秒重试第三次失败后记录错误并放弃等下一个调度周期再重新尝试这个退避逻辑在MQL5里用EventSetTimer配合静态变量可以实现核心代码如下int g_retryCount 0; void ProcessQuoteRequest() { string response HttpGetJson(https://api.example.com/quotes/EURUSD); if(response ) { g_retryCount; Print(Request failed, retry count: , g_retryCount); if(g_retryCount 3) { g_retryCount 0; Print(Give up this round, wait for next timer event); } return; } g_retryCount 0; QuoteData quote; if(ParseQuoteJson(response, quote)) { Print(Bid, quote.bid, Ask, quote.ask); } }每次OnTimer触发时调用这个函数如果失败次数累加到3次就暂时放弃等下一轮周期再试。这样既不会无限重试拖死EA也不会因为一次失败就永久断掉。4. 高频调用下的避坑经验与性能优化4.1 定时器频率和请求节流别把API打崩我要做的MQL5-JSON-API模块最常被问到的问题就是能不能在OnTick里每次价格变动都请求一次外部API技术上可以但实践上非常不建议。MT5的OnTick在活跃行情下可能每秒触发多次而绝大多数信号源API的限流阈值都在每分钟几十次到几百次之间。如果每次都发请求不仅大概率触发服务端限流被拉黑EA自身的执行也会被同步请求卡住。我一般建议用EventSetTimer控制调度频率行情刷新间隔设在1到5秒之间比较合理。如果确实需要秒级更新也可以把WebRequest做成异步但MQL5没有原生的async/await要自己模拟队列复杂度会上去非必要不搞。先用同步加1秒定时器性能完全够用。4.2 大响应体解析是EA卡顿的隐形杀手有一次我拉取了一段一年的M1 K线数据返回的JSON足有几百KBParseQuoteJson跑完后EA直接卡了好几秒。原因是CJAVal解析时会为每个节点分配额外的对象内存大数组的分配开销完全在EA线程里执行卡顿是必然的。优化思路有几种一种是限制单次请求的数据量比如分页拉取每次只请求1000根K线另一种是把数据量大、实时性要求不高的请求放到单独的脚本里做或者用OnTimer在非交易时段错峰拉取还有一种更彻底的做法是请求和解析拆分到DLL里去处理但这对普通开发者来说门槛偏高。最省事的还是设计API时就控制响应体大小。4.3 中文、转义字符和编码问题JSON本身是UTF-8编码但MQL5的字符串是UTF-16中间转来转去很容易出问题。一个典型场景是如果外部API返回的JSON里有中文的品种名称或者注释字段你用CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8)拿到字符串后传递给打印函数时一般没问题但如果再次序列化发出需要确保编码没有变成乱码。如果遇到外部API返回中文乱码的问题常见原因是HTTP响应头里的Content-Type没有标明charsetutf-8部分服务端默认用ISO-8859-1编码。这种问题MQL5端很难彻底处理只能在请求头里强制加上Accept-Charset: utf-8并和服务端约定好统一编码。另外字符串里包含引号、反斜杠、换行符时序列化库会帮你做转义手工拼接JSON字符串时要注意。本文还有配套的精品资源点击获取