diff --git a/changelog.d/md-capital-gains-surtax-net-capital-gain.fixed.md b/changelog.d/md-capital-gains-surtax-net-capital-gain.fixed.md new file mode 100644 index 00000000000..86d15863ce4 --- /dev/null +++ b/changelog.d/md-capital-gains-surtax-net-capital-gain.fixed.md @@ -0,0 +1 @@ +Tax Maryland's 2% capital gains surtax on net capital gain as the Internal Revenue Code defines it (net long-term gain over net short-term loss, with capital gain distributions, for the head and spouse), test the $350,000 threshold on federal rather than Maryland AGI, and add an input for gain from excepted assets (Form 502CG line 8). diff --git a/policyengine_us/parameters/gov/states/md/tax/income/capital_gains/surtax_applies.yaml b/policyengine_us/parameters/gov/states/md/tax/income/capital_gains/surtax_applies.yaml index 0df5bf1d015..ad46c186ea2 100644 --- a/policyengine_us/parameters/gov/states/md/tax/income/capital_gains/surtax_applies.yaml +++ b/policyengine_us/parameters/gov/states/md/tax/income/capital_gains/surtax_applies.yaml @@ -4,10 +4,12 @@ values: 2025-01-01: true metadata: reference: + - title: Md. Code, Tax-General § 10-105(a)(3) and (4) + href: https://mgaleg.maryland.gov/mgawebsite/Laws/StatuteText?article=gtg§ion=10-105&enactments=false + - title: 2025 Md. Laws ch. 604 (HB 352), § 3, amending Tax-General § 10-105(a) + href: https://mgaleg.maryland.gov/2025RS/Chapters_noln/CH_604_hb0352e.pdf#page=163 - title: Maryland 2025 Resident Tax Forms and Instructions - Capital Gains Surtax href: https://www.marylandcomptroller.gov/content/dam/mdcomp/tax/instructions/2025/resident-booklet.pdf#page=22 - - title: Maryland House Bill 352 - Budget Reconciliation and Financing Act of 2025 - href: https://mgaleg.maryland.gov/2025RS/Chapters_noln/CH_604_hb0352e.pdf#page=163 unit: bool period: year label: Maryland capital gains surtax applies diff --git a/policyengine_us/parameters/gov/states/md/tax/income/capital_gains/surtax_rate.yaml b/policyengine_us/parameters/gov/states/md/tax/income/capital_gains/surtax_rate.yaml index 8430afd4ae0..a17d6894c1b 100644 --- a/policyengine_us/parameters/gov/states/md/tax/income/capital_gains/surtax_rate.yaml +++ b/policyengine_us/parameters/gov/states/md/tax/income/capital_gains/surtax_rate.yaml @@ -3,10 +3,12 @@ values: 2025-01-01: 0.02 metadata: reference: - - title: Maryland 2025 Resident Tax Forms and Instructions - Capital Gains Surtax - href: https://www.marylandcomptroller.gov/content/dam/mdcomp/tax/instructions/2025/resident-booklet.pdf#page=22 - - title: Maryland House Bill 352 - Budget Reconciliation and Financing Act of 2025 + - title: Md. Code, Tax-General § 10-105(a)(3)(i)2 + href: https://mgaleg.maryland.gov/mgawebsite/Laws/StatuteText?article=gtg§ion=10-105&enactments=false + - title: 2025 Md. Laws ch. 604 (HB 352), § 3, amending Tax-General § 10-105(a)(3) href: https://mgaleg.maryland.gov/2025RS/Chapters_noln/CH_604_hb0352e.pdf#page=163 + - title: 2025 Maryland Resident Income Tax Instructions, line 21b + href: https://www.marylandcomptroller.gov/content/dam/mdcomp/tax/instructions/2025/resident-booklet.pdf#page=22 unit: /1 period: year label: Maryland capital gains surtax rate diff --git a/policyengine_us/parameters/gov/states/md/tax/income/capital_gains/surtax_threshold.yaml b/policyengine_us/parameters/gov/states/md/tax/income/capital_gains/surtax_threshold.yaml index c36ecbbfd50..8bd0df11f07 100644 --- a/policyengine_us/parameters/gov/states/md/tax/income/capital_gains/surtax_threshold.yaml +++ b/policyengine_us/parameters/gov/states/md/tax/income/capital_gains/surtax_threshold.yaml @@ -1,12 +1,14 @@ -description: Maryland applies the capital gains surtax to filers with adjusted gross income exceeding this threshold. +description: Maryland applies the capital gains surtax to filers with federal adjusted gross income exceeding this threshold. values: 2025-01-01: 350_000 metadata: reference: - - title: Maryland 2025 Resident Tax Forms and Instructions - Capital Gains Surtax - href: https://www.marylandcomptroller.gov/content/dam/mdcomp/tax/instructions/2025/resident-booklet.pdf#page=22 - - title: Maryland House Bill 352 - Budget Reconciliation and Financing Act of 2025 + - title: Md. Code, Tax-General § 10-105(a)(4) + href: https://mgaleg.maryland.gov/mgawebsite/Laws/StatuteText?article=gtg§ion=10-105&enactments=false + - title: 2025 Md. Laws ch. 604 (HB 352), § 3, amending Tax-General § 10-105(a)(4) href: https://mgaleg.maryland.gov/2025RS/Chapters_noln/CH_604_hb0352e.pdf#page=164 + - title: 2025 Maryland Form 502CG instructions, general instructions + href: https://www.marylandcomptroller.gov/content/dam/mdcomp/tax/forms/2025/502cg.pdf#page=2 unit: currency-USD period: year - label: Maryland capital gains surtax AGI threshold + label: Maryland capital gains surtax federal AGI threshold diff --git a/policyengine_us/tests/policy/baseline/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax.yaml b/policyengine_us/tests/policy/baseline/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax.yaml index 1e12d9fed73..71e6db37807 100644 --- a/policyengine_us/tests/policy/baseline/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax.yaml +++ b/policyengine_us/tests/policy/baseline/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax.yaml @@ -1,4 +1,21 @@ -- name: MD Capital gains surtax 2024 - Not yet effective +# Md. Code, Tax-General 10-105(a)(3) and (4): an additional 2% of the net +# capital gain, as defined under the Internal Revenue Code, included in +# Maryland adjusted gross income, for filers with federal adjusted gross +# income over $350,000. Net capital gain is the excess of the net long-term +# capital gain over the net short-term capital loss (26 U.S.C. 1222(11); +# Technical Bulletin No. 58, section I): the smaller of Schedule D lines 15 +# and 16, not below zero. +# +# 2025 Form 502CG: line 1 is that net capital gain, line 8 the part from +# excepted assets, and line 9 = line 1 - line 8, not below zero. Form 502 +# line 21b = line 9 (carried to line 20a) x 2%. +# +# Form 502CG line 1 points to Form 502 line 1c, the federal Form 1040 line 7a +# gain, which also counts a net short-term capital gain. Where a case has a +# net short-term gain its comment gives the line 7a figure too; the model +# follows the statute. + +- name: Not yet in effect in 2024 period: 2024 absolute_error_margin: 0.01 input: @@ -17,7 +34,7 @@ output: md_capital_gains_surtax: 0 -- name: MD Capital gains surtax 2025 - Below AGI threshold (no surtax) +- name: Federal AGI below the threshold period: 2025 absolute_error_margin: 0.01 input: @@ -34,9 +51,11 @@ members: [person1] state_code: MD output: + # Form 502CG line 1 is $50,000, but federal AGI is not over $350,000. + md_net_capital_gain: 50_000 md_capital_gains_surtax: 0 -- name: MD Capital gains surtax 2025 - At AGI threshold (no surtax) +- name: Federal AGI at the threshold is not over it period: 2025 absolute_error_margin: 0.01 input: @@ -55,7 +74,26 @@ output: md_capital_gains_surtax: 0 -- name: MD Capital gains surtax 2025 - Above AGI threshold with capital gains +- name: Federal AGI one dollar over the threshold + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + long_term_capital_gains: 100_000 + tax_units: + tax_unit: + members: [person1] + adjusted_gross_income: 350_001 + households: + household: + members: [person1] + state_code: MD + output: + # Line 1 = line 9 = $100,000; $100,000 x 2% = $2,000. + md_capital_gains_surtax: 2_000 + +- name: A net short-term capital gain is not net capital gain period: 2025 absolute_error_margin: 0.01 input: @@ -72,11 +110,14 @@ members: [person1] state_code: MD output: - # Net capital gains: $100,000 + $50,000 = $150,000 - # Surtax: $150,000 * 2% = $3,000 - md_capital_gains_surtax: 3_000 + # Schedule D line 15 = $100,000, line 16 = $150,000. + # Form 502CG line 1 = the smaller, $100,000; line 9 = $100,000. + # $100,000 x 2% = $2,000. (Form 1040 line 7a is $150,000, which would + # give $3,000.) + md_net_capital_gain: 100_000 + md_capital_gains_surtax: 2_000 -- name: MD Capital gains surtax 2025 - Long-term gains only +- name: Long-term gains only period: 2025 absolute_error_margin: 0.01 input: @@ -93,11 +134,10 @@ members: [person1] state_code: MD output: - # Net capital gains: $200,000 - # Surtax: $200,000 * 2% = $4,000 + # Line 1 = line 9 = $200,000; $200,000 x 2% = $4,000. md_capital_gains_surtax: 4_000 -- name: MD Capital gains surtax 2025 - Short-term gains only +- name: Short-term gains only period: 2025 absolute_error_margin: 0.01 input: @@ -114,19 +154,21 @@ members: [person1] state_code: MD output: - # Net capital gains: $80,000 - # Surtax: $80,000 * 2% = $1,600 - md_capital_gains_surtax: 1_600 + # Schedule D line 15 = $0, line 16 = $80,000. No net long-term capital + # gain, so no net capital gain: line 1 = $0. (Form 1040 line 7a is + # $80,000, which would give $1,600.) + md_net_capital_gain: 0 + md_capital_gains_surtax: 0 -- name: MD Capital gains surtax 2025 - With capital losses (no net gains) +- name: Short-term losses larger than the long-term gains period: 2025 absolute_error_margin: 0.01 input: people: person1: long_term_capital_gains: 50_000 - short_term_capital_gains: 25_000 - capital_losses: 80_000 + # $25,000 of short-term gains less $80,000 of short-term losses. + short_term_capital_gains: -55_000 tax_units: tax_unit: members: [person1] @@ -136,13 +178,13 @@ members: [person1] state_code: MD output: - # Gross gains: $50,000 + $25,000 = $75,000 - # Capital losses limited to $3,000 federal cap - # Net gains after losses: max($75,000 - $3,000, 0) = $72,000 - # Surtax: $72,000 * 2% = $1,440 - md_capital_gains_surtax: 1_440 + # Schedule D line 15 = $50,000, line 16 = -$5,000. + # Line 1 = max(0, min($50,000, -$5,000)) = $0. The $3,000 capital loss + # deduction is part of the same netting and is not subtracted again. + md_net_capital_gain: 0 + md_capital_gains_surtax: 0 -- name: MD Capital gains surtax 2025 - With capital losses (partial offset) +- name: Short-term losses partly offset the long-term gains period: 2025 absolute_error_margin: 0.01 input: @@ -160,14 +202,14 @@ members: [person1] state_code: MD output: - # Federal Schedule D line 16 = $120,000 - $20,000 = $100,000. Losses up - # to the gains are allowed in full (26 U.S.C. 1211(b)); the $3,000 cap - # applies only to losses in excess of gains. - # 2025 Form 502CG line 1 (Form 502 line 1c, federal line 7a): $100,000 - # Surtax: $100,000 * 2% = $2,000 + # Schedule D line 15 = $120,000, line 16 = $100,000. Losses up to the + # gains are allowed in full (26 U.S.C. 1211(b)); the $3,000 limit applies + # only to losses in excess of gains. + # Form 502CG line 1 = $100,000; line 9 = $100,000. + # $100,000 x 2% = $2,000. md_capital_gains_surtax: 2_000 -- name: MD Capital gains surtax 2025 - One spouse's loss within the other's gains +- name: One spouse's loss within the other's gains period: 2025 absolute_error_margin: 0.01 input: @@ -190,15 +232,40 @@ members: [person1, person2] state_code: MD output: - # Joint federal Schedule D line 16 = $100,000 - $20,000 = $80,000. The - # loss is within the gains, so 26 U.S.C. 1211(b) allows it in full and - # no $3,000 capped loss is left to subtract again. - # 2025 Form 502CG line 1 (federal line 7a): $80,000 - # Surtax: $80,000 * 2% = $1,600 (subtracting a $3,000 capped loss again - # gave $1,540) + # Joint Schedule D line 15 = $100,000, line 16 = $80,000. The loss is + # within the gains, so 26 U.S.C. 1211(b) allows it in full and no $3,000 + # capped loss is left to subtract again. + # Form 502CG line 1 = $80,000. Surtax: $80,000 x 2% = $1,600. md_capital_gains_surtax: 1_600 -- name: MD Capital gains surtax 2025 - High earner with large gains +- name: One spouse's short-term gain is not net capital gain + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 50 + long_term_capital_gains: 100_000 + person2: + age: 48 + short_term_capital_gains: 30_000 + tax_units: + tax_unit: + members: [person1, person2] + adjusted_gross_income: 450_000 + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MD + output: + # Joint Schedule D line 15 = $100,000, line 16 = $130,000. + # Form 502CG line 1 = $100,000. Surtax: $100,000 x 2% = $2,000. + md_capital_gains_surtax: 2_000 + +- name: High earner with large long-term and short-term gains period: 2025 absolute_error_margin: 0.01 input: @@ -215,32 +282,228 @@ members: [person1] state_code: MD output: - # Net capital gains: $500,000 + $200,000 = $700,000 - # Surtax: $700,000 * 2% = $14,000 - md_capital_gains_surtax: 14_000 + # Schedule D line 15 = $500,000, line 16 = $700,000. + # Form 502CG line 1 = $500,000. Surtax: $500,000 x 2% = $10,000. + md_capital_gains_surtax: 10_000 -# NOTE: The following test case represents a limitation of the current implementation -# In reality, primary residence sales under $1.5M should be excluded from the surtax -- name: MD Capital gains surtax 2025 - Limitation test (includes all gains) +- name: A net long-term capital loss leaves no net capital gain period: 2025 absolute_error_margin: 0.01 input: people: person1: - long_term_capital_gains: 300_000 # Could include primary residence sale - short_term_capital_gains: 0 + long_term_capital_gains: -10_000 + short_term_capital_gains: 50_000 + tax_units: + tax_unit: + members: [person1] + adjusted_gross_income: 450_000 + households: + household: + members: [person1] + state_code: MD + output: + # Schedule D line 15 = -$10,000, line 16 = $40,000. + # No net long-term capital gain: line 1 = $0. (Form 1040 line 7a is + # $40,000, which would give $800.) + md_net_capital_gain: 0 + md_capital_gains_surtax: 0 + +- name: Capital gain distributions are net capital gain + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + employment_income: 400_000 + # Form 1099-DIV box 2a, reported on Form 1040 line 7a without + # Schedule D. + non_sch_d_capital_gains: 20_000 + households: + household: + members: [person1] + state_code: MD + output: + # Capital gain distributions are long-term capital gains (26 U.S.C. + # 852(b)(3)(B)). Federal AGI = $420,000. + # Form 502CG line 1 = $20,000. Surtax: $20,000 x 2% = $400. + md_net_capital_gain: 20_000 + md_capital_gains_surtax: 400 + +- name: Capital gain distributions net against a short-term loss + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + non_sch_d_capital_gains: 10_000 + short_term_capital_gains: -4_000 + tax_units: + tax_unit: + members: [person1] + adjusted_gross_income: 400_000 + households: + household: + members: [person1] + state_code: MD + output: + # With a capital loss the distributions go on Schedule D line 13. + # Line 15 = $10,000, line 16 = $6,000. + # Form 502CG line 1 = $6,000. Surtax: $6,000 x 2% = $120. + md_net_capital_gain: 6_000 + md_capital_gains_surtax: 120 + +- name: A negative distributions input counts as zero + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + long_term_capital_gains: 10_000 + non_sch_d_capital_gains: -5_000 + tax_units: + tax_unit: + members: [person1] + adjusted_gross_income: 400_000 + households: + household: + members: [person1] + state_code: MD + output: + # Form 1099-DIV box 2a amounts are paid amounts and never negative. + # Line 1 = $10,000. Surtax: $10,000 x 2% = $200. + md_net_capital_gain: 10_000 + md_capital_gains_surtax: 200 + +- name: A dependent's gains are on the dependent's own return + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 400_000 + long_term_capital_gains: 10_000 + child: + age: 10 + long_term_capital_gains: 300_000 + household: + state_code: MD + output: + # The head's federal AGI is $410,000. Only the head's gain is on the + # head's return: line 1 = $10,000. Surtax: $10,000 x 2% = $200. + md_net_capital_gain: 10_000 + md_capital_gains_surtax: 200 + +- name: A dependent's loss does not reduce the filer's gain + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 400_000 + long_term_capital_gains: 100_000 + child: + age: 10 + short_term_capital_gains: -50_000 + household: + state_code: MD + output: + # Line 1 = $100,000 from the head's return. Surtax: $2,000. + md_net_capital_gain: 100_000 + md_capital_gains_surtax: 2_000 + +- name: Gain from excepted assets is not subject to the surtax + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + long_term_capital_gains: 300_000 tax_units: tax_unit: members: [person1] adjusted_gross_income: 750_000 + # Form 502CG line 2: $200,000 of the gain is from a primary + # residence sold for less than $1,500,000. + md_capital_gains_surtax_excepted_gain: 200_000 households: household: members: [person1] state_code: MD output: - # Current implementation includes ALL capital gains - # Real implementation should exclude primary residence < $1.5M - # This test documents current behavior, not ideal behavior - # Net capital gains: $300,000 (should potentially be less with exclusions) - # Surtax: $300,000 * 2% = $6,000 (may overstate actual liability) - md_capital_gains_surtax: 6_000 + # Line 1 = $300,000; line 8 = $200,000; line 9 = $100,000. + # Surtax: $100,000 x 2% = $2,000. + md_capital_gains_surtax: 2_000 + +- name: Excepted gain larger than line 1 leaves nothing subject to the surtax + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + long_term_capital_gains: 120_000 + short_term_capital_gains: -70_000 + tax_units: + tax_unit: + members: [person1] + adjusted_gross_income: 750_000 + md_capital_gains_surtax_excepted_gain: 80_000 + households: + household: + members: [person1] + state_code: MD + output: + # Line 1 = $50,000; line 8 = $80,000; line 9 = max(0, -$30,000) = $0. + md_net_capital_gain: 50_000 + md_capital_gains_surtax: 0 + +- name: Technical Bulletin No. 58 example (Jane McDonald) + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + # TB-58 section III.A: $10,000 from horses (Form 4797), $50,000 from + # E.I.O., LLC (of which $32,000 from cattle), a $400,000 gain on her + # principal residence sold for $1.2 million, and a $150,000 gain on + # her beach house. The bulletin enters all $610,000 on line 1. + long_term_capital_gains: 610_000 + tax_units: + tax_unit: + members: [person1] + # "FAGI in excess of $350,000" + adjusted_gross_income: 900_000 + # Line 2 (residence) $400,000 + line 4 (horses and cattle) $42,000. + md_capital_gains_surtax_excepted_gain: 442_000 + households: + household: + members: [person1] + state_code: MD + output: + # TB-58 page 3, Form 502CG: line 1 = $610,000, line 1a = $50,000, + # line 2 = $400,000, line 4 = $42,000, line 8 = $442,000, + # line 9 = $168,000. Form 502 line 21b: $168,000 x 2% = $3,360. + md_net_capital_gain: 610_000 + md_capital_gains_surtax: 3_360 + +- name: No surtax outside Maryland + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + long_term_capital_gains: 200_000 + tax_units: + tax_unit: + members: [person1] + adjusted_gross_income: 600_000 + households: + household: + members: [person1] + state_code: VA + output: + md_capital_gains_surtax: 0 diff --git a/policyengine_us/tests/policy/baseline/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax_applies.yaml b/policyengine_us/tests/policy/baseline/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax_applies.yaml new file mode 100644 index 00000000000..14895d1ffdc --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax_applies.yaml @@ -0,0 +1,108 @@ +# Md. Code, Tax-General 10-105(a)(4): the additional tax applies to +# individuals "with a federal adjusted gross income in excess of $350,000". +# 2025 Form 502CG general instructions: "a federal adjusted gross income of +# more than $350,000 on Line 1 of Form 502". The test is on federal AGI, not +# Maryland AGI, and is the same for every filing status. + +- name: Maryland subtractions do not take federal AGI under the threshold + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + long_term_capital_gains: 100_000 + tax_units: + tax_unit: + members: [person1] + adjusted_gross_income: 360_000 + md_total_subtractions: 20_000 + households: + household: + members: [person1] + state_code: MD + output: + # Maryland AGI = $360,000 - $20,000 = $340,000, but federal AGI is over + # $350,000. Form 502CG line 1 = $100,000; surtax $2,000. + md_agi: 340_000 + md_capital_gains_surtax_applies: true + md_capital_gains_surtax: 2_000 + +- name: Maryland additions do not put federal AGI over the threshold + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + long_term_capital_gains: 100_000 + tax_units: + tax_unit: + members: [person1] + adjusted_gross_income: 340_000 + md_total_additions: 20_000 + households: + household: + members: [person1] + state_code: MD + output: + md_agi: 360_000 + md_capital_gains_surtax_applies: false + md_capital_gains_surtax: 0 + +- name: Federal AGI equal to the threshold + period: 2025 + input: + people: + person1: + long_term_capital_gains: 100_000 + tax_units: + tax_unit: + members: [person1] + adjusted_gross_income: 350_000 + households: + household: + members: [person1] + state_code: MD + output: + md_capital_gains_surtax_applies: false + +- name: Joint filers face the same threshold + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 50 + long_term_capital_gains: 40_000 + person2: + age: 48 + tax_units: + tax_unit: + members: [person1, person2] + adjusted_gross_income: 351_000 + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MD + output: + md_capital_gains_surtax_applies: true + md_capital_gains_surtax: 800 + +- name: Not in effect before 2025 + period: 2024 + input: + people: + person1: + long_term_capital_gains: 100_000 + tax_units: + tax_unit: + members: [person1] + adjusted_gross_income: 500_000 + households: + household: + members: [person1] + state_code: MD + output: + md_capital_gains_surtax_applies: false diff --git a/policyengine_us/tests/policy/baseline/gov/states/md/tax/income/capital_gains/md_net_capital_gain.yaml b/policyengine_us/tests/policy/baseline/gov/states/md/tax/income/capital_gains/md_net_capital_gain.yaml new file mode 100644 index 00000000000..c02a8e28ce0 --- /dev/null +++ b/policyengine_us/tests/policy/baseline/gov/states/md/tax/income/capital_gains/md_net_capital_gain.yaml @@ -0,0 +1,160 @@ +# 2025 Form 502CG line 1: net capital gain, as defined under 26 U.S.C. +# 1222(11), included in Maryland adjusted gross income (Md. Code, Tax-General +# 10-105(a)(3)(i)). Over the head and spouse: +# Schedule D line 15 = long-term gains and losses + capital gain +# distributions reported without Schedule D +# Schedule D line 16 = line 15 + short-term gains and losses +# line 1 = max(0, min(line 15, line 16)) + +- name: Spouses' long-term gains and losses net, then a short-term loss + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 50 + long_term_capital_gains: -30_000 + person2: + age: 48 + long_term_capital_gains: 100_000 + short_term_capital_gains: -10_000 + tax_units: + tax_unit: + members: [person1, person2] + adjusted_gross_income: 500_000 + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MD + output: + # Line 15 = -$30,000 + $100,000 = $70,000; line 16 = $60,000. + md_net_capital_gain: 60_000 + +- name: Spouses' short-term amounts net to a gain, which adds nothing + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 50 + long_term_capital_gains: 50_000 + short_term_capital_gains: 20_000 + person2: + age: 48 + short_term_capital_gains: -15_000 + tax_units: + tax_unit: + members: [person1, person2] + adjusted_gross_income: 500_000 + marital_units: + marital_unit: + members: [person1, person2] + households: + household: + members: [person1, person2] + state_code: MD + output: + # Line 15 = $50,000; line 16 = $55,000; line 1 = $50,000. + md_net_capital_gain: 50_000 + +- name: A long-term loss carryover already in long-term gains is not subtracted again + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + # $20,000 of 2025 long-term gains less an $8,000 carryover (Schedule + # D line 14). long_term_capital_loss_carryover is a memo item. + long_term_capital_gains: 12_000 + long_term_capital_loss_carryover: 8_000 + tax_units: + tax_unit: + members: [person1] + adjusted_gross_income: 500_000 + households: + household: + members: [person1] + state_code: MD + output: + md_net_capital_gain: 12_000 + +- name: Schedule D line 13 distributions are already in long-term gains + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + long_term_capital_gains: 15_000 + # Memo item: $5,000 of the $15,000 is distributions on line 13. + schedule_d_capital_gain_distributions: 5_000 + tax_units: + tax_unit: + members: [person1] + adjusted_gross_income: 500_000 + households: + household: + members: [person1] + state_code: MD + output: + md_net_capital_gain: 15_000 + +- name: Qualified dividends are not net capital gain + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + long_term_capital_gains: 10_000 + qualified_dividend_income: 50_000 + tax_units: + tax_unit: + members: [person1] + adjusted_gross_income: 500_000 + households: + household: + members: [person1] + state_code: MD + output: + # 26 U.S.C. 1(h)(11) treats qualified dividends as net capital gain only + # for section 1(h). Line 1 = $10,000. + md_net_capital_gain: 10_000 + +- name: A dependent's capital gain distributions are on the dependent's own return + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + is_tax_unit_head: true + age: 40 + employment_income: 400_000 + long_term_capital_gains: 10_000 + child: + age: 12 + non_sch_d_capital_gains: 5_000 + short_term_capital_gains: 3_000 + household: + state_code: MD + output: + md_net_capital_gain: 10_000 + +- name: Not computed outside Maryland + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person1: + long_term_capital_gains: 10_000 + tax_units: + tax_unit: + members: [person1] + adjusted_gross_income: 500_000 + households: + household: + members: [person1] + state_code: DC + output: + md_net_capital_gain: 0 diff --git a/policyengine_us/tests/test_md_capital_gains_surtax_invariants.py b/policyengine_us/tests/test_md_capital_gains_surtax_invariants.py new file mode 100644 index 00000000000..2d9ec3bf3fb --- /dev/null +++ b/policyengine_us/tests/test_md_capital_gains_surtax_invariants.py @@ -0,0 +1,327 @@ +"""Invariants for Maryland's additional 2% tax on net capital gain. + +Md. Code, Tax-General 10-105(a)(3) taxes 2% of the "net capital gain, as +defined and determined under the Internal Revenue Code" included in Maryland +adjusted gross income, less the gain from excepted assets, and (a)(4) +applies it when federal adjusted gross income is over $350,000. Net capital +gain is 26 U.S.C. 1222(11): the net long-term capital gain less the net +short-term capital loss. Over the head and spouse, with long-term (LT) and +short-term (ST) gains and losses and capital gain distributions reported +without Schedule D (D): + + L15 = sum(LT + max(0, D)) Schedule D line 15 + L16 = L15 + sum(ST) Schedule D line 16 + NCG = max(0, min(L15, L16)) Form 502CG line 1 + +The properties below hold for every household: + +1. md_net_capital_gain equals NCG, computed here from the inputs. +2. Differential: for a return with no dependents and no negative + distributions, md_net_capital_gain equals the federal net_capital_gain + (a separate 1222(11) implementation) less qualified dividends, which + section 1(h)(11) adds only for section 1(h). +3. md_net_capital_gain never exceeds the positive part of the filers' Form + 1040 line 7a gain (filer_loss_limited_net_capital_gains), so it is all + included in federal, and hence Maryland, adjusted gross income. +4. The surtax applies exactly when federal adjusted gross income is over + $350,000, and is then 2% of max(0, NCG - excepted gain) (Form 502CG line + 9); otherwise it is zero. It is between 0 and 2% of NCG. +5. Changing a dependent's gains, losses or distributions changes neither + md_net_capital_gain nor the surtax. +6. Raising a filer's LT, ST or D by x raises md_net_capital_gain by between + 0 and x, and a net short-term gain adds nothing. Raising the excepted gain + never raises the surtax. +7. The surtax enters Maryland income tax before credits once: neutralizing + it lowers that tax by exactly the surtax. + +Amounts are whole dollars below $2,000,000, so every sum is exact in single +precision; only the 2% products are compared with a cent of tolerance. +""" + +import itertools + +import numpy as np +import pytest + +from policyengine_core.reforms import Reform + +from policyengine_us import Simulation + +YEAR = 2025 +RATE = 0.02 +THRESHOLD = 350_000 +TOLERANCE = 0.01 +AGES = {"head": 45, "spouse": 43, "dependent": 10} + + +class neutralize_md_capital_gains_surtax(Reform): + def apply(self): + self.neutralize_variable("md_capital_gains_surtax") + + +def person(role, lt=0, st=0, d=0, qd=0, wages=0): + return dict(role=role, lt=lt, st=st, d=d, qd=qd, wages=wages) + + +def household(people, excepted=0): + return dict(people=people, excepted=excepted) + + +def filers(h): + return [p for p in h["people"] if p["role"] != "dependent"] + + +def reference_net_capital_gain(h): + line_15 = sum(p["lt"] + max(0, p["d"]) for p in filers(h)) + line_16 = line_15 + sum(p["st"] for p in filers(h)) + return max(0, min(line_15, line_16)) + + +def build_situation(households): + y = str(YEAR) + people, tax_units, marital_units, households_out = {}, {}, {}, {} + for i, h in enumerate(households): + members = [] + adults = [] + for j, p in enumerate(h["people"]): + name = f"p_{i}_{j}" + people[name] = { + "age": {y: AGES[p["role"]]}, + "long_term_capital_gains": {y: p["lt"]}, + "short_term_capital_gains": {y: p["st"]}, + "non_sch_d_capital_gains": {y: p["d"]}, + "qualified_dividend_income": {y: p["qd"]}, + "employment_income": {y: p["wages"]}, + } + members.append(name) + if p["role"] == "dependent": + marital_units[f"mu_{i}_{j}"] = {"members": [name]} + else: + adults.append(name) + marital_units[f"mu_{i}"] = {"members": adults} + # Set on every unit: an input given for some units gives the others + # its default. + tax_units[f"tu_{i}"] = { + "members": members, + "md_capital_gains_surtax_excepted_gain": {y: h["excepted"]}, + } + households_out[f"hh_{i}"] = { + "members": members, + "state_code": {y: "MD"}, + } + return { + "people": people, + "tax_units": tax_units, + "marital_units": marital_units, + "households": households_out, + } + + +TAX_UNIT_OUTPUTS = [ + "md_net_capital_gain", + "md_capital_gains_surtax", + "md_capital_gains_surtax_applies", + "adjusted_gross_income", + "net_capital_gain", + "filer_loss_limited_net_capital_gains", + "md_income_tax_before_credits", +] + + +def calculate(households, neutralized=False): + situation = build_situation(households) + simulation = Simulation(situation=situation) + out = {v: np.asarray(simulation.calculate(v, YEAR)) for v in TAX_UNIT_OUTPUTS} + dependent = np.asarray(simulation.calculate("is_tax_unit_dependent", YEAR)) + roles = [p["role"] for h in households for p in h["people"]] + # Guard the fixture: the model must see the roles the reference assumes. + assert list(dependent) == [r == "dependent" for r in roles] + if neutralized: + without = Simulation( + situation=situation, reform=neutralize_md_capital_gains_surtax + ) + out["md_tax_without_surtax"] = np.asarray( + without.calculate("md_income_tax_before_credits", YEAR) + ) + return out + + +def assert_single_household_properties(h, r, i): + ncg = reference_net_capital_gain(h) + # 1. Form 502CG line 1. + assert r["md_net_capital_gain"][i] == ncg, h + # 2. Differential with the federal 1222(11) path. + no_dependents = len(filers(h)) == len(h["people"]) + if no_dependents and all(p["d"] >= 0 for p in h["people"]): + qualified_dividends = sum(p["qd"] for p in h["people"]) + assert r["md_net_capital_gain"][i] == ( + r["net_capital_gain"][i] - qualified_dividends + ), h + # 3. Included in adjusted gross income. + assert r["md_net_capital_gain"][i] <= max( + 0, r["filer_loss_limited_net_capital_gains"][i] + ), h + # 4. Threshold on federal AGI, and Form 502CG line 9. + over = r["adjusted_gross_income"][i] > THRESHOLD + assert bool(r["md_capital_gains_surtax_applies"][i]) == over, h + expected = RATE * max(0, ncg - h["excepted"]) if over else 0 + surtax = float(r["md_capital_gains_surtax"][i]) + assert abs(surtax - expected) <= TOLERANCE, h + assert -TOLERANCE <= surtax <= RATE * ncg + TOLERANCE, h + # 7. Added once to Maryland income tax before credits. + if "md_tax_without_surtax" in r: + added = float(r["md_income_tax_before_credits"][i]) - float( + r["md_tax_without_surtax"][i] + ) + assert abs(added - surtax) <= TOLERANCE, h + + +def with_change(h, role, field, delta): + people = [dict(p) for p in h["people"]] + for p in people: + if p["role"] == role: + p[field] += delta + break + return household(people, h["excepted"]) + + +def twins(h, delta): + """Households paired with h for properties 5 and 6.""" + pairs = [] + if any(p["role"] == "dependent" for p in h["people"]): + for field in ["lt", "st", "d"]: + pairs.append( + ("dependent", field, with_change(h, "dependent", field, delta)) + ) + for field in ["lt", "st", "d"]: + pairs.append(("head", field, with_change(h, "head", field, delta))) + pairs.append(("excepted", None, household(h["people"], h["excepted"] + delta))) + return pairs + + +def assert_properties(households, delta, neutralized=False): + batch, index = [], [] + for h in households: + base = len(batch) + batch.append(h) + for kind, field, twin in twins(h, delta): + index.append((base, len(batch), kind, field)) + batch.append(twin) + r = calculate(batch, neutralized=neutralized) + for i, h in enumerate(batch): + assert_single_household_properties(h, r, i) + ncg = r["md_net_capital_gain"].astype(float) + surtax = r["md_capital_gains_surtax"].astype(float) + for base, twin, kind, field in index: + if kind == "dependent": + # 5. Dependent invariance. + assert ncg[twin] == ncg[base], batch[twin] + assert surtax[twin] == surtax[base], batch[twin] + elif kind == "head": + # 6. Monotone, with slope at most 1. + change = ncg[twin] - ncg[base] + assert 0 <= change <= delta, (batch[base], field) + if field == "st" and sum(p["st"] for p in filers(batch[base])) >= 0: + # A net short-term gain adds nothing. + assert change == 0, (batch[base], field) + else: + assert surtax[twin] <= surtax[base] + TOLERANCE, batch[twin] + + +# --------------------------------------------------------------------------- +# A deterministic grid (runs without Hypothesis). +# --------------------------------------------------------------------------- + +LT = [-60_000, -5_000, 0, 40_000, 250_000] +ST = [-80_000, -10_000, 0, 30_000] +D = [-2_000, 0, 7_000] + + +def grid_households(): + households = [] + for lt, st_, d in itertools.product(LT, ST, D): + # A single filer near and over the threshold. + for wages in [200_000, 400_000]: + households.append(household([person("head", lt, st_, d, wages=wages)])) + # The same totals split between spouses, with qualified dividends. + households.append( + household( + [ + person("head", lt=lt, d=d, qd=5_000, wages=380_000), + person("spouse", st=st_), + ] + ) + ) + # A head with a dependent who has the same amounts, and an excepted + # gain. + households.append( + household( + [ + person("head", lt, st_, d, wages=420_000), + person("dependent", lt=lt, st=st_, d=abs(d)), + ], + excepted=20_000, + ) + ) + return households + + +def test_grid(): + assert_properties(grid_households(), delta=15_000, neutralized=True) + + +def test_cases_where_the_form_1040_line_7a_gain_differs(): + """A net short-term gain, and a net long-term loss with a short-term + gain: line 7a counts both, net capital gain neither.""" + cases = [ + household([person("head", lt=100_000, st=50_000, wages=400_000)]), + household([person("head", lt=-10_000, st=50_000, wages=400_000)]), + household([person("head", st=80_000, wages=400_000)]), + ] + r = calculate(cases) + assert list(r["md_net_capital_gain"]) == [100_000, 0, 0] + assert list(r["filer_loss_limited_net_capital_gains"]) == [150_000, 40_000, 80_000] + assert list(r["md_capital_gains_surtax"]) == [2_000, 0, 0] + + +# --------------------------------------------------------------------------- +# Hypothesis properties (Hypothesis is a dev extra). +# --------------------------------------------------------------------------- + +hypothesis = pytest.importorskip("hypothesis") +st = pytest.importorskip("hypothesis.strategies") + +GAIN = st.integers(min_value=-300_000, max_value=300_000) +DISTRIBUTIONS = st.integers(min_value=-5_000, max_value=50_000) +DIVIDENDS = st.integers(min_value=0, max_value=50_000) +WAGES = st.integers(min_value=0, max_value=800_000) + + +@st.composite +def random_household(draw): + def filer(role): + return person( + role, + draw(GAIN), + draw(GAIN), + draw(DISTRIBUTIONS), + draw(DIVIDENDS), + draw(WAGES), + ) + + people = [filer("head")] + if draw(st.booleans()): + people.append(filer("spouse")) + for _ in range(draw(st.integers(min_value=0, max_value=2))): + people.append(person("dependent", draw(GAIN), draw(GAIN), draw(DISTRIBUTIONS))) + excepted = draw(st.sampled_from([0, 0, 10_000, draw(GAIN.map(abs))])) + return household(people, excepted) + + +@hypothesis.settings(max_examples=25, deadline=None) +@hypothesis.given( + st.lists(random_household(), min_size=1, max_size=8), + st.integers(min_value=1, max_value=100_000), +) +def test_properties(households, delta): + assert_properties(households, delta) diff --git a/policyengine_us/variables/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax.py b/policyengine_us/variables/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax.py index 13027bbfbbd..ed617eb94bf 100644 --- a/policyengine_us/variables/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax.py +++ b/policyengine_us/variables/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax.py @@ -7,74 +7,39 @@ class md_capital_gains_surtax(Variable): label = "Maryland capital gains surtax" definition_period = YEAR unit = USD + documentation = ( + "The additional 2% tax on net capital gain included in Maryland " + "adjusted gross income, less the gain from excepted assets, for a " + "filer with federal adjusted gross income over $350,000. Form 502 " + "line 21b: Form 502CG line 9 (line 1 less line 8, not below zero) " + "times 2%." + ) reference = [ - "https://mgaleg.maryland.gov/2025RS/Chapters_noln/CH_604_hb0352e.pdf#page=162" # Maryland House Bill 352 - Budget Reconciliation and Financing Act of 2025 + dict( + title="Md. Code, Tax-General § 10-105(a)(3) and (4)", + href="https://mgaleg.maryland.gov/mgawebsite/Laws/StatuteText?article=gtg§ion=10-105&enactments=false", + ), + dict( + title="2025 Md. Laws ch. 604 (HB 352), § 3, amending Tax-General § 10-105(a)", + href="https://mgaleg.maryland.gov/2025RS/Chapters_noln/CH_604_hb0352e.pdf#page=163", + ), + dict( + title="2025 Maryland Form 502CG, line 9", + href="https://www.marylandcomptroller.gov/content/dam/mdcomp/tax/forms/2025/502cg.pdf#page=1", + ), + dict( + title="2025 Maryland Resident Income Tax Instructions, lines 20a and 21b", + href="https://www.marylandcomptroller.gov/content/dam/mdcomp/tax/instructions/2025/resident-booklet.pdf#page=21", + ), ] defined_for = "md_capital_gains_surtax_applies" def formula(tax_unit, period, parameters): p = parameters(period).gov.states.md.tax.income.capital_gains - - # Get net capital gains (sum of long-term and short-term) - # NOTE: This is the basic implementation using readily available variables - total_capital_gains = add( - tax_unit, - period, - ["short_term_capital_gains", "long_term_capital_gains"], - ) - - # Apply capital losses (with federal limitation) - capital_losses = tax_unit("limited_capital_loss", period) - # Use federal net capital gain calculation which handles loss limitations - net_capital_gains = max_(total_capital_gains - capital_losses, 0) - - # LIMITATIONS AND MISSING IMPLEMENTATIONS: - # - # 1. PRIMARY RESIDENCE EXCLUSION (NOT IMPLEMENTED): - # - Should exclude gains from primary residence sales above threshold - # - Missing: Section 121 exclusion (amounts vary by filing status) - # - Missing: Property sale price tracking - # - Missing: Primary residence identification - # - Reason: PolicyEngine doesn't currently model detailed property sales - # - # 2. RETIREMENT ACCOUNT EXCLUSION (PARTIALLY IMPLEMENTED): - # - Current capital_gains variables should already exclude retirement distributions - # - Retirement distributions are typically handled separately as ordinary income - # - This exclusion should already be working correctly in the current model - # - # 3. LIVESTOCK EXCLUSION (NOT IMPLEMENTED): - # - Should exclude livestock gains if >50% income from farming/ranching - # - Missing: Farming/ranching income classification - # - Missing: Livestock vs other business asset identification - # - Reason: PolicyEngine doesn't track detailed business asset types - # - # 4. CONSERVATION EASEMENT EXCLUSION (NOT IMPLEMENTED): - # - Should exclude gains from land under conservation/agricultural/forest easements - # - Missing: Property easement status tracking - # - Missing: Land use classification system - # - Reason: Requires property-specific data not available in tax records - # - # 5. IRC § 179 BUSINESS PROPERTY EXCLUSION (NOT IMPLEMENTED): - # - Should exclude gains from property eligible for § 179 deduction - # - Missing: Business property classification - # - Missing: § 179 eligibility determination - # - Reason: Requires detailed business asset tracking - # - # 6. NONPROFIT AFFORDABLE HOUSING EXCLUSION (NOT IMPLEMENTED): - # - Should exclude gains from affordable housing owned by nonprofits - # - Missing: Entity type identification (nonprofit vs individual) - # - Missing: Affordable housing property classification - # - Reason: PolicyEngine focuses on individual/household tax units - # - # CURRENT IMPLEMENTATION COVERS: - # - Basic 2% surtax rate - # - Federal AGI threshold for surtax - # - Net capital gains calculation with loss limitations - # - Retirement account exclusion (implicit in capital_gains variables) - # - # ACCURACY IMPACT: - # - Will overstate surtax liability for taxpayers with excluded gain types - # - Most significant overstatement likely from primary residence sales - # - Business-related exclusions may affect fewer taxpayers but could be material - - return net_capital_gains * p.surtax_rate + # Form 502CG line 1: net capital gain included in Maryland AGI. + net_capital_gain = tax_unit("md_net_capital_gain", period) + # Line 8: the part from excepted assets (lines 2 through 7). + excepted = tax_unit("md_capital_gains_surtax_excepted_gain", period) + # Line 9, carried to Form 502 line 20a. + subject_to_surtax = max_(0, net_capital_gain - excepted) + return subject_to_surtax * p.surtax_rate diff --git a/policyengine_us/variables/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax_applies.py b/policyengine_us/variables/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax_applies.py index 39001e8eba7..6f40ee34398 100644 --- a/policyengine_us/variables/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax_applies.py +++ b/policyengine_us/variables/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax_applies.py @@ -6,19 +6,31 @@ class md_capital_gains_surtax_applies(Variable): entity = TaxUnit label = "Maryland capital gains surtax applies" definition_period = YEAR + documentation = ( + "Whether federal adjusted gross income is more than the threshold, " + "the test in Md. Code, Tax-General 10-105(a)(4) and Form 502 line 1. " + "The threshold is on federal, not Maryland, adjusted gross income, " + "and is the same for every filing status." + ) reference = [ - "https://mgaleg.maryland.gov/2025RS/Chapters_noln/CH_604_hb0352e.pdf#page=162" # Maryland House Bill 352 - Budget Reconciliation and Financing Act of 2025 + dict( + title="Md. Code, Tax-General § 10-105(a)(4)", + href="https://mgaleg.maryland.gov/mgawebsite/Laws/StatuteText?article=gtg§ion=10-105&enactments=false", + ), + dict( + title="2025 Md. Laws ch. 604 (HB 352), § 3, amending Tax-General § 10-105(a)(4)", + href="https://mgaleg.maryland.gov/2025RS/Chapters_noln/CH_604_hb0352e.pdf#page=164", + ), + dict( + title="2025 Maryland Form 502CG instructions, general instructions", + href="https://www.marylandcomptroller.gov/content/dam/mdcomp/tax/forms/2025/502cg.pdf#page=2", + ), ] defined_for = StateCode.MD def formula(tax_unit, period, parameters): p = parameters(period).gov.states.md.tax.income.capital_gains - - # First check if surtax is enabled for this year if not p.surtax_applies: return False - - # Check Maryland adjusted gross income threshold - md_agi = tax_unit("md_agi", period) - - return md_agi > p.surtax_threshold + federal_agi = tax_unit("adjusted_gross_income", period) + return federal_agi > p.surtax_threshold diff --git a/policyengine_us/variables/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax_excepted_gain.py b/policyengine_us/variables/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax_excepted_gain.py new file mode 100644 index 00000000000..532d2142c61 --- /dev/null +++ b/policyengine_us/variables/gov/states/md/tax/income/capital_gains/md_capital_gains_surtax_excepted_gain.py @@ -0,0 +1,39 @@ +from policyengine_us.model_api import * + + +class md_capital_gains_surtax_excepted_gain(Variable): + value_type = float + entity = TaxUnit + label = ( + "Maryland net capital gain from assets excepted from the capital gains surtax" + ) + unit = USD + documentation = ( + "Net capital gain included in md_net_capital_gain that comes from the " + "sale or exchange of an asset Md. Code, Tax-General 10-105(a)(3)(ii) " + "excepts from the additional 2% tax: a primary residence sold for " + "less than $1,500,000; assets held in a 401(k), 403(b), 457(b), IRA, " + "Roth IRA or other retirement savings plan; cattle, horses or " + "breeding livestock held more than 12 months by a taxpayer with more " + "than half of gross income from farming or ranching; land under a " + "conservation, agricultural or forest preservation easement; " + "property used in a trade or business whose cost is deductible under " + "IRC 179; and affordable housing owned by a nonprofit. Form 502CG " + "line 8, the sum of lines 2 through 7. Zero unless entered." + ) + definition_period = YEAR + reference = [ + dict( + title="Md. Code, Tax-General § 10-105(a)(3)(ii)", + href="https://mgaleg.maryland.gov/mgawebsite/Laws/StatuteText?article=gtg§ion=10-105&enactments=false", + ), + dict( + title="Maryland Comptroller Technical Bulletin No. 58 (Dec. 29, 2025), sections II and III.A", + href="https://www.marylandcomptroller.gov/content/dam/mdcomp/tax/legal-publications/technical-bulletins/tb-58.pdf#page=1", + ), + dict( + title="2025 Maryland Form 502CG, lines 2 through 8", + href="https://www.marylandcomptroller.gov/content/dam/mdcomp/tax/forms/2025/502cg.pdf#page=1", + ), + ] + defined_for = StateCode.MD diff --git a/policyengine_us/variables/gov/states/md/tax/income/capital_gains/md_net_capital_gain.py b/policyengine_us/variables/gov/states/md/tax/income/capital_gains/md_net_capital_gain.py new file mode 100644 index 00000000000..18a6c516e12 --- /dev/null +++ b/policyengine_us/variables/gov/states/md/tax/income/capital_gains/md_net_capital_gain.py @@ -0,0 +1,70 @@ +from policyengine_us.model_api import * + + +class md_net_capital_gain(Variable): + value_type = float + entity = TaxUnit + label = "Maryland net capital gain included in Maryland adjusted gross income" + unit = USD + documentation = ( + "Net capital gain as defined and determined under the Internal " + "Revenue Code, 26 U.S.C. 1222(11): the excess of the net long-term " + "capital gain over the net short-term capital loss. This is the " + "amount included in Maryland adjusted gross income that Md. Code, " + "Tax-General 10-105(a)(3)(i)2 taxes at an additional 2%, before the " + "excepted assets of (a)(3)(ii). Form 502CG line 1. A net short-term " + "capital gain is not part of it, and neither are qualified dividends, " + "which section 1(h)(11) adds only for section 1(h). Only the head's " + "and spouse's gains count; a tax unit dependent's gains are on the " + "dependent's own return." + ) + definition_period = YEAR + reference = [ + dict( + title="Md. Code, Tax-General § 10-105(a)(3)", + href="https://mgaleg.maryland.gov/mgawebsite/Laws/StatuteText?article=gtg§ion=10-105&enactments=false", + ), + dict( + title="26 U.S. Code § 1222(11)", + href="https://www.law.cornell.edu/uscode/text/26/1222#11", + ), + dict( + title="Maryland Comptroller Technical Bulletin No. 58, Maryland Taxation of Individual Capital Gain Income (Dec. 29, 2025), section I", + href="https://www.marylandcomptroller.gov/content/dam/mdcomp/tax/legal-publications/technical-bulletins/tb-58.pdf#page=1", + ), + dict( + title="2025 Maryland Form 502CG, line 1", + href="https://www.marylandcomptroller.gov/content/dam/mdcomp/tax/forms/2025/502cg.pdf#page=1", + ), + ] + defined_for = StateCode.MD + + def formula(tax_unit, period, parameters): + person = tax_unit.members + filer = ~person("is_tax_unit_dependent", period) + # Capital gain distributions reported without Schedule D are + # long-term capital gains (26 U.S.C. 852(b)(3)(B)). Form 1099-DIV box + # 2a amounts are not negative, so each filer's input is floored at + # zero, as in irs_gross_income. Distributions entered on Schedule D + # line 13, and long-term loss carryovers, are already in + # long_term_capital_gains. + distributions = tax_unit.sum( + filer * max_(0, person("non_sch_d_capital_gains", period)) + ) + # Schedule D line 15: net long-term capital gain or loss, 1222(7)-(8). + long_term = ( + tax_unit_non_dep_add(tax_unit, period, ["long_term_capital_gains"]) + + distributions + ) + # Schedule D line 16: line 15 plus the net short-term gain or loss. + combined = long_term + tax_unit_non_dep_add( + tax_unit, period, ["short_term_capital_gains"] + ) + # 1222(11): a net short-term capital loss reduces the net long-term + # capital gain, and a net short-term capital gain does not add to it. + # That is the smaller of Schedule D lines 15 and 16, as on line 7 of + # the Schedule D Tax Worksheet. Form 502CG line 1 points to Form 502 + # line 1c, the Form 1040 line 7a gain, which would also count a net + # short-term capital gain; the statute and Technical Bulletin 58 use + # the 1222(11) amount. + return max_(0, min_(long_term, combined))