#!/usr/bin/env python3 """ 小唯宏观指标自动化 — 真实数据驱动四维评分的宏观维度 ================================================== 获取上证/沪深300/创业板/原油,计算宏观趋势评分。 评分规则: 上证 20日涨幅 > 0 → +1(大盘向上) 上证 20日涨幅 < -3% → -1(大盘走弱) 沪深300 20日涨幅 > 0 → +1 原油 20日涨幅 > 10% → -1(通胀压力) 综合:sum 后 clip 到 [-1, 1] 用法: python3 stock_macro.py # 输出宏观评分 python3 stock_macro.py --json # JSON 输出(供矛盾周报引用) """ import json, sys, urllib.request from datetime import datetime, timedelta from pathlib import Path OUTPUT = Path.home() / ".hermes" / "stock_backtest" OUTPUT.mkdir(exist_ok=True) def get_url(url, timeout=8): import subprocess, shlex, os env = dict(os.environ) for k in ["http_proxy", "https_proxy", "HTTP_PROXY", "HTTPS_PROXY"]: env.pop(k, None) cmd = f"curl -s --max-time {timeout} --compressed {shlex.quote(url)}" try: r = subprocess.run(cmd, shell=True, capture_output=True, timeout=timeout + 2, env=env) return r.stdout.decode("utf-8", errors="ignore") except Exception: return "" def get_url_gbk(url, timeout=8): import subprocess, shlex, os env = dict(os.environ) for k in ["http_proxy", "https_proxy", "HTTP_PROXY", "HTTPS_PROXY"]: env.pop(k, None) cmd = f"curl -s --max-time {timeout} --compressed {shlex.quote(url)}" try: r = subprocess.run(cmd, shell=True, capture_output=True, timeout=timeout + 2, env=env) return r.stdout.decode("gbk", errors="ignore") except Exception: return "" def get_kline_change(symbol, days=20): """获取指数/品种过去 N 日涨幅""" today = datetime.now().strftime("%Y-%m-%d") start = (datetime.now() - timedelta(days=days * 2)).strftime("%Y-%m-%d") url = (f"https://web.ifzq.gtimg.cn/appstock/app/fqkline/get" f"?_var=kline_dayqfq¶m={symbol},day,{start},{today},{days},qfq") text = get_url(url) if not text or len(text) < 50: return None try: import re json_str = re.sub(r"^[^=]+=", "", text, count=1) data = json.loads(json_str) key = list(data.get("data", {}).keys()) if not key: return None raw = data["data"][key[0]].get("qfqday") or data["data"][key[0]].get("day") or [] if len(raw) < 2: return None closes = [float(r[2]) for r in raw if len(r) > 2 and float(r[2]) > 0] if len(closes) < 2: return None return (closes[-1] - closes[0]) / closes[0] * 100 except Exception: return None def get_macro_score(): """ 计算宏观评分 返回: {"macro": int, "details": {指标: 涨幅}, "reasons": [...]} """ # 上证指数 shanghai = get_kline_change("sh000001", days=20) # 沪深300(用 ETF 510300 代理) hs300 = get_kline_change("sh510300", days=20) # 创业板 chinext = get_kline_change("sz399006", days=20) # 原油 oil = get_kline_change("hf_OIL", days=20) score = 0 details = {} reasons = [] if shanghai is not None: details["上证20日"] = f"{shanghai:+.1f}%" if shanghai > 0: score += 1 reasons.append(f"上证20日{shanghai:+.1f}% → 大盘向上") elif shanghai < -3: score -= 1 reasons.append(f"上证20日{shanghai:+.1f}% → 大盘走弱") else: reasons.append(f"上证20日{shanghai:+.1f}% → 震荡") if hs300 is not None: details["沪深300"] = f"{hs300:+.1f}%" if hs300 > 0: score += 1 reasons.append(f"沪深300 {hs300:+.1f}% → 蓝筹强") elif hs300 < -3: score -= 1 reasons.append(f"沪深300 {hs300:+.1f}% → 蓝筹弱") if chinext is not None: details["创业板"] = f"{chinext:+.1f}%" if chinext < -5: score -= 1 reasons.append(f"创业板{chinext:+.1f}% → 成长弱") if oil is not None: details["原油20日"] = f"{oil:+.1f}%" if oil > 10: score -= 1 reasons.append(f"原油{oil:+.1f}% → 通胀压力") elif oil < -10: score += 1 reasons.append(f"原油{oil:+.1f}% → 通缩缓解") # clip 到 [-1, 1] macro = max(-1, min(1, score)) if macro == 0 and not reasons: macro = 0 reasons.append("宏观数据获取不足,中性") return {"macro": macro, "details": details, "reasons": reasons, "date": datetime.now().strftime("%Y-%m-%d")} def main(): result = get_macro_score() if "--json" in sys.argv: out = OUTPUT / "macro_score.json" with open(out, "w", encoding="utf-8") as f: json.dump(result, f, ensure_ascii=False, indent=2) print(json.dumps(result, ensure_ascii=False)) print(f"📁 已保存: {out}") return print("=" * 50) print(f"小唯宏观评分 {result['date']}") print("=" * 50) for k, v in result["details"].items(): print(f" {k}: {v}") print(f"\n宏观评分: {result['macro']:+d}") for r in result["reasons"]: print(f" • {r}") if __name__ == "__main__": main()