322 lines
9.7 KiB
TypeScript
322 lines
9.7 KiB
TypeScript
import { CommonModule } from '@angular/common';
|
|
import { Component, Input } from '@angular/core';
|
|
import { MatCardModule } from '@angular/material/card';
|
|
import { MatDividerModule } from '@angular/material/divider';
|
|
import { MatGridListModule } from '@angular/material/grid-list';
|
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
import { BacktestResult } from '../../../services/strategy.service';
|
|
|
|
@Component({
|
|
selector: 'app-performance-metrics',
|
|
standalone: true,
|
|
imports: [CommonModule, MatCardModule, MatGridListModule, MatDividerModule, MatTooltipModule],
|
|
template: `
|
|
<mat-card class="metrics-card">
|
|
<mat-card-header>
|
|
<mat-card-title>Performance Metrics</mat-card-title>
|
|
</mat-card-header>
|
|
<mat-card-content>
|
|
<div class="metrics-grid">
|
|
<div class="metric-group">
|
|
<h3>Returns</h3>
|
|
<div class="metrics-row">
|
|
<div class="metric">
|
|
<div class="metric-name" matTooltip="Total return over the backtest period">
|
|
Total Return
|
|
</div>
|
|
<div
|
|
class="metric-value"
|
|
[ngClass]="getReturnClass(backtestResult?.totalReturn || 0)"
|
|
>
|
|
{{ formatPercent(backtestResult?.totalReturn || 0) }}
|
|
</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div
|
|
class="metric-name"
|
|
matTooltip="Annualized return (adjusted for the backtest duration)"
|
|
>
|
|
Annualized Return
|
|
</div>
|
|
<div
|
|
class="metric-value"
|
|
[ngClass]="getReturnClass(backtestResult?.annualizedReturn || 0)"
|
|
>
|
|
{{ formatPercent(backtestResult?.annualizedReturn || 0) }}
|
|
</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-name" matTooltip="Compound Annual Growth Rate">CAGR</div>
|
|
<div class="metric-value" [ngClass]="getReturnClass(backtestResult?.cagr || 0)">
|
|
{{ formatPercent(backtestResult?.cagr || 0) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<mat-divider></mat-divider>
|
|
|
|
<div class="metric-group">
|
|
<h3>Risk Metrics</h3>
|
|
<div class="metrics-row">
|
|
<div class="metric">
|
|
<div class="metric-name" matTooltip="Maximum peak-to-valley drawdown">
|
|
Max Drawdown
|
|
</div>
|
|
<div class="metric-value negative">
|
|
{{ formatPercent(backtestResult?.maxDrawdown || 0) }}
|
|
</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-name" matTooltip="Number of days in the worst drawdown">
|
|
Max DD Duration
|
|
</div>
|
|
<div class="metric-value">
|
|
{{ formatDays(backtestResult?.maxDrawdownDuration || 0) }}
|
|
</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-name" matTooltip="Annualized standard deviation of returns">
|
|
Volatility
|
|
</div>
|
|
<div class="metric-value">
|
|
{{ formatPercent(backtestResult?.volatility || 0) }}
|
|
</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div
|
|
class="metric-name"
|
|
matTooltip="Square root of the sum of the squares of drawdowns"
|
|
>
|
|
Ulcer Index
|
|
</div>
|
|
<div class="metric-value">
|
|
{{ (backtestResult?.ulcerIndex || 0).toFixed(4) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<mat-divider></mat-divider>
|
|
|
|
<div class="metric-group">
|
|
<h3>Risk-Adjusted Returns</h3>
|
|
<div class="metrics-row">
|
|
<div class="metric">
|
|
<div class="metric-name" matTooltip="Excess return per unit of risk">
|
|
Sharpe Ratio
|
|
</div>
|
|
<div
|
|
class="metric-value"
|
|
[ngClass]="getRatioClass(backtestResult?.sharpeRatio || 0)"
|
|
>
|
|
{{ (backtestResult?.sharpeRatio || 0).toFixed(2) }}
|
|
</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-name" matTooltip="Return per unit of downside risk">
|
|
Sortino Ratio
|
|
</div>
|
|
<div
|
|
class="metric-value"
|
|
[ngClass]="getRatioClass(backtestResult?.sortinoRatio || 0)"
|
|
>
|
|
{{ (backtestResult?.sortinoRatio || 0).toFixed(2) }}
|
|
</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-name" matTooltip="Return per unit of max drawdown">
|
|
Calmar Ratio
|
|
</div>
|
|
<div
|
|
class="metric-value"
|
|
[ngClass]="getRatioClass(backtestResult?.calmarRatio || 0)"
|
|
>
|
|
{{ (backtestResult?.calmarRatio || 0).toFixed(2) }}
|
|
</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div
|
|
class="metric-name"
|
|
matTooltip="Probability-weighted ratio of gains vs. losses"
|
|
>
|
|
Omega Ratio
|
|
</div>
|
|
<div
|
|
class="metric-value"
|
|
[ngClass]="getRatioClass(backtestResult?.omegaRatio || 0)"
|
|
>
|
|
{{ (backtestResult?.omegaRatio || 0).toFixed(2) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<mat-divider></mat-divider>
|
|
|
|
<div class="metric-group">
|
|
<h3>Trade Statistics</h3>
|
|
<div class="metrics-row">
|
|
<div class="metric">
|
|
<div class="metric-name" matTooltip="Total number of trades">Total Trades</div>
|
|
<div class="metric-value">
|
|
{{ backtestResult?.totalTrades || 0 }}
|
|
</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-name" matTooltip="Percentage of winning trades">Win Rate</div>
|
|
<div class="metric-value" [ngClass]="getWinRateClass(backtestResult?.winRate || 0)">
|
|
{{ formatPercent(backtestResult?.winRate || 0) }}
|
|
</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-name" matTooltip="Average profit of winning trades">Avg Win</div>
|
|
<div class="metric-value positive">
|
|
{{ formatPercent(backtestResult?.averageWinningTrade || 0) }}
|
|
</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-name" matTooltip="Average loss of losing trades">Avg Loss</div>
|
|
<div class="metric-value negative">
|
|
{{ formatPercent(backtestResult?.averageLosingTrade || 0) }}
|
|
</div>
|
|
</div>
|
|
<div class="metric">
|
|
<div class="metric-name" matTooltip="Ratio of total gains to total losses">
|
|
Profit Factor
|
|
</div>
|
|
<div
|
|
class="metric-value"
|
|
[ngClass]="getProfitFactorClass(backtestResult?.profitFactor || 0)"
|
|
>
|
|
{{ (backtestResult?.profitFactor || 0).toFixed(2) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</mat-card-content>
|
|
</mat-card>
|
|
`,
|
|
styles: `
|
|
.metrics-card {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.metrics-grid {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
}
|
|
|
|
.metric-group {
|
|
padding: 10px 0;
|
|
}
|
|
|
|
.metric-group h3 {
|
|
margin-top: 0;
|
|
margin-bottom: 16px;
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
color: #555;
|
|
}
|
|
|
|
.metrics-row {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 24px;
|
|
}
|
|
|
|
.metric {
|
|
min-width: 120px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.metric-name {
|
|
font-size: 12px;
|
|
color: #666;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.metric-value {
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.positive {
|
|
color: #4caf50;
|
|
}
|
|
|
|
.negative {
|
|
color: #f44336;
|
|
}
|
|
|
|
.neutral {
|
|
color: #ffa000;
|
|
}
|
|
|
|
mat-divider {
|
|
margin: 8px 0;
|
|
}
|
|
`,
|
|
})
|
|
export class PerformanceMetricsComponent {
|
|
@Input() backtestResult?: BacktestResult;
|
|
|
|
// Formatting helpers
|
|
formatPercent(value: number): string {
|
|
return new Intl.NumberFormat('en-US', {
|
|
style: 'percent',
|
|
minimumFractionDigits: 2,
|
|
maximumFractionDigits: 2,
|
|
}).format(value);
|
|
}
|
|
|
|
formatDays(days: number): string {
|
|
return `${days} days`;
|
|
}
|
|
|
|
// Conditional classes
|
|
getReturnClass(value: number): string {
|
|
if (value > 0) {
|
|
return 'positive';
|
|
}
|
|
if (value < 0) {
|
|
return 'negative';
|
|
}
|
|
return '';
|
|
}
|
|
|
|
getRatioClass(value: number): string {
|
|
if (value >= 1.5) {
|
|
return 'positive';
|
|
}
|
|
if (value >= 1) {
|
|
return 'neutral';
|
|
}
|
|
if (value < 0) {
|
|
return 'negative';
|
|
}
|
|
return '';
|
|
}
|
|
|
|
getWinRateClass(value: number): string {
|
|
if (value >= 0.55) {
|
|
return 'positive';
|
|
}
|
|
if (value >= 0.45) {
|
|
return 'neutral';
|
|
}
|
|
return 'negative';
|
|
}
|
|
|
|
getProfitFactorClass(value: number): string {
|
|
if (value >= 1.5) {
|
|
return 'positive';
|
|
}
|
|
if (value >= 1) {
|
|
return 'neutral';
|
|
}
|
|
return 'negative';
|
|
}
|
|
}
|