Quantkit : Une librairie Python pour le Pricing de Dérivés, la Modélisation de la Volatilité et l'Analyse des Taux

Documentation Outils

quantkit est une librairie Python pour les analystes quantitatifs et traders de dérivés. Elle couvre l'intégralité de la chaîne de pricing — sept modèles stochastiques calibrés par FFT sur des surfaces d'options réelles, un moteur de Greeks Black-Scholes complet, la gestion de portefeuilles multi-jambes avec dashboards interactifs, et un toolkit taux couvrant Nelson-Siegel, Hull-White et Vasicek.

Bannière Quant Kit
Antoine Perrin Profile Picture

Antoine

CEO - CodeMarketLabs

2026-03-20

quantkit : Une librairie Python pour le Pricing de Dérivés, la Modélisation de la Volatilité et l'Analyse des Taux

quantkit est une librairie Python professionnelle conçue pour les analystes quantitatifs, traders de dérivés et gérants de portefeuille qui ont besoin d'un framework rigoureux et modulaire couvrant l'intégralité de la chaîne de pricing. Elle couvre trois domaines distincts dans une base de code unifiée : la calibration de modèles stochastiques sur des surfaces d'options réelles, la gestion de stratégies multi-jambes avec un moteur de Greeks complet, et la modélisation de courbes de taux du fitting paramétrique à la simulation de taux courts. Chaque module est utilisable indépendamment — calibrer un modèle unique, calculer une surface de Greeks, ou générer un dashboard interactif complet en une ligne.

Ce que la librairie apporte

  • Sept modèles stochastiques avec pricing par FFT : Heston, Bates, Variance Gamma, Merton, Kou, NIG, CGMY — calibrés via recherche Latin Hypercube et optimisation SLSQP.
  • Moteur de Greeks Black-Scholes complet : 18 sensibilités de Delta à Ultima, calculées analytiquement sur tout portefeuille multi-jambes.
  • Classe DerivativesPortfolio : construire n'importe quelle structure multi-jambes, calculer les Greeks, simuler des chemins Monte Carlo et générer des dashboards interactifs.
  • Courbes de taux : quatre modèles paramétriques (Nelson-Siegel, NSS, Bjork-Christensen, BCA) et deux modèles stochastiques (Hull-White, Vasicek) avec visualisation Plotly.
  • Support courbe de futures : chaque maturité d'option est mappée sur le bon contrat futures sous-jacent — sans approximation par forward synthétique.
  • Pricing de dérivés exotiques par Monte Carlo : vanille, asiatique, barrière, lookback, range accrual, digital, variance swap et Phoenix autocall.

1. Modèles Stochastiques et Calibration FFT

Le moteur de calibration price les options via la Transformée de Fourier Rapide en utilisant la fonction caractéristique de chaque modèle. Cela permet d'évaluer simultanément des milliers de paires (strike, maturité), condition nécessaire pour fitter une surface de volatilité complète en temps raisonnable. La recherche du point de départ utilise un Latin Hypercube Sampler pour couvrir efficacement l'espace des paramètres avant de passer à SLSQP avec des bornes spécifiques à chaque modèle. Chaque modèle expose une méthode set_all_params, une phi (fonction caractéristique), simulate_paths et print_model — fournissant une interface cohérente quel que soit le modèle utilisé.

python
import src.quantkit.stochastics.calibration as cal
import src.quantkit.stochastics.models as mod
from src.quantkit.rates.utils.curve import Curve, FuturesCurve
from src.quantkit.rates.curve_interpolation.bjork_christensen_augmented import BjorkChristensenAugmented
import numpy as np
from datetime import datetime

# --- Rate curve: calibrate BCA on SOFR data ---
rates     = [(datetime(2026,3,19),3.67),(datetime(2026,5,19),3.68),(datetime(2026,6,22),3.64),
             (datetime(2026,7,20),3.61),(datetime(2026,10,19),3.5),(datetime(2026,11,19),3.47),
             (datetime(2026,12,21),3.45)]
today     = datetime(2026, 3, 20)
rates_mat = np.array([(d - today).days / 365 for d, _ in rates])
rates_val = np.array([r / 100 for _, r in rates])
bca       = BjorkChristensenAugmented(beta0=4.0, beta1=-0.5, beta2=-1.0, beta3=0.5, beta4=0.3, tau=3.0)
bca.calibrate(Curve(rates_mat, rates_val))

# --- Futures curve: maps each option maturity to the correct futures contract ---
futures       = [(datetime(2026,6,19),6670.25),(datetime(2026,9,18),6711),(datetime(2026,12,18),6835)]
futures_exp   = np.array([(d - today).days for d, _ in futures])
futures_curve = FuturesCurve(futures_exp, np.array([p for _, p in futures]))
spot          = futures_curve.yield_t(0)   # first futures price as spot

# --- Heston model: calibrate on the call surface ---
model = mod.HestonModel(
    spot_zero=spot, rfr=0.035, div=0.01, tt_maturity=1,
    kappa=1.5, theta=0.04, vol_vol=0.3, rho=-0.7, vol_zero=0.04
)
alpha, eta, n = -2, 0.1, 12

rmse_array, best_start = cal.find_starting_point(
    model, call_surface, bca, bca,
    alpha, eta, n, "call", n_points=100, futures_curve=futures_curve
)
args   = (model, call_surface, bca, bca, alpha, eta, n, "call", 1, futures_curve)
result = cal.calibration(best_start.tolist(), args, method="SLSQP")
print(f"RMSE: {result.fun:.4f}  |  Converged: {result.success}")

