The surprisingly complex math behind startup equity and taxes

Taxes for employees at startups are weird and can vastly change the amount you make.  To illustrate why, let’s take a simple example.

Suppose we have a group of early employees at a startup, we’ll call them the Unicorn Inc. Mafia.  They’re all fresh out of college and managed to get through it without any debt, so they each have a net worth of $0. They all join the same day and are given the same equity package: $10k in stock options with a strike prices of $1 (so, 10,000 common stock). We’ll take four members of the mafia, each with a different strategy.

Name Description Net worth
Alice Exercise very early, before price changes $0
Bob Exercise somewhat early $0
Carol Wait until liquid to exercise, wait until long-term capital gains apply $0
David Wait until liquid to exercise, sell immediately $0

From here on out, keep in mind that this could be the end of the story. The company could always fold, leaving everyone with zero or, if they’ve exercised, a negative net worth.

However, suppose the company is doing well and lets the employees know that they’re going out to raise a $40M round.  Alice exercises her options before the round happens.  This means that she has to pay for them, so she’s in the hole $10k.  Now if anything goes wrong, she’s out $10,000.

Once the company raises the round, the stock is worth $5/share.  Unfortunately, Bob’s significant other got a job across the country, so he has to find a new job. He feels like the company is going places, though, so he wants to collect his equity before he goes. He exercises his options. Because he is buying his stock for $1 and it is now worth $5, the IRS says that he just “made” $4. So he has to pay normal income taxes on that $40,000. To keep things simple, let’s say everyone’s tax rate is 25%. So now he’s paid $10k for the stock and $10k for taxes:

Name Description Exercise Income taxes Net worth
Alice Exercise very early, before price changes ($10,000) 0 ($10,000)
Bob Exercise somewhat early ($10,000) 25%*$40,000 -> ($10,000) ($20,000)
Carol Wait until liquid to exercise, wait until long-term capital gains apply $0 $0 $0
David Wait until liquid to exercise, sell immediately $0 $0 $0

So Bob’s out $20k if the company goes under (ouch!).

However, luckily for Alice & Bob, over the next several years, the company continues to grow and raise money. Finally, the company goes public for $100/share. Wow! Once the lockup period expires, everyone eventually sells (somehow it’s still exactly at the IPO price) and makes $1M. Our final shakeout looks like:

Name Description Exercise Income taxes Short-term capital gains Long-term capital gains Sell price Net worth
Alice Exercise very early, before price changes ($10,000) 0 0 0 $1,000,000 $990,000
Bob Exercise somewhat early ($10,000) 25%*$40,000 -> ($10,000) 0 20%*$990,000 -> ($198,000) $1,000,000 $782,000
Carol Wait until liquid to exercise, wait until long-term capital gains apply ($10,000) 25%*$990,000 -> ($247,500) 0 20%*$990,000 -> ($198,000) $1,000,000 $544,500
David Wait until liquid to exercise, sell immediately ($10,000) 25%*$990,000 -> ($247,500) 25%*$990,000 -> ($247,500) 0 $1,000,000 $495,000

There are, uh, a couple of different outcomes. Alice obviously has an accountant in the family: she avoided paying any taxes at all! How is this possible? First, she exercised his options before the price changed, so she didn’t have to pay any taxes on exercise. Then she held them long enough to qualify for long-term capital gains. However, she didn’t even have to pay those! It turns out that, if you own stock in a startup before it has $50M in assets, long-term capital gains up to $10M are tax-free (Google “QSBS” for details). However, Alice is also taking on more risk for longer than anyone else: most startups don’t have outcomes like this and she’d have just been out $10,000 if they had gone out of business.

Obviously there are a ton of simplifying assumptions (stock prices never change! Everyone has the same tax rate, which happens to be one that make numbers easy!). However, I wish someone had told me about all this ~10 years ago, so putting this out there in the hopes that it’ll help someone else.

Thinking with Pandas

You can see and run all of my work for this blog post at Colabratory.

