gloomberb 0.18.4 → 0.19.0
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 +169 -5
- package/src/account/auth-model.ts +7 -3
- package/src/api-client/auth.ts +9 -4
- package/src/api-client/catalysts.ts +2 -1
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +31 -6
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/index.ts +171 -11
- package/src/api-client/live-scan.ts +91 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +13 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/request.ts +222 -41
- package/src/api-client/research-activity.ts +6 -12
- package/src/api-client/types.ts +74 -0
- package/src/app/keybindings/index.ts +3 -0
- package/src/app/keybindings/labels.ts +18 -1
- package/src/app/keybindings/resolve.ts +14 -0
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +42 -27
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/core-builtin-allowlist.json +1 -1
- 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/fred-series.ts +5 -0
- package/src/cli/commands/keybindings.ts +23 -0
- package/src/cli/commands/market.ts +138 -46
- package/src/cli/commands/options-chain.ts +107 -10
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +176 -39
- package/src/cli/commands/search.ts +39 -3
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +57 -35
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +10 -2
- package/src/cli/export-source.ts +29 -1
- package/src/cli/glossary.ts +73 -1
- package/src/cli/history-rows.ts +87 -16
- package/src/cli/index.ts +2 -0
- package/src/cli/known-gaps.ts +5 -2
- package/src/cli/listing-arg.ts +195 -56
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +3 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +111 -16
- package/src/cli/pane-functions/function-help.ts +23 -13
- package/src/cli/pane-functions/headless.ts +56 -12
- package/src/cli/pane-functions/index.ts +58 -16
- package/src/cli/pane-functions/market-session.ts +81 -9
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/report.ts +5 -1
- package/src/cli/pane-functions/resolver.ts +35 -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 +30 -13
- package/src/cli/report-tables.ts +18 -7
- package/src/cli/restore-plugins.ts +13 -1
- package/src/cli/result.ts +57 -5
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +26 -4
- package/src/components/chart/composite/format.ts +27 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/levels.ts +2 -0
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +61 -5
- package/src/components/chart/composite/range-statistics.ts +62 -0
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +46 -7
- package/src/components/chart/composite/series-axis-tags.tsx +16 -9
- 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/types.ts +4 -0
- 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/pane-templates/items.ts +3 -2
- package/src/components/command-bar/panel/prompt-binding.ts +0 -8
- package/src/components/command-bar/workflow/alert.ts +1 -1
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/header.tsx +26 -40
- package/src/components/layout/pane/footer/index.tsx +1 -0
- package/src/components/layout/pane/footer/model.ts +27 -0
- package/src/components/onboarding/onboarding-frame.tsx +1 -1
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
- 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 +4 -1
- 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 +5 -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/core/state/app/layout.ts +2 -0
- package/src/core/state/app/state.ts +9 -0
- package/src/core/state/app/types.ts +3 -0
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/saved-copies.ts +55 -16
- 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 +427 -216
- package/src/i18n/es.ts +19 -3
- package/src/i18n/ja.ts +19 -3
- package/src/i18n/ko.ts +19 -3
- package/src/i18n/zh-cn.ts +19 -3
- package/src/i18n/zh-tw.ts +19 -3
- package/src/market-data/coordinator/financials.ts +10 -2
- package/src/market-data/coordinator/index.ts +3 -1
- package/src/market-data/coordinator/quotes.ts +4 -2
- package/src/market-data/depositary-pair.ts +2 -2
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +48 -7
- package/src/market-data/market/dated-previous-close.ts +203 -0
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/market/futures-week.ts +68 -0
- package/src/market-data/market/status.ts +16 -4
- package/src/market-data/options-alternatives.ts +141 -4
- package/src/market-data/quotes/contributions.ts +20 -2
- package/src/market-data/quotes/resolution.ts +3 -0
- package/src/market-data/selectors.ts +3 -0
- package/src/news/aggregator.ts +113 -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 +11 -0
- package/src/notifications/notification-log.ts +6 -4
- package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +380 -82
- package/src/plugins/builtin/alerts/command.ts +31 -0
- package/src/plugins/builtin/alerts/constants.ts +1 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
- package/src/plugins/builtin/alerts/fired-log.ts +113 -8
- package/src/plugins/builtin/alerts/format.ts +80 -4
- package/src/plugins/builtin/alerts/headless.ts +30 -7
- package/src/plugins/builtin/alerts/history-rows.ts +7 -3
- package/src/plugins/builtin/alerts/history.ts +2 -0
- package/src/plugins/builtin/alerts/index.tsx +135 -22
- package/src/plugins/builtin/alerts/live.ts +3 -3
- package/src/plugins/builtin/alerts/metric-command.ts +134 -0
- package/src/plugins/builtin/alerts/metric-data.ts +111 -0
- package/src/plugins/builtin/alerts/metric-model.ts +324 -0
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
- package/src/plugins/builtin/alerts/pane.tsx +90 -39
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +37 -10
- package/src/plugins/builtin/alerts/types.ts +7 -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/backtest/headless.ts +10 -5
- package/src/plugins/builtin/backtest/pane.tsx +9 -4
- package/src/plugins/builtin/backtest/presets.ts +29 -3
- package/src/plugins/builtin/broker-manager/index.tsx +10 -5
- package/src/plugins/builtin/broker-manager/model.ts +6 -1
- package/src/plugins/builtin/broker-trading/controller.ts +22 -12
- package/src/plugins/builtin/broker-trading/index.tsx +13 -1
- package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
- package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
- 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 +7 -3
- package/src/plugins/builtin/catalysts/model.ts +2 -1
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
- package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
- package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
- package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
- package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
- package/src/plugins/builtin/chart-composer/formula.ts +71 -18
- package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
- package/src/plugins/builtin/chart-composer/headless.ts +19 -3
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
- package/src/plugins/builtin/chart-composer/presets.ts +50 -15
- package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
- package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
- package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
- package/src/plugins/builtin/chat/content/composer.tsx +13 -6
- package/src/plugins/builtin/chat/content/footer.tsx +6 -3
- package/src/plugins/builtin/chat/content/index.tsx +50 -21
- package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
- package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
- package/src/plugins/builtin/chat/index.tsx +23 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
- package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
- package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
- package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
- package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
- package/src/plugins/builtin/clinical-trials/model.ts +0 -9
- package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- package/src/plugins/builtin/cloud/plugin.tsx +1 -1
- 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/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +9 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -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 +7 -7
- 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/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/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
- package/src/plugins/builtin/econ/calendar-model.ts +14 -9
- package/src/plugins/builtin/econ/figures.ts +72 -0
- package/src/plugins/builtin/econ/index.tsx +33 -3
- package/src/plugins/builtin/econ/types.ts +4 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
- package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
- package/src/plugins/builtin/econ-statistics/view.ts +3 -0
- 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 +70 -15
- package/src/plugins/builtin/exposure/charts.tsx +2 -2
- package/src/plugins/builtin/exposure/model.ts +13 -1
- package/src/plugins/builtin/exposure/pane.tsx +4 -4
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/index.tsx +1 -0
- package/src/plugins/builtin/futures/model.ts +2 -1
- package/src/plugins/builtin/futures/table.tsx +2 -2
- 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 +11 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
- package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
- package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -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 +1 -0
- package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +18 -1
- 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 +36 -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/pane.tsx +7 -5
- 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 +25 -7
- package/src/plugins/builtin/market-movers/footer.ts +2 -2
- package/src/plugins/builtin/market-movers/headless.ts +32 -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 +14 -0
- 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 +82 -0
- package/src/plugins/builtin/members/client.ts +19 -4
- package/src/plugins/builtin/members/headless.ts +20 -7
- package/src/plugins/builtin/members/index.tsx +9 -9
- package/src/plugins/builtin/members/model.ts +23 -4
- package/src/plugins/builtin/members/pane.tsx +25 -15
- package/src/plugins/builtin/news/index.tsx +1 -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 +54 -6
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/model-spot.ts +5 -2
- package/src/plugins/builtin/options/strike-window.ts +2 -1
- package/src/plugins/builtin/options/table.ts +6 -9
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +42 -12
- package/src/plugins/builtin/options-positioning/client.ts +30 -0
- package/src/plugins/builtin/options-positioning/headless.ts +74 -21
- package/src/plugins/builtin/options-positioning/index.tsx +4 -4
- package/src/plugins/builtin/options-positioning/model.ts +122 -14
- package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
- 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/freshness.ts +12 -10
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/live-rows.ts +10 -3
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +32 -31
- 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 +12 -7
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
- 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/resolution.ts +3 -1
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/rate-path/pane.tsx +2 -2
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- 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 +10 -8
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +11 -9
- 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/scanner/feed.ts +2 -2
- 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 +40 -6
- package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
- package/src/plugins/builtin/scanner/live-feed.ts +168 -0
- package/src/plugins/builtin/scanner/live-headless.ts +138 -0
- package/src/plugins/builtin/scanner/live-model.ts +362 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
- package/src/plugins/builtin/scanner/live-track.ts +186 -0
- 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 +2 -1
- package/src/plugins/builtin/series-inventory.ts +3 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
- 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/job-posting.ts +1 -0
- package/src/plugins/builtin/shared/macro-releases.ts +65 -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 +3 -2
- package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
- 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 +10 -3
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/headless.ts +6 -4
- package/src/plugins/builtin/themes/model.ts +17 -8
- package/src/plugins/builtin/themes/pane.tsx +7 -7
- 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 +1 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +6 -1
- 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-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/charts.tsx +9 -4
- package/src/plugins/builtin/vol-surface/client.ts +9 -17
- package/src/plugins/builtin/vol-surface/headless.ts +63 -14
- package/src/plugins/builtin/vol-surface/index.tsx +2 -2
- package/src/plugins/builtin/vol-surface/model.ts +22 -7
- package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
- package/src/plugins/builtin/vol-surface/tables.ts +31 -3
- package/src/plugins/builtin/volatility/dvol.ts +46 -0
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +4 -4
- package/src/plugins/builtin/volatility/series.ts +26 -0
- package/src/plugins/builtin/world-indices/footer.ts +1 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/catalog-browser.ts +13 -8
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +5 -5
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
- package/src/remote/resources.ts +2 -1
- package/src/renderers/browser/app-services.ts +1 -7
- package/src/renderers/browser/config-host.ts +2 -55
- package/src/renderers/browser/main.tsx +7 -57
- package/src/renderers/dom/data-table/row.tsx +26 -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 +222 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
- package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
- package/src/renderers/electrobun/bun/index.ts +25 -1
- package/src/renderers/electrobun/shared/app-name.ts +30 -0
- package/src/renderers/electrobun/shared/protocol.ts +10 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +3 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- package/src/renderers/opentui/toast-host.tsx +71 -43
- 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/index.ts +1 -0
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
- package/src/sources/provider-router/batches.ts +10 -2
- package/src/sources/provider-router/financial-routes.ts +3 -3
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/history.ts +28 -7
- package/src/sources/provider-router/index.ts +149 -10
- package/src/sources/provider-router/search.ts +13 -2
- package/src/state/app/context/index.tsx +3 -0
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/auth-transition.ts +19 -9
- package/src/sync/baseline.ts +10 -4
- package/src/sync/controller.ts +70 -19
- package/src/sync/core-contributors.ts +90 -31
- package/src/sync/react.ts +4 -1
- package/src/sync/types.ts +3 -0
- 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/telemetry/usage-counts.ts +0 -18
- package/src/test-support/preload.ts +14 -0
- package/src/theme/colors.ts +8 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name.ts +103 -3
- package/src/tickers/listing-venues.ts +38 -3
- package/src/tickers/search/bare-symbol.ts +36 -0
- package/src/tickers/search/index.ts +36 -7
- package/src/tickers/search/ranking.ts +10 -1
- package/src/tickers/search/types.ts +5 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/base-series.ts +13 -5
- package/src/time-series/daily-valuation.ts +62 -0
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +2 -4
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/forward-valuation.ts +32 -0
- package/src/time-series/fundamentals.ts +198 -7
- package/src/time-series/hooks.ts +1 -0
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/price-units.ts +72 -2
- package/src/time-series/resolve-context.ts +7 -1
- package/src/time-series/resolve.ts +116 -15
- package/src/time-series/series-presentation.ts +40 -6
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +5 -3
- package/src/time-series/studies.ts +278 -74
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +3 -3
- package/src/time-series/types.ts +27 -1
- package/src/time-series/valuation-releases.ts +39 -0
- package/src/types/data-provider.ts +9 -1
- package/src/types/desktop-window.ts +2 -0
- package/src/types/financials.ts +73 -2
- package/src/types/headless.ts +61 -5
- package/src/types/instrument.ts +3 -0
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +10 -1
- package/src/ui/host.tsx +3 -0
- package/src/updater/command-names.ts +12 -7
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +44 -0
- package/src/utils/financial-statements.ts +6 -1
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +24 -3
- 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/network-failure.ts +9 -6
- package/src/utils/option-expiry.ts +48 -1
- package/src/utils/options.ts +10 -0
- package/src/utils/plugin-proxy-hosts.json +1 -0
- package/src/utils/price-history.ts +12 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
- package/src/renderers/browser/first-screen.ts +0 -193
|
@@ -54,8 +54,8 @@ export function epsBasisShort(basis: EarningsEpsBasis | null): string {
|
|
|
54
54
|
return basis === "gaap" ? "GAAP" : basis === "adjusted" ? "adj" : "?";
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
/**
|
|
58
|
-
const BASIS_TEXT: Record<EpsBasisName, string> = { GAAP: "GAAP", adjusted: "adj", unknown: "" };
|
|
57
|
+
/** A reported row's detail names its basis as ERN's Basis column does, unknown included. */
|
|
58
|
+
const BASIS_TEXT: Record<EpsBasisName, string> = { GAAP: "GAAP", adjusted: "adj", unknown: "unknown" };
|
|
59
59
|
|
|
60
60
|
type EarningsRecord = CorporateActionsData["earnings"][number];
|
|
61
61
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Macro releases people look for by a name no data series carries: the
|
|
3
|
+
* purchasing managers' indexes and the GDP nowcasts. They are releases in the
|
|
4
|
+
* economic calendar (ECO, `gloom econ`), with actual, forecast and prior, that
|
|
5
|
+
* `alerts add` follows; GDPNow's history is also ECST's. `catalog` and
|
|
6
|
+
* `search` answer a query that names one with `line`, and the data catalog's
|
|
7
|
+
* empty search with `paneHint`, rather than with each other.
|
|
8
|
+
*/
|
|
9
|
+
interface MacroReleaseTopic {
|
|
10
|
+
/** Runs of words that name it anywhere in a query ("pmi" in "ISM manufacturing PMI"). */
|
|
11
|
+
phrases: readonly string[];
|
|
12
|
+
/** Where it is, by the commands that list it and alert on it. */
|
|
13
|
+
line: string;
|
|
14
|
+
/** The same, by the functions the command bar opens. */
|
|
15
|
+
paneHint: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const MACRO_RELEASE_TOPICS: readonly MacroReleaseTopic[] = [
|
|
19
|
+
{
|
|
20
|
+
phrases: ["pmi", "pmis", "ism", "purchasing manager", "purchasing managers", "purchasing managers index", "purchasing managers indexes", "caixin", "ratingdog"],
|
|
21
|
+
line: "PMIs (ISM, S&P Global flash and final, NBS, RatingDog, formerly Caixin) are releases in the economic calendar: gloom econ, "
|
|
22
|
+
+ "or fn ECO --impact all (ECO opens on high impact only). An alert before one: gloom alerts add \"ISM Manufacturing PMI\". "
|
|
23
|
+
+ "No PMI series is charted; ECST has industrial production, durables and factory orders.",
|
|
24
|
+
paneHint: "PMIs are releases in the economic calendar: ECO, Impact All. Industrial production and orders: ECST.",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
phrases: ["nowcast", "nowcasts", "nowcasting", "gdpnow", "gdp now"],
|
|
28
|
+
line: "GDP nowcast: the Atlanta Fed's GDPNow, fn ECST gdpnow (history: gloom fred GDPNOW), the one nowcast series served. "
|
|
29
|
+
+ "Each update is a release in gloom econ --country US and fn ECO US --impact all; an alert before the next: "
|
|
30
|
+
+ "gloom alerts add \"Atlanta Fed GDPNow\".",
|
|
31
|
+
paneHint: "GDPNow is in ECST, Nowcasts & conditions, and each update in ECO, Impact All.",
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/** The words of a text, lower case: "ISM-manufacturing" and "ism manufacturing" read alike. */
|
|
36
|
+
function words(text: string): string[] {
|
|
37
|
+
return text.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function containsRun(query: readonly string[], run: readonly string[]): boolean {
|
|
41
|
+
for (let at = 0; at + run.length <= query.length; at += 1) {
|
|
42
|
+
if (run.every((word, index) => query[at + index] === word)) return true;
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The macro release a query names ("flash PMI", "ISM services", "GDPNow"), else null. */
|
|
48
|
+
export function macroReleaseTopic(query: string): Pick<MacroReleaseTopic, "line" | "paneHint"> | null {
|
|
49
|
+
const asked = words(query);
|
|
50
|
+
if (asked.length === 0) return null;
|
|
51
|
+
return MACRO_RELEASE_TOPICS.find((topic) => topic.phrases.some((phrase) => containsRun(asked, words(phrase)))) ?? null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Release series a sponsor renamed, by the name's old words and its new: the
|
|
56
|
+
* S&P Global China PMI was Caixin's until RatingDog's from the August 2025
|
|
57
|
+
* survey, and IHS Markit's PMIs are S&P Global's since 2022. A name typed
|
|
58
|
+
* with the old words (`alerts add "Caixin PMI"`) reads as the series' current
|
|
59
|
+
* name, and an alert kept under the old name follows the rows under the new.
|
|
60
|
+
* Only renames of series the calendar lists.
|
|
61
|
+
*/
|
|
62
|
+
export const RENAMED_RELEASES: ReadonlyArray<{ former: readonly string[]; current: string; since: string }> = [
|
|
63
|
+
{ former: ["Caixin"], current: "RatingDog", since: "the August 2025 survey" },
|
|
64
|
+
{ former: ["IHS Markit", "Markit"], current: "S&P Global", since: "2022" },
|
|
65
|
+
];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
1
2
|
import { ChoiceDialog, TextPromptDialog, usePaneMenuItems } from "../../../components";
|
|
2
3
|
import { createFormCollectionActions } from "../../../components/form-modal/deps";
|
|
3
4
|
import { t, tf } from "../../../i18n";
|
|
@@ -195,7 +196,7 @@ export function useMembersMenu(registrationId: string, list: MemberList | null)
|
|
|
195
196
|
const choice = choices.find((item) => item.id === templateId && !item.disabled);
|
|
196
197
|
// CORR and RIPL resolve `arg` like the command bar; RRG and SIW read `symbols`.
|
|
197
198
|
if (choice) createPaneFromTemplate(choice.id, { symbols: choice.symbols, arg: formatTickerListInput(choice.symbols) });
|
|
198
|
-
}).catch((
|
|
199
|
+
}).catch(logFailure("shared.members-menu", "Open members in a function failed"));
|
|
199
200
|
};
|
|
200
201
|
const saveAsWatchlist = async (requested: string) => {
|
|
201
202
|
// The writes hold the app for about a second with a hundred names; let the dialog close on screen first.
|
|
@@ -253,7 +254,7 @@ export function useMembersMenu(registrationId: string, list: MemberList | null)
|
|
|
253
254
|
saveAsWatchlist(name).catch((error: unknown) => {
|
|
254
255
|
notify({ type: "error", body: error instanceof Error ? error.message : t("The watchlist was not saved.") });
|
|
255
256
|
});
|
|
256
|
-
}).catch((
|
|
257
|
+
}).catch(logFailure("shared.members-menu", "Open save members as watchlist prompt failed"));
|
|
257
258
|
} },
|
|
258
259
|
];
|
|
259
260
|
}, [createPaneFromTemplate, dialog, disabledPlugins, dispatch, getState, list, notify]);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createTtlCache, type TtlCache } from "../../../data/ttl-cache";
|
|
1
2
|
import type { HeadlessPaneContext } from "../../../types/headless";
|
|
2
3
|
import type { PaneLock } from "../../../types/plugin";
|
|
3
4
|
import { settleWithin, signalWithTimeout } from "../../../utils/async-deadline";
|
|
@@ -35,8 +36,7 @@ const ANSWER_TTL_MS = 5 * 60_000;
|
|
|
35
36
|
*/
|
|
36
37
|
const ROUTE_CHECK_WAIT_MS = 3_000;
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
const answers = new WeakMap<FreeRoute, Map<string, KeptAnswer>>();
|
|
39
|
+
const answers = new WeakMap<FreeRoute, TtlCache<boolean>>();
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* Whether the route has data for the symbol, null when its check failed or was
|
|
@@ -45,20 +45,20 @@ const answers = new WeakMap<FreeRoute, Map<string, KeptAnswer>>();
|
|
|
45
45
|
* rather than hiding the route for minutes over one slow read.
|
|
46
46
|
*/
|
|
47
47
|
async function routeAnswers(route: FreeRoute, check: NonNullable<FreeRoute["answers"]>, symbol: string, context: HeadlessPaneContext, deadline: number): Promise<boolean | null> {
|
|
48
|
-
const bySymbol = answers.get(route) ??
|
|
48
|
+
const bySymbol = answers.get(route) ?? createTtlCache<boolean>({ ttlMs: ANSWER_TTL_MS });
|
|
49
49
|
answers.set(route, bySymbol);
|
|
50
50
|
const key = symbol.trim().toUpperCase();
|
|
51
|
-
|
|
52
|
-
if (!kept || Date.now() - kept.at >= ANSWER_TTL_MS) {
|
|
51
|
+
const kept = bySymbol.load(key, () => {
|
|
53
52
|
// The check's own reads stop at the deadline where they take a signal.
|
|
54
53
|
const signal = signalWithTimeout(context.signal, Math.max(0, deadline - Date.now()));
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
const answer = await settleWithin(kept
|
|
61
|
-
|
|
54
|
+
return check(symbol, { ...context, signal }).catch((error: unknown) => {
|
|
55
|
+
logFailure("plugins.pro-lock-note", "Check a free route's data failed")(error);
|
|
56
|
+
throw error;
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
const answer = await settleWithin(kept, Math.max(0, deadline - Date.now()));
|
|
60
|
+
// Only this check is dropped: a newer one for the symbol stays.
|
|
61
|
+
if (answer === null) bySymbol.delete(key, kept);
|
|
62
62
|
return answer;
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
1
2
|
import type { HeadlessPaneFreshness } from "../../../types/headless";
|
|
2
3
|
import type { Quote, QuoteIndexSession } from "../../../types/financials";
|
|
3
4
|
import { isQuoteBeforeLatestSession, isQuoteStaleForCurrentSession } from "../../../market-data/quotes/freshness";
|
|
@@ -129,8 +130,8 @@ export async function cloudRealtimeAccess(): Promise<boolean | null> {
|
|
|
129
130
|
if (!apiClient.isSignedIn()) return false;
|
|
130
131
|
try {
|
|
131
132
|
return hasProAccess(apiClient.getCurrentUser() ?? await apiClient.getSession());
|
|
132
|
-
} catch {
|
|
133
|
-
return null;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return logFailure("shared.report-freshness", "Read Cloud plan for report freshness failed", null)(error);
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
|
|
@@ -7,6 +7,9 @@ import type { apiClient, ScannerFeedEvent, ScannerKind } from "../../../api-clie
|
|
|
7
7
|
|
|
8
8
|
export const HILO_PANE_ID = "scanner-hilo";
|
|
9
9
|
export const FLOW_PANE_ID = "scanner-flow";
|
|
10
|
+
/** SCAN, the user's own live scan; it polls Gloom Cloud rather than reading a shared feed. */
|
|
11
|
+
export const LIVE_SCAN_PANE_ID = "scanner-live";
|
|
12
|
+
export const LIVE_SCAN_TEMPLATE_ID = "scanner-live-pane";
|
|
10
13
|
/** The panes that read a scanner feed, by the scanner each one reads. */
|
|
11
14
|
export const SCANNER_PANE_KINDS: Readonly<Record<string, ScannerKind>> = { [HILO_PANE_ID]: "hilo", [FLOW_PANE_ID]: "flow" };
|
|
12
15
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
1
2
|
import { useCallback } from "react";
|
|
2
3
|
import { ChoiceDialog } from "../../../components/ui/choice-dialog";
|
|
3
4
|
import { t } from "../../../i18n";
|
|
@@ -29,6 +30,6 @@ export function useOpenTickerChoice(): (symbols: readonly string[]) => void {
|
|
|
29
30
|
),
|
|
30
31
|
}).then((symbol) => {
|
|
31
32
|
if (symbol) openTicker(symbol);
|
|
32
|
-
}).catch((
|
|
33
|
+
}).catch(logFailure("shared.ticker-choice", "Open ticker choice failed"));
|
|
33
34
|
}, [dialog, openTicker]);
|
|
34
35
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { isNoSourceAnswer } from "../../../sources/provider-errors";
|
|
3
|
+
import type { DataProvider } from "../../../types/data-provider";
|
|
4
|
+
import { shortCompanyName } from "../../../tickers/company-name";
|
|
5
|
+
import { classifyInstrumentKind } from "../../../tickers/search/ranking";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A search typed as a ticker (`TRIAL VKTX`, `FDA AZN`): one to five letters,
|
|
9
|
+
* with a share class. Registries know companies by name, so a symbol no record
|
|
10
|
+
* mentions is looked up as its company.
|
|
11
|
+
*/
|
|
12
|
+
export function looksLikeTicker(term: string): boolean {
|
|
13
|
+
return /^[A-Za-z]{1,5}(?:[.-][A-Za-z])?$/.test(term.trim());
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The company a ticker is, by the name a registry files its records under:
|
|
18
|
+
* VKTX is "Viking Therapeutics", AZN "AstraZeneca". Read from the listing's
|
|
19
|
+
* quote, which names it; a fund or a contract is no company. Null when no
|
|
20
|
+
* share trades under it. A quote that could not be read (unreachable, busy,
|
|
21
|
+
* timed out) rejects: it says nothing about the company.
|
|
22
|
+
*/
|
|
23
|
+
export async function tickerCompanyName(dataProvider: Pick<DataProvider, "getQuote"> | null, ticker: string): Promise<string | null> {
|
|
24
|
+
const symbol = ticker.trim().toUpperCase();
|
|
25
|
+
if (!dataProvider || !symbol) return null;
|
|
26
|
+
const quote = await dataProvider.getQuote(symbol).catch((error: unknown) => {
|
|
27
|
+
if (isNoSourceAnswer(error)) return null;
|
|
28
|
+
throw error;
|
|
29
|
+
});
|
|
30
|
+
const name = quote?.name?.trim();
|
|
31
|
+
const kind = classifyInstrumentKind(quote?.instrumentType);
|
|
32
|
+
return name && kind !== "fund" && kind !== "derivative" ? shortCompanyName(name) : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface TickerCompanySearch {
|
|
36
|
+
/** The company is being looked up: the pane shows it loading. */
|
|
37
|
+
resolving: boolean;
|
|
38
|
+
/** The lookup failed, for the pane's unavailable state; null otherwise. */
|
|
39
|
+
error: string | null;
|
|
40
|
+
/** Looks again after a failure: the pane's refresh calls it. */
|
|
41
|
+
retry: () => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A search for a ticker that found nothing is searched again as its company's
|
|
46
|
+
* name: `onCompany` runs with it, once per `key` (the term as the pane compares
|
|
47
|
+
* it). `eligible` is true once the search has finished empty and the term reads
|
|
48
|
+
* as a ticker. A lookup that failed is no answer: it reports `error` until
|
|
49
|
+
* `retry`, or until the term changes and comes back.
|
|
50
|
+
*/
|
|
51
|
+
export function useTickerCompanySearch(
|
|
52
|
+
dataProvider: Pick<DataProvider, "getQuote"> | null,
|
|
53
|
+
term: string,
|
|
54
|
+
{ eligible, key, onCompany }: { eligible: boolean; key: string; onCompany: (name: string) => void },
|
|
55
|
+
): TickerCompanySearch {
|
|
56
|
+
const [looked, setLooked] = useState<ReadonlySet<string>>(() => new Set());
|
|
57
|
+
const [failed, setFailed] = useState<string | null>(null);
|
|
58
|
+
// A failure belongs to its term: another search clears it, so coming back looks again.
|
|
59
|
+
if (failed !== null && failed !== key) setFailed(null);
|
|
60
|
+
const onCompanyRef = useRef(onCompany);
|
|
61
|
+
onCompanyRef.current = onCompany;
|
|
62
|
+
const resolving = eligible && !looked.has(key) && failed !== key;
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!resolving) return;
|
|
65
|
+
let live = true;
|
|
66
|
+
tickerCompanyName(dataProvider, term).then((name) => {
|
|
67
|
+
if (!live) return;
|
|
68
|
+
setLooked((current) => new Set([...current, key]));
|
|
69
|
+
if (name) onCompanyRef.current(name);
|
|
70
|
+
}, () => {
|
|
71
|
+
if (live) setFailed(key);
|
|
72
|
+
});
|
|
73
|
+
return () => { live = false; };
|
|
74
|
+
}, [dataProvider, key, resolving, term]);
|
|
75
|
+
const retry = useCallback(() => setFailed(null), []);
|
|
76
|
+
return { resolving, error: eligible && failed === key ? `Couldn't look up ${term}'s company.` : null, retry };
|
|
77
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
1
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
3
|
import type { DataTableCell, DataTableVisibleRange, PaneFooterSegment } from "../../../components";
|
|
4
|
+
import { savedCopyFooterSegment } from "../../../components/layout/pane/footer/model";
|
|
5
|
+
import { oldestSavedCopyAt } from "../../../data/saved-copies";
|
|
3
6
|
import { useQuoteEntries } from "../../../market-data/hooks";
|
|
4
7
|
import { getRegularSessionDisplay, quoteDotMarketState, quoteHeadlineTime, quoteSessionColor, quoteSessionLabel } from "../../../market-data/market/status";
|
|
5
8
|
import type { InstrumentRef } from "../../../market-data/request-types";
|
|
@@ -339,7 +342,7 @@ export function useQuoteBoard(symbols: string[], options: QuoteBoardOptions = {}
|
|
|
339
342
|
if (fetchGenRef.current !== gen) return;
|
|
340
343
|
setSnapshots((prev) => paintCachedQuotes(prev, cached));
|
|
341
344
|
})
|
|
342
|
-
.catch((
|
|
345
|
+
.catch(logFailure("shared.use-quote-board", "Paint cached board quotes failed"));
|
|
343
346
|
}, [dataProvider]);
|
|
344
347
|
|
|
345
348
|
/** The cache-first full load: the listed symbols' cached quotes at once, then from the network. */
|
|
@@ -499,6 +502,8 @@ export interface QuoteBoardStatus {
|
|
|
499
502
|
/** Symbols with no quote to show at all. */
|
|
500
503
|
unavailable: number;
|
|
501
504
|
latestTs: number;
|
|
505
|
+
/** When the oldest quote shown from a saved copy, after its refresh failed, was saved; null when none is. */
|
|
506
|
+
savedAt: number | null;
|
|
502
507
|
}
|
|
503
508
|
|
|
504
509
|
export function quoteBoardStatus(quotes: BoardQuoteMap): QuoteBoardStatus {
|
|
@@ -512,7 +517,7 @@ export function quoteBoardStatus(quotes: BoardQuoteMap): QuoteBoardStatus {
|
|
|
512
517
|
else if (!state.quote && !state.loading) unavailable += 1;
|
|
513
518
|
latestTs = Math.max(latestTs, state.quote?.lastUpdated ?? 0);
|
|
514
519
|
}
|
|
515
|
-
return { loading, stale, unavailable, latestTs };
|
|
520
|
+
return { loading, stale, unavailable, latestTs, savedAt: oldestSavedCopyAt(Array.from(quotes.values(), (state) => state.quote)) };
|
|
516
521
|
}
|
|
517
522
|
|
|
518
523
|
/** Board footer status: everything here changes as loads succeed or fail. */
|
|
@@ -525,7 +530,9 @@ export function quoteBoardFooterInfo(status: QuoteBoardStatus): PaneFooterSegmen
|
|
|
525
530
|
if (status.unavailable > 0) {
|
|
526
531
|
info.push({ id: "error", parts: [{ text: `${status.unavailable} unavailable`, tone: "warning" }] });
|
|
527
532
|
}
|
|
528
|
-
if (status.
|
|
533
|
+
if (status.savedAt != null) {
|
|
534
|
+
info.push(savedCopyFooterSegment("saved-copy", status.savedAt));
|
|
535
|
+
} else if (status.latestTs > 0) {
|
|
529
536
|
info.push({
|
|
530
537
|
id: "fresh",
|
|
531
538
|
quoteStatus: true,
|
|
@@ -112,6 +112,21 @@ export function useOptionsSessionOpen(enabled = true): boolean {
|
|
|
112
112
|
return state.nextChangeAt != null && Date.now() >= state.nextChangeAt ? usOptionsSession(Date.now()).open : state.open;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
export interface LiveSessionRefresh {
|
|
116
|
+
/** The refresh cycle is running, which is what "live" means. */
|
|
117
|
+
live: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Why the last refresh failed, until one succeeds or the cycle stops. The
|
|
120
|
+
* data it would have replaced stays on screen, so the pane shows this where
|
|
121
|
+
* it shows its own failed refreshes.
|
|
122
|
+
*/
|
|
123
|
+
error: string | null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isCancellation(error: unknown): boolean {
|
|
127
|
+
return !!error && typeof error === "object" && (error as { name?: unknown }).name === "AbortError";
|
|
128
|
+
}
|
|
129
|
+
|
|
115
130
|
/**
|
|
116
131
|
* Calls `refresh` every `intervalMs` while enabled, the pane can be seen (the
|
|
117
132
|
* app is visible and the pane is not covered) and the options market is open.
|
|
@@ -119,18 +134,19 @@ export function useOptionsSessionOpen(enabled = true): boolean {
|
|
|
119
134
|
* after the caller's own load (`loadedAt`); returning after a hidden or
|
|
120
135
|
* covered stretch, or reaching the open, more than an interval after the last
|
|
121
136
|
* data fires straight away. A refresh still in flight is never overlapped by
|
|
122
|
-
* the next.
|
|
123
|
-
*
|
|
137
|
+
* the next. A refresh that throws or rejects is reported as `error` until the
|
|
138
|
+
* next one succeeds; a cancelled one (an AbortError) is no failure.
|
|
124
139
|
*/
|
|
125
140
|
export function useLiveSessionRefresh(
|
|
126
141
|
refresh: () => void | Promise<unknown>,
|
|
127
142
|
intervalMs: number,
|
|
128
143
|
enabled: boolean,
|
|
129
144
|
loadedAt?: number | null,
|
|
130
|
-
):
|
|
145
|
+
): LiveSessionRefresh {
|
|
131
146
|
const visible = usePaneVisible();
|
|
132
147
|
const open = useOptionsSessionOpen(enabled);
|
|
133
148
|
const active = enabled && visible && open;
|
|
149
|
+
const [error, setError] = useState<string | null>(null);
|
|
134
150
|
const refreshRef = useRef(refresh);
|
|
135
151
|
refreshRef.current = refresh;
|
|
136
152
|
const lastRef = useRef(Date.now());
|
|
@@ -142,21 +158,31 @@ export function useLiveSessionRefresh(
|
|
|
142
158
|
let cancelled = false;
|
|
143
159
|
// A load the caller finished itself counts as the latest refresh.
|
|
144
160
|
if (loadedAtRef.current != null && loadedAtRef.current > lastRef.current) lastRef.current = loadedAtRef.current;
|
|
161
|
+
const succeeded = () => { if (!cancelled) setError(null); };
|
|
162
|
+
const failed = (failure: unknown) => {
|
|
163
|
+
if (cancelled || isCancellation(failure)) return;
|
|
164
|
+
const message = (failure instanceof Error ? failure.message : String(failure ?? "")).trim();
|
|
165
|
+
setError(message || "Live refresh failed");
|
|
166
|
+
};
|
|
145
167
|
const run = () => {
|
|
146
168
|
if (inFlight || cancelled) return;
|
|
147
169
|
lastRef.current = Date.now();
|
|
148
170
|
let pending: void | Promise<unknown>;
|
|
149
|
-
try { pending = refreshRef.current(); } catch {
|
|
171
|
+
try { pending = refreshRef.current(); } catch (failure) {
|
|
172
|
+
failed(failure);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
150
175
|
if (pending && typeof (pending as Promise<unknown>).then === "function") {
|
|
151
176
|
inFlight = true;
|
|
152
|
-
void (pending as Promise<unknown>).
|
|
153
|
-
}
|
|
177
|
+
void (pending as Promise<unknown>).then(succeeded, failed).finally(() => { inFlight = false; });
|
|
178
|
+
} else succeeded();
|
|
154
179
|
};
|
|
155
180
|
if (Date.now() - lastRef.current >= intervalMs) run();
|
|
156
181
|
const timer = setInterval(run, intervalMs);
|
|
157
|
-
|
|
182
|
+
// A stopped cycle's failure is old news: the pane's own loads take over.
|
|
183
|
+
return () => { cancelled = true; clearInterval(timer); setError(null); };
|
|
158
184
|
}, [active, intervalMs]);
|
|
159
|
-
return active;
|
|
185
|
+
return { live: active, error: active ? error : null };
|
|
160
186
|
}
|
|
161
187
|
|
|
162
188
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isSettlementStamp } from "../../../../utils/option-expiry";
|
|
1
2
|
import { zonedWallClockToUtcMs } from "../../../../utils/zoned-date-time";
|
|
2
3
|
|
|
3
4
|
const DAY_MS = 86_400_000;
|
|
@@ -133,6 +134,25 @@ export function valueOption(input: OptionPricingInput): OptionValuation {
|
|
|
133
134
|
};
|
|
134
135
|
}
|
|
135
136
|
|
|
137
|
+
/**
|
|
138
|
+
* The chance the underlying closes above `strike` at expiry under the same
|
|
139
|
+
* Black-Scholes-Merton model: N(d2), the risk-neutral probability a call there
|
|
140
|
+
* finishes in the money. 1 or 0 when nothing is left uncertain (expiry, zero
|
|
141
|
+
* volatility), by where the discounted forward sits.
|
|
142
|
+
*/
|
|
143
|
+
export function probabilityAbove(input: Omit<OptionPricingInput, "side">): number {
|
|
144
|
+
const spot = Math.max(0, finite(input.spot));
|
|
145
|
+
const strike = Math.max(0, finite(input.strike));
|
|
146
|
+
const years = Math.max(0, finite(input.daysToExpiry)) / DAYS_PER_YEAR;
|
|
147
|
+
const rate = finite(input.rate);
|
|
148
|
+
const dividendYield = finite(input.dividendYield);
|
|
149
|
+
const volatility = Math.max(0, finite(input.volatility));
|
|
150
|
+
if (strike <= 0) return 1;
|
|
151
|
+
if (years <= 0 || volatility <= 0 || spot <= 0) return spot * Math.exp((rate - dividendYield) * years) > strike ? 1 : 0;
|
|
152
|
+
const d2 = (Math.log(spot / strike) + (rate - dividendYield - volatility * volatility / 2) * years) / (volatility * Math.sqrt(years));
|
|
153
|
+
return normalCdf(d2);
|
|
154
|
+
}
|
|
155
|
+
|
|
136
156
|
export interface ImpliedVolatilityResult {
|
|
137
157
|
volatility: number | null;
|
|
138
158
|
/** Why no volatility could be solved, phrased for the pane body. */
|
|
@@ -194,9 +214,14 @@ export function solveImpliedVolatility(
|
|
|
194
214
|
return { volatility: (low + high) / 2, note: null };
|
|
195
215
|
}
|
|
196
216
|
|
|
197
|
-
/**
|
|
217
|
+
/**
|
|
218
|
+
* Time to the option date's 16:00 ET close, preserving live 0DTE time value.
|
|
219
|
+
* A settlement stamp is the settlement itself: Deribit's expiries settle at
|
|
220
|
+
* 08:00 UTC, the time their stamp carries (`isSettlementStamp`).
|
|
221
|
+
*/
|
|
198
222
|
export function daysToExpiryFrom(expirationSeconds: number, now: number): number {
|
|
199
223
|
if (!Number.isFinite(expirationSeconds)) return 0;
|
|
224
|
+
if (isSettlementStamp(expirationSeconds)) return Math.max(0, (expirationSeconds * 1000 - now) / DAY_MS);
|
|
200
225
|
const expirationDate = new Date(expirationSeconds * 1000);
|
|
201
226
|
const close = zonedWallClockToUtcMs(
|
|
202
227
|
OPTION_EXPIRY_TIME_ZONE,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
2
|
+
|
|
1
3
|
type Arm = "control" | "teaser" | null;
|
|
2
4
|
type TeaserKind = "summary" | "sample" | "none";
|
|
3
5
|
const active = new Map<string, Set<object>>();
|
|
@@ -17,7 +19,7 @@ export function trackWallView({ identity, placement, exposure, currentIdentity,
|
|
|
17
19
|
active.set(key, copies);
|
|
18
20
|
let released = false;
|
|
19
21
|
let reported = false;
|
|
20
|
-
const answer = exposure.catch((
|
|
22
|
+
const answer = exposure.catch(logFailure("shared.wall-view-lifecycle", "Resolve wall experiment arm failed", null));
|
|
21
23
|
|
|
22
24
|
return {
|
|
23
25
|
exposure: answer,
|
|
@@ -50,8 +50,10 @@ function boardObservation(payload: ThemesPayload | ThemeMembersPayload, now = Da
|
|
|
50
50
|
regularSessionDate: close?.date ?? null,
|
|
51
51
|
regularSessionClosedAt: close?.closedAt ?? null,
|
|
52
52
|
};
|
|
53
|
-
// A Pro account's board is priced from the server's live read
|
|
54
|
-
|
|
53
|
+
// A Pro account's board is priced from the server's live read while a session trades; outside it the figures are
|
|
54
|
+
// the session's close and the report claims no feed, as the pane's footer names the close instead of "live".
|
|
55
|
+
// Everyone else's, and an older server's, is fifteen minutes delayed.
|
|
56
|
+
const feed = !isLiveBoard(payload) ? { status: "delayed" as const, delayMinutes: 15 } : close ? {} : { status: "live" as const };
|
|
55
57
|
if (close && now - assembledAt <= SNAPSHOT_STALE_MS) {
|
|
56
58
|
return {
|
|
57
59
|
metadata: { asOf: close.closedAt != null ? new Date(close.closedAt).toISOString() : close.date, sessionExchange: "NYSE", ...snapshot },
|
|
@@ -77,10 +79,10 @@ function themeReportTitle(query: string | undefined, theme: Pick<ThemeSummary, "
|
|
|
77
79
|
export const themesHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
78
80
|
shape: "rows",
|
|
79
81
|
// The pane's stored snapshot, refreshed every 15 minutes, priced in real time for a Pro account and
|
|
80
|
-
// fifteen minutes delayed otherwise (boardObservation says which). As in the pane,
|
|
82
|
+
// fifteen minutes delayed otherwise (boardObservation says which, and declares the delay). As in the pane,
|
|
81
83
|
// the payload's stale flags are historical; a snapshot over 30 minutes old is what reads stale
|
|
82
84
|
// (boardObservation, which dates a board outside the regular session by its close).
|
|
83
|
-
freshness: {
|
|
85
|
+
freshness: { ignoreStaleFlags: true },
|
|
84
86
|
argument: { kind: "free-text", optional: true, placeholder: "theme", description: "Theme name or keyword, such as nuclear; omit for all themes." },
|
|
85
87
|
options: [],
|
|
86
88
|
discovery: { aliases: ["THEM"], dataRequirements: ["Gloom Cloud stored equity snapshot"],
|
|
@@ -17,14 +17,16 @@ export function boardYtdBase(payload: { asOf?: unknown; regularSessionDate?: unk
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* A period's header. YTD
|
|
21
|
-
*
|
|
20
|
+
* A period's header. YTD says what it is, a return against the close it is
|
|
21
|
+
* measured from, as long as `room` allows: `YTD vs 31 Dec close`, `YTD vs 31
|
|
22
|
+
* Dec`, then `YTD`.
|
|
22
23
|
*/
|
|
23
24
|
export function periodLabel(period: ThemePeriod, ytdBase: string | null, room = Infinity): string {
|
|
24
25
|
if (period !== "returnYtdPercent" || !ytdBase) return PERIOD_LABELS[period];
|
|
25
|
-
const labels = [`YTD vs ${ytdBase} close`, `YTD vs ${ytdBase}`,
|
|
26
|
+
const labels = [`YTD vs ${ytdBase} close`, `YTD vs ${ytdBase}`, "YTD"];
|
|
26
27
|
return labels.find((label) => label.length <= room) ?? labels.at(-1)!;
|
|
27
28
|
}
|
|
29
|
+
|
|
28
30
|
export interface ThemeSort { columnId: string; direction: SortDirection }
|
|
29
31
|
export const DEFAULT_SORT: ThemeSort = { columnId: "changePercent", direction: "desc" };
|
|
30
32
|
export function percent(value: number | null): string {
|
|
@@ -32,12 +34,17 @@ export function percent(value: number | null): string {
|
|
|
32
34
|
const rounded = Number(value.toFixed(2));
|
|
33
35
|
return `${rounded > 0 ? "+" : ""}${rounded.toFixed(2)}%`;
|
|
34
36
|
}
|
|
37
|
+
/** How many of a theme's names a figure is drawn from, when not all of them: `7 of 8`. Empty when every name is in it. */
|
|
38
|
+
export function coverageText(metric: ThemeAggregate): string {
|
|
39
|
+
return metric.covered < metric.total ? `${metric.covered} of ${metric.total}` : "";
|
|
40
|
+
}
|
|
35
41
|
export function aggregateText(metric: ThemeAggregate, breadth = false): string {
|
|
36
42
|
const value = breadth && metric.value !== null ? `${Math.round(metric.value)}%` : percent(metric.value);
|
|
37
|
-
|
|
43
|
+
const coverage = coverageText(metric);
|
|
44
|
+
return `${value}${coverage ? ` ${coverage}` : ""}`;
|
|
38
45
|
}
|
|
39
46
|
export const memberPrice = (value: number | null) => value === null ? "--" : value.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
40
|
-
export const coverageWidth = (metrics: readonly ThemeAggregate[]) => Math.max(0, ...metrics.map((metric) => metric
|
|
47
|
+
export const coverageWidth = (metrics: readonly ThemeAggregate[]) => Math.max(0, ...metrics.map((metric) => coverageText(metric) ? coverageText(metric).length + 1 : 0));
|
|
41
48
|
export function sortThemes(rows: readonly ThemeSummary[], sort: ThemeSort): ThemeSummary[] {
|
|
42
49
|
const value = (row: ThemeSummary) => {
|
|
43
50
|
if (sort.columnId === "name") return row.name;
|
|
@@ -73,7 +80,7 @@ export function boardClose(payload: { regularSessionDate?: unknown; regularSessi
|
|
|
73
80
|
const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] as const;
|
|
74
81
|
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] as const;
|
|
75
82
|
/** `Fri 9 Oct close` for the US session dated 2026-10-09, named by its own day, never the reader's. */
|
|
76
|
-
function closeLabel(date: string): string {
|
|
83
|
+
export function closeLabel(date: string): string {
|
|
77
84
|
const day = new Date(`${date}T00:00:00Z`);
|
|
78
85
|
return `${WEEKDAYS[day.getUTCDay()]} ${day.getUTCDate()} ${MONTHS[day.getUTCMonth()]} close`;
|
|
79
86
|
}
|
|
@@ -152,9 +159,11 @@ export function themeColumns(width: number, rows: readonly ThemeSummary[], ytdBa
|
|
|
152
159
|
const metricWidth = (period: ThemePeriod) => Math.max(8, ...rows.map((row) => percent(row.returns[period].value).length)) + suffixWidth(period);
|
|
153
160
|
// The same trailing space is reserved in every cell and its header. A note
|
|
154
161
|
// occupies that space without moving the number away from its label.
|
|
155
|
-
const label = (period: ThemePeriod, room
|
|
162
|
+
const label = (period: ThemePeriod, room: number) => periodLabel(period, ytdBase, room) + " ".repeat(suffixWidth(period));
|
|
163
|
+
// The board always keeps room for `YTD vs 31 Dec`, so even a narrow one says what its YTD is measured against.
|
|
164
|
+
const leastRoom = ytdBase ? `YTD vs ${ytdBase}`.length : 0;
|
|
156
165
|
const columns = [column("name", "Theme", 26, "left"), column("memberCount", "N", 3),
|
|
157
|
-
...THEME_PERIODS.map((period) => column(period, label(period,
|
|
166
|
+
...THEME_PERIODS.map((period) => column(period, label(period, leastRoom), Math.max(metricWidth(period), label(period, leastRoom).length + HEADER_ROOM))),
|
|
158
167
|
column("breadth", "Breadth" + " ".repeat(coverageWidth(rows.map((row) => row.breadth))), 7 + coverageWidth(rows.map((row) => row.breadth))),
|
|
159
168
|
column("best", "Best 1D", 15), column("worst", "Worst 1D", 15)];
|
|
160
169
|
const fitted = fitColumns(columns, width, ["worst", "best", "return3MPercent", "returnYtdPercent", "return1MPercent", "return1WPercent", "memberCount"]);
|
|
@@ -5,14 +5,14 @@ import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
|
5
5
|
import type { ExtendedSession } from "../../../market-data/market/status";
|
|
6
6
|
import { useAsyncResource, useAutoRefresh, usePluginPaneState, usePluginTickerActions, useUpdatedAgo } from "../../../public/react";
|
|
7
7
|
import { usePaneInstance } from "../../../state/app/context";
|
|
8
|
-
import {
|
|
8
|
+
import { shownPriceColor } from "../../../theme/colors";
|
|
9
9
|
import { useThemeColors } from "../../../theme/theme-context";
|
|
10
10
|
import { Box, Text } from "../../../ui";
|
|
11
11
|
import { nextHeaderSort } from "../../../utils/sort-values";
|
|
12
12
|
import { useMembersMenu } from "../shared/members-menu";
|
|
13
13
|
import { cachedMembers, cachedThemes, loadMembers, loadThemes } from "./client";
|
|
14
14
|
import {
|
|
15
|
-
aggregateText, boardYtdBase, coverageWidth, DEFAULT_SORT, extendedMove, matchTheme, memberColumns, memberExtendedSessions, memberPrice, percent,
|
|
15
|
+
aggregateText, boardYtdBase, coverageText, coverageWidth, DEFAULT_SORT, extendedMove, matchTheme, memberColumns, memberExtendedSessions, memberPrice, percent,
|
|
16
16
|
isLiveBoard, LIVE_BOARD_REFRESH_MS, SNAPSHOT_STALE_MS, snapshotFooterText, sortMembers, sortThemes, themeColumns, type ThemeSort,
|
|
17
17
|
} from "./model";
|
|
18
18
|
|
|
@@ -80,10 +80,10 @@ function Members({ id, width, height, focused }: { id: string; width: number; he
|
|
|
80
80
|
if (column.id === "preMarket" || column.id === "afterHours") {
|
|
81
81
|
// The extended print's move from the close in Price.
|
|
82
82
|
const move = extendedMove(row, column.id === "preMarket" ? "PRE" : "POST");
|
|
83
|
-
return { text: percent(move), value: move, color: move === null ? colors.textMuted :
|
|
83
|
+
return { text: percent(move), value: move, color: move === null ? colors.textMuted : shownPriceColor(move, 2, colors) };
|
|
84
84
|
}
|
|
85
85
|
const value = row[column.id as ThemePeriod];
|
|
86
|
-
return { text: percent(value), value, color: value === null ? colors.textMuted :
|
|
86
|
+
return { text: percent(value), value, color: value === null ? colors.textMuted : shownPriceColor(value, 2, colors) };
|
|
87
87
|
}, [colors]);
|
|
88
88
|
return <PaneStatusBody subject="theme members" loading={!data && resource.loading} error={!data ? resource.error : null}>
|
|
89
89
|
{data ? <DataTableView<ThemeMember> columns={columns} items={rows} focused={focused} rootWidth={width} rootHeight={height}
|
|
@@ -135,14 +135,14 @@ export function ThemesBoard({ width, height, focused, tabStrip, tabRows = 0 }: T
|
|
|
135
135
|
if (column.id === "best" || column.id === "worst") {
|
|
136
136
|
const member = row[column.id];
|
|
137
137
|
return { text: member ? `${member.symbol} ${percent(member.changePercent)}` : "--", value: member?.changePercent ?? null,
|
|
138
|
-
color: member ?
|
|
138
|
+
color: member ? shownPriceColor(member.changePercent, 2, colors) : colors.textMuted };
|
|
139
139
|
}
|
|
140
140
|
const breadth = column.id === "breadth";
|
|
141
141
|
const metric = breadth ? row.breadth : row.returns[column.id as ThemePeriod];
|
|
142
142
|
const suffixWidth = coverageWidth((data?.themes ?? []).map((theme) => breadth ? theme.breadth : theme.returns[column.id as ThemePeriod]));
|
|
143
|
-
const color = metric.value === null || breadth ? colors.textMuted :
|
|
143
|
+
const color = metric.value === null || breadth ? colors.textMuted : shownPriceColor(metric.value, 2, colors);
|
|
144
144
|
const valueText = breadth && metric.value !== null ? `${Math.round(metric.value)}%` : percent(metric.value);
|
|
145
|
-
const suffix = metric
|
|
145
|
+
const suffix = coverageText(metric);
|
|
146
146
|
return { text: aggregateText(metric, breadth), value: metric.value,
|
|
147
147
|
color, content: suffixWidth ? <Box flexDirection="row" width={column.width} justifyContent="flex-end">
|
|
148
148
|
<Text fg={color}>{valueText}</Text><Text fg={colors.textMuted}>{suffix.padStart(suffixWidth)}</Text>
|