
简介面向MT4开发者的完整API技术文档包系统覆盖DataFeedAPI、ManagerAPI与ReportAPI三大接口体系适合需要开发EA、自定义插件、数据集成或后台管理工具的中高级交易平台开发者。资源共463个文件、约15.02MB以C/C头文件与源文件h/cpp、Visual Studio工程vcproj/sln、PDF说明文档及PHP调用示例为主同时附带mtmanapi.dll与mtmanapi64.dll动态库便于直接查阅和二次开发。已有6869人浏览学习。通过示例工程可快速掌握账户管理、订单操作、报表生成、数据订阅及风险统计等关键接口的调用方法并了解不同版本库的兼容使用方式是一份贴近实战的MT4二次开发参考资料。1. MT4 API 完整开发文档先搞清楚这份文档到底值不值得啃MT4 的 API 开发文档说白了就是 MQL4 语言参考手册加交易服务器接口说明。刚接触的人以为拿到文档就能写 EA真正翻完才发现订单函数、价格引用、指标句柄这些细节全藏在注释和示例里没人带着走一遍很容易卡壳。这份文档覆盖了从 OrderSend 到指标调用、文件读写、DLL 扩展的完整链路适合两类人一是刚从 MQL4 语法入门、准备写第一个自动交易程序的开发者二是接手别人源码但排查不出逻辑问题、需要按 API 规范重审每一处函数调用的老手。我按实际开发顺序把它拆成六块每一块都会给出能直接抄进编辑器的代码和容易翻车的边界。2. 从 OrderSend 到订单生命周期交易函数与参数边界2.1 OrderSend 的参数玄学OrderSend 是 MT4 里最核心的交易函数文档里写得很简略但实际调用时参数的顺序、类型和服务器规则一个都不能错。典型签名是int OrderSend( string symbol, // 交易品种 int cmd, // 订单类型OP_BUY0, OP_SELL1, OP_BUYLIMIT2, OP_SELLLIMIT3, OP_BUYSTOP4, OP_SELLSTOP5 double volume, // 手数 double price, // 开仓价格 int slippage, // 最大滑点单位点 double stoploss, // 止损价 double takeprofit, // 止盈价 string comment, // 订单注释 int magic, // 魔法码用来区分策略 datetime expiration, // 挂单过期时间市价单填0 color arrow_color // 图表箭头颜色 );常见做法是先定义交易参数再调用而不是把一堆数字堆在括号里double ask MarketInfo(EURUSD, MODE_ASK); double bid MarketInfo(EURUSD, MODE_BID); int ticket OrderSend( EURUSD, OP_BUY, 0.01, ask, 10, ask - 20 * _Point, ask 40 * _Point, demo buy, 20250101, 0, clrGreen );参数的坑集中在几个地方。price 必须用服务器认识的报价EURUSD 买盘就是 ask卖盘就是 bid如果反了会被拒。slippage 的单位是点不是货币单位10 表示最多滑 10 个点。stoploss 和 takeprofit 对市价单是绝对价对挂单是相对于挂单价的距离文档里这点写得容易误解。magic 建议用当前日期的整型值比如 20250101这样回测和实盘统计时能按 magic 过滤订单。还有一点OrderSend 是异步的它返回的 ticket 是服务器受理后的订单编号。如果返回 -1必须马上用GetLastError()查错误码常见的是 130错误止损/止盈、131错误价格、138需重新报价。我一般会封装一个带错误日志的下单函数否则翻车时连哪儿错都不知道。2.2 订单状态机与 OrderSelect 的坑MT4 的订单不是即时更新的必须显式调用 OrderSelect 才能把订单池里的记录索引到内存。订单池分“已平仓”和“未平仓/挂单”两种OrderSelect(ticket, SELECT_BY_TICKET, MODE_TRADES)查当前持仓MODE_HISTORY查历史。很多人一开始没搞明白OrderSelect 返回 true 不代表选到的就是你想操作的那单因为SELECT_BY_POS模式下传入的是池内序号而池内顺序会随订单开平不断变化。常见写法是遍历所有持仓for (int i OrdersTotal() - 1; i 0; i--) { if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) { if (OrderMagicNumber() magic) { // 处理当前策略的订单 double openPrice OrderOpenPrice(); double sl OrderStopLoss(); } } }从后往前遍历是避免平仓后订单数量变化导致索引跳过的标准姿势。这里容易踩的坑是OrderSelect(i, SELECT_BY_POS)成功后如果紧接着调用另一个 OrderSelect 选区间的订单之前的选中状态会被覆盖。所以需要同时操作多个订单时先复制关键字段到本地变量再切换订单池别一直依赖 Order 系列函数读取。2.3 用 Magic Number 管理多策略订单Magic Number 是订单归属的身份证回调测试和实盘统计都靠它。同一个 EA 里多个策略模板同时跑如果 magic 混用平仓逻辑会误伤别的策略的单子。我习惯在 EA 的 input 里直接暴露 magicinput int MagicNumber 20250101; input int MaxOrders 5; input double LotSize 0.01;然后在开仓和遍历平仓时统一用这个变量。还有一点MT4 的策略测试器默认不按 magic 隔离而是忽略 magic。所以回测时如果同一图表挂了多个 EA结果会互相干扰。我一般会每次回测只挂一个 EA或者在一个 EA 里按 magic 分成多个子策略回测结果才可信。文档里还提到 OrderTicket、OrderType、OrderLots 这些只读字段。实际开发中修改订单用 OrderModify只能改止损、止盈、挂单价不能改手数和品种这是交易服务器规则API 层面也无法绕过。3. 行情数据与指标调用OnTick 之外的取数姿势3.1 引用内置指标iMA/iRSI 的句柄与复制MQL4 调用内置指标有两种方式一种是直接用iMA(symbol, timeframe, period, shift, method, price, bar)这种带输出值的写法每调用一次就内部计算一次另一种是用iMAHandle创建句柄配合CopyBuffer批量取值。前者代码短但效率低尤其在高频 tick 里反复调用会导致性能瓶颈。后者是文档推荐的高效姿势int maHandle iMA(EURUSD, PERIOD_M15, 20, 0, MODE_SMA, PRICE_CLOSE); double maBuffer[]; ArraySetAsSeries(maBuffer, true); int copied CopyBuffer(maHandle, 0, 0, 100, maBuffer); if (copied 0) { double currentMA maBuffer[0]; // 当前K线对应的值 }这里的坑是CopyBuffer 的返回值和数组对齐方向。MT4 默认数组索引 0 是最近时间还是最远时间取决于 ArraySetAsSeries。如果不设置buffer[0] 是最早的数据很多新手误以为 [0] 是当前值导致信号错位。设置成ArraySetAsSeries(maBuffer, true)后buffer[0] 才是当前 K 线。指标句柄创建后不会自动更新需要在 OnTick 里重新 CopyBuffer。另外 iMAHandle 本身不区分货币对大小写但 symbol 字符串必须精确匹配图表品种比如 EURUSD 和 eurusd 会被识别为不同品种而返回无效句柄。3.2 跨周期取数据的典型写法EA 在 M5 图表上运行但要依赖 H1 的均线信号这个需求在文档里没有直接范例需要组合 iCustom 或 iMA 指定时间段。最直接的方式double h1MA iMA(NULL, PERIOD_H1, 20, 0, MODE_SMA, PRICE_CLOSE, 1);注意最后一个参数 bar1 表示往前数一根已完成 K 线。这里容易翻车如果不指明 bar默认当前这根 K 线是未收盘的它的值会随行情跳动而改变回测和实盘不一致。所有跨周期信号引用我都建议强制使用shift 1甚至shift 2避开正在形成的K线。另一个跨周期取历史数据的方式是iBarShift把时间戳转换成指定周期的K线序号datetime targetTime iTime(NULL, PERIOD_H1, 1); int shiftOnM5 iBarShift(NULL, PERIOD_M5, targetTime, false);iBarShift 是同步 K 线的桥梁。它的第四个参数 exact 设为 false 表示找不到精确时间时返回离目标最近的一根设为 true 则严格要求存在对应 K 线。不同经纪商的 K 线开盘时间戳可能有几秒偏差我一般用 false 并接受轻微偏差避免返回 -1 导致程序越界。3.3 自定义指标脚本的接入与性能文档里自定义指标文件放在MQL4/IndicatorsEA 里用iCustom调用double myValue iCustom(NULL, 0, MyIndicator, param1, param2, 0, 1);参数顺序必须和指标源码的 input 变量顺序一致多得一个不少少一个也不行。很多指标打包过来没有源码只有 ex4这时没法确认内部 input 顺序只能靠文档或黑盒测试。我遇到这种事会在小号测试账户上挂一个 EA 轮询 iCustom把不同参数组合的输出打印到日志找出能输出非零值的那组参数再固化下来。iCustom 的性能瓶颈在于每次调用都会重新计算整个指标缓冲区。如果指标计算复杂tick 密集时回测会慢好几倍。替代方案是把指标逻辑用原生 MQL4 写到 EA 里或者只在开盘第一根 tick 计算一次用Volume[0] 1这类条件做开关。我自己常用的是后者比如只在新的 M15 K线生成时更新指标缓存避免每个 tick 都重复算。4. 把文档变成可运行 EA完整代码骨架与编译验证4.1 EA 骨架init/deinit/tick 三段式MQL4 EA 最少要有 init、deinit、start老版本或 OnTick新版本三个函数。文档里推荐用 OnTick因为 start 已经被逐步淘汰。标准骨架#property strict extern double Lots 0.01; extern int StopLoss 30; extern int TakeProfit 60; int init() { Print(EA 初始化); return 0; } int deinit() { Print(EA 卸载); return 0; } void OnTick() { if (IsNewBar()) { CheckForTrade(); } } bool IsNewBar() { static datetime lastBarTime 0; datetime curTime iTime(NULL, 0, 0); if (lastBarTime ! curTime) { lastBarTime curTime; return true; } return false; }这里的逻辑是init 里做一次性资源初始化比如创建指标句柄、读取设置文件deinit 里释放句柄OnTick 里用 IsNewBar 控制每个 K 线只运行一次主逻辑避免在同一个 K 线内重复开平仓。IsNewBar 的 static 变量跨 tick 保持是控制频率最便宜的方法。init/deinit 的返回值类型必须匹配否则编译器警告。早期版本 init 返回 intdeinit 返回 int新版本可以写void OnTick但 init/deinit 保持原样。文档里没有明确说 init 失败时如何阻止 EA 启动我的做法是在 init 里判断合约数字格式等条件不满足就return(FAIL)让 EA 无法加载比在 OnTick 里反复打印错误更干净。4.2 参数优化与输入变量列表EA 的输入参数用 extern 或 input 定义。extern 和 input 的差异在于是否支持策略测试器的优化功能。input 是 MQL4 新标准input group 交易设置 input double InitLots 0.01; input int MaxTrades 3; input group 移动止损 input bool UseTrailing true; input int TrailStart 20; input int TrailStep 5;group 是 MQL4 后来加的注释性分组不是所有编译器版本都支持旧编译器遇到会报错。参数命名建议带类型前缀InitLots、MaxTrades 这样回测时看报告一目了然。参数能被优化的前提是它属于 int、double、bool 或 datetimestring 不能参与优化。在策略测试器里勾选“优化”后MT4 会按步长遍历参数组合。这里关键的坑是优化结果里浮点数的步长不能太小否则组合数量爆炸一个 5 年的 M15 回测要跑几十个小时。我一般先把参数步长设成较大的倍数跑一轮粗糙优化确定热区后再缩小步长二次优化。还有每次优化时点“全部”而不是“最好”否则测试器只给你最优参数看不到参数的稳定性分布。4.3 编译、历史回测与实盘前置检查在 MetaEditor 里按 F7 编译零警告零错误也不代表策略可上实盘。文档强调要在策略测试器里先跑一遍“控制点”测试用固定点差模式、开仓手续费设置为零、滑点为 0这样能把交易逻辑和成交成本剥离开。回测结果里必须看几个关键列总净利润、最大回撤、获利因子、平均盈利/亏损比、最大连续亏损次数。一个容易忽略的检查项是“每笔最大亏损”是否远小于账户余额的 5%~10%。如果某次回测里出现单笔亏损超过总权益 20%说明仓位管理或硬止损缺失这种 EA 实盘前必须改。另一个检查是回测与实盘数据偏差回测用的是历史固定点差实盘点差会波动滑点也会吃掉利润。我会用实际账户过去三个月的平均点差加上 20% 缓冲重新估算盈亏比。实盘前置检查我习惯列一个清单品种的最小手数和步进手数是否匹配参数止损距离是否满足经纪商最小距离服务器时区与 K 线时间戳是否一致VPS 环境是否允许 DLL 调用。这些都过了再拿最小手数跑三天模拟盘观察 EA 是否有网络重连、订单拒单等只在实盘出现的意外。5. 避坑指南MQL4 开发中我踩过的五个坑5.1 浮点比较与 Price Level 校验现象EA 判断 1.20000 和 1.19999 是否相等用直接比较结果逻辑时对时错。原因MT4 报价是浮点数1.2 在二进制里是无限循环小数浮点运算会产生微小误差直接相等比较不稳定。解决用NormalizeDouble处理价格和手数保持与 broker 小数点一致后再比较double ask MarketInfo(EURUSD, MODE_ASK); double stopLoss NormalizeDouble(ask - 20 * _Point, _Digits); if (stopLoss 0 stopLoss ! ask) { // 这里仍不适合直接比较更可靠的是比较最小价差if (MathAbs(price1 - price2) _Point / 2) { // 视为相等 }从那以后我所有价格比较都加上了基于_Point的容差再也没出现过因为精度问题导致的重复开仓。5.2 止损/止盈价格被服务端拒收现象OrderSend 返回 -1GetLastError 返回 130。单子没开出去但程序里价格算得正正好好。原因止损距离小于经纪商要求的最小止损距离或者止损价超出了当前报价的合理范围。很多斩仓商要求止损距离至少 20 点你设 5 点就会被拒。解决开仓前先查询经纪商的最小止损距离没有直接 API 时用 MarketInfo 的 MODE_STOPLEVELint stopLevel MarketInfo(EURUSD, MODE_STOPLEVEL); double minDist stopLevel * _Point; if (mathAbs(ask - stopLoss) minDist) { stopLoss ask - minDist - 1 * _Point; }还要注意预付款货币与账户货币不一致时止损距离换算可能出错优先用经纪商给的 STOPLEVEL别自己拍脑袋写死 20 点。5.3 循环中 OrderModify 与无限递归现象EA 卡死或出现大量重复订单日志显示 OrderModify 被连续执行成百上千次。原因OrderModify 成功后订单池数据变化触发 OnTick 再次进入如果修改条件仍然满足就形成了自我触发循环。常见于移动止损逻辑价格每次跳动都回调一次止损回调判断没加上次修改值做门槛。解决每次修改前记录当前止损只有新止损与旧止损差大于一个阈值才修改double currentSL OrderStopLoss(); double newSL NormalizeDouble(ask - 20 * _Point, _Digits); if (MathAbs(newSL - currentSL) _Point) { OrderModify(ticket, OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrRed); }加上这个判断后循环逻辑从“每次满足都执行”变成“变化达到阈值才执行”递归问题就消失了。5.4 历史数据缺失导致回测结果偏乐观现象回测报告显示 10 年盈利 500%但实盘一周就亏损。原因测试器下载的历史数据可能缺少大波动的分钟级行情或者经纪商历史点差极低。回测里止损没有被触发实际行情跳空直接打穿止损。解决在回测前强制更新全部历史数据查看数据覆盖度。回测后做一次“最差情况压力测试”把点差人为调高到平时的 3 倍滑点调大到 50 点再看止损表现。你能同时加载多周期数据就用多周期不然只跑 M1 通常不够。5.5 DLL 调用权限与 32 位指针现象调用外部 DLL 时 EA 直接崩溃或者返回一堆乱码字符串。原因MT4 是 32 位程序DLL 必须编译成 32 位。如果 DLL 是 64 位直接无法加载。另一个坑是传入字符串时需要按 MT4 的string结构不能直接传 C 风格 char*否则内存越界。解决DLL 接口用匹配 32 位的调用约定并在 MT4 设置里勾选“允许导入 DLL”字符串传递用StringToCharArray转成数组再传int dllResult MyDll_Process(dataBuffer, ArraySize(dataBuffer));DLL 出错没有日志很难排查我会先在 DLL 里写一个简单的版本号函数确认能调用再加业务逻辑。能从文档里读到的规矩是DLL 路径要放在MQL4/Libraries否则每次都要填绝对路径换电脑就崩。6. 进阶日志、文件输出与外部系统对接6.1 日志分级与 Print/PrintFormat调试阶段最怕 Print 刷屏。我一般分三个级别基础信息用 Print关键交易动作用 PrintFormat 带结构化字段严重错误用Alert配合GetLastError。例如PrintFormat(Order %d op%d lots%.2f price%.5f, ticket, orderType, lots, price);日志时间戳默认为服务器时间回测时如果你想对齐本地时间要自己转换。日志只能写入测试器报告里实盘日志在MQL4/Logs按日期滚动。文件会越来越大定期清理是习惯不是可选项。6.2 文件写入与读取的路径规范文档规定文件操作只能访问MQL4/Files目录下的子目录越界会返回 false。用FileOpen时注意指定文件模式和编码int handle FileOpen(signals.csv, FILE_WRITE|FILE_CSV|FILE_ANSI, ;); if (handle ! INVALID_HANDLE) { FileWrite(handle, TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS), symbol, price); FileClose(handle); }CSV 的分隔符在终端里和编辑器里可能不一致建议固定用分号避免 Excel 打开串列。读完文件要 FileSeek 复位或直接 FileClose 重新打开。文件句柄不关闭EA 卸载后文件还是被锁着后续 EA 再次打开会失败。6.3 把 EA 信号转发到外部程序外部对接有几种方式写文件、数据库、网络 socket。文档本身不覆盖网络库但常见做法是用 WinAPI 或 C DLL 做 socket 转发。最简单可靠的是文件监控EA 每次开平仓把信号写成 JSON 或 CSV 文件外部 Python 程序定时读取。注意 EA 写文件频率不要过高一个 tick 写一次会把硬盘搞崩。我会用一个 static datetime 记录最后写入时间同一秒内只写一次减少 IO。也可以用 MT4 自带的TimeCurrent和OrderSelect组合在每次 K 线收盘后把那一秒的持仓状态输出成一行这样外部程序同步处理时数据不会重复。6.4 策略测试器里调试的额外技巧最后提一个测试器里最容易忽视的技巧把可视化模式打开勾选“买入/卖出箭头”。很多逻辑错误在图表上一眼就能看出比如信号开仓的位置比实际 K 线晚了一根。这时用TextSet或者开仓时OrderSend的注释带上当前时间能快速定位。我知道不少开发者从不用可视化只盯着报告里的曲线那是拿黑匣子猜行为效率太低。从那以后我每次写完新策略强制自己先跑一遍可视化回测再跑优化参数最后才上实盘小仓位验证。希望这些过程对你填平文档之外的沟壑有帮助。本文还有配套的精品资源点击获取