Chaque chiffre de l'article, et d'où il vient
Assez de méthode pour vérifier l'arithmétique à la main, ou pour reconstruire une étude équivalente à partir de l'archive publique Binance.
Plateforme, univers et dates
Uniquement des futures perpétuels Binance margés en USDT, avec une sélection d'univers datée du 30 juin 2022. Un contrat est éligible lorsque, sur la seule information disponible ce jour-là : c'est un perpétuel coté et margé en USDT ; son sous-jacent est une pièce unique, ce qui exclut les trois contrats à sous-jacent indiciel DEFIUSDT, BTCDOMUSDT et ALLUSDT ; il a traité un volume réellement positif sur au moins 80% des jours calendaires des douze mois se terminant à la date de sélection, avec une première observation dans les 7 jours suivant le début de cette fenêtre ; il a été traité au moins 55 jours en avril, mai et juin 2022 ; et il était encore vivant à la date de sélection, son dernier jour à volume positif tombant dans les 7 jours qui la précèdent.
Cette dernière règle existe parce que le décompte trimestriel seul n'attrape pas un contrat mort à l'intérieur de la fenêtre de classement. Quatre contrats franchissaient le décompte de jours sans être traités à la date de sélection et sont exclus : ICPUSDT et SCUSDT ont été suspendus à partir des 16 et 17 juin 2022, tandis que DODOUSDT et AKROUSDT ont été traités pour la dernière fois le 27 mai 2022 tout en comptant encore 57 jours dans le trimestre.
Les contrats éligibles sont classés par volume quotidien médian en actif de cotation du 1er avril au 30 juin 2022, ce qui, pour des perpétuels linéaires, est déjà un notionnel en USDT. Le filtre donne 105 contrats, après quoi l'échelle gelée retient les 10, 25, 50 et 100 premiers et reste strictement emboîtée, laissant une marge de 5 contrats au-dessus du plus grand échelon.
Points de repère à la date de sélection : le rang 1 est BTCUSDT à 13,246M$ de notionnel quotidien médian, le rang 10 XRPUSDT à 367M$, le rang 25 AAVEUSDT à 160M$, le rang 50 ENJUSDT à 46M$ et le rang 100 NKNUSDT à 16M$.
eligible = (
single_coin
& (volume_days_12m / calendar_days_12m >= 0.80)
& (first_obs <= window_start + timedelta(days=7))
& (quarter_trading_days >= 55)
& (last_positive_volume_day >= SELECTION_DATE - timedelta(days=7))
)
ranked = median_daily_quote_volume[eligible].sort_values(ascending=False)
rungs = {n: ranked.index[:n].tolist() for n in (10, 25, 50, 100)}L'existence d'un fichier ne prouve pas qu'il y a eu trading, donc l'éligibilité, le classement et la fin de l'historique d'un contrat reposent tous sur un volume traité positif, le remplissage de la plateforme après le dernier vrai trade étant écarté. Les contrats qui disparaissent après la date de sélection restent dans l'univers et passent en cash à leur dernière barre traitée.
La liste maîtresse des symboles est l'index de l'archive et non le point de terminaison des contrats en vie, parce que ce dernier omet 333 contrats délistés depuis. Deux renommages de ticker sont raccordés après vérification de la continuité des prix au passage de relais, MATIC vers POL en septembre 2024 et EOS vers A en mai 2025, deux migrations 1:1. Une redénomination, où le notionnel change d'un facteur constant comme dans les contrats 1000X, n'est pas un renommage et n'est jamais raccordée.
Les données en échantillon de la première fenêtre walk-forward couvrent juillet 2022 à juin 2023, puis la période hors échantillon court du 1er juillet 2023 au 31 juillet 2026, soit 37 mois calendaires complets et non chevauchants. Chaque barre utilisée où que ce soit est postérieure à la date de sélection, et cette date est placée aussi tard délibérément : un univers choisi sur la liquidité de juin 2022 puis servant à estimer des statistiques de classement sur un historique antérieur conditionnerait ces estimations à une survie qui n'était pas connaissable à l'époque.
Données et calendrier
Deux échelles de temps, 15 minutes et 1 heure, toutes deux sur la grille horloge UTC, traitées comme des cellules distinctes et jamais regroupées. Les barres viennent de l'archive publique Binance pour les 105 contrats : klines 15 minutes et 1 heure comme séries principales, klines à la minute conservées uniquement pour reconstruire l'ordre dans lequel le plus haut et le plus bas de chaque barre sont survenus, événements de taux de funding à leurs horodatages de règlement réels et klines de mark price pour la valorisation du funding.
Les panels ne propagent jamais un prix ni un rendement à travers une barre manquante. Un signal crée un ordre pour la barre immédiatement suivante seulement, donc si cette barre est absente l'ordre est annulé plutôt que reporté à une ouverture observée plus tard. Une position ouverte à travers un trou de données reste ouverte et applique les règles de barrière par gap à sa première barre observée ultérieure, après quoi les indicateurs achèvent de nouveau tout leur préchauffage avant qu'un autre signal puisse se déclencher.
Les panels construits vont du 1er juin 2022 au 31 juillet 2026 et portent 15,015,087 barres quinze minutes et 3,757,437 barres horaires sur 105 contrats, avec 481,644 événements de funding. Les gates de données couvrent les horodatages en double, la monotonie, les identités OHLC, la positivité des prix, l'historique de trading fondé sur le volume, les dates d'inscription, les décomptes de trous, la concordance d'agrégation de la minute vers la barre et la couverture du funding face à la source, tous passant sans aucun échec pour un trou de barre médian de 0.000%.
Spreads
Les spreads sont mesurés plutôt qu'estimés à partir des barres. L'estimateur haut-bas de Corwin-Schultz a été testé et rejeté pour cette plateforme lors d'une construction antérieure : il renvoyait 18.76 bps pour BTCUSDT là où les cotations bookTicker donnent 0.0330 bps, une surestimation d'un facteur 568, et il bougeait aussi avec la fréquence des barres, donnant 8.9 bps sur barres 15 minutes contre 18.8 bps sur barres 1 heure. Un estimateur qui dépend de la fréquence d'échantillonnage des barres dont il est calculé mesure la volatilité.
Ce qui est mesuré à la place, c'est le rebond bid-ask, la variation de prix absolue médiane entre trades agrégés consécutifs. Les trades alternent entre bid et ask, donc cette médiane retrouve directement le spread. Validé sur BTCUSDT pour le 15 juillet 2023, il a renvoyé 0.0330 bps face à une vérité terrain bookTicker de 0.0330 bps sur 8.3M de mises à jour de cotation.
# two sampled days per symbol-month, streamed and discarded changes = agg_trades["price"].diff().abs() changes = changes[changes > 0] spread_bps = 1e4 * changes.median() / agg_trades["price"].median()
Deux jours par symbole-mois sont échantillonnés, le 7 et le 21, avec repli sur des dates voisines quand l'un manque, ce qui fait environ 10,500 symbole-jours et à peu près 26 Go streamés. Un jour échantillonné ne donne aucune estimation quand le contrat a imprimé moins de 500 trades ou moins de 100 variations de prix non nulles, ce qui arrive dans 589 des 5,146 symbole-mois, concentrés dans la queue illiquide.
Un symbole-mois sans estimation prend le 90e centile des spreads mesurés de ce symbole, tandis qu'un symbole sans estimation sur aucun mois prend le 90e centile de son échelon d'univers pour ce mois. Les deux replis sont délibérément larges, donc une donnée absente ne peut que rendre le trading plus cher en apparence, et 88.6% des symbole-mois sont tarifés directement.
L'archive publie bookTicker à partir de mai 2023, ce qui recouvre l'essentiel de la période hors échantillon, et cela avait été enregistré comme validation stratifiée de la série mesurée au titre du gate 16. Ce gate n'a jamais été exécuté pour cette étude, donc la comparaison à la vérité terrain est héritée de la construction antérieure plutôt que refaite ici.
La bibliothèque de stratégies
Huit familles structurelles avec dix configurations numériques gelées chacune, 80 stratégies, générées sous la graine 20260808 et héritées telles quelles : bollinger_reversal, breakout, mean_reversion, momentum, moving_average_crossover, rsi_reversal, trend_following et volatility_breakout.
Les paramètres numériques sont ici des propriétés gelées d'une stratégie plutôt que des réglages ajustables, parce que cette étude n'effectue aucune recherche de paramètres, ce qui laisse les 80 stratégies comme unités de réplication.
Le filtre d'implémentation de la bibliothèque héritée avait été calibré sur un panel de 10 instruments, donc la couverture a été remesurée sur cet univers avant le gel, en lisant l'activité, la durée de détention, la couverture d'instruments et la rotation sans jamais lire le profit. Aucun remplacement n'a été nécessaire : 8,398 des 8,400 paires stratégie-actif produisent au moins un candidat sur barres 1 heure, et 8,400 sur 8,400 le font sur barres 15 minutes.
Signaux, sorties et exécution intra-barre
Chaque règle calcule une condition dans {-1, 0, +1} à partir de barres achevées uniquement, et un candidat ne se déclenche que lorsque la vue de la stratégie s'inverse réellement. Une barre neutre n'est ni un signal ni une remise à zéro, donc la vue est conservée jusqu'à ce que le côté opposé se déclenche.
# a candidate exists only on a genuine directional inversion view = condition.replace(0, np.nan).ffill() candidate = view.ne(view.shift(1)) & view.notna() & view.shift(1).notna()
Une position se ferme à son take-profit, à son stop-loss ou lorsque la stratégie produit un signal opposé sur cet actif, sans durée maximale. L'ATR de Wilder(14), connu à l'entrée, fixe les distances de barrière pour toute la vie du trade, à un stop de 2.0 ATR et un objectif de 3.0 ATR pour chaque stratégie.
Ce couple vient d'une calibration portant seulement sur l'activité, sur la première fenêtre en échantillon de l'échelon focal, lisant la durée de détention, le mélange de motifs de sortie et l'occupation sans jamais lire le profit. C'est l'occupation qui a tranché : sur la grille de barrières, les positions concurrentes moyennes par stratégie sur le top-50 allaient de 1.96 à un stop de 1.0 ATR à 11.33 à 3.0 ATR.
Le couple retenu est celui qui donne 6.39 positions concurrentes en moyenne pour une capacité focale de 5, saturant sur 34.7% des barres, tout en gardant les sorties sur barrière majoritaires à 58.5% contre 41.5% de retournements, une durée de détention médiane de 6 barres et aucune troncature.
L'OHLC de la barre seul ne peut pas dire quelle barrière s'est exécutée en premier quand les deux tombent à l'intérieur d'une même barre, donc le moteur reconstruit le chemin intra-barre à partir des barres à la minute sous la forme open → first recorded extreme → second recorded extreme → close. Là où le plus haut et le plus bas tombent dans la même minute et où l'ordre reste indéterminé, le stop s'exécute en premier, ce qui arrive sur 0.13% des barres à la cellule focale. Un gap à travers un stop s'exécute à la première ouverture disponible, tandis qu'un gap à travers un objectif ne s'exécute pas mieux que l'objectif.
# events sharing one bar timestamp resolve in a fixed order
for ts in timestamps:
apply_funding(ts)
apply_gap_barriers(ts)
apply_opposite_signal_exits(ts)
release_slots(ts) # exits always precede entries
allocate_entries(ts) # a slot freed at this open is available now
apply_intrabar_barriers(ts)Le flux de candidats invariant
C'est la colonne vertébrale de l'étude et la raison pour laquelle les modes d'exécution sont comparables tout court. À capital fixe par place, avec un book sans levier et sans impact modélisé, le prix d'entrée, le prix de sortie, le motif de sortie, les coûts par unité, le funding par unité et le résultat par unité d'un trade ne dépendent pas de ce que le portefeuille détient par ailleurs. Le résultat de chaque candidat est donc calculé une seule fois, après quoi chaque mode d'exécution, chaque taille d'univers, chaque capacité et chaque règle de classement est une politique d'ordonnancement sur le même objet gelé.
Un candidat est généré pour chaque transition de signal sur chaque paire stratégie-actif, indépendamment de l'occupation et de tout portefeuille, portant l'actif, la stratégie, la famille, l'échelle de temps, les horodatages de signal et d'entrée, le sens, le prix d'entrée, l'ATR d'entrée, la valeur de force déclarée, l'horodatage de sortie précalculé, le prix de sortie et le motif de sortie, plus le brut, les frais, le slippage, le funding et le résultat net par unité dans les deux unités comptables.
Que ces résultats soient identiques bit à bit sur chaque bras, chaque capacité et chaque échelon est un gate bloquant et non une hypothèse. Le flux contient 3,199,677 candidats sur barres 1 heure et 13,253,337 sur barres 15 minutes, pour 16,452,374 lignes de résultats au total.
Une limite en découle directement : parce que les coûts sont une fraction fixe du notionnel, le modèle ne facture pas plus cher un book concentré qu'un book diffus pour le même trade, raison pour laquelle les diagnostics neutres en exposition et la sensibilité au slippage dépendant de la taille sont enregistrés.
Coûts, funding et comptabilité
5 bps taker, 2 bps maker, 2 bps de slippage, plus la moitié du spread mesuré sur toute exécution qui traverse. Une exécution qui traverse paie le taker plus le slippage plus la moitié du spread, tandis qu'un take-profit repose comme ordre à cours limité et paie les frais maker sans slippage ni spread. Un stop traverse et les exécutions d'entrée traversent toujours, les coûts étant appliqués au notionnel exécuté absolu.
def fill_cost(notional, crosses, spread_bps):
fee = TAKER_BPS if crosses else MAKER_BPS
slip = SLIPPAGE_BPS if crosses else 0.0
half_spread = 0.5 * spread_bps if crosses else 0.0
return abs(notional) * (fee + slip + half_spread) / 1e4Le funding utilise les événements de funding Binance observés à leurs horodatages de règlement réels plutôt qu'un calendrier supposé, et une observation de funding attendue et manquante est une défaillance de données et non un zéro, un funding positif débitant les longs et créditant les shorts. Le funding est évalué sur la position détenue immédiatement avant l'horodatage de règlement, si bien qu'une nouvelle entrée à cet horodatage ne paie ni ne reçoit, alors qu'une position portée jusque-là et sortie à ce moment le fait.
Chaque ligne du ledger conserve séparément le brut, les frais, le slippage, le funding et le net, si bien que l'identité gross - fee - slippage - funding = net tient à 1e-9 près au niveau de la ligne comme en agrégat, se réconciliant à 0.0 sur l'ensemble des 16,452,374 résultats candidats.
Deux unités comptables sont maintenues d'un bout à l'autre et aucune ne se substitue à l'autre : le R normalisé, l'unité par trade normalisée par l'ATR utilisée pour la qualité de sélection, puis le rendement sur notionnel, utilisé pour le résultat du portefeuille au poids de la position. Le capital inactif ne rapporte rien et les rendements sont arithmétiques sur capital fixe, sans aucune capitalisation.
Les bras et les règles de classement
Tous les bras utilisent un capital fixe, un capital égal par position ouverte, un book sans levier et aucune capitalisation. Le capital inutilisé reste inactif et les positions actives ne sont jamais redimensionnées pour remplacer une position à plat ou rejetée.
| Bras | Places | Poids | Sélection quand les places sont rares |
|---|---|---|---|
| Référence indépendante | U, la taille de l'univers | 1/U | jamais rares, chaque candidat est pris |
| Aléatoire | K | 1/K | tirage uniforme parmi les concurrents |
| Force du signal | K | 1/K | force déclarée la plus élevée |
| Espérance historique | K | 1/K | espérance en échantillon la plus élevée pour l'actif |
| Ratio d'information | K | 1/K | ratio d'information en échantillon le plus élevé pour l'actif |
Les capacités sont K dans {1, 3, 5, 10} et les tailles d'univers U dans {10, 25, 50, 100}. Les portefeuilles sont toujours mono-stratégie : une stratégie concourt à travers les actifs pour ses propres places, et les signaux de familles différentes ne partagent jamais une file. Le rendement rapporté d'un bras est la moyenne équipondérée sur les 80 portefeuilles mono-stratégie, un book à 1/80 qui reste sans levier.
Le bras aléatoire est la moyenne sur 100 réplications indépendantes sous un générateur dont la graine dépend de la cellule, de la fenêtre et de la réplication, puisqu'un tirage unique est du bruit et n'est jamais rapporté comme le bras aléatoire.
La force du signal est déclarée par famille et fixée avant l'exécution, toujours dans une unité sans échelle pour qu'un actif à forte volatilité ne puisse pas devancer un actif à faible volatilité pour des raisons étrangères à la conviction, puisque des distances en pourcentage brut encoderaient un biais de volatilité et sont interdites.
| Famille | Force |
|---|---|
| bollinger_reversal | excédent du z-score absolu du log-prix sur la largeur de bande, en écarts types |
| breakout | distance de la clôture au-delà de l'extrême du canal précédent et de sa marge, en ATR d'entrée |
| mean_reversion | excédent du z-score absolu sur le seuil d'entrée, en écarts types |
| momentum | excédent du rendement de lookback mis à l'échelle par la volatilité sur son seuil |
| moving_average_crossover | écartement absolu entre EMA rapide et EMA lente, en ATR d'entrée |
| rsi_reversal | distance du RSI au-delà de sa borne, en points de RSI |
| trend_following | excédent de la distance à l'EMA mise à l'échelle par la volatilité sur son seuil |
| volatility_breakout | amplitude du rendement de lookback directionnel mise à l'échelle par la volatilité |
L'espérance historique est le résultat net moyen en échantillon par candidat exécuté sous la référence indépendante, en R normalisé, exigeant au moins 20 candidats exécutés en échantillon pour cette paire stratégie-actif. Le ratio d'information divise cette moyenne par son propre écart type en échantillon et exige le même minimum plus un écart type non nul. Les actifs sous l'un ou l'autre seuil se classent en dernier, ordonnés par l'indice gelé de l'univers, sans rétrécissement, sans mise en commun et sans modèle.
stats = (
is_trades.groupby(["strategy", "symbol"])["net_r"]
.agg(["mean", "std", "count"])
.query("count >= 20")
)
expectancy_rank = stats["mean"].sort_values(ascending=False)
info_ratio_rank = (stats["mean"] / stats["std"]).sort_values(ascending=False)La fréquence à laquelle le seuil mord a été mesurée avant le gel à partir des seuls décomptes de candidats, sans lecture du profit. À l'échelon focal, une médiane de 48 actifs sur 50 sont classables par stratégie-fenêtre, et 79.8% des stratégie-fenêtres portent au moins 10 actifs classables, le manque étant concentré dans les familles lentes, où rsi_reversal franchit les 20 trades en échantillon dans 33% des cellules contre 91% pour moving_average_crossover.
Mécanique des places
Le rejet est de type immédiat ou annulé, donc un candidat n'est éligible à une place qu'à sa propre barre d'entrée, et si aucune place n'est libre à cet instant il est définitivement rejeté et n'entre jamais plus tard. Une file d'attente persistante a été envisagée puis écartée, parce qu'un candidat qui attend entre sur un signal périmé et à un prix différent, ce qui en fait un autre trade et casse l'affirmation selon laquelle seule la sélection des trades diffère entre les bras.
À l'intérieur d'un même horodatage, les sorties et les libérations de place sont traitées avant l'allocation des entrées, après quoi les candidats en concurrence sont triés par la règle de classement du bras et les places se remplissent dans l'ordre du classement jusqu'à atteindre la capacité, les égalités étant départagées par l'indice gelé d'univers le plus petit. L'occupation ne peut jamais dépasser K et le capital déployé ne peut jamais dépasser 100%, les deux étant imposés comme gates bloquants.
Quand K est supérieur ou égal à U, la contrainte ne peut jamais mordre et le bras portefeuille doit produire un ledger identique à la référence indépendante, un contrôle positif et un gate bloquant.
Les arrivées simultanées sont le canal minoritaire, avec plus d'un candidat arrivant au même horodatage sur 9.8% des événements d'arrivée et plus de cinq sur 2.4% des barres. Le rejet survient donc surtout parce que des trades antérieurs occupent encore les places, mécanisme réaliste et celui que le design entend tester. Cela veut dire aussi que le classement agit sur une tranche étroite, étant donné que le rejet par un book déjà plein est aveugle au rang.
Calendrier walk-forward
Chaque fenêtre est de 12 mois calendaires en échantillon suivis d'1 mois calendaire hors échantillon, en avançant d'un mois à la fois, si bien que juillet 2023 est le premier mois hors échantillon et juillet 2026 le dernier, ce qui donne 37 fenêtres.
Les données en échantillon servent exactement à une chose, estimer les statistiques de classement par actif, sans aucune recherche de paramètres en échantillon ni aucune sélection de modèle en échantillon nulle part. Les classements sont gelés à la borne de fenêtre et appliqués pendant un mois hors échantillon, et une position ouverte sous le classement d'un mois n'est jamais redécidée quand le classement roule.
Le préchauffage d'indicateur le plus long peut utiliser des barres antérieures au début d'un bloc, parce que ces barres étaient déjà connues, même si les lignes de préchauffage ne contribuent jamais au profit ni à la perte du bloc noté.
Une fenêtre en échantillon de 12 mois rend les statistiques de classement plus bruitées qu'une fenêtre plus longue, et les deux bras classés sont construits exactement à partir de ces statistiques, donc le design penche vers la conclusion que le classement ne bat pas l'aléatoire. Cela a été enregistré comme une propriété connue plutôt que découvert après coup, d'où la sensibilité à fenêtre plus longue.
Inférence
Le moteur construit des rendements nets quotidiens appariés sur capital fixe pour chaque bras à chaque cellule, sur des jours calendaires UTC : 1,127 jours répartis en 162 grappes de semaine. Le rééchantillonnage porte sur des grappes de semaine calendaire du lundi au dimanche UTC, où une borne de mois coupe une semaine en fragments fenêtre-semaine distincts et où chaque actif, stratégie et bras à l'intérieur d'un fragment tiré voyage ensemble.
# paired, week-clustered, common random numbers across arms
rng = np.random.default_rng(20260810)
for b in range(B):
weeks = rng.choice(clusters, size=len(clusters), replace=True)
draw = np.concatenate([day_index[w] for w in weeks])
stat[b] = 1e4 * (ret_a[draw].sum() - ret_b[draw].sum())
point, lo, hi = stat.mean(), *np.percentile(stat, [2.5, 97.5])Des réplications bootstrap appariées et groupées par semaine sous la graine 20260810 construisent l'intervalle principal, où dans chaque réplication la statistique est la différence de rendement arithmétique cumulé sur l'historique hors échantillon rééchantillonné. Chaque p-valeur secondaire bilatérale vaut deux fois la plus petite probabilité de queue du bootstrap avec la correction de réplications finies (count + 1) / (B + 1), plafonnée à un, avant d'entrer dans Benjamini-Hochberg.
Aucun ajustement de multiplicité ne s'applique à l'unique contraste principal enregistré. D_cap et D_rank forment une famille de Benjamini-Hochberg de deux, tandis que la surface enregistrée de 288 contrastes forme une famille distincte corrigée en son sein, dont 193 sont significatifs.
Un trade réalisé comptabilise tout son résultat net le jour où il sort. Les durées de détention médianes sont de 6 heures sur barres 1 heure et de 1.2 heure sur barres 15 minutes, donc ce qui compte pour le groupement par semaine est la fréquence à laquelle un trade chevauche une borne de semaine : 6.1% en 1 heure et 1.5% en 15 minutes. Ces quelques cas introduisent une dépendance légère entre grappes adjacentes, ce qui sous-estime un peu l'incertitude, un effet que la variante d'attribution au jour d'entrée borne à 1.9 bp sur D_cap.
Diagnostics descriptifs plutôt qu'unités de bootstrap, les 37 différences mensuelles ne sont pas 37 stratégies indépendantes. Le Sharpe peut être rapporté à titre descriptif et ne sélectionne jamais une configuration, ne classe jamais un bras et ne décide jamais d'une conclusion.
Le corpus nul sans dérive
Le gate 20 construit trente corpus sans dérive indépendants, chacun de 50 actifs synthétiques portant la volatilité réalisée et le spread d'un contrat réel, et passe chacun dans tout le pipeline de la cellule focale : signaux, candidats, sorties de premier passage sous le modèle de coûts réel, statistiques de classement en échantillon, puis les bras. Ces séries ne contiennent aucune prévisibilité des rendements, donc tout ce qu'un bras classé y gagne a été fabriqué par la machinerie.
# volatility and spread preserved on purpose; drift removed
for seed in range(1, 31):
rng = np.random.default_rng(seed)
for symbol in rung_50:
sigma = realised_vol[symbol] # per-bar, from the real panel
log_ret = rng.normal(loc=0.0, scale=sigma, size=n_bars)
synthetic[symbol] = np.exp(np.cumsum(log_ret)) * first_price[symbol]
run_full_pipeline(synthetic, spreads=measured_spreads)Préserver le profil de volatilité est tout l'objet. Les barrières sont mises à l'échelle par l'ATR alors que les coûts sont une fraction fixe du notionnel, donc un actif à faible volatilité paie proportionnellement plus de coût par unité de risque. L'espérance passée et la force du signal corrèlent toutes deux avec la volatilité, et la volatilité persiste, si bien qu'une règle de classement peut sélectionner des trades moins chers par unité de risque sans rien prévoir du tout.
Un corpus nul qui égaliserait la volatilité aurait masqué précisément le mécanisme qui valait d'être testé.
La condition de réussite enregistrée était qu'aucun avantage d'un bras classé sur l'aléatoire n'ait un intervalle à 95% entièrement au-dessus de zéro sur l'ensemble des graines. La force du signal franchit cette barre sur 8 des 30 corpus ne contenant aucun signal, donc le gate échoue. L'espérance ne déclenche pas la condition enregistrée tout en se situant au 87e centile de son propre nul, soit un p empirique de 0.133 calculé comme la part des tirages nuls supérieurs ou égaux à l'estimation réelle.
Les diagnostics qui séparent le déploiement de la sélection
Quatre diagnostics existent spécifiquement pour empêcher qu'un artefact de cash inactif soit compté comme du talent, et sont obligatoires partout où apparaît un chiffre principal : le R net par candidat offert, le rendement net par unité de capital moyen déployé, l'utilisation des opportunités avec le taux de rejet, puis l'occupation moyenne et maximale avec le capital inactif moyen.
# denominator is the frozen candidate stream, identical for every arm net_r_per_offered = taken["net_r"].sum() / offered_count binding_rate = rejected_book_full / arrivals # asset-block rejections counted separately
Le taux de saturation est la part des arrivées de candidats rejetées parce que le book détenait déjà K positions, les rejets causés par la règle d'une position par actif étant comptés séparément et non traités comme de la saturation, et la référence indépendante est incluse comme contrôle sur une attente de quasi-zéro.
Un book à capacité 1 qui détient une position à la fois dans un univers de 100 noms affichera un Sharpe flatteur presque entièrement fait de cash inactif, donc rapporter le rendement sur capital fixe à côté des mesures par capital déployé et par candidat offert garde cela visible.
Pour la cellule focale, un ledger complet par position, 2,303,532 lignes sur les cinq bras, est écrit pour inspection. Produire ces diagnostics a exigé une réimplémentation de l'ordonnanceur en Python pur, qui s'est accordée avec le noyau numba sur chaque ensemble pris à travers les cinq bras et les 80 stratégies, sans aucun désaccord.
Gates et résultats des vérifications
Huit des 21 gates bloquants de la spécification sont implémentés. Les six gates antérieurs au hors échantillon tournent sur des données en échantillon uniquement et passent tous.
| Gate | Résultat |
|---|---|
| identité K ≥ U face à la référence indépendante | différence absolue maximale de 3.47e-17 |
| le classement clairvoyant domine l'aléatoire et l'anti-classement | +12.53 contre -1.29 contre -15.13 |
| stabilité de graine de l'aléatoire | dispersion de 0.042, quelque 330 fois plus petite que l'écart de clairvoyance |
| le capital déployé ne dépasse jamais le capital fixe | maximum de capital moyen déployé 95.2% |
| une position par actif par stratégie | imposé, 7,244 pris sur 40,824 offerts |
| le défaut planté est détecté | l'identité diverge de 0.514 au lieu de zéro |
Au-delà, la marche de premier passage en numba reproduit exactement la référence Python gelée sur 59,214 candidats avec les six motifs de sortie exercés, et une suite de tests adverses a ajouté 18,672 comparaisons de plus sans aucune divergence. La parité d'ordonnanceur du gate 14 passe sans aucun désaccord, tandis que le gate 20 échoue, et c'est cet échec qui a retiré l'affirmation sur le classement.
Deux vérifications après le premier gel, l'une du moteur et l'autre des statistiques, ont trouvé quatre défauts qui ont changé les chiffres : une place libérée pour tout trade clôturant à l'intérieur de sa propre barre d'entrée, si bien que 7.4% des trades n'occupaient aucune capacité ; des candidats acceptés sur tout ATR positif, laissant des dénominateurs de risque dénormaux produire des résultats atteignant 1e95 ; un book qui se vidait à chaque borne de mois ; et un départage par l'ordre des lignes du parquet plutôt que par l'indice gelé de l'univers. Corriger l'ensemble a déplacé le chiffre principal de -10,459 bp à -8,090 bp, sans changer aucun signe ni aucune conclusion.
Treize gates restent non implémentés, et aucun ne soutient une affirmation faite par l'article. Une exécution pilote antérieure au gel existe et est mise en quarantaine plutôt que rapportée, puisque l'ordonnancement des gates d'origine de la spécification imposait d'exécuter l'ordonnanceur avant le gel, ce qui exposait nécessairement des chiffres hors échantillon. L'ordonnancement a été corrigé pour que chaque gate dépendant de l'ordonnanceur tourne désormais sur la seule période en échantillon.
freeze f575878c86937ab63067aeb98c9b8fb7723e5ec4a0a64c4112cc6f1ce77d8d6b superseded 5ecb4866f65386ddb18358d991b2eb541a3111362a4afb5646caf9cca62eb3b9 superseded bd72f21f1180bb32e2fd17fed02b2a616ffe9f2dfe6f2e91a60385e67de04d39 specification 1f7687a381a256e3f4d6ec0502bc8069705c4d70d0f6b01527107a8f6c0744b0 universe 429fcc435cd8ab32ca98480e6d011ee590ccfd79541b2431af8489128c81a3e5 data manifest add2fc3c1fc460d0eb74ba08d14ae829593f779fcd6ab02dfe890e27e73ab0dd
Les graphiques de l'article sont dessinés à partir des tables de résultats publiées, qui sont les mêmes tables d'où viennent les chiffres du texte.
Demander le pipeline complet
Cette page suffit à vérifier à la main chaque chiffre de l'article, ou à reconstruire une étude équivalente à partir de l'archive publique Binance, même si les scripts de production, la construction des données, l'ordonnanceur numba, l'analyse et la construction des figures ne sont pas joints. Écrivez à daniel@daru.finance et je vous les envoie.

