问题描述
解决方案
在 PTrade 量化交易平台中,处理历史行情数据(get_history 或 get_price)中的 NaN(空值)通常有两种方式:利用 API 自带参数或使用 Pandas 的填充方法。
虽然 PTrade 文档说明在停牌场景下,get_history 通常会自动使用停牌前的数据填充价格(成交量为0),但在某些特定数据缺失或分钟级回测场景下,依然可能出现 NaN。
最稳健且通用的方法是利用 Pandas DataFrame 的 fillna 函数 进行向前填充(Forward Fill)。
核心解决方案
使用 Pandas 的 fillna(method='ffill') 方法。这会将上一个非空值赋给当前的 NaN 位置,非常适合处理停牌或数据缺失的情况。
策略代码示例
以下是一个完整的策略示例,展示了如何获取历史数据并清洗 NaN 值:
def initialize(context):
# 设置股票池,这里以恒生电子为例
g.security = '600570.SS'
set_universe(g.security)
def handle_data(context, data):
# 1. 获取历史行情数据
# 获取过去 100 天的收盘价
# 注意:PTrade 文档指出停牌通常会自动填充价格,但为了保险起见或处理其他缺失,我们手动处理
df = get_history(100, frequency='1d', field=['close', 'volume'], security_list=g.security)
# 打印处理前的数据情况(调试用)
# log.info("处理前是否存在空值: %s" % df.isnull().values.any())
# 2. 使用 Pandas 的 fillna 进行向前填充
# method='ffill' 表示用前一条数据填充当前空值 (Forward Fill)
# inplace=True 表示直接在原 DataFrame 上修改,不创建新对象
df.fillna(method='ffill', inplace=True)
# 3. 处理上市初期可能存在的 NaN
# 如果数据开头就是 NaN(因为前面没有数据可供填充),可以用 0 或其他特定值填充
df.fillna(0, inplace=True)
# 打印处理后的数据
# log.info("处理后是否存在空值: %s" % df.isnull().values.any())
# 4. 基于清洗后的数据计算指标(例如计算均线)
close_prices = df['close']
if len(close_prices) >= 5:
ma5 = close_prices[-5:].mean()
log.info("5日均线价格: %.2f" % ma5)
# 简单的交易逻辑示例
current_price = data[g.security]['close']
if current_price > ma5:
order(g.security, 100)
详细说明
1. Pandas fillna(method='ffill')
这是处理时间序列数据中最常用的方法。
- 原理:如果 T 时刻的数据是
NaN,它会查找 T-1 时刻的数据来填充。如果 T-1 也是NaN,则继续向前查找 T-2,直到找到有效值为止。 - 适用性:适用于所有通过
get_history或get_price返回的 DataFrame 格式数据。
2. PTrade API 的 fill 参数
get_history 函数本身提供了一个 fill 参数:
fill='pre':用上一分钟数据填充。fill='nan':用 NaN 填充(默认)。- 局限性:文档指出该参数仅在交易模块有效(实盘或模拟盘),且主要针对分钟级数据的某一时刻缺失。在回测模式下,建议使用 Pandas 方法以保证逻辑的一致性。
3. 关于停牌数据的特别说明
根据 PTrade 文档:
- 价格(Open/High/Low/Close):停牌期间,系统通常会自动使用停牌前一交易日的收盘价进行填充。
- 成交量(Volume):停牌期间成交量为 0。
- 如果你在计算技术指标(如 MA、MACD)时遇到报错,通常是因为数据长度不足或上市初期的空值,而非中间的停牌空值。使用
df.fillna(method='ffill').fillna(0)是一套完整的防御性编程写法。
Q&A
Q: 为什么 get_history 获取的数据会有 NaN?
A: 除了停牌,新股上市初期的日期(如果请求的时间范围早于上市日期)也会导致 NaN。此外,某些非活跃标的在分钟级别可能没有成交,导致特定分钟数据缺失。
Q: fillna(0) 和 fillna(method='ffill') 有什么区别?
A: fillna(0) 会将所有空值变成 0,这对于价格数据(Price)是不合理的(价格突然变成0会严重扭曲均线计算)。fillna(method='ffill') 是用前值填充,保持了价格的连续性,更符合金融时间序列的逻辑。通常先用 ffill 处理中间的空缺,再用 0 处理最开始无法填充的部分。
Q: 在回测和实盘中,处理 NaN 的方式需要不同吗?
A: 建议保持一致。虽然实盘 API 提供了 fill='pre' 参数,但在代码层面统一使用 Pandas 的 fillna 可以确保你的策略在回测和实盘中表现一致,减少因环境差异导致的 Bug。