利用余额图进行策略优化并将结果与
📉

利用余额图进行策略优化并将结果与 "余额 + 最大锋锐比率" 标准进行比较·进阶篇

(2/3)· 当盈利曲线不再够用时,怎样用趋势线偏差筛掉虚胖的回测结果

案例拆解 第 2/3 篇
很多交易者看到余额曲线一路向上就以为参数靠谱,却忽略中间大幅回撤的毛刺。把斜率当唯一标准,往往把偶然行情里的运气当成能力。平滑度不够的盈利,在实盘外汇贵金属里大概率撑不过波动放大。

「用线性回归把交易序列压成一条稳定性分数」

这段逻辑把每笔交易的序号和利润塞进一个二维矩阵,再用最小二乘拟合出 y = a·x + b 的回归线。x 是交易次序(从 1 开始),y 是对应交易的利润数组 arr_profits,样本量由 arr_size 决定。 CMatrixDouble xy(arr_size,2) 先开好容器,循环里 xy[i].Set(0,i+1) 写序号、xy[i].Set(1,arr_profits[i]) 写利润。LRBuild 跑完若 retcode 不是 1 就直接返回 0.0,说明拟合没成功,调用方拿不到有效分数。 LRUnpack 拆出 coefficients[0] 为截距 coeff_b、coefficients[1] 为斜率 coeff_a。TrendProfit 算的是回归线首尾投影差再除以 arr_size,本质是「平均每笔沿趋势的偏移利润」;TrendMSE 直接取报告里的 m_rmserror,即训练集均方根误差。 ProfitStability = TrendProfit / TrendMSE,这个值越大,说明利润序列沿回归线的方向性越强、抖动越小。若开启成交量归一化(GetVolumeNormalization 为真)会再除以 total_volume,最后乘回 arr_size 并放大 10000 倍输出。外汇与贵金属杠杆高,该分数仅反映历史样本的统计特征,实盘复制前请在 MT5 策略测试器用自己品种验证。 想直接看拟合结果,把 PrintFormat("y = %.1f x + %.1f",coeff_a,coeff_b) 留着,跑完会在日志打出斜率和截距,方便你判断趋势是向上摊利还是向下磨损。

MQL5 / C++
  }
    }
class=class="str">"cmt">//--- CMatrixDouble 对象
  CMatrixDouble xy(arr_size,class="num">2);
  for(class="type">int i=class="num">0;i<arr_size;i++)
    {
      xy[i].Set(class="num">0,i+class="num">1);
      xy[i].Set(class="num">1,arr_profits[i]);
      class=class="str">"cmt">//Print(arr_profits[i]); // 用于调试
    }
class=class="str">"cmt">//--- 线性回归构造
  CLinReg        linear_regression;
  CLinearModel   linear_model;
  CLRReport      linear_report;
  class="type">int retcode;
  linear_regression.LRBuild(xy,arr_size,class="num">1,retcode,linear_model,linear_report);
  if(retcode!=class="num">1)
    {
      Print("线性回归失败, 错误代码=",retcode);
      class="kw">return(class="num">0.0);
    }
  class="type">int nvars;
  class="type">class="kw">double coefficients[];
  linear_regression.LRUnpack(linear_model,coefficients,nvars);
  class="type">class="kw">double coeff_a=coefficients[class="num">0];
  class="type">class="kw">double coeff_b=coefficients[class="num">1];
  PrintFormat("y = %.1f x + %.1f",coeff_a,coeff_b);
class=class="str">"cmt">//--- 参数计算
  class="type">class="kw">double TrendProfit=((class="type">class="kw">double)arr_size*coeff_a+coeff_b)-(class="num">1.0*coeff_a+coeff_b);  class=class="str">"cmt">// 回归线在 "Y" 轴上的投影 
  TrendProfit/=(class="type">class="kw">double)arr_size;                                                                class=class="str">"cmt">// 除以交易数量
  class="type">class="kw">double TrendMSE=linear_report.m_rmserror;                                                    class=class="str">"cmt">// 训练集合上的均方根误差
  class="type">class="kw">double ProfitStability=TrendProfit/TrendMSE;
class=class="str">"cmt">//--- 常规化交易量
  if(GetVolumeNormalization())
    ProfitStability/=total_volume;
class=class="str">"cmt">//--- 我们乘以交易数量 - 我们对传递少量成交不感兴趣
  ProfitStability*=arr_size;
class=class="str">"cmt">//---
  class="kw">return(ProfitStability*class="num">10000.0);

把余额回归模块塞进EA的四个落点

