gloomberb 0.16.1 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/package.json +2 -1
- package/src/api-client/beneficial-owners.ts +92 -0
- package/src/api-client/data.ts +26 -8
- package/src/api-client/errors.ts +12 -0
- package/src/api-client/geo.ts +363 -0
- package/src/api-client/index.ts +2 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/paths.ts +30 -1
- package/src/api-client/perps.ts +37 -0
- package/src/api-client/request.ts +5 -2
- package/src/api-client/types.ts +2 -0
- package/src/brokers/account-cache.ts +24 -0
- package/src/cli/commands/command-utils.ts +8 -0
- package/src/cli/commands/crypto-hints.ts +75 -0
- package/src/cli/commands/market.ts +414 -106
- package/src/cli/commands/overview.ts +77 -32
- package/src/cli/commands/ticker.ts +101 -43
- package/src/cli/desktop-pane-shot.ts +34 -4
- package/src/cli/freshness.ts +61 -0
- package/src/cli/help.ts +1 -0
- package/src/cli/helpers.ts +4 -8
- package/src/cli/index.ts +12 -2
- package/src/cli/listing-arg.ts +321 -0
- package/src/cli/pane-functions/access-gate.ts +42 -0
- package/src/cli/pane-functions/catalog.ts +28 -1
- package/src/cli/pane-functions/data.ts +3 -1
- package/src/cli/pane-functions/dom.ts +20 -2
- package/src/cli/pane-functions/freshness.ts +400 -0
- package/src/cli/pane-functions/function-help.ts +27 -20
- package/src/cli/pane-functions/headless.ts +39 -11
- package/src/cli/pane-functions/index.ts +15 -4
- package/src/cli/pane-functions/options.ts +3 -0
- package/src/cli/pane-functions/report.ts +4 -0
- package/src/cli/pane-functions/resolver.ts +35 -7
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +7 -3
- package/src/cli/result.ts +41 -27
- package/src/cli/ticker-resolution.ts +7 -5
- package/src/components/chart/composite/composite-chart.tsx +88 -2536
- package/src/components/chart/composite/drawing-store.ts +59 -0
- package/src/components/chart/composite/legend.tsx +319 -0
- package/src/components/chart/composite/panel-bitmap.ts +139 -0
- package/src/components/chart/composite/panel-gestures.ts +59 -0
- package/src/components/chart/composite/panel-labels.ts +107 -0
- package/src/components/chart/composite/panel-surface.tsx +876 -0
- package/src/components/chart/composite/tool-catalog.ts +98 -0
- package/src/components/chart/composite/toolbar.tsx +186 -0
- package/src/components/chart/composite/tools.ts +8 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +703 -0
- package/src/components/chart/composite/use-composite-viewport.ts +143 -0
- package/src/components/command-bar/list/model.ts +5 -0
- package/src/components/command-bar/list/view.tsx +16 -3
- package/src/components/command-bar/pane-templates/workflow.ts +1 -0
- package/src/components/command-bar/routes/ticker-search/actions.ts +5 -0
- package/src/components/command-bar/workflow/ops.ts +1 -1
- package/src/components/command-bar/workflow/tickers.ts +6 -0
- package/src/components/data-table/view.tsx +29 -10
- package/src/components/feedback-dialog.tsx +2 -1
- package/src/components/form-modal/field-row.tsx +2 -2
- package/src/components/layout/detached-pane-shell.tsx +6 -3
- package/src/components/layout/pane/content.tsx +23 -10
- package/src/components/layout/pane/error-boundary.tsx +103 -0
- package/src/components/layout/pane/footer/registration.tsx +68 -23
- package/src/components/layout/pane/header.tsx +11 -11
- package/src/components/layout/shell/menu.ts +2 -1
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +14 -3
- package/src/components/metric-treemap/heat-surface.tsx +43 -16
- package/src/components/metric-treemap/heat-tiles.ts +18 -0
- package/src/components/metric-treemap/layout.ts +2 -0
- package/src/components/onboarding/onboarding-keyboard.ts +311 -0
- package/src/components/onboarding/onboarding-wizard.tsx +101 -788
- package/src/components/onboarding/use-onboarding-broker-form.ts +263 -0
- package/src/components/onboarding/use-onboarding-progress.ts +298 -0
- package/src/components/onboarding/use-onboarding-upgrade.ts +83 -0
- package/src/components/ticker/list-table-view.tsx +13 -1
- package/src/components/ui/fields.tsx +8 -0
- package/src/components/ui/range-track.tsx +10 -3
- package/src/components/ui/status.tsx +8 -2
- package/src/components/ui/tabs.tsx +7 -4
- package/src/components/ui/text-prompt-dialog.tsx +4 -2
- package/src/data/config/store/normalize.ts +17 -1
- package/src/i18n/es.ts +19 -1
- package/src/i18n/ja.ts +19 -1
- package/src/i18n/ko.ts +19 -1
- package/src/i18n/zh-cn.ts +19 -1
- package/src/i18n/zh-tw.ts +19 -1
- package/src/market-data/coordinator/chart.ts +1 -1
- package/src/market-data/coordinator/quotes.ts +23 -4
- package/src/market-data/fx-closes.ts +72 -0
- package/src/market-data/market/freshness.ts +78 -6
- package/src/market-data/market/session-close-note.ts +57 -0
- package/src/market-data/market/status.ts +151 -0
- package/src/market-data/published-apac-sessions.ts +66 -0
- package/src/market-data/quotes/contributions.ts +2 -0
- package/src/market-data/quotes/resolution.ts +2 -0
- package/src/market-data/quotes/screener-live-quotes.ts +49 -4
- package/src/plugins/builtin/account-management/pane.tsx +4 -0
- package/src/plugins/builtin/alerts/cli.ts +10 -5
- package/src/plugins/builtin/alerts/index.tsx +1 -0
- package/src/plugins/builtin/analytics/metrics.ts +8 -3
- package/src/plugins/builtin/analytics/risk-client.ts +191 -66
- package/src/plugins/builtin/analytics/risk-fx.ts +102 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +19 -13
- package/src/plugins/builtin/analytics/risk-model.ts +45 -17
- package/src/plugins/builtin/analytics/risk-pane.tsx +1 -0
- package/src/plugins/builtin/analytics/risk-test-data.ts +25 -1
- package/src/plugins/builtin/attention/headless.ts +6 -4
- package/src/plugins/builtin/attention/index.tsx +1 -1
- package/src/plugins/builtin/attention/pane.tsx +1 -1
- package/src/plugins/builtin/awards/headless.ts +1 -0
- package/src/plugins/builtin/awards/pane.tsx +1 -1
- package/src/plugins/builtin/backtest/headless.ts +1 -0
- package/src/plugins/builtin/bond-calculator/headless.ts +1 -0
- package/src/plugins/builtin/buildout/index.tsx +1 -0
- package/src/plugins/builtin/catalysts/headless.ts +3 -1
- package/src/plugins/builtin/cds/headless.ts +2 -0
- package/src/plugins/builtin/central-bank-rates/headless.ts +1 -0
- package/src/plugins/builtin/chart-composer/headless.ts +38 -3
- package/src/plugins/builtin/chart-composer/index.tsx +3 -1
- package/src/plugins/builtin/chart-composer/series-expression.ts +18 -2
- package/src/plugins/builtin/chat/channels.ts +19 -3
- package/src/plugins/builtin/chat/content/composer-runtime.ts +28 -13
- package/src/plugins/builtin/chat/content/footer.tsx +15 -6
- package/src/plugins/builtin/chat/content/index.tsx +120 -21
- package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +261 -113
- package/src/plugins/builtin/chat/content/transcript.tsx +20 -2
- package/src/plugins/builtin/chat/direct-messages.ts +200 -0
- package/src/plugins/builtin/chat/index.tsx +1 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +3 -0
- package/src/plugins/builtin/chat/message/header.tsx +16 -6
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +72 -32
- package/src/plugins/builtin/chat/message/profile-popover.tsx +96 -12
- package/src/plugins/builtin/chat/message/terminal.tsx +3 -0
- package/src/plugins/builtin/chat/message/types.ts +14 -2
- package/src/plugins/builtin/chat/profile-popover.ts +18 -13
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -0
- package/src/plugins/builtin/cloud/askg/executor.ts +2 -1
- package/src/plugins/builtin/cloud/plugin.tsx +1 -0
- package/src/plugins/builtin/cloud/team/module.tsx +1 -0
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -0
- package/src/plugins/builtin/cloud-tweets/registration.ts +1 -0
- package/src/plugins/builtin/comment-letters/index.tsx +1 -0
- package/src/plugins/builtin/company-attention/headless.ts +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +1 -1
- package/src/plugins/builtin/company-kpis/headless.ts +1 -0
- package/src/plugins/builtin/congress-trades/headless.ts +1 -0
- package/src/plugins/builtin/correlation/compute.ts +59 -16
- package/src/plugins/builtin/correlation/geo.ts +34 -0
- package/src/plugins/builtin/correlation/headless-schema.ts +4 -1
- package/src/plugins/builtin/correlation/headless.ts +42 -8
- package/src/plugins/builtin/correlation/history.ts +2 -0
- package/src/plugins/builtin/correlation/index.tsx +68 -14
- package/src/plugins/builtin/correlation/matrix/model.ts +17 -2
- package/src/plugins/builtin/correlation/matrix/selection.ts +5 -3
- package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
- package/src/plugins/builtin/correlation/relationship/pane.tsx +4 -2
- package/src/plugins/builtin/correlation/relationship/view-model.ts +16 -1
- package/src/plugins/builtin/correlation/settings.ts +1 -1
- package/src/plugins/builtin/cot/headless.ts +6 -3
- package/src/plugins/builtin/cot/index.tsx +3 -3
- package/src/plugins/builtin/cot/model.ts +36 -3
- package/src/plugins/builtin/cpi/headless.ts +4 -0
- package/src/plugins/builtin/credit-boards/headless.ts +2 -0
- package/src/plugins/builtin/credit-conditions/headless.ts +1 -0
- package/src/plugins/builtin/credit-documents/gauge.tsx +5 -8
- package/src/plugins/builtin/credit-documents/headless.ts +2 -0
- package/src/plugins/builtin/crypto-board/headless.ts +3 -0
- package/src/plugins/builtin/custom-view/loader.ts +1 -1
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +1 -0
- package/src/plugins/builtin/doe/headless.ts +7 -0
- package/src/plugins/builtin/doe/range-bar.tsx +7 -37
- package/src/plugins/builtin/earnings/headless.ts +5 -0
- package/src/plugins/builtin/earnings-calls/headless.ts +1 -0
- package/src/plugins/builtin/earnings-ripple/client.ts +91 -18
- package/src/plugins/builtin/earnings-ripple/headless.ts +28 -8
- package/src/plugins/builtin/earnings-ripple/index.tsx +2 -2
- package/src/plugins/builtin/earnings-ripple/model.ts +97 -7
- package/src/plugins/builtin/earnings-ripple/pane.tsx +148 -23
- package/src/plugins/builtin/econ/index.tsx +1 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +2 -0
- package/src/plugins/builtin/equity-screener/headless.ts +2 -0
- package/src/plugins/builtin/estimate-revisions/headless.ts +1 -0
- package/src/plugins/builtin/executives/index.tsx +1 -0
- package/src/plugins/builtin/exposure/headless.ts +3 -1
- package/src/plugins/builtin/fear-greed/headless.ts +2 -0
- package/src/plugins/builtin/filing-events/index.tsx +1 -0
- package/src/plugins/builtin/futures-curve/client.ts +12 -3
- package/src/plugins/builtin/futures-curve/headless.ts +39 -10
- package/src/plugins/builtin/futures-curve/index.tsx +3 -3
- package/src/plugins/builtin/futures-curve/model.ts +188 -4
- package/src/plugins/builtin/futures-curve/pane.tsx +51 -9
- package/src/plugins/builtin/gpu/headless.ts +7 -3
- package/src/plugins/builtin/gpu/ladder.tsx +26 -30
- package/src/plugins/builtin/gpu/model.ts +27 -1
- package/src/plugins/builtin/holders/beneficial-client.ts +209 -0
- package/src/plugins/builtin/holders/beneficial-model.ts +279 -0
- package/src/plugins/builtin/holders/beneficial-report.ts +126 -0
- package/src/plugins/builtin/holders/beneficial-view.tsx +241 -0
- package/src/plugins/builtin/holders/headless.ts +139 -5
- package/src/plugins/builtin/holders/index.tsx +10 -3
- package/src/plugins/builtin/holders/pane.tsx +20 -3
- package/src/plugins/builtin/holders/report-header.ts +95 -0
- package/src/plugins/builtin/holders/table-model.ts +20 -5
- package/src/plugins/builtin/holders/thirteenf-match.ts +29 -1
- package/src/plugins/builtin/holders/types.ts +2 -1
- package/src/plugins/builtin/insider/headless.ts +2 -0
- package/src/plugins/builtin/ipo-calendar/headless.ts +1 -0
- package/src/plugins/builtin/iv-history/headless.ts +4 -1
- package/src/plugins/builtin/jobs/headless.ts +1 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -0
- package/src/plugins/builtin/macro-day/headless.ts +2 -0
- package/src/plugins/builtin/market-halts/index.tsx +1 -0
- package/src/plugins/builtin/market-heatmap/data.ts +2 -0
- package/src/plugins/builtin/market-heatmap/headless.ts +135 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +39 -46
- package/src/plugins/builtin/market-heatmap/model.ts +146 -0
- package/src/plugins/builtin/market-heatmap/portfolio.ts +23 -4
- package/src/plugins/builtin/market-movers/headless.ts +2 -0
- package/src/plugins/builtin/market-valuation/headless.ts +2 -0
- package/src/plugins/builtin/members/client.ts +20 -3
- package/src/plugins/builtin/members/headless.ts +5 -3
- package/src/plugins/builtin/members/model.ts +10 -2
- package/src/plugins/builtin/members/pane.tsx +13 -6
- package/src/plugins/builtin/mna/distress/headless.ts +1 -0
- package/src/plugins/builtin/mna/headless.ts +1 -0
- package/src/plugins/builtin/money-markets/headless.ts +1 -0
- package/src/plugins/builtin/news/headless.ts +2 -0
- package/src/plugins/builtin/news/wire/index.tsx +8 -1
- package/src/plugins/builtin/notes/cli.ts +14 -6
- package/src/plugins/builtin/notes/index.tsx +1 -0
- package/src/plugins/builtin/openfda/index.tsx +1 -0
- package/src/plugins/builtin/options-calculator/headless.ts +2 -0
- package/src/plugins/builtin/options-positioning/headless.ts +3 -0
- package/src/plugins/builtin/options-scenario/headless.ts +5 -0
- package/src/plugins/builtin/pe-band/client.ts +48 -3
- package/src/plugins/builtin/pe-band/headless.ts +25 -7
- package/src/plugins/builtin/pe-band/model.ts +92 -16
- package/src/plugins/builtin/pe-band/pane.tsx +33 -16
- package/src/plugins/builtin/perps/client.ts +50 -3
- package/src/plugins/builtin/perps/headless.ts +144 -16
- package/src/plugins/builtin/perps/index.tsx +6 -6
- package/src/plugins/builtin/perps/model.ts +189 -12
- package/src/plugins/builtin/perps/pane.tsx +248 -47
- package/src/plugins/builtin/portfolio-list/allocation.ts +245 -0
- package/src/plugins/builtin/portfolio-list/cached-account.ts +33 -0
- package/src/plugins/builtin/portfolio-list/cell-version.ts +19 -1
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +328 -29
- package/src/plugins/builtin/portfolio-list/cli/render.ts +230 -39
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +67 -2
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +35 -31
- package/src/plugins/builtin/portfolio-list/column-values.ts +124 -47
- package/src/plugins/builtin/portfolio-list/headless.ts +98 -16
- package/src/plugins/builtin/portfolio-list/index.tsx +14 -3
- package/src/plugins/builtin/portfolio-list/live-valuation.ts +7 -5
- package/src/plugins/builtin/portfolio-list/mutations.ts +55 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +6 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +47 -13
- package/src/plugins/builtin/portfolio-list/settings.ts +33 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +24 -1
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +26 -7
- package/src/plugins/builtin/power/headless.ts +3 -1
- package/src/plugins/builtin/rate-path/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/relative-rotation/headless.ts +2 -0
- package/src/plugins/builtin/research/analyst-headless.ts +3 -0
- package/src/plugins/builtin/research/events-headless.ts +2 -0
- package/src/plugins/builtin/research/headless.ts +2 -0
- package/src/plugins/builtin/research/index.tsx +1 -0
- package/src/plugins/builtin/research/relative-valuation-headless.ts +10 -5
- package/src/plugins/builtin/research/relative-valuation-model.ts +11 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +3 -3
- package/src/plugins/builtin/research-search/index.tsx +1 -0
- package/src/plugins/builtin/revenue-breakdown/headless.ts +2 -0
- package/src/plugins/builtin/reverse-dcf/headless.ts +2 -0
- package/src/plugins/builtin/risk-factors/headless.ts +2 -0
- package/src/plugins/builtin/scanner/flow-history.ts +2 -13
- package/src/plugins/builtin/scanner/headless.ts +14 -0
- package/src/plugins/builtin/seasonality/headless.ts +2 -0
- package/src/plugins/builtin/sec/command-bar-search.ts +14 -4
- package/src/plugins/builtin/sec/filing-content.ts +42 -17
- package/src/plugins/builtin/sec/forms.ts +12 -0
- package/src/plugins/builtin/sec/headless.ts +2 -0
- package/src/plugins/builtin/sectors/client.ts +1 -0
- package/src/plugins/builtin/sectors/headless.ts +9 -1
- package/src/plugins/builtin/sectors/sector-model.ts +2 -0
- package/src/plugins/builtin/shared/crypto-pair.ts +14 -0
- package/src/plugins/builtin/shared/locked-rows.tsx +1 -1
- package/src/plugins/builtin/shared/report-freshness.ts +57 -0
- package/src/plugins/builtin/shared/research-cells.tsx +4 -9
- package/src/plugins/builtin/short-interest/headless.ts +2 -0
- package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
- package/src/plugins/builtin/short-volume/headless.ts +1 -0
- package/src/plugins/builtin/social-mentions/headless.ts +1 -0
- package/src/plugins/builtin/supply-chain/headless.ts +7 -1
- package/src/plugins/builtin/supply-chain/index.tsx +10 -1
- package/src/plugins/builtin/supply-chain/model.ts +27 -2
- package/src/plugins/builtin/supply-chain/pane.tsx +71 -32
- package/src/plugins/builtin/themes/headless.ts +3 -0
- package/src/plugins/builtin/thirteenf/headless.ts +110 -25
- package/src/plugins/builtin/thirteenf/report-notes.ts +149 -0
- package/src/plugins/builtin/ticker-detail/headless.ts +7 -1
- package/src/plugins/builtin/ticker-detail/index.tsx +1 -0
- package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -2
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +26 -26
- package/src/plugins/builtin/time-sales/headless.ts +5 -1
- package/src/plugins/builtin/treasury-auctions/headless.ts +1 -0
- package/src/plugins/builtin/volatility/headless.ts +6 -1
- package/src/plugins/builtin/world-indices/headless.ts +2 -0
- package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +157 -0
- package/src/plugins/builtin/world-venue-map/basemap.ts +214 -0
- package/src/plugins/builtin/world-venue-map/client.ts +45 -0
- package/src/plugins/builtin/world-venue-map/entity-detail.tsx +147 -0
- package/src/plugins/builtin/world-venue-map/entity-table.ts +59 -0
- package/src/plugins/builtin/world-venue-map/evidence.ts +77 -0
- package/src/plugins/builtin/world-venue-map/feed.ts +293 -0
- package/src/plugins/builtin/world-venue-map/geo-draw.tsx +248 -0
- package/src/plugins/builtin/world-venue-map/geo-series.ts +142 -0
- package/src/plugins/builtin/world-venue-map/geo-svg.tsx +447 -0
- package/src/plugins/builtin/world-venue-map/headless.ts +146 -0
- package/src/plugins/builtin/world-venue-map/ids.ts +1 -0
- package/src/plugins/builtin/world-venue-map/index.ts +67 -4
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +552 -0
- package/src/plugins/builtin/world-venue-map/layers.ts +348 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +220 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +381 -45
- package/src/plugins/builtin/world-venue-map/model.ts +7 -4
- package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +199 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/world-50m.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +28 -43
- package/src/plugins/builtin/world-venue-map/use-geo.ts +55 -0
- package/src/plugins/builtin/world-venue-map/venue-header.tsx +32 -0
- package/src/plugins/builtin/yield-curve/headless.ts +16 -0
- package/src/plugins/loader.ts +13 -1
- package/src/plugins/registry/pane-settings.ts +1 -0
- package/src/renderers/dom/build-assets.ts +81 -3
- package/src/renderers/dom/data-table/row.tsx +2 -0
- package/src/renderers/dom/desktop/controls.tsx +7 -3
- package/src/renderers/dom/desktop/list-view.tsx +35 -10
- package/src/renderers/dom/desktop/popover.tsx +73 -14
- package/src/renderers/dom/host/input.tsx +14 -3
- package/src/renderers/dom/styles.css +6 -7
- package/src/renderers/electrobun/view/backend-rpc.ts +10 -4
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
- package/src/renderers/electrobun/view/rpc-timeout.ts +14 -3
- package/src/renderers/opentui/host.tsx +5 -0
- package/src/sources/gloomberb-cloud/index.ts +25 -4
- package/src/sources/gloomberb-cloud/normalizers.ts +27 -3
- package/src/sources/provider-router/cached-routes.ts +4 -1
- package/src/sources/provider-router/financials.ts +8 -7
- package/src/sources/provider-router/history.ts +15 -10
- package/src/sources/sec-registrant.ts +75 -0
- package/src/state/app/context/index.tsx +24 -8
- package/src/state/hooks/quote-streaming.ts +41 -26
- package/src/sync/core-contributors.ts +4 -0
- package/src/telemetry/crash-reports.ts +4 -1
- package/src/test-support/cli-context.ts +10 -2
- package/src/theme/heat-colors.ts +266 -84
- package/src/time-series/resolve.ts +1 -1
- package/src/time-series/use-chart-resolution.ts +1 -1
- package/src/types/context-menu.ts +6 -0
- package/src/types/data-provider.ts +2 -0
- package/src/types/financials.ts +3 -0
- package/src/types/headless.ts +79 -2
- package/src/types/plugin.ts +20 -1
- package/src/types/ticker.ts +15 -0
- package/src/ui/host.tsx +14 -0
- package/src/updater/index.ts +9 -11
- package/src/utils/async-deadline.ts +21 -0
- package/src/utils/cli-output.ts +5 -1
- package/src/utils/crypto-pair.ts +41 -0
- package/src/utils/currency-units.ts +14 -0
- package/src/utils/depositary-receipt.ts +26 -0
- package/src/utils/exchanges.ts +72 -0
- package/src/utils/fire-and-forget.ts +24 -0
- package/src/utils/format.ts +13 -0
- package/src/utils/price-earnings.ts +70 -3
- package/src/utils/price-history.ts +58 -13
- package/src/utils/rpc-timeout-error.ts +27 -0
- package/src/utils/sec.ts +13 -1
- package/src/utils/utc-time.ts +79 -0
|
@@ -14,6 +14,17 @@ import type { PricePoint } from "../../../types/financials";
|
|
|
14
14
|
import { resolveDatedReturns, type DatedReturn } from "./metrics";
|
|
15
15
|
import { qualifySharpeCadence } from "./sharpe-cadence";
|
|
16
16
|
import { evidenceDay } from "./risk-evidence";
|
|
17
|
+
import { FX_LIVE_RATE_MAX_DEVIATION } from "../../../market-data/coordinator/fx-legs";
|
|
18
|
+
import {
|
|
19
|
+
convertClosesToUsd,
|
|
20
|
+
FX_DISAGREES,
|
|
21
|
+
FX_NO_PAIR,
|
|
22
|
+
FX_UNAVAILABLE,
|
|
23
|
+
riskConversion,
|
|
24
|
+
validateFxHistory,
|
|
25
|
+
type FxCloses,
|
|
26
|
+
type RiskConversion,
|
|
27
|
+
} from "./risk-fx";
|
|
17
28
|
|
|
18
29
|
export interface RiskInstrument {
|
|
19
30
|
symbol: string;
|
|
@@ -40,6 +51,11 @@ interface RiskMarketHistory {
|
|
|
40
51
|
closeMark?: { price: number; date: string; currency: string } | null;
|
|
41
52
|
/** The listing's own price when it does not quote in USD: it values a left-out holding, never a return. */
|
|
42
53
|
listing?: { price: number; currency: string } | null;
|
|
54
|
+
/**
|
|
55
|
+
* Set when the listing quotes in another currency and its closes were
|
|
56
|
+
* restated in USD at same-date daily FX closes; quote and closeMark are then in USD.
|
|
57
|
+
*/
|
|
58
|
+
converted?: { currency: string; fxAsOf: string } | null;
|
|
43
59
|
error: string | null;
|
|
44
60
|
}
|
|
45
61
|
export interface RiskMarketSnapshot {
|
|
@@ -84,7 +100,7 @@ const message = (error: unknown) =>
|
|
|
84
100
|
export const portfolioRiskCache = createPluginCache<RiskMarketSnapshot>({
|
|
85
101
|
kind: "portfolio-risk",
|
|
86
102
|
source: "gloom-cloud",
|
|
87
|
-
schemaVersion:
|
|
103
|
+
schemaVersion: 4,
|
|
88
104
|
policy: { staleMs: 2 * 60_000, expireMs: 24 * 60 * 60_000 },
|
|
89
105
|
});
|
|
90
106
|
type RiskCloudClient = Pick<
|
|
@@ -96,10 +112,11 @@ function validateRiskQuote(
|
|
|
96
112
|
quote: CloudQuotePayload | null,
|
|
97
113
|
instrument: RiskInstrument,
|
|
98
114
|
now = new Date(),
|
|
115
|
+
currency = "USD",
|
|
99
116
|
): asserts quote is CloudQuotePayload {
|
|
100
117
|
// A stale quote still establishes listing identity; it is not used as a mark.
|
|
101
|
-
if (!quote || quote.currency !==
|
|
102
|
-
throw new Error("Current USD listing identity unavailable");
|
|
118
|
+
if (!quote || quote.currency !== currency)
|
|
119
|
+
throw new Error(currency === "USD" ? "Current USD listing identity unavailable" : "Current listing identity unavailable");
|
|
103
120
|
if (normalizeSymbol(quote.symbol) !== normalizeSymbol(instrument.symbol))
|
|
104
121
|
throw new Error("Quote symbol differs from the requested holding");
|
|
105
122
|
const exchange = canonicalExchange(
|
|
@@ -122,16 +139,23 @@ function validateRiskQuote(
|
|
|
122
139
|
throw new Error("Current quote price or timestamp unavailable");
|
|
123
140
|
}
|
|
124
141
|
|
|
125
|
-
/**
|
|
142
|
+
/**
|
|
143
|
+
* Daily Cloud observations, currency and listing metadata must agree before
|
|
144
|
+
* risk math. With a conversion the listing quotes in that currency and its
|
|
145
|
+
* closes are restated in USD at same-date FX closes on the basket's calendar.
|
|
146
|
+
*/
|
|
126
147
|
export function validateRiskHistory(
|
|
127
148
|
response: CloudMarketResponse<CloudPricePointPayload[]>,
|
|
128
149
|
instrument: RiskInstrument,
|
|
129
150
|
quote: CloudQuotePayload | null,
|
|
130
151
|
now = new Date(),
|
|
152
|
+
conversion?: { listingCurrency: string; divisor: number; fx: FxCloses },
|
|
131
153
|
): {
|
|
132
154
|
returns: DatedReturn[];
|
|
133
155
|
asOf: string;
|
|
134
156
|
closeMark: { price: number; date: string; currency: string } | null;
|
|
157
|
+
/** Latest completed close in the returns' currency, whatever marks the holding. */
|
|
158
|
+
latestClose: { price: number; date: string };
|
|
135
159
|
} {
|
|
136
160
|
if (
|
|
137
161
|
response.status !== "success" ||
|
|
@@ -145,9 +169,10 @@ export function validateRiskHistory(
|
|
|
145
169
|
response.providerMeta?.currency ??
|
|
146
170
|
quote?.currency ??
|
|
147
171
|
(US_LISTING_VENUES.has(canonicalExchange(instrument.exchange)) ? "USD" : null);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
172
|
+
const expectedCurrency = conversion?.listingCurrency ?? "USD";
|
|
173
|
+
if (quote) validateRiskQuote(quote, instrument, now, expectedCurrency);
|
|
174
|
+
else if (historyCurrency !== expectedCurrency)
|
|
175
|
+
throw new Error(conversion ? "Current listing identity unavailable" : "Current USD listing identity unavailable");
|
|
151
176
|
const exchange = canonicalExchange(
|
|
152
177
|
instrument.exchange || quote?.listingExchangeName || "",
|
|
153
178
|
);
|
|
@@ -209,23 +234,37 @@ export function validateRiskHistory(
|
|
|
209
234
|
...point,
|
|
210
235
|
date: new Date(point.date),
|
|
211
236
|
}));
|
|
212
|
-
const
|
|
213
|
-
if (
|
|
237
|
+
const local = resolveDatedReturns(prices);
|
|
238
|
+
if (local.integrity)
|
|
214
239
|
throw new Error("Daily history has inconsistent OHLC observations");
|
|
215
|
-
|
|
216
|
-
|
|
240
|
+
// A converted series is on the NYSE calendar the basket and SPY share.
|
|
241
|
+
const series = conversion
|
|
242
|
+
? convertClosesToUsd(
|
|
243
|
+
points.map((point) => ({ date: new Date(point.date).toISOString().slice(0, 10), close: point.close })),
|
|
244
|
+
conversion.fx,
|
|
245
|
+
conversion.divisor,
|
|
246
|
+
)
|
|
247
|
+
: prices;
|
|
248
|
+
const returns = conversion ? resolveDatedReturns(series).returns : local.returns;
|
|
249
|
+
const cadence = qualifySharpeCadence(returns, [
|
|
250
|
+
{ symbol: instrument.symbol, exchange: conversion ? "NYSE" : exchange, history: series },
|
|
217
251
|
]);
|
|
218
252
|
if (!cadence.supported)
|
|
219
253
|
throw new Error(cadence.reason ?? "Daily session cadence unavailable");
|
|
220
|
-
const lastPoint =
|
|
254
|
+
const lastPoint = series.at(-1)!;
|
|
255
|
+
const latestClose = {
|
|
256
|
+
price: lastPoint.close,
|
|
257
|
+
date: lastPoint.date.toISOString().slice(0, 10),
|
|
258
|
+
};
|
|
221
259
|
return {
|
|
222
|
-
returns
|
|
223
|
-
asOf:
|
|
260
|
+
returns,
|
|
261
|
+
asOf: latestClose.date,
|
|
224
262
|
// A missing or stale quote is not a current mark; the latest dated close is.
|
|
225
263
|
closeMark:
|
|
226
264
|
quote && !quote.stale
|
|
227
265
|
? null
|
|
228
|
-
: {
|
|
266
|
+
: { ...latestClose, currency: conversion ? "USD" : historyCurrency! },
|
|
267
|
+
latestClose,
|
|
229
268
|
};
|
|
230
269
|
}
|
|
231
270
|
function validateFred(
|
|
@@ -269,10 +308,13 @@ const largestFirst = (
|
|
|
269
308
|
|
|
270
309
|
/**
|
|
271
310
|
* Quotes every holding, then requests daily history only for the factor
|
|
272
|
-
* proxies and the largest USD
|
|
273
|
-
* RISK_HISTORY_LIMIT.
|
|
274
|
-
*
|
|
275
|
-
*
|
|
311
|
+
* proxies and the largest holdings by current USD value that can enter the
|
|
312
|
+
* basket, up to RISK_HISTORY_LIMIT. A holding that quotes in another currency
|
|
313
|
+
* competes at its value at the current rate and, when requested, also loads
|
|
314
|
+
* its currency's daily FX history once per currency, so its closes convert to
|
|
315
|
+
* USD. Holdings with no FX pair, positions the caller marks unsupported and
|
|
316
|
+
* holdings past the limit get no history request; their entry says why. The
|
|
317
|
+
* signal stops the fetch: no request starts once it
|
|
276
318
|
* aborts, the ones in flight are cancelled, and the call rejects instead of
|
|
277
319
|
* returning a partial snapshot nobody is waiting for.
|
|
278
320
|
*/
|
|
@@ -314,6 +356,9 @@ export async function fetchPortfolioRiskMarket(
|
|
|
314
356
|
];
|
|
315
357
|
const quotes = new Map<string, CloudQuotePayload>();
|
|
316
358
|
const listings = new Map<string, { price: number; currency: string }>();
|
|
359
|
+
// Listings quoted in another currency that convert to USD, and why the others do not.
|
|
360
|
+
const conversions = new Map<string, RiskConversion>();
|
|
361
|
+
const foreignReasons = new Map<string, string>();
|
|
317
362
|
const warnings: string[] = [];
|
|
318
363
|
for (let start = 0; start < targets.length; start += 50) {
|
|
319
364
|
signal?.throwIfAborted();
|
|
@@ -339,11 +384,24 @@ export async function fetchPortfolioRiskMarket(
|
|
|
339
384
|
)
|
|
340
385
|
continue;
|
|
341
386
|
const id = riskInstrumentId(target);
|
|
342
|
-
// A holding quoted in another currency
|
|
343
|
-
// its
|
|
387
|
+
// A holding quoted in another currency enters the USD basket only
|
|
388
|
+
// through its daily closes restated at same-date FX closes.
|
|
344
389
|
if (!factors.has(id) && item.data.currency && item.data.currency !== "USD") {
|
|
345
390
|
if (Number.isFinite(item.data.price) && item.data.price > 0)
|
|
346
391
|
listings.set(id, { price: item.data.price, currency: item.data.currency });
|
|
392
|
+
if (entries.get(id)?.request.unsupported) continue;
|
|
393
|
+
const conversion = riskConversion(item.data.currency);
|
|
394
|
+
if (!conversion) {
|
|
395
|
+
foreignReasons.set(id, FX_NO_PAIR);
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
try {
|
|
399
|
+
validateRiskQuote(item.data, target, now, item.data.currency);
|
|
400
|
+
quotes.set(id, item.data);
|
|
401
|
+
conversions.set(id, conversion);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
foreignReasons.set(id, message(error));
|
|
404
|
+
}
|
|
347
405
|
continue;
|
|
348
406
|
}
|
|
349
407
|
try {
|
|
@@ -358,9 +416,55 @@ export async function fetchPortfolioRiskMarket(
|
|
|
358
416
|
warnings.push(`Holding marks: ${message(error)}`);
|
|
359
417
|
}
|
|
360
418
|
}
|
|
419
|
+
// Current rates value a foreign holding: its rank, its mark, and its size when left out.
|
|
420
|
+
const fxCurrencies = [
|
|
421
|
+
...new Set(
|
|
422
|
+
[...entries.values()].flatMap((row) => {
|
|
423
|
+
const currency = resolveCurrencyUnit(
|
|
424
|
+
listings.get(row.id)?.currency ?? (quotes.has(row.id) ? null : row.request.currency),
|
|
425
|
+
).currency;
|
|
426
|
+
return currency && currency !== "USD" ? [currency] : [];
|
|
427
|
+
}),
|
|
428
|
+
),
|
|
429
|
+
].slice(0, MAX_FX_CURRENCIES);
|
|
430
|
+
const fx = await Promise.allSettled(
|
|
431
|
+
fxCurrencies.map(async (currency) => {
|
|
432
|
+
const response = await client.getCloudExchangeRate(currency);
|
|
433
|
+
const rate = response.status === "success" || response.status === "partial"
|
|
434
|
+
? response.data?.rate
|
|
435
|
+
: undefined;
|
|
436
|
+
if (typeof rate !== "number" || !Number.isFinite(rate) || rate <= 0)
|
|
437
|
+
throw new Error(response.reasonCode ?? "Rate unavailable");
|
|
438
|
+
return [currency, rate] as const;
|
|
439
|
+
}),
|
|
440
|
+
);
|
|
441
|
+
signal?.throwIfAborted();
|
|
442
|
+
const fxRates: Record<string, number> = {};
|
|
443
|
+
for (const [index, result] of fx.entries()) {
|
|
444
|
+
if (result.status === "fulfilled") fxRates[result.value[0]] = result.value[1];
|
|
445
|
+
else {
|
|
446
|
+
if (isAccessDenied(result.reason)) throw result.reason;
|
|
447
|
+
warnings.push(`${fxCurrencies[index]} exchange rate: ${message(result.reason)}`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
/** The listing's quote in USD at the current rate; null when no rate converts it. */
|
|
451
|
+
const usdQuote = (id: string): CloudQuotePayload | null => {
|
|
452
|
+
const quote = quotes.get(id) ?? null,
|
|
453
|
+
conversion = conversions.get(id);
|
|
454
|
+
if (!quote || !conversion) return quote;
|
|
455
|
+
const rate = fxRates[conversion.currency];
|
|
456
|
+
return rate
|
|
457
|
+
? { ...quote, currency: "USD", price: (quote.price * rate) / conversion.divisor }
|
|
458
|
+
: null;
|
|
459
|
+
};
|
|
361
460
|
const candidates = quoted
|
|
362
|
-
.filter(
|
|
363
|
-
|
|
461
|
+
.filter(
|
|
462
|
+
(row) =>
|
|
463
|
+
!factors.has(row.id) &&
|
|
464
|
+
!row.request.unsupported &&
|
|
465
|
+
(!listings.has(row.id) || conversions.has(row.id)),
|
|
466
|
+
)
|
|
467
|
+
.map((row) => ({ ...row, rank: rankValue(row.request, usdQuote(row.id)) }))
|
|
364
468
|
.sort(largestFirst);
|
|
365
469
|
const requestedIds = new Set([
|
|
366
470
|
...factors.keys(),
|
|
@@ -377,6 +481,15 @@ export async function fetchPortfolioRiskMarket(
|
|
|
377
481
|
endDate = new Date(Date.parse(today) - 86_400_000)
|
|
378
482
|
.toISOString()
|
|
379
483
|
.slice(0, 10);
|
|
484
|
+
const historyParams = {
|
|
485
|
+
interval: "1day",
|
|
486
|
+
startDate,
|
|
487
|
+
// validateRiskHistory drops today's bar; ending on yesterday
|
|
488
|
+
// loses yesterday's close for most listings.
|
|
489
|
+
endDate: today,
|
|
490
|
+
outputsize: 1000,
|
|
491
|
+
rangeKey: "2Y",
|
|
492
|
+
} as const;
|
|
380
493
|
const histories: RiskMarketHistory[] = instruments.map(([id, instrument]) => {
|
|
381
494
|
const quote = quotes.get(id) ?? null,
|
|
382
495
|
listing = listings.get(id) ?? null;
|
|
@@ -389,13 +502,43 @@ export async function fetchPortfolioRiskMarket(
|
|
|
389
502
|
asOf: null,
|
|
390
503
|
error: requestedIds.has(id)
|
|
391
504
|
? null
|
|
392
|
-
: listing
|
|
393
|
-
?
|
|
505
|
+
: listing && !conversions.has(id)
|
|
506
|
+
? (foreignReasons.get(id) ??
|
|
507
|
+
(entries.get(id)?.request.unsupported ? "Outside the long USD equity basket" : FX_UNAVAILABLE))
|
|
394
508
|
: entries.get(id)?.request.unsupported
|
|
395
509
|
? "Outside the long USD equity basket"
|
|
396
510
|
: BEYOND_SIZE_REASON,
|
|
397
511
|
};
|
|
398
512
|
});
|
|
513
|
+
// One daily FX history per currency, shared by every holding that converts with it.
|
|
514
|
+
const fxHistories = new Map<string, Promise<FxCloses>>();
|
|
515
|
+
const fxHistory = (conversion: RiskConversion) => {
|
|
516
|
+
let loading = fxHistories.get(conversion.currency);
|
|
517
|
+
if (!loading) {
|
|
518
|
+
loading = (async () => {
|
|
519
|
+
const { symbol, exchange } = conversion.leg.instrument;
|
|
520
|
+
let closes: FxCloses;
|
|
521
|
+
try {
|
|
522
|
+
closes = validateFxHistory(
|
|
523
|
+
await client.getCloudHistory(symbol, exchange ?? "", historyParams, signal ? { signal } : undefined),
|
|
524
|
+
conversion.leg,
|
|
525
|
+
now,
|
|
526
|
+
);
|
|
527
|
+
} catch (error) {
|
|
528
|
+
if (isAccessDenied(error) || signal?.aborted) throw error;
|
|
529
|
+
throw new Error(message(error).startsWith("Foreign holdings") ? message(error) : FX_UNAVAILABLE);
|
|
530
|
+
}
|
|
531
|
+
// A latest close far from the current rate is a wrong or inverted pair, not a move.
|
|
532
|
+
const rate = fxRates[conversion.currency];
|
|
533
|
+
const latest = closes.closes.get(closes.asOf)!;
|
|
534
|
+
if (rate && Math.abs(latest / rate - 1) > FX_LIVE_RATE_MAX_DEVIATION)
|
|
535
|
+
throw new Error(FX_DISAGREES);
|
|
536
|
+
return closes;
|
|
537
|
+
})();
|
|
538
|
+
fxHistories.set(conversion.currency, loading);
|
|
539
|
+
}
|
|
540
|
+
return loading;
|
|
541
|
+
};
|
|
399
542
|
const pending = instruments.flatMap(([id], index) =>
|
|
400
543
|
requestedIds.has(id) ? [index] : [],
|
|
401
544
|
);
|
|
@@ -406,28 +549,40 @@ export async function fetchPortfolioRiskMarket(
|
|
|
406
549
|
signal?.throwIfAborted();
|
|
407
550
|
const index = pending[next++]!,
|
|
408
551
|
entry = histories[index]!,
|
|
552
|
+
conversion = conversions.get(instruments[index]![0]),
|
|
409
553
|
{ instrument, quote } = entry;
|
|
410
554
|
try {
|
|
555
|
+
const fxCloses = conversion ? await fxHistory(conversion) : null;
|
|
411
556
|
const response = await client.getCloudHistory(
|
|
412
557
|
instrument.symbol,
|
|
413
558
|
instrument.exchange || quote?.listingExchangeName || "",
|
|
414
|
-
|
|
415
|
-
interval: "1day",
|
|
416
|
-
startDate,
|
|
417
|
-
// validateRiskHistory drops today's bar; ending on yesterday
|
|
418
|
-
// loses yesterday's close for most listings.
|
|
419
|
-
endDate: today,
|
|
420
|
-
outputsize: 1000,
|
|
421
|
-
rangeKey: "2Y",
|
|
422
|
-
},
|
|
559
|
+
historyParams,
|
|
423
560
|
signal ? { signal } : undefined,
|
|
424
561
|
);
|
|
425
|
-
const resolved = validateRiskHistory(
|
|
562
|
+
const { latestClose, ...resolved } = validateRiskHistory(
|
|
426
563
|
response,
|
|
427
564
|
instrument,
|
|
428
565
|
quote,
|
|
429
566
|
now,
|
|
567
|
+
conversion && fxCloses
|
|
568
|
+
? { listingCurrency: conversion.listingCurrency, divisor: conversion.divisor, fx: fxCloses }
|
|
569
|
+
: undefined,
|
|
430
570
|
);
|
|
571
|
+
if (conversion && fxCloses) {
|
|
572
|
+
// A current quote marks at the current rate; without one, or without
|
|
573
|
+
// a rate, the latest close at its own date's FX close does.
|
|
574
|
+
const current = quote && !quote.stale ? usdQuote(instruments[index]![0]) : null;
|
|
575
|
+
histories[index] = {
|
|
576
|
+
...entry,
|
|
577
|
+
...resolved,
|
|
578
|
+
quote: current,
|
|
579
|
+
currency: "USD",
|
|
580
|
+
closeMark: current ? null : { ...latestClose, currency: "USD" },
|
|
581
|
+
converted: { currency: conversion.listingCurrency, fxAsOf: fxCloses.asOf },
|
|
582
|
+
error: null,
|
|
583
|
+
};
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
431
586
|
histories[index] = {
|
|
432
587
|
...entry,
|
|
433
588
|
currency: quote?.currency ?? resolved.closeMark?.currency ?? null,
|
|
@@ -441,18 +596,7 @@ export async function fetchPortfolioRiskMarket(
|
|
|
441
596
|
}
|
|
442
597
|
}),
|
|
443
598
|
);
|
|
444
|
-
|
|
445
|
-
const fxCurrencies = [
|
|
446
|
-
...new Set(
|
|
447
|
-
[...entries.values()].flatMap((row) => {
|
|
448
|
-
const currency = resolveCurrencyUnit(
|
|
449
|
-
listings.get(row.id)?.currency ?? (quotes.has(row.id) ? null : row.request.currency),
|
|
450
|
-
).currency;
|
|
451
|
-
return currency && currency !== "USD" ? [currency] : [];
|
|
452
|
-
}),
|
|
453
|
-
),
|
|
454
|
-
].slice(0, MAX_FX_CURRENCIES);
|
|
455
|
-
const [, fred, fx] = await Promise.all([
|
|
599
|
+
const [, fred] = await Promise.all([
|
|
456
600
|
historyWork,
|
|
457
601
|
Promise.allSettled(
|
|
458
602
|
["DGS10", "VIXCLS"].map(async (id) =>
|
|
@@ -467,17 +611,6 @@ export async function fetchPortfolioRiskMarket(
|
|
|
467
611
|
),
|
|
468
612
|
),
|
|
469
613
|
),
|
|
470
|
-
Promise.allSettled(
|
|
471
|
-
fxCurrencies.map(async (currency) => {
|
|
472
|
-
const response = await client.getCloudExchangeRate(currency);
|
|
473
|
-
const rate = response.status === "success" || response.status === "partial"
|
|
474
|
-
? response.data?.rate
|
|
475
|
-
: undefined;
|
|
476
|
-
if (typeof rate !== "number" || !Number.isFinite(rate) || rate <= 0)
|
|
477
|
-
throw new Error(response.reasonCode ?? "Rate unavailable");
|
|
478
|
-
return [currency, rate] as const;
|
|
479
|
-
}),
|
|
480
|
-
),
|
|
481
614
|
]);
|
|
482
615
|
signal?.throwIfAborted();
|
|
483
616
|
for (const [index, result] of fred.entries()) {
|
|
@@ -488,14 +621,6 @@ export async function fetchPortfolioRiskMarket(
|
|
|
488
621
|
);
|
|
489
622
|
}
|
|
490
623
|
}
|
|
491
|
-
const fxRates: Record<string, number> = {};
|
|
492
|
-
for (const [index, result] of fx.entries()) {
|
|
493
|
-
if (result.status === "fulfilled") fxRates[result.value[0]] = result.value[1];
|
|
494
|
-
else {
|
|
495
|
-
if (isAccessDenied(result.reason)) throw result.reason;
|
|
496
|
-
warnings.push(`${fxCurrencies[index]} exchange rate: ${message(result.reason)}`);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
624
|
return {
|
|
500
625
|
histories,
|
|
501
626
|
yields: fred[0]!.status === "fulfilled" ? fred[0]!.value : null,
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { CloudMarketResponse, CloudPricePointPayload } from "../../../api-client/types";
|
|
2
|
+
import { fxLegForCurrency, type FxLeg } from "../../../market-data/coordinator/fx-legs";
|
|
3
|
+
import { dailyFxCloses, FxHistoryError, type FxCloses } from "../../../market-data/fx-closes";
|
|
4
|
+
import { getPublishedUsEquityCalendarDay } from "../../../market-data/published-us-sessions";
|
|
5
|
+
import type { PricePoint } from "../../../types/financials";
|
|
6
|
+
import { resolveCurrencyUnit } from "../../../utils/currency-units";
|
|
7
|
+
|
|
8
|
+
export type { FxCloses };
|
|
9
|
+
|
|
10
|
+
/** How a listing quoted in another currency is restated in USD. */
|
|
11
|
+
export interface RiskConversion {
|
|
12
|
+
/** The listing currency as quoted, sub-units included (GBp, ZAc). */
|
|
13
|
+
listingCurrency: string;
|
|
14
|
+
/** The currency the FX pair prices (GBP for GBp). */
|
|
15
|
+
currency: string;
|
|
16
|
+
/** Listing units per currency unit: 100 for pence. */
|
|
17
|
+
divisor: number;
|
|
18
|
+
leg: FxLeg;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const FX_UNAVAILABLE = "Foreign holdings: daily FX closes unavailable";
|
|
22
|
+
export const FX_NO_PAIR = "Foreign holdings: no daily FX pair for this currency";
|
|
23
|
+
export const FX_STALE = "Foreign holdings: daily FX history is stale";
|
|
24
|
+
export const FX_GAP = "Foreign holdings: daily FX history has gaps on the holding's dates";
|
|
25
|
+
export const FX_DISAGREES = "Foreign holdings: daily FX closes disagree with the current rate";
|
|
26
|
+
export const LOCAL_GAP = "Foreign holdings: local daily history has a gap longer than a market holiday";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Longest run of calendar days a listing may go without a close while US
|
|
30
|
+
* sessions continue (Golden Week, Lunar New Year, national holidays). A longer
|
|
31
|
+
* silence is missing data, not a holiday, and is never carried across.
|
|
32
|
+
*/
|
|
33
|
+
const MAX_LOCAL_CLOSED_DAYS = 10;
|
|
34
|
+
/** The calendar the basket, SPY and the factor proxies share. */
|
|
35
|
+
const BASKET_CALENDAR = "NYSE";
|
|
36
|
+
const DAY_MS = 86_400_000;
|
|
37
|
+
|
|
38
|
+
/** The pair that restates a listing currency in USD, or null when there is none. */
|
|
39
|
+
export function riskConversion(listingCurrency: string): RiskConversion | null {
|
|
40
|
+
const unit = resolveCurrencyUnit(listingCurrency);
|
|
41
|
+
if (!unit.currency || unit.currency === "USD") return null;
|
|
42
|
+
const leg = fxLegForCurrency(unit.currency);
|
|
43
|
+
return leg ? { listingCurrency, currency: unit.currency, divisor: unit.divisor, leg } : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Completed daily closes of the pair as USD per unit, today's still-trading bar left out. */
|
|
47
|
+
export function validateFxHistory(
|
|
48
|
+
response: CloudMarketResponse<CloudPricePointPayload[]>,
|
|
49
|
+
leg: FxLeg,
|
|
50
|
+
now = new Date(),
|
|
51
|
+
): FxCloses {
|
|
52
|
+
try {
|
|
53
|
+
return dailyFxCloses(response, leg, now);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (error instanceof FxHistoryError) throw new Error(error.problem === "stale" ? FX_STALE : FX_UNAVAILABLE);
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Restates a listing's completed local closes in USD on every NYSE session
|
|
62
|
+
* they span, each at the FX close of that same date, so the converted series
|
|
63
|
+
* shares the basket's calendar and its returns pair with SPY and the other
|
|
64
|
+
* holdings interval for interval.
|
|
65
|
+
*
|
|
66
|
+
* On a US session the listing did not trade (its own holiday), the holding is
|
|
67
|
+
* worth its last local close at that day's FX close: its USD value moves with
|
|
68
|
+
* the currency alone, as it would in the account. A US session with no FX
|
|
69
|
+
* close, or a local silence longer than any market holiday, rejects the whole
|
|
70
|
+
* series instead of bridging it. The series ends at the earlier of the last
|
|
71
|
+
* local close and the last FX close, so no close is carried past the data.
|
|
72
|
+
*/
|
|
73
|
+
export function convertClosesToUsd(
|
|
74
|
+
local: readonly { date: string; close: number }[],
|
|
75
|
+
fx: FxCloses,
|
|
76
|
+
divisor: number,
|
|
77
|
+
): PricePoint[] {
|
|
78
|
+
const points = [...local]
|
|
79
|
+
.map((point) => ({ date: point.date.slice(0, 10), close: point.close }))
|
|
80
|
+
.sort((left, right) => left.date.localeCompare(right.date));
|
|
81
|
+
if (points.length < 2) throw new Error("Daily history unavailable");
|
|
82
|
+
const start = points[0]!.date > fx.first ? points[0]!.date : fx.first;
|
|
83
|
+
const end = points.at(-1)!.date < fx.asOf ? points.at(-1)!.date : fx.asOf;
|
|
84
|
+
const converted: PricePoint[] = [];
|
|
85
|
+
let cursor = 0;
|
|
86
|
+
for (let time = Date.parse(`${start}T00:00:00Z`); time <= Date.parse(`${end}T00:00:00Z`); time += DAY_MS) {
|
|
87
|
+
const date = new Date(time).toISOString().slice(0, 10);
|
|
88
|
+
const day = getPublishedUsEquityCalendarDay(BASKET_CALENDAR, date);
|
|
89
|
+
if (day == null) throw new Error("Daily calendar unavailable");
|
|
90
|
+
if (day !== "session") continue;
|
|
91
|
+
while (cursor + 1 < points.length && points[cursor + 1]!.date <= date) cursor += 1;
|
|
92
|
+
const last = points[cursor]!;
|
|
93
|
+
if (last.date > date) continue;
|
|
94
|
+
if (time - Date.parse(`${last.date}T00:00:00Z`) > MAX_LOCAL_CLOSED_DAYS * DAY_MS) throw new Error(LOCAL_GAP);
|
|
95
|
+
const rate = fx.closes.get(date);
|
|
96
|
+
if (rate == null) throw new Error(FX_GAP);
|
|
97
|
+
const close = (last.close * rate) / divisor;
|
|
98
|
+
converted.push({ date: new Date(time), open: close, high: close, low: close, close });
|
|
99
|
+
}
|
|
100
|
+
if (converted.length < 2) throw new Error("Daily history unavailable");
|
|
101
|
+
return converted;
|
|
102
|
+
}
|
|
@@ -62,6 +62,7 @@ function coverageSummary(coverage: RiskCoverage, limit = Infinity) {
|
|
|
62
62
|
coveredValue: coverage.coveredValue,
|
|
63
63
|
holdings: coverage.holdings,
|
|
64
64
|
covered: coverage.covered,
|
|
65
|
+
convertedFromLocalCurrency: coverage.converted,
|
|
65
66
|
leftOut: coverage.leftOut
|
|
66
67
|
.slice(0, limit)
|
|
67
68
|
.map((row) => ({ symbol: row.symbol, reason: row.reason, share: row.share })),
|
|
@@ -69,12 +70,14 @@ function coverageSummary(coverage: RiskCoverage, limit = Infinity) {
|
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
/** One line for the whole coverage in place of a
|
|
73
|
-
function
|
|
73
|
+
/** One line for the whole coverage in place of a note per left-out holding. */
|
|
74
|
+
function coverageLine(coverage: RiskCoverage | undefined): { error?: string; note?: string } {
|
|
74
75
|
const shortfall = riskCoverageShortfall(coverage);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return
|
|
76
|
+
// No estimate at all is a failure; an estimate that leaves holdings out is a caveat.
|
|
77
|
+
if (shortfall) return { error: `Basket estimates unavailable: ${shortfall.title} ${shortfall.message}`.trim() };
|
|
78
|
+
return coverage?.leftOut.length
|
|
79
|
+
? { note: `Basket ${riskCoverageText(coverage)}; metadata.coverage lists each with its reason.` }
|
|
80
|
+
: {};
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
/** One view, its strongest or first rows, and the portfolio's totals instead of the model. */
|
|
@@ -101,6 +104,7 @@ function compactRiskRows(model: PortfolioRiskModel, view: RiskView): { rows: Ris
|
|
|
101
104
|
|
|
102
105
|
export const portfolioRiskHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
103
106
|
shape: "bundle",
|
|
107
|
+
freshness: { source: "Local portfolio and Gloom Cloud", status: "not-a-feed", basis: "risk model on daily closes" },
|
|
104
108
|
argument: {
|
|
105
109
|
kind: "free-text",
|
|
106
110
|
optional: true,
|
|
@@ -115,7 +119,7 @@ export const portfolioRiskHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
115
119
|
"Explicit local evidence for account returns and attribution",
|
|
116
120
|
],
|
|
117
121
|
limitations: [
|
|
118
|
-
`Fixed-current-weight USD equity basket over the holdings that qualify, largest ${RISK_HISTORY_LIMIT} by value; metadata.coverage states the covered share of market value and each holding left out`,
|
|
122
|
+
`Fixed-current-weight USD equity basket over the holdings that qualify, largest ${RISK_HISTORY_LIMIT} by value; foreign listings convert to USD at daily FX closes; metadata.coverage states the covered share of market value and each holding left out`,
|
|
119
123
|
"No account performance inferred",
|
|
120
124
|
"ETF price-return factor proxies",
|
|
121
125
|
"Historical percentiles need 20 rolling samples",
|
|
@@ -237,9 +241,12 @@ export const portfolioRiskHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
237
241
|
: view === "factors"
|
|
238
242
|
? covered && model.factors.every((row) => row.value != null)
|
|
239
243
|
: model.complete;
|
|
244
|
+
// A basket view with too little covered shows no estimate, and the report says why rather than leaving dashes.
|
|
245
|
+
const shortfall = BASKET_VIEWS.has(view as RiskView) ? coverageLine(model.coverage).error : undefined;
|
|
240
246
|
return {
|
|
241
247
|
complete,
|
|
242
|
-
errors: [...
|
|
248
|
+
errors: [...(shortfall ? [shortfall] : []), ...model.warnings],
|
|
249
|
+
notes: [...riskCoverageNotices(model.coverage), ...model.notes],
|
|
243
250
|
sections: RISK_VIEWS.map((view) => ({
|
|
244
251
|
title: view,
|
|
245
252
|
columns: RISK_COLUMNS,
|
|
@@ -253,17 +260,16 @@ export const portfolioRiskHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
253
260
|
if (!model?.rows) return result;
|
|
254
261
|
const view = isRiskView(args.options.view) ? args.options.view : "risk";
|
|
255
262
|
const { rows, notices } = compactRiskRows(model, view);
|
|
256
|
-
// Each left-out holding is in metadata.coverage; the
|
|
263
|
+
// Each left-out holding is in metadata.coverage; the notes keep one line for all of them.
|
|
257
264
|
const perHolding = new Set(riskCoverageNotices(model.coverage));
|
|
258
265
|
const basketView = BASKET_VIEWS.has(view);
|
|
259
|
-
const
|
|
260
|
-
const errors = [
|
|
261
|
-
|
|
262
|
-
...(result.errors ?? []).filter((error) => !perHolding.has(error)),
|
|
263
|
-
];
|
|
266
|
+
const coverage = basketView ? coverageLine(model.coverage) : {};
|
|
267
|
+
const errors = [...new Set([...(coverage.error ? [coverage.error] : []), ...(result.errors ?? [])])];
|
|
268
|
+
const notes = [...(coverage.note ? [coverage.note] : []), ...(result.notes ?? []).filter((note) => !perHolding.has(note))];
|
|
264
269
|
return {
|
|
265
270
|
complete: result.complete,
|
|
266
271
|
...(errors.length ? { errors } : {}),
|
|
272
|
+
...(notes.length ? { notes } : {}),
|
|
267
273
|
sections: [{ title: view, columns: RISK_COLUMNS, rows: rows.map(displayRow) }],
|
|
268
274
|
metadata: {
|
|
269
275
|
portfolio: {
|