Level-2全推行情Python API:盘口策略与涨停板监控实战 简介同花顺Level-2全推行情Python接口手册面向证券高频交易与量化策略开发者服务于涨停板、盘口异动和低延迟行情等实战场景。内容围绕行情服务的C/S架构讲清沪深Level-1、Level-2行情的接入方式并覆盖五档与十档快照、逐笔成交、逐笔委托、委托队列、极速盘口等数据的订阅与回调处理同时梳理多连接模式、上证逐笔委托、级联与异步订阅、离线调试、通道号和委托序号、快照字段扩充及数据精度调整等版本演进。压缩包为1个PDF约850KB目录清晰便于按版本与接口模块查阅并给出运行环境、安装验证、初始化与回调函数定义等落地细节。已有2846人学习适合需要理解接口字段、排查订阅问题并搭建行情接入流程的量化开发者参考也可作为排错与实战思路。1. 盘口上封单还压着八十万手你的委托却排在几万手之后这不是行情慢是五档行情本身看不到队列。做涨停板、封单衰减、撤单率这类信号光靠十个价格档位的汇总数字是不够的你得有 Level-2 全推行情逐笔成交、逐笔委托、委托队列加上带委托笔数的十档快照。这份《Level-2 全推行情 Python API 操作手册》解决的就是「怎么用 Python 把这些数据接进策略进程」这一段路——回调驱动、全推推送、不轮询覆盖沪深两市的指数快照、证券快照、逐笔成交、逐笔委托、委托队列和极速盘口。它适合两类人一类是 Python 已经写熟、准备把策略从日线级推到盘口级的量化开发者另一类是要给自建回测环境灌真实高频行情的人。读完至少能判断清楚这台机器的带宽、账号权限和代码结构撑不撑得起一条盘口级的数据链路。2. TDS 架构与三种运行形态解码、订阅、分发分别在哪一层行情 SDK 最怕被当成黑匣子用——装上、跑通、然后某天回调不来了完全不知道断在哪。TDS 这套东西的结构其实很直白先把它拆成三段看清楚后面调参和排查才有落点。2.1 三段式链路从行情源到你的回调函数TDS 是典型的 C/S 结构。服务端从上游行情接入网关拿到实时数据中间做解码和订阅管理最后以 API 形式对客户端分发。落到代码上你看到的是三个动作set_env注册回调、login建立会话、sub或add_sub声明你要哪些代码的哪类数据。之后数据不是你去拉而是 SDK 在内部线程里推过来。这条链路里SDK 本身做了两件容易被忽略的事。一是多连接V2.0.0 之后从单连接改成了多连接模式不同市场注册不同的域名和端口沪市和深市的数据走各自的通道一个通道抖动不会把另一个拖死。二是高可用断线重连、重连后重发订阅、级联转发都在 SDK 内部处理掉暴露给 Python 层的只有回调。回调函数的签名是固定的三个参数这一点在写第一行代码前就要记住def fnc_data(data_list: list, count: int, stats: dict): SDK 行情回调六类数据共用这一个签名 param data_list: 行情数据列表一次可能推多条 param count: data_list 的条数别拿 len(data_list) 硬算以它为准 param stats: 链路时间戳字典三个 key produce_time 服务端收到行情的 unix 微秒时间戳 send_time 服务端发出的时间戳 recv_time SDK 收到的时间戳 passstats这个参数很多人直接丢掉其实它是排查延迟的唯一抓手。用recv_time - produce_time就能算出这一段链路的端到端耗时单位是微秒。做盘口策略时如果这个差值长期超过几十毫秒先别怀疑策略先看网络和机器负载。多语言支持是这套 SDK 的另一个设计点同一套 C/C 内核外面套 Python、Java 的绑定所以字段名、返回码、订阅语义在三种语言里是一致的。你在 Python 文档里看到的-1018 版本过低换成 Java 也是这个码。2.2 Python 包的安装与模块布局Python 版本是以 tar.gz 分发的不是从包索引拉。安装有两条路效果一样# 方式一直接 pip 安装本地包 pip install ./L2-tdsapi-python-X.X.X-XXXXXXXX.tar.gz # 方式二解包后走 setup.pyLinux 下需要 root python setup.py install # 验证不报异常即安装成功 python -c import datafeed; print(ok)装完之后的模块结构很简单datafeed包里最核心的是tds_apiTDS类就定义在这里from datafeed.tds_api import TDS # 创建实例这一步不建立任何网络连接 tds TDS()有几个路径规则需要提前知道它们决定了你后面调试时去哪找日志、去哪放配置日志默认写在datafeed包的安装目录下通过set_env(m_logpath...)可以改级联模式的tool.ini在 Python 版本里要求和程序文件同级C 版本是跟动态库同级Java 版本是跟 jar 同级别混离线调试用的 CSV 数据放在你自己指定的任意目录通过start_offline_debug(path...)传进去。硬件和系统门槛不高但别忽略64 位双核以上 CPU、8G 以上内存、下行 100Mb 以上带宽系统支持 Windows 7 及以上和主流 Linux 发行版Python 需要 3.5 的 64 位版本以上。带宽这一项在订阅全市场逐笔时是硬约束——十档快照加两路逐笔全推峰值流量不小百兆是底线而不是推荐值。2.3 级联模式一台机器收数据多台机器用数据级联解决的问题很具体你手上有五个策略进程每个都要行情但行情账号的连接数是有限的而且五个进程各自直连服务端带宽和连接都会被放大五倍。级联模式让主 SDK 只连一次服务端其余从 SDK 连主 SDK在局域网内分发。切换靠一个叫tool.ini的本地配置文件Python 版本放在程序文件同级目录。主节点和从节点的差别只有两个字段# 主 SDK 的 tool.ini mastertrue socket_port9999 # 从 SDK 的 tool.ini同一台机器上端口必须错开 masterfalse socket_port9998master决定主从属性默认是truesocket_port是监听端口。这里有个血泪经验端口号后面必须接换行符文件末尾缺了这一行配置参数可能读不出来表现是 SDK 静默按默认值跑你怎么改都不生效。配置好之后从 SDK 侧在登录前调一次register_addrfrom datafeed.tds_api import TDS tds TDS() def callback_stock(data, count, stats): print(receive count:, count) tds.set_env(fnctickstockcallback_stock) # 从 SDK 专用注册主 SDK 的 IP 和监听端口 # market 固定填 master不是 USHA/USZA 这类市场码 tds.register_addr(master, 192.168.1.10, 9999) # 从 SDK 的账号密码必须与主 SDK 一致 tds.login(test1, 123456)调用顺序是死的错了就是连不上主 SDK 先login成功从 SDK 再register_addr然后从 SDK 才login。register_addr必须在登录前调用登录后再注册是无效的。另外从 SDK 侧的账号密码要和主 SDK 完全一致不是随便填一个能过就行。2.4 离线调试用 CSV 造一段行情做回放盘后想验证策略逻辑总不能等到第二天开盘。start_offline_debug就是干这个的读本地 CSV 文件按你设定的节奏往回调里推完全离线不连线上服务端login直接返回成功。from datafeed.tds_api import TDS tds TDS() def callback_stock(data, count, stats): print(receive count:, count, first:, data[0]) tds.set_env(fnctickstockcallback_stock) # 注意必须在实例创建之后、登录之前调用已登录状态下调用不生效 tds.start_offline_debug( pathD:\\datafeed, # 数据目录 subtype[stock, index, order, queue, trans, superstock], push_space3000, # 两次推送间隔单位毫秒 push_count5000, # 单次推送条数 playback_times1 # 文件遍历次数 ) # 离线状态下 login 不连服务端直接返回成功 tds.login(test1, 123456)参数里最容易踩的是subtype它填的是文件名前缀不是完整文件名也不要把codelist塞进去——代码表是 SDK 自己按需读的。文件命名必须严格是stock.csv、index.csv、order.csv、queue.csv、trans.csv、superstock.csv、codelist.ini这几者之一SDK 靠文件名判断数据类型命名不规范直接跳过不读而且不报错。回放行为和实时行情对齐得比较细调用订阅接口一律返回成功调用快照查询时SDK 只读文件前 3000 条指数是前 300 条并剔除重复代码生成一份可查询的数据集回放时按subtype的顺序挨个文件读每次读push_count条间隔push_space毫秒读完一个文件再读下一个所有文件跑完playback_times遍后结束。如果某次读出来的数据里没有你订阅的代码那一次就不触发回调——这点和实盘一致别以为是 SDK 卡了。级联场景下有个限制只有主节点能开离线调试从节点调这个函数无效但主节点开起来之后从节点可以正常拿到回放数据。做多进程联调时这个特性挺省事一套 CSV 喂给整条链路。3. 主链路代码拆解set_env、login、查询、订阅四步走装好包、想清楚用哪种形态接下来就是一条固定顺序的调用链。这四步里任何一步的参数设错后面都不会报错只会表现为「没数据」——所以每一步都值得单独说清楚。3.1 set_env回调注册和环境参数一次性挂上去set_env是整条链路的地基规则只有一条但必须遵守初始化 TDS 之后、注册和登录之前必须先调它。一般场景下只传回调函数其余参数留默认值即可。from datafeed.tds_api import TDS tds TDS() def fnc_tick_stock(data_list, count, stats): 证券快照回调 pass def fnc_transaction(data_list, count, stats): 逐笔成交回调 pass def fnc_orderdata(data_list, count, stats): 逐笔委托回调 pass def fnc_orderqueue(data_list, count, stats): 委托队列回调 pass def fnc_notify(msg_notify): 断线通知回调只收一个参数 msg_notify {code: int, msg: str} code: 1 网络断开 / 2 网卡异常 / 3 重复登录被踢下线 print(disconnect:, msg_notify[msg]) def fuc_subscribe(data_type, op_type, market, code_list, statuscode, callback_type): 异步订阅结果回调六个参数 callback_type: 1 异步订阅触发 / 2 重连或重新登录后自动重发订阅 if statuscode ! 0: print(sub failed:, data_type, market, statuscode) ok tds.set_env( fnctickstockfnc_tick_stock, fnctransactionfnc_transaction, fncorderdatafnc_orderdata, fncorderqueuefnc_orderqueue, fncnotifyfnc_notify, fucsubscribefuc_subscribe, # 下面这些不传就用默认值按需开 m_heartbeat_interval5, # 心跳间隔秒1 用系统默认 m_reconn_count10, # 断线重试次数1 表示不重连 m_reconn_interval3, # 重连间隔秒1 立刻重连 m_timeout5000, # 同步请求超时毫秒默认 5000 m_connectmarketUSH;USZ, # 连接市场可只填 USH 或 USZ m_connectsubscribe1, # 1 重连成功后自动重发订阅 m_loglevelwarn, # 日志级别 m_logpath./logs # 日志路径默认是包安装目录 )参数里最值得动的是三个。m_reconn_count设成 10、m_reconn_interval设成 3意思是断线后每 3 秒重试一次、最多 10 次10 次都失败就触发fncnotify告诉你 SDK 已停止工作此时你得自己重新login加重新订阅。m_timeout只影响同步请求get_tick_stock这类默认 5000 毫秒全市场查询时建议往上调不然很容易拿到空列表。m_connectmarket是省流量的好开关——只做沪市就填USH深市的连接根本不会建。回调函数本身的写法有硬约束不要在回调里做耗时长的操作。回调跑在 SDK 的接收线程上你在里面写数据库、发 HTTP、甚至只是print一大堆内容都会拖慢接收轻则延迟飙高重则堵包丢数据。正确做法是往队列里扔一下就走消费逻辑放到自己的线程里这一点在第 6 章会展开。3.2 login返回码表比 try/except 更可靠login返回的是 int不是抛异常。0 表示成功负数是失败。这套返回码覆盖了认证、网络、订阅校验各个层面比笼统的异常信息有用得多。ret tds.login(test1, 123456) if ret ! 0: # 关键错误码单独处理其余归入通用失败 if ret -1018: # 版本过低SDK 版本与账号权限不匹配必须升级 SDK raise SystemExit(SDK 版本过低请更新到最新版本) elif ret -1005: # 用户名或密码错误重试没意义 raise SystemExit(账号或密码错误) elif ret in (-1004, -1006, -1014): # IP 受限 / 错误 IP / 级联主 IP 异常属于网络与环境问题 raise SystemExit(网络或 IP 受限检查出口 IP 与主节点) elif ret -1016: # 重复登录同一账号在别处已登录会导致先前会话被踢 print(重复登录旧会话可能已被顶下线) else: raise SystemExit(f登录失败返回码 {ret})完整返回码里几个需要记住的-1001一般错误、-1002超时无响应、-1003IP 禁止访问、-1004用户名密码错误、-1005无法连接服务器、-1006错误 IP、-1007错误市场、-1008错误代码、-1009无效 handle、-1010无效数据类型、-1011非法订阅操作、-1012认证中心登录认证失败、-1013服务端登录验证失败、-1014级联模式主 IP 连接异常、-1015级联模式要求同一局域网、-1016重复登录、-1017通行证校验失败次数过多、-1018版本过低。-1015这一条踩坑率很高级联模式要求主从 SDK 在同一局域网内跨网段或者过了 NATregister_addr就会返回这个码。别想着用端口映射绕过这个限制是设计出来的。-1018是 V2.1.3 新增的SDK 版本和账号权限对不上时出现遇到它不要改代码去换包。3.3 同步查询先把代码表和快照拿全查询接口是同步的调一次拿一次结果不走回调。四个接口覆盖了代码表、指数快照、证券快照和极速盘口# 1. 拿代码表market 传市场码返回字符串列表失败返回 [] codes tds.get_code_list(USHA) print(沪市A股代码数:, len(codes)) # 2. 指数快照code_list 传 [] 表示全市场 index_snap tds.get_tick_index(USHI, []) # 只查特定指数 index_snap tds.get_tick_index(USHI, [000001]) # 3. 证券快照十档 委托笔数盘口策略的主力数据 stock_snap tds.get_tick_stock(USHA, [600000]) # 4. 极速盘口需要额外的产品权限没权限返回空 super_snap tds.get_super_stock(USHA, [600000])市场码必须填准这是「查不到数据」最常见的原因。沪深两市拆得比想象中细市场码对应市场市场码对应市场USHI沪市指数USZI深市指数USHA沪市 A 股USZA深市 A 股USHB沪市 B 股USZB深市 B 股USHD沪市债券USZD深市债券USHJ沪市基金USZJ深市基金USHP沪市退市整理USZP深市退市整理USHT沪市风险警示板——USHT是独立市场码容易被漏掉。纯 A 股策略如果只订阅USHAST 和风险警示板的票是拿不到的而这类票恰恰是涨停板策略的高频目标。查某只票之前先确认它在哪个市场码下面。get_code_info在文档里标了暂未实现别在它上面花时间。3.4 订阅sub、add_sub、del_sub 和异步订阅的分工订阅接口有四个同步的加一个异步的语义差别很大尤其是sub接口行为适用场景sub(data_type, market, code_list)取消该市场该类型的所有订阅后重新按 code_list 订阅整体切换股票池unsub(data_type, market)取消该市场该类型的全部订阅收盘或切换策略add_sub(data_type, market, code_list)在现有订阅基础上追加代码盘中动态加票del_sub(data_type, market, code_list)从现有订阅中移除指定代码盘中剔除async_sub(data_type, op_type, market, code_list)异步提交结果走fucsubscribe回调订阅量大、不想阻塞主线程data_type支持六种index指数快照、stock证券快照、queue委托队列、order逐笔委托、trans逐笔成交、superstock极速盘口。盘中动态加票千万别用sub。假设你已经订阅了 3000 只票的逐笔成交现在想再加一只用sub(trans, USHA, [600001])的结果是先把你原来 3000 只全退了再只订这一只。行情会突然少一大片而且不报错——这个坑我见过不止一次。正确写法是用add_sub# 全市场订阅code_list 传 [] 表示该市场全部代码 tds.sub(stock, USHA, []) # 沪市 A 股全市场快照 tds.sub(trans, USZA, []) # 深市 A 股全市场逐笔成交 # 盘中追加不影响已有订阅 tds.add_sub(order, USHA, [600000, 600001]) # 盘中剔除 tds.del_sub(order, USHA, [600001]) # 异步订阅适合订阅请求本身很重的场景结果从 fucsubscribe 回来 # op_type: 1 全新订阅 / 2 取消订阅 / 3 增加订阅代码 / 4 取消订阅代码 tds.async_sub(queue, 1, USHA, [])异步订阅的价值在于不阻塞。同步订阅在订阅量极大时会卡住调用线程几百毫秒甚至更久如果你的策略主循环和订阅在同一线程这段时间是失明的。async_sub提交后立刻返回真实结果从fucsubscribe里拿statuscode为 0 才算成功。注意async_sub是新版本才有的接口老包上会直接属性报错。4. 数据结构与字段快照、逐笔、委托队列怎么读数据到手之后真正决定策略质量的是字段。Level-1 和 Level-2 的差别不只是「五档 vs 十档」还有委托笔数、撤单量、内外盘、盘后成交这些能直接算成因子的东西。4.1 五档与十档快照的字段差异两套快照共用一批基础字段market、code、codename、status、date、time、pre_price、open_price、high_price、low_price、new_price、volume、turnover、trade_num、uplimit_price、downlimit_price、inner_volume、outer_volume、aftermarket_volume、aftermarket_volmount。差别在盘口和委托维度。Level-1 是五档字段名是bidorder_price/bidorder_volume/askorder_price/askorder_volume列表长度 5。Level-2 是十档除了这四个列表变成 10还多出四组字段类型含义bid_numorderslist买一到买十每个档位的委托笔数ask_numorderslist卖一到卖十每个档位的委托笔数totalbid_volumeint总委买量totalask_volumeint总委卖量avgbid_pricefloat加权平均委买价avgask_pricefloat加权平均委卖价委托笔数这个字段是封单分析的起点同样是 80 万手封单由 200 笔构成和由 8000 笔构成性质完全不同。再加上沪市独有的撤单字段withdraw_buynum、withdraw_buyvolume、withdraw_buyturnover、withdraw_sellnum、withdraw_sellvol、withdraw_sellturnover以及totalbid_num、totalask_num、bidorder_num、askorder_num四个总量字段封单质量和撤单率就能实时算出来。def calc_seal_strength(snap: dict) - dict: 从十档快照里算封单强度指标 snap 来自 fnctickstock / get_tick_stock 的单条数据 bids snap.get(bidorder_volume, []) bid_orders snap.get(bid_numorders, []) if not bids or not bid_orders: return {} # 买一档单笔均量值越小说明封单越分散越可能是散户堆的 avg_per_order bids[0] / bid_orders[0] if bid_orders[0] else 0 # 十档买单集中度买一占十档总量的比例 total sum(bids) or 1 concentration bids[0] / total # 沪市才有撤单量深市取 0 withdraw_buy snap.get(withdraw_buyvolume, 0) volume snap.get(volume, 0) or 1 withdraw_ratio withdraw_buy / volume return { code: snap.get(codename), avg_per_order: avg_per_order, concentration: concentration, withdraw_ratio: withdraw_ratio, }这段代码只做了三件事算买一档的单笔均量、算买一集中度、算撤单比。三个值都从快照单条数据里就能得到不需要跨包状态。注意withdraw_buyvolume是沪市字段深市数据里没有.get带默认值是必须的。另外 V2.0.6 把withdraw_buyturnover和withdraw_sellturnover从 int 改成了 float如果你的下游表结构还是整型落库时会截断小数——这种字段类型变更在版本升级说明里只有一行但引发的线上问题不小。4.2 逐笔成交、逐笔委托与委托队列逐笔数据是 Level-2 区别于五档的核心。三条流各有分工trans是已经成交的每一笔order是每一笔新进来的委托queue是买一/卖一档位上的排队明细。逐笔这一路在版本演进里加了不少字段做逐笔还原必须用上channel_no通道号V2.0.7 加、业务序号V2.0.1 加、order_no委托序号V2.1.9 加、deal_mode成交方式V2.1.4 加。业务序号和委托序号是做逐笔委托到逐笔成交配对的关键——没有它们你没法把一笔成交准确追溯到是哪一笔委托产生的。def on_transaction(data_list, count, stats): 逐笔成交回调只做解析和入队不做任何 IO for item in data_list: record { code: item.get(code), market: item.get(market), time: item.get(time), # HHMMSSsss price: item.get(new_price), volume: item.get(volume), turnover: item.get(turnover), channel_no: item.get(channel_no), # 通道号多通道去重用 deal_mode: item.get(deal_mode), # 1 匹配 2 协商 3 点击 4 询价 5 竞买 } raw_queue.put(record) # 只入队消费在另一线程这段代码的重点不在字段而在最后一行。逐笔成交在全市场订阅下每秒可能是几万条回调里做任何字符串拼接、数据库写入都会拖垮接收线程。raw_queue是一个有界队列满了就丢弃并计数——丢数据比堵死好至少你知道丢了多少。委托队列queue是另一个维度它给的是买一档或卖一档上的具体排队构成是判断「我前面还有多少人」的直接依据。打板策略里同一个价位上封单从 200 笔变成 20 笔往往比封单量本身更早给出信号。极速盘口superstock需要单独的产品权限没开通的话订阅返回失败、查询返回空。它比普通快照更快V2.0.7 之后加了最新价字段。如果你的策略对盘口刷新频率敏感这是可选的一层加速但别把它当默认能力规划——权限是额外的。4.3 status、deal_mode、mode_state 三个状态字段这三个字段是策略的开关判断错了会在不该下单的时候下单。status在沪深两市的编码规则不同。沪市是 4 位字符串第 1 位表示时段S 启动、C 开盘集合竞价、T 连续交易、E 闭市、P 停牌、M 可恢复熔断、N 不可恢复熔断、U 收盘集合竞价第 2 位是能否正常交易0/1第 3 位是是否上市0/1第 4 位是当前时段能否接受订单申报0/1无意义的位填空格。深市是 2 位第 1 位是时段S 启动、O 开盘集合竞价、T 连续竞价、B 休市、C 收盘集合竞价、E 已闭市、H 临时停牌、A 盘后交易、V 波动性中断第 2 位是状态0 正常、1 全天停牌。def is_continuous_trading(snap: dict) - bool: 判断当前是否处于可下单的连续交易时段 status snap.get(status, ) market snap.get(market, ) if market.startswith(USH): # 沪市第 1 位 T 连续交易第 4 位 1 表示当前时段可接受订单申报 return len(status) 4 and status[0] T and status[3] 1 else: # 深市第 1 位 T 连续竞价第 2 位 0 表示正常状态 return len(status) 2 and status[0] T and status[1] 0深市的deal_mode和mode_state是 2022 年 5 月 16 日之后的产物。在此之前深市只发竞价交易数据之后拆成匹配成交、协商成交、点击成交、询价成交、竞买成交五种快照里的原有字段变成五种方式的汇总另外单独给matching_volume、matching_turnover、matching_price、deal_mode、mode_state五个字段。deal_mode取值为 1 到 5 对应五种方式快照里的值表示最近一笔成交的方式mode_state是拼接串格式像1T2T3T4T5T两位一组前一位是交易方式编号后一位是该方式的状态码。这套变化对回测的影响是实打实的如果你拿 2022 年 5 月之前的深市数据算「成交量」和之后的数据不是同一个口径混在一起做因子会失真。落库时把deal_mode一起存下来后面按方式过滤才有余地。5. 避坑与常见问题排查断线、堵包、级联、字段类型这套 SDK 的坑有个共同特征大部分错误不抛异常。返回空列表、回调不来、订阅静默失败都得靠你自己判断。下面五条是我在实际项目里反复遇到的。5.1 现象跑了一晚上第二天发现中间少了两个小时的数据原因通常是两类。一类是回调里做了耗时操作导致堵包——SDK 的接收线程被你的数据库写入或日志打印卡住内部缓冲区满了就丢数据而且丢得悄无声息。另一类是断线后没有重连成功fncnotify报了code1但没人处理。解决分两步。第一回调只入队消费另起线程import queue import threading raw_queue queue.Queue(maxsize200000) # 有界队列满了丢弃而不是无限涨 drop_count 0 def on_tick(data_list, count, stats): global drop_count try: raw_queue.put_nowait((data_list, stats)) except queue.Full: drop_count 1 # 记下来监控里要能看到 def consumer(): while True: data_list, stats raw_queue.get() # 这里才做落库、计算、告警 batch_insert(data_list) threading.Thread(targetconsumer, daemonTrue).start()第二fncnotify里必须实现重新登录和重新订阅。SDK 在多次重连失败后会停止工作此时它不会自己恢复def on_notify(msg): if msg[code] in (1, 2): # code 1 网络断开 / 2 网卡异常两种情况都走同一套恢复流程 ret tds.login(USER, PWD) if ret 0: resubscribe_all(tds) # 重新拉起全部订阅 elif msg[code] 3: # 重复登录被踢这台机器不要再自动重连会互相踢 raise SystemExit(账号在别处登录本进程退出)code3单独处理很关键。如果是同一账号在另一台机器上登录把你踢了你还自动重连会变成两台机器互相踢谁都收不到数据。顺带说一句m_connectsubscribe1能让 SDK 在重连成功后自动重发订阅但这个自动重发的结果从fucsubscribe回来callback_type2你没注册这个回调的话根本不知道重发成功了没。5.2 现象订阅全市场之后CPU 直接打满延迟飙到几百毫秒原因几乎都是回调里做了 Python 层的重计算。全市场逐笔成交在活跃时段每秒几万条每条都做一次dict组装加上字符串格式化GIL 直接顶住。解决办法有三层。一是回调里只入队这点上面说过。二是减少订阅范围只订你真正要的代码get_code_list拿全表之后自己筛一遍再sub。三是把重计算挪到批量消费端一次处理 5000 条比 5000 次处理 1 条快一个量级。还有个小技巧m_connectmarket只填你需要的市场能砍掉将近一半的流量。5.3 现象级联模式从 SDK 一直连不上主 SDKregister_addr返回负数按出现频率排三个原因。第一tool.ini最后一行没换行配置根本没被读到SDK 还在默认的非级联模式下跑。第二主从 SDK 放在同一台机器上socket_port配成了同一个端口监听冲突。第三调用顺序错——先login再register_addr或者主 SDK 还没登录成功从 SDK 就开始连。排查顺序建议这么走先cat -A tool.ini看行尾有没有$Linux 下确认换行符再确认两个端口不重复最后核对代码里的调用顺序是不是set_env→register_addr→login。如果返回-1015那是跨网段了级联模式要求主从在同一局域网这个改不了。5.4 现象升级 SDK 之后落库报类型错误或者金额小数全变 0字段类型在版本之间变过。V2.0.6 把withdraw_buyturnover和withdraw_sellturnover从 int 改成 floatV2.1.3 把消息通知回调的入参从 int 改成了 dictV2.1.6 给订阅回调加了参数。这几处都是破坏性变更。解决办法是升级前先做字段对齐把新版本的字段清单拉出来和自己下游的表结构、序列化方案逐项比一遍重点看数值类型和回调签名。建表时金额类字段统一用DECIMAL或至少DOUBLE别用整数。回调签名变更更麻烦——参数个数对不上不会报错只会让你的处理逻辑读错位。升级后在离线调试模式下跑一遍全链路比在实盘发现快得多。5.5 现象登录返回-1018或者盘中突然收到权限到期提醒-1018是 V2.1.3 之后新增的返回码含义是版本过低通常出现在 SDK 包版本落后于账号权限要求的场景。它不是配置问题改代码没用直接换最新的包。权限到期是另一回事。V2.0.5 之后 SDK 会在快照数据里附带权限到期提醒和升级提醒这些信息不会让接口报错只是悄悄混在数据里。如果你的进程长期无人值守到期那天所有订阅突然返回空排查起来很费时间。稳妥做法是把这类提醒当成一个独立监控项收到就告警。6. 二次开发把回调数据接进落库管道和涨停板监控跑通 demo 只是第一步真要用起来得解决三件事数据怎么存、订阅怎么自愈、策略怎么验证。先说服役端结构。我的习惯是三层回调层只做put_nowait中间层按 5000 条或 200 毫秒两个条件哪个先到就批量提交存储层用列式文件或时序库按天分区。批量提交这一步效果很明显单条 insert 和 5000 条批量 insert 在总吞吐上差一个数量级而行情数据天然就是流的形态攒批没有任何副作用。队列一定要有界满了丢弃并计数drop_count挂到监控上——盘口策略丢一秒数据和丢一小时数据结论完全不同。第二件事是订阅自愈这块比想象中重要。完整的自愈链路应该是fncnotify收到断线 → 判断code是否为 3被踢则直接退出不要重连→ 重新login→ 重新拉起全部订阅 → 从fucsubscribe的callback_type2确认重发结果。同时把当前订阅状态维护成一份内存里的字典sub/add_sub/del_sub每次调用都同步更新自愈时直接按字典重放别靠人工记。我一般还会加一个兜底定时器每 30 秒对比一次「期望订阅」和「实际回调里出现过的代码」连续 N 个周期没数据的代码重新add_sub一次。第三件事是验证这块最容易被跳过。start_offline_debug的价值就在这里把一段真实的涨停过程录成 CSV改完代码先用回放跑一遍看封单强度、撤单率、委托队列变化的时间点对不对再去碰实盘。回放参数我习惯设成push_space1000、push_count2000接近真实推送节奏想压测消费端吞吐就把push_space调到 0看队列会不会打满、drop_count涨不涨。至于涨停板监控本身最小实现其实不复杂维护一个「当前涨停代码」的集合每次快照回调检查new_price uplimit_price进入集合时记录首次封单量和封单笔数之后每个快照更新封单衰减率、撤单比、买一集中度。三个指标同时异常封单量掉、撤单比升、集中度降时给出信号。这套逻辑全部基于第 4.1 节那几个字段不需要额外数据源。有个细节值得一提涨停价的字段是uplimit_price但不要用它做判断的唯一依据沪市和深市在停牌、风险警示板的涨跌幅限制不同status里的可交易标志和涨跌停价要一起看。我吃过一次亏某只 ST 票的涨跌幅限制是 5%代码里按 10% 算的上限去比对全天都没触发。最后一句话是我自己的教训从那以后我每次升级 SDK 版本都强制走一遍「字段清单 diff → 离线回放 → 小范围订阅实盘观察一天」这三步一步不省。这套流程比起在盘中发现数据不对再回头查省下的时间不止十倍。希望帮到你。本文还有配套的精品资源点击获取