Files
quanxiel/alpha/动量策略.ipynb
T

8.6 KiB

动量策略 — 构建与回测

策略思路

动量效应(Momentum Effect)指过去一段时间表现良好的股票,未来一段时间内往往继续表现良好。 本策略使用 20 日收益率 作为动量因子,每月调仓,买入过去表现最好的一个分位组股票。

研究流程

  1. 加载数据
  2. 动量因子计算与 IC 分析
  3. 分层回测验证因子有效性
  4. 构建策略并回测
  5. 绩效评估
In [ ]:
import sys
sys.path.insert(0, '..')

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from alpha.config import AlphaConfig
from alpha.factors import FactorAnalyzer
from alpha.strategy import Strategy, QuantileSignal, EqualWeightAllocator
from alpha.backtest import BacktestEngine
from alpha.evaluation import PerformanceEvaluator, ReportGenerator
from alpha.data_loader import DataLoader

%matplotlib inline
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

print('模块导入成功')

1. 加载数据

In [ ]:
# 回测区间
START_DATE = '2020-01-01'
END_DATE   = '2024-12-31'

loader = DataLoader()

# 后复权收盘价
price_data = loader.load_prices(START_DATE, END_DATE)
print(f'价格数据: {price_data.shape}, 日期: {price_data.index[0]} ~ {price_data.index[-1]}')
print(f'股票数量: {price_data.shape[1]}')
price_data.head()

2. 动量因子计算与 IC 分析

In [ ]:
# 动量因子: 20日收益率(过去20个交易日涨跌幅)
MOM_WINDOW = 20
momentum = price_data.pct_change(MOM_WINDOW)

# 未来 5 日收益率(验证短期预测能力)
fwd_5d = price_data.pct_change(5).shift(-5)

# 堆叠为长表 (MultiIndex: date, stock)
factor_stacked = momentum.stack()
fwd_stacked = fwd_5d.stack()

analyzer = FactorAnalyzer(factor_stacked, fwd_stacked)
ic = analyzer.compute_ic(method='rank')

print('动量因子 IC 汇总:')
for k, v in analyzer.ic_summary().items():
    print(f'  {k}: {v:.4f}')

# IC 曲线
ic.plot(figsize=(12, 3), title='动量因子 Rank IC 时间序列')
plt.axhline(y=0, color='r', linestyle='--')
plt.show()

3. 分层回测

In [ ]:
# 分 5 组,观察单调性
quantile_ret = analyzer.quantile_returns(n_quantiles=5)
print('各分位组平均收益 (Q1=最低动量, Q5=最高动量):')
print(quantile_ret)

# 绘制分组收益条形图
quantile_ret['avg_return'].plot(kind='bar', figsize=(8, 4), color='steelblue')
plt.title('动量因子分层收益 (未来5日)')
plt.ylabel('平均收益率')
plt.grid(True, alpha=0.3)
plt.show()

4. 构建策略并回测

In [ ]:
# 回测配置
config = AlphaConfig(
    initial_cash=1_000_000,
    commission_rate=0.0003,
    slippage=0.001,
    stamp_tax=0.001,
)

# 动量策略:Top 20% (Q5) 长期持有,等权
strategy = Strategy(
    name='动量策略-20日Top20%',
    factors=[],
    signal_generator=QuantileSignal(
        n_quantiles=5, long_quantile=5, short_quantile=0,  # 只做多
    ),
    weight_allocator=EqualWeightAllocator(max_positions=10),
    description='每月持有过去20日涨幅最高的20%股票,等权配置'
)

# 回测引擎
engine = BacktestEngine(config)
equity_curve = engine.run(
    strategy=strategy,
    price_data=price_data,
    factor_data={'momentum': momentum},
    rebalance_freq='M',  # 月频调仓
)

print(f'回测完成, 共 {len(equity_curve)} 个交易日')
print(f'累计收益率: {(equity_curve["nav"].iloc[-1] - 1) * 100:.2f}%')

5. 净值曲线与回撤

In [ ]:
fig, axes = plt.subplots(2, 1, figsize=(14, 8))

# 净值曲线
axes[0].plot(equity_curve.index, equity_curve['nav'], label='策略净值', color='steelblue')
axes[0].axhline(y=1.0, color='gray', linestyle='--')
axes[0].set_title('动量策略净值曲线')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# 回撤曲线
nav = equity_curve['nav']
running_max = nav.cummax()
drawdown = (nav - running_max) / running_max
axes[1].fill_between(equity_curve.index, 0, drawdown.values, color='red', alpha=0.3)
axes[1].set_title('回撤曲线')
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

6. 绩效评估

In [ ]:
# 基准:股票池等权组合
bench_ret = price_data.pct_change().mean(axis=1)

evaluator = PerformanceEvaluator(equity_curve, benchmark_returns=bench_ret, risk_free_rate=0.03)
print(evaluator.summary())

# 导出报告
report_df = evaluator.full_report()
pd.DataFrame(list(report_df.items()), columns=['指标', '数值']).set_index('指标')

7. 交易记录分析

In [ ]:
trades = engine.portfolio.trades
if trades:
    trade_df = pd.DataFrame([
        {'日期': t.date, '股票': t.stock, '方向': t.side,
         '数量': t.quantity, '价格': t.price, '佣金': t.commission}
        for t in trades
    ])
    print(f'总交易数: {len(trade_df)}')
    print(f'买入: {(trade_df["方向"]=="buy").sum()}, 卖出: {(trade_df["方向"]=="sell").sum()}')
    display(trade_df.head(20))
else:
    print('无交易记录(可能数据量不足)')

参数敏感性分析

动量窗口是影响策略表现的关键参数,可尝试不同窗口:

In [ ]:
# 不同动量窗口对比
for win in [10, 20, 40, 60]:
    mom = price_data.pct_change(win)
    fwd = price_data.pct_change(5).shift(-5)
    a = FactorAnalyzer(mom.stack(), fwd.stack())
    summary = a.ic_summary()
    print(f'窗口 {win}日: IC均值={summary["IC_Mean"]:.4f}, ICIR={summary["IR"]:.4f}, '
          f'IC>0占比={summary["IC>0_Ratio"]:.2%}')