Markets: A Rigorous Course

Exercise · m06b_amc.py

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.

Tests (6)

  • test_revenue_6b10
  • test_worked_example_6b6_operating_leverage
  • test_profit_beta_matches_finite_difference
  • test_monthly_conversions
  • test_worked_example_6b7_value_of_a_sip_customer
  • test_closed_form_matches_direct_sum_and_simulation

Theory: M06B notes. Run with Ctrl/⌘+Enter.

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.

Create a free account

Reading is open to everyone. An account keeps your progress, answers, notes and code drafts, and gives you the study plan, the journals and the simulator. Running code needs an account approved for it.