Skip to content

CGT higher and additional band split ignores taxable income above the basic rate limit #2432

Open

Description

@vahid-ahmadi

What's wrong

capital_gains_tax splits gains above the basic rate band between the higher and additional CGT bands at the income tax higher rate limit, but it gives every gainer the whole higher band (higher_rate_limit - basic_rate_limit, £87,440 in 2025-26). It never subtracts taxable income that already sits above the basic rate limit.

In policyengine_uk/variables/gov/hmrc/capital_gains_tax/capital_gains_tax.py:

higher_rate_band = higher_rate_limit - basic_rate_limit
...
band_left = higher_rate_band
for name in higher_order:
    inside_higher[name], above_higher[name], band_left = _take(above_basic[name], band_left)

So someone with £200,000 of taxable income has their gains charged at the CGT higher rate, not the additional rate, even though their income alone is already £74,860 above the higher rate limit.

Worked examples (2025-26, on main)

£200,000 of earnings and £50,000 of gains (47,000 above the £3,000 annual exempt amount; the personal allowance is fully withdrawn):

Reform Main Correct
Current law (18% / 24% / 24%) £11,280 £11,280
CGT additional rate 45% £11,280 (unchanged) £21,150 (47,000 x 45%)
CGT higher rate 45% £21,150 £11,280 (no higher band left)

£100,000 of taxable income and £100,000 of gains, additional rate at 45%: the higher band left is 125,140 - 100,000 = £25,140, so CGT should be 25,140 x 24% + 71,860 x 45% = £38,370.60. Main charges 87,440 x 24% + 9,560 x 45% = £25,287.60.

Law and stacking

Under TCGA 1992 s. 1H(3) gains are charged at 18% or 24%, and s. 1I decides which by treating gains as the top slice of income: the lower rate applies only to the part within the unused basic rate band. Current law has no separate additional CGT rate. The model's additional_rate parameters (main, residential property, carried interest) equal the higher rate at every date and exist only so reforms can mirror the income tax additional-rate band, as the variable's documentation says.

For that split to mirror income tax, gains must stack on top of taxable income in the higher band too. The first pound of gains above the basic rate band sits at taxable income or the basic rate limit, whichever is higher, so the higher band left for gains is:

max_(higher_rate_limit - max_(taxable_income, basic_rate_limit), 0)

Relief (BADR / investors' relief), residential property and carried interest gains share the same loop and band, so they have the same problem; the fix covers all of them.

Impact

  • Current-law CGT is unaffected (beyond float32 rounding), because the higher and additional CGT rates are equal.
  • Any reform that sets the CGT higher and additional rates apart is mis-scored: raising only the additional rate misses gainers whose income is above the higher rate limit, and raising only the higher rate wrongly charges them.
  • With a realisation response that answers the rate on the last pound of gains (as in the stacked default-response PR Make a retention-rate elasticity of 1 the default CGT realisation response #2419), the wrong marginal band also drives the wrong behavioural response.

🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions