diff --git a/backend/app/__pycache__/main.cpython-313.pyc b/backend/app/__pycache__/main.cpython-313.pyc index 419c8fd4..82e9880d 100644 Binary files a/backend/app/__pycache__/main.cpython-313.pyc and b/backend/app/__pycache__/main.cpython-313.pyc differ diff --git a/backend/app/api/__pycache__/market.cpython-313.pyc b/backend/app/api/__pycache__/market.cpython-313.pyc index 40b70f23..b8d34fb1 100644 Binary files a/backend/app/api/__pycache__/market.cpython-313.pyc and b/backend/app/api/__pycache__/market.cpython-313.pyc differ diff --git a/backend/app/api/auth.py b/backend/app/api/auth.py index bb4ac1a6..3819af65 100644 --- a/backend/app/api/auth.py +++ b/backend/app/api/auth.py @@ -227,7 +227,6 @@ async def get_data_stats(admin: dict = Depends(get_current_admin)): track_count = (await db.execute(text("SELECT COUNT(*) FROM recommendation_tracking"))).scalar() or 0 sector_count = (await db.execute(text("SELECT COUNT(*) FROM sector_heat"))).scalar() or 0 temp_count = (await db.execute(text("SELECT COUNT(*) FROM market_temperature"))).scalar() or 0 - review_count = (await db.execute(text("SELECT COUNT(*) FROM daily_reviews"))).scalar() or 0 low_score = (await db.execute(text("SELECT COUNT(*) FROM recommendations WHERE score < 60"))).scalar() or 0 # 最新日期 @@ -239,7 +238,6 @@ async def get_data_stats(admin: dict = Depends(get_current_admin)): "tracking": track_count, "sector_heat": sector_count, "market_temperature": temp_count, - "daily_reviews": review_count, "low_score_count": low_score, "latest_date": str(latest_rec), "earliest_date": str(earliest_rec), @@ -251,7 +249,7 @@ async def data_reset(req: DataResetRequest, admin: dict = Depends(get_current_ad """数据重置(管理员) mode: - - "all": 清除所有业务数据(推荐、跟踪、板块热度、市场温度、复盘) + - "all": 清除所有业务数据(推荐、跟踪、板块热度、市场温度、诊断) - "recommendations": 清除推荐记录和跟踪数据,保留板块和市场温度 - "date_range": 清除指定日期之前的数据 - "low_score": 清除低分推荐(score < min_score)和过期跟踪数据 @@ -269,8 +267,6 @@ async def data_reset(req: DataResetRequest, admin: dict = Depends(get_current_ad deleted["sector_heat"] = result.rowcount or 0 result = await db.execute(text("DELETE FROM market_temperature")) deleted["market_temperature"] = result.rowcount or 0 - result = await db.execute(text("DELETE FROM daily_reviews")) - deleted["daily_reviews"] = result.rowcount or 0 result = await db.execute(text("DELETE FROM stock_diagnoses")) deleted["stock_diagnoses"] = result.rowcount or 0 diff --git a/backend/app/api/debug.py b/backend/app/api/debug.py index 55127f3b..6f96005d 100644 --- a/backend/app/api/debug.py +++ b/backend/app/api/debug.py @@ -109,7 +109,7 @@ async def system_status(_admin: dict = Depends(get_current_admin)): # 各表数据量 tables_counts = {} for t in ["recommendations", "sector_heat", "market_temperature", - "recommendation_tracking", "daily_reviews", "stock_diagnoses", + "recommendation_tracking", "stock_diagnoses", "error_logs", "users"]: result = await db.execute(text(f"SELECT COUNT(*) FROM {t}")) tables_counts[t] = result.scalar() or 0 @@ -145,4 +145,4 @@ async def system_status(_admin: dict = Depends(get_current_admin)): "last_errors": last_errors, "tables_counts": tables_counts, "db_size_mb": db_size_mb, - } \ No newline at end of file + } diff --git a/backend/app/api/market.py b/backend/app/api/market.py index 31fc4438..f9130aca 100644 --- a/backend/app/api/market.py +++ b/backend/app/api/market.py @@ -57,26 +57,6 @@ async def get_overview(): return _overview_daily() -@router.get("/daily-review") -async def get_daily_review(): - """获取每日复盘报告""" - from sqlalchemy import text - from app.db.database import get_db - async with get_db() as db: - result = await db.execute( - text("SELECT * FROM daily_reviews ORDER BY trade_date DESC LIMIT 5") - ) - reviews = [] - for row in result.fetchall(): - r = row._mapping - reviews.append({ - "trade_date": r["trade_date"], - "content": r["content"] or "", - "created_at": str(r["created_at"]) if r["created_at"] else "", - }) - return {"reviews": reviews} - - @router.get("/strategy-board") async def get_strategy_board(): """获取今日市场作战面板(只读,不触发 LLM)""" @@ -123,13 +103,6 @@ async def get_ops_status(): "ORDER BY REPLACE(trade_date, '-', '') DESC, id DESC LIMIT 1" ) )).fetchone() - board_row = (await db.execute( - text( - "SELECT created_at FROM daily_reviews " - "ORDER BY trade_date DESC LIMIT 1" - ) - )).fetchone() - def _fmt_dt(value): return str(value or "") @@ -149,7 +122,6 @@ async def get_ops_status(): "last_tracking_created_at": _fmt_dt(tracking_row._mapping["created_at"]) if tracking_row else "", "last_market_created_at": _fmt_dt(market_row._mapping["created_at"]) if market_row else "", "last_sector_created_at": _fmt_dt(sector_row._mapping["created_at"]) if sector_row else "", - "last_review_created_at": _fmt_dt(board_row._mapping["created_at"]) if board_row else "", "status": "fresh" if latest_market_date else "empty", "message": ( f"最新市场日期 {latest_market_date},最近跟踪 {latest_tracking_date or '暂无'}" @@ -180,15 +152,6 @@ async def generate_strategy_iteration(limit: int = 50, _admin: dict = Depends(ge from app.llm.strategy_iteration import build_strategy_iteration_report return await build_strategy_iteration_report(limit=limit, include_llm=True) - -@router.post("/generate-review") -async def generate_daily_review(_admin: dict = Depends(get_current_admin)): - """手动触发生成每日复盘""" - from app.llm.daily_review import generate_review - result = await generate_review() - return result - - async def _overview_realtime(): """盘中:腾讯实时指数行情""" index_data = await tencent_client.get_index_realtime() diff --git a/backend/app/db/__pycache__/tables.cpython-313.pyc b/backend/app/db/__pycache__/tables.cpython-313.pyc index b3a4609a..5f5a0403 100644 Binary files a/backend/app/db/__pycache__/tables.cpython-313.pyc and b/backend/app/db/__pycache__/tables.cpython-313.pyc differ diff --git a/backend/app/db/tables.py b/backend/app/db/tables.py index 18a3140c..ded446b6 100644 --- a/backend/app/db/tables.py +++ b/backend/app/db/tables.py @@ -113,14 +113,6 @@ users_table = Table( Column("updated_at", DateTime, server_default=func.now()), ) -daily_reviews_table = Table( - "daily_reviews", metadata, - Column("id", Integer, primary_key=True, autoincrement=True), - Column("trade_date", Text, nullable=False, unique=True), - Column("content", Text, default=""), - Column("created_at", DateTime, server_default=func.now()), -) - stock_diagnoses_table = Table( "stock_diagnoses", metadata, Column("id", Integer, primary_key=True, autoincrement=True), diff --git a/backend/app/engine/__pycache__/scheduler.cpython-313.pyc b/backend/app/engine/__pycache__/scheduler.cpython-313.pyc index 9f7731d8..ee98e749 100644 Binary files a/backend/app/engine/__pycache__/scheduler.cpython-313.pyc and b/backend/app/engine/__pycache__/scheduler.cpython-313.pyc differ diff --git a/backend/app/engine/scheduler.py b/backend/app/engine/scheduler.py index 80d6b7ff..13f49925 100644 --- a/backend/app/engine/scheduler.py +++ b/backend/app/engine/scheduler.py @@ -39,22 +39,6 @@ async def _run_scan(session_name: str): await log_error("scheduler", f"定时扫描失败 ({session_name}): {e}", detail=traceback.format_exc()) -async def _generate_daily_review(): - """16:10 自动生成每日复盘报告""" - logger.info("=== 开始生成每日复盘报告 ===") - try: - from app.llm.daily_review import generate_review - result = await generate_review() - if result.get("status") == "ok": - logger.info(f"复盘报告生成成功: {result.get('trade_date')}") - else: - logger.warning(f"复盘报告生成失败: {result.get('message')}") - except Exception as e: - logger.error(f"复盘报告生成异常: {e}") - from app.db.error_logger import log_error - await log_error("scheduler", f"复盘报告生成异常: {e}", detail=traceback.format_exc()) - - async def _run_watchlist_analysis(): """收盘后自动分析所有用户自选股。""" logger.info("=== 开始自选股定时分析 ===") @@ -76,39 +60,26 @@ def setup_scheduler(): args=["pre_market"], id="pre_market", replace_existing=True ) - # 早盘扫描 09:35-09:55 每5分钟 + 10:00 - for m in range(35, 60, 5): + # 盘中扫描:按交易节奏执行,避免高频重复计算 + scan_schedule = [ + ("morning_open_0935", 9, 35, "morning_open"), + ("morning_open_0950", 9, 50, "morning_open"), + ("morning_mid_1020", 10, 20, "morning_mid"), + ("morning_mid_1050", 10, 50, "morning_mid"), + ("morning_mid_1120", 11, 20, "morning_mid"), + ("afternoon_1310", 13, 10, "afternoon"), + ("afternoon_1340", 13, 40, "afternoon"), + ("late_1410", 14, 10, "late_session"), + ("late_1440", 14, 40, "late_session"), + ("close_1500", 15, 0, "late_session"), + ] + for job_id, hour, minute, session_name in scan_schedule: scheduler.add_job( - _run_scan, CronTrigger(hour=9, minute=m, day_of_week="mon-fri"), - args=["morning_open"], id=f"morning_{m}", replace_existing=True - ) - scheduler.add_job( - _run_scan, CronTrigger(hour=10, minute=0, day_of_week="mon-fri"), - args=["morning_open"], id="morning_1000", replace_existing=True - ) - - # 上午盘中 10:10-11:30 - 每10分钟 - for h in [10, 11]: - start_m = 10 if h == 10 else 0 - end_m = 60 if h == 10 else 31 - for m in range(start_m, end_m, 10): - scheduler.add_job( - _run_scan, CronTrigger(hour=h, minute=m, day_of_week="mon-fri"), - args=["morning_mid"], id=f"morning_mid_{h}_{m}", replace_existing=True - ) - - # 午后扫描 13:00-14:00 - 每10分钟 - for m in range(0, 60, 10): - scheduler.add_job( - _run_scan, CronTrigger(hour=13, minute=m, day_of_week="mon-fri"), - args=["afternoon"], id=f"afternoon_{m}", replace_existing=True - ) - - # 尾盘扫描 14:00-14:50 - 每10分钟 - for m in range(0, 51, 10): - scheduler.add_job( - _run_scan, CronTrigger(hour=14, minute=m, day_of_week="mon-fri"), - args=["late_session"], id=f"late_{m}", replace_existing=True + _run_scan, + CronTrigger(hour=hour, minute=minute, day_of_week="mon-fri"), + args=[session_name], + id=job_id, + replace_existing=True, ) # 收盘总结 16:00(Tushare 日线数据通常在 15:30 后更新完成) @@ -117,12 +88,6 @@ def setup_scheduler(): args=["post_market"], id="post_market", replace_existing=True ) - # 每日复盘报告 16:10(扫描完成后生成) - scheduler.add_job( - _generate_daily_review, CronTrigger(hour=16, minute=10, day_of_week="mon-fri"), - id="daily_review", replace_existing=True - ) - scheduler.add_job( _run_watchlist_analysis, CronTrigger(hour=16, minute=20, day_of_week="mon-fri"), id="watchlist_analysis", replace_existing=True diff --git a/backend/app/llm/analysis_agent.py b/backend/app/llm/analysis_agent.py deleted file mode 100644 index aef3b591..00000000 --- a/backend/app/llm/analysis_agent.py +++ /dev/null @@ -1,252 +0,0 @@ -"""AI 深度分析 - -预先获取 K 线、资金流、技术信号等数据,一次性传入 LLM 生成结构化分析报告。 -不依赖 tool calling,避免 DeepSeek DSML 标签问题。 -""" - -import asyncio -import json -import logging -import re -import traceback - -from app.llm.client import chat_completion -from app.llm.prompts import TREND_BREAKOUT_ANALYSIS_PROMPT -from app.config import settings - -logger = logging.getLogger(__name__) - - -async def analyze_recommendations(result: dict) -> None: - """对所有推荐股票执行 AI 深度分析""" - recommendations = result.get("recommendations", []) - if not recommendations or not settings.deepseek_api_key: - return - - try: - await _do_analyze(result, recommendations) - except Exception as e: - logger.error(f"AI 分析任务异常: {e}") - from app.db.error_logger import log_error - await log_error("analysis_agent", f"AI 分析任务异常: {e}", detail=traceback.format_exc()) - for rec in recommendations: - if not rec.llm_analysis: - rec.llm_analysis = "AI 分析暂时不可用" - await _broadcast_llm_ready(recommendations) - - -async def _do_analyze(result: dict, recommendations: list) -> None: - """分析核心逻辑""" - market_temp = result.get("market_temp") - hot_sectors = result.get("hot_sectors", []) - - # 构建板块文本 - sectors_text = "\n".join( - f"- {s.sector_name}: 涨幅{s.pct_change}%, 资金流入{s.capital_inflow}万, " - f"涨停{s.limit_up_count}家, 热度{s.heat_score}分, 阶段={s.stage}" - for s in hot_sectors[:5] - ) if hot_sectors else "暂无板块数据" - - # 温度等级 - temp_val = market_temp.temperature if market_temp else 0 - if temp_val >= 60: - temp_level = "积极" - elif temp_val >= 30: - temp_level = "谨慎" - else: - temp_level = "低迷" - - enhanced_count = 0 - for rec in recommendations: - try: - # 预先获取该股票的详细数据 - stock_data = await _fetch_stock_data(rec.ts_code, rec.sector) - - strategy_label = "趋势突破" - signal_map = {"breakout": "突破型", "pullback": "回踩型", "launch": "启动型", "none": "无信号"} - entry_label = signal_map.get(rec.entry_signal_type, "无信号") - system_prompt = TREND_BREAKOUT_ANALYSIS_PROMPT - - user_msg = _build_user_message( - rec=rec, - strategy_label=strategy_label, - entry_label=entry_label, - market_temp=market_temp, - temp_level=temp_level, - sectors_text=sectors_text, - stock_data=stock_data, - ) - - messages = [ - {"role": "system", "content": system_prompt}, - {"role": "user", "content": user_msg}, - ] - - resp = await chat_completion(messages) - if resp and resp.content: - analysis = resp.content.strip() - rec.llm_analysis = analysis - rec.llm_score = _extract_score(analysis) - enhanced_count += 1 - else: - rec.llm_analysis = "AI 分析暂时不可用" - - except asyncio.CancelledError: - logger.warning(f"AI 分析 {rec.ts_code} 被取消") - break - except Exception as e: - logger.error(f"AI 分析 {rec.ts_code} 失败: {e}") - rec.llm_analysis = "AI 分析暂时不可用" - - # 无论成功失败都保存并广播 - await _save_llm_analysis_to_db(recommendations) - await _broadcast_llm_ready(recommendations) - - logger.info(f"AI 深度分析完成: {enhanced_count}/{len(recommendations)} 条") - - -async def _fetch_stock_data(ts_code: str, sector: str) -> str: - """预先获取个股详细数据,拼接为文本供 LLM 分析""" - from app.llm.tool_executor import ( - _get_stock_kline, - _get_stock_capital_flow, - _get_stock_technical_signal, - _get_sector_performance, - ) - - parts = [] - - # K 线(最近 30 天摘要) - try: - kline_text = await _get_stock_kline(ts_code, 60) - kline_data = json.loads(kline_text) - if isinstance(kline_data, list) and kline_data: - # 只取最近 10 条以控制 token - recent = kline_data[-10:] - kline_summary = "\n".join( - f" {d.get('trade_date', '')}: 收{d.get('close', '')} " - f"涨跌{d.get('pct_chg', '')}% 量{d.get('vol', '')} " - f"MA5={d.get('ma5', '')} MA10={d.get('ma10', '')} MA20={d.get('ma20', '')} " - f"DIF={d.get('dif', '')} DEA={d.get('dea', '')} RSI={d.get('rsi14', '')}" - for d in recent - ) - parts.append(f"## K线数据(近10日)\n{kline_summary}") - except Exception as e: - logger.debug(f"获取K线数据失败 {ts_code}: {e}") - - # 资金流向 - try: - flow_text = await _get_stock_capital_flow(ts_code, 5) - flow_data = json.loads(flow_text) - if isinstance(flow_data, list) and flow_data: - flow_summary = "\n".join( - f" {d.get('trade_date', '')}: 主力净流入{d.get('main_net_inflow', 0)}万" - for d in flow_data[-5:] - ) - parts.append(f"## 资金流向(近5日)\n{flow_summary}") - except Exception as e: - logger.debug(f"获取资金流向失败 {ts_code}: {e}") - - # 技术信号 - try: - signal_text = await _get_stock_technical_signal(ts_code) - parts.append(f"## 技术信号\n{signal_text}") - except Exception as e: - logger.debug(f"获取技术信号失败 {ts_code}: {e}") - - # 板块表现 - if sector: - try: - sector_text = await _get_sector_performance(sector) - parts.append(f"## 板块数据\n{sector_text}") - except Exception as e: - logger.debug(f"获取板块数据失败 {sector}: {e}") - - return "\n\n".join(parts) if parts else "暂无额外数据" - - -def _build_user_message( - rec, - strategy_label: str, - entry_label: str, - market_temp, - temp_level: str, - sectors_text: str, - stock_data: str, -) -> str: - """构建完整的用户消息(含预获取的数据)""" - return f"""## 量化系统数据 -- 股票: {rec.name}({rec.ts_code}) -- 所属板块: {rec.sector} -- 策略类型: {strategy_label} -- 入场信号: {entry_label} -- 综合评分: {rec.score}分({rec.level}) -- 各维度: 市场{rec.market_temp_score} | 板块{rec.sector_score} | 资金{rec.capital_score} | 技术{rec.technical_score} | 位置{rec.position_score} | 估值{rec.valuation_score} -- 信号: {rec.signal} -- 参考价: 入场{rec.entry_price or 'N/A'} / 目标{rec.target_price or 'N/A'} / 止损{rec.stop_loss or 'N/A'} -- 量化理由: {";".join(rec.reasons) if rec.reasons else "无"} - -## 市场环境 -- 市场温度: {market_temp.temperature if market_temp else 'N/A'}/100({temp_level}) -- 涨跌比: {market_temp.up_count if market_temp else 0}涨 / {market_temp.down_count if market_temp else 0}跌 -- 涨停: {market_temp.limit_up_count if market_temp else 0}家 - -## 热门板块 -{sectors_text} - -## 个股详细数据 -{stock_data} - -请根据以上所有数据,按照指定格式输出深度分析报告。""" - - -def _extract_score(text: str) -> float | None: - """从 AI 分析报告中提取评分(1-10)""" - match = re.search(r"###\s*AI\s*评分[^\d]*(\d+(?:\.\d+)?)", text) - if match: - score = float(match.group(1)) - return min(max(score, 1), 10) - return None - - -async def _save_llm_analysis_to_db(recommendations: list) -> None: - """将 AI 分析结果更新到数据库""" - try: - from app.db.database import get_db - from sqlalchemy import text - - async with get_db() as db: - for rec in recommendations: - if not rec.llm_analysis: - continue - await db.execute( - text( - "UPDATE recommendations SET llm_analysis = :analysis, " - "llm_score = :score " - "WHERE ts_code = :code AND date(created_at) = date('now', 'localtime') " - "AND scan_session = :session" - ), - { - "analysis": rec.llm_analysis, - "score": rec.llm_score, - "code": rec.ts_code, - "session": rec.scan_session, - }, - ) - await db.commit() - except Exception as e: - logger.error(f"保存 AI 分析到数据库失败: {e}") - from app.db.error_logger import log_error - await log_error("analysis_agent", f"保存 AI 分析到数据库失败: {e}", detail=traceback.format_exc()) - - -async def _broadcast_llm_ready(recommendations: list) -> None: - """通过 WebSocket 广播 AI 分析完成事件""" - try: - from app.api.websocket import broadcast_update - await broadcast_update({ - "type": "llm_analysis_ready", - "count": len([r for r in recommendations if r.llm_analysis]), - }) - except Exception as e: - logger.error(f"广播 AI 分析完成失败: {e}") diff --git a/backend/app/llm/daily_review.py b/backend/app/llm/daily_review.py deleted file mode 100644 index 7172001d..00000000 --- a/backend/app/llm/daily_review.py +++ /dev/null @@ -1,169 +0,0 @@ -"""每日复盘报告生成""" - -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} - -## 明日关注 -优先跟踪重点关注标的能否满足触发条件,同时观察主线板块是否延续。若市场温度回落或板块资金退潮,应降低仓位并把未确认标的转回观察池。""" diff --git a/backend/app/llm/enhancer.py b/backend/app/llm/enhancer.py deleted file mode 100644 index 1d45eade..00000000 --- a/backend/app/llm/enhancer.py +++ /dev/null @@ -1,15 +0,0 @@ -"""推荐结果 LLM 增强 - -现在统一使用 analysis_agent 模块进行深度分析。 -此文件保留为兼容入口,内部直接调用 analysis_agent。 -""" - -import logging - -logger = logging.getLogger(__name__) - - -async def enhance_recommendations(result: dict) -> None: - """对推荐结果进行 LLM 增强分析(兼容入口,委托给 analysis_agent)""" - from app.llm.analysis_agent import analyze_recommendations - await analyze_recommendations(result) \ No newline at end of file diff --git a/frontend/.next/server/app-paths-manifest.json b/frontend/.next/server/app-paths-manifest.json index 21ce9557..a923b582 100644 --- a/frontend/.next/server/app-paths-manifest.json +++ b/frontend/.next/server/app-paths-manifest.json @@ -1,7 +1,7 @@ { - "/(auth)/stock/[code]/page": "app/(auth)/stock/[code]/page.js", "/(auth)/dashboard/page": "app/(auth)/dashboard/page.js", "/(auth)/chat/page": "app/(auth)/chat/page.js", "/(public)/login/page": "app/(public)/login/page.js", + "/(auth)/stock/[code]/page": "app/(auth)/stock/[code]/page.js", "/(public)/page": "app/(public)/page.js" } \ No newline at end of file diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 671ffb1d..af7fe275 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -608,7 +608,6 @@ export interface DataStats { tracking: number; sector_heat: number; market_temperature: number; - daily_reviews: number; low_score_count: number; latest_date: string; earliest_date: string;