拓冰建站拓冰建站
首页 / 资讯中心 / 正文

通达信数据读取利器:基于pytdx的Python封装工具设计

简介这是一套面向金融数据分析从业者与Python进阶开发者的通达信数据接入工具旨在降低pytdx原生库的使用门槛解决手动连接行情服务器、解析复杂数据格式、管理多源配置等实际痛点适用于量化研究、实盘策略开发及教学演示等场景。资源包共203个文件含79个Python脚本覆盖行情获取、K线解析、板块分类、指标计算等核心逻辑、33个.dat二进制数据文件用于本地缓存或测试样本、19个.cfg配置文件如tdxzs.cfg、tdxhy.cfg等预置主流通达信服务器与市场参数、19个.md文档含快速上手指南、API说明与常见问题另有Dockerfile、Makefile、.yaml等工程化支持文件压缩包大小为54.79MB。已有477人学习下载。用户可直接复用封装后的简洁接口读取A股/期货实时与历史行情无需深入pytdx底层协议完整目录结构体现模块化设计思想配置与代码分离便于二次开发与环境迁移配套许可证文件与标准化项目配置符合生产级工具交付规范。 做量化或者自己研究股票数据的人大概率都碰到过这么个问题通达信看盘确实方便各种指标、公式、板块一眼就能看到但真想把行情数据拿出来自己算却绕了一大圈还是卡在数据获取这一步。手动导出Excel、写爬虫抓网页、翻第三方接口文档……每一种方案都各有各的麻烦。我这次做的东西就是围绕“基于Python的通达信数据读取pytdx库设计源码封装工具”这个方向把pytdx这个底层库重新包了一层做成统一、好调、不容易踩坑的数据读取工具。算是把以前重复写过的连接、取数、转DataFrame、异常处理这些脏活累活一次性收进一个类里。这篇文章会把设计思路、核心接口、封装代码、踩坑记录都摊开来讲适合刚接触Python行情数据、想搞量化策略或者只是想省点时间的读者。1. 项目概览与设计思路1.1 为什么需要再包一层pytdxpytdx本身是个不错的库直接连通达信行情服务器能拿到实时行情、历史K线、财务数据、分笔成交等等而且不走官方付费接口社区维护也比较活跃。但真实用起来你会发现它更像是一套底层通信协议而不是一个开箱即用的数据工具。一个最典型的问题是连接管理。pytdx的API需要手动connect、手动disconnect用完后忘记断开连接就一直挂着短时间多跑几次就能把服务器连接数占满。另一个问题是数据格式不统一。有的接口返回list有的返回dict有的字段是英文缩写有的字段是数字代号每次用都要去翻一遍文档。还有超时问题、服务器IP失效问题、非交易时段数据为空问题一旦遇到就是一堆重复的try except。我封装这个工具的目的很简单把pytdx变成一个“给个股票代码就能拿到干净DataFrame”的黑盒。内部处理好连接池、重试、超时、数据格式化这些事外部只暴露几个语义清晰的方法。这样一来写策略的人不用陷入通信细节写研究脚本的人也不用每次从零开始拼代码。1.2 pytdx与其他数据获取方案的对比在做封装之前我其实把市面上常见的几种行情数据获取方式都过了一遍这里直接做个对比方便你理解为什么围绕pytdx做封装是合理路径。方案优点缺点适用场景通达信软件手动导出操作简单无需编程无法自动化、数据覆盖有限一次性少量数据爬虫抓取网页行情无需部署本地服务反爬风险、延迟高、数据不稳定临时应急通达信DLL扩展与软件内部深度集成开发门槛高、依赖软件运行在通达信内部跑复杂逻辑第三方HTTP接口数据规范、文档齐全收费、频率限制、数据可控性差商业量化产品pytdx直连免费、数据全、实时性好需要自己处理连接和格式个人量化研究对比下来pytdx是个人研究场景性价比最高的方案。免费、不限制请求次数、底层协议稳定、数据字段覆盖完整。缺点是它把复杂度留给了使用者而“封装”正好解决的就是这个问题。1.3 工具的服务对象与使用场景这个封装工具我定位成三类人在用第一类是自己写量化策略的人。需要历史K线做回测、需要实时行情做监控、需要财务数据做基本面筛选这些接口统一封装后策略代码可以瞬间清爽很多。第二类是数据分析师和研究员。他们可能对Python非常熟练但对行情协议不熟。给他们一个get_daily_kline(000001, 100)这样的方法比让他们去理解market、category、start这些参数友好得多。第三类是维护和维护系统的人。比如定时任务每天收盘后拉取全市场数据入库封装工具里增加的自动重连、多服务器切换、失败重试机制能显著降低半夜告警的概率。2. 核心接口与数据模型详解2.1 安装、连接与服务器选择首先是安装。pytdx可以直接通过pip安装这是基础中的基础。pip install pytdx安装完成后先看两样东西一个是pytdx的hq_hosts这里放着一批通达信行情服务器的IP和端口另一个是TdxHq_API这个核心类所有行情请求都通过它发出。from pytdx.hq import TdxHq_API from pytdx.config.hosts import hq_hosts import random host_info random.choice(hq_hosts) api TdxHq_API() if api.connect(host_info[ip], host_info[port], time_out15): print(f连接成功: {host_info[ip]}:{host_info[port]}) else: print(连接失败)这里有几个实际经验。一是服务器延迟差异很大不同IP的响应速度可能差好几倍建议连接前排一下所有服务器的延迟选最快的二是尽量避开交易高峰时段频繁重连否则容易触发服务器端的频率限制三是time_out不要设太短行情服务器在大波动时响应会变慢5秒以内的超时容易误报。封装工具有个核心逻辑就是自动服务器调度。当一台服务器连接失败或者连续请求超时自动切换到下一台不让单点故障影响整个数据流程。2.2 常用接口与返回字段解释pytdx的接口不少但日常用得最频繁的其实就这几个封装时优先把它们做稳。第一个是实时行情快照get_security_quotes。它接受一个(market, code)的列表返回最新买卖盘口和成交数据。quotes api.get_security_quotes([(0, 000001), (1, 600000)])第二个是历史K线get_security_bars。这个接口是回测和分析的主力支持不同周期。category参数是关键9代表日K0代表5分钟K线1代表15分钟K线2代表30分钟K线3代表60分钟K线5代表周K6代表月K7代表1分钟K线。start表示起始位置偏移count表示返回数量日K最多一次取800根。# 获取平安银行最近100根日K线 bars api.get_security_bars(9, 0, 000001, 0, 100)第三个是指数K线get_index_bars参数格式和个股K线基本一致主要用于读取上证指数、深证成指等大盘指数。第四个是财务数据get_finance_info。它返回每股收益、每股净资产、总股本、流通股本、股东人数等一批基本面字段。注意这些字段是中文拼音缩写封装时最好重新映射成英文或中文可读字段名。第五个是分笔成交get_transaction_data。它返回当日的逐笔成交明细适合做盘口分析。历史分笔则用get_history_transaction_data。2.3 返回数据的单位与字段坑这是最容易被忽视的部分。pytdx返回的数据直接存下来用很容易出问题因为通达信协议里的单位和你在看盘软件里看到的单位不总是完全一致。以K线数据为例open、high、low、close这些价格字段单位是元一般是两位小数这个没有歧义。但vol字段要特别注意。在通达信客户端里成交量单位通常是“手”而pytdx接口返回的vol实际是“股”。也就是说你需要根据股票的每手股数A股固定100股/手做一次换算。amount字段单位是元这个相对明确。除了单位还有字段命名问题。同一个含义在不同接口里字段名可能完全不同。比如K线接口里用datetime表示时间分笔接口里用time表示时间财务接口里直接没有时间字段需要你自己记录拉取日期。封装时我统一把时间字段改成datetime类型并放到DataFrame的索引或者第一列方便后续时序分析。另外价格字段虽然返回的是浮点数但通达信协议里很多价格是按整数传输的解析时才除以100。所以如果你看到某个字段突然大了一百倍先想想是不是单位或者精度问题不要急着怀疑数据坏了。3. 封装工具的设计与实现3.1 整体架构与模块划分封装工具我分成了三层。最底层是连接管理层负责服务器选择、连接维护、断线重连。中间层是数据请求层每个公开方法对应一类数据接口统一处理参数格式化和异常捕获。最上层是数据模型层把所有返回结果规范成DataFrame并对字段名、数据单位做统一处理。下面这张模块划分示意图虽然不能直接运行但能帮你理解整个包的结构封装工具 ├── 连接管理模块 │ ├── 服务器列表管理 │ ├── 自动重连 │ └── 连接池状态维护 ├── 数据请求模块 │ ├── get_realtime_quotes │ ├── get_daily_kline │ ├── get_minute_kline │ ├── get_index_kline │ ├── get_finance │ └── get_transaction └── 数据规范化模块 ├── 字段名映射 ├── 数据类型转换 └── 单位换算这样一个结构好处是每一层都可以独立测试。连接层坏了不影响数据格式化逻辑数据格式化逻辑错了也不需要去翻底层通信代码。后续要加新的数据接口只需要在数据请求层加一个方法然后复用规范化模块即可。3.2 核心类的关键代码下面这个是封装工具的核心类代码可以直接抄去用。我删掉了一些不影响主流程的日志和统计逻辑保留主干。import random import time import logging from typing import List, Tuple, Optional import pandas as pd from pytdx.hq import TdxHq_API from pytdx.config.hosts import hq_hosts logger logging.getLogger(__name__) # K线周期映射 BAR_TYPE { 5min: 0, 15min: 1, 30min: 2, 60min: 3, day: 9, week: 5, month: 6, 1min: 7, } class TdxDataReader: 通达信行情数据读取封装类 def __init__(self, host: Optional[str] None, port: Optional[int] None, timeout: int 15, max_retry: int 3): self.timeout timeout self.max_retry max_retry self._connect_flag False self.api TdxHq_API() if host and port: self.host, self.port host, port else: self.host, self.port self._pick_server() staticmethod def _pick_server() - Tuple[str, int]: 随机选择一台行情服务器实际可改成按延迟排序 host_info random.choice(hq_hosts) return host_info[ip], host_info[port] def connect(self) - bool: 建立连接失败时自动换服务器重试 for attempt in range(self.max_retry): try: if self.api.connect(self.host, self.port, time_outself.timeout): self._connect_flag True logger.info(f连接成功: {self.host}:{self.port}) return True logger.warning(f第{attempt 1}次连接失败: {self.host}:{self.port}) except Exception as e: logger.error(f连接异常: {e}) # 换一台服务器重试 self.host, self.port self._pick_server() return False def disconnect(self): if self._connect_flag: self.api.disconnect() self._connect_flag False def _ensure_connected(self): 确保连接可用断开时自动重连 if not self._connect_flag: if not self.connect(): raise ConnectionError(无法连接到通达信行情服务器) def _request(self, func, *args, **kwargs): 统一请求入口带重试机制 for attempt in range(self.max_retry): try: self._ensure_connected() data func(*args, **kwargs) if data is not None: return data except Exception as e: logger.error(f请求失败: {e}, 第{attempt 1}次重试) self.disconnect() time.sleep(1) return None staticmethod def _guess_market(code: str) - int: 根据股票代码前缀判断市场6开头是上海其余默认深圳 if code.startswith((6, 9)): return 1 return 0 def get_realtime_quotes(self, codes: List[str]) - pd.DataFrame: 获取实时行情快照 market_code_list [(self._guess_market(code), code) for code in codes] data self._request(self.api.get_security_quotes, market_code_list) if not data: return pd.DataFrame() df pd.DataFrame(data) df[code] df[code].astype(str) return df def get_daily_kline(self, code: str, count: int 100) - pd.DataFrame: 获取日K线最多800条 market self._guess_market(code) data self._request( self.api.get_security_bars, BAR_TYPE[day], market, code, 0, count ) if not data: return pd.DataFrame() df pd.DataFrame(data) df[code] code df[datetime] pd.to_datetime(df[datetime]) # 成交量单位从股转手 df[vol] df[vol] / 100 return df def get_minute_kline(self, code: str, period: str 15min, count: int 800) - pd.DataFrame: 获取分钟K线period支持1min/5min/15min/30min/60min if period not in BAR_TYPE: raise ValueError(f不支持周期: {period}) market self._guess_market(code) data self._request( self.api.get_security_bars, BAR_TYPE[period], market, code, 0, count ) if not data: return pd.DataFrame() df pd.DataFrame(data) df[code] code df[datetime] pd.to_datetime(df[datetime]) df[vol] df[vol] / 100 return df def get_finance(self, code: str) - pd.Series: 获取财务数据返回Series market self._guess_market(code) data self._request(self.api.get_finance_info, market, code) if not data: return pd.Series() return pd.Series(data)这段代码里有两个细节可以说一下。第一个是_request统一入口。所有数据请求都走这个方法好处是重试逻辑、断开重连逻辑、异常记录逻辑只写一遍后续加接口不会漏掉这些保护。注意每次请求失败后先disconnect()再重试因为连接可能已经处于半死状态不主动断开的话重试大概率还是失败。第二个是_guess_market市场判断。其实通达信里市场代码是核心参数0是深圳1是上海写错市场代码会直接返回空数据。简单地按股票代码首位判断并不完全准确比如北交所代码8开头也是0市场但基本覆盖了主流的沪深A股场景。真要做到严谨建议维护一份股票代码到市场的映射表。3.3 快速上手调用示例与数据落地封装完之后使用体验就很舒服了。下面是几个直接能跑的调用示例。# 初始化读取器 reader TdxDataReader() if not reader.connect(): print(连接失败) exit() # 获取实时行情 quotes_df reader.get_realtime_quotes([000001, 600000, 300750]) print(quotes_df[[code, price, vol, amount]].head()) # 获取日K线并保存到CSV kline_df reader.get_daily_kline(000001, count500) kline_df.to_csv(pingan_bank_daily.csv, indexFalse) # 获取分钟K线 minute_df reader.get_minute_kline(600000, period30min, count200) print(minute_df.head()) # 获取财务数据 finance reader.get_finance(000001) print(finance) # 使用完释放连接 reader.disconnect()把K线数据落地到文件有个小技巧建议把datetime和code放在前两列这样后续用pandas合并数据或者回测脚本读取时会更方便。我习惯把单只股票的K线存成一个CSV文件文件名带代码和周期目录结构按数据/日K线/600000.csv这种方式组织。如果你打算做全市场扫描建议直接存到SQLite或者MySQL。SQLite不需要额外服务单机场景最省事。pandas的to_sql方法可以直接把DataFrame写进去封装工具返回的数据格式天然就是为pandas设计的。import sqlite3 conn sqlite3.connect(market_data.db) kline_df.to_sql(daily_kline, conn, if_existsappend, indexFalse) conn.close()注意to_sql重复运行会插入重复数据全市场扫描脚本里最好先按(code, datetime)去重或者建唯一索引。这个坑我踩过数据量大了之后重复行会让回测结果变得极其诡异。4. 常见问题与排查记录4.1 “pytdx无法获取除深圳市场以外的股票列表”问题排查这个是我在维护社区时看到问得最多的一个点值得单独说。很多人第一次用get_security_list去拉股票列表发现深圳市场的股票能正常返回上海市场却一直返回空或者报告异常。先说结论这通常不是pytdx库本身的问题而是使用方式和服务器状态的适应性问题。get_security_list接口有两个必要参数market表示市场0深圳、1上海start表示起始位置。很多人只传了market没传start或者传了start但超过了服务器返回的股票总数就会拿不到数据。另外部分行情服务器对get_security_list支持不稳定尤其是在非交易时段。我建议的做法是# 分页拉取上海市场股票列表 all_stocks [] start 0 while True: batch api.get_security_list(1, start) if not batch: break all_stocks.extend(batch) if len(batch) 1000: break start len(batch)这里有一个关键点get_security_list返回的数据量通常不是固定的所以不要用固定的步长去递增而是取本次返回的实际条数来做下一轮偏移。如果某次服务器返回空也不要立刻判定“到结尾了”先重试两次再结束。把这段逻辑封装在工具里能解决绝大多数“股票列表获取不全”的问题。4.2 数据为空、停牌、退市与边界情况处理使用过程中数据为空的情况很常见。新股上市第一天有些服务器的日K线还没缓存停牌股在交易时段可能没有实时行情退市股可能直接查不到任何数据。遇到这些情况pytdx返回的通常是None或者空列表。封装工具里的_request方法已经做了None判断但调用方也要有心理准备。我的经验是拉取全市场数据时先跑一遍股票列表再逐只拉K线对返回空DataFrame的股票单独记录日志不要直接跳过。因为空数据也可能意味着你的市场代码判断错了这不是数据问题而是代码bug。另外指数和个股的接口是不同的。很多人用get_security_bars去拉上证指数结果一直失败。指数要使用get_index_bars而且市场代码、代码编号都和个股不一样。比如上证指数市场是1代码是000001但指数代码也是000001很容易和股票平安银行混淆。我封装时把指数和个股接口分成了两个方法从根本上避免这种混淆。4.3 并发、连接池与性能优化pytdx的单连接其实是串行请求模式效率不高。如果你要拉几千只股票的数据串行跑一遍可能要十几分钟甚至更久。封装工具里我加了线程池方案但注意有度。一个比较稳妥的做法是准备一批TdxDataReader实例每个实例维护独立的连接然后丢进ThreadPoolExecutor里并发请求。我实测过4到6个连接并发时全市场日K线数据的拉取时间能缩短到原来的五分之一左右。但超过10个连接后服务器会开始拒绝连接或者频繁超时收益不升反降。from concurrent.futures import ThreadPoolExecutor, as_completed def fetch_one(reader, code): return reader.get_daily_kline(code, count100) readers [TdxDataReader() for _ in range(4)] for r in readers: r.connect() with ThreadPoolExecutor(max_workers4) as executor: futures [executor.submit(fetch_one, readers[i % 4], code) for i, code in enumerate(stock_list)] for future in as_completed(futures): df future.result() # 处理df这种并发方式有一个隐藏问题某个连接如果被服务器断开会导致分配到该连接的所有线程都报错。所以我的建议是在fetch_one里加一个简单的重试失败后重新连接当前reader再试一次。这比创建新reader实例要快得多因为TdxHq_API的连接建立本身还有一次握手开销。4.4 关于通达信指标公式、DLL和密码解锁的常见误区经常看到有人问“通达信指标带密码怎么解锁”、“通达信DLL怎么写”、“通达信指标源码怎么调用”。这类问题其实和pytdx是两种完全不同的技术路线。通达信的指标公式比如MA、MACD、KDJ的自定义版本运行在通达信软件内部DLL扩展也是给软件内部调用的。这些方案能做到的是“在通达信软件里画出你想要的线”不是把数据输出到Python环境里。很多人想要的其实是“把指标计算逻辑迁移到Python里”用talib或者Pandas自己算一遍指标那就完全不需要碰DLL和密码解锁这些事。pytdx定位在数据层它给你的是原始行情数据。指标计算在数据拿到之后自己算这是我觉得更干净、更可控的方案。所以如果你是为了写策略才去研究通达信公式不如直接把精力放在Python的指标计算上一步到位而且能彻底摆脱“只能在通达信里跑”的限制。5. 扩展思路从行情数据到策略落地5.1 复权数据处理pytdx返回的是不复权数据。做回测时不复权数据直接用于计算收益率会有严重误差因为分红送股会形成价格跳空看起来像暴跌实际上是除权除息导致的。要处理复权可以用get_xdxr_info接口获取除权除息信息然后自己计算前复权或者后复权价格。计算逻辑简单说一下先找到历史所有的除权除息事件根据送股比例、配股价、分红金额等数据把历史价格统一调整到当前价格口径。这个过程不是几十行代码能写完的但网上也有不少现成实现可以参考。我的建议是如果只是研究短期交易策略直接用不复权数据问题不大因为短期内的除权事件影响有限。如果做长期持有或者回测跨度超过半年一定要处理复权否则结果失真严重。5.2 增量更新与本地缓存行情数据属于典型的“一次下载、多次复用”数据。每次跑策略都重新全量拉取浪费流量和时间。合理的做法是定期增量更新。我设计的工具使用方式是这样的每天收盘后跑一个定时任务增量拉取当天新增的K线更新到本地SQLite数据库策略运行时不连接行情服务器只读本地数据库。这样不仅速度快而且不受行情服务器稳定性影响更重要是减少了被封IP的风险。增量更新的判断条件很简单看本地库里每只股票的最大日期然后从该日期之后开始拉取。这个逻辑我用SQL很容易实现但要注意datetime字段的时区统一问题别让时间比较出现偏差。5.3 与量化回测框架的衔接数据拿到手、格式清洗干净之后就能接进主流回测框架了。比如backtrader、vnpy或者自己写简单的回测循环。这里的关键是把pytdx数据的字段映射到回测框架要求的字段。大多数框架要求至少包含datetime, open, high, low, close, volume这几个字段封装工具返回的DataFrame直接rename一下就能用。df reader.get_daily_kline(000001, count500) df df.rename(columns{ datetime: datetime, open: open, high: high, low: low, close: close, vol: volume, amount: amount, })[[datetime, open, high, low, close, volume, amount]]如果发现回测框架要求的字段名不同只需要在接口层加一个参数控制输出字段命名不要让策略代码感知到底层数据源是谁这样以后换数据源成本会低很多。就我个人的实际使用体会来说这个封装工具最有价值的不是某一两个接口而是它把连接管理、重试机制、数据格式化这些“隐性成本”集中消化掉了。做量化研究的人精力应该花在策略逻辑上而不是反复折腾数据接口。后续我还会加更多功能比如日线全量拉取的分页处理、分钟线数据落库的性能优化、以及通过本地缓存减少重复请求。目前这个版本日常的行情读取、K线下载、财务数据获取和简单回测已经完全够用了。如果你也正在被通达信数据读取折腾建议直接照上面的思路自己封装一遍踩一遍坑比看十篇文档都管用。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门