Featured image of post 用 DuckDB 搭建自动财报分析器:从原始 CSV 到投资报告,全程一键生成

用 DuckDB 搭建自动财报分析器:从原始 CSV 到投资报告,全程一键生成

用 DuckDB 把财报分析从 2 小时压缩到 30 秒:read_csv_auto 批量读取、LAG 窗口函数算环比同比、一键导出 Excel,附完整可运行代码和变现建议。

用 DuckDB 搭建自动财报分析器:从原始 CSV 到投资报告,全程一键生成

很多金融从业者和量化爱好者每天都在重复同一套动作:打开十几张 Excel,手动核对数字,写一堆 VLOOKUP 和公式,最后拼出一份财报分析。这套流程耗时 2-3 小时,且极易出错。

今天用 DuckDB 把整个流程自动化——从原始财报数据到可分享的分析报告,全程一条 SQL 搞定,耗时从 2 小时降到 30 秒。

数据流向架构


一、场景定义:你需要分析什么

假设你持有以下原始数据文件(CSV 格式):

  • income_statement/*.csv:利润表(日期、股票代码、营收、净利润、毛利率等)
  • balance_sheet/*.csv:资产负债表(日期、股票代码、总资产、总负债、净资产等)
  • stock_quotes/*.csv:每日行情(日期、股票代码、收盘价、成交量等)

目标输出:每只股票的季度关键指标 + 同比/环比变化 + 简单的估值判断信号。

传统方案需要用 Excel 或 Python pandas 写十几行代码来处理,而 DuckDB 可以做到"数据在哪里,SQL 就在哪里跑"——不需要先把所有文件加载到内存,直接对磁盘上的文件执行分析。


二、核心代码:一条 SQL 搞定全链路

import duckdb
from datetime import datetime

# 连接 DuckDB(内存模式,也可换成持久化数据库)
con = duckdb.connect(":memory:")

# 1. 直接读取 CSV(支持 glob,一次读多文件)
con.execute("""
    CREATE TABLE income AS
    SELECT * FROM read_csv_auto('data/income_statement/*.csv');

    CREATE TABLE balance AS
    SELECT * FROM read_csv_auto('data/balance_sheet/*.csv');

    CREATE TABLE quotes AS
    SELECT * FROM read_csv_auto('data/stock_quotes/*.csv');
""")

# 2. 一行 SQL 输出季度财报分析
report_sql = """
WITH quarterly AS (
    SELECT
        i.code,
        STRFTIME(i.date, '%Y-%q') AS quarter,
        AVG(i.revenue)   AS revenue,
        AVG(i.net_profit) AS net_profit,
        AVG(i.gross_margin) AS gross_margin,
        b.total_assets,
        b.total_liabilities,
        b.equity
    FROM income i
    LEFT JOIN balance b
        ON i.code = b.code
        AND STRFTIME(i.date, '%Y-%m') = STRFTIME(b.date, '%Y-%m')
    GROUP BY i.code, quarter, b.total_assets, b.total_liabilities, b.equity
),
with_growth AS (
    SELECT
        *,
        LAG(revenue)        OVER (PARTITION BY code ORDER BY quarter) AS rev_lag1,
        LAG(net_profit)     OVER (PARTITION BY code ORDER BY quarter) AS profit_lag1,
        LAG(gross_margin)   OVER (PARTITION BY code ORDER BY quarter) AS margin_lag1,
        LAG(revenue)        OVER (PARTITION BY code ORDER BY quarter) AS rev_lag4,
        LAG(net_profit)     OVER (PARTITION BY code ORDER BY quarter) AS profit_lag4
    FROM quarterly
)
SELECT
    code,
    quarter,
    ROUND(revenue / 1e8, 2)                AS revenue_亿,
    ROUND(net_profit / 1e8, 2)             AS net_profit_亿,
    ROUND(gross_margin * 100, 1)           AS gross_margin_pct,
    ROUND((revenue  - rev_lag1)  / NULLIF(ABS(rev_lag1), 0)  * 100, 1) AS qoq_rev,
    ROUND((net_profit - profit_lag1) / NULLIF(ABS(profit_lag1), 0) * 100, 1) AS qoq_profit,
    ROUND((revenue - rev_lag4)  / NULLIF(ABS(rev_lag4), 0)  * 100, 1) AS yoy_rev,
    ROUND((net_profit - profit_lag4) / NULLIF(ABS(profit_lag4), 0) * 100, 1) AS yoy_profit,
    ROUND(equity / NULLIF(total_assets, 0) * 100, 1) AS equity_ratio,
    ROUND(net_profit / NULLIF(equity, 0) * 100, 1) AS roe_pct
FROM with_growth
ORDER BY code, quarter DESC;
"""

result = con.execute(report_sql).fetchdf()
print(result.to_string(index=False))

# 3. 一键导出为 Excel 报告
result.to_excel("财报分析_自动生成.xlsx", index=False, engine='openpyxl')
print("✅ 报告已保存")

三、关键技术点深度解析

3.1 read_csv_auto —— 零配置读取,碾压 pandas

DuckDB 的 read_csv_auto 是真正的"开箱即用"。它自动推断每一列的数据类型(整数、浮点、日期、字符串),支持 glob 模式一次性读取目录下所有匹配的文件。

对比 pandas 的传统做法:

# pandas 需要手动循环、推断、合并
import pandas as pd
import glob

files = glob.glob('data/income_statement/*.csv')
dfs = [pd.read_csv(f) for f in files]
df = pd.concat(dfs, ignore_index=True)

DuckDB 只需要一行:

SELECT * FROM read_csv_auto('data/income_statement/*.csv');

当文件达到几百 MB 甚至几个 GB 时,DuckDB 的列式存储和向量化执行优势更加明显——pandas 很可能直接 OOM,而 DuckDB 依然流畅运行。

3.2 CTE + Window Function —— 替代所有 VLOOKUP

传统 Excel 方案中,计算环比需要:

  1. 用 VLOOKUP 找到上个季度的数据
  2. 手写 (本期 - 上期) / 上期 公式
  3. 复制公式到每一行

DuckDB 用 CTE(公共表表达式)+ LAG() 窗口函数一步到位:

LAG(revenue) OVER (PARTITION BY code ORDER BY quarter) AS rev_lag1

LAG(revenue, 1) 表示取当前行之前第 1 行的 revenue 值。配合 PARTITION BY code ORDER BY quarter,就能按股票分组、按季度排序,精确拿到"上个季度"的数据。

同理,LAG(revenue, 4) 拿到的是去年同季度的数据(同比)。

NULLIF(ABS(...), 0) 用于防止除以零的错误——当基期为 0 时返回 NULL,避免整行计算崩掉。

3.3 链式管道,内存零拷贝

DuckDB 的所有中间 CTE 都是虚拟视图,不占额外内存。只有当你调用 fetchdf() 时,结果才会真正物化到 pandas DataFrame。这意味着你可以在一条 SQL 里写 10 层 CTE,中间数据不会在内存里反复拷贝。


四、进阶:接入行情数据,生成投资建议

在基础财报分析之上,加入实时行情可以做简单的估值和信号判断:

enrich_sql = """
WITH base AS (
    -- 复用上面的 quarterly + with_growth 逻辑
    SELECT
        code, quarter, revenue_亿, net_profit_亿,
        qoq_rev, qoq_profit, yoy_rev, yoy_profit,
        roe_pct
    FROM (/* 上面的完整 report_sql */ sub)
),
latest_quote AS (
    SELECT code, close_price
    FROM quotes
    WHERE date = (SELECT MAX(date) FROM quotes)
)
SELECT
    b.*,
    q.close_price,
    CASE
        WHEN b.roe_pct > 15 AND b.yoy_profit > 10 THEN '🟢 推荐关注'
        WHEN b.roe_pct > 10 AND b.yoy_profit > 0  THEN '🟡 观察'
        ELSE '🔴 谨慎'
    END AS signal
