将您的线性交易系统提升为幂交易系统·进阶篇
(2/3)·固定手数赚得慢?本篇把 Ralph Vince 模型塞进你的 MQL5 系统,让净值曲线弯成抛物线
很多交易者把系统写完后就一直用固定手数跑,账户涨得像直线却跑不赢复利。问题不在信号,在仓位模型——你让资金规模和市场风险脱了钩。
「把仓位管理塞进 CEvolution 类里」
做 EA 如果用面向对象思路,CEvolution 这个核心类不该只记状态和历史操作,它更适合承载资金曲线相关的动态逻辑。净值曲线斜率一直在变,固定分数法下的手数其实是个变量,而不是开盘写死就完事——这部分计算必须跟着 OnTick 走,放在 CEvolution 里才顺手。 EA 初始化时,原始净值会存进 m_originalEquity 成员;之后 CalcEquityGrowth 算的是相对原始值的增长比例,RefreshExpEquityLevel 按每次报价刷新指数级别,RefreshLotSize 则同步刷新手数。也就是说,你在 EA 的 OnTick 里得显式调这两个 Refresh 方法,否则仓位规模不会随净值漂移。 这套实现算固定分数的变体:净值在摸到“水平十”之前走指数增长,越过之后转线性,但底层“仓位规模与净值曲线成比例放大”的设定没变。外汇和贵金属杠杆高,这种随净值加码的逻辑会放大回撤,实盘前务必在 MT5 策略测试器里跑不同净值路径验证。 下面这段是类的受保护成员与接口声明,重点看 m_originalEquity 和 m_lotSize 怎么被 getter/setter 暴露出来:
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CEvolution.mqh | class=class="str">"cmt">//| Copyright © class="num">2013, Jordi Bassagañas | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Mine\Enums.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CEvolution Class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CEvolution { class="kw">protected: ENUM_STATUS_EA m_status; class=class="str">"cmt">// The current EA&class="macro">#x27;s status ENUM_EXP_EQUITY_CURVE_LEVEL m_expEquityLevel; class=class="str">"cmt">// The current exponential equity level class="type">class="kw">double m_originalEquity; class=class="str">"cmt">// The original equity value class="type">class="kw">double m_lotSize; class=class="str">"cmt">// The current lot size class="kw">public: class=class="str">"cmt">//--- Constructor and destructor methods CEvolution(ENUM_STATUS_EA status,ENUM_EXP_EQUITY_CURVE_LEVEL exp_equity_level); ~CEvolution(class="type">void); class=class="str">"cmt">//--- Getter methods ENUM_STATUS_EA GetStatus(class="type">void); ENUM_EXP_EQUITY_CURVE_LEVEL GetExpEquityLevel(class="type">void); class="type">class="kw">double GetOriginalEquity(class="type">void); class="type">class="kw">double GetLotSize(class="type">void); class=class="str">"cmt">//--- Setter methods class="type">void SetStatus(ENUM_STATUS_EA status); class="type">void SetExpEquityLevel(ENUM_EXP_EQUITY_CURVE_LEVEL exp_equity_level); class="type">void SetOriginalEquity(class="type">class="kw">double equity);
仓位进化类的构造与取值接口
这段 CEvolution 类的实现,把 EA 运行时的状态、预期权益曲线等级和初始仓位绑在了一起。构造函数接收两个枚举参数:EA 状态与预期权益层级,随后立即调用 RefreshLotSize() 重算手数,并用 AccountInfoDouble(ACCOUNT_EQUITY) 把账户当前权益存进 m_originalEquity 作为基准。 析构函数为空,说明这个类不持有需要手动释放的资源,挂在 EA 生命周期内随实例消亡即可。 对外暴露的取数接口很直接:GetStatus 拿 EA 状态,GetExpEquityLevel 拿权益层级,GetOriginalEquity 回吐建类时的权益快照,GetLotSize 返回当前计算好的手数。注意 m_originalEquity 只在构造瞬间采一次,后续净值波动不会改写它,这对做权益曲线对比是有用的固定锚点。 在 MT5 里把这段贴进头文件配套的实现文件,编译后可在 OnInit 打印 GetOriginalEquity 与 GetLotSize,验证手数是否随账户权益变化重算。外汇与贵金属杠杆高,自动调仓逻辑一旦写错,可能放大回撤。
class="type">void SetLotSize(class="type">class="kw">double size); class=class="str">"cmt">//--- CEvolution specific methods class="type">class="kw">double CalcEquityGrowth(class="type">class="kw">double currentEquity); class="type">void RefreshExpEquityLevel(class="type">class="kw">double currentEquity); class="type">void RefreshLotSize(); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CEvolution::CEvolution(ENUM_STATUS_EA status,ENUM_EXP_EQUITY_CURVE_LEVEL exp_equity_level) { m_status=status; m_expEquityLevel=exp_equity_level; RefreshLotSize(); m_originalEquity=AccountInfoDouble(ACCOUNT_EQUITY); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Destructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CEvolution::~CEvolution(class="type">void) { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| GetStatus | class=class="str">"cmt">//+------------------------------------------------------------------+ ENUM_STATUS_EA CEvolution::GetStatus(class="type">void) { class="kw">return m_status; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| GetExpEquityLevel | class=class="str">"cmt">//+------------------------------------------------------------------+ ENUM_EXP_EQUITY_CURVE_LEVEL CEvolution::GetExpEquityLevel(class="type">void) { class="kw">return m_expEquityLevel; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| GetEquity | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CEvolution::GetOriginalEquity(class="type">void) { class="kw">return m_originalEquity; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| GetLotSize | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CEvolution::GetLotSize(class="type">void) { class="kw">return m_lotSize; } class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 用权益曲线分级给EA踩刹车
在自进化EA里,光有开仓逻辑不够,得先给账户状态留个接口。CEvolution类用一组Setter把运行状态、预期权益档位、原始权益、手数这些关键变量写进私有成员,方便后续模块读取并做风控切换。 CalcEquityGrowth算的是权益相对原始值的百分比增幅,公式是 currentEquity*100/originalEquity-100,再用NormalizeDouble保留两位小数。比如原始权益10000、当前11000,返回就是10.00,代表增长了10%。 RefreshExpEquityLevel按增幅把账户划成四档:≤10%进LEVEL_ONE,10%~20%进LEVEL_TWO,20%~30%进LEVEL_THREE,30%~40%进LEVEL_FOUR。外汇和贵金属杠杆高,权益掉到原始值一成内往往意味着策略已严重偏离,分级后就能让仓位或算法自动降级。 把下面这段代码直接塞进你的EA类里,跑起来后打印m_expEquityLevel,就能看清楚账户处在哪一档,再决定要不要砍手数。
class=class="str">"cmt">//| SetStatus | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CEvolution::SetStatus(ENUM_STATUS_EA status) { m_status=status; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| SetExpEquityLevel | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CEvolution::SetExpEquityLevel(ENUM_EXP_EQUITY_CURVE_LEVEL exp_equity_level) { m_expEquityLevel=exp_equity_level; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| SetEquity | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CEvolution::SetOriginalEquity(class="type">class="kw">double equity) { m_originalEquity=equity; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| SetLotSize | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CEvolution::SetLotSize(class="type">class="kw">double lot_size) { m_lotSize=lot_size; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CalcEquityGrowth | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CEvolution::CalcEquityGrowth(class="type">class="kw">double currentEquity) { class="kw">return NormalizeDouble(currentEquity * class="num">100 / m_originalEquity - class="num">100,class="num">2); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| RefreshExpEquityLevel | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CEvolution::RefreshExpEquityLevel(class="type">class="kw">double currentEquity) { class="type">class="kw">double growth = CalcEquityGrowth(currentEquity); class=class="str">"cmt">//--- is the current equity less than class="num">10% of the original amount? if(growth <= class="num">10) { SetExpEquityLevel(LEVEL_ONE); } class=class="str">"cmt">//--- is the current equity more than class="num">10% of the original amount and less than class="num">20%? else if(growth > class="num">10 && growth <= class="num">20) { SetExpEquityLevel(LEVEL_TWO); } class=class="str">"cmt">//--- is the current equity more than class="num">20% of the original amount and less than class="num">30%? else if(growth > class="num">20 && growth <= class="num">30) { SetExpEquityLevel(LEVEL_THREE); } class=class="str">"cmt">//--- is the current equity more than class="num">30% of the original amount and less than class="num">40%? else if(growth > class="num">30 && growth <= class="num">40) { SetExpEquityLevel(LEVEL_FOUR); }
「净值区间映射到手数档位」
这段逻辑把账户净值相对初始资金的增长率(growth)切成若干区间,每个区间对应一个风险档位常量。注意 80%~90% 这段是空档:growth 在 (80,90] 时既不命中 LEVEL_NINE 也不命中 LEVEL_TEN,档位维持上一次设定值,实盘里可能漏掉一档加仓节奏。 定位到 40%~50% 时写 LEVEL_FIVE,50%~60% 写 LEVEL_SEVEN,60%~70% 写 LEVEL_EIGHT,70%~80% 写 LEVEL_NINE,大于 90% 直接 LEVEL_TEN。档位编号和增长率并不线性对齐,例如跳过 LEVEL_SIX 而用 LEVEL_SEVEN 承接 50% 拐点,说明作者把中段风险抬升刻意压了一档。 RefreshLotSize 用 switch 把档位翻译成具体手数:LEVEL_ONE 到 LEVEL_TEN 依次 0.01~0.1,步长 0.01。外汇与贵金属杠杆高,这种按净值爬坡线性加手数的做法在回撤段会放大亏损概率,MT5 策略测试器里把初始本金设 1000 美元跑一遍 EURUSD 就能看到 0.1 手在 LEVEL_TEN 时的回撤曲线。 别把档位编号当增长率 档位名 LEVEL_FIVE/SEVEN/EIGHT 只是枚举标签,和 40/50/60 这些数字无算术关系。改阈值前先确认枚举定义,否则动一行 growth 边界可能让 0.07 手错挂到别的区间。
class=class="str">"cmt">//--- is the current equity more than class="num">40% of the original amount and less than class="num">50%? else if(growth > class="num">40 && growth <= class="num">50) { SetExpEquityLevel(LEVEL_FIVE); } class=class="str">"cmt">//--- is the current equity more than class="num">50% of the original amount and less than class="num">60%? else if(growth > class="num">50 && growth <= class="num">60) { SetExpEquityLevel(LEVEL_SEVEN); } class=class="str">"cmt">//--- is the current equity more than class="num">60% of the original amount and less than class="num">70%? else if(growth > class="num">60 && growth <= class="num">70) { SetExpEquityLevel(LEVEL_EIGHT); } class=class="str">"cmt">//--- is the current equity more than class="num">70% of the original amount and less than class="num">80%? else if(growth > class="num">70 && growth <= class="num">80) { SetExpEquityLevel(LEVEL_NINE); } class=class="str">"cmt">//--- is the current equity more than class="num">90% of the original amount? else if(growth > class="num">90) { SetExpEquityLevel(LEVEL_TEN); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| RefreshLotSize | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CEvolution::RefreshLotSize() { class="kw">switch(m_expEquityLevel) { case LEVEL_ONE: SetLotSize(class="num">0.01); break; case LEVEL_TWO: SetLotSize(class="num">0.02); break; case LEVEL_THREE: SetLotSize(class="num">0.03); break; case LEVEL_FOUR: SetLotSize(class="num">0.04); break; case LEVEL_FIVE: SetLotSize(class="num">0.05); break; case LEVEL_SIX: SetLotSize(class="num">0.06); break; case LEVEL_SEVEN: SetLotSize(class="num">0.07); break; case LEVEL_EIGHT: SetLotSize(class="num">0.08); break; case LEVEL_NINE: SetLotSize(class="num">0.09); break; case LEVEL_TEN: SetLotSize(class="num">0.1); break; } }
用净值曲线驱动仓位与状态切换
把账户净值做成可计算的演化对象,比写死手数更贴近实盘。下面这段代码在构造时记录原始权益,并立刻按状态刷新手数,相当于给 EA 一个‘出生基准线’。 [CODE]
| // | Constructor |
|---|
//+------------------------------------------------------------------+ CEvolution::CEvolution(ENUM_STATUS_EA status,ENUM_EXP_EQUITY_CURVE_LEVEL exp_equity_level) { m_status=status; m_expEquityLevel=exp_equity_level; RefreshLotSize(); m_originalEquity=AccountInfoDouble(ACCOUNT_EQUITY); } //+------------------------------------------------------------------+
| // | CalcEquityGrowth |
|---|
//+------------------------------------------------------------------+ double CEvolution::CalcEquityGrowth(double currentEquity) { return NormalizeDouble(currentEquity * 100 / m_originalEquity - 100,2); } GetEvolution().RefreshExpEquityLevel(AccountInfoDouble(ACCOUNT_EQUITY)); GetEvolution().RefreshLotSize(); //+------------------------------------------------------------------+
| // | Exponential equity curve level enumeration |
|---|
//+------------------------------------------------------------------+ enum ENUM_EXP_EQUITY_CURVE_LEVEL { LEVEL_ONE, LEVEL_TWO, LEVEL_THREE, LEVEL_FOUR, LEVEL_FIVE, LEVEL_SIX, LEVEL_SEVEN, LEVEL_EIGHT, LEVEL_NINE, LEVEL_TEN }; [/CODE] 逐行看:构造函数先把外部传入的状态与曲线等级存进成员变量,调用 RefreshLotSize() 按当前设定算手数;随后用 AccountInfoDouble(ACCOUNT_EQUITY) 抓当下净值写进 m_originalEquity,这个基准后续不会变。CalcEquityGrowth 把当前权益除以原始权益乘 100 再减 100,NormalizeDouble 精确到 2 位小数——出来的就是净值增长率百分比,例如权益从 10000 到 10150 会返回 1.50。 每次 tick 或定时里那两行 GetEvolution() 调用很关键:先按最新权益重算曲线等级,再依等级重刷手数,让仓位随净值台阶往上走。ENUM_EXP_EQUITY_CURVE_LEVEL 列了 LEVEL_ONE 到 LEVEL_TEN 十个档,具体每档对应多少权益阈值,要在 RefreshExpEquityLevel 里自己定义。 外汇和贵金属杠杆高,净值回撤时若等级往下调而手数没同步缩,回撤会被放大。开 MT5 把这段代码塞进 EA,先只打印 CalcEquityGrowth 的返回值,观察十天模拟盘里等级切换是否和你的权益台阶预期一致。
class=class="str">"cmt">//| Constructor | class=class="str">"cmt">//+------------------------------------------------------------------+ CEvolution::CEvolution(ENUM_STATUS_EA status,ENUM_EXP_EQUITY_CURVE_LEVEL exp_equity_level) { m_status=status; m_expEquityLevel=exp_equity_level; RefreshLotSize(); m_originalEquity=AccountInfoDouble(ACCOUNT_EQUITY); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CalcEquityGrowth | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CEvolution::CalcEquityGrowth(class="type">class="kw">double currentEquity) { class="kw">return NormalizeDouble(currentEquity * class="num">100 / m_originalEquity - class="num">100,class="num">2); } GetEvolution().RefreshExpEquityLevel(AccountInfoDouble(ACCOUNT_EQUITY)); GetEvolution().RefreshLotSize(); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Exponential equity curve level enumeration | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_EXP_EQUITY_CURVE_LEVEL { LEVEL_ONE, LEVEL_TWO, LEVEL_THREE, LEVEL_FOUR, LEVEL_FIVE, LEVEL_SIX, LEVEL_SEVEN, LEVEL_EIGHT, LEVEL_NINE, LEVEL_TEN };