gloomberb 0.17.4 → 0.17.6
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 +3 -1
- package/patches/electrobun@1.18.1.patch +80 -0
- package/src/api-client/chat.ts +9 -0
- package/src/api-client/commodity-balances.ts +77 -0
- package/src/api-client/data.ts +35 -3
- package/src/api-client/earnings.ts +10 -0
- package/src/api-client/index.ts +5 -0
- package/src/api-client/insider-signals.ts +75 -0
- package/src/api-client/market-discovery.ts +1 -1
- package/src/api-client/normalizers.ts +1 -0
- package/src/api-client/paths.ts +9 -0
- package/src/api-client/research-activity.ts +10 -3
- package/src/api-client/socket.ts +10 -0
- package/src/api-client/types.ts +25 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +2 -1
- package/src/app/runtime/broker-import.ts +23 -21
- package/src/app/runtime/plugin-bindings.ts +13 -7
- package/src/app/runtime/update.ts +64 -4
- package/src/app.tsx +2 -0
- package/src/architecture/core-builtin-allowlist.json +2 -10
- package/src/ask-gloom/pane-context.ts +11 -1
- package/src/brokers/broker-portfolio-sync.ts +66 -0
- package/src/brokers/sync-broker-instance.ts +75 -41
- package/src/cli/commands/market.ts +101 -34
- package/src/cli/commands/options-chain.ts +20 -6
- package/src/cli/commands/overview.ts +54 -13
- package/src/cli/commands/search.ts +1 -1
- package/src/cli/commands/system.ts +19 -1
- package/src/cli/commands/ticker.ts +24 -4
- package/src/cli/context.ts +13 -0
- package/src/cli/errors.ts +15 -2
- package/src/cli/fx-pair.ts +77 -0
- package/src/cli/glossary.ts +34 -4
- package/src/cli/history-rows.ts +25 -11
- package/src/cli/index.ts +30 -12
- package/src/cli/listing-arg.ts +61 -138
- package/src/cli/listing-suggestion.ts +3 -21
- package/src/cli/pane-functions/catalog.ts +50 -0
- package/src/cli/pane-functions/freshness.ts +44 -9
- package/src/cli/pane-functions/function-help.ts +9 -9
- package/src/cli/pane-functions/headless.ts +19 -7
- package/src/cli/pane-functions/index.ts +63 -5
- package/src/cli/pane-functions/market-session.ts +18 -3
- package/src/cli/pane-functions/resolver.ts +29 -8
- package/src/cli/pane-functions/screenshot.ts +2 -0
- package/src/cli/report-tables.ts +11 -4
- package/src/cli/result.ts +2 -0
- package/src/cli/series-search.ts +6 -1
- package/src/cli/ticker-resolution.ts +1 -2
- package/src/cli/unknown-command.ts +18 -0
- package/src/components/chart/composite/composite-chart.tsx +80 -19
- package/src/components/chart/composite/format.ts +17 -0
- package/src/components/chart/composite/legend.tsx +26 -15
- package/src/components/chart/composite/panel-labels.ts +38 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +25 -7
- package/src/components/chart/composite/price-axis-labels.tsx +16 -3
- package/src/components/chart/composite/scene.ts +60 -2
- package/src/components/chart/composite/series-axis-tags.tsx +67 -0
- package/src/components/chart/composite/tools.ts +2 -2
- package/src/components/chart/composite/types.ts +28 -0
- package/src/components/chart/core/view-reset.ts +46 -0
- package/src/components/command-bar/commands/collection.ts +1 -1
- package/src/components/command-bar/commands/direct/index.ts +48 -1
- package/src/components/command-bar/commands/registry.ts +8 -0
- package/src/components/command-bar/list/model.ts +7 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/panel/prompt-binding.ts +8 -0
- package/src/components/command-bar/routes/root/command-items.ts +17 -2
- package/src/components/command-bar/routes/root/pick-prefetch.ts +38 -0
- package/src/components/command-bar/routes/root/provider-search.ts +6 -0
- package/src/components/command-bar/routes/root/runtime.ts +3 -0
- package/src/components/command-bar/routes/root/shortcut-feedback.ts +3 -1
- package/src/components/command-bar/routes/root/shortcut-items.ts +1 -1
- package/src/components/command-bar/routes/ticker-search/actions.ts +34 -0
- package/src/components/command-bar/surface/index.tsx +7 -1
- package/src/components/command-bar/workflow/collection-actions.ts +6 -5
- package/src/components/command-bar/workflow/ops.ts +8 -5
- package/src/components/command-bar/workflow/pane-reuse.ts +3 -2
- package/src/components/command-bar/workflow/tickers.ts +1 -1
- package/src/components/form-modal/confirm.tsx +1 -0
- package/src/components/form-modal/request.ts +2 -0
- package/src/components/layout/detached-pane-shell.tsx +6 -1
- package/src/components/layout/pane/content.tsx +11 -8
- package/src/components/layout/pane/sidebar.tsx +24 -11
- package/src/components/layout/pane/ticker-fate-gate.tsx +56 -0
- package/src/components/layout/shell/index.tsx +5 -1
- package/src/components/layout/shell/menu.ts +8 -0
- package/src/components/layout/shell/pane/capture.ts +29 -12
- package/src/components/onboarding/wizard-model.ts +1 -1
- package/src/components/onboarding/wizard-positions.ts +18 -10
- package/src/components/ticker/badge/format.ts +64 -15
- package/src/components/ticker/badge/index.tsx +32 -11
- package/src/components/ticker/badge/list.tsx +4 -3
- package/src/components/ticker/badge/text.tsx +2 -2
- package/src/components/ticker/list-table-view.tsx +8 -1
- package/src/components/ui/confirm-dialog.tsx +30 -4
- package/src/components/ui/data-table/opentui/index.tsx +61 -20
- package/src/components/ui/data-table/opentui/model.ts +104 -0
- package/src/components/ui/fields.tsx +5 -0
- package/src/components/ui/inline-quick-add.tsx +4 -1
- package/src/components/ui/query-bar-keys.tsx +29 -5
- package/src/components/ui/query-bar.tsx +29 -4
- package/src/components/ui/windowed-list.tsx +27 -0
- package/src/data/config/store/normalize.ts +8 -0
- package/src/docs/snapshot.json +248 -5
- package/src/i18n/es.ts +3 -1
- package/src/i18n/ja.ts +3 -1
- package/src/i18n/ko.ts +3 -1
- package/src/i18n/zh-cn.ts +3 -1
- package/src/i18n/zh-tw.ts +3 -1
- package/src/layout-marketplace/cloud.ts +16 -5
- package/src/layout-marketplace/gallery.tsx +4 -4
- package/src/layout-marketplace/linked-sync.ts +9 -3
- package/src/layout-marketplace/linked.ts +24 -3
- package/src/layout-marketplace/model.ts +39 -4
- package/src/market-data/coordinator/events.ts +5 -1
- package/src/market-data/coordinator/index.ts +38 -1
- package/src/market-data/coordinator/quotes.ts +4 -0
- package/src/market-data/futures-contract.ts +53 -0
- package/src/market-data/hooks.tsx +225 -30
- package/src/market-data/market/bar-period.ts +71 -0
- package/src/market-data/market/format.ts +14 -2
- package/src/market-data/market/freshness.ts +28 -1
- package/src/market-data/market/status.ts +152 -13
- package/src/market-data/model-dividend-yield.ts +76 -0
- package/src/market-data/quotes/freshness.ts +30 -1
- package/src/{plugins/builtin/portfolio-list → market-data/quotes}/live-valuation.ts +6 -6
- package/src/market-data/quotes/regular-session-valuation.ts +32 -1
- package/src/plugins/builtin/analytics/metrics.ts +8 -4
- package/src/plugins/builtin/analytics/risk-headless.ts +12 -5
- package/src/plugins/builtin/analytics/risk-model.ts +74 -15
- package/src/plugins/builtin/analytics/risk-pane.tsx +6 -6
- package/src/plugins/builtin/broker-manager/detail.tsx +2 -2
- package/src/plugins/builtin/broker-manager/model.ts +2 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +0 -1
- package/src/plugins/builtin/chart-composer/editor/controller.ts +14 -10
- package/src/plugins/builtin/chart-composer/formula.ts +233 -0
- package/src/plugins/builtin/chart-composer/headless.ts +27 -2
- package/src/plugins/builtin/chart-composer/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/pane.tsx +41 -10
- package/src/plugins/builtin/chart-composer/presets.ts +91 -2
- package/src/plugins/builtin/chart-composer/quick-add.tsx +9 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +44 -11
- package/src/plugins/builtin/chart-composer/series-expression.ts +1 -1
- package/src/plugins/builtin/chart-composer/settings.ts +16 -4
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/content/channel-navigation.ts +17 -6
- package/src/plugins/builtin/chat/content/footer.tsx +5 -2
- package/src/plugins/builtin/chat/content/index.tsx +12 -5
- package/src/plugins/builtin/chat/content/mentions.ts +50 -21
- package/src/plugins/builtin/chat/content/scroll.ts +12 -5
- package/src/plugins/builtin/chat/content/snapshot.ts +11 -0
- package/src/plugins/builtin/chat/content/transcript.tsx +48 -37
- package/src/plugins/builtin/chat/content/whole-message-copy.ts +44 -2
- package/src/plugins/builtin/chat/controller/index.ts +88 -17
- package/src/plugins/builtin/chat/controller/mention-candidates.ts +104 -0
- package/src/plugins/builtin/chat/controller/messages.ts +14 -2
- package/src/plugins/builtin/chat/controller/realtime.ts +9 -0
- package/src/plugins/builtin/chat/controller/state.ts +25 -0
- package/src/plugins/builtin/chat/controller/unread-people.ts +240 -0
- package/src/plugins/builtin/chat/controller/view.ts +4 -0
- package/src/plugins/builtin/chat/conversation-recency.ts +73 -0
- package/src/plugins/builtin/chat/conversations.ts +12 -5
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/layout.ts +23 -5
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -0
- package/src/plugins/builtin/chat/share/flow.tsx +18 -9
- package/src/plugins/builtin/chat/sidebar-rows.ts +18 -5
- package/src/plugins/builtin/chat/sidebar.tsx +6 -1
- package/src/plugins/builtin/chat/unread-badge.ts +38 -7
- package/src/plugins/builtin/cloud/askg/manifest.ts +4 -3
- package/src/plugins/builtin/cloud/askg/pane-attachment.ts +2 -3
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +16 -5
- package/src/plugins/builtin/commodities-energy/index.tsx +23 -4
- package/src/plugins/builtin/commodity-balances/client.ts +51 -193
- package/src/plugins/builtin/commodity-balances/headless.ts +10 -3
- package/src/plugins/builtin/commodity-balances/model.ts +5 -180
- package/src/plugins/builtin/commodity-balances/pane.tsx +3 -0
- package/src/plugins/builtin/custom-view/index.tsx +1 -1
- package/src/plugins/builtin/custom-view/loader.ts +57 -7
- package/src/plugins/builtin/custom-view/sources.ts +146 -0
- package/src/plugins/builtin/custom-view/view-spec.ts +37 -1
- package/src/plugins/builtin/earnings/board-model.ts +33 -3
- package/src/plugins/builtin/earnings/board-table.ts +59 -14
- package/src/plugins/builtin/earnings/board.tsx +36 -3
- package/src/plugins/builtin/earnings/headless.ts +24 -5
- package/src/plugins/builtin/earnings/history-model.ts +9 -1
- package/src/plugins/builtin/earnings/history-table.ts +16 -5
- package/src/plugins/builtin/earnings/history.tsx +40 -10
- package/src/plugins/builtin/earnings/index.tsx +3 -1
- package/src/plugins/builtin/earnings/no-earnings.ts +52 -0
- package/src/plugins/builtin/econ-statistics/detail.tsx +4 -1
- package/src/plugins/builtin/econ-statistics/headless.ts +5 -0
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +4 -0
- package/src/plugins/builtin/futures/model.ts +5 -4
- package/src/plugins/builtin/futures/series.ts +38 -1
- package/src/plugins/builtin/futures-curve/client.ts +4 -4
- package/src/plugins/builtin/futures-curve/headless.ts +33 -8
- package/src/plugins/builtin/futures-curve/index.tsx +4 -4
- package/src/plugins/builtin/futures-curve/model.ts +109 -6
- package/src/plugins/builtin/futures-curve/pane.tsx +24 -13
- package/src/plugins/builtin/insider/headless.ts +74 -4
- package/src/plugins/builtin/insider/index.tsx +99 -5
- package/src/plugins/builtin/insider/signals-client.ts +69 -0
- package/src/plugins/builtin/insider/signals-model.ts +146 -0
- package/src/plugins/builtin/insider/signals.tsx +176 -0
- package/src/plugins/builtin/iv-history/client.ts +41 -2
- package/src/plugins/builtin/iv-history/headless.ts +24 -16
- package/src/plugins/builtin/iv-history/screen-pane.tsx +25 -7
- package/src/plugins/builtin/jodi/client.ts +42 -205
- package/src/plugins/builtin/jodi/headless.ts +13 -4
- package/src/plugins/builtin/jodi/model.ts +18 -43
- package/src/plugins/builtin/jodi/pane.tsx +5 -0
- package/src/plugins/builtin/kelly-sizer/headless.ts +1 -1
- package/src/plugins/builtin/kelly-sizer/history.ts +5 -3
- package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
- package/src/plugins/builtin/macro-day/release-table.ts +3 -3
- package/src/plugins/builtin/market-movers/client.ts +32 -6
- package/src/plugins/builtin/market-movers/headless.ts +7 -2
- package/src/plugins/builtin/market-movers/model.ts +64 -3
- package/src/plugins/builtin/market-movers/screener.ts +20 -9
- package/src/plugins/builtin/market-valuation/detail.tsx +7 -2
- package/src/plugins/builtin/market-valuation/headless.ts +17 -4
- package/src/plugins/builtin/market-valuation/pane.tsx +7 -3
- package/src/plugins/builtin/market-valuation/view.ts +10 -0
- package/src/plugins/builtin/mna/model.ts +2 -1
- package/src/plugins/builtin/options-positioning/client.ts +10 -0
- package/src/plugins/builtin/options-positioning/headless.ts +88 -13
- package/src/plugins/builtin/options-positioning/index.tsx +2 -2
- package/src/plugins/builtin/options-positioning/model.ts +47 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +55 -26
- package/src/plugins/builtin/options-scenario/charts.tsx +13 -4
- package/src/plugins/builtin/options-scenario/client.ts +111 -17
- package/src/plugins/builtin/options-scenario/collar.ts +141 -0
- package/src/plugins/builtin/options-scenario/csp.ts +1 -1
- package/src/plugins/builtin/options-scenario/freshness.ts +27 -0
- package/src/plugins/builtin/options-scenario/headless.ts +28 -12
- package/src/plugins/builtin/options-scenario/index.tsx +1 -1
- package/src/plugins/builtin/options-scenario/model.ts +55 -5
- package/src/plugins/builtin/options-scenario/pane.tsx +24 -5
- package/src/plugins/builtin/perps/client.ts +77 -6
- package/src/plugins/builtin/perps/headless.ts +25 -14
- package/src/plugins/builtin/perps/index.tsx +1 -1
- package/src/plugins/builtin/perps/model.ts +32 -0
- package/src/plugins/builtin/perps/pane.tsx +5 -4
- package/src/plugins/builtin/portfolio-list/allocation.ts +9 -4
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +39 -20
- package/src/plugins/builtin/portfolio-list/cli/render.ts +5 -3
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +15 -7
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +1 -1
- package/src/plugins/builtin/portfolio-list/column-values.ts +4 -3
- package/src/plugins/builtin/portfolio-list/command-bar.ts +1 -1
- package/src/plugins/builtin/portfolio-list/headless.ts +10 -3
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +36 -4
- package/src/plugins/builtin/portfolio-list/pane/remove-ticker.ts +1 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +35 -6
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +2 -1
- package/src/plugins/builtin/portfolio-list/settings.ts +22 -0
- package/src/plugins/builtin/portfolio-list/since-last-seen.ts +197 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +14 -2
- package/src/plugins/builtin/rate-path/model.ts +1 -1
- package/src/plugins/builtin/rate-path/pane.tsx +2 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +14 -5
- package/src/plugins/builtin/research/event-model.ts +41 -49
- package/src/plugins/builtin/research/event-sources.ts +1 -1
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +6 -0
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +3 -3
- package/src/plugins/builtin/reverse-dcf/headless.ts +25 -9
- package/src/plugins/builtin/reverse-dcf/index.tsx +4 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +39 -5
- package/src/plugins/builtin/reverse-dcf/pane.tsx +16 -6
- package/src/plugins/builtin/sectors/client.ts +46 -19
- package/src/plugins/builtin/sectors/sector-model.ts +22 -10
- package/src/plugins/builtin/shared/earnings-report.ts +48 -1
- package/src/plugins/builtin/shared/history-span.ts +55 -0
- package/src/plugins/builtin/shared/report-freshness.ts +20 -11
- package/src/plugins/builtin/shared/series-detail-chart.tsx +5 -2
- package/src/plugins/builtin/shared/series-list-detail.tsx +7 -3
- package/src/plugins/builtin/shared/ticker-surface.ts +16 -2
- package/src/plugins/builtin/shared/use-quote-board.ts +12 -5
- package/src/plugins/builtin/themes/headless.ts +15 -2
- package/src/plugins/builtin/thirteenf/api.ts +60 -43
- package/src/plugins/builtin/thirteenf/data.ts +76 -35
- package/src/plugins/builtin/thirteenf/headless.ts +2 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +10 -2
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +85 -17
- package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +92 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +26 -8
- package/src/plugins/builtin/ticker-detail/headless-schema.ts +4 -2
- package/src/plugins/builtin/ticker-detail/headless.ts +95 -22
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +1 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +17 -3
- package/src/plugins/builtin/yield-curve/curves.ts +15 -1
- package/src/plugins/builtin/yield-curve/headless.ts +17 -7
- package/src/plugins/builtin/yield-curve/history.ts +2 -0
- package/src/plugins/builtin/yield-curve/index.tsx +23 -18
- package/src/plugins/builtin/yield-curve/series.ts +19 -9
- package/src/plugins/builtin/yield-curve/treasury-data.ts +2 -0
- package/src/plugins/builtin/yield-curve/world.ts +4 -0
- package/src/plugins/registry/host-actions.ts +3 -0
- package/src/{plugins/builtin/portfolio-list → portfolio}/mutations.ts +6 -6
- package/src/portfolio/valuation-basis.ts +4 -21
- package/src/public/__snapshots__/public-api.test.ts.snap +2 -0
- package/src/remote/controller.ts +27 -7
- package/src/remote/schema.ts +25 -9
- package/src/renderers/browser/app-services.ts +7 -1
- package/src/renderers/browser/config-host.ts +55 -2
- package/src/renderers/browser/first-screen.ts +193 -0
- package/src/renderers/browser/main.tsx +59 -7
- package/src/renderers/cloudflare/http-proxy.ts +21 -13
- package/src/renderers/dom/desktop/controls.tsx +2 -0
- package/src/renderers/dom/desktop/query-bar.tsx +1 -1
- package/src/renderers/dom/dom-ui-host.tsx +2 -0
- package/src/renderers/dom/windowed-list.tsx +201 -0
- package/src/renderers/dom/windowed-rows.ts +65 -0
- package/src/renderers/electrobun/bun/application-menu/index.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +3 -7
- package/src/renderers/electrobun/bun/desktop/http-stream.ts +21 -6
- package/src/renderers/electrobun/bun/desktop/update.ts +132 -14
- package/src/renderers/electrobun/shared/protocol.ts +7 -4
- package/src/renderers/electrobun/view/http-fetch.ts +12 -1
- package/src/renderers/electrobun/view/update-host.ts +2 -2
- package/src/shares/bug-report-dialog.tsx +162 -0
- package/src/shares/bug-report-flow.tsx +84 -0
- package/src/shares/bug-report.ts +42 -0
- package/src/shares/capture-flow.ts +19 -0
- package/src/shares/capture-preview.tsx +129 -0
- package/src/shares/chat.ts +10 -1
- package/src/shares/dialog.tsx +16 -91
- package/src/shares/flow.tsx +12 -16
- package/src/shares/personal-pane.ts +35 -1
- package/src/sources/listing-symbols.ts +8 -4
- package/src/state/app/context/index.tsx +13 -0
- package/src/state/app/tab-cursor.ts +85 -0
- package/src/sync/core-contributors.ts +206 -59
- package/src/telemetry/usage-counts.ts +16 -0
- package/src/test-support/config-store.ts +31 -0
- package/src/test-support/moved-symbols.ts +64 -0
- package/src/theme/colors.ts +30 -0
- package/src/tickers/listing-fate.ts +242 -0
- package/src/tickers/open-target.ts +61 -11
- package/src/tickers/search/index.ts +45 -3
- package/src/tickers/search/ranking.ts +19 -2
- package/src/tickers/search/types.ts +3 -0
- package/src/tickers/search/upsert.ts +22 -7
- package/src/tickers/ticker-changes.ts +26 -9
- package/src/time-series/bar-spacing.ts +19 -0
- package/src/time-series/chart-data.ts +37 -13
- package/src/time-series/field-catalog.ts +5 -2
- package/src/time-series/forward-valuation.ts +10 -7
- package/src/time-series/fundamentals.ts +49 -11
- package/src/time-series/hooks.ts +2 -0
- package/src/time-series/live-bars.ts +3 -1
- package/src/time-series/parsed-history-cache.ts +2 -0
- package/src/time-series/price-units.ts +69 -0
- package/src/time-series/resolve.ts +71 -11
- package/src/time-series/session-bar.ts +138 -0
- package/src/time-series/spec.ts +20 -3
- package/src/time-series/studies.ts +211 -12
- package/src/time-series/transforms.ts +2 -0
- package/src/time-series/types.ts +11 -1
- package/src/time-series/valuation-price.ts +13 -0
- package/src/types/config.ts +24 -1
- package/src/types/financials.ts +2 -0
- package/src/types/headless.ts +27 -0
- package/src/types/plugin.ts +7 -0
- package/src/ui/host.tsx +5 -0
- package/src/updater/channel.ts +51 -0
- package/src/updater/github-releases.ts +85 -16
- package/src/updater/index.ts +128 -17
- package/src/utils/asset-category.ts +20 -0
- package/src/utils/datetime-format.ts +7 -1
- package/src/utils/decimal-comma.ts +83 -3
- package/src/utils/fundamentals.ts +7 -2
- package/src/utils/http-proxy-response.ts +66 -9
- package/src/utils/plugin-proxy-hosts.json +1 -5
- package/src/utils/semver.ts +47 -2
- package/src/utils/typo-distance.ts +22 -0
- package/src/plugins/builtin/shared/zip-text.ts +0 -46
package/src/cli/listing-arg.ts
CHANGED
|
@@ -13,16 +13,8 @@ import {
|
|
|
13
13
|
type ListingVenue,
|
|
14
14
|
} from "../tickers/listing-venues";
|
|
15
15
|
import { findUsListingOfCompany, type UsListing } from "../tickers/us-listing";
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
quotedListing,
|
|
19
|
-
startTickerChangeLookup,
|
|
20
|
-
tickerChangeLine,
|
|
21
|
-
type CurrentListing,
|
|
22
|
-
type TickerChange,
|
|
23
|
-
type TickerChangeLookup,
|
|
24
|
-
} from "../tickers/ticker-changes";
|
|
25
|
-
import type { TickerChanges } from "../types/ticker-changes";
|
|
16
|
+
import { movedAwayChange, startTickerChangeLookup, type MovedTickerChange } from "../tickers/ticker-changes";
|
|
17
|
+
import { loadListingFate, MovedSymbolError, type ListingFate } from "../tickers/listing-fate";
|
|
26
18
|
import { quotedListingName } from "../tickers/listing-name";
|
|
27
19
|
import { cliStyles } from "../utils/cli-output";
|
|
28
20
|
import { groupByCompany, sameCompany, shortCompanyName } from "../tickers/company-name";
|
|
@@ -35,17 +27,14 @@ import {
|
|
|
35
27
|
isUsListingExchange,
|
|
36
28
|
parsePublicTickerKey,
|
|
37
29
|
publicTickerKey,
|
|
38
|
-
resolveExchangeTimeZone,
|
|
39
30
|
typedExchangeCode,
|
|
40
31
|
} from "../utils/exchanges";
|
|
41
32
|
import { withMarketData } from "./scoped-context";
|
|
33
|
+
import { failWithFields } from "./errors";
|
|
42
34
|
import { stripStrayEdges } from "./symbol-args";
|
|
43
35
|
import { isNoProviderMessage } from "../sources/provider-errors";
|
|
44
36
|
import { dashedCoinPair } from "../utils/crypto-pair";
|
|
45
37
|
import { currencyPairSpelling } from "../utils/currency-pair";
|
|
46
|
-
import { isQuoteStaleForCurrentSession } from "../market-data/quotes/freshness";
|
|
47
|
-
import { formatMarketPriceWithCurrency, quoteFormatOptions } from "../market-data/market/format";
|
|
48
|
-
import { zonedDateKey } from "../utils/zoned-date-time";
|
|
49
38
|
import { bareFuturesCode, futuresContractByCode } from "../market-data/futures-board";
|
|
50
39
|
|
|
51
40
|
/** The `--exchange` option of every command that takes a ticker. */
|
|
@@ -260,7 +249,9 @@ export interface ListingDataRequest {
|
|
|
260
249
|
* What became of the symbol (`loadListingFate`), for a command a listing
|
|
261
250
|
* that stopped trading has nothing for (history, options): a request no
|
|
262
251
|
* source serves fails with `fate.note` instead of the source's miss, and the
|
|
263
|
-
* change line leads any listings the failure names.
|
|
252
|
+
* change line leads any listings the failure names. A symbol whose company
|
|
253
|
+
* moved, with nothing new listed under it, fails naming its new symbol
|
|
254
|
+
* whatever came back (`failMovedSymbol`).
|
|
264
255
|
*/
|
|
265
256
|
fate?: () => Promise<ListingFate>;
|
|
266
257
|
}
|
|
@@ -472,7 +463,7 @@ interface OtherListingsMessage {
|
|
|
472
463
|
* The command that asks again for another listing: `gloomberb history SAN:EPA
|
|
473
464
|
* --range 5Y` keeps the options. Null where the request names listings alone.
|
|
474
465
|
*/
|
|
475
|
-
function retryCommand(request: ListingDataRequest, key: string): string | null {
|
|
466
|
+
function retryCommand(request: Pick<ListingDataRequest, "command" | "retry">, key: string): string | null {
|
|
476
467
|
if (request.retry !== undefined) return request.retry?.(key) ?? null;
|
|
477
468
|
const [command, ...options] = request.command.split(" ");
|
|
478
469
|
return ["gloomberb", command, key, ...options].join(" ");
|
|
@@ -638,95 +629,23 @@ export function chosenListingNote(symbol: string, exchange: string, others: read
|
|
|
638
629
|
return `${symbol} -> ${exchange}${aside ? ` (${aside})` : ""}`;
|
|
639
630
|
}
|
|
640
631
|
|
|
641
|
-
// A quote this much older than now, and stale, is a listing that stopped trading, not a quiet day.
|
|
642
|
-
const ENDED_AFTER_MS = 183 * 24 * 60 * 60_000;
|
|
643
|
-
|
|
644
|
-
/**
|
|
645
|
-
* The exchange-local date a listing last traded, when its quote is stale and
|
|
646
|
-
* that was over half a year ago (TWTR's 2022-10-27); null for anything newer.
|
|
647
|
-
*/
|
|
648
|
-
function endedTradingDate(quote: Quote, now = Date.now()): string | null {
|
|
649
|
-
const last = Math.max(...[quote.lastTradeTime, quote.lastUpdated].filter((time): time is number => Number.isFinite(time)));
|
|
650
|
-
if (!(last > 0) || now - last < ENDED_AFTER_MS || !isQuoteStaleForCurrentSession(quote, now)) return null;
|
|
651
|
-
const zone = resolveExchangeTimeZone(quote.listingExchangeName || quote.exchangeName) ?? "UTC";
|
|
652
|
-
return zonedDateKey(last, zone);
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
/** What trades under a symbol now, from its quote: nothing when it has none or stopped long ago. */
|
|
656
|
-
export function tradingListing(quote: Quote | null | undefined, now = Date.now()): CurrentListing | null {
|
|
657
|
-
return quote && !endedTradingDate(quote, now) ? quotedListing(quote) : null;
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
/** "TWTR: no longer trading since 2022-10-27 (last price $53.86)", for a quote that stopped long ago. */
|
|
661
|
-
export function noLongerTradingNote(label: string, quote: Quote, now = Date.now()): string | null {
|
|
662
|
-
const date = endedTradingDate(quote, now);
|
|
663
|
-
if (!date) return null;
|
|
664
|
-
const price = formatMarketPriceWithCurrency(quote.price, quote.currency, quoteFormatOptions(quote));
|
|
665
|
-
return `${label}: no longer trading since ${date} (last price ${price})`;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
632
|
/**
|
|
669
|
-
*
|
|
670
|
-
*
|
|
671
|
-
* beside that quote (FB became META).
|
|
633
|
+
* "HFC became DINO on 2022-03-15: `gloomberb history DINO`": a symbol whose
|
|
634
|
+
* company moved, with the command that asks for it under its new symbol.
|
|
672
635
|
*/
|
|
673
|
-
export
|
|
674
|
-
|
|
675
|
-
endedOn: string | null;
|
|
676
|
-
/** The symbol's change as `quote` reads it; null when it has none, or was read on a listing abroad. */
|
|
677
|
-
change: TickerChange | null;
|
|
678
|
-
/**
|
|
679
|
-
* The line a command with nothing to serve for the symbol says: `quote`'s
|
|
680
|
-
* change line when its company left it ("FB now lists ...; Meta Platforms
|
|
681
|
-
* trades as META (changed 2022-06-09)"), else, for a listing that stopped,
|
|
682
|
-
* "TWTR stopped trading 2022-10-27; no history or options served". Null
|
|
683
|
-
* for a symbol that trades as it always has.
|
|
684
|
-
*/
|
|
685
|
-
note: string | null;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
const NO_FATE: ListingFate = { endedOn: null, change: null, note: null };
|
|
689
|
-
/** How long a fate whose change lookup started late waits for it. */
|
|
690
|
-
const LATE_CHANGE_WAIT_MS = 2_000;
|
|
691
|
-
|
|
692
|
-
/** `ListingFate` from a listing's quote (null when it has none) and its ticker changes. */
|
|
693
|
-
export function describeListingFate(
|
|
694
|
-
listing: { symbol: string; exchange?: string | null },
|
|
695
|
-
quote: Quote | null,
|
|
696
|
-
changes: TickerChanges | null,
|
|
697
|
-
now = Date.now(),
|
|
698
|
-
): ListingFate {
|
|
699
|
-
const symbol = listing.symbol.trim().toUpperCase();
|
|
700
|
-
const endedOn = quote ? endedTradingDate(quote, now) : null;
|
|
701
|
-
// A bare symbol read on a listing abroad (SXR8 -> XETRA) is not the US symbol the changes are of.
|
|
702
|
-
const abroad = !!quote && !!bareListingNote({ symbol, request: { symbol, exchange: listing.exchange ?? "" } }, quote);
|
|
703
|
-
const change = abroad ? null : describeTickerChange(symbol, changes, tradingListing(quote, now));
|
|
704
|
-
const note = tickerChangeLine(change) ?? (endedOn ? `${symbol} stopped trading ${endedOn}; no history or options served` : null);
|
|
705
|
-
return { endedOn, change, note };
|
|
636
|
+
export function movedSymbolLine(change: MovedTickerChange, retry: string | null): string {
|
|
637
|
+
return retry ? `${change.note}: \`${retry}\`` : change.note;
|
|
706
638
|
}
|
|
707
639
|
|
|
708
640
|
/**
|
|
709
|
-
*
|
|
710
|
-
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
641
|
+
* Fails a command that has nothing to serve for a symbol whose company
|
|
642
|
+
* moved (`ListingFate.moved`) with `movedSymbolLine`; `--json` reads the
|
|
643
|
+
* change as quote rows carry it (`tickerChange`) and the command that asks
|
|
644
|
+
* for the new symbol (`retry`). Exits as any failure does.
|
|
713
645
|
*/
|
|
714
|
-
export
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
options: { quote?: Quote | null | Promise<Quote | null>; changes?: TickerChangeLookup } = {},
|
|
718
|
-
): Promise<ListingFate> {
|
|
719
|
-
const request = listing.request ?? { symbol: listing.symbol, exchange: listing.exchange ?? "" };
|
|
720
|
-
const lookup = options.changes ?? startTickerChangeLookup(dataProvider, listing.symbol, listing.exchange);
|
|
721
|
-
try {
|
|
722
|
-
const [quote, changes] = await Promise.all([
|
|
723
|
-
options.quote !== undefined ? options.quote : dataProvider.getQuote(request.symbol, request.exchange).catch(() => null),
|
|
724
|
-
options.changes ? lookup.settle() : lookup.settle(LATE_CHANGE_WAIT_MS),
|
|
725
|
-
]);
|
|
726
|
-
return describeListingFate(listing, quote, changes);
|
|
727
|
-
} catch {
|
|
728
|
-
return NO_FATE;
|
|
729
|
-
}
|
|
646
|
+
export function failMovedSymbol(change: MovedTickerChange, request: Pick<ListingDataRequest, "command" | "retry">): never {
|
|
647
|
+
const retry = retryCommand(request, change.tradesAs);
|
|
648
|
+
return failWithFields(movedSymbolLine(change, retry), "ticker_moved", { tickerChange: change, retry });
|
|
730
649
|
}
|
|
731
650
|
|
|
732
651
|
/**
|
|
@@ -745,41 +664,28 @@ export function listingFateLoader(
|
|
|
745
664
|
}
|
|
746
665
|
|
|
747
666
|
/**
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
* are from its old symbol: "Includes FB-era bars: META was FB until 2022-06-09".
|
|
667
|
+
* A command's load for one listing beside its fate (`listingFateLoader`): a
|
|
668
|
+
* symbol whose company moved serves nothing, whatever came back or failed,
|
|
669
|
+
* and the command fails naming its new symbol (`failMovedSymbol`). A loader
|
|
670
|
+
* that finds that itself (`MovedSymbolError`) fails the same way.
|
|
753
671
|
*/
|
|
754
|
-
export function
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
* company or fund: a row that shows the symbol as typed says which listing it
|
|
772
|
-
* is. Null for a named or saved venue, and for a venue the app does not place
|
|
773
|
-
* outside the US (a US exchange, OTC, a futures exchange).
|
|
774
|
-
*/
|
|
775
|
-
export function bareListingNote(
|
|
776
|
-
listing: Pick<CliListing, "symbol" | "request">,
|
|
777
|
-
quote: Pick<Quote, "listingExchangeName" | "exchangeName"> | null | undefined,
|
|
778
|
-
): string | null {
|
|
779
|
-
if (listing.request.exchange || !quote || !/^[A-Z0-9]{1,10}$/.test(listing.symbol)) return null;
|
|
780
|
-
const venue = canonicalExchange(quote.listingExchangeName || quote.exchangeName);
|
|
781
|
-
const zone = resolveExchangeTimeZone(venue);
|
|
782
|
-
return zone && !US_TIME_ZONES.has(zone) && !isUsListingExchange(venue) ? `${listing.symbol} -> ${venue}` : null;
|
|
672
|
+
export async function refuseMovedListing<T>(
|
|
673
|
+
fate: (() => Promise<ListingFate>) | null,
|
|
674
|
+
request: Pick<ListingDataRequest, "command" | "retry">,
|
|
675
|
+
load: () => Promise<T>,
|
|
676
|
+
): Promise<T> {
|
|
677
|
+
let value: T;
|
|
678
|
+
try {
|
|
679
|
+
value = await load();
|
|
680
|
+
} catch (error) {
|
|
681
|
+
if (error instanceof MovedSymbolError) failMovedSymbol(error.change, request);
|
|
682
|
+
const moved = (await fate?.())?.moved;
|
|
683
|
+
if (moved) failMovedSymbol(moved, request);
|
|
684
|
+
throw error;
|
|
685
|
+
}
|
|
686
|
+
const moved = (await fate?.())?.moved;
|
|
687
|
+
if (moved) failMovedSymbol(moved, request);
|
|
688
|
+
return value;
|
|
783
689
|
}
|
|
784
690
|
|
|
785
691
|
/**
|
|
@@ -802,8 +708,9 @@ export function tradingBoardNote(
|
|
|
802
708
|
* calls what it returned empty, and a listing's exchange is one its symbol does
|
|
803
709
|
* not trade on, the command ends in that message instead of the request's own
|
|
804
710
|
* "unavailable". With `request`, a bare symbol no source serves ends naming
|
|
805
|
-
* its other listings (`otherListingsMessage`)
|
|
806
|
-
*
|
|
711
|
+
* its other listings (`otherListingsMessage`), and with `request.fate` a symbol
|
|
712
|
+
* whose company moved ends naming its new symbol, served or not. A request
|
|
713
|
+
* that succeeds costs no extra lookup beyond the fate the command reads anyway.
|
|
807
714
|
*/
|
|
808
715
|
export async function loadForListing<T>(
|
|
809
716
|
listings: ListingForLoad | readonly ListingForLoad[],
|
|
@@ -823,12 +730,20 @@ export async function loadForListing<T>(
|
|
|
823
730
|
await checkVenues();
|
|
824
731
|
if (request && all.length === 1 && isNoProviderError(error)) {
|
|
825
732
|
const [other, fate] = await Promise.all([otherListingsMessage(all[0]!, deps, request), request.fate?.() ?? null]);
|
|
733
|
+
// Nothing served either way: a company that moved is under its new symbol, before any other listing.
|
|
734
|
+
const moved = movedAwayChange(fate?.change);
|
|
735
|
+
if (moved) failMovedSymbol(moved, request);
|
|
826
736
|
if (other) ctx.fail(other.message, [fate?.note, other.details].filter(Boolean).join("\n"));
|
|
827
737
|
if (fate?.note) ctx.fail(`${fate.note}.`);
|
|
828
738
|
}
|
|
829
739
|
throw error;
|
|
830
740
|
}
|
|
831
741
|
if (isMiss?.(value)) await checkVenues();
|
|
742
|
+
// Data came back, but the symbol's company moved and nothing new lists there: the data is another security's.
|
|
743
|
+
if (request?.fate && all.length === 1) {
|
|
744
|
+
const { moved } = await request.fate();
|
|
745
|
+
if (moved) failMovedSymbol(moved, request);
|
|
746
|
+
}
|
|
832
747
|
return value;
|
|
833
748
|
}
|
|
834
749
|
|
|
@@ -969,10 +884,18 @@ export function listingIdentity(
|
|
|
969
884
|
|
|
970
885
|
/** The listing's own quote, for its company name and venue; null when it has none. */
|
|
971
886
|
export async function loadListingQuote(dataProvider: DataProvider, listing: Pick<CliListing, "request">): Promise<Quote | null> {
|
|
887
|
+
return (await loadListingQuoteAnswer(dataProvider, listing)).quote;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/** The listing's own quote, or the error its request failed with: "Not a ticker: PALANTIR." for a name typed as a symbol. */
|
|
891
|
+
export async function loadListingQuoteAnswer(
|
|
892
|
+
dataProvider: DataProvider,
|
|
893
|
+
listing: Pick<CliListing, "request">,
|
|
894
|
+
): Promise<{ quote: Quote | null; error?: unknown }> {
|
|
972
895
|
try {
|
|
973
|
-
return await dataProvider.getQuote(listing.request.symbol, listing.request.exchange);
|
|
974
|
-
} catch {
|
|
975
|
-
return null;
|
|
896
|
+
return { quote: await dataProvider.getQuote(listing.request.symbol, listing.request.exchange) };
|
|
897
|
+
} catch (error) {
|
|
898
|
+
return { quote: null, error };
|
|
976
899
|
}
|
|
977
900
|
}
|
|
978
901
|
|
|
@@ -5,6 +5,7 @@ import { mapWithConcurrency } from "../utils/concurrency";
|
|
|
5
5
|
import { isUsListingExchange } from "../utils/exchanges";
|
|
6
6
|
import { STARTER_SYMBOLS } from "../tickers/starter-symbols";
|
|
7
7
|
import { TickerNotFoundError } from "./ticker-resolution";
|
|
8
|
+
import { typoDistance } from "../utils/typo-distance";
|
|
8
9
|
|
|
9
10
|
/** How long a suggestion waits for search; a slow search leaves the plain "not found" as it was. */
|
|
10
11
|
const SUGGESTION_TIMEOUT_MS = 6_000;
|
|
@@ -39,25 +40,6 @@ function typedForm(symbol: string): string {
|
|
|
39
40
|
return compact(symbol.split(":")[0]!.replace(/=[A-Z]?$/i, ""));
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
/** The edits (insert, delete, replace, swap two neighbours) between two strings, up to two; anything further apart is 2. */
|
|
43
|
-
function editDistance(left: string, right: string): number {
|
|
44
|
-
if (left === right) return 0;
|
|
45
|
-
if (Math.abs(left.length - right.length) > 1) return 2;
|
|
46
|
-
const rows: number[][] = Array.from({ length: left.length + 1 }, (_, row) => [row, ...new Array<number>(right.length).fill(0)]);
|
|
47
|
-
for (let column = 1; column <= right.length; column += 1) rows[0]![column] = column;
|
|
48
|
-
for (let row = 1; row <= left.length; row += 1) {
|
|
49
|
-
for (let column = 1; column <= right.length; column += 1) {
|
|
50
|
-
const same = left[row - 1] === right[column - 1] ? 0 : 1;
|
|
51
|
-
let best = Math.min(rows[row - 1]![column]! + 1, rows[row]![column - 1]! + 1, rows[row - 1]![column - 1]! + same);
|
|
52
|
-
if (row > 1 && column > 1 && left[row - 1] === right[column - 2] && left[row - 2] === right[column - 1]) {
|
|
53
|
-
best = Math.min(best, rows[row - 2]![column - 2]! + 1);
|
|
54
|
-
}
|
|
55
|
-
rows[row]![column] = best;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return rows[left.length]![right.length]!;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
43
|
function candidateName(candidate: TickerSearchCandidate): string {
|
|
62
44
|
return candidate.detail.split(" | ")[0]?.trim() ?? "";
|
|
63
45
|
}
|
|
@@ -68,7 +50,7 @@ function nearness(typed: string, candidate: TickerSearchCandidate): number | nul
|
|
|
68
50
|
if (!wanted) return null;
|
|
69
51
|
const symbol = typedForm(candidate.label || candidate.symbol);
|
|
70
52
|
if (wanted === symbol || wanted === compact(candidateName(candidate))) return 0;
|
|
71
|
-
return wanted.length >= MIN_TYPO_LENGTH && symbol.length >= MIN_TYPO_LENGTH &&
|
|
53
|
+
return wanted.length >= MIN_TYPO_LENGTH && symbol.length >= MIN_TYPO_LENGTH && typoDistance(wanted, symbol) === 1 ? 1 : null;
|
|
72
54
|
}
|
|
73
55
|
|
|
74
56
|
/**
|
|
@@ -154,7 +136,7 @@ export async function suggestUsTicker(typed: string, deps: SuggestionDeps): Prom
|
|
|
154
136
|
const saved = (await deps.store.loadAllTickers().catch(() => [])).filter((ticker) => isUsListingExchange(ticker.metadata.exchange));
|
|
155
137
|
const near = new Set([...STARTER_SYMBOLS, ...saved.map((ticker) => ticker.metadata.ticker)]
|
|
156
138
|
.map((symbol) => symbol.trim().toUpperCase())
|
|
157
|
-
.filter((symbol) => compact(symbol).length >= MIN_TYPO_LENGTH &&
|
|
139
|
+
.filter((symbol) => compact(symbol).length >= MIN_TYPO_LENGTH && typoDistance(wanted, compact(symbol)) === 1));
|
|
158
140
|
if (near.size !== 1) return null;
|
|
159
141
|
const [symbol] = near;
|
|
160
142
|
const candidates = await searchCandidates(symbol!, deps);
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
} from "../../utils/cli-output";
|
|
27
27
|
import { renderGlossaryEntries, type GlossaryLookup } from "../glossary";
|
|
28
28
|
import { renderSeriesMatches, type SeriesMatch } from "../series-search";
|
|
29
|
+
import { typoDistance } from "../../utils/typo-distance";
|
|
29
30
|
|
|
30
31
|
export interface PaneFunctionCatalog {
|
|
31
32
|
panes: ReadonlyMap<string, PaneDef>;
|
|
@@ -152,6 +153,55 @@ export function findFunctionCode(registry: PaneFunctionCatalog, token: string):
|
|
|
152
153
|
};
|
|
153
154
|
}
|
|
154
155
|
|
|
156
|
+
/** A function code a typed one is a slip away from: what to type, and the function's name. */
|
|
157
|
+
export interface NearFunctionCode {
|
|
158
|
+
code: string;
|
|
159
|
+
label: string;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Typed names shorter than this are too short for one slip to leave them recognisable (`HQ` is a slip from a dozen codes). */
|
|
163
|
+
const MIN_CODE_TYPO_LENGTH = 3;
|
|
164
|
+
/**
|
|
165
|
+
* A word typed as a command is as likely a ticker as a slip from a code
|
|
166
|
+
* (`PLTR`, `META`, `QQQ`): it is read as a slip only from this length and
|
|
167
|
+
* when its first letter is the code's.
|
|
168
|
+
*/
|
|
169
|
+
const MIN_COMMAND_WORD_TYPO_LENGTH = 4;
|
|
170
|
+
/** Near codes an unknown one is answered with; more than this many is no suggestion. */
|
|
171
|
+
const NEAR_CODE_LIMIT = 3;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The function codes one slip of the keys from a name that opens no function
|
|
175
|
+
* (a letter swapped, missing, extra or wrong: `RCDF` for RDCF, `HPP` for HP),
|
|
176
|
+
* among the mnemonics the command bar takes, each named by the code it is
|
|
177
|
+
* near (`DES` for Ticker Research, whose code is T). Empty when none is that
|
|
178
|
+
* close, the name is too short to tell, or so many are that none stands out.
|
|
179
|
+
* A word typed as a command (`commandWord`, `gloomberb RCDF PLTR`) is held to
|
|
180
|
+
* MIN_COMMAND_WORD_TYPO_LENGTH and the code's first letter, so a ticker typed
|
|
181
|
+
* there is not taken for a code.
|
|
182
|
+
*/
|
|
183
|
+
export function nearFunctionCodes(
|
|
184
|
+
registry: PaneFunctionCatalog,
|
|
185
|
+
name: string,
|
|
186
|
+
{ commandWord = false }: { commandWord?: boolean } = {},
|
|
187
|
+
): NearFunctionCode[] {
|
|
188
|
+
const wanted = normalizeLookupToken(name);
|
|
189
|
+
if (wanted.length < (commandWord ? MIN_COMMAND_WORD_TYPO_LENGTH : MIN_CODE_TYPO_LENGTH)) return [];
|
|
190
|
+
const near = new Map<PaneTemplateDef, string>();
|
|
191
|
+
for (const template of registry.paneTemplates.values()) {
|
|
192
|
+
const prefix = template.shortcut?.prefix;
|
|
193
|
+
if (!prefix) continue;
|
|
194
|
+
const code = [prefix, ...template.shortcut?.aliases ?? []].find((candidate) => {
|
|
195
|
+
const known = normalizeLookupToken(candidate);
|
|
196
|
+
return (!commandWord || known[0] === wanted[0]) && typoDistance(wanted, known) === 1;
|
|
197
|
+
});
|
|
198
|
+
if (code) near.set(template, code.toUpperCase());
|
|
199
|
+
}
|
|
200
|
+
if (near.size > NEAR_CODE_LIMIT) return [];
|
|
201
|
+
return [...near].map(([template, code]) => ({ code, label: template.label }))
|
|
202
|
+
.sort((left, right) => left.code.localeCompare(right.code));
|
|
203
|
+
}
|
|
204
|
+
|
|
155
205
|
function sampleArgForTemplate(template: PaneTemplateDef): string {
|
|
156
206
|
switch (template.shortcut?.argKind) {
|
|
157
207
|
case "ticker":
|
|
@@ -44,6 +44,12 @@ export interface ReportFreshness {
|
|
|
44
44
|
basis?: string;
|
|
45
45
|
/** Set when the dates are the start of a period: the status line then reads `Sep 2026 (monthly)`. */
|
|
46
46
|
periodicity?: "monthly" | "quarterly" | "annual";
|
|
47
|
+
/**
|
|
48
|
+
* The newest bar's week or month had not ended, and the last day it covers
|
|
49
|
+
* so far (`asOf`, when known): the status line reads `month to date, through
|
|
50
|
+
* Thu 15 Oct 2026`.
|
|
51
|
+
*/
|
|
52
|
+
periodToDate?: { period: "week" | "month"; through?: string };
|
|
47
53
|
/** The local date of the session close `asOf` is, when the newest observation is its venue's latest close. */
|
|
48
54
|
asOfClose?: string;
|
|
49
55
|
/**
|
|
@@ -346,11 +352,20 @@ function declaredSources(declared: HeadlessPaneFreshness, now: number): Array<{
|
|
|
346
352
|
});
|
|
347
353
|
}
|
|
348
354
|
|
|
355
|
+
/** A declared bar period still forming, its last day read as a date; null for anything else. */
|
|
356
|
+
function readPeriodToDate(declared: HeadlessPaneFreshness["periodToDate"], now: number): { period: "week" | "month"; through: ReportTime | null } | null {
|
|
357
|
+
if (declared?.period !== "week" && declared?.period !== "month") return null;
|
|
358
|
+
const through = declared.through ? parseReportTime(declared.through) : null;
|
|
359
|
+
return { period: declared.period, through: through?.dateOnly && through.time <= now + FUTURE_TOLERANCE_MS ? through : null };
|
|
360
|
+
}
|
|
361
|
+
|
|
349
362
|
function resolveFreshness({ declared, signals, observations, now }: FreshnessInputs): ReportFreshness {
|
|
350
363
|
const sources = declaredSources(declared, now);
|
|
351
364
|
// Sources date the data by their own observations, as a declared as-of does.
|
|
352
365
|
const declaredAsOf = observedTime({ asOf: declared.asOf }, now, ["asOf"]) ?? newest(sources.map(({ time }) => time));
|
|
353
|
-
|
|
366
|
+
// A bar still forming covers its period through a later day than the one it is dated by.
|
|
367
|
+
const periodToDate = readPeriodToDate(declared.periodToDate, now);
|
|
368
|
+
const asOf = declaredAsOf ?? periodToDate?.through ?? newest([...observations.units, ...observations.extra]);
|
|
354
369
|
// The oldest source, not the newest, says how far behind the view can be.
|
|
355
370
|
const agedFrom = sources.length ? oldest(sources.map(({ time }) => time))
|
|
356
371
|
: declared.oldestAge ? oldest(observations.units) ?? asOf : null;
|
|
@@ -443,6 +458,7 @@ function resolveFreshness({ declared, signals, observations, now }: FreshnessInp
|
|
|
443
458
|
...(partial ?? {}),
|
|
444
459
|
...(status === "not-a-feed" && declared.basis ? { basis: declared.basis } : {}),
|
|
445
460
|
...(periodicity ? { periodicity } : {}),
|
|
461
|
+
...(periodToDate && !declaredAsOf ? { periodToDate: { period: periodToDate.period, ...(periodToDate.through ? { through: isoTime(periodToDate.through) } : {}) } } : {}),
|
|
446
462
|
...(asOfClose ? { asOfClose } : {}),
|
|
447
463
|
...(tradingDayMarket ? { tradingDayMarket } : {}),
|
|
448
464
|
...(market ? { market } : {}),
|
|
@@ -550,6 +566,16 @@ function periodText(date: string, periodicity: NonNullable<ReportFreshness["peri
|
|
|
550
566
|
: `${year}`;
|
|
551
567
|
}
|
|
552
568
|
|
|
569
|
+
/**
|
|
570
|
+
* A bar still forming: `month to date, through Thu 15 Oct 2026`, or with no
|
|
571
|
+
* day to name, the period from its start, `Oct 2026 to date` or `week of Mon
|
|
572
|
+
* 12 Oct 2026 to date`.
|
|
573
|
+
*/
|
|
574
|
+
export function periodToDateText({ period, through }: NonNullable<ReportFreshness["periodToDate"]>, asOf: string): string {
|
|
575
|
+
if (through) return `${period} to date, through ${dayText(through)}`;
|
|
576
|
+
return period === "month" ? `${periodText(asOf, "monthly")} to date` : `week of ${dayText(asOf)} to date`;
|
|
577
|
+
}
|
|
578
|
+
|
|
553
579
|
/** The period a FRED series frequency ("Monthly", "Quarterly") publishes in; null for daily and weekly series, whose dates are days. */
|
|
554
580
|
export function periodicityOf(frequency: string | null | undefined): ReportFreshness["periodicity"] | null {
|
|
555
581
|
const word = frequency?.trim().toLowerCase() ?? "";
|
|
@@ -674,7 +700,8 @@ function asOfText(freshness: ReportFreshness, short: boolean, local: boolean, un
|
|
|
674
700
|
const market = freshness.tradingDayMarket;
|
|
675
701
|
const period = freshness.periodicity;
|
|
676
702
|
const time = parseReportTime(freshness.asOf);
|
|
677
|
-
const asOf =
|
|
703
|
+
const asOf = freshness.periodToDate ? periodToDateText(freshness.periodToDate, freshness.asOf)
|
|
704
|
+
: period ? `${periodText(freshness.asOf, period)} (${period})`
|
|
678
705
|
: freshness.asOfClose
|
|
679
706
|
? `${tradingDayText(freshness.asOfClose, market)} close`
|
|
680
707
|
: time?.dateOnly ? tradingDayText(freshness.asOf, market)
|
|
@@ -706,16 +733,22 @@ function sourcesAsOfParts(freshness: ReportFreshness, sources: readonly ReportSo
|
|
|
706
733
|
* When the first venue opens again: its UTC time when that is later the same
|
|
707
734
|
* UTC day, else its weekday at the venue (`Mon`), with the date a week out.
|
|
708
735
|
*/
|
|
709
|
-
function reopenText(market: ReportMarket, now: number, local: boolean): string | null {
|
|
736
|
+
function reopenText(market: ReportMarket, now: number, local: boolean): { text: string; time: boolean } | null {
|
|
710
737
|
if (!market.reopensAt || !market.reopensOn) return null;
|
|
711
738
|
const at = Date.parse(market.reopensAt);
|
|
712
739
|
if (!Number.isFinite(at)) return null;
|
|
713
740
|
const iso = new Date(at).toISOString();
|
|
714
|
-
if (iso.slice(0, 10) === new Date(now).toISOString().slice(0, 10)) return `${iso.slice(11, 16)} UTC${local ? localTimeSuffix(at) : ""}
|
|
715
|
-
return at - now > WEEKDAY_ONLY_MS ? dayText(market.reopensOn) : WEEKDAYS[new Date(`${market.reopensOn}T00:00:00Z`).getUTCDay()]
|
|
741
|
+
if (iso.slice(0, 10) === new Date(now).toISOString().slice(0, 10)) return { text: `${iso.slice(11, 16)} UTC${local ? localTimeSuffix(at) : ""}`, time: true };
|
|
742
|
+
return { text: at - now > WEEKDAY_ONLY_MS ? dayText(market.reopensOn) : WEEKDAYS[new Date(`${market.reopensOn}T00:00:00Z`).getUTCDay()]!, time: false };
|
|
716
743
|
}
|
|
717
744
|
|
|
718
|
-
/**
|
|
745
|
+
/**
|
|
746
|
+
* `markets closed until Mon`; `short` is the compact form a narrow capture
|
|
747
|
+
* falls back to (`reopens Mon`). A time is always the regular session's open,
|
|
748
|
+
* and says so (`pre-market open · regular session opens 13:30 UTC`), so it is
|
|
749
|
+
* never read as when the pre-market opens, nor a US market as closed until
|
|
750
|
+
* then while its pre-market trades first.
|
|
751
|
+
*/
|
|
719
752
|
function marketText(market: ReportMarket, now: number, short: boolean, local: boolean): string {
|
|
720
753
|
const reopen = reopenText(market, now, local);
|
|
721
754
|
switch (market.state) {
|
|
@@ -724,10 +757,12 @@ function marketText(market: ReportMarket, now: number, short: boolean, local: bo
|
|
|
724
757
|
case "after-hours":
|
|
725
758
|
return "after hours";
|
|
726
759
|
case "pre-market":
|
|
727
|
-
|
|
760
|
+
if (!reopen) return "pre-market open";
|
|
761
|
+
return short ? `pre-market · regular opens ${reopen.text}` : `pre-market open · regular session opens ${reopen.text}`;
|
|
728
762
|
case "closed":
|
|
729
|
-
if (
|
|
730
|
-
|
|
763
|
+
if (!reopen) return short ? "closed" : "markets closed";
|
|
764
|
+
if (reopen.time) return short ? `regular opens ${reopen.text}` : `markets closed · regular session opens ${reopen.text}`;
|
|
765
|
+
return short ? `reopens ${reopen.text}` : `markets closed until ${reopen.text}`;
|
|
731
766
|
default: {
|
|
732
767
|
const _exhaustive: never = market.state;
|
|
733
768
|
return _exhaustive;
|
|
@@ -68,10 +68,10 @@ const QUOTES: FunctionFreshness = { free: "15 minutes delayed, crypto real-time"
|
|
|
68
68
|
/** US option chains and the quotes behind them. */
|
|
69
69
|
const OPTIONS: FunctionFreshness = { free: DELAYED, pro: REAL_TIME };
|
|
70
70
|
const FX: FunctionFreshness = { free: DELAYED, pro: REAL_TIME };
|
|
71
|
-
/** Open interest
|
|
71
|
+
/** Open interest is counted once a session and reaches Gloom about two sessions late; spot and the quotes behind gamma follow the plan. */
|
|
72
72
|
const OPX_DATA: FunctionFreshness = {
|
|
73
|
-
free: "Open interest
|
|
74
|
-
pro: "Open interest
|
|
73
|
+
free: "Open interest about two sessions old, its date in the footer; spot and gamma 15 minutes delayed",
|
|
74
|
+
pro: "Open interest about two sessions old, its date in the footer; spot and gamma real-time",
|
|
75
75
|
};
|
|
76
76
|
const NEWS: FunctionFreshness = { free: "12 hours delayed", pro: REAL_TIME };
|
|
77
77
|
const US_SCANNER: FunctionFreshness = { free: DELAYED, pro: REAL_TIME };
|
|
@@ -175,7 +175,7 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
175
175
|
bloomberg: ["DDIS"],
|
|
176
176
|
},
|
|
177
177
|
HP: {
|
|
178
|
-
summary: "
|
|
178
|
+
summary: "Open, high, low, close and volume in a scrollable table: daily bars over 3M to start, weekly for 5Y and monthly for ALL. The newest bar is marked to date while its day, week or month runs on.",
|
|
179
179
|
usage: ["HP AAPL"],
|
|
180
180
|
keys: [key("t", " range")],
|
|
181
181
|
data: QUOTES,
|
|
@@ -241,14 +241,14 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
241
241
|
bloomberg: ["OMON"],
|
|
242
242
|
},
|
|
243
243
|
OPX: {
|
|
244
|
-
summary: "Open interest by strike for one expiry with max pain and spot marked, every expiry's open interest, put/call ratio and
|
|
244
|
+
summary: "Open interest by strike for one expiry with max pain and spot marked, every expiry's open interest, put/call ratio, max pain and net dealer gamma, and dealer gamma (GEX) by strike with its flip level, call and put walls and a range for the dealer assumption. MAXPAIN opens it too.",
|
|
245
245
|
usage: ["OPX", "OPX SPY", "OPX SPX"],
|
|
246
246
|
keys: [TABS, OPEN],
|
|
247
247
|
data: OPX_DATA,
|
|
248
248
|
bloomberg: ["OPX"],
|
|
249
249
|
},
|
|
250
250
|
GEX: {
|
|
251
|
-
summary: "Net dealer gamma by strike across every expiry or one, with the total, the flip level and a range for the dealer assumption. The GEX tab of OPX.",
|
|
251
|
+
summary: "Net dealer gamma by strike across every expiry or one, with the total, the flip level, the call and put walls and a range for the dealer assumption. The GEX tab of OPX.",
|
|
252
252
|
usage: ["GEX", "GEX SPY", "GEX SPX"],
|
|
253
253
|
keys: [TABS],
|
|
254
254
|
data: OPX_DATA,
|
|
@@ -756,7 +756,7 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
756
756
|
bloomberg: ["GLCO"],
|
|
757
757
|
},
|
|
758
758
|
CTM: {
|
|
759
|
-
summary: "A futures root's listed contracts as a curve against a week and a month ago, with roll yield, contango or backwardation, and each contract's price, open interest and volume. Takes a FUT root, VX or a CME crypto root (BTC, ETH, SOL, XRP), which also shows each contract's premium to spot and annualised basis against the USD pair quote.",
|
|
759
|
+
summary: "A futures root's listed contracts as a curve against a week and a month ago, with roll yield, contango or backwardation, and each contract's price, open interest and volume. Takes a FUT root, VX or a CME crypto root (BTC, ETH, SOL, XRP), which also shows each contract's premium to spot and annualised basis against the USD pair quote: Ann Basis = (Last / spot - 1) × 365 / days to expiry, simple actual/365.",
|
|
760
760
|
usage: ["CTM GC", "CTM BTC"],
|
|
761
761
|
keys: [key("d", "ate"), key("c", "urrent"), STEP],
|
|
762
762
|
data: same("Delayed, usually 10 minutes; VIX at settlement"),
|
|
@@ -1068,8 +1068,8 @@ export const FUNCTION_HELP: Readonly<Record<string, FunctionHelp>> = {
|
|
|
1068
1068
|
bloomberg: ["13F"],
|
|
1069
1069
|
},
|
|
1070
1070
|
INS: {
|
|
1071
|
-
summary: "Insider buys, sells, awards and gifts from Form 4 filings, with insider name, size and price. US equities.",
|
|
1072
|
-
usage: ["INS NVDA"],
|
|
1071
|
+
summary: "Insider buys, sells, awards and gifts from Form 4 filings, with insider name, size and price. US equities. Alone it ranks the companies whose insiders are buying in the open market, with why.",
|
|
1072
|
+
usage: ["INS NVDA", "INS"],
|
|
1073
1073
|
keys: [OPEN, key("f", "ilter")],
|
|
1074
1074
|
data: AS_FILED,
|
|
1075
1075
|
bloomberg: ["INS"],
|
|
@@ -286,6 +286,7 @@ function serializeBundleSection(
|
|
|
286
286
|
title: section.title,
|
|
287
287
|
columns: serializableColumns(columns),
|
|
288
288
|
rows: section.rows,
|
|
289
|
+
...(section.unit ? { unit: section.unit } : {}),
|
|
289
290
|
...(section.empty && section.rows.length === 0 ? { empty: section.empty } : {}),
|
|
290
291
|
};
|
|
291
292
|
}
|
|
@@ -410,9 +411,20 @@ function seriesPointExportDate(series: HeadlessSeries, date: SeriesPointDate): s
|
|
|
410
411
|
return isDateOnly(date) ? date as string : new Date(time).toISOString().replace(".000Z", "Z");
|
|
411
412
|
}
|
|
412
413
|
|
|
414
|
+
/**
|
|
415
|
+
* A latest value as the series report prints it: percentages and multiples
|
|
416
|
+
* (P/E, EV/EBITDA, one series over another) to two decimals, which a multiple
|
|
417
|
+
* under 0.1 trades for three significant digits so a small ratio is not
|
|
418
|
+
* flattened to 0.03. The exports keep every digit. Any other unit prints as
|
|
419
|
+
* loaded, without float noise.
|
|
420
|
+
*/
|
|
413
421
|
function seriesValue(series: HeadlessSeries, value: number | null): string {
|
|
414
422
|
if (value == null) return "-";
|
|
415
|
-
|
|
423
|
+
if (!Number.isFinite(value)) return displayNumber(value);
|
|
424
|
+
const unit = series.unit?.trim();
|
|
425
|
+
if (unit === "%") return value.toFixed(2);
|
|
426
|
+
if (unit?.toLowerCase() === "x") return value !== 0 && Math.abs(value) < 0.1 ? value.toPrecision(3) : value.toFixed(2);
|
|
427
|
+
return displayNumber(value);
|
|
416
428
|
}
|
|
417
429
|
|
|
418
430
|
function renderRows(
|
|
@@ -467,7 +479,7 @@ function renderBundle(
|
|
|
467
479
|
renderSection(section.title),
|
|
468
480
|
"rows" in section && section.rows
|
|
469
481
|
? renderRows(section.textRows ?? section.rows, section.textColumns ?? section.columns, definition.columns, section.empty)
|
|
470
|
-
: renderEntries(section.entries),
|
|
482
|
+
: renderEntries(section.textEntries ?? section.entries),
|
|
471
483
|
]);
|
|
472
484
|
}
|
|
473
485
|
|
|
@@ -618,11 +630,11 @@ export function headlessReportTables(
|
|
|
618
630
|
break;
|
|
619
631
|
}
|
|
620
632
|
case "bundle":
|
|
621
|
-
tables = (result as HeadlessBundleResult).sections.map((section) =>
|
|
622
|
-
"rows" in section && section.rows
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
)
|
|
633
|
+
tables = (result as HeadlessBundleResult).sections.map((section) => {
|
|
634
|
+
if (!("rows" in section && section.rows)) return exportEntriesTable(section.title, section.entries);
|
|
635
|
+
const table = exportRowsTable(section.title, displayedColumns(columnsFor(section.rows, section.columns, definition.columns)), section.rows);
|
|
636
|
+
return section.unit?.trim() ? { ...table, identity: { currencyOrUnit: section.unit.trim() } } : table;
|
|
637
|
+
});
|
|
626
638
|
break;
|
|
627
639
|
case "series": {
|
|
628
640
|
const seriesResult = result as HeadlessSeriesResult;
|