简介:本资源是一套面向金融数据分析工程师与量化策略开发者的Python通达信数据接口实现方案,解决本地Python环境直连通达信行情服务器、高效获取实时及历史股票数据的技术难题。压缩包共29个文件,含24个Python源码(覆盖行情获取、K线解析、公司信息查询、除权除息处理等核心模块)、2个XML配置文件(用于协议参数与服务端地址管理)、2个文本文档(含变更日志与基础说明)及1个.gitignore,整体仅65KB,轻量易集成。已有1744人学习下载,适合具备Python基础并从事股票量化分析、策略回测或交易系统开发的中高级开发者。读者可直接复用完整接口架构,快速调用get_security_bars、get_history_transaction_data等20余个标准化方法,结合内置日志、缓存与错误处理机制,大幅降低通达信数据接入门槛。
1. 项目思路与接口方案选型
1.1 为什么需要自建数据接口
做量化或者技术分析的人,第一道坎基本都是数据。我自己最早做回测的时候,从通达信客户端手工导K线,一天导几百只股票,导完还得重新整理格式,临时加个股票又要重来。最痛苦的是每天收盘后要重复一遍流程,错一次图就废了。后来我才彻底想明白,与其在“导出数据”上耗时间,不如直接写一个数据接口层,把从行情服务器取数、清洗、落库这件事自动化。
这个项目要做的事,简单说就是用 Python 对接通达信的行情数据源,不依赖手工导出,也不用打开软件。拿到日线、分钟线、实时报价、股票列表和基础财务信息。整个设计不是一条脚本写完就跑,而是按照“接口化”的思路来做:上层策略/分析代码只跟接口打交道,不关心数据是从哪台服务器、什么协议、什么格式取回来的。这样后期换数据源的时候,只需要改最底下的连接层,业务逻辑完全不用动。
1.2 三种常见取数方案的对比
在动手写代码之前,我对比过圈子里常见的取数路子,大致有三类。
| 方案 | 免费程度 | 数据时效 | 批量能力 | 主要问题 |
|---|---|---|---|---|
| pytdx 协议接口 | 免费 | 实时 | 强,支持批量 | 需要维护服务器列表,偶尔有连接风险 |
| 通达信本地文件解析 | 免费 | 依赖下载 | 中 | 文件格式私有,每次要手动更新本地数据 |
| 商业数据接口如 Wind / 聚宽 | 收费或限权限 | 实时 | 强 | 年费门槛高,不适合个人练手 |
我最后选的是第一类,也就是直接用 pytdx 这套开源协议实现。原因是它的数据字段和通达信客户端基本一致,足够全,而且免费、延迟低。pytdx 不是什么黑科技,它就是帮我们省掉了自己手动构造协议包、解析返回字节流的脏活,把行情服务器的数据拿回来变成了纯 Python 对象的调用。代价是服务器列表需要自己维护,因为通达信行情服务器有公网入口,也有一批历史遗留的IP,经常出现某几个连着连着就失效的情况。
1.3 接口程序的分层设计
整个数据接口的源码,我拆成了三层来写,这样职责清楚,后面扩展也方便。
- 连接层:负责服务器连接、断线重连、心跳保活。对外只暴露一个“当前是否可用”的状态。
- 数据层:负责具体的数据组装,比如 K 线、实时报价、股票列表。这里统一做市场代码判断、字段映射、DataFrame 转换。
- 存储层:负责把清洗后的数据写进 SQLite 或者直接返回给调用方。
这三层之间用最朴素的类和方法来衔接,不引入复杂框架。为什么这么设计?因为个人项目最重要的是“好维护”。我之前见过很多人的代码,把服务器地址、字段映射、SQL 语句全部写在一个巨型函数里,表面上跑得通,但换一个需求就要动一行改三处。分层之后,连接出问题查连接层,字段不对查数据层,入库出错查存储层,问题定位快很多。
2. 环境准备与连接管理实现
2.1 Python 环境与依赖安装
我项目的运行环境是 Python 3.9,核心依赖只有两个:pytdx 和 pandas。前者负责行情协议,后者负责数据处理。装的时候直接用 pip 就行。
pip install pytdx pandas如果你用的是 VSCode,建议提前配好 Python 环境,F1 调出命令面板,选Python: Select Interpreter,把解释器切换到项目所在的虚拟环境,否则后面 pip 装的包容易跟全局环境搞混。这一步虽然基础,但踩过的人都知道,环境错位的问题比代码问题更难排查。
2.2 连接管理类怎么写才算稳
pytdx 的用法其实很直接,核心是TdxHq_API,但如果你每次都手动 connect、手动处理失败,代码会越写越乱。我封装了一个连接管理类,把服务器列表、重连逻辑和线程安全都放在一起。
import threading from pytdx.hq import TdxHq_API class TdxClient: def __init__(self, hosts=None, port=7709): # 服务器列表:优先放自己实测延迟低的 self.hosts = hosts or [ ("119.147.212.81", 7709), ("114.80.63.12", 7709), ("218.108.98.244", 7709), ] self.api = TdxHq_API(heartbeat=True) self.lock = threading.Lock() self.connected = False def connect(self): for host, port in self.hosts: try: if self.api.connect(host, port): self.connected = True print(f"connected to {host}:{port}") return except Exception as e: print(f"connect {host} failed: {e}") continue raise ConnectionError("所有行情服务器连接失败") def close(self): if self.api: self.api.disconnect() self.connected = False这里有几个点值得说。第一是heartbeat=True这个参数,pytdx 会自动发心跳包,防止长时间空闲被服务器踢掉。第二是加锁,因为很多取数场景会用线程池并发拉数据,如果不加锁,多个线程同时 commit 同一个连接,轻则数据错乱,重则直接断连。第三是服务器列表要维护成“候补制”,第一个连不上就换下一个,实测里这个设计救了我很多次。
2.3 核心 API 的记忆口诀
pytdx 的接口数量不少,但真正高频使用的其实就那么几个,我列一张表,方便你自己写的时候快速定位。
| 方法 | 功能 | 关键参数 |
|---|---|---|
| get_security_bars | 获取个股K线 | category、market、code、start、count |
| get_index_bars | 获取指数K线 | 同上 |
| get_security_quotes | 获取实时行情 | 传入 [(market, code), ...] 列表 |
| get_security_list | 获取股票列表 | market、start |
| get_security_count | 获取市场股票数量 | market |
| get_finance_info | 获取财务摘要 | market、code |
记忆重点:market是市场代码,0 代表深圳,1 代表上海;category是K线周期,9 代表日K,0 代表5分钟K,4 也是日K(老版本协议),建议统一用 9。count一次最大 800 根,超过的部分要靠start参数往前翻。
3. 核心数据模块的实现细节
3.1 K线获取与 DataFrame 统一封装
取K线是整套接口最核心的功能。通达信协议里,K线数据结构包含开高低收、成交量、成交额、时间字段。pytdx 返回的是原始 dict 列表,我先统一转成 pandas DataFrame,再清洗字段。
import pandas as pd def get_kline(self, code: str, category: int = 9, count: int = 800, market: int = None): if market is None: market = 1 if code.startswith("6") else 0 with self.lock: raw = self.api.get_security_bars(category, market, code, 0, count) if not raw: return pd.DataFrame() df = self.api.to_df(raw) if df.empty: return df # 统一时间格式:把分开的年月日时分秒拼成datetime df["datetime"] = ( df["year"].astype(str) + "-" + df["month"].astype(str).str.zfill(2) + "-" + df["day"].astype(str).str.zfill(2) + " " + df["hour"].astype(str).str.zfill(2) + ":" + df["minute"].astype(str).str.zfill(2) ) df["code"] = code df = df[["datetime", "code", "open", "close", "high", "low", "vol", "amount"]] return df代码里的market自动判断值得单独说一句。股票代码以 6 开头是上海,其他大多是深圳,这是最常用的区分方法。但要注意,指数、基金、转债的规则不完全一样,如果你要拉指数数据,建议显式传market参数,不要依赖自动判断。比如上证指数代码是999999,虽然首位不是6,但市场是上海(1)。
3.2 一次最多取800根,怎么突破
很多第一次用 pytdx 的人都会遇到一个疑问:我明明要2000根日K,为什么只返回800根?这其实不是 bug,而是服务器端的限制。单次请求最多返回 800 根K线,想要更长的历史,就得用start参数翻页。
def get_kline_history(self, code: str, category: int = 9, total: int = 3000): all_data = [] start = 0 while start < total: with self.lock: raw = self.api.get_security_bars(category, market, code, start, min(800, total - start)) if not raw: break df = self.api.to_df(raw) if df.empty: break all_data.append(df) start += len(df) if not all_data: return pd.DataFrame() df_all = pd.concat(all_data, ignore_index=True) # 按时间正序 df_all = df_all.sort_values("datetime").reset_index(drop=True) return df_all这里要特别注意:翻页的时候,start是“从最新数据往回偏移的根数”,不是日期。也就是说start=0是最新的800根,start=800是再往前推800根。方向不要搞反,否则取出来的是未来数据,回测就会莫名其妙地“穿越”。
3.3 实时行情快照的获取
做盘中监控的时候,实时报价比K线更常用。get_security_quotes支持一次传多只股票,接口返回的是最新价、开盘、最高、最低、买卖五档、内外盘、成交额等字段。
def get_quotes(self, codes: list): # 自动判断市场 qlist = [(1 if c.startswith("6") else 0, c) for c in codes] with self.lock: quotes = self.api.get_security_quotes(qlist) if not quotes: return pd.DataFrame() df = self.api.to_df(quotes) keep = ["market", "code", "price", "last_close", "open", "high", "low", "vol", "amount", "bid1", "ask1", "bid_vol1", "ask_vol1"] df = df[[c for c in keep if c in df.columns]] return df实测来看,get_security_quotes一次传50到100只股票是比较合理的范围,数量太大会触发服务器风控,返回可能不完整。如果你要监控全市场4000多只股票,建议分批拉取,并且每批次之间做一个小延迟。
3.4 全市场股票列表同步
做全市场扫描或者选股的时候,需要先拿到一份股票列表。pytdx 的get_security_list是按市场、按位置拉取的,一次最多取 1000 条(实际不同服务器可能返回 800 条),所以要用循环来拉全。
def get_all_securities(self): all_records = [] for market in (0, 1): # 0深圳 1上海 total = self.api.get_security_count(market) start = 0 while start < total: with self.lock: records = self.api.get_security_list(market, start) if not records: break all_records.extend(records) start += len(records) df = pd.DataFrame(all_records) if not df.empty: df = df[["code", "name", "pre_close", "volunit", "decimal_point"]] return dfget_all_securities返回的字段里有code、name、pre_close等,够用。注意不要每天重复拉全量列表,通常开市前拉一次存到本地就足够。
4. 数据持久化与增量更新策略
4.1 为什么用 SQLite 而不是 CSV
很多个人项目图省事,直接把数据存成 CSV,我发现数据量一大就成灾难。一是每次全量读进内存慢,二是做增量更新的时候,必须把整个文件读出来去重再写回,性能和可靠性都差。所以我选 SQLite,它不需要单独启动服务,Python 内置支持,而且支持事务和主键去重,对个人量化项目来说足够了。
建表语句我这样设计:
CREATE TABLE IF NOT EXISTS daily_kline ( code VARCHAR(10), date VARCHAR(10), open REAL, high REAL, low REAL, close REAL, vol INTEGER, amount REAL, PRIMARY KEY (code, date) ); CREATE INDEX IF NOT EXISTS idx_daily_code ON daily_kline(code);为什么主键要设置成(code, date)的组合?因为同一只股票同一天只能有一条日K,这样数据库层面就去重了。写入的时候用INSERT OR REPLACE,就算重复拉取也不会产生脏数据。
4.2 增量更新的核心逻辑
全量更新全部历史数据只需要做一次,之后每天只要拉最新几根K线就行。增量更新的逻辑,我写成这样:
def update_daily(self, code: str): # 1. 查询本地最新日期 latest = self.get_latest_date(code) # 2. 讨论需要拉多少根 if latest is None: total = 800 # 无本地数据,先拉800根再说 else: # 简单策略:距离现在的自然日差 + 10根容错 days = (pd.Timestamp.now().date() - pd.Timestamp(latest).date()).days total = min(days + 10, 800) # 3. 拉取并通过主键写库 df = self.get_kline(code, count=total) if df.empty: return df["date"] = pd.to_datetime(df["datetime"]).dt.date.astype(str) self.save_to_db(df)这里有个细节:total计算不能只看“缺失几天就拉几根”,因为中间可能有停牌、节假日,而且服务器可能还没更新最后一根完整日K。所以加一个10根左右的容错,确保补全。拉多了一点没关系,反正有主键去重。
4.3 批量更新与并发限速
单线程跑全市场更新太慢了,我用ThreadPoolExecutor做并发,但并发数一定要控制好。行情服务器对单个IP的连接数有硬限制,实测并发超过 10 就很容易出现超时或者连接被重置。
from concurrent.futures import ThreadPoolExecutor, as_completed def batch_update(self, codes: list, max_workers: int = 6): with ThreadPoolExecutor(max_workers=max_workers) as executor: futures = {executor.submit(self.update_daily, c): c for c in codes} for future in as_completed(futures): code = futures[future] try: future.result() print(f"{code} updated") except Exception as e: print(f"{code} failed: {e}")另外,并发场景下,SQLite 写入需要小心。SQLite 同一时刻只允许一个写事务,多个线程同时写会报database is locked。解决办法有两个:一是写操作全部集中到一个专用连接里串行执行,二是使用 WAL 模式减少读写锁冲突。
conn = sqlite3.connect("tdx.db", timeout=30) conn.execute("PRAGMA journal_mode=WAL;")加了 WAL 模式之后,并发读写表现会好很多,但仍建议把“查询数据”和“写入数据”用不同的连接对象分开,避免在同一个连接上交错操作。
5. 常见问题排查与稳定性优化
5.1 高频问题速查表
| 现象 | 原因 | 解决办法 |
|---|---|---|
| connect 返回 False | 服务器IP失效或端口不通 | 换一个服务器,维护候补列表 |
| 某个代码取不到K线 | market 判断错误 | 手动传入 market 参数 |
| 返回数据只有800根 | 单次请求上限 | 用 start 翻页 |
| 线程并发时连接被重置 | 并发数过高 | 降低 worker 数量,加锁 |
| 数据库报 locked | 多线程同时写 SQLite | 使用 WAL 模式或串行写入 |
| 盘中取“当日K线”为空 | 服务器尚未生成该周期K线 | 改拉实时报价,或者延迟取数 |
5.2 数据可靠性检查
数据接口不能只写“能跑就行”,还要考虑数据对不对。我自己的代码里加了一个简单的校验:把拉到的日K按时间排序后,检查 open、high、low、close 四价是否异常。比如 high 必须大于等于 open 和 close,low 必须小于等于 open 和 close,量不能为负数。如果发现异常,宁可跳过这只股票也不把脏数据写进库里。
def validate_kline(self, df: pd.DataFrame) -> bool: if df.empty: return False if (df["high"] < df["low"]).any(): return False if (df["vol"] < 0).any(): return False return True经验之谈:行情数据偶尔会有个别异常点,比如某天最高价突然比前后高出一大截,但这不是接口错误,而是除权除息或者服务器数据校正的问题。如果你做的是回测,最好在拿到数据后做一个前复权处理,否则历史收益算出来偏差会很大。
5.3 稳定性优化心得
这套接口上线跑了一段时间之后,我总结出三个提高稳定性的关键点。
一是连接心跳。pytdx 的heartbeat=True必须开,否则长时间空闲会被服务器主动断开。二是断线重连。不要在每次取数时才判断连接状态,而是写一个独立的定时任务,每隔几分钟检查一次连接;断开了就自动重连。三是服务器列表要常更新。公网开放的行情服务器名单会变化,我每隔一段时间就测一次延迟,把失效的IP从列表里删掉,把表现好的放前面。
另外,取数任务尽量避开开盘后的尖峰时段。比如交易日 9:30 到 10:00 服务器压力最大,如果只是做盘后数据更新,建议下午 15:30 之后再拉,成功率会高很多。
6. 数据接口的指标计算与扩展应用
6.1 用 pandas 快速计算常用指标
数据接口的价值不只是拿原始K线,更重要的是能跟后续分析接上。我习惯在拿到 DataFrame 之后,直接用 pandas 算指标。比如常见的 MA、RSI,代码很简单。
def add_ma(df, window=5): df[f"ma{window}"] = df["close"].rolling(window).mean() return df def add_rsi(df, window=14): delta = df["close"].diff() gain = delta.clip(lower=0).rolling(window).mean() loss = -delta.clip(upper=0).rolling(window).mean() df[f"rsi{window}"] = 100 - 100 / (1 + gain / loss) return df网上那些“98%胜率”“顶底信号”指标,很多本质就是这类技术指标的组合。我自己不迷信胜率数字,但有了稳定的数据接口之后,你可以拿任何指标到历史数据上回测验证,这是最有价值的事。
6.2 把接口输出的数据用于选股策略
接口设计好之后,跑一个简单的选股策略就非常顺。比如选出“日K突破20日均线且成交量放大1.5倍”的股票:
def scan_stocks(codes): result = [] for code in codes: df = get_kline(code, count=30) if df.empty: continue df = add_ma(df, 20) latest = df.iloc[-1] prev = df.iloc[-2] if latest["close"] > latest["ma20"] and prev["close"] <= prev["ma20"]: if latest["vol"] > prev["vol"] * 1.5: result.append(code) return result配合前面说的并发批量更新,全市场扫描一次只需要几分钟。这已经是个人量化研究里一个很实用的工具了。
6.3 和通达信本身的配合
最后说一句,这套 Python 接口设计出来,不是为了“替代”通达信,而是补足它的短板。通达信的优势是看盘、公式编辑、板块联动,数据接口的优势是自动化、批量化、可编程。我实际工作中,常常是“通达信看盘、Python出数据、Excel出报表”三条线并行。把接口做成独立源码模块之后,你甚至可以把它封装成一个 Python 包,供团队里其他人直接 import 使用,这才是接口设计的真正价值。
写在最后
我实际把这一套接口跑了大半年,从最开始手动导出Excel,到现在每天自动拉数据、入库、跑选股条件,节省的时间非常可观。要说最后一点心得,就是数据接口这类底层组件,一定要舍得花时间把结构设计好,别为了赶进度省掉异常处理或并发控制。前期多写几十行代码,后期能少熬好几个夜。希望这篇拆解能帮到正打算做同类数据接口的人,少踩几个我踩过的坑。
本文还有配套的精品资源,点击获取