第4章:财务数据获取:资产负债表、利润表、现金流量表数据获取、财报数据对齐与清洗

做量化投资,财务数据是绕不开的基石。

我个人习惯把这三张表比作一个人的体检报告:资产负债表看家底,利润表看赚钱能力,现金流量表看血液是否流通。缺了哪一张,你都无法判断一家公司的真实健康状况。

4.1 三张表的核心逻辑

先别急着写代码。咱们得先搞清楚,这三张表到底在说什么。

  • 资产负债表:某个时间点的快照。资产 = 负债 + 所有者权益。说白了,就是公司有多少钱、欠多少钱、股东还剩多少。
  • 利润表:一段时间的经营成果。收入 - 成本 - 费用 = 净利润。你想想看,如果利润表好看但现金流是负的,那大概率是纸面富贵。
  • 现金流量表:真金白银的进出。经营、投资、筹资三大活动。我见过太多公司利润表漂亮,结果现金流一塌糊涂,最后暴雷。

核心要点:三张表必须联动看。单看一张表,很容易被误导。

4.2 数据获取:从哪里拿?

国内常用的财务数据源,我整理了一下:

数据源 特点 推荐指数
Tushare 免费,数据全,但有限流 ⭐⭐⭐⭐
AkShare 开源,接口丰富,适合个人 ⭐⭐⭐⭐⭐
Wind/Choice 付费,专业,机构常用 ⭐⭐⭐⭐
东方财富API 免费,但需要解析 ⭐⭐⭐

我个人最常用的是 AkShare。为什么?因为它免费、接口稳定,而且文档写得还算清楚。我在项目中遇到过几次Tushare限流导致数据拉取失败的情况,后来切到AkShare就再没出过问题。

4.3 实战:用Python拉取三张表

咱们直接上代码。以AkShare为例,拉取贵州茅台2023年的财报数据。

import akshare as ak
import pandas as pd

# 1. 资产负债表
balance_sheet = ak.stock_financial_balance_sheet_by_report_em(
    symbol="600519",
    start_date="20230101",
    end_date="20231231"
)
print("资产负债表列名:", balance_sheet.columns.tolist())

# 2. 利润表
income_statement = ak.stock_financial_profit_by_report_em(
    symbol="600519",
    start_date="20230101",
    end_date="20231231"
)
print("利润表列名:", income_statement.columns.tolist())

# 3. 现金流量表
cash_flow = ak.stock_financial_cash_flow_by_report_em(
    symbol="600519",
    start_date="20230101",
    end_date="20231231"
)
print("现金流量表列名:", cash_flow.columns.tolist())

小提示:拉取数据时,建议加上 try-except 处理网络异常。我曾经在凌晨跑批量任务时,因为网络抖动导致整个脚本中断,损失了3个小时的数据。

4.4 财报数据对齐:最头疼的一步

数据拉下来了,但你会发现一个问题:三张表的日期对不上

为什么会这样?因为资产负债表是时点数据(比如2023-12-31),而利润表和现金流量表是期间数据(比如2023-01-01到2023-12-31)。如果你直接合并,数据会错位。

我的对齐策略是这样的:

  1. 统一报告期:把三张表的报告期都转为 YYYY-MM-DD 格式,资产负债表取期末日期,利润表和现金流量表取报告期结束日期。
  2. 按股票代码+报告期合并:用 pd.merge()symbolreport_date 为键进行合并。
  3. 处理多期数据:如果拉取了多个季度,建议用 pivot_table 把数据转成宽表。
# 对齐示例
def align_financial_data(balance, income, cashflow):
    # 统一列名
    balance['report_date'] = pd.to_datetime(balance['END_DATE'])
    income['report_date'] = pd.to_datetime(income['REPORT_DATE'])
    cashflow['report_date'] = pd.to_datetime(cashflow['REPORT_DATE'])
    
    # 合并
    merged = pd.merge(balance, income, on=['symbol', 'report_date'], how='outer')
    merged = pd.merge(merged, cashflow, on=['symbol', 'report_date'], how='outer')
    
    return merged

aligned_data = align_financial_data(balance_sheet, income_statement, cash_flow)
print("对齐后数据形状:", aligned_data.shape)

避坑指南:我曾经在合并时忘记处理 how='outer',结果导致部分数据丢失。后来检查发现,有些公司会在不同时间点发布不同报表,导致日期不完全匹配。建议先用 isnull() 检查缺失情况。

4.5 数据清洗:让数据变得可用

原始数据通常很脏。我总结了几条清洗规则:

  • 缺失值处理:财务数据中的空值,通常用0填充(因为财报中空值往往表示该科目不存在)。但要注意,有些科目如“研发费用”为空,可能意味着公司没有披露,而不是为0。
  • 数据类型转换:金额字段从字符串转为浮点数。注意单位,有些接口返回的是元,有些是万元。
  • 异常值过滤:比如资产负债率超过100%的,或者净利润突然暴涨1000%的,需要标记出来人工核查。
  • 去重:同一个报告期可能出现多条记录,保留最新的一条即可。
def clean_financial_data(df):
    # 1. 填充缺失值
    df = df.fillna(0)
    
    # 2. 转换金额字段(假设列名以'AMT'结尾)
    amt_cols = [col for col in df.columns if col.endswith('AMT')]
    for col in amt_cols:
        df[col] = pd.to_numeric(df[col], errors='coerce')
    
    # 3. 去重
    df = df.drop_duplicates(subset=['symbol', 'report_date'], keep='last')
    
    # 4. 标记异常值
    df['is_abnormal'] = (df['DEBT_RATIO'] > 1.0) | (df['NET_PROFIT_GROWTH'] > 10)
    
    return df

cleaned_data = clean_financial_data(aligned_data)
print("清洗后数据量:", len(cleaned_data))

4.6 知识体系总览

下面这张图,是我自己整理的三张表获取与处理的全流程。你照着这个框架走,基本不会出错。

财务数据获取与处理全流程 数据源 Tushare / AkShare / Wind 资产负债表 时点数据 利润表 期间数据 现金流量表 期间数据 步骤1:数据对齐 统一报告期 → 按symbol+report_date合并 步骤2:数据清洗 缺失值填充 → 类型转换 → 去重 → 异常值标记

4.7 一些心里话

财务数据获取这件事,看起来简单,但坑真的不少。我刚开始做量化的时候,花了两周时间才把三张表对齐,结果发现数据单位不一致,又重来了一遍。

嗯,这里要注意:永远不要相信原始数据。拿到数据后,先随机抽几家公司,手动核对一下财报原文。我吃过这个亏,所以现在养成了这个习惯。

另外,如果你用的是免费数据源,记得做好缓存。我一般会把拉下来的数据存到本地CSV或SQLite里,避免重复请求。既省时间,又不会被封IP。

总结一句话:财务数据获取,三分靠代码,七分靠清洗。把对齐和清洗做好,后面的分析才能站得住脚。


公众号:蓝海数据掘金营,微信deep3321