MT5编程方法

MT5 EA实盘应急指南:何时停用、如何安全重启与状态恢复实战

晓
晓辉
🕐 2026年11月14日 👁 544 阅读
MT5 EA实盘应急指南:何时停用、如何安全重启与状态恢复实战
M

风险提示:本文为软件开发与技术服务类技术文章,仅用于MQL5程序化开发的技术交流与软件工程实践探讨。文中涉及的停机判据、状态保存方式、持仓对账逻辑与重启检查清单等内容,均为软件工程原理演示与教学用途,不构成任何投资建议、交易策略推荐或收益预期。外汇、贵金属、CFD等杠杆交易存在较高风险,可能导致重大本金损失。文中代码片段仅供学习参考,需经人工审查、编译测试与充分的模拟盘验证后方可使用,直接投入实盘可能造成资金损失。本网站仅提供软件开发技术服务,不涉及任何交易平台运营、经纪业务或投资咨询业务。

EA 出事那一刻,多数人的选择只有两种:打开图表看一眼,停,或者留着等它自己好。这个决定通常几十秒内做完,但它决定后面是修一个问题,还是补一个洞。真正危险的不是 EA 跑错的那一下,是它被重新挂上去的那一下——内存里的记录清零了,账户上的持仓还在,它按空仓的逻辑重算一遍,于是同一方向又开了一笔。这篇讲三件事:什么信号出现必须停、停之前要留下什么、重启后怎么把状态对齐。

一、这5类信号出现,先停EA再谈原因

停的判据必须是可计算的条件。把”感觉不对”翻译成阈值,写进代码,让它自己停——人盯盘会疲劳,代码不会。五类信号里前四类可以完全自动化,第五类看似要靠人判断,其实同样能量化。

滑点连续超限。单次滑点是流动性问题,连续多次是执行环境变了,比”这一单滑得有点多”可靠。连接或行情源中断。TerminalInfoInteger(TERMINAL_CONNECTED) 为 false 是显性的;更隐蔽的是连接显示正常、服务器却不再推报价,这时要看 TimeCurrent() 是否还在前进。净值跌幅越线。用 ACCOUNT_EQUITY 而非 ACCOUNT_BALANCE,浮亏必须算进去。时间戳异常。OnTimer 本该每 N 毫秒触发一次,实际间隔远超设定,说明事件队列被堵住。策略与行情背离。趋势型逻辑在无趋势区间连续止损,止损次数本身就是信号。

string   g_stop    = "";      // 非空 = 已停机
int      g_slip    = 0;       // 连续滑点超限次数,由成交回调累加
double   g_dayBase = 0;       // 当日净值基准
datetime g_srvLast = 0;       // 上次看到的服务器时间

string StopReason()
  {
   static int dc = 0;
   if(!TerminalInfoInteger(TERMINAL_CONNECTED))
     { if(++dc >= 2) return("connection lost"); }   // 连续两次才算断,防瞬时抖动误停
   else dc = 0;

   datetime t = TimeCurrent();
   if(t == g_srvLast && t > 0) return("server time frozen");  // 时间不走 = 行情源停了
   g_srvLast = t;

   double eq = AccountInfoDouble(ACCOUNT_EQUITY);
   if(g_dayBase <= 0) g_dayBase = eq;
   if(eq < g_dayBase * (1.0 - InpDayLossLimit)) return("equity drop");

   if(g_slip >= InpSlipLimit) return("slippage");
   return("");
  }

void Halt(const string why)
  {
   g_stop = why;
   SaveState();          // 先落盘,再摘自己
   EventKillTimer();
   ExpertRemove();       // 触发 OnDeinit(REASON_PROGRAM)
  }

风险提示:ExpertRemove() 只把 EA 从图表摘掉,它不碰任何持仓。停机前先确认每个持仓都有服务器端止损单;顺序反了,等于主动卸掉保险。

二、状态恢复的三种模式,别默认挑最省事的那个

恢复的本质是回答一个问题:重启之后的 EA 该相信谁?相信文件里写的、相信终端里真实存在的、还是谁都不信从零开始。三种答案对应三种模式,没有优劣,只有边界。

模式 恢复依据 内存占用 恢复耗时 适用边界 主要风险
冷启动 不恢复,OnInit 全部从终端重建 最低 最短 策略逻辑改过、版本号不一致 丢失跨周期的信号上下文
热恢复 状态文件 + GlobalVariable 回填 中等 较长 参数未变,仅终端或 VPS 重启 过期记录覆盖真实持仓
部分恢复 只回填持仓对账数据,信号缓存丢弃 较低 中等 持仓要对齐,历史信号可重算 需多跑一次全量对账

