用TdxHqApi.dll实现实时行情采集器:从接口调用到稳定运行

发布时间:2026/10/2 8:21:19
用TdxHqApi.dll实现实时行情采集器:从接口调用到稳定运行 简介一份基于通达信TdxHqApi.dll实现的实时数据采集器源码包面向金融量化开发者、股票行情分析人员用于从通达信行情接口高效获取实时数据解决手动抓取效率低、接口对接复杂等问题。压缩包共248个文件约105.88MB以84个C#源码文件和21个Java源码文件为主辅以23个DLL动态库、配置文件、说明文档及工程样例便于多语言环境调用与配置过渡。已有831人学习下载适合具备一定编程基础、希望掌握通达信接口封装与数据采集逻辑的开发者。资源内提供C#与Java两套调用实现涵盖行情库加载、错误事件处理、TradeX核心封装及App示例并附数据格式说明与工程文件可帮助理解接口交互流程、减少环境搭建障碍为实时行情采集、量化策略回测或交易工具开发提供可复用的代码基础。1. 借 TdxHqApi.dll 实现的实时数据采集器它到底解决什么问题“借TdxHqApi.dll实现的实时数据采集器StockRealData”这标题描述的事简单说就是把一个通达信行情DLL变成你自己可控的盘中数据管道。最典型的场景是开盘后一边盯行情软件一边手工往表格里填价格等填完价已经变了或者做策略回测需要分钟级甚至 tick 级数据却找不到稳定又便宜的行情源。这个方案让程序按你设定的频率去拉快照、逐笔、分时数据再落成 CSV、SQLite 或数据库表供策略、看板和复盘使用。适合人群也相对明确个人量化、小团队、要做盘中监控面板或攒行情历史库的开发者。这里要先泼一盆冷水这类DLL的接入成本从来不是“调通一个函数”而是后面的进程稳定性、时间戳对齐、字段解析边界。你从网上拿到 StockRealData.zip 这类资源时里面那颗 DLL 的版本和接口形态五花八门处理不好连第一行调用都跑不通。下文就从接口形态确认开始一步步把它变成能扛住整天的采集器。2. 把 TdxHqApi.dll 用起来先确认接口形态再写第一行调用拿到 StockRealData.zip 后第一件事不是急着编译工程而是检查里面的 TdxHqApi.dll 到底以什么方式暴露接口。这个 DLL 在市面上流通的版本并不统一同样一个文件名可能是纯 C 导出函数也可能是 COM 组件甚至某些版本同时支持两种。用错调用姿势轻则拿不到数据重则进程直接崩。所以本章先把选型理由讲清楚再给出确认形态和最小调用的完整路径。2.1 为什么选通达信行情 DLL 做实时数据源做实时行情采集常见备选有网页爬取、窗口自动化、行情DLL三种。网页爬取看上去最通用但行情网站延迟高字段埋在 HTML 结构里要额外解析改版一次就要重写一次解析器窗口自动化控制行情软件界面能拿到图但拿不到结构化数据而且刷新频率稍高界面就会卡死。TdxHqApi.dll 这类行情接口直接绕过界面提供的是内存结构体延迟和字段质量都高一个量级。很多版本的接口还自带历史数据查询对回测场景特别友好。选型边界也要说清楚它是行情通道不是交易通道不能用来下单接口授权范围以你拿到DLL的渠道说明为准并发连接数和请求频率有限制不适合商业级行情分发。如果你是自用或小团队采集频率控制在秒级一天跑下来数据量在百万行以内这套方案是性价比很高的选择。如果你的目标是全市场、全 tick、永久保存建议直接考虑商业行情源自建采集器的维护成本会超过数据本身的价值。2.2 先查导出表DLL 是 COM 还是 C 接口拿到 DLL 文件后不要凭文件名猜直接看导出表。在命令行里执行dumpbin /exports TdxHqApi.dll如果没有安装 Visual Studio也可以用 MinGW 套件里的 objdumpobjdump -x TdxHqApi.dll | grep -iE export输出结果里如果能看到DllGetClassObject、DllRegisterServer、DllCanUnloadNow这些名字说明它是 COM 组件调用方式是注册后创建实例如果看到Init、Login、Logout、GetQuote这类函数名说明是 C 风格接口直接在 C# 里用DllImport声明。还有一种情况是两种都有优先走 COM 路线因为 COM 封装一般把登录、连接、缓冲区管理都包好了用起来比裸 C 接口省心。确认是 COM 组件后在管理员命令行里注册regsvr32 TdxHqApi.dll注册成功后打开注册表编辑器展开HKEY_CLASSES_ROOT搜索 DLL 文件名关键词比如TdxHq或TdxApi能找到对应的 ProgID。这个 ProgID 是后面Type.GetTypeFromProgID的唯一线索不同版本命名差异很大务必以注册表里查到的为准。2.3 最小调用骨架登录一次取回一只股票的快照如果确定是 C 接口形态C# 里可以这样声明。注意函数名以你 dump 出来的导出表为准下面是一份常见命名示例[DllImport(TdxHqApi.dll, CallingConvention CallingConvention.Cdecl, CharSet CharSet.Ansi)] private static extern int TdxHq_Init(string configPath); [DllImport(TdxHqApi.dll, CallingConvention CallingConvention.Cdecl, CharSet CharSet.Ansi)] private static extern int TdxHq_Login(string server, int port, string user, string password); [DllImport(TdxHqApi.dll, CallingConvention CallingConvention.Cdecl)] private static extern int TdxHq_GetSecurityQuotes(string[] codes, int count, IntPtr outBuffer, ref int outCount); [DllImport(TdxHqApi.dll, CallingConvention CallingConvention.Cdecl)] private static extern int TdxHq_Logout();调用骨架如下TdxHq_Init(tdx_config.ini); int ret TdxHq_Login(你的行情服务器地址, 7709, , ); if (ret ! 0) { Console.WriteLine($登录失败错误码: {ret}); return; } string[] codes new string[] { 600000 }; // 证券代码 IntPtr buffer Marshal.AllocHGlobal(4096); int count 0; TdxHq_GetSecurityQuotes(codes, 1, buffer, ref count); // 此处再把 buffer 里的结构体解析成可读字段见第 4 章 TdxHq_Logout();如果你拿到的是 COM 形态调用方式换成创建实例加反射调用Type tdxType Type.GetTypeFromProgID(注册表里查到的ProgID); object api Activator.CreateInstance(tdxType); var result tdxType.InvokeMember(GetQuote, System.Reflection.BindingFlags.InvokeMethod, null, api, new object[] { 600000 });这段代码有两个参数细节要留意。第一CallingConvention不要凭感觉选C 接口老版本 DLL 常见的是Cdecl但也不排除StdCall对不上会直接报EntryPointNotFoundException或者调用栈损坏。第二CharSet用Ansi老行情接口返回的多是 GBK 编码字符串改成Unicode后代码端会收到乱码反序列化时疯涨。第一次调通后先别写循环打印一条快照和行情软件人工对一下字段值确认没有偏移错位再往下走。3. 把采集器从“取一次”改成“一直跑”实时管线的架构与参数跑通一次取快照很简单难的是让它从开盘前到收盘后稳定运行期间不掉线、不重连风暴、不产生数据空洞。直接把第 2 章的取数代码丢进while(true)是新手最常见翻车姿势开盘半小时连接就可能被服务端掐断或者跑一上午内存涨几百 MB。这套采集器我一般会拆成三层每一层各管一件事。3.1 采集器分层连接层、调度层、存储层连接层持有 DLL 实例负责Init / Login / Logout和连接状态检测。连接层不关心业务股票池只维护“当前登录是否有效”这一个状态。调度层维护股票池和轮询间隔按批次发起取数请求把返回数据交给存储层。存储层负责把结构体转成行记录写入文件或数据库也可以推到内存队列供策略端消费。为什么要强制分层DLL 实例的状态非常脆弱登录态一旦失效重连时调度层需要无感衔接存储层如果在取数线程里同步写文件磁盘抖动会直接拖垮取数节奏。层与层之间用队列解耦生产者和消费者各跑各的。调度线程只管问“数据来了没”存储线程只管“拿到数据怎么写”任何一层崩溃重启都不影响另一层。3.2 快照轮询还是逐笔拉取频率与数据量的权衡TdxHqApi.dll 大多是请求响应模型不是 WebSocket 那种服务端主动推送所以实时性是靠轮询频率撑起来的。不同行情类型的推荐策略差别很大我常用的参数如下行情类型获取方式推荐间隔单日数据量参考典型用途快照批量拉取3 秒单股票约 4800 条盘中监控、分钟因子、异动提醒逐笔按需拉取30 秒或事件触发单活跃股数千笔tick 级回测、盘口行为研究分时批量拉取60 秒单股票 240 条日内走势复现3 秒间隔不是拍脑袋定的。A 股连续竞价 4 小时共 14400 秒3 秒一轮对单只股票能攒约 4800 个快照点足够画平滑分时曲线压到 1 秒数据量翻三倍还更容易触发服务端频控。逐笔数据量要单独估算一只活跃股一天几千笔自选池几十只就是几十万行想全市场存逐笔一天就是千万级以上没有数仓规划不要轻易上。3.3 采集主循环一个能扛住整天的 C# 骨架下面这个骨架是采集器的核心把连接层、调度层、存储层的交互压缩到了一个循环里private void Run() { _api new TdxApiWrapper(); // 内部封装 DLL 调用 if (!_api.Connect(_server, _port, _user, _pass)) return; var pool LoadStockPool(stock_pool.txt); // 一行一个证券代码 int batchSize 80; // 单批请求股票数 int intervalMs 3000; // 轮询间隔 int failCount 0; while (!_cancelled) { var sw Stopwatch.StartNew(); try { foreach (var batch in Chunk(pool, batchSize)) { var rows _api.GetQuotes(batch); // 一次取一批 _storage.Write(rows); // 只入队不阻塞 Thread.Sleep(50); // 批间小停顿 } failCount 0; } catch (Exception ex) { _logger.Error(ex, 采集异常); failCount; if (failCount 3) Reconnect(); // 连续失败才重连 } var elapsed sw.ElapsedMilliseconds; _logger.Info($本轮耗时 {elapsed} ms); if (elapsed intervalMs) Thread.Sleep(intervalMs - (int)elapsed); // 若 elapsed 长期接近 intervalMs说明需要降频或优化存储 } }三个必调参数说明。batchSize受 DLL 响应包长度限制常见控制在 50 到 100太小请求次数多效率低太大返回可能被截断或超时。intervalMs要以本轮实际耗时为基准目标让耗时占比低于 70%留出余量给 CPU 抖动和 GC 停顿。批间Thread.Sleep(50)是为了防止请求排队不要设成 0否则连续请求会在对端被判定为扫描行为。为什么要连续失败 3 次才重连偶发一次超时很常见立刻重连反而容易引发重连风暴。Reconnect()里要先释放旧连接再重新Init顺序反了会导致句柄泄漏。日志里一定要记每轮耗时这条数据是后面排查数据空洞的唯一线索。4. 解析返回结构快照、逐笔、分时的字段映射与时间对齐取数只是把结构体从 DLL 拿到内存解析才是把内存变成能信的字段。很多采集器跑出来的数据没法用于策略问题不在采集在解析层把字段理解错了。这一章按快照、逐笔、分时三种类型讲字段映射再重点说两个高频翻车点。4.1 三种行情数据分别回答什么问题数据类型内容最小时间粒度适合做什么快照最新价、涨跌幅、五档、成交额轮询间隔异动监控、因子计算、看板逐笔每笔成交价、量、主动性每笔成交高频回测、盘口行为研究分时每分钟均价、累计成交量1 分钟日内策略、复盘快照能覆盖 80% 的场景先把它做扎实再说逐笔。逐笔字段里“主动买卖方向”在部分 DLL 版本里没有直接字段要用成交价与最近一次快照价的对比推断不要假设一定存在。分时数据和快照高度相关如果已经有 3 秒快照60 秒分时其实是冗余数据可以后面按需生成不必单独采集。4.2 从内存块到结构化记录解析代码与字段映射DLL 返回的通常是连续内存块里面按固定长度排列结构体。C# 端定义一个与 C 头文件对齐的结构体再用PtrToStructure批量转换。下面是一份常见结构体定义[StructLayout(LayoutKind.Sequential, Pack 1)] public unsafe struct QuoteData { public int Market; // 市场标识0深1沪 public fixed byte Code[8]; // 证券代码字节流 public int Time; // 时间字段可能是 HHMMSS 或当日累计秒 public float Price; // 最新价 public float PreClose; // 昨收 public float Open; public float High; public float Low; public int Volume; // 累计成交量单位通常为股 public long Turnover; // 累计成交额单位通常为元 public float Bid1; public int BidVol1; // 五档买卖以此类推 }解析循环for (int i 0; i outCount; i) { QuoteData q Marshal.PtrToStructureQuoteData( buffer i * sizeof(QuoteData)); // 转成业务记录时手工补上 TradeDate 和 ServerTime Storage.Write(new StockTick { Code Encoding.GetEncoding(GBK).GetString(q.Code).TrimEnd(\0), Time NormalizeTime(q.Time, _tradeDate), Price q.Price, Volume q.Volume, Turnover q.Turnover }); }字段映射要注意三条。第一Pack 1必须和 C 头文件的#pragma pack(push, 1)对应否则结构体内部会按 4 字节或 8 字节对齐字段全部错位。第二fixed byte Code[8]转字符串用 GBK 编码默认的 UTF8 会把汉字代码和数字代码解乱。第三Volume 和 Turnover 的累加基数从当日 0 点开始不是从你订阅时刻开始核对数据时拿收盘后的累计成交额和行情软件对比差很多说明结构体偏移错了。4.3 时间戳与复权两个高频翻车点时间戳是最容易埋雷的字段。很多 DLL 版本返回的 Time 不带日期只有时分秒比如143015表示 14:30:15跨日存储时必须自己在业务层补一个 TradeDate 字段。另一些版本返回当日累计秒比如52215表示当日第 52215 秒。解析前务必先打印一条样本肉眼确认格式再写死解析逻辑不要想当然。复权是第二个大坑。实时行情全部是未复权价但回测系统里常用前复权数据直接把实时价拿去做回测遇到除权除息日曲线会硬生生砍一截。正确做法是采集时只存原始价每天单独存一份复权因子算指标时再决定用哪种价格。不要在采集器里做复权因为除权日后复权因子会变化历史数据跟着变不如存原值留到计算层处理。5. 装上到跑通最容易翻车的 4 个避坑记录以下四个坑是调试同类采集器时反复遇到的按现象、原因、解决写清楚。每一条都值得在测试阶段就验证一遍别等跑了一周再回头查。5.1 登录掉线后重连成风暴现象采集器跑几分钟后日志出现连接失败重连成功后几十秒又失败反复循环严重时行情账号被服务端临时限制。原因多数行情服务端对短连接敏感失败后立刻重连会被判定为异常行为也可能是本机 TCP 连接没释放干净TIME_WAIT 堆积占满端口。解决重连用指数退避1 秒、5 秒、30 秒递推重连前先释放 DLL 旧实例再重新初始化顺序不能反单机并发连接控制在个位数以内不要一个股票池开一个连接。5.2 32 位 DLL 撞上 64 位进程现象工程用 AnyCPU 编译后程序能跑但调用取数函数返回 0 或结构体全是 0有的直接 AccessViolation。原因老版本 TdxHqApi.dll 是 32 位本机组件无法在 64 位进程里加载。解决项目平台目标改成x86再编译.NET 工程勾选“首选 32 位”用任务管理器确认进程位数x86 进程会标注“32 位”。这个坑在安装版 DLL 上尤其隐蔽64 位系统下 32 位组件注册在 WOW6432Node 节点64 位程序根本找不到。5.3 轮询间隔调小数据反而出现空洞现象把快照轮询从 3 秒调到 500 毫秒后日志时间戳出现不规则跳跃有的记录间隔变成 5 秒甚至 8 秒。原因DLL 请求是同步阻塞的频率提高后请求排队单轮实际耗时超过设定间隔下一轮被推迟。解决先记录单轮实际耗时目标间隔不要小于耗时两倍调参后看一个小时的日志统计间隔分布出现长尾就降频。记住实时采集器的核心参数是“峰值耗时 余量”不是拍脑袋定的轮询间隔。5.4 多线程取同一个 DLL 实例跑几小时崩一次现象稳定性测试时偶发内存访问错误重启后恢复正常崩溃时间没有规律很像玄学。原因DLL 内部状态不是线程安全的多个线程同时调用同一实例会破坏内部缓冲区。解决所有 DLL 调用放进同一线程用锁串行化需要并行时按股票池拆分到多个进程每个进程一个 DLL 实例进程间物理隔离不要在回调里直接更新界面控件。这个规则要在设计初期就定下来后面加线程只会让问题更隐蔽。6. 给采集器加数据自检连续竞价对账与缺失率统计数据采了不等于数据可信。我的习惯是每天收盘后跑一次对账确认当天落盘的数据没有漏段、错位连续一周零差异才敢让采集器无人值守。这里分享三个自检方法。6.1 理论条数对账A 股连续竞价时段为 9:30 到 11:30、13:00 到 15:00合计 14400 秒。按 3 秒轮询间隔单只股票理论快照条数约 4800 条。对账脚本长这样import pandas as pd def check_snapshot_count(df, interval_s3): # df 需要包含 trade_date, code, time 三列 expect 14400 / interval_s actual df.groupby([trade_date, code]).size() bad actual[actual expect * 0.9] if not bad.empty: print(缺失较重的记录, bad.index.tolist()) return bad90% 阈值是经验值因为程序启动时刻、行情服务端重启都会造成少量缺失低于 90% 才值得排查。注意这里按“交易日 证券代码”分组如果某天你只从 10 点开始采集要把理论值按实际启动时间折算。6.2 抽样复核对账每天从股票池随机抽 20 只把落盘最后一笔快照的最新价与行情软件的收盘价比对误差超过 0.01 元就检查是不是价格字段解析错了。这一步也顺带验证结构体偏移没有因为 DLL 更新而变化。DLL 一旦换版本第一时间跑这个对账比看任何文档都直观。6.3 逐笔数据完整性检查逐笔没有统一理论条数实践中用两个指标当天落盘的逐笔成交额总和与收盘快照里的当日累计成交额对比偏差应小于 0.5%每笔时间戳要单调递增且都在连续竞价时段内。出现异常时优先查本地磁盘空间和存储层队列堆积这两个原因占逐笔缺失的八成。我自己的教训是不要迷信 DLL 没报错就是没错。曾经采集器日志全绿复盘时发现某天下午数据全是一个小时前的旧值原因是行情服务端连接半开本地一直读到旧缓存。从那以后每天收盘固定跑 15 分钟对账确认条数、抽样价、逐笔成交额三个维度全部通过才敢把实时采集器放手。这套自检流程不复杂但能帮你免掉大量复盘时的数据清洗时间希望帮到你。本文还有配套的精品资源点击获取