6.3 错误处理与 GetLastError
先清空,再调用,再读取
MQL4 的错误码是线程级的“最后一次错误”。如果不清空,上一次的旧错误可能被误读。推荐固定三步:
- 调用前
ResetLastError()。 - 执行可能失败的操作,例如 OrderSend、OrderModify、OrderClose。
- 失败后立刻
int err = GetLastError();,并打印到日志。
不要在失败后隔很多行再读 GetLastError(),中间调用的其他函数可能覆盖错误码。也不要用 `if(GetLastError() != 0)` 判断成功——成功时错误码也可能是上一次遗留的非零值,应该先看返回值(ticket > 0)。
常见错误码
- 130 ERR_INVALID_STOPS:止损/止盈太接近市价,小于 MODE_STOPLEVEL。改距离,不是重试。
- 131 ERR_INVALID_TRADE_VOLUME:手数不合法,小于最小手数或不符合步长。用 MODE_MINLOT、MODE_LOTSTEP 校正。
- 134 ERR_NOT_ENOUGH_MONEY:可用保证金不足。减手数,不要死循环重试。
- 138 ERR_REQUOTE:报价变化。RefreshRates() 后重发,通常 1–2 次即可。
- 146 ERR_TRADE_CONTEXT_BUSY:交易上下文忙。等待几百毫秒再试,不要立刻狂刷。
- 4108 ERR_INVALID_TICKET:订单号不存在或已平仓。OrderSelect 失败时常见。
分类原则:参数类错误(130/131/134)修参数,重试没有意义;瞬态类错误(138/146)可以有限重试;订单类错误(4108)先确认订单是否还在。重试次数建议 3 次以内,并在每次之间 Sleep 或记录下一次尝试时间。
止损距离:最容易被忽略的 130
MQL4 的止损是“价格”,不是“点数”。假设 Ask=1.10000,Point=0.00001,止损 8 点就是 1.09920;如果经纪商要求最小止损 20 点,这个价格会被拒绝。正确做法是先读 MarketInfo(Symbol(), MODE_STOPLEVEL),再把点数补到 minStop+5,最后 NormalizeDouble 到 Digits。
空单方向相反:止损在入场价上方,止盈在下方;多单止损在下方,止盈在上方。方向写反时,常见错误码也是 130。
完整示例:带分类重试的下单函数
下面的 SendBuy 先校正止损距离,再最多尝试 3 次。130/131/134 直接返回,138/146 等待后刷新报价再试。每次失败都打印 attempt 与 err,方便回测日志定位。
注意:OrderSend 的 price 参数对市价单是 Ask/Bid,对挂单是挂单价。重试时价格会变,所以要重新 RefreshRates() 并重算 sl/tp,不能沿用第一次的绝对价格。
重试的边界
重试不是越多越好。每一次重试都可能在下一次 Tick 再次触发,形成“失败—重试—再失败”的雪崩。工程上建议:
- 同一个信号最多重试 3 次,超过就记录并等待下一根 K 线。
- 用静态变量记录上次尝试时间,避免每个 Tick 都重试。
- 区分“可重试”和“不可重试”,不可重试的错误立即停止并报警。
- 回测里把失败次数、错误码分布打印出来,不要只看净利润。
常见错误
- 只看 OrderSend 返回值,不看 GetLastError:知道失败,不知道为什么。
- 130 也重试:参数没改,重试一百次还是 130,只是刷日志。
- 146 立刻重试:上下文还没释放,越试越忙。先 Sleep 或等下一个 Tick。
- 忘记 NormalizeDouble:价格小数位过多,部分经纪商会拒绝或四舍五入到意外价位。
- 在指标里调用 OrderSend:指标不能交易,应该把信号写给 EA。
小结
错误处理是一条决策树:先看返回值,再读错误码,再按错误码分类。参数错误修参数,瞬态错误有限重试,订单错误先确认状态。把这条树写进代码,你的 EA 才能在下单失败时给出可解释的行为,而不是沉默或刷屏。
日志怎么写才有用
失败日志至少包含六个字段:函数名、attempt、错误码、品种、方向/手数、关键价格。例如 `PrintFormat("OrderSend BUY fail attempt=%d err=%d sym=%s lots=%.2f ask=%.5f sl=%.5f", attempt, err, Symbol(), lots, Ask, sl);`。只写“下单失败”四个字,等于没写。回测日志还可以加上 `TimeCurrent()` 和当前 K 线时间,方便定位是哪个时间段出的问题。
日志级别也要控制。成功下单打印一行 INFO;可重试的 138/146 打印 WARN;参数错误 130/131/134 打印 ERROR。不要用 Print 输出整个订单结构,日志会变得不可读。生产环境建议把日志写到文件,并按天轮转;MQL4 的文件操作要用 FILE_COMMON 或 relative path,注意不要与同目录的其他 EA 冲突。
重试状态机
比“for 循环重试三次”更稳的做法是状态机:用全局变量记录 `retryCount`、`nextRetryTime`、`lastError`。OnTick 或 OnTimer 发现 `TimeCurrent() >= nextRetryTime` 时才尝试,失败后设置下一次时间。这样不会在一个 Tick 里连续发三次请求,也不会因为 Sleep 阻塞事件循环。
状态机还要有“放弃条件”:重试次数达到上限、错误码属于不可重试类别、当天亏损达到上限、点差超过阈值。满足任一条件就回到空闲状态,等待下一个信号。把状态写进日志,复盘时能清楚看到“为什么没有再试”。
IsTradeContextBusy 与 146
146 表示交易上下文忙,常见于多个 EA 同时下单、终端正在处理上一个请求、或网络延迟较高。IsTradeContextBusy() 可以提前判断,但它不是万能的:检查通过后、OrderSend 发出前,上下文仍可能被占用。所以即使先调用了 IsTradeContextBusy(),OrderSend 仍要检查返回值和 146。
处理 146 的正确顺序是:等待(Sleep 或状态机延时)→ RefreshRates() → 重新计算价格与止损 → 再发送。不要原样重发旧价格,行情可能已经变了,旧价格会触发 138 或滑点过大。
止损止盈的方向检查
多单:止损价 < 开仓价 < 止盈价;空单:止盈价 < 开仓价 < 止损价。写反方向时,除了 130,还可能出现“止损一开仓就触发”的异常。下单前用两行代码自检:`if(cmd==OP_BUY && !(sl
还要检查距离是否满足 MODE_STOPLEVEL 和 MODE_FREEZELEVEL。前者是最小止损距离,后者是冻结距离(价格太接近时不允许修改)。不同经纪商、不同品种的数值不同,不要写死 20 点。
资金不足与手数校正
134 不是“重试就能解决”的错误。它表示可用保证金不足以支撑当前手数。正确做法是先读 AccountFreeMargin()、MarketInfo(Symbol(), MODE_MARGINREQUIRED)(部分版本)或按杠杆估算,再把手数降到可用范围内。手数还要满足 MODE_MINLOT、MODE_MAXLOT、MODE_LOTSTEP:先按步长向下取整,再夹在最小/最大之间,最后 NormalizeDouble 到 2 位。
不要用“每次失败减 0.01”这种试探式降手数,既慢又可能在下一次 Tick 重复。把手数计算做成独立函数,输入是风险百分比和止损点数,输出是合法手数;下单失败时只记录,不盲目递归。
错误码与日志级别
建议把错误码分成三档:INFO(138 重试成功)、WARN(146 等待后重试)、ERROR(130/131/134/4108 不可重试)。日志级别不是为了好看,而是为了在几千行日志里快速筛选。回测时只打印 WARN/ERROR,实盘时再打开 INFO,能显著减少日志体积。
错误码不是越全越好。你不需要记住所有 4xxx 错误,只要把最常见的十几类覆盖到,剩下的统一走“记录并停止”分支。未知错误继续重试,往往比停止更危险。
交互演示:130 与 146:错误码怎么处置
第一次下单因止损 8 点小于最低 20 点报 130;修正到 25 点后又遇到 146 上下文忙;等待后第三次成功。
提示:动画默认暂停。点「播放」连续演示,点「单步」自己控制节奏;桌面端也可以用空格播放、方向键单步、R 重置。
练习题
-
6.3 错误处理与 GetLastError 选择题1.OrderSend 返回 -1 后,读取失败原因应调用? (2分) [选择]A. GetLastError()B. OrderTicket()C. MarketInfo()D. RefreshRates()2.错误码 130 通常表示? (2分) [选择]A. 资金不足B. 止损/止盈距离不合法C. 交易上下文忙D. 订单号无效3.遇到 130 错误时,应该立即原样重试,通常第二次就会成功。 (2分) [判断]
-
6.3 错误处理与 GetLastError 编程实战(AI 批改)1.【实战】写带分类重试的 SendBuy (10分) [MQL4自动批改]写函数 int SendBuy(double lots,int slPts,int tpPts,int magic):先判断 IsTradeAllowed,RefreshRates;读取 MODE_STOPLEVEL,若 slPts/tpPts 小于它则补到 minStop+5;最多重试 3 次,每次 ResetLastError 后 OrderSend;ticket>0 返回 ticket;130/131/134 直接返回 -1;138/146 则 Sleep(500)、RefreshRates 并重算价格后续试;其它错误返回 -1。必须出现 ResetLastError、GetLastError、OrderSend、MODE_STOPLEVEL。
《MQL4 教程与 MT4 EA 编程》课程目录
系统讲解 MQL4 编程:从变量、循环、函数到 OrderSend 下单、iMA 指标调用与 EA 回测。36 节中文教程,覆盖 MT4 编程入门、外汇 EA 编写与外汇量化策略三类实战,每节配可运行代码和 AI 自动批改练习
- 第1章 基本语法
- 1.1 第一个EA
- 1.2 如何学习
- 1.3 变量
- 1.4 语句
- 1.5 运算符
- 1.6 常量与枚举
- 第2章 条件
- 2.1 if语句
- 2.2 关系运算符
- 2.3 逻辑运算符
- 2.4 复合条件
- 2.5 switch语句
- 第3章 循环
- 3.1 for循环
- 3.2 while循环
- 3.3 break和continue
- 3.4 嵌套循环
- 第4章 函数
- 4.1 函数的定义
- 4.2 参数和返回值
- 4.3 变量的作用域
- 4.4 常用内置函数
- 4.5 日期和时间函数
- 4.6 交易函数
- 4.7 账户和市场信息函数
- 4.8 图表和对象函数
- 4.9 文件操作函数
- 4.10 数组函数
- 4.11 技术指标函数
- 第5章 如何写EA
- 5.1 EA的基本结构
- 5.2 常用内置变量
- 5.3 下单函数
- 5.4 技术指标
- 5.5 实战:均线交叉EA
- 第6章 进阶实战:从能跑走向可靠
- 6.1 OnTimer 定时器与事件处理
- 6.2 订单池遍历与 OrderSelect
- 6.3 错误处理与 GetLastError
- 6.4 自定义指标与指标缓冲区
- 6.5 图表对象与交易面板
- 6.6 回测与参数优化