mirror of
https://github.com/shy3130/tick-stock-panel.git
synced 2026-11-02 10:26:13 +00:00
- 日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>
82 lines
3.2 KiB
Python
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()})"
|