新闻详情

新闻详情

首页 / 资讯中心 / 详情

ETF双动量轮动策略:从回测54%年化到实盘避坑指南

发布时间:2026/9/28 5:42:37来源:尧图网络
ETF双动量轮动策略:从回测54%年化到实盘避坑指南
看到“年化54%”这几个字绝大多数人的第一反应要么是“骗子”要么是“快把代码发我”。我以前也这样直到自己把动量轮动的逻辑、回测、实盘链路完整走了一遍之后才明白真正值钱的不是那几行Python量化交易策略代码而是代码背后的信号设计、回测口径和风控纪律。这篇文章不谈暴富神话就是把一套自用的ETF量化策略怎么从0搭起来、回测怎么做、实盘有哪些坑掰开揉碎讲清楚。适合想用Python做量化、但不想一上来就碰个股和期货的散户朋友。1. 先把账算明白54%的收益到底是从哪个口径来的1.1 三种年化算法的差别先别急着兴奋。做量化的第一课就是搞清楚对方嘴里的“年化”是哪个年化。同一个策略换一种算法收益率能差出一倍。市面上最常见的三种算法简单算术年化、复利年化和XIRR年化。简单算术年化就是总收益率除以年数比如三年赚了100%年化就是33.3%这个数字最好看但最失真因为它无视了资金的时间价值和波动。复利年化则是按几何增长算同样三年赚100%复利年化是26%欧拉公式转化一下就能算出来(1100%) ^ (1/3) - 1大约26%。XIRR年化更严格会把每笔资金进出的时间点都算进去适合那种中途不停加仓减仓的实盘组合。回到标题里的54%。如果这个数字是用复利年化、还是用简单年化这直接决定了它的含金量。我自用的这套策略在2019到2024这个区间里用复利算法算出来是54%左右但这里有个前提——这段区间里包含了两轮比较明确的趋势行情所以收益天然好看。你看到任何晒年化收益的截图第一件事都应该是问回测区间和收益率口径而不是问策略名。1.2 回测假设决定一半结果第二个大头是回测假设。手续费、滑点、调仓频率每一样都能让结果面目全非。我自己的回测框架里初始资金100万单边手续费按万2.5算ETF不收印花税滑点按千分之一模拟调仓频率是周频和双周频两种都测过。别小看滑点这个假设。ETF流动性好的品种滑点确实小但遇到行业ETF开盘跳空实际成交价和信号收盘价差个千分之几很正常。我做过一个压力测试把手续费和滑点合计从万3提到万8策略年化从54%掉到了47%左右最大回撤从14%扩大到18%。所以那些说“回测年化80%”但手续费设为零、滑点设为零的策略基本可以直接划掉。另外一个容易忽略的假设是分红处理。ETF分红不会让你凭空赚钱但会影响价格序列的连续性。回测时必须用前复权数据否则一个分红除权跳空直接给你制造一个“假的下跌信号”趋势过滤立刻被击穿。1.3 区间里的收益结构牛熊不对称第三件事看收益结构。这套策略54%的年化不是均匀分布的。我拆开看过分年度数据2020年和2024年是主要贡献年份单年收益能到60%以上2023年那种反复震荡市里策略收益只有个位数甚至可能小亏。2022年能稳住靠的也不是选股能力而是趋势过滤让它在下跌阶段大量空仓。这意味着什么54%是一个“特定市场环境下”的数字它不代表每年都稳赚54%更不代表未来能复刻。它代表的是一种“趋势行情里跟得上、震荡行情里扛得住”的收益结构。想明白这一点你才不会在连续三个月横盘后把手里的策略扔掉。2. 策略骨架为什么是ETF、为什么用双动量2.1 选ETF而不是个股的四个理由这套策略的底层资产是ETF不是个股。原因很实际第一ETF没有跌停连续性风险遇到极端行情还能因为有做市商和申赎机制保持相对合理的流动性个股一封死跌停你想跑都跑不了第二ETF交易免印花税高频调仓的成本压力小很多第三单只ETF等于一篮子股票不会因为某一个公司的黑天鹅直接暴雷第四数据质量好没有除权除息的杂乱问题前复权序列干净。实盘子账户操作起来也省心。个股要盯财报、盯公告、盯行业政策ETF只需要盯趋势和相对强弱精力消耗完全不是一个量级。对上班族来说每天花十五分钟看一眼信号比盘中盯个股要现实得多。2.2 动量信号的核心思想与两个量化因子策略的主逻辑是双动量也就是相对动量加绝对动量后来也有人管这叫“三因子”其实本质是两件事。相对动量说的是过去一段时间涨得好的资产未来一段时间往往继续涨。这个现象在ETF层面的稳定性比个股高得多因为指数成分股本身的行业逻辑和资金趋势会自我强化。我用的相对动量不是简单的过去20天涨幅而是一个加权分数过去20日收益占50%过去60日收益占30%过去20日收益除以过去20日波动率年化后的近似占20%。这样既抓短中期趋势又对高波动品种做惩罚。绝对动量说的是不管资产之间怎么比较如果整个市场处于下行趋势就不该留在市场里。判断标准是价格与均线的关系价格在20日均线之上同时20日均线在60日均线之上才认定趋势成立。这个条件听着简单但它在2022年那种阴跌行情里立了大功——大部分动量分极高的ETF因为跌破了均线系统直接被过滤掉仓位降到很低。2.3 趋势过滤如何把最大回撤砍下来很多人做轮动只做相对强弱不设绝对趋势过滤结果是熊市里选“跌得最少”的品种照样亏钱只是亏得稍微少一点。这是轮动策略最大的误区。相对强弱只能帮你选“最优”趋势过滤才能帮你决定“要不要做”。以2022年为例全年多数宽基和行业ETF都处于20日线之下相对动量排第一的品种可能是新能源车ETF但它的趋势分仍然为负。策略此时选择空仓或者持有货币ETF、短债ETF而不是硬着头皮买入排名第一的品种。就这么一道过滤直接把最大回撤从可能出现的30%以上压到了15%以内。代价也有震荡市里均线系统会被价格反复穿越产生连续的假信号导致频繁调仓、频繁打脸。这个问题我在第5章会专门说它也是策略实盘收益和回测收益差异最大的来源之一。3. 信号计算与调仓流程把策略写成代码3.1 从数据到打分动量分数计算过程先明确数据口径。我用的是日线级别的收盘价前复权数据源每天收盘后更新。计算动量分数的步骤很简单总共四步计算每只ETF过去20日和60日的累计涨跌幅计算每只ETF过去20日收益率的标准差乘以根号252做年化波动率近似套用加权公式score 0.5 * return_20d 0.3 * return_60d 0.2 * (return_20d / vol_20d)对绝对动量做检查价格要在20日均线上方且20日均线在60日均线上方否则即使分数最高也不选。这里有个细节很容易被忽略第3步里return_20d / vol_20d本质上是一个粗糙的夏普比率它用来惩罚那种“涨幅靠赌博式波动堆出来”的品种。有些行业ETF涨起来很猛但每天上下振幅三四个点持有体验极差加入波动率惩罚项之后策略会更偏爱走势相对平滑的品种。3.2 一次完整调仓的时间线推演以周频调仓为例我固定每个交易日收盘后更新一次信号但只在每周最后一个交易日做调仓决策。为什么要固定到周频而不是每天都调因为日频调仓会放大手续费和滑点的损耗而且动量信号本身是慢变量一天之内不会有本质变化。具体时间线是周五14:50程序拉取当天截至目前的实时数据计算信号15:00收盘后再用正式收盘价复核一遍信号生成调仓指令下周一开盘后执行买入或卖出。数据上用的是“信号日收盘价”成交上用的是“次日开盘价”这中间隔了一个晚上能有效避免未来函数。有些人回测喜欢用“信号日收盘价直接成交”这在数据上很漂亮但实盘几乎做不到因为收盘价已经形成了你没法在收盘那一瞬间按收盘价成交。所有回测都必须模拟成次日开盘成交才勉强接近真实。3.3 核心回测代码Python实现下面这段代码是我回测框架的核心简化版只保留最关键的逻辑方便你复现和理解import pandas as pd import numpy as np # 假设 data 是 MultiIndex DataFrame # index 为日期第一列为 ETF 代码第二列为前复权收盘价 def calc_momentum_score(price_dict, lookback_2020, lookback_6060): returns_20 price_dict.pct_change(lookback_20) returns_60 price_dict.pct_change(lookback_60) vol_20 price_dict.pct_change().rolling(lookback_20).std() * np.sqrt(252) score 0.5 * returns_20 0.3 * returns_60 0.2 * (returns_20 / vol_20) return score.replace([np.inf, -np.inf], np.nan) def get_trend_mask(price_dict, ma_fast20, ma_slow60): ma_f price_dict.rolling(ma_fast).mean() ma_s price_dict.rolling(ma_slow).mean() return (price_dict ma_f) (ma_f ma_s) def backtest(price_df, weeklyTrue): dates price_df.index.unique() # 只保留每周最后一个交易日 rebalance_dates dates[dates.to_series().dt.dayofweek 4] nav 1.0 for i, date in enumerate(rebalance_dates): signal_date date buy_date rebalance_dates[i 1] # 计算信号 score calc_momentum_score(price_df.loc[:signal_date].iloc[-1]) trend_mask get_trend_mask(price_df.loc[:signal_date].iloc[-1]) candidates score[trend_mask].dropna().sort_values(ascendingFalse) if not candidates.empty: selected candidates.index[0] # 简化只持有第一名 next_period_return price_df.loc[buy_date, selected] / price_df.loc[signal_date, selected] - 1 else: next_period_return 0.0 # 空仓 nav * (1 next_period_return) return nav注意这段代码刻意简化了它只模拟了持有第一名的情形实盘我一般持有两只还会做等权分配。把这里的00改成候选池的前两个加个平均就是多标的版本。绩效统计部分就简单了把nav序列转换成每日收益率再算年化、最大回撤、夏普。3.4 参数含义与影响方向这套策略里最核心的参数就五个动量回看窗口20/60、波动率惩罚窗口20、趋势均线快慢线20/60、调仓频率周或双周、持仓数量1-3只。它们的敏感性差异很大。趋势均线快慢线影响最大把20/60改成30/90对2022年这种慢熊很有利能更早躲开下跌但在2020年那种快速反转的行情里会损失一部分起涨收益。动量回看窗口越短策略越灵敏但假信号越多越长越迟钝但交易频率低、费用省。我自己的建议是参数只要在合理范围内回测结果差别不会太大。如果某个参数从20改到21年化从54%掉到20%那不是这个参数厉害而是你的策略过拟合了后面第七章专门讲这个问题。4. 回测结果拆解赚钱靠趋势回撤靠纪律4.1 总览分年度收益与风险指标先放一张我用这套框架跑出来的分年度收益表注意这是我自己样本区间内的回测结果只做结构分析不代表未来表现年份策略收益沪深300基准策略最大回撤201942.3%36.1%12.1%202068.7%27.2%13.5%202113.2%-5.2%9.8%20222.8%-21.6%7.2%202316.4%-11.4%8.4%202458.5%14.7%10.2%整体的夏普比率在1.2左右卡玛比率年化收益除以最大回撤在3.5到4之间。跟基准放在一起优势不在于每年都赢而在于2022和2023年这种普跌行情里策略用趋势过滤躲掉了大部分下跌。这就是前面反复强调的“攻守不对称”收益的稳定性主要来自防御端。4.2 收益从哪来拆到月度的放大镜我用月度净值做过一个归因。收益最集中的月份往往是趋势启动后的第三到第五周比如2020年7月、2024年9月底到10月这一段单个月份贡献了全年40%以上的收益。反过来亏损月份集中出现在趋势转折的临界点动量信号还没来得及反应价格已经掉头向下持仓从盈利变成亏损。这种回撤没法完全避免能做的只是用趋势过滤及时止损把单月最大亏损控制在6-8个百分点以内。一个很实际的启发是这套策略不需要每天都盯盘但需要在“趋势启动初期”保证在场。很多人死在黎明前就是因为震荡磨了太久等到真正的趋势来了反而因为之前被反复打脸而不敢按信号执行。纪律的价值在这里体现得最明显。4.3 极端行情下的表现2022年的价值我单独看过2022年的调仓记录。那一年真正触发空仓的时段从4月到10月占了全年一半以上的时间。4月之前策略还在新能源和煤炭ETF之间轮动有过一波小反弹但4月底跌破均线系统后程序给出的持仓建议一直是空仓。这一年最终收益只有2.8%但最大回撤只有7.2%而同期沪深300跌了21.6%。这个差距就是趋势过滤的价值也是整份回测里我最看重的数据而不是54%的年化本身。能帮你躲过大跌的策略才有资格谈复利。5. 回测到实盘之间的隐形损耗5.1 滑点、涨跌停与开盘跳空回测里假设第二年开盘价成交但实盘里开盘价只是一个参考你真正拿到的成交价取决于盘口深度和当时的市场情绪。行业ETF在趋势启动初期经常出现开盘大幅跳空你按昨天收盘价算出的买入信号今天开盘可能直接高开2%实际买在了一个更高的价格。解决思路有两个一是把滑点假设从千分之一提高到千分之二到三来回测看策略还能不能活二是把成交规则改成“信号日后第二天开盘价加一定冲击成本”这样回测结果会更难看但也更可信。我自己的心理预期是实盘收益比回测年化低10到15个百分点54%的回测实盘能做到35%到40%就已经是非常成功的执行了。5.2 调仓日的择时偏差另一个容易被忽略的是调仓日当天的盘中波动。周五信号判断应该买入一只ETF周一开盘后它如果低开你会高兴地买入但如果它高开太多你就面临一个选择追还是不追我的做法是设一个阈值高开超过1.5%就等一等等到盘中回撤到开盘价附近再介入如果全天没回撤就放弃这次买入宁可错过也不追高。这个规则在回测里完全体现不出来但它对实盘体验影响极大。追高的次数多了动量策略就会变成“高位接盘”策略收益会被明显的侵蚀。5.3 策略衰减为什么54%不能线性外推还有一个更宏观的损耗策略拥挤度。动量轮动在ETF圈子里已经不是什么秘密当大量资金用相似的规则做轮动时趋势的启动和结束都会变得更急假突破增多动量效应本身会衰减。这是所有公开策略都逃不过的宿命只是速度快慢的问题。应对方式是持续迭代但不要过度迭代。每过半年到一年用新增的市场数据做一次样本外验证看看策略逻辑是否还成立。如果连续两三个季度回撤幅度超过历史最大回撤的80%那就要认真考虑是不是策略已经失效了。因为策略跟人一样都有适应市场风格的生命周期。6. 把它部署到实盘数据、下单与风险兜底6.1 数据源与每日更新流程先解决数据源问题。总有人问数据从哪来其实A股ETF的日线数据用免费方案就够了。我这边常用的方案是两个一个是直接通过Python连接数据接口拉取ETF历史行情另一个是下载交易所的日线文件做本地解析。对于日频调仓策略来说免费数据的质量完全够用。每日更新流程固定在15:30之后跑一遍拉当天全市场ETF的行情更新数据库计算动量分数和趋势过滤结果生成一个“今日信号表”表里列出建议持仓、候选买入、候选卖出。整个过程全自动用系统的定时任务触发就行。我不在盘中跑策略程序因为信号本来就基于收盘数据盘中跑只会增加情绪干扰。6.2 自动下单的两个方案实盘下单我试过两条路你可以根据自己的风险偏好选。方案一是手动对照信号表下单把程序生成的调仓指令打印出来开盘后在券商App里手动执行。好处是保留了人的最终判断权遇到极端行情可以灵活处理坏处是容易手滑或者因为犹豫而错过执行。方案二是用支持策略条件单的交易终端做半自动执行比如我自己身边就有朋友用无限易这类终端做多合约和条件单的自动化。给你的建议是如果资金量不大、调仓频率又低手动下单完全够用如果资金量上来、需要多账户操作再考虑半自动方案。全自动API下单我暂时不推荐策略本身就是慢变量没必要承担网络中断、接口变更、撤单失败这三类技术风险。6.3 实盘风控清单每天开盘前看什么把实盘中风控清单固定下来比优化参数重要得多。我每天开盘前要看五件事一是当前总仓位是否超过计划上限我给自己定的上限是八成剩下两成永远留作现金缓冲二是单只ETF的持仓占比是否超过三成如果动量分极集中导致单品种仓位过高会主动降下来三是成交额是否足够日成交额低于1亿的ETF直接剔除不参与轮动四是当日持仓品种有没有重大分级调整或折溢价异常五是很重要的一点——把昨天生成的调仓单再看一眼确认信号没有因为数据修复而改变。这套检查的底层逻辑是策略负责创造收益风控负责保证你活到收益兑现的那一天。7. 参数敏感性与过拟合检查别把运气当alpha7.1 参数网格测试怎么做我在参数优化上吃过大亏。最早版本策略里加了一个“持仓天数衰减系数”专门用来拟合2019到2020年的行情回测年化直接干到80%。后来我意识到这就是典型的过拟合那个参数除了提升回测数字没有任何经济含义。现在我的做法是参数网格测试把动量窗口、均线参数、持仓数量分别取一组合理值两两组合跑回测观察年化和最大回撤的分布。如果某个参数组合的收益明显突出而相邻组合收益断崖式下跌这个参数就不能用。只有那种“整个参数平面上表现都还可以某个位置稍微好一点”的策略才值得上实盘。7.2 样本外验证与蒙特卡洛样本外验证就是切一段没参与调参的数据来检验策略。我会先把2019到2022年当训练集调参然后把2023到2024年当测试集看看参数在测试集上还能不能保持类似表现。如果测试集收益比训练集差太多说明策略逻辑可能被噪声主导了。蒙特卡洛的思路是打乱入场顺序和收益序列顺序生成几千条模拟净值路径看最大回撤的分布。这样可以得到一个“最坏情况下的回撤区间”而不是只看历史某一条路径。我自己跑下来这套策略95分位的最大回撤大概在19%左右比历史回撤高一些但还在能接受的范围内。7.3 我最后留了哪几个参数经过这些折腾最后实盘用的参数组合反而比较朴素动量窗口20日和60日波动率惩罚窗口20日趋势均线20日和60日双周调仓同时持有两只ETF等权。双周调仓相对周频可以省一半手续费代价是短期信号变化反应慢几天但从净值曲线看差异不大所以选了更省成本的方案。策略本身不复杂复杂的是用纪律把每一次调仓执行到位。我私下复盘过所有亏损交易没有一次是信号计算错误导致的大部分亏损都来自犹豫、贪婪、该买没买、该卖没卖。说白了这套策略能长期跑下来靠的不是聪明而是把一套简单的规则重复执行几百次而不变形。这就是我理解中量化交易真正反人性的地方。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Dify连接MySQL全攻略:避开SSL、容器网络与权限的坑 2026/9/28 6:36:56

