def td(dummy=None):
    """
    ================================================================
    RÉSUMÉ COMPLET TD - VAR & BACKTESTING
    ================================================================

    --- IMPORTS NÉCESSAIRES ---
    import numpy as np
    import pandas as pd
    from scipy.stats import chi2, norm
    from scipy.optimize import minimize
    from statsmodels.stats.stattools import jarque_bera
    import scipy.stats as stats
    import seaborn as sns
    import matplotlib.pyplot as plt

    ================================================================
    1. RENDEMENTS & DONNÉES DE BASE
    ================================================================
    historical_returns = log_returns @ weights
    hist_ret = hist_ret.rename("Rend journalier").to_frame()
    hist_x_day_ret = hist_x_day_ret.squeeze()  # DataFrame → Series

    def calc_expected_returns(df, weights):
        log_returns = np.log(df/df.shift(1))
        return np.sum(log_returns.mean() * weights)

    def calc_expected_vol(df, weights):
        log_returns = np.log(df/df.shift(1))
        cov_matrix = log_returns.cov()
        return np.sqrt(weights.T @ cov_matrix @ weights)

    ================================================================
    2. VAR HISTORIQUE
    ================================================================
    days = 50
    range_returns = historical_returns.rolling(window=days).sum().dropna()
    confidence_interval = 0.99
    alpha = 1 - confidence_interval
    Pf_value_0 = 1000000

    VaR = Pf_value_0 * np.quantile(range_returns, 0.01)
    # → VaR 50 jours car range_returns est à 50

    # Visualisation
    plt.figure(figsize=(12,6))
    plt.hist(Pf_value_0*range_returns, bins=50, density=True)
    # density=True important car c'est la vraie distribution
    plt.axvline(VaR, linestyle="dashed", color="red",
                label="VaR 50j, seuil 99%")

    ================================================================
    3. VAR PARAMÉTRIQUE
    ================================================================
    cov_matrix = log_returns.cov() * 252
    # *252 car c'est une variance (pas std), déjà au carré
    portfolio_std_dev = np.sqrt(weights.T @ cov_matrix @ weights)
    # l'annualisation est déjà prise en compte par cov_matrix

    confidence_levels = [0.90, 0.95, 0.99]
    VaRs = []
    for cl in confidence_levels:
        VaR = Pf_value_0 * (norm.ppf(1-cl) * portfolio_std_dev
              * np.sqrt(days/252) - historical_x_day_returns.mean() * days)
        VaRs.append(VaR)

    for cl, VaR in zip(confidence_levels, VaRs):
        plt.axvline(VaR, linestyle="dashed", color="red",
                    label=f"VaR at {cl} confiance sous {days} jours")

    ================================================================
    4. MONTE CARLO
    ================================================================
    Prices_CTL = np.zeros([horizon, simulations])
    Prices_CTL[0, :] = df_price[Tick].iloc[-1]

    for sim in range(0, simulations):
        for jour in range(1, horizon):
            p = np.random.uniform(0, 1)
            while p == 0 or p == 1:
                p = np.random.uniform(0, 1)
            r = norm.ppf(p, loc=Er_spy_journalier, scale=vol_spy_journalier)
            Prices_CTL[jour, sim] = Prices_CTL[jour-1, sim] * (1 + r)

    Prices[-1, :].mean()  # prix moyen fin des 100j avec 1000 simulations

    # Avec z-score direct :
    def random_z_score():
        return np.random.normal(0, 1)

    def scenario_gain_loss(portfolio_value, std_deviation, z_score, days):
        return (portfolio_value * expected_pf_return * days
                + portfolio_value * std_deviation * z_score * np.sqrt(days))

    VaR = np.percentile(scenarioReturn, alpha*100)

    COMMENTAIRE MONTE CARLO :
    Rolling Window → capture clusters de volatilité sans GARCH
    Fenêtre glissante → ajuste sigma dynamiquement

    ================================================================
    5. PIÈGES EXAMEN - VAR
    ================================================================
    - JB rejette normalité (p<0.05) → VaR Paramétrique FAUSSE
      car fat tails non pris en compte → sous-estime le risque
    - VaR seule ne suffit pas → toujours compléter avec ES
    - VaR_Hist > VaR_Para → risque de queue (skewness négative
      ou kurtosis élevée) que la loi normale ne capture pas
    - Solutions si non-normalité : GARCH ou Bootstrapping

    ================================================================
    6. TESTS DE NORMALITÉ
    ================================================================
    H0 : rendements normaux
    p < 0.05 → rejet H0 → fat tails → VaR Paramétrique fausse

    jb_stat, jb_p, skew, kurt = jarque_bera(returns)
    # .ravel() si returns est un DataFrame : jarque_bera(returns.ravel())
    sw_stat, sw_p = stats.shapiro(returns)
    ks_stat, ks_p = stats.kstest((returns-returns.mean())/returns.std(),
                                  'norm')

    INTERPRÉTATION :
    Fat Tails        → mouvements extrêmes plus fréquents que Gauss
    Skewness négative → krachs plus violents que les hausses
    Kurtosis élevée  → queues épaisses → sous-estimation du risque

    # Visualisation
    fig, axes = plt.subplots(1, 2, figsize=(15, 6))
    sns.histplot(returns, kde=True, stat="density",
                 ax=axes[0], color='skyblue')
    mu, std = returns.mean(), returns.std()
    x = np.linspace(mu - 4*std, mu + 4*std, 100)
    axes[0].plot(x, stats.norm.pdf(x, mu, std), color='red',
                 lw=2, label='Theoretical Normal')
    axes[0].set_title('Returns Distribution')
    stats.probplot(returns, dist="norm", plot=axes[1])
    axes[1].set_title('Q-Q Plot: Tail Analysis')
    plt.tight_layout()
    plt.show()

    ================================================================
    7. CONSTRUCTION DES VIOLATIONS (BASE DE TOUT)
    ================================================================
    VaR_95     = returns.quantile(0.05)       # VaR 95% historique simple
    VaR_series = pd.Series([VaR_95] * len(returns))
    violations = (returns < VaR_series).astype(int)
    n          = len(returns)

    TABLEAU RÉCAP BACKTESTING :
    ┌─────────────────┬──────────────────────────────────┬──────┐
    │ Kupiec (POF)    │ Bonne fréquence de violations ?  │ df=1 │
    │ TUFF            │ 1ère violation trop tôt ?        │ df=1 │
    │ Christoffersen  │ Violations indépendantes ?       │ df=1 │
    └─────────────────┴──────────────────────────────────┴──────┘
    RÈGLE : p > 0.05 → H0 non rejetée → test passé → bon modèle

    ================================================================
    8. KUPIEC (POF)
    ================================================================
    H0 : fréquence empirique = alpha théorique
    p > 0.05 → bon nombre de violations → modèle OK

    alpha = 0.05
    x     = violations.sum()      # nombre de violations
    n     = len(returns)
    p_hat = x / n                 # proportion empirique

    LR_POF = -2 * np.log(
        ((1 - alpha)**(n-x) * alpha**x) /
        ((1 - p_hat)**(n-x) * p_hat**x)
    )
    p_value_POF = 1 - chi2.cdf(LR_POF, df=1)

    RÉSULTAT TYPIQUE :
    66/1305 violations → compatible avec alpha=0.05
    p > 0.05 → on ne rejette pas H0 → fréquence correcte

    ================================================================
    9. TUFF
    ================================================================
    H0 : délai 1ère violation compatible avec alpha
    p < 0.05 → 1ère violation trop tôt → rejeté

    TUFF_index = np.argmax(violations.values == 1) + 1
    # période de la 1ère violation

    hat_alpha = 1 / TUFF_index

    L0_TUFF = (1 - alpha)     * (alpha     ** (TUFF_index - 1))
    L1_TUFF = (1 - hat_alpha) * (hat_alpha ** (TUFF_index - 1))

    LR_TUFF      = -2 * np.log(L0_TUFF / L1_TUFF)
    p_value_TUFF = 1 - chi2.cdf(LR_TUFF, df=1)

    RÉSULTAT TYPIQUE :
    TUFF_index = 3 → violation dès le 3ème jour
    LR = 6.880, p-value = 0.009 < 0.05 → trop tôt → H0 rejetée

    ================================================================
    10. CHRISTOFFERSEN (IC)
    ================================================================
    H0 : violations indépendantes (pi0 = pi1)
    p > 0.05 → indépendantes → bon modèle

    --- COMPTEURS ---
    n00 = n01 = n10 = n11 = 0
    for t in range(1, n):
        if violations[t-1]==0 and violations[t]==0: n00 += 1
        if violations[t-1]==0 and violations[t]==1: n01 += 1
        if violations[t-1]==1 and violations[t]==0: n10 += 1
        if violations[t-1]==1 and violations[t]==1: n11 += 1

    n00 = calme    → calme        n01 = calme    → violation
    n10 = violation→ calme        n11 = violation→ violation

    --- PROBABILITÉS ---
    pi0 = n01 / (n00 + n01) if (n00+n01) > 0 else 0
    pi1 = n11 / (n10 + n11) if (n10+n11) > 0 else 0
    pi  = (n01 + n11) / (n00 + n01 + n10 + n11)

    pi0 = P(violation | hier calme)
    pi1 = P(violation | hier violation)
    pi  = P(violation globale)

    --- LIKELIHOOD RATIO ---
    L0_ind = (1-pi)**(n00+n10) * pi**(n01+n11)
    L1_ind = ((1-pi0)**n00) * (pi0**n01) * ((1-pi1)**n10) * (pi1**n11)
    LR_ind      = -2 * np.log(L0_ind / L1_ind)
    p_value_ind = chi2.sf(LR_ind, df=1)

    RÉSULTAT TYPIQUE :
    p > 0.05 → on ne rejette pas l'indépendance des violations

    ================================================================
    11. OPTIMISATION DE PORTEFEUILLE
    ================================================================
    def port_var(w, cov): return w.T @ cov @ w

    cons       = ({'type': 'eq', 'fun': lambda w: np.sum(w) - 1})
    bounds     = [(0, 1) for _ in range(len(weights))]  # Long only
    init_guess = [1/len(weights)] * len(weights)

    res   = minimize(port_var, init_guess, args=(cov_matrix,),
                     method='SLSQP', bounds=bounds, constraints=cons)
    w_opt = res.x   # poids optimaux

    opt_ret = np.sum(log_returns.mean() * w_opt)
    opt_vol = np.sqrt(port_var(w_opt, cov_matrix))
    VaR_opt = Pf_value_0 * (norm.ppf(0.01) * opt_vol
              * np.sqrt(days/252) - opt_ret * days)

    ================================================================
    USAGE : from christoffersen import td  puis  td??
    ================================================================
    """
    pass
```