"""在指定关键词范围内,按"最近 120 根无价格断层 + 规模/成交额"挑选 ETF。 用法: py -3.14 -B labs/analysis/etf/pick_sector.py 通信 py -3.14 -B labs/analysis/etf/pick_sector.py 半导体 芯片 """ import json import sys import time import urllib.parse 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 from screen_etf import fetch_all, code_of, size_yi, turnover_yi # noqa: E402 def window_clean(code: str) -> tuple[bool, float, str]: """最近 120 根内最大单日跳空;<=15% 视为干净。""" path = CACHE / f"{code}.json" try: if path.exists(): rows = json.loads(path.read_text(encoding="utf-8")) else: 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) path.write_text(json.dumps(rows), encoding="utf-8") except Exception as exc: return False, 999.0, f"抓取失败:{exc}" rows = sorted(rows, key=lambda r: str(r["trade_date"]))[-120:] worst, day = 0.0, "" 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 > worst: worst, day = abs(b / a - 1) * 100, str(cur["trade_date"]) return worst <= 15, worst, day def main() -> int: keywords = sys.argv[1:] or ["通信"] wanted = {c: r for c, r in ((code_of(r), r) for r in fetch_all()) if any(k in str(r.get("f14") or "") for k in keywords)} rows = sorted(wanted.values(), key=lambda r: -size_yi(r)) print(f"关键词 {keywords}:命中 {len(rows)} 只,按规模前 12 只做断层检查") print(f"{'代码':11} {'名称':26} {'规模(亿)':>9} {'成交额(亿)':>10} {'窗口跳空':>9} 判定") for row in rows[:12]: code = code_of(row) clean, worst, day = window_clean(code) time.sleep(0.2) print(f"{code:11} {str(row.get('f14')):26} {size_yi(row):9.2f} {turnover_yi(row):10.3f} " f"{worst:8.1f}% {'可用' if clean else '有断层(' + day + ')'}") return 0 if __name__ == "__main__": raise SystemExit(main())