问题描述
解决方案
这是一个基于 沪深300指数(000300.XSHG) 的 PE(市盈率)估值波段 交易策略。
策略逻辑
- 核心思想:价值投资,低买高卖。当指数整体估值(PE)处于历史低位时买入,处于历史高位时卖出。
- 标的:300ETF(510300.XSHG)。
- 指数PE计算方法:
- 获取沪深300所有成分股。
- 获取成分股的总市值(Market Cap)和市盈率(PE)。
- 指数PE = 成分股总市值之和 / 成分股总净利润之和。
- 注:这里采用“总市值/总净利润”法(即加权PE),比简单的“PE求平均”更准确反映指数整体估值。
- 交易信号:
- 买入信号:当 指数PE < 低估阈值(例如 11.5倍),视为低估,全仓买入。
- 卖出信号:当 指数PE > 高估阈值(例如 16.0倍),视为高估,清仓卖出。
- 持仓:在两个阈值之间时,保持原有仓位不动。
策略代码
# -*- coding: utf-8 -*-
# 导入函数库
from jqdata import *
import pandas as pd
def initialize(context):
# 1. 设定基准
set_benchmark('000300.XSHG')
# 2. 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 3. 设置手续费(股票类,买入万三,卖出万三+千一印花税)
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# --- 策略参数设置 ---
# 目标指数:沪深300
g.index_code = '000300.XSHG'
# 交易标的:300ETF
g.etf_code = '510300.XSHG'
# 估值波段阈值 (基于沪深300历史数据经验值)
# 历史低位通常在 10-12倍左右,高位在 15-17倍左右
g.pe_buy_threshold = 11.5 # 买入阈值
g.pe_sell_threshold = 16.0 # 卖出阈值
# 每天开盘时运行
run_daily(market_open, time='09:30')
def market_open(context):
# 1. 计算当前指数的加权PE
current_pe = get_index_pe(context, g.index_code)
if current_pe is None:
return
# 记录PE值到图表,方便回测查看
record(Index_PE=current_pe)
# 获取当前现金和持仓
cash = context.portfolio.available_cash
position = context.portfolio.positions[g.etf_code].total_amount
# 2. 交易逻辑
# 信号:低估 -> 买入
if current_pe < g.pe_buy_threshold:
if cash > 0:
log.info(f"当前PE: {current_pe:.2f} < 买入阈值 {g.pe_buy_threshold},执行买入")
order_value(g.etf_code, cash)
# 信号:高估 -> 卖出
elif current_pe > g.pe_sell_threshold:
if position > 0:
log.info(f"当前PE: {current_pe:.2f} > 卖出阈值 {g.pe_sell_threshold},执行卖出")
order_target(g.etf_code, 0)
# 处于中间波段 -> 持仓不动
else:
pass
# log.info(f"当前PE: {current_pe:.2f},处于持有/观望区间")
def get_index_pe(context, index_code):
"""
计算指数的加权PE (总市值 / 总净利润)
"""
# 获取指数成分股
stocks = get_index_stocks(index_code)
if not stocks:
log.error("获取指数成分股失败")
return None
# 查询成分股的市值和PE(TTM)
# valuation.market_cap 单位: 亿元
# valuation.pe_ratio: 市盈率(TTM)
q = query(
valuation.code,
valuation.market_cap,
valuation.pe_ratio
).filter(
valuation.code.in_(stocks),
valuation.pe_ratio > 0 # 过滤掉亏损股,避免计算异常,也可以选择不过滤
)
df = get_fundamentals(q)
if df is None or len(df) == 0:
return None
# 计算原理:
# 单个股票净利润 = 市值 / PE
# 指数PE = 总市值 / 总净利润
# 1. 计算每只股票的净利润 (单位:亿元)
df['net_profit'] = df['market_cap'] / df['pe_ratio']
# 2. 计算总市值和总净利润
total_market_cap = df['market_cap'].sum()
total_net_profit = df['net_profit'].sum()
# 3. 计算指数加权PE
if total_net_profit > 0:
index_pe = total_market_cap / total_net_profit
return index_pe
else:
return None
常见问题与优化建议
1. 为什么使用固定阈值(11.5 和 16.0)?
- 原因:在回测中,实时计算“过去10年的PE百分位”非常耗时,容易导致回测超时。
- 经验值:对于沪深300,历史上PE通常在 10倍到 18倍之间波动。11.5以下通常是极具性价比的底部区域,16以上风险开始积聚。
- 如何优化:你可以根据当前的市场环境调整这两个参数。例如在牛市预期强时,可以适当调高卖出阈值。
2. 如何改成“动态百分位”策略?
如果你希望策略自动计算百分位(例如:PE处于过去5年 20% 分位点买入),你需要维护一个历史PE列表。由于聚宽 initialize 中不能大量查询历史财务数据,通常的做法是:
- 使用固定阈值(如上代码)。
- 或者在本地计算好历史PE数据,作为自定义数据上传到聚宽的研究环境读取。
3. 为什么过滤掉 PE < 0 的股票?
- 代码中
valuation.pe_ratio > 0过滤了亏损股。 - 理由:亏损股的PE是负数,直接参与加权计算会抵消盈利股的利润,导致算出来的指数PE异常偏高或偏低(甚至为负)。在指数估值计算中,通常的做法是剔除亏损股,或者将亏损股的PE视为无穷大处理。
4. 适合的标的
- 该策略最适合 宽基指数(如沪深300、上证50),因为它们的盈利能力相对稳定,PE估值逻辑有效。
- 对于 成长型指数(如创业板指、科创50),PE波动范围极大(可能在30-100之间波动),需要重新设定阈值,或者改用 PEG 等指标。
Q&A
Q: 这个策略可以直接用于实盘吗?
A: 这是一个基础的择时策略模板。实盘前建议:1. 结合当前宏观环境调整阈值;2. 考虑分批建仓(例如PE每跌0.5买入20%)而不是一次性全仓,以平滑风险。
Q: 如何查看回测中的PE变化曲线?
A: 代码中包含 record(Index_PE=current_pe),回测结束后,在结果页面的图表下方,勾选 "Index_PE" 即可看到指数估值随时间变化的曲线,方便你验证买卖点是否合理。
Q: 可以换成中证500指数吗?
A: 可以。将 g.index_code 改为 '000905.XSHG',将 g.etf_code 改为 '510500.XSHG'。注意:中证500的估值中枢比沪深300高,你需要将买入阈值调整为 20-25 左右,卖出阈值调整为 35-40 左右(具体需参考中证500历史估值)。