M16 — HAR asymétrique débiaisé : le signal survit-il hors biais ?
Ce notebook réévalue le modèle HAR asymétrique fondé sur les semivariances positive (RV+) et négative (RV-) face à une baseline HAR classique débiaisée selon le même protocole train-only.
Objectif
Le résultat historique sur BTC annonçait trois horizons gagnants, mais comparait deux prévisionneurs dont les biais OOS différaient. Comme MSE = biais² + variance, une baseline biaisée peut gonfler artificiellement l’avantage apparent du modèle asymétrique.
Nous testons donc une question plus stricte : après calibration sur la seule queue d’entraînement, l’asymétrie RV-/RV+ conserve-t-elle un avantage de précision sur BTC ?
Protocole
walk-forward expanding, 5 folds ;
horizons 1, 5 et 10 jours ;
quatre seeds de contrôle (OLS déterministe : elles doivent être identiques) ;
calibration du biais sur 60 observations train-only ;
test Diebold-Mariano sur la perte MSE ;
biais signé et variance des erreurs reportés séparément.
Les seeds ne créent pas quatre expériences indépendantes : l’estimation OLS est déterministe. Elles servent ici de contrôle de reproductibilité. Une différence entre seeds signalerait une source de non-déterminisme inattendue.
stability = ( df.groupby("horizon") .agg( asym_mse_min=("asym_mse_logrv", "min"), asym_mse_max=("asym_mse_logrv", "max"), classic_mse_min=("classic_mse_logrv", "min"), classic_mse_max=("classic_mse_logrv", "max"), ))stability["asym_spread"] = stability["asym_mse_max"] - stability["asym_mse_min"]stability["classic_spread"] = stability["classic_mse_max"] - stability["classic_mse_min"]print("=== Stabilité exacte entre seeds ===")print(stability[["asym_spread", "classic_spread"]].to_string(float_format="%.3e"))assert (stability[["asym_spread", "classic_spread"]].to_numpy() ==0.0).all()print("\nContrôle réussi : les quatre seeds OLS sont bit-identiques.")
=== Stabilité exacte entre seeds ===
asym_spread classic_spread
horizon
1 0.000e+00 0.000e+00
5 0.000e+00 0.000e+00
10 0.000e+00 0.000e+00
Contrôle réussi : les quatre seeds OLS sont bit-identiques.
2. Résultats débiaisés et test Diebold-Mariano
Chaque modèle soustrait un biais estimé sur une queue de calibration appartenant exclusivement à l’entraînement. Le test DM compare ensuite les pertes quadratiques sur les mêmes dates OOS. Le verdict BEATS exige une amélioration positive et un DM significatif ; pour l’OLS déterministe, le ratio cross-seed en σ est non applicable plutôt qu’artificiellement infini.
table = aggregated[["horizon","mean_classic_mse","mean_asym_mse","mean_reduction_pct","median_dm_pvalue","seed_stable","verdict",]].copy()table.columns = ["Horizon", "MSE HAR débiaisé", "MSE asymétrique débiaisé","Réduction MSE (%)", "DM p médiane", "Seeds stables", "Verdict",]print("=== HAR asymétrique contre HAR classique, calibration symétrique ===")print(table.to_string(index=False, float_format="%.6f"))print("\nSynthèse : "f"{int((table['Verdict'] =='BEATS').sum())}/3 BEATS, "f"{int((table['Verdict'] =='INCONCLUSIVE').sum())}/3 INCONCLUSIVE.")
Le débiaisage doit ramener le biais signé près de zéro sans éditer les erreurs OOS. Cette décomposition distingue un gain de niveau moyen d’un véritable gain de dispersion des erreurs.
bias_variance = ( df.drop_duplicates(subset=["horizon"]) [["horizon", "asym_bias_oos", "classic_bias_oos","asym_error_variance", "classic_error_variance", ]] .sort_values("horizon") .copy())bias_variance["asym_bias_squared"] = bias_variance["asym_bias_oos"] **2bias_variance["classic_bias_squared"] = bias_variance["classic_bias_oos"] **2print("=== Biais signé et variance des erreurs OOS ===")print(bias_variance.to_string(index=False, float_format="%.6f"))print("\nLes biais résiduels sont petits ; l'écart restant porte surtout sur la variance.")
=== Biais signé et variance des erreurs OOS ===
horizon asym_bias_oos classic_bias_oos asym_error_variance classic_error_variance asym_bias_squared classic_bias_squared
1 -0.005109 -0.003880 0.848128 0.843759 0.000026 0.000015
5 -0.003967 -0.001552 0.403577 0.417884 0.000016 0.000002
10 -0.004194 -0.002419 0.369596 0.389452 0.000018 0.000006
Les biais résiduels sont petits ; l'écart restant porte surtout sur la variance.
4. Ce qui change par rapport au résultat historique
Le verdict historique brut était BEATS sur les trois horizons BTC. Après calibration symétrique et sans fuite, l’horizon court ne montre plus d’avantage : l’asymétrie conserve un signal uniquement aux horizons 5 et 10 jours.
Le script conserve les dates, les cibles et les deux prévisions sur un support strictement commun. Cela permet de recalculer le MSE et le biais indépendamment du résumé JSON, plutôt que de faire confiance à un verdict opaque.
=== Recalcul indépendant depuis les séries persistées ===
horizon n asym_mse_recalculé classic_mse_recalculé dates_uniques
1 1890 0.848154 0.843774 True
5 1870 0.403593 0.417886 True
10 1845 0.369614 0.389457 True
6. Lecture par horizon
La calibration change fortement l’interprétation : le modèle asymétrique est légèrement moins précis à un jour, puis meilleur à cinq et dix jours. Le résultat soutient un signal de persistance à moyen terme, pas un avantage universel de la décomposition asymétrique.
horizon_reading = table[["Horizon", "Réduction MSE (%)", "DM p médiane", "Verdict",]].copy()horizon_reading["Lecture"] = horizon_reading["Horizon"].map({1: "aucun avantage après débiaisage",5: "gain modeste et significatif",10: "gain persistant et significatif",})print("=== Lecture par horizon ===")print(horizon_reading.to_string(index=False, float_format="%.6f"))
=== Lecture par horizon ===
Horizon Réduction MSE (%) DM p médiane Verdict Lecture
1 -0.519177 0.244862 INCONCLUSIVE aucun avantage après débiaisage
5 3.420461 0.011363 BEATS gain modeste et significatif
10 5.095250 0.005123 BEATS gain persistant et significatif
Conclusion
Le re-test symétriquement débiaisé réfute le verdict brut 3/3 :
h=1 devient INCONCLUSIVE et l’avantage numérique s’inverse légèrement ;
h=5 et h=10 restent BEATS sous DM-MSE ;
les quatre seeds sont bit-identiques, comme attendu pour OLS ;
les biais OOS résiduels sont proches de zéro, donc le résultat restant porte surtout sur la variance des erreurs.
La décomposition RV-/RV+ reste utile pour les horizons moyens et longs, mais ne doit plus être présentée comme supérieure à tous les horizons. Ce notebook évalue une prévision de volatilité, pas une stratégie : coûts de transaction, Sharpe et drawdown sont donc hors de ce verdict et devront être mesurés avant tout claim de performance de trading.
final_summary = table.copy()final_summary["Biais² asym."] = bias_variance["asym_bias_squared"].to_numpy()final_summary["Biais² HAR"] = bias_variance["classic_bias_squared"].to_numpy()print("=== Synthèse finale M16 BTC débiaisé ===")print(final_summary.to_string(index=False, float_format="%.6f"))print("\nVerdict final : 2/3 BEATS, 1/3 INCONCLUSIVE, 0/3 NO BEATS.")