stock-portfolio_byQwen3.6/src/utils/finance.ts

68 lines
2.1 KiB
TypeScript

import Big from 'big.js';
// 定义流水参数结构
export interface TxRecord {
date: string | Date;
txType: 'BUY' | 'SELL' | 'DIVIDEND' | string;
quantity: string | number;
price: string | number;
fee: string | number;
totalValue?: string | number;
}
export function calculateAssetMetrics(transactions: TxRecord[], currentPrice: string | number) {
let holdings = new Big(0);
let totalInvested = new Big(0);
let totalRealized = new Big(0);
let averageCost = new Big(0);
const sortedTx = [...transactions].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
for (const tx of sortedTx) {
const qty = new Big(tx.quantity || 0);
const price = new Big(tx.price || 0);
const fee = new Big(tx.fee || 0);
if (tx.txType === 'BUY') {
const cost = qty.times(price).plus(fee);
totalInvested = totalInvested.plus(cost);
if (holdings.plus(qty).gt(0)) {
const oldTotalValue = holdings.times(averageCost);
averageCost = oldTotalValue.plus(cost).div(holdings.plus(qty));
}
holdings = holdings.plus(qty);
} else if (tx.txType === 'SELL') {
const revenue = qty.times(price).minus(fee);
totalRealized = totalRealized.plus(revenue);
holdings = holdings.minus(qty);
if (holdings.lte(0)) {
holdings = new Big(0);
averageCost = new Big(0);
}
} else if (tx.txType === 'DIVIDEND') {
totalRealized = totalRealized.plus(tx.totalValue || 0);
}
}
const currentMarketValue = holdings.times(new Big(currentPrice));
const accumulatedPnl = currentMarketValue.plus(totalRealized).minus(totalInvested);
const floatingPnl = holdings.gt(0) ? new Big(currentPrice).minus(averageCost).times(holdings) : new Big(0);
const dilutedCost = holdings.gt(0) ? totalInvested.minus(totalRealized).div(holdings) : new Big(0);
return {
holdings: holdings.toString(),
averageCost: averageCost.toString(),
dilutedCost: dilutedCost.toString(),
floatingPnl: floatingPnl.toString(),
accumulatedPnl: accumulatedPnl.toString(),
marketValue: currentMarketValue.toString()
};
}