← Vesper homeGo to dashboard →

How Vesper works

Updated for revision vesper-web-00022. Everything below matches exactly what the current deployed product does. When product decisions change, this page changes.

Who Vesper is for — the muni thesis in plain terms

Municipal bonds used to be perceived as a product for top-bracket investors only — useful if you were paying 35–37% federal tax, irrelevant otherwise. That perception is out of date. With current muni yields and the rate environment, the break-even is meaningfully lower than most advisors and clients think.

Anyone in the 22% federal bracket or higher typically comes out ahead on an after-tax basis with a muni ladder versus comparable taxable bonds. The math:

Client in the 22% federal bracket.
Muni ladder yields 3.50% YTW (reasonable current muni rate).
TEY = 3.50% / (1 − 0.22) = 4.49%

A comparable-duration Treasury at 4.00%? Muni wins by 49bps after tax.

Move up to the 24% bracket:
TEY = 3.50% / (1 − 0.24) = 4.61% → muni wins by 61bps.

Add a 6% state tax (in-state muni) for the 24% federal client:
TEY = 3.50% / (1 − (0.24 + 0.06 − 0.24 × 0.06)) = 4.92%
Muni wins by 92bps — nearly a full percentage point of take-home yield.

So when you're pitching Vesper to clients, "it's not just for the Rockefellers" is a fair framing. Anyone with enough of an income to hit the 22%+ bracket and enough taxable-account savings to warrant a ladder is a plausible candidate. That's a much larger total addressable market than the traditional <37%-bracket narrative implies.

What Vesper does

Vesper automates municipal bond ladder construction for registered investment advisors (RIAs). A muni ladder is a portfolio of municipal bonds with staggered maturities — typically one bond per year out to some horizon — so the client gets a predictable stream of interest payments and principal repayments. Muni interest is exempt from federal income tax (and often state tax too), which is why high-bracket clients use them.

Building one manually: an advisor screens thousands of available bonds against the client's tax bracket, state, risk tolerance, and horizon, picks one bond per rung, checks for concentration in individual issuers or sectors, and writes up the rationale. It's typically 3–6 weeks of work.

Vesper compresses that to minutes. The advisor enters the client's profile once, sets a few constraints for each ladder, and a constraint-based solver (Google OR-Tools CP-SAT) picks bonds that maximize after-tax yield while respecting every constraint.

The math that matters: Tax-Equivalent Yield (TEY)

Munis pay lower headline yields than taxable bonds, but the interest is tax-free. TEY is the apples-to-apples comparison — the yield a taxable bond would have to pay to deliver the same after-tax income.

TEY = muni_yield / (1 − marginal_tax_rate)

Example — 37% federal bracket, 3.00% muni:
TEY = 0.03 / (1 − 0.37) = 4.76%

A 4.50% corporate bond pays less take-home than this 3% muni.

For a client who lives in the state a muni is issued from, interest is also exempt from state tax. Vesper combines federal + state rates (with the federal deduction for state taxes approximated):

TEY_in_state = muni_yield / (1 − (fed + state − fed × state))

Every bond in a ladder's result page shows both its YTW (pre-tax) and TEY (what that YTW translates to after the client's specific taxes).

Adding a client

A client record is the anchor for every ladder you'll build for that person. The tax profile is the load-bearing part; everything else is metadata.

