问题描述
解决方案
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)来获取特定股票的数据。
- 结构差异:Python 3.11 返回的 DataFrame 通常包含
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)
总结
- 现状:Python 3.5 多股多字段返回
Panel,Python 3.11 返回DataFrame。 - 风险:
Panel已被 Pandas 官方弃用,代码移植性差。 - 建议:始终在
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_price 与 get_history 在多股多字段场景下的返回逻辑是一致的。同样建议使用 is_dict=True 参数。