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, y compris ce que change l'addendum et le hash sous lequel chaque exécution est gelée.
Données et univers
Crypto. Dix contrats perpétuels Binance marginés en USDT, sélectionnés le 31 décembre 2022 par volume notionnel quotidien médian d'octobre à décembre 2022 : BTC, ETH, DOGE, XRP, BNB, SOL, MATIC, LTC, CHZ et ETC. Les klines et l'historique complet des taux de funding proviennent des archives publiques de Binance.
Actions. Dix actions américaines à grande capitalisation sélectionnées de la même manière : TSLA, AAPL, NVDA, AMZN, MSFT, AMD, META, GOOGL, NFLX et XOM. La source est constituée de données de transactions et de cotations à 1 minute éligibles NBBO, indexées 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 puissent pas corrompre les rendements.
Portée. Deux fréquences de barre, 15 minutes et 1 heure, donnant quatre cellules : crypto:15m, crypto:1h, equity:15m et equity:1h. L'historique d'entrée commence le 1er janvier 2021 et l'évaluation hors échantillon court du 1er janvier 2023 au 31 juillet 2026.
Chaque fichier d'entrée est haché dans un manifeste de données que l'exécution vérifie avant de toucher la moindre barre. Le funding, les spreads, les frais, le slippage, les horodatages des plus hauts et des plus bas, les champs d'ordre de parcours, les rendements futurs et les résultats de trades sont des métadonnées d'exécution, et ils sont interdits dans les définitions de signaux de base comme dans les entrées d'état de la courbe d'équité.
La bibliothèque de stratégies, et sa calibration
Quatre-vingts stratégies mécaniques réparties sur huit familles, toutes symétriques en signe. La bibliothèque a été calibrée une seule fois, sur janvier 2021 à décembre 2022 uniquement, pour que les durées de détention ressemblent à celles d'un trader discrétionnaire intraday et swing : la plupart des positions se ferment en quelques heures, avec une queue atteignant environ deux semaines. La calibration lit le nombre de candidats, les durées de détention et la couverture des instruments, et ne lit aucun rendement, Sharpe, taux de réussite ni drawdown d'aucune sorte. Elle a convergé en trois tours avec cinq remplacements, tous dans la famille retournement RSI, chacun pour couverture insuffisante.
Un candidat ne se déclenche que lorsque la vue directionnelle d'une stratégie s'inverse réellement, de long à short ou de short à long. Une barre neutre n'est pas un signal et ne réinitialise pas la vue :
# The candidate grid: genuine inversions only, never a re-entry from neutral. view = raw_view.replace(0, method="ffill") # neutral holds the previous view signal = view != view.shift(1) # a transition, not a level
Cela compte plus qu'il n'y paraît. Traiter chaque retour depuis le neutre comme un nouveau signal fait qu'une règle dont la condition oscille autour d'une bande émet un candidat à chaque fois, et ne compter que les véritables inversions a divisé le nombre de candidats par environ trois.
Ces transitions de signal constituent aussi la grille de candidats commune sur laquelle chaque politique est comparée. À chaque transition, chaque politique ferme ce qu'elle détient et ouvre une position dans la direction nouvellement signalée, si bien que les bornes et le nombre de trades sont identiques d'une politique à l'autre par construction, et que la seule chose qui diffère est la façon dont chaque trade s'est terminé et à quel prix.
Les barrières, et la référence que les traitements doivent battre
La largeur des barrières et le ratio gain/risque sont des paramètres de stratégie, pas des constantes universelles, donc ils sont calibrés en échantillon pour chaque couple stratégie-fenêtre sur l'objectif enregistré, et comptés dans le même budget de recherche que les overlays.
stop in {3, 6} ATR x reward-to-risk in {1.5, 3} = 4 base configurationsCela donne des couples objectif et stop de (4.5, 3), (9, 3), (9, 6) et (18, 6) ATR. L'ATR est l'ATR de Wilder(14) calculé jusqu'à la barre de signal complétée puis figé pour toute la durée du trade, et une unité de risque vaut un mouvement d'un ATR à l'entrée, à la taille de référence. Calibrer la référence de cette façon relève la barre que les traitements doivent franchir : la comparaison honnête se fait contre une gestion statique calibrée, pas contre une référence universelle à deux pour un, facile à améliorer.
Variables d'état de la courbe d'équité
Huit variables orientées, chacune définie pour qu'une valeur plus grande signifie toujours une meilleure performance récente : le résultat net cumulé sur les 10 trades précédents ; le même sur 50 ; l'équité moins sa moyenne mobile sur 20 trades, divisée par l'écart-type des 20 incréments précédents ; le drawdown maximal sur les 50 derniers trades clôturés, stocké comme valeur non positive ; la longueur signée de la série en cours ; le taux de réussite sur 20 trades ; le taux de réussite sur 50 ; et le résultat net moyen sur 20 trades.
state = {
"cum_r_10": net_r.rolling(10).sum(),
"cum_r_50": net_r.rolling(50).sum(),
"equity_vs_ma": (equity - equity.rolling(20).mean()) / net_r.rolling(20).std(),
"max_dd_50": (equity - equity.rolling(50).max()).rolling(50).min(), # <= 0
"streak": signed_streak(net_r > 0),
"win_rate_20": (net_r > 0).rolling(20).mean(),
"win_rate_50": (net_r > 0).rolling(50).mean(),
"mean_r_20": net_r.rolling(20).mean(),
}Une neuvième candidate, le drawdown courant face à un plus haut glissant de tous les temps, a été retirée pour des raisons structurelles avant l'exécution. Pour une stratégie à espérance négative, elle n'est pas bornée et dérive de façon monotone vers le bas, si bien que les seuils ajustés en échantillon sont dépassés en permanence hors échantillon : dans un exemple mesuré, toute la plage hors échantillon est tombée sous le plus bas point de coupure en échantillon et le filtre n'a exécuté aucun trade. C'est un défaut de stationnarité, observable sans référence à la rentabilité, et c'est pourquoi le drawdown fenêtré sur 50 trades est utilisé à la place.
Tant que le lookback d'une variable n'est pas complet, la gestion reste à la référence sélectionnée et la trace de décision enregistre insufficient_history. Il existe une courbe d'équité par stratégie de base, marché, timeframe et politique, et les dix instruments d'une cellule la partagent.
La bibliothèque de gestion et le budget de recherche
Les actions d'overlay sont des multiplicateurs de la base sélectionnée, si bien qu'une famille garde son sens pendant que la base flotte. La réponse mean-reversion inverse la correspondance entre l'état et l'action.
| État | TP seul | SL seul | TP + SL | Taille de position | Filtre de trade |
|---|---|---|---|---|---|
| 1 | 0.5x TP | 0.5x SL | 0.5x les deux | 0.5R | sauter |
| 2 | 0.75x TP | 0.75x SL | 0.75x les deux | 1R | sauter |
| 3 | 1x | 1x | 1x | 1R | prendre |
| 4 | 1.5x TP | 1.5x SL | 1.5x les deux | 1.5R | prendre |
| 5 | 2x TP | 2x SL | 2x les deux | 2R | prendre |
Huit variables d'état multipliées par deux sens donnent 16 overlays par famille, et 16 overlays multipliés par 4 configurations de base donnent 64 essais par famille et par fenêtre. Chaque famille reçoit exactement le même budget, et c'est ce que recherche égale veut dire ici : un même nombre de configurations tentées, pas un même nombre de réglages.
Les sources d'état sont choisies pour qu'aucune famille ne puisse fabriquer son propre état. Les familles barrières lisent leur propre résultat net réalisé ; le dimensionnement de position lit son propre résultat réalisé à taille unitaire, de sorte que le levier ne peut pas gonfler la variable qui le pilote ; le filtrage de trades lit un book fantôme de référence mis à jour en continu, de sorte que l'état off ne peut jamais être absorbant.
Walk-forward
Quatorze fenêtres trimestrielles hors échantillon complètes, de 2023Q1 à 2026Q2, plus une fenêtre partielle enregistrée couvrant juillet 2026 seul, qui est notée, signalée et déclarée partout où elle est incluse. L'échantillon d'entraînement est constitué des 24 mois calendaires précédents dans tous les cas, et les bornes de calendrier sont identiques d'un marché à l'autre.
for window in quarterly_windows(oos_start="2023-01-01", oos_end="2026-08-01"):
grid = candidate_grid(strategy, window) # no equity state, no OOS values
is_candidates = [c for c in grid if c.entry_ts < window.oos_start
and c.exit_ts < window.oos_start]
thresholds = state_thresholds(baseline_replay(is_candidates))
attempts = [simulate(cfg, is_candidates, thresholds) for cfg in family_grid] # all 64
persist(attempts) # including degenerate and inactive
winner = max(attempts, key=lambda a: (a.net_r_per_common_candidate,
a.executed_trades, -a.canonical_id))
winner.freeze() # config and thresholds fixed
score(winner, window.oos_slice) # OOS starts flat, state from IS replayLa sélection est chronologique, chaque tentative est persistée, y compris celles qui n'ont rien fait, et l'état hors échantillon de l'overlay sélectionné est initialisé à partir de son rejeu en échantillon complété.
Exécution intrabarre
Les panels préparés portent l'horodatage du plus haut et du plus bas définitifs de chaque barre parente, si bien que le parcours intrabarre est reconstruit comme ouverture, premier extrême enregistré, second extrême enregistré, clôture, les franchissements de barrière étant évalués le long de ces segments monotones.
Un gap qui traverse un stop sort à la première ouverture disponible, tandis qu'un gap qui traverse un objectif est exécuté à l'objectif, ce que fait un ordre limite en attente. Si une seule barrière est atteignable, elle s'exécute ; si les deux le sont, l'ordre des extrêmes enregistrés tranche ; et si cet ordre est inconnu, le stop s'exécute en premier, les trades qui dépendaient de cette convention étant comptés et leur contribution économique déclarée.
Modèle de coûts
| Exécution | Frais | Slippage | Spread |
|---|---|---|---|
| Entrée, quelle qu'en soit la raison | taker 5 bps | 2 bps | moitié du spread mesuré |
| Sortie : take-profit, ou gap traversant l'objectif | maker 2 bps | aucun | aucun |
| Sortie : stop-loss, ou gap traversant le stop | taker 5 bps | 2 bps | moitié du spread mesuré |
| Sortie : retournement sur signal inverse | taker 5 bps | 2 bps | moitié du spread mesuré |
Les actions paient une commission et provision de frais forfaitaire de 0.5 bps par exécution, sans distinction maker ou taker. Le funding crypto est facturé à chaque settlement traversé par une position ouverte, à son signe observé. De 2021 à 2026, le funding a été positif 74.5% du temps et s'est établi en moyenne à +0.87 bp par tranche de huit heures, soit environ 9.5% par an contre un book en permanence long, ce qui explique pourquoi il est rapporté décomposé par sens de trade plutôt que fondu dans un chiffre net unique.
Chaque ledger conserve le résultat brut de prix, le spread d'entrée et de sortie, les frais d'entrée et de sortie, le slippage d'entrée et de sortie, le funding signé et le résultat net comme colonnes séparées, et l'identité doit se réconcilier sous 1e-9 unité de risque :
net_r = (gross_price_r
- entry_spread_r - exit_spread_r
- entry_fee_r - exit_fee_r
- entry_slip_r - exit_slip_r
+ funding_r) # signed: debits longs when funding is positive
assert abs(net_r - ledger.net_r).max() < 1e-9Inférence
Le bootstrap apparié rééchantillonne des semaines calendaires entières du lundi au dimanche, en prenant ensemble tous les instruments et toutes les stratégies d'une même semaine, sur 10,000 rééchantillonnages avec une seed fixe. Le rééchantillonnage ligne par ligne, la moyenne des Sharpe par fenêtre et la multiplication du nombre de fenêtres par le nombre de stratégies sont tous interdits.
weeks = pd.Grouper(key="ts", freq="W-MON") blocks = [df for _, df in paired_deltas.groupby(weeks)] # whole weeks move together draws = [np.concatenate(rng.choice(blocks, len(blocks))).mean() for _ in range(10_000)] p = 2 * min((np.array(draws) <= 0).mean(), (np.array(draws) >= 0).mean()) q = benjamini_hochberg(p_values, alpha=0.05) # 20 contrasts in the declared family
La multiplicité est corrigée à l'intérieur de la famille déclarée de 2 marchés par 2 timeframes par 5 familles de gestion, soit 20 contrastes, échecs compris.
Le placebo par rotation d'actions reprend la même politique et fait tourner ses actions sur les mêmes candidats, sur 1,000 tirages à seed fixée, avec une correspondance d'exposition comme contrôle bloquant : le null permuté doit reproduire exactement le turnover, le nombre d'exécutions et l'exposition nominale d'action de l'overlay réel, de sorte que la seule chose détruite soit l'alignement entre l'état de la courbe d'équité et le trade.
La suite de perturbation exécute 20 tirages de suppression de candidats à 1% et 20 chocs d'exécution avec des coûts mis à l'échelle entre 0.75x et 1.25x, et exige qu'au moins 80% des tirages conservent le signe de l'effet, que l'accord médian des décisions reste au-dessus de 80%, et que la corrélation médiane des courbes d'équité reste au-dessus de 0.9.
Les huit conditions d'attribution
- le résultat net agrégé par candidat commun dépasse la référence
- l'espérance sur les trades exécutés s'améliore, une espérance non définie comptant comme un échec
- le résultat net par unité de risque brut nominal moyen ne se dégrade pas
- la différence appariée groupée par semaine a un q ajusté BH inférieur à 0.05
- taux de réussite sur fenêtres complètes supérieur à 50% et ratio gain/risque supérieur à 1
- la différence reste positive après suppression de la meilleure fenêtre hors échantillon
- le résultat bat le 95e centile du placebo apparié par rotation d'actions
- le contrôle de stabilité à la dépendance de parcours est franchi
La condition 4 a échoué dans les 20 comparaisons de l'étude et dans les 24 de l'addendum. La condition 2 est rédigée pour qu'une politique à turnover nul y échoue, parce qu'une telle politique peut battre une référence perdante sur la condition 1 en ne faisant rien du tout, et la suite d'analyse teste exactement ce cas.
Ce que change l'addendum
L'univers, les données, les timeframes, le calendrier walk-forward, la grille de barrières calibrable en échantillon, les huit variables d'état de la courbe d'équité, le budget de 64 essais, la règle de sélection, l'éligibilité, le bootstrap, le placebo, la suite de perturbation et les huit conditions d'attribution sont tous hérités sans changement. Quatre choses diffèrent.
Sorties
Une position ne se ferme que lorsque son take-profit ou son stop-loss s'exécute. Si aucun des deux ne s'est exécuté à la fin de l'historique de l'instrument, le trade est tronqué à la dernière barre observée et signalé, et le taux de troncature est une statistique à rapporter obligatoirement, car un taux élevé signifierait que les barrières ne se résolvent pas à l'intérieur de l'échantillon et que le protocole n'a pas livré le test équitable qu'il promet.
Largeurs de barrière
Supprimer la sortie sur retournement allonge les détentions, donc la grille de stops a été recalibrée par cellule avant l'exécution, en lisant uniquement la durée de détention et la résolution :
| Cellule | Stops (ATR) | Détention médiane sur les quatre bases | Détentions de deux semaines ou plus |
|---|---|---|---|
| crypto:15m | 3.0, 6.0 | 6.2h à 38.5h | 0.0% à 4.4% |
| equity:15m | 3.0, 6.0 | 6.0h à 28.9h | 0.6% à 24.3% |
| crypto:1h | 1.5, 3.0 | 6.5h à 38.0h | 0.0% à 3.8% |
| equity:1h | 1.5, 2.25 | 7.0h à 21.0h | 0.3% à 13.7% |
Quinze des seize combinaisons cellule et base se situent dans la bande convenue. L'exception est equity:15m à sa base la plus large, rapportée dans les limites de la note plutôt que retirée.
Occupation
Chaque branche tranche le cas d'un signal qui arrive alors qu'une position est ouverte, et les deux sont notées sur le même dénominateur, à savoir chaque transition de signal de la fenêtre, laquelle est fixée par la règle et par la série de prix :
if branch == "single" and book.holds(instrument):
decision = "declined_as_occupied" # earns nothing, still counts in the denominator
else:
book.open(instrument, side, size, tp, sl)Une politique occupée qui saute un signal n'en tire simplement rien. Le nombre de trades diffère alors d'une politique à l'autre, ce qui est un résultat à rapporter plutôt qu'une comparaison cassée, et le turnover est rapporté à côté.
Multiplicité
L'addendum est corrigé à l'intérieur de sa propre famille déclarée de 3 familles par 4 cellules par 2 branches, soit 24 contrastes. Les deux branches sont corrigées ensemble parce que les deux ont été exécutées et que les deux sont rapportées, si bien que les noter séparément sous-estimerait l'ampleur de la recherche. Les contrastes de l'addendum ne sont jamais agrégés avec ceux de l'étude, et aucun des deux ne réévalue l'autre.
Vérification, et les hashes gelés
Le moteur d'exécution en Rust a été vérifié ligne par ligne contre une implémentation de référence en Python écrite indépendamment. Sur le moteur de l'addendum, les deux concordent exactement, à une tolérance de 1e-9, sur chaque barre de sortie, chaque prix de sortie et chaque résultat net, sur 362,100 lignes. La parité est la suite qui compte le plus ici, parce que supprimer la sortie sur retournement a modifié le cœur d'exécution lui-même, et le gel refuse un verdict enregistré avant le code qu'il certifie, donc aucun verdict n'est hérité du moteur de l'étude.
| Suite | Étude | Addendum |
|---|---|---|
| Parité d'exécution, moteur Rust contre la référence Python | réussi | réussi |
| Calibration avant gel, durée de détention uniquement | réussi | réussi |
| Primitives d'inférence | réussi | réussi |
| Contrôles d'analyse | réussi | réussi |
| État, sélection, robustesse et parité des artefacts | non rejouée | non rejouée |
| Détection de défauts plantés, causalité et contrôles de fuite | non rejouée | non rejouée |
Les deux ont été spécifiés par écrit et gelés avant qu'aucun chiffre hors échantillon ne soit produit ou ouvert, et chaque chiffre rapporté est généré à partir d'artefacts stockés plutôt que saisi à la main. L'addendum porte ses propres hashes d'overlay et d'implémentation, et c'est ce qui en fait un ajout plutôt qu'une réévaluation :
| Artefact | SHA-256 |
|---|---|
| strategy_configs.json, partagé par les deux | d5c1fabbd22fcf20907e24025c6868f739afb11a2a952fc71241630f318cf68e |
| overlay_configs.json, étude | c0eaaaedd74800e2e973dda23a23053ee7966a07762803e7b2592cabfed5320f |
| analysis_config.json, étude | 1afcb713478d37d234268038280d9a83f38dfc535c8b1e3fbb9b6acc649e4fef |
| data_manifest.json, étude | 93aefeaa5ad78d920fdaf48d36cc61794921eaaaa92cbf52f3b7f5119c855a59 |
| strategy_selection.json, étude | a37718869d539141b64c492f1d221a7a9ba8395f7e9ca323d2c536028a21df4c |
| Implémentation d'analyse, étude | e46041ab30b8f44ad1029a798e1d0bb58aa97959ba88bfb0558bf4468393600f |
| overlay_configs.json, addendum | 7d575275961bee72f5f6557ae5c253545f69cc352d5b9e35a216643714b7331e |
| Implémentation d'analyse, addendum | 667f6ab6e65c2421c670179c6a5de8405209e2d8db35347e40fd71acb5fb4105 |
Demander le pipeline complet
Cette page suffit pour vérifier à la main chaque chiffre de la note, ou pour reconstruire une étude équivalente à partir des mêmes sources de données publiques et sous licence. Les scripts de production, c'est-à-dire la collecte des données, la construction des panels, le moteur Rust, le lanceur d'expériences et l'analyse, ne sont pas publiés ici. Envoyez un e-mail à daniel@daru.finance et je vous les enverrai.

