#!/usr/bin/env python """Calculate how much money you'd leave on the table if you quit right now. Place a file named TICKER-stock.csv (where TICKER is the ticker symbol you want to track) in the same directory as this script, formatted as: MM/DD/YYYY,shares Where MM is the month, DD is the date, and YYYY is the year that "shares" number of your TICKER shares will vest. A file named "TICKER-cache.txt" (again, where TICKER is the ticker symbol you looked up) will be created in the same directory as the script, caching the last price retrieved from Yahoo Finance for up to an hour so you don't keep requesting it (handy for running this when you logging in). Run as "remaining.py ", ie. "remaining.py MSFT". """ from __future__ import print_function import argparse import csv import datetime import json import os import urllib DATADIR = os.path.dirname(__file__) SHARES = os.path.join(DATADIR, "%s-shares.csv") QUOTE = ("https://query.yahooapis.com/v1/public/yql?q=select%%20*%%20from" "%%20yahoo.finance.quote%%20where%%20symbol%%3D%%22%s%%22&" "format=json&diagnostics=true&env=store%%3A%%2F%%2Fdatatables.org" "%%2Falltableswithkeys&callback=") CACHED_QUOTE = os.path.join(DATADIR, "%s-cache.txt") def get_stock(ticker): """Retrieve the current stock price for a given ticker symbol.""" cached = False result = None cachedate = 0 # Check if there's a cache. try: oldquote = os.stat(CACHED_QUOTE % ticker) cachedate = datetime.datetime.fromtimestamp(oldquote.st_mtime) result = open(CACHED_QUOTE % ticker, "r").read().strip() cached = True except (OSError, IOError): pass # If we didn't get a result from cache, or if the date on the cache is too # old, force a reload from network. cachelimit = datetime.datetime.now() - datetime.timedelta(hours=1) if result is None or cachedate < cachelimit: try: query = json.load(urllib.urlopen(QUOTE % ticker)) result = query["query"]["results"]["quote"]["LastTradePriceOnly"] with open(CACHED_QUOTE % ticker, "w") as oldquote: print(result, file=oldquote) cached = False except: # pylint: disable=bare-except # If we got this far and still don't have a result, bail out. if result is None: raise return float(result), cached def import_vesting(ticker): """Given a ticker symbol, parse a csv of vesting dates.""" vested = 0 vesting = {} with open(SHARES % ticker, "r") as csvfh: for date, amount in csv.reader(csvfh): date = datetime.datetime.strptime(date, "%m/%d/%Y") amount = int(amount) if date > datetime.datetime.now(): vesting[date] = amount else: vested += amount return vested, vesting def output_upcoming(vesting, total, stockval): """For a set of upcoming vesting events, output a summary.""" shrwidth = max(len(str(x)) for x in vesting.itervalues()) for nextvest in sorted(vesting.keys()): nextvestamt = vesting[nextvest] nextshares = ("%%%dd" % shrwidth) % nextvestamt nextvestpct = (float(nextvestamt) / total) * 100 nextvestval = nextvestamt * stockval print(" On %s, %s (%.2f%%) shares will vest, worth $%.2f." % (nextvest.strftime("%b %d %Y"), nextshares, nextvestpct, nextvestval)) def output_remaining(ticker, verbose=False): """For the given ticker, output a summary of how much is left.""" vested, vesting = import_vesting(ticker) unvested = sum(vesting.values()) total = vested + unvested unvestpct = (float(unvested) / total) * 100 stockval, cached = get_stock(ticker) unvestval = unvested * stockval stock = "%.2f%s" % (stockval, " (cached)" if cached else "") print("%d/%d (%.2f%%) shares, worth $%.2f at $%s/share" % (unvested, total, unvestpct, unvestval, stock)) if verbose: output_upcoming(vesting, total, stockval) def main(): """__main__""" parser = argparse.ArgumentParser() parser.add_argument("ticker", help="Stock ticker symbol to look up") parser.add_argument("-v", "--verbose", action="store_true", help="Show all upcoming vesting events") args = parser.parse_args() output_remaining(args.ticker, args.verbose) if __name__ == "__main__": main()