🚀 AI 一键生成 joinquant 策略代码

JoinQuant 平台如何计算 Altman Z-Score 识别财务风险?

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

问题描述

如何编写计算 Altman Z-Score 的代码来评估破产风险?

解决方案

Altman Z-Score(阿特曼Z分模型)是一种通过财务比率来评估公司破产风险的经典模型。在聚宽(JoinQuant)平台上,我们可以通过 get_fundamentals 函数获取财务数据(资产负债表、利润表)和市值数据来计算该指标。

Altman Z-Score 公式

标准公式(适用于上市公司):
$$Z = 1.2X_1 + 1.4X_2 + 3.3X_3 + 0.6X_4 + 1.0X_5$$

其中:

  • $X_1 = \text{营运资本} / \text{总资产} = (\text{流动资产} - \text{流动负债}) / \text{总资产}$
  • $X_2 = \text{留存收益} / \text{总资产}$
  • $X_3 = \text{息税前利润 (EBIT)} / \text{总资产}$
  • $X_4 = \text{股权市场价值} / \text{总负债}$
  • $X_5 = \text{销售收入} / \text{总资产}$

代码实现

以下是一个完整的策略代码示例,包含一个计算 Z-Score 的通用函数。该代码可以在聚宽的回测或研究环境中运行。

注意单位换算:聚宽的财务报表数据(如总资产)单位通常为,而市值数据(valuation.market_cap)单位为亿元。在计算 $X_4$ 时必须统一单位。

# -*- coding: utf-8 -*-
from jqdata import *
import pandas as pd

def initialize(context):
    # 设定基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    # 过滤掉order系列API产生的比error级别低的log
    log.set_level('order', 'error')
    
    # 每天运行一次计算
    run_daily(calculate_and_print_z_score, 'open')

def calculate_and_print_z_score(context):
    """
    计算并打印股票池的 Altman Z-Score
    """
    # 示例:选取沪深300成分股的前10只作为演示
    security_list = get_index_stocks('000300.XSHG')[:10]
    
    # 获取计算所需的财务和市值数据
    # 注意:为了避免未来函数,get_fundamentals 在回测中默认获取昨日及之前的数据
    q = query(
        valuation.code,
        valuation.market_cap,             # 市值 (亿元)
        balance.total_assets,             # 总资产 (元)
        balance.total_liability,          # 总负债 (元)
        balance.total_current_assets,     # 流动资产 (元)
        balance.total_current_liability,  # 流动负债 (元)
        balance.retained_profit,          # 留存收益 (元)
        income.EBIT,                      # 息税前利润 (元)
        income.operating_revenue          # 营业收入 (元)
    ).filter(
        valuation.code.in_(security_list)
    )
    
    df = get_fundamentals(q, date=context.previous_date)
    
    if df is None or len(df) == 0:
        log.info("未获取到财务数据")
        return

    # --- 开始计算 Z-Score ---
    
    # 1. 计算中间变量 (处理除数为0的情况,虽然财务数据通常不为0,但需防范)
    # 营运资本 = 流动资产 - 流动负债
    working_capital = df['total_current_assets'] - df['total_current_liability']
    
    # X1: 营运资本 / 总资产
    df['X1'] = working_capital / df['total_assets']
    
    # X2: 留存收益 / 总资产
    df['X2'] = df['retained_profit'] / df['total_assets']
    
    # X3: EBIT / 总资产
    df['X3'] = df['EBIT'] / df['total_assets']
    
    # X4: 股权市场价值 / 总负债
    # 注意:market_cap 单位是亿元,需要乘以 10^8 转换为元
    df['X4'] = (df['market_cap'] * 100000000) / df['total_liability']
    
    # X5: 销售收入 / 总资产
    df['X5'] = df['operating_revenue'] / df['total_assets']
    
    # 2. 计算最终 Z-Score
    # Z = 1.2*X1 + 1.4*X2 + 3.3*X3 + 0.6*X4 + 1.0*X5
    df['Z_Score'] = 1.2 * df['X1'] + 1.4 * df['X2'] + 3.3 * df['X3'] + 0.6 * df['X4'] + 1.0 * df['X5']
    
    # --- 结果展示与分析 ---
    
    log.info("Altman Z-Score 计算结果 (前5只):")
    for index, row in df.head(5).iterrows():
        code = row['code']
        z_score = row['Z_Score']
        
        # 简单的风险评级判断
        risk_level = ""
        if z_score > 2.99:
            risk_level = "安全区 (Safe)"
        elif 1.81 < z_score <= 2.99:
            risk_level = "灰色区域 (Grey Zone)"
        else:
            risk_level = "危险区 (Distress)"
            
        log.info(f"股票: {code}, Z-Score: {z_score:.4f}, 评级: {risk_level}")

    # 可以在此处添加交易逻辑,例如卖出 Z-Score < 1.81 的股票
    # ...

结果解读

Altman Z-Score 的经典判别标准如下:

  1. Z > 2.99安全区。公司财务状况良好,破产风险极低。
  2. 1.81 < Z ≤ 2.99灰色区域。财务状况不稳定,需要密切关注。
  3. Z ≤ 1.81危险区。公司面临较大的财务困境,破产可能性较高。

关键点说明

  1. 数据单位:代码中特别处理了 valuation.market_cap,将其从“亿元”转换为“元”,以匹配资产负债表中的数据单位。
  2. 数据获取:使用了 get_fundamentals 获取基本面数据。在回测中,为了避免未来函数,通常使用 context.previous_date 或默认日期(即回测日期的前一天可见的数据)。
  3. 适用范围:该模型最初是针对制造业上市公司设计的。对于非制造业(如服务业)或新兴行业,参数权重可能需要调整(例如 Altman Z'' 模型),但上述代码使用的是最通用的原始版本。

Q&A

Q1: 为什么计算出的 Z-Score 有些非常高或非常低?
A1: 某些行业(如轻资产行业或科技股)可能因为总资产较低而导致 X3、X5 偏高,或者因为市值极高导致 X4 极大。Z-Score 最适合用于传统的重资产制造业评估。对于银行、券商等金融行业,由于其资产负债结构的特殊性,Z-Score 通常不适用。

Q2: 如何在研究环境(Notebook)中使用这段代码?
A2: 在研究环境中,只需将 get_fundamentals 中的 date 参数指定为具体的日期字符串(如 '2023-01-01'),并移除 context 相关的调用即可直接运行数据查询和计算逻辑。

Q3: 代码中的 EBIT 是如何获取的?
A3: 聚宽的 income 表中直接提供了 EBIT(息税前利润)字段。如果某些早期数据缺失该字段,可以用 利润总额 + 利息费用 来近似替代。