🚀 AI 一键生成 ptrade 策略代码

PTrade 财务风控:如何剔除高商誉占比的股票?

ptrade | 发布时间: 2025/12/30
以下内容由 EasyQuant 生成。

问题描述

怎么筛选出商誉占比过高的股票并进行排除?

解决方案

在PTrade中,筛选并排除商誉占比过高的股票主要依赖于 get_fundamentals 函数来获取财务数据。

通常我们使用 商誉占净资产的比例(商誉 / 所有者权益合计)来衡量风险。如果该比例过高(例如超过10%或20%),则意味着公司资产中“虚”的部分较多,存在商誉减值暴雷的风险。

实现思路

  1. 获取股票池:确定要筛选的范围(如沪深300、全A股等)。
  2. 获取财务数据:使用 get_fundamentals 查询 balance_statement 表中的 good_will(商誉)和 total_shareholder_equity(所有者权益合计/净资产)。
  3. 数据清洗:将商誉为 NaN(空值)的填充为0,因为没有商誉的公司是安全的。
  4. 计算比例:计算 商誉 / 净资产
  5. 过滤:保留比例小于设定阈值(如 0.1)的股票,剔除高商誉股票。
  6. 更新股票池:使用 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)

代码关键点解析

  1. get_fundamentals: 这是核心API。
    • table='balance_statement': 商誉数据位于资产负债表。
    • fields=['good_will', 'total_shareholder_equity']: 我们需要这两个字段来计算比例。
  2. fillna(0): 这一点非常重要。大多数公司(特别是银行、传统制造业)可能没有商誉,数据库返回 NaN。如果不填充为0,这些优质公司可能会在计算或比较时被错误排除。
  3. 阈值设定: g.goodwill_threshold = 0.10 是一个经验值。你可以根据风险偏好调整为 0.05 (严格) 或 0.20 (宽松)。
  4. 执行时机: 放在 before_trading_start 中执行,确保当天的 handle_data 交易逻辑使用的是已经清洗过的安全股票池。

Q&A

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']])