'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 PortfolioMathModal from './PortfolioMathModal'; 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('Fokus auf KI-Infrastruktur'); const [orderError, setOrderError] = useState(null); const [orderSuccess, setOrderSuccess] = useState(false); const [showMathAccordion, setShowMathAccordion] = useState(false); const [isMathModalOpen, setIsMathModalOpen] = useState(false); const [showMsciBenchmark, setShowMsciBenchmark] = useState(true); // Kelly Position Sizing states const [kellySource, setKellySource] = useState<'scanner' | 'crypto' | 'econometric' | 'custom'>('custom'); const [customProb, setCustomProb] = useState(0.60); const [oddsRatio, setOddsRatio] = useState(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(null); const [stressError, setStressError] = useState(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("Fehler beim Laden der Stresstest-Daten."); } } catch (err) { console.error("Stress test fetch error:", err); setStressError("Netzwerkfehler beim Laden des Stresstests."); } finally { setStressLoading(false); } }; fetchStressTest(); }, [portfolio, activeStressTab]); // Ingested Portfolio Ingestion Cockpit States const [newAssetTicker, setNewAssetTicker] = useState(''); const [newAssetShares, setNewAssetShares] = useState(''); const [newAssetPrice, setNewAssetPrice] = useState(''); const [portfolioPrices, setPortfolioPrices] = useState>({}); const MOCK_PRICES: Record = { '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 = {}; 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("Bitte geben Sie eine gültige Stückzahl und einen Einstandskurs an."); 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 (
Lade Sandbox-Modul...
); } // 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('Bitte geben Sie eine gültige Stückzahl und einen Kurs an.'); 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' ? 'Unzureichendes Barguthaben (inklusive allfälliger Transaktionsgebühren).' : 'Unzureichende Anteile im Depot für den Verkauf.' ); } }; return (
{activePortfolio.riskProfile?.status === 'RED' && (
Kritische Klumpenrisiken (Kovarianz RED): {activePortfolio.riskProfile.message}
)} {/* SECTION 1: Portfolio Selector & Stats Bar */}
Strategic Sandbox
{/* Net Worth Card */}
Gesamtwert
${netWorth.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
{/* Performance Card */}
GuV (Gesamt)
{isPositiveOverall ? : } {isPositiveOverall ? '+' : ''}{totalGainLossPct.toFixed(2)}%
{/* Live EWMA Vol Card */}
EWMA Volatilität
{ewmaResult.latest.toFixed(2)}%
{/* Covariance Risk Traffic Light Card */}
Kovarianz-Ampel
{activePortfolio.riskProfile?.status || 'GREEN'} RISK
{/* Modal for creating a new Portfolio */} {showNewPortfolioModal && (

Neues Sandbox-Portfolio

setNewPortfolioName(e.target.value)} />
setNewStartingBalance(Number(e.target.value))} />
)} {/* Accordion / Math Button */}
{showMathAccordion && (

1. EWMA Volatilitätsmodell

Die Volatilität wird mittels des Exponentially Weighted Moving Average (EWMA)-Modells ermittelt. Jüngere Renditen erhalten hierbei ein höheres Gewicht als weiter in der Vergangenheit liegende Renditen, gesteuert durch den Zerfallsparameter (Lambda).

Die tägliche Volatilität wird auf ein ganzes Jahr hochgerechnet (Annualisierung) unter der Annahme von 252 Handelstagen:

RiskMetrics empfiehlt für tägliche Finanzdaten einen Lambda-Wert von .

2. Kelly-Kriterium zur Positionsgrößenbestimmung

Die Kelly-Formel bestimmt den optimalen Anteil des Kapitals (), der in ein Geschäft investiert werden soll, um das exponentielle Wachstum des Kapitals zu maximieren:

Um Risiken durch ungenaue Schätzungen zu verringern, wenden wir das konservative Half-Kelly-Sizing an und begrenzen das Ergebnis auf (zusätzlich begrenzt auf ).

3. Covariance & Cluster Risk (Kovarianz-Ampel)

Die Kovarianz zwischen Assets wird durch Multiplikation ihrer paarweisen Korrelation mit ihren jeweiligen Standardabweichungen (Volatilitäten) bestimmt:

Ein Klumpenrisiko (Risk RED) wird ausgelöst, wenn ein Asset eine Korrelation zu bestehenden Positionen aufweist und diese Positionen jeweils mehr als 15% des Portfolios ausmachen.

)}
{/* SECTION: Mein Portfolio Ingestion Cockpit */}

Mein Portfolio Cockpit