验证交易量常规化到底有没有用,得先让EA能按账户状态动态算开仓手数。MQL5向导里选「智能交易系统(生成)」,在资金管理页挂一个模块就行:固定保证金交易按可用保证金百分比算手数,固定风险交易按余额百分比算,优化的成交量交易则依据历史成交反推。本文取了「固定风险交易」做样本,EA存为 EA test balance regression.mq5,后续就以它演示怎么挂 CBalanceRegression。 类文件必须丢进 [数据文件夹]\MQL5\Include\Balance regression 目录,否则编译阶段就报找不到文件。接入分四步:包含头文件并声明 m_balance_regression 对象;输入参数里加一个启用/禁用常规化的布尔开关;OnInit 里给对象喂初始余额和起算时间;文件末尾补 OnTester,把 GetProfitStability 的自定义优化值回传给测试器。 标准 MACD 采样和移动平均线两个官方样例(路径分别在 Experts\Examples\MACD 与 Experts\Examples\Moving Average)也照同样四步改,另存为 MACD Sample balance regression.mq5 和 Moving Average balance regression.mq5 即可。外汇与贵金属杠杆高,动态手数放大盈利的同时也会等比例放大回撤,接入后务必先用策略测试器跑历史段确认风险敞口。 下面这段是从改完的EA里抽出的关键声明与初始化,高亮行就是余额回归模块的接入点:

MQL5 / C++
class=class="str">"cmt">//--- 可用的资金管理
class="macro">#include <Expert\Money\MoneyFixedRisk.mqh>
class=class="str">"cmt">//--- 可用的余额回归模块
class="macro">#include <Balance regression\BalanceRegression.mqh>
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 输入                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 智能系统的全局对象                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
CExpert ExtExpert;
CBalanceRegression m_balance_regression;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 智能系统的初始化函数                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">input class="type">class="kw">double      Money_FixRisk_Percent=class="num">10.0;            class=class="str">"cmt">// 风险百分比
class=class="str">"cmt">//--- 余额常规化的输入
class="kw">input class="type">bool        InpVolumeNormalization=true;           class=class="str">"cmt">// 交易量常规化
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 智能系统的全局对象                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 智能系统的初始化函数                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- 设置 CBalanceRegression 的参数
   m_balance_regression.SetStartBalance(AccountInfoDouble(ACCOUNT_BALANCE));
   m_balance_regression.SetFromDate(TimeCurrent());

◍ 把余额回归模块接进EA初始化

在 MT5 里做带资金曲线监控的 EA,光有信号逻辑不够,得在初始化阶段把余额回归对象(CBalanceRegression)喂好参数,否则回测里的稳定性指标会是空转。 上面这段散落的片段里,核心就三行:用 AccountInfoDouble(ACCOUNT_BALANCE) 取当前余额当起点,SetFromDate(TimeCurrent()) 标记统计起点时间,SetVolumeNormalization 按输入开关决定是否对成交量做常规化。InpVolumeNormalization 默认 true,意味着不同手数会被折算到统一量纲再算回归。 OnTester 里只调了 GetProfitStability(TimeCurrent()) 并把返回值交给优化器——也就是说你跑遗传优化时,筛选目标其实能换成「利润稳定性」而非单纯净值。外汇与贵金属杠杆高,这种稳定性偏好可能降低极端回撤概率,但不承诺免亏。 注意原文把 #include <Balance regression\BalanceRegression.mqh> 和对象声明写了两遍,实盘编译会重定义报错;正确做法只留一处,且必须放在 OnInit 或类 Init 之前。

MQL5 / C++
m_balance_regression.SetVolumeNormalization(InpVolumeNormalization);
class=class="str">"cmt">//--- 智能系统初始化
class="type">class="kw">double OnTester()
  {
class=class="str">"cmt">//---
   class="type">class="kw">double ret=m_balance_regression.GetProfitStability(TimeCurrent());
class=class="str">"cmt">//---
   class="kw">return(ret);
  }