这里有个 MQL5 的事实值得单独说,它比 VPS 宕机更常造成孤儿持仓:改图表周期、改品种、改 input 参数、重新编译,都会走一遍 OnDeinit → OnInit,原因码分别是 REASON_CHARTCHANGE(3)、REASON_PARAMETERS(5)、REASON_RECOMPILE(2)。也就是说,”我只是把周期从 M5 调到 M15″在 EA 看来就是重启了一次——静态变量和全局变量全部重新初始化,内存里的持仓计数归零,而账户上的持仓一笔没少。

知识点:全局变量与 static 变量只在两次 OnTick 之间保值,跨过一次重新初始化就回到初值。持仓状态不能只活在内存里。

重点提示:终端全局变量做持久化有三条边界:只能存 double、满 4 周未访问会自动删除、不调 GlobalVariablesFlush 则落盘时机不可控。它适合存几个数字,不适合存一份状态;键名要带品种与 magic 隔离。

// 只存"最小可重建集",其余全部从终端读
void SaveState()
  {
   string k = "EA_" + _Symbol + "_" + IntegerToString(InpMagic);
   GlobalVariableSet(k + "_ver", (double)EA_VERSION);   // GV 只能存 double
   GlobalVariableSet(k + "_vol", (double)g_trackVol);
   GlobalVariableSet(k + "_dir", (double)g_trackDir);
   GlobalVariablesFlush();                              // 主动落盘,别等终端关

   int h = FileOpen(k + ".state", FILE_WRITE|FILE_TXT|FILE_ANSI);
   if(h == INVALID_HANDLE) return;                      // 文件兜底:能存字符串与时间
   FileWrite(h, IntegerToString(EA_VERSION));
   FileWrite(h, DoubleToString(g_trackVol, 2));
   FileWrite(h, IntegerToString(g_trackDir));
   FileWrite(h, TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS));
   FileClose(h);
  }

操作参考:逻辑改过就冷启动,参数未变只是进程重启就热恢复,只有持仓对不上就部分恢复。判断”改过”靠写进文件的 EA_VERSION 比对,不靠记忆。

三、持仓对不上:三步诊断,别急着平仓重开

对账的原则只有一条:终端才是真相源。PositionsTotal 与 PositionSelect 读出来的是券商认的,EA 内存里的记录只是缓存。缓存错了就改缓存,不要为了让缓存”看起来对”去动真实持仓——后者是把一个数据问题升级成一次真实交易成本。

// 以终端为准重建净头寸,只认本 EA 的 magic
double RebuildNet(const long magic)
  {
   double buy = 0, sell = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong tk = PositionGetTicket(i);          // 该函数会同时选中这笔持仓
      if(tk == 0) continue;
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;  // 别的品种不碰
      if(PositionGetInteger(POSITION_MAGIC) != magic)   continue;  // 别人的仓位不碰
      double v = PositionGetDouble(POSITION_VOLUME);
      if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) buy  += v;
      else                                                       sell += v;
     }
   return(buy - sell);                          // 净头寸:正为多,负为空
  }

第一步,枚举。按品种加 magic 双重过滤算净头寸。手动开的仓、别的 EA 开的仓都不该进这个数,混进来会让后面每一步都错。第二步,分类差异。gap 等于真实值减记录值。为正说明终端多出一块,可能是部分成交补量或人工加仓;为负说明记录里有幽灵仓位,被手动平掉或被服务器端止损打掉。要分清是哪一种,用 HistorySelect 拉这段时间的成交记录看 magic 与 entry 类型。第三步,处置。孤儿仓位方向与当前信号一致就接管跟踪,冲突才平掉;幽灵记录直接清掉,不要为了”对上”补开一笔。若 gap 小于该品种最小手数,那是浮点误差,忽略。

知识点:10009 DONE 表示已完成但 volume 可能小于请求手数;10010 DONE_PARTIAL 明确只完成一部分;10008 PLACED 是挂单已被接受、尚未成交。只看 retcode 就判断”开成功了”会误判。

风险提示:平仓重开不是万能修复。一平一开之间要付点差滑点,还可能撞上拒单、报价过期或流动性空档,亚洲早盘与周五收盘前尤其容易开不回来。只有方向冲突或来路完全不明时才平。

四、重启前的8项检查

这8项按”会不会直接导致下错单”排序,越靠前越致命。逐项过一遍花不了几秒,却能挡掉多数重启事故。每项都要写成可判定的条件,不写成要靠回忆的描述。

# 检查项 通过判据 不通过怎么办
1 持仓归属 按品种加 magic 枚举出的手数与记录一致 进对账流程,不动仓位
2 挂单清理 无过期、无失效、无重复挂单 撤单后按当前逻辑重挂
3 状态文件可读 存在、字段完整、时间戳不早于上次运行 放弃热恢复,改冷启动
4 时间基准 TimeCurrent 与 TimeLocal 偏差在预期区间 不交易,等时间同步恢复
5 交易权限 ACCOUNT_TRADE_EXPERT、TERMINAL_TRADE_ALLOWED、MQL_TRADE_ALLOWED 均为 true 进只读模式,不报单只告警
6 内存残留 指标句柄与图表对象已在上次 OnDeinit 释放 记录后重启终端,防句柄泄漏
7 日志连续性 上一份日志里没有未闭合的请求 ID 人工核对最后几笔订单再启动
8 保证金余量 OrderCalcMargin 估算的最坏开仓成本低于可用保证金 降手数或不开新仓

