问题描述
解决方案
基于盈利收益率(Earnings Yield, EY)的选股策略是一种经典的价值投资策略。
策略原理
盈利收益率是市盈率(P/E Ratio)的倒数,即:
$$ EY = \frac{E}{P} = \frac{1}{PE} $$
- 逻辑:盈利收益率越高,意味着投资者以同样的价格买入了更多的盈利能力,或者以更低的价格买入了同样的盈利。这通常代表股票被低估(Value)。
- 操作:在股票池中,选取市盈率(PE)最低(且大于0)的股票,即等同于选取盈利收益率最高的股票。
策略设计
- 股票池:沪深300指数成分股(确保流动性和基本面相对稳定)。
- 选股因子:市盈率(PE_TTM)。
- 过滤条件:
- 剔除停牌、ST股票。
- 剔除上市不满60天的次新股。
- 剔除市盈率为负(亏损)的股票。
- 排序与买入:按市盈率从小到大排序(即盈利收益率从大到小),选取排名前20只股票。
- 调仓频率:按月调仓(每月第一个交易日)。
- 仓位管理:等权重买入。
策略代码实现
# -*- 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')
# 设定股票类每笔交易时的手续费
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 设定全局变量
g.stock_num = 20 # 持仓股票数量
g.index_security = '000300.XSHG' # 股票池:沪深300
# 设定按月运行,每月第一个交易日开盘后运行
run_monthly(rebalance, monthday=1, time='09:30')
def rebalance(context):
"""
调仓主函数
"""
# 1. 获取股票池(沪深300)
target_list = get_index_stocks(g.index_security, date=context.previous_date)
# 2. 过滤掉 停牌、ST、退市、次新股
target_list = filter_basic_stock(context, target_list)
# 3. 获取财务数据并进行排序(核心逻辑:低PE = 高盈利收益率)
# 查询 PE_TTM (市盈率-动态)
q = query(
valuation.code,
valuation.pe_ratio
).filter(
valuation.code.in_(target_list),
valuation.pe_ratio > 0 # 剔除亏损股(PE为负)
).order_by(
valuation.pe_ratio.asc() # 按PE从小到大排序,即盈利收益率从大到小
)
# 获取查询结果
df = get_fundamentals(q, date=context.previous_date)
# 取前 N 只股票
if len(df) > 0:
buy_list = list(df['code'][:g.stock_num])
else:
buy_list = []
# 4. 执行交易
trade(context, buy_list)
def filter_basic_stock(context, stock_list):
"""
基础过滤器:剔除停牌、ST、次新股
"""
current_data = get_current_data()
final_list = []
for stock in stock_list:
# 剔除停牌和已退市
if current_data[stock].paused:
continue
# 剔除ST
if current_data[stock].is_st:
continue
# 剔除上市不满60天的次新股
info = get_security_info(stock)
if not info or (context.current_dt.date() - info.start_date).days < 60:
continue
final_list.append(stock)
return final_list
def trade(context, buy_list):
"""
交易执行函数
"""
# 获取当前持仓
current_holdings = list(context.portfolio.positions.keys())
# 1. 卖出不在买入列表中的股票
for stock in current_holdings:
if stock not in buy_list:
order_target_value(stock, 0)
# log.info(f"卖出 {stock}")
# 如果没有需要买入的股票,直接返回
if len(buy_list) == 0:
return
# 2. 买入/调仓
# 计算每只股票的目标资金(等权重)
# 注意:这里使用 total_value 可能会导致满仓波动,实际操作中可预留少量现金
position_per_stk = context.portfolio.total_value / len(buy_list)
for stock in buy_list:
order_target_value(stock, position_per_stk)
# log.info(f"买入/调整 {stock} 到目标市值 {position_per_stk}")
代码逻辑解析
-
选股逻辑 (
rebalance函数):- 我们使用了
get_fundamentals查询valuation表。 valuation.pe_ratio代表市盈率(TTM)。valuation.pe_ratio > 0:盈利收益率策略通常只针对盈利企业,因此剔除亏损股。order_by(valuation.pe_ratio.asc()):将市盈率从小到大排序。数学上,$\frac{1}{PE}$ 最大等同于 $PE$ 最小(在 $PE>0$ 的前提下)。
- 我们使用了
-
过滤逻辑 (
filter_basic_stock函数):- 使用
get_current_data()获取当天的停牌和ST状态,避免买入无法交易或风险过大的股票。 - 使用
get_security_info()获取上市日期,剔除次新股,因为次新股的估值通常不稳定。
- 使用
-
交易逻辑 (
trade函数):- 先卖出:遍历当前持仓,如果持仓股票不在新的选股列表中,则清仓。
- 后买入:计算每只股票应分配的资金(总资产 / 选股数量),使用
order_target_value自动调整仓位。如果已持有该股且价格上涨,会自动卖出部分以保持权重;如果未持有,则买入。
常见问题 Q&A
Q: 为什么不直接使用 1/PE 计算盈利收益率?
A: 在数据库查询层面,直接对 PE 进行升序排序(Ascending)的效果与计算 1/PE 后进行降序排序(Descending)的效果是完全一样的,且直接查询 PE 效率更高,代码更简洁。
Q: 这个策略适合什么市场环境?
A: 盈利收益率策略属于典型的价值策略。它通常在市场震荡期或熊市末期表现较好,因为这类股票具有较高的安全边际。在疯狂的牛市或成长股主导的行情中,该策略可能会跑输大盘。
Q: 如何改进这个策略?
A:
- 结合成长性:低 PE 可能意味着公司陷入困境(价值陷阱)。可以结合 PEG 指标(市盈率相对盈利增长比率)或 ROE(净资产收益率)来筛选既便宜又赚钱的公司。
- 行业中性化:不同行业的 PE 水平差异很大(如银行股 PE 常年很低,科技股 PE 很高)。可以在每个行业内分别选取低 PE 股票,避免持仓过于集中在银行地产板块。
- 格雷厄姆选股:结合 PB(市净率),例如要求 $PE \times PB < 22.5$。