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, Set
from .invest_portfolio_weights_types import HoldingWeight, PortfolioHolding, PortfolioWeights
from .money_amount import money ← from money.amount ^1.0.0 · built alongside by fune
def _round_half_away(n: int, d: int) -> int:
"""n / d rounded half away from zero; d > 0."""
a = -n if n < 0 else n
q, r = divmod(a, d)
if 2 * r >= d:
q += 1
return -q if n < 0 else q
def _is_int(value: object) -> bool:
return isinstance(value, int) and not isinstance(value, bool)
def portfolio_weights(holdings: Sequence[PortfolioHolding]) -> PortfolioWeights:
"""Each holding's weight, target and drift. Everything is derived from the
integer values and rounded once at the end, so a weight is never computed
from another rounded figure."""
if len(holdings) == 0:
raise ValueError("holdings must not be empty")
currency = holdings[0].value.currency
seen: Set[str] = set()
total = 0
targets = 0
for h in holdings:
if h.id in seen:
raise ValueError('duplicate holding id "%s"' % h.id)
seen.add(h.id)
if h.value.currency != currency:
raise ValueError("currency mismatch: %s and %s" % (currency, h.value.currency))
if not _is_int(h.value.minor) or h.value.minor < 0:
raise ValueError(
'holding values must be whole minor units, 0 or more; received %s for "%s"' % (h.value.minor, h.id)
)
t = h.target_basis_points
if not _is_int(t) or t < 0 or t > 10000:
raise ValueError(
'targetBasisPoints must be a whole number from 0 to 10000; received %s for "%s"' % (t, h.id)
)
total += h.value.minor
targets += t
if targets != 10000:
raise ValueError("targets must sum to 10000 basis points, received %d" % targets)
if total == 0:
raise ValueError("the portfolio total must be greater than zero")
max_abs = 0
rows: List[HoldingWeight] = []
for h in holdings:
value = h.value.minor
target = h.target_basis_points
drift = _round_half_away(value * 10000 - target * total, total)
target_value = _round_half_away(total * target, 10000)
max_abs = max(max_abs, abs(drift))
rows.append(
HoldingWeight(
id=h.id,
value=money(value, currency),
weight_basis_points=_round_half_away(value * 10000, total),
target_basis_points=target,
drift_basis_points=drift,
target_value=money(target_value, currency),
drift_value=money(value - target_value, currency),
)
)
return PortfolioWeights(total=money(total, currency), holdings=rows, max_abs_drift_basis_points=max_abs)