예제 #1
0
    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
예제 #2
0
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'
예제 #3
0
    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
예제 #4
0
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()}
    })
예제 #5
0
    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)
예제 #6
0
    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:]
예제 #7
0
    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}
예제 #8
0
 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)