Skip to main content

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

Metadata

Release files for plan529lab 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for plan529lab 0.1.0
File Size Uploaded
plan529lab-0.1.0.tar.gz 58.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for plan529lab 0.1.0
File Interpreter ABI Platform
plan529lab-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 98.6 kB

Release files / plan529lab-0.1.0.tar.gz

Download URL plan529lab-0.1.0.tar.gz
Size 58.3 kB
Tags Source
SHA-256 checksum
How to use checksums
eb86a6023e9022e99e53c27a14c70c73dee56e36da57bfef32c371badc319dec
BLAKE2b-256 checksum
How to use checksums
b1df9cd6f3a209b9240634886f85f440e900a03bedd52db78ae8eb2f15495b40
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 2, 2026.

Transparency log

Release files / plan529lab-0.1.0-py3-none-any.whl

Download URL plan529lab-0.1.0-py3-none-any.whl
Size 40.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
49e4949755b978029d716d968237e9d81be8a6982e844bae7c39a93cc5185d18
BLAKE2b-256 checksum
How to use checksums
954ece40f2843189d5b471ebb5d9dfc8b1057afe60a54cf6f5fb8975b7f33b5a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 2, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page