@gloom-sh/terminal 0.18.5 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +140 -7
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +17 -6
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/index.ts +11 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +1 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/research-activity.ts +3 -2
- package/src/api-client/types.ts +70 -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/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 +72 -26
- package/src/cli/commands/options-chain.ts +48 -7
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +61 -12
- package/src/cli/commands/search.ts +21 -1
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +55 -34
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +8 -0
- package/src/cli/glossary.ts +25 -2
- package/src/cli/history-rows.ts +57 -9
- package/src/cli/index.ts +2 -0
- package/src/cli/listing-arg.ts +180 -52
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +3 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +86 -3
- package/src/cli/pane-functions/function-help.ts +13 -10
- package/src/cli/pane-functions/headless.ts +17 -7
- package/src/cli/pane-functions/index.ts +49 -14
- 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/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/pane-templates/items.ts +3 -2
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- 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 +4 -1
- package/src/components/ui/choice-dialog.tsx +23 -18
- package/src/components/ui/choice-groups.ts +18 -0
- package/src/components/ui/data-table/opentui/index.tsx +9 -7
- package/src/components/ui/data-table/opentui/model.ts +26 -1
- package/src/components/ui/data-table/types.ts +2 -0
- package/src/components/ui/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/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 +71 -137
- package/src/i18n/es.ts +18 -2
- package/src/i18n/ja.ts +18 -2
- package/src/i18n/ko.ts +18 -2
- package/src/i18n/zh-cn.ts +18 -2
- package/src/i18n/zh-tw.ts +18 -2
- package/src/market-data/depositary-pair.ts +2 -2
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +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/news/aggregator.ts +113 -9
- package/src/news/cloud-session.ts +21 -0
- package/src/news/hooks.ts +1 -0
- package/src/news/news-model.ts +1 -0
- package/src/news/types.ts +11 -0
- package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +336 -77
- package/src/plugins/builtin/alerts/command.ts +25 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
- 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 +24 -6
- 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/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/headless-schema.ts +7 -1
- package/src/plugins/builtin/chart-composer/headless.ts +6 -1
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- 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/pane-links/commands.ts +7 -1
- package/src/plugins/builtin/clinical-trials/model.ts +0 -9
- package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- package/src/plugins/builtin/company-attention/building.tsx +5 -1
- package/src/plugins/builtin/company-attention/client.ts +8 -9
- package/src/plugins/builtin/company-attention/evidence.ts +4 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +9 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/correlation/headless.ts +13 -5
- package/src/plugins/builtin/correlation/history.ts +22 -0
- package/src/plugins/builtin/cot/client.ts +22 -18
- package/src/plugins/builtin/cot/headless.ts +15 -10
- package/src/plugins/builtin/cot/model.ts +14 -1
- package/src/plugins/builtin/cot/pane.tsx +22 -14
- package/src/plugins/builtin/credit-boards/client.ts +6 -10
- package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
- package/src/plugins/builtin/credit-conditions/model.ts +4 -0
- package/src/plugins/builtin/credit-conditions/series.ts +3 -1
- package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
- package/src/plugins/builtin/credit-documents/export.ts +173 -0
- package/src/plugins/builtin/credit-documents/headless.ts +7 -7
- package/src/plugins/builtin/credit-documents/model.ts +104 -14
- package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
- package/src/plugins/builtin/custom-view/sources.ts +2 -0
- package/src/plugins/builtin/dividend-yield/client.ts +131 -23
- package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
- package/src/plugins/builtin/dividend-yield/model.ts +8 -5
- package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
- package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
- package/src/plugins/builtin/dividend-yield/types.ts +20 -2
- package/src/plugins/builtin/dividend-yield/view.ts +30 -2
- package/src/plugins/builtin/docs/targets.ts +5 -1
- package/src/plugins/builtin/earnings/board-model.ts +16 -15
- package/src/plugins/builtin/earnings/board.tsx +62 -71
- package/src/plugins/builtin/earnings/headless.ts +40 -4
- package/src/plugins/builtin/earnings/scope.ts +31 -43
- package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
- package/src/plugins/builtin/econ/types.ts +2 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +6 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +36 -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 +1 -0
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +18 -1
- package/src/plugins/builtin/iv-history/client.ts +52 -11
- package/src/plugins/builtin/iv-history/format.ts +36 -23
- package/src/plugins/builtin/iv-history/headless.ts +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/release-table.ts +2 -2
- package/src/plugins/builtin/market-heatmap/data.ts +8 -33
- package/src/plugins/builtin/market-movers/client.ts +25 -7
- package/src/plugins/builtin/market-movers/headless.ts +32 -12
- package/src/plugins/builtin/market-movers/index.tsx +26 -14
- package/src/plugins/builtin/market-movers/min-float.ts +74 -0
- package/src/plugins/builtin/market-movers/model.ts +4 -1
- package/src/plugins/builtin/market-movers/screener.ts +14 -0
- package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
- package/src/plugins/builtin/market-movers/session-table.ts +4 -3
- package/src/plugins/builtin/market-movers/table.ts +17 -5
- package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
- package/src/plugins/builtin/news/index.tsx +1 -0
- package/src/plugins/builtin/news/wire/categories.ts +19 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
- package/src/plugins/builtin/openfda/client.ts +14 -2
- package/src/plugins/builtin/openfda/index.tsx +3 -2
- package/src/plugins/builtin/openfda/model.ts +9 -0
- package/src/plugins/builtin/openfda/pane.tsx +42 -6
- package/src/plugins/builtin/options/analytics.ts +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 +4 -1
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/client.ts +18 -4
- package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
- package/src/plugins/builtin/relative-rotation/model.ts +16 -40
- package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
- package/src/plugins/builtin/research/event-model.ts +10 -8
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +7 -5
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
- package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
- package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
- package/src/plugins/builtin/scanner/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 +27 -3
- 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 +2 -1
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
- package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
- package/src/plugins/builtin/shared/earnings-report.ts +2 -2
- package/src/plugins/builtin/shared/job-posting.ts +1 -0
- package/src/plugins/builtin/shared/members-menu.tsx +3 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
- package/src/plugins/builtin/shared/report-freshness.ts +3 -2
- package/src/plugins/builtin/shared/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/ticker-detail/data-panes/historical-prices.tsx +43 -18
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
- package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
- package/src/plugins/builtin/time-sales/pane.tsx +31 -19
- package/src/plugins/builtin/treasury-daily/client.ts +41 -5
- package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
- package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
- package/src/plugins/builtin/treasury-daily/model.ts +50 -3
- package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
- package/src/plugins/builtin/vol-surface/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 +2 -2
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/marketplace/model.ts +18 -5
- 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/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 +12 -0
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/shared/app-name.ts +13 -0
- package/src/renderers/electrobun/shared/protocol.ts +10 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +3 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- package/src/series/registry.ts +1 -0
- package/src/series/types.ts +6 -0
- package/src/series/validate.ts +1 -0
- package/src/sources/gloomberb-cloud/index.ts +1 -0
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/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.ts +44 -3
- package/src/tickers/listing-venues.ts +15 -3
- package/src/tickers/terminal-codes.ts +19 -0
- 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/financials.ts +32 -1
- package/src/types/headless.ts +21 -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 +44 -0
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +24 -3
- package/src/utils/fx-market-hours.ts +26 -3
- package/src/utils/main-thread-stall.ts +35 -2
- package/src/utils/market-capitalization.ts +24 -1
- package/src/utils/price-history.ts +7 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
package/src/cli/history-rows.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { classifyInstrumentType } from "../tickers/instrument-kind";
|
|
13
13
|
import { isRoundTheClockCoin } from "../utils/crypto-pair";
|
|
14
14
|
import { currencyPairCode } from "../utils/currency-pair";
|
|
15
|
-
import { currencyUnitLabel } from "../utils/currency-units";
|
|
15
|
+
import { currencyUnitLabel, resolveCurrencyUnit } from "../utils/currency-units";
|
|
16
16
|
import { getPricePointTimestamp } from "../utils/price-history";
|
|
17
17
|
import { latestTradeIndex, missingVolumeNotice, pricePointIntegrityFlags, pricePointValues } from "../utils/price-history-integrity";
|
|
18
18
|
import { formatUtcTime } from "../utils/utc-time";
|
|
@@ -109,6 +109,42 @@ export function cleanFloat32Price(value: number): number {
|
|
|
109
109
|
return value;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* The finest step a venue that quotes in a sub-unit prices in, in decimals of
|
|
114
|
+
* that unit: a ten-thousandth of a penny, the smallest tick London's tick
|
|
115
|
+
* tables use (for its lowest-priced shares).
|
|
116
|
+
*/
|
|
117
|
+
const SUB_UNIT_TICK_DECIMALS = 4;
|
|
118
|
+
/** The decimals of that sub-unit a table of bars shows: a tenth of a penny, 638.5p as 6.385. */
|
|
119
|
+
const SUB_UNIT_SHOWN_DECIMALS = 1;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* A bar's price as the decimal it is (`cleanFloat32Price`). A price served in
|
|
123
|
+
* a sub-unit and divided into its currency (`divisor` 100: a London line's
|
|
124
|
+
* pence, as pounds) carries the float32 noise of the pence it was quoted in
|
|
125
|
+
* (623.59998p is 6.2359998), so it is cleaned in pence, kept to the venue's
|
|
126
|
+
* finest tick there, then divided: 6.236.
|
|
127
|
+
*/
|
|
128
|
+
export function cleanBarPrice(value: number, divisor = 1): number {
|
|
129
|
+
if (!(divisor > 1) || !Number.isFinite(divisor) || !Number.isFinite(value) || value === 0) return cleanFloat32Price(value);
|
|
130
|
+
const subUnit = cleanFloat32Price(value * divisor);
|
|
131
|
+
const places = Math.min(SUB_UNIT_TICK_DECIMALS, decimalPlaces(subUnit));
|
|
132
|
+
const unitPlaces = Math.round(Math.log10(divisor));
|
|
133
|
+
return Number((Number(subUnit.toFixed(places)) / divisor).toFixed(places + unitPlaces));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* How many of the unit a quote's prices were served in make one of the
|
|
138
|
+
* currency it shows them in: 100 for a London line priced in pence and shown
|
|
139
|
+
* in pounds, 1 for any other. A quote in a sub-unit itself (`GBp`) shows the
|
|
140
|
+
* prices as served: they are cleaned as they are.
|
|
141
|
+
*/
|
|
142
|
+
export function quotedPriceDivisor(quote: { currency?: string; providerPriceDivisor?: number } | null | undefined): number {
|
|
143
|
+
const divisor = quote?.providerPriceDivisor;
|
|
144
|
+
if (resolveCurrencyUnit(quote?.currency).divisor > 1) return 1;
|
|
145
|
+
return divisor != null && Number.isFinite(divisor) && divisor > 1 ? divisor : 1;
|
|
146
|
+
}
|
|
147
|
+
|
|
112
148
|
function isUtcMidnight(time: number): boolean {
|
|
113
149
|
return new Date(time).toISOString().endsWith("T00:00:00.000Z");
|
|
114
150
|
}
|
|
@@ -229,6 +265,8 @@ export function historyRows(
|
|
|
229
265
|
currency: string | null = null,
|
|
230
266
|
unit: string | null = null,
|
|
231
267
|
volume: HistoryVolume | null = null,
|
|
268
|
+
/** The listing's `quotedPriceDivisor`: prices served in pence are cleaned in pence. */
|
|
269
|
+
priceDivisor = 1,
|
|
232
270
|
): HistoryRow[] {
|
|
233
271
|
const times = points.map(getPricePointTimestamp);
|
|
234
272
|
const intraday = resolution ? isIntradayResolution(resolution) : looksIntraday(times);
|
|
@@ -240,7 +278,7 @@ export function historyRows(
|
|
|
240
278
|
const iso = Number.isFinite(time) ? new Date(time).toISOString() : "";
|
|
241
279
|
const values = pricePointValues(point);
|
|
242
280
|
const flags = pricePointIntegrityFlags(point);
|
|
243
|
-
const price = (value: number | null) => value == null ? null :
|
|
281
|
+
const price = (value: number | null) => value == null ? null : cleanBarPrice(value, priceDivisor);
|
|
244
282
|
return {
|
|
245
283
|
date: intraday ? iso.replace(".000Z", "Z") : iso.slice(0, 10),
|
|
246
284
|
open: price(values.open),
|
|
@@ -440,8 +478,14 @@ function neededDecimals(value: number, cap: number): number {
|
|
|
440
478
|
* row. A known asset kind caps it with the app's price display rules; without
|
|
441
479
|
* one, the largest price sets a seven significant digit ceiling (BTC keeps
|
|
442
480
|
* cents, FX keeps pips, sub-cent coins keep their digits). Cents at least above 1.
|
|
481
|
+
* Prices served in a sub-unit (`priceDivisor` 100, a London line's pence) may
|
|
482
|
+
* keep a tenth of it too: 6.385 for 638.5p. Finer ticks stay in the export.
|
|
443
483
|
*/
|
|
444
|
-
export function historyPriceDecimals(
|
|
484
|
+
export function historyPriceDecimals(
|
|
485
|
+
rows: readonly Pick<HistoryRow, typeof PRICE_KEYS[number]>[],
|
|
486
|
+
assetCategory?: string,
|
|
487
|
+
priceDivisor = 1,
|
|
488
|
+
): number {
|
|
445
489
|
const values = rows.flatMap((row) => PRICE_KEYS.flatMap((key) => {
|
|
446
490
|
const value = row[key];
|
|
447
491
|
return value != null && Number.isFinite(value) && value !== 0 ? [Math.abs(value)] : [];
|
|
@@ -450,9 +494,12 @@ export function historyPriceDecimals(rows: readonly Pick<HistoryRow, typeof PRIC
|
|
|
450
494
|
const largest = values.reduce((max, value) => Math.max(max, value), 0);
|
|
451
495
|
const magnitude = Math.floor(Math.log10(largest));
|
|
452
496
|
const significantCap = largest >= 1 ? Math.max(2, 6 - magnitude) : 6 - magnitude;
|
|
453
|
-
|
|
497
|
+
// A line priced in pence is a share or a fund on its exchange, whatever kind its row was saved as.
|
|
498
|
+
const subUnit = priceDivisor > 1;
|
|
499
|
+
const kind = subUnit && resolveAssetDisplayKind({ assetCategory }) === "other" ? "EQUITY" : assetCategory;
|
|
500
|
+
const cap = resolveAssetDisplayKind({ assetCategory: kind }) === "other"
|
|
454
501
|
? significantCap
|
|
455
|
-
: Math.min(significantCap, marketPriceFractionDigitCeiling(largest, { assetCategory }));
|
|
502
|
+
: Math.min(significantCap, marketPriceFractionDigitCeiling(largest, { assetCategory: kind }) + (subUnit ? SUB_UNIT_SHOWN_DECIMALS : 0));
|
|
456
503
|
const needed = values.map((value) => neededDecimals(value, cap)).sort((left, right) => left - right);
|
|
457
504
|
const decimals = needed[Math.floor(0.9 * (needed.length - 1))]!;
|
|
458
505
|
return largest >= 1 ? Math.max(2, decimals) : decimals;
|
|
@@ -470,13 +517,14 @@ interface BarPrices {
|
|
|
470
517
|
* The decimals one table of bars prints its prices with (`history`, HP's pane
|
|
471
518
|
* and `fn HP`): historyPriceDecimals over the float32-cleaned prices, so a
|
|
472
519
|
* feed's 4013.39990234 reads as the 4013.4 it is and the count follows the
|
|
473
|
-
* asset's display rules (two for gold, three for silver, cents for a US equity
|
|
520
|
+
* asset's display rules (two for gold, three for silver, cents for a US equity,
|
|
521
|
+
* and a London line's pence: 6.385 for 638.5p, with `priceDivisor` 100).
|
|
474
522
|
*/
|
|
475
|
-
export function barPriceDecimals(bars: readonly BarPrices[], assetCategory?: string): number {
|
|
476
|
-
const clean = (value: number | null | undefined) => value == null ? null :
|
|
523
|
+
export function barPriceDecimals(bars: readonly BarPrices[], assetCategory?: string, priceDivisor = 1): number {
|
|
524
|
+
const clean = (value: number | null | undefined) => value == null ? null : cleanBarPrice(value, priceDivisor);
|
|
477
525
|
return historyPriceDecimals(bars.flatMap((bar) => bar.close == null ? [] : [{
|
|
478
526
|
date: "", open: clean(bar.open), high: clean(bar.high), low: clean(bar.low), close: clean(bar.close)!, volume: null,
|
|
479
|
-
}]), assetCategory);
|
|
527
|
+
}]), assetCategory, priceDivisor);
|
|
480
528
|
}
|
|
481
529
|
|
|
482
530
|
/** A price at the table's decimal count (barPriceDecimals), within `maxWidth` when the pane gives one. */
|
package/src/cli/index.ts
CHANGED
|
@@ -179,6 +179,7 @@ export function createCoreCliCommands(
|
|
|
179
179
|
{ flags: "--<option> <value>", description: "A function setting; gloom catalog <function> lists them" },
|
|
180
180
|
{ flags: "--require-bot-safe", description: "Fail unless the function has a verified, complete report" },
|
|
181
181
|
{ flags: "--explain", description: "Follow the report with what its terms mean (text and JSON; CSV keeps the rows)" },
|
|
182
|
+
{ flags: "--output <path>, --out <path>", description: "Write the report to this file, in the format asked for, and print where it went" },
|
|
182
183
|
{
|
|
183
184
|
...TABLE_SECTION_OPTION,
|
|
184
185
|
description: `${TABLE_SECTION_OPTION.description} (CALLS keeps --section for the transcript part)`,
|
|
@@ -190,6 +191,7 @@ export function createCoreCliCommands(
|
|
|
190
191
|
"fn ANR SAN:EPA",
|
|
191
192
|
"fn CBR --json",
|
|
192
193
|
"fn WEI --csv --section europe > europe.csv",
|
|
194
|
+
"fn RV NEE,FSLR --csv --output rv.csv",
|
|
193
195
|
"fn 13F AAPL --view=ticker-holdings",
|
|
194
196
|
"fn OVME --spot 100 --strike 100 --days 30 --volatility 25",
|
|
195
197
|
"fn HIVG SPY --event fomc --offset -6 --lookback ALL --csv",
|
package/src/cli/listing-arg.ts
CHANGED
|
@@ -15,12 +15,12 @@ import {
|
|
|
15
15
|
type ListingVenue,
|
|
16
16
|
} from "../tickers/listing-venues";
|
|
17
17
|
import { findUsListingOfCompany, type UsListing } from "../tickers/us-listing";
|
|
18
|
-
import { findListingOfCompanyName, isCompanyNameInput, readTerminalCodeArg } from "../tickers/company-name-listing";
|
|
18
|
+
import { findListingOfCompanyName, isCompanyNameInput, readTerminalCodeArg, type CompanyNameListing } from "../tickers/company-name-listing";
|
|
19
19
|
import { movedAwayChange, startTickerChangeLookup, type MovedTickerChange } from "../tickers/ticker-changes";
|
|
20
20
|
import { loadListingFate, MovedSymbolError, type ListingFate } from "../tickers/listing-fate";
|
|
21
21
|
import { quotedListingName } from "../tickers/listing-name";
|
|
22
22
|
import { cliStyles } from "../utils/cli-output";
|
|
23
|
-
import { groupByCompany, sameCompany, shortCompanyName } from "../tickers/company-name";
|
|
23
|
+
import { groupByCompany, namesDepositaryReceipt, sameCompany, sameListedName, shortCompanyName } from "../tickers/company-name";
|
|
24
24
|
import {
|
|
25
25
|
canonicalExchange,
|
|
26
26
|
exchangeCodeVenues,
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
import { withMarketData } from "./scoped-context";
|
|
36
36
|
import { failWithFields } from "./errors";
|
|
37
37
|
import { stripStrayEdges } from "./symbol-args";
|
|
38
|
-
import { isNoProviderMessage, isProviderBusy } from "../sources/provider-errors";
|
|
38
|
+
import { isNoProviderMessage, isNoSourceAnswer, isProviderBusy } from "../sources/provider-errors";
|
|
39
39
|
import { dashedCoinPair, MAJOR_COIN_NAMES } from "../utils/crypto-pair";
|
|
40
40
|
import { boardCoinFor, primeBoardCoins } from "./board-coins";
|
|
41
41
|
import { knownGapMessage } from "./known-gaps";
|
|
@@ -49,10 +49,12 @@ import {
|
|
|
49
49
|
bloombergMarketHint,
|
|
50
50
|
endsInLegalForm,
|
|
51
51
|
isSearchOnlyVenue,
|
|
52
|
+
readLondonDottedSymbol,
|
|
52
53
|
readNearMissListing,
|
|
53
54
|
readTerminalListingCode,
|
|
54
55
|
terminalCodeReadAs,
|
|
55
56
|
unknownBloombergMarket,
|
|
57
|
+
type NearMissListing,
|
|
56
58
|
type TerminalListingCode,
|
|
57
59
|
} from "../tickers/terminal-codes";
|
|
58
60
|
import { listingAbroadVenue } from "../utils/sec";
|
|
@@ -338,6 +340,15 @@ export function isNoProviderError(error: unknown): boolean {
|
|
|
338
340
|
return isNoProviderMessage(error instanceof Error ? error.message : String(error ?? ""));
|
|
339
341
|
}
|
|
340
342
|
|
|
343
|
+
/**
|
|
344
|
+
* The router's answer that no source served a request because one failed (a
|
|
345
|
+
* timeout, a rate limit, a refused session) rather than missed: it says
|
|
346
|
+
* nothing about whether the symbol exists, unlike "Not a ticker".
|
|
347
|
+
*/
|
|
348
|
+
export function isUnsettledNoProvider(error: unknown): boolean {
|
|
349
|
+
return isNoProviderError(error) && !isNoSourceAnswer(error);
|
|
350
|
+
}
|
|
351
|
+
|
|
341
352
|
const MAX_OTHER_LISTINGS = 5;
|
|
342
353
|
|
|
343
354
|
type BareListing = Pick<CliListing, "symbol" | "exchange"> & Partial<Pick<CliListing, "saved">>;
|
|
@@ -359,33 +370,42 @@ function listsSeveralCompanies(venues: readonly ListingVenue[]): boolean {
|
|
|
359
370
|
return groupByCompany(venues, (venue) => venue.name).length > 1;
|
|
360
371
|
}
|
|
361
372
|
|
|
362
|
-
/**
|
|
373
|
+
/**
|
|
374
|
+
* Every other company or fund that lists the symbol, once, at its first venue
|
|
375
|
+
* in search order. A venue is another's only when its name says so: one with
|
|
376
|
+
* no name, or a depositary receipt named unlike the company (a CEDEAR's short
|
|
377
|
+
* form of its fund's name), is left out rather than guessed at, and one
|
|
378
|
+
* fund's venues are one fund however each words it (`sameListedName`).
|
|
379
|
+
*/
|
|
363
380
|
function otherCompanies(venues: readonly ListingVenue[], company: string): ListingVenue[] {
|
|
364
|
-
|
|
365
|
-
|
|
381
|
+
if (!company.trim()) return [];
|
|
382
|
+
const known = venues.filter((venue) => !namesDepositaryReceipt(venue.name) || sameListedName(venue.name, company));
|
|
383
|
+
return groupByCompany(known, (venue) => venue.name, sameListedName)
|
|
384
|
+
.filter((group) => !group.some((venue) => sameListedName(venue.name, company)))
|
|
366
385
|
.map((group) => group[0]!);
|
|
367
386
|
}
|
|
368
387
|
|
|
369
388
|
/**
|
|
370
|
-
* "CAR = Avis Budget Group, NASDAQ (
|
|
371
|
-
* symbol several companies list was read on, and where the
|
|
372
|
-
* Null when
|
|
373
|
-
* nothing.
|
|
389
|
+
* "CAR = Avis Budget Group, NASDAQ (different companies on XETRA, ASX)": the
|
|
390
|
+
* listing a bare symbol several companies list was read on, and where the
|
|
391
|
+
* others list it. Null when no other company is known to list it, so a symbol
|
|
392
|
+
* that means one thing (XLK, and its receipt in Buenos Aires) says nothing.
|
|
374
393
|
*/
|
|
375
394
|
export function listingChoiceNote(
|
|
376
395
|
symbol: string,
|
|
377
396
|
chosen: { exchange: string; name?: string | null },
|
|
378
397
|
venues: readonly ListingVenue[],
|
|
379
398
|
): string | null {
|
|
380
|
-
if (!chosen.exchange
|
|
399
|
+
if (!chosen.exchange) return null;
|
|
381
400
|
const chosenVenue = venues.find((venue) => venue.exchange === chosen.exchange);
|
|
382
401
|
const name = chosen.name?.trim() || chosenVenue?.name || "";
|
|
383
402
|
const others = otherCompanies(venues, chosenVenue?.name || name).map((venue) => venue.exchange)
|
|
384
403
|
.filter((exchange) => exchange !== chosen.exchange);
|
|
385
404
|
if (others.length === 0) return null;
|
|
386
405
|
const more = others.length - MAX_OTHER_LISTINGS;
|
|
387
|
-
const
|
|
388
|
-
|
|
406
|
+
const named = `${others.slice(0, MAX_OTHER_LISTINGS).join(", ")}${more > 0 ? ` and ${more} more: gloom search ${symbol}` : ""}`;
|
|
407
|
+
const which = others.length === 1 ? "a different company" : "different companies";
|
|
408
|
+
return `${symbol} = ${name ? `${shortCompanyName(name)}, ` : ""}${chosen.exchange} (${which} on ${named})`;
|
|
389
409
|
}
|
|
390
410
|
|
|
391
411
|
// Quotes of these never name a company's listing, so they are never a choice between companies.
|
|
@@ -409,34 +429,50 @@ export function listingReadNotes(listing: Pick<ListingArg, "readAs">): string[]
|
|
|
409
429
|
return listing.readAs ? [listing.readAs] : [];
|
|
410
430
|
}
|
|
411
431
|
|
|
432
|
+
/**
|
|
433
|
+
* The coin line for a bare coin ticker (SOL, ETH) a command read as a listing that is
|
|
434
|
+
* not the coin, from the quote it was read on (`notTheCoinNote`). A list, so a command can
|
|
435
|
+
* spread it into its warnings. A named listing (`ETH:ARCA`, `--exchange`) asked for that
|
|
436
|
+
* listing on purpose and gets none.
|
|
437
|
+
*/
|
|
438
|
+
export function coinListingNotes(
|
|
439
|
+
listing: Pick<CliListing, "symbol" | "exchange">,
|
|
440
|
+
quote: Pick<Quote, "name" | "instrumentType" | "stale" | "delisting"> | null | undefined,
|
|
441
|
+
): string[] {
|
|
442
|
+
const note = listing.exchange ? null : notTheCoinNote(listing.symbol, quote);
|
|
443
|
+
return note ? [note] : [];
|
|
444
|
+
}
|
|
445
|
+
|
|
412
446
|
/**
|
|
413
447
|
* `listingChoiceNote` for a listing a command resolved, from the quote it was
|
|
414
448
|
* read on: only a bare symbol went where the data service chose. A named
|
|
415
449
|
* listing whose code names several venues says which it was read as
|
|
416
|
-
* (`listingReadNotes`). A
|
|
450
|
+
* (`listingReadNotes`). A bare coin ticker read as another listing ends with
|
|
451
|
+
* the coin's pair (`coinListingNotes`). A list, so a command can spread it into
|
|
452
|
+
* its warnings.
|
|
417
453
|
*/
|
|
418
454
|
export async function listingChoiceNotes(
|
|
419
455
|
listing: Pick<CliListing, "symbol" | "exchange" | "saved" | "readAs">,
|
|
420
456
|
deps: ListingDeps,
|
|
421
|
-
quote: Pick<Quote, "name" | "listingExchangeName" | "exchangeName" | "instrumentType"> | null | undefined,
|
|
457
|
+
quote: Pick<Quote, "name" | "listingExchangeName" | "exchangeName" | "instrumentType" | "stale" | "delisting"> | null | undefined,
|
|
422
458
|
/** The venues `loadBareVenues` already started; looked up here when not given. */
|
|
423
459
|
venues?: Promise<ListingVenue[]> | null,
|
|
424
460
|
): Promise<string[]> {
|
|
425
|
-
|
|
426
|
-
if (!
|
|
461
|
+
const coin = coinListingNotes(listing, quote);
|
|
462
|
+
if (!isBareListing(listing)) return [...listingReadNotes(listing), ...coin];
|
|
463
|
+
if (!quote || UNLISTED_TYPES.has(quote.instrumentType?.trim().toUpperCase() ?? "")) return coin;
|
|
427
464
|
const exchange = canonicalExchange(quote.listingExchangeName || quote.exchangeName);
|
|
428
|
-
if (!exchange) return
|
|
465
|
+
if (!exchange) return coin;
|
|
429
466
|
const listed = await (venues ?? loadBareVenues(listing, deps) ?? []);
|
|
430
467
|
const note = listingChoiceNote(listing.symbol, { exchange, name: quote.name }, listed);
|
|
431
|
-
return note ? [note] : [];
|
|
468
|
+
return [...note ? [note] : [], ...coin];
|
|
432
469
|
}
|
|
433
470
|
|
|
434
471
|
/**
|
|
435
472
|
* `listingChoiceNotes` for a command with no quote of its own to read the
|
|
436
473
|
* listing from (`fn`): it loads the listing's quote, and only for a bare
|
|
437
474
|
* symbol, so a named or saved listing costs nothing. A bare coin ticker read
|
|
438
|
-
* as
|
|
439
|
-
* coin's pair.
|
|
475
|
+
* as another listing (BTC, the Grayscale trust) ends with the coin's pair.
|
|
440
476
|
*/
|
|
441
477
|
export async function quotedListingChoiceNotes(
|
|
442
478
|
listing: Pick<CliListing, "symbol" | "exchange" | "saved" | "request" | "readAs">,
|
|
@@ -444,9 +480,7 @@ export async function quotedListingChoiceNotes(
|
|
|
444
480
|
): Promise<string[]> {
|
|
445
481
|
const venues = loadBareVenues(listing, deps);
|
|
446
482
|
if (!venues) return listingReadNotes(listing);
|
|
447
|
-
|
|
448
|
-
const coin = notTheCoinNote(listing.symbol, quote);
|
|
449
|
-
return [...await listingChoiceNotes(listing, deps, quote, venues), ...coin ? [coin] : []];
|
|
483
|
+
return listingChoiceNotes(listing, deps, await loadListingQuote(deps.dataProvider, listing), venues);
|
|
450
484
|
}
|
|
451
485
|
|
|
452
486
|
/**
|
|
@@ -563,31 +597,43 @@ export function futuresTryLine(symbol: string, request: ListingDataRequest): str
|
|
|
563
597
|
return `${future.symbol} ${future.name} future${retry ? ` (try: ${retry})` : ""}`;
|
|
564
598
|
}
|
|
565
599
|
|
|
600
|
+
/** A venue's listing as its key names it: under its own symbol there (BP:LSE for `BP.`), else the one asked for. */
|
|
601
|
+
function venueKey(symbol: string, venue: Pick<ListingVenue, "exchange" | "symbol">): string {
|
|
602
|
+
return `${venue.symbol ?? symbol}:${venue.exchange}`;
|
|
603
|
+
}
|
|
604
|
+
|
|
566
605
|
function describeOtherListings(
|
|
567
606
|
listing: BareListing,
|
|
568
607
|
{ exchange, resolved, others, choose }: BareListingVenues,
|
|
569
608
|
request: ListingDataRequest,
|
|
609
|
+
/** Listings just asked that had none: named as having none, never offered to try. */
|
|
610
|
+
without: readonly ListingVenue[] = [],
|
|
570
611
|
): OtherListingsMessage | null {
|
|
571
612
|
const future = futuresTryLine(listing.symbol, request);
|
|
572
|
-
|
|
613
|
+
const offered = others.filter((venue) => !without.includes(venue));
|
|
614
|
+
if (offered.length === 0 && !future && without.length === 0) return null;
|
|
573
615
|
const name = resolved?.name || listing.saved?.metadata.name?.trim() || "";
|
|
574
|
-
const lines =
|
|
575
|
-
const key =
|
|
616
|
+
const lines = offered.slice(0, MAX_OTHER_LISTINGS).map((venue) => {
|
|
617
|
+
const key = venueKey(listing.symbol, venue);
|
|
576
618
|
const retry = retryCommand(request, key);
|
|
577
619
|
return `${key}${venue.name ? ` ${venue.name}` : ""}${retry ? ` (try: ${retry})` : ""}`;
|
|
578
620
|
});
|
|
579
|
-
if (
|
|
621
|
+
if (offered.length > MAX_OTHER_LISTINGS) lines.push(`and ${offered.length - MAX_OTHER_LISTINGS} more: gloom search ${listing.symbol}`);
|
|
580
622
|
if (future) lines.push(future);
|
|
581
|
-
|
|
623
|
+
// Every other listing was asked and had nothing: they are named as such, with nothing to try.
|
|
624
|
+
const heading = choose ? "Listings" : lines.length === 0 ? `No ${request.noun} on its other listings either` : "Other listings";
|
|
625
|
+
const named = lines.length === 0 ? without : offered;
|
|
626
|
+
const details = lines.length === 0 ? `${heading}: ${without.map((venue) => venueKey(listing.symbol, venue)).join(", ")}.`
|
|
627
|
+
: lines.length === 1 ? `${heading}: ${lines[0]}` : [`${heading}:`, ...lines.map((line) => ` ${line}`)].join("\n");
|
|
582
628
|
return {
|
|
583
629
|
// The symbol went to no venue, so none of them is said to lack the data.
|
|
584
630
|
message: choose
|
|
585
631
|
? `Choose an exchange for ${listing.symbol}.`
|
|
586
632
|
: `${listing.symbol} resolved to ${name ? `${name} (${exchange})` : exchange}, which has no ${request.noun}.`,
|
|
587
|
-
details
|
|
633
|
+
details,
|
|
588
634
|
heading,
|
|
589
|
-
listings: [...
|
|
590
|
-
choices:
|
|
635
|
+
listings: [...named.map((venue) => venueKey(listing.symbol, venue)), ...future ? [futuresContractByCode(listing.symbol)!.symbol] : []],
|
|
636
|
+
choices: offered.slice(0, MAX_OTHER_LISTINGS).map((venue) => ({ key: venueKey(listing.symbol, venue), name: venue.name || null })),
|
|
591
637
|
choose,
|
|
592
638
|
};
|
|
593
639
|
}
|
|
@@ -612,30 +658,44 @@ async function otherListingsMessage(
|
|
|
612
658
|
return venues && describeOtherListings(listing, venues, request);
|
|
613
659
|
}
|
|
614
660
|
|
|
615
|
-
/** The listing on one venue, as `SYM:EXCH` names it. */
|
|
616
|
-
function namedListing(listing: CliListing,
|
|
617
|
-
const
|
|
661
|
+
/** The listing on one venue, as `SYM:EXCH` names it, under the venue's own symbol when search lists one. */
|
|
662
|
+
function namedListing(listing: CliListing, venue: Pick<ListingVenue, "exchange" | "symbol">): CliListing {
|
|
663
|
+
const { exchange } = venue;
|
|
664
|
+
const symbol = venue.symbol ?? listing.symbol;
|
|
665
|
+
const key = `${symbol}:${exchange}`;
|
|
618
666
|
const saved = canonicalExchange(listing.saved?.metadata.exchange) === exchange ? listing.saved : null;
|
|
619
|
-
return { symbol
|
|
667
|
+
return { symbol, exchange, key, saved, request: { symbol: key, exchange } };
|
|
620
668
|
}
|
|
621
669
|
|
|
670
|
+
/**
|
|
671
|
+
* Each listing's quote, null where it has none; `undefined` where the request
|
|
672
|
+
* itself failed, which says nothing of whether it has one.
|
|
673
|
+
*/
|
|
622
674
|
async function quoteListings(
|
|
623
675
|
dataProvider: DataProvider,
|
|
624
676
|
listings: readonly CliListing[],
|
|
625
677
|
refresh: boolean,
|
|
626
678
|
datesChangeBasis = false,
|
|
627
|
-
): Promise<Array<Quote | null>> {
|
|
679
|
+
): Promise<Array<Quote | null | undefined>> {
|
|
628
680
|
if (listings.length === 0) return [];
|
|
629
681
|
const targets = listings.map((listing) => listing.request);
|
|
630
682
|
if (dataProvider.getQuotesBatch) {
|
|
631
683
|
const results = await dataProvider.getQuotesBatch(targets, { forceRefresh: refresh, ...(datesChangeBasis ? { datesChangeBasis } : {}) })
|
|
632
|
-
.catch(logFailure("cli.listing-arg", "Read listing quote batch failed",
|
|
633
|
-
|
|
684
|
+
.catch(logFailure("cli.listing-arg", "Read listing quote batch failed", null));
|
|
685
|
+
if (!results) return targets.map(() => undefined);
|
|
686
|
+
return targets.map((target, index) => {
|
|
687
|
+
const result = results.find((entry) => entry.target === target) ?? results[index];
|
|
688
|
+
// No source quotes it, which is an answer; any other failure is none.
|
|
689
|
+
return result?.quote ?? (result && (result.error == null || isNoProviderError(result.error)) ? null : undefined);
|
|
690
|
+
});
|
|
634
691
|
}
|
|
635
692
|
return Promise.all(targets.map((target) => dataProvider.getQuote(target.symbol, target.exchange, {
|
|
636
693
|
cacheMode: refresh ? "refresh" : "default",
|
|
637
694
|
...(datesChangeBasis ? { datesChangeBasis } : {}),
|
|
638
|
-
}).catch(
|
|
695
|
+
}).catch((error: unknown) => {
|
|
696
|
+
logFailure("cli.listing-arg", "Read listing quote failed")(error);
|
|
697
|
+
return isNoProviderError(error) ? null : undefined;
|
|
698
|
+
})));
|
|
639
699
|
}
|
|
640
700
|
|
|
641
701
|
export type BareListingQuote =
|
|
@@ -674,8 +734,8 @@ export async function quoteBareListing(
|
|
|
674
734
|
const other = describeOtherListings(listing, venues, request);
|
|
675
735
|
return other && { kind: "missing", ...other };
|
|
676
736
|
}
|
|
677
|
-
const first = [namedListing(listing, venues.exchange)];
|
|
678
|
-
const rest = venues.others.map((venue) => namedListing(listing, venue
|
|
737
|
+
const first = [namedListing(listing, venues.resolved ?? { exchange: venues.exchange })];
|
|
738
|
+
const rest = venues.others.map((venue) => namedListing(listing, venue));
|
|
679
739
|
let tried = first;
|
|
680
740
|
let quotes = await quoteListings(deps.dataProvider, first, refresh, datesChangeBasis);
|
|
681
741
|
if (!quotes[0] && rest.length > 0) {
|
|
@@ -684,7 +744,9 @@ export async function quoteBareListing(
|
|
|
684
744
|
}
|
|
685
745
|
const found = quotes.findIndex(Boolean);
|
|
686
746
|
if (found < 0) {
|
|
687
|
-
|
|
747
|
+
// A listing just asked that had no quote is no listing to try.
|
|
748
|
+
const without = venues.others.filter((_venue, index) => quotes[index + 1] === null);
|
|
749
|
+
const other = describeOtherListings(listing, venues, request, without);
|
|
688
750
|
return other && { kind: "missing", ...other };
|
|
689
751
|
}
|
|
690
752
|
const answer = tried[found]!;
|
|
@@ -697,7 +759,8 @@ export async function quoteBareListing(
|
|
|
697
759
|
kind: "quoted",
|
|
698
760
|
listing: answer,
|
|
699
761
|
quote: quotes[found]!,
|
|
700
|
-
|
|
762
|
+
// Under the venue's own symbol (BP:LSE for `BP.`), the note names the listing, not only its venue.
|
|
763
|
+
note: chosenListingNote(listing.symbol, answer.symbol === listing.symbol ? answer.exchange : answer.key, others, without),
|
|
701
764
|
};
|
|
702
765
|
}
|
|
703
766
|
|
|
@@ -708,6 +771,8 @@ export interface ReadListing {
|
|
|
708
771
|
exchange: string;
|
|
709
772
|
/** The listing's company, from its quote. */
|
|
710
773
|
name: string | null;
|
|
774
|
+
/** A bare coin ticker read as a listing that is not the coin (`coinListingNotes`); null otherwise. */
|
|
775
|
+
coinNote: string | null;
|
|
711
776
|
}
|
|
712
777
|
|
|
713
778
|
/**
|
|
@@ -728,10 +793,14 @@ export async function readBareListing(
|
|
|
728
793
|
const listing = await resolveCliListing(symbol, null, deps);
|
|
729
794
|
if (listing.exchange) return null;
|
|
730
795
|
const answer = await loadListingQuoteAnswer(deps.dataProvider, listing);
|
|
731
|
-
if (answer.quote)
|
|
796
|
+
if (answer.quote) {
|
|
797
|
+
return { symbol: listing.symbol, exchange: listing.request.exchange, name: answer.quote.name?.trim() || null, coinNote: coinListingNotes(listing, answer.quote)[0] ?? null };
|
|
798
|
+
}
|
|
732
799
|
if (!isNoProviderError(answer.error)) return null;
|
|
733
800
|
const other = await quoteBareListing(listing, deps, request, { refresh });
|
|
734
|
-
if (other?.kind === "quoted")
|
|
801
|
+
if (other?.kind === "quoted") {
|
|
802
|
+
return { symbol: listing.symbol, exchange: other.listing.exchange, name: other.quote.name?.trim() || null, coinNote: coinListingNotes(listing, other.quote)[0] ?? null };
|
|
803
|
+
}
|
|
735
804
|
if (other?.choose) throw new ListingArgError(other.message, other.details, other.choices);
|
|
736
805
|
return null;
|
|
737
806
|
}
|
|
@@ -1096,7 +1165,9 @@ export async function resolveCliListing(
|
|
|
1096
1165
|
const terminal = named ? null : read;
|
|
1097
1166
|
// "AAPL GP" is written as a code, with a market code no venue answers to and no company's name: one entry, never AAPL and GP.
|
|
1098
1167
|
if (!terminal && !named) await refuseUnknownMarketCode(typed, deps);
|
|
1099
|
-
|
|
1168
|
+
// `BP.` is London's code for BP: its London line where London lists it, and `GE.` a US ticker with a stray dot.
|
|
1169
|
+
const dotted = terminal || named || exchangeOption?.trim() ? null : readLondonDottedSymbol(typed);
|
|
1170
|
+
let parsed = named ?? (dotted ? await londonDottedListing(dotted, deps) : parseListingArg(raw, exchangeOption, { ...options, terminalCode: terminal }));
|
|
1100
1171
|
if (!parsed.symbol) throw new ListingArgError("Ticker symbol is required.");
|
|
1101
1172
|
// $VIX is Schwab's code for ^VIX, unless a security trades as VIX: $AAPL is a cashtag.
|
|
1102
1173
|
const index = named || parsed.exchange ? null : await readDollarIndexCode(raw, deps);
|
|
@@ -1140,6 +1211,21 @@ export async function resolveCliListing(
|
|
|
1140
1211
|
};
|
|
1141
1212
|
}
|
|
1142
1213
|
|
|
1214
|
+
/**
|
|
1215
|
+
* A two-letter symbol written with London's trailing dot (`BP.`, `RR.`) as its
|
|
1216
|
+
* London line, said as "Read BP. as BP:LSE.", where search lists it on LSE or
|
|
1217
|
+
* its quote there says it trades. Elsewhere the dot was a slip after the
|
|
1218
|
+
* ticker, dropped as a stray edge is (`GE.` is GE).
|
|
1219
|
+
*/
|
|
1220
|
+
async function londonDottedListing(dotted: NearMissListing, deps: ListingDeps): Promise<ListingArg> {
|
|
1221
|
+
const london = { symbol: dotted.symbol, exchange: "LSE" };
|
|
1222
|
+
const venues = await listingVenues(dotted.symbol, deps).catch(logFailure("cli.listing-arg", "Read listing venues failed", []));
|
|
1223
|
+
const listed = venues.some((venue) => venue.exchange === london.exchange) || await quotedOnVenue(london, deps);
|
|
1224
|
+
if (!listed) return parseListingArg(dotted.symbol);
|
|
1225
|
+
const key = `${london.symbol}:${london.exchange}`;
|
|
1226
|
+
return { ...parseListingArg(key), readAs: `Read ${dotted.typed} as ${key}.` };
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1143
1229
|
/**
|
|
1144
1230
|
* Fails a line written as a Bloomberg code whose market code no venue answers
|
|
1145
1231
|
* to (`AAPL GP`), suggesting the code of the symbol's own listing (`AAPL
|
|
@@ -1235,17 +1321,23 @@ const ONE_WORD_NAME = /^\p{L}[\p{L}\p{N}&'’.-]*$/u;
|
|
|
1235
1321
|
* for anything else, and when search names no company or is too busy to say.
|
|
1236
1322
|
*/
|
|
1237
1323
|
export async function oneWordCompanyName(typed: string, deps: Pick<ListingDeps, "dataProvider">): Promise<ListingArg | null> {
|
|
1324
|
+
const found = await findOneWordCompany(typed, deps);
|
|
1325
|
+
return found ? companyListingArg(typed, found) : null;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
/** The company `oneWordCompanyName` reads a word as, before it is read as a listing. */
|
|
1329
|
+
async function findOneWordCompany(typed: string, deps: Pick<ListingDeps, "dataProvider">): Promise<CompanyNameListing | null> {
|
|
1238
1330
|
const word = typed.trim();
|
|
1239
1331
|
if (!ONE_WORD_NAME.test(word) || knownGapMessage(word) || isMajorCoinName(word)) return null;
|
|
1240
|
-
const
|
|
1332
|
+
const found = await findCompanyName(word, deps, { oneWord: true }).catch((error: unknown) => {
|
|
1241
1333
|
if (!isProviderBusy(error)) throw error;
|
|
1242
1334
|
// Intentional ignore: a busy search says nothing about the name, so the word keeps its own "Not a ticker".
|
|
1243
1335
|
return null;
|
|
1244
1336
|
});
|
|
1245
|
-
if (!
|
|
1337
|
+
if (!found) return null;
|
|
1246
1338
|
// The board is read only for a word that names a company, and once for the run (`primeBoardCoins`).
|
|
1247
1339
|
if (/^[A-Za-z0-9]+$/.test(word)) await primeBoardCoins([`Not a ticker: ${word.toUpperCase()}.`]);
|
|
1248
|
-
return boardCoinFor(word) ? null :
|
|
1340
|
+
return boardCoinFor(word) ? null : found;
|
|
1249
1341
|
}
|
|
1250
1342
|
|
|
1251
1343
|
function isMajorCoinName(word: string): boolean {
|
|
@@ -1253,9 +1345,16 @@ function isMajorCoinName(word: string): boolean {
|
|
|
1253
1345
|
return [...MAJOR_COIN_NAMES.values()].some((name) => name.toLowerCase() === wanted);
|
|
1254
1346
|
}
|
|
1255
1347
|
|
|
1348
|
+
function findCompanyName(typed: string, deps: Pick<ListingDeps, "dataProvider">, options: { oneWord?: boolean } = {}): Promise<CompanyNameListing | null> {
|
|
1349
|
+
return findListingOfCompanyName(deps.dataProvider, typed, { meant: true, quoted: true, ...options });
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1256
1352
|
async function readCompanyName(typed: string, deps: Pick<ListingDeps, "dataProvider">, options: { oneWord?: boolean } = {}): Promise<ListingArg | null> {
|
|
1257
|
-
const found = await
|
|
1258
|
-
|
|
1353
|
+
const found = await findCompanyName(typed, deps, options);
|
|
1354
|
+
return found ? companyListingArg(typed, found) : null;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
function companyListingArg(typed: string, found: CompanyNameListing): ListingArg {
|
|
1259
1358
|
const key = `${found.symbol}:${found.exchange}`;
|
|
1260
1359
|
// A fund or an index carrying the name asks about the company's stock: "Did you mean JPM (JPMorgan Chase & Co.)?".
|
|
1261
1360
|
const meant = found.meant ? { symbol: found.meant.symbol, name: found.meant.name } : null;
|
|
@@ -1263,6 +1362,35 @@ async function readCompanyName(typed: string, deps: Pick<ListingDeps, "dataProvi
|
|
|
1263
1362
|
return { ...parseListingArg(key), readAs: `Read "${typed.trim()}" as ${key} (${found.name}).${question}`, ...meant ? { meant } : {} };
|
|
1264
1363
|
}
|
|
1265
1364
|
|
|
1365
|
+
/**
|
|
1366
|
+
* The listing search names for a company's name typed where a ticker goes,
|
|
1367
|
+
* as a sentence offers it: `PLTR (Palantir Technologies Inc.)`, a listing
|
|
1368
|
+
* abroad with its venue (`SIE:XETRA (Siemens AG)`), a fund or an index that
|
|
1369
|
+
* carries the name as the company's stock it means. Null when the words name
|
|
1370
|
+
* no company, search is busy, or the listing found is the word itself: a
|
|
1371
|
+
* real ticker is never a company's name. Never throws.
|
|
1372
|
+
*/
|
|
1373
|
+
export async function companyNameSuggestion(typed: string, deps: Pick<ListingDeps, "dataProvider">): Promise<{ symbol: string; name: string } | null> {
|
|
1374
|
+
const words = typed.trim().replace(/^\$/, "");
|
|
1375
|
+
if (!words) return null;
|
|
1376
|
+
try {
|
|
1377
|
+
const found = isCompanyNameInput(words) ? await findCompanyName(words, deps).catch(busyAsMiss) : await findOneWordCompany(words, deps);
|
|
1378
|
+
const listing = found?.meant ?? found;
|
|
1379
|
+
if (!listing || listing.symbol.toUpperCase() === words.toUpperCase()) return null;
|
|
1380
|
+
return { symbol: isUsListingExchange(listing.exchange) ? listing.symbol : `${listing.symbol}:${listing.exchange}`, name: listing.name };
|
|
1381
|
+
} catch (error) {
|
|
1382
|
+
logFailure("cli.listing-arg", "Search a company name for a suggestion failed")(error);
|
|
1383
|
+
return null;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
/** A search too busy to answer is no company, as a name that matches none. */
|
|
1388
|
+
function busyAsMiss(error: unknown): null {
|
|
1389
|
+
if (!isProviderBusy(error)) throw error;
|
|
1390
|
+
// Intentional ignore: a busy search says nothing about the name, so no company is suggested.
|
|
1391
|
+
return null;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1266
1394
|
/** `resolveCliListing` for a command: a bad exchange fails the command with its message. */
|
|
1267
1395
|
export async function requireCliListing(
|
|
1268
1396
|
raw: string,
|