@@ -278,32 +278,16 @@ def get_cached(
|
||||
request: Request,
|
||||
ext_columns: Optional[str] = Query(None, description="逗号分隔: config_id.field_name"),
|
||||
):
|
||||
"""读取策略结果缓存, 并叠加监控引擎本轮实时算出的结果。
|
||||
"""读取策略结果缓存。
|
||||
|
||||
- 盘后缓存 (strategy_cache.json): 非监控策略 / 页面秒加载用, run_all 写入。
|
||||
- 监控引擎内存结果 (latest_strategy_results): 实时行情每轮对「加入监控的策略」算出,
|
||||
不落盘 (避免与 read_cache 的 mtime 校验冲突), 在此直接叠加覆盖盘后结果。
|
||||
被监控的策略拿到新鲜数据, 非监控策略仍用盘后缓存。
|
||||
- 盘后缓存 (strategy_cache.json): run_all 写入, 页面秒加载用。
|
||||
"""
|
||||
data_dir = request.app.state.repo.store.data_dir
|
||||
cached = strategy_cache.read_cache(data_dir)
|
||||
if cached is None:
|
||||
cached = {"as_of": None, "results": {}, "updated_at": None}
|
||||
|
||||
# 叠加监控引擎内存里的实时结果 (若有), 用新鲜数据覆盖同策略的盘后结果
|
||||
monitor_engine = getattr(request.app.state, "monitor_engine", None)
|
||||
if monitor_engine is not None:
|
||||
realtime_results = monitor_engine.latest_strategy_results()
|
||||
if realtime_results:
|
||||
results = dict(cached.get("results") or {})
|
||||
results.update(realtime_results)
|
||||
cached = dict(cached)
|
||||
cached["results"] = results
|
||||
# 有实时数据时, 以最新时间戳为准
|
||||
import time as _time
|
||||
cached["updated_at"] = int(_time.time() * 1000)
|
||||
|
||||
# 无任何数据 (盘后缓存空 + 无实时结果) → 返回空标记, 前端据此提示
|
||||
# 无任何数据 → 返回空标记, 前端据此提示
|
||||
if not cached.get("results") and cached.get("as_of") is None:
|
||||
return {"as_of": None, "results": {}, "updated_at": None}
|
||||
|
||||
@@ -503,34 +487,8 @@ def limit_ladder(
|
||||
count_up_raw = int(df.filter(pl.col("signal_limit_up").fill_null(False)).height) if "signal_limit_up" in df.columns else 0
|
||||
count_down_raw = int(df.filter(pl.col("signal_limit_down").fill_null(False)).height) if "signal_limit_down" in df.columns else 0
|
||||
|
||||
# 双方向 sealed 修正: 减去各自的假涨停(假涨停已归炸板, 不计入涨停数)
|
||||
depth_svc_global = getattr(request.app.state, "depth_service", None)
|
||||
fake_up = 0
|
||||
fake_down = 0
|
||||
sealed_up_ready = False
|
||||
sealed_down_ready = False
|
||||
if depth_svc_global:
|
||||
up_map = depth_svc_global.get_sealed_map(as_of, is_down=False)
|
||||
down_map = depth_svc_global.get_sealed_map(as_of, is_down=True)
|
||||
sealed_up_ready = bool(up_map) and depth_svc_global.is_sealed_ready(as_of)
|
||||
sealed_down_ready = bool(down_map) and depth_svc_global.is_sealed_ready(as_of)
|
||||
if up_map:
|
||||
fake_up = sum(1 for v in up_map.values() if v.get("sealed") is False)
|
||||
if down_map:
|
||||
fake_down = sum(1 for v in down_map.values() if v.get("sealed") is False)
|
||||
count_up = count_up_raw - fake_up if sealed_up_ready else count_up_raw
|
||||
count_down = count_down_raw - fake_down if sealed_down_ready else count_down_raw
|
||||
|
||||
# 双方向 sealed 明细(供前端弹窗同时显示涨跌停)
|
||||
def _count_sealed(m: dict, ready: bool):
|
||||
if not m or not ready:
|
||||
return {"real": 0, "fake": 0, "pending": 0}
|
||||
real = sum(1 for v in m.values() if v.get("sealed") is True)
|
||||
fake = sum(1 for v in m.values() if v.get("sealed") is False)
|
||||
pending = sum(1 for v in m.values() if v.get("sealed") is None)
|
||||
return {"real": real, "fake": fake, "pending": pending}
|
||||
sealed_counts_up = _count_sealed(up_map, sealed_up_ready)
|
||||
sealed_counts_down = _count_sealed(down_map, sealed_down_ready)
|
||||
count_up = count_up_raw
|
||||
count_down = count_down_raw
|
||||
|
||||
# 加载前一日数据获取 prev consecutive_limit_ups/downs
|
||||
prev_consec: pl.DataFrame = pl.DataFrame()
|
||||
@@ -569,69 +527,11 @@ def limit_ladder(
|
||||
|
||||
df = df.filter(pl.col("status").is_not_null() & (pl.col("boards") > 0))
|
||||
|
||||
# ── 五档 sealed 叠加(独立旁路, 不改 signal_limit_up) ──
|
||||
# 假涨停(收盘价=涨停价但卖一有量)从 limit 降级为 broken(归炸板视图)
|
||||
# 真涨停保留 + 附封单量; sealed=null(待确认/降级)保持原状
|
||||
depth_svc = getattr(request.app.state, "depth_service", None)
|
||||
sealed_ready = False
|
||||
sealed_age: float | None = None
|
||||
if depth_svc:
|
||||
sealed_map = depth_svc.get_sealed_map(as_of, is_down=is_down)
|
||||
sealed_ready = bool(sealed_map) and depth_svc.is_sealed_ready(as_of)
|
||||
sealed_age = depth_svc.get_sealed_age(as_of) if sealed_ready else None
|
||||
|
||||
if sealed_map:
|
||||
# 构建 sealed 列(symbol → sealed bool, vol)
|
||||
sym_sealed = {s: v.get("sealed") for s, v in sealed_map.items()}
|
||||
sym_vol = {s: v.get("vol") for s, v in sealed_map.items()}
|
||||
|
||||
# JOIN sealed: 对每只 status=main 的票, 看 sealed 值
|
||||
sealed_rows = pl.DataFrame({
|
||||
"symbol": list(sym_sealed.keys()),
|
||||
"_sealed": list(sym_sealed.values()),
|
||||
"_sealed_vol": list(sym_vol.values()),
|
||||
}) if sym_sealed else pl.DataFrame()
|
||||
|
||||
if not sealed_rows.is_empty():
|
||||
df = df.join(sealed_rows, on="symbol", how="left")
|
||||
# 假涨停(main 状态但 sealed=False)→ 降级为 broken
|
||||
df = df.with_columns(
|
||||
pl.when(
|
||||
(pl.col("status") == status_main)
|
||||
& pl.col("_sealed").is_not_null()
|
||||
& (pl.col("_sealed") == False) # noqa: E712
|
||||
).then(pl.lit(status_broken))
|
||||
.otherwise(pl.col("status")).alias("status"),
|
||||
# sealed_status: real/fake/pending/null
|
||||
pl.when(
|
||||
(pl.col("status") == status_main)
|
||||
& (pl.col("_sealed") == True) # noqa: E712
|
||||
).then(pl.lit("real"))
|
||||
.when(
|
||||
(pl.col("_sealed") == False) # noqa: E712
|
||||
).then(pl.lit("fake"))
|
||||
.when(
|
||||
(pl.col("status") == status_main)
|
||||
& pl.col("_sealed").is_null()
|
||||
).then(pl.lit("pending"))
|
||||
.otherwise(None).alias("sealed_status"),
|
||||
pl.col("_sealed_vol").alias("sealed_vol"),
|
||||
).drop(["_sealed", "_sealed_vol"])
|
||||
else:
|
||||
df = df.with_columns(
|
||||
pl.lit(None).alias("sealed_status"),
|
||||
pl.lit(None).alias("sealed_vol"),
|
||||
)
|
||||
else:
|
||||
df = df.with_columns(
|
||||
pl.lit(None).alias("sealed_status"),
|
||||
pl.lit(None).alias("sealed_vol"),
|
||||
)
|
||||
else:
|
||||
df = df.with_columns(
|
||||
pl.lit(None).alias("sealed_status"),
|
||||
pl.lit(None).alias("sealed_vol"),
|
||||
)
|
||||
# Add null columns for sealed_status and sealed_vol (no longer used)
|
||||
df = df.with_columns(
|
||||
pl.lit(None).alias("sealed_status"),
|
||||
pl.lit(None).alias("sealed_vol"),
|
||||
)
|
||||
|
||||
# 动态 JOIN 扩展数据
|
||||
ext_specs = _parse_ext_columns(ext_columns) if ext_columns else []
|
||||
@@ -700,15 +600,6 @@ def limit_ladder(
|
||||
"tiers": tier_list,
|
||||
"counts": {"up": count_up, "down": count_down},
|
||||
"counts_raw": {"up": count_up_raw, "down": count_down_raw},
|
||||
"sealed_ready": sealed_ready,
|
||||
"sealed_age": round(sealed_age, 0) if sealed_age is not None else None,
|
||||
"sealed_counts": {
|
||||
"real": sum(1 for t in tier_list for s in t.get("stocks", []) if s.get("sealed_status") == "real"),
|
||||
"fake": sum(1 for t in tier_list for s in t.get("stocks", []) if s.get("sealed_status") == "fake"),
|
||||
"pending": sum(1 for t in tier_list for s in t.get("stocks", []) if s.get("sealed_status") == "pending"),
|
||||
},
|
||||
"sealed_counts_up": sealed_counts_up,
|
||||
"sealed_counts_down": sealed_counts_down,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user