Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ REST_SPM_TESTS := $(TESTS)/core/test_spm_policy_family.py \
# JUnit measures per-file elapsed time, not memory. The extra boundaries
# below isolate retained full models and target <= ~10 GB per process;
# verify those estimates against each group's GNU time report in CI.
# core (142 s), policy (185 s), remaining (239 s).
# heavy-a also holds capital_gains_mtr_measurement (#10058): 396 s of
# reformed household simulations in run 40045994536, with heavy-a at
# 8.1 GiB/11:16.
# SSI resource deeming retains a reformed system at import and builds another
# in its grid fixture. Core reached 13.3 GB in CI run 38141803955 after this
# and three smaller modules arrived; isolate SSI rather than filling heavy-a
Expand All @@ -55,7 +57,8 @@ REST_CACHED_ARRAY_TESTS := $(TESTS)/test_formulas_do_not_write_into_cached_array
REST_HEAVY_A_TESTS := $(TESTS)/test_md_poverty_line_credit_invariants.py \
$(TESTS)/core/test_ald_determinism.py \
$(TESTS)/core/test_dependent_net_investment_income_invariants.py \
$(TESTS)/core/test_ssi_state_supplement_medicaid_dependency.py
$(TESTS)/core/test_ssi_state_supplement_medicaid_dependency.py \
$(TESTS)/core/test_capital_gains_mtr_measurement.py
REST_HEAVY_B_TESTS := $(TESTS)/core/test_marginal_tax_rate_coverage.py \
$(TESTS)/core/test_multi_year_simulation.py \
$(TESTS)/core/test_behavioral_response_measurements.py \
Expand Down
1 change: 1 addition & 0 deletions changelog.d/fix-cg-mtr-measurement.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Measure the capital gains marginal tax rate on long-term gains, so capital gains realization responses see the preferential rates and changes to them, and scale the measured rise with income so the rate keeps its precision for large households.
24 changes: 19 additions & 5 deletions docs-quarto/methodology/capital-gains-responses.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ long_term_capital_gains = long_term_capital_gains_before_response + ca

so gains after the response are the pre-response gains times the multiplier `1 + f`. The response is zero unless the simulation is a reform with a baseline and one of the three parameters under `gov.simulation.capital_gains_responses` is nonzero. All three default to zero.

