开发多币种 EA 交易系统(第 15 部分):为真实交易准备 EA·综合运用
🔐

开发多币种 EA 交易系统(第 15 部分):为真实交易准备 EA·综合运用

(3/3)·从交易品种替换到重启恢复,缺一环都可能让实盘变成盲盒

含代码示例 第 3/3 篇
直接在真实账户挂测试通过的EA,十有八九会因为品种名带后缀或终端重启而失联。很多人以为回测漂亮就万事大吉,却从没给EA写过断开重连和品种映射逻辑。真金白银进场前,这些看不见的环节才是生死线。

EA 状态文件的载入门禁

虚拟顾问在 MT5 重启后想续上之前的仓位与风控上下文,靠的是 Load() 从 CSV 文件把状态读回。但这个函数不是无脑读盘,它先卡三道门:文件必须存在、当前不在策略优化中、以及要么没在回测要么正处于可视化测试。 门禁通过后用 FileOpen 以 FILE_CSV|FILE_READ 和制表符 '\t' 分隔打开文件,先读一个 datetime 作为 m_lastSaveTime,日志会打印形如 'LAST SAVE at 2024.05.13 09:31:45' 的字符串——这就是你判断状态新鲜度的硬数据点。 随后 FOREACH 遍历 m_strategies 逐个 Load,再单独 load 风控管理器 m_riskManager。任意一步返回 false 就 break 并打 ERROR 日志,最终 FileClose 收尾;外汇与贵金属 EA 用这套持久化务必清楚:盘前若状态文件时间戳错位,可能让风控参数误载入,属于高风险操作,开 MT5 跑一遍可视化测试最能暴露问题。

MQL5 / C++
class="type">bool CVirtualAdvisor::Load() {
   class="type">bool res = true;
class=class="str">"cmt">// Load status if:
   if(true
class=class="str">"cmt">// file exists
      && FileIsExist(m_name)
class=class="str">"cmt">// currently, there is no optimization
      && !MQLInfoInteger(MQL_OPTIMIZATION)
class=class="str">"cmt">// and there is no testing at the moment or there is a visual test at the moment
      && (!MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_VISUAL_MODE))
   ) {
      class="type">int f = FileOpen(m_name, FILE_CSV | FILE_READ, &class="macro">#x27;\t&class="macro">#x27;);
      if(f != INVALID_HANDLE) { class=class="str">"cmt">// If the file is open, then load
         m_lastSaveTime = FileReadDatetime(f);      class=class="str">"cmt">// Last save time
         PrintFormat(__FUNCTION__" | LAST SAVE at %s", TimeToString(m_lastSaveTime, TIME_DATE | TIME_MINUTES | TIME_SECONDS));
         class=class="str">"cmt">// Load all strategies
         FOREACH(m_strategies, {
            res &= ((CVirtualStrategy*) m_strategies[i]).Load(f);
            if(!res) break;
         });
         if(!res) {
            PrintFormat(__FUNCTION__" | ERROR loading strategies from file %s", m_name);
         }
         res &= m_riskManager.Load(f);
         if(!res) {
            PrintFormat(__FUNCTION__" | ERROR loading risk manager from file %s", m_name);
         }
         FileClose(f);
      } else {
         PrintFormat(__FUNCTION__" | ERROR: Operation FileOpen for %s failed, LastError=%d", m_name, GetLastError());
         res = false;
      }
   }
   class="kw">return res;
}

「虚拟仓位风险的存取与状态复原」

做虚拟持仓风控,最怕 EA 重启后把亏损限额进度清零。上面这段代码把风险管理器状态直接落盘,重启时再读回,仓位缩放逻辑就能接着跑而不是从头来过。 核心成员变量里,m_lastVirtualProfit 记录触发亏损限额那一刻虚拟持仓的浮亏,m_startRestoreTime 是开始恢复开仓规模的起点时间,m_baseDepoPart 代表原始占用的余额比例,而 m_dailyDepoPart 与 m_overallDepoPart 分别是日亏、总亏对这部分比例的乘数。 Save() 用 FileWrite 一次性写出状态枚举、浮亏、两个时间以及两个乘数,返回 true 即视为保存成功;Load() 则按相同顺序用 FileReadNumber / FileReadDatetime 还原,注意 m_state 做了 ENUM_RM_STATE 强转。 实盘里把这套接进 EA 的 OnInit/OnDeinit,MT5 终端异常退出后重新加载,风控尺度大概率能无缝延续,外汇与贵金属波动剧烈,这类状态持久化对控制连亏扩张有现实意义。

MQL5 / C++
  class="type">class="kw">double                m_lastVirtualProfit;       class=class="str">"cmt">// Profit of open class="kw">virtual positions at the moment of loss limit 
  class="type">class="kw">datetime              m_startRestoreTime;        class=class="str">"cmt">// Start time of restoring the size of open positions
  class="type">class="kw">datetime              m_startTime;
class=class="str">"cmt">// Updated values

