136 lines
4.8 KiB
Python
136 lines
4.8 KiB
Python
"""推荐结果 LLM 增强
|
||
|
||
扫描完成后异步调用 LLM,为每只推荐股票生成深度分析。
|
||
"""
|
||
|
||
import asyncio
|
||
import json
|
||
import logging
|
||
from app.llm.client import chat_completion
|
||
from app.llm.prompts import ENHANCE_SYSTEM_PROMPT, ENHANCE_USER_TEMPLATE
|
||
from app.config import settings
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
async def enhance_recommendations(result: dict) -> None:
|
||
"""对推荐结果进行 LLM 增强分析(fire-and-forget)"""
|
||
if not settings.deepseek_api_key:
|
||
return
|
||
|
||
recommendations = result.get("recommendations", [])
|
||
if not recommendations:
|
||
return
|
||
|
||
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:
|
||
user_msg = ENHANCE_USER_TEMPLATE.format(
|
||
temperature=market_temp.temperature if market_temp else "N/A",
|
||
temp_level=temp_level,
|
||
up_count=market_temp.up_count if market_temp else 0,
|
||
down_count=market_temp.down_count if market_temp else 0,
|
||
limit_up_count=market_temp.limit_up_count if market_temp else 0,
|
||
max_streak=market_temp.max_streak if market_temp else 0,
|
||
broken_rate=market_temp.broken_rate if market_temp else 0,
|
||
sectors_text=sectors_text,
|
||
name=rec.name,
|
||
ts_code=rec.ts_code,
|
||
sector=rec.sector,
|
||
score=rec.score,
|
||
level=rec.level,
|
||
market_temp_score=rec.market_temp_score,
|
||
sector_score=rec.sector_score,
|
||
capital_score=rec.capital_score,
|
||
technical_score=rec.technical_score,
|
||
position_score=rec.position_score,
|
||
valuation_score=rec.valuation_score,
|
||
signal=rec.signal,
|
||
entry_price=rec.entry_price or "N/A",
|
||
target_price=rec.target_price or "N/A",
|
||
stop_loss=rec.stop_loss or "N/A",
|
||
reasons=";".join(rec.reasons) if rec.reasons else "无",
|
||
)
|
||
|
||
messages = [
|
||
{"role": "system", "content": ENHANCE_SYSTEM_PROMPT},
|
||
{"role": "user", "content": user_msg},
|
||
]
|
||
|
||
resp = await chat_completion(messages)
|
||
if resp and resp.content:
|
||
rec.llm_analysis = resp.content.strip()
|
||
enhanced_count += 1
|
||
|
||
except asyncio.CancelledError:
|
||
logger.warning(f"LLM 增强 {rec.ts_code} 被取消")
|
||
break
|
||
except Exception as e:
|
||
logger.error(f"LLM 增强 {rec.ts_code} 失败: {e}")
|
||
|
||
if enhanced_count > 0:
|
||
# 更新数据库
|
||
await _save_llm_analysis_to_db(recommendations)
|
||
# 通过 WebSocket 通知前端
|
||
await _broadcast_llm_ready(recommendations)
|
||
|
||
logger.info(f"LLM 增强完成: {enhanced_count}/{len(recommendations)} 条")
|
||
|
||
|
||
async def _save_llm_analysis_to_db(recommendations: list) -> None:
|
||
"""将 LLM 分析结果更新到数据库"""
|
||
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 "
|
||
"WHERE ts_code = :code AND date(created_at) = date('now', 'localtime') "
|
||
"AND scan_session = :session"
|
||
),
|
||
{
|
||
"analysis": rec.llm_analysis,
|
||
"code": rec.ts_code,
|
||
"session": rec.scan_session,
|
||
},
|
||
)
|
||
await db.commit()
|
||
except Exception as e:
|
||
logger.error(f"保存 LLM 分析到数据库失败: {e}")
|
||
|
||
|
||
async def _broadcast_llm_ready(recommendations: list) -> None:
|
||
"""通过 WebSocket 广播 LLM 分析完成事件"""
|
||
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"广播 LLM 分析完成失败: {e}")
|