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:

  • Residential
    • 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%

  • Non-residential
    • 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%

    Hide 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.

    Hide 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:

    Hide 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