# --- Simulate paths from the calibrated model ---
sim = model.simulate_paths(n_paths=5000, divider=365)
sim.plot(max_paths=450)
Simulation Monte Carlo — 5 000 chemins sur un an sous le modèle de Bates calibré, avec le chemin moyen en superposition.
Simulation Monte Carlo — 5 000 chemins sur un an sous le modèle de Bates calibré, avec le chemin moyen en superposition.

2. Les Sept Modèles

Chaque modèle adresse un aspect différent du smile de volatilité observé. Heston introduit une variance stochastique à retour à la moyenne, produisant un smile lisse qui s'atténue progressivement sur les maturités. Bates étend Heston avec des sauts de Poisson composé, permettant de fitter simultanément la surface de volatilité et la queue gauche — le modèle standard le plus flexible pour les indices actions. Le Variance Gamma est un processus de saut pur piloté par un Brownien subordonné par un processus Gamma, capturant l'asymétrie et l'excès de kurtosis avec seulement trois paramètres. Merton est le modèle de diffusion-saut de référence : simple, interprétable et rapide. Kou remplace les sauts gaussiens par une distribution double exponentielle, donnant des queues plus épaisses et un pricing analytique des options à barrière. NIG et CGMY sont des processus de Lévy à activité infinie — NIG est analytiquement tractable et largement utilisé dans les marchés de l'énergie, tandis que le paramètre Y de CGMY contrôle la structure fine des petits sauts d'une activité finie à infinie.

python
# All seven models share the same calibration interface.
# Swap the model object — nothing else changes.

models = {
    "Heston": mod.HestonModel(
        spot_zero=spot, rfr=0.035, div=0.01, tt_maturity=1,
        kappa=1.5, theta=0.04, vol_vol=0.3, rho=-0.7, vol_zero=0.04
    ),
    "Bates": mod.BatesModel(
        spot_zero=spot, rfr=0.035, div=0.01, tt_maturity=1,
        kappa=2.0, theta=0.04, vol_vol=0.3, rho=-0.7,
        vol_zero=0.04, sig_j=0.1, lamb=0.5, mu_j=-0.05
    ),
    "VG": mod.VgModel(
        spot_zero=spot, rfr=0.035, div=0.01, tt_maturity=1,
        theta=-0.1, nu=0.2, sigma=0.15
    ),
    "Merton": mod.Merton(
        spot_zero=spot, rfr=0.035, div=0.01, tt_maturity=1,
        sigma=0.2, lamb=0.5, mu_j=-0.05, sig_j=0.1
    ),
    "Kou": mod.KouModel(
        spot_zero=spot, rfr=0.035, div=0.01, tt_maturity=1,
        sigma=0.2, lamb=0.5, rho=0.6, eta_1=10.0, eta_2=5.0
    ),
    "NIG": mod.NigModel(
        spot_zero=spot, rfr=0.035, div=0.01, tt_maturity=1,
        delta=0.5, alpha=2.0, beta=-0.5
    ),
    "CGMY": mod.CGMY(
        spot_zero=spot, rfr=0.035, div=0.01, tt_maturity=1,
        c=0.5, g=5.0, m=5.0, y=0.5
    ),
}

for name, model in models.items():
    _, best = cal.find_starting_point(
        model, call_surface, bca, bca,
        alpha=-2, eta=0.1, n=12, opt_type="call",
        n_points=100, futures_curve=futures_curve
    )
    args   = (model, call_surface, bca, bca, -2, 0.1, 12, "call", 1, futures_curve)
    result = cal.calibration(best.tolist(), args, method="SLSQP")
    print(f"{name:8s}  RMSE: {result.fun:.4f}  Converged: {result.success}")
    cal.plot_errors(call_surface, model, -2, 0.1, 12, 'call', futures_curve=futures_curve)
Calibration du modèle de Bates sur la surface d'options SPX — prix marché (×) vs prix modèle (○) sur sept maturités de 7 à 70 jours.
Calibration du modèle de Bates sur la surface d'options SPX — prix marché (×) vs prix modèle (○) sur sept maturités de 7 à 70 jours.

3. Pricing Monte Carlo — Dérivés Vanille et Exotiques

Une fois un modèle calibré, simulate_paths retourne un objet Simulation contenant la matrice complète des prix. Chaque payoff exotique est alors une opération numpy vectorisée sur les prix terminaux ou path-wise. Le même bloc de simulation price tous les instruments simultanément — une seule simulation, autant de produits que souhaité. Le framework gère naturellement les payoffs path-dépendants : le monitoring des barrières est un simple appel any() sur l'axe temporel, la moyenne asiatique est mean() sur l'axe du temps, et la boucle Phoenix autocall itère sur les dates d'observation trimestrielles en suivant quels chemins sont encore vivants.

python
# Calibrate Merton, simulate paths, price everything in one pass
model_bates = mod.Merton(
    spot_zero=spot, rfr=0.035, div=0.01, tt_maturity=1,
    sigma=0.2, lamb=0.75, mu_j=-0.23, sig_j=0.05
)
# ... calibrate as above ...

x        = model_bates.simulate_paths(5000, 365)
terminal = x.paths[-1, :]
paths    = x.paths
T, rfr_  = model_bates.tt_maturity, model_bates.rfr
df       = np.exp(-rfr_ * T)
S0, K    = model_bates.spot_zero, 7000

# Vanilla
payoff_call = np.maximum(terminal - K, 0)
payoff_put  = np.maximum(K - terminal, 0)
print(f"Call K={K}          : {df * np.mean(payoff_call):.2f}")
print(f"Put  K={K}          : {df * np.mean(payoff_put):.2f}")
print(f"Straddle            : {df * np.mean(payoff_call + payoff_put):.2f}")

# Asian call (arithmetic average)
print(f"Asian Call          : {df * np.mean(np.maximum(paths.mean(axis=0) - K, 0)):.2f}")

# Knock-out call (barrier at 7500)
knocked_out = np.any(paths > 7500, axis=0)
print(f"KO Call B=7500      : {df * np.mean(np.where(knocked_out, 0, np.maximum(terminal - K, 0))):.2f}")

# Knock-in put (barrier at 6000)
knocked_in = np.any(paths < 6000, axis=0)
print(f"KI Put  B=6000      : {df * np.mean(np.where(knocked_in, np.maximum(K - terminal, 0), 0)):.2f}")

# Lookback call
print(f"Lookback Call       : {df * np.mean(np.maximum(terminal - paths.min(axis=0), 0)):.2f}")

# Digital call
print(f"Digital  B=8000     : {df * np.mean(np.where(terminal > 8000, 1000, 0)):.2f}")

# Range accrual — pays 1000/365 per day S stays in [6000, 7500]
in_range = (paths >= 6000) & (paths <= 7500)
print(f"Range Accrual       : {df * np.mean(in_range.sum(axis=0) * 1000 / 365):.2f}")

4. Pricing de Dérivés Exotiques

Le même bloc de simulation price tous les produits simultanément — un modèle calibré, un appel simulate_paths, autant d'instruments que souhaité. Les options digitales pricent directement la probabilité risque-neutre d'un événement. Les options à barrière (knock-out et knock-in) monitorent le chemin complet via un seul appel any() vectorisé. Les options asiatiques moyennent le chemin arithmétiquement. Les options lookback utilisent le minimum réalisé comme strike flottant. Les range accruals comptent les jours où le sous-jacent reste dans un corridor. Chaque payoff tient en quelques lignes numpy — pas d'intégration numérique, pas de solveurs EDP.

python
# All payoffs computed from the same simulation — one pass, many products
x        = model_bates.simulate_paths(5000, 365)
terminal = x.paths[-1, :]
paths    = x.paths
T, rfr_  = model_bates.tt_maturity, model_bates.rfr
df       = np.exp(-rfr_ * T)
S0, K    = model_bates.spot_zero, 7000

# Digital call — pays 1000 if S(T) > 8000, else 0
payoff_digital = np.where(terminal > 8000, 1000, 0)
price_digital  = df * np.mean(payoff_digital)
prob_above     = np.mean(terminal > 8000)
print(f"Digital Call B=8000   price: {price_digital:.2f}  |  risk-neutral prob: {prob_above*100:.1f}%")

# Knock-out call — barrier kills the option if S ever exceeds 7500
knocked_out  = np.any(paths > 7500, axis=0)
payoff_ko    = np.where(knocked_out, 0, np.maximum(terminal - K, 0))
print(f"KO Call K=7000 B=7500 : {df * np.mean(payoff_ko):.2f}")

# Knock-in put — only activates if S ever drops below 6000
knocked_in   = np.any(paths < 6000, axis=0)
payoff_ki    = np.where(knocked_in, np.maximum(K - terminal, 0), 0)
print(f"KI Put  K=7000 B=6000 : {df * np.mean(payoff_ki):.2f}")

# Asian call — strike vs arithmetic average of the path
payoff_asian = np.maximum(paths.mean(axis=0) - K, 0)
print(f"Asian Call K=7000     : {df * np.mean(payoff_asian):.2f}")

# Lookback call — floating strike = minimum realised price
payoff_lb    = np.maximum(terminal - paths.min(axis=0), 0)
print(f"Lookback Call         : {df * np.mean(payoff_lb):.2f}")

# Range accrual — pays 1000/365 per day S stays inside [6000, 7500]
in_range     = (paths >= 6000) & (paths <= 7500)
payoff_ra    = in_range.sum(axis=0) * (1000 / 365)
print(f"Range Accrual [6k,7.5k]: {df * np.mean(payoff_ra):.2f}")

# Vanilla reference
payoff_call  = np.maximum(terminal - K, 0)
payoff_put   = np.maximum(K - terminal, 0)
print(f"Vanilla Call K=7000   : {df * np.mean(payoff_call):.2f}")
print(f"Vanilla Put  K=7000   : {df * np.mean(payoff_put):.2f}")
print(f"Straddle              : {df * np.mean(payoff_call + payoff_put):.2f}")

5. DerivativesPortfolio et Moteur de Greeks

DerivativesPortfolio est la couche de gestion de portefeuille. Elle accepte des positions longues et courtes en calls, puts, spot et futures via une interface fluente propre et stocke chaque jambe en interne. Le moteur de Greeks utilise des formules analytiques fermées Black-Scholes pour 18 sensibilités — du premier ordre (Delta, Vega, Theta, Rho) au deuxième ordre (Gamma, Vanna, Vomma, Charm) jusqu'au troisième ordre (Speed, Zomma, Color, Ultima). Chaque Greek est pondéré par la quantité nette et agrégé au niveau du portefeuille. La méthode compute_greeks accepte des volatilités implicites, taux de dividende et taux sans risque par jambe, permettant de représenter directement une structure de skew réaliste.

python
from src.quantkit.strategy.DerivativesPortfolio import DerivativesPortfolio
from src.quantkit.stochastics.models import BatesModel
from src.quantkit.graphs.graphs_options import *
from datetime import datetime

spot, rfr, div = 6670.0, 0.037, 0.011
today  = datetime.now()
exp_1m = datetime(2026, 4, 19, 16)

model = BatesModel(
    spot_zero=spot, rfr=rfr, div=div, tt_maturity=1,
    kappa=1.85, theta=0.062, vol_vol=0.98, vol_zero=0.052,
    sig_j=0.031, lamb=7.4, mu_j=-0.028, rho=-0.97
)

# --- Build an Iron Condor ---
port = DerivativesPortfolio('Iron Condor', 365, model)
port.long_call(  2.10, 6900, exp_1m, 100, "call_6900", option_id=1)
port.short_call( 8.40, 6820, exp_1m, 100, "call_6820", option_id=2)
port.long_put(   2.30, 6400, exp_1m, 100, "put_6400",  option_id=3)
port.short_put( 10.20, 6480, exp_1m, 100, "put_6480",  option_id=4)

vols  = {1: 0.52, 2: 0.44, 3: 0.41, 4: 0.36}  # per-leg implied vols
rates = {1: rfr,  2: rfr,  3: rfr,  4: rfr}
divs  = {1: div,  2: div,  3: div,  4: div}

greeks = port.compute_greeks(spot, vols, divs, rates, today)
print(f"Delta           : {greeks['Delta']:.4f}")
print(f"Gamma           : {greeks['Gamma']:.6f}")
print(f"Vega            : {greeks['Vega']:.4f}")
print(f"Theta           : {greeks['Theta']:.4f}")
print(f"Vanna           : {greeks['Vanna']:.6f}")
print(f"Mark to Market  : {greeks['Mark_Market']:.2f}")
print(f"Net premium     : {port.t_zero_premium():.2f}")

# --- Scenario grid: MTM across spot and vol shift ---
scenarios = port.scenario_grid(
    min_spot=6200, max_spot=7100, spot_divider=50,
    vol_shifts=[0.00, 0.05, 0.10, -0.05, -0.10],
    vols_dict=vols, divs_dict=divs, rfrs_dict=rates
)
print(scenarios)
Payoff à l'expiration d'un Iron Condor sur ES — break-evens à 6 466 (-3,1%) et 6 834 (+2,5%), spot courant à 6 670.
Payoff à l'expiration d'un Iron Condor sur ES — break-evens à 6 466 (-3,1%) et 6 834 (+2,5%), spot courant à 6 670.
Panel Monte Carlo — chemins simulés, distribution des prix terminaux et P&L ladder pour un Iron Condor, avec une probabilité de gain de 26,5%.
Panel Monte Carlo — chemins simulés, distribution des prix terminaux et P&L ladder pour un Iron Condor, avec une probabilité de gain de 26,5%.

6. Dashboards Interactifs

Le module graphs fournit des figures Plotly autonomes pour chaque dimension analytique et une fonction dashboard qui les assemble toutes en une figure unique scrollable. La mise en page couvre : diagrammes de payoff avec annotations des break-evens, simulation de chemins Monte Carlo, distributions des prix terminaux et P&L, heatmap de sensibilité vol/spot, profils de Greeks en fonction du spot, de la vol ou du temps, surfaces 3D MTM et Delta, et tableaux de statistiques récapitulatives. Les thèmes dark et light sont supportés partout. Le dashboard se rend dans les notebooks Jupyter ou s'exporte en fichier HTML auto-contenu ou PNG.

python
from src.quantkit.graphs.dashboard_strategy import dashboard, export_dashboard_png

# --- Individual charts ---
plot_payoff(port, spot, mode='dark').show()                                       # payoff at expiry + break-evens
plot_mc_paths(port, n_paths=3000, n_steps=30, n_show=300, mode='dark').show()     # simulated spot paths
plot_mc_price_dist(port, n_paths=3000, n_steps=30, mode='dark').show()            # terminal price distribution
plot_pnl_distribution(port, n_paths=5000, time_increment=30, mode='dark').show() # terminal P&L histogram
plot_vol_spot_matrix(port, vols, divs, rates, mode='dark').show()                 # MTM heatmap: spot x vol shift
plot_greek_vs(port, "Delta", "spot", vols, divs, rates, mode='dark').show()       # Delta profile
plot_greek_vs(port, "Gamma", "spot", vols, divs, rates, mode='dark').show()       # Gamma profile
plot_greek_vs(port, "Vanna", "spot", vols, divs, rates, mode='dark').show()       # Vanna profile
plot_greek_vs(port, "Theta", "days", vols, divs, rates, mode='dark').show()       # Theta decay
plot_mtm_vs(port, "spot",   vols, divs, rates, mode='dark').show()               # MTM vs spot
plot_surface(port, "Mark_Market", "spot", "days", vols, divs, rates, mode='dark').show()  # 3D surface
plot_surface(port, "Delta",       "spot", "vol",  vols, divs, rates, mode='dark').show()  # Delta 3D
table_greeks(port, spot, vols, divs, rates, mode='dark').show()                   # Greeks table
table_mc_stats(port, n_paths=5000, mode='dark').show()                            # MC statistics table

# --- Full dashboard (all panels assembled) ---
fig = dashboard(
    port, spot, vols, divs, rates,
    comp_date=today, n_paths=5000, time_increment=365,
    title="Iron Condor — ES Jun26", mode="dark"
)
fig.show()

# --- Export to PNG ---
export_dashboard_png("/path/to/dashboard.png", port, spot, vols, divs, rates,
    comp_date=today, n_paths=5000, time_increment=365,
    title="Iron Condor — ES Jun26", mode="dark")
Dashboard complet d'un Broken Wing Butterfly sur ES Jun26 — payoff, Greeks, stress test, Monte Carlo, distribution P&L, profils de Greeks et surfaces 3D.
Dashboard complet d'un Broken Wing Butterfly sur ES Jun26 — payoff, Greeks, stress test, Monte Carlo, distribution P&L, profils de Greeks et surfaces 3D.

7. Modélisation des Courbes de Taux

Le module rates fournit un toolkit complet de structure de terme. Du côté de l'interpolation, LinearCurve et CubicCurve encapsulent les interpolateurs scipy avec une interface cohérente d_rate / df_t / forward. Du côté paramétrique, quatre modèles sont disponibles : Nelson-Siegel (3 facteurs), Nelson-Siegel-Svensson (4 facteurs avec une deuxième bosse), Bjork-Christensen (facteur alternatif pour un meilleur fit du court terme), et Bjork-Christensen Augmenté (5 facteurs incluant un terme de dérive linéaire). Les quatre partagent la même interface calibrate / d_rate / df_t / forward_rate / plot_* et sont calibrés par minimisation L-BFGS-B des erreurs quadratiques sur les taux zéro observés. Chaque modèle produit cinq graphes Plotly : calibré vs observé, décomposition des composantes, sous-graphes de décomposition des paramètres, structure par terme des facteurs d'actualisation, et courbe spot vs forward.

python
from src.quantkit.rates.utils.curve import Curve
from src.quantkit.rates.curve_interpolation.nelson_siegel import NelsonSiegel
from src.quantkit.rates.curve_interpolation.svensson_nelson_siegel import NelsonSiegelSvensson
from src.quantkit.rates.curve_interpolation.bjork_christensen import BjorkChristensen
from src.quantkit.rates.curve_interpolation.bjork_christensen_augmented import BjorkChristensenAugmented
from src.quantkit.rates.curve_interpolation.cubic import CubicCurve
from src.quantkit.rates.curve_interpolation.linear import LinearCurve
import numpy as np
from datetime import datetime

# --- SOFR curve data (1-month term, 2026-2036) ---
dates = [datetime(2026,4,1), datetime(2026,7,1), datetime(2026,10,1),
         datetime(2027,1,1), datetime(2027,7,1), datetime(2028,1,1),
         datetime(2028,7,1), datetime(2029,1,1), datetime(2029,7,1),
         datetime(2030,1,1), datetime(2030,7,1), datetime(2031,1,1),
         datetime(2031,6,1), datetime(2032,1,1), datetime(2033,1,1),
         datetime(2034,1,1), datetime(2035,1,1), datetime(2036,7,1)]
rates_pct = [3.72, 3.65, 3.55, 3.45, 3.35, 3.28, 3.25, 3.27, 3.33,
             3.42, 3.52, 3.62, 3.69, 3.78, 3.92, 4.05, 4.17, 4.30]
today  = datetime(2026, 3, 19)
t      = np.array([(d - today).days / 365 for d in dates])
curve  = Curve(t, np.array(rates_pct))

# --- Interpolation ---
linear = LinearCurve(curve)
cubic  = CubicCurve(curve)
print(f"Linear rate at 5y : {linear.d_rate(5.0):.3f}%")
print(f"Cubic  rate at 5y : {cubic.d_rate(5.0):.3f}%")

# --- Nelson-Siegel ---
ns = NelsonSiegel(beta0=4.0, beta1=-0.5, beta2=-1.0, tau=3.0)
ns.calibrate(curve)
ns.plot_calibrated(curve, mode='dark').show()
ns.plot_model(mode='dark').show()
ns.plot_forward_curve(dt=0.25, mode='dark').show()
ns.plot_discount_factors(mode='dark').show()

# --- Nelson-Siegel-Svensson ---
nss = NelsonSiegelSvensson(beta0=4.0, beta1=-0.5, beta2=-1.0, beta3=0.5, tau=2.0, tau2=8.0)
nss.calibrate(curve)

# --- Bjork-Christensen ---
bc = BjorkChristensen(beta0=4.0, beta1=-0.5, beta2=-1.0, beta3=0.5, tau=3.0)
bc.calibrate(curve)

# --- Bjork-Christensen Augmented ---
bca = BjorkChristensenAugmented(beta0=4.0, beta1=-0.5, beta2=-1.0, beta3=0.5, beta4=0.3, tau=3.0)
bca.calibrate(curve)

# --- Cross-model comparison ---
t_fine = np.linspace(t.min(), t.max(), 500)
for model, name in [(ns,'NS'),(nss,'NSS'),(bc,'BC'),(bca,'BCA')]:
    print(f"{name}: 5y rate = {float(model.d_rate(5.0)):.3f}%  |  5y DF = {model.df_t(5.0):.4f}")
Modèle Bjork-Christensen Augmenté calibré sur la structure de terme SOFR — taux zéro observés (points) vs courbe fittée (ligne) de 0 à 10 ans, capturant l'inversion en U avec un creux autour de 2,5 ans.
Modèle Bjork-Christensen Augmenté calibré sur la structure de terme SOFR — taux zéro observés (points) vs courbe fittée (ligne) de 0 à 10 ans, capturant l'inversion en U avec un creux autour de 2,5 ans.
Bjork-Christensen Augmenté — décomposition des cinq facteurs : niveau long terme (β₀), dérive linéaire (β₁), bosse (β₂), deuxième bosse (β₃), troisième bosse (β₄), et la courbe BCA complète résultante sur un horizon de 50 ans.
Bjork-Christensen Augmenté — décomposition des cinq facteurs : niveau long terme (β₀), dérive linéaire (β₁), bosse (β₂), deuxième bosse (β₃), troisième bosse (β₄), et la courbe BCA complète résultante sur un horizon de 50 ans.

8. Modèles de Taux Stochastiques — Hull-White et Vasicek

Le module de taux stochastiques fournit deux modèles de taux courts. Vasicek est le modèle affine à retour à la moyenne original avec des paramètres constants et des prix analytiques de zéro-coupon — analytiquement tractable mais structurellement limité aux structures de termes monotones. Hull-White est l'extension time-dépendante : le drift theta(t) est inféré de la courbe de taux initiale via une spline Pchip sur les log-facteurs d'actualisation, permettant au modèle de fitter exactement toute courbe initiale par construction. Hull-White est le modèle standard pour le pricing de dérivés de taux — obligations callables, swaptions bermuda et produits structurés sensibles aux taux nécessitent une simulation cohérente des chemins, que la contrainte monotone de Vasicek rend impossible sur des courbes de marché réelles.

python
from src.quantkit.rates.stochastics.hull_and_white import HullWhite
from src.quantkit.rates.stochastics.vasicek import Vasicek
import numpy as np

# --- Hull-White on the U-shaped SOFR curve ---
hw = HullWhite(a=0.1, sigma=0.01, curve=curve)  # curve = SOFR Curve object from above
hw.calibrate()   # calibrates a and sigma; theta(t) is computed analytically
hw.print_model()

hw.plot_calibrated(mode='dark').show()                                           # zero rate fit
hw.plot_term_structure(mode='dark').show()                                       # zero + forward curve
hw.plot_simulated_paths(n_paths=500, n_steps=252, T=10.0, n_show=200, mode='dark').show()

paths_hw    = hw.simulate_paths(n_paths=50000, n_steps=252, T=10.0)
terminal_hw = paths_hw[-1, :] * 100
print(f"Mean terminal rate  : {terminal_hw.mean():.3f}%")
print(f"Std terminal rate   : {terminal_hw.std():.3f}%")
print(f"P(r < 0)            : {(terminal_hw < 0).mean()*100:.2f}%")

# VaR on a 10Y futures — use the Hull-White sigma from market swaption vol
vol_atm_10y   = 0.0080   # 80 bps annualised — replace hw.sigma to make market-consistent
hw_var        = HullWhite(a=hw.a, sigma=vol_atm_10y, curve=curve)
paths_var     = hw_var.simulate_paths(n_paths=50000, n_steps=10, T=10/252)
r_terminal    = paths_var[-1, :]   # short rate at T+10 days
f0_10y        = hw._f_mkt(10.0)    # instantaneous forward at 10y
B             = (1 - np.exp(-hw.a * 10.0)) / hw.a
r10y_paths    = (f0_10y + (r_terminal - f0_10y) * B / 10.0) * 100
dv01          = 0.09 * 100000 / 100
pnl           = (hw._f_mkt(10.0)*100 - r10y_paths) * 100 * dv01
print(f"VaR 1% (10d)  : ${np.percentile(pnl, 1):,.0f}")
print(f"VaR 5% (10d)  : ${np.percentile(pnl, 5):,.0f}")

# --- Vasicek on a monotone curve (its correct domain) ---
curve_mono = Curve(t, np.array([3.5,3.6,3.65,3.8,3.9,3.95,4.0,4.05,4.1,4.15,4.25,4.4,4.5,4.6,4.75,4.85,4.9,5.0]))
vasicek    = Vasicek(a=0.3, b=0.05, sigma=0.01, r0=float(curve_mono.rt[0])/100)
vasicek.calibrate(curve_mono)
vasicek.plot_calibrated(curve_mono, mode='dark').show()
vasicek.plot_term_structure(mode='dark').show()
vasicek.plot_simulated_paths(n_paths=500, n_steps=252, T=30.0, n_show=200, mode='dark').show()

9. Pricing Black-Scholes et Volatilité Implicite

Le module options fournit le moteur analytique Black-Scholes utilisé en interne par DerivativesPortfolio. BsParams est un dataclass frozen qui valide tous les inputs à la construction — type d'option invalide, vol négative ou spot ou strike non positif déclenchent immédiatement une exception. La fonction de pricing gère calls et puts via la formulation standard d1/d2 avec dividendes continus. Les conversions put-call parity sont disponibles via call_from_put et put_from_call. La volatilité implicite est résolue par bisection — le solveur converge à 1e-15 d'erreur absolue. Les 18 Greeks sont disponibles en fonctions standalone ou via l'agrégateur option_carac qui retourne un dict complet.

python
from src.quantkit.options.black_scholes_params import BsParams
from src.quantkit.options.pricing import option_price, call_from_put, put_from_call
from src.quantkit.options.greeks import option_carac, delta, gamma, vega, theta, vanna
from src.quantkit.options.iv_solver import implied_vol

# --- Vanilla pricing ---
params = BsParams(
    opt_type='c',
    volatility=0.20,
    time_maturity=30/365,
    risk_free_rate=0.035,
    dividend=0.011,
    spot=6670.0,
    strike=6700.0
)
print(f"Call price  : {option_price(params):.4f}")
print(f"Put via PCP : {put_from_call(params, option_price(params)):.4f}")

