Greeks: Delta, Gamma, Theta, Vega, Rho
The Greeks are the partial derivatives of the Black-Scholes price (chapter 118) with respect to each input — each answers "how much does the option's price change if this one thing changes, holding everything else constant?"
import math
from scipy.stats import norm
def compute_greeks(S: float, K: float, T: float, r: float, sigma: float, option_type: str = "CE") -> dict:
if T <= 0:
return {"delta": 0, "gamma": 0, "theta": 0, "vega": 0, "rho": 0}
d1 = (math.log(S / K) + (r + sigma**2 / 2) * T) / (sigma * math.sqrt(T))
d2 = d1 - sigma * math.sqrt(T)
pdf_d1 = norm.pdf(d1)
if option_type == "CE":
delta = norm.cdf(d1)
theta = (-S * pdf_d1 * sigma / (2 * math.sqrt(T)) - r * K * math.exp(-r * T) * norm.cdf(d2)) / 365
rho = K * T * math.exp(-r * T) * norm.cdf(d2) / 100
else:
delta = norm.cdf(d1) - 1
theta = (-S * pdf_d1 * sigma / (2 * math.sqrt(T)) + r * K * math.exp(-r * T) * norm.cdf(-d2)) / 365
rho = -K * T * math.exp(-r * T) * norm.cdf(-d2) / 100
gamma = pdf_d1 / (S * sigma * math.sqrt(T))
vega = S * pdf_d1 * math.sqrt(T) / 100
return {"delta": delta, "gamma": gamma, "theta": theta, "vega": vega, "rho": rho}
Reading each Greek in plain terms
| Greek | Measures | Typical range (near ATM) | Practical meaning |
|---|---|---|---|
| Delta | Price change per ₹1 move in spot | 0 to 1 (calls), -1 to 0 (puts) | ~0.5 at ATM; also roughly approximates probability of expiring ITM |
| Gamma | Rate of change of Delta itself | Highest ATM, near expiry | High gamma = Delta changes fast = position risk changes fast |
| Theta | Price decay per day (time passing) | Negative for long options | The cost of holding a long option position — accelerates near expiry |
| Vega | Price change per 1% change in IV | Highest ATM, far from expiry | Long options gain from rising IV, lose from falling IV, independent of direction |
| Rho | Price change per 1% change in rates | Small for short-dated options | Usually the least relevant Greek for short-term Indian index options |
Why these matter for practical position management, not just theory
def portfolio_delta_exposure(positions: list[dict]) -> float:
"""positions: [{quantity, delta, lot_size}, ...]"""
return sum(p["quantity"] * p["delta"] * p["lot_size"] for p in positions)
A trader holding "5 lots of NIFTY calls" doesn't actually know their real directional exposure without converting through Delta — 5 lots of a deep-OTM call (delta ~0.1) is a very different directional bet than 5 lots of an ATM call (delta ~0.5), despite identical lot count. This is the foundation for chapter 122's portfolio-level Greeks aggregation.
Theta decay is not linear — it accelerates
def theta_over_time(S, K, r, sigma, option_type, days_range: list[int]) -> pd.DataFrame:
results = []
for days in days_range:
greeks = compute_greeks(S, K, days_to_years(days), r, sigma, option_type)
results.append({"days_to_expiry": days, "theta": greeks["theta"]})
return pd.DataFrame(results)
Running this shows theta decay is roughly flat far from expiry and sharply accelerates in the final week — directly relevant to why option sellers often prefer holding through the final days before expiry (faster theta capture) while option buyers face the opposite headwind.
Sanity-checking your Greeks against your broker's own display
def sanity_check_greeks(computed: dict, broker_displayed: dict, tolerance: float = 0.05):
for greek, value in computed.items():
if greek in broker_displayed:
assert abs(value - broker_displayed[greek]) < tolerance, f"{greek} mismatch"
Small discrepancies against your broker's displayed Greeks are normal (different IV inputs, slightly different rate assumptions) — large discrepancies mean a bug in your formula or a wrong input (commonly: T computed in days instead of years, or sigma as a percentage like 20 instead of a decimal like 0.20).