gloomberb 0.16.2 → 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/account-cache.ts +45 -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/command-options.ts +74 -0
- package/src/cli/commands/command-utils.ts +29 -0
- package/src/cli/commands/crypto-hints.ts +12 -4
- package/src/cli/commands/market.ts +524 -148
- package/src/cli/commands/options-chain.ts +172 -0
- package/src/cli/commands/options-leaps.ts +422 -0
- package/src/cli/commands/overview.ts +223 -42
- 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 +200 -73
- package/src/cli/desktop-pane-shot.ts +100 -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 +40 -7
- package/src/cli/history-rows.ts +187 -15
- package/src/cli/index-roots.ts +51 -0
- package/src/cli/index.ts +21 -13
- package/src/cli/listing-arg.ts +211 -3
- package/src/cli/not-a-ticker.ts +37 -0
- package/src/cli/options.ts +55 -7
- package/src/cli/pane-functions/capabilities.ts +13 -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 +89 -28
- package/src/cli/pane-functions/freshness.ts +205 -26
- package/src/cli/pane-functions/function-help.ts +71 -24
- package/src/cli/pane-functions/headless.ts +168 -35
- package/src/cli/pane-functions/index.ts +12 -2
- 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 +12 -1
- package/src/cli/pane-functions/resolver.ts +32 -8
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +109 -23
- package/src/cli/registry.ts +8 -4
- package/src/cli/report-tables.ts +460 -0
- package/src/cli/result.ts +63 -16
- 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/export.ts +6 -51
- package/src/components/data-table/view.tsx +31 -10
- 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 +67 -35
- package/src/components/layout/shell/cursor-occlusion.ts +6 -5
- package/src/components/layout/shell/drag/index.ts +16 -7
- package/src/components/layout/shell/drag/live.ts +193 -0
- package/src/components/layout/shell/drag/overlays.tsx +179 -35
- package/src/components/layout/shell/drag/runtime.ts +62 -50
- package/src/components/layout/shell/drag/slide.ts +10 -0
- package/src/components/layout/shell/index.tsx +84 -57
- package/src/components/layout/shell/layout-state.ts +5 -11
- package/src/components/layout/shell/menu.ts +28 -1
- package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
- package/src/components/layout/shell/native/surfaces.ts +30 -14
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +461 -180
- 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/terminal-pointer-runtime.ts +18 -18
- package/src/components/layout/shell/visible-layout.ts +44 -1
- package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
- 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/data/json-persistence.ts +17 -3
- package/src/data/json-storage.ts +2 -0
- package/src/data/memory-resource-store.ts +46 -0
- 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 +156 -11
- 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/freshness.ts +5 -0
- 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/pane-model.ts +10 -6
- 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/attention/pane.tsx +1 -1
- 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/catalysts/detail.tsx +1 -1
- 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-attention/pane.tsx +1 -1
- 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/client.ts +3 -6
- package/src/plugins/builtin/holders/format.ts +31 -1
- package/src/plugins/builtin/holders/headless.ts +32 -6
- package/src/plugins/builtin/holders/pane.tsx +25 -13
- package/src/plugins/builtin/holders/report-header.ts +46 -5
- 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 +121 -45
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
- 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/members/pane.tsx +10 -0
- 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/perps/pane.tsx +1 -1
- 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/account-metrics.ts +16 -1
- package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
- package/src/plugins/builtin/portfolio-list/cli/render.ts +89 -18
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
- package/src/plugins/builtin/portfolio-list/headless.ts +40 -7
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
- package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
- 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/research/relative-valuation-model.ts +7 -3
- 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/model.ts +2 -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 +260 -0
- 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/short-volume/pane.tsx +1 -1
- package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
- package/src/plugins/builtin/supply-chain/model.ts +1 -1
- package/src/plugins/builtin/themes/pane.tsx +3 -0
- package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
- package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
- package/src/plugins/builtin/ticker-detail/headless.ts +63 -25
- package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
- 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/basemap-layer.tsx +2 -0
- package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
- package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
- package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
- package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -6
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
- package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
- package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
- package/src/plugins/builtin/world-venue-map/model.ts +16 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
- 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/browser/app-services.ts +3 -1
- package/src/renderers/dom/data-table/dom.ts +31 -0
- package/src/renderers/dom/data-table/index.tsx +5 -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 +113 -17
- package/src/renderers/dom/host/drag-shield.ts +35 -0
- package/src/renderers/dom/host/live-frame.ts +62 -0
- package/src/renderers/dom/host/mouse.ts +8 -1
- package/src/renderers/dom/host/scroll-box.tsx +6 -5
- package/src/renderers/dom/host/tabs.tsx +3 -2
- package/src/renderers/dom/input-host.tsx +6 -1
- package/src/renderers/dom/styles.css +26 -5
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +15 -1
- package/src/renderers/electrobun/shared/protocol.ts +12 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +4 -1
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
- package/src/renderers/electrobun/view/host/native.ts +0 -10
- package/src/renderers/electrobun/view/host/window-move.ts +28 -0
- package/src/renderers/electrobun/view/main.tsx +4 -1
- package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
- package/src/renderers/electrobun/view/ui-host.tsx +2 -5
- package/src/renderers/opentui/input-host.tsx +6 -1
- package/src/renderers/opentui/start.tsx +9 -0
- package/src/renderers/opentui/ui-host.tsx +7 -1
- package/src/shares/portable-layout.ts +16 -6
- package/src/sources/gloomberb-cloud/index.ts +19 -10
- package/src/sources/gloomberb-cloud/normalizers.ts +20 -21
- package/src/sources/listing-symbols.ts +11 -5
- package/src/sources/provider-errors.ts +88 -3
- package/src/sources/provider-router/batches.ts +3 -3
- package/src/sources/provider-router/cached-routes.ts +8 -4
- package/src/sources/provider-router/financial-routes.ts +13 -7
- package/src/sources/provider-router/financials.ts +106 -16
- 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 +24 -6
- package/src/sources/sec-edgar.ts +68 -8
- package/src/state/pane-scroll-registry.tsx +67 -35
- 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/margin-account.ts +108 -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/resolution.ts +15 -0
- package/src/time-series/resolve.ts +1 -14
- package/src/types/config.ts +61 -1
- package/src/types/financials.ts +32 -3
- 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 +16 -0
- package/src/ui/index.tsx +10 -3
- package/src/ui/scroll-box-axis.ts +26 -0
- package/src/utils/cli-output.ts +14 -1
- package/src/utils/currency-units.ts +8 -3
- package/src/utils/depositary-receipt.ts +54 -10
- package/src/utils/display-number.ts +63 -0
- package/src/utils/exchanges.ts +13 -0
- package/src/utils/format.ts +2 -1
- package/src/utils/fundamentals.ts +17 -1
- package/src/utils/fx-market-hours.ts +34 -0
- package/src/utils/option-expiry.ts +126 -0
- package/src/utils/price-history-integrity.ts +19 -6
- 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
|
@@ -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.
|
|
@@ -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
|
+
}
|