Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from typing import List, Sequence
from .banking_overdraft_interest_types import OverdraftInterest
from .dates_day_count_fraction import DayCountConvention ← from dates.day-count-fraction ^1.0.0 · built alongside by fune
from .dates_days_between import days_between ← from dates.days-between ^1.0.0 · built alongside by fune
from .lending_daily_interest import DatedBalance, daily_interest ← from lending.daily-interest ^1.0.0 · built alongside by fune
from .math_round_div import RoundingMode ← from math.round-div ^1.0.0 · built alongside by fune
from .money_amount import Money, money ← from money.amount ^1.0.0 · built alongside by fune
def overdraft_interest(
balances: Sequence[DatedBalance],
from_iso: str,
to_iso: str,
annual_rate_basis_points: int,
interest_free_buffer: Money,
convention: DayCountConvention,
mode: RoundingMode,
) -> OverdraftInterest:
"""Overdraft interest over a statement period.
Each day the account is overdrawn, the part of the overdrawn amount above
the interest-free buffer accrues interest; credit balances earn nothing
here. The accrual itself is lending.daily-interest on those chargeable
amounts, so it is summed exactly and rounded once for the period.
"""
b = annual_rate_basis_points
if isinstance(b, bool) or not isinstance(b, int) or b < 0:
raise ValueError("annualRateBasisPoints must not be negative, received %r" % (b,))
if interest_free_buffer.minor < 0:
raise ValueError("interestFreeBuffer must not be negative, received %r" % (interest_free_buffer.minor,))
for entry in balances:
if entry.balance.currency != interest_free_buffer.currency:
raise ValueError("currency mismatch: %s and %s" % (entry.balance.currency, interest_free_buffer.currency))
chargeable: List[DatedBalance] = [
DatedBalance(
date=entry.date,
balance=money(max(0, -entry.balance.minor - interest_free_buffer.minor), entry.balance.currency),
)
for entry in balances
]
# Validates the dates, their order and the period before anything is counted.
accrued = daily_interest(chargeable, from_iso, to_iso, b, convention, mode)
days_overdrawn = 0
days_charged = 0
for i, entry in enumerate(balances):
start = entry.date if entry.date > from_iso else from_iso
following = balances[i + 1].date if i + 1 < len(balances) else to_iso
end = following if following < to_iso else to_iso
if start >= end:
continue
days = days_between(start, end)
if entry.balance.minor < 0:
days_overdrawn += days
if chargeable[i].balance.minor > 0:
days_charged += days
return OverdraftInterest(interest=accrued.interest, days_overdrawn=days_overdrawn, days_charged=days_charged)