重点提示:8 项要在 OnInit 里跑完再决定是否交易,不能”先跑起来再说”。任一项不通过就进只读模式——不报单只告警,比带着不确定状态开跑安全。第 7 项的做法在上一篇日志系统文章里展开过,这里只当前置条件引用。

五、VPS重启后的四步恢复

有个坑不少人踩过:周末重启服务器,EA 挂上去,看图表觉得一切正常,实际上它一次 OnTick 都没执行过。MQL5 的 OnTick 由价格变动触发,周末没有报价,第一个 tick 要等到开盘才来。所以恢复逻辑写在 OnTick 里等于没写——它得放在 OnInit 里做一次性检查,或者放在 OnTimer 里,定时器与行情无关。

重点提示:挂上去没报错不等于跑起来了。判断依据是 OnTimer 有没有在动,不是图表上的笑脸图标。省事的做法是 OnInit 末尾写一行 init done,第一个 OnTick 再写一行 first tick,两行都出现才算接上行情。

① 故障发生。先分清是终端进程挂了、VPS 挂了、还是只是网络断了。看终端日志最后一条的时间戳,跟系统时间对比,差多少就是断了多久。② 判断影响面。故障期间有没有持仓、有没有被止损触发。用 HistorySelect 拉这段时间的成交记录,看有没有 DEAL_ENTRY_OUT。③ 选择恢复模式。先看代码版本号有没有变,再看持仓是否对得上。④ 验证一致性。OnInit 里先跑一遍对账,结果写进日志第一行,对账不平就不开单。

操作参考:四步顺序不能颠倒。故障期间被止损过,记录里还留着已平掉的仓位,必须走对账修正;什么都没发生,热恢复就够。顺序反了,就会拿过期记录覆盖真实持仓。

进阶思考:更省事的解法是把”状态”从 EA 里删掉:持仓、挂单、当日盈亏全部现算,内存只留本 tick 的中间变量。重启代价接近零,因为它没有需要恢复的东西。能重建的数据,才不怕丢。

停机、重启、对账,这三件事的顺序是固定的:先保住仓位,再保住记录,最后才是保住策略。反过来做——先想策略怎么继续跑,再想记录怎么接上,最后才想起仓位——那就不是应急处理,而是给下一次事故铺路。

风险提示:本文内容仅供软件开发与技术学习参考,不构成任何投资建议或交易指导。程序化交易存在较高风险,可能导致重大本金损失。文中涉及的停机判据、状态保存、持仓对账与检查清单代码均为技术原理演示,未经完整工程化封装,直接复制使用可能存在边界条件遗漏、文件句柄泄漏、多图表并发写入冲突、magic 号重复导致误判等缺陷,需经人工审查、编译测试与充分的模拟盘验证。文中出现的返回码数值(10008、10009、10010 等)与原因码(REASON_CHARTCHANGE 等)依据 MQL5 官方文档整理,具体含义与行为以对应版本官方文档及实测结果为准。实盘应用前请自行决策并承担相应风险。本网站仅提供软件开发技术服务,不涉及任何交易平台运营、经纪业务或投资咨询业务。

第三方免责声明:本文仅代表作者个人技术观点,与任何经纪商、交易平台或金融机构无关。文中提及的 MetaTrader 5、MQL5、MetaEditor、ExpertRemove、OnInit、OnDeinit、OnTick、OnTimer、EventKillTimer、GlobalVariableSet、GlobalVariablesFlush、GlobalVariableGet、PositionsTotal、PositionGetTicket、PositionGetInteger、PositionGetString、PositionGetDouble、OrderCalcMargin、HistorySelect、TerminalInfoInteger、AccountInfoDouble、FileOpen、FileWrite、FileClose、TRADE_RETCODE_* 系列常量、REASON_* 系列常量等名称与产品,其商标与权利归各自所有者所有,本文仅作技术说明与交流引用,不构成任何推荐、背书或商业合作关系。文中关于事件生命周期、全局变量持久化行为、部分成交返回码与券商执行差异的描述基于公开资料与工程实践,具体表现以各平台官方说明与实测为准。读者应自行核实相关信息,因使用本文内容而产生的直接或间接损失,作者不承担任何责任。本文不推荐任何具体的经纪商、交易平台或金融产品。

扫码关注,获取更多EA开发干货

晓辉编程微信公众号

微信公众号

晓辉编程视频号

视频号

🎲 猜你喜欢

首页 文章 下载 产品 定制
回到顶部 ↑ 搜索 🔍 EA定制 ⚙️
微信咨询 💬
晓辉微信二维码

扫码加微信
XiaoHuiProgramming