Pandas is a Python library for manipulating data. Wrapping my head around it took a while: I’ve been using it for ~6 months and I’m still learning how to use it “right.” It uses all kinds of syntactic sugar to optimize working with vectors and matrices instead of scalars. This makes working with Pandas very different than working with “vanilla” Python.

For example, let’s say you wanted to get a bunch of random dice rolls for playing Dungeons and Dragons. D&D uses 20-sided dice, so in normal Python, you’d probably do something like:

rolls = [random(1, 21) for i in range(0, 10000)]

In Pandas, it would be something like:

rolls = pd.DataFrame(np.random.randint(1, 21, size=(10000, 1)), columns=['roll'])

In D&D, rolling a 20 on the die is special and called a “critical hit.” It usually does good things for the player. If we iterate through rolls seeing how many critical hits we have in vanilla Python, it’s pretty fast:

%%timeit
count = 0
for roll in rolls:
  if roll == 20:
    count += 1

# Output:
1000 loops, best of 3: 267 µs per loop

If we do the same naive approach with Pandas, it’s, uh, slower:

%%timeit
count = 0
for roll in rolls.iterrows():
  count += (roll[1] == 20)
count

# Output:
1 loop, best of 3: 3.12 s per loop

That’s over 10,000x slower (267 µs -> 3.12 seconds). Like, they-must-have-put-a-sleep()-in-iterrows()-to-discourage-you-from-using-it slow.

So why use Pandas? Because it isn’t slow if you do it the “Pandas way”:

%%timeit
(rolls.roll == 20).sum()

# Output:
1000 loops, best of 3: 341 µs per loop

Nice! Only 1.3x slower than vanilla Python. Also, notice the syntactic sugar: you can pretend that the vector is a single number, comparing it to a scalar. If you look at (rolls.roll == 20), it’s a series of booleans:

(rolls.roll == 20).head()
0    False
1    False
2    False
3    True
4    False

When you take the sum() of that series, False is converted to 0 and True to 1, so the sum is the number of True elements.

Modifying some elements of a vector

If you’re attacking and your roll (as calculated above) is greater than the defender’s armor class (say, 14), then you roll for damage. Suppose you do 2d6 damage on a hit. If your attack roll is greater than or equal to 14, then you do 2d6 damage, otherwise the blow glances off their armor and you do 0 damage.

With vanilla Python, this would look something like:

for roll in rolls:
  if roll >= 14: 
    damage = roll_damage()
  else: 
    damage = 0

However, with Pandas, we shouldn’t loop through the rows. Instead, we’ll use a mask. Like a theater mask, a Pandas mask is an opaque structure that you “punch holes” in wherever you want operations to happen. Then you apply the mask to your Pandas dataframe and apply the operation: only the entries where there are “holes” will get the operation applied.

First we create the mask with the criteria we want to update:

mask = rolls.roll >= 14
mask.head()
0    False
1    False
2    False
3    True 
4    True 
Name: roll, dtype: bool

Now we want to:

  1. Grab all the hits (wherever the mask is True).
  2. Generate random numbers for each hit (equivalent to rolling 2d6).
  3. Merge those hits back into the rolls dataframe.

First we’ll create a series that tracks all of the hits. We want to be able to merge that back into our original dataframe at the end, so we want to preserve the index of each hit from the original dataframe. Based on the mask above, this would be [3, 4, ...] and so on (wherever the mask is True). We get this with mask[mask], which is a series of all the True values with their associated index. Then we set every element of this series to a randomly generate 2d6 roll:

hits = pd.Series(index=mask[mask].index)
hits.loc[:] = np.random.randint(1, 7, size=(len(hits),)) + np.random.randint(1, 7, size=(len(hits),))
hits.head()
3     6
4     3
9     8
10    3
14    3
dtype: int64

Note that we’re generating a “1D” random int (randint(len(hits),)) for the damage instead of the 2D one above (randint(10000,1)) because this is a series (1D), not a dataframe (2D).

