feat: track screener candidates across five trading days
This commit is contained in:
@@ -62,6 +62,7 @@ from sentiment_engine import (
|
||||
build_sentiment_history,
|
||||
latest_contiguous_history,
|
||||
)
|
||||
from strategy_tracking import StrategyTrackingService
|
||||
from tushare_client import TushareClient, TushareError
|
||||
|
||||
|
||||
@@ -127,6 +128,7 @@ class DashboardService:
|
||||
self._request_context = threading.local()
|
||||
self._system_credentials = self._load_system_credentials(environment_credentials)
|
||||
self.screener = ScreenerEngine(self.database)
|
||||
self.strategy_tracking = StrategyTrackingService(self.database)
|
||||
self.mentor_skills = MentorSkillRegistry(MENTOR_SKILLS_DIR)
|
||||
self.realtime_aggregator = WebRealtimeAggregator()
|
||||
self.screener.ensure_builtin_strategies()
|
||||
@@ -1082,6 +1084,26 @@ class DashboardService:
|
||||
"latest_result": self.database.latest_screener_run(self.current_user_id, normalized_date),
|
||||
}
|
||||
|
||||
def screener_tracking(self, limit: int = 12) -> dict[str, Any]:
|
||||
return self.strategy_tracking.list_tracking(self.current_user_id, limit)
|
||||
|
||||
def refresh_screener_tracking(self, trade_date: str) -> dict[str, Any]:
|
||||
normalized_date = normalize_date(trade_date)
|
||||
notice = ""
|
||||
if self.configured:
|
||||
try:
|
||||
FactorDataService(self.database, TushareClient(self.token)).sync(
|
||||
normalized_date, 15
|
||||
)
|
||||
except TushareError:
|
||||
notice = "最新日线暂未补齐,已按现有数据更新跟踪。"
|
||||
else:
|
||||
notice = "公共行情尚未配置,已按现有数据更新跟踪。"
|
||||
return {
|
||||
"tracking": self.screener_tracking(),
|
||||
"notice": notice,
|
||||
}
|
||||
|
||||
def sync_screener_data(self, trade_date: str, lookback: int = 45) -> dict[str, Any]:
|
||||
if not self.configured:
|
||||
raise ValueError("请先配置 Tushare Token。")
|
||||
@@ -2570,11 +2592,19 @@ class DashboardService:
|
||||
realtime_snapshot = TushareClient(self.token).realtime_factor_snapshot(trade_date)
|
||||
except TushareError as exc:
|
||||
raise ValueError(f"实时选股行情不可用,已停止筛选:{exc}") from exc
|
||||
return self.screener.screen(
|
||||
result = self.screener.screen(
|
||||
self.current_user_id, trade_date, formula, regime, strategy_name,
|
||||
bool(payload.get("run_backtest", True)),
|
||||
realtime_snapshot,
|
||||
)
|
||||
self.strategy_tracking.record_run(
|
||||
self.current_user_id,
|
||||
int(result.get("meta", {}).get("run_id") or 0),
|
||||
normalize_date(str(result.get("meta", {}).get("trade_date") or trade_date)),
|
||||
strategy_name,
|
||||
list(result.get("candidates") or []),
|
||||
)
|
||||
return result
|
||||
|
||||
def get_dragon_tiger(self, trade_date: str, force: bool = False) -> dict[str, Any]:
|
||||
normalized_date = normalize_date(trade_date)
|
||||
@@ -3465,6 +3495,15 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||
except ValueError as exc:
|
||||
self.send_json({"error": str(exc)}, HTTPStatus.BAD_REQUEST)
|
||||
return
|
||||
if parsed.path == "/api/screener/tracking":
|
||||
query = parse_qs(parsed.query)
|
||||
try:
|
||||
self.send_json(
|
||||
SERVICE.screener_tracking(int(query.get("limit", ["12"])[0]))
|
||||
)
|
||||
except (TypeError, ValueError) as exc:
|
||||
self.send_json({"error": str(exc)}, HTTPStatus.BAD_REQUEST)
|
||||
return
|
||||
if parsed.path == "/api/mentors/setup":
|
||||
query = parse_qs(parsed.query)
|
||||
trade_date = query.get("trade_date", [date.today().isoformat()])[0]
|
||||
@@ -3577,6 +3616,9 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||
if parsed.path == "/api/screener/run":
|
||||
self.run_screener()
|
||||
return
|
||||
if parsed.path == "/api/screener/tracking/refresh":
|
||||
self.refresh_screener_tracking()
|
||||
return
|
||||
if parsed.path == "/api/mentors/chat":
|
||||
self.mentor_chat()
|
||||
return
|
||||
@@ -4010,6 +4052,16 @@ class RequestHandler(BaseHTTPRequestHandler):
|
||||
except Exception as exc:
|
||||
self.send_json({"error": f"选股执行失败:{exc}"}, HTTPStatus.INTERNAL_SERVER_ERROR)
|
||||
|
||||
def refresh_screener_tracking(self) -> None:
|
||||
try:
|
||||
body = self.read_json_body(True)
|
||||
trade_date = str(body.get("trade_date") or date.today().isoformat())
|
||||
self.send_json({"ok": True, **SERVICE.refresh_screener_tracking(trade_date)})
|
||||
except (ValueError, json.JSONDecodeError) as exc:
|
||||
self.send_json({"error": str(exc)}, HTTPStatus.BAD_REQUEST)
|
||||
except Exception as exc:
|
||||
self.send_json({"error": f"跟踪刷新失败:{exc}"}, HTTPStatus.INTERNAL_SERVER_ERROR)
|
||||
|
||||
def mentor_chat(self) -> None:
|
||||
try:
|
||||
body = self.read_json_body()
|
||||
|
||||
Reference in New Issue
Block a user