功能细节优化
This commit is contained in:
170
backend/ai.py
170
backend/ai.py
@@ -202,6 +202,176 @@ def diagnose(symbol):
|
||||
return base
|
||||
|
||||
|
||||
# ============ 走势分析(右键K线) ============
|
||||
def trend_analysis(symbol: str, date: str, period: str = "daily"):
|
||||
"""分析某只股票在指定日期(或最新)附近暴涨/暴跌的原因。
|
||||
period: daily / weekly / monthly
|
||||
"""
|
||||
with get_session() as s:
|
||||
sec = s.get(Security, symbol)
|
||||
# 取该日期前后一段数据作为上下文
|
||||
if date:
|
||||
try:
|
||||
target_date = dt.date.fromisoformat(date)
|
||||
except Exception:
|
||||
target_date = None
|
||||
else:
|
||||
target_date = None
|
||||
|
||||
# 取最近60根K线
|
||||
rows = s.execute(
|
||||
select(DailyQuote).where(DailyQuote.code == symbol)
|
||||
.order_by(DailyQuote.date.desc()).limit(60)
|
||||
).scalars().all()
|
||||
rows = list(reversed(rows))
|
||||
|
||||
# 当日及相邻数据
|
||||
target_row = None
|
||||
if target_date and rows:
|
||||
# 找最近的一根
|
||||
closest = min(rows, key=lambda r: abs((r.date - target_date).days))
|
||||
if abs((closest.date - target_date).days) <= 7:
|
||||
target_row = closest
|
||||
|
||||
if not target_row and rows:
|
||||
target_row = rows[-1]
|
||||
|
||||
m = s.get(StockMetric, symbol)
|
||||
|
||||
name = (sec.name if sec else (m.name if m else symbol)) or symbol
|
||||
|
||||
# 计算目标K线的涨跌幅
|
||||
pct = 0.0
|
||||
if target_row and rows:
|
||||
idx = rows.index(target_row)
|
||||
if idx > 0:
|
||||
prev_close = rows[idx - 1].close
|
||||
if prev_close:
|
||||
pct = round((target_row.close - prev_close) / prev_close * 100, 2)
|
||||
|
||||
# 拉取相关新闻
|
||||
import akshare_service as svc
|
||||
try:
|
||||
news_data = svc.get_stock_news(symbol, limit=10)
|
||||
news_items = news_data.get("list", [])
|
||||
except Exception:
|
||||
news_items = []
|
||||
|
||||
# 拉取RAG上下文
|
||||
import rag
|
||||
rctx = rag.stock_context(symbol, limit=6)
|
||||
|
||||
# 构造上下文数据
|
||||
period_cn = {"daily": "日K", "weekly": "周K", "monthly": "月K"}.get(period, "K线")
|
||||
date_str = target_row.date.isoformat() if target_row else (date or "最新")
|
||||
|
||||
# 当日技术面
|
||||
if target_row:
|
||||
tech_line = (
|
||||
f"目标K线:{date_str},开{target_row.open} 收{target_row.close} "
|
||||
f"高{target_row.high} 低{target_row.low},"
|
||||
f"涨跌幅{pct:+.2f}%,成交量{target_row.volume:,}"
|
||||
)
|
||||
else:
|
||||
tech_line = f"目标日期:{date_str},暂无日线数据"
|
||||
|
||||
# 前后走势(最近5根)
|
||||
if target_row and rows:
|
||||
idx = rows.index(target_row)
|
||||
window = rows[max(0, idx-4):idx+2]
|
||||
trend_line = "前后走势:" + " → ".join(
|
||||
f"{r.date.strftime('%m/%d')}({'↑' if i == 0 or r.close >= rows[rows.index(r)-1].close else '↓'}{abs(round((r.close/rows[rows.index(r)-1].close-1)*100,1)) if rows.index(r) > 0 else 0}%)"
|
||||
for i, r in enumerate(window)
|
||||
)
|
||||
else:
|
||||
trend_line = ""
|
||||
|
||||
# 均线状态
|
||||
ma_line = ""
|
||||
if m:
|
||||
ma_line = (f"均线状态:MA5={m.ma5} MA10={m.ma10} MA20={m.ma20} MA60={m.ma60},"
|
||||
f"{'多头排列' if m.ma_bull else '非多头'},"
|
||||
f"量比{m.vol_ratio},RSI14={m.rsi14}")
|
||||
|
||||
# 新闻摘要
|
||||
news_block = ""
|
||||
if news_items:
|
||||
news_block = "相关新闻(近期):\n" + "\n".join(
|
||||
f"- [{n.get('time','')[:10]}] {n.get('title','')}" for n in news_items[:6]
|
||||
)
|
||||
|
||||
# 判断是否暴涨/暴跌
|
||||
move_desc = ""
|
||||
if abs(pct) >= 5:
|
||||
move_desc = f"该股{'暴涨' if pct > 0 else '暴跌'} {abs(pct):.2f}%({'接近/涨停' if pct >= 9.5 else '显著上涨' if pct > 0 else '接近/跌停' if pct <= -9.5 else '显著下跌'})"
|
||||
elif abs(pct) >= 2:
|
||||
move_desc = f"该股{'上涨' if pct > 0 else '下跌'} {abs(pct):.2f}%"
|
||||
else:
|
||||
move_desc = f"该股小幅变动 {pct:+.2f}%"
|
||||
|
||||
facts = f"""{name}({symbol}){period_cn}走势分析
|
||||
分析日期:{date_str}
|
||||
{move_desc}
|
||||
{tech_line}
|
||||
{trend_line}
|
||||
{ma_line}
|
||||
{news_block}
|
||||
消息面情绪:{rctx['tone']}
|
||||
{rctx['block'] or ''}"""
|
||||
|
||||
if llm.enabled():
|
||||
try:
|
||||
prompt = (
|
||||
f"请分析 {name}({symbol})在 {date_str} 前后{period_cn}的走势,"
|
||||
f"重点解释:① 为什么{'暴涨' if pct >= 5 else ('暴跌' if pct <= -5 else '出现此走势')}(从技术面、资金面、政策面、新闻事件等维度);"
|
||||
f"② 背后的主要驱动逻辑是什么;③ 后续需关注的信号或风险。250字以内,分点清晰。\n\n{facts}"
|
||||
)
|
||||
text = llm.ask(prompt, temperature=0.5, max_tokens=600)
|
||||
return {"ok": True, "source": "llm", "symbol": symbol, "name": name,
|
||||
"date": date_str, "period": period, "pct": pct, "facts": facts, "text": text}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 规则降级
|
||||
reasons = []
|
||||
if m:
|
||||
if m.ma_bull and pct > 0:
|
||||
reasons.append("均线多头排列,趋势向上")
|
||||
if m.vol_ratio >= 2 and pct > 0:
|
||||
reasons.append(f"成交量显著放大(量比{m.vol_ratio}),主力资金介入")
|
||||
if m.vol_ratio >= 2 and pct < 0:
|
||||
reasons.append(f"放量下跌(量比{m.vol_ratio}),资金出逃信号")
|
||||
if m.macd_gold and pct > 0:
|
||||
reasons.append("MACD金叉,动能转强")
|
||||
if m.rsi14 >= 80:
|
||||
reasons.append(f"RSI超买({m.rsi14}),注意回调风险")
|
||||
if m.rsi14 < 30:
|
||||
reasons.append(f"RSI超卖({m.rsi14}),存在超跌反弹机会")
|
||||
if m.pos60 >= 0.95 and pct > 0:
|
||||
reasons.append("突破60日新高,动量突破")
|
||||
if m.pos60 <= 0.1 and pct > 0:
|
||||
reasons.append("低位反弹,超跌修复")
|
||||
if rctx['tone'] == '利好':
|
||||
reasons.append("近期资讯面偏利好")
|
||||
elif rctx['tone'] == '利空':
|
||||
reasons.append("近期资讯面偏利空")
|
||||
if news_items:
|
||||
hot_news = news_items[0]['title'][:40]
|
||||
reasons.append(f"最新消息:{hot_news}…")
|
||||
if not reasons:
|
||||
reasons.append("暂无明确技术或消息面驱动,可能为市场情绪或板块联动")
|
||||
|
||||
text = (
|
||||
f"{name} 在 {date_str} {move_desc}。\n"
|
||||
f"主要原因分析:\n" +
|
||||
"\n".join(f"{i+1}. {r}" for i, r in enumerate(reasons)) +
|
||||
f"\n\n建议:{'关注量能是否持续配合,谨防高位回调。' if pct >= 5 else ('关注是否企稳止跌,底部确认前谨慎抄底。' if pct <= -5 else '走势相对平稳,跟踪板块动向。')}"
|
||||
f"\n{DISCLAIMER}"
|
||||
)
|
||||
return {"ok": True, "source": "rule", "symbol": symbol, "name": name,
|
||||
"date": date_str, "period": period, "pct": pct, "facts": facts, "text": text}
|
||||
|
||||
|
||||
# ============ 今日策略 ============
|
||||
def today_strategy():
|
||||
with get_session() as s:
|
||||
|
||||
Reference in New Issue
Block a user