Open-source engine · Python · research alpha

Equilibria: Algorithmic Game Theory for Multi-Class Private Equity

An open-source Python engine that treats a cap table's payout at exit as a game among the preferred positions, certifies the equilibrium it returns, and refuses the calculations it cannot justify.

On generated governed tables the Option Pricing Method is inapplicable in 33.9% of cases, and the unchecked construction would have reported a median error of 6.4% of equity value while still conserving value exactly.

View on GitHub
After Georges de La Tour, The Cheat with the Ace of Diamonds, c. 1636–1638

A startup’s cap table can be written down precisely. The Open Cap Table Format gives every preferred class fields like seniority, liquidation_preference_multiple and conversion_rights. What no standard defines is what those fields actually pay when the company is sold for a given price. Every spreadsheet, law-firm model and valuation report rebuilds that meaning on its own, and the answer depends on choices that are rarely written down: who converts, in what order claims are paid, where a capped participant stops, what a vote does, which of several IRRs gets reported.

Equilibria, also called the Open Venture Framework (ovf), treats that meaning as a mathematical object. A contract snapshot θ\theta and an exit xx define a game among the preferred positions, and the equilibrium of that game is the cash allocation. Every number the engine returns comes with its assumptions, a hash of its inputs and the check that verified it. It is a research-alpha Python library (Apache-2.0). Every result depends on the documented contract model, and nothing it produces is a 409A valuation, a fairness opinion or legal advice.

Conversion is a game, not a sort

When a company is sold, each convertible preferred position chooses between its liquidation preference and converting to common. One position’s choice changes the residual everyone else shares, so the choices are simultaneous and depend on each other. Take an illustrative USD 60M exit with no debt, no dividends and 1:1 conversion:

Position Shares Invested Terms
Common 8,000,000 — residual claimant
Series A 2,000,000 USD 5M junior 1x participating, total payout capped at 2x
Series B 1,500,000 USD 9M senior 1x non-participating

Payoffs for (Series A, Series B), in USD millions:

A ↓ / B → B retains preference B converts
A retains (10.000, 9.000) (10.000, 7.895)
A converts (10.200, 9.000), the equilibrium (10.435, 7.826)

The only pure Nash equilibrium is A converts, B retains. B takes its senior USD 9M. A takes 20% of the USD 51M residual, which is USD 10.2M. Common gets USD 40.8M, and the three amounts add up to exactly USD 60M. If A deviated it would fall to its USD 10M cap, and if B deviated it would drop to USD 7.826M. The profile A likes best, where both convert and A gets USD 10.435M, is not stable because B would walk away from it. Outcomes that look good to one party do not combine into a stable allocation, so the allocation has to be solved.

The same stack shows why payout curves have kinks. A hits its participation cap at USD 39M and becomes indifferent to converting at USD 59M, while B becomes indifferent at USD 69M. The engine does not assume where these kinks are. It derives them and checks them.

Where the semantics decide real money

The repository grounds the engine in documented cases where standard tooling guesses or says nothing. Four of them:

  • Valuation needs an equilibrium. Gornall and Strebulaev (2020) value venture-backed companies by modelling each share class. Their payoff step is a conversion game solved by iteration, and they report convergence to a Nash equilibrium as an observation on their sample. The author did not find any published existence, uniqueness or complexity result for this game, and the repository’s own review calls that an unverified literature-search statement, not a novelty claim.
  • Terms vary. Kaplan and Strömberg (2003) found participating preferred in roughly 40% of 1987–1999 rounds, while recent market reports show 1x non-participating as the overwhelming norm. A tool that hard-codes one shape gets the other wrong.
  • A vote can erase a preference. In Alta Berkeley VI C.V. v. Omneon, Inc. (Del. 2012), a dissenting Series C-1 was converted to common by a majority vote of all preferred just before a merger and lost its liquidation preference. Greenmont Capital Partners I v. Mary’s Gone Crackers (Del. Ch. 2012) turned on the same mechanism, the NVCA model charter’s mandatory conversion, s.5.1(b).
  • Some governance does not move cash. Broughman and Fried (2010) document carve-outs to common in 11 of 50 sales, averaging USD 3.7M. The NVCA documents show that drag-along and protective provisions decide whether a sale happens, not how the proceeds are split.

