38 lines
1.1 KiB
Python
38 lines
1.1 KiB
Python
|
|
"""Withdrawal-strategy abstract base.
|
||
|
|
|
||
|
|
All strategies operate in REAL GBP terms — the simulator deflates by
|
||
|
|
the cumulative CPI index before calling. Brackets inside the tax
|
||
|
|
engines are also assumed to inflate with CPI (simplifying assumption
|
||
|
|
that tax thresholds keep pace with inflation — fiscal drag is a
|
||
|
|
documented v2 follow-up).
|
||
|
|
"""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from abc import ABC, abstractmethod
|
||
|
|
from dataclasses import dataclass
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass(frozen=True)
|
||
|
|
class StrategyState:
|
||
|
|
"""Inputs to a strategy's per-year decision. Real GBP throughout."""
|
||
|
|
portfolio: float
|
||
|
|
initial_portfolio: float
|
||
|
|
initial_withdrawal: float
|
||
|
|
year_idx: int
|
||
|
|
horizon_years: int
|
||
|
|
last_withdrawal: float
|
||
|
|
expected_real_return: float = 0.04
|
||
|
|
|
||
|
|
|
||
|
|
class WithdrawalStrategy(ABC):
|
||
|
|
name: str
|
||
|
|
|
||
|
|
@abstractmethod
|
||
|
|
def propose_withdrawal(self, state: StrategyState) -> float:
|
||
|
|
"""Return the proposed withdrawal in real GBP for this year.
|
||
|
|
|
||
|
|
The simulator may clip downward if the portfolio is exhausted —
|
||
|
|
strategies can request more than the portfolio holds.
|
||
|
|
"""
|
||
|
|
raise NotImplementedError
|