UNPKG

fitr

Version:
171 lines (139 loc) 6.61 kB
import { getPortfolio } from '../services/portfolioService'; import {getCurrencyExchangeRate} from '../services/currencyService'; import { formatNumber, getColoredFormatedNumber, highlight, } from '../utils/formatUtils'; import {Logger} from '../utils/logger'; import {Config} from "../config"; function calculatePMT(rate: number, nper: number, pv: number, fv: number): string { //PMT(annualGrowthRate/12,noOfYears*12,currentValue,targetValue) const pvif = Math.pow(1 + rate, nper); if (rate === 0) { return formatNumber(-(pv + fv) / nper, 0); } return formatNumber(rate * (fv + pv * pvif) / ((pvif - 1)), 0); } function generateTargets(currentValue: number): number[] { const start = Math.ceil(currentValue / Config.getInstance().getConfig().targetValuePace) * Config.getInstance().getConfig().minTargetValue; const targets = []; for (let target = start; target <= Config.getInstance().getConfig().maxTargetValue; target += Config.getInstance().getConfig().targetValuePace) { targets.push(target); } return targets; } async function displayPrediction(currentValue: number, annualGrowthRate: number = 0.07): Promise<void> { Logger.debug(`Calculating monthly investment needed ${(annualGrowthRate * 100).toFixed(2)}% annual growth rate`); const monthlyRate = annualGrowthRate / 12; const minYear = Config.getInstance().getConfig().minTargetYear; const maxYears = Config.getInstance().getConfig().maxTargetYear; const targets = generateTargets(currentValue); const yearsData = []; for (let i = minYear; i <= maxYears; i++) { const yearData: Record<string, string> = {year: (`${new Date().getFullYear() + i} (${i})`)}; for (const target of targets) { const key = target >= 1000000 ? `${(target / 1000000).toFixed(2)}m` : `${(target / 1000).toFixed(0)}k`; const value = calculatePMT( monthlyRate, i * 12, -currentValue, target ); yearData[key] = highlight(target === Config.getInstance().getConfig().highlightTargetValue, value); } yearsData.push(yearData); } Logger.printTable(yearsData, true, `Monthly Investment Needed - ${((annualGrowthRate * 100).toFixed(2))}% annual growth rate, current value of ${formatNumber(currentValue, 0)}`); } /** * Display the current portfolio with latest prices */ export async function displayPortfolio(annualGrowthRate: string, skipPrediction: boolean): Promise<void> { try { const portfolio = await getPortfolio(); if (portfolio.assets.length === 0) { Logger.warn('Portfolio is empty. Add some assets first.'); return; } const currenciesWithoutExchangeRates = []; // region Convert the total portfolio value to main currency const mainCurrency = Config.getInstance().getConfig().mainCurrency; let valueInMainCurrency: number = 0; const valueInCurrenciesDataTable = []; for (const c1 of (portfolio.currencies || [])) { Logger.debug(`Calculating total portfolio in ${c1.currency}`); let valueInCurrency: number = 0; for (const c2 of (portfolio.currencies || [])) { if (c2.currency.toUpperCase() === c1.currency.toUpperCase()) { valueInCurrency += c2.value; } else { const exchangeRate = await getCurrencyExchangeRate(c2.currency, c1.currency); if (exchangeRate === 0) { Logger.debug(`Skip conversion from ${c2.currency} to ${c1.currency}`); currenciesWithoutExchangeRates.push(c1.currency); break; } valueInCurrency += c2.value * exchangeRate; } } if (c1.currency.toUpperCase() === mainCurrency.toUpperCase()) { valueInMainCurrency = valueInCurrency; } if (!currenciesWithoutExchangeRates.includes(c1.currency)) { valueInCurrenciesDataTable.push({ 'TOTAL': `${c1.currency.toUpperCase()}\t${formatNumber(valueInCurrency)}` }); } } if (valueInCurrenciesDataTable.length > 0) { Logger.printTable(valueInCurrenciesDataTable, false, 'Total portfolio value'); } // endregion //region Display prediction if (!skipPrediction) { await displayPrediction(valueInMainCurrency, parseFloat(annualGrowthRate)); } //endregion // region Display portfolio summary const summaryTableData = [] for (const currency of (portfolio.currencies || [])) { summaryTableData.push({ 'Currency': currency.currency, 'Value': formatNumber(currency.value, 0), 'Cost': formatNumber(currency.cost, 0), 'P&L': `${getColoredFormatedNumber(currency.profit)} ${getColoredFormatedNumber(currency.profitPercentage, '%')}` }) } Logger.printTable(summaryTableData, true, 'Portfolio value by currency'); // endregion // region Display assets const assetsData: Record<string, Record<string, string>[]> = {}; portfolio.assets.sort((a, b) => b.currentValue - a.currentValue); for (const asset of portfolio.assets) { if (!assetsData[asset.type]) { assetsData[asset.type] = []; } assetsData[asset.type].push({ 'Symbol': asset.symbol, 'Name': asset.name, 'Currency': asset.currency, 'Value': formatNumber(asset.currentValue), 'P&L': `${getColoredFormatedNumber(asset.profit)} ${getColoredFormatedNumber(asset.profitPercentage, '%')}`, 'MWR': getColoredFormatedNumber(asset.mwr * 100, '%'), 'TWR': getColoredFormatedNumber(asset.twr * 100, '%'), 'Current Price': formatNumber(asset.lastPrice), 'Quantity': formatNumber(asset.quantity, 2), 'Avg Price': formatNumber(asset.avgCost), 'Updated': asset.lastUpdated, }); } for (const type in assetsData) { Logger.printTable(assetsData[type], true, `Assets - ${type}`); } // endregion } catch (error) { Logger.error('Failed to display portfolio!', error, true); } }