SPB Git forge

spb/hfmarketdata

Public

Open high-frequency market data platform — FirstRate full-history downloader, DuckDB/Parquet lake, open REST API and React docs platform (www.hfmarketdata.io)

127commits 1branches 0releases
24.7 MBsize
maindefault branch
11 days agolast push
JavaScript 53.7% Python 38.3% CSS 4.6% TypeScript 3.1%
3.6 KB · 93 lines python
Raw Blame History
1#!/usr/bin/env python32"""Build the downloadable skills pack.341. Copies `skills/_shared/hfmd.py` into every `skills/hfmd-*/scripts/hfmd.py` (self-contained skills).52. Zips the five skill folders (+ a top-level INSTALL.md) into6   `hfmarketdata/web/public/downloads/hfmarketdata-skills.zip`, deterministically (fixed timestamps,7   sorted entries) so re-running without changes yields an identical file.89Usage: python3 skills/scripts/build_skills.py [--check] [--out PATH]10"""11from __future__ import annotations1213import argparse14import shutil15import sys16import zipfile17from pathlib import Path1819ROOT = Path(__file__).resolve().parents[2]20SKILLS = ROOT / "skills"21SHARED = SKILLS / "_shared" / "hfmd.py"22OUT = ROOT / "hfmarketdata" / "web" / "public" / "downloads" / "hfmarketdata-skills.zip"23FIXED_TIME = (2026, 1, 1, 0, 0, 0)2425INSTALL = """# HF Market Data skills — install2627Unzip into `~/.claude/skills/` (all projects) or `.claude/skills/` (this project only):2829    unzip -o hfmarketdata-skills.zip -d ~/.claude/skills/3031Requirements: Python 3.10+, `pip install requests pandas` (+ `matplotlib` for charts).32Optional: `export HFMD_API_KEY=hfmd_live_…` (free API key → 120 requests/min; keyless mode has low hourly limits, 30/h).33Everything is free; higher limits are granted on request by e-mail to contact@spboucher.ai (also free). See https://www.hfmarketdata.io/limits3435Skills: hfmd-data-analysis · hfmd-quick-backtest · hfmd-continuous-futures · hfmd-fundamentals-screen · hfmd-term-structure36Docs: https://www.hfmarketdata.io/integrations/skills37"""383940def skill_dirs() -> list[Path]:41    return sorted(p for p in SKILLS.iterdir() if p.is_dir() and p.name.startswith("hfmd-") and (p / "SKILL.md").exists())424344def sync_shared(check: bool) -> int:45    drift = 046    src = SHARED.read_bytes()47    for d in skill_dirs():48        dst = d / "scripts" / "hfmd.py"49        if dst.exists() and dst.read_bytes() == src:50            continue51        if check:52            print(f"DRIFT: {dst.relative_to(ROOT)} differs from _shared/hfmd.py", file=sys.stderr)53            drift += 154        else:55            dst.parent.mkdir(parents=True, exist_ok=True)56            shutil.copyfile(SHARED, dst)57            print(f"synced {dst.relative_to(ROOT)}")58    return drift596061def build_zip(out: Path) -> None:62    out.parent.mkdir(parents=True, exist_ok=True)63    entries: list[tuple[str, bytes]] = [("INSTALL.md", INSTALL.encode())]64    for d in skill_dirs():65        for f in sorted(d.rglob("*")):66            if f.is_dir() or "__pycache__" in f.parts or f.suffix in {".pyc", ".png", ".csv"}:67                continue68            entries.append((str(f.relative_to(SKILLS)), f.read_bytes()))69    with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as z:70        for name, data in entries:71            info = zipfile.ZipInfo(name, date_time=FIXED_TIME)72            info.compress_type = zipfile.ZIP_DEFLATED73            info.external_attr = (0o755 if name.endswith(".py") else 0o644) << 1674            z.writestr(info, data)75    print(f"wrote {out.relative_to(ROOT)} ({out.stat().st_size:,} bytes, {len(entries)} files, {len(skill_dirs())} skills)")767778def main() -> int:79    ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)80    ap.add_argument("--check", action="store_true", help="only verify the copies of hfmd.py are in sync")81    ap.add_argument("--out", type=Path, default=OUT)82    a = ap.parse_args()83    drift = sync_shared(a.check)84    if a.check:85        print("in sync" if not drift else f"{drift} file(s) out of sync")86        return 1 if drift else 087    build_zip(a.out)88    return 0899091if __name__ == "__main__":92    sys.exit(main())93