from typing import Sequence from .invest_dividend_yield_types import DividendYield from .money_amount import Money, money def _is_int(value: object) -> bool: return isinstance(value, int) and not isinstance(value, bool) def _basis_points(amount: int, price: int) -> int: """amount x 10000 / price, half-up; both 0 or more, price > 0.""" q, r = divmod(amount * 10000, price) return q + 1 if 2 * r >= price else q def dividend_yield( price: Money, trailing_dividends: Sequence[Money], forward_dividend: Money, payments_per_year: int ) -> DividendYield: """Trailing yield from the dividends paid in the last twelve months, and forward yield from the latest regular dividend annualised, both over the current price.""" currency = price.currency if not _is_int(price.minor) or price.minor <= 0: raise ValueError("price must be whole minor units greater than zero; received %s" % (price.minor,)) if not _is_int(payments_per_year) or payments_per_year < 1 or payments_per_year > 52: raise ValueError("paymentsPerYear must be a whole number from 1 to 52; received %s" % (payments_per_year,)) for dividend in list(trailing_dividends) + [forward_dividend]: if dividend.currency != currency: raise ValueError("currency mismatch: %s and %s" % (currency, dividend.currency)) if not _is_int(dividend.minor) or dividend.minor < 0: raise ValueError("dividends must be whole minor units, 0 or more; received %s" % (dividend.minor,)) trailing = sum(d.minor for d in trailing_dividends) forward = forward_dividend.minor * payments_per_year return DividendYield( trailing_annual=money(trailing, currency), forward_annual=money(forward, currency), trailing_basis_points=_basis_points(trailing, price.minor), forward_basis_points=_basis_points(forward, price.minor), )