Reverse stress testing for your portfolio. Not "what if NVDA drops 20%", but "what is the smallest drop that pushes my portfolio past the loss I refuse to tolerate — and what is the smallest change that buys me distance from it".
Most stress tests make you guess the scenario first, and the scenario is the part nobody checks. Minima solves backwards: you name the failure, it finds the shock.
The loss is not just the shock. Crowded institutions hold the same names you do; when one is forced to deleverage, its selling moves the price of everything else it holds, and that reaches you. Minima models that feedback on the actual Q2 2026 13F filings of Citadel, Millennium, Point72, Two Sigma and Renaissance, then shows you the cascade round by round.
Then it recommends the smallest position change that helps — and tests whether the recommendation actually helped, by replaying the identical shock, recomputing the new break point, and scoring both portfolios across hundreds of simulated stress scenarios.
Portfolio → Risk limit → Break → Cascade → Fix → Validate
Two questions, one engine. The difference is only what counts as failure:
| Your book — the seven pages | Your holdings. Fails when you cross your loss limit. |
The institutional books — /api/break, /api/stabilise, /api/boundary |
Many leveraged books. Fails when N funds breach. The prime-brokerage question. |
The second is not a mode you toggle in this build. It surfaces on two screens — the Boundary map, which sweeps it across the whole configuration space, and the Model page's "What computed the institutional patch" card — and in full through the API.
HackRice 16 — Finance track, plus the Capital One and MathWorks challenges.
Full write-up in docs/devpost.md.
git clone <this repo> && cd minima
python3 -m pip install numpy pytest # the only dependencies
python3 -m pytest tests/ -q # 429 collected (426 passing, 3 skipped)
PYTHONPATH=src python3 -m minima.serverThen open http://localhost:8765.
scripts/setup.sh does the installs and the tests in one go, then prints the
server command.
Tested on Python 3.13; nothing here uses syntax newer than 3.8. numpy is the only
runtime dependency — the server is stdlib (http.server), because nothing should need
a pip install at 3am.
Everything below runs with no network and no account. Use demo portfolio is the path a
judge takes; CSV is the path a user takes.
$ curl -s localhost:8765/api/portfolio/full?limit=0.10
On the demo portfolio ($12,300 across five names), at a 10% loss limit:
| Break point | NVDA −25.05% |
| Direct loss | 7.33% |
| After cascade | 10.00% |
| Amplification | 1.36× |
| Fix | reduce NVDA by $472 (13.3% of the position, to cash) |
| Same shock, after | 10.00% FAIL → 9.00% PASS |
| New break point | −25.05% → −28.17% (+3.07pp) |
| Worst of 400 simulated | 11.94% → 10.73% |
The gap between 7.33% and 10.00% is the entire argument: the shock is the trigger, the crowding is the damage.
Your portfolio is an observer. The institutions deleverage identically whether or not you trim NVDA — a retail account does not move markets, and pretending otherwise would be the most flattering lie this app could tell. What your change alters is how much of that price path lands on you.
Your position doesn't move the market. It decides how much of the market's move lands on you.
Three tests ship and one does not.
- Same shock — one cascade, both portfolios scored against it. Only the weights differ, and that is enforced by running the cascade once rather than promised in a caption.
- New break point — the same reverse search, re-run. Recomputed, never inferred from the size of the cut.
- Simulated stress — 400 sampled shocks, both portfolios on identical draws, seeded so it reproduces. "Stayed under your limit in 94% of our scenarios" is a fact about our scenarios. "94% chance you are safe" is a fact about the world, and we do not have one.
- Historical replay — not available, and says so. We ship one frozen quarter of holdings and no price history. Building it on invented returns would put a confident number with nothing beneath it on the screen that exists to show evidence. There is a test asserting the feature is absent.
The holdings matrix (data/cache/dataset.json, one frozen quarter) is committed, and so
are 53 recorded answers in data/cache/golden/ (420KB). The web fonts are vendored into
web/fonts/ too, so not even the stylesheet reaches out. Nothing here touches the
internet unless you explicitly ask it to.
MINIMA_DEMO=1 PYTHONPATH=src python3 -m minima.serverAn institutional response — /api/break, /api/stabilise, /api/boundary — comes off
disk, labelled cached: true, only when a recording is of exactly those knob values.
Anything else computes, in 11–20ms, and says cached: false. ?demo=1 on a single request
does the same for that request. In live mode those endpoints also fall back to a recording
if one throws, with the reason in fallback_reason.
It used to serve the nearest recording for knobs it had never seen, and say which settings
it was actually of. That is gone, because "nearest" is not a synonym for "the same question":
the leverage axis is recorded at 1.5 and then not again until 4.0, so a request at 2.5 was
answered with the 1.5 recording — 51.5% against a live 16.8%, and on /api/stabilise an
instruction to sell $1,327,529 where the engine says $51,406.
The Portfolio Mode routes — /api/portfolio/* and /api/cascade — are not in that machinery
at all, and answer cached: false even under the flag. A recording is keyed on knob values,
which is sound when the knobs are the whole question; a portfolio is not a knob value, so
keying a portfolio answer on knobs alone would serve one person's result to another.
That costs nothing offline, because there is nothing for them to reach for: the holdings
come from data/cache/dataset.json, which is committed and frozen, and everything
downstream of it is arithmetic. The network is touched only by refresh_dataset.py, which
you have to run on purpose.
The committed dataset is deliberately frozen. To move to a new quarter:
PYTHONPATH=src python3 scripts/refresh_dataset.py # refetch from EDGAR, ~6s
PYTHONPATH=src python3 scripts/record_golden.py # re-record, or the recordings lieDo both or neither. The golden recordings are of specific numbers, and nothing will warn you if the dataset moves out from under them.
python3 -m pytest tests/ -q # 429 collected, no network requiredThe frontend has its own jsdom harness. It needs one extra install, because
tests/ui/node_modules/ is gitignored, and a running server — it walks all six
pages against the real endpoints, carrying sessionStorage forward the way a browser
does, and compares every number on screen against the payload that produced it:
npm --prefix tests/ui install # once
MINIMA_DEMO=1 PYTHONPATH=src python3 -m minima.server &
node tests/ui/pages.jsIt catches the failure mode that matters most in a demo: a panel that renders a plausible wrong number, or renders empty instead of throwing. With no server on 8765 every fetch fails and the output is noise, so check the server is up before believing a red run.
src/minima/engine.py |
the cascade — leverage breach, forced selling, price impact, repeat |
src/minima/search.py |
bisection for the smallest shock meeting a breach condition |
src/minima/stabilise.py |
the cheapest single position change that pushes the break point out |
src/minima/thirteenf.py |
EDGAR 13F-HR fetch, amendment handling, multi-CIK managers |
src/minima/api.py |
JSON endpoints and the golden-recording fallback |
src/minima/server.py |
stdlib dev server on 8765 |
web/ |
the frontend, no build step |
matlab/ |
optional patternsearch solver — see matlab/README.md |
MATLAB is optional. Without it the Model page's solver card reads "SciPy-free Python · exhaustive
position scan" and everything still works; matlab/README.md covers wiring up the real
one.