将您的线性交易系统提升为幂交易系统·进阶篇
📈

将您的线性交易系统提升为幂交易系统·进阶篇

(2/3)·固定手数赚得慢?本篇把 Ralph Vince 模型塞进你的 MQL5 系统,让净值曲线弯成抛物线

新手友好 第 2/3 篇

很多交易者把系统写完后就一直用固定手数跑,账户涨得像直线却跑不赢复利。问题不在信号,在仓位模型——你让资金规模和市场风险脱了钩。

「把仓位管理塞进 CEvolution 类里」

做 EA 如果用面向对象思路,CEvolution 这个核心类不该只记状态和历史操作,它更适合承载资金曲线相关的动态逻辑。净值曲线斜率一直在变,固定分数法下的手数其实是个变量,而不是开盘写死就完事——这部分计算必须跟着 OnTick 走,放在 CEvolution 里才顺手。 EA 初始化时,原始净值会存进 m_originalEquity 成员;之后 CalcEquityGrowth 算的是相对原始值的增长比例,RefreshExpEquityLevel 按每次报价刷新指数级别,RefreshLotSize 则同步刷新手数。也就是说,你在 EA 的 OnTick 里得显式调这两个 Refresh 方法,否则仓位规模不会随净值漂移。 这套实现算固定分数的变体:净值在摸到“水平十”之前走指数增长,越过之后转线性,但底层“仓位规模与净值曲线成比例放大”的设定没变。外汇和贵金属杠杆高,这种随净值加码的逻辑会放大回撤,实盘前务必在 MT5 策略测试器里跑不同净值路径验证。 下面这段是类的受保护成员与接口声明,重点看 m_originalEquity 和 m_lotSize 怎么被 getter/setter 暴露出来:

MQL5 / C++
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,验证手数是否随账户权益变化重算。外汇与贵金属杠杆高,自动调仓逻辑一旦写错,可能放大回撤。

MQL5 / C++
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,就能看清楚账户处在哪一档,再决定要不要砍手数。

MQL5 / C++
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 手错挂到别的区间。

MQL5 / C++
  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 的返回值,观察十天模拟盘里等级切换是否和你的权益台阶预期一致。

MQL5 / C++
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
  };
把仓位计算交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到当前净值下的固定分数建议手数,你只管确认信号。

常见问题

f 越小破产概率越低但曲线越平;f 偏大连亏时回撤陡,外汇贵金属本身高杠杆,建议先用小分数回测再上实盘。
目前小布在品种页给出分数测算与风险占比提示,下单执行仍需你在终端确认,它不代操作。
它封装了权益读取、单笔风险换算和合约数取整,让线性 EA 只改几行就能接上幂级仓位。
因每笔风险是净值的同一比例,权益趋近零时下单量也趋近零,连亏也留得住底仓,但实盘点差滑点会扰动该假设。