Then we can combine that damage back into the rolls dataframe using our original mask:

rolls.loc[mask, 'damage'] = hits
rolls.loc[~mask, 'damage'] = 0
rolls.head()
        roll	damage
0	8	0
1	8	0
2	6	0
3	17	7
4	15	7

This lets you quickly and selectively update data.

Also, I’m still learning! Let me know in the comments if there’s a better way to do any of this.

The thing about renting an apartment in NYC

There’s a lot of weird stuff about NYC real estate and this post attempts to cover some of the things that I’ve experienced. I’ve spent years renting apartments in NYC, mostly from rather small-time landlords with pre-war, rent stabilized buildings. If you’re looking for luxury rentals in new construction, a lot of this advice probably doesn’t apply.

The Mysterious Other Renter

When I’ve found a place I like, invariably the real estate agent calls me a couple hours later telling me that someone else is willing to take the apartment for $100-$200 more per month. Luckily, I’ve never been in a position where I need that particular shitty rental, so I’ve always said, “That’s too bad, I guess I’ll keep looking.” Then a couple hours later the real estate agent calls back and tells me that the other deal miraculously fell through, would I still like the apartment?

This is, I assume, a scummy trick on the part of real estate agent to get an extra $100 commission for a few minutes of extra work (and ingratiate themselves with the landlord). If you have options, don’t fall into a bidding war over a rental.

“Legal rent” vs. what you’re paying

This can actually works out in your favor (at least in the short term). If you are looking for a cheap place, you’re likely to end up with a rent-stabilized apartment. Often, when you sign the lease, there will be an alarmingly high rent listed on the lease (e.g., if you agreed to pay $1,800/month, the lease lists $2,600).

How this works: for rent-stabilized apartments, the landlord is only allowed to raise the rent by a certain city-determined amount each year. For example, if you’re paying $2000/month and the city says landlords can raise rent-stabilized rents by 2%, the landlord can ask you to pay $2200 next year. However, let’s say you’re a good tenant: you always pay your rent on time. You threaten* to move unless the landlord keeps the rent at $2000. So they do: they’d rather have another “guaranteed” $24,000 than risk months of vacancy, a bad tenant, etc. for an extra $2,400.

The city also sometimes puts the rent increase at 0%. Up until last year, there were ~5 years of 0% rent increases. If a landlord had been bumping the legal rent by the max allowable amount before that, though, they could keep bumping the actually rent (up to the legal rent’s ceiling).

Gentrification also is an issue: if you are renting a rent-stabilized apartment and the landlord wants to renovate it into luxury apartments, they have to get rid of their current tenants, first. A great way to do this is to suddenly bump your rent to the max legal rent. If you balked at paying an extra $200/month for a crappy studio with no view, how would you feel about a $1k/month bump? Only the old tenants are gone, the landlord is free to renovate the apartment. Renovations increase the allowable rent they can charge for the apartment, often bumping it out of the range that the city will consider rent-stabilized… allowing the landlord to charge the new tenants absolutely anything.

The migratory patterns of renters

Most people move in the warmer months. Once it starts to get cold, people seem to have a nesting instinct and just don’t want to go out and find a place. This means that landlords are more likely to make concessions and give discounts in the winter months, if you can move then. And they’re more likely to be friendly about letting you out of your lease in the summer months.

Rental agents: the crème de la crap

Real estate agents work on commission: they literally make $0 salary. Like strippers, they generally pay the brokerage for getting to use their space. Because brokerages can charge agents to work for them, they’ll often hire literally anyone that can pass the licensing exam.

Now guess which gives a better commission: signing a lease on a rent-stabilized apartment or closing a $2.5 million sale. The lease gives the agent one month’s rent (maybe a thousand). The sale gives them a few percent of the listing price (maybe $30,000 for the example above).* There are more expenses with selling a listing, but still. Thus, the real estate agents you’ll be working with will generally be the least-experienced agents at the brokerage. A broker once told me, “If a new agent has a deep network and skills, I’m not going to waste them doing rentals.” Putting together rental deals is the latrine duty of real estate.

