Functional Weave
Code in TypeScript

invest.fee-drag@1.0.1

impl/python.py

2,470 bytes · the Python implementation · view raw

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

from .invest_fee_drag_types import FeeDrag, FeeDragYear
from .money_amount import Money, money  ← from money.amount ^1.0.0 · built alongside by fune
from .money_apply_rate import apply_rate  ← from money.apply-rate ^1.0.0 · built alongside by fune


def _is_int(value: object) -> bool:
    return isinstance(value, int) and not isinstance(value, bool)


def fee_drag(
    start_value: Money, annual_growth_basis_points: int, annual_fee_basis_points: int, years: int
) -> FeeDrag:
    """The same investment grown twice, with and without an annual charge
    taken on the year-end value, rounding to minor units every year as a
    statement would."""
    if not _is_int(start_value.minor) or start_value.minor < 0:
        raise ValueError("startValue must be whole minor units, 0 or more; received %s" % (start_value.minor,))
    g = annual_growth_basis_points
    if not _is_int(g) or g < -10000:
        raise ValueError("annualGrowthBasisPoints must be a whole number, -10000 or more; received %s" % (g,))
    f = annual_fee_basis_points
    if not _is_int(f) or f < 0 or f > 10000:
        raise ValueError("annualFeeBasisPoints must be a whole number from 0 to 10000; received %s" % (f,))
    if not _is_int(years) or years < 0 or years > 100:
        raise ValueError("years must be a whole number from 0 to 100; received %s" % (years,))
    currency = start_value.currency
    without = start_value.minor
    with_fee = start_value.minor
    fees = 0
    schedule: List[FeeDragYear] = []
    for year in range(1, years + 1):
        without += apply_rate(money(without, currency), g, "half-up").minor
        grown = with_fee + apply_rate(money(with_fee, currency), g, "half-up").minor
        fee = apply_rate(money(grown, currency), f, "half-up").minor
        with_fee = grown - fee
        fees += fee
        schedule.append(
            FeeDragYear(
                year=year,
                value_without_fee=money(without, currency),
                value_with_fee=money(with_fee, currency),
                fee=money(fee, currency),
            )
        )
    drag = without - with_fee
    drag_bp = 0
    if without > 0:
        n = drag * 10000
        q, r = divmod(abs(n), without)
        if 2 * r >= without:
            q += 1
        drag_bp = -q if n < 0 else q
    return FeeDrag(
        value_without_fee=money(without, currency),
        value_with_fee=money(with_fee, currency),
        drag=money(drag, currency),
        drag_basis_points=drag_bp,
        fees_paid=money(fees, currency),
        schedule=schedule,
    )