feat(webui)!: 行情终端重大升级——市场看板/自选/个股与板块弹窗 + SSE 实时推送

展示层对标专业看盘终端(tick-stock-panel 模式),数据全部来自通达信协议直连:

- 市场看板:五大指数实时条(迷你分时)、涨跌统计、四维情绪雷达、
  全市场涨跌分布直方图(约 5500 只 22 桶、鼠标跟随浮窗)、涨停雷达、
  行业/概念热冷榜(可下钻)、涨幅/跌幅/成交额/换手四联排行榜、异动雷达
- 自选行情:6 位代码即加(symbol-info 自动取名)、SSE 实时全表刷新、
  行内迷你分时;SQLite 持久化(watchlist.db,幂等加删)
- 个股弹窗:五档盘口 + 1/3/5 日分时 + 日K(MA/BOLL/EMA + MACD/KDJ/RSI
  前端本地计算)+ 一键加自选 + 一键全策略寻优
- 板块弹窗:板块分时/日K + 成分股涨跌榜直达个股
- SSE 架构:QuoteStreamer 单循环轮询 fan-out(独立队列 + 背压丢旧、
  无订阅休眠、盘中 8s/盘外 60s 降频);前端 pinia 单连接指数退避重连
- fix(codec): SH 000 系大盘指数与 881/885 板块指数报价按 2 位小数解析
  (曾缩小 10 倍:科创50 1647.53→164.753);880 统计指数保持 3 位
- fix: 前端批量五档路径 /security/quotes→/quotes、SSE 五档白名单
  bid1_vol→bid_vol1、MAC 排行 close 列归一化、日K v-if 撑满、
  寻优查看跳转 /→/backtest、FastAPI 0.141 _IncludedRouter 测试适配

新增 27 个单测(streamer/自选/小数位语义),全套 1078 passed
This commit is contained in:
GitHub
2026-09-01 17:45:07 +08:00
parent 12d308e1a4
commit 33d44ba5d6
31 changed files with 4600 additions and 59 deletions
+18 -5
View File
@@ -2,8 +2,8 @@
// 参数寻优主页面:左配置(选标的 + 策略 + 寻优参数)/ 右报告(排名表 + 热力图)。
// 取行情已整合进「开始寻优」。另有「一键寻优所有策略」:用各策略预设网格逐策略寻优再全局排名。
import { computed, onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { computed, nextTick, onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import GradeBadge from '../components/GradeBadge.vue'
import OptimizeHeatmap from '../components/OptimizeHeatmap.vue'
@@ -18,6 +18,7 @@ import { useBacktestStore } from '../stores/backtest'
const store = useBacktestStore()
const router = useRouter()
const route = useRoute()
// SymbolPicker 实例引用,用于触发取行情
const symbolPicker = ref<InstanceType<typeof SymbolPicker> | null>(null)
@@ -86,10 +87,22 @@ const selectedStrategy = computed(
() => store.strategies.find((s) => s.name === strategy.value) ?? null,
)
onMounted(() => {
onMounted(async () => {
store.loadStrategies().catch((e) => {
store.error = `加载策略列表失败:${e instanceof Error ? e.message : e}`
})
// 个股弹窗「一键寻优」入口:/optimize?code=601088&autoAll=1
// 填入代码后自动触发「一键寻优所有策略」(等 nextTick 让 SymbolPicker
// 的 v-model 同步,onRunAll 里的 loadBars 才会取到正确标的)。
const qCode = String(route.query.code ?? '')
if (route.query.autoAll === '1' && /^\d{6}$/.test(qCode)) {
code.value = qCode
await nextTick()
if (!store.optimizeRunning && !store.optimizeAllRunning) {
onRunAll()
}
}
})
// 网格点数(前端预校验,提示用户)
@@ -172,12 +185,12 @@ function buildBacktestQuery(strategyName: string, params: Record<string, number
// 点击排名表「查看」→ 跳转单标的页用该参数回测
function onViewParams(params: Record<string, number | string>) {
// 通过 query 传递参数,单标的页接收后自动填充
router.push({ path: '/', query: buildBacktestQuery(strategy.value, params) })
router.push({ path: '/backtest', query: buildBacktestQuery(strategy.value, params) })
}
// 一键寻优结果点击「查看」→ 跳转单标的页用该策略 + 参数回测
function onViewAll(strategyName: string, params: Record<string, number | string>) {
router.push({ path: '/', query: buildBacktestQuery(strategyName, params) })
router.push({ path: '/backtest', query: buildBacktestQuery(strategyName, params) })
}
function pct(v: number | null | undefined): string {