问题描述
解决方案
在聚宽(JoinQuant)平台上,要找到最近一个月上市的新股(次新股),核心思路是利用 get_all_securities 函数获取股票的上市日期(start_date),然后将其与当前回测日期进行比较。
以下是实现该逻辑的完整策略代码。该策略每天开盘时运行,筛选出上市时间在30天以内的股票,并采用等权重买入的简单逻辑。
策略实现思路
- 获取数据:使用
get_all_securities(types=['stock'], date=context.current_dt)获取当前日期所有在市股票的信息。 - 筛选日期:计算当前日期与股票
start_date(上市日期)的差值,保留差值小于等于30天的股票。 - 过滤标的:为了交易安全,通常需要过滤掉停牌和ST股票。
- 执行交易:卖出不再满足“次新股”条件的持仓,买入新筛选出的次新股。
策略代码
# -*- coding: utf-8 -*-
import datetime
from jqdata import *
def initialize(context):
"""
初始化函数,设定基准和全局配置
"""
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 设定手续费(股票类每笔交易时的手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱)
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 每天开盘时运行选股和交易逻辑
run_daily(trade_new_stocks, '09:30')
def trade_new_stocks(context):
"""
每日交易逻辑:筛选近一个月上市的股票并调仓
"""
# 1. 获取当前回测日期
current_date = context.current_dt.date()
# 2. 计算30天前的日期
start_date_limit = current_date - datetime.timedelta(days=30)
# 3. 获取所有股票信息
# date=current_date 确保只获取当前已经上市的股票
all_securities = get_all_securities(types=['stock'], date=current_date)
# 4. 筛选出上市时间在 [30天前, 今天] 范围内的股票
# all_securities['start_date'] 是 datetime.date 类型
new_stocks_df = all_securities[
(all_securities['start_date'] >= start_date_limit) &
(all_securities['start_date'] <= current_date)
]
target_list = new_stocks_df.index.tolist()
# 5. 进一步过滤:剔除停牌和ST股(可选,为了防止买入无法交易或风险过大的股票)
target_list = filter_basic_stock(context, target_list)
# 打印日志方便调试
log.info("今日日期: %s, 筛选出的次新股数量: %d" % (current_date, len(target_list)))
# 6. 执行交易
do_trading(context, target_list)
def filter_basic_stock(context, stock_list):
"""
过滤停牌和ST股票
"""
current_data = get_current_data()
filtered_list = []
for stock in stock_list:
# 如果未停牌 且 不是ST
if not current_data[stock].paused and not current_data[stock].is_st:
# 还可以增加过滤涨停板无法买入的逻辑,视策略需求而定
# if current_data[stock].last_price < current_data[stock].high_limit:
filtered_list.append(stock)
return filtered_list
def do_trading(context, target_list):
"""
执行调仓逻辑:等权重买入
"""
# 1. 卖出不在目标列表中的持仓
for stock in context.portfolio.positions:
if stock not in target_list:
order_target_value(stock, 0)
log.info("卖出不再是次新股的标的: %s" % stock)
# 2. 买入目标列表中的股票
if len(target_list) > 0:
# 简单的资金分配:将总资产平均分配给目标股票
# 注意:次新股数量可能波动很大,实际策略中可能需要限制最大持仓数量
position_count = len(target_list)
value_per_stock = context.portfolio.total_value / position_count
for stock in target_list:
order_target_value(stock, value_per_stock)
else:
log.info("今日无符合条件的次新股,空仓运行")
关键API说明
-
get_all_securities(types=['stock'], date=...):- 这是核心函数。它返回一个 DataFrame,其中包含
start_date(上市日期)列。 - 传入
date参数非常重要,它确保你获取的是在该日期当时存在的股票列表,避免了幸存者偏差,同时也包含了该日期刚刚上市的新股。
- 这是核心函数。它返回一个 DataFrame,其中包含
-
datetime.timedelta(days=30):- 用于计算时间差。通过
current_date - timedelta我们可以得到一个月前的日期界限。
- 用于计算时间差。通过
-
get_current_data():- 用于获取当天的实时状态,如是否停牌(
paused)或是否为ST(is_st)。新股上市初期波动大,且可能有停牌核查的情况,过滤这些状态能提高回测稳定性。
- 用于获取当天的实时状态,如是否停牌(
注意事项
- 新股涨停限制:A股新股上市初期通常会连续一字涨停。在回测中,如果使用默认的撮合机制,可能会在涨停板上买入成功(取决于回测引擎的撮合设置),但这在实盘中几乎是不可能的。建议在回测设置中开启严格的撮合模式,或者在代码中增加判断:如果
current_price == high_limit则不买入。 - 资金分配:上述代码使用了简单的等权重分配。由于新股数量在不同时期差异巨大(有时一个月只有几只,有时几十只),全仓等权分配可能导致单只股票仓位过低或过高。建议设置一个
max_stock_count(最大持仓数)来控制风险。
Q&A
Q: 为什么使用 get_all_securities 而不是 get_fundamentals?
A: get_fundamentals 主要用于查询财务数据(如市值、PE、利润等)。虽然它也能查到股票代码,但 get_all_securities 是专门用于获取证券基础信息(如上市时间、退市时间、中文名称)的API,且数据结构更适合直接进行日期比较,效率更高。
Q: 如何避免在回测中买入一字涨停的新股?
A: 可以在 filter_basic_stock 函数中增加逻辑。使用 get_current_data() 获取 last_price(最新价)和 high_limit(涨停价),如果两者相等,则将其从买入列表中剔除。
Q: 这段代码可以直接用于模拟交易吗?
A: 可以。聚宽的模拟交易环境与回测环境API兼容。但请注意,实盘中新股申购(打新)和二级市场买入次新股是两回事。此策略针对的是在二级市场买入已经上市交易的股票。