数据源pytdxakshareDuckDB增量更新免费数据数据架构

国内量化交易免费数据源全景指南:从pytdx到DuckDB的实战数据架构


为什么免费数据源是量化交易的第一道门槛

很多量化新手第一件事就是去找回测框架——聚宽、米筐、优矿,注册后发现:免费账号只能跑分钟级回测、标的数量受限、API调用有配额。等你策略写好了,想搬到本地跑实盘,突然发现——数据从哪来?

商业数据源(Wind、iFinD、Choice)年费2~5万起步,对个人投资者来说成本太高。好在A股生态中有一批高质量免费数据源,配合合理的架构设计,完全能支撑从回测到实盘的全流程。

本文先横向对比国内主流免费数据源,然后以我实际运行的量化系统为例——5607只标的、1628万行K线数据、1094MB DuckDB本地仓库——拆解一套可复用的数据管道。

国内免费数据源横向对比

一、K线历史行情(核心需求)

数据源协议覆盖范围速度稳定性成本
pytdx(通达信)TCP直连股票/ETF/可转债/指数~0.16s/只⭐⭐⭐⭐免费
akshareHTTP抓取最全(含期货/港美股)中等⭐⭐免费
新浪行情HTTP实时行情为主⭐⭐⭐免费
腾讯行情HTTP实时+含市值/PE/PB⭐⭐⭐免费
东财行情HTTP实时+历史⭐(IP封)免费
BaoStockTCP股票/指数⭐⭐免费

各数据源深度评测

1. pytdx(通达信行情服务器直连)— 个人量化首选

pytdx 是通达信PC客户端背后的行情协议的Python封装,直连通达信公共行情服务器,不经过网页接口,不会被封IP。这是个人量化交易者最可靠的数据源。

from pytdx.hq import TdxHq_API

api = TdxHq_API()
# 连接公共行情服务器(9个节点自动选优)
api.connect('180.153.18.170', 7709)

# 获取贵州茅台日K线(market=1上证, 每次最多800条)
bars = api.get_bars(4, 1, '600519', 0, 800)
# 返回: date, open, high, low, close, vol, amount, datetime

api.disconnect()

