Skip to content

Commit

Permalink
Add capital gains responses (#979)
Browse files Browse the repository at this point in the history
* Add capital gains responses

* Add changes

* Versioning
  • Loading branch information
nikhilwoodruff authored Oct 23, 2024
1 parent 369c7ee commit 6e59b57
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 2 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,6 @@ policyengine_uk/calibration/*.h5
**/*.csv
**/*.pkl
**/*.log
**/ukmod.json
**/ukmod.json

*.ipynb
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.12.0] - 2024-10-23 14:47:21

### Added

- Capital Gains Tax elasticities.

## [2.11.0] - 2024-10-23 10:15:26

### Added
Expand Down Expand Up @@ -1548,6 +1554,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0



[2.12.0]: https://github.com/PolicyEngine/openfisca-uk/compare/2.11.0...2.12.0
[2.11.0]: https://github.com/PolicyEngine/openfisca-uk/compare/2.10.0...2.11.0
[2.10.0]: https://github.com/PolicyEngine/openfisca-uk/compare/2.9.0...2.10.0
[2.9.0]: https://github.com/PolicyEngine/openfisca-uk/compare/2.8.0...2.9.0
Expand Down
5 changes: 5 additions & 0 deletions changelog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1299,3 +1299,8 @@
added:
- Benefit uprating for 2025/26.
date: 2024-10-23 10:15:26
- bump: minor
changes:
added:
- Capital Gains Tax elasticities.
date: 2024-10-23 14:47:21
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
description: Elasticity of capital gains with respect to the capital gains marginal tax rate.
values:
2000-01-01: 0
metadata:
unit: /1
label: capital gains elasticity
21 changes: 21 additions & 0 deletions policyengine_uk/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ def __init__(self, *args, **kwargs):
self.set_input("employment_income_before_lsr", known_period, array)
employment_income.delete_arrays(known_period)

# Capital gains responses

cg_holder = self.get_holder("capital_gains")
for known_period in cg_holder.get_known_periods():
array = cg_holder.get_array(known_period)
self.set_input(
"capital_gains_before_response", known_period, array
)
employment_income.delete_arrays(known_period)


class Microsimulation(CoreMicrosimulation):
default_tax_benefit_system = CountryTaxBenefitSystem
Expand Down Expand Up @@ -105,3 +115,14 @@ def __init__(self, *args, **kwargs):
"employment_income_before_lsr", known_period, array
)
employment_income.delete_arrays(known_period)

# Capital gains responses

for simulation in list(self.branches.values()) + [self]:
cg_holder = self.get_holder("capital_gains")
for known_period in cg_holder.get_known_periods():
array = cg_holder.get_array(known_period)
self.set_input(
"capital_gains_before_response", known_period, array
)
employment_income.delete_arrays(known_period)
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from policyengine_uk.model_api import *
from policyengine_core.holders import Holder


class capital_gains_tax(Variable):
Expand Down
163 changes: 163 additions & 0 deletions policyengine_uk/variables/gov/hmrc/capital_gains_tax/responses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
from policyengine_uk.model_api import *
from policyengine_core.simulations import *


class relative_capital_gains_mtr_change(Variable):
value_type = float
entity = Person
label = "relative change in capital gains tax rate"
unit = "/1"
definition_period = YEAR

def formula(person, period, parameters):
simulation: Simulation = person.simulation
baseline_branch = simulation.get_branch("baseline").get_branch(
"baseline_cgr_measurement"
)
baseline_person = baseline_branch.populations["person"]
baseline_branch.tax_benefit_system.neutralize_variable(
"capital_gains_behavioural_response"
)
baseline_branch.set_input(
"capital_gains_before_response",
period,
person("capital_gains_before_response", period),
)
baseline_mtr = baseline_person(
"marginal_tax_rate_on_capital_gains", period
)
del simulation.branches["baseline"].branches[
"baseline_cgr_measurement"
]

