拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python获取A股行情数据:通达信tdxpy接口从入门到工程化

Python获取A股行情数据:通达信tdxpy接口从入门到工程化

这段时间把通达信的数据接口在 Python 里的用法从头到尾过了一遍,趁着细节还没忘,赶紧整理成一篇笔记。如果你跟我一样没有 wind 账号,又不想为数据终端花大几千,还想靠 Python 拿 A 股行情数据做研究,那 tdxpy 这类数据接口 API 应该是目前性价比最高的方案之一。这篇笔记会从选型逻辑、环境准备、核心 API 拆解、真实踩坑到工程化落地,完整记录我自己的实操过程,希望能帮你少走一段冤枉路。

1. 为什么要折腾 tdxpy:免费行情接口的选型逻辑

1.1 商业数据源的价格门槛

做量化研究或者个人投研,数据是第一关。市面上的商业数据源,wind 一年几万起步,choice、iFinD 虽然稍微便宜点,但对一个还没产生稳定策略的个人研究者来说,这笔开销怎么看都不划算。更尴尬的是,很多开源方案虽然免费,但要么数据范围受限,要么更新时效满足不了日内行情研究。

我自己的需求很明确:日频和分钟频的 K 线、实时五档盘口、全市场股票列表,最好还能拿到板块成分。这几点如果全走商业数据源,月费轻松上千;如果走免费接口,最关键的是要搞清楚哪一类接口能满足其中大部分需求。

1.2 tdxpy 这类接口到底属于什么定位

tdxpy 简单理解就是"通达信数据接口在 Python 里的封装"。通达信这套行情体系在券商行情里覆盖得非常广,很多券商的行情软件底层都在用。tdxpy 做的事情,本质上是把行情服务器返回的数据转成 Python 对象,让你能像操作普通 list/dict 一样处理行情数据。

这里要说明一下:tdxpy 在社区里不同的分支、不同的作者手里,API 命名和一些调用细节会略有差异,有的版本需要本机装了通达信客户端才能跑,有的版本是走协议直连。但核心思路是一样的——连上行情服务器,发请求,收数据,解析。所以与其纠结某一个具体函数名,不如先把这套接口的共性逻辑吃透。

相比之下,akshare 是聚合型数据源,爬的是公开网页;tushare 靠积分制,数据质量也不错;baostock 免费但历史数据偏日频。tdxpy 这类接口最大的优势是"原始行情、实时性好、不做过多加工",拿到手的数据干净,适合自己掌控股利计算和复权逻辑。

1.3 选型的时候我主要看这几点

我的选型标准其实就四条,你可以直接套用:

  • 数据时效:能不能拿到当天甚至当下的行情,而不是延迟一天。
  • 数据粒度:有没有分钟线、五档盘口,还是只有日线。
  • 成本与门槛:是否免费,需不需要本机装额外软件,安装配置复杂度如何。
  • 扩展性:能不能把数据存到本地,方便回测和研究反复用。

按这个标准筛下来,tdxpy 这类直连通达信行情服务器的接口,在"实时性"和"原始性"上是明显占优的。这也是我最终决定花时间折腾它而不是无脑用现成聚合库的原因。

2. 环境准备与第一次连通:先把通道跑通

2.1 准备一个干净的 Python 环境

很多人在装这类接口时遇到问题,先别急着怀疑库本身,很大概率是 Python 环境版本太新,或者本机装了好几个 Python 导致依赖装错地方。

我建议你用 Python 3.9 到 3.11 之间的版本,配合 venv 或 conda 单独建一个环境。太新的 Python 版本,部分涉及 c 扩展或者二进制依赖的库可能还没有对应轮子,安装时会当场编译失败;太老的版本又容易和 pandas、numpy 的新版本冲突。

conda create -n tdx python=3.10 -y conda activate tdx pip install pandas numpy

先把 pandas 和 numpy 装好,后面不管是数据解析还是落盘保存都用得上。

2.2 安装与最小连接验证

装好基础依赖后,再装数据接口库本身。因为 tdxpy 的具体封装在不同项目里可能有差异,我在这里不写死某个安装命令,建议你直接到项目的 README 或者 PyPI 页面确认官方推荐的安装方式。装完之后,第一步不是写复杂逻辑,而是跑一个最简连接脚本,确认网络层和协议层是通的。

