Skip to main content

nmtc-calc

A correct pedagogical model of a simplified single-CDE New Markets Tax Credit (NMTC) leveraged structure — not a deal tool.

nmtc-calc models one CDE, one qualified equity investment (QEI), a two-source Investment Fund (investor equity plus one leverage loan), an interest-only A/B QLICI loan pair, and an unwind. That is enough to learn how the credit schedule, the capital stack, the leverage loan and the QALICB's subsidy relate to each other, and to see how they move. Many NMTC transactions combine allocations from more than one CDE, which this model cannot represent, and it leaves out much of what a real closing depends on (see What this model does not do). Use it to understand a structure, not to price or underwrite one.

Every capital-stack figure it prints — QEI, total credits, investor equity, the leverage loan, the CDE fee, QLICI total and the A and B loans, wherever they appear — is labelled SUPPLIED (you entered it) or DERIVED (with the rule used). Computed results such as DSCR, the credit-only IRR or net subsidy are outputs of those inputs and carry the rule in their notes rather than a label. Figures it cannot honestly compute are REFUSED with a reason, and everything else still renders.

Part of the CDFI Superpowers portfolio of open-source tools for community development finance.

Installation

# docs-check: skip shell installation command, not executable Python
pip install nmtc-calc

Quickstart

# docs-check: run quickstart
from nmtccalc import NMTCDeal, transaction, credits, investor, subsidy, waterfall

deal = NMTCDeal(
    project_name="Southside Community Health Center",
    total_project_cost=12_000_000,
    nmtc_allocation=10_000_000,          # QEI
    credit_price=0.83,
    leverage_loan_rate=0.045,
    qlici_a_loan_rate=0.045,
    qlici_b_loan_rate=0.010,
    cde_fee_rate=0.02,
    noi=[520_000, 560_000, 600_000, 620_000, 640_000, 660_000, 680_000],
    b_loan_forgiveness_rate=1.0,          # a negotiated term: no default
    qalicb_alternative_borrowing_rate=0.07,
)

tx = transaction.structure(deal)
cr = credits.schedule(deal)
inv = investor.analyze(deal)
sub = subsidy.analyze(deal)
wf = waterfall.analyze(deal)

print(f"Investor equity:        ${tx.investor_equity:,.0f}  ({deal.basis['investor_equity'].label()})")
print(f"Leverage loan:          ${tx.leverage_loan:,.0f}")
print(f"A / B loans:            ${tx.qlici_a_loan:,.0f} / ${tx.qlici_b_loan:,.0f}")
print(f"Deployment at closing:  {tx.closing_qlici_deployment_ratio:.1%} of QEI  (substantially-all test: {tx.substantially_all_test})")
print(f"Allowance dates:        {cr.allowance_years}")
print(f"PV of credits @ 8%:     ${cr.pv_credits:,.0f}")
print(f"Credit-only IRR / MOIC: {inv.credit_only_irr:.2%} / {inv.credit_only_moic:.3f}x")
print(f"Net subsidy at unwind:  ${sub.net_subsidy:,.0f}")
print(f"Interest savings:       ${sub.interest_savings_to_unwind:,.0f} over {sub.unwind_year} years")
print(f"Blended QLICI coupon:   {sub.blended_qlici_coupon:.2%}")
print(f"DSCR by year:           {[round(y.dscr, 2) for y in wf.years]}  (varies: {wf.dscr_varies})")
print(f"Fund shortfall / year:  ${wf.annual_fund_shortfall:,.0f}  (leverage serviced: {wf.leverage_serviced})")

Every result object also has .summary(), which prints a table with its disclosures, and .to_dict().

What each module computes

transaction — the capital stack

transaction.structure(deal) returns the stack: QEI, total NMTCs, investor equity, leverage loan, CDE fee, QLICI total, and the A and B loans, each with its basis. By default everything after your inputs is DERIVED — a screening model:

