def balance_from_cashflows(cashflows, rates, as_of): """ Computes balance on the as_of date, assuming rate is constant and cashflows are all the cashflows. Args: cashflows: List of cashflows. (Fake cashflows can be used to compute partial results. rates (list of Rates): as_of: The date to give the answer for. Returns: (float) Balance on as of day. """ cashflows = [c for c in cashflows if get_date(c.datetime) <= as_of] # Cashflows on the date included. if not cashflows: return 0 date = get_date(cashflows[0].datetime) current_balance = 0 next_cashflow = cashflows.pop(0) while next_cashflow: while get_date(next_cashflow.datetime) == date: current_balance -= next_cashflow.amount try: next_cashflow = cashflows.pop(0) next_date = get_date(next_cashflow.datetime) except IndexError: next_cashflow = None next_date = as_of break current_balance = UserAccount.balance_interpolate(current_balance, date, next_date, rates) date = next_date return current_balance
def request_funding(user_account, approval_id, amount, dt): active_decision = user_account.get_active_decision(dt) if (active_decision is None or approval_id != active_decision.id or active_decision.decision == DECLINED_STATE_NAME): return None, 'Invalid Decision' cur_balance = user_account.balance(get_date(dt)) if MIN_LOAN_AMOUNT <= cur_balance + amount <= active_decision.amount: try: funding = user_account.add_funding(amount) except ValueError: return None, 'Funding Error' fee = active_decision.fee_rate * amount + active_decision.fee_amount if fee: user_account.add_cashflow(-1 * fee, funding.datetime, cashflow_type=FEE_TYPE, ref='Internal') loan = user_account.create_loan( funding.datetime, cur_balance + amount + fee, duration_days=active_decision.duration_days, interest_daily=active_decision.interest_daily, repayment_frequency_days=active_decision.repayment_frequency_days) return loan, None else: return None, 'Invalid Amount'
def payment_due(self, loan, cashflows, as_of): """ Returns payment due, considering min repayment and any cashflows. Doesn't consider balance. Args: loan: cashflows: as_of: Returns: """ min_repayment = loan.repayment_amount unpaid_amount = 0 prev_date = get_date(loan.start_datetime) repayment_frequency = datetime.timedelta(loan.repayment_frequency_days) cur_date = prev_date + repayment_frequency while cur_date < as_of: past, present, future = self.cashflows_by_period(prev_date, cur_date, cashflows) paid_this_period = sum(c.amount for c in present) unpaid_amount += min_repayment - paid_this_period unpaid_amount = max(0, unpaid_amount) prev_date = cur_date cur_date += repayment_frequency cashflows = future if cur_date == as_of: return unpaid_amount + min_repayment # still need to limit by balance else: return unpaid_amount
def get_schedule(): user_account = UserAccount(g.user.id) as_of = get_date(time_now()) balance = user_account.balance(as_of) schedule = user_account.repayment_schedule_for_date(as_of) return json.dumps({ 'balance': balance, 'schedule': {k.isoformat(): v for k, v in schedule.items()} })
def repayment_schedule_for_date(self, as_of): real_cashflows = [c for c in self.cashflows if get_date(c.datetime) <= as_of] loans = [l for l in self.loans if l.start_datetime.date() <= as_of] if loans: loan = loans[-1] else: # cashflows before loan are bad return {} rates = self.interest_rates_from_loans(loans) return self.repayment_schedule2(loan, real_cashflows, rates, as_of)
def cashflows_by_period(period_start, period_end, cashflows): """ Splits cashflows by period into 3: before start of period, during period and after end of period. Period is a date, inside period is if date in (period_start, period_end]. """ assert period_end > period_start dates = [get_date(c.datetime) for c in cashflows] start = bisect.bisect_right(dates, period_start) end = bisect.bisect_right(dates, period_end) return cashflows[:start], cashflows[start: end], cashflows[end:]
def repayment_schedule2(self, loan, cashflows, rates, as_of): schedule = [] repayment_frequency = datetime.timedelta(loan.repayment_frequency_days) start_date = get_date(loan.start_datetime) min_repayment = loan.repayment_amount prev_date = start_date cur_date = prev_date + repayment_frequency while cur_date < as_of: prev_date = cur_date cur_date += repayment_frequency balance = self.balance_from_cashflows(cashflows, rates, prev_date) #what if overdue? should really be falling due now. Do future cashflows make sense in such case though? while balance > 0.01: past, present, future = self.cashflows_by_period(prev_date, cur_date, cashflows) paid_this_period = sum(c.amount for c in present) balance = self.balance_from_cashflows( [CashFlow(amount=-balance, datetime=prev_date)] + present, rates, cur_date ) repayment = round(min(max(min_repayment - paid_this_period, 0), balance), 2) balance -= repayment schedule += present if repayment > 0: schedule += [CashFlow(amount=repayment, datetime=cur_date)] cashflows = future prev_date = cur_date cur_date += repayment_frequency dates = [dt for dt in [get_date(c.datetime) for c in schedule] if dt >= as_of] return {date: sum(c.amount for c in schedule if get_date(c.datetime) == date) for date in dates}
def interest_rates_from_loans(loans): rate_dict = {get_date(loan.start_datetime): loan.interest_daily for loan in loans} return sorted([Rate(date, rate) for date, rate in rate_dict.items()], key=lambda x: x.date)