54 lines
2.2 KiB
Python
54 lines
2.2 KiB
Python
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
from backend.data.numbers import finite_number as _number
|
||
from backend.features.screener.catalog import REGIMES
|
||
from backend.features.screener.indicators import _regime_reason
|
||
from backend.features.sentiment.engine import build_sentiment_history, latest_contiguous_history
|
||
from database import ReviewDatabase
|
||
|
||
|
||
class RegimeDetector:
|
||
def __init__(self, database: ReviewDatabase) -> None:
|
||
self.database = database
|
||
|
||
def detect_regime(self, trade_date: str) -> dict[str, Any]:
|
||
series = latest_contiguous_history(
|
||
build_sentiment_history(self.database.list_snapshot_payloads(trade_date, 260))
|
||
)
|
||
if not series:
|
||
return {
|
||
"id": "repair", "label": REGIMES["repair"], "confidence": 25,
|
||
"reason": "复盘快照不足,暂按中性修复处理。", "evidence": [], "history": [],
|
||
}
|
||
current = series[-1]
|
||
previous = series[-2] if len(series) > 1 else current
|
||
score = _number(current.get("score"))
|
||
previous_score = _number(previous.get("score"))
|
||
delta = score - previous_score
|
||
seal_rate = _number(current.get("seal_rate"))
|
||
limit_up = _number(current.get("limit_up_count"))
|
||
broken = _number(current.get("broken_count"))
|
||
regime = next(
|
||
(key for key, label in REGIMES.items() if label == current.get("phase")),
|
||
"divergence",
|
||
)
|
||
confidence = min(92, 45 + len(series[-8:]) * 5 + min(abs(delta), 12))
|
||
evidence = [
|
||
f"情绪温度 {score:.0f},较前一交易日 {delta:+.0f},{current.get('direction') or '持平'}",
|
||
f"封板率 {seal_rate:.1f}%",
|
||
f"涨停 {limit_up:.0f} 家,炸板 {broken:.0f} 家",
|
||
]
|
||
return {
|
||
"id": regime,
|
||
"label": REGIMES[regime],
|
||
"confidence": round(confidence),
|
||
"reason": _regime_reason(regime),
|
||
"evidence": evidence,
|
||
"history": [
|
||
{"trade_date": item["trade_date"], "score": _number(item.get("score"))}
|
||
for item in series[-8:]
|
||
],
|
||
}
|