Thus, if you can avoid it, don’t work with an agent. They’re unlikely to be good at their job and are generally a huge waste of money. However, landlords of big buildings (especially) will sometimes only work with real estate agents. This is because the real estate agents will do some pre-screening (if you make $30k/year they’re not going to waste anyone’s time showing you a $3k/month place) and they will steer tenants to the buildings that they work with (which works out well for everyone, pretty much: the landlords get tenants, the tenants find a place, the real estate agent gets a commission fairly easily).

If you are trying to avoid a broker’s fee, though, you’ll want to avoid those buildings.

Leases

In Manhattan, you’re almost certainly going to have standard year-long leases. The landlord sends you a new lease within 90 days of your lease expiring and you either sign it, ask for modifications, or decline and move out. If you need to move out before that, try to find someone to take over your lease (hey, I know a startup that can help with that) or your landlord is likely to fine you at least a month’s rent (good bye security deposit) for breaking the lease.

A perk of Queens and Brooklyn is that leases often turn into month-to-month leases after then initial year is up (and occasionally will start as month-to-month leases). This gives you a lot more flexibility: often you only have to give 30 days notice before moving out. (I don’t know much about renting in the Bronx or Staten Island.)

Wrapping up

When dealing with landlords: remember that they really just want someone who will reliably pay rent. And, I mean, not burn the place down or turn it into a drug den, but hopefully you won’t have to work very hard to convince them of that side of it.

When working with agents: remember that they are in it for the commission and you are probably more skilled at literally everything than they are, but they have connections and (some) experience. But do your own research and don’t trust anything they say. And since I’m a real estate agent… and I wrote this post… well, I guess this was a huge waste of your time 🙂

I’d love to hear what your experiences have been, let me know in the comments.

* Threatening your landlord: I use the word “threaten” loosely. I’ve always had good luck with just calling the landlord and saying, “I’d really like to stay, is there any way you can see your way to keeping the rent at $2k?” So I’m not going all First Blood on them.
* Commission on sales and rentals: I’m assuming that a brokerage is likely to take at least 50% of the commission for either, although this varies widely.

A self-indulgent post for a self-indulgent day

I think how I feel around holidays is a good barometer for how my life is going. For example, last year over the winter vacation, I just wanted to stay there forever and dreaded returning to “real life.” By contrast, year’s winter vacation I had a lot of fun, but I was looking forward to coming back to work.

Similarly, today is my birthday and I did exactly what I wanted to do: went to work and got dumplings for lunch (which I do a couple times a week). I got a bonus sesame pancake sandwich, which I don’t usually get and was delicious, but I was so full I could only eat half of it. And got a piece of my favorite kind of cake (tres leches from Whole Foods), which I don’t have on a weekly basis. At work I was actually doing work that I don’t enjoy very much, but I knew it was important and related directly to my company’s success. And I found and fixed the bug I was looking for, pushed the fix, and got to see my fix working in production, which was satisfying.

Not actually mine, but basically my lunch.

Now I am drinking whiskey and finishing out the day coding up some more enjoyable stuff with two dogs at my feet (both had some of my leftover sandwich). Life is good.

I liked the name. It’s pretty good!

IPOs, 101

When a friend IM’d me and told me she saw an article saying that MongoDB had filed for IPO, I embarked on a months-long journey of discovering that I knew nothing about IPOs. I found it a bit difficult to get information about them, so I figured I’d write up what I learned. This is mostly from other people/things I read online, so please leave a comment if I got anything wrong/missed anything.

An IPO is a chance for the company to raise funds. Being a publicly-traded company also forces them to have more transparency in accounting and other good things, but the IPO itself is mostly a fundraising event: the company is selling X shares for $Y.

