Skip to main content

529 vs taxable brokerage after-tax tradeoff engine

Project description

plan529lab

A Python package for evaluating the after-tax tradeoff between investing through a 529/Qualified Tuition Program (QTP) and a taxable brokerage account for education savings.

This is not a simple "529 penalty calculator." It is a scenario engine that compares after-tax outcomes across multiple future states — including qualified education use, nonqualified withdrawal, beneficiary change, and Roth IRA rollover paths.

Disclaimer: This package is for educational and analytical purposes only. It is not tax, legal, or investment advice. Tax outcomes depend on facts, jurisdiction, and future law changes. Consult a qualified tax professional for your situation.

Why This Exists

Public discussion of 529 plans often oversimplifies the tradeoff. Common claims that 529s are "risky" because of "penalties" miss key nuances:

  • Only the earnings portion of a nonqualified withdrawal is taxable — contributions/basis come back tax-free
  • The 10% additional tax applies to the amount included in income, not the entire withdrawal
  • Qualified expenses must be reduced by tax-free educational assistance and expenses used for AOTC/LLC
  • State tax benefits, recapture rules, and the Roth rollover path materially affect the comparison

This package makes these interactions explicit and quantifiable.

Installation

pip install plan529lab

For development:

pip install -e ".[dev]"

Requires Python 3.11+.

Quick Start

Python API

from plan529lab.api import analyze_tradeoff
from plan529lab.io.yaml_loader import load_config
from plan529lab.state_rules.no_income_tax import NoIncomeTaxStateRule

config = load_config("examples/washington_no_income_tax.yaml")
result = analyze_tradeoff(config, state_rule=NoIncomeTaxStateRule("WA"))

print(result.explain())
print(f"Delta: ${result.delta:,.2f}")
print(f"Break-even probability: {result.break_even_qualified_use_probability:.1%}")

Monte Carlo Simulation

from plan529lab.api import run_monte_carlo
from plan529lab.models.monte_carlo import MonteCarloConfig, StochasticAssumptions

mc_config = MonteCarloConfig(
    n_paths=10_000,
    seed=42,
    stochastic=StochasticAssumptions(return_std=0.15),
)
mc_result = run_monte_carlo(config, mc_config)
print(f"P(529 wins): {mc_result.prob_qtp_wins:.1%}")
print(f"Mean delta: ${mc_result.mean_delta:,.2f}")

Sensitivity Analysis

from plan529lab.api import run_sensitivity

result = run_sensitivity(config, "qualified_use_probability", [0.0, 0.25, 0.5, 0.75, 1.0])
for v, d in zip(result.param_values, result.deltas):
    print(f"  p={v:.0%}: delta=${d:,.0f}")

CLI

# Deterministic analysis
python -m plan529lab analyze --config examples/washington_no_income_tax.yaml

# Monte Carlo simulation
python -m plan529lab monte-carlo --config examples/washington_no_income_tax.yaml --n-sims 10000 --seed 42

# Sensitivity analysis
python -m plan529lab sensitivity --config examples/washington_no_income_tax.yaml \
    --param qualified_use_probability --min 0 --max 1 --steps 11

# Break-even probability
python -m plan529lab breakeven --config examples/washington_no_income_tax.yaml

# State rule info
python -m plan529lab state-info WA

Configuration

Scenarios are defined in YAML files. See examples/ for templates.

tax_profile:
  ordinary_income_rate: 0.35
  ltcg_rate: 0.15
  qualified_dividend_rate: 0.15

portfolio_assumptions:
  annual_return: 0.07
  dividend_yield: 0.015
  qualified_dividend_share: 0.95
  turnover_realization_rate: 0.05

scenario_policy:
  qualified_use_probability: 0.75

horizon_years: 18

State Rules

The package uses a plugin architecture for state-specific tax treatment:

  • NoIncomeTaxStateRule — for WA, TX, FL, NV, SD, WY, AK, TN, NH
  • GenericDeductionStateRule — models a state income tax deduction on 529 contributions
  • GenericCreditStateRule — models a state tax credit on 529 contributions

Generic rules do not reflect any specific state's exact rules, caps, or conditions.

Assumptions and Limitations

  • Annual timesteps with start-of-year contributions
  • Simplified taxable account model (no lot-level accounting, wash-sale rules, or AMT)
  • Tax rates are assumed constant over the horizon
  • Roth rollover uses one-shot eligibility estimate (not year-by-year staged rollover)
  • Monte Carlo treats dividend yield and turnover as per-path constants (not per-year)
  • State rules are generic unless specifically implemented

Development

# Install dev dependencies
pip install -e ".[dev]"

# Run tests
pytest -v

# Lint
ruff check .

# Type check
mypy .

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

plan529lab-0.1.0.tar.gz (58.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

plan529lab-0.1.0-py3-none-any.whl (40.4 kB view details)

Uploaded Python 3

File details

Details for the file plan529lab-0.1.0.tar.gz.

File metadata

  • Download URL: plan529lab-0.1.0.tar.gz
  • Upload date:
  • Size: 58.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for plan529lab-0.1.0.tar.gz
Algorithm Hash digest
SHA256 eb86a6023e9022e99e53c27a14c70c73dee56e36da57bfef32c371badc319dec
MD5 ea0c447f9738668736419a80558a01f0
BLAKE2b-256 b1df9cd6f3a209b9240634886f85f440e900a03bedd52db78ae8eb2f15495b40

See more details on using hashes here.

Provenance

The following attestation bundles were made for plan529lab-0.1.0.tar.gz:

Publisher: publish.yml on engineerinvestor/plan529lab

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file plan529lab-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: plan529lab-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 40.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for plan529lab-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 49e4949755b978029d716d968237e9d81be8a6982e844bae7c39a93cc5185d18
MD5 3b7a12527083e6e1c640fcfaf46770fc
BLAKE2b-256 954ece40f2843189d5b471ebb5d9dfc8b1057afe60a54cf6f5fb8975b7f33b5a

See more details on using hashes here.

Provenance

The following attestation bundles were made for plan529lab-0.1.0-py3-none-any.whl:

Publisher: publish.yml on engineerinvestor/plan529lab

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page