蒙特卡洛方法在交易策略优化中的应用(基础篇)
📘

蒙特卡洛方法在交易策略优化中的应用(基础篇)

第 1/2 篇

用蒙特卡洛给 EA 利润挑刺

蒙特卡洛方法在 MT5 里不是用来算命,而是把历史行情打乱重排,看策略利润在随机扰动下还能不能站得住。2018 年 8 月那篇实测贴里,作者用概率性 EA 模型跑了几百次重采样,样本量约 5577 次观测,核心结论指向一个事实:历史回测好看,不代表随机散射下不崩。 具体做法分三层:利润鲁棒性对随机散射,看单次交易顺序打乱后净值曲线衰减;利润鲁棒性对回撤,统计最大回撤分布而非只看一个点;利润分布鲁棒性对价格不稳定性,用不同波动率缩放喂给同一套逻辑。外汇和贵金属杠杆高,这类测试暴露的是爆仓概率,不是收益承诺。 想在 MT5 验证,直接开策略测试器的自定义模式,把「使用随机延迟」和「重采样」勾上,跑 200 次以上取分位数。比对着一条曲线自我感动有用得多。

◍ 蒙特卡洛建模到底在算什么

蒙特卡洛方法本质上是一种用随机模拟给「带概率性质的对象」建模的思路。它把研究对象抽象成一套算法,在程序里用伪随机数发生器去逼近理论要求的随机分布,从而批量造出大量样本。 这种建模之所以好用,是因为算法结构简单:先按分布随机生成很多研究对象状态,对每个样本算一遍所需特征,就能从「只有一组真实特征」扩展出整个分布函数。样本越多,分布越有参考价值,但外汇和贵金属市场的高风险意味着:分布再漂亮也不代表下一笔就按它走。 最早的教学例子是布丰投针——往格子上随机丢针来估 Pi,后来轮盘赌这类天然随机数发生器也印证了「随机即概率」的来源。值得注意,蒙特卡洛不仅能处理随机对象,也能拿去近似普通积分,因为任意积分都能看成某个随机变量的数学期望。 落到交易上,我们要研究 EA 稳定性,就得先给 EA 工作过程配一个概率模型;模型越简单越容易算,但简化总会丢精度,这点心里要有数。

「把历史交易当成随机变量来建模」

开 MT5 跑 EA 前,大家习惯在报价历史上测和调。但过去那串成交结果,凭什么能预示将来?我们并不指望未来每笔单子顺序都一样,只是假定它们有“统计相似性”。用概率论把这直觉钉死:每笔交易结果视作某随机变量的实现,相似性由分布函数 F(x) 的接近程度衡量。过去数据的作用,就是帮我们反推未来利润分布的类型与可能区间。外汇与贵金属杠杆高,这种推断只代表概率倾向,不等于实盘必然重现。 最常用也最简的模型,是把每笔相对利润定义为 k = C1/C0(C0 建仓前资产、C1 平仓后资产),绝对利润就是两者差。假定所有交易利润相互独立、同分布,任务变成用历史样本还原 F(x)——标准的数理统计问题,答案永远是近似的。 一种做法是直接取经验分布函数,用离散近似:亏损按平均亏、获利按平均利,各占一个值。另一种用核密度估计做连续密度近似。实战里交易者不在乎函数长相,关心的是给定风险水平下可能落袋多少、利润能否持续,这些都能从分布里算出来。 具体建模用 bootstrap:历史利润序列 k1…kn,随机有放回抽 n 次生成新序列,跑几万条资金曲线(下图不同颜色仅示意,实际量级数万)。每条算终值利润,整体统计出可能结果分布。N 取 n 即可,N 再大经验分布近似精度反而掉。MT5 目前没有内建“随机 EA”常规接口,这套得自己写脚本跑。

把三种稳定性准则塞进 EA 的优化回路

