Featured image of post Build an Automated Financial Data Product with DuckDB: From Data Collection to Paid API

Build an Automated Financial Data Product with DuckDB: From Data Collection to Paid API

Learn how to build a complete automated financial data pipeline with DuckDB + FastAPI, from data collection to multi-factor stock screening to paid API monetization.

DuckDB Automated Financial Data Product Architecture

Introduction: The “Passive Income” Dream for Data Products

People building data products share a common pain point: manual data updates, overtime report production. You spend a week building an analysis system, but every data update requires re-running the entire pipeline. When clients want new dimensions, you have to modify the code again. This “handcraft” model can never scale.

Today I’ll show you an architecture that enables true passive income: build once, run automatically, charge on demand.

The entire flow has three layers:

  • Data Collection Layer: Python scripts fetch data from financial APIs on schedule
  • Data Processing Layer: DuckDB handles ETL, aggregation, and multi-factor calculations
  • Product Output Layer: FastAPI wraps everything into paid APIs for on-demand querying

The key advantage of using DuckDB for the middle layer: single-file database = zero ops cost, vectorized execution = millisecond responses.


1. Architecture Design: Three-Layer Separation

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  Data Collection │────▶│  Data Processing │────▶│  Product Output │
│  Python Scraper │     │  DuckDB (.duckdb)│     │  FastAPI + DB   │
│  Yahoo Finance  │     │  ETL + Factors   │     │  REST API       │
│  Alpha Vantage  │     │  Window Functions│     │  Scheduled Rpts │
└─────────────────┘     └─────────────────┘     └─────────────────┘
        │                       │                       │
        └───────────────────────┼───────────────────────┘
                              │
                      .duckdb File
                      (Persistent Storage)

Why DuckDB?

DimensionTraditional (Pandas + SQLite)DuckDB Approach
Memory Usage2-4GB RAM for 1M rowsSingle file handles GB-scale data
Deployment CostRequires standalone DB service.duckdb file IS the warehouse
Query PerformanceGROUP BY is slowVectorized engine, milliseconds
Ops ComplexityHigh (backup, monitoring, scaling)Zero (file backup is enough)

For indie developers, zero ops = true passive income. Your data product needs no DBA, no monitoring alerts — just periodic backup of the .duckdb file.


2. Step One: Build the DuckDB Data Model

Let’s build a “US Stock Quantitative Screening” data product that updates financial data daily and generates stock screening signals.

2.1 Create Database and Tables

import duckdb
import pandas as pd

# Create DuckDB database file (persistent storage)
con = duckdb.connect("stock_data.duckdb")

