实时行情接入:限频、抖动、断线
三种客户端跑同一个源——朴素轮询用 128 次请求换 86 条数据并触发 21 次限频,指数退避用 92 次换 84 条、零次限频
真实行情源会做三件事让你难受:限频、抖动、断线。
用一个确定性的假数据源(固定种子)模拟它们,三种客户端的正常轮询节奏完全相同,唯一区别是失败之后怎么办:
客户端 请求数 成功 429 500 断线读 成功率%
A 朴素轮询(失败立刻重试) 128 86 21 8 13 67.2
B 固定间隔重试(3 步) 97 86 0 7 4 88.7
C 指数退避+抖动+熔断 92 84 0 5 3 91.3
C 用少了 28% 的请求,拿到几乎一样多的数据(84 vs 86),而且零次限频。
A 触发 21 次 429,C 是 0 次——失败立刻重试,是在给限频火上浇油。
⚠️ 不联网:真实源不可复现,而不可复现的实验测不出策略差异(p5.9)。
电梯按钮按一次和按十次,电梯来的速度是一样的。
但如果按钮是坏的,你连按十次只会让它坏得更彻底。
失败之后立刻重试,就是连按十次。
一、三条必须实现的机制
| 机制 | 做什么 | 为什么 |
|---|---|---|
| 指数退避 | 失败后等待时间翻倍(上限 32) | 避免把限频打得更死 |
| 抖动 jitter | 等待时间加一个随机量 | 避免多个客户端同时重试形成尖峰 |
| 熔断 | 连续失败 N 次就停一段时间 | 源真的挂了时,别再浪费配额与日志 |
抖动这一条最容易被省掉。 如果所有客户端都是"失败后等 2 秒、4 秒、8 秒",那它们会在同一时刻集体重试——把一次故障变成一次雪崩。
二、这个实验一开始是错的
第一版的假数据源用「自己被调用了多少次」当时间。
结果是:低频客户端的"最近 10 次调用"永远就是它最近的 10 次调用,于是它也必然被限频。三种客户端因此测不出任何差别——C 只拿到 5 条数据,还被 429 了 11 次。
排查结论:模型错了,不是客户端错了。
修法是把客户端的时间步传进去:
def fetch(self, step: int):
# ⚠️ 必须用客户端的时间步,不能用「源自己被调用了多少次」
self.t = step
这与 p7.4 的第一版实验、p7.6 的第一版体检工具是同一个模式:
实验没测出预期的差别时,先怀疑实验设计。
三、断线重连之后:最容易错的一步
重连成功后不能直接续着推,必须先做两件事:
- 拉一次快照补齐断线期间的缺口——否则你的持仓/行情状态是残的
- 按主键去重(symbol + 时间戳)——因为补齐的快照会和后续推送重叠
漏掉 ① → 状态残缺且不报错;漏掉 ② → 同一根 K 线被消费两次。
这两种错都不会抛异常,只会让你的结果慢慢变得不可解释——这正是 p1.6 讲的那类问题。
四、实时接入的三条纪律(接 p1.5)
| 纪律 | 理由 | |
|---|---|---|
| ① | 落盘先于计算 | 收到就写原始报文,再做解析与计算 |
| ② | 时间戳用源的,不用本地的 | 本地时钟会漂,而回放要按源的顺序 |
| ③ | 任何丢弃都要计数 | 去重丢了多少、限频丢了多少——不记录就是黑洞 |
第 ③ 条最容易被忽略。系统里每一次"这条数据我不要了"的决定,都必须留下一个计数器;否则数据缺了一段,你连"是不是我自己丢的"都答不出来。
五、一个必须说清的取舍
本节 A 拿到的数据(86 条)比 C(84 条)略多。
这是实测结果,不能藏。但要连着后半句一起读:
A 付出了 21 次 429,而 C 是 0 次。
真实环境里,持续触发限频通常会被降级或封禁——那时 A 的「更多数据」会一次性归零。
用两条数据换 21 次限频,是一笔看起来赚、实际上在借高利贷的交易。
🔧 动手做:C 那套复杂机制,到底什么时候才值?(8 分钟)
默认源下,B(固定间隔)和 C(退避+熔断)看着差不多——429 都是 0、成功率 88.7% vs 91.3%。那 C 多出来的复杂度,是白费吗?
压测它:把第 20 行 RATE_LIMIT = 5 改成 2(模拟一个严得多的源),重跑 python p86_realtime.py。
你会看到(实测):
| 客户端 | 成功率(源宽松) | 成功率(源变严) |
|---|---|---|
| A 朴素轮询 | 67.2% | 崩到 2.8%(237 次 429) |
| B 固定间隔 | 88.7% | 也崩了,5.3% |
| C 退避+熔断 | 91.3% | 39.1%——是 B 的 7 倍 |
想明白:平时源宽松,B 够用,C 的退避+熔断根本看不出价值。但源一变严、故障一变长,只有 C 扛得住。 这类机制不是给顺境写的,是给出事那天写的——而你事先不知道哪天出事。这才是"复杂度要花在刀刃上"的真正含义:不是平时炫技,是灾难时保命。
📌 免责:本课为技术教学,行情源为离线模拟,不构成投资建议。
四件事:
- 三种客户端正常节奏完全相同,唯一区别是失败后怎么办:朴素轮询 128 次请求 / 86 条数据 / 21 次 429;指数退避 92 次 / 84 条 / 0 次 429。C 少用 28% 的请求,拿到几乎一样多的数据
- 三条必须实现的机制:指数退避 · 抖动(最容易省掉,没有它多客户端会集体重试形成雪崩)· 熔断
- 这个实验第一版是错的:假数据源用"自己被调用了多少次"当时间,导致低频客户端也必然被限频,三者测不出差别。排查结论是模型错了,不是客户端错了——与 p7.4、p7.6 同一个模式:实验没测出预期差别时,先怀疑实验设计
- 断线重连后必须先做两件事:拉快照补缺口 · 按主键去重。漏掉前者状态残缺、漏掉后者同一根 K 线被消费两次,而两种错都不会抛异常
三条纪律:落盘先于计算 · 时间戳用源的 · 任何丢弃都要计数(不记录就是黑洞)。
最后一个必须说清的取舍:A 确实多拿到 2 条数据,但付出了 21 次 429——真实环境里持续触发限频通常会被降级或封禁,那时这点优势会一次性归零。
下一节是本章的核心:模拟撮合引擎——七条 A 股规则,以及一条回测里根本不存在的状态。
🔎 来源与核验· 2 条,点开核对
