3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
这是一个非常经典的价值投资选股策略。在PTrade中实现筛选“连续三年ROE大于15%”的股票,主要涉及以下几个关键点:
get_fundamentals 函数。profit_ability(盈利能力表)中的 roe(净资产收益率)字段。report_types='4',代表筛选年报数据。以下是完整的策略代码实现:
def initialize(context):
"""
初始化函数
"""
# 设置股票池为空,稍后在 before_trading_start 中筛选
g.security = []
set_universe(g.security)
# 设置手续费(可选)
set_commission(commission_ratio=0.0003, min_commission=5.0, type="STOCK")
# 设定一个全局变量,用于记录上一次筛选的年份,避免每天重复查询大量数据
g.last_check_year = 0
def before_trading_start(context, data):
"""
盘前处理:每天开盘前运行,用于筛选股票
"""
current_dt = context.blotter.current_dt
current_year = current_dt.year
current_month = current_dt.month
# ----------------------------------------------------------------
# 1. 确定要查询的年份范围
# ----------------------------------------------------------------
# 逻辑:年报通常在次年4月30日前披露完毕。
# 如果当前月份 >= 5月,说明去年的年报已经出齐,可以查 [去年, 前年, 大前年]
# 如果当前月份 < 5月,去年的年报可能还没出,只能查 [前年, 大前年, 大大前年]
if current_month >= 5:
end_report_year = current_year - 1
else:
end_report_year = current_year - 2
start_report_year = end_report_year - 2 # 往前推2年,共3年
# ----------------------------------------------------------------
# 2. 避免每天重复计算,仅在年份变化或关键月份时重新筛选
# 这里为了演示逻辑,简化为:如果计算出的截止年份变了,就重新筛选
# ----------------------------------------------------------------
if end_report_year == g.last_check_year:
return
log.info("开始筛选 %s 至 %s 年连续三年ROE > 15%% 的股票" % (start_report_year, end_report_year))
# 获取全市场A股代码
all_stocks = get_Ashares()
# ----------------------------------------------------------------
# 3. 查询财务数据
# ----------------------------------------------------------------
# 查询盈利能力表(profit_ability)中的ROE字段
# report_types='4' 代表年报
# 注意:一次性查询全市场几千只股票的3年数据可能会比较慢或超时,
# 实际实盘中建议分批查询或缩小范围。这里为演示直接查询。
fundamentals = get_fundamentals(
security=all_stocks,
table='profit_ability',
fields='roe',
start_year=str(start_report_year),
end_year=str(end_report_year),
report_types='4'
)
selected_stocks = []
# ----------------------------------------------------------------
# 4. 处理数据并筛选
# ----------------------------------------------------------------
if fundamentals is not None:
# PTrade Python3.5环境下,按年份范围查询返回的是 Panel 对象
# Items轴是股票代码,Major轴是时间(年份),Minor轴是字段(roe)
# 如果是 Python3.11 环境,返回的可能是 MultiIndex DataFrame,需根据实际环境调整
# 这里按标准 Panel 结构处理
if hasattr(fundamentals, 'items'):
for stock in fundamentals.items:
# 获取该股票的 DataFrame (Index=日期, Column=roe)
df = fundamentals[stock]
# 过滤掉数据不全的股票(必须有3年的数据)
if len(df) < 3:
continue
# 核心筛选逻辑:判断这3年的ROE是否都大于15
# PTrade中 roe 字段通常是百分比数值(如 15.5 代表 15.5%)
# 使用 min() 函数判断最小的一年是否也大于 15
roe_values = df['roe'].values
if min(roe_values) > 15:
selected_stocks.append(stock)
log.info("筛选完成,共选出 %d 只股票" % len(selected_stocks))
# 更新全局股票池和 PTrade 股票池
g.security = selected_stocks
set_universe(g.security)
# 更新检查年份标记
g.last_check_year = end_report_year
def handle_data(context, data):
"""
盘中处理:简单的买入持有逻辑
"""
if not g.security:
return
# 简单的等权重买入逻辑
# 1. 卖出不在股票池中的股票
for stock in context.portfolio.positions:
if stock not in g.security:
order_target(stock, 0)
# 2. 买入股票池中的股票(等权重)
if len(g.security) > 0:
# 计算每只股票应该分配的资金
target_value = context.portfolio.portfolio_value / len(g.security)
for stock in g.security:
# 过滤停牌、跌停等无法买入的情况
if data[stock].is_open == 0:
continue
# 调仓到目标市值
order_target_value(stock, target_value)
时间窗口的选择 (before_trading_start):
current_month >= 5。因为根据A股规定,上市公司必须在4月30日前披露上一年度的年报。数据获取 (get_fundamentals):
table='profit_ability':ROE数据位于盈利能力表中。report_types='4':非常重要,这指定了我们只看年报。如果不加这个参数,可能会混入季报数据,导致逻辑错误(季报的ROE通常不能直接和年报ROE比较,除非做年化处理)。start_year 和 end_year:用于指定获取数据的年份区间。数据筛选:
get_fundamentals 在查询多只股票、多个年份时,返回的数据结构通常是 pandas.Panel(在PTrade Python 3.5环境中)。min(roe_values) > 15 来确保每一年的ROE都超过15%。交易执行 (handle_data):
get_fundamentals 一次性查询全市场4000+只股票的3年数据可能会比较耗时或触发接口限制。如果遇到超时问题,可以将 all_stocks 切片分批查询。roe 字段单位通常是百分比数值(例如返回 18.5 代表 18.5%),因此代码中直接与 15 比较是正确的。get_fundamentals 返回的将是 DataFrame(多级索引),此时遍历逻辑需要微调(通常使用 df.groupby(level=0) 进行遍历)。Q: 为什么不使用 get_history 来获取ROE?
A: get_history 主要用于获取行情数据(如开盘价、收盘价、成交量)。ROE属于财务数据,必须通过 get_fundamentals 获取。
Q: 如何筛选“扣非ROE”?
A: 在 get_fundamentals 中,可以将字段 roe 改为 roe_cut(净资产收益率%扣除摊薄),这通常更能反映企业主营业务的盈利能力。
Q: 策略回测时为什么5月之前买入的是前年的优绩股?
A: 这是为了严谨性。在现实世界中,直到4月底年报披露完毕前,我们无法确切知道所有公司去年的完整ROE。为了防止回测利用“未公开信息”作弊,必须等待年报披露期结束后再使用当年的数据。