问题描述
大家好,我是一名量化新手,正在学习使用聚宽平台编写多因子策略。最近我在尝试实现一个结合动量(20日涨幅)和估值(PE/PB)的选股策略,并希望剔除上市不足60天的新股。
但在判断股票上市天数时,遇到了一个典型的 TypeError 错误:
python
我的原始代码
if (context.current_dt - get_security_info(stock).start_date).days < 60:
continue
运行后报错:
text
TypeError: unsupported operand type(s) for -: 'datetime.datetime' and 'datetime.date'
我查阅了 Python 文档,了解到:
context.current_dt 是 datetime.datetime 类型(含时分秒)
get_security_info(stock).start_date 是 datetime.date 类型(仅年月日)
我的问题如下:
最佳实践:在聚宽策略中,最简洁且高效的方式是什么?是否推荐使用 datetime.combine()?
性能考量:如果对300只股票都做此转换,是否会显著增加回测耗时?
替代方案:能否直接用字符串比较(如 str(context.current_dt.date()))?这样做是否有隐患?
官方建议:聚宽 API 是否有内置函数可直接获取“上市以来交易天数”?这样可避免手动计算。
附:我的修复尝试(已能运行,但不确定是否最优)
python
from datetime import datetime
start_date = get_security_info(stock).start_date
start_dt = datetime(start_date.year, start_date.month, start_date.day)
if (context.current_dt - start_dt).days < 60:
continue
期待社区大佬的指导!也希望能帮助其他遇到同样问题的新手。谢谢!
解决方案
你好!这是一个在 JoinQuant(聚宽)平台编写策略时非常经典且常见的问题。Python 对日期时间类型的严格限制导致了 datetime.datetime 和 datetime.date 无法直接相减。你通过构造新的 datetime 对象来解决问题的思路是完全正确的,但确实还有更简洁、更高效的“最佳实践”。
下面针对你的四个问题逐一解答:
1. 最佳实践:最简洁且高效的方式是什么?
最简洁的方式不是把 date 升级为 datetime,而是把 datetime 降级为 date。你可以直接调用 context.current_dt.date() 方法,这样代码会非常优雅:
# 最佳实践:将 current_dt 转换为 date 类型再相减
if (context.current_dt.date() - get_security_info(stock).start_date).days < 60:
continue
2. 性能考量:对300只股票做转换会显著增加耗时吗?
如果你在循环内部每次都调用 context.current_dt.date(),虽然单次转换的开销极小,但在大量股票和长回测周期下,依然是不必要的重复计算。
性能优化建议:将当前日期的获取提取到循环外部(因为在同一个 handle_data 或 before_trading_start 周期内,当前日期是不变的)。
# 性能优化写法
current_date = context.current_dt.date() # 提取到循环外,O(1) 复杂度
for stock in stock_list:
start_date = get_security_info(stock).start_date
if (current_date - start_date).days < 60:
continue
# 后续的动量和估值逻辑...
3. 替代方案:能否直接用字符串比较?
强烈不建议使用字符串比较来计算时间差。
字符串比较(如 "2023-10-01" > "2023-08-01")只能用于判断日期的先后顺序,但无法直接计算出相差的天数。如果你需要判断“是否小于60天”,字符串是无能为力的,必须依赖 datetime 模块的 timedelta 对象(即相减后调用 .days)。
4. 官方建议:是否有内置函数直接获取“上市以来交易天数”?
聚宽 API 中没有直接名为 get_listed_days(stock) 的函数,目前获取上市日期的标准做法就是你所使用的 get_security_info(stock).start_date。
⚠️ 一个重要的业务逻辑提示:自然日 vs 交易日
你目前的代码 (current_date - start_date).days < 60 计算的是自然日(Calendar Days)。通常情况下,剔除上市不足60天的自然日已经足够满足大部分策略的需求。
但如果你严格要求剔除上市不足60个交易日(Trading Days)的新股,你需要使用聚宽的 get_trade_days API:
# 严格计算交易日天数(注意:此方法在循环内调用性能开销较大,不推荐对全市场股票频繁使用)
trade_days_since_listed = len(get_trade_days(start_date, current_date))
if trade_days_since_listed < 60:
continue
实战妥协方案:由于 60 个交易日大约等于 85 个自然日,量化实战中为了追求极速的回测性能,通常直接用自然日近似替代:
# 用 85 个自然日近似替代 60 个交易日,性能极高
if (current_date - start_date).days < 85:
continue
总结:你的最终优化代码框架
结合动量与估值策略,你的选股过滤模块可以这样写:
def filter_new_stocks(context, stock_list):
"""剔除上市不足60个自然日的新股"""
current_date = context.current_dt.date() # 循环外获取当前日期
valid_stocks = []
for stock in stock_list:
# 获取上市日期
start_date = get_security_info(stock).start_date
# 降级相减,计算自然日差值
if (current_date - start_date).days >= 60:
valid_stocks.append(stock)
return valid_stocks
希望这个解答能帮到你!祝你的多因子策略早日跑出漂亮的超额收益(Alpha)!