# 以常见的协议直连封装为例,具体类名/方法名以你实际安装的版本为准 from tdxpy import TdxClient client = TdxClient() # 服务器地址可以从社区公开的行情服务器列表中找延迟最低的 connected = client.connect("行情服务器地址", 7709) if connected: print("连接成功") print(client.get_server_time()) # 先拿服务器时间,确认链路没问题 else: print("连接失败,检查网络和服务器地址")

这里我特别提醒两点。

第一,行情服务器地址的变化非常频繁,网上能找到很多公开的列表,建议自己写个小脚本批量测延迟,选延迟最低的。不要长期硬编码一个地址,失效是常态。

第二,有的封装版本"连接成功"不代表能拿到数据,还得验证一下服务器时间或者指数行情,确认返回结果不是空值。这一步忘掉的话,后面写再多代码都是白搭。

2.3 为什么第一次连通如此重要

我最初犯过的错误是跳过自检直接写全量拉数据脚本,结果跑了几分钟才发现其中一个服务器地址早就失效了,报错信息又很隐晦,排查了半天。其实问题不在代码逻辑,而在最底层网络层根本没通。

所以我的经验是:先花十分钟把最小链路跑通,再往上层加逻辑。连接成功、能拿到服务器时间、能取到任意一只股票的快照,这三步确认完,后面再写批量脚本腰杆都硬了。

3. 核心 API 拆解:行情快照、K 线与板块列表

3.1 连接管理与会话机制

tdxpy 这类接口的使用方式,一句话总结就是"一次连接、多次请求"。连接一次之后可以反复发查询请求,用完再断开,千万不要每条数据都重新连一次。这就像打电话,不是发短信——拨号有成本,保持通话状态才能高效沟通。

工程上的做法一般是:在脚本开始时建立连接,然后将连接对象作为公共组件传入需要取数的函数里,脚本结束统一关闭。如果担心长时间挂着连接被服务端断开,可以隔一段时间发一个轻量查询保持活跃。

# 示意:连接生命周期管理 with client as conn: data1 = conn.get_stock_quotes("000001") data2 = conn.get_kline("000001", period="daily", count=100) # 退出 with 块时自动断开

3.2 行情快照接口

行情快照是最常用的接口,返回某只股票当前的实时状态,包括最新价、涨跌幅、成交量、成交额、五档盘口等。它的特点是"一次请求多只股票",你可以把几十只股票代码放在一个列表里传给接口,而不是一只一只地查,这样能显著减少请求次数。

关于返回值,我提醒大家不要想当然。不同封装对字段的单位处理不一样,有的价格按元直接返回,有的需要你手动除以 100 或 1000;成交量有的按股、有的按手;成交额有的单位是元,有的单位是万元。拿到数据后的第一件事,是找一只已知行情的股票做校验,把字段单位全部确认清楚,不然后面的计算全错。

# 示意:批量获取快照 codes = ["000001", "600519", "300750"] quotes = client.get_stock_quotes(codes) for item in quotes: # 先打印原始字段,人工确认单位再写后续逻辑 print(item)

3.3 K 线历史数据接口

K 线是回测研究最核心的数据。这个接口的参数一般包括市场代码、证券代码、K 线周期、起始位置和数量。

这里有个非常关键的细节:市场代码和证券代码是分开传的。A 股里上海市场和深圳市场的代码规则不同,接口通常单列一个 market 参数(0 代表深圳,1 代表上海),写代码的时候要注意别把两个市场搞混。如果你直接传完整的 6 位代码而不指定市场,部分接口会返回空数据。

另一个细节是单次请求的数量上限。不同周期上限不同,日线单次能取的数量通常比分钟线多,但都不可能一次取全量历史。所以正确的取数姿势是:先确认上限,再分段拉取,最后拼接。

# 示意:分段拉取日K def fetch_kline(client, code, market, period, total): bars = [] batch_size = 800 # 视具体接口上限调整 remaining = total while remaining > 0: count = min(batch_size, remaining) part = client.get_kline( code, market=market, period=period, start=remaining - count, # 从最早处往前补 count=count, ) if not part: break bars = part + bars remaining -= count return bars