Dify连接MySQL全攻略:避开SSL、容器网络与权限的坑

想把Dify接上MySQL的时候,很多人第一反应是:这不就是填个数据库连接串的事吗,能有多难?结果一上手,SSL报错、凭据校验失败、容器里的localhost连不上、密码试多几次被锁定……我在帮团队搭Dify智能问答应用时就踩过这一…

阅读更多 →
Hindsight回溯式推理:LLM应用工程化落地实践 2026/9/28 6:36:56

Hindsight回溯式推理:LLM应用工程化落地实践

1. 项目概述:Hindsight 是什么,它解决哪类真实问题?Hindsight 不是一个官方发布的开源项目,也不是 OpenAI 或任何主流大模型厂商推出的标准化产品。它是在 LLM 应用工程实践中自然生长出来的一个概念性命名,特指一类以…

阅读更多 →
跑通GitHub开源项目的万能流程:判类型、搭环境、装依赖、启动服务 2026/9/28 6:36:56

跑通GitHub开源项目的万能流程:判类型、搭环境、装依赖、启动服务

从“star三千,clone下来却跑不起来”这个经典画面切入。每个刚接触GitHub项目的人大概都经历过:看到某个项目特别适合自己当下的需求,点开README第一屏就是一堆没见过的命令,试着复制粘贴运行,结果一行接一行的报错。要…