优势:

  • 直连行情服务器,不走网页接口,IP不会被封
  • 支持股票、ETF、可转债、指数,全品种覆盖
  • 单次请求~0.16秒,全市场4500+只股票增量更新约15分钟
  • 数据质量好,除权除息信息完整(get_finance_info/get_xdxr_info

局限:

  • 每次最多返回800条K线,长历史需循环翻页
  • 不提供前复权/后复权,需要自行处理(下文详解)
  • 在WSL2环境中TCP长连接偶尔超时,需定期重连(每500只重连一次)

2. akshare — 数据最全但稳定性存疑

akshare 封装了东财、新浪、雪球等几十个网页接口,覆盖范围最广(期货、港美股、宏观、财务报表等),但稳定性是最大问题:

  • 东财API对服务器IP长期封禁(非临时限流),fund_etf_hist_em等接口彻底不可用
  • 分红汇总接口HTML解析经常失败
  • 接口签名随时可能变化,代码维护跟不上

经验法则: akshare作为备用数据源和宏观/基本面数据源,不做K线主源。

3. 新浪/腾讯/东财HTTP行情 — 四路并行实时报价

实时行情场景下,单一数据源容易超时或被封。实际工程中采用多源并行架构:

import requests
import concurrent.futures

# 新浪行情
def sina_quote(code):
    prefix = 'sh' if code.startswith('6') else 'sz'
    r = requests.get(f"http://hq.sinajs.cn/list={prefix}{code}")
    # 返回字段: 名称/今开/昨收/最新价/最高/最低/买一价/卖一价/成交量...

# 腾讯行情(额外含市值/PE/PB)
def tencent_quote(codes):
    # f45=总市值(万), f39=PE, f46=PB
    url = f"http://qt.gtimg.cn/q={','.join(codes)}"
    r = requests.get(url)

# 四路并行,50只<200ms
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as pool:
    futures = [pool.submit(sina_quote, c) for c in codes]

二、基本面与宏观数据

数据类型推荐数据源接口示例
财务报表akshareak.stock_yjbb_em(date="20241231")
PE/PB估值akshare(百度)ak.stock_zh_valuation_baidu(symbol, indicator)
申万行业akshareak.sw_index_second_info() + ak.index_component_sw()
宏观数据akshareak.macro_china_shibor_all()
可转债数据集思录ak.bond_cb_jsl()(免费仅30条)
智能选股东财妙想自然语言选股API(免费50次/天)

关键提醒: 申万行业代码接口返回带.SI后缀,但成分股查询需要纯数字,必须.replace('.SI','')处理。这类细节是免费数据源使用的家常便饭。


实战:我的量化数据管道

下面以我实际运行的系统为例,展示从数据采集到入库的完整流程。

系统全貌

┌─────────────────────────────────────────────────────────────┐
│                     每日01:00自动执行                          │
│                                                              │
│  ┌──────────┐    ┌──────────────┐    ┌───────────────────┐  │
│  │  pytdx    │───▶│  CSV本地存储  │───▶│  DuckDB同步(ETL)  │  │
│  │  增量下载  │    │  /data/stocks│    │  UPSERT防重复     │  │
│  └──────────┘    │  /data/bonds │    └───────┬───────────┘  │
│       │          │  /data/etf   │            │              │
│       ▼          └──────────────┘            ▼              │
│  ┌──────────┐    ┌──────────────┐    ┌───────────────────┐  │
│  │ 指数+宏观 │    │  前复权计算   │    │  quant.duckdb  │  │
│  │ 辅助更新  │    │  分红复权因子 │    │  1094MB / 20张表  │  │
│  └──────────┘    └──────────────┘    └───────────────────┘  │
│                                                              │
│  全流程自动完成,无需人工干预                                   │
└─────────────────────────────────────────────────────────────┘

数据规模(截至2026年6月)

表名标的数行数说明
stock_daily4,777只15,425,394行A股全市场日K(排除北交所/科创板)
bond_daily988只745,411行可转债日K
etf_daily59只118,105行主流ETF日K
index_daily7个41,505行上证/深证/沪深300等
stock_fundamentals财务指标(EPS/营收/ROE等)
macro_dailySHIBOR/PMI/M2等宏观数据
合计5607只~1628万行DuckDB 1094.5MB

第一层:pytdx增量下载

核心思路是:每个标的维护一个CSV文件,每次只下载最新数据追加到文件末尾。

class PytdxDataSource:
    """通达信行情服务器直连数据源"""
    
    # 9个公共行情服务器节点
    TDX_SERVERS = [
        ('180.153.18.170', 7709),
        ('xxx.xxx.xxx.xxx', 7709),
        ('112.74.214.43', 7709),
        # ... 共9个节点
    ]
    
    def get_daily_bars(self, code, market, start=0, count=800):
        """获取日K线,每次最多800条"""
        bars = self.api.get_bars(4, market, code, start, count)
        # 返回: datetime, open, high, low, close, vol, amount
        return pd.DataFrame(bars)

# 增量更新流程
downloader = StockDownloader()
downloader.run_incremental()
# 输出: 统计: 成功=4591 失败=0 跳过=0

工程细节:

  • 每500只主动重连pytdx,防止TCP长连接datetime反序列化错误
  • 重试机制:失败自动重试5次,间隔15秒
  • 请求间隔2秒(pytdx直连不受此限制,仅akshare备用源需要限流)

第二层:ETL同步到DuckDB

CSV是中间层,DuckDB是分析层。每天把更新的CSV增量同步到DuckDB。

# etl_incremental.py 核心逻辑
con = duckdb.connect("quant.duckdb")
con.execute("PRAGMA threads=8")
con.execute("PRAGMA memory_limit='4GB'")

# 检查点机制:记录每个CSV的mtime+size,跳过未变更文件
for csv_file in csv_files:
    fp = get_file_fingerprint(csv_file)
    if not need_reimport(csv_file, state):
        skipped += 1
        continue
    
    df = pd.read_csv(csv_file)
    # UPSERT: 先删除重复键再插入(防重复积累)
    con.register('tmp_df', df)
    con.execute("""
        DELETE FROM stock_daily 
        WHERE (code, date) IN (SELECT code, date FROM tmp_df)
    """)
    con.execute("INSERT INTO stock_daily SELECT * FROM tmp_df")
    
    save_checkpoint(con, csv_file, ...)

为什么用DuckDB而不是MySQL/SQLite?

  • 列式存储,读取OHLCV快10倍
  • SQL兼容,Python无缝集成
  • 单文件部署,无需安装服务端
  • 1628万行聚合查询<1秒

第三层:前复权处理

这是免费数据源最大的坑。pytdx默认返回不复权数据,直接用于回测会导致收益率严重失真。

问题根源: pytdx的”不复权”数据中,送转/配股导致的跳空已经被消除,但纯现金分红的跳空仍然存在。一只每年稳定分红5%的股票,不复权数据看起来每年都有5%的跳空下跌,策略会误判为”大跌”而卖出。

def apply_dividend_qfq(raw_df, datasource, code):
    """对纯分红事件做前复权
    
    pytdx不复权数据中:
    - 送转/配股跳空已消除(不需要处理)
    - 纯分红(fenhong>0)跳空仍在(必须处理)
    
    修正方式: ratio = (close_before - fenhong/10) / close_before
    乘到除权日之前的所有价格上
    """
    events = datasource._compute_qfq_factor(code)
    if events is None or len(events) == 0:
        return raw_df  # 无分红,无需复权
    
    df = raw_df.copy()
    for _, event in events.sort_values('date', ascending=False).iterrows():
        # 除权日前所有价格乘以复权因子
        mask = pd.to_datetime(df['date']) <= event['date']
        ratio = (event['close_before'] - event['fenhong']/10) / event['close_before']
        df.loc[mask, ['open','high','low','close']] *= ratio
    
    return df

实测影响: 修正后策略年化收益提升1.5~4.3个百分点,与聚宽/Ptrade回测结果对齐。这是不处理前复权根本发现不了的隐性偏差。

第四层:自动化调度

整套管道通过cron job每日凌晨1点自动执行:

#!/bin/bash
# cron_incremental_download.sh — 每日01:00执行
# 被 cron job 调用,三步流水线

# 第1步: pytdx增量下载CSV
python3.10 multi_downloader.py incremental

# 第2步: CSV → DuckDB同步(检查点+UPSERT)
python3.10 etl_incremental.py

# 第3步: 指数+宏观数据更新
python3.10 update_index_aux.py

# 输出: ✅ 增量数据下载+同步完成 (12分34秒)
#        📥 下载: 成功4591只, 失败0只
#        📦 DuckDB同步: 2,384行 (1.2s)
#        📈 指数更新: 2026-06-17 | 宏观: USD_CNY更新

工程保障:

  • flock 文件锁防止并发执行
  • 失败标记文件记录上次成功时间
  • 详细日志按日期归档(/data/logs/incremental_YYYYMMDD.log
  • 成功/失败都只输出一行汇总,适合飞书/钉钉通知

避坑指南:我踩过的6个坑

坑1:东财API IP长期封禁

现象: ak.fund_etf_hist_em() 持续报 RemoteDisconnected
根因: 东财对服务器IP做了永久封禁,不是临时限流
解决: ETF历史数据改用 pytdx get_daily_bars + apply_qfq 替代

坑2:DuckDB数据重复率75%

现象: 一次SELECT发现4500万行中有3400万行重复
根因: 早期ETL用INSERT而非UPSERT,每天追加导致全量重复
解决: DELETE重复键→INSERT (UPSERT模式),重复率从75%降至0%
影响: 数据库从4.2GB瘦身到1.1GB

坑3:前复权遗漏纯分红

现象: 回测年化收益比聚宽低3-4个百分点
根因: pytdx不复权数据含纯分红跳空,未做前复权
解决: rebuild_stock_qfq.py 全市场4439只重建前复权
教训: 回测结果必须与第三方平台交叉验证

坑4:集思录免费接口仅返回30条

现象: ak.bond_cb_jsl() 只拿到30只可转债数据
根因: 集思录免费接口限制返回条数,全市场需付费cookie
解决: 用 bond_zh_hs_cov_spot() (337只) 拿行情,自行算双低值

坑5:WSL2下pytdx TCP超时

现象: pytdx在WSL2中运行30秒后卡死
根因: WSL2网络栈对TCP长连接处理有bug
解决: 每500只主动断开重连,避免长连接累积

坑6:宏观数据日期格式混乱

现象: PMI/M2等宏观日期为"2024年03月份"中文格式
根因: akshare直接返回网页爬取文本,未做格式标准化
解决: parse_cn_date() 统一解析为 YYYY-MM-DD

免费数据源选型建议

根据我的实战经验,给个人量化交易者的选型建议:

K线历史行情: pytdx > akshare > BaoStock。pytdx直连行情服务器最稳定,全市场增量15分钟搞定。

实时行情: 四路并行(新浪+腾讯+东财+pytdx),50只报价<200ms。单源必超时。

基本面数据: akshare财务报表接口尚可,估值用百度数据源。申万行业注意代码后缀处理。

可转债数据: 集思录免费仅30条,全样本用东货行情接口+自行计算双低值。

宏观数据: akshare的SHIBOR/PMI/CPI接口基本可用,但日期格式需自行标准化。

一句话总结: pytdx做K线主源,akshare做基本面/宏观补充,多源HTTP行情做实时报价,DuckDB做本地仓库。零成本、全品种、自动化的数据管道,完全可行。


本文所有数据均为作者实际系统运行数据(截至2026年6月)。完整源代码和数据管道脚本已在实际系统中验证通过。如果你也在搭建量化数据系统,欢迎在评论区交流经验。

延伸阅读

💬 评论