flatten app to repo root and update docker deployment for single-stack runtime
This commit is contained in:
69
lib/server/portfolio.ts
Normal file
69
lib/server/portfolio.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
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)
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user