"""Glide-path functions — stock-allocation as a function of years since retirement. Pfau & Kitces (2014) showed that *rising* equity glide paths (starting low and rising) reduce sequence-of-returns risk in the critical first decade of retirement. We default to that, with a classic static 60/40 also available. Each glide returns a fraction in [0, 1] for stock allocation — the remainder is bonds. """ from __future__ import annotations from collections.abc import Callable GlideFn = Callable[[int], float] def rising_equity(start: float = 0.30, end: float = 0.70, ramp_years: int = 15) -> GlideFn: """Linear interpolation from `start` to `end` over `ramp_years`, then constant at `end`.""" span = end - start def fn(year: int) -> float: if year >= ramp_years: return end return start + span * (year / ramp_years) return fn def static(allocation: float) -> GlideFn: """Constant allocation, e.g. 60/40 = static(0.60).""" return lambda _year: allocation GLIDE_PATHS: dict[str, GlideFn] = { "rising": rising_equity(), "static_60_40": static(0.60), } def get(name: str) -> GlideFn: if name not in GLIDE_PATHS: raise KeyError(f"Unknown glide path: {name!r}. Known: {sorted(GLIDE_PATHS)}") return GLIDE_PATHS[name]