class="macro">#include <Trade\AccountInfo.mqh>
class=class="str">"cmt">//--- 可用的余额回归模块
class="macro">#include <Balance regression\BalanceRegression.mqh>
CBalanceRegression m_balance_regression;
class=class="str">"cmt">//---
class="kw">input class="type">class="kw">double InpLots =class="num">0.1; class=class="str">"cmt">// Lots
class="macro">#include <Trade\Trade.mqh>
class=class="str">"cmt">//--- 可用的余额回归模块
class="macro">#include <Balance regression\BalanceRegression.mqh>
CBalanceRegression m_balance_regression;
class="kw">input class="type">class="kw">double MaximumRisk = class="num">0.02;   class=class="str">"cmt">// 最大风险百分比
class="kw">input class="type">int   InpMATrendPeriod =class="num">26;  class=class="str">"cmt">// MA 趋势周期
class=class="str">"cmt">//--- 余额常规化的输入
class="kw">input class="type">bool  InpVolumeNormalization=true; class=class="str">"cmt">// 成交量常规化
class=class="str">"cmt">//---
class="type">int ExtTimeOut=class="num">10; class=class="str">"cmt">// 交易操作之间的超时秒数
class="kw">input class="type">int   MovingShift = class="num">6;       class=class="str">"cmt">// 移动均线偏移
class=class="str">"cmt">//--- 余额常规化的输入
class="kw">input class="type">bool  InpVolumeNormalization=true;  class=class="str">"cmt">// 成交量常规化
class=class="str">"cmt">//---
class="type">int   ExtHandle=class="num">0;
class="type">bool CSampleExpert::Init(class="type">void)
  {
class=class="str">"cmt">//--- 设置 CBalanceRegression 的参数
   m_balance_regression.SetStartBalance(AccountInfoDouble(ACCOUNT_BALANCE));
   m_balance_regression.SetFromDate(TimeCurrent());
   m_balance_regression.SetVolumeNormalization(InpVolumeNormalization);
class=class="str">"cmt">//--- 初始化公用信息
class="type">int OnInit(class="type">void)
  {
class=class="str">"cmt">//--- 设置 CBalanceRegression 的参数
   m_balance_regression.SetStartBalance(AccountInfoDouble(ACCOUNT_BALANCE));
   m_balance_regression.SetFromDate(TimeCurrent());
   m_balance_regression.SetVolumeNormalization(InpVolumeNormalization);

「用 OnTester 把稳定性塞进优化目标」

MT5 策略测试器的自定义优化条件,靠 OnTester 返回值驱动。想让遗传优化去挑「盈利曲线更稳」的参数组,就得在这里算一个数丢回去。 上面这段把稳定性计算收敛成一行:取当前时间下的盈利稳定度,直接 return。测试器每跑完一组参数,就会拿这个 double 值当适应度打分,数值越高代表该组参数在样本内的权益回撤与波动越克制。 外汇与贵金属品种点差跳空频繁,这种稳定性指标只能反映历史样本,实盘换周期可能失效,属于高概率参考而非保证。把 GetProfitStability 接进 OnTester,你开 MT5 选「自定义最大化」就能让引擎自动筛平一点的曲线。

MQL5 / C++
class=class="str">"cmt">//| 测试器函数                                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">class="kw">double OnTester()
  {
class=class="str">"cmt">//---
   class="type">class="kw">double ret=m_balance_regression.GetProfitStability(TimeCurrent());
class=class="str">"cmt">//---
   class="kw">return(ret);
  }

EURUSD H4 上回归优化的三次对照

在 EURUSD H4 周期、未启用前瞻测试的条件下,用三个 EA 验证了「回归方法优化余额图」的思路。其中 EA test balance regression 与 Moving Average 版本在交易过程中动态计算持仓量,MACD Sample 版本则用固定手数,三者都跑了同一组三测试对照。 第一组对照以 EA test balance regression 为例:测试1用「余额 + 最大锋锐比率」做优化标准,在家庭双机(i3 四核用3代理、 Phenom II X6 用4代理)跑完花了 21分43秒;同样的任务丢到 MQL5 Cloud Europe 1 只用了 1分9秒,成本 $0.06。云端前五佳的净值回撤都是 60.48%、交易次数 98,开仓阈值从15到30、平仓阈值95~100 区间结果高度重合。 测试2把优化目标换成「自定义最大」且交易量常规化=false,前五佳净值回撤降到 41.18%、交易 119 次,自定义列值 40085.45 明显压住了回撤。测试3把交易量常规化=true 后在云端花 2分27秒($0.08),选出的最佳项与测试1完全一致——这至少说明回归优化方法没有把原标准下的好参数弄丢。 Moving Average 版的结论更直白:测试1云端 1分22秒($0.03)最佳通关 #19 的净值回撤仅 4.29%;测试2用「自定义最大」+false 直接从缓存拿到结果(成本 $0.00),最佳通关 #141 的自定义值冲到 102076.24,回撤仍只有 3.93%;测试3 交易量=true 花 1分13秒($0.05),选出的前排与测试2相同,没有因常规化而偏移。 MACD Sample 固定手数版在 H4 上交易稀薄(13~16笔),测试1云端 3分55秒($0.04)最佳 #163 的锋锐比率奇高 74.02、回撤 0.91%,但样本过少,结论外推要谨慎。外汇与贵金属杠杆品种本身高风险,上述回测均为历史数据,实盘表现可能偏离。 把三组放一起看,用「自定义最大」做余额图回归优化,在 MA 与基础版上都不差于甚至优于「余额+锋锐」标准;交易量常规化开关在 H4 上基本不改变选优结果。想确认这套逻辑是否过拟合,自己开 MT5 把交易量常规化从 false 切到 true 重跑一遍最直观。

把回归诊断交给小布
这些余额图回归与方差诊断,小布盯盘的AIGC已内置,打开对应品种页即可看到曲线平滑度评分,你只需比对不同参数组的稳定性差异。

常见问题

平方形式对远端离群点惩罚更重,能更敏感地暴露曲线毛刺,便于量化平滑度,但也要注意极端异常值会扭曲回归。
ALGLIB封装了数值稳定的矩阵分解,样本量大时比手写的简易递推更不易溢出,回测中调用开销可接受。
它把趋势线提取、方差计算和向导生成EA的对接封装好了,避免每换一个采样EA就重写回归逻辑。
可以,品种页的AIGC诊断模块会列出回测的回归平滑度与最大锋锐比率,方便跨参数横向比,但外汇贵金属仍属高风险,数值仅作概率参考。
没有固定比例,取决于样本拆分方式;前瞻开启后曲线往往更顺,但那是透题式拟合,实盘迁移能力可能倾向走弱。