fix(portfolio): 桥1m超时降级no_anchor——盘后桥不伺候分钟史不再炸整份IS日报 [vps] [no-doc]
CI/CD / test (push) Successful in 36s
CI/CD / nas-deploy (push) Successful in 6s
CI/CD / nas-verify (push) Successful in 10s

infra 09-28 部署后 §287 probe 定案:盘后桥 1m 查询 >30s 抛 TimeoutError,
经 make_price_fn→_build_leg 穿透 run_day,scheduler 兜底只记日志 → 整份
IS 日报缺失(而非 no_anchor 降级)。修=is_price_source_vps.make_price_fn
捕获 TimeoutError→缓存空 dict:调用方按无分钟数据走宁缺毋假降级链
(decision→no_anchor,arrival→派发 tick 兜底),日线锚(本地 dbbardata)
不受牵连;同 (day,code) 只烧一次 30s 超时后 fail-fast。probe() 不经此层
保持 fail-loud。今晚 20:50 首跑 cd1793c 新决策价口径的前提保障。

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
2026-09-28 15:36:45 +08:00
parent 06d1d55ae1
commit 4d61b643a5
2 changed files with 40 additions and 1 deletions
+8 -1
View File
@@ -146,6 +146,10 @@ def make_price_fn():
"""PriceFn:minute≠None 走桥分钟缓存;None 走 dbbardata 日 close。
缓存键=(day, code)——只按 day 键会首票之后全票静默拿 None 锚(终审核修)。
桥 1m 超时(盘后桥不伺候分钟史,RPC 默认 30s)按无分钟数据降级:缓存空
dict → 调用方走 no_anchor/派发 tick 兜底(宁缺毋假),不炸 run_day 整份
日报(09-28 infra probe 定案);同 (day,code) 只烧一次超时,空缓存命中
fail-fast。probe() 不经此层,保持 fail-loud。
"""
cache: dict[tuple[str, str], dict[tuple[str, str], float]] = {}
@@ -156,7 +160,10 @@ def make_price_fn():
return bars and bars["close"]
key = (day, code)
if key not in cache:
cache[key] = _minute_closes([code], day)
try:
cache[key] = _minute_closes([code], day)
except TimeoutError:
cache[key] = {}
return cache[key].get((code, minute))
return fn
@@ -32,3 +32,35 @@ def test_minute_rows_to_map_legacy_nested_and_nan():
"bad": None}}
m = minute_rows_to_map(legacy, "600276.SH")
assert m == {("600276.SH", "10:00"): 44.8}
def test_make_price_fn_minute_timeout_degrades(monkeypatch):
"""桥 1m 超时(盘后桥不伺候分钟史,RPC 默认 30s)→ 按无分钟数据降级返
None(宁缺毋假 no_anchor/派发 tick 兜底),不炸 run_day 整份日报;
同 (day,code) 只烧一次超时(空缓存命中 fail-fast)。09-28 infra probe 定案。"""
import scripts.pipeline.is_price_source_vps as psrc
calls = []
def boom(symbols, day):
calls.append((symbols, day))
raise TimeoutError("rpc timeout")
monkeypatch.setattr(psrc, "_minute_closes", boom)
fn = psrc.make_price_fn()
assert fn("600276.SH", "2026-09-28", "09:31") is None
assert fn("600276.SH", "2026-09-28", "09:32") is None # 缓存命中不再烧
assert calls == [(["600276.SH"], "2026-09-28")]
def test_make_price_fn_daily_path_untouched_by_minute_timeout(monkeypatch):
"""分钟腿降级不影响日线锚(close/prev_close 走本地 dbbardata,日报仍有
close 锚——两条腿互不牵连)。"""
import scripts.pipeline.is_price_source_vps as psrc
def boom(symbols, day):
raise TimeoutError("rpc timeout")
monkeypatch.setattr(psrc, "_minute_closes", boom)
monkeypatch.setattr(psrc, "_daily_bars", lambda sym, day: {"close": 44.9})
fn = psrc.make_price_fn()
assert fn("600276.SH", "2026-09-28", None) == 44.9