Skip to content

fix(pulse): 组合敞口百分比不再用未折算的 marketValue 除以本币总资产 - #159

Open
wxrbyte wants to merge 1 commit into
helsome:mainfrom
wxrbyte:codex/fix-pulse-exposure-currency
Open

wxrbyte wants to merge 1 commit into
helsome:mainfrom
wxrbyte:codex/fix-pulse-exposure-currency

Conversation

@wxrbyte

@wxrbyte wxrbyte commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

问题

portfolioExposurePercent()packages/shared/src/pulse/service.ts)在缺少
marketValueBase 时直接回退到 marketValue

const weight = holding.marketValueBase ?? holding.marketValue
return round2((weight / total) * 100)

marketValueholding.currency(持仓原币)计价,而分母
PortfolioSnapshot.totalAssets 是本币口径。币种不一致时敞口被汇率倍数放大。

复现

portfolioExposurePercent('0700.HK', {
  baseCurrency: 'USD',
  totalAssets: 100_000,
  accounts: [],
  holdings: [{ symbol: '0700.HK', name: 'Tencent', currency: 'HKD', marketValue: 780_000 }],
  fetchedAt: Date.now(),
})
  • 实际:780(780% 敞口)
  • 预期:undefined(敞口未知)

根因

回退路径没有校验 holding.currency 是否等于 portfolio.baseCurrency

修复

新增 baseCurrencyValue() 辅助函数:

  1. 优先使用已折算的 marketValueBase(永远安全);
  2. 仅在 holding.currencyportfolio.baseCurrency 已知且相同时才用
    marketValue 兜底;
  3. 币种未知时保持宽松(与修改前一致),币种已知且不同则返回 undefined

该判定与 alerts 的 evaluatePositionWeight()packages/shared/src/alerts/evaluators.ts
保持一致。

范围

portfolioExposurePercent()computePersonalImpact() 及其余 pulse 逻辑未改动。
无可见 UI 变化(只是极端跨币种场景下不再给出错误数字,改为"未知")。

测试

packages/shared/src/pulse/service.test.ts 新增 2 个用例:

用例 输入 期望
does not divide an unconverted marketValue by a base-currency total USD 账本 / HKD 持仓 780,000 undefined
uses marketValue when the holding currency matches the base currency HKD 账本 / HKD 持仓 780,000 / 总额 1,000,000 78

回归证明:把修复回退成 holding.marketValueBase ?? holding.marketValue 后,
第一个用例 0 pass / 1 fail;修复后通过。

命令与结果:

bun test packages/shared/src/pulse --isolate
  -> 20 pass / 0 fail  (Ran 20 tests across 1 file)

bun run typecheck
  -> @finagent/core / i18n / shared / ui / electron 全部 exit 0

基线说明:干净 origin/main7c9b550)上 bun run typecheck 五个工作区全部
exit 0,无基线故障;本 PR 未引入任何新的 advisory 失败。

环境

  • Bun 1.4.2
  • Windows

Closes #158

`portfolioExposurePercent()` 在缺少 `marketValueBase` 时直接回退到
`marketValue`,而 `marketValue` 是按 `holding.currency`(持仓原币)计价的,
分母 `PortfolioSnapshot.totalAssets` 却是本币口径。二者币种不一致时,敞口
会被汇率倍数放大。

复现:
- baseCurrency = 'USD',totalAssets = 100000
- 持仓 0700.HK,currency = 'HKD',marketValue = 780000,没有 marketValueBase

实际:返回 780(即 780% 敞口)
预期:敞口未知,返回 undefined

修复:新增 `baseCurrencyValue()` 辅助函数——优先使用已折算的
`marketValueBase`;仅在持仓币种与本币**已知且相同**时才用 `marketValue`
兜底。逻辑与 alerts 的 `evaluatePositionWeight()` 保持一致。

范围:仅 `portfolioExposurePercent()`,不动 `computePersonalImpact()`
及其余 pulse 逻辑。无可见 UI 变化(只是极端跨币种场景下不再给出错误数字)。

测试:`packages/shared/src/pulse/service.test.ts` 新增 2 个用例
(跨币种返回 undefined / 同币种返回 78)。旧代码下前者失败,新代码通过。

@helsome helsome left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

有 1 个金融口径 blocker,需要 fail-closed 后再合:当前 baseCurrencyValue()holding.currencyportfolio.baseCurrency 任一未知时仍返回原币 marketValue,这仍可能把未折算金额除以本币 totalAssets。这也没有真正对齐 Issue #158 所引用的 alerts.evaluatePositionWeight():后者只有在两边币种都已知且相同才允许 raw marketValue 兜底。请改为:无有限 marketValueBase 时,只有 holding/base currency 均存在且相等才返回 marketValue,否则 undefined;再补一个币种缺失的 focused 回归用例即可。现有 Bun/OS 报告及 head 的 Focused / Typecheck / Secret scan / advisory full suite 均已足够,其余无需扩大。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pulse: 组合敞口百分比把未折算的 marketValue 直接除以本币 totalAssets

2 participants