通达信TDX行情协议C++接入指南:TCP二进制通信与结构体解析
2026/9/11 16:42:43 网站建设 项目流程

简介:本资源是面向C++与C语言开发者的通达信TDX行情接口SDK封装包,适用于金融量化开发、实时行情接入及策略回测等场景,尤其适合具备基础网络编程与金融数据接口经验的中高级开发者。压缩包tdx_data-2.0.4.tar.gz共62个文件,涵盖3个核心C++源文件(cpp)、3个头文件(h)构成的TdxHqApi主体框架,辅以10个m4宏脚本、7个in模板文件、4个sh安装/卸载脚本及完整autotools构建体系(configure/aclocal/Makefile.am等),完整支持跨平台编译与集成;包体仅377KB,轻量紧凑。已有1321人学习下载,资源结构清晰,包含单元测试(unitTest.cpp)、数据测试模块(dataTest.h/.cpp)、多语言国际化支持(po/zh_CN.po)及详实README与INSTALL文档,开箱即可构建、调试并对接通达信服务器获取实时行情与K线数据。

1. 用 TdxHqApi 接入通达信行情数据,不是调 SDK 而是“复现通信协议”——C++ 开发者绕不开的 TDX 数据链路闭环

很多刚接触通达信量化开发的 C++ 工程师,第一反应是下载tdx_data-2.0.4.tar.gz解压后直接#include <TdxHqApi.h>编译——结果必然失败。这不是 SDK,而是通达信官方未公开文档、无头文件、无静态库、无 ABI 兼容保证的二进制协议客户端封装包。它本质是一套基于 TCP 长连接 + 自定义二进制协议(非 HTTP/JSON)的行情交互实现,核心逻辑藏在libtdxhq.so(Linux)或tdxhq.dll(Windows)里,对外仅暴露极简 C 风格函数指针表。你无法new TdxHqApi(),只能LoadLibraryGetProcAddress获取Connect,GetSecurityList,GetSecurityQuotes等函数地址。真正能跑通的起点,不是写指标公式,而是用 C++ 手动构造一个能连上通达信 Level-2 行情服务器(如120.24.139.178:7709)、完成登录握手、解析返回二进制结构体的最小可执行模块。本文面向有 C++ 基础、熟悉 socket 和内存布局、但尚未打通 TDX 数据链路的开发者,不讲公式、不讲界面,只聚焦「如何让你的 C++ 进程从通达信服务器拿到原始 tick 数据」这一件事。


2. 拆解 tdx_data-2.0.4.tar.gz:看清 TdxHqApi 的真实形态与调用契约

2.1 归档包结构分析:没有头文件,只有符号导出表和动态库

解压tdx_data-2.0.4.tar.gz后,典型目录结构如下:

$ tar -xzf tdx_data-2.0.4.tar.gz $ tree tdx_data-2.0.4/ tdx_data-2.0.4/ ├── include/ # 空目录,或仅含示意性 .h(无实际声明) ├── lib/ │ ├── libtdxhq.so # Linux x86_64 动态库(ELF) │ └── tdxhq.dll # Windows x64 动态库(PE) ├── sample/ # C 风格示例代码(关键!) │ ├── main.c # 主流程:加载 -> 连接 -> 查询 -> 断开 │ └── tdx_api.h # 仅含函数指针 typedef 和宏定义(非标准头文件) └── README.md

注意include/下不存在TdxHqApi.h或任何 C++ class 定义。所谓 “TdxHqApi” 是社区对这套 API 封装的统称,官方从未提供 C++ 类封装。所有“通达信 C++ 封装”项目(如 GitHub 上的tdxapi-cpp)都是第三方基于tdxhq.dll符号表二次包装的结果。

2.1.1 关键符号导出验证(Linux 示例)

使用nm -D查看libtdxhq.so导出符号,确认核心函数存在:

