astock-agent/backend/app/llm/daily_review.py
2026-04-22 11:56:23 +08:00

170 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""每日复盘报告生成"""
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}
## 明日关注
优先跟踪重点关注标的能否满足触发条件,同时观察主线板块是否延续。若市场温度回落或板块资金退潮,应降低仓位并把未确认标的转回观察池。"""