DuckDB搭建A股量化数据库教程:4589只股票本地数据库从零实战
为什么需要本地数据库?
做A股量化回测,最头疼的问题就是数据。
免费数据源(akshare、tushare)有调用次数限制,付费数据源(Wind、聚宽)年费几千到几万。而且每次回测都要重新拉取数据,速度慢、依赖网络、不可复现。
方案:用pytdx(通达信数据接口)下载原始数据,存入DuckDB本地数据库。一次下载,永久使用,查询速度毫秒级。
结果:294MB的DuckDB文件,管理了4589只股票 + 988只可转债 + 30只ETF的全部日线行情。
技术架构
pytdx(通达信接口)→ CSV文件(增量存储)→ DuckDB(OLAP查询引擎)→ 策略回测
三层架构,每层各司其职:
| 层 | 技术 | 作用 | 数据量 |
|---|---|---|---|
| 采集层 | pytdx | 从通达信服务器获取原始K线数据 | 4500+只股票,每秒~6只 |
| 存储层 | CSV (ZSTD压缩) | 增量持久化,意外恢复 | 每只股票一个文件 |
| 分析层 | DuckDB | SQL查询、JOIN、聚合、回测 | 294MB单文件,内存映射 |
第一步:数据采集(pytdx)
pytdx是Python连接通达信行情服务器的库,不需要账号,直接TCP连接。
from pytdx.hq import TDHQ
import pandas as pd
api = TDHQ()
# 连接通达信主站
api.connect('119.147.212.81', 7709)
# 获取个股日线数据
# market: 0=深圳, 1=上海
# code: 股票代码
# start: 0表示从第一天开始
# count: 每次最多800条
df = api.get_security_bars(9, 0, '000001', 0, 800)
但有个大坑要注意——pytdx返回的是不复权数据。纯分红除权的收益差异会被忽略。
⚠️ 详见后续文章《pytdx不复权数据的坑:纯分红收益去哪了?》
采集优化要点:
# 批量采集,每500只重连一次
def batch_download(stock_list, batch_size=500):
results = []
for i in range(0, len(stock_list), batch_size):
batch = stock_list[i:i+batch_size]
for code in batch:
try:
df = api.get_security_bars(9, market_of(code), code, 0, 800)
results.append(df)
except:
continue
# 每500只重连,防止连接断开
api.disconnect()
api.connect('119.147.212.81', 7709)
return pd.concat(results)
性能:单线程采集,约0.16秒/只,4589只股票约12分钟采集完毕。
第二步:增量更新
全量采集一次后,每天只需要增量下载最新数据:
def incremental_update(code, existing_dates):
"""只下载缺失的最新数据"""
# 先获取基本信息,确定最后日期
bars = api.get_security_bars(9, market_of(code), code, 0, 1)
latest_date = bars[0]['datetime']
if latest_date in existing_dates:
return # 已是最新
# 增量下载
new_data = api.get_security_bars(
9, market_of(code), code,
0, 800 # 最多800条,实际上只补最近几天
)
return pd.DataFrame(new_data)
增量下载单日全部股票约2-3分钟,适合每天定时运行。
第二步半:前复权处理
pytdx提供get_xdxr_info()接口获取除权除息信息:
# 获取除权除息数据
xdxr = api.get_xdxr_info(market, code)
# xdxr字段:bd(x)date(除权日), fh(分红/每10股),
# peigong(配股/每10股), songzhuangu(送转股/每10股), ...
纯分红(fh>0, szg=0, pg=0)的前复权处理:
def apply_fh_adjust(df, fh_per_10):
"""纯分红前复权:往前调整价格"""
fh_per_share = fh_per_10 / 10
df['adj_close'] = df['close'] - fh_per_share
df['adj_open'] = df['open'] - fh_per_share
df['adj_high'] = df['high'] - fh_per_share
df['adj_low'] = df['low'] - fh_per_share
return df
⚠️ 注意:pytdx的xdxr字段单位是每10股,需要除以10才能得到每股分红。
第三步:DuckDB建库
DuckDB最大的优势——不需要安装服务器,一个文件就是数据库:
import duckdb
# 创建/连接数据库
conn = duckdb.connect('quant_v2.duckdb')
# 创建股票日线表
conn.execute("""
CREATE TABLE IF NOT EXISTS stock_daily (
code VARCHAR,
date DATE,
open DOUBLE,
high DOUBLE,
low DOUBLE,
close DOUBLE,
volume BIGINT,
amount DOUBLE,
PRIMARY KEY (code, date)
)
""")
# 批量导入CSV
conn.execute("""
INSERT OR REPLACE INTO stock_daily
SELECT * FROM read_csv_auto('data/stock/*.csv')
""")
DuckDB配置优化:
-- 使用多线程
SET threads = 8;
-- 内存限制
SET memory_limit = '4GB';
数据量实测
| 数据维度 | 行数 | 存储大小 |
|---|---|---|
| 股票日线 | 15,200,000 | ~180MB |
| 可转债日线 | 736,000 | ~60MB |
| ETF日线 | 69,000 | ~12MB |
| 其他(指数/财务等) | - | ~42MB |
| 总计 | ~1600万行 | 294MB |
数据库大小294MB,普通笔记本电脑完全无压力。查询性能:
-- 获取某只股票全部历史数据:<10ms
SELECT * FROM stock_daily WHERE code = '601318' ORDER BY date;
-- 跨股票查询(某日所有股票收盘价):~50ms
SELECT code, close FROM stock_daily WHERE date = '2025-01-02';
-- 复杂聚合(月均收益率):~200ms
SELECT code,
AVG((close - open) / open) as daily_return,
COUNT(*) as trading_days
FROM stock_daily
WHERE date >= '2024-01-01'
GROUP BY code
HAVING COUNT(*) > 200;
踩坑总结
🕳️ 坑1:pytdx连接不稳定
通达信主站有连接数限制,频繁请求会被断开。
解决:每500只股票重连一次,用try/except包裹每个请求。
🕳️ 坑2:不复权数据丢失分红收益
pytdx返回的是原始不复权数据。纯分红(不送转股)的股票,如果不做前复权处理,回测会低估历史收益。
解决:从xdxr接口获取除权信息,手动计算前复权价格。
🕳️ 坑3:增量更新要去重
第一次全量采集后,增量更新时可能遇到日期重叠。
解决:用INSERT OR REPLACE(DuckDB的UPSERT),或者先查已有日期再下载。
🕳️ 坑4:DuckDB线程数
默认单线程查询,大数据量聚合会很慢。
解决:SET threads = 8,利用多核CPU。
完整的数据管线
这套数据系统已经自动化运行了几个月:
每日06:00 → 早间检查(数据完整性)
每日18:00 → 增量数据下载(pytdx CSV → DuckDB同步)
每周一19:00 → 全市场估值周报
全部用Python脚本+Hermes Agent的cron调度,无人值守。
下一步
拥有了本地量化数据库后,就可以在上面跑各种策略回测了:
- 布林带均值回归策略:7笔交易85.7%胜率的完整回测与拆解 — 均值回归+本地数据库,单标的实战验证
- Barra 10因子选股:年化+30.5% Sharpe 1.217 的多因子合成实战 — 多因子选股必备,配合本地数据库可直接跑因子回测
- ETF轮动策略:动量+R2过滤,精准避坑 — 动量轮动策略,利用本地数据做多标的回测
代码已开源,你可以在自己的笔记本上复现这套系统,只需要安装DuckDB和pytdx。
❓ 常见问题
DuckDB比SQLite好在哪?
DuckDB专为分析型查询设计,列式存储+向量化执行引擎使其在聚合查询上比SQLite快10-100倍。支持SQL窗口函数、复杂JOIN和JSON操作,非常适合量化数据的时间序列分析。
数据多久更新一次?
建议每日收盘后增量更新。本文提供的Python脚本支持增量模式,只下载当日新增数据,约5分钟完成全部更新。可用cron或Hermes Agent自动调度。
支持美股或期货数据吗?
当前实现基于pytdx(通达信数据接口),主要覆盖A股、可转债和ETF。如需美股数据,可扩展yfinance或Alpha Vantage接口;期货数据可用天勤(TQSDK)对接。
数据库会占用多大空间?
294MB(包含4589只股票+988只可转债+30只ETF),加上回测结果和因子计算表后约500MB。DuckDB压缩效率高,全量历史数据仅需数百MB。
风险提示:本文所有内容均为技术分享,不构成投资建议。历史回测不代表未来收益。
📖 相关文章推荐
- Barra 10因子选股:年化+30.5% Sharpe 1.217 的多因子合成实战 — 多因子选股必备,配合本地数据库可直接跑因子回测
- 布林带均值回归策略:7笔交易85.7%胜率的完整回测与拆解 — 基于本地数据的均值回归策略,单标的85.7%胜率
- 可转债S04低价轮动策略:从回测框架到自动轮动实盘的完整教程 — 988只可转债数据已入库,可直接套用轮动策略