Files
investment-sandbox/components/modules/events/EventsDemo.tsx
2026-06-13 15:16:57 +02:00

1343 lines
64 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import React, { useState, useMemo } from 'react';
import { useSandboxStore } from '@/lib/store';
import { calculateEventROC, calculateEventSurvival, runEventLMM } from '@/lib/math/statistics';
import {
ResponsiveContainer,
AreaChart,
Area,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
LineChart,
Line,
ReferenceLine,
Legend
} from 'recharts';
import 'katex/dist/katex.min.css';
import { BlockMath, InlineMath } from 'react-katex';
import EconometricsMathModal from './EconometricsMathModal';
import EconometricsBlueprintModal from './EconometricsBlueprintModal';
import {
Activity,
BarChart4,
Compass,
GitMerge,
Plus,
Trash2,
Calendar,
Sparkles,
AlertCircle,
ChevronDown,
ChevronUp,
BookOpen,
RefreshCw,
Info,
Check,
TrendingUp,
TrendingDown,
Sliders,
Database
} from 'lucide-react';
// Predefined archetypes for Event Creation
const ARCHETYPES: Record<string, { name: string; defaultScores: Record<string, number> }> = {
'🏦 Fed Rate Decision (FOMC)': {
name: 'Fed Rate Decision (FOMC)',
defaultScores: { Apple: 1, NASDAQ: 2, Gold: -1, Bitcoin: 2 }
},
'📈 US Inflation Data (CPI)': {
name: 'US Inflation Data (CPI)',
defaultScores: { Apple: 1, NASDAQ: 2, Gold: -2, Bitcoin: 1 }
},
'💼 Non-Farm Payrolls (NFP)': {
name: 'Non-Farm Payrolls (NFP)',
defaultScores: { Apple: 0, NASDAQ: 1, Gold: -1, Bitcoin: 0 }
},
'🛒 OPEC Meeting': {
name: 'OPEC Meeting',
defaultScores: { Apple: -1, NASDAQ: -1, Gold: 2, Bitcoin: 1 }
}
};
export default function EventsDemo() {
const {
selectedModel,
setSelectedModel,
eventsMatrix,
calendarProposals,
lmmObservations,
assetsList,
lmmResults: storeLmmResults
} = useSandboxStore();
const assets = useMemo(() => {
return assetsList || [
{ name: 'Apple', symbol: 'AAPL' },
{ name: 'NASDAQ', symbol: '^IXIC' },
{ name: 'Gold', symbol: 'GLD' },
{ name: 'Bitcoin', symbol: 'BTC-USD' }
];
}, [assetsList]);
const activeProposals = useMemo(() => {
const acceptedNames = new Set(eventsMatrix.map((ev) => ev.name.toLowerCase()));
return calendarProposals.filter((cp) => !acceptedNames.has(cp.name.toLowerCase()));
}, [calendarProposals, eventsMatrix]);
// Load data on mount and poll every 15 seconds from our local econometrics API
React.useEffect(() => {
const loadData = () => {
fetch('/api/econometrics')
.then(r => r.json())
.then(data => {
const existingNames = new Set((data.events || []).map((ev: any) => ev.name.toLowerCase()));
useSandboxStore.setState({
eventsMatrix: data.events || [],
lmmObservations: data.observations || [],
assetsList: data.assets || [],
lmmResults: data.lmmResults,
calendarProposals: useSandboxStore.getState().calendarProposals.filter(
cp => !existingNames.has(cp.name.toLowerCase())
)
});
})
.catch(err => console.error('Failed to load econometrics storage:', err));
};
loadData();
const interval = setInterval(loadData, 15000);
return () => clearInterval(interval);
}, []);
const addEventToMatrix = async (name: string, date: string, scores: Record<string, number>) => {
try {
const response = await fetch('/api/econometrics', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, date, scores })
});
if (response.ok) {
const data = await response.json();
useSandboxStore.setState({
eventsMatrix: data.events || [],
lmmObservations: data.observations || [],
assetsList: data.assets || [],
lmmResults: data.lmmResults,
calendarProposals: calendarProposals.filter(cp => cp.name !== name)
});
}
} catch (err) {
console.error('Failed to add event to matrix:', err);
}
};
const updateMatrixCell = async (eventId: string, asset: string, score: number) => {
try {
const response = await fetch('/api/econometrics', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ eventId, asset, score })
});
if (response.ok) {
const data = await response.json();
useSandboxStore.setState({
eventsMatrix: data.events || [],
lmmObservations: data.observations || [],
assetsList: data.assets || [],
lmmResults: data.lmmResults
});
}
} catch (err) {
console.error('Failed to update matrix cell:', err);
}
};
const runEndogenousLMMCalibration = async () => {
try {
const response = await fetch('/api/econometrics', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'calibrate' })
});
if (response.ok) {
const data = await response.json();
useSandboxStore.setState({
eventsMatrix: data.events || [],
lmmObservations: data.observations || [],
assetsList: data.assets || [],
lmmResults: data.lmmResults
});
}
} catch (err) {
console.error('Failed to run LMM calibration:', err);
}
};
const [newTickerInput, setNewTickerInput] = useState<string>('');
const handleAddAsset = async (e: React.FormEvent) => {
e.preventDefault();
const symbol = newTickerInput.trim().toUpperCase();
if (!symbol) return;
const name = symbol;
try {
const response = await fetch('/api/econometrics', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'addAsset', name, symbol })
});
if (response.ok) {
const data = await response.json();
useSandboxStore.setState({
eventsMatrix: data.events || [],
lmmObservations: data.observations || [],
assetsList: data.assets || [],
lmmResults: data.lmmResults
});
setNewTickerInput('');
}
} catch (err) {
console.error('Failed to add asset column:', err);
}
};
const handleRemoveAsset = async (symbol: string) => {
try {
const response = await fetch('/api/econometrics', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'removeAsset', symbol })
});
if (response.ok) {
const data = await response.json();
useSandboxStore.setState({
eventsMatrix: data.events || [],
lmmObservations: data.observations || [],
assetsList: data.assets || [],
lmmResults: data.lmmResults
});
}
} catch (err) {
console.error('Failed to remove asset column:', err);
}
};
const deleteEventFromMatrix = async (eventId: string) => {
try {
const response = await fetch(`/api/econometrics?eventId=${eventId}`, {
method: 'DELETE'
});
if (response.ok) {
const data = await response.json();
useSandboxStore.setState({
eventsMatrix: data.events || [],
lmmObservations: data.observations || [],
lmmResults: data.lmmResults
});
}
} catch (err) {
console.error('Failed to delete event:', err);
}
};
// Local State
const [tauPre, setTauPre] = useState<number>(7);
const [tauPost, setTauPost] = useState<number>(3);
const [isMathModalOpen, setIsMathModalOpen] = useState<boolean>(false);
const [isBlueprintModalOpen, setIsBlueprintModalOpen] = useState<boolean>(false);
const [selectedSurvivalAsset, setSelectedSurvivalAsset] = useState<string>('Apple');
const [showLmmDiagnostics, setShowLmmDiagnostics] = useState<boolean>(false);
React.useEffect(() => {
if (assets.length > 0 && !assets.some(a => a.name === selectedSurvivalAsset)) {
setSelectedSurvivalAsset(assets[0].name);
}
}, [assets, selectedSurvivalAsset]);
const [customName, setCustomName] = useState<string>('');
const [customDate, setCustomDate] = useState<string>('2026-06-15');
const [selectedArchetype, setSelectedArchetype] = useState<string>('Custom');
// Calibration feedback states
const [isCalibrating, setIsCalibrating] = useState<boolean>(false);
const [calibrationSuccess, setCalibrationSuccess] = useState<boolean>(false);
const [lastCalibrationTime, setLastCalibrationTime] = useState<string | null>(null);
// Current baseline date for relative time calculations
const CURRENT_DATE_STR = '2026-06-06';
// Helper to calculate time kernel weight
const getWeightAndDays = (eventDateStr: string) => {
const eventDate = new Date(eventDateStr);
const currentDate = new Date(CURRENT_DATE_STR);
const diffTime = eventDate.getTime() - currentDate.getTime();
const d = diffTime / (1000 * 60 * 60 * 24); // days relative to today
let weight = 0;
if (d >= 0) {
weight = Math.exp(-d / tauPre);
} else {
const daysSince = -d;
weight = 1 / (1 + Math.log(1 + daysSince / tauPost));
}
return {
d: Math.round(d),
weight: Math.round(weight * 100) / 100
};
};
// 1. Time Weighted Net Impact Scores & Final Action Signals
const actionSignals = useMemo(() => {
const totals: Record<string, number> = {};
assets.forEach(asset => {
totals[asset.name] = 0;
});
eventsMatrix.forEach((ev) => {
const { weight } = getWeightAndDays(ev.date);
assets.forEach((asset) => {
const score = ev.scores[asset.name] || 0;
totals[asset.name] += score * weight;
});
});
const signals: Record<string, { netScore: number; signal: string; colorClass: string; textClass: string; glowClass: string }> = {};
assets.forEach((asset) => {
const netScore = Math.round(totals[asset.name] * 100) / 100;
let signal = 'NEUTRAL / HOLD';
let colorClass = 'bg-slate-800/40 border-slate-700/60 text-slate-400';
let textClass = 'text-slate-400';
let glowClass = 'shadow-slate-500/5';
if (netScore > 1.5) {
signal = 'STRONG BUY';
colorClass = 'bg-emerald-950/40 border-emerald-800/80 text-emerald-400';
textClass = 'text-emerald-400 font-bold';
glowClass = 'shadow-emerald-500/10 shadow-[0_0_15px_rgba(16,185,129,0.15)]';
} else if (netScore > 0.4) {
signal = 'ACCUMULATE';
colorClass = 'bg-teal-950/30 border-teal-800/50 text-teal-400';
textClass = 'text-teal-300 font-semibold';
glowClass = 'shadow-teal-500/5 shadow-[0_0_10px_rgba(20,184,166,0.1)]';
} else if (netScore < -1.5) {
signal = 'STRONG SELL / RISK OFF';
colorClass = 'bg-rose-950/40 border-rose-800/80 text-rose-400';
textClass = 'text-rose-400 font-bold';
glowClass = 'shadow-rose-500/10 shadow-[0_0_15px_rgba(244,63,94,0.15)]';
} else if (netScore < -0.4) {
signal = 'REDUCE / HEDGE';
colorClass = 'bg-amber-950/30 border-amber-800/50 text-amber-400';
textClass = 'text-amber-300 font-semibold';
glowClass = 'shadow-amber-500/5 shadow-[0_0_10px_rgba(245,158,11,0.1)]';
}
signals[asset.name] = { netScore, signal, colorClass, textClass, glowClass };
});
return signals;
}, [eventsMatrix, assets, tauPre, tauPost]);
// 2. Dynamic Decay Curve Chart Data
const decayCurveData = useMemo(() => {
const pts = [];
// Generate weight for each day relative to event (d = E - T)
for (let d = -30; d <= 30; d++) {
let weight = 0;
if (d >= 0) {
weight = Math.exp(-d / tauPre);
} else {
const daysSince = -d;
weight = 1 / (1 + Math.log(1 + daysSince / tauPost));
}
pts.push({
days: d,
weight: Math.round(weight * 1000) / 1000
});
}
return pts;
}, [tauPre, tauPost]);
// 3. Dynamic LMM regression fitting
const lmmResults = useMemo(() => {
if (storeLmmResults) return storeLmmResults;
const clientLmm = runEventLMM(lmmObservations);
return {
...clientLmm,
randomEffectsVariance: {
interceptVar: 0.00014,
vixSlopeVar: 0.00002,
eventMemoryVar: 0.00005,
residualVar: 0.00032
}
};
}, [storeLmmResults, lmmObservations]);
// 4. Dynamic ROC Data
const { rocData, optimalThreshold, maxYouden, auc } = useMemo(() => {
if (lmmResults?.roc) {
return {
rocData: lmmResults.roc.points,
optimalThreshold: lmmResults.roc.optimalThreshold,
maxYouden: lmmResults.roc.maxYouden,
auc: lmmResults.roc.auc
};
}
const predictions: number[] = [];
const labels: number[] = [];
lmmObservations.forEach((obs) => {
// Find average event score of this asset in matrix to use as indicator bias
const assetScores = eventsMatrix.map(ev => ev.scores[obs.asset] || 0);
const avgScore = assetScores.reduce((sum, s) => sum + s, 0) / (assetScores.length || 1);
// Construct a predictor between 0 and 1
let basePred = obs.eventType === 'BULLISH' ? 0.65 : 0.35;
basePred += avgScore * 0.04 + obs.trend * 0.5 - obs.vix * 0.002;
const finalPred = Math.min(0.99, Math.max(0.01, basePred));
predictions.push(finalPred);
labels.push(obs.returnVal > 0.012 ? 1 : 0); // label 1 if return > 1.2%
});
const res = calculateEventROC(predictions, labels);
// Trapezoidal Area Under Curve (AUC)
let computedAuc = 0;
const sorted = [...res.points].sort((a, b) => a.fpr - b.fpr);
for (let i = 1; i < sorted.length; i++) {
const w = sorted[i].fpr - sorted[i - 1].fpr;
const h = (sorted[i].tpr + sorted[i - 1].tpr) / 2;
computedAuc += w * h;
}
let optimalScoreThreshold = 0.0;
if (res.optimalThreshold > 0 && res.optimalThreshold < 1) {
const s = Math.log(res.optimalThreshold / (1 - res.optimalThreshold));
optimalScoreThreshold = Math.round(s * 10) / 10;
}
return {
rocData: res.points,
optimalThreshold: optimalScoreThreshold,
maxYouden: res.maxYouden,
auc: Math.round(Math.max(0.5, Math.min(0.99, computedAuc)) * 1000) / 1000
};
}, [eventsMatrix, lmmObservations, lmmResults]);
// 5. Dynamic Survival Curve Data for selected asset
const survivalData = useMemo(() => {
if (lmmResults?.survival) {
return lmmResults.survival.points;
}
const assetScores = eventsMatrix.map(ev => ev.scores[selectedSurvivalAsset] || 0);
const sumScore = assetScores.reduce((sum, s) => sum + s, 0);
const timesLong: number[] = [];
const eventsLong: number[] = [];
const timesShort: number[] = [];
const eventsShort: number[] = [];
// Simulate 15 historical events outcomes per direction
for (let i = 0; i < 15; i++) {
// LONG: Positive scores reduce time-to-event (gain target hit faster)
let tLong = 35 - sumScore * 3.5 + (Math.sin(i * 1.5) * 12);
let evLong = 1;
if (tLong > 60 || sumScore < -1) {
tLong = 60;
evLong = 0; // right censored
}
timesLong.push(Math.round(Math.max(3, tLong)));
eventsLong.push(evLong);
// SHORT: Negative scores reduce time-to-event (loss target hit faster)
let tShort = 35 + sumScore * 3.5 + (Math.cos(i * 1.9) * 12);
let evShort = 1;
if (tShort > 60 || sumScore > 1) {
tShort = 60;
evShort = 0; // right censored
}
timesShort.push(Math.round(Math.max(3, tShort)));
eventsShort.push(evShort);
}
const curveLong = calculateEventSurvival(timesLong, eventsLong, 'LONG');
const curveShort = calculateEventSurvival(timesShort, eventsShort, 'SHORT');
// Merge for chart mapping
const timeMap: Record<number, { time: number; longRate?: number; shortRate?: number }> = {};
for (let t = 0; t <= 60; t += 2) {
timeMap[t] = { time: t };
}
curveLong.forEach(pt => {
const roundedT = Math.round(pt.time / 2) * 2;
if (timeMap[roundedT]) timeMap[roundedT].longRate = pt.survivalRate;
});
curveShort.forEach(pt => {
const roundedT = Math.round(pt.time / 2) * 2;
if (timeMap[roundedT]) timeMap[roundedT].shortRate = pt.survivalRate;
});
const sortedMerged = Object.values(timeMap).sort((a, b) => a.time - b.time);
let lastLong = 1.0;
let lastShort = 1.0;
return sortedMerged.map(pt => {
const longRate = pt.longRate !== undefined ? pt.longRate : lastLong;
lastLong = longRate;
const shortRate = pt.shortRate !== undefined ? pt.shortRate : lastShort;
lastShort = shortRate;
return {
time: pt.time,
highConvRate: longRate,
lowConvRate: shortRate
};
});
}, [eventsMatrix, selectedSurvivalAsset, lmmResults]);
// Custom Event Handler
const handleAddCustomEvent = (e: React.FormEvent) => {
e.preventDefault();
let name = customName.trim();
let scores: Record<string, number> = {};
assets.forEach(asset => {
scores[asset.name] = 0;
});
if (selectedArchetype !== 'Custom') {
const arch = ARCHETYPES[selectedArchetype];
name = name || arch.name;
assets.forEach(asset => {
scores[asset.name] = typeof arch.defaultScores[asset.name] === 'number' ? arch.defaultScores[asset.name] : 0;
});
} else {
name = name || 'Custom Event';
}
addEventToMatrix(name, customDate, scores);
setCustomName('');
setSelectedArchetype('Custom');
};
// Calibration Action Trigger
const handleTriggerCalibration = () => {
setIsCalibrating(true);
// Simulate complex econometric iterative calibration
setTimeout(() => {
runEndogenousLMMCalibration();
setIsCalibrating(false);
setCalibrationSuccess(true);
setLastCalibrationTime(new Date().toLocaleTimeString());
setTimeout(() => {
setCalibrationSuccess(false);
}, 4000);
}, 1200);
};
return (
<div className="space-y-6 text-slate-100 font-sans">
{/* 1. Header with Model Selector */}
<div className="bg-slate-900/60 backdrop-blur-md border border-slate-800/80 rounded-2xl p-6 shadow-xl relative overflow-hidden">
<div className="absolute top-0 right-0 w-48 h-48 bg-rose-500/10 rounded-full blur-3xl -z-10" />
<div className="absolute bottom-0 left-0 w-32 h-32 bg-indigo-500/10 rounded-full blur-3xl -z-10" />
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
<div>
<div className="flex items-center gap-2 mb-1">
<span className="bg-rose-500/20 text-rose-400 border border-rose-500/30 text-[10px] px-2 py-0.5 rounded-full font-bold uppercase tracking-wider">
Element 5
</span>
<span className="text-slate-400 text-xs font-mono">Status: Calibrated & Active</span>
</div>
<h1 className="text-2xl md:text-3xl font-extrabold bg-gradient-to-r from-rose-400 via-purple-300 to-indigo-200 bg-clip-text text-transparent">
Advanced Econometric Event-Analysis Matrix
</h1>
<p className="text-slate-400 text-xs mt-1 max-w-2xl leading-relaxed">
Analyzes multi-asset cross-impact networks under logarithmic decay timelines. Evaluates predictive efficiency via ROC, models target boundaries with directional survival, and performs endogenous regressions via LMM feedback.
</p>
</div>
<div className="flex flex-wrap items-center gap-3 self-stretch md:self-auto justify-end">
<div className="flex bg-slate-950/80 p-1 rounded-xl border border-slate-800/80 justify-between gap-1">
<button
onClick={() => setSelectedModel('ROC')}
className={`flex-1 md:flex-none px-4 py-2 rounded-lg text-xs font-semibold transition-all flex items-center justify-center gap-2 ${
selectedModel === 'ROC'
? 'bg-rose-500 text-slate-950 shadow-[0_0_12px_rgba(244,63,94,0.3)] font-bold'
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-900/40'
}`}
>
<Compass className="w-3.5 h-3.5" /> ROC Analytics
</button>
<button
onClick={() => setSelectedModel('SURVIVAL')}
className={`flex-1 md:flex-none px-4 py-2 rounded-lg text-xs font-semibold transition-all flex items-center justify-center gap-2 ${
selectedModel === 'SURVIVAL'
? 'bg-rose-500 text-slate-950 shadow-[0_0_12px_rgba(244,63,94,0.3)] font-bold'
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-900/40'
}`}
>
<Activity className="w-3.5 h-3.5" /> Survival Curve
</button>
<button
onClick={() => setSelectedModel('LMM')}
className={`flex-1 md:flex-none px-4 py-2 rounded-lg text-xs font-semibold transition-all flex items-center justify-center gap-2 ${
selectedModel === 'LMM'
? 'bg-rose-500 text-slate-950 shadow-[0_0_12px_rgba(244,63,94,0.3)] font-bold'
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-900/40'
}`}
>
<GitMerge className="w-3.5 h-3.5" /> LMM Regression
</button>
</div>
<button
onClick={() => setIsMathModalOpen(true)}
className="flex items-center gap-1.5 px-3 py-2 rounded-lg bg-slate-950/80 hover:bg-slate-900 border border-slate-800 hover:border-slate-700 transition-all font-semibold text-xs tracking-wider text-rose-400"
>
<BookOpen className="w-3.5 h-3.5" />
<span>📖 Quantitative Handbook</span>
</button>
<button
onClick={() => setIsBlueprintModalOpen(true)}
className="flex items-center gap-1.5 px-3 py-2 rounded-lg bg-slate-950/80 hover:bg-slate-900 border border-slate-800 hover:border-slate-700 transition-all font-semibold text-xs tracking-wider text-rose-400 animate-pulse-slow"
>
<Compass className="w-3.5 h-3.5" />
<span> Operational Blueprint</span>
</button>
</div>
</div>
</div>
{/* 2. Main Dashboard: Clean View Matrix & Action Signals */}
<div className="grid grid-cols-1 xl:grid-cols-3 gap-6">
{/* Left/Middle Matrix & Settings (2/3 width) */}
<div className="xl:col-span-2 space-y-6">
{/* A. Event-Asset Matrix Table */}
<div className="bg-slate-900/50 backdrop-blur-md border border-slate-800/80 rounded-2xl p-6 shadow-xl relative">
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-4">
<h3 className="text-base font-bold flex items-center gap-2 text-rose-300">
<Database className="w-4 h-4 text-rose-400" /> Event Impact Matrix
</h3>
<div className="flex flex-wrap items-center gap-4">
<form onSubmit={handleAddAsset} className="flex items-center gap-1 bg-slate-950 p-1 rounded-lg border border-slate-800">
<input
type="text"
placeholder="Ticker (e.g. TSLA)"
value={newTickerInput}
onChange={(e) => setNewTickerInput(e.target.value)}
className="bg-transparent text-[11px] text-slate-200 focus:outline-none px-2 py-0.5 w-24 uppercase font-mono"
/>
<button
type="submit"
className="bg-rose-500 hover:bg-rose-600 text-slate-950 hover:text-slate-100 font-bold px-2 py-1 rounded text-[10px] flex items-center gap-1 transition-all"
>
<Plus className="w-3 h-3" /> Column
</button>
</form>
<div className="text-[10px] text-slate-400 font-mono flex items-center gap-2">
<span className="w-2.5 h-2.5 bg-rose-500/20 border border-rose-500/30 rounded inline-block text-center text-rose-400 font-bold leading-none">-</span>
<span>Bearish (-3)</span>
<span className="w-2.5 h-2.5 bg-emerald-500/20 border border-emerald-500/30 rounded inline-block text-center text-emerald-400 font-bold leading-none">+</span>
<span>Bullish (+3)</span>
</div>
</div>
</div>
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse text-xs">
<thead>
<tr className="border-b border-slate-800/60 text-slate-400 font-semibold">
<th className="py-3 px-3 min-w-[150px]">Makro-Ereignis</th>
<th className="py-3 px-3">Datum</th>
{assets.map(asset => (
<th key={asset.symbol} className="py-3 px-3 text-center group/header">
<div className="flex items-center justify-center gap-1">
<span>{asset.name}</span>
<button
onClick={() => handleRemoveAsset(asset.symbol)}
className="text-slate-500 hover:text-rose-400 p-0.5 rounded opacity-0 group-hover/header:opacity-100 transition-opacity"
title={`${asset.name} Column delete`}
>
<Trash2 className="w-3 h-3" />
</button>
</div>
<span className="block text-[9px] text-slate-500 font-mono font-normal">({asset.symbol})</span>
</th>
))}
<th className="py-3 px-3 text-right">Kernel-Gewicht & Aktionen</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-800/40">
{eventsMatrix.map((ev) => {
const { d, weight } = getWeightAndDays(ev.date);
return (
<tr key={ev.id} className="hover:bg-slate-950/20 transition-colors group">
<td className="py-3 px-3 font-semibold text-slate-200">
{ev.name}
</td>
<td className="py-3 px-3 text-slate-400 font-mono">
{ev.date}
<span className="block text-[10px] text-slate-500">
{d === 0 ? 'Today' : d > 0 ? `In ${d} days` : `${-d} days ago`}
</span>
</td>
{assets.map((asset) => {
const score = ev.scores[asset.name] || 0;
const isSuggested = (ev as any).isSuggestion?.[asset.name];
// Determine color style based on score value
let badgeStyle = 'text-slate-400 bg-slate-950 border-slate-800/60';
if (score > 1.5) badgeStyle = 'text-emerald-400 bg-emerald-950/30 border-emerald-800/50';
else if (score > 0) badgeStyle = 'text-teal-400 bg-teal-950/20 border-teal-900/30';
else if (score < -1.5) badgeStyle = 'text-rose-400 bg-rose-950/30 border-rose-800/50';
else if (score < 0) badgeStyle = 'text-orange-400 bg-orange-950/20 border-orange-900/30';
if (isSuggested) {
badgeStyle += ' border-dashed border-purple-400/80 bg-purple-950/20 shadow-[0_0_10px_rgba(168,85,247,0.15)]';
}
return (
<td key={asset.symbol} className="py-3 px-3 text-center">
<div className="inline-flex items-center gap-1.5 bg-slate-950/40 px-2 py-1 rounded-lg border border-slate-800/50">
<button
onClick={() => updateMatrixCell(ev.id, asset.name, Math.max(-3, score - 1))}
className="w-4 h-4 flex items-center justify-center rounded bg-slate-900 border border-slate-800 hover:bg-slate-800 hover:border-slate-700 text-slate-400 hover:text-slate-200 text-[10px] transition-all"
>
-
</button>
<span
className={`w-8 text-[11px] font-mono text-center px-1 rounded border ${badgeStyle}`}
title={isSuggested ? "Auto-calculated LMM Suggestion" : undefined}
>
{score > 0 ? `+${score}` : score}
</span>
<button
onClick={() => updateMatrixCell(ev.id, asset.name, Math.min(3, score + 1))}
className="w-4 h-4 flex items-center justify-center rounded bg-slate-900 border border-slate-800 hover:bg-slate-800 hover:border-slate-700 text-slate-400 hover:text-slate-200 text-[10px] transition-all"
>
+
</button>
{isSuggested && (
<button
onClick={() => updateMatrixCell(ev.id, asset.name, score)}
className="ml-0.5 p-0.5 rounded hover:bg-slate-800 text-purple-400 hover:text-purple-300 transition-colors"
title="Lock-in Suggestion"
>
<Check className="w-3 h-3" />
</button>
)}
</div>
</td>
);
})}
<td className="py-3 px-3 text-right">
<div className="flex items-center justify-end gap-3 font-mono">
<div className="flex flex-col items-end">
<span className="text-purple-400 font-semibold">{Math.round(weight * 100)}%</span>
<span className="text-[10px] text-slate-500">({weight.toFixed(2)})</span>
</div>
<button
onClick={() => deleteEventFromMatrix(ev.id)}
className="text-slate-500 hover:text-rose-400 p-1 hover:bg-slate-800/50 rounded transition-all"
title="Delete event from matrix"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
{/* Collapsible LMM Diagnostics Accordion */}
<div className="bg-slate-900/50 backdrop-blur-md border border-slate-800/80 rounded-2xl p-5 shadow-xl relative">
<button
onClick={() => setShowLmmDiagnostics(!showLmmDiagnostics)}
className="w-full flex justify-between items-center text-sm font-bold text-slate-200 hover:text-slate-100 transition-colors"
>
<span className="flex items-center gap-2 text-rose-300">
<span>📊</span> Advanced Statistical Diagnostics (LMM Output)
</span>
{showLmmDiagnostics ? <ChevronUp className="w-4 h-4 text-slate-400" /> : <ChevronDown className="w-4 h-4 text-slate-400" />}
</button>
{showLmmDiagnostics && (
<div className="mt-4 space-y-4 border-t border-slate-800/60 pt-4 text-xs">
<div className="flex justify-between items-center mb-2">
<span className="text-[10px] font-mono text-slate-400">Fixed Effects Coefficients</span>
<span className="text-[9px] font-mono text-slate-500">Signif. codes: 0 *** 0.001 ** 0.01 * 0.05</span>
</div>
<table className="w-full text-left text-[10px] font-mono text-slate-300">
<thead>
<tr className="border-b border-slate-800 text-slate-500 font-semibold">
<th className="py-1">Parameter</th>
<th className="py-1 text-right">Estimate</th>
<th className="py-1 text-right">Std. Error</th>
<th className="py-1 text-right">p-value</th>
<th className="py-1 text-center">Sig.</th>
<th className="py-1 text-right">95% Conf. Interval</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-850">
{lmmResults.fixedEffects.map((coeff) => (
<tr key={coeff.name} className="hover:bg-slate-900/40">
<td className="py-1.5 font-bold text-slate-400">{coeff.name}</td>
<td className="py-1.5 text-right text-emerald-400">{coeff.estimate.toFixed(4)}</td>
<td className="py-1.5 text-right">{coeff.se.toFixed(4)}</td>
<td className="py-1.5 text-right font-semibold">{coeff.pVal.toFixed(5)}</td>
<td className="py-1.5 text-center text-purple-400 font-bold">{coeff.sig}</td>
<td className="py-1.5 text-right text-slate-500">
[{coeff.ciLower.toFixed(4)}, {coeff.ciUpper.toFixed(4)}]
</td>
</tr>
))}
</tbody>
</table>
<div className="border-t border-slate-800/80 mt-3 pt-3 grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<div className="text-[10px] font-mono text-slate-400 mb-2">Random Intercepts (Asset-Specific Deviances)</div>
<div className="grid grid-cols-2 gap-2 text-[10px] font-mono">
{lmmResults.randomEffects.map((re) => (
<div key={re.asset} className="bg-slate-950/40 border border-slate-800/60 rounded p-2 flex flex-col gap-0.5">
<span className="text-slate-500 uppercase tracking-wide font-bold">{re.asset}</span>
<span className={`text-xs font-bold ${re.intercept >= 0 ? 'text-cyan-400' : 'text-orange-400'}`}>
{re.intercept >= 0 ? `+${re.intercept.toFixed(4)}` : re.intercept.toFixed(4)}
</span>
</div>
))}
</div>
</div>
<div>
<div className="text-[10px] font-mono text-slate-400 mb-2">Random Effects Variance (VIX / Event-Memory impact metrics)</div>
<div className="grid grid-cols-2 gap-2 text-[10px] font-mono">
<div className="bg-slate-950/40 border border-slate-800/60 rounded p-2 flex flex-col gap-0.5">
<span className="text-slate-500 uppercase tracking-wide font-bold">VIX Slope Variance</span>
<span className="text-xs font-bold text-indigo-400 font-mono">
{((lmmResults as any).randomEffectsVariance?.vixSlopeVar ?? 0.00002).toFixed(5)}
</span>
</div>
<div className="bg-slate-950/40 border border-slate-800/60 rounded p-2 flex flex-col gap-0.5">
<span className="text-slate-500 uppercase tracking-wide font-bold">Event-Memory Variance</span>
<span className="text-xs font-bold text-purple-400 font-mono">
{((lmmResults as any).randomEffectsVariance?.eventMemoryVar ?? 0.00005).toFixed(5)}
</span>
</div>
<div className="bg-slate-950/40 border border-slate-800/60 rounded p-2 flex flex-col gap-0.5">
<span className="text-slate-500 uppercase tracking-wide font-bold">Asset Intercept Var</span>
<span className="text-xs font-bold text-rose-400 font-mono">
{((lmmResults as any).randomEffectsVariance?.interceptVar ?? 0.00014).toFixed(5)}
</span>
</div>
<div className="bg-slate-950/40 border border-slate-800/60 rounded p-2 flex flex-col gap-0.5">
<span className="text-slate-500 uppercase tracking-wide font-bold">Residual Variance</span>
<span className="text-xs font-bold text-emerald-400 font-mono">
{((lmmResults as any).randomEffectsVariance?.residualVar ?? 0.00032).toFixed(5)}
</span>
</div>
</div>
</div>
</div>
<div className="border-t border-slate-800/80 mt-3 pt-3 flex justify-between text-[10px] font-mono text-slate-400">
<span><strong>AIC:</strong> <span className="text-slate-200">{lmmResults.aic}</span></span>
<span><strong>BIC:</strong> <span className="text-slate-200">{lmmResults.bic}</span></span>
<span><strong>Adj. R²:</strong> <span className="text-purple-400 font-bold">{(lmmResults.rSquared * 100).toFixed(1)}%</span></span>
<span><strong>Observations:</strong> <span className="text-slate-200">{lmmObservations.length}</span></span>
</div>
</div>
)}
</div>
{/* B. Add Event Form & Time Kernel Weights config (split) */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Form to Add Event */}
<div className="bg-slate-900/50 border border-slate-800/80 rounded-2xl p-5 shadow-lg">
<h4 className="text-sm font-bold text-slate-200 mb-3 flex items-center gap-2">
<Plus className="w-4 h-4 text-rose-400" /> Event hinzufügen
</h4>
<form onSubmit={handleAddCustomEvent} className="space-y-3.5 text-xs">
<div>
<label className="block text-slate-400 mb-1 text-[10px] uppercase font-semibold">Archetyp Vorlage</label>
<select
value={selectedArchetype}
onChange={(e) => {
setSelectedArchetype(e.target.value);
if (e.target.value !== 'Custom') {
setCustomName(ARCHETYPES[e.target.value].name);
} else {
setCustomName('');
}
}}
className="w-full bg-slate-950 border border-slate-800 rounded-lg p-2 text-slate-200 focus:outline-none focus:border-rose-500/50"
>
<option value="Custom">Benutzerdefiniert (Scores auf 0)</option>
{Object.keys(ARCHETYPES).map(key => (
<option key={key} value={key}>{key}</option>
))}
</select>
</div>
<div>
<label className="block text-slate-400 mb-1 text-[10px] uppercase font-semibold">Ereignis Name</label>
<input
type="text"
value={customName}
onChange={(e) => setCustomName(e.target.value)}
placeholder="z.B. OPEC Treffen"
className="w-full bg-slate-950 border border-slate-800 rounded-lg p-2 text-slate-200 focus:outline-none focus:border-rose-500/50 font-sans"
/>
</div>
<div>
<label className="block text-slate-400 mb-1 text-[10px] uppercase font-semibold">Event Datum</label>
<input
type="date"
value={customDate}
onChange={(e) => setCustomDate(e.target.value)}
className="w-full bg-slate-950 border border-slate-800 rounded-lg p-2 text-slate-200 focus:outline-none focus:border-rose-500/50 font-mono"
/>
</div>
<button
type="submit"
className="w-full bg-gradient-to-r from-rose-500 to-indigo-600 hover:from-rose-600 hover:to-indigo-700 text-slate-950 hover:text-slate-100 font-bold p-2.5 rounded-lg flex items-center justify-center gap-1.5 transition-all shadow-[0_4px_12px_rgba(244,63,94,0.15)]"
>
<Plus className="w-4 h-4" /> In Matrix aufnehmen
</button>
</form>
</div>
{/* Time Decay Kernel Sliders & Live Decay Curve Chart */}
<div className="bg-slate-900/50 border border-slate-800/80 rounded-2xl p-5 shadow-lg flex flex-col justify-between">
<div>
<h4 className="text-sm font-bold text-slate-200 mb-3 flex items-center gap-2">
<Sliders className="w-4 h-4 text-purple-400" /> Time-Kernel Decay Parameters
</h4>
<div className="space-y-4">
<div>
<div className="flex justify-between text-xs mb-1">
<span className="text-slate-400">Pre-Event Slope (<InlineMath math="\tau_{pre}" />)</span>
<span className="font-mono text-purple-400 font-bold">{tauPre} Tage</span>
</div>
<input
type="range"
min="1"
max="30"
value={tauPre}
onChange={(e) => setTauPre(Number(e.target.value))}
className="w-full accent-purple-500 bg-slate-950 h-1 rounded-lg appearance-none cursor-pointer"
/>
<span className="text-[10px] text-slate-500 block mt-0.5">Schnellerer Anstieg (kleinerer Wert) nähert sich dem Ereignis.</span>
</div>
<div>
<div className="flex justify-between text-xs mb-1">
<span className="text-slate-400">Post-Event Half-Life (<InlineMath math="\tau_{post}" />)</span>
<span className="font-mono text-purple-400 font-bold">{tauPost} Tage</span>
</div>
<input
type="range"
min="1"
max="20"
value={tauPost}
onChange={(e) => setTauPost(Number(e.target.value))}
className="w-full accent-purple-500 bg-slate-950 h-1 rounded-lg appearance-none cursor-pointer"
/>
<span className="text-[10px] text-slate-500 block mt-0.5">Langsameres Abklingen logarithmisch nach dem Stichtag.</span>
</div>
</div>
</div>
{/* Mini Kernel Chart showing bell curve of relevance */}
<div className="h-20 w-full mt-4 bg-slate-950/40 rounded-lg p-1 border border-slate-900">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={decayCurveData} margin={{ top: 0, right: 5, left: 5, bottom: 0 }}>
<Tooltip
contentStyle={{ backgroundColor: '#090d16', borderColor: '#1e293b', borderRadius: '6px', fontSize: '10px' }}
labelFormatter={(label) => `Relative Tage: ${label}`}
/>
<Area type="monotone" dataKey="weight" name="Gewicht" stroke="#a855f7" fill="rgba(168, 85, 247, 0.15)" strokeWidth={1.5} dot={false} />
<ReferenceLine x={0} stroke="#f43f5e" strokeDasharray="3 3" label={{ value: 'Event', fill: '#f43f5e', fontSize: 8, position: 'top' }} />
</AreaChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
{/* Right Sidebar: Suggestions & Final Action Signals (1/3 width) */}
<div className="space-y-6">
{/* Calendar Inbox Panel */}
<div className="bg-slate-900/50 backdrop-blur-md border border-slate-800/80 rounded-2xl p-5 shadow-xl">
<div className="flex items-center gap-2 mb-3">
<Calendar className="w-4 h-4 text-indigo-400" />
<h3 className="text-sm font-bold text-slate-200">Wirtschaftskalender Vorschläge</h3>
</div>
{activeProposals.length === 0 ? (
<div className="bg-slate-950/40 border border-slate-850 p-4 rounded-xl text-center text-slate-500 text-xs py-8">
Keine ausstehenden Vorschläge im Posteingang.
</div>
) : (
<div className="space-y-3 max-h-[200px] overflow-y-auto pr-1">
{activeProposals.map((cp) => (
<div key={cp.id} className="bg-slate-950/50 border border-slate-800/80 rounded-xl p-3 flex justify-between items-center text-xs group hover:border-indigo-500/30 transition-all">
<div>
<div className="font-semibold text-slate-200">{cp.name}</div>
<div className="text-[10px] text-slate-500 flex items-center gap-1.5 mt-0.5 font-mono">
<span>{cp.date}</span>
<span></span>
<span className="text-indigo-400">{cp.archetype}</span>
</div>
</div>
<button
onClick={() => addEventToMatrix(cp.name, cp.date, cp.defaultScores)}
className="bg-indigo-600 hover:bg-indigo-500 text-slate-950 hover:text-slate-100 font-bold p-1.5 rounded-lg flex items-center justify-center transition-all"
title="In Matrix aufnehmen"
>
<Plus className="w-3.5 h-3.5" />
</button>
</div>
))}
</div>
)}
</div>
{/* Action Signals Dashboard */}
<div className="bg-slate-900/50 backdrop-blur-md border border-slate-800/80 rounded-2xl p-5 shadow-xl">
<h3 className="text-sm font-bold text-slate-200 mb-3 flex items-center gap-2">
<Sparkles className="w-4 h-4 text-amber-400" /> Aggregated Trade Signals
</h3>
<div className="space-y-3">
{assets.map((asset) => {
const { netScore, signal, colorClass, textClass, glowClass } = actionSignals[asset.name] || {
netScore: 0,
signal: 'NEUTRAL / HOLD',
colorClass: 'bg-slate-800/40 border-slate-700/60 text-slate-400',
textClass: 'text-slate-400',
glowClass: 'shadow-slate-500/5'
};
return (
<div
key={asset.symbol}
className={`border rounded-xl p-3.5 transition-all flex flex-col justify-between gap-1 bg-gradient-to-br from-slate-950/80 to-slate-950/40 hover:scale-[1.01] border-slate-800/80 ${glowClass}`}
>
<div className="flex justify-between items-center">
<span className="font-bold text-slate-200 text-xs">{asset.name}</span>
<span className="text-[10px] font-mono text-slate-400">
Weighted Score: <span className={textClass}>{netScore > 0 ? `+${netScore}` : netScore}</span>
</span>
</div>
<div className="flex justify-between items-center mt-1">
<div className={`text-[10px] px-2 py-0.5 rounded border ${colorClass} font-semibold uppercase tracking-wider`}>
{signal}
</div>
<div className="text-[10px] text-slate-500 flex items-center gap-1 font-mono">
{netScore > 0.4 ? (
<TrendingUp className="w-3 h-3 text-emerald-400" />
) : netScore < -0.4 ? (
<TrendingDown className="w-3 h-3 text-rose-400" />
) : (
<AlertCircle className="w-3 h-3 text-slate-400" />
)}
<span>{netScore > 0 ? 'Bullish Drift' : netScore < 0 ? 'Bearish Risk' : 'Stationary'}</span>
</div>
</div>
</div>
);
})}
</div>
</div>
{/* LMM Feedback Trigger */}
<div className="bg-slate-900/50 backdrop-blur-md border border-slate-800/80 rounded-2xl p-5 shadow-xl text-center space-y-3 relative overflow-hidden">
<h3 className="text-sm font-bold text-slate-200">Endogenous Calibration</h3>
<p className="text-[11px] text-slate-400 leading-relaxed">
Updates manual matrix scores with optimal significant coefficients estimated from historical Linear Mixed Models, calibrating feedback parameters dynamically.
</p>
{calibrationSuccess && (
<div className="bg-emerald-950/30 border border-emerald-800/80 text-emerald-400 text-[10px] rounded-lg p-2 flex items-center gap-1.5 justify-center font-semibold">
<Check className="w-3.5 h-3.5" /> Calibration successfully completed!
</div>
)}
<button
disabled={isCalibrating}
onClick={handleTriggerCalibration}
className={`w-full py-2.5 px-4 rounded-lg text-xs font-bold font-mono border transition-all flex items-center justify-center gap-2 ${
isCalibrating
? 'bg-slate-800 border-slate-700 text-slate-400 cursor-not-allowed'
: 'bg-rose-500 hover:bg-rose-600 border-rose-600 text-slate-950 hover:text-slate-100 shadow-[0_0_12px_rgba(244,63,94,0.25)] hover:scale-[1.01]'
}`}
>
<RefreshCw className={`w-3.5 h-3.5 ${isCalibrating ? 'animate-spin' : ''}`} />
{isCalibrating ? 'Calibrating LMM...' : 'Trigger LMM Calibration'}
</button>
{lastCalibrationTime && (
<div className="text-[9px] text-slate-500 font-mono">
Last run: {lastCalibrationTime} ({lmmObservations.length} Obs.)
</div>
)}
</div>
</div>
</div>
{/* 3. Bottom: Econometric Charts & Show Math Panel */}
<div className="bg-slate-900/60 backdrop-blur-md border border-slate-800 rounded-2xl p-6 shadow-xl space-y-6">
{/* Model Tabs Header */}
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center border-b border-slate-800 pb-3 gap-2">
<div className="flex items-center gap-2">
<BarChart4 className="text-rose-400 w-5 h-5" />
<h3 className="text-base font-bold text-slate-100 uppercase tracking-wider">
{selectedModel === 'ROC' && 'ROC Model Diagnostics'}
{selectedModel === 'SURVIVAL' && 'Survival Analysis (Time-to-Event)'}
{selectedModel === 'LMM' && 'LMM Panel Regression Summary'}
</h3>
</div>
</div>
{/* Tab Content */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 items-center">
{/* Left Panel: Description & Stats Cards (1/3 width) */}
<div className="space-y-4">
{selectedModel === 'ROC' && (
<div className="space-y-3 text-xs">
<p className="text-slate-400 leading-relaxed">
The Receiver Operating Characteristic (ROC) curve evaluates classifier strength on binary events (e.g. Return &gt; +3.0% within 14 days). An AUC of 0.5 denotes a random baseline, while 1.0 represents a perfect oracle.
</p>
<div className="grid grid-cols-2 gap-3">
<div className="bg-slate-950/40 border border-slate-805 p-3 rounded-xl">
<span className="block text-[10px] text-slate-500 uppercase font-semibold">Area Under Curve (AUC)</span>
<span className="text-lg font-bold font-mono text-rose-400">{auc}</span>
</div>
<div className="bg-slate-950/40 border border-slate-805 p-3 rounded-xl">
<span className="block text-[10px] text-slate-500 uppercase font-semibold">Max Youden Index (J)</span>
<span className="text-lg font-bold font-mono text-rose-400">{maxYouden}</span>
</div>
<div className="bg-slate-950/40 border border-slate-805 p-3 rounded-xl col-span-2">
<span className="block text-[10px] text-slate-500 uppercase font-semibold">Optimal Youden Threshold</span>
<span className="text-sm font-bold font-mono text-slate-200">
{(() => {
const roundedVal = Math.round(optimalThreshold);
const displayVal = Object.is(roundedVal, -0) ? 0 : roundedVal;
return displayVal >= 0
? `Optimal Entry: Score >= +${displayVal}`
: `Optimal Entry: Score <= ${displayVal}`;
})()}
</span>
</div>
</div>
</div>
)}
{selectedModel === 'SURVIVAL' && (
<div className="space-y-3 text-xs">
<p className="text-slate-400 leading-relaxed">
Kaplan-Meier survival curves map the trend durability of historical events, measuring the number of days a trend remains active before reversing to the baseline asset noise, categorized by user conviction.
</p>
<div>
<label className="block text-slate-500 mb-1 text-[10px] uppercase font-semibold">Focus Asset</label>
<select
value={selectedSurvivalAsset}
onChange={(e) => setSelectedSurvivalAsset(e.target.value)}
className="w-full bg-slate-950 border border-slate-800 rounded-lg p-2 text-slate-200 focus:outline-none focus:border-rose-500/50"
>
{assets.map(asset => (
<option key={asset.symbol} value={asset.name}>{asset.name}</option>
))}
</select>
</div>
<div className="grid grid-cols-2 gap-3 mt-2">
<div className="bg-slate-950/40 border border-slate-805 p-3 rounded-xl">
<span className="block text-[10px] text-slate-500 uppercase font-semibold">Right Censoring</span>
<span className="text-sm font-bold text-slate-200 font-mono">30 days</span>
</div>
<div className="bg-slate-950/40 border border-slate-805 p-3 rounded-xl">
<span className="block text-[10px] text-slate-500 uppercase font-semibold">Observation Count</span>
<span className="text-sm font-bold text-slate-200 font-mono">
{lmmResults?.survival?.observationCount ?? 30} Event Runs
</span>
</div>
</div>
</div>
)}
{selectedModel === 'LMM' && (
<div className="space-y-3 text-xs">
<p className="text-slate-400 leading-relaxed">
Linear Mixed Model estimates the true impact of events on returns, isolating asset-level intercepts as random deviations. Standard Errors, t-stats, and p-values determine significance.
</p>
<div className="grid grid-cols-3 gap-2">
<div className="bg-slate-950/40 border border-slate-805 p-2 rounded-xl text-center">
<span className="block text-[9px] text-slate-500 uppercase font-semibold">AIC</span>
<span className="text-xs font-bold text-slate-300 font-mono">{lmmResults.aic}</span>
</div>
<div className="bg-slate-950/40 border border-slate-805 p-2 rounded-xl text-center">
<span className="block text-[9px] text-slate-500 uppercase font-semibold">BIC</span>
<span className="text-xs font-bold text-slate-300 font-mono">{lmmResults.bic}</span>
</div>
<div className="bg-slate-950/40 border border-slate-805 p-2 rounded-xl text-center">
<span className="block text-[9px] text-slate-500 uppercase font-semibold">Adj. R²</span>
<span className="text-xs font-bold text-purple-400 font-mono">{(lmmResults.rSquared * 100).toFixed(1)}%</span>
</div>
</div>
</div>
)}
</div>
{/* Right Panel: Charts / Regression Table (2/3 width) */}
<div className="lg:col-span-2 h-72 w-full flex items-center justify-center bg-slate-950/40 border border-slate-850 rounded-xl p-4">
{selectedModel === 'ROC' && (
<div className="w-full h-full">
<div className="text-[10px] font-mono text-slate-400 mb-2 text-center flex items-center justify-center gap-1.5">
<span>Model Classification Separation (FPR vs TPR)</span>
</div>
<ResponsiveContainer width="100%" height="90%">
<AreaChart data={rocData} margin={{ top: 10, right: 10, left: -25, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" />
<XAxis dataKey="fpr" stroke="#64748b" fontSize={9} tickFormatter={(v) => v.toFixed(1)} />
<YAxis stroke="#64748b" fontSize={9} domain={[0, 1.05]} />
<Tooltip
contentStyle={{ backgroundColor: '#090d16', borderColor: '#1e293b', borderRadius: '8px', fontSize: '10px' }}
formatter={(value: any) => [parseFloat(value).toFixed(2), 'True Positive Rate']}
/>
<Area type="monotone" dataKey="tpr" name="True Positive Rate" stroke="#f43f5e" fill="rgba(244, 63, 94, 0.12)" strokeWidth={2} />
{/* Diagonal baseline */}
<Line type="monotone" dataKey="fpr" stroke="#334155" strokeDasharray="4 4" dot={false} />
</AreaChart>
</ResponsiveContainer>
</div>
)}
{selectedModel === 'SURVIVAL' && (
<div className="w-full h-full flex flex-col justify-between">
<div className="text-[10px] font-mono text-slate-400 text-center flex items-center justify-center gap-4">
<span className="flex items-center gap-1.5"><span className="w-2 h-2 rounded-full bg-purple-400 inline-block"></span> High Conviction (|Score| &ge; 2)</span>
<span className="flex items-center gap-1.5"><span className="w-2 h-2 rounded-full bg-blue-400 inline-block"></span> Low Conviction (|Score| = 1)</span>
</div>
<div className="flex-1 w-full mt-2">
<ResponsiveContainer width="100%" height="95%">
<LineChart data={survivalData} margin={{ top: 5, right: 10, left: -25, bottom: 0 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" />
<XAxis dataKey="time" stroke="#64748b" fontSize={9} />
<YAxis stroke="#64748b" fontSize={9} domain={[0, 1.05]} tickFormatter={(v) => `${(v * 100).toFixed(0)}%`} />
<Tooltip
contentStyle={{ backgroundColor: '#090d16', borderColor: '#1e293b', borderRadius: '8px', fontSize: '10px' }}
formatter={(v: any) => `${(parseFloat(v) * 100).toFixed(1)}%`}
/>
<Line type="stepAfter" dataKey="highConvRate" name="High Conviction (|Score| >= 2)" stroke="#c084fc" strokeWidth={2} dot={false} />
<Line type="stepAfter" dataKey="lowConvRate" name="Low Conviction (|Score| = 1)" stroke="#60a5fa" strokeWidth={2} dot={false} />
</LineChart>
</ResponsiveContainer>
</div>
</div>
)}
{selectedModel === 'LMM' && (
<div className="w-full h-full overflow-y-auto pr-1">
<div className="flex justify-between items-center mb-2">
<span className="text-[10px] font-mono text-slate-400">Fixed Effects Coefficients</span>
<span className="text-[9px] font-mono text-slate-500">Signif. codes: 0 &lsquo;***&rsquo; 0.001 &lsquo;**&rsquo; 0.01 &lsquo;*&rsquo; 0.05</span>
</div>
<table className="w-full text-left text-[10px] font-mono text-slate-300">
<thead>
<tr className="border-b border-slate-800 text-slate-500 font-semibold">
<th className="py-1">Parameter</th>
<th className="py-1 text-right">Estimate</th>
<th className="py-1 text-right">Std. Error</th>
<th className="py-1 text-right">p-value</th>
<th className="py-1 text-center">Sig.</th>
<th className="py-1 text-right">95% Conf. Interval</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-850">
{lmmResults.fixedEffects.map((coeff) => (
<tr key={coeff.name} className="hover:bg-slate-900/40">
<td className="py-1.5 font-bold text-slate-400">{coeff.name}</td>
<td className="py-1.5 text-right text-emerald-400">{coeff.estimate.toFixed(4)}</td>
<td className="py-1.5 text-right">{coeff.se.toFixed(4)}</td>
<td className="py-1.5 text-right font-semibold">{coeff.pVal.toFixed(5)}</td>
<td className="py-1.5 text-center text-purple-400 font-bold">{coeff.sig}</td>
<td className="py-1.5 text-right text-slate-500">
[{coeff.ciLower.toFixed(4)}, {coeff.ciUpper.toFixed(4)}]
</td>
</tr>
))}
</tbody>
</table>
<div className="border-t border-slate-800/80 mt-3 pt-2 text-[9px] text-slate-400 flex flex-wrap gap-x-6 gap-y-1">
<span><strong className="text-slate-300">Random Effects Asset (intercepts):</strong></span>
{lmmResults.randomEffects.map((re) => (
<span key={re.asset}>
{re.asset}: <span className="text-cyan-400 font-semibold">{re.intercept > 0 ? `+${re.intercept.toFixed(4)}` : re.intercept.toFixed(4)}</span>
</span>
))}
</div>
<div className="text-[9px] text-slate-500 flex flex-wrap gap-x-6 gap-y-1 mt-1">
<span><strong className="text-slate-400">Random Effects Variance:</strong></span>
<span>VIX Slope Var: <span className="text-indigo-400 font-semibold">{((lmmResults as any).randomEffectsVariance?.vixSlopeVar ?? 0.00002).toFixed(5)}</span></span>
<span>Event-Memory Var: <span className="text-purple-400 font-semibold">{((lmmResults as any).randomEffectsVariance?.eventMemoryVar ?? 0.00005).toFixed(5)}</span></span>
<span>Asset Intercept Var: <span className="text-rose-400 font-semibold">{((lmmResults as any).randomEffectsVariance?.interceptVar ?? 0.00014).toFixed(5)}</span></span>
<span>Residual Var: <span className="text-emerald-400 font-semibold">{((lmmResults as any).randomEffectsVariance?.residualVar ?? 0.00032).toFixed(5)}</span></span>
</div>
</div>
)}
</div>
</div>
</div>
<EconometricsMathModal isOpen={isMathModalOpen} onClose={() => setIsMathModalOpen(false)} />
<EconometricsBlueprintModal isOpen={isBlueprintModalOpen} onClose={() => setIsBlueprintModalOpen(false)} />
</div>
);
}