构建优化标准的核心目的,是给 EA 一个可量化的目标函数,而不只是盯着总利润。未来利润不可知,但能用当前样本偏离利润的可能程度去评估——偏差越小,系统的利润越稳定,实盘中外汇与贵金属的高波动风险才更可控。 我们探讨三条路线:蒙特卡洛大样本下,取平均利润与离散程度之比,类似夏普比率,方差小代表不确定性低;把回撤约束进来,超过允许回撤就终止交易,该准则只算平均利润;用前后子样本同质性统计检验衡量利润持久性,思路接近前瞻测试。 演示用 MetaTrader 5 自带的 Moving Average.mq5,仅做小改:开头包含头文件,结尾用 OnTester 返回优化值。所有计算集中在 mcarlo.mqh 的 optpr(),它按 noptpr 参数分发到六个子函数(如 mean_sd、frw_wmw)。 回测设定统一为 EURUSD 2017 年春夏季、1 小时周期、1 分钟 OHLC 模式、遗传算法自定义优化。这不是为实盘做准备,而是验证理论——你打开 MT5 把头文件丢进 MQL5/Include/ 就能复跑。 下面这段是 optpr 与 setks 的骨架:optpr 先做参数与样本量守卫,再按 noptpr 分支;setks 从交易历史提取每笔盈亏相对资金曲线的增长系数存入 k[]。注意 k[nk-1]=1.0+hdprofit_full/capital 把利润归一化为复利因子,这是后续蒙特卡洛重采样的输入。

MQL5 / C++
class="macro">#include <mcarlo.mqh>
class="type">class="kw">double OnTester()
  {
   class="kw">return optpr();        class=class="str">"cmt">// 优化参数
  }
class="type">class="kw">double optpr()
  {
   if(noptpr<class="num">1||noptpr>NOPTPRMAX) class="kw">return class="num">0.0;
   class="type">class="kw">double k[];
   if(!setks(k)) class="kw">return class="num">0.0;
   if(ArraySize(k)<NDEALSMIN) class="kw">return class="num">0.0;
   MathSrand(GetTickCount());
   class="kw">switch(noptpr)
     {
      case class="num">1: class="kw">return mean_sd(k);
      case class="num">2: class="kw">return med_intq(k);
      case class="num">3: class="kw">return rmnd_abs(k);
      case class="num">4: class="kw">return rmnd_rel(k);
      case class="num">5: class="kw">return frw_wmw(k);
      case class="num">6: class="kw">return frw_wmw_prf(k);
     }
   class="kw">return class="num">0.0;
  }
class="type">bool setks(class="type">class="kw">double &k[])
  {
   if(!HistorySelect(class="num">0,TimeCurrent())) class="kw">return class="kw">false;
   class="type">uint nhd=HistoryDealsTotal();
   class="type">int nk=class="num">0;
   class="type">class="kw">ulong hdticket;
   class="type">class="kw">double capital=TesterStatistics(STAT_INITIAL_DEPOSIT);
   class="type">long hdtype;
   class="type">class="kw">double hdcommission,hdswap,hdprofit,hdprofit_full;
   for(class="type">uint n=class="num">0;n<nhd;++n)
     {
      hdticket=HistoryDealGetTicket(n);
      if(hdticket==class="num">0) class="kw">continue;
      if(!HistoryDealGetInteger(hdticket,DEAL_TYPE,hdtype)) class="kw">return class="kw">false;
      if(hdtype!=DEAL_TYPE_BUY && hdtype!=DEAL_TYPE_SELL) class="kw">continue;
      hdcommission=HistoryDealGetDouble(hdticket,DEAL_COMMISSION);
      hdswap=HistoryDealGetDouble(hdticket,DEAL_SWAP);
      hdprofit=HistoryDealGetDouble(hdticket,DEAL_PROFIT);
      if(hdcommission==class="num">0.0 && hdswap==class="num">0.0 && hdprofit==class="num">0.0) class="kw">continue;
      ++nk;
      ArrayResize(k,nk,NADD);
      hdprofit_full=hdcommission+hdswap+hdprofit;
      k[nk-class="num">1]=class="num">1.0+hdprofit_full/capital;
      capital+=hdprofit_full;
     }

常见问题

把历史交易顺序随机打乱重跑几百次,看净值曲线盈利占比。若多数重排后仍盈利,说明策略本身有韧性;若一打乱就亏,利润大概率来自某段特定行情。
把每笔历史交易当作随机变量,反复抽样重组交易序列,估算未来收益分布和回撤边界。核心输出是盈利概率、最大回撤可能区间,而非固定收益。
可以。小布能读取你的交易记录,自动做顺序重排与分布诊断,直接标出策略在随机扰动下的失效概率和脆弱区间。
别用单一品种极端行情段做样本,否则随机分布会失真。建议用至少一年多品种数据,且保留手续费与滑点设定。
在每轮参数组合跑完后,仅对达标者做50次轻量重排校验,过滤掉脆弱参数。这样比全量蒙特卡洛省90%以上计算量。