gloomberb 0.16.3 → 0.16.4
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/package.json +4 -1
- package/src/api-client/askg.ts +43 -0
- package/src/api-client/auth.ts +15 -0
- package/src/api-client/beneficial-owners.ts +4 -0
- package/src/api-client/chat.ts +19 -0
- package/src/api-client/company-kpis.ts +14 -2
- package/src/api-client/data.ts +34 -16
- package/src/api-client/index.ts +12 -0
- package/src/api-client/normalizers.ts +27 -0
- package/src/api-client/paths.ts +88 -23
- package/src/api-client/request.ts +2 -1
- package/src/api-client/research-activity.ts +31 -0
- package/src/api-client/socket.ts +39 -8
- package/src/api-client/types.ts +57 -1
- package/src/api-client/upgrade-experiment.ts +90 -0
- package/src/app/global-shortcuts.ts +11 -1
- package/src/app/keybindings/actions.ts +10 -0
- package/src/app/pane-runtime/index.tsx +8 -0
- package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
- package/src/app/pane-runtime/plugin-bindings.ts +65 -25
- package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
- package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
- package/src/app/runtime/desktop-deeplink.ts +5 -0
- package/src/app/star-prompt/model.ts +98 -0
- package/src/app/star-prompt/runtime.ts +41 -0
- package/src/app/star-prompt/status-line.tsx +190 -0
- package/src/app.tsx +13 -7
- package/src/architecture/core-builtin-allowlist.json +70 -0
- package/src/brokers/signed-in/adapter.ts +33 -2
- package/src/brokers/signed-in/client.ts +10 -0
- package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
- package/src/brokers/signed-in/sign-in.ts +62 -5
- package/src/capabilities/registry.ts +8 -1
- package/src/capabilities/types.ts +2 -0
- package/src/cli/commands/crypto-hints.ts +12 -4
- package/src/cli/commands/market.ts +359 -99
- package/src/cli/commands/options-chain.ts +172 -0
- package/src/cli/commands/options-leaps.ts +422 -0
- package/src/cli/commands/overview.ts +164 -26
- package/src/cli/commands/search.ts +29 -8
- package/src/cli/commands/system.ts +99 -19
- package/src/cli/commands/themes.ts +64 -0
- package/src/cli/commands/ticker.ts +62 -30
- package/src/cli/desktop-pane-shot.ts +97 -16
- package/src/cli/errors.ts +25 -2
- package/src/cli/freshness.ts +7 -4
- package/src/cli/fx-pair.ts +57 -0
- package/src/cli/help.ts +14 -1
- package/src/cli/index-roots.ts +51 -0
- package/src/cli/index.ts +9 -6
- package/src/cli/listing-arg.ts +211 -3
- package/src/cli/not-a-ticker.ts +37 -0
- package/src/cli/options.ts +50 -6
- package/src/cli/pane-functions/capabilities.ts +8 -2
- package/src/cli/pane-functions/catalog.ts +13 -2
- package/src/cli/pane-functions/data.ts +36 -5
- package/src/cli/pane-functions/dom.ts +21 -13
- package/src/cli/pane-functions/freshness.ts +205 -26
- package/src/cli/pane-functions/function-help.ts +67 -22
- package/src/cli/pane-functions/headless.ts +5 -4
- package/src/cli/pane-functions/index.ts +3 -0
- package/src/cli/pane-functions/market-session.ts +133 -0
- package/src/cli/pane-functions/options.ts +15 -4
- package/src/cli/pane-functions/report-notices.ts +63 -0
- package/src/cli/pane-functions/report.ts +4 -0
- package/src/cli/pane-functions/resolver.ts +4 -6
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +105 -23
- package/src/cli/registry.ts +2 -1
- package/src/cli/result.ts +50 -13
- package/src/cli/row-window.ts +76 -0
- package/src/cli/scroll-truncation.ts +34 -0
- package/src/components/chart/composite/composite-chart.tsx +43 -4
- package/src/components/chart/composite/legend.tsx +39 -6
- package/src/components/chart/composite/rasterizer.ts +64 -4
- package/src/components/chart/composite/series-cues.ts +71 -0
- package/src/components/chart/composite/text-renderer.ts +16 -8
- package/src/components/chart/composite/types.ts +3 -0
- package/src/components/command-bar/commands/direct/index.ts +9 -1
- package/src/components/command-bar/commands/plugin/items.ts +3 -1
- package/src/components/command-bar/commands/registry.ts +7 -0
- package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/routes/root/command-items.ts +2 -0
- package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
- package/src/components/command-bar/routes/root/results.ts +40 -10
- package/src/components/command-bar/routes/root/runtime.ts +8 -0
- package/src/components/command-bar/routes/root/search-providers.ts +97 -14
- package/src/components/command-bar/surface/index.tsx +18 -4
- package/src/components/command-bar/theme-picker.tsx +48 -13
- package/src/components/command-bar/workflow/ops.ts +5 -3
- package/src/components/data-table/view.tsx +28 -9
- package/src/components/layout/floating-pane.tsx +6 -2
- package/src/components/layout/market-summary.ts +23 -9
- package/src/components/layout/pane/content.tsx +46 -29
- package/src/components/layout/pane/cycle-order.ts +2 -11
- package/src/components/layout/pane/header.tsx +2 -1
- package/src/components/layout/pane/title.ts +1 -1
- package/src/components/layout/presentation.ts +22 -0
- package/src/components/layout/shell/active-drag.ts +1 -1
- package/src/components/layout/shell/drag/index.ts +4 -0
- package/src/components/layout/shell/drag/live.ts +76 -5
- package/src/components/layout/shell/drag/runtime.ts +8 -8
- package/src/components/layout/shell/index.tsx +47 -6
- package/src/components/layout/shell/layout-state.ts +5 -11
- package/src/components/layout/shell/menu.ts +28 -1
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +51 -10
- package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
- package/src/components/layout/shell/pane/new-layout.ts +116 -0
- package/src/components/layout/shell/shortcuts.ts +5 -2
- package/src/components/layout/shell/visible-layout.ts +44 -1
- package/src/components/layout/status-bar.tsx +4 -1
- package/src/components/markdown-text.tsx +9 -5
- package/src/components/metric-treemap/heat-surface.tsx +27 -8
- package/src/components/metric-treemap/heat-tiles.ts +48 -17
- package/src/components/onboarding/onboarding-frame.tsx +33 -10
- package/src/components/onboarding/onboarding-wizard.tsx +49 -40
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
- package/src/components/onboarding/pro-step-layout.ts +86 -0
- package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
- package/src/components/ui/button.tsx +10 -0
- package/src/components/ui/data-table/index.tsx +38 -0
- package/src/components/ui/data-table/types.ts +8 -1
- package/src/components/ui/data-table/visible-range.ts +2 -0
- package/src/components/ui/icon.tsx +11 -2
- package/src/components/ui/index.ts +1 -1
- package/src/core/connection-health.ts +1 -1
- package/src/core/state/app/layout-reducer.ts +89 -3
- package/src/core/state/app/state.ts +8 -5
- package/src/core/state/app/types.ts +13 -2
- package/src/data/config/layout.ts +55 -21
- package/src/data/config/store/fold-panes.ts +149 -0
- package/src/data/config/store/migrations.ts +7 -0
- package/src/data/config/store/node.ts +27 -6
- package/src/data/config/store/normalize.ts +37 -3
- package/src/i18n/es.ts +14 -1
- package/src/i18n/ja.ts +14 -1
- package/src/i18n/ko.ts +14 -1
- package/src/i18n/zh-cn.ts +14 -1
- package/src/i18n/zh-tw.ts +14 -1
- package/src/layout/desks.ts +1 -2
- package/src/layout/pane-layout-move.ts +264 -0
- package/src/layout/pane-manager/hidden-panes.ts +160 -0
- package/src/layout/pane-manager/index.ts +1 -0
- package/src/market-data/coordinator/entries.ts +7 -1
- package/src/market-data/coordinator/quotes.ts +2 -2
- package/src/market-data/market/freshness.ts +118 -18
- package/src/market-data/market/status.ts +40 -0
- package/src/market-data/options-alternatives.ts +69 -0
- package/src/market-data/quotes/contributions.ts +19 -2
- package/src/market-data/quotes/metadata.ts +16 -0
- package/src/market-data/quotes/screener-live-quotes.ts +56 -5
- package/src/plugins/builtin/account-management/keyboard.ts +3 -0
- package/src/plugins/builtin/account-management/model.ts +1 -0
- package/src/plugins/builtin/account-management/navigation.ts +1 -1
- package/src/plugins/builtin/account-management/pane.tsx +31 -4
- package/src/plugins/builtin/alerts/quotes.ts +3 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
- package/src/plugins/builtin/analytics/risk-model.ts +37 -8
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
- package/src/plugins/builtin/backtest/pane.tsx +1 -1
- package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/broker-manager/model.ts +8 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
- package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
- package/src/plugins/builtin/broker-manager/table.ts +5 -0
- package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
- package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
- package/src/plugins/builtin/chat/channels.ts +34 -36
- package/src/plugins/builtin/chat/command-bar.ts +138 -0
- package/src/plugins/builtin/chat/composer-commands.ts +21 -1
- package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
- package/src/plugins/builtin/chat/content/footer.tsx +5 -3
- package/src/plugins/builtin/chat/content/index.tsx +21 -1
- package/src/plugins/builtin/chat/content/mentions.ts +2 -1
- package/src/plugins/builtin/chat/content/user-map.ts +3 -1
- package/src/plugins/builtin/chat/controller/index.ts +48 -0
- package/src/plugins/builtin/chat/conversations.ts +256 -0
- package/src/plugins/builtin/chat/discord-command.ts +91 -0
- package/src/plugins/builtin/chat/edit-window.ts +12 -6
- package/src/plugins/builtin/chat/ghost-user.ts +14 -0
- package/src/plugins/builtin/chat/index.tsx +40 -2
- package/src/plugins/builtin/chat/layout.ts +2 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
- package/src/plugins/builtin/chat/message/header.tsx +24 -15
- package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
- package/src/plugins/builtin/chat/message/render-state.ts +3 -0
- package/src/plugins/builtin/chat/pane-requests.ts +60 -0
- package/src/plugins/builtin/chat/pane-state.ts +32 -2
- package/src/plugins/builtin/chat/pane.tsx +8 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
- package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
- package/src/plugins/builtin/cloud/askg/model.ts +129 -70
- package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
- package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
- package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
- package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
- package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
- package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
- package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
- package/src/plugins/builtin/cloud/plugin.tsx +36 -1
- package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
- package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
- package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
- package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
- package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
- package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
- package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
- package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
- package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
- package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
- package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
- package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
- package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
- package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
- package/src/plugins/builtin/comment-letters/client.ts +1 -3
- package/src/plugins/builtin/company-kpis/client.ts +23 -8
- package/src/plugins/builtin/company-kpis/headless.ts +3 -1
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
- package/src/plugins/builtin/composite-plugins.ts +107 -64
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
- package/src/plugins/builtin/credit-boards/index.tsx +21 -21
- package/src/plugins/builtin/credit-boards/model.ts +0 -1
- package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
- package/src/plugins/builtin/debt-maturities/client.ts +37 -9
- package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/data.ts +28 -7
- package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
- package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
- package/src/plugins/builtin/econ/calendar-model.ts +42 -17
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/index.tsx +68 -22
- package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +17 -0
- package/src/plugins/builtin/executives/data.ts +17 -6
- package/src/plugins/builtin/executives/pane.tsx +16 -7
- package/src/plugins/builtin/filing-events/pane.tsx +22 -5
- package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
- package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
- package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
- package/src/plugins/builtin/gpu/headless.ts +9 -3
- package/src/plugins/builtin/gpu/model.ts +14 -5
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
- package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
- package/src/plugins/builtin/holders/headless.ts +6 -1
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/insider/index.tsx +4 -4
- package/src/plugins/builtin/iv-history/charts.tsx +21 -7
- package/src/plugins/builtin/iv-history/client.ts +12 -2
- package/src/plugins/builtin/iv-history/format.ts +33 -2
- package/src/plugins/builtin/iv-history/headless.ts +22 -10
- package/src/plugins/builtin/iv-history/index.tsx +3 -3
- package/src/plugins/builtin/iv-history/model.ts +61 -13
- package/src/plugins/builtin/iv-history/pane.tsx +11 -3
- package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
- package/src/plugins/builtin/jobs/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
- package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +109 -39
- package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
- package/src/plugins/builtin/layout-manager/index.tsx +9 -1
- package/src/plugins/builtin/macro-day/client.ts +24 -0
- package/src/plugins/builtin/macro-day/headless.ts +11 -8
- package/src/plugins/builtin/macro-day/index.tsx +3 -0
- package/src/plugins/builtin/macro-day/model.ts +11 -6
- package/src/plugins/builtin/macro-day/pane.tsx +12 -8
- package/src/plugins/builtin/macro-day/release-table.ts +40 -0
- package/src/plugins/builtin/macro-day/releases.ts +62 -44
- package/src/plugins/builtin/market-heatmap/data.ts +25 -5
- package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
- package/src/plugins/builtin/market-heatmap/model.ts +94 -3
- package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
- package/src/plugins/builtin/market-movers/headless.ts +31 -4
- package/src/plugins/builtin/market-movers/index.tsx +8 -1
- package/src/plugins/builtin/market-movers/model.ts +71 -17
- package/src/plugins/builtin/market-movers/table.ts +50 -23
- package/src/plugins/builtin/news/headless.ts +9 -2
- package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
- package/src/plugins/builtin/openfda/client.ts +1 -1
- package/src/plugins/builtin/options/carry.ts +95 -0
- package/src/plugins/builtin/options/index.tsx +58 -5
- package/src/plugins/builtin/options/strike-window.ts +183 -0
- package/src/plugins/builtin/options/table.ts +55 -3
- package/src/plugins/builtin/options/types.ts +8 -1
- package/src/plugins/builtin/options/view.tsx +161 -12
- package/src/plugins/builtin/options-positioning/headless.ts +4 -2
- package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
- package/src/plugins/builtin/options-scenario/client.ts +73 -17
- package/src/plugins/builtin/options-scenario/headless.ts +71 -24
- package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
- package/src/plugins/builtin/options-scenario/index.tsx +9 -2
- package/src/plugins/builtin/options-scenario/model.ts +33 -0
- package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
- package/src/plugins/builtin/pe-band/model.ts +2 -2
- package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
- package/src/plugins/builtin/perps/equity-row.tsx +8 -5
- package/src/plugins/builtin/perps/model.ts +16 -5
- package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
- package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
- package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
- package/src/plugins/builtin/plugin-module.ts +87 -4
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
- package/src/plugins/builtin/portfolio-list/cli/render.ts +87 -18
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
- package/src/plugins/builtin/portfolio-list/headless.ts +38 -7
- package/src/plugins/builtin/realized-vol/headless.ts +3 -0
- package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
- package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
- package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
- package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
- package/src/plugins/builtin/risk-factors/data.ts +23 -9
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
- package/src/plugins/builtin/seasonality/pane.tsx +1 -1
- package/src/plugins/builtin/sec/index.tsx +5 -5
- package/src/plugins/builtin/sectors/client.ts +10 -2
- package/src/plugins/builtin/sectors/headless.ts +35 -6
- package/src/plugins/builtin/sectors/index.tsx +18 -1
- package/src/plugins/builtin/sectors/sector-model.ts +150 -41
- package/src/plugins/builtin/shared/command-tab.ts +51 -0
- package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
- package/src/plugins/builtin/shared/members-menu.tsx +4 -4
- package/src/plugins/builtin/shared/report-freshness.ts +50 -2
- package/src/plugins/builtin/shared/ticker-request.ts +25 -1
- package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
- package/src/plugins/builtin/shared/volatility/options.ts +1 -46
- package/src/plugins/builtin/short-interest/index.tsx +19 -4
- package/src/plugins/builtin/short-interest/surface.tsx +4 -4
- package/src/plugins/builtin/short-volume/index.tsx +1 -10
- package/src/plugins/builtin/supply-chain/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/headless.ts +43 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
- package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
- package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
- package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
- package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
- package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
- package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
- package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
- package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
- package/src/plugins/builtin/vol-surface/client.ts +6 -2
- package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
- package/src/plugins/builtin/vol-surface/headless.ts +124 -20
- package/src/plugins/builtin/vol-surface/model.ts +23 -11
- package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
- package/src/plugins/builtin/vol-surface/tables.ts +124 -0
- package/src/plugins/builtin/world-indices/client.ts +22 -4
- package/src/plugins/builtin/world-indices/headless.ts +54 -27
- package/src/plugins/builtin/world-indices/model.ts +4 -2
- package/src/plugins/builtin/world-indices/table.tsx +13 -2
- package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
- package/src/plugins/builtin/world-venue-map/index.ts +1 -1
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/builtin-editorial.ts +126 -0
- package/src/plugins/catalog-backend.ts +16 -9
- package/src/plugins/catalog-browser.ts +127 -63
- package/src/plugins/catalog-manifest.ts +73 -40
- package/src/plugins/catalog-ui.ts +26 -6
- package/src/plugins/extracted-plugins.ts +7 -1
- package/src/plugins/ownership.ts +150 -7
- package/src/plugins/plugin-functions.ts +54 -0
- package/src/plugins/plugin-off.ts +36 -0
- package/src/plugins/registry/host-actions.ts +16 -0
- package/src/plugins/registry/index.ts +84 -24
- package/src/plugins/registry/plugin-state.ts +25 -1
- package/src/plugins/runtime/context.tsx +29 -0
- package/src/react/input.ts +48 -1
- package/src/remote/app-host.tsx +5 -0
- package/src/remote/controller.ts +3 -53
- package/src/remote/matching.ts +69 -0
- package/src/remote/schema.ts +1 -1
- package/src/remote/semantic-tree.tsx +14 -0
- package/src/remote/types.ts +2 -0
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/query-bar.tsx +59 -32
- package/src/renderers/dom/host/box.tsx +1 -1
- package/src/renderers/dom/host/drag-shield.ts +17 -6
- package/src/renderers/dom/host/live-frame.ts +10 -2
- package/src/renderers/dom/input-host.tsx +6 -1
- package/src/renderers/dom/styles.css +13 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +5 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
- package/src/renderers/electrobun/view/main.tsx +4 -1
- package/src/renderers/opentui/input-host.tsx +6 -1
- package/src/renderers/opentui/start.tsx +9 -0
- package/src/shares/portable-layout.ts +16 -6
- package/src/sources/gloomberb-cloud/index.ts +19 -10
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -1
- package/src/sources/listing-symbols.ts +11 -5
- package/src/sources/provider-errors.ts +88 -3
- package/src/sources/provider-router/cached-routes.ts +8 -4
- package/src/sources/provider-router/financial-routes.ts +11 -5
- package/src/sources/provider-router/financials.ts +42 -15
- package/src/sources/provider-router/history.ts +27 -11
- package/src/sources/provider-router/index.ts +1 -1
- package/src/sources/provider-router/primary.ts +23 -5
- package/src/sources/sec-edgar.ts +68 -8
- package/src/sync/core-contributors.ts +4 -3
- package/src/test-support/cli-context.ts +2 -1
- package/src/test-support/data-provider.ts +34 -0
- package/src/test-support/shell-pane-keys.tsx +1 -0
- package/src/theme/series-colors.ts +46 -0
- package/src/theme/themes.ts +149 -0
- package/src/tickers/search/index.ts +22 -2
- package/src/tickers/search/ranking.ts +45 -1
- package/src/tickers/search/types.ts +3 -1
- package/src/time-series/resolve.ts +1 -14
- package/src/types/config.ts +61 -1
- package/src/types/headless.ts +11 -0
- package/src/types/instrument.ts +8 -0
- package/src/types/plugin.ts +49 -3
- package/src/ui/host.tsx +8 -4
- package/src/ui/index.tsx +2 -0
- package/src/utils/cli-output.ts +14 -1
- package/src/utils/exchanges.ts +13 -0
- package/src/utils/format.ts +2 -1
- package/src/utils/fx-market-hours.ts +34 -0
- package/src/utils/option-expiry.ts +126 -0
- package/src/utils/price-history.ts +39 -6
- package/src/utils/rpc-timeout-error.ts +6 -2
- package/src/utils/sec-eps-basis.ts +159 -30
- package/src/utils/sec.ts +38 -34
- package/src/utils/throttled-fetch.ts +51 -22
|
@@ -9,12 +9,16 @@ import type {
|
|
|
9
9
|
import type { TickerRecord } from "../../../types/ticker";
|
|
10
10
|
import { formatNumber, formatPercentRaw } from "../../../utils/format";
|
|
11
11
|
import {
|
|
12
|
+
collectionQuoteHeadline,
|
|
13
|
+
extendedQuoteFields,
|
|
14
|
+
extendedSessionsOf,
|
|
12
15
|
findCollection,
|
|
13
16
|
loadCollectionQuotes,
|
|
14
17
|
valuePortfolioAllocation,
|
|
15
18
|
valuePortfolioPositions,
|
|
16
19
|
type CollectionMatch,
|
|
17
20
|
} from "./cli/render";
|
|
21
|
+
import { EXTENDED_SESSION_LABELS, type ExtendedSession } from "../../../market-data/market/status";
|
|
18
22
|
import {
|
|
19
23
|
BROKER_ACCOUNT_MISSING_NOTE,
|
|
20
24
|
CASH_SYMBOL,
|
|
@@ -61,6 +65,7 @@ function textOf(value: unknown): string | undefined {
|
|
|
61
65
|
const POSITION_COLUMNS: HeadlessPaneColumn[] = [
|
|
62
66
|
{ key: "symbol", header: "Ticker" },
|
|
63
67
|
{ key: "name", header: "Name" },
|
|
68
|
+
{ key: "exchange", header: "Exchange" },
|
|
64
69
|
{ key: "shares", header: "Qty", align: "right", format: quantity },
|
|
65
70
|
{ key: "avgCost", header: "Avg Cost", align: "right", format: unitMoney("positionCurrency"), description: "Per unit, in the position's currency." },
|
|
66
71
|
{ key: "price", header: "Last", align: "right", format: unitMoney("priceCurrency"), description: "In the quote's currency." },
|
|
@@ -79,6 +84,26 @@ const TARGET_COLUMNS: HeadlessPaneColumn[] = [
|
|
|
79
84
|
{ key: "tradeValue", header: "Trade Value", align: "right", format: signedAmount, description: "The trade in the portfolio's currency." },
|
|
80
85
|
];
|
|
81
86
|
|
|
87
|
+
/** The pre-market or after-hours print and its move from the regular close, for the rows that have one. */
|
|
88
|
+
function extendedColumn(session: ExtendedSession): HeadlessPaneColumn {
|
|
89
|
+
return {
|
|
90
|
+
key: "extendedPrice",
|
|
91
|
+
header: EXTENDED_SESSION_LABELS[session],
|
|
92
|
+
align: "right",
|
|
93
|
+
format: (value, row) => row.extendedSession === session && typeof value === "number"
|
|
94
|
+
? `${unitMoney("priceCurrency")(value, row)} ${percent(row.extendedChangePercent)}`
|
|
95
|
+
: formatNumber(undefined),
|
|
96
|
+
description: "Measured from the regular close in Last.",
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** `columns` with an extended column after Chg for each session a row has a print for. */
|
|
101
|
+
function withExtendedColumns(columns: HeadlessPaneColumn[], sessions: readonly ExtendedSession[]): HeadlessPaneColumn[] {
|
|
102
|
+
if (sessions.length === 0) return columns;
|
|
103
|
+
const at = columns.findIndex((column) => column.key === "changePercent") + 1;
|
|
104
|
+
return [...columns.slice(0, at), ...sessions.map(extendedColumn), ...columns.slice(at)];
|
|
105
|
+
}
|
|
106
|
+
|
|
82
107
|
const WATCHLIST_COLUMNS: HeadlessPaneColumn[] = [
|
|
83
108
|
{ key: "symbol", header: "Ticker" },
|
|
84
109
|
{ key: "name", header: "Name" },
|
|
@@ -154,8 +179,9 @@ async function portfolioHoldings(
|
|
|
154
179
|
const gross = held.reduce((sum, entry) => sum + Math.abs(finite(entry.row.marketValue) ?? 0), 0);
|
|
155
180
|
const sum = (key: string) => held.reduce((total, entry) => total + (finite(entry.row[key]) ?? 0), 0);
|
|
156
181
|
const seen = new Set<string>();
|
|
182
|
+
const extendedSessions = extendedSessionsOf(held.map(({ extended }) => extended?.session));
|
|
157
183
|
const rows = held
|
|
158
|
-
.map(({ ticker,
|
|
184
|
+
.map(({ ticker, headline, extended, row }) => {
|
|
159
185
|
const marketValue = finite(row.marketValue);
|
|
160
186
|
// A ticker held in several lots carries its allocation on its first row.
|
|
161
187
|
const figures = seen.has(ticker.metadata.ticker) ? undefined : allocationBySymbol.get(ticker.metadata.ticker);
|
|
@@ -170,9 +196,11 @@ async function portfolioHoldings(
|
|
|
170
196
|
shares: finite(row.shares),
|
|
171
197
|
avgCost: finite(row.avgCost),
|
|
172
198
|
positionCurrency: row.positionCurrency ?? null,
|
|
199
|
+
// Last and Chg are the regular session; market value and P&L are at the live price.
|
|
173
200
|
price: finite(row.quotePrice),
|
|
174
201
|
priceCurrency: row.quoteCurrency ?? null,
|
|
175
|
-
changePercent: finite(
|
|
202
|
+
changePercent: finite(headline?.changePercent),
|
|
203
|
+
...extendedQuoteFields(extended),
|
|
176
204
|
marketValue,
|
|
177
205
|
unrealizedPnl: finite(row.unrealizedPnl),
|
|
178
206
|
weight: figures?.weight ?? null,
|
|
@@ -216,7 +244,7 @@ async function portfolioHoldings(
|
|
|
216
244
|
|
|
217
245
|
return {
|
|
218
246
|
freshness: { source: broker ? "Your broker account and Gloom Cloud" : "Local portfolio and Gloom Cloud" },
|
|
219
|
-
columns: showTargets ? [...POSITION_COLUMNS, ...TARGET_COLUMNS] : POSITION_COLUMNS,
|
|
247
|
+
columns: withExtendedColumns(showTargets ? [...POSITION_COLUMNS, ...TARGET_COLUMNS] : POSITION_COLUMNS, extendedSessions),
|
|
220
248
|
rows: shown,
|
|
221
249
|
complete: unavailable.length === 0,
|
|
222
250
|
...(unavailable.length
|
|
@@ -254,21 +282,24 @@ async function watchlistHoldings(
|
|
|
254
282
|
ctx.signal.throwIfAborted();
|
|
255
283
|
const rows = tickers.map((ticker) => {
|
|
256
284
|
const quote = quotes.get(ticker.metadata.ticker);
|
|
285
|
+
// As the watchlist pane shows it: the regular session, then any extended print from its close.
|
|
286
|
+
const { headline, extended } = collectionQuoteHeadline(quote);
|
|
257
287
|
return {
|
|
258
288
|
...quoteFreshnessFields(quote),
|
|
259
289
|
updatedAt: quote?.lastUpdated ?? null,
|
|
260
290
|
symbol: ticker.metadata.ticker,
|
|
261
291
|
name: ticker.metadata.name ?? null,
|
|
262
292
|
exchange: ticker.metadata.exchange || null,
|
|
263
|
-
price: finite(quote?.price),
|
|
293
|
+
price: finite(headline?.price ?? quote?.price),
|
|
264
294
|
priceCurrency: quote?.currency ?? null,
|
|
265
|
-
changePercent: finite(quote?.changePercent),
|
|
295
|
+
changePercent: finite(headline ? headline.changePercent : quote?.changePercent),
|
|
296
|
+
...extendedQuoteFields(extended),
|
|
266
297
|
};
|
|
267
298
|
});
|
|
268
299
|
const shown = rows.slice(0, limit);
|
|
269
300
|
return {
|
|
270
301
|
freshness: { source: "Local watchlist and Gloom Cloud" },
|
|
271
|
-
columns: WATCHLIST_COLUMNS,
|
|
302
|
+
columns: withExtendedColumns(WATCHLIST_COLUMNS, extendedSessionsOf(shown.map((row) => row.extendedSession))),
|
|
272
303
|
rows: shown,
|
|
273
304
|
metadata: {
|
|
274
305
|
collection: { kind: "watchlist", id: target.id, name: target.name },
|
|
@@ -288,7 +319,7 @@ async function watchlistHoldings(
|
|
|
288
319
|
export const collectionHoldingsHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
289
320
|
shape: "rows",
|
|
290
321
|
description:
|
|
291
|
-
"Positions held in a portfolio, broker or manual: symbol,
|
|
322
|
+
"Positions held in a portfolio, broker or manual: symbol, exchange, shares, average cost, last price, market value, unrealized P&L and weight of the total with cash, largest first, then the cash line and totals. With target weights set, each row adds its target, drift and the trade to reach it. For a watchlist, its tickers with quotes. Takes a portfolio or watchlist ID; the first portfolio when omitted.",
|
|
292
323
|
discovery: {
|
|
293
324
|
dataRequirements: ["Local portfolios, watchlists and synced broker positions; current quotes"],
|
|
294
325
|
limitations: ["Unrealized P&L on current positions; excludes realized trades, distributions and cash flows"],
|
|
@@ -46,7 +46,10 @@ export function realizedVolHeadless(initialView: "graph" | "cone"): HeadlessPane
|
|
|
46
46
|
estimator: ESTIMATOR_OPTIONS.find(({ value }) => value === args.options.estimator)?.value,
|
|
47
47
|
windows, lookbackYears: Number(args.options.lookbackYears) === 2 ? 2 : 1 });
|
|
48
48
|
const errors = [history.error, iv?.error].filter((value): value is string => !!value);
|
|
49
|
+
// The cone rows are as of the last daily bar, not of when the report ran.
|
|
50
|
+
const lastBar = model.asOf && Number.isFinite(model.asOf.getTime()) ? model.asOf.toISOString().slice(0, 10) : null;
|
|
49
51
|
return {
|
|
52
|
+
freshness: { asOf: lastBar },
|
|
50
53
|
sections: [
|
|
51
54
|
{ title: "Volatility cone", columns: [
|
|
52
55
|
{ key: "window", header: "Sessions" },
|
|
@@ -164,7 +164,7 @@ export function RealizedVolPane({ width, height, focused }: PaneProps) {
|
|
|
164
164
|
{ id: "estimator", label: "Estimator", value: estimator, options: ESTIMATOR_OPTIONS, onChange: (value: string) => setEstimator(value as RealizedVolatilityEstimator) },
|
|
165
165
|
{ id: "lookback", label: "Lookback", value: String(lookback), options: [{ value: "1", label: "1Y" }, { value: "2", label: "2Y" }], onChange: setLookback },
|
|
166
166
|
]} meta={reference ? `ATM IV observed ${reference.date.toISOString().slice(0, 16).replace("T", " ")} UTC` : "Annualized %"} />
|
|
167
|
-
{!symbol ? <EmptyState title="
|
|
167
|
+
{!symbol ? <EmptyState title="Select a ticker." /> : <PaneStatusBody subject="realized volatility" loading={history.loading && !model}
|
|
168
168
|
error={!model ? history.error ?? identityError ?? null : null} empty={!!model && !model.history.length}>
|
|
169
169
|
{model && view === "cone" ? <>
|
|
170
170
|
<StatGrid items={coneStats} width={width} />
|
|
@@ -25,7 +25,7 @@ import { formatPercent } from "../../../utils/format";
|
|
|
25
25
|
import { useResolvedEntryValue, useSecFilingDocuments, useSecFilingsQuery } from "../../../market-data/hooks";
|
|
26
26
|
import { instrumentFromTicker } from "../../../market-data/request-types";
|
|
27
27
|
import { usePaneTicker } from "../../../state/app/context";
|
|
28
|
-
import {
|
|
28
|
+
import { mayBeUsEquityTicker } from "../../../utils/sec";
|
|
29
29
|
import { useAssetData, usePluginPaneState } from "../../runtime";
|
|
30
30
|
import { handleRefreshKey } from "../../../components/data-table/table-pane";
|
|
31
31
|
import { SignInWall } from "../cloud/auth-actions";
|
|
@@ -496,7 +496,7 @@ export function CorporateActionsView({
|
|
|
496
496
|
: null;
|
|
497
497
|
const instrument = useMemo(() => instrumentFromTicker(ticker, symbol), [symbol, ticker]);
|
|
498
498
|
const secFilingsEntry = useSecFilingsQuery(
|
|
499
|
-
openRow?.status === "Earnings" && instrument &&
|
|
499
|
+
openRow?.status === "Earnings" && instrument && mayBeUsEquityTicker(ticker)
|
|
500
500
|
? { instrument, count: SEC_EVENT_FILING_LIMIT }
|
|
501
501
|
: null,
|
|
502
502
|
);
|
|
@@ -558,7 +558,7 @@ export function EquityDiagnosticView({ focused, width, height }: {
|
|
|
558
558
|
// Walls and empty states are drawn straight into the pane, like every other
|
|
559
559
|
// pane's; only a report sits in the padded scroll area.
|
|
560
560
|
if (!symbol) {
|
|
561
|
-
return <PaneStatusBody empty emptyTitle="
|
|
561
|
+
return <PaneStatusBody empty emptyTitle="Select a ticker." emptyMessage="Move the cursor in a list pane to populate this view." />;
|
|
562
562
|
}
|
|
563
563
|
if (signInRequired || verificationRequired) {
|
|
564
564
|
return <SignInWall placement="diag-signin" width={width} height={height} symbol={symbol} exchange={exchange} action="run the Equity Diagnostic" needsVerification={verificationRequired} />;
|
|
@@ -4,7 +4,9 @@ import type {
|
|
|
4
4
|
RevenueBreakdownPayload,
|
|
5
5
|
RevenueBreakdownView,
|
|
6
6
|
} from "../../../api-client/revenue-breakdown";
|
|
7
|
+
import type { IssuerListingParams } from "../../../api-client/paths";
|
|
7
8
|
import { createPluginCache } from "../../../data/plugin-cache";
|
|
9
|
+
import { secListingKey } from "../../../utils/sec";
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Revenue by product, segment or region comes from Gloom Cloud, which reads
|
|
@@ -59,10 +61,12 @@ export async function fetchRevenueBreakdown(
|
|
|
59
61
|
symbol: string,
|
|
60
62
|
view: RevenueBreakdownView,
|
|
61
63
|
client: Pick<typeof apiClient, "getCloudRevenueBreakdown"> = apiClient,
|
|
64
|
+
listing?: IssuerListingParams,
|
|
62
65
|
): Promise<RevenueBreakdownPayload> {
|
|
63
66
|
try {
|
|
64
|
-
return validateRevenueBreakdown(await client.getCloudRevenueBreakdown(symbol, view));
|
|
67
|
+
return validateRevenueBreakdown(await client.getCloudRevenueBreakdown(symbol, view, listing));
|
|
65
68
|
} catch (error) {
|
|
69
|
+
// 404 is also a listing abroad whose company the SEC does not know.
|
|
66
70
|
if (error instanceof ApiRequestError && error.status === 404) throw new Error(NO_BREAKDOWN);
|
|
67
71
|
throw error;
|
|
68
72
|
}
|
|
@@ -74,8 +78,9 @@ export interface RevenueResource {
|
|
|
74
78
|
refreshError: string | null;
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
// SAN in Paris is not SAN in New York: a listing abroad is cached under its venue.
|
|
82
|
+
const cacheKey = (symbol: string, view: RevenueBreakdownView, pro: boolean, listing?: IssuerListingParams) =>
|
|
83
|
+
`${secListingKey(symbol, listing?.exchange)}:${view}:${pro ? "full" : "preview"}`;
|
|
79
84
|
|
|
80
85
|
// Seeds the first paint while the mount load runs; cache age is not staleness,
|
|
81
86
|
// and a failed refresh reports its own.
|
|
@@ -83,8 +88,9 @@ export function cachedRevenueBreakdown(
|
|
|
83
88
|
symbol: string,
|
|
84
89
|
view: RevenueBreakdownView,
|
|
85
90
|
pro: boolean,
|
|
91
|
+
listing?: IssuerListingParams,
|
|
86
92
|
): RevenueResource | null {
|
|
87
|
-
const cached = revenueBreakdownCache.get(cacheKey(symbol, view, pro), { allowExpired: true });
|
|
93
|
+
const cached = revenueBreakdownCache.get(cacheKey(symbol, view, pro, listing), { allowExpired: true });
|
|
88
94
|
if (!cached) return null;
|
|
89
95
|
try {
|
|
90
96
|
return { payload: validateRevenueBreakdown(cached.data), stale: false, refreshError: null };
|
|
@@ -98,10 +104,11 @@ export async function loadRevenueBreakdown(
|
|
|
98
104
|
view: RevenueBreakdownView,
|
|
99
105
|
pro: boolean,
|
|
100
106
|
force = false,
|
|
107
|
+
listing?: IssuerListingParams,
|
|
101
108
|
): Promise<RevenueResource> {
|
|
102
109
|
const result = await revenueBreakdownCache.load(
|
|
103
|
-
cacheKey(symbol, view, pro),
|
|
104
|
-
() => fetchRevenueBreakdown(symbol, view),
|
|
110
|
+
cacheKey(symbol, view, pro, listing),
|
|
111
|
+
() => fetchRevenueBreakdown(symbol, view, apiClient, listing),
|
|
105
112
|
{ force },
|
|
106
113
|
);
|
|
107
114
|
// A company that stopped reporting a breakdown must not keep showing an old one.
|
|
@@ -3,6 +3,7 @@ import type { HeadlessPaneDefinition } from "../../../types/plugin";
|
|
|
3
3
|
import { fetchRevenueBreakdown } from "./client";
|
|
4
4
|
import { quarterLabel, reportedSpan } from "./model";
|
|
5
5
|
import { SEC_FILINGS } from "../shared/report-freshness";
|
|
6
|
+
import { resolveHeadlessIssuerListing } from "../shared/headless-market-data";
|
|
6
7
|
|
|
7
8
|
export const revenueBreakdownHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
8
9
|
shape: "bundle",
|
|
@@ -34,10 +35,12 @@ export const revenueBreakdownHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
34
35
|
],
|
|
35
36
|
describe: (args) => `Revenue breakdown | ${args.symbols[0]}`,
|
|
36
37
|
async load(args, ctx) {
|
|
38
|
+
const symbol = args.symbols[0]!;
|
|
37
39
|
const data = reportedSpan(await fetchRevenueBreakdown(
|
|
38
|
-
|
|
40
|
+
symbol,
|
|
39
41
|
(args.options.view as RevenueBreakdownView | undefined) ?? "product",
|
|
40
42
|
ctx.apiClient,
|
|
43
|
+
await resolveHeadlessIssuerListing(ctx, symbol),
|
|
41
44
|
));
|
|
42
45
|
return {
|
|
43
46
|
complete: data.access === "full",
|
|
@@ -30,7 +30,7 @@ import { Blurred, LockedOverlay, UpgradeLabel } from "../shared/locked-rows";
|
|
|
30
30
|
import { usePaneStatusFooter } from "../../../components/layout/pane/status-footer";
|
|
31
31
|
import { usePlanAccess } from "../../../api-client/plan-access";
|
|
32
32
|
import { useResearchCloudSession } from "../shared/research-cloud-session";
|
|
33
|
-
import { listingIdentity } from "../shared/ticker-request";
|
|
33
|
+
import { listingIdentity, useIssuerListing } from "../shared/ticker-request";
|
|
34
34
|
import { QuarterBars, type BarHover } from "./bars";
|
|
35
35
|
import {
|
|
36
36
|
cachedRevenueBreakdown,
|
|
@@ -82,17 +82,20 @@ function RevenueBreakdownView({ width, height, focused }: { width: number; heigh
|
|
|
82
82
|
const openUpgrade = useCloudUpgradeAction("seg");
|
|
83
83
|
const identity = listingIdentity(boundSymbol, ticker?.metadata.exchange ?? "");
|
|
84
84
|
const symbol = identity?.symbol ?? null;
|
|
85
|
+
const { name } = useIssuerListing(ticker);
|
|
86
|
+
const exchange = identity?.exchange ?? "";
|
|
87
|
+
const listing = useMemo(() => ({ exchange, name }), [exchange, name]);
|
|
85
88
|
const [requestedView, setView] = usePaneSettingValue<RevenueBreakdownView>("view", "product");
|
|
86
89
|
const [mode, setMode] = usePluginPaneState<RevenueMode>("revenue:mode", "trend");
|
|
87
90
|
const [sort, setSort] = usePluginPaneState<RevenueSort>("revenue:sort", { column: "revenue", direction: "desc" });
|
|
88
91
|
const view = REVENUE_VIEWS.includes(requestedView) ? requestedView : "product";
|
|
89
92
|
|
|
90
93
|
const loader = useCallback(
|
|
91
|
-
(force: boolean) => loadRevenueBreakdown(symbol!, view, pro, force),
|
|
92
|
-
[symbol, view, pro, session.requestKey],
|
|
94
|
+
(force: boolean) => loadRevenueBreakdown(symbol!, view, pro, force, listing),
|
|
95
|
+
[symbol, listing, view, pro, session.requestKey],
|
|
93
96
|
);
|
|
94
97
|
const resource = useAsyncResource(symbol ? loader : null, {
|
|
95
|
-
initialData: () => (symbol ? cachedRevenueBreakdown(symbol, view, pro) : null),
|
|
98
|
+
initialData: () => (symbol ? cachedRevenueBreakdown(symbol, view, pro, listing) : null),
|
|
96
99
|
clearOnError: (error) => error instanceof Error && error.message === NO_BREAKDOWN,
|
|
97
100
|
});
|
|
98
101
|
useAutoRefresh(resource.updatedAt, resource.load);
|
|
@@ -36,7 +36,11 @@ export const reverseDcfHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
36
36
|
...Object.fromEntries(row.implied.map((value, index) => [`t${index}`, implied(value)])) })) },
|
|
37
37
|
],
|
|
38
38
|
complete: !inputs.stale && !inputs.error && !model.error,
|
|
39
|
-
|
|
39
|
+
// Dated by the price the enterprise value is marked at, not by when the report ran.
|
|
40
|
+
freshness: {
|
|
41
|
+
asOf: inputs.financials?.quote?.lastUpdated ?? null,
|
|
42
|
+
...(inputs.stale ? { status: "stale" as const } : {}),
|
|
43
|
+
},
|
|
40
44
|
unavailableSymbols: model.implied ? [] : [instrument.symbol],
|
|
41
45
|
errors: [inputs.error, model.error].filter((value): value is string => !!value),
|
|
42
46
|
metadata: { currency: model.currency, discountRate, terminalGrowth: TERMINAL_GROWTH, discountRates: DISCOUNT_RATES,
|
|
@@ -91,7 +91,7 @@ export function ReverseDcfPane({ width, height, focused }: PaneProps) {
|
|
|
91
91
|
|
|
92
92
|
return <Box width={width} height={height} flexDirection="column" overflow="hidden">
|
|
93
93
|
<QueryBar width={width} filters={[{ id: "discount", label: "Discount", value: String(discount), options: DISCOUNT_OPTIONS, onChange: setDiscount }]} />
|
|
94
|
-
{!symbol ? <EmptyState title="
|
|
94
|
+
{!symbol ? <EmptyState title="Select a ticker." /> : <PaneStatusBody subject="reverse DCF" loading={inputs.loading && !model}
|
|
95
95
|
error={!model ? inputs.error ?? identityError ?? null : model.error} empty={false}>
|
|
96
96
|
{model && !model.error ? <>
|
|
97
97
|
<StatGrid items={stats} width={width} />
|
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
type CloudRiskReportPayload,
|
|
5
5
|
} from "../../../api-client";
|
|
6
6
|
import { ApiRequestError, isPermanentClientError } from "../../../api-client/errors";
|
|
7
|
+
import { listingAbroad, type IssuerListingParams } from "../../../api-client/paths";
|
|
7
8
|
import type { HeadlessPaneApiClient, PluginPersistence } from "../../../types/plugin";
|
|
9
|
+
import { secListingKey } from "../../../utils/sec";
|
|
8
10
|
|
|
9
11
|
/** Annual reports are immutable; discovery of newly produced reports is not. */
|
|
10
12
|
const LIST_KIND = "reports";
|
|
@@ -88,12 +90,20 @@ function loadCached<T extends object>(
|
|
|
88
90
|
return request.promise;
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
interface RiskLoadOptions {
|
|
94
|
+
force?: boolean;
|
|
95
|
+
/** The listing's venue and company, for a listing outside the US. */
|
|
96
|
+
listing?: IssuerListingParams;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function loadRiskReportsWithClient(client: RiskApiClient, ticker: string, options?: RiskLoadOptions): Promise<RiskReportsResult> {
|
|
100
|
+
const symbol = ticker.trim().toUpperCase();
|
|
101
|
+
// SAN in Paris is not SAN in New York: a listing abroad is cached under its venue.
|
|
102
|
+
const key = secListingKey(symbol, options?.listing?.exchange);
|
|
93
103
|
return loadCached(client, LIST_KIND, key, activeListFetches, LIST_CACHE_POLICY, options?.force ?? false, async () => {
|
|
94
104
|
let payload: CloudRiskReportListPayload;
|
|
95
105
|
try {
|
|
96
|
-
payload = await client.getRiskReports(
|
|
106
|
+
payload = await client.getRiskReports(symbol, options?.listing);
|
|
97
107
|
} catch (error) {
|
|
98
108
|
// The list answers 404 when no 10-K risk report is on file (20-F filers, funds).
|
|
99
109
|
if (error instanceof ApiRequestError && error.status === 404) return { company: null, reports: [] };
|
|
@@ -104,12 +114,16 @@ export function loadRiskReportsWithClient(client: RiskApiClient, ticker: string,
|
|
|
104
114
|
});
|
|
105
115
|
}
|
|
106
116
|
|
|
107
|
-
export function loadRiskReportWithClient(client: RiskApiClient, ticker: string, year: number, options?:
|
|
117
|
+
export function loadRiskReportWithClient(client: RiskApiClient, ticker: string, year: number, options?: RiskLoadOptions): Promise<RiskReportResult> {
|
|
108
118
|
const symbol = ticker.trim().toUpperCase();
|
|
109
|
-
|
|
110
|
-
|
|
119
|
+
const abroad = listingAbroad(symbol, options?.listing?.exchange) !== null;
|
|
120
|
+
const key = `${secListingKey(symbol, options?.listing?.exchange)}:${year}`;
|
|
121
|
+
return loadCached(client, REPORT_KIND, key, activeReportFetches, REPORT_CACHE_POLICY, options?.force ?? false, async () => {
|
|
122
|
+
const payload = await client.getRiskReport(symbol, year, options?.listing);
|
|
111
123
|
// SEC rows spell share classes BRK-B; a BRK.B request is the same company.
|
|
112
|
-
|
|
124
|
+
// A listing abroad reads its registrant's rows, filed under that
|
|
125
|
+
// registrant's US ticker (Sanofi's SNY for SAN in Paris).
|
|
126
|
+
const sameTicker = (value: string | undefined) => abroad || value?.toUpperCase().replace(/\./g, "-") === symbol.replace(/\./g, "-");
|
|
113
127
|
if (!payload || !sameTicker(payload.ticker) || payload.reportYear !== year) {
|
|
114
128
|
throw new Error(`Risk report response does not match ${symbol} ${year}.`);
|
|
115
129
|
}
|
|
@@ -117,5 +131,5 @@ export function loadRiskReportWithClient(client: RiskApiClient, ticker: string,
|
|
|
117
131
|
});
|
|
118
132
|
}
|
|
119
133
|
|
|
120
|
-
export const loadRiskReports = (ticker: string, options?:
|
|
121
|
-
export const loadRiskReport = (ticker: string, year: number, options?:
|
|
134
|
+
export const loadRiskReports = (ticker: string, options?: RiskLoadOptions) => loadRiskReportsWithClient(apiClient, ticker, options);
|
|
135
|
+
export const loadRiskReport = (ticker: string, year: number, options?: RiskLoadOptions) => loadRiskReportWithClient(apiClient, ticker, year, options);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { HeadlessBundleSection, HeadlessPaneDefinition } from "../../../types/plugin";
|
|
2
2
|
import { loadRiskReportWithClient, loadRiskReportsWithClient, type RiskReportsResult } from "./data";
|
|
3
3
|
import { SEC_FILINGS } from "../shared/report-freshness";
|
|
4
|
+
import { resolveHeadlessIssuerListing } from "../shared/headless-market-data";
|
|
4
5
|
|
|
5
6
|
export const riskFactorsHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
6
7
|
shape: "bundle",
|
|
@@ -16,9 +17,11 @@ export const riskFactorsHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
16
17
|
const selection = String(args.options.year ?? "latest").trim().toLowerCase();
|
|
17
18
|
if (selection !== "latest" && !/^\d{4}$/.test(selection)) throw new Error("Risk report year must be a four-digit filing year or latest.");
|
|
18
19
|
const force = args.options.refresh === true;
|
|
20
|
+
// A listing abroad names its venue and company, so it never reads a US namesake's reports.
|
|
21
|
+
const listing = await resolveHeadlessIssuerListing(context, ticker);
|
|
19
22
|
// A specific immutable filing does not depend on the latest discovery list.
|
|
20
23
|
let list: RiskReportsResult | null = null;
|
|
21
|
-
if (selection === "latest") list = await loadRiskReportsWithClient(context.apiClient, ticker, { force });
|
|
24
|
+
if (selection === "latest") list = await loadRiskReportsWithClient(context.apiClient, ticker, { force, listing });
|
|
22
25
|
const year = list
|
|
23
26
|
? [...list.reports].sort((a, b) => b.reportYear - a.reportYear)[0]?.reportYear
|
|
24
27
|
: Number(selection);
|
|
@@ -27,7 +30,7 @@ export const riskFactorsHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
27
30
|
? `No cached risk report available for ${ticker}; report discovery failed: ${list.refreshError}`
|
|
28
31
|
: `No 10-K risk reports on file for ${ticker}.`);
|
|
29
32
|
}
|
|
30
|
-
const report = await loadRiskReportWithClient(context.apiClient, ticker, year, { force });
|
|
33
|
+
const report = await loadRiskReportWithClient(context.apiClient, ticker, year, { force, listing });
|
|
31
34
|
const sections: HeadlessBundleSection[] = [
|
|
32
35
|
{ title: "Filing", entries: [
|
|
33
36
|
{ label: "Ticker", value: report.ticker },
|
|
@@ -21,7 +21,7 @@ import { isPermanentClientError } from "../../../api-client/errors";
|
|
|
21
21
|
import { usePluginPaneState } from "../../runtime";
|
|
22
22
|
import { useFilingYearReader } from "../shared/filing-year-reader";
|
|
23
23
|
import { useProFeatureWall, type ProFeatureWallCopy, type ReadGuard } from "../shared/pro-feature-wall";
|
|
24
|
-
import { useBoundTicker } from "../shared/ticker-request";
|
|
24
|
+
import { useBoundTicker, useIssuerListing } from "../shared/ticker-request";
|
|
25
25
|
import { loadRiskReport, loadRiskReports } from "./data";
|
|
26
26
|
|
|
27
27
|
export const RISK_FACTORS_PANE_ID = "risk-factors";
|
|
@@ -100,8 +100,9 @@ function RiskFactorsReader({
|
|
|
100
100
|
nested = false,
|
|
101
101
|
guard,
|
|
102
102
|
}: RiskFactorsPaneProps & { guard: ReadGuard }) {
|
|
103
|
-
const { symbol } = useBoundTicker();
|
|
103
|
+
const { symbol, ticker: boundTicker } = useBoundTicker();
|
|
104
104
|
const ticker = symbol ? symbol.toUpperCase() : null;
|
|
105
|
+
const listing = useIssuerListing(boundTicker);
|
|
105
106
|
const nativePaneChrome = useUiCapabilities().nativePaneChrome === true;
|
|
106
107
|
const rendererHost = useRendererHost();
|
|
107
108
|
|
|
@@ -109,13 +110,13 @@ function RiskFactorsReader({
|
|
|
109
110
|
// one the pane opened with, so a restored year applies to it.
|
|
110
111
|
const [selectedYear, setSelectedYear] = usePluginPaneState<number | null>("filingYear", null);
|
|
111
112
|
const [selectedTicker, setSelectedTicker] = useState<string | null>(ticker);
|
|
112
|
-
const listLoader = useCallback((force: boolean) => guard(loadRiskReports(ticker!, { force })), [guard, ticker]);
|
|
113
|
+
const listLoader = useCallback((force: boolean) => guard(loadRiskReports(ticker!, { force, listing })), [guard, listing, ticker]);
|
|
113
114
|
const list = useAsyncResource(ticker ? listLoader : null, { clearOnError: isPermanentClientError });
|
|
114
115
|
const years = useMemo(() => (list.data?.reports ?? []).map((entry) => entry.reportYear).sort((a, b) => b - a), [list.data]);
|
|
115
116
|
// Null follows the newest discovered filing; an explicit choice stays on that year.
|
|
116
117
|
const year = selectedTicker === ticker && selectedYear !== null
|
|
117
118
|
? selectedYear : years[0] ?? null;
|
|
118
|
-
const reportLoader = useCallback((force: boolean) => guard(loadRiskReport(ticker!, year!, { force })), [guard, ticker, year]);
|
|
119
|
+
const reportLoader = useCallback((force: boolean) => guard(loadRiskReport(ticker!, year!, { force, listing })), [guard, listing, ticker, year]);
|
|
119
120
|
const detail = useAsyncResource(ticker && year !== null ? reportLoader : null, { clearOnError: isPermanentClientError });
|
|
120
121
|
const report = detail.data;
|
|
121
122
|
const listError = list.error ?? list.data?.refreshError ?? null;
|
|
@@ -166,7 +167,7 @@ function RiskFactorsReader({
|
|
|
166
167
|
const bodyWidth = Math.max(12, width - 2);
|
|
167
168
|
const proseWidth = Math.min(bodyWidth, READING_WIDTH);
|
|
168
169
|
|
|
169
|
-
if (!ticker) return <EmptyState title="
|
|
170
|
+
if (!ticker) return <EmptyState title="Select a ticker." />;
|
|
170
171
|
if (!list.data && list.loading) return <PaneStatusBody loading align="center" loadingLabel="Loading risk factors..." />;
|
|
171
172
|
if (!list.data && listError && year === null) return <PaneStatusBody error={listError} errorTitle="Could not load risk reports." />;
|
|
172
173
|
if (year === null) return <EmptyState title={`No 10-K risk factors on file for ${ticker}.`} />;
|
|
@@ -133,7 +133,7 @@ export function SeasonalityPane({ width, height, focused }: PaneProps) {
|
|
|
133
133
|
return <Box width={width} height={height} flexDirection="column" overflow="hidden">
|
|
134
134
|
{strip}
|
|
135
135
|
<QueryBar width={width} filters={[{ id: "lookback", label: "Lookback", value: String(lookback), options: LOOKBACK_OPTIONS, onChange: setLookback }]} />
|
|
136
|
-
{!symbol ? <EmptyState title="
|
|
136
|
+
{!symbol ? <EmptyState title="Select a ticker." /> : <PaneStatusBody subject="seasonality" loading={history.loading && !model}
|
|
137
137
|
error={!model ? history.error ?? identityError ?? null : null} empty={!!model && !model.years.length}>
|
|
138
138
|
{model ? <>
|
|
139
139
|
<StatGrid items={stats} width={width} />
|
|
@@ -10,7 +10,7 @@ import { useDebouncedPluginPaneState } from "../../runtime";
|
|
|
10
10
|
import { usePaneSettingValue } from "../../../state/app/context";
|
|
11
11
|
import type { ScrollBoxRenderable } from "../../../ui";
|
|
12
12
|
import { EmptyState, FeedDataTableStackView, Spinner, StatGrid, usePaneNoticeFooter, useTableLoadMore, type FeedDataTableItem, type StatItem } from "../../../components";
|
|
13
|
-
import {
|
|
13
|
+
import { mayBeUsEquityOrFundTicker, mayBeUsEquityTicker, secFilingItemCodes } from "../../../utils/sec";
|
|
14
14
|
import { parseForm4Xml, transactionTypeLabel } from "../insider/insider-data";
|
|
15
15
|
import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
|
|
16
16
|
import {
|
|
@@ -241,7 +241,7 @@ function SecView({ width, height, focused }: { width: number; height: number; fo
|
|
|
241
241
|
// ticker. The fund filings view (ETF) lists other forms, so it leaves it.
|
|
242
242
|
useSecFilingFocusRequest(forms ? null : ticker?.metadata.ticker, setOpenItemId);
|
|
243
243
|
// A form list is the fund filings view (ETF), which US-listed funds open too.
|
|
244
|
-
const eligibleTicker = forms ?
|
|
244
|
+
const eligibleTicker = forms ? mayBeUsEquityOrFundTicker(ticker) : mayBeUsEquityTicker(ticker);
|
|
245
245
|
const instrument = instrumentFromTicker(ticker, ticker?.metadata.ticker ?? null);
|
|
246
246
|
const filingsEntry = useSecFilingsQuery(
|
|
247
247
|
instrument && eligibleTicker
|
|
@@ -449,7 +449,7 @@ export const secModule: PluginModule = {
|
|
|
449
449
|
keywords: ["sec", "filings", "10-k", "10-q", "8-k"],
|
|
450
450
|
shortcut: "SEC",
|
|
451
451
|
shortcutAliases: ["CF"],
|
|
452
|
-
canCreate: (_context, options) => !options?.ticker ||
|
|
452
|
+
canCreate: (_context, options) => !options?.ticker || mayBeUsEquityTicker(options.ticker),
|
|
453
453
|
}),
|
|
454
454
|
headless: secHeadless,
|
|
455
455
|
},
|
|
@@ -462,7 +462,7 @@ export const secModule: PluginModule = {
|
|
|
462
462
|
keywords: ["etf", "fund", "n-1a", "485bpos", "497", "n-csr", "n-cen", "n-port", "prospectus"],
|
|
463
463
|
shortcut: "ETF",
|
|
464
464
|
viewKey: "etf",
|
|
465
|
-
canCreate: (_context, options) => !options?.ticker ||
|
|
465
|
+
canCreate: (_context, options) => !options?.ticker || mayBeUsEquityOrFundTicker(options.ticker),
|
|
466
466
|
settings: () => ({ forms: ETF_FORMS_SETTING }),
|
|
467
467
|
}),
|
|
468
468
|
headless: createSecHeadless(undefined, {
|
|
@@ -481,7 +481,7 @@ export const secModule: PluginModule = {
|
|
|
481
481
|
order: 45,
|
|
482
482
|
component: SecView,
|
|
483
483
|
instruments: ["equity"],
|
|
484
|
-
isVisible: ({ ticker }) =>
|
|
484
|
+
isVisible: ({ ticker }) => mayBeUsEquityTicker(ticker),
|
|
485
485
|
});
|
|
486
486
|
},
|
|
487
487
|
dispose() {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { DataProvider } from "../../../types/data-provider";
|
|
2
2
|
import type { PricePoint, Quote } from "../../../types/financials";
|
|
3
3
|
import type { SectorDef } from "./sector-data";
|
|
4
|
+
import { getRegularSessionDisplay } from "../../../market-data/market/status";
|
|
4
5
|
import {
|
|
5
6
|
computeTrailingReturn,
|
|
6
7
|
historySessionBefore,
|
|
7
8
|
latestHistoryDate,
|
|
9
|
+
sectorExtendedFields,
|
|
8
10
|
sectorReturnTargetDate,
|
|
9
11
|
sectorQuoteSessionDate,
|
|
10
12
|
sectorReturnStartDate,
|
|
@@ -41,6 +43,8 @@ export async function loadSectorRows(
|
|
|
41
43
|
sector.etf,
|
|
42
44
|
await provider.getPriceHistory(sector.etf, "", "1Y").catch(() => []),
|
|
43
45
|
] as const)));
|
|
46
|
+
// The quotes as sent carry any pre-market or after-hours print, for its own column.
|
|
47
|
+
const sentQuotes = new Map(quotes);
|
|
44
48
|
for (const [symbol, quote] of quotes) {
|
|
45
49
|
if (quote) quotes.set(symbol, completedSessionQuote(quote, histories.get(symbol) ?? []));
|
|
46
50
|
}
|
|
@@ -52,7 +56,10 @@ export async function loadSectorRows(
|
|
|
52
56
|
let history: PricePoint[] = histories.get(sector.etf) ?? [];
|
|
53
57
|
const quote = quotes.get(sector.etf) ?? null;
|
|
54
58
|
if (!quote && history.length === 0) return { etf: sector.etf, row: null };
|
|
55
|
-
|
|
59
|
+
// Price and 1D are the regular session, as `ticker` reads them: after the close
|
|
60
|
+
// its close and move, with an extended print in its own column.
|
|
61
|
+
const headline = getRegularSessionDisplay(quote);
|
|
62
|
+
const lastReportedPrice = headline && Number.isFinite(headline.price) && headline.price > 0 ? headline.price : null;
|
|
56
63
|
const sessionDate = quoteDates.get(sector.etf) ?? null;
|
|
57
64
|
const priceIssue = !quote || lastReportedPrice == null ? "quote unavailable"
|
|
58
65
|
: !sessionDate ? "quote session unknown"
|
|
@@ -60,7 +67,7 @@ export async function loadSectorRows(
|
|
|
60
67
|
: sessionDate !== asOfDate ? `quote from ${sessionDate}; shared session is ${asOfDate}`
|
|
61
68
|
: null;
|
|
62
69
|
const price = priceIssue ? null : lastReportedPrice;
|
|
63
|
-
const changePercent = price != null && Number.isFinite(
|
|
70
|
+
const changePercent = price != null && Number.isFinite(headline?.changePercent) ? headline!.changePercent! : null;
|
|
64
71
|
const quoteIssue = priceIssue ?? (changePercent == null ? "1D change unavailable" : null);
|
|
65
72
|
// A strict trailing request may begin after the prior year's weekend or
|
|
66
73
|
// holiday. Ask for a small boundary buffer when the provider supports it.
|
|
@@ -89,6 +96,7 @@ export async function loadSectorRows(
|
|
|
89
96
|
quoteUpdatedAt: quote && Number.isFinite(quote.lastUpdated) ? quote.lastUpdated : null,
|
|
90
97
|
quoteDataSource: quote?.dataSource,
|
|
91
98
|
changePercent,
|
|
99
|
+
...sectorExtendedFields(price == null ? null : sentQuotes.get(sector.etf)),
|
|
92
100
|
return1M: month?.value ?? null,
|
|
93
101
|
return1Y: year?.value ?? null,
|
|
94
102
|
returnAsOfDate: asOfDate,
|
|
@@ -3,19 +3,39 @@ import type {
|
|
|
3
3
|
HeadlessPaneDefinition,
|
|
4
4
|
HeadlessPaneLoadArgs,
|
|
5
5
|
} from "../../../types/plugin";
|
|
6
|
+
import type { HeadlessPaneColumn } from "../../../types/headless";
|
|
6
7
|
import { formatCurrency, formatPercentRaw } from "../../../utils/format";
|
|
8
|
+
import { EXTENDED_SESSION_LABELS, type ExtendedSession } from "../../../market-data/market/status";
|
|
7
9
|
import { loadSectorRows, type SectorRowOutcome } from "./client";
|
|
8
10
|
import { getSectorCollection, type SectorCollectionId, type SectorDef } from "./sector-data";
|
|
9
|
-
import { DEFAULT_SORT_PREFERENCE, sectorRowIssues, sortRows, type SectorRow } from "./sector-model";
|
|
11
|
+
import { DEFAULT_SORT_PREFERENCE, sectorExtendedSessions, sectorRowIssues, sortRows, type SectorRow } from "./sector-model";
|
|
10
12
|
|
|
11
|
-
const
|
|
13
|
+
const price = (value: unknown, row: Record<string, unknown>) => value == null ? "-" : formatCurrency(Number(value), String(row.currency));
|
|
14
|
+
const percent = (value: unknown) => value == null ? "-" : formatPercentRaw(Number(value));
|
|
15
|
+
|
|
16
|
+
const LEAD_COLUMNS: HeadlessPaneColumn[] = [
|
|
12
17
|
{ key: "name", header: "Sector" },
|
|
13
18
|
{ key: "etf", header: "ETF" },
|
|
14
|
-
{ key: "price", header: "Last", align: "right"
|
|
15
|
-
{ key: "changePercent", header: "1D", align: "right"
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
{ key: "price", header: "Last", align: "right", format: price },
|
|
20
|
+
{ key: "changePercent", header: "1D", align: "right", format: percent },
|
|
21
|
+
];
|
|
22
|
+
const RETURN_COLUMNS: HeadlessPaneColumn[] = [
|
|
23
|
+
{ key: "return1M", header: "1M", align: "right", format: percent },
|
|
24
|
+
{ key: "return1Y", header: "1Y", align: "right", format: percent },
|
|
18
25
|
];
|
|
26
|
+
const COLUMNS = [...LEAD_COLUMNS, ...RETURN_COLUMNS];
|
|
27
|
+
|
|
28
|
+
/** The pre-market or after-hours print and its move from the regular close, for the funds that have one. */
|
|
29
|
+
function extendedColumn(session: ExtendedSession): HeadlessPaneColumn {
|
|
30
|
+
return {
|
|
31
|
+
key: "extendedPrice",
|
|
32
|
+
header: EXTENDED_SESSION_LABELS[session],
|
|
33
|
+
align: "right",
|
|
34
|
+
format: (value, row) => row.extendedSession === session && typeof value === "number"
|
|
35
|
+
? `${price(value, row)} ${percent(row.extendedChangePercent)}`
|
|
36
|
+
: "-",
|
|
37
|
+
};
|
|
38
|
+
}
|
|
19
39
|
|
|
20
40
|
export function projectSectorRows(
|
|
21
41
|
definitions: readonly SectorDef[],
|
|
@@ -36,6 +56,10 @@ export function projectSectorRows(
|
|
|
36
56
|
quoteDataSource: byEtf.get(definition.etf)?.quoteDataSource,
|
|
37
57
|
quoteIssue: byEtf.get(definition.etf)?.quoteIssue ?? null,
|
|
38
58
|
lastReportedPrice: byEtf.get(definition.etf)?.lastReportedPrice ?? null,
|
|
59
|
+
extendedSession: byEtf.get(definition.etf)?.extendedSession ?? null,
|
|
60
|
+
extendedPrice: byEtf.get(definition.etf)?.extendedPrice ?? null,
|
|
61
|
+
extendedChange: byEtf.get(definition.etf)?.extendedChange ?? null,
|
|
62
|
+
extendedChangePercent: byEtf.get(definition.etf)?.extendedChangePercent ?? null,
|
|
39
63
|
returnIntegrity: byEtf.get(definition.etf)?.returnIntegrity ?? {},
|
|
40
64
|
returnAsOfDate: byEtf.get(definition.etf)?.returnAsOfDate ?? null,
|
|
41
65
|
return1MStartDate: byEtf.get(definition.etf)?.return1MStartDate ?? null,
|
|
@@ -82,7 +106,12 @@ export function createSectorsHeadless(
|
|
|
82
106
|
const unavailableReturns = rows.filter((row) => row.return1M == null || row.return1Y == null).map((row) => row.etf);
|
|
83
107
|
const unavailableDailyChanges = rows.filter((row) => row.changePercent == null).map((row) => row.etf);
|
|
84
108
|
const unavailableSymbols = [...new Set([...unavailableQuotes, ...unavailableReturns, ...unavailableDailyChanges])];
|
|
109
|
+
// Last and 1D are the regular session; an extended print gets a column only when a fund has one.
|
|
110
|
+
const extendedSessions = sectorExtendedSessions(rows);
|
|
85
111
|
return {
|
|
112
|
+
...(extendedSessions.length > 0
|
|
113
|
+
? { columns: [...LEAD_COLUMNS, ...extendedSessions.map(extendedColumn), ...RETURN_COLUMNS] }
|
|
114
|
+
: {}),
|
|
86
115
|
unavailableSymbols: unavailableSymbols.length > 0 ? unavailableSymbols : undefined,
|
|
87
116
|
errors: rows.flatMap((row) => sectorRowIssues(row).map((issue) => `${row.etf}: ${issue}.`)),
|
|
88
117
|
// The quote behind each row says whether it is real-time or delayed, and how current it is.
|