from typing import List, Sequence from .banking_overdraft_interest_types import OverdraftInterest from .dates_day_count_fraction import DayCountConvention from .dates_days_between import days_between from .lending_daily_interest import DatedBalance, daily_interest from .math_round_div import RoundingMode from .money_amount import Money, money 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)