# --- Full Greeks ---
g = option_carac(params, side=1)   # side=1 long, side=-1 short
for name, val in g.items():
    print(f"  {name:12s}: {val:.6f}")

# --- Implied volatility (bisection, 1e-15 convergence) ---
market_price = 25.0
iv = implied_vol(params, market_price)
print(f"\nImplied vol for market price {market_price}: {iv:.4f}")

# --- Per-leg greeks without building a full portfolio ---
print(f"Delta       : {delta(params, side=1):.4f}")
print(f"Gamma       : {gamma(params, side=1):.6f}")
print(f"Vega (1%)   : {vega(params, side=1):.4f}")
print(f"Theta/day   : {theta(params, side=1):.4f}")
print(f"Vanna       : {vanna(params, side=1):.6f}")

10. Intégration des Données de Marché via marketconnect-exec

quantkit s'intègre directement avec la librairie marketconnect-exec pour récupérer des données en direct depuis Interactive Brokers. Le workflow de calibration peut être entièrement automatisé : récupérer la chaîne de futures pour construire le spot et la courbe de futures, récupérer la chaîne de futures SOFR pour construire la courbe de taux, récupérer la surface d'options en direct avec filtrage bid-ask et contraintes de plage de strikes, et alimenter les trois objets directement dans le moteur de calibration. Cela ferme la boucle entre données de marché en direct et calibration de modèle sans aucune transformation manuelle des données.

python
from marketconnect_exec.brokers.ibkr.adapter import IBKRAdapter
from marketconnect_exec.static.brokers import BrokerName
from marketconnect_exec.exec.execution import Execution
from marketconnect_exec.exec.router import APIRouter
import nest_asyncio
nest_asyncio.apply()   # required in Jupyter

# --- Connect to IBKR ---
router = APIRouter()
router.register_broker(IBKRAdapter(port=7496, testnet=False))
router.connect_all_brokers()
executor = Execution(router)

# --- Futures chain → spot and FuturesCurve ---
futures_raw = executor.get_futures_chain(BrokerName.IBKR, 649180695)   # ES contract ID
futures     = [(f['expiration'], f['mark_price']) for f in futures_raw if not np.isnan(f.get('mark_price', np.nan))]

# --- SOFR futures → rate curve ---
rates_raw = executor.get_futures_chain(BrokerName.IBKR, 385575904)     # SOFR futures ID
rates     = [(f['expiration'], 100 - f['mark_price']) for f in rates_raw if not np.isnan(f.get('mark_price', np.nan))]

# --- Options surface with quality filters ---
surface = executor.get_vol_surface(
    BrokerName.IBKR, 649180678,                # SPX options ID
    strike_range=(6100, 7100),
    maximum_days=365,
    filter_bid_ask=True, filter_bid_ask_pct=0.01,
    surface_type='all', strike_increment=10
)

# --- Save and proceed with calibration ---
import pandas as pd
df_surf = pd.DataFrame(surface)
df_surf.to_csv('data_options.csv', index=False)

# The rest of the calibration pipeline is identical to the CSV path
router.disconnect_all_brokers()

Obtenir quantkit

quantkit est disponible en tant que package Python standalone. Que vous calibriez des modèles stochastiques sur des données d'options en direct, gériez un portefeuille de dérivés multi-jambes, construisiez des outils de pricing de produits structurés, ou construisiez et simuliez des courbes de taux, la librairie fournit un stack analytique prêt pour la production avec une API cohérente et bien documentée sur tous les modules.

Ce que vous obtenez

  • Code source complet : sept modèles stochastiques, moteur de calibration FFT, Greeks complets, toolkit taux et dashboards interactifs.
  • Intégration données live : connectivité directe à Interactive Brokers via marketconnect-exec pour une calibration automatisée de surface.
  • Interface cohérente : chaque modèle — actions ou taux, paramétrique ou stochastique — partage le même pattern calibrate / simulate / plot.
  • Graphes prêts pour la production : dashboards Plotly dark et light exportables en HTML et PNG.
  • Mises à jour à vie — nouveaux modèles, produits et fonctionnalités ajoutés régulièrement.
Quels modèles stochastiques sont inclus et comment choisir entre eux ?

Sept modèles sont disponibles : Heston, Bates, Variance Gamma, Merton, Kou, NIG et CGMY. Pour les indices actions avec un skew prononcé, Bates est généralement le meilleur point de départ — il combine volatilité stochastique et risque de saut. Heston seul fitte la forme du smile mais peine sur la queue gauche. Les modèles de saut pur (VG, NIG, CGMY) fittent bien les smiles de courte maturité et sont utiles quand on veut éviter l'hypothèse brownienne. Merton et Kou sont des modèles de diffusion-saut plus simples — rapides, interprétables et adaptés à l'enseignement ou quand le temps de calcul est limité. Les sept partagent la même interface de calibration, donc la comparaison est directe.

Comment fonctionne le moteur de calibration FFT ?

Les prix d'options sont calculés via la méthode Carr-Madan FFT : pour un modèle donné, la fonction caractéristique phi est évaluée sur une grille de fréquences de log-strikes, multipliée par un facteur d'amortissement, et transformée par inverse de Fourier pour produire simultanément une surface de prix sur une plage de strikes. C'est plusieurs ordres de grandeur plus rapide qu'évaluer chaque strike individuellement. La fonction objectif minimise le RMSE relatif entre les prix modèle et marché sur toutes les maturités et strikes sélectionnés. Un Latin Hypercube Sampler trouve un bon point de départ avant que SLSQP affine la solution avec des bornes de paramètres spécifiques au modèle.

