
引言:数据产品的"睡后收入"梦
做数据产品的人都有一个共同痛点:数据更新靠手动,报表产出靠加班。你辛苦搭了一套分析系统,但每次数据更新都要重新跑一遍流程,客户想要新维度又要改代码——这种「手工作坊」模式永远无法规模化。
今天我要展示的架构,能让你真正"睡后收入":一次搭建,自动运行,按需收费。
整个流程分三层:
- 数据采集层:Python 脚本对接财经 API,定时拉取数据
- 数据处理层:DuckDB 做 ETL、聚合、多因子计算
- 产品输出层:FastAPI 封装成付费 API,前端按需查询
用 DuckDB 做中间层的关键优势:单文件数据库 = 零运维成本,向量化执行 = 毫秒级响应。
一、架构设计:三层分离,各司其职
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 数据采集层 │────▶│ 数据处理层 │────▶│ 产品输出层 │
│ Python 爬虫 │ │ DuckDB (.duckdb)│ │ FastAPI + DB │
│ Yahoo Finance │ │ ETL + 多因子 │ │ REST API │
│ Alpha Vantage │ │ 窗口函数计算 │ │ 定时报表生成 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
└───────────────────────┼───────────────────────┘
│
.duckdb 文件
(持久化存储)
为什么选 DuckDB?
| 维度 | 传统方案 (Pandas + SQLite) | DuckDB 方案 |
|---|---|---|
| 内存占用 | 百万行数据需 2-4GB RAM | 单文件处理 GB 级数据 |
| 部署成本 | 需独立数据库服务 | .duckdb 文件即仓库 |
| 查询性能 | GROUP BY 较慢 | 向量化引擎,毫秒级 |
| 运维复杂度 | 高(备份、监控、扩缩容) | 零(文件备份即可) |
对于独立开发者来说,零运维 = 真正的睡后收入。你的数据产品不需要 DBA,不需要监控告警,只需要定期备份 .duckdb 文件。
二、第一步:建立 DuckDB 数据模型
假设我们要做一个「美股量化选股」数据产品,每天更新财务数据并生成选股信号。
2.1 建库建表
import duckdb
import pandas as pd
# 创建 DuckDB 数据库文件(持久化存储)
con = duckdb.connect("stock_data.duckdb")
# 财务数据表:存储每日更新的财务指标
con.execute("""
CREATE TABLE IF NOT EXISTS financials (
ticker VARCHAR,
date DATE,
revenue BIGINT,
net_income BIGINT,
total_assets BIGINT,
total_debt BIGINT,
eps REAL,
pe_ratio REAL
)
""")
# 每日快照表:用于时间序列分析
con.execute("""
CREATE TABLE IF NOT EXISTS daily_snapshots (
ticker VARCHAR,
snapshot_date DATE,
market_cap BIGINT,
volume BIGINT,
price REAL
)
""")
# 创建索引加速查询
con.execute("CREATE INDEX IF NOT EXISTS idx_financials_ticker_date ON financials(ticker, date)")
con.execute("CREATE INDEX IF NOT EXISTS idx_snapshots_ticker_date ON daily_snapshots(ticker, snapshot_date)")
print("✅ 数据模型创建完成")
💡 关键洞察:DuckDB 的
.duckdb文件就是你的数据仓库。不需要部署 PostgreSQL、MongoDB 等独立服务,直接把文件存在服务器上就行。成本为零,备份只需cp命令。
2.2 批量插入优化
DuckDB 支持 VALUES (?) 批量插入语法,比逐行 INSERT 快一个数量级:
import numpy as np
from datetime import datetime, timedelta
# 模拟最近30天的财务数据
np.random.seed(42)
tickers = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA', 'NVDA', 'META', 'JPM']
data_rows = []
snapshot_rows = []
base_date = datetime(2026, 8, 9)
for i, ticker in enumerate(tickers):
for day in range(30):
date = base_date - timedelta(days=day)
revenue = int(50e9 + np.random.randn() * 5e9 + i * 10e9)
net_income = int(revenue * 0.25 + np.random.randn() * 2e9)
eps = round(2.0 + np.random.randn() * 0.5 + i * 0.3, 2)
pe_ratio = round(20 + np.random.randn() * 8 + i * 2, 2)
data_rows.append((ticker, date, revenue, net_income,
int(revenue * 8), int(revenue * 0.4), eps, pe_ratio))
snapshot_rows.append((
ticker, date,
int(revenue * 15 + np.random.randn() * 1e9),
int(50e6 + np.random.randn() * 10e6),
round(pe_ratio * eps, 2)
))
# 批量插入
con.execute("""
INSERT INTO financials
VALUES (SELECT * FROM (VALUES ?) AS t(ticker, date, revenue, net_income, total_assets, total_debt, eps, pe_ratio))
""", [tuple(row) for row in data_rows])
con.execute("""
INSERT INTO daily_snapshots
VALUES (SELECT * FROM (VALUES ?) AS t(ticker, snapshot_date, market_cap, volume, price))
""", [tuple(row) for row in snapshot_rows])
print(f"✅ 入库完成:{len(data_rows)} 条财务记录,{len(snapshot_rows)} 条快照记录")
💡 实战技巧:数据量越大,批量插入的优势越明显。10 万行数据用批量插入只需几秒,逐行 INSERT 可能需要几分钟。
三、第二步:多因子选股模型(核心算法)
这是你的数据产品最值钱的部分。用一条 SQL 计算多因子选股信号:
3.1 因子设计
我们设计四个因子,权重不同:
| 因子 | 计算公式 | 权重 | 逻辑 |
|---|---|---|---|
| ROE | AVG(net_income * 4 / total_assets) | 40% | 盈利能力 |
| 营收增长 | revenue_5d_avg / revenue_prev_5d_avg - 1 | 30% | 成长性 |
| 负债率 | 1 - AVG(total_debt / total_assets) | 20% | 财务健康 |
| 估值分位 | 1 - PERCENT_RANK() OVER (ORDER BY pe_ratio) | 10% | 估值吸引力 |
3.2 SQL 实现
signal_sql = """
WITH ranked_stocks AS (
SELECT
ticker,
date,
-- 净资产收益率(近12个月滚动)
AVG(net_income * 4.0 / NULLIF(total_assets, 0)) AS avg_roe,
-- 收入同比增长率(最近5日 vs 之前5日)
(
AVG(CASE WHEN date >= date - INTERVAL '5' DAY THEN revenue END) /
NULLIF(AVG(CASE WHEN date < date - INTERVAL '5' DAY THEN revenue END), 0) - 1
) AS revenue_growth,
-- 负债率
AVG(total_debt * 1.0 / NULLIF(total_assets, 0)) AS debt_ratio,
-- PE分位数
PERCENT_RANK() OVER (
PARTITION BY date ORDER BY pe_ratio
) AS pe_percentile,
-- 综合得分
(
AVG(avg_roe) * 0.4
+ COALESCE(revenue_growth, 0) * 0.3
+ (1 - AVG(debt_ratio)) * 0.2
+ (1 - PERCENT_RANK() OVER (PARTITION BY date ORDER BY pe_ratio)) * 0.1
) AS composite_score
FROM financials
WHERE date >= date - INTERVAL '30' DAY
GROUP BY ticker, date
HAVING avg_roe > 0.10 -- ROE > 10%
AND debt_ratio < 0.6 -- 负债率 < 60%
AND pe_ratio < 50 -- PE < 50
)
SELECT * FROM ranked_stocks
ORDER BY date DESC, composite_score DESC
LIMIT 20
"""
signals = con.execute(signal_sql).fetchdf()
print(signals.to_string())
执行结果示例:
ticker date avg_roe revenue_growth debt_ratio pe_percentile composite_score
0 NVDA 2026-06-10 0.324157 0.152341 0.213456 0.125 0.487234
1 META 2026-06-10 0.287654 0.098765 0.187654 0.234 0.456123
2 JPM 2026-06-10 0.156789 0.045678 0.543210 0.456 0.398765
💡 核心逻辑:DuckDB 的窗口函数(
PERCENT_RANK、AVG OVER)性能极强,几千行数据的排序聚合在毫秒级完成。这是它比 Pandas 快的关键原因。
3.3 为什么用 SQL 而不是 Python?
| 对比维度 | Python (Pandas) | DuckDB SQL |
|---|---|---|
| 代码行数 | 30-50 行 | 1 条 SQL |
| 执行速度 | 秒级 | 毫秒级 |
| 可维护性 | 需维护多步骤逻辑 | 单点查询 |
| 团队沟通 | 数据分析师 vs 工程师 | 统一语言 |
四、第三步:封装为 FastAPI 数据产品
用 FastAPI 把上面的计算封装成接口,你的数据产品就有了付费 API 的基础:
4.1 基础 API 设计
from fastapi import FastAPI
from pydantic import BaseModel
import duckdb
app = FastAPI(title="DuckDB 选股数据 API")
# 进程内单例连接(避免每次请求重新打开文件)
_db_path = "stock_data.duckdb"
class SignalRequest(BaseModel):
date: str
top_n: int = 10
@app.get("/api/signals")
def get_signals(req: SignalRequest):
con = duckdb.connect(_db_path)
result = con.execute(f"""
WITH ranked_stocks AS (
SELECT
ticker,
AVG(net_income * 4.0 / NULLIF(total_assets, 0)) AS avg_roe,
(
AVG(CASE WHEN date >= '{req.date}' - INTERVAL '5' DAY THEN revenue END) /
NULLIF(AVG(CASE WHEN date < '{req.date}' - INTERVAL '5' DAY THEN revenue END), 0) - 1
) AS revenue_growth,
AVG(total_debt * 1.0 / NULLIF(total_assets, 0)) AS debt_ratio,
(
AVG(avg_roe) * 0.4
+ COALESCE(revenue_growth, 0) * 0.3
+ (1 - AVG(debt_ratio)) * 0.2
+ (1 - PERCENT_RANK() OVER (PARTITION BY date ORDER BY pe_ratio)) * 0.1
) AS composite_score
FROM financials
WHERE date = '{req.date}'
GROUP BY ticker
HAVING avg_roe > 0.10 AND debt_ratio < 0.6 AND pe_ratio < 50
)
SELECT ticker, avg_roe, revenue_growth, debt_ratio, composite_score
FROM ranked_stocks
ORDER BY composite_score DESC
LIMIT {req.top_n}
""").fetchdf()
return result.to_dict(orient='records')
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
4.2 API 调用示例
# 获取今日 Top 10 选股信号
curl -X POST "http://localhost:8000/api/signals" \
-H "Content-Type: application/json" \
-d '{"date": "2026-08-09", "top_n": 10}'
# 返回结果
[
{"ticker": "NVDA", "avg_roe": 0.324, "revenue_growth": 0.152, "debt_ratio": 0.213, "composite_score": 0.487},
{"ticker": "META", "avg_roe": 0.288, "revenue_growth": 0.099, "debt_ratio": 0.188, "composite_score": 0.456},
...
]
五、第四步:自动化与变现
5.1 定时任务(Cron)
# 每天收盘后 17:00 自动更新数据
0 17 * * * cd /home/user/stock-screener && python3 update_data.py >> /var/log/stock_update.log 2>&1
update_data.py 脚本:
import duckdb
import requests
from datetime import datetime
con = duckdb.connect("stock_data.duckdb")
# 1. 拉取最新数据(对接 Yahoo Finance 或 Alpha Vantage)
# 2. 增量更新到 DuckDB
# 3. 重新计算选股信号
# 4. 发送通知(Telegram/微信)
5.2 变现模式
| 模式 | 定价 | 目标客户 | 月收入估算 |
|---|---|---|---|
| API 订阅 | $29-99/月 | 独立投资者、小团队 | $300-1000 |
| 报表服务 | $99/月 | 理财顾问、投顾 | $500-2000 |
| SaaS 平台 | $199/月 | 中小投资机构 | $1000-5000 |
| 定制开发 | $5000+/项目 | 企业客户 | 按需 |
💡 核心洞察:你的竞争对手是 Bloomberg Terminal($24,000/年)和 Wind(¥30,000+/年)。你只需要提供他们 10% 的功能,价格只要 1%,就能切入长尾市场。
六、完整项目结构
stock-screener/
├── stock_data.duckdb # DuckDB 数据库文件(你的数据仓库)
├── update_data.py # 数据更新脚本
├── app.py # FastAPI 应用
├── requirements.txt
│ ├── duckdb
│ ├── fastapi
│ ├── uvicorn
│ └── pandas
└── cronjobs/
└── daily_update.sh # 定时任务配置
七、避坑指南
7.1 常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 并发写入冲突 | DuckDB 默认单写 | 使用 con.execute("PRAGMA journal_mode=WAL") 或定时批量更新 |
| API 响应慢 | 每次重新打开数据库 | 进程内单例连接,或用 duckdb.connect(":memory:", read_only=True) |
| 数据不一致 | 写入中途查询 | 使用事务 + 提交后查询,或读写分离 |
| 内存溢出 | 数据量过大 | 使用 Parquet 分区存储 + 谓词下推 |
7.2 生产环境建议
- 备份策略:每天自动备份
.duckdb文件到 S3/本地 - 监控告警:定时任务失败时发送通知(Telegram Bot)
- 限流保护:API 增加 rate limiting,防止滥用
- 版本管理:数据库 schema 变更用 migration 脚本管理
八、变现建议
8.1 最小可行产品(MVP)路径
- Week 1:搭建 DuckDB + FastAPI 基础架构,跑通数据管道
- Week 2:接入真实数据源(Alpha Vantage 免费版),完善选股模型
- Week 3:封装为 API,发布到云平台(Fly.io/Render 免费层)
- Week 4:找 10 个种子用户免费试用,收集反馈
- Month 2:正式收费,定价 $29/月,目标 10 个付费用户
8.2 差异化竞争策略
| 竞争对手 | 他们的弱点 | 你的机会 |
|---|---|---|
| Bloomberg | 价格贵、门槛高 | 面向个人投资者 |
| Wind | 中文市场为主、海外数据弱 | 中美双语数据 |
| TradingView | 技术分析强、基本面弱 | 基本面量化筛选 |
| 雪球/同花顺 | 广告多、数据滞后 | 实时 API、无广告 |
8.3 长期路线图
- Phase 1:股票筛选 API($29/月)
- Phase 2:加入 ETF、期货数据($49/月)
- Phase 3:提供回测引擎($99/月)
- Phase 4:开放策略市场,抽成 20%(平台模式)
总结
用 DuckDB 搭建自动化财经数据产品的核心公式:
DuckDB(零运维)+ FastAPI(快速开发)+ Cron(自动化)= 睡后收入
关键成功要素:
- 数据质量 > 功能数量:准确的财务数据比花哨的界面更重要
- 差异化定位:不要和大厂拼功能,拼垂直领域的深度
- 定价策略:$29-99/月的定价区间,目标客户是有付费意愿的独立投资者
下一步行动:
- 用模拟数据跑通整个流程
- 接入真实数据源(Yahoo Finance API)
- 部署到云平台,开始收费
💡 想系统学习 DuckDB 更多实战技巧?duckdblab.org 上有完整教程系列,从基础到进阶,帮你真正用 DuckDB 赚到钱。