The others (option pricing that assumes smooth payoffs, IRRs with several roots, SAFE rounds as fixed points) are covered in the model below.

The model

The allocation map

Debt settles first, in order of seniority. For a conversion profile s∈{0,1}ns \in \{0,1\}^n the equity is then paid out tier by tier. A non-converted preferred position has claim Li=ℓiIiL_i = \ell_i I_i, which is its multiple times invested capital plus any accrued dividends. With Rk−1R_{k-1} remaining and tier claim QkQ_k,

Bk=min⁡(Rk−1,Qk),pi=Bk LiQk,Rk=Rk−1−Bk.B_k=\min(R_{k-1},Q_k),\qquad p_i=B_k\,\frac{L_i}{Q_k},\qquad R_k=R_{k-1}-B_k .

The residual RR is then shared by common, converted preferred and participating preferred in common-equivalent weights wjw_j. Each holder is capped by its headroom hj=max⁡(0, cjIj−pj)h_j=\max(0,\,c_jI_j-p_j). The process is capped pro-rata water-filling at a cash-per-unit level λ\lambda:

aj=min⁡(hj,λwj),∑j∈A(s)aj=R,Wj(s;x,θ)=pj+aj.a_j=\min(h_j,\lambda w_j),\qquad \sum_{j\in\mathcal A(s)} a_j=R,\qquad W_j(s;x,\theta)=p_j+a_j .

An allocation is feasible only if it conserves cash within ε=10−8+10−12x\varepsilon = 10^{-8}+10^{-12}x.

The game and its certificate

The players are positions identified by security_id, not owners, and each player’s payoff is ui(s)=Wi(s;x,θ)u_i(s)=W_i(s;x,\theta). The key quantity is the largest gain any single position could get by switching on its own:

r(s)=max⁡{0, max⁡i∈N[ui(1−si,s−i)−ui(s)]}.r(s)=\max\Bigl\{0,\ \max_{i\in N}\bigl[u_i(1-s_i,s_{-i})-u_i(s)\bigr]\Bigr\}.

The engine returns a profile only if r(s∗)≤εr(s^{\ast})\le\varepsilon. That is an ε-Nash certificate, computed after the search by evaluating all nn deviations, not inferred from how the search went. The search itself runs Gauss–Seidel best-response sweeps from the all-retain profile. If it cycles or runs out of iterations it raises WaterfallConvergenceError instead of returning an approximation. A separate survey, enumerate_equilibria, evaluates all 2n2^n profiles (limited to n≤12n\le 12) and reports whether the equilibrium payoff is unique. Multiple equilibrium profiles are common, because some positions are indifferent. What a calculator needs is a unique payoff. That held on every instance the checked-in exact-rational oracle enumerates, and the repository states plainly that it is not proven in general.

Dividends, rounds and anti-dilution

Proposition 1 shows that accrued dividends add no new game. With d=A/Id=A/I (accrued dividend over invested capital), a dividend-bearing table is exactly the same game as a dividend-free twin with multiple m+dm+d and an adjusted cap, or, for paid-in-kind dividends, with shares scaled by 1+d1+d. Any uniqueness statement carries over.

A priced round with SAFEs comes down to one scalar equation in the post-round fully diluted count TT. Each SAFE converts at the better of its cap and its discount, which is a maximum of affine maps. The round therefore solves g(T)=0g(T)=0 for a gg that is concave and piecewise affine. If the terminal slope s∞>0s_\infty>0 there is exactly one solution. If s∞≤0s_\infty\le 0 the round is refused, and every result reports the margin as uniqueness_margin. Price-based anti-dilution follows the NVCA 4.4.4 formula,

