feat(dashboard): 优化资产分布图表,实现按市场维度的聚合展示与 Tooltip 交互增强
This commit is contained in:
@@ -24,6 +24,7 @@ interface Position {
|
||||
avgCost: string;
|
||||
dilutedCost: string;
|
||||
holdingDays: number;
|
||||
exchange: string;
|
||||
}
|
||||
|
||||
interface RawRate {
|
||||
@@ -80,6 +81,30 @@ function calculateCnyValueFromPrice(
|
||||
return new Big('0');
|
||||
}
|
||||
|
||||
function getMarketFromExchange(exchange: string): string {
|
||||
if (!exchange) return '未知';
|
||||
const upper = exchange.toUpperCase();
|
||||
if (upper === 'SSE' || upper === 'SZSE') return 'A股';
|
||||
if (upper === 'HKEX') return '港股';
|
||||
if (upper === 'CRYPTO') return '虚拟币';
|
||||
return '美股';
|
||||
}
|
||||
|
||||
const MARKET_COLORS: Record<string, string> = {
|
||||
'A股': '#ef4444',
|
||||
'港股': '#f59e0b',
|
||||
'美股': '#3b82f6',
|
||||
'虚拟币': '#10b981',
|
||||
};
|
||||
|
||||
interface MarketAllocation {
|
||||
market: string;
|
||||
name: string;
|
||||
totalCnyValue: number;
|
||||
percentage: number;
|
||||
fill: string;
|
||||
}
|
||||
|
||||
function getTodayInShanghai(): Date {
|
||||
const now = new Date();
|
||||
const utcStr = now.toLocaleString('en-US', { timeZone: 'UTC' });
|
||||
@@ -102,6 +127,7 @@ export async function getPortfolioPositions(): Promise<Position[]> {
|
||||
assetType: assets.type,
|
||||
assetBaseCurrency: assets.baseCurrency,
|
||||
assetLatestPrice: assets.latestPrice,
|
||||
assetExchange: assets.exchange,
|
||||
executedAt: transactions.executedAt,
|
||||
})
|
||||
.from(transactions)
|
||||
@@ -124,6 +150,7 @@ export async function getPortfolioPositions(): Promise<Position[]> {
|
||||
quantity: Big;
|
||||
baseCurrency: string;
|
||||
latestPrice: string;
|
||||
exchange: string;
|
||||
// 累计买入指标
|
||||
totalBuyCostCny: Big;
|
||||
totalBuyCostNative: Big;
|
||||
@@ -147,6 +174,7 @@ export async function getPortfolioPositions(): Promise<Position[]> {
|
||||
quantity: new Big('0'),
|
||||
baseCurrency: tx.assetBaseCurrency || '',
|
||||
latestPrice: tx.assetLatestPrice || '0',
|
||||
exchange: tx.assetExchange || 'US',
|
||||
totalBuyCostCny: new Big('0'),
|
||||
totalBuyCostNative: new Big('0'),
|
||||
totalBuyQuantity: new Big('0'),
|
||||
@@ -266,6 +294,7 @@ export async function getPortfolioPositions(): Promise<Position[]> {
|
||||
avgCost: avgCost.toString(),
|
||||
dilutedCost: dilutedCost.toString(),
|
||||
holdingDays,
|
||||
exchange: holding.exchange,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -307,11 +336,52 @@ export async function getPortfolioSummary() {
|
||||
][index % 6],
|
||||
}));
|
||||
|
||||
// 按市场维度聚合资产分布
|
||||
const marketMap = new Map<string, {
|
||||
market: string;
|
||||
totalCnyValue: Big;
|
||||
}>();
|
||||
|
||||
for (const pos of positions) {
|
||||
const market = getMarketFromExchange(pos.exchange);
|
||||
const existing = marketMap.get(market);
|
||||
if (existing) {
|
||||
existing.totalCnyValue = existing.totalCnyValue.plus(new Big(pos.cnyValue));
|
||||
} else {
|
||||
marketMap.set(market, {
|
||||
market,
|
||||
totalCnyValue: new Big(pos.cnyValue),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const marketAllocation: MarketAllocation[] = [];
|
||||
let grandTotal = new Big('0');
|
||||
for (const [, data] of marketMap) {
|
||||
grandTotal = grandTotal.plus(data.totalCnyValue);
|
||||
}
|
||||
|
||||
for (const [, data] of marketMap) {
|
||||
const percentage = grandTotal.gt(0)
|
||||
? data.totalCnyValue.div(grandTotal).times(100)
|
||||
: new Big('0');
|
||||
marketAllocation.push({
|
||||
market: data.market,
|
||||
name: data.market,
|
||||
totalCnyValue: Number(data.totalCnyValue.toString()),
|
||||
percentage: Number(percentage.toString()),
|
||||
fill: MARKET_COLORS[data.market] || '#6b7280',
|
||||
});
|
||||
}
|
||||
|
||||
marketAllocation.sort((a, b) => b.totalCnyValue - a.totalCnyValue);
|
||||
|
||||
return {
|
||||
positions,
|
||||
totalCnyValue: totalCnyValue.toString(),
|
||||
totalPnlCny: totalPnlCny.toString(),
|
||||
unrealizedPnlCny: unrealizedPnlCny.toString(),
|
||||
chartData,
|
||||
marketAllocation,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user