Exercise · m09a_margin_greeks.py
Portfolio Greeks of multi-leg positions, a SPAN-like scenario margin, and adjustments viewed as changes of Greeks
not run yet
Not saved: sign up to keep your code"""M09A exercise 3: portfolio Greeks of multi-leg positions, a SPAN-like scenario margin,
and adjustments viewed as changes of Greeks (notes §9A.12-9A.13).
A position is a list of Leg objects on one Nifty-like forward F. Options are European,
priced with Black-76 at one flat volatility. A 'fut' leg is a future bought/sold at price K
(its value today is qty * (F - K); carry and daily settlement are ignored).
Greek conventions (as in M08/M09): delta per unit of F, gamma per unit F^2, vega per 1.00
of sigma, theta per YEAR of calendar time (dV/dt; a long option has negative theta).
qty is signed and in units of the underlying (one Nifty lot = 65 units as of 5 Nov 2026).
The scenario parameters (scan range 6%, vol shift 4 points, extreme move 2x scan at 35%)
are illustrative defaults, not NSE Clearing's published values; read those from the
daily SPAN risk-parameter files.
Provided (toolkit.options; you wrote Black-76 in M08):
Leg(kind, K, T, qty) frozen dataclass; kind 'call', 'put' or 'fut'
(K = strike, or the futures trade price)
b76(F, K, tau, r, sigma, kind) Black-76 price; intrinsic value at tau <= 0
b76_greeks(F, K, tau, r, sigma, kind) dict(delta, gamma, vega, theta, rho), per unit
leg_value(leg, F, t, r, sigma) below
Run: cd exercises && .venv/bin/python -m pytest modules/M09A-indian-options-desk/tests/test_m09a_margin_greeks.py
"""
from __future__ import annotations
import math
from dataclasses import replace
import numpy as np
from toolkit.options import Leg # noqa: F401 (provided)
from toolkit.options import black76_greeks as b76_greeks # noqa: F401 (provided, M08)
from toolkit.options import black76_price as b76 # noqa: F401 (provided, M08)
# ------------------------------------------------------------------ provided
def leg_value(leg: Leg, F: float, t: float, r: float, sigma: float) -> float:
if not leg.is_option: # 'fut': a future traded at K is worth qty * (F - K)
return leg.qty * (F - leg.K)
return leg.qty * b76(F, leg.K, leg.T - t, r, sigma, leg.kind)
# ------------------------------------------------------------------ exercises
def position_value(legs, F, t, r, sigma) -> float:
"""Sum of leg_value over legs at forward F, calendar time t (years from today)."""
raise NotImplementedError
def position_greeks(legs, F, t, r, sigma) -> dict:
"""dict(delta, gamma, vega, theta) = qty-weighted sum of b76_greeks over option legs
(time to expiry T - t); a 'fut' leg adds qty to delta and nothing else. (9A.16)"""
raise NotImplementedError
def scenario_losses(legs, F, r, sigma, scan, vol_shift, extreme_mult=2.0, extreme_weight=0.35) -> np.ndarray:
"""Array of 16 losses (positive = loss) relative to the value today, revalued instantly
(t = 0), notes (9A.17):
* scenarios 0-6: forward F (1 + m scan) for m in (0, +1/3, -1/3, +2/3, -2/3, +1, -1)
at volatility sigma + vol_shift;
* scenarios 7-13: the same seven moves at max(sigma - vol_shift, 0.01);
* scenarios 14-15: F (1 + extreme_mult scan) and F (1 - extreme_mult scan) at sigma,
each loss multiplied by extreme_weight."""
raise NotImplementedError
def scenario_margin(legs, F, r, sigma, scan=0.06, vol_shift=0.04, extreme_mult=2.0, extreme_weight=0.35) -> float:
"""max(0, largest scenario loss)."""
raise NotImplementedError
def exposure_margin(legs, F, elm_rate=0.02, extra_expiry_elm=0.0) -> float:
"""(elm_rate + extra_expiry_elm) * F * (sum of |qty| over 'fut' legs and SHORT option
legs). Long options attract no exposure margin in this model."""
raise NotImplementedError
def total_margin(legs, F, r, sigma, scan=0.06, vol_shift=0.04, elm_rate=0.02, extra_expiry_elm=0.0) -> float:
"""scenario_margin(...) + exposure_margin(...)."""
raise NotImplementedError
def strangle_to_condor(legs, wing: float):
"""Return a new list: legs followed by protective wings. For every SHORT option leg add a
long leg of the same kind, same T, quantity -qty, struck `wing` further out of the money
(put: K - wing; call: K + wing)."""
raise NotImplementedError
def delta_hedge(legs, F, t, r, sigma):
"""Return legs + [Leg('fut', F, 0.0, q)] with q chosen so the position delta is zero."""
raise NotImplementedError
def roll(legs, old_T: float, new_T: float):
"""Replace every option leg whose T equals old_T (to 1e-12) by the same leg with T=new_T."""
raise NotImplementedError
def adjustment_trades(before, after):
"""Trades turning `before` into `after`: aggregate each book by key
(kind, K, T) (use T = 0.0 for 'fut'), take qty_after - qty_before, drop zeros, and return
Leg(kind, K, T, dq) objects sorted by key."""
raise NotImplementedError
def adjustment_cost(before, after, half_spread=0.5, fee_per_order=20.0) -> float:
"""sum over adjustment_trades of |qty| * half_spread (points per unit) plus
fee_per_order for each trade."""
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.