"""校验候选 ETF 在策略数据源上有足够日线(策略要求 >=61 根,这里按最新 120 根缓存)。 用法: py -3.14 -B labs/analysis/etf/check_candidates.py """ import json import sys import urllib.request from datetime import datetime, date 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 DAILY_URL, CACHE # noqa: E402 CANDIDATES = { "AI": "159819.SZ", "CPO": "515880.SH", "PCB": "159732.SZ", "人形机器人": "562500.SH", "光": "515790.SH", "创新药": "159992.SZ", "半导体": "588200.SH", "存储": "588750.SH", "半导体材料": "588160.SH", "玻璃基板": "159745.SZ", "电力": "159326.SZ", "航空航天": "159227.SZ", "金属": "518880.SH", "金融": "512880.SH", "能源": "515220.SH", "宽基(保留)": "510300.SH", } def probe(code: str) -> 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) if not isinstance(rows, list) or not rows: return {"bars": 0} CACHE.mkdir(parents=True, exist_ok=True) (CACHE / f"{code}.json").write_text(json.dumps(rows), encoding="utf-8") bars = sorted(rows, key=lambda r: str(r["trade_date"])) closes = [float(b["close"]) for b in bars] return { "bars": len(bars), "first": str(bars[0]["trade_date"]), "last": str(bars[-1]["trade_date"]), "close": closes[-1], "min": min(closes), "max": max(closes), } print(f"{'板块':12} {'代码':11} {'根数':>5} {'首日':>9} {'末日':>9} {'最新价':>8} {'区间低':>8} {'区间高':>8} 判定") ok = True for sector, code in CANDIDATES.items(): try: info = probe(code) except Exception as exc: print(f"{sector:12} {code:11} 抓取失败:{exc}") ok = False continue bars = info.get("bars", 0) verdict = "OK" if bars >= 61 else ("不足 61 根 → 策略会跳过" if bars else "无数据 → 策略会跳过") if bars < 61: ok = False print(f"{sector:12} {code:11} {bars:5} {str(info.get('first','-')):>9} {str(info.get('last','-')):>9} " f"{info.get('close', 0):8.3f} {info.get('min', 0):8.3f} {info.get('max', 0):8.3f} {verdict}") print() print("全部可用" if ok else "存在数据不足的标的,需替换或接受其被跳过")