feat etf
This commit is contained in:
80
labs/analysis/etf/check_candidates.py
Normal file
80
labs/analysis/etf/check_candidates.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""校验候选 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 "存在数据不足的标的,需替换或接受其被跳过")
|
||||
Reference in New Issue
Block a user