`τ0` and `τ1` are the person's marginal tax rates on capital gains under baseline and reform policy. Both come from `marginal_tax_rate_on_capital_gains`, measured in two branches that hold the same pre-response inputs and have every behavioral response switched off (`get_behavioral_response_measurements`). The rate is a household finite difference, `1 − Δ household_net_income / $1,000`, for an extra $1,000 of gains. It is measured for the two adults in each household with the largest long-term gains and is zero for everyone else, so other members do not respond. Because `household_net_income` includes benefits and refundable credits, the rate can fall outside [0, 1] where the extra $1,000 crosses a benefit cliff or phase-in.
`τ0` and `τ1` are the person's marginal tax rates on capital gains under baseline and reform policy. Both come from `marginal_tax_rate_on_capital_gains`, measured in two branches that hold the same pre-response inputs and have every behavioral response switched off (`get_behavioral_response_measurements`). The rate is a household finite difference: the share of a rise in the person's long-term gains that does not reach `household_net_income` (see [How the rate is measured](#how-the-rate-is-measured)). It is measured for the two adults in each household with the largest long-term gains before responses and is zero for everyone else, so other members do not respond. Because `household_net_income` includes benefits and refundable credits, the rate can fall outside [0, 1] where the rise crosses a benefit cliff or phase-in.

## The three forms

Expand Down Expand Up @@ -52,7 +52,9 @@ The log of a 0% rate is undefined, so both rates are floored at 0.001. A person

The response depends only on the change in the rate, so it is defined at a 0% baseline without a floor. The same person loses 29% of their gains at β = 3.4. β is roughly the percent fall in realizations per percentage-point rise in the rate.

Single-rate revenue `τ · A · exp(−βτ)` has derivative `A · exp(−βτ) · (1 − βτ)`, so it peaks at `τ = 1/β` when that is below 1. Without the clip described below, a fixed state rate `s` that adds to the federal rate `t` multiplies realizations by the constant `exp(−βs)`, so the analytical peak of single-rate federal revenue `t · R` stays at a federal rate (including the net investment income tax) of `1/β`. With the clip, realizations stop falling once `t + s` reaches 100%, and federal revenue then rises with `t`. The analytical peak is the maximum only when `β(1 − s) − ln β > 1`, which for β from 3.1 to 3.51 means a state rate below about 31% to 36%; otherwise revenue at a 100% federal rate is higher. With no state rate the condition holds for every β above 1.
Single-rate revenue `τ · A · exp(−βτ)` has derivative `A · exp(−βτ) · (1 − βτ)`, so it peaks at `τ = 1/β` when that is below 1. Without the clip described below, a fixed state rate `s` that adds to the federal rate `t` multiplies realizations by the constant `exp(−βs)`, so the analytical peak of single-rate federal revenue `t · R` stays at a federal rate (including the net investment income tax) of `1/β`. With the clip, realizations stop falling once `t + s` reaches 100%, and federal revenue then rises with `t`. The analytical peak is the unique maximum when `β(1 − s) − ln β > 1` (at equality a 100% federal rate ties), which for β from 3.1 to 3.51 means a state rate below about 31% to 36%; where the left side is less than 1, revenue at a 100% federal rate is higher. With no state rate the condition holds for every β above 1.

A filer's own federal tax peaks at a lower top rate than `1/β`, because the gains below the top bracket are not taxed at it. With β = 1/0.30, a Texas single filer with $20 million of gains realizes `A = $20 million × exp(−β · (r − 20%))` at a top rate `r`, and the part of their federal income tax that moves with `r` is `r · (A − $545,500) + 3.8% · (A − $200,000)`: the top rate applies from $545,500 of gains, not of taxable income, because the alternative minimum tax, which this filer owes, taxes the standard deduction at it too. That peaks where `r + 3.8% = (1 − $545,500 / A) / β`, a top rate of 25.2%: 30% less the 3.8-point net investment income tax and about 1 point for the gains below the top bracket. In household simulations the tax is highest at 25.25% on a quarter-point grid, and `policyengine_us/tests/core/test_capital_gains_mtr_measurement.py` checks the peak and the simulated tax at it.

Rates are clipped to [0, 1] before the change is taken. Without the clip, a household whose measured baseline rate is 10 because of a benefit cliff, and whose reform removes the cliff, would have its realizations multiplied by `exp(9.75β)`. With it, the multiplier `1 + f` stays between `exp(−β)` and `exp(β)`.

Expand All @@ -75,8 +77,20 @@ A target revenue-maximizing rate `τ*` maps to `β = 1/τ*`: 28.5% is 3.51, 29%

JCT's coefficient applies to the marginal rate from its individual income tax model. PolicyEngine applies `β` to the change in a household's marginal rate on gains, which also includes state income tax, the net investment income tax and benefit interactions. Because only the change enters, a federal rate change that passes through one-for-one produces the response JCT's equation would.

## Known limitation: how the rate is measured
## How the rate is measured

`marginal_tax_rate_on_capital_gains` raises the person's `long_term_capital_gains` in a branch of the simulation, calculates the household again and reports `1 − Δ household_net_income / rise`. The rise goes through net capital gain, so the preferential rates, the net investment income tax, the alternative minimum tax and state income taxes apply to it as they do to the rest of the person's long-term gains. Where a situation or dataset gives a `capital_gains` total, which adjusted gross income reads, the branch raises that total by the same amount. The branch holds any behavioral response at its value in the simulation. The test suite checks that the result equals the difference between two new simulations of the same household, across states, filing statuses, wages and gains.

The rise is $1,000, or 0.1% of the larger of the absolute values of the household's net income and the person's long-term gains where that is more. `household_net_income` is a 32-bit float, which resolves about $8 at $100 million, so a $1,000 rise would resolve that household's rate only to 0.008. Like any finite difference, the rate averages over the rise, so a person within the rise of a cliff gets the whole cliff spread over it: a New York single filer with $24,985,000 of gains crosses the state's $25 million threshold, above which all income is taxed at the top rate, and measures 6.345, where a $1,000 rise measures 0.342 and one that crossed the threshold would measure about 150. Below $1 million, where the benefit cliffs are, the rise is $1,000.

For a Texas single filer in 2026 whose only income is long-term gains, the measured rate is:

`marginal_tax_rate_on_capital_gains` raises the person's `capital_gains` input by $1,000. Gross income counts `capital_gains`, but the preferential-rate computation reads `long_term_capital_gains`, which that override does not change. So the extra $1,000 reaches adjusted gross income but not net capital gain, and the measured rate can reflect ordinary rates, deductions and how gains stack on other income rather than the long-term rate. In household tests for 2026, the measured rate for a Texas filer with $100 million of long-term gains is 0.296 rather than about 0.24 (20% plus the 3.8% net investment income tax) from raising long-term gains directly, with the alternative minimum tax making up the difference; for a Texas filer with $60,000 of wages and $200,000 of gains it is 0.308 rather than 0.188. Raising the top capital gains rate from 20% to 28% leaves the first filer's measured rate unchanged, so no form produces a response to that reform. All three forms take their rates from this measurement. [PolicyEngine US #10058](https://github.com/PolicyEngine/policyengine-us/pull/10058) raises long-term gains instead.
| Long-term gains | Rate | Made up of |
|---|---|---|
| $65,550 to $200,000 | 15% | The 15% bracket |
| $200,000 to $561,600 | 18.8% | The 15% bracket and the 3.8% net investment income tax |
| $561,600 to $648,000 | 23.8% | The 20% bracket and the net investment income tax |
| $648,000 to $680,200 | 33.8% | The same, plus 10 points while each dollar of gains removes 50 cents of alternative minimum tax exemption |
| Above $680,200 | 23.8% | The 20% bracket and the net investment income tax |

Separately, the measured rate stops rising once a top capital gains rate exceeds the top ordinary rate. 26 U.S.C. 1(h)(1) caps the tax on gains at the tax at ordinary rates (`limit_to_tax_at_main_rates`), so raising only the capital gains rates cannot tax large gains above about 37% plus the net investment income tax.
Raising the top rate (`gov.irs.capital_gains.rates.3`) raises the two 23.8% rows one for one and the alternative minimum tax row by one and a half times as much, so a rise from 20% to 28% is a `τ1 − τ0` of 0.08 for filers with more than $680,200 of gains. Just above 28% there is a narrow range of top rates, 28.38% to 28.40% for a filer with $20 million of gains, where the alternative minimum tax's 26% and 28% rate schedule, applied to all of the filer's income, sets the tax and the rate is 28% plus the net investment income tax. The rate follows the top rate only up to about the top ordinary rate of 37%: 26 U.S.C. 1(h)(1) caps the tax on gains at the tax on all taxable income at the ordinary rates (`limit_to_tax_at_main_rates`), so once the tax at the capital gains rates exceeds that, the ordinary rates apply. With a 40% top capital gains rate, a filer with $20 million of gains faces 37% plus the net investment income tax, while a filer with $1 million, whose tax at the capital gains rates is still the smaller, faces 40% plus it. Raising only the capital gains rates cannot tax large gains above about 37% plus the net investment income tax.
Loading
Loading