CP2=CP1 A+BA+C,CP_2 = CP_1\,\frac{A+B}{A+C},

with full ratchet as the limit A→0A\to 0. The charter’s definition of “deemed outstanding” is a required input, not a default.

Governance as a two-stage game

A Requisite Holders mandatory conversion adds a vote before the game. In stage 2, for each possible vote outcome, the remaining positions play the ordinary game. A forced conversion is just common stock, so stage 2 is an ordinary table. In stage 1, each holder compares the two stage-2 payoffs and votes sincerely, and the result is decided in exact rational arithmetic against the charter threshold. If a pivotal holder is indifferent, the outcome is undetermined and the engine refuses it.

Because the payoff now depends on a vote, it can jump. On the worked example with a majority term, the first jump sits at exactly x=207,000,000/19x=207{,}000{,}000/19. The engine found that point by solving for the root of the pivotal holder’s gain, not by scanning a grid:

Exit Vote Common Series A Series B
USD 10,894,736 approved 7,578,946.78 1,894,736.70 1,421,052.52
USD 10,894,737 fails 0.00 1,895,000.00 9,000,000.00

One more dollar of sale price, and the founders receive nothing instead of USD 7.58M.

The Option Pricing Method, and where it stops

Valuation practice prices each class as a portfolio of Black–Scholes–Merton call spreads between waterfall breakpoints. In this engine the breakpoints come from the engine itself. Candidates such as preference levels, caps, conversion-indifference points and vote flips are computed in exact rational arithmetic and then checked by probing the waterfall on both sides. Tranche values are

Vt=C(kt)−C(kt+1),class valuej=∑twjt Vt,V_t=C(k_t)-C(k_{t+1}),\qquad \text{class value}_j=\sum_t w_{jt}\,V_t,

and they add up to the discounted equity value only if each tranche’s weights sum to one. A call spread is continuous, so no set of weights can reproduce a step. When a class’s payoff steps, opm_allocate raises OpmAssumptionError, naming the exit, the position and the jump. The checks have no bypass flag.

A certified XIRR

For fund cash flows, write δ=ln⁡(1+r)\delta=\ln(1+r). The NPV becomes an exponential sum f(δ)=∑iaie−δtif(\delta)=\sum_i a_i e^{-\delta t_i} over the whole real line. By Descartes’ rule as extended by Jameson (2006), the number of real roots is at most VV, the number of sign changes in the flows, and has the same parity as VV. Roots are then isolated by Rolle’s theorem, working in log space, inside a provable bound. The status is unique, multiple (all roots listed, none chosen), none or undetermined, and never a wrong claim of completeness. KS-PME, Direct Alpha, PME+, the Long–Nickels ICM and a labelled mPME reconstruction are built on top.

What the engine found

Running the engine on generated and published tables produced these findings. The main ones:

  1. Under a mandatory-conversion vote, payoffs are not monotone in the exit. On the worked example, common loses USD 7.58M for one extra dollar of price at 207,000,000/19207{,}000{,}000/19. At USD 57.5M, Series B loses USD 1.49M for USD 0.2M more. In generated governed tables, some position’s cash fell as the exit rose in 1,246 of 3,000. Without the term, it never did.
  2. The OPM is inapplicable on about a third of governed tables: 677 of 2,000 (33.9%, 95% Wilson interval 31.8–36.0%), against 0 of 4,000 refusals on the same tables without the governance term. With its checks removed, the call-spread construction’s largest class gap had a median of 6.4% of equity value, while still conserving value exactly. The repository stresses that this is a frequency over a generator, not an estimate for real charters.
  3. Finnerty and Ghaidarov bracket the payoff they both approximate. Finnerty is below the simulated average-strike put in all 24 grid cells, Ghaidarov is above it from σT≈0.42\sigma\sqrt{T}\approx 0.42, by 5 to 154 standard errors. Neither closed form is exact.
  4. A published Direct Alpha illustration hides a second IRR root. In Gredil, Griffiths and Stucke’s example, the Long–Nickels series has roots at −27.26% and +5.97%, and only the second is shown. Its index position also goes short on 2007-12-31.
  5. Microsoft’s published XIRR value is an iterate, not the root: the exact root is 0.37336253351883151, while the displayed value is 0.373362535.

