feat: complete heaven readings and screener publication

This commit is contained in:
leefer
2026-08-05 23:44:17 +08:00
parent 6a058c2929
commit b3a21d05b7
34 changed files with 3538 additions and 225 deletions
+96 -7
View File
@@ -15,6 +15,11 @@ from backend.features.screener.compiler import (
from backend.features.screener.catalog import FACTOR_FIELDS, FACTOR_GROUPS, REGIMES
from backend.features.screener.data_sync import FactorDataService
from backend.features.screener.formula import compile_local_strategy
from backend.features.screener.publication import resolve_published_batch
from backend.features.screener.signals import (
attach_strategy_validity,
build_candidate_archive,
)
SCREENER_LIBRARY_VERSION = 8
@@ -91,26 +96,71 @@ class ScreenerServiceMixin:
factor_health = self.screener.factor_health(normalized_date)
strategies = self.database.list_screener_strategies(self.current_user_id)
for strategy in strategies:
attach_strategy_validity(strategy)
missing = self._strategy_missing_data(strategy, factor_dates, factor_health)
strategy["data_ready"] = not missing
strategy["missing_data"] = missing
automatic_results = self.database.screener_runs_for_date(0, normalized_date)
personal_results = self.database.screener_runs_for_date(
self.current_user_id, normalized_date
batch_markers = self.database.list_screener_batch_markers(normalized_date, 120)
complete_markers = [
item for item in batch_markers if item.get("status") == "complete"
][:30]
legacy_results = []
legacy_date = ""
if not batch_markers:
legacy_results = self.database.screener_runs_for_date(0, normalized_date)
if legacy_results:
legacy_date = normalized_date
published_marker, automatic_status, published_batch = resolve_published_batch(
batch_markers, normalized_date, legacy_date
)
published_date = str((published_batch or {}).get("trade_date") or "")
automatic_results = (
legacy_results
if legacy_results and published_date == normalized_date
else self.database.screener_runs_for_date(0, published_date)
if published_date
else []
)
personal_results = self.database.recent_screener_runs(
self.current_user_id, normalized_date, "quant", 40
)
recent_results = [
*[item for item in automatic_results if item.get("meta", {}).get("mode") in {"smart", "curated"}],
*[item for item in personal_results if item.get("meta", {}).get("mode") == "quant"],
*personal_results,
]
latest_results: dict[str, dict[str, Any]] = {}
for result in reversed(recent_results):
mode = str(result.get("meta", {}).get("mode") or "smart")
latest_results[mode] = result
automatic_status = self.database.get_data_snapshot(
"screener_auto_v1", normalized_date
) or {}
published_dates = [
str(item.get("trade_date") or "") for item in complete_markers
if item.get("trade_date")
]
if legacy_date and legacy_date not in published_dates:
published_dates.append(legacy_date)
archive_runs = self.database.screener_runs_for_dates(0, published_dates, 1800)
archive_runs.extend(personal_results)
archive_as_of_date = published_date or (factor_dates[-1] if factor_dates else "")
marker_regime = (published_marker or {}).get("regime") or {}
archive_regime = str(
(marker_regime.get("id") if isinstance(marker_regime, dict) else marker_regime)
or regime.get("id") or "repair"
)
active_signals, candidate_history = build_candidate_archive(
archive_runs,
strategies,
factor_dates,
archive_as_of_date,
archive_regime,
)
self._attach_published_strategy_status(
strategies, automatic_results, published_marker, published_date
)
return {
"trade_date": normalized_date,
"requested_trade_date": normalized_date,
"regime": regime,
"regimes": [{"id": key, "label": value} for key, value in REGIMES.items()],
"strategies": strategies,
@@ -141,10 +191,49 @@ class ScreenerServiceMixin:
"latest_results": latest_results,
"recent_results": recent_results,
"automatic_status": automatic_status,
"published_batch": published_batch,
"published_status": published_marker or {},
"active_signals": active_signals,
"candidate_history": candidate_history,
# Kept during the client transition for compatibility with older frontends.
"latest_result": latest_results.get("smart"),
}
@staticmethod
def _attach_published_strategy_status(
strategies: list[dict[str, Any]],
automatic_results: list[dict[str, Any]],
marker: dict[str, Any] | None,
published_date: str,
) -> None:
results_by_name = {
str((item.get("meta") or {}).get("strategy_name") or ""): item
for item in automatic_results
}
skipped_by_name = {
str(item.get("name") or ""): item
for item in (marker or {}).get("skipped") or []
}
for strategy in strategies:
name = str(strategy.get("name") or "")
result = results_by_name.get(name)
skipped = skipped_by_name.get(name)
if result is not None:
candidates = result.get("candidates") or []
status = "ready" if candidates else "no_signal"
detail = f"{len(candidates)} 只候选" if candidates else "数据完整,暂无符合条件个股"
elif skipped is not None:
status = "missing_data"
detail = str(skipped.get("reason") or "缺少策略必需数据")
else:
status = "not_run"
detail = "该成功批次未运行此策略"
strategy["published_run"] = {
"trade_date": published_date,
"status": status,
"detail": detail,
}
def screener_tracking(self, limit: int = 12) -> dict[str, Any]:
return self.strategy_tracking.list_tracking(self.current_user_id, limit)