Files
Neon-Desk/lib/server/portfolio.ts

70 lines
2.1 KiB
TypeScript

import type { Holding, PortfolioSummary } from '@/lib/types';
function asFiniteNumber(value: string | number | null | undefined) {
if (value === null || value === undefined) {
return 0;
}
const parsed = typeof value === 'number' ? value : Number(value);
return Number.isFinite(parsed) ? parsed : 0;
}
function toDecimalString(value: number, digits = 4) {
return value.toFixed(digits);
}
export function recalculateHolding(base: Holding): Holding {
const shares = asFiniteNumber(base.shares);
const avgCost = asFiniteNumber(base.avg_cost);
const price = base.current_price === null
? avgCost
: asFiniteNumber(base.current_price);
const marketValue = shares * price;
const costBasis = shares * avgCost;
const gainLoss = marketValue - costBasis;
const gainLossPct = costBasis > 0 ? (gainLoss / costBasis) * 100 : 0;
return {
...base,
shares: toDecimalString(shares, 6),
avg_cost: toDecimalString(avgCost, 6),
current_price: toDecimalString(price, 6),
market_value: toDecimalString(marketValue, 2),
gain_loss: toDecimalString(gainLoss, 2),
gain_loss_pct: toDecimalString(gainLossPct, 2)
};
}
export function buildPortfolioSummary(holdings: Holding[]): PortfolioSummary {
const positions = holdings.length;
const totals = holdings.reduce(
(acc, holding) => {
const shares = asFiniteNumber(holding.shares);
const avgCost = asFiniteNumber(holding.avg_cost);
const marketValue = asFiniteNumber(holding.market_value);
const gainLoss = asFiniteNumber(holding.gain_loss);
acc.totalValue += marketValue;
acc.totalGainLoss += gainLoss;
acc.totalCostBasis += shares * avgCost;
return acc;
},
{ totalValue: 0, totalGainLoss: 0, totalCostBasis: 0 }
);
const avgReturnPct = totals.totalCostBasis > 0
? (totals.totalGainLoss / totals.totalCostBasis) * 100
: 0;
return {
positions,
total_value: toDecimalString(totals.totalValue, 2),
total_gain_loss: toDecimalString(totals.totalGainLoss, 2),
total_cost_basis: toDecimalString(totals.totalCostBasis, 2),
avg_return_pct: toDecimalString(avgReturnPct, 2)
};
}