import { useState } from 'react' import { useBayesianPosteriors, useRegimeClusters, useRegimeTransitions, usePatternEmbeddings, api } from '../hooks/useApi' import { useMutation, useQueryClient } from '@tanstack/react-query' import clsx from 'clsx' import { Brain, GitBranch, Layers, Cpu, RefreshCw, AlertTriangle } from 'lucide-react' // ── Bayesian Posteriors ─────────────────────────────────────────────────────── function BayesianTable({ data }: { data: any[] }) { if (!data || data.length === 0) { return (
Pas encore de données bayésiennes
Les posteriors se calculent après des trades matures (≥35% de l'horizon)
) } const withData = data.filter(d => d.sample_size > 0) const withoutData = data.filter(d => d.sample_size === 0) return (
{withData.length > 0 && (
{withData.map((d: any) => { const wr = d.bayesian_win_rate_pct const wrColor = wr >= 60 ? 'text-emerald-400' : wr >= 40 ? 'text-amber-400' : 'text-red-400' const drift = d.prior_vs_posterior_drift const driftColor = Math.abs(drift) < 0.05 ? 'text-slate-400' : drift > 0 ? 'text-emerald-400' : 'text-red-400' const confColor = d.confidence_level === 'haute' ? 'text-emerald-400' : d.confidence_level === 'moyenne' ? 'text-amber-400' : 'text-slate-500' return ( ) })}
Pattern Prior GPT WR Bayésien IC 95% Trades matures Dérive Confiance
{d.pattern_name}
{d.asset_class ?? '—'}
{Math.round(d.prior_probability * 100)}% {wr}% [{Math.round(d.lower_ci_95 * 100)}%–{Math.round(d.upper_ci_95 * 100)}%] {d.sample_size} {drift >= 0 ? '+' : ''}{Math.round(drift * 100)}% {d.confidence_level}
)} {withoutData.length > 0 && (
{withoutData.length} pattern(s) sans trades matures — prior GPT uniquement
)}
) } // ── Regime Transition Matrix ────────────────────────────────────────────────── function TransitionMatrix({ data }: { data: any }) { if (!data || !data.matrix || Object.keys(data.matrix).length === 0) { return (
Pas encore de transitions détectées
Les clusters se remplissent au fil des cycles
) } const { matrix, labels, cluster_ids } = data const ids: number[] = cluster_ids ?? Object.keys(matrix).map(Number) return (
Probabilité de transition d'un régime à l'autre (lignes = source, colonnes = destination) · {data.n_transitions} transitions totales
{ids.map(dst => ( ))} {ids.map(src => ( {ids.map(dst => { const prob = matrix?.[src]?.[dst] ?? 0 const pct = Math.round(prob * 100) const bg = src === dst ? 'bg-blue-500/20 text-blue-300' : pct >= 50 ? 'bg-emerald-500/20 text-emerald-300' : pct >= 25 ? 'bg-amber-500/10 text-amber-300' : 'text-slate-600' return ( ) })} ))}
De ↓ / Vers →
C{dst}
{(labels?.[dst] ?? `Cluster ${dst}`).split(' ').slice(0, 2).join(' ')}
C{src}
{(labels?.[src] ?? `Cluster ${src}`).split(' ').slice(0, 2).join(' ')}
{pct > 0 ? `${pct}%` : '—'}
Diagonal = reste dans le même cluster · Vert = transition dominante · Bleu = auto-transition
) } // ── Cluster History Timeline ────────────────────────────────────────────────── const CLUSTER_COLORS: Record = { 0: 'bg-red-500', 1: 'bg-emerald-500', 2: 'bg-orange-500', 3: 'bg-yellow-500', 4: 'bg-slate-400', } function ClusterTimeline({ data }: { data: any[] }) { if (!data || data.length === 0) { return (
Aucun cluster détecté encore — lancez un cycle pour démarrer
) } // Afficher les 30 derniers points const recent = [...data].reverse().slice(0, 30) return (
{Object.entries(CLUSTER_COLORS).map(([k, col]) => ( C{k} ))}
{recent.map((r: any, i: number) => (
))}
Chaque carré = 1 snapshot · Blanc cerclé = anomalie détectée · {data.length} entrées au total
{/* Latest cluster detail */} {data[0] && (
Cluster actuel
{data[0].cluster_label} ({data[0].dominant_regime}) {data[0].anomaly_flag ? ( Anomalie ) : null}
{data[0].timestamp?.slice(0, 16)}
)}
) } // ── Embeddings Summary ──────────────────────────────────────────────────────── function EmbeddingsSummary({ data }: { data: any[] }) { if (!data || data.length === 0) { return (
Aucun embedding disponible — ils se génèrent automatiquement lors du filtrage des patterns
) } return (
{data.length} patterns vectorisés
{data.slice(0, 20).map((e: any, i: number) => ( ))}
Pattern Classe Modèle Mis à jour
{e.name ?? e.pattern_id} {e.asset_class ?? '—'} {e.model_version} {e.updated_at?.slice(0, 10)}
) } // ── Main Page ───────────────────────────────────────────────────────────────── export default function AnalyticsAdvanced() { const [regimeDays, setRegimeDays] = useState(90) const qc = useQueryClient() const { data: bayesData, isLoading: loadingBayes } = useBayesianPosteriors() const { data: clustersData, isLoading: loadingClusters } = useRegimeClusters(regimeDays) const { data: transData, isLoading: loadingTrans } = useRegimeTransitions(180) const { data: embData, isLoading: loadingEmb } = usePatternEmbeddings() const posteriors: any[] = (bayesData as any)?.posteriors ?? [] const clusters: any[] = (clustersData as any)?.clusters ?? [] const embeddings: any[] = (embData as any)?.embeddings ?? [] const bayesUpdate = useMutation({ mutationFn: () => api.post('/analytics/bayesian/update').then(r => r.data), onSuccess: () => qc.invalidateQueries({ queryKey: ['bayesian-posteriors'] }), }) const regimeDetect = useMutation({ mutationFn: () => api.post('/analytics/regime-detect').then(r => r.data), onSuccess: () => { qc.invalidateQueries({ queryKey: ['regime-clusters'] }) qc.invalidateQueries({ queryKey: ['regime-transitions'] }) }, }) const patternsWithData = posteriors.filter((p: any) => p.sample_size > 0) const avgBayesWR = patternsWithData.length ? Math.round(patternsWithData.reduce((s: number, p: any) => s + p.bayesian_win_rate_pct, 0) / patternsWithData.length) : null const latestCluster = clusters[0] return (
{/* Header */}

