Skip to content

Commit 297cfd7

Browse files
style: page, webui updated
1 parent 4f2d444 commit 297cfd7

File tree

13 files changed

+660
-0
lines changed

13 files changed

+660
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "metrics-dashboard",
3+
"private": true,
4+
"version": "1.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "tsc && vite build",
9+
"preview": "vite preview",
10+
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
11+
},
12+
"dependencies": {
13+
"react": "^18.2.0",
14+
"react-dom": "^18.2.0",
15+
"recharts": "^2.8.0",
16+
"lucide-react": "^0.294.0"
17+
},
18+
"devDependencies": {
19+
"@types/react": "^18.2.37",
20+
"@types/react-dom": "^18.2.15",
21+
"@typescript-eslint/eslint-plugin": "^6.10.0",
22+
"@typescript-eslint/parser": "^6.10.0",
23+
"@vitejs/plugin-react": "^4.1.1",
24+
"autoprefixer": "^10.4.16",
25+
"eslint": "^8.53.0",
26+
"eslint-plugin-react-hooks": "^4.6.0",
27+
"eslint-plugin-react-refresh": "^0.4.4",
28+
"postcss": "^8.4.31",
29+
"tailwindcss": "^3.3.5",
30+
"typescript": "^5.2.2",
31+
"vite": "^4.5.0"
32+
}
33+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default {
2+
plugins: {
3+
tailwindcss: {},
4+
autoprefixer: {},
5+
},
6+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//import React from 'react';
2+
import { useBacktestData } from './hooks/useBacktestData';
3+
import MetricCard from './components/MetricCard';
4+
import PerformanceChart from './components/PerformanceChart';
5+
import DrawdownChart from './components/DrawdownChart';
6+
//import { RefreshCw, AlertCircle, Database } from 'lucide-react';
7+
8+
function App() {
9+
const { data, loading, error, refreshData } = useBacktestData();
10+
11+
if (error) {
12+
return (
13+
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
14+
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-8 max-w-md w-full text-center">
15+
<AlertCircle className="w-12 h-12 text-red-500 mx-auto mb-4" />
16+
<h2 className="text-xl font-semibold text-gray-800 mb-2">Error Loading Data</h2>
17+
<p className="text-gray-600 mb-4">{error}</p>
18+
<button
19+
onClick={refreshData}
20+
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors"
21+
>
22+
Try Again
23+
</button>
24+
</div>
25+
</div>
26+
);
27+
}
28+
29+
return (
30+
<div className="min-h-screen bg-gray-50">
31+
{/* Header */}
32+
<header className="bg-white shadow-sm border-b border-gray-200">
33+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
34+
<div className="flex justify-between items-center h-16">
35+
<div className="flex items-center gap-3">
36+
<Database className="w-8 h-8 text-blue-600" />
37+
<h1 className="text-2xl font-bold text-gray-900">Metrics Dashboard</h1>
38+
</div>
39+
<button
40+
onClick={refreshData}
41+
disabled={loading}
42+
className="flex items-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
43+
>
44+
<RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />
45+
{loading ? 'Refreshing...' : 'Refresh Data'}
46+
</button>
47+
</div>
48+
</div>
49+
</header>
50+
51+
{/* Main Content */}
52+
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
53+
{/* Metrics Grid */}
54+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 mb-8">
55+
<MetricCard
56+
title="Total Return"
57+
value={data?.metrics.totalReturn || 0}
58+
format="percentage"
59+
description="Cumulative return over the period"
60+
/>
61+
<MetricCard
62+
title="Sharpe Ratio"
63+
value={data?.metrics.sharpeRatio || 0}
64+
format="ratio"
65+
description="Risk-adjusted return"
66+
/>
67+
<MetricCard
68+
title="Max Drawdown"
69+
value={data?.metrics.maxDrawdown || 0}
70+
format="percentage"
71+
description="Maximum peak-to-trough decline"
72+
/>
73+
<MetricCard
74+
title="Volatility"
75+
value={data?.metrics.volatility || 0}
76+
format="percentage"
77+
description="Annualized standard deviation of returns"
78+
/>
79+
<MetricCard
80+
title="Win Rate"
81+
value={data?.metrics.winRate || 0}
82+
format="percentage"
83+
description="Percentage of winning trades"
84+
/>
85+
<MetricCard
86+
title="Profit Factor"
87+
value={data?.metrics.profitFactor || 0}
88+
format="ratio"
89+
description="Gross profit divided by gross loss"
90+
/>
91+
</div>
92+
93+
{/* Charts */}
94+
<div className="grid grid-cols-1 xl:grid-cols-2 gap-8">
95+
<PerformanceChart
96+
data={data?.performance || []}
97+
loading={loading}
98+
height={400}
99+
/>
100+
<DrawdownChart
101+
data={data?.drawdown || []}
102+
loading={loading}
103+
height={400}
104+
/>
105+
</div>
106+
107+
{/* Loading State */}
108+
{loading && !data && (
109+
<div className="fixed inset-0 bg-white bg-opacity-80 flex items-center justify-center z-50">
110+
<div className="text-center">
111+
<RefreshCw className="w-12 h-12 text-blue-600 animate-spin mx-auto mb-4" />
112+
<p className="text-lg font-medium text-gray-700">Loading metrics data...</p>
113+
</div>
114+
</div>
115+
)}
116+
</main>
117+
118+
{/* Footer */}
119+
<footer className="bg-white border-t border-gray-200 mt-12">
120+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
121+
<p className="text-center text-gray-500 text-sm">
122+
Metrics Dashboard • Built with React, TypeScript, and Recharts
123+
</p>
124+
</div>
125+
</footer>
126+
</div>
127+
);
128+
}
129+
130+
export default App;
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import React from 'react';
2+
import {
3+
AreaChart,
4+
Area,
5+
XAxis,
6+
YAxis,
7+
CartesianGrid,
8+
Tooltip,
9+
ResponsiveContainer
10+
} from 'recharts';
11+
import { DrawdownDataPoint } from '../types';
12+
import { formatNumber, formatDate } from '../utils/formatters';
13+
14+
interface DrawdownChartProps {
15+
data: DrawdownDataPoint[];
16+
height?: number;
17+
loading?: boolean;
18+
}
19+
20+
const DrawdownChart: React.FC<DrawdownChartProps> = ({
21+
data,
22+
height = 300,
23+
loading = false
24+
}) => {
25+
if (loading) {
26+
return (
27+
<div
28+
className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 animate-pulse"
29+
style={{ height }}
30+
>
31+
<div className="h-full bg-gray-200 rounded"></div>
32+
</div>
33+
);
34+
}
35+
36+
const chartData = data.map(item => ({
37+
...item,
38+
date: formatDate(item.date),
39+
drawdown: item.drawdown * 100 // Convert to percentage
40+
}));
41+
42+
const CustomTooltip = ({ active, payload, label }: any) => {
43+
if (active && payload && payload.length) {
44+
return (
45+
<div className="bg-white p-3 border border-gray-300 rounded-lg shadow-sm">
46+
<p className="text-sm font-medium text-gray-700 mb-1">{label}</p>
47+
<p className="text-sm text-red-600">
48+
Drawdown: {formatNumber(payload[0].value, 'percentage')}
49+
</p>
50+
</div>
51+
);
52+
}
53+
return null;
54+
};
55+
56+
return (
57+
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
58+
<h3 className="text-lg font-semibold text-gray-800 mb-4">Drawdown Chart</h3>
59+
<ResponsiveContainer width="100%" height={height}>
60+
<AreaChart data={chartData} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>
61+
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
62+
<XAxis
63+
dataKey="date"
64+
axisLine={false}
65+
tickLine={false}
66+
tick={{ fontSize: 12 }}
67+
interval="preserveStartEnd"
68+
/>
69+
<YAxis
70+
axisLine={false}
71+
tickLine={false}
72+
tick={{ fontSize: 12 }}
73+
tickFormatter={(value) => formatNumber(value, 'percentage')}
74+
/>
75+
<Tooltip content={<CustomTooltip />} />
76+
<Area
77+
type="monotone"
78+
dataKey="drawdown"
79+
stroke="#ef4444"
80+
fill="#fecaca"
81+
fillOpacity={0.6}
82+
strokeWidth={2}
83+
/>
84+
</AreaChart>
85+
</ResponsiveContainer>
86+
</div>
87+
);
88+
};
89+
90+
export default DrawdownChart;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React from 'react';
2+
import { MetricCardProps } from '../types';
3+
import { formatNumber, formatChange, getColorForValue } from '../utils/formatters';
4+
import { TrendingUp, TrendingDown, HelpCircle } from 'lucide-react';
5+
6+
const MetricCard: React.FC<MetricCardProps> = ({
7+
title,
8+
value,
9+
change,
10+
format = 'number',
11+
description
12+
}) => {
13+
const formattedValue = typeof value === 'number' ? formatNumber(value, format) : value;
14+
const changeColor = change && change >= 0 ? 'text-green-600' : 'text-red-600';
15+
const valueColor = getColorForValue(typeof value === 'number' ? value : 0, title.toLowerCase());
16+
17+
return (
18+
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 hover:shadow-md transition-shadow duration-200">
19+
<div className="flex items-center justify-between mb-2">
20+
<h3 className="text-sm font-medium text-gray-600 flex items-center gap-1">
21+
{title}
22+
{description && (
23+
<HelpCircle className="w-3 h-3 text-gray-400" title={description} />
24+
)}
25+
</h3>
26+
{change !== undefined && (
27+
<div className={`flex items-center text-xs font-medium ${changeColor}`}>
28+
{change >= 0 ? (
29+
<TrendingUp className="w-3 h-3 mr-1" />
30+
) : (
31+
<TrendingDown className="w-3 h-3 mr-1" />
32+
)}
33+
{formatChange(change)}
34+
</div>
35+
)}
36+
</div>
37+
38+
<div className={`text-2xl font-bold ${valueColor}`}>
39+
{formattedValue}
40+
</div>
41+
42+
{description && (
43+
<p className="text-xs text-gray-500 mt-2 hidden sm:block">
44+
{description}
45+
</p>
46+
)}
47+
</div>
48+
);
49+
};
50+
51+
export default MetricCard;

0 commit comments

Comments
 (0)