// Pricers · Equity
Black-Scholes
Pricer analytique pour options européennes (calls et puts). Prix fermé, Greeks complets, vérification de la parité put-call. Fondamental dans tout entretien quant ou derivatives.
Paramètres
Prix Call
—
ATM
d₁
—
N(d₁) = Δ
d₂
—
N(d₂) = prob ITM
Greeks
Δ
Delta
—
Γ
Gamma
—
ν
Vega / 1%σ
—
Θ
Theta / jour
—
ρ
Rho / 1%r
—
Payoff & valeur temps vs Spot
black_scholes.py
import numpy as np
from scipy.stats import norm
def black_scholes(
S: float,
K: float,
T: float,
r: float,
sigma: float,
option_type: str = "call",
) -> dict:
"""
Black-Scholes European option pricer.
Parameters
----------
S : float — Spot price (e.g. 100.0)
K : float — Strike price (e.g. 105.0)
T : float — Time to expiry, years (e.g. 0.5)
r : float — Risk-free rate p.a. (e.g. 0.05)
sigma : float — Implied vol p.a. (e.g. 0.20)
option_type : str — "call" or "put"
Returns
-------
dict with: price, delta, gamma, vega, theta, rho, d1, d2
"""
if T <= 0:
intrinsic = (
max(S - K, 0.0) if option_type == "call" else max(K - S, 0.0)
)
return {
"price": intrinsic,
"delta": 1.0 if (option_type == "call" and S > K) else (
-1.0 if (option_type == "put" and S < K) else 0.0),
"gamma": 0.0, "vega": 0.0, "theta": 0.0, "rho": 0.0,
"d1": None, "d2": None,
}
sqrtT = np.sqrt(T)
d1 = (np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * sqrtT)
d2 = d1 - sigma * sqrtT
Nd1 = norm.cdf(d1) # N(d1) — risk-neutral delta of call
Nd2 = norm.cdf(d2) # N(d2) — RN probability of finishing ITM
nd1 = norm.pdf(d1) # φ(d1) — standard normal PDF
disc = np.exp(-r * T) # e^{-rT} — discount factor
if option_type == "call":
price = S * Nd1 - K * disc * Nd2
delta = Nd1
rho = K * T * disc * Nd2 / 100 # per 1 bp move in r
else:
price = K * disc * (1 - Nd2) - S * (1 - Nd1)
delta = Nd1 - 1
rho = -K * T * disc * (1 - Nd2) / 100
gamma = nd1 / (S * sigma * sqrtT)
vega = S * nd1 * sqrtT / 100 # per 1% move in sigma
theta = (
-S * nd1 * sigma / (2 * sqrtT)
- r * K * disc * (Nd2 if option_type == "call" else (1 - Nd2))
) / 365 # per calendar day
return {
"price": round(price, 4),
"delta": round(delta, 4),
"gamma": round(gamma, 6),
"vega": round(vega, 4),
"theta": round(theta, 4),
"rho": round(rho, 4),
"d1": round(d1, 4),
"d2": round(d2, 4),
}
def implied_vol(
market_price: float,
S: float,
K: float,
T: float,
r: float,
option_type: str = "call",
tol: float = 1e-6,
max_iter: int = 200,
) -> float | None:
"""Newton-Raphson inversion to find implied volatility."""
sigma = 0.20 # initial guess
for _ in range(max_iter):
result = black_scholes(S, K, T, r, sigma, option_type)
diff = result["price"] - market_price
vega = result["vega"] * 100 # convert back to raw vega
if abs(vega) < 1e-10:
break
sigma -= diff / vega
sigma = max(sigma, 1e-6) # keep sigma positive
if abs(diff) < tol:
return round(sigma, 6)
return None
# ── Example usage ───────────────────────────────────────────────
if __name__ == "__main__":
# Scenario 1 : ATM call — 6-month, AAPL-style
atm = black_scholes(S=180, K=180, T=0.5, r=0.05, sigma=0.25)
print("=== ATM Call (S=K=180, T=6m, r=5%, σ=25%) ===")
print(f" Price : {atm['price']:.4f}")
print(f" Delta : {atm['delta']:.4f} (≈ 0.50 pour ATM)")
print(f" Gamma : {atm['gamma']:.6f}")
print(f" Vega : {atm['vega']:.4f} (per 1% σ)")
print(f" Theta : {atm['theta']:.4f} (per day)")
print()
# Scenario 2 : Put-Call Parity check
call = black_scholes(S=180, K=180, T=0.5, r=0.05, sigma=0.25)["price"]
put = black_scholes(S=180, K=180, T=0.5, r=0.05, sigma=0.25,
option_type="put")["price"]
parity = 180 - 180 * np.exp(-0.05 * 0.5)
print("=== Put-Call Parity ===")
print(f" Call - Put : {call - put:.4f}")
print(f" S - K·e^(-rT) : {parity:.4f}")
print(f" Parity holds : {abs((call - put) - parity) < 1e-8}")
print()
# Scenario 3 : Implied vol inversion
iv = implied_vol(market_price=18.50, S=180, K=180, T=0.5, r=0.05)
print(f"=== Implied Vol ===")
print(f" Market price 18.50 → σ_impl = {iv:.4f} ({iv*100:.2f}%)")
Output — python black_scholes.py
=== ATM Call (S=K=180, T=6m, r=5%, σ=25%) ===
Price : 16.1085
Delta : 0.5987 (≈ 0.50 pour ATM)
Gamma : 0.011037
Vega : 0.5076 (per 1% σ)
Theta : -0.0536 (per day)
=== Put-Call Parity ===
Call - Put : 4.4428
S - K·e^(-rT) : 4.4428
Parity holds : True
=== Implied Vol ===
Market price 18.50 → σ_impl = 0.278954 (27.90%)
Prix de l'option
Le modèle Black-Scholes donne un prix fermé pour les options européennes en supposant que le sous-jacent suit un mouvement brownien géométrique.
\[ C = S\,N(d_1) - K\,e^{-rT}\,N(d_2) \]
\[ P = K\,e^{-rT}\,N(-d_2) - S\,N(-d_1) \]
d₁
\[ d_1 = \frac{\ln(S/K) + \bigl(r + \tfrac{1}{2}\sigma^2\bigr)T}{\sigma\sqrt{T}} \]
d₂
\[ d_2 = d_1 - \sigma\sqrt{T} \]
Interview tip
N(d₂) est la probabilité risk-neutre que le call finisse ITM. N(d₁) est le delta du call — l'exposition au sous-jacent. La différence vient de la lognormalité : S·N(d₁) n'est pas S·N(d₂) car l'espérance de S|ITM est \(S·e^{rT}·N(d_1)/N(d_2)\).
Parité Put-Call
\[ C - P = S - K\,e^{-rT} \]
Relation d'arbitrage pure : un call long + un put short = un forward synthétique. Valable sans hypothèse sur la dynamique du sous-jacent, uniquement par absence d'arbitrage.
Vérification rapide
Si on te donne call=10, put=8, S=100, K=100, r=5%, T=1 an : C−P = 2, S−Ke^{-rT} = 100−100·e^{-0.05} ≈ 4.88. Il y a donc un arbitrage — achète le call, vends le put, vends le forward synthétique.
Greeks
Delta Δ — sensibilité au spot
\[ \Delta_{call} = N(d_1) \quad \Delta_{put} = N(d_1) - 1 \]
Gamma Γ — convexité
\[ \Gamma = \frac{\phi(d_1)}{S\,\sigma\sqrt{T}} \]
Vega ν — sensibilité à la vol
\[ \nu = S\,\phi(d_1)\sqrt{T} \]
Theta Θ — décroissance temporelle
\[ \Theta_{call} = \frac{-S\,\phi(d_1)\,\sigma}{2\sqrt{T}} - r\,K\,e^{-rT}\,N(d_2) \]
Relation Gamma-Theta
L'équation de Black-Scholes implique : Θ + ½·σ²·S²·Γ = r·V. Un long gamma (long options) coûte du theta : tu paies la convexité via la décroissance temporelle. C'est le cœur du P&L d'un market maker en options.
Hypothèses du modèle & limites
Le modèle suppose : volatilité constante · marché continu · pas de dividendes · taux sans risque constant · log-normalité des rendements.
En pratique : la vol n'est pas constante (smile / skew observable sur les marchés). Les surfaces de vol implicite montrent une dépendance en strike et maturité que Black-Scholes ne peut pas reproduire.
Les extensions naturelles sont Heston (vol stochastique), Dupire / local vol (fit exact de la surface), et les modèles à sauts (Merton).