feat: 四维选股系统 + 组合评分逻辑
- stock_selector.py: 宏观+基本面+技术+消息 四维评分
- 核心改进: 技术超跌+基本面低估 = 逆向机会(不是卖点)
- 五粮液/茅台评分从负转正(+2, ⭐逆向机会)
- 组合规则: 逆向机会/双杀/共振/常规 四种模式
- 四维_000858.json / 四维_600519.json 已生成
Phase5进展: 选股→回测→信号系统完成, 等MA20金叉信号
This commit is contained in:
parent
62000ceca3
commit
9d10d4a96c
|
|
@ -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¶m=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¶m={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¶m={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 五粮液 # 同上简写")
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)]"
|
||||
}
|
||||
|
|
@ -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": [
|
||||
"白酒消费降级压制 ⬇️",
|
||||
"白酒估值历史低位有支撑 ⬆️"
|
||||
]
|
||||
}
|
||||
|
|
@ -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": [
|
||||
"白酒消费降级压制 ⬇️",
|
||||
"白酒估值历史低位有支撑 ⬆️"
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue