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 (
No Bayesian data yet
Posteriors are computed after mature trades (≥35% of the 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 Bayesian WR CI 95% Mature trades Drift Confidence
{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) without mature trades — GPT prior only
)}
) } // ── Regime Transition Matrix ────────────────────────────────────────────────── function TransitionMatrix({ data }: { data: any }) { if (!data || !data.matrix || Object.keys(data.matrix).length === 0) { return (
No transitions detected yet
Clusters fill up as cycles run
) } const { matrix, labels, cluster_ids } = data const ids: number[] = cluster_ids ?? Object.keys(matrix).map(Number) return (
Transition probability from one regime to another (rows = source, columns = destination) · {data.n_transitions} total transitions
{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 ( ) })} ))}
From ↓ / To →
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 = stays in same cluster · Green = dominant transition · Blue = self-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 (
No cluster detected yet — run a cycle to get started
) } // 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) => (
))}
Each square = 1 snapshot · White ring = anomaly detected · {data.length} total entries
{/* Latest cluster detail */} {data[0] && (
Current cluster
{data[0].cluster_label} ({data[0].dominant_regime}) {data[0].anomaly_flag ? ( Anomaly ) : null}
{data[0].timestamp?.slice(0, 16)}
)}
) } // ── Embeddings Summary ──────────────────────────────────────────────────────── function EmbeddingsSummary({ data }: { data: any[] }) { if (!data || data.length === 0) { return (
No embeddings available — they are generated automatically during pattern filtering
) } return (
{data.length} vectorized patterns
{data.slice(0, 20).map((e: any, i: number) => ( ))}
Pattern Class Model Updated
{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 */}

Advanced Analytics — Phase 4

Bayesian updating · Regime clustering · Semantic embeddings

{/* KPIs */}
{patternsWithData.length}
Active Bayesian patterns
{avgBayesWR !== null ? `${avgBayesWR}%` : '—'}
Average Bayesian win rate
{latestCluster ? `C${latestCluster.cluster_id}` : '—'}
{latestCluster?.cluster_label ?? 'No cluster'}
{embeddings.length}
Vectorized patterns
{/* Bayesian Posteriors */}
Bayesian posteriors by pattern Beta(α,β) · CI 95%
{bayesUpdate.data && ( {bayesUpdate.data.message} )}
{loadingBayes ? (
{[1,2,3].map(i =>
)}
) : ( )}
Reading: GPT Prior = probability stated by GPT-4o · Bayesian WR = observed win rate with Laplace smoothing (weak β prior) · Drift = posterior − prior gap · CI 95% based on Beta distribution
{/* Regime Clustering */}
{/* Cluster timeline */}
Cluster history
{loadingClusters ? (
) : ( )}
{/* Transition matrix */}
Transition matrix P(regime j | regime i)
{loadingTrans ? (
) : ( )}
{/* Embeddings */}
Semantic embeddings text-embedding-3-small · Cosine similarity replaces Jaccard
{loadingEmb ? (
) : ( )}
How it works: Each newly suggested pattern is vectorized and compared to existing patterns via cosine similarity (threshold: 0.75). If similarity is > 0.75, the pattern is rejected as a semantic duplicate — more precise than Jaccard which only sees common words.
) }