Level-2全推行情Python API:推送模型与低延迟排障

发布时间:2026/11/3 12:03:31
Level-2全推行情Python API:推送模型与低延迟排障 简介《同花顺level-2全推行情Python API操作手册》面向从事高频交易、量化研究与涨停板策略的证券开发者围绕TDS系统的C/S架构说明如何用Python接入沪深Level-1/Level-2实时行情。文档从TDS简介、系统特性与运行环境讲起覆盖Windows/Linux下Python3.564位的安装验证再进入API使用初始化TDS实例、定义行情回调与断线通知、设置连接市场与超时、注册证券/指数快照、逐笔成交、逐笔委托、委托队列及极速盘口等订阅接口并给出五档十档快照与字段含义。版本修订表完整梳理V2.0.0至V2.1.9的多连接、级联、通道号、异步订阅async_sub等变更便于排查兼容问题。包内为1个PDF文件约850KB结构清晰、便于检索已有2846人学习下载适合需要快速理解数据接口与版本差异的量化开发者和交易系统搭建者查阅。1. 全推行情Level-2 的难点藏在推送模型里很多人以为 Level-2 行情的门槛在「十档」。其实十档只是比五档多五个价格档位数据处理逻辑几乎没变。真正跑过 Level-2 全推行情 Python API 的人会发现难点在别处一条回调链路要同时接收全市场每秒成百上千个数据包快照只是其中一路旁边还并行着逐笔成交、逐笔委托、委托队列和极速盘口四路数据。量化策略里那些最影响延迟的逻辑——涨停板封单的撤单速度、委托队列里排队位置的变化——全都压在这条链路的稳定性上。这套接口的几乎每个细节都在回答同一个问题全推场景下数据怎么做到不丢、不堵、不乱序。2. 推送链路从行情源到 Python 回调的三段式拆解2.1 接入、解码订阅、API 分发各自在干什么这套系统是典型的 C/S 结构SDK 作为客户端连到行情服务端服务端从交易所的行情接入通道取原始流解码成结构化数据再按订阅关系过滤后分发给各个客户端。整条链路可以切成三段——接入层负责与交易所对接和首层解码处理层负责按订阅关系做过滤与路由API 层负责把二进制结构转成各语言可用的对象。三段之间任何一段出问题表现都是「数据不来」但排查手段完全不同。理解这个分层最直接的好处是知道延迟该在哪一段量。接入层的延迟由行情源决定你改不了处理层和 API 层的延迟取决于服务端负载和你自己的消费速度是可以优化的。很多人一遇到延迟就怀疑行情源实际上去看stats里的三个时间戳后面 2.3 会拆就能定位到底是哪一段慢。另一件容易被忽略的事这类行情是非展示性的。它不是给终端画 K 线用的而是给程序做决策用的所以接口设计偏向吞吐和低延迟而不是完整性和友好性。这就解释了很多看起来「反直觉」的设计——比如回调里没有缓冲队列比如订阅接口的语义竟然是「取消全部再重订」。2.2 回调签名 (data_list, count, stats) 为什么是批量的先看一段能跑起来的最小骨架注意回调函数里只做入队这一件事from datafeed.tds_api import TDS import queue tds TDS() q queue.Queue(maxsize20000) def on_stock(data_list, count, stats): # data_list 是一批同类型数据, count 是本批条数 # 这里只做入队, 任何耗时操作(落库/计算/加锁)都会拖慢整条接收链路 for tick in data_list: try: q.put_nowait(tick) except queue.Full: # 消费不过来时, 主动丢最旧的, 而不是阻塞回调 q.get_nowait() q.put_nowait(tick) def on_trans(data_list, count, stats): q.put_nowait((trans, data_list)) # set_env 必须先于 login 调用, 回调在这里一次性注册 assert tds.set_env( fnctickstockon_stock, # 证券快照 fnctransactionon_trans, # 逐笔成交 m_connectmarketUSH;USZ, # 默认双市场, 只连单市场时填 USH 或 USZ m_timeout5000, # 同步请求超时, 单位毫秒 )data_list是列表而不是单条记录count又与列表长度同源这个看起来冗余的设计是绑定层的常见取舍一个网络包解出来的同类型数据往往有好几条一次跨语言调用传一批比逐条穿透 Python/C 边界省下大量切换开销count则来自 C 层结构体字段绑定层直接透传省掉在 Python 侧再算一次长度。实际写代码时用count比自己len()更贴近底层的真实语义——虽然两者正常情况下一相等但一旦不等说明数据被截断了。提示回调函数是 SDK 的接收线程在调用不是你的主线程。这意味着回调里不能有阻塞调用也不能假设线程安全。最常见的翻车方式是在回调里直接写数据库或发 HTTP 请求前期看着没问题行情一密集就开始堆延迟最后表现为「数据延迟越来越大」而不是报错。2.3 stats 三元组把端到端延迟拆成两段stats是个字典装着数据包在链路上到达各节点的时间戳单位是微秒级 Unix 时间def on_stock(data_list, count, stats): produce stats[produce_time] # 服务端收到行情的时间 send stats[send_time] # 服务端发出该包的时间 recv stats[recv_time] # SDK 收到该包的时间 # 服务端内部处理耗时: 解码 订阅过滤 排队 server_cost send - produce # 网络传输耗时: 从服务端出口到你机器 net_cost recv - send # 端到端总延迟 e2e recv - produce这三个值能拆出两个独立的指标。send - produce反映服务端处理层的负载如果这个值在工作日开盘前几分钟突然抬高说明是服务端在排队你本地再怎么优化也没用recv - send反映网络和客户端的接收情况这个值飙高才该怀疑自己的带宽和消费速度。把两个混在一起看是最容易做无用功的地方。还有更细的用法这三个时间戳都来自不同机器前两个是服务端时钟第三个是本地时钟跨机器做差会混入时钟偏移。所以要么只比同一台机器上的差值要么确保双方都做了时间同步。判断标准很简单——如果算出来的net_cost长期是个稳定的负值或异常大的正值先怀疑时钟不要怀疑网络。2.4 订阅的四条路径与重连后的重发语义订阅接口按「影响范围」和「是否阻塞」分成四个同步接口加一个异步接口语义差别比名字看起来大得多接口语义副作用sub取消该市场该类型的全部订阅再按新列表重订之前订的其他代码会被冲掉unsub取消该市场该类型的全部订阅全量清空add_sub在现有订阅上追加代码幂等性取决于服务端实现del_sub从现有订阅中移除指定代码不影响其他代码async_sub异步下发订阅请求结果走回调不阻塞调用线程sub的语义是最容易踩的坑它不是说「订阅这些代码」而是说「把这个市场这个类型的订阅整体替换成这些代码」。分两次调用sub(stock, USHA, [600000])和sub(stock, USHA, [600001])结果只订到了 600001。要做增量必须用add_sub。异步订阅的价值在于把「等结果」从调用线程里拿掉。它的结果通过订阅回调函数通知回调参数里有一个callback_type字段用来区分这次回调是主动调用异步接口触发值为 1还是断线重连成功后自动重发触发值为 2。这个区分很重要前者你要处理失败重试后者你只需要记日志确认订阅恢复。def on_subscribe(data_type, op_type, market, code_list, statuscode, callback_type): # op_type: 1 全新订阅 2 取消订阅 3 增加订阅代码 4 取消订阅代码 # statuscode: 0 成功, 其余失败 # callback_type: 1 异步请求触发, 2 重连/登录成功后自动重发触发 if statuscode ! 0: print(f订阅失败 {data_type} {market} op{op_type} code{statuscode}) tds.set_env(fucsubscribeon_subscribe) tds.async_sub(stock, 1, USHA, [600000, 600001])重发订阅这件事本身是个开关环境参数里的m_connectsubscribe设为 1 时断线重连成功或手动重新登录成功后SDK 会把之前的订阅信息重新发一遍。不开这个开关重连之后连接是通的、心跳是好的但一包数据都不来——这是排查时最耗时间的一类故障因为所有「连接是否正常」的检查项都通过。注意订阅回调函数的参数个数在接口文档的描述文字和示例代码之间有过不一致实际以 SDK 签名和示例为准。稳妥做法是先用inspect.signature打印一次真实签名再照着写回调别照着注释写。3. 连接拓扑单连接为什么会撞上瓶颈3.1 从单连接改多连接改的到底是哪一层早期版本所有市场共用一条连接行情源、解码、订阅过滤全压在一个 socket 上。这个设计在数据量小的时候能稳定运行但有两个结构性隐患一是故障域不隔离深市那边网络抖动导致重连沪市的推送也跟着断二是无法按市场做独立的限速和优先级开盘瞬间两个市场的流量叠在一起谁也别想快。改成多连接之后每个市场有自己的域名和端口连接、心跳、重连、订阅都在各自的连接上独立进行。代价也要看清楚连接数翻倍意味着心跳包翻倍、客户端内存里的接收缓冲翻倍、文件描述符翻倍以及重连逻辑从「一份」变成「每市场一份」。第一次上手时最常犯的错是把原来的单份重连计数当成全局的结果某一个市场疯狂重连把其他市场挤掉。后来版本里还有一个务实的改进把各市场的域名和端口写进默认配置装完即用不用手动填。这个改动看着小实际上消掉了一大类「配置写错一个字连上了但没数据」的问题。3.2 市场号不是标签是寻址的一部分市场号是这套接口里最容易被当成「随便填填」的参数但它直接决定请求被路由到哪条连接、哪个数据集合。沪深两市的市场号大致如下市场号对应市场市场号对应市场USHI上证指数USZI深证指数USHA上证 A 股USZA深证 A 股USHB上证 B 股USZB深证 B 股USHD上证债券USZD深证债券USHJ上证基金USZJ深证基金USHP上证退市整理USZP深证退市整理USHT上证风险警示板——关键在于最后一行。上证 A 股被拆成了USHA和USHT两个市场号风险警示板是单独一个。如果你只订了USHA全市场然后把「全市场都订到了」当成前提去跑选股逻辑ST 股会静默缺席不报错、不告警只是数据里没有。这类问题在回测里几乎不可能发现实盘时才会以「某只票怎么一直没信号」的形式暴露出来。正确的做法是先用代码表接口探路再决定订哪些市场号# 拿指数市场的代码表, 返回字符串列表, 失败返回空列表 codes tds.get_code_list(USHI) # code_list 传 [] 表示该市场全量 snap tds.get_tick_stock(USHA, []) # 上证 A 股全市场快照 snap_st tds.get_tick_stock(USHT, []) # 风险警示板单独一份 super_stock tds.get_super_stock(USHA, [600000]) # 极速盘口需单独产品权限get_tick_stock这类同步查询函数和订阅是两条独立的路查询是「我问一次你答一次」订阅是「你持续推给我」。很多人混用比如用查询接口去补订阅漏掉的数据结果发现查询返回的快照和推送的快照时间戳对不上拼出来的序列是乱的。两者适合的场景完全不同——查询适合冷启动时补一份初始状态推送才是常态数据源。3.3 级联模式主从分工与两个必填配置需要在一个局域网里跑多个 SDK 实例时如果每个实例都去连服务端一是浪费带宽二是服务端连接数会有上限。级联模式解决的就是这个问题主 SDK 连服务端负责收数据和分发从 SDK 连主 SDK从局域网内拿数据。开启级联靠两步先改配置文件tool.ini再从 SDK 调接口注册主节点地址。# 主 SDK 的 tool.ini, 放在与程序/动态库/jar 同级的目录 mastertrue socket_port9999# 从 SDK 的 tool.ini, 同一台机器上端口必须错开 masterfalse socket_port9998from datafeed.tds_api import TDS tds TDS() tds.set_env(fnctickstockon_stock) # 必须在 login 之前调用, 且主 SDK 已经登录成功 ret tds.register_addr(master, 192.0.2.10, 9999) if ret ! 0: raise RuntimeError(f连接主节点失败: {ret}) # 从 SDK 的账号密码需要与主 SDK 保持一致 tds.login(demo, demo)这里有三处硬约束每一条都是踩出来的tool.ini里socket_port的值后面必须带换行符否则配置读取会失败表现为「明明写了 9999但连接不上」register_addr必须在登录前调用因为它是建立连接的动作不是登录后追加的配置主从必须处于同一局域网跨网段会直接返回级联专用错误码。另外主从同机部署时两个 SDK 的监听端口要错开不然第二个实例起来时会静默失败。级联这条链路在版本里被反复打磨过修复过级联模式下回调状态参数为空的问题做过级联模式性能优化也调整过订阅回调的参数。如果你的使用场景是多实例分发尽量用较新的版本早期版本在级联下的边界情况确实不够稳。3.4 环境参数与登录返回码先看返回值再看日志环境参数决定了 SDK 的容错行为几个关键项值得单独说清楚参数含义取值注意m_heartbeat_interval心跳间隔秒小于 1 用系统默认m_reconn_count断线重试次数小于 1 表示不重连m_reconn_interval重连间隔秒小于 1 表示立即重连m_timeout同步请求超时毫秒默认 5000m_connectmarket连接市场默认USH;USZ可只填单市场m_connectsubscribe重连后是否重发订阅1 为重发登录函数的返回值是个整数成功返回 0失败返回负数或特定正数。比较坑的是版本相关的那两个一个是正数 1 表示「版本过低请及时更新」,另一个是 -1018 表示「版本太低请更新程序」。同一个含义用了两个码原因是它们来自不同的校验环节——一个在客户端本地判断一个由服务端在下发时判断。写重试逻辑时如果只判断 0正数 1 这种情况会被当成成功然后你就会得到一个「登录成功但一包数据都没有」的诡异状态。其它值得记住的码-1002 超时无响应、-1004 用户名密码错误、-1005 无法连接至服务器、-1007 错误市场、-1011 非法订阅操作、-1016 重复登录、-1017 通行证校验失败次数太多、-1014 级联模式下主 IP 连接异常、-1015 级联模式下必须同一局域网。这套码足够细排查时直接按码查比看日志快得多。4. 数据语义字段名背后的口径与陷阱4.1 status 状态位为什么是定长字符串而不是枚举status字段看起来是个简单的状态标签实际上是整个快照里信息密度最高的字段。上证用 4 位深证用 2 位每一位含义不同无意义的位置填空格。上证第 1 位是交易阶段S 启动开市前、C 开盘集合竞价、T 连续交易、E 闭市、P 产品停牌、M 可恢复交易的熔断、N 不可恢复交易的熔断、U 收盘集合竞价。第 2 位表示该产品当前是否可正常交易第 3 位表示是否已上市第 4 位表示当前时段是否接受订单申报。深证第 1 位是阶段S、O 开盘集合竞价、T 连续竞价、B 休市、C 收盘集合竞价、E 已闭市、H 临时停牌、A 盘后交易、V 波动性中断第 2 位是 0 正常、1 全天停牌。SH_PHASE {S: 启动, C: 开盘集合竞价, T: 连续交易, E: 闭市, P: 停牌, M: 可恢复熔断, N: 不可恢复熔断, U: 收盘集合竞价} SZ_PHASE {S: 启动, O: 开盘集合竞价, T: 连续竞价, B: 休市, C: 收盘集合竞价, E: 已闭市, H: 临时停牌, A: 盘后交易, V: 波动性中断} def parse_status(market: str, status: str) - dict: # 定长字符串, 空位是空格; 不要 strip 之后再按下标取, 会错位 if market.startswith(USH): return { phase: SH_PHASE.get(status[0], status[0]), tradable: status[1:2] 1, # 用切片, 越界时返回空串而不是抛异常 listed: status[2:3] 1, accept_order: status[3:4] 1, } return {phase: SZ_PHASE.get(status[0], status[0]), halted_all_day: status[1:2] 1}为什么用定长字符串而不是整型枚举或位图因为它要跨语言、跨版本、跨市场传。字符串在任何语言的绑定层里都不会有字节序和对齐问题加一位新状态也不破坏老客户端的解析代价是每帧多几个字节并且解析责任丢给了调用方。这是这套接口「把复杂度留给调用方」思路的第一个典型体现。实际用法上有个关键区分判断「能不能在这个价位挂单排队」要看的是accept_order和tradable不是看最新价涨没涨、是不是停牌状态字符串。有些标的在连续竞价阶段是 T 且可正常交易但当前时段不接受订单申报只看第 1 位会误判。4.2 十档、撤单与内外盘字段名不能猜Level-2 快照相比 Level-1 最大的变化是档位从五档变十档并且每档多了委托笔数for tick in data_list: # 十档买卖盘, list 长度 10, 下标 0 是买一/卖一 bid_p tick[bidorder_price] # 申买价, 买一到买十 bid_v tick[bidorder_volume] # 申买量 bid_n tick[bidorder_numorders] # 该档委托笔数, Level-1 没有这个字段 ask_p tick[askorder_price] # 汇总口径 total_bid_v tick[totalbid_volume] # 总委买量 avg_bid_p tick[avgbid_price] # 加权平均委买价totalbid_volume和avgbid_price是服务端算好的汇总值。好处是省掉你在客户端遍历十档的 CPU坏处是这些值的计算口径由服务端定义和你在本地按档位加总的结果可能有细微差异——比如是否包含已撤单未确认的部分。做盘口强度类指标时口径必须统一要么全用服务端汇总值要么全用本地加总混着用会得到不可解释的指标跳动。撤单类字段只在上证提供一共 6 个买入撤单笔数、买入撤单量、买入撤单金额、卖出撤单笔数、卖出撤单量、卖出撤单金额。这几个字段是做封单真实性的核心素材——涨停板上的巨量封单如果买一量的增长伴随着买入撤单量的同步飙升说明封单在反复挂撤不是真买盘。这个判断逻辑在对延迟敏感的涨停板策略里非常常见也是对全推推送模型要求最高的部分你必须在毫秒级看到撤单字段的变化等三秒一次的轮询快照撤单早就完成了。值得注意的是买入撤单金额和卖出撤单金额这两个字段的数据类型在某次版本更新里从 int 改成了 float。这个改动的动机很实际金额是累加量用整型存会在长时间累加后溢出或者丢掉小数部分导致精度偏差而且在 Python 侧做除法时整型还会带来隐式的截断。同类字段改动在整个版本历史里出现过多次比如把某些字段的类型对齐、把结构体做优化——这类「不改变语义只改类型」的更新最容易被忽略因为它不会让程序报错只会让你的计算结果悄悄偏一点点。还有一对容易看反的字段内盘成交量inner_volume和外盘成交量outer_volume。内盘是按买价成交的量卖方主动外盘是按卖价成交的量买方主动名字和直觉相反。另外盘后成交相关的两个字段其中一个的字段名拼写有意外的形式成交量与成交次数不要按名字猜语义按文档的类型定义读。4.3 逐笔流的序号体系通道号和业务序号在解决什么快照是「某一时刻的截面」逐笔是「事件流」。事件流要能用于回放和校验就必须能判断丢包和乱序所以逐笔数据里带了一组序号字段。逐笔成交和逐笔委托都增加了通道号字段。通道号的意义在于一个市场的行情可能从多个通道并行下发同一只股票的事件在不同通道上到达你本地的时间顺序不保证和交易所的产生顺序一致。没有通道号你只能按到达时间排序一旦跨通道乱序就会拼出错误的事件序列。逐笔委托后来还加了委托序号逐笔数据本身带业务序号。这几个字段的分工大致是业务序号标识事件在服务端处理流中的位置用来判断是否丢包委托序号标识委托单本身用来把「委托—成交—撤单」串成一条完整的生命周期通道号标识事件来自哪条通道用来做跨通道去重和排序。缺了任何一个事件流就只能是「大概能用」的状态。常见的实现思路是建一个按通道分开的排序缓冲每条通道内部按序号严格递增消费跨通道之间用一个很小的滑动窗口做重排窗口大小取「最大通道间延迟差」的估计值。窗口开太小会误判乱序开太大会引入额外延迟。这个取舍在低延迟场景里很关键而 SDK 本身不做这件事——它只负责把带序号的原始事件推给你。4.4 深市五种交易方式与 mode_state 的解析2022 年 5 月中旬之后深交所发布的数据不再只有竞价交易一种。快照原来的字段变成五种交易方式的汇总值另外新增了匹配成交量、匹配成交金额、匹配成交价、成交方式、各交易方式状态这几个字段。这带来一个隐蔽的断层同一个字段名在时间点前后代表的口径不一样。跨这个时间点做历史回测如果不做口径对齐成交量和成交金额会出现系统性偏差。这不是 bug是市场规则变化导致的数据语义变化只能靠回测框架里显式标注数据版本解决。成交方式deal_mode是一个整型1 到 5 分别对应匹配成交、协商成交、点击成交、询价成交、竞买成交其余值无意义。快照里的deal_mode表示的是最近一笔成交的方式不是整个快照的。这个细节很关键不能用它来判断「这只票今天走的是哪种交易方式」只能判断「最后一笔是什么方式」。各交易方式状态mode_state是个拼接字符串格式是「交易方式 1 位 状态 1 位」循环比如1T2T3T4T5T表示五种交易方式当前都处于连续竞价状态DEAL_MODE {1: 匹配成交, 2: 协商成交, 3: 点击成交, 4: 询价成交, 5: 竞买成交} def parse_mode_state(s: str) - dict: # 1T2T3T4T5T - {1: T, 2: T, 3: T, 4: T, 5: T} # 步长 2, 奇数长度时最后一对会被丢弃, 属于预期行为 return {s[i]: s[i 1] for i in range(0, len(s) - 1, 2)}解析完能得到的结论是「某种交易方式当前是否可交易」比如只有匹配成交是 T 而协商成交是 B休市说明当前只有竞价通道开着。做多交易方式的策略时这个字段比status更细因为status描述的是标的状态mode_state描述的是交易通道状态。4.5 离线调试文件名即类型回放即压测这套系统由数据驱动。没有实时行情时可以进入离线调试模式读本地数据文件来驱动程序运行——这一点在做回归测试时价值极大因为行情是没法复现的但回放可以。机制上分三块。第一块是文件发现数据文件必须按stock.csv、index.csv、order.csv、queue.csv、trans.csv、superstock.csv、codelist.ini命名SDK 靠文件名判断数据类型命名不规范的文件直接不读。第二块是查询缓存调用快照查询接口时读取对应文件的前 3000 条指数是前 300 条剔除重复代码后生成一份可查询的数据集再从里面按传入的代码筛选返回。第三块是回放调用订阅接口一律返回成功然后按文件顺序、按设定的间隔和单次条数循环推送触发对应的回调函数。tds TDS() tds.set_env(fnctickstockon_stock) # 必须在 login 之前调用; 已经登录过则不生效 ret tds.start_offline_debug( path/data/replay, # 数据文件所在目录 subtype[stock, index, order, queue, trans, superstock], push_space3000, # 两次推送间隔, 毫秒 push_count5000, # 单次推送条数 playback_times1, # 文件遍历次数 ) # 离线模式下 login 不连网关, 直接返回成功 tds.login(demo, demo)push_count和push_space这两个参数的组合本质是在模拟不同的行情密度。把push_space调到 0、push_count调大就是一次压力测试看你的回调链路在满速推送下会不会丢数据、队列会不会爆。这比等开盘日抓问题高效得多。还有两个边界要知道离线调试必须在实例创建之后、登录之前调用登录之后再调不生效级联模式下只有父节点支持离线调试子节点调用无效但父节点开了调试后子节点可以正常取到数据。5. 避坑与排查五类真实故障的现象、原因与解法5.1 连接与登录报错码比日志更快现象一登录返回正数 1 或 -1018之后程序正常运行但一包数据都收不到。原因SDK 版本与服务端网关的协议版本不匹配服务端拒绝下发数据但连接层是通的所以心跳、重连全都正常。这两个码来自不同校验环节一个本地判断、一个服务端判断容易只处理了其中一个。 解决登录返回值不要只判断 0把正数 1 和 -1018 都归为「版本问题」直接终止并提示升级不要在版本不对的情况下继续跑业务逻辑。同时检查set_env里有没有手动指定过m_version硬编码的版本号会覆盖 SDK 自己的判断。现象二运行几小时后数据突然静止进程还活着日志里没有任何异常。原因断线通知回调没有注册或者注册了但只打印了一行日志没做处理。断线重连次数耗尽后 SDK 会停止工作此时它不会自己恢复必须由调用方重新登录并重订阅。 解决一定要注册断线通知回调并在回调里做完整的状态重置——重新调用登录函数再重新下发订阅。通知内容里带的原因码能区分网络断开、网卡异常、重复登录被踢三种情况重复登录被踢这种要单独告警因为它通常意味着账号在别处被使用了。def on_notify(msg_notify): # code: 1 网络断开, 2 网卡不正常, 3 重复登录被踢下线 print(fdisconnect: code{msg_notify[code]} msg{msg_notify[msg]}) if msg_notify[code] 3: alert(账号在别处登录) # 这种情况重连也没意义, 先告警 return relogin_and_resubscribe() # 重新 login sub, 不要只重连注意m_reconn_count小于 1 表示完全不重连m_reconn_interval小于 1 表示立即重连。两个参数都设成「激进值」时一次短暂的网络抖动会引发密集重连反而更容易被服务端判定为异常。5.2 订阅与回调静默丢数据最麻烦现象三订阅返回成功大部分票有数据个别票一直没有。原因市场号没覆盖全。上证 A 股拆成USHA和USHT两个市场号只订前者会漏掉风险警示板另一种情况是code_list传了字符串而不是列表SDK 把一个字符当成一个代码去解析失败但不报错。 解决先用代码表接口确认目标代码落在哪个市场号下再补订阅code_list一律用列表只有一个代码也写成[600000]。全市场订阅记得两个市场号都订。现象四刚启动时延迟很低跑一段时间后回调越来越慢最后数据严重滞后。原因回调函数里做了耗时操作——写数据库、加锁、发网络请求、做重计算。回调运行在接收线程里一次慢调用就会让后面的包排队排队又会加剧下一次处理的延迟形成正反馈。这种故障的典型特征是「不报错、不丢包、就是越来越慢」。 解决回调里只做入队所有处理搬到独立工作线程。队列要有界并且有明确的丢弃策略——行情数据过期就失去价值宁可丢最旧的也不要阻塞回调。改造后回看stats里的recv - send正常情况下应该回到一个稳定的窄区间。现象五分两次订阅第二次调用后第一次订的代码没数据了。原因用了sub而不是add_sub。sub的语义是「取消该市场该类型的全部订阅再按新列表重订」它做的是替换不是追加。 解决增量订阅一律用add_sub只有在需要整体重置订阅集合时才用sub。改完之后建议在订阅回调里把op_type打出来1 是全量替换、3 是增量追加日志里一眼能看出这次到底做了什么操作。5.3 级联与调试回放配置的第一个字符就可能出错现象六级联从 SDK 连接主 SDK 失败或者连上了但登录报错。原因常见有三种——主 SDK 还没登录成功就先起了从 SDK级联要求主节点先就绪tool.ini里socket_port的值后面少了换行符配置读不出来端口用了默认值主从不在同一局域网跨网段会被直接拒绝。 解决按顺序检查——主 SDK 的mastertrue和端口是否生效看启动日志里打印的监听端口、主是否已登录、从的register_addr是否在login之前调用、两台机器是否同网段。主从同机部署时两个socket_port必须不同。现象七回放调了离线调试函数但回调一次都没被触发。原因调用时机在登录之后函数不生效或者subtype里填的文件名对应文件不存在、命名不规范比如写成stock.CSV或stocks.csv再或者是级联模式下的子节点本身就不支持离线调试。 解决把start_offline_debug严格放在实例创建之后、login之前核对文件名与数据类型的对应关系本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询