Gesamt-Inventarwert: ${portfolioCalculated.totalValue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
{portfolioCalculated.items.length === 0 ? ( ) : ( portfolioCalculated.items.map((item) => { const isPositive = item.pnlUsd >= 0; const weightPct = item.weight * 100; return ( {/* Symbol & Name */} {/* Shares (Inline input) */} {/* Entry Price (Inline input) */} {/* Current Price */} {/* Position Value */} {/* PnL */} {/* Weighting Progress Bar */} {/* Actions */} ); }) )} {/* Adding Asset Inline Row */}
Asset / Ticker Stücke (Shares) Einstandspreis Aktueller Kurs Positionswert Performance (PnL) Gewichtung (w_i) Aktionen
Bislang keine Assets im Ingestion-Cockpit. Fügen Sie unten ein Asset hinzu.
{item.ticker}
{item.name}
{ 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" /> { 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" /> ${item.currentPrice.toFixed(2)} ${item.positionValue.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
{isPositive ? '+' : ''}${item.pnlUsd.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
{isPositive ? '+' : ''}{item.pnlPct.toFixed(2)}%
{weightPct.toFixed(1)}%
setNewAssetTicker(e.target.value)} /> setNewAssetShares(e.target.value === '' ? '' : Number(e.target.value))} /> setNewAssetPrice(e.target.value === '' ? '' : Number(e.target.value))} /> - - - -
{/* SECTION: Systemischer Makro-Stresstest (Portfolio-LMM) */}

Systemischer Makro-Stresstest (Portfolio-LMM)

Analysiert die historische Sensitivität des Portfolios gegenüber Kern-Makro-Ereignissen über die letzten 36 Monate.

{/* Event type tabs */}
{(['FOMC Rates', 'CPI Inflation', 'Labor Market'] as const).map((tab) => ( ))}
{stressLoading ? (
Kalkuliere Swamy-Arora GLS-Schätzer...
) : stressError || !stressData ? (
{stressError || 'Keine Daten geladen.'}
) : (
{/* LMM Summary Statistics */}
Regressions-Koeffizienten (GLS) {/* Fixed Effects list */}
{stressData.regressionResults?.fixedEffects.map((fe: any) => { const isPositive = fe.estimate >= 0; const isImpact = fe.name === 'Post-Event Impact'; return (
{fe.name === 'Intercept' ? 'Basisschnittstelle (Intercept)' : fe.name === 'Pre-Event Drift' ? 'Pre-Event Trend (Drift)' : fe.name === 'Post-Event Impact' ? 'Systemisches Portfolio Beta' : 'VIX-Volatilitäts-Sensitivität'}
SE: {fe.se.toFixed(4)} | p-Wert: {fe.pVal.toFixed(4)}
{isPositive ? '+' : ''}{fe.estimate.toFixed(4)} {fe.sig}
); })}
{/* Model Fit metrics */}
R-Quadrat (Bestimmtheitsmaß): {(stressData.regressionResults?.rSquared * 100).toFixed(1)}%
AIC: {stressData.regressionResults?.aic} BIC: {stressData.regressionResults?.bic} Events: {stressData.eventCount}
{/* Recharts Area/Line Chart (2/3 width) */}
Durchschnittlicher kumulierter Ertrag im Zeitfenster [-30, +30] Tage Akkumulierte Log-Renditen
`T${v >= 0 ? '+' : ''}${v}`} /> `${v.toFixed(1)}%`} /> `Relativer Tag: T${label >= 0 ? '+' : ''}${label}`} />
)} {/* Quantitative Commentary Card */} {stressData && !stressLoading && (
Quantitative Analyse-Auswertung

{(() => { 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-Zinsentscheiden' : activeStressTab === 'CPI Inflation' ? 'CPI-Inflationsdaten' : 'Arbeitsmarktdaten'; let significanceText = isSignificant ? `Dieser Effekt ist mit einem p-Wert von ${pVal.toFixed(4)} statistisch signifikant.` : `Dieser Effekt ist mit einem p-Wert von ${pVal.toFixed(4)} statistisch nicht hochgradig signifikant (Rauscheinfluss möglich).`; if (isNegative) { return `Historische Reaktivität: Bei ${eventNameText} zeigt dein Depot ein negatives Beta von -${absBeta}. ${significanceText} Eine Absicherung über defensive Sektoren (z.B. Erhöhung der Bargeldquote oder defensive Consumer-Titel) senkt das Volatilitätsrisiko in dieser Post-Event-Phase um ca. ${Math.round(Math.abs(impactBeta) * 35)}%.`; } else { return `Historische Reaktivität: Bei ${eventNameText} zeigt dein Depot ein positives Beta von +${absBeta}. ${significanceText} Dein Portfolio profitiert tendenziell von der anschließenden Marktdynamik. Sie können erwägen, die Hebelwirkung durch Zukäufe in Momentum-Aktien zu erhöhen.`; } })()}

)}
{/* SECTION 2: Chart / Analytics & Order Form */}
{/* Left 2 Columns: Analytics Performance Plot */}

Portfolio Wertentwicklung & Benchmark

`$${v.toLocaleString()}`} /> {showMsciBenchmark && ( )}
{/* EWMA parameter tuner slider */}

Parameteranpassung EWMA Lambda (λ)

Zerfallsfaktor steuert die Schock-Sensitivität des Volatilitätsmodells.

setEwmaLambda(parseFloat(e.target.value))} className="w-40 accent-emerald-400 cursor-pointer" /> λ = {ewmaLambda.toFixed(2)}
{/* Right 1 Column: Advanced Order Mask */}

Order-Maske (Simuliert)

setTradeSymbol(e.target.value.toUpperCase())} />
setTradeWknOrIsin(e.target.value)} />
setTradeShares(Number(e.target.value))} />
setTradePrice(Number(e.target.value))} />
{/* Order direction buttons */}
{/* Kelly Sizing Risk Recommendation Widget */} {tradeType === 'BUY' && (
Risk-Engine Sizing Kelly recommendation
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" />
{kellySource === 'custom' && (
Erfolgswahrscheinlichkeit: {(kellyProbability * 100).toFixed(0)}%
setCustomProb(Number(e.target.value))} className="w-full accent-emerald-500 h-1 bg-slate-900 rounded" />
)} {kellySource !== 'custom' && (
System-Wahrscheinlichkeit: {(kellyProbability * 100).toFixed(1)}%
)} {potentialClusterRisk && (
Klumpenrisiko! Korrelation > 0.70 zu bestehenden Positionen. Kelly-Empfehlung wurde um 50% halbiert.
)}
Kelly-Anteil: {(kellyFraction * 100).toFixed(1)}% des Cashs
Kaufvolumen: ${Math.round(recommendedKellyCash).toLocaleString()}
)} {/* Hypothesis input */}
setHypothesisTag(e.target.value)} />
{/* Fees Toggle */}
Ordergebühren simulieren setSimulateFees(e.target.checked)} className="rounded border-slate-800 text-emerald-500 focus:ring-0 accent-emerald-500 w-4 h-4 cursor-pointer" />
{/* Backfill Date Picker Toggle */}
Historischer Backfill setIsBackfill(e.target.checked)} className="rounded border-slate-800 text-emerald-500 focus:ring-0 accent-emerald-500 w-4 h-4 cursor-pointer" />
{isBackfill && ( setBackfillDate(e.target.value)} /> )}
{orderError && (
{orderError}
)} {orderSuccess && (
Transaktion erfolgreich gebucht!
)}
{/* SECTION 3: Holdings Table & Transactions Log */}
{/* Left 2 Columns: Holdings List */}

