You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[chore·hardening] account_perf's cash_residual gate can KeyError on a currency outside the value-carrying set — latent, currently unreachable from main() #690
Discussion — mechanism-level hardening note from PR #685's investigation into #649, listed in the PR body under "Adjacent findings, deliberately not folded in." No real trade data anywhere below; no maintainer decision yet, and deliberately not framed as a defect with observed user impact — see "Why this is not being filed as a live defect" below.
Owning outcome
account_perf never raises on a data shape it was not specifically built to reject; every failure to compute is a stated {"gate": ...}, matching its own docstring's fail-closed contract ("算不了 → {"note": ...} fail-closed(同 pnl_curve)").
Mechanism
skills/fomo-kernel/engine/perf.py (line numbers against origin/main@e662164):
_fx_getter (:184-242) builds cols, a per-currency daily FX-factor series, only for the currencies passed in as currencies (:217-238). fx_at(c, i) (:240-241) is 1.0 if c == "USD" else cols[c][i] — an unconditional dict index with no .get/default for any currency absent from cols.
The cash_residual gate (:452-458) evaluates fx_at(r["currency"], n - 1) for every row r in cash_residuals (:454-455). That list is built by cash_reconcile_residuals (perf.py:118-) from ledger snapshot events' own declared cash dicts (_cash_snaps, built at trade_recap.py:2903-2905) — a source independent of cash_flows or cur_map. A ledger snapshot can declare a cash balance in a currency with no matching cash-flow row and no held position in that currency (for example, a residual balance surviving in a third currency the account touched only once, outside the window that still has a flow or holding for it). Such a currency is absent from value_ccys, hence absent from cols, and fx_at would raise KeyError rather than gate cleanly.
Why this is not being filed as a live defect
The investigating PR (#685) characterizes this as pre-existing — not introduced by #649's fix — and unreachable from main() today: no path from the CLI entry point currently constructs a ledger-snapshot-declared cash currency with no corresponding flow or holding that also clears cash_reconcile_residuals's own gates (at least two same-currency snapshots, residual above the abs_floor/pct threshold). This issue records the latent crash mechanism as a hardening note, verified directly by reading the code above, so it is not re-discovered from scratch if a future change — a richer snapshot-refresh flow, for instance — makes that combination reachable.
Evidence
Read against origin/main@e662164, by direct reading of _fx_getter, account_perf, cash_reconcile_residuals, and the call graph from trade_recap.py. No real trade data; no live reproduction attempted, consistent with filing this as a hardening note rather than a reproduced defect.
Status
Discussion — mechanism-level hardening note from PR #685's investigation into #649, listed in the PR body under "Adjacent findings, deliberately not folded in." No real trade data anywhere below; no maintainer decision yet, and deliberately not framed as a defect with observed user impact — see "Why this is not being filed as a live defect" below.
Owning outcome
account_perfnever raises on a data shape it was not specifically built to reject; every failure to compute is a stated{"gate": ...}, matching its own docstring's fail-closed contract ("算不了 → {"note": ...} fail-closed(同 pnl_curve)").Mechanism
skills/fomo-kernel/engine/perf.py(line numbers againstorigin/main@e662164):_fx_getter(:184-242) buildscols, a per-currency daily FX-factor series, only for the currencies passed in ascurrencies(:217-238).fx_at(c, i)(:240-241) is1.0 if c == "USD" else cols[c][i]— an unconditional dict index with no.get/default for any currency absent fromcols.account_perf(:245-) builds that input set asvalue_ccys(:262-268):cash_ccys(the union ofcash_data["by_currency"]keys and cash-flow-row currencies) unioned with held tickers' currencies. This is deliberately narrower than "every currency that could ever appear anywhere downstream" — by design, per [defect·P1] A currency that appears only in a cash-flow row is added to the USD total at 1.0 — #612's refusal has held currencies as its domain, and no rate is ever requested #649's fix, it is the set that actually carries value.cash_residualgate (:452-458) evaluatesfx_at(r["currency"], n - 1)for every rowrincash_residuals(:454-455). That list is built bycash_reconcile_residuals(perf.py:118-) from ledger snapshot events' own declaredcashdicts (_cash_snaps, built attrade_recap.py:2903-2905) — a source independent ofcash_flowsorcur_map. A ledger snapshot can declare a cash balance in a currency with no matching cash-flow row and no held position in that currency (for example, a residual balance surviving in a third currency the account touched only once, outside the window that still has a flow or holding for it). Such a currency is absent fromvalue_ccys, hence absent fromcols, andfx_atwould raiseKeyErrorrather than gate cleanly.Why this is not being filed as a live defect
The investigating PR (#685) characterizes this as pre-existing — not introduced by #649's fix — and unreachable from
main()today: no path from the CLI entry point currently constructs a ledger-snapshot-declared cash currency with no corresponding flow or holding that also clearscash_reconcile_residuals's own gates (at least two same-currency snapshots, residual above theabs_floor/pctthreshold). This issue records the latent crash mechanism as a hardening note, verified directly by reading the code above, so it is not re-discovered from scratch if a future change — a richer snapshot-refresh flow, for instance — makes that combination reachable.Evidence
Read against
origin/main@e662164, by direct reading of_fx_getter,account_perf,cash_reconcile_residuals, and the call graph fromtrade_recap.py. No real trade data; no live reproduction attempted, consistent with filing this as a hardening note rather than a reproduced defect.Cross-references
cash_reconcile_residualsand thecash_residualgate. This is a follow-on gap in that already-shipped mechanism, not a regression of it.