"""每日复盘报告生成""" import logging from app.config import settings, today_trade_date logger = logging.getLogger(__name__) async def generate_review() -> dict: """生成每日复盘报告""" from app.data.tushare_client import tushare_client from app.data import tencent_client from app.engine.recommender import get_latest_recommendations, get_latest_sectors latest_trade_date = tushare_client.get_latest_trade_date() trade_date = today_trade_date() # 收集市场数据 result = await get_latest_recommendations() mt = result.get("market_temp") sectors = await get_latest_sectors() recs = result.get("recommendations", []) # 实时指数 try: index_data = await tencent_client.get_index_realtime() except Exception: index_data = {} # 构建数据摘要 market_summary = "" if mt: market_summary = ( f"市场温度: {mt.temperature}/100, " f"上涨{mt.up_count}家/下跌{mt.down_count}家, " f"涨停{mt.limit_up_count}家/跌停{mt.limit_down_count}家, " f"连板最高{mt.max_streak}板, 炸板率{mt.broken_rate}%" ) index_summary = "" name_map = {"000001.SH": "上证", "399001.SZ": "深证", "399006.SZ": "创业板"} for code in ["000001.SH", "399001.SZ", "399006.SZ"]: d = index_data.get(code, {}) if d: index_summary += f"{name_map[code]}: {d.get('price', 0):.2f} ({d.get('pct_chg', 0):+.2f}%), " sector_summary = "热门板块: " + "、".join( f"{s.sector_name}({(getattr(s, 'realtime_pct_change', None) or s.pct_change):+.1f}%)" for s in sectors[:5] ) rec_summary = "" for r in recs[:5]: signal_map = {"breakout": "突破", "breakout_confirm": "确认", "pullback": "回踩", "launch": "启动", "reversal": "反转"} st = signal_map.get(r.entry_signal_type, r.entry_signal_type) rec_summary += f"\n- {r.name}({r.ts_code}): {st}型, 评分{r.score}, {r.signal}" user_msg = f"""请根据以下数据生成今日A股市场复盘报告(中文): 日期: {trade_date} Tushare 最新交易日: {latest_trade_date} {market_summary} {index_summary} {sector_summary} 今日推荐股票: {rec_summary} 请按以下格式输出(Markdown格式,总字数300-500字): ## 市场概况 (指数走势、量能变化) ## 板块热点 (哪些板块领涨、资金流向) ## 交易机会 (今日推荐个股简要点评) ## 明日关注 (关注方向和操作建议)""" if settings.deepseek_api_key: try: from app.llm.client import get_client client = get_client() response = await client.chat.completions.create( model=settings.deepseek_model, messages=[ {"role": "system", "content": "你是一位专业的A股市场分析师,擅长市场复盘和策略分析。回复使用Markdown格式,简洁专业。"}, {"role": "user", "content": user_msg}, ], max_tokens=1500, temperature=0.5, ) content = response.choices[0].message.content.strip() generated_by = "llm" except Exception as e: logger.error(f"生成复盘报告失败,使用规则兜底: {e}") content = _build_fallback_review( trade_date=trade_date, market_summary=market_summary, index_summary=index_summary, sector_summary=sector_summary, recommendations=recs, ) generated_by = "rules" else: content = _build_fallback_review( trade_date=trade_date, market_summary=market_summary, index_summary=index_summary, sector_summary=sector_summary, recommendations=recs, ) generated_by = "rules" try: # 保存到数据库 from sqlalchemy import text from app.db.database import get_db async with get_db() as db: await db.execute( text( "INSERT OR REPLACE INTO daily_reviews (trade_date, content) " "VALUES (:td, :content)" ), {"td": trade_date, "content": content}, ) await db.commit() logger.info(f"已生成 {trade_date} 复盘报告 ({generated_by})") return {"status": "ok", "trade_date": trade_date, "content": content, "generated_by": generated_by} except Exception as e: logger.error(f"保存复盘报告失败: {e}") return {"status": "error", "message": str(e)} def _build_fallback_review( trade_date: str, market_summary: str, index_summary: str, sector_summary: str, recommendations: list, ) -> str: """LLM 不可用时生成结构化规则复盘,避免页面空白。""" actionable = [r for r in recommendations if getattr(r, "action_plan", "") == "可操作"] watch = [r for r in recommendations if getattr(r, "action_plan", "") == "重点关注"] top_recs = recommendations[:5] rec_lines = "\n".join( f"- {r.name}({r.ts_code}):{getattr(r, 'action_plan', '观察')}," f"{getattr(r, 'entry_signal_type', 'none')} 信号,评分 {getattr(r, 'score', 0)}。" for r in top_recs ) or "- 暂无推荐标的。" return f"""## 市场概况 {trade_date} 市场温度处于中性偏谨慎区间。{market_summary or "暂无市场温度数据。"} {index_summary or ""} ## 板块热点 {sector_summary or "暂无板块热度数据。"} 当前板块证据主要用于确认推荐方向是否有资金和赚钱效应支撑。 ## 交易机会 今日推荐池共 {len(recommendations)} 只,其中可操作 {len(actionable)} 只、重点关注 {len(watch)} 只。当前更适合按触发条件等待确认,不宜把观察标的直接当作买入标的。 {rec_lines} ## 明日关注 优先跟踪重点关注标的能否满足触发条件,同时观察主线板块是否延续。若市场温度回落或板块资金退潮,应降低仓位并把未确认标的转回观察池。"""