Cap Table Functions
Creating Functions to Track Cap Tables¶
Instead of doing this over and over again, we can create a function which takes a current cap table as well as the new investor information and returns what the new cap table will look like.
import pandas as pd
#Start with our basic cap table
cap_table = pd.DataFrame([['Michael', 1500],
['Sean', 1500],
['Employees', 1000]], columns = ['Investor', 'Shares'])
cap_table['% Ownership'] = cap_table['Shares'] / cap_table['Shares'].sum()
cap_table['Price'] = np.NaN
cap_table['Value'] = np.NaN
Our inputs would be the total amount raised and the amount owernship that is going to be given after creation of new shares.
#Create our inputs
total_raised = 100000
post_ownership = {"ABC Capital": .1,
"XYZ Capital": .1}
To make it easier to understand, we will break down each part of the function to see how it all flows. One new thing that we are going to control for is the case where an investor who is already invested in the company decides to increase their stake. We need to consolidate their previous stake and new stake in this case.
#Create a copy of the table to update with
new_cap_table = cap_table.copy()
#Find the valuations
new_ownership = sum(post_ownership.values())
post_money_valuation = total_raised / new_ownership
pre_money_valuation = post_money_valuation - total_raised
print(new_ownership)
print(post_money_valuation)
print(pre_money_valuation)
#Compute the price
total_shares = cap_table['Shares'].sum()
share_price = pre_money_valuation / total_shares
print(share_price)
#Create a dataframe to track the newly created shares
post_ownership = pd.Series(post_ownership).reset_index()
post_ownership.columns = ["Investor", "% Ownership"]
post_ownership['Shares'] = post_ownership["% Ownership"] * post_money_valuation / share_price
print(post_ownership)
#Combine the old and new data, and group by the investor name to get the total shares (to consolidate)
new_cap_table = pd.concat([new_cap_table, post_ownership])
new_cap_table = new_cap_table.groupby("Investor")['Shares'].sum()
new_cap_table = new_cap_table.reset_index()
print(new_cap_table)
#Add back in the columns for price, value and percent ownership
new_cap_table['Price'] = share_price
new_cap_table['Value'] = new_cap_table['Price'] * new_cap_table['Shares']
new_cap_table['% Ownership'] = new_cap_table['Shares'] / new_cap_table['Shares'].sum()
print(new_cap_table)
#Put it all into a function
def update_cap_table(cap_table, total_raised, post_ownership):
#Create a copy of the table to update with
new_cap_table = cap_table.copy()
#Find the valuations
new_ownership = sum(post_ownership.values())
post_money_valuation = total_raised / new_ownership
pre_money_valuation = post_money_valuation - total_raised
#Compute the price
total_shares = cap_table['Shares'].sum()
share_price = pre_money_valuation / total_shares
#Create a dataframe to track the newly created shares
post_ownership = pd.Series(post_ownership).reset_index()
post_ownership.columns = ["Investor", "% Ownership"]
post_ownership['Shares'] = post_ownership["% Ownership"] * post_money_valuation / share_price
#Combine the old and new data, and group by the investor name to get the total shares (to consolidate)
new_cap_table = pd.concat([new_cap_table, post_ownership])
new_cap_table = new_cap_table.groupby("Investor")['Shares'].sum()
new_cap_table = new_cap_table.reset_index()
#Add back in the columns for price, value and percent ownership
new_cap_table['Price'] = share_price
new_cap_table['Value'] = new_cap_table['Price'] * new_cap_table['Shares']
new_cap_table['% Ownership'] = new_cap_table['Shares'] / new_cap_table['Shares'].sum()
return new_cap_table
cap_table = pd.DataFrame([['Michael', 1500],
['Sean', 1500],
['Employees', 1000]], columns = ['Investor', 'Shares'])
cap_table['% Ownership'] = cap_table['Shares'] / cap_table['Shares'].sum()
cap_table['Price'] = np.NaN
cap_table['Value'] = np.NaN
total_raised = 100000
post_ownership = {"ABC Capital": .1,
"XYZ Capital": .1}
print(update_cap_table(cap_table, total_raised, post_ownership))
Now that we have created a function to update our cap tables, we can try it with a few rounds of financing. This function will make the process automated and much easier! We will track all the cap tables over time as well to see the evolution.
cap_table = pd.DataFrame([['Michael', 1500],
['Sean', 1500],
['Employees', 1000]], columns = ['Investor', 'Shares'])
cap_table['% Ownership'] = cap_table['Shares'] / cap_table['Shares'].sum()
cap_table['Price'] = np.NaN
cap_table['Value'] = np.NaN
historical_cap_tables = [cap_table]
total_raised = 100000
post_ownership = {"ABC Capital": .1,
"XYZ Capital": .1}
cap_table = update_cap_table(cap_table, total_raised, post_ownership)
historical_cap_tables.append(cap_table)
total_raised = 200000
post_ownership = {"ABC Capital": .1,
"M Ventures": .05}
cap_table = update_cap_table(cap_table, total_raised, post_ownership)
historical_cap_tables.append(cap_table)
total_raised = 500000
post_ownership = {"ABC Capital": .05,
"M Ventures": .05,
"ABC Capital": .05}
cap_table = update_cap_table(cap_table, total_raised, post_ownership)
historical_cap_tables.append(cap_table)
total_raised = 1000000
post_ownership = {"ABC Capital": .025,
"Growth Fund": .025}
cap_table = update_cap_table(cap_table, total_raised, post_ownership)
historical_cap_tables.append(cap_table)
print(historical_cap_tables)
The first thing to track is how the ownership changes over time. If we pull the column for ownernship from each of the historical cap tables, we begin to see the evolution. First let's look at what we are pulling out with list comprehension.
historical_owernship = [x.set_index('Investor')['% Ownership'] for x in historical_cap_tables]
print(historical_owernship)
Now, we concat these together and transpose them (so that the columns correspond to the different inevstors).
historical_owernship = [x.set_index('Investor')['% Ownership'] for x in historical_cap_tables]
historical_owernship = pd.concat(historical_owernship, axis=1).transpose()
print(historical_owernship)
Finally, let's reset the index (dropping it) because it is all labeled the same, and fillna with 0 to denote that if there is no value, it should be 0% ownernship.
historical_owernship = [x.set_index("Investor")['% Ownership'] for x in historical_cap_tables]
historical_owernship = pd.concat(historical_owernship, axis=1).transpose()
historical_owernship = historical_owernship.reset_index(drop=True).fillna(0)
print(historical_owernship)
Plotting the ownership that different parties have over time really drives home how these rounds of financing have led to dilution for many parties that were not increasing their stakes in the rounds.
import matplotlib.pyplot as plt
from matplotlib.ticker import PercentFormatter
ax = historical_owernship.plot(kind='line')
ax.set_xlabel("Round")
ax.set_ylabel("Percent Ownership")
ax.set_title("Company Ownership over Time")
ax.yaxis.set_major_formatter(PercentFormatter(1))
plt.show()
historical_value = [x.set_index("Investor")['Value'] for x in historical_cap_tables]
historical_value = pd.concat(historical_value, axis=1).transpose()
historical_value = historical_value.reset_index(drop=True)
print(historical_value)
historical_value.plot(kind='line')
plt.show()