FROM base b
JOIN latest_quote q ON b.code = q.code
ORDER BY b.code, b.quarter DESC;
"""

这段 SQL 的逻辑是:取最新日期的收盘价,与财报数据 JOIN,然后根据 ROE 和净利润同比增长率给出简单的信号标签。


五、与传统工具的对比

Excel 方案

  • 每只股票手动填公式,10 只股票 = 10 个 sheet
  • 数据更新需要重新打开文件、刷新公式
  • 容易出错,版本管理混乱

pandas 方案

  • 代码量多 3-5 倍
  • 大文件内存压力大
  • 需要手动处理 glob、类型推断、缺失值

DuckDB 方案

  • 一条 SQL 搞定全链路
  • 列式存储,GB 级数据轻松处理
  • 结果直接 fetchdf() 进入 pandas 生态
  • 可无缝集成到 Streamlit / FastAPI 等应用

六、实际落地价值

这个脚本可以:

  1. 每周自动跑一次 —— 替换掉你手动整理报表的时间,把 2 小时的周报工作压缩到 30 秒
  2. 作为数据产品嵌入投研平台 —— 成为团队的差异化功能,提升你的不可替代性
  3. 副业起点 —— 把这套逻辑封装成标准财报分析 SaaS,向小机构或独立投资者收费(99-299 元/月)
  4. 量化策略数据源 —— 作为你的因子库,为量化回测提供清洗好的财务数据

建议先把 CSV 数据准备好,在本地 Jupyter 里跑通,再考虑部署到定时任务。

学习更多 DuckDB 实战经验 → duckdblab.org

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计

⚠️ 本站为独立社区项目,与 DuckDB 基金会及 DuckDB 官方项目无任何从属、背书或赞助关系。

"DuckDB" 是 DuckDB 基金会的注册商标,本站仅以事实描述方式使用该名称。

本站内容仅供教育与社区推广用途,不构成任何商业服务。