"""推荐列表 API""" from fastapi import APIRouter from app.engine.recommender import ( refresh_recommendations, get_latest_recommendations, get_recommendation_history, get_performance_stats, ) from app.config import is_trading_hours router = APIRouter(prefix="/api/recommendations", tags=["recommendations"]) @router.get("/latest") async def get_latest(): """获取最新推荐列表""" result = await get_latest_recommendations() mt = result.get("market_temp") return { "market_temperature": { "trade_date": mt.trade_date if mt else "", "temperature": mt.temperature if mt else 0, "up_count": mt.up_count if mt else 0, "down_count": mt.down_count if mt else 0, "limit_up_count": mt.limit_up_count if mt else 0, "limit_down_count": mt.limit_down_count if mt else 0, "max_streak": mt.max_streak if mt else 0, "broken_rate": mt.broken_rate if mt else 0, "index_above_ma20": mt.index_above_ma20 if mt else False, } if mt else None, "recommendations": [ { "ts_code": r.ts_code, "name": r.name, "sector": r.sector, "score": r.score, "level": r.level, "signal": r.signal, "market_temp_score": r.market_temp_score, "sector_score": r.sector_score, "capital_score": r.capital_score, "technical_score": r.technical_score, "supply_demand_score": r.supply_demand_score, "price_action_score": r.price_action_score, "position_score": r.position_score, "valuation_score": r.valuation_score, "entry_price": r.entry_price, "target_price": r.target_price, "stop_loss": r.stop_loss, "reasons": r.reasons, "risk_note": r.risk_note, "llm_analysis": r.llm_analysis, "entry_timing": r.entry_timing, "llm_score": r.llm_score, "strategy": r.strategy, "entry_signal_type": r.entry_signal_type, "scan_session": r.scan_session, "created_at": r.created_at.isoformat() if r.created_at else None, } for r in result.get("recommendations", []) ], "scan_mode": result.get("scan_mode", "unknown"), } @router.post("/refresh") async def refresh(scan_session: str = "manual"): """手动触发一次全量筛选""" result = await refresh_recommendations(scan_session=scan_session) rec_count = len(result.get("recommendations", [])) mt = result.get("market_temp") return { "status": "ok", "count": rec_count, "temperature": mt.temperature if mt else 0, "scan_mode": result.get("scan_mode", "unknown"), "is_trading": is_trading_hours(), } @router.post("/update-tracking") async def update_tracking(): """独立更新推荐跟踪数据(不触发新扫描,盘中可单独调用)""" from app.engine.recommender import _update_tracking await _update_tracking() stats = await get_performance_stats() return { "status": "ok", "tracked": stats.get("tracked", 0), "win_rate": stats.get("win_rate", 0), "avg_return": stats.get("avg_return", 0), } @router.get("/status") async def get_scan_status(): """获取当前扫描状态信息""" return { "is_trading": is_trading_hours(), "scan_mode": "intraday" if is_trading_hours() else "post_market", "description": "盘中实时扫描(腾讯行情)" if is_trading_hours() else "盘后分析(Tushare日级数据)", } @router.get("/history") async def get_history(days: int = 7): """获取历史推荐(按日期分组)""" return await get_recommendation_history(days) @router.get("/performance") async def performance(): """获取推荐胜率统计""" return await get_performance_stats()