ARTICLE DETAIL

资讯详情

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

TdxHqApi.dll 行情采集实战:从 TCP 协议到本地缓存

TdxHqApi.dll 行情采集实战:从 TCP 协议到本地缓存 简介这份资源围绕TdxHqApi.dll构建的证券行情实时监测系统展开面向具备一定C#或Java基础、希望深入理解金融行情数据采集与解析的开发者。系统通过持久化连接行情服务器持续获取证券代码、最新成交价、分时成交量及买卖盘挂单量价等核心指标并采用多线程异步机制对二进制流实时解码与结构化重组配合数据校验、断线重连与缓存补偿整体延迟控制在毫秒级。包内共299个文件以cs源码、zbak备份、txt说明、dll动态库、java源码、config配置、csproj工程文件及class字节码为主另含pdf、doc等文档压缩包约105.88MB覆盖数据接入、协议解析与业务逻辑三层模块。已有46人学习适合用于价格预警、技术指标计算与行情图表生成等场景的二次开发参考。1. 拆开一个只有 2MB 的行情采集器TdxHqApi.dll 能解决什么如果你做过 A 股行情采集大概率绕不开一个尴尬的现实官方行情接口要么贵要么权限卡得死而网页抓取又慢又不稳定分时数据延迟几分钟是常态。TdxHqApi.dll 这个动态库之所以在个人学习圈子里被反复提起是因为它把通达信行情服务器的通信协议封装成了几个导出函数调用方只需要传入服务器地址和端口就能拿到实时行情、历史 K 线和分时成交。整个库体积很小依赖也少适合拿来理解行情采集的完整链路——从连接服务器、发送请求、解析二进制包到落地成本地文件或数据库。它适合谁适合想自己动手写采集程序、又不希望从零逆向通信协议的个人开发者。不适合谁不适合需要 Level-2 逐笔委托、需要毫秒级撮合数据的场景那是另一套东西。下面按“能干什么 → 怎么调 → 坑在哪”的顺序拆一遍。2. 通信模型与接口选型为什么是 DLL 而不是 HTTP 轮询2.1 通达信行情服务器的通信特征通达信行情服务器走的是 TCP 长连接请求和响应都是二进制格式不是 HTTP。每个请求包有固定的头部结构包含请求类型、序列号、包体长度等字段响应包同样有头部后面跟具体数据。TdxHqApi.dll 做的事情就是把这套二进制协议封装成 C 风格的导出函数调用方不需要自己拼包和拆包。常见做法是先调用连接函数建立 TCP 连接然后循环调用行情请求函数拿到结构体数组后再做后续处理。这种模型和 HTTP 轮询的本质区别在于HTTP 每次请求都要重新建连除非用 keep-alive头部开销大而且行情服务器根本不提供 HTTP 接口。用 DLL 走原生 TCP单次请求的往返延迟可以压到几十毫秒级别对于分钟级或秒级采集来说完全够用。另一个好处是DLL 内部通常维护了连接状态断线后可以重新连接不需要调用方自己管理 socket 生命周期。但要注意这个 DLL 是 32 位的因为通达信客户端本身是 32 位程序。如果你的采集程序编译成 64 位加载会直接失败。这是第一个选型约束宿主进程必须是 32 位或者用 32 位的 Python/Node 来调用。2.2 导出函数与调用约定DLL 的导出函数通常是 stdcall 调用约定在 C# 里用[DllImport]声明时要加CallingConvention CallingConvention.StdCall。在 Python 里用ctypes加载时要指定windll而不是cdll。函数名一般包括连接、断开、获取行情数量、获取行情数据、获取 K 线数据、获取分时数据等。参数里经常出现服务器 IP 和端口通达信常用的行情服务器端口是 7709但不同服务器可能不同需要自己准备一份可用服务器列表。下面是一段 Python 加载 DLL 并建立连接的示例import ctypes from ctypes import wintypes # 加载 32 位 DLL注意 Python 也必须是 32 位 tdx ctypes.windll.LoadLibrary(r./TdxHqApi.dll) # 声明函数原型stdcall 用 windll 加载后默认就是 stdcall # 连接服务器参数为 IP 字符串和端口 tdx.TdxHq_Connect.argtypes [ctypes.c_char_p, ctypes.c_ushort] tdx.TdxHq_Connect.restype ctypes.c_bool # 获取行情数量参数为市场代码0 深市1 沪市 tdx.TdxHq_GetSecurityCount.argtypes [ctypes.c_ubyte] tdx.TdxHq_GetSecurityCount.restype ctypes.c_ushort ip b119.147.212.81 # 示例服务器实际需替换为可用地址 port 7709 ok tdx.TdxHq_Connect(ip, port) if not ok: raise RuntimeError(连接行情服务器失败) count tdx.TdxHq_GetSecurityCount(0) print(f深市证券数量: {count})这段代码的逻辑是先加载 DLL然后声明两个函数的参数类型和返回类型。TdxHq_Connect接收 IP 和端口返回布尔值表示是否连接成功。TdxHq_GetSecurityCount接收市场代码返回该市场的证券总数。参数说明市场代码 0 代表深圳1 代表上海这是通达信体系的约定。注意 IP 和端口需要替换成实际可用的服务器不同网络环境下可用服务器不同这是采集能否跑通的第一道门槛。2.3 数据包结构与解析方式DLL 返回的行情数据通常是结构体数组每个结构体包含股票代码、最新价、买卖五档、成交量、成交额等字段。K 线数据则包含日期、开盘、最高、最低、收盘、成交量、成交额。分时数据包含时间、价格、成交量、均价。这些结构体在 DLL 的头文件里有定义用 ctypes 调用时需要自己定义对应的Structure类字段顺序和类型必须和头文件完全一致否则解析出来的数据全是乱的。常见做法是先用TdxHq_GetSecurityQuotes拿到实时行情再用TdxHq_GetSecurityBars拿历史 K 线最后用TdxHq_GetMinuteTimeData拿分时。每个函数返回的是数据条数数据通过指针参数传出。调用方需要预先分配好缓冲区缓冲区大小要足够容纳最大返回条数否则会截断或报错。3. 从连接服务器到落地 CSV一套可复现的采集流程3.1 准备服务器列表与连接重试采集程序最怕的就是连不上服务器。通达信的行情服务器有很多个但并不是每个都随时可用。我一般会准备一个文本文件每行一个IP:端口程序启动时逐个尝试连接连上就停。连接失败的原因可能是服务器下线、网络不通、或者并发连接数超限。重试策略上不要死循环重连同一个 IP而是轮换列表每次失败后 sleep 一两秒再试下一个。import time def load_servers(pathservers.txt): with open(path, r, encodingutf-8) as f: return [line.strip() for line in f if line.strip()] def connect_any(tdx, servers): for addr in servers: ip, port addr.split(:) if tdx.TdxHq_Connect(ip.encode(), int(port)): print(f已连接 {addr}) return addr time.sleep(0.5) raise RuntimeError(所有服务器均连接失败)逻辑说明load_servers从文件读取服务器地址connect_any遍历列表逐个尝试。参数方面time.sleep(0.5)是给 TCP 连接留出超时时间避免瞬间大量重试被服务器拒绝。如果所有服务器都失败直接抛异常不要静默继续。3.2 拉取实时行情并写入 CSV连接成功后就可以拉取实时行情了。以深市为例先获取证券数量再分批获取行情。DLL 通常限制单次最多获取 80 条行情所以需要循环调用。每条行情包含股票代码、最新价、买卖五档价格和量、成交量、成交额等。写入 CSV 时建议用追加模式每次采集打一个时间戳方便后续按时间筛选。import csv from datetime import datetime class Quote(ctypes.Structure): _fields_ [ (code, ctypes.c_char * 6), (price, ctypes.c_float), (buy1, ctypes.c_float), (sell1, ctypes.c_float), (vol, ctypes.c_int), (amount, ctypes.c_float), ] def fetch_quotes(tdx, market0, batch80): total tdx.TdxHq_GetSecurityCount(market) quotes [] for start in range(0, total, batch): buf (Quote * batch)() ret tdx.TdxHq_GetSecurityQuotes(market, start, batch, buf) for i in range(ret): quotes.append(buf[i]) return quotes def save_csv(quotes, pathquotes.csv): with open(path, a, newline, encodingutf-8) as f: writer csv.writer(f) ts datetime.now().strftime(%Y-%m-%d %H:%M:%S) for q in quotes: writer.writerow([ts, q.code.decode(), q.price, q.buy1, q.sell1, q.vol, q.amount])逻辑说明Quote结构体定义了行情字段字段顺序必须和 DLL 头文件一致。fetch_quotes先拿总数再按 80 条一批循环获取。save_csv用追加模式写入每行带时间戳。参数说明market0是深市batch80是单次最大条数这个值不能随意调大超过 DLL 内部缓冲区会出错。注意code字段是定长字符数组需要 decode 成字符串。3.3 历史 K 线与分时数据的采集节奏实时行情是快照历史 K 线和分时数据才是做分析的基础。K 线数据按日期范围请求DLL 通常要求传入起始日期和结束日期格式是YYYYMMDD的整数。分时数据只能拿当天的历史分时拿不到。采集节奏上K 线可以每天收盘后跑一次分时可以在盘中每隔几分钟拉一次。注意不要高频请求同一只股票的分时服务器会限制频率触发后可能直接断开连接。def fetch_bars(tdx, code, market, start_date, end_date, count800): buf (Bar * count)() ret tdx.TdxHq_GetSecurityBars(market, code.encode(), start_date, end_date, count, buf) return [buf[i] for i in range(ret)]参数说明code是 6 位股票代码market是市场代码start_date和end_date是整数日期count是最大返回条数。常见做法是每次请求不超过 800 条超过就分段请求。返回的Bar结构体包含日期、开盘、最高、最低、收盘、成交量、成交额。4. 避坑与排查连接、解析、频率控制里的血泪经验4.1 连接成功但拿不到数据现象TdxHq_Connect返回 true但调用TdxHq_GetSecurityCount返回 0 或负数。原因通常是服务器虽然 TCP 连上了但行情服务没有正常响应或者该服务器只提供部分市场数据。解决方法是换服务器不要在一个 IP 上死磕。我一般会准备至少 5 个不同网段的服务器地址轮换测试。4.2 结构体字段错位导致数据全乱现象拿到的价格是几万或负数成交量是随机大数。原因几乎都是 ctypes 的Structure字段顺序或类型和 DLL 头文件不一致。比如把c_float写成了c_double或者漏掉了某个保留字段。解决方法是找到 DLL 配套的头文件逐字段对照不要凭猜测写。如果没有头文件用dumpbin /exports看导出函数名再结合网络上的结构体定义交叉验证。4.3 高频请求被服务器断开现象程序跑了几分钟后所有请求返回失败重连也连不上。原因是请求频率太高触发了服务器的限流。通达信行情服务器对单 IP 的请求频率有隐性限制具体阈值不公开。解决方法是控制采集间隔实时行情不要低于 3 秒一次分时不要低于 1 分钟一次。如果确实需要更高频率考虑多服务器轮换但不要用同一 IP 并发。4.4 32 位与 64 位不匹配现象加载 DLL 时报OSError: [WinError 193] %1 不是有效的 Win32 应用程序。原因是 Python 是 64 位而 DLL 是 32 位。解决方法是换 32 位 Python或者用 32 位的宿主程序调用。这个问题没有绕过的方法只能匹配位数。4.5 日期格式传错导致 K 线为空现象TdxHq_GetSecurityBars返回 0 条。原因通常是日期格式不对比如传了字符串而不是整数或者传了2024-01-01而不是20240101。解决方法是统一用整数日期并且在请求前打印出来确认。5. 进阶技巧用本地缓存和增量更新把采集成本压下来采集跑通之后下一个问题就是效率。全量拉取所有股票的 K 线每次都要几十分钟而且大部分数据是重复的。我一般会做两层缓存第一层是本地 SQLite按股票代码和日期建唯一索引第二层是增量更新每次只请求最近 N 天的数据用INSERT OR REPLACE写入。这样第一次全量之后后续每天只需要几分钟就能完成更新。import sqlite3 def init_db(pathbars.db): conn sqlite3.connect(path) conn.execute( CREATE TABLE IF NOT EXISTS bars ( code TEXT, date INTEGER, open REAL, high REAL, low REAL, close REAL, vol INTEGER, amount REAL, PRIMARY KEY (code, date) ) ) return conn def upsert_bars(conn, code, bars): rows [(code, b.date, b.open, b.high, b.low, b.close, b.vol, b.amount) for b in bars] conn.executemany(INSERT OR REPLACE INTO bars VALUES (?,?,?,?,?,?,?,?), rows) conn.commit()逻辑说明init_db建表时用(code, date)做联合主键保证同一只股票同一天只有一条记录。upsert_bars用INSERT OR REPLACE实现增量更新重复数据自动覆盖。参数说明date存整数和 DLL 返回的日期格式一致方便比较。增量更新时先查本地最大日期再请求该日期之后的数据避免全量拉取。另一个技巧是分市场、分板块采集。深市和沪市分开跑创业板和主板分开跑这样单次请求的数据量小失败后重试成本低。我习惯在采集前先拉一遍证券列表把退市和停牌的股票过滤掉减少无效请求。验证采集结果是否正确最简单的办法是拿几只熟悉的股票对比通达信客户端里的价格和 K 线。如果收盘价对不上大概率是复权问题——DLL 返回的通常是不复权数据需要自己根据除权除息信息做前复权或后复权。这一步没有捷径只能老老实实处理。从那以后我每次写采集程序都会先把服务器列表、结构体定义、日期格式这三样东西各验证一遍再跑全量。希望帮到你。本文还有配套的精品资源点击获取
返回列表