feat: 自选列表新增「近3日/近1周/近2周」涨跌幅三列(issue #7)

交易日偏移口径:T = 上证指数日线(交易日历)中 <= 今天的最后一天,
D_n = T 往前 n 个交易日,锚点 = 个股日线(/bars 同款 QFQ)中
date <= D_n 的最后一根 bar。后端只回锚点收盘价,涨跌幅由前端用
SSE 实时价现算,盘中三列随报价免费跳动、无需轮询本接口。

- 新增 GET /watchlist/returns + 纯计算模块 web/returns.py(零 IO,单测覆盖
  停牌回退/次新 null/除权日 QFQ/非交易日回退等口径)
- 个股日线与交易日历均进程内缓存(当日不变、次日失效),重复刷新零行情请求;
  日历缺今天(serve 盘前启动)时按 60s 间隔重取,避免 T 整体前移
- 取数并发 ≤ 4(TDX 防封红线);单只失败只落 error,不影响整表
- 前端 +3 列(着色复用 dirClass/fmtPctSigned),e2e 同步断言

与 issue 定稿的两处偏差(/simplify 收敛,"减少不必要的改动"):
- 删除 windows 查询参数:列名与窗口一一对应(issue 亦将"用户自定义窗口"
  列为 out of scope),固定 3/5/10
- 个股缓存由磁盘 JSON 改为进程内 dict:可观测行为不变(当日不重复请求),
  但 serve 重启后当天首次请求会重取一次

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
awayings
2026-09-11 11:39:10 +08:00
co-authored by Claude Code
parent 57b1a7b32b
commit fb35677802
9 changed files with 1406 additions and 7 deletions
+30
View File
@@ -1,4 +1,6 @@
// 自选页 E2E:加入自选(行情校验 + 名称补全走 mock)→ 表格出现 → 删除 → 消失。
// 另覆盖 issue #7 的「近3日 / 近1周 / 近2周」三列(交易日偏移口径,锚点走后端
// /watchlist/returns,涨跌幅由前端用实时价现算)。
//
// 每轮 E2E 用独立的临时 EASY_TDX_CONFIG_DIR,自选从空开始,断言可写死。
@@ -9,6 +11,8 @@ test('自选页增删自选', async ({ page }) => {
// 初始为空(临时配置目录)
await expect(page.locator('.empty-row')).toBeVisible()
// 空行 colspan 与表头列数一致(新增 3 列后 = 15)
await expect(page.locator('.empty-row td')).toHaveAttribute('colspan', '15')
// 加入 600519(市场自动识别 SH;名称走 mock /mac/symbol-info → 贵州茅台)
await page.fill('.code-input', '600519')
@@ -22,3 +26,29 @@ test('自选页增删自选', async ({ page }) => {
await expect(page.locator('.data-row')).toHaveCount(0)
await expect(page.locator('.empty-row')).toBeVisible()
})
test('自选页近3日/近1周/近2周涨跌幅三列', async ({ page }) => {
await page.goto('/watchlist')
await page.fill('.code-input', '600519')
await page.getByRole('button', { name: '加入自选' }).click()
await expect(page.locator('.data-row')).toHaveCount(1, { timeout: 30_000 })
// 表头:现价/涨跌幅之后依次是 近3日、近1周、近2周(共 15 列 = 12 + 3
const headers = page.locator('.qtable thead th')
await expect(headers).toHaveCount(15)
await expect(headers.nth(2)).toHaveText('涨跌幅')
await expect(headers.nth(3)).toHaveText('近3日')
await expect(headers.nth(4)).toHaveText('近1周')
await expect(headers.nth(5)).toHaveText('近2周')
// 数据格:与表头列数一致,三列都是带符号百分比(合成行情锚点 → 一定会算出数)
const cells = page.locator('.data-row td')
await expect(cells).toHaveCount(15)
for (const i of [3, 4, 5]) {
await expect(cells.nth(i)).toHaveText(/^[+-]?\d+\.\d+%$/)
}
await page.locator('.data-row .del').first().click()
await expect(page.locator('.data-row')).toHaveCount(0)
})