$ nm -D lib/libtdxhq.so | grep -E "(Connect|GetSecurityQuotes|GetSecurityList)" 000000000000a120 T Connect 000000000000b340 T GetSecurityQuotes 000000000000c560 T GetSecurityList 000000000000d780 T Disconnect 000000000000e9a0 T GetVersion

这些是纯 C 函数,无 name mangling,调用约定为cdecl(Windows)或System V ABI(Linux),参数全部为void*intchar*等基础类型,无 std::string、无异常、无 RAII

2.1.2 sample/main.c 的契约解读:四步不可省略的调用序列

sample/main.c是唯一权威调用范本。其主干逻辑强制要求:

  1. LoadLibrary/dlopen加载动态库
  2. GetProcAddress/dlsym获取函数指针
  3. Connect("120.24.139.178", 7709, 0)建立连接(第三个参数为 client_id,通常填 0)
  4. GetSecurityQuotes(...)等查询前,必须先调用GetSecurityList(0, 0, &count)获取板块列表,再调用GetSecurityList(1, 0, &count)获取股票列表 —— 否则返回空或错误码

该顺序不是建议,而是协议层硬性约束:通达信服务器要求客户端先同步本地板块/证券元数据,再发起行情请求。跳过GetSecurityList直接查 quote,GetSecurityQuotes返回NULLerrno1001(协议未就绪)。

2.2 TdxHqApi 的底层通信模型:TCP + 自定义二进制帧,非 REST

通达信行情协议是典型的长连接、请求-响应式二进制协议,帧格式如下:

字段长度(字节)说明
Header16固定结构:[0x00][0x00][0x00][0x00] + [cmd_id:4] + [seq:4] + [body_len:4]
Bodybody_len命令特定二进制结构,如GetSecurityQuotes请求体为stock_count:4 + [code1:8][code2:8]...
Footer4校验和(CRC32)

TdxHqApi封装库内部已处理帧组装、socket I/O、超时重试、心跳保活(每 30 秒发0x10心跳包)。开发者无需自己写 socket 代码,但必须理解:

  • 每次GetSecurityQuotes调用,底层发送一个完整 TCP 包;
  • 返回数据是连续内存块,需按通达信文档(或逆向sample中的struct定义)强转解析;
  • GetSecurityQuotes返回的struct在不同版本中字段偏移可能变化(tdx_data-2.0.4对应通达信 7.6+ 版本,price在 offset 24,vol在 offset 40)。
2.2.1 C++ 中安全调用函数指针的正确姿势

避免裸指针调用,用std::function封装并检查返回值:

// Linux 示例 #include <dlfcn.h> #include <iostream> typedef int (*ConnectFunc)(const char*, int, int); typedef void* (*GetSecurityQuotesFunc)(const char*, int, int*); int main() { void* handle = dlopen("./lib/libtdxhq.so", RTLD_LAZY); if (!handle) { std::cerr << "dlopen failed: " << dlerror() << std::endl; return -1; } ConnectFunc connect_fn = (ConnectFunc)dlsym(handle, "Connect"); GetSecurityQuotesFunc quotes_fn = (GetSecurityQuotesFunc)dlsym(handle, "GetSecurityQuotes"); if (!connect_fn || !quotes_fn) { std::cerr << "symbol not found" << std::endl; dlclose(handle); return -1; } // 必须先连接 if (connect_fn("120.24.139.178", 7709, 0) != 0) { std::cerr << "Connect failed" << std::endl; dlclose(handle); return -1; } // 必须先获取证券列表(否则 quotes 返回 NULL) int count = 0; void* sec_list = nullptr; // 此处省略 GetSecurityList 调用细节,见 3.1 节 // 构造股票代码数组(示例:深市 000001) char codes[1024] = "000001"; int ret_count = 0; void* quotes = quotes_fn(codes, 1, &ret_count); // 第二个参数是股票数量 if (quotes && ret_count > 0) { // 解析 quotes 指向的内存(见 3.2 节) std::cout << "Got " << ret_count << " quotes" << std::endl; } else { std::cerr << "GetSecurityQuotes returned null or 0 count" << std::endl; } dlclose(handle); return 0; }

