QuantStats + TA-Lib:量化策略分析入门实战的五阶段构建之旅

QuantStats + TA-Lib:量化策略分析入门实战的五阶段构建之旅

本文以QuantStats为核心分析工具,通过"数据获取→指标计算→策略构建→回测验证→实战应用"五阶段进阶路径,结合Tushare金融数据接口与TA-Lib技术分析库,深入解析双均线策略、多因子模型等经典量化模型的实现细节与优化方法。内容涵盖参数网格搜索Walk Forward分析等回测方法,演示如何规避过拟合风险与策略陷阱。
文中内容仅限技术学习与代码实践参考,市场存在不确定性,技术分析需谨慎验证,不构成任何投资建议。适合量化新手建立系统认知,为策略开发打下基础。

茅台股票分析

阶段 1:环境配置与数据准备

1. 环境安装

目标:正确安装 QuantStats、TA-Lib、Tushare 及相关依赖库。

步骤说明

  1. 安装 Python 库

    pip install quantstats==0.0.62 pandas==2.0.2 numpy==1.26.4 tushare==1.4.19  # 基础库
    

    ‼️ 重要提示:请务必安装对应版本,避免浪费时间 ‼️

  2. 安装 TA-Lib

    • Windows:从 TA-Lib 预编译库 下载对应版本的 .whl 文件,通过 pip install 文件名.whl 安装。

    • Mac/Linux:使用 pip 安装:

      conda install -c conda-forge ta-lib
      
  3. 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. 数据预处理

目标:清洗数据,确保格式正确、无缺失。

步骤说明

  1. 处理缺失值

    df = df.dropna(subset=['close'])  # 删除无收盘价的无效行
    
  2. 验证复权效果
    对比复权前后价格(示例):

     # 获取未复权数据对比
     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())  # 复权后收盘价
     ```
    
    
  3. 计算收益率(为阶段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) 数据完成以下操作:

  1. 计算 20天 RSI 并添加到 DataFrame;
  2. 使用 QuantStats 生成包含 Sharpe、RSI 曲线 的 HTML 报告;
  3. (可选)当 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 数据完成:

  1. 实现MACD策略(金叉买入/死叉卖出)
  2. 进行参数优化(测试fast=12/15/20, slow=26/30, signal=9/12)
  3. 使用Walk Forward分析验证参数稳定性
  4. (可选)加入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个月的稳定运行。

推荐阅读🚀

风险提示与免责声明
本文内容基于公开信息研究整理,不构成任何形式的投资建议。历史表现不应作为未来收益保证,市场存在不可预见的波动风险。投资者需结合自身财务状况及风险承受能力独立决策,并自行承担交易结果。作者及发布方不对任何依据本文操作导致的损失承担法律责任。市场有风险,投资须谨慎。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

船长@Quant

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值