refactor(ledger): PnL 引擎接入动态汇率字典,实现跨币种资产的高精度法币折算

This commit is contained in:
2026-05-02 00:21:46 +08:00
parent b7077ec9d3
commit 87292b107a
2 changed files with 48 additions and 13 deletions
+41 -12
View File
@@ -1,9 +1,9 @@
'use server';
import { db } from '@/db';
import { transactions, assets, exchangeRates } from '@/db/schema';
import { transactions, assets, exchangeRates, exchangeRatesHistory } from '@/db/schema';
import Big from 'big.js';
import { asc, eq } from 'drizzle-orm';
import { asc, desc, eq } from 'drizzle-orm';
import { calculateAssetMetrics } from '@/utils/finance';
interface Position {
@@ -64,6 +64,35 @@ interface RawRate {
rate: string;
}
async function getLatestRatesMap(): Promise<Record<string, Big>> {
const usdResult = await db
.select({
rate: exchangeRatesHistory.rate,
})
.from(exchangeRatesHistory)
.where(eq(exchangeRatesHistory.fromCurrency, 'USD'))
.orderBy(desc(exchangeRatesHistory.fetchTime))
.limit(1);
const hkdResult = await db
.select({
rate: exchangeRatesHistory.rate,
})
.from(exchangeRatesHistory)
.where(eq(exchangeRatesHistory.fromCurrency, 'HKD'))
.orderBy(desc(exchangeRatesHistory.fetchTime))
.limit(1);
const dbUsd = usdResult[0];
const dbHkd = hkdResult[0];
return {
CNY: new Big(1),
USD: new Big(dbUsd?.rate || 7.2),
HKD: new Big(dbHkd?.rate || 0.9),
};
}
function buildRateMap(rates: RawRate[]): Map<string, string> {
const map = new Map<string, string>();
for (const r of rates) {
@@ -167,13 +196,14 @@ export async function getPortfolioPositions(): Promise<Position[]> {
.leftJoin(assets, eq(assets.id, transactions.assetId))
.orderBy(asc(transactions.executedAt));
const rates = await db.select({
fromCurrency: exchangeRates.fromCurrency,
toCurrency: exchangeRates.toCurrency,
rate: exchangeRates.rate,
}).from(exchangeRates);
const dynamicRateMap = await getLatestRatesMap();
const rateMap = buildRateMap(rates);
const rateMap = new Map<string, string>();
for (const [currency, rate] of Object.entries(dynamicRateMap)) {
if (currency !== 'CNY') {
rateMap.set(`${currency}_CNY`, rate.toString());
}
}
const holdings = new Map<string, {
assetId: string;
@@ -330,10 +360,9 @@ export async function getPortfolioPositions(): Promise<Position[]> {
holding.latestPrice
);
// 获取资产对人民币的汇率
const fxRate = new Big(
getRate(rateMap, holding.baseCurrency, 'CNY') || '1'
);
// 从动态汇率字典获取资产对人民币的汇率
const currencyKey = holding.baseCurrency || 'CNY';
const fxRate = dynamicRateMap[currencyKey] || new Big(1);
// 将引擎返回的原生币种金额折算为 CNY
const marketValueCny = new Big(metrics.marketValue).times(fxRate).toString();