3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
这是一个非常经典的事件驱动策略(Event-Driven Strategy),在量化交易中通常被称为盈余公告后漂移策略(PEAD, Post-Earnings Announcement Drift)。
该策略的核心逻辑是:当公司发布好于预期的财报(如净利润大幅增长)后,股价往往不会一步到位,而是在接下来的几天或几周内持续上涨。
以下是一个完整的 JoinQuant 策略代码。你可以直接复制到聚宽的回测环境中运行。
# -*- coding: utf-8 -*-
from jqdata import *
def initialize(context):
"""
初始化函数,设定基准、手续费、全局变量等
"""
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# --- 策略参数设置 ---
g.growth_threshold = 30.0 # 净利润同比增长率阈值(%)
g.pe_max = 60 # 最大市盈率
g.pe_min = 0 # 最小市盈率
g.max_hold_days = 20 # 最大持仓天数
g.max_stocks = 10 # 最大持仓股票数量
# 记录持仓天数的字典 {security: days}
g.hold_days = {}
# 每天开盘前运行
run_daily(before_market_open, time='before_open')
# 每天开盘时运行
run_daily(market_open, time='open')
def before_market_open(context):
"""
开盘前筛选股票
"""
# 获取当前回测日期的前一个交易日
# 我们寻找昨天发布财报的股票,今天开盘买入
prev_date = context.previous_date
# 1. 查询财务数据
# 查询条件:
# - 昨天发布的财报 (pubDate == prev_date)
# - 净利润同比增长率 > 阈值
q = query(
indicator.code,
indicator.inc_net_profit_year_on_year, # 净利润同比增长率
valuation.pe_ratio
).filter(
indicator.pubDate == prev_date,
indicator.inc_net_profit_year_on_year > g.growth_threshold,
valuation.pe_ratio > g.pe_min,
valuation.pe_ratio < g.pe_max
).order_by(
indicator.inc_net_profit_year_on_year.desc() # 按增长率降序排列
)
df = get_fundamentals(q)
# 2. 过滤 ST、停牌、退市股票
current_data = get_current_data()
target_list = []
if not df.empty:
raw_list = list(df['code'])
for stock in raw_list:
# 过滤 ST 和 停牌
if not current_data[stock].is_st and \
not current_data[stock].paused and \
'退' not in current_data[stock].name:
target_list.append(stock)
# 限制每日买入候选名单长度,避免资金过于分散
g.buy_list = target_list[:5]
# 更新持仓天数
for stock in list(g.hold_days.keys()):
g.hold_days[stock] += 1
def market_open(context):
"""
开盘交易逻辑
"""
# --- 卖出逻辑 ---
# 遍历当前持仓
for stock in list(context.portfolio.positions.keys()):
# 如果持仓天数超过设定值,或者股票停牌/ST(风控),则卖出
days_held = g.hold_days.get(stock, 0)
should_sell = False
# 条件1: 持仓时间到期
if days_held >= g.max_hold_days:
should_sell = True
log.info("股票 %s 持仓达到 %d 天,止盈/止损卖出" % (stock, days_held))
# 条件2: 也可以加入止损逻辑,例如亏损超过10%卖出
# cost = context.portfolio.positions[stock].avg_cost
# price = context.portfolio.positions[stock].price
# if price < cost * 0.9:
# should_sell = True
if should_sell:
order_target_value(stock, 0)
if stock in g.hold_days:
del g.hold_days[stock]
# --- 买入逻辑 ---
# 计算当前还可以买入多少只股票
current_positions_count = len(context.portfolio.positions)
available_slots = g.max_stocks - current_positions_count
if available_slots > 0 and len(g.buy_list) > 0:
# 计算每只股票分配的资金
# 注意:这里简单处理,用可用资金除以剩余仓位。实际中可能需要更复杂的资金管理。
cash = context.portfolio.available_cash
if cash > 0:
# 避免资金过小无法买入
cash_per_stock = cash / available_slots
for stock in g.buy_list:
if current_positions_count >= g.max_stocks:
break
if stock not in context.portfolio.positions:
order_value(stock, cash_per_stock)
# 记录买入,初始化持仓天数为0
g.hold_days[stock] = 0
current_positions_count += 1
log.info("财报利好,买入: %s" % stock)
def after_code_changed(context):
"""
模拟交易中更新代码后执行
"""
g.growth_threshold = 30.0
g.max_hold_days = 20
获取财报发布日期 (indicator.pubDate):
get_fundamentals 查询中的 indicator.pubDate 字段代表财报的实际公告日期。filter(indicator.pubDate == context.previous_date):这行代码的意思是“筛选出昨天刚刚发布财报的股票”。因为我们在回测中通常是在 T+1 日(今天)根据 T 日(昨天)晚间发布的消息进行交易。财务指标选择 (indicator.inc_net_profit_year_on_year):
inc_net_profit_year_on_year(归属于母公司所有者的净利润同比增长率)。这是衡量公司业绩增长最直接的指标。inc_revenue_year_on_year(营业收入增长率)或 roe(净资产收益率)等。持仓周期管理 (g.hold_days):
g.hold_days 来记录每只股票持有了多少天。before_market_open 中每天加 1。market_open 中检查,如果超过 g.max_hold_days 就卖出。这是一种简单的时间止盈止损策略。防坑指南(未来函数):
get_fundamentals 默认查询的是 date 参数指定日期当时可见的数据。context.previous_date 作为筛选条件,模拟的是在今天开盘前,看到了昨天收盘后发布的公告,因此不存在未来函数。如果你想进一步提高策略的收益风险比,可以考虑以下优化方向:
Q: 为什么使用 indicator.pubDate 而不是 statDate?
A: statDate 是财报的统计截止日期(如 2023-12-31),而 pubDate 是财报实际对外公布的日期(如 2024-03-15)。我们在做交易时,必须等到信息公开(pubDate)后才能根据该信息交易。如果使用 statDate 进行回测,会导致严重的“未来函数”问题(即在 1 月份就用到了 3 月份才发布的数据)。
Q: 这个策略适合什么频率的回测?
A: 适合 按天 (Daily) 回测。因为财报数据是按天更新的,盘中分钟级别的波动对财报选股逻辑影响较小。
Q: 为什么有时候选不出股票?
A: 财报发布有季节性。上市公司通常在 4 月(年报+一季报)、8 月(中报)、10 月(三季报)密集发布财报。在其他月份(如 5、6、7 月),可能连续很多天都没有公司发布财报,这是正常现象。