问题描述
解决方案
在聚宽(JoinQuant)平台进行历史回测时,平台默认已经包含了退市股票的数据(涵盖2005年以来的A股数据)。
要避免幸存者偏差(Survivorship Bias),关键在于如何构建股票池(Universe)。你不能使用“当前”的股票列表去回测历史,而必须使用“历史当时”的股票列表。
以下是具体的方法和代码示例:
核心方法
-
使用
get_index_stocks获取历史成分股(推荐)
这是最常用的方法。通过传入date参数,你可以获取某一指数在历史某一天的真实成分股列表。如果某只股票当时在指数内,即使现在已退市,也会被包含在内。 -
使用
get_all_securities获取历史全市场股票
通过指定date参数,可以获取在该日期处于上市状态的所有股票列表。 -
直接查询数据
对于已知的退市股票代码(如000003.XSHE),可以直接使用get_price或attribute_history获取其在市期间的行情数据。
代码实现示例
以下是一个完整的策略示例,展示如何在回测中动态获取历史成分股(包含当时的退市股)并获取其数据。
# -*- coding: utf-8 -*-
from jqdata import *
def initialize(context):
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 每天开盘前运行,更新股票池
run_daily(before_market_open, time='09:00')
def before_market_open(context):
# 【关键步骤】获取“当前回测日期”的沪深300成分股
# 如果 context.current_dt 是 2015年某天,这里返回的就是2015年那天的成分股
# 其中可能包含后来退市的股票
current_date = context.current_dt.date()
g.security_list = get_index_stocks('000300.XSHG', date=current_date)
# 打印日志验证(可选)
# log.info(f"日期: {current_date}, 股票池数量: {len(g.security_list)}")
def handle_data(context, data):
# 获取股票池中所有股票的收盘价
# 如果股票池中包含退市股,只要在它退市前,get_price 都能正常返回数据
# panel=False 返回 DataFrame
df = get_price(g.security_list,
end_date=context.current_dt,
frequency='daily',
fields=['close'],
count=1,
panel=False)
# 简单的逻辑示例:买入股票池中的前5只股票
buy_list = g.security_list[:5]
# 资金分配
if len(buy_list) > 0:
cash_per_stock = context.portfolio.available_cash / len(buy_list)
for stock in buy_list:
# 过滤掉停牌或数据缺失的股票
if stock in df['close'].index and not isnan(df['close'][stock][0]):
order_value(stock, cash_per_stock)
详细说明
1. 为什么这样能避免偏差?
如果你在代码中写死股票列表(例如 g.stocks = ['000001.XSHE', ...]),或者使用不带日期的 get_index_stocks('000300.XSHG')(这会默认取今天的成分股),那么你回测2015年时,用的就是2023年的“幸存者”名单,表现差被剔除或退市的股票就被忽略了,导致回测虚高。
使用 date=context.current_dt,平台会去查询历史数据库,还原当时的真实市场情况。
2. 如何获取全市场历史股票?
如果你不局限于指数成分股,想全市场选股,可以使用 get_all_securities:
# 获取回测当日还在上市的所有股票信息
all_stocks_info = get_all_securities(types=['stock'], date=context.current_dt)
# 提取股票代码列表
g.security_list = list(all_stocks_info.index)
3. 退市后的数据表现
- 退市前: 数据正常获取。
- 退市后: 如果你在策略中请求该股票退市日期之后的数据:
get_price会返回NaN(空值)。attribute_history会返回NaN。get_current_data()[stock].paused会返回True(表示停止交易)。
常见问题 Q&A
Q: 我需要专门去下载退市股票的数据包吗?
A: 不需要。聚宽的云端数据库已经内置了所有历史退市股票的数据,只要通过 API 正确调用即可。
Q: 如果我持有的股票在回测过程中退市了,系统怎么处理?
A: 在聚宽回测引擎中,如果持仓股票退市,通常无法再进行卖出操作(因为没有后续行情撮合),这笔资产会留在账户中直到回测结束。在编写策略时,建议通过 get_current_data() 检查股票状态,或利用 get_all_securities 中的 end_date(退市日期)提前进行风控平仓。
Q: get_price 获取退市股票数据时需要设置 skip_paused 吗?
A: 视情况而定。
skip_paused=False(默认):如果股票已退市,查询退市后的日期会用NaN填充。skip_paused=True:会跳过退市后的日期,只返回有交易数据的行。如果请求的时间段完全在退市后,可能返回空 DataFrame。
Q: 如何查询某只股票具体的退市日期?
A: 使用 get_security_info 函数:
info = get_security_info('000003.XSHE')
print(info.end_date) # 输出退市日期,未退市则为 2200-01-01