功能细节优化

This commit is contained in:
2026-06-15 01:26:39 +08:00
parent e524a3589a
commit 964c17c200
33 changed files with 6990 additions and 210 deletions

View File

@@ -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: