Open-Cap-Table-Coalition / Open-Cap-Format-OCF

Open Cap Format (OCF) - The Open Source Company Capitalization Data Standard. OCF can be used to structure and track the complex data structures necessary to build and maintain accurate capitalization (cap) tables.
https://opencaptablecoalition.com
Other
143 stars 30 forks source link

[Enhancement]: Add ability to track ISO / NSO Splits #504

Open JSv4 opened 3 months ago

JSv4 commented 3 months ago

Description of Enhancement :

Investigate whether we can already model ISO/NSO splits. If we don't have required data, let's add additional primitives and types.

How does this flow into repricing events? Do we want to track this at vesting event level.

Why is this Needed?

We currently don't have a place to capture ISO/NSO splits calculated.

Anything else we need to know?

This probably won't be a static value.

MattCantor commented 3 months ago

Kicking this off with some thoughts regarding calculating ISO/NSO splits:

Background

Up to $100,000 of an optionholder's options may first become exercisable as ISOs in any calendar year.

When does an option first become exercisable?

An ISO/NSO split analysis is a snapshot. Future events can impact the analysis

Pseudocode to convey the algorithm

function getSplitsForYear(
    stockOptions: StockOption[], // sorted in ascending order of grant date
    index: number,
    startingCapacity: number, // starts at 100,000 the first time the recursive function is run
    testedYear: number,
    result: Record<string, {
        ISOShares: number,
        NSOShares: number
    }>
): Record<string, { ISOShares: number, NSOShares: number)> {

    if (index === stockOptions.length || startingCapacity < 0) return result

    const stockOption = stockOptions[index]
    const sharesInTest = getSharesInTest(stockOption, testedYear)  // e.g., shares that first became exercisable in the testedYear, that are eligible to be ISOs, and that are still outstanding

    const shareCapacity = Math.floor(startingCapacity / stockOption.fmv)
    const ISOShares = Math.min(shareCapacity, sharesInTest)
    const NSOShares = sharesInTest - ISOShares
    const utilizedCapacity = ISOShares * stockOption.fmv
    const endingCapacity = startingCapacity - utilizedCapacity

    result[stockOption.id] = {
        ISOShares: ISOShares,
        NSOShares: NSOShares
}

    return getSplitsForYear(stockOptions, index + 1, endingCapacity, testedYear, result)

Tabular example

Grant Id Shares In Test FMV Starting Capacity Share Capacity ISO NSO Capacity Utilized Ending Capacity
1 6,000 10 100,000 10,000 6,000 0 60,000 40,000
2 6,000 10 40,000 4,000 4,000 2,000 40,000 0
3 6,000 10 0 0 0 6,000 0 0

Data needed to perform this analysis

JSv4 commented 3 weeks ago

To reflect this in OCF to match more recent thinking in OCX. We have ISO and NSO tracking in two places. Cut to one.