refactor(ledger): PnL 引擎接入动态汇率字典,实现跨币种资产的高精度法币折算
This commit is contained in:
+41
-12
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user