diff --git a/changelog.d/mgd-higher-stake-2027.added.md b/changelog.d/mgd-higher-stake-2027.added.md new file mode 100644 index 0000000000..8a5f687a51 --- /dev/null +++ b/changelog.d/mgd-higher-stake-2027.added.md @@ -0,0 +1 @@ +Machine Games Duty charges the new `machine_games_standard_rate_higher_stake_net_takings` input, the part of standard-rate takings from machines with a maximum charge to play above £1 or a maximum prize above £100, at the higher rate of 25% from 1 April 2027 (Budget 2026, OOTLAR paragraph 1.50). The dated parameter `gov.hmrc.gambling_duty.machine_games.higher_stake_machines_at_higher_rate` switches the change on. diff --git a/docs/engineering/gambling-duties.md b/docs/engineering/gambling-duties.md index ba66d26751..4ca593ca85 100644 --- a/docs/engineering/gambling-duties.md +++ b/docs/engineering/gambling-duties.md @@ -97,7 +97,8 @@ Supply attributed dutiable net takings separately: | Input | Rate | Statutory machine class | | --- | --- | --- | | `machine_games_lower_rate_net_takings` | 5% | Every dutiable game costs at most 20p and has a maximum cash prize of at most £10. | -| `machine_games_standard_rate_net_takings` | 20% | The machine does not qualify for the lower rate, and every dutiable game costs at most £5. | +| `machine_games_standard_rate_net_takings` | 20%, except the part below | The machine does not qualify for the lower rate, and every dutiable game costs at most £5. | +| `machine_games_standard_rate_higher_stake_net_takings` | 20% to 31 March 2027, then 25% | The part of the standard-rate input from machines with a maximum charge to play above £1 or a maximum prize above £100. | | `machine_games_higher_rate_net_takings` | 25% | Other dutiable machines. | Classify the entire machine using all dutiable games it offers. Exactly £5 per @@ -110,6 +111,69 @@ class's net takings by premises. Sources: [Finance Act 2012, Schedule 24](https://www.legislation.gov.uk/ukpga/2012/14/schedule/24) and [HMRC Notice 452](https://www.gov.uk/guidance/machine-games-duty-excise-notice-452). +### Higher-stake machines from 1 April 2027 + +Budget 2026 moves machines with a maximum charge to play of more than £1, or +a maximum prize of more than £100, from the standard rate of 20% to the higher +rate of 25% from 1 April 2027. Finance Bill 2026-27 will amend the stake and +prize thresholds that set each machine's type, and with it the rate +([OOTLAR paragraph 1.50](https://www.gov.uk/government/publications/budget-2026-overview-of-tax-legislation-and-rates-ootlar/budget-2026-overview-of-tax-legislation-and-rates-ootlar)). +Today, Finance Act 2012 Schedule 24 paragraph 5(3) puts every machine outside +the lower-rate class with a highest charge of at most £5 in the standard-rate +type. From 1 April 2027, such a machine stays at the standard rate only if its +maximum charge is at most £1 and its maximum prize at most £100: a machine at +exactly £1 and £100 stays at 20%, while £1 with a £500 prize, or £2 with a +£100 prize, moves to 25%. + +`machine_games_standard_rate_higher_stake_net_takings` is a subset of +`machine_games_standard_rate_net_takings`, not a separate amount: + +- The standard-rate input keeps its meaning: takings from every machine with a + highest charge of at most £5 outside the lower-rate class. A dataset without + the new input gives the duty before Budget 2026 in every year. +- HMRC's standard-rate liabilities for periods before 1 April 2027 include the + higher-stake machines, so a provider that builds the standard-rate input + from them needs no change. From that date HMRC reports those machines at the + higher rate. A provider that builds the inputs from later liabilities must + add their takings back to the standard-rate input and leave them out of the + higher-rate input. Take £100 of remaining standard-rate takings and £100 of + higher-stake takings: the duty comes to £45. A provider that leaves the £100 + out of the higher-rate input but doesn't add it back gets £25; one that does + neither gets £50. +- A data provider supplies the higher-stake share of that base, for example + from the Gambling Commission's machine yield by category. The class + corresponds closely to Category B, whose limits ([B1: £5 stakes; B2, B3 and + B3A: £2 and £500; B4: £2 and £400](https://www.gamblingcommission.gov.uk/licensees-and-businesses/guide/gaming-machine-categories)) + exceed one of the thresholds. Category C (£1 and £100) and Category D stay + within them. The tax class turns on the highest charge and prize a machine + actually offers, so a category split approximates it. +- The duty floors the new input at zero and caps it at the standard-rate input. + It never taxes takings that the standard-rate input leaves out. + +Within the 6 April to 5 April model year, the change splits by day, as +paragraph 6(6) (a machine that changes type) and paragraph 9(4) (a rate that +changes) require within an accounting period. Model year 2026 charges the +higher-stake part at 25% for 5 of its 365 days, 1 to 5 April 2027; model year +2027 and later charge all of it at 25%. For £1,000 of standard-rate takings, +£400 of them higher-stake, the duty rises from £200 to £200.27 in 2026 and to +£220 from 2027. + +The dated switch +`gov.hmrc.gambling_duty.machine_games.higher_stake_machines_at_higher_rate` +turns on from 1 April 2027. Set it to false for the counterfactual without the +change, or move its date to delay it. It shares fiscal-year segments with the +rates, so a rate reform applies to the higher-stake part at whichever rate the +switch selects. + +The policy costing puts the measure at £135 million in 2027-28 and £130 +million a year to 2031-32 +([Budget 2026 policy costings, page 67](https://assets.publishing.service.gov.uk/media/6ae1883fe4367e5c8b0467df/AB26_Policy_Costing.pdf#page=67)). +That costing grows its base with the OBR's Machine Games Duty forecast and nets +off a behavioural response: an elasticity of -0.95, a quarter of lost stakes +moving to Remote Gaming Duty, general attrition and lower Corporation Tax. The +model applies the rates statically to supplied takings and reproduces none of +those responses. + ## Bingo Duty Supply `bingo_net_receipts`, the household's nonnegative share of dutiable @@ -260,7 +324,7 @@ figures, rounded. These are cash receipts for a 1 April to 31 March year, so a reconciliation must handle the timing, period and coverage differences described for the casino proxy. -The three machine tax-class bases cannot score a betting-shop-only reform +The machine tax-class bases cannot score a betting-shop-only reform without joint tax-class-by-premises yield data. Premises shares alone do not identify that allocation, and machine counts do not provide yield weights. No assumption of independence between premises and tax class enters the diff --git a/policyengine_uk/parameters/gov/hmrc/gambling_duty/machine_games/higher_stake_machines_at_higher_rate.yaml b/policyengine_uk/parameters/gov/hmrc/gambling_duty/machine_games/higher_stake_machines_at_higher_rate.yaml new file mode 100644 index 0000000000..ecfd6ddea1 --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/gambling_duty/machine_games/higher_stake_machines_at_higher_rate.yaml @@ -0,0 +1,22 @@ +description: Whether the UK charges Machine Games Duty at the higher rate, instead of the standard rate, on net takings from standard-rate machines with a maximum charge to play above GBP 1 or a maximum prize above GBP 100. +values: + 2013-02-01: false + 2027-04-01: + value: true + reference: + - title: Budget 2026 Overview of Tax Legislation and Rates, paragraph 1.50 + href: https://www.gov.uk/government/publications/budget-2026-overview-of-tax-legislation-and-rates-ootlar/budget-2026-overview-of-tax-legislation-and-rates-ootlar + - title: Budget 2026 policy costings, Machine Games Duty higher stake machines, page 67 + href: https://assets.publishing.service.gov.uk/media/6ae1883fe4367e5c8b0467df/AB26_Policy_Costing.pdf#page=67 +metadata: + unit: bool + period: year + label: Machine Games Duty higher rate on higher-stake machines + reference: + - title: Finance Act 2012, Schedule 24, paragraph 5 + href: https://www.legislation.gov.uk/ukpga/2012/14/schedule/24/paragraph/5 + - title: Finance Act 2012, Schedule 24, paragraph 6 + href: https://www.legislation.gov.uk/ukpga/2012/14/schedule/24/paragraph/6 + - title: Budget 2026 Overview of Tax Legislation and Rates, paragraph 1.50 + href: https://www.gov.uk/government/publications/budget-2026-overview-of-tax-legislation-and-rates-ootlar/budget-2026-overview-of-tax-legislation-and-rates-ootlar + preserve_calendar_dates: true diff --git a/policyengine_uk/parameters/gov/hmrc/gambling_duty/machine_games/rates/standard.yaml b/policyengine_uk/parameters/gov/hmrc/gambling_duty/machine_games/rates/standard.yaml index 71d5b1d668..2aeac6d7e2 100644 --- a/policyengine_uk/parameters/gov/hmrc/gambling_duty/machine_games/rates/standard.yaml +++ b/policyengine_uk/parameters/gov/hmrc/gambling_duty/machine_games/rates/standard.yaml @@ -1,4 +1,4 @@ -description: The UK levies Machine Games Duty at this rate on dutiable net takings from machines outside the lower-rate class on which every dutiable game costs at most GBP 5. +description: The UK levies Machine Games Duty at this rate on dutiable net takings from machines outside the lower-rate class on which every dutiable game costs at most GBP 5, and, from 1 April 2027, at most GBP 1 with a maximum prize of at most GBP 100. values: '2013-02-01': 0.2 metadata: @@ -12,4 +12,6 @@ metadata: href: https://www.gov.uk/guidance/machine-games-duty-excise-notice-452 - title: HMRC historical gambling duty rates href: https://www.gov.uk/government/statistics/uk-betting-and-gaming-statistics/historical-uk-betting-and-gaming-duty-rates + - title: Budget 2026 Overview of Tax Legislation and Rates, paragraph 1.50 + href: https://www.gov.uk/government/publications/budget-2026-overview-of-tax-legislation-and-rates-ootlar/budget-2026-overview-of-tax-legislation-and-rates-ootlar preserve_calendar_dates: true diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/gambling_duty/machine_games_duty.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/gambling_duty/machine_games_duty.yaml index 70d2aa8adc..39feb3512f 100644 --- a/policyengine_uk/tests/policy/baseline/gov/hmrc/gambling_duty/machine_games_duty.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/gambling_duty/machine_games_duty.yaml @@ -36,3 +36,70 @@ machine_games_higher_rate_net_takings: 1_000 output: machine_games_duty: 250 # Only the higher-rate base: 1,000 * 25%. + +- name: Case 6, before April 2027 higher-stake takings stay at the standard rate. + period: 2025 + input: + machine_games_standard_rate_net_takings: 1_000 + machine_games_standard_rate_higher_stake_net_takings: 400 + output: + machine_games_duty: 200 # 1,000 * 20%; the higher-stake part is within it. + +- name: Case 7, FY2026 charges five of its 365 days at the higher rate. + period: 2026 + input: + machine_games_standard_rate_net_takings: 36_500 + machine_games_standard_rate_higher_stake_net_takings: 7_300 + output: + # OOTLAR 1.50 from 1 April 2027: 36,500 * 20% + 7,300 * 5% * 5 / 365. + machine_games_duty: 7_305 + +- name: Case 8, from FY2027 higher-stake takings use twenty-five percent. + period: 2027 + input: + machine_games_standard_rate_net_takings: 1_000 + machine_games_standard_rate_higher_stake_net_takings: 400 + output: + machine_games_duty: 220 # 600 * 20% + 400 * 25%. + +- name: Case 9, the higher rate continues in later years. + period: 2029 + input: + machine_games_standard_rate_net_takings: 1_000 + machine_games_standard_rate_higher_stake_net_takings: 400 + output: + machine_games_duty: 220 + +- name: Case 10, the duty caps higher-stake takings at the standard-rate base. + period: 2027 + input: + machine_games_standard_rate_net_takings: 1_000 + machine_games_standard_rate_higher_stake_net_takings: 1_500 + output: + machine_games_duty: 250 # 1,000 * 25%; the extra 500 is not in the base. + +- name: Case 11, higher-stake takings without a standard-rate base give no duty. + period: 2027 + input: + machine_games_standard_rate_higher_stake_net_takings: 1_000 + output: + machine_games_duty: 0 + +- name: Case 12, negative higher-stake takings leave the standard rate on the whole base. + period: 2027 + input: + machine_games_standard_rate_net_takings: 1_000 + machine_games_standard_rate_higher_stake_net_takings: -500 + output: + machine_games_duty: 200 + +- name: Case 13, all four bases in FY2027 reach gambling duty and household tax. + period: 2027 + input: + machine_games_lower_rate_net_takings: 1_000 + machine_games_standard_rate_net_takings: 2_000 + machine_games_standard_rate_higher_stake_net_takings: 1_000 + machine_games_higher_rate_net_takings: 3_000 + output: + machine_games_duty: 1_250 # 50 + 1,000 * 20% + 1,000 * 25% + 750. + gambling_duty: 1_250 diff --git a/policyengine_uk/tests/test_machine_games_higher_stake.py b/policyengine_uk/tests/test_machine_games_higher_stake.py new file mode 100644 index 0000000000..1645820a6f --- /dev/null +++ b/policyengine_uk/tests/test_machine_games_higher_stake.py @@ -0,0 +1,229 @@ +"""Machine Games Duty on higher-stake machines from 1 April 2027. + +Budget 2026 (OOTLAR paragraph 1.50) moves machines with a maximum charge to +play above GBP 1 or a maximum prize above GBP 100 from the standard rate (20%) +to the higher rate (25%) from 1 April 2027. The household input +`machine_games_standard_rate_higher_stake_net_takings` is the part of +`machine_games_standard_rate_net_takings` from those machines. + +Invariants, over generated households and model years 2024 to 2030: + +1. Closed form: the duty equals the statutory duty without the change plus + min(max(H, 0), max(S, 0)) * (25% - 20%) * f, where S is the standard-rate + input, H the higher-stake input and f the share of the 6 April to 5 April + model year on or after 1 April 2027 (0 to FY2025, 5/365 in FY2026, 1 + after). The reference uses Finance Act 2012 Schedule 24 paragraph 9's + rates and its own date arithmetic, not `fiscal_year_segments`. +2. Zero takings in the class give the old duty, in every year. +3. Before FY2026 the split of the standard-rate base does not change the duty. +4. Monotonicity: moving takings into the class (raising H with S fixed) never + lowers the duty, given a higher rate at least the standard rate, as in law. +5. Bounds: old duty <= duty <= old duty + max(S, 0) * 5% * f, and duty >= 0. +6. Counterfactual: switching the reclassification off gives the old duty. + +Comparisons allow for the engine's float32 arithmetic (RTOL, ATOL). +""" + +from datetime import date +from functools import cache + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st +from policyengine_core.parameters import ParameterNode + +from policyengine_uk import CountryTaxBenefitSystem, Simulation +from policyengine_uk.model_api import Scenario +from policyengine_uk.utils.excise import fiscal_year_segments + +SWITCH = "gov.hmrc.gambling_duty.machine_games.higher_stake_machines_at_higher_rate" +# Finance Act 2012, Schedule 24, paragraph 9. +LOWER, STANDARD, HIGHER = 0.05, 0.20, 0.25 +CHANGE = date(2027, 4, 1) # OOTLAR paragraph 1.50. +INPUTS = ( + "machine_games_lower_rate_net_takings", + "machine_games_standard_rate_net_takings", + "machine_games_higher_rate_net_takings", + "machine_games_standard_rate_higher_stake_net_takings", +) +SETTINGS = settings( + max_examples=25, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +CAPACITY = 24 # Households in each shared simulation. +# The engine computes in float32, about seven significant digits. +RTOL, ATOL = 2e-6, 1e-3 +takings = st.integers(min_value=-20_000, max_value=5_000_000) +households = st.lists( + st.tuples(takings, takings, takings, takings), min_size=1, max_size=8 +) + + +def share_after_change(year): + start, end = date(year, 4, 6), date(year + 1, 4, 6) + return max((end - max(start, CHANGE)).days, 0) / (end - start).days + + +def slack(values): + return RTOL * np.abs(values) + ATOL + + +def old_duty(lower, standard, higher): + return max(lower, 0) * LOWER + max(standard, 0) * STANDARD + max(higher, 0) * HIGHER + + +def reference_duty(year, lower, standard, higher, higher_stake): + moved = min(max(higher_stake, 0), max(standard, 0)) + return old_duty(lower, standard, higher) + moved * ( + HIGHER - STANDARD + ) * share_after_change(year) + + +def simulation(changes=None, households=CAPACITY): + situation = {"people": {}, "benunits": {}, "households": {}} + for i in range(households): + situation["people"][f"p{i}"] = {"age": {2026: 40}} + situation["benunits"][f"b{i}"] = {"members": [f"p{i}"]} + situation["households"][f"h{i}"] = {"members": [f"p{i}"]} + return Simulation( + situation=situation, scenario=Scenario(parameter_changes=changes or {}) + ) + + +@cache +def shared_simulation(switched_off): + """One simulation per scenario, reused across examples: building one + takes seconds, while resetting its inputs takes milliseconds.""" + return simulation( + {SWITCH: {"2013-02-01.2099-12-31": False}} if switched_off else {} + ) + + +@pytest.fixture(scope="module", autouse=True) +def release_shared_simulations(): + yield + shared_simulation.cache_clear() + + +def duties(year, rows, switched_off=False, sim=None): + """Machine Games Duty for each row of the four inputs.""" + sim = sim or shared_simulation(switched_off) + size = sim.populations["household"].count + assert len(rows) <= size + padded = list(rows) + [(0, 0, 0, 0)] * (size - len(rows)) + for name, column in zip(INPUTS, zip(*padded)): + sim.set_input(name, year, np.array(column, dtype=float)) + sim.delete_arrays("machine_games_duty") + return np.asarray(sim.calculate("machine_games_duty", year))[: len(rows)] + + +def test_statutory_rates_hold_across_the_tested_years(): + """The references assume para 9's rates through the tested window.""" + rates = CountryTaxBenefitSystem().parameters.gov.hmrc.gambling_duty.machine_games + for day in ["2024-04-06", "2027-04-01", "2031-04-05"]: + assert rates.rates.lower(day) == LOWER + assert rates.rates.standard(day) == STANDARD + assert rates.rates.higher(day) == HIGHER + + +def test_switch_changes_on_the_statutory_date(): + switch = CountryTaxBenefitSystem().parameters.get_child(SWITCH) + assert not switch("2027-03-31") + assert switch("2027-04-01") + + +def test_share_after_change_matches_the_fiscal_calendar(): + assert share_after_change(2025) == 0 + assert share_after_change(2026) == pytest.approx(5 / 365) + assert share_after_change(2027) == 1 + + +@SETTINGS +@given(year=st.integers(min_value=2024, max_value=2030), rows=households) +def test_duty_matches_the_closed_form(year, rows): + rows = rows + [(0, 0, 0, 0)] + expected = [reference_duty(year, *row) for row in rows] + assert duties(year, rows) == pytest.approx(expected, rel=RTOL, abs=ATOL) + + +@SETTINGS +@given( + year=st.integers(min_value=2024, max_value=2030), + rows=households, + extra=st.lists(st.integers(min_value=0, max_value=5_000_000), max_size=8), +) +def test_moving_takings_into_the_class_never_lowers_duty(year, rows, extra): + base = [(*row[:3], max(row[3], 0)) for row in rows] + raised = [ + (*row[:3], row[3] + bump) for row, bump in zip(base, extra + [0] * len(base)) + ] + zero = [(*row[:3], 0) for row in base] + result = duties(year, zero + base + raised) + n = len(base) + old, low, high = result[:n], result[n : 2 * n], result[2 * n :] + expected_old = [old_duty(*row[:3]) for row in base] + assert old == pytest.approx(expected_old, rel=RTOL, abs=ATOL) + assert np.all(low >= old - slack(old)) + assert np.all(high >= low - slack(low)) + ceiling = [ + old_duty(*row[:3]) + + max(row[1], 0) * (HIGHER - STANDARD) * share_after_change(year) + for row in base + ] + assert np.all(high <= np.asarray(ceiling) + slack(high)) + assert np.all(result >= 0) + if year <= 2025: + assert high == pytest.approx(old, rel=RTOL, abs=ATOL) + + +@SETTINGS +@given(year=st.integers(min_value=2024, max_value=2030), rows=households) +def test_switching_the_change_off_gives_the_old_duty(year, rows): + expected = [old_duty(*row[:3]) for row in rows] + off = duties(year, rows, switched_off=True) + assert off == pytest.approx(expected, rel=RTOL, abs=ATOL) + + +def test_annual_reform_applies_to_the_whole_fiscal_year(): + """A reform naming FY2026 covers all 365 days, not only 1 to 5 April.""" + row = (0, 36_500, 0, 36_500) + annual = simulation({SWITCH: {"2026": True}}, households=1) + assert duties(2026, [row])[0] == pytest.approx(7_325) + assert duties(2026, [row], sim=annual)[0] == pytest.approx(9_125) + + +def test_dated_delay_splits_the_fiscal_year_by_day(): + """Delaying the change to 1 April 2028 leaves 5 of FY2027's 366 days.""" + row = (0, 36_600, 0, 36_600) + delay = simulation({SWITCH: {"2027-04-01.2028-03-31": False}}, households=1) + # 36,600 * 20% + 36,600 * 5% * 5 / 366. + assert duties(2027, [row], sim=delay)[0] == pytest.approx(7_345) + + +def test_segments_split_at_dates_from_every_mapped_leaf(): + rates = ParameterNode( + "rates", data={"rate": {"values": {"2020-01-01": 1, "2026-10-01": 2}}} + ) + switch = ParameterNode( + "switch", data={"on": {"values": {"2020-01-01": False, "2027-04-01": True}}} + ) + segments = list( + fiscal_year_segments({**rates.children, "on": switch.children["on"]}, 2026) + ) + assert [(p.rate, p.on) for p, share in segments] == [ + (1, False), + (2, False), + (2, True), + ] + assert [share for p, share in segments] == pytest.approx( + [178 / 365, 182 / 365, 5 / 365] + ) + + +def test_mapped_segments_reject_nodes(): + rates = ParameterNode("rates", data={"rate": {"values": {"2020-01-01": 1}}}) + with pytest.raises(TypeError): + list(fiscal_year_segments({"rates": rates}, 2026)) diff --git a/policyengine_uk/utils/excise.py b/policyengine_uk/utils/excise.py index 61c98dbacf..9d107170bd 100644 --- a/policyengine_uk/utils/excise.py +++ b/policyengine_uk/utils/excise.py @@ -1,6 +1,6 @@ """Annualise transaction duties over the UK fiscal year.""" -from collections.abc import Iterator +from collections.abc import Iterator, Mapping from datetime import datetime from types import SimpleNamespace @@ -8,19 +8,21 @@ def fiscal_year_segments( - node: ParameterNode, year: int + node: ParameterNode | Mapping[str, Parameter], year: int ) -> Iterator[tuple[SimpleNamespace, float]]: """Yield (rates, share of year) for a flat preserved rate schedule. Consumption is uniform from 6 April to 5 April. Apply nonlinear rules, such as cigarette minimum duty, within each segment before averaging. Value-level ``effective_time`` metadata supports 6pm Budget-day changes; - dates supplied by ordinary reforms take effect at midnight. + dates supplied by ordinary reforms take effect at midnight. Pass a + mapping of names to leaves to segment leaves from several nodes jointly. """ start, end = datetime(year, 4, 6), datetime(year + 1, 4, 6) changes = {start, end} schedules = {} - for name, parameter in node.children.items(): + leaves = node.children if isinstance(node, ParameterNode) else node + for name, parameter in leaves.items(): if not isinstance(parameter, Parameter): raise TypeError("Excise rate schedules must contain only parameter leaves") schedule = [] diff --git a/policyengine_uk/variables/gov/hmrc/gambling_duty/machine_games_duty.py b/policyengine_uk/variables/gov/hmrc/gambling_duty/machine_games_duty.py index 7292d7fc74..7a5f5f99bc 100644 --- a/policyengine_uk/variables/gov/hmrc/gambling_duty/machine_games_duty.py +++ b/policyengine_uk/variables/gov/hmrc/gambling_duty/machine_games_duty.py @@ -8,17 +8,42 @@ class machine_games_duty(Variable): definition_period = YEAR unit = GBP label = "Machine Games Duty" - reference = "https://www.legislation.gov.uk/ukpga/2012/14/schedule/24" + reference = ( + "https://www.legislation.gov.uk/ukpga/2012/14/schedule/24", + "https://www.gov.uk/government/publications/budget-2026-overview-of-tax-legislation-and-rates-ootlar/budget-2026-overview-of-tax-legislation-and-rates-ootlar", + ) def formula_2024(household, period, parameters): lower = max_(household("machine_games_lower_rate_net_takings", period), 0) standard = max_(household("machine_games_standard_rate_net_takings", period), 0) higher = max_(household("machine_games_higher_rate_net_takings", period), 0) + # Part of the standard-rate base; OOTLAR 1.50 moves it to the higher + # rate from 1 April 2027. + higher_stake = min_( + max_( + household( + "machine_games_standard_rate_higher_stake_net_takings", period + ), + 0, + ), + standard, + ) + machine_games = parameters.gov.hmrc.gambling_duty.machine_games + schedule = { + **machine_games.rates.children, + "higher_stake_at_higher_rate": ( + machine_games.higher_stake_machines_at_higher_rate + ), + } total = 0 - for rates, share in fiscal_year_segments( - parameters.gov.hmrc.gambling_duty.machine_games.rates, period.start.year - ): + for rates, share in fiscal_year_segments(schedule, period.start.year): + higher_stake_rate = ( + rates.higher if rates.higher_stake_at_higher_rate else rates.standard + ) total += share * ( - lower * rates.lower + standard * rates.standard + higher * rates.higher + lower * rates.lower + + (standard - higher_stake) * rates.standard + + higher_stake * higher_stake_rate + + higher * rates.higher ) return total diff --git a/policyengine_uk/variables/input/consumption/gambling/machine_games_standard_rate_higher_stake_net_takings.py b/policyengine_uk/variables/input/consumption/gambling/machine_games_standard_rate_higher_stake_net_takings.py new file mode 100644 index 0000000000..c4121a45b6 --- /dev/null +++ b/policyengine_uk/variables/input/consumption/gambling/machine_games_standard_rate_higher_stake_net_takings.py @@ -0,0 +1,25 @@ +from policyengine_uk.model_api import * + + +class machine_games_standard_rate_higher_stake_net_takings(Variable): + value_type = float + entity = Household + definition_period = YEAR + default_value = 0 + unit = GBP + label = "Attributed higher-stake part of standard-rate machine games net takings" + documentation = ( + "The part of machine_games_standard_rate_net_takings that comes from " + "machines with a maximum charge to play above GBP 1 or a maximum prize " + "above GBP 100. The class corresponds closely to the Gambling " + "Commission's Category B, whose stake or prize limits exceed those " + "thresholds; Category C and D limits stay within them. This input is " + "a subset of the standard-rate input, not an addition to it: the duty " + "caps it at that input. Machine Games Duty charges it at the standard " + "rate until 31 March 2027 and at the higher rate from 1 April 2027." + ) + reference = ( + "https://www.gov.uk/government/publications/budget-2026-overview-of-tax-legislation-and-rates-ootlar/budget-2026-overview-of-tax-legislation-and-rates-ootlar", + "https://assets.publishing.service.gov.uk/media/6ae1883fe4367e5c8b0467df/AB26_Policy_Costing.pdf#page=67", + "https://www.legislation.gov.uk/ukpga/2012/14/schedule/24/paragraph/5", + )