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
|
@@ -12,6 +12,7 @@ import { useLiveQuoteEntries } from "../../../state/hooks/quote-streaming";
|
|
|
12
12
|
import { buildQuoteKey, resolveEntryData } from "../../../market-data/selectors";
|
|
13
13
|
import type { QuoteSubscriptionTarget } from "../../../types/data-provider";
|
|
14
14
|
import type { Quote } from "../../../types/financials";
|
|
15
|
+
import type { ExtendedSession } from "../../../market-data/market/status";
|
|
15
16
|
import { useAutoRefresh, useUpdatedAgo } from "../../../react/auto-refresh";
|
|
16
17
|
import { useLiveStreamingSetting } from "../../../state/hooks/live-streaming";
|
|
17
18
|
import { isStreamCarryingQuote, useVisibleBoardSymbols } from "../shared/use-quote-board";
|
|
@@ -33,6 +34,7 @@ import {
|
|
|
33
34
|
nextSortPreference,
|
|
34
35
|
normalizeRowsForCollection,
|
|
35
36
|
overlayLiveSectorQuote,
|
|
37
|
+
sectorExtendedSessions,
|
|
36
38
|
sectorRowFollowsQuote,
|
|
37
39
|
sectorRowIssues,
|
|
38
40
|
sortRows,
|
|
@@ -149,7 +151,6 @@ function EtfBoard({ collectionId, focused, width, height, rootBefore }: EtfBoard
|
|
|
149
151
|
const fetchGenRef = useRef(0);
|
|
150
152
|
const [loadError, setLoadError] = useState<string | null>(null);
|
|
151
153
|
|
|
152
|
-
const columns = useMemo(() => buildSectorColumns(width), [width]);
|
|
153
154
|
const rows = useMemo(
|
|
154
155
|
() => normalizeRowsForCollection(rowsByCollection, activeCollection.id, activeItems),
|
|
155
156
|
[activeCollection.id, activeItems, rowsByCollection],
|
|
@@ -203,6 +204,12 @@ function EtfBoard({ collectionId, focused, width, height, rootBefore }: EtfBoard
|
|
|
203
204
|
return carried && !!quote && sectorRowFollowsQuote(row, quote);
|
|
204
205
|
}), [freshnessNow, liveEntries, liveStreaming, rows, subscriptionStartedAt]);
|
|
205
206
|
const sortedRows = useMemo(() => sortRows(liveRows, sortPreference), [liveRows, sortPreference]);
|
|
207
|
+
// A pre-market or after-hours column only while a fund has such a print.
|
|
208
|
+
const extendedSessionsKey = sectorExtendedSessions(liveRows).join(",");
|
|
209
|
+
const columns = useMemo(
|
|
210
|
+
() => buildSectorColumns(width, extendedSessionsKey ? extendedSessionsKey.split(",") as ExtendedSession[] : []),
|
|
211
|
+
[extendedSessionsKey, width],
|
|
212
|
+
);
|
|
206
213
|
const lastRefreshMs = lastRefreshByCollection[activeCollection.id] ?? null;
|
|
207
214
|
const loading = rows.some((row) => row.loading);
|
|
208
215
|
/**
|
|
@@ -296,6 +303,16 @@ function EtfBoard({ collectionId, focused, width, height, rootBefore }: EtfBoard
|
|
|
296
303
|
text: row.changePercent !== null ? formatPercentRaw(shownPercent(row.changePercent)) : "—",
|
|
297
304
|
color: row.changePercent !== null ? priceColor(shownPercent(row.changePercent)) : colors.textDim,
|
|
298
305
|
};
|
|
306
|
+
case "preMarket":
|
|
307
|
+
case "afterHours": {
|
|
308
|
+
// The extended print's move from the close in LAST.
|
|
309
|
+
const move = row.extendedSession === (column.id === "preMarket" ? "PRE" : "POST") ? row.extendedChangePercent ?? null : null;
|
|
310
|
+
return {
|
|
311
|
+
text: move !== null ? formatPercentRaw(shownPercent(move)) : "—",
|
|
312
|
+
value: move,
|
|
313
|
+
color: move !== null ? priceColor(shownPercent(move)) : colors.textDim,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
299
316
|
case "return1M":
|
|
300
317
|
return {
|
|
301
318
|
text: row.loading && row.return1M === null ? "…" : row.return1M !== null ? formatPercentRaw(shownPercent(row.return1M)) : "—",
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import type { DataTableColumn } from "../../../components";
|
|
2
|
+
import { getTableWidth, tableColumnWidth } from "../../../components/ui/table-layout";
|
|
2
3
|
import type { PricePoint, Quote } from "../../../types/financials";
|
|
3
4
|
import { compareSortValues, type SortDirection } from "../../../utils/sort-values";
|
|
4
5
|
import { getPricePointTimestamp } from "../../../utils/price-history";
|
|
5
6
|
import { mergePriceHistoryIntegrity, pricePointIntegrity, type PriceHistoryIntegrity } from "../../../utils/price-history-integrity";
|
|
7
|
+
import {
|
|
8
|
+
getExtendedSessionDisplay,
|
|
9
|
+
getRegularSessionDisplay,
|
|
10
|
+
marketStateLabel,
|
|
11
|
+
type ExtendedSession,
|
|
12
|
+
} from "../../../market-data/market/status";
|
|
6
13
|
import {
|
|
7
14
|
getSectorCollection,
|
|
8
15
|
type SectorCollectionId,
|
|
@@ -12,8 +19,21 @@ import {
|
|
|
12
19
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
13
20
|
export const DEFAULT_COLLECTION_ID: SectorCollectionId = "sectors";
|
|
14
21
|
|
|
15
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The pre-market or after-hours print beside a row's regular close, measured
|
|
24
|
+
* from that close (getExtendedSessionDisplay); all null without one.
|
|
25
|
+
*/
|
|
26
|
+
export interface SectorExtendedFields {
|
|
27
|
+
extendedSession?: ExtendedSession | null;
|
|
28
|
+
extendedPrice?: number | null;
|
|
29
|
+
extendedChange?: number | null;
|
|
30
|
+
extendedChangePercent?: number | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface SectorRow extends SectorDef, SectorExtendedFields {
|
|
34
|
+
/** The regular session's price: its close once it is over, never an extended print. */
|
|
16
35
|
price: number | null;
|
|
36
|
+
/** The regular session's move, as `ticker` reports it. */
|
|
17
37
|
changePercent: number | null;
|
|
18
38
|
return1M: number | null;
|
|
19
39
|
return1Y: number | null;
|
|
@@ -33,7 +53,7 @@ export interface SectorRow extends SectorDef {
|
|
|
33
53
|
return1YStartDate?: string | null;
|
|
34
54
|
}
|
|
35
55
|
|
|
36
|
-
type SectorColumnId = "name" | "etf" | "price" | "changePercent" | "return1M" | "return1Y" | "bar";
|
|
56
|
+
type SectorColumnId = "name" | "etf" | "price" | "changePercent" | "preMarket" | "afterHours" | "return1M" | "return1Y" | "bar";
|
|
37
57
|
export type SectorColumn = DataTableColumn & { id: SectorColumnId };
|
|
38
58
|
export type SectorRowsByCollection = Record<SectorCollectionId, SectorRow[]>;
|
|
39
59
|
export type SectorRefreshByCollection = Partial<Record<SectorCollectionId, number>>;
|
|
@@ -48,6 +68,45 @@ export const DEFAULT_SORT_PREFERENCE: SectorSortPreference = {
|
|
|
48
68
|
direction: "desc",
|
|
49
69
|
};
|
|
50
70
|
|
|
71
|
+
const NO_EXTENDED_PRINT: Required<SectorExtendedFields> = {
|
|
72
|
+
extendedSession: null,
|
|
73
|
+
extendedPrice: null,
|
|
74
|
+
extendedChange: null,
|
|
75
|
+
extendedChangePercent: null,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/** A quote's pre-market or after-hours print, from the regular close a row shows, as row fields. */
|
|
79
|
+
export function sectorExtendedFields(quote: Quote | null | undefined): Required<SectorExtendedFields> {
|
|
80
|
+
const extended = getExtendedSessionDisplay(quote);
|
|
81
|
+
if (!extended || !Number.isFinite(extended.price)) return NO_EXTENDED_PRINT;
|
|
82
|
+
return {
|
|
83
|
+
extendedSession: extended.session,
|
|
84
|
+
extendedPrice: extended.price,
|
|
85
|
+
extendedChange: Number.isFinite(extended.change) ? extended.change! : null,
|
|
86
|
+
extendedChangePercent: Number.isFinite(extended.changePercent) ? extended.changePercent! : null,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function extendedFieldsOf(row: SectorExtendedFields | undefined): Required<SectorExtendedFields> {
|
|
91
|
+
return {
|
|
92
|
+
extendedSession: row?.extendedSession ?? null,
|
|
93
|
+
extendedPrice: row?.extendedPrice ?? null,
|
|
94
|
+
extendedChange: row?.extendedChange ?? null,
|
|
95
|
+
extendedChangePercent: row?.extendedChangePercent ?? null,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function sameExtended(left: SectorExtendedFields, right: SectorExtendedFields): boolean {
|
|
100
|
+
return (left.extendedSession ?? null) === (right.extendedSession ?? null)
|
|
101
|
+
&& (left.extendedPrice ?? null) === (right.extendedPrice ?? null)
|
|
102
|
+
&& (left.extendedChangePercent ?? null) === (right.extendedChangePercent ?? null);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The extended sessions some row has a print for, in the order their columns go. */
|
|
106
|
+
export function sectorExtendedSessions(rows: readonly SectorExtendedFields[]): ExtendedSession[] {
|
|
107
|
+
return (["PRE", "POST"] as const).filter((session) => rows.some((row) => row.extendedSession === session));
|
|
108
|
+
}
|
|
109
|
+
|
|
51
110
|
/** Full length at a 5% session move, which covers all but crash days. */
|
|
52
111
|
const MOVE_BAR_FULL_SCALE_PERCENT = 5;
|
|
53
112
|
|
|
@@ -99,6 +158,7 @@ export function normalizeRowsForCollection(
|
|
|
99
158
|
quoteIssue: existing?.quoteIssue ?? null,
|
|
100
159
|
lastReportedPrice: existing?.lastReportedPrice ?? null,
|
|
101
160
|
quoteUpdatedAt: existing?.quoteUpdatedAt ?? null,
|
|
161
|
+
...extendedFieldsOf(existing),
|
|
102
162
|
returnIntegrity: existing?.returnIntegrity ?? {},
|
|
103
163
|
returnAsOfDate: existing?.returnAsOfDate ?? null,
|
|
104
164
|
return1MStartDate: existing?.return1MStartDate ?? null,
|
|
@@ -138,6 +198,7 @@ const UNAVAILABLE_SECTOR_ROW: Partial<SectorRow> = {
|
|
|
138
198
|
lastReportedPrice: null,
|
|
139
199
|
quoteUpdatedAt: null,
|
|
140
200
|
returnIntegrity: {},
|
|
201
|
+
...NO_EXTENDED_PRINT,
|
|
141
202
|
};
|
|
142
203
|
|
|
143
204
|
function sameSectorRow(left: SectorRow, right: SectorRow): boolean {
|
|
@@ -276,33 +337,49 @@ export function sectorRowIssues(row: SectorRow): string[] {
|
|
|
276
337
|
return issues;
|
|
277
338
|
}
|
|
278
339
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
{ id: "
|
|
296
|
-
{ id: "
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
340
|
+
const EXTENDED_COLUMN_WIDTH = 9;
|
|
341
|
+
/** The longest name in either collection ("Consumer Staples"), which a narrow pane keeps whole as long as it can. */
|
|
342
|
+
const FULL_NAME_WIDTH = 16;
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* The board's columns at `width`. A pre-market or after-hours column follows
|
|
346
|
+
* 1D for each session in `extendedSessions`. A narrow pane gives up the move
|
|
347
|
+
* bar first, then that column, before it cuts a sector's name.
|
|
348
|
+
*/
|
|
349
|
+
export function buildSectorColumns(width: number, extendedSessions: readonly ExtendedSession[] = []): SectorColumn[] {
|
|
350
|
+
const lead: SectorColumn[] = [
|
|
351
|
+
{ id: "etf", label: "ETF", width: 4, align: "left" },
|
|
352
|
+
{ id: "price", label: "LAST", width: 8, align: "right" },
|
|
353
|
+
{ id: "changePercent", label: "1D", width: 8, align: "right" },
|
|
354
|
+
];
|
|
355
|
+
const returns: SectorColumn[] = [
|
|
356
|
+
{ id: "return1M", label: "1M", width: 8, align: "right" },
|
|
357
|
+
{ id: "return1Y", label: "1Y", width: 8, align: "right" },
|
|
358
|
+
];
|
|
359
|
+
const extendedColumns = extendedSessions.map((session): SectorColumn => ({
|
|
360
|
+
id: session === "PRE" ? "preMarket" : "afterHours",
|
|
361
|
+
// Named for the session it trades in, as the header labels it.
|
|
362
|
+
label: marketStateLabel(session),
|
|
363
|
+
width: EXTENDED_COLUMN_WIDTH,
|
|
364
|
+
align: "right",
|
|
365
|
+
}));
|
|
366
|
+
// Measured the way the table draws them (a header and its sort mark widen a
|
|
367
|
+
// column, gaps between them); the name adds its own width and a gap.
|
|
368
|
+
const nameRoom = (columns: SectorColumn[]) => width - getTableWidth(columns) - 1;
|
|
369
|
+
const shownExtended = extendedColumns.length > 0 && nameRoom([...lead, ...extendedColumns, ...returns]) >= FULL_NAME_WIDTH
|
|
370
|
+
? extendedColumns
|
|
371
|
+
: [];
|
|
372
|
+
const extendedWidth = shownExtended.reduce((total, column) => total + tableColumnWidth(column) + 1, 0);
|
|
373
|
+
const barWidth = width - extendedWidth < 82 ? 6 : Math.max(8, Math.min(18, Math.floor(width * 0.16)));
|
|
374
|
+
// Labelled with the window it encodes: it sits after 1Y but tracks 1D.
|
|
375
|
+
const bar: SectorColumn = { id: "bar", label: "1D MOVE", width: barWidth, align: "left" };
|
|
376
|
+
const withBar = [...lead, ...shownExtended, ...returns, bar];
|
|
377
|
+
const showBar = width - extendedWidth >= 67 && nameRoom(withBar) >= FULL_NAME_WIDTH;
|
|
378
|
+
const columns = showBar ? withBar : [...lead, ...shownExtended, ...returns];
|
|
379
|
+
return [
|
|
380
|
+
{ id: "name", label: "SECTOR", width: Math.max(10, Math.min(22, nameRoom(columns))), align: "left", flexGrow: 1 },
|
|
381
|
+
...columns,
|
|
300
382
|
];
|
|
301
|
-
if (showBar) {
|
|
302
|
-
// Labelled with the window it encodes: it sits after 1Y but tracks 1D.
|
|
303
|
-
columns.push({ id: "bar", label: "1D MOVE", width: barWidth, align: "left" });
|
|
304
|
-
}
|
|
305
|
-
return columns;
|
|
306
383
|
}
|
|
307
384
|
|
|
308
385
|
function getSortValue(columnId: SectorColumnId, row: SectorRow): string | number | null {
|
|
@@ -315,6 +392,9 @@ function getSortValue(columnId: SectorColumnId, row: SectorRow): string | number
|
|
|
315
392
|
return row.price;
|
|
316
393
|
case "changePercent":
|
|
317
394
|
return row.changePercent;
|
|
395
|
+
case "preMarket":
|
|
396
|
+
case "afterHours":
|
|
397
|
+
return row.extendedSession === (columnId === "preMarket" ? "PRE" : "POST") ? row.extendedChangePercent ?? null : null;
|
|
318
398
|
case "return1M":
|
|
319
399
|
return row.return1M;
|
|
320
400
|
case "return1Y":
|
|
@@ -338,6 +418,8 @@ export function nextSortPreference(current: SectorSortPreference, columnId: stri
|
|
|
338
418
|
return {
|
|
339
419
|
columnId: typedColumnId,
|
|
340
420
|
direction: typedColumnId === "changePercent"
|
|
421
|
+
|| typedColumnId === "preMarket"
|
|
422
|
+
|| typedColumnId === "afterHours"
|
|
341
423
|
|| typedColumnId === "return1M"
|
|
342
424
|
|| typedColumnId === "return1Y"
|
|
343
425
|
|| typedColumnId === "bar"
|
|
@@ -373,17 +455,30 @@ export function sectorQuoteSessionDate(quote: Quote): string | null {
|
|
|
373
455
|
/**
|
|
374
456
|
* A live quote extends a loaded row only inside the session its returns are
|
|
375
457
|
* measured to. Every fund shares that session, so a pre-market print (the
|
|
376
|
-
* next session) or a quote older than the snapshot leaves the row
|
|
377
|
-
* the board keeps ranking on one completed session. The 1M and 1Y
|
|
378
|
-
* keep their baselines: the row's own return and price imply the
|
|
458
|
+
* next session) or a quote older than the snapshot leaves the row's price
|
|
459
|
+
* alone and the board keeps ranking on one completed session. The 1M and 1Y
|
|
460
|
+
* returns keep their baselines: the row's own return and price imply the
|
|
461
|
+
* start close.
|
|
379
462
|
*/
|
|
380
463
|
function isLiveSectorQuote(row: SectorRow, quote: Quote | null | undefined): quote is Quote {
|
|
381
|
-
if (!
|
|
382
|
-
if (!finitePositive(row.price) || !row.returnAsOfDate) return false;
|
|
383
|
-
if (row.quoteUpdatedAt != null && quote.lastUpdated < row.quoteUpdatedAt) return false;
|
|
464
|
+
if (!isCurrentSectorQuote(row, quote) || !finitePositive(getRegularSessionDisplay(quote)?.price)) return false;
|
|
384
465
|
return sectorQuoteSessionDate(quote) === row.returnAsOfDate;
|
|
385
466
|
}
|
|
386
467
|
|
|
468
|
+
/** A usable quote no older than the snapshot behind a loaded row. */
|
|
469
|
+
function isCurrentSectorQuote(row: SectorRow, quote: Quote | null | undefined): quote is Quote {
|
|
470
|
+
if (!quote || quote.stale === true) return false;
|
|
471
|
+
if (!finitePositive(row.price) || !row.returnAsOfDate) return false;
|
|
472
|
+
return row.quoteUpdatedAt == null || quote.lastUpdated >= row.quoteUpdatedAt;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/** A pre-market print of the session after the row's, which moves only the extended column. */
|
|
476
|
+
function isNextPreMarketQuote(row: SectorRow, quote: Quote): boolean {
|
|
477
|
+
if (quote.marketState !== "PRE" && quote.marketState !== "PREPRE") return false;
|
|
478
|
+
const session = sectorQuoteSessionDate(quote);
|
|
479
|
+
return !!session && !!row.returnAsOfDate && session > row.returnAsOfDate;
|
|
480
|
+
}
|
|
481
|
+
|
|
387
482
|
/**
|
|
388
483
|
* Whether the feed keeps a loaded row as current as a snapshot reload would,
|
|
389
484
|
* given a quote the feed is carrying. It does when the quote belongs to the
|
|
@@ -402,18 +497,32 @@ export function sectorRowFollowsQuote(row: SectorRow, quote: Quote): boolean {
|
|
|
402
497
|
return quote.marketState === "PRE" || quote.marketState === "PREPRE";
|
|
403
498
|
}
|
|
404
499
|
|
|
500
|
+
/**
|
|
501
|
+
* A row with the feed's quote on it. Price and 1D are the regular session,
|
|
502
|
+
* as `ticker` reads them, so they hold at the close; a pre-market or
|
|
503
|
+
* after-hours print moves only the extended column, measured from that close.
|
|
504
|
+
*/
|
|
405
505
|
export function overlayLiveSectorQuote(row: SectorRow, quote: Quote | null | undefined): SectorRow {
|
|
406
|
-
if (!
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
506
|
+
if (!isCurrentSectorQuote(row, quote) || row.price == null) return row;
|
|
507
|
+
if (!isLiveSectorQuote(row, quote)) {
|
|
508
|
+
if (!isNextPreMarketQuote(row, quote)) return row;
|
|
509
|
+
const extended = sectorExtendedFields(quote);
|
|
510
|
+
return sameExtended(extended, row) ? row : { ...row, ...extended };
|
|
511
|
+
}
|
|
512
|
+
const headline = getRegularSessionDisplay(quote)!;
|
|
513
|
+
const extended = sectorExtendedFields(quote);
|
|
514
|
+
const changePercent = Number.isFinite(headline.changePercent) ? headline.changePercent! : row.changePercent;
|
|
515
|
+
if (headline.price === row.price && changePercent === row.changePercent && sameExtended(extended, row)) return row;
|
|
516
|
+
const scale = headline.price / row.price;
|
|
517
|
+
// A held close leaves the returns exactly as they were.
|
|
518
|
+
const rescale = (value: number | null) => (value == null || scale === 1 ? value : ((1 + value / 100) * scale - 1) * 100);
|
|
411
519
|
return {
|
|
412
520
|
...row,
|
|
413
|
-
price:
|
|
414
|
-
lastReportedPrice:
|
|
521
|
+
price: headline.price,
|
|
522
|
+
lastReportedPrice: headline.price,
|
|
415
523
|
changePercent,
|
|
416
524
|
return1M: rescale(row.return1M),
|
|
417
525
|
return1Y: rescale(row.return1Y),
|
|
526
|
+
...extended,
|
|
418
527
|
};
|
|
419
528
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { usePluginPaneState } from "../../../public/react";
|
|
3
|
+
import { usePaneInstance } from "../../../state/app/context";
|
|
4
|
+
import type { PaneTemplateDef, PaneTemplateInstanceConfig } from "../../../types/plugin";
|
|
5
|
+
|
|
6
|
+
/** A command's request to show one tab, stamped so a later command can ask again. */
|
|
7
|
+
export function commandTabParams(tab: string, params?: Record<string, string>): Record<string, string> {
|
|
8
|
+
return { ...(params ?? {}), tab, tabAt: String(Date.now()) };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** The template's pane, with the command's tab written onto the instance. */
|
|
12
|
+
function stampCommandTab(instance: PaneTemplateInstanceConfig | null, tab: string): PaneTemplateInstanceConfig | null {
|
|
13
|
+
return instance ? { ...instance, params: commandTabParams(tab, instance.params) } : null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function withCommandTab(template: PaneTemplateDef, tab: string): PaneTemplateDef {
|
|
17
|
+
const create = template.createInstance;
|
|
18
|
+
return {
|
|
19
|
+
...template,
|
|
20
|
+
createInstance: (context, options) => {
|
|
21
|
+
const created = create?.(context, options);
|
|
22
|
+
if (!created) return null;
|
|
23
|
+
return created instanceof Promise ? created.then((instance) => stampCommandTab(instance, tab)) : stampCommandTab(created, tab);
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The tab a command asked this pane to show. A stored choice wins until a
|
|
30
|
+
* newer command arrives. Another pane's params, such as Ticker Research
|
|
31
|
+
* around a nested tab, are ignored.
|
|
32
|
+
*/
|
|
33
|
+
export function useCommandTab<T extends string>(
|
|
34
|
+
paneId: string,
|
|
35
|
+
stateKey: string,
|
|
36
|
+
tabs: readonly T[],
|
|
37
|
+
fallback: T,
|
|
38
|
+
): [T, (next: T) => void] {
|
|
39
|
+
const instance = usePaneInstance();
|
|
40
|
+
const params = instance?.paneId === paneId ? instance.params : undefined;
|
|
41
|
+
const requested = params && (tabs as readonly string[]).includes(params.tab ?? "") ? params.tab as T : null;
|
|
42
|
+
const requestAt = params?.tabAt ?? "";
|
|
43
|
+
const [tab, setTab] = usePluginPaneState<T>(stateKey, requested ?? fallback);
|
|
44
|
+
const seen = useRef(requestAt);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!requested || requestAt === seen.current) return;
|
|
47
|
+
seen.current = requestAt;
|
|
48
|
+
setTab(requested);
|
|
49
|
+
}, [requestAt, requested, setTab]);
|
|
50
|
+
return [tab, setTab];
|
|
51
|
+
}
|
|
@@ -1,12 +1,44 @@
|
|
|
1
|
+
import type { IssuerListingParams } from "../../../api-client/paths";
|
|
2
|
+
import type { DataProvider } from "../../../types/data-provider";
|
|
1
3
|
import type { HeadlessPaneContext } from "../../../types/headless";
|
|
2
4
|
import type { TimeRange } from "../../../time-series/range";
|
|
3
5
|
import { clipPriceHistoryToRange } from "../../../time-series/history-window";
|
|
4
|
-
import { parsePublicTickerKey } from "../../../utils/exchanges";
|
|
6
|
+
import { canonicalExchange, parsePublicTickerKey } from "../../../utils/exchanges";
|
|
7
|
+
import { nonUsSecListingVenue } from "../../../utils/sec";
|
|
5
8
|
|
|
6
9
|
export async function resolveHeadlessInstrument(ctx: HeadlessPaneContext, symbol: string) {
|
|
7
10
|
return ctx.resolveInstrument ? ctx.resolveInstrument(symbol) : parsePublicTickerKey(symbol);
|
|
8
11
|
}
|
|
9
12
|
|
|
13
|
+
/**
|
|
14
|
+
* The venue and company an issuer lookup names for a listing abroad: the
|
|
15
|
+
* venue from the key or the saved listing, the company from the listing's own
|
|
16
|
+
* quote. A US listing or a symbol with no venue names neither.
|
|
17
|
+
*/
|
|
18
|
+
export async function resolveIssuerListing(
|
|
19
|
+
key: string,
|
|
20
|
+
exchange: string | undefined,
|
|
21
|
+
marketData: Pick<DataProvider, "getQuote">,
|
|
22
|
+
): Promise<IssuerListingParams> {
|
|
23
|
+
const venue = nonUsSecListingVenue(key, exchange);
|
|
24
|
+
if (!venue) return {};
|
|
25
|
+
try {
|
|
26
|
+
const quote = await marketData.getQuote(parsePublicTickerKey(key).symbol, venue);
|
|
27
|
+
const quoteVenue = canonicalExchange(quote.listingExchangeName || quote.exchangeName);
|
|
28
|
+
// A quote that prices another venue names another listing's company.
|
|
29
|
+
const name = !quoteVenue || quoteVenue === venue ? quote.name?.trim() : undefined;
|
|
30
|
+
return name ? { exchange: venue, name } : { exchange: venue };
|
|
31
|
+
} catch {
|
|
32
|
+
return { exchange: venue };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** `resolveIssuerListing` for a report's ticker, with the venue of the saved listing when the key has none. */
|
|
37
|
+
export async function resolveHeadlessIssuerListing(ctx: HeadlessPaneContext, key: string): Promise<IssuerListingParams> {
|
|
38
|
+
const { exchange } = await resolveHeadlessInstrument(ctx, key);
|
|
39
|
+
return resolveIssuerListing(key, exchange, ctx.marketData);
|
|
40
|
+
}
|
|
41
|
+
|
|
10
42
|
export async function loadHeadlessFinancials(ctx: HeadlessPaneContext, key: string) {
|
|
11
43
|
const { symbol, exchange } = await resolveHeadlessInstrument(ctx, key);
|
|
12
44
|
return ctx.marketData.getTickerFinancials(symbol, exchange ?? "");
|
|
@@ -180,10 +180,10 @@ export function useMembersMenu(registrationId: string, list: MemberList | null)
|
|
|
180
180
|
const registry = getSharedRegistry();
|
|
181
181
|
const symbols = list ? memberSymbols(list.members) : [];
|
|
182
182
|
if (!list || !symbols.length || !dialog || !registry) return null;
|
|
183
|
-
const destinations = MEMBER_DESTINATIONS.filter((destination) =>
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
183
|
+
const destinations = MEMBER_DESTINATIONS.filter((destination) => (
|
|
184
|
+
registry.paneTemplates.has(destination.templateId)
|
|
185
|
+
&& !registry.getDisabledPaneTemplateOwner?.(destination.templateId, disabledPlugins)
|
|
186
|
+
));
|
|
187
187
|
const choices = memberDestinationChoices(symbols, destinations);
|
|
188
188
|
const text = memberListText(list, symbols.length);
|
|
189
189
|
const openIn = () => {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { HeadlessPaneFreshness } from "../../../types/headless";
|
|
2
2
|
import type { Quote } from "../../../types/financials";
|
|
3
3
|
import { isQuoteStaleForCurrentSession } from "../../../market-data/quotes/freshness";
|
|
4
|
+
import { delayedFeedLagMs } from "../../../market-data/market/freshness";
|
|
5
|
+
import { apiClient } from "../../../api-client";
|
|
6
|
+
import { CLOUD_NEWS_DELAY_HOURS, hasProAccess } from "../../../api-client/plan-rules";
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* Freshness declarations shared by built-in reports. Each `fn` report ends
|
|
@@ -41,17 +44,62 @@ export const REFERENCE_DATA: HeadlessPaneFreshness = { status: "not-a-feed", bas
|
|
|
41
44
|
/** Company fundamentals, estimates and calendars: reported figures, not a feed. */
|
|
42
45
|
export const REPORTED_DATA: HeadlessPaneFreshness = { status: "not-a-feed", basis: "reported data" };
|
|
43
46
|
|
|
47
|
+
/** Gloom Cloud's delayed quotes run behind each venue by a lag the app knows (`delayedFeedLagMs`). */
|
|
48
|
+
const CLOUD_QUOTE_PROVIDER_ID = "gloomberb-cloud";
|
|
49
|
+
|
|
50
|
+
interface QuoteFreshnessFields {
|
|
51
|
+
dataSource?: "live" | "delayed";
|
|
52
|
+
stale?: boolean;
|
|
53
|
+
/** How far a delayed Gloom Cloud quote runs behind its venue's trades. */
|
|
54
|
+
delayMinutes?: number;
|
|
55
|
+
/** The venue whose sessions the quote follows, so the report can say whether its market is open. */
|
|
56
|
+
sessionExchange?: string;
|
|
57
|
+
marketState?: Quote["marketState"];
|
|
58
|
+
}
|
|
59
|
+
|
|
44
60
|
/**
|
|
45
61
|
* The standard freshness fields of one quote, spread into a report row so the
|
|
46
|
-
* report can say whether that row is live, delayed
|
|
62
|
+
* report can say whether that row is live, delayed (and by how much) or stale
|
|
63
|
+
* for its session, and where its market stands.
|
|
47
64
|
*/
|
|
48
65
|
export function quoteFreshnessFields(
|
|
49
66
|
quote: Quote | null | undefined,
|
|
50
67
|
now = Date.now(),
|
|
51
|
-
):
|
|
68
|
+
): QuoteFreshnessFields {
|
|
52
69
|
if (!quote) return {};
|
|
70
|
+
const exchange = quote.listingExchangeName || quote.exchangeName;
|
|
71
|
+
const delayed = quote.dataSource === "delayed" && quote.providerId === CLOUD_QUOTE_PROVIDER_ID;
|
|
53
72
|
return {
|
|
54
73
|
...(quote.dataSource === "live" || quote.dataSource === "delayed" ? { dataSource: quote.dataSource } : {}),
|
|
55
74
|
stale: isQuoteStaleForCurrentSession(quote, now),
|
|
75
|
+
...(delayed ? { delayMinutes: delayedFeedLagMs(exchange) / 60_000 } : {}),
|
|
76
|
+
...(exchange ? { sessionExchange: exchange } : {}),
|
|
77
|
+
...(quote.marketState ? { marketState: quote.marketState } : {}),
|
|
56
78
|
};
|
|
57
79
|
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Whether the Gloom Cloud session a report reads with has real-time access:
|
|
83
|
+
* no session reads as a free account; a session is asked for its plan. Null
|
|
84
|
+
* when the plan cannot be read, so the report does not guess a delay.
|
|
85
|
+
*/
|
|
86
|
+
export async function cloudRealtimeAccess(): Promise<boolean | null> {
|
|
87
|
+
if (!apiClient.isSignedIn()) return false;
|
|
88
|
+
try {
|
|
89
|
+
return hasProAccess(apiClient.getCurrentUser() ?? await apiClient.getSession());
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Gloom Cloud news stories, dated by the newest: held back
|
|
97
|
+
* `CLOUD_NEWS_DELAY_HOURS` without real-time access, the live wire with it.
|
|
98
|
+
* The list is a history, so its last story is not stale data.
|
|
99
|
+
*/
|
|
100
|
+
export function cloudNewsFreshness(realtime: boolean | null): HeadlessPaneFreshness {
|
|
101
|
+
const stories: HeadlessPaneFreshness = { observedKey: "publishedAt", oldest: null };
|
|
102
|
+
if (realtime === true) return { ...stories, status: "live" };
|
|
103
|
+
if (realtime === false) return { ...stories, status: "delayed", delayMinutes: CLOUD_NEWS_DELAY_HOURS * 60 };
|
|
104
|
+
return { ...stories, status: "not-a-feed", basis: "published stories" };
|
|
105
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { useCallback } from "react";
|
|
1
|
+
import { useCallback, useMemo } from "react";
|
|
2
2
|
import { ApiRequestError } from "../../../api-client/errors";
|
|
3
|
+
import { listingAbroad, type IssuerListingParams } from "../../../api-client/paths";
|
|
4
|
+
import type { TickerRecord } from "../../../types/ticker";
|
|
3
5
|
import { useAsyncResource } from "../../../react/async-resource";
|
|
4
6
|
import { usePaneTickerIdentity } from "../../../state/hooks/pane-ticker";
|
|
5
7
|
import { parsePublicTickerKey } from "../../../utils/exchanges";
|
|
@@ -22,6 +24,28 @@ export function listingIdentity(key: string | null | undefined, savedExchange =
|
|
|
22
24
|
return { symbol: parsed.symbol.toUpperCase(), exchange: parsed.exchange ?? savedExchange };
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
/**
|
|
28
|
+
* The venue and company an SEC issuer read names for the pane's listing, from
|
|
29
|
+
* the pane's ticker as the SEC filings pane reads it: the key's venue, else the
|
|
30
|
+
* saved one, and the company the listing was saved as (listingAbroad). Nothing
|
|
31
|
+
* for a US listing or a symbol with no venue. A name that is only the symbol
|
|
32
|
+
* is left out, so the server falls back to the name on a quote it holds.
|
|
33
|
+
*/
|
|
34
|
+
function issuerListing(ticker: TickerRecord | null | undefined): IssuerListingParams {
|
|
35
|
+
if (!ticker) return {};
|
|
36
|
+
const key = ticker.metadata.ticker.trim().toUpperCase();
|
|
37
|
+
const name = ticker.metadata.name?.trim();
|
|
38
|
+
const known = !!name && ![key, parsePublicTickerKey(key).symbol].includes(name.toUpperCase());
|
|
39
|
+
const abroad = listingAbroad(key, ticker.metadata.exchange, known ? name : undefined);
|
|
40
|
+
return { exchange: abroad?.exchange, name: abroad?.name };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** `issuerListing` as a stable value, for loader dependencies. */
|
|
44
|
+
export function useIssuerListing(ticker: TickerRecord | null | undefined): IssuerListingParams {
|
|
45
|
+
const { exchange, name } = issuerListing(ticker);
|
|
46
|
+
return useMemo(() => ({ exchange, name }), [exchange, name]);
|
|
47
|
+
}
|
|
48
|
+
|
|
25
49
|
/**
|
|
26
50
|
* The pane's symbol for research panes that load their own data. Identity only:
|
|
27
51
|
* it does not observe the quote, so a price tick does not re-render the pane.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import type { DataTableCell, DataTableVisibleRange, PaneFooterSegment } from "../../../components";
|
|
3
3
|
import { useQuoteEntries } from "../../../market-data/hooks";
|
|
4
|
-
import { marketStateColor, marketStateLabel } from "../../../market-data/market/status";
|
|
4
|
+
import { getRegularSessionDisplay, marketStateColor, marketStateLabel } from "../../../market-data/market/status";
|
|
5
5
|
import type { InstrumentRef } from "../../../market-data/request-types";
|
|
6
6
|
import type { QueryEntry } from "../../../market-data/result-types";
|
|
7
7
|
import { buildQuoteKey, resolveEntryData } from "../../../market-data/selectors";
|
|
@@ -466,12 +466,31 @@ export type QuoteBoardCellKind = "status" | "price" | "change" | "changePercent"
|
|
|
466
466
|
export interface QuoteBoardCellFormat {
|
|
467
467
|
/** Spell out the session instead of a colored dot. */
|
|
468
468
|
sessionText?: boolean;
|
|
469
|
-
/** Called with a finite price. */
|
|
469
|
+
/** Called with the quote at its headline: a finite price. */
|
|
470
470
|
formatPrice: (quote: Quote) => string;
|
|
471
|
-
/** Called with a finite change; the text carries its sign. */
|
|
471
|
+
/** Called with the quote at its headline: a finite change; the text carries its sign. */
|
|
472
472
|
formatChange: (quote: Quote) => string;
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
+
/** Each quote's figures as the board shows them, cached so a held quote keeps one object. */
|
|
476
|
+
const boardHeadlines = new WeakMap<Quote, Quote>();
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* The quote with its regular session's price and move (getRegularSessionDisplay),
|
|
480
|
+
* as `ticker` reads it: after the close its close and move, never an extended
|
|
481
|
+
* print, which a board of these columns has nowhere to show.
|
|
482
|
+
*/
|
|
483
|
+
export function boardHeadlineQuote(quote: Quote): Quote {
|
|
484
|
+
const cached = boardHeadlines.get(quote);
|
|
485
|
+
if (cached) return cached;
|
|
486
|
+
const display = getRegularSessionDisplay(quote);
|
|
487
|
+
const headline = !display || (display.price === quote.price && display.change === quote.change && display.changePercent === quote.changePercent)
|
|
488
|
+
? quote
|
|
489
|
+
: { ...quote, price: display.price, change: display.change ?? Number.NaN, changePercent: display.changePercent ?? Number.NaN };
|
|
490
|
+
boardHeadlines.set(quote, headline);
|
|
491
|
+
return headline;
|
|
492
|
+
}
|
|
493
|
+
|
|
475
494
|
/** A row is loading while it has no quote yet, so none of its cells shows a no-data dash. */
|
|
476
495
|
export function isBoardRowLoading(state: BoardQuoteState | undefined): boolean {
|
|
477
496
|
return !state?.quote && (state?.loading ?? true);
|
|
@@ -482,7 +501,7 @@ export function renderQuoteBoardCell(
|
|
|
482
501
|
state: BoardQuoteState | undefined,
|
|
483
502
|
format: QuoteBoardCellFormat,
|
|
484
503
|
): DataTableCell {
|
|
485
|
-
const quote = state?.quote;
|
|
504
|
+
const quote = state?.quote ? boardHeadlineQuote(state.quote) : state?.quote;
|
|
486
505
|
// One row must not mix a loading marker with a no-data marker.
|
|
487
506
|
if (isBoardRowLoading(state)) return { text: kind === "status" ? "" : "…", color: colors.textDim };
|
|
488
507
|
|
|
@@ -517,7 +536,8 @@ export function renderQuoteBoardCell(
|
|
|
517
536
|
case "time":
|
|
518
537
|
return {
|
|
519
538
|
text: formatQuoteTime(quote?.lastUpdated),
|
|
520
|
-
|
|
539
|
+
// A Date, so the export writes it whole and a rendered-view report dates the board by its quotes.
|
|
540
|
+
value: quote?.lastUpdated ? new Date(quote.lastUpdated) : null,
|
|
521
541
|
color: colors.textDim,
|
|
522
542
|
};
|
|
523
543
|
}
|