import { CommonModule } from '@angular/common'; import { Component, Input } from '@angular/core'; import { MatCardModule } from '@angular/material/card'; import { MatIconModule } from '@angular/material/icon'; import { MatPaginatorModule, PageEvent } from '@angular/material/paginator'; import { MatSortModule, Sort } from '@angular/material/sort'; import { MatTableModule } from '@angular/material/table'; import { BacktestResult } from '../../../services/strategy.service'; @Component({ selector: 'app-trades-table', standalone: true, imports: [ CommonModule, MatTableModule, MatSortModule, MatPaginatorModule, MatCardModule, MatIconModule, ], template: ` Trades
Symbol {{ trade.symbol }} Entry Time {{ formatDate(trade.entryTime) }} Entry Price {{ formatCurrency(trade.entryPrice) }} Exit Time {{ formatDate(trade.exitTime) }} Exit Price {{ formatCurrency(trade.exitPrice) }} Quantity {{ trade.quantity }} P&L {{ formatCurrency(trade.pnl) }} P&L % {{ formatPercent(trade.pnlPercent) }}
`, styles: ` .trades-card { margin-bottom: 20px; } .trades-table { width: 100%; border-collapse: collapse; } .mat-column-pnl, .mat-column-pnlPercent { text-align: right; font-weight: 500; } .positive { color: #4caf50; } .negative { color: #f44336; } .mat-mdc-row:hover { background-color: rgba(0, 0, 0, 0.04); } `, }) export class TradesTableComponent { @Input() set backtestResult(value: BacktestResult | undefined) { if (value) { this._backtestResult = value; this.updateDisplayedTrades(); } } get backtestResult(): BacktestResult | undefined { return this._backtestResult; } private _backtestResult?: BacktestResult; // Table configuration displayedColumns: string[] = [ 'symbol', 'entryTime', 'entryPrice', 'exitTime', 'exitPrice', 'quantity', 'pnl', 'pnlPercent', ]; // Pagination pageSize = 10; currentPage = 0; displayedTrades: any[] = []; get totalTrades(): number { return this._backtestResult?.trades.length || 0; } // Sort the trades sortData(sort: Sort): void { if (!sort.active || sort.direction === '') { this.updateDisplayedTrades(); return; } const data = this._backtestResult?.trades.slice() || []; this.displayedTrades = data .sort((a, b) => { const isAsc = sort.direction === 'asc'; switch (sort.active) { case 'symbol': return this.compare(a.symbol, b.symbol, isAsc); case 'entryTime': return this.compare( new Date(a.entryTime).getTime(), new Date(b.entryTime).getTime(), isAsc ); case 'entryPrice': return this.compare(a.entryPrice, b.entryPrice, isAsc); case 'exitTime': return this.compare( new Date(a.exitTime).getTime(), new Date(b.exitTime).getTime(), isAsc ); case 'exitPrice': return this.compare(a.exitPrice, b.exitPrice, isAsc); case 'quantity': return this.compare(a.quantity, b.quantity, isAsc); case 'pnl': return this.compare(a.pnl, b.pnl, isAsc); case 'pnlPercent': return this.compare(a.pnlPercent, b.pnlPercent, isAsc); default: return 0; } }) .slice(this.currentPage * this.pageSize, (this.currentPage + 1) * this.pageSize); } // Handle page changes pageChange(event: PageEvent): void { this.pageSize = event.pageSize; this.currentPage = event.pageIndex; this.updateDisplayedTrades(); } // Update displayed trades based on current page and page size updateDisplayedTrades(): void { if (this._backtestResult) { this.displayedTrades = this._backtestResult.trades.slice( this.currentPage * this.pageSize, (this.currentPage + 1) * this.pageSize ); } else { this.displayedTrades = []; } } // Helper methods for formatting formatDate(date: Date | string): string { return new Date(date).toLocaleString(); } formatCurrency(value: number): string { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', }).format(value); } formatPercent(value: number): string { return new Intl.NumberFormat('en-US', { style: 'percent', minimumFractionDigits: 2, maximumFractionDigits: 2, }).format(value); } private compare(a: number | string, b: number | string, isAsc: boolean): number { return (a < b ? -1 : 1) * (isAsc ? 1 : -1); } }