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
|
@@ -7,11 +7,19 @@ import type {
|
|
|
7
7
|
CorporateActionsData,
|
|
8
8
|
HolderData,
|
|
9
9
|
OptionsChain,
|
|
10
|
+
Quote,
|
|
10
11
|
TickerFinancials,
|
|
11
12
|
} from "../../types/financials";
|
|
12
13
|
import { currencyMinorDigits, formatMarketPrice, formatMarketPriceWithCurrency, quoteFormatOptions } from "../../market-data/market/format";
|
|
13
|
-
import {
|
|
14
|
-
|
|
14
|
+
import {
|
|
15
|
+
EXTENDED_SESSION_LABELS,
|
|
16
|
+
getExtendedSessionDisplay,
|
|
17
|
+
getQuoteSessionFields,
|
|
18
|
+
getRegularSessionDisplay,
|
|
19
|
+
marketStateLabel,
|
|
20
|
+
type ExtendedSession,
|
|
21
|
+
} from "../../market-data/market/status";
|
|
22
|
+
import { formatCompact, formatDistributionAmount, formatPercent, formatPercentRaw } from "../../utils/format";
|
|
15
23
|
import { withCliServices, withMarketData } from "../context";
|
|
16
24
|
import { isoDate, parsePositiveInt, rejectExtraArgs, requireOneArg, takeFlag, takeOption } from "./command-utils";
|
|
17
25
|
import type { BuiltinCliCommandDef } from "../command-options";
|
|
@@ -32,9 +40,25 @@ import {
|
|
|
32
40
|
recommendationTotal,
|
|
33
41
|
targetUpside,
|
|
34
42
|
} from "../../plugins/builtin/research/analyst-model";
|
|
35
|
-
import { optionQuoteSide } from "../../plugins/builtin/options/market-reference";
|
|
36
43
|
import { getPublishedUsEquityCalendarYears, getPublishedUsEquitySession } from "../../market-data/published-us-sessions";
|
|
44
|
+
import { isQuoteStaleForCurrentSession } from "../../market-data/quotes/freshness";
|
|
45
|
+
import { isOptionsUnavailableError, listedOptionsAlternative, optionsUnavailableTitle } from "../../market-data/options-alternatives";
|
|
37
46
|
import { fundamentalsReportTables, renderFundamentalsReport } from "./ticker";
|
|
47
|
+
import {
|
|
48
|
+
chainHasExpiry,
|
|
49
|
+
chainWithModelFigures,
|
|
50
|
+
formatExpiryList,
|
|
51
|
+
loadOptionModelInputs,
|
|
52
|
+
formatOptionDeltaCell,
|
|
53
|
+
formatOptionIvCell,
|
|
54
|
+
formatOptionQuoteCell,
|
|
55
|
+
missingExpiryMessage,
|
|
56
|
+
OPTION_MODEL_RATE,
|
|
57
|
+
OPTIONS_USAGE,
|
|
58
|
+
optionRows,
|
|
59
|
+
parseOptionExpiration,
|
|
60
|
+
} from "./options-chain";
|
|
61
|
+
import { DEFAULT_LEAPS_CRITERIA, LEAPS_USAGE, parseLeapsCriteria, runLeapsScreen, takeLeapsOptions } from "./options-leaps";
|
|
38
62
|
import type { CliResultColumn } from "../result";
|
|
39
63
|
import {
|
|
40
64
|
exportRowsTable,
|
|
@@ -60,11 +84,15 @@ import { fundamentalsFreshness, quotesFreshness, rowsFreshness } from "../freshn
|
|
|
60
84
|
import {
|
|
61
85
|
barHistoryFreshness,
|
|
62
86
|
barResolutionFromDates,
|
|
87
|
+
cloudNewsFreshness,
|
|
88
|
+
cloudRealtimeAccess,
|
|
63
89
|
REPORTED_DATA,
|
|
64
90
|
SEC_FILINGS,
|
|
65
91
|
} from "../../plugins/builtin/shared/report-freshness";
|
|
66
92
|
import {
|
|
93
|
+
bareListingNote,
|
|
67
94
|
EXCHANGE_OPTION,
|
|
95
|
+
isNoProviderError,
|
|
68
96
|
listingHeading,
|
|
69
97
|
listingIdentity,
|
|
70
98
|
listingTitle,
|
|
@@ -72,15 +100,22 @@ import {
|
|
|
72
100
|
loadForListing,
|
|
73
101
|
loadListingQuote,
|
|
74
102
|
notTradedMessage,
|
|
103
|
+
quoteBareListing,
|
|
75
104
|
requireCliListing,
|
|
76
105
|
type CliListing,
|
|
106
|
+
type ListingDataRequest,
|
|
77
107
|
type ListingIdentity,
|
|
78
108
|
} from "../listing-arg";
|
|
109
|
+
import { indexRootAlsoLine, indexRootFor, indexRootTryLine } from "../index-roots";
|
|
110
|
+
import { providerMissReason } from "../../sources/provider-errors";
|
|
111
|
+
import { isNotATickerMessage } from "../not-a-ticker";
|
|
79
112
|
import { secRegistrantMismatchMessage, SecRegistrantMismatchError, areDifferentCompanies } from "../../sources/sec-registrant";
|
|
80
113
|
import { isCryptoPairSymbol } from "../../utils/crypto-pair";
|
|
81
114
|
import { isUsListingExchange } from "../../utils/exchanges";
|
|
82
115
|
import { nonUsSecListingVenue } from "../../utils/sec";
|
|
83
116
|
import type { MarketContext } from "../types";
|
|
117
|
+
import { windowRows } from "../row-window";
|
|
118
|
+
import { crossRate, describeFxRate, isUsableRate, parseFxRequest } from "../fx-pair";
|
|
84
119
|
import {
|
|
85
120
|
holderListFacts,
|
|
86
121
|
holderShareBasisMarker,
|
|
@@ -138,28 +173,67 @@ function listingMetadata(identity: ListingIdentity) {
|
|
|
138
173
|
return { symbol: identity.symbol, exchange: identity.exchange || null, name: identity.name };
|
|
139
174
|
}
|
|
140
175
|
|
|
176
|
+
// Fitted to a narrow width, the columns marked optional go first (higher dropPriority first), then Name is cut short.
|
|
177
|
+
// What a user types or reads the price by stays whole.
|
|
141
178
|
const QUOTE_LEAD_COLUMNS = [
|
|
142
|
-
{ key: "symbol", header: "Symbol" },
|
|
179
|
+
{ key: "symbol", header: "Symbol", shrink: false },
|
|
143
180
|
{ key: "name", header: "Name" },
|
|
144
181
|
{
|
|
145
182
|
key: "price",
|
|
146
183
|
header: "Last",
|
|
147
184
|
align: "right" as const,
|
|
148
|
-
format: (value: unknown, row:
|
|
149
|
-
row.error && !value
|
|
185
|
+
format: (value: unknown, row: QuoteRow) => (
|
|
186
|
+
row.error && !value
|
|
187
|
+
// A typo is not an outage: say what is wrong with the symbol itself.
|
|
188
|
+
? isNotATickerMessage(row.error) ? cliStyles.warning("not a ticker") : cliStyles.danger("unavailable")
|
|
189
|
+
: String(value ?? "")
|
|
150
190
|
),
|
|
151
191
|
},
|
|
152
192
|
{ key: "changePercent", header: "Chg%", align: "right" as const, format: formatChangePercentCell },
|
|
153
|
-
|
|
193
|
+
// Text drops a column no row fills, so a table without an extended print stays as narrow as before.
|
|
194
|
+
extendedColumn("PRE"),
|
|
195
|
+
extendedColumn("POST"),
|
|
196
|
+
{ key: "session", header: "Session", shrink: false },
|
|
154
197
|
];
|
|
155
198
|
|
|
199
|
+
type QuoteRow = ReturnType<typeof quoteRows>[number];
|
|
200
|
+
|
|
201
|
+
/** The pre-market or after-hours print and its move from the regular close, in its own column. */
|
|
202
|
+
function extendedColumn(session: ExtendedSession) {
|
|
203
|
+
return {
|
|
204
|
+
key: session === "PRE" ? "preMarket" : "afterHours",
|
|
205
|
+
header: EXTENDED_SESSION_LABELS[session],
|
|
206
|
+
align: "right" as const,
|
|
207
|
+
value: (row: QuoteRow) => row.extendedSession === session && row.extendedPrice
|
|
208
|
+
? [row.extendedPrice, row.extendedChangePercent == null ? "" : formatPercentRaw(row.extendedChangePercent)].join(" ").trim()
|
|
209
|
+
: "",
|
|
210
|
+
format: (value: unknown, row: QuoteRow) => (
|
|
211
|
+
value ? [row.extendedPrice, formatChangePercentCell(row.extendedChangePercent)].join(" ").trim() : ""
|
|
212
|
+
),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** A percent to two decimals, as the table prints it. */
|
|
217
|
+
function roundedPercent(value: number | undefined): number | null {
|
|
218
|
+
return value == null ? null : Number(value.toFixed(2));
|
|
219
|
+
}
|
|
220
|
+
|
|
156
221
|
function quoteColumns() {
|
|
157
222
|
return [
|
|
158
223
|
...QUOTE_LEAD_COLUMNS,
|
|
159
|
-
{ key: "currency", header: "Cur" },
|
|
160
|
-
// Whether the price is real-time or
|
|
161
|
-
|
|
162
|
-
{
|
|
224
|
+
{ key: "currency", header: "Cur", shrink: false, optional: true, dropPriority: 1 },
|
|
225
|
+
// Whether the price is real-time, delayed or stale: a feed state, not where it came from. A stale
|
|
226
|
+
// row says so here, so the closing line's "N of M stale" always points at rows the table marks.
|
|
227
|
+
{
|
|
228
|
+
key: "feed",
|
|
229
|
+
header: "Feed",
|
|
230
|
+
shrink: false,
|
|
231
|
+
optional: true,
|
|
232
|
+
dropPriority: 2,
|
|
233
|
+
format: (value: unknown) => value === "stale" ? cliStyles.warning(value) : String(value ?? ""),
|
|
234
|
+
},
|
|
235
|
+
// In a narrow terminal the closing line's as-of stands in for each row's.
|
|
236
|
+
{ key: "updatedAt", header: "Updated", shrink: false, optional: true, dropPriority: 3 },
|
|
163
237
|
];
|
|
164
238
|
}
|
|
165
239
|
|
|
@@ -167,9 +241,10 @@ function compareColumns() {
|
|
|
167
241
|
return [
|
|
168
242
|
...QUOTE_LEAD_COLUMNS,
|
|
169
243
|
{ key: "previousClose", header: "Prev Close", align: "right" as const },
|
|
170
|
-
|
|
171
|
-
{ key: "
|
|
172
|
-
{ key: "
|
|
244
|
+
// A narrow terminal drops these before it cuts the names short.
|
|
245
|
+
{ key: "dayRange", header: "Day Range", align: "right" as const, optional: true, dropPriority: 1 },
|
|
246
|
+
{ key: "volume", header: "Volume", align: "right" as const, format: formatCountCell, optional: true, dropPriority: 2 },
|
|
247
|
+
{ key: "currency", header: "Cur", shrink: false, optional: true, dropPriority: 3 },
|
|
173
248
|
];
|
|
174
249
|
}
|
|
175
250
|
|
|
@@ -178,11 +253,20 @@ function errorMessage(error: unknown): string | null {
|
|
|
178
253
|
return error instanceof Error ? error.message : String(error);
|
|
179
254
|
}
|
|
180
255
|
|
|
181
|
-
|
|
256
|
+
/** What the Feed cell says: stale wins over the feed's own live or delayed, as the closing line counts it. */
|
|
257
|
+
function quoteFeedState(quote: QuoteCliRecord["quote"], now: number): "stale" | "live" | "delayed" | "" {
|
|
258
|
+
if (!quote) return "";
|
|
259
|
+
if (isQuoteStaleForCurrentSession(quote, now)) return "stale";
|
|
260
|
+
return quote.dataSource === "live" || quote.dataSource === "delayed" ? quote.dataSource : "";
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function quoteRows(results: QuoteCliRecord[], now = Date.now()) {
|
|
182
264
|
return results.map((result) => {
|
|
183
265
|
const quote = result.quote;
|
|
184
|
-
//
|
|
185
|
-
|
|
266
|
+
// Last and Chg% are the regular session, as `ticker` and the quote monitor read it; a
|
|
267
|
+
// pre-market or after-hours print is its own column, measured from that session's close.
|
|
268
|
+
const display = getRegularSessionDisplay(quote);
|
|
269
|
+
const extended = getExtendedSessionDisplay(quote);
|
|
186
270
|
// An index level is in points, not in the currency its members trade in.
|
|
187
271
|
const indexPoints = quote?.instrumentType?.trim().toUpperCase() === "INDEX";
|
|
188
272
|
// Pad to two decimals so a column lines up, but never past the currency's minor unit (JPY has none).
|
|
@@ -204,9 +288,14 @@ function quoteRows(results: QuoteCliRecord[]) {
|
|
|
204
288
|
priceBasis: quote?.priceBasis ?? null,
|
|
205
289
|
instrumentType: quote?.instrumentType ?? null,
|
|
206
290
|
change: display?.change ?? null,
|
|
207
|
-
changePercent: display?.changePercent
|
|
291
|
+
changePercent: roundedPercent(display?.changePercent),
|
|
292
|
+
extendedSession: extended?.session ?? null,
|
|
293
|
+
extendedPrice: price(extended?.price),
|
|
294
|
+
rawExtendedPrice: extended?.price ?? null,
|
|
295
|
+
extendedChange: extended?.change ?? null,
|
|
296
|
+
extendedChangePercent: roundedPercent(extended?.changePercent),
|
|
208
297
|
session: quote?.marketState ? marketStateLabel(quote.marketState) : "",
|
|
209
|
-
// The close the shown move is measured from
|
|
298
|
+
// The close the shown move is measured from: the one before the session Last is.
|
|
210
299
|
previousClose: price(display?.change != null ? display.price - display.change : quote?.previousClose),
|
|
211
300
|
dayRange: quote?.low != null && quote.high != null
|
|
212
301
|
? indexPoints ? `${price(quote.low)}-${price(quote.high)}` : formatPriceRange(quote.low, quote.high, quote.currency, options, "-")
|
|
@@ -215,6 +304,7 @@ function quoteRows(results: QuoteCliRecord[]) {
|
|
|
215
304
|
currency: quote?.currency ?? "",
|
|
216
305
|
providerId: quote?.providerId ?? "",
|
|
217
306
|
source: quote?.dataSource ?? quote?.providerId ?? "",
|
|
307
|
+
feed: quoteFeedState(quote, now),
|
|
218
308
|
updatedAt: quote?.lastUpdated ? new Date(quote.lastUpdated).toISOString() : "",
|
|
219
309
|
error: result.error ?? "",
|
|
220
310
|
};
|
|
@@ -345,22 +435,6 @@ function corporateActionRows(data: CorporateActionsData) {
|
|
|
345
435
|
].sort((left, right) => right.date.localeCompare(left.date));
|
|
346
436
|
}
|
|
347
437
|
|
|
348
|
-
function optionRows(chain: OptionsChain) {
|
|
349
|
-
return [...chain.calls.map((contract) => ({ side: "call", ...contract })), ...chain.puts.map((contract) => ({ side: "put", ...contract }))]
|
|
350
|
-
.map((contract) => ({
|
|
351
|
-
side: contract.side,
|
|
352
|
-
contract: contract.contractSymbol,
|
|
353
|
-
strike: contract.strike,
|
|
354
|
-
last: contract.lastPrice,
|
|
355
|
-
bid: contract.bid,
|
|
356
|
-
ask: contract.ask,
|
|
357
|
-
volume: contract.volume,
|
|
358
|
-
openInterest: contract.openInterest,
|
|
359
|
-
iv: contract.impliedVolatility,
|
|
360
|
-
expiration: new Date(contract.expiration * 1000).toISOString().slice(0, 10),
|
|
361
|
-
}));
|
|
362
|
-
}
|
|
363
|
-
|
|
364
438
|
/**
|
|
365
439
|
* After a US open, a chain whose latest trade predates it still carries the
|
|
366
440
|
* prior session's quotes and volume. The delayed feed lags the open by about
|
|
@@ -375,11 +449,6 @@ function priorSessionChainWarning(chain: OptionsChain, exchange: string, now: nu
|
|
|
375
449
|
return `No option trades this session yet (last trade ${chain.asOf})`;
|
|
376
450
|
}
|
|
377
451
|
|
|
378
|
-
function formatOptionQuoteCell(row: Record<string, unknown>, side: "bid" | "ask"): string {
|
|
379
|
-
const quote = optionQuoteSide({ bid: Number(row.bid), ask: Number(row.ask) }, side);
|
|
380
|
-
return quote == null ? "—" : String(quote);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
452
|
/** Per-share earnings to the cent, as reported; consensus averages carry more digits. */
|
|
384
453
|
function formatEpsCell(value: unknown): string {
|
|
385
454
|
return typeof value === "number" && Number.isFinite(value) ? value.toFixed(2) : "";
|
|
@@ -412,20 +481,25 @@ async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execut
|
|
|
412
481
|
const targets: QuoteSubscriptionTarget[] = listings.map((listing) => ({ symbol: listing.request.symbol, exchange: listing.request.exchange }));
|
|
413
482
|
const results = await market.dataProvider.getQuotesBatch(targets, { forceRefresh: ctx.cliOptions.refresh });
|
|
414
483
|
const listingOf = (result: QuoteBatchResult, index: number) => listings[targets.indexOf(result.target)] ?? listings[index]!;
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
))
|
|
419
|
-
if (listings.length === 1 && notTraded[0]) ctx.fail(notTraded[0]);
|
|
484
|
+
const request = { command: commandName, noun: "quote" };
|
|
485
|
+
const answers = await Promise.all(results.map((result, index) => answerQuote(result, listingOf(result, index), market, request, ctx.cliOptions.refresh)));
|
|
486
|
+
const failed = answers[0]?.failure;
|
|
487
|
+
if (listings.length === 1 && failed) ctx.fail(failed.message, failed.details);
|
|
420
488
|
// Each row names its listing by key (SAN:EPA) and company, so the table needs no line above it.
|
|
421
|
-
const data =
|
|
489
|
+
const data = answers.map(({ result, listing, failure }) => ({
|
|
422
490
|
target: result.target,
|
|
423
|
-
listing: listingMetadata(listingIdentity(
|
|
491
|
+
listing: listingMetadata(listingIdentity(listing, result.quote)),
|
|
492
|
+
// The figures the table shows, beside the quote as the source sent it.
|
|
493
|
+
...getQuoteSessionFields(result.quote),
|
|
424
494
|
quote: result.quote,
|
|
425
|
-
error:
|
|
495
|
+
error: failure?.oneLine ?? errorMessage(result.error),
|
|
426
496
|
}));
|
|
427
|
-
|
|
428
|
-
|
|
497
|
+
const notes = [
|
|
498
|
+
// Which listing a bare symbol was read as, and the index a bare root is not, are in no row.
|
|
499
|
+
...answers.flatMap((answer) => answer.notes),
|
|
500
|
+
// Text mode shows only "unavailable" in the cell; the JSON rows already carry each reason.
|
|
501
|
+
...ctx.cliOptions.format === "text" ? quoteNotes(data, { exchange }) : [],
|
|
502
|
+
];
|
|
429
503
|
ctx.printResult({
|
|
430
504
|
data,
|
|
431
505
|
warnings: notes.length > 0 ? notes : undefined,
|
|
@@ -437,6 +511,58 @@ async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execut
|
|
|
437
511
|
});
|
|
438
512
|
}
|
|
439
513
|
|
|
514
|
+
interface QuoteAnswer {
|
|
515
|
+
result: QuoteBatchResult;
|
|
516
|
+
/** The listing the row is: the one asked for, or the venue a bare symbol was quoted on. */
|
|
517
|
+
listing: CliListing;
|
|
518
|
+
/** Why there is no quote, when there is more to say than the source's own error. */
|
|
519
|
+
failure?: { message: string; details?: string; oneLine: string };
|
|
520
|
+
notes: string[];
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* One symbol's row of `quote` and `compare`. A listing on an exchange its symbol
|
|
525
|
+
* is not listed on says so. A bare symbol the data service read on its home
|
|
526
|
+
* listing abroad names that venue (SXR8 -> XETRA). One no source quotes is
|
|
527
|
+
* quoted on its listings (`quoteBareListing`), else names them; a bare index
|
|
528
|
+
* root (VIX) also names its index (^VIX). Venues are only looked up for a
|
|
529
|
+
* symbol with no quote, so a request that succeeds costs no search.
|
|
530
|
+
*/
|
|
531
|
+
async function answerQuote(
|
|
532
|
+
result: QuoteBatchResult,
|
|
533
|
+
listing: CliListing,
|
|
534
|
+
market: MarketContext,
|
|
535
|
+
request: ListingDataRequest,
|
|
536
|
+
refresh: boolean,
|
|
537
|
+
): Promise<QuoteAnswer> {
|
|
538
|
+
const root = listing.exchange ? null : indexRootFor(listing.symbol);
|
|
539
|
+
// A bare root that quoted something else, such as MOVE (Corvex), points at the index too.
|
|
540
|
+
const also = (quote: Quote) => root && quote.instrumentType?.trim().toUpperCase() !== "INDEX"
|
|
541
|
+
? [indexRootAlsoLine(root, request.command)] : [];
|
|
542
|
+
if (result.quote) return { result, listing, notes: [bareListingNote(listing, result.quote) ?? [], also(result.quote)].flat() };
|
|
543
|
+
const notTraded = await notTradedMessage(listing, market);
|
|
544
|
+
if (notTraded) return { result, listing, failure: { message: notTraded, oneLine: notTraded }, notes: [] };
|
|
545
|
+
if (!isNoProviderError(result.error)) return { result, listing, notes: [] };
|
|
546
|
+
const other = await quoteBareListing(listing, market, request, { refresh });
|
|
547
|
+
if (other?.kind === "quoted") {
|
|
548
|
+
return {
|
|
549
|
+
result: { target: other.listing.request, quote: other.quote },
|
|
550
|
+
listing: other.listing,
|
|
551
|
+
notes: [other.note, ...also(other.quote)],
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
const indexLine = root ? indexRootTryLine(root, request.command) : null;
|
|
555
|
+
if (!other && !indexLine) return { result, listing, notes: [] };
|
|
556
|
+
const message = other?.message ?? `${listing.symbol} has no ${request.noun}.`;
|
|
557
|
+
const details = [other?.details, indexLine].filter(Boolean).join("\n");
|
|
558
|
+
const oneLine = [
|
|
559
|
+
message,
|
|
560
|
+
other ? `Other listings: ${other.others.join(", ")}.` : "",
|
|
561
|
+
indexLine ?? "",
|
|
562
|
+
].filter(Boolean).join(" ");
|
|
563
|
+
return { result, listing, failure: { message, details, oneLine }, notes: [] };
|
|
564
|
+
}
|
|
565
|
+
|
|
440
566
|
/** A crypto pair that will not load points at the crypto board. Structured formats keep the error as thrown. */
|
|
441
567
|
function failHistory(error: unknown, symbol: string, ctx: Parameters<CliCommandDef["execute"]>[1]): never {
|
|
442
568
|
if (ctx.cliOptions.format !== "text" || !isCryptoPairSymbol(symbol)) throw error;
|
|
@@ -461,7 +587,9 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
461
587
|
? market.dataProvider.getPriceHistoryWithMetadata(symbol, exchange, range, context)
|
|
462
588
|
: market.dataProvider.getPriceHistory(symbol, exchange, range, context).then((points) => ({ points, resolution: null }));
|
|
463
589
|
const [{ points, resolution }, quote] = await Promise.all([
|
|
464
|
-
loadForListing(listing, market, ctx, load, (loaded) => loaded.points.length === 0
|
|
590
|
+
loadForListing(listing, market, ctx, load, (loaded) => loaded.points.length === 0, {
|
|
591
|
+
command: range === "1Y" ? "history" : `history --range ${range}`, noun: "history",
|
|
592
|
+
})
|
|
465
593
|
.catch((error) => failHistory(error, listing.key, ctx)),
|
|
466
594
|
loadListingQuote(market.dataProvider, listing),
|
|
467
595
|
]);
|
|
@@ -515,6 +643,7 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
515
643
|
freshness,
|
|
516
644
|
}, {
|
|
517
645
|
heading: `${listingHeading(identity)}\n${cliStyles.muted(historyFacts(unit, data).join(" · "))}`,
|
|
646
|
+
dateKey: "date",
|
|
518
647
|
textColumns: [...priceColumns, flagColumn],
|
|
519
648
|
// Every exported row says its currency and bar size, so it survives head and concatenation.
|
|
520
649
|
columns: [
|
|
@@ -560,7 +689,7 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
560
689
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
561
690
|
const financials = await loadForListing(listing, market, ctx, () => market.dataProvider.getTickerFinancials(
|
|
562
691
|
listing.request.symbol, listing.request.exchange, { cacheMode: ctx.cliOptions.refresh ? "refresh" : "default" },
|
|
563
|
-
));
|
|
692
|
+
), undefined, { command: commandName, noun: commandName === "valuation" ? "valuation data" : commandName });
|
|
564
693
|
const identity = listingIdentity(listing, financials.quote);
|
|
565
694
|
const data: FinancialsCliData = {
|
|
566
695
|
symbol: listing.key,
|
|
@@ -619,7 +748,8 @@ async function runNews(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
619
748
|
rejectExtraArgs(args, 1, { usage: "news [symbol] [--feed <feed>]", takes: "one symbol at most", advice: "Run it once per symbol." }, ctx);
|
|
620
749
|
await withMarketData(ctx, async (market) => {
|
|
621
750
|
const listing = args[0] ? await requireCliListing(args[0], exchangeOption, market, ctx) : null;
|
|
622
|
-
|
|
751
|
+
// Stories come newest first, so the newest n are the first n either way.
|
|
752
|
+
const limit = ctx.cliOptions.tail ?? ctx.cliOptions.limit ?? 20;
|
|
623
753
|
const loadNews = () => market.dataProvider.getNews({
|
|
624
754
|
feed: feed ?? (listing ? "ticker" : "latest"),
|
|
625
755
|
// Still set for news plugins that read the deprecated scope.
|
|
@@ -636,9 +766,7 @@ async function runNews(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
636
766
|
ctx.printResult({
|
|
637
767
|
data: articles,
|
|
638
768
|
metadata: { ticker: listing?.key ?? null, ...(identity ? listingMetadata(identity) : {}), feed: feed ?? null },
|
|
639
|
-
freshness: rowsFreshness(newsRows(articles),
|
|
640
|
-
status: "not-a-feed", basis: "published stories", observedKey: "publishedAt", oldest: null,
|
|
641
|
-
}),
|
|
769
|
+
freshness: rowsFreshness(newsRows(articles), cloudNewsFreshness(await cloudRealtimeAccess())),
|
|
642
770
|
}, {
|
|
643
771
|
...(identity ? { heading: listingHeading(identity) } : {}),
|
|
644
772
|
rows: newsRows,
|
|
@@ -674,7 +802,8 @@ async function registrantUsExchange(market: MarketContext, symbol: string, regis
|
|
|
674
802
|
|
|
675
803
|
async function runFilings(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
676
804
|
const args = [...rawArgs];
|
|
677
|
-
|
|
805
|
+
// Filings come newest first, so --tail asks for the same first n as --limit.
|
|
806
|
+
const count = parsePositiveInt(takeOption(args, "--count"), ctx.cliOptions.tail ?? ctx.cliOptions.limit ?? 15, "Count", ctx);
|
|
678
807
|
const exchangeOption = takeOption(args, "--exchange");
|
|
679
808
|
const form = takeOption(args, "--form")?.trim() || null;
|
|
680
809
|
const raw = requireOneArg(args, "filings <symbol> [--count <n>] [--form <form>]", "symbol", ctx);
|
|
@@ -745,10 +874,14 @@ async function printBeneficialOwners(
|
|
|
745
874
|
ctx: Parameters<CliCommandDef["execute"]>[1],
|
|
746
875
|
) {
|
|
747
876
|
const { symbol, exchange } = listing.request;
|
|
877
|
+
// A listing outside the US sends its company to the lookup, which needs its quote first.
|
|
878
|
+
const quotePromise = loadListingQuote(market.dataProvider, listing);
|
|
879
|
+
const abroad = nonUsSecListingVenue(symbol, exchange) ? listingIdentity(listing, await quotePromise) : null;
|
|
880
|
+
const request = { form: beneficialRouteForm(form), history, listing: { exchange, name: abroad?.name ?? undefined } };
|
|
748
881
|
const [payload, holders, quote] = await Promise.all([
|
|
749
|
-
loadForListing(listing, market, ctx, () => fetchBeneficialOwners(symbol,
|
|
882
|
+
loadForListing(listing, market, ctx, () => fetchBeneficialOwners(symbol, request)),
|
|
750
883
|
market.dataProvider.getHolders?.(symbol, exchange).catch(() => null) ?? null,
|
|
751
|
-
|
|
884
|
+
quotePromise,
|
|
752
885
|
]);
|
|
753
886
|
const identity = listingIdentity(listing, quote);
|
|
754
887
|
const rows = buildBeneficialReportRows(payload, { history, holders });
|
|
@@ -804,13 +937,13 @@ async function runHolders(
|
|
|
804
937
|
listing, market, ctx,
|
|
805
938
|
() => market.dataProvider.getHolders(listing.request.symbol, listing.request.exchange),
|
|
806
939
|
(found) => found.holders.length === 0,
|
|
940
|
+
{ command: commandName, noun: "holder data" },
|
|
807
941
|
),
|
|
808
942
|
loadListingQuote(market.dataProvider, listing),
|
|
809
943
|
]);
|
|
810
944
|
const identity = listingIdentity(listing, quote);
|
|
811
945
|
const rows = holderRows(data, ownerTypes);
|
|
812
|
-
const
|
|
813
|
-
const shown = limit == null ? rows.length : Math.min(limit, rows.length);
|
|
946
|
+
const shown = windowRows(rows, ctx.cliOptions).rows.length;
|
|
814
947
|
const institutional = !ownerTypes?.has("insider");
|
|
815
948
|
const total = institutional ? data.summary?.institutionsCount ?? null : null;
|
|
816
949
|
const reportDate = sharedReportDate(rows);
|
|
@@ -878,7 +1011,10 @@ async function runAnalyst(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
878
1011
|
await withMarketData(ctx, async (market) => {
|
|
879
1012
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
880
1013
|
const [data, quote] = await Promise.all([
|
|
881
|
-
loadForListing(
|
|
1014
|
+
loadForListing(
|
|
1015
|
+
listing, market, ctx, () => market.dataProvider.getAnalystResearch(listing.request.symbol, listing.request.exchange),
|
|
1016
|
+
undefined, { command: "analyst", noun: "analyst research" },
|
|
1017
|
+
),
|
|
882
1018
|
loadListingQuote(market.dataProvider, listing),
|
|
883
1019
|
]);
|
|
884
1020
|
const identity = listingIdentity(listing, quote);
|
|
@@ -915,7 +1051,10 @@ async function runEvents(rawArgs: string[], ctx: Parameters<CliCommandDef["execu
|
|
|
915
1051
|
await withMarketData(ctx, async (market) => {
|
|
916
1052
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
917
1053
|
const [data, quote] = await Promise.all([
|
|
918
|
-
loadForListing(
|
|
1054
|
+
loadForListing(
|
|
1055
|
+
listing, market, ctx, () => market.dataProvider.getCorporateActions(listing.request.symbol, listing.request.exchange),
|
|
1056
|
+
undefined, { command: "events", noun: "corporate events" },
|
|
1057
|
+
),
|
|
919
1058
|
loadListingQuote(market.dataProvider, listing),
|
|
920
1059
|
]);
|
|
921
1060
|
const identity = listingIdentity(listing, quote);
|
|
@@ -939,85 +1078,168 @@ async function runEvents(rawArgs: string[], ctx: Parameters<CliCommandDef["execu
|
|
|
939
1078
|
|
|
940
1079
|
async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
941
1080
|
const args = [...rawArgs];
|
|
1081
|
+
const leapsOptions = takeLeapsOptions(args);
|
|
942
1082
|
const expiration = takeOption(args, "--expiration");
|
|
943
1083
|
const exchangeOption = takeOption(args, "--exchange");
|
|
944
|
-
|
|
1084
|
+
if (leapsOptions.leaps) {
|
|
1085
|
+
if (expiration != null || rawArgs.includes("--expiration")) {
|
|
1086
|
+
ctx.fail("--leaps reads every expiry more than a year out; drop --expiration.", `Usage: gloomberb ${LEAPS_USAGE}`);
|
|
1087
|
+
}
|
|
1088
|
+
const criteria = parseLeapsCriteria(leapsOptions.raw);
|
|
1089
|
+
if ("error" in criteria) ctx.fail(criteria.error, `Usage: gloomberb ${LEAPS_USAGE}`);
|
|
1090
|
+
const symbols = normalizeSymbols(args);
|
|
1091
|
+
if (symbols.length === 0) ctx.fail("--leaps needs at least one symbol.", `Usage: gloomberb ${LEAPS_USAGE}`);
|
|
1092
|
+
await withMarketData(ctx, (market) => runLeapsScreen(symbols, exchangeOption, criteria, market, ctx));
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
if (leapsOptions.given.length > 0) {
|
|
1096
|
+
ctx.fail(`${leapsOptions.given.join(", ")} only ${leapsOptions.given.length > 1 ? "apply" : "applies"} with --leaps.`, `Usage: gloomberb ${LEAPS_USAGE}`);
|
|
1097
|
+
}
|
|
1098
|
+
const raw = requireOneArg(args, OPTIONS_USAGE, "symbol", ctx);
|
|
1099
|
+
let expirationDate: number | undefined;
|
|
1100
|
+
if (expiration != null || rawArgs.includes("--expiration")) {
|
|
1101
|
+
const parsed = expiration == null ? null : parseOptionExpiration(expiration);
|
|
1102
|
+
if (parsed == null) {
|
|
1103
|
+
ctx.fail(
|
|
1104
|
+
expiration == null ? "--expiration needs a date." : `Invalid --expiration "${expiration}".`,
|
|
1105
|
+
`Use YYYY-MM-DD (2028-01-21) or Unix seconds (1832025600).\nUsage: gloomberb ${OPTIONS_USAGE}`,
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
expirationDate = parsed;
|
|
1109
|
+
}
|
|
945
1110
|
await withMarketData(ctx, async (market) => {
|
|
946
1111
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
947
1112
|
const { symbol, exchange } = listing.request;
|
|
948
|
-
const
|
|
1113
|
+
const refresh = ctx.cliOptions.refresh;
|
|
949
1114
|
const quotePromise = loadListingQuote(market.dataProvider, listing);
|
|
950
|
-
const
|
|
1115
|
+
const inputsPromise = loadOptionModelInputs(market.dataProvider, listing, quotePromise, refresh);
|
|
1116
|
+
const { result, chain: loaded } = await loadForListing(listing, market, ctx, async () => {
|
|
951
1117
|
const cached = await market.dataProvider.getCachedQuery?.("getOptionsChain", [symbol, exchange, expirationDate, undefined])
|
|
952
|
-
.load({ force:
|
|
1118
|
+
.load({ force: refresh });
|
|
953
1119
|
return {
|
|
954
1120
|
result: cached,
|
|
955
1121
|
chain: cached?.value ?? await market.dataProvider.getOptionsChain(symbol, exchange, expirationDate, {
|
|
956
|
-
cacheMode:
|
|
1122
|
+
cacheMode: refresh ? "refresh" : "default",
|
|
957
1123
|
}),
|
|
958
1124
|
};
|
|
1125
|
+
}, undefined, { command: "options", noun: "options chain" }).catch((error: unknown) => {
|
|
1126
|
+
if (!isOptionsUnavailableError(error)) throw error;
|
|
1127
|
+
// A future or commodity with no chain of its own: name the listed fund whose options stand in.
|
|
1128
|
+
const alternative = listedOptionsAlternative(listing.key);
|
|
1129
|
+
return ctx.fail(optionsUnavailableTitle(listing.key), alternative
|
|
1130
|
+
? `Nearest listed alternative: ${alternative.symbol} (${alternative.name}), gloomberb options ${alternative.symbol}.`
|
|
1131
|
+
+ ` Strategy payoffs: gloomberb fn OSA ${alternative.symbol}`
|
|
1132
|
+
: "Strategy payoffs on a listed underlying: gloomberb fn OSA <ticker>");
|
|
959
1133
|
});
|
|
1134
|
+
const inputs = await inputsPromise;
|
|
1135
|
+
if (expirationDate != null && !chainHasExpiry(loaded, expirationDate)) {
|
|
1136
|
+
// An unlisted date comes back as an empty chain; its own list of expiries, else the default chain's, says what to pick.
|
|
1137
|
+
const listed = loaded.expirationDates.length > 0 ? loaded.expirationDates : await market.dataProvider
|
|
1138
|
+
.getOptionsChain(symbol, exchange, undefined, { cacheMode: "default" })
|
|
1139
|
+
.then((fallback) => fallback.expirationDates, () => []);
|
|
1140
|
+
const miss = missingExpiryMessage(listing.key, expirationDate, listed);
|
|
1141
|
+
ctx.fail(miss.message, miss.details);
|
|
1142
|
+
}
|
|
960
1143
|
const identity = listingIdentity(listing, await quotePromise);
|
|
1144
|
+
const chain = chainWithModelFigures(loaded, inputs);
|
|
961
1145
|
// A failed refresh falls back to the stored chain, which can be days old.
|
|
962
1146
|
const refreshWarning = result?.refreshError == null ? null
|
|
963
1147
|
: `Options refresh failed; showing the chain stored ${new Date(result.fetchedAt).toISOString()}`
|
|
964
1148
|
+ (chain.asOf ? ` (last trade ${chain.asOf})` : "");
|
|
965
1149
|
const sessionWarning = refreshWarning ? null : priorSessionChainWarning(chain, identity.exchange, Date.now());
|
|
966
|
-
const
|
|
1150
|
+
const modelWarning = inputs.spot == null && (chain.calls.length > 0 || chain.puts.length > 0)
|
|
1151
|
+
? `IV and delta need a current ${listing.key} quote; those columns are blank`
|
|
1152
|
+
: null;
|
|
1153
|
+
const warnings = [refreshWarning ?? sessionWarning, modelWarning].filter((warning): warning is string => warning != null);
|
|
967
1154
|
// Dated by the chain's last trade; a chain that failed to refresh is the stored one, stale.
|
|
968
1155
|
const freshness = rowsFreshness([], { asOf: chain.asOf ?? null }, {
|
|
969
1156
|
...(chain.dataSource ? { dataSource: chain.dataSource } : {}),
|
|
970
1157
|
...(chain.delayMinutes != null ? { delayMinutes: chain.delayMinutes } : {}),
|
|
971
1158
|
stale: refreshWarning != null,
|
|
972
1159
|
});
|
|
973
|
-
ctx.printResult({
|
|
1160
|
+
ctx.printResult({
|
|
1161
|
+
data: chain,
|
|
1162
|
+
metadata: {
|
|
1163
|
+
...listingMetadata(identity),
|
|
1164
|
+
expirations: chain.expirationDates,
|
|
1165
|
+
// What `iv` and `delta` on each contract are valued from; the provider's own impliedVolatility is left as sent.
|
|
1166
|
+
model: { spot: inputs.spot ?? null, dividendYield: inputs.dividendYield ?? null, rate: OPTION_MODEL_RATE },
|
|
1167
|
+
},
|
|
1168
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
1169
|
+
freshness,
|
|
1170
|
+
}, {
|
|
974
1171
|
heading: listingHeading(identity),
|
|
975
1172
|
rows: optionRows,
|
|
1173
|
+
// Choosing an expiry needs the list; once one is chosen the table is that expiry.
|
|
1174
|
+
summary: (data: OptionsChain) => expirationDate == null ? formatExpiryList(data.expirationDates) : "",
|
|
1175
|
+
empty: `No option contracts for ${listingTitle(identity)}.`,
|
|
976
1176
|
columns: [
|
|
977
1177
|
{ key: "side", header: "Side" },
|
|
978
1178
|
{ key: "contract", header: "Contract", shrink: false },
|
|
979
|
-
|
|
1179
|
+
// The contract symbol already carries the date, so this goes first when the table is narrow.
|
|
1180
|
+
{ key: "expiration", header: "Expiry", optional: true, dropPriority: 2 },
|
|
980
1181
|
{ key: "strike", header: "Strike", align: "right" },
|
|
981
1182
|
{ key: "last", header: "Last", align: "right" },
|
|
982
1183
|
{ key: "bid", header: "Bid", align: "right", format: (_value, row) => formatOptionQuoteCell(row, "bid") },
|
|
983
1184
|
{ key: "ask", header: "Ask", align: "right", format: (_value, row) => formatOptionQuoteCell(row, "ask") },
|
|
984
|
-
{ key: "
|
|
985
|
-
{ key: "
|
|
1185
|
+
{ key: "iv", header: "IV", align: "right", format: formatOptionIvCell },
|
|
1186
|
+
{ key: "delta", header: "Delta", align: "right", format: formatOptionDeltaCell },
|
|
1187
|
+
{ key: "volume", header: "Vol", align: "right", format: formatCountCell, optional: true, dropPriority: 1 },
|
|
1188
|
+
{ key: "openInterest", header: "OI", align: "right", format: formatCountCell, optional: true, dropPriority: 1 },
|
|
986
1189
|
],
|
|
987
1190
|
});
|
|
988
1191
|
});
|
|
989
1192
|
}
|
|
990
1193
|
|
|
991
|
-
const FX_USAGE = "fx <currency>";
|
|
1194
|
+
const FX_USAGE = "fx <currency> | fx <base>/<quote>";
|
|
992
1195
|
|
|
993
1196
|
async function runFx(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
994
|
-
const
|
|
1197
|
+
const raw = requireOneArg(rawArgs, FX_USAGE, "currency or pair", ctx);
|
|
995
1198
|
await withMarketData(ctx, async (market) => {
|
|
996
|
-
const
|
|
1199
|
+
const request = parseFxRequest(raw, market.config.baseCurrency);
|
|
1200
|
+
if ("error" in request) return ctx.fail(request.error, `Usage: gloomberb ${FX_USAGE}`);
|
|
1201
|
+
const { currency, baseCurrency } = request;
|
|
1202
|
+
const pair = `${currency}/${baseCurrency}`;
|
|
1203
|
+
const reasons = new Map<string, string>();
|
|
997
1204
|
const load = (code: string) => market.dataProvider.getCachedQuery("getExchangeRate", [code])
|
|
998
|
-
.load({ force: ctx.cliOptions.refresh }).catch(() =>
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1205
|
+
.load({ force: ctx.cliOptions.refresh }).catch((error) => {
|
|
1206
|
+
const reason = providerMissReason(error);
|
|
1207
|
+
if (reason) reasons.set(code, reason);
|
|
1208
|
+
return null;
|
|
1209
|
+
});
|
|
1210
|
+
// Every rate is a cross of two USD legs; a code priced in itself needs none.
|
|
1211
|
+
const codes = currency === baseCurrency ? [] : [currency, baseCurrency];
|
|
1212
|
+
const legs = await Promise.all(codes.map(load));
|
|
1213
|
+
const missing = codes.filter((_code, index) => !isUsableRate(legs[index]?.value));
|
|
1214
|
+
if (missing.length > 0) {
|
|
1215
|
+
// A reason the data service gave for the first missing leg says more than the generic line.
|
|
1216
|
+
const reason = missing.map((code) => reasons.get(code)).find(Boolean);
|
|
1217
|
+
if (reason) ctx.fail(reason);
|
|
1218
|
+
ctx.fail(
|
|
1219
|
+
`Exchange rate unavailable for ${missing.join(" and ")}.`,
|
|
1220
|
+
`${pair} is crossed from each currency's USD rate, and none came back for ${missing.join(" or ")}. Check the ISO code.`,
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1223
|
+
const rate = codes.length === 0 ? 1 : crossRate(legs[0]?.value, legs[1]?.value);
|
|
1003
1224
|
// A cross rate is only as current as its older leg.
|
|
1004
1225
|
const observed = legs.flatMap((leg) => leg?.asOf ?? []);
|
|
1005
1226
|
const asOf = observed.length > 0 ? new Date(Math.min(...observed)).toISOString() : null;
|
|
1006
1227
|
const stale = legs.some((leg) => leg != null && (leg.staleAt <= Date.now() || leg.refreshError != null));
|
|
1007
|
-
|
|
1008
|
-
|
|
1228
|
+
const row = { currency, baseCurrency, rate, asOf, stale, pair, inverse: 1 / rate };
|
|
1229
|
+
ctx.printResult({ data: [row] }, {
|
|
1230
|
+
text: () => {
|
|
1231
|
+
const time = asOf ? `As of ${formatUtcTime(asOf)}` : "";
|
|
1232
|
+
const when = stale ? cliStyles.warning(time ? `${time}, stale` : "Stale") : time && cliStyles.muted(time);
|
|
1233
|
+
return [describeFxRate(request, rate), when].filter(Boolean).join("\n");
|
|
1234
|
+
},
|
|
1009
1235
|
columns: [
|
|
1010
1236
|
{ key: "currency", header: "Currency" },
|
|
1011
1237
|
{ key: "baseCurrency", header: "Base" },
|
|
1012
1238
|
{ key: "rate", header: "Rate", align: "right" },
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
const time = typeof value === "string" ? formatUtcTime(value) : "";
|
|
1018
|
-
return row.stale ? cliStyles.warning(`${time} stale`.trim()) : time;
|
|
1019
|
-
},
|
|
1020
|
-
}] : []),
|
|
1239
|
+
{ key: "asOf", header: "As Of" },
|
|
1240
|
+
{ key: "stale", header: "Stale" },
|
|
1241
|
+
{ key: "pair", header: "Pair" },
|
|
1242
|
+
{ key: "inverse", header: "Inverse", align: "right" },
|
|
1021
1243
|
],
|
|
1022
1244
|
});
|
|
1023
1245
|
});
|
|
@@ -1094,7 +1316,7 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
|
1094
1316
|
"A bar whose prices contradict each other (high below the open or close, or low above them) is left blank and flagged, such as high<open, with the count in a warning. Every exported row (--csv, --ndjson, --json) carries currency, interval and flag.",
|
|
1095
1317
|
],
|
|
1096
1318
|
}],
|
|
1097
|
-
examples: ["history AAPL", "history BHP:ASX --range 5Y", "history ZAR=X --range ALL", "history AAPL --range 5Y --csv > aapl.csv"],
|
|
1319
|
+
examples: ["history AAPL", "history BHP:ASX --range 5Y", "history ZAR=X --range ALL", "history ZAR=X --tail 5", "history AAPL --range 5Y --csv > aapl.csv"],
|
|
1098
1320
|
},
|
|
1099
1321
|
unknownOptionHint: (flag) => DATE_WINDOW_OPTIONS.has(flag)
|
|
1100
1322
|
? `history takes a --range instead of dates: ${TIME_RANGES.join(", ")}. ${HISTORY_ALL_RANGE}.`
|
|
@@ -1103,15 +1325,46 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
|
1103
1325
|
},
|
|
1104
1326
|
{
|
|
1105
1327
|
name: "options",
|
|
1106
|
-
description: "Fetch an options chain",
|
|
1328
|
+
description: "Fetch an options chain, or rank LEAPS across symbols",
|
|
1107
1329
|
help: {
|
|
1108
1330
|
group: CLI_COMMAND_GROUPS.research,
|
|
1109
|
-
usage: [
|
|
1331
|
+
usage: [OPTIONS_USAGE, LEAPS_USAGE],
|
|
1110
1332
|
options: [
|
|
1111
|
-
{
|
|
1333
|
+
{
|
|
1334
|
+
flags: "--expiration <YYYY-MM-DD|unix>",
|
|
1335
|
+
description: "Expiration as a date (2028-01-21) or Unix seconds; defaults to the nearest one, and lists the others",
|
|
1336
|
+
},
|
|
1112
1337
|
EXCHANGE_OPTION,
|
|
1338
|
+
{ flags: "--leaps", description: "Rank contracts more than a year out across the symbols given, as a stock replacement" },
|
|
1339
|
+
{ flags: "--side <calls|puts>", description: `With --leaps: the side to rank (default ${DEFAULT_LEAPS_CRITERIA.side}s)` },
|
|
1340
|
+
{
|
|
1341
|
+
flags: "--delta <low-high>",
|
|
1342
|
+
description: `With --leaps: absolute delta band (default ${DEFAULT_LEAPS_CRITERIA.minDelta.toFixed(2)}-${DEFAULT_LEAPS_CRITERIA.maxDelta.toFixed(2)})`,
|
|
1343
|
+
},
|
|
1344
|
+
{
|
|
1345
|
+
flags: "--max-spread <percent>",
|
|
1346
|
+
description: `With --leaps: widest bid/ask spread, in percent of the midpoint (default ${DEFAULT_LEAPS_CRITERIA.maxSpreadPercent})`,
|
|
1347
|
+
},
|
|
1348
|
+
{ flags: "--min-oi <contracts>", description: `With --leaps: least open interest (default ${DEFAULT_LEAPS_CRITERIA.minOpenInterest})` },
|
|
1349
|
+
{
|
|
1350
|
+
flags: "--sort <order>",
|
|
1351
|
+
description: "With --leaps: carry (extrinsic per year, lowest first; the default), spread, oi, delta, expiry or symbol",
|
|
1352
|
+
},
|
|
1353
|
+
],
|
|
1354
|
+
sections: [{
|
|
1355
|
+
title: "LEAPS",
|
|
1356
|
+
lines: [
|
|
1357
|
+
"--leaps reads every expiry more than a year out for each symbol and keeps the liquid contracts in the delta band, ranked by extrinsic per year: time value (midpoint less intrinsic) as a percent of spot, per year to expiry.",
|
|
1358
|
+
"A symbol without LEAPS, a quote or a chain is named in a warning and the others still rank. docs/research-data.md defines each figure.",
|
|
1359
|
+
],
|
|
1360
|
+
}],
|
|
1361
|
+
examples: [
|
|
1362
|
+
"options AAPL",
|
|
1363
|
+
"options AAPL --expiration 2028-01-21",
|
|
1364
|
+
"options AAPL:NASDAQ --json",
|
|
1365
|
+
"options AAPL MSFT NVDA GOOGL AMZN META --leaps",
|
|
1366
|
+
"options SPY QQQ --leaps --side puts --delta 0.20-0.40 --csv",
|
|
1113
1367
|
],
|
|
1114
|
-
examples: ["options AAPL", "options AAPL:NASDAQ --json"],
|
|
1115
1368
|
},
|
|
1116
1369
|
execute: runOptions,
|
|
1117
1370
|
},
|
|
@@ -1280,11 +1533,18 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
|
1280
1533
|
},
|
|
1281
1534
|
{
|
|
1282
1535
|
name: "fx",
|
|
1283
|
-
description: "Convert a currency into your base currency",
|
|
1536
|
+
description: "Convert a currency into your base currency, or quote a pair such as USD/NGN",
|
|
1284
1537
|
help: {
|
|
1285
1538
|
group: CLI_COMMAND_GROUPS.markets,
|
|
1286
|
-
usage: [
|
|
1287
|
-
|
|
1539
|
+
usage: ["fx <currency>", "fx <base>/<quote>"],
|
|
1540
|
+
sections: [{
|
|
1541
|
+
title: "Direction",
|
|
1542
|
+
lines: [
|
|
1543
|
+
"A bare code is one unit of it in your base currency. A pair reads as markets quote it: USD/NGN is how many NGN one USD buys. Two codes neither of which is USD, such as ZAR/NGN, cross through their USD rates.",
|
|
1544
|
+
"The answer states both directions: 1 NGN = 0.000752791 USD (USD/NGN 1328.39). --json, --csv and --ndjson carry rate (base currency per currency), pair, inverse, asOf and stale.",
|
|
1545
|
+
],
|
|
1546
|
+
}],
|
|
1547
|
+
examples: ["fx EUR", "fx NGN", "fx USD/NGN", "fx ZAR/NGN", "fx JPY --json"],
|
|
1288
1548
|
},
|
|
1289
1549
|
execute: runFx,
|
|
1290
1550
|
},
|