React frontend with: - Authentication UI - Trading dashboard - ML signals display - Portfolio management Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
340 lines
9.6 KiB
TypeScript
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;
|