"""Saved portfolio definitions, persisted to portfolios.json next to the app. A portfolio is fully defined by its symbol/weight spec, rebalance scheme and cost. Tax rates and the analysis period are global (sidebar), not part of the saved definition. """ from __future__ import annotations import json from dataclasses import asdict, dataclass from pathlib import Path FILE = Path(__file__).parent / "portfolios.json" @dataclass class Portfolio: name: str spec: str # "sym1:0.5, sym2:0.5" (parsed by portfolio.parse_weights) scheme: str | None # rebalance freq alias ("1W", "1ME", "QE", "YE") or None cost_bps: float = 0.0 def load_all() -> list[Portfolio]: if not FILE.exists(): return [] try: return [Portfolio(**d) for d in json.loads(FILE.read_text())] except Exception: return [] def save(p: Portfolio) -> None: all_p = {q.name: q for q in load_all()} all_p[p.name] = p FILE.write_text(json.dumps([asdict(q) for q in all_p.values()], indent=2)) def delete(name: str) -> None: all_p = [q for q in load_all() if q.name != name] FILE.write_text(json.dumps([asdict(q) for q in all_p], indent=2))