Files
zulip/tools/deprecated/finbot/money.py
2016-05-29 05:03:08 -07:00

215 lines
7.6 KiB
Python
Executable File

#!/usr/bin/env python
from __future__ import division
from __future__ import print_function
import datetime
import monthdelta
def parse_date(date_str):
return datetime.datetime.strptime(date_str, "%Y-%m-%d")
def unparse_date(date_obj):
return date_obj.strftime("%Y-%m-%d")
class Company(object):
def __init__(self, name):
self.name = name
self.flows = []
self.verbose = False
def __str__(self):
return self.name
def add_flow(self, flow):
self.flows.append(flow)
def cash_at_date_internal(self, start_date, end_date):
cash = 0
for flow in self.flows:
delta = flow.cashflow(start_date, end_date, (end_date - start_date).days)
cash += delta
if self.verbose:
print(flow.name, round(delta, 2))
return round(cash, 2)
def cash_at_date(self, start, end):
start_date = parse_date(start)
end_date = parse_date(end)
return self.cash_at_date_internal(start_date, end_date)
def cash_monthly_summary(self, start, end):
start_date = parse_date(start)
cur_date = parse_date(start)
end_date = parse_date(end)
while cur_date <= end_date:
print(cur_date, self.cash_at_date_internal(start_date, cur_date))
cur_date += monthdelta.MonthDelta(1)
if self.verbose:
print()
# CashFlow objects fundamentally just provide a function that says how
# much cash has been spent by that source at each time
#
# The API is that one needs to define a function .cashflow(date)
class CashFlow(object):
def __init__(self, name):
self.name = name
class FixedCost(CashFlow):
def __init__(self, name, amount):
super(FixedCost, self).__init__(name)
self.cost = -amount
def cashflow(self, start, end, days):
return self.cost
class ConstantCost(CashFlow):
def __init__(self, name, amount):
super(ConstantCost, self).__init__(name)
self.rate = -amount
def cashflow(self, start, end, days):
return self.rate * days / 365.
class PeriodicCost(CashFlow):
def __init__(self, name, amount, start, interval):
super(PeriodicCost, self).__init__(name)
self.amount = -amount
self.start = parse_date(start)
self.interval = interval
def cashflow(self, start, end, days):
cur = self.start
delta = 0
while (cur <= end):
if cur >= start:
delta += self.amount
cur += datetime.timedelta(days=self.interval)
return delta
class MonthlyCost(CashFlow):
def __init__(self, name, amount, start):
super(MonthlyCost, self).__init__(name)
self.amount = -amount
self.start = parse_date(start)
def cashflow(self, start, end, days):
cur = self.start
delta = 0
while (cur <= end):
if cur >= start:
delta += self.amount
cur += monthdelta.MonthDelta(1)
return delta
class TotalCost(CashFlow):
def __init__(self, name, *args):
self.name = name
self.flows = args
def cashflow(self, start, end, days):
return sum(cost.cashflow(start, end, days) for cost in self.flows)
class SemiMonthlyCost(TotalCost):
def __init__(self, name, amount, start1, start2 = None):
if start2 is None:
start2 = unparse_date(parse_date(start1) + datetime.timedelta(days=14))
super(SemiMonthlyCost, self).__init__(name,
MonthlyCost(name, amount, start1),
MonthlyCost(name, amount, start2)
)
class SemiMonthlyWagesNoTax(SemiMonthlyCost):
def __init__(self, name, wage, start):
super(SemiMonthlyWagesNoTax, self).__init__(name, self.compute_wage(wage), start)
def compute_wage(self, wage):
return wage / 24.
class SemiMonthlyWages(SemiMonthlyWagesNoTax):
def compute_wage(self, wage):
fica_tax = min(wage, 110100) * 0.062 + wage * 0.0145
unemp_tax = 450
return (wage + fica_tax + unemp_tax) / 24.
def __init__(self, name, wage, start):
super(SemiMonthlyWages, self).__init__(name, wage, start)
class DelayedCost(CashFlow):
def __init__(self, start, base_model):
super(DelayedCost, self).__init__("Delayed")
self.base_model = base_model
self.start = parse_date(start)
def cashflow(self, start, end, days):
start = max(start, self.start)
if start > end:
return 0
time_delta = (end-start).days
return self.base_model.cashflow(start, end, time_delta)
class BiweeklyWagesNoTax(PeriodicCost):
def __init__(self, name, wage, start):
super(BiweeklyWagesNoTax, self).__init__(name, self.compute_wage(wage), start, 14)
def compute_wage(self, wage):
# You would think this calculation would be (wage * 14 /
# 365.24), but you'd be wrong -- companies paying biweekly
# wages overpay by about 0.34% by doing the math this way
return wage / 26.
class BiweeklyWages(BiweeklyWagesNoTax):
def compute_wage(self, wage):
fica_tax = min(wage, 110100) * 0.062 + wage * 0.0145
unemp_tax = 450
# You would think this calculation would be (wage * 14 /
# 365.24), but you'd be wrong -- companies paying biweekly
# wages overpay by about 0.34% by doing the math this way
return (wage + fica_tax + unemp_tax) / 26.
def __init__(self, name, wage, start):
super(BiweeklyWages, self).__init__(name, wage, start)
if __name__ == "__main__":
# Tests
c = Company("Example Inc")
c.add_flow(FixedCost("Initial Cash", -500000))
c.add_flow(FixedCost("Incorporation", 500))
assert(c.cash_at_date("2012-01-01", "2012-03-01") == 500000 - 500)
c.add_flow(FixedCost("Incorporation", -500))
c.add_flow(ConstantCost("Office", 50000))
assert(c.cash_at_date("2012-01-01", "2012-01-02") == 500000 - round(50000*1/365., 2))
c.add_flow(ConstantCost("Office", -50000))
c.add_flow(PeriodicCost("Payroll", 4000, "2012-01-05", 14))
assert(c.cash_at_date("2012-01-01", "2012-01-02") == 500000)
assert(c.cash_at_date("2012-01-01", "2012-01-06") == 500000 - 4000)
c.add_flow(PeriodicCost("Payroll", -4000, "2012-01-05", 14))
c.add_flow(DelayedCost("2012-02-01", ConstantCost("Office", 50000)))
assert(c.cash_at_date("2012-01-01", "2012-01-05") == 500000)
assert(c.cash_at_date("2012-01-01", "2012-02-05") == 500000 - round(50000*4/365., 2))
c.add_flow(DelayedCost("2012-02-01", ConstantCost("Office", -50000)))
c.add_flow(DelayedCost("2012-02-01", FixedCost("Financing", 50000)))
assert(c.cash_at_date("2012-01-01", "2012-01-15") == 500000)
c.add_flow(DelayedCost("2012-02-01", FixedCost("Financing", -50000)))
c.add_flow(SemiMonthlyCost("Payroll", 4000, "2012-01-01"))
assert(c.cash_at_date("2012-01-01", "2012-01-01") == 500000 - 4000)
assert(c.cash_at_date("2012-01-01", "2012-01-14") == 500000 - 4000)
assert(c.cash_at_date("2012-01-01", "2012-01-15") == 500000 - 4000 * 2)
assert(c.cash_at_date("2012-01-01", "2012-01-31") == 500000 - 4000 * 2)
assert(c.cash_at_date("2012-01-01", "2012-02-01") == 500000 - 4000 * 3)
assert(c.cash_at_date("2012-01-01", "2012-02-15") == 500000 - 4000 * 4)
c.add_flow(SemiMonthlyCost("Payroll", -4000, "2012-01-01"))
c.add_flow(SemiMonthlyWages("Payroll", 4000, "2012-01-01"))
assert(c.cash_at_date("2012-01-01", "2012-02-15") == 499207.33)
c.add_flow(SemiMonthlyWages("Payroll", -4000, "2012-01-01"))
print(c)
c.cash_monthly_summary("2012-01-01", "2012-07-01")