"""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