Exercise · m07_carry.py
Cost of carry, discrete dividends, value of a forward, arbitrage
not run yet
Not saved: sign up to keep your code"""M07 exercise 1: cost of carry, discrete dividends, value of a forward, arbitrage.
Implement each function, then run from the repo root:
cd exercises && .venv/bin/python -m pytest modules/M07-forwards-futures/tests/test_m07_carry.py
Conventions (notes §7.0): rates are continuously compounded, T is in years
(ACT/365), q is a continuous dividend yield, and the basis is b = F - S
(Hull uses S - F). Use only the standard library (math).
"""
from __future__ import annotations
import math
from typing import Iterable, Sequence
def fair_forward(S: float, r: float, q: float, T: float) -> float:
"""No-arbitrage forward price F0 = S0 exp((r - q) T) (notes Prop. 7.1, eq. 7.1).
S: spot price; r: risk-free rate; q: dividend yield; T: years to delivery.
"""
raise NotImplementedError
def pv_dividends(dividends: Iterable[tuple[float, float]], r: float, T: float) -> float:
"""Present value I = sum_i D_i exp(-r t_i) of the dividends paid during the life of the forward.
dividends: iterable of (t_i, D_i) with t_i in years from today. Include only
0 < t_i <= T; dividends after delivery do not affect the forward.
"""
raise NotImplementedError
def fair_forward_discrete(S: float, r: float, T: float, dividends: Sequence[tuple[float, float]]) -> float:
"""Forward price with known cash dividends: F0 = (S0 - I) exp(r T) (Prop. 7.2, eq. 7.3),
where I = pv_dividends(dividends, r, T)."""
raise NotImplementedError
def implied_carry(F: float, S: float, T: float, q: float = 0.0) -> float:
"""Implied financing rate r_impl = ln(F/S)/T + q (inverts fair_forward in r).
With q = 0 this returns the implied net carry r - q.
"""
raise NotImplementedError
def forward_value(K: float, S: float, r: float, q: float, T: float, position: int = 1) -> float:
"""Value today of an existing forward with delivery price K and T years left (Prop. 7.3).
Long (position=+1): f = S e^{-qT} - K e^{-rT}, equivalently (F_t - K) e^{-rT}.
Short (position=-1): the negative.
"""
raise NotImplementedError
def arbitrage(F_mkt: float, S: float, r: float, q: float, T: float, cost: float = 0.0, lot_size: int = 1) -> dict:
"""Detect a cost-of-carry arbitrage and return the strategy that locks it in (§7.1.3, Prop. 7.4).
cost: total frictions per unit of underlying, in rupees at time T. An
arbitrage exists only when |F_mkt - fair| > cost.
Return a dict with keys
'strategy': 'cash_and_carry' if F_mkt - fair > cost (buy spot with borrowed
money, sell the forward), 'reverse_cash_and_carry' if
fair - F_mkt > cost (short spot, invest proceeds, buy the
forward), otherwise 'none';
'fair': fair_forward(S, r, q, T);
'profit_at_T': |F_mkt - fair| - cost per unit (0.0 if 'none');
'profit_pv': profit_at_T * exp(-r T);
'profit_per_lot_at_T': profit_at_T * lot_size;
'spot_units': spot units held today per unit of forward: +e^{-qT} for
cash and carry, -e^{-qT} for the reverse, 0.0 for none.
"""
raise NotImplementedError
def carry_trade_pnl(S0: float, S_T: float, r: float, q: float, T: float, F_mkt: float, strategy: str) -> float:
"""Terminal P&L per unit of the arbitrage trade, computed leg by leg.
Cash and carry: borrow S0 e^{-qT}, buy e^{-qT} units of the asset and
reinvest dividends so that exactly 1 unit is held at T (worth S_T); repay
S0 e^{-qT} e^{rT}; the short forward pays F_mkt - S_T.
'reverse_cash_and_carry' is the mirror image (negate every leg).
Raise ValueError for any other strategy string.
The answer must not depend on S_T.
"""
raise NotImplementedError
def roll_cost(F_near: float, F_far: float, qty: float) -> float:
"""Rupee cost of rolling a long futures position: sell qty at F_near, buy qty at F_far.
Returns (F_far - F_near) * qty; positive means the roll costs the long money (contango).
"""
raise NotImplementedError
def calendar_implied_carry(F_near: float, F_far: float, T_near: float, T_far: float) -> float:
"""Annualised net carry r - q implied by a calendar spread: ln(F_far / F_near) / (T_far - T_near)."""
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.