feat: complete heaven readings and screener publication
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user