Repository navigation
Stack gains on taxable income when splitting the higher and additional CGT bands - #2433
vahid-ahmadi wants to merge 1 commit into
Conversation
…l CGT bands The split of gains above the basic rate band gave every gainer the whole higher band (higher rate limit less basic rate limit), never subtracting taxable income already above the basic rate limit. Gains are the top slice of income (TCGA 1992 s. 1H), so the band left for them is the higher rate limit less taxable income or the basic rate limit, whichever is higher. Current law charges the higher and additional CGT rates alike, so only reforms that set them apart change; current-law results move by float32 rounding where gains straddle the new split point. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
juaristi22
left a comment
There was a problem hiding this comment.
Reviewed 54fa62d64d28a976df2ac6cc3ce5e79a38d53a5d. The new remaining-band expression matches the declared third-band reform convention in isolation, but it exposes one material interaction with the annual exemption.
C1 — Allocate the exemption at the rates actually reached after stacking. At capital_gains_tax.py:75–77 (consumed at line 179), the higher-band capacity becomes zero for a person already above the higher limit. The unchanged exemption ordering at lines 141–151 still compares only each schedule’s higher_rate, so it can exempt the wrong gains.
Reproducer: one adult aged 45 in 2025, employment income £200,000, total gains £50,000, of which residential-property gains are £25,000; reform only gov.hmrc.cgt.additional_rate to 0.45. No intermediate tax, allowance or eligibility inputs are supplied, and behavioural elasticities retain their zero defaults.
Head charges £16,530 CGT. It assigns the £3,000 exemption to residential gains at 24%, while the remaining main gains are now charged at 45%. Assigning the exemption to main gains instead gives £15,900: £25,000 × 24% + £22,000 × 45%. The resulting £630 excess reaches household tax and net income. An independent same-head simulation using only the exact captured-base CGT formula gives £11,280: under the old whole-higher-band allocation all £47,000 taxable gains in this case remained at 24%, so exemption placement was immaterial. This demonstrates why the changed split exposes the interaction.
TCGA 1992 s.1K(5) preserves the most-beneficial exemption allocation, irrespective of the otherwise applicable rate. The third rate remains a hypothetical reform hook; this finding does not claim it is an enacted general CGT rate. Please preserve beneficial exemption allocation under the new stacked bands and add a mixed-schedule regression. The new YAML cases each use only one taxable gain schedule.
Validation: 243 repository checks and 11 independent diagnostics passed; the added mixed-gain exemption regression failed at £16,530 versus £15,900. One private microsimulation test was deselected. Independent boundary, Gift Aid/pension extension, four-schedule conservation and final household-income checks passed. A 384-household synthetic current-law comparison against the captured-base formula found only float32 rounding differences (maximum CGT £0.0078125, household tax £0.015625; final net income unchanged). Nine official source originals checked. The Axiom n/a explanation and required reproducers/changelog are adequate.
At capture and the mid-review check, four hosted Python shards were still running; other reported checks passed. Review status: COMPLETE. Review severity: REQUEST_CHANGES. Confirmed critical count: 1.
Fixes #2432
What and why
capital_gains_taxsplit gains above the basic rate band between the higher and additional CGT bands using the whole higher band (higher_rate_limit - basic_rate_limit, £87,440 in 2025-26) for every gainer. It never subtracted taxable income already above the basic rate limit, so a reform that sets the CGT higher and additional rates apart charged someone with £200,000 of income at the higher rate on their gains.Gains are the top slice of income (TCGA 1992 ss. 1H(3), 1I). In law CGT has only two rates, 18% and 24%, and the model's
additional_rateparameters exist only so reforms can mirror the income tax additional-rate band. For that split to mirror income tax, the gains above the basic rate band start at taxable income or the basic rate limit, whichever is higher:The main, residential property, carried interest and relief schedules share one higher-band loop, so the fix covers all four. No values are hard-coded.
£200,000 of earnings and £50,000 of gains, 2025-26:
Current law
The higher and additional CGT rates are equal at every date, so current-law CGT is unchanged apart from float32 rounding where gains now straddle the moved split point. Two frozen expectations were updated for this:
test_gift_aid.py::test_zero_gift_preserves_main_outputs_bit_for_bit: one row (£101,000 earnings, £40,000 gains) moves from 8880 to 8879.9990234375.test_capital_gains_tax_components.py::legacy_capital_gains_tax: the reference formula behind the bit-identity tests now uses the stacked higher band, so it still matches the new formula bit for bit.TestEqualisationReformshard-coded the buggy figure (£39,278: 87,440 at 40% and 9,560 at 45% on £87,430 of taxable income). Its expectation is now £41,764.50: 37,710 at 40% and 59,290 at 45%.Interaction with #2419
#2419 (default CGT realisation response) answers the rate on the last pound of gains, so the wrong band also drives the wrong response there. Its docs example (£100,000 earnings, £200,000 gains, higher rate raised to 45%) quotes tax rising from £47,280 to £65,642 with no response. After this fix it rises to about £55,199: 37,710 at 45% and 159,290 at 24%. Its other example, with both rates at 45%, does not change.
Test plan
capital_gains_tax_rate_bands.yaml(10 cases, hand-computed): income below the basic rate limit, between the limits and above the higher rate limit under an additional-rate reform; higher-rate reforms; residential property and carried interest additional-rate reforms; current law; and the earnings-based example above. 7 fail on main and all pass here. The 3 that pass on main are the two current-law cases and the below-basic-limit case, where the whole band is correctly left.policyengine-core test policyengine_uk/tests/policy: 3,444 passed.pytest test_capital_gains_tax_components.py test_capital_gains_responses.py test_capital_gains_projection.py test_gift_aid.pyand the other tests that touch CGT: pass.pytest -n 8 policyengine_uk/tests: 5,966 passed, 102 skipped, 1 xfailed.ruff format --checkandruff check: clean.changelog.d/Reproducer
Reproducer: policyengine_uk/tests/policy/baseline/gov/hmrc/capital_gains_tax/capital_gains_tax_rate_bands.yaml, policyengine_uk/tests/test_capital_gains_tax_components.py::TestEqualisationReforms::test_main_rate_reform_leaves_the_other_schedules_at_current_law
axiom: n/a: the additional CGT band is a model-only reform hook; TCGA 1992 ss. 1H-1I as enacted have two rates (18% and 24%) and the higher and additional parameters are equal at every date, so no encoded rule or current-law result changes
🤖 Generated with Claude Code