Pourquoi le ratio RV_short/RV_long n'est pas le bon signal ?

L'hypothèse initiale était fausse. Voici comment on l'a su, et avec quoi on l'a remplacée.

Cette page n'est pas une démonstration. C'est un carnet de bord. On va suivre, dans l'ordre où elles sont arrivées, les quatre étapes d'une correction : l'hypothèse posée, la mesure qui l'a contredite, la math qui l'a expliquée, le signal qui l'a remplacée. C'est ADR-0005 raconté comme le réacteur l'a vécu — « le réacteur apprend de ce qu'il brûle ».


T0 — l'hypothèse qu'on a parié

On a parié que le ratio

R(t) = RV_short(60s) / RV_long(600s)

mesurerait le passage du marché en régime rugueux. L'intuition était naturelle : en jours calmes, court et long se ressemblent (R ≈ 1) ; en jours violents, le court explose tandis que le long met du temps à intégrer le choc (R → 1.5+). On a fixé deux seuils dans l'ADR-0003 §4 : θ_high = 0.7 (entrée Rough), θ_low = 0.4 (sortie). La state machine 3-états Smooth / Transition / Rough, son hystérèse double-nommée anti-chattering + Ulysses-contract, son dwell minimum de 60 s — tout était en place. Il restait à observer.

On a écrit le pipeline. Il compilait. Les tests de propriété passaient. Il ne manquait plus qu'à le lâcher sur une journée connue pour être violente, et regarder le ribbon de régime s'allumer rouge.


T1 — le smoke run, 27 janvier 2021

On a choisi la journée pic du short squeeze GameStop / AMC, le moment le plus mémorable du retail trading de la décennie (le lendemain, Robinhood coupera l'achat — mais le 27 c'est le pic de volatilité naturelle, sans intervention courtier). Si un signal de rugosité de marché existe et veut dire quelque chose, c'est cette journée-là qu'il doit s'allumer le plus fort.

On a streamé les 11 millions de ticks XNAS sur AMC ce jour-là depuis le .dbn.zst databento, on a accumulé RV_short(60s) et RV_long(600s) sur 899 fenêtres de fin-de-minute, on a fait tourner la state machine — et on a regardé la distribution.

Le ratio R plafonne — AMC 2021-01-27 (smoke run commit 7746e00)

897 fenêtres Smooth, 2 Transition, 0 Rough.

Sur le jour le plus rugueux de la décennie pour AMC, sur 11 millions de ticks streamés, le classifier est resté Smooth 99.8 % du temps. Les deux transitions vers Transition n'ont jamais débordé en Rough ; aucune n'a tenu son dwell de 60 s. Le ribbon de régime, qu'on avait dessiné pour qu'il s'allume rouge, était plat bleu.

Le bug n'était pas dans le code. Le code mesurait exactement ce qu'on lui avait demandé de mesurer. Le bug était dans l'hypothèse.


T2 — la math du plafonnement

On a regardé la figure ci-dessus. La distribution de R(t) sur la journée est centrée autour de 0.094, le 99e percentile est à 0.231. Pas une fenêtre n'atteint 0.4. Le seuil θ_low = 0.4 est inaccessible. Le seuil θ_high = 0.7 est au-delà de toute observation possible — pas sur cette journée, pas sur n'importe laquelle.

Pourquoi ? À l'équilibre statistique, RV_long collecte \(~10\times\) plus de samples que RV_short (10 minutes vs 1 minute). En posant RV_long(τ_long) ≈ E[r²] · N_long et RV_short(τ_short) ≈ E[r²] · N_short (approximation Andersen–Bollerslev–Diebold–Labys 2003 Eq. 3) :

R ≈ N_short / N_long
  ≈ τ_short / τ_long      (densité de ticks comparable entre les deux fenêtres)
  = 60s / 600s
  = 0.1

Le ratio est structurellement borné par sa propre définition, pas par la rugosité du marché. C'est la moyenne empirique mesurée : R̄ = 0.094, exactement le 0.1 attendu. La queue droite atteint 0.23 lors de fenêtres ultra-actives où la densité de ticks short_60s dépasse temporairement la densité moyenne du long_600s — mais jamais à des facteurs qui permettraient à R d'atteindre 0.7.

Citation stricte : docs/adr/0005-classifier-signal-j-based.md §"Pourquoi R plafonne" L21–24. La borne théorique était écrite explicitement dans l'invariant INV-X-5 de ADR-0003 §1 (« R̄ → τ_short/τ_long ≈ 0.1 sur returns iid stationnaires ») — on l'avait écrit, on ne l'avait pas lu quand on a calibré θ_high = 0.7. Le code respectait sa propre spec ; la spec, au moment de la calibration, contredisait l'autre spec.

Conclusion mathématique : nos seuils 0.7/0.4 supposaient un signal qui peut atteindre 1.0. Le ratio R ne peut pas, par construction. Le classifier n'était pas mal calibré sur le mauvais signal — il était bien calibré sur un signal qui ne pouvait pas le déclencher.


T3 — le pivot vers J

Le pipeline calculait déjà autre chose. Pour chaque fenêtre, depuis le commit 7746e00, WindowVerdict.j_bv_rv contenait

J(t) = 1 − BV(t) / RV(t)

BV est la bipower variation (BV(t) = (π/2) · Σ |r_i| · |r_{i-1}|). Sous absence de saut, BV →_p RV asymptotiquement ([barndorff2004power, Thm. 1, p. ~7]) — donc J → 0 quand la trajectoire est continue. En présence de sauts, RV capte le saut au carré (r² ≫ |r|·|r_{i-1}|) tandis que BV ne le voit pas (le produit de retours voisins étouffe les sauts isolés) — donc J → 1 quand la variance vient des discontinuités.

J ∈ [−ε, 1] à fenêtre finie, avec ε ≈ 10/N (invariant INV-BV-3, ADR-0003 §2). Les seuils 0.7 et 0.4 retrouvent leur sens — ils correspondent maintenant à « la majorité de la variance vient de sauts » et « la majorité vient du diffusif », exactement la sémantique régime.

Sémantique alignée. La délibération-mère tick/delib-20260513-c2b8 parlait de régime rugueux Mandelbrot — Mandelbrot = sauts = ratio des variances biaisé vers la composante discontinue. C'est précisément ce que J mesure. Le ratio R, lui, mesurait l'inertie d'intégration d'un estimateur lent par rapport à un estimateur rapide — une grandeur réelle, mais sans rapport avec la rugosité topologique.

Le wiring change. Une ligne dans salim-pipeline::worker::maybe_emit :

- classifier.step(r_short_over_long, ts_ns)
+ classifier.step(j_short, ts_ns)

Aucun nouveau calcul. Le pipeline calculait déjà J par discipline d'observabilité (ADR-0003 §2). On bascule juste quel signal alimente la state machine. La colonne WindowVerdict.r_short_over_long reste exportée au Parquet pour audit — schéma V1 non breaking (ADR-0005 §"Impact sur WindowVerdict").

Le re-run post-pivot sur la même journée AMC 2021-01-27 a donné J median = 0.360, p99 = 0.504, max = 0.553. Strictement sous θ_high = 0.7et c'est un vrai verdict. La journée du squeeze était volatile mais directionnelle et continue, pas une succession de jumps discrets. Le J-based classifier dit Smooth, à raison. C'est documenté dans docs/lore/CHRONICLES.md §"2026-05-14 — Le squeeze n'est pas rough".


T4 — les leçons

Ce qui a été appris, en trois phrases qui survivront à la session.

Leçon 1 — borne naturelle d'un estimateur. Un ratio est borné par les fenêtres dont il est issu, pas par ce qu'on aimerait qu'il mesure. Avant de calibrer les seuils d'un signal, dérive sa borne théorique à l'équilibre. Si la borne est et que tes seuils visent au-delà de , le signal ne s'allumera jamais — peu importe le ticker, peu importe la journée, peu importe l'amplitude réelle du phénomène que tu cherches à détecter. Cinq minutes d'algèbre R ≈ N_short/N_long auraient évité la boucle complète. La discipline n'est pas « écris la spec ». La spec était écrite (INV-X-5). La discipline est « relis la spec quand tu calibres une autre partie de la spec ».

Leçon 2 — empirisme avant shipping. Si on n'avait pas streamé 11 millions de ticks sur AMC 2021-01-27 avant de partager une figure à Salim, l'erreur aurait survécu. Toute analyse théorique aurait conclu « le classifier est cohérent, les invariants tiennent » — ce qui était vrai, et inutile, et trompeur. Le smoke run sur une journée connue est une discipline non-négociable, parce qu'il révèle les erreurs de spécification que la cohérence formelle ne révèle pas. Le réacteur apprend de ce qu'il brûle, pas de ce qu'il pourrait brûler. Cf. ADR-0003 §5 (gate de déploiement property-tests) — étendu de facto à « gate empirique sur fixture réelle ».

Leçon 3 — discipline ADR. ADR-0005 amende ADR-0003 §1 §4. Pas n'importe quel changement — un changement typé avec source empirique nommée (commit 7746e00), invariants conservés (INV-CLASSIFIER- MONOTONIC-DWELL, hystérèse), lien superseded-by explicite, schéma Parquet non-breaking (r_short_over_long conservé en colonne d'audit). Le pivot s'écrit dans le ledger, pas dans Git seul. La trace cognitive vit là où on la cherchera dans six mois — dans docs/adr/, à côté de la décision originelle qu'elle corrige.


Pour ton cas

Si tu construis un signal à partir d'un ratio — RV / IV, BV / RV, spread / mid, volume_aggressor / volume_total, n'importe lequel — demande-toi quelle est sa borne théorique à l'équilibre.

Écris-la en deux lignes au-dessus du code, avant de calibrer les seuils. Si le seuil que tu vises est au-dessus de cette borne, le signal ne s'allumera jamais ; si la borne est très inférieure à 1.0, la borne est l'échelle à laquelle tu dois caler tes seuils. Cinq minutes de math au tableau économisent une session de débogage sur 11 millions de ticks.

C'est moins glamour que d'optimiser un sweep d'hyperparamètres sur 200 instruments. C'est l'algèbre du primaire. Mais c'est le geste qui sépare « j'ai mesuré quelque chose » de « j'ai mesuré ce que je croyais mesurer ».


Conséquences code

Pour la formule J = 1 − BV/RV elle-même, ses invariants, et le détail de la consistance asymptotique BV →_p IV — voir bipower-and-jumps. Pour la state machine, son hystérèse, son dwell — voir regime-classifier. Pour RV et sa variance d'estimateur — voir realized-variance.


Footer

Adversaire endogène : estimator-instability — le risque que tu construises un signal sans dériver sa borne. La leçon 1 est l'antidote explicite.

Pages soeurs realized-variance \(\cdot\) bipower-and-jumps \(\cdot\) regime-classifier \(\cdot\) cases-index

Sources primaires