stock-bot/apps/stock/web-app/src/features/dashboard/components/PortfolioTable.tsx

642 lines
18 KiB
TypeScript

import { DataTable } from '@/components/ui';
import type { ColumnDef } from '@tanstack/react-table';
import React from 'react';
interface PortfolioItem {
symbol: string;
quantity: number;
avgPrice: number;
currentPrice: number;
value: number;
change: number;
changePercent: number;
// Additional columns for stress testing
volume: number;
marketCap: number;
pe: number;
pb: number;
roe: number;
debt: number;
revenue: number;
earnings: number;
dividend: number;
beta: number;
rsi: number;
macd: number;
sma20: number;
sma50: number;
sma200: number;
support: number;
resistance: number;
volatility: number;
sharpe: number;
alpha: number;
correlation: number;
sector: string;
industry: string;
country: string;
exchange: string;
currency: string;
lastUpdate: string;
analyst1: string;
analyst2: string;
analyst3: string;
rating1: number;
rating2: number;
rating3: number;
target1: number;
target2: number;
target3: number;
risk: string;
esg: number;
}
export function PortfolioTable() {
// Generate 100,000 rows of sample data
const data: PortfolioItem[] = React.useMemo(() => {
const symbols = [
'AAPL',
'GOOGL',
'MSFT',
'TSLA',
'AMZN',
'META',
'NFLX',
'NVDA',
'AMD',
'INTC',
'CRM',
'ORCL',
'IBM',
'CSCO',
'UBER',
'LYFT',
'SNAP',
'TWTR',
'SPOT',
'SQ',
];
const sectors = [
'Technology',
'Healthcare',
'Finance',
'Energy',
'Consumer',
'Industrial',
'Materials',
'Utilities',
'Real Estate',
'Telecom',
];
const industries = [
'Software',
'Hardware',
'Biotech',
'Banking',
'Oil & Gas',
'Retail',
'Manufacturing',
'Mining',
'Utilities',
'REITs',
];
const countries = [
'USA',
'Canada',
'UK',
'Germany',
'Japan',
'China',
'India',
'Brazil',
'Australia',
'France',
];
const exchanges = ['NYSE', 'NASDAQ', 'LSE', 'TSX', 'Nikkei', 'SSE', 'BSE', 'ASX', 'Euronext'];
const currencies = ['USD', 'CAD', 'GBP', 'EUR', 'JPY', 'CNY', 'INR', 'BRL', 'AUD'];
const analysts = [
'Goldman Sachs',
'Morgan Stanley',
'JPMorgan',
'Bank of America',
'Wells Fargo',
'Credit Suisse',
'Deutsche Bank',
'Barclays',
'UBS',
'Citigroup',
];
const risks = ['Low', 'Medium', 'High', 'Very High'];
return Array.from({ length: 100000 }, (_, i) => {
const basePrice = 50 + Math.random() * 500;
const change = (Math.random() - 0.5) * 20;
const quantity = Math.floor(Math.random() * 1000) + 1;
return {
symbol: `${symbols[i % symbols.length]}${Math.floor(i / symbols.length)}`,
quantity,
avgPrice: basePrice,
currentPrice: basePrice + change,
value: (basePrice + change) * quantity,
change: change * quantity,
changePercent: (change / basePrice) * 100,
volume: Math.floor(Math.random() * 10000000),
marketCap: Math.floor(Math.random() * 1000000000000),
pe: Math.random() * 50 + 5,
pb: Math.random() * 10 + 0.5,
roe: Math.random() * 30 + 5,
debt: Math.random() * 50,
revenue: Math.floor(Math.random() * 100000000000),
earnings: Math.floor(Math.random() * 10000000000),
dividend: Math.random() * 5,
beta: Math.random() * 3 + 0.5,
rsi: Math.random() * 100,
macd: (Math.random() - 0.5) * 10,
sma20: basePrice + (Math.random() - 0.5) * 10,
sma50: basePrice + (Math.random() - 0.5) * 20,
sma200: basePrice + (Math.random() - 0.5) * 50,
support: basePrice - Math.random() * 20,
resistance: basePrice + Math.random() * 20,
volatility: Math.random() * 100,
sharpe: Math.random() * 3,
alpha: (Math.random() - 0.5) * 20,
correlation: (Math.random() - 0.5) * 2,
sector: sectors[Math.floor(Math.random() * sectors.length)] || 'Technology',
industry: industries[Math.floor(Math.random() * industries.length)] || 'Software',
country: countries[Math.floor(Math.random() * countries.length)] || 'USA',
exchange: exchanges[Math.floor(Math.random() * exchanges.length)] || 'NYSE',
currency: currencies[Math.floor(Math.random() * currencies.length)] || 'USD',
lastUpdate: new Date(Date.now() - Math.random() * 86400000).toISOString(),
analyst1: analysts[Math.floor(Math.random() * analysts.length)] || 'Goldman Sachs',
analyst2: analysts[Math.floor(Math.random() * analysts.length)] || 'Morgan Stanley',
analyst3: analysts[Math.floor(Math.random() * analysts.length)] || 'JPMorgan',
rating1: Math.random() * 5 + 1,
rating2: Math.random() * 5 + 1,
rating3: Math.random() * 5 + 1,
target1: basePrice + (Math.random() - 0.3) * 50,
target2: basePrice + (Math.random() - 0.3) * 50,
target3: basePrice + (Math.random() - 0.3) * 50,
risk: risks[Math.floor(Math.random() * risks.length)] || 'Medium',
esg: Math.random() * 100,
};
});
}, []);
const columns: ColumnDef<PortfolioItem>[] = [
{
id: 'symbol',
header: 'Symbol',
accessorKey: 'symbol',
size: 120,
cell: ({ getValue }) => (
<span className="font-mono font-bold text-primary-400">{getValue() as string}</span>
),
},
{
id: 'quantity',
header: 'Quantity',
accessorKey: 'quantity',
size: 100,
cell: ({ getValue }) => (
<span className="font-mono">{(getValue() as number).toLocaleString()}</span>
),
},
{
id: 'avgPrice',
header: 'Avg Price',
accessorKey: 'avgPrice',
size: 120,
cell: ({ getValue }) => (
<span className="font-mono">${(getValue() as number).toFixed(2)}</span>
),
},
{
id: 'currentPrice',
header: 'Current Price',
accessorKey: 'currentPrice',
size: 120,
cell: ({ getValue }) => (
<span className="font-mono">${(getValue() as number).toFixed(2)}</span>
),
},
{
id: 'value',
header: 'Value',
accessorKey: 'value',
size: 120,
cell: ({ getValue }) => (
<span className="font-mono font-bold">${(getValue() as number).toLocaleString()}</span>
),
},
{
id: 'change',
header: 'P&L',
accessorKey: 'change',
size: 120,
cell: ({ getValue }) => {
const value = getValue() as number;
const isPositive = value >= 0;
return (
<span className={`font-mono font-medium ${isPositive ? 'text-success' : 'text-danger'}`}>
{isPositive ? '+' : ''}${value.toLocaleString()}
</span>
);
},
},
{
id: 'changePercent',
header: 'P&L %',
accessorKey: 'changePercent',
size: 100,
cell: ({ getValue }) => {
const value = getValue() as number;
const isPositive = value >= 0;
return (
<span className={`font-mono font-medium ${isPositive ? 'text-success' : 'text-danger'}`}>
{isPositive ? '+' : ''}
{value.toFixed(2)}%
</span>
);
},
},
{
id: 'volume',
header: 'Volume',
accessorKey: 'volume',
size: 120,
cell: ({ getValue }) => (
<span className="font-mono text-text-secondary">
{(getValue() as number).toLocaleString()}
</span>
),
},
{
id: 'marketCap',
header: 'Market Cap',
accessorKey: 'marketCap',
size: 120,
cell: ({ getValue }) => {
const value = getValue() as number;
if (value >= 1e12) {return <span className="font-mono">${(value / 1e12).toFixed(2)}T</span>;}
if (value >= 1e9) {return <span className="font-mono">${(value / 1e9).toFixed(2)}B</span>;}
if (value >= 1e6) {return <span className="font-mono">${(value / 1e6).toFixed(2)}M</span>;}
return <span className="font-mono">${value.toLocaleString()}</span>;
},
},
{
id: 'pe',
header: 'P/E',
accessorKey: 'pe',
size: 80,
cell: ({ getValue }) => (
<span className="font-mono">{(getValue() as number).toFixed(2)}</span>
),
},
{
id: 'pb',
header: 'P/B',
accessorKey: 'pb',
size: 80,
cell: ({ getValue }) => (
<span className="font-mono">{(getValue() as number).toFixed(2)}</span>
),
},
{
id: 'roe',
header: 'ROE %',
accessorKey: 'roe',
size: 80,
cell: ({ getValue }) => (
<span className="font-mono">{(getValue() as number).toFixed(1)}%</span>
),
},
{
id: 'debt',
header: 'Debt %',
accessorKey: 'debt',
size: 80,
cell: ({ getValue }) => (
<span className="font-mono">{(getValue() as number).toFixed(1)}%</span>
),
},
{
id: 'revenue',
header: 'Revenue',
accessorKey: 'revenue',
size: 120,
cell: ({ getValue }) => {
const value = getValue() as number;
if (value >= 1e9) {return <span className="font-mono">${(value / 1e9).toFixed(2)}B</span>;}
if (value >= 1e6) {return <span className="font-mono">${(value / 1e6).toFixed(2)}M</span>;}
return <span className="font-mono">${value.toLocaleString()}</span>;
},
},
{
id: 'earnings',
header: 'Earnings',
accessorKey: 'earnings',
size: 120,
cell: ({ getValue }) => {
const value = getValue() as number;
if (value >= 1e9) {return <span className="font-mono">${(value / 1e9).toFixed(2)}B</span>;}
if (value >= 1e6) {return <span className="font-mono">${(value / 1e6).toFixed(2)}M</span>;}
return <span className="font-mono">${value.toLocaleString()}</span>;
},
},
{
id: 'dividend',
header: 'Dividend %',
accessorKey: 'dividend',
size: 100,
cell: ({ getValue }) => {
const value = getValue() as number;
return <span className="font-mono text-success">{value.toFixed(2)}%</span>;
},
},
{
id: 'beta',
header: 'Beta',
accessorKey: 'beta',
size: 80,
cell: ({ getValue }) => {
const value = getValue() as number;
const color = value > 1 ? 'text-warning' : value < 1 ? 'text-success' : 'text-text-primary';
return <span className={`font-mono ${color}`}>{value.toFixed(2)}</span>;
},
},
{
id: 'rsi',
header: 'RSI',
accessorKey: 'rsi',
size: 80,
cell: ({ getValue }) => {
const value = getValue() as number;
const color =
value > 70 ? 'text-danger' : value < 30 ? 'text-success' : 'text-text-primary';
return <span className={`font-mono ${color}`}>{value.toFixed(1)}</span>;
},
},
{
id: 'macd',
header: 'MACD',
accessorKey: 'macd',
size: 80,
cell: ({ getValue }) => {
const value = getValue() as number;
const color = value > 0 ? 'text-success' : 'text-danger';
return <span className={`font-mono ${color}`}>{value.toFixed(2)}</span>;
},
},
{
id: 'sma20',
header: 'SMA 20',
accessorKey: 'sma20',
size: 100,
cell: ({ getValue }) => (
<span className="font-mono">${(getValue() as number).toFixed(2)}</span>
),
},
{
id: 'sma50',
header: 'SMA 50',
accessorKey: 'sma50',
size: 100,
cell: ({ getValue }) => (
<span className="font-mono">${(getValue() as number).toFixed(2)}</span>
),
},
{
id: 'sma200',
header: 'SMA 200',
accessorKey: 'sma200',
size: 100,
cell: ({ getValue }) => (
<span className="font-mono">${(getValue() as number).toFixed(2)}</span>
),
},
{
id: 'support',
header: 'Support',
accessorKey: 'support',
size: 100,
cell: ({ getValue }) => (
<span className="font-mono text-success">${(getValue() as number).toFixed(2)}</span>
),
},
{
id: 'resistance',
header: 'Resistance',
accessorKey: 'resistance',
size: 100,
cell: ({ getValue }) => (
<span className="font-mono text-danger">${(getValue() as number).toFixed(2)}</span>
),
},
{
id: 'volatility',
header: 'Volatility',
accessorKey: 'volatility',
size: 100,
cell: ({ getValue }) => (
<span className="font-mono">{(getValue() as number).toFixed(1)}%</span>
),
},
{
id: 'sharpe',
header: 'Sharpe',
accessorKey: 'sharpe',
size: 80,
cell: ({ getValue }) => (
<span className="font-mono">{(getValue() as number).toFixed(2)}</span>
),
},
{
id: 'alpha',
header: 'Alpha',
accessorKey: 'alpha',
size: 80,
cell: ({ getValue }) => {
const value = getValue() as number;
const color = value > 0 ? 'text-success' : 'text-danger';
return <span className={`font-mono ${color}`}>{value.toFixed(2)}</span>;
},
},
{
id: 'correlation',
header: 'Correlation',
accessorKey: 'correlation',
size: 100,
cell: ({ getValue }) => (
<span className="font-mono">{(getValue() as number).toFixed(2)}</span>
),
},
{
id: 'sector',
header: 'Sector',
accessorKey: 'sector',
size: 150,
cell: ({ getValue }) => <span className="text-text-secondary">{getValue() as string}</span>,
},
{
id: 'industry',
header: 'Industry',
accessorKey: 'industry',
size: 150,
cell: ({ getValue }) => <span className="text-text-secondary">{getValue() as string}</span>,
},
{
id: 'country',
header: 'Country',
accessorKey: 'country',
size: 100,
cell: ({ getValue }) => <span className="text-text-secondary">{getValue() as string}</span>,
},
{
id: 'exchange',
header: 'Exchange',
accessorKey: 'exchange',
size: 100,
cell: ({ getValue }) => <span className="text-text-secondary">{getValue() as string}</span>,
},
{
id: 'currency',
header: 'Currency',
accessorKey: 'currency',
size: 80,
cell: ({ getValue }) => (
<span className="font-mono text-text-secondary">{getValue() as string}</span>
),
},
{
id: 'lastUpdate',
header: 'Last Update',
accessorKey: 'lastUpdate',
size: 150,
cell: ({ getValue }) => (
<span className="text-xs text-text-muted">
{new Date(getValue() as string).toLocaleString()}
</span>
),
},
{
id: 'analyst1',
header: 'Analyst 1',
accessorKey: 'analyst1',
size: 120,
cell: ({ getValue }) => (
<span className="text-xs text-text-secondary">{getValue() as string}</span>
),
},
{
id: 'analyst2',
header: 'Analyst 2',
accessorKey: 'analyst2',
size: 120,
cell: ({ getValue }) => (
<span className="text-xs text-text-secondary">{getValue() as string}</span>
),
},
{
id: 'analyst3',
header: 'Analyst 3',
accessorKey: 'analyst3',
size: 120,
cell: ({ getValue }) => (
<span className="text-xs text-text-secondary">{getValue() as string}</span>
),
},
{
id: 'rating1',
header: 'Rating 1',
accessorKey: 'rating1',
size: 80,
cell: ({ getValue }) => {
const value = getValue() as number;
const color = value >= 4 ? 'text-success' : value >= 3 ? 'text-warning' : 'text-danger';
return <span className={`font-mono ${color}`}>{value.toFixed(1)}</span>;
},
},
{
id: 'rating2',
header: 'Rating 2',
accessorKey: 'rating2',
size: 80,
cell: ({ getValue }) => {
const value = getValue() as number;
const color = value >= 4 ? 'text-success' : value >= 3 ? 'text-warning' : 'text-danger';
return <span className={`font-mono ${color}`}>{value.toFixed(1)}</span>;
},
},
{
id: 'rating3',
header: 'Rating 3',
accessorKey: 'rating3',
size: 80,
cell: ({ getValue }) => {
const value = getValue() as number;
const color = value >= 4 ? 'text-success' : value >= 3 ? 'text-warning' : 'text-danger';
return <span className={`font-mono ${color}`}>{value.toFixed(1)}</span>;
},
},
{
id: 'target1',
header: 'Target 1',
accessorKey: 'target1',
size: 100,
cell: ({ getValue }) => (
<span className="font-mono">${(getValue() as number).toFixed(2)}</span>
),
},
{
id: 'target2',
header: 'Target 2',
accessorKey: 'target2',
size: 100,
cell: ({ getValue }) => (
<span className="font-mono">${(getValue() as number).toFixed(2)}</span>
),
},
{
id: 'target3',
header: 'Target 3',
accessorKey: 'target3',
size: 100,
cell: ({ getValue }) => (
<span className="font-mono">${(getValue() as number).toFixed(2)}</span>
),
},
{
id: 'risk',
header: 'Risk Level',
accessorKey: 'risk',
size: 100,
cell: ({ getValue }) => {
const value = getValue() as string;
const color =
value === 'Low' ? 'text-success' : value === 'Medium' ? 'text-warning' : 'text-danger';
return <span className={`px-2 py-1 rounded text-xs font-medium ${color}`}>{value}</span>;
},
},
{
id: 'esg',
header: 'ESG Score',
accessorKey: 'esg',
size: 100,
cell: ({ getValue }) => {
const value = getValue() as number;
const color = value >= 70 ? 'text-success' : value >= 40 ? 'text-warning' : 'text-danger';
return <span className={`font-mono ${color}`}>{value.toFixed(0)}</span>;
},
},
];
return (
<DataTable
data={data}
columns={columns}
onRowClick={_row => {/* Handle row click */}}
className="border border-border rounded-lg"
/>
);
}