问题描述
解决方案
在PTrade中,过滤上市时间不足90天的股票主要依赖于 get_stock_info 接口来获取股票的上市日期(listed_date),然后结合 datetime 模块计算当前回测/交易日期与上市日期的天数差。
以下是具体的实现步骤和代码示例。
核心逻辑
- 获取上市日期:使用
get_stock_info(stock_list, field=['listed_date'])获取股票的基础信息。 - 获取当前日期:使用
context.blotter.current_dt获取当前策略运行的时间(注意:不要使用datetime.datetime.now(),因为在回测中now()返回的是服务器系统时间,而不是回测的历史时间)。 - 计算时间差:将当前日期与上市日期进行相减,判断天数是否大于等于90天。
策略代码实现
以下是一个完整的策略示例,展示了如何在 before_trading_start 阶段过滤掉次新股。
import datetime
def initialize(context):
# 示例:以沪深300成分股作为初始股票池
g.security = get_index_stocks('000300.SS')
set_universe(g.security)
def before_trading_start(context, data):
# 获取初始股票池
all_stocks = g.security
# 调用过滤函数,获取上市超过90天的股票列表
g.filtered_stocks = filter_new_stocks(context, all_stocks, days=90)
# 打印日志对比数量
log.info("原始股票数量: %d, 过滤次新股后数量: %d" % (len(all_stocks), len(g.filtered_stocks)))
# 更新当天的股票池(可选,视策略逻辑而定)
# set_universe(g.filtered_stocks)
def handle_data(context, data):
# 在这里编写具体的买卖逻辑,仅针对 g.filtered_stocks 操作
pass
def filter_new_stocks(context, stock_list, days=90):
"""
过滤上市时间少于指定天数的股票
:param context: 上下文对象,用于获取当前回测时间
:param stock_list: 待过滤的股票代码列表
:param days: 设定的天数阈值,默认90天
:return: 满足条件的股票列表
"""
target_list = []
# 获取股票的基础信息,包含上市日期
# 注意:get_stock_info 返回的是嵌套字典 {'code': {'listed_date': 'yyyy-mm-dd', ...}}
infos = get_stock_info(stock_list, field=['listed_date'])
# 获取当前策略运行日期的 date 对象
current_date = context.blotter.current_dt.date()
for stock in stock_list:
# 获取该股票的具体信息
stock_data = infos.get(stock)
if stock_data and 'listed_date' in stock_data:
listed_date_str = stock_data['listed_date']
# 确保日期字符串有效
if listed_date_str and listed_date_str != '0000-00-00':
try:
# 将字符串转换为 date 对象
listed_date = datetime.datetime.strptime(listed_date_str, "%Y-%m-%d").date()
# 计算上市天数
delta = (current_date - listed_date).days
# 如果上市天数大于等于指定天数,则保留
if delta >= days:
target_list.append(stock)
except Exception as e:
# 日期格式解析出错或其他异常,跳过该股票
continue
else:
# 如果没有上市日期数据,视情况决定是否保留,这里选择跳过
continue
else:
continue
return target_list
代码关键点解析
get_stock_info: 这是获取静态数据的核心API。必须指定field=['listed_date']才能获取上市日期。context.blotter.current_dt: 在回测模式下,必须使用此变量来获取“当下的历史时间”。如果使用 Python 原生的datetime.datetime.now(),在回测2020年的数据时,它会返回2023年(或当前实际年份),导致过滤逻辑失效。- 异常处理: 代码中加入了
try-except块和对空字符串的检查,防止因为某些股票数据缺失或格式错误导致策略中断。 - Python 版本兼容: 代码使用了标准的
datetime库和基础语法,完全兼容 PTrade 支持的 Python 3.5+ 环境。
Q&A
Q: 为什么在回测中不能使用 datetime.datetime.now() 来计算时间差?
A: datetime.datetime.now() 返回的是代码运行时的服务器系统时间(例如今天是2023年10月1日)。在回测模式下,策略是在模拟过去的时间(例如2018年1月1日)。如果使用 now(),所有股票的上市时间都会被计算为距离2023年很多年,导致无法正确过滤掉回测当时刚上市的新股。必须使用 context.blotter.current_dt。
Q: get_stock_info 如果查询不到股票信息会返回什么?
A: 如果查询不到,通常会返回 None 或者该股票代码对应的 value 为空字典。在代码中需要判断 if stock_data 以避免 NoneType 错误。
Q: 如何同时过滤掉 ST 股票和停牌股票?
A: 可以结合 get_stock_status 接口。通常建议先使用 filter_stock_by_status(stock_list, filter_type=["ST", "HALT", "DELISTING"]) 进行初步过滤,然后再将结果传入上面的 filter_new_stocks 函数进行次新股过滤。