百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

亲测!QuantStats一键生成量化报告,比手动快百倍

yund56 2025-03-02 19:33 13 浏览

一、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))  

运行结果应输出 pandas.core.indexes.datetimes.DatetimeIndex'> ,表示索引类型正确。

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 报错的源码修改

步骤详解

  1. 找到 QuantStats 安装路径:
# Windows示例  
C:\Users\你的用户名\.conda\envs\quant\Lib\site-packages\quantstats\_plotting\core.py  
  1. 修改第 294 行(文本对比):

diff

- returns = returns.sum(axis=0)  
+ returns = returns.sum()  # 删除axis=0参数  
  1. 保存修改后的文件,然后重启 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版本是否匹配")

七、终极验证清单,确保量化分析顺利进行

  1. 检查 Pandas 版本是否为 2.2.3,这是与当前配置最适配的版本。
  2. 确认 QuantStats 源码已按照上述方法修改(core.py 第 294 行),否则可能会持续报错。
  3. 验证数据索引类型为 DatetimeIndex,这是保证后续分析准确的关键。
  4. 报告路径不要包含中文,避免出现乱码问题,影响报告的正常查看。
  5. 最终效果

相关推荐

一对多查询,Vlookup公式组合Countifs,详细解读!

我们模拟工作中的使用场景,只保留了关键的两列数据需要根据部门条件,把所有的员工信息给匹配出来首先,当存在多个结果值时,我们去查找的时候,只会返回第一个出现的值:=VLOOKUP(D2,A:B,2,0)...

自动获取vlookup函数的第三参数,再也不用一列一列的数了

对于vlookup函数,很多人都有会这样的想法:vlookup函数的第三参数为什么就不能自动的获取到呢,还需要一个一个的数太烦人了。有没有什么方法能自动的获取vlookup函数的第三参数呢?当然有了,...

VLOOKUP函数比对一般用法

格式"=VLOOKUP(要查谁,在哪查,返还值为查询范围的第几列,精确还是近似)"。中间用逗号隔开。如:A列为全部人员姓名,B列为对应全部身份证号码,C列为部分人员姓名(有在A列里的,...

VLOOKUP的这些坑,你都知道吗?

VLOOKUP的这些坑,你都知道吗?VLOOKUP是大家常用的查找引用函数,很多人正式学习Excel都是从这个函数开始的,但有的时候VLOOKUP却不太听话,公式返回错误结果,让人苦不堪言。有时明明数...

重塑Excel体验:VLOOKUP与公式联动,轻松实现多列信息一键匹配!

真心羡慕啊!每次我都要慢悠悠地一个个用VLOOKUP去找数据,跟蜗牛爬似的慢,还容易出错,可你看看人家同事,手一挥,几十秒就搞定了,那叫一个利索!步骤一:输入VLOOKUP函数并设置查找值在需要输入公...

巧用Vlookup函数揪出“第三者”

在一张Excel表格的重复记录中,让你快速列出每种不同物品第2次或第n次出现的记录,你会怎么做?Vlookup函数就有这个本事。举例来说,产品或者物流表格中往往会记录有同一货物的多笔数据(如下图的今日...

一次匹配多个值,Vlookup、Xlookup、Filter公式,你用哪个

举个工作实例,左边是员工信息表数据需要根据编号,把后面所有的信息一次性的匹配出来工作中,经常遇到这种问题,有3个公式,可以快速的解决,分别是VLOOKUP公式,XLOOKUP公式,Filter公式1、...

Excel实例:VLOOKUP与XLOOKUP双条件精确查找、模糊查找

咱们今天通过实例聊聊VLOOKUP和XLOOKUP,看看它们在精确和模糊查找上有什么不同。我们假设有一个数据表,这个表里有地区、品类以及对应的金额。根据大区和品类这两个条件,来查找并匹配出相应的金额数...

带超链接的Vlookup公式查找匹配,你会么

举个例子,左边有源数据,现在我们需要根据部分员工数据,查找匹配工资,并且带一个超链接,当我们点击工资数据时,自动跳转到原数据位置1、查找结果正常我们想查找匹配出结果,用vlookup公式,或xlook...

Vlookup公式查找出错,原表明明有数据

Vlookup公式天天用,但是经常出错,还找不出原因,今天分享3步检查曲,举个工作中的例子:左边是工资表,需要查找匹配部分员工的工资数据1、公式引用出错我们使用VLOOKUP公式,查找匹配,输入的公式...

不用嵌套其它函数,VlOOKUP实现一对多查找

在处理日常工作时,我们常会遇到一份详细的花名册,其中记录了众多人员的多项信息。然而,当需要从这份名单中快速提取特定人员的相关资料时,一个常见的问题是:目标信息与花名册中的列顺序并不一致。如图所示:面对...

VLOOKUP函数开挂用法:多表多文件多区域查找大揭秘

在日常办公中,Excel堪称我们的得力助手,而VLOOKUP函数更是其中的明星,被大家广泛运用,堪称“数据查找神器”。说起VLOOKUP函数,相信大家都不陌生。最常见的用法,就是在一个表...

按需填充 根据指定数字填充重复数据

由于公司启用了新标签,现在需要根据盘点产品数重新打印指定数量的标签。比如冰箱4台,洗衣机2台,那么就要在D2:D5数据区域复制4个重复的冰箱品名、2个重复的洗衣机品名,以此类推(图1)。手动复制容易出...

那些可以替代VLOOKUP的函数们!

大家好,今天我们来讲讲那些和VLOOKUP功能一样的函数们,但是却没有VLOOKUP函数那么有名气,所有总是嫉妒VLOOKUP函数。VLOOKUP函数大家肯定都很熟悉了。VLOOKUP函数是一个查询类...

VLOOKUP跨表查找,你会吗?

VLOOKUP跨表查找,你会吗?VLOOKUP函数是大家最常用的查找引用函数,我们在工作中经常用它按照条件查找对应的数据,但是当数据源分散在多张工作表中时,你知道怎么跨表查找吗?今天我通过一个Exce...