← La note
Reproductibilité

Chaque chiffre de la note, et sa provenance

De quoi vérifier l'arithmétique à la main, ou reconstruire une étude équivalente à partir des mêmes sources publiques et sous licence.

Données et univers

Actions. Dix actions américaines à grande capitalisation, sélectionnées le 31 décembre 2022 par volume quotidien médian en dollars entre octobre et décembre 2022. La source est constituée de données de transactions et de cotations à la minute éligibles NBBO, excluant les lots impairs et les venues hors NBBO. Les instruments sont indexés par un identifiant de titre stable, avec des facteurs d'ajustement chaînés à travers les changements d'identifiant, afin que les splits et la réutilisation de tickers ne corrompent pas les rendements.

Crypto. Dix contrats perpétuels Binance marginés en USDT, sélectionnés de la même manière à partir des archives publiques de klines et de funding, chacun exigeant 24 mois de volume réellement échangé avant le début de l'échantillon.

Échantillon. Le test hors échantillon court du 1er janvier 2023 au 31 juillet 2026, aux fréquences de barre 15 minutes et 1 heure, pour les deux marchés.

Ensemble de features

Chaque famille de modèle voit le même ensemble de 62 features, construites uniquement à partir de l'OHLCV : rendements et momentum à plusieurs décalages, volatilité réalisée glissante, position dans la fourchette, distance au plus haut et au plus bas glissants, ratios et variations de volume, et features de forme de bougie comme la fraction du corps et la taille des mèches. Aucune colonne de coût, de spread, de frais ou de funding n'est présente dans la matrice de features, ce qui a été vérifié programmatiquement avant tout résultat hors échantillon.

# volatilité réalisée sur 6 barres et position dans la fourchette haut/bas récente
df["vol_6"] = df["ret_1"].rolling(6).std()
df["pos_in_range_6"] = (
    (df["close"] - df["low"].rolling(6).min())
    / (df["high"].rolling(6).max() - df["low"].rolling(6).min())
)

Trois familles de modèles : un modèle linéaire régularisé, un ensemble d'arbres à gradient boosting et un petit réseau de neurones feed-forward. Chaque famille reçoit exactement 30 configurations candidates, générées et hachées une seule fois, avant que la moindre fenêtre hors échantillon ne soit notée. Chaque exécution vérifie ce hash avant de toucher aux données, et une divergence interrompt l'exécution plutôt que de laisser la grille dériver silencieusement d'une session à l'autre.

import hashlib, json

configs = {"linear": linear_grid, "gbt": gbt_grid, "mlp": mlp_grid}
digest = hashlib.sha256(json.dumps(configs, sort_keys=True).encode()).hexdigest()
assert digest == "6c6d89b6cfd435ce875876820443405f5d383956a5673ea38f5d2d410b490e47"

Mécanique walk-forward

Six longueurs d'entraînement (1, 2, 3, 6, 12 et 24 mois calendaires) croisées avec trois calendriers hors échantillon (fixe un mois, fixe trois mois, et deux tiers de la longueur d'entraînement) donnent 216 cellules de résultat. Faire avancer cela sur tout l'échantillon a produit 5 868 fenêtres de modèle glissantes et 176 040 tentatives de configuration au total.

La sélection à l'intérieur de chaque fenêtre est chronologique, jamais mélangée, et la configuration gagnante est ré-entraînée sur la fenêtre in-sample complète avant même de toucher au bloc hors échantillon. Les scores de configuration, métadonnées, prédictions et le ledger de trades complet de chaque fenêtre sont persistés avant qu'un sentinel d'achèvement ne soit écrit, si bien que l'exécution est reprenable et vérifiable fenêtre par fenêtre.

for window in rolling_windows(train_months, schedule):
    fit_rows, val_rows = chronological_split(window.is_slice, frac=0.8)

    scored = [fit_and_validate(cfg, fit_rows, val_rows) for cfg in configs[model_family]]
    winner = max(scored, key=lambda s: s.validation_ic)

    final_model = fit(winner.config, window.is_slice)   # ré-entraîné sur la fenêtre IS complète
    preds = final_model.predict(window.oos_slice)       # noté exactement une fois, hors échantillon

Règle de portefeuille et modèle de coûts

La cible de prédiction est le rendement ouverture-clôture de la barre suivante. La règle de trading achète les deux meilleures prédictions cross-sectionnelles et vend les deux plus faibles, à 25 % de poids absolu chacune, entrant à l'ouverture de la barre suivante et sortant à la clôture de cette même barre.

