Skip to content

CGT band-aware calc + realisation hook; pensions tax relief & annual allowance#79

Open
vahid-ahmadi wants to merge 1 commit into
mainfrom
vahid/cgt-pensions-tax
Open

CGT band-aware calc + realisation hook; pensions tax relief & annual allowance#79
vahid-ahmadi wants to merge 1 commit into
mainfrom
vahid/cgt-pensions-tax

Conversation

@vahid-ahmadi

Copy link
Copy Markdown
Contributor

Addresses #45.

Scope

Capital gains tax (TCGA 1992; FA 2024 s.7)

  • New band-aware calculate_capital_gains_tax_banded: gains (after the £3,000 AEA) stack on top of adjusted_net_income; the slice within the remaining basic-rate band is taxed at the basic CGT rate (18%) and the rest at the higher rate (24%), matching PolicyEngine-UK capital_gains_tax. Replaces the previous all-or-nothing higher/basic split in simulation Phase 2c.
  • Reuses the existing residential-surcharge work (applied pro-rata across both slices).
  • Behavioural realisation response: new optional cgt_response parameter (CgtResponseParams { elasticity, baseline_rate }). apply_cgt_realisation_response scales realised gains by (reform_rate / baseline_rate) ^ elasticity before tax — mirroring PolicyEngine-UK's log-difference capital_gains_behavioural_response.
  • The original calculate_capital_gains_tax(person, params, is_higher_rate) form is kept unchanged (still used by the residential-surcharge benchmark).

Pensions tax (FA 2004 Part 4) — new optional pensions parameter (PensionsParams)

  • Relief at source vs net pay: net pay reduces taxable earned income directly; relief at source extends the basic-rate band by the grossed-up contribution (gross_up_contribution, extend_basic_rate_band) to deliver higher-rate relief.
  • Annual allowance + taper (tapered_annual_allowance): £60,000, reduced £1-per-£2 above £260,000 adjusted income down to a £10,000 minimum.
  • Annual-allowance charge (annual_allowance_charge): excess contributions taxed at the member's marginal rate, applied in income tax.

Parameters: 2025/26 values added for pensions and cgt_response. Legislative citations added (new §13A CGT, §13B Pensions Tax). Changelog fragment added.

Tests

cargo build and cargo test pass (204 tests). New worked-example unit tests:

  • CGT: all-basic, basic/higher split (£20k gain at £45k income → £3,763.80), all-higher, residential surcharge in the higher band, and the realisation response (no-op without params, reduction when the rate rises vs baseline).
  • Pensions: net-pay reduces taxable income; relief-at-source extends the band (£60k income + £8k RAS contribution → all at 20%) and does not reduce taxable income; tapered allowance and annual-allowance charge worked examples.

Remaining

  • The CGT realisation response is a documented hook: it is a no-op until a reform sets cgt_response.baseline_rate, because the engine has no automatic baseline-vs-reform CGT plumbing threaded through Simulation::new yet (cf. baseline_old_sp_weekly for state pension). The static CGT calc is fully implemented.
  • Pension annual-allowance carry-forward of unused prior-year allowance is not modelled (the FRS does not record historical contributions), which can overstate the charge.

🤖 Generated with Claude Code

…nual allowance

Port a coherent slice of HMRC capital gains tax and pensions tax (issue #45).

CGT (TCGA 1992): new band-aware calculate_capital_gains_tax_banded stacks gains
on top of adjusted net income, taxing the slice within the remaining basic-rate
band at the basic rate and the rest at the higher rate (matching PolicyEngine-UK),
reusing the residential surcharge. Wired into Phase 2c. A configurable realisation
behavioural response (CgtResponseParams) scales gains by
(reform_rate/baseline_rate)^elasticity as the documented hook; static calc complete.

Pensions (FA 2004 Part 4): new PensionsParams. Net pay reduces taxable income;
relief at source extends the basic-rate band by the grossed-up contribution.
Tapered annual allowance and the annual-allowance charge are applied in income tax.
Carry-forward not modelled (documented simplification).

2025/26 parameters set for pensions and cgt_response. Unit tests cover worked
examples for each. cargo build and cargo test pass (204 tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant