Stamp Duty Land Tax#
Stamp Duty Land Tax (SDLT) is a tax imposed on the purchase and rental of properties or land with values over a certain threshold in England and Northern Ireland. It applies to both residential and non-residential (commercial) properties.
Stamp Duty Land Tax parameters can be found in policyengine_uk/parameters/gov/hmrc/stamp_duty
and logic in policyengine_uk/variables/gov/hmrc/stamp_duty_land_tax.py
.
Legislation#
The regulation of SDLT is defined in part 4 of the Finance Act 2003.
Rates#
The rates of SDLT depend on various factors, including the purchase price of the property, property type, and whether it is an additional property or a purchase by a first-time buyer. Generally, SDLT rates are higher for properties or land with a purchase price exceeding a specific threshold, often referred to as “stamp duty bands” or “stamp duty brackets.” The rules and thresholds for SDLT may change over time due to government policy adjustments. First-time buyers may be eligible for reduced SDLT rates if the purchase price of the property is not more than £500,000.
Here we show current rates and thresholds for SDLT under different situations in 2023 below:
Rent
Up to £125,000: 0%
Over £125,000: 1%
Purchase
Main property
First-time
Up to £300,000: 0%
£300,000 to £500,000: 5%
Over £500,000: Ineligible for relief, follow standard rates
Subsequent (standard rates):
Up to £125,000: 0%
£125,000 to £250,000: 2%
£250,000 to £925,000: 5%
£925,000 to £1,500,000: 10%
Over £1,500,000: 12%
Additional property
An additional 3% on top of the standard rates for each band. However, a secondary property or land with a purchase price or market value less than £40,000 will be exempt from SDLT.
Less than £40,000: 0%
£40,000 to £125,000: 3%
£125,000 to £250,000: 5%
£250,000 to £925,000: 8%
£925,000 to £1,500,000: 13%
Over £1,500,000: 15%
Rent
Up to £150,000: 0%
£150,000 to £5,000,000: 1%
Over £5,000,000: 2%
Purchase
Up to £150,000: 0%
£150,000 to £250,000: 2%
Over £250,000: 5%
Show code cell source
import pandas as pd
import plotly.express as px
from policyengine_core.charts import format_fig
residential_rent = pd.DataFrame(
{
"Threshold": [0, 125000, 5200000],
"Rate": [0, 0.01, 0.01],
"Label": ["Residential rent"] * 3,
}
)
residential_purchase_main_first = pd.DataFrame(
{
"Threshold": [0, 300000, 500000, 925000, 1500000, 5200000],
"Rate": [0, 0.05, 0.05, 0.1, 0.12, 0.12],
"Label": ["Residential purchase main (first home)"] * 6,
}
)
residential_purchase_main_standard = pd.DataFrame(
{
"Threshold": [0, 125000, 250000, 925000, 1500000, 5200000],
"Rate": [0, 0.02, 0.05, 0.1, 0.12, 0.12],
"Label": ["Residential purchase main standard"] * 6,
}
)
residential_purchase_additional = pd.DataFrame(
{
"Threshold": [0, 40000, 125000, 250000, 925000, 1500000, 5200000],
"Rate": [0, 0.03, 0.05, 0.08, 0.13, 0.15, 0.15],
"Label": ["Residential purchase additional"] * 7,
}
)
non_residential_rent = pd.DataFrame(
{
"Threshold": [0, 150000, 5000000, 5200000],
"Rate": [0, 0.01, 0.02, 0.02],
"Label": ["Non-residential rent"] * 4,
}
)
non_residential_purchase = pd.DataFrame(
{
"Threshold": [0, 150000, 250000, 5200000],
"Rate": [0, 0.02, 0.05, 0.05],
"Label": ["Non-residential purchase"] * 4,
}
)
df_list = [
residential_rent,
residential_purchase_main_first,
residential_purchase_main_standard,
residential_purchase_additional,
non_residential_rent,
non_residential_purchase,
]
df = pd.concat(df_list)
# plot
fig = (
px.line(
df,
x="Threshold",
y="Rate",
color="Label",
title="Stamp duty land tax rates over property price thresholds",
)
.update_layout(
yaxis_tickformat=",.0%",
xaxis_tickprefix="£",
legend=dict(
orientation="h",
yanchor="bottom",
y=-0.3,
xanchor="right",
x=0.9,
title="",
),
)
.update_traces(line_shape="hv")
)
fig = format_fig(fig)
fig
The first home discount only applies for homes priced under £500,000. When simulating the marginal effect of SDLT, we find a cliff effect for first-time buyers.
Show code cell source
from policyengine_uk import Simulation
sim = Simulation(
situation=dict(
households=dict(
household=dict(
main_residential_property_purchased_is_first_home=True,
members=["person"],
)
),
people=dict(
person=dict(
age=30,
)
),
axes=[
[
dict(
name="main_residential_property_purchased",
min=0,
max=5_000_000,
count=1_000,
)
]
],
)
)
import pandas as pd
stamp_duty = sim.calculate("stamp_duty_land_tax")
home_price = sim.calculate("main_residential_property_purchased")
marginal_rate = (stamp_duty[1:] - stamp_duty[:-1]) / (
home_price[1:] - home_price[:-1]
)
home_price = home_price[:-1]
df = pd.DataFrame(
{
"Home price": home_price,
"Marginal SDLT rate": marginal_rate,
"SDLT": stamp_duty[:-1],
}
)
import plotly.express as px
from plotly.subplots import make_subplots
# left shows marginal rate, right shows total SDLT
fig = make_subplots(
rows=1, cols=2, subplot_titles=("Marginal SDLT rate", "Total SDLT")
)
fig.add_trace(
px.line(
df, x="Home price", y="Marginal SDLT rate", title="Marginal SDLT rate"
)
.update_traces(line_shape="hv")
.data[0],
row=1,
col=1,
).update_xaxes(row=1, col=1, title_text="Price", tickprefix="£").update_yaxes(
row=1, col=1, tickformat=",.0%"
)
fig.add_trace(
px.line(df, x="Home price", y="SDLT", title="Total SDLT")
.update_traces(line_shape="hv")
.data[0],
row=1,
col=2,
).update_xaxes(row=1, col=2, title_text="Price", tickprefix="£").update_yaxes(
row=1, col=2, tickprefix="£"
)
fig = format_fig(fig)
fig
SDLT Statistics for 2019 and 2020#
HM Revenue & Customs has collected statistics for revenues generated from Stamp Duty Land Tax (SDLT). The SDLT statistics were collected separately for residential and non-residental properties, and for each property type, HMRC divided the data into two categories: corporate and households. The graph below shows SDLT statistics for 2019 and 2020:
Show code cell source
from policyengine_uk.system import system
from policyengine_core.charts import format_fig
sdlt = system.parameters.gov.hmrc.stamp_duty.statistics
stats = {"Year": [], "Revenue (£m)": [], "Type": []}
# residential corporate
for parameter in sdlt.residential.corporate.revenue.values_list:
stats["Year"].append(parameter.instant_str[:4])
stats["Revenue (£m)"].append(parameter.value / 1000000)
stats["Type"].append("Residental corporate")
# residential household
for parameter in sdlt.residential.household.revenue.values_list:
stats["Year"].append(parameter.instant_str[:4])
stats["Revenue (£m)"].append(parameter.value / 1000000)
stats["Type"].append("Residental household")
# non-residential corporate
for parameter in sdlt.non_residential.corporate.revenue.values_list:
stats["Year"].append(parameter.instant_str[:4])
stats["Revenue (£m)"].append(parameter.value / 1000000)
stats["Type"].append("Non-residental corporate")
# non-residential household
for parameter in sdlt.non_residential.household.revenue.values_list:
stats["Year"].append(parameter.instant_str[:4])
stats["Revenue (£m)"].append(parameter.value / 1000000)
stats["Type"].append("Non-residental household")
df = pd.DataFrame(stats)
df.sort_values("Year", inplace=True)
fig = (
px.bar(
df,
x="Type",
y="Revenue (£m)",
color="Year",
barmode="group",
text_auto=True,
title="SDLT statistics for 2019 and 2020",
height=500,
)
.update_traces(textposition="outside")
.update_layout(xaxis_title="Property type")
)
fig = format_fig(fig)
fig