"""为存在价格断层的板块挑选替补:先抓日线,再做断层扫描 + 规模/成交额提示。 用法: py -3.14 -B labs/analysis/etf/pick_alt.py """ import json import sys import urllib.request from pathlib import Path HERE = Path(__file__).resolve().parent sys.path.insert(0, str(HERE)) try: sys.stdout.reconfigure(encoding="utf-8", errors="replace") except Exception: pass from backtest import CACHE, DAILY_URL # noqa: E402 # 板块 -> 需要考察的替补(按规模顺序) ALTS = { "CPO/通信": ["515050.SH", "159583.SZ", "159994.SZ", "159695.SZ"], "半导体": ["159516.SZ", "588170.SH", "159995.SZ", "159558.SZ", "512480.SH"], } def fetch(code: str) -> list[dict]: request = urllib.request.Request(f"{DAILY_URL}?code={code}", headers={"User-Agent": "Mozilla/5.0"}) with urllib.request.urlopen(request, timeout=30) as response: rows = json.load(response) CACHE.mkdir(parents=True, exist_ok=True) (CACHE / f"{code}.json").write_text(json.dumps(rows), encoding="utf-8") return sorted(rows, key=lambda r: str(r["trade_date"])) def gaps(rows: list[dict]) -> list[tuple[str, float]]: out = [] for prev, cur in zip(rows, rows[1:]): a, b = float(prev["close"]), float(cur["close"]) if a > 0 and abs(b / a - 1) * 100 > 15: out.append((str(cur["trade_date"]), (b / a - 1) * 100)) return out for sector, codes in ALTS.items(): print(f"\n== {sector} 替补 ==") for code in codes: try: rows = fetch(code) except Exception as exc: print(f" {code}: 抓取失败 {exc}") continue closes = [float(r["close"]) for r in rows] bad = gaps(rows) flag = "OK" if not bad else f"断层 {len(bad)} 处(最大 {max(abs(g[1]) for g in bad):.1f}%)" print(f" {code:11} 根数={len(rows):4} 最新={closes[-1]:7.3f} " f"区间={min(closes):6.3f}~{max(closes):6.3f} {flag}")