rerun complete
This commit is contained in:
parent
11c6c19628
commit
d15e542f20
17 changed files with 4694 additions and 146 deletions
|
|
@ -0,0 +1,297 @@
|
|||
import type { BacktestResult } from '../types/backtest.types';
|
||||
|
||||
interface ExtendedMetrics {
|
||||
// Core metrics
|
||||
totalReturn?: number;
|
||||
sharpeRatio?: number;
|
||||
maxDrawdown?: number;
|
||||
winRate?: number;
|
||||
totalProfit?: number;
|
||||
profitFactor?: number;
|
||||
totalTrades?: number;
|
||||
avgWin?: number;
|
||||
avgLoss?: number;
|
||||
expectancy?: number;
|
||||
sortinoRatio?: number;
|
||||
calmarRatio?: number;
|
||||
profitableTrades?: number;
|
||||
|
||||
// Extended metrics from orchestrator
|
||||
annualizedReturn?: number;
|
||||
volatility?: number;
|
||||
avgHoldingPeriod?: number;
|
||||
maxConsecutiveLosses?: number;
|
||||
maxConsecutiveWins?: number;
|
||||
payoffRatio?: number;
|
||||
largestWin?: number;
|
||||
largestLoss?: number;
|
||||
avgWinLoss?: number;
|
||||
skewness?: number;
|
||||
kurtosis?: number;
|
||||
tailRatio?: number;
|
||||
kellyFraction?: number;
|
||||
informationRatio?: number;
|
||||
avgTradesPerDay?: number;
|
||||
}
|
||||
|
||||
interface CompactPerformanceMetricsProps {
|
||||
result: any | null;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export function CompactPerformanceMetrics({ result, isLoading }: CompactPerformanceMetricsProps) {
|
||||
if (isLoading || !result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const metrics = result.metrics as ExtendedMetrics;
|
||||
const analytics = result.analytics || {};
|
||||
|
||||
// Merge metrics from both sources
|
||||
const allMetrics = {
|
||||
...metrics,
|
||||
...analytics,
|
||||
// Override with metrics values if they exist
|
||||
...metrics
|
||||
};
|
||||
|
||||
// Calculate totalProfit if not provided
|
||||
const totalProfit = allMetrics.totalProfit ??
|
||||
(allMetrics.totalReturn && result.config?.initialCapital ?
|
||||
allMetrics.totalReturn * result.config.initialCapital :
|
||||
undefined);
|
||||
|
||||
const formatValue = (value: number | undefined, format: 'percent' | 'number' | 'currency', decimals = 2) => {
|
||||
if (value === undefined || value === null) return '-';
|
||||
|
||||
switch (format) {
|
||||
case 'percent':
|
||||
return `${(value * 100).toFixed(decimals)}%`;
|
||||
case 'currency':
|
||||
const prefix = value < 0 ? '-$' : '$';
|
||||
return `${prefix}${Math.abs(value).toFixed(decimals)}`;
|
||||
case 'number':
|
||||
return value.toFixed(decimals);
|
||||
}
|
||||
};
|
||||
|
||||
const getColorClass = (value: number | undefined, thresholds: { good: number; warning?: number }) => {
|
||||
if (value === undefined || value === null) return 'text-text-secondary';
|
||||
|
||||
if (thresholds.warning !== undefined) {
|
||||
if (value >= thresholds.good) return 'text-success';
|
||||
if (value >= thresholds.warning) return 'text-warning';
|
||||
return 'text-error';
|
||||
}
|
||||
|
||||
return value >= thresholds.good ? 'text-success' : 'text-error';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-surface-secondary rounded-lg border border-border p-4">
|
||||
<h3 className="text-sm font-medium text-text-primary mb-3">Performance Analytics</h3>
|
||||
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
{/* Column 1 - Return Metrics */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs text-text-secondary">Total Return</span>
|
||||
<span className={`text-sm font-medium ${getColorClass(allMetrics.totalReturn, { good: 0 })}`}>
|
||||
{formatValue(allMetrics.totalReturn, 'percent')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs text-text-secondary">Sharpe Ratio</span>
|
||||
<span className={`text-sm font-medium ${getColorClass(allMetrics.sharpeRatio, { good: 1, warning: 0 })}`}>
|
||||
{formatValue(allMetrics.sharpeRatio, 'number')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs text-text-secondary">Max Drawdown</span>
|
||||
<span className={`text-sm font-medium ${getColorClass(allMetrics.maxDrawdown, { good: -0.1, warning: -0.2 })}`}>
|
||||
{formatValue(allMetrics.maxDrawdown, 'percent')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs text-text-secondary">Win Rate</span>
|
||||
<span className={`text-sm font-medium ${getColorClass(allMetrics.winRate, { good: 0.5 })}`}>
|
||||
{formatValue(allMetrics.winRate, 'percent', 1)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs text-text-secondary">Total Profit</span>
|
||||
<span className={`text-sm font-medium ${getColorClass(totalProfit, { good: 0 })}`}>
|
||||
{formatValue(totalProfit, 'currency')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Column 2 - Risk Metrics */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs text-text-secondary">Profit Factor</span>
|
||||
<span className={`text-sm font-medium ${getColorClass(allMetrics.profitFactor, { good: 1.5, warning: 1 })}`}>
|
||||
{formatValue(allMetrics.profitFactor, 'number')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs text-text-secondary">Total Trades</span>
|
||||
<span className="text-sm font-medium text-text-primary">
|
||||
{allMetrics.totalTrades ?? '-'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs text-text-secondary">Avg Win/Loss</span>
|
||||
<span className="text-sm font-medium text-text-primary">
|
||||
<span className="text-success">{formatValue(allMetrics.avgWin, 'currency')}</span>
|
||||
<span className="text-text-secondary mx-1">/</span>
|
||||
<span className="text-error">{formatValue(allMetrics.avgLoss, 'currency')}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs text-text-secondary">Expectancy</span>
|
||||
<span className={`text-sm font-medium ${getColorClass(allMetrics.expectancy, { good: 0 })}`}>
|
||||
{formatValue(allMetrics.expectancy, 'currency')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs text-text-secondary">Sortino Ratio</span>
|
||||
<span className={`text-sm font-medium ${getColorClass(allMetrics.sortinoRatio, { good: 1, warning: 0 })}`}>
|
||||
{formatValue(allMetrics.sortinoRatio, 'number')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Column 3 - Additional Metrics */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs text-text-secondary">Annual Return</span>
|
||||
<span className={`text-sm font-medium ${getColorClass(allMetrics.annualizedReturn, { good: 0.1, warning: 0 })}`}>
|
||||
{formatValue(allMetrics.annualizedReturn, 'percent')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs text-text-secondary">Volatility</span>
|
||||
<span className={`text-sm font-medium ${getColorClass(-(allMetrics.volatility ?? 0), { good: -15, warning: -25 })}`}>
|
||||
{formatValue(allMetrics.volatility ? allMetrics.volatility / 100 : undefined, 'percent', 1)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs text-text-secondary">Avg Holding</span>
|
||||
<span className="text-sm font-medium text-text-primary">
|
||||
{allMetrics.avgHoldingPeriod ? `${(allMetrics.avgHoldingPeriod / 60).toFixed(1)}h` : '-'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs text-text-secondary">Max Consec Loss</span>
|
||||
<span className={`text-sm font-medium ${allMetrics.maxConsecutiveLosses > 5 ? 'text-error' : 'text-text-primary'}`}>
|
||||
{allMetrics.maxConsecutiveLosses ?? '-'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xs text-text-secondary">Payoff Ratio</span>
|
||||
<span className={`text-sm font-medium ${getColorClass(allMetrics.payoffRatio, { good: 1.5, warning: 1 })}`}>
|
||||
{formatValue(allMetrics.payoffRatio, 'number')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Advanced Metrics - Show only if available */}
|
||||
{(allMetrics.skewness !== undefined || allMetrics.kurtosis !== undefined ||
|
||||
allMetrics.informationRatio !== undefined || allMetrics.kellyFraction !== undefined) && (
|
||||
<div className="mt-3 pt-3 border-t border-border">
|
||||
<div className="grid grid-cols-4 gap-2 text-xs">
|
||||
{allMetrics.informationRatio !== undefined && (
|
||||
<div className="text-center">
|
||||
<span className="text-text-secondary">Info Ratio</span>
|
||||
<div className={`font-medium ${getColorClass(allMetrics.informationRatio, { good: 0.5, warning: 0 })}`}>
|
||||
{formatValue(allMetrics.informationRatio, 'number')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{allMetrics.skewness !== undefined && (
|
||||
<div className="text-center">
|
||||
<span className="text-text-secondary">Skewness</span>
|
||||
<div className={`font-medium ${getColorClass(allMetrics.skewness, { good: 0 })}`}>
|
||||
{formatValue(allMetrics.skewness, 'number')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{allMetrics.kurtosis !== undefined && (
|
||||
<div className="text-center">
|
||||
<span className="text-text-secondary">Kurtosis</span>
|
||||
<div className="font-medium text-text-primary">
|
||||
{formatValue(allMetrics.kurtosis, 'number')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{allMetrics.kellyFraction !== undefined && (
|
||||
<div className="text-center">
|
||||
<span className="text-text-secondary">Kelly %</span>
|
||||
<div className="font-medium text-text-primary">
|
||||
{formatValue(allMetrics.kellyFraction, 'percent')}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Additional Metrics Row */}
|
||||
<div className="mt-3 pt-3 border-t border-border">
|
||||
<div className="grid grid-cols-5 gap-2 text-xs">
|
||||
<div className="text-center">
|
||||
<span className="text-text-secondary">Calmar</span>
|
||||
<div className={`font-medium ${getColorClass(allMetrics.calmarRatio, { good: 1, warning: 0.5 })}`}>
|
||||
{formatValue(allMetrics.calmarRatio, 'number')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<span className="text-text-secondary">Profitable</span>
|
||||
<div className="font-medium text-text-primary">
|
||||
{allMetrics.profitableTrades ?? '-'}/{allMetrics.totalTrades ?? '-'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<span className="text-text-secondary">Exposure</span>
|
||||
<div className="font-medium text-text-primary">
|
||||
{result.analytics?.exposureTime ? formatValue(result.analytics.exposureTime, 'percent', 1) : '-'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<span className="text-text-secondary">Largest Win</span>
|
||||
<div className={`font-medium text-success`}>
|
||||
{formatValue(allMetrics.largestWin, 'currency')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<span className="text-text-secondary">Largest Loss</span>
|
||||
<div className={`font-medium text-error`}>
|
||||
{formatValue(allMetrics.largestLoss, 'currency')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue