开发多币种 EA 交易系统(第 15 部分):为真实交易准备 EA·综合运用
(3/3)·从交易品种替换到重启恢复,缺一环都可能让实盘变成盲盒
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 跑一遍可视化测试最能暴露问题。
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 终端异常退出后重新加载,风控尺度大概率能无缝延续,外汇与贵金属波动剧烈,这类状态持久化对控制连亏扩张有现实意义。
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 个月,权益曲线出现明显断层,恢复后虽继续交易但已错过那段行情——外汇与贵金属杠杆高,这类断层可能直接改变样本期收益结构,需用历史数据自行核对。
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 按最新文章版本补齐,编译看重启后状态是否还原。