Exercise · m06a_merger_arb.py
Merger arbitrage: spreads, implied deal probability, annualised returns
not run yet
Not saved: sign up to keep your code"""M06A · Merger arbitrage: spreads, implied deal probability, annualised returns.
Exercise stubs. Replace each ``raise NotImplementedError`` with your code, then run the tests in
../tests. See notes.md §6A.7 (Propositions 6A.10–6A.11).
Conventions
-----------
* ``p_target`` : current price of one target share (₹).
* ``deal_value``: value per target share delivered if the deal closes. For a cash deal this is the
offer price; for a stock-for-stock deal with exchange ratio R it is R * P_acquirer (the arbitrageur
shorts R acquirer shares, so the acquirer price is locked in).
* ``fallback`` : estimated target price if the deal fails (the "downside").
* ``T`` : time to close in years. ``r``: simple annual financing rate.
* ``carry`` : net cash received per target share while waiting (target dividends minus
R * acquirer dividends owed on the short, plus any short rebate); default 0.
Simple (not continuous) carry is used throughout: the arbitrageur finances p_target at rate r for T.
"""
from __future__ import annotations
import numpy as np
def stock_deal_value(p_acquirer: float, ratio: float) -> float:
"""Deal value per target share in a stock-for-stock deal: R * P_A."""
raise NotImplementedError
def gross_spread(p_target: float, deal_value: float) -> tuple[float, float]:
"""Return (spread in ₹, spread as a fraction of the target price).
spread = deal_value - p_target; pct = spread / p_target.
"""
raise NotImplementedError
def implied_deal_probability(p_target: float, deal_value: float, fallback: float,
T: float = 0.0, r: float = 0.0, carry: float = 0.0) -> float:
"""Implied probability of completion (Proposition 6A.10).
The arbitrage price satisfies
p_target * (1 + r T) = p * deal_value + (1 - p) * fallback + carry,
so solve for p. Raise ValueError if deal_value <= fallback. Do NOT clip the result: a value
outside [0, 1] tells you the inputs (usually the fallback) are inconsistent with the price.
"""
raise NotImplementedError
def expected_return(p_target: float, deal_value: float, fallback: float, p: float,
T: float = 0.0, carry: float = 0.0) -> float:
"""Expected holding-period return (not annualised) of the long-target, hedged position:
E[R] = (p * deal_value + (1 - p) * fallback + carry) / p_target - 1.
"""
raise NotImplementedError
def annualised_spread_return(p_target: float, deal_value: float, T: float,
carry: float = 0.0, compound: bool = True) -> float:
"""Annualised return if the deal closes at T years.
Holding-period return h = (deal_value + carry) / p_target - 1.
compound=True : (1 + h) ** (1 / T) - 1; compound=False : h / T.
Raise ValueError if T <= 0.
"""
raise NotImplementedError
def breakeven_probability(p_target: float, deal_value: float, fallback: float,
T: float, hurdle: float, carry: float = 0.0) -> float:
"""Completion probability at which the expected simple annualised return equals ``hurdle``:
solve p * deal_value + (1-p) * fallback + carry = p_target * (1 + hurdle * T) for p.
Raise ValueError if deal_value <= fallback.
"""
raise NotImplementedError
def kelly_fraction_binary(p: float, gain: float, loss: float) -> float:
"""Kelly fraction for a binary bet returning +gain (prob p) or -loss (prob 1-p), as fractions
of the stake (M03): f* = p/loss - (1-p)/gain. Return 0 if the edge is non-positive.
Raise ValueError unless gain > 0 and loss > 0."""
raise NotImplementedError
def spread_series(p_target: np.ndarray, p_acquirer: np.ndarray, ratio: float) -> np.ndarray:
"""Percentage spread time series (R * P_A - P_T) / P_T for aligned price arrays."""
raise NotImplementedError
def years_between(d0: str, d1: str) -> float:
"""Actual/365 year fraction between ISO dates (helper; provided)."""
import datetime as dt
a = dt.date.fromisoformat(d0)
b = dt.date.fromisoformat(d1)
return (b - a).days / 365.0
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.