gloomberb 0.16.0 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/package.json +2 -1
- package/src/api-client/beneficial-owners.ts +92 -0
- package/src/api-client/data.ts +44 -11
- package/src/api-client/errors.ts +12 -0
- package/src/api-client/geo.ts +363 -0
- package/src/api-client/index.ts +5 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/members.ts +26 -0
- package/src/api-client/paths.ts +30 -1
- package/src/api-client/perps.ts +41 -0
- package/src/api-client/request.ts +5 -2
- package/src/api-client/types.ts +9 -1
- package/src/app/global-shortcuts.ts +14 -6
- package/src/app/keybindings/actions.ts +1 -1
- package/src/app/runtime/update.ts +38 -2
- package/src/app.tsx +10 -6
- package/src/brokers/account-cache.ts +24 -0
- package/src/cli/commands/command-utils.ts +8 -0
- package/src/cli/commands/crypto-hints.ts +75 -0
- package/src/cli/commands/market.ts +414 -106
- package/src/cli/commands/overview.ts +77 -32
- package/src/cli/commands/ticker.ts +101 -43
- package/src/cli/desktop-pane-shot.ts +34 -4
- package/src/cli/freshness.ts +61 -0
- package/src/cli/help.ts +1 -0
- package/src/cli/helpers.ts +4 -8
- package/src/cli/index.ts +12 -2
- package/src/cli/listing-arg.ts +321 -0
- package/src/cli/pane-functions/access-gate.ts +42 -0
- package/src/cli/pane-functions/catalog.ts +28 -1
- package/src/cli/pane-functions/data.ts +3 -1
- package/src/cli/pane-functions/dom.ts +20 -2
- package/src/cli/pane-functions/freshness.ts +400 -0
- package/src/cli/pane-functions/function-help.ts +36 -22
- package/src/cli/pane-functions/headless.ts +39 -11
- package/src/cli/pane-functions/index.ts +15 -4
- package/src/cli/pane-functions/options.ts +3 -0
- package/src/cli/pane-functions/report.ts +4 -0
- package/src/cli/pane-functions/resolver.ts +35 -7
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +7 -3
- package/src/cli/result.ts +41 -27
- package/src/cli/ticker-resolution.ts +7 -5
- package/src/components/chart/composite/composite-chart.tsx +88 -2536
- package/src/components/chart/composite/drawing-store.ts +59 -0
- package/src/components/chart/composite/legend.tsx +319 -0
- package/src/components/chart/composite/panel-bitmap.ts +139 -0
- package/src/components/chart/composite/panel-gestures.ts +59 -0
- package/src/components/chart/composite/panel-labels.ts +107 -0
- package/src/components/chart/composite/panel-surface.tsx +876 -0
- package/src/components/chart/composite/tool-catalog.ts +98 -0
- package/src/components/chart/composite/toolbar.tsx +186 -0
- package/src/components/chart/composite/tools.ts +8 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +703 -0
- package/src/components/chart/composite/use-composite-viewport.ts +143 -0
- package/src/components/command-bar/commands/collection.ts +5 -1
- package/src/components/command-bar/list/model.ts +5 -0
- package/src/components/command-bar/list/view.tsx +16 -3
- package/src/components/command-bar/pane-templates/workflow.ts +1 -0
- package/src/components/command-bar/routes/root/command-items.ts +9 -2
- package/src/components/command-bar/routes/root/plugin-install.ts +2 -2
- package/src/components/command-bar/routes/root/provider-search.ts +19 -5
- package/src/components/command-bar/routes/root/runtime.ts +14 -5
- package/src/components/command-bar/routes/ticker-search/actions.ts +5 -0
- package/src/components/command-bar/routes/ticker-search/results.ts +31 -5
- package/src/components/command-bar/routes/ticker-search/route.ts +2 -2
- package/src/components/command-bar/routing/actions.ts +2 -0
- package/src/components/command-bar/selection-runtime.ts +38 -0
- package/src/components/command-bar/surface/index.tsx +7 -2
- package/src/components/command-bar/workflow/ops.ts +1 -1
- package/src/components/command-bar/workflow/tickers.ts +6 -0
- package/src/components/data-table/view.tsx +29 -10
- package/src/components/feedback-dialog.tsx +2 -1
- package/src/components/form-modal/field-row.tsx +2 -2
- package/src/components/index.ts +2 -2
- package/src/components/layout/detached-pane-shell.tsx +6 -3
- package/src/components/layout/floating-pane.tsx +6 -0
- package/src/components/layout/header.tsx +24 -7
- package/src/components/layout/missing-pane.tsx +2 -2
- package/src/components/layout/pane/content.tsx +23 -10
- package/src/components/layout/pane/error-boundary.tsx +103 -0
- package/src/components/layout/pane/footer/registration.tsx +68 -23
- package/src/components/layout/pane/header.tsx +61 -28
- package/src/components/layout/pane/index.tsx +6 -0
- package/src/components/layout/pane/title.ts +31 -7
- package/src/components/layout/shell/drag/index.ts +6 -5
- package/src/components/layout/shell/drag/runtime.ts +3 -0
- package/src/components/layout/shell/index.tsx +6 -0
- package/src/components/layout/shell/menu.ts +2 -1
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +29 -10
- package/src/components/layout/shell/terminal-pointer-runtime.ts +24 -3
- package/src/components/layout/shell/ticker-link-menu.ts +23 -9
- package/src/components/metric-treemap/heat-surface.tsx +745 -0
- package/src/components/metric-treemap/heat-tiles.ts +228 -0
- package/src/components/metric-treemap/index.tsx +2 -0
- package/src/components/metric-treemap/layout.ts +8 -0
- package/src/components/metric-treemap/scene.ts +432 -0
- package/src/components/onboarding/onboarding-keyboard.ts +311 -0
- package/src/components/onboarding/onboarding-wizard.tsx +101 -788
- package/src/components/onboarding/use-onboarding-broker-form.ts +263 -0
- package/src/components/onboarding/use-onboarding-progress.ts +298 -0
- package/src/components/onboarding/use-onboarding-upgrade.ts +83 -0
- package/src/components/pane-settings-dialog/field-dialogs.tsx +1 -0
- package/src/components/ticker/list-table-view.tsx +13 -1
- package/src/components/toggle-list.tsx +38 -12
- package/src/components/ui/country-flag-art.tsx +449 -0
- package/src/components/ui/country-flag.tsx +41 -0
- package/src/components/ui/data-table/types.ts +13 -0
- package/src/components/ui/fields.tsx +8 -0
- package/src/components/ui/icon.tsx +2 -0
- package/src/components/ui/index.ts +4 -0
- package/src/components/ui/multi-select/dialog.tsx +108 -22
- package/src/components/ui/range-track.tsx +53 -0
- package/src/components/ui/status.tsx +8 -2
- package/src/components/ui/tabs.tsx +11 -4
- package/src/components/ui/text-prompt-dialog.tsx +4 -2
- package/src/core/state/app/types.ts +10 -1
- package/src/data/config/store/normalize.ts +17 -1
- package/src/i18n/es.ts +24 -4
- package/src/i18n/ja.ts +24 -4
- package/src/i18n/ko.ts +24 -4
- package/src/i18n/zh-cn.ts +24 -4
- package/src/i18n/zh-tw.ts +24 -4
- package/src/layout/ticker-navigation.ts +28 -2
- package/src/market-data/coordinator/chart.ts +1 -1
- package/src/market-data/coordinator/quotes.ts +23 -4
- package/src/market-data/fx-closes.ts +72 -0
- package/src/market-data/market/freshness.ts +78 -6
- package/src/market-data/market/session-close-note.ts +57 -0
- package/src/market-data/market/status.ts +151 -0
- package/src/market-data/published-apac-sessions.ts +66 -0
- package/src/market-data/quotes/contributions.ts +8 -0
- package/src/market-data/quotes/freshness.ts +36 -0
- package/src/market-data/quotes/resolution.ts +24 -8
- package/src/market-data/quotes/screener-live-quotes.ts +49 -4
- package/src/market-data/quotes/time.ts +16 -0
- package/src/plugins/builtin/account-management/pane.tsx +4 -0
- package/src/plugins/builtin/alerts/cli.ts +10 -5
- package/src/plugins/builtin/alerts/index.tsx +1 -0
- package/src/plugins/builtin/analytics/metrics.ts +8 -3
- package/src/plugins/builtin/analytics/risk-client.ts +330 -79
- package/src/plugins/builtin/analytics/risk-fx.ts +102 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +61 -49
- package/src/plugins/builtin/analytics/risk-math.ts +33 -13
- package/src/plugins/builtin/analytics/risk-model.ts +282 -64
- package/src/plugins/builtin/analytics/risk-pane.tsx +62 -30
- package/src/plugins/builtin/analytics/risk-test-data.ts +206 -0
- package/src/plugins/builtin/attention/headless.ts +6 -4
- package/src/plugins/builtin/attention/index.tsx +1 -1
- package/src/plugins/builtin/attention/pane.tsx +1 -1
- package/src/plugins/builtin/awards/headless.ts +1 -0
- package/src/plugins/builtin/awards/pane.tsx +1 -1
- package/src/plugins/builtin/backtest/headless.ts +1 -0
- package/src/plugins/builtin/bond-calculator/headless.ts +1 -0
- package/src/plugins/builtin/buildout/cells.tsx +15 -12
- package/src/plugins/builtin/buildout/detail/company.tsx +3 -3
- package/src/plugins/builtin/buildout/detail/index.tsx +0 -1
- package/src/plugins/builtin/buildout/detail/intel.tsx +6 -6
- package/src/plugins/builtin/buildout/detail/site.tsx +4 -7
- package/src/plugins/builtin/buildout/detail/ui.tsx +0 -19
- package/src/plugins/builtin/buildout/index.tsx +1 -0
- package/src/plugins/builtin/catalysts/headless.ts +3 -1
- package/src/plugins/builtin/catalysts/index.tsx +2 -2
- package/src/plugins/builtin/catalysts/pane.tsx +3 -1
- package/src/plugins/builtin/cds/headless.ts +2 -0
- package/src/plugins/builtin/cds/index.tsx +1 -0
- package/src/plugins/builtin/central-bank-rates/headless.ts +1 -0
- package/src/plugins/builtin/chart-composer/headless.ts +38 -3
- package/src/plugins/builtin/chart-composer/index.tsx +3 -1
- package/src/plugins/builtin/chart-composer/series-expression.ts +18 -2
- package/src/plugins/builtin/chat/channels.ts +19 -3
- package/src/plugins/builtin/chat/content/composer-runtime.ts +28 -13
- package/src/plugins/builtin/chat/content/footer.tsx +15 -6
- package/src/plugins/builtin/chat/content/index.tsx +120 -21
- package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +261 -113
- package/src/plugins/builtin/chat/content/transcript.tsx +20 -2
- package/src/plugins/builtin/chat/direct-messages.ts +200 -0
- package/src/plugins/builtin/chat/index.tsx +1 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +3 -0
- package/src/plugins/builtin/chat/message/header.tsx +16 -6
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +72 -32
- package/src/plugins/builtin/chat/message/profile-popover.tsx +96 -12
- package/src/plugins/builtin/chat/message/terminal.tsx +3 -0
- package/src/plugins/builtin/chat/message/types.ts +14 -2
- package/src/plugins/builtin/chat/profile-popover.ts +18 -13
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -0
- package/src/plugins/builtin/cloud/askg/executor.ts +2 -1
- package/src/plugins/builtin/cloud/plugin.tsx +1 -0
- package/src/plugins/builtin/cloud/team/module.tsx +1 -0
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -0
- package/src/plugins/builtin/cloud-tweets/registration.ts +1 -0
- package/src/plugins/builtin/comment-letters/index.tsx +1 -0
- package/src/plugins/builtin/company-attention/headless.ts +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +1 -1
- package/src/plugins/builtin/company-kpis/headless.ts +1 -0
- package/src/plugins/builtin/congress-trades/headless.ts +1 -0
- package/src/plugins/builtin/correlation/compute.ts +59 -16
- package/src/plugins/builtin/correlation/geo.ts +34 -0
- package/src/plugins/builtin/correlation/headless-schema.ts +4 -1
- package/src/plugins/builtin/correlation/headless.ts +42 -8
- package/src/plugins/builtin/correlation/history.ts +2 -0
- package/src/plugins/builtin/correlation/index.tsx +68 -14
- package/src/plugins/builtin/correlation/matrix/model.ts +17 -2
- package/src/plugins/builtin/correlation/matrix/selection.ts +5 -3
- package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
- package/src/plugins/builtin/correlation/relationship/pane.tsx +4 -2
- package/src/plugins/builtin/correlation/relationship/view-model.ts +16 -1
- package/src/plugins/builtin/correlation/settings.ts +1 -1
- package/src/plugins/builtin/cot/headless.ts +6 -3
- package/src/plugins/builtin/cot/index.tsx +3 -3
- package/src/plugins/builtin/cot/model.ts +36 -3
- package/src/plugins/builtin/cpi/headless.ts +4 -0
- package/src/plugins/builtin/credit-boards/headless.ts +2 -0
- package/src/plugins/builtin/credit-conditions/headless.ts +1 -0
- package/src/plugins/builtin/credit-documents/gauge.tsx +5 -8
- package/src/plugins/builtin/credit-documents/headless.ts +2 -0
- package/src/plugins/builtin/crypto-board/headless.ts +3 -0
- package/src/plugins/builtin/custom-view/loader.ts +1 -1
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -0
- package/src/plugins/builtin/debt-maturities/wall-bar.tsx +14 -41
- package/src/plugins/builtin/dividend-yield/client.ts +4 -2
- package/src/plugins/builtin/dividend-yield/headless.ts +1 -0
- package/src/plugins/builtin/doe/headless.ts +7 -0
- package/src/plugins/builtin/doe/range-bar.tsx +7 -37
- package/src/plugins/builtin/earnings/client.ts +2 -2
- package/src/plugins/builtin/earnings/headless.ts +5 -0
- package/src/plugins/builtin/earnings-calls/headless.ts +1 -0
- package/src/plugins/builtin/earnings-calls/index.tsx +1 -0
- package/src/plugins/builtin/earnings-ripple/client.ts +91 -18
- package/src/plugins/builtin/earnings-ripple/headless.ts +28 -8
- package/src/plugins/builtin/earnings-ripple/index.tsx +2 -2
- package/src/plugins/builtin/earnings-ripple/model.ts +97 -7
- package/src/plugins/builtin/earnings-ripple/pane.tsx +148 -23
- package/src/plugins/builtin/econ/index.tsx +1 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +2 -0
- package/src/plugins/builtin/equity-screener/headless.ts +2 -0
- package/src/plugins/builtin/estimate-revisions/headless.ts +1 -0
- package/src/plugins/builtin/executives/index.tsx +1 -0
- package/src/plugins/builtin/exposure/headless.ts +3 -1
- package/src/plugins/builtin/fear-greed/headless.ts +2 -0
- package/src/plugins/builtin/filing-events/index.tsx +1 -0
- package/src/plugins/builtin/futures-curve/client.ts +12 -3
- package/src/plugins/builtin/futures-curve/headless.ts +39 -10
- package/src/plugins/builtin/futures-curve/index.tsx +3 -3
- package/src/plugins/builtin/futures-curve/model.ts +188 -4
- package/src/plugins/builtin/futures-curve/pane.tsx +51 -9
- package/src/plugins/builtin/fx-matrix/direction.ts +81 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +71 -33
- package/src/plugins/builtin/fx-matrix/live-legs.ts +17 -0
- package/src/plugins/builtin/fx-matrix/pairs.ts +68 -14
- package/src/plugins/builtin/fx-matrix/settings.ts +62 -0
- package/src/plugins/builtin/gpu/headless.ts +7 -3
- package/src/plugins/builtin/gpu/ladder.tsx +26 -30
- package/src/plugins/builtin/gpu/model.ts +27 -1
- package/src/plugins/builtin/holders/beneficial-client.ts +209 -0
- package/src/plugins/builtin/holders/beneficial-model.ts +279 -0
- package/src/plugins/builtin/holders/beneficial-report.ts +126 -0
- package/src/plugins/builtin/holders/beneficial-view.tsx +241 -0
- package/src/plugins/builtin/holders/headless.ts +139 -5
- package/src/plugins/builtin/holders/index.tsx +10 -3
- package/src/plugins/builtin/holders/pane.tsx +20 -3
- package/src/plugins/builtin/holders/report-header.ts +95 -0
- package/src/plugins/builtin/holders/table-model.ts +20 -5
- package/src/plugins/builtin/holders/thirteenf-match.ts +29 -1
- package/src/plugins/builtin/holders/types.ts +2 -1
- package/src/plugins/builtin/insider/headless.ts +2 -0
- package/src/plugins/builtin/ipo-calendar/headless.ts +1 -0
- package/src/plugins/builtin/iv-history/headless.ts +4 -1
- package/src/plugins/builtin/jobs/headless.ts +1 -0
- package/src/plugins/builtin/jobs/index.tsx +1 -0
- package/src/plugins/builtin/jobs/share-bars.tsx +5 -28
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -0
- package/src/plugins/builtin/macro-day/headless.ts +2 -0
- package/src/plugins/builtin/market-halts/index.tsx +1 -0
- package/src/plugins/builtin/market-heatmap/data.ts +30 -2
- package/src/plugins/builtin/market-heatmap/headless.ts +135 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +245 -81
- package/src/plugins/builtin/market-heatmap/live.ts +88 -0
- package/src/plugins/builtin/market-heatmap/model.ts +146 -0
- package/src/plugins/builtin/market-heatmap/portfolio.ts +50 -11
- package/src/plugins/builtin/market-movers/client.ts +3 -2
- package/src/plugins/builtin/market-movers/headless.ts +10 -1
- package/src/plugins/builtin/market-movers/screener.ts +18 -5
- package/src/plugins/builtin/market-valuation/headless.ts +2 -0
- package/src/plugins/builtin/members/client.ts +53 -0
- package/src/plugins/builtin/members/headless.ts +35 -0
- package/src/plugins/builtin/members/index.tsx +20 -0
- package/src/plugins/builtin/members/model.ts +68 -0
- package/src/plugins/builtin/members/pane.tsx +175 -0
- package/src/plugins/builtin/mna/distress/headless.ts +1 -0
- package/src/plugins/builtin/mna/headless.ts +1 -0
- package/src/plugins/builtin/mna/index.tsx +2 -1
- package/src/plugins/builtin/mna/pane.tsx +5 -2
- package/src/plugins/builtin/money-markets/headless.ts +1 -0
- package/src/plugins/builtin/news/headless.ts +2 -0
- package/src/plugins/builtin/news/wire/index.tsx +8 -1
- package/src/plugins/builtin/notes/cli.ts +14 -6
- package/src/plugins/builtin/notes/index.tsx +1 -0
- package/src/plugins/builtin/openfda/index.tsx +1 -0
- package/src/plugins/builtin/options/index.tsx +2 -1
- package/src/plugins/builtin/options-calculator/headless.ts +2 -0
- package/src/plugins/builtin/options-positioning/headless.ts +3 -0
- package/src/plugins/builtin/options-scenario/headless.ts +5 -0
- package/src/plugins/builtin/pe-band/client.ts +74 -2
- package/src/plugins/builtin/pe-band/headless.ts +26 -8
- package/src/plugins/builtin/pe-band/model.ts +102 -22
- package/src/plugins/builtin/pe-band/pane.tsx +34 -16
- package/src/plugins/builtin/pe-band/report-dates.ts +78 -0
- package/src/plugins/builtin/perps/client.ts +66 -5
- package/src/plugins/builtin/perps/equity-identity.ts +16 -0
- package/src/plugins/builtin/perps/equity-row.tsx +6 -4
- package/src/plugins/builtin/perps/headless.ts +144 -16
- package/src/plugins/builtin/perps/index.tsx +6 -6
- package/src/plugins/builtin/perps/model.ts +189 -12
- package/src/plugins/builtin/perps/pane.tsx +248 -47
- package/src/plugins/builtin/plugin-marketplace/activation.ts +19 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -2
- package/src/plugins/builtin/plugin-marketplace/store.ts +2 -0
- package/src/plugins/builtin/portfolio-list/allocation.ts +245 -0
- package/src/plugins/builtin/portfolio-list/cached-account.ts +33 -0
- package/src/plugins/builtin/portfolio-list/cell-version.ts +19 -1
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +328 -29
- package/src/plugins/builtin/portfolio-list/cli/render.ts +230 -39
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +67 -2
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +35 -31
- package/src/plugins/builtin/portfolio-list/column-values.ts +124 -47
- package/src/plugins/builtin/portfolio-list/headless.ts +98 -16
- package/src/plugins/builtin/portfolio-list/index.tsx +14 -3
- package/src/plugins/builtin/portfolio-list/live-valuation.ts +7 -5
- package/src/plugins/builtin/portfolio-list/mutations.ts +55 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +6 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +47 -13
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +43 -2
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +48 -5
- package/src/plugins/builtin/portfolio-list/settings.ts +33 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +24 -1
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +26 -7
- package/src/plugins/builtin/power/headless.ts +3 -1
- package/src/plugins/builtin/rate-path/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/relative-rotation/headless.ts +2 -0
- package/src/plugins/builtin/research/analyst-headless.ts +3 -0
- package/src/plugins/builtin/research/events-headless.ts +2 -0
- package/src/plugins/builtin/research/headless.ts +2 -0
- package/src/plugins/builtin/research/index.tsx +1 -0
- package/src/plugins/builtin/research/relative-valuation-headless.ts +10 -5
- package/src/plugins/builtin/research/relative-valuation-model.ts +11 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +3 -3
- package/src/plugins/builtin/research-search/index.tsx +1 -0
- package/src/plugins/builtin/revenue-breakdown/headless.ts +2 -0
- package/src/plugins/builtin/reverse-dcf/headless.ts +2 -0
- package/src/plugins/builtin/risk-factors/headless.ts +2 -0
- package/src/plugins/builtin/scanner/flow-history.ts +2 -13
- package/src/plugins/builtin/scanner/headless.ts +14 -0
- package/src/plugins/builtin/scanner/hilo-bars.tsx +12 -70
- package/src/plugins/builtin/scanner/hilo-model.ts +0 -18
- package/src/plugins/builtin/seasonality/headless.ts +2 -0
- package/src/plugins/builtin/sec/command-bar-search.ts +14 -4
- package/src/plugins/builtin/sec/filing-content.ts +42 -17
- package/src/plugins/builtin/sec/forms.ts +12 -0
- package/src/plugins/builtin/sec/headless.ts +2 -0
- package/src/plugins/builtin/sectors/client.ts +1 -0
- package/src/plugins/builtin/sectors/headless.ts +9 -1
- package/src/plugins/builtin/sectors/move-bar.tsx +10 -32
- package/src/plugins/builtin/sectors/sector-model.ts +2 -0
- package/src/plugins/builtin/shared/crypto-pair.ts +14 -0
- package/src/plugins/builtin/shared/locked-rows.tsx +1 -1
- package/src/plugins/builtin/shared/report-freshness.ts +57 -0
- package/src/plugins/builtin/shared/research-cells.tsx +4 -9
- package/src/plugins/builtin/short-interest/headless.ts +2 -0
- package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
- package/src/plugins/builtin/short-volume/headless.ts +1 -0
- package/src/plugins/builtin/social-mentions/headless.ts +1 -0
- package/src/plugins/builtin/social-mentions/index.tsx +1 -1
- package/src/plugins/builtin/supply-chain/headless.ts +7 -1
- package/src/plugins/builtin/supply-chain/index.tsx +10 -1
- package/src/plugins/builtin/supply-chain/model.ts +27 -2
- package/src/plugins/builtin/supply-chain/pane.tsx +71 -32
- package/src/plugins/builtin/themes/headless.ts +3 -0
- package/src/plugins/builtin/thirteenf/headless.ts +110 -25
- package/src/plugins/builtin/thirteenf/index.tsx +2 -1
- package/src/plugins/builtin/thirteenf/report-notes.ts +149 -0
- package/src/plugins/builtin/ticker-detail/headless.ts +7 -1
- package/src/plugins/builtin/ticker-detail/index.tsx +1 -0
- package/src/plugins/builtin/ticker-detail/overview/components.tsx +5 -60
- package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -2
- package/src/plugins/builtin/ticker-detail/overview/pane.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +26 -26
- package/src/plugins/builtin/ticker-detail/pane.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +7 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +20 -4
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -2
- package/src/plugins/builtin/time-sales/headless.ts +5 -1
- package/src/plugins/builtin/treasury-auctions/headless.ts +1 -0
- package/src/plugins/builtin/vol-surface/index.tsx +1 -1
- package/src/plugins/builtin/volatility/headless.ts +6 -1
- package/src/plugins/builtin/world-indices/headless.ts +2 -0
- package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +157 -0
- package/src/plugins/builtin/world-venue-map/basemap.ts +214 -0
- package/src/plugins/builtin/world-venue-map/client.ts +45 -0
- package/src/plugins/builtin/world-venue-map/entity-detail.tsx +147 -0
- package/src/plugins/builtin/world-venue-map/entity-table.ts +59 -0
- package/src/plugins/builtin/world-venue-map/evidence.ts +77 -0
- package/src/plugins/builtin/world-venue-map/feed.ts +293 -0
- package/src/plugins/builtin/world-venue-map/geo-draw.tsx +248 -0
- package/src/plugins/builtin/world-venue-map/geo-series.ts +142 -0
- package/src/plugins/builtin/world-venue-map/geo-svg.tsx +447 -0
- package/src/plugins/builtin/world-venue-map/headless.ts +146 -0
- package/src/plugins/builtin/world-venue-map/ids.ts +1 -0
- package/src/plugins/builtin/world-venue-map/index.ts +67 -4
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +552 -0
- package/src/plugins/builtin/world-venue-map/layers.ts +348 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +220 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +381 -45
- package/src/plugins/builtin/world-venue-map/model.ts +7 -4
- package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +199 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/world-50m.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +28 -43
- package/src/plugins/builtin/world-venue-map/use-geo.ts +55 -0
- package/src/plugins/builtin/world-venue-map/venue-header.tsx +32 -0
- package/src/plugins/builtin/yield-curve/headless.ts +16 -0
- package/src/plugins/catalog-browser.ts +2 -0
- package/src/plugins/catalog-manifest.ts +1 -0
- package/src/plugins/catalog-ui.ts +3 -1
- package/src/plugins/catalog.ts +1 -1
- package/src/plugins/host-link.ts +33 -1
- package/src/plugins/installer.ts +48 -12
- package/src/plugins/loader.ts +30 -3
- package/src/plugins/manager-node.ts +15 -5
- package/src/plugins/registry/index.ts +1 -1
- package/src/plugins/registry/pane-settings.ts +26 -7
- package/src/public/__snapshots__/public-api.test.ts.snap +2 -0
- package/src/renderers/dom/build-assets.ts +81 -3
- package/src/renderers/dom/data-table/row.tsx +52 -16
- package/src/renderers/dom/desktop/controls.tsx +7 -3
- package/src/renderers/dom/desktop/icons.tsx +1 -0
- package/src/renderers/dom/desktop/list-view.tsx +35 -10
- package/src/renderers/dom/desktop/popover.tsx +73 -14
- package/src/renderers/dom/host/box.tsx +69 -26
- package/src/renderers/dom/host/input.tsx +14 -3
- package/src/renderers/dom/host/tabs.tsx +37 -1
- package/src/renderers/dom/styles.css +41 -7
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +11 -6
- package/src/renderers/electrobun/bun/desktop/backend.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +181 -87
- package/src/renderers/electrobun/bun/index.ts +73 -6
- package/src/renderers/electrobun/bun/window/remembered-frame.ts +122 -0
- package/src/renderers/electrobun/shared/protocol.ts +3 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +10 -4
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +3 -1
- package/src/renderers/electrobun/view/main.tsx +7 -1
- package/src/renderers/electrobun/view/renderer-window-error.ts +18 -0
- package/src/renderers/electrobun/view/rpc-timeout.ts +14 -3
- package/src/renderers/electrobun/view/update-host.ts +45 -21
- package/src/renderers/opentui/host.tsx +5 -0
- package/src/sources/gloomberb-cloud/index.ts +25 -4
- package/src/sources/gloomberb-cloud/normalizers.ts +36 -5
- package/src/sources/provider-router/cached-routes.ts +4 -1
- package/src/sources/provider-router/financials.ts +11 -8
- package/src/sources/provider-router/history.ts +15 -10
- package/src/sources/sec-registrant.ts +75 -0
- package/src/state/app/context/index.tsx +24 -8
- package/src/state/hooks/quote-streaming.ts +41 -26
- package/src/sync/core-contributors.ts +4 -0
- package/src/telemetry/benign-window-error.ts +7 -0
- package/src/telemetry/crash-reports-dom.ts +2 -0
- package/src/telemetry/crash-reports.ts +4 -1
- package/src/test-support/cli-context.ts +10 -2
- package/src/test-support/daily-nav.ts +25 -0
- package/src/theme/heat-colors.ts +357 -2
- package/src/tickers/instrument-kind.ts +34 -7
- package/src/tickers/search/asset-classes.ts +24 -9
- package/src/tickers/search/index.ts +44 -11
- package/src/tickers/search/ranking.ts +23 -3
- package/src/time-series/resolve.ts +1 -1
- package/src/time-series/use-chart-resolution.ts +1 -1
- package/src/types/context-menu.ts +6 -0
- package/src/types/data-provider.ts +2 -0
- package/src/types/financials.ts +5 -0
- package/src/types/headless.ts +79 -2
- package/src/types/plugin.ts +38 -6
- package/src/types/ticker.ts +15 -0
- package/src/ui/context-menu.tsx +1 -0
- package/src/ui/host.tsx +16 -0
- package/src/updater/index.ts +38 -13
- package/src/utils/async-deadline.ts +21 -0
- package/src/utils/cli-output.ts +5 -1
- package/src/utils/crypto-pair.ts +41 -0
- package/src/utils/currency-units.ts +14 -0
- package/src/utils/depositary-receipt.ts +26 -0
- package/src/utils/exchanges.ts +72 -0
- package/src/utils/fire-and-forget.ts +24 -0
- package/src/utils/format.ts +13 -0
- package/src/utils/price-earnings.ts +70 -3
- package/src/utils/price-history.ts +58 -13
- package/src/utils/rpc-timeout-error.ts +27 -0
- package/src/utils/sec.ts +13 -1
- package/src/utils/utc-time.ts +79 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { MarketHeatmapAsset } from "../../../api-client/market-discovery";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The board's grouping and move arithmetic, shared by the pane and the `fn HM`
|
|
5
|
+
* report so a sector reads the same in both. Isomorphic: no React or renderer.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
type HeatmapMoveAsset = Pick<MarketHeatmapAsset, "symbol" | "hasChange" | "changePercent" | "size" | "sector" | "industry"> & {
|
|
9
|
+
/** Treemap area, when it is not `size` itself. */
|
|
10
|
+
weight?: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The move, or null when there is none: no change data is not a flat session.
|
|
15
|
+
* A name without the last session's close still has the move of the open
|
|
16
|
+
* pre-market or after-hours session it streams (`extendedSession`).
|
|
17
|
+
*/
|
|
18
|
+
export function heatmapMove(asset: Pick<MarketHeatmapAsset, "hasChange" | "changePercent"> & { extendedSession?: string }): number | null {
|
|
19
|
+
return (asset.hasChange || asset.extendedSession != null) && typeof asset.changePercent === "number" && Number.isFinite(asset.changePercent)
|
|
20
|
+
? asset.changePercent
|
|
21
|
+
: null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type HeatmapGrouping = "sector-industry" | "sector" | "flat";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Sector and industry blocks when the board knows its sectors. A board where
|
|
28
|
+
* most of the value has none (ETFs, or a snapshot whose classification
|
|
29
|
+
* failed) lays out flat rather than as one big unlabeled block.
|
|
30
|
+
*/
|
|
31
|
+
export function resolveHeatmapGrouping(assets: readonly HeatmapMoveAsset[], industries: boolean): HeatmapGrouping {
|
|
32
|
+
let total = 0;
|
|
33
|
+
let classified = 0;
|
|
34
|
+
const sectors = new Set<string>();
|
|
35
|
+
for (const asset of assets) {
|
|
36
|
+
const weight = asset.weight ?? asset.size ?? 0;
|
|
37
|
+
total += weight;
|
|
38
|
+
if (asset.sector) {
|
|
39
|
+
classified += weight;
|
|
40
|
+
sectors.add(asset.sector);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (sectors.size < 2 || classified < total / 2) return "flat";
|
|
44
|
+
return industries ? "sector-industry" : "sector";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The move of a set of names weighted by their size (market cap, an ETF's net
|
|
49
|
+
* assets, a position's value), over the names that have both. Null when none do.
|
|
50
|
+
*/
|
|
51
|
+
export function sizeWeightedMove(assets: readonly HeatmapMoveAsset[]): number | null {
|
|
52
|
+
let weight = 0;
|
|
53
|
+
let weightedMove = 0;
|
|
54
|
+
for (const asset of assets) {
|
|
55
|
+
const change = heatmapMove(asset);
|
|
56
|
+
const size = asset.size ?? 0;
|
|
57
|
+
if (change == null || !(size > 0)) continue;
|
|
58
|
+
weight += size;
|
|
59
|
+
weightedMove += change * size;
|
|
60
|
+
}
|
|
61
|
+
return weight > 0 ? weightedMove / weight : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type HeatmapGroupBy = "sector" | "industry";
|
|
65
|
+
|
|
66
|
+
export interface HeatmapGroupSummary {
|
|
67
|
+
/** The sector or industry; null for names the snapshot does not classify, or every name on a flat board. */
|
|
68
|
+
group: string | null;
|
|
69
|
+
/** The industry's sector, when grouping by industry. */
|
|
70
|
+
sector: string | null;
|
|
71
|
+
names: number;
|
|
72
|
+
/** Total size: market cap, or net assets on the ETF board. */
|
|
73
|
+
size: number;
|
|
74
|
+
/** This group's share of the board's total size. */
|
|
75
|
+
share: number;
|
|
76
|
+
move: number | null;
|
|
77
|
+
/** Names with a move, and the size they carry; the rest have no change data yet. */
|
|
78
|
+
moved: number;
|
|
79
|
+
up: number;
|
|
80
|
+
down: number;
|
|
81
|
+
unchanged: number;
|
|
82
|
+
best: { symbol: string; move: number } | null;
|
|
83
|
+
worst: { symbol: string; move: number } | null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function summarize(group: string | null, sector: string | null, assets: readonly HeatmapMoveAsset[], boardSize: number): HeatmapGroupSummary {
|
|
87
|
+
let up = 0;
|
|
88
|
+
let down = 0;
|
|
89
|
+
let unchanged = 0;
|
|
90
|
+
let size = 0;
|
|
91
|
+
let best: HeatmapGroupSummary["best"] = null;
|
|
92
|
+
let worst: HeatmapGroupSummary["worst"] = null;
|
|
93
|
+
for (const asset of assets) {
|
|
94
|
+
size += asset.size ?? 0;
|
|
95
|
+
const move = heatmapMove(asset);
|
|
96
|
+
if (move == null) continue;
|
|
97
|
+
if (move > 0) up += 1;
|
|
98
|
+
else if (move < 0) down += 1;
|
|
99
|
+
else unchanged += 1;
|
|
100
|
+
if (!best || move > best.move) best = { symbol: asset.symbol, move };
|
|
101
|
+
if (!worst || move < worst.move) worst = { symbol: asset.symbol, move };
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
group,
|
|
105
|
+
sector,
|
|
106
|
+
names: assets.length,
|
|
107
|
+
size,
|
|
108
|
+
share: boardSize > 0 ? size / boardSize : 0,
|
|
109
|
+
move: sizeWeightedMove(assets),
|
|
110
|
+
moved: up + down + unchanged,
|
|
111
|
+
up,
|
|
112
|
+
down,
|
|
113
|
+
unchanged,
|
|
114
|
+
best,
|
|
115
|
+
worst,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* One summary per sector or industry, largest first as the board lays them
|
|
121
|
+
* out. A flat board (ETFs) is one group of every name. Unclassified names form
|
|
122
|
+
* their own group last.
|
|
123
|
+
*/
|
|
124
|
+
export function summarizeHeatmapGroups(
|
|
125
|
+
assets: readonly HeatmapMoveAsset[],
|
|
126
|
+
groupBy: HeatmapGroupBy,
|
|
127
|
+
): { grouping: HeatmapGrouping; groups: HeatmapGroupSummary[]; board: HeatmapGroupSummary } {
|
|
128
|
+
const grouping = resolveHeatmapGrouping(assets, groupBy === "industry");
|
|
129
|
+
const boardSize = assets.reduce((sum, asset) => sum + (asset.size ?? 0), 0);
|
|
130
|
+
const board = summarize(null, null, assets, boardSize);
|
|
131
|
+
if (grouping === "flat") return { grouping, groups: [board], board };
|
|
132
|
+
const buckets = new Map<string, { group: string | null; sector: string | null; assets: HeatmapMoveAsset[] }>();
|
|
133
|
+
for (const asset of assets) {
|
|
134
|
+
const sector = asset.sector ?? null;
|
|
135
|
+
const group = sector == null ? null : grouping === "sector-industry" ? asset.industry ?? null : sector;
|
|
136
|
+
const key = `${sector ?? ""}\u0000${group ?? ""}`;
|
|
137
|
+
const bucket = buckets.get(key) ?? { group, sector, assets: [] };
|
|
138
|
+
bucket.assets.push(asset);
|
|
139
|
+
buckets.set(key, bucket);
|
|
140
|
+
}
|
|
141
|
+
const groups = [...buckets.values()]
|
|
142
|
+
.map((bucket) => summarize(bucket.group, grouping === "sector-industry" ? bucket.sector : null, bucket.assets, boardSize))
|
|
143
|
+
.sort((left, right) => Number(left.sector == null && left.group == null) - Number(right.sector == null && right.group == null)
|
|
144
|
+
|| right.size - left.size);
|
|
145
|
+
return { grouping, groups, board };
|
|
146
|
+
}
|
|
@@ -3,10 +3,28 @@ import type { MarketHeatmapAsset, MarketHeatmapUniverseId } from "../../../api-c
|
|
|
3
3
|
import { useAppSelector, usePaneStateValue } from "../../../state/app/context";
|
|
4
4
|
import type { ColumnConfig } from "../../../types/config";
|
|
5
5
|
import type { TickerFinancials } from "../../../types/financials";
|
|
6
|
+
import { getSessionMoveDisplay, type ExtendedSession } from "../../../market-data/market/status";
|
|
6
7
|
import type { TickerRecord } from "../../../types/ticker";
|
|
7
8
|
import { getSortValue, type ColumnContext } from "../portfolio-list/metrics";
|
|
8
9
|
|
|
9
10
|
export const PORTFOLIO_HEATMAP_TAB = "portfolio";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* How a market board's tile area follows size. The square root, the default,
|
|
14
|
+
* gives mid-size names room next to the largest; groups still sum their
|
|
15
|
+
* tiles, so a sector's block stays the sum of its transformed names.
|
|
16
|
+
* Portfolio holdings always take their position value as it is.
|
|
17
|
+
*/
|
|
18
|
+
export type HeatmapSizeBy = "market-cap" | "sqrt-market-cap";
|
|
19
|
+
|
|
20
|
+
/** A saved choice wins; a pane that never chose gets the square root. */
|
|
21
|
+
export function heatmapSizeBy(value: unknown): HeatmapSizeBy {
|
|
22
|
+
return value === "market-cap" ? "market-cap" : "sqrt-market-cap";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function heatmapSizeWeight(size: number, sizeBy: HeatmapSizeBy): number {
|
|
26
|
+
return sizeBy === "sqrt-market-cap" ? Math.sqrt(size) : size;
|
|
27
|
+
}
|
|
10
28
|
const MAX_PORTFOLIO_TILES = 160;
|
|
11
29
|
|
|
12
30
|
export type HeatmapTabId = MarketHeatmapUniverseId | typeof PORTFOLIO_HEATMAP_TAB;
|
|
@@ -19,6 +37,8 @@ export interface HeatmapBoardAsset extends MarketHeatmapAsset {
|
|
|
19
37
|
sizeCurrency?: string;
|
|
20
38
|
/** Treemap area, when it is not `size` itself. */
|
|
21
39
|
weight?: number;
|
|
40
|
+
/** The price and move are this open pre-market or after-hours session's, from the regular close. */
|
|
41
|
+
extendedSession?: ExtendedSession;
|
|
22
42
|
}
|
|
23
43
|
|
|
24
44
|
export interface HeatmapPortfolioPane {
|
|
@@ -31,6 +51,19 @@ export function heatmapTabId(value: string | null | undefined): HeatmapTabId {
|
|
|
31
51
|
return "us-equity";
|
|
32
52
|
}
|
|
33
53
|
|
|
54
|
+
/** Pane state where the pane says which kind of list its portfolio tab shows, for the header to read. */
|
|
55
|
+
export const HEATMAP_COLLECTION_KIND_STATE_KEY = "collectionKind";
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Whether Size by changes the board: the market boards and a watchlist, never
|
|
59
|
+
* a portfolio, whose holdings are always sized by position value. Until the
|
|
60
|
+
* pane has said which list its tab shows, the control stays away.
|
|
61
|
+
*/
|
|
62
|
+
export function heatmapSizeByApplies(universe: unknown, collectionKind: unknown): boolean {
|
|
63
|
+
return heatmapTabId(typeof universe === "string" ? universe : null) !== PORTFOLIO_HEATMAP_TAB
|
|
64
|
+
|| collectionKind === "watchlist";
|
|
65
|
+
}
|
|
66
|
+
|
|
34
67
|
export function isRemoteHeatmapUniverse(value: HeatmapTabId): value is MarketHeatmapUniverseId {
|
|
35
68
|
return value !== PORTFOLIO_HEATMAP_TAB;
|
|
36
69
|
}
|
|
@@ -147,11 +180,12 @@ function positiveNumber(value: number | string | null): number | null {
|
|
|
147
180
|
/**
|
|
148
181
|
* A portfolio's holdings are sized by market value, read as the portfolio pane
|
|
149
182
|
* reads its MKT VALUE column: lots, contract multipliers and price basis, in
|
|
150
|
-
* the portfolio's currency
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
* rate) gets the smallest tile and
|
|
154
|
-
* the smallest names are left out and
|
|
183
|
+
* the portfolio's currency, which a holder reads linearly whatever `sizeBy`
|
|
184
|
+
* says. A watchlist's names are sized by market cap in the base currency, by
|
|
185
|
+
* default its square root (`sizeBy`). A name with no
|
|
186
|
+
* size (no position, no quote, no cap, no FX rate) gets the smallest tile and
|
|
187
|
+
* no caption. Past `MAX_PORTFOLIO_TILES`, the smallest names are left out and
|
|
188
|
+
* counted in `omitted`.
|
|
155
189
|
*/
|
|
156
190
|
export function buildPortfolioHeatmapAssets({
|
|
157
191
|
tickers,
|
|
@@ -160,6 +194,7 @@ export function buildPortfolioHeatmapAssets({
|
|
|
160
194
|
kind,
|
|
161
195
|
currency,
|
|
162
196
|
exchangeRates,
|
|
197
|
+
sizeBy = "sqrt-market-cap",
|
|
163
198
|
}: {
|
|
164
199
|
tickers: readonly TickerRecord[];
|
|
165
200
|
financials: ReadonlyMap<string, TickerFinancials>;
|
|
@@ -168,6 +203,7 @@ export function buildPortfolioHeatmapAssets({
|
|
|
168
203
|
/** The portfolio's totals currency, or the base currency for a watchlist. */
|
|
169
204
|
currency: string;
|
|
170
205
|
exchangeRates: Map<string, number>;
|
|
206
|
+
sizeBy?: HeatmapSizeBy;
|
|
171
207
|
}): { assets: HeatmapBoardAsset[]; omitted: number } {
|
|
172
208
|
const context: ColumnContext = {
|
|
173
209
|
activeTab: kind === "portfolio" ? collectionId : undefined,
|
|
@@ -180,18 +216,21 @@ export function buildPortfolioHeatmapAssets({
|
|
|
180
216
|
const symbol = ticker.metadata.ticker;
|
|
181
217
|
const snapshot = financials.get(symbol);
|
|
182
218
|
const quote = snapshot?.quote;
|
|
183
|
-
|
|
219
|
+
// Colored like the market boards: an open extended session's move, else the regular session's.
|
|
220
|
+
const display = getSessionMoveDisplay(quote);
|
|
221
|
+
const price = display != null && Number.isFinite(display.price) ? display.price : null;
|
|
184
222
|
const size = positiveNumber(getSortValue(sizeColumn, ticker, snapshot, context));
|
|
185
|
-
const hasChange =
|
|
223
|
+
const hasChange = display?.changePercent != null && Number.isFinite(display.changePercent);
|
|
186
224
|
return {
|
|
187
225
|
symbol,
|
|
188
226
|
name: quote?.name?.trim() || ticker.metadata.name || symbol,
|
|
189
227
|
price: price ?? 0,
|
|
190
|
-
change:
|
|
191
|
-
changePercent: hasChange ?
|
|
228
|
+
change: display?.change != null && Number.isFinite(display.change) ? display.change : 0,
|
|
229
|
+
changePercent: hasChange ? display.changePercent! : 0,
|
|
192
230
|
hasChange,
|
|
231
|
+
...(display?.session ? { extendedSession: display.session } : {}),
|
|
193
232
|
size,
|
|
194
|
-
weight: size == null ? undefined : kind === "portfolio" ? size :
|
|
233
|
+
weight: size == null ? undefined : kind === "portfolio" ? size : heatmapSizeWeight(size, sizeBy),
|
|
195
234
|
sizeKind: "market-cap",
|
|
196
235
|
sizeCaption: kind === "portfolio" ? "Value" : undefined,
|
|
197
236
|
sizeCurrency: currency,
|
|
@@ -212,7 +251,7 @@ export function buildPortfolioHeatmapAssets({
|
|
|
212
251
|
const kept = measured.slice(0, MAX_PORTFOLIO_TILES);
|
|
213
252
|
const sizes = kept.flatMap((asset) => (asset.size != null ? [asset.size] : []));
|
|
214
253
|
const floor = sizes.length > 0 ? Math.min(...sizes) : 1;
|
|
215
|
-
const floorWeight = kind === "portfolio" ? floor :
|
|
254
|
+
const floorWeight = kind === "portfolio" ? floor : heatmapSizeWeight(floor, sizeBy);
|
|
216
255
|
return {
|
|
217
256
|
assets: kept.map((asset) => (asset.size != null ? asset : { ...asset, weight: floorWeight })),
|
|
218
257
|
omitted: measured.length - kept.length,
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
fetchPreferredMarketMovers,
|
|
11
11
|
fetchTrending,
|
|
12
12
|
type MarketMoversResult,
|
|
13
|
+
type PreferredMoversOptions,
|
|
13
14
|
type ScreenerQuote,
|
|
14
15
|
type TrendingSymbol,
|
|
15
16
|
} from "./screener";
|
|
@@ -22,7 +23,7 @@ export interface MarketMoverTabDependencies {
|
|
|
22
23
|
fetchPreferred(
|
|
23
24
|
category: "day_gainers" | "day_losers" | "most_actives",
|
|
24
25
|
count: number,
|
|
25
|
-
options?:
|
|
26
|
+
options?: PreferredMoversOptions,
|
|
26
27
|
): Promise<MarketMoversResult>;
|
|
27
28
|
fetchTrending(count: number, options?: { forceRefresh?: boolean }): Promise<TrendingSymbol[]>;
|
|
28
29
|
}
|
|
@@ -65,7 +66,7 @@ async function hydrateTrending(
|
|
|
65
66
|
export async function loadMarketMoverTab(
|
|
66
67
|
tab: ScreenerTabId,
|
|
67
68
|
provider: DataProvider | null,
|
|
68
|
-
options?:
|
|
69
|
+
options?: Pick<PreferredMoversOptions, "forceRefresh" | "metadataWaitMs">,
|
|
69
70
|
dependencies: MarketMoverTabDependencies = defaultDependencies,
|
|
70
71
|
): Promise<MarketMoverTabResult> {
|
|
71
72
|
if (tab === "trending") {
|
|
@@ -64,8 +64,15 @@ export interface MarketMoversHeadlessDependencies {
|
|
|
64
64
|
): Promise<CloudSessionMoversPayload>;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Headless reads, including VIEW sources, wait for average volumes rather
|
|
69
|
+
* than returning a partial first paint. Keep that wait bounded by the usual
|
|
70
|
+
* market request timeout; interactive MOST retains its shorter cutoff.
|
|
71
|
+
*/
|
|
72
|
+
const ONE_SHOT_METADATA_WAIT_MS = 10_000;
|
|
73
|
+
|
|
67
74
|
const defaultDependencies: MarketMoversHeadlessDependencies = {
|
|
68
|
-
load: (_args, tab, provider) => loadMarketMoverTab(tab, provider),
|
|
75
|
+
load: (_args, tab, provider) => loadMarketMoverTab(tab, provider, { metadataWaitMs: ONE_SHOT_METADATA_WAIT_MS }),
|
|
69
76
|
loadSession: (view, side) => loadSessionMovers(view, side),
|
|
70
77
|
};
|
|
71
78
|
|
|
@@ -74,6 +81,8 @@ export function createMarketMoversHeadless(
|
|
|
74
81
|
): HeadlessPaneDefinition<"rows"> {
|
|
75
82
|
return {
|
|
76
83
|
shape: "rows",
|
|
84
|
+
// A screener snapshot, not a real-time feed; rows keep the time of their own last price.
|
|
85
|
+
freshness: { status: "delayed" },
|
|
77
86
|
argument: { kind: "none" },
|
|
78
87
|
options: [{
|
|
79
88
|
key: "list",
|
|
@@ -18,6 +18,7 @@ const CACHE_POLICY = {
|
|
|
18
18
|
staleMs: 5 * 60 * 1000,
|
|
19
19
|
expireMs: 60 * 60 * 1000,
|
|
20
20
|
} as const;
|
|
21
|
+
/** A pane shows the live ranking first; the backend's average volumes follow if they are quick. */
|
|
21
22
|
const MARKET_METADATA_WAIT_MS = 1_500;
|
|
22
23
|
|
|
23
24
|
export interface MarketScreenerApi {
|
|
@@ -30,6 +31,14 @@ export interface FetchCacheOptions {
|
|
|
30
31
|
forceRefresh?: boolean;
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
export interface PreferredMoversOptions extends FetchCacheOptions {
|
|
35
|
+
/**
|
|
36
|
+
* How long a Cloud list waits for the backend's average volumes and market
|
|
37
|
+
* caps, which it borrows by symbol: without them every row has no ratio.
|
|
38
|
+
*/
|
|
39
|
+
metadataWaitMs?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
let marketMoversPersistence: PluginPersistence | null = null;
|
|
34
43
|
const activeFetches = new Map<string, Promise<unknown>>();
|
|
35
44
|
const failedFetches = new Set<string>();
|
|
@@ -243,6 +252,8 @@ function mergeCloudScreenerItem(
|
|
|
243
252
|
metadata?: ScreenerQuote,
|
|
244
253
|
): ScreenerQuote {
|
|
245
254
|
const currency = typeof item.currency === "string" ? item.currency.trim() : "";
|
|
255
|
+
const directAverage = screenerNumber(item.avgVolume);
|
|
256
|
+
const avgVolume = directAverage != null && directAverage > 0 ? directAverage : metadata?.avgVolume ?? null;
|
|
246
257
|
const metadataPrice = (value: number | undefined) => convertScreenerPriceUnit(value, metadata?.currency ?? "", currency);
|
|
247
258
|
return {
|
|
248
259
|
symbol: item.symbol,
|
|
@@ -253,8 +264,8 @@ function mergeCloudScreenerItem(
|
|
|
253
264
|
change: screenerNumber(item.change),
|
|
254
265
|
changePercent: screenerNumber(item.changePercent),
|
|
255
266
|
volume: screenerVolume(item.volume),
|
|
256
|
-
avgVolume
|
|
257
|
-
volumeRatio: screenerVolumeRatio(screenerVolume(item.volume),
|
|
267
|
+
avgVolume,
|
|
268
|
+
volumeRatio: screenerVolumeRatio(screenerVolume(item.volume), avgVolume),
|
|
258
269
|
marketCap: metadata?.marketCap,
|
|
259
270
|
currency,
|
|
260
271
|
fiftyTwoWeekHigh: screenerNumber(item.high52w) ?? metadataPrice(metadata?.fiftyTwoWeekHigh),
|
|
@@ -268,13 +279,14 @@ function mergeCloudScreenerItem(
|
|
|
268
279
|
|
|
269
280
|
async function bestEffortMarketMetadata(
|
|
270
281
|
request: Promise<CachedResult<ScreenerQuote[]>>,
|
|
282
|
+
waitMs: number,
|
|
271
283
|
): Promise<ScreenerQuote[]> {
|
|
272
284
|
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
273
285
|
try {
|
|
274
286
|
return await Promise.race([
|
|
275
287
|
request.then((result) => result.data).catch(() => []),
|
|
276
288
|
new Promise<ScreenerQuote[]>((resolve) => {
|
|
277
|
-
timeout = setTimeout(() => resolve([]),
|
|
289
|
+
timeout = setTimeout(() => resolve([]), waitMs);
|
|
278
290
|
}),
|
|
279
291
|
]);
|
|
280
292
|
} finally {
|
|
@@ -303,9 +315,10 @@ export function rankScreenerQuotes(category: ScreenerCategory, quotes: ScreenerQ
|
|
|
303
315
|
export async function fetchPreferredMarketMovers(
|
|
304
316
|
category: ScreenerCategory,
|
|
305
317
|
count = 25,
|
|
306
|
-
|
|
318
|
+
preferredOptions?: PreferredMoversOptions,
|
|
307
319
|
sources: PreferredMarketMoverSources = defaultPreferredMarketMoverSources,
|
|
308
320
|
): Promise<MarketMoversResult> {
|
|
321
|
+
const { metadataWaitMs = MARKET_METADATA_WAIT_MS, ...options } = preferredOptions ?? {};
|
|
309
322
|
if (!sources.isCloudEligible()) {
|
|
310
323
|
const result = await sources.fetchMarket(category, count, options);
|
|
311
324
|
return { quotes: rankScreenerQuotes(category, result.data), source: "gloom", stale: result.stale };
|
|
@@ -327,7 +340,7 @@ export async function fetchPreferredMarketMovers(
|
|
|
327
340
|
&& response.data
|
|
328
341
|
&& response.data.items.length > 0
|
|
329
342
|
) {
|
|
330
|
-
const metadata = await bestEffortMarketMetadata(marketMetadata);
|
|
343
|
+
const metadata = await bestEffortMarketMetadata(marketMetadata, metadataWaitMs);
|
|
331
344
|
const metadataBySymbol = new Map(metadata.map((quote) => [quote.symbol, quote]));
|
|
332
345
|
return {
|
|
333
346
|
quotes: rankScreenerQuotes(category, response.data.items.map((item) => (
|
|
@@ -120,6 +120,8 @@ function projectValuationHeadlessBundle(
|
|
|
120
120
|
|
|
121
121
|
export const marketValuationHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
122
122
|
shape: "bundle",
|
|
123
|
+
// Each indicator row checks its own release schedule and says when it is stale.
|
|
124
|
+
freshness: { status: "not-a-feed", basis: "published statistics" },
|
|
123
125
|
argument: {
|
|
124
126
|
kind: "free-text",
|
|
125
127
|
placeholder: "indicator",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { apiClient } from "../../../api-client";
|
|
2
|
+
import { ApiRequestError } from "../../../api-client/errors";
|
|
3
|
+
import type { FundMembersPayload, FundChangesPayload, MemberFundsPayload } from "../../../api-client/members";
|
|
4
|
+
import { createPluginCache } from "../../../data/plugin-cache";
|
|
5
|
+
import { cachedCloudResource, loadCloudResource, unavailableOnServer } from "../shared/cloud-resource";
|
|
6
|
+
import { coveredFund, notCoveredMessage } from "./model";
|
|
7
|
+
const policy = { staleMs: 60_000, expireMs: 2 * 60 * 60_000 };
|
|
8
|
+
export const fundListCache = createPluginCache<MemberFundsPayload>({ kind: "member-funds", source: "gloom-cloud", schemaVersion: 1, policy });
|
|
9
|
+
export const fundMembersCache = createPluginCache<FundMembersPayload>({ kind: "fund-members", source: "gloom-cloud", schemaVersion: 1, policy });
|
|
10
|
+
export const fundChangesCache = createPluginCache<FundChangesPayload>({ kind: "fund-changes", source: "gloom-cloud", schemaVersion: 1, policy });
|
|
11
|
+
type Client = Pick<typeof apiClient, "getMemberFunds" | "getFundMembers" | "getFundChanges">;
|
|
12
|
+
const number = (value: unknown) => value === null || typeof value === "number" && Number.isFinite(value);
|
|
13
|
+
function validateMembers(data: FundMembersPayload) {
|
|
14
|
+
if (!data?.fund?.ticker || !Number.isFinite(Date.parse(data.asOf)) || !Array.isArray(data.members)
|
|
15
|
+
|| !data.members.every((row) => typeof row.id === "string" && typeof row.name === "string" && [row.weight, row.price, row.changePercent, row.return1WPercent, row.return1MPercent, row.returnYtdPercent, row.contribution].every(number))
|
|
16
|
+
|| !data.aggregate || !number(data.aggregate.sum)) throw new Error("Unreadable fund holdings response");
|
|
17
|
+
return data;
|
|
18
|
+
}
|
|
19
|
+
function validateChanges(data: FundChangesPayload) {
|
|
20
|
+
if (!data?.fund?.ticker || !Array.isArray(data.changes) || !data.changes.every((row) => typeof row.id === "string" && typeof row.reason === "string" && Array.isArray(row.estimates))) throw new Error("Unreadable fund changes response");
|
|
21
|
+
return data;
|
|
22
|
+
}
|
|
23
|
+
function validateFunds(data: MemberFundsPayload) {
|
|
24
|
+
if (!data || !Array.isArray(data.funds) || !data.funds.every((fund) => typeof fund.ticker === "string" && typeof fund.name === "string")) throw new Error("Unreadable covered funds response");
|
|
25
|
+
return data;
|
|
26
|
+
}
|
|
27
|
+
const UNAVAILABLE = "Fund members are not available on this server yet.";
|
|
28
|
+
async function fetchPayload<T>(fetcher: () => Promise<T>, validate: (data: T) => T) {
|
|
29
|
+
try { return validate(await fetcher()); } catch (error) { throw unavailableOnServer(error, UNAVAILABLE); }
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A 404 for one fund means either an uncovered fund or a server without the route, and the two read the
|
|
33
|
+
* same. The covered list tells them apart: a fund missing from a readable list is not covered; a list
|
|
34
|
+
* that cannot be read, or that holds the fund, leaves the server-side message.
|
|
35
|
+
*/
|
|
36
|
+
async function fetchFundPayload<T>(fund: string, fetcher: () => Promise<T>, validate: (data: T) => T, client: Client) {
|
|
37
|
+
try { return validate(await fetcher()); } catch (error) {
|
|
38
|
+
if (error instanceof ApiRequestError && error.status === 404) {
|
|
39
|
+
const funds = await fetchFunds(client).then((payload) => payload.funds, () => []);
|
|
40
|
+
if (funds.length && !coveredFund(funds, fund)) throw new Error(notCoveredMessage(fund, funds));
|
|
41
|
+
}
|
|
42
|
+
throw unavailableOnServer(error, UNAVAILABLE);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export const fetchFunds = (client: Client = apiClient) => fetchPayload(() => client.getMemberFunds(), validateFunds);
|
|
46
|
+
export const fetchMembers = (fund: string, client: Client = apiClient) => fetchFundPayload(fund, () => client.getFundMembers(fund), validateMembers, client);
|
|
47
|
+
export const fetchChanges = (fund: string, client: Client = apiClient) => fetchFundPayload(fund, () => client.getFundChanges(fund), validateChanges, client);
|
|
48
|
+
export const cachedFunds = () => cachedCloudResource(fundListCache, "list", validateFunds);
|
|
49
|
+
export const loadFunds = (force = false) => loadCloudResource(fundListCache, "list", () => fetchFunds(), { force, validate: validateFunds });
|
|
50
|
+
export const cachedMembers = (fund: string) => cachedCloudResource(fundMembersCache, fund, validateMembers);
|
|
51
|
+
export const loadMembers = (fund: string, force = false) => loadCloudResource(fundMembersCache, fund, () => fetchMembers(fund), { force, validate: validateMembers });
|
|
52
|
+
export const cachedChanges = (fund: string) => cachedCloudResource(fundChangesCache, fund, validateChanges);
|
|
53
|
+
export const loadChanges = (fund: string, force = false) => loadCloudResource(fundChangesCache, fund, () => fetchChanges(fund), { force, validate: validateChanges });
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { HeadlessPaneColumn, HeadlessPaneDefinition } from "../../../types/plugin";
|
|
2
|
+
import { fetchChanges, fetchFunds, fetchMembers } from "./client";
|
|
3
|
+
import { canonicalFund, changeReason, decimal, DEFAULT_SORT, memberRows, percent } from "./model";
|
|
4
|
+
const columns: HeadlessPaneColumn[] = [
|
|
5
|
+
{ key: "symbol", header: "Ticker" }, { key: "name", header: "Name" },
|
|
6
|
+
{ key: "weight", header: "Weight %", align: "right", format: (value) => value == null ? "--" : decimal(Number(value) * 100) },
|
|
7
|
+
{ key: "shares", header: "Shares", align: "right", format: (value) => decimal(value as number | null, 0) },
|
|
8
|
+
{ key: "price", header: "Price $", align: "right", format: (value) => decimal(value as number | null) },
|
|
9
|
+
...["changePercent", "return1WPercent", "return1MPercent", "returnYtdPercent"].map((key, i) => ({ key, header: ["1D", "1W", "1M", "YTD"][i]!, align: "right" as const, format: (value: unknown) => percent(value as number | null) })),
|
|
10
|
+
];
|
|
11
|
+
export const membersHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
12
|
+
shape: "rows", argument: { kind: "ticker", optional: true, placeholder: "fund", description: "Covered fund or index alias; omit for the fund list." },
|
|
13
|
+
options: [{ key: "tab", type: "enum", values: [{ value: "members" }, { value: "movers" }, { value: "changes" }], defaultValue: "members", description: "Members, movers or changes." }],
|
|
14
|
+
discovery: { aliases: ["MEMB", "MRR", "IMOV"], limitations: ["ETF holdings, with file dates", "Partial delayed member return coverage", "Nasdaq-100 is not covered"] },
|
|
15
|
+
describe: (args) => args.rawArgument ? `MEMB ${args.rawArgument}` : "Index and ETF members",
|
|
16
|
+
async load(args, context) {
|
|
17
|
+
const input = args.symbols[0] ?? args.rawArgument ?? "";
|
|
18
|
+
if (!input) return { freshness: { status: "not-a-feed", basis: "fund holdings files" }, rows: (await fetchFunds(context.apiClient)).funds.map((fund) => ({ ...fund })), columns: [{ key: "ticker", header: "Fund" }, { key: "name", header: "Index" }, { key: "asOf", header: "Holdings as of" }] };
|
|
19
|
+
const fund = canonicalFund(input);
|
|
20
|
+
if (args.options.tab === "changes") {
|
|
21
|
+
const data = await fetchChanges(fund, context.apiClient);
|
|
22
|
+
return { freshness: { status: "not-a-feed", basis: "index changes" }, rows: data.changes.map((row) => ({ ...row, reason: changeReason(row) })), columns: [{ key: "effectiveDate", header: "Effective" }, { key: "added", header: "Added" }, { key: "removed", header: "Removed" }, { key: "daysToGo", header: "In days", align: "right" }, { key: "reason", header: "Reason" }],
|
|
23
|
+
complete: data.available && !data.stale, metadata: { ...data, changes: undefined } };
|
|
24
|
+
}
|
|
25
|
+
const data = await fetchMembers(fund, context.apiClient);
|
|
26
|
+
const movers = args.options.tab === "movers";
|
|
27
|
+
const rows = memberRows(data.members, movers ? "movers" : "members", "", DEFAULT_SORT).flatMap((row) => row.kind === "item" ? [{ ...row.item }] : []);
|
|
28
|
+
// Member prices are the pane's 15-minute delayed quotes. As in the pane, the fund's snapshot says
|
|
29
|
+
// whether it is stale; the members' own flags are not what the pane reads.
|
|
30
|
+
return { freshness: { status: data.stale ? "stale" : "delayed", delayMinutes: 15, ignoreStaleFlags: true }, rows, columns: movers ? [...columns.slice(0, 3), { key: "contribution", header: "Contrib. pp", align: "right", format: (value) => percent(value as number | null, "", 3) }] : columns,
|
|
31
|
+
complete: !data.stale && data.aggregate.fresh1D === data.aggregate.total,
|
|
32
|
+
unavailableSymbols: data.members.flatMap((row) => row.symbol && row.changePercent === null ? [row.symbol] : []),
|
|
33
|
+
metadata: { fund: data.fund, asOf: data.asOf, snapshotAsOf: data.snapshotAsOf, aggregate: data.aggregate, stale: data.stale } };
|
|
34
|
+
},
|
|
35
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { GloomPlugin } from "../../../types/plugin";
|
|
2
|
+
import { fundChangesCache, fundListCache, fundMembersCache } from "./client";
|
|
3
|
+
import { membersHeadless } from "./headless";
|
|
4
|
+
import { MembersPane } from "./pane";
|
|
5
|
+
import { membersTitle } from "./model";
|
|
6
|
+
export const membersPlugin: GloomPlugin = {
|
|
7
|
+
id: "members", name: "Index and ETF members", version: "1.0.0", description: "ETF holdings, member returns, contributions and index changes", toggleable: true,
|
|
8
|
+
setup(ctx) { fundListCache.attach(ctx.persistence); fundMembersCache.attach(ctx.persistence); fundChangesCache.attach(ctx.persistence); },
|
|
9
|
+
dispose() { fundListCache.reset(); fundMembersCache.reset(); fundChangesCache.reset(); },
|
|
10
|
+
panes: [{ id: "members", name: "Index and ETF members", icon: "M", component: MembersPane, tickerFollower: true,
|
|
11
|
+
defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 132, height: 32 }, tableExport: true,
|
|
12
|
+
settings: { fields: [{ key: "tab", label: "View", type: "select", options: [{ value: "members", label: "Members" }, { value: "movers", label: "Movers" }, { value: "changes", label: "Changes" }] }] } }],
|
|
13
|
+
paneTemplates: [{ id: "members-pane", paneId: "members", label: "Index and ETF members", description: "ETF holdings, member returns, daily contributions and index changes.",
|
|
14
|
+
keywords: ["members", "index", "ETF", "holdings", "weights", "rebalance", "MRR", "IMOV"],
|
|
15
|
+
shortcut: { prefix: "MEMB", argKind: "ticker", argPlaceholder: "fund", argOptional: true, openWithoutArg: true }, headless: membersHeadless,
|
|
16
|
+
createInstance: (_context, options) => { const symbol = (options?.arg ?? options?.symbol)?.trim().toUpperCase();
|
|
17
|
+
return { placement: "floating", title: membersTitle(symbol ?? ""),
|
|
18
|
+
...(symbol ? { binding: { kind: "fixed", symbol } } : {}), settings: { tab: options?.values?.tab ?? "members" } }; },
|
|
19
|
+
}],
|
|
20
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { FundChange, FundMember, MemberFundsPayload } from "../../../api-client/members";
|
|
2
|
+
import type { DataTableColumn } from "../../../components";
|
|
3
|
+
import { buildSectionedRows, type SectionedRow } from "../../../components/data-table/sections";
|
|
4
|
+
import { getTableWidth } from "../../../components/ui/table-layout";
|
|
5
|
+
import { compareSortValues, type SortPreference } from "../../../utils/sort-values";
|
|
6
|
+
export type MembersTab = "members" | "movers" | "changes";
|
|
7
|
+
export const TABS = [{ value: "members", label: "Members" }, { value: "movers", label: "Movers" }, { value: "changes", label: "Changes" }];
|
|
8
|
+
export const isTab = (value: unknown): value is MembersTab => TABS.some((tab) => tab.value === value);
|
|
9
|
+
const COVERED = [{ ticker: "IVV", name: "S&P 500" }, { ticker: "IJH", name: "S&P 400" }, { ticker: "IJR", name: "S&P 600" }, { ticker: "IWM", name: "Russell 2000" }, { ticker: "IWB", name: "Russell 1000" }];
|
|
10
|
+
export function canonicalFund(input: string) { const symbol = input.split(":")[0]!.trim().toUpperCase(); return ["SPX", "SPY", "^GSPC"].includes(symbol) ? "IVV" : symbol; }
|
|
11
|
+
/** The covered fund the user asked for, by ticker or by an alias such as SPX, or undefined when it is not covered. */
|
|
12
|
+
export function coveredFund(funds: MemberFundsPayload["funds"], input: string) {
|
|
13
|
+
const symbol = canonicalFund(input);
|
|
14
|
+
return funds.find((fund) => fund.ticker === symbol || fund.aliases.some((alias) => alias.toUpperCase() === symbol));
|
|
15
|
+
}
|
|
16
|
+
export function notCoveredMessage(input: string, funds: MemberFundsPayload["funds"]) {
|
|
17
|
+
return `${input.split(":")[0]!.trim().toUpperCase()} is not a covered fund. Covered: ${funds.map((fund) => fund.ticker).join(", ")}.`;
|
|
18
|
+
}
|
|
19
|
+
export function membersTitle(input: string) {
|
|
20
|
+
const requested = input.split(":")[0]!.trim().toUpperCase();
|
|
21
|
+
const fund = canonicalFund(requested);
|
|
22
|
+
const definition = COVERED.find((item) => item.ticker === fund);
|
|
23
|
+
return definition ? `${definition.name}, ${fund} holdings${requested !== fund ? ` (${requested})` : ""}`
|
|
24
|
+
: requested ? `MEMB ${requested}` : "Index and ETF members";
|
|
25
|
+
}
|
|
26
|
+
export const DEFAULT_SORT: SortPreference<string> = { columnId: "weight", direction: "desc" };
|
|
27
|
+
export const percent = (value: number | null, suffix = "%", digits = 2) => value == null || !Number.isFinite(value) ? "--" : `${value > 0 ? "+" : ""}${value.toFixed(digits)}${suffix}`;
|
|
28
|
+
export const decimal = (value: number | null, digits = 2) => value == null || !Number.isFinite(value) ? "--" : value.toLocaleString("en-US", { minimumFractionDigits: digits, maximumFractionDigits: digits });
|
|
29
|
+
export function sortMembers(rows: readonly FundMember[], sort: SortPreference<string>) {
|
|
30
|
+
return [...rows].sort((a, b) => compareSortValues(a[sort.columnId as keyof FundMember] as number | string | null,
|
|
31
|
+
b[sort.columnId as keyof FundMember] as number | string | null, sort.direction) || a.id.localeCompare(b.id));
|
|
32
|
+
}
|
|
33
|
+
export function memberRows(rows: readonly FundMember[], tab: MembersTab, query: string, sort: SortPreference<string>): SectionedRow<FundMember>[] {
|
|
34
|
+
const needle = query.trim().toLowerCase();
|
|
35
|
+
const filtered = rows.filter((row) => `${row.symbol ?? ""} ${row.name} ${row.sector ?? ""}`.toLowerCase().includes(needle));
|
|
36
|
+
if (tab !== "movers") return sortMembers(filtered, sort).map((item) => ({ kind: "item", key: item.id, item }));
|
|
37
|
+
const ranked = sortMembers(filtered.filter((row) => row.contribution !== null), { columnId: "contribution", direction: "desc" });
|
|
38
|
+
return buildSectionedRows([
|
|
39
|
+
{ label: "Contributors", items: ranked.filter((row) => row.contribution! > 0).slice(0, 10) },
|
|
40
|
+
{ label: "Detractors", items: ranked.filter((row) => row.contribution! < 0).reverse().slice(0, 10) },
|
|
41
|
+
], (row) => row.id);
|
|
42
|
+
}
|
|
43
|
+
const column = (id: string, label: string, width: number, align: "left" | "right" = "right"): DataTableColumn => ({ id, label, width, align });
|
|
44
|
+
function fit(columns: DataTableColumn[], width: number, drops: string[]) {
|
|
45
|
+
for (const id of drops) { if (getTableWidth(columns) <= width) break; columns = columns.filter((col) => col.id !== id); }
|
|
46
|
+
const flexible = columns.find((col) => col.id === "name" || col.id === "reason") ?? columns[0]!;
|
|
47
|
+
flexible.width = Math.max(6, flexible.width + Math.max(0, width - getTableWidth(columns)));
|
|
48
|
+
flexible.flexGrow = 1;
|
|
49
|
+
return columns;
|
|
50
|
+
}
|
|
51
|
+
export function memberColumns(width: number, movers = false): DataTableColumn[] {
|
|
52
|
+
const compact = width < 50;
|
|
53
|
+
return fit([column("symbol", "Ticker", 8, "left"), column("name", "Name", 22, "left"),
|
|
54
|
+
column("weight", compact ? "Wt. %" : "Weight %", compact ? 7 : 10), ...(movers ? [column("contribution", compact ? "pp" : "Contrib. pp", compact ? 7 : 13)] : []),
|
|
55
|
+
...(!movers ? [column("shares", "Shares", 14), column("price", "Price $", 11)] : []),
|
|
56
|
+
column("changePercent", "1D", compact ? 8 : 9), ...(!movers ? [column("return1WPercent", "1W", 9), column("return1MPercent", "1M", 9), column("returnYtdPercent", "YTD", 9), column("sector", "Sector", 22, "left")] : [])], width,
|
|
57
|
+
["sector", "shares", "returnYtdPercent", "return1MPercent", "return1WPercent", "price", "name"]);
|
|
58
|
+
}
|
|
59
|
+
export function changeColumns(width: number) {
|
|
60
|
+
const compact = width < 50;
|
|
61
|
+
return fit([column("effectiveDate", compact ? "Date" : "Effective", compact ? 10 : 12, "left"), column("daysToGo", compact ? "In" : "In days", compact ? 4 : 9), column("added", "Added", compact ? 7 : 8, "left"), column("removed", "Removed", 9, "left"), column("estimate", "Est. ADV days", 15), column("reason", "Reason", 28, "left")], width, ["estimate", "reason"]);
|
|
62
|
+
}
|
|
63
|
+
export const changeLabel = (row: FundChange) => row.added && row.removed ? `${row.added} replaces ${row.removed}`
|
|
64
|
+
: row.added ? `${row.added} added` : row.removed ? `${row.removed} removed` : "Index announcement";
|
|
65
|
+
export function changeReason(row: FundChange) {
|
|
66
|
+
if (row.kind !== "announcement" || row.reason !== row.headline || !row.added && !row.removed) return row.reason;
|
|
67
|
+
return row.added && row.removed ? "Index replacement" : row.added ? "Index addition" : "Index deletion";
|
|
68
|
+
}
|