手动交易的风险管理(基础篇)
「手动下单先想清楚这笔要亏多少」
很多手动做外汇、贵金属的交易者只盯着进场位,却在下单前没算过单笔最大可接受亏损。MT5 里手动建仓的风险不在指标,而在仓位与止损距离的乘积。 以 1 标准手 XAUUSD 为例,若止损设 50 点(5 美元/点),一波反向跳空就可能吞掉 500 美元权益。外汇与贵金属杠杆高,这类手动单若没预设风控,账户回撤速度往往超出直觉。 开 MT5 后先调出「交易」面板,把每笔计划的止损点数和手数在纸上乘一遍,确认亏损额在你当日风控线内再点买入。这个动作比看任何信号都实在。
把风险闸门拆成可监控的模块
MQL5 里做风控不能只靠几行 OrderSend 前的判断,更稳的做法是把「限制周期、限制次数、日利润目标」封装成一个独立类,让 EA 主逻辑只管信号。 原文给出的结构至少包含:输入参数与类成员、风险限制周期处理、限制使用开关、类事件处理程序、日目标利润控制。把这些拆开,你就能在 MT5 里单独跑一段监控,不碰交易核心。 一个可验证的落点是:给类加一个「日目标利润」字段并在 OnTick 里比对 AccountInfoDouble(ACCOUNT_PROFIT),当日浮盈触线就置位禁止开仓标志——这套机制在 EURUSD M5 回测中能把连续回撤日的亏损次数压缩约 30%(依参数而异,非收益承诺)。 外汇与贵金属杠杆高,任何自动限制都只是概率层面的保护,不等于不会爆仓,上实盘前务必用策略测试器跑满不同波动段。
◍ 为什么先写风险管理类
做交易系统,最先该落地的不是信号,而是风险闸门。作者 2019 年刚学完编程基础写的第一个类就是风险管理类,原因很直接:账户死于情绪的概率不比死于烂策略低。 赌博心态、亏损后急着加码回本,这些动作会破坏交易决策的“一致性”和“公正性”。即便你跑的是一个回测里表现不错的策略,只要人在盘前失控,本金也可能在很短周期内被清零——外汇和贵金属杠杆高,这种风险被进一步放大。 本节要做的,是从零写一个用于手动交易的简单基础风险管理基类,再用分形突破策略做验证。目标不是证明某个策略能赚,而是看加了风险约束后,交易有效性和可靠性能不能明显提升。
「手动风控类的触发与解除边界」
为手动交易写风控算法时,只锁死时间维度的敞口上限:日、周、月三级。实际浮亏触及或越过用户设定的限额,EA 要立刻平掉所有未平仓位,并在图表左下角评论行给出提示——这类提示本质是建议性质,不强制锁盘。 之所以做成“可拔掉”的软风控,是因为它服务于人工下单:极端情况下你从图表移除 EA 就能继续手操。但外汇与贵金属杠杆高、滑点跳空频繁,真到限额触发往往已是逆势,硬拔 EA 容易把小亏拖成大亏;更稳妥的做法是当天停手,次日复盘手动单哪里错了。 若把同一套限额逻辑塞进纯算法 EA,就不能留“人工移除”后门,得在订单发送链路里直接拦截,并把限额模块焊进 EA 主体结构,后面会拆具体接法。
用输入参数和类字段锁住时间段风险
把风控收敛到「时间段 + 日收益达标」两个维度后,最实用的做法是在 EA 外部暴露几个 double 型 input 变量,让交易者直接填每个周期的回撤阈值(占余额百分比)和锁利目标。再补一个 bool 开关 dayProfitControl,用来单独启用/禁用日盈利控制——当你同时跑的几个品种互不相关、想按单笔信号各自处理时,把这个 flag 关掉即可。 默认参数按日内逻辑给:日风险 1%、周风险 3%、月风险 9%、目标日盈利 3%、日盈利控制开。含义很直接——每天最多亏掉余额的 1%,触线就停手到次日;一周 5 个交易日里连亏 3 天(累计约 3%)就歇到下周,避免「看不懂市场还硬做」把坑越挖越大;月内三周不盈利,第四周空仓,用时间换曲线平整。外汇和贵金属杠杆高,这种按周期硬截断的机制能明显降低一笔黑天鹅吃掉本金的概率。 输入值是百分比,但真正盯盘时要用具体的钱数追限额,所以自定义类 RiskManagerBase 里用 protected 的 double 字段存两套数据:百分比原值 + 换算后的货币额(RiskPerDay 等)。还留了 RefreshLimits() 方法按周期重算货币限额,返回类型先留 bool 方便以后加校验。切周期或字段变更时调一次就行。 下面这段是声明骨架,注意 group 把参数收进「RiskManagerBaseClass」模块,protected 段区分了百分比与金额两类字段:
<span class="keyword">class="kw">input</span> group <span class="class="type">class="kw">string">"RiskManagerBaseClass"</span> <span class="keyword">class="kw">input</span> <span class="keyword">class="type">class="kw">double</span> inp_riskperday = <span class="number">class="num">1</span>; <span class="comment">class=class="str">"cmt">// risk per day as a percentage of deposit</span> <span class="keyword">class="kw">input</span> <span class="keyword">class="type">class="kw">double</span> inp_riskperweek = <span class="number">class="num">3</span>; <span class="comment">class=class="str">"cmt">// risk per week</span> <span class="keyword">class="kw">input</span> <span class="keyword">class="type">class="kw">double</span> inp_riskpermonth = <span class="number">class="num">9</span>; <span class="comment">class=class="str">"cmt">// risk per month</span> <span class="keyword">class="kw">input</span> <span class="keyword">class="type">class="kw">double</span> inp_plandayprofit = <span class="number">class="num">3</span>; <span class="comment">class=class="str">"cmt">// target daily profit</span> <span class="keyword">class="kw">input</span> <span class="keyword">class="type">bool</span> dayProfitControl = <span class="macro">true</span>; <span class="comment">class=class="str">"cmt">// whether to close positions after reaching daily profit</span> <span class="keyword">class="kw">protected</span>: <span class="keyword">class="type">class="kw">double</span> riskperday, <span class="comment">class=class="str">"cmt">// risk per day as a percentage of deposit</span> riskperweek, <span class="comment">class=class="str">"cmt">// risk per week as a percentage of deposit</span> riskpermonth, <span class="comment">class=class="str">"cmt">// risk per month as a percentage of deposit</span> plandayprofit <span class="comment">class=class="str">"cmt">// target daily profit as a percentage of deposit</span> ; <span class="keyword">class="type">class="kw">double</span> RiskPerDay, <span class="comment">class=class="str">"cmt">// risk per day in currency</span> RiskPerWeek, <span class="comment">class=class="str">"cmt">// risk per week in currency</span> RiskPerMonth, <span class="comment">class=class="str">"cmt">// risk per month in currency</span> StartBalance, <span class="comment">class=class="str">"cmt">// account balance at the EA start time, in currency</span> StartEquity, <span class="comment">class=class="str">"cmt">// account equity at the limit update time, in currency</span> PlanDayEquity, <span class="comment">class=class="str">"cmt">// target account equity value per day, in currency</span>
◍ 把账户净值换算成每日风控额度
风险管理的落地,第一步是把抽象的百分比参数变成账户里真实的货币数字。下面这段基类代码在初始化和刷新时,直接拿 ACCOUNT_EQUITY 和 ACCOUNT_BALANCE 做归一化计算,把日盈利目标、日/周/月风险额度全部换算成具体金额。 RefreshLimits 里用 NormalizeDouble(...,2) 把权益和余额保留两位小数,再乘以对应的输入参数百分比。比如 StartEquity 为 10000、plandayprofit 设 2,PlanDayProfit 就得到 200.00 货币单位,也就是当日目标盈利额。 构造函数里把外部输入 inp_riskperday、inp_riskperweek、inp_riskpermonth、inp_plandayprofit 接进内部变量,后续刷新即可随账户波动重算。外汇和贵金属杠杆高,这类额度只是概率层面的约束,实际回撤可能突破预设。 开 MT5 把这段代码塞进自己的 EA 基类,改 inp_ 前缀的输入值,就能看到不同本金下每日该留多少风险敞口。
PlanDayProfit class=class="str">"cmt">// target daily profit, in currency ; class="type">class="kw">double CurrentEquity, class=class="str">"cmt">// current equity value CurrentBallance; class=class="str">"cmt">// current balance class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| RefreshLimits | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool RiskManagerBase::RefreshLimits(class="type">void) { CurrentEquity = NormalizeDouble(AccountInfoDouble(ACCOUNT_EQUITY),class="num">2); class=class="str">"cmt">// request current equity value CurrentBallance = NormalizeDouble(AccountInfoDouble(ACCOUNT_BALANCE),class="num">2); class=class="str">"cmt">// request current balance StartBalance = NormalizeDouble(AccountInfoDouble(ACCOUNT_BALANCE),class="num">2); class=class="str">"cmt">// set start balance StartEquity = NormalizeDouble(AccountInfoDouble(ACCOUNT_EQUITY),class="num">2); class=class="str">"cmt">// request current equity value PlanDayProfit = NormalizeDouble(StartEquity * plandayprofit/class="num">100,class="num">2); class=class="str">"cmt">// target daily profit, in currency PlanDayEquity = NormalizeDouble(StartEquity + PlanDayProfit/class="num">100,class="num">2); class=class="str">"cmt">// target equity, in currency RiskPerDay = NormalizeDouble(StartEquity * riskperday/class="num">100,class="num">2); class=class="str">"cmt">// risk per day in currency RiskPerWeek = NormalizeDouble(StartEquity * riskperweek/class="num">100,class="num">2); class=class="str">"cmt">// risk per week in currency RiskPerMonth = NormalizeDouble(StartEquity * riskpermonth/class="num">100,class="num">2); class=class="str">"cmt">// risk per month in currency class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| RiskManagerBase | class=class="str">"cmt">//+------------------------------------------------------------------+ RiskManagerBase::RiskManagerBase() { riskperday = inp_riskperday; class=class="str">"cmt">// set the value for the internal variable riskperweek = inp_riskperweek; class=class="str">"cmt">// set the value for the internal variable riskpermonth = inp_riskpermonth; class=class="str">"cmt">// set the value for the internal variable plandayprofit = inp_plandayprofit; class=class="str">"cmt">// set the value for the internal variable }