Depotbestände ({activePortfolio.holdings.length})

Barguthaben: ${activePortfolio.cash.toLocaleString()}
{activePortfolio.holdings.length === 0 ? ( ) : ( activePortfolio.holdings.map((hold) => { const profitLoss = (hold.currentPrice - hold.avgPrice) * hold.shares; const isPositive = profitLoss >= 0; return ( ); }) )}
Asset Stücke Einstand Kurs Hypothese GuV
Keine Bestände in diesem Sandbox-Portfolio. Nutzen Sie die Order-Maske, um Werte hinzuzufügen.
{hold.symbol}
{hold.wknOrIsin &&
WKN: {hold.wknOrIsin}
}
{hold.shares} ${hold.avgPrice.toFixed(2)} ${hold.currentPrice.toFixed(2)} {hold.hypothesisTag || '-'} {isPositive ? : } ${Math.abs(profitLoss).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
{/* Right 1 Column: Transactions History */}

Letzte Orderbuch-Einträge

{activePortfolio.transactions.length === 0 ? (

Bislang keine Transaktionen in diesem Portfolio.

) : ( activePortfolio.transactions.map((tx) => { const isBuy = tx.type === 'BUY'; return (
{isBuy ? 'KAUF' : 'VERKAUF'} {tx.symbol}
{tx.timestamp}
{tx.shares} Stk @ ${tx.price.toFixed(2)} Gebühr: ${tx.feeApplied.toFixed(2)}
{tx.hypothesisTag && (
{tx.hypothesisTag}
)}
); }) )}
setIsMathModalOpen(false)} />
); }