rebuild(runtime): govern market operations and job truth
This commit is contained in:
@@ -11,7 +11,8 @@ from backend.data.providers.base import ProviderError
|
||||
from backend.data.repository import MarketRepository
|
||||
from backend.data.sentiment import calculate_sentiment
|
||||
from backend.database.connection import Database
|
||||
from backend.features.market.snapshot import build_snapshot
|
||||
from backend.features.market.events import apply_event_revisions
|
||||
from backend.features.market.snapshot import build_realtime_inputs, build_snapshot
|
||||
|
||||
SHANGHAI = ZoneInfo("Asia/Shanghai")
|
||||
|
||||
@@ -94,6 +95,79 @@ class MarketSnapshotService:
|
||||
"limit_down": len(snapshot["down_limits"]),
|
||||
"broken": len(snapshot["broken"]),
|
||||
"temperature": sentiment["score"],
|
||||
"source_set": ["tushare", "local"],
|
||||
"output_version": "market-summary-v1",
|
||||
}
|
||||
|
||||
def sync_realtime(
|
||||
self, requested_date: str | None = None, now: datetime | None = None
|
||||
) -> dict[str, Any]:
|
||||
clock = now or datetime.now(SHANGHAI)
|
||||
target = _date(requested_date or clock.date().isoformat())
|
||||
if target != clock.date().isoformat():
|
||||
raise SnapshotSyncError("盘中任务只允许同步当前交易日")
|
||||
local_time = clock.time().replace(tzinfo=None)
|
||||
in_window = time(9, 15) <= local_time < time(11, 35) or time(
|
||||
12, 55
|
||||
) <= local_time < time(15, 5)
|
||||
if not in_window:
|
||||
raise SnapshotSyncError("当前不在盘中行情刷新窗口")
|
||||
with self._database.read() as connection:
|
||||
dates = self._repository.open_dates(connection, target, 2)
|
||||
active_count = self._repository.active_stock_count(connection)
|
||||
if len(dates) < 2 or dates[0] != target:
|
||||
raise SnapshotSyncError("当前日期不是有效交易日")
|
||||
if active_count <= 0:
|
||||
raise SnapshotSyncError("请先同步股票目录")
|
||||
try:
|
||||
raw = self._gateway.realtime_snapshot_inputs(target, dates[1])
|
||||
except (ProviderError, MarketDataUnavailable) as exc:
|
||||
raise SnapshotSyncError("盘中行情读取失败,已保留最后成功快照") from exc
|
||||
daily = raw.get("daily")
|
||||
price_limits = raw.get("price_limits")
|
||||
if not isinstance(daily, ProviderResult):
|
||||
raise SnapshotSyncError("盘中行情缺失,已保留最后成功快照")
|
||||
coverage = len(daily.rows) / active_count
|
||||
if coverage < 0.9:
|
||||
raise SnapshotSyncError(
|
||||
f"盘中行情覆盖率仅{coverage * 100:.1f}%,已保留最后成功快照"
|
||||
)
|
||||
if not isinstance(price_limits, ProviderResult):
|
||||
raise SnapshotSyncError("盘中涨跌停价格缺失,已保留最后成功快照")
|
||||
limit_coverage = len(price_limits.rows) / max(len(daily.rows), 1)
|
||||
if limit_coverage < 0.95:
|
||||
raise SnapshotSyncError("盘中涨跌停价格覆盖不足,已保留最后成功快照")
|
||||
inputs = build_realtime_inputs(raw, self._gateway.stock_directory())
|
||||
snapshot = build_snapshot(target, dates[1], inputs)
|
||||
with self._database.read() as connection:
|
||||
rows = self._repository.summaries(connection, dates[1], 250)
|
||||
history = [json.loads(str(row["payload_json"])) for row in rows]
|
||||
sentiment = calculate_sentiment(snapshot, history)
|
||||
snapshot["sentiment"] = sentiment
|
||||
snapshot.update(snapshot["overview"])
|
||||
snapshot["temperature"] = sentiment["score"]
|
||||
observed_at = clock.isoformat(timespec="seconds")
|
||||
with self._database.transaction() as connection:
|
||||
self._repository.save_summary(
|
||||
connection,
|
||||
trade_date=target,
|
||||
observed_at=observed_at,
|
||||
state=SnapshotState.REALTIME.value,
|
||||
source="tushare",
|
||||
coverage=min(coverage, 1),
|
||||
payload=snapshot,
|
||||
)
|
||||
return {
|
||||
"trade_date": target,
|
||||
"observed_at": observed_at,
|
||||
"coverage": round(min(coverage, 1), 4),
|
||||
"stocks": len(daily.rows),
|
||||
"limit_up": len(snapshot["limits"]),
|
||||
"limit_down": len(snapshot["down_limits"]),
|
||||
"broken": len(snapshot["broken"]),
|
||||
"temperature": sentiment["score"],
|
||||
"source_set": ["tushare", "local"],
|
||||
"output_version": "market-summary-realtime-v1",
|
||||
}
|
||||
|
||||
def workspace(self, key: str, requested_date: str | None = None) -> dict[str, Any]:
|
||||
@@ -104,6 +178,9 @@ class MarketSnapshotService:
|
||||
if row is None:
|
||||
return {"trade_date": None, "message": "等待管理员首次同步真实收盘行情"}
|
||||
payload = json.loads(str(row["payload_json"]))
|
||||
with self._database.read() as connection:
|
||||
revisions = self._repository.event_revisions(connection, str(row["trade_date"]))
|
||||
apply_event_revisions(payload, revisions)
|
||||
response: dict[str, Any] = {
|
||||
"trade_date": str(row["trade_date"]),
|
||||
"observed_at": str(row["observed_at"]),
|
||||
@@ -173,6 +250,12 @@ class MarketSnapshotService:
|
||||
).fetchone()
|
||||
factors = json.loads(str(factor_row["payload_json"])) if factor_row else {}
|
||||
snapshot = json.loads(str(summary["payload_json"])) if summary else {}
|
||||
if summary:
|
||||
with self._database.read() as connection:
|
||||
revisions = self._repository.event_revisions(
|
||||
connection, str(summary["trade_date"])
|
||||
)
|
||||
apply_event_revisions(snapshot, revisions)
|
||||
event = (
|
||||
_entity_event(snapshot, entity.identifier, entity.code)
|
||||
if entity_type == "stock"
|
||||
|
||||
Reference in New Issue
Block a user