diff --git a/app/dashboard/assets/page.tsx b/app/dashboard/assets/page.tsx index 27a3c06..fca5edb 100644 --- a/app/dashboard/assets/page.tsx +++ b/app/dashboard/assets/page.tsx @@ -10,6 +10,7 @@ import { TableHeader, TableRow, } from '@/components/ui/table'; +import Big from 'big.js'; export default async function AssetsPage() { const assets = await getAssets(); @@ -53,14 +54,14 @@ export default async function AssetsPage() { {asset.symbol} {typeLabels[asset.type] || asset.type} {asset.baseCurrency} - {asset.latestPrice} + {asset.latestPrice ? new Big(asset.latestPrice).toString() : '-'} {asset.createdAt ? new Date(asset.createdAt).toLocaleString('zh-CN') : '-'} - + )) diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index d97dd54..b30ec1d 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -90,21 +90,33 @@ export default async function DashboardPage() { {pos.baseCurrency}
- CNY 估值 - - ¥{formatAmount(pos.cnyValue)} + 持仓成本 ({pos.baseCurrency}) + + {formatAmount(pos.totalCostNative)}
-
+ {(() => { + const posPnlNative = new Big(pos.pnlNative); + const posPnlNativePositive = posPnlNative.gte(0); + return ( +
+ 当前盈亏 ({pos.baseCurrency}) + + {posPnlNativePositive ? '+' : ''}{formatAmount(pos.pnlNative)} + +
+ ); + })()} +
成本 (CNY) ¥{formatAmount(pos.totalCostCny)}
-
- 盈亏 +
+ 盈亏 (CNY) - {posPnlPositive ? '+' : ''}{formattedPosPnl} + {posPnlPositive ? '+' : ''}{formatAmount(pos.pnlCny)}
diff --git a/src/actions/portfolio.ts b/src/actions/portfolio.ts index 0279a82..7cc9741 100644 --- a/src/actions/portfolio.ts +++ b/src/actions/portfolio.ts @@ -14,6 +14,8 @@ interface Position { cnyValue: string; totalCostCny: string; pnlCny: string; + totalCostNative: string; + pnlNative: string; } interface RawRate { @@ -95,6 +97,7 @@ export async function getPortfolioPositions(): Promise { baseCurrency: string; latestPrice: string; totalCostCny: Big; + totalCostNative: Big; }>(); for (const tx of allTransactions) { @@ -110,6 +113,7 @@ export async function getPortfolioPositions(): Promise { baseCurrency: tx.assetBaseCurrency || '', latestPrice: tx.assetLatestPrice || '0', totalCostCny: new Big('0'), + totalCostNative: new Big('0'), }); } @@ -118,10 +122,15 @@ export async function getPortfolioPositions(): Promise { if (tx.txType === 'BUY') { holding.quantity = holding.quantity.plus(new Big(tx.quantity)); const costPerUnit = new Big(tx.quantity).times(new Big(tx.price)); + holding.totalCostNative = holding.totalCostNative.plus(costPerUnit); const costCny = costPerUnit.times(new Big(tx.exchangeRate || '1')); holding.totalCostCny = holding.totalCostCny.plus(costCny); } else if (tx.txType === 'SELL') { holding.quantity = holding.quantity.minus(new Big(tx.quantity)); + const sellCostPerUnit = new Big(tx.quantity).times(new Big(tx.price)); + holding.totalCostNative = holding.totalCostNative.minus(sellCostPerUnit); + const sellCostCny = sellCostPerUnit.times(new Big(tx.exchangeRate || '1')); + holding.totalCostCny = holding.totalCostCny.minus(sellCostCny); } else if (tx.txType === 'AIRDROP') { holding.quantity = holding.quantity.plus(new Big(tx.quantity)); } else if (tx.txType === 'DIVIDEND') { @@ -160,6 +169,9 @@ export async function getPortfolioPositions(): Promise { const pnlCny = cnyValue.minus(holding.totalCostCny); totalPnlCny = totalPnlCny.plus(pnlCny); + const currentNativeValue = new Big(holding.latestPrice).times(holding.quantity); + const pnlNative = currentNativeValue.minus(holding.totalCostNative); + result.push({ assetId: holding.assetId, symbol: holding.symbol, @@ -169,6 +181,8 @@ export async function getPortfolioPositions(): Promise { cnyValue: cnyValue.toString(), totalCostCny: holding.totalCostCny.toString(), pnlCny: pnlCny.toString(), + totalCostNative: holding.totalCostNative.toString(), + pnlNative: pnlNative.toString(), }); } diff --git a/src/components/transactions/add-transaction-dialog.tsx b/src/components/transactions/add-transaction-dialog.tsx index 44a960f..99dbe86 100644 --- a/src/components/transactions/add-transaction-dialog.tsx +++ b/src/components/transactions/add-transaction-dialog.tsx @@ -218,9 +218,18 @@ export function AddTransactionDialog({ assets }: AddTransactionDialogProps) { render={({ field }) => ( 交易币种 - - - + )}