智能荐股系统 v1.0.0:股票池/行情/新闻RAG/机构持仓/多因子评分/AI深度研报
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
荐股评分引擎:多因子打分模型(满分100)
|
||||
- 趋势 25分:均线多头排列 + 站上MA20
|
||||
- 动量 20分:5日涨幅区间映射
|
||||
- 技术 15分:RSI健康区间 / 超买超卖
|
||||
- 量能 10分:量比
|
||||
- 消息 15分:近7日相关新闻情感均值(RAG 信号)
|
||||
- 机构 15分:近30日评级上调 + 基金持仓环比增持
|
||||
|
||||
评级:>=82 强烈推荐 / >=68 推荐 / >=55 关注 / <55 观望
|
||||
"""
|
||||
RATINGS = [
|
||||
(82, "强烈推荐"),
|
||||
(68, "推荐"),
|
||||
(55, "关注"),
|
||||
(-1e9, "观望"),
|
||||
]
|
||||
|
||||
|
||||
def _rating(score):
|
||||
for threshold, name in RATINGS:
|
||||
if score >= threshold:
|
||||
return name
|
||||
return "观望"
|
||||
|
||||
|
||||
def _bracket(v, cuts):
|
||||
"""v 落在 [val, 分数] 区间的第一个匹配"""
|
||||
for hi, lo, score in cuts:
|
||||
if hi is None or v <= hi:
|
||||
if v >= lo:
|
||||
return score
|
||||
return 0
|
||||
|
||||
|
||||
def score_stock(ind, news_score, inst_score):
|
||||
"""ind: indicators dict;news_score: -1~1(无新闻用0);inst_score: 0~1 归一化机构热度"""
|
||||
s = {}
|
||||
|
||||
# 1. 趋势 25
|
||||
if ind.get("trend_bull"):
|
||||
s["trend"] = 25
|
||||
elif ind.get("trend_partial"):
|
||||
s["trend"] = 17
|
||||
else:
|
||||
s["trend"] = 8
|
||||
ma20 = ind.get("ma20")
|
||||
if ma20 and ind.get("close", 0) >= ma20:
|
||||
s["trend"] = min(25, s["trend"] + 4)
|
||||
|
||||
# 2. 动量 20(5日涨幅)
|
||||
chg5 = ind.get("chg_5d", 0)
|
||||
if chg5 > 12:
|
||||
s["momentum"] = 16 # 过急,扣分防追高
|
||||
elif chg5 > 6:
|
||||
s["momentum"] = 20
|
||||
elif chg5 > 2:
|
||||
s["momentum"] = 15
|
||||
elif chg5 > -2:
|
||||
s["momentum"] = 10
|
||||
elif chg5 > -6:
|
||||
s["momentum"] = 6
|
||||
else:
|
||||
s["momentum"] = 3
|
||||
|
||||
# 3. 技术 15(RSI)
|
||||
rsi = ind.get("rsi", 50)
|
||||
if 50 <= rsi <= 68:
|
||||
s["technical"] = 15
|
||||
elif 40 <= rsi < 50:
|
||||
s["technical"] = 11
|
||||
elif 68 < rsi <= 80:
|
||||
s["technical"] = 8 # 接近超买
|
||||
elif rsi < 35:
|
||||
s["technical"] = 9 # 超卖修复机会
|
||||
else:
|
||||
s["technical"] = 5
|
||||
|
||||
# 4. 量能 10(量比)
|
||||
vr = ind.get("vol_ratio", 1.0)
|
||||
if vr >= 2.0:
|
||||
s["volume"] = 10
|
||||
elif vr >= 1.3:
|
||||
s["volume"] = 8
|
||||
elif vr >= 0.8:
|
||||
s["volume"] = 6
|
||||
else:
|
||||
s["volume"] = 4
|
||||
|
||||
# 5. 消息 15
|
||||
s["news"] = round(max(0, min(15, (news_score + 1) / 2 * 15)), 1)
|
||||
|
||||
# 6. 机构 15
|
||||
s["institutional"] = round(inst_score * 15, 1)
|
||||
|
||||
total = round(sum(s.values()), 1)
|
||||
return {
|
||||
"total": total,
|
||||
"trend": s["trend"],
|
||||
"momentum": s["momentum"],
|
||||
"technical": s["technical"],
|
||||
"volume": s["volume"],
|
||||
"news": s["news"],
|
||||
"institutional": s["institutional"],
|
||||
"rating": _rating(total),
|
||||
"score_parts": s,
|
||||
}
|
||||
|
||||
|
||||
def build_reasons(ind, news_score, inst_up, hold_up):
|
||||
"""生成规则化推荐理由(供列表直接展示,无需 LLM)"""
|
||||
reasons = []
|
||||
if ind.get("trend_bull"):
|
||||
reasons.append("均线呈多头排列,中短期趋势向上")
|
||||
elif ind.get("close", 0) >= (ind.get("ma20") or 0):
|
||||
reasons.append("股价站上20日均线,趋势转强")
|
||||
else:
|
||||
reasons.append("均线空头排列,趋势偏弱,注意风险")
|
||||
|
||||
chg5 = ind.get("chg_5d", 0)
|
||||
if chg5 >= 6:
|
||||
reasons.append(f"5日涨幅{chg5:+.1f}%,动量强劲")
|
||||
elif chg5 < -6:
|
||||
reasons.append(f"5日跌幅{chg5:+.1f}%,弱势调整")
|
||||
else:
|
||||
reasons.append(f"5日涨跌{chg5:+.1f}%,动量平稳")
|
||||
|
||||
rsi = ind.get("rsi", 50)
|
||||
if rsi >= 70:
|
||||
reasons.append(f"RSI {rsi:.0f} 超买,短线回调风险增大")
|
||||
elif rsi <= 30:
|
||||
reasons.append(f"RSI {rsi:.0f} 超卖,存在修复反弹机会")
|
||||
|
||||
vr = ind.get("vol_ratio", 1.0)
|
||||
if vr >= 1.5:
|
||||
reasons.append(f"量比{vr:.2f},放量明显,资金活跃")
|
||||
elif vr < 0.7:
|
||||
reasons.append(f"量比{vr:.2f},缩量整理")
|
||||
|
||||
if news_score > 0.25:
|
||||
reasons.append("近期相关消息面偏正面,情绪回暖")
|
||||
elif news_score < -0.25:
|
||||
reasons.append("近期消息面偏空,注意利空扰动")
|
||||
|
||||
if inst_up > 0:
|
||||
reasons.append(f"近30日{inst_up}家机构给出正面评级")
|
||||
if hold_up > 0:
|
||||
reasons.append("基金最新季度环比增持")
|
||||
return reasons[:4]
|
||||
Reference in New Issue
Block a user