MQL5 向导:如何创建风险和资金管理模块·综合运用
(3/3)· 前两部分拆了信号与基类,这一篇把资金风控模块拼进完整 EA 跑通最后一公里
亏损后翻倍手数的资金管理类骨架
在 MT5 的 EA 向导里,资金管理模块(Money 类)可以通过一段向导描述头快速注册。上面这段源码展示了名为 CSampleMoney 的样例类,其标题直译是「亏损后翻倍手数交易」,本质是一套马丁格尔式的仓位放大逻辑。 注意两个外部参数:Lots 默认 0.1,Percent 默认 100.0。Lots 是首笔基准手数,Percent=100 意味着若启用百分比模式,下注将按账户净值 100% 折算——实盘外汇或贵金属这样设会瞬间爆仓,高风险且只适合隔离测试。 代码头部用 //| Type=Money 等注释块向向导声明类名与参数,编译器并不读取它们,真正生效的是 #include <Expert\ExpertMoney.mqh> 与 <Trade\DealInfo.mqh>。开 MT5 把这段头文件拼进自定义 Money 类,就能在策略测试器里直接选到「Sample」并改 Lots 跑一遍。
class=class="str">"cmt">//| Type=Money | class=class="str">"cmt">//| Name=Sample | class=class="str">"cmt">//| Class=CSampleMoney | class=class="str">"cmt">//| Page= | class=class="str">"cmt">//| Parameter=Lots,class="type">class="kw">double,class="num">0.1 | class=class="str">"cmt">//| Parameter=Percent,class="type">class="kw">double,class="num">100.0 | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">// wizard description end class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| SampleMoney.mqh | class=class="str">"cmt">//| Copyright class="num">2010, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2010, MetaQuotes Software Corp." class="macro">#class="kw">property link "[MQL5官方文档] class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Include files | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Expert\ExpertMoney.mqh> class="macro">#include <Trade\DealInfo.mqh> class=class="str">"cmt">// wizard description start class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Description of the class | class=class="str">"cmt">//| Title=Trading with lot doubling after a loss | class=class="str">"cmt">//| Type=Money | class=class="str">"cmt">//| Name=Sample | class=class="str">"cmt">//| Class=CSampleMoney | class=class="str">"cmt">//| Page= | class=class="str">"cmt">//| Parameter=Lots,class="type">class="kw">double,class="num">0.1 | class=class="str">"cmt">//| Parameter=Percent,class="type">class="kw">double,class="num">100.0 | class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 亏损后翻倍仓位的资金管理类骨架
在 MT5 的 EA 框架里,可以把「亏损后加倍手数」的逻辑封装成一个继承自 CExpertMoney 的子类,而不是散落在交易函数里。下面这段类声明定义了核心成员:正常市况下的基础手数 m_lots,以及几个必须由子类实现的虚函数接口。 构造函数把 m_lots 默认设为 0.1,也就是说不传参时每笔常规交易先按 0.1 手跑。CheckPrevLoss() 是 protected 方法,留给后续实现去统计上一次平仓是否亏损并返回加仓倍数,CheckOpenLong / CheckOpenShort 则分别在多空开仓前被调用来算实际手数。 外汇和贵金属杠杆高,这种马丁式翻仓在连亏时保证金占用会指数级膨胀,实盘前务必在策略测试器里用历史数据跑一遍最大回撤。
class CSampleMoney : class="kw">public CExpertMoney { class="kw">protected: class=class="str">"cmt">//--- Setup parameters class="type">class="kw">double m_lots; class=class="str">"cmt">// Deal volume for "normal" conditions class="kw">public: CSampleMoney(); class=class="str">"cmt">//--- Methods to set the parameters class="type">void Lots(class="type">class="kw">double lots) { m_lots=lots; } class=class="str">"cmt">//--- Methods to validate the parameters class="kw">virtual class="type">bool ValidationSettings(); class=class="str">"cmt">//--- Methods to define the volume class="kw">virtual class="type">class="kw">double CheckOpenLong(class="type">class="kw">double price,class="type">class="kw">double sl); class="kw">virtual class="type">class="kw">double CheckOpenShort(class="type">class="kw">double price,class="type">class="kw">double sl); class="kw">protected: class="type">class="kw">double CheckPrevLoss(); }; class="type">void CSampleMoney::CSampleMoney() { class=class="str">"cmt">//--- Setting class="kw">default values m_lots=class="num">0.1; }
「手数校验与加仓倍数的落地写法」
在 MT5 写资金管理类时,开仓前必须把手数卡在经纪商允许区间内。CSampleMoney::ValidationSettings() 先调基类校验,再比对 m_lots 是否小于 LotsMin() 或大于 LotsMax(),越界就 printf 报错并返回 false。 另一个隐蔽坑是手数步长:用 MathAbs(m_lots/LotsStep() - MathRound(m_lots/LotsStep())) > 1e-10 判断是否为步长整数倍,浮点误差阈值取 1.0E-10 而非 0,避免极小偏差误杀合法手数。 开多单的 CheckOpenLong 里,lot 先设为 2*CheckPrevLoss(),若上次无亏损记录则回退到基础 m_lots。随后两次封顶:先砍到 LotsMax(),再经 m_account.MaxLotCheck() 按账户保证金百分比重算可开上限,取较小值返回。 外汇与贵金属杠杆高,MaxLotCheck 返回的额度随持仓和浮动盈亏实时变,实盘里这段代码可能在非农行情中把计划 0.5 手压到 0.12 手,务必在策略测试器里用不同账户净值跑一遍验证。
class="type">bool CSampleMoney::ValidationSettings() { class=class="str">"cmt">//--- Call the base class method if(!CExpertMoney::ValidationSettings()) class="kw">return(false); class=class="str">"cmt">//--- Validating the parameters if(m_lots<m_symbol.LotsMin() || m_lots>m_symbol.LotsMax()) { printf(__FUNCTION__+": The deal volume must be in the range %f to %f",m_symbol.LotsMin(),m_symbol.LotsMax()); class="kw">return(false); } if(MathAbs(m_lots/m_symbol.LotsStep()-MathRound(m_lots/m_symbol.LotsStep()))>class="num">1.0E-10) { printf(__FUNCTION__+": The deal volume must be multiple of %f",m_symbol.LotsStep()); class="kw">return(false); } class=class="str">"cmt">//--- Successful completion class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Defining the volume to open a class="type">long position. | class=class="str">"cmt">//| INPUT: no. | class=class="str">"cmt">//| OUTPUT: lot-if successful, class="num">0.0 otherwise. | class=class="str">"cmt">//| REMARK: no. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CSampleMoney::CheckOpenLong(class="type">class="kw">double price,class="type">class="kw">double sl) { if(m_symbol==NULL) class="kw">return(class="num">0.0); class=class="str">"cmt">//--- Select the lot size class="type">class="kw">double lot=class="num">2*CheckPrevLoss(); if(lot==class="num">0.0) lot=m_lots; class=class="str">"cmt">//--- Check the limits class="type">class="kw">double maxvol=m_symbol.LotsMax(); if(lot>maxvol) lot=maxvol; class=class="str">"cmt">//--- Check the margin requirements if(price==class="num">0.0) price=m_symbol.Ask(); maxvol=m_account.MaxLotCheck(m_symbol.Name(),ORDER_TYPE_BUY,price,m_percent); if(lot>maxvol) lot=maxvol; class=class="str">"cmt">//--- Return the trade volume class="kw">return(lot); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//|Defining the volume to open a class="type">short position. | class=class="str">"cmt">//| INPUT: no. | class=class="str">"cmt">//| OUTPUT: lot-if successful, class="num">0.0 otherwise. | class=class="str">"cmt">//| REMARK: no. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CSampleMoney::CheckOpenShort(class="type">class="kw">double price,class="type">class="kw">double sl) { if(m_symbol==NULL) class="kw">return(class="num">0.0); class=class="str">"cmt">//--- Select the lot size class="type">class="kw">double lot=class="num">2*CheckPrevLoss(); if(lot==class="num">0.0) lot=m_lots;
手把手掐掉超限仓位与上一单亏损捕获
下单前如果不先卡死经纪商允许的 LotsMax(),MT5 会直接报 4756 错误(无效交易量)拒单。下面这段先把计算出的 lot 和 symbol.LotsMax() 比大小,超了就拉回上限,属于裸奔 EA 最容易漏的一步。 //--- Check the limits double maxvol=m_symbol.LotsMax(); if(lot>maxvol) lot=maxvol; 保证金校验不能只看手数上限。price 为 0 时直接取买价 Bid(),再用 AccountInfoDouble 封装的 MaxLotCheck 按 m_percent 风险比例反推可行手数,二次截断后才 return。外汇和贵金属杠杆波动大,这套双保险能避免满仓被 margin call 的概率升高。 //--- Check the margin requirements if(price==0.0) price=m_symbol.Bid(); maxvol=m_account.MaxLotCheck(m_symbol.Name(),ORDER_TYPE_SELL,price,m_percent); if(lot>maxvol) lot=maxvol; 另一段 CheckPrevLoss() 专挖历史里上一笔同品种成交:HistorySelect(0,TimeCurrent()) 拉全量,倒序遍历 deals,遇到 Symbol() 匹配且 Profit()<0.0 就返回那单 Volume(),否则返 0.0。马丁类加仓逻辑靠它判断「上一单是否亏着」再决定翻倍手数。 //--- Request the history of deals and orders HistorySelect(0,TimeCurrent()); int deals=HistoryDealsTotal(); CDealInfo deal; for(int i=deals-1;i>=0;i--) { if(!deal.SelectByIndex(i)) break; if(deal.Symbol()!=m_symbol.Name()) continue; if(deal.Profit()<0.0) lot=deal.Volume(); break; } 开 MT5 按 F4 把这两段塞进你的资金管理类,改 m_percent 从 1 到 3 跑回测,能直接看到爆仓前手数被掐下来的次数。贵金属 XAUUSD 点值大,建议 m_percent 别超 2,否则滑点一来亏损倾向放大。
class=class="str">"cmt">//--- Check the limits class="type">class="kw">double maxvol=m_symbol.LotsMax(); if(lot>maxvol) lot=maxvol; class=class="str">"cmt">//--- Check the margin requirements if(price==class="num">0.0) price=m_symbol.Bid(); maxvol=m_account.MaxLotCheck(m_symbol.Name(),ORDER_TYPE_SELL,price,m_percent); if(lot>maxvol) lot=maxvol; class=class="str">"cmt">//--- Return the trade volume class="kw">return(lot); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Defines whether the prev. deal was losing. | class=class="str">"cmt">//| INPUT: no. | class=class="str">"cmt">//| OUTPUT: Volume of the prev. deal if it&class="macro">#x27;s losing, otherwise class="num">0.0 | class=class="str">"cmt">//| REMARK: no. | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CSampleMoney::CheckPrevLoss() { class="type">class="kw">double lot=class="num">0.0; class=class="str">"cmt">//--- Request the history of deals and orders HistorySelect(class="num">0,TimeCurrent()); class=class="str">"cmt">//--- variables class="type">int deals=HistoryDealsTotal(); class=class="str">"cmt">// Total number of deals in the history CDealInfo deal; class=class="str">"cmt">//--- Find the previous deal for(class="type">int i=deals-class="num">1;i>=class="num">0;i--) { if(!deal.SelectByIndex(i)) { printf(__FUNCTION__+": Error of deal selection by index"); break; } class=class="str">"cmt">//--- Check the symbol if(deal.Symbol()!=m_symbol.Name()) class="kw">continue; class=class="str">"cmt">//---Check the profit if(deal.Profit()<class="num">0.0) lot=deal.Volume(); break; } class=class="str">"cmt">//--- Return the volume class="kw">return(lot); }
◍ 把自定义风控接进向导后的下一步
MQL5 向导里的策略生成器,本质是把信号、资金管理和持仓支持三类标准库组件拼起来,省掉手写 EA 框架的重复劳动。前面几篇已经把自定义资金模块、追踪止损模块和信号模块的接法拆开讲过,核心就是按向导要求的类说明格式写 MQH,再在生成器里勾选启用。 以随上一笔结果调整手数的资金类为例,它依赖历史订单查询。代码里那行 HistorySelect(0, TimeCurrent()) 只拉从头到现在的全部历史,订单量一大回测就会明显变慢——有用户反馈日线周期下过去24小时缓存法并不适用,得按品种周期自己定查询深度。 外汇和贵金属杠杆高,这类马丁或盈亏加权手数逻辑回测漂亮不等于实盘能跑,请用 MT5 策略测试器拿真实点差验一遍再考虑上 VPS。 想继续压榨速度,就把 HistorySelect 换成限定窗口的按需查询,别每次 tick 全量重拉。
HistorySelect(class="num">0,TimeCurrent());