Analytics Avancées — Phase 4

Bayesian updating · Clustering de régimes · Embeddings sémantiques

{/* KPIs */}
{patternsWithData.length}
Patterns bayésiens actifs
{avgBayesWR !== null ? `${avgBayesWR}%` : '—'}
Win rate bayésien moyen
{latestCluster ? `C${latestCluster.cluster_id}` : '—'}
{latestCluster?.cluster_label ?? 'Aucun cluster'}
{embeddings.length}
Patterns vectorisés
{/* Bayesian Posteriors */}
Posteriors bayésiens par pattern Beta(α,β) · IC 95%
{bayesUpdate.data && ( {bayesUpdate.data.message} )}
{loadingBayes ? (
{[1,2,3].map(i =>
)}
) : ( )}
Lecture : Prior GPT = probabilité annoncée par GPT-4o · WR Bayésien = win rate observé avec lissage Laplace (β a priori faible) · Dérive = écart posterior − prior · IC 95% basé sur la distribution Beta
{/* Regime Clustering */}
{/* Cluster timeline */}
Historique des clusters
{loadingClusters ? (
) : ( )}
{/* Transition matrix */}
Matrice de transition P(régime j | régime i)
{loadingTrans ? (
) : ( )}
{/* Embeddings */}
Embeddings sémantiques text-embedding-3-small · Similarité cosinus remplace Jaccard
{loadingEmb ? (
) : ( )}
Comment ça marche : Chaque nouveau pattern suggéré est vectorisé et comparé aux patterns existants via similarité cosinus (seuil : 0.75). Si la similarité est > 0.75, le pattern est rejeté comme doublon sémantique — plus précis que Jaccard qui ne voit que les mots communs.
) }