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

1373 lines
68 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.
'use client';
import React, { useState, useMemo } from 'react';
import { useSandboxStore, PortfolioHolding, Transaction } from '@/lib/store';
import { calculateEWMA, calculateKellyFraction, calculateAssetCovariance } from '@/lib/math/statistics';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend, ReferenceLine, AreaChart, Area } from 'recharts';
import 'katex/dist/katex.min.css';
import { BlockMath, InlineMath } from 'react-katex';
import SandboxMathModal from './SandboxMathModal';
import SandboxBlueprintModal from './SandboxBlueprintModal';
import {
TrendingUp, Wallet, ArrowDownRight, ArrowUpRight, Percent, Plus, FolderSync,
HelpCircle, Settings, Calendar, DollarSign, Tag, Check, AlertCircle, ChevronDown, ChevronUp, Sparkles,
BookOpen, Trash2
} from 'lucide-react';
export default function SandboxDemo() {
const {
portfolios,
activePortfolioId,
ewmaLambda,
createPortfolio,
setActivePortfolio,
executeTransaction,
setEwmaLambda,
scannerAlerts,
posteriorProbability,
portfolio,
watchlist,
updatePortfolioAsset,
removePortfolioAsset
} = useSandboxStore();
// Selected portfolio
const activePortfolio = useMemo(() => {
return portfolios.find(p => p.id === activePortfolioId) || portfolios[0];
}, [portfolios, activePortfolioId]);
const [mounted, setMounted] = useState(false);
React.useEffect(() => {
setMounted(true);
}, []);
// UI state
const [showNewPortfolioModal, setShowNewPortfolioModal] = useState(false);
const [newPortfolioName, setNewPortfolioName] = useState('');
const [newStartingBalance, setNewStartingBalance] = useState(50000);
const [tradeSymbol, setTradeSymbol] = useState('AAPL');
const [tradeWknOrIsin, setTradeWknOrIsin] = useState('865985');
const [tradeShares, setTradeShares] = useState(10);
const [tradePrice, setTradePrice] = useState(182);
const [tradeType, setTradeType] = useState<'BUY' | 'SELL'>('BUY');
const [simulateFees, setSimulateFees] = useState(true);
const [isBackfill, setIsBackfill] = useState(false);
const [backfillDate, setBackfillDate] = useState('2026-05-20');
const [hypothesisTag, setHypothesisTag] = useState('Focus on AI Infrastructure');
const [orderError, setOrderError] = useState<string | null>(null);
const [orderSuccess, setOrderSuccess] = useState(false);
const [showMathAccordion, setShowMathAccordion] = useState(false);
const [isMathModalOpen, setIsMathModalOpen] = useState(false);
const [isBlueprintModalOpen, setIsBlueprintModalOpen] = useState(false);
const [showMsciBenchmark, setShowMsciBenchmark] = useState(true);
// Kelly Position Sizing states
const [kellySource, setKellySource] = useState<'scanner' | 'crypto' | 'econometric' | 'custom'>('custom');
const [customProb, setCustomProb] = useState<number>(0.60);
const [oddsRatio, setOddsRatio] = useState<number>(1.5);
// Systemic Macro Stress-Test States
const [activeStressTab, setActiveStressTab] = useState<'FOMC Rates' | 'CPI Inflation' | 'Labor Market'>('FOMC Rates');
const [stressLoading, setStressLoading] = useState(false);
const [stressData, setStressData] = useState<any>(null);
const [stressError, setStressError] = useState<string | null>(null);
React.useEffect(() => {
const fetchStressTest = async () => {
setStressLoading(true);
setStressError(null);
try {
const response = await fetch('/api/sandbox/lmm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
portfolio: portfolio,
eventType: activeStressTab
})
});
if (response.ok) {
const data = await response.json();
setStressData(data);
} else {
setStressError("Error loading stress test data.");
}
} catch (err) {
console.error("Stress test fetch error:", err);
setStressError("Network error loading stress test.");
} finally {
setStressLoading(false);
}
};
fetchStressTest();
}, [portfolio, activeStressTab]);
// Ingested Portfolio Ingestion Cockpit States
const [newAssetTicker, setNewAssetTicker] = useState('');
const [newAssetShares, setNewAssetShares] = useState<number | ''>('');
const [newAssetPrice, setNewAssetPrice] = useState<number | ''>('');
const [portfolioPrices, setPortfolioPrices] = useState<Record<string, { currentPrice: number; name: string }>>({});
const MOCK_PRICES: Record<string, number> = {
'AAPL': 185.20, 'MSFT': 415.50, 'NVDA': 945.00, 'TSLA': 178.50, 'AMD': 160.20,
'SMCI': 820.00, 'NFLX': 610.00, 'AMZN': 182.40, 'GOOGL': 175.50, 'META': 475.00,
'WMT': 60.50, 'JNJ': 158.30, 'PG': 162.10, 'MRK': 128.40, 'PLTR': 21.50,
'BABA': 78.40, 'CVX': 155.20, 'XOM': 118.60, 'BAC': 38.20, 'JPM': 195.40,
'ASML': 920.00, 'SAP': 178.50, 'MC.PA': 810.00, 'OR.PA': 440.00, 'NESN': 92.40,
'NOVOB': 125.60, 'SHEL': 32.40, 'BP': 38.50, 'HSBC': 42.10, 'ALV.DE': 248.50,
'VOW3.DE': 118.40, 'BMW.DE': 98.60, 'SIE.DE': 172.40, 'DTE.DE': 22.10,
'MBG.DE': 68.45, 'BAS.DE': 48.20, 'SAN.MC': 4.50, 'BBVA.MC': 9.80,
'BTC-USD': 65420.00, 'ETH-USD': 3480.00, 'SOL-USD': 148.50, 'ADA-USD': 0.46,
'XRP-USD': 0.49, 'DOGE-USD': 0.14, 'DOT-USD': 6.20, 'LINK-USD': 15.40,
'LTC-USD': 78.50, 'AVAX-USD': 32.40, 'BNB-USD': 580.00, 'TRX-USD': 0.12,
'NEAR-USD': 5.80
};
const portfolioTickers = useMemo(() => {
return portfolio.map(p => p.ticker);
}, [portfolio]);
React.useEffect(() => {
const fetchPrices = async () => {
if (portfolioTickers.length === 0) return;
try {
const response = await fetch(`/api/finance?tickers=${portfolioTickers.join(',')}`);
if (response.ok) {
const data = await response.json();
const pricesMap: Record<string, { currentPrice: number; name: string }> = {};
data.results.forEach((r: any) => {
if (!r.error) {
pricesMap[r.ticker] = { currentPrice: r.currentPrice, name: r.name };
}
});
setPortfolioPrices(prev => ({ ...prev, ...pricesMap }));
}
} catch (err) {
console.error("Error fetching sandbox portfolio prices:", err);
}
};
fetchPrices();
}, [portfolioTickers]);
const getTickerPrice = (ticker: string): number => {
if (portfolioPrices[ticker]) return portfolioPrices[ticker].currentPrice;
const w = watchlist.find(item => item.ticker === ticker);
if (w) return w.currentPrice;
const h = activePortfolio.holdings.find(item => item.symbol === ticker);
if (h) return h.currentPrice;
return MOCK_PRICES[ticker] || 100.0;
};
const getTickerName = (ticker: string): string => {
if (portfolioPrices[ticker]) return portfolioPrices[ticker].name;
const w = watchlist.find(item => item.ticker === ticker);
if (w) return w.ticker + ' Corp.';
const h = activePortfolio.holdings.find(item => item.symbol === ticker);
if (h) return h.symbol + ' Corp.';
return ticker + ' Corp.';
};
const handleAddNewAsset = (e: React.FormEvent) => {
e.preventDefault();
const ticker = newAssetTicker.trim().toUpperCase();
if (!ticker) return;
const shares = Number(newAssetShares);
const price = Number(newAssetPrice);
if (isNaN(shares) || shares <= 0 || isNaN(price) || price <= 0) {
alert("Please enter a valid number of shares and entry price.");
return;
}
updatePortfolioAsset(ticker, shares, price);
setNewAssetTicker('');
setNewAssetShares('');
setNewAssetPrice('');
};
const portfolioCalculated = useMemo(() => {
let totalValue = 0;
const items = portfolio.map((asset) => {
const currentPrice = getTickerPrice(asset.ticker);
const name = getTickerName(asset.ticker);
const positionValue = asset.shares * currentPrice;
totalValue += positionValue;
const pnlUsd = asset.shares * (currentPrice - asset.entryPrice);
const pnlPct = ((currentPrice - asset.entryPrice) / asset.entryPrice) * 100;
return {
...asset,
name,
currentPrice,
positionValue,
pnlUsd,
pnlPct
};
});
const itemsWithWeights = items.map((item) => {
const weight = totalValue > 0 ? item.positionValue / totalValue : 0;
return {
...item,
weight
};
});
return {
totalValue,
items: itemsWithWeights
};
}, [portfolio, portfolioPrices, watchlist, activePortfolio.holdings]);
// Compute Net Worth
const netWorth = useMemo(() => {
const assetsVal = activePortfolio.holdings.reduce((sum, h) => sum + h.shares * h.currentPrice, 0);
return Math.round((activePortfolio.cash + assetsVal) * 100) / 100;
}, [activePortfolio]);
// Dynamic winning probability (p) based on selected source
const kellyProbability = useMemo(() => {
if (kellySource === 'scanner') {
const alert = scannerAlerts.find(a => a.ticker.toUpperCase() === tradeSymbol.toUpperCase());
return alert ? alert.overreactionScore / 100 : 0.52;
}
if (kellySource === 'crypto') {
return posteriorProbability; // e.g. 0.72
}
if (kellySource === 'econometric') {
return 0.65; // ROC target probability
}
return customProb;
}, [kellySource, customProb, tradeSymbol, scannerAlerts, posteriorProbability]);
// Check potential cluster risk for the input symbol
const potentialClusterRisk = useMemo(() => {
if (!tradeSymbol) return false;
const holdingsWithWeights = activePortfolio.holdings.map(h => ({
symbol: h.symbol,
weight: (h.shares * h.currentPrice) / (netWorth || 1.0)
}));
const covResult = calculateAssetCovariance(holdingsWithWeights, tradeSymbol);
return covResult.clusterRisk;
}, [activePortfolio.holdings, tradeSymbol, netWorth]);
// Compute Kelly fraction and recommended cash amount
const kellyFraction = useMemo(() => {
const rawKelly = calculateKellyFraction(kellyProbability, oddsRatio);
// Cap at Half-Kelly already done in calculateKellyFraction, but we can scale by 50% if there is cluster risk
return potentialClusterRisk ? rawKelly * 0.5 : rawKelly;
}, [kellyProbability, oddsRatio, potentialClusterRisk]);
const recommendedKellyCash = useMemo(() => {
return activePortfolio.cash * kellyFraction;
}, [activePortfolio.cash, kellyFraction]);
// Compute returns based on active portfolio's historical value series
const portfolioReturns = useMemo(() => {
const vals = activePortfolio.historicalValues;
if (vals.length < 2) return [];
const r: number[] = [];
for (let i = 1; i < vals.length; i++) {
r.push((vals[i].value - vals[i - 1].value) / vals[i - 1].value);
}
return r;
}, [activePortfolio.historicalValues]);
// Calculate EWMA Volatility live
const ewmaResult = useMemo(() => {
return calculateEWMA(portfolioReturns, ewmaLambda);
}, [portfolioReturns, ewmaLambda]);
// Combine data for charting
const chartData = useMemo(() => {
const vals = activePortfolio.historicalValues;
if (vals.length === 0) return [];
// Normalize MSCI World index from the same starting value of the portfolio
const baseValue = vals[0].value;
let msciVal = baseValue;
return vals.map((hv, idx) => {
// Deterministic pseudo-random walk for MSCI World
if (idx > 0) {
const rand = Math.sin(idx * 57.8) * 0.45 + 0.05; // range: -0.4% to +0.5% return
msciVal = msciVal * (1 + rand * 0.015);
}
const vol = ewmaResult.series[idx - 1] || 0;
return {
date: hv.date,
Portfolio: hv.value,
'MSCI World (Benchmark)': Math.round(msciVal),
'EWMA Vol (%)': parseFloat(vol.toFixed(2)),
};
});
}, [activePortfolio.historicalValues, ewmaResult]);
if (!mounted) {
return (
<div className="bg-slate-900/60 backdrop-blur-md border border-slate-800 rounded-2xl p-6 text-slate-100 shadow-xl min-h-[400px] flex items-center justify-center">
<div className="text-slate-400 text-sm font-mono animate-pulse">Loading Sandbox Module...</div>
</div>
);
}
// Total gain/loss
const totalGainLoss = netWorth - activePortfolio.startingBalance;
const totalGainLossPct = (totalGainLoss / activePortfolio.startingBalance) * 100;
const isPositiveOverall = totalGainLoss >= 0;
const handleCreatePortfolio = (e: React.FormEvent) => {
e.preventDefault();
if (!newPortfolioName.trim()) return;
createPortfolio(newPortfolioName, newStartingBalance);
setNewPortfolioName('');
setShowNewPortfolioModal(false);
};
const handleTransactionSubmit = (e: React.FormEvent) => {
e.preventDefault();
setOrderError(null);
setOrderSuccess(false);
if (tradeShares <= 0 || tradePrice <= 0) {
setOrderError('Please enter a valid number of shares and price.');
return;
}
const ok = executeTransaction(
activePortfolio.id,
tradeSymbol,
tradeWknOrIsin,
tradeType,
tradeShares,
tradePrice,
simulateFees,
isBackfill,
backfillDate,
hypothesisTag
);
if (ok) {
setOrderSuccess(true);
setTimeout(() => setOrderSuccess(false), 3000);
} else {
setOrderError(
tradeType === 'BUY'
? 'Insufficient cash balance (including transaction fees).'
: 'Insufficient shares in portfolio for sale.'
);
}
};
return (
<div className="space-y-6">
{activePortfolio.riskProfile?.status === 'RED' && (
<div className="bg-rose-950/40 border border-rose-800/80 text-rose-400 text-xs rounded-xl p-4 flex items-center gap-3 shadow-[0_0_15px_rgba(244,63,94,0.15)] animate-pulse">
<AlertCircle className="w-5 h-5 text-rose-400 shrink-0" />
<div className="flex-1">
<span className="font-bold">Critical Concentration Risks (Covariance RED):</span> {activePortfolio.riskProfile.message}
</div>
</div>
)}
{/* SECTION 1: Portfolio Selector & Stats Bar */}
<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-emerald-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 mb-6">
<div className="space-y-1">
<span className="text-emerald-400 text-xs font-semibold uppercase tracking-wider">Strategic Sandbox</span>
<div className="flex items-center gap-3">
<FolderSync className="text-emerald-400 w-6 h-6" />
<select
value={activePortfolioId}
onChange={(e) => setActivePortfolio(e.target.value)}
className="bg-slate-950 border border-slate-800 rounded-xl px-4 py-2 text-slate-100 font-sans font-semibold text-lg focus:outline-none focus:border-emerald-500 cursor-pointer"
>
{portfolios.map((p) => (
<option key={p.id} value={p.id}>
{p.name}
</option>
))}
</select>
<button
onClick={() => setShowNewPortfolioModal(true)}
className="p-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-emerald-400 hover:text-emerald-300 transition-colors border border-slate-700"
title="Create new Sandbox Portfolio"
>
<Plus className="w-5 h-5" />
</button>
</div>
</div>
<div className="flex flex-wrap gap-4 w-full md:w-auto items-center">
<button
onClick={() => setIsMathModalOpen(true)}
className="flex items-center gap-1.5 px-4 py-3 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-emerald-400 justify-center h-[58px] shrink-0"
>
<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-4 py-3 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-emerald-400 justify-center h-[58px] shrink-0"
>
<Settings className="w-3.5 h-3.5" />
<span> Operational Blueprint</span>
</button>
{/* Net Worth Card */}
<div className="flex-1 md:flex-initial bg-slate-950/80 border border-slate-800 rounded-xl p-3 px-5 min-w-[140px]">
<div className="text-[10px] text-slate-400 uppercase font-semibold">Total Value</div>
<div className="font-mono text-xl font-bold text-slate-100 mt-1">
${netWorth.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div>
</div>
{/* Performance Card */}
<div className="flex-1 md:flex-initial bg-slate-950/80 border border-slate-800 rounded-xl p-3 px-5 min-w-[140px]">
<div className="text-[10px] text-slate-400 uppercase font-semibold">PnL (Total)</div>
<div className={`font-mono text-xl font-bold mt-1 flex items-center gap-1 ${isPositiveOverall ? 'text-emerald-400' : 'text-rose-400'}`}>
{isPositiveOverall ? <ArrowUpRight className="w-5 h-5" /> : <ArrowDownRight className="w-5 h-5" />}
<span>{isPositiveOverall ? '+' : ''}{totalGainLossPct.toFixed(2)}%</span>
</div>
</div>
{/* Live EWMA Vol Card */}
<div className="flex-1 md:flex-initial bg-slate-950/80 border border-slate-800 rounded-xl p-3 px-5 min-w-[140px] relative group">
<div className="text-[10px] text-slate-400 uppercase font-semibold flex items-center gap-1">
<span>EWMA Volatility</span>
<span className="cursor-help flex items-center" title="Annualized volatility based on historical returns.">
<HelpCircle className="w-3.5 h-3.5 text-slate-500 group-hover:text-emerald-400 transition-colors" />
</span>
</div>
<div className="font-mono text-xl font-bold text-teal-400 mt-1">
{ewmaResult.latest.toFixed(2)}%
</div>
</div>
{/* Covariance Risk Traffic Light Card */}
<div className="flex-1 md:flex-initial bg-slate-950/80 border border-slate-800 rounded-xl p-3 px-5 min-w-[150px] relative group">
<div className="text-[10px] text-slate-400 uppercase font-semibold flex items-center gap-1">
<span>Covariance Traffic Light</span>
<span className="cursor-help flex items-center" title="Systemic portfolio concentration risks based on historical asset covariances.">
<HelpCircle className="w-3.5 h-3.5 text-slate-500 group-hover:text-rose-400 transition-colors" />
</span>
</div>
<div className="flex items-center gap-2 mt-1.5">
<span className={`w-3.5 h-3.5 rounded-full ${
activePortfolio.riskProfile?.status === 'RED' ? 'bg-rose-500 animate-pulse shadow-[0_0_8px_#f43f5e]' :
activePortfolio.riskProfile?.status === 'YELLOW' ? 'bg-amber-500 shadow-[0_0_8px_#f59e0b]' :
'bg-emerald-500 shadow-[0_0_8px_#10b981]'
}`} />
<span className={`font-mono text-sm font-bold ${
activePortfolio.riskProfile?.status === 'RED' ? 'text-rose-400' :
activePortfolio.riskProfile?.status === 'YELLOW' ? 'text-amber-400' :
'text-emerald-400'
}`}>
{activePortfolio.riskProfile?.status || 'GREEN'} RISK
</span>
</div>
</div>
</div>
</div>
{/* Modal for creating a new Portfolio */}
{showNewPortfolioModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
<div className="bg-slate-900 border border-slate-800 rounded-2xl p-6 max-w-sm w-full space-y-4 shadow-2xl">
<h3 className="text-lg font-bold text-white">New Sandbox Portfolio</h3>
<form onSubmit={handleCreatePortfolio} className="space-y-4">
<div>
<label className="text-xs text-slate-400 block mb-1">Portfolio Name</label>
<input
type="text"
required
placeholder="e.g. Biotech Risk High Yield"
className="w-full bg-slate-950 border border-slate-800 rounded-lg p-2.5 text-slate-100 focus:outline-none focus:border-emerald-500"
value={newPortfolioName}
onChange={(e) => setNewPortfolioName(e.target.value)}
/>
</div>
<div>
<label className="text-xs text-slate-400 block mb-1">Starting Capital ($)</label>
<input
type="number"
required
className="w-full bg-slate-950 border border-slate-800 rounded-lg p-2.5 text-slate-100 focus:outline-none focus:border-emerald-500 font-mono"
value={newStartingBalance}
onChange={(e) => setNewStartingBalance(Number(e.target.value))}
/>
</div>
<div className="flex gap-3 pt-2">
<button
type="button"
onClick={() => setShowNewPortfolioModal(false)}
className="flex-1 bg-slate-850 hover:bg-slate-800 text-slate-300 font-semibold py-2 rounded-lg transition-colors border border-slate-700"
>
Cancel
</button>
<button
type="submit"
className="flex-1 bg-gradient-to-r from-emerald-500 to-teal-500 hover:from-emerald-600 hover:to-teal-600 text-slate-950 font-bold py-2 rounded-lg transition-all shadow-lg shadow-emerald-500/20"
>
Create
</button>
</div>
</form>
</div>
</div>
)}
{/* Accordion / Math Button */}
<div className="border-t border-slate-850 pt-4 mt-4">
<button
onClick={() => setShowMathAccordion(!showMathAccordion)}
className="flex items-center gap-1.5 text-xs text-slate-400 hover:text-emerald-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 Specification & EWMA Volatility Model</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-semibold text-emerald-400 mb-1.5">1. EWMA Volatility Model</h4>
<p className="mb-2">
Volatility is calculated using the <strong>Exponentially Weighted Moving Average (EWMA)</strong> model. Recent returns receive a higher weighting than returns further in the past, controlled by the decay parameter <InlineMath math="\lambda" /> (Lambda).
</p>
<div className="py-2 overflow-x-auto">
<BlockMath math="\sigma_t^2 = \lambda \sigma_{t-1}^2 + (1 - \lambda) r_{t-1}^2" />
</div>
<p className="mb-2">
The daily volatility <InlineMath math="\sigma_t" /> is extrapolated to an entire year (annualization) assuming 252 trading days:
</p>
<div className="py-2 overflow-x-auto">
<BlockMath math="\sigma_{\text{ann}} = \sqrt{\sigma_t^2 \times 252}" />
</div>
<p className="text-slate-400">
RiskMetrics recommends a Lambda value of <InlineMath math="\lambda = 0.94" /> for daily financial data.
</p>
</div>
<div className="border-t border-slate-800 pt-3">
<h4 className="font-semibold text-emerald-400 mb-1.5">2. Kelly Criterion for Position Sizing</h4>
<p className="mb-2">
The Kelly formula determines the optimal fraction of capital (<InlineMath math="f^*" />) to invest in a trade to maximize the exponential growth of wealth:
</p>
<div className="py-2 overflow-x-auto">
<BlockMath math="f^* = \frac{p \cdot b - q}{b} = \frac{p \cdot b - (1 - p)}{b}" />
</div>
<p className="mb-2">
To mitigate risks from inaccurate estimations, we apply the conservative <strong>Half-Kelly</strong> sizing and limit the result to <InlineMath math="0.5 \times f^*" /> (additionally constrained to <InlineMath math="\ge 0" />).
</p>
</div>
<div className="border-t border-slate-800 pt-3">
<h4 className="font-semibold text-rose-400 mb-1.5">3. Covariance & Cluster Risk (Covariance Traffic Light)</h4>
<p className="mb-2">
The covariance between assets is determined by multiplying their pairwise correlation by their respective standard deviations (volatilities):
</p>
<div className="py-2 overflow-x-auto">
<BlockMath math="\text{Cov}(A, B) = \text{Corr}(A, B) \times \sigma_A \times \sigma_B" />
</div>
<p className="text-slate-400">
A <strong>concentration risk (Risk RED)</strong> is triggered when an asset exhibits a correlation <InlineMath math="\text{Corr}(A, B) > 0.70" /> to existing positions and these positions each exceed 15% of the portfolio.
</p>
</div>
</div>
)}
</div>
</div>
{/* SECTION: My Portfolio Ingestion Cockpit */}
<div className="bg-slate-900/60 backdrop-blur-md border border-slate-800 rounded-2xl p-6 text-slate-100 shadow-xl space-y-4">
<div className="flex justify-between items-center border-b border-slate-800 pb-3">
<h3 className="text-lg font-bold text-white flex items-center gap-2">
<Wallet className="text-emerald-400 w-5 h-5" /> My Portfolio Cockpit
</h3>
<span className="text-xs text-slate-400 font-mono">
Total Inventory Value: <span className="text-emerald-400 font-bold font-mono">${portfolioCalculated.totalValue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span>
</span>
</div>
<div className="overflow-x-auto border border-slate-850 rounded-xl bg-slate-950/40">
<table className="w-full border-collapse text-left text-sm min-w-[800px]">
<thead>
<tr className="border-b border-slate-800 text-slate-400 font-semibold bg-slate-900/40">
<th className="p-3">Asset / Ticker</th>
<th className="p-3 text-center">Shares</th>
<th className="p-3 text-center">Entry Price</th>
<th className="p-3 text-center">Current Price</th>
<th className="p-3 text-right">Position Value</th>
<th className="p-3 text-right">Performance (PnL)</th>
<th className="p-3">Weighting (w_i)</th>
<th className="p-3 text-center">Actions</th>
</tr>
</thead>
<tbody>
{portfolioCalculated.items.length === 0 ? (
<tr>
<td colSpan={8} className="p-8 text-center text-slate-500 italic">
No assets in the Ingestion Cockpit yet. Add an asset below.
</td>
</tr>
) : (
portfolioCalculated.items.map((item) => {
const isPositive = item.pnlUsd >= 0;
const weightPct = item.weight * 100;
return (
<tr key={item.ticker} className="border-b border-slate-850/50 hover:bg-slate-850/20 transition-colors">
{/* Symbol & Name */}
<td className="p-3">
<div className="font-bold text-slate-100 font-mono">{item.ticker}</div>
<div className="text-[10px] text-slate-500">{item.name}</div>
</td>
{/* Shares (Inline input) */}
<td className="p-3 text-center">
<input
type="number"
key={`${item.ticker}-shares-${item.shares}`}
defaultValue={item.shares}
onBlur={(e) => {
const val = Number(e.target.value);
if (val > 0) {
updatePortfolioAsset(item.ticker, val, item.entryPrice);
} else {
e.target.value = String(item.shares);
}
}}
className="w-20 bg-slate-950 border border-slate-800 rounded px-2 py-1 text-slate-100 font-mono text-center focus:border-emerald-500 focus:outline-none"
/>
</td>
{/* Entry Price (Inline input) */}
<td className="p-3 text-center">
<input
type="number"
key={`${item.ticker}-entry-${item.entryPrice}`}
defaultValue={item.entryPrice}
onBlur={(e) => {
const val = Number(e.target.value);
if (val > 0) {
updatePortfolioAsset(item.ticker, item.shares, val);
} else {
e.target.value = String(item.entryPrice);
}
}}
className="w-24 bg-slate-950 border border-slate-800 rounded px-2 py-1 text-slate-100 font-mono text-center focus:border-emerald-500 focus:outline-none"
/>
</td>
{/* Current Price */}
<td className="p-3 text-center font-mono text-slate-350">
${item.currentPrice.toFixed(2)}
</td>
{/* Position Value */}
<td className="p-3 text-right font-mono font-semibold text-slate-200">
${item.positionValue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</td>
{/* PnL */}
<td className={`p-3 text-right font-mono ${isPositive ? 'text-emerald-400' : 'text-rose-400'}`}>
<div className="flex items-center justify-end gap-1 font-semibold">
{isPositive ? '+' : ''}${item.pnlUsd.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</div>
<div className="text-[10px]">
{isPositive ? '+' : ''}{item.pnlPct.toFixed(2)}%
</div>
</td>
{/* Weighting Progress Bar */}
<td className="p-3 max-w-[150px]">
<div className="flex items-center gap-2 justify-between">
<span className="font-mono text-xs text-slate-300 font-bold">{weightPct.toFixed(1)}%</span>
<div className="w-20 bg-slate-950 rounded-full h-1.5 overflow-hidden border border-slate-800">
<div
className="bg-gradient-to-r from-emerald-500 to-teal-500 h-full rounded-full transition-all duration-500"
style={{ width: `${weightPct}%` }}
/>
</div>
</div>
</td>
{/* Actions */}
<td className="p-3 text-center">
<button
onClick={() => removePortfolioAsset(item.ticker)}
className="p-1.5 rounded-lg bg-slate-950 hover:bg-rose-950/40 text-slate-500 hover:text-rose-400 transition-colors border border-slate-850 hover:border-rose-900/30 cursor-pointer"
title="Delete asset"
>
<Trash2 className="w-4 h-4" />
</button>
</td>
</tr>
);
})
)}
{/* Adding Asset Inline Row */}
<tr className="bg-slate-950/20 border-t border-slate-800">
<td className="p-3">
<input
type="text"
required
placeholder="Ticker (e.g. AAPL)"
className="w-full bg-slate-950 border border-slate-800 rounded-lg p-2 text-slate-100 font-mono text-xs uppercase focus:border-emerald-500 focus:outline-none"
value={newAssetTicker}
onChange={(e) => setNewAssetTicker(e.target.value)}
/>
</td>
<td className="p-3 text-center">
<input
type="number"
required
placeholder="Shares"
className="w-24 bg-slate-950 border border-slate-800 rounded-lg p-2 text-slate-100 font-mono text-xs text-center focus:border-emerald-500 focus:outline-none"
value={newAssetShares === '' ? '' : newAssetShares}
onChange={(e) => setNewAssetShares(e.target.value === '' ? '' : Number(e.target.value))}
/>
</td>
<td className="p-3 text-center">
<input
type="number"
required
placeholder="Entry ($)"
className="w-28 bg-slate-950 border border-slate-800 rounded-lg p-2 text-slate-100 font-mono text-xs text-center focus:border-emerald-500 focus:outline-none"
value={newAssetPrice === '' ? '' : newAssetPrice}
onChange={(e) => setNewAssetPrice(e.target.value === '' ? '' : Number(e.target.value))}
/>
</td>
<td className="p-3 text-center text-slate-500 font-mono text-xs">-</td>
<td className="p-3 text-right text-slate-500 font-mono text-xs">-</td>
<td className="p-3 text-right text-slate-500 font-mono text-xs">-</td>
<td className="p-3 text-slate-500 font-mono text-xs">-</td>
<td className="p-3 text-center">
<button
onClick={handleAddNewAsset}
className="bg-emerald-500 hover:bg-emerald-600 text-slate-950 font-bold py-1.5 px-3 rounded-lg text-xs shadow-md shadow-emerald-500/10 flex items-center justify-center gap-1 mx-auto transition-all active:scale-[0.96] cursor-pointer"
>
<Plus className="w-3.5 h-3.5" /> Add
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* SECTION: Systemic Macro Stress Test (Portfolio LMM) */}
<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">
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 border-b border-slate-800 pb-3">
<div className="space-y-1">
<h3 className="text-lg font-bold text-white flex items-center gap-2">
<TrendingUp className="text-purple-400 w-5 h-5" /> Systemic Macro Stress Test (Portfolio LMM)
</h3>
<p className="text-xs text-slate-400">
Analyzes the historical sensitivity of the portfolio to core macro events over the last 36 months.
</p>
</div>
{/* Event type tabs */}
<div className="flex bg-slate-950 p-1 rounded-xl border border-slate-850 w-full sm:w-auto shrink-0">
{(['FOMC Rates', 'CPI Inflation', 'Labor Market'] as const).map((tab) => (
<button
key={tab}
onClick={() => setActiveStressTab(tab)}
className={`flex-1 sm:flex-none px-3 py-1.5 rounded-lg text-xs font-semibold transition-all ${
activeStressTab === tab
? 'bg-purple-600 text-white font-bold shadow-md shadow-purple-500/20'
: 'text-slate-400 hover:text-slate-200'
}`}
>
{tab === 'FOMC Rates' ? '🏦 FOMC Rates' : tab === 'CPI Inflation' ? '🎯 CPI Inflation' : '💼 Labor Market'}
</button>
))}
</div>
</div>
{stressLoading ? (
<div className="h-80 flex flex-col items-center justify-center space-y-3">
<div className="w-8 h-8 rounded-full border-2 border-purple-500 border-t-transparent animate-spin" />
<span className="text-xs text-slate-400 font-mono animate-pulse">Calculating Swamy-Arora GLS estimators...</span>
</div>
) : stressError || !stressData ? (
<div className="h-80 flex items-center justify-center text-slate-500 italic">
{stressError || 'No data loaded.'}
</div>
) : (
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* LMM Summary Statistics */}
<div className="bg-slate-950/40 rounded-xl p-4 border border-slate-850 flex flex-col justify-between space-y-4">
<div>
<span className="text-[10px] uppercase tracking-wider text-purple-400 font-bold block mb-2">Regression Coefficients (GLS)</span>
{/* Fixed Effects list */}
<div className="space-y-3">
{stressData.regressionResults?.fixedEffects.map((fe: any) => {
const isPositive = fe.estimate >= 0;
const isImpact = fe.name === 'Post-Event Impact';
return (
<div key={fe.name} className={`flex justify-between items-center p-2 rounded-lg border ${
isImpact ? 'bg-purple-950/20 border-purple-900/40' : 'bg-slate-950/60 border-slate-900'
}`}>
<div>
<div className={`text-xs font-semibold ${isImpact ? 'text-purple-300 font-bold' : 'text-slate-350'}`}>
{fe.name === 'Intercept' ? 'Baseline Intercept' :
fe.name === 'Pre-Event Drift' ? 'Pre-Event Trend (Drift)' :
fe.name === 'Post-Event Impact' ? 'Systemic Portfolio Beta' :
'VIX Volatility Sensitivity'}
</div>
<div className="text-[9px] text-slate-500">
SE: {fe.se.toFixed(4)} | p-value: {fe.pVal.toFixed(4)}
</div>
</div>
<div className="text-right">
<span className={`font-mono text-sm font-bold ${
isImpact ? 'text-purple-400 text-base' :
isPositive ? 'text-emerald-400' : 'text-rose-400'
}`}>
{isPositive ? '+' : ''}{fe.estimate.toFixed(4)}
</span>
<span className="text-purple-400 text-xs font-bold font-mono ml-1">{fe.sig}</span>
</div>
</div>
);
})}
</div>
</div>
{/* Model Fit metrics */}
<div className="border-t border-slate-850 pt-3 space-y-2">
<div className="flex justify-between text-xs">
<span className="text-slate-400">R-Squared:</span>
<span className="font-mono font-bold text-slate-200">{(stressData.regressionResults?.rSquared * 100).toFixed(1)}%</span>
</div>
<div className="flex justify-between text-[11px] text-slate-500 font-mono">
<span>AIC: {stressData.regressionResults?.aic}</span>
<span>BIC: {stressData.regressionResults?.bic}</span>
<span>Events: {stressData.eventCount}</span>
</div>
</div>
</div>
{/* Recharts Area/Line Chart (2/3 width) */}
<div className="lg:col-span-2 bg-slate-950/30 rounded-xl p-4 border border-slate-850 space-y-3">
<div className="flex justify-between items-center text-xs">
<span className="text-slate-400 font-mono">Average cumulative return in the [-30, +30] days window</span>
<span className="text-[9px] text-slate-500 font-mono">Accumulated log returns</span>
</div>
<div className="h-64 w-full">
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={stressData.chartData} margin={{ top: 10, right: 10, left: -20, bottom: 5 }}>
<defs>
<linearGradient id="colorPortfolio" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#c084fc" stopOpacity={0.2}/>
<stop offset="95%" stopColor="#c084fc" stopOpacity={0.0}/>
</linearGradient>
<linearGradient id="colorBenchmark" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#60a5fa" stopOpacity={0.1}/>
<stop offset="95%" stopColor="#60a5fa" stopOpacity={0.0}/>
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" />
<XAxis
dataKey="relativeDay"
stroke="#64748b"
fontSize={10}
tickFormatter={(v) => `T${v >= 0 ? '+' : ''}${v}`}
/>
<YAxis
stroke="#64748b"
fontSize={10}
tickFormatter={(v) => `${v.toFixed(1)}%`}
/>
<Tooltip
contentStyle={{ backgroundColor: '#090d16', borderColor: '#1e293b', borderRadius: '8px', fontSize: '11px' }}
labelFormatter={(label) => `Relative Day: T${label >= 0 ? '+' : ''}${label}`}
/>
<Legend verticalAlign="top" height={36} iconType="circle" />
<Area
type="monotone"
dataKey="Mein Portfolio (%)"
stroke="#c084fc"
fillOpacity={1}
fill="url(#colorPortfolio)"
strokeWidth={2.5}
dot={false}
/>
<Area
type="monotone"
dataKey="NASDAQ Benchmark (%)"
stroke="#60a5fa"
fillOpacity={1}
fill="url(#colorBenchmark)"
strokeWidth={1.5}
strokeDasharray="4 4"
dot={false}
/>
<Line
type="monotone"
dataKey="LMM Trend (%)"
name="Purged LMM Trend (%)"
stroke="#f43f5e"
strokeWidth={2}
dot={false}
/>
<ReferenceLine
x={0}
stroke="#ef4444"
strokeWidth={1.5}
strokeDasharray="3 3"
label={{ value: 'Event Date (T0)', fill: '#ef4444', fontSize: 9, position: 'top' }}
/>
</AreaChart>
</ResponsiveContainer>
</div>
</div>
</div>
)}
{/* Quantitative Commentary Card */}
{stressData && !stressLoading && (
<div className="bg-purple-950/20 border border-purple-900/40 rounded-xl p-4 flex gap-3 items-start text-xs text-purple-300">
<Sparkles className="w-5 h-5 text-purple-400 shrink-0 mt-0.5" />
<div>
<span className="font-bold uppercase tracking-wider block mb-1">Quantitative Analysis Evaluation</span>
<p className="leading-relaxed">
{(() => {
const impactBeta = stressData.regressionResults?.fixedEffects.find((f: any) => f.name === 'Post-Event Impact')?.estimate || 0;
const isNegative = impactBeta < 0;
const absBeta = Math.abs(impactBeta).toFixed(2);
const pVal = stressData.regressionResults?.fixedEffects.find((f: any) => f.name === 'Post-Event Impact')?.pVal || 0;
const isSignificant = pVal < 0.05;
let eventNameText = activeStressTab === 'FOMC Rates' ? 'FOMC rate decisions' :
activeStressTab === 'CPI Inflation' ? 'CPI inflation releases' : 'labor market updates';
let significanceText = isSignificant
? `This effect is statistically significant with a p-value of ${pVal.toFixed(4)}.`
: `This effect is not highly statistically significant (potential noise) with a p-value of ${pVal.toFixed(4)}.`;
if (isNegative) {
return `Historical reactivity: During ${eventNameText}, your portfolio exhibits a negative Beta of -${absBeta}. ${significanceText} Hedging via defensive sectors (e.g., increasing cash ratio or defensive consumer stocks) reduces volatility risk in this post-event phase by approx. ${Math.round(Math.abs(impactBeta) * 35)}%.`;
} else {
return `Historical reactivity: During ${eventNameText}, your portfolio exhibits a positive Beta of +${absBeta}. ${significanceText} Your portfolio tends to benefit from the subsequent market momentum. You might consider increasing leverage via momentum additions.`;
}
})()}
</p>
</div>
</div>
)}
</div>
{/* SECTION 2: Chart / Analytics & Order Form */}
<div className="grid grid-cols-1 xl:grid-cols-3 gap-8">
{/* Left 2 Columns: Analytics Performance Plot */}
<div className="xl:col-span-2 bg-slate-900/60 backdrop-blur-md border border-slate-800 rounded-2xl p-6 text-slate-100 shadow-xl space-y-6">
<div className="flex justify-between items-center border-b border-slate-800 pb-3">
<h3 className="text-lg font-bold text-white flex items-center gap-2">
<TrendingUp className="text-emerald-400 w-5 h-5" /> Portfolio Performance & Benchmark
</h3>
<div className="flex items-center gap-3">
<label className="flex items-center gap-2 text-xs text-slate-400 cursor-pointer">
<input
type="checkbox"
checked={showMsciBenchmark}
onChange={(e) => setShowMsciBenchmark(e.target.checked)}
className="rounded border-slate-800 text-emerald-500 focus:ring-0 accent-emerald-500 w-4 h-4"
/>
<span>Show MSCI World (Benchmark)</span>
</label>
</div>
</div>
<div className="h-80 w-full">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={chartData}>
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" />
<XAxis dataKey="date" stroke="#64748b" fontSize={10} />
<YAxis stroke="#64748b" fontSize={10} domain={['auto', 'auto']} tickFormatter={(v) => `$${v.toLocaleString()}`} />
<Tooltip contentStyle={{ backgroundColor: '#0f172a', borderColor: '#334155', borderRadius: '8px' }} />
<Legend verticalAlign="top" height={36} />
<Line type="monotone" dataKey="Portfolio" name={activePortfolio.name} stroke="#10b981" strokeWidth={3} dot={false} activeDot={{ r: 6 }} />
{showMsciBenchmark && (
<Line type="monotone" dataKey="MSCI World (Benchmark)" name="MSCI World Index (Normalized)" stroke="#3b82f6" strokeWidth={2} strokeDasharray="4 4" dot={false} />
)}
</LineChart>
</ResponsiveContainer>
</div>
{/* EWMA parameter tuner slider */}
<div className="p-4 rounded-xl border border-slate-850 bg-slate-950/20 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div className="space-y-1">
<h4 className="text-sm font-semibold text-slate-200">Parameter Tuning EWMA Lambda (&lambda;)</h4>
<p className="text-xs text-slate-400">Decay factor controls the shock sensitivity of the volatility model.</p>
</div>
<div className="flex items-center gap-4 w-full sm:w-auto">
<input
type="range"
min="0.80"
max="0.99"
step="0.01"
value={ewmaLambda}
onChange={(e) => setEwmaLambda(parseFloat(e.target.value))}
className="w-40 accent-emerald-400 cursor-pointer"
/>
<span className="font-mono text-emerald-400 text-sm font-bold bg-emerald-500/10 px-2 py-0.5 rounded border border-emerald-500/20">&lambda; = {ewmaLambda.toFixed(2)}</span>
</div>
</div>
</div>
{/* Right 1 Column: Advanced Order Mask */}
<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 flex flex-col justify-between">
<div className="space-y-6">
<div className="border-b border-slate-800 pb-3">
<h3 className="text-lg font-bold text-white flex items-center gap-2">
<Settings className="text-emerald-400 w-5 h-5" /> Order Mask (Simulated)
</h3>
</div>
<form onSubmit={handleTransactionSubmit} className="space-y-4">
<div className="grid grid-cols-2 gap-3">
<div>
<label className="text-xs text-slate-400 block mb-1">Ticker / Symbol</label>
<input
type="text"
required
placeholder="AAPL"
className="w-full bg-slate-950 border border-slate-800 rounded-lg p-2 text-slate-100 font-mono focus:outline-none focus:border-emerald-500"
value={tradeSymbol}
onChange={(e) => setTradeSymbol(e.target.value.toUpperCase())}
/>
</div>
<div>
<label className="text-xs text-slate-400 block mb-1">WKN / ISIN</label>
<input
type="text"
placeholder="865985"
className="w-full bg-slate-950 border border-slate-800 rounded-lg p-2 text-slate-100 font-mono focus:outline-none focus:border-emerald-500"
value={tradeWknOrIsin}
onChange={(e) => setTradeWknOrIsin(e.target.value)}
/>
</div>
<div>
<label className="text-xs text-slate-400 block mb-1">Shares</label>
<input
type="number"
required
className="w-full bg-slate-950 border border-slate-800 rounded-lg p-2 text-slate-100 font-mono focus:outline-none focus:border-emerald-500"
value={tradeShares}
onChange={(e) => setTradeShares(Number(e.target.value))}
/>
</div>
<div>
<label className="text-xs text-slate-400 block mb-1">Price ($)</label>
<input
type="number"
required
className="w-full bg-slate-950 border border-slate-800 rounded-lg p-2 text-slate-100 font-mono focus:outline-none focus:border-emerald-500"
value={tradePrice}
onChange={(e) => setTradePrice(Number(e.target.value))}
/>
</div>
</div>
{/* Order direction buttons */}
<div className="flex gap-2 p-1 rounded-xl bg-slate-950 border border-slate-800">
<button
type="button"
onClick={() => setTradeType('BUY')}
className={`flex-1 py-1.5 text-xs font-bold rounded-lg transition-all ${tradeType === 'BUY' ? 'bg-emerald-500 text-slate-950 shadow-md shadow-emerald-500/10' : 'text-slate-400 hover:text-slate-200'}`}
>
Buy (Long)
</button>
<button
type="button"
onClick={() => setTradeType('SELL')}
className={`flex-1 py-1.5 text-xs font-bold rounded-lg transition-all ${tradeType === 'SELL' ? 'bg-rose-500 text-white shadow-md shadow-rose-500/10' : 'text-slate-400 hover:text-slate-200'}`}
>
Sell (Short)
</button>
</div>
{/* Kelly Sizing Risk Recommendation Widget */}
{tradeType === 'BUY' && (
<div className="bg-slate-950/60 border border-slate-850 rounded-xl p-3 text-[11px] space-y-2 relative overflow-hidden">
<div className="flex justify-between items-center">
<span className="text-[10px] text-slate-400 uppercase font-semibold flex items-center gap-1">
<Sparkles className="w-3 h-3 text-emerald-400" /> Risk-Engine Sizing
</span>
<span className="font-bold text-emerald-400 text-[10px] uppercase tracking-wider">Kelly recommendation</span>
</div>
<div className="grid grid-cols-2 gap-2">
<div>
<label className="text-[9px] text-slate-500 block mb-0.5 font-semibold uppercase">Prob Source (p)</label>
<select
value={kellySource}
onChange={(e) => setKellySource(e.target.value as any)}
className="w-full bg-slate-900 border border-slate-800 rounded px-1.5 py-1 text-[10px] text-slate-200 focus:outline-none"
>
<option value="custom">Manual Slider</option>
<option value="scanner">Level 2 Scanner Score</option>
<option value="crypto">Level 4 Bayes Posterior</option>
<option value="econometric">Level 5 ROC Breakout</option>
</select>
</div>
<div>
<label className="text-[9px] text-slate-500 block mb-0.5 font-semibold uppercase">Odds Ratio (b)</label>
<input
type="number"
step="0.1"
min="0.1"
value={oddsRatio}
onChange={(e) => setOddsRatio(Number(e.target.value))}
className="w-full bg-slate-900 border border-slate-800 rounded px-1.5 py-1 text-[10px] text-slate-200 font-mono focus:outline-none"
/>
</div>
</div>
{kellySource === 'custom' && (
<div className="space-y-1">
<div className="flex justify-between text-[9px] text-slate-500">
<span>Probability of Success:</span>
<span className="font-mono text-emerald-400 font-bold">{(kellyProbability * 100).toFixed(0)}%</span>
</div>
<input
type="range"
min="0.1"
max="0.99"
step="0.01"
value={customProb}
onChange={(e) => setCustomProb(Number(e.target.value))}
className="w-full accent-emerald-500 h-1 bg-slate-900 rounded"
/>
</div>
)}
{kellySource !== 'custom' && (
<div className="text-[9px] text-slate-400 flex justify-between bg-slate-900/40 p-1 px-1.5 rounded border border-slate-900">
<span>System Probability:</span>
<span className="font-mono text-emerald-400 font-bold">{(kellyProbability * 100).toFixed(1)}%</span>
</div>
)}
{potentialClusterRisk && (
<div className="p-2 bg-rose-500/10 text-rose-400 border border-rose-500/20 text-[9px] rounded flex items-start gap-1">
<AlertCircle className="w-3.5 h-3.5 shrink-0 text-rose-400" />
<div>
<strong>Concentration Risk!</strong> Correlation &gt; 0.70 to existing positions. Kelly recommendation halved by 50%.
</div>
</div>
)}
<div className="bg-slate-900/80 p-2 rounded-lg border border-slate-850 flex justify-between items-center text-xs">
<div>
<span className="block text-[9px] text-slate-500 uppercase font-semibold">Kelly Fraction:</span>
<span className="font-mono font-bold text-slate-200">{(kellyFraction * 100).toFixed(1)}% of Cash</span>
</div>
<div className="text-right">
<span className="block text-[9px] text-slate-500 uppercase font-semibold">Buy Volume:</span>
<span className="font-mono font-bold text-emerald-400">${Math.round(recommendedKellyCash).toLocaleString()}</span>
</div>
</div>
</div>
)}
{/* Hypothesis input */}
<div>
<label className="text-xs text-slate-400 block mb-1 flex items-center gap-1">
<Tag className="w-3 h-3 text-emerald-400" />
<span>Hypothesis / What-if Note</span>
</label>
<input
type="text"
placeholder="e.g., Ferrari EV Skepticism"
className="w-full bg-slate-950 border border-slate-800 rounded-lg p-2 text-slate-100 text-xs focus:outline-none focus:border-emerald-500"
value={hypothesisTag}
onChange={(e) => setHypothesisTag(e.target.value)}
/>
</div>
{/* Fees Toggle */}
<div className="flex items-center justify-between border-t border-slate-850 pt-3 text-xs">
<span className="text-slate-400 flex items-center gap-1">
<DollarSign className="w-3.5 h-3.5 text-slate-500" /> Simulate transaction fees
</span>
<input
type="checkbox"
checked={simulateFees}
onChange={(e) => setSimulateFees(e.target.checked)}
className="rounded border-slate-800 text-emerald-500 focus:ring-0 accent-emerald-500 w-4 h-4 cursor-pointer"
/>
</div>
{/* Backfill Date Picker Toggle */}
<div className="space-y-2 border-t border-slate-850 pt-3 text-xs">
<div className="flex items-center justify-between">
<span className="text-slate-400 flex items-center gap-1">
<Calendar className="w-3.5 h-3.5 text-slate-500" /> Historical Backfill
</span>
<input
type="checkbox"
checked={isBackfill}
onChange={(e) => setIsBackfill(e.target.checked)}
className="rounded border-slate-800 text-emerald-500 focus:ring-0 accent-emerald-500 w-4 h-4 cursor-pointer"
/>
</div>
{isBackfill && (
<input
type="date"
className="w-full bg-slate-950 border border-slate-800 rounded-lg p-2 text-slate-100 text-xs focus:outline-none focus:border-emerald-500 font-mono"
value={backfillDate}
onChange={(e) => setBackfillDate(e.target.value)}
/>
)}
</div>
{orderError && (
<div className="p-3 rounded-lg bg-rose-500/10 text-rose-400 border border-rose-500/20 text-xs flex items-center gap-2">
<AlertCircle className="w-4 h-4 shrink-0" />
<span>{orderError}</span>
</div>
)}
{orderSuccess && (
<div className="p-3 rounded-lg bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 text-xs flex items-center gap-2">
<Check className="w-4 h-4 shrink-0" />
<span>Transaction successfully recorded!</span>
</div>
)}
<button
type="submit"
className={`w-full font-bold py-2.5 px-4 rounded-lg transition-all active:scale-[0.98] mt-2 shadow-lg ${tradeType === 'BUY' ? 'bg-gradient-to-r from-emerald-500 to-teal-500 hover:from-emerald-600 hover:to-teal-600 text-slate-950 shadow-emerald-500/10' : 'bg-gradient-to-r from-rose-500 to-pink-500 hover:from-rose-600 hover:to-pink-600 text-white shadow-rose-500/10'}`}
>
Submit Order to Market
</button>
</form>
</div>
</div>
</div>
{/* SECTION 3: Holdings Table & Transactions Log */}
<div className="grid grid-cols-1 xl:grid-cols-3 gap-8">
{/* Left 2 Columns: Holdings List */}
<div className="xl:col-span-2 bg-slate-900/60 backdrop-blur-md border border-slate-800 rounded-2xl p-6 text-slate-100 shadow-xl space-y-4">
<div className="flex justify-between items-center border-b border-slate-800 pb-3">
<h3 className="text-lg font-bold text-white flex items-center gap-2">
<TrendingUp className="text-emerald-400 w-5 h-5" /> Portfolio Holdings ({activePortfolio.holdings.length})
</h3>
<div className="text-xs text-slate-400 flex items-center gap-4">
<span>Cash Balance: <span className="font-mono text-emerald-400 font-bold">${activePortfolio.cash.toLocaleString()}</span></span>
</div>
</div>
<div className="overflow-x-auto border border-slate-850 rounded-xl bg-slate-950/40">
<table className="w-full border-collapse text-left text-sm">
<thead>
<tr className="border-b border-slate-800 text-slate-400 font-semibold bg-slate-900/40">
<th className="p-3">Asset</th>
<th className="p-3">Shares</th>
<th className="p-3">Avg Price</th>
<th className="p-3">Price</th>
<th className="p-3">Hypothesis</th>
<th className="p-3 text-right">PnL</th>
</tr>
</thead>
<tbody>
{activePortfolio.holdings.length === 0 ? (
<tr>
<td colSpan={6} className="p-8 text-center text-slate-500">
No holdings in this Sandbox portfolio. Use the order mask to add assets.
</td>
</tr>
) : (
activePortfolio.holdings.map((hold) => {
const profitLoss = (hold.currentPrice - hold.avgPrice) * hold.shares;
const isPositive = profitLoss >= 0;
return (
<tr key={hold.symbol} className="border-b border-slate-850/50 hover:bg-slate-850/20 transition-colors">
<td className="p-3">
<div className="font-bold text-teal-400 font-mono">{hold.symbol}</div>
{hold.wknOrIsin && <div className="text-[10px] text-slate-500 font-mono">WKN: {hold.wknOrIsin}</div>}
</td>
<td className="p-3 font-mono font-medium">{hold.shares}</td>
<td className="p-3 font-mono text-slate-300">${hold.avgPrice.toFixed(2)}</td>
<td className="p-3 font-mono text-slate-300">${hold.currentPrice.toFixed(2)}</td>
<td className="p-3 text-slate-400 max-w-[200px] truncate text-xs" title={hold.hypothesisTag}>
{hold.hypothesisTag || '-'}
</td>
<td className={`p-3 font-mono text-right flex items-center justify-end gap-1 ${isPositive ? 'text-emerald-400' : 'text-rose-400'}`}>
{isPositive ? <ArrowUpRight className="w-4 h-4" /> : <ArrowDownRight className="w-4 h-4" />}
${Math.abs(profitLoss).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</td>
</tr>
);
})
)}
</tbody>
</table>
</div>
</div>
{/* Right 1 Column: Transactions History */}
<div className="bg-slate-900/60 backdrop-blur-md border border-slate-800 rounded-2xl p-6 text-slate-100 shadow-xl space-y-4">
<div className="border-b border-slate-800 pb-3">
<h3 className="text-lg font-bold text-white flex items-center gap-2">
<Calendar className="text-emerald-400 w-5 h-5" /> Recent Order Book Entries
</h3>
</div>
<div className="max-h-60 overflow-y-auto space-y-2 pr-1">
{activePortfolio.transactions.length === 0 ? (
<p className="text-xs text-slate-500 text-center py-8">No transactions in this portfolio yet.</p>
) : (
activePortfolio.transactions.map((tx) => {
const isBuy = tx.type === 'BUY';
return (
<div key={tx.id} className="p-3 bg-slate-950/40 border border-slate-850 rounded-lg space-y-1.5">
<div className="flex justify-between items-start">
<div className="flex items-center gap-2">
<span className={`px-1.5 py-0.5 rounded text-[9px] font-bold ${isBuy ? 'bg-emerald-500/10 text-emerald-400 border border-emerald-500/20' : 'bg-rose-500/10 text-rose-400 border border-rose-500/20'}`}>
{isBuy ? 'BUY' : 'SELL'}
</span>
<span className="font-mono font-bold text-slate-200">{tx.symbol}</span>
</div>
<span className="text-[10px] text-slate-500 font-mono">{tx.timestamp}</span>
</div>
<div className="flex justify-between text-xs font-mono text-slate-400">
<span>{tx.shares} shares @ ${tx.price.toFixed(2)}</span>
<span className="text-[10px] text-slate-500">Fee: ${tx.feeApplied.toFixed(2)}</span>
</div>
{tx.hypothesisTag && (
<div className="text-[10px] text-slate-500 flex items-center gap-1 border-t border-slate-900 pt-1">
<Tag className="w-2.5 h-2.5 text-teal-500" />
<span className="italic truncate max-w-[220px]">{tx.hypothesisTag}</span>
</div>
)}
</div>
);
})
)}
</div>
</div>
</div>
<SandboxMathModal isOpen={isMathModalOpen} onClose={() => setIsMathModalOpen(false)} />
<SandboxBlueprintModal isOpen={isBlueprintModalOpen} onClose={() => setIsBlueprintModalOpen(false)} />
</div>
);
}