大部分 MT5「日内亏损上限」的实现是这样的:
double today_pnl = 0.0; // 模块级变量
bool daily_blocked = false;平时工作得很好——直到终端重启、EA 重新编译、切换图表、重新加载配置。这些动作每一个都会重新调用 OnInit(),变量归零,你辛苦设的上限被原封不动地还回来。
如果你跑的是 prop firm 账户(5% 日回撤规则),这不是小 bug:崩溃和重启总是扎堆出现在糟糕的交易日,而那正是你最需要这道保险的时刻。

事件 | 内存变量 | 终端全局变量 | 文件(common 目录) |
|---|---|---|---|
新 tick / 新 bar | 有效 | 有效 | 有效 |
EA 重编译 / 重新挂载 | 失效 | 有效 | 有效 |
终端重启 | 失效 | 有效 | 有效 |
重装 / 换机器 / 多开终端 | 失效 | 失效 | 有效 |
GlobalVariableSet() 能覆盖前两种,最后一行只有落盘才行。
struct GuardState
{
long day_stamp; // 属于哪个交易日
double day_start_balance; // 当日回撤锚点
bool blocked; // 今天是否已熔断
};day_stamp 是最容易被忘掉的字段。没有它,恢复出来的状态要么永远不生效,要么永远生效——两种都是错的。
long TradingDayStamp()
{
MqlDateTime t;
TimeToStruct(TimeTradeServer(), t); // 不是 TimeLocal()
return((long)t.year * 10000 + t.mon * 100 + t.day);
}TimeLocal() 会在券商交易时段中间把上限重置掉。TimeCurrent() 是最后一笔报价时间,冷清时段严重滞后。
if(blocked) return; // 便宜,且能接住外部写入的熔断
if(!risk_ok) { blocked = true; SaveState(); return; }
SendOrder();顺序反了,两行之间崩一次就可能放过一单本不该存在的订单。
如果新进程痛快地发了单,说明状态在内存里——那道守护只是摆设。

三行纪律:状态写盘、用券商交易日、先写后发。这就是「风险上限」和「愿望」的区别。
我把这套实现打包成了一个免费 MT5 工具(纯 MQL5、无 DLL、无外部请求、界面只显示百分比):
https://xuks124.github.io/vigildesk/free.html
只做风控,不承诺收益。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。