import { useState } from 'react'
import { usePatternReliability, useCalibration } from '../hooks/useApi'
import clsx from 'clsx'
import { BarChart2, Target, TrendingUp, AlertTriangle } from 'lucide-react'
function ReliabilityTable({ data }: { data: any[] }) {
if (!data || data.length === 0) {
return (
No reliability data
Data appears after 3+ mature trades (≥35% of horizon elapsed) per pattern
)
}
return (
| Pattern |
Trades |
Win Rate |
Avg PnL |
Max gain |
Max loss |
Reliability score |
{data.map((r: any) => {
const wr = r.win_rate_pct
const wrColor = wr >= 60 ? 'text-emerald-400' : wr >= 40 ? 'text-amber-400' : 'text-red-400'
const rel = r.reliability_score
const relColor = rel >= 1.5 ? 'text-emerald-400' : rel >= 0.8 ? 'text-amber-400' : 'text-red-400'
return (
|
{r.pattern_name}
{r.pattern_id}
|
{r.trade_count} |
{wr}% |
= 0 ? 'text-emerald-400' : 'text-red-400')}>
{r.avg_pnl_pct > 0 ? '+' : ''}{r.avg_pnl_pct}%
|
+{r.max_pnl_pct}% |
{r.max_loss_pct}% |
{rel.toFixed(2)}
WR × log(n+1)
|
)
})}
)
}
function CalibrationSection({ data }: { data: any }) {
if (!data) return null
const { brier_score, interpretation, buckets, sample_size } = data
if (!brier_score && sample_size === 0) {
return (
No calibration data yet
Requires mature trades with stored probability vs realized outcome
)
}
const brierColor = brier_score < 0.15 ? 'text-emerald-400' : brier_score < 0.25 ? 'text-amber-400' : 'text-red-400'
return (
{/* Score summary */}
{brier_score?.toFixed(3) ?? '—'}
Brier Score
(0 = perfect, 1 = null)
{sample_size}
Trades analyzed
{interpretation ?? '—'}
Interpretation
{/* Calibration buckets */}
{buckets && buckets.length > 0 && (
Calibration by decile (predicted vs realized)
| Predicted prob. |
Actual rate |
Bias |
Trades |
Bar |
{buckets.map((b: any) => {
const bias = b.bias
const biasColor = Math.abs(bias) < 0.05 ? 'text-emerald-400' : Math.abs(bias) < 0.15 ? 'text-amber-400' : 'text-red-400'
const actualPct = Math.round(b.actual_rate * 100)
const predPct = Math.round(b.predicted_mid * 100)
return (
| {b.predicted_range} |
{actualPct}% |
{bias > 0 ? '+' : ''}{(bias * 100).toFixed(1)}%
|
{b.count} |
{/* Predicted (grey) vs actual (colored) */}
= predPct ? 'bg-emerald-500' : 'bg-red-500')}
style={{ width: `${actualPct}%` }} />
|
)
})}
Grey bar = predicted prob. · Colored bar = realized rate · Green if actual ≥ predicted, red otherwise
)}
)
}
export default function Analytics() {
const { data: reliabilityData, isLoading: loadingR } = usePatternReliability()
const [calDays, setCalDays] = useState(365)
const { data: calData, isLoading: loadingC } = useCalibration(calDays)
const reliability: any[] = (reliabilityData as any)?.reliability ?? []
const topPatterns = reliability.slice(0, 5)
const bottomPatterns = reliability.filter((r: any) => r.trade_count >= 3).slice(-3)
return (
Analytics & Calibration
Historical pattern reliability · Probabilistic calibration · Brier score
{/* Summary KPIs */}
{reliability.length > 0 && (
{reliability.length}
Patterns with history
{reliability.reduce((s: number, r: any) => s + r.trade_count, 0)}
Mature trades analyzed
{topPatterns[0] && (
Best pattern
{topPatterns[0].pattern_name}
{topPatterns[0].win_rate_pct}% WR
)}
{bottomPatterns[0] && (
{bottomPatterns[0].pattern_name}
{bottomPatterns[0].win_rate_pct}% WR
)}
)}
{/* Reliability table */}
Reliability by pattern
(trades ≥35% of horizon only)
{loadingR ? (
) : (
)}
{/* Calibration */}
Probabilistic calibration
{loadingC ? (
) : (
)}
)
}