fix(index): 后端分钟K隔离加固与规则资产类型纠正

- kline.sync_minute_single 对指数 symbol 显式 400 (防污染 kline_minute)
- kline.sync_minute 全市场 universe 剔除指数 symbol
- kline.get_minute 响应新增 asset_type 字段 (3 处 return)
- monitor_rules._reconcile_index_asset_type 纠正误存为 stock 的指数规则
  (应用于 save_rule/list_rules/_sync_engine, 混合池不动)
- 文案资产中立化: "个股信号→信号" "指定股票→指定标的" (options label + 校验报错)
- 测试: reconcile 5 断言 + sync_minute_single 拒指数 400 + 分时报错断言同步
This commit is contained in:
intfoo
2026-07-26 19:59:10 +08:00
parent 4d069cc10f
commit 3e6f9bcfb7
6 changed files with 104 additions and 6 deletions
+33 -4
View File
@@ -20,11 +20,35 @@ def _data_dir(request: Request) -> Path:
return request.app.state.repo.store.data_dir
def _reconcile_index_asset_type(rule: dict, repo) -> dict:
"""纠正误存为 stock 的指数规则 (asset_type → index)。
个股弹窗加监控 / 点位提醒等入口未传 asset_type, 指数 symbol 的规则被存成
stock, 导致监控中心显示「个股」、引擎在股票轮评估 (指数 symbol 永不命中)。
仅当规则全部 symbols 都 resolve 为指数时纠正 (股票+指数混合池不动)。
"""
if rule.get("asset_type", "stock") != "stock" or rule.get("scope") != "symbols":
return rule
symbols = [s for s in rule.get("symbols", []) if s]
if not symbols:
return rule
try:
if all(repo.resolve_asset_type(s) == "index" for s in symbols):
rule["asset_type"] = "index"
except Exception: # noqa: BLE001
pass
return rule
def _sync_engine(request: Request) -> None:
"""保存/删除后,把最新规则集 reload 到引擎内存态。"""
engine = getattr(request.app.state, "monitor_engine", None)
if engine is not None:
rules = monitor_rules.load_all(_data_dir(request))
repo = request.app.state.repo
rules = [
_reconcile_index_asset_type(r, repo)
for r in monitor_rules.load_all(_data_dir(request))
]
engine.set_rules(rules)
@@ -100,13 +124,13 @@ def get_options(request: Request):
"custom_signals": custom_sigs,
"operators": [">", ">=", "<", "<=", "==", "!="],
"types": [
{"key": "signal", "label": "个股信号"},
{"key": "signal", "label": "信号"},
{"key": "price", "label": "价格/涨跌"},
{"key": "market", "label": "市场异动"},
{"key": "strategy", "label": "策略监控"},
],
"scopes": [
{"key": "symbols", "label": "指定股票"},
{"key": "symbols", "label": "指定标的"},
{"key": "all", "label": "全市场"},
{"key": "sector", "label": "板块"},
],
@@ -133,7 +157,11 @@ def get_options(request: Request):
# ── 列表 ───────────────────────────────────────────────
@router.get("")
def list_rules(request: Request):
rules = monitor_rules.load_all(_data_dir(request))
repo = request.app.state.repo
rules = [
_reconcile_index_asset_type(r, repo)
for r in monitor_rules.load_all(_data_dir(request))
]
from app.services.kline_sync import intraday_monitor_support
support = intraday_monitor_support(getattr(request.app.state, "capabilities", None))
@@ -166,6 +194,7 @@ def list_rules(request: Request):
@router.post("")
def save_rule(req: RuleModel, request: Request):
rule = monitor_rules.normalize(req.model_dump())
rule = _reconcile_index_asset_type(rule, request.app.state.repo)
# 连板梯队封单监控 (type=ladder) 依赖五档盘口数据, 需 Pro+ (DEPTH5_BATCH 能力)。
# 无能力时拒绝创建, 避免规则存了却永远无法触发。
if rule.get("type") == "ladder":