Files
tick-stock-panel/backend/tests/test_daily_batch_asset.py
T
shy3130 d43a3021da feat(kline): 批量上限可配置(前后端同源) + 响应懒净化与 Polars 直出
- 日K/分时批量单请求上限改为系统设置可配 (默认 400), 后端守卫与前端分块
  读同一偏好值, 不再发出必然被拒的请求; 两档独立 (分时成本约为日K 20 倍)
- _gzip_payload 懒净化: 无 NaN/inf 常态跳过 _json_safe 整树深拷贝,
  峰值 -65% (40→14MB/块), CPU -25%
- minute-batch/daily-batch 全程持有 Polars 帧直出 JSON (write_json 行式),
  不再构建 Python dict: 96k 根序列化 1222ms→26ms, 峰值 69→18MB;
  字节级等价由 tests/test_kline_batch_frames_json.py 锁定 (含 inf/NaN/None)
- 白盒测试改为解析 Response body (HTTP 真实契约)
- 随 kline.py 同文件: minute-batch 门控在全量分钟服务健康时放行本地读,
  补拉限速改走 resolve_limit (×0.8, 与盘后同步同 rpm 桶)

实测: 300 只分时(7.2万根)整请求 191ms; 20 并发尖峰 +728MB→+60MB

Signed-off-by: shy3130 <shy3130@qq.com>
2026-10-25 17:31:01 +08:00

82 lines
3.2 KiB
Python

"""daily-batch 混合资产分组测试。"""
import json
import datetime as _dt
import polars as pl
import pytest
from app.tickflow.repository import DataStore, KlineRepository
@pytest.fixture()
def repo(tmp_path):
return KlineRepository(DataStore(tmp_path))
def test_daily_batch_groups_index_symbols(repo, monkeypatch):
from app.api import kline as kline_api
calls = {"stock_batch": [], "index": []}
def fake_stock_batch(symbols, start, end, columns=None):
calls["stock_batch"].append(list(symbols))
return pl.DataFrame()
def fake_index_daily(symbol, start, end, columns=None):
calls["index"].append(symbol)
return pl.DataFrame({
"symbol": [symbol], "date": [_dt.date(2026, 7, 24)],
"open": [1.0], "high": [1.0], "low": [1.0], "close": [1.0], "volume": [1],
})
monkeypatch.setattr(repo, "get_daily_batch", fake_stock_batch)
monkeypatch.setattr(repo, "get_index_daily", fake_index_daily)
monkeypatch.setattr(repo, "get_index_symbol_set", lambda: {"000001.SH"})
monkeypatch.setattr(repo, "get_etf_symbol_set", lambda: set())
state = type("S", (), {"repo": repo})()
req = type("R", (), {"app": type("A", (), {"state": state})()})()
out = json.loads(kline_api.get_daily_batch(req, {"symbols": ["600000.SH", "000001.SH"], "days": 12}).body)
# 本 fixture 每只只回 1 行 (< days), 停牌回补会对不足额标的追加更宽窗口的读取;
# 分流断言只看首次调用 (股票走批量、指数走逐只)。
assert calls["stock_batch"][0] == ["600000.SH"]
assert calls["index"][0] == "000001.SH"
assert "000001.SH" in out["data"]
# 钉死的北京日期, 不会碰巧等于跑测试那天的 date.today()
_BJ = _dt.date(2026, 3, 2)
def test_daily_batch_window_ends_on_beijing_today(repo, monkeypatch):
"""自选迷你K 的默认截止日必须是北京今天。
QuoteService 盘中 flush 的当日分区日期是北京交易日。未修复代码用
date.today() 做窗口右端: 美西主机整个 A 股交易时段、UTC 主机北京
00:00-08:00, 本地日历日比北京早一天, 今日实时 K 被排除, 迷你蜡烛停在昨天。
raising=False: 未修复代码没有调用 cn_today, 钉了也不会被用到。
"""
from app.api import kline as kline_api
captured: list[tuple[_dt.date, _dt.date]] = []
def fake_stock_batch(symbols, start, end, columns=None):
captured.append((start, end))
return pl.DataFrame({
"symbol": ["600000.SH"], "date": [_BJ],
"open": [10.0], "high": [10.6], "low": [9.9], "close": [10.6], "volume": [1.0],
})
monkeypatch.setattr(kline_api, "cn_today", lambda: _BJ, raising=False)
monkeypatch.setattr(repo, "get_daily_batch", fake_stock_batch)
monkeypatch.setattr(repo, "resolve_asset_type", lambda s: "stock")
state = type("S", (), {"repo": repo})()
req = type("R", (), {"app": type("A", (), {"state": state})()})()
kline_api.get_daily_batch(req, {"symbols": ["600000.SH"], "days": 12})
assert captured, "应查询日K"
_start, end = captured[0]
assert end == _BJ, f"窗口右端必须是北京日期 {_BJ}, 实际 {end} (服务器本地 {_dt.date.today()})"