class=class="str">"cmt">// Managing the size of open positions
  class="type">class="kw">double                m_baseDepoPart;            class=class="str">"cmt">// Used(original) part of the total balance
  class="type">class="kw">double                m_dailyDepoPart;           class=class="str">"cmt">// Multiplier of the used part of the total balance by daily loss
  class="type">class="kw">double                m_overallDepoPart;         class=class="str">"cmt">// Multiplier of the used part of the total balance by overall loss
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Save                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CVirtualRiskManager::Save(const class="type">int f) {
   FileWrite(f,
             m_state, m_lastVirtualProfit, m_startRestoreTime, m_startTime,
             m_dailyDepoPart, m_overallDepoPart);
   class="kw">return true;
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Load                                                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CVirtualRiskManager::Load(const class="type">int f) {
   m_state = (ENUM_RM_STATE) FileReadNumber(f);
   m_lastVirtualProfit = FileReadNumber(f);
   m_startRestoreTime = FileReadDatetime(f);
   m_startTime = FileReadDatetime(f);
   m_dailyDepoPart = FileReadNumber(f);
   m_overallDepoPart = FileReadNumber(f);
   class="kw">return true;
}

◍ 用模拟中断验证 EA 状态恢复

验证 EA 在停止后能否正确恢复,有两条路可走。实盘挂编译好的 EA 并等它开仓最直观,但等风险管理器触发可能耗很久,不适合快速迭代。更可控的做法是在策略测试器里造一次人为停机。 我们在原 EA 上派生一个新版本,加两个输入参数:重启前停止时间、重启开始时间。逻辑很直接——若停止时间留 0(即 1970-01-01 00:00:00)或不在测试区间内,EA 行为与原版一致;若落在区间内,到达该时刻就暂停分时报价处理,直到重启时间才续上。 全局布尔变量 isRestarting 用来标记待命状态。时间越过恢复点后,加载此前存档的 EA 状态并清掉标志位,EA 接着跑。文件存为 SimpleVolumesTestRestartExpert.mq5。 2021–2022 年不中断的回测作基线。插一小段短暂中断,结果与无中断完全一致,说明状态恢复生效。若把中断拉长到 4 个月,权益曲线出现明显断层,恢复后虽继续交易但已错过那段行情——外汇与贵金属杠杆高,这类断层可能直接改变样本期收益结构,需用历史数据自行核对。

MQL5 / C++
input class="type">class="kw">datetime restartStopTime_  = class="num">0;        class=class="str">"cmt">// - Stop time before restart
input class="type">class="kw">datetime restartStartTime_ = class="num">0;        class=class="str">"cmt">// - Restart launch time
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick() {
class=class="str">"cmt">// If the stop time is specified,
  if(restartStopTime_ != class="num">0) {
      class=class="str">"cmt">// Define the current time
      class="type">class="kw">datetime tc = TimeCurrent();
      class=class="str">"cmt">// If we are in the interval between stopping and resuming,
      if(tc >= restartStopTime_ && tc <= restartStartTime_) {
          class=class="str">"cmt">// Save the status and exit
          isRestarting = true;
          class="kw">return;
      }
      class=class="str">"cmt">// If we were in a state between stopping and resuming,
      class=class="str">"cmt">// and it is time to resume work,
      if(isRestarting && tc > restartStartTime_) {
          class=class="str">"cmt">// Load the EA status
          expert.Load();
          class=class="str">"cmt">// Reset the status flag between stop and resume
          isRestarting = false;
      }
  }
class=class="str">"cmt">// Perform tick handling
expert.Tick();
}

断线重连后 EA 捡回仓位的结果

  • 07.27 至 2021.11.29 这段测试里,图表上用黄色边框矩形标出了大致的交易中断区间。中断发生时,EA 已开的仓位被直接搁置,没有强制平仓。

随后 EA 重新连上终端并恢复运行,把那些未平仓位重新接管过来继续管理,整体走下来普遍是良性结果。 这一段的实测现象说明,EA 的状态保存与重载机制是生效的——断线不丢上下文,重启能续上旧仓。外汇与贵金属品种下终端断连属高频风险,验证这类容错逻辑建议直接在自己 MT5 用相同日期区间回放。

「真实盘前的最后一道工序」

EA 从测试器走到真实账户,差的不只是参数。原文作者在收尾时列了几项实盘才暴露的改造:交易品种名与优化时略有出入时,靠名称替换功能兜底;换参数启动前用平缓平仓避免硬砍;终端重启后靠状态保存恢复现场。 这些还不是全部。作者提到要把辅助信息分层显示,并在图表上汇总 EA 当前状态;更关键的是改到不依赖数据库也能跑——优化结果直接放终端工作文件夹,因为初始化字符串的组件正从这取。当前版本离了这功能转不动。 附带文件里 VirtualRiskManager.mqh 有 49.76 KB、SimpleVolumesExpert.mq5 为 17.4 KB,可直接下 ZIP 在 MT5 里编译验证。外汇与贵金属杠杆高,实盘前务必用策略测试器跑一遍改名与重启恢复。 下一步作者打算聊信息显示与无库运行的细节,这部分留到后续。现在你能做的,是把这几个 mqh 按最新文章版本补齐,编译看重启后状态是否还原。

让小布替你跑这套
品种替换规则和重启恢复状态这类枯燥校验,小布盯盘的AIGC已内置,打开对应品种页即可看到诊断,你只管判断策略本身该不该上实盘。

常见问题

需要在EA设置里加入替换规则,把优化时用的裸名映射成真实账户的具体品种名,前缀后缀都要覆盖,否则订单会发到错误标的。
不会立刻。EA会按设定努力平仓直至整体退出,若遇亏损仓位可能持续一段时间,属于有序撤退而非瞬间砍仓。
可以。小布盯盘内置了品种映射与断线恢复的诊断视图,能直观提示哪些品种未对齐或状态未持久化,减少人工核对成本。
要在运行期把必要信息写入文件或全局变量,重启后读取并重建策略实例与持仓上下文,才能保证连续性。
外汇贵金属杠杆高、规则差异大,品种命名与报价时区可能不同,务必逐账户验证替换与恢复逻辑,真实交易存在重大亏损可能。