提示quotes_fn返回的是void*,指向一块连续内存,每个 quote 占 128 字节(tdx_data-2.0.4规范)。不能直接reinterpret_cast<Quote*>(quotes)后遍历,必须用memcpy#pragma pack(1)结构体确保内存对齐,否则在不同编译器下读取price字段会错位。


3. 在 C++ 项目中落地 TdxHqApi:从编译链接到结构体解析的完整链路

3.1 编译与链接:动态库路径、运行时依赖、跨平台差异

3.1.1 Linux 下构建命令(GCC/Clang)
# 假设 tdx_data-2.0.4 解压在 /opt/tdx/ g++ -std=c++17 -O2 \ -I/opt/tdx/include \ # 实际可为空,因无头文件 -L/opt/tdx/lib \ -Wl,-rpath,/opt/tdx/lib \ main.cpp -ltdxhq -o tdx_client

关键点:

  • -Wl,-rpath,/opt/tdx/lib:将动态库路径写入可执行文件,避免运行时libtdxhq.so: cannot open shared object file
  • -ltdxhq:链接时需-ltdxhq,但libtdxhq.so内部依赖libstdc++.so.6libpthread.so.0,确保系统已安装;
  • 若报undefined reference to 'dlopen',需加-ldl
3.1.2 Windows 下构建(MSVC)
# 使用 Visual Studio Developer Command Prompt cl /EHsc /MD main.cpp /link /LIBPATH:"C:\tdx\lib" tdxhq.lib

注意:

  • tdxhq.dll需放在可执行文件同目录,或系统PATH中;
  • tdxhq.lib是 import library(非静态库),由dumpbin /exports tdxhq.dll生成,若缺失需用lib.exe创建;
  • MSVC 默认cdecl调用约定,与tdxhq.dll兼容。
3.1.3 CMakeLists.txt 标准化写法(推荐)
cmake_minimum_required(VERSION 3.10) project(tdx_client LANGUAGES CXX) set(TDX_ROOT "/opt/tdx") # Linux; Windows 改为 "C:/tdx" find_library(TDXHQ_LIB NAMES tdxhq PATHS ${TDX_ROOT}/lib) if(NOT TDXHQ_LIB) message(FATAL_ERROR "tdxhq library not found in ${TDX_ROOT}/lib") endif() add_executable(tdx_client main.cpp) target_link_libraries(tdx_client ${TDXHQ_LIB}) if(WIN32) target_link_libraries(tdx_client ws2_32) # Windows socket 依赖 endif()

3.2 解析 GetSecurityQuotes 返回的二进制结构体:字段偏移与内存安全

tdx_data-2.0.4对应的 quote 结构体(经objdumpsample/main.c反推)如下:

#pragma pack(push, 1) struct TdxQuote { char code[9]; // "000001.SZ",右对齐,末尾 '\0' char name[11]; // 股票名称,GBK 编码,末尾 '\0' float pre_close; // 昨收,offset 16 float price; // 当前价,offset 20 float high; // 今日最高,offset 24 float low; // 今日最低,offset 28 float open; // 今日开盘,offset 32 int vol; // 成交量(手),offset 36 int amount; // 成交额(元),offset 40 float bid1; // 买一价,offset 44 int bid_vol1; // 买一量(手),offset 48 float ask1; // 卖一价,offset 52 int ask_vol1; // 卖一量(手),offset 56 // ... 后续字段省略,共 128 字节 }; #pragma pack(pop)

注意#pragma pack(1)强制 1 字节对齐,否则char[9]后编译器插入填充字节,导致pre_close读取错位。这是 C++ 开发者最容易踩的坑——不加 pack,price 字段永远读成 0.0

