feat: wavelets

This commit is contained in:
OpenSquared
2026-07-20 20:30:24 +02:00
parent 32d30c01f2
commit 28f7c77103
2 changed files with 24 additions and 6 deletions

View File

@@ -616,14 +616,29 @@ def wavelet_reliability(
# showed up (earliest match) — compared against avg_cycle_days below to check
# whether "one cycle" is actually a well-calibrated horizon, or systematically
# too long/short relative to how quickly a real reversal actually confirms.
actual_delay = (min(matches) - d_pos) if confirmed else None
tested.append({"date": d_date, "confirmed": confirmed, "actual_delay_days": actual_delay})
actual_delay = None
height_diff = None
if confirmed:
confirm_idx = min(matches)
actual_delay = confirm_idx - d_pos
# Direction agreeing doesn't mean the peak/trough had the same amplitude —
# the live (causal) reading can be a real reversal but distorted in height by
# edge effects. Compare the band's own value at the original causal turning
# point against its value at the confirming point in the hindsight
# reconstruction (same units as the band, e.g. price terms).
height_diff = abs(band["series"][t] - h_series[confirm_idx])
tested.append({
"date": d_date, "confirmed": confirmed,
"actual_delay_days": actual_delay, "height_diff": height_diff,
})
n_tested = len(tested)
n_confirmed = sum(1 for t in tested if t["confirmed"])
delays = [t["actual_delay_days"] for t in tested if t["actual_delay_days"] is not None]
avg_actual_delay = round(sum(delays) / len(delays), 1) if delays else None
calibration_gap = round(sum(abs(avg_cycle_days - d) for d in delays) / len(delays), 1) if delays else None
heights = [t["height_diff"] for t in tested if t["height_diff"] is not None]
avg_height_diff = round(sum(heights) / len(heights), 6) if heights else None
bands_out.append({
"label": band["label"],
"n_tested": n_tested,
@@ -633,6 +648,7 @@ def wavelet_reliability(
"confirm_horizon": confirm_horizon,
"avg_actual_delay_days": avg_actual_delay,
"calibration_gap_days": calibration_gap,
"avg_height_diff": avg_height_diff,
"turns": tested,
})

View File

@@ -2389,6 +2389,7 @@ export default function InstrumentDashboard({ instrumentIdProp, isVisible }: { i
<th className="py-1 pr-3 font-normal text-right" title="= cycle moyen × 1.10 — le retournement peut être confirmé n'importe où entre la date d'origine et date+horizon, pas juste pile à cette date">Horizon max</th>
<th className="py-1 pr-3 font-normal text-right" title="Délai réel moyen (parmi les retournements confirmés) entre la date d'origine et l'apparition du retournement confirmant, en hindsight">Délai réel moyen</th>
<th className="py-1 pr-3 font-normal text-right" title="Écart absolu moyen entre le cycle moyen (l'horizon supposé) et le délai réel observé — petit = l'horizon est bien calibré, grand = il est trop long ou trop court par rapport à la réalité">Écart calibration</th>
<th className="py-1 pr-3 font-normal text-right" title="Différence absolue moyenne entre la hauteur du pic/creux mesurée en direct (causal) et sa hauteur une fois confirmée en hindsight — direction confirmée ne veut pas dire même amplitude : un pic peut se retourner au bon moment mais avec une hauteur très différente de ce qui avait été vu en direct.">Écart hauteur</th>
<th className="py-1 pr-3 font-normal text-right">Retournements testés</th>
<th className="py-1 pr-3 font-normal text-right">Confirmés</th>
<th className="py-1 pr-3 font-normal text-right">Indice de confiance</th>
@@ -2407,6 +2408,7 @@ export default function InstrumentDashboard({ instrumentIdProp, isVisible }: { i
<td className="py-1 pr-3 text-right text-slate-400 font-mono">{b.confirm_horizon != null ? `+${b.confirm_horizon}j` : '—'}</td>
<td className="py-1 pr-3 text-right text-slate-400 font-mono">{b.avg_actual_delay_days != null ? `${b.avg_actual_delay_days}j` : '—'}</td>
<td className={clsx('py-1 pr-3 text-right font-mono', gapColor)}>{gap != null ? `${gap}j` : '—'}</td>
<td className="py-1 pr-3 text-right text-slate-400 font-mono">{b.avg_height_diff != null ? b.avg_height_diff.toFixed(4) : '—'}</td>
<td className="py-1 pr-3 text-right text-slate-300 font-mono">{b.n_tested}</td>
<td className="py-1 pr-3 text-right text-slate-300 font-mono">{b.n_confirmed}</td>
<td className={clsx('py-1 pr-3 text-right font-mono font-semibold', color)}>
@@ -2421,10 +2423,10 @@ export default function InstrumentDashboard({ instrumentIdProp, isVisible }: { i
Pour chaque retournement détecté en mode causal (pente lissée sur {waveletReliability.smooth_days}j qui change de signe, sans regarder le futur),
l'horizon de recalcul est propre à chaque bande — cycle moyen historique (pic-à-pic) × 1.10, pas un nombre de jours fixe identique pour toutes
(10 jours ne veut rien dire pour une bande à 2j de période comme pour une à 20j). Le retournement confirme l'original s'il réapparaît n'importe
entre la date d'origine et date+horizon. "Écart calibration" compare l'horizon supposé (cycle moyen) au délai réel observé un écart élevé
signale que l'horizon utilisé n'est pas représentatif de la vitesse réelle de confirmation, indépendamment de l'indice de confiance lui-même.
Un indice bas signale des retournements souvent dus à un effet de bord de la décomposition (peu fiable pile au bord de la fenêtre disponible),
pas à un vrai signal.
entre la date d'origine et date+horizon. "Écart calibration" compare l'horizon supposé (cycle moyen) au délai réel observé ; "Écart hauteur"
compare l'amplitude du pic/creux vue en direct à celle confirmée en hindsight — comparez-le au RMS de la bande (tableau du dessus) pour juger
s'il est significatif. Un indice bas signale des retournements souvent dus à un effet de bord de la décomposition (peu fiable pile au bord de
la fenêtre disponible), pas à un vrai signal.
</p>
</div>
)}