Files
investment-sandbox/components/modules/crypto/CryptoDemo.tsx

508 lines
24 KiB
TypeScript

'use client';
import React, { useState, useMemo } from 'react';
import { useSandboxStore } from '@/lib/store';
import { predictCryptoTrend, calculateBetaPosterior } from '@/lib/math/statistics';
import 'katex/dist/katex.min.css';
import { BlockMath, InlineMath } from 'react-katex';
import CryptoMathModal from './CryptoMathModal';
import {
Cpu, Search, RefreshCw, BarChart2, TrendingUp, AlertCircle, Info,
ChevronDown, ChevronUp, ArrowUpRight, ArrowDownRight, Compass, ShieldAlert, Sparkles,
BookOpen
} from 'lucide-react';
interface CoinData {
ticker: string;
name: string;
price: string;
change24h: number;
fundingRate: number; // in %
openInterestChange: number; // in %
longShortRatio: number;
whaleInflow: number; // net flows
exchangeReserves: number; // in %
liqLong: string;
liqShort: string;
}
const defaultCoins: Record<string, CoinData> = {
'BTC': {
ticker: 'BTC',
name: 'Bitcoin',
price: '$69,450',
change24h: 2.4,
fundingRate: -0.015,
openInterestChange: 8.2,
longShortRatio: 0.92,
whaleInflow: 480,
exchangeReserves: -1.4,
liqLong: '$68,200',
liqShort: '$70,500'
},
'ETH': {
ticker: 'ETH',
name: 'Ethereum',
price: '$3,820',
change24h: -1.2,
fundingRate: 0.045,
openInterestChange: -3.5,
longShortRatio: 1.34,
whaleInflow: -120,
exchangeReserves: 0.8,
liqLong: '$3,710',
liqShort: '$3,920'
},
'SOL': {
ticker: 'SOL',
name: 'Solana',
price: '$184.20',
change24h: 5.8,
fundingRate: 0.082,
openInterestChange: 14.5,
longShortRatio: 1.62,
whaleInflow: 1250,
exchangeReserves: -2.8,
liqLong: '$176.00',
liqShort: '$192.50'
}
};
export default function CryptoDemo() {
const { alphaSuccess, betaFailure, addModelTrial } = useSandboxStore();
const [activeTicker, setActiveTicker] = useState<string>('BTC');
const [searchQuery, setSearchQuery] = useState('');
const [customCoins, setCustomCoins] = useState<Record<string, CoinData>>({});
const [searchError, setSearchError] = useState(false);
const [showMathAccordion, setShowMathAccordion] = useState(false);
const [isMathModalOpen, setIsMathModalOpen] = useState(false);
const [simulatedTrialLogged, setSimulatedTrialLogged] = useState(false);
const [lastTrialSuccess, setLastTrialSuccess] = useState(false);
// Active Coin data retrieval
const activeCoin = useMemo(() => {
return customCoins[activeTicker] || defaultCoins[activeTicker] || defaultCoins['BTC'];
}, [activeTicker, customCoins]);
// Compute live Random Forest baseline predictions
const mlPredictions = useMemo(() => {
const inputs = {
fundingRate: activeCoin.fundingRate,
openInterestChange: activeCoin.openInterestChange,
longShortRatio: activeCoin.longShortRatio,
whaleInflow: activeCoin.whaleInflow
};
return predictCryptoTrend(inputs);
}, [activeCoin]);
// Apply Bayesian online learning error-correction posterior update
const correctedPredictions = useMemo(() => {
// Correct short term probability
const shortTermCorrected = calculateBetaPosterior(
alphaSuccess,
betaFailure,
mlPredictions.shortTermProb,
12 // pseudo weight/confidence scale
);
// Correct medium term probability
const mediumTermCorrected = calculateBetaPosterior(
alphaSuccess,
betaFailure,
mlPredictions.mediumTermProb,
12
);
return {
shortTerm: shortTermCorrected,
mediumTerm: mediumTermCorrected
};
}, [mlPredictions, alphaSuccess, betaFailure]);
// Perform search check for Altcoins
const handleAltcoinSearch = (e: React.FormEvent) => {
e.preventDefault();
setSearchError(false);
const query = searchQuery.trim().toUpperCase();
if (!query) return;
if (defaultCoins[query]) {
setActiveTicker(query);
setSearchQuery('');
return;
}
if (customCoins[query]) {
setActiveTicker(query);
setSearchQuery('');
return;
}
// Generate simulated data for Altcoin
const isBull = Math.random() > 0.45;
const simulatedChange = isBull ? 3 + Math.random() * 8 : -2 - Math.random() * 6;
const simulatedPrice = isBull ? 2 + Math.random() * 10 : 0.2 + Math.random() * 3;
const newCoin: CoinData = {
ticker: query,
name: `${query} Token`,
price: `$${simulatedPrice.toFixed(4)}`,
change24h: parseFloat(simulatedChange.toFixed(2)),
fundingRate: parseFloat((Math.random() * 0.12 - 0.04).toFixed(3)),
openInterestChange: parseFloat((Math.random() * 30 - 10).toFixed(1)),
longShortRatio: parseFloat((0.8 + Math.random() * 1.1).toFixed(2)),
whaleInflow: Math.floor(Math.random() * 1500 - 400),
exchangeReserves: parseFloat((Math.random() * 4 - 2).toFixed(1)),
liqLong: `$${(simulatedPrice * 0.9).toFixed(4)}`,
liqShort: `$${(simulatedPrice * 1.1).toFixed(4)}`
};
setCustomCoins(prev => ({ ...prev, [query]: newCoin }));
setActiveTicker(query);
setSearchQuery('');
};
const handleSimulateTrial = (success: boolean) => {
addModelTrial(success);
setLastTrialSuccess(success);
setSimulatedTrialLogged(true);
setTimeout(() => setSimulatedTrialLogged(false), 2500);
};
const totalTrials = alphaSuccess + betaFailure;
const priorAccuracy = (alphaSuccess / (totalTrials || 1)) * 100;
return (
<div className="bg-slate-900/60 backdrop-blur-md border border-slate-800 rounded-2xl p-6 text-slate-100 shadow-xl relative overflow-hidden">
<div className="absolute top-0 right-0 w-32 h-32 bg-cyan-500/10 rounded-full blur-3xl -z-10" />
{/* Header */}
<div className="flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4 border-b border-slate-800 pb-4 mb-6">
<div>
<span className="text-cyan-400 text-xs font-semibold uppercase tracking-wider">Element 4</span>
<h2 className="text-2xl font-bold bg-gradient-to-r from-cyan-400 to-sky-200 bg-clip-text text-transparent">
Predictive Krypto-Modelle & Bayes Self-Correction
</h2>
</div>
<div className="flex flex-wrap items-center gap-3">
<span className="inline-flex items-center gap-1.5 px-3 py-2.5 rounded-xl text-xs font-bold bg-amber-500/10 text-amber-400 border border-amber-500/20 shadow-[0_0_15px_rgba(245,158,11,0.15)] h-11">
<ShieldAlert className="w-4 h-4 text-amber-400" />
<span>SYSTEM-AUTARK (OFFLINE-CORE)</span>
</span>
<button
onClick={() => setIsMathModalOpen(true)}
className="flex items-center gap-1.5 px-4 py-2 rounded-xl 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-cyan-400 justify-center h-11"
>
<BookOpen className="w-3.5 h-3.5" />
<span>📖 Modulerklärung</span>
</button>
<div className="bg-slate-900 border border-slate-800 rounded-xl px-4 py-2 flex items-center gap-3 h-11">
<Cpu className="text-cyan-400 w-5 h-5 animate-spin-slow" />
<div>
<p className="text-slate-400 text-xs">A-Priori Genauigkeit</p>
<p className="font-mono text-sm font-bold text-cyan-400">
{priorAccuracy.toFixed(1)}% (n={totalTrials})
</p>
</div>
</div>
</div>
</div>
{/* SECTION 1: Top 3 Cards & Search Mask */}
<div className="grid grid-cols-1 lg:grid-cols-4 gap-4 mb-6">
{/* Status Cards BTC, ETH, SOL */}
{['BTC', 'ETH', 'SOL'].map((tick) => {
const coin = defaultCoins[tick];
const isActive = activeTicker === tick;
const isUp = coin.change24h >= 0;
return (
<div
key={tick}
onClick={() => setActiveTicker(tick)}
className={`p-4 rounded-xl border cursor-pointer transition-all hover:bg-slate-850 flex items-center justify-between relative overflow-hidden ${isActive ? 'border-cyan-500/40 bg-cyan-500/5 shadow-md shadow-cyan-500/5' : 'border-slate-850 bg-slate-950/20'}`}
>
<div>
<div className="text-xs text-slate-400 font-semibold">{coin.name}</div>
<div className="text-xl font-extrabold font-mono mt-1 text-slate-100">{coin.price}</div>
<div className={`text-[10px] font-bold font-mono mt-0.5 flex items-center gap-0.5 ${isUp ? 'text-emerald-400' : 'text-rose-400'}`}>
{isUp ? <ArrowUpRight className="w-3.5 h-3.5" /> : <ArrowDownRight className="w-3.5 h-3.5" />}
<span>{isUp ? '+' : ''}{coin.change24h}%</span>
</div>
</div>
<div className="text-right">
<span className="text-2xl font-bold font-mono text-slate-800">{tick}</span>
</div>
</div>
);
})}
{/* Custom Search bar */}
<div className="p-4 rounded-xl border border-slate-850 bg-slate-950/20 flex flex-col justify-center gap-2">
<form onSubmit={handleAltcoinSearch} className="flex gap-2">
<input
type="text"
required
placeholder="Altcoin Ticker (z.B. LINK)"
className="bg-slate-900 border border-slate-800 rounded-lg p-2 flex-1 text-slate-100 font-mono text-xs uppercase focus:outline-none focus:border-cyan-500"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
<button
type="submit"
className="bg-slate-800 hover:bg-slate-700 text-cyan-400 hover:text-cyan-350 font-bold px-3 py-2 border border-slate-700 rounded-lg transition-colors text-xs"
>
<Search className="w-4 h-4" />
</button>
</form>
{Object.keys(customCoins).length > 0 && (
<div className="flex flex-wrap gap-1.5 mt-1 overflow-x-auto max-h-[32px]">
{Object.keys(customCoins).map(tick => (
<button
key={tick}
onClick={() => setActiveTicker(tick)}
className={`px-2 py-0.5 rounded font-mono text-[9px] border ${activeTicker === tick ? 'bg-cyan-500/10 text-cyan-400 border-cyan-500/30' : 'bg-slate-900 text-slate-500 border-slate-800'}`}
>
{tick}
</button>
))}
</div>
)}
</div>
</div>
{/* SECTION 2: Derivatives & On-Chain Metrics Ledger */}
<div className="grid grid-cols-1 xl:grid-cols-3 gap-8">
{/* Left Column: Metrics Widgets */}
<div className="xl:col-span-2 space-y-6">
<h3 className="text-base font-bold text-white flex items-center gap-2 border-b border-slate-800 pb-3">
<BarChart2 className="text-cyan-400 w-5 h-5" /> On-Chain &amp; Derivate-Indikatoren ({activeCoin.ticker})
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{/* Funding & Open Interest Widget */}
<div className="p-4 rounded-xl border border-slate-850 bg-slate-950/40 space-y-3">
<h4 className="text-xs font-semibold text-slate-400 uppercase tracking-wider">Ref-Zinssatz &amp; Kontrakte (OI)</h4>
<div className="space-y-2">
<div className="flex justify-between items-center text-sm font-mono">
<span className="text-slate-400 text-xs">Daily Funding Rate:</span>
<span className={`font-bold ${activeCoin.fundingRate < 0 ? 'text-emerald-400' : 'text-rose-400'}`}>
{activeCoin.fundingRate > 0 ? '+' : ''}{activeCoin.fundingRate.toFixed(3)}%
</span>
</div>
<div className="flex justify-between items-center text-sm font-mono">
<span className="text-slate-400 text-xs">Open Interest (24h Δ):</span>
<span className={`font-bold ${activeCoin.openInterestChange >= 0 ? 'text-emerald-400' : 'text-rose-400'}`}>
{activeCoin.openInterestChange > 0 ? '+' : ''}{activeCoin.openInterestChange.toFixed(1)}%
</span>
</div>
</div>
</div>
{/* Long/Short & Liquidation Widget */}
<div className="p-4 rounded-xl border border-slate-850 bg-slate-950/40 space-y-3">
<h4 className="text-xs font-semibold text-slate-400 uppercase tracking-wider">Positionierung &amp; Liquidationen</h4>
<div className="space-y-2">
<div className="flex justify-between items-center text-sm font-mono">
<span className="text-slate-400 text-xs">Long / Short Ratio:</span>
<span className="text-slate-200 font-bold">{activeCoin.longShortRatio}</span>
</div>
<div className="flex justify-between items-center text-xs font-mono">
<span className="text-slate-400">Liq-Cluster:</span>
<span className="text-rose-400">Long: {activeCoin.liqLong} | Short: {activeCoin.liqShort}</span>
</div>
</div>
</div>
{/* Whale Flows Widget */}
<div className="p-4 rounded-xl border border-slate-850 bg-slate-950/40 space-y-3">
<h4 className="text-xs font-semibold text-slate-400 uppercase tracking-wider">Whale-Ströme (Nettozufluss)</h4>
<div className="space-y-2">
<div className="flex justify-between items-center text-sm font-mono">
<span className="text-slate-400 text-xs">Netto Inflow (Wallets):</span>
<span className={`font-bold ${activeCoin.whaleInflow >= 0 ? 'text-emerald-400' : 'text-rose-400'}`}>
{activeCoin.whaleInflow > 0 ? '+' : ''}{activeCoin.whaleInflow} {activeCoin.ticker}
</span>
</div>
<div className="text-[10px] text-slate-500 leading-relaxed font-sans">
Positive Werte signalisieren, dass Großinvestoren Bestände von Börsen auf private Wallets (Akkumulation) abziehen.
</div>
</div>
</div>
{/* Exchange Reserves Widget */}
<div className="p-4 rounded-xl border border-slate-850 bg-slate-950/40 space-y-3">
<h4 className="text-xs font-semibold text-slate-400 uppercase tracking-wider">Börsenreserven (Spot)</h4>
<div className="space-y-2">
<div className="flex justify-between items-center text-sm font-mono">
<span className="text-slate-400 text-xs">Reservenänderung (7d):</span>
<span className={`font-bold ${activeCoin.exchangeReserves <= 0 ? 'text-emerald-400' : 'text-rose-400'}`}>
{activeCoin.exchangeReserves > 0 ? '+' : ''}{activeCoin.exchangeReserves}%
</span>
</div>
<div className="text-[10px] text-slate-500 leading-relaxed font-sans">
Sinkende Reserven an den Spot-Börsen reduzieren den verfügbaren Verkaufsdruck und begünstigen Squeezes.
</div>
</div>
</div>
</div>
</div>
{/* Right Column: Predictive Gauges & Correction Calibration */}
<div className="bg-slate-900/60 backdrop-blur-md border border-slate-800 rounded-2xl p-6 text-slate-100 shadow-xl space-y-6">
<h3 className="text-base font-bold text-white flex items-center gap-2 border-b border-slate-800 pb-3">
<Compass className="text-cyan-400 w-5 h-5" /> Vorhersage-Wahrscheinlichkeiten
</h3>
{/* Gauges / Progress Bars */}
<div className="space-y-4">
{/* 24h Gauge */}
<div className="space-y-2">
<div className="flex justify-between text-xs font-semibold">
<span className="text-slate-300">24h Volatility Squeeze (Short-Term)</span>
<span className="text-cyan-400 font-mono">{(correctedPredictions.shortTerm * 100).toFixed(0)}%</span>
</div>
<div className="w-full bg-slate-950 rounded-full h-3 overflow-hidden border border-slate-850 flex">
{/* ML Baseline Overlay */}
<div
className="bg-cyan-500 h-full rounded-l transition-all duration-500 opacity-30"
style={{ width: `${mlPredictions.shortTermProb * 100}%` }}
/>
{/* Corrected Posterior Marker */}
<div
className="bg-cyan-400 h-full rounded-r transition-all duration-500 -ml-[20%] shadow-[0_0_10px_rgba(34,211,238,0.5)]"
style={{ width: `${correctedPredictions.shortTerm * 100}%` }}
/>
</div>
<div className="flex justify-between text-[9px] text-slate-500 font-mono">
<span>ML-Signal: {(mlPredictions.shortTermProb * 100).toFixed(0)}%</span>
<span className="text-cyan-400">Bayes-Korrigiert: {(correctedPredictions.shortTerm * 100).toFixed(0)}%</span>
</div>
</div>
{/* 14d Gauge */}
<div className="space-y-2">
<div className="flex justify-between text-xs font-semibold">
<span className="text-slate-300">14d Struktureller Bullish-Trend (Medium-Term)</span>
<span className="text-teal-400 font-mono">{(correctedPredictions.mediumTerm * 100).toFixed(0)}%</span>
</div>
<div className="w-full bg-slate-950 rounded-full h-3 overflow-hidden border border-slate-850 flex">
<div
className="bg-teal-500 h-full rounded-l transition-all duration-500 opacity-30"
style={{ width: `${mlPredictions.mediumTermProb * 100}%` }}
/>
<div
className="bg-teal-400 h-full rounded-r transition-all duration-500 -ml-[20%] shadow-[0_0_10px_rgba(45,212,191,0.5)]"
style={{ width: `${correctedPredictions.mediumTerm * 100}%` }}
/>
</div>
<div className="flex justify-between text-[9px] text-slate-500 font-mono">
<span>ML-Signal: {(mlPredictions.mediumTermProb * 100).toFixed(0)}%</span>
<span className="text-teal-400">Bayes-Korrigiert: {(correctedPredictions.mediumTerm * 100).toFixed(0)}%</span>
</div>
</div>
</div>
{/* Model Calibration Log & Simulation */}
<div className="p-4 rounded-xl border border-slate-850 bg-slate-950/40 space-y-3">
<div className="flex items-center justify-between">
<h4 className="text-xs font-bold text-slate-300 uppercase">Bayes Modell-Kalibrierung</h4>
<span className="text-[10px] text-slate-500 font-mono">n = {totalTrials} Trials</span>
</div>
<div className="grid grid-cols-2 gap-2 text-xs font-mono pb-2 border-b border-slate-900">
<div className="text-slate-400">Erfolge (&alpha;):</div>
<div className="text-emerald-400 font-bold">{alphaSuccess}</div>
<div className="text-slate-400">Fehlalarme (&beta;):</div>
<div className="text-rose-400 font-bold">{betaFailure}</div>
</div>
{/* Trial simulator */}
<div className="space-y-2">
<p className="text-[10px] text-slate-400">Modell-Drift simulieren: Fügen Sie richtige/falsche Outcomes hinzu, um die Beta-Verteilung anzupassen.</p>
<div className="flex gap-2">
<button
onClick={() => handleSimulateTrial(true)}
className="flex-1 bg-emerald-500/10 hover:bg-emerald-500/20 text-emerald-400 border border-emerald-500/20 py-1.5 rounded-lg text-xs font-semibold font-mono"
>
+1 Erfolg
</button>
<button
onClick={() => handleSimulateTrial(false)}
className="flex-1 bg-rose-500/10 hover:bg-rose-500/20 text-rose-400 border border-rose-500/20 py-1.5 rounded-lg text-xs font-semibold font-mono"
>
+1 Fehlalarm
</button>
</div>
{simulatedTrialLogged && (
<div className="text-[10px] text-cyan-400 font-mono text-center animate-pulse">
Trial geloggt! Bayes-Prior wurde auf {lastTrialSuccess ? 'Erfolg' : 'Fehlalarm'} aktualisiert.
</div>
)}
</div>
</div>
</div>
</div>
{/* SECTION 3: Mathematical LaTeX Accordion */}
<div className="border-t border-slate-850 pt-4 mt-6">
<button
onClick={() => setShowMathAccordion(!showMathAccordion)}
className="flex items-center gap-1.5 text-xs text-slate-400 hover:text-cyan-400 transition-colors focus:outline-none"
>
<span>{showMathAccordion ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}</span>
<span className="font-semibold uppercase tracking-wider">Mathematische Formulierung (Beta-Update &amp; Random Forest)</span>
</button>
{showMathAccordion && (
<div className="mt-4 p-4 rounded-xl border border-slate-850 bg-slate-950/40 text-xs text-slate-300 space-y-4">
<div>
<h4 className="font-bold text-cyan-400 mb-1">1. Bayesianische Beta-Konjugierte Fehlerkorrektur</h4>
<p className="mb-2">
Wir modellieren das Vertrauensintervall über die Fehlerraten des Modells mittels einer Beta-Verteilung. Der A-Priori Fehlerzustand wird durch die Parameter <InlineMath math="\alpha" /> (Erfolge) und <InlineMath math="\beta" /> (Fehlalarme) dargestellt:
</p>
<div className="py-2 overflow-x-auto text-slate-200">
<BlockMath math="P \sim \text{Beta}(\alpha, \beta) \quad \text{mit Erwartungswert } \mathbb{E}[P] = \frac{\alpha}{\alpha + \beta}" />
</div>
<p className="mb-2">
Bei einem neuen ML-Signal <InlineMath math="P_{\text{ML}}" /> führen wir ein konjugiertes Bayes-Update mit einem Vertrauensgewicht <InlineMath math="w" /> aus:
</p>
<div className="py-2 overflow-x-auto text-slate-200">
<BlockMath math="\alpha_{\text{post}} = \alpha + w \cdot P_{\text{ML}}, \quad \beta_{\text{post}} = \beta + w \cdot (1 - P_{\text{ML}})" />
</div>
<div className="py-2 overflow-x-auto text-slate-200">
<BlockMath math="P_{\text{Posterior}} = \frac{\alpha_{\text{post}}}{\alpha_{\text{post}} + \beta_{\text{post}}}" />
</div>
<p className="text-slate-400">
Ist das Modell historisch sehr instabil (hohes <InlineMath math="\beta" />), korrigiert der Bayesianische Term ein übermütiges ML-Signal nach unten, was die Robustheit des Gesamtsystems schützt.
</p>
</div>
<div className="border-t border-slate-900 pt-3">
<h4 className="font-bold text-cyan-400 mb-1">2. Random Forest Nicht-Lineares Signalmapping</h4>
<p className="mb-2">
Der Random Forest simuliert ein Ensemble von 10 schwachen Entscheidungsbäumen. Jeder Baum spaltet die Daten nach Schwellenwerten (z.B. &bdquo;Funding-Rate &lt; -0.04%&ldquo; und &bdquo;Open Interest &gt; 10%&ldquo;) auf:
</p>
<div className="py-2 overflow-x-auto text-slate-200">
<BlockMath math="\text{ML}_{\text{prob}} = \frac{1}{M} \sum_{m=1}^{M} T_m(\mathbf{x})" />
</div>
<p className="text-slate-400">
wobei <InlineMath math="T_m(\mathbf{x})" /> der prognostizierte Ausgabewert des <InlineMath math="m" />-ten Entscheidungsbaumes für den Featurevektor <InlineMath math="\mathbf{x}" /> ist.
</p>
</div>
</div>
)}
</div>
<CryptoMathModal isOpen={isMathModalOpen} onClose={() => setIsMathModalOpen(false)} />
</div>
);
}