Name (required)
The client's full name. Appears on ladder titles and future PDF reports.
Email (optional)
Stored for future use (report delivery, notifications). Nothing reads it today.
State (required)
The US state the client lives in. Drives two things: whether a muni qualifies for double tax exemption (residents of the issuing state avoid state income tax on that muni's interest), and the optimizer's in-state preference constraint when enabled.
Filing status
IRS filing category (Single / Married Joint / Married Separate / Head of Household). Stored for future use — will drive the 3.8% Net Investment Income Tax and AMT phase-out thresholds in later iterations. Not used by today's optimizer math.
Federal tax rate (%) (required)
The client's marginal federal bracket — the rate on their next dollar earned, not their average rate. Muni ladders typically benefit anyone at 22% and above on an after-tax basis (see "Who Vesper is for" at the top of this page for the break-even math); the seven 2024/2025 brackets are 10%, 12%, 22%, 24%, 32%, 35%, 37%.

Separately, rates ≥32% trigger the AMT filter in the optimizer: AMT-subject (private-activity) munis are excluded because clients at 32%+ are materially more likely to be subject to the Alternative Minimum Tax, where that interest becomes taxable. Nothing about being below 32% means munis don't work; it just means the AMT filter isn't applied.
State tax rate (%) (required)
The client's marginal state income tax rate. 0 for TX/FL/WA/NV/SD/TN/AK (no state income tax). Up to 13.3% for California top bracket. Combined with the federal rate to compute in-state TEY.
Investment amount ($) (optional)
Total dollars the client wants laddered. Optional here — defaults into the ladder form but can be overridden per ladder. When present, drives the total investment hard constraint and the concentration caps (20% per issuer → max dollars per issuer = 0.20 × investment amount).
Risk tolerance
Permanent client trait. Sets the default ladder risk profilewhen this client is selected on the ladder form. Three tiers (all investment grade):
  • Conservative — AA min rating, 5-year horizon, 5 years call protection, 20% max per issuer, 30% max per sector. Capital preservation; typical for retirees drawing income.
  • Moderate — A min rating, 10-year horizon, 3 years call protection, 20% per issuer, 30% per sector. Balanced; typical RIA default.
  • Aggressive — BBB min rating (lowest tier of investment grade), 20-year horizon, 0 years call protection, 20% per issuer, 30% per sector. Yield-seeking but still IG. Appropriate for high-bracket accumulators comfortable at the edge of investment grade.

The ladder form itself has a fourth option — High Yield — that isn't a client tier because it's meant to be an explicit per-ladder choice for satellite allocations, not a default posture. See the ladder section below.

The advisor can override any default on a per-ladder basis.
Notes
Free-form advisor notes. Never read by the system — reference only.

The client workspace

Client list at /dashboard/clients shows every client you've added: Name (clickable), State, Fed/State tax rates, Investment amount, Risk tolerance, Email. Row hover highlights for scanability. Adding a new client uses the form at the top of the same page.

Client detail page at /dashboard/clients/[id] — click any client name to land here. Four cards show everything about that client: Tax profile (federal, state, combined-effective rate, filing status, AMT filter status), Investment profile (default amount, risk tolerance), Contact (email, phone), Notes. Below the cards is that client's ladder history — every ladder ever built for them with status, total invested, avg YTW / TEY, and links into each result page. A prominent "Build ladder for [First Name]" CTA pre-selects this client on the new-ladder form.

Building a ladder

The form is split into two numbered boxes to keep the workflow unambiguous:

Defaults come from the client's risk tolerance but every field is overridable.

Client
The client this ladder is being built for. Their federal + state tax rates, state, and risk tolerance are pulled from the client record.
Risk profile
A coordinated preset that prefills the rating floor, horizon, call protection, and concentration caps in one choice. Defaults from the client's tier but can be switched. Four options:
  • Conservative / Moderate / Aggressive — same as the three client tiers described above, all investment grade.
  • High Yield — BB+ minimum rating (crosses below investment grade into the high-yield muni space), 15-year horizon, 0 years call protection, and tighter concentration caps: 10% per issuer (vs. 20% default) and 20% per sector (vs. 30% default). The tightened concentration compensates for the higher default risk on any individual HY bond by pushing the solver toward more issuers and more sectors. Appropriate for satellite yield allocations — not meant to be a whole-portfolio posture.
Investment amount ($)
Total dollars for this ladder. Prefills from the client's stored amount. Must be high enough that each rung can meet bond minimum denominations (typically $5,000 per bond).
Time horizon (years)
Number of rungs — one (or more) bond maturing each year out to this horizon. A 10-year ladder = maturities spread across years 1–10. Shorter = less duration risk but lower yield.
Minimum rating
Credit quality floor. Bonds below this threshold are dropped before the solver runs. AAA = highest. BBB- is the lowest investment grade (BB+ and below is non-investment-grade / "high yield" / "junk"). The dropdown includes BB+ and BB for advisors who explicitly want HY exposure — picking either is usually done via the High Yield risk profile above, which also tightens concentration.
Max per issuer (%)
Diversification cap. No single issuer (e.g., State of California, a specific school district) can represent more than this fraction of the ladder's dollars. 20% is a common default — limits default risk if one issuer downgrades or defaults.
Max per sector (%)
Per-sector cap. Prevents concentration in one industry. The sector enum: General Obligation, Revenue, Water/Sewer, Transportation, Education, Healthcare, Housing, Power, Tax Increment, Other.
Min call protection (years)
Callable bonds can be redeemed early by the issuer (usually when interest rates drop, which hurts the investor). Call protection is the minimum years before the issuer can call. Higher = safer but fewer candidate bonds qualify.
Rung tolerance (years)
Maturity-drift tolerance per rung. 0 = strict; a bond must mature in the exact rung year to fill it. 1 = ±1 year accepted (a 2028 bond can fill the 2027 or 2029 rung). Critical when bond inventory is sparse, especially for high-yield munis where a specific state × sector × year combo may have very few available bonds. Each selected bond still fills exactly one rung; tolerance just widens the candidate pool per rung. Max ±3 years.
In-state minimum (%)
Minimum fraction of the ladder's dollars that must be in bonds issued in the client's state. Only applied when "Prefer in-state issuers" is checked. 100% = pure in-state. 0% = no minimum (diversify across states). Typical values: 60% (balanced), 80–100% (pure-local shops), 20–40% (diversifiers).
Include callable bonds with protection
If checked, callable bonds meeting the min-call-protection above are allowed in the ladder. Unchecked = non-callable only. Most ladders include callables because it widens the candidate pool dramatically.
Prefer in-state issuers
Enables the in-state minimum constraint. When checked, the optimizer must put at least the in-state minimum percent in bonds from the client's state.
Equal weight per rung
When checked, each maturity year gets roughly the same dollar allocation (within ±20% of the target). Produces a classic flat-rung ladder. Unchecked allows the optimizer to concentrate dollars in higher-yielding rungs.

Handling thin HY inventory — the three-part answer

High-yield muni inventory is famously thin. For a given state, in a given sector, in a given maturity year, there are often only a handful of bonds available — sometimes zero. Any ladder tool that can't handle this fails constantly on the HY use case. Vesper handles it with three tools that compose together:

  1. The High Yield risk profile doesn't just drop the rating floor to BB+. It also tightens per-issuer concentration to 10% and per-sector to 20%, forcing the solver toward more issuers and more sectors. This compensates for the higher default risk on any individual HY pick.
  2. Rung tolerance (0-3 years) lets a bond maturing in, say, 2028 fill the 2027 or 2029 rung when no 2028 bond is available at the required rating. Ladder shape preserved (one bond per rung, no double-counting) but the candidate pool per rung is much wider. For the HY use case, tolerance of 1 or 2 is typical.
  3. Partial ladders with gap reporting are the fallback. When some rung still has zero candidates even after tolerance and profile relaxation, the solver fills the rungs it can and returns the gaps explicitly. The result page shows which rungs are unfilled and offers concrete next steps — widen tolerance further, lower rating, reduce in-state minimum, etc. — instead of a blanket "infeasible" error.

In practice: if you're building a 10-year California HY ladder and three rungs come up short, you'll see a 7-rung ladder with an amber banner listing the unfilled years and the relaxations that would likely close the gaps. That's a useful tool even when the universe is sparse, instead of a binary fail.

How the optimizer actually picks bonds

Under the hood: Google OR-Tools CP-SAT, a constraint-programming solver that finds the optimal combination of decision variables subject to every constraint you give it. For Vesper the variables are (1) which bonds to select and (2) how much par to allocate to each.

1. Hard filters (prefilter)

Before the solver even runs, the universe is filtered. Bonds are dropped if they fail any of:

2. Scoring → top 200 candidates

Surviving bonds are scored 0–100 and the top 200 move on to the solver. The weights are defined in constants.py and shown on every ladder's result page:

3. Solver constraints

4. Objective function

maximize( Σ par_i × YTW_i + unique_issuers_selected × (investment / 100) )

Plain English: pick the combination that gets the most yield-weighted return, with a small bonus for including more distinct issuers. The bonus size is roughly "each unique issuer is worth ~$5,000 of yield on a $500k ladder" — calibrated so diversification nudges the solver without dominating the yield objective.

5. Possible outcomes

When a ladder fails: diagnostics + one-click retry

An infeasible combination of constraints returns a FAILED ladder instead of silently producing nothing. The FAILED result page is designed to tell the advisor exactly what was asked for and why it didn't work — never a generic "try relaxing constraints" message.

What you see on every ladder result page

Every completed ladder (or partial ladder) renders four sections, in this order:

What's not in this version yet

Honest disclosure so expectations match reality:

Where to find the numbers

All "magic numbers" that shape ladder results live in optimizer/constants.py in the repo at rk-stacks/vesper. That file is the single place to adjust AMT thresholds, scoring weights, solver timeouts, and default in-state percentages.

The tax-year bucket for the AMT threshold (currently 0.32 for 2024/2025 brackets) should be reviewed annually as brackets shift.

← Vesper homeContinue to dashboard →