790 lines
34 KiB
TypeScript
790 lines
34 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, Check
|
|
} 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'
|
|
}
|
|
};
|
|
|
|
interface Forecast {
|
|
id: string;
|
|
ticker: string;
|
|
predictedDirection: 'UP' | 'DOWN';
|
|
predictedProb: number;
|
|
entryPrice: number;
|
|
resolved: boolean;
|
|
result?: 'SUCCESS' | 'FAILURE';
|
|
timestamp: number;
|
|
targetTime: number;
|
|
}
|
|
|
|
export default function CryptoDemo() {
|
|
const { addModelTrial } = useSandboxStore();
|
|
|
|
// Local state for alphaSuccess and betaFailure to satisfy SSR hydration safeguarding
|
|
const [alphaSuccess, setAlphaSuccess] = useState<number>(394);
|
|
const [betaFailure, setBetaFailure] = useState<number>(118);
|
|
const [forecasts, setForecasts] = useState<Forecast[]>([]);
|
|
const [learningLoopLog, setLearningLoopLog] = useState<string>('');
|
|
|
|
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);
|
|
|
|
// Safely load counters and forecasts from localStorage on client mount
|
|
React.useEffect(() => {
|
|
const savedAlpha = localStorage.getItem('crypto_bayes_alpha');
|
|
const savedBeta = localStorage.getItem('crypto_bayes_beta');
|
|
const savedForecasts = localStorage.getItem('crypto_bayes_forecasts');
|
|
|
|
let loadedAlpha = 394;
|
|
let loadedBeta = 118;
|
|
|
|
if (savedAlpha !== null) {
|
|
loadedAlpha = parseInt(savedAlpha, 10);
|
|
setAlphaSuccess(loadedAlpha);
|
|
} else {
|
|
localStorage.setItem('crypto_bayes_alpha', '394');
|
|
}
|
|
|
|
if (savedBeta !== null) {
|
|
loadedBeta = parseInt(savedBeta, 10);
|
|
setBetaFailure(loadedBeta);
|
|
} else {
|
|
localStorage.setItem('crypto_bayes_beta', '118');
|
|
}
|
|
|
|
if (savedForecasts !== null) {
|
|
setForecasts(JSON.parse(savedForecasts));
|
|
} else {
|
|
const now = Date.now();
|
|
const mockForecasts: Forecast[] = [
|
|
{
|
|
id: 'mock-1',
|
|
ticker: 'BTC',
|
|
predictedDirection: 'UP',
|
|
predictedProb: 0.68,
|
|
entryPrice: 65000,
|
|
resolved: true,
|
|
result: 'SUCCESS',
|
|
timestamp: now - 86400 * 1000 * 3,
|
|
targetTime: now - 86400 * 1000 * 2,
|
|
},
|
|
{
|
|
id: 'mock-2',
|
|
ticker: 'ETH',
|
|
predictedDirection: 'DOWN',
|
|
predictedProb: 0.35,
|
|
entryPrice: 3950,
|
|
resolved: true,
|
|
result: 'SUCCESS',
|
|
timestamp: now - 86400 * 1000 * 3,
|
|
targetTime: now - 86400 * 1000 * 2,
|
|
},
|
|
{
|
|
id: 'mock-3',
|
|
ticker: 'SOL',
|
|
predictedDirection: 'UP',
|
|
predictedProb: 0.72,
|
|
entryPrice: 170,
|
|
resolved: true,
|
|
result: 'SUCCESS',
|
|
timestamp: now - 86400 * 1000 * 2,
|
|
targetTime: now - 86400 * 1000 * 1,
|
|
},
|
|
{
|
|
id: 'mock-4',
|
|
ticker: 'BTC',
|
|
predictedDirection: 'UP',
|
|
predictedProb: 0.62,
|
|
entryPrice: 71000,
|
|
resolved: true,
|
|
result: 'FAILURE',
|
|
timestamp: now - 86400 * 1000 * 2,
|
|
targetTime: now - 86400 * 1000 * 1,
|
|
},
|
|
{
|
|
id: 'mock-5',
|
|
ticker: 'ETH',
|
|
predictedDirection: 'UP',
|
|
predictedProb: 0.58,
|
|
entryPrice: 3900,
|
|
resolved: true,
|
|
result: 'FAILURE',
|
|
timestamp: now - 86400 * 1000 * 2,
|
|
targetTime: now - 86400 * 1000 * 1,
|
|
}
|
|
];
|
|
setForecasts(mockForecasts);
|
|
localStorage.setItem('crypto_bayes_forecasts', JSON.stringify(mockForecasts));
|
|
}
|
|
}, []);
|
|
|
|
// Client-side background learning loop evaluating forecasts against actual live returns
|
|
React.useEffect(() => {
|
|
const runLearningLoop = async () => {
|
|
try {
|
|
const res = await fetch('/api/finance?region=crypto');
|
|
if (!res.ok) return;
|
|
const data = await res.ok ? await res.json() : { results: [] };
|
|
const results = data.results || [];
|
|
|
|
const pricesMap: Record<string, number> = {};
|
|
results.forEach((r: any) => {
|
|
pricesMap[r.ticker] = r.currentPrice;
|
|
const cleanTicker = r.ticker.replace('-USD', '');
|
|
pricesMap[cleanTicker] = r.currentPrice;
|
|
});
|
|
|
|
let updatedAny = false;
|
|
let newAlpha = alphaSuccess;
|
|
let newBeta = betaFailure;
|
|
|
|
const updatedForecasts = forecasts.map((f) => {
|
|
if (f.resolved) return f;
|
|
|
|
const currentPrice = pricesMap[f.ticker] || pricesMap[`${f.ticker}-USD`];
|
|
if (!currentPrice) return f;
|
|
|
|
const now = Date.now();
|
|
if (now >= f.targetTime) {
|
|
const priceWentUp = currentPrice > f.entryPrice;
|
|
const success = (f.predictedDirection === 'UP' && priceWentUp) || (f.predictedDirection === 'DOWN' && !priceWentUp);
|
|
|
|
updatedAny = true;
|
|
if (success) {
|
|
newAlpha += 1;
|
|
} else {
|
|
newBeta += 1;
|
|
}
|
|
|
|
addModelTrial(success);
|
|
|
|
return {
|
|
...f,
|
|
resolved: true,
|
|
result: success ? ('SUCCESS' as const) : ('FAILURE' as const)
|
|
};
|
|
}
|
|
return f;
|
|
});
|
|
|
|
if (updatedAny) {
|
|
setAlphaSuccess(newAlpha);
|
|
setBetaFailure(newBeta);
|
|
setForecasts(updatedForecasts);
|
|
localStorage.setItem('crypto_bayes_alpha', String(newAlpha));
|
|
localStorage.setItem('crypto_bayes_beta', String(newBeta));
|
|
localStorage.setItem('crypto_bayes_forecasts', JSON.stringify(updatedForecasts));
|
|
setLearningLoopLog(`Processed active forecasts. New successes: ${newAlpha}, New failures: ${newBeta}`);
|
|
setTimeout(() => setLearningLoopLog(''), 6000);
|
|
}
|
|
} catch (err) {
|
|
console.error("Learning loop evaluation error:", err);
|
|
}
|
|
};
|
|
|
|
if (forecasts.length > 0) {
|
|
runLearningLoop();
|
|
}
|
|
const interval = setInterval(runLearningLoop, 30000);
|
|
return () => clearInterval(interval);
|
|
}, [forecasts, alphaSuccess, betaFailure, addModelTrial]);
|
|
|
|
// 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(() => {
|
|
const shortTermCorrected = calculateBetaPosterior(
|
|
alphaSuccess,
|
|
betaFailure,
|
|
mlPredictions.shortTermProb,
|
|
12 // confidence scale
|
|
);
|
|
const mediumTermCorrected = calculateBetaPosterior(
|
|
alphaSuccess,
|
|
betaFailure,
|
|
mlPredictions.mediumTermProb,
|
|
12
|
|
);
|
|
|
|
return {
|
|
shortTerm: shortTermCorrected,
|
|
mediumTerm: mediumTermCorrected
|
|
};
|
|
}, [mlPredictions, alphaSuccess, betaFailure]);
|
|
|
|
// Search/add new altcoin
|
|
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;
|
|
}
|
|
|
|
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('');
|
|
};
|
|
|
|
// Manual logging of active forecast
|
|
const handleLogManualForecast = () => {
|
|
const entryPrice = parseFloat(activeCoin.price.replace(/[^0-9.]/g, ''));
|
|
const predictedDirection = correctedPredictions.shortTerm > 0.5 ? 'UP' : 'DOWN';
|
|
const predictedProb = correctedPredictions.shortTerm;
|
|
|
|
const newForecast: Forecast = {
|
|
id: 'fc-' + Date.now(),
|
|
ticker: activeCoin.ticker,
|
|
predictedDirection,
|
|
predictedProb,
|
|
entryPrice,
|
|
resolved: false,
|
|
timestamp: Date.now(),
|
|
targetTime: Date.now() + 60 * 1000 // resolves in 60s for direct visual validation
|
|
};
|
|
|
|
const nextForecasts = [newForecast, ...forecasts];
|
|
setForecasts(nextForecasts);
|
|
localStorage.setItem('crypto_bayes_forecasts', JSON.stringify(nextForecasts));
|
|
|
|
setLearningLoopLog(`Registered active forecast for ${activeCoin.ticker} at $${entryPrice}. Evaluating returns in 60 seconds.`);
|
|
setTimeout(() => setLearningLoopLog(''), 6000);
|
|
};
|
|
|
|
// Simulator for calibration
|
|
const handleSimulateTrial = (success: boolean) => {
|
|
addModelTrial(success);
|
|
setAlphaSuccess(prev => {
|
|
const next = success ? prev + 1 : prev;
|
|
localStorage.setItem('crypto_bayes_alpha', String(next));
|
|
return next;
|
|
});
|
|
setBetaFailure(prev => {
|
|
const next = !success ? prev + 1 : prev;
|
|
localStorage.setItem('crypto_bayes_beta', String(next));
|
|
return next;
|
|
});
|
|
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">Level 4</span>
|
|
<h2 className="text-2xl font-bold bg-gradient-to-r from-cyan-400 to-sky-200 bg-clip-text text-transparent">
|
|
Predictive Crypto Models & 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>📖 Quantitative Handbook</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">Prior Accuracy</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 (e.g. 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 & Derivative Indicators ({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">Funding Rates & Open Interest</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">Positioning & Liquidations</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 Flows (Net Inflow)</h4>
|
|
<div className="space-y-2">
|
|
<div className="flex justify-between items-center text-sm font-mono">
|
|
<span className="text-slate-400 text-xs">Net 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 values signal that large investors are withdrawing assets from exchanges to private wallets (accumulation).
|
|
</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">Exchange Reserves (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">Reserve Change (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">
|
|
Falling reserves at spot exchanges reduce available selling pressure and favor 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" /> Prediction Probabilities
|
|
</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">
|
|
<div
|
|
className="bg-cyan-500 h-full rounded-l transition-all duration-500 opacity-30"
|
|
style={{ width: `${mlPredictions.shortTermProb * 100}%` }}
|
|
/>
|
|
<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 Corrected: {(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 Structural 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 Corrected: {(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 Model Calibration</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">Successes (α):</div>
|
|
<div className="text-emerald-400 font-bold">{alphaSuccess}</div>
|
|
<div className="text-slate-400">False Alarms (β):</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">Simulate model drift: Add correct/false outcomes to calibrate the Beta distribution.</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 Success
|
|
</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 False Alarm
|
|
</button>
|
|
</div>
|
|
{simulatedTrialLogged && (
|
|
<div className="text-[10px] text-cyan-400 font-mono text-center animate-pulse">
|
|
Trial logged! Bayes prior updated to {lastTrialSuccess ? 'Success' : 'False Alarm'}.
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* SECTION 3: Active Forecasts (Feedback Loop) */}
|
|
<div className="grid grid-cols-1 xl:grid-cols-3 gap-8 mt-6">
|
|
<div className="xl:col-span-2 bg-slate-950/30 rounded-xl p-4 border border-slate-850 space-y-4">
|
|
<div className="flex justify-between items-center border-b border-slate-800 pb-3">
|
|
<h3 className="text-sm font-bold text-white flex items-center gap-2">
|
|
<TrendingUp className="text-cyan-400 w-4 h-4" /> Active Learning Feedback Loop
|
|
</h3>
|
|
<button
|
|
onClick={handleLogManualForecast}
|
|
className="bg-cyan-500 hover:bg-cyan-600 text-slate-950 font-bold py-1.5 px-3 rounded-lg text-xs transition-all active:scale-[0.96]"
|
|
>
|
|
Log Forecast to Feedback Loop
|
|
</button>
|
|
</div>
|
|
|
|
{learningLoopLog && (
|
|
<div className="p-3 bg-cyan-950/20 border border-cyan-900/40 rounded-lg text-xs text-cyan-300 flex items-center gap-2">
|
|
<Info className="w-4 h-4 shrink-0" />
|
|
<span>{learningLoopLog}</span>
|
|
</div>
|
|
)}
|
|
|
|
<div className="overflow-x-auto max-h-56 scrollbar-thin">
|
|
<table className="w-full border-collapse text-left text-xs font-mono">
|
|
<thead>
|
|
<tr className="border-b border-slate-800 text-slate-400 font-semibold bg-slate-900/40">
|
|
<th className="p-2">Ticker</th>
|
|
<th className="p-2">Direction</th>
|
|
<th className="p-2">Probability</th>
|
|
<th className="p-2">Entry Price</th>
|
|
<th className="p-2">Status</th>
|
|
<th className="p-2 text-right">Result</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{forecasts.length === 0 ? (
|
|
<tr>
|
|
<td colSpan={6} className="p-4 text-center text-slate-500 italic">No forecasts registered yet.</td>
|
|
</tr>
|
|
) : (
|
|
forecasts.map((fc) => (
|
|
<tr key={fc.id} className="border-b border-slate-900 hover:bg-slate-850/10">
|
|
<td className="p-2 text-slate-200 font-bold">{fc.ticker}</td>
|
|
<td className="p-2">
|
|
<span className={`px-1.5 py-0.5 rounded text-[10px] font-bold ${fc.predictedDirection === 'UP' ? 'bg-emerald-500/10 text-emerald-400' : 'bg-rose-500/10 text-rose-400'}`}>
|
|
{fc.predictedDirection}
|
|
</span>
|
|
</td>
|
|
<td className="p-2 text-slate-350">{(fc.predictedProb * 100).toFixed(0)}%</td>
|
|
<td className="p-2 text-slate-300">${fc.entryPrice.toLocaleString()}</td>
|
|
<td className="p-2 text-slate-400">{fc.resolved ? 'RESOLVED' : 'PENDING'}</td>
|
|
<td className={`p-2 text-right font-bold ${fc.result === 'SUCCESS' ? 'text-emerald-400' : fc.result === 'FAILURE' ? 'text-rose-400' : 'text-slate-500'}`}>
|
|
{fc.result || '-'}
|
|
</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Informational overlay */}
|
|
<div className="bg-slate-950/40 rounded-xl p-4 border border-slate-850 text-xs text-slate-400 space-y-2">
|
|
<h4 className="font-bold text-slate-300 uppercase">Econometric Feedback Loop Spec</h4>
|
|
<p className="leading-relaxed">
|
|
The learning loop automatically evaluates active forecast parameters in the background against actual price histories returned by <code className="text-cyan-400 font-mono">/api/finance?region=crypto</code>.
|
|
</p>
|
|
<p className="leading-relaxed">
|
|
When a logged forecast passes its evaluation target timestamp, it resolves against live market data, updating the Bayesian online calibration metrics <InlineMath math="\alpha" /> and <InlineMath math="\beta" />.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* SECTION 4: 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">Mathematical Formulation (Beta Update & 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. Bayesian Beta-Conjugate Error Correction</h4>
|
|
<p className="mb-2">
|
|
We model the error rate confidence interval of the model using a Beta distribution. The prior error state is represented by the parameters <InlineMath math="\alpha" /> (Successes) and <InlineMath math="\beta" /> (False Alarms):
|
|
</p>
|
|
<div className="py-2 overflow-x-auto text-slate-200">
|
|
<BlockMath math="P \\sim \\text{Beta}(\\alpha, \\beta) \\quad \\text{with expected value } \\mathbb{E}[P] = \\frac{\\alpha}{\\alpha + \\beta}" />
|
|
</div>
|
|
<p className="mb-2">
|
|
With a new ML signal <InlineMath math="P_{\\text{ML}}" />, we perform a conjugate Bayes update with a confidence weight <InlineMath math="w" />:
|
|
</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">
|
|
If the model is historically highly unstable (high <InlineMath math="\\beta" />), the Bayesian term corrects an overconfident ML signal downwards, safeguarding the robustness of the system.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="border-t border-slate-900 pt-3">
|
|
<h4 className="font-bold text-cyan-400 mb-1">2. Random Forest Non-Linear Signal Mapping</h4>
|
|
<p className="mb-2">
|
|
The Random Forest simulates an ensemble of 10 weak decision trees. Each tree splits the data based on threshold criteria (e.g., 'Funding Rate < -0.04%' and 'Open Interest > 10%'):
|
|
</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">
|
|
where <InlineMath math="T_m(\\mathbf{x})" /> is the predicted output value of the <InlineMath math="m" />-th decision tree for the feature vector <InlineMath math="\\mathbf{x}" />.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<CryptoMathModal isOpen={isMathModalOpen} onClose={() => setIsMathModalOpen(false)} />
|
|
</div>
|
|
);
|
|
}
|