🚀 AI 一键生成 ptrade 策略代码

PTrade 避坑:get_history 返回 Panel 还是 DataFrame?

ptrade | 发布时间: 2025/12/24
以下内容由 EasyQuant 生成。

问题描述

回测时 get_history 返回的是 Panel 还是 DataFrame?如何处理兼容性?

解决方案

PTrade 回测中 get_history 返回类型及兼容性处理

在 PTrade 中,get_history 的返回类型取决于Python 环境版本(Python 3.5 或 Python 3.11)、查询标的数量以及查询字段的数量

由于 Pandas 库在更新过程中移除了 Panel 数据结构,导致不同 Python 内核下的表现不一致。

1. 返回类型详解

Python 3.5 环境

  • 单只股票:无论查询单个还是多个字段,返回 DataFrame
  • 多只股票 + 单个字段:返回 DataFrame(列名为股票代码)。
  • 多只股票 + 多个字段:返回 Panel
    • 注意:Panel 是旧版 Pandas 的数据结构,三维数组(Items: 字段, Major_axis: 时间, Minor_axis: 股票代码)。

Python 3.11 环境

  • 单只股票:返回 DataFrame
  • 多只股票:无论查询单个还是多个字段,均返回 DataFrame
    • 结构差异:Python 3.11 返回的 DataFrame 通常包含 code 列,数据呈“长表”格式(Long Format),需要通过筛选(如 .query)来获取特定股票的数据。

2. 最佳兼容性解决方案:使用 is_dict=True

为了避免在不同环境(或未来升级)中处理 Panel 和 DataFrame 的差异,强烈建议在调用 get_history 时设置参数 is_dict=True

这样无论是在 Python 3.5 还是 3.11 中,返回的都将是标准的 Python 字典 (dict),且官方文档指出这种方式取数速度相对较快。

推荐代码示例

def initialize(context):
    g.security = ['600570.SS', '000001.SZ']
    set_universe(g.security)

def handle_data(context, data):
    # 【推荐方式】强制返回字典,无需关心是 Panel 还是 DataFrame
    # 获取过去5天的收盘价和开盘价
    history_data = get_history(5, '1d', ['close', 'open'], g.security, is_dict=True)
    
    # 遍历股票池进行处理
    for stock in g.security:
        if stock in history_data:
            # 获取该股票的数据(numpy array格式,包含时间戳和请求的字段)
            stock_data = history_data[stock]
            
            # 获取收盘价序列
            close_prices = stock_data['close']
            # 获取开盘价序列
            open_prices = stock_data['open']
            
            # 打印最新一天的收盘价
            log.info("股票: %s, 最新收盘价: %f" % (stock, close_prices[-1]))
            
            # 计算均值示例
            avg_close = close_prices.mean()

3. 如果必须处理 Panel (Python 3.5 遗留代码)

如果您维护的是旧策略,且必须在 Python 3.5 环境下处理多股多字段返回的 Panel 数据,可以使用以下方式进行转换或切片。

处理 Panel 的代码示例

def initialize(context):
    g.security = ['600570.SS', '000001.SZ']
    set_universe(g.security)

def handle_data(context, data):
    # 未使用 is_dict=True,在 Py3.5 下多股多字段会返回 Panel
    res = get_history(5, '1d', ['close', 'open'], g.security)
    
    # 检查返回类型是否为 Panel (仅在 Py3.5 有效)
    import pandas as pd
    # 注意:在 Py3.11 中没有 pd.Panel,这里仅作逻辑演示
    if isinstance(res, pd.Panel):
        # Panel 切片方式:res[字段名][股票代码]
        # 获取所有股票的收盘价 DataFrame (行: 时间, 列: 股票代码)
        close_df = res['close']
        log.info("Panel 转换后的 Close DataFrame:\n%s" % close_df)
        
        # 获取特定股票的收盘价
        stock_close = close_df['600570.SS']
        
        # 也可以转换轴,将 Items(字段) 换成股票代码
        # 转换后可以通过 res['600570.SS'] 获取该股票的所有字段 DataFrame
        res_swapped = res.swapaxes("minor_axis", "items")
        stock_df = res_swapped['600570.SS']
        log.info("600570.SS 的数据:\n%s" % stock_df)
        
    else:
        # Python 3.11 或 单股情况,直接是 DataFrame
        # Py3.11 多股返回的 DataFrame 包含 'code' 列
        if 'code' in res.columns:
            # 使用 query 筛选特定股票
            stock_df = res.query('code == "600570.SS"')
            log.info("Py3.11 DataFrame 筛选结果:\n%s" % stock_df)

总结

  1. 现状:Python 3.5 多股多字段返回 Panel,Python 3.11 返回 DataFrame
  2. 风险Panel 已被 Pandas 官方弃用,代码移植性差。
  3. 建议:始终在 get_history 中使用 is_dict=True。这不仅解决了兼容性问题,还能获得更好的性能。

Q&A

Q: 为什么官方文档推荐使用 is_dict=True
A: 除了解决版本兼容性问题外,返回字典格式(底层是 Numpy 数组)通常比构建 Pandas DataFrame 或 Panel 开销更小,取数速度相对较快,特别是在高频调用或处理大量数据时。

Q: 如果我在 Python 3.11 中运行旧的 Panel 代码会怎样?
A: 会报错。因为 Python 3.11 配套的 Pandas 版本较高,已经彻底移除了 pd.Panel 类。旧策略迁移到新环境时,必须重写数据获取逻辑,或者改用 is_dict=True

Q: get_price 接口也有这个问题吗?
A: 是的,get_priceget_history 在多股多字段场景下的返回逻辑是一致的。同样建议使用 is_dict=True 参数。