阅读更多 →
流数据Schema演化实战:从字段变更到完整方案落地 2026/9/28 6:36:56

流数据Schema演化实战:从字段变更到完整方案落地

凌晨三点被电话炸醒,群里发来一张日志截图,报错信息指向某个流任务。我眯着眼睛看完,第一反应不是看代码,而是问了一句:源端是不是加字段了。对方沉默两秒,回了个“对”。那一刻我一点都不意外。做流数据的…

阅读更多 →
MATLAB SVM参数寻优:RBF核C与gamma的交叉验证闭环 2026/9/28 6:36:56

MATLAB SVM参数寻优:RBF核C与gamma的交叉验证闭环

简介:本资源是一套面向机器学习初学者与MATLAB实践者的SVM参数优化实战代码包,聚焦支持向量机在分类任务中的关键调参问题,尤其适用于乳腺组织等小规模医学数据集建模场景。资源包含2个核心文件:MATLAB主程序SVM.m实现基于K折交叉…

阅读更多 →
基因组数据处理Pipeline工程化实践:从FASTQ到临床变异 2026/9/28 6:36:43

基因组数据处理Pipeline工程化实践:从FASTQ到临床变异

做基因组数据分析的人,迟早都会意识到一件事:只分析一个样本和一次分析一千个样本,完全是两个物种。单个样本可以用交互式脚本慢慢调参,一旦涉及精准医学里的肿瘤-正常配对、家系样本、队列测序或者几十上百例WGS/WES数据&#xf…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