The first thing a company does is find some underwriters, which are large banks who try to sell the offering. Then the company files a confidential S-1 (probably). The confidential version lets them go back and forth with the SEC and iron out any details. Apparently there have been some embarrassing S-1s in the past where companies gave out trade secrets, had blatantly bad accounting, etc. Generally this stage will last a while, as the SEC tends to have suggestions. The SEC could come back with major issues and the company might decide not to file after all. Basically, at this point, you know nothing about anything. But there’s hope.

If all goes well with the SEC, the company will publicly file their S-1. You can find public S-1s on Edgar, the SEC database, but this will still give you approximately no information. Okay, it has profits and losses and risks and all sorts of information that you’re probably interested in if you’re an investor, but if you’re a techie you’re probably looking for the opening price and that probably won’t be there yet. This is because the company will keep amending the S-1 as the IPO approaches. I recommend subscribing: you can ask Edgar to alert you every time there’s activity for a given company.

At a certain point, the IPO date will be announced. You can find a calendar of upcoming IPOs on the stock exchange’s website (e.g., NASDAQ’s calendar). One thing I found confusing was that the day listed was actually the day before what I’d think of as the IPO: the day listed is the “pricing day” when they decide on the price to open at. About a week before this date, a possible opening price will be announced (i.e., the S-1 filing will be amended). This is subject to change, MongoDB’s original projection was $18-$20, then $22, then ended up opening at $24.

Some companies will do a “road show,” where they present what the company does to investors. Honestly, if MongoDB did one of these, I totally missed it. Of course, I’m not the target audience, so maybe it happened.

Around this time, the underwriters will find institutional investors to buy the initial offering. At the point the stock goes on sale, hopefully a large portion of offering has already been spoken for. However, the company doesn’t want the entire offering to be spoken for, so they can take advantage if the price “pops” on the opening day.

Another thing underwriters do is gauge excitement and try to choose a good opening price. A good opening price is as high as possible (to raise the maximum amount for the company) while still allowing it to “pop” sufficiently the first day. There is a psychological factor here (investors like it when the stock jumps the first day) and a practical one: the investors the underwriters convinced to invest in the IPO want to get a 20-30% return. In one day. This blows my mind: if you want to make money off of a startup, obviously the best way isn’t to be involved in one at all. Just work on Wall St. Sigh.

The next morning, at least for NASDAQ, you can look at the NASDAQ Facebook page to get a livestream of the event. It happened at ~10:30, although I thought the market opened at 9:30. So that was a little confusing. Some stock market guy will go up, say a couple of words, and introduce the company president. That guy will get up, say some stuff about the company (MongoDB’s the first DB company to go public in 27 years!). Then they get a bunch of people from the company onstage, there will be a countdown, the company president gets to “ring the bell” (which, AFAICT, is now touching a button on a touchscreen, which was a little sad) and then trading starts.

But not for you.

Remember the option agreements you signed? Me neither, but there’s a clause that’s called a “lock out period.” This is to prevent current/former employees (and investors) from dumping their stock and driving the overall price down. Since a main goal of an IPO is to raise money for the company (and, apparently, fat cats, grr), they don’t want a free-for-all when trading opens. Thus, you have to wait 6 months to start selling your equity. If you do not, the SEC will hunt you down and fine the crap out of you.

10 days after the IPO, the “quiet period” will end. This is when “insiders” (AFAICT, anyone on Wall St) will publish what they think of the company: buy/sell/hold recommendations. This is interesting and will give you an idea about what large banks think the price should be, but again, there are no guarantees.

At 6 months, the lock out period will expire. Generally around this time the stock will dip, as everyone’s expecting a lot of noobs to be dumping their equity.

All in all, it’s a very slow process. I expected it to be more fast-paced and dramatic, but it’s basically several months of speculation and then a few days of activity.

Resources:

  • I found Seeking Alpha had good financial analysis before the event.

A magic trick

I was standing on the street corner with Domino and a group of schoolkids came up. “Hey, can I show you a magic trick?” one of them asked.

“Uh…”

“Think of a number,” he said.

“Ok.”

“Now add 4 to it.”

