问题
portfolioExposurePercent()(packages/shared/src/pulse/service.ts)在计算持仓敞口占比时:
const weight = holding.marketValueBase ?? holding.marketValue
...
return round2((weight / total) * 100)
marketValue 是按 holding.currency(持仓原币)计价的,而分母 PortfolioSnapshot.totalAssets
是本币口径。当数据提供方只给出 marketValue、没有 marketValueBase(或该字段缺失)时,
函数会用一个外币金额去除以本币总额,敞口被汇率倍数放大。
复现
const portfolio: PortfolioSnapshot = {
baseCurrency: 'USD',
totalAssets: 100_000,
accounts: [],
holdings: [
{ symbol: '0700.HK', name: 'Tencent', currency: 'HKD', marketValue: 780_000 },
],
fetchedAt: Date.now(),
}
portfolioExposurePercent('0700.HK', portfolio)
- 实际:
780(780% 敞口 —— 把 780,000 HKD 当成 780,000 USD 去除以 100,000 USD 的总资产)
- 预期:
undefined(敞口未知)
根因
缺少 marketValueBase 时直接回退到 marketValue,未校验币种是否与 portfolio.baseCurrency
一致。
对比参照
同一个仓库里 alerts 的 evaluatePositionWeight()(packages/shared/src/alerts/evaluators.ts)
已经正确处理了这一点:只有在持仓币种与本币已知且相同时才用未折算金额兜底,否则视为未知。
pulse 这里应当与之一致。
影响
Pulse / 个人化影响面板在跨币种持仓下会给出明显错误的敞口百分比(可能 > 100%),
进而污染"个人影响"排序与展示。
环境
问题
portfolioExposurePercent()(packages/shared/src/pulse/service.ts)在计算持仓敞口占比时:marketValue是按holding.currency(持仓原币)计价的,而分母PortfolioSnapshot.totalAssets是本币口径。当数据提供方只给出
marketValue、没有marketValueBase(或该字段缺失)时,函数会用一个外币金额去除以本币总额,敞口被汇率倍数放大。
复现
780(780% 敞口 —— 把 780,000 HKD 当成 780,000 USD 去除以 100,000 USD 的总资产)undefined(敞口未知)根因
缺少
marketValueBase时直接回退到marketValue,未校验币种是否与portfolio.baseCurrency一致。
对比参照
同一个仓库里 alerts 的
evaluatePositionWeight()(packages/shared/src/alerts/evaluators.ts)已经正确处理了这一点:只有在持仓币种与本币已知且相同时才用未折算金额兜底,否则视为未知。
pulse 这里应当与之一致。
影响
Pulse / 个人化影响面板在跨币种持仓下会给出明显错误的敞口百分比(可能 > 100%),
进而污染"个人影响"排序与展示。
环境