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