“Ok.”

“Now add 5 to it.”

“Ok.”

“Now add 1 to it.”

“Ok.”

“Now subtract your original number from it.”

“Ok.”

“Did you get 10?”

“Nope!”

“What? What number did you choose?”

“I was using a different base. If you’re using base 16, then 4, 5, and 1 don’t add up to 10. Don’t kid a kidder, kid.” Then I flipped him a nickel and his head exploded with the power of math.

Well, not really. I wish I had the presence of mind to do that. But next time an elementary school kid tries that, I’m so ready.

P.T. Barnum on salary vs. equity

I’ve been reading P.T. Barnum’s autobiography and came across an interesting passage about when he was employed at Mr. Taylor’s (maybe of Lord & Taylor’s? Not sure) shop:

My employer manifested great interest in me, and treated me with the upmost kindness, but the situation did not suit me. The fact is, there are some persons so constituted that they can never be satisfied with labor for a fixed salary, let it be never so great. I am one of that sort. My disposition is, and ever was, of a speculative character, and I am never content to engage in any business unless it is of such a nature that my profits may be greatly enhanced by an increase of energy, perseverance, attention to business, tact, etc.

He also sounds like quite an ass and a racist, but it’s an interesting read.

Barnum & Bailey show poster

The next great frontier in ML: dogs in hats

I’ve been messing around with Keras, a ML library that makes it pretty easy to do AI experiments. I decided to try out image recognition, so I found a picture of Domino in a fez:

Then I wrote the following Python program which uses the existing ResNet50 image recognition library.

import numpy as np
from keras.preprocessing import image
from keras.applications import resnet50

# Load the image
img = image.load_img('fez.jpg', target_size=(224, 224))
input = image.img_to_array(img)
# Keras can process an array of images, but we're only passing one, so turn it into an array with one element.
input = np.expand_dims(input, axis=0)
# Normalize the RGB values into a 0-1 range, since ML algorithms get thrown off by big ranges.
input = resnet50.preprocess_input(input)

# Predict what's in the photo.
model = resnet50.ResNet50()
predictions = model.predict(input)
predicted_classes = resnet50.decode_predictions(predictions, top=10)

for _, name, liklihood in predicted_classes[0]:
    print("this is an image of {} {}".format(name, liklihood))

In addition to the obvious prereqs, you have to sudo apt-get install libhdf5-dev; pip install pillow certifi h5py.

This prints:

this is an image of Bouvier_des_Flandres 0.4082675576210022
this is an image of briard 0.3710797429084778
this is an image of Newfoundland 0.10781265050172806
this is an image of giant_schnauzer 0.04042242094874382
this is an image of Scotch_terrier 0.038422249257564545
this is an image of komondor 0.012891216203570366
this is an image of Tibetan_terrier 0.0026010528672486544
this is an image of affenpinscher 0.0024157813750207424
this is an image of standard_poodle 0.0021669857669621706
this is an image of Kerry_blue_terrier 0.002110496861860156

It’s pretty solid on “it’s a dog.” I’m disappointed in the lack of fez-related IDs. However, I like some of these as possible breeds for Domino:

Tibetan terrier is pretty close, they are relatives.
Newfies are adorable, but the size is a little off.
I have no idea where Komondor came from. They’re amazing looking, but pretty distinct.

So, still needs some work. But not bad for less than 30 lines of code.

GitHub notification… notifier

Here is what my inbox look like each morning:

All those pink-tagged messages are emails from GitHub. Gmail cannot figure out which ones are important and GitHub’s notification stream, in my experience, is useless. It’s noisy and doesn’t clear the way I’d expect. The problem is, people actually do mention me on bugs. And I often have no idea.

So, I made my own notification system. It’s a Chrome extension that is a little ear that sits next to your location bar. If you have an unread GitHub notification, it turns red. If you’re all caught up, it’s green.

If this would be useful to you, please give it a try!

Download the Chrome extension here.

Feedback and suggestions welcome!