Negotiating a job offer is hard. It is often a tense process involving rash emotional decisions. I built financier, a tool to visualize compensation packages and make it easier to compare job offers and take some of the edge of the job negotiation process.
While this is potentially generalizable to all lines of work, it is particularly useful to compare complex compensation packages such as the ones of Startup companies.
Note: I use interchangeably the terms “job offer” and “compensation package” in this article: they mean the same thing in this context.
How to represent a job offer?
I decided to represent a job
Offer as a python object whose constructor expects a name and list of
components. Components are sources of income, they include:
- 401(k) Match
- Stock Grant
- Onetime Bonus
- Periodic Bonus
When designing financier, one of my goals was to make the code as expressive as possible so that it would read like English. See for yourself with the following example:
HOOLI = Offer( 'Hooli', Salary(yearly_amount=15000), StockGrant( amount=10000, schedule=FourYearsScheduleOneYearCliff( grant_date=datetime.date(2017, 2, 25) ) ), OneTimeBonus(amount=10000, payoff_date=datetime.date(2019, 3, 5)), Match401k(yearly_income=150000, match_percentage=0.03, match_contribution_per_dollar=0.5), )
NOTE: HOOLI is a fictional company featured in the Silicon Valley HBO show!
Once you build an offer you can compute the income over time, for example:
HOOLI.detailed_income( date_range=two_years_by_month() )
Will return a pandas
dataframe with the income from the HOOLI offer for the next two
detailed_income returns the income broken down by components:
Hooli Salary Hooli Grant... Date 2019-02-01 1151 625 ... 2019-03-01 1274 0 2019-04-01 1233 0 2019-05-01 1274 625 2019-06-01 1233 0 2019-07-01 1274 0 ...
If you want to get the total cumulative income over four years you can swap the
date range and substitute
HOOLI.cumulative_income( date_range=four_years_by_month() )
Here is what the resulting
dataframe looks like:
Hooli Gross Income Date 2019-02-01 1948.287671 2019-03-01 13413.356164 2019-04-01 14831.164384 2019-05-01 16921.232877 2019-06-01 18339.041096 2019-07-01 19804.109589 ...
You can then export these
dataframe easily with the
to_csv method and leverage
a spreadsheet software:
HOOLI.cumulative_income( date_range=four_years_by_month() ).to_csv("hooli.csv")
Visualizing and comparing income
While you can build your own visualization for the income
dataframe or use a
financier comes with a handy visualization wrapper using
matplotlib. You can plot the frame above with the following command:
plot_income(HOOLI.cumulative_income( date_range=four_years_by_month() ), "/tmp/hooli.png")
This is very handy to compare offers visually (here we use
income to get
the total income broken down by period):
HOOLI_INCOME = Offer( 'Hooli', Salary(yearly_amount=15000), StockGrant( amount=10000, schedule=FourYearsScheduleOneYearCliff( grant_date=datetime.date(2017, 2, 25) ) ), OneTimeBonus(amount=10000, payoff_date=datetime.date(2019, 3, 5)), Match401k(yearly_income=15_0000, match_percentage=0.03, match_contribution_per_dollar=0.5), ).income( date_range=two_years_by_month() ) RAVIGA_INCOME = Offer( 'Raviga', Salary(yearly_amount=5000), StockGrant( amount=40000, schedule=FourYearsScheduleOneYearCliff( grant_date=datetime.date(2017, 2, 25) ) ), ).income( date_range=two_years_by_month() ) # Make the dates look nicer (Month name and year) plot_filename = "/tmp/hooli_vs_raviga.png" HOOLI_INCOME.index = HOOLI_INCOME.index.to_series().apply(lambda x:x.strftime("%b %y")) RAVIGA_INCOME.index = RAVIGA_INCOME.index.to_series().apply(lambda x:x.strftime("%b %y")) plot_income(HOOLI_INCOME["Hooli Gross Income"].to_frame("Hooli Gross Income").join( RAVIGA_INCOME["Raviga Gross Income"].to_frame("Raviga Gross Income") ), plot_filename)
NOTE: The astute reader will recognize that Raviga is another company from the Silicon Valley Show
Adding components to financier
If you are trying to model an offer but cannot find a component that fits the bill, you can define your own! The contract to define a new component is simple:
Create a class that has an instance method named
valuetaking two dates:
end, a closed interval. Return the value of the component over that interval.
For example, here is the definition of the One Time Bonus component, a bonus paid at one point in time:
class OneTimeBonus: def __init__(self, amount, payoff_date): self.amount = amount self.payoff_date = payoff_date def value(self, start, end): if self.payoff_date >= start and self.payoff_date <= end: return self.amount return 0
It is as simple as it gets, now let’s look at a more complex example, a stock grant:
class StockGrant(object): def __init__(self, amount, schedule): self.amount = amount self.cliffs = [ (e.date, e.value * amount) for e in schedule.vesting_events ] def value(self, start, end): return sum(amount for date, amount in self.cliffs if (date >= start and date <= end))
It isn’t too complicated either. So, if you want to use financier but need to define new components to express complicated compensation packages, it shouldn’t be too hard to get it done!
You can find the code on GitHub