gloomberb 0.16.4 → 0.16.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -91
- package/package.json +4 -1
- package/src/{plugins/builtin/cloud → account}/auth-form.tsx +9 -9
- package/src/{plugins/builtin/cloud → account}/auth-model.ts +4 -4
- package/src/account/billing.ts +57 -0
- package/src/{plugins/builtin/cloud → account}/device-signin-dialog.tsx +18 -12
- package/src/{plugins/builtin/cloud → account}/device-signin.ts +2 -2
- package/src/{plugins/builtin/cloud → account}/email-auth-attempt.ts +2 -2
- package/src/account/session.ts +16 -0
- package/src/{plugins/builtin/cloud → account}/upgrade-dialog.tsx +16 -15
- package/src/{plugins/builtin/cloud → account}/upgrade-personalization.ts +7 -7
- package/src/{plugins/builtin/cloud → account}/upgrade-tickers.ts +6 -6
- package/src/{plugins/builtin/cloud → account}/verification-panel.tsx +6 -6
- package/src/api-client/data.ts +24 -2
- package/src/api-client/earnings.ts +6 -0
- package/src/api-client/futures-curve.ts +56 -0
- package/src/api-client/index.ts +4 -1
- package/src/api-client/market-discovery.ts +10 -1
- package/src/api-client/paths.ts +7 -0
- package/src/api-client/research-activity.ts +15 -10
- package/src/api-client/types.ts +17 -1
- package/src/api-client/yield-curves.ts +8 -0
- package/src/app/global-shortcuts.ts +15 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +7 -3
- package/src/architecture/core-builtin-allowlist.json +4 -13
- package/src/brokers/signed-in/sign-in-dialog.tsx +11 -5
- package/src/capabilities/chart-series.ts +11 -1
- package/src/cli/commands/crypto-hints.ts +7 -0
- package/src/cli/commands/fred-series.ts +61 -0
- package/src/cli/commands/market.ts +407 -104
- package/src/cli/commands/news-hints.ts +62 -0
- package/src/cli/commands/news-search.ts +53 -0
- package/src/cli/commands/options-leaps.ts +5 -2
- package/src/cli/commands/overview.ts +93 -20
- package/src/cli/commands/remote.ts +2 -1
- package/src/cli/commands/search.ts +82 -9
- package/src/cli/commands/system.ts +55 -8
- package/src/cli/commands/ticker.ts +88 -11
- package/src/cli/context.ts +45 -14
- package/src/cli/desktop-pane-shot.ts +59 -7
- package/src/cli/earnings-calendar.ts +71 -0
- package/src/cli/freshness.ts +18 -7
- package/src/cli/glossary.ts +429 -0
- package/src/cli/history-rows.ts +48 -7
- package/src/cli/index-roots.ts +7 -0
- package/src/cli/index.ts +21 -7
- package/src/cli/known-gaps.ts +27 -0
- package/src/cli/listing-arg.ts +417 -56
- package/src/cli/not-a-ticker.ts +12 -5
- package/src/cli/pane-functions/access-gate.ts +5 -0
- package/src/cli/pane-functions/capabilities.ts +3 -2
- package/src/cli/pane-functions/catalog.ts +35 -5
- package/src/cli/pane-functions/data.ts +12 -8
- package/src/cli/pane-functions/discovery.ts +1 -0
- package/src/cli/pane-functions/dom.ts +20 -6
- package/src/cli/pane-functions/freshness.ts +363 -43
- package/src/cli/pane-functions/function-help.ts +12 -5
- package/src/cli/pane-functions/headless.ts +45 -17
- package/src/cli/pane-functions/index.ts +63 -10
- package/src/cli/pane-functions/market-session.ts +22 -8
- package/src/cli/pane-functions/options.ts +63 -22
- package/src/cli/pane-functions/report-notices.ts +25 -7
- package/src/cli/pane-functions/report.ts +15 -8
- package/src/cli/pane-functions/resolver.ts +49 -12
- package/src/cli/pane-functions/screenshot.ts +110 -23
- package/src/cli/report-tables.ts +3 -0
- package/src/cli/result.ts +4 -1
- package/src/cli/staged-tickers.ts +52 -0
- package/src/cli/symbol-args.ts +9 -0
- package/src/cli/symbol-list.ts +172 -0
- package/src/cli/ticker-resolution.ts +223 -30
- package/src/cli/types.ts +5 -0
- package/src/components/chart/composite/axis-overlays.tsx +6 -2
- package/src/components/chart/composite/composite-chart.tsx +40 -6
- package/src/components/chart/composite/legend.tsx +49 -2
- package/src/components/chart/composite/panel-surface.tsx +44 -17
- package/src/components/chart/composite/price-axis-labels.tsx +3 -1
- package/src/components/command-bar/panel/index.tsx +11 -3
- package/src/components/command-bar/panel/runtime.ts +3 -0
- package/src/components/command-bar/panel/types.ts +2 -0
- package/src/components/command-bar/routes/root/provider-search.ts +4 -3
- package/src/components/command-bar/routes/ticker-search/results.ts +12 -11
- package/src/components/command-bar/routes/ticker-search/route.ts +4 -3
- package/src/components/command-bar/workflow/tickers.ts +23 -4
- package/src/components/layout/detached-pane-shell.tsx +9 -0
- package/src/components/layout/floating-pane.tsx +3 -0
- package/src/components/layout/header.tsx +20 -5
- package/src/components/layout/pane/content.tsx +7 -13
- package/src/components/layout/pane/footer/index.tsx +6 -2
- package/src/components/layout/pane/footer/model.ts +35 -1
- package/src/components/layout/pane/header.tsx +24 -1
- package/src/components/layout/pane/index.tsx +12 -2
- package/src/components/layout/pane/sidebar.tsx +78 -10
- package/src/components/layout/shell/active-drag.ts +21 -5
- package/src/components/layout/shell/drag/index.ts +39 -9
- package/src/components/layout/shell/drag/live.ts +61 -51
- package/src/components/layout/shell/drag/runtime.ts +54 -12
- package/src/components/layout/shell/index.tsx +25 -7
- package/src/components/layout/shell/menu.ts +5 -0
- package/src/components/layout/shell/native/pointer-runtime.ts +3 -1
- package/src/components/layout/shell/pane/layers.tsx +192 -45
- package/src/components/layout/shell/terminal-pointer-runtime.ts +24 -0
- package/src/components/markdown-text.tsx +62 -20
- package/src/components/metric-treemap/heat-surface.tsx +10 -1
- package/src/components/onboarding/account-step/form-panel.tsx +1 -1
- package/src/components/onboarding/account-step/index.tsx +1 -1
- package/src/components/onboarding/account-step/qr-panel.tsx +1 -1
- package/src/components/onboarding/account-step/signed-in-panel.tsx +1 -1
- package/src/components/onboarding/onboarding-keyboard.ts +1 -1
- package/src/components/onboarding/onboarding-wizard.tsx +2 -2
- package/src/components/onboarding/use-onboarding-upgrade.ts +3 -3
- package/src/components/onboarding/wizard-account.ts +2 -2
- package/src/components/pane-template-wizard.tsx +9 -1
- package/src/components/price-selector-dialog.tsx +11 -2
- package/src/components/sign-in-gate.tsx +3 -3
- package/src/components/ticker/list-table-view.tsx +24 -1
- package/src/components/ui/action-row.tsx +2 -1
- package/src/components/ui/button.tsx +7 -1
- package/src/components/ui/choice-dialog.tsx +6 -4
- package/src/components/ui/confirm-dialog.tsx +28 -18
- package/src/components/ui/data-table/index.tsx +17 -6
- package/src/components/ui/data-table/opentui/index.tsx +37 -6
- package/src/components/ui/data-table/types.ts +12 -0
- package/src/components/ui/dialog-actions.tsx +64 -0
- package/src/components/ui/display.tsx +17 -3
- package/src/components/ui/frame.tsx +35 -2
- package/src/components/ui/icon.tsx +2 -0
- package/src/components/ui/text-prompt-dialog.tsx +11 -9
- package/src/data/cached-query.ts +2 -0
- package/src/data/config/store/normalize.ts +7 -2
- package/src/data/ticker-repository.ts +5 -0
- package/src/data/ticker-store.ts +15 -0
- package/src/docs/content.ts +215 -0
- package/src/docs/load.ts +139 -0
- package/src/docs/requests.ts +39 -0
- package/src/docs/snapshot.chunk.ts +8 -0
- package/src/docs/snapshot.json +7416 -0
- package/src/docs/topics.ts +216 -0
- package/src/i18n/es.ts +61 -4
- package/src/i18n/ja.ts +61 -4
- package/src/i18n/ko.ts +61 -4
- package/src/i18n/zh-cn.ts +61 -4
- package/src/i18n/zh-tw.ts +61 -4
- package/src/layout/desks.ts +1 -1
- package/src/layout/pane-manager/dock-tree.ts +18 -0
- package/src/layout/pane-manager/index.ts +1 -0
- package/src/market-data/coordinator/chart.ts +10 -4
- package/src/market-data/coordinator/index.ts +6 -3
- package/src/{plugins/builtin/futures/contracts.ts → market-data/futures-board.ts} +81 -28
- package/src/market-data/futures-contract.ts +151 -0
- package/src/market-data/hooks.tsx +10 -2
- package/src/market-data/market/format.ts +39 -21
- package/src/market-data/market/freshness.ts +81 -18
- package/src/market-data/market/status.ts +19 -23
- package/src/market-data/published-apac-sessions.ts +23 -1
- package/src/market-data/quotes/contributions.ts +3 -0
- package/src/market-data/quotes/metadata.ts +7 -24
- package/src/market-data/quotes/resolution.ts +2 -0
- package/src/market-data/quotes/screener-live-quotes.ts +5 -4
- package/src/market-data/result-types.ts +2 -0
- package/src/plugins/builtin/account-management/model.ts +1 -56
- package/src/plugins/builtin/account-management/pane.tsx +6 -4
- package/src/plugins/builtin/account-management/password-dialog.tsx +1 -1
- package/src/plugins/builtin/alerts/cli.ts +313 -47
- package/src/plugins/builtin/alerts/events-pane.tsx +45 -7
- package/src/plugins/builtin/alerts/events.ts +23 -2
- package/src/plugins/builtin/alerts/history-pane.tsx +7 -2
- package/src/plugins/builtin/alerts/history.ts +2 -0
- package/src/plugins/builtin/alerts/index.tsx +41 -9
- package/src/plugins/builtin/alerts/pane.tsx +8 -0
- package/src/plugins/builtin/alerts/research-builder.ts +8 -3
- package/src/plugins/builtin/alerts/research-rules.ts +18 -2
- package/src/plugins/builtin/alerts/scheduled-check.ts +122 -0
- package/src/plugins/builtin/alerts/scheduled-sources.ts +25 -0
- package/src/plugins/builtin/alerts/scheduled.ts +254 -0
- package/src/plugins/builtin/alerts/targets.ts +92 -0
- package/src/plugins/builtin/analytics/index.tsx +4 -2
- package/src/plugins/builtin/analytics/risk-headless.ts +30 -12
- package/src/plugins/builtin/analytics/risk-math.ts +91 -27
- package/src/plugins/builtin/analytics/risk-model.ts +35 -5
- package/src/plugins/builtin/analytics/risk-pane.tsx +15 -8
- package/src/plugins/builtin/attention/index.tsx +2 -0
- package/src/plugins/builtin/attention/pane.tsx +3 -1
- package/src/plugins/builtin/attention/series.ts +26 -0
- package/src/plugins/builtin/awards/index.tsx +2 -0
- package/src/plugins/builtin/awards/pane.tsx +3 -1
- package/src/plugins/builtin/awards/series.ts +29 -0
- package/src/plugins/builtin/bond-calculator/model.ts +2 -1
- package/src/plugins/builtin/catalysts/pane.tsx +3 -1
- package/src/plugins/builtin/cds/client.ts +70 -13
- package/src/plugins/builtin/cds/headless.ts +116 -19
- package/src/plugins/builtin/cds/index.tsx +3 -1
- package/src/plugins/builtin/cds/model.ts +193 -31
- package/src/plugins/builtin/cds/pane.tsx +56 -14
- package/src/plugins/builtin/cds/series.ts +19 -0
- package/src/plugins/builtin/central-bank-rates/index.tsx +2 -0
- package/src/plugins/builtin/central-bank-rates/series.ts +42 -0
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +192 -255
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +84 -46
- package/src/plugins/builtin/chart-composer/editor.tsx +5 -4
- package/src/plugins/builtin/chart-composer/field-series.ts +40 -0
- package/src/plugins/builtin/chart-composer/headless.ts +8 -3
- package/src/plugins/builtin/chart-composer/index.tsx +9 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +58 -50
- package/src/plugins/builtin/chart-composer/series-expression.ts +18 -10
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +59 -3
- package/src/plugins/builtin/chart-composer/user-series.ts +84 -0
- package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +10 -2
- package/src/plugins/builtin/chat/controller/index.ts +2 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +111 -19
- package/src/plugins/builtin/cloud/askg/executor.ts +57 -3
- package/src/plugins/builtin/cloud/askg/manifest.ts +32 -7
- package/src/plugins/builtin/cloud/askg/model.ts +27 -8
- package/src/plugins/builtin/cloud/askg/protocol.ts +10 -1
- package/src/plugins/builtin/cloud/askg/tool-display.ts +3 -0
- package/src/plugins/builtin/cloud/askg/undo.ts +2 -0
- package/src/plugins/builtin/cloud/auth-actions.tsx +1 -1
- package/src/plugins/builtin/cloud/auth-commands.ts +1 -1
- package/src/plugins/builtin/cloud/auth-dialog.tsx +3 -3
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +3 -0
- package/src/plugins/builtin/cloud/terminal-relay/engine.ts +77 -9
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +6 -1
- package/src/plugins/builtin/cloud/terminal-relay/policy.ts +9 -0
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +35 -11
- package/src/plugins/builtin/cloud/thesis/exposure.ts +1 -1
- package/src/plugins/builtin/cloud/thesis/prompts.tsx +2 -1
- package/src/plugins/builtin/company-attention/app-rank.tsx +1 -1
- package/src/plugins/builtin/company-attention/headless.ts +19 -5
- package/src/plugins/builtin/company-attention/index.tsx +2 -0
- package/src/plugins/builtin/company-attention/model.ts +26 -7
- package/src/plugins/builtin/company-attention/pane.tsx +12 -9
- package/src/plugins/builtin/company-attention/series.ts +52 -0
- package/src/plugins/builtin/company-kpis/pane.tsx +4 -2
- package/src/plugins/builtin/composite-plugins.ts +19 -13
- package/src/plugins/builtin/cot/index.tsx +3 -1
- package/src/plugins/builtin/cot/model.ts +40 -11
- package/src/plugins/builtin/cot/series.ts +27 -0
- package/src/plugins/builtin/credit-boards/index.tsx +2 -0
- package/src/plugins/builtin/credit-boards/series.ts +66 -0
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -0
- package/src/plugins/builtin/credit-conditions/series.ts +26 -0
- package/src/plugins/builtin/credit-documents/headless.ts +31 -1
- package/src/plugins/builtin/credit-documents/pane.tsx +3 -1
- package/src/plugins/builtin/crypto-board/headless.ts +2 -3
- package/src/plugins/builtin/crypto-board/index.tsx +2 -0
- package/src/plugins/builtin/crypto-board/model.ts +12 -0
- package/src/plugins/builtin/crypto-board/pane.tsx +19 -12
- package/src/plugins/builtin/crypto-board/series.ts +48 -0
- package/src/plugins/builtin/custom-view/live-quotes.ts +2 -2
- package/src/plugins/builtin/debt-maturities/index.tsx +2 -0
- package/src/plugins/builtin/debt-maturities/series.ts +45 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +2 -2
- package/src/plugins/builtin/dividend-yield/index.tsx +2 -0
- package/src/plugins/builtin/dividend-yield/series.ts +20 -0
- package/src/plugins/builtin/docs/blocks.tsx +181 -0
- package/src/plugins/builtin/docs/index.tsx +68 -0
- package/src/plugins/builtin/docs/pane.tsx +426 -0
- package/src/plugins/builtin/docs/targets.ts +72 -0
- package/src/plugins/builtin/docs/use-docs.ts +60 -0
- package/src/plugins/builtin/doe/index.tsx +2 -0
- package/src/plugins/builtin/doe/series.ts +62 -0
- package/src/plugins/builtin/earnings/client.ts +3 -2
- package/src/plugins/builtin/earnings/format.ts +1 -5
- package/src/plugins/builtin/earnings/headless.ts +58 -6
- package/src/plugins/builtin/earnings/history-model.ts +15 -1
- package/src/plugins/builtin/earnings/history-table.ts +13 -2
- package/src/plugins/builtin/earnings/history.tsx +7 -3
- package/src/plugins/builtin/econ/fred-catalog.ts +189 -0
- package/src/plugins/builtin/econ/fred-series-map.ts +0 -46
- package/src/plugins/builtin/econ/index.tsx +2 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +5 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +19 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +1 -0
- package/src/plugins/builtin/equity-screener/pane.tsx +4 -11
- package/src/plugins/builtin/exposure/holdings.ts +1 -1
- package/src/plugins/builtin/exposure/pane.tsx +3 -1
- package/src/plugins/builtin/fear-greed/index.tsx +4 -0
- package/src/plugins/builtin/fear-greed/series.ts +52 -0
- package/src/plugins/builtin/futures/index.tsx +43 -3
- package/src/plugins/builtin/futures/model.ts +1 -1
- package/src/plugins/builtin/futures/returns.ts +1 -1
- package/src/plugins/builtin/futures/series.ts +13 -0
- package/src/plugins/builtin/futures/table.tsx +1 -1
- package/src/plugins/builtin/futures/use-front-returns.ts +1 -1
- package/src/plugins/builtin/futures-curve/client.ts +12 -3
- package/src/plugins/builtin/futures-curve/headless.ts +70 -9
- package/src/plugins/builtin/futures-curve/index.tsx +1 -1
- package/src/plugins/builtin/futures-curve/model.ts +66 -11
- package/src/plugins/builtin/futures-curve/pane.tsx +52 -18
- package/src/plugins/builtin/fx-matrix/index.tsx +1 -1
- package/src/plugins/builtin/gpu/board.tsx +13 -10
- package/src/plugins/builtin/gpu/changes.tsx +4 -4
- package/src/plugins/builtin/gpu/client.ts +19 -1
- package/src/plugins/builtin/gpu/equities.tsx +14 -10
- package/src/plugins/builtin/gpu/headless.ts +56 -25
- package/src/plugins/builtin/gpu/history.tsx +3 -3
- package/src/plugins/builtin/gpu/index.tsx +2 -0
- package/src/plugins/builtin/gpu/model.ts +101 -16
- package/src/plugins/builtin/gpu/pane.tsx +28 -12
- package/src/plugins/builtin/gpu/series.ts +38 -0
- package/src/plugins/builtin/help/function-card.tsx +25 -6
- package/src/plugins/builtin/help/index.tsx +17 -8
- package/src/plugins/builtin/holders/beneficial-model.ts +90 -3
- package/src/plugins/builtin/holders/beneficial-report.ts +57 -5
- package/src/plugins/builtin/holders/beneficial-view.tsx +55 -11
- package/src/plugins/builtin/holders/client.ts +22 -3
- package/src/plugins/builtin/holders/format.ts +18 -3
- package/src/plugins/builtin/holders/headless.ts +36 -133
- package/src/plugins/builtin/holders/pane.tsx +11 -4
- package/src/plugins/builtin/holders/report-header.ts +40 -5
- package/src/plugins/builtin/holders/report-rows.ts +240 -0
- package/src/plugins/builtin/insider/client.ts +35 -20
- package/src/plugins/builtin/insider/headless.ts +41 -11
- package/src/plugins/builtin/insider/index.tsx +26 -9
- package/src/plugins/builtin/insider/insider-data.ts +10 -0
- package/src/plugins/builtin/insider/issuer.ts +49 -0
- package/src/plugins/builtin/insider/model.ts +3 -0
- package/src/plugins/builtin/iv-history/client.ts +14 -4
- package/src/plugins/builtin/iv-history/headless.ts +7 -4
- package/src/plugins/builtin/iv-history/index.tsx +2 -0
- package/src/plugins/builtin/iv-history/model.ts +111 -7
- package/src/plugins/builtin/iv-history/screen-pane.tsx +5 -9
- package/src/plugins/builtin/iv-history/series.ts +29 -0
- package/src/plugins/builtin/jobs/headless.ts +28 -3
- package/src/plugins/builtin/jobs/index.tsx +3 -1
- package/src/plugins/builtin/jobs/series.ts +19 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +2 -1
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +0 -39
- package/src/plugins/builtin/macro-day/client.ts +44 -3
- package/src/plugins/builtin/macro-day/headless.ts +13 -10
- package/src/plugins/builtin/macro-day/model.ts +56 -10
- package/src/plugins/builtin/macro-day/pane.tsx +30 -20
- package/src/plugins/builtin/market-heatmap/model.ts +19 -11
- package/src/plugins/builtin/market-heatmap/portfolio.ts +2 -2
- package/src/plugins/builtin/market-movers/client.ts +7 -5
- package/src/plugins/builtin/market-movers/index.tsx +3 -2
- package/src/plugins/builtin/market-movers/model.ts +30 -0
- package/src/plugins/builtin/market-movers/screener.ts +31 -0
- package/src/plugins/builtin/market-valuation/chart-series.ts +29 -69
- package/src/plugins/builtin/market-valuation/index.tsx +3 -3
- package/src/plugins/builtin/market-valuation/indicators.ts +0 -5
- package/src/plugins/builtin/members/headless.ts +7 -2
- package/src/plugins/builtin/members/model.ts +31 -3
- package/src/plugins/builtin/members/pane.tsx +3 -6
- package/src/plugins/builtin/mna/headless.ts +15 -3
- package/src/plugins/builtin/money-markets/index.tsx +2 -0
- package/src/plugins/builtin/money-markets/series.ts +41 -0
- package/src/plugins/builtin/notes/cli.ts +12 -7
- package/src/plugins/builtin/options/view.tsx +1 -1
- package/src/plugins/builtin/options-calculator/inputs.ts +2 -1
- package/src/plugins/builtin/options-positioning/client.ts +7 -3
- package/src/plugins/builtin/options-positioning/headless.ts +1 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +2 -2
- package/src/plugins/builtin/options-scenario/client.ts +2 -1
- package/src/plugins/builtin/options-scenario/headless.ts +1 -0
- package/src/plugins/builtin/options-scenario/hedge.ts +2 -1
- package/src/plugins/builtin/perps/client.ts +40 -6
- package/src/plugins/builtin/perps/equity-row.tsx +33 -11
- package/src/plugins/builtin/perps/freshness.ts +66 -0
- package/src/plugins/builtin/perps/headless.ts +45 -30
- package/src/plugins/builtin/perps/index.tsx +18 -1
- package/src/plugins/builtin/perps/model.ts +20 -5
- package/src/plugins/builtin/perps/pane.tsx +76 -20
- package/src/plugins/builtin/perps/series.ts +76 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +67 -61
- package/src/plugins/builtin/plugin-module.ts +3 -0
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +1 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +273 -0
- package/src/plugins/builtin/portfolio-list/cli/import-13f.ts +109 -0
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +331 -65
- package/src/plugins/builtin/portfolio-list/cli/render.ts +200 -31
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +24 -0
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +120 -47
- package/src/plugins/builtin/portfolio-list/column-values.ts +1 -1
- package/src/plugins/builtin/portfolio-list/grid.tsx +1 -1
- package/src/plugins/builtin/portfolio-list/headless.ts +34 -4
- package/src/plugins/builtin/portfolio-list/imported-from.ts +8 -0
- package/src/plugins/builtin/portfolio-list/live-valuation.ts +6 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -1
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +17 -1
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +1 -1
- package/src/plugins/builtin/portfolio-list/table.tsx +7 -1
- package/src/plugins/builtin/power/client.ts +16 -0
- package/src/plugins/builtin/power/evidence.ts +6 -2
- package/src/plugins/builtin/power/headless.ts +71 -7
- package/src/plugins/builtin/power/index.tsx +4 -1
- package/src/plugins/builtin/power/model.ts +72 -1
- package/src/plugins/builtin/power/pane.tsx +32 -9
- package/src/plugins/builtin/rate-path/headless.ts +11 -3
- package/src/plugins/builtin/realized-vol/headless.ts +5 -0
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +5 -2
- package/src/plugins/builtin/research/event-model.ts +68 -24
- package/src/plugins/builtin/research/events-headless.ts +2 -1
- package/src/plugins/builtin/research/headless.ts +2 -1
- package/src/plugins/builtin/seasonality/client.ts +15 -7
- package/src/plugins/builtin/seasonality/headless.ts +36 -12
- package/src/plugins/builtin/seasonality/index.tsx +2 -2
- package/src/plugins/builtin/seasonality/model.ts +119 -0
- package/src/plugins/builtin/seasonality/pane.tsx +108 -37
- package/src/plugins/builtin/series-inventory.ts +91 -0
- package/src/plugins/builtin/shared/cloud-upgrade.ts +1 -1
- package/src/plugins/builtin/shared/command-tab.ts +30 -12
- package/src/plugins/builtin/shared/earnings-report.ts +55 -0
- package/src/plugins/builtin/shared/fred-points.ts +20 -0
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +101 -0
- package/src/plugins/builtin/shared/open-description.ts +29 -0
- package/src/plugins/builtin/shared/report-freshness.ts +9 -0
- package/src/plugins/builtin/shared/series-warning.ts +5 -0
- package/src/plugins/builtin/short-interest/headless.ts +1 -0
- package/src/plugins/builtin/short-interest/index.tsx +2 -0
- package/src/plugins/builtin/short-interest/series.ts +36 -0
- package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
- package/src/plugins/builtin/short-interest/watch-model.ts +9 -5
- package/src/plugins/builtin/short-volume/headless.ts +1 -0
- package/src/plugins/builtin/short-volume/index.tsx +2 -0
- package/src/plugins/builtin/short-volume/series.ts +23 -0
- package/src/plugins/builtin/social-mentions/index.tsx +2 -0
- package/src/plugins/builtin/social-mentions/series.ts +23 -0
- package/src/plugins/builtin/supply-chain/graph-pane.tsx +3 -2
- package/src/plugins/builtin/supply-chain/pane.tsx +3 -1
- package/src/plugins/builtin/thirteenf/api.ts +1 -0
- package/src/plugins/builtin/thirteenf/data.ts +6 -4
- package/src/plugins/builtin/thirteenf/headless.ts +217 -36
- package/src/plugins/builtin/thirteenf/model.ts +39 -1
- package/src/plugins/builtin/thirteenf/overlap.ts +75 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +10 -1
- package/src/plugins/builtin/thirteenf/report-notes.ts +103 -1
- package/src/plugins/builtin/thirteenf/types.ts +2 -0
- package/src/plugins/builtin/ticker-detail/index.tsx +3 -0
- package/src/plugins/builtin/ticker-detail/overview/components.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/overview/futures-contract.ts +31 -0
- package/src/plugins/builtin/ticker-detail/overview/model.ts +3 -3
- package/src/plugins/builtin/ticker-detail/overview/pane.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +48 -4
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +13 -3
- package/src/plugins/builtin/time-sales/headless.ts +1 -0
- package/src/plugins/builtin/treasury-auctions/headless.ts +16 -2
- package/src/plugins/builtin/treasury-auctions/index.tsx +1 -0
- package/src/plugins/builtin/treasury-auctions/model.ts +8 -0
- package/src/plugins/builtin/vol-surface/headless.ts +1 -0
- package/src/plugins/builtin/volatility/index.tsx +2 -0
- package/src/plugins/builtin/volatility/series.ts +84 -0
- package/src/plugins/builtin/world-venue-map/geo-series.ts +26 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +4 -1
- package/src/plugins/builtin/world-venue-map/index.ts +2 -1
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +8 -4
- package/src/plugins/builtin/world-venue-map/layers.ts +29 -0
- package/src/plugins/builtin/yield-curve/chart.ts +2 -2
- package/src/plugins/builtin/yield-curve/curves.ts +21 -8
- package/src/plugins/builtin/yield-curve/headless.ts +71 -18
- package/src/plugins/builtin/yield-curve/index.tsx +3 -1
- package/src/plugins/builtin/yield-curve/series.ts +86 -0
- package/src/plugins/catalog-browser.ts +2 -1
- package/src/plugins/registry/index.ts +89 -4
- package/src/{plugins/builtin/portfolio-list → portfolio}/position-metrics.ts +6 -6
- package/src/portfolio/position-value.ts +40 -0
- package/src/public/__snapshots__/public-api.test.ts.snap +7 -0
- package/src/public/time-series.ts +17 -0
- package/src/react/auto-refresh.ts +13 -3
- package/src/remote/app-host.tsx +20 -4
- package/src/remote/confirm-change.tsx +85 -0
- package/src/remote/controller-utils.ts +8 -1
- package/src/remote/controller.ts +123 -6
- package/src/remote/pane-screenshot.ts +47 -0
- package/src/remote/schema.ts +62 -0
- package/src/remote/types.ts +30 -0
- package/src/remote/watchlist.ts +306 -0
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/worker.ts +1 -1
- package/src/renderers/dom/data-table/frozen-column.ts +3 -1
- package/src/renderers/dom/data-table/index.tsx +33 -6
- package/src/renderers/dom/data-table/row.tsx +27 -1
- package/src/renderers/dom/desktop/control-styles.ts +63 -1
- package/src/renderers/dom/desktop/controls.tsx +30 -23
- package/src/renderers/dom/desktop/dialog-actions.tsx +170 -0
- package/src/renderers/dom/desktop/icons.tsx +7 -0
- package/src/renderers/dom/dom-ui-host.tsx +3 -0
- package/src/renderers/dom/host/box.tsx +3 -0
- package/src/renderers/dom/host/chart-surface.tsx +6 -2
- package/src/renderers/dom/host/live-frame.ts +6 -2
- package/src/renderers/dom/host/overflow-fade.ts +14 -1
- package/src/renderers/dom/host/size-observer.ts +26 -0
- package/src/renderers/dom/host/surface-3d.tsx +7 -2
- package/src/renderers/dom/host/text.tsx +1 -1
- package/src/renderers/dom/styles.css +61 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +17 -2
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +19 -11
- package/src/renderers/electrobun/view/cli-pane-shot-health.ts +10 -26
- package/src/renderers/electrobun/view/main.tsx +2 -0
- package/src/series/capability.ts +75 -0
- package/src/series/csv.ts +160 -0
- package/src/series/files.ts +107 -0
- package/src/series/registry.ts +198 -0
- package/src/series/resolve.ts +107 -0
- package/src/series/standalone.ts +48 -0
- package/src/series/types.ts +145 -0
- package/src/series/validate.ts +171 -0
- package/src/sources/gloomberb-cloud/index.ts +7 -7
- package/src/sources/gloomberb-cloud/normalizers.ts +1 -0
- package/src/sources/history-result.ts +1 -0
- package/src/sources/listing-symbols.ts +4 -1
- package/src/sources/provider-router/batches.ts +4 -1
- package/src/sources/provider-router/cache.ts +4 -2
- package/src/sources/provider-router/cached-routes.ts +1 -1
- package/src/sources/provider-router/financial-routes.ts +1 -0
- package/src/sources/provider-router/financials.ts +13 -0
- package/src/sources/provider-router/history.ts +43 -25
- package/src/sources/provider-router/search.ts +1 -0
- package/src/sync/core-contributors.ts +1 -0
- package/src/tickers/company-name.ts +75 -0
- package/src/tickers/instrument-kind.ts +25 -0
- package/src/tickers/open-target.ts +3 -2
- package/src/tickers/search/index.ts +45 -7
- package/src/tickers/search/ranking.ts +33 -17
- package/src/tickers/search/types.ts +7 -1
- package/src/tickers/us-listing.ts +34 -0
- package/src/time-series/hooks.ts +9 -0
- package/src/time-series/resolve.ts +95 -16
- package/src/time-series/types.ts +4 -2
- package/src/time-series/use-chart-resolution.ts +32 -0
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +5 -0
- package/src/types/financials.ts +52 -1
- package/src/types/headless.ts +61 -0
- package/src/types/plugin.ts +19 -0
- package/src/types/price-history.ts +7 -0
- package/src/types/ticker.ts +14 -0
- package/src/ui/host.tsx +18 -0
- package/src/ui/live-resize.ts +81 -0
- package/src/ui/live-stretch.ts +121 -0
- package/src/ui/measured-cells.ts +58 -0
- package/src/ui/pane-fill.ts +11 -0
- package/src/utils/basis-points.ts +17 -0
- package/src/utils/cli-output.ts +7 -1
- package/src/utils/concurrency.ts +25 -0
- package/src/utils/crypto-pair.ts +20 -0
- package/src/utils/currency-pair.ts +19 -0
- package/src/utils/decimal-comma.ts +19 -0
- package/src/utils/dom-screenshot-css.ts +265 -0
- package/src/utils/dom-screenshot-rasters.ts +276 -0
- package/src/utils/dom-screenshot.ts +437 -52
- package/src/utils/exchange-rate-snapshot.ts +6 -2
- package/src/utils/exchanges.ts +107 -1
- package/src/utils/format.ts +16 -3
- package/src/utils/fundamentals.ts +66 -0
- package/src/utils/futures-generic.ts +6 -12
- package/src/utils/fx-market-hours.ts +15 -5
- package/src/utils/price-history.ts +99 -42
- package/src/utils/sec-eps-basis.ts +24 -11
- package/src/utils/sec.ts +61 -7
- package/src/utils/utc-time.ts +51 -4
- package/src/plugins/builtin/fx-matrix/freshness.ts +0 -65
- /package/src/{plugins/builtin/cloud → tickers}/starter-symbols.ts +0 -0
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
Quote,
|
|
11
11
|
TickerFinancials,
|
|
12
12
|
} from "../../types/financials";
|
|
13
|
-
import {
|
|
13
|
+
import { currencyPriceMinDigits, formatMarketPrice, formatMarketPriceWithCurrency, quoteFormatOptions } from "../../market-data/market/format";
|
|
14
14
|
import {
|
|
15
15
|
EXTENDED_SESSION_LABELS,
|
|
16
16
|
getExtendedSessionDisplay,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
} from "../../market-data/market/status";
|
|
22
22
|
import { formatCompact, formatDistributionAmount, formatPercent, formatPercentRaw } from "../../utils/format";
|
|
23
23
|
import { withCliServices, withMarketData } from "../context";
|
|
24
|
-
import { isoDate, parsePositiveInt,
|
|
24
|
+
import { isoDate, parsePositiveInt, requireOneArg, takeFlag, takeOption } from "./command-utils";
|
|
25
25
|
import type { BuiltinCliCommandDef } from "../command-options";
|
|
26
26
|
import { CLI_COMMAND_GROUPS, TABLE_SECTION_OPTION } from "../help";
|
|
27
27
|
import {
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
formatFractionPercentCell,
|
|
31
31
|
formatPriceRange,
|
|
32
32
|
} from "../helpers";
|
|
33
|
-
import { cliStyles, renderStats } from "../../utils/cli-output";
|
|
33
|
+
import { cliStyles, colorBySign, renderSection, renderStats } from "../../utils/cli-output";
|
|
34
34
|
import { formatPerShareNumber } from "../../utils/reported-money";
|
|
35
35
|
import {
|
|
36
36
|
analystTargetCurrency,
|
|
@@ -43,7 +43,9 @@ import {
|
|
|
43
43
|
import { getPublishedUsEquityCalendarYears, getPublishedUsEquitySession } from "../../market-data/published-us-sessions";
|
|
44
44
|
import { isQuoteStaleForCurrentSession } from "../../market-data/quotes/freshness";
|
|
45
45
|
import { isOptionsUnavailableError, listedOptionsAlternative, optionsUnavailableTitle } from "../../market-data/options-alternatives";
|
|
46
|
-
import { fundamentalsReportTables, renderFundamentalsReport } from "./ticker";
|
|
46
|
+
import { cliFuturesContract, futuresContractMetrics, fundamentalsReportTables, renderFundamentalsReport } from "./ticker";
|
|
47
|
+
import type { FuturesContractTermsPayload } from "../../api-client/futures-curve";
|
|
48
|
+
import { futuresSettleText, isFuturesInstrument } from "../../market-data/futures-contract";
|
|
47
49
|
import {
|
|
48
50
|
chainHasExpiry,
|
|
49
51
|
chainWithModelFigures,
|
|
@@ -69,6 +71,7 @@ import {
|
|
|
69
71
|
import {
|
|
70
72
|
historyFacts,
|
|
71
73
|
historyFlagNote,
|
|
74
|
+
historyGapNote,
|
|
72
75
|
historyAllRangeYears,
|
|
73
76
|
historyIntervalsByRange,
|
|
74
77
|
historyNotes,
|
|
@@ -77,10 +80,13 @@ import {
|
|
|
77
80
|
historyUnit,
|
|
78
81
|
} from "../history-rows";
|
|
79
82
|
import { CRYPTO_BOARD_HINT, quoteNotes } from "./crypto-hints";
|
|
83
|
+
import { bareFuturesCode } from "../../market-data/futures-board";
|
|
84
|
+
import { newsEmptyMessage } from "./news-hints";
|
|
85
|
+
import { newsFallbackText, newsSearchHeading, newsSearchText, newsSearchTextProblem, searchNewsStories } from "./news-search";
|
|
80
86
|
import { formatUtcTime } from "../../utils/utc-time";
|
|
81
87
|
import { isFiniteNumber } from "../../utils/guards";
|
|
82
88
|
import { exportedFundamentals } from "../../utils/price-earnings";
|
|
83
|
-
import { fundamentalsFreshness, quotesFreshness, rowsFreshness } from "../freshness";
|
|
89
|
+
import { fundamentalsFreshness, fxPairFreshness, quotesFreshness, rowsFreshness } from "../freshness";
|
|
84
90
|
import {
|
|
85
91
|
barHistoryFreshness,
|
|
86
92
|
barResolutionFromDates,
|
|
@@ -92,13 +98,19 @@ import {
|
|
|
92
98
|
import {
|
|
93
99
|
bareListingNote,
|
|
94
100
|
EXCHANGE_OPTION,
|
|
101
|
+
futuresTryLine,
|
|
95
102
|
isNoProviderError,
|
|
96
103
|
listingHeading,
|
|
97
104
|
listingIdentity,
|
|
98
105
|
listingTitle,
|
|
99
106
|
listingVenues,
|
|
107
|
+
listingChoiceNotes,
|
|
108
|
+
listingReadNotes,
|
|
109
|
+
loadBareVenues,
|
|
110
|
+
type ListingVenue,
|
|
100
111
|
loadForListing,
|
|
101
112
|
loadListingQuote,
|
|
113
|
+
noLongerTradingNote,
|
|
102
114
|
notTradedMessage,
|
|
103
115
|
quoteBareListing,
|
|
104
116
|
requireCliListing,
|
|
@@ -106,18 +118,22 @@ import {
|
|
|
106
118
|
type ListingDataRequest,
|
|
107
119
|
type ListingIdentity,
|
|
108
120
|
} from "../listing-arg";
|
|
109
|
-
import { indexRootAlsoLine, indexRootFor, indexRootTryLine } from "../index-roots";
|
|
121
|
+
import { indexRootAlsoLine, indexRootFor, indexRootTryLine, isYieldIndexSymbol } from "../index-roots";
|
|
122
|
+
import { earningsCalendarFailure, earningsChoiceNote, earningsDateLabel, isListingAbroad, noListingCalendarNote } from "../earnings-calendar";
|
|
123
|
+
import { formatBasisPoints, toBasisPoints } from "../../utils/basis-points";
|
|
110
124
|
import { providerMissReason } from "../../sources/provider-errors";
|
|
111
125
|
import { isNotATickerMessage } from "../not-a-ticker";
|
|
126
|
+
import { splitSymbolArgs } from "../symbol-args";
|
|
112
127
|
import { secRegistrantMismatchMessage, SecRegistrantMismatchError, areDifferentCompanies } from "../../sources/sec-registrant";
|
|
113
128
|
import { isCryptoPairSymbol } from "../../utils/crypto-pair";
|
|
114
129
|
import { isUsListingExchange } from "../../utils/exchanges";
|
|
115
|
-
import { nonUsSecListingVenue } from "../../utils/sec";
|
|
130
|
+
import { noSecReportsNotice, nonUsSecListingVenue } from "../../utils/sec";
|
|
116
131
|
import type { MarketContext } from "../types";
|
|
117
132
|
import { windowRows } from "../row-window";
|
|
118
133
|
import { crossRate, describeFxRate, isUsableRate, parseFxRequest } from "../fx-pair";
|
|
119
134
|
import {
|
|
120
135
|
holderListFacts,
|
|
136
|
+
holderListPeriod,
|
|
121
137
|
holderShareBasisMarker,
|
|
122
138
|
holderShareBasisNote,
|
|
123
139
|
holderValueBasis,
|
|
@@ -125,15 +141,27 @@ import {
|
|
|
125
141
|
nonUsHolderCaveat,
|
|
126
142
|
sharedReportDate,
|
|
127
143
|
} from "../../plugins/builtin/holders/report-header";
|
|
128
|
-
import {
|
|
144
|
+
import { formatPercentHeld, holderStakeMarketCap, holderValueCurrency } from "../../plugins/builtin/holders/format";
|
|
145
|
+
import {
|
|
146
|
+
holderReportColumns,
|
|
147
|
+
holderReportDates,
|
|
148
|
+
holderReportNotices,
|
|
149
|
+
holderReportRows,
|
|
150
|
+
holderReportUnits,
|
|
151
|
+
unvaluedHoldersNote,
|
|
152
|
+
} from "../../plugins/builtin/holders/report-rows";
|
|
129
153
|
import { fetchBeneficialOwners } from "../../plugins/builtin/holders/beneficial-client";
|
|
154
|
+
import { classSharesToday } from "../../plugins/builtin/holders/client";
|
|
130
155
|
import { filingFormMatches, SEC_FILING_FETCH_LIMIT } from "../../plugins/builtin/sec/forms";
|
|
156
|
+
import { loadParsedInsiderFilings } from "../../plugins/builtin/insider/client";
|
|
157
|
+
import { insiderEmptyNotice } from "../../plugins/builtin/insider/issuer";
|
|
131
158
|
import {
|
|
132
159
|
BENEFICIAL_REPORT_COLUMNS,
|
|
133
160
|
beneficialCoverageNotices,
|
|
134
161
|
beneficialListComplete,
|
|
135
162
|
beneficialListFacts,
|
|
136
163
|
beneficialListUnreadable,
|
|
164
|
+
beneficialReportLegend,
|
|
137
165
|
beneficialRouteForm,
|
|
138
166
|
buildBeneficialReportRows,
|
|
139
167
|
HOLDER_FORMS,
|
|
@@ -144,7 +172,13 @@ import {
|
|
|
144
172
|
const VALID_RANGES = new Set<TimeRange>(TIME_RANGES);
|
|
145
173
|
const VALID_NEWS_FEEDS = new Set<NewsFeed>(["latest", "top", "breaking", "ticker", "sector", "topic"]);
|
|
146
174
|
|
|
147
|
-
type QuoteCliRecord = Omit<QuoteBatchResult, "error"> & {
|
|
175
|
+
type QuoteCliRecord = Omit<QuoteBatchResult, "error"> & {
|
|
176
|
+
error: string | null;
|
|
177
|
+
/** The listing the row is, named from its saved ticker when the quote names nothing. */
|
|
178
|
+
listing?: { name: string | null };
|
|
179
|
+
/** A futures row's contract, terms and latest confirmed settlement, as `ticker` reads them. */
|
|
180
|
+
futuresContract?: FuturesContractTermsPayload;
|
|
181
|
+
};
|
|
148
182
|
type FinancialsCliData = TickerFinancials & {
|
|
149
183
|
symbol: string;
|
|
150
184
|
exchange: string;
|
|
@@ -161,11 +195,9 @@ function parseNewsFeed(value: string | undefined): NewsQuery["feed"] | undefined
|
|
|
161
195
|
return value && VALID_NEWS_FEEDS.has(value as NewsFeed) ? value as NewsQuery["feed"] : undefined;
|
|
162
196
|
}
|
|
163
197
|
|
|
198
|
+
/** The symbols of a command's arguments, one each however they were separated: spaces, commas or pasted lines. */
|
|
164
199
|
function normalizeSymbols(args: string[]): string[] {
|
|
165
|
-
return args
|
|
166
|
-
.flatMap((arg) => arg.split(","))
|
|
167
|
-
.map((symbol) => symbol.trim().toUpperCase())
|
|
168
|
-
.filter(Boolean);
|
|
200
|
+
return splitSymbolArgs(args).map((symbol) => symbol.toUpperCase());
|
|
169
201
|
}
|
|
170
202
|
|
|
171
203
|
/** What JSON metadata says about the listing a command resolved. */
|
|
@@ -186,10 +218,15 @@ const QUOTE_LEAD_COLUMNS = [
|
|
|
186
218
|
row.error && !value
|
|
187
219
|
// A typo is not an outage: say what is wrong with the symbol itself.
|
|
188
220
|
? isNotATickerMessage(row.error) ? cliStyles.warning("not a ticker") : cliStyles.danger("unavailable")
|
|
189
|
-
:
|
|
221
|
+
: yieldLevelCell(value, row)
|
|
190
222
|
),
|
|
191
223
|
},
|
|
192
|
-
{
|
|
224
|
+
{
|
|
225
|
+
key: "changePercent",
|
|
226
|
+
header: "Chg%",
|
|
227
|
+
align: "right" as const,
|
|
228
|
+
format: (value: unknown, row: QuoteRow) => row.yieldIndex ? yieldChangeCell(row) : formatChangePercentCell(value),
|
|
229
|
+
},
|
|
193
230
|
// Text drops a column no row fills, so a table without an extended print stays as narrow as before.
|
|
194
231
|
extendedColumn("PRE"),
|
|
195
232
|
extendedColumn("POST"),
|
|
@@ -198,6 +235,17 @@ const QUOTE_LEAD_COLUMNS = [
|
|
|
198
235
|
|
|
199
236
|
type QuoteRow = ReturnType<typeof quoteRows>[number];
|
|
200
237
|
|
|
238
|
+
/** A Cboe yield index level is a yield: `5.24%`, not a price. */
|
|
239
|
+
function yieldLevelCell(value: unknown, row: QuoteRow): string {
|
|
240
|
+
const text = String(value ?? "");
|
|
241
|
+
return row.yieldIndex && text ? `${text}%` : text;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** A yield index moves in basis points: `+1.3bp`, from the real previous close, not a percent of a percent. */
|
|
245
|
+
function yieldChangeCell(row: QuoteRow): string {
|
|
246
|
+
return row.changeBasisPoints == null ? "" : colorBySign(formatBasisPoints(row.changeBasisPoints / 100, 1), row.changeBasisPoints);
|
|
247
|
+
}
|
|
248
|
+
|
|
201
249
|
/** The pre-market or after-hours print and its move from the regular close, in its own column. */
|
|
202
250
|
function extendedColumn(session: ExtendedSession) {
|
|
203
251
|
return {
|
|
@@ -240,7 +288,7 @@ function quoteColumns() {
|
|
|
240
288
|
function compareColumns() {
|
|
241
289
|
return [
|
|
242
290
|
...QUOTE_LEAD_COLUMNS,
|
|
243
|
-
{ key: "previousClose", header: "Prev Close", align: "right" as const },
|
|
291
|
+
{ key: "previousClose", header: "Prev Close", align: "right" as const, format: yieldLevelCell },
|
|
244
292
|
// A narrow terminal drops these before it cuts the names short.
|
|
245
293
|
{ key: "dayRange", header: "Day Range", align: "right" as const, optional: true, dropPriority: 1 },
|
|
246
294
|
{ key: "volume", header: "Volume", align: "right" as const, format: formatCountCell, optional: true, dropPriority: 2 },
|
|
@@ -267,13 +315,14 @@ function quoteRows(results: QuoteCliRecord[], now = Date.now()) {
|
|
|
267
315
|
// pre-market or after-hours print is its own column, measured from that session's close.
|
|
268
316
|
const display = getRegularSessionDisplay(quote);
|
|
269
317
|
const extended = getExtendedSessionDisplay(quote);
|
|
318
|
+
const yieldIndex = isYieldIndexSymbol(result.target.symbol);
|
|
270
319
|
// An index level is in points, not in the currency its members trade in.
|
|
271
320
|
const indexPoints = quote?.instrumentType?.trim().toUpperCase() === "INDEX";
|
|
272
321
|
// Pad to two decimals so a column lines up, but never past the currency's minor unit (JPY has none).
|
|
273
322
|
// Points have no minor unit, so a yen-listed index still pads to two.
|
|
274
323
|
const options = {
|
|
275
324
|
...quoteFormatOptions(quote),
|
|
276
|
-
minimumFractionDigits: indexPoints ? 2 :
|
|
325
|
+
minimumFractionDigits: indexPoints ? 2 : currencyPriceMinDigits(quote?.currency),
|
|
277
326
|
};
|
|
278
327
|
const price = (value: number | undefined) => (
|
|
279
328
|
quote && value != null
|
|
@@ -282,13 +331,16 @@ function quoteRows(results: QuoteCliRecord[], now = Date.now()) {
|
|
|
282
331
|
);
|
|
283
332
|
return {
|
|
284
333
|
symbol: result.target.symbol,
|
|
285
|
-
name: quote?.name
|
|
334
|
+
name: quote?.name || result.listing?.name || "",
|
|
286
335
|
price: price(display?.price),
|
|
287
336
|
rawPrice: display?.price ?? null,
|
|
288
337
|
priceBasis: quote?.priceBasis ?? null,
|
|
289
338
|
instrumentType: quote?.instrumentType ?? null,
|
|
290
339
|
change: display?.change ?? null,
|
|
291
340
|
changePercent: roundedPercent(display?.changePercent),
|
|
341
|
+
// A Cboe yield index is read as a yield in percent and moves in basis points.
|
|
342
|
+
yieldIndex,
|
|
343
|
+
changeBasisPoints: yieldIndex && display?.change != null ? toBasisPoints(display.change, 1) : null,
|
|
292
344
|
extendedSession: extended?.session ?? null,
|
|
293
345
|
extendedPrice: price(extended?.price),
|
|
294
346
|
rawExtendedPrice: extended?.price ?? null,
|
|
@@ -349,22 +401,6 @@ function filingRows(filings: SecFilingItem[]) {
|
|
|
349
401
|
}));
|
|
350
402
|
}
|
|
351
403
|
|
|
352
|
-
function holderRows(data: HolderData, ownerTypes?: Set<string>) {
|
|
353
|
-
const holders = ownerTypes
|
|
354
|
-
? data.holders.filter((holder) => ownerTypes.has(holder.ownerType))
|
|
355
|
-
: data.holders;
|
|
356
|
-
return holders.map((holder) => ({
|
|
357
|
-
type: holder.ownerType,
|
|
358
|
-
name: holder.name,
|
|
359
|
-
reportDate: holder.reportDate ?? "",
|
|
360
|
-
shares: holder.shares ?? null,
|
|
361
|
-
value: holder.value ?? null,
|
|
362
|
-
percentHeld: holder.percentHeld ?? null,
|
|
363
|
-
changeShares: holder.changeShares ?? null,
|
|
364
|
-
shareBasis: holder.shareBasis ?? null,
|
|
365
|
-
}));
|
|
366
|
-
}
|
|
367
|
-
|
|
368
404
|
function analystSummary(data: AnalystResearchData): string {
|
|
369
405
|
const target = data.priceTarget;
|
|
370
406
|
const currency = analystTargetCurrency(data);
|
|
@@ -459,6 +495,8 @@ function earningsRows(events: EarningsEvent[]) {
|
|
|
459
495
|
symbol: event.symbol,
|
|
460
496
|
name: event.name,
|
|
461
497
|
date: isoDate(event.earningsDate).slice(0, 10),
|
|
498
|
+
// Whether the company announced the date or it is projected from past reports.
|
|
499
|
+
status: earningsDateLabel(event),
|
|
462
500
|
timing: event.timing,
|
|
463
501
|
epsEstimate: event.epsEstimate,
|
|
464
502
|
epsActual: event.epsActual,
|
|
@@ -467,6 +505,9 @@ function earningsRows(events: EarningsEvent[]) {
|
|
|
467
505
|
}));
|
|
468
506
|
}
|
|
469
507
|
|
|
508
|
+
/** Symbols at most whose venues a command looks up to name the company each bare one went to. */
|
|
509
|
+
const MAX_CHOICE_LOOKUPS = 10;
|
|
510
|
+
|
|
470
511
|
async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1], commandName: "quote" | "compare") {
|
|
471
512
|
const args = [...rawArgs];
|
|
472
513
|
const exchange = takeOption(args, "--exchange") ?? "";
|
|
@@ -479,38 +520,87 @@ async function runQuote(rawArgs: string[], ctx: Parameters<CliCommandDef["execut
|
|
|
479
520
|
symbol, exchange, market, ctx, { ownExchangeWins: symbols.length > 1 },
|
|
480
521
|
)));
|
|
481
522
|
const targets: QuoteSubscriptionTarget[] = listings.map((listing) => ({ symbol: listing.request.symbol, exchange: listing.request.exchange }));
|
|
523
|
+
// A bare symbol's venues load beside its quote, to name the company it was read as when several list it.
|
|
524
|
+
// Each costs a search, so a long list (a portfolio check, not a lookup) names none.
|
|
525
|
+
const venues = new Map(listings.length > MAX_CHOICE_LOOKUPS ? []
|
|
526
|
+
: listings.map((listing) => [listing, loadBareVenues(listing, market)] as const));
|
|
482
527
|
const results = await market.dataProvider.getQuotesBatch(targets, { forceRefresh: ctx.cliOptions.refresh });
|
|
483
528
|
const listingOf = (result: QuoteBatchResult, index: number) => listings[targets.indexOf(result.target)] ?? listings[index]!;
|
|
484
529
|
const request = { command: commandName, noun: "quote" };
|
|
485
|
-
const answers = await Promise.all(results.map((result, index) =>
|
|
530
|
+
const answers = await Promise.all(results.map((result, index) => {
|
|
531
|
+
const listing = listingOf(result, index);
|
|
532
|
+
return answerQuote(result, listing, market, request, ctx.cliOptions.refresh, venues.get(listing) ?? null);
|
|
533
|
+
}));
|
|
486
534
|
const failed = answers[0]?.failure;
|
|
487
535
|
if (listings.length === 1 && failed) ctx.fail(failed.message, failed.details);
|
|
536
|
+
// A futures quote adds its dated settlement and contract terms, as `ticker` prints them; `compare` stays a table.
|
|
537
|
+
const contracts = await Promise.all(answers.map(({ result }) => commandName === "quote" && result.quote
|
|
538
|
+
? cliFuturesContract(result.quote.symbol || result.target.symbol, result.quote.instrumentType)
|
|
539
|
+
: null));
|
|
488
540
|
// Each row names its listing by key (SAN:EPA) and company, so the table needs no line above it.
|
|
489
|
-
const data = answers.map(({ result, listing, failure }) => ({
|
|
541
|
+
const data = answers.map(({ result, listing, failure }, index) => ({
|
|
490
542
|
target: result.target,
|
|
491
543
|
listing: listingMetadata(listingIdentity(listing, result.quote)),
|
|
492
544
|
// The figures the table shows, beside the quote as the source sent it.
|
|
493
545
|
...getQuoteSessionFields(result.quote),
|
|
494
546
|
quote: result.quote,
|
|
547
|
+
...contracts[index] ? { futuresContract: contracts[index] } : {},
|
|
495
548
|
error: failure?.oneLine ?? errorMessage(result.error),
|
|
549
|
+
...yieldIndexFields(result),
|
|
496
550
|
}));
|
|
551
|
+
const now = Date.now();
|
|
497
552
|
const notes = [
|
|
498
553
|
// Which listing a bare symbol was read as, and the index a bare root is not, are in no row.
|
|
499
554
|
...answers.flatMap((answer) => answer.notes),
|
|
555
|
+
// A row that stopped trading long ago reads as a quote that is merely stale.
|
|
556
|
+
...data.flatMap((row) => row.quote
|
|
557
|
+
? noLongerTradingNote(`${row.target.symbol}${row.listing.name ? ` (${row.listing.name})` : ""}`, row.quote, now) ?? []
|
|
558
|
+
: []),
|
|
500
559
|
// Text mode shows only "unavailable" in the cell; the JSON rows already carry each reason.
|
|
501
560
|
...ctx.cliOptions.format === "text" ? quoteNotes(data, { exchange }) : [],
|
|
502
561
|
];
|
|
562
|
+
const columns = commandName === "compare" ? compareColumns() : quoteColumns();
|
|
563
|
+
// Only yield indices: their change is in bp, so the text header drops the percent sign (CSV keeps the percent it holds).
|
|
564
|
+
const yieldsOnly = data.length > 0 && data.every((row) => isYieldIndexSymbol(row.target.symbol));
|
|
503
565
|
ctx.printResult({
|
|
504
566
|
data,
|
|
505
567
|
warnings: notes.length > 0 ? notes : undefined,
|
|
506
568
|
freshness: quotesFreshness(data.map((row) => row.quote)),
|
|
507
569
|
}, {
|
|
508
570
|
rows: quoteRows,
|
|
509
|
-
|
|
571
|
+
details: quoteFuturesContractText,
|
|
572
|
+
columns,
|
|
573
|
+
...(yieldsOnly ? { textColumns: columns.map((column) => column.key === "changePercent" ? { ...column, header: "Chg" } : column) } : {}),
|
|
510
574
|
});
|
|
511
575
|
});
|
|
512
576
|
}
|
|
513
577
|
|
|
578
|
+
/**
|
|
579
|
+
* Under the table, each futures row's Settle line and Contract block, worded
|
|
580
|
+
* as `ticker` words them. Several symbols name theirs in the block's title.
|
|
581
|
+
*/
|
|
582
|
+
function quoteFuturesContractText(data: QuoteCliRecord[]): string {
|
|
583
|
+
return data.flatMap((row) => {
|
|
584
|
+
const terms = row.futuresContract;
|
|
585
|
+
if (!terms || !row.quote) return [];
|
|
586
|
+
const settle: [string, string] = ["Settle", futuresSettleText(terms, row.quote)];
|
|
587
|
+
const contract = futuresContractMetrics(terms, row.quote.symbol || row.target.symbol);
|
|
588
|
+
if (data.length > 1) return [`${renderSection(`${row.target.symbol} Contract`)}\n${renderStats([settle, ...contract])}`];
|
|
589
|
+
return [[renderStats([settle]), ...contract.length ? ["", renderSection("Contract"), renderStats(contract)] : []].join("\n")];
|
|
590
|
+
}).join("\n\n");
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* What a Cboe yield index adds to its JSON row: the level is a yield in percent
|
|
595
|
+
* and its change since the previous close is in basis points (the quote's own
|
|
596
|
+
* `change` is the same move in percentage points).
|
|
597
|
+
*/
|
|
598
|
+
function yieldIndexFields(result: QuoteBatchResult): { unit?: "percent"; changeBasisPoints?: number } {
|
|
599
|
+
if (!isYieldIndexSymbol(result.target.symbol)) return {};
|
|
600
|
+
const change = getRegularSessionDisplay(result.quote)?.change;
|
|
601
|
+
return { unit: "percent", ...(change == null ? {} : { changeBasisPoints: toBasisPoints(change, 1) }) };
|
|
602
|
+
}
|
|
603
|
+
|
|
514
604
|
interface QuoteAnswer {
|
|
515
605
|
result: QuoteBatchResult;
|
|
516
606
|
/** The listing the row is: the one asked for, or the venue a bare symbol was quoted on. */
|
|
@@ -524,8 +614,10 @@ interface QuoteAnswer {
|
|
|
524
614
|
* One symbol's row of `quote` and `compare`. A listing on an exchange its symbol
|
|
525
615
|
* is not listed on says so. A bare symbol the data service read on its home
|
|
526
616
|
* listing abroad names that venue (SXR8 -> XETRA). One no source quotes is
|
|
527
|
-
* quoted on its listings (`quoteBareListing`), else names them
|
|
528
|
-
*
|
|
617
|
+
* quoted on its listings (`quoteBareListing`), else names them, or asks for an
|
|
618
|
+
* exchange when several companies list it (2222); a bare index root (VIX) also
|
|
619
|
+
* names its index (^VIX), and a futures board code a security keeps (GC) its
|
|
620
|
+
* future (GC=F). Venues are only looked up for a
|
|
529
621
|
* symbol with no quote, so a request that succeeds costs no search.
|
|
530
622
|
*/
|
|
531
623
|
async function answerQuote(
|
|
@@ -534,12 +626,18 @@ async function answerQuote(
|
|
|
534
626
|
market: MarketContext,
|
|
535
627
|
request: ListingDataRequest,
|
|
536
628
|
refresh: boolean,
|
|
629
|
+
venues: Promise<ListingVenue[]> | null,
|
|
537
630
|
): Promise<QuoteAnswer> {
|
|
538
631
|
const root = listing.exchange ? null : indexRootFor(listing.symbol);
|
|
539
632
|
// A bare root that quoted something else, such as MOVE (Corvex), points at the index too.
|
|
540
633
|
const also = (quote: Quote) => root && quote.instrumentType?.trim().toUpperCase() !== "INDEX"
|
|
541
634
|
? [indexRootAlsoLine(root, request.command)] : [];
|
|
542
|
-
if (result.quote)
|
|
635
|
+
if (result.quote) {
|
|
636
|
+
// "CAR = Avis Budget Group, NASDAQ (also XETRA, ASX)" names the venue too, so it replaces "SXR8 -> XETRA".
|
|
637
|
+
const choice = venues ? await listingChoiceNotes(listing, market, result.quote, venues) : listingReadNotes(listing);
|
|
638
|
+
const venue = choice.length > 0 ? null : bareListingNote(listing, result.quote);
|
|
639
|
+
return { result, listing, notes: [...choice, venue ?? [], also(result.quote)].flat() };
|
|
640
|
+
}
|
|
543
641
|
const notTraded = await notTradedMessage(listing, market);
|
|
544
642
|
if (notTraded) return { result, listing, failure: { message: notTraded, oneLine: notTraded }, notes: [] };
|
|
545
643
|
if (!isNoProviderError(result.error)) return { result, listing, notes: [] };
|
|
@@ -551,14 +649,18 @@ async function answerQuote(
|
|
|
551
649
|
notes: [other.note, ...also(other.quote)],
|
|
552
650
|
};
|
|
553
651
|
}
|
|
554
|
-
|
|
555
|
-
|
|
652
|
+
// Other listings already name a futures board code's future (GC=F); with none, it is named here.
|
|
653
|
+
const tryLines = [
|
|
654
|
+
root ? indexRootTryLine(root, request.command) : null,
|
|
655
|
+
other || listing.exchange ? null : futuresTryLine(listing.symbol, request),
|
|
656
|
+
].filter((line): line is string => !!line);
|
|
657
|
+
if (!other && tryLines.length === 0) return { result, listing, notes: [] };
|
|
556
658
|
const message = other?.message ?? `${listing.symbol} has no ${request.noun}.`;
|
|
557
|
-
const details = [other?.details,
|
|
659
|
+
const details = [other?.details, ...tryLines].filter(Boolean).join("\n");
|
|
558
660
|
const oneLine = [
|
|
559
661
|
message,
|
|
560
|
-
other ?
|
|
561
|
-
|
|
662
|
+
other ? `${other.heading}: ${other.listings.join(", ")}.` : "",
|
|
663
|
+
...tryLines,
|
|
562
664
|
].filter(Boolean).join(" ");
|
|
563
665
|
return { result, listing, failure: { message, details, oneLine }, notes: [] };
|
|
564
666
|
}
|
|
@@ -581,6 +683,7 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
581
683
|
const raw = requireOneArg(args, HISTORY_USAGE, "symbol", ctx);
|
|
582
684
|
await withMarketData(ctx, async (market) => {
|
|
583
685
|
const listing = await requireCliListing(raw, requestedExchange, market, ctx);
|
|
686
|
+
const venues = loadBareVenues(listing, market);
|
|
584
687
|
const { symbol, exchange } = listing.request;
|
|
585
688
|
const context = { cacheMode: ctx.cliOptions.refresh ? "refresh" as const : "default" as const };
|
|
586
689
|
const load = () => market.dataProvider.getPriceHistoryWithMetadata
|
|
@@ -596,7 +699,7 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
596
699
|
const identity = listingIdentity(listing, quote);
|
|
597
700
|
// Without a quote currency, the listing metadata research reads (as `ticker` does) may still state it.
|
|
598
701
|
const listed = quote?.currency ? quote : await market.dataProvider.getQuoteMetadata?.(symbol, exchange).catch(() => null);
|
|
599
|
-
const unit = historyUnit(listing.
|
|
702
|
+
const unit = historyUnit(listing.key, listed ?? quote);
|
|
600
703
|
const data = historyRows(points, resolution, unit.currency);
|
|
601
704
|
const interval = data[0]?.interval ?? null;
|
|
602
705
|
const decimals = historyPriceDecimals(data, listing.saved?.metadata.assetCategory);
|
|
@@ -608,8 +711,16 @@ async function runHistory(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
608
711
|
...barHistoryFreshness(intraday ? null : barResolutionFromDates(data.map((row) => row.date))),
|
|
609
712
|
observedKey: "date",
|
|
610
713
|
oldest: null,
|
|
714
|
+
// Bars stop arriving on the listing's own trading days, not New York's.
|
|
715
|
+
cadenceExchange: quote?.listingExchangeName || exchange || undefined,
|
|
716
|
+
});
|
|
717
|
+
const gaps = historyGapNote(data, {
|
|
718
|
+
listing: listing.key, exchange: quote?.listingExchangeName || exchange || undefined, instrumentType: quote?.instrumentType,
|
|
611
719
|
});
|
|
612
|
-
const notes = [
|
|
720
|
+
const notes = [
|
|
721
|
+
...await listingChoiceNotes(listing, market, quote, venues),
|
|
722
|
+
...historyNotes(range, interval, data[0]?.date), gaps ?? [], historyFlagNote(data) ?? [],
|
|
723
|
+
].flat();
|
|
613
724
|
const priceColumns: CliResultColumn[] = [
|
|
614
725
|
intraday
|
|
615
726
|
? { key: "date", header: "Time", format: (value) => typeof value === "string" ? formatUtcTime(value) : "" }
|
|
@@ -687,10 +798,12 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
687
798
|
};
|
|
688
799
|
await withMarketData(ctx, async (market) => {
|
|
689
800
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
801
|
+
const venues = loadBareVenues(listing, market);
|
|
690
802
|
const financials = await loadForListing(listing, market, ctx, () => market.dataProvider.getTickerFinancials(
|
|
691
803
|
listing.request.symbol, listing.request.exchange, { cacheMode: ctx.cliOptions.refresh ? "refresh" : "default" },
|
|
692
804
|
), undefined, { command: commandName, noun: commandName === "valuation" ? "valuation data" : commandName });
|
|
693
805
|
const identity = listingIdentity(listing, financials.quote);
|
|
806
|
+
const choice = await listingChoiceNotes(listing, market, financials.quote, venues);
|
|
694
807
|
const data: FinancialsCliData = {
|
|
695
808
|
symbol: listing.key,
|
|
696
809
|
exchange: identity.exchange,
|
|
@@ -703,6 +816,7 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
703
816
|
// JSON reads a multiple over a loss as null with its reason, never as a number.
|
|
704
817
|
data: { ...data, fundamentals: exportedFundamentals(data.fundamentals) },
|
|
705
818
|
freshness,
|
|
819
|
+
...(choice.length > 0 ? { warnings: choice } : {}),
|
|
706
820
|
}, {
|
|
707
821
|
text: () => renderFundamentalsReport(data, view),
|
|
708
822
|
...(tabular ? { tables: selectTables(fundamentalsReportTables(data, view, freshness)) } : {}),
|
|
@@ -731,6 +845,7 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
731
845
|
profile: financials.profile,
|
|
732
846
|
},
|
|
733
847
|
freshness,
|
|
848
|
+
...(choice.length > 0 ? { warnings: choice } : {}),
|
|
734
849
|
}, {
|
|
735
850
|
heading: listingHeading(identity),
|
|
736
851
|
rows: () => rows,
|
|
@@ -740,16 +855,55 @@ async function runFinancials(rawArgs: string[], ctx: Parameters<CliCommandDef["e
|
|
|
740
855
|
});
|
|
741
856
|
}
|
|
742
857
|
|
|
858
|
+
const NEWS_COLUMNS = [
|
|
859
|
+
// UTC with the zone named, as every CLI time prints, rather than the host zone unlabeled.
|
|
860
|
+
{
|
|
861
|
+
key: "publishedAt",
|
|
862
|
+
header: "Published",
|
|
863
|
+
format: (value: unknown) => typeof value === "string" ? formatUtcTime(value) : "",
|
|
864
|
+
},
|
|
865
|
+
{ key: "source", header: "Source", maxWidth: 20 },
|
|
866
|
+
{ key: "title", header: "Title" },
|
|
867
|
+
{ key: "tickers", header: "Tickers", maxWidth: 16 },
|
|
868
|
+
{ key: "url", header: "URL", optional: true },
|
|
869
|
+
];
|
|
870
|
+
|
|
871
|
+
/** Stories matching text, printed as a symbol's news is, under a line saying what they match. */
|
|
872
|
+
async function printNewsSearch(
|
|
873
|
+
text: string,
|
|
874
|
+
articles: NewsArticle[],
|
|
875
|
+
{ reason, empty }: { reason?: string; empty?: string },
|
|
876
|
+
ctx: Parameters<CliCommandDef["execute"]>[1],
|
|
877
|
+
) {
|
|
878
|
+
ctx.printResult({
|
|
879
|
+
data: articles,
|
|
880
|
+
metadata: { ticker: null, search: text, feed: null },
|
|
881
|
+
freshness: rowsFreshness(newsRows(articles), cloudNewsFreshness(await cloudRealtimeAccess())),
|
|
882
|
+
}, {
|
|
883
|
+
heading: newsSearchHeading(text, reason),
|
|
884
|
+
rows: newsRows,
|
|
885
|
+
columns: NEWS_COLUMNS,
|
|
886
|
+
empty: empty ?? `No stories match "${text}".`,
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
|
|
743
890
|
async function runNews(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]>[1]) {
|
|
744
891
|
const args = [...rawArgs];
|
|
745
892
|
const feed = parseNewsFeed(takeOption(args, "--feed"));
|
|
746
893
|
const exchangeOption = takeOption(args, "--exchange");
|
|
894
|
+
// Stories come newest first, so the newest n are the first n either way.
|
|
895
|
+
const limit = ctx.cliOptions.tail ?? ctx.cliOptions.limit ?? 20;
|
|
896
|
+
const text = newsSearchText(args);
|
|
897
|
+
if (text != null) {
|
|
898
|
+
if (exchangeOption || feed) ctx.fail(`${exchangeOption ? "--exchange" : "--feed"} goes with a symbol, not with text to search stories for.`, "Usage: gloomberb news <query>");
|
|
899
|
+
const problem = newsSearchTextProblem(text);
|
|
900
|
+
if (problem) ctx.fail(problem);
|
|
901
|
+
await printNewsSearch(text, await searchNewsStories(text, limit), {}, ctx);
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
747
904
|
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);
|
|
749
905
|
await withMarketData(ctx, async (market) => {
|
|
750
906
|
const listing = args[0] ? await requireCliListing(args[0], exchangeOption, market, ctx) : null;
|
|
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;
|
|
753
907
|
const loadNews = () => market.dataProvider.getNews({
|
|
754
908
|
feed: feed ?? (listing ? "ticker" : "latest"),
|
|
755
909
|
// Still set for news plugins that read the deprecated scope.
|
|
@@ -763,25 +917,33 @@ async function runNews(rawArgs: string[], ctx: Parameters<CliCommandDef["execute
|
|
|
763
917
|
listing ? loadListingQuote(market.dataProvider, listing) : null,
|
|
764
918
|
]);
|
|
765
919
|
const identity = listing ? listingIdentity(listing, quote) : null;
|
|
920
|
+
const resolved = quote !== null || identity?.name != null;
|
|
921
|
+
// A bare word no listing answers to is text, and so is a futures board code (TTF), which resolves
|
|
922
|
+
// to its future: news is filed under company tickers, so the stories that mention the word are the news.
|
|
923
|
+
const word = listing && !listing.exchange && !feed && articles.length === 0 ? newsFallbackText(args[0]!) : null;
|
|
924
|
+
const futuresCode = !!word && !!bareFuturesCode(word) && isFuturesInstrument(listing!.symbol, quote?.instrumentType);
|
|
925
|
+
const reason = !resolved ? "no ticker by that name" : futuresCode ? "a futures code, not a ticker" : null;
|
|
926
|
+
const emptyMessage = (searched: boolean) => listing && identity
|
|
927
|
+
? newsEmptyMessage(searched && word ? word : listing.symbol, listingTitle(identity), quote, resolved, searched)
|
|
928
|
+
: undefined;
|
|
929
|
+
if (word && reason) {
|
|
930
|
+
// The lookup that found no ticker already answered; a search that fails leaves that answer.
|
|
931
|
+
const found = await searchNewsStories(word, limit).catch(() => []);
|
|
932
|
+
await printNewsSearch(word, found, { reason, empty: emptyMessage(true) }, ctx);
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
const empty = emptyMessage(false);
|
|
936
|
+
const read = listing ? listingReadNotes(listing) : [];
|
|
766
937
|
ctx.printResult({
|
|
767
938
|
data: articles,
|
|
939
|
+
...(read.length > 0 ? { warnings: read } : {}),
|
|
768
940
|
metadata: { ticker: listing?.key ?? null, ...(identity ? listingMetadata(identity) : {}), feed: feed ?? null },
|
|
769
941
|
freshness: rowsFreshness(newsRows(articles), cloudNewsFreshness(await cloudRealtimeAccess())),
|
|
770
942
|
}, {
|
|
771
943
|
...(identity ? { heading: listingHeading(identity) } : {}),
|
|
772
944
|
rows: newsRows,
|
|
773
|
-
columns:
|
|
774
|
-
|
|
775
|
-
{
|
|
776
|
-
key: "publishedAt",
|
|
777
|
-
header: "Published",
|
|
778
|
-
format: (value) => typeof value === "string" ? formatUtcTime(value) : "",
|
|
779
|
-
},
|
|
780
|
-
{ key: "source", header: "Source", maxWidth: 20 },
|
|
781
|
-
{ key: "title", header: "Title" },
|
|
782
|
-
{ key: "tickers", header: "Tickers", maxWidth: 16 },
|
|
783
|
-
{ key: "url", header: "URL", optional: true },
|
|
784
|
-
],
|
|
945
|
+
columns: NEWS_COLUMNS,
|
|
946
|
+
...(empty ? { empty } : {}),
|
|
785
947
|
});
|
|
786
948
|
});
|
|
787
949
|
}
|
|
@@ -809,6 +971,7 @@ async function runFilings(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
809
971
|
const raw = requireOneArg(args, "filings <symbol> [--count <n>] [--form <form>]", "symbol", ctx);
|
|
810
972
|
await withMarketData(ctx, async (market) => {
|
|
811
973
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
974
|
+
const venues = loadBareVenues(listing, market);
|
|
812
975
|
const { symbol, exchange } = listing.request;
|
|
813
976
|
// A listing outside the US sends its company to the lookup, which needs its quote first.
|
|
814
977
|
const quotePromise = loadListingQuote(market.dataProvider, listing);
|
|
@@ -825,15 +988,20 @@ async function runFilings(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
825
988
|
quotePromise,
|
|
826
989
|
]);
|
|
827
990
|
const resolved = identity ?? listingIdentity(listing, quote);
|
|
991
|
+
const choice = await listingChoiceNotes(listing, market, quote, venues);
|
|
828
992
|
ctx.printResult({
|
|
829
993
|
data: filings,
|
|
830
994
|
metadata: { ...listingMetadata(resolved), ...(form ? { form } : {}) },
|
|
831
995
|
freshness: rowsFreshness(filingRows(filings), { ...SEC_FILINGS, observedKey: "filingDate" }),
|
|
996
|
+
...(choice.length > 0 ? { warnings: choice } : {}),
|
|
832
997
|
}, {
|
|
833
998
|
heading: listingHeading(resolved),
|
|
834
999
|
rows: filingRows,
|
|
835
1000
|
columns: FILING_COLUMNS,
|
|
836
|
-
|
|
1001
|
+
// A listing abroad is looked up by its company: none found means the company files nothing with the SEC.
|
|
1002
|
+
empty: form ? `No ${form} filings found for ${listingTitle(resolved)}.`
|
|
1003
|
+
: identity ? noSecReportsNotice(resolved)
|
|
1004
|
+
: `No SEC filings found for ${listingTitle(resolved)}.`,
|
|
837
1005
|
});
|
|
838
1006
|
} catch (error) {
|
|
839
1007
|
if (!(error instanceof SecRegistrantMismatchError)) throw error;
|
|
@@ -851,17 +1019,50 @@ async function runFilings(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
851
1019
|
});
|
|
852
1020
|
}
|
|
853
1021
|
|
|
1022
|
+
type Form4Insiders = "available" | "none" | "foreign-private-issuer" | "no-sec-reports";
|
|
1023
|
+
|
|
1024
|
+
/**
|
|
1025
|
+
* Whether the company has Form 4 filings of its own, read as `fn INS` reads
|
|
1026
|
+
* them; null when they could not be read. A listing abroad the SEC lists
|
|
1027
|
+
* nothing for files no SEC reports at all.
|
|
1028
|
+
*/
|
|
1029
|
+
async function loadForm4Insiders(market: MarketContext, listing: CliListing): Promise<Form4Insiders | null> {
|
|
1030
|
+
try {
|
|
1031
|
+
const { parsed, foreignPrivateIssuer, noFilings } = await loadParsedInsiderFilings(market.dataProvider, listing.request.symbol, {
|
|
1032
|
+
limit: 1,
|
|
1033
|
+
exchange: listing.request.exchange,
|
|
1034
|
+
});
|
|
1035
|
+
if (parsed.length > 0) return "available";
|
|
1036
|
+
if (noFilings && nonUsSecListingVenue(listing.request.symbol, listing.request.exchange)) return "no-sec-reports";
|
|
1037
|
+
return foreignPrivateIssuer ? "foreign-private-issuer" : "none";
|
|
1038
|
+
} catch {
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
854
1043
|
/**
|
|
855
1044
|
* Holder lists name institutions and funds; individual insiders rarely appear.
|
|
856
|
-
* The insider share of the company is reported either way, so say it.
|
|
1045
|
+
* The insider share of the company is reported either way, so say it. The
|
|
1046
|
+
* Form 4 trades are pointed to only when the company has some; otherwise the
|
|
1047
|
+
* line says why there are none, in the words `fn INS` uses.
|
|
857
1048
|
*/
|
|
858
|
-
function insiderSummary(
|
|
1049
|
+
function insiderSummary(
|
|
1050
|
+
data: HolderData,
|
|
1051
|
+
ownerTypes: Set<string>,
|
|
1052
|
+
listing: Pick<CliListing, "key">,
|
|
1053
|
+
identity: ListingIdentity,
|
|
1054
|
+
form4: Form4Insiders | null,
|
|
1055
|
+
): string {
|
|
859
1056
|
const held = data.summary?.insidersPercentHeld;
|
|
860
1057
|
const share = held == null || !Number.isFinite(held)
|
|
861
1058
|
? ""
|
|
862
1059
|
: `Insiders hold ${formatFractionPercentCell(held)} of shares outstanding.`;
|
|
863
1060
|
const listed = data.holders.some((holder) => ownerTypes.has(holder.ownerType));
|
|
864
|
-
|
|
1061
|
+
// The listing as the command read it (CAR:ASX), not the source's spelling of it (CAR.AX), which fn does not take.
|
|
1062
|
+
const transactions = form4 === "available"
|
|
1063
|
+
? listed ? "" : `Form 4 transactions: gloomberb fn INS ${listing.key}`
|
|
1064
|
+
: form4 === "no-sec-reports" ? noSecReportsNotice(identity)
|
|
1065
|
+
: form4 ? insiderEmptyNotice(form4 === "foreign-private-issuer") : "";
|
|
865
1066
|
return [share, transactions].filter(Boolean).join("\n");
|
|
866
1067
|
}
|
|
867
1068
|
|
|
@@ -878,14 +1079,18 @@ async function printBeneficialOwners(
|
|
|
878
1079
|
const quotePromise = loadListingQuote(market.dataProvider, listing);
|
|
879
1080
|
const abroad = nonUsSecListingVenue(symbol, exchange) ? listingIdentity(listing, await quotePromise) : null;
|
|
880
1081
|
const request = { form: beneficialRouteForm(form), history, listing: { exchange, name: abroad?.name ?? undefined } };
|
|
881
|
-
const [payload, holders, quote] = await Promise.all([
|
|
1082
|
+
const [payload, holders, quote, financials] = await Promise.all([
|
|
882
1083
|
loadForListing(listing, market, ctx, () => fetchBeneficialOwners(symbol, request)),
|
|
883
1084
|
market.dataProvider.getHolders?.(symbol, exchange).catch(() => null) ?? null,
|
|
884
1085
|
quotePromise,
|
|
1086
|
+
// Today's share count, which the filed percents are set against.
|
|
1087
|
+
market.dataProvider.getTickerFinancials(symbol, exchange).catch(() => null),
|
|
885
1088
|
]);
|
|
886
1089
|
const identity = listingIdentity(listing, quote);
|
|
887
|
-
const
|
|
1090
|
+
const sharesOutstanding = classSharesToday(financials);
|
|
1091
|
+
const rows = buildBeneficialReportRows(payload, { history, holders, sharesOutstanding });
|
|
888
1092
|
const notices = beneficialCoverageNotices(payload.coverage);
|
|
1093
|
+
const legend = beneficialReportLegend(rows, sharesOutstanding);
|
|
889
1094
|
ctx.printResult({
|
|
890
1095
|
data: rows,
|
|
891
1096
|
...(notices.length ? { warnings: notices } : {}),
|
|
@@ -898,12 +1103,16 @@ async function printBeneficialOwners(
|
|
|
898
1103
|
asOf: payload.asOf,
|
|
899
1104
|
coverage: payload.coverage,
|
|
900
1105
|
complete: beneficialListComplete(payload),
|
|
1106
|
+
sharesOutstanding,
|
|
901
1107
|
},
|
|
902
1108
|
}, {
|
|
903
1109
|
heading: `${listingHeading(identity)}${cliStyles.muted(beneficialListFacts(payload, form, rows.length, history).map((fact) => ` · ${fact}`).join(""))}`,
|
|
904
|
-
|
|
1110
|
+
// Text draws each column as the HDS report does; CSV and JSON keep the raw values.
|
|
1111
|
+
textColumns: BENEFICIAL_REPORT_COLUMNS.map((column) => ({ ...column, format: column.textFormat ?? column.format })),
|
|
1112
|
+
...(legend ? { footnote: legend } : {}),
|
|
905
1113
|
columns: [
|
|
906
|
-
...BENEFICIAL_REPORT_COLUMNS,
|
|
1114
|
+
...BENEFICIAL_REPORT_COLUMNS.map(({ key, header, align }) => ({ key, header, ...(align ? { align } : {}) })),
|
|
1115
|
+
{ key: "otherPersons", header: "Other persons" },
|
|
907
1116
|
{ key: "status", header: "Status" },
|
|
908
1117
|
{ key: "filerCik", header: "Filer CIK" },
|
|
909
1118
|
{ key: "accessionNumber", header: "Accession" },
|
|
@@ -911,6 +1120,8 @@ async function printBeneficialOwners(
|
|
|
911
1120
|
],
|
|
912
1121
|
empty: beneficialListUnreadable(payload)
|
|
913
1122
|
? `13D/13G filings for ${listingTitle(identity)} are listed but could not be read.`
|
|
1123
|
+
// The SEC knows no company for a listing abroad that files there under no name of its own.
|
|
1124
|
+
: abroad && !payload.cik ? noSecReportsNotice(identity)
|
|
914
1125
|
: `No 13D/13G filings in the last 4 years for ${listingTitle(identity)}.`,
|
|
915
1126
|
});
|
|
916
1127
|
}
|
|
@@ -932,7 +1143,9 @@ async function runHolders(
|
|
|
932
1143
|
await withMarketData(ctx, async (market) => {
|
|
933
1144
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
934
1145
|
if (form !== "13f") return printBeneficialOwners(listing, market, form, history, ctx);
|
|
935
|
-
const
|
|
1146
|
+
const insiderCommand = commandName === "insider" && ownerTypes != null;
|
|
1147
|
+
const venues = loadBareVenues(listing, market);
|
|
1148
|
+
const [data, quote, form4] = await Promise.all([
|
|
936
1149
|
loadForListing(
|
|
937
1150
|
listing, market, ctx,
|
|
938
1151
|
() => market.dataProvider.getHolders(listing.request.symbol, listing.request.exchange),
|
|
@@ -940,43 +1153,70 @@ async function runHolders(
|
|
|
940
1153
|
{ command: commandName, noun: "holder data" },
|
|
941
1154
|
),
|
|
942
1155
|
loadListingQuote(market.dataProvider, listing),
|
|
1156
|
+
insiderCommand ? loadForm4Insiders(market, listing) : null,
|
|
943
1157
|
]);
|
|
944
1158
|
const identity = listingIdentity(listing, quote);
|
|
945
|
-
const
|
|
946
|
-
|
|
1159
|
+
const choice = await listingChoiceNotes(listing, market, quote, venues);
|
|
1160
|
+
// A London line's values can be dollars from the 13F filings; the listing's currency is not their unit.
|
|
1161
|
+
const valueCurrency = holderValueCurrency(data, "USD")!;
|
|
1162
|
+
// The rows, their units and their order are HDS's, so `holders --csv` and `fn HDS --csv` agree.
|
|
1163
|
+
const rows = holderReportRows(data, {
|
|
1164
|
+
marketCap: holderStakeMarketCap(quote?.marketCap, quote?.currency, valueCurrency),
|
|
1165
|
+
ownerTypes,
|
|
1166
|
+
});
|
|
1167
|
+
const shownRows = windowRows(rows, ctx.cliOptions).rows;
|
|
1168
|
+
const shown = shownRows.length;
|
|
947
1169
|
const institutional = !ownerTypes?.has("insider");
|
|
948
1170
|
const total = institutional ? data.summary?.institutionsCount ?? null : null;
|
|
949
1171
|
const reportDate = sharedReportDate(rows);
|
|
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
1172
|
const valueBasis = rows.length > 0 ? holderValueBasis(reportDate, data.valueBasis, valueCurrency) : null;
|
|
953
1173
|
const positionsBasis = rows.length > 0 ? nonUsHolderCaveat(identity.exchange || data.exchange, data.currency) : null;
|
|
954
1174
|
const shareBasisNote = holderShareBasisNote(data, rows);
|
|
955
1175
|
const markedRows = rows.some((row) => holderShareBasisMarker(row));
|
|
956
1176
|
// The heading names the listing; its unit, date and how much of the list follows on the same line.
|
|
1177
|
+
// One "as of" only when every row shown is of that date; each row carries its own.
|
|
957
1178
|
const facts = holderListFacts({
|
|
958
|
-
currency: valueCurrency,
|
|
1179
|
+
currency: valueCurrency,
|
|
1180
|
+
listingCurrency: data.currency,
|
|
1181
|
+
period: holderListPeriod(shownRows, data.asOf),
|
|
1182
|
+
shown,
|
|
1183
|
+
reported: rows.length,
|
|
1184
|
+
total,
|
|
1185
|
+
ranked: !positionsBasis,
|
|
959
1186
|
});
|
|
1187
|
+
const unvaluedNote = unvaluedHoldersNote(shownRows);
|
|
1188
|
+
const columns = holderReportColumns(shownRows, valueCurrency);
|
|
1189
|
+
// Dated as HDS dates it: by the list's as-of.
|
|
1190
|
+
const freshness = rowsFreshness(shownRows, { ...SEC_FILINGS, basis: "13F filings" }, { asOf: data.asOf ?? null });
|
|
1191
|
+
// Named as fn HDS takes it, so the closing notes point to the same listing.
|
|
1192
|
+
const requested = exchangeOption && !raw.includes(":") ? `${raw}:${exchangeOption.toUpperCase()}` : raw;
|
|
1193
|
+
const { notices } = holderReportNotices({ data, requested, shown: shownRows, reported: rows.length, total });
|
|
960
1194
|
ctx.printResult({
|
|
961
|
-
data,
|
|
1195
|
+
data: rows,
|
|
1196
|
+
...(freshness ? { freshness } : {}),
|
|
962
1197
|
metadata: {
|
|
963
1198
|
...listingMetadata(identity),
|
|
964
|
-
summary: data.summary,
|
|
1199
|
+
summary: data.summary ?? null,
|
|
965
1200
|
currency: data.currency ?? null,
|
|
966
|
-
valueCurrency
|
|
1201
|
+
valueCurrency,
|
|
1202
|
+
units: holderReportUnits(valueCurrency),
|
|
967
1203
|
asOf: data.asOf ?? null,
|
|
1204
|
+
// Each holder's latest period; several when the list mixes quarters.
|
|
1205
|
+
reportDates: holderReportDates(shownRows),
|
|
968
1206
|
shown,
|
|
969
1207
|
reported: rows.length,
|
|
970
1208
|
total,
|
|
971
1209
|
truncated: shown < (total ?? rows.length),
|
|
972
1210
|
valueBasis,
|
|
973
1211
|
positionsBasis,
|
|
1212
|
+
...(insiderCommand ? { form4Insiders: form4 } : {}),
|
|
974
1213
|
},
|
|
1214
|
+
...(choice.length > 0 ? { warnings: choice } : {}),
|
|
975
1215
|
}, {
|
|
976
1216
|
heading: `${listingHeading(identity)}${cliStyles.muted(facts.map((fact) => ` · ${fact}`).join(""))}`,
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
{ key: "
|
|
1217
|
+
columns,
|
|
1218
|
+
textColumns: [
|
|
1219
|
+
{ key: "ownerType", header: "Type" },
|
|
980
1220
|
{ key: "name", header: "Holder" },
|
|
981
1221
|
{ key: "reportDate", header: "Date" },
|
|
982
1222
|
{ key: "shares", header: "Shares", align: "right", format: formatCountCell },
|
|
@@ -992,12 +1232,17 @@ async function runHolders(
|
|
|
992
1232
|
key: "percentHeld",
|
|
993
1233
|
header: "% Held",
|
|
994
1234
|
align: "right",
|
|
995
|
-
format: (value) => isFiniteNumber(value) ?
|
|
1235
|
+
format: (value) => isFiniteNumber(value) ? formatPercentHeld(value) : "",
|
|
996
1236
|
},
|
|
997
1237
|
],
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1238
|
+
// CSV and NDJSON are the HDS report's table and closing lines.
|
|
1239
|
+
tables: {
|
|
1240
|
+
tables: [exportRowsTable("Holders", columns, shownRows)],
|
|
1241
|
+
footer: reportFooterLines({ freshness, notes: notices }),
|
|
1242
|
+
},
|
|
1243
|
+
summary: () => [
|
|
1244
|
+
[valueBasis ? `Value = ${valueBasis}.` : "", positionsBasis ?? "", shareBasisNote ?? "", unvaluedNote ?? ""].filter(Boolean).join(" "),
|
|
1245
|
+
insiderCommand ? insiderSummary(data, ownerTypes, listing, identity, form4) : "",
|
|
1001
1246
|
].filter(Boolean).join("\n"),
|
|
1002
1247
|
empty: `No holders reported for ${listingTitle(identity)}.`,
|
|
1003
1248
|
});
|
|
@@ -1010,6 +1255,7 @@ async function runAnalyst(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1010
1255
|
const raw = requireOneArg(args, "analyst <symbol>", "symbol", ctx);
|
|
1011
1256
|
await withMarketData(ctx, async (market) => {
|
|
1012
1257
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
1258
|
+
const venues = loadBareVenues(listing, market);
|
|
1013
1259
|
const [data, quote] = await Promise.all([
|
|
1014
1260
|
loadForListing(
|
|
1015
1261
|
listing, market, ctx, () => market.dataProvider.getAnalystResearch(listing.request.symbol, listing.request.exchange),
|
|
@@ -1018,6 +1264,7 @@ async function runAnalyst(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1018
1264
|
loadListingQuote(market.dataProvider, listing),
|
|
1019
1265
|
]);
|
|
1020
1266
|
const identity = listingIdentity(listing, quote);
|
|
1267
|
+
const choice = await listingChoiceNotes(listing, market, quote, venues);
|
|
1021
1268
|
ctx.printResult({
|
|
1022
1269
|
data,
|
|
1023
1270
|
metadata: {
|
|
@@ -1029,6 +1276,7 @@ async function runAnalyst(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1029
1276
|
freshness: rowsFreshness(analystRows(data), {
|
|
1030
1277
|
...REPORTED_DATA, basis: "analyst ratings", observedKey: "date", oldest: null,
|
|
1031
1278
|
}, { stale: data.stale === true }),
|
|
1279
|
+
...(choice.length > 0 ? { warnings: choice } : {}),
|
|
1032
1280
|
}, {
|
|
1033
1281
|
heading: listingHeading(identity),
|
|
1034
1282
|
rows: analystRows,
|
|
@@ -1050,6 +1298,7 @@ async function runEvents(rawArgs: string[], ctx: Parameters<CliCommandDef["execu
|
|
|
1050
1298
|
const raw = requireOneArg(args, "events <symbol>", "symbol", ctx);
|
|
1051
1299
|
await withMarketData(ctx, async (market) => {
|
|
1052
1300
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
1301
|
+
const venues = loadBareVenues(listing, market);
|
|
1053
1302
|
const [data, quote] = await Promise.all([
|
|
1054
1303
|
loadForListing(
|
|
1055
1304
|
listing, market, ctx, () => market.dataProvider.getCorporateActions(listing.request.symbol, listing.request.exchange),
|
|
@@ -1058,12 +1307,14 @@ async function runEvents(rawArgs: string[], ctx: Parameters<CliCommandDef["execu
|
|
|
1058
1307
|
loadListingQuote(market.dataProvider, listing),
|
|
1059
1308
|
]);
|
|
1060
1309
|
const identity = listingIdentity(listing, quote);
|
|
1310
|
+
const choice = await listingChoiceNotes(listing, market, quote, venues);
|
|
1061
1311
|
ctx.printResult({
|
|
1062
1312
|
data,
|
|
1063
1313
|
metadata: listingMetadata(identity),
|
|
1064
1314
|
freshness: rowsFreshness(corporateActionRows(data), {
|
|
1065
1315
|
...REPORTED_DATA, basis: "corporate actions", observedKey: "date", oldest: null,
|
|
1066
1316
|
}),
|
|
1317
|
+
...(choice.length > 0 ? { warnings: choice } : {}),
|
|
1067
1318
|
}, {
|
|
1068
1319
|
heading: listingHeading(identity),
|
|
1069
1320
|
rows: corporateActionRows,
|
|
@@ -1111,6 +1362,7 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1111
1362
|
const listing = await requireCliListing(raw, exchangeOption, market, ctx);
|
|
1112
1363
|
const { symbol, exchange } = listing.request;
|
|
1113
1364
|
const refresh = ctx.cliOptions.refresh;
|
|
1365
|
+
const venues = loadBareVenues(listing, market);
|
|
1114
1366
|
const quotePromise = loadListingQuote(market.dataProvider, listing);
|
|
1115
1367
|
const inputsPromise = loadOptionModelInputs(market.dataProvider, listing, quotePromise, refresh);
|
|
1116
1368
|
const { result, chain: loaded } = await loadForListing(listing, market, ctx, async () => {
|
|
@@ -1140,7 +1392,8 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1140
1392
|
const miss = missingExpiryMessage(listing.key, expirationDate, listed);
|
|
1141
1393
|
ctx.fail(miss.message, miss.details);
|
|
1142
1394
|
}
|
|
1143
|
-
const
|
|
1395
|
+
const quote = await quotePromise;
|
|
1396
|
+
const identity = listingIdentity(listing, quote);
|
|
1144
1397
|
const chain = chainWithModelFigures(loaded, inputs);
|
|
1145
1398
|
// A failed refresh falls back to the stored chain, which can be days old.
|
|
1146
1399
|
const refreshWarning = result?.refreshError == null ? null
|
|
@@ -1150,7 +1403,10 @@ async function runOptions(rawArgs: string[], ctx: Parameters<CliCommandDef["exec
|
|
|
1150
1403
|
const modelWarning = inputs.spot == null && (chain.calls.length > 0 || chain.puts.length > 0)
|
|
1151
1404
|
? `IV and delta need a current ${listing.key} quote; those columns are blank`
|
|
1152
1405
|
: null;
|
|
1153
|
-
const warnings = [
|
|
1406
|
+
const warnings = [
|
|
1407
|
+
...await listingChoiceNotes(listing, market, quote, venues),
|
|
1408
|
+
...[refreshWarning ?? sessionWarning, modelWarning].filter((warning): warning is string => warning != null),
|
|
1409
|
+
];
|
|
1154
1410
|
// Dated by the chain's last trade; a chain that failed to refresh is the stored one, stale.
|
|
1155
1411
|
const freshness = rowsFreshness([], { asOf: chain.asOf ?? null }, {
|
|
1156
1412
|
...(chain.dataSource ? { dataSource: chain.dataSource } : {}),
|
|
@@ -1226,12 +1482,16 @@ async function runFx(rawArgs: string[], ctx: Parameters<CliCommandDef["execute"]
|
|
|
1226
1482
|
const asOf = observed.length > 0 ? new Date(Math.min(...observed)).toISOString() : null;
|
|
1227
1483
|
const stale = legs.some((leg) => leg != null && (leg.staleAt <= Date.now() || leg.refreshError != null));
|
|
1228
1484
|
const row = { currency, baseCurrency, rate, asOf, stale, pair, inverse: 1 / rate };
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1485
|
+
// The closing line dates the rate, and says when it is stale, as `fn FXC` does.
|
|
1486
|
+
const freshness = fxPairFreshness(new Map(codes.flatMap((code, index) => {
|
|
1487
|
+
const leg = legs[index];
|
|
1488
|
+
return leg ? [[code, {
|
|
1489
|
+
rate: leg.value,
|
|
1490
|
+
timing: { asOf: leg.asOf, staleAt: leg.staleAt, error: leg.refreshError, delayMinutes: leg.delayMinutes },
|
|
1491
|
+
}] as const] : [];
|
|
1492
|
+
})));
|
|
1493
|
+
ctx.printResult({ data: [row], freshness }, {
|
|
1494
|
+
text: () => describeFxRate(request, rate),
|
|
1235
1495
|
columns: [
|
|
1236
1496
|
{ key: "currency", header: "Currency" },
|
|
1237
1497
|
{ key: "baseCurrency", header: "Base" },
|
|
@@ -1254,24 +1514,55 @@ async function runEarnings(rawArgs: string[], ctx: Parameters<CliCommandDef["exe
|
|
|
1254
1514
|
const listings = await Promise.all(symbols.map((symbol) => requireCliListing(
|
|
1255
1515
|
symbol, exchangeOption, services, ctx, { ownExchangeWins: symbols.length > 1 },
|
|
1256
1516
|
)));
|
|
1257
|
-
const
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
(
|
|
1261
|
-
|
|
1517
|
+
const keys = listings.map((listing) => listing.key);
|
|
1518
|
+
// Each bare symbol's venues cost a search, so a long list (a portfolio check, not a lookup) names none.
|
|
1519
|
+
const venues = new Map(listings.length > MAX_CHOICE_LOOKUPS ? []
|
|
1520
|
+
: listings.map((listing) => [listing, loadBareVenues(listing, services)] as const));
|
|
1521
|
+
let events: EarningsEvent[];
|
|
1522
|
+
try {
|
|
1523
|
+
events = await loadForListing(
|
|
1524
|
+
listings, services, ctx,
|
|
1525
|
+
() => services.dataProvider.getEarningsCalendar(keys),
|
|
1526
|
+
(found) => found.length === 0,
|
|
1527
|
+
);
|
|
1528
|
+
} catch (error) {
|
|
1529
|
+
const failure = earningsCalendarFailure(error, keys);
|
|
1530
|
+
if (failure) ctx.fail(failure);
|
|
1531
|
+
throw error;
|
|
1532
|
+
}
|
|
1533
|
+
const eventOf = (listing: CliListing) => events.find((event) => event.symbol.toUpperCase() === listing.key.toUpperCase());
|
|
1534
|
+
const [abroadNotes, choiceNotes] = await Promise.all([
|
|
1535
|
+
// A listing abroad with no date of its own says so, with its US line's date when that line is the same company.
|
|
1536
|
+
Promise.all(listings.filter((listing) => isListingAbroad(listing) && !eventOf(listing))
|
|
1537
|
+
.map((listing) => noListingCalendarNote(listing, services))),
|
|
1538
|
+
Promise.all(listings.map(async (listing) => {
|
|
1539
|
+
const listed = venues.get(listing);
|
|
1540
|
+
return listed ? earningsChoiceNote(listing, eventOf(listing), await listed) : null;
|
|
1541
|
+
})),
|
|
1542
|
+
]);
|
|
1543
|
+
const noCalendar = abroadNotes.join("\n");
|
|
1544
|
+
// Text prints the listings without a calendar under the table; other formats carry them as warnings.
|
|
1545
|
+
const warnings = [
|
|
1546
|
+
...listings.flatMap(listingReadNotes),
|
|
1547
|
+
...choiceNotes.filter((note): note is string => !!note),
|
|
1548
|
+
...ctx.cliOptions.format === "text" ? [] : abroadNotes,
|
|
1549
|
+
];
|
|
1262
1550
|
ctx.printResult({
|
|
1263
1551
|
data: events,
|
|
1264
1552
|
freshness: rowsFreshness(earningsRows(events), { status: "not-a-feed", basis: "calendar", observedKey: "date", oldest: null }),
|
|
1553
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
1265
1554
|
}, {
|
|
1266
1555
|
rows: earningsRows,
|
|
1267
1556
|
columns: [
|
|
1268
1557
|
{ key: "date", header: "Date" },
|
|
1558
|
+
{ key: "status", header: "Status" },
|
|
1269
1559
|
{ key: "symbol", header: "Symbol" },
|
|
1270
1560
|
{ key: "name", header: "Name" },
|
|
1271
1561
|
{ key: "timing", header: "Timing" },
|
|
1272
1562
|
{ key: "epsEstimate", header: "EPS Est", align: "right", format: formatEpsCell },
|
|
1273
1563
|
{ key: "epsActual", header: "EPS", align: "right", format: formatEpsCell },
|
|
1274
1564
|
],
|
|
1565
|
+
...(noCalendar ? { empty: noCalendar, footnote: noCalendar } : {}),
|
|
1275
1566
|
});
|
|
1276
1567
|
});
|
|
1277
1568
|
}
|
|
@@ -1312,7 +1603,7 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
|
1312
1603
|
sections: [{
|
|
1313
1604
|
title: "Bars",
|
|
1314
1605
|
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.`,
|
|
1606
|
+
`Each range comes in one bar size: ${historyIntervalsByRange()}. The line under the heading, and the --json metadata, give the currency, the bar size actually served, the first and last bar and the bar count. A warning says when the bars are coarser, or start later, than the range asked for, and for a coin, which trades every day, names each day it has no bar for.`,
|
|
1316
1607
|
"A bar whose prices contradict each other (high below the open or close, or low above them) is left blank and flagged, such as high<open, with the count in a warning. Every exported row (--csv, --ndjson, --json) carries currency, interval and flag.",
|
|
1317
1608
|
],
|
|
1318
1609
|
}],
|
|
@@ -1472,7 +1763,13 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
|
1472
1763
|
{ flags: "--history", description: "With --form 13d, 13g or all, every report newest first instead of the latest per filer" },
|
|
1473
1764
|
EXCHANGE_OPTION,
|
|
1474
1765
|
],
|
|
1475
|
-
|
|
1766
|
+
sections: [{
|
|
1767
|
+
title: "Output",
|
|
1768
|
+
lines: [
|
|
1769
|
+
"Holders run largest value first, as in fn HDS. --csv and --json carry the HDS report's columns: values in the currency the header names, shares, and the quarter's change and the stake in percent (0.59 is 0.59%).",
|
|
1770
|
+
],
|
|
1771
|
+
}],
|
|
1772
|
+
examples: ["holders AAPL", "holders SAN:EPA", "holders SAN --csv", "holders CAR --form all", "holders CAR --form 13g --history --json"],
|
|
1476
1773
|
},
|
|
1477
1774
|
execute: (args, ctx) => runHolders(args, ctx, "holders"),
|
|
1478
1775
|
},
|
|
@@ -1519,15 +1816,21 @@ export const marketDataCliCommands: BuiltinCliCommandDef[] = [
|
|
|
1519
1816
|
},
|
|
1520
1817
|
{
|
|
1521
1818
|
name: "news",
|
|
1522
|
-
description: "Fetch market headlines,
|
|
1819
|
+
description: "Fetch market headlines, news for one symbol, or stories matching text",
|
|
1523
1820
|
help: {
|
|
1524
1821
|
group: CLI_COMMAND_GROUPS.companyData,
|
|
1525
|
-
usage: ["news [symbol] [--feed <feed>]"],
|
|
1822
|
+
usage: ["news [symbol] [--feed <feed>]", "news <query>"],
|
|
1526
1823
|
options: [
|
|
1527
1824
|
{ flags: "--feed <feed>", description: "latest, top, or breaking for market news (default latest)" },
|
|
1528
1825
|
EXCHANGE_OPTION,
|
|
1529
1826
|
],
|
|
1530
|
-
|
|
1827
|
+
sections: [{
|
|
1828
|
+
title: "Text",
|
|
1829
|
+
lines: [
|
|
1830
|
+
"Several words, in quotes or not, search the stories that mention them. So does a single word of three letters or more that no listing answers to, and a futures board code such as TTF, a gas benchmark rather than a ticker. A real ticker always gives its own news. The best matches print newest first, 20 unless --limit says otherwise; a search takes neither --feed nor --exchange.",
|
|
1831
|
+
],
|
|
1832
|
+
}],
|
|
1833
|
+
examples: ["news", "news TSLA", "news SAN:EPA", "news TTF", "news \"european gas\"", "news --feed top --limit 10"],
|
|
1531
1834
|
},
|
|
1532
1835
|
execute: runNews,
|
|
1533
1836
|
},
|