import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatCardModule } from '@angular/material/card'; import { MatTabsModule } from '@angular/material/tabs'; import { MatIconModule } from '@angular/material/icon'; import { MatButtonModule } from '@angular/material/button'; import { MatTableModule } from '@angular/material/table'; import { MatChipsModule } from '@angular/material/chips'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatDividerModule } from '@angular/material/divider'; import { MatDialog } from '@angular/material/dialog'; import { BacktestResult, TradingStrategy, StrategyService } from '../../../services/strategy.service'; import { WebSocketService } from '../../../services/websocket.service'; import { EquityChartComponent } from '../components/equity-chart.component'; import { DrawdownChartComponent } from '../components/drawdown-chart.component'; import { TradesTableComponent } from '../components/trades-table.component'; import { PerformanceMetricsComponent } from '../components/performance-metrics.component'; import { StrategyDialogComponent } from '../dialogs/strategy-dialog.component'; import { BacktestDialogComponent } from '../dialogs/backtest-dialog.component'; @Component({ selector: 'app-strategy-details', standalone: true, imports: [ CommonModule, MatCardModule, MatTabsModule, MatIconModule, MatButtonModule, MatTableModule, MatChipsModule, MatProgressBarModule, MatDividerModule, EquityChartComponent, DrawdownChartComponent, TradesTableComponent, PerformanceMetricsComponent ], templateUrl: './strategy-details.component.html', styleUrl: './strategy-details.component.css' }) export class StrategyDetailsComponent implements OnChanges { @Input() strategy: TradingStrategy | null = null; signals: any[] = []; trades: any[] = []; performance: any = {}; isLoadingSignals = false; isLoadingTrades = false; backtestResult: BacktestResult | undefined; constructor( private strategyService: StrategyService, private webSocketService: WebSocketService, private dialog: MatDialog ) {} ngOnChanges(changes: SimpleChanges): void { if (changes['strategy'] && this.strategy) { this.loadStrategyData(); this.listenForUpdates(); } } loadStrategyData(): void { if (!this.strategy) return; // In a real implementation, these would call API methods to fetch the data this.loadSignals(); this.loadTrades(); this.loadPerformance(); } loadSignals(): void { if (!this.strategy) return; this.isLoadingSignals = true; // First check if we can get real signals from the API this.strategyService.getStrategySignals(this.strategy.id) .subscribe({ next: (response) => { if (response.success && response.data && response.data.length > 0) { this.signals = response.data; } else { // Fallback to mock data if no real signals available this.signals = this.generateMockSignals(); } this.isLoadingSignals = false; }, error: (error) => { console.error('Error loading signals', error); // Fallback to mock data on error this.signals = this.generateMockSignals(); this.isLoadingSignals = false; } }); } loadTrades(): void { if (!this.strategy) return; this.isLoadingTrades = true; // First check if we can get real trades from the API this.strategyService.getStrategyTrades(this.strategy.id) .subscribe({ next: (response) => { if (response.success && response.data && response.data.length > 0) { this.trades = response.data; } else { // Fallback to mock data if no real trades available this.trades = this.generateMockTrades(); } this.isLoadingTrades = false; }, error: (error) => { console.error('Error loading trades', error); // Fallback to mock data on error this.trades = this.generateMockTrades(); this.isLoadingTrades = false; } }); } loadPerformance(): void { // This would be an API call in a real implementation this.performance = { totalReturn: this.strategy?.performance.totalReturn || 0, winRate: this.strategy?.performance.winRate || 0, sharpeRatio: this.strategy?.performance.sharpeRatio || 0, maxDrawdown: this.strategy?.performance.maxDrawdown || 0, totalTrades: this.strategy?.performance.totalTrades || 0, // Additional metrics that would come from the API dailyReturn: 0.0012, volatility: 0.008, sortinoRatio: 1.2, calmarRatio: 0.7 }; } listenForUpdates(): void { if (!this.strategy) return; // Subscribe to strategy signals this.webSocketService.getStrategySignals(this.strategy.id) .subscribe((signal: any) => { // Add the new signal to the top of the list this.signals = [signal, ...this.signals.slice(0, 9)]; // Keep only the latest 10 signals }); // Subscribe to strategy trades this.webSocketService.getStrategyTrades(this.strategy.id) .subscribe((trade: any) => { // Add the new trade to the top of the list this.trades = [trade, ...this.trades.slice(0, 9)]; // Keep only the latest 10 trades // Update performance metrics this.updatePerformanceMetrics(); }); // Subscribe to strategy status updates this.webSocketService.getStrategyUpdates() .subscribe((update: any) => { if (update.strategyId === this.strategy?.id) { // Update strategy status if changed if (update.status && this.strategy.status !== update.status) { this.strategy.status = update.status; } // Update other fields if present if (update.performance && this.strategy) { this.strategy.performance = { ...this.strategy.performance, ...update.performance }; this.performance = { ...this.performance, ...update.performance }; } } }); console.log('WebSocket listeners for strategy updates initialized'); } /** * Update performance metrics when new trades come in */ private updatePerformanceMetrics(): void { if (!this.strategy || this.trades.length === 0) return; // Calculate basic metrics const winningTrades = this.trades.filter(t => t.pnl > 0); const losingTrades = this.trades.filter(t => t.pnl < 0); const totalPnl = this.trades.reduce((sum, trade) => sum + trade.pnl, 0); const winRate = winningTrades.length / this.trades.length; // Update performance data const currentPerformance = this.performance || {}; this.performance = { ...currentPerformance, totalTrades: this.trades.length, winRate: winRate, totalReturn: (currentPerformance.totalReturn || 0) + (totalPnl / 10000) // Approximate }; // Update strategy performance as well if (this.strategy && this.strategy.performance) { this.strategy.performance = { ...this.strategy.performance, totalTrades: this.trades.length, winRate: winRate }; } } getStatusColor(status: string): string { switch (status) { case 'ACTIVE': return 'green'; case 'PAUSED': return 'orange'; case 'ERROR': return 'red'; default: return 'gray'; } } getSignalColor(action: string): string { switch (action) { case 'BUY': return 'green'; case 'SELL': return 'red'; default: return 'gray'; } } /** * Open the backtest dialog to run a backtest for this strategy */ openBacktestDialog(): void { if (!this.strategy) return; const dialogRef = this.dialog.open(BacktestDialogComponent, { width: '800px', data: this.strategy }); dialogRef.afterClosed().subscribe(result => { if (result) { // Store the backtest result for visualization this.backtestResult = result; } }); } /** * Open the strategy edit dialog */ openEditDialog(): void { if (!this.strategy) return; const dialogRef = this.dialog.open(StrategyDialogComponent, { width: '600px', data: this.strategy }); dialogRef.afterClosed().subscribe(result => { if (result) { // Refresh strategy data after edit this.loadStrategyData(); } }); } /** * Start the strategy */ activateStrategy(): void { if (!this.strategy) return; this.strategyService.startStrategy(this.strategy.id).subscribe({ next: (response) => { if (response.success) { this.strategy!.status = 'ACTIVE'; } }, error: (error) => { console.error('Error starting strategy:', error); } }); } /** * Pause the strategy */ pauseStrategy(): void { if (!this.strategy) return; this.strategyService.pauseStrategy(this.strategy.id).subscribe({ next: (response) => { if (response.success) { this.strategy!.status = 'PAUSED'; } }, error: (error) => { console.error('Error pausing strategy:', error); } }); } /** * Stop the strategy */ stopStrategy(): void { if (!this.strategy) return; this.strategyService.stopStrategy(this.strategy.id).subscribe({ next: (response) => { if (response.success) { this.strategy!.status = 'INACTIVE'; } }, error: (error) => { console.error('Error stopping strategy:', error); } }); } // Methods to generate mock data private generateMockSignals(): any[] { if (!this.strategy) return []; const signals = []; const actions = ['BUY', 'SELL', 'HOLD']; const now = new Date(); for (let i = 0; i < 10; i++) { const symbol = this.strategy.symbols[Math.floor(Math.random() * this.strategy.symbols.length)]; const action = actions[Math.floor(Math.random() * actions.length)]; signals.push({ id: `sig_${i}`, symbol, action, confidence: 0.7 + Math.random() * 0.3, price: 100 + Math.random() * 50, timestamp: new Date(now.getTime() - i * 1000 * 60 * 30), // 30 min intervals quantity: Math.floor(10 + Math.random() * 90) }); } return signals; } private generateMockTrades(): any[] { if (!this.strategy) return []; const trades = []; const now = new Date(); for (let i = 0; i < 10; i++) { const symbol = this.strategy.symbols[Math.floor(Math.random() * this.strategy.symbols.length)]; const entryPrice = 100 + Math.random() * 50; const exitPrice = entryPrice * (1 + (Math.random() * 0.1 - 0.05)); // -5% to +5% const quantity = Math.floor(10 + Math.random() * 90); const pnl = (exitPrice - entryPrice) * quantity; trades.push({ id: `trade_${i}`, symbol, entryPrice, entryTime: new Date(now.getTime() - (i + 5) * 1000 * 60 * 60), // Hourly intervals exitPrice, exitTime: new Date(now.getTime() - i * 1000 * 60 * 60), quantity, pnl, pnlPercent: ((exitPrice - entryPrice) / entryPrice) * 100 }); } return trades; } }