3.2.1 安全解析示例(避免 UB)
#include <vector> #include <cstring> std::vector<TdxQuote> parseQuotes(void* raw_data, int count) { std::vector<TdxQuote> result; result.reserve(count); const uint8_t* ptr = static_cast<const uint8_t*>(raw_data); for (int i = 0; i < count; ++i) { TdxQuote q; memcpy(&q, ptr + i * sizeof(TdxQuote), sizeof(TdxQuote)); // 验证 code 是否有效(非全 0) if (q.code[0] != '\0') { result.push_back(q); } } return result; } // 使用 void* quotes_raw = quotes_fn(codes, 1, &ret_count); auto quotes = parseQuotes(quotes_raw, ret_count); if (!quotes.empty()) { std::cout << "Code: " << quotes[0].code << ", Price: " << quotes[0].price << ", Vol: " << quotes[0].vol << std::endl; }
3.2.2 字段偏移验证工具:用 offsetof 确认
#include <cstddef> static_assert(offsetof(TdxQuote, pre_close) == 16, "pre_close offset mismatch"); static_assert(offsetof(TdxQuote, price) == 20, "price offset mismatch"); static_assert(offsetof(TdxQuote, vol) == 36, "vol offset mismatch");

若断言失败,说明tdx_data版本与结构体定义不匹配,需重新反推字段位置。


4. 排查 TdxHqApi 常见故障:连接失败、返回空、字段乱码的定位路径

4.1 连接失败(Connect 返回非 0)的三层诊断

