
简介本资源为1991年至2024年6月沪深北上市公司财务指标现金流分析数据包面向金融研究、量化投资、会计实证与论文写作人群可解决长周期现金流指标获取难、跨软件格式不兼容的问题。压缩包内共1个docx文件约51KB以文档形式给出百度网盘数据链接配套Excel与dta两种数据文件及字段说明便于SPSS、Stata、Python等不同工具直接读取分析。数据为季度频率覆盖全部A股主板、中小企业板、创业板、科创板公司字段涵盖净利润现金净含量、营业收入现金含量、营业利润现金净含量及其TTM值还包括筹资活动债权人及股东现金净流量、折旧摊销、公司现金流、股权现金流、企业自由现金流、全部现金回收率、营运指数、现金适合比率、现金再投资比率、资本支出与折旧摊销比等派生指标并按行业代码与行业名称分类。目前已有144人学习适合做现金流质量、盈余含金量与自由现金流相关实证研究。1. 上市公司现金流分析数据集为什么值得从1991年一路拉到2024年6月做基本面量化的人迟早会撞上一份名字很长的压缩包——上市公司财务指标现金流分析1991-202406的网盘链接.docx。它本质是一份跨越三十多年的A股财务长面板终点是2024年6月30日的半年报起点是1991年那批数量极少、口径原始的早期样本。很多人拿到它第一反应是直接跑因子结果被前导零丢掉的股票代码、按万元和元混着写的报表、以及1998年以前大面积为空的现金流字段绊住。真正值钱的地方在于现金流的连续性。净利润可以靠会计估计调整营业收入可以靠赊销堆出来但经营活动产生的现金流量净额要和银行流水对得上长期偏离的样本几乎都会在后续年份暴雷。把1991到2024年6月这条线拉全能做的事包括验证净现比在长周期上的均值回复、观察不同行业收现比的季节性、用现金流正负号组合判断企业所处的生命周期阶段。适合谁看做多因子选股的研究员、做信用风险预警的风控工程师、以及需要给财务舞弊模型准备特征的数据开发。前提是你愿意花半天时间把这份数据从网盘落地成本地可信表而不是把它当成开箱即用的CSV。2. 从「网盘链接.docx」到本地可信数据下载件校验与字段落地网盘分发的数据包有个共同特征真正的地址藏在一个docx里正文只有两三行字链接、提取码、解压密码、甚至校验值混在一起。直接复制粘贴经常漏掉尾部字符或者把中文全角括号带进URL。正确姿势是用脚本把文档内容整体抽出来再做正则匹配而不是肉眼抠。2.1 用 python-docx 提取文档里的下载地址与校验信息发布方习惯把链接放在段落里偶尔也会塞进表格甚至文本框。python-docx 默认只能拿到段落和表格文本框内容需要走底层XML。下面这段把三种情况都覆盖了。from docx import Document import re path 上市公司财务指标现金流分析1991-202406的网盘链接.docx doc Document(path) # 1) 普通段落 text \n.join(p.text for p in doc.paragraphs) # 2) 表格单元格 for tbl in doc.tables: for row in tbl.rows: for cell in row.cells: text \n cell.text # 3) 文本框w:txbxContentpython-docx 不直接暴露 from docx.oxml.ns import qn for node in doc.element.body.iter(qn(w:txbxContent)): for t in node.iter(qn(w:t)): text \n (t.text or ) # 链接里常见全角括号、中文标点正则里要排除 urls re.findall(rhttps?://[^\s,、)】\]], text) codes re.findall(r(?:提取码|密码|访问码|解压码)[:\s]*([0-9A-Za-z]{4,12}), text) print(URL:, urls) print(CODE:, sorted(set(codes)))逻辑说明先把文档里所有可能承载文字的XML节点摊平成一个字符串再做无状态正则。[^\s,、)】\]]这个字符集是关键网盘链接常以全角右括号结尾如果只排除空格提取出来的URL会多带一个字符请求时直接404。提取码用{4,12}而不是固定长度因为不同平台的编码长度不一样。参数上没什么可调的唯一要注意的是doc.element.body只覆盖正文如果文档里有页眉页脚需要额外遍历doc.sections。注意不要用text.split(链接)这类基于位置的解析。同一批数据被不同人转发几次措辞就会变只有正则能扛住。2.2 下载件的落地校验先看四件事再入库文件到手后别急着pd.read_csv。先按顺序确认四件事能把后续80%的脏数据问题挡在门外。校验项期望结果失败时的动作文件完整性哈希与发布方 manifest 一致或解压无报错重新下载不要尝试修复压缩包目录结构按年份或按表拆分命名规则统一先写一份文件清单再决定合并策略记录数与主键主键组合唯一无重复行定位重复来源多半是合并报表与母公司报表混放时间跨度最早报告期落在1991-1993最晚为2024-06-30缺年份说明分包不全缺末端说明版本滞后# 发布方给了校验文件就直接验比肉眼比对靠谱 sha256sum -c manifest.sha256 # 先列目录再解压避免解压出一堆嵌套文件夹 unzip -l cashflow_pkg.zip | head -50 # 判断编码与BOMfile 对压缩格式和中文编码的识别很直接 file data/*.csv命令含义sha256sum -c会逐行读取 manifest 并比对任何一行 FAILED 都说明文件在传输中被改动过尤其是分卷压缩的包。unzip -l只列不解压能提前发现压缩包里套了一层同名目录直接解压会让你多出一级路径。file输出的UTF-8 Unicode (with BOM)决定了后面 pandas 该用utf-8还是utf-8-sig用错的话第一列列名会带一个不可见字符之后所有df[股票代码]都会抛 KeyError。2.3 字段落地的三个高频坑编码、前导零、数值单位这一步处理完数据才算能进分析管道。import pandas as pd df pd.read_csv( data/cashflow_1991_202406.csv, encodingutf-8-sig, # 吃掉BOM dtype{股票代码: string, 交易市场: string}, # 防止 000001 变成 1 parse_dates[报表日期, 公告日期], na_values[, -, --, NULL, null, nan, N/A], ) # 补齐到6位兼容早期只有5位甚至4位的代码 df[股票代码] df[股票代码].str.strip().str.zfill(6) # 单位统一常见做法是元、千元、万元三种混用按文件名或字段后缀判定 UNIT {元: 1.0, 千元: 1e3, 万元: 1e4} money_cols [c for c in df.columns if c.endswith((_元, _万元, _千元))] for c in money_cols: suffix c.rsplit(_, 1)[-1] df[c] pd.to_numeric(df[c], errorscoerce) * UNIT[suffix] df df.rename(columns{c: c.rsplit(_, 1)[0] for c in money_cols})逻辑与参数dtypestring而不是默认的 object是为了让.str访问器始终可用同时避免 pandas 把列推断成整数后抹掉前导零。errorscoerce把无法解析的文本转成 NaN 而不是抛异常因为这类长面板里一定混着--、空格、全角数字。单位统一必须显式做把万元当元用会让净现比看起来正常但绝对值差一万倍而净现比是比值它反而不报错这是最阴的一类错误。2.4 1991-2024年6月 的口径断层别把空值当零跨度三十多年最大的障碍不是数据量是会计准则和披露要求的变更。三个断层必须记住。1998年之前上市公司披露的是财务状况变动表以营运资金为基础不是现金流量表。所以1991到1997年的样本经营活动产生的现金流量净额这类字段大面积为空。处理方式是在面板上打一个report_std标志字段把1998年以前的记录标为pre_cf计算现金流类指标时直接排除绝对不要 fillna(0)。填零会让早期样本的净现比全部变成0或无穷任何按年份做的统计都会失真。1998到2006年执行旧准则下的现金流量表2007年起执行新准则报表项目名称和归集方式有变化比如购建固定资产、无形资产和其他长期资产支付的现金在新准则下才稳定存在。做法是建一张字段映射表把不同年份的项目名映射到统一的标准列名映射不上的记入unmapped列而不是静默丢弃。第三个断层是金融业。银行、保险、券商没有销售商品、提供劳务收到的现金它们用收取利息、手续费及佣金的现金。如果全市场统一算收现比金融业的分子会全是空值分位数基准被拉偏。正确做法是在行业分类上先把金融业拆出来单独建基准后面第4章会展开。早期还有一批已退市公司代码在1991-2005年有记录之后断档做时序连续性校验时要把退市标志一起带出来否则会误判为数据缺失。3. 财务指标面板的现金流派生净现比、收现比、自由现金流的计算原始表只有报表项目没有指标。净现比、收现比、自由现金流这些要自己派生。派生之前先定键键定错了后面全白算。3.1 三张表拼长面板键、粒度与去重A股财务数据的主键是(股票代码, 报告期, 报表类型)。报表类型必须区分合并报表和母公司报表做集团整体分析一律取合并报表混用会让同一报告期出现两行指标直接翻倍。报告期粒度上现金流量表项目是累计值2024-06-30 那一行代表的是1月到6月的累计数不是单季数。这一点决定了滚动指标怎么写。# 只保留合并报表且只保留标准报告期 df df[df[报表类型] 合并报表].copy() df df[df[报表日期].dt.strftime(%m-%d).isin([03-31, 06-30, 09-30, 12-31])] # 主键去重同一 (代码, 报告期) 保留最新公告的那条 df (df.sort_values(公告日期) .drop_duplicates(subset[股票代码, 报表日期], keeplast) .reset_index(dropTrue))逻辑说明先按报告期过滤是因为有些数据源会带上招股说明书期或非标报告期那些行的口径不可比。去重时保留最新公告的一条处理的是财务重述场景——同一份年报可能因为会计差错更正被重新披露用最早的版本会拿到已被推翻的数字。3.2 净现比与收现比的 Python 实现import numpy as np # 净现比分母为负时比值失去意义直接置空 df[净现比] np.where( df[净利润] 0, df[经营活动产生的现金流量净额] / df[净利润], np.nan ) # 收现比分母用含税收入更合理增值税率按主体适用税率给 VAT 1.13 df[收现比] np.where( df[营业收入] 0, df[销售商品、提供劳务收到的现金] / (df[营业收入] * VAT), np.nan ) # 现金流量利息保障倍数 df[现金利息保障倍数] np.where( df[现金利息支出] 0, (df[经营活动产生的现金流量净额] df[现金利息支出] df[所得税付现]) / df[现金利息支出], np.nan )参数说明净现比的分母用净利润而不是归母净利润是因为经营活动现金流是整个集团口径与归母净利润不匹配。收现比的分母乘1.13是常见处理因为现金流量表里的销售收现包含增值税销项用不含税的营业收入做分母会让健康的制造业公司收现比看起来只有0.88误判为回款差。np.where的第二个分支返回 NaN 而不是跳过保证结果列的行数与原表严格对齐。行业差异要注意农林牧渔、建筑装饰的收现比天然偏低医药和白酒天然偏高跨行业比绝对值没有意义必须用行业内分位。指标公式健康区间制造业常见误用净现比经营现金流净额 / 净利润0.8 - 1.3长期 1净利润为负时不置空收现比销售收现 / 含税营业收入0.95 - 1.15分母用不含税收入自由现金流经营现金流净额 − 资本开支长期 0资本开支漏掉无形资产现金利息保障倍数(经营现金流利息税付现) / 利息3 相对安全分母用财务费用代替现金利息3.3 自由现金流与资本开支口径自由现金流最容易出错的地方在资本开支的取数。现金流量表投资活动下有三个相邻科目购建固定资产、无形资产和其他长期资产支付的现金、取得子公司及其他营业单位支付的现金净额、投资支付的现金。只有第一个算维持性和扩张性资本开支后两个属于对外投资混进来会让重资产公司的自由现金流被严重低估。df[资本开支] df[购建固定资产、无形资产和其他长期资产支付的现金].fillna(0) df[自由现金流] df[经营活动产生的现金流量净额] - df[资本开支] df[FCF利润率] np.where(df[营业收入] 0, df[自由现金流] / df[营业收入], np.nan)逻辑与参数资本开支这里用了fillna(0)和前面现金流字段的处理看起来矛盾。区别在于pre_cf时期整张现金流量表都缺而2007年以后个别公司某个明细科目为空多数是当年确实没有这笔支出填零比置空更接近真实。判定规则是整行关键字段全空按缺失处理单科目为空按零处理。FCF利润率用营业收入做分母是为了在规模差异极大的样本间可比用总资产做分母会被杠杆结构干扰。3.4 用 SQL 在千万行级别上做同一件事数据量上来以后Pandas 的窗口函数写法会吃内存。同一套逻辑放到 SQL 里更稳。-- 计算单季值与滚动12个月经营现金流 WITH base AS ( SELECT 股票代码, 报表日期, 经营活动产生的现金流量净额 AS ocf, LAG(经营活动产生的现金流量净额, 4) OVER (PARTITION BY 股票代码 ORDER BY 报表日期) AS ocf_yoy FROM cashflow_panel WHERE 报表类型 合并报表 ) SELECT 股票代码, 报表日期, ocf - ocf_yoy AS ocf_ttm_增量口径, AVG(ocf) OVER (PARTITION BY 股票代码 ORDER BY 报表日期 ROWS BETWEEN 3 PRECEDING AND CURRENT ROW) AS ocf_ma4 FROM base;逻辑与参数LAG(..., 4)取的是去年同期的那一行因为一年有四个报告期Q1、半年、Q3、年报往前推4行正好落在同一个报告期。ROWS BETWEEN 3 PRECEDING AND CURRENT ROW是4个报告期的移动平均由于每行都是累计值这个平均值不代表任何会计期间只能当平滑后的趋势量使用。要真正得到滚动12个月的现金流正确公式是最近一期年报累计 本期累计 − 去年同期累计2024-06-30 对应的就是2023年报 2024半年 − 2023半年。4. 现金流结构筛选实战正负号组合、行业基准与滚动窗口指标算完只是半成品。用现金流做筛选最有效的不是单个比值的阈值而是三大活动现金流的符号组合。4.1 八种现金流符号组合与生命周期含义经营活动、投资活动、筹资活动各取正负共八种状态。金融业要单独处理下面讨论的都是非金融企业。经营投资筹资典型含义需要警惕的点−扩张期主业造血且持续融资投入筹资规模远超经营现金流时靠外部输血−−成熟期自我造血并偿还债务或分红投资大幅收缩可能是无项目可投−收缩期变卖资产还债投资现金流入来自处置长期资产要确认现金充裕但缺乏投向长期停留在此状态说明资本配置效率低−−烧钱扩张全靠融资最需要预警的组合−主业失血靠卖资产和借款维持风险极高−−主业失血同时还债流动性危机前兆−−−三线净流出一般只出现在季末调头寸或重大并购当期def sign(x): return np.where(x 0, 1, np.where(x 0, -1, 0)) df[cf_pattern] (sign(df[经营活动产生的现金流量净额]).astype(str) / sign(df[投资活动产生的现金流量净额]).astype(str) / sign(df[筹资活动产生的现金流量净额]).astype(str)) # 筛选连续两期落在烧钱扩张组合的样本 df df.sort_values([股票代码, 报表日期]) df[prev_pattern] df.groupby(股票代码)[cf_pattern].shift(1) risk df[(df[cf_pattern] -1/-1/1) (df[prev_pattern] -1/-1/1)]逻辑说明sign用嵌套 where 而不是np.sign是为了把零单独归为0类避免数值上极接近零的科目被误判为正或负。实战中建议给一个容差比如绝对值小于营业收入的1%就归零否则小额尾差会让组合在正负之间反复跳。groupby().shift(1)生成上一期组合两期联合筛选能过滤掉单期并表造成的偶发形态。4.2 行业内分位基准别用全市场均值前面反复提过收现比和净现比有强行业属性。全市场均值的做法等于把白酒和建筑装饰平均到一起两边都不像。正确姿势是先按申万一级行业分组再按报告期算分位。df[收现比_行业分位] (df.groupby([行业名称, 报表日期])[收现比] .rank(pctTrue, na_optionkeep)) # 只保留行业样本数足够的分组样本太少的行业分位没有统计意义 n df.groupby([行业名称, 报表日期])[收现比].transform(count) df.loc[n 15, 收现比_行业分位] np.nan参数说明pctTrue得到0到1的分位na_optionkeep保证原有的缺失值不参与排名也不被填成中间值。n 15这个阈值不是硬规定行业分类越细需要的最小样本越大二级行业建议放到30。2024年6月这一期要注意部分行业半年报披露延迟截面当天样本数可能明显低于年报期脚本里最好加一条日志把每期实际样本数打出来。4.3 滚动 12 个月与半年报口径处理单期现金流量表受季节影响极大尤其是零售、建筑这类Q4集中回款的行业。用半年报数据直接做同比看不出趋势。稳妥做法是统一换成滚动12个月TTM。df df.sort_values([股票代码, 报表日期]) g df.groupby(股票代码) df[ocf_ttm] np.where( df[报表日期].dt.month 12, df[经营活动产生的现金流量净额], # 年报本身就是12个月 g[经营活动产生的现金流量净额].shift(4) # 上年年报累计 df[经营活动产生的现金流量净额] # 本期累计 - g[经营活动产生的现金流量净额].shift(2) # 上年同期累计 )逻辑与参数三个分支对应不同报告期类型。年报期直接用累计数不用拼。季报和半年报期用上年年报 本期累计 − 上年同期累计shift(2)对半年报来说正好是上年半年报对Q1来说是上年Q1此时 shift(2) 落在上上年Q3需要按报告期类型分别取 shift 步数。实际写的时候别用一刀切的 shift按报表日期.dt.month分成 3、6、9、12 四套分支步数分别是 2、2、2、0 相对年报位置计算写错一步整列数据就废了。4.4 银行、保险、券商要单独一档金融业的报表结构和一般工商业完全不同。银行没有销售商品、提供劳务收到的现金也没有购买商品、接受劳务支付的现金它的经营活动现金流主要受存贷款规模变动影响一家银行经营现金流为负可能是放贷扩张属于正常经营用工商业的组合表去套会得到完全错误的结论。做法是在面板上加一列is_financial金融业样本单独建一套指标用经营活动现金流净额 / 平均总资产代替净现比用收取利息、手续费及佣金的现金 / 利息收入代替收现比。分位数基准也按银行、保险、多元金融三个二级组分别算。这一步省不得否则每次跑全市场筛选金融板块都会排在头部或尾部把选股结果整体带偏。5. 让 1991-2024年6月 的现金流面板可复现校验、参数与常见误用数据管道跑通一次不算成功能重复跑出同一份结果才算。下面三组校验建议直接写进脚本每次更新数据自动执行。5.1 三组校验恒等式、跨表勾稽、时序连续性第一组是恒等式。现金流量表内部必须满足现金及现金等价物净增加额 经营活动净额 投资活动净额 筹资活动净额 汇率变动影响期末现金及现金等价物余额 期初余额 净增加额。这两条是硬约束不满足的行一定能定位到取数或单位问题。def check_identity(df, tol1e-6): left df[现金及现金等价物净增加额] right (df[经营活动产生的现金流量净额] df[投资活动产生的现金流量净额] df[筹资活动产生的现金流量净额] df[汇率变动对现金的影响].fillna(0)) bad df.loc[(left - right).abs() tol * df[总资产].clip(lower1)] return bad[[股票代码, 报表日期, 现金及现金等价物净增加额]] problem check_identity(df) print(f恒等式不通过行数: {len(problem)})参数说明tol * 总资产用的是相对容差因为不同公司金额量级差几个数量级固定绝对容差对小公司太松、对大公司太紧。汇率变动对现金的影响只有外币业务较多的公司才非空所以fillna(0)。第二组是跨表勾稽。现金流量表补充资料里披露的净利润应当和利润表的净利润一致允许有少数股东损益调整的差异但不应超过千分之一。第三组是时序连续性同一股票代码的期末现金余额应当和下一期期初余额对得上断层往往意味着数据源在中间年份用了不同供应商的表或者当年有会计差错追溯重述。df df.sort_values([股票代码, 报表日期]) df[prev_end] df.groupby(股票代码)[期末现金及现金等价物余额].shift(1) gap df[(df[期初现金及现金等价物余额] - df[prev_end]).abs() df[期末现金及现金等价物余额].abs() * 0.01 1] print(gap[[股票代码, 报表日期]].head(20))逻辑说明加1是为了让金额接近零的公司不至于因为四舍五入被判为断层。查出来的断层样本别急着手工修先去核对是不是报告期不连续比如中间某年停牌未披露确认是原始缺失后在面板上打缺口标记指标计算时跳过而不是插值填补——插值在现金流序列上会造出不存在的平滑趋势。5.2 关键参数建议表下面这些参数是我自己跑这份1991-2024年6月面板时会固定下来的一套可以直接抄走再按行业微调。参数建议值理由现金流指标起算期1998年以后之前披露的是财务状况变动表收现比分母增值税率13%农林牧渔用9%现金流含税口径要匹配净现比有效样本净利润 0 且营业收入 0否则比值无经济含义行业分位最小样本一级行业15二级行业30样本太少分位不稳定符号组合容差科目绝对值 / 营业收入 1% 归零消除尾差导致的形态抖动恒等式容差总资产的 1e-6相对容差量级无关时序断层阈值期末余额绝对值的1% 1元兼顾零值公司表格里最容易被忽略的是符号组合容差。不加这个很多公司的经营现金流在几百万和-几百万之间来回跳形态标签一年一变基于形态的因子完全没有预测力。5.3 一个具体技巧用5年累计值代替单年值做筛选单年净现比噪声大尤其对项目制、周期类行业。把窗口拉长到5年用累计经营现金流除以累计净利润能有效平滑掉单年的大额预收或集中回款。df df.sort_values([股票代码, 报表日期]) g df.groupby(股票代码) df[ocf_5y] g[经营活动产生的现金流量净额].transform( lambda s: s.rolling(window20, min_periods16).sum()) # 20个报告期≈5年 df[np_5y] g[净利润].transform( lambda s: s.rolling(window20, min_periods16).sum()) df[净现比_5y] np.where(df[np_5y] 0, df[ocf_5y] / df[np_5y], np.nan) # 落地筛选五年累计净现比大于1且最近一期收现比在行业中位数以上 candidates df[(df[净现比_5y] 1) (df[收现比_行业分位] 0.5) (df[报表日期] 2024-06-30)]逻辑与参数窗口取20个报告期对应5年min_periods16允许公司中途上市只要凑够4年数据就参与计算否则会把2019年以后上市的公司全部剔掉样本量损失很大。注意这是累计值口径滚动求和得到的是5年累计经营现金流和前面 TTM 的算法不是一回事两列不要混用。用这种方式筛出来的名单在周期顶部的假繁荣阶段比单年净现比阈值稳定得多。本文还有配套的精品资源点击获取