import { createLogger } from '@stock-bot/logger'; export interface Position { symbol: string; quantity: number; averagePrice: number; currentPrice: number; marketValue: number; unrealizedPnL: number; unrealizedPnLPercent: number; costBasis: number; lastUpdated: Date; } export interface PortfolioSnapshot { timestamp: Date; totalValue: number; cashBalance: number; positions: Position[]; totalReturn: number; totalReturnPercent: number; dayChange: number; dayChangePercent: number; } export interface Trade { id: string; symbol: string; quantity: number; price: number; side: 'buy' | 'sell'; timestamp: Date; commission: number; } export class PortfolioManager { private logger = createLogger('PortfolioManager'); private positions: Map = new Map(); private trades: Trade[] = []; private cashBalance: number = 100000; // Starting cash constructor(initialCash: number = 100000) { this.cashBalance = initialCash; } addTrade(trade: Trade): void { this.trades.push(trade); this.updatePosition(trade); logger.info(`Trade added: ${trade.symbol} ${trade.side} ${trade.quantity} @ ${trade.price}`); } private updatePosition(trade: Trade): void { const existing = this.positions.get(trade.symbol); if (!existing) { // New position if (trade.side === 'buy') { this.positions.set(trade.symbol, { symbol: trade.symbol, quantity: trade.quantity, averagePrice: trade.price, currentPrice: trade.price, marketValue: trade.quantity * trade.price, unrealizedPnL: 0, unrealizedPnLPercent: 0, costBasis: trade.quantity * trade.price + trade.commission, lastUpdated: trade.timestamp }); this.cashBalance -= (trade.quantity * trade.price + trade.commission); } return; } // Update existing position if (trade.side === 'buy') { const newQuantity = existing.quantity + trade.quantity; const newCostBasis = existing.costBasis + (trade.quantity * trade.price) + trade.commission; existing.quantity = newQuantity; existing.averagePrice = (newCostBasis - this.getTotalCommissions(trade.symbol)) / newQuantity; existing.costBasis = newCostBasis; existing.lastUpdated = trade.timestamp; this.cashBalance -= (trade.quantity * trade.price + trade.commission); } else if (trade.side === 'sell') { existing.quantity -= trade.quantity; existing.lastUpdated = trade.timestamp; const proceeds = trade.quantity * trade.price - trade.commission; this.cashBalance += proceeds; // Remove position if quantity is zero if (existing.quantity <= 0) { this.positions.delete(trade.symbol); } } } updatePrice(symbol: string, price: number): void { const position = this.positions.get(symbol); if (position) { position.currentPrice = price; position.marketValue = position.quantity * price; position.unrealizedPnL = position.marketValue - (position.quantity * position.averagePrice); position.unrealizedPnLPercent = position.unrealizedPnL / (position.quantity * position.averagePrice) * 100; position.lastUpdated = new Date(); } } getPosition(symbol: string): Position | undefined { return this.positions.get(symbol); } getAllPositions(): Position[] { return Array.from(this.positions.values()); } getPortfolioSnapshot(): PortfolioSnapshot { const positions = this.getAllPositions(); const totalMarketValue = positions.reduce((sum, pos) => sum + pos.marketValue, 0); const totalValue = totalMarketValue + this.cashBalance; const totalUnrealizedPnL = positions.reduce((sum, pos) => sum + pos.unrealizedPnL, 0); return { timestamp: new Date(), totalValue, cashBalance: this.cashBalance, positions, totalReturn: totalUnrealizedPnL, // Simplified - should include realized gains totalReturnPercent: (totalUnrealizedPnL / (totalValue - totalUnrealizedPnL)) * 100, dayChange: 0, // TODO: Calculate from previous day dayChangePercent: 0 }; } getTrades(symbol?: string): Trade[] { if (symbol) { return this.trades.filter(trade => trade.symbol === symbol); } return this.trades; } private getTotalCommissions(symbol: string): number { return this.trades .filter(trade => trade.symbol === symbol) .reduce((sum, trade) => sum + trade.commission, 0); } getCashBalance(): number { return this.cashBalance; } getNetLiquidationValue(): number { const positions = this.getAllPositions(); const positionValue = positions.reduce((sum, pos) => sum + pos.marketValue, 0); return positionValue + this.cashBalance; } }