mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 16:44:15 +08:00
fix(strategy): 渐进式 run_all 逐策略隔离失败
单个策略执行崩溃 (如自定义策略 filter_history 的数据类型错误) 会杀掉 整批剩余策略: handle 整体 fail, 后台线程结束, 页面剩余卡片永远不亮。 线上实证: 70 策略池跑到第 35 个 (custom_1782999589 pl.DataFrame 构造 schema 混杂) 崩溃, 后 36 个全部没算。 改为单策略 try/except: 失败记入 handle.errors 并移出待算队列, 其余 策略照常算完落缓存; 响应新增 errors 字段 (前端可忽略)。job 级失败 (context 构建崩溃) 仍走整体 500 语义不变。
This commit is contained in:
@@ -177,17 +177,23 @@ def test_run_all_same_key_piggybacks_running_execution(
|
||||
assert engine.executed.count("fast_a") == 1
|
||||
|
||||
|
||||
def test_run_all_background_error_without_results_is_500(
|
||||
def test_run_all_job_level_error_without_results_is_500(
|
||||
monkeypatch, tmp_path, fast_first_return
|
||||
):
|
||||
class _BoomEngine(_FakeEngine):
|
||||
def run_all(self, context, params_map=None, overrides_map=None, *, strategy_ids=None, parallel=True):
|
||||
"""job 级失败 (如 context 构建崩溃) 且无任何结果 → 500, 语义不变。
|
||||
|
||||
策略级失败 (engine.run_all 对单个 sid 抛错) 已改为逐策略隔离, 见
|
||||
test_run_all_isolates_single_strategy_failure。
|
||||
"""
|
||||
|
||||
class _BoomCtxService(_FakeService):
|
||||
def build_strategy_context(self, *args, **kwargs):
|
||||
raise ValueError("缺少列: volume")
|
||||
|
||||
monkeypatch.setattr(screener_api, "ScreenerService", _FakeService)
|
||||
monkeypatch.setattr(screener_api, "ScreenerService", _BoomCtxService)
|
||||
with pytest.raises(HTTPException) as excinfo:
|
||||
screener_api.run_all(
|
||||
_request(tmp_path, _BoomEngine({"bad_a": 0.01})),
|
||||
_request(tmp_path, _FakeEngine({"bad_a": 0.01})),
|
||||
{
|
||||
"as_of": AS_OF,
|
||||
"strategy_ids": ["bad_a"],
|
||||
@@ -291,3 +297,38 @@ def test_run_all_progressive_builds_matrix_once_and_shares_it(
|
||||
assert engine.matrix_builds == 1
|
||||
assert engine.seen_markets and all(m == {"fields": 3} for m in engine.seen_markets)
|
||||
assert len(engine.seen_markets) == 3
|
||||
|
||||
|
||||
def test_run_all_isolates_single_strategy_failure(
|
||||
monkeypatch, tmp_path, fast_first_return
|
||||
):
|
||||
"""单个策略执行崩溃只跳过它自己: 其余策略照常算完落缓存, 整批不失败。"""
|
||||
|
||||
class _FlakyEngine(_FakeEngine):
|
||||
def run_all(self, context, params_map=None, overrides_map=None, *, strategy_ids=None, parallel=True):
|
||||
for sid in strategy_ids or []:
|
||||
if sid == "broken":
|
||||
raise ValueError("boom: schema mismatch")
|
||||
return super().run_all(
|
||||
context, params_map=params_map, overrides_map=overrides_map,
|
||||
strategy_ids=strategy_ids, parallel=parallel,
|
||||
)
|
||||
|
||||
engine = _FlakyEngine({"ok_a": 0.01, "broken": 0.01, "ok_b": 0.5})
|
||||
monkeypatch.setattr(screener_api, "ScreenerService", _FakeService)
|
||||
|
||||
resp = screener_api.run_all(
|
||||
_request(tmp_path, engine),
|
||||
{
|
||||
"as_of": AS_OF,
|
||||
"strategy_ids": ["ok_a", "broken", "ok_b"],
|
||||
"asset_type": "stock",
|
||||
"timeframe": "1d",
|
||||
"summary_only": True,
|
||||
},
|
||||
)
|
||||
# 后台继续: 好策略都落缓存; broken 不在结果也不在 pending, 而是进 errors
|
||||
results = _wait_cache_results(tmp_path, ["ok_a", "ok_b"])
|
||||
assert set(results) == {"ok_a", "ok_b"}
|
||||
assert "broken" not in results
|
||||
assert "boom: schema mismatch" in (resp["errors"] or {}).get("broken", "")
|
||||
|
||||
Reference in New Issue
Block a user