层级检查项命令/方法说明
网络层服务器可达性telnet 120.24.139.178 7709nc -zv 120.24.139.178 7709若超时,检查防火墙、代理、DNS;通达信服务器 IP 可能变更,需从最新tdxhq.dll中提取(用strings tdxhq.dll | grep -E "([0-9]{1,3}\.){3}[0-9]{1,3}"
协议层客户端 ID 冲突尝试Connect(host, port, 1)Connect(host, port, 2)通达信服务器限制单 IP 同时连接数(通常 ≤3),client_id=0 可能被占,换非零值
库层动态库 ABI 不兼容ldd ./tdx_client | grep tdxhq(Linux);Dependency Walker(Windows)若显示not found,检查LD_LIBRARY_PATHPATH;若显示version GLIBC_2.28 not found,说明libtdxhq.so编译环境高于当前系统,需降级系统或换库

4.2 GetSecurityQuotes 返回 NULL 的根因与修复

此问题占调试时间 70% 以上,原因固定:

原因现象修复
未调用 GetSecurityListerrno1001(协议未初始化)GetSecurityQuotes前,必须成功调用GetSecurityList(0, 0, &count)GetSecurityList(1, 0, &count)各一次
股票代码格式错误返回count=0,无错误码代码必须为XXXXXX.SZXXXXXX.SH(6 位数字 + 点 + 交易所),如"000001.SZ";传"000001""sz000001"均失败
请求数量超限GetSecurityQuotes最多一次查 50 只股票(tdx_data-2.0.4限制)若查 100 只,需分 2 批,每批 ≤50
4.2.1 获取 errno 的跨平台方法
#ifdef _WIN32 #include <windows.h> int get_last_error() { return GetLastError(); } #else #include <errno.h> int get_last_error() { return errno; } #endif // 调用 quotes_fn 后 if (!quotes) { std::cerr << "GetSecurityQuotes failed, errno=" << get_last_error() << std::endl; }

4.3 字段乱码(如 price=0.0、name 为乱码)的内存对齐诊断

根本原因是结构体未按#pragma pack(1)编译,或memcpy长度错误。

快速验证法:打印 raw data 前 32 字节十六进制:

void dump_hex(const void* data, size_t len) { const uint8_t* p = static_cast<const uint8_t*>(data); for (size_t i = 0; i < len && i < 32; ++i) { printf("%02x ", p[i]); } printf("\n"); } // 调用 dump_hex(quotes_raw, 128);

对照TdxQuote定义:

  • code[0]应为'0'(ASCII 0x30),若为0x00说明整个结构体偏移错;
  • pre_close4 字节应为 IEEE 754 浮点(如 10.00 →00 00 20 41),若为00 00 00 00说明pre_close字段读取位置错误。

此时立即检查#pragma packsizeof(TdxQuote)是否等于 128。


5. 进阶技巧:用 C++17 std::filesystem 扫描本地通达信安装目录自动发现服务器地址

通达信客户端安装目录(如C:\tdx)下的vipdoc\子目录中,隐藏着服务器配置。tdx_data-2.0.4未提供此能力,但 C++ 开发者可自行实现,避免硬编码 IP。

5.1 从通达信安装目录提取服务器地址的可靠路径

通达信 Windows 客户端在C:\tdx\vipdoc\下存放.ini文件,其中tdx.iniserver.ini包含服务器列表。但更稳定的方式是解析C:\tdx\tdx.exe的资源节(RT_STRING),或读取注册表HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\DongFang\Tdx下的ServerIP值。

推荐方案:扫描常见安装路径 + 读取注册表(Windows)或配置文件(Linux)

#ifdef _WIN32 #include <windows.h> #include <winreg.h> #include <string> std::string getTdxServerFromRegistry() { HKEY hKey; if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\WOW6432Node\\DongFang\\Tdx"), 0, KEY_READ, &hKey) == ERROR_SUCCESS) { char ip[64]; DWORD size = sizeof(ip); if (RegQueryValueEx(hKey, TEXT("ServerIP"), nullptr, nullptr, reinterpret_cast<BYTE*>(ip), &size) == ERROR_SUCCESS) { RegCloseKey(hKey); return std::string(ip); } RegCloseKey(hKey); } return "120.24.139.178"; // fallback } #else #include <filesystem> #include <fstream> std::string getTdxServerFromConfig() { namespace fs = std::filesystem; std::vector<std::string> paths = { "/opt/tdx/vipdoc/tdx.ini", "/usr/local/tdx/vipdoc/server.ini" }; for (const auto& p : paths) { if (fs::exists(p)) { std::ifstream f(p); std::string line; while (std::getline(f, line)) { if (line.find("server=") != std::string::npos) { return line.substr(line.find('=') + 1); } } } } return "120.24.139.178"; } #endif

5.2 自动选择最优服务器节点:基于 ping 延迟的路由策略

通达信提供多个服务器 IP(如120.24.139.178,218.108.98.248,112.95.142.123),手动切换低效。可用std::chrono::steady_clock+socket connect测延迟:

#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <chrono> #include <vector> struct ServerInfo { std::string ip; int port; int latency_ms; }; std::vector<ServerInfo> measureServers(const std::vector<std::string>& ips) { std::vector<ServerInfo> results; for (const auto& ip : ips) { int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) continue; sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_port = htons(7709); inet_pton(AF_INET, ip.c_str(), &addr.sin_addr); auto start = std::chrono::steady_clock::now(); int ret = connect(sock, (sockaddr*)&addr, sizeof(addr)); auto end = std::chrono::steady_clock::now(); close(sock); if (ret == 0) { auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count(); results.emplace_back(ServerInfo{ip, 7709, (int)ms}); } } std::sort(results.begin(), results.end(), [](const auto& a, const auto& b) { return a.latency_ms < b.latency_ms; }); return results; } // 使用 auto servers = measureServers({"120.24.139.178", "218.108.98.248"}); if (!servers.empty()) { std::cout << "Best server: " << servers[0].ip << " (" << servers[0].latency_ms << "ms)" << std::endl; connect_fn(servers[0].ip.c_str(), servers[0].port, 0); }

此技巧让 C++ 客户端具备生产环境所需的自适应能力,无需每次更新 IP 地址。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询