gloomberb 0.16.2 → 0.16.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -1
- package/src/api-client/askg.ts +43 -0
- package/src/api-client/auth.ts +15 -0
- package/src/api-client/beneficial-owners.ts +4 -0
- package/src/api-client/chat.ts +19 -0
- package/src/api-client/company-kpis.ts +14 -2
- package/src/api-client/data.ts +34 -16
- package/src/api-client/index.ts +12 -0
- package/src/api-client/normalizers.ts +27 -0
- package/src/api-client/paths.ts +88 -23
- package/src/api-client/request.ts +2 -1
- package/src/api-client/research-activity.ts +31 -0
- package/src/api-client/socket.ts +39 -8
- package/src/api-client/types.ts +57 -1
- package/src/api-client/upgrade-experiment.ts +90 -0
- package/src/app/global-shortcuts.ts +11 -1
- package/src/app/keybindings/actions.ts +10 -0
- package/src/app/pane-runtime/index.tsx +8 -0
- package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
- package/src/app/pane-runtime/plugin-bindings.ts +65 -25
- package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
- package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
- package/src/app/runtime/desktop-deeplink.ts +5 -0
- package/src/app/star-prompt/model.ts +98 -0
- package/src/app/star-prompt/runtime.ts +41 -0
- package/src/app/star-prompt/status-line.tsx +190 -0
- package/src/app.tsx +13 -7
- package/src/architecture/core-builtin-allowlist.json +70 -0
- package/src/brokers/account-cache.ts +45 -0
- package/src/brokers/signed-in/adapter.ts +33 -2
- package/src/brokers/signed-in/client.ts +10 -0
- package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
- package/src/brokers/signed-in/sign-in.ts +62 -5
- package/src/capabilities/registry.ts +8 -1
- package/src/capabilities/types.ts +2 -0
- package/src/cli/command-options.ts +74 -0
- package/src/cli/commands/command-utils.ts +29 -0
- package/src/cli/commands/crypto-hints.ts +12 -4
- package/src/cli/commands/market.ts +524 -148
- package/src/cli/commands/options-chain.ts +172 -0
- package/src/cli/commands/options-leaps.ts +422 -0
- package/src/cli/commands/overview.ts +223 -42
- package/src/cli/commands/search.ts +29 -8
- package/src/cli/commands/system.ts +99 -19
- package/src/cli/commands/themes.ts +64 -0
- package/src/cli/commands/ticker.ts +200 -73
- package/src/cli/desktop-pane-shot.ts +100 -16
- package/src/cli/errors.ts +25 -2
- package/src/cli/freshness.ts +7 -4
- package/src/cli/fx-pair.ts +57 -0
- package/src/cli/help.ts +40 -7
- package/src/cli/history-rows.ts +187 -15
- package/src/cli/index-roots.ts +51 -0
- package/src/cli/index.ts +21 -13
- package/src/cli/listing-arg.ts +211 -3
- package/src/cli/not-a-ticker.ts +37 -0
- package/src/cli/options.ts +55 -7
- package/src/cli/pane-functions/capabilities.ts +13 -2
- package/src/cli/pane-functions/catalog.ts +13 -2
- package/src/cli/pane-functions/data.ts +36 -5
- package/src/cli/pane-functions/dom.ts +89 -28
- package/src/cli/pane-functions/freshness.ts +205 -26
- package/src/cli/pane-functions/function-help.ts +71 -24
- package/src/cli/pane-functions/headless.ts +168 -35
- package/src/cli/pane-functions/index.ts +12 -2
- package/src/cli/pane-functions/market-session.ts +133 -0
- package/src/cli/pane-functions/options.ts +15 -4
- package/src/cli/pane-functions/report-notices.ts +63 -0
- package/src/cli/pane-functions/report.ts +12 -1
- package/src/cli/pane-functions/resolver.ts +32 -8
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +109 -23
- package/src/cli/registry.ts +8 -4
- package/src/cli/report-tables.ts +460 -0
- package/src/cli/result.ts +63 -16
- package/src/cli/row-window.ts +76 -0
- package/src/cli/scroll-truncation.ts +34 -0
- package/src/components/chart/composite/composite-chart.tsx +43 -4
- package/src/components/chart/composite/legend.tsx +39 -6
- package/src/components/chart/composite/rasterizer.ts +64 -4
- package/src/components/chart/composite/series-cues.ts +71 -0
- package/src/components/chart/composite/text-renderer.ts +16 -8
- package/src/components/chart/composite/types.ts +3 -0
- package/src/components/command-bar/commands/direct/index.ts +9 -1
- package/src/components/command-bar/commands/plugin/items.ts +3 -1
- package/src/components/command-bar/commands/registry.ts +7 -0
- package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/routes/root/command-items.ts +2 -0
- package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
- package/src/components/command-bar/routes/root/results.ts +40 -10
- package/src/components/command-bar/routes/root/runtime.ts +8 -0
- package/src/components/command-bar/routes/root/search-providers.ts +97 -14
- package/src/components/command-bar/surface/index.tsx +18 -4
- package/src/components/command-bar/theme-picker.tsx +48 -13
- package/src/components/command-bar/workflow/ops.ts +5 -3
- package/src/components/data-table/export.ts +6 -51
- package/src/components/data-table/view.tsx +31 -10
- package/src/components/layout/floating-pane.tsx +6 -2
- package/src/components/layout/market-summary.ts +23 -9
- package/src/components/layout/pane/content.tsx +46 -29
- package/src/components/layout/pane/cycle-order.ts +2 -11
- package/src/components/layout/pane/header.tsx +2 -1
- package/src/components/layout/pane/title.ts +1 -1
- package/src/components/layout/presentation.ts +22 -0
- package/src/components/layout/shell/active-drag.ts +67 -35
- package/src/components/layout/shell/cursor-occlusion.ts +6 -5
- package/src/components/layout/shell/drag/index.ts +16 -7
- package/src/components/layout/shell/drag/live.ts +193 -0
- package/src/components/layout/shell/drag/overlays.tsx +179 -35
- package/src/components/layout/shell/drag/runtime.ts +62 -50
- package/src/components/layout/shell/drag/slide.ts +10 -0
- package/src/components/layout/shell/index.tsx +84 -57
- package/src/components/layout/shell/layout-state.ts +5 -11
- package/src/components/layout/shell/menu.ts +28 -1
- package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
- package/src/components/layout/shell/native/surfaces.ts +30 -14
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +461 -180
- 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 +18 -18
- package/src/components/layout/shell/visible-layout.ts +44 -1
- package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
- package/src/components/layout/status-bar.tsx +4 -1
- package/src/components/markdown-text.tsx +9 -5
- package/src/components/metric-treemap/heat-surface.tsx +27 -8
- package/src/components/metric-treemap/heat-tiles.ts +48 -17
- package/src/components/onboarding/onboarding-frame.tsx +33 -10
- package/src/components/onboarding/onboarding-wizard.tsx +49 -40
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
- package/src/components/onboarding/pro-step-layout.ts +86 -0
- package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
- package/src/components/ui/button.tsx +10 -0
- package/src/components/ui/data-table/index.tsx +38 -0
- package/src/components/ui/data-table/types.ts +8 -1
- package/src/components/ui/data-table/visible-range.ts +2 -0
- package/src/components/ui/icon.tsx +11 -2
- package/src/components/ui/index.ts +1 -1
- package/src/core/connection-health.ts +1 -1
- package/src/core/state/app/layout-reducer.ts +89 -3
- package/src/core/state/app/state.ts +8 -5
- package/src/core/state/app/types.ts +13 -2
- package/src/data/config/layout.ts +55 -21
- package/src/data/config/store/fold-panes.ts +149 -0
- package/src/data/config/store/migrations.ts +7 -0
- package/src/data/config/store/node.ts +27 -6
- package/src/data/config/store/normalize.ts +37 -3
- package/src/data/json-persistence.ts +17 -3
- package/src/data/json-storage.ts +2 -0
- package/src/data/memory-resource-store.ts +46 -0
- package/src/i18n/es.ts +14 -1
- package/src/i18n/ja.ts +14 -1
- package/src/i18n/ko.ts +14 -1
- package/src/i18n/zh-cn.ts +14 -1
- package/src/i18n/zh-tw.ts +14 -1
- package/src/layout/desks.ts +1 -2
- package/src/layout/pane-layout-move.ts +264 -0
- package/src/layout/pane-manager/hidden-panes.ts +160 -0
- package/src/layout/pane-manager/index.ts +1 -0
- package/src/market-data/coordinator/entries.ts +7 -1
- package/src/market-data/coordinator/quotes.ts +2 -2
- package/src/market-data/market/freshness.ts +156 -11
- package/src/market-data/market/status.ts +40 -0
- package/src/market-data/options-alternatives.ts +69 -0
- package/src/market-data/quotes/contributions.ts +19 -2
- package/src/market-data/quotes/freshness.ts +5 -0
- package/src/market-data/quotes/metadata.ts +16 -0
- package/src/market-data/quotes/screener-live-quotes.ts +56 -5
- package/src/plugins/builtin/account-management/keyboard.ts +3 -0
- package/src/plugins/builtin/account-management/model.ts +1 -0
- package/src/plugins/builtin/account-management/navigation.ts +1 -1
- package/src/plugins/builtin/account-management/pane.tsx +31 -4
- package/src/plugins/builtin/alerts/quotes.ts +3 -0
- package/src/plugins/builtin/analytics/pane-model.ts +10 -6
- package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
- package/src/plugins/builtin/analytics/risk-model.ts +37 -8
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
- package/src/plugins/builtin/attention/pane.tsx +1 -1
- package/src/plugins/builtin/backtest/pane.tsx +1 -1
- package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/broker-manager/model.ts +8 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
- package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
- package/src/plugins/builtin/broker-manager/table.ts +5 -0
- package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
- package/src/plugins/builtin/catalysts/detail.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
- package/src/plugins/builtin/chat/channels.ts +34 -36
- package/src/plugins/builtin/chat/command-bar.ts +138 -0
- package/src/plugins/builtin/chat/composer-commands.ts +21 -1
- package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
- package/src/plugins/builtin/chat/content/footer.tsx +5 -3
- package/src/plugins/builtin/chat/content/index.tsx +21 -1
- package/src/plugins/builtin/chat/content/mentions.ts +2 -1
- package/src/plugins/builtin/chat/content/user-map.ts +3 -1
- package/src/plugins/builtin/chat/controller/index.ts +48 -0
- package/src/plugins/builtin/chat/conversations.ts +256 -0
- package/src/plugins/builtin/chat/discord-command.ts +91 -0
- package/src/plugins/builtin/chat/edit-window.ts +12 -6
- package/src/plugins/builtin/chat/ghost-user.ts +14 -0
- package/src/plugins/builtin/chat/index.tsx +40 -2
- package/src/plugins/builtin/chat/layout.ts +2 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
- package/src/plugins/builtin/chat/message/header.tsx +24 -15
- package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
- package/src/plugins/builtin/chat/message/render-state.ts +3 -0
- package/src/plugins/builtin/chat/pane-requests.ts +60 -0
- package/src/plugins/builtin/chat/pane-state.ts +32 -2
- package/src/plugins/builtin/chat/pane.tsx +8 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
- package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
- package/src/plugins/builtin/cloud/askg/model.ts +129 -70
- package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
- package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
- package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
- package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
- package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
- package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
- package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
- package/src/plugins/builtin/cloud/plugin.tsx +36 -1
- package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
- package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
- package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
- package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
- package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
- package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
- package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
- package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
- package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
- package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
- package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
- package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
- package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
- package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
- package/src/plugins/builtin/comment-letters/client.ts +1 -3
- package/src/plugins/builtin/company-attention/pane.tsx +1 -1
- package/src/plugins/builtin/company-kpis/client.ts +23 -8
- package/src/plugins/builtin/company-kpis/headless.ts +3 -1
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
- package/src/plugins/builtin/composite-plugins.ts +107 -64
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
- package/src/plugins/builtin/credit-boards/index.tsx +21 -21
- package/src/plugins/builtin/credit-boards/model.ts +0 -1
- package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
- package/src/plugins/builtin/debt-maturities/client.ts +37 -9
- package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/data.ts +28 -7
- package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
- package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
- package/src/plugins/builtin/econ/calendar-model.ts +42 -17
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/index.tsx +68 -22
- package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +17 -0
- package/src/plugins/builtin/executives/data.ts +17 -6
- package/src/plugins/builtin/executives/pane.tsx +16 -7
- package/src/plugins/builtin/filing-events/pane.tsx +22 -5
- package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
- package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
- package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
- package/src/plugins/builtin/gpu/headless.ts +9 -3
- package/src/plugins/builtin/gpu/model.ts +14 -5
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
- package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
- package/src/plugins/builtin/holders/client.ts +3 -6
- package/src/plugins/builtin/holders/format.ts +31 -1
- package/src/plugins/builtin/holders/headless.ts +32 -6
- package/src/plugins/builtin/holders/pane.tsx +25 -13
- package/src/plugins/builtin/holders/report-header.ts +46 -5
- package/src/plugins/builtin/insider/index.tsx +4 -4
- package/src/plugins/builtin/iv-history/charts.tsx +21 -7
- package/src/plugins/builtin/iv-history/client.ts +12 -2
- package/src/plugins/builtin/iv-history/format.ts +33 -2
- package/src/plugins/builtin/iv-history/headless.ts +22 -10
- package/src/plugins/builtin/iv-history/index.tsx +3 -3
- package/src/plugins/builtin/iv-history/model.ts +61 -13
- package/src/plugins/builtin/iv-history/pane.tsx +11 -3
- package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
- package/src/plugins/builtin/jobs/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
- package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +121 -45
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
- package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
- package/src/plugins/builtin/layout-manager/index.tsx +9 -1
- package/src/plugins/builtin/macro-day/client.ts +24 -0
- package/src/plugins/builtin/macro-day/headless.ts +11 -8
- package/src/plugins/builtin/macro-day/index.tsx +3 -0
- package/src/plugins/builtin/macro-day/model.ts +11 -6
- package/src/plugins/builtin/macro-day/pane.tsx +12 -8
- package/src/plugins/builtin/macro-day/release-table.ts +40 -0
- package/src/plugins/builtin/macro-day/releases.ts +62 -44
- package/src/plugins/builtin/market-heatmap/data.ts +25 -5
- package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
- package/src/plugins/builtin/market-heatmap/model.ts +94 -3
- package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
- package/src/plugins/builtin/market-movers/headless.ts +31 -4
- package/src/plugins/builtin/market-movers/index.tsx +8 -1
- package/src/plugins/builtin/market-movers/model.ts +71 -17
- package/src/plugins/builtin/market-movers/table.ts +50 -23
- package/src/plugins/builtin/members/pane.tsx +10 -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/openfda/client.ts +1 -1
- package/src/plugins/builtin/options/carry.ts +95 -0
- package/src/plugins/builtin/options/index.tsx +58 -5
- package/src/plugins/builtin/options/strike-window.ts +183 -0
- package/src/plugins/builtin/options/table.ts +55 -3
- package/src/plugins/builtin/options/types.ts +8 -1
- package/src/plugins/builtin/options/view.tsx +161 -12
- package/src/plugins/builtin/options-positioning/headless.ts +4 -2
- package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
- package/src/plugins/builtin/options-scenario/client.ts +73 -17
- package/src/plugins/builtin/options-scenario/headless.ts +71 -24
- package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
- package/src/plugins/builtin/options-scenario/index.tsx +9 -2
- package/src/plugins/builtin/options-scenario/model.ts +33 -0
- package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
- package/src/plugins/builtin/pe-band/model.ts +2 -2
- package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
- package/src/plugins/builtin/perps/equity-row.tsx +8 -5
- package/src/plugins/builtin/perps/model.ts +16 -5
- package/src/plugins/builtin/perps/pane.tsx +1 -1
- package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
- package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
- package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
- package/src/plugins/builtin/plugin-module.ts +87 -4
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
- package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
- package/src/plugins/builtin/portfolio-list/cli/render.ts +89 -18
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
- package/src/plugins/builtin/portfolio-list/headless.ts +40 -7
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
- package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
- package/src/plugins/builtin/realized-vol/headless.ts +3 -0
- package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +7 -3
- 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/model.ts +2 -1
- package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
- package/src/plugins/builtin/risk-factors/data.ts +23 -9
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
- package/src/plugins/builtin/seasonality/pane.tsx +1 -1
- package/src/plugins/builtin/sec/index.tsx +5 -5
- package/src/plugins/builtin/sectors/client.ts +10 -2
- package/src/plugins/builtin/sectors/headless.ts +35 -6
- package/src/plugins/builtin/sectors/index.tsx +18 -1
- package/src/plugins/builtin/sectors/sector-model.ts +150 -41
- package/src/plugins/builtin/shared/command-tab.ts +51 -0
- package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
- package/src/plugins/builtin/shared/members-menu.tsx +260 -0
- package/src/plugins/builtin/shared/report-freshness.ts +50 -2
- package/src/plugins/builtin/shared/ticker-request.ts +25 -1
- package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
- package/src/plugins/builtin/shared/volatility/options.ts +1 -46
- package/src/plugins/builtin/short-interest/index.tsx +19 -4
- package/src/plugins/builtin/short-interest/surface.tsx +4 -4
- package/src/plugins/builtin/short-volume/index.tsx +1 -10
- package/src/plugins/builtin/short-volume/pane.tsx +1 -1
- package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
- package/src/plugins/builtin/supply-chain/model.ts +1 -1
- package/src/plugins/builtin/themes/pane.tsx +3 -0
- package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
- package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
- package/src/plugins/builtin/ticker-detail/headless.ts +63 -25
- package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
- package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
- package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
- package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
- package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
- package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
- package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
- package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
- package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
- package/src/plugins/builtin/vol-surface/client.ts +6 -2
- package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
- package/src/plugins/builtin/vol-surface/headless.ts +124 -20
- package/src/plugins/builtin/vol-surface/model.ts +23 -11
- package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
- package/src/plugins/builtin/vol-surface/tables.ts +124 -0
- package/src/plugins/builtin/world-indices/client.ts +22 -4
- package/src/plugins/builtin/world-indices/headless.ts +54 -27
- package/src/plugins/builtin/world-indices/model.ts +4 -2
- package/src/plugins/builtin/world-indices/table.tsx +13 -2
- package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +2 -0
- package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
- package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
- package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
- package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -6
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
- package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
- package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
- package/src/plugins/builtin/world-venue-map/model.ts +16 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/builtin-editorial.ts +126 -0
- package/src/plugins/catalog-backend.ts +16 -9
- package/src/plugins/catalog-browser.ts +127 -63
- package/src/plugins/catalog-manifest.ts +73 -40
- package/src/plugins/catalog-ui.ts +26 -6
- package/src/plugins/extracted-plugins.ts +7 -1
- package/src/plugins/ownership.ts +150 -7
- package/src/plugins/plugin-functions.ts +54 -0
- package/src/plugins/plugin-off.ts +36 -0
- package/src/plugins/registry/host-actions.ts +16 -0
- package/src/plugins/registry/index.ts +84 -24
- package/src/plugins/registry/plugin-state.ts +25 -1
- package/src/plugins/runtime/context.tsx +29 -0
- package/src/react/input.ts +48 -1
- package/src/remote/app-host.tsx +5 -0
- package/src/remote/controller.ts +3 -53
- package/src/remote/matching.ts +69 -0
- package/src/remote/schema.ts +1 -1
- package/src/remote/semantic-tree.tsx +14 -0
- package/src/remote/types.ts +2 -0
- package/src/renderers/browser/app-services.ts +3 -1
- package/src/renderers/dom/data-table/dom.ts +31 -0
- package/src/renderers/dom/data-table/index.tsx +5 -0
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/query-bar.tsx +59 -32
- package/src/renderers/dom/host/box.tsx +113 -17
- package/src/renderers/dom/host/drag-shield.ts +35 -0
- package/src/renderers/dom/host/live-frame.ts +62 -0
- package/src/renderers/dom/host/mouse.ts +8 -1
- package/src/renderers/dom/host/scroll-box.tsx +6 -5
- package/src/renderers/dom/host/tabs.tsx +3 -2
- package/src/renderers/dom/input-host.tsx +6 -1
- package/src/renderers/dom/styles.css +26 -5
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +15 -1
- package/src/renderers/electrobun/shared/protocol.ts +12 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +4 -1
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
- package/src/renderers/electrobun/view/host/native.ts +0 -10
- package/src/renderers/electrobun/view/host/window-move.ts +28 -0
- package/src/renderers/electrobun/view/main.tsx +4 -1
- package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
- package/src/renderers/electrobun/view/ui-host.tsx +2 -5
- package/src/renderers/opentui/input-host.tsx +6 -1
- package/src/renderers/opentui/start.tsx +9 -0
- package/src/renderers/opentui/ui-host.tsx +7 -1
- package/src/shares/portable-layout.ts +16 -6
- package/src/sources/gloomberb-cloud/index.ts +19 -10
- package/src/sources/gloomberb-cloud/normalizers.ts +20 -21
- package/src/sources/listing-symbols.ts +11 -5
- package/src/sources/provider-errors.ts +88 -3
- package/src/sources/provider-router/batches.ts +3 -3
- package/src/sources/provider-router/cached-routes.ts +8 -4
- package/src/sources/provider-router/financial-routes.ts +13 -7
- package/src/sources/provider-router/financials.ts +106 -16
- package/src/sources/provider-router/history.ts +27 -11
- package/src/sources/provider-router/index.ts +1 -1
- package/src/sources/provider-router/primary.ts +24 -6
- package/src/sources/sec-edgar.ts +68 -8
- package/src/state/pane-scroll-registry.tsx +67 -35
- package/src/sync/core-contributors.ts +4 -3
- package/src/test-support/cli-context.ts +2 -1
- package/src/test-support/data-provider.ts +34 -0
- package/src/test-support/margin-account.ts +108 -0
- package/src/test-support/shell-pane-keys.tsx +1 -0
- package/src/theme/series-colors.ts +46 -0
- package/src/theme/themes.ts +149 -0
- package/src/tickers/search/index.ts +22 -2
- package/src/tickers/search/ranking.ts +45 -1
- package/src/tickers/search/types.ts +3 -1
- package/src/time-series/resolution.ts +15 -0
- package/src/time-series/resolve.ts +1 -14
- package/src/types/config.ts +61 -1
- package/src/types/financials.ts +32 -3
- package/src/types/headless.ts +11 -0
- package/src/types/instrument.ts +8 -0
- package/src/types/plugin.ts +49 -3
- package/src/ui/host.tsx +16 -0
- package/src/ui/index.tsx +10 -3
- package/src/ui/scroll-box-axis.ts +26 -0
- package/src/utils/cli-output.ts +14 -1
- package/src/utils/currency-units.ts +8 -3
- package/src/utils/depositary-receipt.ts +54 -10
- package/src/utils/display-number.ts +63 -0
- package/src/utils/exchanges.ts +13 -0
- package/src/utils/format.ts +2 -1
- package/src/utils/fundamentals.ts +17 -1
- package/src/utils/fx-market-hours.ts +34 -0
- package/src/utils/option-expiry.ts +126 -0
- package/src/utils/price-history-integrity.ts +19 -6
- package/src/utils/price-history.ts +39 -6
- package/src/utils/rpc-timeout-error.ts +6 -2
- package/src/utils/sec-eps-basis.ts +159 -30
- package/src/utils/sec.ts +38 -34
- package/src/utils/throttled-fetch.ts +51 -22
|
@@ -7,14 +7,23 @@ import type {
|
|
|
7
7
|
CorporateActionsData,
|
|
8
8
|
HolderData,
|
|
9
9
|
OptionsChain,
|
|
10
|
+
Quote,
|
|
10
11
|
TickerFinancials,
|
|
11
12
|
} from "../../types/financials";
|
|
12
13
|
import { currencyMinorDigits, formatMarketPrice, formatMarketPriceWithCurrency, quoteFormatOptions } from "../../market-data/market/format";
|
|
13
|
-
import {
|
|
14
|
-
|
|
14
|
+
import {
|
|
15
|
+
EXTENDED_SESSION_LABELS,
|
|
16
|
+
getExtendedSessionDisplay,
|
|
17
|
+
getQuoteSessionFields,
|
|
18
|
+
getRegularSessionDisplay,
|
|
19
|
+
marketStateLabel,
|
|
20
|
+
type ExtendedSession,
|
|
21
|
+
} from "../../market-data/market/status";
|
|
22
|
+
import { formatCompact, formatDistributionAmount, formatPercent, formatPercentRaw } from "../../utils/format";
|
|
15
23
|
import { withCliServices, withMarketData } from "../context";
|
|
16
|
-
import { isoDate, parsePositiveInt,
|
|
17
|
-
import {
|
|
24
|
+
import { isoDate, parsePositiveInt, rejectExtraArgs, requireOneArg, takeFlag, takeOption } from "./command-utils";
|
|
25
|
+
import type { BuiltinCliCommandDef } from "../command-options";
|
|
26
|
+
import { CLI_COMMAND_GROUPS, TABLE_SECTION_OPTION } from "../help";
|
|
18
27
|
import {
|
|
19
28
|
formatChangePercentCell,
|
|
20
29
|
formatCountCell,
|
|
@@ -31,22 +40,59 @@ import {
|
|
|
31
40
|
recommendationTotal,
|
|
32
41
|
targetUpside,
|
|
33
42
|
} from "../../plugins/builtin/research/analyst-model";
|
|
34
|
-
import { optionQuoteSide } from "../../plugins/builtin/options/market-reference";
|
|
35
43
|
import { getPublishedUsEquityCalendarYears, getPublishedUsEquitySession } from "../../market-data/published-us-sessions";
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
44
|
+
import { isQuoteStaleForCurrentSession } from "../../market-data/quotes/freshness";
|
|
45
|
+
import { isOptionsUnavailableError, listedOptionsAlternative, optionsUnavailableTitle } from "../../market-data/options-alternatives";
|
|
46
|
+
import { fundamentalsReportTables, renderFundamentalsReport } from "./ticker";
|
|
47
|
+
import {
|
|
48
|
+
chainHasExpiry,
|
|
49
|
+
chainWithModelFigures,
|
|
50
|
+
formatExpiryList,
|
|
51
|
+
loadOptionModelInputs,
|
|
52
|
+
formatOptionDeltaCell,
|
|
53
|
+
formatOptionIvCell,
|
|
54
|
+
formatOptionQuoteCell,
|
|
55
|
+
missingExpiryMessage,
|
|
56
|
+
OPTION_MODEL_RATE,
|
|
57
|
+
OPTIONS_USAGE,
|
|
58
|
+
optionRows,
|
|
59
|
+
parseOptionExpiration,
|
|
60
|
+
} from "./options-chain";
|
|
61
|
+
import { DEFAULT_LEAPS_CRITERIA, LEAPS_USAGE, parseLeapsCriteria, runLeapsScreen, takeLeapsOptions } from "./options-leaps";
|
|
62
|
+
import type { CliResultColumn } from "../result";
|
|
63
|
+
import {
|
|
64
|
+
exportRowsTable,
|
|
65
|
+
reportFooterLines,
|
|
66
|
+
selectReportTables,
|
|
67
|
+
type CliReportTables,
|
|
68
|
+
} from "../report-tables";
|
|
69
|
+
import {
|
|
70
|
+
historyFacts,
|
|
71
|
+
historyFlagNote,
|
|
72
|
+
historyAllRangeYears,
|
|
73
|
+
historyIntervalsByRange,
|
|
74
|
+
historyNotes,
|
|
75
|
+
historyPriceDecimals,
|
|
76
|
+
historyRows,
|
|
77
|
+
historyUnit,
|
|
78
|
+
} from "../history-rows";
|
|
38
79
|
import { CRYPTO_BOARD_HINT, quoteNotes } from "./crypto-hints";
|
|
39
80
|
import { formatUtcTime } from "../../utils/utc-time";
|
|
81
|
+
import { isFiniteNumber } from "../../utils/guards";
|
|
40
82
|
import { exportedFundamentals } from "../../utils/price-earnings";
|
|
41
83
|
import { fundamentalsFreshness, quotesFreshness, rowsFreshness } from "../freshness";
|
|
42
84
|
import {
|
|
43
85
|
barHistoryFreshness,
|
|
44
86
|
barResolutionFromDates,
|
|
87
|
+
cloudNewsFreshness,
|
|
88
|
+
cloudRealtimeAccess,
|
|
45
89
|
REPORTED_DATA,
|
|
46
90
|
SEC_FILINGS,
|
|
47
91
|
} from "../../plugins/builtin/shared/report-freshness";
|
|
48
92
|
import {
|
|
93
|
+
bareListingNote,
|
|
49
94
|
EXCHANGE_OPTION,
|
|
95
|
+
isNoProviderError,
|
|
50
96
|
listingHeading,
|
|
51
97
|
listingIdentity,
|
|
52
98
|
listingTitle,
|
|
@@ -54,22 +100,32 @@ import {
|
|
|
54
100
|
loadForListing,
|
|
55
101
|
loadListingQuote,
|
|
56
102
|
notTradedMessage,
|
|
103
|
+
quoteBareListing,
|
|
57
104
|
requireCliListing,
|
|
58
105
|
type CliListing,
|
|
106
|
+
type ListingDataRequest,
|
|
59
107
|
type ListingIdentity,
|
|
60
108
|
} from "../listing-arg";
|
|
109
|
+
import { indexRootAlsoLine, indexRootFor, indexRootTryLine } from "../index-roots";
|
|
110
|
+
import { providerMissReason } from "../../sources/provider-errors";
|
|
111
|
+
import { isNotATickerMessage } from "../not-a-ticker";
|
|
61
112
|
import { secRegistrantMismatchMessage, SecRegistrantMismatchError, areDifferentCompanies } from "../../sources/sec-registrant";
|
|
62
113
|
import { isCryptoPairSymbol } from "../../utils/crypto-pair";
|
|
63
114
|
import { isUsListingExchange } from "../../utils/exchanges";
|
|
64
115
|
import { nonUsSecListingVenue } from "../../utils/sec";
|
|
65
116
|
import type { MarketContext } from "../types";
|
|
117
|
+
import { windowRows } from "../row-window";
|
|
118
|
+
import { crossRate, describeFxRate, isUsableRate, parseFxRequest } from "../fx-pair";
|
|
66
119
|
import {
|
|
67
120
|
holderListFacts,
|
|
121
|
+
holderShareBasisMarker,
|
|
122
|
+
holderShareBasisNote,
|
|
68
123
|
holderValueBasis,
|
|
69
124
|
moneyColumnHeader,
|
|
70
125
|
nonUsHolderCaveat,
|
|
71
126
|
sharedReportDate,
|
|
72
127
|
} from "../../plugins/builtin/holders/report-header";
|
|
128
|
+
import { formatHolderOwnershipPercent, holderValueCurrency } from "../../plugins/builtin/holders/format";
|
|
73
129
|
import { fetchBeneficialOwners } from "../../plugins/builtin/holders/beneficial-client";
|
|
74
130
|
import { filingFormMatches, SEC_FILING_FETCH_LIMIT } from "../../plugins/builtin/sec/forms";
|
|
75
131
|
import {
|
|
@@ -117,28 +173,67 @@ function listingMetadata(identity: ListingIdentity) {
|
|
|
117
173
|
return { symbol: identity.symbol, exchange: identity.exchange || null, name: identity.name };
|
|
118
174
|
}
|
|
119
175
|
|
|
176
|
+
// Fitted to a narrow width, the columns marked optional go first (higher dropPriority first), then Name is cut short.
|
|
177
|
+
// What a user types or reads the price by stays whole.
|
|
120
178
|
const QUOTE_LEAD_COLUMNS = [
|
|
121
|
-
{ key: "symbol", header: "Symbol" },
|
|
179
|
+
{ key: "symbol", header: "Symbol", shrink: false },
|
|
122
180
|
{ key: "name", header: "Name" },
|
|
123
181
|
{
|
|
124
182
|
key: "price",
|
|
125
183
|
header: "Last",
|
|
126
184
|
align: "right" as const,
|
|
127
|
-
format: (value: unknown, row:
|
|
128
|
-
row.error && !value
|
|
185
|
+
format: (value: unknown, row: QuoteRow) => (
|
|
186
|
+
row.error && !value
|
|
187
|
+
// A typo is not an outage: say what is wrong with the symbol itself.
|
|
188
|
+
? isNotATickerMessage(row.error) ? cliStyles.warning("not a ticker") : cliStyles.danger("unavailable")
|
|
189
|
+
: String(value ?? "")
|
|
129
190
|
),
|
|
130
191
|
},
|
|
131
192
|
{ key: "changePercent", header: "Chg%", align: "right" as const, format: formatChangePercentCell },
|
|
132
|
-
|
|
193
|
+
// Text drops a column no row fills, so a table without an extended print stays as narrow as before.
|
|
194
|
+
extendedColumn("PRE"),
|
|
195
|
+
extendedColumn("POST"),
|
|
196
|
+
{ key: "session", header: "Session", shrink: false },
|
|
133
197
|
];
|
|
134
198
|
|
|
199
|
+
type QuoteRow = ReturnType<typeof quoteRows>[number];
|
|
200
|
+
|
|
201
|
+
/** The pre-market or after-hours print and its move from the regular close, in its own column. */
|
|
202
|
+
function extendedColumn(session: ExtendedSession) {
|
|
203
|
+
return {
|
|
204
|
+
key: session === "PRE" ? "preMarket" : "afterHours",
|
|
205
|
+
header: EXTENDED_SESSION_LABELS[session],
|
|
206
|
+
align: "right" as const,
|
|
207
|
+
value: (row: QuoteRow) => row.extendedSession === session && row.extendedPrice
|
|
208
|
+
? [row.extendedPrice, row.extendedChangePercent == null ? "" : formatPercentRaw(row.extendedChangePercent)].join(" ").trim()
|
|
209
|
+
: "",
|
|
210
|
+
format: (value: unknown, row: QuoteRow) => (
|
|
211
|
+
value ? [row.extendedPrice, formatChangePercentCell(row.extendedChangePercent)].join(" ").trim() : ""
|
|
212
|
+
),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** A percent to two decimals, as the table prints it. */
|
|
217
|
+
function roundedPercent(value: number | undefined): number | null {
|
|
218
|
+
return value == null ? null : Number(value.toFixed(2));
|
|
219
|
+
}
|
|
220
|
+
|
|
135
221
|
function quoteColumns() {
|
|
136
222
|
return [
|
|
137
223
|
...QUOTE_LEAD_COLUMNS,
|
|
138
|
-
{ key: "currency", header: "Cur" },
|
|
139
|
-
// Whether the price is real-time or
|
|
140
|
-
|
|
141
|
-
{
|
|
224
|
+
{ key: "currency", header: "Cur", shrink: false, optional: true, dropPriority: 1 },
|
|
225
|
+
// Whether the price is real-time, delayed or stale: a feed state, not where it came from. A stale
|
|
226
|
+
// row says so here, so the closing line's "N of M stale" always points at rows the table marks.
|
|
227
|
+
{
|
|
228
|
+
key: "feed",
|
|
229
|
+
header: "Feed",
|
|
230
|
+
shrink: false,
|
|
231
|
+
optional: true,
|
|
232
|
+
dropPriority: 2,
|
|
233
|
+
format: (value: unknown) => value === "stale" ? cliStyles.warning(value) : String(value ?? ""),
|
|
234
|
+
},
|
|
235
|
+
// In a narrow terminal the closing line's as-of stands in for each row's.
|
|
236
|
+
{ key: "updatedAt", header: "Updated", shrink: false, optional: true, dropPriority: 3 },
|
|
142
237
|
];
|
|
143
238
|
}
|
|
144
239
|
|
|
@@ -146,9 +241,10 @@ function compareColumns() {
|
|
|
146
241
|
return [
|
|
147
242
|
...QUOTE_LEAD_COLUMNS,
|
|
148
243
|
{ key: "previousClose", header: "Prev Close", align: "right" as const },
|
|
149
|
-
|
|
150
|
-
{ key: "
|
|
151
|
-
{ key: "
|
|
244
|
+
// A narrow terminal drops these before it cuts the names short.
|
|
245
|
+
{ key: "dayRange", header: "Day Range", align: "right" as const, optional: true, dropPriority: 1 },
|
|
246
|
+
{ key: "volume", header: "Volume", align: "right" as const, format: formatCountCell, optional: true, dropPriority: 2 },
|
|
247
|
+
{ key: "currency", header: "Cur", shrink: false, optional: true, dropPriority: 3 },
|
|
152
248
|
];
|
|
153
249
|
}
|
|
154
250
|
|
|
@@ -157,11 +253,20 @@ function errorMessage(error: unknown): string | null {
|
|
|
157
253
|
return error instanceof Error ? error.message : String(error);
|
|
158
254
|
}
|
|
159
255
|
|
|
160
|
-
|
|
256
|
+
/** What the Feed cell says: stale wins over the feed's own live or delayed, as the closing line counts it. */
|
|
257
|
+
function quoteFeedState(quote: QuoteCliRecord["quote"], now: number): "stale" | "live" | "delayed" | "" {
|
|
258
|
+
if (!quote) return "";
|
|
259
|
+
if (isQuoteStaleForCurrentSession(quote, now)) return "stale";
|
|
260
|
+
return quote.dataSource === "live" || quote.dataSource === "delayed" ? quote.dataSource : "";
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function quoteRows(results: QuoteCliRecord[], now = Date.now()) {
|
|
161
264
|
return results.map((result) => {
|
|
162
265
|
const quote = result.quote;
|
|
163
|
-
//
|
|
164
|
-
|
|
266
|
+
// Last and Chg% are the regular session, as `ticker` and the quote monitor read it; a
|
|
267
|
+
// pre-market or after-hours print is its own column, measured from that session's close.
|
|
268
|
+
const display = getRegularSessionDisplay(quote);
|
|
269
|
+
const extended = getExtendedSessionDisplay(quote);
|
|
165
270
|
// An index level is in points, not in the currency its members trade in.
|
|
166
271
|
const indexPoints = quote?.instrumentType?.trim().toUpperCase() === "INDEX";
|
|
167
272
|
// Pad to two decimals so a column lines up, but never past the currency's minor unit (JPY has none).
|
|
@@ -183,9 +288,14 @@ function quoteRows(results: QuoteCliRecord[]) {
|
|
|
183
288
|
priceBasis: quote?.priceBasis ?? null,
|
|
184
289
|
instrumentType: quote?.instrumentType ?? null,
|
|
185
290
|
change: display?.change ?? null,
|
|
186
|
-
changePercent: display?.changePercent
|
|
291
|
+
changePercent: roundedPercent(display?.changePercent),
|
|
292
|
+
extendedSession: extended?.session ?? null,
|
|
293
|
+
extendedPrice: price(extended?.price),
|
|
294
|
+
rawExtendedPrice: extended?.price ?? null,
|
|
295
|
+
extendedChange: extended?.change ?? null,
|
|
296
|
+
extendedChangePercent: roundedPercent(extended?.changePercent),
|
|
187
297
|
session: quote?.marketState ? marketStateLabel(quote.marketState) : "",
|
|
188
|
-
// The close the shown move is measured from
|
|
298
|
+
// The close the shown move is measured from: the one before the session Last is.
|
|
189
299
|
previousClose: price(display?.change != null ? display.price - display.change : quote?.previousClose),
|
|
190
300
|
dayRange: quote?.low != null && quote.high != null
|
|
191
301
|
? indexPoints ? `${price(quote.low)}-${price(quote.high)}` : formatPriceRange(quote.low, quote.high, quote.currency, options, "-")
|
|
@@ -194,6 +304,7 @@ function quoteRows(results: QuoteCliRecord[]) {
|
|
|
194
304
|
currency: quote?.currency ?? "",
|
|
195
305
|
providerId: quote?.providerId ?? "",
|
|
196
306
|
source: quote?.dataSource ?? quote?.providerId ?? "",
|
|
307
|
+
feed: quoteFeedState(quote, now),
|
|
197
308
|
updatedAt: quote?.lastUpdated ? new Date(quote.lastUpdated).toISOString() : "",
|
|
198
309
|
error: result.error ?? "",
|
|
199
310
|
};
|
|
@@ -250,6 +361,7 @@ function holderRows(data: HolderData, ownerTypes?: Set<string>) {
|
|
|
250
361
|
value: holder.value ?? null,
|
|
251
362
|
percentHeld: holder.percentHeld ?? null,
|
|
252
363
|
changeShares: holder.changeShares ?? null,
|
|
364
|
+
shareBasis: holder.shareBasis ?? null,
|
|
253
365
|
}));
|
|
254
366
|
}
|
|
255
367
|
|
|
@@ -323,22 +435,6 @@ function corporateActionRows(data: CorporateActionsData) {
|
|
|
323
435
|
].sort((left, right) => right.date.localeCompare(left.date));
|
|
324
436
|
}
|
|
325
437
|
|
|
326
|
-
function optionRows(chain: OptionsChain) {
|
|
327
|
-
return [...chain.calls.map((contract) => ({ side: "call", ...contract })), ...chain.puts.map((contract) => ({ side: "put", ...contract }))]
|
|
328
|
-
.map((contract) => ({
|
|
329
|
-
side: contract.side,
|
|
330
|
-
contract: contract.contractSymbol,
|
|
331
|
-
strike: contract.strike,
|
|
332
|
-
last: contract.lastPrice,
|
|
333
|
-
bid: contract.bid,
|
|
334
|
-
ask: contract.ask,
|
|
335
|
-
volume: contract.volume,
|
|
336
|
-
openInterest: contract.openInterest,
|
|
337
|
-
iv: contract.impliedVolatility,
|
|
338
|
-
expiration: new Date(contract.expiration * 1000).toISOString().slice(0, 10),
|
|
339
|
-
}));
|
|
340
|
-
}
|
|
341
|
-
|
|
342
438
|
/**
|
|
343
439
|
* After a US open, a chain whose latest trade predates it still carries the
|
|
344
440
|
* prior session's quotes and volume. The delayed feed lags the open by about
|
|
@@ -353,11 +449,6 @@ function priorSessionChainWarning(chain: OptionsChain, exchange: string, now: nu
|
|
|
353
449
|
return `No option trades this session yet (last trade ${chain.asOf})`;
|
|
354
450
|
}
|
|
355
451
|
|
|
356
|
-
function formatOptionQuoteCell(row: Record<string, unknown>, side: "bid" | "ask"): string {
|
|
357
|
-
const quote = optionQuoteSide({ bid: Number(row.bid), ask: Number(row.ask) }, side);
|
|
358
|
-
return quote == null ? "—" : String(quote);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
452
|
/** Per-share earnings to the cent, as reported; consensus averages carry more digits. */
|
|
362
453
|
function formatEpsCell(value: unknown): string {
|
|
363
454
|
return typeof value === "number" && Number.isFinite(value) ? value.toFixed(2) : "";
|
|
@@ -390,20 +481,25 @@ async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execut
|
|
|
390
481
|
const targets: QuoteSubscriptionTarget[] = listings.map((listing) => ({ symbol: listing.request.symbol, exchange: listing.request.exchange }));
|
|
391
482
|
const results = await market.dataProvider.getQuotesBatch(targets, { forceRefresh: ctx.cliOptions.refresh });
|
|
392
483
|
const listingOf = (result: QuoteBatchResult, index: number) => listings[targets.indexOf(result.target)] ?? listings[index]!;
|
|
393
|
-
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
))
|
|
397
|
-
if (listings.length === 1 && notTraded[0]) ctx.fail(notTraded[0]);
|
|
484
|
+
const request = { command: commandName, noun: "quote" };
|
|
485
|
+
const answers = await Promise.all(results.map((result, index) => answerQuote(result, listingOf(result, index), market, request, ctx.cliOptions.refresh)));
|
|
486
|
+
const failed = answers[0]?.failure;
|
|
487
|
+
if (listings.length === 1 && failed) ctx.fail(failed.message, failed.details);
|
|
398
488
|
// Each row names its listing by key (SAN:EPA) and company, so the table needs no line above it.
|
|
399
|
-
const data =
|
|
489
|
+
const data = answers.map(({ result, listing, failure }) => ({
|
|
400
490
|
target: result.target,
|
|
401
|
-
listing: listingMetadata(listingIdentity(
|
|
491
|
+
listing: listingMetadata(listingIdentity(listing, result.quote)),
|
|
492
|
+
// The figures the table shows, beside the quote as the source sent it.
|
|
493
|
+
...getQuoteSessionFields(result.quote),
|
|
402
494
|
quote: result.quote,
|
|
403
|
-
error:
|
|
495
|
+
error: failure?.oneLine ?? errorMessage(result.error),
|
|
404
496
|
}));
|
|
405
|
-
|
|
406
|
-
|
|
497
|
+
const notes = [
|
|
498
|
+
// Which listing a bare symbol was read as, and the index a bare root is not, are in no row.
|
|
499
|
+
...answers.flatMap((answer) => answer.notes),
|
|
500
|
+
// Text mode shows only "unavailable" in the cell; the JSON rows already carry each reason.
|
|
501
|
+
...ctx.cliOptions.format === "text" ? quoteNotes(data, { exchange }) : [],
|
|
502
|
+
];
|
|
407
503
|
ctx.printResult({
|
|
408
504
|
data,
|
|
409
505
|
warnings: notes.length > 0 ? notes : undefined,
|
|
@@ -415,17 +511,74 @@ async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execut
|
|
|
415
511
|
});
|
|
416
512
|
}
|
|
417
513
|
|
|
514
|
+
interface QuoteAnswer {
|
|
515
|
+
result: QuoteBatchResult;
|
|
516
|
+
/** The listing the row is: the one asked for, or the venue a bare symbol was quoted on. */
|
|
517
|
+
listing: CliListing;
|
|
518
|
+
/** Why there is no quote, when there is more to say than the source's own error. */
|
|
519
|
+
failure?: { message: string; details?: string; oneLine: string };
|
|
520
|
+
notes: string[];
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* One symbol's row of `quote` and `compare`. A listing on an exchange its symbol
|
|
525
|
+
* is not listed on says so. A bare symbol the data service read on its home
|
|
526
|
+
* listing abroad names that venue (SXR8 -> XETRA). One no source quotes is
|
|
527
|
+
* quoted on its listings (`quoteBareListing`), else names them; a bare index
|
|
528
|
+
* root (VIX) also names its index (^VIX). Venues are only looked up for a
|
|
529
|
+
* symbol with no quote, so a request that succeeds costs no search.
|
|
530
|
+
*/
|
|
531
|
+
async function answerQuote(
|
|
532
|
+
result: QuoteBatchResult,
|
|
533
|
+
listing: CliListing,
|
|
534
|
+
market: MarketContext,
|
|
535
|
+
request: ListingDataRequest,
|
|
536
|
+
refresh: boolean,
|
|
537
|
+
): Promise<QuoteAnswer> {
|
|
538
|
+
const root = listing.exchange ? null : indexRootFor(listing.symbol);
|
|
539
|
+
// A bare root that quoted something else, such as MOVE (Corvex), points at the index too.
|
|
540
|
+
const also = (quote: Quote) => root && quote.instrumentType?.trim().toUpperCase() !== "INDEX"
|
|
541
|
+
? [indexRootAlsoLine(root, request.command)] : [];
|
|
542
|
+
if (result.quote) return { result, listing, notes: [bareListingNote(listing, result.quote) ?? [], also(result.quote)].flat() };
|
|
543
|
+
const notTraded = await notTradedMessage(listing, market);
|
|
544
|
+
if (notTraded) return { result, listing, failure: { message: notTraded, oneLine: notTraded }, notes: [] };
|
|
545
|
+
if (!isNoProviderError(result.error)) return { result, listing, notes: [] };
|
|
546
|
+
const other = await quoteBareListing(listing, market, request, { refresh });
|
|
547
|
+
if (other?.kind === "quoted") {
|
|
548
|
+
return {
|
|
549
|
+
result: { target: other.listing.request, quote: other.quote },
|
|
550
|
+
listing: other.listing,
|
|
551
|
+
notes: [other.note, ...also(other.quote)],
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
const indexLine = root ? indexRootTryLine(root, request.command) : null;
|
|
555
|
+
if (!other && !indexLine) return { result, listing, notes: [] };
|
|
556
|
+
const message = other?.message ?? `${listing.symbol} has no ${request.noun}.`;
|
|
557
|
+
const details = [other?.details, indexLine].filter(Boolean).join("\n");
|
|
558
|
+
const oneLine = [
|
|
559
|
+
message,
|
|
560
|
+
other ? `Other listings: ${other.others.join(", ")}.` : "",
|
|
561
|
+
indexLine ?? "",
|
|
562
|
+
].filter(Boolean).join(" ");
|
|
563
|
+
return { result, listing, failure: { message, details, oneLine }, notes: [] };
|
|
564
|
+
}
|
|
565
|
+
|
|
418
566
|
/** A crypto pair that will not load points at the crypto board. Structured formats keep the error as thrown. */
|
|
419
567
|
function failHistory(error: unknown, symbol: string, ctx: Parameters<CliCommandDef["execute"]>[1]): never {
|
|
420
568
|
if (ctx.cliOptions.format !== "text" || !isCryptoPairSymbol(symbol)) throw error;
|
|
421
569
|
return ctx.fail(errorMessage(error) ?? `No history available for ${symbol}`, CRYPTO_BOARD_HINT);
|
|
422
570
|
}
|
|
423
571
|
|
|
572
|
+
const HISTORY_USAGE = "history <symbol> [--range <range>]";
|
|
573
|
+
const HISTORY_ALL_RANGE = `ALL gives the full history the source has, up to ${historyAllRangeYears()} years`;
|
|
574
|
+
/** Options people reach for to ask for dates, which history answers with a range. */
|
|
575
|
+
const DATE_WINDOW_OPTIONS = new Set(["--from", "--to", "--start", "--end", "--since", "--until", "--date", "--start-date", "--end-date"]);
|
|
576
|
+
|
|
424
577
|
async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
425
578
|
const args = [...rawArgs];
|
|
426
579
|
const range = parseRange(takeOption(args, "--range"), ctx);
|
|
427
580
|
const requestedExchange = takeOption(args, "--exchange");
|
|
428
|
-
const raw =
|
|
581
|
+
const raw = requireOneArg(args, HISTORY_USAGE, "symbol", ctx);
|
|
429
582
|
await withMarketData(ctx, async (market) => {
|
|
430
583
|
const listing = await requireCliListing(raw, requestedExchange, market, ctx);
|
|
431
584
|
const { symbol, exchange } = listing.request;
|
|
@@ -434,12 +587,18 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
434
587
|
? market.dataProvider.getPriceHistoryWithMetadata(symbol, exchange, range, context)
|
|
435
588
|
: market.dataProvider.getPriceHistory(symbol, exchange, range, context).then((points) => ({ points, resolution: null }));
|
|
436
589
|
const [{ points, resolution }, quote] = await Promise.all([
|
|
437
|
-
loadForListing(listing, market, ctx, load, (loaded) => loaded.points.length === 0
|
|
590
|
+
loadForListing(listing, market, ctx, load, (loaded) => loaded.points.length === 0, {
|
|
591
|
+
command: range === "1Y" ? "history" : `history --range ${range}`, noun: "history",
|
|
592
|
+
})
|
|
438
593
|
.catch((error) => failHistory(error, listing.key, ctx)),
|
|
439
594
|
loadListingQuote(market.dataProvider, listing),
|
|
440
595
|
]);
|
|
441
596
|
const identity = listingIdentity(listing, quote);
|
|
442
|
-
|
|
597
|
+
// Without a quote currency, the listing metadata research reads (as `ticker` does) may still state it.
|
|
598
|
+
const listed = quote?.currency ? quote : await market.dataProvider.getQuoteMetadata?.(symbol, exchange).catch(() => null);
|
|
599
|
+
const unit = historyUnit(listing.symbol, listed ?? quote);
|
|
600
|
+
const data = historyRows(points, resolution, unit.currency);
|
|
601
|
+
const interval = data[0]?.interval ?? null;
|
|
443
602
|
const decimals = historyPriceDecimals(data, listing.saved?.metadata.assetCategory);
|
|
444
603
|
const price = (value: unknown) => typeof value === "number" ? value.toFixed(decimals) : "";
|
|
445
604
|
// Intraday bars print in UTC, as the charts and time and sales label them, not the host zone.
|
|
@@ -450,17 +609,48 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
450
609
|
observedKey: "date",
|
|
451
610
|
oldest: null,
|
|
452
611
|
});
|
|
453
|
-
|
|
454
|
-
|
|
612
|
+
const notes = [...historyNotes(range, interval, data[0]?.date), historyFlagNote(data) ?? []].flat();
|
|
613
|
+
const priceColumns: CliResultColumn[] = [
|
|
614
|
+
intraday
|
|
615
|
+
? { key: "date", header: "Time", format: (value) => typeof value === "string" ? formatUtcTime(value) : "" }
|
|
616
|
+
: { key: "date", header: "Date" },
|
|
617
|
+
{ key: "open", header: "Open", align: "right", format: price },
|
|
618
|
+
{ key: "high", header: "High", align: "right", format: price },
|
|
619
|
+
{ key: "low", header: "Low", align: "right", format: price },
|
|
620
|
+
{ key: "close", header: "Close", align: "right", format: price },
|
|
621
|
+
{ key: "volume", header: "Volume", align: "right", format: formatCountCell },
|
|
622
|
+
];
|
|
623
|
+
// Text shows a flag only on a bar that has one; the column is dropped when none does.
|
|
624
|
+
const flagColumn: CliResultColumn = {
|
|
625
|
+
key: "flag", header: "Flag", format: (value) => typeof value === "string" ? cliStyles.warning(value) : "",
|
|
626
|
+
};
|
|
627
|
+
ctx.printResult({
|
|
628
|
+
data,
|
|
629
|
+
metadata: {
|
|
630
|
+
...listingMetadata(identity),
|
|
631
|
+
range,
|
|
632
|
+
resolution,
|
|
633
|
+
requestedRange: range,
|
|
634
|
+
currency: unit.currency,
|
|
635
|
+
unit: unit.unit,
|
|
636
|
+
interval,
|
|
637
|
+
firstDate: data[0]?.date ?? null,
|
|
638
|
+
lastDate: data.at(-1)?.date ?? null,
|
|
639
|
+
bars: data.length,
|
|
640
|
+
asOf: freshness?.asOf ?? null,
|
|
641
|
+
},
|
|
642
|
+
...(notes.length > 0 ? { warnings: notes } : {}),
|
|
643
|
+
freshness,
|
|
644
|
+
}, {
|
|
645
|
+
heading: `${listingHeading(identity)}\n${cliStyles.muted(historyFacts(unit, data).join(" · "))}`,
|
|
646
|
+
dateKey: "date",
|
|
647
|
+
textColumns: [...priceColumns, flagColumn],
|
|
648
|
+
// Every exported row says its currency and bar size, so it survives head and concatenation.
|
|
455
649
|
columns: [
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
{ key: "high", header: "High", align: "right", format: price },
|
|
461
|
-
{ key: "low", header: "Low", align: "right", format: price },
|
|
462
|
-
{ key: "close", header: "Close", align: "right", format: price },
|
|
463
|
-
{ key: "volume", header: "Volume", align: "right", format: formatCountCell },
|
|
650
|
+
...priceColumns,
|
|
651
|
+
{ key: "currency", header: "Currency" },
|
|
652
|
+
{ key: "interval", header: "Interval" },
|
|
653
|
+
flagColumn,
|
|
464
654
|
],
|
|
465
655
|
});
|
|
466
656
|
});
|
|
@@ -468,16 +658,38 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
468
658
|
|
|
469
659
|
type FinancialsView = "statements" | "fundamentals" | "valuation";
|
|
470
660
|
|
|
661
|
+
const STATEMENT_COLUMNS: CliResultColumn<ReturnType<typeof financialStatementRows>[number]>[] = [
|
|
662
|
+
{ key: "date", header: "Date" },
|
|
663
|
+
{ key: "revenue", header: "Revenue", align: "right", format: (value) => value == null ? "" : formatCompact(Number(value)) },
|
|
664
|
+
{ key: "grossProfit", header: "Gross", align: "right", format: (value) => value == null ? "" : formatCompact(Number(value)) },
|
|
665
|
+
{ key: "operatingIncome", header: "Op Inc", align: "right", format: (value) => value == null ? "" : formatCompact(Number(value)) },
|
|
666
|
+
{ key: "netIncome", header: "Net Inc", align: "right", format: (value) => value == null ? "" : formatCompact(Number(value)) },
|
|
667
|
+
{ key: "eps", header: "EPS", align: "right", format: (value) => value == null ? "" : formatPerShareNumber(Number(value)) },
|
|
668
|
+
{ key: "currency", header: "Cur" },
|
|
669
|
+
];
|
|
670
|
+
|
|
471
671
|
async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1], view: FinancialsView) {
|
|
472
672
|
const args = [...rawArgs];
|
|
473
673
|
const exchangeOption = takeOption(args, "--exchange");
|
|
674
|
+
const sectionFlag = args.some((arg) => arg === "--section" || arg.startsWith("--section="));
|
|
675
|
+
const section = takeOption(args, "--section");
|
|
474
676
|
const commandName = view === "statements" ? "financials" : view;
|
|
475
|
-
const
|
|
677
|
+
const tabular = ctx.cliOptions.format === "csv" || ctx.cliOptions.format === "ndjson";
|
|
678
|
+
if (sectionFlag && !tabular) ctx.fail("--section picks one table of --csv or --ndjson output.");
|
|
679
|
+
if (sectionFlag && !section?.trim()) ctx.fail("--section needs a section title or number.");
|
|
680
|
+
const raw = requireOneArg(args, `${commandName} <symbol>`, "symbol", ctx);
|
|
681
|
+
const selectTables = (tables: CliReportTables) => {
|
|
682
|
+
try {
|
|
683
|
+
return selectReportTables(tables, section);
|
|
684
|
+
} catch (error) {
|
|
685
|
+
return ctx.fail(error instanceof Error ? error.message : String(error));
|
|
686
|
+
}
|
|
687
|
+
};
|
|
476
688
|
await withMarketData(ctx, async (market) => {
|
|
477
689
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
478
690
|
const financials = await loadForListing(listing, market, ctx, () => market.dataProvider.getTickerFinancials(
|
|
479
691
|
listing.request.symbol, listing.request.exchange, { cacheMode: ctx.cliOptions.refresh ? "refresh" : "default" },
|
|
480
|
-
));
|
|
692
|
+
), undefined, { command: commandName, noun: commandName === "valuation" ? "valuation data" : commandName });
|
|
481
693
|
const identity = listingIdentity(listing, financials.quote);
|
|
482
694
|
const data: FinancialsCliData = {
|
|
483
695
|
symbol: listing.key,
|
|
@@ -486,13 +698,28 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
486
698
|
...financials,
|
|
487
699
|
};
|
|
488
700
|
if (view !== "statements") {
|
|
701
|
+
const freshness = fundamentalsFreshness(financials);
|
|
489
702
|
ctx.printResult({
|
|
490
703
|
// JSON reads a multiple over a loss as null with its reason, never as a number.
|
|
491
704
|
data: { ...data, fundamentals: exportedFundamentals(data.fundamentals) },
|
|
492
|
-
freshness
|
|
493
|
-
}, {
|
|
705
|
+
freshness,
|
|
706
|
+
}, {
|
|
707
|
+
text: () => renderFundamentalsReport(data, view),
|
|
708
|
+
...(tabular ? { tables: selectTables(fundamentalsReportTables(data, view, freshness)) } : {}),
|
|
709
|
+
});
|
|
494
710
|
return;
|
|
495
711
|
}
|
|
712
|
+
const rows = financialStatementRows(data);
|
|
713
|
+
const freshness = rowsFreshness(rows, {
|
|
714
|
+
...REPORTED_DATA, basis: "financial statements", observedKey: "date", oldest: null,
|
|
715
|
+
});
|
|
716
|
+
// NDJSON keeps the raw statement rows it always wrote; CSV gets the table and its closing lines.
|
|
717
|
+
const tables = tabular
|
|
718
|
+
? selectTables({
|
|
719
|
+
tables: [exportRowsTable("Annual statements", STATEMENT_COLUMNS, rows)],
|
|
720
|
+
footer: reportFooterLines({ freshness }),
|
|
721
|
+
})
|
|
722
|
+
: undefined;
|
|
496
723
|
ctx.printResult({
|
|
497
724
|
data,
|
|
498
725
|
metadata: {
|
|
@@ -503,21 +730,12 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
503
730
|
fundamentals: exportedFundamentals(financials.fundamentals),
|
|
504
731
|
profile: financials.profile,
|
|
505
732
|
},
|
|
506
|
-
freshness
|
|
507
|
-
...REPORTED_DATA, basis: "financial statements", observedKey: "date", oldest: null,
|
|
508
|
-
}),
|
|
733
|
+
freshness,
|
|
509
734
|
}, {
|
|
510
735
|
heading: listingHeading(identity),
|
|
511
|
-
rows:
|
|
512
|
-
columns:
|
|
513
|
-
|
|
514
|
-
{ key: "revenue", header: "Revenue", align: "right", value: (row) => row.revenue == null ? "" : formatCompact(Number(row.revenue)) },
|
|
515
|
-
{ key: "grossProfit", header: "Gross", align: "right", value: (row) => row.grossProfit == null ? "" : formatCompact(Number(row.grossProfit)) },
|
|
516
|
-
{ key: "operatingIncome", header: "Op Inc", align: "right", value: (row) => row.operatingIncome == null ? "" : formatCompact(Number(row.operatingIncome)) },
|
|
517
|
-
{ key: "netIncome", header: "Net Inc", align: "right", value: (row) => row.netIncome == null ? "" : formatCompact(Number(row.netIncome)) },
|
|
518
|
-
{ key: "eps", header: "EPS", align: "right", format: (value) => value == null ? "" : formatPerShareNumber(Number(value)) },
|
|
519
|
-
{ key: "currency", header: "Cur" },
|
|
520
|
-
],
|
|
736
|
+
rows: () => rows,
|
|
737
|
+
columns: STATEMENT_COLUMNS,
|
|
738
|
+
...(tables && ctx.cliOptions.format === "csv" ? { tables } : {}),
|
|
521
739
|
});
|
|
522
740
|
});
|
|
523
741
|
}
|
|
@@ -527,9 +745,11 @@ async function runNews(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
527
745
|
const feed = parseNewsFeed(takeOption(args, "--feed"));
|
|
528
746
|
const exchangeOption = takeOption(args, "--exchange");
|
|
529
747
|
if (exchangeOption && !args[0]) ctx.fail("--exchange needs a symbol: gloomberb news <symbol> --exchange <code>");
|
|
748
|
+
rejectExtraArgs(args, 1, { usage: "news [symbol] [--feed <feed>]", takes: "one symbol at most", advice: "Run it once per symbol." }, ctx);
|
|
530
749
|
await withMarketData(ctx, async (market) => {
|
|
531
750
|
const listing = args[0] ? await requireCliListing(args[0], exchangeOption, market, ctx) : null;
|
|
532
|
-
|
|
751
|
+
// Stories come newest first, so the newest n are the first n either way.
|
|
752
|
+
const limit = ctx.cliOptions.tail ?? ctx.cliOptions.limit ?? 20;
|
|
533
753
|
const loadNews = () => market.dataProvider.getNews({
|
|
534
754
|
feed: feed ?? (listing ? "ticker" : "latest"),
|
|
535
755
|
// Still set for news plugins that read the deprecated scope.
|
|
@@ -546,9 +766,7 @@ async function runNews(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
546
766
|
ctx.printResult({
|
|
547
767
|
data: articles,
|
|
548
768
|
metadata: { ticker: listing?.key ?? null, ...(identity ? listingMetadata(identity) : {}), feed: feed ?? null },
|
|
549
|
-
freshness: rowsFreshness(newsRows(articles),
|
|
550
|
-
status: "not-a-feed", basis: "published stories", observedKey: "publishedAt", oldest: null,
|
|
551
|
-
}),
|
|
769
|
+
freshness: rowsFreshness(newsRows(articles), cloudNewsFreshness(await cloudRealtimeAccess())),
|
|
552
770
|
}, {
|
|
553
771
|
...(identity ? { heading: listingHeading(identity) } : {}),
|
|
554
772
|
rows: newsRows,
|
|
@@ -584,10 +802,11 @@ async function registrantUsExchange(market: MarketContext, symbol: string, regis
|
|
|
584
802
|
|
|
585
803
|
async function runFilings(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
586
804
|
const args = [...rawArgs];
|
|
587
|
-
|
|
805
|
+
// Filings come newest first, so --tail asks for the same first n as --limit.
|
|
806
|
+
const count = parsePositiveInt(takeOption(args, "--count"), ctx.cliOptions.tail ?? ctx.cliOptions.limit ?? 15, "Count", ctx);
|
|
588
807
|
const exchangeOption = takeOption(args, "--exchange");
|
|
589
808
|
const form = takeOption(args, "--form")?.trim() || null;
|
|
590
|
-
const raw =
|
|
809
|
+
const raw = requireOneArg(args, "filings <symbol> [--count <n>] [--form <form>]", "symbol", ctx);
|
|
591
810
|
await withMarketData(ctx, async (market) => {
|
|
592
811
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
593
812
|
const { symbol, exchange } = listing.request;
|
|
@@ -655,10 +874,14 @@ async function printBeneficialOwners(
|
|
|
655
874
|
ctx: Parameters<CliCommandDef["execute"]>[1],
|
|
656
875
|
) {
|
|
657
876
|
const { symbol, exchange } = listing.request;
|
|
877
|
+
// A listing outside the US sends its company to the lookup, which needs its quote first.
|
|
878
|
+
const quotePromise = loadListingQuote(market.dataProvider, listing);
|
|
879
|
+
const abroad = nonUsSecListingVenue(symbol, exchange) ? listingIdentity(listing, await quotePromise) : null;
|
|
880
|
+
const request = { form: beneficialRouteForm(form), history, listing: { exchange, name: abroad?.name ?? undefined } };
|
|
658
881
|
const [payload, holders, quote] = await Promise.all([
|
|
659
|
-
loadForListing(listing, market, ctx, () => fetchBeneficialOwners(symbol,
|
|
882
|
+
loadForListing(listing, market, ctx, () => fetchBeneficialOwners(symbol, request)),
|
|
660
883
|
market.dataProvider.getHolders?.(symbol, exchange).catch(() => null) ?? null,
|
|
661
|
-
|
|
884
|
+
quotePromise,
|
|
662
885
|
]);
|
|
663
886
|
const identity = listingIdentity(listing, quote);
|
|
664
887
|
const rows = buildBeneficialReportRows(payload, { history, holders });
|
|
@@ -702,7 +925,7 @@ async function runHolders(
|
|
|
702
925
|
const exchangeOption = takeOption(args, "--exchange");
|
|
703
926
|
const formOption = commandName === "holders" ? takeOption(args, "--form") : undefined;
|
|
704
927
|
const history = commandName === "holders" && takeFlag(args, "--history");
|
|
705
|
-
const raw =
|
|
928
|
+
const raw = requireOneArg(args, `${commandName} <symbol>`, "symbol", ctx);
|
|
706
929
|
const form = formOption == null ? "13f" : parseHolderForm(formOption);
|
|
707
930
|
if (!form) ctx.fail(`Unknown form "${formOption}".`, `Use one of ${HOLDER_FORMS.join(", ")}.`);
|
|
708
931
|
if (form === "13f" && history) ctx.fail("--history lists 13D/13G reports.", "Add --form 13d, 13g or all.");
|
|
@@ -714,26 +937,33 @@ async function runHolders(
|
|
|
714
937
|
listing, market, ctx,
|
|
715
938
|
() => market.dataProvider.getHolders(listing.request.symbol, listing.request.exchange),
|
|
716
939
|
(found) => found.holders.length === 0,
|
|
940
|
+
{ command: commandName, noun: "holder data" },
|
|
717
941
|
),
|
|
718
942
|
loadListingQuote(market.dataProvider, listing),
|
|
719
943
|
]);
|
|
720
944
|
const identity = listingIdentity(listing, quote);
|
|
721
945
|
const rows = holderRows(data, ownerTypes);
|
|
722
|
-
const
|
|
723
|
-
const shown = limit == null ? rows.length : Math.min(limit, rows.length);
|
|
946
|
+
const shown = windowRows(rows, ctx.cliOptions).rows.length;
|
|
724
947
|
const institutional = !ownerTypes?.has("insider");
|
|
725
948
|
const total = institutional ? data.summary?.institutionsCount ?? null : null;
|
|
726
949
|
const reportDate = sharedReportDate(rows);
|
|
727
|
-
|
|
950
|
+
// A London line's values can be dollars from the 13F filings; the listing's currency is not their unit.
|
|
951
|
+
const valueCurrency = holderValueCurrency(data);
|
|
952
|
+
const valueBasis = rows.length > 0 ? holderValueBasis(reportDate, data.valueBasis, valueCurrency) : null;
|
|
728
953
|
const positionsBasis = rows.length > 0 ? nonUsHolderCaveat(identity.exchange || data.exchange, data.currency) : null;
|
|
954
|
+
const shareBasisNote = holderShareBasisNote(data, rows);
|
|
955
|
+
const markedRows = rows.some((row) => holderShareBasisMarker(row));
|
|
729
956
|
// The heading names the listing; its unit, date and how much of the list follows on the same line.
|
|
730
|
-
const facts = holderListFacts({
|
|
957
|
+
const facts = holderListFacts({
|
|
958
|
+
currency: valueCurrency, listingCurrency: data.currency, asOf: data.asOf, shown, reported: rows.length, total,
|
|
959
|
+
});
|
|
731
960
|
ctx.printResult({
|
|
732
961
|
data,
|
|
733
962
|
metadata: {
|
|
734
963
|
...listingMetadata(identity),
|
|
735
964
|
summary: data.summary,
|
|
736
965
|
currency: data.currency ?? null,
|
|
966
|
+
valueCurrency: valueCurrency ?? null,
|
|
737
967
|
asOf: data.asOf ?? null,
|
|
738
968
|
shown,
|
|
739
969
|
reported: rows.length,
|
|
@@ -750,16 +980,23 @@ async function runHolders(
|
|
|
750
980
|
{ key: "name", header: "Holder" },
|
|
751
981
|
{ key: "reportDate", header: "Date" },
|
|
752
982
|
{ key: "shares", header: "Shares", align: "right", format: formatCountCell },
|
|
983
|
+
// Marks the rows of a home line held as receipts; absent when no row is.
|
|
984
|
+
...(markedRows ? [{ key: "shareBasis", header: "Basis", value: (row: Record<string, unknown>) => holderShareBasisMarker(row) }] : []),
|
|
753
985
|
{
|
|
754
986
|
key: "value",
|
|
755
|
-
header: moneyColumnHeader("Value",
|
|
987
|
+
header: moneyColumnHeader("Value", valueCurrency),
|
|
756
988
|
align: "right",
|
|
757
989
|
value: (row) => row.value == null ? "" : formatCompact(Number(row.value)),
|
|
758
990
|
},
|
|
759
|
-
{
|
|
991
|
+
{
|
|
992
|
+
key: "percentHeld",
|
|
993
|
+
header: "% Held",
|
|
994
|
+
align: "right",
|
|
995
|
+
format: (value) => isFiniteNumber(value) ? formatHolderOwnershipPercent(value) : "",
|
|
996
|
+
},
|
|
760
997
|
],
|
|
761
998
|
summary: (holderData: HolderData) => [
|
|
762
|
-
[valueBasis ? `Value = ${valueBasis}.` : "", positionsBasis ?? ""].filter(Boolean).join(" "),
|
|
999
|
+
[valueBasis ? `Value = ${valueBasis}.` : "", positionsBasis ?? "", shareBasisNote ?? ""].filter(Boolean).join(" "),
|
|
763
1000
|
commandName === "insider" && ownerTypes ? insiderSummary(holderData, ownerTypes) : "",
|
|
764
1001
|
].filter(Boolean).join("\n"),
|
|
765
1002
|
empty: `No holders reported for ${listingTitle(identity)}.`,
|
|
@@ -770,11 +1007,14 @@ async function runHolders(
|
|
|
770
1007
|
async function runAnalyst(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
771
1008
|
const args = [...rawArgs];
|
|
772
1009
|
const exchangeOption = takeOption(args, "--exchange");
|
|
773
|
-
const raw =
|
|
1010
|
+
const raw = requireOneArg(args, "analyst <symbol>", "symbol", ctx);
|
|
774
1011
|
await withMarketData(ctx, async (market) => {
|
|
775
1012
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
776
1013
|
const [data, quote] = await Promise.all([
|
|
777
|
-
loadForListing(
|
|
1014
|
+
loadForListing(
|
|
1015
|
+
listing, market, ctx, () => market.dataProvider.getAnalystResearch(listing.request.symbol, listing.request.exchange),
|
|
1016
|
+
undefined, { command: "analyst", noun: "analyst research" },
|
|
1017
|
+
),
|
|
778
1018
|
loadListingQuote(market.dataProvider, listing),
|
|
779
1019
|
]);
|
|
780
1020
|
const identity = listingIdentity(listing, quote);
|
|
@@ -807,11 +1047,14 @@ async function runAnalyst(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
807
1047
|
async function runEvents(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
808
1048
|
const args = [...rawArgs];
|
|
809
1049
|
const exchangeOption = takeOption(args, "--exchange");
|
|
810
|
-
const raw =
|
|
1050
|
+
const raw = requireOneArg(args, "events <symbol>", "symbol", ctx);
|
|
811
1051
|
await withMarketData(ctx, async (market) => {
|
|
812
1052
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
813
1053
|
const [data, quote] = await Promise.all([
|
|
814
|
-
loadForListing(
|
|
1054
|
+
loadForListing(
|
|
1055
|
+
listing, market, ctx, () => market.dataProvider.getCorporateActions(listing.request.symbol, listing.request.exchange),
|
|
1056
|
+
undefined, { command: "events", noun: "corporate events" },
|
|
1057
|
+
),
|
|
815
1058
|
loadListingQuote(market.dataProvider, listing),
|
|
816
1059
|
]);
|
|
817
1060
|
const identity = listingIdentity(listing, quote);
|
|
@@ -835,83 +1078,168 @@ async function runEvents(rawArgs: string[], ctx: Parameters<CliCommandDef["execu
|
|
|
835
1078
|
|
|
836
1079
|
async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
837
1080
|
const args = [...rawArgs];
|
|
1081
|
+
const leapsOptions = takeLeapsOptions(args);
|
|
838
1082
|
const expiration = takeOption(args, "--expiration");
|
|
839
1083
|
const exchangeOption = takeOption(args, "--exchange");
|
|
840
|
-
|
|
1084
|
+
if (leapsOptions.leaps) {
|
|
1085
|
+
if (expiration != null || rawArgs.includes("--expiration")) {
|
|
1086
|
+
ctx.fail("--leaps reads every expiry more than a year out; drop --expiration.", `Usage: gloomberb ${LEAPS_USAGE}`);
|
|
1087
|
+
}
|
|
1088
|
+
const criteria = parseLeapsCriteria(leapsOptions.raw);
|
|
1089
|
+
if ("error" in criteria) ctx.fail(criteria.error, `Usage: gloomberb ${LEAPS_USAGE}`);
|
|
1090
|
+
const symbols = normalizeSymbols(args);
|
|
1091
|
+
if (symbols.length === 0) ctx.fail("--leaps needs at least one symbol.", `Usage: gloomberb ${LEAPS_USAGE}`);
|
|
1092
|
+
await withMarketData(ctx, (market) => runLeapsScreen(symbols, exchangeOption, criteria, market, ctx));
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
if (leapsOptions.given.length > 0) {
|
|
1096
|
+
ctx.fail(`${leapsOptions.given.join(", ")} only ${leapsOptions.given.length > 1 ? "apply" : "applies"} with --leaps.`, `Usage: gloomberb ${LEAPS_USAGE}`);
|
|
1097
|
+
}
|
|
1098
|
+
const raw = requireOneArg(args, OPTIONS_USAGE, "symbol", ctx);
|
|
1099
|
+
let expirationDate: number | undefined;
|
|
1100
|
+
if (expiration != null || rawArgs.includes("--expiration")) {
|
|
1101
|
+
const parsed = expiration == null ? null : parseOptionExpiration(expiration);
|
|
1102
|
+
if (parsed == null) {
|
|
1103
|
+
ctx.fail(
|
|
1104
|
+
expiration == null ? "--expiration needs a date." : `Invalid --expiration "${expiration}".`,
|
|
1105
|
+
`Use YYYY-MM-DD (2028-01-21) or Unix seconds (1832025600).\nUsage: gloomberb ${OPTIONS_USAGE}`,
|
|
1106
|
+
);
|
|
1107
|
+
}
|
|
1108
|
+
expirationDate = parsed;
|
|
1109
|
+
}
|
|
841
1110
|
await withMarketData(ctx, async (market) => {
|
|
842
1111
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
843
1112
|
const { symbol, exchange } = listing.request;
|
|
844
|
-
const
|
|
1113
|
+
const refresh = ctx.cliOptions.refresh;
|
|
845
1114
|
const quotePromise = loadListingQuote(market.dataProvider, listing);
|
|
846
|
-
const
|
|
1115
|
+
const inputsPromise = loadOptionModelInputs(market.dataProvider, listing, quotePromise, refresh);
|
|
1116
|
+
const { result, chain: loaded } = await loadForListing(listing, market, ctx, async () => {
|
|
847
1117
|
const cached = await market.dataProvider.getCachedQuery?.("getOptionsChain", [symbol, exchange, expirationDate, undefined])
|
|
848
|
-
.load({ force:
|
|
1118
|
+
.load({ force: refresh });
|
|
849
1119
|
return {
|
|
850
1120
|
result: cached,
|
|
851
1121
|
chain: cached?.value ?? await market.dataProvider.getOptionsChain(symbol, exchange, expirationDate, {
|
|
852
|
-
cacheMode:
|
|
1122
|
+
cacheMode: refresh ? "refresh" : "default",
|
|
853
1123
|
}),
|
|
854
1124
|
};
|
|
1125
|
+
}, undefined, { command: "options", noun: "options chain" }).catch((error: unknown) => {
|
|
1126
|
+
if (!isOptionsUnavailableError(error)) throw error;
|
|
1127
|
+
// A future or commodity with no chain of its own: name the listed fund whose options stand in.
|
|
1128
|
+
const alternative = listedOptionsAlternative(listing.key);
|
|
1129
|
+
return ctx.fail(optionsUnavailableTitle(listing.key), alternative
|
|
1130
|
+
? `Nearest listed alternative: ${alternative.symbol} (${alternative.name}), gloomberb options ${alternative.symbol}.`
|
|
1131
|
+
+ ` Strategy payoffs: gloomberb fn OSA ${alternative.symbol}`
|
|
1132
|
+
: "Strategy payoffs on a listed underlying: gloomberb fn OSA <ticker>");
|
|
855
1133
|
});
|
|
1134
|
+
const inputs = await inputsPromise;
|
|
1135
|
+
if (expirationDate != null && !chainHasExpiry(loaded, expirationDate)) {
|
|
1136
|
+
// An unlisted date comes back as an empty chain; its own list of expiries, else the default chain's, says what to pick.
|
|
1137
|
+
const listed = loaded.expirationDates.length > 0 ? loaded.expirationDates : await market.dataProvider
|
|
1138
|
+
.getOptionsChain(symbol, exchange, undefined, { cacheMode: "default" })
|
|
1139
|
+
.then((fallback) => fallback.expirationDates, () => []);
|
|
1140
|
+
const miss = missingExpiryMessage(listing.key, expirationDate, listed);
|
|
1141
|
+
ctx.fail(miss.message, miss.details);
|
|
1142
|
+
}
|
|
856
1143
|
const identity = listingIdentity(listing, await quotePromise);
|
|
1144
|
+
const chain = chainWithModelFigures(loaded, inputs);
|
|
857
1145
|
// A failed refresh falls back to the stored chain, which can be days old.
|
|
858
1146
|
const refreshWarning = result?.refreshError == null ? null
|
|
859
1147
|
: `Options refresh failed; showing the chain stored ${new Date(result.fetchedAt).toISOString()}`
|
|
860
1148
|
+ (chain.asOf ? ` (last trade ${chain.asOf})` : "");
|
|
861
1149
|
const sessionWarning = refreshWarning ? null : priorSessionChainWarning(chain, identity.exchange, Date.now());
|
|
862
|
-
const
|
|
1150
|
+
const modelWarning = inputs.spot == null && (chain.calls.length > 0 || chain.puts.length > 0)
|
|
1151
|
+
? `IV and delta need a current ${listing.key} quote; those columns are blank`
|
|
1152
|
+
: null;
|
|
1153
|
+
const warnings = [refreshWarning ?? sessionWarning, modelWarning].filter((warning): warning is string => warning != null);
|
|
863
1154
|
// Dated by the chain's last trade; a chain that failed to refresh is the stored one, stale.
|
|
864
1155
|
const freshness = rowsFreshness([], { asOf: chain.asOf ?? null }, {
|
|
865
1156
|
...(chain.dataSource ? { dataSource: chain.dataSource } : {}),
|
|
866
1157
|
...(chain.delayMinutes != null ? { delayMinutes: chain.delayMinutes } : {}),
|
|
867
1158
|
stale: refreshWarning != null,
|
|
868
1159
|
});
|
|
869
|
-
ctx.printResult({
|
|
1160
|
+
ctx.printResult({
|
|
1161
|
+
data: chain,
|
|
1162
|
+
metadata: {
|
|
1163
|
+
...listingMetadata(identity),
|
|
1164
|
+
expirations: chain.expirationDates,
|
|
1165
|
+
// What `iv` and `delta` on each contract are valued from; the provider's own impliedVolatility is left as sent.
|
|
1166
|
+
model: { spot: inputs.spot ?? null, dividendYield: inputs.dividendYield ?? null, rate: OPTION_MODEL_RATE },
|
|
1167
|
+
},
|
|
1168
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
1169
|
+
freshness,
|
|
1170
|
+
}, {
|
|
870
1171
|
heading: listingHeading(identity),
|
|
871
1172
|
rows: optionRows,
|
|
1173
|
+
// Choosing an expiry needs the list; once one is chosen the table is that expiry.
|
|
1174
|
+
summary: (data: OptionsChain) => expirationDate == null ? formatExpiryList(data.expirationDates) : "",
|
|
1175
|
+
empty: `No option contracts for ${listingTitle(identity)}.`,
|
|
872
1176
|
columns: [
|
|
873
1177
|
{ key: "side", header: "Side" },
|
|
874
1178
|
{ key: "contract", header: "Contract", shrink: false },
|
|
875
|
-
|
|
1179
|
+
// The contract symbol already carries the date, so this goes first when the table is narrow.
|
|
1180
|
+
{ key: "expiration", header: "Expiry", optional: true, dropPriority: 2 },
|
|
876
1181
|
{ key: "strike", header: "Strike", align: "right" },
|
|
877
1182
|
{ key: "last", header: "Last", align: "right" },
|
|
878
1183
|
{ key: "bid", header: "Bid", align: "right", format: (_value, row) => formatOptionQuoteCell(row, "bid") },
|
|
879
1184
|
{ key: "ask", header: "Ask", align: "right", format: (_value, row) => formatOptionQuoteCell(row, "ask") },
|
|
880
|
-
{ key: "
|
|
881
|
-
{ key: "
|
|
1185
|
+
{ key: "iv", header: "IV", align: "right", format: formatOptionIvCell },
|
|
1186
|
+
{ key: "delta", header: "Delta", align: "right", format: formatOptionDeltaCell },
|
|
1187
|
+
{ key: "volume", header: "Vol", align: "right", format: formatCountCell, optional: true, dropPriority: 1 },
|
|
1188
|
+
{ key: "openInterest", header: "OI", align: "right", format: formatCountCell, optional: true, dropPriority: 1 },
|
|
882
1189
|
],
|
|
883
1190
|
});
|
|
884
1191
|
});
|
|
885
1192
|
}
|
|
886
1193
|
|
|
1194
|
+
const FX_USAGE = "fx <currency> | fx <base>/<quote>";
|
|
1195
|
+
|
|
887
1196
|
async function runFx(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
888
|
-
const
|
|
1197
|
+
const raw = requireOneArg(rawArgs, FX_USAGE, "currency or pair", ctx);
|
|
889
1198
|
await withMarketData(ctx, async (market) => {
|
|
890
|
-
const
|
|
1199
|
+
const request = parseFxRequest(raw, market.config.baseCurrency);
|
|
1200
|
+
if ("error" in request) return ctx.fail(request.error, `Usage: gloomberb ${FX_USAGE}`);
|
|
1201
|
+
const { currency, baseCurrency } = request;
|
|
1202
|
+
const pair = `${currency}/${baseCurrency}`;
|
|
1203
|
+
const reasons = new Map<string, string>();
|
|
891
1204
|
const load = (code: string) => market.dataProvider.getCachedQuery("getExchangeRate", [code])
|
|
892
|
-
.load({ force: ctx.cliOptions.refresh }).catch(() =>
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1205
|
+
.load({ force: ctx.cliOptions.refresh }).catch((error) => {
|
|
1206
|
+
const reason = providerMissReason(error);
|
|
1207
|
+
if (reason) reasons.set(code, reason);
|
|
1208
|
+
return null;
|
|
1209
|
+
});
|
|
1210
|
+
// Every rate is a cross of two USD legs; a code priced in itself needs none.
|
|
1211
|
+
const codes = currency === baseCurrency ? [] : [currency, baseCurrency];
|
|
1212
|
+
const legs = await Promise.all(codes.map(load));
|
|
1213
|
+
const missing = codes.filter((_code, index) => !isUsableRate(legs[index]?.value));
|
|
1214
|
+
if (missing.length > 0) {
|
|
1215
|
+
// A reason the data service gave for the first missing leg says more than the generic line.
|
|
1216
|
+
const reason = missing.map((code) => reasons.get(code)).find(Boolean);
|
|
1217
|
+
if (reason) ctx.fail(reason);
|
|
1218
|
+
ctx.fail(
|
|
1219
|
+
`Exchange rate unavailable for ${missing.join(" and ")}.`,
|
|
1220
|
+
`${pair} is crossed from each currency's USD rate, and none came back for ${missing.join(" or ")}. Check the ISO code.`,
|
|
1221
|
+
);
|
|
1222
|
+
}
|
|
1223
|
+
const rate = codes.length === 0 ? 1 : crossRate(legs[0]?.value, legs[1]?.value);
|
|
897
1224
|
// A cross rate is only as current as its older leg.
|
|
898
1225
|
const observed = legs.flatMap((leg) => leg?.asOf ?? []);
|
|
899
1226
|
const asOf = observed.length > 0 ? new Date(Math.min(...observed)).toISOString() : null;
|
|
900
1227
|
const stale = legs.some((leg) => leg != null && (leg.staleAt <= Date.now() || leg.refreshError != null));
|
|
901
|
-
|
|
902
|
-
|
|
1228
|
+
const row = { currency, baseCurrency, rate, asOf, stale, pair, inverse: 1 / rate };
|
|
1229
|
+
ctx.printResult({ data: [row] }, {
|
|
1230
|
+
text: () => {
|
|
1231
|
+
const time = asOf ? `As of ${formatUtcTime(asOf)}` : "";
|
|
1232
|
+
const when = stale ? cliStyles.warning(time ? `${time}, stale` : "Stale") : time && cliStyles.muted(time);
|
|
1233
|
+
return [describeFxRate(request, rate), when].filter(Boolean).join("\n");
|
|
1234
|
+
},
|
|
903
1235
|
columns: [
|
|
904
1236
|
{ key: "currency", header: "Currency" },
|
|
905
1237
|
{ key: "baseCurrency", header: "Base" },
|
|
906
1238
|
{ key: "rate", header: "Rate", align: "right" },
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
const time = typeof value === "string" ? formatUtcTime(value) : "";
|
|
912
|
-
return row.stale ? cliStyles.warning(`${time} stale`.trim()) : time;
|
|
913
|
-
},
|
|
914
|
-
}] : []),
|
|
1239
|
+
{ key: "asOf", header: "As Of" },
|
|
1240
|
+
{ key: "stale", header: "Stale" },
|
|
1241
|
+
{ key: "pair", header: "Pair" },
|
|
1242
|
+
{ key: "inverse", header: "Inverse", align: "right" },
|
|
915
1243
|
],
|
|
916
1244
|
});
|
|
917
1245
|
});
|
|
@@ -948,7 +1276,7 @@ async function runEarnings(rawArgs: string[], ctx: Parameters<CliCommandDef["exe
|
|
|
948
1276
|
});
|
|
949
1277
|
}
|
|
950
1278
|
|
|
951
|
-
export const marketDataCliCommands:
|
|
1279
|
+
export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
952
1280
|
{
|
|
953
1281
|
name: "quote",
|
|
954
1282
|
description: "Show the latest price for one or more symbols",
|
|
@@ -976,26 +1304,67 @@ export const marketDataCliCommands: CliCommandDef[] = [
|
|
|
976
1304
|
description: "Fetch open, high, low, close, and volume over a range",
|
|
977
1305
|
help: {
|
|
978
1306
|
group: CLI_COMMAND_GROUPS.research,
|
|
979
|
-
usage: [
|
|
1307
|
+
usage: [HISTORY_USAGE],
|
|
980
1308
|
options: [
|
|
981
|
-
{ flags: "--range <range>", description: `${TIME_RANGES.join(", ")} (default 1Y)` },
|
|
1309
|
+
{ flags: "--range <range>", description: `${TIME_RANGES.join(", ")} (default 1Y); ${HISTORY_ALL_RANGE}` },
|
|
982
1310
|
EXCHANGE_OPTION,
|
|
983
1311
|
],
|
|
984
|
-
|
|
1312
|
+
sections: [{
|
|
1313
|
+
title: "Bars",
|
|
1314
|
+
lines: [
|
|
1315
|
+
`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.`,
|
|
1316
|
+
"A bar whose prices contradict each other (high below the open or close, or low above them) is left blank and flagged, such as high<open, with the count in a warning. Every exported row (--csv, --ndjson, --json) carries currency, interval and flag.",
|
|
1317
|
+
],
|
|
1318
|
+
}],
|
|
1319
|
+
examples: ["history AAPL", "history BHP:ASX --range 5Y", "history ZAR=X --range ALL", "history ZAR=X --tail 5", "history AAPL --range 5Y --csv > aapl.csv"],
|
|
985
1320
|
},
|
|
1321
|
+
unknownOptionHint: (flag) => DATE_WINDOW_OPTIONS.has(flag)
|
|
1322
|
+
? `history takes a --range instead of dates: ${TIME_RANGES.join(", ")}. ${HISTORY_ALL_RANGE}.`
|
|
1323
|
+
: null,
|
|
986
1324
|
execute: runHistory,
|
|
987
1325
|
},
|
|
988
1326
|
{
|
|
989
1327
|
name: "options",
|
|
990
|
-
description: "Fetch an options chain",
|
|
1328
|
+
description: "Fetch an options chain, or rank LEAPS across symbols",
|
|
991
1329
|
help: {
|
|
992
1330
|
group: CLI_COMMAND_GROUPS.research,
|
|
993
|
-
usage: [
|
|
1331
|
+
usage: [OPTIONS_USAGE, LEAPS_USAGE],
|
|
994
1332
|
options: [
|
|
995
|
-
{
|
|
1333
|
+
{
|
|
1334
|
+
flags: "--expiration <YYYY-MM-DD|unix>",
|
|
1335
|
+
description: "Expiration as a date (2028-01-21) or Unix seconds; defaults to the nearest one, and lists the others",
|
|
1336
|
+
},
|
|
996
1337
|
EXCHANGE_OPTION,
|
|
1338
|
+
{ flags: "--leaps", description: "Rank contracts more than a year out across the symbols given, as a stock replacement" },
|
|
1339
|
+
{ flags: "--side <calls|puts>", description: `With --leaps: the side to rank (default ${DEFAULT_LEAPS_CRITERIA.side}s)` },
|
|
1340
|
+
{
|
|
1341
|
+
flags: "--delta <low-high>",
|
|
1342
|
+
description: `With --leaps: absolute delta band (default ${DEFAULT_LEAPS_CRITERIA.minDelta.toFixed(2)}-${DEFAULT_LEAPS_CRITERIA.maxDelta.toFixed(2)})`,
|
|
1343
|
+
},
|
|
1344
|
+
{
|
|
1345
|
+
flags: "--max-spread <percent>",
|
|
1346
|
+
description: `With --leaps: widest bid/ask spread, in percent of the midpoint (default ${DEFAULT_LEAPS_CRITERIA.maxSpreadPercent})`,
|
|
1347
|
+
},
|
|
1348
|
+
{ flags: "--min-oi <contracts>", description: `With --leaps: least open interest (default ${DEFAULT_LEAPS_CRITERIA.minOpenInterest})` },
|
|
1349
|
+
{
|
|
1350
|
+
flags: "--sort <order>",
|
|
1351
|
+
description: "With --leaps: carry (extrinsic per year, lowest first; the default), spread, oi, delta, expiry or symbol",
|
|
1352
|
+
},
|
|
1353
|
+
],
|
|
1354
|
+
sections: [{
|
|
1355
|
+
title: "LEAPS",
|
|
1356
|
+
lines: [
|
|
1357
|
+
"--leaps reads every expiry more than a year out for each symbol and keeps the liquid contracts in the delta band, ranked by extrinsic per year: time value (midpoint less intrinsic) as a percent of spot, per year to expiry.",
|
|
1358
|
+
"A symbol without LEAPS, a quote or a chain is named in a warning and the others still rank. docs/research-data.md defines each figure.",
|
|
1359
|
+
],
|
|
1360
|
+
}],
|
|
1361
|
+
examples: [
|
|
1362
|
+
"options AAPL",
|
|
1363
|
+
"options AAPL --expiration 2028-01-21",
|
|
1364
|
+
"options AAPL:NASDAQ --json",
|
|
1365
|
+
"options AAPL MSFT NVDA GOOGL AMZN META --leaps",
|
|
1366
|
+
"options SPY QQQ --leaps --side puts --delta 0.20-0.40 --csv",
|
|
997
1367
|
],
|
|
998
|
-
examples: ["options AAPL", "options AAPL:NASDAQ --json"],
|
|
999
1368
|
},
|
|
1000
1369
|
execute: runOptions,
|
|
1001
1370
|
},
|
|
@@ -1032,8 +1401,8 @@ export const marketDataCliCommands: CliCommandDef[] = [
|
|
|
1032
1401
|
help: {
|
|
1033
1402
|
group: CLI_COMMAND_GROUPS.companyData,
|
|
1034
1403
|
usage: ["financials <symbol>"],
|
|
1035
|
-
options: [EXCHANGE_OPTION],
|
|
1036
|
-
examples: ["financials MSFT", "financials SAN:EPA", "financials MSFT --json"],
|
|
1404
|
+
options: [EXCHANGE_OPTION, TABLE_SECTION_OPTION],
|
|
1405
|
+
examples: ["financials MSFT", "financials SAN:EPA", "financials MSFT --json", "financials MSFT --csv > msft.csv"],
|
|
1037
1406
|
},
|
|
1038
1407
|
execute: (args, ctx) => runFinancials(args, ctx, "statements"),
|
|
1039
1408
|
},
|
|
@@ -1043,8 +1412,8 @@ export const marketDataCliCommands: CliCommandDef[] = [
|
|
|
1043
1412
|
help: {
|
|
1044
1413
|
group: CLI_COMMAND_GROUPS.companyData,
|
|
1045
1414
|
usage: ["fundamentals <symbol>"],
|
|
1046
|
-
options: [EXCHANGE_OPTION],
|
|
1047
|
-
examples: ["fundamentals NVDA", "fundamentals ASML:AMS"],
|
|
1415
|
+
options: [EXCHANGE_OPTION, TABLE_SECTION_OPTION],
|
|
1416
|
+
examples: ["fundamentals NVDA", "fundamentals ASML:AMS", "fundamentals NVDA --csv --section fundamentals"],
|
|
1048
1417
|
},
|
|
1049
1418
|
execute: (args, ctx) => runFinancials(args, ctx, "fundamentals"),
|
|
1050
1419
|
},
|
|
@@ -1054,8 +1423,8 @@ export const marketDataCliCommands: CliCommandDef[] = [
|
|
|
1054
1423
|
help: {
|
|
1055
1424
|
group: CLI_COMMAND_GROUPS.companyData,
|
|
1056
1425
|
usage: ["valuation <symbol>"],
|
|
1057
|
-
options: [EXCHANGE_OPTION],
|
|
1058
|
-
examples: ["valuation NVDA", "valuation BP:LSE"],
|
|
1426
|
+
options: [EXCHANGE_OPTION, TABLE_SECTION_OPTION],
|
|
1427
|
+
examples: ["valuation NVDA", "valuation BP:LSE", "valuation NVDA --csv"],
|
|
1059
1428
|
},
|
|
1060
1429
|
execute: (args, ctx) => runFinancials(args, ctx, "valuation"),
|
|
1061
1430
|
},
|
|
@@ -1164,11 +1533,18 @@ export const marketDataCliCommands: CliCommandDef[] = [
|
|
|
1164
1533
|
},
|
|
1165
1534
|
{
|
|
1166
1535
|
name: "fx",
|
|
1167
|
-
description: "Convert a currency into your base currency",
|
|
1536
|
+
description: "Convert a currency into your base currency, or quote a pair such as USD/NGN",
|
|
1168
1537
|
help: {
|
|
1169
1538
|
group: CLI_COMMAND_GROUPS.markets,
|
|
1170
|
-
usage: ["fx <currency>"],
|
|
1171
|
-
|
|
1539
|
+
usage: ["fx <currency>", "fx <base>/<quote>"],
|
|
1540
|
+
sections: [{
|
|
1541
|
+
title: "Direction",
|
|
1542
|
+
lines: [
|
|
1543
|
+
"A bare code is one unit of it in your base currency. A pair reads as markets quote it: USD/NGN is how many NGN one USD buys. Two codes neither of which is USD, such as ZAR/NGN, cross through their USD rates.",
|
|
1544
|
+
"The answer states both directions: 1 NGN = 0.000752791 USD (USD/NGN 1328.39). --json, --csv and --ndjson carry rate (base currency per currency), pair, inverse, asOf and stale.",
|
|
1545
|
+
],
|
|
1546
|
+
}],
|
|
1547
|
+
examples: ["fx EUR", "fx NGN", "fx USD/NGN", "fx ZAR/NGN", "fx JPY --json"],
|
|
1172
1548
|
},
|
|
1173
1549
|
execute: runFx,
|
|
1174
1550
|
},
|