Comment fonctionne la courbe de futures, et pourquoi est-elle importante pour la calibration ?

L'objet FuturesCurve mappe chaque maturité d'option sur le premier contrat futures expirant après elle — le sous-jacent livrable réel. Quand vous l'utilisez en calibration, spot_zero pour chaque maturité est fixé au prix futures observé plutôt qu'à un forward synthétique calculé comme S * exp((r-q)*T). Cela compte car le marché price les futures directement, et tout basis entre le forward synthétique et le futures coté polluerait sinon les paramètres du modèle. Avec la courbe de futures, le drift est absorbé par le prix futures lui-même, et les paramètres du modèle se concentrent uniquement sur le fitting du smile de volatilité.

Puis-je calibrer sur des options put plutôt que call ?

Oui. Le moteur de calibration accepte un paramètre opt_type fixé à 'call' ou 'put'. En pratique, calibrer séparément sur calls et puts et comparer les paramètres résultants est un bon test de cohérence. Pour les options sur indices actions liquides, les deux devraient produire des paramètres similaires car la parité put-call tient strictement. Pour les marchés avec des puts moins liquides ou un skew significatif, calibrer sur les options out-of-the-money de chaque type séparément donne une meilleure couverture du smile.

Quels Greeks le moteur calcule-t-il, et sont-ils analytiques ou numériques ?

Le moteur calcule 18 Greeks analytiquement en utilisant des formules fermées Black-Scholes : Delta, Vega, Rho, Theta, Epsilon, Omega (premier ordre), Gamma, Vanna, Vomma, Charm, Veta, Vera (deuxième ordre), et Speed, Zomma, Color, Ultima (troisième ordre). Tous sont analytiques — aucune approximation par différences finies. Chaque fonction de Greek accepte un objet BsParams et un paramètre side (1 pour long, -1 pour short). L'agrégateur option_carac retourne les 18 dans un seul dict, utilisé par DerivativesPortfolio pour agréger entre les jambes.

Quelle est la différence entre Hull-White et Vasicek, et quand utiliser chacun ?

Vasicek a une moyenne long terme b constante et ne peut produire que des structures de terme monotones — les taux convergent régulièrement vers b depuis n'importe quel niveau initial. Il a des prix analytiques de ZCB et est analytiquement tractable, mais il ne peut pas fitter une courbe de taux en U ou en bosse. Hull-White étend Vasicek avec un drift time-dépendant theta(t) inféré analytiquement de la courbe de taux initiale, lui permettant de fitter exactement toute structure de terme observée par construction. Utilisez Vasicek pour l'enseignement ou l'exploration théorique. Utilisez Hull-White dès que vous avez besoin de simuler des chemins de taux cohérents avec la courbe observée aujourd'hui — obligations callables, swaptions bermuda, produits structurés sensibles aux taux ou scénarios VaR.

Comment pricer des dérivés exotiques comme des options digitales ou à barrière ?

Calibrez le modèle de votre choix sur la surface d'options, appelez simulate_paths pour obtenir la matrice complète des prix, puis implémentez le payoff comme des opérations numpy vectorisées. L'objet Simulation contient un tableau paths de forme (n_steps+1, n_paths). Une option digitale est np.where(terminal > B, N, 0). Une barrière knock-out est np.any(paths > B, axis=0) suivi d'un masquage. Une option asiatique est paths.mean(axis=0). Chaque produit est indépendant — tous sont évalués en une seule passe sur la même simulation.

quantkit supporte-t-il les options sur futures, ou uniquement les options sur spot ?

Les options sur futures sont entièrement supportées via la classe FuturesCurve. Lors de la calibration d'options sur futures, passez un FuturesCurve au moteur de calibration. Chaque maturité d'option est mappée sur le contrat futures le plus proche expirant après elle — une option expirant dans 30 jours mappe sur le futures front-month, pas sur un spot synthétique. Le moteur FFT fixe spot_zero au prix futures concerné et neutralise le drift en fixant div égal à rfr, ce qui équivaut à pricer sous la mesure forward.

Puis-je utiliser les modèles de courbe de taux comme courbes d'actualisation dans la calibration des options ?

Oui, et c'est l'approche recommandée. Le moteur de calibration accepte tout objet avec une méthode d_rate — LinearCurve, CubicCurve, NelsonSiegel, NelsonSiegelSvensson, BjorkChristensen ou BjorkChristensenAugmented. Calibrez d'abord le modèle de taux sur les taux de marché observés, puis passez-le comme market_rates et market_div au moteur de calibration. Combiné avec une FuturesCurve, les facteurs d'actualisation et de drift sont tous deux pleinement market-consistent : les taux viennent de la courbe calibrée, et le prix futures gère la relation spot-forward.

Quel format de données le moteur de calibration attend-il pour la surface d'options ?

Le moteur attend un objet CleanSurface, qui encapsule un DataFrame avec trois colonnes : times (jours entiers jusqu'à l'expiration), strikes (float) et prices (prix mid float). Le workflow standard est : charger les prix bid et ask, calculer le mid, calculer le temps à l'expiration en jours, filtrer les contrats avec volatilité implicite ou bid/ask manquants, puis passer le DataFrame filtré à CleanSurface. Le notebook de calibration inclut le pipeline complet de nettoyage des données pour les données IBKR en direct comme pour les fichiers CSV.