trading-platform-frontend-v2/src/modules/backtesting/components/PerformanceMetricsPanel.tsx
rckrdmrd 5b53c2539a feat: Initial commit - Trading Platform Frontend
React frontend with:
- Authentication UI
- Trading dashboard
- ML signals display
- Portfolio management

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 04:30:39 -06:00

340 lines
9.6 KiB
TypeScript

/**
* Performance Metrics Panel
* Displays comprehensive backtesting performance metrics
*/
import React from 'react';
import {
ChartBarIcon,
ArrowTrendingUpIcon,
ArrowTrendingDownIcon,
ScaleIcon,
ClockIcon,
FireIcon,
ShieldCheckIcon,
} from '@heroicons/react/24/solid';
import type { BacktestMetrics } from '../../../services/backtestService';
import { formatMetric, getMetricColor } from '../../../services/backtestService';
interface PerformanceMetricsPanelProps {
metrics: BacktestMetrics;
initialCapital: number;
finalCapital: number;
className?: string;
}
export const PerformanceMetricsPanel: React.FC<PerformanceMetricsPanelProps> = ({
metrics,
initialCapital,
finalCapital,
className = '',
}) => {
const returnPercent = ((finalCapital - initialCapital) / initialCapital) * 100;
return (
<div className={`bg-gray-900 border border-gray-800 rounded-xl p-5 ${className}`}>
<h3 className="text-lg font-bold text-white mb-4 flex items-center gap-2">
<ChartBarIcon className="w-5 h-5 text-purple-400" />
Performance Metrics
</h3>
{/* Capital Summary */}
<div className="grid grid-cols-3 gap-3 mb-6 p-4 bg-gray-800/50 rounded-lg">
<div className="text-center">
<p className="text-xs text-gray-400">Initial Capital</p>
<p className="text-lg font-bold text-white">{formatMetric(initialCapital, 'currency')}</p>
</div>
<div className="text-center">
<p className="text-xs text-gray-400">Final Capital</p>
<p className={`text-lg font-bold ${getMetricColor(finalCapital - initialCapital, 'pnl')}`}>
{formatMetric(finalCapital, 'currency')}
</p>
</div>
<div className="text-center">
<p className="text-xs text-gray-400">Return</p>
<p className={`text-lg font-bold ${getMetricColor(returnPercent, 'pnl')}`}>
{formatMetric(returnPercent, 'percent')}
</p>
</div>
</div>
{/* Trade Statistics */}
<div className="mb-6">
<h4 className="text-sm font-semibold text-gray-400 mb-3 flex items-center gap-2">
<ScaleIcon className="w-4 h-4" />
Trade Statistics
</h4>
<div className="grid grid-cols-2 gap-3">
<MetricCard
label="Total Trades"
value={metrics.total_trades}
format="number"
/>
<MetricCard
label="Win Rate"
value={metrics.win_rate}
format="percent"
colorType="winrate"
/>
<MetricCard
label="Winning Trades"
value={metrics.winning_trades}
format="number"
icon={<ArrowTrendingUpIcon className="w-4 h-4 text-green-400" />}
/>
<MetricCard
label="Losing Trades"
value={metrics.losing_trades}
format="number"
icon={<ArrowTrendingDownIcon className="w-4 h-4 text-red-400" />}
/>
</div>
</div>
{/* Profit/Loss */}
<div className="mb-6">
<h4 className="text-sm font-semibold text-gray-400 mb-3 flex items-center gap-2">
<FireIcon className="w-4 h-4" />
Profit & Loss
</h4>
<div className="grid grid-cols-2 gap-3">
<MetricCard
label="Net Profit"
value={metrics.net_profit}
format="currency"
colorType="pnl"
/>
<MetricCard
label="Profit Factor"
value={metrics.profit_factor}
format="ratio"
colorType="ratio"
/>
<MetricCard
label="Gross Profit"
value={metrics.gross_profit}
format="currency"
positive
/>
<MetricCard
label="Gross Loss"
value={metrics.gross_loss}
format="currency"
negative
/>
<MetricCard
label="Avg Win"
value={metrics.avg_win}
format="currency"
positive
/>
<MetricCard
label="Avg Loss"
value={metrics.avg_loss}
format="currency"
negative
/>
<MetricCard
label="Largest Win"
value={metrics.largest_win}
format="currency"
positive
/>
<MetricCard
label="Largest Loss"
value={metrics.largest_loss}
format="currency"
negative
/>
</div>
</div>
{/* Risk Metrics */}
<div className="mb-6">
<h4 className="text-sm font-semibold text-gray-400 mb-3 flex items-center gap-2">
<ShieldCheckIcon className="w-4 h-4" />
Risk Metrics
</h4>
<div className="grid grid-cols-2 gap-3">
<MetricCard
label="Max Drawdown"
value={metrics.max_drawdown_percent}
format="percent"
colorType="drawdown"
invertColor
/>
<MetricCard
label="Max DD ($)"
value={metrics.max_drawdown}
format="currency"
negative
/>
<MetricCard
label="Sharpe Ratio"
value={metrics.sharpe_ratio}
format="ratio"
colorType="ratio"
/>
<MetricCard
label="Sortino Ratio"
value={metrics.sortino_ratio}
format="ratio"
colorType="ratio"
/>
<MetricCard
label="Calmar Ratio"
value={metrics.calmar_ratio}
format="ratio"
colorType="ratio"
/>
<MetricCard
label="Avg Trade"
value={metrics.avg_trade}
format="currency"
colorType="pnl"
/>
</div>
</div>
{/* Streaks */}
<div className="mb-6">
<h4 className="text-sm font-semibold text-gray-400 mb-3 flex items-center gap-2">
<ClockIcon className="w-4 h-4" />
Streaks & Timing
</h4>
<div className="grid grid-cols-2 gap-3">
<MetricCard
label="Max Consecutive Wins"
value={metrics.max_consecutive_wins}
format="number"
positive
/>
<MetricCard
label="Max Consecutive Losses"
value={metrics.max_consecutive_losses}
format="number"
negative
/>
<MetricCard
label="Avg Holding Time"
value={`${Math.round(metrics.avg_holding_time_minutes)} min`}
format="custom"
/>
<MetricCard
label="Trading Days"
value={metrics.trading_days}
format="number"
/>
</div>
</div>
{/* Performance Grade */}
<div className="p-4 bg-gray-800/50 rounded-lg">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-400">Overall Grade</span>
<PerformanceGrade metrics={metrics} />
</div>
</div>
</div>
);
};
// Helper Components
interface MetricCardProps {
label: string;
value: number | string;
format: 'percent' | 'currency' | 'ratio' | 'number' | 'custom';
colorType?: 'pnl' | 'winrate' | 'drawdown' | 'ratio';
positive?: boolean;
negative?: boolean;
invertColor?: boolean;
icon?: React.ReactNode;
}
const MetricCard: React.FC<MetricCardProps> = ({
label,
value,
format,
colorType,
positive,
negative,
invertColor,
icon,
}) => {
let displayValue: string;
let colorClass = 'text-white';
if (format === 'custom') {
displayValue = String(value);
} else {
displayValue = formatMetric(typeof value === 'number' ? value : parseFloat(String(value)), format);
}
if (colorType && typeof value === 'number') {
colorClass = getMetricColor(invertColor ? -value : value, colorType);
} else if (positive) {
colorClass = 'text-green-400';
} else if (negative) {
colorClass = 'text-red-400';
}
return (
<div className="p-3 bg-gray-800 rounded-lg">
<div className="flex items-center justify-between mb-1">
<span className="text-xs text-gray-400">{label}</span>
{icon}
</div>
<p className={`text-sm font-bold ${colorClass}`}>{displayValue}</p>
</div>
);
};
const PerformanceGrade: React.FC<{ metrics: BacktestMetrics }> = ({ metrics }) => {
// Calculate grade based on multiple factors
let score = 0;
// Win rate (max 25 points)
score += Math.min(25, (metrics.win_rate / 100) * 40);
// Profit factor (max 25 points)
score += Math.min(25, metrics.profit_factor * 10);
// Sharpe ratio (max 25 points)
score += Math.min(25, metrics.sharpe_ratio * 12.5);
// Max drawdown penalty (max 25 points, lower is better)
score += Math.max(0, 25 - metrics.max_drawdown_percent);
let grade: string;
let gradeColor: string;
if (score >= 80) {
grade = 'A';
gradeColor = 'bg-green-600';
} else if (score >= 65) {
grade = 'B';
gradeColor = 'bg-blue-600';
} else if (score >= 50) {
grade = 'C';
gradeColor = 'bg-yellow-600';
} else if (score >= 35) {
grade = 'D';
gradeColor = 'bg-orange-600';
} else {
grade = 'F';
gradeColor = 'bg-red-600';
}
return (
<div className="flex items-center gap-2">
<div className={`w-10 h-10 rounded-full ${gradeColor} flex items-center justify-center`}>
<span className="text-white font-bold text-lg">{grade}</span>
</div>
<span className="text-xs text-gray-400">Score: {Math.round(score)}/100</span>
</div>
);
};
export default PerformanceMetricsPanel;