@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
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { logFailure } from "../utils/log-failure";
|
|
2
|
+
import type { StallEvent } from "../utils/main-thread-stall";
|
|
3
|
+
import { VERSION } from "../version";
|
|
4
|
+
import { reportCrash } from "./crash-reports";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Freezes, made visible outside the app's own Debug Log.
|
|
8
|
+
*
|
|
9
|
+
* The stall monitor (utils/main-thread-stall.ts) notices when a process could
|
|
10
|
+
* not answer the user for a while and names the last measured section, and
|
|
11
|
+
* hands the stall to its sink. The terminal app and the desktop app's Bun
|
|
12
|
+
* process record their own stalls here; the desktop window sends the stalls
|
|
13
|
+
* it had in front of the user (stall-reports-dom.ts) to the Bun process,
|
|
14
|
+
* which records them too. Where a host is installed, each
|
|
15
|
+
* stall:
|
|
16
|
+
*
|
|
17
|
+
* - goes to a plain-text stall log in the profile folder, one line each, so a
|
|
18
|
+
* user or support can read what froze and when;
|
|
19
|
+
* - from a second up, counts toward a report sent through the crash reporter
|
|
20
|
+
* (and so behind the same off switch), the way update failures are: the
|
|
21
|
+
* duration bucket, the section's fixed name, the process, the longest
|
|
22
|
+
* stall and how many there were. No free text: a section name keeps only
|
|
23
|
+
* its fixed leading part, so a ticker in a queue key never leaves.
|
|
24
|
+
*
|
|
25
|
+
* One report per process, bucket, section and app version a day; stalls of
|
|
26
|
+
* the same kind after that are counted and carried in the next report.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/** Which process stalled. */
|
|
30
|
+
export type StallProcess = "window" | "app" | "terminal";
|
|
31
|
+
|
|
32
|
+
/** What error tracking gets: fixed values only. */
|
|
33
|
+
export interface StallReport {
|
|
34
|
+
process: StallProcess;
|
|
35
|
+
bucket: StallBucket;
|
|
36
|
+
section: string;
|
|
37
|
+
appVersion: string;
|
|
38
|
+
platform: "macos" | "windows" | "linux" | "other";
|
|
39
|
+
arch: "arm64" | "x64" | "other";
|
|
40
|
+
/** Stalls of this kind since the last report, this one included. */
|
|
41
|
+
count: number;
|
|
42
|
+
/** The longest of them, rounded to 100 ms. */
|
|
43
|
+
longestMs: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type StallBucket = "1-2s" | "2-5s" | "5-15s" | "15s+";
|
|
47
|
+
|
|
48
|
+
/** What the host keeps on disk between launches. */
|
|
49
|
+
export interface StallReportState {
|
|
50
|
+
/** When each kind was last reported, in ms. */
|
|
51
|
+
sent: Record<string, number>;
|
|
52
|
+
/** Stalls counted since each kind was last reported. */
|
|
53
|
+
pending: Record<string, { count: number; longestMs: number }>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface StallReporterHost {
|
|
57
|
+
/** The running version; defaults to this build's. */
|
|
58
|
+
appVersion?: string;
|
|
59
|
+
platform: string;
|
|
60
|
+
arch: string;
|
|
61
|
+
/** Appends one line to the stall log. */
|
|
62
|
+
appendLog(line: string): void;
|
|
63
|
+
load(): StallReportState;
|
|
64
|
+
save(state: StallReportState): void;
|
|
65
|
+
now?(): number;
|
|
66
|
+
/** Defaults to the crash reporter. */
|
|
67
|
+
send?(report: StallReport): void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
71
|
+
/** Shorter freezes go to the log only. */
|
|
72
|
+
const STALL_REPORT_MIN_MS = 1_000;
|
|
73
|
+
const SECTION_PATTERN = /^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*/;
|
|
74
|
+
const VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]{1,60})?$/;
|
|
75
|
+
/** Kinds with counts waiting for their next report; the oldest go first past this. */
|
|
76
|
+
const MAX_KINDS_KEPT = 200;
|
|
77
|
+
|
|
78
|
+
let host: StallReporterHost | null = null;
|
|
79
|
+
|
|
80
|
+
/** The fixed part of a section name: lower-case dotted words, cut where anything else starts. */
|
|
81
|
+
function stallSectionName(name: string | undefined): string {
|
|
82
|
+
if (!name) return "unmeasured";
|
|
83
|
+
const match = SECTION_PATTERN.exec(name);
|
|
84
|
+
const section = match?.[0].replace(/\.$/, "");
|
|
85
|
+
return section && section.length <= 80 ? section : "unmeasured";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function stallBucket(blockedMs: number): StallBucket {
|
|
89
|
+
if (blockedMs < 2_000) return "1-2s";
|
|
90
|
+
if (blockedMs < 5_000) return "2-5s";
|
|
91
|
+
if (blockedMs < 15_000) return "5-15s";
|
|
92
|
+
return "15s+";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A stall another process sent over, kept only when it has the right shape. */
|
|
96
|
+
export function parseStallEvent(value: unknown): StallEvent | null {
|
|
97
|
+
if (!value || typeof value !== "object") return null;
|
|
98
|
+
const { blockedMs, lastMeasured, lastMeasuredEndedMsAgo } = value as Record<string, unknown>;
|
|
99
|
+
if (typeof blockedMs !== "number" || !Number.isFinite(blockedMs) || blockedMs <= 0) return null;
|
|
100
|
+
return {
|
|
101
|
+
blockedMs,
|
|
102
|
+
...(typeof lastMeasured === "string" && lastMeasured.length <= 200 ? { lastMeasured } : {}),
|
|
103
|
+
...(typeof lastMeasuredEndedMsAgo === "number" && Number.isFinite(lastMeasuredEndedMsAgo) ? { lastMeasuredEndedMsAgo } : {}),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function formatSeconds(ms: number): string {
|
|
108
|
+
return `${(ms / 1000).toFixed(ms < 10_000 ? 1 : 0)} s`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** One line of the stall log: when, which process, how long, and what ran last. */
|
|
112
|
+
function formatStallLogLine(process: StallProcess, event: StallEvent, at: number): string {
|
|
113
|
+
const time = new Date(at).toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
114
|
+
const last = event.lastMeasured
|
|
115
|
+
? `last measured: ${event.lastMeasured}${event.lastMeasuredEndedMsAgo === undefined ? "" : `, ended ${Math.round(event.lastMeasuredEndedMsAgo)} ms before`}`
|
|
116
|
+
: "nothing measured before it";
|
|
117
|
+
return `${time} ${process.padEnd(8)} froze ${formatSeconds(event.blockedMs).padStart(6)} ${last}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function reportPlatform(platform: string): StallReport["platform"] {
|
|
121
|
+
if (platform === "darwin") return "macos";
|
|
122
|
+
if (platform === "win32") return "windows";
|
|
123
|
+
if (platform === "linux") return "linux";
|
|
124
|
+
return "other";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function reportArch(arch: string): StallReport["arch"] {
|
|
128
|
+
return arch === "arm64" || arch === "x64" ? arch : "other";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function sendThroughCrashReporter(report: StallReport): void {
|
|
132
|
+
const details = [
|
|
133
|
+
`process=${report.process}`,
|
|
134
|
+
`count=${report.count}`,
|
|
135
|
+
`longest=${report.longestMs}ms`,
|
|
136
|
+
`platform=${report.platform}-${report.arch}`,
|
|
137
|
+
].join(" ");
|
|
138
|
+
// A plain object, not an Error: it has no stack, so no file path goes along.
|
|
139
|
+
// The message is the bucket and section, so error tracking keeps one issue per kind.
|
|
140
|
+
reportCrash(
|
|
141
|
+
{ name: "MainThreadStall", message: `${report.bucket} after ${report.section}` },
|
|
142
|
+
{ kind: "plugin", plugin: "stall-monitor", pane: details },
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function loadState(current: StallReporterHost): StallReportState {
|
|
147
|
+
try {
|
|
148
|
+
const state = current.load();
|
|
149
|
+
return { sent: { ...state.sent }, pending: { ...state.pending } };
|
|
150
|
+
} catch (error) {
|
|
151
|
+
logFailure("telemetry-stalls", "Could not read stall report state")(error);
|
|
152
|
+
return { sent: {}, pending: {} };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function prune(state: StallReportState, now: number): void {
|
|
157
|
+
for (const [key, at] of Object.entries(state.sent)) {
|
|
158
|
+
if (!(now - at < DAY_MS) || at > now) delete state.sent[key];
|
|
159
|
+
}
|
|
160
|
+
const kinds = Object.keys(state.pending);
|
|
161
|
+
for (const key of kinds.slice(0, Math.max(0, kinds.length - MAX_KINDS_KEPT))) delete state.pending[key];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Records one stall of `process`. Does nothing where no host is installed; never throws. */
|
|
165
|
+
export function recordStall(process: StallProcess, event: StallEvent): void {
|
|
166
|
+
const current = host;
|
|
167
|
+
if (!current || !(event.blockedMs > 0)) return;
|
|
168
|
+
try {
|
|
169
|
+
const now = current.now?.() ?? Date.now();
|
|
170
|
+
current.appendLog(formatStallLogLine(process, event, now));
|
|
171
|
+
if (event.blockedMs < STALL_REPORT_MIN_MS) return;
|
|
172
|
+
const appVersion = (current.appVersion ?? VERSION).trim().replace(/^v/, "");
|
|
173
|
+
const section = stallSectionName(event.lastMeasured);
|
|
174
|
+
const bucket = stallBucket(event.blockedMs);
|
|
175
|
+
const version = VERSION_PATTERN.test(appVersion) ? appVersion : "unknown";
|
|
176
|
+
const key = `${process}|${bucket}|${section}|${version}`;
|
|
177
|
+
const state = loadState(current);
|
|
178
|
+
prune(state, now);
|
|
179
|
+
const pending = state.pending[key] ?? { count: 0, longestMs: 0 };
|
|
180
|
+
pending.count += 1;
|
|
181
|
+
pending.longestMs = Math.max(pending.longestMs, Math.round(event.blockedMs / 100) * 100);
|
|
182
|
+
if (state.sent[key] !== undefined) {
|
|
183
|
+
state.pending[key] = pending;
|
|
184
|
+
} else {
|
|
185
|
+
delete state.pending[key];
|
|
186
|
+
state.sent[key] = now;
|
|
187
|
+
(current.send ?? sendThroughCrashReporter)({
|
|
188
|
+
process,
|
|
189
|
+
bucket,
|
|
190
|
+
section,
|
|
191
|
+
appVersion: version,
|
|
192
|
+
platform: reportPlatform(current.platform),
|
|
193
|
+
arch: reportArch(current.arch),
|
|
194
|
+
count: pending.count,
|
|
195
|
+
longestMs: pending.longestMs,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
current.save(state);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
logFailure("telemetry-stalls", "Could not record a stall")(error);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function installStallReporter(next: StallReporterHost): () => void {
|
|
205
|
+
host = next;
|
|
206
|
+
return () => {
|
|
207
|
+
if (host === next) host = null;
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Test seam. */
|
|
212
|
+
export function resetStallReporterForTests(): void {
|
|
213
|
+
host = null;
|
|
214
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { beforeEach } from "bun:test";
|
|
2
|
+
import { resetCrashReporterForTests } from "../telemetry/crash-reports";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `bun test` runs every file in one process, so module state outlives the file
|
|
6
|
+
* that set it. A crash report queued while no reporter is installed, such as a
|
|
7
|
+
* plugin that fails to register in an app runtime test, would wait in the
|
|
8
|
+
* queue and go out with the next reporter another file installs, and its key
|
|
9
|
+
* would keep a later identical report from being queued at all. Every test
|
|
10
|
+
* starts with no reporter, an empty queue and nothing seen.
|
|
11
|
+
*/
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
resetCrashReporterForTests();
|
|
14
|
+
});
|
package/src/theme/themes.ts
CHANGED
|
@@ -745,6 +745,16 @@ const THEME_TRAITS: Readonly<Record<string, readonly ThemeTrait[]>> = {
|
|
|
745
745
|
"high-contrast": ["high-contrast", "colorblind-safe"],
|
|
746
746
|
};
|
|
747
747
|
|
|
748
|
+
/**
|
|
749
|
+
* Whether a theme's gains and losses are told apart by something other than
|
|
750
|
+
* the red and green everyone reads: a color-blind safe theme's blue and
|
|
751
|
+
* vermillion, which a chart keys (`▲▼` beside a candle series) so a reader
|
|
752
|
+
* knows which is which.
|
|
753
|
+
*/
|
|
754
|
+
export function themeKeysDirection(id: string): boolean {
|
|
755
|
+
return (THEME_TRAITS[id] ?? []).includes("colorblind-safe");
|
|
756
|
+
}
|
|
757
|
+
|
|
748
758
|
export interface ThemeSummary {
|
|
749
759
|
id: string;
|
|
750
760
|
name: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { logFailure } from "../utils/log-failure";
|
|
2
2
|
import type { DataProvider } from "../types/data-provider";
|
|
3
|
+
import type { Quote } from "../types/financials";
|
|
3
4
|
import type { InstrumentSearchResult } from "../types/instrument";
|
|
4
5
|
import { canonicalExchange, isUsListingExchange, parsePublicTickerKey } from "../utils/exchanges";
|
|
5
6
|
import { companyNameMatch, sameCompany, shortCompanyName } from "./company-name";
|
|
@@ -12,7 +13,7 @@ import { hasNonLatinLetters } from "../utils/typed-text";
|
|
|
12
13
|
|
|
13
14
|
export interface CompanyNameListing {
|
|
14
15
|
symbol: string;
|
|
15
|
-
/** Canonical code of its exchange. */
|
|
16
|
+
/** Canonical code of its exchange; empty for a US symbol read bare (a line its company `moved` to). */
|
|
16
17
|
exchange: string;
|
|
17
18
|
/** The listing's name as search gives it. */
|
|
18
19
|
name: string;
|
|
@@ -22,8 +23,24 @@ export interface CompanyNameListing {
|
|
|
22
23
|
* run together (JPM, JPMorgan Chase & Co.), which the reader is asked about.
|
|
23
24
|
*/
|
|
24
25
|
meant?: Omit<CompanyNameListing, "meant">;
|
|
26
|
+
/**
|
|
27
|
+
* When the company's US line, which search still lists, moved to another
|
|
28
|
+
* symbol and this is the line it trades as now (GETY became GETYQ): the
|
|
29
|
+
* change in one line, "GETY became GETYQ on 2026-10-22".
|
|
30
|
+
*/
|
|
31
|
+
moved?: string;
|
|
25
32
|
}
|
|
26
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Where a listing's company trades now when it moved off the symbol (GETY
|
|
36
|
+
* became GETYQ on 2026-10-22), from the line's quote (null when it has none)
|
|
37
|
+
* and the name search gives it: the symbol it took, and the change in one
|
|
38
|
+
* line. Null when it did not move, or that cannot be told.
|
|
39
|
+
*/
|
|
40
|
+
export type MovedLineLookup = (
|
|
41
|
+
listing: { symbol: string; exchange: string; quote: Quote | null; name: string | null },
|
|
42
|
+
) => Promise<{ symbol: string; note: string } | null>;
|
|
43
|
+
|
|
27
44
|
/** Share lines that are not the company's common stock, though search may type them as such. */
|
|
28
45
|
const NOT_COMMON = /preferred|warrant|right|unit|note|bond|debenture/i;
|
|
29
46
|
const RECEIPT = /depositary|^(?:ADR|ADS|GDR|CDR|BDR)$/i;
|
|
@@ -70,7 +87,7 @@ export function isCompanyNameInput(input: string): boolean {
|
|
|
70
87
|
export async function findListingOfCompanyName(
|
|
71
88
|
dataProvider: Pick<DataProvider, "search"> & Partial<Pick<DataProvider, "getQuote">>,
|
|
72
89
|
typed: string,
|
|
73
|
-
options: { meant?: boolean; oneWord?: boolean; quoted?: boolean } = {},
|
|
90
|
+
options: { meant?: boolean; oneWord?: boolean; quoted?: boolean; movedLine?: MovedLineLookup } = {},
|
|
74
91
|
): Promise<CompanyNameListing | null> {
|
|
75
92
|
const name = typed.trim();
|
|
76
93
|
if (!name) return null;
|
|
@@ -110,8 +127,12 @@ export async function findListingOfCompanyName(
|
|
|
110
127
|
const lines = ranked.filter((listing) => listing === first || (!listing.home && !otherHomes.has(plainName(listing.result.name))
|
|
111
128
|
&& sameCompany(first.result.name, listing.result.name)
|
|
112
129
|
&& listing.rank.slice(0, NAME_RANKS).every((value, index) => value === first.rank[index])));
|
|
113
|
-
const
|
|
114
|
-
const
|
|
130
|
+
const quoted = options.quoted ? await firstQuoted(dataProvider, lines, options.movedLine) : { line: first, moved: null };
|
|
131
|
+
const best = quoted.line;
|
|
132
|
+
const found: CompanyNameListing = quoted.moved
|
|
133
|
+
// The line it moved to is a US symbol, read bare as `quote` reads one (GETYQ, which trades over the counter).
|
|
134
|
+
? { symbol: quoted.moved.symbol, exchange: "", name: best.result.name, moved: quoted.moved.note }
|
|
135
|
+
: { symbol: best.result.symbol.toUpperCase(), exchange: best.exchange, name: best.result.name };
|
|
115
136
|
const meant = best.stock && options.meant ? await stockNamedRunTogether(dataProvider, name, results) : null;
|
|
116
137
|
return meant ? { ...found, meant } : found;
|
|
117
138
|
}
|
|
@@ -127,22 +148,41 @@ interface RankedListing {
|
|
|
127
148
|
exchange: string;
|
|
128
149
|
}
|
|
129
150
|
|
|
151
|
+
/** A US exchange, or the US over-the-counter market a line moves to when it leaves one. */
|
|
152
|
+
function isUsVenue(exchange: string): boolean {
|
|
153
|
+
return isUsListingExchange(exchange) || exchange.startsWith("OTC") || exchange === "PNK";
|
|
154
|
+
}
|
|
155
|
+
|
|
130
156
|
/**
|
|
131
157
|
* The first of a company's lines, in their order, that has a quote: the first
|
|
132
158
|
* itself, or when it has none, the first of the next few that does, asked
|
|
133
|
-
* together.
|
|
134
|
-
*
|
|
159
|
+
* together. A first US line whose company moved to another symbol (GETY
|
|
160
|
+
* became GETYQ) is followed there before any other line, so a name never lands
|
|
161
|
+
* on a listing abroad that still quotes the old shares. The first stands when none has a quote,
|
|
162
|
+
* when the source is too busy to say, and when nothing here can ask for quotes.
|
|
135
163
|
*/
|
|
136
|
-
async function firstQuoted<T extends RankedListing>(
|
|
164
|
+
async function firstQuoted<T extends RankedListing>(
|
|
165
|
+
dataProvider: Partial<Pick<DataProvider, "getQuote">>,
|
|
166
|
+
lines: readonly T[],
|
|
167
|
+
movedLine?: MovedLineLookup,
|
|
168
|
+
): Promise<{ line: T; moved: { symbol: string; note: string } | null }> {
|
|
137
169
|
const first = lines[0]!;
|
|
138
|
-
|
|
139
|
-
|
|
170
|
+
const only = (line: T) => ({ line, moved: null });
|
|
171
|
+
if (!dataProvider.getQuote) return only(first);
|
|
172
|
+
const quoteOf = ({ result, exchange }: RankedListing) => dataProvider.getQuote!(result.symbol.toUpperCase(), exchange);
|
|
173
|
+
let quote: Quote | null = null;
|
|
140
174
|
try {
|
|
141
|
-
|
|
175
|
+
quote = await quoteOf(first) ?? null;
|
|
142
176
|
} catch (error) {
|
|
143
177
|
// A busy source says nothing of the listing; any other failure is a listing with no quote.
|
|
144
|
-
if (isProviderBusy(error)) return first;
|
|
178
|
+
if (isProviderBusy(error)) return only(first);
|
|
179
|
+
}
|
|
180
|
+
if (movedLine && isUsVenue(first.exchange)) {
|
|
181
|
+
const symbol = first.result.symbol.toUpperCase();
|
|
182
|
+
const moved = await movedLine({ symbol, exchange: first.exchange, quote, name: first.result.name?.trim() || null });
|
|
183
|
+
if (moved && moved.symbol.toUpperCase() !== symbol) return { line: first, moved: { ...moved, symbol: moved.symbol.toUpperCase() } };
|
|
145
184
|
}
|
|
185
|
+
if (hasPrice(quote)) return only(first);
|
|
146
186
|
const keys = new Set([listingKey(first)]);
|
|
147
187
|
const next: T[] = [];
|
|
148
188
|
for (const line of lines) {
|
|
@@ -151,11 +191,11 @@ async function firstQuoted<T extends RankedListing>(dataProvider: Partial<Pick<D
|
|
|
151
191
|
keys.add(listingKey(line));
|
|
152
192
|
next.push(line);
|
|
153
193
|
}
|
|
154
|
-
const priced = await Promise.all(next.map((line) =>
|
|
194
|
+
const priced = await Promise.all(next.map((line) => quoteOf(line).then(hasPrice).catch(() => {
|
|
155
195
|
// Intentional ignore: a line whose quote fails is a line with no quote.
|
|
156
196
|
return false;
|
|
157
197
|
})));
|
|
158
|
-
return next.find((_, index) => priced[index]) ?? first;
|
|
198
|
+
return only(next.find((_, index) => priced[index]) ?? first);
|
|
159
199
|
}
|
|
160
200
|
|
|
161
201
|
/** A name as two venues may spell it alike: "SIEMENS LTD" for Siemens Ltd. */
|
|
@@ -99,12 +99,53 @@ export function companyNameMatch(typed: string, name: string | null | undefined)
|
|
|
99
99
|
return { extra: words.size - matched.size, abbreviated };
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
// Words one fund's venues name it with or without: "iShares MSCI Japan Index Fund" in New York is "iShares MSCI Japan ETF" in Mexico.
|
|
103
|
+
const FUND_NOISE = new Set(["etf", "etfs", "fund", "funds", "index", "trust", "ucits"]);
|
|
104
|
+
|
|
105
|
+
function instrumentWords(name: string | null | undefined): Set<string> {
|
|
106
|
+
return new Set([...companyWords(name)].filter((word) => !FUND_NOISE.has(word)));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Whether two venues' names can be one instrument's: one company's
|
|
111
|
+
* (`sameCompany`), or one fund's however a venue words it, with or without
|
|
112
|
+
* "ETF", "Index Fund" or "Trust" ("Technology Select Sector SPDR Fund" in
|
|
113
|
+
* Buenos Aires is State Street's ETF in New York). Two names that are not are
|
|
114
|
+
* another company's or another fund's ("W.A.G Payment Solutions" is not the
|
|
115
|
+
* iShares fund, nor "UBS MSCI India" iShares' India fund). An empty name
|
|
116
|
+
* matches none.
|
|
117
|
+
*/
|
|
118
|
+
export function sameListedName(left: string | null | undefined, right: string | null | undefined): boolean {
|
|
119
|
+
if (sameCompany(left, right)) return true;
|
|
120
|
+
const a = instrumentWords(left);
|
|
121
|
+
const b = instrumentWords(right);
|
|
122
|
+
if (a.size === 0 || b.size === 0) return false;
|
|
123
|
+
const [fewer, more] = a.size <= b.size ? [a, b] : [b, a];
|
|
124
|
+
return [...fewer].every((word) => more.has(word));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Whether a venue names its line a depositary receipt ("Toyota Motor
|
|
129
|
+
* Corporation CEDEAR", "SS SPDR S&P 500 ETF Trust - CEDEAR"): a receipt of a
|
|
130
|
+
* line elsewhere, whose short form of that line's name says little of whose it is.
|
|
131
|
+
*/
|
|
132
|
+
export function namesDepositaryReceipt(name: string | null | undefined): boolean {
|
|
133
|
+
return /\b(?:cedears?|adrs?|ads|cdrs?|cdis?|depositary)\b/i.test(name ?? "");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Items grouped by the company their names are (`same`, `sameCompany` unless
|
|
138
|
+
* given), in the order each company first appears. Unnamed items are left out.
|
|
139
|
+
*/
|
|
140
|
+
export function groupByCompany<T>(
|
|
141
|
+
items: readonly T[],
|
|
142
|
+
nameOf: (item: T) => string | null | undefined,
|
|
143
|
+
same: (left: string | null | undefined, right: string | null | undefined) => boolean = sameCompany,
|
|
144
|
+
): T[][] {
|
|
104
145
|
const groups: T[][] = [];
|
|
105
146
|
for (const item of items) {
|
|
106
147
|
if (companyWords(nameOf(item)).size === 0) continue;
|
|
107
|
-
const group = groups.find((members) => members.some((member) =>
|
|
148
|
+
const group = groups.find((members) => members.some((member) => same(nameOf(member), nameOf(item))));
|
|
108
149
|
if (group) group.push(item);
|
|
109
150
|
else groups.push([item]);
|
|
110
151
|
}
|
|
@@ -174,10 +215,15 @@ function nameWord(word: string, opening: boolean, domain: boolean): string {
|
|
|
174
215
|
* the way its source means it and comes back as it is.
|
|
175
216
|
*/
|
|
176
217
|
export function companyNameCase(name: string): string {
|
|
177
|
-
|
|
218
|
+
// A brand that opens in lower case before capitals (aTYR, iROBOT) is still a name in capitals: aTyr, iRobot.
|
|
219
|
+
const brand = /^\p{Ll}(?=\p{Lu}{2})/u;
|
|
220
|
+
const capitals = name.split(/\s+/).map((word) => word.replace(brand, "")).join(" ");
|
|
221
|
+
if (!/\p{Lu}/u.test(name) || capitals !== capitals.toUpperCase()) return name;
|
|
178
222
|
let words = 0;
|
|
179
223
|
return name.replace(/\S+/g, (token) => {
|
|
180
224
|
const opening = words++ === 0;
|
|
225
|
+
const lead = brand.exec(token)?.[0];
|
|
226
|
+
if (lead) return `${lead}${token.charAt(1)}${token.slice(2).toLowerCase()}`;
|
|
181
227
|
// A separator inside a word (Coca-Cola, AT&T, Amazon.com, McDonald's) cuts it into words of its own.
|
|
182
228
|
return token.split(/([-/.&,'\u2019]+)/).map((part, index, parts) => {
|
|
183
229
|
if (index % 2 === 1) return part;
|
|
@@ -45,3 +45,10 @@ export function indexRootSuggestion(symbol: string): string | null {
|
|
|
45
45
|
const root = indexRootFor(symbol);
|
|
46
46
|
return root ? `Did you mean ${root.symbol} (${root.name})?` : null;
|
|
47
47
|
}
|
|
48
|
+
|
|
49
|
+
/** Cboe's Treasury yield indices: 13-week bill, 5-, 10- and 30-year notes. Each level is a yield in percent. */
|
|
50
|
+
const YIELD_INDEX_SYMBOLS: ReadonlySet<string> = new Set(["^IRX", "^FVX", "^TNX", "^TYX"]);
|
|
51
|
+
|
|
52
|
+
export function isYieldIndexSymbol(symbol: string): boolean {
|
|
53
|
+
return YIELD_INDEX_SYMBOLS.has(symbol.trim().toUpperCase());
|
|
54
|
+
}
|
|
@@ -182,7 +182,7 @@ export function describeListingFate(
|
|
|
182
182
|
* ended long ago. Only such a line can be a company that moved away, so only
|
|
183
183
|
* its change lookup is waited for.
|
|
184
184
|
*/
|
|
185
|
-
function
|
|
185
|
+
export function lineMayHaveMoved(quote: Quote | null, now = Date.now()): boolean {
|
|
186
186
|
return !!quote && (isQuoteBeforeLatestSession(quote, now) || endedTradingDate(quote, now) != null);
|
|
187
187
|
}
|
|
188
188
|
|
|
@@ -206,7 +206,7 @@ export async function loadListingFate(
|
|
|
206
206
|
// A lookup started beside the command's request gets a short grace once that is ready, so a slow
|
|
207
207
|
// one never holds a symbol that trades. A line that stopped is waited for: only its company can
|
|
208
208
|
// have moved, and then the answer decides whether the command serves anything at all.
|
|
209
|
-
const changes = await lookup.settle(!options.changes ||
|
|
209
|
+
const changes = await lookup.settle(!options.changes || lineMayHaveMoved(quote) ? LATE_CHANGE_WAIT_MS : undefined);
|
|
210
210
|
return describeListingFate(listing, quote, changes);
|
|
211
211
|
} catch (error) {
|
|
212
212
|
logFailure("tickers-listing-fate", "Could not resolve listing fate")(error);
|
|
@@ -28,6 +28,17 @@ export interface ListingVenue {
|
|
|
28
28
|
/** Canonical code. */
|
|
29
29
|
exchange: string;
|
|
30
30
|
name: string;
|
|
31
|
+
/**
|
|
32
|
+
* The listing's own symbol there, when search lists it under another
|
|
33
|
+
* spelling than the one asked for: BP for `BP.` (London's code for it).
|
|
34
|
+
*/
|
|
35
|
+
symbol?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The venue's own symbol, when it is not `asked` (ignoring case). */
|
|
39
|
+
function ownSymbol(listed: string | undefined, asked: string): { symbol: string } | Record<string, never> {
|
|
40
|
+
const symbol = listed?.trim().toUpperCase();
|
|
41
|
+
return symbol && symbol !== asked.trim().toUpperCase() ? { symbol } : {};
|
|
31
42
|
}
|
|
32
43
|
|
|
33
44
|
/**
|
|
@@ -50,14 +61,15 @@ export async function listingVenues(symbol: string, { store, dataProvider }: Lis
|
|
|
50
61
|
: {
|
|
51
62
|
exchange: resolved.result.exchange === "SMART" ? resolved.result.primaryExchange ?? "" : resolved.result.exchange || resolved.result.primaryExchange || "",
|
|
52
63
|
name: resolved.result.name,
|
|
64
|
+
...ownSymbol(parsePublicTickerKey(resolved.result.symbol).symbol, symbol),
|
|
53
65
|
};
|
|
54
|
-
venues = only?.exchange ? [{ exchange: canonicalExchange(only.exchange)
|
|
66
|
+
venues = only?.exchange ? [{ ...only, exchange: canonicalExchange(only.exchange) }] : [];
|
|
55
67
|
} catch (error) {
|
|
56
68
|
// One listing settles the picker; several are its choices.
|
|
57
69
|
if (!(error instanceof AmbiguousTickerError) || error instanceof AmbiguousContractError) return [];
|
|
58
70
|
venues = error.listings.flatMap((listing) => {
|
|
59
|
-
const
|
|
60
|
-
return exchange ? [{ exchange, name: error.listingNames[listing] ?? "" }] : [];
|
|
71
|
+
const key = parsePublicTickerKey(listing);
|
|
72
|
+
return key.exchange ? [{ exchange: key.exchange, name: error.listingNames[listing] ?? "", ...ownSymbol(key.symbol, symbol) }] : [];
|
|
61
73
|
});
|
|
62
74
|
}
|
|
63
75
|
return venues.filter((venue, index) => venues.findIndex((other) => other.exchange === venue.exchange) === index);
|
|
@@ -7,6 +7,7 @@ import { getListingSymbol, listingSuffixConflictsWithExchange, tickerHasListingS
|
|
|
7
7
|
import { parseOptionSymbol } from "../../utils/options";
|
|
8
8
|
import { resolveCurrencyUnit } from "../../utils/currency-units";
|
|
9
9
|
import { currencyPairSpelling } from "../../utils/currency-pair";
|
|
10
|
+
import { typedCoinPairListing } from "../../utils/crypto-pair";
|
|
10
11
|
import { assetClassMarketSymbol, parseAssetClassQuery } from "./asset-classes";
|
|
11
12
|
import { bareFuturesCode, futuresContractByCode } from "../../market-data/futures-board";
|
|
12
13
|
import { searchContractKey, searchInstrumentKey } from "./identity";
|
|
@@ -387,9 +388,12 @@ export function resolveTickerSearch(options: ResolveTickerSearchOptions): Promis
|
|
|
387
388
|
// JPY=X are EURUSD=X and USDJPY=X. USD-NGN and USDNGN, which a security could also be written as
|
|
388
389
|
// (MNT-USD is a coin), are the pair only when nothing is listed under those letters.
|
|
389
390
|
const pair = pickListing || future ? null : currencyPairSpelling(typed);
|
|
390
|
-
|
|
391
|
+
// A coin pair written as crypto exchanges write it (BTC/GBP, or BTC/GBP:KRAKEN) is the crypto venue's BTC-GBP,
|
|
392
|
+
// and what was typed when the venue has no such coin.
|
|
393
|
+
const coin = pickListing || future || pair || options.tickers.has(typed) ? null : typedCoinPairListing(typed);
|
|
394
|
+
const symbol = future?.symbol ?? coin?.symbol ?? (pair && !pair.ambiguous ? pair.symbol : typed);
|
|
391
395
|
const literal = resolveTickerSymbol(symbol, options, pickListing ? requested : null);
|
|
392
|
-
const alternatives = pickListing ? [] : [...dottedVenueQueries(symbol), ...pair?.ambiguous ? [pair.symbol] : []];
|
|
396
|
+
const alternatives = pickListing ? [] : [...dottedVenueQueries(symbol), ...pair?.ambiguous ? [pair.symbol] : [], ...coin ? [typed] : []];
|
|
393
397
|
// A listing suffix search does not carry (CEZ.PR) is read from its own quote, as `quote` reads it.
|
|
394
398
|
const quoted = !pickListing && tickerHasListingSuffix(symbol) && !parsePublicTickerKey(symbol).exchange;
|
|
395
399
|
// Most input has no venue code to retry: hand back the literal lookup
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
} from "./types";
|
|
5
5
|
import { getListingSymbol, tickerHasListingSuffix } from "../../sources/listing-symbols";
|
|
6
6
|
import { currencyPairCode } from "../../utils/currency-pair";
|
|
7
|
+
import { typedCoinPairListing } from "../../utils/crypto-pair";
|
|
7
8
|
import { canonicalExchange, exchangeCodeVenues, isUsListingExchange, parsePublicTickerKey, typedExchangeCode } from "../../utils/exchanges";
|
|
8
9
|
import { assetClassKeeps, assetClassMarketSymbol, instrumentClassCode, parseAssetClassQuery } from "./asset-classes";
|
|
9
10
|
import { futuresContractByCode } from "../../market-data/futures-board";
|
|
@@ -322,9 +323,12 @@ export function rankTickerSearchItems<T extends Pick<TickerSearchRankableItem, "
|
|
|
322
323
|
.map(({ item }) => getTickerSearchListingKey(item)),
|
|
323
324
|
);
|
|
324
325
|
|
|
326
|
+
// BTC/GBP reads as a currency pair's spelling, and is the crypto venue's BTC-GBP.
|
|
327
|
+
const coinListing = typedCoinPairListing(query)?.symbol;
|
|
325
328
|
const filtered = ranked.filter(({ item, textScore }) => {
|
|
326
329
|
if (textScore <= 0) return false;
|
|
327
|
-
if (isExplicitMarketSymbol(query) && !isExplicitMarketSymbol(item.symbol || item.label)
|
|
330
|
+
if (isExplicitMarketSymbol(query) && !isExplicitMarketSymbol(item.symbol || item.label)
|
|
331
|
+
&& parsePublicTickerKey(item.symbol || item.label).symbol !== coinListing) return false;
|
|
328
332
|
if (item.kind !== "search") return true;
|
|
329
333
|
return !matchedLocalListings.has(getTickerSearchListingKey(item));
|
|
330
334
|
});
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { canonicalExchange, parsePublicTickerKey, publicTickerKey } from "../../utils/exchanges";
|
|
12
12
|
import { scopedBrokerContractIdentityKey } from "../../utils/instrument-identity";
|
|
13
13
|
import { listingSuffixVenue } from "../../sources/listing-symbols";
|
|
14
|
+
import { coinPairQuoteCurrency, exchangeLineCoinListing, isCoinRowType } from "../../utils/crypto-pair";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* The key a result is saved under when its symbol is already saved for
|
|
@@ -36,6 +37,18 @@ export async function upsertTickerFromSearchResult(
|
|
|
36
37
|
result: InstrumentSearchResult,
|
|
37
38
|
options: { tickerSymbol?: string } = {},
|
|
38
39
|
): Promise<{ ticker: TickerRecord; created: boolean }> {
|
|
40
|
+
// A crypto exchange's line of a coin pair (BTC/GBP on COINBASE PRO), which no source quotes, is saved as the
|
|
41
|
+
// crypto venue's listing of the pair (BTC-GBP on CCC), in the currency the pair is quoted in.
|
|
42
|
+
const coin = result.brokerContract ? null
|
|
43
|
+
: exchangeLineCoinListing(getSearchResultSymbol(result), listingVenue(result), { coinRow: isCoinRowType(result.type) });
|
|
44
|
+
if (coin) {
|
|
45
|
+
result = { ...result, symbol: coin.symbol, exchange: coin.exchange, primaryExchange: undefined, currency: coin.currency };
|
|
46
|
+
if (options.tickerSymbol) options = { ...options, tickerSymbol: publicTickerKey(coin.symbol, coin.exchange) };
|
|
47
|
+
} else if (!result.currency && !result.brokerContract && canonicalExchange(listingVenue(result)) === "CCC") {
|
|
48
|
+
// The venue's own row of a pair (BTC-GBP) often comes without its currency, which the pair names.
|
|
49
|
+
const currency = coinPairQuoteCurrency(getSearchResultSymbol(result));
|
|
50
|
+
if (currency) result = { ...result, currency };
|
|
51
|
+
}
|
|
39
52
|
let symbol = options.tickerSymbol ?? getSearchResultSymbol(result);
|
|
40
53
|
// A vendor's suffixed symbol (GARAN.IS) is saved as the listing it is (GARAN on BIST), under the key
|
|
41
54
|
// GARAN:BIST is saved under, so one listing is one row. A key the caller names is kept as given.
|
|
@@ -412,6 +412,25 @@ export interface NearMissListing {
|
|
|
412
412
|
symbol: string;
|
|
413
413
|
}
|
|
414
414
|
|
|
415
|
+
/**
|
|
416
|
+
* `BP.`, `RR.`: London's code for a company with a two-letter symbol, which
|
|
417
|
+
* the exchange writes with a dot to make three. No other venue writes a
|
|
418
|
+
* trailing dot, though one can trail a US ticker by slip (`GE.`).
|
|
419
|
+
*/
|
|
420
|
+
const LONDON_DOTTED = /^([A-Z]{2})\.$/;
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* A two-letter symbol written with London's trailing dot (`BP.`): what was
|
|
424
|
+
* typed and the symbol (BP). Whether it is London's line (BP:LSE) or a US
|
|
425
|
+
* ticker with a stray dot (`GE.`) is for where London lists it to say
|
|
426
|
+
* (`resolveCliListing`). Null for anything else.
|
|
427
|
+
*/
|
|
428
|
+
export function readLondonDottedSymbol(raw: string): NearMissListing | null {
|
|
429
|
+
const typed = raw.trim();
|
|
430
|
+
const london = LONDON_DOTTED.exec(typed.toUpperCase());
|
|
431
|
+
return london ? { typed, symbol: london[1]! } : null;
|
|
432
|
+
}
|
|
433
|
+
|
|
415
434
|
/** `BRK/B`: a US share class after a slash, as a broker writes it. One letter before the slash is a ratio or an abbreviation (P/E, N/A). */
|
|
416
435
|
const SLASH_CLASS = /^([A-Z]{2,5})\/([A-Z])$/;
|
|
417
436
|
/** `700.HK`: a Hong Kong code without its leading zeros. */
|