ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

双均线策略从回测到实战:30分钟跑通你的第一个量化交易模型

2026/9/16 6:18:52 拓冰建站 浏览量
双均线策略从回测到实战:30分钟跑通你的第一个量化交易模型 不管你是学金融的还是学计算机的只要聊起量化交易第一个被提到的策略大概率就是双均线。它的思路简单到我第一次看的时候有点怀疑就这可偏偏是这么个“就这”能让我在一个小时内把完整回测、绩效指标一条龙跑出来也让我第一次真正理解什么叫趋势跟踪、什么叫回撤、什么叫参数过拟合。这篇文章就把我踩过的坑和完整流程都摊开说照着做30分钟足够跑通你的第一个策略。1. 双均线策略为什么值得作为你的第一个策略1.1 双均线到底在交易什么逻辑双均线策略的核心逻辑一句话就能讲清楚当短期均线从下往上穿过长期均线时买入当短期均线从上往下跌破长期均线时卖出。前者叫“金叉”后者叫“死叉”。快线代表近期持仓成本慢线代表长期持仓成本两者交叉意味着短期内市场买方的力量对比发生了方向性变化。但别因为它简单就小看它。双均线本质上是趋势跟踪策略的元祖它背后博弈的其实是市场惯性。价格一旦形成趋势在情绪和资金的推动下短期均线和长期均线的位置关系会保持一段时间你顺着这个关系站队吃到的就是趋势延续的利润。它不预测顶和底只回答“现在该不该在这个方向上待着”这个问题。我第一次实盘跑这个策略的时候最吃惊的一点是策略的盈利来源并不是每次都做对而是做错的时候亏得少做对的时候拿得住。均线本身的过滤作用会把一些杂乱的短期波动过滤掉让你别天天手痒乱动。这一点放到今天的A股、期货、加密货币市场也一样成立。1.2 选参数的本质是在权衡趋势和震荡双均线的参数组合五花八门最常见的像5日对20日、10日对30日、20日对60日。参数并不是随便拍的它直接决定了策略对市场节奏的敏感度。快慢线参数拉得越近比如5日和10日信号越灵敏稍微有一点上涨苗头就能进场但问题是震荡行情里你会被反复打脸。价格一反弹金叉进场一回落死叉止损来回几次手续费和止损的亏损就能吃掉一大块本金。反过来参数拉得越远比如20日和60日信号越迟钝但一旦出现大趋势你拿到的仓位质量更高不会因为中途的洗盘被震下车。所以选参数这件事本质上是在做趋势和震荡之间的权衡。你要先想清楚自己要在什么样的市场环境里赚什么钱。如果你交易的标的一年到头都在窄幅箱体里晃那短周期双均线很可能是给券商打工如果标的长期有清晰的牛熊波动慢一点的参数反而更稳。1.3 双均线最大的短板我必须把丑话说在前面双均线策略最大的短板是它在震荡市里几乎必然连续亏损。趋势策略的宿命就是如此用震荡期的小额亏损换取趋势期的超额收益整体实现正期望。很多人第一次跑回测看到某个年份收益曲线一路向下就开始怀疑策略有问题其实不是策略坏了而是那一整年市场都在横盘。你要接受一个事实双均线不是全天候策略它只在有趋势的市场里赚钱。理解了这个你才能客观看待回测结果而不是一看到连续亏损就急着调参数。这也是我强烈建议新手把双均线作为第一个策略的原因——它足够简单简单到你能够彻底理解每一笔盈亏的来源而不是把结果归因给运气。2. 动手前5分钟环境、数据与参数2.1 30分钟内跑完的最小环境我默认你电脑上已经装了Python 3这个条件基本是量化交易的最低门槛了。如果你连Python都没装那可以直接装一个Anaconda装完自带Jupyter Notebook和pandas省心很多。需要安装的库就三个pandas、numpy还有用于数据获取的akshare。后两个库如果版本冲突建议先升级一下pip再安装pip install pandas numpy akshare -U这步正常情况下两三分钟搞定。数据获取我用的是akshare它对国内行情支持很友好不需要注册token直接调接口就能拿到A股、指数、ETF的日线数据。如果你在香港或海外交易也可以换成yfinance或者自己准备CSV文件逻辑完全一样。参数我先给一个默认组合快线5日、慢线20日。这个组合在日线级别上属于中短周期一年到头交易次数不至于太多趋势响应速度也还算及时适合拿来当基线跑通全流程。标的选择我建议用宽基指数或者流动性好的大票别一上来就选一只日均成交额几千万的小票后边你会被跳空和停牌问题折磨疯。2.2 数据获取与清洗的三个关键细节数据是整个回测的地基地基歪了后面全白搭。我分享三个会被新手忽略、却直接影响结果的细节。第一个是复权。股票分红送股都会导致价格出现“断层”如果直接拿不复权的收盘价算均线金叉死叉的位置会被这些除权缺口干扰出现大量虚假信号。我个人的习惯是统一用前复权数据它能保证历史价格连续贴近真实的可交易价格。第二个是数据排序。很多接口返回的数据是按日期倒序的你直接 rolling 算均线等于把未来数据当成过去数据用了出来的信号完全颠倒。所以拿到数据第一件事就是按日期升序排序然后重置索引这点代码里必须做好。第三个是缺失值和停牌。停牌期间的成交量为0价格可能维持在前一日的收盘价这种假数据会让均线计算出现横线进而导致信号失真。最简单的做法是把成交量为0或者收盘价为空的行直接删除回测时默认这些天无法交易。import pandas as pd import numpy as np import akshare as ak # 拉取沪深300指数日线数据这里以510300 ETF为例前复权 df ak.fund_etf_hist_em(symbol510300, perioddaily, start_date20180101, end_date20241231, adjustqfq) # 统一列名并清洗 df.rename(columns{日期: date, 开盘: open, 收盘: close, 最高: high, 最低: low, 成交量: volume}, inplaceTrue) df[date] pd.to_datetime(df[date]) df df.sort_values(date).reset_index(dropTrue) df df[df[volume] 0].reset_index(dropTrue)这段代码里最关键的是adjustqfq参数它直接帮你完成了前复权处理。数据清洗之后你可以顺手打印一下头尾几行确认时间范围和价格连续性没问题再往下走。3. 核心实现信号生成、回测与绩效3.1 用 pandas 向量化生成双均线信号终于到了写策略代码的环节。双均线策略用pandas实现非常干净核心就四步算均线、定信号、映射持仓、计算收益。# 计算快慢均线 df[ma_fast] df[close].rolling(window5).mean() df[ma_slow] df[close].rolling(window20).mean() # 生成原始信号快线在慢线上方1否则0 df[signal] np.where(df[ma_fast] df[ma_slow], 1, 0)这一段生成的signal是逐日的状态1代表该持有0代表该空仓。但这里有个关键点signal是用当日收盘价计算的而收盘价是收盘后才确定的真实交易中你不可能在收盘的那一瞬间以收盘价成交。所以信号必须往后推一天也就是第二天开盘再执行。这就是我能告诉你的最影响回测真实性的一个细节信号延迟。不少新手直接把signal当持仓直接算收益等于每个交易日收盘那一刻精准穿越到第二天开盘买入这在现实中根本做不到回测收益会被显著高估。# 持仓状态信号后移一天模拟次日开盘成交 df[position] df[signal].shift(1).fillna(0)完成这一步position里就记录了每一日实际应该持有的仓位状态。今天收盘信号变成1明天position才变成1也就是明天整个交易日都处于持仓状态后天拿到明天收益逻辑闭环。3.2 回测引擎搭建与仓位管理逻辑有了持仓状态回测就顺理成章了。策略的每日收益等于持仓状态乘以当日标的收益率注意收益要用百分比收益率而不是绝对价差否则净值曲线会失真。# 计算每日收益率 df[ret] df[close].pct_change().fillna(0) # 策略每日收益 持仓状态 * 当日标的收益 df[strategy_ret] df[position] * df[ret] # 计算策略净值曲线 df[nav] (1 df[strategy_ret]).cumprod()那么交易成本怎么加我见过很多人在这个环节偷懒直接忽略手续费和滑点。实际上手续费对双均线这种中低频策略的影响可能没那么致命但滑点和印花税会真实地吃掉利润尤其在你频繁开平仓的时候。A股的交易成本大致可以这样估算买入时佣金按万2.5卖出时佣金万2.5加上印花税千1另外还有少量过户费。我们简化处理按每次换手仓位从0变1或从1变0扣除0.15%的成本再把滑点按0.05%计入总共每笔换手扣0.2%。# 计算换手仓位每次变化就代表一次买入或卖出 df[turnover] df[position].diff().abs().fillna(0) # 单边成本滑点粗略按0.002计 cost 0.002 df[strategy_ret_net] df[position] * df[ret] - df[turnover] * cost # 扣费后的净值 df[nav_net] (1 df[strategy_ret_net]).cumprod()这个写法的含义是平时持仓的日子只承担市场波动不扣额外费用只有在仓位改变的那一天也就是真实买卖发生的那一天才扣除一笔交易成本。三个月不开仓就没有多余成本跟现实交易的情况是吻合的。3.3 绩效指标计算不能只看总收益率跑完回测之后很多人第一眼就去看总收益率发现策略涨了80%就打算直接上实盘。别急这个判断方式太粗糙了。真正衡量一个策略好坏的指标至少看四个年化收益率、最大回撤、夏普比率、胜率或盈亏比。最大回撤指的是净值从最高点跌到之后最低点的幅度它直接反映你实盘时可能要承受的心理压力。一个年化50%但最大回撤40%的策略实操中大部分人拿不住因为没等它创新高你就已经割肉了。夏普比率衡量的是每承受一单位风险能换来多少超额收益粗略算法就是策略年化收益率除以收益波动率。对散户来说夏普大于1就挺不错超过1.5已经算优秀。我写了个简化版的计算代码# 总收益率 total_return df[nav_net].iloc[-1] - 1 # 年化收益率按252个交易日计算 days len(df) annual_return (df[nav_net].iloc[-1]) ** (252 / days) - 1 # 最大回撤 drawdown df[nav_net] / df[nav_net].cummax() - 1 max_drawdown drawdown.min() # 夏普比率简化无风险利率假设为0 daily_ret df[strategy_ret_net].dropna() sharpe daily_ret.mean() / daily_ret.std() * (252 ** 0.5) # 交易次数和胜率 trade_count df[turnover].sum() profit_days daily_ret[daily_ret 0].sum() loss_days daily_ret[daily_ret 0].sum()打印这几个数字你对策略的“健康度”就有概念了。我特意强调这一点是想说没有回撤信息的收益率都是耍流氓。很多新手第一版策略看着收益亮眼一算最大回撤直接崩溃这时候反而应该庆幸因为花在回测上的时间帮你避免了真金白银的教训。4. 常见问题与排查技巧实录4.1 未来函数看起来赚钱的元凶双均线策略跑出来的收益如果异常高我第一个怀疑的就是未来函数。未来函数的意思是你的回测代码里偷偷用了未来才可能出现的信息导致“未卜先知”。这是回测中最高频、也最隐蔽的错误。最常见的错误写法是这样的用当天的收盘价计算均线交叉信号然后当天的收益也按当天的信号来算。等于你认为自己能在收盘那一刻完美买卖这根本不现实。我们前面用shift(1)做的信号延迟就是为了解决这个问题。另外还有一种更隐蔽的情况你在计算某一个指标时不小心用了整个数据集的信息比如用全样本的均值去做标准化这等于让模型偷看了未来。排查未来函数我有一个土办法把策略的每日收益曲线拎出来看看有没有某一天涨幅特别夸张比如单日超过10%甚至20%。正常来说日线级别的双均线策略每天能赚一个涨跌幅就已经很了不起了如果出现单日暴涨几乎可以断定是信号和成交使用了同一天的收盘价。把那一天找出来往前倒推基本就能定位到代码里的问题。4.2 复权与涨跌停数据细节决定策略命运如果说未来函数是策略逻辑的硬伤那么数据细节就是回测的软刀子。复权和涨跌停处理不好回测结果会跟实盘差出一大截。先说复权问题。如果你用不复权数据回测一只长期高分红的股票那么每次除权除息价格都会向下跳空均线会被莫名其妙地拉下来产生一个虚假的“死叉”策略可能就会因此卖出。用后复权数据会把历史价格调整得非常大收益率曲线倒是准确但显示价格时很别扭。前复权是最符合直观认知的选择它让当前价格保持真实历史价格向前调整虽然历史区间长的时候会有轻微失真但做日线级别的均线策略完全够用。再说涨跌停的问题。双均线的金叉往往会出现在股价已经有一定涨幅之后这时候如果标的直接一字涨停你根本买不进去同样死叉后如果连续跌停你也卖不掉。回测代码里如果不加约束系统会默认你每天都能按收盘价成交这等于在回测里偷偷加了杠杆。主流回测框架里可以加涨跌停过滤比如收盘价触及涨停且封死的情况下不执行买入信号触及跌停且封死的情况下不执行卖出信号。A股主板和ETF的涨跌幅限制不同回测脚本里要按标的类型区分。4.3 交易成本与滑点回测利润消失术我见过太多人在这一步翻车。同一个双均线策略忽略交易成本时年化收益可能做到20%加入0.2%的单边成本后直接变成12%如果再考虑实际滑点可能只剩8%。这就是为什么我强烈建议从一开始就在回测里把成本算进去。滑点这件事尤其容易被低估。你在回测里假设以收盘价买入但真实环境下当你看到均线交叉信号时市场里和你一样动作的人有很多冲击成本会推动成交价偏离你预期的价格。尤其在小盘股上滑点可能比佣金还贵。保守一点单边滑点按0.05%到0.1%估算如果你做的还是分钟级别的高频策略这个比例还得往上调。我想再提醒一点双均线策略的换手频率并不高按5日对20日的参数一年可能也就交易二十几次交易成本对整个收益的侵蚀不算特别大。但如果你的参数改成5日对10日交易次数翻倍成本的影响就会显著放大。这也是为什么参数选择会间接影响净收益因为交易成本对高频信号的惩罚是成倍增加的。4.4 参数敏感性过拟合悬崖跑通基线策略之后大部分人的第一反应是优化参数。把5日改成6日20日改成22日观察收益率是不是变得更好这种思路本身没问题但千万别滑入过拟合的陷阱。过拟合最典型的症状是某个参数组合的回测结果异常好但稍微改一个参数收益就断崖式下跌。比如20日对60日年化收益15%改成19日对60日就变成负的5%这说明你的“最佳参数”是在捕捉噪音而不是市场规律。真正有效的策略应该在参数附近的邻域内表现平滑比如20日到25日之间的慢线参数回测结果应该差异不大而不是呈锯齿状剧烈波动。排查过拟合我建议画出参数热力图。把快线参数从5变到30慢线参数从20变到60每个组合都跑一遍回测记录年化收益或夏普比率。如果地图上是一整片连续抬升或连续下降的区域那说明策略有稳定的逻辑基础如果只有某一个孤立的小格子特别亮其他全是黑暗那这个亮点大概率是噪声。这类排查虽然要花几分钟去循环跑回测但比起将来用真金白银在实盘里试错这点时间成本低得可以忽略。5. 跑通之后还能往哪里走先给你一个提醒回测效果不错不代表直接上实盘也能赚钱但你已经比绝大多数没跑过回测的人强了。接下来如果想让这套双均线策略真正可用我的建议是沿着三个方向去折腾。第一个方向是加过滤器。裸的双均线策略在震荡市会反复亏损既然知道它的痛点那就想办法避开震荡市。比如加一条更长周期的均线作为大趋势过滤器只在价格位于长周期均线上方时才允许做多这样能过滤掉一部分空头趋势里的假金叉。再比如加一个波动率过滤当市场波动太小的时候不交易因为低波动往往意味着趋势还没启动。第二个方向是改离场逻辑。双均线的交叉出场虽然简单但滞后性很强往往把利润回吐大半才发出离场信号。你可以试试用移动止损替代交叉出场比如从最高点回撤8%就离场这样在趋势反转早期就能锁定利润代价是可能会被短暂的洗盘震出去。每种离场方式都有取舍你把它们组合起来反复测试才能找到适合自己风险偏好的版本。第三个方向是换标的和周期。双均线策略在日线上的表现和在小周期上完全不同因为小周期上的噪音更多信号更难稳定。同一套参数在沪深300上有效不代表在全市场所有股票上都有效这就是所谓的“策略失效边界”。我在跑完基线之后会习惯性地把同一套代码往三五个不同标的上跑一遍看看策略的收益分布是不是靠运气。如果只有一只票赚钱其他全亏那这策略离实盘还很远如果在多个标的上都有稳定表现你对它的信心会完全不同。我个人做了这么多年回测的一个深刻体会是任何一个策略在实盘之前都只是假说。双均线的价值不光是让你赚到钱更是让你以最快速度理解量化交易中最重要的几个概念——信号、延迟、成本、回撤、过拟合。想清楚这些你再去看那些更复杂的策略会发现它们不过是把双均线的某个环节做得更精细而已。先把这条线跑通后面的大楼都有地基。