fire-planner/fire_planner/glide_path.py

47 lines
1.3 KiB
Python
Raw Permalink Normal View History

2026-05-07 17:06:19 +00:00
"""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]