Initial extraction from monorepo

This commit is contained in:
Viktor Barzin 2026-05-07 17:06:19 +00:00
commit f7ef7ca4ab
56 changed files with 6163 additions and 0 deletions

View file

@ -0,0 +1,46 @@
"""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]