# Financial data table: stores daily updated financial metrics
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
)
""")

# Daily snapshot table: for time series analysis
con.execute("""
CREATE TABLE IF NOT EXISTS daily_snapshots (
    ticker VARCHAR,
    snapshot_date DATE,
    market_cap BIGINT,
    volume BIGINT,
    price REAL
)
""")

# Create indexes to accelerate queries
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("✅ Data model created")

💡 Key Insight: DuckDB’s .duckdb file IS your data warehouse. No need to deploy PostgreSQL, MongoDB, or other standalone services — just store the file on your server. Cost is zero; backup is just a cp command.

2.2 Bulk Insert Optimization

DuckDB supports VALUES (?) bulk insert syntax, which is 10x faster than row-by-row INSERT:

import numpy as np
from datetime import datetime, timedelta

# Simulate 30 days of financial data
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)
        ))

# Bulk insert
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"✅ Inserted: {len(data_rows)} financial records, {len(snapshot_rows)} snapshot records")

💡 Pro Tip: The larger the data volume, the more pronounced the bulk insert advantage. 100K rows with bulk insert takes seconds; row-by-row INSERT could take minutes.


3. Step Two: Multi-Factor Stock Screening Model (Core Algorithm)

This is the most valuable part of your data product. Calculate multi-factor stock screening signals with a single SQL query:

3.1 Factor Design

We design four factors with different weights:

FactorFormulaWeightLogic
ROEAVG(net_income * 4 / total_assets)40%Profitability
Revenue Growthrevenue_5d_avg / revenue_prev_5d_avg - 130%Growth
Debt Ratio1 - AVG(total_debt / total_assets)20%Financial Health
Valuation Percentile1 - PERCENT_RANK() OVER (ORDER BY pe_ratio)10%Attractiveness

3.2 SQL Implementation

signal_sql = """
WITH ranked_stocks AS (
    SELECT 
        ticker,
        date,
        -- Return on Equity (rolling 12-month)
        AVG(net_income * 4.0 / NULLIF(total_assets, 0)) AS avg_roe,
        -- Revenue growth (last 5 days vs previous 5 days)
        (
            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,
        -- Debt ratio
        AVG(total_debt * 1.0 / NULLIF(total_assets, 0)) AS debt_ratio,
        -- PE percentile
        PERCENT_RANK() OVER (
            PARTITION BY date ORDER BY pe_ratio
        ) AS pe_percentile,
        -- Composite score
        (
            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        -- Debt ratio < 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())

Example output:

 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

💡 Core Logic: DuckDB’s window functions (PERCENT_RANK, AVG OVER) are extremely performant — sorting and aggregation on thousands of rows completes in milliseconds. This is why it’s faster than Pandas.

3.3 Why SQL Instead of Python?

ComparisonPython (Pandas)DuckDB SQL
Code Lines30-50 lines1 SQL query
Execution SpeedSecondsMilliseconds
MaintainabilityMultiple steps to maintainSingle point of query
Team CommunicationData analysts vs engineersUnified language

4. Step Three: Wrap as FastAPI Data Product

Wrap the calculations above into API endpoints — your data product now has a paid API foundation:

4.1 Basic API Design

from fastapi import FastAPI
from pydantic import BaseModel
import duckdb

app = FastAPI(title="DuckDB Stock Screening API")

# Process-local singleton connection (avoid reopening file per request)
_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 Call Example

# Get today's Top 10 stock screening signals
curl -X POST "http://localhost:8000/api/signals" \
  -H "Content-Type: application/json" \
  -d '{"date": "2026-08-09", "top_n": 10}'

# Response
[
  {"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. Step Four: Automation and Monetization

5.1 Scheduled Tasks (Cron)

# Automatically update data daily at 17:00 (after market close)
0 17 * * * cd /home/user/stock-screener && python3 update_data.py >> /var/log/stock_update.log 2>&1

update_data.py script:

import duckdb
import requests
from datetime import datetime

con = duckdb.connect("stock_data.duckdb")

# 1. Fetch latest data (connect to Yahoo Finance or Alpha Vantage)
# 2. Incremental update to DuckDB
# 3. Recalculate stock screening signals
# 4. Send notification (Telegram/WeChat)

5.2 Monetization Models

ModelPricingTarget CustomersMonthly Revenue Estimate
API Subscription$29-99/monthIndividual investors, small teams$300-1000
Report Service$99/monthFinancial advisors, investment consultants$500-2000
SaaS Platform$199/monthSmall investment institutions$1000-5000
Custom Development$5000+/projectEnterprise clientsOn-demand

💡 Core Insight: Your competitors are Bloomberg Terminal ($24,000/year) and Wind (¥30,000+/year). You only need to provide 10% of their features at 1% of the price to capture the long-tail market.


6. Complete Project Structure

stock-screener/
├── stock_data.duckdb          # DuckDB database file (your data warehouse)
├── update_data.py             # Data update script
├── app.py                     # FastAPI application
├── requirements.txt
│   ├── duckdb
│   ├── fastapi
│   ├── uvicorn
│   └── pandas
└── cronjobs/
    └── daily_update.sh        # Scheduled task configuration

7. Pitfall Guide

7.1 Common Issues

ProblemCauseSolution
Concurrent write conflictsDuckDB defaults to single-writerUse con.execute("PRAGMA journal_mode=WAL") or scheduled batch updates
Slow API responsesReopening database per requestProcess-local singleton connection, or use duckdb.connect(":memory:", read_only=True)
Data inconsistencyQueries during writesUse transactions + query after commit, or read-write separation
Memory overflowData volume too largeUse Parquet partitioned storage + predicate pushdown

7.2 Production Environment Recommendations

  1. Backup Strategy: Auto-backup .duckdb file to S3/local daily
  2. Monitoring: Send alerts when scheduled tasks fail (Telegram Bot)
  3. Rate Limiting: Add rate limiting to API to prevent abuse
  4. Version Control: Manage database schema changes with migration scripts

8. Monetization Suggestions

8.1 Minimum Viable Product (MVP) Path

  1. Week 1: Build DuckDB + FastAPI foundation, run through data pipeline
  2. Week 2: Connect to real data source (Alpha Vantage free tier), refine screening model
  3. Week 3: Wrap as API, deploy to cloud platform (Fly.io/Render free tier)
  4. Week 4: Find 10 seed users for free trial, collect feedback
  5. Month 2: Start charging, price at $29/month, target 10 paying users

8.2 Differentiated Competition Strategy

CompetitorTheir WeaknessYour Opportunity
BloombergExpensive, high barrierServe individual investors
WindChina-focused, weak overseas dataBilingual CN/US data
TradingViewStrong technical analysis, weak fundamentalsFundamental quantitative screening
Xueqiu/TonghuashunAd-heavy, delayed dataReal-time API, no ads

8.3 Long-term Roadmap

  • Phase 1: Stock screening API ($29/month)
  • Phase 2: Add ETF, futures data ($49/month)
  • Phase 3: Provide backtesting engine ($99/month)
  • Phase 4: Open strategy marketplace, take 20% commission (platform model)

Summary

The core formula for building automated financial data products with DuckDB:

DuckDB (zero ops) + FastAPI (rapid development) + Cron (automation) = Passive Income

Key success factors:

  1. Data quality > Feature quantity: Accurate financial data matters more than fancy UI
  2. Differentiated positioning: Don’t compete with giants on features; compete on vertical depth
  3. Pricing strategy: $29-99/month pricing targets customers with willingness to pay

Next steps:

  • Run through the entire flow with mock data
  • Connect to real data sources (Yahoo Finance API)
  • Deploy to cloud platform, start charging

💡 Want to systematically learn more DuckDB实战技巧? duckdblab.org has complete tutorial series — from basics to advanced — to help you truly make money with DuckDB.

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials

Built with Hugo
Theme Stack designed by Jimmy

⚠️ This site is an independent community project, not affiliated with, endorsed by, or sponsored by the DuckDB Foundation or official DuckDB project.

"DuckDB" is a registered trademark of the DuckDB Foundation. This site uses the name solely for factual description purposes.

All content is for educational and community promotion purposes only and does not constitute any commercial service.