3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
在PTrade中,筛选并排除商誉占比过高的股票主要依赖于 get_fundamentals 函数来获取财务数据。
通常我们使用 商誉占净资产的比例(商誉 / 所有者权益合计)来衡量风险。如果该比例过高(例如超过10%或20%),则意味着公司资产中“虚”的部分较多,存在商誉减值暴雷的风险。
get_fundamentals 查询 balance_statement 表中的 good_will(商誉)和 total_shareholder_equity(所有者权益合计/净资产)。NaN(空值)的填充为0,因为没有商誉的公司是安全的。商誉 / 净资产。set_universe 更新当天的交易标的。以下是一个完整的策略示例,展示了如何在每日盘前(before_trading_start)进行商誉筛选。
def initialize(context):
"""
初始化函数
"""
# 设定基准指数,例如沪深300
g.index = '000300.SS'
# 设定商誉占比阈值,这里设定为 10% (0.1)
# 如果商誉/净资产 > 10%,则排除该股票
g.goodwill_threshold = 0.10
# 初始设置股票池
set_universe(get_index_stocks(g.index))
def before_trading_start(context, data):
"""
盘前处理函数:每日开盘前筛选股票
"""
# 1. 获取待筛选的股票列表
current_stocks = get_index_stocks(g.index)
# 2. 获取财务数据
# 查询字段:good_will(商誉), total_shareholder_equity(所有者权益合计/净资产)
# date参数传入当前回测日期,获取最近发布的财报数据
q_date = context.blotter.current_dt.strftime("%Y%m%d")
df = get_fundamentals(
current_stocks,
'balance_statement',
['good_will', 'total_shareholder_equity'],
date=q_date
)
if df is not None and not df.empty:
# 3. 数据清洗
# 很多公司没有商誉,数据库中可能为NaN,将其填充为0
df['good_will'] = df['good_will'].fillna(0)
# 过滤掉净资产为0或负数的公司(资不抵债),避免除零错误或逻辑错误
df = df[df['total_shareholder_equity'] > 0]
# 4. 计算商誉占比 (商誉 / 净资产)
df['gw_ratio'] = df['good_will'] / df['total_shareholder_equity']
# 5. 筛选符合条件的股票
# 保留商誉占比小于阈值的股票
valid_df = df[df['gw_ratio'] < g.goodwill_threshold]
valid_stocks = valid_df.index.tolist()
# 6. 更新股票池
if len(valid_stocks) > 0:
set_universe(valid_stocks)
log.info("原始股票数量: %s, 排除高商誉后数量: %s" % (len(current_stocks), len(valid_stocks)))
else:
log.warning("筛选后无符合条件的股票,保持原股票池或清空")
else:
log.warning("未获取到财务数据,跳过筛选")
def handle_data(context, data):
"""
盘中运行函数
"""
# 这里可以编写具体的买卖逻辑
# 示例:简单的买入操作,仅作演示
# 此时 context.universe 已经是剔除了高商誉股票后的列表
# 获取当前股票池
universe = context.get_universe()
# 简单的示例逻辑:买入股票池中的第一只股票
if len(universe) > 0:
target = universe[0]
# 简单的仓位控制
if len(context.portfolio.positions) == 0:
order_target_value(target, context.portfolio.cash)
get_fundamentals: 这是核心API。
table='balance_statement': 商誉数据位于资产负债表。fields=['good_will', 'total_shareholder_equity']: 我们需要这两个字段来计算比例。fillna(0): 这一点非常重要。大多数公司(特别是银行、传统制造业)可能没有商誉,数据库返回 NaN。如果不填充为0,这些优质公司可能会在计算或比较时被错误排除。g.goodwill_threshold = 0.10 是一个经验值。你可以根据风险偏好调整为 0.05 (严格) 或 0.20 (宽松)。before_trading_start 中执行,确保当天的 handle_data 交易逻辑使用的是已经清洗过的安全股票池。Q1: 为什么要用“净资产”而不是“总资产”作为分母?
A1: 使用净资产(所有者权益)作为分母更为保守和严格。商誉是收购溢价产生的,如果商誉发生减值,直接扣减的是利润,最终侵蚀的是净资产。商誉占净资产比例过高,意味着一旦发生减值,对股东权益的打击是毁灭性的。当然,你也可以使用 total_assets(总资产)作为分母,只需将代码中的 total_shareholder_equity 替换为 total_assets 即可。
Q2: get_fundamentals 获取的是什么时候的数据?
A2: 在回测模式下,如果不指定 date,默认获取回测当前日期之前最近一次发布的财报数据,避免了未来函数。在代码中显式传入 date=context.blotter.current_dt 是为了确保逻辑的清晰性,PTrade底层会自动处理财报发布的滞后性(即只会获取截止到当天已公告的数据)。
Q3: 如果我想查看具体的商誉数值,怎么打印?
A3: 可以在 before_trading_start 中添加日志输出。例如:
# 打印商誉占比最高的前5名
top_risk = df.sort_values(by='gw_ratio', ascending=False).head(5)
log.info("高商誉风险股票前5名:\n%s" % top_risk[['good_will', 'gw_ratio']])