风险管理(第三部分):构建风险管理主类·综合运用
(3/3)·从结构体到事件驱动,把分散的风险控制逻辑封装进一个类里
◍ 损益读取与按标志平仓的内联封装
把最大回撤、最大利润这类指标做成 inline const 函数,能在 EA 热路径里直接返回值而不触发类成员改写,MT5 编译后调用开销接近零。下面这组 GetML / GetMWL / GetMDL 分别吐出监控到的最大利润、加权最大亏损与最大日亏损,日内和周利润也有独立接口,方便你做分层绩效统计。 平仓逻辑用 2 的幂做标志位:FLAG_CLOSE_ALL_PROFIT 十进制 2(二进制 00000010)只平盈利仓,FLAG_CLOSE_ALL_LOSS 十进制 4(二进制 00000100)只平亏损仓,两个可用 | 组合。循环必须从 PositionsTotal()-1 倒序到 0,实时平掉仓位后总数会变,正序遍历会漏单或越界。 幻数过滤别省:PositionGetInteger(POSITION_MAGIC) 不等预期且不是 NOT_MAGIC_NUMBER 就 continue,否则会把别的 EA 的仓给误平了。利润正负用 PositionGetDouble(POSITION_PROFIT) 拿,再用 & 位运算判标志,>0 或 <0 各自触发 trade.PositionClose。外汇和贵金属杠杆高,这类批量平仓在滑点行情里可能成交在偏离价,上线前务必用策略测试器跑一遍。
class="kw">inline class="type">class="kw">double GetML() const { class="kw">return this.ml.value; } class="kw">inline class="type">class="kw">double GetMWL() const { class="kw">return this.mwl.value; } class="kw">inline class="type">class="kw">double GetMDL() const { class="kw">return this.mdl.value; } class="kw">inline class="type">class="kw">double GetGMLPO() const { class="kw">return this.gmlpo.value; } class="kw">inline class="type">class="kw">double GetNMLPO() const { class="kw">return this.nmlpo; } class="kw">inline class="type">class="kw">double GetMDP() const { class="kw">return this.mdp.value; } class=class="str">"cmt">//--- Obtain only profits: class="kw">inline class="type">class="kw">double GetGrossProfit() const { class="kw">return this.gross_profit; } class="kw">inline class="type">class="kw">double GetWeeklyProfit() const { class="kw">return this.weekly_profit; } class="kw">inline class="type">class="kw">double GetDailyProfit() const { class="kw">return this.daily_profit; } class="type">void CloseAllPositions(class="type">int flags = FLAG_CLOSE_ALL_LOSS | FLAG_CLOSE_ALL_PROFIT); class="macro">#define FLAG_CLOSE_ALL_PROFIT class="num">2 class=class="str">"cmt">//Flag indicating to close only operations with profit class="macro">#define FLAG_CLOSE_ALL_LOSS class="num">4 class=class="str">"cmt">//Flag indicating to close only operations without profit for(class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--) class="type">ulong position_ticket = PositionGetTicket(i); if (!PositionSelectByTicket(position_ticket)) class="kw">continue; class="type">ulong magic = PositionGetInteger(POSITION_MAGIC); if (magic != this.magic_number && this.magic_number != NOT_MAGIC_NUMBER) class="kw">continue; class="type">class="kw">double profit = PositionGetDouble(POSITION_PROFIT); if ((flags & FLAG_CLOSE_ALL_PROFIT) != class="num">0 && profit > class="num">0) { trade.PositionClose(position_ticket); } else if ((flags & FLAG_CLOSE_ALL_LOSS) != class="num">0 && profit < class="num">0) { trade.PositionClose(position_ticket); } class="type">void CRiskManagemet::CloseAllPositions(class="type">int flags = FLAG_CLOSE_ALL_LOSS | FLAG_CLOSE_ALL_PROFIT) { for(class="type">int i = PositionsTotal() - class="num">1; i >= class="num">0; i--) { class="type">ulong position_ticket = PositionGetTicket(i);
按盈亏标记批量平仓的循环写法
在 EA 的持仓巡检循环里,先用 PositionSelectByTicket 按 ticket 锁定单子,选不到就 continue 跳走,避免对失效 ticket 误操作。 拿到持仓后读 PositionGetDouble(POSITION_PROFIT) 得到该单浮动盈亏。注意这是账户结算币种下的数值,不是点数,跨品种巡检时要心里有数。 下面这段用位标志分流:FLAG_CLOSE_ALL_PROFIT 置位且 profit>0 才平盈利单;FLAG_CLOSE_ALL_LOSS 置位且 profit<0 才平亏损单。这样一套循环能按策略只清某一类仓位,不会误杀。 外汇与贵金属杠杆高,批量平仓瞬间滑点可能吞掉预期盈亏,上实盘前先在 MT5 策略测试器用历史数据跑一遍标志组合。
if(!PositionSelectByTicket(position_ticket)) class="kw">continue; class=class="str">"cmt">// If you don&class="macro">#x27;t select the position, class="kw">continue class="type">class="kw">double profit = PositionGetDouble(POSITION_PROFIT); class=class="str">"cmt">// Check flags before closing the position if((flags & FLAG_CLOSE_ALL_PROFIT) != class="num">0 && profit > class="num">0) class=class="str">"cmt">// Close only profit positions { trade.PositionClose(position_ticket); } else if((flags & FLAG_CLOSE_ALL_LOSS) != class="num">0 && profit < class="num">0) class=class="str">"cmt">// Close only losing positions { trade.PositionClose(position_ticket); }
「用日线与周线切分重置风控状态」
把风险管理从连续累加改成按自然周期切断,是 EA 不自爆的前提。每天开盘重置 daily_profit 为 0,每周开盘重置 weekly_profit 为 0,避免上一周期的盈亏污染本期仓位尺度。 每日事件里要重算最大盈利/亏损类参数(SetMWL、SetMDL、SetML、SetMDP、SetGMLPO),再用 iTime(_Symbol, PERIOD_D1, 0) 记下当天 D1 柱开盘时间,最后把 MDP/MWL/ML/MDl/GMLPO 以两位小数打进日志,肉眼能直接核对数值是否随行情刷新。 周事件逻辑更轻:只更新 last_weekly_time 为 PERIOD_W1 的 iTime,并把 weekly_profit 清 0。外汇与贵金属杠杆高,周期切错可能让单周回撤失控,建议先在策略测试器用 2023 年 EURUSD 数据跑一周验证重置时机。 检测靠 OnTick 里比时间戳:prev_time 不等于当前 D1 的 iTime 就判为新一天,调用 OnNewDay 并回写 prev_time;周线同理用 PERIOD_W1。这套比对可扩展到 H1、H12 等任意周期。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Function that runs every new day | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CRiskManagemet::OnNewDay(class="type">void) { SetMWL(); SetMDL(); SetML(); SetMDP(); SetGMLPO(); this.daily_profit = class="num">0; this.last_day_time = iTime(_Symbol,PERIOD_D1,class="num">0); Print(" New day "); Print(StringFormat("%-6s| %s", "Losses", "Loss")); Print(StringFormat("%-6s| %.2f", "MDP", this.mdp.value)); Print(StringFormat("%-6s| %.2f", "MWL", this.mwl.value)); Print(StringFormat("%-6s| %.2f", "ML", this.ml.value)); Print(StringFormat("%-6s| %.2f", "MDl", this.mdl.value)); Print(StringFormat("%-6s| %.2f", "GMLPO", this.gmlpo.value)); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Function that runs every new week | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CRiskManagemet::OnNewWeek(class="type">void) { this.last_weekly_time = iTime(_Symbol,PERIOD_W1,class="num">0); this.weekly_profit = class="num">0; } class="type">class="kw">datetime prev_time = class="num">0; class="type">void OnTick() { if(prev_time != iTime(_Symbol, PERIOD_D1, class="num">0)) { Print("New day detected"); prev_time = iTime(_Symbol, PERIOD_D1, class="num">0); OnNewDay(); class=class="str">"cmt">// Call the corresponding function } } class="type">class="kw">datetime prev_week_time = class="num">0; class="type">void OnTick() { if(prev_week_time != iTime(_Symbol, PERIOD_W1, class="num">0)) { Print("New week detected") class=class="str">"cmt">//Call the corresponding function prev_week_time = iTime(_Symbol, PERIOD_W1, class="num">0); } }
◍ 把工具请下神坛
这一阶段的 CRiskManagement 类只完成了第一步:给最大亏损和最大利润赋值,相当于把风险控制的骨架搭了起来。它现在还不能丢进实盘跑,但你已经能在 MT5 里打开 Risk_Management.mqh(60.5 KB 那个包含文件)看到这些值在系统里是怎么被塞进去、怎么被管理的。 下一篇会补上几个关键函数:查是否击穿最大亏损限额并触发动作、加事件钩子、还有针对 FTMO 这类自营账户自动刷新每日亏损上限。类成型后,才可能在不同行情里真正兜住风险。 外汇和贵金属杠杆高、滑点狠,这类限额逻辑只是防线的一层,真上线前得自己跑历史 tick 验证。工具再顺手也只是工具,别把它当圣杯。