Maturité de marché et alpha
Cette page donne la recette exacte derrière chaque chiffre de l'article : données, univers, bibliothèque de règles, moteur d'exécution, coûts, la grandeur mesurée appariée, le gel et les tables descriptives. Les extraits sont de courtes illustrations de la logique plutôt que le code de production.
Données
Binance : Chaque série provient de l'archive de données publique de Binance (data.binance.vision), futures margés en USD, sans identifiants, et couvre par contrat :
- les klines mensuelles à 1 heure, la série de prix du moteur et de la grandeur mesurée ;
- les klines mensuelles à 1 minute, réduites dès leur arrivée à une ligne par heure puis écartées ;
- les fichiers mensuels de taux de funding, une ligne par paiement, portant la colonne d'intervalle de règlement propre à l'archive ;
- les fichiers quotidiens
bookTicker(meilleure limite à l'achat et à la vente), publiés du 16 mai 2023 au 30 mars 2024 ; - les fichiers quotidiens
bookDepth(notionnel cumulé posé à ±1% à ±5% du mid, environ un instantané toutes les 30 secondes), utilisés du 1er janvier 2023 au 9 août 2026.
Recensement : La liste des contrats est reconstruite à partir de l'archive plutôt que reprise de la plateforme en ligne. Chaque symbole ayant un jour figuré sous le préfixe des klines mensuelles est énuméré, ce qui récupère les contrats retirés ensuite par la plateforme : 986 symboles, dont 12 n'ont jamais traité.
Pour chaque symbole, les fichiers mensuels sont parcourus en avant et en arrière jusqu'à la première et à la dernière barre au volume traité positif. L'existence d'un fichier ne prouve pas qu'il y a eu trading, donc les bornes viennent du volume et jamais du premier ou du dernier fichier, après quoi les 832 symboles traités et cotés en USDT constituent l'étude.
Premier jour traité : L'horloge d'âge d'un contrat démarre à 00:00 UTC du premier jour où son perpétuel a enregistré un volume traité positif sur la plateforme. L'âge en jours est le nombre entier de jours depuis ce minuit, de sorte que toutes les barres d'un jour UTC partagent un même âge.
first_day_ms = first_positive_volume_bar_ms // DAY_MS * DAY_MS age_days = (bar_open_ms - first_day_ms) // DAY_MS # one clock, used everywhere
Comme le mois le plus ancien de l'archive est janvier 2020, 3 contrats dont la première barre archivée tombe dans ce mois ont une date de listing censurée à gauche, tous dans la cohorte de calibration. Le dernier jour traité de tout le recensement, le 31 juillet 2026, est la fin d'archive utilisée pour la survie.
Trajectoire intra-barre : Chaque mois de barres à 1 minute se réduit à une ligne par heure : le plus haut et le plus bas de l'heure, la minute où chacun s'est imprimé, un indicateur high_first (la minute du plus haut précède celle du plus bas) et un indicateur ambiguous (les deux dans la même minute).
Bybit : Les klines horaires de 802 fichiers de perpétuels linéaires USDT ont été gelées en instantané à 22:23:09 UTC le 21 septembre 2026, afin que l'entrée ne puisse plus changer après le scellement. Le funding provient du point d'accès public v5 d'historique de funding de Bybit, paginé à rebours depuis la dernière barre de chaque contrat jusqu'à la première.
Les carnets d'ordres proviennent de l'archive publique de carnets de Bybit (fichiers à 500 niveaux, 200 niveaux à partir de 2025), qui commence en 2023. Le premier jour traité d'un contrat Bybit est le jour UTC de sa première barre horaire au volume supérieur à zéro.
Univers
Cohortes Binance : La séparation se fait sur le premier jour traité.
| Cohorte | Listés | Contrats | Usage |
|---|---|---|---|
| Calibration | le 31 décembre 2021 ou avant | 140 | choisit la bibliothèque de règles, rien d'autre |
| Preuve | à partir du 1er janvier 2022 | 692 | la grandeur mesurée Binance (échantillon de développement) |
Sur les 692 contrats de preuve, 108 ne traitent plus. La grandeur mesurée Binance tourne sur les 692 ; 688 disposent des 96 barres horaires dont elle a besoin (une fenêtre de volatilité de 48 barres plus 48 barres) et 4 non. Sa référence mature est tirée des mêmes 692, un contrat de preuve y entrant chaque jour où il a plus de 365 jours.
Univers Bybit : L'échantillon confirmatoire est constitué de perpétuels linéaires USDT de Bybit traités pour la première fois du 1er janvier 2022 au 31 juillet 2026, une fenêtre de listing qui contient 620 contrats, en gardant ceux dont la trajectoire de prix jeune n'est pas dans les données Binance. Les tokens sont appariés à Binance par identité plutôt que par symbole, parce que les deux plateformes cotent souvent le même token sous des préfixes multiplicateurs différents :
- Retirer le suffixe
USDTainsi que tout préfixe1Mou puissance de dix (10,100,1000, ...) pour obtenir le nom du token. - Une correspondance de nom obtenue en retirant un préfixe est confirmée sur une heure commune, où le rapport des ouvertures des deux plateformes doit se situer à moins de 15% d'une puissance de dix ; une correspondance de préfixe qui échoue est traitée comme un token différent.
- Un symbole identique compte toujours comme le même token même si le contrôle de prix échoue, puisque le déclarer intact serait la direction risquée, tandis qu'une correspondance de nom sans heure commune à contrôler compte elle aussi.
def token(sym): # "1000000BABYDOGEUSDT" -> "BABYDOGE"
return re.sub(r"^(1M|10+)(?=[A-Z])", "", sym[:-4])
def same_token(bybit_open, binance_open):
r = bybit_open / binance_open
k = round(math.log10(r))
return abs(r / 10**k - 1) < 0.15 # 1000TAG vs TAG passes at 996.40La correspondance par préfixe a confirmé 5 paires (1000000BABYDOGE/1MBABYDOGE, 10000SATS/1000SATS, 1000TAG/TAG, 1000TOSHI/TOSHI, 1000TURBO/TURBO). Parmi les symboles identiques, 4 ont échoué au contrôle de prix et ont été conservés comme le même token (HNT, ON, ORBS, SNT), et 7 autres correspondances de nom n'avaient aucune heure commune et ont également été conservées comme le même token.
Chaque contrat tombe ensuite dans un groupe.
| Groupe | Règle | Contrats |
|---|---|---|
| Bybit seulement | le token n'a jamais eu de perpétuel USDT sur Binance | 79 |
| Bybit en premier | Binance a listé le token au moins 30 jours après Bybit | 77 |
| Recouvrement | tout le reste | 464, exclus |
Pour le groupe Bybit en premier, les jours jeunes s'arrêtent la veille du listing Binance, toute position encore ouverte étant fermée à la dernière barre de ce jour-là. Cela donne 156 contrats et 4,524 contrat-jours calendaires jeunes aux âges 2 à 30, dont 4,284 (à partir de l'âge 3) ont une estimation de volatilité et 3,295 sont appariés.
Référence mature Bybit : Les contrats USDT de Bybit traités pour la première fois à partir du 1er janvier 2022 entrent les jours où ils ont plus de 365 jours, ce qui correspond à la fenêtre de listing de la jambe mature Binance. Un contrat Bybit en premier rejoint la jambe mature une fois passés 365 jours, comme n'importe quel autre. Sur les 802 fichiers, 715 contrats se chargent dans la grandeur mesurée ; 80 sont écartés comme listés avant 2022 et hors univers ; 7 ont moins de 96 barres.
Règles
Vocabulaire : 34 variantes de règles composent le vocabulaire, 11 transformations et 23 portes de confluence. Une règle de transformation calcule le z-score d'une transformation de la clôture sur une fenêtre glissante de L barres et traite le signe lorsque le z-score franchit un seuil. Une règle de confluence calcule de la même façon le z-score de la clôture brute et ne conserve le signal que sur les barres où la porte est vraie.
def desired_state(kind, name, L, thr, o, h, l, c):
if kind == "transform":
z = rolling_z(transform(name, c, L), L)
gate = True
else:
z = rolling_z(c, L)
gate = confluence_gate(name, o, h, l, c, L)
d = np.where(z > thr, 1, np.where(z < -thr, -1, 0))
return np.where(gate, d, 0) # 0 = no instruction this barTransformations : base (la clôture), slope, normalized_price, roc, bias, volZ, accel, disFromMedian, quant_stretch, rank_resid, fold_dev. Portes : RSIge40, RSIge50, Pge0.7, Pge0.8, BW_filter, pi, vr, kurtosis, skew, skew0.75, atr_pct, atr_pct0.8, burstfreq, TinyBody, NoNewLowGreen, RangeSpike, YesterdayPeak, DeadFlat10, InsideBar, SameDirection, TopOfRange, VolContraction, EMAHug.
Grille : Chaque variante est croisée avec un lookback L dans {4, 8, 12} barres, un seuil dans {0.5, 1.0, 1.5}, une détention maximale dans {12, 48, 168} barres, une distance de stop dans {1, 2, 4} × ATR et un rapport gain/risque dans {1, 1.5, 2, 3, 5}, ce qui donne 34 × 405 = 13,770 configurations. Cribler chaque configuration sur les 140 contrats de calibration porte la recherche à 13,770 × 140 = 1,927,800 exécutions du moteur au total. Les lookbacks s'arrêtent à 12 barres pour que chaque règle puisse être amorcée avant le plus jeune jour mesuré.
Criblage : Le criblage tourne sur les contrats comptant au moins 2,000 barres horaires traitées, sous la convention intra-barre conservatrice (plus bas), en facturant les 5 bp de frais plus les 7.4 bp de coût d'exécution mesuré d'un contrat Binance mature sur chaque exécution. Les jours jeunes ne l'atteignent jamais, puisque les signaux de toute barre où le contrat a moins de 31 jours sont mis à zéro avant la notation. Une configuration est éligible si au moins 35 contrats (un quart de la cohorte) montrent au moins 24 trades clôturés sur au moins 6 semaines calendaires distinctes, et si au moins 35 montrent un premier signal dans les 40 premières barres du contrat.
signals[age_days < 31] = 0 # selection never sees young days eligible = (n_contracts_active >= 35) & (n_contracts_warm >= 35) ranked = sorted(eligible_rows, key=lambda r: -r.pooled_net) library = first_per_variant(ranked, k=20) # one rule per variant
Sous ces filtres, 12,555 configurations sont éligibles, aucune avec un net agrégé positif après coûts sur la cohorte de calibration et 73 avec un brut positif. La bibliothèque est le top 20 par net de calibration agrégé, au plus une par variante, sans filtre de positivité, parce que la grandeur mesurée est une différence jeunes moins matures qui reste définie quel que soit le signe de l'une ou l'autre jambe.
Bibliothèque gelée :
| Variante | Type | L | Seuil | Stop (× ATR) | Gain/risque | Détention max. (barres) |
|---|---|---|---|---|---|---|
| BW_filter | porte | 4 | 1.5 | 4 | 1.5 | 168 |
| kurtosis | porte | 12 | 0.5 | 4 | 5 | 168 |
| NoNewLowGreen | porte | 12 | 1.5 | 4 | 5 | 168 |
| pi | porte | 12 | 1.5 | 4 | 5 | 168 |
| Pge0.8 | porte | 12 | 1.5 | 4 | 5 | 168 |
| Pge0.7 | porte | 12 | 1.5 | 4 | 5 | 168 |
| TinyBody | porte | 12 | 1.5 | 4 | 3 | 168 |
| skew0.75 | porte | 4 | 1.5 | 1 | 5 | 168 |
| InsideBar | porte | 4 | 1.5 | 4 | 5 | 168 |
| DeadFlat10 | porte | 4 | 1.5 | 4 | 5 | 168 |
| atr_pct0.8 | porte | 12 | 1.5 | 4 | 5 | 168 |
| vr | porte | 12 | 1.5 | 4 | 5 | 168 |
| atr_pct | porte | 12 | 1.5 | 4 | 5 | 168 |
| YesterdayPeak | porte | 12 | 1.5 | 4 | 5 | 168 |
| RangeSpike | porte | 8 | 1.5 | 4 | 3 | 168 |
| bias | transformation | 12 | 1.5 | 4 | 5 | 168 |
| skew | porte | 4 | 1.5 | 4 | 5 | 168 |
| RSIge50 | porte | 12 | 1.5 | 4 | 5 | 48 |
| EMAHug | porte | 4 | 1.5 | 4 | 3 | 168 |
| SameDirection | porte | 4 | 1.5 | 4 | 5 | 168 |
Chaque porte de la bibliothèque teste des barres déjà clôturées : BW_filter, le corps de la barre précédente dépasse 70% de son range ; TinyBody, le corps de la barre précédente est sous 10% de son range ; pi, le corps de la barre précédente divisé par l'ATR à L barres est à moins de 0.05 de π/4 ; kurtosis, un kurtosis glissant des rendements sur L barres d'au moins 5 ; skew et skew0.75, une asymétrie glissante des rendements en valeur absolue d'au moins 0.5 et 0.75 ; Pge0.7 et Pge0.8, la clôture précédente se situe dans les 30% ou 20% supérieurs de son range glissant sur L barres ; atr_pct et atr_pct0.8, le même test sur l'ATR à L barres à l'intérieur de son propre range glissant ; vr, un ratio de variance 2 barres sur 1 barre d'au moins 1 ; NoNewLowGreen, la barre précédente a clôturé en hausse et son plus bas est au-dessus de la clôture 5 barres plus tôt ; RangeSpike, le range de la barre précédente dépasse 1.3 fois sa moyenne sur 20 barres ; YesterdayPeak, la clôture 2 barres en arrière est la plus haute des 3 dernières ; DeadFlat10, la clôture a bougé de moins de 0.5% sur les 10 barres jusqu'à la clôture précédente ; InsideBar, la barre 2 en arrière se situe à l'intérieur du range de la barre précédente ; SameDirection, les 2 dernières barres ont clôturé dans le même sens ; RSIge50, le RSI 14 barres précédent vaut au moins 50 ; EMAHug, la clôture précédente est à moins de 0.92 du range de la barre précédente de sa moyenne sur 21 barres. L'unique transformation, bias, est une moyenne exponentielle sur L barres de la pente sur L barres de la clôture.
Le SHA-256 du fichier de bibliothèque est 3b4c9b75e5ce3ddb66ba21c1d4fcdeda3ad8a6513881857295b0cbdfae528556.
Moteur
Une position tourne par règle et par contrat, à notionnel fixe et sans capitalisation. La taille de position vaut une unité de notionnel à l'entrée, de sorte que chaque chiffre de P&L est une fraction du notionnel d'entrée.
- Entrée : Un signal calculé sur la barre achevée
tentre à l'ouverture de la barret+1, et un signal de même sens alors qu'une position est ouverte est ignoré. Un signal opposé ferme à l'ouverture suivante et se retourne à ce moment-là, payant 2 exécutions, tandis qu'un zéro n'est pas une instruction de sortie. - Barrières : Le stop et l'objectif sont fixés à l'entrée à partir de l'ATR de Wilder(14) jusqu'à la barre précédant l'entrée, le stop à
stop × ATRdu prix d'entrée et l'objectif àstop × ATR × gain/risque; ni l'un ni l'autre ne bouge ensuite. - Détention maximale : La position sort à l'ouverture de la barre dès que
Hbarres se sont écoulées depuis l'entrée. - Gaps : Une ouverture au-delà du stop est exécutée à cette ouverture. Une ouverture au-delà de l'objectif est exécutée à l'objectif, jamais mieux.
- Barre d'entrée : L'entrée se fait à l'ouverture, donc tout le range de la barre vient après elle et les deux barrières sont actives sur la barre d'entrée.
- Une même barre touche les deux barrières : Sous l'ordre réel (Binance seulement), la trajectoire à la minute tranche, avec l'objectif en premier si le plus haut est venu avant le plus bas pour un long, le stop en premier sinon, et le miroir pour un short ; une barre dont les extrêmes se sont imprimés dans la même minute, ou dont l'enregistrement de trajectoire ne reproduit pas le plus haut et le plus bas de la barre, est résolue comme un stop. Le mode conservateur résout toute barre de ce type comme le stop, long comme short. Bybit n'a pas de trajectoire à la minute et tourne donc en conservateur seulement, tandis que Binance tourne dans les deux modes.
- Ré-entrée après une exécution sur barrière : Si la règle veut encore une position après l'exécution d'un stop ou d'un objectif à l'intérieur d'une barre, la nouvelle position est exécutée au prix de la barrière, le prix auquel le carnet est réellement à plat, plutôt qu'à l'ouverture de cette barre. Sur cette barre, seul le nouveau stop peut se déclencher, jugé sur l'extrême adverse de la barre, tandis que le nouvel objectif est actif à partir de la barre suivante, parce que la partie du range antérieure à l'exécution ne peut pas être attribuée à la nouvelle position.
- Ordre à l'intérieur d'une barre : funding, barrières sur gap, retournement, détention maximale, nouvelle entrée, barrières intra-barre, fermeture forcée.
- Funding : Les paiements observés sont facturés à leur véritable heure de règlement, chacun tombant sur la première barre ouvrant à son heure de règlement ou après, et facturé sur la position détenue immédiatement avant le règlement comme
direction × taux × quantité × prix, valorisé à l'ouverture de cette barre. Un funding positif débite les longs et crédite les shorts ; une position ouverte sur la barre de règlement ne paie ni ne reçoit, tandis qu'une position portée jusque-là si.
Aucun signal n'utilise jamais le funding. Les taux Binance proviennent des fichiers de funding de l'archive avec leur intervalle par paiement, et les taux Bybit du point d'accès d'historique de funding.
for i in range(1, n):
if pos: funding_cost += pos * rate[i] * qty * open_[i] # 1. funding
if pos:
hit = barrier(pos, open_[i], high[i], low[i], hi_first[i], ambiguous[i])
if hit: close(i, hit.price); reentry_px = hit.price # 2. gap / intrabar barrier
elif want[i-1] == -pos: close(i, open_[i]) # 3. reversal
elif i - entry_i >= max_hold: close(i, open_[i]) # 4. maximum hold
if not pos and want[i-1]:
enter(i, at=reentry_px if exited_this_bar else open_[i]) # 5. entry
check_barriers_on_entry_bar(i) # 6. intrabar barriersChaque trade porte le P&L brut, les frais, le slippage et le funding dans des colonnes distinctes, avec gross - fee - slippage - funding = net vérifié sur chaque trade. Sur 7,234,659 trades Binance (ordre réel), 7,234,637 (conservateur) et 8,059,098 trades Bybit, aucun trade ne l'enfreint et le plus grand résidu vaut 0.0.
Sous la convention d'ordre réel, sur 7,555,948 barres horaires Binance, 7,525,107 portent un ordre à la minute résolu, 24,391 ont leurs deux extrêmes dans la même minute et 6,450 ont un enregistrement de trajectoire qui ne correspond pas au plus haut et au plus bas de la barre, ces 2 derniers groupes étant résolus comme des stops.
Coûts
Chaque exécution paie des frais taker plus un coût d'exécution mesuré pour la tranche d'âge du contrat, à une taille de position enregistrée de 10,000 $. L'exécution d'entrée est facturée à la tranche de l'âge d'entrée et l'exécution de sortie à la tranche de l'âge de sortie, mise à l'échelle par le prix de sortie sur le prix d'entrée, afin que la facturation ne dépende pas de l'issue du trade. Une exécution à l'âge 0 ou 1, qui n'a pas de tranche, paie le coût 2-7 jours.
slip = (cost_bp[bucket(age_entry)] + cost_bp[bucket(age_exit)] * exit_px / entry_px) / 1e4 fee = fee_bp / 1e4 * (1 + exit_px / entry_px) net = gross - fee - slip - funding
Frais : Binance facture 5 bp par côté et Bybit 5.5 bp par côté, le palier taker de base.
Demi-spread Binance : Tiré de l'archive bookTicker, échantillonné sur une échelle d'âges fixe : âges 0, 1, 2, 3, 5, 7, 10, 14, 21, 30, 45, 60, 120, 200, 300, 400, 500, 700, 900 et 1,200 jours, pour chaque contrat USDT traitant à un moment quelconque à l'intérieur de la fenêtre de cotations, en gardant les échelons qui y tombent. Par contrat-jour, le demi-spread est la médiane sur les cotations de ce jour de (ask - bid) / (ask + bid) en points de base, en écartant les jours comptant moins de 100 cotations valides. La valeur de tranche est la médiane sur les contrat-jours, 1,425 contrat-jours issus de 257 contrats.
Impact sur Binance : Un seul échantillon de profondeur, sur un unique schéma d'échantillonnage, couvre tous les âges, donc les jambes jeune et mature proviennent du même panel. Pour chaque contrat, il prend des jours bookDepth aux âges 3, 7, 14, 28, 45, 75, 120, 160, 240 et 300, plus 8 jours régulièrement espacés de l'âge 366 (ou du 1er janvier 2023 si c'est plus tard) jusqu'au dernier jour du contrat, tous à l'intérieur de la fenêtre d'archive.
À l'intérieur de chaque jour, le notionnel cumulé posé de chaque bande est la médiane sur les instantanés de ce jour. Un achat de 10,000 $ est ensuite balayé le long de l'échelle des offres de 1% à 5% avec une densité uniforme à l'intérieur de chaque bande, et le coût est la distance moyenne au mid pondérée par le notionnel. Par tranche, la valeur est la médiane sur les jours de chaque contrat dans la tranche, puis la médiane sur les contrats.
def sweep_cost_bp(cum_notional, size=10_000, bands=(1, 2, 3, 4, 5)): # bands in % of mid
filled = paid = prev_n = prev_d = 0.0
for d, n in zip(bands, cum_notional):
take = min(size - filled, n - prev_n)
paid += take * (prev_d + (d - prev_d) * take / (n - prev_n) / 2) # uniform in band
filled += take; prev_n, prev_d = n, d
if filled >= size: break
return paid / size * 100 # % of mid -> bp| Tranche Binance | Demi-spread, bp | Contrat-jours (contrats) | Impact de 10,000 $, bp | Contrat-jours (contrats) | Coût par exécution, bp |
|---|---|---|---|---|---|
| 2-7j | 1.56 | 346 (88) | 4.56 | 1,273 (642) | 6.12 |
| 8-30j | 1.22 | 355 (94) | 3.98 | 1,266 (650) | 5.20 |
| 31-90j | 1.20 | 168 (89) | 4.80 | 1,160 (613) | 6.00 |
| 91-180j | 1.20 | 91 (91) | 5.69 | 1,022 (526) | 6.89 |
| 181-365j | 1.22 | 104 (64) | 7.39 | 848 (448) | 8.61 |
| plus de 365j | 1.18 | 189 (150) | 6.22 | 3,780 (477) | 7.40 |
Bybit : Les coûts sont échantillonnés dans l'archive publique de carnets de Bybit. Les jours jeunes couvrent chaque contrat de l'univers aux âges 3, 7, 14 et 28, là où cet âge est encore jeune (pour les contrats Bybit en premier, avant le listing Binance). Les jours matures proviennent de 60 contrats USDT de Bybit hors univers tirés avec la graine 3, 4 dates aléatoires chacun, à des âges supérieurs à 365 et du 1er juin 2023 au 31 juillet 2026, seuls ceux traités pour la première fois à partir de 2022 comptant pour la tranche mature.
Le fichier de chaque jour est rejoué depuis l'instantané et ses deltas, et toutes les 60 secondes de temps de plateforme le demi-spread et le coût d'un ordre au marché de 10,000 $ contre le mid sont enregistrés, ce coût étant la moyenne d'un achat et d'une vente parcourant le carnet. Par contrat-jour, la médiane de chacun est conservée, et par tranche la médiane sur les contrat-jours. L'impact est le coût total moins le demi-spread.
Sur les 788 contrat-jours échantillonnés, 706 existent dans l'archive.
| Tranche source Bybit | Contrat-jours (contrats) | Demi-spread, bp | Impact, bp | Coût par exécution, bp |
|---|---|---|---|---|
| 2-7j | 270 (135) | 3.81 | 25.20 | 29.01 |
| 8-30j | 272 (136) | 3.97 | 25.17 | 29.14 |
| plus de 365j | 164 (42) | 2.61 | 27.20 | 29.81 |
Comme l'échantillon mesure 3 tranches, les autres sont rattachées à la tranche mesurée la plus proche : 31-90j prend le coût 8-30j, tandis que 91-180j et 181-365j prennent le coût de plus de 365j. Toutes les constantes de coût des deux plateformes vivent dans un seul fichier, que la grandeur mesurée et le gel lisent l'une comme l'autre (SHA-256 eb0e6aa42c367840b8d59fd8ac59ec43200bd968b4514ef53f594a0cd434e4eb).
Grandeur mesurée
Unité : L'observation est le contrat-jour, un jour UTC où le contrat a au moins une barre horaire ; les âges 0 et 1 ne sont jamais des jours d'observation, bien que les trades ouverts alors contribuent encore aux jours pendant lesquels ils sont détenus à partir de l'âge 2. Le P&L brut, les frais, le slippage, le funding et le net de chaque trade sont répartis également sur les jours vivants de son jour d'entrée à son jour de sortie inclus. La valeur d'un contrat-jour est la somme, sur les 20 règles, de leurs parts de ce jour, divisée par 20, en points de base, les jours où aucune règle ne détient de position comptant pour zéro.
share = 1.0 / (exit_day_idx - entry_day_idx + 1) day_pnl[entry_day_idx : exit_day_idx + 1] += trade_pnl * share daily_bp = day_pnl.sum(over_rules) / 20 * 1e4 # fixed library size
Volatilité : La volatilité réalisée est l'écart type des 48 derniers rendements logarithmiques horaires, comptés en barres plutôt qu'en heures d'horloge. La valeur du jour d est lue sur la barre qui ouvre à 23:00 UTC la veille, la barre qui clôture exactement au début du jour d, de sorte que rien de l'intérieur du jour d n'atteint sa propre covariable.
Si cette barre manque, le jour n'a pas de volatilité et ne peut pas être apparié. Avec une fenêtre de 48 barres close avant le début du jour, l'âge 2 n'a pas d'estimation, donc le premier jour jeune avec estimation est l'âge 3.
Tranches : Les tranches de volatilité sont découpées sur le logarithme naturel de la volatilité réalisée horaire à −7.0, −6.5, −6.0, −5.5, −5.0, −4.5, −4.25, −4.0, −3.75, −3.5, −3.25 et −3.0, ce qui donne 13 tranches à extrémités ouvertes. Les tranches d'âge vont de 2-7, 8-30, 31-90, 91-180 et 181-365 jours, la maturité commençant à 366 jours et au-delà, et la fenêtre jeune principale couvrant les âges 2 à 30.
Appariement : Les contrat-jours matures sont d'abord moyennés à l'intérieur de chaque cellule (jour calendaire, tranche de volatilité), afin qu'une cellule chargée ne pèse pas plus qu'une cellule mince. Chaque contrat-jour jeune est différencié de la moyenne mature de sa propre cellule. Un jour jeune dont la cellule n'a aucun contrat mature est compté comme non apparié et n'est jamais comparé à une autre cellule.
La grandeur mesurée est la moyenne des différences appariées. Un contrôle de comptage de lignes vérifie que chaque observation jeune ressort comme exactement une ligne appariée ou une ligne non appariée.
ref = mature.groupby(["day", "vol_bucket"]).net.mean() diffs = young.join(ref, on=["day", "vol_bucket"], how="left", rsuffix="_ref") matched = diffs.dropna(subset=["net_ref"]) estimand = (matched.net - matched.net_ref).mean()
| Échantillon | Jours jeunes appariés | Non appariés | Part non appariée |
|---|---|---|---|
| Binance, âges 2-30 | 15,244 | 3,573 | 19.0% |
| Bybit, âges 2-30 | 3,295 | 989 | 23.1% |
Pour la décomposition, le même appariement tourne séparément sur le brut, les frais, le slippage et le funding, sur chaque tranche d'âge (seulement lorsqu'elle compte au moins 30 lignes jeunes) et sur les 3 bras. Tous les bras viennent d'un seul flux de signaux : le long-short traite les deux signes, le long seul met les signaux courts à plat en cash et le short seul met les signaux longs à plat.
Inférence : Un bootstrap par percentiles groupé par semaine sur les différences appariées utilise 2,000 réplications et la graine 20260810. Les semaines sont des blocs de 7 jours comptés depuis l'époque Unix, si bien qu'elles vont du jeudi au mercredi UTC, et chaque observation jeune à l'intérieur d'un bloc tiré voyage avec lui.
Les moyennes des cellules matures sont maintenues à leurs valeurs sur échantillon complet, le rééchantillonnage ne portant que sur les différences jeunes. L'intervalle à 95% est donné par les 2.5e et 97.5e percentiles des moyennes de réplication.
rng = np.random.default_rng(20260810) block = matched_day // 7 # epoch-day blocks groups = [diff[block == b] for b in np.unique(block)] draws = [np.concatenate(rng.choice(groups, len(groups))).mean() for _ in range(2000)] lo, hi = np.percentile(draws, [2.5, 97.5])
Lectures : Binance tourne sous les deux conventions intra-barre et constitue l'échantillon de développement. Bybit tourne une seule fois sous la convention conservatrice et constitue le test confirmatoire. La règle de publication a été arrêtée avant l'exécution sur Bybit : un intervalle qui exclut zéro avec le même signe que l'estimation Binance est un effet d'âge répliqué, et tout le reste est un résultat nul rapporté avec sa largeur d'intervalle mesurée.
Gel
Avant l'exécution de la grandeur mesurée Bybit, un paquet a été scellé contenant les empreintes SHA-256 de :
- l'implémentation, dont la grandeur mesurée, le moteur, le moteur de criblage, le vocabulaire de règles, le criblage de bibliothèque, le module de constantes, le client d'archive, le constructeur de coûts, le fichier de coûts, le constructeur d'univers Bybit, le pilote d'analyse et le gel lui-même ;
- les artefacts : la bibliothèque gelée, le criblage complet de 13,770 lignes, l'univers Bybit, l'échantillon de coûts Bybit, le recensement et la liste complète des symboles de l'archive ;
- chaque série de données d'entrée, fichier par fichier : chaque série de panel Binance pour les 832 contrats (klines horaires, prix mark, indice de prime, funding, trajectoire à la minute, profondeur et l'échantillon de profondeur), les échantillons de spread coté, les 986 enregistrements de recensement, ainsi que 802 fichiers de klines Bybit, 802 fichiers de funding Bybit et 872 fichiers d'échantillon de carnet Bybit ;
- les constantes enregistrées telles que lues dans les modules qui les exécutent : tranches d'âge et de volatilité, fenêtre de volatilité, taille de bibliothèque, taille de position, graine du bootstrap et nombre de réplications, la grille, les seuils d'éligibilité et les tables de coûts.
Le scellement s'est achevé à 22:26:51 UTC le 21 septembre 2026 sous l'empreinte de paquet 23f5bf876015564577d880fe86f7a5ac22b370d05f42713a7ef4fb846c8d31e1. Une copie a été déposée à 22:28:34 UTC sur un stockage objet hors machine, qui enregistre une heure de dépôt que la machine locale ne peut pas modifier, et les résultats Bybit ont été générés à 22:32:53 UTC, les 2 résultats Binance suivant à 22:32:58 UTC.
Vérification : La grandeur mesurée recalcule l'empreinte de tout le contenu du paquet avant de tourner et refuse de démarrer si un élément a bougé, ou si l'heure de scellement n'est pas antérieure à l'exécution. Une exécution Bybit refuse également une bibliothèque autre que celle scellée, un sous-ensemble de symboles, un emplacement de sortie autre que celui scellé et tout fichier de sortie Bybit antérieur au scellement. Chaque fichier de résultats enregistre ensuite l'empreinte du paquet et l'heure de scellement.
ok, moved, bundle = verify(bundle_path) # re-hash code, data, artifacts, constants
if not ok or bundle["created_utc"] >= now_utc():
raise SystemExit("freeze does not verify; run refused")
results["seal"] = {"bundle_sha256": bundle["bundle_sha256"], "sealed_utc": bundle["created_utc"]}Tables descriptives
Chaque table de la première moitié de l'article est calculée comme suit, l'âge étant toujours compté en jours entiers depuis le premier jour traité du recensement.
Comment un contrat change en vieillissant : Les 692 contrats de preuve Binance entrent tous sans minimum d'historique, dont 691 contribuent. Par contrat et par tranche d'âge (0-1, 2-7, 8-30, 31-90, 91-180, 181-365, plus de 365 jours), les colonnes utilisent les barres horaires de ce contrat au volume supérieur à zéro et exigent au moins 24 barres dans la tranche :
- volatilité : écart type des rendements logarithmiques horaires multiplié par √8,760 ;
- corrélation au Bitcoin : corrélation de Pearson des rendements logarithmiques horaires avec BTCUSDT sur les horodatages communs ;
- volume face à son propre niveau mature : le volume horaire médian en quote de la tranche divisé par la médiane du même contrat au-delà de 365 jours, uniquement pour les 352 contrats ayant atteint cet âge.
Le funding est calculé par paiement plutôt que par barre. Chaque paiement est remis à l'échelle en équivalent 8 heures comme taux × 8 / intervalle (zéros compris) et assigné à la tranche de l'âge du paiement. Par contrat, la moyenne et l'écart type sont pris sur au moins 3 paiements, après quoi chaque colonne est la médiane sur les contrats.
r8 = rate * 8.0 / interval_hours # 1h, 2h, 4h and 8h payments comparable
per_contract = payments.groupby("bucket").r8.agg(["mean", "std"]) # needs >= 3 payments
table = per_contract_all.groupby("bucket").median() # median across contractsCalendrier de règlement : Les 832 contrats USDT sont couverts à partir de la colonne d'intervalle par paiement de l'archive, l'intervalle de départ étant pris comme l'intervalle du premier paiement d'un contrat, puis tabulé par année de listing. « Ne règle jamais à huit heures » et « change d'intervalle au moins une fois » sont calculés sur l'historique complet des paiements de chaque contrat. Les parts de paiements par âge regroupent tous les paiements de la tranche d'âge (0-7 jours, 8-30 jours et ainsi de suite) sur l'ensemble des contrats.
Spreads cotés par âge : Ils utilisent l'échantillon de demi-spread décrit sous Coûts, avec des médianes de tranche sur les contrat-jours. La table intra-contrat utilise les 86 contrats listés à l'intérieur de la fenêtre de cotations : par contrat, le demi-spread médian de chaque tranche ; le rapport de sa valeur à 0-1 jour à sa valeur dans la tranche plus tardive ; puis la médiane de ces rapports et la part au-dessus de un.
Profondeur et impact par âge : Ils utilisent l'échantillon de profondeur et le balayage décrits sous Coûts, côté achat, 10,000 $, la première ligne regroupant les âges 3 et 7. La colonne intra-contrat divise l'impact médian de chaque contrat dans la tranche jeune par sa propre médiane au-delà de 365 jours pour les contrats disposant des deux, puis rapporte le rapport médian et la part en dessous de un.
Coûts d'exécution Bybit : L'échantillon de carnets décrit sous Coûts, 706 contrat-jours.
Test d'Abdi-Ranaldo : Les 86 contrats listés à l'intérieur de la fenêtre de cotations fournissent les âges 0 à 60 de l'échelle fixe, soit 987 contrat-jours. L'estimation par contrat-jour est la médiane, sur les barres horaires de ce jour, de la valeur d'Abdi-Ranaldo (valeurs négatives ramenées à zéro), sur les seules barres au volume supérieur à zéro. La valeur réelle à laquelle elle est comparée est le demi-spread coté mesuré du même jour.
Les cellules sont des médianes sur les paires de chaque intervalle d'âge et la colonne de rapport est la médiane des rapports par paire, tandis que la corrélation de rang est un Spearman sur les 987 paires (+0.0349). L'estimateur renvoie le spread effectif complet, ce qui explique environ la moitié de l'écart de niveau, et le diviser par deux ne change pas la corrélation de rang.
Survie : La fin d'archive est le dernier jour traité du recensement (31 juillet 2026), et un contrat est éligible au repère d'âge k s'il a été listé au moins k jours avant la fin d'archive. Il a atteint le repère s'il a traité pendant au moins k jours, ou s'il traitait encore dans les 7 jours précédant la fin d'archive. La part est le nombre atteint sur le nombre éligible, calculée séparément pour les contrats listés à partir de 2022 (692) et jusqu'en 2021 (140).
eligible = archive_end - first_traded >= k_days reached = (last_traded - first_traded >= k_days) | (last_traded >= archive_end - 7_days) share = reached[eligible].mean()
Demander le pipeline complet
Le pipeline complet (recensement, constructeur de panels, échantillonneurs de carnets, criblage, moteur, grandeur mesurée, gel et scripts descriptifs) n'est pas distribué depuis cette page. Pour le demander, écrivez à daniel@daru.finance.

