QuantStats + TA-Lib:量化策略分析入门实战的五阶段构建之旅
本文以
QuantStats
为核心分析工具,通过"数据获取→指标计算→策略构建→回测验证→实战应用"五阶段进阶路径,结合Tushare
金融数据接口与TA-Lib
技术分析库,深入解析双均线策略、多因子模型等经典量化模型的实现细节与优化方法。内容涵盖参数网格搜索
、Walk Forward分析
等回测方法,演示如何规避过拟合风险与策略陷阱。
文中内容仅限技术学习与代码实践参考,市场存在不确定性,技术分析需谨慎验证,不构成任何投资建议。适合量化新手建立系统认知,为策略开发打下基础。
阶段 1:环境配置与数据准备
1. 环境安装
目标:正确安装 QuantStats、TA-Lib、Tushare 及相关依赖库。
步骤说明
-
安装 Python 库:
pip install quantstats==0.0.62 pandas==2.0.2 numpy==1.26.4 tushare==1.4.19 # 基础库
‼️ 重要提示:请务必安装对应版本,避免浪费时间 ‼️
-
安装 TA-Lib:
-
Windows:从 TA-Lib 预编译库 下载对应版本的
.whl
文件,通过pip install 文件名.whl
安装。 -
Mac/Linux:使用
pip
安装:conda install -c conda-forge ta-lib
-
-
Tushare Token 注册:
-
访问 Tushare官网 注册账号
-
在个人中心获取
token
,替换以下代码中的your_token
:import tushare as ts ts.set_token('your_token') # 设置 token,仅需执行一次
-
2. 获取复权行情数据
目标:使用 pro_bar
接口获取复权后的股票数据(示例:贵州茅台 600519.SH
)。
代码示例
import tushare as ts
import pandas as pd
# 初始化 Tushare Pro
ts.set_token('your_token')
# 获取后复权日线数据(复权因子 adj='hfq')
df = ts.pro_bar(
ts_code='600519.SH', # 股票代码(贵州茅台)
adj='qfq', # 前复权
start_date='20180101', # 起始日期
end_date='20230101', # 结束日期
asset='E', # 资产类型:E 股票
factors=['tor'], # 换手率等因子(可选)
freq='D' # 日频数据
)
# 数据整理
df['trade_date'] = pd.to_datetime(df['trade_date'])
df = df.sort_values('trade_date').set_index('trade_date') # 按日期排序并设索引
df.index.name = 'date' # 重命名索引列
print(df[['open', 'high', 'low', 'close', 'vol']].head()) # 查看前5行
关键参数解释
adj='hfq'
:后复权(默认未复权),可选hfq
(后复权)、qfq
(前复权)asset='E'
:股票类型(E 表示 A 股)ts_code
:股票代码格式为代码.交易所后缀
,如上交所(.SH
)、深交所(.SZ
)
3. 数据预处理
目标:清洗数据,确保格式正确、无缺失。
步骤说明
-
处理缺失值:
df = df.dropna(subset=['close']) # 删除无收盘价的无效行
-
验证复权效果:
对比复权前后价格(示例):# 获取未复权数据对比 df_raw = ts.pro_bar(ts_code='600519.SH', adj=None, start_date='20220101', end_date='20221231') df_raw = ts.pro_bar( ts_code="600519.SH", adj=None, start_date="20180101", end_date="20221231" ) df_raw["trade_date"] = pd.to_datetime(df_raw["trade_date"]) df_raw = df_raw.sort_values("trade_date").set_index("trade_date") # 按日期排序并设索引 df_raw.index.name = "date" # 重命名索引列 print(df_raw[["close"]].head()) # 原始收盘价 print(df[["close"]].head()) # 复权后收盘价 ```
-
计算收益率(为阶段2准备):
returns = df['close'].pct_change().dropna() # 日收益率序列
4. 常见问题排查
-
问题1:TA-Lib 安装失败
解决:Windows 用户从 这里 下载预编译包,Mac 使用brew install ta-lib
。 -
问题2:Tushare 权限错误
解决:检查 token 是否有效,免费用户需确认接口权限(如pro_bar
需要至少 120积分)。 -
问题3:复权数据异常
解决:对比 Tushare 官网的复权价格,或使用ts.adj_factor
接口获取复权因子手动计算。
5. 阶段性任务
任务:获取 沪深300 (000300.SH
) 的 后复权 数据,并计算其 2023 年收益率。
代码提示:
df_hs300 = ts.pro_bar(
ts_code="000300.SH",
adj="hfq",
start_date="20230101",
end_date="20231231",
asset="I",
)
df_hs300["trade_date"] = pd.to_datetime(df_hs300["trade_date"])
df_hs300 = df_hs300.sort_values("trade_date").set_index(
"trade_date"
) # 按日期排序并设索引
df_hs300.index.name = "date" # 重命名索引列
returns_hs300 = df_hs300["close"].pct_change().dropna()
阶段 2:QuantStats 核心指标与 TA-Lib 集成
1. QuantStats 核心指标计算
目标:掌握 QuantStats 的回报率分析、风险指标与可视化方法。
步骤 1:基础回报率分析
使用 QuantStats 的 metrics
函数快速计算关键指标:
import quantstats as qs
# 假设 returns 是从阶段1获取的贵州茅台日收益率序列(pandas Series)
returns = df['close'].pct_change().dropna()
# 计算基础指标
metrics = qs.reports.metrics(returns, mode='basic')
输出:
Strategy
------------------ ----------
Start Period 2018-01-03
End Period 2022-12-30
Risk-Free Rate 0.0%
Time in Market 100.0%
Cumulative Return 163.99%
CAGR﹪ 14.37%
Sharpe 0.78
Prob. Sharpe Ratio 95.62%
Sortino 1.16
Sortino/√2 0.82
Omega 1.14
Max Drawdown -47.04%
Longest DD Days 681
Gain/Pain Ratio 0.14
Gain/Pain (1M) 0.69
Payoff Ratio 1.14
Profit Factor 1.14
Common Sense Ratio 1.32
CPC Index 0.65
Tail Ratio 1.15
Outlier Win Ratio 3.49
Outlier Loss Ratio 3.52
MTD 9.38%
3M -6.99%
6M -12.91%
YTD -13.76%
1Y -13.38%
3Y (ann.) 13.67%
5Y (ann.) 13.88%
10Y (ann.) 14.37%
All-time (ann.) 14.37%
Avg. Drawdown -5.2%
Avg. Drawdown Days 38
Recovery Factor 2.61
Ulcer Index 0.19
Serenity Index 0.38
关键指标解释:
- CAGR(年化复合增长率):策略的年化收益,衡量长期增长能力。
- Sharpe 比率:单位风险下的超额收益,>1 为良好。
- Max Drawdown(最大回撤):历史最大亏损幅度,越小越好。
步骤 2:自定义指标扩展
QuantStats 支持扩展更多风险指标(如 Sortino、Omega):
extended_html = qs.reports.metrics(returns, mode="full", display=False) # 完整模式
print(extended_html.loc[["Sortino", "Omega", "Calmar"]])
输出:
Strategy
Sortino 1.16
Omega 1.14
Calmar 0.31
2. TA-Lib 技术指标集成
目标:使用 TA-Lib 计算技术指标(RSI、MACD、移动平均线),并与 QuantStats 结合展示。
步骤 1:计算 RSI(相对强弱指数)
import talib
# 确保数据是 numpy 格式(TA-Lib 的要求)
close_prices = df["close"].values.astype("float64")
# 计算 RST(14天周期)
df["RSI"] = talib.RSI(close_prices, timeperiod=14)
# 可视化 RSI
df["RSI"].plot(title="RSI (14 Days)")
步骤 2:计算 MACD
# 计算 MACD 与信号线
df["MACD"], df["MACD_signal"], _ = talib.MACDEXT(
close_prices, fastperiod=12, slowperiod=26, signalperiod=9
)
# 绘制 MACD 柱状图
df[["MACD", "MACD_signal"]].plot(title="MACD (12-26-9)")
3. 结合 QuantStats 可视化
目标:将 TA-Lib 指标与 QuantStats 图表结合,生成专业分析。
示例 1:叠加 RSI 到 QuantStats 收益图
import matplotlib.pyplot as plt
# 创建 QuantStats 收益曲线
fig = qs.plots.returns(returns, figsize=(12, 6))
# 在同一图中添加 RSI(右轴)
ax2 = plt.twinx()
ax2.plot(df["RSI"], label="RSI", color="purple", alpha=0.3)
ax2.axhline(70, linestyle="--", color="red", alpha=0.3)
ax2.axhline(30, linestyle="--", color="green", alpha=0.3)
plt.legend()
示例 2:生成快照报告
qs.plots.snapshot(
returns, title="茅台股票分析", show=True, fontname="Arial Unicode MS"
) # 注意正确函数名为 'snapshot',增加支持中文显示 fontname="Arial Unicode MS"
示例 3:生成 HTML 报告(含 TA-Lib 指标)
# 生成完整 HTML 报告
report = qs.reports.html(
returns,
output="./reports/茅台分析.html",
title="QuantStats 分析",
# 添加自定义指标到报告
rsi=df["RSI"],
macd=df["MACD"],
)
4. 最佳实践与常见问题
-
数据对齐:确保 TA-Lib 计算的指标与收益序列索引对齐,避免未来数据泄漏。
# 错误示例:未使用 shift(1) 会导致未来函数 df["signal"] = np.where(df["RSI"] <30, 1, 0).shift(1)
-
参数优化:通过调整 TA-Lib 指标参数(如 RSI 周期),观察指标敏感性。
5. 阶段任务
任务:对 沪深300 (000300.SH
) 数据完成以下操作:
- 计算 20天 RSI 并添加到 DataFrame;
- 使用 QuantStats 生成包含 Sharpe、RSI 曲线 的 HTML 报告;
- (可选)当 RSI <30 时生成买入信号,计算策略收益。
代码提示:
# 使用 阶段1 中的数据集 returns_hs300
# 计算 RSI
close = df_hs300["close"].values.astype("float64")
df_hs300["RSI"] = talib.RSI(close, 20)
# 生成报告
qs.reports.html(
returns_hs300,
output="./reports/沪深300分析.html",
title="沪深300 分析",
rsi=df_hs300["RSI"],
)
阶段 3:高级可视化与自定义报表
目标
掌握 QuantStats 的高级图表定制方法,生成交互式 HTML 报告,并将 TA-Lib 技术指标无缝集成到分析流程中。
1. 高级可视化方法
1.1 绘制复合收益曲线
对比股票收益与基准(如沪深300)的累计收益走势:
import quantstats as qs
import matplotlib.pyplot as plt
# 获取基准收益率(示例:沪深300)
hs300 = ts.pro_bar(
ts_code="000300.SH",
adj="hfq",
start_date="20180101",
end_date="20230101",
asset="I",
)
hs300["trade_date"] = pd.to_datetime(hs300["trade_date"])
hs300 = hs300.sort_values("trade_date").set_index("trade_date") # 按日期排序并设索引
hs300.index.name = "date" # 重命名索引列
benchmark_rets = hs300["close"].pct_change().dropna()
# 绘制累计收益对比图
qs.plots.returns(
returns, # 目标收益率(茅台)
benchmark=benchmark_rets, # 基准收益率(沪深300)
match_volatility=True, # 波动率匹配
figsize=(12, 6),
)
关键参数:
benchmark
: 基准收益率序列match_volatility
: 是否调整基准波动率以匹配目标compound=True
: 是否计算复利收益(默认开启)
1.2 回撤曲线与压力区域
可视化历史最大回撤及持续周期:
qs.plots.drawdown(returns)
高级定制:
plt.fill_between(
qs.stats.to_drawdown_series(returns).index,
qs.stats.to_drawdown_series(returns).values * 100,
color="red",
alpha=0.3,
label="回撤区域",
)
plt.ylabel("回撤比例 (%)")
plt.legend()
1.3 月收益热力图
生成月度收益分布热力图,识别季节性规律:
qs.plots.monthly_heatmap(returns)
输出效果:
- 颜色越绿表示当月收益越高,越红表示亏损越大
2. 自定义 HTML 报告
生成完整分析报告,整合所有分析模块到单个 HTML 文件:
qs.reports.html(
returns,
benchmark=benchmark_rets,
output="./reports/茅台_量化分析报告.html",
title="茅台股票深度分析",
# 添加 TA-Lib 指标数据
rsi=df["RSI"],
macd=df["MACD"],
# 自定义报告模块
periods=5, # 显示最近5年的年化数据
)
报告模块说明:
- Returns:收益统计(日/月/年化)
- Risk Metrics:风险指标(Sharpe、最大回撤等)
- Rolling Metrics:滚动窗口指标(动态波动率)
- TA-Lib Indicators:自定义添加的技术指标图表
3. 最佳实践与常见问题
3.1 数据对齐验证
确保 TA-Lib 指标与收益率序列索引严格对齐:
# 验证数据长度一致性
assert len(returns) == len(df["RSI"].dropna()), "指标与收益序列长度不一致!"
# 解决未来函数陷阱
df["signal"] = np.where(df["RSI"].shift(1) < 30, 1, 0) # 使用 shift(1) 避免数据泄漏
3.2 参数敏感性分析
调整 TA-Lib 指标参数观察策略变化:
# 测试不同 RSI 周期
periods = [10, 14, 20]
for p in periods:
df[f"RSI_{p}"] = talib.RSI(df["close"], timeperiod=p)
df[f"RSI_{p}"].plot(title=f"RSI ({p} Days)")
4. 阶段任务
任务 1:生成 宁德时代(300750.SZ
) 的 HTML 报告,需包含:
- 双均线(5日、20日)与价格叠加图
- RSI 超买/超卖信号标注
- 对比创业板指数(
399006.SZ
)的累计收益
代码框架:
# 数据准备函数
def data_preparation(df):
df["trade_date"] = pd.to_datetime(df["trade_date"])
df = df.sort_values("trade_date").set_index("trade_date") # 按日期排序并设索引
df.index.name = "date" # 重命名索引列
return df
# 获取数据
df_nd = ts.pro_bar(ts_code="300750.SZ", adj="hfq", start_date="20200101")
df_cyb = ts.pro_bar(ts_code="399006.SZ", asset="I", start_date="20200101")
df_nd = data_preparation(df_nd)
df_cyb = data_preparation(df_cyb)
# 计算指标
df_nd["MA5"] = talib.MA(df_nd["close"], timeperiod=5)
df_nd["MA20"] = talib.MA(df_nd["close"], timeperiod=20)
df_nd["RSI"] = talib.RSI(df_nd["close"], 14)
# 生成报告
qs.reports.html(
df_nd["close"].pct_change().dropna(),
benchmark=df_cyb["close"].pct_change().dropna(),
rsi=df_nd["RSI"],
output="./reports/宁德时代分析.html",
)
输出验证:运行代码后检查是否生成交互式 HTML 报告,查看技术指标数值。
阶段 4:策略回测与最佳实践
目标
掌握基于技术指标构建量化策略的方法,实现完整的策略回测流程,并理解参数优化与过拟合预防的核心原则。
1. 策略构建基础
1.1 双均线策略实现
通过5日均线与20日均线的金叉/死叉生成交易信号:
import talib
import numpy as np
# 计算移动平均线
df["MA5"] = talib.MA(df["close"], timeperiod=5)
df["MA20"] = talib.MA(df["close"], timeperiod=20)
# 生成交易信号(1: 买入, -1: 卖出)
df["signal"] = 0
df["signal"] = np.where(df["MA5"] > df["MA20"], 1, -1)
df["signal"] = df["signal"].shift(1) # 避免未来函数
# 计算策略收益
df["strategy_returns"] = df["signal"] * df["close"].pct_change()
strategy_rets = df["strategy_returns"].dropna()
关键要点:
- 使用
shift(1)
确保信号基于历史数据生成 - 空头仓位用负收益表示(可修改为仅做多)
1.2 策略评估
使用QuantStats对比策略与基准表现:
benchmark_rets = df["close"].pct_change().dropna() # 基准为买入持有
qs.reports.html(
strategy_rets,
benchmark=benchmark_rets,
title="双均线策略回测",
output="./reports/双均线策略报告.html",
)
核心指标关注:
- Win Rate(胜率):盈利交易比例
- Profit Factor(盈亏比):总盈利/总亏损
- Tail Ratio(尾端比率):正收益与负收益的极端值比
2. 参数优化方法
2.1 网格搜索优化
测试不同均线周期组合:
from itertools import product
# 定义参数范围
fast_periods = [5, 10, 20]
slow_periods = [20, 30, 50]
# 存储最优结果
best_sharpe = -np.inf
best_params = {}
for fast, slow in product(fast_periods, slow_periods):
if fast >= slow:
continue
# 计算信号
df["MA_fast"] = talib.MA(df["close"], fast)
df["MA_slow"] = talib.MA(df["close"], slow)
df["signal"] = np.where(df["MA_fast"] > df["MA_slow"], 1, -1)
# 计算收益
rets = df["signal"].shift(1) * df["close"].pct_change().dropna()
# 评估指标
sharpe = qs.stats.sharpe(rets)
if sharpe > best_sharpe:
best_sharpe = sharpe
best_params = {"fast": fast, "slow": slow}
print(f"最优参数:{best_params}, Sharpe比率:{best_sharpe:.2f}")
2.2 Walk Forward 分析
防止过拟合的经典方法:
# 划分训练集/测试集
train_data = df.loc["2018-01-01":"2020-12-31"]
test_data = df.loc["2021-01-01":"2023-01-01"]
# 在训练集寻找最优参数
# (此处需封装参数优化逻辑为函数)
# 在测试集验证参数
best_fast = 5
best_slow = 20
test_data["signal"] = np.where(
talib.MA(test_data["close"], best_fast) > talib.MA(test_data["close"], best_slow),
1,
-1,
)
test_rets = test_data["signal"].shift(1) * test_data["close"].pct_change()
# 输出测试结果
qs.reports.metrics(test_rets, mode="basic")
3. 过拟合预防技巧
3.1 交叉验证
将数据分为多个时间段滚动测试:
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=3)
sharpe_list = []
for train_index, test_index in tscv.split(df):
train = df.iloc[train_index]
test = df.iloc[test_index]
# 在每折数据上重复参数优化
# ...
# 记录每次测试的Sharpe比率
sharpe_list.append(qs.stats.sharpe(test_rets))
print(f"Sharpe比率波动范围:{min(sharpe_list):.2f} - {max(sharpe_list):.2f}")
判断标准:
- 若各折Sharpe比率差异>1.5,可能存在过拟合
- 选择参数在多数时间段表现稳定的组合
3.2 蒙特卡洛随机化测试
对策略逻辑进行压力测试:
def random_signal_strategy(returns, prob=0.5):
"""生成随机交易信号"""
signals = np.random.choice([-1, 1], size=len(returns), p=[prob, 1 - prob])
return signals * returns
# 对比随机策略与真实策略
random_sharpe = qs.stats.sharpe(random_signal_strategy(benchmark_rets))
real_sharpe = qs.stats.sharpe(strategy_rets)
print(f"随机策略Sharpe: {random_sharpe:.2f} vs 真实策略Sharpe: {real_sharpe:.2f}")
结论判断:
- 若真实策略未显著优于随机策略,需重新检验策略逻辑
4. 多因子策略实践
4.1 动量+波动率复合策略
结合两个因子生成交易信号:
# 计算动量因子(20日收益率)
df["momentum"] = df["close"].pct_change(20)
# 计算波动率因子(20日标准差)
df["volatility"] = df["close"].pct_change().rolling(20).std()
# 生成复合信号
df["signal"] = np.where(
(df["momentum"] > 0.05) & (df["volatility"] < 0.02), 1, 0 # 动量筛选 # 低波动筛选
)
# 计算策略收益
strategy_rets = df["signal"].shift(1) * df["close"].pct_change()
4.2 因子权重优化
使用均值-方差模型优化因子组合:
from sklearn.covariance import LedoitWolf
# 获取多因子收益矩阵(示例:动量、波动率、RSI)
factors = pd.DataFrame(
{
"momentum": df["momentum"],
"volatility": -df["volatility"], # 波动率取负(低波动更好)
"rsi": (df["RSI"] - 50) / 50, # RSI归一化
}
)
# 计算因子协方差矩阵
cov = LedoitWolf().fit(factors).covariance_
# 计算最优权重(最大化夏普比率)
inv_cov = np.linalg.inv(cov)
ones = np.ones(len(cov))
weights = inv_cov.dot(ones) / ones.dot(inv_cov).dot(ones)
# 应用权重生成信号
df["composite_score"] = factors.dot(weights)
df["signal"] = np.where(df["composite_score"] > 0, 1, -1)
5. 最佳实践指南
5.1 回测陷阱规避
- 幸存者偏差:使用历史成分股数据(Tushare提供
index_weight
接口) - 前视偏差:严格使用
shift(1)
处理所有指标 - 交易成本:在收益中扣除手续费(示例):
transaction_cost = 0.0015 # 单边0.15%
df["strategy_returns"] = (
df["strategy_returns"] - abs(df["signal"].diff()) * transaction_cost
)
5.2 策略部署准备
- 参数固化:锁定通过Walk Forward验证的参数
- 敏感性分析:测试±20%参数波动对结果影响
- 文档输出:自动生成策略报告:
qs.reports.html(
strategy_rets,
benchmark=benchmark_rets,
title="最终策略报告",
output="./reports/最终策略报告.html",
# 添加参数记录
params={"fast_ma": 5, "slow_ma": 20, "transaction_cost": "0.15%"},
)
6. 阶段任务
任务:基于 宁德时代(300750.SZ
) 数据完成:
- 实现MACD策略(金叉买入/死叉卖出)
- 进行参数优化(测试fast=12/15/20, slow=26/30, signal=9/12)
- 使用Walk Forward分析验证参数稳定性
- (可选)加入1.5‰的交易成本计算净收益
代码框架:
# 获取数据
df = ts.pro_bar(ts_code="300750.SZ", adj="qfq", start_date="20180101")
# 预处理数据
df = data_preparation(df)
# MACD策略实现
df["macd"], df["signal_line"], _ = talib.MACD(
df["close"], fastperiod=12, slowperiod=26, signalperiod=9
)
df["position"] = np.where(df["macd"] > df["signal_line"], 1, -1).shift(1)
# Walk Forward分析
train = df.loc[:"2021-01-01"]
test = df.loc["2021-01-01":]
# 在训练集优化参数...
# 在测试集验证结果...
输出验证:最终报告应展示参数优化过程、Walk Forward测试结果,以及包含交易成本的净值曲线。
阶段 5:实战项目
目标
通过完整项目实战,掌握量化策略从数据获取到报告输出的全流程实现,培养解决实际问题的能力。
项目 1:A 股多因子选股策略回测
1.1 项目目标
构建基于 动量因子 + 市值因子 的选股策略,在沪深300成分股中:
- 每月调仓一次
- 选择因子得分最高的10只股票
- 对比策略与基准的收益风险指标
1.2 实现步骤
步骤 1:获取成分股数据
import tushare as ts
import pandas as pd
pro = ts.pro_api()
# 获取当前沪深300成分股
hs300 = pro.index_weight(index_code="000300.SH", start_date="20230101")
stock_list = hs300["con_code"].unique().tolist() # 获取股票代码列表
# 获取所有成分股行情数据(示例取前10只简化计算)
all_data = []
for ts_code in stock_list[:10]:
df = pro.daily(ts_code=ts_code, start_date="20200101", end_date="20231001")
df["ts_code"] = ts_code
all_data.append(df)
full_df = pd.concat(all_data).sort_values("trade_date")
步骤 2:计算因子值
# 市值因子(Tushare接口获取)
basic_info = pro.daily_basic(
ts_code=",".join(stock_list[:10]),
start_date="20200101",
end_date="20231001",
fields="ts_code, trade_date, circ_mv",
)
full_df = full_df.merge(basic_info, on=["ts_code", "trade_date"], how="inner")
# 动量因子(过去60日收益率)
full_df["momentum"] = full_df.groupby("ts_code")["close"].pct_change(60)
# 数据透视处理
factor_df = full_df.pivot(
index="trade_date", columns="ts_code", values=["momentum", "circ_mv"]
)
factor_df.index = pd.to_datetime(factor_df.index)
步骤 3:生成月度调仓信号
# 定义选股函数
def select_stocks(data, top_n=10):
if data.empty:
return []
# 获取月度最后一个交易日数据
last_date = data.index[-1]
daily_data = data.loc[last_date]
# 将Series转换为二维DataFrame [关键步骤]
# 列索引结构转为: ts_code × 因子(momentum/circ_mv)
factor_matrix = daily_data.unstack(level=0)
# 因子标准化
momentum_z = (
factor_matrix["momentum"] - factor_matrix["momentum"].mean()
) / factor_matrix["momentum"].std()
circ_mv_z = (
factor_matrix["circ_mv"] - factor_matrix["circ_mv"].mean()
) / factor_matrix["circ_mv"].std()
# 计算综合得分
total_score = momentum_z - 0.5 * circ_mv_z
# 选取Top N股票代码
return total_score.nlargest(top_n).index.tolist()
# 按月调仓
monthly_selected = factor_df.resample("M").apply(select_stocks)
步骤 4:计算组合收益
# 生成持仓权重矩阵
weights = pd.DataFrame(
index=monthly_selected.index, columns=factor_df.columns.levels[1]
).fillna(0)
for date, stocks in monthly_selected.items():
weights.loc[date, stocks] = 1 / len(stocks) # 等权重配置
# 计算策略收益
returns = full_df.pivot(index="trade_date", columns="ts_code", values="pct_chg").shift(
-1
)
returns.index = pd.to_datetime(returns.index)
strategy_rets = (returns * weights).sum(axis=1).dropna()
步骤 5:绩效分析
# 获取基准收益
benchmark = pro.index_daily(ts_code="000300.SH", start_date="20200101")
benchmark["trade_date"] = pd.to_datetime(benchmark["trade_date"])
benchmark = benchmark.sort_values("trade_date").set_index("trade_date")
benchmark_rets = benchmark["close"].pct_change().dropna()
# 生成报告
qs.reports.html(
strategy_rets,
benchmark=benchmark_rets,
title="多因子选股策略回测",
output="./reports/多因子策略报告.html",
)
1.3 关键问题解决
- 数据对齐:使用
shift(-1)
处理未来收益率,确保交易信号与收益周期匹配 - 幸存者偏差:使用历史成分股数据(需Tushare接口获取完整历史权重)
- 行业中性:可通过
pro.stock_company
接口获取行业信息,进行行业约束
项目 2:自动生成季度分析报告
2.1 项目目标
实现自动化工作流:
- 每季度首月1日自动获取数据
- 生成PDF/HTML格式分析报告
- 邮件发送给指定联系人
2.2 实现步骤
步骤 1:配置自动化任务
from apscheduler.schedulers.blocking import BlockingScheduler
def job():
# 获取数据
df = pro.daily(ts_code="000001.SH", start_date="20100101")
# 生成报告
report_path = "/reports/quarter_report.html"
qs.reports.html(df["pct_change"], output=report_path)
# 发送邮件
send_email(report_path)
# 设置定时任务
scheduler = BlockingScheduler()
scheduler.add_job(job, "cron", month="1,4,7,10", day=1, hour=9)
scheduler.start()
步骤 2:邮件发送函数
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
def send_email(filepath):
msg = MIMEMultipart()
msg["Subject"] = "季度分析报告"
msg["From"] = "your_email@domain.com"
msg["To"] = "recipient@domain.com"
# 添加HTML正文
with open(filepath, "r") as f:
html = MIMEText(f.read(), "html")
msg.attach(html)
# 添加PDF附件
with open(filepath.replace("html", "pdf"), "rb") as f:
attach = MIMEApplication(f.read())
attach.add_header("Content-Disposition", "attachment", filename="季度报告.pdf")
msg.attach(attach)
# 发送邮件
with smtplib.SMTP("smtp.domain.com", 587) as server:
server.login("user", "password")
server.send_message(msg)
步骤 3:报告模板定制
# 生成带样式的报告
qs.reports.html(
returns,
output="custom_report.html",
template_path="custom_template.html", # 自定义模板文件
)
2.3 最佳实践
- 错误处理:添加重试机制和异常通知
from tenacity import retry, stop_after_attempt
@retry(stop=stop_after_attempt(3))
def safe_pro_api_call():
return pro.daily(...)
- 日志记录:使用logging模块记录任务执行情况
- 安全存储:将token和邮箱密码存储在环境变量中
3. 阶段任务
任务 1:构建行业轮动策略
- 使用Tushare获取申万一级行业指数
- 计算各行业动量因子(过去3个月收益)
- 每月选择动量最强的3个行业等权配置
- 对比行业等权基准
代码提示:
# 获取行业指数
industries = pro.index_class(level="L1")
industry_codes = industries["index_code"].tolist()
# 计算行业动量
df = pro.index_daily(ts_code=industry_codes, start_date="20200101")
df["mom"] = df.groupby("ts_code")["close"].pct_change(63) # 3个月约63个交易日
任务 2:实现实时监控系统
- 每15分钟检查持仓股票异常波动
- 当出现以下情况时发送飞书消息推送预警:
- 单日跌幅超过7%
- RSI低于25
- 成交量突增3倍
代码框架:
import requests
def send_wechat_alert(msg):
url = "飞书 Webhook URL"
data = {
"msg_type": "text", # 指定消息类型
"content": {"text": "your-message"}, # 消息内容主体
}
requests.post(url, json=data)
# 在定时任务中添加监控逻辑
if current_pct < -0.07:
send_wechat_alert(f"暴跌预警:{ts_code} 当前跌幅{current_pct*100:.2f}%")
输出验证:最终项目应生成可交互的策略报告,自动化系统需实现至少1个月的稳定运行。
推荐阅读🚀:
风险提示与免责声明
本文内容基于公开信息研究整理,不构成任何形式的投资建议。历史表现不应作为未来收益保证,市场存在不可预见的波动风险。投资者需结合自身财务状况及风险承受能力独立决策,并自行承担交易结果。作者及发布方不对任何依据本文操作导致的损失承担法律责任。市场有风险,投资须谨慎。