@gloom-sh/terminal 0.18.5 → 0.19.1
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +163 -8
- package/src/api-client/app-attention.ts +3 -0
- package/src/api-client/beneficial-owners.ts +3 -0
- package/src/api-client/company-kpis.ts +3 -0
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +19 -7
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/hiring.ts +3 -0
- package/src/api-client/index.ts +15 -0
- package/src/api-client/ipo.ts +24 -1
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +1 -1
- package/src/api-client/mna.ts +3 -0
- package/src/api-client/paths.ts +9 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/research-activity.ts +3 -2
- package/src/api-client/roadmap.ts +85 -0
- package/src/api-client/types.ts +82 -2
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +0 -2
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/core-builtin-allowlist.json +2 -2
- package/src/architecture/pane-tabs-allowlist.json +3 -0
- package/src/architecture/ttl-caches-allowlist.json +19 -0
- package/src/brokers/sync-broker-instance.ts +2 -8
- package/src/capabilities/chart-series.ts +1 -1
- package/src/cli/commands/crypto-hints.ts +40 -24
- package/src/cli/commands/market.ts +107 -38
- package/src/cli/commands/news-search.ts +22 -0
- package/src/cli/commands/options-chain.ts +48 -7
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +147 -32
- package/src/cli/commands/search.ts +21 -1
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +87 -36
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +25 -2
- package/src/cli/glossary.ts +31 -2
- package/src/cli/history-rows.ts +57 -9
- package/src/cli/index-roots.ts +0 -7
- package/src/cli/index.ts +2 -0
- package/src/cli/listing-arg.ts +235 -60
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +4 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/discovery.ts +21 -1
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +93 -4
- package/src/cli/pane-functions/function-help.ts +20 -10
- package/src/cli/pane-functions/headless.ts +17 -7
- package/src/cli/pane-functions/index.ts +54 -16
- package/src/cli/pane-functions/market-session.ts +66 -4
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/resolver.ts +19 -8
- package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
- package/src/cli/pane-functions/screenshot-types.ts +5 -0
- package/src/cli/quote-answer.ts +22 -11
- package/src/cli/saved-copy-persistence.ts +88 -0
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +4 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-surface.tsx +52 -3
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +39 -4
- package/src/components/chart/composite/series-cues.ts +14 -0
- package/src/components/chart/composite/text-renderer.ts +9 -2
- package/src/components/chart/composite/tool-catalog.ts +36 -0
- package/src/components/chart/composite/tools.ts +203 -44
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
- package/src/components/command-bar/assist/inventory.ts +2 -1
- package/src/components/command-bar/commands/direct/index.ts +28 -13
- package/src/components/command-bar/list/model.ts +5 -0
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/routes/root/runtime.ts +6 -7
- package/src/components/command-bar/selection-runtime.ts +52 -3
- package/src/components/command-bar/surface/index.tsx +2 -0
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/pane/capture-stamp.ts +2 -2
- package/src/components/layout/pane/footer/index.tsx +22 -6
- package/src/components/layout/pane/footer/model.ts +83 -15
- package/src/components/layout/pane/status-footer.ts +35 -2
- package/src/components/layout/status-widget-space.ts +9 -0
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
- package/src/components/onboarding/wizard-broker-sync.ts +0 -1
- package/src/components/onboarding/wizard-positions.ts +3 -3
- package/src/components/ticker/list-table-view.tsx +8 -21
- package/src/components/ui/choice-dialog.tsx +23 -18
- package/src/components/ui/choice-groups.ts +18 -0
- package/src/components/ui/data-table/opentui/index.tsx +9 -7
- package/src/components/ui/data-table/opentui/model.ts +26 -1
- package/src/components/ui/data-table/types.ts +2 -0
- package/src/components/ui/display.tsx +18 -3
- package/src/components/ui/icon.tsx +4 -1
- package/src/components/ui/list-view.tsx +8 -4
- package/src/components/ui/query-bar-keys.tsx +2 -0
- package/src/components/ui/query-bar.tsx +1 -0
- package/src/components/ui/select-button.tsx +5 -1
- package/src/components/ui/select-field.tsx +1 -0
- package/src/components/ui/table-layout.ts +8 -5
- package/src/core/app-runtime.ts +2 -0
- package/src/core/app-services.ts +2 -2
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/plugin-cache.ts +5 -3
- package/src/data/saved-copies.ts +8 -0
- package/src/data/ticker-repository.ts +16 -9
- package/src/data/ticker-writes.ts +81 -0
- package/src/data/ttl-cache.ts +165 -0
- package/src/docs/snapshot.json +103 -142
- package/src/i18n/es.ts +20 -6
- package/src/i18n/ja.ts +20 -6
- package/src/i18n/ko.ts +20 -6
- package/src/i18n/zh-cn.ts +20 -6
- package/src/i18n/zh-tw.ts +20 -6
- package/src/market-data/depositary-pair.ts +27 -3
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +16 -3
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/options-alternatives.ts +81 -4
- package/src/market-data/quotes/metadata.ts +7 -0
- package/src/news/aggregator.ts +119 -9
- package/src/news/cloud-session.ts +21 -0
- package/src/news/hooks.ts +1 -0
- package/src/news/news-model.ts +1 -0
- package/src/news/types.ts +17 -0
- package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +362 -80
- package/src/plugins/builtin/alerts/command.ts +25 -1
- package/src/plugins/builtin/alerts/event-rows.ts +24 -3
- package/src/plugins/builtin/alerts/events-pane.tsx +34 -10
- package/src/plugins/builtin/alerts/fired-log.ts +73 -9
- package/src/plugins/builtin/alerts/format.ts +75 -5
- package/src/plugins/builtin/alerts/headless.ts +34 -12
- package/src/plugins/builtin/alerts/history-rows.ts +3 -2
- package/src/plugins/builtin/alerts/history.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +79 -16
- package/src/plugins/builtin/alerts/metric-command.ts +77 -4
- package/src/plugins/builtin/alerts/metric-data.ts +54 -13
- package/src/plugins/builtin/alerts/metric-model.ts +163 -14
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
- package/src/plugins/builtin/alerts/pane.tsx +61 -23
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +12 -8
- package/src/plugins/builtin/alerts/types.ts +4 -0
- package/src/plugins/builtin/analytics/index.tsx +2 -1
- package/src/plugins/builtin/analytics/risk-client.ts +16 -5
- package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
- package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
- package/src/plugins/builtin/analytics/risk-model.ts +55 -9
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/attention/index.tsx +2 -1
- package/src/plugins/builtin/attention/listing.ts +14 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/buildout/cells.tsx +12 -4
- package/src/plugins/builtin/buildout/format.ts +38 -0
- package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
- package/src/plugins/builtin/buildout/table-model.ts +23 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
- package/src/plugins/builtin/cds/client.ts +142 -30
- package/src/plugins/builtin/cds/headless.ts +35 -8
- package/src/plugins/builtin/cds/model.ts +145 -18
- package/src/plugins/builtin/cds/pane.tsx +16 -5
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
- package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
- package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
- package/src/plugins/builtin/chart-composer/headless.ts +6 -1
- package/src/plugins/builtin/chart-composer/index.tsx +5 -6
- package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
- package/src/plugins/builtin/chat/controller/index.ts +5 -1
- package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
- package/src/plugins/builtin/chat/controller/state.ts +25 -2
- package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
- package/src/plugins/builtin/chat/controller/utils.ts +7 -2
- package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
- package/src/plugins/builtin/chat/status-preview.ts +79 -0
- package/src/plugins/builtin/chat/status-widget.tsx +147 -10
- package/src/plugins/builtin/chat/unread-badge.ts +51 -10
- package/src/plugins/builtin/clinical-trials/client.ts +49 -4
- package/src/plugins/builtin/clinical-trials/model.ts +86 -31
- package/src/plugins/builtin/clinical-trials/pane.tsx +61 -49
- package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +95 -15
- package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- package/src/plugins/builtin/company-attention/building.tsx +5 -1
- package/src/plugins/builtin/company-attention/client.ts +8 -9
- package/src/plugins/builtin/company-attention/evidence.ts +4 -1
- package/src/plugins/builtin/company-attention/headless.ts +2 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +12 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/company-kpis/headless.ts +8 -3
- package/src/plugins/builtin/company-kpis/model.ts +73 -5
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/composite-plugins.ts +2 -1
- package/src/plugins/builtin/correlation/headless.ts +13 -5
- package/src/plugins/builtin/correlation/history.ts +22 -0
- package/src/plugins/builtin/cot/client.ts +22 -18
- package/src/plugins/builtin/cot/headless.ts +15 -10
- package/src/plugins/builtin/cot/model.ts +14 -1
- package/src/plugins/builtin/cot/pane.tsx +22 -14
- package/src/plugins/builtin/credit-boards/client.ts +6 -10
- package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
- package/src/plugins/builtin/credit-conditions/model.ts +4 -0
- package/src/plugins/builtin/credit-conditions/series.ts +3 -1
- package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
- package/src/plugins/builtin/credit-documents/export.ts +173 -0
- package/src/plugins/builtin/credit-documents/headless.ts +10 -10
- package/src/plugins/builtin/credit-documents/model.ts +104 -14
- package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
- package/src/plugins/builtin/custom-view/sources.ts +2 -0
- package/src/plugins/builtin/dividend-yield/client.ts +131 -23
- package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
- package/src/plugins/builtin/dividend-yield/model.ts +8 -5
- package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
- package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
- package/src/plugins/builtin/dividend-yield/types.ts +20 -2
- package/src/plugins/builtin/dividend-yield/view.ts +30 -2
- package/src/plugins/builtin/docs/targets.ts +5 -1
- package/src/plugins/builtin/doe/headless.ts +3 -2
- package/src/plugins/builtin/doe/pane.tsx +13 -3
- package/src/plugins/builtin/earnings/board-model.ts +16 -15
- package/src/plugins/builtin/earnings/board.tsx +62 -71
- package/src/plugins/builtin/earnings/headless.ts +40 -4
- package/src/plugins/builtin/earnings/scope.ts +31 -43
- package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
- package/src/plugins/builtin/earnings-calls/data.ts +18 -6
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +20 -5
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +12 -4
- package/src/plugins/builtin/econ/calendar-model.ts +48 -1
- package/src/plugins/builtin/econ/calendar-source.ts +4 -2
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/figures.ts +48 -0
- package/src/plugins/builtin/econ/index.tsx +14 -5
- package/src/plugins/builtin/econ/print-watch.ts +3 -1
- package/src/plugins/builtin/econ/types.ts +2 -0
- package/src/plugins/builtin/econ-statistics/client.ts +11 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +16 -6
- package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +39 -9
- package/src/plugins/builtin/equity-screener/categories.ts +179 -0
- package/src/plugins/builtin/equity-screener/client.ts +4 -2
- package/src/plugins/builtin/equity-screener/headless.ts +144 -13
- package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
- package/src/plugins/builtin/equity-screener/model.ts +99 -10
- package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
- package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
- package/src/plugins/builtin/fx-matrix/client.ts +6 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
- package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
- package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
- package/src/plugins/builtin/gpu/headless.ts +8 -2
- package/src/plugins/builtin/help/function-index.ts +4 -1
- package/src/plugins/builtin/help/index.tsx +62 -59
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
- package/src/plugins/builtin/help/table-cells.tsx +13 -2
- package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +22 -3
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/holders/report-rows.ts +4 -1
- package/src/plugins/builtin/insider/headless.ts +2 -2
- package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
- package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
- package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
- package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
- package/src/plugins/builtin/iv-history/client.ts +52 -11
- package/src/plugins/builtin/iv-history/format.ts +36 -23
- package/src/plugins/builtin/iv-history/headless.ts +33 -18
- package/src/plugins/builtin/iv-history/index.tsx +2 -2
- package/src/plugins/builtin/iv-history/model.ts +163 -27
- package/src/plugins/builtin/iv-history/pane.tsx +10 -6
- package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
- package/src/plugins/builtin/iv-history/universe.ts +5 -1
- package/src/plugins/builtin/jobs/client.ts +25 -29
- package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
- package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
- package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
- package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
- package/src/plugins/builtin/macro-day/headless.ts +12 -5
- package/src/plugins/builtin/macro-day/index.tsx +1 -1
- package/src/plugins/builtin/macro-day/model.ts +39 -5
- package/src/plugins/builtin/macro-day/pane.tsx +27 -19
- package/src/plugins/builtin/macro-day/release-table.ts +2 -2
- package/src/plugins/builtin/market-heatmap/data.ts +8 -33
- package/src/plugins/builtin/market-movers/client.ts +27 -7
- package/src/plugins/builtin/market-movers/headless.ts +34 -12
- package/src/plugins/builtin/market-movers/index.tsx +26 -14
- package/src/plugins/builtin/market-movers/min-float.ts +74 -0
- package/src/plugins/builtin/market-movers/model.ts +4 -1
- package/src/plugins/builtin/market-movers/screener.ts +22 -4
- package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
- package/src/plugins/builtin/market-movers/session-table.ts +4 -3
- package/src/plugins/builtin/market-movers/table.ts +17 -5
- package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
- package/src/plugins/builtin/mna/headless.ts +2 -0
- package/src/plugins/builtin/mna/pane.tsx +3 -1
- package/src/plugins/builtin/news/headless.ts +3 -1
- package/src/plugins/builtin/news/index.tsx +4 -0
- package/src/plugins/builtin/news/wire/categories.ts +19 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
- package/src/plugins/builtin/openfda/client.ts +14 -2
- package/src/plugins/builtin/openfda/index.tsx +3 -2
- package/src/plugins/builtin/openfda/model.ts +9 -0
- package/src/plugins/builtin/openfda/pane.tsx +42 -6
- package/src/plugins/builtin/options/analytics.ts +29 -5
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/table.ts +5 -2
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +17 -4
- package/src/plugins/builtin/options-positioning/client.ts +11 -0
- package/src/plugins/builtin/options-positioning/headless.ts +21 -8
- package/src/plugins/builtin/options-positioning/index.tsx +1 -1
- package/src/plugins/builtin/options-positioning/model.ts +78 -9
- package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
- package/src/plugins/builtin/options-scenario/client.ts +116 -47
- package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
- package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
- package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
- package/src/plugins/builtin/options-scenario/csp.ts +8 -2
- package/src/plugins/builtin/options-scenario/index.tsx +9 -7
- package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
- package/src/plugins/builtin/pe-band/model.ts +12 -49
- package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
- package/src/plugins/builtin/perps/cascade.ts +236 -0
- package/src/plugins/builtin/perps/client.ts +56 -2
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +19 -22
- package/src/plugins/builtin/perps/status-line.ts +25 -0
- package/src/plugins/builtin/plugin-module.ts +5 -1
- package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
- package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -7
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +0 -14
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +5 -2
- package/src/plugins/builtin/portfolio-list/table.tsx +15 -9
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/realized-vol/client.ts +13 -5
- package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
- package/src/plugins/builtin/realized-vol/headless.ts +8 -4
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/model.ts +37 -6
- package/src/plugins/builtin/realized-vol/pane.tsx +11 -9
- package/src/plugins/builtin/relative-rotation/client.ts +18 -4
- package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
- package/src/plugins/builtin/relative-rotation/model.ts +16 -40
- package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
- package/src/plugins/builtin/research/event-model.ts +40 -12
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/index.tsx +7 -3
- package/src/plugins/builtin/research/relative-valuation-headless.ts +13 -26
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +59 -20
- package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
- package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
- package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
- package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
- package/src/plugins/builtin/risk-factors/data.ts +4 -2
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/roadmap/index.tsx +28 -0
- package/src/plugins/builtin/roadmap/model.ts +67 -0
- package/src/plugins/builtin/roadmap/pane.tsx +244 -0
- package/src/plugins/builtin/scanner/flow-model.ts +50 -4
- package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
- package/src/plugins/builtin/scanner/flow-session.ts +146 -0
- package/src/plugins/builtin/scanner/headless.ts +204 -35
- package/src/plugins/builtin/scanner/index.ts +16 -5
- package/src/plugins/builtin/scanner/live-headless.ts +48 -5
- package/src/plugins/builtin/scanner/live-model.ts +11 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/headless.ts +10 -1
- package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
- package/src/plugins/builtin/sec/fund-facts.ts +116 -34
- package/src/plugins/builtin/sec/headless.ts +4 -2
- package/src/plugins/builtin/sec/index.tsx +3 -0
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +16 -7
- package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
- package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
- package/src/plugins/builtin/shared/earnings-report.ts +2 -2
- package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
- package/src/plugins/builtin/shared/job-posting.ts +1 -0
- package/src/plugins/builtin/shared/members-menu.tsx +3 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
- package/src/plugins/builtin/shared/report-freshness.ts +11 -2
- package/src/plugins/builtin/shared/research-cells.tsx +1 -1
- package/src/plugins/builtin/shared/series-cache.ts +4 -4
- package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
- package/src/plugins/builtin/shared/ticker-company.ts +77 -0
- package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/model.ts +16 -7
- package/src/plugins/builtin/themes/pane.tsx +5 -5
- package/src/plugins/builtin/thirteenf/api.ts +12 -1
- package/src/plugins/builtin/thirteenf/data.ts +7 -7
- package/src/plugins/builtin/thirteenf/headless.ts +31 -4
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
- package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
- package/src/plugins/builtin/ticker-detail/overview/model.ts +11 -3
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +14 -8
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
- package/src/plugins/builtin/time-sales/pane.tsx +31 -19
- package/src/plugins/builtin/treasury-auctions/headless.ts +21 -12
- package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
- package/src/plugins/builtin/treasury-daily/client.ts +41 -5
- package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
- package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
- package/src/plugins/builtin/treasury-daily/model.ts +50 -3
- package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
- package/src/plugins/builtin/vol-surface/client.ts +3 -13
- package/src/plugins/builtin/vol-surface/headless.ts +3 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +7 -4
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/builtin/yield-curve/client.ts +61 -1
- package/src/plugins/builtin/yield-curve/headless.ts +9 -6
- package/src/plugins/builtin/yield-curve/index.tsx +34 -10
- package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
- package/src/plugins/catalog-browser.ts +2 -1
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/host-link.ts +1 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +11 -7
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/remote/resources.ts +2 -1
- package/src/renderers/dom/data-table/row.tsx +26 -0
- package/src/renderers/dom/desktop/icons.tsx +7 -0
- package/src/renderers/dom/desktop/list-view.tsx +13 -10
- package/src/renderers/dom/desktop/query-bar.tsx +4 -2
- package/src/renderers/dom/desktop/select-field.tsx +4 -2
- package/src/renderers/dom/host/chart-surface.tsx +21 -0
- package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
- package/src/renderers/electrobun/bun/desktop/backend.ts +21 -10
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +15 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
- package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
- package/src/renderers/electrobun/bun/index.ts +26 -2
- package/src/renderers/electrobun/bun/window/create.ts +43 -4
- package/src/renderers/electrobun/bun/window/events.ts +20 -0
- package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
- package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
- package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
- package/src/renderers/electrobun/shared/app-name.ts +13 -0
- package/src/renderers/electrobun/shared/protocol.ts +12 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +5 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/ui-host.tsx +8 -0
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- package/src/series/registry.ts +1 -0
- package/src/series/types.ts +6 -0
- package/src/series/validate.ts +1 -0
- package/src/sources/gloomberb-cloud/fred-series.ts +3 -1
- package/src/sources/gloomberb-cloud/index.ts +43 -8
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/index.ts +8 -5
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/controller.ts +4 -3
- package/src/sync/core-contributors.ts +90 -31
- package/src/telemetry/stall-reports-dom.ts +38 -0
- package/src/telemetry/stall-reports-node.ts +75 -0
- package/src/telemetry/stall-reports.ts +214 -0
- package/src/test-support/preload.ts +14 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name-listing.ts +53 -13
- package/src/tickers/company-name.ts +50 -4
- package/src/tickers/index-roots.ts +7 -0
- package/src/tickers/listing-fate.ts +2 -2
- package/src/tickers/listing-venues.ts +15 -3
- package/src/tickers/search/index.ts +6 -2
- package/src/tickers/search/ranking.ts +5 -1
- package/src/tickers/search/upsert.ts +13 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/tickers/ticker-changes.ts +8 -4
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +1 -1
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/fundamentals.ts +44 -1
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/resolve-context.ts +3 -1
- package/src/time-series/resolve.ts +56 -4
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +3 -2
- package/src/time-series/studies.ts +8 -2
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +2 -3
- package/src/time-series/types.ts +22 -1
- package/src/types/company-from.ts +40 -0
- package/src/types/data-provider.ts +7 -0
- package/src/types/financials.ts +59 -1
- package/src/types/headless.ts +27 -3
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +9 -0
- package/src/ui/host.tsx +3 -0
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +165 -7
- package/src/utils/declared-dividends.ts +1 -1
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +60 -8
- package/src/utils/fx-market-hours.ts +26 -3
- package/src/utils/main-thread-stall.ts +35 -2
- package/src/utils/market-capitalization.ts +24 -1
- package/src/utils/price-history.ts +7 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/version.ts +2 -2
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
|
@@ -13,7 +13,10 @@ import { useThemeColors } from "../../../theme/theme-context";
|
|
|
13
13
|
import type { PaneProps } from "../../../types/plugin";
|
|
14
14
|
import { Box } from "../../../ui";
|
|
15
15
|
import { loadMacroDayEarnings, loadMacroDayHistory, loadMacroReleases, macroDayEarningsNotice } from "./client";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
formatMacroDayMove, macroDayEventLabel, macroDayMoveDecimals, macroDayStats, matchesSurprise, projectMacroDays, type MacroDayEvent,
|
|
18
|
+
type MacroDayModel, type MacroDayMoveBasis,
|
|
19
|
+
} from "./model";
|
|
17
20
|
import { MACRO_EVENT_KINDS, MACRO_EVENT_LABELS, MACRO_SURPRISE_LABELS, MACRO_SURPRISES, staleMacroReleasesNotice, type MacroEventKind,
|
|
18
21
|
type MacroSurprise } from "./releases";
|
|
19
22
|
|
|
@@ -21,17 +24,13 @@ export const LOOKBACK_OPTIONS = [{ value: "1", label: "1Y" }, { value: "2", labe
|
|
|
21
24
|
const RELEASE_OPTIONS = [{ value: "all", label: "All" }, ...MACRO_EVENT_KINDS.map((kind) => ({ value: kind, label: MACRO_EVENT_LABELS[kind] }))];
|
|
22
25
|
const SURPRISE_OPTIONS = [{ value: "all", label: "All" }, ...MACRO_SURPRISES.map((surprise) => ({ value: surprise, label: MACRO_SURPRISE_LABELS[surprise] }))];
|
|
23
26
|
|
|
24
|
-
function pct(value: number | null | undefined, signed = true): string {
|
|
25
|
-
if (value == null) return "--";
|
|
26
|
-
const fixed = (value * 100).toFixed(2);
|
|
27
|
-
return /[1-9]/.test(fixed) ? `${signed && value > 0 ? "+" : ""}${fixed}%` : "0.00%";
|
|
28
|
-
}
|
|
29
27
|
const times = (value: number | null | undefined) => value == null ? "--" : `${value.toFixed(1)}x`;
|
|
30
28
|
|
|
31
29
|
const EARNINGS_MARK = "earnings";
|
|
32
30
|
const DATE: DataTableColumn = { id: "date", label: "Date", width: 19, align: "left" };
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
/** The move and its multiple of a normal day's; a yield's move is in basis points, and its header says so. */
|
|
32
|
+
const moveColumns = (basis: MacroDayMoveBasis): DataTableColumn[] => [
|
|
33
|
+
{ id: "move", label: basis === "yield" ? "Move bp" : "Move", width: 9, align: "right" },
|
|
35
34
|
{ id: "multiple", label: "vs Normal", width: 10, align: "right" },
|
|
36
35
|
];
|
|
37
36
|
/** What each release came out with, under the labels the economic calendar gives them. */
|
|
@@ -48,23 +47,25 @@ function figureColumns(rows: readonly MacroDayEvent[]): DataTableColumn[] {
|
|
|
48
47
|
* With every release listed, the release's name, widened to take the earnings marker where a row has one. With one
|
|
49
48
|
* selected, a column only for the marker: every row would repeat the name.
|
|
50
49
|
*/
|
|
51
|
-
export function columnsFor(release: string, rows: readonly MacroDayEvent[]): DataTableColumn[] {
|
|
50
|
+
export function columnsFor(release: string, rows: readonly MacroDayEvent[], basis: MacroDayMoveBasis = "return"): DataTableColumn[] {
|
|
52
51
|
const marked = rows.some((event) => event.earnings);
|
|
53
|
-
const values = [...figureColumns(rows), ...
|
|
52
|
+
const values = [...figureColumns(rows), ...moveColumns(basis)];
|
|
54
53
|
if (release === "all") return [DATE, { id: "release", label: "Release", width: marked ? 15 : 8, align: "left" }, ...values];
|
|
55
54
|
return marked ? [DATE, { id: "release", label: "Note", width: EARNINGS_MARK.length, align: "left" }, ...values] : [DATE, ...values];
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
/** By the sign the
|
|
59
|
-
export const moveTone = (value: number | null
|
|
60
|
-
|
|
57
|
+
/** By the sign the text shows: an average that rounds to 0.00% (or 0.0bp) is neither up nor down. */
|
|
58
|
+
export const moveTone = (value: number | null, basis: MacroDayMoveBasis = "return") =>
|
|
59
|
+
value == null || !/[1-9]/.test((value * 100).toFixed(macroDayMoveDecimals(basis))) ? undefined
|
|
60
|
+
: value > 0 ? "positive" as const : "negative" as const;
|
|
61
61
|
|
|
62
62
|
/** The figures over the days a surprise filter keeps: the same four as one release's. */
|
|
63
63
|
function surpriseFigures(model: MacroDayModel, rows: readonly MacroDayEvent[]): StatItem[] {
|
|
64
64
|
const stats = macroDayStats(rows, model.normal.meanAbs);
|
|
65
|
+
const pct = (value: number | null, signed = true) => formatMacroDayMove(value, model.basis, { signed });
|
|
65
66
|
return [
|
|
66
67
|
{ id: "abs", label: "Avg |move|", value: pct(stats.meanAbs, false), detail: stats.count ? `${times(stats.multiple)} normal` : undefined },
|
|
67
|
-
{ id: "mean", label: "Avg move", value: pct(stats.mean), tone: moveTone(stats.mean) },
|
|
68
|
+
{ id: "mean", label: "Avg move", value: pct(stats.mean), tone: moveTone(stats.mean, model.basis) },
|
|
68
69
|
{ id: "up", label: "Up", value: stats.hitRate == null ? "--" : `${Math.round(stats.hitRate * 100)}%`,
|
|
69
70
|
detail: stats.count ? `${Math.round(stats.hitRate! * stats.count)} of ${stats.count}` : undefined },
|
|
70
71
|
{ id: "normal", label: "Normal day", value: pct(model.normal.meanAbs, false), detail: "avg |move|" },
|
|
@@ -72,6 +73,7 @@ function surpriseFigures(model: MacroDayModel, rows: readonly MacroDayEvent[]):
|
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
function figures(model: MacroDayModel, release: string): StatItem[] {
|
|
76
|
+
const pct = (value: number | null, signed = true) => formatMacroDayMove(value, model.basis, { signed });
|
|
75
77
|
const normal: StatItem = { id: "normal", label: "Normal day", value: pct(model.normal.meanAbs, false), detail: "avg |move|" };
|
|
76
78
|
if (release === "all") {
|
|
77
79
|
return [...model.kinds.map((kind): StatItem => {
|
|
@@ -84,7 +86,7 @@ function figures(model: MacroDayModel, release: string): StatItem[] {
|
|
|
84
86
|
const stats = model.byKind[release as MacroEventKind];
|
|
85
87
|
return [
|
|
86
88
|
{ id: "abs", label: "Avg |move|", value: pct(stats.meanAbs, false), detail: stats.count ? `${times(stats.multiple)} normal` : undefined },
|
|
87
|
-
{ id: "mean", label: "Avg move", value: pct(stats.mean), tone: moveTone(stats.mean) },
|
|
89
|
+
{ id: "mean", label: "Avg move", value: pct(stats.mean), tone: moveTone(stats.mean, model.basis) },
|
|
88
90
|
{ id: "up", label: "Up", value: stats.hitRate == null ? "--" : `${Math.round(stats.hitRate * 100)}%`,
|
|
89
91
|
detail: stats.count ? `${Math.round(stats.hitRate! * stats.count)} of ${stats.count}` : undefined },
|
|
90
92
|
normal,
|
|
@@ -123,7 +125,7 @@ export function MacroDayPane({ width, height, focused }: PaneProps) {
|
|
|
123
125
|
&& matchesSurprise(event, surprise)) : [], [model, release, surprise]);
|
|
124
126
|
const stats = useMemo(() => !model?.events.length ? [] : surprise === "all" ? figures(model, release) : surpriseFigures(model, rows),
|
|
125
127
|
[model, release, surprise, rows]);
|
|
126
|
-
const columns = useMemo(() => columnsFor(release, rows), [release, rows]);
|
|
128
|
+
const columns = useMemo(() => columnsFor(release, rows, model?.basis), [model?.basis, release, rows]);
|
|
127
129
|
|
|
128
130
|
usePaneNoticeFooter({ registrationId: "macro-day-notices", focused,
|
|
129
131
|
notices: [...new Set([identityError, history.error, history.data?.error, releasesNotice, earningsNotice].filter((value): value is string => !!value))] });
|
|
@@ -141,9 +143,14 @@ export function MacroDayPane({ width, height, focused }: PaneProps) {
|
|
|
141
143
|
: event.figures?.surprise === "cool" ? colors.negative : undefined };
|
|
142
144
|
if (column.id === "forecast") return { text: event.figures?.forecast ?? "" };
|
|
143
145
|
if (column.id === "prior") return { text: event.figures?.prior ?? "" };
|
|
144
|
-
if (column.id === "move")
|
|
146
|
+
if (column.id === "move") {
|
|
147
|
+
const basis = model?.basis ?? "return";
|
|
148
|
+
// Under a column headed bp, a yield's move is the number alone.
|
|
149
|
+
return { text: formatMacroDayMove(event.move, basis, { unit: false }), value: event.move * 100,
|
|
150
|
+
color: shownPriceColor(event.move * 100, macroDayMoveDecimals(basis), colors) };
|
|
151
|
+
}
|
|
145
152
|
return { text: times(event.multiple), value: event.multiple };
|
|
146
|
-
}, [colors, release]);
|
|
153
|
+
}, [colors, model?.basis, release]);
|
|
147
154
|
|
|
148
155
|
const contentHeight = Math.max(4, height - 1 - statGridRows(stats, width));
|
|
149
156
|
const key = (event: MacroDayEvent) => `${event.kind}:${event.date}`;
|
|
@@ -162,7 +169,8 @@ export function MacroDayPane({ width, height, focused }: PaneProps) {
|
|
|
162
169
|
getItemKey={key} emptyStateTitle={surprise === "all" ? "No release days." : `No ${MACRO_SURPRISE_LABELS[surprise].toLowerCase()} prints.`} sortColumnId={null} sortDirection="desc" selectedTextOverridesCellColor
|
|
163
170
|
selection={{ kind: "id", selectedId: selectedRow, getId: key, onChange: (id) => setSelectedRow(id) }}
|
|
164
171
|
getExportMetadata={() => [["symbol", model.symbol], ["from", model.start ?? ""], ["to", model.asOf ?? ""],
|
|
165
|
-
["units", "
|
|
172
|
+
["units", model.basis === "yield" ? "percentage points of yield, close to close on the release day"
|
|
173
|
+
: "percent, close to close on the release day"]]}
|
|
166
174
|
renderCell={renderCell} />
|
|
167
175
|
</> : null}
|
|
168
176
|
</PaneStatusBody>}
|
|
@@ -5,9 +5,9 @@ export const MACRO_RELEASE_TABLE: {
|
|
|
5
5
|
coveredThrough: string;
|
|
6
6
|
days: Record<"cpi" | "jobs" | "fomc", Record<number, readonly string[]>>;
|
|
7
7
|
} = {
|
|
8
|
-
checkedAt: "2026-10-
|
|
8
|
+
checkedAt: "2026-10-28",
|
|
9
9
|
/** The last day the lists are complete for: later sessions are left out, release day or not. */
|
|
10
|
-
coveredThrough: "2026-10-
|
|
10
|
+
coveredThrough: "2026-10-27",
|
|
11
11
|
days: {
|
|
12
12
|
// BLS Consumer Price Index, 8:30 ET.
|
|
13
13
|
cpi: {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { apiClient } from "../../../api-client";
|
|
2
|
+
import { createTtlCache } from "../../../data/ttl-cache";
|
|
2
3
|
import type { MarketHeatmapResult, MarketHeatmapUniverseId } from "../../../api-client/market-discovery";
|
|
3
4
|
export type { MarketHeatmapUniverseId } from "../../../api-client/market-discovery";
|
|
4
5
|
|
|
@@ -39,8 +40,7 @@ const MAX_MARKET_HEATMAP_COUNT = 500;
|
|
|
39
40
|
export function heatmapCheckIntervalMs(board: Pick<MarketHeatmapResult, "access"> | null | undefined): number {
|
|
40
41
|
return board?.access === "realtime" ? 15_000 : 60_000;
|
|
41
42
|
}
|
|
42
|
-
const
|
|
43
|
-
const memoryCache = new Map<string, { expiresAt: number; result: MarketHeatmapBoard }>();
|
|
43
|
+
const boards = createTtlCache<MarketHeatmapBoard>({ ttlMs: heatmapCheckIntervalMs });
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* One tile per symbol. A company listed on two exchanges (KHC on NASDAQ and
|
|
@@ -75,43 +75,18 @@ async function loadMarketHeatmap(universe: MarketHeatmapUniverseId, count: numbe
|
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
export
|
|
78
|
+
export function fetchMarketHeatmap(
|
|
79
79
|
universe: MarketHeatmapUniverseId,
|
|
80
80
|
options?: MarketHeatmapFetchOptions,
|
|
81
81
|
sources?: MarketHeatmapSources,
|
|
82
82
|
): Promise<MarketHeatmapBoard> {
|
|
83
83
|
const count = Math.max(1, Math.min(MAX_MARKET_HEATMAP_COUNT, Math.round(options?.count ?? DEFAULT_COUNT)));
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (useCache && !options?.forceRefresh && cached && cached.expiresAt > now) {
|
|
89
|
-
return cached.result;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (useCache) {
|
|
93
|
-
const active = activeFetches.get(cacheKey);
|
|
94
|
-
if (active) return active;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const fetchPromise = loadMarketHeatmap(universe, count, sources)
|
|
98
|
-
.then((result) => {
|
|
99
|
-
if (useCache) {
|
|
100
|
-
memoryCache.set(cacheKey, { result, expiresAt: Date.now() + heatmapCheckIntervalMs(result) });
|
|
101
|
-
}
|
|
102
|
-
return result;
|
|
103
|
-
})
|
|
104
|
-
.finally(() => {
|
|
105
|
-
if (activeFetches.get(cacheKey) === fetchPromise) {
|
|
106
|
-
activeFetches.delete(cacheKey);
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
if (useCache) activeFetches.set(cacheKey, fetchPromise);
|
|
111
|
-
return fetchPromise;
|
|
84
|
+
const load = () => loadMarketHeatmap(universe, count, sources);
|
|
85
|
+
// A board asked for uncached, or through another client, neither reads nor fills the shared cache.
|
|
86
|
+
if (options?.cache === false || sources?.client) return load();
|
|
87
|
+
return boards.load(`${universe}:${count}`, load, { force: options?.forceRefresh });
|
|
112
88
|
}
|
|
113
89
|
|
|
114
90
|
export function resetMarketHeatmapCache(): void {
|
|
115
|
-
|
|
116
|
-
memoryCache.clear();
|
|
91
|
+
boards.clear();
|
|
117
92
|
}
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
} from "../../../api-client/market-movers";
|
|
7
7
|
import type { DataProvider } from "../../../types/data-provider";
|
|
8
8
|
import { logFailure } from "../../../utils/log-failure";
|
|
9
|
+
import { savedCopyMark } from "../../../data/saved-copies";
|
|
9
10
|
import {
|
|
10
11
|
CATEGORY_MAP,
|
|
11
12
|
moverRegularSession,
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
type PreferredMoversOptions,
|
|
25
26
|
type TrendingSymbol,
|
|
26
27
|
} from "./screener";
|
|
28
|
+
import { meetsMinFloat } from "./min-float";
|
|
27
29
|
|
|
28
30
|
export interface MarketMoverTabResult extends MarketMoversResult {
|
|
29
31
|
tab: ScreenerTabId;
|
|
@@ -100,14 +102,19 @@ export function marketMoverQuoteError(result: Pick<MarketMoverTabResult, "unavai
|
|
|
100
102
|
export async function loadMarketMoverTab(
|
|
101
103
|
tab: ScreenerTabId,
|
|
102
104
|
provider: DataProvider | null,
|
|
103
|
-
options?: Pick<PreferredMoversOptions, "forceRefresh" | "metadataWaitMs"> & {
|
|
105
|
+
options?: Pick<PreferredMoversOptions, "forceRefresh" | "metadataWaitMs"> & {
|
|
106
|
+
basis?: MoverRankBasis;
|
|
107
|
+
/** Keep the rows with a float of at least this many shares. Trending carries no float and ignores it. */
|
|
108
|
+
minFloat?: number | null;
|
|
109
|
+
},
|
|
104
110
|
dependencies: MarketMoverTabDependencies = defaultDependencies,
|
|
105
111
|
): Promise<MarketMoverTabResult> {
|
|
106
|
-
const { basis, ...fetchOptions } = options ?? {};
|
|
112
|
+
const { basis, minFloat = null, ...fetchOptions } = options ?? {};
|
|
107
113
|
if (tab === "trending") {
|
|
108
114
|
const trending = await dependencies.fetchTrending(LIST_ROWS, fetchOptions);
|
|
109
115
|
const hydrated = await hydrateTrending(trending.data, provider);
|
|
110
116
|
return {
|
|
117
|
+
...savedCopyMark(trending),
|
|
111
118
|
tab,
|
|
112
119
|
...hydrated,
|
|
113
120
|
source: "gloom",
|
|
@@ -116,13 +123,15 @@ export async function loadMarketMoverTab(
|
|
|
116
123
|
}
|
|
117
124
|
const category = CATEGORY_MAP[tab];
|
|
118
125
|
// The server picks the candidates on the basis the list ranks by, so after the close they are the session's movers.
|
|
119
|
-
|
|
126
|
+
// A minimum float drops rows, so Most Active then reads as many candidates as the price lists.
|
|
127
|
+
const result = await dependencies.fetchPreferred(category, category === "most_actives" && minFloat == null ? LIST_ROWS : MOVER_CANDIDATES, {
|
|
120
128
|
...fetchOptions,
|
|
121
129
|
session: basis ?? "regular",
|
|
122
130
|
});
|
|
123
131
|
// Each row read as `ticker` reads it, then ranked by the move its Chg% shows.
|
|
124
132
|
const { quotes, rankedBy } = rankMoverQuotes(category, result.quotes.map(moverRegularSession), basis);
|
|
125
|
-
|
|
133
|
+
const kept = quotes.filter((quote) => meetsMinFloat(quote.floatShares, minFloat));
|
|
134
|
+
return { ...result, quotes: kept.slice(0, LIST_ROWS), rankedBy, tab };
|
|
126
135
|
}
|
|
127
136
|
|
|
128
137
|
/** The server refused the list for the account's plan (gaps, without Pro). */
|
|
@@ -136,12 +145,23 @@ export class SessionMoversAccessError extends Error {
|
|
|
136
145
|
export async function loadSessionMovers(
|
|
137
146
|
view: CloudSessionMoversCategory,
|
|
138
147
|
side: CloudSessionMoversSide,
|
|
139
|
-
options?: { forceRefresh?: boolean },
|
|
148
|
+
options?: { forceRefresh?: boolean; minFloat?: number | null },
|
|
140
149
|
): Promise<CloudSessionMoversPayload> {
|
|
141
|
-
const
|
|
150
|
+
const minFloat = options?.minFloat ?? null;
|
|
151
|
+
// A minimum float drops rows, so it reads the most the server lists.
|
|
152
|
+
const response = await apiClient.getCloudMarketScreener(view, minFloat == null ? LIST_ROWS : MOVER_CANDIDATES, options?.forceRefresh ? "refresh" : "cache-first", side);
|
|
142
153
|
if (response.reasonCode === "PRO_REQUIRED") throw new SessionMoversAccessError();
|
|
143
154
|
if ((response.status === "success" || response.status === "partial") && response.data) {
|
|
144
|
-
|
|
155
|
+
const payload = response.stale ? { ...response.data, stale: true } : response.data;
|
|
156
|
+
return minFloat == null ? payload : { ...payload, items: withMinFloat(payload.items, minFloat) };
|
|
145
157
|
}
|
|
146
158
|
throw new Error("Session movers temporarily unavailable");
|
|
147
159
|
}
|
|
160
|
+
|
|
161
|
+
/** The rows with a float of at least `minFloat`, the first LIST_ROWS of them, ranked again from 1. */
|
|
162
|
+
export function withMinFloat<T extends { rank: number; floatShares: number | null }>(items: readonly T[], minFloat: number): T[] {
|
|
163
|
+
return items
|
|
164
|
+
.filter((item) => meetsMinFloat(item.floatShares, minFloat))
|
|
165
|
+
.slice(0, LIST_ROWS)
|
|
166
|
+
.map((item, index) => ({ ...item, rank: index + 1 }));
|
|
167
|
+
}
|
|
@@ -14,6 +14,8 @@ import { createRows, fiftyTwoWeekPositionPercent, formatMoverPrice, MOVER_RANKIN
|
|
|
14
14
|
import { EXTENDED_SESSION_LABELS, type ExtendedSession } from "../../../market-data/market/status";
|
|
15
15
|
import type { HeadlessPaneColumn } from "../../../types/headless";
|
|
16
16
|
import { isSessionTab, resolveSide } from "./session";
|
|
17
|
+
import { hasPartialFloat, minFloatShares, normalizeMinFloat, PARTIAL_FLOAT_NOTE } from "./min-float";
|
|
18
|
+
import { savedCopyMark } from "../../../data/saved-copies";
|
|
17
19
|
|
|
18
20
|
const rowPrice = (value: unknown, row: Record<string, unknown>) => formatMoverPrice(typeof value === "number" ? value : null, typeof row.currency === "string" ? row.currency : "", moverReferencePrice(row as unknown as MarketMoverRow));
|
|
19
21
|
|
|
@@ -69,15 +71,19 @@ function sessionColumns(view: CloudSessionMoversCategory) {
|
|
|
69
71
|
];
|
|
70
72
|
}
|
|
71
73
|
|
|
74
|
+
const FLOAT_COLUMN: HeadlessPaneColumn = { key: "floatShares", header: "Float", align: "right", format: compact };
|
|
75
|
+
|
|
72
76
|
export interface MarketMoversHeadlessDependencies {
|
|
73
77
|
load(
|
|
74
78
|
args: HeadlessPaneLoadArgs,
|
|
75
79
|
tab: ScreenerTabId,
|
|
76
80
|
provider: DataProvider,
|
|
81
|
+
minFloat: number | null,
|
|
77
82
|
): Promise<MarketMoverTabResult>;
|
|
78
83
|
loadSession(
|
|
79
84
|
view: CloudSessionMoversCategory,
|
|
80
85
|
side: CloudSessionMoversSide,
|
|
86
|
+
minFloat: number | null,
|
|
81
87
|
): Promise<CloudSessionMoversPayload>;
|
|
82
88
|
}
|
|
83
89
|
|
|
@@ -89,8 +95,8 @@ export interface MarketMoversHeadlessDependencies {
|
|
|
89
95
|
const ONE_SHOT_METADATA_WAIT_MS = 10_000;
|
|
90
96
|
|
|
91
97
|
const defaultDependencies: MarketMoversHeadlessDependencies = {
|
|
92
|
-
load: (_args, tab, provider) => loadMarketMoverTab(tab, provider, { metadataWaitMs: ONE_SHOT_METADATA_WAIT_MS }),
|
|
93
|
-
loadSession: (view, side) => loadSessionMovers(view, side),
|
|
98
|
+
load: (_args, tab, provider, minFloat) => loadMarketMoverTab(tab, provider, { metadataWaitMs: ONE_SHOT_METADATA_WAIT_MS, minFloat }),
|
|
99
|
+
loadSession: (view, side, minFloat) => loadSessionMovers(view, side, { minFloat }),
|
|
94
100
|
};
|
|
95
101
|
|
|
96
102
|
export function createMarketMoversHeadless(
|
|
@@ -128,6 +134,14 @@ export function createMarketMoversHeadless(
|
|
|
128
134
|
],
|
|
129
135
|
defaultValue: "up",
|
|
130
136
|
pluginState: { pluginId: "market-overview", key: "sessionSide" },
|
|
137
|
+
}, {
|
|
138
|
+
key: "min-float",
|
|
139
|
+
description: "Keep the rows whose float (the one EQS serves) is at least this many shares, such as 10M or 500k; any lifts it. A row with no float is left out. Trending carries no float and ignores it.",
|
|
140
|
+
type: "string",
|
|
141
|
+
placeholder: "shares",
|
|
142
|
+
example: "--min-float 10M",
|
|
143
|
+
normalize: normalizeMinFloat,
|
|
144
|
+
pluginState: { pluginId: "market-overview", key: "minFloat" },
|
|
131
145
|
}],
|
|
132
146
|
columns: COLUMNS,
|
|
133
147
|
describe: (args) => isSessionTab(String(args.options.list))
|
|
@@ -135,21 +149,26 @@ export function createMarketMoversHeadless(
|
|
|
135
149
|
: `Market Movers | ${String(args.options.list)}`,
|
|
136
150
|
async load(args, ctx) {
|
|
137
151
|
const tab = args.options.list as TabId;
|
|
152
|
+
const minFloatValue = typeof args.options["min-float"] === "string" ? args.options["min-float"] : null;
|
|
153
|
+
const minFloat = minFloatShares(minFloatValue);
|
|
138
154
|
if (isSessionTab(tab)) {
|
|
139
155
|
const side = resolveSide(tab, String(args.options.side));
|
|
140
|
-
const payload = await dependencies.loadSession(tab, side);
|
|
156
|
+
const payload = await dependencies.loadSession(tab, side, minFloat);
|
|
141
157
|
return {
|
|
142
158
|
columns: sessionColumns(tab),
|
|
143
159
|
rows: payload.items.map((item) => ({
|
|
144
160
|
...item,
|
|
145
161
|
events: item.catalysts.map((catalyst) => CATALYST_LABEL[catalyst] ?? catalyst).join(" "),
|
|
146
162
|
})),
|
|
147
|
-
|
|
163
|
+
...(hasPartialFloat(payload.items) ? { notes: [PARTIAL_FLOAT_NOTE] } : {}),
|
|
164
|
+
metadata: { list: tab, side, session: payload.session, phase: payload.phase, asOf: payload.asOf, stale: payload.stale === true, minFloat },
|
|
148
165
|
// A Pro account's list is the live board; the free one says it is delayed.
|
|
149
166
|
...(payload.access === "delayed" ? {} : { freshness: { status: "live" as const } }),
|
|
150
167
|
};
|
|
151
168
|
}
|
|
152
|
-
|
|
169
|
+
// Trending carries no float, so a minimum does not apply to it.
|
|
170
|
+
const floatFiltered = minFloat != null && tab !== "trending";
|
|
171
|
+
const result = await dependencies.load(args, tab, ctx.marketData, floatFiltered ? minFloat : null);
|
|
153
172
|
const ranked = createRows(result.quotes);
|
|
154
173
|
// Last and Change % are the regular session; an extended print is a column of its own.
|
|
155
174
|
const extendedSessions = moverExtendedSessions(ranked);
|
|
@@ -168,16 +187,19 @@ export function createMarketMoversHeadless(
|
|
|
168
187
|
// Beside an extended column, say which move the order follows.
|
|
169
188
|
const rankedBy = result.rankedBy ?? null;
|
|
170
189
|
const quoteError = marketMoverQuoteError(result);
|
|
190
|
+
const columns = [...COLUMNS.slice(0, 5), ...extendedSessions.map(extendedColumn), ...COLUMNS.slice(5), ...(floatFiltered ? [FLOAT_COLUMN] : [])];
|
|
191
|
+
const notes = [
|
|
192
|
+
...(rankedBy && rankedBy !== "volume" && extendedSessions.length > 0 ? [`Ranked by ${MOVER_RANKING_LABELS[rankedBy]}.`] : []),
|
|
193
|
+
...(minFloat != null && !floatFiltered ? ["Trending carries no float, so --min-float does not apply to it."] : []),
|
|
194
|
+
...(floatFiltered && hasPartialFloat(rows) ? [PARTIAL_FLOAT_NOTE] : []),
|
|
195
|
+
];
|
|
171
196
|
return {
|
|
172
|
-
...(
|
|
173
|
-
? { columns: [...COLUMNS.slice(0, 5), ...extendedSessions.map(extendedColumn), ...COLUMNS.slice(5)] }
|
|
174
|
-
: {}),
|
|
197
|
+
...(columns.length !== COLUMNS.length ? { columns } : {}),
|
|
175
198
|
rows,
|
|
176
199
|
...(quoteError ? { complete: false, unavailableSymbols: result.unavailableSymbols, errors: [quoteError] } : {}),
|
|
177
|
-
...(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
metadata: { list: tab, source: result.source, stale: result.stale, rankedBy },
|
|
200
|
+
...(notes.length > 0 ? { notes } : {}),
|
|
201
|
+
// A list served from a saved copy ends with when it was saved.
|
|
202
|
+
metadata: { ...savedCopyMark(result), list: tab, source: result.source, stale: result.stale, rankedBy, minFloat: floatFiltered ? minFloat : null },
|
|
181
203
|
// A Pro account's Gainers, Losers and Most Active are the real-time lists.
|
|
182
204
|
...(result.source === "cloud" ? { freshness: { status: "live" as const } } : {}),
|
|
183
205
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Box, useUiCapabilities } from "../../../ui";
|
|
2
2
|
import { nextHeaderSort } from "../../../utils/sort-values";
|
|
3
3
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import { DataTableView, EmptyState, usePaneFooter, usePaneTabs, type DataTableKeyEvent } from "../../../components";
|
|
4
|
+
import { DataTableView, EmptyState, QueryBar, usePaneFooter, usePaneTabs, type DataTableKeyEvent } from "../../../components";
|
|
5
5
|
import { handleRefreshKey } from "../../../components/data-table/table-pane";
|
|
6
6
|
import type { PaneProps } from "../../../types/plugin";
|
|
7
7
|
import type { PluginModule } from "../plugin-module";
|
|
@@ -41,6 +41,7 @@ import { loadMarketMoverTab, marketMoverQuoteError } from "./client";
|
|
|
41
41
|
import { summaryFooterSegments } from "./footer";
|
|
42
42
|
import { isSessionTab, resolveActiveTab, usSessionAt, type UsSession } from "./session";
|
|
43
43
|
import { SessionMoversBody } from "./session-body";
|
|
44
|
+
import { ANY_FLOAT, minFloatEmptyTitle, minFloatFilter, minFloatShares, partialFloatFooter } from "./min-float";
|
|
44
45
|
import { marketMoversHeadless } from "./headless";
|
|
45
46
|
import { buildMarketMoverColumns, renderMarketMoverCell } from "./table";
|
|
46
47
|
import type { ExtendedSession } from "../../../market-data/market/status";
|
|
@@ -152,16 +153,21 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
|
|
|
152
153
|
}) {
|
|
153
154
|
const dataProvider = useAssetData();
|
|
154
155
|
const { pinTicker } = usePluginTickerActions();
|
|
156
|
+
const [minFloatValue, setMinFloatValue] = usePluginPaneState<string>("minFloat", ANY_FLOAT);
|
|
157
|
+
// Trending carries no float, so the minimum is not offered there.
|
|
158
|
+
const floatFiltered = activeTab !== "trending";
|
|
159
|
+
const minFloat = floatFiltered ? minFloatShares(minFloatValue) : null;
|
|
160
|
+
const listKey = `${activeTab}:${minFloat ?? ""}`;
|
|
155
161
|
const [quotes, setQuotes] = useState<MoverQuote[]>([]);
|
|
156
|
-
const [
|
|
157
|
-
const visibleQuotes =
|
|
162
|
+
const [loadedKey, setLoadedKey] = useState<string | null>(null);
|
|
163
|
+
const visibleQuotes = loadedKey === listKey ? quotes : NO_QUOTES;
|
|
158
164
|
// The first load starts before the effect runs; an empty board is not "no data".
|
|
159
165
|
const [loading, setLoading] = useState(true);
|
|
160
166
|
const [loadError, setLoadError] = useState<string | null>(null);
|
|
161
167
|
const [selectedSymbol, setSelectedSymbol] = useState<string | null>(null);
|
|
162
168
|
const [sortPreference, setSortPreference] = useState<MarketMoverSortPreference>(DEFAULT_SORT_PREFERENCE);
|
|
163
169
|
const [moversStale, setMoversStale] = useState(false);
|
|
164
|
-
const [lastSuccess, setLastSuccess] = useState<{
|
|
170
|
+
const [lastSuccess, setLastSuccess] = useState<{ key: string; at: number } | null>(null);
|
|
165
171
|
|
|
166
172
|
const fetchGenRef = useRef(0);
|
|
167
173
|
|
|
@@ -192,8 +198,8 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
|
|
|
192
198
|
// A pre-market or after-hours column only while a row has such a print.
|
|
193
199
|
const extendedSessionsKey = moverExtendedSessions(rankedRows).join(",");
|
|
194
200
|
const columns = useMemo(
|
|
195
|
-
() => buildMarketMoverColumns(width, extendedSessionsKey ? extendedSessionsKey.split(",") as ExtendedSession[] : []),
|
|
196
|
-
[extendedSessionsKey, width],
|
|
201
|
+
() => buildMarketMoverColumns(width, extendedSessionsKey ? extendedSessionsKey.split(",") as ExtendedSession[] : [], { float: minFloat != null }),
|
|
202
|
+
[extendedSessionsKey, minFloat, width],
|
|
197
203
|
);
|
|
198
204
|
const rows = useMemo(() => sortRows(rankedRows, sortPreference), [rankedRows, sortPreference]);
|
|
199
205
|
const selectedIdx = selectedSymbol
|
|
@@ -223,6 +229,7 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
|
|
|
223
229
|
try {
|
|
224
230
|
const result = await loadMarketMoverTab(tab, dataProvider, {
|
|
225
231
|
forceRefresh: options?.forceRefresh,
|
|
232
|
+
minFloat,
|
|
226
233
|
});
|
|
227
234
|
if (fetchGenRef.current !== gen) return;
|
|
228
235
|
|
|
@@ -230,12 +237,12 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
|
|
|
230
237
|
// A partial list shows only its current rows; a total failure keeps this list's prior rows.
|
|
231
238
|
if (!quoteError || result.quotes.length > 0) {
|
|
232
239
|
setQuotes(result.quotes);
|
|
233
|
-
|
|
240
|
+
setLoadedKey(`${tab}:${minFloat ?? ""}`);
|
|
234
241
|
if (!options?.background) setSelectedSymbol(null);
|
|
235
242
|
}
|
|
236
243
|
setMoversStale(result.stale);
|
|
237
244
|
setLoadError(quoteError);
|
|
238
|
-
if (!quoteError && !result.stale) setLastSuccess({ tab
|
|
245
|
+
if (!quoteError && !result.stale) setLastSuccess({ key: `${tab}:${minFloat ?? ""}`, at: Date.now() });
|
|
239
246
|
} catch {
|
|
240
247
|
if (fetchGenRef.current !== gen) return;
|
|
241
248
|
setMoversStale(true);
|
|
@@ -244,7 +251,7 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
|
|
|
244
251
|
finally {
|
|
245
252
|
if (fetchGenRef.current === gen && !options?.background) setLoading(false);
|
|
246
253
|
}
|
|
247
|
-
}, [dataProvider]);
|
|
254
|
+
}, [dataProvider, minFloat]);
|
|
248
255
|
|
|
249
256
|
useEffect(() => {
|
|
250
257
|
setSelectedSymbol(null);
|
|
@@ -254,7 +261,7 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
|
|
|
254
261
|
const backgroundRefresh = useCallback(() => {
|
|
255
262
|
void loadTab(activeTab, { background: true });
|
|
256
263
|
}, [activeTab, loadTab]);
|
|
257
|
-
useAutoRefresh(lastSuccess?.
|
|
264
|
+
useAutoRefresh(lastSuccess?.key === listKey ? lastSuccess.at : null, backgroundRefresh);
|
|
258
265
|
|
|
259
266
|
const openSymbol = useCallback((row: MarketMoverRow) => {
|
|
260
267
|
pinTicker(moverKey(row), { floating: true, paneType: TICKER_RESEARCH_PANE_ID, instrument: null });
|
|
@@ -281,16 +288,21 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
|
|
|
281
288
|
id: "feed",
|
|
282
289
|
parts: [{ text: feedStatus, tone: feedStatus === "live" ? "value" as const : "muted" as const }],
|
|
283
290
|
}] : []),
|
|
284
|
-
...(moversStale &&
|
|
291
|
+
...(moversStale && loadedKey === listKey ? [{
|
|
285
292
|
id: "stale",
|
|
286
293
|
parts: [{ text: "stale", tone: "muted" as const }],
|
|
287
294
|
}] : []),
|
|
295
|
+
// The FLOAT column shows while a minimum is set; a float the source does not date says so once.
|
|
296
|
+
...partialFloatFooter(columns.some((column) => column.id === "floatShares"), rows),
|
|
288
297
|
],
|
|
289
|
-
}), [
|
|
298
|
+
}), [columns, feedStatus, listKey, loadedKey, loadError, loading, moversStale, rows, summaryQuotes]);
|
|
290
299
|
|
|
291
300
|
return (
|
|
292
301
|
<DataTableView<MarketMoverRow, MarketMoverColumn>
|
|
293
302
|
focused={focused}
|
|
303
|
+
rootBefore={floatFiltered ? (
|
|
304
|
+
<QueryBar width={Math.max(1, width - 2)} filters={[minFloatFilter(minFloatValue, setMinFloatValue)]} />
|
|
305
|
+
) : undefined}
|
|
294
306
|
selection={{
|
|
295
307
|
kind: "id",
|
|
296
308
|
selectedId: selectedSymbol,
|
|
@@ -298,7 +310,7 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
|
|
|
298
310
|
onChange: (symbol) => setSelectedSymbol(symbol),
|
|
299
311
|
}}
|
|
300
312
|
onRootKeyDown={handleTableKeyDown}
|
|
301
|
-
resetScrollKey={
|
|
313
|
+
resetScrollKey={listKey}
|
|
302
314
|
sortable
|
|
303
315
|
columns={columns}
|
|
304
316
|
items={rows}
|
|
@@ -309,7 +321,7 @@ function ScreenerMoversBody({ activeTab, focused, width, summaryQuotes, liveStre
|
|
|
309
321
|
onActivate={openSymbol}
|
|
310
322
|
renderCell={renderCell}
|
|
311
323
|
selectedTextOverridesCellColor
|
|
312
|
-
emptyStateTitle={loading ? "Loading movers..." : loadError ?? "No movers returned."}
|
|
324
|
+
emptyStateTitle={loading ? "Loading movers..." : loadError ?? (minFloat != null ? minFloatEmptyTitle(minFloatValue) : "No movers returned.")}
|
|
313
325
|
emptyContent={loadError ? (
|
|
314
326
|
<Box paddingX={1} paddingY={1}>
|
|
315
327
|
<EmptyState title={loadError} message="Try again in a moment." />
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { PaneFooterSegment } from "../../../components";
|
|
2
|
+
import type { QueryBarSelectFilter } from "../../../components/ui/query-bar";
|
|
3
|
+
import { formatCompact } from "../../../utils/format";
|
|
4
|
+
|
|
5
|
+
/** No minimum: every row, with or without a float. */
|
|
6
|
+
export const ANY_FLOAT = "any";
|
|
7
|
+
/** The minimums the pane's menu offers; `--min-float` takes any other. */
|
|
8
|
+
const PRESETS = ["1M", "5M", "10M", "20M", "50M", "100M"] as const;
|
|
9
|
+
const SCALE: Record<string, number> = { k: 1e3, m: 1e6, b: 1e9 };
|
|
10
|
+
|
|
11
|
+
/** Shares in `text` (10M, 500k, 2.5M, 25000000), or null when it is not a share count. */
|
|
12
|
+
function parseShares(text: string): number | null {
|
|
13
|
+
const match = /^((?:\d+(?:\.\d*)?|\.\d+))([kmb])?$/i.exec(text.trim().replace(/[_,]/g, ""));
|
|
14
|
+
if (!match) return null;
|
|
15
|
+
const value = Number(match[1]) * (match[2] ? SCALE[match[2].toLowerCase()]! : 1);
|
|
16
|
+
return Number.isFinite(value) ? value : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** The minimum a saved or typed value sets, in shares; null for none (`any`, 0 or a value that is not a count). */
|
|
20
|
+
export function minFloatShares(value: string | null | undefined): number | null {
|
|
21
|
+
if (!value || value.trim().toLowerCase() === ANY_FLOAT) return null;
|
|
22
|
+
const shares = parseShares(value);
|
|
23
|
+
return shares != null && shares > 0 ? shares : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** `--min-float` as the pane keeps it: `10M` for 10m or 10000000, `any` for none. Throws on a value that is not a share count. */
|
|
27
|
+
export function normalizeMinFloat(value: string): string {
|
|
28
|
+
if (value.trim().toLowerCase() === ANY_FLOAT) return ANY_FLOAT;
|
|
29
|
+
const shares = parseShares(value);
|
|
30
|
+
if (shares == null) throw new Error(`--min-float takes a share count such as 10M or 500k, or any; got "${value}".`);
|
|
31
|
+
return shares > 0 ? formatCompact(shares) : ANY_FLOAT;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A row passes with a float at or above the minimum; a row whose float is not known does not. */
|
|
35
|
+
export function meetsMinFloat(floatShares: number | null | undefined, minimum: number | null): boolean {
|
|
36
|
+
return minimum == null || (floatShares != null && floatShares >= minimum);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The pane's Min float menu: Any and the presets, with a minimum set from the CLI kept as its own choice. */
|
|
40
|
+
export function minFloatFilter(value: string, onChange: (value: string) => void): QueryBarSelectFilter {
|
|
41
|
+
const current = minFloatShares(value) == null ? ANY_FLOAT : value;
|
|
42
|
+
const presets: string[] = [...PRESETS];
|
|
43
|
+
const options = [ANY_FLOAT, ...(presets.includes(current) || current === ANY_FLOAT ? [] : [current]), ...presets]
|
|
44
|
+
.map((option) => ({ value: option, label: option === ANY_FLOAT ? "Any" : option }));
|
|
45
|
+
return {
|
|
46
|
+
id: "min-float",
|
|
47
|
+
label: "Min float",
|
|
48
|
+
title: "Minimum float",
|
|
49
|
+
value: current,
|
|
50
|
+
defaultValue: ANY_FLOAT,
|
|
51
|
+
options,
|
|
52
|
+
onChange,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** What a list says under its Float column while a shown float is partial. */
|
|
57
|
+
export const PARTIAL_FLOAT_NOTE = "Float is partial: the source does not say when it measured it.";
|
|
58
|
+
|
|
59
|
+
/** Whether any row's float is partial, which the list then says once. */
|
|
60
|
+
export function hasPartialFloat(rows: readonly { floatShares?: number | null; floatSharesState?: string }[]): boolean {
|
|
61
|
+
return rows.some((row) => row.floatShares != null && row.floatSharesState === "partial");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The footer's word for a FLOAT column of partial floats, or none. */
|
|
65
|
+
export function partialFloatFooter(shown: boolean, rows: readonly { floatShares?: number | null; floatSharesState?: string }[]): PaneFooterSegment[] {
|
|
66
|
+
return shown && hasPartialFloat(rows)
|
|
67
|
+
? [{ id: "float-partial", parts: [{ text: "FLOAT", tone: "label" }, { text: "partial, undated", tone: "muted" }] }]
|
|
68
|
+
: [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The empty list's title while a minimum hides every row. */
|
|
72
|
+
export function minFloatEmptyTitle(value: string): string {
|
|
73
|
+
return `No movers with a float of ${value} or more.`;
|
|
74
|
+
}
|
|
@@ -50,7 +50,8 @@ type MarketMoverColumnId =
|
|
|
50
50
|
| "volume"
|
|
51
51
|
| "volumeRatio"
|
|
52
52
|
| "range"
|
|
53
|
-
| "marketCap"
|
|
53
|
+
| "marketCap"
|
|
54
|
+
| "floatShares";
|
|
54
55
|
export type MarketMoverColumn = DataTableColumn & { id: MarketMoverColumnId };
|
|
55
56
|
/**
|
|
56
57
|
* A list row. Price and change are the regular session's, as `ticker` reads
|
|
@@ -123,6 +124,8 @@ function getSortValue(
|
|
|
123
124
|
return fiftyTwoWeekPositionPercent(row.price, row.fiftyTwoWeekLow, row.fiftyTwoWeekHigh);
|
|
124
125
|
case "marketCap":
|
|
125
126
|
return row.marketCap ?? null;
|
|
127
|
+
case "floatShares":
|
|
128
|
+
return row.floatShares ?? null;
|
|
126
129
|
}
|
|
127
130
|
}
|
|
128
131
|
|