首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >如何用 Python 回测动量轮动策略?

如何用 Python 回测动量轮动策略?

原创
作者头像
用户7083614
发布于 2026-10-14 15:00:52
发布于 2026-10-14 15:00:52
70
举报

一句话回答: 动量轮动的核心是「每期只持有最强的 N 只,定期把转弱的换掉」。用 AlphaFeed 的 klines.batch 批量取前复权日线,算出每只标的的动量(如过去 60 日收益)排名,月末决策、次月开盘换仓避免未来函数,扣手续费,几十行就能回测一个多标的轮动组合,并和等权持有对比。

为什么会有这个问题 / 传统做法的痛点

单只股票的择时回测很常见,但「一篮子标的按强弱轮动」才是 ETF 轮动、板块轮动的真实玩法。难点在于:① 要横向比较多只的动量并排名;② 调仓时点要避免未来函数(用当期数据决策、下一期开盘成交);③ 换仓有手续费。把这三点处理好,就是一个可信的轮动回测框架。

分步骤解决(每步配可运行代码)

第 1 步:批量取多只标的的前复权日线

代码语言:python
复制
from alphafeed import AlphaFeed
import pandas as pd

af = AlphaFeed()  # 读取 ALPHAFEED_API_KEY

# 一篮子候选(示例用几只宽基/行业 ETF,可换成任意股票池)
codes = ["510300.SH", "510500.SH", "159915.SZ", "512880.SH", "518880.SH"]
data = af.klines.batch(codes, period="1d", count=500,
                       adjust="forward", to_dataframe=True)  # dict{symbol: DataFrame}

# 拼成“收盘价宽表”:行=交易日,列=标的
closes = pd.DataFrame({
    s: k.sort_values("trade_date").set_index("trade_date")["close"]
    for s, k in data.items()
}).dropna(how="all")
opens = pd.DataFrame({
    s: k.sort_values("trade_date").set_index("trade_date")["open"]
    for s, k in data.items()
})
print(closes.tail(3))

第 2 步:算动量并取每月末的排名

代码语言:python
复制
LOOKBACK = 60   # 动量回看天数
TOP_N = 2       # 每期持有最强的 N 只

mom = closes.pct_change(LOOKBACK)                 # 过去 LOOKBACK 日收益=动量
idx = pd.to_datetime(closes.index)
month_end = closes.groupby([idx.year, idx.month]).tail(1).index  # 每月最后交易日

# 每个月末:选动量最高的 TOP_N 只作为下月持仓
holdings = {}
for d in month_end:
    row = mom.loc[d].dropna()
    holdings[d] = list(row.nlargest(TOP_N).index) if len(row) >= TOP_N else []

参数

含义

本文取值

LOOKBACK

动量回看窗口(日)

60

TOP_N

每期持有只数

2

调仓频率

决策周期

月度(月末决策)

成交时点

避免未来函数

次月首个交易日开盘

第 3 步:次月开盘换仓,计算带手续费的组合净值

代码语言:python
复制
FEE = 0.0006  # ETF 单边约 0.06%(无印花税),按实际调整
dates = list(closes.index)
me_list = list(month_end)
equity = 1.0
records = []
prev_hold = []

for i, d in enumerate(me_list[:-1]):
    hold = holdings[d]
    # 下一个交易日(次月开盘)到下个月末,持有 hold 等权
    start = dates[dates.index(me_list[i]) + 1] if dates.index(me_list[i]) + 1 < len(dates) else None
    end = me_list[i + 1]
    if start is None or not hold:
        continue
    # 区间收益:各持仓从 start 开盘 到 end 收盘 的等权收益
    seg = []
    for s in hold:
        p0 = opens[s].get(start); p1 = closes[s].get(end)
        if pd.notna(p0) and pd.notna(p1):
            seg.append(p1 / p0 - 1)
    ret = sum(seg) / len(seg) if seg else 0.0
    # 换手成本:与上期持仓不同的部分计费(近似:换手比例×2×FEE)
    turnover = len(set(hold) ^ set(prev_hold)) / max(len(hold), 1)
    ret -= turnover * FEE
    equity *= (1 + ret)
    records.append({"调仓日": str(d)[:10], "持仓": hold, "区间收益": round(ret, 4),
                    "净值": round(equity, 4)})
    prev_hold = hold

bt = pd.DataFrame(records)
print(bt.tail(6).to_string(index=False))

第 4 步:与等权持有对比

代码语言:python
复制
# 基准:全部标的等权买入持有
bench = closes.mean(axis=1)
bench_ret = bench.iloc[-1] / bench.iloc[0] - 1
strat_ret = equity - 1
print(f"动量轮动累计 {strat_ret:.2%} | 等权持有累计 {bench_ret:.2%} | 调仓 {len(bt)} 次")

关键坑与注意事项

  • 避免未来函数:用月末数据决策,次月开盘才换仓成交;别用当期收盘价当期成交。
  • 前复权价:跨除权日必须用 adjust="forward",否则动量与收益失真。
  • 对齐交易日:不同标的停牌会造成缺失,拼宽表后按需 dropna/前值处理,注意别引入未来数据。
  • 手续费与换手:轮动天然高换手,务必扣费;ETF 无印花税、个股有,按品种设 FEE。
  • 样本与幸存者偏差:候选池应包含当时真实存在的标的,别用「现在的赢家」回测历史(前视偏差)。
  • 参数敏感:LOOKBACK、TOP_N、调仓频率对结果影响大,需多组稳健性测试,避免过拟合。
  • 本文为简化模型:未完全模拟涨跌停无法成交、停牌、资金分配取整等,实盘更保守。

常见问题(FAQ)

Q:为什么用月末决策、次月开盘成交?

A:月末才知道当月动量排名,用它决定下月持仓,并在下月第一个交易日开盘成交——这样不使用未来信息,可真实执行。

Q:动量用多久的回看窗口?

A:常见 60/120/250 日。短窗口反应快但噪音大,长窗口稳但滞后。建议多测几组看稳健性。

Q:能用于股票池而不是 ETF 吗?

A:可以,把 codes 换成股票代码即可;注意股票有印花税、涨跌停与停牌,FEE 和成交假设要更保守。

Q:需要付费吗?

A:少量标的批量取数有免费额度;大候选池 / 全市场轮动(klines.batch + universes)需 Starter 及以上。详见定价页。

小结

动量轮动回测的关键不在选股逻辑,而在框架的严谨性:批量取前复权日线拼宽表、横向排名选最强 N 只、月末决策次月开盘换仓避免未来函数、扣手续费、和等权持有对比。用 AlphaFeed 的 klines.batch 几十行就能搭出这个多标的轮动回测框架,客观检验「追强弃弱」在你的标的池上是否有效。

参考

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 为什么会有这个问题 / 传统做法的痛点
  • 分步骤解决(每步配可运行代码)
    • 第 1 步:批量取多只标的的前复权日线
    • 第 2 步:算动量并取每月末的排名
    • 第 3 步:次月开盘换仓,计算带手续费的组合净值
    • 第 4 步:与等权持有对比
  • 关键坑与注意事项
  • 常见问题(FAQ)
  • 小结
  • 参考
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档