Implement API health check badge in NavBar, enhance PlatformLineChart with metric filtering, and refactor PlatformSummary for improved layout and styling. Update dashboard to prioritize Platform Summary tab.
This commit is contained in:
@@ -9,6 +9,12 @@ import Logo from '../../../assets/img/logo.png'
|
|||||||
import {Loader} from '../../atoms'
|
import {Loader} from '../../atoms'
|
||||||
import useCookie from '../../../hooks/useCookie'
|
import useCookie from '../../../hooks/useCookie'
|
||||||
|
|
||||||
|
interface HealthCheckResponse {
|
||||||
|
status: string
|
||||||
|
totalDuration?: string
|
||||||
|
entries?: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
const navigation = [
|
const navigation = [
|
||||||
{ href: '/bots', name: 'Strategies' },
|
{ href: '/bots', name: 'Strategies' },
|
||||||
{ href: '/backtest', name: 'Backtests' },
|
{ href: '/backtest', name: 'Backtests' },
|
||||||
@@ -18,13 +24,83 @@ const navigation = [
|
|||||||
{ href: '/admin', name: 'Admin' },
|
{ href: '/admin', name: 'Admin' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// API Health Check Badge Component
|
||||||
|
const ApiHealthBadge = () => {
|
||||||
|
const { apiUrl } = useApiUrlStore()
|
||||||
|
|
||||||
|
const { data: healthData, isLoading, error } = useQuery<HealthCheckResponse>({
|
||||||
|
queryKey: ['apiHealth', apiUrl],
|
||||||
|
queryFn: async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${apiUrl}/health`)
|
||||||
|
if (!response.ok) throw new Error('Health check failed')
|
||||||
|
const data = await response.json()
|
||||||
|
// Handle .NET health check response format
|
||||||
|
// The status might be in the root or we need to check entries
|
||||||
|
return data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Health check error:', error)
|
||||||
|
return { status: 'Unhealthy' }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
refetchInterval: 30000, // Refetch every 30 seconds
|
||||||
|
retry: 1,
|
||||||
|
staleTime: 20000,
|
||||||
|
})
|
||||||
|
|
||||||
|
const getHealthStatus = () => {
|
||||||
|
if (isLoading) return { color: 'badge-ghost', label: '...', status: 'Checking...' }
|
||||||
|
if (error) return { color: 'badge-error', label: '●', status: 'Health Check Failed' }
|
||||||
|
|
||||||
|
// Check status field (could be "Healthy", "Degraded", "Unhealthy")
|
||||||
|
const status = healthData?.status || 'Unknown'
|
||||||
|
const statusLower = status.toLowerCase()
|
||||||
|
|
||||||
|
// Also check entries if status is not directly available
|
||||||
|
let overallStatus = statusLower
|
||||||
|
if (healthData?.entries) {
|
||||||
|
const entries = Object.values(healthData.entries)
|
||||||
|
const hasUnhealthy = entries.some((entry: any) => entry?.status?.toLowerCase() === 'unhealthy')
|
||||||
|
const hasDegraded = entries.some((entry: any) => entry?.status?.toLowerCase() === 'degraded')
|
||||||
|
|
||||||
|
if (hasUnhealthy) {
|
||||||
|
overallStatus = 'unhealthy'
|
||||||
|
} else if (hasDegraded) {
|
||||||
|
overallStatus = 'degraded'
|
||||||
|
} else if (entries.length > 0) {
|
||||||
|
overallStatus = 'healthy'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (overallStatus) {
|
||||||
|
case 'healthy':
|
||||||
|
return { color: 'badge-success', label: '●', status: 'API Status: Healthy' }
|
||||||
|
case 'degraded':
|
||||||
|
return { color: 'badge-warning', label: '●', status: 'API Status: Degraded' }
|
||||||
|
case 'unhealthy':
|
||||||
|
return { color: 'badge-error', label: '●', status: 'API Status: Unhealthy' }
|
||||||
|
default:
|
||||||
|
return { color: 'badge-ghost', label: '?', status: 'API Status: Unknown' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const healthStatus = getHealthStatus()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="tooltip tooltip-bottom" data-tip={healthStatus.status}>
|
||||||
|
<div className={`badge ${healthStatus.color} badge-xs cursor-help p-0.5`}>
|
||||||
|
<span className="text-[8px] leading-none">{healthStatus.label}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Global Loader Component
|
// Global Loader Component
|
||||||
const GlobalLoader = () => {
|
const GlobalLoader = () => {
|
||||||
const isFetching = useIsFetching()
|
const isFetching = useIsFetching()
|
||||||
return isFetching ? (
|
return isFetching ? (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Loader size="xs"></Loader>
|
<Loader size="xs"></Loader>
|
||||||
<span className="text-xs text-base-content/70 hidden lg:inline">Loading...</span>
|
|
||||||
</div>
|
</div>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
@@ -278,27 +354,28 @@ export default function NavBar() {
|
|||||||
<>
|
<>
|
||||||
{/* Navbar */}
|
{/* Navbar */}
|
||||||
<div className="navbar bg-base-300 shadow-lg border-b border-base-content/10 w-full relative z-50">
|
<div className="navbar bg-base-300 shadow-lg border-b border-base-content/10 w-full relative z-50">
|
||||||
{/* Navbar Start - Mobile Menu Button and Logo */}
|
{/* Navbar Start - Mobile Menu Button and Logo */}
|
||||||
<div className="navbar-start">
|
<div className="navbar-start flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
className="btn btn-ghost lg:hidden"
|
className="btn btn-ghost lg:hidden"
|
||||||
onClick={toggleSidebar}
|
onClick={toggleSidebar}
|
||||||
aria-label="Open sidebar"
|
aria-label="Open sidebar"
|
||||||
>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
className="h-5 w-5"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
stroke="currentColor"
|
|
||||||
>
|
>
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16" />
|
<svg
|
||||||
</svg>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
</button>
|
className="h-5 w-5"
|
||||||
<Link className="btn btn-ghost text-xl" to="/">
|
fill="none"
|
||||||
<img src={Logo} className="h-8 w-8 object-contain" alt="logo" />
|
viewBox="0 0 24 24"
|
||||||
</Link>
|
stroke="currentColor"
|
||||||
</div>
|
>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<Link className="btn btn-ghost text-xl" to="/">
|
||||||
|
<img src={Logo} className="h-8 w-8 object-contain" alt="logo" />
|
||||||
|
</Link>
|
||||||
|
<ApiHealthBadge />
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Navbar Center - Desktop Navigation */}
|
{/* Navbar Center - Desktop Navigation */}
|
||||||
<div className="navbar-center hidden lg:flex">
|
<div className="navbar-center hidden lg:flex">
|
||||||
@@ -326,7 +403,6 @@ export default function NavBar() {
|
|||||||
<div className="navbar-end gap-2">
|
<div className="navbar-end gap-2">
|
||||||
<GlobalLoader />
|
<GlobalLoader />
|
||||||
<div className="hidden md:flex items-center gap-2">
|
<div className="hidden md:flex items-center gap-2">
|
||||||
<span className="text-xs opacity-70 hidden xl:inline">Environment:</span>
|
|
||||||
<EnvironmentDropdown isInSidebar={false} />
|
<EnvironmentDropdown isInSidebar={false} />
|
||||||
</div>
|
</div>
|
||||||
{/* Show environment badge on mobile */}
|
{/* Show environment badge on mobile */}
|
||||||
@@ -394,9 +470,6 @@ export default function NavBar() {
|
|||||||
{authenticated && (
|
{authenticated && (
|
||||||
<li>
|
<li>
|
||||||
<div className="px-4 py-2">
|
<div className="px-4 py-2">
|
||||||
<div className="flex items-center justify-between mb-2">
|
|
||||||
<span className="text-sm opacity-70">Environment:</span>
|
|
||||||
</div>
|
|
||||||
<EnvironmentDropdown isInSidebar={true} />
|
<EnvironmentDropdown isInSidebar={true} />
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -14,12 +14,15 @@ interface CurrentPlatformData {
|
|||||||
lastUpdated?: Date | string
|
lastUpdated?: Date | string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MetricType = 'agents' | 'strategies' | 'volume' | 'pnl' | 'openInterest' | 'positionCount'
|
||||||
|
|
||||||
interface PlatformLineChartProps {
|
interface PlatformLineChartProps {
|
||||||
dailySnapshots: DailySnapshot[]
|
dailySnapshots: DailySnapshot[]
|
||||||
currentData?: CurrentPlatformData
|
currentData?: CurrentPlatformData
|
||||||
title?: string
|
title?: string
|
||||||
height?: number
|
height?: number
|
||||||
width?: number
|
width?: number
|
||||||
|
metrics?: MetricType[] // Filter which metrics to display
|
||||||
}
|
}
|
||||||
|
|
||||||
function PlatformLineChart({
|
function PlatformLineChart({
|
||||||
@@ -27,7 +30,8 @@ function PlatformLineChart({
|
|||||||
currentData,
|
currentData,
|
||||||
title = "Platform Metrics Over Time",
|
title = "Platform Metrics Over Time",
|
||||||
height = 400,
|
height = 400,
|
||||||
width = 800
|
width = 800,
|
||||||
|
metrics = ['agents', 'strategies', 'volume', 'pnl', 'openInterest', 'positionCount'] // Default: show all
|
||||||
}: PlatformLineChartProps) {
|
}: PlatformLineChartProps) {
|
||||||
const theme = useTheme().themeProperty()
|
const theme = useTheme().themeProperty()
|
||||||
|
|
||||||
@@ -78,13 +82,15 @@ function PlatformLineChart({
|
|||||||
return `$${value.toFixed(0)}`
|
return `$${value.toFixed(0)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const plotData = [
|
const allPlotData = [
|
||||||
{
|
{
|
||||||
|
key: 'agents' as MetricType,
|
||||||
type: 'scatter' as const,
|
type: 'scatter' as const,
|
||||||
mode: 'lines+markers' as const,
|
mode: 'lines+markers' as const,
|
||||||
name: 'Total Agents',
|
name: 'Total Agents',
|
||||||
x: dates,
|
x: dates,
|
||||||
y: agents,
|
y: agents,
|
||||||
|
yaxis: 'y' as const,
|
||||||
line: {
|
line: {
|
||||||
color: theme.primary,
|
color: theme.primary,
|
||||||
width: 2
|
width: 2
|
||||||
@@ -98,11 +104,13 @@ function PlatformLineChart({
|
|||||||
(currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>'
|
(currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
key: 'strategies' as MetricType,
|
||||||
type: 'scatter' as const,
|
type: 'scatter' as const,
|
||||||
mode: 'lines+markers' as const,
|
mode: 'lines+markers' as const,
|
||||||
name: 'Active Strategies',
|
name: 'Active Strategies',
|
||||||
x: dates,
|
x: dates,
|
||||||
y: strategies,
|
y: strategies,
|
||||||
|
yaxis: 'y' as const,
|
||||||
line: {
|
line: {
|
||||||
color: theme.success,
|
color: theme.success,
|
||||||
width: 2
|
width: 2
|
||||||
@@ -116,12 +124,13 @@ function PlatformLineChart({
|
|||||||
(currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>'
|
(currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
key: 'volume' as MetricType,
|
||||||
type: 'scatter' as const,
|
type: 'scatter' as const,
|
||||||
mode: 'lines+markers' as const,
|
mode: 'lines+markers' as const,
|
||||||
name: 'Total Volume',
|
name: 'Total Volume',
|
||||||
x: dates,
|
x: dates,
|
||||||
y: volume,
|
y: volume,
|
||||||
yaxis: 'y2',
|
yaxis: 'y2' as const,
|
||||||
line: {
|
line: {
|
||||||
color: theme.info,
|
color: theme.info,
|
||||||
width: 2
|
width: 2
|
||||||
@@ -135,12 +144,13 @@ function PlatformLineChart({
|
|||||||
(currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>'
|
(currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
key: 'pnl' as MetricType,
|
||||||
type: 'scatter' as const,
|
type: 'scatter' as const,
|
||||||
mode: 'lines+markers' as const,
|
mode: 'lines+markers' as const,
|
||||||
name: 'Platform PnL',
|
name: 'Platform PnL',
|
||||||
x: dates,
|
x: dates,
|
||||||
y: pnl,
|
y: pnl,
|
||||||
yaxis: 'y2',
|
yaxis: 'y2' as const,
|
||||||
line: {
|
line: {
|
||||||
color: pnl.some(p => p >= 0) ? theme.success : theme.error,
|
color: pnl.some(p => p >= 0) ? theme.success : theme.error,
|
||||||
width: 2
|
width: 2
|
||||||
@@ -154,12 +164,13 @@ function PlatformLineChart({
|
|||||||
(currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>'
|
(currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
key: 'openInterest' as MetricType,
|
||||||
type: 'scatter' as const,
|
type: 'scatter' as const,
|
||||||
mode: 'lines+markers' as const,
|
mode: 'lines+markers' as const,
|
||||||
name: 'Open Interest',
|
name: 'Open Interest',
|
||||||
x: dates,
|
x: dates,
|
||||||
y: openInterest,
|
y: openInterest,
|
||||||
yaxis: 'y2',
|
yaxis: 'y2' as const,
|
||||||
line: {
|
line: {
|
||||||
color: theme.warning,
|
color: theme.warning,
|
||||||
width: 2
|
width: 2
|
||||||
@@ -173,11 +184,13 @@ function PlatformLineChart({
|
|||||||
(currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>'
|
(currentSnapshot ? '<br><i>Current Data</i>' : '') + '<extra></extra>'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
key: 'positionCount' as MetricType,
|
||||||
type: 'scatter' as const,
|
type: 'scatter' as const,
|
||||||
mode: 'lines+markers' as const,
|
mode: 'lines+markers' as const,
|
||||||
name: 'Position Count',
|
name: 'Position Count',
|
||||||
x: dates,
|
x: dates,
|
||||||
y: positionCount,
|
y: positionCount,
|
||||||
|
yaxis: 'y' as const,
|
||||||
line: {
|
line: {
|
||||||
color: theme.secondary,
|
color: theme.secondary,
|
||||||
width: 2
|
width: 2
|
||||||
@@ -192,6 +205,11 @@ function PlatformLineChart({
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// Filter plot data based on metrics prop and remove key property
|
||||||
|
const plotData = allPlotData
|
||||||
|
.filter(item => metrics.includes(item.key))
|
||||||
|
.map(({ key, ...rest }) => rest)
|
||||||
|
|
||||||
const layout = {
|
const layout = {
|
||||||
title: {
|
title: {
|
||||||
text: title,
|
text: title,
|
||||||
@@ -214,7 +232,7 @@ function PlatformLineChart({
|
|||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
title: {
|
title: {
|
||||||
text: 'Count',
|
text: plotData.some(d => d.yaxis === 'y') ? 'Count' : '',
|
||||||
font: {
|
font: {
|
||||||
color: theme['base-content'] || '#ffffff'
|
color: theme['base-content'] || '#ffffff'
|
||||||
}
|
}
|
||||||
@@ -223,11 +241,12 @@ function PlatformLineChart({
|
|||||||
gridcolor: theme.neutral || '#333333',
|
gridcolor: theme.neutral || '#333333',
|
||||||
showgrid: true,
|
showgrid: true,
|
||||||
zeroline: false,
|
zeroline: false,
|
||||||
side: 'left' as const
|
side: 'left' as const,
|
||||||
|
visible: plotData.some(d => d.yaxis === 'y')
|
||||||
},
|
},
|
||||||
yaxis2: {
|
yaxis2: {
|
||||||
title: {
|
title: {
|
||||||
text: 'Amount ($)',
|
text: plotData.some(d => d.yaxis === 'y2') ? 'Amount ($)' : '',
|
||||||
font: {
|
font: {
|
||||||
color: theme['base-content'] || '#ffffff'
|
color: theme['base-content'] || '#ffffff'
|
||||||
}
|
}
|
||||||
@@ -238,7 +257,8 @@ function PlatformLineChart({
|
|||||||
zeroline: false,
|
zeroline: false,
|
||||||
side: 'right' as const,
|
side: 'right' as const,
|
||||||
overlaying: 'y' as const,
|
overlaying: 'y' as const,
|
||||||
tickformat: '$,.0f'
|
tickformat: '$,.0f',
|
||||||
|
visible: plotData.some(d => d.yaxis === 'y2')
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
font: {
|
font: {
|
||||||
|
|||||||
@@ -13,43 +13,44 @@ import PlatformSummary from './platformSummary'
|
|||||||
|
|
||||||
const tabs: ITabsType = [
|
const tabs: ITabsType = [
|
||||||
{
|
{
|
||||||
Component: Monitoring,
|
Component: PlatformSummary,
|
||||||
index: 1,
|
index: 1,
|
||||||
|
label: 'Platform Summary',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Component: Monitoring,
|
||||||
|
index: 2,
|
||||||
label: 'Monitoring',
|
label: 'Monitoring',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: Analytics,
|
Component: Analytics,
|
||||||
index: 2,
|
index: 3,
|
||||||
label: 'Analytics',
|
label: 'Analytics',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: BestAgents,
|
Component: BestAgents,
|
||||||
index: 3,
|
index: 4,
|
||||||
label: 'Best Agents',
|
label: 'Best Agents',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: AgentSearch,
|
Component: AgentSearch,
|
||||||
index: 4,
|
index: 5,
|
||||||
label: 'Agent Search',
|
label: 'Agent Search',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: AgentIndex,
|
Component: AgentIndex,
|
||||||
index: 5,
|
index: 6,
|
||||||
label: 'Agent Index',
|
label: 'Agent Index',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: AgentStrategy,
|
Component: AgentStrategy,
|
||||||
index: 6,
|
|
||||||
label: 'Agent Strategy',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Component: PlatformSummary,
|
|
||||||
index: 7,
|
index: 7,
|
||||||
label: 'Platform Summary',
|
label: 'Agent Strategy',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const Dashboard: React.FC = () => {
|
const Dashboard: React.FC = () => {
|
||||||
|
// Platform Summary is now the first tab (index 1)
|
||||||
const [selectedTab, setSelectedTab] = useState<number>(tabs[0].index)
|
const [selectedTab, setSelectedTab] = useState<number>(tabs[0].index)
|
||||||
|
|
||||||
useEffect(() => {}, [])
|
useEffect(() => {}, [])
|
||||||
|
|||||||
@@ -77,19 +77,19 @@ function PlatformSummary({index}: { index: number }) {
|
|||||||
const formatChangeIndicator = (change: number) => {
|
const formatChangeIndicator = (change: number) => {
|
||||||
if (change > 0) {
|
if (change > 0) {
|
||||||
return (
|
return (
|
||||||
<span className="text-green-500">
|
<span className="text-success">
|
||||||
+{change} Today
|
+{change} Today
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
} else if (change < 0) {
|
} else if (change < 0) {
|
||||||
return (
|
return (
|
||||||
<span className="text-red-500">
|
<span className="text-error">
|
||||||
{change} Today
|
{change} Today
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<span className="text-gray-500">
|
<span className="text-base-content/60">
|
||||||
No change
|
No change
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
@@ -130,284 +130,307 @@ function PlatformSummary({index}: { index: number }) {
|
|||||||
return (
|
return (
|
||||||
<div className="p-6 bg-base-100 min-h-screen">
|
<div className="p-6 bg-base-100 min-h-screen">
|
||||||
{/* Subtle refetching indicator */}
|
{/* Subtle refetching indicator */}
|
||||||
{isFetching && data && (
|
|
||||||
<div className="fixed top-4 right-4 z-50">
|
|
||||||
<div className="bg-blue-500 text-white px-3 py-1 rounded-full text-sm flex items-center gap-2">
|
|
||||||
<div className="loading loading-spinner loading-xs"></div>
|
|
||||||
Updating...
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Header */}
|
|
||||||
<div className="mb-8">
|
|
||||||
<h1 className="text-4xl font-bold text-white mb-2">
|
|
||||||
{formatNumber(platformData?.totalActiveStrategies || 0)} Strategies Deployed
|
|
||||||
</h1>
|
|
||||||
<div className="text-lg">
|
|
||||||
{platformData && formatChangeIndicator(changesToday.strategiesChange)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Main Stats Grid */}
|
{/* Main Stats Grid */}
|
||||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
<div className="grid grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||||
{/* Total Volume Traded */}
|
{/* Total Volume Traded */}
|
||||||
<div className="bg-base-200 rounded-lg p-6">
|
<div className="card bg-base-200">
|
||||||
<h3 className="text-lg font-semibold text-gray-400 mb-2">Total Volume Traded</h3>
|
<div className="card-body">
|
||||||
<div className="text-3xl font-bold text-white mb-1">
|
<h3 className="card-title text-base-content/70">Total Volume Traded</h3>
|
||||||
{formatCurrency(platformData?.totalPlatformVolume || 0)}
|
<div className="text-3xl font-bold text-base-content mb-1">
|
||||||
</div>
|
{formatCurrency(platformData?.totalPlatformVolume || 0)}
|
||||||
<div
|
</div>
|
||||||
className={`text-sm ${changesToday.volumeChange >= 0 ? 'text-green-500' : 'text-red-500'}`}>
|
<div
|
||||||
{changesToday.volumeChange >= 0 ? '+' : ''}{formatCurrency(changesToday.volumeChange)} Today
|
className={`text-sm ${changesToday.volumeChange >= 0 ? 'text-success' : 'text-error'}`}>
|
||||||
<span className="ml-2 text-gray-400">
|
{changesToday.volumeChange >= 0 ? '+' : ''}{formatCurrency(changesToday.volumeChange)} Today
|
||||||
|
<span className="ml-2 text-base-content/60">
|
||||||
({formatPercentageChange(platformData?.totalPlatformVolume || 0, changesToday.volumeChange)})
|
({formatPercentageChange(platformData?.totalPlatformVolume || 0, changesToday.volumeChange)})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/* Volume chart using daily snapshots */}
|
{/* Volume chart using daily snapshots */}
|
||||||
<div className="mt-4 h-16 flex items-end gap-1">
|
<div className="mt-4 h-16 flex items-end gap-1">
|
||||||
{platformData?.dailySnapshots && platformData.dailySnapshots.length > 0 ? (
|
{platformData?.dailySnapshots && platformData.dailySnapshots.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{/* Historical data bars */}
|
{/* Historical data bars */}
|
||||||
{[...platformData.dailySnapshots]
|
{[...platformData.dailySnapshots]
|
||||||
.sort((a, b) => new Date(a.date || 0).getTime() - new Date(b.date || 0).getTime())
|
.sort((a, b) => new Date(a.date || 0).getTime() - new Date(b.date || 0).getTime())
|
||||||
.slice(-7) // Last 7 days
|
.slice(-7) // Last 7 days
|
||||||
.map((snapshot, index) => {
|
.map((snapshot, index) => {
|
||||||
const maxVolume = Math.max(...platformData.dailySnapshots?.map(s => s.totalVolume || 0) || [0], platformData?.totalPlatformVolume || 0)
|
|
||||||
const height = maxVolume > 0 ? ((snapshot.totalVolume || 0) / maxVolume) * 100 : 0
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={`historical-${index}`}
|
|
||||||
className="flex-1 bg-green-500 rounded-sm opacity-60 hover:opacity-80 transition-opacity"
|
|
||||||
style={{ height: `${Math.max(height, 4)}%` }}
|
|
||||||
title={`${new Date(snapshot.date || 0).toLocaleDateString()}: ${formatCurrency(snapshot.totalVolume || 0)}`}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
{/* Current data bar */}
|
|
||||||
<div
|
|
||||||
className="flex-1 bg-blue-500 rounded-sm opacity-80 hover:opacity-100 transition-opacity border-2 border-blue-300"
|
|
||||||
style={{
|
|
||||||
height: `${(() => {
|
|
||||||
const maxVolume = Math.max(...platformData.dailySnapshots?.map(s => s.totalVolume || 0) || [0], platformData?.totalPlatformVolume || 0)
|
const maxVolume = Math.max(...platformData.dailySnapshots?.map(s => s.totalVolume || 0) || [0], platformData?.totalPlatformVolume || 0)
|
||||||
const height = maxVolume > 0 ? ((platformData?.totalPlatformVolume || 0) / maxVolume) * 100 : 0
|
const height = maxVolume > 0 ? ((snapshot.totalVolume || 0) / maxVolume) * 100 : 0
|
||||||
return Math.max(height, 4)
|
return (
|
||||||
})()}%`
|
<div
|
||||||
}}
|
key={`historical-${index}`}
|
||||||
title={`Current Total: ${formatCurrency(platformData?.totalPlatformVolume || 0)}`}
|
className="flex-1 bg-success rounded-sm opacity-60 hover:opacity-80 transition-opacity"
|
||||||
/>
|
style={{ height: `${Math.max(height, 4)}%` }}
|
||||||
</>
|
title={`${new Date(snapshot.date || 0).toLocaleDateString()}: ${formatCurrency(snapshot.totalVolume || 0)}`}
|
||||||
) : (
|
/>
|
||||||
<div className="w-full h-8 bg-green-500 rounded opacity-20"></div>
|
)
|
||||||
)}
|
})}
|
||||||
|
{/* Current data bar */}
|
||||||
|
<div
|
||||||
|
className="flex-1 bg-primary rounded-sm opacity-80 hover:opacity-100 transition-opacity border-2 border-primary/50"
|
||||||
|
style={{
|
||||||
|
height: `${(() => {
|
||||||
|
const maxVolume = Math.max(...platformData.dailySnapshots?.map(s => s.totalVolume || 0) || [0], platformData?.totalPlatformVolume || 0)
|
||||||
|
const height = maxVolume > 0 ? ((platformData?.totalPlatformVolume || 0) / maxVolume) * 100 : 0
|
||||||
|
return Math.max(height, 4)
|
||||||
|
})()}%`
|
||||||
|
}}
|
||||||
|
title={`Current Total: ${formatCurrency(platformData?.totalPlatformVolume || 0)}`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="w-full h-8 bg-success rounded opacity-20"></div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Top 3 Most Profitable */}
|
{/* Top 3 Most Profitable */}
|
||||||
<div className="bg-base-200 rounded-lg p-6">
|
<div className="card bg-base-200">
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="card-body">
|
||||||
<span className="text-2xl">🤑</span>
|
<div className="flex items-center gap-2 mb-4">
|
||||||
<h3 className="text-lg font-semibold text-gray-400">Top 3 Most Profitable</h3>
|
<span className="text-2xl">🤑</span>
|
||||||
</div>
|
<h3 className="card-title text-base-content/70">Top 3 Most Profitable</h3>
|
||||||
<div className="space-y-3">
|
</div>
|
||||||
{topStrategies?.topStrategies?.slice(0, 3).map((strategy, index) => (
|
<div className="space-y-3">
|
||||||
<div key={index} className="flex items-center justify-between">
|
{topStrategies?.topStrategies?.slice(0, 3).map((strategy, index) => (
|
||||||
<div className="flex items-center gap-3">
|
<div key={index} className="flex items-center justify-between">
|
||||||
<div className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
|
<div className="flex items-center gap-3">
|
||||||
<span className="text-xs font-bold text-white">
|
<div className="avatar placeholder">
|
||||||
{strategy.strategyName?.charAt(0) || 'S'}
|
<div className="bg-primary text-primary-content rounded-full w-8">
|
||||||
</span>
|
<span className="text-xs font-bold">
|
||||||
</div>
|
{strategy.strategyName?.charAt(0) || 'S'}
|
||||||
<div>
|
</span>
|
||||||
<div className="text-sm text-white font-medium">
|
</div>
|
||||||
{strategy.strategyName || '[Strategy Name]'}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-400">📧</div>
|
<div>
|
||||||
|
<div className="text-sm text-base-content font-medium">
|
||||||
|
{strategy.strategyName || '[Strategy Name]'}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-base-content/60">📧</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`text-sm font-bold ${strategy.netPnL && strategy.netPnL >= 0 ? 'text-success' : 'text-error'}`}>
|
||||||
|
{strategy.netPnL && strategy.netPnL >= 0 ? '+' : ''}{formatCurrency(strategy.netPnL || 0)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
)) || (
|
||||||
className={`text-sm font-bold ${strategy.netPnL && strategy.netPnL >= 0 ? 'text-green-500' : 'text-red-500'}`}>
|
<div className="text-base-content/60 text-sm">No profitable strategies found</div>
|
||||||
{strategy.netPnL && strategy.netPnL >= 0 ? '+' : ''}{formatCurrency(strategy.netPnL || 0)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)) || (
|
|
||||||
<div className="text-gray-500 text-sm">No profitable strategies found</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Top 3 Rising (by ROI) */}
|
{/* Top 3 Rising (by ROI) */}
|
||||||
<div className="bg-base-200 rounded-lg p-6">
|
<div className="card bg-base-200">
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="card-body">
|
||||||
<span className="text-2xl">📈</span>
|
<div className="flex items-center gap-2 mb-4">
|
||||||
<h3 className="text-lg font-semibold text-gray-400">Top 3 by ROI</h3>
|
<span className="text-2xl">📈</span>
|
||||||
</div>
|
<h3 className="card-title text-base-content/70">Top 3 by ROI</h3>
|
||||||
<div className="space-y-3">
|
</div>
|
||||||
{topStrategiesByRoi?.topStrategiesByRoi?.slice(0, 3).map((strategy, index) => (
|
<div className="space-y-3">
|
||||||
<div key={index} className="flex items-center justify-between">
|
{topStrategiesByRoi?.topStrategiesByRoi?.slice(0, 3).map((strategy, index) => (
|
||||||
<div className="flex items-center gap-3">
|
<div key={index} className="flex items-center justify-between">
|
||||||
<div className="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center">
|
<div className="flex items-center gap-3">
|
||||||
<span className="text-xs font-bold text-white">
|
<div className="avatar placeholder">
|
||||||
{strategy.strategyName?.charAt(0) || 'S'}
|
<div className="bg-success text-success-content rounded-full w-8">
|
||||||
</span>
|
<span className="text-xs font-bold">
|
||||||
</div>
|
{strategy.strategyName?.charAt(0) || 'S'}
|
||||||
<div>
|
</span>
|
||||||
<div className="text-sm text-white font-medium">
|
</div>
|
||||||
{strategy.strategyName || '[Strategy Name]'}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-400">
|
<div>
|
||||||
Vol: {formatCurrency(strategy.volume || 0)}
|
<div className="text-sm text-base-content font-medium">
|
||||||
|
{strategy.strategyName || '[Strategy Name]'}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-base-content/60">
|
||||||
|
Vol: {formatCurrency(strategy.volume || 0)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-right">
|
||||||
|
<div
|
||||||
|
className={`text-sm font-bold ${(strategy.roi || 0) >= 0 ? 'text-success' : 'text-error'}`}>
|
||||||
|
{(strategy.roi || 0) >= 0 ? '+' : ''}{strategy.roi?.toFixed(2) || 0}%
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`text-xs ${(strategy.netPnL || 0) >= 0 ? 'text-success' : 'text-error'}`}>
|
||||||
|
{(strategy.netPnL || 0) >= 0 ? '+' : ''}{formatCurrency(strategy.netPnL || 0)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
)) || (
|
||||||
<div
|
<div className="text-base-content/60 text-sm">No ROI data available</div>
|
||||||
className={`text-sm font-bold ${(strategy.roi || 0) >= 0 ? 'text-green-500' : 'text-red-500'}`}>
|
)}
|
||||||
{(strategy.roi || 0) >= 0 ? '+' : ''}{strategy.roi?.toFixed(2) || 0}%
|
</div>
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`text-xs ${(strategy.netPnL || 0) >= 0 ? 'text-green-400' : 'text-red-400'}`}>
|
|
||||||
{(strategy.netPnL || 0) >= 0 ? '+' : ''}{formatCurrency(strategy.netPnL || 0)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)) || (
|
|
||||||
<div className="text-gray-500 text-sm">No ROI data available</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Top 3 Agents by PnL */}
|
{/* Top 3 Agents by PnL */}
|
||||||
<div className="bg-base-200 rounded-lg p-6">
|
<div className="card bg-base-200">
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="card-body">
|
||||||
<span className="text-2xl">👥</span>
|
<div className="flex items-center gap-2 mb-4">
|
||||||
<h3 className="text-lg font-semibold text-gray-400">Top 3 Agents by PnL</h3>
|
<span className="text-2xl">👥</span>
|
||||||
</div>
|
<h3 className="card-title text-base-content/70">Top 3 Agents by PnL</h3>
|
||||||
<div className="space-y-3">
|
</div>
|
||||||
{topAgentsByPnL?.slice(0, 3).map((agent, index) => (
|
<div className="space-y-3">
|
||||||
<div key={index} className="flex items-center justify-between">
|
{topAgentsByPnL?.slice(0, 3).map((agent, index) => (
|
||||||
<div className="flex items-center gap-3">
|
<div key={index} className="flex items-center justify-between">
|
||||||
<div className="w-8 h-8 bg-purple-500 rounded-full flex items-center justify-center">
|
<div className="flex items-center gap-3">
|
||||||
<span className="text-xs font-bold text-white">
|
<div className="avatar placeholder">
|
||||||
{agent.agentName?.charAt(0) || 'A'}
|
<div className="bg-secondary text-secondary-content rounded-full w-8">
|
||||||
</span>
|
<span className="text-xs font-bold">
|
||||||
</div>
|
{agent.agentName?.charAt(0) || 'A'}
|
||||||
<div>
|
</span>
|
||||||
<div className="text-sm text-white font-medium">
|
</div>
|
||||||
{agent.agentName || '[Agent Name]'}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-400">
|
<div>
|
||||||
{agent.activeStrategiesCount || 0} strategies
|
<div className="text-sm text-base-content font-medium">
|
||||||
|
{agent.agentName || '[Agent Name]'}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-base-content/60">
|
||||||
|
{agent.activeStrategiesCount || 0} strategies
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-right">
|
||||||
|
<div
|
||||||
|
className={`text-sm font-bold ${(agent.netPnL || 0) >= 0 ? 'text-success' : 'text-error'}`}>
|
||||||
|
{(agent.netPnL || 0) >= 0 ? '+' : ''}{formatCurrency(agent.netPnL || 0)}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-base-content/60">
|
||||||
|
{(agent.totalROI || 0).toFixed(2)}% ROI
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
)) || (
|
||||||
<div
|
<div className="text-base-content/60 text-sm">No agent data available</div>
|
||||||
className={`text-sm font-bold ${(agent.netPnL || 0) >= 0 ? 'text-green-500' : 'text-red-500'}`}>
|
)}
|
||||||
{(agent.netPnL || 0) >= 0 ? '+' : ''}{formatCurrency(agent.netPnL || 0)}
|
</div>
|
||||||
</div>
|
|
||||||
<div className="text-xs text-gray-400">
|
|
||||||
{(agent.totalROI || 0).toFixed(2)}% ROI
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)) || (
|
|
||||||
<div className="text-gray-500 text-sm">No agent data available</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Platform Summary Stats */}
|
{/* Platform Summary Stats */}
|
||||||
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-7 gap-6 mb-8">
|
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-8 gap-6 mb-8">
|
||||||
<div className="bg-base-200 rounded-lg p-6">
|
<div className="card bg-base-200">
|
||||||
<h3 className="text-lg font-semibold text-gray-400 mb-2">Total Agents</h3>
|
<div className="card-body">
|
||||||
<div className="text-3xl font-bold text-white mb-1">
|
<h3 className="card-title text-base-content/70 text-base">Total Agents</h3>
|
||||||
{formatNumber(platformData?.totalAgents || 0)}
|
<div className="text-3xl font-bold text-base-content mb-1">
|
||||||
</div>
|
{formatNumber(platformData?.totalAgents || 0)}
|
||||||
<div
|
</div>
|
||||||
className={`text-sm ${changesToday.agentsChange >= 0 ? 'text-green-500' : 'text-red-500'}`}>
|
<div
|
||||||
{formatChangeIndicator(changesToday.agentsChange)}
|
className={`text-sm ${changesToday.agentsChange >= 0 ? 'text-success' : 'text-error'}`}>
|
||||||
|
{formatChangeIndicator(changesToday.agentsChange)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-base-200 rounded-lg p-6">
|
<div className="card bg-base-200">
|
||||||
<h3 className="text-lg font-semibold text-gray-400 mb-2">Active Strategies</h3>
|
<div className="card-body">
|
||||||
<div className="text-3xl font-bold text-white mb-1">
|
<h3 className="card-title text-base-content/70 text-base">Strategies Deployed</h3>
|
||||||
{formatNumber(platformData?.totalActiveStrategies || 0)}
|
<div className="text-3xl font-bold text-base-content mb-1">
|
||||||
</div>
|
{formatNumber(platformData?.totalActiveStrategies || 0)}
|
||||||
<div
|
</div>
|
||||||
className={`text-sm ${changesToday.strategiesChange >= 0 ? 'text-green-500' : 'text-red-500'}`}>
|
<div
|
||||||
{formatChangeIndicator(changesToday.strategiesChange)}
|
className={`text-sm ${changesToday.strategiesChange >= 0 ? 'text-success' : 'text-error'}`}>
|
||||||
|
{formatChangeIndicator(changesToday.strategiesChange)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-base-200 rounded-lg p-6">
|
<div className="card bg-base-200">
|
||||||
<h3 className="text-lg font-semibold text-gray-400 mb-2">Total Platform PnL</h3>
|
<div className="card-body">
|
||||||
<div
|
<h3 className="card-title text-base-content/70 text-base">Active Strategies</h3>
|
||||||
className={`text-3xl font-bold ${(platformData?.totalPlatformPnL || 0) >= 0 ? 'text-green-500' : 'text-red-500'}`}>
|
<div className="text-3xl font-bold text-base-content mb-1">
|
||||||
{(platformData?.totalPlatformPnL || 0) >= 0 ? '+' : ''}{formatCurrency(platformData?.totalPlatformPnL || 0)}
|
{formatNumber(platformData?.totalActiveStrategies || 0)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-400 mb-1">
|
<div
|
||||||
Fees: {formatCurrency(platformData?.totalPlatformFees || 0)}
|
className={`text-sm ${changesToday.strategiesChange >= 0 ? 'text-success' : 'text-error'}`}>
|
||||||
</div>
|
{formatChangeIndicator(changesToday.strategiesChange)}
|
||||||
<div
|
</div>
|
||||||
className={`text-sm ${changesToday.pnLChange >= 0 ? 'text-green-500' : 'text-red-500'}`}>
|
|
||||||
{changesToday.pnLChange >= 0 ? '+' : ''}{formatCurrency(changesToday.pnLChange)} Today
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-base-200 rounded-lg p-6">
|
<div className="card bg-base-200">
|
||||||
<h3 className="text-lg font-semibold text-gray-400 mb-2">Open Interest</h3>
|
<div className="card-body">
|
||||||
<div className="text-3xl font-bold text-white mb-1">
|
<h3 className="card-title text-base-content/70 text-base">Total Platform PnL</h3>
|
||||||
{formatCurrency(platformData?.openInterest || 0)}
|
<div
|
||||||
</div>
|
className={`text-3xl font-bold ${(platformData?.totalPlatformPnL || 0) >= 0 ? 'text-success' : 'text-error'}`}>
|
||||||
<div
|
{(platformData?.totalPlatformPnL || 0) >= 0 ? '+' : ''}{formatCurrency(platformData?.totalPlatformPnL || 0)}
|
||||||
className={`text-sm ${changesToday.openInterestChange >= 0 ? 'text-green-500' : 'text-red-500'}`}>
|
</div>
|
||||||
{changesToday.openInterestChange >= 0 ? '+' : ''}{formatCurrency(changesToday.openInterestChange)} Today
|
<div className="text-sm text-base-content/60 mb-1">
|
||||||
|
Fees: {formatCurrency(platformData?.totalPlatformFees || 0)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`text-sm ${changesToday.pnLChange >= 0 ? 'text-success' : 'text-error'}`}>
|
||||||
|
{changesToday.pnLChange >= 0 ? '+' : ''}{formatCurrency(changesToday.pnLChange)} Today
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-base-200 rounded-lg p-6">
|
<div className="card bg-base-200">
|
||||||
<h3 className="text-lg font-semibold text-gray-400 mb-2">Total Positions</h3>
|
<div className="card-body">
|
||||||
<div className="text-3xl font-bold text-white mb-1">
|
<h3 className="card-title text-base-content/70 text-base">Open Interest</h3>
|
||||||
{formatNumber(platformData?.totalPositionCount || 0)}
|
<div className="text-3xl font-bold text-base-content mb-1">
|
||||||
</div>
|
{formatCurrency(platformData?.openInterest || 0)}
|
||||||
<div
|
</div>
|
||||||
className={`text-sm ${changesToday.positionCountChange >= 0 ? 'text-green-500' : 'text-red-500'}`}>
|
<div
|
||||||
{formatChangeIndicator(changesToday.positionCountChange)}
|
className={`text-sm ${changesToday.openInterestChange >= 0 ? 'text-success' : 'text-error'}`}>
|
||||||
|
{changesToday.openInterestChange >= 0 ? '+' : ''}{formatCurrency(changesToday.openInterestChange)} Today
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-base-200 rounded-lg p-6">
|
<div className="card bg-base-200">
|
||||||
<h3 className="text-lg font-semibold text-gray-400 mb-2">Long Positions</h3>
|
<div className="card-body">
|
||||||
<div className="text-3xl font-bold text-green-400 mb-1">
|
<h3 className="card-title text-base-content/70 text-base">Total Positions</h3>
|
||||||
{formatNumber(platformData?.positionCountByDirection?.Long || 0)}
|
<div className="text-3xl font-bold text-base-content mb-1">
|
||||||
</div>
|
{formatNumber(platformData?.totalPositionCount || 0)}
|
||||||
<div className="text-sm text-gray-400">
|
</div>
|
||||||
{(() => {
|
<div
|
||||||
const longCount = platformData?.positionCountByDirection?.Long || 0;
|
className={`text-sm ${changesToday.positionCountChange >= 0 ? 'text-success' : 'text-error'}`}>
|
||||||
const shortCount = platformData?.positionCountByDirection?.Short || 0;
|
{formatChangeIndicator(changesToday.positionCountChange)}
|
||||||
const total = longCount + shortCount;
|
</div>
|
||||||
if (total === 0) return '0%';
|
|
||||||
return ((longCount / total) * 100).toFixed(1) + '%';
|
|
||||||
})()} of total positions
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-base-200 rounded-lg p-6">
|
<div className="card bg-base-200">
|
||||||
<h3 className="text-lg font-semibold text-gray-400 mb-2">Short Positions</h3>
|
<div className="card-body">
|
||||||
<div className="text-3xl font-bold text-red-400 mb-1">
|
<h3 className="card-title text-base-content/70 text-base">Long Positions</h3>
|
||||||
{formatNumber(platformData?.positionCountByDirection?.Short || 0)}
|
<div className="text-3xl font-bold text-success mb-1">
|
||||||
|
{formatNumber(platformData?.positionCountByDirection?.Long || 0)}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-base-content/60">
|
||||||
|
{(() => {
|
||||||
|
const longCount = platformData?.positionCountByDirection?.Long || 0;
|
||||||
|
const shortCount = platformData?.positionCountByDirection?.Short || 0;
|
||||||
|
const total = longCount + shortCount;
|
||||||
|
if (total === 0) return '0%';
|
||||||
|
return ((longCount / total) * 100).toFixed(1) + '%';
|
||||||
|
})()} of total positions
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm text-gray-400">
|
</div>
|
||||||
{(() => {
|
|
||||||
const longCount = platformData?.positionCountByDirection?.Long || 0;
|
<div className="card bg-base-200">
|
||||||
const shortCount = platformData?.positionCountByDirection?.Short || 0;
|
<div className="card-body">
|
||||||
const total = longCount + shortCount;
|
<h3 className="card-title text-base-content/70 text-base">Short Positions</h3>
|
||||||
if (total === 0) return '0%';
|
<div className="text-3xl font-bold text-error mb-1">
|
||||||
return ((shortCount / total) * 100).toFixed(1) + '%';
|
{formatNumber(platformData?.positionCountByDirection?.Short || 0)}
|
||||||
})()} of total positions
|
</div>
|
||||||
|
<div className="text-sm text-base-content/60">
|
||||||
|
{(() => {
|
||||||
|
const longCount = platformData?.positionCountByDirection?.Long || 0;
|
||||||
|
const shortCount = platformData?.positionCountByDirection?.Short || 0;
|
||||||
|
const total = longCount + shortCount;
|
||||||
|
if (total === 0) return '0%';
|
||||||
|
return ((shortCount / total) * 100).toFixed(1) + '%';
|
||||||
|
})()} of total positions
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -417,131 +440,152 @@ function PlatformSummary({index}: { index: number }) {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Platform Metrics Chart */}
|
{/* Platform Metrics Charts */}
|
||||||
<div className="mb-8">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
||||||
|
{/* Trading Metrics Chart */}
|
||||||
<PlatformLineChart
|
<PlatformLineChart
|
||||||
dailySnapshots={platformData?.dailySnapshots || []}
|
dailySnapshots={platformData?.dailySnapshots || []}
|
||||||
currentData={{
|
currentData={{
|
||||||
totalAgents: platformData?.totalAgents,
|
|
||||||
totalActiveStrategies: platformData?.totalActiveStrategies,
|
|
||||||
totalPlatformVolume: platformData?.totalPlatformVolume,
|
totalPlatformVolume: platformData?.totalPlatformVolume,
|
||||||
totalPlatformPnL: platformData?.totalPlatformPnL,
|
totalPlatformPnL: platformData?.totalPlatformPnL,
|
||||||
openInterest: platformData?.openInterest,
|
openInterest: platformData?.openInterest,
|
||||||
totalPositionCount: platformData?.totalPositionCount,
|
totalPositionCount: platformData?.totalPositionCount,
|
||||||
lastUpdated: platformData?.lastUpdated
|
lastUpdated: platformData?.lastUpdated
|
||||||
}}
|
}}
|
||||||
title="Platform Metrics Over Time"
|
title="Trading Metrics"
|
||||||
height={400}
|
height={400}
|
||||||
|
metrics={['volume', 'pnl', 'openInterest', 'positionCount']}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* User-Centric Metrics Chart */}
|
||||||
|
<PlatformLineChart
|
||||||
|
dailySnapshots={platformData?.dailySnapshots || []}
|
||||||
|
currentData={{
|
||||||
|
totalAgents: platformData?.totalAgents,
|
||||||
|
totalActiveStrategies: platformData?.totalActiveStrategies,
|
||||||
|
lastUpdated: platformData?.lastUpdated
|
||||||
|
}}
|
||||||
|
title="User-Centric Metrics"
|
||||||
|
height={400}
|
||||||
|
metrics={['agents', 'strategies']}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Volume and Positions by Asset */}
|
{/* Volume and Positions by Asset */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
||||||
{/* Volume by Asset */}
|
{/* Volume by Asset */}
|
||||||
<div className="bg-base-200 rounded-lg p-6">
|
<div className="card bg-base-200">
|
||||||
<h3 className="text-lg font-semibold text-gray-400 mb-4">Volume by Asset</h3>
|
<div className="card-body">
|
||||||
<div className="space-y-3 max-h-80 overflow-y-auto">
|
<h3 className="card-title text-base-content/70">Volume by Asset</h3>
|
||||||
{platformData?.volumeByAsset && Object.keys(platformData.volumeByAsset).length > 0 ? (
|
<div className="space-y-3 max-h-80 overflow-y-auto">
|
||||||
Object.entries(platformData.volumeByAsset)
|
{platformData?.volumeByAsset && Object.keys(platformData.volumeByAsset).length > 0 ? (
|
||||||
.sort(([, a], [, b]) => b - a)
|
Object.entries(platformData.volumeByAsset)
|
||||||
.slice(0, 10)
|
.sort(([, a], [, b]) => b - a)
|
||||||
.map(([asset, volume]) => (
|
.slice(0, 10)
|
||||||
<div key={asset} className="flex items-center justify-between">
|
.map(([asset, volume]) => (
|
||||||
<div className="flex items-center gap-2">
|
<div key={asset} className="flex items-center justify-between">
|
||||||
<div
|
<div className="flex items-center gap-2">
|
||||||
className="w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center">
|
<div className="avatar placeholder">
|
||||||
<span className="text-xs font-bold text-white">
|
<div className="bg-primary text-primary-content rounded-full w-8">
|
||||||
{asset.substring(0, 2)}
|
<span className="text-xs font-bold">
|
||||||
</span>
|
{asset.substring(0, 2)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className="text-base-content font-medium">{asset}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-white font-medium">{asset}</span>
|
<div className="text-right">
|
||||||
</div>
|
<div className="text-base-content font-semibold">
|
||||||
<div className="text-right">
|
{formatCurrency(volume)}
|
||||||
<div className="text-white font-semibold">
|
</div>
|
||||||
{formatCurrency(volume)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))
|
||||||
))
|
) : (
|
||||||
) : (
|
<div className="text-base-content/60 text-sm">No volume data available</div>
|
||||||
<div className="text-gray-500 text-sm">No volume data available</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Positions by Asset */}
|
{/* Positions by Asset */}
|
||||||
<div className="bg-base-200 rounded-lg p-6">
|
<div className="card bg-base-200">
|
||||||
<h3 className="text-lg font-semibold text-gray-400 mb-4">Positions by Asset</h3>
|
<div className="card-body">
|
||||||
<div className="space-y-3 max-h-80 overflow-y-auto">
|
<h3 className="card-title text-base-content/70">Positions by Asset</h3>
|
||||||
{platformData?.positionCountByAsset && Object.keys(platformData.positionCountByAsset).length > 0 ? (
|
<div className="space-y-3 max-h-80 overflow-y-auto">
|
||||||
Object.entries(platformData.positionCountByAsset)
|
{platformData?.positionCountByAsset && Object.keys(platformData.positionCountByAsset).length > 0 ? (
|
||||||
.sort(([, a], [, b]) => b - a)
|
Object.entries(platformData.positionCountByAsset)
|
||||||
.slice(0, 10)
|
.sort(([, a], [, b]) => b - a)
|
||||||
.map(([asset, count]) => (
|
.slice(0, 10)
|
||||||
<div key={asset} className="flex items-center justify-between">
|
.map(([asset, count]) => (
|
||||||
<div className="flex items-center gap-2">
|
<div key={asset} className="flex items-center justify-between">
|
||||||
<div
|
<div className="flex items-center gap-2">
|
||||||
className="w-8 h-8 bg-purple-500 rounded-full flex items-center justify-center">
|
<div className="avatar placeholder">
|
||||||
<span className="text-xs font-bold text-white">
|
<div className="bg-secondary text-secondary-content rounded-full w-8">
|
||||||
{asset.substring(0, 2)}
|
<span className="text-xs font-bold">
|
||||||
</span>
|
{asset.substring(0, 2)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className="text-base-content font-medium">{asset}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-white font-medium">{asset}</span>
|
<div className="text-right">
|
||||||
</div>
|
<div className="text-base-content font-semibold">
|
||||||
<div className="text-right">
|
{formatNumber(count)} positions
|
||||||
<div className="text-white font-semibold">
|
</div>
|
||||||
{formatNumber(count)} positions
|
<div className="text-xs text-base-content/60">
|
||||||
</div>
|
{platformData?.totalPositionCount ?
|
||||||
<div className="text-xs text-gray-400">
|
(count / platformData.totalPositionCount * 100).toFixed(1) : 0}% of
|
||||||
{platformData?.totalPositionCount ?
|
total
|
||||||
(count / platformData.totalPositionCount * 100).toFixed(1) : 0}% of
|
</div>
|
||||||
total
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))
|
||||||
))
|
) : (
|
||||||
) : (
|
<div className="text-base-content/60 text-sm">No position data available</div>
|
||||||
<div className="text-gray-500 text-sm">No position data available</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Data Freshness Indicator */}
|
{/* Data Freshness Indicator */}
|
||||||
<div className="bg-base-200 rounded-lg p-4">
|
<div className="card bg-base-200">
|
||||||
<div className="flex items-center justify-between text-sm text-gray-400">
|
<div className="card-body">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center justify-between text-sm text-base-content/60">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-4">
|
||||||
<span>Last updated: {platformData?.lastUpdated ? new Date(platformData.lastUpdated).toLocaleString() : 'Unknown'}</span>
|
<div className="flex items-center gap-2">
|
||||||
{isFetching && (
|
<span>Last updated: {platformData?.lastUpdated ? new Date(platformData.lastUpdated).toLocaleString() : 'Unknown'}</span>
|
||||||
<div className="flex items-center gap-1 text-blue-400">
|
{isFetching && (
|
||||||
<div className="loading loading-spinner loading-xs"></div>
|
<div className="flex items-center gap-1 text-primary">
|
||||||
<span>Refreshing...</span>
|
<div className="loading loading-spinner loading-xs"></div>
|
||||||
</div>
|
<span>Refreshing...</span>
|
||||||
)}
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleForceRefresh}
|
||||||
|
disabled={isFetching}
|
||||||
|
className="btn btn-xs btn-outline btn-primary disabled:loading"
|
||||||
|
title="Force refresh data"
|
||||||
|
>
|
||||||
|
{isFetching ? (
|
||||||
|
<>
|
||||||
|
<span className="loading loading-spinner loading-xs"></span>
|
||||||
|
Refreshing...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-3 h-3">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
|
||||||
|
</svg>
|
||||||
|
Refresh
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<span>Last snapshot: {platformData?.lastSnapshot ? new Date(platformData.lastSnapshot).toLocaleString() : 'Unknown'}</span>
|
||||||
onClick={handleForceRefresh}
|
|
||||||
disabled={isFetching}
|
|
||||||
className="btn btn-xs btn-outline btn-primary disabled:loading"
|
|
||||||
title="Force refresh data"
|
|
||||||
>
|
|
||||||
{isFetching ? (
|
|
||||||
<>
|
|
||||||
<span className="loading loading-spinner loading-xs"></span>
|
|
||||||
Refreshing...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-3 h-3">
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
|
|
||||||
</svg>
|
|
||||||
Refresh
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<span>Last snapshot: {platformData?.lastSnapshot ? new Date(platformData.lastSnapshot).toLocaleString() : 'Unknown'}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user