Markets: A Rigorous Course

Exercise · m00_ipo_allotment.py

Retail IPO allotment by lottery

Implement every function, then press Run tests.

Model (SEBI ICDR basis of allotment for retail individual investors, as described in the notes): the retail portion holds K = floor(R / L) minimum lots. If the number of valid applications N exceeds K, K applications are drawn uniformly at random without replacement and each receives one lot, whatever the number of lots it applied for. If N <= K, every application receives one lot and the remaining K - N lots go proportionately to the unmet demand.

Run locally

From the repository root, with the exercises environment installed:

cd exercises && .venv/bin/python -m pytest modules/M00-market-on-ramp/tests/test_m00_ipo_allotment.py

Tests (5)

  • test_lots_and_probability
  • test_at_least_one_matches_hypergeometric
  • test_expected_lots_regimes
  • test_probability_from_subscription
  • test_simulation_agrees

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

not run yet

Not saved: sign up to keep your code
"""Exercise: retail IPO allotment by lottery (notes §0.5).

Implement every function, then run from the repo root:

    cd exercises && .venv/bin/python -m pytest modules/M00-market-on-ramp/tests/test_m00_ipo_allotment.py

Model (SEBI ICDR basis of allotment for retail individual investors, as
described in the notes): the retail portion holds K = floor(R / L) minimum
lots. If the number of valid applications N exceeds K, K applications are
drawn uniformly at random without replacement and each receives one lot,
whatever the number of lots it applied for. If N <= K, every application
receives one lot and the remaining K - N lots go proportionately to the
unmet demand.
"""

from __future__ import annotations

from typing import Sequence

import numpy as np


def lots_available(reserved_shares: int, lot_size: int) -> int:
    """K = floor(reserved_shares / lot_size). Raise ValueError if lot_size <= 0."""
    raise NotImplementedError


def allotment_probability(n_applications: int, n_lots: int) -> float:
    """P(a given application wins a lot) = min(1, K / N) (notes Proposition 0.3)."""
    raise NotImplementedError


def prob_at_least_one(n_applications: int, n_lots: int, m: int) -> float:
    """P(at least one of m distinct applications wins) when K of N are drawn
    without replacement: 1 - C(N - m, K) / C(N, K) = 1 - prod_{j=0}^{m-1} (N-K-j)/(N-j).
    Return 1.0 when K >= N (and m > 0) or when m > N - K. Avoid huge binomials.
    """
    raise NotImplementedError


def expected_lots(applied_lots: Sequence[int], n_lots: int) -> np.ndarray:
    """Expected lots allotted to each application (array, same order).

    If N > K: each gets K / N.
    If N <= K: each gets 1 lot; the spare K - N lots are split in proportion to
    the unmet demand a_i - 1, capped at the demand (if total unmet demand is at
    most the spare lots, everyone gets everything they applied for).
    """
    raise NotImplementedError


def probability_from_subscription(subscription_times: float, avg_lots_per_application: float) -> float:
    """If retail bids total x times the reserved shares and the average
    application is for a lots, then N = x K / a and P = min(1, a / x)."""
    raise NotImplementedError


def simulate_lottery(n_applications: int, n_lots: int, n_sims: int, seed: int) -> float:
    """Monte Carlo check: fraction of n_sims lotteries (K of N drawn without
    replacement, rng = np.random.default_rng(seed)) in which application 0 wins."""
    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.