Markets: A Rigorous Course

Exercise · m12_scenarios.py

Bayesian scenario updating, expected value and decision trees

Read notes.md 12.5 first. Run: cd exercises && .venv/bin/python -m pytest modules/M12-geopolitics-events/tests/test_m12_scenarios.py

Scenarios are indexed s = 0..S-1. A prior is a length-S probability vector. A likelihood vector L has L[s] = P(news | scenario s).

Tests (7)

  • test_hand_computed_posterior
  • test_odds_form_agrees_with_bayes
  • test_sequential_order_invariance_and_log_odds
  • test_uninformative_news_leaves_prior
  • test_expected_value_and_evpi
  • test_option_payoffs_are_convex_and_hedge_caps_loss
  • test_tree_value_rolls_back

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

not run yet

Not saved: sign up to keep your code
"""M12 exercise: Bayesian scenario updating, expected value and decision trees.

Read notes.md §12.5 first. Run:
    cd exercises && .venv/bin/python -m pytest modules/M12-geopolitics-events/tests/test_m12_scenarios.py

Scenarios are indexed s = 0..S-1. A prior is a length-S probability vector.
A likelihood vector L has L[s] = P(news | scenario s).
"""

from __future__ import annotations

import numpy as np


def normalize(w) -> np.ndarray:
    """Scale non-negative weights to sum to one. Raise ValueError on negative
    weights or a zero sum."""
    raise NotImplementedError


def bayes_update(prior, likelihood) -> np.ndarray:
    """Posterior P(s|news) = L[s] prior[s] / sum_j L[j] prior[j]  (eq. 12.16).
    Normalise the prior first. Raise ValueError if lengths differ."""
    raise NotImplementedError


def posterior_odds(prior_odds: float, likelihood_ratio: float) -> float:
    """Odds form (eq. 12.17): posterior odds = prior odds x likelihood ratio."""
    raise NotImplementedError


def odds_to_prob(odds: float) -> float:
    """p = odds / (1 + odds)."""
    raise NotImplementedError


def sequential_update(prior, likelihoods) -> np.ndarray:
    """Apply likelihood vectors one after another (conditionally independent news).
    Return array (k+1, S): row 0 = normalised prior, row j = posterior after j items."""
    raise NotImplementedError


def log_odds_update(prior, likelihoods, ref: int = 0) -> np.ndarray:
    """Same final posterior as sequential_update, computed in log-odds against
    scenario `ref` (eq. 12.18): log(p_s/p_ref) += log(L_s/L_ref) for each item.
    Return probabilities (normalise in a numerically stable way)."""
    raise NotImplementedError


def expected_value(probs, payoffs):
    """E[payoff] per action: normalise(probs) @ payoffs, payoffs shape (S,) or (S, A)."""
    raise NotImplementedError


def best_action(probs, payoffs) -> tuple[int, np.ndarray]:
    """(argmax index, array of expected values) for payoffs of shape (S, A)."""
    raise NotImplementedError


def value_of_perfect_information(probs, payoffs) -> float:
    """EVPI = E_s[max_a payoff(s,a)] - max_a E_s[payoff(s,a)]  (eq. 12.20)."""
    raise NotImplementedError


def call_payoff(s_T, strike: float) -> np.ndarray:
    """max(S_T - K, 0), elementwise."""
    raise NotImplementedError


def put_payoff(s_T, strike: float) -> np.ndarray:
    """max(K - S_T, 0), elementwise."""
    raise NotImplementedError


def scenario_pnl(s0: float, s_T, qty: float = 0.0, calls=(), puts=()) -> np.ndarray:
    """Horizon P&L of qty units of the underlying bought at s0 plus option legs.
    calls / puts: iterables of (strike, premium, quantity), quantity < 0 = short.
    P&L = qty (S_T - s0) + sum over legs of quantity * (payoff - premium)."""
    raise NotImplementedError


def tree_value(node):
    """Roll back a decision tree (eq. 12.19). Node formats:
        {"type": "terminal", "value": v}
        {"type": "chance",   "branches": [(prob, child), ...]}   (raise ValueError if probs don't sum to 1)
        {"type": "decision", "options": {name: child, ...}}
    Chance node value = sum prob * child value; decision node value = max over options.
    Return (value, policy): policy is a list of (path, chosen_option) for every
    decision node, where path is the tuple of labels from the root (chance branches
    by integer index, decision branches by option name)."""
    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.