gloomberb 0.16.3 → 0.16.4
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 +4 -1
- package/src/api-client/askg.ts +43 -0
- package/src/api-client/auth.ts +15 -0
- package/src/api-client/beneficial-owners.ts +4 -0
- package/src/api-client/chat.ts +19 -0
- package/src/api-client/company-kpis.ts +14 -2
- package/src/api-client/data.ts +34 -16
- package/src/api-client/index.ts +12 -0
- package/src/api-client/normalizers.ts +27 -0
- package/src/api-client/paths.ts +88 -23
- package/src/api-client/request.ts +2 -1
- package/src/api-client/research-activity.ts +31 -0
- package/src/api-client/socket.ts +39 -8
- package/src/api-client/types.ts +57 -1
- package/src/api-client/upgrade-experiment.ts +90 -0
- package/src/app/global-shortcuts.ts +11 -1
- package/src/app/keybindings/actions.ts +10 -0
- package/src/app/pane-runtime/index.tsx +8 -0
- package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
- package/src/app/pane-runtime/plugin-bindings.ts +65 -25
- package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
- package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
- package/src/app/runtime/desktop-deeplink.ts +5 -0
- package/src/app/star-prompt/model.ts +98 -0
- package/src/app/star-prompt/runtime.ts +41 -0
- package/src/app/star-prompt/status-line.tsx +190 -0
- package/src/app.tsx +13 -7
- package/src/architecture/core-builtin-allowlist.json +70 -0
- package/src/brokers/signed-in/adapter.ts +33 -2
- package/src/brokers/signed-in/client.ts +10 -0
- package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
- package/src/brokers/signed-in/sign-in.ts +62 -5
- package/src/capabilities/registry.ts +8 -1
- package/src/capabilities/types.ts +2 -0
- package/src/cli/commands/crypto-hints.ts +12 -4
- package/src/cli/commands/market.ts +359 -99
- package/src/cli/commands/options-chain.ts +172 -0
- package/src/cli/commands/options-leaps.ts +422 -0
- package/src/cli/commands/overview.ts +164 -26
- package/src/cli/commands/search.ts +29 -8
- package/src/cli/commands/system.ts +99 -19
- package/src/cli/commands/themes.ts +64 -0
- package/src/cli/commands/ticker.ts +62 -30
- package/src/cli/desktop-pane-shot.ts +97 -16
- package/src/cli/errors.ts +25 -2
- package/src/cli/freshness.ts +7 -4
- package/src/cli/fx-pair.ts +57 -0
- package/src/cli/help.ts +14 -1
- package/src/cli/index-roots.ts +51 -0
- package/src/cli/index.ts +9 -6
- package/src/cli/listing-arg.ts +211 -3
- package/src/cli/not-a-ticker.ts +37 -0
- package/src/cli/options.ts +50 -6
- package/src/cli/pane-functions/capabilities.ts +8 -2
- package/src/cli/pane-functions/catalog.ts +13 -2
- package/src/cli/pane-functions/data.ts +36 -5
- package/src/cli/pane-functions/dom.ts +21 -13
- package/src/cli/pane-functions/freshness.ts +205 -26
- package/src/cli/pane-functions/function-help.ts +67 -22
- package/src/cli/pane-functions/headless.ts +5 -4
- package/src/cli/pane-functions/index.ts +3 -0
- package/src/cli/pane-functions/market-session.ts +133 -0
- package/src/cli/pane-functions/options.ts +15 -4
- package/src/cli/pane-functions/report-notices.ts +63 -0
- package/src/cli/pane-functions/report.ts +4 -0
- package/src/cli/pane-functions/resolver.ts +4 -6
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +105 -23
- package/src/cli/registry.ts +2 -1
- package/src/cli/result.ts +50 -13
- package/src/cli/row-window.ts +76 -0
- package/src/cli/scroll-truncation.ts +34 -0
- package/src/components/chart/composite/composite-chart.tsx +43 -4
- package/src/components/chart/composite/legend.tsx +39 -6
- package/src/components/chart/composite/rasterizer.ts +64 -4
- package/src/components/chart/composite/series-cues.ts +71 -0
- package/src/components/chart/composite/text-renderer.ts +16 -8
- package/src/components/chart/composite/types.ts +3 -0
- package/src/components/command-bar/commands/direct/index.ts +9 -1
- package/src/components/command-bar/commands/plugin/items.ts +3 -1
- package/src/components/command-bar/commands/registry.ts +7 -0
- package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/routes/root/command-items.ts +2 -0
- package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
- package/src/components/command-bar/routes/root/results.ts +40 -10
- package/src/components/command-bar/routes/root/runtime.ts +8 -0
- package/src/components/command-bar/routes/root/search-providers.ts +97 -14
- package/src/components/command-bar/surface/index.tsx +18 -4
- package/src/components/command-bar/theme-picker.tsx +48 -13
- package/src/components/command-bar/workflow/ops.ts +5 -3
- package/src/components/data-table/view.tsx +28 -9
- package/src/components/layout/floating-pane.tsx +6 -2
- package/src/components/layout/market-summary.ts +23 -9
- package/src/components/layout/pane/content.tsx +46 -29
- package/src/components/layout/pane/cycle-order.ts +2 -11
- package/src/components/layout/pane/header.tsx +2 -1
- package/src/components/layout/pane/title.ts +1 -1
- package/src/components/layout/presentation.ts +22 -0
- package/src/components/layout/shell/active-drag.ts +1 -1
- package/src/components/layout/shell/drag/index.ts +4 -0
- package/src/components/layout/shell/drag/live.ts +76 -5
- package/src/components/layout/shell/drag/runtime.ts +8 -8
- package/src/components/layout/shell/index.tsx +47 -6
- package/src/components/layout/shell/layout-state.ts +5 -11
- package/src/components/layout/shell/menu.ts +28 -1
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +51 -10
- package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
- package/src/components/layout/shell/pane/new-layout.ts +116 -0
- package/src/components/layout/shell/shortcuts.ts +5 -2
- package/src/components/layout/shell/visible-layout.ts +44 -1
- package/src/components/layout/status-bar.tsx +4 -1
- package/src/components/markdown-text.tsx +9 -5
- package/src/components/metric-treemap/heat-surface.tsx +27 -8
- package/src/components/metric-treemap/heat-tiles.ts +48 -17
- package/src/components/onboarding/onboarding-frame.tsx +33 -10
- package/src/components/onboarding/onboarding-wizard.tsx +49 -40
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
- package/src/components/onboarding/pro-step-layout.ts +86 -0
- package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
- package/src/components/ui/button.tsx +10 -0
- package/src/components/ui/data-table/index.tsx +38 -0
- package/src/components/ui/data-table/types.ts +8 -1
- package/src/components/ui/data-table/visible-range.ts +2 -0
- package/src/components/ui/icon.tsx +11 -2
- package/src/components/ui/index.ts +1 -1
- package/src/core/connection-health.ts +1 -1
- package/src/core/state/app/layout-reducer.ts +89 -3
- package/src/core/state/app/state.ts +8 -5
- package/src/core/state/app/types.ts +13 -2
- package/src/data/config/layout.ts +55 -21
- package/src/data/config/store/fold-panes.ts +149 -0
- package/src/data/config/store/migrations.ts +7 -0
- package/src/data/config/store/node.ts +27 -6
- package/src/data/config/store/normalize.ts +37 -3
- package/src/i18n/es.ts +14 -1
- package/src/i18n/ja.ts +14 -1
- package/src/i18n/ko.ts +14 -1
- package/src/i18n/zh-cn.ts +14 -1
- package/src/i18n/zh-tw.ts +14 -1
- package/src/layout/desks.ts +1 -2
- package/src/layout/pane-layout-move.ts +264 -0
- package/src/layout/pane-manager/hidden-panes.ts +160 -0
- package/src/layout/pane-manager/index.ts +1 -0
- package/src/market-data/coordinator/entries.ts +7 -1
- package/src/market-data/coordinator/quotes.ts +2 -2
- package/src/market-data/market/freshness.ts +118 -18
- package/src/market-data/market/status.ts +40 -0
- package/src/market-data/options-alternatives.ts +69 -0
- package/src/market-data/quotes/contributions.ts +19 -2
- package/src/market-data/quotes/metadata.ts +16 -0
- package/src/market-data/quotes/screener-live-quotes.ts +56 -5
- package/src/plugins/builtin/account-management/keyboard.ts +3 -0
- package/src/plugins/builtin/account-management/model.ts +1 -0
- package/src/plugins/builtin/account-management/navigation.ts +1 -1
- package/src/plugins/builtin/account-management/pane.tsx +31 -4
- package/src/plugins/builtin/alerts/quotes.ts +3 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
- package/src/plugins/builtin/analytics/risk-model.ts +37 -8
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
- package/src/plugins/builtin/backtest/pane.tsx +1 -1
- package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/broker-manager/model.ts +8 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
- package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
- package/src/plugins/builtin/broker-manager/table.ts +5 -0
- package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
- package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
- package/src/plugins/builtin/chat/channels.ts +34 -36
- package/src/plugins/builtin/chat/command-bar.ts +138 -0
- package/src/plugins/builtin/chat/composer-commands.ts +21 -1
- package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
- package/src/plugins/builtin/chat/content/footer.tsx +5 -3
- package/src/plugins/builtin/chat/content/index.tsx +21 -1
- package/src/plugins/builtin/chat/content/mentions.ts +2 -1
- package/src/plugins/builtin/chat/content/user-map.ts +3 -1
- package/src/plugins/builtin/chat/controller/index.ts +48 -0
- package/src/plugins/builtin/chat/conversations.ts +256 -0
- package/src/plugins/builtin/chat/discord-command.ts +91 -0
- package/src/plugins/builtin/chat/edit-window.ts +12 -6
- package/src/plugins/builtin/chat/ghost-user.ts +14 -0
- package/src/plugins/builtin/chat/index.tsx +40 -2
- package/src/plugins/builtin/chat/layout.ts +2 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
- package/src/plugins/builtin/chat/message/header.tsx +24 -15
- package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
- package/src/plugins/builtin/chat/message/render-state.ts +3 -0
- package/src/plugins/builtin/chat/pane-requests.ts +60 -0
- package/src/plugins/builtin/chat/pane-state.ts +32 -2
- package/src/plugins/builtin/chat/pane.tsx +8 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
- package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
- package/src/plugins/builtin/cloud/askg/model.ts +129 -70
- package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
- package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
- package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
- package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
- package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
- package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
- package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
- package/src/plugins/builtin/cloud/plugin.tsx +36 -1
- package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
- package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
- package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
- package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
- package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
- package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
- package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
- package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
- package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
- package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
- package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
- package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
- package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
- package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
- package/src/plugins/builtin/comment-letters/client.ts +1 -3
- package/src/plugins/builtin/company-kpis/client.ts +23 -8
- package/src/plugins/builtin/company-kpis/headless.ts +3 -1
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
- package/src/plugins/builtin/composite-plugins.ts +107 -64
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
- package/src/plugins/builtin/credit-boards/index.tsx +21 -21
- package/src/plugins/builtin/credit-boards/model.ts +0 -1
- package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
- package/src/plugins/builtin/debt-maturities/client.ts +37 -9
- package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/data.ts +28 -7
- package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
- package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
- package/src/plugins/builtin/econ/calendar-model.ts +42 -17
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/index.tsx +68 -22
- package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +17 -0
- package/src/plugins/builtin/executives/data.ts +17 -6
- package/src/plugins/builtin/executives/pane.tsx +16 -7
- package/src/plugins/builtin/filing-events/pane.tsx +22 -5
- package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
- package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
- package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
- package/src/plugins/builtin/gpu/headless.ts +9 -3
- package/src/plugins/builtin/gpu/model.ts +14 -5
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
- package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
- package/src/plugins/builtin/holders/headless.ts +6 -1
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/insider/index.tsx +4 -4
- package/src/plugins/builtin/iv-history/charts.tsx +21 -7
- package/src/plugins/builtin/iv-history/client.ts +12 -2
- package/src/plugins/builtin/iv-history/format.ts +33 -2
- package/src/plugins/builtin/iv-history/headless.ts +22 -10
- package/src/plugins/builtin/iv-history/index.tsx +3 -3
- package/src/plugins/builtin/iv-history/model.ts +61 -13
- package/src/plugins/builtin/iv-history/pane.tsx +11 -3
- package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
- package/src/plugins/builtin/jobs/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
- package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +109 -39
- package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
- package/src/plugins/builtin/layout-manager/index.tsx +9 -1
- package/src/plugins/builtin/macro-day/client.ts +24 -0
- package/src/plugins/builtin/macro-day/headless.ts +11 -8
- package/src/plugins/builtin/macro-day/index.tsx +3 -0
- package/src/plugins/builtin/macro-day/model.ts +11 -6
- package/src/plugins/builtin/macro-day/pane.tsx +12 -8
- package/src/plugins/builtin/macro-day/release-table.ts +40 -0
- package/src/plugins/builtin/macro-day/releases.ts +62 -44
- package/src/plugins/builtin/market-heatmap/data.ts +25 -5
- package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
- package/src/plugins/builtin/market-heatmap/model.ts +94 -3
- package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
- package/src/plugins/builtin/market-movers/headless.ts +31 -4
- package/src/plugins/builtin/market-movers/index.tsx +8 -1
- package/src/plugins/builtin/market-movers/model.ts +71 -17
- package/src/plugins/builtin/market-movers/table.ts +50 -23
- package/src/plugins/builtin/news/headless.ts +9 -2
- package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
- package/src/plugins/builtin/openfda/client.ts +1 -1
- package/src/plugins/builtin/options/carry.ts +95 -0
- package/src/plugins/builtin/options/index.tsx +58 -5
- package/src/plugins/builtin/options/strike-window.ts +183 -0
- package/src/plugins/builtin/options/table.ts +55 -3
- package/src/plugins/builtin/options/types.ts +8 -1
- package/src/plugins/builtin/options/view.tsx +161 -12
- package/src/plugins/builtin/options-positioning/headless.ts +4 -2
- package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
- package/src/plugins/builtin/options-scenario/client.ts +73 -17
- package/src/plugins/builtin/options-scenario/headless.ts +71 -24
- package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
- package/src/plugins/builtin/options-scenario/index.tsx +9 -2
- package/src/plugins/builtin/options-scenario/model.ts +33 -0
- package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
- package/src/plugins/builtin/pe-band/model.ts +2 -2
- package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
- package/src/plugins/builtin/perps/equity-row.tsx +8 -5
- package/src/plugins/builtin/perps/model.ts +16 -5
- package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
- package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
- package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
- package/src/plugins/builtin/plugin-module.ts +87 -4
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
- package/src/plugins/builtin/portfolio-list/cli/render.ts +87 -18
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
- package/src/plugins/builtin/portfolio-list/headless.ts +38 -7
- package/src/plugins/builtin/realized-vol/headless.ts +3 -0
- package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
- package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
- package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
- package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
- package/src/plugins/builtin/risk-factors/data.ts +23 -9
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
- package/src/plugins/builtin/seasonality/pane.tsx +1 -1
- package/src/plugins/builtin/sec/index.tsx +5 -5
- package/src/plugins/builtin/sectors/client.ts +10 -2
- package/src/plugins/builtin/sectors/headless.ts +35 -6
- package/src/plugins/builtin/sectors/index.tsx +18 -1
- package/src/plugins/builtin/sectors/sector-model.ts +150 -41
- package/src/plugins/builtin/shared/command-tab.ts +51 -0
- package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
- package/src/plugins/builtin/shared/members-menu.tsx +4 -4
- package/src/plugins/builtin/shared/report-freshness.ts +50 -2
- package/src/plugins/builtin/shared/ticker-request.ts +25 -1
- package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
- package/src/plugins/builtin/shared/volatility/options.ts +1 -46
- package/src/plugins/builtin/short-interest/index.tsx +19 -4
- package/src/plugins/builtin/short-interest/surface.tsx +4 -4
- package/src/plugins/builtin/short-volume/index.tsx +1 -10
- package/src/plugins/builtin/supply-chain/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/headless.ts +43 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
- package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
- package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
- package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
- package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
- package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
- package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
- package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
- package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
- package/src/plugins/builtin/vol-surface/client.ts +6 -2
- package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
- package/src/plugins/builtin/vol-surface/headless.ts +124 -20
- package/src/plugins/builtin/vol-surface/model.ts +23 -11
- package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
- package/src/plugins/builtin/vol-surface/tables.ts +124 -0
- package/src/plugins/builtin/world-indices/client.ts +22 -4
- package/src/plugins/builtin/world-indices/headless.ts +54 -27
- package/src/plugins/builtin/world-indices/model.ts +4 -2
- package/src/plugins/builtin/world-indices/table.tsx +13 -2
- package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
- package/src/plugins/builtin/world-venue-map/index.ts +1 -1
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/builtin-editorial.ts +126 -0
- package/src/plugins/catalog-backend.ts +16 -9
- package/src/plugins/catalog-browser.ts +127 -63
- package/src/plugins/catalog-manifest.ts +73 -40
- package/src/plugins/catalog-ui.ts +26 -6
- package/src/plugins/extracted-plugins.ts +7 -1
- package/src/plugins/ownership.ts +150 -7
- package/src/plugins/plugin-functions.ts +54 -0
- package/src/plugins/plugin-off.ts +36 -0
- package/src/plugins/registry/host-actions.ts +16 -0
- package/src/plugins/registry/index.ts +84 -24
- package/src/plugins/registry/plugin-state.ts +25 -1
- package/src/plugins/runtime/context.tsx +29 -0
- package/src/react/input.ts +48 -1
- package/src/remote/app-host.tsx +5 -0
- package/src/remote/controller.ts +3 -53
- package/src/remote/matching.ts +69 -0
- package/src/remote/schema.ts +1 -1
- package/src/remote/semantic-tree.tsx +14 -0
- package/src/remote/types.ts +2 -0
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/query-bar.tsx +59 -32
- package/src/renderers/dom/host/box.tsx +1 -1
- package/src/renderers/dom/host/drag-shield.ts +17 -6
- package/src/renderers/dom/host/live-frame.ts +10 -2
- package/src/renderers/dom/input-host.tsx +6 -1
- package/src/renderers/dom/styles.css +13 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +5 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
- package/src/renderers/electrobun/view/main.tsx +4 -1
- package/src/renderers/opentui/input-host.tsx +6 -1
- package/src/renderers/opentui/start.tsx +9 -0
- package/src/shares/portable-layout.ts +16 -6
- package/src/sources/gloomberb-cloud/index.ts +19 -10
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -1
- package/src/sources/listing-symbols.ts +11 -5
- package/src/sources/provider-errors.ts +88 -3
- package/src/sources/provider-router/cached-routes.ts +8 -4
- package/src/sources/provider-router/financial-routes.ts +11 -5
- package/src/sources/provider-router/financials.ts +42 -15
- package/src/sources/provider-router/history.ts +27 -11
- package/src/sources/provider-router/index.ts +1 -1
- package/src/sources/provider-router/primary.ts +23 -5
- package/src/sources/sec-edgar.ts +68 -8
- package/src/sync/core-contributors.ts +4 -3
- package/src/test-support/cli-context.ts +2 -1
- package/src/test-support/data-provider.ts +34 -0
- package/src/test-support/shell-pane-keys.tsx +1 -0
- package/src/theme/series-colors.ts +46 -0
- package/src/theme/themes.ts +149 -0
- package/src/tickers/search/index.ts +22 -2
- package/src/tickers/search/ranking.ts +45 -1
- package/src/tickers/search/types.ts +3 -1
- package/src/time-series/resolve.ts +1 -14
- package/src/types/config.ts +61 -1
- package/src/types/headless.ts +11 -0
- package/src/types/instrument.ts +8 -0
- package/src/types/plugin.ts +49 -3
- package/src/ui/host.tsx +8 -4
- package/src/ui/index.tsx +2 -0
- package/src/utils/cli-output.ts +14 -1
- package/src/utils/exchanges.ts +13 -0
- package/src/utils/format.ts +2 -1
- package/src/utils/fx-market-hours.ts +34 -0
- package/src/utils/option-expiry.ts +126 -0
- package/src/utils/price-history.ts +39 -6
- package/src/utils/rpc-timeout-error.ts +6 -2
- package/src/utils/sec-eps-basis.ts +159 -30
- package/src/utils/sec.ts +38 -34
- package/src/utils/throttled-fetch.ts +51 -22
|
@@ -39,13 +39,13 @@ import { normalizeNewsFeed } from "../../news/news-model";
|
|
|
39
39
|
import { resolveCurrencyUnit } from "../../utils/currency-units";
|
|
40
40
|
import { canonicalExchange, canonicalTickerKey, parsePublicTickerKey } from "../../utils/exchanges";
|
|
41
41
|
import { normalizePriceHistory, priceHistoryIntervalMs, reachesLatestSettledSession } from "../../utils/price-history";
|
|
42
|
-
import { createProviderMiss } from "../provider-errors";
|
|
42
|
+
import { createProviderMiss, providerMissReason } from "../provider-errors";
|
|
43
43
|
import { assertSecRegistrantMatches } from "../sec-registrant";
|
|
44
44
|
import { nonUsSecListingVenue } from "../../utils/sec";
|
|
45
45
|
import { publicListingTarget } from "../listing-target";
|
|
46
46
|
import { canonicalHistoryInterval, HistoryRetentionError, parseHistoryRecoveryCandidate, parseHistoryRetention, type HistoryRetention } from "../history-retention";
|
|
47
47
|
import { getRouterEntityKey } from "../provider-router/cache";
|
|
48
|
-
import { tickerHasListingSuffix } from "../listing-symbols";
|
|
48
|
+
import { hongKongListingCode, tickerHasListingSuffix } from "../listing-symbols";
|
|
49
49
|
import { parseSecAcceptanceTime } from "../sec-edgar/acceptance-time";
|
|
50
50
|
import { hasMalformedIntradayHistory } from "../../time-series/history-quality";
|
|
51
51
|
import {
|
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
formatCloudDateTime,
|
|
58
58
|
isEmptyCloudStatus,
|
|
59
59
|
mapBatchError,
|
|
60
|
+
NOT_FOUND_REASON,
|
|
60
61
|
mapCloudFinancials,
|
|
61
62
|
mapOptionsChain,
|
|
62
63
|
mapPricePoint,
|
|
@@ -124,7 +125,7 @@ async function withCloudFallback<T>(load: () => Promise<T>, message: string): Pr
|
|
|
124
125
|
return await load();
|
|
125
126
|
} catch (error) {
|
|
126
127
|
if (isCloudProviderMiss(error)) {
|
|
127
|
-
throw createProviderMiss(message);
|
|
128
|
+
throw createProviderMiss(message, providerMissReason(error));
|
|
128
129
|
}
|
|
129
130
|
throw error;
|
|
130
131
|
}
|
|
@@ -237,7 +238,7 @@ function cloudHistoryResolution(interval: string): ManualChartResolution | null
|
|
|
237
238
|
}
|
|
238
239
|
|
|
239
240
|
function quoteTargetKey(symbol: string, exchange?: string): string {
|
|
240
|
-
const target =
|
|
241
|
+
const target = cloudInstrumentTarget(symbol, exchange);
|
|
241
242
|
const base = target.exchange && tickerHasListingSuffix(target.symbol) ? target.symbol.slice(0, target.symbol.indexOf(".")) : target.symbol;
|
|
242
243
|
return canonicalTickerKey(base, target.exchange);
|
|
243
244
|
}
|
|
@@ -251,7 +252,14 @@ function cloudResponseTargetKey(key: string, requested: Set<string>): string | u
|
|
|
251
252
|
? symbol : undefined;
|
|
252
253
|
}
|
|
253
254
|
|
|
254
|
-
|
|
255
|
+
/**
|
|
256
|
+
* The listing as Gloom Cloud knows it. A Hong Kong code goes out with its four
|
|
257
|
+
* digits: Cloud has no 700 or 5 on HKEX, only 0700 and 0005.
|
|
258
|
+
*/
|
|
259
|
+
function cloudInstrumentTarget(symbol: string, exchange?: string) {
|
|
260
|
+
const target = publicListingTarget(symbol, exchange);
|
|
261
|
+
return target.exchange ? { ...target, symbol: hongKongListingCode(target.symbol, target.exchange) } : target;
|
|
262
|
+
}
|
|
255
263
|
|
|
256
264
|
function cloudHistoryRecovery(
|
|
257
265
|
context: MarketDataRequestContext | undefined,
|
|
@@ -323,7 +331,7 @@ function unwrapRequiredCloudResponse<T>(response: CloudMarketResponse<T>, messag
|
|
|
323
331
|
return response.data;
|
|
324
332
|
}
|
|
325
333
|
if (isEmptyCloudStatus(response.status)) {
|
|
326
|
-
throw createProviderMiss(response.reasonCode ?? message);
|
|
334
|
+
throw createProviderMiss(response.reasonCode ?? message, response.message, { notFound: response.reasonCode === NOT_FOUND_REASON });
|
|
327
335
|
}
|
|
328
336
|
throw new Error(response.reasonCode ?? message);
|
|
329
337
|
}
|
|
@@ -404,13 +412,14 @@ export class GloomberbCloudProvider implements AssetDataProvider {
|
|
|
404
412
|
return withCloudFallback(
|
|
405
413
|
async () => {
|
|
406
414
|
const response = await apiClient.getCloudQuote(target.symbol, target.exchange);
|
|
407
|
-
|
|
408
|
-
throw createProviderMiss(`Cloud quotes are stale for ${ticker}`);
|
|
409
|
-
}
|
|
410
|
-
return retainRequestedQuoteSymbol(mapQuote(
|
|
415
|
+
const quote = retainRequestedQuoteSymbol(mapQuote(
|
|
411
416
|
unwrapRequiredCloudResponse(response, `Cloud quotes are unavailable for ${ticker}`),
|
|
412
417
|
response.providerMeta,
|
|
413
418
|
), ticker);
|
|
419
|
+
// A stale answer is the last price the service has. It stays flagged, so
|
|
420
|
+
// no freshness check passes it and only a request with no current quote
|
|
421
|
+
// from any source falls back to it.
|
|
422
|
+
return isStaleCloudResponse(response) ? { ...quote, stale: true } : quote;
|
|
414
423
|
},
|
|
415
424
|
`Cloud quotes are unavailable for ${ticker}`,
|
|
416
425
|
);
|
|
@@ -306,6 +306,9 @@ export function mapOptionsChain(
|
|
|
306
306
|
};
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
+
/** The reason code Gloom Cloud gives an empty answer for a symbol no listing carries. */
|
|
310
|
+
export const NOT_FOUND_REASON = "NOT_FOUND";
|
|
311
|
+
|
|
309
312
|
export function isEmptyCloudStatus(
|
|
310
313
|
status: CloudMarketResponse<unknown>["status"],
|
|
311
314
|
): boolean {
|
|
@@ -317,7 +320,7 @@ export function mapBatchError<T>(
|
|
|
317
320
|
fallbackMessage: string,
|
|
318
321
|
): Error {
|
|
319
322
|
if (isEmptyCloudStatus(item.status)) {
|
|
320
|
-
return createProviderMiss(item.reasonCode ?? fallbackMessage);
|
|
323
|
+
return createProviderMiss(item.reasonCode ?? fallbackMessage, item.message, { notFound: item.reasonCode === NOT_FOUND_REASON });
|
|
321
324
|
}
|
|
322
325
|
return new Error(item.reasonCode ?? fallbackMessage);
|
|
323
326
|
}
|
|
@@ -23,7 +23,7 @@ const EXCHANGE_SUFFIX_MAP: Record<string, string> = {
|
|
|
23
23
|
TASE: ".TA",
|
|
24
24
|
JSE: ".JO",
|
|
25
25
|
BVMF: ".SA", MEXI: ".MX", BYMA: ".BA", BCS: ".SN",
|
|
26
|
-
TADAWUL: ".
|
|
26
|
+
TADAWUL: ".SR", QE: ".QA", DFM: ".AE",
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
const EXCHANGE_FALLBACKS: Record<string, string[]> = {
|
|
@@ -143,11 +143,17 @@ export function listingSuffixExchange(ticker: string): string | undefined {
|
|
|
143
143
|
return venues.size === 1 ? [...venues][0] : undefined;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* A Hong Kong listing's four-digit code. Brokers such as Interactive Brokers
|
|
148
|
+
* report Tencent as 700 and HSBC as 5; the exchange and market data know
|
|
149
|
+
* them as 0700 and 0005.
|
|
150
|
+
*/
|
|
151
|
+
export function hongKongListingCode(ticker: string, exchange: string): string {
|
|
152
|
+
return isHongKongExchange(exchange) && /^\d+$/.test(ticker) ? ticker.padStart(4, "0") : ticker;
|
|
153
|
+
}
|
|
154
|
+
|
|
146
155
|
function normalizeListingTicker(ticker: string, exchange: string): string {
|
|
147
|
-
|
|
148
|
-
return ticker.padStart(4, "0");
|
|
149
|
-
}
|
|
150
|
-
return ticker.replace(/ /g, "-");
|
|
156
|
+
return hongKongListingCode(ticker, exchange).replace(/ /g, "-");
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
function isHongKongExchange(exchange: string): boolean {
|
|
@@ -1,17 +1,45 @@
|
|
|
1
1
|
const PROVIDER_MISS_BRAND = Symbol("provider-miss");
|
|
2
2
|
const EXPECTED_PROVIDER_MISS = /No data found|symbol may be delisted|"code":"Not Found"|No history for /i;
|
|
3
3
|
|
|
4
|
+
const MAX_REASON_LENGTH = 200;
|
|
5
|
+
// Whole ANSI sequences go first, so their printable tail does not outlive the escape byte.
|
|
6
|
+
const ANSI_SEQUENCE = /\u001b(?:\[[0-9;?]*[ -/]*[@-~]|\][^\u0007\u001b]*(?:\u0007|\u001b\\)?)/g;
|
|
7
|
+
const LINE_BREAKS = /[\r\n\t\v\f\u0085\u2028\u2029]+/g;
|
|
8
|
+
// Remaining controls, and the bidirectional overrides that can reorder text in a terminal.
|
|
9
|
+
const CONTROL_CHARACTERS = /[\p{Cc}\u200e\u200f\u202a-\u202e\u2066-\u2069]/gu;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A reason a data service wrote for an empty answer, made safe to show: one
|
|
13
|
+
* line, no control characters, at most 200 characters. Anything that is not a
|
|
14
|
+
* non-empty string has no reason.
|
|
15
|
+
*/
|
|
16
|
+
export function cleanProviderReason(value: unknown): string | undefined {
|
|
17
|
+
if (typeof value !== "string") return undefined;
|
|
18
|
+
const line = value.replace(ANSI_SEQUENCE, "").replace(LINE_BREAKS, " ").replace(CONTROL_CHARACTERS, "").replace(/\s+/g, " ").trim();
|
|
19
|
+
if (!line) return undefined;
|
|
20
|
+
const characters = Array.from(line);
|
|
21
|
+
return characters.length > MAX_REASON_LENGTH
|
|
22
|
+
? `${characters.slice(0, MAX_REASON_LENGTH - 1).join("").trimEnd()}…`
|
|
23
|
+
: line;
|
|
24
|
+
}
|
|
25
|
+
|
|
4
26
|
export class ProviderMissError extends Error {
|
|
5
27
|
readonly [PROVIDER_MISS_BRAND] = true;
|
|
28
|
+
/** Why the service has nothing, in its own words, when it said. */
|
|
29
|
+
readonly reason?: string;
|
|
30
|
+
/** The service found no listing for the symbol: it is not a ticker, which differs from a ticker it has no data for. */
|
|
31
|
+
readonly notFound: boolean;
|
|
6
32
|
|
|
7
|
-
constructor(message = "Provider could not satisfy request") {
|
|
33
|
+
constructor(message = "Provider could not satisfy request", reason?: unknown, options: { notFound?: boolean } = {}) {
|
|
8
34
|
super(message);
|
|
9
35
|
this.name = "ProviderMissError";
|
|
36
|
+
this.reason = cleanProviderReason(reason);
|
|
37
|
+
this.notFound = options.notFound === true;
|
|
10
38
|
}
|
|
11
39
|
}
|
|
12
40
|
|
|
13
|
-
export function createProviderMiss(message?: string): ProviderMissError {
|
|
14
|
-
return new ProviderMissError(message);
|
|
41
|
+
export function createProviderMiss(message?: string, reason?: unknown, options?: { notFound?: boolean }): ProviderMissError {
|
|
42
|
+
return new ProviderMissError(message, reason, options);
|
|
15
43
|
}
|
|
16
44
|
|
|
17
45
|
function isProviderMiss(error: unknown): error is ProviderMissError {
|
|
@@ -19,6 +47,63 @@ function isProviderMiss(error: unknown): error is ProviderMissError {
|
|
|
19
47
|
|| (!!error && typeof error === "object" && PROVIDER_MISS_BRAND in error);
|
|
20
48
|
}
|
|
21
49
|
|
|
50
|
+
/** The reason a provider gave for a miss, or undefined when it gave none. */
|
|
51
|
+
export function providerMissReason(error: unknown): string | undefined {
|
|
52
|
+
return isProviderMiss(error) ? error.reason : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Collects the latest reason across the providers of one request. */
|
|
56
|
+
export interface ProviderMissNote {
|
|
57
|
+
reason?: string;
|
|
58
|
+
/** True while every provider that failed said it found no listing; one that failed another way (a timeout) clears it. */
|
|
59
|
+
notFound?: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function noteProviderMiss(note: ProviderMissNote | undefined, error: unknown): void {
|
|
63
|
+
if (!note) return;
|
|
64
|
+
const reason = providerMissReason(error);
|
|
65
|
+
if (reason) note.reason = reason;
|
|
66
|
+
note.notFound = note.notFound !== false && isProviderMiss(error) && error.notFound === true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** A provider answered with data the router could not use: whatever else is missing, the symbol exists. */
|
|
70
|
+
export function noteProviderAnswer(note: ProviderMissNote | undefined): void {
|
|
71
|
+
if (note) note.notFound = false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const NOT_A_TICKER = /^Not a ticker: (.+)\.$/;
|
|
75
|
+
|
|
76
|
+
/** "Not a ticker: APPLE.", what a request for a symbol no listing carries ends in. */
|
|
77
|
+
function notATickerMessage(symbol: string): string {
|
|
78
|
+
return `Not a ticker: ${symbol}.`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The symbol of a "Not a ticker: APPLE." message, or null for any other text. */
|
|
82
|
+
export function notATickerSymbol(message: string): string | null {
|
|
83
|
+
return NOT_A_TICKER.exec(message.split("\n")[0] ?? "")?.[1] ?? null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Whether a message is the router's answer that no source serves a symbol: "No history provider
|
|
88
|
+
* available for 2222", or "Not a ticker: 2222." when the service found no listing for it.
|
|
89
|
+
* A timeout or any other failure is not.
|
|
90
|
+
*/
|
|
91
|
+
export function isNoProviderMessage(message: string): boolean {
|
|
92
|
+
return /\bprovider available for\b/i.test(message) || notATickerSymbol(message) !== null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The router's final error when no provider answered. A reason a provider gave
|
|
97
|
+
* replaces the generic wording, and so does the service's answer that no
|
|
98
|
+
* listing exists for `symbol` ("Not a ticker"); a timeout or any other failure
|
|
99
|
+
* keeps the generic message.
|
|
100
|
+
*/
|
|
101
|
+
export function noProviderError(message: string, note: ProviderMissNote | undefined, symbol?: string): Error {
|
|
102
|
+
if (note?.reason) return createProviderMiss(note.reason, note.reason);
|
|
103
|
+
if (note?.notFound === true && symbol) return createProviderMiss(notATickerMessage(symbol), undefined, { notFound: true });
|
|
104
|
+
return new Error(message);
|
|
105
|
+
}
|
|
106
|
+
|
|
22
107
|
export function shouldLogProviderError(error: unknown): boolean {
|
|
23
108
|
if (isProviderMiss(error)) return false;
|
|
24
109
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -4,7 +4,7 @@ import type { CachedAssetArgs, CachedAssetMethod, CachedAssetValue, DataProvider
|
|
|
4
4
|
import type { AnalystResearchData, CorporateActionsData, HolderData } from "../../types/financials";
|
|
5
5
|
import { canonicalExchange } from "../../utils/exchanges";
|
|
6
6
|
import { nonUsSecListingVenue } from "../../utils/sec";
|
|
7
|
-
import { shouldLogProviderError } from "../provider-errors";
|
|
7
|
+
import { noProviderError, noteProviderMiss, shouldLogProviderError, type ProviderMissNote } from "../provider-errors";
|
|
8
8
|
import { hasBrokerContext, withBrokerTimeout } from "./brokers";
|
|
9
9
|
import { publicListingExchange } from "../listing-target";
|
|
10
10
|
import { buildVariantKey, compactUrl, listCachedResources, resolveCachePolicy, type ProviderRouterCachePolicyKey } from "./cache";
|
|
@@ -18,6 +18,8 @@ interface Route {
|
|
|
18
18
|
variants: string[];
|
|
19
19
|
request: (provider: DataProvider, force: boolean) => Promise<unknown> | undefined;
|
|
20
20
|
error: string;
|
|
21
|
+
/** The symbol the request is for, so an answer that no listing exists can name it. */
|
|
22
|
+
symbol?: string;
|
|
21
23
|
rank?: (value: any, fetchedAt?: number) => number;
|
|
22
24
|
acceptCached?: (value: any) => boolean;
|
|
23
25
|
decode?: (value: any) => unknown;
|
|
@@ -111,6 +113,7 @@ export class ProviderRouterCachedRoutes {
|
|
|
111
113
|
let best: CachedValue<unknown> | null = null;
|
|
112
114
|
let bestRank = -1;
|
|
113
115
|
let lastError: unknown;
|
|
116
|
+
const misses: ProviderMissNote = {};
|
|
114
117
|
for (const provider of providers) {
|
|
115
118
|
try {
|
|
116
119
|
const request = route.request(provider, force);
|
|
@@ -123,6 +126,7 @@ export class ProviderRouterCachedRoutes {
|
|
|
123
126
|
if (rank > bestRank) { best = result; bestRank = rank; }
|
|
124
127
|
} catch (error) {
|
|
125
128
|
lastError = error;
|
|
129
|
+
noteProviderMiss(misses, error);
|
|
126
130
|
if (shouldLogProviderError(error)) this.deps.logProviderError(`${provider.id} failed: ${error}`);
|
|
127
131
|
}
|
|
128
132
|
}
|
|
@@ -133,7 +137,7 @@ export class ProviderRouterCachedRoutes {
|
|
|
133
137
|
const policy = resolveCachePolicy(undefined, route.policy);
|
|
134
138
|
return { value: route.empty(), fetchedAt, staleAt: fetchedAt + policy.staleMs, expiresAt: fetchedAt + policy.expireMs, source: "" };
|
|
135
139
|
}
|
|
136
|
-
throw
|
|
140
|
+
throw noProviderError(route.error, misses, route.symbol);
|
|
137
141
|
},
|
|
138
142
|
});
|
|
139
143
|
this.queries.set(key, query);
|
|
@@ -175,8 +179,8 @@ export class ProviderRouterCachedRoutes {
|
|
|
175
179
|
&& !hasBrokerContext(instrumentContext) && !instrumentContext?.instrument) {
|
|
176
180
|
exchange = publicListingExchange(ticker, exchange);
|
|
177
181
|
}
|
|
178
|
-
const tickerRoute = (kind: string, policy: ProviderRouterCachePolicyKey): Pick<Route, "kind" | "policy" | "entityKey" | "variants"> => ({
|
|
179
|
-
kind, policy, entityKey: this.deps.getEntityKey(ticker, instrumentContext?.instrument),
|
|
182
|
+
const tickerRoute = (kind: string, policy: ProviderRouterCachePolicyKey): Pick<Route, "kind" | "policy" | "entityKey" | "variants" | "symbol"> => ({
|
|
183
|
+
kind, policy, symbol: ticker, entityKey: this.deps.getEntityKey(ticker, instrumentContext?.instrument),
|
|
180
184
|
variants: this.deps.getTickerVariantCandidates(exchange),
|
|
181
185
|
});
|
|
182
186
|
switch (method) {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
selectCachedResource,
|
|
16
16
|
sortCachedRecords,
|
|
17
17
|
} from "./cache";
|
|
18
|
+
import { noProviderError, type ProviderMissNote } from "../provider-errors";
|
|
18
19
|
import { withBrokerTimeout } from "./brokers";
|
|
19
20
|
import {
|
|
20
21
|
hasMeaningfulProfile,
|
|
@@ -31,7 +32,7 @@ import {
|
|
|
31
32
|
type CachedFinancialsReadOptions,
|
|
32
33
|
type CachedFinancialsSelection,
|
|
33
34
|
} from "./financials";
|
|
34
|
-
import type { ProviderRouterPrimaryRoutes } from "./primary";
|
|
35
|
+
import type { ProviderQuoteMissNote, ProviderRouterPrimaryRoutes } from "./primary";
|
|
35
36
|
import type { ProviderRouterCoreDeps, SourceResult } from "./route-types";
|
|
36
37
|
|
|
37
38
|
export interface ProviderRouterFinancialRouteDeps extends Pick<
|
|
@@ -178,13 +179,14 @@ export class ProviderRouterFinancialRoutes {
|
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
const brokerResult = await withBrokerTimeout(this.deps.primaryRoutes.fetchBrokerFinancials(ticker, exchange, context));
|
|
181
|
-
const
|
|
182
|
+
const misses: ProviderMissNote = {};
|
|
183
|
+
const fallback = await this.deps.primaryRoutes.fetchProviderFinancials(ticker, exchange, context, misses);
|
|
182
184
|
const merged = mergeFinancials(brokerResult?.value ?? null, fallback?.value ?? null);
|
|
183
185
|
if (isOptionTicker && !merged?.quote) {
|
|
184
186
|
return quoteOnlyFinancials(merged);
|
|
185
187
|
}
|
|
186
188
|
if (!merged) {
|
|
187
|
-
throw
|
|
189
|
+
throw noProviderError(`No provider available for ${ticker}`, misses, ticker);
|
|
188
190
|
}
|
|
189
191
|
return merged;
|
|
190
192
|
}
|
|
@@ -221,12 +223,16 @@ export class ProviderRouterFinancialRoutes {
|
|
|
221
223
|
);
|
|
222
224
|
}
|
|
223
225
|
|
|
224
|
-
const
|
|
226
|
+
const misses: ProviderQuoteMissNote = {};
|
|
227
|
+
const providerQuote = await this.deps.primaryRoutes.fetchProviderQuote(ticker, exchange, context, misses);
|
|
225
228
|
if (providerQuote) {
|
|
226
229
|
return providerQuote.value;
|
|
227
230
|
}
|
|
228
231
|
if (cached) return cached.value;
|
|
229
|
-
|
|
232
|
+
// No source has a current quote: the last price one answered with, flagged
|
|
233
|
+
// stale and dated by its own time, rather than none.
|
|
234
|
+
if (misses.lastKnownQuote) return misses.lastKnownQuote;
|
|
235
|
+
throw noProviderError(`No quote provider available for ${ticker}`, misses, ticker);
|
|
230
236
|
}
|
|
231
237
|
|
|
232
238
|
private async getProviderReferenceQuote(
|
|
@@ -4,15 +4,15 @@ import { hasLikelyQuoteUnitMismatch } from "../../utils/currency-units";
|
|
|
4
4
|
import { coalesceFinancialPeriodAliases, mergeFinancialStatementRows } from "../../utils/financial-statements";
|
|
5
5
|
import { normalizePriceHistory, normalizeTickerFinancialsPriceHistory } from "../../utils/price-history";
|
|
6
6
|
import { redactUnavailableFundamentals, RETRACTABLE_VALUATION_FIELDS } from "../../utils/fundamentals";
|
|
7
|
-
import { isExtendedHoursExchange, isQuoteStaleForCurrentSession } from "../../market-data/quotes/freshness";
|
|
7
|
+
import { hasValidQuoteObservationTime, isExtendedHoursExchange, isQuoteStaleForCurrentSession } from "../../market-data/quotes/freshness";
|
|
8
8
|
import { mergeQuoteMetadata, quoteMetadataFromQuote, quoteMetadataMatchesTarget } from "../../market-data/quotes/metadata";
|
|
9
|
-
import { parsePublicTickerKey } from "../../utils/exchanges";
|
|
9
|
+
import { canonicalExchange, parsePublicTickerKey } from "../../utils/exchanges";
|
|
10
10
|
import {
|
|
11
11
|
activeUsMarketSession,
|
|
12
12
|
delayedFeedLagMs,
|
|
13
13
|
isMiddayBreakPrint,
|
|
14
14
|
isRegularSessionTime,
|
|
15
|
-
|
|
15
|
+
isUsExtendedHoursSessionPrint,
|
|
16
16
|
isUsPriorSessionPremarketQuote,
|
|
17
17
|
isUsRegularCloseQuoteInPostSession,
|
|
18
18
|
latestRegularSessionOpen,
|
|
@@ -147,19 +147,27 @@ const OPENING_AUCTION_MS = 15 * 60_000;
|
|
|
147
147
|
* - the quote is delayed. The service bounds a delayed quote's feed age itself
|
|
148
148
|
* but not a real-time one's, so real-time quotes keep the age bound;
|
|
149
149
|
* - it reports the regular session, the venue's session is in progress as
|
|
150
|
-
* the delayed feed shows it, and the trade belongs to that session;
|
|
151
|
-
*
|
|
152
|
-
*
|
|
150
|
+
* the delayed feed shows it, and the trade belongs to that session; a
|
|
151
|
+
* currency pair's session is the service's to judge; or
|
|
152
|
+
* - it reports a US extended-hours session and the trade is no older than
|
|
153
|
+
* the regular session that session extends: after the close, today's
|
|
154
|
+
* regular session or its after-hours; before the open, the previous
|
|
155
|
+
* regular session or anything since. Whether the quote carries the
|
|
156
|
+
* session's price is the freshness rule's call, made before this one.
|
|
153
157
|
* The answer says nothing about how often a listing trades, so a liquid
|
|
154
158
|
* listing the service wrongly vouches for is accepted too. The service's own
|
|
155
159
|
* bound for liquid listings, about twenty minutes, is tighter than this one.
|
|
156
160
|
*/
|
|
157
161
|
function isVouchedCurrentByService(quote: Quote, exchange: string | undefined, now: number): boolean {
|
|
158
162
|
if (quote.providerId !== "gloomberb-cloud" || quote.stale !== false || quote.dataSource !== "delayed") return false;
|
|
159
|
-
if (quote.marketState === "POST") {
|
|
160
|
-
return
|
|
163
|
+
if (quote.marketState === "PRE" || quote.marketState === "POST") {
|
|
164
|
+
return isUsExtendedHoursSessionPrint(quote.lastUpdated, exchange, quote.marketState, now);
|
|
161
165
|
}
|
|
162
166
|
if (quote.marketState !== "REGULAR") return false;
|
|
167
|
+
// A currency pair trades around the clock through the week, and the service
|
|
168
|
+
// measures its silence against how often that pair prints: a pegged or
|
|
169
|
+
// exotic pair such as SAR=X goes hours without a print while its feed is fine.
|
|
170
|
+
if (canonicalExchange(exchange) === "CCY") return true;
|
|
163
171
|
const feedTime = now - delayedFeedLagMs(exchange);
|
|
164
172
|
if (isRegularSessionTime(exchange, feedTime) !== true) return false;
|
|
165
173
|
const open = latestRegularSessionOpen(exchange, feedTime);
|
|
@@ -221,21 +229,40 @@ export function isProviderQuoteUsableForCurrentSession(
|
|
|
221
229
|
const normalized = quoteWithFreshnessExchange(quote, exchange);
|
|
222
230
|
if (isQuoteStaleForCurrentSession(normalized)) return false;
|
|
223
231
|
if (isActiveProviderQuoteTooOld(normalized, Date.now(), options.recentAnswer === true)) return false;
|
|
232
|
+
return hasQuotedPrice(normalized);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function hasQuotedPrice(quote: Quote): boolean {
|
|
224
236
|
// Futures may trade or settle at zero or negative prices. Require explicit
|
|
225
237
|
// source type metadata; an alias alone cannot establish the price domain.
|
|
226
|
-
const futures = ["FUT", "FUTURE", "FUTURES"].includes(
|
|
238
|
+
const futures = ["FUT", "FUTURE", "FUTURES"].includes(quote.instrumentType?.trim().toUpperCase() ?? "");
|
|
227
239
|
return [
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
240
|
+
quote.price,
|
|
241
|
+
quote.preMarketPrice,
|
|
242
|
+
quote.postMarketPrice,
|
|
243
|
+
quote.bid,
|
|
244
|
+
quote.ask,
|
|
245
|
+
quote.mark,
|
|
234
246
|
].some((value) => futures
|
|
235
247
|
? typeof value === "number" && Number.isFinite(value)
|
|
236
248
|
: finitePositiveNumber(value));
|
|
237
249
|
}
|
|
238
250
|
|
|
251
|
+
/**
|
|
252
|
+
* A source's answer for the target that is priced and dated but not current
|
|
253
|
+
* for its session, flagged stale: the last price there is, for when no source
|
|
254
|
+
* has a current one. It keeps its own observation time, which dates it
|
|
255
|
+
* wherever it is shown, and the flag keeps it out of anything that needs a
|
|
256
|
+
* current price. Null for an answer that names another listing, carries no
|
|
257
|
+
* price or no valid time.
|
|
258
|
+
*/
|
|
259
|
+
export function lastKnownProviderQuote(quote: Quote | null | undefined, exchange?: string, symbol?: string): Quote | null {
|
|
260
|
+
if (!providerQuoteMatchesTarget(quote, symbol, exchange)) return null;
|
|
261
|
+
const normalized = quoteWithFreshnessExchange(quote, exchange);
|
|
262
|
+
if (!hasValidQuoteObservationTime(normalized) || !hasQuotedPrice(normalized)) return null;
|
|
263
|
+
return { ...quote, stale: true };
|
|
264
|
+
}
|
|
265
|
+
|
|
239
266
|
export function providerFinancialsMatchTarget(value: TickerFinancials, symbol: string, exchange?: string): boolean {
|
|
240
267
|
if (value.quote && !providerQuoteMatchesTarget(value.quote, symbol, exchange)) return false;
|
|
241
268
|
if (Object.values(value.quoteContributions ?? {}).some((quote) => !providerQuoteMatchesTarget(quote, symbol, exchange))) return false;
|
|
@@ -23,7 +23,7 @@ import { isRoundTheClockCoin } from "../../utils/crypto-pair";
|
|
|
23
23
|
import { zonedDateKey } from "../../utils/zoned-date-time";
|
|
24
24
|
import { resolvePriceHistoryCurrencyUnit } from "../../utils/currency-units";
|
|
25
25
|
import { calendarHistoryFetchState, calendarHistoryLastBarDate, dropLeadingPlaceholderBars, getPricePointTimestamp, hasUsablePriceHistory, preservePriceHistoryGaps, isPriceHistoryStaleForCurrentWindow, normalizePriceHistory, priceHistoryIntervalMs, type CalendarHistoryFetchState } from "../../utils/price-history";
|
|
26
|
-
import { shouldLogProviderError } from "../provider-errors";
|
|
26
|
+
import { noProviderError, noteProviderAnswer, noteProviderMiss, shouldLogProviderError, type ProviderMissNote } from "../provider-errors";
|
|
27
27
|
import { HistoryCoverageError } from "../history-coverage";
|
|
28
28
|
import {
|
|
29
29
|
buildVariantKey,
|
|
@@ -66,6 +66,8 @@ interface HistoryAttempts {
|
|
|
66
66
|
outcomes: Map<string, HistorySourceOutcome>;
|
|
67
67
|
candidates: Map<string, HistoryRecoveryCandidate>;
|
|
68
68
|
pending: Set<string>;
|
|
69
|
+
/** Whether every provider that failed said it found no listing for the symbol. */
|
|
70
|
+
misses: ProviderMissNote;
|
|
69
71
|
coverageError?: HistoryCoverageError;
|
|
70
72
|
retention?: HistoryRetention;
|
|
71
73
|
}
|
|
@@ -105,6 +107,7 @@ function candidateForRequest(request: HistoryRequestDescriptor, sourceKey: strin
|
|
|
105
107
|
|
|
106
108
|
function recordHistoryError(attempts: HistoryAttempts | undefined, request: HistoryRequestDescriptor, sourceKey: string, error: unknown): void {
|
|
107
109
|
if (!attempts) return;
|
|
110
|
+
noteProviderMiss(attempts.misses, error);
|
|
108
111
|
if (error instanceof InvalidHistoryResultError) {
|
|
109
112
|
recordHistoryOutcome(attempts, sourceKey, "malformed");
|
|
110
113
|
} else if (isHistoryRetentionError(error)) {
|
|
@@ -286,8 +289,13 @@ function calendarRecheckMarkerKey(request: HistoryRequestDescriptor) {
|
|
|
286
289
|
variantKey: request.identity.variantKey, sourceKey: "router" };
|
|
287
290
|
}
|
|
288
291
|
|
|
292
|
+
interface HistoryRouteDeps extends ProviderRouterCoreDeps {
|
|
293
|
+
/** False in a short-lived process that must await refreshes of stale copies. */
|
|
294
|
+
revalidatesInBackground?(): boolean;
|
|
295
|
+
}
|
|
296
|
+
|
|
289
297
|
export class ProviderRouterHistoryRoutes {
|
|
290
|
-
constructor(private readonly deps:
|
|
298
|
+
constructor(private readonly deps: HistoryRouteDeps) {}
|
|
291
299
|
private readonly historyRefreshInFlight = new Map<string, Promise<unknown>>();
|
|
292
300
|
private readonly calendarRecheckAt = new Map<string, number>();
|
|
293
301
|
|
|
@@ -557,8 +565,9 @@ export class ProviderRouterHistoryRoutes {
|
|
|
557
565
|
|| hasRangeBarSize(record.value.points, request.requestedRange));
|
|
558
566
|
// A background revalidation cannot correct bars from before a close in
|
|
559
567
|
// time: a one-shot CLI exits first, and this caller keeps the old bars.
|
|
560
|
-
//
|
|
561
|
-
//
|
|
568
|
+
// That holds for daily bars and for intraday bars that stop short of the
|
|
569
|
+
// close. Broader variants answer the same way. A current copy under
|
|
570
|
+
// another key (such as the refetch of this range) answers first. Next come copies
|
|
562
571
|
// fetched after the latest settled close that are behind, due a re-check
|
|
563
572
|
// or not, the one reaching furthest first. A copy fetched before that close
|
|
564
573
|
// answers only when no later copy exists: its latest bar may be the session
|
|
@@ -606,12 +615,16 @@ export class ProviderRouterHistoryRoutes {
|
|
|
606
615
|
if (cachedBeforeClose) this.markCalendarChecked(request, now);
|
|
607
616
|
const usableCached = hasUsablePriceHistory(cachedValue.points) && cached && !cached.expired && !cachedHistoryStale
|
|
608
617
|
&& !cachedBeforeClose;
|
|
609
|
-
|
|
618
|
+
// A copy behind or before the latest session is re-checked on the paced
|
|
619
|
+
// schedule above, not on its short TTL: the source is likely to answer
|
|
620
|
+
// the same. A short-lived process (the CLI) closes its store before a
|
|
621
|
+
// background refresh lands, so it refetches a copy past its TTL before
|
|
622
|
+
// answering and falls back to that copy.
|
|
623
|
+
const refreshDue = !!cached?.stale && servedState === "current";
|
|
624
|
+
const awaitRefresh = refreshDue && !(this.deps.revalidatesInBackground?.() ?? true);
|
|
625
|
+
if (usableCached && !forceRefresh && !awaitRefresh) {
|
|
610
626
|
const exactHit = request.exactCacheVariantKeys.includes(cached.variantKey);
|
|
611
|
-
|
|
612
|
-
// paced schedule above, not on its short TTL: the source is likely to
|
|
613
|
-
// answer the same.
|
|
614
|
-
if (cached.stale && servedState === "current") {
|
|
627
|
+
if (refreshDue) {
|
|
615
628
|
scheduleRouterRevalidation(this.historyRefreshInFlight, request.identity.revalidationKey, () => this.refreshHistory(request));
|
|
616
629
|
}
|
|
617
630
|
return exactHit || !request.requestedRange
|
|
@@ -623,7 +636,7 @@ export class ProviderRouterHistoryRoutes {
|
|
|
623
636
|
const onUnavailable = (sourceKey: string, value: PriceHistoryResult) => {
|
|
624
637
|
if (value.points.length && !hasUsablePriceHistory(value.points)) supersededCacheSources.add(sourceKey);
|
|
625
638
|
};
|
|
626
|
-
const attempts: HistoryAttempts = { outcomes: new Map(), candidates: new Map(), pending: new Set() };
|
|
639
|
+
const attempts: HistoryAttempts = { outcomes: new Map(), candidates: new Map(), pending: new Set(), misses: {} };
|
|
627
640
|
const brokerResult = await withBrokerTimeout(this.fetchBrokerHistory(request, brokerCandidates, onUnavailable, attempts));
|
|
628
641
|
for (const sourceKey of [...attempts.pending]) recordHistoryOutcome(attempts, sourceKey, "timeout");
|
|
629
642
|
if (brokerResult && hasUsablePriceHistory(brokerResult.value.points)) return withReportedGaps(brokerResult.value);
|
|
@@ -662,7 +675,9 @@ export class ProviderRouterHistoryRoutes {
|
|
|
662
675
|
// A source answered a coin's history but it stops short of the present: say so, not that nothing answered.
|
|
663
676
|
const behind = isRoundTheClockCoin(target.symbol, target.exchange || request.target.exchange)
|
|
664
677
|
&& [...attempts.outcomes.values()].some((outcome) => outcome.outcome === "stale");
|
|
665
|
-
throw new Error(
|
|
678
|
+
if (behind) throw new Error(`Latest history for ${target.symbol} is behind`);
|
|
679
|
+
// The service has no listing for the symbol: say it is not a ticker, not that no source answered.
|
|
680
|
+
throw noProviderError(request.missingProviderError, { notFound: attempts.misses.notFound }, request.target.symbol);
|
|
666
681
|
}
|
|
667
682
|
return withReportedGaps(providerResult?.value ?? { points: [], resolution: historyResolutionForInterval(request.interval) });
|
|
668
683
|
}
|
|
@@ -739,6 +754,7 @@ export class ProviderRouterHistoryRoutes {
|
|
|
739
754
|
attempts?.pending.add(sourceKey);
|
|
740
755
|
try {
|
|
741
756
|
const fetched = await request.fetchProvider(provider);
|
|
757
|
+
noteProviderAnswer(attempts?.misses);
|
|
742
758
|
if (fetched === null) { recordHistoryOutcome(attempts, sourceKey, "empty"); return null; }
|
|
743
759
|
if (!Array.isArray(fetched.points)) { recordHistoryOutcome(attempts, sourceKey, "malformed"); return null; }
|
|
744
760
|
const value = normalizeRequestResult({ ...fetched, sourceKey }, request);
|
|
@@ -109,7 +109,7 @@ export class AssetDataRouter implements DataProvider {
|
|
|
109
109
|
getQuote: (ticker, exchange, context) => this.financialRoutes.getQuote(ticker, exchange, context),
|
|
110
110
|
getTickerFinancials: (ticker, exchange, context) => this.financialRoutes.getTickerFinancials(ticker, exchange, context),
|
|
111
111
|
});
|
|
112
|
-
this.historyRoutes = new ProviderRouterHistoryRoutes(routeDeps);
|
|
112
|
+
this.historyRoutes = new ProviderRouterHistoryRoutes({ ...routeDeps, revalidatesInBackground: () => this.revalidateInBackground });
|
|
113
113
|
this.newsRoutes = new ProviderRouterNewsRoutes({
|
|
114
114
|
newsSourcesInPriorityOrder: () => this.newsSourcesInPriorityOrder(),
|
|
115
115
|
logProviderError: (message) => providerLog.error(message),
|