双均线策略回测大概是我见过被最多人当作“量化第一课”的项目。两条均线一短一长金叉做多、死叉离场逻辑简单到可以写在一张便签纸上。但真正从零动手把它做成一次完整回测——从拿到历史行情数据到生成交易信号再到扣除手续费和滑点之后算出净值曲线——你会发现它几乎覆盖了量化回测里的全部基本功数据清洗、复权处理、未来函数规避、成本建模、过拟合检验。这篇文章记录的就是我从头做“经典双均线策略回测”的完整过程每一步怎么设计、为什么这么设计、踩过哪些坑都会写清楚。如果你正准备上手第一个回测项目或者已经跑出一份漂亮的净值曲线却不知道结果到底靠不靠谱这篇应该能给你一份可以直接照抄的作业。1. 双均线策略拆解金叉死叉背后到底在验证什么逻辑1.1 金叉死叉的市场含义与策略代价先补一点基础认知。移动平均线就是把过去N日收盘价求平均它平滑掉了短期噪声反映的是一段时间内市场参与者的平均持仓成本。MA5是最近5天的平均成本MA20是最近20天的平均成本别看这两个数只是简单的算术平均它们之间的关系在交易者眼里代表了一种市场状态。当MA5从下方上穿MA20形成金叉说明短期买入者的平均成本在抬升近期资金进场的意愿比中期趋势更强通常被解读为趋势可能由跌转涨的启动信号。反过来MA5下穿MA20形成死叉说明短期成本重心在下移近期持有者倾向离场可能意味着趋势转弱。双均线策略做的事情就是把这套市场状态切换翻译成两条操作指令金叉了就持有死叉了就空仓。不过一定要清楚它的代价。均线天然是滞后指标金叉确认的时候价格往往已经离开底部一段距离死叉发生的时候顶部利润也已经回吐了一部分。换句话说双均线吃的是趋势中段那段肉指望它从头抄底逃顶是不现实的。明白了这一点回测中出现“刚金叉就回调”或者“刚离场就反弹”的窘境时就不会觉得是策略写错了这是策略本身必须承受的成本。1.2 均线参数组合的选型逻辑参数选择是双均线的灵魂。常见组合有MA5/MA20、MA10/MA30、MA20/MA60。周期越短金叉死叉越灵敏越能在早期捕捉反弹但也会在震荡行情里频繁触发被反复打脸周期越长信号次数越少但每次信号背后对应的趋势级别更大理论上“含金量”更高。我的基准组合选了MA5/MA20理由很实际日线级别上这个组合能兼顾信号数量和信号质量一年内能给出十几个可交易的信号足够支撑统计分析又不至于高频到被手续费拖垮。我同时还用MA10/MA60做了一组对照回测用来观察不同灵敏度的策略在净值曲线上呈现出怎样的差异。下面这个对比表我只放了规则层面的结论具体收益数字放在第四章参数组合信号频率适合的市场环境主要弱点MA5/MA20较高波动幅度较大的趋势行情震荡市反复止损MA10/MA60较低慢牛或大级别趋势信号少容易错过启动段MA20/MA60很低月线级别大周期中期回调几乎不回避这里有个新手容易忽略的细节不管参数怎么选信号判断都应该基于收盘价计算的均线。也就是说当日收盘后才能确认“今天是不是金叉”。如果盘中用实时价格触发信号同样的参数在回测里会与实盘产生巨大的执行偏差。更多细节我在第三章展开。总之选参数不要一上来就想“找最优”先找一组业务上说得通的数字把流程跑通后面再做参数扫描验证稳定性。2. 回测前的准备工作数据、工具和参数选型2.1 回测工具选型pandas手写还是专业框架做双均线这个量级的回测工具选择其实很自由。现在主流的路径有三条直接用带回测引擎的平台如聚宽、掘金这类在线量化平台、用现成的Python回测框架如backtrader、或者用pandas从零手写。我的建议是第一次做双均线回测老老实实用pandas手写最划算。原因很直接双均线的策略逻辑本身只有几行用框架反而要先学习那套框架的规则比如怎么组织Account/Order对象、怎么注册指标、怎么处理交易日历学习成本比策略本身还高。而手写的好处是每个环节都完全透明信号是在哪一天产生的、用哪个价格成交、手续费加在哪一步都是自己写出来的出了问题一眼就能定位。等你把流程跑熟了再迁移到专业框架反而能理解框架里每个配置项在干什么。2.2 数据获取与预处理复权和停牌怎么处理回测第一步是拿数据。A股日线级别的历史数据开源渠道有很多比如tushare、akshare、baostock。这次我用的是本地CSV格式的日线数据字段就是date、open、high、low、close、volume六列回测真正用到的主要是close和open。数据拿到手不能直接用至少做三件事。第一检查缺失值和重复值删除重复的交易日把close列的NaN和0揪出来否则均线会在这些位置出现断崖式跳变。第二检查日期排序。数据源可能默认按时间倒序输出必须先按date升序排好否则后面的rolling滚动计算全乱套。第三复权处理这是新手最容易翻车的地方。股票分红送股之后会除权除息价格会瞬间“跳空”下跌但这个跳空是假象是除权造成的数字游戏。如果不做复权均线就会在这个位置被强行拉出一个假的死叉策略会误判卖出。回测中我统一用前复权价格来做信号和净值计算。前复权剔除了历史除权跳空能保证信号的一致性。需要提醒的是不同数据源的前复权基准可能不同做多策略对比时务必确保所有标的使用同一个数据源、同一个复权规则。还有一个实操细节如果持仓股票当天停牌你挂的单根本成交不了。回测里我的处理是把成交量为0的交易日标记出来凡是卖出信号落在停牌日就顺延到下一个可交易日的开盘价成交否则遇到长期停牌的重仓股净值曲线会严重失真。3. 回测核心实现信号生成、成交价格与成本模拟3.1 信号生成与成交假设未来函数必须规避这是双均线回测里最牵扯精力的一环也是决定回测结果是否可信的核心。所谓未来函数就是你在回测中使用了当时无法获得的信息导致策略“开挂”回测收益率虚高。最常见的错误就是用当天收盘价算出金叉信号然后假设以当天收盘价买入了。问题在哪金叉信号是收盘之后才确认的收盘那一刻你根本来不及用收盘价成交。除非在收盘集合竞价阶段挂单并且严格假设成交在收盘竞价但这在日线级回测里会引入太多执行层面的假设。最稳妥的做法是T日收盘后确认信号T1日以开盘价成交。对应到代码就是把持仓信号整体向后平移一天import pandas as pd df[MA5] df[close].rolling(5).mean() df[MA20] df[close].rolling(20).mean() # 持仓信号1代表持仓0代表空仓 df[signal] 0 df.loc[df[MA5] df[MA20], signal] 1 # 关键次日生效避免用当日收盘价成交 df[position] df[signal].shift(1) df[ret] df[close].pct_change() df[strategy_ret] (df[position] * df[ret]).fillna(0) df[equity] (1 df[strategy_ret]).cumprod()上面这段代码里最核心的就是df[position] df[signal].shift(1)它保证策略在信号出现后的下一个交易日才持有头寸而不是“瞬移”进场。同时它也符合A股T1制度T日收盘确认信号T1日开盘买入最早也要等到再下一个交易日才可能触发新的卖出信号不存在当天买当天卖的问题。提醒一句市面不少平台默认用“信号当天收盘价成交”如果你拿自己的回测结果和别人对比先问清楚对方的成交假设是什么否则收益数字没有可比性。3.2 成本模型手续费、印花税和滑点的量化信号再准扣完成本可能变成亏钱机器。双均线在震荡市的换手率不低成本必须精打细算。我在回测中按A股实际交易成本做了如下设定成本项参数说明佣金万2.5双边收取按单笔金额足够大计算5元最低佣金的影响可以忽略印花税0.05%2023年8月减半后执行仅卖出时收取滑点0.05%每次成交额外让出0.05%模拟开盘价跳空的误差具体计算方式我直接在收益序列里修正开仓那天strategy_ret额外扣掉万2.5佣金加0.05%滑点平仓那天再扣掉万2.5佣金、0.05%滑点和0.05%印花税。这样每个信号对应的交易摩擦都被实实在在扣在当天的净值里而不是最后从总收益里一刀切掉。有个很多人没意识到的点回测里的成交价永远只是近似值。开盘价成交已经算比较保守但开盘瞬间同样有跳空所以额外加一笔滑点做安全垫是必要的。如果你把滑点从0.05%调到0.1%策略就从盈利变成亏损那只能说明这个策略很脆弱不值得实盘押注。我后来做敏感性测试时习惯把滑点参数单独拎出来跑一遍看看策略对交易摩擦的容忍度到底有多高。4. 回测结果解读从指标到参数敏感性的完整分析4.1 核心绩效指标收益、回撤、夏普、胜率怎么配合看回测跑完会得到一串收益率和一条净值曲线但“总收益率多少”远不足以描述一个策略。我固定计算一套指标每个指标都能从不同角度暴露策略的弱点。年化收益率回答“这笔钱一年能赚多少”按复利年化最大回撤回答“最惨的时候从高点亏了多少”这决定了你在实盘里能不能拿得住仓位夏普比率衡量风险调整后的收益简单理解就是每承担一单位风险换来多少超额收益A股日线策略能跑到1以上已经不错胜率和盈亏比则用来拆解盈亏来源。双均线的胜率通常不高可能不到40%但盈利单的平均利润要明显大于亏损单的平均亏损它赚钱靠的是“截断亏损、让利润奔跑”而不是靠高胜率。对应的指标计算我补一段核心代码import numpy as np total_return df[equity].iloc[-1] - 1 # 252是A股一年的交易日数量 annual_return (df[equity].iloc[-1]) ** (252 / len(df)) - 1 cum_max df[equity].cummax() max_drawdown (df[equity] / cum_max - 1).min() daily_vol df[strategy_ret].std() * np.sqrt(252) sharpe annual_return / daily_vol print(f总收益: {total_return:.2%}) print(f年化收益: {annual_return:.2%}) print(f最大回撤: {max_drawdown:.2%}) print(f夏普比率: {sharpe:.2f})我的MA5/MA20基准回测放在过去五年的沪深300成分股某只样本上年化收益大概在百分之十几最大回撤约25%夏普比率在0.7到0.9之间。听着不算惊艳但这是扣掉各项成本和滑点后实打实的结果。做回测不是看谁跑出来的数字惊悚而是看这个数字是怎么来的、能不能复现。4.2 参数敏感性分析与基准指数对比参数敏感性分析是检验双均线是不是在碰运气的最直接方式。做法是把快线周期从3日扫到15日、慢线从15日扫到60日形成一个参数网格每个组合跑一遍完整回测最后把年化收益或夏普比率画成热力图看走势。我实测下来MA5/MA20附近确实有一片相对稳健的区域但周围不是“别的参数全亏、唯我独赚”的极端状态而是一个渐进变化的平滑曲面。如果你跑出来的结果是在某个参数上突然冒出巨大尖峰比如MA7/MA23收益翻倍旁边MA6/MA23直接亏损那基本可以判定这个参数是过拟合的换个时间段就会失效。基准对比同样重要。沪深300指数是一个免费的参照系如果策略跑出来的收益还不如无脑持有沪深300那就没创造alpha价值这套系统没有存在的意义。我习惯把策略净值和基准净值画在同一张图里盯着超额收益的来源。双均线在单边行情里可能跑赢基准震荡行情里跑输都很正常关键是长期累计下来有没有稳定超越。同时任何带择时信号的策略都要额外关注熊市里的下行保护双均线的死叉在漫长熊市里能帮你离场这也是它最核心的价值所在。5. 双均线回测的坑与避坑经验5.1 回测陷阱排查从幸存者偏差到涨跌停约束第一是历史区间选择。如果只拿最近两年的单边上涨数据回测任何买入持有策略都会很好看双均线也一样单边行情里金叉频繁出现、死叉基本不出现收益自然接近满仓买入。真正检验策略的是混合行情样本我现在会把回测区间拉长到至少包含一轮完整牛熊比如2018到2023年让策略在下跌段有机会展示离场能力。第二是忽略涨跌停和流动性约束。回测模型默认想买就买、想卖就卖但A股跌停时你是卖不出去的。如果回测不考虑这个约束大跌之后死叉信号出现时正好挂单卖出成功净值就会比实盘漂亮。我在成交逻辑里加了一层过滤T1日开盘若跌停平仓信号顺延到下一个可交易日。这个细节对回测结果的影响比大多数人想象的要大。第三是成本后置。有些新手先生成一条完美净值曲线到结尾才把手续费从总收益里扣除这样总收益看起来扣了钱但过程中每一次信号的盈亏、最大回撤、换手率相关的风险指标全部失真。成本必须跟着交易走哪次开平仓就扣哪次的成本这一条建议直接照抄。我把排查过程中比较典型的问题整理成一个速查表各位回测出异常结果时可以对着自查问题现象可能原因排查方法回测年化高得离谱使用了未来数据检查成交价格是否是信号日当天股灾期间净值几乎不跌涨跌停约束未建模检查跌停日是否仍假设成交加成本后由盈转亏换手率过高统计年均交易次数尝试拉长均线周期不同时间区间结果差异巨大参数过拟合做样本外验证和参数热力图还有一个账很多人不算把交易频率统计出来双均线一年可能产生几十次交易单边佣金万2.5加上卖出印花税每笔往返成本大约千分之几几十次下来就能吃掉几个点到十几个点的收益。这个账不算清楚回测收益率虚高个5%都很常见。5.2 过拟合与样本外验证别让参数骗了你最后重点说过拟合。双均线只有两个参数看着简单实际上正因为参数少好调更容易被人反复优化到某个狭窄区间最终收获一条曲线拟合得很好的回测。我的铁律是优化只用训练集验证只用测试集。历史数据按时间切成两段前70%用于参数扫描和策略调整后30%完全冻结作为没碰过的样本外数据。参数一旦确定就绝不再为了样本外结果回头调整。实际操作中样本外表现差于样本内很正常关键是不能出现突然变脸训练集年化15%、样本外年化8%这种衰减可以接受训练集年化30%、样本外为负数那不是发现圣杯是过拟合了。这种断层现象在实盘里会直接导致灾难。我有一条主观但很好用的经验最终参数一定选在“参数高原”上而不是“参数尖峰”上。所谓高原就是周围一圈参数表现都不差的位置尖峰则是只有你最得意的那个点收益奇高、旁边全部拉胯。选高原上的参数代表策略逻辑是稳健的不是被某个特殊时间段喂养出来的巧合。做完这个双均线项目我最大的收获就是这条判断标准。最后再补一句个人体会。双均线这个级别的简单策略指望它实盘常年稳定盈利是不现实的但它作为回测练手项目简直完美——逻辑足够简单可以把全部精力放到回测本身的正确性上。我做完这个项目后把数据清洗、复权处理、未来函数规避、成本建模、参数敏感性检验这一整套流程全部用手过了一遍这些东西搬到任何更复杂的策略上都通用它们才是回测真正值钱的部分。如果你正准备做第一个回测项目我建议就从双均线开始把它做扎实后面你会感激自己这个决定。
