Introduction aux surfaces de volatilité crypto

La construction d'une surface de volatilité pour les options sur cryptocurrency représente l'un des défis les plus stimulants en finance quantitative. Contrairement aux actifs traditionnels, les crypto-actifs présentent une volatilité implicite instable, des skews asymétriques prononcés et des surfaces qui évoluent de manière erratique au gré des événements macro-économiques et des cycles de marché. Dans ce tutoriel pratique, nous allons explorer comment collecter des données historiques de qualité via l'API Tardis, puis comment construire une surface de volatilité tridimensionnelle en utilisant les techniques d'interpolation Scipy. Nous intégrerons également l'intelligence artificielle de HolySheep pour automatiser l'analyse et la détection des anomalies de pricing.

Architecture de la solution

Notre pipeline se compose de trois étapes principales. Premièrement, la collecte des données OHLCV et des Greek letters via l'API Tardis. Deuxièmement, le nettoyage et la transformation des données pour correspondre au format requis par notre modèle. Troisièmement, l'interpolation par splines cubiques et l'extrapolation pour les strikes manquants. L'architecture repose sur Python 3.11+, pandas pour la manipulation des données, scipy pour l'interpolation numérique et l'API HolySheep pour l'analyse contextuelle. Le taux de change avantageux de HolySheep (1¥ = 1$ avec WeChat et Alipay) permet de réduire considérablement les coûts d'inférence IA lors des appels récurrents à notre modèle d'analyse.

Configuration de l'environnement

pip install tardis-python pandas numpy scipy matplotlib
pip install requests pandas-datareader
export HOLYSHEEP_API_KEY="your-key-here"
export TARDIS_API_KEY="your-tardis-key-here"

Récupération des données depuis l'API Tardis

L'API Tardis propose des données historiques de qualité institutionnelle pour plus de 30 exchanges crypto. Pour les options BTC et ETH, la couverture est particulièrement exhaustive avec des données degree-level pour les Greeks (delta, gamma, vega, theta).
import requests
import pandas as pd
from datetime import datetime, timedelta

Configuration de l'API Tardis

TARDIS_BASE_URL = "https://api.tardis.dev/v1" def fetch_option_chain(symbol, expiry_date, exchange="deribit"): """Récupère la chaîne d'options pour un sous-jacent et une échéance donnée""" url = f"{TARDIS_BASE_URL}/options/chain" params = { "exchange": exchange, "symbol": symbol, "expiry_date": expiry_date.strftime("%Y-%m-%d"), "include_greeks": True, "include_ohlcv": True } headers = { "Authorization": f"Bearer {TARDIS_API_KEY}", "Content-Type": "application/json" } response = requests.get(url, params=params, headers=headers, timeout=30) response.raise_for_status() data = response.json() return pd.DataFrame(data['options']) def fetch_historical_volatility(symbol, days=90): """Calcule la volatilité historique sur une période glissante""" end_date = datetime.now() start_date = end_date - timedelta(days=days) url = f"{TARDIS_BASE_URL}/historical/{symbol}/ohlcv" params = { "exchange": "binance", "timeframe": "1d", "from": start_date.isoformat(), "to": end_date.isoformat() } response = requests.get(url, params=params, timeout=30) response.raise_for_status() ohlcv = pd.DataFrame(response.json()) ohlcv['returns'] = ohlcv['close'].pct_change() historical_vol = ohlcv['returns'].std() * np.sqrt(365) return historical_vol, ohlcv

Exemple d'utilisation

btc_options = fetch_option_chain("BTC", datetime(2026, 3, 28)) print(f"Options BTC récupérées : {len(btc_options)} contracts") print(btc_options[['strike', 'iv_bid', 'iv_ask', 'delta', 'gamma']].head())

Construction de la surface de volatilité avec Scipy

Une fois les données nettoyées, nous utilisons scipy.interpolate pour construire une surface lisse. La méthode RBF (Radial Basis Function) offre une flexibilité supérieure pour les données crypto présentant des irrégularités structurelles.
import numpy as np
from scipy.interpolate import RBFInterpolator, griddata, CloughTocher2DInterpolator
from scipy.spatial import ConvexHull
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

def build_volatility_surface(options_df, spot_price):
    """
    Construit une surface de volatilité implicite
    
    Paramètres:
        options_df: DataFrame avec colonnes ['strike', 'time_to_expiry', 'iv_bid', 'iv_ask']
        spot_price: Prix spot actuel du sous-jacent
    
    Retourne:
        Interpolateur RBF prêt à l'emploi
    """
    
    # Calcul du moneyness (log-moneyness)
    options_df['moneyness'] = np.log(options_df['strike'] / spot_price)
    options_df['mid_iv'] = (options_df['iv_bid'] + options_df['iv_ask']) / 2
    
    # Suppression des outliers (IV hors de [5%, 500%])
    clean_df = options_df[
        (options_df['mid_iv'] > 0.05) & 
        (options_df['mid_iv'] < 5.0) &
        (options_df['time_to_expiry'] > 0) &
        (options_df['time_to_expiry'] < 2)
    ].copy()
    
    # Points d'observation
    X = clean_df[['moneyness', 'time_to_expiry']].values
    y = clean_df['mid_iv'].values
    
    # Normalisation des features
    X_mean = X.mean(axis=0)
    X_std = X.std(axis=0)
    X_norm = (X - X_mean) / X_std
    
    # Construction de l'interpolateur RBF avec noyau gaussien
    rbf = RBFInterpolator(X_norm, y, kernel='gaussian', smoothing=0.01)
    
    return rbf, X_mean, X_std

def evaluate_surface(rbf, X_mean, X_std, spot_price, 
                     strikes_range, tenors_range):
    """Évalue la surface sur une grille régulière"""
    
    moneyness_grid = np.log(strikes_range / spot_price)
    time_grid = tenors_range
    
    # Création du maillage
    M, T = np.meshgrid(moneyness_grid, time_grid)
    points = np.column_stack([M.ravel(), T.ravel()])
    
    # Normalisation et interpolation
    points_norm = (points - X_mean) / X_std
    iv_surface = rbf(points_norm).reshape(M.shape)
    
    return M, T, iv_surface

def plot_vol_surface(M, T, iv_surface, spot_price):
    """Visualisation 3D de la surface de volatilité"""
    
    fig = plt.figure(figsize=(14, 8))
    ax = fig.add_subplot(111, projection='3d')
    
    strikes = spot_price * np.exp(M)
    
    surf = ax.plot_surface(strikes, T * 365, iv_surface * 100,
                          cmap='viridis', edgecolor='none', alpha=0.9)
    
    ax.set_xlabel('Strike Price