- edgar.py: SEC FTS + submissions API; strict cover-gate extraction (name in title position or (TICKER) on the cover; underlying-reference names like leveraged wrappers rejected); 4-pass fetch (ticker->CIK filings, name search, annual reports, ticker search); keyword category classifier. Returns None rather than a wrong fund's objective. - fundinfo.py CLI: curated -> cached -> EDGAR resolution into funds.json - funds_curated.json: human-verified objectives for 13 benchmark-pool funds (iShares/Vanguard family-trust classes the scraper can't reach) - tests: 26 checks incl. live EDGAR fetch of VTSAX
50 lines
1.4 KiB
Python
50 lines
1.4 KiB
Python
"""Benchmark discovery pool.
|
|
|
|
A copy of ~/prog/fin/benchmarks.txt (the user's curated benchmark universe),
|
|
augmented with a few common index ETFs that exist in the data universe.
|
|
The ORIGINAL file outside this repository is never modified.
|
|
|
|
Format: section header lines (ending in ':' or free-standing), then
|
|
'symbol<tab or 2+ spaces>label' lines.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
POOL_FILE = Path(__file__).parent / "pool" / "benchmarks.txt"
|
|
|
|
_ENTRY = re.compile(r"^(\S{1,6})[\t ]{2,}(.+)$")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PoolEntry:
|
|
section: str
|
|
symbol: str # lowercase
|
|
label: str
|
|
|
|
|
|
def load_pool(path: Path = POOL_FILE) -> list[PoolEntry]:
|
|
entries: list[PoolEntry] = []
|
|
section = ""
|
|
for line in path.read_text().splitlines():
|
|
line = line.rstrip()
|
|
if not line.strip():
|
|
continue
|
|
m = _ENTRY.match(line)
|
|
if m:
|
|
entries.append(PoolEntry(section, m.group(1).lower(),
|
|
m.group(2).strip()))
|
|
else:
|
|
section = line.strip().rstrip(":")
|
|
return entries
|
|
|
|
|
|
def pool_labels(entries: list[PoolEntry] | None = None) -> dict[str, str]:
|
|
"""symbol -> 'section: label' (for keyword matching and display)."""
|
|
if entries is None:
|
|
entries = load_pool()
|
|
return {e.symbol: f"{e.section}: {e.label}" for e in entries if e.section}
|