问题描述
解决方案
这是一个非常经典的基于基本面数据的选股策略。在PTrade中实现该策略主要分为三个步骤:
- 获取股票池:在盘前获取沪深300指数的成分股。
- 获取财务数据:查询这些股票的市盈率(PE)数据。
- 交易执行:筛选出符合条件(PE < 20)的股票,并进行等权重买入,同时卖出不再符合条件的股票。
策略实现思路
- initialize: 初始化设置,设定基准和手续费。
- before_trading_start: 每天开盘前运行。获取沪深300成分股,通过
get_fundamentals获取估值数据,筛选出0 < PE(TTM) < 20的股票列表。这里加上大于0的条件是为了排除亏损股(亏损股PE通常为负)。 - handle_data: 盘中运行。执行调仓逻辑,卖出不在目标列表中的持仓,对目标列表中的股票进行等权重买入。
PTrade 策略代码
def initialize(context):
"""
初始化函数,设置基准、手续费等
"""
# 设置本策略的基准为沪深300指数
set_benchmark('000300.SS')
# 设置股票交易佣金(示例:万分之三)
set_commission(commission_ratio=0.0003, min_commission=5.0, type="STOCK")
# 定义一个全局变量,用于存储当日计划买入的股票列表
g.target_stocks = []
def before_trading_start(context, data):
"""
盘前处理函数,用于获取数据和筛选股票
"""
# 1. 获取沪深300成分股代码列表
# 注意:get_index_stocks 建议在盘前或盘后调用,不要在 initialize 中调用
index_code = '000300.SS'
stocks = get_index_stocks(index_code)
if not stocks:
log.info("未获取到沪深300成分股")
g.target_stocks = []
return
# 2. 获取财务数据
# 查询 valuation 表中的 pe_ttm (滚动市盈率)
# pe_ttm 比静态市盈率更能反映近期盈利能力
q = get_fundamentals(stocks, 'valuation', ['pe_ttm'])
# 3. 筛选逻辑
if q is not None and not q.empty:
# 筛选条件:0 < PE(TTM) < 20
# 解释:PE < 20 是用户要求;PE > 0 是为了剔除亏损企业(负PE)
df_filtered = q[(q['pe_ttm'] > 0) & (q['pe_ttm'] < 20)]
# 获取筛选后的股票代码列表
g.target_stocks = df_filtered.index.tolist()
log.info("今日符合筛选条件(PE < 20)的股票数量: %s" % len(g.target_stocks))
else:
g.target_stocks = []
log.info("今日未获取到财务数据")
def handle_data(context, data):
"""
盘中运行函数,执行交易逻辑
"""
# 如果没有选出股票,则清仓(可选逻辑,视策略需求而定)
if len(g.target_stocks) == 0:
for stock in context.portfolio.positions:
order_target(stock, 0)
return
# 1. 卖出逻辑
# 遍历当前持仓,如果持仓股票不在今日的目标列表中,则卖出
for stock in context.portfolio.positions:
if stock not in g.target_stocks:
order_target(stock, 0)
log.info("卖出不再符合条件的股票: %s" % stock)
# 2. 买入逻辑
# 采用等权重分配资金
# 计算每只股票的目标持仓价值 = 当前账户总资产 / 目标股票数量
position_count = len(g.target_stocks)
target_value = context.portfolio.portfolio_value / position_count
for stock in g.target_stocks:
# 过滤停牌、退市等无法交易的股票(order函数内部有一定保护,但加上判断更严谨)
# 这里简单直接下单,PTrade回测引擎会自动处理停牌无法成交的情况
order_target_value(stock, target_value)
代码关键点解析
get_index_stocks('000300.SS'):- 这是获取指数成分股的标准API。注意代码后缀
.SS代表上交所发布的指数(沪深300由中证指数公司发布,但在PTrade中通常归类在上海市场后缀下或使用.XBHS)。
- 这是获取指数成分股的标准API。注意代码后缀
get_fundamentals:- 这是获取基本面数据的核心函数。
valuation表包含了估值数据。pe_ttm(滚动市盈率) 通常比pe_static(静态市盈率) 更具参考价值,因为它包含了最近4个季度的净利润。
order_target_value:- 这个函数非常适合用于“调仓”策略。它会自动计算需要买入或卖出的数量,使该股票的持仓市值达到指定金额。如果账户资金不足,它会自动调整。
- Python 版本兼容性:
- 代码使用了
%s格式化字符串,避免了 Python 3.6+ 的 f-string,确保在 PTrade 可能使用的旧版本 Python 环境中也能运行。
- 代码使用了
Q&A
Q: 为什么在 before_trading_start 中获取成分股而不是 initialize?
A: initialize 仅在策略开始时运行一次。指数成分股会随时间调整(通常半年调整一次),在 before_trading_start 中获取可以确保每天使用的是当天最新的成分股列表,避免回测时间较长时成分股数据失真。
Q: pe_ttm 和 pe_dynamic 有什么区别?
A: pe_ttm 是滚动市盈率,基于过去12个月的净利润计算,数据是确定的;pe_dynamic 是动态市盈率,通常基于预测或当前季度年化计算,波动较大。对于价值选股策略,通常推荐使用 pe_ttm。
Q: 如何避免每天频繁调仓?
A: 当前策略是每天检查并调仓。如果希望降低换手率,可以在 before_trading_start 中增加日期判断(例如只在月初或每周一运行筛选逻辑),或者使用 run_daily 设置定时任务来控制调仓频率。