Chaque chiffre de la note, et sa provenance
De quoi vérifier l'arithmétique à la main, ou reconstruire une étude équivalente à partir des données publiques de Binance.
Plateforme, univers et dates
La plateforme est celle des futures perpétuels Binance marginés en USDT et la date de sélection de l'univers est le 31 décembre 2022. Les contrats éligibles devaient être cotés et marginés en USDT, porter au moins 24 mois d'historique de volume réellement échangé à cette date, se négocier entre octobre et décembre 2022 et ne pas dupliquer l'exposition d'un autre contrat. Les contrats éligibles ont ensuite été classés par notionnel quotidien médian échangé entre octobre et décembre 2022, en n'utilisant que l'information disponible à la date de sélection, et les six premiers ont été gelés : BTCUSDT, ETHUSDT, DOGEUSDT, XRPUSDT, BNBUSDT et SOLUSDT.
L'existence d'un fichier n'est pas une preuve de négociation. L'éligibilité et la fin d'historique utilisent toutes deux un volume échangé positif, et le remplissage de l'exchange après le dernier vrai trade d'un contrat est écarté.
La période hors échantillon primaire court du 1er janvier 2023 au 30 juin 2026 : quatorze fenêtres complètes et non chevauchantes de trois mois. Juillet 2026 est exclu, trimestre incomplet.
Données et calendrier
Les six contrats utilisent des barres horaires UTC depuis leur lancement sur les futures Binance jusqu'au 30 juin 2026, avec les klines officielles à la minute conservées pour reconstruire l'ordre dans lequel le plus haut et le plus bas de chaque heure sont survenus, et les événements de funding et les ouvertures horaires du prix mark conservés à leurs vrais horodatages. Les lancements observés sont BTC le 8 septembre 2019 à 17:00 UTC, ETH le 27 novembre 2019 à 07:00, XRP le 6 janvier 2020 à 08:00, BNB le 10 février 2020 à 08:00, DOGE le 10 juillet 2020 à 09:00 et SOL le 14 septembre 2020 à 07:00.
Le panel garde des frontières de calendrier communes plutôt qu'un masque d'intersection, si bien que le trou d'un marché ne supprime jamais une heure aux cinq autres, et les prix ne sont jamais reportés en avant à travers une barre manquante. Un signal crée un ordre pour l'heure UTC immédiatement suivante et rien d'autre, et si cette heure est absente l'ordre est annulé plutôt que reporté. Une position ouverte à travers un trou reste ouverte, et les indicateurs doivent refaire tout leur warm-up avant un nouveau signal.
Une ligne horaire ne peut être remplacée par un agrégat à la minute ou par une réponse REST actuelle que lorsque la ligne source est manifestement corrompue ou creuse, et chaque remplacement stocke sa réponse source, sa raison, son horodatage et son hash dans un manifeste de réparation. Quatorze lignes de plus-haut/plus-bas et 240 lignes horaires absentes ont été réparées sous cette règle avant le gel des données, laissant un panel de 333,424 lignes horaires et aucun trou.
Les six familles de stratégies
Chaque règle calcule un état désiré après la barre complétée t, et un événement de signal ne se déclenche que lorsque l'état désiré est non nul et que l'état précédent était neutre ou opposé, si bien qu'une condition persistante ne peut pas émettre un signal toutes les heures.
# a desired state becomes a signal only on a fresh transition
state = np.where(fast_ema - slow_ema > threshold * atr, 1,
np.where(fast_ema - slow_ema < -threshold * atr, -1, 0))
prior = np.roll(state, 1)
signal = (state != 0) & ((prior == 0) | (prior == -state))| Famille | Règle d'entrée | Domaines numériques |
|---|---|---|
| Tendance EMA | EMA rapide au-dessus de l'EMA lente d'un seuil en unités d'ATR | rapide 12/24/48, lente 48/96/168/336, seuil 0/0.25/0.50 ATR |
| Breakout Donchian | Clôture au-delà du canal glissant précédent, barre courante exclue | lookback 24/48/96/168/336/720 h, buffer 0/5/10 bp |
| Momentum TS | Signe du rendement log sur L heures au-delà d'un seuil mis à l'échelle de la volatilité | lookback 24/72/168/336/720, seuil 0/0.25/0.50/1.00 |
| Retournement RSI | RSI de Wilder hors d'une paire de bornes, pris à contre-pied | longueur 6/14/28/56, bornes 20/80, 30/70, 40/60 |
| Fade Bollinger | Clôture hors d'une bande, prise à contre-pied | lookback 24/48/96/168, largeur 1.0/1.5/2.0/2.5 sd |
| Compression de vol | Cassure de canal pendant que la vol courte reste sous une fraction de la vol longue | courte 12/24, longue 72/168/336, ratio 0.40/0.60/0.80, cassure 12/24/48 |
La propriété qui définit la règle de compression a été mesurée avant toute exposition hors échantillon : ses événements de signal éligibles devaient réellement se produire pendant la période de calibration 2021-2022 dans chaque marché, parce qu'un filtre qui ne filtre jamais est une étiquette et non une stratégie.
Sorties, exécution et trajectoire intrabar
Les six familles ne diffèrent que par la logique d'entrée et partagent une seule architecture de sortie, ce qui empêche une recherche sur les sorties d'introduire en douce de nouvelles familles. L'entrée se fait à l'ouverture de l'heure t+1 après un signal sur la barre complétée t, une position par sleeve, les signaux de même direction ignorés tant que la sleeve est occupée, et un signal opposé retourne la position à l'ouverture suivante et paie deux exécutions. Sinon la position sort au plus tôt du stop, du target ou de la durée de détention maximale.
L'ATR de Wilder(14) connu à l'entrée fixe les distances des barrières pour toute la vie du trade, et les domaines de sortie sont communs à toutes les familles : stops à 1.0, 1.5, 2.0 ou 3.0 ATR d'entrée, avec un rapport gain/risque de 1.0, 1.5, 2.0 ou 3.0 et des détentions maximales de 12, 24, 48, 96 ou 168 heures.
L'OHLC horaire ne peut pas dire quelle barrière a été exécutée en premier quand les deux se trouvent dans la même bougie, donc le moteur reconstruit la trajectoire de chaque heure à partir des barres à la minute comme ouverture, premier extrême enregistré, second extrême enregistré, clôture. Si les deux extrêmes tombent dans la même minute et que l'ordre reste indéterminé, le stop s'exécute en premier. Un gap à travers un stop est exécuté à la première ouverture disponible, alors qu'un gap à travers un target n'est pas exécuté mieux que le target.
# events sharing one hourly timestamp resolve in a fixed order
for hour in window:
apply_funding(hour) # carried into this timestamp
apply_gap_barriers(hour)
apply_reversal(hour) # closes old, opens new, two taker fills
apply_max_hold(hour)
apply_entry(hour)
apply_intrabar_barriers(hour, minute_path[hour])Budget de recherche et éligibilité
Chaque famille reçoit exactement 64 configurations tentées dans chaque triplet stratégie-marché-fenêtre. Un générateur déterministe d'hypercube latin discret avec la seed 20260809 échantillonne les domaines de signal et de sortie, rejette les combinaisons dupliquées ou invalides et écrit une liste canonique de 64 lignes par famille. Les listes sont générées une fois, réutilisées dans chaque marché et chaque fenêtre, sérialisées canoniquement et hachées avant toute exposition hors échantillon, et le manifeste de recherche déclare n_searched = 2304, soit 64 tentatives dans chacune des 36 cellules famille-marché.
Une tentative n'est éligible à remporter le bloc de paramètres que si elle clôt au moins 24 trades répartis sur au moins six semaines calendaires distinctes. Les tentatives échouées, non finies et inéligibles restent dans la table de scores et comptent quand même dans les 64. Les égalités de paramètres se départagent sur le turnover le plus faible, puis sur l'indice canonique le plus bas ; les égalités de sélecteur se départagent sur le turnover agrégé de bloc d'axe le plus faible, puis sur l'ordre gelé des marchés ou des stratégies.
Calendrier walk-forward
Chaque fenêtre externe fait douze mois d'historique in-sample suivis de trois mois hors échantillon, et avance de trois mois. Les neuf premiers mois fittent les paramètres numériques de chaque cellule, les trois derniers mois rejouent la configuration sélectionnée sans y toucher, et seuls ces scores tenus en avant décident de l'axe marché et de l'axe stratégie.
for window in windows: # 14 non-overlapping OOS quarters
fit = window.is_slice[:9_months] # choose numeric parameters per cell
axis = window.is_slice[9_months:] # replay the chosen config, untouched
picks = {
"market": best_mean_over(axis, group="market"), # 6 strategies on it
"strategy": best_mean_over(axis, group="family"), # on 6 markets
}
apply_once(picks, window.oos_slice) # frozen configs, frozen decisionsUn trade dont l'intervalle de détention traverse une frontière de bloc est exclu du score du bloc antérieur, et aucun label, aucune sortie et aucun flux de trésorerie n'enjambe le bloc de paramètres vers le bloc de sélection d'axe, ni l'in-sample vers le hors échantillon. Le warm-up des indicateurs peut lire des barres antérieures au début d'un bloc, puisque ces barres étaient déjà connues, mais les lignes de warm-up ne contribuent jamais au P&L du bloc évalué. La configuration sélectionnée n'est pas refittée sur les douze mois complets après la décision d'axe.
Coûts, funding et comptabilité
Chaque entrée, jambe de retournement, stop, target et sortie sur le temps est une exécution taker qui paie 5 points de base de frais et 2 points de base de slippage sur le notionnel exécuté en valeur absolue, avec une quantité de position fixée au capital de sleeve divisé par le mid d'entrée brut pour toute la vie du trade. Le funding utilise les événements Binance observés à leurs vrais horodatages, un funding positif débitant les longs et créditant les shorts, calculé sur la position tenue immédiatement avant l'horodatage de settlement.
# a buy fills adversely, and the gap is booked as slippage rather than hidden in gross fill = raw_mid * (1 + 0.0002) if side > 0 else raw_mid * (1 - 0.0002) fee = 0.0005 * abs(quantity * fill) slippage = abs(quantity * (fill - raw_mid)) funding_cost = direction * funding_rate * quantity * mark_open # positive when paid net_pnl = gross_pnl - fee - slippage - funding_cost # holds to 1e-9
Une observation de funding attendue et manquante est une défaillance de données, pas un zéro et pas une invitation à imputer. Chaque ligne de ledger conserve le P&L brut, les frais, le slippage, le funding et le P&L net en colonnes séparées, et l'identité ci-dessus se réconcilie sur tout le corpus à 5.55e-17 unité de rendement près.
| Composante, les 36 cellules OOS | bp de capital de sleeve fixe |
|---|---|
| P&L brut | +27,454.6 |
| Frais taker | -168,288.4 |
| Slippage | -67,315.4 |
| Funding | -2,147.0 |
| P&L net | -210,296.2 |
Bras de portefeuille
| Bras | Décision d'axe issue du bloc tenu en avant | Portefeuille hors échantillon |
|---|---|---|
| Aucune sélection d'axe | aucune | les 36 cellules à 1/36 |
| Sélecteur de marché | meilleure moyenne équipondérée sur six stratégies | six stratégies sur ce marché à 1/6 |
| Sélecteur de stratégie | meilleure moyenne équipondérée sur six marchés | cette stratégie sur six marchés à 1/6 |
Tous les rendements de sleeve utilisent un capital fixe, une unité de position absolue maximale et aucune capitalisation. Les sleeves plates, manquantes et inéligibles restent en cash et les sleeves actives ne sont jamais renormalisées, donc chaque bras porte le même budget brut maximal de 100% pendant que l'exposition réalisée et le turnover sont des résultats et non des réglages. Le Sharpe peut être rapporté de façon descriptive à partir du book quotidien non chevauchant, mais il ne sélectionne jamais une configuration, ne choisit jamais un bras et ne décide jamais d'une conclusion.
Inférence
Le moteur construit des rendements nets quotidiens appariés pour les trois bras, et à l'intérieur de chaque fenêtre hors échantillon fixe le bootstrap rééchantillonne des clusters de semaines calendaires du lundi au dimanche UTC, une frontière de trimestre qui coupe une semaine laissant deux fragments séparés. Chaque marché, chaque stratégie et chaque bras à l'intérieur d'un fragment échantillonné voyagent ensemble, ce qui préserve la dépendance transversale sans changer le nombre de clusters d'une fenêtre.
draws = paired_week_cluster_bootstrap(
daily_by_arm, windows=14, n_boot=10_000, seed=20260809,
)
point = cumulative(market_picker) - cumulative(strategy_picker) # +193.9 bp
low, high = np.percentile(draws, [2.5, 97.5]) # -7868.3, +8194.8
ruling = "no_reliable_winner" if low < 0 < high else "winner"Chaque p-value bilatérale secondaire vaut deux fois la plus petite probabilité de queue du bootstrap, avec la correction pour réplicats finis (count + 1) / (B + 1), plafonnée à un, avant que les deux p-values sélecteur contre absence de sélection n'entrent dans Benjamini-Hochberg à 5%, où toutes deux ont renvoyé 0.7513. Aucun ajustement de multiplicité ne s'applique au contraste primaire unique enregistré, et les quatorze différences par fenêtre sont des diagnostics descriptifs et non des unités de bootstrap.
Décomposition
La décomposition utilise les 36 rendements nets hors échantillon au niveau cellule, non pondérés, dans chaque fenêtre complète, après le gel des paramètres par cellule, et calcule des sommes des carrés à effets fixes orthogonales sur le cube équilibré marché par stratégie par fenêtre.
ss = orthogonal_fixed_effect_ss(cube) # market x strategy x window
shares = {k: v / ss["total"] for k, v in ss.items()}
# market 0.46%, strategy 1.94%, interaction 6.98%, time 1.78%, residual 88.84%
boot = [shares_of(resample_weeks(cube)) for _ in range(n_boot)]
critical = np.percentile(max_abs_centered_error(boot), 95) # 4.92 ppCe sont des parts descriptives de sommes des carrés, pas des composantes de variance de population ; les appeler estimations de variance à effets aléatoires affirmerait une échangeabilité que six marchés délibérément sélectionnés n'ont pas. Une composante n'est dite dominante que lorsque sa part ponctuelle est la plus grande et que ses intervalles simultanés contre les deux autres composantes transversales excluent zéro. L'interaction remplit le critère : marché moins interaction va de -11.45 à -1.61 pp et stratégie moins interaction de -9.96 à -0.12 pp, tandis que marché moins stratégie couvre -6.40 à +3.44 pp.
Clones, transfert et sensibilités
Les groupes de clones sont déterminés à l'intérieur de chaque fenêtre à partir des vecteurs signés de positions quotidiennes du bloc d'axe pour les configurations déjà sélectionnées, concaténés sur les six marchés. Les doublons exacts fusionnent toujours, les quasi-clones fusionnent par lien complet uniquement quand chaque corrélation croisée vaut au moins 0.95, une corrélation négative ne fusionne jamais des stratégies inverses, et une paire a besoin d'au moins 60 observations quotidiennes finies à variance non nulle avant qu'une corrélation soit seulement définie. Six groupes singletons se sont formés dans chaque fenêtre, donc le bras à clones fusionnés est égal au bras primaire.
Le transfert est testé en rejouant les 36 configurations gelées de chaque fenêtre sur les six marchés cibles à travers le bloc de sélection d'axe et le bloc hors échantillon correspondant, avec des coûts, un funding, un dimensionnement et une exécution identiques. L'étape de transfert tourne une fois par fenêtre plutôt qu'en pooled, parce qu'une invocation poolée laisserait des périodes in-sample postérieures participer à une sélection notée sur des périodes hors échantillon antérieures.
La liste de sensibilités enregistrée, fixée avant les résultats, comprend un bloc de paramètres sur historique de lancement en expansion, un événement de funding constant et positif de 1 bp, un axe stratégie à clones fusionnés, un transfert noté par expectancy au lieu du P&L total, six panels de sélecteurs leave-one-market-out et la fenêtre partielle de juillet 2026 à titre purement descriptif. Aucune sensibilité n'a été ajoutée après que le résultat primaire est devenu visible, et aucune ne peut remplacer le résultat principal.
| Estimation | Marché moins stratégie | Intervalle à 95% |
|---|---|---|
| Primaire enregistrée | +193.9 bp | -7,868.3 à 8,194.8 |
| Historique de paramètres en expansion | -1,596.7 bp | -8,488.4 à 5,322.1 |
| Funding constant de 1 bp | +1,359.1 bp | -6,012.0 à 8,808.5 |
| Marché retiré | Marché moins stratégie | Sélecteur de marché | Sélecteur de stratégie |
|---|---|---|---|
| BTC | +928.4 bp | -4,979.0 | -5,907.4 |
| ETH | +345.3 bp | -3,320.1 | -3,665.4 |
| DOGE | +4,064.3 bp | -3,646.1 | -7,710.4 |
| XRP | +940.3 bp | -4,278.4 | -5,218.7 |
| BNB | -4,494.8 bp | -6,474.6 | -1,979.7 |
| SOL | +465.2 bp | -4,795.8 | -5,260.9 |
Gates de pré-enregistrement
Dix-neuf gates bloquantes devaient passer après le dernier changement de code pertinent et avant la lecture de la moindre barre hors échantillon, chacune avec un défaut planté prouvant que la gate peut échouer. Elles couvrent l'accord des hashes sur les données, les stratégies, la recherche, les coûts et l'analyse ; les contrôles de signal, de direction et d'activité sur données réelles pour les six familles sur les six marchés ; un filtre de compression qui filtre de façon mesurable ; une troncature du futur laissant inchangés tous les signaux, candidats et trades complétés antérieurs ; aucun champ de funding, de coût, de chronologie plus-haut/plus-bas ou de résultat n'atteignant un signal ; la parité d'exécution Python contre Rust sur des tranches réelles ; l'ordre de la trajectoire à la minute changeant un résultat planté de double touche ; les signes et les comptes de funding se réconciliant avec les données sources ; l'identité du P&L net tenant par trade et en agrégat ; les 64 tentatives persistées, échecs compris ; des frontières de blocs ordonnées, purgées et disjointes ; un panel planté transposé intervertissant exactement les deux sélecteurs ; les comptes et poids de sleeves par bras sans renormalisation du cash ; des contrôles de clones plantés produisant les clusters attendus ; un résultat futur planté améliorant matériellement l'optimiseur et étant détecté ; une rotation des actions intra-fenêtre préservant exactement le turnover tout en détruisant la relation plantée ; des gates échouées propageant un code de sortie non nul ; et l'état de préparation rapportant les contrôles enregistrés comme exécutables plutôt que silencieusement partiels.
La dernière d'entre elles est la gate de corpus nul : sur un corpus de marches aléatoires sans drift réservé aux tests, à travers 30 seeds indépendantes, aucune moyenne hors échantillon sélectionnée in-sample n'avait un intervalle bootstrap à 95% entièrement au-dessus de zéro.
Résultats de vérification
- Corpus : 31,770,479 lignes de trades candidats, 16,826 trades hors échantillon sélectionnés.
- Identité de coûts : réconciliée à 5.55e-17 unité de rendement.
- Parité des moteurs : 25,259 trades sur 72 cas de données réelles, désaccord absolu maximal de P&L de 1.94e-16.
- Écran de fuite intra-barre : corrélation de -0.0001, contre +0.0051 pour le rendement futur.
- Contrôle des frontières : zéro trade traversant sa fenêtre hors échantillon enregistrée ou tombant en dehors.
- Backtest guard : zéro échec automatique sur les contrôles walk-forward, ledger, coûts et incertitude groupée par semaine.
Deux avertissements automatiques ont été résolus en vérifiant directement les données plutôt que par argument. Le contrôle générique a lu l'horodatage partagé entre in-sample et hors échantillon comme une purge de zéro jour, alors que le moteur purge au niveau du trade, ce que le comptage direct aux frontières tranche. L'étape des entrées orientée Sharpe du même contrôle a rapporté une faible puissance et une dispersion d'échelle de bruit entre familles, ce qui ne touche pas une comparaison décidée sur le P&L mais exclut bien de présenter ici une famille isolée comme un edge découvert.
pre-OOS freeze b7c102a3a6cbbbb0752e174cc610ed7c56a0f9fe8f94fd42ef184fa16ccd12cb engine manifest f2e70f0c2334751029be76673e7f8a429413944ef8026af149c1c9730f717a78 results 4cdf4ac1b42420f480528f3db6323b2e12b7d21ac160d99712e08983dc35451d data manifest 34a26b3380223fe19afb73cf3d63a60d9e991fba82917e42adc8e7d66adae1e3
Tout changement postérieur à la visibilité d'un résultat hors échantillon exige une nouvelle racine d'exécution, de nouveaux hashes et un amendement écrit. Deux amendements existent, tous deux postérieurs au gel et tous deux présentationnels : l'un a retiré un renommage de colonne dupliqué dans l'export de fenêtres, l'autre a restauré un label de figure raccourci, écrit les frontières de fenêtres en datetimes sans fuseau et fait retourner un échec au lanceur de contrôles quand un contrôle demandé échoue. Ni l'un ni l'autre n'a touché aux données, configurations, trades, rendements, portefeuilles, à l'inférence ou au verdict.
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 données publiques de Binance. Les scripts de production, la construction des données, le moteur Rust, l'analyse et la génération des figures ne sont pas fournis. Envoyez un e-mail à daniel@daru.finance et je vous les enverrai.