Figure Rule
Total NMTCs 39% × QEI — the §45D(a)(2) percentages over the §45D(a)(3) allowance dates; the 39% total is cited to the IRS NMTC Audit Technique Guide, not to the statute
Investor equity total NMTCs × credit price
Leverage loan QEI − investor equity (two-source fund)
QLICI total QEI − CDE fee
A loan mirrors the leverage loan
B loan investor equity − CDE fee

Where real terms exist, supply the A/B split with qlici_a_loan_amount and/or qlici_b_loan_amount; the other tranche is DERIVED as the balance, and two supplied amounts must reconcile to QLICI total. deal.provenance and deal.basis expose the label for every figure, as Provenance and Basis objects.

It also reports the closing-date QLICI deployment ratio (QLICI total / QEI at face). That is not the substantially-all test of 26 CFR §1.45D-1(c)(5), which the package refuses to compute and says why: the 85% is regulatory, the numerator is §1012 cost basis, there are two tests with different denominators, it is not testable at closing, and the threshold is 75% in year seven.

leverage_loan_to_equity_ratio depends on the credit price alone and is disclosed as such.

credits — the statutory credit schedule

credits.schedule(deal) places the seven credits on the statutory credit allowance dates, t = 0 (the QEI date) through t = 6 (26 U.S.C. §45D(a)(3)): 5% of QEI on the first three, 6% on the remaining four. The first credit falls on the day the equity is paid and is not discounted.

investor — credit-only returns

investor.analyze(deal) reports credit_only_irr and credit_only_moic. The cash flows are the equity paid and the credits received and nothing else, so when they are computed both figures depend on no deal input except the credit price (credit-only MOIC = 1 / credit price); both are REFUSED when the unwind falls inside the recapture period. The investor's cash realization is not modeled; it can fall earlier or later than each allowance date, so the credit-only IRR is neither a floor nor a ceiling on an IRR measured on cash realization. They are labelled credit-only, disclosed as such on every summary, and are not an investor IRR.

subsidy — the QALICB's side

subsidy.analyze(deal) reports the B loan forgiven at unwind (b_loan_forgiveness_rate × B loan), the net subsidy (less the exit fee, which assumes the QALICB bears it; the model does not know who does), the blended_qlici_coupon, and interest_savings_to_unwind against the QALICB's own qalicb_alternative_borrowing_rate. Neither rate has a default. B-loan forgiveness is a negotiated exit term, and the IRS NMTC Audit Technique Guide (p. 17) holds that a loan whose documents state it will be forgiven is not bona fide debt. Leave either rate out and the deal still constructs and renders; only the figures that depend on it are REFUSED.

waterfall — cash flow, DSCR and the leverage loan

waterfall.analyze(deal) runs years 1 to unwind_year: NOI (one number, or a series with one entry per year), interest-only A/B debt service, DSCR, net cash flow, and the unwind. dscr_varies says whether DSCR changes over the years; a flat NOI is reported as one stabilized figure, not a schedule.

It also services the leverage loan at the Investment Fund level and reconciles it against the QLICI interest reaching the fund and the A-loan principal repaid at unwind. A structure that cannot fund its leverage loan raises a LeverageShortfallWarning.

Whether guarantee fees enter the DSCR denominator is a house election (include_guarantee_fee_in_dscr, default False = excluded, which makes DSCR higher); it is disclosed on every summary, not attributed to any authority.

utils — sensitivity sweeps

utils.credit_price_sensitivity(deal) and utils.discount_rate_sensitivity(deal) sweep one input using deal.with_credit_price() / deal.with_discount_rate(), which re-derive DERIVED figures, hold SUPPLIED ones and re-run every check. A price at which a tranche would go negative renders a REFUSED row. The credit-only MOIC and IRR columns are the same for every deal at the same prices, and the table says so.

statute — the law, retrieved

nmtccalc.statute holds the schedule, the 7-year period, the recapture rules and the substantially-all refusal reasons, each quoted from the provision it comes from.

An unwind inside the recapture period

