UNPKG

stock-average-calculator

Version:

A comprehensive utility for calculating stock price averages and managing investment portfolios

325 lines (274 loc) 10.8 kB
/** * Stock Average Calculator * A utility for calculating average stock prices and portfolio metrics */ /** * Calculate the average purchase price of stocks * @param {Array<Object>} transactions - Array of stock transactions * @param {number} transactions[].shares - Number of shares in the transaction * @param {number} transactions[].price - Price per share in the transaction * @returns {Object} Object containing average price and other relevant metrics */ function calculateAveragePrice(transactions) { if (!Array.isArray(transactions)) { throw new Error('Transactions must be an array'); } if (transactions.length === 0) { throw new Error('At least one transaction is required'); } let totalShares = 0; let totalCost = 0; transactions.forEach((transaction, index) => { if (typeof transaction !== 'object' || transaction === null) { throw new Error(`Transaction at index ${index} must be an object`); } const { shares, price } = transaction; if (typeof shares !== 'number' || shares <= 0) { throw new Error(`Shares at index ${index} must be a positive number`); } if (typeof price !== 'number' || price < 0) { throw new Error(`Price at index ${index} must be a non-negative number`); } totalShares += shares; totalCost += shares * price; }); const averagePrice = totalCost / totalShares; return { totalShares, totalCost: parseFloat(totalCost.toFixed(2)), averagePrice: parseFloat(averagePrice.toFixed(2)) }; } /** * Calculate portfolio metrics with current market price * @param {Array<Object>} transactions - Array of stock transactions * @param {number} currentPrice - Current market price per share * @returns {Object} Object containing portfolio metrics and profitability */ function calculatePortfolioMetrics(transactions, currentPrice) { if (typeof currentPrice !== 'number' || currentPrice < 0) { throw new Error('Current price must be a non-negative number'); } const averagePriceResult = calculateAveragePrice(transactions); const { totalShares, totalCost, averagePrice } = averagePriceResult; const currentValue = totalShares * currentPrice; const profitLoss = currentValue - totalCost; const profitLossPercentage = (profitLoss / totalCost) * 100; return { ...averagePriceResult, currentPrice: parseFloat(currentPrice.toFixed(2)), currentValue: parseFloat(currentValue.toFixed(2)), profitLoss: parseFloat(profitLoss.toFixed(2)), profitLossPercentage: parseFloat(profitLossPercentage.toFixed(2)), isProfit: profitLoss >= 0 }; } /** * Calculate dollar cost averaging strategy results * @param {number} initialInvestment - Initial investment amount * @param {number} periodicInvestment - Amount invested periodically * @param {Array<number>} prices - Array of stock prices over time * @param {string} frequency - Investment frequency (e.g., 'monthly', 'weekly') * @returns {Object} Object containing DCA results */ function calculateDollarCostAveraging(initialInvestment, periodicInvestment, prices, frequency = 'monthly') { if (typeof initialInvestment !== 'number' || initialInvestment < 0) { throw new Error('Initial investment must be a non-negative number'); } if (typeof periodicInvestment !== 'number' || periodicInvestment < 0) { throw new Error('Periodic investment must be a non-negative number'); } if (!Array.isArray(prices) || prices.length === 0) { throw new Error('Prices must be a non-empty array of numbers'); } prices.forEach((price, index) => { if (typeof price !== 'number' || price <= 0) { throw new Error(`Price at index ${index} must be a positive number`); } }); const transactions = []; // Initial investment const initialShares = initialInvestment / prices[0]; transactions.push({ shares: initialShares, price: prices[0] }); // Periodic investments for (let i = 1; i < prices.length; i++) { const shares = periodicInvestment / prices[i]; transactions.push({ shares, price: prices[i] }); } const lastPrice = prices[prices.length - 1]; const result = calculatePortfolioMetrics(transactions, lastPrice); return { ...result, frequency, initialInvestment, periodicInvestment, periods: prices.length, totalInvestment: initialInvestment + (periodicInvestment * (prices.length - 1)) }; } /** * Calculate the break-even price after averaging down * @param {Array<Object>} transactions - Array of stock transactions * @returns {number} Break-even price per share */ function calculateBreakEvenPrice(transactions) { const { averagePrice } = calculateAveragePrice(transactions); return averagePrice; } /** * Format average price calculation result as a string * @param {Object} result - The calculation result * @returns {string} Formatted string with the calculation result */ function formatAveragePriceResult(result) { if (!result) { return 'Invalid calculation result'; } return ` Average Price Calculation: ------------------------- Total Shares: ${result.totalShares.toFixed(2)} Total Cost: $${result.totalCost.toFixed(2)} Average Price per Share: $${result.averagePrice.toFixed(2)} `; } /** * Format portfolio metrics result as a string * @param {Object} result - The calculation result * @returns {string} Formatted string with the calculation result */ function formatPortfolioMetricsResult(result) { if (!result) { return 'Invalid calculation result'; } return ` Portfolio Metrics: ---------------- Total Shares: ${result.totalShares.toFixed(2)} Average Price per Share: $${result.averagePrice.toFixed(2)} Total Cost: $${result.totalCost.toFixed(2)} Current Price per Share: $${result.currentPrice.toFixed(2)} Current Portfolio Value: $${result.currentValue.toFixed(2)} Profit/Loss: $${result.profitLoss.toFixed(2)} (${result.profitLossPercentage.toFixed(2)}%) Status: ${result.isProfit ? 'PROFIT' : 'LOSS'} `; } /** * Format dollar cost averaging result as a string * @param {Object} result - The calculation result * @returns {string} Formatted string with the calculation result */ function formatDCAResult(result) { if (!result) { return 'Invalid calculation result'; } return ` Dollar Cost Averaging Results: ---------------------------- Investment Frequency: ${result.frequency} Initial Investment: $${result.initialInvestment.toFixed(2)} Periodic Investment: $${result.periodicInvestment.toFixed(2)} Total Periods: ${result.periods} Total Investment: $${result.totalInvestment.toFixed(2)} Total Shares Acquired: ${result.totalShares.toFixed(2)} Average Price per Share: $${result.averagePrice.toFixed(2)} Current Price per Share: $${result.currentPrice.toFixed(2)} Current Portfolio Value: $${result.currentValue.toFixed(2)} Profit/Loss: $${result.profitLoss.toFixed(2)} (${result.profitLossPercentage.toFixed(2)}%) Status: ${result.isProfit ? 'PROFIT' : 'LOSS'} `; } /** * Quick calculate average price from simple inputs * @param {Array<number>} amounts - Array of share amounts * @param {Array<number>} prices - Array of corresponding prices * @returns {Object} Average price calculation result */ function quickCalculateAverage(amounts, prices) { if (!Array.isArray(amounts) || !Array.isArray(prices)) { throw new Error('Amounts and prices must be arrays'); } if (amounts.length !== prices.length) { throw new Error('Amounts and prices arrays must have the same length'); } if (amounts.length === 0) { throw new Error('At least one transaction is required'); } const transactions = amounts.map((shares, index) => ({ shares, price: prices[index] })); return calculateAveragePrice(transactions); } /** * Calculate optimal averaging strategy to reach target average * @param {Array<Object>} currentTransactions - Current transactions * @param {number} targetAveragePrice - Desired average price * @param {number} currentMarketPrice - Current market price * @returns {Object|null} Suggested transaction or null if target cannot be reached */ function calculateOptimalAveragingStrategy(currentTransactions, targetAveragePrice, currentMarketPrice) { if (typeof targetAveragePrice !== 'number' || targetAveragePrice <= 0) { throw new Error('Target average price must be a positive number'); } if (typeof currentMarketPrice !== 'number' || currentMarketPrice <= 0) { throw new Error('Current market price must be a positive number'); } const { totalShares, totalCost, averagePrice } = calculateAveragePrice(currentTransactions); // Check if the target is already achieved if (Math.abs(averagePrice - targetAveragePrice) < 0.01) { return { currentAverage: averagePrice, targetAverage: targetAveragePrice, achievable: true, message: 'Target average already achieved', suggestedAction: null }; } // Cannot average up to a lower price or average down to a higher price if ((averagePrice < targetAveragePrice && currentMarketPrice <= averagePrice) || (averagePrice > targetAveragePrice && currentMarketPrice >= averagePrice)) { return { currentAverage: averagePrice, targetAverage: targetAveragePrice, achievable: false, message: `Cannot reach target average with current market price of $${currentMarketPrice}` }; } // Calculate required shares to reach target average let requiredShares; if (averagePrice > targetAveragePrice) { // Averaging down requiredShares = (totalCost - (targetAveragePrice * totalShares)) / (targetAveragePrice - currentMarketPrice); } else { // Averaging up requiredShares = (targetAveragePrice * totalShares - totalCost) / (currentMarketPrice - targetAveragePrice); } requiredShares = Math.max(0, requiredShares); const requiredInvestment = requiredShares * currentMarketPrice; return { currentAverage: averagePrice, targetAverage: targetAveragePrice, achievable: true, suggestedAction: { action: averagePrice > targetAveragePrice ? 'BUY' : 'SELL', shares: parseFloat(requiredShares.toFixed(2)), price: currentMarketPrice, investment: parseFloat(requiredInvestment.toFixed(2)) }, newTotalShares: parseFloat((totalShares + requiredShares).toFixed(2)), newTotalCost: parseFloat((totalCost + requiredInvestment).toFixed(2)) }; } // Export functions module.exports = { calculateAveragePrice, calculatePortfolioMetrics, calculateDollarCostAveraging, calculateBreakEvenPrice, formatAveragePriceResult, formatPortfolioMetricsResult, formatDCAResult, quickCalculateAverage, calculateOptimalAveragingStrategy };