Inspiration Small businesses make big, irreversible capital calls: open a second location, buy new equipment, kill a product line, pilot a new market before scaling. Almost all of them lean on a static NPV spreadsheet, or just gut feel. That method has a real blind spot. It assumes you decide once, today, and can never change course. It can't price the option to wait for more signal, expand once a bet is confirmed working, or abandon for a known salvage value.
Big companies already price that flexibility. Oil companies value drilling leases as options on future prices. Pharma companies value R&D pipelines as options on trial results. The math is called real options valuation, and it's standard practice in corporate finance. But the tooling for it assumes a quant on staff, and it's never applied one level down, to the decisions a restaurant owner or shop owner actually makes. That gap, genuinely useful, decades-old financial theory locked behind a skill floor most business owners will never clear, is what OptionRun closes.
What it does Pick a decision template: defer, expand, abandon, or a staged pilot-then-scale decision. Enter a handful of numbers, or upload a revenue-history CSV and let the app estimate volatility from real data. You get:
Static NPV next to real option value, with the dollar gap explained in plain English and a recommendation (invest, wait, abandon, or hold) The Greeks (Delta, Gamma, vega, theta, rho), the same sensitivities an options desk reads A Monte Carlo projection cone: thousands of simulated futures for the business value A convergence diagnostic that proves the price is a real converged number, not a guess For staged decisions, an independent second pricing engine, plus the exact threshold at which piloting then scaling beats deciding everything today A shareable link for any scenario, including hand-edited numbers, with nothing stored on a server How we built it Every decision is modeled as an option on a "project value" process S(t) following Geometric Brownian Motion:
dS(t) = (μ − δ)·S(t)·dt + σ·S(t)·dW(t) δ here is a cost-of-waiting yield: the real-options analog of a dividend, the value eroded per year by not exercising (rent inflation, a competitor moving first). Without it, "wait" would be a free option, which is economically wrong.
The three base decision types map onto standard option payoffs. Defer is an American call, max(S(T) − K, 0). Expand is a scaled call, max(α·S(T) − K, 0). Abandon is an American put, max(K − S(T), 0). Every value gets computed three independent ways so the numbers can check each other:
Cox-Ross-Rubinstein binomial lattice. Backward induction with early exercise at every node, using the standard risk-neutral probability p = (e^((r−δ)·Δt) − d) / (u − d) u = e^(σ·√Δt) d = 1 / u This also produces the exercise boundary: the critical underlying value at each time step where exercising beats waiting.
Longstaff-Schwartz Least-Squares Monte Carlo. Thousands of simulated GBM paths, with continuation value regressed against a quadratic basis at each time step. It's a structurally different algorithm from the lattice (regression over paths instead of backward induction over a tree), so when it agrees with the lattice, that agreement actually means something. Black-Scholes closed form, used as the European floor and the analytic limit the lattice should converge to. For staged decisions we went one level further: a compound option, an option on an option. It's priced by nesting the same lattice. A stage-1 lattice's terminal payoff, at every node, is itself the price of a full stage-2 American option, plus an independent Monte Carlo cross-check of the same integral. Stage 1 is deliberately modeled as exercisable only at its review date instead of continuously. That's the honest simplification behind every real compound-option model, and it matches how a real staged-funding decision actually gets made.
The stack is Next.js (App Router, TypeScript), with lib/pricing written as pure, framework-free TypeScript with zero dependency on React or the DOM. That's what made it independently testable, and it's the real technical-complexity surface of the project. 55 Vitest tests encode actual finance theory (binomial-to-Black-Scholes convergence, put-call parity, Greek signs, percentile ordering, compound-option monotonicity), not smoke tests. No options-pricing library anywhere. Every pricer, the Gaussian-elimination solver for the LSM regression, and the seeded mulberry32 PRNG (never Math.random(), so every run is reproducible) were hand-written.
Challenges we ran into The math was the easy part, honestly. It's well-documented theory. The real bugs were all in the UI layer, and they were subtle enough that no automated test would have caught them on its own.
The first was a silent scaling bug: the Black-Scholes cross-check for the "expand" payoff wasn't rescaled for the α multiplier, making American look roughly 2.4× European for no economic reason instead of a modest early-exercise premium. Only a regression test comparing the cross-check's magnitude, not just its sign, caught it.
The second was a React reconciliation trap. Switching between the compound and single-stage results panels used two elements at the same JSX position, with no distinct key. React reconciles by type and tree position, not by which branch of a conditional produced an element, so both branches got treated as one instance across the switch. It never force-unmounted. It just waited forever for an exit animation that could never complete, and the page silently froze on stale results even after the correct data had already arrived. The fix was explicit per-branch keys, which force a real unmount and remount no matter whether the animation itself finishes.
A quieter version of the same bug showed up one layer up: the share-link restore effect's setSelected() call could trigger the template-picker effect to fire again and silently overwrite a shared link's hand-edited numbers with the template's preset defaults. No error, just wrong numbers. We fixed it with a one-shot skip guard, armed only when the template selection was actually changing; arming it unconditionally would have left it stuck and broken the next legitimate click instead.
The last one had nothing to do with the app itself. A bulk hex-color find-and-replace through PowerShell's -replace and Set-Content silently corrupted every em dash and every ± or · character already in six source files, even with -Encoding utf8 explicitly specified, because Get-Content -Raw reads with the system codepage first. We caught it by grepping for the mojibake markers before committing, not with anything automated.
Every one of these got root-caused and fixed instead of papered over, and each is documented in the README next to the code it touches.
What we learned A numerical method is never trustworthy on its own. It becomes trustworthy once a second, structurally different method is forced to agree with it, and the app's whole design, three pricers for single-stage decisions, two independent checks for compound ones, grew out of taking that seriously instead of treating it as a nice-to-have.
The other lesson was about process, not math. In a codebase built almost entirely with an AI pair-programmer, speed of writing code mattered less than the habit of re-verifying against a real production build instead of trusting a dev server. More than one of these bugs only showed up, or only got diagnosed correctly, once we stopped debugging against next dev's hot-reload state and rebuilt clean.
What's next A portfolio view for pricing several linked decisions at once (for example, "open location A and have the option to open location B once A proves out," where the two options are correlated, not independent). A PDF or print-friendly export of a scenario for taking to a lender. And extending the compound-option engine past two stages.
Built With
- css
- framer-motion
- geist
- html
- javascript
- lucide-react
- next.js
- node.js
- react
- recharts
- tailwindcss
- typescript
- vercel
- vitest

Log in or sign up for Devpost to join the conversation.