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 # showed up (earliest match) — compared against avg_cycle_days below to check
# whether "one cycle" is actually a well-calibrated horizon, or systematically # whether "one cycle" is actually a well-calibrated horizon, or systematically
# too long/short relative to how quickly a real reversal actually confirms. # too long/short relative to how quickly a real reversal actually confirms.
actual_delay = (min(matches) - d_pos) if confirmed else None actual_delay = None
tested.append({"date": d_date, "confirmed": confirmed, "actual_delay_days": actual_delay}) 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_tested = len(tested)
n_confirmed = sum(1 for t in tested if t["confirmed"]) 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] 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 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 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({ bands_out.append({
"label": band["label"], "label": band["label"],
"n_tested": n_tested, "n_tested": n_tested,
@@ -633,6 +648,7 @@ def wavelet_reliability(
"confirm_horizon": confirm_horizon, "confirm_horizon": confirm_horizon,
"avg_actual_delay_days": avg_actual_delay, "avg_actual_delay_days": avg_actual_delay,
"calibration_gap_days": calibration_gap, "calibration_gap_days": calibration_gap,
"avg_height_diff": avg_height_diff,
"turns": tested, "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="= 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="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="É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">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">Confirmés</th>
<th className="py-1 pr-3 font-normal text-right">Indice de confiance</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.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="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={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_tested}</td>
<td className="py-1 pr-3 text-right text-slate-300 font-mono">{b.n_confirmed}</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)}> <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), 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 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 (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é entre la date d'origine et date+horizon. "Écart calibration" compare l'horizon supposé (cycle moyen) au délai réel observé ; "Écart hauteur"
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. 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
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), 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
pas à un vrai signal. la fenêtre disponible), pas à un vrai signal.
</p> </p>
</div> </div>
)} )}