f/scripts/backup_events.py
Greg Pomerantz bb8e39605e Event-history protection: backup dir, empty-download fallback, docs
Yahoo's 2026 event-feed change (capitalGain events dropped for some
funds; no events at all for terminated tickers) can wipe good event
history on re-download. Defense in depth:

- overrides/event-backup/: last-known-good copy of every dividend/
  capitalGain file (8,192 files, 58 MB); refresh with
  scripts/backup_events.py after each dump update
- data.py event_file(): frozen > data-root (while populated) > backup;
  used by panel reads, verify_official, and the double-listing scanner
- README: capital-gain files are mutual-fund-only in this dump; Yahoo
  has no LT/ST split (tax.py taxes capg at lt_rate; the per-fund split
  would come from fund-company annual tax statements or commercial feeds)
- tests: exact Timestamp .loc keys (pandas 3.x string matching returns a
  Series on large DatetimeIndex)
2026-08-31 21:59:25 -04:00

47 lines
1.6 KiB
Python

#!/usr/bin/env python3
"""Copy the dividend/capitalGain event files from the data root into
overrides/event-backup/ (the fallback data.py uses when a re-download
empties them — Yahoo dropped capitalGain events for some funds in 2026).
Re-runnable: it refreshes every file from the current data root, so run it
again whenever the dump is updated (or not — the backup is a floor, new
data still wins while it is non-empty). Symbols with a frozen copy are
skipped (overrides/frozen/ already preserves them).
Usage: python3 scripts/backup_events.py [--data DIR]
"""
import argparse
import shutil
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
BACKUP = ROOT / "overrides" / "event-backup"
FROZEN = ROOT / "overrides" / "frozen"
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--data", type=Path, default=Path("~/prog/fin/stocks").expanduser())
args = ap.parse_args()
BACKUP.mkdir(parents=True, exist_ok=True)
n = skipped = 0
for sfx in ("dividend", "capitalGain"):
for f in sorted(args.data.glob(f"*-{sfx}.csv")):
sym = f.name[: -len(f"-{sfx}.csv")]
if (FROZEN / f"{sym}-history.csv").exists():
skipped += 1
continue
# only files with real rows (a re-download may leave header-only)
if sum(1 for _ in open(f, errors="replace")) <= 1:
continue
shutil.copyfile(f, BACKUP / f.name)
n += 1
print(f"backed up {n} event files to {BACKUP}, "
f"skipped {skipped} frozen symbols")
return 0
if __name__ == "__main__":
sys.exit(main())