ARTICLE DETAIL

资讯详情

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

C#调用通达信TdxHqApi实现实时行情采集的工程实践

C#调用通达信TdxHqApi实现实时行情采集的工程实践 简介一套基于TdxHqApi动态库封装的实时行情数据采集器工程面向熟悉证券行情接口或个人量化学习的开发者用于解决实时行情获取、数据解析与本地落盘等问题。项目融合C#、Java与C组件DLL负责底层API通信CS文件承载业务逻辑与界面Java文件提供跨平台调用配合config、xml等配置便于快速部署。压缩包共299个文件大小约110.7MB涵盖源码、编译后的class与库文件、说明文档及多种数据格式参考目录结构兼顾工程开发与学习查看。内容包含Web入口、交易接口封装与行情解析等核心模块可帮助读者理解TdxHqApi的调用方式、实时数据采集流程及多语言工程组织方法。目前已有54人学习浏览。资源源自网络分享仅供个人学习交流请勿商业使用若涉及版权请联系删除。1. 实时数据采集器StockRealData是什么一个dll调用流程的封装一提到TdxHqApi这个dll做过A股行情对接的人多半会心一笑——它就是通达信对外提供的一个C风格动态链接库用来从行情服务器拉取实时报价和K线。StockRealData这个名字听起来像中台系统实际就是把TdxHqApi.dll的调用流程封装成一个能持续运行的采集器初始化、连接登录、循环查询、解析二进制结构体、把数据落盘或转发出去。做量化回测需要本地行情缓存、做盯盘工具又不想买付费数据源、或者想在自己的软件里复用通达信行情的人都会用到这个方向。这篇笔记只讲我对这套接口的落地经验从函数声明写到参数设置再到踩过的坑。2. 先看懂TdxHqApi dll的接口契约数据结构与登录/查询流程TdxHqApi这一类接口和常见HTTP接口最大的不同在于它走的是TCP私有协议请求和响应都是定长二进制结构体没有JSON、没有XML甚至连个正经的SDK头文件都要自己整理。你直接拿dumpbin /exports导出函数列表能看到一串以TdxHq_开头的导出函数剩下的就是靠抓包或者同行分享的声明把协议补齐。2.1 接口能干什么五档盘口、分时、K线与股票列表这个dll对外提供的核心能力基本可以分成四类实时快照、历史K线、分时成交、基础信息。实时快照对应的是TdxHq_GetSecurityQuotes一次请求能带多个股票代码返回最新价、昨收、今开、最高、最低、成交量、成交额、买卖五档价量等字段历史K线对应TdxHq_GetSecurityBars可以拉日线、周线、月线、一分钟、五分钟等周期数据分时成交则通过专门的函数逐笔拉取。股票列表用TdxHq_GetSecurityList按市场分页获取做全市场扫描时很常用。这里要特别注意TdxHqApi接口的「一个连接只能绑定一个行情服务器」设计。深交所和上交所的行情服务器地址甚至端口都可能不同所以大部分人的做法是同时维护两条连接一条管上海market1一条管深圳market0。接口返回的结构体里通常不会有「市场」字段而是靠你自己发起请求时的入参区分字段里的股票代码往往也只是一个纯数字的代码比如600000需要配合外部逻辑拼成SH600000这种全量格式。2.2 调用生命周期初始化、登录、查询、断开无论用什么语言包装TdxHqApi dll的调用顺序都绕不开下面这个流程先调用TdxHq_Init完成dll内部的资源初始化这一步一般只需要一次。注册回调函数用于接收连接状态变化、行情推送等异步事件。这个函数指针在C#里要小心不能让它被垃圾回收器回收否则回调触发时程序会直接崩掉。调用TdxHq_Login传入行情服务器IP、端口、用户名和密码等待回调里返回登录结果。用户名密码通常是预留的占位参数但程序不会校验空字符串。登录成功后进入查询循环调用TdxHq_GetSecurityQuotes或TdxHq_GetSecurityBars拉数据每次调用前设置超时时间调用后检查返回值。程序退出时调用TdxHq_Disconnect和TdxHq_Release把连接断开、释放资源。伪代码大致是TdxHq_Init() TdxHq_SetCallback(OnConnected, OnData, OnError) TdxHq_Login(11.22.33.44, 7709, user, pwd) loop: TdxHq_GetSecurityQuotes(request) parse(response) sleep(interval) TdxHq_Disconnect() TdxHq_Release()这条流程里的坑集中在回调机制上。TdxHqApi的回调不是每个请求同步触发的部分版本把行情数据分成了「主动推送」和「请求应答」两种模式。如果你只调了请求函数却没开推送标志部分行情可能要等下一个心跳周期才返回来。另一个容易漏掉的是登录成功并不代表可以立刻发请求服务器端需要几十毫秒建立会话冒然发送数据会被当成非法包踢掉。3. 用C# P/Invoke把StockRealData的最小实现跑起来C#调用TdxHqApi dll是最常见的选择因为写界面、存数据库、对接Excel都方便。难点在于P/Invoke声明和结构体布局。TdxHqApi是32位dll如果你的项目编译成x64运行时会直接报BadImageFormatException所以第一步不是写代码而是把项目的「平台目标」改成x86。3.1 核心DllImport声明与结构体映射先看写好的P/Invoke声明这是整个StockRealData能跑起来的地基[DllImport(TdxHqApi.dll, CallingConvention CallingConvention.StdCall, CharSet CharSet.Ansi)] public static extern int TdxHq_Init(); [DllImport(TdxHqApi.dll, CallingConvention CallingConvention.StdCall)] public static extern int TdxHq_Login(string serverIp, ushort serverPort, string userName, string password); [DllImport(TdxHqApi.dll, CallingConvention CallingConvention.StdCall)] public static extern int TdxHq_GetSecurityQuotes(int market, string code, int count, IntPtr result); [DllImport(TdxHqApi.dll, CallingConvention CallingConvention.StdCall)] public static extern int TdxHq_Disconnect();CallingConvention必须用StdCall这是C/C DLL的默认调用约定。CharSet.Ansi是为了让字符串以GBK字节序列传给dll如果手滑写成Unicode行情代码传过去就变成一串乱码服务器根本认不出来。结构体映射要用LayoutKind.Sequential字段顺序必须和dll内部C结构体完全一致。五档行情数据大致长这样[StructLayout(LayoutKind.Sequential, Pack 1)] public struct TdxSecurityQuotes { public int market; [MarshalAs(UnmanagedType.ByValTStr, SizeConst 16)] public string code; public float price; public float lastClose; public float open; public float high; public float low; public int vol; public float amount; [MarshalAs(UnmanagedType.ByValArray, SizeConst 5)] public float[] bidPrice; [MarshalAs(UnmanagedType.ByValArray, SizeConst 5)] public int[] bidVolume; [MarshalAs(UnmanagedType.ByValArray, SizeConst 5)] public float[] askPrice; [MarshalAs(UnmanagedType.ByValArray, SizeConst 5)] public int[] askVolume; }Pack 1是字节对齐的关键。C#默认按8字节对齐而C编译器在处理这种行情结构体时通常用1字节对齐不写Pack 1会导致后面的浮点数组字段错位读出来的价格全是乱值。这个坑我见过不下三次每次都有人在论坛上问「明明登录成功了为什么价格是天文数字」十有八九都是对齐没设对。3.2 采集循环与回调处理把IntPtr变成结构化数据TdxHqApi的行情查询函数返回的是一个IntPtr指向的内存块里面可能是单个结构体也可能是连续多个结构体。连续多个时需要自己按Marshal.SizeOfTdxSecurityQuotes()做步长遍历把每一条数据还原成对象。写一个工具方法public static TdxSecurityQuotes[] ParseQuoteBuffer(IntPtr buffer, int count) { var result new TdxSecurityQuotes[count]; int size Marshal.SizeOfTdxSecurityQuotes(); for (int i 0; i count; i) { IntPtr p IntPtr.Add(buffer, i * size); result[i] Marshal.PtrToStructureTdxSecurityQuotes(p); } return result; }实际操作中如果TdxHqApi版本较老返回的内存块可能在调用下一个请求后被复用所以最好在拿到IntPtr后立即解析并拷贝字段不要图省事只保存指针。采集循环本身不复杂比较稳妥的节奏是while (!stopFlag) { IntPtr buf IntPtr.Zero; int ret TdxHq_GetSecurityQuotes(market, code, 1, out buf); if (ret 0 buf ! IntPtr.Zero) { var quotes ParseQuoteBuffer(buf, 1); OnQuoteArrived(quotes[0]); } Thread.Sleep(3000); }每只股票3秒查一次一百只股票就是300秒一轮如果要覆盖几千只股票单线程肯定顶不住。常见做法是开多个采集线程每个线程负责一组股票但不要让超过三个连接同时打到同一台行情服务器否则容易触发服务端的连接数限制导致登录直接被拒。参数说明sleep(3000)不是随便拍的。对A股快照行情来说3000毫秒能覆盖服务器端的刷新周期又不会让请求频率高到被视为恶意访问。如果行情波动大可以压到1000毫秒但再用200毫秒以下就属于自找麻烦了服务器照样按自己的节奏推数据快不了多少还白白占用带宽和CPU。4. StockRealData的参数怎么设服务器、超时、心跳与编码陷阱TdxHqApi的成败一半在代码另一半在参数。这套接口不像HTTP服务有那么标准的RESTful风格服务器地址、端口、超时、心跳间隔、编码这些都要自己摸索出一套合理的值。下面这个表是我经过多个版本验证后沉淀的参数组合能覆盖绝大多数场景。4.1 关键参数配置表与推荐值参数项推荐值说明平台目标x86TdxHqApi.dll通常为32位x64进程直接报错行情服务器通达信官方行情服务的地址端口7709为主不同运营线路有不同IP选ping值低的登录超时5000毫秒小于3秒容易误判登录失败查询超时10003000毫秒单次行情请求的等待上限心跳/轮询间隔3000毫秒兼顾实时性与服务器压力连接数2一个上海市场一个深圳市场不要超过3字符串编码GBK等价UTF-8encoding936股票名称等中文信息必须转码注意行情服务器地址并不是一个固定IP通达信的行情服务器有很多条线路网络环境不同可连的服务器差别很大。我一般会在程序里内置一个服务器列表启动时逐个尝试登录先到先得这个思路比写死一个地址更抗网络波动。4.2 中文编码处理GBK转UTF-8否则股票名称面目全非dll返回的股票名称、板块名称等字段是GBK编码字节。C#里如果直接用Marshal.PtrToStringAnsi拿字符串中文可能会变成「鎴伐」这种乱码。正确姿势是先把字节读成byte数组再手动转码public static string DecodeGbk(IntPtr ptr, int maxLen) { byte[] raw new byte[maxLen]; Marshal.Copy(ptr, raw, 0, maxLen); int realLen Array.IndexOf(raw, (byte)0); if (realLen 0) realLen maxLen; return Encoding.GetEncoding(936).GetString(raw, 0, realLen); }Encoding.GetEncoding(936)在.NET Core/Linux上可能抛NotSupportedException需要先注册代码页提供程序Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);这个细节很多人都栽过。Windows自带的.NET Framework没这个问题但一旦把采集器部署到Linux服务器上或者用.NET 5以上的版本不注册Provider就直接抛异常查起来很费劲。4.3 日志与状态机让采集器自己报告「我在哪一步」StockRealData不能只是一个闷头跑的循环最好在关键节点留下状态日志Init成功、Login成功、Login失败、查询超时、断线重连。我的习惯是维护一个简单的状态枚举Disconnected - Connecting - Connected - Querying - Reconnecting每个状态的转换都记录时间戳和错误码。TdxHqApi的错误码往往是一串负数不查表根本不知道什么意思。把这些错误码和自己总结的中文含义写进日志后续排查速度能快一倍。5. StockRealData常见问题排查从dll加载失败到断线重连的五个坑这一章是血泪经验汇总。几乎每一个新上手TdxHqApi的人都会在下面这几个坑里翻车严重的一个接一个熬通宵都是轻的。5.1 提示找不到指定的模块或DllNotFoundException现象程序启动DllNotFoundException直接抛出来明明已经把TdxHqApi.dll放到了exe同目录。原因TdxHqApi.dll本身还依赖一批VC运行时库。有些精简版系统缺msvcr120.dll或api-ms-win-*系列dll主dll就加载不进去。另外你把dll放到了exe目录不代表加载器能正确找到它特别是当你的程序被某个安装包改过默认工作目录时。解决先用Dependencies工具看dll的导入表把缺失的运行时库装上然后把dll放在exe同目录之外用绝对路径加载[DllImport(C:\StockRealData\bin\TdxHqApi.dll)]或者更稳妥一点在进程启动时临时切换当前工作目录Environment.CurrentDirectory C:\StockRealData\bin;5.2regsvr32注册失败或0x3错误现象有人习惯拿到dll先右键注册结果弹窗「无法注册dll/ocxregsvr32失败错误码0x3」。原因TdxHqApi.dll不是COM组件没有DllRegisterServer导出函数regsvr32纯粹是白费功夫。错误码0x3通常意味着系统找不到指定路径但根因还是注册方式不对。解决这个dll根本不需要注册只要进程能加载即可。更需要注意的是有些杀毒软件会把陌生dll直接拦截注册表的AppInit_DLLs也可能干扰加载。我一般会先关掉实时防护加载成功后加白名单。5.3 行情回调触发但价格字段全是零或乱值现象登录成功查询函数返回值也是0但结构体里的价格全是0或者大得不正常。原因多半是结构体布局不对。Pack1没设置或者bidPrice之类的数组大小定义错了比如服务器端是5档你只定义了3档。还有一种可能是你只申请了单条结构体的内存而dll返回的是多条连续内存解析时越界读到垃圾数据。解决先打印Marshal.SizeOfTdxSecurityQuotes()对比C结构体的大小。通达信快照结构体一般在150250字节之间如果差了几十字节大概率是字段顺序或对齐错了。和服务器真实数据对拍时可以先查一只停牌股票它的价格应该是0但lastClose不为0这样能定位字段偏移是否整体错位。5.4 用一段时间后连接被踢进入死循环重连现象采集器运行半小时或两小时后Login突然失败程序不断重试日志刷屏。原因高频查询触发了服务器端的限流或者服务器端做了一次长连接回收你的心跳包没有及时回。TdxHqApi对连接保活的判断有时模糊断开是静默的不主动发一单查询根本发现不了。解决轮询循环里加一个「连续失败计数」超过3次就拉长重试间隔从10秒起步指数退避到60秒封顶不要出现「断线、重连、秒断、再重连」的恶性循环。另外登录成功后每隔10分钟主动发一次轻量查询一是验证链路二是让服务器认为连接还活跃。还要记得处理重连后的补偿逻辑——断线期间漏掉的行情要在恢复后重新拉取一次快照而不是继续傻等增量。5.5 回调委托被垃圾回收程序不定时崩溃现象程序能跑但偶尔在毫无征兆的情况下崩溃崩溃点总在回调函数里。原因C#里把方法传给dll作为回调委托后如果这个委托对象没有其他引用垃圾回收器会把委托回收dll继续调用这块已回收的内存直接访问违规。解决在类中用一个静态字段或者类级别字段保持委托引用别用局部变量。比如private static readonly TdxHqCallBack _callback OnServerCallback;把_callback注册给dll保证整个进程生命周期内它都活着。这块机制有点玄学但实打实是踩得最深的坑之一。6. 进阶玩法从「可以跑」到「值得信」如果只是定时打印行情这个采集器也就止步于玩具了。真正把它做成数据服务我习惯再加三件事数据落库、数据对拍、消费解耦。落库我直接用SQLite按交易日分表表结构就是一个快照一个表字段和结构体一一对应。落库时注意要把价格从float转成decimal再存浮点误差在行情数据里很隐蔽但对策略回测是致命的。转储频率不要按条写攒50条或者间隔500毫秒批量提交一次磁盘IO压力小很多。数据对拍是我最建议新手做的事。拿StockRealData拉到的行情和通达信客户端、或者同花顺/东方财富页面上的同一时点价格对一遍误差应该在分拆价位上不超过1个tick。如果发现慢了几秒大概率是轮询间隔太大可以缩小到1秒如果价格都对不上就要回去检查结构体字段拿错了。消费解耦的做法是在后台开一个BlockingCollection队列采集线程只往队列里塞数据消费线程负责入库或转发两个环节互不拖累BlockingCollectionTdxSecurityQuotes quoteQueue new BlockingCollectionTdxSecurityQuotes();采集线程满了就丢弃最老的数据消费线程写库失败可以回头补。这个模式看着朴素但能扛住多大并发都不怕也方便以后改成WebSocket推给前端或者合并成分钟K线。最后说一个我自己的习惯。做好重连带来的重复数据治理是行情采集器能否长期稳定运行的分水岭。断线重连后服务器会从最新快照重新推数据重复写入会导致K线被「凭空拉高」。我用(代码, 时间, 最新价)作为唯一键做插入重复数据就当幂等跳过。这个习惯救了我很多次数据仓库里的行情表干净得让人心安。希望这篇TdxHqApi的实战笔记帮到你少踩几个我踩过的坑。本文还有配套的精品资源点击获取
返回列表