stock-bot/apps/dashboard/src/app/pages/portfolio/portfolio.component.ts

168 lines
4.7 KiB
TypeScript

import { CommonModule } from '@angular/common';
import { Component, inject, OnDestroy, OnInit, signal } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { interval, Subscription } from 'rxjs';
import { ApiService } from '../../services/api.service';
export interface Position {
symbol: string;
quantity: number;
avgPrice: number;
currentPrice: number;
marketValue: number;
unrealizedPnL: number;
unrealizedPnLPercent: number;
dayChange: number;
dayChangePercent: number;
}
export interface PortfolioSummary {
totalValue: number;
totalCost: number;
totalPnL: number;
totalPnLPercent: number;
dayChange: number;
dayChangePercent: number;
cash: number;
positionsCount: number;
}
@Component({
selector: 'app-portfolio',
standalone: true,
imports: [
CommonModule,
MatCardModule,
MatIconModule,
MatButtonModule,
MatTableModule,
MatProgressSpinnerModule,
MatSnackBarModule,
MatTabsModule,
],
templateUrl: './portfolio.component.html',
styleUrl: './portfolio.component.css',
})
export class PortfolioComponent implements OnInit, OnDestroy {
private apiService = inject(ApiService);
private snackBar = inject(MatSnackBar);
private subscriptions: Subscription[] = [];
protected portfolioSummary = signal<PortfolioSummary>({
totalValue: 0,
totalCost: 0,
totalPnL: 0,
totalPnLPercent: 0,
dayChange: 0,
dayChangePercent: 0,
cash: 0,
positionsCount: 0,
});
protected positions = signal<Position[]>([]);
protected isLoading = signal<boolean>(true);
protected error = signal<string | null>(null);
protected displayedColumns = [
'symbol',
'quantity',
'avgPrice',
'currentPrice',
'marketValue',
'unrealizedPnL',
'dayChange',
];
ngOnInit() {
this.loadPortfolioData();
// Refresh portfolio data every 30 seconds
const portfolioSubscription = interval(30000).subscribe(() => {
this.loadPortfolioData();
});
this.subscriptions.push(portfolioSubscription);
}
ngOnDestroy() {
this.subscriptions.forEach(sub => sub.unsubscribe());
}
private loadPortfolioData() {
// Since we don't have a portfolio endpoint yet, let's create mock data
// In a real implementation, this would call this.apiService.getPortfolio()
setTimeout(() => {
const mockPositions: Position[] = [
{
symbol: 'AAPL',
quantity: 100,
avgPrice: 180.5,
currentPrice: 192.53,
marketValue: 19253,
unrealizedPnL: 1203,
unrealizedPnLPercent: 6.67,
dayChange: 241,
dayChangePercent: 1.27,
},
{
symbol: 'MSFT',
quantity: 50,
avgPrice: 400.0,
currentPrice: 415.26,
marketValue: 20763,
unrealizedPnL: 763,
unrealizedPnLPercent: 3.82,
dayChange: 436.5,
dayChangePercent: 2.15,
},
{
symbol: 'GOOGL',
quantity: 10,
avgPrice: 2900.0,
currentPrice: 2847.56,
marketValue: 28475.6,
unrealizedPnL: -524.4,
unrealizedPnLPercent: -1.81,
dayChange: -123.4,
dayChangePercent: -0.43,
},
];
const summary: PortfolioSummary = {
totalValue: mockPositions.reduce((sum, pos) => sum + pos.marketValue, 0) + 25000, // + cash
totalCost: mockPositions.reduce((sum, pos) => sum + pos.avgPrice * pos.quantity, 0),
totalPnL: mockPositions.reduce((sum, pos) => sum + pos.unrealizedPnL, 0),
totalPnLPercent: 0,
dayChange: mockPositions.reduce((sum, pos) => sum + pos.dayChange, 0),
dayChangePercent: 0,
cash: 25000,
positionsCount: mockPositions.length,
};
summary.totalPnLPercent = (summary.totalPnL / summary.totalCost) * 100;
summary.dayChangePercent =
(summary.dayChange / (summary.totalValue - summary.dayChange)) * 100;
this.positions.set(mockPositions);
this.portfolioSummary.set(summary);
this.isLoading.set(false);
this.error.set(null);
}, 1000);
}
refreshData() {
this.isLoading.set(true);
this.loadPortfolioData();
}
getPnLColor(value: number): string {
if (value > 0) return 'text-green-600';
if (value < 0) return 'text-red-600';
return 'text-gray-600';
}
}