feat: wavelets
This commit is contained in:
@@ -1433,6 +1433,8 @@ export default function InstrumentDashboard({ instrumentIdProp, isVisible }: { i
|
||||
const [waveletLookback, setWaveletLookback] = useState(130)
|
||||
const [waveletData, setWaveletData] = useState<any | null>(null)
|
||||
const [loadingWavelet, setLoadingWavelet] = useState(false)
|
||||
const [waveletReliability, setWaveletReliability] = useState<any | null>(null)
|
||||
const [loadingReliability, setLoadingReliability] = useState(false)
|
||||
const [hiddenBands, setHiddenBands] = useState<Set<string>>(new Set())
|
||||
const [hoveredWaveletIdx, setHoveredWaveletIdx] = useState<number | null>(null)
|
||||
const [templates, setTemplates] = useState<CausalTemplate[]>([])
|
||||
@@ -1647,6 +1649,25 @@ export default function InstrumentDashboard({ instrumentIdProp, isVisible }: { i
|
||||
}
|
||||
}
|
||||
|
||||
const runWaveletReliability = async () => {
|
||||
if (!selected) return
|
||||
setLoadingReliability(true)
|
||||
try {
|
||||
const { data } = await api.get('/wavelet/reliability', {
|
||||
params: {
|
||||
symbol: selected.yf_ticker, period, levels: waveletLevels,
|
||||
wavelet: waveletFamily, method: waveletMethod, lookback: waveletLookback,
|
||||
},
|
||||
})
|
||||
setWaveletReliability(data)
|
||||
} catch (e) {
|
||||
console.error('Wavelet reliability failed', e)
|
||||
setWaveletReliability(null)
|
||||
} finally {
|
||||
setLoadingReliability(false)
|
||||
}
|
||||
}
|
||||
|
||||
const waveletChartData = useMemo(() => {
|
||||
if (!waveletData) return []
|
||||
const { dates, original, bands, mean } = waveletData
|
||||
@@ -2142,6 +2163,11 @@ export default function InstrumentDashboard({ instrumentIdProp, isVisible }: { i
|
||||
className="px-2.5 py-1 rounded text-xs font-medium border border-blue-600/40 bg-blue-700/30 text-blue-200 hover:bg-blue-700/50 transition-colors disabled:opacity-50">
|
||||
{loadingWavelet ? '↻ Calcul…' : "▶ Lancer l'analyse"}
|
||||
</button>
|
||||
<button onClick={runWaveletReliability} disabled={loadingReliability}
|
||||
title="Pour chaque retournement détecté en mode causal (walk-forward, sans anticipation), vérifie si un recalcul 10 jours plus tard confirme le même retournement — un indice de fiabilité par bande."
|
||||
className="px-2.5 py-1 rounded text-xs font-medium border border-purple-600/40 bg-purple-700/30 text-purple-200 hover:bg-purple-700/50 transition-colors disabled:opacity-50">
|
||||
{loadingReliability ? '↻ Calcul…' : '◈ Tester la fiabilité'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2343,6 +2369,46 @@ export default function InstrumentDashboard({ instrumentIdProp, isVisible }: { i
|
||||
{loadingWavelet ? 'Calcul en cours…' : "Cliquez sur \"Lancer l'analyse\" pour décomposer le prix en bandes de fréquence"}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{waveletReliability && (
|
||||
<div className="mt-3 pt-3 border-t border-slate-700/30">
|
||||
<div className="text-xs font-semibold text-slate-400 uppercase tracking-wide mb-2">
|
||||
Fiabilité des retournements (walk-forward vs. recalcul +{waveletReliability.confirm_horizon}j)
|
||||
</div>
|
||||
<table className="w-full text-[11px]">
|
||||
<thead>
|
||||
<tr className="text-slate-500 text-left">
|
||||
<th className="py-1 pr-3 font-normal">Bande</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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{waveletReliability.bands.map((b: any, i: number) => {
|
||||
const pct = b.confidence_pct
|
||||
const color = pct == null ? 'text-slate-500' : pct >= 70 ? 'text-emerald-400' : pct >= 40 ? 'text-amber-400' : 'text-red-400'
|
||||
return (
|
||||
<tr key={b.label} className="border-t border-slate-800/60">
|
||||
<td className="py-1 pr-3" style={{ color: WAVELET_BAND_COLORS[i % WAVELET_BAND_COLORS.length] }}>{b.label}</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)}>
|
||||
{pct != null ? `${pct.toFixed(0)}%` : 'n/a (pas assez de retournements testables)'}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
<p className="text-[10px] text-slate-600 mt-1">
|
||||
Pour chaque retournement détecté en mode causal (pente lissée sur {waveletReliability.smooth_days}j qui change de signe, sans regarder le futur),
|
||||
on recalcule la décomposition avec {waveletReliability.confirm_horizon} jours de données réelles en plus et on vérifie qu'un retournement dans le même sens
|
||||
réapparaît à ±{waveletReliability.tolerance_days}j de la date d'origine. 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>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user