feat: 四维选股系统 + 组合评分逻辑

- stock_selector.py: 宏观+基本面+技术+消息 四维评分
- 核心改进: 技术超跌+基本面低估 = 逆向机会(不是卖点)
- 五粮液/茅台评分从负转正(+2, 逆向机会)
- 组合规则: 逆向机会/双杀/共振/常规 四种模式
- 四维_000858.json / 四维_600519.json 已生成

Phase5进展: 选股→回测→信号系统完成, 等MA20金叉信号
This commit is contained in:
小唯 A06 2026-07-12 01:49:40 +08:00
parent 62000ceca3
commit 9d10d4a96c
9 changed files with 703 additions and 0 deletions

459
scripts/stock_selector.py Normal file
View File

@ -0,0 +1,459 @@
#!/usr/bin/env python3
"""
小唯四维选股系统 宏观+基本面+技术+消息
========================================
综合四个维度筛选股票
维度说明:
1. 宏观面 美联储政策/人民币汇率/地缘风险
2. 基本面 PE/PB/业绩增速/行业景气度
3. 技术面 MA20趋势/成交量/均线排列
4. 消息面 政策利好/行业新闻/重大事件
评分规则:
- 宏观负面(-1)/中性(0)/正面(+1)
- 基本面低估(+1)/合理(0)/高估(-1)
- 技术面多头(+1)/空头(-1)/震荡(0)
- 消息面利好(+1)/利空(-1)/中性(0)
- 综合评分: 4分以上关注, 6分以上重点
用法:
python3 stock_selector.py scan # 扫描全市场
python3 stock_selector.py analyze <代码> # 分析单只股票
python3 stock_selector.py watchlist # 展示关注列表
"""
import json, sys, urllib.request, time
from datetime import datetime
from pathlib import Path
import numpy as np
import pandas as pd
OUTPUT = Path.home() / ".hermes" / "stock_backtest"
OUTPUT.mkdir(exist_ok=True)
FEISHU_WEBHOOK = "https://open.feishu.cn/open-apis/bot/v2/hook/446db983-e392-4d2c-bfb8-f9060e5df3ad"
# ===================== 飞书推送 =====================
def send_feishu(msg):
payload = json.dumps({"msg_type": "text", "content": {"text": msg}}).encode()
req = urllib.request.Request(FEISHU_WEBHOOK, data=payload,
headers={"Content-Type": "application/json"})
try:
with urllib.request.urlopen(req, timeout=10):
pass
except Exception:
pass
# ===================== 宏观面 — 汇率/利率/风险 =====================
def get_macro_score():
"""
宏观面评分:
考虑: 人民币汇率, 美联储政策, 地缘风险
返回: -1(负面) / 0(中性) / +1(正面)
"""
score = 0
reasons = []
# 人民币汇率
try:
url = "https://qt.gtimg.cn/q=usdcnh"
text = urllib.request.urlopen(url, timeout=5).read().decode("gbk")
parts = text.split("~")
if len(parts) > 10:
usdcnh = float(parts[3])
if usdcnh > 7.3:
score -= 1
reasons.append(f"离岸人民币破7.3({usdcnh}) → 外资流出压力 ⬇️")
else:
score += 1
reasons.append(f"人民币相对稳定({usdcnh}) → 外资流出压力小 ⬆️")
except Exception:
reasons.append("人民币汇率获取失败")
# A股风险偏好(用沪深300判断)
try:
url = "https://web.ifzq.gtimg.cn/appstock/app/fqkline/get?_var=kline_dayqfq&param=sh510300,day,2026-07-01,2026-07-12,10,qfq"
text = urllib.request.urlopen(url, timeout=5).read().decode("utf-8")
data = json.loads(text.replace("kline_dayqfq=", "", 1))
rows = data.get("data", {}).get("sh510300", {}).get("qfqday") or []
if len(rows) >= 5:
closes = [float(r[2]) for r in rows]
if closes[-1] > closes[0]:
score += 1
reasons.append("沪深300近期上涨 → 市场风险偏好上升 ⬆️")
else:
score -= 1
reasons.append("沪深300近期下跌 → 市场风险偏好下降 ⬇️")
except Exception:
pass
# 平均
avg_score = max(-1, min(1, score // max(1, len([r for r in reasons if r]))))
return avg_score, reasons
# ===================== 基本面 — PE/PB/业绩 =====================
def get_fundamental_score(code):
"""
基本面评分:
基于估值(PE/PB)和近期趋势
返回: -1(高估) / 0(合理) / +1(低估)
"""
try:
mc = f"sh{code}" if code.startswith("6") else f"sz{code}"
url = f"https://qt.gtimg.cn/q={mc}"
text = urllib.request.urlopen(url, timeout=5).read().decode("gbk")
parts = text.split("~")
if len(parts) < 40:
return 0, [], {}
pe = float(parts[39]) if parts[39] else 0
pb = float(parts[46]) if parts[46] else 0
price = float(parts[3])
yclose = float(parts[4])
score = 0
reasons = []
# PE判断
if 0 < pe < 15:
score += 1
reasons.append(f"PE={pe:.1f}(历史低位) → 估值有支撑 ⬆️")
elif 15 <= pe <= 30:
reasons.append(f"PE={pe:.1f}(合理区间) → 无明显低估/高估")
elif pe > 30:
score -= 1
reasons.append(f"PE={pe:.1f}(历史高位) → 估值偏高风险 ⬇️")
elif pe <= 0:
reasons.append(f"PE={pe}(亏损/无效) → 无法判断")
# PB判断
if 0 < pb < 3:
score += 1
reasons.append(f"PB={pb:.1f}(相对低估) → 净资产有支撑 ⬆️")
elif pb >= 3:
reasons.append(f"PB={pb:.1f}(相对高估)")
# 近期价格位置
try:
url2 = f"https://web.ifzq.gtimg.cn/appstock/app/fqkline/get?_var=kline_dayqfq&param={mc},day,2026-01-01,2026-07-12,200,qfq"
text2 = urllib.request.urlopen(url2, timeout=5).read().decode("utf-8")
data2 = json.loads(text2.replace("kline_dayqfq=", "", 1))
hist = data2.get("data", {}).get(mc, {}).get("qfqday") or []
if len(hist) >= 60:
highs = [float(r[3]) for r in hist]
low = min(highs)
high = max(highs)
pos = (price - low) / (high - low) if high > low else 0.5
if pos < 0.2:
score += 1
reasons.append(f"价格处于近半年低位({pos*100:.0f}%) → 相对安全边际 ⬆️")
elif pos > 0.8:
score -= 1
reasons.append(f"价格处于近半年高位({pos*100:.0f}%) → 追高风险 ⬇️")
except Exception:
pass
avg_score = max(-1, min(1, score))
info = {"pe": pe, "pb": pb, "price": price, "yclose": yclose}
return avg_score, reasons, info
except Exception as e:
return 0, [f"基本面数据获取失败({e})"], {}
# ===================== 技术面 — MA20趋势 =====================
def get_technical_score(code):
"""
技术面评分:
基于MA20均线状态
返回: -1(空头) / 0(震荡) / +1(多头)
"""
try:
mc = f"sh{code}" if code.startswith("6") else f"sz{code}"
end = datetime.now().strftime("%Y-%m-%d")
url = f"https://web.ifzq.gtimg.cn/appstock/app/fqkline/get?_var=kline_dayqfq&param={mc},day,2025-07-01,{end},500,qfq"
text = urllib.request.urlopen(url, timeout=8).read().decode("utf-8")
data = json.loads(text.replace("kline_dayqfq=", "", 1))
rows = data.get("data", {}).get(mc, {}).get("qfqday") or []
if len(rows) < 30:
return 0, ["数据不足"], {}
closes = [float(r[2]) for r in rows]
dates = [r[0] for r in rows]
closes_s = pd.Series(closes)
ma20 = closes_s.rolling(20).mean()
ma60 = closes_s.rolling(60).mean()
ma120 = closes_s.rolling(120).mean()
last_close = closes[-1]
last_ma20 = ma20.iloc[-1]
last_ma60 = ma60.iloc[-1]
last_ma120 = ma120.iloc[-1]
# 均线排列
if last_ma20 > last_ma60 > last_ma120:
ma_arrangement = "多头排列"
t_score = 1
elif last_ma20 < last_ma60 < last_ma120:
ma_arrangement = "空头排列"
t_score = -1
else:
ma_arrangement = "均线混乱"
t_score = 0
# 价格与MA20关系
pct_above = (last_close - last_ma20) / last_ma20 * 100
# 近期趋势
trend_20d = (closes[-1] - closes[-20]) / closes[-20] * 100 if len(closes) >= 20 else 0
reasons = [
f"{ma_arrangement}(价格{'' if last_close>last_ma20 else ''}{pct_above:+.1f}%vsMA20)",
f"近20日涨跌: {trend_20d:+.1f}%",
]
info = {
"price": last_close,
"ma20": last_ma20,
"ma60": last_ma60,
"ma120": last_ma120,
"ma_arrangement": ma_arrangement,
"pct_above_ma20": pct_above,
"trend_20d": trend_20d,
}
return t_score, reasons, info
except Exception as e:
return 0, [f"技术面数据获取失败({e})"], {}
# ===================== 消息面 — 行业/政策 =====================
def get_sentiment_score(code, name=""):
"""
消息面评分:
基于行业和政策(静态规则真实场景需接入新闻)
返回: -1(利空) / 0(中性) / +1(利好)
"""
score = 0
reasons = []
# 白酒行业规则
if name in ["五粮液", "贵州茅台", "泸州老窖", "洋河股份"] or code in ["000858", "600519", "000568", "002304"]:
# 白酒消费降级压制
score -= 1
reasons.append("白酒消费降级压制 ⬇️")
# 但估值低位有支撑
score += 1
reasons.append("白酒估值历史低位有支撑 ⬆️")
# 银行股
elif name in ["平安银行", "招商银行", "工商银行"] or code in ["000001", "600036", "601398"]:
score += 1
reasons.append("高股息防御配置价值 ⬆️")
score += 1
reasons.append("银行板块低估值 + 稳健 ⬆️")
# 宁德时代/新能源
elif "宁德" in name or code == "300750":
score -= 1
reasons.append("新能源产能过剩担忧 ⬇️")
score += 1
reasons.append("行业龙头有技术壁垒 ⬆️")
# 沪深300ETF
elif code in ["510300", "510100"]:
score += 1
reasons.append("ETF分散风险 + 低费率 ⬆️")
score += 1
reasons.append("反映大盘整体,宏观代理 ⬆️")
else:
reasons.append("消息面数据有限(待接入新闻源)")
avg_score = max(-1, min(1, score // 2))
return avg_score, reasons
# ===================== 综合评分 =====================
def analyze_stock(code, name=""):
"""对单只股票进行四维评分"""
print(f"\n{'='*60}")
print(f"四维选股分析: {name}({code})")
print(f"{'='*60}")
# 1. 宏观面
m_score, m_reasons = get_macro_score()
print(f"[宏观面] 评分: {m_score:+d}")
for r in m_reasons:
print(f" - {r}")
# 2. 基本面
f_score, f_reasons, f_info = get_fundamental_score(code)
print(f"[基本面] 评分: {f_score:+d}")
for r in f_reasons:
print(f" - {r}")
# 3. 技术面
t_score, t_reasons, t_info = get_technical_score(code)
print(f"[技术面] 评分: {t_score:+d}")
for r in t_reasons:
print(f" - {r}")
# 4. 消息面
s_score, s_reasons = get_sentiment_score(code, name)
print(f"[消息面] 评分: {s_score:+d}")
for r in s_reasons:
print(f" - {r}")
# ===========================================================
# 组合评分逻辑(修正简单相加的缺陷)
# ===========================================================
# 规则1: 技术面空头 + 基本面低估 = 买入机会(逆向)
# 规则2: 技术面空头 + 基本面高估 = 危险加倍(双杀)
# 规则3: 宏观负面时,技术面多头无法持续
# 规则4: 综合评分考虑方向匹配
f_strong = f_score >= 1 # 基本面好(低估)
f_weak = f_score <= -1 # 基本面差(高估)
t_bear = t_score <= -1 # 技术面空头(超跌)
t_bull = t_score >= 1 # 技术面多头
m_bad = m_score <= -1 # 宏观差
# 逆向机会: 技术面空头+基本面好 = 低估买入机会
if t_bear and f_strong:
adjusted_total = 2 # 视为关注机会
combo_reason = "逆向机会: 技术超跌+基本面低估"
# 双杀: 技术差+基本面也差
elif t_bear and f_weak:
adjusted_total = -2
combo_reason = "双杀风险: 技术超跌+基本面高估"
# 技术多头+基本面好 + 宏观不差
elif t_bull and f_strong and not m_bad:
adjusted_total = 3
combo_reason = "共振: 技术+基本面+宏观同向"
# 强势股(宏观好时)
elif t_bull and m_score >= 0:
adjusted_total = 2
combo_reason = "技术多头,宏观中性支撑"
# 常规计算
else:
adjusted_total = m_score + f_score + t_score + s_score
combo_reason = "常规评分"
total = adjusted_total
max_score = 4
print(f"\n组合逻辑: {combo_reason}")
print(f"\n{'='*60}")
print(f"综合评分: {total:+d} / {max_score} ({(total/max_score*100):.0f}%)")
if total >= 6:
verdict = "⭐⭐⭐ 重点关注 — 四维共振,强烈看多"
elif total >= 4:
verdict = "⭐⭐ 关注 — 多维度偏多"
elif total >= 2:
verdict = "⭐ 关注 — 逆向机会或温和看多"
elif total >= 0:
verdict = "⚠️ 观察 — 方向不明"
else:
verdict = "❌ 不碰 — 双杀风险"
print(f"结论: {verdict}")
print(f"{'='*60}")
# 保存
result = {
"code": code, "name": name, "date": datetime.now().strftime("%Y-%m-%d %H:%M"),
"scores": {"macro": m_score, "fundamental": f_score, "technical": t_score, "sentiment": s_score},
"total": total,
"verdict": verdict,
"macro_reasons": m_reasons,
"fundamental_reasons": f_reasons,
"fundamental_info": f_info,
"technical_reasons": t_reasons,
"technical_info": t_info,
"sentiment_reasons": s_reasons,
}
result_file = OUTPUT / f"四维_{code}.json"
with open(result_file, "w") as f:
json.dump(result, f, ensure_ascii=False, indent=2)
print(f"数据存: {result_file}")
return result
def scan_watchlist():
"""扫描关注列表"""
stocks = [
("000858", "五粮液"),
("600519", "贵州茅台"),
("000001", "平安银行"),
("300750", "宁德时代"),
("510300", "沪深300ETF"),
]
print(f"\n{'='*70}")
print(f"{'股票':<12} {'宏观':>5} {'基本面':>6} {'技术面':>6} {'消息面':>6} {'综合':>5} {'结论'}")
print(f"{'-'*70}")
results = []
for code, name in stocks:
r = analyze_stock(code, name)
results.append(r)
sc = r["scores"]
ts = r["total"]
print(f"{name:<12} {sc['macro']:>+4} {sc['fundamental']:>+5} {sc['technical']:>+5} {sc['sentiment']:>+5} {ts:>+4} {r['verdict'].split(' ')[0]}")
time.sleep(0.5) # 避免请求过快
print(f"{'='*70}")
# 汇总
print(f"\n📊 宏观面: {'负面 ⬇️' if results[0]['scores']['macro'] < 0 else '正面 ⬆️' if results[0]['scores']['macro'] > 0 else '中性'}")
good = [r for r in results if r["total"] >= 2]
print(f"可选股票({len(good)}只): {[r['name'] for r in good]}")
msg = f"""📊 四维选股扫描
宏观面: {'负面 ⬇️' if results[0]['scores']['macro'] < 0 else '正面 ⬆️'}
| 股票 | 宏观 | 基本面 | 技术 | 消息 | 综合 |
|------|------|--------|------|------|------|
"""
for r in results:
sc = r["scores"]
msg += f"| {r['name']} | {sc['macro']:>+2} | {sc['fundamental']:>+2} | {sc['technical']:>+2} | {sc['sentiment']:>+2} | **{r['total']:>+2}** |\n"
good = [r for r in results if r["total"] >= 2]
msg += f"\n可选股票({len(good)}): "
msg += " / ".join([r["name"] for r in good]) if good else ""
msg += f"\n\n生成: {datetime.now().strftime('%Y-%m-%d %H:%M')}"
send_feishu(msg)
if __name__ == "__main__":
if "--scan" in sys.argv or "scan" in sys.argv:
scan_watchlist()
elif "--watchlist" in sys.argv:
scan_watchlist()
elif len(sys.argv) >= 3 and sys.argv[1] == "analyze":
code = sys.argv[2]
name = sys.argv[3] if len(sys.argv) > 3 else code
analyze_stock(code, name)
elif len(sys.argv) >= 2 and sys.argv[1] not in ["--scan", "--watchlist"]:
code = sys.argv[1]
name = sys.argv[2] if len(sys.argv) > 2 else code
analyze_stock(code, name)
else:
print("用法:")
print(" python3 stock_selector.py --scan # 扫描关注列表")
print(" python3 stock_selector.py analyze <代码> [名称] # 分析单只")
print(" python3 stock_selector.py 000858 五粮液 # 同上简写")

View File

@ -0,0 +1,29 @@
{
"code": "000001",
"start": "2023-01-01",
"end": "2026-07-11",
"macd": {
"final": 120375.26999999996,
"ret": 20.375269999999958,
"buyhold": 16.811983009166102,
"max_dd": 15.17831825636983,
"trades": 17,
"winrate": 100.0
},
"ma_breakout": {
"final": 106590.07800000005,
"ret": 6.590078000000052,
"buyhold": 16.811983009166102,
"max_dd": 19.721143710032454,
"trades": 28,
"winrate": 100.0
},
"dual_ma": {
"final": 106071.49599999996,
"ret": 6.071495999999955,
"buyhold": 16.811983009166102,
"max_dd": 16.798085483555862,
"trades": 16,
"winrate": 100.0
}
}

View File

@ -0,0 +1,29 @@
{
"code": "000858",
"start": "2023-01-01",
"end": "2026-07-11",
"macd": {
"final": 76947.11599999995,
"ret": -23.05288400000005,
"buyhold": -39.213211578278774,
"max_dd": 44.878871690805,
"trades": 17,
"winrate": 100.0
},
"ma_breakout": {
"final": 101170.14899999999,
"ret": 1.1701489999999903,
"buyhold": -39.213211578278774,
"max_dd": 23.664447453495896,
"trades": 21,
"winrate": 100.0
},
"dual_ma": {
"final": 95861.20800000001,
"ret": -4.138791999999987,
"buyhold": -39.213211578278774,
"max_dd": 20.604195617002638,
"trades": 12,
"winrate": 100.0
}
}

View File

@ -0,0 +1,29 @@
{
"code": "300750",
"start": "2023-01-01",
"end": "2026-07-11",
"macd": {
"final": 150499.62499999994,
"ret": 50.49962499999994,
"buyhold": 98.90498460134594,
"max_dd": 34.88774617376622,
"trades": 19,
"winrate": 100.0
},
"ma_breakout": {
"final": 158262.061,
"ret": 58.26206099999999,
"buyhold": 98.90498460134594,
"max_dd": 28.559082676834624,
"trades": 26,
"winrate": 100.0
},
"dual_ma": {
"final": 154464.575,
"ret": 54.46457500000002,
"buyhold": 98.90498460134594,
"max_dd": 27.554417784937762,
"trades": 14,
"winrate": 100.0
}
}

View File

@ -0,0 +1,29 @@
{
"code": "510300",
"start": "2023-01-01",
"end": "2026-07-11",
"macd": {
"final": 115267.80799999999,
"ret": 15.267807999999992,
"buyhold": 36.2200282087447,
"max_dd": 14.997761342985108,
"trades": 21,
"winrate": 100.0
},
"ma_breakout": {
"final": 115447.9689999999,
"ret": 15.447968999999894,
"buyhold": 36.2200282087447,
"max_dd": 15.935361000063708,
"trades": 31,
"winrate": 100.0
},
"dual_ma": {
"final": 115366.04399999997,
"ret": 15.366043999999965,
"buyhold": 36.2200282087447,
"max_dd": 17.87071025720955,
"trades": 17,
"winrate": 100.0
}
}

View File

@ -0,0 +1,29 @@
{
"code": "600519",
"start": "2023-01-01",
"end": "2026-07-11",
"macd": {
"final": 81035.66900000004,
"ret": -18.964330999999962,
"buyhold": -13.778311415010839,
"max_dd": 32.61292831854593,
"trades": 19,
"winrate": 100.0
},
"ma_breakout": {
"final": 90402.06400000003,
"ret": -9.597935999999972,
"buyhold": -13.778311415010839,
"max_dd": 26.17300455046467,
"trades": 27,
"winrate": 100.0
},
"dual_ma": {
"final": 70685.70200000005,
"ret": -29.31429799999995,
"buyhold": -13.778311415010839,
"max_dd": 30.74843181679206,
"trades": 16,
"winrate": 100.0
}
}

View File

@ -0,0 +1,11 @@
{
"initial_cash": 100000,
"final_value": 85162.30799999999,
"strategy_return": -14.837692000000011,
"buyhold_return": -14.444494145563223,
"alpha": -0.3931978544367887,
"max_drawdown": 24.8571042432124,
"total_trades": 13,
"win_rate": 100.0,
"trades": "[('BUY', Timestamp('2025-03-06 00:00:00'), 70, 1426.399, 152.07000000000698), ('SELL', Timestamp('2025-03-26 00:00:00'), 70, 1496.419, 104901.40000000001), ('BUY', Timestamp('2025-05-08 00:00:00'), 69, 1498.609, 1497.3790000000154), ('SELL', Timestamp('2025-05-22 00:00:00'), 69, 1500.419, 105026.29000000002), ('BUY', Timestamp('2025-06-26 00:00:00'), 76, 1368.019, 1056.8460000000196), ('SELL', Timestamp('2025-08-01 00:00:00'), 76, 1365.019, 104798.29000000002), ('BUY', Timestamp('2025-08-19 00:00:00'), 75, 1386.019, 846.8650000000198), ('SELL', Timestamp('2025-09-18 00:00:00'), 75, 1415.979, 107045.29000000002), ('BUY', Timestamp('2025-10-16 00:00:00'), 74, 1432.929, 1008.544000000009), ('SELL', Timestamp('2025-10-28 00:00:00'), 74, 1393.019, 104091.95000000001), ('BUY', Timestamp('2025-11-10 00:00:00'), 73, 1410.319, 1138.663000000015), ('SELL', Timestamp('2025-11-26 00:00:00'), 73, 1397.169, 103132.00000000003), ('BUY', Timestamp('2025-12-17 00:00:00'), 74, 1381.119, 929.1940000000322), ('SELL', Timestamp('2025-12-31 00:00:00'), 74, 1349.156, 100766.73800000003), ('BUY', Timestamp('2026-01-05 00:00:00'), 72, 1397.976, 112.4660000000149), ('SELL', Timestamp('2026-01-15 00:00:00'), 72, 1360.866, 98094.81800000001), ('BUY', Timestamp('2026-01-29 00:00:00'), 69, 1409.696, 825.7940000000235), ('SELL', Timestamp('2026-02-26 00:00:00'), 69, 1438.186, 100060.62800000001), ('BUY', Timestamp('2026-03-17 00:00:00'), 68, 1456.976, 986.2600000000093), ('SELL', Timestamp('2026-03-24 00:00:00'), 68, 1379.306, 94779.06800000001), ('BUY', Timestamp('2026-03-31 00:00:00'), 66, 1421.976, 928.6520000000019), ('SELL', Timestamp('2026-04-17 00:00:00'), 66, 1379.216, 91956.908), ('BUY', Timestamp('2026-06-01 00:00:00'), 71, 1281.576, 965.0119999999879), ('SELL', Timestamp('2026-06-08 00:00:00'), 71, 1234.956, 88646.88799999998), ('BUY', Timestamp('2026-06-10 00:00:00'), 71, 1247.856, 49.11199999997916), ('SELL', Timestamp('2026-06-18 00:00:00'), 71, 1186.976, 84324.40799999998), ('BUY', Timestamp('2026-07-01 00:00:00'), 70, 1193.01, 813.7079999999842)]"
}

View File

@ -0,0 +1,44 @@
{
"code": "000858",
"name": "五粮液",
"date": "2026-07-12 01:49",
"scores": {
"macro": -1,
"fundamental": 1,
"technical": -1,
"sentiment": 0
},
"total": 2,
"verdict": "⭐ 关注 — 逆向机会或温和看多",
"macro_reasons": [
"沪深300近期下跌 → 市场风险偏好下降 ⬇️"
],
"fundamental_reasons": [
"PE=22.7(合理区间) → 无明显低估/高估",
"PB=2.2(相对低估) → 净资产有支撑 ⬆️",
"价格处于近半年低位(6%) → 相对安全边际 ⬆️"
],
"fundamental_info": {
"pe": 22.7,
"pb": 2.23,
"price": 73.69,
"yclose": 70.9
},
"technical_reasons": [
"空头排列(价格-1.4%vsMA20)",
"近20日涨跌: -7.8%"
],
"technical_info": {
"price": 73.69,
"ma20": 74.739,
"ma60": 85.086,
"ma120": 94.62158333333333,
"ma_arrangement": "空头排列",
"pct_above_ma20": -1.4035510242309992,
"trend_20d": -7.7952952952953
},
"sentiment_reasons": [
"白酒消费降级压制 ⬇️",
"白酒估值历史低位有支撑 ⬆️"
]
}

View File

@ -0,0 +1,44 @@
{
"code": "600519",
"name": "贵州茅台",
"date": "2026-07-12 01:49",
"scores": {
"macro": -1,
"fundamental": 1,
"technical": -1,
"sentiment": 0
},
"total": 2,
"verdict": "⭐ 关注 — 逆向机会或温和看多",
"macro_reasons": [
"沪深300近期下跌 → 市场风险偏好下降 ⬇️"
],
"fundamental_reasons": [
"PE=18.2(合理区间) → 无明显低估/高估",
"PB=6.5(相对高估)",
"价格处于近半年低位(4%) → 相对安全边际 ⬆️"
],
"fundamental_info": {
"pe": 18.21,
"pb": 6.47,
"price": 1204.98,
"yclose": 1182.19
},
"technical_reasons": [
"空头排列(价格+0.3%vsMA20)",
"近20日涨跌: -4.7%"
],
"technical_info": {
"price": 1204.98,
"ma20": 1201.3412,
"ma60": 1279.8285666666666,
"ma120": 1341.4840333333334,
"ma_arrangement": "空头排列",
"pct_above_ma20": 0.30289479791419344,
"trend_20d": -4.6607051585348644
},
"sentiment_reasons": [
"白酒消费降级压制 ⬇️",
"白酒估值历史低位有支撑 ⬆️"
]
}