一、QuantStats 极简入门,量化小白的福音
它能做什么?
- 3 行代码生成专业报告:夏普比率、最大回撤等 30 + 关键指标,一键生成,轻松掌握投资策略的核心表现。
- 可视化对比:策略与基准的收益曲线、月度热力图直观呈现,策略优劣一目了然,让分析决策更高效。
- 自动化分析:复权、日期对齐等复杂操作统统自动完成,大大节省时间和精力,量化分析变得轻松简单。
为什么新手必学?
传统量化分析需要编写成百上千行代码,对于非专业程序员出身的投资者来说,难度极大。而 QuantStats 却能将代码量压缩至 10 行以内 ,极大降低了量化分析的门槛,让更多人能够轻松开启量化投资之旅。
二、环境精准配置,避开 50% 的错误
第 1 步:安装 Miniconda(环境隔离)
- 访问 Miniconda 官网,找到 Python 3.9 版本进行下载。
- 安装时,一定要记得勾选 Add to PATH,这一步很关键,否则后续可能会遇到各种环境配置问题。
第 2 步:创建独立环境
bash
conda create -n quant python=3.9 -y
conda activate quant
?验证方式:当你看到命令行开头显示 (quant) ,就说明你的独立环境已经成功激活啦。
第 3 步:安装指定版本依赖
bash
# 卸载旧版本(防冲突)
pip uninstall -y pandas numpy quantstats akshare
# 精准安装(2025年稳定组合)
pip install pandas==2.2.3 quantstats==0.0.64 akshare==5.0.2
?验证命令:
python
import pandas as pd
print(pd.__version__)
运行上述代码后,必须输出 2.2.3,才说明安装正确。
三、全流程代码及报错修复,从数据获取到报告生成
1. 数据获取(含自动重试)
python
import akshare as ak
def fetch_data(symbol, is_index=False):
"""获取股票/指数数据(自动处理接口错误)"""
try:
if is_index:
df = ak.stock_zh_index_daily_em(symbol=symbol)
df = df.rename(columns={'date': 'date', 'close': 'close'})
else:
df = ak.stock_zh_a_hist(symbol=symbol, period="daily", adjust="hfq")
df = df.rename(columns={'日期': 'date', '收盘': 'close'})
return df
except Exception as e:
print(f"数据获取失败:{str(e)},请检查网络或代码")
exit()
新手注意:
- 股票代码需带市场后缀(如.SS),不过 AKShare 的 A 股接口比较特殊,不需要添加后缀。
- 如果数据获取失败,可以尝试更换数据源:
python
# 使用Yahoo数据(需梯子)
import yfinance as yf
df = yf.download('601398.SS', start='2020-01-01')
2. 数据预处理(解决 RangeIndex 报错)
python
def process_data(stock_df, index_df):
# 转换日期格式(防御性编程)
for df in [stock_df, index_df]:
df['date'] = pd.to_datetime(df['date'], errors='coerce')
df.dropna(subset=['date'], inplace=True)
# 合并数据并设置时间索引(关键!)
merged = pd.merge(stock_df, index_df, on='date', suffixes=('_stock', '_index'))
merged = merged.set_index('date').sort_index()
# 终极校验
assert isinstance(merged.index, pd.DatetimeIndex), "时间索引必须为DatetimeIndex!"
return merged
?验证方法:
python
print("索引类型:", type(merged.index))
运行结果应输出
3. 生成报告(解决 ME 报错)
python
def generate_report(merged):
# 计算收益率(限制涨跌幅防异常值)
merged['return_stock'] = merged['close_stock'].pct_change().clip(-0.3, 0.3)
merged['return_index'] = merged['close_index'].pct_change().clip(-0.3, 0.3)
merged.dropna(inplace=True)
# 必须修改QuantStats源码(见下方)
qs.reports.html(
returns=merged['return_stock'],
benchmark=merged['return_index'],
output='report.html',
title='策略分析报告'
)
四、必做操作:解决 ME 报错的源码修改
步骤详解
- 找到 QuantStats 安装路径:
# Windows示例
C:\Users\你的用户名\.conda\envs\quant\Lib\site-packages\quantstats\_plotting\core.py
- 修改第 294 行(文本对比):
diff
- returns = returns.sum(axis=0)
+ returns = returns.sum() # 删除axis=0参数
- 保存修改后的文件,然后重启 Python 内核,让修改生效。
五、常见报错全解(附解决方案),遇到问题不再抓瞎
报错信息 | 原因分析 | 解决方案 |
Invalid frequency: ME | Pandas 版本≥2.0 后别名变更 | 修改 QuantStats 源码或降级 Pandas |
numpy operations are not valid... | 错误使用 Numpy 函数 | 改用 Pandas 原生聚合如.sum () |
RangeIndex 时间索引错误 | 未正确设置 DatetimeIndex | 执行 df.set_index ('date') 并校验类型 |
AttributeError: module..._FREQUENCIES | QuantStats 版本不兼容 | 升级 QuantStats 至 0.0.64 + 或手动添加映射 |
六、完整代码模板(复制即用),轻松搭建量化分析框架
python
# -*- coding: utf-8 -*-
# 作者:itsok
# 功能:QuantStats 全流程稳健版(增强错误处理+自动化重试)
import time
import pandas as pd
import quantstats as qs
from functools import wraps
# ---- 新增代码:全局频率适配 ----
if hasattr(pd, 'infer_freq'):
pd.infer_freq = lambda x: pd.infer_freq(x).replace("ME", "M")
# 劫持QuantStats内部频率推断逻辑
def patched_infer_freq(index):
freq = pd.infer_freq(index)
return freq.replace("ME", "M") if freq else "D"
# ------------------- 第0步:环境检查 -------------------
def check_environment():
"""检查依赖库是否安装"""
try:
import akshare as ak
return ak
except ImportError:
raise ImportError("请先安装依赖库:pip install -r requirements.txt")
# ------------------- 第1步:配置重试机制 -------------------
def retry(max_retries=3, delay=5):
"""通用重试装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for i in range(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"?? 第{i+1}次尝试失败: {str(e)}")
if i < max_retries - 1:
print(f" {delay}秒后重试...")
time.sleep(delay)
raise RuntimeError(f"操作失败,已达最大重试次数{max_retries}次")
return wrapper
return decorator
# ------------------- 第2步:数据获取模块 -------------------
@retry(max_retries=5, delay=10)
def fetch_stock_data(symbol: str) -> pd.DataFrame:
"""
获取股票数据(带自动重试)
参数:
symbol: 股票代码 (例: "601398")
"""
print("? 正在获取股票数据...")
df = ak.stock_zh_a_hist(
symbol=symbol,
period="daily",
start_date="20200101",
end_date="20250223",
adjust="hfq"
)
if df.empty:
raise ValueError("获取的股票数据为空,请检查代码或日期范围")
return df
@retry(max_retries=5, delay=10)
def fetch_benchmark_data(symbol: str) -> pd.DataFrame:
"""
获取基准指数数据(带自动重试)
参数:
symbol: 指数代码 (例: "sh000300")
"""
print("? 正在获取基准数据...")
df = ak.stock_zh_index_daily_em(symbol=symbol)
if df.empty:
raise ValueError("获取的基准数据为空,请检查代码")
return df
# ------------------- 第3步:数据预处理 -------------------
def preprocess_data(stock: pd.DataFrame, benchmark: pd.DataFrame) -> pd.DataFrame:
"""
数据预处理管道
返回:
merged_data: 处理后的合并数据
"""
print(" 数据预处理中...")
# 重命名列(防御性编程)
stock = stock.rename(columns={'日期': 'date', '收盘': 'close'})
benchmark = benchmark.rename(columns={'date': 'date', 'close': 'benchmark_close'})
# 转换日期类型(强制校验)
for df in [stock, benchmark]:
df['date'] = pd.to_datetime(df['date'], errors='coerce')
if df['date'].isnull().any():
invalid_dates = df[df['date'].isnull()]['date'].index.tolist()
raise ValueError("日期格式转换失败,存在无效日期")
# 合并数据集(自动对齐日期)
merged = pd.merge(
stock[['date', 'close']],
benchmark[['date', 'benchmark_close']],
on='date',
how='inner',
validate='one_to_one'
)
# 二次校验日期格式
if not pd.api.types.is_datetime64_any_dtype(merged['date']):
raise TypeError("合并后的date列仍非时间类型")
# 设置时间索引(核心修复)
merged = merged.set_index('date').sort_index()
# 有效性检查
if merged.empty:
raise ValueError("合并后的数据为空,请检查日期范围是否重叠")
# 计算收益率(带边界检查)
merged['strategy_return'] = merged['close'].pct_change()
merged['benchmark_return'] = merged['benchmark_close'].pct_change()
# 清理无效数据
cleaned = merged.dropna(subset=['strategy_return', 'benchmark_return'])
if len(cleaned) < 10:
raise ValueError("有效数据不足,至少需要10个交易日数据")
assert isinstance(cleaned.index, pd.DatetimeIndex), "索引必须为DatetimeIndex"
return cleaned
# ------------------- 第4步:报告生成 -------------------
def generate_report(strategy_ret: pd.Series, benchmark_ret: pd.Series):
# ---- 新增代码:全局替换ME为M ----
import quantstats.utils as qs_utils
from pandas.tseries.frequencies import to_offset
# ----------------------------
print(" 生成分析报告中...")
report_path = "quant_analysis_report.html"
qs.reports.html(
returns=strategy_ret,
benchmark=benchmark_ret,
output=report_path,
title='Industrial Bank vs CSI 300 Analysis',
download_filename=report_path
)
print(f"? 报告已生成:{report_path}")
# ------------------- 主执行流程 -------------------
if __name__ == "__main__":
try:
# 初始化环境
ak = check_environment()
qs.extend_pandas()
# 数据获取
stock_df = fetch_stock_data("601398")
print(f"?? 股票数据获取成功,共{len(stock_df)}条记录")
benchmark_df = fetch_benchmark_data("sh000300")
print(f"?? 基准数据获取成功,共{len(benchmark_df)}条记录")
# 数据处理
processed_data = preprocess_data(stock_df, benchmark_df)
print(f"?? 数据预处理完成,有效交易日:{len(processed_data)}天")
# 报告生成
generate_report(
processed_data['strategy_return'],
processed_data['benchmark_return']
)
except Exception as e:
print(f"? 发生严重错误: {str(e)}")
print("建议排查步骤:")
print("1. 检查网络连接")
print("2. 验证股票/指数代码有效性")
print("3. 查看requirements.txt版本是否匹配")
七、终极验证清单,确保量化分析顺利进行
- 检查 Pandas 版本是否为 2.2.3,这是与当前配置最适配的版本。
- 确认 QuantStats 源码已按照上述方法修改(core.py 第 294 行),否则可能会持续报错。
- 验证数据索引类型为 DatetimeIndex,这是保证后续分析准确的关键。
- 报告路径不要包含中文,避免出现乱码问题,影响报告的正常查看。
- 最终效果