This commit is contained in:
2026-09-21 12:38:13 +08:00
parent bcacbed427
commit ed6d4ee7a5
14 changed files with 2458 additions and 1073 deletions

View File

@@ -0,0 +1,67 @@
"""在指定关键词范围内,按"最近 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())