The 7-year credit period and recapture period both run from the QEI date to t = 7 (26 U.S.C. §45D(g)(1); the IRS ATG describes the credit period the same way). The last credit is at t = 6, so the final year carries full recapture exposure after the last credit is earned. Redemption of the QEI is a recapture event (§45D(g)(3)(C)).

# docs-check: run recapture
from nmtccalc import NMTCDeal, credits, investor

deal = NMTCDeal(
    project_name="Early Exit", total_project_cost=10_000_000,
    nmtc_allocation=10_000_000, credit_price=0.83, leverage_loan_rate=0.045,
    qlici_a_loan_rate=0.045, qlici_b_loan_rate=0.010, cde_fee_rate=0.02,
    unwind_year=4,
)
cr = credits.schedule(deal)
inv = investor.analyze(deal)
print(cr.statuses)
print(f"Net credits retained: ${cr.net_credits_retained:,.0f}")
sign = "-" if inv.net_benefit < 0 else ""
print(f"Net benefit: {sign}${abs(inv.net_benefit):,.0f}")
print(inv.credit_only_irr, inv.credit_only_moic)

Refusals, warnings and errors

Name When
NegativeTrancheError construction would produce a negative tranche (e.g. cde_fee_rate > 39% × credit_price with the B loan derived)
UnbalancedStackError a SUPPLIED A and B loan do not add up to QLICI total
LeverageShortfallWarning the fund's QLICI interest does not cover leverage interest, or the A-loan principal does not cover the leverage principal
REFUSED figures credit-only IRR/MOIC inside the recapture period; credit-only IRR when the cash flows have no sign change (credit price at or below 0.05/0.39) or the IRR exceeds the solver's bound; DSCR when QLICI debt service is zero; net subsidy without a forgiveness rate; interest savings without a QALICB rate; the substantially-all test always

What this model does not do

Multi-CDE structures; state NMTC programs and other state tax credits; historic tax credits; ongoing CDE or sub-CDE fees; fund-level taxable income, the put or disposition value, and exit taxes in any return; §45D(h) basis reduction and the §38 tax-capacity limit; the reinvestment rule as an ongoing obligation; tax on cancellation-of-debt income from a forgiven B loan; the day-level boundary at the seventh anniversary; sub-annual cash timing (credits are placed on their statutory dates; the timing of the investor's cash realization is not modeled).

Tests and gates

499 tests, run in CI on Python 3.9–3.12.

# docs-check: skip shell commands; CI runs these, not this gate
pytest tests/                              # the suite, from a checkout
python tools/check_sdist.py                # the sdist ships and passes its own suite
python tools/mutation_gate.py              # mutation testing, floor derived from the baseline
python tools/docs_check.py --root .        # this README against the installed wheel

License

MIT — see LICENSE.

Metadata

Release files for nmtc-calc 0.3.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 nmtc-calc 0.3.0
File Size Uploaded
nmtc_calc-0.3.0.tar.gz 110.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for nmtc-calc 0.3.0
File Interpreter ABI Platform
nmtc_calc-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 148.0 kB

Release files / nmtc_calc-0.3.0.tar.gz

Download URL nmtc_calc-0.3.0.tar.gz
Size 110.3 kB
Tags Source
SHA-256 checksum
How to use checksums
f92df56d318d74e7aeb4eb9f17750351e4a80ba370ee5d5b47815f97d0901c5d
BLAKE2b-256 checksum
How to use checksums
be1d69ea64d8972e1335fb5c549deecf8dacc0a81f31334caa09bd44a2ca80c5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

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 Sep 28, 2026.

Transparency log

Release files / nmtc_calc-0.3.0-py3-none-any.whl

Download URL nmtc_calc-0.3.0-py3-none-any.whl
Size 37.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
41d89cecf838e50d2c7fef862a0b036530fcdcc2faef4494bc162e58d3a6e8a3
BLAKE2b-256 checksum
How to use checksums
ded30d2438476717460d0785a9ae2b383a618920349b74ee978cc538df73fe50
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.13

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 Sep 28, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

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