One caveat matters. The large conversion-game search counts in findings.md (hundreds of thousands of generated games, all with a unique equilibrium payoff) are labelled there as historical notes whose generators, seeds and logs are absent. They have not been independently reproduced. The OPM and governance counts, by contrast, come with checked-in generators and seeds (opm-findings.md). None of these results is a theorem.

Open research problems

  • Existence and payoff uniqueness of pure equilibria in the conversion game for general contracts, through a proof (supermodularity or a potential argument) or an explicit counterexample.
  • Owner-level games, where one beneficial owner optimises jointly across several positions.
  • Strategic voting and coalitions in place of the sincere two-stage vote.
  • A certified IRR for cash-flow series with many sign changes, which today can return a conservative undetermined on 1,000-date alternating flows.
  • Payoff uniqueness under exit-triggered anti-dilution.
  • Selection-corrected tail inference, fed through these contract semantics so that TVPI, PME and fair value become distributions rather than single numbers.

The repository says the most valuable contribution would be a counterexample: explicit contract terms with an independently derived payout that disagrees with the engine.

Evidence and engineering

The suite has 2,211 passing tests on Python 3.11 and 3.12. Tests written from the same specification can share its misreadings, so the repository also relies on three independently written oracles. The first is an exact-rational water-filling allocator for small games. The second is an OPM oracle that evaluates no option formula and instead integrates the payoff against the lognormal density. It agrees exactly on 36,009 breakpoints and 2,441 steps, and matches the call spreads to 6.3e−15 of equity value. The third is a 60-digit PME oracle checked against 19 hand-derived fixtures.

The design rules are strict. Snapshots are immutable. Conventions such as day count and vote threshold have no defaults, so leaving one out raises a TypeError. Unsupported or ambiguous cases raise typed errors instead of returning approximations, and every result carries an input_hash and its verification quantities. The engine also ships a CLI, OCF v1.2.0 read/write, and an MCP server with 12 typed tools for AI agents.

Try it

Install it from source (Python 3.11 or 3.12):

git clone https://github.com/ogulcan-ozyavuz/algorithmic-game-theory-for-private-equity.git
cd algorithmic-game-theory-for-private-equity
python -m pip install -e '.[dev]'      # Python 3.11+; add '.[mcp]' for the MCP server

Solving and certifying the worked example:

import equilibria as eq  # same implementation as `import ovf`

ct = eq.CapTable(securities=[
    eq.common(8_000_000, holder_id="founders", security_id="common"),
    eq.preferred(2_000_000, 2.50, seniority=2, participating=True, participation_cap=2.0,
                 holder_id="series_a", security_id="series_a"),
    eq.preferred(1_500_000, 6.00, seniority=1,
                 holder_id="series_b", security_id="series_b"),
])

report = ct.waterfall_detailed(60_000_000)
for payout in report.payouts:
    print(payout.holder_id, round(payout.amount), payout.converted)
# founders 40800000 False
# series_a 10200000 True
# series_b 9000000 False

assert report.max_unilateral_gain <= report.tolerance      # ε-Nash certificate
assert abs(report.conservation_error) <= report.tolerance  # every dollar reconciled

survey = eq.enumerate_equilibria(ct.securities, 60_000_000)
print(survey.states_evaluated, survey.payoff_unique)
# 4 True

Cite

Based on the repository’s CITATION.cff:

Ozyavuz, Ogulcan. Equilibria / Open Venture Framework: Startup Financing and Exit Calculations.
Version 0.1.0-alpha. Apache-2.0.
https://github.com/ogulcan-ozyavuz/algorithmic-game-theory-for-private-equity

The source, documentation and full derivations are in the repository on GitHub.