def deposit(self, timeline, date, amount, description): assert amount >= 0 assert amount == money(amount) if date != self.__next_payment_due: raise NotImplementedError() current_period = Period(date, add_month(date)) interest_rate = self.interest_rate.period_interest_rate(current_period) interest = money(interest_rate * self.balance) if amount < interest: raise NotImplementedError() # TODO(strager): Ensure minimum monthly payment is met. principal = money(amount - interest) if principal > self.balance: raise NotImplementedError() timeline.add_interest_deposit( date=date, account=self, amount=interest, description='{} (interest ({:.5}%))'.format( description, interest_rate * Decimal(12) * Decimal(100))) timeline.add_principal_deposit( date=date, account=self, amount=principal, description='{} (principal)'.format(description)) self.balance = money(self.balance - principal) self.__next_payment_due = current_period.end_date
def __update_finance_charge(self, date): ''' Accrue finance charges for all days before (but not including) the given date. Also, mark finance charges as due as necessary. ''' if self.__last_update is None: assert self.__balance == money(0) return now = self.__last_update while now < date: if now.day == 1: # TODO(strager): Ensure __due_finance_charge is paid within the payment window. if self.__due_finance_charge != money(0): raise NotImplementedError() self.__due_finance_charge = self.__period_finance_charge self.__period_finance_charge = money(0) tomorrow = now + datetime.timedelta(days=1) if self.__balance < money(0): if now in self.__draw_term: interest_rate = self.__interest_rate.period_interest_rate( Period(now, tomorrow)) finance_charge = money(interest_rate * -self.__balance) self.__period_finance_charge += finance_charge elif now in self.__repayment_term: raise NotImplementedError() else: raise NotImplementedError() self.__last_update = tomorrow now = tomorrow assert self.__last_update == date
def __init__(self, name, interest_rate, draw_term, repayment_term): super(LineOfCreditAccount, self).__init__(name=name) self.__interest_rate = interest_rate self.__draw_term = draw_term self.__repayment_term = repayment_term self.__balance = money(0) self.__period_finance_charge = money(0) self.__due_finance_charge = money(0) self.__last_update = None
def tax_due(events, year): taxable_cash_income = sum(event.amount for event in events if event.tax_effect == TaxEffect.CASH_INCOME) withheld_cash = sum(-event.amount for event in events if event.tax_effect == TaxEffect.CASH_WITHHELD) deductible = sum(abs(event.amount) for event in events if event.tax_effect == TaxEffect.DEDUCTIBLE) tax_rate = us_tax_rate(year=year, amount=taxable_cash_income) + ca_tax_rate(year=year, amount=taxable_cash_income) taxable_income = max((taxable_cash_income - deductible, money(0))) total_due = money(taxable_income * tax_rate) net_due = total_due - withheld_cash return net_due
def deposit(self, timeline, date, amount, description): assert amount >= 0 assert amount == money(amount) assert self.__last_update is None or date >= self.__last_update timeline.add_generic_deposit(date=date, account=self, amount=amount, description=description) self.__balance = money(self.__balance + amount) self.__last_update = date
def play(self): start_date = datetime.date(year=2017, month=1, day=1) end_date = datetime.date(year=2047, month=1, day=1) home_purchase_date = datetime.date(2017, 1, 1) home_purchase_amount = money('1200000.00') home_loan_amount = money('975000.00') home_appraisal_amount = home_purchase_amount self.timeline = moneycalc.timeline.Timeline() funcs = [ self.__iter_year_summary_funcs(timeline=self.timeline, start_date=start_date), [(home_purchase_date, lambda date: self.purchase_home(date, home_loan_amount))], iter_tax_payment_funcs(timeline=self.timeline, start_date=start_date, account=self.primary_account), iter_salary_funcs(timeline=self.timeline, start_date=start_date, to_account=self.primary_account), iter_expenses_funcs(timeline=self.timeline, start_date=start_date, account=self.primary_account), iter_property_expense_funcs(timeline=self.timeline, start_date=start_date, account=self.primary_account, home_value=home_appraisal_amount), self.iter_activity_funcs(), ] for (date, func) in moneycalc.util.iter_merge_sort(funcs, key=lambda (date, func): date): if date > end_date: break try: func(date) except NotImplementedError: traceback.print_exc() break print_timeline = False if print_timeline: sys.stdout.write('Timeline:\n\n') for event in self.timeline: sys.stdout.write('{}\n'.format(event)) self.timeline = None
def tax_func(date): amount = money(home_value * tax_rate / 2) account.withdraw(timeline=timeline, date=date, amount=amount, description='Property tax', tax_effect=TaxEffect.DEDUCTIBLE)
def __init__(self, name, amount, interest_rate, term): super(AmortizedMonthlyLoan, self).__init__(name=name) assert amount == money(amount) self.balance = amount self.interest_rate = interest_rate self.term = term self.__next_payment_due = term.start_date self.__maturity_date = sub_month(self.term.end_date)
def minimum_deposit(self, date): if date > self.__maturity_date: raise NotImplementedError() if date != self.__next_payment_due: raise NotImplementedError() current_period = Period(date, add_month(date)) interest_rate = self.interest_rate.period_interest_rate(current_period) if date == self.__maturity_date: interest = money(interest_rate * self.balance) return money(interest + self.balance) else: months_remaining = moneycalc.time.diff_months( self.term.end_date, current_period.start_date) tmp = Decimal( math.pow(Decimal(1) + interest_rate, months_remaining)) return money(self.balance * (interest_rate * tmp) / (tmp - Decimal(1)))
def withdraw(self, timeline, date, amount, description, tax_effect=TaxEffect.NONE): assert amount >= 0 assert amount == money(amount) assert self.__last_update is None or date >= self.__last_update if amount > self.__balance: raise OverdraftError() timeline.add_withdrawl(date=date, account=self, amount=amount, description=description, tax_effect=tax_effect) self.__balance = money(self.__balance - amount) self.__last_update = date
def iter_half_bonus_income_funcs(): year = start_date.year while True: q1 = datetime.date(year=year, month=1, day=1) q3 = datetime.date(year=year, month=7, day=1) for now in [q1, q3]: if now >= start_date: yield ( now, lambda date: receive_income(date, money('14728.95'))) year += 1
def withdraw(self, timeline, date, amount, description, tax_effect=TaxEffect.NONE): assert amount >= 0 assert amount == money(amount) assert self.__last_update is None or date >= self.__last_update if date not in self.__draw_term: # TODO(strager) #raise OverdraftError() pass self.__update_finance_charge(date) timeline.add_withdrawl(date=date, account=self, amount=amount, description=description, tax_effect=tax_effect) self.__balance = money(self.__balance - amount) self.__last_update = date
def iter_quarter_bonus_income_funcs(): year = start_date.year while True: q1 = datetime.date(year=year, month=1, day=1) q2 = datetime.date(year=year, month=4, day=1) q3 = datetime.date(year=year, month=7, day=1) q4 = datetime.date(year=year, month=10, day=1) for now in [q1, q2, q3, q3]: if now >= start_date: yield ( now, lambda date: receive_income(date, money('18750.00'))) year += 1
def deposit(self, timeline, date, amount, description): assert amount >= 0 assert amount == money(amount) assert self.__last_update is None or date >= self.__last_update self.__update_finance_charge(date) principal_amount = amount if self.__due_finance_charge > money(0): # Pay the finance charge due before paying the principal. finance_charge_payment = min(self.__due_finance_charge, amount) if finance_charge_payment > money(0): timeline.add_interest_deposit( date=date, account=self, amount=finance_charge_payment, description='{} (interest)'.format(description)) self.__due_finance_charge -= finance_charge_payment principal_amount -= finance_charge_payment timeline.add_generic_deposit(date=date, account=self, amount=principal_amount, description=description) self.__balance = money(self.__balance + principal_amount) self.__last_update = date
def receive_income(date, gross_income): withheld_401k = money(0) # TODO(strager) taxable_income = gross_income - withheld_401k withheld_us_income_tax = money(taxable_income * Decimal(0.215)) # FIXME(strager) withheld_ca_income_tax = money(taxable_income * Decimal(0.080)) # FIXME(strager) other_tax = money(taxable_income * Decimal(0.15)) # FIXME(strager) net_income = gross_income - withheld_401k - withheld_us_income_tax - withheld_ca_income_tax - other_tax # TODO(strager): 401k. timeline.add_withheld_cash(date=date, amount=withheld_us_income_tax, description='Salary (withheld US tax)') timeline.add_withheld_cash(date=date, amount=withheld_ca_income_tax, description='Salary (withheld CA tax)') timeline.add_income(date=date, amount=taxable_income, description='Salary (taxable)') to_account.deposit(timeline=timeline, date=date, amount=net_income, description='Salary (net)')
def iter_activity_funcs(self): yield (datetime.date(2017, 1, 1), lambda date: self.__checking.deposit(timeline=self.timeline, date=date, amount=money('5000.00'), description='Tooth fairy')) def mortgage_payment_func(date): payment = self.__home_loan.minimum_deposit(date=date) moneycalc.account.transfer(timeline=self.timeline, date=date, from_account=self.__checking, to_account=self.__home_loan, amount=payment, description='{} payment'.format( self.__home_loan)) now = datetime.date(2017, 1, 1) # FIXME(strager) while now < datetime.date(2047, 1, 1): # FIXME(strager) yield (now, mortgage_payment_func) now = moneycalc.time.add_month(now)
def expenses_func(date): account.withdraw(timeline=timeline, date=date, amount=money('1873.61'), description='Expenses')
def iter_base_salary_income_funcs(): now = start_date while True: yield (now, lambda date: receive_income(date, money('7553.31'))) now += datetime.timedelta(days=2 * 7)
def __init__(self, name): super(CheckingAccount, self).__init__(name=name) self.__balance = money(0) self.__last_update = None
def insurance_func(date): account.withdraw(timeline=timeline, date=date, amount=money('1000.00'), description='Home insurance')
def auto_func(date): account.withdraw(timeline=timeline, date=date, amount=money('2225.70'), description='Auto')