gloomberb 0.16.3 → 0.16.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -91
- package/package.json +7 -1
- package/src/{plugins/builtin/cloud → account}/auth-form.tsx +9 -9
- package/src/{plugins/builtin/cloud → account}/auth-model.ts +4 -4
- package/src/account/billing.ts +57 -0
- package/src/{plugins/builtin/cloud → account}/device-signin-dialog.tsx +18 -12
- package/src/{plugins/builtin/cloud → account}/device-signin.ts +2 -2
- package/src/{plugins/builtin/cloud → account}/email-auth-attempt.ts +2 -2
- package/src/account/session.ts +16 -0
- package/src/{plugins/builtin/cloud → account}/upgrade-dialog.tsx +42 -17
- package/src/account/upgrade-personalization.ts +57 -0
- package/src/account/upgrade-tickers.ts +124 -0
- package/src/{plugins/builtin/cloud → account}/verification-panel.tsx +6 -6
- 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 +58 -18
- package/src/api-client/earnings.ts +6 -0
- package/src/api-client/futures-curve.ts +56 -0
- package/src/api-client/index.ts +16 -1
- package/src/api-client/market-discovery.ts +10 -1
- package/src/api-client/normalizers.ts +27 -0
- package/src/api-client/paths.ts +95 -23
- package/src/api-client/request.ts +2 -1
- package/src/api-client/research-activity.ts +46 -10
- package/src/api-client/socket.ts +39 -8
- package/src/api-client/types.ts +74 -2
- package/src/api-client/upgrade-experiment.ts +90 -0
- package/src/api-client/yield-curves.ts +8 -0
- package/src/app/global-shortcuts.ts +26 -2
- 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 +25 -4
- 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 +61 -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 +34 -14
- package/src/brokers/signed-in/sign-in.ts +62 -5
- package/src/capabilities/chart-series.ts +11 -1
- package/src/capabilities/registry.ts +8 -1
- package/src/capabilities/types.ts +2 -0
- package/src/cli/commands/crypto-hints.ts +19 -4
- package/src/cli/commands/fred-series.ts +61 -0
- package/src/cli/commands/market.ts +744 -181
- package/src/cli/commands/news-hints.ts +62 -0
- package/src/cli/commands/news-search.ts +53 -0
- package/src/cli/commands/options-chain.ts +172 -0
- package/src/cli/commands/options-leaps.ts +425 -0
- package/src/cli/commands/overview.ts +251 -40
- package/src/cli/commands/remote.ts +2 -1
- package/src/cli/commands/search.ts +109 -15
- package/src/cli/commands/system.ts +151 -24
- package/src/cli/commands/themes.ts +64 -0
- package/src/cli/commands/ticker.ts +149 -40
- package/src/cli/context.ts +45 -14
- package/src/cli/desktop-pane-shot.ts +150 -17
- package/src/cli/earnings-calendar.ts +71 -0
- package/src/cli/errors.ts +25 -2
- package/src/cli/freshness.ts +22 -8
- package/src/cli/fx-pair.ts +57 -0
- package/src/cli/glossary.ts +429 -0
- package/src/cli/help.ts +14 -1
- package/src/cli/history-rows.ts +48 -7
- package/src/cli/index-roots.ts +58 -0
- package/src/cli/index.ts +29 -12
- package/src/cli/known-gaps.ts +27 -0
- package/src/cli/listing-arg.ts +595 -26
- package/src/cli/not-a-ticker.ts +44 -0
- package/src/cli/options.ts +50 -6
- package/src/cli/pane-functions/access-gate.ts +5 -0
- package/src/cli/pane-functions/capabilities.ts +11 -4
- package/src/cli/pane-functions/catalog.ts +48 -7
- package/src/cli/pane-functions/data.ts +48 -13
- package/src/cli/pane-functions/discovery.ts +1 -0
- package/src/cli/pane-functions/dom.ts +41 -19
- package/src/cli/pane-functions/freshness.ts +531 -32
- package/src/cli/pane-functions/function-help.ts +77 -25
- package/src/cli/pane-functions/headless.ts +50 -21
- package/src/cli/pane-functions/index.ts +66 -10
- package/src/cli/pane-functions/market-session.ts +147 -0
- package/src/cli/pane-functions/options.ts +77 -25
- package/src/cli/pane-functions/report-notices.ts +81 -0
- package/src/cli/pane-functions/report.ts +19 -8
- package/src/cli/pane-functions/resolver.ts +53 -18
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +199 -30
- package/src/cli/registry.ts +2 -1
- package/src/cli/report-tables.ts +3 -0
- package/src/cli/result.ts +53 -13
- package/src/cli/row-window.ts +76 -0
- package/src/cli/scroll-truncation.ts +34 -0
- package/src/cli/staged-tickers.ts +52 -0
- package/src/cli/symbol-args.ts +9 -0
- package/src/cli/symbol-list.ts +172 -0
- package/src/cli/ticker-resolution.ts +223 -30
- package/src/cli/types.ts +5 -0
- package/src/components/chart/composite/axis-overlays.tsx +6 -2
- package/src/components/chart/composite/composite-chart.tsx +83 -10
- package/src/components/chart/composite/legend.tsx +88 -8
- package/src/components/chart/composite/panel-surface.tsx +44 -17
- package/src/components/chart/composite/price-axis-labels.tsx +3 -1
- 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/panel/index.tsx +11 -3
- package/src/components/command-bar/panel/runtime.ts +3 -0
- package/src/components/command-bar/panel/types.ts +2 -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/provider-search.ts +4 -3
- 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/routes/ticker-search/results.ts +12 -11
- package/src/components/command-bar/routes/ticker-search/route.ts +4 -3
- 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/command-bar/workflow/tickers.ts +23 -4
- package/src/components/data-table/view.tsx +28 -9
- package/src/components/layout/detached-pane-shell.tsx +9 -0
- package/src/components/layout/floating-pane.tsx +9 -2
- package/src/components/layout/header.tsx +20 -5
- package/src/components/layout/market-summary.ts +23 -9
- package/src/components/layout/pane/content.tsx +40 -29
- package/src/components/layout/pane/cycle-order.ts +2 -11
- package/src/components/layout/pane/footer/index.tsx +6 -2
- package/src/components/layout/pane/footer/model.ts +35 -1
- package/src/components/layout/pane/header.tsx +26 -2
- package/src/components/layout/pane/index.tsx +12 -2
- package/src/components/layout/pane/sidebar.tsx +78 -10
- 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 +21 -5
- package/src/components/layout/shell/drag/index.ts +43 -9
- package/src/components/layout/shell/drag/live.ts +87 -6
- package/src/components/layout/shell/drag/runtime.ts +62 -20
- package/src/components/layout/shell/index.tsx +71 -12
- package/src/components/layout/shell/layout-state.ts +5 -11
- package/src/components/layout/shell/menu.ts +33 -1
- package/src/components/layout/shell/native/pointer-runtime.ts +3 -1
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +216 -28
- 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/terminal-pointer-runtime.ts +24 -0
- 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 +69 -23
- package/src/components/metric-treemap/heat-surface.tsx +37 -9
- package/src/components/metric-treemap/heat-tiles.ts +48 -17
- package/src/components/onboarding/account-step/form-panel.tsx +1 -1
- package/src/components/onboarding/account-step/index.tsx +1 -1
- package/src/components/onboarding/account-step/qr-panel.tsx +1 -1
- package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
- package/src/components/onboarding/onboarding-frame.tsx +33 -10
- package/src/components/onboarding/onboarding-keyboard.ts +1 -1
- package/src/components/onboarding/onboarding-wizard.tsx +50 -41
- 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 +8 -4
- package/src/components/onboarding/wizard-account.ts +2 -2
- package/src/components/pane-template-wizard.tsx +9 -1
- package/src/components/price-selector-dialog.tsx +11 -2
- package/src/components/sign-in-gate.tsx +3 -3
- package/src/components/ticker/list-table-view.tsx +24 -1
- package/src/components/ui/action-row.tsx +2 -1
- package/src/components/ui/button.tsx +17 -1
- package/src/components/ui/choice-dialog.tsx +6 -4
- package/src/components/ui/confirm-dialog.tsx +28 -18
- package/src/components/ui/data-table/index.tsx +49 -0
- package/src/components/ui/data-table/opentui/index.tsx +37 -6
- package/src/components/ui/data-table/types.ts +20 -1
- package/src/components/ui/data-table/visible-range.ts +2 -0
- package/src/components/ui/dialog-actions.tsx +64 -0
- package/src/components/ui/display.tsx +17 -3
- package/src/components/ui/frame.tsx +35 -2
- package/src/components/ui/icon.tsx +13 -2
- package/src/components/ui/index.ts +1 -1
- package/src/components/ui/text-prompt-dialog.tsx +11 -9
- 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/cached-query.ts +2 -0
- 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 +44 -5
- package/src/data/ticker-repository.ts +5 -0
- package/src/data/ticker-store.ts +15 -0
- package/src/docs/content.ts +215 -0
- package/src/docs/load.ts +139 -0
- package/src/docs/requests.ts +39 -0
- package/src/docs/snapshot.chunk.ts +8 -0
- package/src/docs/snapshot.json +7416 -0
- package/src/docs/topics.ts +216 -0
- package/src/i18n/es.ts +75 -5
- package/src/i18n/ja.ts +75 -5
- package/src/i18n/ko.ts +75 -5
- package/src/i18n/zh-cn.ts +75 -5
- package/src/i18n/zh-tw.ts +75 -5
- package/src/layout/desks.ts +2 -3
- package/src/layout/pane-layout-move.ts +264 -0
- package/src/layout/pane-manager/dock-tree.ts +18 -0
- package/src/layout/pane-manager/hidden-panes.ts +160 -0
- package/src/layout/pane-manager/index.ts +2 -0
- package/src/market-data/coordinator/chart.ts +10 -4
- package/src/market-data/coordinator/entries.ts +7 -1
- package/src/market-data/coordinator/index.ts +6 -3
- package/src/market-data/coordinator/quotes.ts +2 -2
- package/src/{plugins/builtin/futures/contracts.ts → market-data/futures-board.ts} +81 -28
- package/src/market-data/futures-contract.ts +151 -0
- package/src/market-data/hooks.tsx +10 -2
- package/src/market-data/market/format.ts +39 -21
- package/src/market-data/market/freshness.ts +193 -30
- package/src/market-data/market/status.ts +59 -23
- package/src/market-data/options-alternatives.ts +69 -0
- package/src/market-data/published-apac-sessions.ts +23 -1
- package/src/market-data/quotes/contributions.ts +22 -2
- package/src/market-data/quotes/metadata.ts +8 -9
- package/src/market-data/quotes/resolution.ts +2 -0
- package/src/market-data/quotes/screener-live-quotes.ts +61 -9
- package/src/market-data/result-types.ts +2 -0
- package/src/plugins/builtin/account-management/keyboard.ts +3 -0
- package/src/plugins/builtin/account-management/model.ts +2 -56
- package/src/plugins/builtin/account-management/navigation.ts +1 -1
- package/src/plugins/builtin/account-management/pane.tsx +37 -8
- package/src/plugins/builtin/account-management/password-dialog.tsx +1 -1
- package/src/plugins/builtin/alerts/cli.ts +313 -47
- package/src/plugins/builtin/alerts/events-pane.tsx +45 -7
- package/src/plugins/builtin/alerts/events.ts +23 -2
- package/src/plugins/builtin/alerts/history-pane.tsx +7 -2
- package/src/plugins/builtin/alerts/history.ts +2 -0
- package/src/plugins/builtin/alerts/index.tsx +41 -9
- package/src/plugins/builtin/alerts/pane.tsx +8 -0
- package/src/plugins/builtin/alerts/quotes.ts +3 -0
- package/src/plugins/builtin/alerts/research-builder.ts +8 -3
- package/src/plugins/builtin/alerts/research-rules.ts +18 -2
- package/src/plugins/builtin/alerts/scheduled-check.ts +122 -0
- package/src/plugins/builtin/alerts/scheduled-sources.ts +25 -0
- package/src/plugins/builtin/alerts/scheduled.ts +254 -0
- package/src/plugins/builtin/alerts/targets.ts +92 -0
- package/src/plugins/builtin/analytics/index.tsx +4 -2
- package/src/plugins/builtin/analytics/risk-headless.ts +33 -13
- package/src/plugins/builtin/analytics/risk-math.ts +91 -27
- package/src/plugins/builtin/analytics/risk-model.ts +71 -12
- package/src/plugins/builtin/analytics/risk-pane.tsx +23 -12
- package/src/plugins/builtin/attention/index.tsx +2 -0
- package/src/plugins/builtin/attention/pane.tsx +3 -1
- package/src/plugins/builtin/attention/series.ts +26 -0
- package/src/plugins/builtin/awards/index.tsx +2 -0
- package/src/plugins/builtin/awards/pane.tsx +3 -1
- package/src/plugins/builtin/awards/series.ts +29 -0
- package/src/plugins/builtin/backtest/pane.tsx +1 -1
- package/src/plugins/builtin/bond-calculator/model.ts +2 -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/catalysts/pane.tsx +3 -1
- package/src/plugins/builtin/cds/client.ts +70 -13
- package/src/plugins/builtin/cds/headless.ts +116 -19
- package/src/plugins/builtin/cds/index.tsx +3 -1
- package/src/plugins/builtin/cds/model.ts +193 -31
- package/src/plugins/builtin/cds/pane.tsx +56 -14
- package/src/plugins/builtin/cds/series.ts +19 -0
- package/src/plugins/builtin/central-bank-rates/index.tsx +2 -0
- package/src/plugins/builtin/central-bank-rates/series.ts +42 -0
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +192 -255
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +84 -46
- package/src/plugins/builtin/chart-composer/editor.tsx +5 -4
- package/src/plugins/builtin/chart-composer/field-series.ts +40 -0
- package/src/plugins/builtin/chart-composer/headless.ts +8 -3
- package/src/plugins/builtin/chart-composer/index.tsx +9 -2
- package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
- package/src/plugins/builtin/chart-composer/series-catalog.ts +58 -50
- package/src/plugins/builtin/chart-composer/series-expression.ts +18 -10
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +59 -3
- package/src/plugins/builtin/chart-composer/user-series.ts +84 -0
- 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/new-dm-dialog.tsx +10 -2
- package/src/plugins/builtin/chat/content/user-map.ts +3 -1
- package/src/plugins/builtin/chat/controller/index.ts +50 -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 +223 -23
- package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
- package/src/plugins/builtin/cloud/askg/executor.ts +57 -3
- package/src/plugins/builtin/cloud/askg/manifest.ts +33 -7
- package/src/plugins/builtin/cloud/askg/model.ts +156 -78
- package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
- package/src/plugins/builtin/cloud/askg/protocol.ts +50 -3
- package/src/plugins/builtin/cloud/askg/tool-display.ts +440 -0
- package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
- package/src/plugins/builtin/cloud/askg/undo.ts +2 -0
- package/src/plugins/builtin/cloud/auth-actions.tsx +1 -1
- package/src/plugins/builtin/cloud/auth-commands.ts +1 -1
- package/src/plugins/builtin/cloud/auth-dialog.tsx +3 -3
- 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 +363 -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/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 +734 -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 +239 -0
- package/src/plugins/builtin/cloud/terminal-relay/policy.ts +162 -0
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +192 -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/thesis/exposure.ts +1 -1
- package/src/plugins/builtin/cloud/thesis/prompts.tsx +2 -1
- package/src/plugins/builtin/comment-letters/client.ts +1 -3
- package/src/plugins/builtin/company-attention/app-rank.tsx +1 -1
- package/src/plugins/builtin/company-attention/headless.ts +19 -5
- package/src/plugins/builtin/company-attention/index.tsx +2 -0
- package/src/plugins/builtin/company-attention/model.ts +26 -7
- package/src/plugins/builtin/company-attention/pane.tsx +12 -9
- package/src/plugins/builtin/company-attention/series.ts +52 -0
- 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 +19 -9
- package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
- package/src/plugins/builtin/composite-plugins.ts +116 -67
- package/src/plugins/builtin/cot/index.tsx +3 -1
- package/src/plugins/builtin/cot/model.ts +40 -11
- package/src/plugins/builtin/cot/series.ts +27 -0
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
- package/src/plugins/builtin/credit-boards/index.tsx +23 -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/series.ts +66 -0
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -0
- package/src/plugins/builtin/credit-conditions/series.ts +26 -0
- package/src/plugins/builtin/credit-documents/headless.ts +31 -1
- package/src/plugins/builtin/credit-documents/pane.tsx +3 -1
- package/src/plugins/builtin/crypto-board/headless.ts +2 -3
- package/src/plugins/builtin/crypto-board/index.tsx +2 -0
- package/src/plugins/builtin/crypto-board/model.ts +12 -0
- package/src/plugins/builtin/crypto-board/pane.tsx +19 -12
- package/src/plugins/builtin/crypto-board/series.ts +48 -0
- package/src/plugins/builtin/custom-view/live-quotes.ts +2 -2
- 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/index.tsx +2 -0
- package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
- package/src/plugins/builtin/debt-maturities/series.ts +45 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +2 -2
- package/src/plugins/builtin/dividend-yield/index.tsx +3 -1
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/series.ts +20 -0
- package/src/plugins/builtin/docs/blocks.tsx +181 -0
- package/src/plugins/builtin/docs/index.tsx +68 -0
- package/src/plugins/builtin/docs/pane.tsx +426 -0
- package/src/plugins/builtin/docs/targets.ts +72 -0
- package/src/plugins/builtin/docs/use-docs.ts +60 -0
- package/src/plugins/builtin/doe/index.tsx +2 -0
- package/src/plugins/builtin/doe/series.ts +62 -0
- package/src/plugins/builtin/earnings/client.ts +3 -2
- package/src/plugins/builtin/earnings/format.ts +1 -5
- package/src/plugins/builtin/earnings/headless.ts +58 -6
- package/src/plugins/builtin/earnings/history-model.ts +15 -1
- package/src/plugins/builtin/earnings/history-table.ts +13 -2
- package/src/plugins/builtin/earnings/history.tsx +7 -3
- 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/fred-catalog.ts +189 -0
- package/src/plugins/builtin/econ/fred-series-map.ts +0 -46
- package/src/plugins/builtin/econ/index.tsx +70 -22
- package/src/plugins/builtin/econ-statistics/defs.ts +5 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +30 -5
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/stats.ts +1 -0
- package/src/plugins/builtin/econ-statistics/view.ts +17 -0
- package/src/plugins/builtin/equity-screener/pane.tsx +4 -11
- package/src/plugins/builtin/executives/data.ts +17 -6
- package/src/plugins/builtin/executives/pane.tsx +16 -7
- package/src/plugins/builtin/exposure/holdings.ts +1 -1
- package/src/plugins/builtin/exposure/pane.tsx +3 -1
- package/src/plugins/builtin/fear-greed/index.tsx +4 -0
- package/src/plugins/builtin/fear-greed/series.ts +52 -0
- package/src/plugins/builtin/filing-events/pane.tsx +22 -5
- package/src/plugins/builtin/futures/index.tsx +43 -3
- package/src/plugins/builtin/futures/model.ts +1 -1
- package/src/plugins/builtin/futures/returns.ts +1 -1
- package/src/plugins/builtin/futures/series.ts +13 -0
- package/src/plugins/builtin/futures/table.tsx +1 -1
- package/src/plugins/builtin/futures/use-front-returns.ts +1 -1
- package/src/plugins/builtin/futures-curve/client.ts +12 -3
- package/src/plugins/builtin/futures-curve/headless.ts +70 -9
- package/src/plugins/builtin/futures-curve/index.tsx +1 -1
- package/src/plugins/builtin/futures-curve/model.ts +66 -11
- package/src/plugins/builtin/futures-curve/pane.tsx +52 -18
- 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/board.tsx +13 -10
- package/src/plugins/builtin/gpu/changes.tsx +4 -4
- package/src/plugins/builtin/gpu/client.ts +19 -1
- package/src/plugins/builtin/gpu/equities.tsx +14 -10
- package/src/plugins/builtin/gpu/headless.ts +64 -27
- package/src/plugins/builtin/gpu/history.tsx +3 -3
- package/src/plugins/builtin/gpu/index.tsx +2 -0
- package/src/plugins/builtin/gpu/model.ts +114 -20
- package/src/plugins/builtin/gpu/pane.tsx +28 -12
- package/src/plugins/builtin/gpu/series.ts +38 -0
- package/src/plugins/builtin/help/function-card.tsx +25 -6
- package/src/plugins/builtin/help/index.tsx +18 -8
- package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
- package/src/plugins/builtin/holders/beneficial-model.ts +90 -3
- package/src/plugins/builtin/holders/beneficial-report.ts +57 -5
- package/src/plugins/builtin/holders/beneficial-view.tsx +62 -14
- package/src/plugins/builtin/holders/client.ts +22 -3
- package/src/plugins/builtin/holders/format.ts +18 -3
- package/src/plugins/builtin/holders/headless.ts +42 -134
- package/src/plugins/builtin/holders/pane.tsx +14 -4
- package/src/plugins/builtin/holders/report-header.ts +40 -5
- package/src/plugins/builtin/holders/report-rows.ts +240 -0
- package/src/plugins/builtin/insider/client.ts +35 -20
- package/src/plugins/builtin/insider/headless.ts +41 -11
- package/src/plugins/builtin/insider/index.tsx +27 -10
- package/src/plugins/builtin/insider/insider-data.ts +10 -0
- package/src/plugins/builtin/insider/issuer.ts +49 -0
- package/src/plugins/builtin/insider/model.ts +3 -0
- package/src/plugins/builtin/iv-history/charts.tsx +21 -7
- package/src/plugins/builtin/iv-history/client.ts +26 -6
- package/src/plugins/builtin/iv-history/format.ts +33 -2
- package/src/plugins/builtin/iv-history/headless.ts +27 -12
- package/src/plugins/builtin/iv-history/index.tsx +5 -3
- package/src/plugins/builtin/iv-history/model.ts +172 -20
- package/src/plugins/builtin/iv-history/pane.tsx +11 -3
- package/src/plugins/builtin/iv-history/screen-pane.tsx +45 -25
- package/src/plugins/builtin/iv-history/series.ts +29 -0
- package/src/plugins/builtin/jobs/headless.ts +28 -3
- package/src/plugins/builtin/jobs/index.tsx +3 -1
- package/src/plugins/builtin/jobs/pane.tsx +1 -1
- package/src/plugins/builtin/jobs/series.ts +19 -0
- 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 +111 -40
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +0 -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 +68 -3
- package/src/plugins/builtin/macro-day/headless.ts +18 -12
- package/src/plugins/builtin/macro-day/index.tsx +3 -0
- package/src/plugins/builtin/macro-day/model.ts +66 -15
- package/src/plugins/builtin/macro-day/pane.tsx +34 -20
- 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 +102 -3
- package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
- package/src/plugins/builtin/market-movers/client.ts +7 -5
- package/src/plugins/builtin/market-movers/headless.ts +31 -4
- package/src/plugins/builtin/market-movers/index.tsx +11 -3
- package/src/plugins/builtin/market-movers/model.ts +101 -17
- package/src/plugins/builtin/market-movers/screener.ts +31 -0
- package/src/plugins/builtin/market-movers/table.ts +50 -23
- package/src/plugins/builtin/market-valuation/chart-series.ts +29 -69
- package/src/plugins/builtin/market-valuation/index.tsx +3 -3
- package/src/plugins/builtin/market-valuation/indicators.ts +0 -5
- package/src/plugins/builtin/members/headless.ts +7 -2
- package/src/plugins/builtin/members/model.ts +31 -3
- package/src/plugins/builtin/members/pane.tsx +3 -6
- package/src/plugins/builtin/mna/headless.ts +15 -3
- package/src/plugins/builtin/money-markets/index.tsx +2 -0
- package/src/plugins/builtin/money-markets/series.ts +41 -0
- package/src/plugins/builtin/news/headless.ts +9 -2
- package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
- package/src/plugins/builtin/notes/cli.ts +12 -7
- 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 +162 -13
- package/src/plugins/builtin/options-calculator/inputs.ts +2 -1
- package/src/plugins/builtin/options-positioning/client.ts +7 -3
- package/src/plugins/builtin/options-positioning/headless.ts +5 -2
- package/src/plugins/builtin/options-positioning/pane.tsx +2 -2
- package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
- package/src/plugins/builtin/options-scenario/client.ts +75 -18
- package/src/plugins/builtin/options-scenario/headless.ts +72 -24
- package/src/plugins/builtin/options-scenario/hedge.ts +285 -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/client.ts +40 -6
- package/src/plugins/builtin/perps/equity-row.tsx +33 -8
- package/src/plugins/builtin/perps/freshness.ts +66 -0
- package/src/plugins/builtin/perps/headless.ts +45 -30
- package/src/plugins/builtin/perps/index.tsx +18 -1
- package/src/plugins/builtin/perps/model.ts +32 -6
- package/src/plugins/builtin/perps/pane.tsx +76 -20
- package/src/plugins/builtin/perps/series.ts +76 -0
- 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 +357 -39
- package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
- package/src/plugins/builtin/plugin-module.ts +90 -4
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +1 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +273 -0
- package/src/plugins/builtin/portfolio-list/cli/import-13f.ts +109 -0
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +400 -99
- package/src/plugins/builtin/portfolio-list/cli/render.ts +281 -43
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +26 -16
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +120 -47
- package/src/plugins/builtin/portfolio-list/column-values.ts +1 -1
- package/src/plugins/builtin/portfolio-list/grid.tsx +1 -1
- package/src/plugins/builtin/portfolio-list/headless.ts +70 -9
- package/src/plugins/builtin/portfolio-list/imported-from.ts +8 -0
- package/src/plugins/builtin/portfolio-list/live-valuation.ts +6 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +17 -1
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +7 -1
- package/src/plugins/builtin/power/client.ts +16 -0
- package/src/plugins/builtin/power/evidence.ts +6 -2
- package/src/plugins/builtin/power/headless.ts +71 -7
- package/src/plugins/builtin/power/index.tsx +4 -1
- package/src/plugins/builtin/power/model.ts +72 -1
- package/src/plugins/builtin/power/pane.tsx +32 -9
- package/src/plugins/builtin/rate-path/headless.ts +11 -3
- package/src/plugins/builtin/realized-vol/headless.ts +8 -0
- package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +7 -4
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
- package/src/plugins/builtin/research/event-model.ts +68 -24
- package/src/plugins/builtin/research/events-headless.ts +2 -1
- package/src/plugins/builtin/research/headless.ts +2 -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/client.ts +15 -7
- package/src/plugins/builtin/seasonality/headless.ts +36 -12
- package/src/plugins/builtin/seasonality/index.tsx +2 -2
- package/src/plugins/builtin/seasonality/model.ts +119 -0
- package/src/plugins/builtin/seasonality/pane.tsx +108 -37
- 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/series-inventory.ts +91 -0
- package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -1
- package/src/plugins/builtin/shared/command-tab.ts +69 -0
- package/src/plugins/builtin/shared/earnings-report.ts +55 -0
- package/src/plugins/builtin/shared/fred-points.ts +20 -0
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +101 -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/open-description.ts +29 -0
- package/src/plugins/builtin/shared/report-freshness.ts +59 -2
- package/src/plugins/builtin/shared/series-warning.ts +5 -0
- 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/headless.ts +1 -0
- package/src/plugins/builtin/short-interest/index.tsx +21 -4
- package/src/plugins/builtin/short-interest/series.ts +36 -0
- package/src/plugins/builtin/short-interest/surface.tsx +4 -4
- package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
- package/src/plugins/builtin/short-interest/watch-model.ts +9 -5
- package/src/plugins/builtin/short-volume/headless.ts +1 -0
- package/src/plugins/builtin/short-volume/index.tsx +3 -10
- package/src/plugins/builtin/short-volume/series.ts +23 -0
- package/src/plugins/builtin/social-mentions/index.tsx +2 -0
- package/src/plugins/builtin/social-mentions/series.ts +23 -0
- package/src/plugins/builtin/supply-chain/graph-pane.tsx +3 -2
- package/src/plugins/builtin/supply-chain/model.ts +1 -1
- package/src/plugins/builtin/supply-chain/pane.tsx +3 -1
- package/src/plugins/builtin/thirteenf/api.ts +1 -0
- package/src/plugins/builtin/thirteenf/data.ts +6 -4
- package/src/plugins/builtin/thirteenf/headless.ts +217 -36
- package/src/plugins/builtin/thirteenf/model.ts +39 -1
- package/src/plugins/builtin/thirteenf/overlap.ts +75 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +10 -1
- package/src/plugins/builtin/thirteenf/report-notes.ts +103 -1
- package/src/plugins/builtin/thirteenf/types.ts +2 -0
- package/src/plugins/builtin/ticker-detail/headless.ts +43 -21
- package/src/plugins/builtin/ticker-detail/index.tsx +3 -0
- package/src/plugins/builtin/ticker-detail/overview/components.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/overview/futures-contract.ts +31 -0
- package/src/plugins/builtin/ticker-detail/overview/model.ts +3 -3
- package/src/plugins/builtin/ticker-detail/overview/pane.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +48 -4
- 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/ticker-detail/quote-monitor/index.tsx +13 -3
- package/src/plugins/builtin/time-sales/headless.ts +1 -0
- 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 +56 -6
- package/src/plugins/builtin/treasury-auctions/index.tsx +12 -9
- package/src/plugins/builtin/treasury-auctions/model.ts +205 -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 +125 -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/volatility/index.tsx +2 -0
- package/src/plugins/builtin/volatility/series.ts +84 -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 +27 -2
- package/src/plugins/builtin/world-venue-map/headless.ts +4 -1
- package/src/plugins/builtin/world-venue-map/index.ts +3 -2
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +8 -4
- package/src/plugins/builtin/world-venue-map/layers.ts +29 -0
- package/src/plugins/builtin/yield-curve/chart.ts +2 -2
- package/src/plugins/builtin/yield-curve/curves.ts +21 -8
- package/src/plugins/builtin/yield-curve/headless.ts +71 -18
- package/src/plugins/builtin/yield-curve/index.tsx +4 -2
- package/src/plugins/builtin/yield-curve/series.ts +86 -0
- package/src/plugins/builtin-editorial.ts +126 -0
- package/src/plugins/catalog-backend.ts +16 -9
- package/src/plugins/catalog-browser.ts +129 -64
- 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 +173 -28
- package/src/plugins/registry/plugin-state.ts +25 -1
- package/src/plugins/runtime/context.tsx +29 -0
- package/src/{plugins/builtin/portfolio-list → portfolio}/position-metrics.ts +6 -6
- package/src/portfolio/position-value.ts +40 -0
- package/src/public/__snapshots__/public-api.test.ts.snap +7 -0
- package/src/public/time-series.ts +17 -0
- package/src/react/auto-refresh.ts +13 -3
- package/src/react/input.ts +48 -1
- package/src/remote/app-host.tsx +25 -4
- package/src/remote/confirm-change.tsx +85 -0
- package/src/remote/controller-utils.ts +8 -1
- package/src/remote/controller.ts +126 -59
- package/src/remote/matching.ts +69 -0
- package/src/remote/pane-screenshot.ts +47 -0
- package/src/remote/schema.ts +63 -1
- package/src/remote/semantic-tree.tsx +14 -0
- package/src/remote/types.ts +32 -0
- package/src/remote/watchlist.ts +306 -0
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/worker.ts +1 -1
- package/src/renderers/dom/data-table/frozen-column.ts +3 -1
- package/src/renderers/dom/data-table/index.tsx +33 -6
- package/src/renderers/dom/data-table/row.tsx +27 -1
- package/src/renderers/dom/desktop/control-styles.ts +63 -1
- package/src/renderers/dom/desktop/controls.tsx +30 -23
- package/src/renderers/dom/desktop/dialog-actions.tsx +170 -0
- package/src/renderers/dom/desktop/icons.tsx +21 -0
- package/src/renderers/dom/desktop/query-bar.tsx +59 -32
- package/src/renderers/dom/dom-ui-host.tsx +3 -0
- package/src/renderers/dom/host/box.tsx +4 -1
- package/src/renderers/dom/host/chart-surface.tsx +6 -2
- package/src/renderers/dom/host/drag-shield.ts +17 -6
- package/src/renderers/dom/host/live-frame.ts +14 -2
- package/src/renderers/dom/host/overflow-fade.ts +14 -1
- package/src/renderers/dom/host/size-observer.ts +26 -0
- package/src/renderers/dom/host/surface-3d.tsx +7 -2
- package/src/renderers/dom/host/text.tsx +1 -1
- package/src/renderers/dom/input-host.tsx +6 -1
- package/src/renderers/dom/styles.css +74 -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 +18 -3
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +109 -5
- package/src/renderers/electrobun/view/cli-pane-shot-health.ts +10 -26
- package/src/renderers/electrobun/view/main.tsx +6 -1
- package/src/renderers/opentui/input-host.tsx +6 -1
- package/src/renderers/opentui/start.tsx +9 -0
- package/src/series/capability.ts +75 -0
- package/src/series/csv.ts +160 -0
- package/src/series/files.ts +107 -0
- package/src/series/registry.ts +198 -0
- package/src/series/resolve.ts +107 -0
- package/src/series/standalone.ts +48 -0
- package/src/series/types.ts +145 -0
- package/src/series/validate.ts +171 -0
- package/src/shares/portable-layout.ts +16 -6
- package/src/sources/gloomberb-cloud/index.ts +26 -17
- package/src/sources/gloomberb-cloud/normalizers.ts +5 -1
- package/src/sources/history-result.ts +1 -0
- package/src/sources/listing-symbols.ts +14 -5
- package/src/sources/provider-errors.ts +88 -3
- package/src/sources/provider-router/batches.ts +4 -1
- package/src/sources/provider-router/cache.ts +4 -2
- package/src/sources/provider-router/cached-routes.ts +9 -5
- package/src/sources/provider-router/financial-routes.ts +12 -5
- package/src/sources/provider-router/financials.ts +55 -15
- package/src/sources/provider-router/history.ts +68 -34
- package/src/sources/provider-router/index.ts +1 -1
- package/src/sources/provider-router/primary.ts +23 -5
- package/src/sources/provider-router/search.ts +1 -0
- package/src/sources/sec-edgar.ts +68 -8
- package/src/sync/core-contributors.ts +5 -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/company-name.ts +75 -0
- package/src/tickers/instrument-kind.ts +25 -0
- package/src/tickers/open-target.ts +3 -2
- package/src/tickers/search/index.ts +67 -9
- package/src/tickers/search/ranking.ts +78 -18
- package/src/tickers/search/types.ts +10 -2
- package/src/tickers/starter-symbols.ts +8 -0
- package/src/tickers/us-listing.ts +34 -0
- package/src/time-series/hooks.ts +9 -0
- package/src/time-series/resolve.ts +96 -30
- package/src/time-series/types.ts +4 -2
- package/src/time-series/use-chart-resolution.ts +32 -0
- package/src/types/config.ts +67 -1
- package/src/types/data-provider.ts +5 -0
- package/src/types/financials.ts +52 -1
- package/src/types/headless.ts +72 -0
- package/src/types/instrument.ts +8 -0
- package/src/types/plugin.ts +68 -3
- package/src/types/price-history.ts +7 -0
- package/src/types/ticker.ts +14 -0
- package/src/ui/host.tsx +26 -4
- package/src/ui/index.tsx +2 -0
- package/src/ui/live-resize.ts +81 -0
- package/src/ui/live-stretch.ts +121 -0
- package/src/ui/measured-cells.ts +58 -0
- package/src/ui/pane-fill.ts +11 -0
- package/src/utils/basis-points.ts +17 -0
- package/src/utils/cli-output.ts +21 -2
- package/src/utils/concurrency.ts +25 -0
- package/src/utils/crypto-pair.ts +20 -0
- package/src/utils/currency-pair.ts +19 -0
- package/src/utils/decimal-comma.ts +19 -0
- package/src/utils/dom-screenshot-css.ts +265 -0
- package/src/utils/dom-screenshot-rasters.ts +276 -0
- package/src/utils/dom-screenshot.ts +437 -52
- package/src/utils/exchange-rate-snapshot.ts +6 -2
- package/src/utils/exchanges.ts +120 -1
- package/src/utils/format.ts +17 -3
- package/src/utils/fundamentals.ts +66 -0
- package/src/utils/futures-generic.ts +6 -12
- package/src/utils/fx-market-hours.ts +44 -0
- package/src/utils/option-expiry.ts +126 -0
- package/src/utils/price-history.ts +130 -40
- package/src/utils/rpc-timeout-error.ts +6 -2
- package/src/utils/sec-eps-basis.ts +178 -36
- package/src/utils/sec.ts +98 -40
- package/src/utils/throttled-fetch.ts +51 -22
- package/src/utils/utc-time.ts +51 -4
|
@@ -7,13 +7,21 @@ import type {
|
|
|
7
7
|
CorporateActionsData,
|
|
8
8
|
HolderData,
|
|
9
9
|
OptionsChain,
|
|
10
|
+
Quote,
|
|
10
11
|
TickerFinancials,
|
|
11
12
|
} from "../../types/financials";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
|
|
13
|
+
import { currencyPriceMinDigits, formatMarketPrice, formatMarketPriceWithCurrency, quoteFormatOptions } from "../../market-data/market/format";
|
|
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
|
-
import { isoDate, parsePositiveInt,
|
|
24
|
+
import { isoDate, parsePositiveInt, requireOneArg, takeFlag, takeOption } from "./command-utils";
|
|
17
25
|
import type { BuiltinCliCommandDef } from "../command-options";
|
|
18
26
|
import { CLI_COMMAND_GROUPS, TABLE_SECTION_OPTION } from "../help";
|
|
19
27
|
import {
|
|
@@ -22,7 +30,7 @@ import {
|
|
|
22
30
|
formatFractionPercentCell,
|
|
23
31
|
formatPriceRange,
|
|
24
32
|
} from "../helpers";
|
|
25
|
-
import { cliStyles, renderStats } from "../../utils/cli-output";
|
|
33
|
+
import { cliStyles, colorBySign, renderSection, renderStats } from "../../utils/cli-output";
|
|
26
34
|
import { formatPerShareNumber } from "../../utils/reported-money";
|
|
27
35
|
import {
|
|
28
36
|
analystTargetCurrency,
|
|
@@ -32,9 +40,27 @@ 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";
|
|
37
|
-
import {
|
|
44
|
+
import { isQuoteStaleForCurrentSession } from "../../market-data/quotes/freshness";
|
|
45
|
+
import { isOptionsUnavailableError, listedOptionsAlternative, optionsUnavailableTitle } from "../../market-data/options-alternatives";
|
|
46
|
+
import { cliFuturesContract, futuresContractMetrics, fundamentalsReportTables, renderFundamentalsReport } from "./ticker";
|
|
47
|
+
import type { FuturesContractTermsPayload } from "../../api-client/futures-curve";
|
|
48
|
+
import { futuresSettleText, isFuturesInstrument } from "../../market-data/futures-contract";
|
|
49
|
+
import {
|
|
50
|
+
chainHasExpiry,
|
|
51
|
+
chainWithModelFigures,
|
|
52
|
+
formatExpiryList,
|
|
53
|
+
loadOptionModelInputs,
|
|
54
|
+
formatOptionDeltaCell,
|
|
55
|
+
formatOptionIvCell,
|
|
56
|
+
formatOptionQuoteCell,
|
|
57
|
+
missingExpiryMessage,
|
|
58
|
+
OPTION_MODEL_RATE,
|
|
59
|
+
OPTIONS_USAGE,
|
|
60
|
+
optionRows,
|
|
61
|
+
parseOptionExpiration,
|
|
62
|
+
} from "./options-chain";
|
|
63
|
+
import { DEFAULT_LEAPS_CRITERIA, LEAPS_USAGE, parseLeapsCriteria, runLeapsScreen, takeLeapsOptions } from "./options-leaps";
|
|
38
64
|
import type { CliResultColumn } from "../result";
|
|
39
65
|
import {
|
|
40
66
|
exportRowsTable,
|
|
@@ -45,6 +71,7 @@ import {
|
|
|
45
71
|
import {
|
|
46
72
|
historyFacts,
|
|
47
73
|
historyFlagNote,
|
|
74
|
+
historyGapNote,
|
|
48
75
|
historyAllRangeYears,
|
|
49
76
|
historyIntervalsByRange,
|
|
50
77
|
historyNotes,
|
|
@@ -53,36 +80,60 @@ import {
|
|
|
53
80
|
historyUnit,
|
|
54
81
|
} from "../history-rows";
|
|
55
82
|
import { CRYPTO_BOARD_HINT, quoteNotes } from "./crypto-hints";
|
|
83
|
+
import { bareFuturesCode } from "../../market-data/futures-board";
|
|
84
|
+
import { newsEmptyMessage } from "./news-hints";
|
|
85
|
+
import { newsFallbackText, newsSearchHeading, newsSearchText, newsSearchTextProblem, searchNewsStories } from "./news-search";
|
|
56
86
|
import { formatUtcTime } from "../../utils/utc-time";
|
|
57
87
|
import { isFiniteNumber } from "../../utils/guards";
|
|
58
88
|
import { exportedFundamentals } from "../../utils/price-earnings";
|
|
59
|
-
import { fundamentalsFreshness, quotesFreshness, rowsFreshness } from "../freshness";
|
|
89
|
+
import { fundamentalsFreshness, fxPairFreshness, quotesFreshness, rowsFreshness } from "../freshness";
|
|
60
90
|
import {
|
|
61
91
|
barHistoryFreshness,
|
|
62
92
|
barResolutionFromDates,
|
|
93
|
+
cloudNewsFreshness,
|
|
94
|
+
cloudRealtimeAccess,
|
|
63
95
|
REPORTED_DATA,
|
|
64
96
|
SEC_FILINGS,
|
|
65
97
|
} from "../../plugins/builtin/shared/report-freshness";
|
|
66
98
|
import {
|
|
99
|
+
bareListingNote,
|
|
67
100
|
EXCHANGE_OPTION,
|
|
101
|
+
futuresTryLine,
|
|
102
|
+
isNoProviderError,
|
|
68
103
|
listingHeading,
|
|
69
104
|
listingIdentity,
|
|
70
105
|
listingTitle,
|
|
71
106
|
listingVenues,
|
|
107
|
+
listingChoiceNotes,
|
|
108
|
+
listingReadNotes,
|
|
109
|
+
loadBareVenues,
|
|
110
|
+
type ListingVenue,
|
|
72
111
|
loadForListing,
|
|
73
112
|
loadListingQuote,
|
|
113
|
+
noLongerTradingNote,
|
|
74
114
|
notTradedMessage,
|
|
115
|
+
quoteBareListing,
|
|
75
116
|
requireCliListing,
|
|
76
117
|
type CliListing,
|
|
118
|
+
type ListingDataRequest,
|
|
77
119
|
type ListingIdentity,
|
|
78
120
|
} from "../listing-arg";
|
|
121
|
+
import { indexRootAlsoLine, indexRootFor, indexRootTryLine, isYieldIndexSymbol } from "../index-roots";
|
|
122
|
+
import { earningsCalendarFailure, earningsChoiceNote, earningsDateLabel, isListingAbroad, noListingCalendarNote } from "../earnings-calendar";
|
|
123
|
+
import { formatBasisPoints, toBasisPoints } from "../../utils/basis-points";
|
|
124
|
+
import { providerMissReason } from "../../sources/provider-errors";
|
|
125
|
+
import { isNotATickerMessage } from "../not-a-ticker";
|
|
126
|
+
import { splitSymbolArgs } from "../symbol-args";
|
|
79
127
|
import { secRegistrantMismatchMessage, SecRegistrantMismatchError, areDifferentCompanies } from "../../sources/sec-registrant";
|
|
80
128
|
import { isCryptoPairSymbol } from "../../utils/crypto-pair";
|
|
81
129
|
import { isUsListingExchange } from "../../utils/exchanges";
|
|
82
|
-
import { nonUsSecListingVenue } from "../../utils/sec";
|
|
130
|
+
import { noSecReportsNotice, nonUsSecListingVenue } from "../../utils/sec";
|
|
83
131
|
import type { MarketContext } from "../types";
|
|
132
|
+
import { windowRows } from "../row-window";
|
|
133
|
+
import { crossRate, describeFxRate, isUsableRate, parseFxRequest } from "../fx-pair";
|
|
84
134
|
import {
|
|
85
135
|
holderListFacts,
|
|
136
|
+
holderListPeriod,
|
|
86
137
|
holderShareBasisMarker,
|
|
87
138
|
holderShareBasisNote,
|
|
88
139
|
holderValueBasis,
|
|
@@ -90,15 +141,27 @@ import {
|
|
|
90
141
|
nonUsHolderCaveat,
|
|
91
142
|
sharedReportDate,
|
|
92
143
|
} from "../../plugins/builtin/holders/report-header";
|
|
93
|
-
import {
|
|
144
|
+
import { formatPercentHeld, holderStakeMarketCap, holderValueCurrency } from "../../plugins/builtin/holders/format";
|
|
145
|
+
import {
|
|
146
|
+
holderReportColumns,
|
|
147
|
+
holderReportDates,
|
|
148
|
+
holderReportNotices,
|
|
149
|
+
holderReportRows,
|
|
150
|
+
holderReportUnits,
|
|
151
|
+
unvaluedHoldersNote,
|
|
152
|
+
} from "../../plugins/builtin/holders/report-rows";
|
|
94
153
|
import { fetchBeneficialOwners } from "../../plugins/builtin/holders/beneficial-client";
|
|
154
|
+
import { classSharesToday } from "../../plugins/builtin/holders/client";
|
|
95
155
|
import { filingFormMatches, SEC_FILING_FETCH_LIMIT } from "../../plugins/builtin/sec/forms";
|
|
156
|
+
import { loadParsedInsiderFilings } from "../../plugins/builtin/insider/client";
|
|
157
|
+
import { insiderEmptyNotice } from "../../plugins/builtin/insider/issuer";
|
|
96
158
|
import {
|
|
97
159
|
BENEFICIAL_REPORT_COLUMNS,
|
|
98
160
|
beneficialCoverageNotices,
|
|
99
161
|
beneficialListComplete,
|
|
100
162
|
beneficialListFacts,
|
|
101
163
|
beneficialListUnreadable,
|
|
164
|
+
beneficialReportLegend,
|
|
102
165
|
beneficialRouteForm,
|
|
103
166
|
buildBeneficialReportRows,
|
|
104
167
|
HOLDER_FORMS,
|
|
@@ -109,7 +172,13 @@ import {
|
|
|
109
172
|
const VALID_RANGES = new Set<TimeRange>(TIME_RANGES);
|
|
110
173
|
const VALID_NEWS_FEEDS = new Set<NewsFeed>(["latest", "top", "breaking", "ticker", "sector", "topic"]);
|
|
111
174
|
|
|
112
|
-
type QuoteCliRecord = Omit<QuoteBatchResult, "error"> & {
|
|
175
|
+
type QuoteCliRecord = Omit<QuoteBatchResult, "error"> & {
|
|
176
|
+
error: string | null;
|
|
177
|
+
/** The listing the row is, named from its saved ticker when the quote names nothing. */
|
|
178
|
+
listing?: { name: string | null };
|
|
179
|
+
/** A futures row's contract, terms and latest confirmed settlement, as `ticker` reads them. */
|
|
180
|
+
futuresContract?: FuturesContractTermsPayload;
|
|
181
|
+
};
|
|
113
182
|
type FinancialsCliData = TickerFinancials & {
|
|
114
183
|
symbol: string;
|
|
115
184
|
exchange: string;
|
|
@@ -126,11 +195,9 @@ function parseNewsFeed(value: string | undefined): NewsQuery["feed"] | undefined
|
|
|
126
195
|
return value && VALID_NEWS_FEEDS.has(value as NewsFeed) ? value as NewsQuery["feed"] : undefined;
|
|
127
196
|
}
|
|
128
197
|
|
|
198
|
+
/** The symbols of a command's arguments, one each however they were separated: spaces, commas or pasted lines. */
|
|
129
199
|
function normalizeSymbols(args: string[]): string[] {
|
|
130
|
-
return args
|
|
131
|
-
.flatMap((arg) => arg.split(","))
|
|
132
|
-
.map((symbol) => symbol.trim().toUpperCase())
|
|
133
|
-
.filter(Boolean);
|
|
200
|
+
return splitSymbolArgs(args).map((symbol) => symbol.toUpperCase());
|
|
134
201
|
}
|
|
135
202
|
|
|
136
203
|
/** What JSON metadata says about the listing a command resolved. */
|
|
@@ -138,38 +205,94 @@ function listingMetadata(identity: ListingIdentity) {
|
|
|
138
205
|
return { symbol: identity.symbol, exchange: identity.exchange || null, name: identity.name };
|
|
139
206
|
}
|
|
140
207
|
|
|
208
|
+
// Fitted to a narrow width, the columns marked optional go first (higher dropPriority first), then Name is cut short.
|
|
209
|
+
// What a user types or reads the price by stays whole.
|
|
141
210
|
const QUOTE_LEAD_COLUMNS = [
|
|
142
|
-
{ key: "symbol", header: "Symbol" },
|
|
211
|
+
{ key: "symbol", header: "Symbol", shrink: false },
|
|
143
212
|
{ key: "name", header: "Name" },
|
|
144
213
|
{
|
|
145
214
|
key: "price",
|
|
146
215
|
header: "Last",
|
|
147
216
|
align: "right" as const,
|
|
148
|
-
format: (value: unknown, row:
|
|
149
|
-
row.error && !value
|
|
217
|
+
format: (value: unknown, row: QuoteRow) => (
|
|
218
|
+
row.error && !value
|
|
219
|
+
// A typo is not an outage: say what is wrong with the symbol itself.
|
|
220
|
+
? isNotATickerMessage(row.error) ? cliStyles.warning("not a ticker") : cliStyles.danger("unavailable")
|
|
221
|
+
: yieldLevelCell(value, row)
|
|
150
222
|
),
|
|
151
223
|
},
|
|
152
|
-
{
|
|
153
|
-
|
|
224
|
+
{
|
|
225
|
+
key: "changePercent",
|
|
226
|
+
header: "Chg%",
|
|
227
|
+
align: "right" as const,
|
|
228
|
+
format: (value: unknown, row: QuoteRow) => row.yieldIndex ? yieldChangeCell(row) : formatChangePercentCell(value),
|
|
229
|
+
},
|
|
230
|
+
// Text drops a column no row fills, so a table without an extended print stays as narrow as before.
|
|
231
|
+
extendedColumn("PRE"),
|
|
232
|
+
extendedColumn("POST"),
|
|
233
|
+
{ key: "session", header: "Session", shrink: false },
|
|
154
234
|
];
|
|
155
235
|
|
|
236
|
+
type QuoteRow = ReturnType<typeof quoteRows>[number];
|
|
237
|
+
|
|
238
|
+
/** A Cboe yield index level is a yield: `5.24%`, not a price. */
|
|
239
|
+
function yieldLevelCell(value: unknown, row: QuoteRow): string {
|
|
240
|
+
const text = String(value ?? "");
|
|
241
|
+
return row.yieldIndex && text ? `${text}%` : text;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** A yield index moves in basis points: `+1.3bp`, from the real previous close, not a percent of a percent. */
|
|
245
|
+
function yieldChangeCell(row: QuoteRow): string {
|
|
246
|
+
return row.changeBasisPoints == null ? "" : colorBySign(formatBasisPoints(row.changeBasisPoints / 100, 1), row.changeBasisPoints);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** The pre-market or after-hours print and its move from the regular close, in its own column. */
|
|
250
|
+
function extendedColumn(session: ExtendedSession) {
|
|
251
|
+
return {
|
|
252
|
+
key: session === "PRE" ? "preMarket" : "afterHours",
|
|
253
|
+
header: EXTENDED_SESSION_LABELS[session],
|
|
254
|
+
align: "right" as const,
|
|
255
|
+
value: (row: QuoteRow) => row.extendedSession === session && row.extendedPrice
|
|
256
|
+
? [row.extendedPrice, row.extendedChangePercent == null ? "" : formatPercentRaw(row.extendedChangePercent)].join(" ").trim()
|
|
257
|
+
: "",
|
|
258
|
+
format: (value: unknown, row: QuoteRow) => (
|
|
259
|
+
value ? [row.extendedPrice, formatChangePercentCell(row.extendedChangePercent)].join(" ").trim() : ""
|
|
260
|
+
),
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** A percent to two decimals, as the table prints it. */
|
|
265
|
+
function roundedPercent(value: number | undefined): number | null {
|
|
266
|
+
return value == null ? null : Number(value.toFixed(2));
|
|
267
|
+
}
|
|
268
|
+
|
|
156
269
|
function quoteColumns() {
|
|
157
270
|
return [
|
|
158
271
|
...QUOTE_LEAD_COLUMNS,
|
|
159
|
-
{ key: "currency", header: "Cur" },
|
|
160
|
-
// Whether the price is real-time or
|
|
161
|
-
|
|
162
|
-
{
|
|
272
|
+
{ key: "currency", header: "Cur", shrink: false, optional: true, dropPriority: 1 },
|
|
273
|
+
// Whether the price is real-time, delayed or stale: a feed state, not where it came from. A stale
|
|
274
|
+
// row says so here, so the closing line's "N of M stale" always points at rows the table marks.
|
|
275
|
+
{
|
|
276
|
+
key: "feed",
|
|
277
|
+
header: "Feed",
|
|
278
|
+
shrink: false,
|
|
279
|
+
optional: true,
|
|
280
|
+
dropPriority: 2,
|
|
281
|
+
format: (value: unknown) => value === "stale" ? cliStyles.warning(value) : String(value ?? ""),
|
|
282
|
+
},
|
|
283
|
+
// In a narrow terminal the closing line's as-of stands in for each row's.
|
|
284
|
+
{ key: "updatedAt", header: "Updated", shrink: false, optional: true, dropPriority: 3 },
|
|
163
285
|
];
|
|
164
286
|
}
|
|
165
287
|
|
|
166
288
|
function compareColumns() {
|
|
167
289
|
return [
|
|
168
290
|
...QUOTE_LEAD_COLUMNS,
|
|
169
|
-
{ key: "previousClose", header: "Prev Close", align: "right" as const },
|
|
170
|
-
|
|
171
|
-
{ key: "
|
|
172
|
-
{ key: "
|
|
291
|
+
{ key: "previousClose", header: "Prev Close", align: "right" as const, format: yieldLevelCell },
|
|
292
|
+
// A narrow terminal drops these before it cuts the names short.
|
|
293
|
+
{ key: "dayRange", header: "Day Range", align: "right" as const, optional: true, dropPriority: 1 },
|
|
294
|
+
{ key: "volume", header: "Volume", align: "right" as const, format: formatCountCell, optional: true, dropPriority: 2 },
|
|
295
|
+
{ key: "currency", header: "Cur", shrink: false, optional: true, dropPriority: 3 },
|
|
173
296
|
];
|
|
174
297
|
}
|
|
175
298
|
|
|
@@ -178,18 +301,28 @@ function errorMessage(error: unknown): string | null {
|
|
|
178
301
|
return error instanceof Error ? error.message : String(error);
|
|
179
302
|
}
|
|
180
303
|
|
|
181
|
-
|
|
304
|
+
/** What the Feed cell says: stale wins over the feed's own live or delayed, as the closing line counts it. */
|
|
305
|
+
function quoteFeedState(quote: QuoteCliRecord["quote"], now: number): "stale" | "live" | "delayed" | "" {
|
|
306
|
+
if (!quote) return "";
|
|
307
|
+
if (isQuoteStaleForCurrentSession(quote, now)) return "stale";
|
|
308
|
+
return quote.dataSource === "live" || quote.dataSource === "delayed" ? quote.dataSource : "";
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function quoteRows(results: QuoteCliRecord[], now = Date.now()) {
|
|
182
312
|
return results.map((result) => {
|
|
183
313
|
const quote = result.quote;
|
|
184
|
-
//
|
|
185
|
-
|
|
314
|
+
// Last and Chg% are the regular session, as `ticker` and the quote monitor read it; a
|
|
315
|
+
// pre-market or after-hours print is its own column, measured from that session's close.
|
|
316
|
+
const display = getRegularSessionDisplay(quote);
|
|
317
|
+
const extended = getExtendedSessionDisplay(quote);
|
|
318
|
+
const yieldIndex = isYieldIndexSymbol(result.target.symbol);
|
|
186
319
|
// An index level is in points, not in the currency its members trade in.
|
|
187
320
|
const indexPoints = quote?.instrumentType?.trim().toUpperCase() === "INDEX";
|
|
188
321
|
// Pad to two decimals so a column lines up, but never past the currency's minor unit (JPY has none).
|
|
189
322
|
// Points have no minor unit, so a yen-listed index still pads to two.
|
|
190
323
|
const options = {
|
|
191
324
|
...quoteFormatOptions(quote),
|
|
192
|
-
minimumFractionDigits: indexPoints ? 2 :
|
|
325
|
+
minimumFractionDigits: indexPoints ? 2 : currencyPriceMinDigits(quote?.currency),
|
|
193
326
|
};
|
|
194
327
|
const price = (value: number | undefined) => (
|
|
195
328
|
quote && value != null
|
|
@@ -198,15 +331,23 @@ function quoteRows(results: QuoteCliRecord[]) {
|
|
|
198
331
|
);
|
|
199
332
|
return {
|
|
200
333
|
symbol: result.target.symbol,
|
|
201
|
-
name: quote?.name
|
|
334
|
+
name: quote?.name || result.listing?.name || "",
|
|
202
335
|
price: price(display?.price),
|
|
203
336
|
rawPrice: display?.price ?? null,
|
|
204
337
|
priceBasis: quote?.priceBasis ?? null,
|
|
205
338
|
instrumentType: quote?.instrumentType ?? null,
|
|
206
339
|
change: display?.change ?? null,
|
|
207
|
-
changePercent: display?.changePercent
|
|
340
|
+
changePercent: roundedPercent(display?.changePercent),
|
|
341
|
+
// A Cboe yield index is read as a yield in percent and moves in basis points.
|
|
342
|
+
yieldIndex,
|
|
343
|
+
changeBasisPoints: yieldIndex && display?.change != null ? toBasisPoints(display.change, 1) : null,
|
|
344
|
+
extendedSession: extended?.session ?? null,
|
|
345
|
+
extendedPrice: price(extended?.price),
|
|
346
|
+
rawExtendedPrice: extended?.price ?? null,
|
|
347
|
+
extendedChange: extended?.change ?? null,
|
|
348
|
+
extendedChangePercent: roundedPercent(extended?.changePercent),
|
|
208
349
|
session: quote?.marketState ? marketStateLabel(quote.marketState) : "",
|
|
209
|
-
// The close the shown move is measured from
|
|
350
|
+
// The close the shown move is measured from: the one before the session Last is.
|
|
210
351
|
previousClose: price(display?.change != null ? display.price - display.change : quote?.previousClose),
|
|
211
352
|
dayRange: quote?.low != null && quote.high != null
|
|
212
353
|
? indexPoints ? `${price(quote.low)}-${price(quote.high)}` : formatPriceRange(quote.low, quote.high, quote.currency, options, "-")
|
|
@@ -215,6 +356,7 @@ function quoteRows(results: QuoteCliRecord[]) {
|
|
|
215
356
|
currency: quote?.currency ?? "",
|
|
216
357
|
providerId: quote?.providerId ?? "",
|
|
217
358
|
source: quote?.dataSource ?? quote?.providerId ?? "",
|
|
359
|
+
feed: quoteFeedState(quote, now),
|
|
218
360
|
updatedAt: quote?.lastUpdated ? new Date(quote.lastUpdated).toISOString() : "",
|
|
219
361
|
error: result.error ?? "",
|
|
220
362
|
};
|
|
@@ -259,22 +401,6 @@ function filingRows(filings: SecFilingItem[]) {
|
|
|
259
401
|
}));
|
|
260
402
|
}
|
|
261
403
|
|
|
262
|
-
function holderRows(data: HolderData, ownerTypes?: Set<string>) {
|
|
263
|
-
const holders = ownerTypes
|
|
264
|
-
? data.holders.filter((holder) => ownerTypes.has(holder.ownerType))
|
|
265
|
-
: data.holders;
|
|
266
|
-
return holders.map((holder) => ({
|
|
267
|
-
type: holder.ownerType,
|
|
268
|
-
name: holder.name,
|
|
269
|
-
reportDate: holder.reportDate ?? "",
|
|
270
|
-
shares: holder.shares ?? null,
|
|
271
|
-
value: holder.value ?? null,
|
|
272
|
-
percentHeld: holder.percentHeld ?? null,
|
|
273
|
-
changeShares: holder.changeShares ?? null,
|
|
274
|
-
shareBasis: holder.shareBasis ?? null,
|
|
275
|
-
}));
|
|
276
|
-
}
|
|
277
|
-
|
|
278
404
|
function analystSummary(data: AnalystResearchData): string {
|
|
279
405
|
const target = data.priceTarget;
|
|
280
406
|
const currency = analystTargetCurrency(data);
|
|
@@ -345,22 +471,6 @@ function corporateActionRows(data: CorporateActionsData) {
|
|
|
345
471
|
].sort((left, right) => right.date.localeCompare(left.date));
|
|
346
472
|
}
|
|
347
473
|
|
|
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
474
|
/**
|
|
365
475
|
* After a US open, a chain whose latest trade predates it still carries the
|
|
366
476
|
* prior session's quotes and volume. The delayed feed lags the open by about
|
|
@@ -375,11 +485,6 @@ function priorSessionChainWarning(chain: OptionsChain, exchange: string, now: nu
|
|
|
375
485
|
return `No option trades this session yet (last trade ${chain.asOf})`;
|
|
376
486
|
}
|
|
377
487
|
|
|
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
488
|
/** Per-share earnings to the cent, as reported; consensus averages carry more digits. */
|
|
384
489
|
function formatEpsCell(value: unknown): string {
|
|
385
490
|
return typeof value === "number" && Number.isFinite(value) ? value.toFixed(2) : "";
|
|
@@ -390,6 +495,8 @@ function earningsRows(events: EarningsEvent[]) {
|
|
|
390
495
|
symbol: event.symbol,
|
|
391
496
|
name: event.name,
|
|
392
497
|
date: isoDate(event.earningsDate).slice(0, 10),
|
|
498
|
+
// Whether the company announced the date or it is projected from past reports.
|
|
499
|
+
status: earningsDateLabel(event),
|
|
393
500
|
timing: event.timing,
|
|
394
501
|
epsEstimate: event.epsEstimate,
|
|
395
502
|
epsActual: event.epsActual,
|
|
@@ -398,6 +505,9 @@ function earningsRows(events: EarningsEvent[]) {
|
|
|
398
505
|
}));
|
|
399
506
|
}
|
|
400
507
|
|
|
508
|
+
/** Symbols at most whose venues a command looks up to name the company each bare one went to. */
|
|
509
|
+
const MAX_CHOICE_LOOKUPS = 10;
|
|
510
|
+
|
|
401
511
|
async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1], commandName: "quote" | "compare") {
|
|
402
512
|
const args = [...rawArgs];
|
|
403
513
|
const exchange = takeOption(args, "--exchange") ?? "";
|
|
@@ -410,33 +520,151 @@ async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execut
|
|
|
410
520
|
symbol, exchange, market, ctx, { ownExchangeWins: symbols.length > 1 },
|
|
411
521
|
)));
|
|
412
522
|
const targets: QuoteSubscriptionTarget[] = listings.map((listing) => ({ symbol: listing.request.symbol, exchange: listing.request.exchange }));
|
|
523
|
+
// A bare symbol's venues load beside its quote, to name the company it was read as when several list it.
|
|
524
|
+
// Each costs a search, so a long list (a portfolio check, not a lookup) names none.
|
|
525
|
+
const venues = new Map(listings.length > MAX_CHOICE_LOOKUPS ? []
|
|
526
|
+
: listings.map((listing) => [listing, loadBareVenues(listing, market)] as const));
|
|
413
527
|
const results = await market.dataProvider.getQuotesBatch(targets, { forceRefresh: ctx.cliOptions.refresh });
|
|
414
528
|
const listingOf = (result: QuoteBatchResult, index: number) => listings[targets.indexOf(result.target)] ?? listings[index]!;
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
529
|
+
const request = { command: commandName, noun: "quote" };
|
|
530
|
+
const answers = await Promise.all(results.map((result, index) => {
|
|
531
|
+
const listing = listingOf(result, index);
|
|
532
|
+
return answerQuote(result, listing, market, request, ctx.cliOptions.refresh, venues.get(listing) ?? null);
|
|
533
|
+
}));
|
|
534
|
+
const failed = answers[0]?.failure;
|
|
535
|
+
if (listings.length === 1 && failed) ctx.fail(failed.message, failed.details);
|
|
536
|
+
// A futures quote adds its dated settlement and contract terms, as `ticker` prints them; `compare` stays a table.
|
|
537
|
+
const contracts = await Promise.all(answers.map(({ result }) => commandName === "quote" && result.quote
|
|
538
|
+
? cliFuturesContract(result.quote.symbol || result.target.symbol, result.quote.instrumentType)
|
|
539
|
+
: null));
|
|
420
540
|
// Each row names its listing by key (SAN:EPA) and company, so the table needs no line above it.
|
|
421
|
-
const data =
|
|
541
|
+
const data = answers.map(({ result, listing, failure }, index) => ({
|
|
422
542
|
target: result.target,
|
|
423
|
-
listing: listingMetadata(listingIdentity(
|
|
543
|
+
listing: listingMetadata(listingIdentity(listing, result.quote)),
|
|
544
|
+
// The figures the table shows, beside the quote as the source sent it.
|
|
545
|
+
...getQuoteSessionFields(result.quote),
|
|
424
546
|
quote: result.quote,
|
|
425
|
-
|
|
547
|
+
...contracts[index] ? { futuresContract: contracts[index] } : {},
|
|
548
|
+
error: failure?.oneLine ?? errorMessage(result.error),
|
|
549
|
+
...yieldIndexFields(result),
|
|
426
550
|
}));
|
|
427
|
-
|
|
428
|
-
const notes =
|
|
551
|
+
const now = Date.now();
|
|
552
|
+
const notes = [
|
|
553
|
+
// Which listing a bare symbol was read as, and the index a bare root is not, are in no row.
|
|
554
|
+
...answers.flatMap((answer) => answer.notes),
|
|
555
|
+
// A row that stopped trading long ago reads as a quote that is merely stale.
|
|
556
|
+
...data.flatMap((row) => row.quote
|
|
557
|
+
? noLongerTradingNote(`${row.target.symbol}${row.listing.name ? ` (${row.listing.name})` : ""}`, row.quote, now) ?? []
|
|
558
|
+
: []),
|
|
559
|
+
// Text mode shows only "unavailable" in the cell; the JSON rows already carry each reason.
|
|
560
|
+
...ctx.cliOptions.format === "text" ? quoteNotes(data, { exchange }) : [],
|
|
561
|
+
];
|
|
562
|
+
const columns = commandName === "compare" ? compareColumns() : quoteColumns();
|
|
563
|
+
// Only yield indices: their change is in bp, so the text header drops the percent sign (CSV keeps the percent it holds).
|
|
564
|
+
const yieldsOnly = data.length > 0 && data.every((row) => isYieldIndexSymbol(row.target.symbol));
|
|
429
565
|
ctx.printResult({
|
|
430
566
|
data,
|
|
431
567
|
warnings: notes.length > 0 ? notes : undefined,
|
|
432
568
|
freshness: quotesFreshness(data.map((row) => row.quote)),
|
|
433
569
|
}, {
|
|
434
570
|
rows: quoteRows,
|
|
435
|
-
|
|
571
|
+
details: quoteFuturesContractText,
|
|
572
|
+
columns,
|
|
573
|
+
...(yieldsOnly ? { textColumns: columns.map((column) => column.key === "changePercent" ? { ...column, header: "Chg" } : column) } : {}),
|
|
436
574
|
});
|
|
437
575
|
});
|
|
438
576
|
}
|
|
439
577
|
|
|
578
|
+
/**
|
|
579
|
+
* Under the table, each futures row's Settle line and Contract block, worded
|
|
580
|
+
* as `ticker` words them. Several symbols name theirs in the block's title.
|
|
581
|
+
*/
|
|
582
|
+
function quoteFuturesContractText(data: QuoteCliRecord[]): string {
|
|
583
|
+
return data.flatMap((row) => {
|
|
584
|
+
const terms = row.futuresContract;
|
|
585
|
+
if (!terms || !row.quote) return [];
|
|
586
|
+
const settle: [string, string] = ["Settle", futuresSettleText(terms, row.quote)];
|
|
587
|
+
const contract = futuresContractMetrics(terms, row.quote.symbol || row.target.symbol);
|
|
588
|
+
if (data.length > 1) return [`${renderSection(`${row.target.symbol} Contract`)}\n${renderStats([settle, ...contract])}`];
|
|
589
|
+
return [[renderStats([settle]), ...contract.length ? ["", renderSection("Contract"), renderStats(contract)] : []].join("\n")];
|
|
590
|
+
}).join("\n\n");
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* What a Cboe yield index adds to its JSON row: the level is a yield in percent
|
|
595
|
+
* and its change since the previous close is in basis points (the quote's own
|
|
596
|
+
* `change` is the same move in percentage points).
|
|
597
|
+
*/
|
|
598
|
+
function yieldIndexFields(result: QuoteBatchResult): { unit?: "percent"; changeBasisPoints?: number } {
|
|
599
|
+
if (!isYieldIndexSymbol(result.target.symbol)) return {};
|
|
600
|
+
const change = getRegularSessionDisplay(result.quote)?.change;
|
|
601
|
+
return { unit: "percent", ...(change == null ? {} : { changeBasisPoints: toBasisPoints(change, 1) }) };
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
interface QuoteAnswer {
|
|
605
|
+
result: QuoteBatchResult;
|
|
606
|
+
/** The listing the row is: the one asked for, or the venue a bare symbol was quoted on. */
|
|
607
|
+
listing: CliListing;
|
|
608
|
+
/** Why there is no quote, when there is more to say than the source's own error. */
|
|
609
|
+
failure?: { message: string; details?: string; oneLine: string };
|
|
610
|
+
notes: string[];
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* One symbol's row of `quote` and `compare`. A listing on an exchange its symbol
|
|
615
|
+
* is not listed on says so. A bare symbol the data service read on its home
|
|
616
|
+
* listing abroad names that venue (SXR8 -> XETRA). One no source quotes is
|
|
617
|
+
* quoted on its listings (`quoteBareListing`), else names them, or asks for an
|
|
618
|
+
* exchange when several companies list it (2222); a bare index root (VIX) also
|
|
619
|
+
* names its index (^VIX), and a futures board code a security keeps (GC) its
|
|
620
|
+
* future (GC=F). Venues are only looked up for a
|
|
621
|
+
* symbol with no quote, so a request that succeeds costs no search.
|
|
622
|
+
*/
|
|
623
|
+
async function answerQuote(
|
|
624
|
+
result: QuoteBatchResult,
|
|
625
|
+
listing: CliListing,
|
|
626
|
+
market: MarketContext,
|
|
627
|
+
request: ListingDataRequest,
|
|
628
|
+
refresh: boolean,
|
|
629
|
+
venues: Promise<ListingVenue[]> | null,
|
|
630
|
+
): Promise<QuoteAnswer> {
|
|
631
|
+
const root = listing.exchange ? null : indexRootFor(listing.symbol);
|
|
632
|
+
// A bare root that quoted something else, such as MOVE (Corvex), points at the index too.
|
|
633
|
+
const also = (quote: Quote) => root && quote.instrumentType?.trim().toUpperCase() !== "INDEX"
|
|
634
|
+
? [indexRootAlsoLine(root, request.command)] : [];
|
|
635
|
+
if (result.quote) {
|
|
636
|
+
// "CAR = Avis Budget Group, NASDAQ (also XETRA, ASX)" names the venue too, so it replaces "SXR8 -> XETRA".
|
|
637
|
+
const choice = venues ? await listingChoiceNotes(listing, market, result.quote, venues) : listingReadNotes(listing);
|
|
638
|
+
const venue = choice.length > 0 ? null : bareListingNote(listing, result.quote);
|
|
639
|
+
return { result, listing, notes: [...choice, venue ?? [], also(result.quote)].flat() };
|
|
640
|
+
}
|
|
641
|
+
const notTraded = await notTradedMessage(listing, market);
|
|
642
|
+
if (notTraded) return { result, listing, failure: { message: notTraded, oneLine: notTraded }, notes: [] };
|
|
643
|
+
if (!isNoProviderError(result.error)) return { result, listing, notes: [] };
|
|
644
|
+
const other = await quoteBareListing(listing, market, request, { refresh });
|
|
645
|
+
if (other?.kind === "quoted") {
|
|
646
|
+
return {
|
|
647
|
+
result: { target: other.listing.request, quote: other.quote },
|
|
648
|
+
listing: other.listing,
|
|
649
|
+
notes: [other.note, ...also(other.quote)],
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
// Other listings already name a futures board code's future (GC=F); with none, it is named here.
|
|
653
|
+
const tryLines = [
|
|
654
|
+
root ? indexRootTryLine(root, request.command) : null,
|
|
655
|
+
other || listing.exchange ? null : futuresTryLine(listing.symbol, request),
|
|
656
|
+
].filter((line): line is string => !!line);
|
|
657
|
+
if (!other && tryLines.length === 0) return { result, listing, notes: [] };
|
|
658
|
+
const message = other?.message ?? `${listing.symbol} has no ${request.noun}.`;
|
|
659
|
+
const details = [other?.details, ...tryLines].filter(Boolean).join("\n");
|
|
660
|
+
const oneLine = [
|
|
661
|
+
message,
|
|
662
|
+
other ? `${other.heading}: ${other.listings.join(", ")}.` : "",
|
|
663
|
+
...tryLines,
|
|
664
|
+
].filter(Boolean).join(" ");
|
|
665
|
+
return { result, listing, failure: { message, details, oneLine }, notes: [] };
|
|
666
|
+
}
|
|
667
|
+
|
|
440
668
|
/** A crypto pair that will not load points at the crypto board. Structured formats keep the error as thrown. */
|
|
441
669
|
function failHistory(error: unknown, symbol: string, ctx: Parameters<CliCommandDef["execute"]>[1]): never {
|
|
442
670
|
if (ctx.cliOptions.format !== "text" || !isCryptoPairSymbol(symbol)) throw error;
|
|
@@ -455,20 +683,23 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
455
683
|
const raw = requireOneArg(args, HISTORY_USAGE, "symbol", ctx);
|
|
456
684
|
await withMarketData(ctx, async (market) => {
|
|
457
685
|
const listing = await requireCliListing(raw, requestedExchange, market, ctx);
|
|
686
|
+
const venues = loadBareVenues(listing, market);
|
|
458
687
|
const { symbol, exchange } = listing.request;
|
|
459
688
|
const context = { cacheMode: ctx.cliOptions.refresh ? "refresh" as const : "default" as const };
|
|
460
689
|
const load = () => market.dataProvider.getPriceHistoryWithMetadata
|
|
461
690
|
? market.dataProvider.getPriceHistoryWithMetadata(symbol, exchange, range, context)
|
|
462
691
|
: market.dataProvider.getPriceHistory(symbol, exchange, range, context).then((points) => ({ points, resolution: null }));
|
|
463
692
|
const [{ points, resolution }, quote] = await Promise.all([
|
|
464
|
-
loadForListing(listing, market, ctx, load, (loaded) => loaded.points.length === 0
|
|
693
|
+
loadForListing(listing, market, ctx, load, (loaded) => loaded.points.length === 0, {
|
|
694
|
+
command: range === "1Y" ? "history" : `history --range ${range}`, noun: "history",
|
|
695
|
+
})
|
|
465
696
|
.catch((error) => failHistory(error, listing.key, ctx)),
|
|
466
697
|
loadListingQuote(market.dataProvider, listing),
|
|
467
698
|
]);
|
|
468
699
|
const identity = listingIdentity(listing, quote);
|
|
469
700
|
// Without a quote currency, the listing metadata research reads (as `ticker` does) may still state it.
|
|
470
701
|
const listed = quote?.currency ? quote : await market.dataProvider.getQuoteMetadata?.(symbol, exchange).catch(() => null);
|
|
471
|
-
const unit = historyUnit(listing.
|
|
702
|
+
const unit = historyUnit(listing.key, listed ?? quote);
|
|
472
703
|
const data = historyRows(points, resolution, unit.currency);
|
|
473
704
|
const interval = data[0]?.interval ?? null;
|
|
474
705
|
const decimals = historyPriceDecimals(data, listing.saved?.metadata.assetCategory);
|
|
@@ -480,8 +711,16 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
480
711
|
...barHistoryFreshness(intraday ? null : barResolutionFromDates(data.map((row) => row.date))),
|
|
481
712
|
observedKey: "date",
|
|
482
713
|
oldest: null,
|
|
714
|
+
// Bars stop arriving on the listing's own trading days, not New York's.
|
|
715
|
+
cadenceExchange: quote?.listingExchangeName || exchange || undefined,
|
|
483
716
|
});
|
|
484
|
-
const
|
|
717
|
+
const gaps = historyGapNote(data, {
|
|
718
|
+
listing: listing.key, exchange: quote?.listingExchangeName || exchange || undefined, instrumentType: quote?.instrumentType,
|
|
719
|
+
});
|
|
720
|
+
const notes = [
|
|
721
|
+
...await listingChoiceNotes(listing, market, quote, venues),
|
|
722
|
+
...historyNotes(range, interval, data[0]?.date), gaps ?? [], historyFlagNote(data) ?? [],
|
|
723
|
+
].flat();
|
|
485
724
|
const priceColumns: CliResultColumn[] = [
|
|
486
725
|
intraday
|
|
487
726
|
? { key: "date", header: "Time", format: (value) => typeof value === "string" ? formatUtcTime(value) : "" }
|
|
@@ -515,6 +754,7 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
515
754
|
freshness,
|
|
516
755
|
}, {
|
|
517
756
|
heading: `${listingHeading(identity)}\n${cliStyles.muted(historyFacts(unit, data).join(" · "))}`,
|
|
757
|
+
dateKey: "date",
|
|
518
758
|
textColumns: [...priceColumns, flagColumn],
|
|
519
759
|
// Every exported row says its currency and bar size, so it survives head and concatenation.
|
|
520
760
|
columns: [
|
|
@@ -558,10 +798,12 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
558
798
|
};
|
|
559
799
|
await withMarketData(ctx, async (market) => {
|
|
560
800
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
801
|
+
const venues = loadBareVenues(listing, market);
|
|
561
802
|
const financials = await loadForListing(listing, market, ctx, () => market.dataProvider.getTickerFinancials(
|
|
562
803
|
listing.request.symbol, listing.request.exchange, { cacheMode: ctx.cliOptions.refresh ? "refresh" : "default" },
|
|
563
|
-
));
|
|
804
|
+
), undefined, { command: commandName, noun: commandName === "valuation" ? "valuation data" : commandName });
|
|
564
805
|
const identity = listingIdentity(listing, financials.quote);
|
|
806
|
+
const choice = await listingChoiceNotes(listing, market, financials.quote, venues);
|
|
565
807
|
const data: FinancialsCliData = {
|
|
566
808
|
symbol: listing.key,
|
|
567
809
|
exchange: identity.exchange,
|
|
@@ -574,6 +816,7 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
574
816
|
// JSON reads a multiple over a loss as null with its reason, never as a number.
|
|
575
817
|
data: { ...data, fundamentals: exportedFundamentals(data.fundamentals) },
|
|
576
818
|
freshness,
|
|
819
|
+
...(choice.length > 0 ? { warnings: choice } : {}),
|
|
577
820
|
}, {
|
|
578
821
|
text: () => renderFundamentalsReport(data, view),
|
|
579
822
|
...(tabular ? { tables: selectTables(fundamentalsReportTables(data, view, freshness)) } : {}),
|
|
@@ -602,6 +845,7 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
602
845
|
profile: financials.profile,
|
|
603
846
|
},
|
|
604
847
|
freshness,
|
|
848
|
+
...(choice.length > 0 ? { warnings: choice } : {}),
|
|
605
849
|
}, {
|
|
606
850
|
heading: listingHeading(identity),
|
|
607
851
|
rows: () => rows,
|
|
@@ -611,15 +855,55 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
611
855
|
});
|
|
612
856
|
}
|
|
613
857
|
|
|
858
|
+
const NEWS_COLUMNS = [
|
|
859
|
+
// UTC with the zone named, as every CLI time prints, rather than the host zone unlabeled.
|
|
860
|
+
{
|
|
861
|
+
key: "publishedAt",
|
|
862
|
+
header: "Published",
|
|
863
|
+
format: (value: unknown) => typeof value === "string" ? formatUtcTime(value) : "",
|
|
864
|
+
},
|
|
865
|
+
{ key: "source", header: "Source", maxWidth: 20 },
|
|
866
|
+
{ key: "title", header: "Title" },
|
|
867
|
+
{ key: "tickers", header: "Tickers", maxWidth: 16 },
|
|
868
|
+
{ key: "url", header: "URL", optional: true },
|
|
869
|
+
];
|
|
870
|
+
|
|
871
|
+
/** Stories matching text, printed as a symbol's news is, under a line saying what they match. */
|
|
872
|
+
async function printNewsSearch(
|
|
873
|
+
text: string,
|
|
874
|
+
articles: NewsArticle[],
|
|
875
|
+
{ reason, empty }: { reason?: string; empty?: string },
|
|
876
|
+
ctx: Parameters<CliCommandDef["execute"]>[1],
|
|
877
|
+
) {
|
|
878
|
+
ctx.printResult({
|
|
879
|
+
data: articles,
|
|
880
|
+
metadata: { ticker: null, search: text, feed: null },
|
|
881
|
+
freshness: rowsFreshness(newsRows(articles), cloudNewsFreshness(await cloudRealtimeAccess())),
|
|
882
|
+
}, {
|
|
883
|
+
heading: newsSearchHeading(text, reason),
|
|
884
|
+
rows: newsRows,
|
|
885
|
+
columns: NEWS_COLUMNS,
|
|
886
|
+
empty: empty ?? `No stories match "${text}".`,
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
|
|
614
890
|
async function runNews(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
615
891
|
const args = [...rawArgs];
|
|
616
892
|
const feed = parseNewsFeed(takeOption(args, "--feed"));
|
|
617
893
|
const exchangeOption = takeOption(args, "--exchange");
|
|
894
|
+
// Stories come newest first, so the newest n are the first n either way.
|
|
895
|
+
const limit = ctx.cliOptions.tail ?? ctx.cliOptions.limit ?? 20;
|
|
896
|
+
const text = newsSearchText(args);
|
|
897
|
+
if (text != null) {
|
|
898
|
+
if (exchangeOption || feed) ctx.fail(`${exchangeOption ? "--exchange" : "--feed"} goes with a symbol, not with text to search stories for.`, "Usage: gloomberb news <query>");
|
|
899
|
+
const problem = newsSearchTextProblem(text);
|
|
900
|
+
if (problem) ctx.fail(problem);
|
|
901
|
+
await printNewsSearch(text, await searchNewsStories(text, limit), {}, ctx);
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
618
904
|
if (exchangeOption && !args[0]) ctx.fail("--exchange needs a symbol: gloomberb news <symbol> --exchange <code>");
|
|
619
|
-
rejectExtraArgs(args, 1, { usage: "news [symbol] [--feed <feed>]", takes: "one symbol at most", advice: "Run it once per symbol." }, ctx);
|
|
620
905
|
await withMarketData(ctx, async (market) => {
|
|
621
906
|
const listing = args[0] ? await requireCliListing(args[0], exchangeOption, market, ctx) : null;
|
|
622
|
-
const limit = ctx.cliOptions.limit ?? 20;
|
|
623
907
|
const loadNews = () => market.dataProvider.getNews({
|
|
624
908
|
feed: feed ?? (listing ? "ticker" : "latest"),
|
|
625
909
|
// Still set for news plugins that read the deprecated scope.
|
|
@@ -633,27 +917,33 @@ async function runNews(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
633
917
|
listing ? loadListingQuote(market.dataProvider, listing) : null,
|
|
634
918
|
]);
|
|
635
919
|
const identity = listing ? listingIdentity(listing, quote) : null;
|
|
920
|
+
const resolved = quote !== null || identity?.name != null;
|
|
921
|
+
// A bare word no listing answers to is text, and so is a futures board code (TTF), which resolves
|
|
922
|
+
// to its future: news is filed under company tickers, so the stories that mention the word are the news.
|
|
923
|
+
const word = listing && !listing.exchange && !feed && articles.length === 0 ? newsFallbackText(args[0]!) : null;
|
|
924
|
+
const futuresCode = !!word && !!bareFuturesCode(word) && isFuturesInstrument(listing!.symbol, quote?.instrumentType);
|
|
925
|
+
const reason = !resolved ? "no ticker by that name" : futuresCode ? "a futures code, not a ticker" : null;
|
|
926
|
+
const emptyMessage = (searched: boolean) => listing && identity
|
|
927
|
+
? newsEmptyMessage(searched && word ? word : listing.symbol, listingTitle(identity), quote, resolved, searched)
|
|
928
|
+
: undefined;
|
|
929
|
+
if (word && reason) {
|
|
930
|
+
// The lookup that found no ticker already answered; a search that fails leaves that answer.
|
|
931
|
+
const found = await searchNewsStories(word, limit).catch(() => []);
|
|
932
|
+
await printNewsSearch(word, found, { reason, empty: emptyMessage(true) }, ctx);
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
const empty = emptyMessage(false);
|
|
936
|
+
const read = listing ? listingReadNotes(listing) : [];
|
|
636
937
|
ctx.printResult({
|
|
637
938
|
data: articles,
|
|
939
|
+
...(read.length > 0 ? { warnings: read } : {}),
|
|
638
940
|
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
|
-
}),
|
|
941
|
+
freshness: rowsFreshness(newsRows(articles), cloudNewsFreshness(await cloudRealtimeAccess())),
|
|
642
942
|
}, {
|
|
643
943
|
...(identity ? { heading: listingHeading(identity) } : {}),
|
|
644
944
|
rows: newsRows,
|
|
645
|
-
columns:
|
|
646
|
-
|
|
647
|
-
{
|
|
648
|
-
key: "publishedAt",
|
|
649
|
-
header: "Published",
|
|
650
|
-
format: (value) => typeof value === "string" ? formatUtcTime(value) : "",
|
|
651
|
-
},
|
|
652
|
-
{ key: "source", header: "Source", maxWidth: 20 },
|
|
653
|
-
{ key: "title", header: "Title" },
|
|
654
|
-
{ key: "tickers", header: "Tickers", maxWidth: 16 },
|
|
655
|
-
{ key: "url", header: "URL", optional: true },
|
|
656
|
-
],
|
|
945
|
+
columns: NEWS_COLUMNS,
|
|
946
|
+
...(empty ? { empty } : {}),
|
|
657
947
|
});
|
|
658
948
|
});
|
|
659
949
|
}
|
|
@@ -674,12 +964,14 @@ async function registrantUsExchange(market: MarketContext, symbol: string, regis
|
|
|
674
964
|
|
|
675
965
|
async function runFilings(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
676
966
|
const args = [...rawArgs];
|
|
677
|
-
|
|
967
|
+
// Filings come newest first, so --tail asks for the same first n as --limit.
|
|
968
|
+
const count = parsePositiveInt(takeOption(args, "--count"), ctx.cliOptions.tail ?? ctx.cliOptions.limit ?? 15, "Count", ctx);
|
|
678
969
|
const exchangeOption = takeOption(args, "--exchange");
|
|
679
970
|
const form = takeOption(args, "--form")?.trim() || null;
|
|
680
971
|
const raw = requireOneArg(args, "filings <symbol> [--count <n>] [--form <form>]", "symbol", ctx);
|
|
681
972
|
await withMarketData(ctx, async (market) => {
|
|
682
973
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
974
|
+
const venues = loadBareVenues(listing, market);
|
|
683
975
|
const { symbol, exchange } = listing.request;
|
|
684
976
|
// A listing outside the US sends its company to the lookup, which needs its quote first.
|
|
685
977
|
const quotePromise = loadListingQuote(market.dataProvider, listing);
|
|
@@ -696,15 +988,20 @@ async function runFilings(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
696
988
|
quotePromise,
|
|
697
989
|
]);
|
|
698
990
|
const resolved = identity ?? listingIdentity(listing, quote);
|
|
991
|
+
const choice = await listingChoiceNotes(listing, market, quote, venues);
|
|
699
992
|
ctx.printResult({
|
|
700
993
|
data: filings,
|
|
701
994
|
metadata: { ...listingMetadata(resolved), ...(form ? { form } : {}) },
|
|
702
995
|
freshness: rowsFreshness(filingRows(filings), { ...SEC_FILINGS, observedKey: "filingDate" }),
|
|
996
|
+
...(choice.length > 0 ? { warnings: choice } : {}),
|
|
703
997
|
}, {
|
|
704
998
|
heading: listingHeading(resolved),
|
|
705
999
|
rows: filingRows,
|
|
706
1000
|
columns: FILING_COLUMNS,
|
|
707
|
-
|
|
1001
|
+
// A listing abroad is looked up by its company: none found means the company files nothing with the SEC.
|
|
1002
|
+
empty: form ? `No ${form} filings found for ${listingTitle(resolved)}.`
|
|
1003
|
+
: identity ? noSecReportsNotice(resolved)
|
|
1004
|
+
: `No SEC filings found for ${listingTitle(resolved)}.`,
|
|
708
1005
|
});
|
|
709
1006
|
} catch (error) {
|
|
710
1007
|
if (!(error instanceof SecRegistrantMismatchError)) throw error;
|
|
@@ -722,17 +1019,50 @@ async function runFilings(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
722
1019
|
});
|
|
723
1020
|
}
|
|
724
1021
|
|
|
1022
|
+
type Form4Insiders = "available" | "none" | "foreign-private-issuer" | "no-sec-reports";
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* Whether the company has Form 4 filings of its own, read as `fn INS` reads
|
|
1026
|
+
* them; null when they could not be read. A listing abroad the SEC lists
|
|
1027
|
+
* nothing for files no SEC reports at all.
|
|
1028
|
+
*/
|
|
1029
|
+
async function loadForm4Insiders(market: MarketContext, listing: CliListing): Promise<Form4Insiders | null> {
|
|
1030
|
+
try {
|
|
1031
|
+
const { parsed, foreignPrivateIssuer, noFilings } = await loadParsedInsiderFilings(market.dataProvider, listing.request.symbol, {
|
|
1032
|
+
limit: 1,
|
|
1033
|
+
exchange: listing.request.exchange,
|
|
1034
|
+
});
|
|
1035
|
+
if (parsed.length > 0) return "available";
|
|
1036
|
+
if (noFilings && nonUsSecListingVenue(listing.request.symbol, listing.request.exchange)) return "no-sec-reports";
|
|
1037
|
+
return foreignPrivateIssuer ? "foreign-private-issuer" : "none";
|
|
1038
|
+
} catch {
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
725
1043
|
/**
|
|
726
1044
|
* Holder lists name institutions and funds; individual insiders rarely appear.
|
|
727
|
-
* The insider share of the company is reported either way, so say it.
|
|
1045
|
+
* The insider share of the company is reported either way, so say it. The
|
|
1046
|
+
* Form 4 trades are pointed to only when the company has some; otherwise the
|
|
1047
|
+
* line says why there are none, in the words `fn INS` uses.
|
|
728
1048
|
*/
|
|
729
|
-
function insiderSummary(
|
|
1049
|
+
function insiderSummary(
|
|
1050
|
+
data: HolderData,
|
|
1051
|
+
ownerTypes: Set<string>,
|
|
1052
|
+
listing: Pick<CliListing, "key">,
|
|
1053
|
+
identity: ListingIdentity,
|
|
1054
|
+
form4: Form4Insiders | null,
|
|
1055
|
+
): string {
|
|
730
1056
|
const held = data.summary?.insidersPercentHeld;
|
|
731
1057
|
const share = held == null || !Number.isFinite(held)
|
|
732
1058
|
? ""
|
|
733
1059
|
: `Insiders hold ${formatFractionPercentCell(held)} of shares outstanding.`;
|
|
734
1060
|
const listed = data.holders.some((holder) => ownerTypes.has(holder.ownerType));
|
|
735
|
-
|
|
1061
|
+
// The listing as the command read it (CAR:ASX), not the source's spelling of it (CAR.AX), which fn does not take.
|
|
1062
|
+
const transactions = form4 === "available"
|
|
1063
|
+
? listed ? "" : `Form 4 transactions: gloomberb fn INS ${listing.key}`
|
|
1064
|
+
: form4 === "no-sec-reports" ? noSecReportsNotice(identity)
|
|
1065
|
+
: form4 ? insiderEmptyNotice(form4 === "foreign-private-issuer") : "";
|
|
736
1066
|
return [share, transactions].filter(Boolean).join("\n");
|
|
737
1067
|
}
|
|
738
1068
|
|
|
@@ -745,14 +1075,22 @@ async function printBeneficialOwners(
|
|
|
745
1075
|
ctx: Parameters<CliCommandDef["execute"]>[1],
|
|
746
1076
|
) {
|
|
747
1077
|
const { symbol, exchange } = listing.request;
|
|
748
|
-
|
|
749
|
-
|
|
1078
|
+
// A listing outside the US sends its company to the lookup, which needs its quote first.
|
|
1079
|
+
const quotePromise = loadListingQuote(market.dataProvider, listing);
|
|
1080
|
+
const abroad = nonUsSecListingVenue(symbol, exchange) ? listingIdentity(listing, await quotePromise) : null;
|
|
1081
|
+
const request = { form: beneficialRouteForm(form), history, listing: { exchange, name: abroad?.name ?? undefined } };
|
|
1082
|
+
const [payload, holders, quote, financials] = await Promise.all([
|
|
1083
|
+
loadForListing(listing, market, ctx, () => fetchBeneficialOwners(symbol, request)),
|
|
750
1084
|
market.dataProvider.getHolders?.(symbol, exchange).catch(() => null) ?? null,
|
|
751
|
-
|
|
1085
|
+
quotePromise,
|
|
1086
|
+
// Today's share count, which the filed percents are set against.
|
|
1087
|
+
market.dataProvider.getTickerFinancials(symbol, exchange).catch(() => null),
|
|
752
1088
|
]);
|
|
753
1089
|
const identity = listingIdentity(listing, quote);
|
|
754
|
-
const
|
|
1090
|
+
const sharesOutstanding = classSharesToday(financials);
|
|
1091
|
+
const rows = buildBeneficialReportRows(payload, { history, holders, sharesOutstanding });
|
|
755
1092
|
const notices = beneficialCoverageNotices(payload.coverage);
|
|
1093
|
+
const legend = beneficialReportLegend(rows, sharesOutstanding);
|
|
756
1094
|
ctx.printResult({
|
|
757
1095
|
data: rows,
|
|
758
1096
|
...(notices.length ? { warnings: notices } : {}),
|
|
@@ -765,12 +1103,16 @@ async function printBeneficialOwners(
|
|
|
765
1103
|
asOf: payload.asOf,
|
|
766
1104
|
coverage: payload.coverage,
|
|
767
1105
|
complete: beneficialListComplete(payload),
|
|
1106
|
+
sharesOutstanding,
|
|
768
1107
|
},
|
|
769
1108
|
}, {
|
|
770
1109
|
heading: `${listingHeading(identity)}${cliStyles.muted(beneficialListFacts(payload, form, rows.length, history).map((fact) => ` · ${fact}`).join(""))}`,
|
|
771
|
-
|
|
1110
|
+
// Text draws each column as the HDS report does; CSV and JSON keep the raw values.
|
|
1111
|
+
textColumns: BENEFICIAL_REPORT_COLUMNS.map((column) => ({ ...column, format: column.textFormat ?? column.format })),
|
|
1112
|
+
...(legend ? { footnote: legend } : {}),
|
|
772
1113
|
columns: [
|
|
773
|
-
...BENEFICIAL_REPORT_COLUMNS,
|
|
1114
|
+
...BENEFICIAL_REPORT_COLUMNS.map(({ key, header, align }) => ({ key, header, ...(align ? { align } : {}) })),
|
|
1115
|
+
{ key: "otherPersons", header: "Other persons" },
|
|
774
1116
|
{ key: "status", header: "Status" },
|
|
775
1117
|
{ key: "filerCik", header: "Filer CIK" },
|
|
776
1118
|
{ key: "accessionNumber", header: "Accession" },
|
|
@@ -778,6 +1120,8 @@ async function printBeneficialOwners(
|
|
|
778
1120
|
],
|
|
779
1121
|
empty: beneficialListUnreadable(payload)
|
|
780
1122
|
? `13D/13G filings for ${listingTitle(identity)} are listed but could not be read.`
|
|
1123
|
+
// The SEC knows no company for a listing abroad that files there under no name of its own.
|
|
1124
|
+
: abroad && !payload.cik ? noSecReportsNotice(identity)
|
|
781
1125
|
: `No 13D/13G filings in the last 4 years for ${listingTitle(identity)}.`,
|
|
782
1126
|
});
|
|
783
1127
|
}
|
|
@@ -799,51 +1143,80 @@ async function runHolders(
|
|
|
799
1143
|
await withMarketData(ctx, async (market) => {
|
|
800
1144
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
801
1145
|
if (form !== "13f") return printBeneficialOwners(listing, market, form, history, ctx);
|
|
802
|
-
const
|
|
1146
|
+
const insiderCommand = commandName === "insider" && ownerTypes != null;
|
|
1147
|
+
const venues = loadBareVenues(listing, market);
|
|
1148
|
+
const [data, quote, form4] = await Promise.all([
|
|
803
1149
|
loadForListing(
|
|
804
1150
|
listing, market, ctx,
|
|
805
1151
|
() => market.dataProvider.getHolders(listing.request.symbol, listing.request.exchange),
|
|
806
1152
|
(found) => found.holders.length === 0,
|
|
1153
|
+
{ command: commandName, noun: "holder data" },
|
|
807
1154
|
),
|
|
808
1155
|
loadListingQuote(market.dataProvider, listing),
|
|
1156
|
+
insiderCommand ? loadForm4Insiders(market, listing) : null,
|
|
809
1157
|
]);
|
|
810
1158
|
const identity = listingIdentity(listing, quote);
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
const
|
|
1159
|
+
const choice = await listingChoiceNotes(listing, market, quote, venues);
|
|
1160
|
+
// A London line's values can be dollars from the 13F filings; the listing's currency is not their unit.
|
|
1161
|
+
const valueCurrency = holderValueCurrency(data, "USD")!;
|
|
1162
|
+
// The rows, their units and their order are HDS's, so `holders --csv` and `fn HDS --csv` agree.
|
|
1163
|
+
const rows = holderReportRows(data, {
|
|
1164
|
+
marketCap: holderStakeMarketCap(quote?.marketCap, quote?.currency, valueCurrency),
|
|
1165
|
+
ownerTypes,
|
|
1166
|
+
});
|
|
1167
|
+
const shownRows = windowRows(rows, ctx.cliOptions).rows;
|
|
1168
|
+
const shown = shownRows.length;
|
|
814
1169
|
const institutional = !ownerTypes?.has("insider");
|
|
815
1170
|
const total = institutional ? data.summary?.institutionsCount ?? null : null;
|
|
816
1171
|
const reportDate = sharedReportDate(rows);
|
|
817
|
-
// A London line's values can be dollars from the 13F filings; the listing's currency is not their unit.
|
|
818
|
-
const valueCurrency = holderValueCurrency(data);
|
|
819
1172
|
const valueBasis = rows.length > 0 ? holderValueBasis(reportDate, data.valueBasis, valueCurrency) : null;
|
|
820
1173
|
const positionsBasis = rows.length > 0 ? nonUsHolderCaveat(identity.exchange || data.exchange, data.currency) : null;
|
|
821
1174
|
const shareBasisNote = holderShareBasisNote(data, rows);
|
|
822
1175
|
const markedRows = rows.some((row) => holderShareBasisMarker(row));
|
|
823
1176
|
// The heading names the listing; its unit, date and how much of the list follows on the same line.
|
|
1177
|
+
// One "as of" only when every row shown is of that date; each row carries its own.
|
|
824
1178
|
const facts = holderListFacts({
|
|
825
|
-
currency: valueCurrency,
|
|
1179
|
+
currency: valueCurrency,
|
|
1180
|
+
listingCurrency: data.currency,
|
|
1181
|
+
period: holderListPeriod(shownRows, data.asOf),
|
|
1182
|
+
shown,
|
|
1183
|
+
reported: rows.length,
|
|
1184
|
+
total,
|
|
1185
|
+
ranked: !positionsBasis,
|
|
826
1186
|
});
|
|
1187
|
+
const unvaluedNote = unvaluedHoldersNote(shownRows);
|
|
1188
|
+
const columns = holderReportColumns(shownRows, valueCurrency);
|
|
1189
|
+
// Dated as HDS dates it: by the list's as-of.
|
|
1190
|
+
const freshness = rowsFreshness(shownRows, { ...SEC_FILINGS, basis: "13F filings" }, { asOf: data.asOf ?? null });
|
|
1191
|
+
// Named as fn HDS takes it, so the closing notes point to the same listing.
|
|
1192
|
+
const requested = exchangeOption && !raw.includes(":") ? `${raw}:${exchangeOption.toUpperCase()}` : raw;
|
|
1193
|
+
const { notices } = holderReportNotices({ data, requested, shown: shownRows, reported: rows.length, total });
|
|
827
1194
|
ctx.printResult({
|
|
828
|
-
data,
|
|
1195
|
+
data: rows,
|
|
1196
|
+
...(freshness ? { freshness } : {}),
|
|
829
1197
|
metadata: {
|
|
830
1198
|
...listingMetadata(identity),
|
|
831
|
-
summary: data.summary,
|
|
1199
|
+
summary: data.summary ?? null,
|
|
832
1200
|
currency: data.currency ?? null,
|
|
833
|
-
valueCurrency
|
|
1201
|
+
valueCurrency,
|
|
1202
|
+
units: holderReportUnits(valueCurrency),
|
|
834
1203
|
asOf: data.asOf ?? null,
|
|
1204
|
+
// Each holder's latest period; several when the list mixes quarters.
|
|
1205
|
+
reportDates: holderReportDates(shownRows),
|
|
835
1206
|
shown,
|
|
836
1207
|
reported: rows.length,
|
|
837
1208
|
total,
|
|
838
1209
|
truncated: shown < (total ?? rows.length),
|
|
839
1210
|
valueBasis,
|
|
840
1211
|
positionsBasis,
|
|
1212
|
+
...(insiderCommand ? { form4Insiders: form4 } : {}),
|
|
841
1213
|
},
|
|
1214
|
+
...(choice.length > 0 ? { warnings: choice } : {}),
|
|
842
1215
|
}, {
|
|
843
1216
|
heading: `${listingHeading(identity)}${cliStyles.muted(facts.map((fact) => ` · ${fact}`).join(""))}`,
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
{ key: "
|
|
1217
|
+
columns,
|
|
1218
|
+
textColumns: [
|
|
1219
|
+
{ key: "ownerType", header: "Type" },
|
|
847
1220
|
{ key: "name", header: "Holder" },
|
|
848
1221
|
{ key: "reportDate", header: "Date" },
|
|
849
1222
|
{ key: "shares", header: "Shares", align: "right", format: formatCountCell },
|
|
@@ -859,12 +1232,17 @@ async function runHolders(
|
|
|
859
1232
|
key: "percentHeld",
|
|
860
1233
|
header: "% Held",
|
|
861
1234
|
align: "right",
|
|
862
|
-
format: (value) => isFiniteNumber(value) ?
|
|
1235
|
+
format: (value) => isFiniteNumber(value) ? formatPercentHeld(value) : "",
|
|
863
1236
|
},
|
|
864
1237
|
],
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1238
|
+
// CSV and NDJSON are the HDS report's table and closing lines.
|
|
1239
|
+
tables: {
|
|
1240
|
+
tables: [exportRowsTable("Holders", columns, shownRows)],
|
|
1241
|
+
footer: reportFooterLines({ freshness, notes: notices }),
|
|
1242
|
+
},
|
|
1243
|
+
summary: () => [
|
|
1244
|
+
[valueBasis ? `Value = ${valueBasis}.` : "", positionsBasis ?? "", shareBasisNote ?? "", unvaluedNote ?? ""].filter(Boolean).join(" "),
|
|
1245
|
+
insiderCommand ? insiderSummary(data, ownerTypes, listing, identity, form4) : "",
|
|
868
1246
|
].filter(Boolean).join("\n"),
|
|
869
1247
|
empty: `No holders reported for ${listingTitle(identity)}.`,
|
|
870
1248
|
});
|
|
@@ -877,11 +1255,16 @@ async function runAnalyst(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
877
1255
|
const raw = requireOneArg(args, "analyst <symbol>", "symbol", ctx);
|
|
878
1256
|
await withMarketData(ctx, async (market) => {
|
|
879
1257
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
1258
|
+
const venues = loadBareVenues(listing, market);
|
|
880
1259
|
const [data, quote] = await Promise.all([
|
|
881
|
-
loadForListing(
|
|
1260
|
+
loadForListing(
|
|
1261
|
+
listing, market, ctx, () => market.dataProvider.getAnalystResearch(listing.request.symbol, listing.request.exchange),
|
|
1262
|
+
undefined, { command: "analyst", noun: "analyst research" },
|
|
1263
|
+
),
|
|
882
1264
|
loadListingQuote(market.dataProvider, listing),
|
|
883
1265
|
]);
|
|
884
1266
|
const identity = listingIdentity(listing, quote);
|
|
1267
|
+
const choice = await listingChoiceNotes(listing, market, quote, venues);
|
|
885
1268
|
ctx.printResult({
|
|
886
1269
|
data,
|
|
887
1270
|
metadata: {
|
|
@@ -893,6 +1276,7 @@ async function runAnalyst(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
893
1276
|
freshness: rowsFreshness(analystRows(data), {
|
|
894
1277
|
...REPORTED_DATA, basis: "analyst ratings", observedKey: "date", oldest: null,
|
|
895
1278
|
}, { stale: data.stale === true }),
|
|
1279
|
+
...(choice.length > 0 ? { warnings: choice } : {}),
|
|
896
1280
|
}, {
|
|
897
1281
|
heading: listingHeading(identity),
|
|
898
1282
|
rows: analystRows,
|
|
@@ -914,17 +1298,23 @@ async function runEvents(rawArgs: string[], ctx: Parameters<CliCommandDef["execu
|
|
|
914
1298
|
const raw = requireOneArg(args, "events <symbol>", "symbol", ctx);
|
|
915
1299
|
await withMarketData(ctx, async (market) => {
|
|
916
1300
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
1301
|
+
const venues = loadBareVenues(listing, market);
|
|
917
1302
|
const [data, quote] = await Promise.all([
|
|
918
|
-
loadForListing(
|
|
1303
|
+
loadForListing(
|
|
1304
|
+
listing, market, ctx, () => market.dataProvider.getCorporateActions(listing.request.symbol, listing.request.exchange),
|
|
1305
|
+
undefined, { command: "events", noun: "corporate events" },
|
|
1306
|
+
),
|
|
919
1307
|
loadListingQuote(market.dataProvider, listing),
|
|
920
1308
|
]);
|
|
921
1309
|
const identity = listingIdentity(listing, quote);
|
|
1310
|
+
const choice = await listingChoiceNotes(listing, market, quote, venues);
|
|
922
1311
|
ctx.printResult({
|
|
923
1312
|
data,
|
|
924
1313
|
metadata: listingMetadata(identity),
|
|
925
1314
|
freshness: rowsFreshness(corporateActionRows(data), {
|
|
926
1315
|
...REPORTED_DATA, basis: "corporate actions", observedKey: "date", oldest: null,
|
|
927
1316
|
}),
|
|
1317
|
+
...(choice.length > 0 ? { warnings: choice } : {}),
|
|
928
1318
|
}, {
|
|
929
1319
|
heading: listingHeading(identity),
|
|
930
1320
|
rows: corporateActionRows,
|
|
@@ -939,85 +1329,177 @@ async function runEvents(rawArgs: string[], ctx: Parameters<CliCommandDef["execu
|
|
|
939
1329
|
|
|
940
1330
|
async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
941
1331
|
const args = [...rawArgs];
|
|
1332
|
+
const leapsOptions = takeLeapsOptions(args);
|
|
942
1333
|
const expiration = takeOption(args, "--expiration");
|
|
943
1334
|
const exchangeOption = takeOption(args, "--exchange");
|
|
944
|
-
|
|
1335
|
+
if (leapsOptions.leaps) {
|
|
1336
|
+
if (expiration != null || rawArgs.includes("--expiration")) {
|
|
1337
|
+
ctx.fail("--leaps reads every expiry more than a year out; drop --expiration.", `Usage: gloomberb ${LEAPS_USAGE}`);
|
|
1338
|
+
}
|
|
1339
|
+
const criteria = parseLeapsCriteria(leapsOptions.raw);
|
|
1340
|
+
if ("error" in criteria) ctx.fail(criteria.error, `Usage: gloomberb ${LEAPS_USAGE}`);
|
|
1341
|
+
const symbols = normalizeSymbols(args);
|
|
1342
|
+
if (symbols.length === 0) ctx.fail("--leaps needs at least one symbol.", `Usage: gloomberb ${LEAPS_USAGE}`);
|
|
1343
|
+
await withMarketData(ctx, (market) => runLeapsScreen(symbols, exchangeOption, criteria, market, ctx));
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
if (leapsOptions.given.length > 0) {
|
|
1347
|
+
ctx.fail(`${leapsOptions.given.join(", ")} only ${leapsOptions.given.length > 1 ? "apply" : "applies"} with --leaps.`, `Usage: gloomberb ${LEAPS_USAGE}`);
|
|
1348
|
+
}
|
|
1349
|
+
const raw = requireOneArg(args, OPTIONS_USAGE, "symbol", ctx);
|
|
1350
|
+
let expirationDate: number | undefined;
|
|
1351
|
+
if (expiration != null || rawArgs.includes("--expiration")) {
|
|
1352
|
+
const parsed = expiration == null ? null : parseOptionExpiration(expiration);
|
|
1353
|
+
if (parsed == null) {
|
|
1354
|
+
ctx.fail(
|
|
1355
|
+
expiration == null ? "--expiration needs a date." : `Invalid --expiration "${expiration}".`,
|
|
1356
|
+
`Use YYYY-MM-DD (2028-01-21) or Unix seconds (1832025600).\nUsage: gloomberb ${OPTIONS_USAGE}`,
|
|
1357
|
+
);
|
|
1358
|
+
}
|
|
1359
|
+
expirationDate = parsed;
|
|
1360
|
+
}
|
|
945
1361
|
await withMarketData(ctx, async (market) => {
|
|
946
1362
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
947
1363
|
const { symbol, exchange } = listing.request;
|
|
948
|
-
const
|
|
1364
|
+
const refresh = ctx.cliOptions.refresh;
|
|
1365
|
+
const venues = loadBareVenues(listing, market);
|
|
949
1366
|
const quotePromise = loadListingQuote(market.dataProvider, listing);
|
|
950
|
-
const
|
|
1367
|
+
const inputsPromise = loadOptionModelInputs(market.dataProvider, listing, quotePromise, refresh);
|
|
1368
|
+
const { result, chain: loaded } = await loadForListing(listing, market, ctx, async () => {
|
|
951
1369
|
const cached = await market.dataProvider.getCachedQuery?.("getOptionsChain", [symbol, exchange, expirationDate, undefined])
|
|
952
|
-
.load({ force:
|
|
1370
|
+
.load({ force: refresh });
|
|
953
1371
|
return {
|
|
954
1372
|
result: cached,
|
|
955
1373
|
chain: cached?.value ?? await market.dataProvider.getOptionsChain(symbol, exchange, expirationDate, {
|
|
956
|
-
cacheMode:
|
|
1374
|
+
cacheMode: refresh ? "refresh" : "default",
|
|
957
1375
|
}),
|
|
958
1376
|
};
|
|
1377
|
+
}, undefined, { command: "options", noun: "options chain" }).catch((error: unknown) => {
|
|
1378
|
+
if (!isOptionsUnavailableError(error)) throw error;
|
|
1379
|
+
// A future or commodity with no chain of its own: name the listed fund whose options stand in.
|
|
1380
|
+
const alternative = listedOptionsAlternative(listing.key);
|
|
1381
|
+
return ctx.fail(optionsUnavailableTitle(listing.key), alternative
|
|
1382
|
+
? `Nearest listed alternative: ${alternative.symbol} (${alternative.name}), gloomberb options ${alternative.symbol}.`
|
|
1383
|
+
+ ` Strategy payoffs: gloomberb fn OSA ${alternative.symbol}`
|
|
1384
|
+
: "Strategy payoffs on a listed underlying: gloomberb fn OSA <ticker>");
|
|
959
1385
|
});
|
|
960
|
-
const
|
|
1386
|
+
const inputs = await inputsPromise;
|
|
1387
|
+
if (expirationDate != null && !chainHasExpiry(loaded, expirationDate)) {
|
|
1388
|
+
// An unlisted date comes back as an empty chain; its own list of expiries, else the default chain's, says what to pick.
|
|
1389
|
+
const listed = loaded.expirationDates.length > 0 ? loaded.expirationDates : await market.dataProvider
|
|
1390
|
+
.getOptionsChain(symbol, exchange, undefined, { cacheMode: "default" })
|
|
1391
|
+
.then((fallback) => fallback.expirationDates, () => []);
|
|
1392
|
+
const miss = missingExpiryMessage(listing.key, expirationDate, listed);
|
|
1393
|
+
ctx.fail(miss.message, miss.details);
|
|
1394
|
+
}
|
|
1395
|
+
const quote = await quotePromise;
|
|
1396
|
+
const identity = listingIdentity(listing, quote);
|
|
1397
|
+
const chain = chainWithModelFigures(loaded, inputs);
|
|
961
1398
|
// A failed refresh falls back to the stored chain, which can be days old.
|
|
962
1399
|
const refreshWarning = result?.refreshError == null ? null
|
|
963
1400
|
: `Options refresh failed; showing the chain stored ${new Date(result.fetchedAt).toISOString()}`
|
|
964
1401
|
+ (chain.asOf ? ` (last trade ${chain.asOf})` : "");
|
|
965
1402
|
const sessionWarning = refreshWarning ? null : priorSessionChainWarning(chain, identity.exchange, Date.now());
|
|
966
|
-
const
|
|
1403
|
+
const modelWarning = inputs.spot == null && (chain.calls.length > 0 || chain.puts.length > 0)
|
|
1404
|
+
? `IV and delta need a current ${listing.key} quote; those columns are blank`
|
|
1405
|
+
: null;
|
|
1406
|
+
const warnings = [
|
|
1407
|
+
...await listingChoiceNotes(listing, market, quote, venues),
|
|
1408
|
+
...[refreshWarning ?? sessionWarning, modelWarning].filter((warning): warning is string => warning != null),
|
|
1409
|
+
];
|
|
967
1410
|
// Dated by the chain's last trade; a chain that failed to refresh is the stored one, stale.
|
|
968
1411
|
const freshness = rowsFreshness([], { asOf: chain.asOf ?? null }, {
|
|
969
1412
|
...(chain.dataSource ? { dataSource: chain.dataSource } : {}),
|
|
970
1413
|
...(chain.delayMinutes != null ? { delayMinutes: chain.delayMinutes } : {}),
|
|
971
1414
|
stale: refreshWarning != null,
|
|
972
1415
|
});
|
|
973
|
-
ctx.printResult({
|
|
1416
|
+
ctx.printResult({
|
|
1417
|
+
data: chain,
|
|
1418
|
+
metadata: {
|
|
1419
|
+
...listingMetadata(identity),
|
|
1420
|
+
expirations: chain.expirationDates,
|
|
1421
|
+
// What `iv` and `delta` on each contract are valued from; the provider's own impliedVolatility is left as sent.
|
|
1422
|
+
model: { spot: inputs.spot ?? null, dividendYield: inputs.dividendYield ?? null, rate: OPTION_MODEL_RATE },
|
|
1423
|
+
},
|
|
1424
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
1425
|
+
freshness,
|
|
1426
|
+
}, {
|
|
974
1427
|
heading: listingHeading(identity),
|
|
975
1428
|
rows: optionRows,
|
|
1429
|
+
// Choosing an expiry needs the list; once one is chosen the table is that expiry.
|
|
1430
|
+
summary: (data: OptionsChain) => expirationDate == null ? formatExpiryList(data.expirationDates) : "",
|
|
1431
|
+
empty: `No option contracts for ${listingTitle(identity)}.`,
|
|
976
1432
|
columns: [
|
|
977
1433
|
{ key: "side", header: "Side" },
|
|
978
1434
|
{ key: "contract", header: "Contract", shrink: false },
|
|
979
|
-
|
|
1435
|
+
// The contract symbol already carries the date, so this goes first when the table is narrow.
|
|
1436
|
+
{ key: "expiration", header: "Expiry", optional: true, dropPriority: 2 },
|
|
980
1437
|
{ key: "strike", header: "Strike", align: "right" },
|
|
981
1438
|
{ key: "last", header: "Last", align: "right" },
|
|
982
1439
|
{ key: "bid", header: "Bid", align: "right", format: (_value, row) => formatOptionQuoteCell(row, "bid") },
|
|
983
1440
|
{ key: "ask", header: "Ask", align: "right", format: (_value, row) => formatOptionQuoteCell(row, "ask") },
|
|
984
|
-
{ key: "
|
|
985
|
-
{ key: "
|
|
1441
|
+
{ key: "iv", header: "IV", align: "right", format: formatOptionIvCell },
|
|
1442
|
+
{ key: "delta", header: "Delta", align: "right", format: formatOptionDeltaCell },
|
|
1443
|
+
{ key: "volume", header: "Vol", align: "right", format: formatCountCell, optional: true, dropPriority: 1 },
|
|
1444
|
+
{ key: "openInterest", header: "OI", align: "right", format: formatCountCell, optional: true, dropPriority: 1 },
|
|
986
1445
|
],
|
|
987
1446
|
});
|
|
988
1447
|
});
|
|
989
1448
|
}
|
|
990
1449
|
|
|
991
|
-
const FX_USAGE = "fx <currency>";
|
|
1450
|
+
const FX_USAGE = "fx <currency> | fx <base>/<quote>";
|
|
992
1451
|
|
|
993
1452
|
async function runFx(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
994
|
-
const
|
|
1453
|
+
const raw = requireOneArg(rawArgs, FX_USAGE, "currency or pair", ctx);
|
|
995
1454
|
await withMarketData(ctx, async (market) => {
|
|
996
|
-
const
|
|
1455
|
+
const request = parseFxRequest(raw, market.config.baseCurrency);
|
|
1456
|
+
if ("error" in request) return ctx.fail(request.error, `Usage: gloomberb ${FX_USAGE}`);
|
|
1457
|
+
const { currency, baseCurrency } = request;
|
|
1458
|
+
const pair = `${currency}/${baseCurrency}`;
|
|
1459
|
+
const reasons = new Map<string, string>();
|
|
997
1460
|
const load = (code: string) => market.dataProvider.getCachedQuery("getExchangeRate", [code])
|
|
998
|
-
.load({ force: ctx.cliOptions.refresh }).catch(() =>
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1461
|
+
.load({ force: ctx.cliOptions.refresh }).catch((error) => {
|
|
1462
|
+
const reason = providerMissReason(error);
|
|
1463
|
+
if (reason) reasons.set(code, reason);
|
|
1464
|
+
return null;
|
|
1465
|
+
});
|
|
1466
|
+
// Every rate is a cross of two USD legs; a code priced in itself needs none.
|
|
1467
|
+
const codes = currency === baseCurrency ? [] : [currency, baseCurrency];
|
|
1468
|
+
const legs = await Promise.all(codes.map(load));
|
|
1469
|
+
const missing = codes.filter((_code, index) => !isUsableRate(legs[index]?.value));
|
|
1470
|
+
if (missing.length > 0) {
|
|
1471
|
+
// A reason the data service gave for the first missing leg says more than the generic line.
|
|
1472
|
+
const reason = missing.map((code) => reasons.get(code)).find(Boolean);
|
|
1473
|
+
if (reason) ctx.fail(reason);
|
|
1474
|
+
ctx.fail(
|
|
1475
|
+
`Exchange rate unavailable for ${missing.join(" and ")}.`,
|
|
1476
|
+
`${pair} is crossed from each currency's USD rate, and none came back for ${missing.join(" or ")}. Check the ISO code.`,
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
const rate = codes.length === 0 ? 1 : crossRate(legs[0]?.value, legs[1]?.value);
|
|
1003
1480
|
// A cross rate is only as current as its older leg.
|
|
1004
1481
|
const observed = legs.flatMap((leg) => leg?.asOf ?? []);
|
|
1005
1482
|
const asOf = observed.length > 0 ? new Date(Math.min(...observed)).toISOString() : null;
|
|
1006
1483
|
const stale = legs.some((leg) => leg != null && (leg.staleAt <= Date.now() || leg.refreshError != null));
|
|
1007
|
-
|
|
1008
|
-
|
|
1484
|
+
const row = { currency, baseCurrency, rate, asOf, stale, pair, inverse: 1 / rate };
|
|
1485
|
+
// The closing line dates the rate, and says when it is stale, as `fn FXC` does.
|
|
1486
|
+
const freshness = fxPairFreshness(new Map(codes.flatMap((code, index) => {
|
|
1487
|
+
const leg = legs[index];
|
|
1488
|
+
return leg ? [[code, {
|
|
1489
|
+
rate: leg.value,
|
|
1490
|
+
timing: { asOf: leg.asOf, staleAt: leg.staleAt, error: leg.refreshError, delayMinutes: leg.delayMinutes },
|
|
1491
|
+
}] as const] : [];
|
|
1492
|
+
})));
|
|
1493
|
+
ctx.printResult({ data: [row], freshness }, {
|
|
1494
|
+
text: () => describeFxRate(request, rate),
|
|
1009
1495
|
columns: [
|
|
1010
1496
|
{ key: "currency", header: "Currency" },
|
|
1011
1497
|
{ key: "baseCurrency", header: "Base" },
|
|
1012
1498
|
{ 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
|
-
}] : []),
|
|
1499
|
+
{ key: "asOf", header: "As Of" },
|
|
1500
|
+
{ key: "stale", header: "Stale" },
|
|
1501
|
+
{ key: "pair", header: "Pair" },
|
|
1502
|
+
{ key: "inverse", header: "Inverse", align: "right" },
|
|
1021
1503
|
],
|
|
1022
1504
|
});
|
|
1023
1505
|
});
|
|
@@ -1032,24 +1514,55 @@ async function runEarnings(rawArgs: string[], ctx: Parameters<CliCommandDef["exe
|
|
|
1032
1514
|
const listings = await Promise.all(symbols.map((symbol) => requireCliListing(
|
|
1033
1515
|
symbol, exchangeOption, services, ctx, { ownExchangeWins: symbols.length > 1 },
|
|
1034
1516
|
)));
|
|
1035
|
-
const
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
(
|
|
1039
|
-
|
|
1517
|
+
const keys = listings.map((listing) => listing.key);
|
|
1518
|
+
// Each bare symbol's venues cost a search, so a long list (a portfolio check, not a lookup) names none.
|
|
1519
|
+
const venues = new Map(listings.length > MAX_CHOICE_LOOKUPS ? []
|
|
1520
|
+
: listings.map((listing) => [listing, loadBareVenues(listing, services)] as const));
|
|
1521
|
+
let events: EarningsEvent[];
|
|
1522
|
+
try {
|
|
1523
|
+
events = await loadForListing(
|
|
1524
|
+
listings, services, ctx,
|
|
1525
|
+
() => services.dataProvider.getEarningsCalendar(keys),
|
|
1526
|
+
(found) => found.length === 0,
|
|
1527
|
+
);
|
|
1528
|
+
} catch (error) {
|
|
1529
|
+
const failure = earningsCalendarFailure(error, keys);
|
|
1530
|
+
if (failure) ctx.fail(failure);
|
|
1531
|
+
throw error;
|
|
1532
|
+
}
|
|
1533
|
+
const eventOf = (listing: CliListing) => events.find((event) => event.symbol.toUpperCase() === listing.key.toUpperCase());
|
|
1534
|
+
const [abroadNotes, choiceNotes] = await Promise.all([
|
|
1535
|
+
// A listing abroad with no date of its own says so, with its US line's date when that line is the same company.
|
|
1536
|
+
Promise.all(listings.filter((listing) => isListingAbroad(listing) && !eventOf(listing))
|
|
1537
|
+
.map((listing) => noListingCalendarNote(listing, services))),
|
|
1538
|
+
Promise.all(listings.map(async (listing) => {
|
|
1539
|
+
const listed = venues.get(listing);
|
|
1540
|
+
return listed ? earningsChoiceNote(listing, eventOf(listing), await listed) : null;
|
|
1541
|
+
})),
|
|
1542
|
+
]);
|
|
1543
|
+
const noCalendar = abroadNotes.join("\n");
|
|
1544
|
+
// Text prints the listings without a calendar under the table; other formats carry them as warnings.
|
|
1545
|
+
const warnings = [
|
|
1546
|
+
...listings.flatMap(listingReadNotes),
|
|
1547
|
+
...choiceNotes.filter((note): note is string => !!note),
|
|
1548
|
+
...ctx.cliOptions.format === "text" ? [] : abroadNotes,
|
|
1549
|
+
];
|
|
1040
1550
|
ctx.printResult({
|
|
1041
1551
|
data: events,
|
|
1042
1552
|
freshness: rowsFreshness(earningsRows(events), { status: "not-a-feed", basis: "calendar", observedKey: "date", oldest: null }),
|
|
1553
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
1043
1554
|
}, {
|
|
1044
1555
|
rows: earningsRows,
|
|
1045
1556
|
columns: [
|
|
1046
1557
|
{ key: "date", header: "Date" },
|
|
1558
|
+
{ key: "status", header: "Status" },
|
|
1047
1559
|
{ key: "symbol", header: "Symbol" },
|
|
1048
1560
|
{ key: "name", header: "Name" },
|
|
1049
1561
|
{ key: "timing", header: "Timing" },
|
|
1050
1562
|
{ key: "epsEstimate", header: "EPS Est", align: "right", format: formatEpsCell },
|
|
1051
1563
|
{ key: "epsActual", header: "EPS", align: "right", format: formatEpsCell },
|
|
1052
1564
|
],
|
|
1565
|
+
...(noCalendar ? { empty: noCalendar, footnote: noCalendar } : {}),
|
|
1053
1566
|
});
|
|
1054
1567
|
});
|
|
1055
1568
|
}
|
|
@@ -1090,11 +1603,11 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
|
1090
1603
|
sections: [{
|
|
1091
1604
|
title: "Bars",
|
|
1092
1605
|
lines: [
|
|
1093
|
-
`Each range comes in one bar size: ${historyIntervalsByRange()}. The line under the heading, and the --json metadata, give the currency, the bar size actually served, the first and last bar and the bar count. A warning says when the bars are coarser, or start later, than the range asked for.`,
|
|
1606
|
+
`Each range comes in one bar size: ${historyIntervalsByRange()}. The line under the heading, and the --json metadata, give the currency, the bar size actually served, the first and last bar and the bar count. A warning says when the bars are coarser, or start later, than the range asked for, and for a coin, which trades every day, names each day it has no bar for.`,
|
|
1094
1607
|
"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
1608
|
],
|
|
1096
1609
|
}],
|
|
1097
|
-
examples: ["history AAPL", "history BHP:ASX --range 5Y", "history ZAR=X --range ALL", "history AAPL --range 5Y --csv > aapl.csv"],
|
|
1610
|
+
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
1611
|
},
|
|
1099
1612
|
unknownOptionHint: (flag) => DATE_WINDOW_OPTIONS.has(flag)
|
|
1100
1613
|
? `history takes a --range instead of dates: ${TIME_RANGES.join(", ")}. ${HISTORY_ALL_RANGE}.`
|
|
@@ -1103,15 +1616,46 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
|
1103
1616
|
},
|
|
1104
1617
|
{
|
|
1105
1618
|
name: "options",
|
|
1106
|
-
description: "Fetch an options chain",
|
|
1619
|
+
description: "Fetch an options chain, or rank LEAPS across symbols",
|
|
1107
1620
|
help: {
|
|
1108
1621
|
group: CLI_COMMAND_GROUPS.research,
|
|
1109
|
-
usage: [
|
|
1622
|
+
usage: [OPTIONS_USAGE, LEAPS_USAGE],
|
|
1110
1623
|
options: [
|
|
1111
|
-
{
|
|
1624
|
+
{
|
|
1625
|
+
flags: "--expiration <YYYY-MM-DD|unix>",
|
|
1626
|
+
description: "Expiration as a date (2028-01-21) or Unix seconds; defaults to the nearest one, and lists the others",
|
|
1627
|
+
},
|
|
1112
1628
|
EXCHANGE_OPTION,
|
|
1629
|
+
{ flags: "--leaps", description: "Rank contracts more than a year out across the symbols given, as a stock replacement" },
|
|
1630
|
+
{ flags: "--side <calls|puts>", description: `With --leaps: the side to rank (default ${DEFAULT_LEAPS_CRITERIA.side}s)` },
|
|
1631
|
+
{
|
|
1632
|
+
flags: "--delta <low-high>",
|
|
1633
|
+
description: `With --leaps: absolute delta band (default ${DEFAULT_LEAPS_CRITERIA.minDelta.toFixed(2)}-${DEFAULT_LEAPS_CRITERIA.maxDelta.toFixed(2)})`,
|
|
1634
|
+
},
|
|
1635
|
+
{
|
|
1636
|
+
flags: "--max-spread <percent>",
|
|
1637
|
+
description: `With --leaps: widest bid/ask spread, in percent of the midpoint (default ${DEFAULT_LEAPS_CRITERIA.maxSpreadPercent})`,
|
|
1638
|
+
},
|
|
1639
|
+
{ flags: "--min-oi <contracts>", description: `With --leaps: least open interest (default ${DEFAULT_LEAPS_CRITERIA.minOpenInterest})` },
|
|
1640
|
+
{
|
|
1641
|
+
flags: "--sort <order>",
|
|
1642
|
+
description: "With --leaps: carry (extrinsic per year, lowest first; the default), spread, oi, delta, expiry or symbol",
|
|
1643
|
+
},
|
|
1644
|
+
],
|
|
1645
|
+
sections: [{
|
|
1646
|
+
title: "LEAPS",
|
|
1647
|
+
lines: [
|
|
1648
|
+
"--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.",
|
|
1649
|
+
"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.",
|
|
1650
|
+
],
|
|
1651
|
+
}],
|
|
1652
|
+
examples: [
|
|
1653
|
+
"options AAPL",
|
|
1654
|
+
"options AAPL --expiration 2028-01-21",
|
|
1655
|
+
"options AAPL:NASDAQ --json",
|
|
1656
|
+
"options AAPL MSFT NVDA GOOGL AMZN META --leaps",
|
|
1657
|
+
"options SPY QQQ --leaps --side puts --delta 0.20-0.40 --csv",
|
|
1113
1658
|
],
|
|
1114
|
-
examples: ["options AAPL", "options AAPL:NASDAQ --json"],
|
|
1115
1659
|
},
|
|
1116
1660
|
execute: runOptions,
|
|
1117
1661
|
},
|
|
@@ -1219,7 +1763,13 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
|
1219
1763
|
{ flags: "--history", description: "With --form 13d, 13g or all, every report newest first instead of the latest per filer" },
|
|
1220
1764
|
EXCHANGE_OPTION,
|
|
1221
1765
|
],
|
|
1222
|
-
|
|
1766
|
+
sections: [{
|
|
1767
|
+
title: "Output",
|
|
1768
|
+
lines: [
|
|
1769
|
+
"Holders run largest value first, as in fn HDS. --csv and --json carry the HDS report's columns: values in the currency the header names, shares, and the quarter's change and the stake in percent (0.59 is 0.59%).",
|
|
1770
|
+
],
|
|
1771
|
+
}],
|
|
1772
|
+
examples: ["holders AAPL", "holders SAN:EPA", "holders SAN --csv", "holders CAR --form all", "holders CAR --form 13g --history --json"],
|
|
1223
1773
|
},
|
|
1224
1774
|
execute: (args, ctx) => runHolders(args, ctx, "holders"),
|
|
1225
1775
|
},
|
|
@@ -1266,25 +1816,38 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
|
1266
1816
|
},
|
|
1267
1817
|
{
|
|
1268
1818
|
name: "news",
|
|
1269
|
-
description: "Fetch market headlines,
|
|
1819
|
+
description: "Fetch market headlines, news for one symbol, or stories matching text",
|
|
1270
1820
|
help: {
|
|
1271
1821
|
group: CLI_COMMAND_GROUPS.companyData,
|
|
1272
|
-
usage: ["news [symbol] [--feed <feed>]"],
|
|
1822
|
+
usage: ["news [symbol] [--feed <feed>]", "news <query>"],
|
|
1273
1823
|
options: [
|
|
1274
1824
|
{ flags: "--feed <feed>", description: "latest, top, or breaking for market news (default latest)" },
|
|
1275
1825
|
EXCHANGE_OPTION,
|
|
1276
1826
|
],
|
|
1277
|
-
|
|
1827
|
+
sections: [{
|
|
1828
|
+
title: "Text",
|
|
1829
|
+
lines: [
|
|
1830
|
+
"Several words, in quotes or not, search the stories that mention them. So does a single word of three letters or more that no listing answers to, and a futures board code such as TTF, a gas benchmark rather than a ticker. A real ticker always gives its own news. The best matches print newest first, 20 unless --limit says otherwise; a search takes neither --feed nor --exchange.",
|
|
1831
|
+
],
|
|
1832
|
+
}],
|
|
1833
|
+
examples: ["news", "news TSLA", "news SAN:EPA", "news TTF", "news \"european gas\"", "news --feed top --limit 10"],
|
|
1278
1834
|
},
|
|
1279
1835
|
execute: runNews,
|
|
1280
1836
|
},
|
|
1281
1837
|
{
|
|
1282
1838
|
name: "fx",
|
|
1283
|
-
description: "Convert a currency into your base currency",
|
|
1839
|
+
description: "Convert a currency into your base currency, or quote a pair such as USD/NGN",
|
|
1284
1840
|
help: {
|
|
1285
1841
|
group: CLI_COMMAND_GROUPS.markets,
|
|
1286
|
-
usage: [
|
|
1287
|
-
|
|
1842
|
+
usage: ["fx <currency>", "fx <base>/<quote>"],
|
|
1843
|
+
sections: [{
|
|
1844
|
+
title: "Direction",
|
|
1845
|
+
lines: [
|
|
1846
|
+
"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.",
|
|
1847
|
+
"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.",
|
|
1848
|
+
],
|
|
1849
|
+
}],
|
|
1850
|
+
examples: ["fx EUR", "fx NGN", "fx USD/NGN", "fx ZAR/NGN", "fx JPY --json"],
|
|
1288
1851
|
},
|
|
1289
1852
|
execute: runFx,
|
|
1290
1853
|
},
|