MQL5 向导:如何创建风险和资金管理模块·综合运用
🧩

MQL5 向导:如何创建风险和资金管理模块·综合运用

(3/3)· 前两部分拆了信号与基类,这一篇把资金风控模块拼进完整 EA 跑通最后一公里

案例拆解 第 3/3 篇
多数人在向导里只改信号类,资金模块沿用默认,回测漂亮实盘却爆仓。把 CExpertMoney 当成可替换组件来设计,才能在风险敞口上留自己的旋钮。

亏损后翻倍手数的资金管理类骨架

在 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 跑一遍。

MQL5 / C++
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 则分别在多空开仓前被调用来算实际手数。 外汇和贵金属杠杆高,这种马丁式翻仓在连亏时保证金占用会指数级膨胀,实盘前务必在策略测试器里用历史数据跑一遍最大回撤。

MQL5 / C++
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 手,务必在策略测试器里用不同账户净值跑一遍验证。

MQL5 / C++
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,否则滑点一来亏损倾向放大。

MQL5 / C++
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 全量重拉。

MQL5 / C++
HistorySelect(class="num">0,TimeCurrent());
让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到仓位占比与风险百分比的实时预警,把重复劳动交给小布,你专注决策。

常见问题

Percent 一般在初始化或参数刷新时设置风险百分比,Validation 在每次下单前做敞口与账户状态校验,返回 false 会阻断交易。
默认模块按固定百分比算量,未考虑贵金属点值波动大,可能在高波动时段给出偏大仓位,概率上提高回撤风险。
可以,小布盯盘的品种页支持解析 EA 公开参数与账户信息,对超阈值风险百分比给出提示,外汇贵金属属高风险品类需自行把关。
先 Init 信号与资金,再 Init 追踪;CExpert 主引擎在 Tick 中依次问信号准入、问资金给量、问追踪改单,顺序乱了可能漏单或重复开仓。
常见原因是 Validation 里用了账户实时杠杆或余额字段,回测环境与实际账户信息不同,导致计算路径分叉,倾向在基类里隔离这类调用。