measurement_branch = simulation.get_branch("cgr_measurement")
measurement_branch.tax_benefit_system.neutralize_variable(
"capital_gains_behavioural_response"
)
measurement_branch.set_input(
"capital_gains_before_response",
period,
person("capital_gains_before_response", period),
)
measurement_person = measurement_branch.populations["person"]
reform_mtr = measurement_person(
"marginal_tax_rate_on_capital_gains", period
)
del simulation.branches["cgr_measurement"]

# Handle zeros in tax rates to prevent log(0)
min_rate = 0.001
baseline_mtr_adj = np.maximum(baseline_mtr, min_rate)
reform_mtr_adj = np.maximum(reform_mtr, min_rate)

# Calculate log difference
return np.log(reform_mtr_adj) - np.log(baseline_mtr_adj)


class capital_gains_elasticity(Variable):
value_type = float
entity = Person
label = "elasticity of capital gains realizations"
unit = "/1"
definition_period = YEAR

def formula(person, period, parameters):
gov = parameters(period).gov
return gov.simulation.capital_gains_responses.elasticity


class capital_gains_behavioural_response(Variable):
value_type = float
entity = Person
label = "capital gains behavioral response"
unit = GBP
definition_period = YEAR

def formula(person, period, parameters):
simulation = person.simulation
if simulation.baseline is None:
return 0

capital_gains = person("capital_gains_before_response", period)
tax_rate_change = person("relative_capital_gains_mtr_change", period)
elasticity = person("capital_gains_elasticity", period)

# Calculate response using log differences
response_factor = np.exp(elasticity * tax_rate_change) - 1
response = capital_gains * response_factor

return response


class capital_gains_before_response(Variable):
label = "capital gains before responses"
entity = Person
definition_period = YEAR
value_type = float
unit = GBP
uprating = "calibration.programs.capital_gains.total"


class adult_index_cg(Variable):
value_type = int
entity = Person
label = "index of adult in household, ranked by capital gains"
definition_period = YEAR

def formula(person, period, parameters):
return (
person.get_rank(
person.household,
-person("capital_gains_before_response", period),
condition=person("is_adult", period),
)
+ 1
)


class marginal_tax_rate_on_capital_gains(Variable):
label = "capital gains marginal tax rate"
documentation = "Percent of marginal capital gains that do not increase household net income."
entity = Person
definition_period = YEAR
value_type = float
unit = "/1"

def formula(person, period, parameters):
mtr_values = np.zeros(person.count, dtype=np.float32)
simulation = person.simulation
DELTA = 1_000
adult_index_values = person("adult_index_cg", period)
for adult_index in [1, 2]:
alt_simulation = simulation.get_branch(
f"adult_{adult_index}_cg_rise"
)
mask = adult_index_values == adult_index
for variable in simulation.tax_benefit_system.variables:
variable_data = simulation.tax_benefit_system.variables[
variable
]
if (
variable not in simulation.input_variables
and not variable_data.is_input_variable()
):
alt_simulation.delete_arrays(variable)
alt_simulation.set_input(
"capital_gains",
period,
person("capital_gains", period) + mask * DELTA,
)
alt_person = alt_simulation.person
household_net_income = person.household(
"household_net_income", period
)
household_net_income_higher_earnings = alt_person.household(
"household_net_income", period
)
increase = (
household_net_income_higher_earnings - household_net_income
)
mtr_values += where(mask, 1 - increase / DELTA, 0)

del simulation.branches[f"adult_{adult_index}_cg_rise"]
return mtr_values
4 changes: 4 additions & 0 deletions policyengine_uk/variables/household/income/income.py
Original file line number Diff line number Diff line change
Expand Up @@ -430,3 +430,7 @@ class capital_gains(Variable):
value_type = float
unit = GBP
uprating = "calibration.programs.capital_gains.total"
adds = [
"capital_gains_before_response",
"capital_gains_behavioural_response",
]
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

setup(
name="PolicyEngine-UK",
version="2.11.0",
version="2.12.0",
author="PolicyEngine",
author_email="nikhil@policyengine.org",
classifiers=[
Expand Down

0 comments on commit 6e59b57

Please sign in to comment.