3.4 列表与板块数据

除了单只股票的行情,做策略研究通常还需要全市场股票列表和板块成分。这类接口的返回值往往是一个大的列表,每条包含代码、名称、所属市场等信息。

我的习惯是:全市场列表不频繁拉取,每天收盘后拉一次存下来就够了。板块数据同理,板块分类标准各家人为因素很多,如果你要做行业轮动,建议以自己长期跟踪的分类为准,而不是频繁切换数据源导致分类口径不一致。

4. 免费接口的坑:这些细节文档里不会写

4.1 复权数据的处理

复权是免费接口最大的坑,没有之一。很多接口返回的 K 线是不复权的原始价,遇到除权除息的日子,K 线图上会出现明显的价格断层,直接拿来算收益率或者画均线,结果会非常离谱。

我的处理思路是:本地存原始价,同时把每次除权除息的事件记录下来,自己计算复权因子。回测时如果策略是短周期,我一般用后复权保证历史价格序列可比;如果只看当前形态,再用前复权。这样做的问题是代码量会多不少,但好处是数据完全可控,不会被接口的默认策略绑架。

如果实在不想自己算复权因子,至少也要确认你用的接口有没有提供"指定复权类型"的参数,并在拉数时显式传入,而不是依赖默认值。好多坑都是"我没指定,它默认不复权"造成的。

4.2 停牌股的缺失值处理

停牌股会让数据出现缺失 K 线。问题在于,如果你按股票代码去拼 DataFrame,缺的 K 线会导致索引错位,一次错位后面全乱。

正确做法是:以交易日历为基准,把所有股票的数据做 outer join,缺失日期填充 NaN 或前值。这样虽然多占点存储,但后续做截面分析时的数据结构是统一的,比事后补错位容易得多。

# 示意:以交易日历为基准对齐 df = df.set_index("date").reindex(trade_calendar) df["close"] = df["close"].fillna(method="ffill") # 停牌期间沿用前收盘价

这种处理方式在回测时还有一个好处:遇到停牌股,策略会自动跳过不可交易的日子,不会因为假数据而误入场。

4.3 请求频率与连接稳定性

免费接口没有 SLA,请求太猛会被服务端限流甚至断连。我自己的实测经验是:普通行情服务器,每秒请求控制在 2 到 4 次比较稳,批量拉 K 线时要主动加 sleep 控制节奏。

import time # 示意:批量拉数时控制节奏 for i, code in enumerate(all_codes): bars = client.get_kline(code, period="daily", count=500) store(code, bars) time.sleep(0.3) # 平滑请求速率

另外,长连接偶尔会被服务端断开,这是正常现象。工程上必须做断线重连机制——捕获连接异常,重新 connect 后继续跑,而不是整个脚本崩溃退出。我在第 5 节会讲一个完整的重试思路。

4.4 数据精度与字段单位

这一条看似基础,实际上最容易埋雷。有些接口用 int 返回价格,数值上等于真实价格乘以 100 或 1000,你如果直接把 int 当价格用,算出来的收益率差出好几倍。成交量同理,有的按手、有的按股。

我的建议是:写一个统一的数据清洗层,把接口返回的原始数据全部换算成标准单位,再进入下一环节。这样数据清洗只在这一层发生,后面回测或计算用的数据是一致的,不会这个模块按手、那个模块按股地互相打架。

4.5 时区与时间戳

接口返回的时间大多数是北京时间,但不少字段是字符串或者无时区的时间对象,直接和本地 datetime 比较容易出问题。

我的做法是:入库时统一转成无时区的交易时间,或者统一存成固定时区的字符串。这里的关键不是绝对标准,而是全项目统一标准,别在一个库里混着不同时区语义的时间,否则做时间切片的时候非常痛苦。

5. 从笔记到工具:把采集脚本做成可复用模块

5.1 三层模块结构

"能跑"和"能稳定跑"是两码事。我从笔记阶段过渡到工具阶段时,把采集脚本重构成了三层结构:连接层、请求层、存储层。