book = pd.concat([
    ranked.head(2).assign(side=+1),
    ranked.tail(2).assign(side=-1),
]).assign(weight=lambda d: 0.25 * d["side"])

gross = book["weight"] * book["fwd_return"]
net = (
    gross
    - book["weight"].abs() * book["spread"]
    - book["weight"].abs() * fee_bps           # 5 bps taker (crypto), 0.5 bps (actions)
    - book["weight"].abs() * slippage_bps      # 2 bps (crypto seulement)
    + book["weight"] * book["funding_signed"]  # crypto seulement, sur la vraie barre de settlement
)

La crypto paie le spread mensuel mesuré, des frais taker de 5 bps et 2 bps de slippage par exécution, plus le funding signé réel à son véritable événement de settlement de huit heures : un funding positif débite les positions longues et crédite les courtes, un funding négatif inverse cela. Les actions paient le spread mesuré par barre et 0.5 bps par exécution, sans jambe de funding. Le brut, le spread, les frais, le slippage, le funding et le net sont conservés comme colonnes de ledger séparées pour chaque position, et l'agrégat se réconcilie exactement avec ces composantes. La plus grande erreur d'identité observée sur toute l'exécution était de 0.0 bps.

Inférence statistique

Les comparaisons utilisent un bootstrap groupé par semaine sur la série cumulée hors échantillon, pas une moyenne de Sharpe par fenêtre ni un rééchantillonnage ligne par ligne, parce que les rendements sont corrélés à la fois dans le temps et entre instruments. La procédure de Benjamini-Hochberg contrôle le taux de fausses découvertes sur chaque cellule testée, y compris les perdantes.

boot_diffs = week_clustered_bootstrap(shorter_length_pnl, baseline_24m_pnl, n_boot=10_000)
p_values = [two_sided_p(observed_diff, boot_diffs) for observed_diff in cell_diffs]
significant = benjamini_hochberg(p_values, alpha=0.05)

Crédibilité et vérifications de fuite

Avant qu'aucun chiffre hors échantillon ne soit inspecté : le test de troncature future a reproduit chaque feature avec une erreur maximale de 0.0 ; la cible de prédiction correspondait au rendement ouverture-clôture réalisé de la barre suivante à 0.0000018 bps près ; tous les événements de funding de l'échantillon de contrôle correspondaient à leurs horodatages bruts d'exchange ; un contrôle positif avec la cible plantée directement dans l'ensemble de features a fait passer l'IC cumulé de 0.0172 à 0.9999 ; et trois contrôles négatifs par rotation circulaire, qui détruisent tout alignement temporel véritable, ont renvoyé des IC de 0.0165, 0.0418 et −0.0461, cohérents avec du bruit.

Un garde-fou générique de lookahead a d'abord signalé la majorité des lots représentatifs, parce qu'il suppose qu'un score corrélé au rendement à l'horodatage t a été négocié pendant ce même rendement. La convention de ce moteur est différente : le rendement à t est une entrée observée à la clôture de cette barre, et la position entre à l'ouverture de la barre suivante. Une vérification de chronologie ligne par ligne, reliant décisions, entrées, sorties et cibles au panel source, sur 3 563 992 lignes de prédiction, n'a trouvé aucune erreur de timing, aucune divergence de prix d'entrée ou de sortie, et une erreur maximale de reconstruction de la cible de 0.0000008 bps — confirmant que c'était l'hypothèse du garde-fou, et non le pipeline, qui était fausse.

Environnement

Python 3.10.12
polars 1.40.1        numpy 2.2.6          scipy 1.15.3
scikit-learn 1.7.2   lightgbm 4.6.0       torch 2.11.0+cu130
matplotlib 3.10.9    pyarrow 24.0.0       markdown 3.10.2

Toutes les seeds de modèle, de bootstrap et de génération de configuration sont fixes. Une fenêtre n'est réutilisée d'une exécution précédente que lorsque son sentinel d'achèvement et son hash de configuration correspondent aux fichiers de l'exécution en cours ; sinon, elle est recalculée.

Les graphiques de la note sont dessinés à partir des tables de résultats publiées, les mêmes tables d'où viennent les chiffres du texte.

Demander le pipeline complet

Cette page suffit pour vérifier chaque chiffre de la note à la main, ou pour reconstruire une étude équivalente à partir des mêmes sources de données publiques et sous licence. Les scripts de production — collecte des données, construction du panel, moteur d'exécution de l'expérience, analyse et génération des figures — ne sont pas fournis ici. Envoyez un e-mail à daniel@daru.finance et je vous les enverrai.

← Retour à la note