// 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.

Python C++ Options vanille Greeks Volatilité implicite Interactif
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).