70 lines
2.1 KiB
TypeScript
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)
|
|
};
|
|
}
|