@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
|
@@ -70,3 +70,51 @@ export function econRowFigures(row: { actual?: string | null; forecast?: string
|
|
|
70
70
|
scale: first?.scale ?? null,
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
|
+
|
|
74
|
+
/** A figure printed as a bare number, with no unit and no scale letter: `0.6`, `-3`, `1,5`. */
|
|
75
|
+
const BARE_NUMBER = /^[+-]?(?:\d+(?:[.,]\d+)?|\.\d+)$/;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Release names that are a percent whatever one row's figures say: a change
|
|
79
|
+
* month on month, quarter on quarter or year on year, and the rates published
|
|
80
|
+
* in percent. "Consumer Inflation Expectations" is a balance, not a rate, and
|
|
81
|
+
* is not one of them.
|
|
82
|
+
*/
|
|
83
|
+
const PERCENT_NAME = /\b(?:mom|qoq|yoy)\b|(?:^|\s)[mqy]\/[mqy](?=\s|$)|\b(?:inflation|unemployment|jobless|interest|growth|mortgage|funds|participation|savings) rate\b/i;
|
|
84
|
+
|
|
85
|
+
type EconRowText = { country: string; event: string; indicatorId?: string | null; actual: string | null; forecast: string | null; prior: string | null };
|
|
86
|
+
|
|
87
|
+
const FIGURE_KEYS = ["actual", "forecast", "prior"] as const;
|
|
88
|
+
|
|
89
|
+
/** Every release of one indicator: its id, else its country and name. */
|
|
90
|
+
const indicatorKey = (event: Pick<EconRowText, "country" | "event" | "indicatorId">) => event.indicatorId || `${event.country}|${event.event}`;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The calendar with every figure of a percent indicator in percent. The feeds
|
|
94
|
+
* drop the sign on some rows: US Inflation Rate MoM's prior came as `0.6`, and
|
|
95
|
+
* the 30-year mortgage rate's as `7.32` beside its actual `7.41%`. An
|
|
96
|
+
* indicator is in percent when any of its figures is, or its name says so
|
|
97
|
+
* (`PERCENT_NAME`), and none of its figures is in another unit or scale; its
|
|
98
|
+
* bare figures then carry `%`. Anything else is left as the feed wrote it.
|
|
99
|
+
*/
|
|
100
|
+
export function withPercentUnits<T extends EconRowText>(events: readonly T[]): T[] {
|
|
101
|
+
const indicators = new Map<string, { percent: boolean; other: boolean }>();
|
|
102
|
+
for (const event of events) {
|
|
103
|
+
const key = indicatorKey(event);
|
|
104
|
+
const seen = indicators.get(key) ?? { percent: false, other: false };
|
|
105
|
+
if (PERCENT_NAME.test(event.event)) seen.percent = true;
|
|
106
|
+
for (const field of FIGURE_KEYS) {
|
|
107
|
+
const figure = parseEconFigure(event[field]);
|
|
108
|
+
if (!figure) continue;
|
|
109
|
+
if (figure.unit === "%") seen.percent = true;
|
|
110
|
+
else if (figure.unit || figure.scale !== 1) seen.other = true;
|
|
111
|
+
}
|
|
112
|
+
indicators.set(key, seen);
|
|
113
|
+
}
|
|
114
|
+
return events.map((event) => {
|
|
115
|
+
const seen = indicators.get(indicatorKey(event))!;
|
|
116
|
+
if (!seen.percent || seen.other) return event;
|
|
117
|
+
const bare = FIGURE_KEYS.filter((field) => BARE_NUMBER.test(event[field]?.trim() ?? ""));
|
|
118
|
+
return bare.length ? { ...event, ...Object.fromEntries(bare.map((field) => [field, `${event[field]!.trim()}%`])) } : event;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
@@ -36,10 +36,12 @@ import {
|
|
|
36
36
|
FILTER_CYCLE,
|
|
37
37
|
attachEconCalendarPersistence,
|
|
38
38
|
actualColor,
|
|
39
|
+
awaitsConsensus,
|
|
39
40
|
calendarDisplayRows,
|
|
40
41
|
CALENDAR_TIME_ZONE_LABEL,
|
|
41
42
|
dateKey,
|
|
42
43
|
dayLabel,
|
|
44
|
+
econEventName,
|
|
43
45
|
filteredEmptyTitle,
|
|
44
46
|
formatStaleness,
|
|
45
47
|
getCalendarCache,
|
|
@@ -50,6 +52,7 @@ import {
|
|
|
50
52
|
eventCountry,
|
|
51
53
|
matchesCountry,
|
|
52
54
|
matchesImpact,
|
|
55
|
+
NO_CONSENSUS_NOTE,
|
|
53
56
|
noScheduledReleasesMessage,
|
|
54
57
|
parseCountryFilter,
|
|
55
58
|
resetEconCalendarPersistence,
|
|
@@ -322,7 +325,7 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
|
|
|
322
325
|
: formatCountdown(nextEvent.date.getTime() - now);
|
|
323
326
|
// The countdown changes every tick, so it is footer status, not query bar
|
|
324
327
|
// context; the footer ellipsizes a long event name.
|
|
325
|
-
const nextText = nextEvent && nextCountdown ? `next ${nextEvent
|
|
328
|
+
const nextText = nextEvent && nextCountdown ? `next ${econEventName(nextEvent)} ${nextCountdown}` : null;
|
|
326
329
|
// The cached first paint is being replaced; its age only counts once a load has answered.
|
|
327
330
|
const showStale = stale && (!loading || calendar.updatedAt !== null);
|
|
328
331
|
// An empty list says it in its empty state instead.
|
|
@@ -330,6 +333,9 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
|
|
|
330
333
|
// What the High filter, where the calendar opens, leaves out, and the key that lists it (Shift+F steps back to All).
|
|
331
334
|
const hiddenCount = useMemo(() => lowerImpactHidden(events, impactFilter, countryFilter), [countryFilter, events, impactFilter]);
|
|
332
335
|
const hiddenStatus = hiddenCount > 0 && !detailEvent ? `${hiddenCount} lower-impact hidden` : null;
|
|
336
|
+
// The dash under Forecast on the row in hand, while its consensus is not in yet.
|
|
337
|
+
const focusedEvent = detailEvent ?? filtered[selectedIdx];
|
|
338
|
+
const noConsensus = !!focusedEvent && awaitsConsensus(focusedEvent);
|
|
333
339
|
const calendarStatus = useMemo<PaneFooterSegment[]>(() => [
|
|
334
340
|
...(hiddenStatus ? [{
|
|
335
341
|
id: "hidden-impact",
|
|
@@ -341,8 +347,11 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
|
|
|
341
347
|
...(nextText && !detailEvent ? [{ id: "next", parts: [{ text: nextText, tone: "muted" as const }] }] : []),
|
|
342
348
|
...(listedAfterStatus ? [{ id: "listed-after", parts: [{ text: listedAfterStatus, tone: "warning" as const }] }] : []),
|
|
343
349
|
...(showStale ? [{ id: "stale", parts: [{ text: "STALE", tone: "warning" as const }] }] : []),
|
|
350
|
+
// Behind the warnings, so a narrow footer gives it up before them, and ahead of the update time, which goes first.
|
|
351
|
+
...(noConsensus ? [{ id: "no-consensus", parts: [{ text: NO_CONSENSUS_NOTE, tone: "muted" as const }],
|
|
352
|
+
variants: [[{ text: "no consensus yet", tone: "muted" as const }]] }] : []),
|
|
344
353
|
...(staleness ? [{ id: "updated", parts: [{ text: staleness, tone: "muted" as const }] }] : []),
|
|
345
|
-
], [detailEvent, hiddenStatus, listedAfterStatus, nextText, showStale, staleness]);
|
|
354
|
+
], [detailEvent, hiddenStatus, listedAfterStatus, nextText, noConsensus, showStale, staleness]);
|
|
346
355
|
// A report of the pane (`gloom fn ECO`) says it too.
|
|
347
356
|
const listedThrough = listedEnd ? dateKey(listedEnd) : null;
|
|
348
357
|
useRemoteUiNode(listedAfter && listedThrough ? {
|
|
@@ -433,7 +442,7 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
|
|
|
433
442
|
return withCountryFlag({ text: country, color: colors.textMuted }, country, flagged);
|
|
434
443
|
}
|
|
435
444
|
case "event":
|
|
436
|
-
return { text: ev
|
|
445
|
+
return { text: econEventName(ev), color: colors.text };
|
|
437
446
|
case "actual":
|
|
438
447
|
return {
|
|
439
448
|
text: ev.actual ?? "—",
|
|
@@ -480,7 +489,7 @@ function EconCalendarPane({ focused, width, height }: PaneProps) {
|
|
|
480
489
|
focused={focused}
|
|
481
490
|
detailOpen={!!detailEvent}
|
|
482
491
|
onBack={() => setOpenKey(null)}
|
|
483
|
-
detailTitle={detailEvent
|
|
492
|
+
detailTitle={detailEvent ? econEventName(detailEvent) : undefined}
|
|
484
493
|
detailContent={detailContent}
|
|
485
494
|
rootWidth={width}
|
|
486
495
|
rootHeight={Math.max(1, height - 1)}
|
|
@@ -576,7 +585,7 @@ export const economicCalendarModule: PluginModule = {
|
|
|
576
585
|
description: "Upcoming economic events, releases, and indicators.",
|
|
577
586
|
// "econ" stays a keyword so the old ECON prefix still finds this pane.
|
|
578
587
|
keywords: ["econ", "economic", "calendar", "events", "macro", "releases", "fed", "cpi", "gdp", "rate decision", "ipca",
|
|
579
|
-
"brazil inflation", "pmi", "ism", "purchasing managers", "flash pmi"],
|
|
588
|
+
"brazil inflation", "pmi", "ism", "purchasing managers", "flash pmi", "jobs report", "nfp", "payrolls", "nonfarm"],
|
|
580
589
|
// ECO MX,BR opens the calendar on those countries.
|
|
581
590
|
shortcut: {
|
|
582
591
|
prefix: "ECO",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { GloomPluginContext } from "../../../types/plugin";
|
|
2
2
|
import {
|
|
3
|
+
econEventName,
|
|
3
4
|
eventCountry,
|
|
4
5
|
getCalendarCache,
|
|
5
6
|
landedHighImpactPrints,
|
|
@@ -57,7 +58,8 @@ const DEFAULT_DEPS: PrintWatchDeps = {
|
|
|
57
58
|
};
|
|
58
59
|
|
|
59
60
|
function printBody(event: EconEvent): string {
|
|
60
|
-
|
|
61
|
+
const name = econEventName(event);
|
|
62
|
+
return event.forecast ? `${name} ${event.actual} vs ${event.forecast}` : `${name} ${event.actual}`;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
type PrintWatchContext = Pick<GloomPluginContext, "notify" | "showPane">;
|
|
@@ -29,6 +29,8 @@ export interface EconEvent {
|
|
|
29
29
|
source?: { name: string; url?: string };
|
|
30
30
|
/** The indicator this release is of, the same from one release to the next; `id` is this row's alone. */
|
|
31
31
|
indicatorId?: string | null;
|
|
32
|
+
/** Which feed the forecast was read from (`week-feed`, `release-calendar`) and when that feed was read. */
|
|
33
|
+
consensus?: { source: string; readAt: string } | null;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
/** Whether a row's time is blank because its publisher states none (a bank whose calendar gives the day only). */
|
|
@@ -101,6 +101,17 @@ function bundleFrom(unaligned: StatBuild[], errors: string[]): StatsBundle {
|
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/**
|
|
105
|
+
* The bundle's errors less the refresh failures of statistics the copy an
|
|
106
|
+
* earlier read saved answered for: those say when that copy is from instead.
|
|
107
|
+
*/
|
|
108
|
+
export function unansweredErrors(bundle: StatsBundle): string[] {
|
|
109
|
+
const answered = new Set(bundle.builds.flatMap(({ stat, cache }) => (
|
|
110
|
+
cache?.source === "stale-fallback" && cache.refreshError ? [`${stat.seriesId}: ${cache.refreshError}`] : []
|
|
111
|
+
)));
|
|
112
|
+
return bundle.errors.filter((error) => !answered.has(error));
|
|
113
|
+
}
|
|
114
|
+
|
|
104
115
|
/** Builds whatever the on-disk cache can already answer, for an instant first paint. */
|
|
105
116
|
export function getCachedStatsBundle(stats: readonly StatDef[] = STATS): StatsBundle | null {
|
|
106
117
|
const builds: StatBuild[] = [];
|
|
@@ -60,6 +60,12 @@ export interface StatDef {
|
|
|
60
60
|
note: string;
|
|
61
61
|
/** Other words the catalog and the pane's filter find it by: `PMMS` for the mortgage rate. */
|
|
62
62
|
keywords?: readonly string[];
|
|
63
|
+
/**
|
|
64
|
+
* The names a reader gives the indicator, read as an argument ahead of any
|
|
65
|
+
* FRED series id: `PCE` is the price index release, not the series FRED files
|
|
66
|
+
* under that id (personal spending), and `gdp` is real GDP, not its nowcast.
|
|
67
|
+
*/
|
|
68
|
+
aliases?: readonly string[];
|
|
63
69
|
/** Concise measurement identity that must remain beside the values. */
|
|
64
70
|
measurementBasis?: string;
|
|
65
71
|
/**
|
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
HeadlessPaneDefinition,
|
|
4
4
|
HeadlessPaneEntry,
|
|
5
5
|
} from "../../../types/plugin";
|
|
6
|
+
import { servedFromSavedCopy } from "../../../data/saved-copies";
|
|
6
7
|
import { fredSourceLabel } from "../../../sources/gloomberb-cloud/fred-series";
|
|
7
8
|
import { formatBasisPoints, toBasisPoints } from "../../../utils/basis-points";
|
|
8
9
|
import { formatNumber } from "../../../utils/format";
|
|
@@ -10,6 +11,7 @@ import type { StatDef } from "./defs";
|
|
|
10
11
|
import {
|
|
11
12
|
createStatSeriesLoader,
|
|
12
13
|
loadStatsBundle,
|
|
14
|
+
unansweredErrors,
|
|
13
15
|
type StatsBundle,
|
|
14
16
|
} from "./client";
|
|
15
17
|
import { historyStartNotice } from "../shared/history-span";
|
|
@@ -17,7 +19,7 @@ import { formatMoveRank } from "../shared/move-rank";
|
|
|
17
19
|
import type { StatPoint } from "../shared/stat-transform";
|
|
18
20
|
import { formatSigma } from "../shared/trend";
|
|
19
21
|
import { STAT_CATEGORIES } from "./defs";
|
|
20
|
-
import { DEFAULT_STAT_ID,
|
|
22
|
+
import { DEFAULT_STAT_ID, readStatArg, STATS } from "./stats";
|
|
21
23
|
import { selectStatViews, type StatRangeId, type StatViewModel } from "./view";
|
|
22
24
|
|
|
23
25
|
/** How a report reads a statistic: a spread in basis points, everything else as the pane does. */
|
|
@@ -84,6 +86,11 @@ function detailEntries(view: StatViewModel, cutoffShown: boolean, text: boolean)
|
|
|
84
86
|
];
|
|
85
87
|
}
|
|
86
88
|
|
|
89
|
+
/** A statistic answered from the copy saved by an earlier read, its refresh having failed, says so in its row. */
|
|
90
|
+
function savedCopyRow<T extends object>(view: StatViewModel, row: T): T {
|
|
91
|
+
return view.cacheSource === "stale-fallback" && view.fetchedAt != null ? servedFromSavedCopy(row, view.fetchedAt) : row;
|
|
92
|
+
}
|
|
93
|
+
|
|
87
94
|
/** The window the %ile column counts over, in words. */
|
|
88
95
|
const PERCENTILE_WINDOW: Record<StatRangeId, string> = {
|
|
89
96
|
"5Y": "the last 5 years",
|
|
@@ -163,7 +170,7 @@ export function projectStatsHeadlessBundle(
|
|
|
163
170
|
textColumns: columns(true),
|
|
164
171
|
// `as_of` is the release; a figure with none stated is as of its own day when it is observed daily or weekly, never its period's first day.
|
|
165
172
|
rowAsOfKey: "exportAsOf",
|
|
166
|
-
rows: categoryViews.map((view) => ({
|
|
173
|
+
rows: categoryViews.map((view) => savedCopyRow(view, {
|
|
167
174
|
id: view.stat.id,
|
|
168
175
|
indicator: view.stat.shortLabel,
|
|
169
176
|
latest: view.latest.value,
|
|
@@ -208,7 +215,8 @@ export function projectStatsHeadlessBundle(
|
|
|
208
215
|
...exportDating(selected),
|
|
209
216
|
}] : []),
|
|
210
217
|
],
|
|
211
|
-
|
|
218
|
+
// A statistic answered from its saved copy says so in the source line, not as an error.
|
|
219
|
+
errors: unansweredErrors(bundle),
|
|
212
220
|
notes: [
|
|
213
221
|
`%ile is the share of past readings at or below the latest one, over ${PERCENTILE_WINDOW[range]}.`
|
|
214
222
|
+ (range === "ALL" ? "" : " Full history: --range ALL."),
|
|
@@ -238,7 +246,7 @@ export const econStatisticsHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
238
246
|
argument: {
|
|
239
247
|
kind: "free-text",
|
|
240
248
|
placeholder: "statistic",
|
|
241
|
-
description: "Optional statistic id, FRED series id,
|
|
249
|
+
description: "Optional statistic id, name (PCE, GDP), label, FRED series id, or prefix.",
|
|
242
250
|
optional: true,
|
|
243
251
|
},
|
|
244
252
|
options: [{
|
|
@@ -250,7 +258,8 @@ export const econStatisticsHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
250
258
|
}],
|
|
251
259
|
describe: (args) => `Economic Statistics | ${String(args.options.range)}`,
|
|
252
260
|
async load(args, ctx) {
|
|
253
|
-
const
|
|
261
|
+
const read = typeof args.argument === "string" ? readStatArg(args.argument) : null;
|
|
262
|
+
const requested = read?.stat ?? null;
|
|
254
263
|
if (args.argument && !requested) {
|
|
255
264
|
throw new Error(
|
|
256
265
|
`Unknown economic statistic "${String(args.argument)}". Use one of: ${STATS.map(({ id }) => id).join(", ")}.`,
|
|
@@ -261,6 +270,7 @@ export const econStatisticsHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
261
270
|
loader: createStatSeriesLoader(ctx.apiClient),
|
|
262
271
|
...(requested ? { stats: [requested] } : {}),
|
|
263
272
|
});
|
|
264
|
-
|
|
273
|
+
const report = projectStatsHeadlessBundle(bundle, range, requested?.id ?? DEFAULT_STAT_ID);
|
|
274
|
+
return read?.readAs ? { ...report, warnings: [read.readAs] } : report;
|
|
265
275
|
},
|
|
266
276
|
};
|
|
@@ -12,8 +12,9 @@ import type { PaneProps } from "../../../types/plugin";
|
|
|
12
12
|
import { formatNumber } from "../../../utils/format";
|
|
13
13
|
import { useAutoRefresh } from "../../../react/auto-refresh";
|
|
14
14
|
import { usePaneStatusFooter } from "../../../components/layout/pane/status-footer";
|
|
15
|
+
import { savedCopyFooterSegment } from "../../../components/layout/pane/footer";
|
|
15
16
|
import { SeriesListDetail, useSeriesList } from "../shared/series-list-detail";
|
|
16
|
-
import { getCachedStatsBundle, loadStatsBundle } from "./client";
|
|
17
|
+
import { getCachedStatsBundle, loadStatsBundle, unansweredErrors } from "./client";
|
|
17
18
|
import { categoryLabel, changeColor, type StatCategoryId } from "./defs";
|
|
18
19
|
import { StatDetail } from "./detail";
|
|
19
20
|
import { DEFAULT_STAT_ID } from "./stats";
|
|
@@ -145,7 +146,7 @@ export function EconStatisticsPane({ focused, width, height }: PaneProps) {
|
|
|
145
146
|
const { selected, searchFocused } = list;
|
|
146
147
|
const rows = useMemo(() => withCategoryHeaders(list.visible), [list.visible]);
|
|
147
148
|
|
|
148
|
-
const error = resource.error ?? bundle
|
|
149
|
+
const error = resource.error ?? (bundle ? unansweredErrors(bundle)[0] : null) ?? null;
|
|
149
150
|
const footerInfo = useMemo<PaneFooterSegment[]>(() => {
|
|
150
151
|
if (!selected) return [];
|
|
151
152
|
// A Treasury yield names when in the day it was taken, as GC does, so it is not read against ^TNX's earlier close.
|
|
@@ -153,11 +154,14 @@ export function EconStatisticsPane({ focused, width, height }: PaneProps) {
|
|
|
153
154
|
const info: PaneFooterSegment[] = [
|
|
154
155
|
{ id: "as-of", parts: [{ text: `reading for ${cutoff ? selected.reading : selected.readingReleased}${cutoff}`, tone: "muted" }] },
|
|
155
156
|
];
|
|
156
|
-
// The cached first paint is being replaced; its age only counts once that load fails
|
|
157
|
+
// The cached first paint is being replaced; its age only counts once that load fails, and then it is the
|
|
158
|
+
// copy an earlier read saved, which says when that was.
|
|
157
159
|
const seeding = resource.loading && resource.updatedAt === null && !selected.refreshError;
|
|
158
|
-
|
|
160
|
+
const savedAt = selected.cacheSource === "stale-fallback" ? selected.fetchedAt : null;
|
|
161
|
+
if (selected.observationStale || (selected.cacheStale && !seeding && savedAt == null)) {
|
|
159
162
|
info.push({ id: "stale", parts: [{ text: "STALE", tone: "warning", bold: true }] });
|
|
160
163
|
}
|
|
164
|
+
if (savedAt != null) info.push(savedCopyFooterSegment("saved-copy", savedAt));
|
|
161
165
|
return info;
|
|
162
166
|
}, [resource.loading, resource.updatedAt, selected]);
|
|
163
167
|
|
|
@@ -68,6 +68,7 @@ export const STATS: readonly StatDef[] = [
|
|
|
68
68
|
}),
|
|
69
69
|
stat({
|
|
70
70
|
id: "pce-yoy",
|
|
71
|
+
aliases: ["pce", "pce inflation", "pce price index"],
|
|
71
72
|
label: "PCE Prices",
|
|
72
73
|
shortLabel: "PCE y/y",
|
|
73
74
|
category: "inflation",
|
|
@@ -143,6 +144,9 @@ export const STATS: readonly StatDef[] = [
|
|
|
143
144
|
reference: { value: 0, label: "flat" },
|
|
144
145
|
staleAfterMs: MONTHLY_EARLY,
|
|
145
146
|
note: "Jobs added or lost last month, the single most watched release.",
|
|
147
|
+
// The jobs report as it is called: `ECST nfp` opens it.
|
|
148
|
+
aliases: ["nfp", "nonfarm", "nonfarm payrolls", "jobs report"],
|
|
149
|
+
keywords: ["jobs report", "nfp", "nonfarm"],
|
|
146
150
|
limit: 900,
|
|
147
151
|
}),
|
|
148
152
|
stat({
|
|
@@ -179,6 +183,7 @@ export const STATS: readonly StatDef[] = [
|
|
|
179
183
|
// ---- Growth ----------------------------------------------------------
|
|
180
184
|
stat({
|
|
181
185
|
id: "real-gdp",
|
|
186
|
+
aliases: ["gdp", "gdp growth"],
|
|
182
187
|
measurementBasis: "q/q annualized",
|
|
183
188
|
label: "Real GDP",
|
|
184
189
|
shortLabel: "Real GDP q/q",
|
|
@@ -637,15 +642,40 @@ export const STATS: readonly StatDef[] = [
|
|
|
637
642
|
|
|
638
643
|
export const DEFAULT_STAT_ID = STATS[0]!.id;
|
|
639
644
|
|
|
640
|
-
/**
|
|
641
|
-
|
|
645
|
+
/** A statistic an argument names, and what to say when it had to be read. */
|
|
646
|
+
interface StatArgRead {
|
|
647
|
+
stat: StatDef;
|
|
648
|
+
/** "Read "PCE" as PCE Prices (PCE y/y)." when the argument is a name or a prefix, not the statistic's id, label or FRED series; null when it spelled one. */
|
|
649
|
+
readAs: string | null;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Reads a shortcut argument like `ECST cpi` as a statistic. What it is called
|
|
654
|
+
* comes before what FRED files it under: a statistic's id, its label, one of
|
|
655
|
+
* its common names (`PCE`, `gdp`), then a FRED series id, then the start of an
|
|
656
|
+
* id or a label. A name or a start says what it read, and a name that is also
|
|
657
|
+
* another statistic's FRED series (`PCE` is personal spending there) points at it.
|
|
658
|
+
*/
|
|
659
|
+
export function readStatArg(arg: string | null | undefined): StatArgRead | null {
|
|
642
660
|
const query = arg?.trim().toLowerCase();
|
|
643
661
|
if (!query) return null;
|
|
644
|
-
|
|
645
|
-
?? STATS.find((entry) => entry.
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
662
|
+
const exact = STATS.find((entry) => entry.id === query)
|
|
663
|
+
?? STATS.find((entry) => entry.label.toLowerCase() === query || entry.shortLabel.toLowerCase() === query);
|
|
664
|
+
if (exact) return { stat: exact, readAs: null };
|
|
665
|
+
const named = STATS.find((entry) => entry.aliases?.some((alias) => alias.toLowerCase() === query));
|
|
666
|
+
const series = STATS.find((entry) => entry.seriesId.toLowerCase() === query);
|
|
667
|
+
if (!named && series) return { stat: series, readAs: null };
|
|
668
|
+
const stat = named ?? STATS.find((entry) =>
|
|
669
|
+
entry.id.startsWith(query)
|
|
670
|
+
|| entry.label.toLowerCase().startsWith(query)
|
|
671
|
+
|| entry.shortLabel.toLowerCase().startsWith(query));
|
|
672
|
+
if (!stat) return null;
|
|
673
|
+
const otherSeries = series && series !== stat
|
|
674
|
+
? ` FRED's series ${series.seriesId} is ${series.label.toLowerCase()}: type ${series.id} for it.` : "";
|
|
675
|
+
return { stat, readAs: `Read "${arg!.trim()}" as ${stat.label} (${stat.shortLabel}).${otherSeries}` };
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/** Resolves shortcut arguments like `ECST cpi` onto a statistic. */
|
|
679
|
+
export function resolveStatArg(arg: string | null | undefined): StatDef | null {
|
|
680
|
+
return readStatArg(arg)?.stat ?? null;
|
|
651
681
|
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { ScreenDefinition, ScreenPayload, ScreenRow } from "../../../api-client/equity-screener";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The sector and industry names a screen's `sector` and `industry` criteria
|
|
5
|
+
* match, as the screener's rows carry them (read from the covered universe on
|
|
6
|
+
* 2026-10-28): the eleven sectors and the industries under them. A criterion
|
|
7
|
+
* matches a name exactly, so `Utility` or `utilities` matches nothing.
|
|
8
|
+
*/
|
|
9
|
+
export const SCREEN_SECTORS = [
|
|
10
|
+
"Basic Materials", "Communication Services", "Consumer Cyclical", "Consumer Defensive", "Energy",
|
|
11
|
+
"Financial Services", "Healthcare", "Industrials", "Real Estate", "Technology", "Utilities",
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
export const SCREEN_INDUSTRIES = [
|
|
15
|
+
"Advertising Agencies", "Aerospace & Defense", "Agricultural Inputs", "Airlines", "Airports & Air Services",
|
|
16
|
+
"Aluminum", "Apparel Manufacturing", "Apparel Retail", "Asset Management", "Auto Manufacturers", "Auto Parts",
|
|
17
|
+
"Auto & Truck Dealerships", "Banks - Diversified", "Banks - Regional", "Beverages - Brewers",
|
|
18
|
+
"Beverages - Non-Alcoholic", "Beverages - Wineries & Distilleries", "Biotechnology", "Broadcasting",
|
|
19
|
+
"Building Materials", "Building Products & Equipment", "Business Equipment & Supplies", "Capital Markets",
|
|
20
|
+
"Chemicals", "Coking Coal", "Communication Equipment", "Computer Hardware", "Confectioners", "Conglomerates",
|
|
21
|
+
"Consulting Services", "Consumer Electronics", "Copper", "Credit Services", "Department Stores",
|
|
22
|
+
"Diagnostics & Research", "Discount Stores", "Drug Manufacturers - General",
|
|
23
|
+
"Drug Manufacturers - Specialty & Generic", "Education & Training Services", "Electrical Equipment & Parts",
|
|
24
|
+
"Electronic Components", "Electronic Gaming & Multimedia", "Electronics & Computer Distribution",
|
|
25
|
+
"Engineering & Construction", "Entertainment", "Farm & Heavy Construction Machinery", "Farm Products",
|
|
26
|
+
"Financial Conglomerates", "Financial Data & Stock Exchanges", "Food Distribution", "Footwear & Accessories",
|
|
27
|
+
"Furnishings, Fixtures & Appliances", "Gambling", "Gold", "Grocery Stores", "Healthcare Plans",
|
|
28
|
+
"Health Information Services", "Home Improvement Retail", "Household & Personal Products",
|
|
29
|
+
"Industrial Distribution", "Information Technology Services", "Infrastructure Operations", "Insurance Brokers",
|
|
30
|
+
"Insurance - Diversified", "Insurance - Life", "Insurance - Property & Casualty", "Insurance - Reinsurance",
|
|
31
|
+
"Insurance - Specialty", "Integrated Freight & Logistics", "Internet Content & Information", "Internet Retail",
|
|
32
|
+
"Leisure", "Lodging", "Lumber & Wood Production", "Luxury Goods", "Marine Shipping", "Medical Care Facilities",
|
|
33
|
+
"Medical Devices", "Medical Distribution", "Medical Instruments & Supplies", "Metal Fabrication",
|
|
34
|
+
"Mortgage Finance", "Oil & Gas Drilling", "Oil & Gas E&P", "Oil & Gas Equipment & Services",
|
|
35
|
+
"Oil & Gas Integrated", "Oil & Gas Midstream", "Oil & Gas Refining & Marketing",
|
|
36
|
+
"Other Industrial Metals & Mining", "Other Precious Metals & Mining", "Packaged Foods",
|
|
37
|
+
"Packaging & Containers", "Paper & Paper Products", "Personal Services", "Pharmaceutical Retailers",
|
|
38
|
+
"Pollution & Treatment Controls", "Publishing", "Railroads", "Real Estate - Development",
|
|
39
|
+
"Real Estate Services", "Recreational Vehicles", "REIT - Diversified", "REIT - Healthcare Facilities",
|
|
40
|
+
"REIT - Hotel & Motel", "REIT - Industrial", "REIT - Mortgage", "REIT - Office", "REIT - Residential",
|
|
41
|
+
"REIT - Retail", "REIT - Specialty", "Rental & Leasing Services", "Residential Construction",
|
|
42
|
+
"Resorts & Casinos", "Restaurants", "Scientific & Technical Instruments", "Security & Protection Services",
|
|
43
|
+
"Semiconductor Equipment & Materials", "Semiconductors", "Shell Companies", "Silver",
|
|
44
|
+
"Software - Application", "Software - Infrastructure", "Solar", "Specialty Business Services",
|
|
45
|
+
"Specialty Chemicals", "Specialty Industrial Machinery", "Specialty Retail", "Staffing & Employment Services",
|
|
46
|
+
"Steel", "Telecom Services", "Textile Manufacturing", "Thermal Coal", "Tobacco", "Tools & Accessories",
|
|
47
|
+
"Travel Services", "Trucking", "Uranium", "Utilities - Diversified", "Utilities - Independent Power Producers",
|
|
48
|
+
"Utilities - Regulated Electric", "Utilities - Regulated Gas", "Utilities - Regulated Water",
|
|
49
|
+
"Utilities - Renewable", "Waste Management",
|
|
50
|
+
] as const;
|
|
51
|
+
|
|
52
|
+
/** GICS sector names, which these sectors are not, as the sector each means. */
|
|
53
|
+
const GICS_SECTORS: Record<string, (typeof SCREEN_SECTORS)[number]> = {
|
|
54
|
+
informationtechnology: "Technology",
|
|
55
|
+
financials: "Financial Services",
|
|
56
|
+
healthcare: "Healthcare",
|
|
57
|
+
consumerstaples: "Consumer Defensive",
|
|
58
|
+
consumerdiscretionary: "Consumer Cyclical",
|
|
59
|
+
materials: "Basic Materials",
|
|
60
|
+
telecommunicationservices: "Communication Services",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** The category fields whose names a screen can be checked against. A symbol has no list to check. */
|
|
64
|
+
type NamedField = "sector" | "industry" | "exchange" | "currency";
|
|
65
|
+
const NAMED_FIELDS: readonly NamedField[] = ["sector", "industry", "exchange", "currency"];
|
|
66
|
+
|
|
67
|
+
/** A criterion's value that names nothing the screen covers, with the names it was likely meant as. */
|
|
68
|
+
interface UnmatchedCategoryValue {
|
|
69
|
+
field: NamedField;
|
|
70
|
+
value: string;
|
|
71
|
+
closest: string[];
|
|
72
|
+
/** The value is a name of the other of sector and industry: `Solar` given as a sector. */
|
|
73
|
+
otherField?: "sector" | "industry";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Names compared without case, spaces or punctuation: `Oil and Gas E&P` beside `oil & gas e&p`. */
|
|
77
|
+
const fold = (name: string) => name.toLowerCase().replace(/\band\b/g, "&").replace(/[^a-z0-9&]+/g, "");
|
|
78
|
+
|
|
79
|
+
function editDistance(left: string, right: string): number {
|
|
80
|
+
let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
81
|
+
for (let row = 1; row <= left.length; row += 1) {
|
|
82
|
+
const current = [row];
|
|
83
|
+
for (let column = 1; column <= right.length; column += 1) {
|
|
84
|
+
const cost = left[row - 1] === right[column - 1] ? 0 : 1;
|
|
85
|
+
current[column] = Math.min(previous[column]! + 1, current[column - 1]! + 1, previous[column - 1]! + cost);
|
|
86
|
+
}
|
|
87
|
+
previous = current;
|
|
88
|
+
}
|
|
89
|
+
return previous[right.length]!;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** How far a typed name is from a candidate, 0 the same and 1 nothing alike: against the whole name or one of its parts. */
|
|
93
|
+
function nameDistance(typed: string, candidate: string): number {
|
|
94
|
+
const needle = fold(typed);
|
|
95
|
+
if (!needle) return 1;
|
|
96
|
+
const parts = [candidate, ...candidate.split(/\s+-\s+|\s+/)].map(fold).filter(Boolean);
|
|
97
|
+
return Math.min(...parts.map((part) => {
|
|
98
|
+
if (part === needle) return 0;
|
|
99
|
+
// "Bank" in "Banks - Regional", "Regulated" in "Utilities - Regulated Gas".
|
|
100
|
+
if (needle.length >= 4 && part.startsWith(needle)) return 0.2;
|
|
101
|
+
return editDistance(needle, part) / Math.max(needle.length, part.length);
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Names further than this from what was typed are not offered. */
|
|
106
|
+
const CLOSEST_DISTANCE = 0.4;
|
|
107
|
+
const CLOSEST_LIMIT = 5;
|
|
108
|
+
|
|
109
|
+
/** The names a typed one was likely meant as, closest first. */
|
|
110
|
+
function closestCategoryNames(typed: string, names: Iterable<string>, limit = CLOSEST_LIMIT): string[] {
|
|
111
|
+
const scored = [...new Set(names)].map((name) => ({ name, distance: nameDistance(typed, name) }));
|
|
112
|
+
// The name typed in another case or spacing is the one meant, whatever else is near it.
|
|
113
|
+
const same = scored.filter(({ name }) => fold(name) === fold(typed));
|
|
114
|
+
return (same.length > 0 ? same : scored)
|
|
115
|
+
.filter(({ distance }) => distance <= CLOSEST_DISTANCE)
|
|
116
|
+
.sort((a, b) => a.distance - b.distance || a.name.localeCompare(b.name))
|
|
117
|
+
.slice(0, limit)
|
|
118
|
+
.map(({ name }) => name);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The names each category field matches: the sector and industry lists above
|
|
123
|
+
* with those the rows carry, and the exchanges and currencies the covered
|
|
124
|
+
* universe reports. A field with no list (an older server's universe) is left
|
|
125
|
+
* out, so nothing is said of its values.
|
|
126
|
+
*/
|
|
127
|
+
function knownNames(payload: Pick<ScreenPayload, "universe">, rows: readonly Pick<ScreenRow, NamedField>[]): Record<NamedField, Set<string> | null> {
|
|
128
|
+
const seen = (field: NamedField) => rows.flatMap((row) => row[field] ? [row[field]!] : []);
|
|
129
|
+
const served = (list: readonly string[] | undefined, field: NamedField) =>
|
|
130
|
+
list?.length ? new Set([...list, ...seen(field)]) : null;
|
|
131
|
+
return {
|
|
132
|
+
sector: new Set([...SCREEN_SECTORS, ...payload.universe.sectors ?? [], ...seen("sector")]),
|
|
133
|
+
industry: new Set([...SCREEN_INDUSTRIES, ...seen("industry")]),
|
|
134
|
+
exchange: served(payload.universe.exchanges, "exchange"),
|
|
135
|
+
currency: served(payload.universe.currencies, "currency"),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The values of a screen's category criteria that name nothing the screen
|
|
141
|
+
* covers (`Utility` for the Utilities sector), so the screen reads why it
|
|
142
|
+
* matched nothing, or less than it should, rather than "No data".
|
|
143
|
+
*/
|
|
144
|
+
export function unmatchedCategoryValues(
|
|
145
|
+
definition: ScreenDefinition,
|
|
146
|
+
payload: Pick<ScreenPayload, "universe">,
|
|
147
|
+
rows: readonly Pick<ScreenRow, NamedField>[] = [],
|
|
148
|
+
): UnmatchedCategoryValue[] {
|
|
149
|
+
const known = knownNames(payload, rows);
|
|
150
|
+
const unmatched: UnmatchedCategoryValue[] = [];
|
|
151
|
+
for (const criterion of definition.criteria) {
|
|
152
|
+
if (criterion.op !== "in" || !(NAMED_FIELDS as readonly string[]).includes(criterion.field)) continue;
|
|
153
|
+
const field = criterion.field as NamedField;
|
|
154
|
+
const names = known[field];
|
|
155
|
+
if (!names) continue;
|
|
156
|
+
for (const value of criterion.value) {
|
|
157
|
+
if (names.has(value)) continue;
|
|
158
|
+
const other = field === "sector" ? "industry" : field === "industry" ? "sector" : null;
|
|
159
|
+
const otherNames = other ? known[other] : null;
|
|
160
|
+
const otherField = other && otherNames && [...otherNames].some((name) => fold(name) === fold(value)) ? other : undefined;
|
|
161
|
+
const gics = field === "sector" ? GICS_SECTORS[fold(value)] : undefined;
|
|
162
|
+
const closest = gics ? [gics] : closestCategoryNames(value, names);
|
|
163
|
+
unmatched.push({ field, value, closest, ...(otherField ? { otherField } : {}) });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return unmatched;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const quoted = (value: string) => JSON.stringify(value);
|
|
170
|
+
|
|
171
|
+
/** What a screen says of a value that names nothing: `No sector is named "Utility". Closest: Utilities.` */
|
|
172
|
+
export function unmatchedCategoryText(entry: UnmatchedCategoryValue): string {
|
|
173
|
+
const head = `No ${entry.field} is named ${quoted(entry.value)}`;
|
|
174
|
+
if (entry.otherField) return `${head}; it is ${entry.otherField === "industry" ? "an industry" : "a sector"} name, so use "field": ${quoted(entry.otherField)}.`;
|
|
175
|
+
return entry.closest.length > 0 ? `${head}. Closest: ${entry.closest.join(", ")}.` : `${head}.`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Where every sector and industry name is listed, said once after the values that name nothing. */
|
|
179
|
+
export const CATEGORY_NAMES_POINTER = "gloom catalog EQS lists every sector and industry.";
|
|
@@ -21,7 +21,7 @@ const savedPath = (id: string) => `saved/${encodeURIComponent(id)}`;
|
|
|
21
21
|
export const screenerApi = {
|
|
22
22
|
query: (query: ScreenQuery, signal?: AbortSignal, client: ScreenerClient = apiClient) =>
|
|
23
23
|
client.equityScreener<ScreenPayload>("query", json("POST", query, { signal })),
|
|
24
|
-
fields: (signal?: AbortSignal) => apiClient.equityScreener<ScreenFieldsResponse>("fields?include=social,research,attention,hiring-apps,kpis,volatility,dividends", { signal }),
|
|
24
|
+
fields: (signal?: AbortSignal) => apiClient.equityScreener<ScreenFieldsResponse>("fields?include=social,research,attention,hiring-apps,kpis,volatility,dividends,technical,option-volatility,ownership", { signal }),
|
|
25
25
|
export: (definition: ScreenDefinition, snapshotId: string) =>
|
|
26
26
|
apiClient.equityScreener<ScreenExportResponse>("export", json("POST", { ...definition, snapshotId }, { headers: { Accept: "application/json" } })),
|
|
27
27
|
saved: async (signal?: AbortSignal, client: ScreenerClient = apiClient) =>
|
|
@@ -40,10 +40,12 @@ export async function fetchScreen(
|
|
|
40
40
|
cursor: string | null = null,
|
|
41
41
|
signal?: AbortSignal,
|
|
42
42
|
client: ScreenerClient = apiClient,
|
|
43
|
+
/** Rows a page holds, at most the server's 200. */
|
|
44
|
+
limit = 100,
|
|
43
45
|
): Promise<ScreenPayload> {
|
|
44
46
|
const payload = validateScreenPayload(
|
|
45
47
|
await screenerApi.query(
|
|
46
|
-
{ ...parseScreenDefinition(definition), limit
|
|
48
|
+
{ ...parseScreenDefinition(definition), limit, cursor },
|
|
47
49
|
signal,
|
|
48
50
|
client,
|
|
49
51
|
),
|