问题描述
解决方案
这是一个基于 JoinQuant (聚宽) 平台的次新股(上市不久的股票)交易策略。
策略思路
该策略的核心逻辑是捕捉小盘次新股的溢价效应。次新股由于套牢盘少、股本扩张潜力大,在A股市场中常被资金追捧,尤其是流通市值较小的次新股。
- 选股范围:选择上市时间在 20天到150天 之间的股票(避开上市初期的连续一字涨停板,选择开板后经过一定换手的次新股)。
- 过滤条件:
- 剔除停牌股票。
- 剔除ST、*ST股票。
- 剔除涨停或跌停的股票(防止无法成交)。
- 排序因子:按照流通市值从小到大排序(小盘股弹性更大)。
- 持仓设置:持仓 5 只股票,等权重买入。
- 调仓频率:按日调仓。
策略代码
# -*- coding: utf-8 -*-
# 导入聚宽函数库
import jqdata
import datetime
def initialize(context):
"""
初始化函数,设定基准、手续费、滑点等
"""
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# 设定股票类每笔交易时的手续费
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 设定全局变量
g.stock_num = 5 # 持仓股票数量
# 每天开盘前运行
run_daily(before_market_open, time='09:00')
# 每天开盘时运行
run_daily(market_open, time='09:30')
def before_market_open(context):
"""
开盘前选股逻辑
"""
# 获取当前日期
current_date = context.current_dt.date()
# 1. 获取所有股票信息
all_securities = get_all_securities(types=['stock'], date=current_date)
# 2. 筛选上市时间在 20天 到 150天 之间的次新股
# 避开上市最初期的连续一字板(通常难以买入且风险极高),选择开板后的次新股
sub_new_stocks = []
for code, row in all_securities.iterrows():
start_date = row['start_date']
days_listed = (current_date - start_date).days
if 20 < days_listed < 150:
sub_new_stocks.append(code)
if not sub_new_stocks:
g.target_list = []
return
# 3. 过滤停牌和ST股票
current_data = get_current_data()
sub_new_stocks = [stock for stock in sub_new_stocks
if not current_data[stock].paused
and not current_data[stock].is_st
and 'ST' not in current_data[stock].name
and '*' not in current_data[stock].name]
if not sub_new_stocks:
g.target_list = []
return
# 4. 按流通市值从小到大排序,选取前 g.stock_num 只
# 查询财务数据:流通市值 (circulating_market_cap)
q = query(
valuation.code
).filter(
valuation.code.in_(sub_new_stocks)
).order_by(
valuation.circulating_market_cap.asc()
).limit(
g.stock_num * 2 # 多取一些备用,防止涨跌停无法买入
)
df = get_fundamentals(q, date=current_date)
if len(df) > 0:
g.target_list = list(df['code'])
else:
g.target_list = []
log.info("今日备选次新股: %s" % g.target_list)
def market_open(context):
"""
交易逻辑
"""
target_list = g.target_list
current_data = get_current_data()
# 1. 卖出不在目标列表中的股票
for stock in context.portfolio.positions:
if stock not in target_list:
# 检查是否跌停,跌停无法卖出
if current_data[stock].last_price > current_data[stock].low_limit:
order_target_value(stock, 0)
log.info("卖出不再符合条件的股票: %s" % stock)
# 2. 买入目标股票
# 计算每只股票的目标资金
if len(target_list) > 0:
# 实际上我们只持有 g.stock_num 只,但 target_list 可能更多(备选)
# 我们需要遍历 target_list,直到买满 g.stock_num 只或者资金用完
position_count = len(context.portfolio.positions)
target_cash_per_stock = context.portfolio.total_value / g.stock_num
for stock in target_list:
# 如果持仓已满,停止买入
if len(context.portfolio.positions) >= g.stock_num and stock not in context.portfolio.positions:
break
# 过滤涨停的股票(无法买入)
if current_data[stock].last_price >= current_data[stock].high_limit:
continue
# 过滤停牌的股票
if current_data[stock].paused:
continue
# 调仓到目标金额
order_target_value(stock, target_cash_per_stock)
def after_market_close(context):
pass
策略要点解析
-
上市时间筛选 (
days_listed):- 代码中使用了
20 < days_listed < 150。 - 原因:新股上市后的前几周通常会有连续的涨停板(一字板),此时普通投资者很难买入,且估值泡沫极大。选择上市20天后,通常意味着一字板已经打开,市场开始进行换手和博弈,此时介入机会更多。
- 代码中使用了
-
小市值因子 (
circulating_market_cap):- 代码使用了
valuation.circulating_market_cap.asc()进行排序。 - 原因:在次新股板块中,流通盘越小,主力资金拉升所需的资金量越少,因此往往弹性更好,容易产生超额收益。
- 代码使用了
-
涨跌停处理:
- 在
market_open中,使用了get_current_data()获取当天的涨跌停价格。 - 如果股票涨停,跳过买入(因为买不进)。
- 如果股票跌停,跳过卖出(因为卖不出)。
- 在
-
风险提示:
- 次新股波动极大,该策略属于高风险高收益策略。
- 在市场行情低迷时,次新股往往是杀跌的主力军,回撤可能较大。建议在实盘中配合大盘择时(如大盘均线之上才交易)使用。
Q&A
Q: 如何修改策略以交易刚刚上市几天的股票?
A: 修改 before_market_open 函数中的天数判断。将 if 20 < days_listed < 150: 改为 if 0 < days_listed < 20:。但请注意,上市前5天不设涨跌幅限制(科创板/创业板),且主板新股初期通常一字涨停,回测成交可能与实盘有较大偏差。
Q: 为什么使用流通市值而不是总市值?
A: 对于次新股,大量的原始股处于限售期。真正能在市场上交易的只有流通股。流通市值更能反映当前市场上该股票的供需关系和炒作所需的资金体量。
Q: 这个策略适合大资金吗?
A: 不适合。该策略专门针对小盘股,流动性有限。如果资金量过大,买入和卖出都会产生较大的冲击成本(滑点),导致收益大幅下降。