连接层专门负责建立连接、心跳探测、断线重连。请求层把各种 API 调用封装成语义化函数,比如 get_daily_bars(code)、get_realtime_quote(code),上层代码不用关心数据是从哪个服务器来的。存储层负责读写本地数据,暴露 save_bars、load_bars 这样的接口。

这样设计的好处是:哪天想换数据源,只需要改连接层和请求层的实现,策略代码完全不用动;反过来,想从本地存储换成数据库,也只动存储层。

5.2 增量更新策略

拉数据的正确姿势不是"全量重拉",而是"首次全量建底稿,之后增量更新"。

全量拉一次全市场日线大概要跑很久,但增量更新就快得多。每天收盘后,只需要拉最近几天(留足缓冲)的 K 线,跟本地已有的数据做去重拼接。这样每天运行时间从小时级降到分钟级。

# 示意:增量更新逻辑 last_date = get_local_last_date(code) # 本地已有的最后日期 latest = client.get_kline(code, period="daily", count=10) new_bars = [b for b in latest if b.date > last_date] if new_bars: append_to_local(code, new_bars)

这里要注意:增量拉的时候多拉几天做缓冲,是为了防止除权除息或数据修正导致最后几根 K 线和前一天的存储不一致。宁可多做一次覆盖更新,也别留缺口。

5.3 本地存储选型

数据量不大,SQLite 加 CSV 就够用;如果做全市场多年分钟线研究,建议直接上 Parquet。

我用的是按股票代码分文件的目录结构,每天数据存一个 Parquet 文件。原因是分钟线数据量很大,按代码分文件能避免单个文件过大;跨天读取时再用多线程批量加载,性能完全够用。

SQLite 适合按"日期范围"查询的场景,比如"2024 年 1 月到 3 月所有股票的日线数据"。如果你主要做截面分析,优先考虑它。

5.4 容错与重试机制

免费接口的运行环境里,网络抖动和数据为空都是常态。我的做法是给请求层加一个带重试的装饰器,遇到连接异常、超时、空数据,先等一秒再重试,最多重试三次,还失败就把代码写进失败队列,下一轮启动时补拉。

import functools import time def retry(max_retries=3, delay=1): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): for i in range(max_retries): try: result = func(*args, **kwargs) if result: return result except Exception as e: print(f"request error: {e}, retry {i + 1}") time.sleep(delay) return None return wrapper return decorator @retry(max_retries=3, delay=1) def safe_get_kline(client, code): return client.get_kline(code, period="daily", count=500)

有了这层保障,一个采集任务挂机跑几个小时,中途偶尔报几个错,脚本也能自己恢复而不是直接死掉。

6. 和其它 Python 行情接口的横向对比与选型建议

6.1 主流方案的差异对比

用 tdxpy 之前,我也把市面上常见的 Python 行情接口过了一遍,这里给出我自己的横向对比,方便你做选型参考。

方案数据时效数据范围成本上手难度适合场景
tdxpy/协议直连类实时行情,盘中可取行情、板块为主,财务少免费中本地量化研究,需原始行情
akshare准实时/日频范围极广,含宏观、财报免费低数据探索,飞快拿数据
tushare日频/分钟财务数据质量好免费额度有限低需要财报+行情结合的轻量项目
baostock有延迟日线为主免费低长周期回测,数据稳定

6.2 为什么我最终选择混用而非单一方案

把表拉出来后你会发现,没有一个方案是完美的。tdxpy 类接口强在实时行情和原始数据,弱在财务数据几乎为零;akshare 数据范围广,但毕竟是聚合爬取,稳定性和数据规范性差一些;tushare 数据质量不错,但免费积分有额度限制;baostock 稳定,但分钟线基本不用想。

我现在的实践是混用:用 tdxpy 类接口拉日线和分钟线底稿,用 akshare 补财报和板块数据,用 baostock 做历史行情数据的交叉校验。三套数据源互相印证,关键数据不只看单边,很大程度上避免了单一免费源突然挂掉或者数据出错的风险。

这种做法虽然前期配置麻烦一点,但长期来看,当你发现某个策略的收益率异常得可疑时,可以快速交叉验证是不是数据源出了问题,而不是在策略逻辑里浪费时间排查。行情数据这件事,免费不等于廉价,花点心思把数据链路做扎实,后面做研究的心态会完全不一样。

返回列表