简介:面向MT4平台二次开发者与外汇量化交易人员,覆盖DataFeedAPI、ManagerAPI、ReportAPI三大接口的完整开发文档资源包。内容结合mtmanapi.dll与mtmanapi64.dll库文件,不仅拆解DataFeedAPI的实时行情订阅、历史数据请求、自定义指标与新闻/信号源集成,ManagerAPI的账户创建修改删除、订单开平仓与挂单、客户权限设置、服务器监控,还覆盖ReportAPI的交易记录查询、财务盈亏报告、风险指标计算与多维度统计分析,并整理出从开发环境搭建、库文件引入、代码编写、测试调试到部署维护的完整路径,专门提示MT4安全规范、用户隐私与版本兼容性等易错点。资源共463个文件,以h头文件与cpp示例源码为主体,搭配vcproj/sln工程文件、def导出定义、pdf说明文档,以及少量php/html辅助脚本和可运行dll/exe;压缩包仅15.02MB,目录按数据接入、管理端、报表等模块组织,便于按需查阅与复用。目前已有6864人学习下载,适合具备一定C++/C#基础、希望深度集成MT4、开发EA自动化交易系统或后台管理工具的中高级开发者参考。
1. MT4 API 开发文档:从这套资料里最先该读懂的三个层
很多人拿到一份 MT4 API 开发文档,第一反应是打开 MQL4 语法开始啃,结果啃到一半发现真正要解决的问题不在语法层。我当初接一个桥接项目,技术负责人丢过来这份文档,说“把 MT4 的 API 摸清楚”,我翻了两个小时才意识到,文档里其实藏着三套完全不同的接口:MQL4 内置的交易函数、Manager API 的管理通道、以及 WebRequest 这类外部通信接口。这三套接口解决的是三件不同的事——策略端怎么下单、服务端怎么管单、外部系统怎么传数据。如果你连自己要走哪条线都没定,后面的开发全是在打转。这份文档适合三类人:写 EA 的策略开发者、要给 MT4 对接外部 CRM 或风控系统的后端工程师、以及想自建信号分发的团队。下面我把这三条线拆开讲,按实际开发顺序走一遍。
2. 搭建本地开发环境:终端版本、账户类型与工具链
2.1 先确认终端版本和服务器归属,再谈开发
MT4 的 API 开发第一步不是写代码,是把环境搞对。文档里反复出现一个词:build。终端的 build 版本直接决定你用的函数库是否被支持。老教程里很常见的是 build 600 到 900 时代的写法,那时候OrderSend的参数限制、MQLInfoInteger的返回逻辑跟现在差异不小。我一般建议先到官网下载最新的 MT4 终端,登录一个模拟账户,然后在 MetaEditor 里把#property version写到 1.10 以上,这能规避大部分旧 API 的坑。
另一个容易被忽略的点是服务器归属。同一个经纪商的 MT4 终端,可能同时挂着十几个数据中心,比如 live01、live02、demo03。API 开发时如果你用错了服务器,Manager API 连不上、WebRequest 拿到的报价也对不上,排查起来非常难受。正确做法是打开终端右下角看连接状态,把服务器名完整记到开发文档的配置页里。
提示:文档里给的函数清单再全,也不要跳过真实终端环境验证。API 的行为在模拟盘和实盘服务器上表现不一样,尤其是订单执行时的返回码。
2.2 MetaEditor 里的工程组织:把 EA、脚本、Include 分开
MT4 的代码目录是分层的,默认安装路径下是MQL4\Experts、MQL4\Scripts、MQL4\Include和MQL4\Files。我见过不少新手把所有文件塞进 Experts 目录,结果 Include 文件夹里引用不到自定义库,编译直接报错。建议一开始就把工程拆分好:把通用的交易工具函数放进 Include,把策略入口放在 Experts,把一次性校准工具放在 Scripts。
下面是一个标准的目录结构,用 bash 列出:
MQL4/ ├── Experts/ │ ├── EmaCrossEA.mq4 # 主策略文件 │ └── EmaCrossEA.ex4 # 编译产物 ├── Include/ │ ├── TradeHelper.mqh # 通用交易函数库 │ └── RiskManager.mqh # 仓位与止损工具 ├── Scripts/ │ ├── CheckSymbolInfo.mq4 # 校验合约参数的脚本 │ └── ExportTicks.mq4 # 导出 tick 数据的脚本 └── Files/ └── signals_external.json # 外部信号缓存文件这段目录结构的逻辑是:Experts放编译入口,Include放可复用的封装,Scripts放调试工具,Files放运行时写入的临时数据。新手最容易翻车的点是Files目录的读写路径——MQL4 里FileOpen("signals_external.json", FILE_READ)是相对于MQL4\Files寻址的,如果你在绝对路径上写C:\Users\...,会直接在测试环境里返回失败。
2.3 连接模拟盘:从 MetaQuotes-Demo 开始的正确姿势
开发阶段的账户选择有讲究。文档里不会提醒你的是,不同经纪商的模拟盘服务器在 API 返回上可能有细微差别,比如MarketInfo(Symbol(), MODE_TICKVALUE)返回的数值精度不一样。我习惯先用官方 MetaQuotes-Demo 做语法验证,再用实际目标经纪商的模拟盘做功能验证,最后才切到实盘小账户跑灰度。
连接模拟盘的步骤很简单:终端登录窗口选择“开设模拟账户”,填一个不会用到的邮箱,选择服务器 MetaQuotes-Demo。登录后在Navigator窗口里能看到账户号,这个号码是 Manager API 和 WebRequest 校验身份的关联键,别弄丢。
3. MQL4 实战:把指标信号转成自动下单的完整流程
3.1 订单执行链路的代码骨架:从 OnTick 到 OrderSend
现在进入文档的核心区——MQL4 API。大多数 EA 的运转都依赖OnTick()作为主循环,每来一个新报价就执行一次判断。写订单逻辑时,最基础也最关键的函数是OrderSend(),它一次调用负责检查、下单、返回订单号或错误码。
下面是一个完整的双均线策略下单骨架:
#property strict input double LotSize = 0.01; input int FastMAPeriod = 10; input int SlowMAPeriod = 30; input int StopLossPoints = 200; input int TakeProfitPoints = 400; input int MagicNumber = 20240501; bool IsNewCross(int fastPeriod, int slowPeriod) { double fastPrev = iMA(_Symbol, _Period, fastPeriod, 0, MODE_EMA, PRICE_CLOSE, 1); double fastCurr = iMA(_Symbol, _Period, fastPeriod, 0, MODE_EMA, PRICE_CLOSE, 0); double slowPrev = iMA(_Symbol, _Period, slowPeriod, 0, MODE_EMA, PRICE_CLOSE, 1); double slowCurr = iMA(_Symbol, _Period, slowPeriod, 0, MODE_EMA, PRICE_CLOSE, 0); // 前一根K线快线在慢线下方,当前K线快线上穿,判定为金叉 return (fastPrev <= slowPrev && fastCurr > slowCurr); } void OnTick() { if (!IsNewCross(FastMAPeriod, SlowMAPeriod)) { return; } // 先检查当前 Symbol 是否已有该 MagicNumber 的持仓 int count = 0; for (int i = OrdersTotal() - 1; i >= 0; i--) { if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) { if (OrderSymbol() == _Symbol && OrderMagicNumber() == MagicNumber) { count++; } } } if (count > 0) { return; // 已有持仓,避免重复开单 } double price = Ask; double sl = (StopLossPoints > 0) ? price - StopLossPoints * _Point : 0; double tp = (TakeProfitPoints > 0) ? price + TakeProfitPoints * _Point : 0; int ticket = OrderSend(_Symbol, OP_BUY, LotSize, price, 3, sl, tp, "EMA Cross EA", MagicNumber, 0, clrGreen); if (ticket < 0) { Print("OrderSend failed, error code: ", GetLastError()); } }代码里的关键点有四个。IsNewCross函数通过对比前一根和当前 K 线的均线位置捕捉交叉,这里用的是iMA的 shift 参数,1 代表前一根 K 线,0 代表当前正在形成的 K 线。OrderSelect按持仓位置遍历,通过OrderMagicNumber()识别是不是本 EA 的仓位,防止同一策略在同一品种上重复下单。OrderSend的滑点参数我给了 3 点,实际要根据经纪商服务器质量调整,服务器差的话 3 点会频繁重报。最后GetLastError()必须打印出来,这是后面排查所有异常的依据。
3.2 参数与风控:止损计算、手数限制与错误码表
LotSize不是拍脑袋填的。实盘环境里,账户杠杆、合约大小、保证金率共同决定了一单能否成交。文档里MarketInfo系列函数就是为这个服务的。常用的几个参数是MODE_MARGINREQUIRED(每手占用保证金)、MODE_MINLOT(最小手数)、MODE_LOTSTEP(手数步进)。写仓位计算函数时,必须用MathFloor把最终手数对齐到MODE_LOTSTEP,否则OrderSend会返回无效参数错误。
下单失败的错误码是开发文档里最值得背的部分。4108表示无效交易参数,通常是sl或tp离现价太近;4109是无效价格,常见于用Close()而不是Ask/Bid作为开仓价;4110是止损距离小于STOPS_LEVEL,这个值从MarketInfo(Symbol(), MODE_STOPSLEVEL)读取,部分经纪商锁仓或限制严格,直接写 200 点可能过不了。
我一般会在下单前做一次三层校验:手数是否在MODE_MINLOT到MODE_MAXLOT区间、止损距离是否大于MODE_STOPSLEVEL、账户可用保证金是否充足。三层校验都过了再调OrderSend,这样能把错误码从“未知翻车”变成“已知边界的正常拦截”。
4. 官方 API 与数据传输:Manager API、WebRequest 与文件接口
4.1 Manager API:从服务器端拉取订单与账户状态
策略端用 MQL4 搞定下单后,接着要解决的是“外部系统怎么拿到这些订单”。MT4 官方提供了一套 Manager API,它不跑在终端里,而是一套独立的动态链接库,用 C++ 或 C# 调用,直连经纪商的交易服务器管理端口。这套接口的登录凭证是管理员的账号密码,不是普通交易账户。
Manager API 最常用的是拉取订单列表和账户信息。调用流程一般是初始化 API 实例、连接服务器、登录、查询、关闭连接。C++ 伪代码大概是这样:
#include "ManagerAPI.h" CManager* manager = new CManager(); if (!manager->Connect("live01.broker.com:443", 10000)) { printf("connect failed, error=%d\n", manager->GetLastError()); return -1; } if (!manager->Login("manager_login", "manager_password")) { printf("login failed\n"); return -1; } COrderInfo info; int total = manager->GetOrdersTotal(); for (int i = 0; i < total; i++) { if (manager->GetOrderByIndex(i, &info)) { printf("order=%d symbol=%s lots=%.2f open_price=%.2f\n", info.order, info.symbol, info.volume, info.open_price); } } manager->Disconnect(); delete manager;这段代码里Connect的端口不是终端连接端口,而是服务器配置里的 Manager API 端口,很多经纪商会用独立的端口并做 IP 白名单。GetOrderByIndex是按索引拉单,性能上适合做全量同步;如果只想拿增量,可以用GetOrderByTicket按单号定位。文档里最容易被忽略的是登录方式:Manager API 使用密钥文件(.key),不是纯密码登录,这就是为什么很多人拿着账号密码连不上。
4.2 WebRequest 与外部 REST 网关:把交易信号送给远端服务
如果不想碰 C++ 的编译链,MT4 还有一条轻量通道——WebRequest()函数。它允许 EA 在满足白名单配置的前提下,向指定域名发送 HTTP 请求。我用它把 EA 的订单状态推送到自建的 Flask 网关,再由网关转发给 CRM 或企业微信机器人。
启用 WebRequest 要两步。第一步是把目标域名加进终端设置的白名单,路径是工具->选项->专家顾问->允许 WebRequest。第二步是在 EA 里设置#property strict并填写完整 URL。下面是一个推送示例:
string BuildPayload(int ticket, string symbol, double price) { // 手动拼 JSON,注意转义双引号 return StringFormat("{\"ticket\":%d,\"symbol\":\"%s\",\"price\":%.2f}", ticket, symbol, price); } void PushOrderToServer(int ticket, string symbol, double price) { string json = BuildPayload(ticket, symbol, price); string headers = "Content-Type: application/json\r\n"; char postData[]; int dataLen = StringToCharArray(json, postData, 0, StringLen(json)); // StringToCharArray 会在末尾补一个终止符,需要去掉 dataLen--; char result[]; string resultHeaders; int status = WebRequest( "POST", "https://your-gateway.example.com/api/order", headers, 3000, // 超时 3 秒 postData, dataLen, result, resultHeaders ); if (status == -1) { Print("WebRequest failed, error=", GetLastError()); } }这段代码的坑集中在两个地方。StringToCharArray默认会写入\0终止符,所以发送的数据长度要减掉 1,否则网关收到的 JSON 末尾带一个看不见的字符,解析直接报错。WebRequest的返回值是 HTTP 状态码,200 表示网关已接收;返回 -1 时要立刻查GetLastError(),常见错误是4060(URL 不在白名单)和4062(请求格式不合法)。
4.3 文件接口:无网络环境下的订单导出兜底方案
金融公司内部网络经常做隔离,外网 HTTP 请求被防火墙挡死。这时候文件接口是最后的兜底。MQL4 的FileWrite能把订单数据落成 CSV,再由独立的后台任务读取上传。
文件导出要注意FILE_SHARE_READ标志,如果 EA 在写入时没加共享读权限,外部程序会拿不到文件句柄,读到的是空内容。
5. MT4 API 开发避坑:五个高频故障的排查记录
5.1 连接超时:终端能打开,但 Manager API 连不上
现象:终端正常显示行情,但 Manager API 调用Connect一直超时。
原因:终端连接的是行情端口,Manager API 连接的是独立的管理端口。两个端口不对等,经纪商管理端口往往还有 IP 白名单限制。
解决:在文档配置表里找到服务器完整域名,再向经纪商要管理端口和密钥文件;开发阶段尽量用自己的经纪商模拟盘环境,对方会提供单独的 Manager 测试端口。
5.2 OrderSend 返回 4109:价格参数用了 Close()
现象:策略在回测里跑得好好的,实盘一启动就连续报 4109 下单失败。
原因:回测中Close()取得的 K 线收盘价和实盘当前Ask/Bid不一致。OrderSend对价格有效性有强校验,收盘价一般不是当前可成交价。
解决:所有开仓买单价用Ask,卖单价用Bid,不要在订单函数里传Close()或Open()。如果设计上就是以收盘价做信号,那下单价格仍要映射到Ask或Bid上执行。
5.3 WebRequest 返回 4060:域名加了白名单还是不生效
现象:终端设置里已经把https://your-gateway.example.com加进白名单,调用仍然返回 4060。
原因:MT4 的白名单严格匹配协议头和端口。如果 EA 里写的是http://而白名单加的是https://,或者 URL 带了自定义端口,都会判定不匹配。
解决:在 EA 代码里完整写出协议头、域名和端口,与设置里的条目保持一致。调试时先访问一个已知可用的 URL,确认WebRequest基础能力没被禁用。
5.4 历史数据回测信号漂移:同一个 EA 回测和实盘持仓不一致
现象:回测里每天开两单,实盘跑了一周开了八单,入场位置明显偏了。
原因:回测默认用历史 K 线数据重算指标,而实盘OnTick是逐 tick 驱动的。如果 EA 里用了iClose这类依赖未收盘 K 线数据的函数,最后一根 K 线在回测和实盘中数值不同,信号自然偏。
解决:统一所有指标读取用已收盘 K 线,即 shift 从 1 开始读。信号产生后,用Volume或者时间戳做一次“仅新 K 线触发”的节流,避免一根 K 线内重复触发。
5.5 OnTick 与挂单修改冲突:同一个账户被两个 EA 同时操作
现象:两个 EA 跑同一个品种,一个负责止损,一个负责开仓,结果止损单被反复修改,最终被经纪商拒单。
原因:没有协调好两个 EA 对同一订单的写入权限。MT4 默认多个 EA 可以操作同一账户的同一订单,最后写入方会覆盖前一个设置。
解决:划定账户区域,比如开仓 EA 使用 MagicNumber 2024,止损管理 EA 使用 MagicNumber 2025,并在止损 EA 的OrderSelect里只选中自己 MagicNumber 范围内的单子。
6. 进阶验证:从模拟盘到小资金实盘的测试清单
环境切换这一步,我习惯把它做成一页检查清单,而不是靠脑子记。从模拟盘切到实盘时,第一件事是确认档位和 STOPS_LEVEL 的差异,同一经纪商的模拟盘和实盘这两套数值经常不一致,尤其在高波动货币对上。
第二件事是验证 WebRequest 网关在实盘环境里的超时表现。模拟盘服务器通常在数据中心内网,延迟在 5 毫秒以内;实盘信号推送后,如果网关在慢速网络下响应超过 3 秒,EA 的推送逻辑会被锁住,影响下一次开仓判断。我一般把超时时间调到 1 秒,推送失败也不阻塞主交易流。
第三件事是检查日志。MT4 的Experts日志和Journal日志是两组不同的记录,Expert 日志记的是Print输出和GetLastError的错误码,Journal 记的是终端的全局事件。排查下单问题时,我通常先看 Expert 日志的最后一个OrderSend failed错误码,再去 Journal 里对照同一个时间戳有没有服务器拒绝记录,这条链路走完基本能定位 80% 的问题。
下面这组检查项我会固化成一个表格:
| 检查项 | 模拟盘操作 | 实盘切换操作 |
|---|---|---|
| STOPS_LEVEL | 读取当前值 | 重新读取,确认与模拟盘一致 |
| 最小手数 | MarketInfo读取 | 确认与账户类型匹配 |
| WebRequest 白名单 | 网关测试站 | 切换到生产网关域名并重测 |
| 订单错误码日志 | 保留全部 | 保留最近 500 条,便于追溯 |
| 点差模式 | 固定点差 | 浮动点差时预留滑点余量 |
最后聊一个小技巧:每次切换环境,我都会在 EA 的init里加一段环境标识打印,把服务器名、账户类型、杠杆和MODE_STOPSLEVEL一次性输出到日志。之前有过一次翻车,实盘上线后一单都没开,日志里全是没有上下文的 4109,后来才发现是账户杠杆从模拟盘的 1:500 换到了实盘的 1:100,手数计算越界了。从那以后我每次切环境都强制走一遍环境标识打印,确认参数落位,再放第一单实盘。这个习惯救过我很多次,希望帮到你。
本文还有配套的精品资源,点击获取