Exercise · m06b_amc.py
How fund houses make money: AMC revenue and profit, operating leverage, the value of a SIP customer
not run yet
Not saved: sign up to keep your code"""M06B · How fund houses make money: AMC revenue and profit, operating leverage, the value of a SIP customer.
Exercise stubs. Replace each ``raise NotImplementedError`` with your code, then run the tests in
../tests. See notes.md §6B.5: revenue (6B.10), Proposition 6B.5.1 (profit (6B.11) and its beta to the
market (6B.12), Worked example 6B.6), and Proposition 6B.5.2 (the present value of the fees on a SIP
customer (6B.13), Worked example 6B.7).
Conventions
-----------
* AUM: equity AUM A_e with elasticity β_A = d ln A_e / d ln M to the market level M, and other AUM A_d
that does not move with M. Fee yields y are annual fractions of average AUM kept by the AMC.
* Costs: a fixed amount F a year plus a share v of revenue. Profit Π = (1 − v) · revenue − F.
* SIP customer (6B.13): monthly rates throughout. Instalment P at the start of each of the first n
months; holdings grow at g a month (net of fees); the AMC earns y/12 on the end-of-month holding; the
customer leaves each month with probability λ; cash flows are discounted at k a month.
"""
from __future__ import annotations
import numpy as np
def amc_revenue(fee_yields, avg_aums) -> float:
"""Revenue = Σ_s y_s Ā_s (6B.10)."""
raise NotImplementedError
def amc_profit(aum_equity: float, aum_other: float, yield_equity: float, yield_other: float, fixed: float,
var_share: float) -> float:
"""Π = (1 − v)(y_e A_e + y_d A_d) − F (6B.11)."""
raise NotImplementedError
def profit_at_market(m: float, aum_equity: float, aum_other: float, yield_equity: float, yield_other: float,
fixed: float, var_share: float, aum_beta: float = 1.0) -> float:
"""Profit when the market is at m times today's level: equity AUM becomes A_e · m^β_A."""
raise NotImplementedError
def profit_beta(aum_equity: float, aum_other: float, yield_equity: float, yield_other: float, fixed: float,
var_share: float, aum_beta: float = 1.0) -> float:
"""β_Π = d ln Π / d ln M = β_A (1 − v) y_e A_e / Π (6B.12); with A_d = 0 it is β_A (1 + F/Π).
Raise ValueError if Π <= 0."""
raise NotImplementedError
def monthly_rate(annual: float) -> float:
"""Monthly rate equivalent to an annual compound rate: (1 + annual)^(1/12) − 1."""
raise NotImplementedError
def monthly_attrition(annual: float) -> float:
"""Monthly leaving probability λ equivalent to losing a fraction ``annual`` of customers a year:
1 − (1 − annual)^(1/12)."""
raise NotImplementedError
def pv_fees_sip(P: float, n: int, fee_yield: float, g: float, lam: float, k: float) -> float:
"""PV = y_m P · q/(1 − q) · (1 − a^n)/(1 − a) (6B.13), with y_m = fee_yield/12, a = (1 − λ)/(1 + k)
and q = (1 + g) a. Raise ValueError unless q < 1."""
raise NotImplementedError
def pv_fees_sip_simulated(P: float, n: int, fee_yield: float, g: float, lam: float, k: float,
n_customers: int, horizon: int, seed: int = 0) -> np.ndarray:
"""Discounted fees from ``n_customers`` simulated customers over ``horizon`` months (the model of
(6B.13), truncated at the horizon). With rng = np.random.default_rng(seed), each customer's departure
month is rng.geometric(lam, n_customers): a customer leaving in month L earns fees in months
1, …, L − 1 and none from month L on. Return the array of per-customer present values."""
raise NotImplementedError
No runs yet
The stub's functions raise
NotImplementedError until you write them. Fill in one, press
Run tests, and watch its tests turn green.Reference solution
Available after your first run, with an account approved for code runs.