ai_trade_assistance/docs/superpowers/specs/2026-07-22-baidu-quote-ws-design.md
dongzp 1c1f370d59 fix(ws): proxy Baidu WebSocket via Vite to bypass Origin 403
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 10:01:23 +08:00

147 lines
6.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 百度财经 WebSocket 行情实时刷新 — 设计
**日期:** 2026-07-22
**状态:** 已口头确认,待书面复核
---
## 1. 背景与目标
当前行情通过 HTTP `getStockQuotation` 在页面打开时拉取一次全量(分时、盘口、成交明细)。`quotation.ts` 已预留「实时更新后续对接 WebSocket」。
本阶段目标:接入百度财经 WebSocket,在 HTTP 全量底之上做增量刷新,使报价、盘口、分时图、分时成交与大单分析实时更新。
---
## 2. 已确认决策
| 项 | 选择 |
|---|---|
| 初始全量 | 现有 HTTP `getStockQuotation`,成功后再连 WS |
| 数据映射 | `tick` → 成交明细(TradeTape)+ 大单分析;`snapshot` → 报价 / 盘口 / 分时点 |
| 架构 | 独立 `BaiduQuoteWs` 客户端 + mapper;状态仍落 `quota` store |
| 合约 | 仍读 `contractConfig` 单合约,不做多合约订阅 |
| WS 库 | 原生 `WebSocket`,不引入第三方 |
| 连接 UI | 本期不做连接指示灯 / 手动重连按钮 |
---
## 3. 架构与数据流
```
页面挂载
→ quota.fetchQuote() // HTTP 全量
→ BaiduQuoteWs.connect() // ws(s)://{host}/finance-ws/ → Vite/Nginx 代理到百度
→ subscribe tick + snapshot
→ 每 6s ping
→ 每 60s patch snapshot
→ onMessage
→ product=tick → 合并 trades(最新在前)
→ product=snapshot → 更新报价 / 盘口 / upsert intraday
页面卸载 / 主动 refresh
→ BaiduQuoteWs.disconnect()(refresh 成功后再 connect)
```
### 模块划分
| 文件 | 职责 |
|------|------|
| `src/api/baidu/ws.ts` | 连接、心跳、订阅、重连、回调 |
| `src/api/baidu/wsTypes.ts` | tick / snapshot 消息 TypeScript 类型 |
| `src/api/baidu/mapWsQuote.ts` | 增量消息映射并合并进 `QuoteData` |
| `src/stores/quota.ts` | HTTP 成功后启 WS;应用增量;卸载断开 |
| `src/composables/useQuote.ts` | `onMounted` 拉行情;`onUnmounted` 断开 WS |
大单分析已基于 `quote.trades` 计算,只需更新 `trades`,不改组件。
---
## 4. 协议细节
**端点:** 浏览器连同域 `/finance-ws/`;由 Vite(开发)或 Nginx(生产)代理到 `wss://finance-ws.pae.baidu.com/`,并改写 `Origin`/`Referer` 为 `https://gushitong.baidu.com`(否则 localhost Origin 会 403)。
**订阅(open 后立即发送两条):**
```json
{"method":"subscribe","source":"pc-web","product":"tick","items":[{"code":"FG609","name":"玻璃2609","market":"ab","financeType":"futures"}]}
{"method":"subscribe","source":"pc-web","product":"snapshot","items":[{"code":"FG609","name":"玻璃2609","market":"ab","financeType":"futures"}]}
```
`code` / `name` 来自 `contractConfig`;`market` 固定 `ab`;`financeType` 固定 `futures`。
**保活:**
- 每 6s:`{"method":"ping","source":"pc-web"}`
- 每 60s:snapshot 的 `patch`(items 同订阅)
**重连:** 断线后指数退避(1s → 2s → 4s … 上限 30s);重连成功后重新 subscribe;不自动重拉 HTTP。
**disconnect:** 清 ping/patch 定时器、关闭 socket、停止重连调度。
---
## 5. 合并规则
### 5.1 tick → trades
- 解析 `data.detailinfos` → `TradeTick[]`(字段映射与现有 HTTP `parseTrades` 一致:`formatTime` / `price` / `volume` / `bsFlag`;最新在前)
- 前置合并到 `quote.trades`
- 去重键:`time + price + volume + side`
- 上限:最近 **200** 条
### 5.2 snapshot → 报价 / 盘口 / 分时
- `cur`:更新 `last` / `change` / `changePercent` / `avg`;状态可从 `cur.status` 或 `update` 取
- `pankouinfos`(数组,按 `ename`):更新 open / high / low / volume / amount / openInterest / amplitude / settlement / prevSettlement / outerVol / innerVol 等
- `askinfos` / `buyinfos`:刷新五档;过滤无效价;重算 `buyRatio` / `sellRatio`
- `update`:刷新 `updatedAt` / `status`
- `point`:按分钟时间(从 `point.time` 取 `HH:mm`)upsert 到 `intraday`
- 覆盖 / 写入:`price` ← `point.price`,`avg` ← `point.avgPrice`
- `volume`:WS 只给 `totalVolume`(全日累计)。分钟成交量 = `max(0, totalVolume - 此前各分钟 volume 之和)`;同分钟再次推送时用同一公式重算并覆盖
- 新分钟:append;同分钟:覆盖该点
### 5.3 错误处理
- `resultCode !== "0"`:打日志,忽略本条
- JSON / 字段解析失败:打日志,不中断连接
- 组件层无感;不暴露 WS 连接状态到 UI
---
## 6. 生命周期
1. `useQuote` `onMounted` → `store.fetchQuote()`
2. `fetchQuote` 成功 → `store.connectWs()`(若已连接则先 disconnect 再 connect)
3. `useQuote` `onUnmounted` → `store.disconnectWs()`
4. `refresh()` / `fetchForAnalysis()`:仍走 HTTP 全量;成功后重连 WS,用最新全量覆盖后再接增量
5. K 线逻辑不变;WS 不修改 `candles`
---
## 7. 明确不做(本期)
- 连接状态 UI / 手动重连按钮
- 多合约同时订阅
- 修改 TradeTape / LargeOrderAnalysis / ChartPanel(仅消费响应式 `quote`)
- 引入第三方 WS 库
- WS 失败时回退 HTTP 轮询
---
## 8. 验证要点
- HTTP 成功后会发送 tick + snapshot 两条 subscribe
- 约每 6s 有 ping,约每 60s 有 snapshot patch
- tick 合并 trades:去重生效、长度 ≤ 200;TradeTape / 大单分析随之刷新
- snapshot.point:同分钟覆盖、新分钟追加;分时图更新
- snapshot 盘口 / 现价字段正确刷新
- 页面卸载后无残留 timer / 未关闭的 socket
- 手动 refresh 后 WS 会断开并重新订阅
---
## 9. 风险与注意
- WS 与 HTTP 的 snapshot 字段结构不完全一致(HTTP 用 `origin_pankou`,WS 用 `pankouinfos` 数组);mapper 需独立,勿强行复用 HTTP mapper 全文
- 去重键不含服务端唯一 id(协议未提供);极端情况下同秒同价同量同向两笔可能被误去重,可接受
- 跨域 / 鉴权:浏览器不能直连(Origin=localhost → 403)。开发经 Vite 代理 `/finance-ws`,并把 Origin/Referer 改写为 `https://gushitong.baidu.com`;生产需同等反向代理。