fix(ledger): 修复时区黑洞,强制使用绝对字符串边界获取精准历史汇率
This commit is contained in:
+56
-52
@@ -213,71 +213,75 @@ export async function getEffectivePrice(
|
||||
return record?.price ?? null;
|
||||
}
|
||||
|
||||
interface RateRecord {
|
||||
rate: string;
|
||||
fetchTime: Date;
|
||||
}
|
||||
|
||||
async function buildDailyRatesMap(targetDateStr: string): Promise<Record<string, Big>> {
|
||||
const allRates = await db
|
||||
const boundaryString = `${targetDateStr} 23:59:59`;
|
||||
|
||||
// 获取 USD/CNY — 取目标时间点之前最后一条 USD->CNY 记录
|
||||
const usdRecords = await db
|
||||
.select({
|
||||
fromCurrency: exchangeRatesHistory.fromCurrency,
|
||||
toCurrency: exchangeRatesHistory.toCurrency,
|
||||
rate: exchangeRatesHistory.rate,
|
||||
fetchTime: exchangeRatesHistory.fetchTime,
|
||||
})
|
||||
.from(exchangeRatesHistory)
|
||||
.where(lte(exchangeRatesHistory.fetchTime, new Date(targetDateStr + 'T23:59:59')))
|
||||
.orderBy(asc(exchangeRatesHistory.fetchTime));
|
||||
.where(
|
||||
and(
|
||||
eq(exchangeRatesHistory.fromCurrency, 'USD'),
|
||||
eq(exchangeRatesHistory.toCurrency, 'CNY'),
|
||||
lte(exchangeRatesHistory.fetchTime, sql`${boundaryString}`)
|
||||
)
|
||||
)
|
||||
.orderBy(desc(exchangeRatesHistory.fetchTime))
|
||||
.limit(1);
|
||||
|
||||
const ratesCache = new Map<string, RateRecord[]>();
|
||||
for (const rec of allRates) {
|
||||
const key = `${rec.fromCurrency}_${rec.toCurrency}`;
|
||||
if (!ratesCache.has(key)) {
|
||||
ratesCache.set(key, []);
|
||||
// 获取 HKD/CNY — 取目标时间点之前最后一条 HKD->CNY 记录
|
||||
const hkdRecords = await db
|
||||
.select({
|
||||
rate: exchangeRatesHistory.rate,
|
||||
fetchTime: exchangeRatesHistory.fetchTime,
|
||||
})
|
||||
.from(exchangeRatesHistory)
|
||||
.where(
|
||||
and(
|
||||
eq(exchangeRatesHistory.fromCurrency, 'HKD'),
|
||||
eq(exchangeRatesHistory.toCurrency, 'CNY'),
|
||||
lte(exchangeRatesHistory.fetchTime, sql`${boundaryString}`)
|
||||
)
|
||||
)
|
||||
.orderBy(desc(exchangeRatesHistory.fetchTime))
|
||||
.limit(1);
|
||||
|
||||
// 若 HKD->CNY 不存在,尝试走 HKD->USD 再 USD->CNY 的交叉换算
|
||||
let hkdRateStr: string | null = hkdRecords[0]?.rate ?? null;
|
||||
if (!hkdRateStr) {
|
||||
const hkdUsdRecords = await db
|
||||
.select({
|
||||
rate: exchangeRatesHistory.rate,
|
||||
fetchTime: exchangeRatesHistory.fetchTime,
|
||||
})
|
||||
.from(exchangeRatesHistory)
|
||||
.where(
|
||||
and(
|
||||
eq(exchangeRatesHistory.fromCurrency, 'HKD'),
|
||||
eq(exchangeRatesHistory.toCurrency, 'USD'),
|
||||
lte(exchangeRatesHistory.fetchTime, sql`${boundaryString}`)
|
||||
)
|
||||
)
|
||||
.orderBy(desc(exchangeRatesHistory.fetchTime))
|
||||
.limit(1);
|
||||
|
||||
const usdToCnyRate = usdRecords[0]?.rate ?? null;
|
||||
if (hkdUsdRecords[0]?.rate && usdToCnyRate) {
|
||||
hkdRateStr = new Big(hkdUsdRecords[0].rate).times(new Big(usdToCnyRate)).toString();
|
||||
}
|
||||
ratesCache.get(key)!.push({ rate: rec.rate, fetchTime: rec.fetchTime });
|
||||
}
|
||||
|
||||
function getClosestRateForDate(currencyPair: string): string | null {
|
||||
const records = ratesCache.get(currencyPair);
|
||||
if (!records || records.length === 0) return null;
|
||||
const endOfDay = new Date(targetDateStr + 'T23:59:59.999');
|
||||
let closest: RateRecord | null = null;
|
||||
for (const rec of records) {
|
||||
if (rec.fetchTime <= endOfDay) {
|
||||
closest = rec;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return closest?.rate ?? null;
|
||||
}
|
||||
const usdRateStr = usdRecords[0]?.rate ?? null;
|
||||
|
||||
function resolveRate(from: string, to: string): string | null {
|
||||
const directKey = `${from}_${to}`;
|
||||
const directRate = getClosestRateForDate(directKey);
|
||||
if (directRate) return directRate;
|
||||
|
||||
const usdKey = `USD_${to}`;
|
||||
const usdRate = getClosestRateForDate(usdKey);
|
||||
if (!usdRate) return null;
|
||||
if (from === 'USD') return usdRate;
|
||||
|
||||
const fromToUsdKey = `${from}_USD`;
|
||||
const fromToUsdRate = getClosestRateForDate(fromToUsdKey);
|
||||
if (fromToUsdRate) {
|
||||
return new Big(fromToUsdRate).times(new Big(usdRate)).toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const hkdRate = resolveRate('HKD', 'CNY');
|
||||
const usdRate = resolveRate('USD', 'CNY');
|
||||
console.log(`[FX Fetch] Date: ${targetDateStr}, USD: ${usdRateStr}, HKD: ${hkdRateStr}`);
|
||||
|
||||
return {
|
||||
USD: new Big(usdRate || '7.22'),
|
||||
HKD: new Big(hkdRate || '0.92'),
|
||||
USD: new Big(usdRateStr || '7.22'),
|
||||
HKD: new Big(hkdRateStr || '0.92'),
|
||||
CNY: new Big(1),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user