MQL5 向导:如何创建风险和资金管理模块·进阶篇
(2/3)· 接基础信号篇,深挖 EA 四大支柱里的资金阀值与敞口控制类如何落地
「从零搭一个资金管理类的壳」
MT5 里做风控模块,第一步往往是继承系统自带的 CExpertMoney,而不是从头写仓位计算。下面这段头文件骨架演示了最小可用形态:只声明一个 CSampleMoney 类,公有继承自 CExpertMoney,类体暂时为空,纯粹先把编译链路跑通。 文件头部用 #property copyright 和 #property link 标注了 2010 年 MetaQuotes 的版权信息,并通过 #include <Expert\ExpertMoney.mqh> 把官方资金管理基类拉进来。实际你自己在 MT5 里新建 .mqh 时,这两行可以改成你自己的署名,但 include 路径必须指向标准库里的 Expert 目录,否则编译器会报找不到 CExpertMoney。 注释里还残留了 DLL 和 EX5 的 import 示例(SendMessageA、ExpertRecalculate、ErrorDescription),它们不是本类必须的,只是提示你后续若要把风控信号抛给外部 DLL 或调用 stdlib.ex5 的错误描述函数,导入语法长这样。先不动它们,把空类编译过,再往里塞计算逻辑才是稳妥路径。
class=class="str">"cmt">// class="type">int SendMessageA(class="type">int hWnd,class="type">int Msg,class="type">int wParam,class="type">int lParam); class=class="str">"cmt">// class="macro">#class="kw">import "my_expert.dll" class=class="str">"cmt">// class="type">int ExpertRecalculate(class="type">int wParam,class="type">int lParam); class=class="str">"cmt">// class="macro">#class="kw">import class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EX5 imports | class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">// class="macro">#class="kw">import "stdlib.ex5" class=class="str">"cmt">// class="type">class="kw">string ErrorDescription(class="type">int error_code); class=class="str">"cmt">// class="macro">#class="kw">import class=class="str">"cmt">//+------------------------------------------------------------------+ 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=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class CSampleMoney. | class=class="str">"cmt">//| Purpose: Class for risk and money management. | class=class="str">"cmt">//| It is derived from the CExpertMoney class. | class=class="str">"cmt">//+------------------------------------------------------------------+ class CSampleMoney : class="kw">public CExpertMoney { }; class=class="str">"cmt">//+------------------------------------------------------------------+
亏损后翻倍仓位的资金管理类骨架
在 MT5 的 EA 框架里,想做「亏一笔就下一笔双倍手数」的马丁式风控,通常从标准库 CExpertMoney 派生一个子类最省事。下面这段头文件只搭了空壳,但已经把基础参数位留出来了。 CSampleMoney 继承自 CExpertMoney,protected 区里放了 m_lots 这个 double 变量,注释写明它是「正常市况下的成交手数」。public 区给了构造函数声明和 Lots() 设值方法,用内联写法把传入的 lots 直接赋给 m_lots。 注意此时类体里还没有任何加仓翻倍的逻辑,只是把「基准手数」这一个点钉死了。真要跑翻倍,得在 CheckOpen/CheckClose 的派生实现里读持仓盈亏后改写下单量——外汇与贵金属杠杆高,这类序列加码在连亏 6~7 笔后保证金占用会指数级膨胀,实盘前务必在策略测试器里用历史数据验一遍爆仓距离。
…
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Include files | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Expert\ExpertMoney.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class CSampleMoney. | class=class="str">"cmt">//| Purpose: Class for risk and money management | class=class="str">"cmt">//| doubling the volume after a loss deal. | class=class="str">"cmt">//| It is derived from the CExpertMoney class. | class=class="str">"cmt">//+------------------------------------------------------------------+ class CSampleMoney : class="kw">public CExpertMoney { }; class=class="str">"cmt">//+------------------------------------------------------------------+ 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=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class CSampleMoney. | class=class="str">"cmt">//| Purpose: Class for risk and money management | class=class="str">"cmt">//| doubling the volume after a loss deal. | class=class="str">"cmt">//| It is derived from the CExpertMoney class. | class=class="str">"cmt">//+------------------------------------------------------------------+ 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">//+------------------------------------------------------------------+
◍ 手搓资金管理类的构造与参数校验
在 MT5 的 EA 框架里,自定义头寸管理类通常继承自 CExpertMoney。下面这段 CSampleMoney 的实现,展示了构造函数如何给默认手数落地,以及校验逻辑怎么拦住不合规的下单量。 构造函数 CSampleMoney() 只做一件事:把成员变量 m_lots 设为 0.1。也就是说,如果你不在外部调用 Lots(0.2) 之类的方法,策略默认每次按 0.1 手去跑。 校验函数 ValidationSettings() 先调用父类的同名方法,再对 m_lots 做两道闸:一是必须在当前品种的 LotsMin() 与 LotsMax() 区间内,二是必须能被 LotsStep() 整除(容差 1e-10)。任意一道不过,就 printf 报错并返回 false,EA 不会继续初始化。 [CODE] 里的类声明还暴露了一个受保护方法 CheckPrevLoss(),从命名看是为连亏加仓或减仓预留的钩子,本小节未给出实现,但你在改写自己的资金管理器时可以照这个骨架接进去。外汇与贵金属杠杆高,手数越界可能直接被经纪商拒单或强平,参数校验不是可选项。
class=class="str">"cmt">//| Constructor CSampleMoney. |//| INPUT: no. |//| OUTPUT: no. |//| REMARK: no. |//+------------------------------------------------------------------+ class="type">void CSampleMoney::CSampleMoney() { class=class="str">"cmt">//--- setting the class="kw">default values m_lots=class="num">0.1; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Validation of the setup parameters. |//| INPUT: no. |//| OUTPUT: true if the settings are correct, otherwise false. |//| REMARK: no. |//+------------------------------------------------------------------+ 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">//--- Validation of 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 volume of the deal must be multiple of %f",m_symbol.LotsStep()); class="kw">return(false); } class=class="str">"cmt">//--- Successful completion class="kw">return(true); } 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 parameters class="type">void Lots(class="type">class="kw">double lots) { m_lots=lots; } class=class="str">"cmt">//--- Methods to validate parameters class="kw">virtual class="type">bool ValidationSettings(); class="kw">protected: class="type">class="kw">double CheckPrevLoss(); }; class=class="str">"cmt">//+------------------------------------------------------------------+
「亏损后翻倍手数的检测逻辑」
在马丁类资金管理中,判断上一笔同品种交易是否亏损并取回其手数,是加仓倍数的起点。下面这段方法从账户历史里倒序扫描成交记录,只认当前品种、且利润为负的那一笔,返回它的成交量;若没有亏损单则返回 0.0。
class="type">class="kw">double CSampleMoney::CheckPrevLoss() { class="type">class="kw">double lot=class="num">0.0; HistorySelect(class="num">0,TimeCurrent()); class="type">int deals=HistoryDealsTotal(); CDealInfo 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; } if(deal.Symbol()!=m_symbol.Name()) class="kw">continue; if(deal.Profit()<class="num">0.0) lot=deal.Volume(); break; } class="kw">return(lot); }
class="type">class="kw">double CSampleMoney::CheckPrevLoss() { class="type">class="kw">double lot=class="num">0.0; HistorySelect(class="num">0,TimeCurrent()); class="type">int deals=HistoryDealsTotal(); CDealInfo 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; } if(deal.Symbol()!=m_symbol.Name()) class="kw">continue; if(deal.Profit()<class="num">0.0) lot=deal.Volume(); break; } class="kw">return(lot); }
马丁类加仓的手数约束写法
这段代码给出的是多头与空头开仓手数的两套取量逻辑,核心思路是:若上笔止损有记录,则本次手数取 CheckPrevLoss() 返回值的 2 倍,否则回落到基础手数 m_lots。外汇与贵金属杠杆高,这种翻倍取量在连亏情形下会快速放大敞口,实盘前务必在 MT5 策略测试器跑极端行情样本。 两个函数都先判 m_symbol 是否为空,空则直接返 0.0 不开仓;随后用 m_symbol.LotsMax() 截断交易所允许上限,再用 m_account.MaxLotCheck() 按账户保证金百分比 m_percent 复核可开手数。多空差异仅在价格源:多单用 Ask(),空单用 Bid()。 下面把多头函数逐行拆一下,方便你直接复制到自己的 CSampleMoney 类里改参数验证。
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); }