gloomberb 0.17.6 → 0.18.0
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 +2 -0
- package/README.zh-CN.md +2 -0
- package/package.json +6 -1
- package/patches/electrobun@1.18.1.patch +74 -0
- package/src/account/auth-model.ts +3 -2
- package/src/account/device-signin-dialog.tsx +38 -13
- package/src/account/device-signin.ts +2 -1
- package/src/account/upgrade-dialog.tsx +6 -2
- package/src/api-client/askg.ts +5 -3
- package/src/api-client/bank-figures.ts +73 -0
- package/src/api-client/credit-boards.ts +21 -1
- package/src/api-client/crypto-markets.ts +88 -1
- package/src/api-client/data.ts +104 -4
- package/src/api-client/debt-maturities.ts +18 -0
- package/src/api-client/equity-screener.ts +3 -0
- package/src/api-client/errors.ts +13 -0
- package/src/api-client/fund-facts.ts +72 -0
- package/src/api-client/futures-curve.ts +26 -3
- package/src/api-client/geo.ts +11 -1
- package/src/api-client/index.ts +25 -2
- package/src/api-client/insider-signals.ts +95 -11
- package/src/api-client/normalizers.ts +1 -0
- package/src/api-client/paths.ts +20 -1
- package/src/api-client/request.ts +11 -3
- package/src/api-client/research-activity.ts +15 -8
- package/src/api-client/socket.ts +6 -3
- package/src/api-client/supply-chain-graph.ts +2 -2
- package/src/api-client/supply-chain.ts +8 -0
- package/src/api-client/tape.ts +12 -0
- package/src/api-client/types.ts +66 -2
- package/src/api-client/upgrade-experiment.ts +4 -3
- package/src/api-client/wall-experiments.ts +5 -3
- package/src/api-client/wall-summary.ts +3 -1
- package/src/api-client/web-experiments.ts +5 -2
- package/src/api-client/yield-curves.ts +17 -0
- package/src/app/command-bar-intro.ts +38 -0
- package/src/app/keybindings/actions.ts +1 -1
- package/src/app/pane-runtime/pane-template-runtime.ts +35 -7
- package/src/app/pane-runtime/plugin-bindings.ts +4 -0
- package/src/app/pane-runtime/ticker-open-runtime.ts +4 -14
- package/src/app/pane-runtime/ticker-open-target.ts +31 -0
- package/src/app/runtime/desktop-deeplink.ts +3 -4
- package/src/app/runtime/plugin-bindings.ts +3 -2
- package/src/app/runtime/startup.ts +3 -4
- package/src/app/runtime/ticker-refresh.ts +13 -12
- package/src/app/runtime/update.ts +3 -2
- package/src/architecture/core-builtin-allowlist.json +0 -3
- package/src/architecture/silent-catch-allowlist.json +3 -0
- package/src/architecture/ui-literals-allowlist.json +186 -0
- package/src/architecture/ui-tokens-allowlist.json +40 -0
- package/src/ask-gloom/pane-context.ts +3 -1
- package/src/brokers/signed-in/catalog.ts +2 -1
- package/src/brokers/signed-in/client.ts +7 -2
- package/src/brokers/signed-in/sign-in-dialog.tsx +120 -26
- package/src/brokers/signed-in/sign-in.ts +22 -20
- package/src/brokers/sync-broker-instance.ts +2 -1
- package/src/capabilities/chart-series.ts +2 -0
- package/src/capabilities/registry.ts +1 -3
- package/src/cli/bank-section.ts +147 -0
- package/src/cli/base-converter.ts +5 -2
- package/src/cli/board-coins.ts +70 -0
- package/src/cli/commands/fred-series.ts +90 -0
- package/src/cli/commands/market.ts +340 -69
- package/src/cli/commands/news-search.ts +35 -2
- package/src/cli/commands/options-chain.ts +7 -5
- package/src/cli/commands/options-leaps.ts +3 -3
- package/src/cli/commands/overview.ts +78 -29
- package/src/cli/commands/plugins.ts +1 -0
- package/src/cli/commands/search.ts +87 -24
- package/src/cli/commands/system.ts +5 -2
- package/src/cli/commands/ticker.ts +337 -60
- package/src/cli/context.ts +2 -1
- package/src/cli/desktop-pane-shot.ts +11 -3
- package/src/cli/earnings-calendar.ts +2 -1
- package/src/cli/errors.ts +10 -0
- package/src/cli/export-source.ts +18 -3
- package/src/cli/glossary.ts +196 -14
- package/src/cli/history-rows.ts +60 -9
- package/src/cli/index-roots.ts +26 -0
- package/src/cli/index.ts +34 -9
- package/src/cli/known-gaps.ts +156 -22
- package/src/cli/listing-arg.ts +156 -12
- package/src/cli/listing-suggestion.ts +4 -2
- package/src/cli/name-entries.ts +76 -0
- package/src/cli/not-a-ticker.ts +15 -6
- package/src/cli/pane-functions/access-gate.ts +22 -0
- package/src/cli/pane-functions/capabilities.ts +62 -4
- package/src/cli/pane-functions/catalog.ts +94 -39
- package/src/cli/pane-functions/data.ts +6 -5
- package/src/cli/pane-functions/discovery.ts +1 -1
- package/src/cli/pane-functions/dom.ts +3 -3
- package/src/cli/pane-functions/freshness.ts +49 -8
- package/src/cli/pane-functions/function-help.ts +13 -10
- package/src/cli/pane-functions/headless.ts +177 -42
- package/src/cli/pane-functions/index.ts +64 -8
- package/src/cli/pane-functions/options.ts +17 -3
- package/src/cli/pane-functions/report-notices.ts +17 -3
- package/src/cli/pane-functions/resolver.ts +48 -13
- package/src/cli/pane-functions/screenshot.ts +124 -23
- package/src/cli/pane-functions/shot-options.ts +68 -0
- package/src/cli/perpetual-contract.ts +3 -1
- package/src/cli/report-tables.ts +10 -2
- package/src/cli/staged-tickers.ts +5 -0
- package/src/cli/statement-filings.ts +261 -0
- package/src/cli/symbol-args.ts +47 -8
- package/src/cli/symbol-list.ts +9 -2
- package/src/cli/symbol-rows.ts +269 -29
- package/src/cli/ticker-resolution.ts +148 -34
- package/src/cli/treasury-yields.ts +86 -0
- package/src/cli/types.ts +3 -0
- package/src/cli/uncovered-instrument.ts +20 -0
- package/src/components/chart/composite/axis-overlays.tsx +16 -9
- package/src/components/chart/composite/composite-chart.tsx +85 -6
- package/src/components/chart/composite/downsample.ts +19 -3
- package/src/components/chart/composite/format.ts +3 -0
- package/src/components/chart/composite/legend.tsx +155 -76
- package/src/components/chart/composite/panel-bitmap.ts +8 -2
- package/src/components/chart/composite/panel-surface.tsx +3 -3
- package/src/components/chart/composite/toolbar.tsx +12 -7
- package/src/components/chart/composite/types.ts +6 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +28 -1
- package/src/components/chart/curve/curve-surface.tsx +1 -1
- package/src/components/chart/static/series.ts +39 -0
- package/src/components/chart/static/trail-chart-surface.tsx +2 -1
- package/src/components/command-bar/commands/collection-list.ts +45 -15
- package/src/components/command-bar/commands/collection.ts +10 -3
- package/src/components/command-bar/commands/registry.ts +2 -2
- package/src/components/command-bar/list/badge.ts +4 -3
- package/src/components/command-bar/list/model.ts +5 -0
- package/src/components/command-bar/list/view.tsx +1 -1
- package/src/components/command-bar/panel/index.tsx +19 -8
- package/src/components/command-bar/panel/native-surface.ts +6 -4
- package/src/components/command-bar/panel/prompt-example.ts +22 -0
- package/src/components/command-bar/routes/root/provider-search.ts +3 -9
- package/src/components/command-bar/routes/root/results.ts +13 -0
- package/src/components/command-bar/routes/root/start-here.ts +90 -0
- package/src/components/command-bar/routes/ticker-search/results.ts +42 -3
- package/src/components/command-bar/routes/ticker-search/route.ts +3 -10
- package/src/components/command-bar/theme-picker.tsx +3 -1
- package/src/components/command-bar/workflow/builtin.ts +5 -1
- package/src/components/command-bar/workflow/collection-actions.ts +21 -12
- package/src/components/command-bar/workflow/ops.ts +21 -1
- package/src/{plugins/builtin/portfolio-list/command-bar.ts → components/command-bar/workflow/portfolio-position.ts} +42 -9
- package/src/components/command-bar/workflow/tickers.ts +6 -4
- package/src/components/feedback-dialog.tsx +1 -1
- package/src/components/form-modal/broker-step.tsx +7 -63
- package/src/components/form-modal/content.tsx +7 -2
- package/src/components/form-modal/field-row.tsx +6 -8
- package/src/components/form-modal/host.tsx +3 -3
- package/src/components/form-modal/model.ts +3 -0
- package/src/components/form-modal/routes.ts +17 -2
- package/src/components/index.ts +6 -6
- package/src/components/input-search-bar.tsx +43 -6
- package/src/components/layout/detached-pane-shell.tsx +0 -1
- package/src/components/layout/floating-pane.tsx +0 -1
- package/src/components/layout/header.tsx +8 -7
- package/src/components/layout/pane/footer/index.tsx +7 -4
- package/src/components/layout/pane/footer/model.ts +28 -2
- package/src/components/layout/pane/header.tsx +9 -19
- package/src/components/layout/pane/index.tsx +0 -1
- package/src/components/layout/pane/instrument-coverage-gate.tsx +57 -0
- package/src/components/layout/pane/pane-tabs.tsx +4 -3
- package/src/components/layout/pane/sizing.ts +9 -0
- package/src/components/layout/pane/ticker-fate-gate.tsx +8 -2
- package/src/components/layout/shell/pane/capture.ts +44 -20
- package/src/components/layout/status-bar.tsx +3 -6
- package/src/components/markdown-text.tsx +17 -2
- package/src/components/market-board/index.tsx +19 -8
- package/src/components/metric-treemap/heat-surface.tsx +2 -7
- package/src/components/onboarding/account-step/qr-panel.tsx +5 -2
- package/src/components/onboarding/onboarding-frame.tsx +24 -21
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +20 -10
- package/src/components/onboarding/wizard-positions.ts +40 -11
- package/src/components/pane-settings-dialog/desktop.tsx +12 -50
- package/src/components/pane-settings-dialog/field-dialogs.tsx +1 -1
- package/src/components/price-performance.tsx +18 -2
- package/src/components/reader/article-reader.tsx +11 -10
- package/src/components/reader/detail-reader.tsx +9 -5
- package/src/components/reader/reader-text.tsx +51 -24
- package/src/components/sign-in-code-panel.tsx +129 -75
- package/src/components/ticker/badge/index.tsx +2 -1
- package/src/components/ui/action-row.tsx +5 -2
- package/src/components/ui/audio-player.tsx +70 -0
- package/src/components/ui/card.tsx +75 -0
- package/src/components/ui/data-table/index.tsx +4 -2
- package/src/components/ui/data-table/types.ts +11 -0
- package/src/components/ui/detail-scroll-body.tsx +1 -1
- package/src/components/ui/dialog-actions.tsx +1 -21
- package/src/components/ui/display.tsx +13 -5
- package/src/components/ui/external-link.tsx +4 -2
- package/src/components/ui/fields.tsx +12 -0
- package/src/components/ui/figure.tsx +5 -3
- package/src/components/ui/frame.tsx +40 -6
- package/src/components/ui/icon.tsx +4 -0
- package/src/components/ui/index.ts +4 -1
- package/src/components/ui/list-view.tsx +8 -0
- package/src/components/ui/prose.tsx +97 -25
- package/src/components/ui/query-bar-keys.tsx +3 -1
- package/src/components/ui/query-bar.tsx +6 -0
- package/src/components/ui/quote.ts +42 -0
- package/src/components/ui/range-track.tsx +15 -2
- package/src/components/ui/reveal-in-scroll-box.ts +21 -3
- package/src/components/ui/select-button.tsx +38 -3
- package/src/components/ui/status.tsx +73 -4
- package/src/components/ui/table-layout.ts +20 -0
- package/src/components/use-pinned-moment.ts +19 -0
- package/src/core/app-runtime.ts +2 -2
- package/src/core/state/app/instrument.ts +18 -1
- package/src/core/state/app/layout.ts +6 -0
- package/src/core/state/app/state.ts +14 -2
- package/src/data/cached-query.ts +1 -1
- package/src/data/config/chart-settings.ts +3 -1
- package/src/data/config/home.ts +4 -1
- package/src/data/config/store/node.ts +9 -4
- package/src/data/config/store/normalize.ts +9 -0
- package/src/data/json-storage.ts +6 -3
- package/src/data/json-ticker-repository.ts +3 -1
- package/src/data/plugin-cache.ts +5 -1
- package/src/data/sqlite/json.ts +3 -1
- package/src/data/ticker-repository.ts +5 -2
- package/src/feedback/diagnostics.ts +2 -6
- package/src/i18n/es.ts +86 -6
- package/src/i18n/index.ts +3 -9
- package/src/i18n/ja.ts +86 -6
- package/src/i18n/ko.ts +86 -6
- package/src/i18n/zh-cn.ts +86 -6
- package/src/i18n/zh-tw.ts +86 -6
- package/src/layout-marketplace/gallery-desktop.tsx +8 -4
- package/src/market-data/bars-per-year.ts +43 -0
- package/src/market-data/bond-funds.ts +85 -0
- package/src/market-data/coordinator/auxiliary.ts +5 -2
- package/src/market-data/coordinator/index.ts +6 -5
- package/src/market-data/futures-contract.ts +102 -5
- package/src/market-data/hooks.tsx +20 -11
- package/src/market-data/market/bar-period.ts +36 -0
- package/src/market-data/market/format.ts +5 -13
- package/src/market-data/market/freshness.ts +1 -3
- package/src/market-data/market/index-level.ts +51 -0
- package/src/market-data/model-dividend-yield.ts +57 -16
- package/src/market-data/quotes/live-valuation.ts +48 -2
- package/src/market-data/realized-volatility.ts +16 -7
- package/src/market-data/tax-equivalent.ts +344 -0
- package/src/news/aggregator.ts +4 -2
- package/src/notifications/notification-log.ts +5 -3
- package/src/plugins/auto-update-node.ts +1 -0
- package/src/plugins/builtin/account-management/footer.ts +1 -1
- package/src/plugins/builtin/account-management/keyboard.ts +3 -0
- package/src/plugins/builtin/account-management/model.ts +1 -0
- package/src/plugins/builtin/account-management/pane.tsx +71 -19
- package/src/plugins/builtin/account-management/trial-cancel.ts +40 -0
- package/src/plugins/builtin/alerts/cli.ts +7 -18
- package/src/plugins/builtin/alerts/constants.ts +8 -0
- package/src/plugins/builtin/alerts/event-rows.ts +16 -0
- package/src/plugins/builtin/alerts/events-pane.tsx +2 -14
- package/src/plugins/builtin/alerts/headless.ts +182 -0
- package/src/plugins/builtin/alerts/index.tsx +3 -0
- package/src/plugins/builtin/alerts/pane.tsx +1 -7
- package/src/plugins/builtin/alerts/storage.ts +17 -1
- package/src/plugins/builtin/analytics/index.tsx +6 -1
- package/src/plugins/builtin/analytics/metrics.ts +47 -9
- package/src/plugins/builtin/analytics/risk-headless.ts +14 -2
- package/src/plugins/builtin/analytics/risk-math.ts +83 -0
- package/src/plugins/builtin/analytics/risk-model.ts +97 -4
- package/src/plugins/builtin/analytics/risk-pane.tsx +11 -6
- package/src/plugins/builtin/analytics/sector-model.ts +14 -3
- package/src/plugins/builtin/awards/headless.ts +6 -5
- package/src/plugins/builtin/awards/index.tsx +1 -1
- package/src/plugins/builtin/backtest/client.ts +38 -8
- package/src/plugins/builtin/backtest/engine.ts +25 -13
- package/src/plugins/builtin/backtest/export.ts +27 -0
- package/src/plugins/builtin/backtest/format.ts +19 -3
- package/src/plugins/builtin/backtest/headless.ts +42 -28
- package/src/plugins/builtin/backtest/index.tsx +3 -0
- package/src/plugins/builtin/backtest/pane.tsx +39 -10
- package/src/plugins/builtin/backtest/report.ts +160 -0
- package/src/plugins/builtin/backtest/rules.ts +2 -0
- package/src/plugins/builtin/backtest/window.ts +112 -0
- package/src/plugins/builtin/bond-calculator/headless.ts +3 -2
- package/src/plugins/builtin/broker-manager/add-flow.tsx +4 -4
- package/src/plugins/builtin/catalysts/headless.ts +24 -13
- package/src/plugins/builtin/catalysts/index.tsx +4 -2
- package/src/plugins/builtin/catalysts/model.ts +18 -1
- package/src/plugins/builtin/catalysts/pane.tsx +2 -2
- package/src/plugins/builtin/cds/client.ts +28 -5
- package/src/plugins/builtin/cds/headless.ts +183 -31
- package/src/plugins/builtin/cds/index.tsx +1 -1
- package/src/plugins/builtin/cds/model.ts +229 -53
- package/src/plugins/builtin/cds/pane.tsx +82 -26
- package/src/plugins/builtin/central-bank-rates/headless.ts +1 -1
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +39 -1
- package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +77 -0
- package/src/plugins/builtin/chart-composer/chart-spec.ts +15 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +22 -2
- package/src/plugins/builtin/chart-composer/editor/desktop-view.tsx +2 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -2
- package/src/plugins/builtin/chart-composer/headless.ts +99 -21
- package/src/plugins/builtin/chart-composer/index.tsx +70 -13
- package/src/plugins/builtin/chart-composer/pane.tsx +65 -15
- package/src/plugins/builtin/chart-composer/presets.ts +79 -11
- package/src/plugins/builtin/chart-composer/series-catalog.ts +50 -14
- package/src/plugins/builtin/chart-composer/series-expression.ts +9 -7
- package/src/plugins/builtin/chart-composer/series-listings.ts +178 -0
- package/src/plugins/builtin/chart-composer/session-range.ts +87 -0
- package/src/plugins/builtin/chat/attachments/desktop.tsx +4 -1
- package/src/plugins/builtin/chat/content/composer-runtime.ts +49 -14
- package/src/plugins/builtin/chat/content/composer.tsx +22 -1
- package/src/plugins/builtin/chat/content/index.tsx +35 -5
- package/src/plugins/builtin/chat/content/layout-metrics.ts +3 -0
- package/src/plugins/builtin/chat/content/shortcuts.ts +32 -13
- package/src/plugins/builtin/chat/content/snapshot.ts +29 -6
- package/src/plugins/builtin/chat/ghost-user.ts +11 -3
- package/src/plugins/builtin/chat/layout.ts +2 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +13 -5
- package/src/plugins/builtin/chat/message/header.tsx +26 -4
- package/src/plugins/builtin/chat/message/pane-chip.tsx +131 -35
- package/src/plugins/builtin/chat/message/render-state.ts +28 -1
- package/src/plugins/builtin/chat/message/terminal.tsx +9 -3
- package/src/plugins/builtin/chat/pane-links/composer.ts +87 -0
- package/src/plugins/builtin/chat/pane-links/model.ts +1 -1
- package/src/plugins/builtin/chat/share/flow.tsx +9 -73
- package/src/plugins/builtin/cloud/askg/controller.ts +5 -3
- package/src/plugins/builtin/cloud/askg/executor.ts +3 -1
- package/src/plugins/builtin/cloud/askg/lock-note.ts +16 -0
- package/src/plugins/builtin/cloud/askg/pane.tsx +6 -2
- package/src/plugins/builtin/cloud/auth-actions.tsx +4 -2
- package/src/plugins/builtin/cloud/company-picker.tsx +21 -26
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +5 -3
- package/src/plugins/builtin/cloud/plugin.tsx +2 -0
- package/src/plugins/builtin/cloud/team/module.tsx +5 -1
- package/src/plugins/builtin/cloud/team/pane.tsx +11 -4
- package/src/plugins/builtin/cloud/team/views.ts +11 -4
- package/src/plugins/builtin/cloud/terminal-relay/engine.ts +9 -4
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +3 -2
- package/src/plugins/builtin/cloud/thesis/detail.tsx +2 -1
- package/src/plugins/builtin/cloud/thesis/exposure.ts +3 -0
- package/src/plugins/builtin/cloud/thesis/prompts.tsx +3 -2
- package/src/plugins/builtin/cloud/thesis/ticker-tab.tsx +2 -1
- package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +2 -1
- package/src/plugins/builtin/cloud/verification-status-widget.tsx +2 -1
- package/src/plugins/builtin/commodities-energy/index.tsx +8 -5
- package/src/plugins/builtin/commodities-energy/view-state.ts +11 -0
- package/src/plugins/builtin/company-attention/headless.ts +15 -7
- package/src/plugins/builtin/company-attention/index.tsx +1 -1
- package/src/plugins/builtin/company-attention/model.ts +49 -4
- package/src/plugins/builtin/company-attention/pane.tsx +11 -9
- package/src/plugins/builtin/company-kpis/headless.ts +4 -3
- package/src/plugins/builtin/company-kpis/index.tsx +2 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +9 -3
- package/src/plugins/builtin/congress-trades/headless.ts +23 -2
- package/src/plugins/builtin/correlation/relationship/pane.tsx +18 -46
- package/src/plugins/builtin/cot/headless.ts +5 -2
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +41 -13
- package/src/plugins/builtin/credit-boards/client.ts +9 -2
- package/src/plugins/builtin/credit-boards/headless.ts +93 -27
- package/src/plugins/builtin/credit-boards/index.tsx +15 -4
- package/src/plugins/builtin/credit-boards/model.ts +105 -6
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +14 -3
- package/src/plugins/builtin/credit-documents/detail.tsx +2 -1
- package/src/plugins/builtin/credit-documents/index.tsx +3 -0
- package/src/plugins/builtin/credit-documents/model.ts +3 -10
- package/src/plugins/builtin/crypto-board/client.ts +152 -2
- package/src/plugins/builtin/crypto-board/headless.ts +138 -19
- package/src/plugins/builtin/crypto-board/index.tsx +5 -3
- package/src/plugins/builtin/crypto-board/model.ts +210 -12
- package/src/plugins/builtin/crypto-board/pane.tsx +148 -9
- package/src/plugins/builtin/crypto-board/series.ts +52 -1
- package/src/plugins/builtin/crypto-board/supply.ts +123 -0
- package/src/plugins/builtin/debt-maturities/client.ts +54 -1
- package/src/plugins/builtin/debt-maturities/headless.ts +27 -3
- package/src/plugins/builtin/debt-maturities/index.tsx +2 -0
- package/src/plugins/builtin/debt-maturities/model.ts +58 -13
- package/src/plugins/builtin/debt-maturities/pane.tsx +47 -7
- package/src/plugins/builtin/dividend-yield/client.ts +42 -36
- package/src/plugins/builtin/dividend-yield/headless.ts +113 -18
- package/src/plugins/builtin/dividend-yield/index.tsx +5 -2
- package/src/plugins/builtin/dividend-yield/pane.tsx +37 -13
- package/src/plugins/builtin/dividend-yield/provider-client.ts +7 -3
- package/src/plugins/builtin/dividend-yield/ranking.ts +187 -0
- package/src/plugins/builtin/dividend-yield/reference-price.ts +28 -5
- package/src/plugins/builtin/dividend-yield/types.ts +6 -1
- package/src/plugins/builtin/dividend-yield/view.ts +1 -1
- package/src/plugins/builtin/docs/blocks.tsx +54 -8
- package/src/plugins/builtin/docs/pane.tsx +8 -13
- package/src/plugins/builtin/earnings/headless.ts +3 -2
- package/src/plugins/builtin/earnings-calls/data.ts +51 -0
- package/src/plugins/builtin/earnings-calls/headless.ts +7 -0
- package/src/plugins/builtin/earnings-calls/index.tsx +1 -0
- package/src/plugins/builtin/earnings-calls/model.ts +97 -0
- package/src/plugins/builtin/earnings-calls/pane.tsx +107 -7
- package/src/plugins/builtin/earnings-calls/transcript-view.tsx +140 -22
- package/src/plugins/builtin/earnings-calls/use-call-playback.ts +194 -0
- package/src/plugins/builtin/econ/calendar-model.ts +22 -0
- package/src/plugins/builtin/econ/calendar-source.ts +35 -0
- package/src/plugins/builtin/econ/index.tsx +40 -8
- package/src/plugins/builtin/econ-statistics/defs.ts +7 -0
- package/src/plugins/builtin/econ-statistics/detail.tsx +8 -1
- package/src/plugins/builtin/econ-statistics/headless.ts +26 -5
- package/src/plugins/builtin/econ-statistics/pane.tsx +4 -2
- package/src/plugins/builtin/econ-statistics/stats.ts +10 -0
- package/src/plugins/builtin/econ-statistics/view.ts +19 -0
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/model.ts +46 -4
- package/src/plugins/builtin/equity-screener/pane.tsx +3 -0
- package/src/plugins/builtin/estimate-revisions/client.ts +32 -22
- package/src/plugins/builtin/estimate-revisions/headless.ts +1 -0
- package/src/plugins/builtin/estimate-revisions/index.tsx +1 -0
- package/src/plugins/builtin/estimate-revisions/pane.tsx +5 -3
- package/src/plugins/builtin/executives/index.tsx +1 -0
- package/src/plugins/builtin/exposure/editor.tsx +3 -3
- package/src/plugins/builtin/fear-greed/index.tsx +9 -0
- package/src/plugins/builtin/filing-events/index.tsx +1 -0
- package/src/plugins/builtin/futures/index.tsx +2 -2
- package/src/plugins/builtin/futures/model.ts +34 -21
- package/src/plugins/builtin/futures/table.tsx +1 -1
- package/src/plugins/builtin/futures/use-board-terms.ts +2 -2
- package/src/plugins/builtin/futures-curve/headless.ts +4 -2
- package/src/plugins/builtin/futures-curve/model.ts +5 -7
- package/src/plugins/builtin/help/index.tsx +55 -8
- package/src/plugins/builtin/help/shortcut-table.tsx +15 -2
- package/src/plugins/builtin/holders/headless.ts +12 -0
- package/src/plugins/builtin/insider/headless.ts +311 -41
- package/src/plugins/builtin/insider/index.tsx +280 -106
- package/src/plugins/builtin/insider/insiders.tsx +224 -0
- package/src/plugins/builtin/insider/signals-cells.tsx +52 -0
- package/src/plugins/builtin/insider/signals-client.ts +135 -11
- package/src/plugins/builtin/insider/signals-model.ts +628 -56
- package/src/plugins/builtin/insider/signals.tsx +283 -83
- package/src/plugins/builtin/ipo-calendar/headless.ts +17 -7
- package/src/plugins/builtin/ipo-calendar/index.tsx +23 -3
- package/src/plugins/builtin/ipo-calendar/model.ts +84 -36
- package/src/plugins/builtin/ipo-calendar/pane.tsx +20 -8
- package/src/plugins/builtin/iv-history/headless.ts +2 -0
- package/src/plugins/builtin/jobs/headless.ts +1 -0
- package/src/plugins/builtin/jobs/index.tsx +1 -0
- package/src/plugins/builtin/jodi/headless.ts +2 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +1 -1
- package/src/plugins/builtin/market-heatmap/headless.ts +1 -0
- package/src/plugins/builtin/market-heatmap/portfolio.ts +3 -1
- package/src/plugins/builtin/market-valuation/pane.tsx +1 -1
- package/src/plugins/builtin/market-valuation/zone-scale/desktop.tsx +31 -57
- package/src/plugins/builtin/mna/distress/headless.ts +2 -1
- package/src/plugins/builtin/mna/headless.ts +14 -2
- package/src/plugins/builtin/mna/index.tsx +1 -0
- package/src/plugins/builtin/news/headless.ts +4 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +5 -3
- package/src/plugins/builtin/news/wire/news/table.tsx +57 -11
- package/src/plugins/builtin/notes/cli.ts +8 -4
- package/src/plugins/builtin/notes/files.ts +66 -20
- package/src/plugins/builtin/notes/migration.ts +18 -6
- package/src/plugins/builtin/notes/owner.tsx +3 -2
- package/src/plugins/builtin/notes/quick-notes-pane.tsx +3 -2
- package/src/plugins/builtin/options-scenario/client.ts +7 -4
- package/src/plugins/builtin/options-scenario/headless.ts +5 -3
- package/src/plugins/builtin/pe-band/index.tsx +1 -1
- package/src/plugins/builtin/pe-band/model.ts +1 -15
- package/src/plugins/builtin/perps/headless.ts +30 -13
- package/src/plugins/builtin/perps/model.ts +45 -3
- package/src/plugins/builtin/perps/pane.tsx +7 -7
- package/src/plugins/builtin/portfolio-list/cell-version.ts +10 -4
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +128 -14
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +108 -46
- package/src/plugins/builtin/portfolio-list/cli/render.ts +368 -45
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +8 -2
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +24 -34
- package/src/plugins/builtin/portfolio-list/grid.tsx +3 -1
- package/src/plugins/builtin/portfolio-list/headless.ts +151 -29
- package/src/plugins/builtin/portfolio-list/income.ts +296 -0
- package/src/plugins/builtin/portfolio-list/metrics.ts +2 -2
- package/src/plugins/builtin/portfolio-list/pane/futures-terms.ts +58 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +27 -10
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +47 -28
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +3 -2
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +10 -3
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +26 -4
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +31 -3
- package/src/plugins/builtin/portfolio-list/table.tsx +25 -5
- package/src/plugins/builtin/power/prices-headless.ts +2 -1
- package/src/plugins/builtin/realized-vol/chart-model.ts +29 -5
- package/src/plugins/builtin/realized-vol/charts.tsx +14 -8
- package/src/plugins/builtin/realized-vol/evidence.ts +2 -0
- package/src/plugins/builtin/realized-vol/headless.ts +6 -4
- package/src/plugins/builtin/realized-vol/model.ts +5 -1
- package/src/plugins/builtin/realized-vol/pane.tsx +31 -11
- package/src/plugins/builtin/realized-vol/settings.ts +1 -1
- package/src/plugins/builtin/research/analyst-headless.ts +4 -0
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -0
- package/src/plugins/builtin/research/event-model.ts +22 -2
- package/src/plugins/builtin/research/headless-schema.ts +1 -0
- package/src/plugins/builtin/research/headless.ts +3 -0
- package/src/plugins/builtin/research/index.tsx +3 -0
- package/src/plugins/builtin/research/relative-valuation-columns.ts +27 -5
- package/src/plugins/builtin/research/relative-valuation-headless.ts +2 -0
- package/src/plugins/builtin/revenue-breakdown/index.tsx +1 -0
- package/src/plugins/builtin/reverse-dcf/headless.ts +9 -6
- package/src/plugins/builtin/reverse-dcf/index.tsx +2 -2
- package/src/plugins/builtin/reverse-dcf/model.ts +51 -4
- package/src/plugins/builtin/reverse-dcf/pane.tsx +23 -10
- package/src/plugins/builtin/risk-factors/headless.ts +7 -0
- package/src/plugins/builtin/risk-factors/index.tsx +1 -0
- package/src/plugins/builtin/scanner/feed.ts +5 -3
- package/src/plugins/builtin/scanner/headless.ts +9 -57
- package/src/plugins/builtin/scanner/index.ts +1 -2
- package/src/plugins/builtin/sec/forms.ts +2 -6
- package/src/plugins/builtin/sec/fund-facts-view.tsx +53 -0
- package/src/plugins/builtin/sec/fund-facts.ts +328 -0
- package/src/plugins/builtin/sec/headless.ts +140 -44
- package/src/plugins/builtin/sec/index.tsx +95 -30
- package/src/plugins/builtin/sec/model.ts +2 -44
- package/src/plugins/builtin/shared/headless-options.ts +10 -0
- package/src/plugins/builtin/shared/locked-rows.tsx +3 -1
- package/src/plugins/builtin/shared/move-rank.ts +108 -0
- package/src/plugins/builtin/shared/report-freshness.ts +25 -9
- package/src/plugins/builtin/shared/scanner-capture.ts +95 -0
- package/src/plugins/builtin/shared/treasury-cutoffs.ts +15 -0
- package/src/plugins/builtin/shared/use-quote-board.ts +1 -0
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -0
- package/src/plugins/builtin/shared/volatility-indices.ts +32 -0
- package/src/plugins/builtin/short-interest/headless.ts +3 -0
- package/src/plugins/builtin/short-interest/watch-model.ts +2 -2
- package/src/plugins/builtin/social-mentions/client.ts +8 -1
- package/src/plugins/builtin/social-mentions/pane.tsx +8 -6
- package/src/plugins/builtin/supply-chain/client.ts +12 -3
- package/src/plugins/builtin/supply-chain/disclosure-quote.tsx +9 -5
- package/src/plugins/builtin/supply-chain/evidence.ts +3 -1
- package/src/plugins/builtin/supply-chain/flow-layout.ts +2 -1
- package/src/plugins/builtin/supply-chain/flow.tsx +23 -16
- package/src/plugins/builtin/supply-chain/graph-client.ts +8 -5
- package/src/plugins/builtin/supply-chain/graph-evidence.ts +1 -1
- package/src/plugins/builtin/supply-chain/graph-model.ts +10 -2
- package/src/plugins/builtin/supply-chain/graph-pane.tsx +9 -7
- package/src/plugins/builtin/supply-chain/graph.tsx +8 -6
- package/src/plugins/builtin/supply-chain/headless.ts +69 -27
- package/src/plugins/builtin/supply-chain/index.tsx +1 -1
- package/src/plugins/builtin/supply-chain/model.ts +76 -15
- package/src/plugins/builtin/supply-chain/pane.tsx +29 -15
- package/src/plugins/builtin/supply-chain/row-evidence.tsx +5 -4
- package/src/plugins/builtin/themes/model.ts +2 -5
- package/src/plugins/builtin/thirteenf/api.ts +50 -2
- package/src/plugins/builtin/thirteenf/data.ts +250 -11
- package/src/plugins/builtin/thirteenf/headless.ts +311 -28
- package/src/plugins/builtin/thirteenf/index.tsx +4 -1
- package/src/plugins/builtin/thirteenf/model.ts +244 -19
- package/src/plugins/builtin/thirteenf/overlap-pane.tsx +133 -33
- package/src/plugins/builtin/thirteenf/overlap.ts +1 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +24 -5
- package/src/plugins/builtin/thirteenf/report-notes.ts +78 -0
- package/src/plugins/builtin/thirteenf/types.ts +49 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -8
- package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +17 -1
- package/src/plugins/builtin/ticker-detail/financials/model.ts +2 -42
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/headless.ts +23 -10
- package/src/plugins/builtin/ticker-detail/overview/components.tsx +27 -3
- package/src/plugins/builtin/ticker-detail/overview/dividend.ts +60 -0
- package/src/plugins/builtin/ticker-detail/overview/model.ts +46 -9
- package/src/plugins/builtin/ticker-detail/overview/pane.tsx +3 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +36 -3
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +18 -19
- package/src/plugins/builtin/time-sales/client.ts +7 -4
- package/src/plugins/builtin/time-sales/headless.ts +71 -22
- package/src/plugins/builtin/time-sales/model.ts +12 -0
- package/src/plugins/builtin/time-sales/pane.tsx +31 -7
- package/src/plugins/builtin/time-sales/window.ts +128 -0
- package/src/plugins/builtin/trace-bonds/client.ts +351 -33
- package/src/plugins/builtin/trace-bonds/headless.ts +60 -22
- package/src/plugins/builtin/trace-bonds/index.tsx +4 -2
- package/src/plugins/builtin/trace-bonds/model.ts +27 -25
- package/src/plugins/builtin/trace-bonds/pane.tsx +63 -45
- package/src/plugins/builtin/treasury-auctions/cache.ts +61 -15
- package/src/plugins/builtin/treasury-auctions/client.ts +45 -10
- package/src/plugins/builtin/treasury-auctions/headless.ts +35 -13
- package/src/plugins/builtin/treasury-auctions/index.tsx +5 -2
- package/src/plugins/builtin/treasury-auctions/model.ts +20 -16
- package/src/plugins/builtin/treasury-auctions/pane.tsx +29 -12
- package/src/plugins/builtin/vol-surface/charts.tsx +2 -1
- package/src/plugins/builtin/vol-surface/headless.ts +3 -2
- package/src/plugins/builtin/vol-surface/pane.tsx +2 -1
- package/src/plugins/builtin/vol-surface/stored.ts +4 -7
- package/src/plugins/builtin/volatility/chart-model.ts +16 -15
- package/src/plugins/builtin/volatility/charts.tsx +5 -5
- package/src/plugins/builtin/volatility/client.ts +73 -2
- package/src/plugins/builtin/volatility/evidence.ts +13 -14
- package/src/plugins/builtin/volatility/headless.ts +13 -7
- package/src/plugins/builtin/volatility/index.tsx +1 -1
- package/src/plugins/builtin/volatility/model.ts +104 -38
- package/src/plugins/builtin/volatility/pane.tsx +42 -24
- package/src/plugins/builtin/volatility/series.ts +22 -13
- package/src/plugins/builtin/world-venue-map/geo-series.ts +9 -0
- package/src/plugins/builtin/world-venue-map/headless.ts +8 -5
- package/src/plugins/builtin/world-venue-map/index.ts +16 -2
- package/src/plugins/builtin/world-venue-map/map.tsx +13 -17
- package/src/plugins/builtin/yield-curve/curves.ts +29 -5
- package/src/plugins/builtin/yield-curve/headless.ts +41 -8
- package/src/plugins/builtin/yield-curve/history.ts +5 -5
- package/src/plugins/builtin/yield-curve/index.tsx +30 -13
- package/src/plugins/compat.ts +3 -1
- package/src/plugins/dependencies.ts +8 -2
- package/src/plugins/host-link.ts +12 -5
- package/src/plugins/installer.ts +16 -7
- package/src/plugins/loader.ts +9 -2
- package/src/plugins/marketplace/activation.ts +10 -4
- package/src/plugins/registry/host-actions.ts +3 -0
- package/src/plugins/registry/index.ts +3 -1
- package/src/{plugins/builtin/portfolio-list → portfolio}/allocation.ts +74 -12
- package/src/{plugins/builtin/portfolio-list → portfolio}/column-values.ts +140 -53
- package/src/portfolio/futures-positions.ts +213 -0
- package/src/portfolio/futures-terms.ts +75 -0
- package/src/portfolio/manual-lots.ts +78 -0
- package/src/portfolio/mutations.ts +54 -10
- package/src/portfolio/position-metrics.ts +44 -10
- package/src/portfolio/valuation-basis.ts +31 -4
- package/src/public/__snapshots__/public-api.test.ts.snap +4 -0
- package/src/public/time-series.ts +2 -1
- package/src/remote/confirm-change.tsx +2 -1
- package/src/remote/server.ts +1 -1
- package/src/remote/watchlist.ts +9 -1
- package/src/renderers/dom/data-table/row.tsx +1 -1
- package/src/renderers/dom/desktop/audio-player.tsx +312 -0
- package/src/renderers/dom/desktop/control-styles.ts +17 -82
- package/src/renderers/dom/desktop/controls.tsx +39 -63
- package/src/renderers/dom/desktop/dialog-actions.tsx +13 -53
- package/src/renderers/dom/desktop/icons.tsx +8 -0
- package/src/renderers/dom/desktop/list-view.tsx +6 -6
- package/src/renderers/dom/desktop/popover.tsx +0 -3
- package/src/renderers/dom/desktop/query-bar.tsx +6 -4
- package/src/renderers/dom/desktop/select-field.tsx +9 -8
- package/src/renderers/dom/desktop/stack-header.ts +3 -0
- package/src/renderers/dom/dom-ui-host.tsx +3 -2
- package/src/renderers/dom/host/chart-surface.tsx +3 -3
- package/src/renderers/dom/host/size-observer.ts +33 -4
- package/src/renderers/dom/host/surface-3d.tsx +3 -2
- package/src/renderers/dom/host/surface-fit.ts +4 -1
- package/src/renderers/dom/host/tabs.tsx +22 -132
- package/src/renderers/dom/styles.css +358 -160
- package/src/renderers/electrobun/bun/core-capabilities.ts +1 -1
- package/src/renderers/electrobun/bun/desktop/backend.ts +1 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +43 -1
- package/src/renderers/electrobun/bun/desktop/workspace/requests.ts +5 -1
- package/src/renderers/electrobun/shared/rpc-codec.ts +7 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +21 -17
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
- package/src/renderers/electrobun/view/desktop/window/bridge.ts +6 -8
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +18 -0
- package/src/series/capability.ts +1 -0
- package/src/series/files.ts +9 -3
- package/src/series/registry.ts +40 -14
- package/src/series/resolve.ts +121 -18
- package/src/series/standalone.ts +3 -2
- package/src/series/types.ts +55 -1
- package/src/series/validate.ts +39 -0
- package/src/shares/api.ts +26 -7
- package/src/shares/bug-report-dialog.tsx +2 -2
- package/src/shares/bug-report-flow.tsx +4 -7
- package/src/shares/capture-flow.ts +5 -4
- package/src/shares/capture-preview.tsx +2 -2
- package/src/shares/chat.ts +7 -27
- package/src/shares/flow.tsx +86 -40
- package/src/shares/location.ts +1 -3
- package/src/shares/pane-image.ts +11 -9
- package/src/shares/pane.ts +5 -2
- package/src/shares/payload.ts +1 -1
- package/src/shares/personal-pane.ts +3 -3
- package/src/shares/portable-layout.ts +9 -2
- package/src/shares/ready.ts +42 -0
- package/src/shares/test-harness.ts +62 -0
- package/src/sources/gloomberb-cloud/batched-lookups.ts +73 -0
- package/src/sources/gloomberb-cloud/index.ts +64 -12
- package/src/sources/gloomberb-cloud/normalizers.ts +31 -2
- package/src/sources/gloomberb-cloud/perpetuals.ts +20 -1
- package/src/sources/history-omitted.ts +34 -0
- package/src/sources/history-result.ts +3 -0
- package/src/sources/history-retention.ts +1 -1
- package/src/sources/provider-errors.ts +43 -1
- package/src/sources/provider-router/batches.ts +3 -2
- package/src/sources/provider-router/cache.ts +2 -1
- package/src/sources/provider-router/cached-routes.ts +9 -3
- package/src/sources/provider-router/history.ts +9 -8
- package/src/sources/provider-router/index.ts +18 -3
- package/src/sources/provider-router/primary.ts +5 -4
- package/src/sources/provider-router/quote-route.ts +3 -1
- package/src/sources/provider-router/routing.ts +2 -1
- package/src/sources/provider-router/search.ts +47 -13
- package/src/sources/provider-router/streaming.ts +7 -4
- package/src/sources/sec-edgar.ts +1 -3
- package/src/state/app/context/index.tsx +9 -13
- package/src/state/app/tab-cursor.ts +149 -12
- package/src/state/hooks/quote-streaming.ts +3 -2
- package/src/state/hooks/session-snapshot.ts +3 -2
- package/src/state/persist-scheduler.ts +12 -4
- package/src/sync/controller.ts +22 -1
- package/src/sync/core-contributors.ts +58 -15
- package/src/team/collections-sync.ts +14 -4
- package/src/team/collections.ts +40 -11
- package/src/team/store.ts +5 -4
- package/src/telemetry/attention-counts.ts +8 -2
- package/src/telemetry/crash-reports-dom.ts +3 -0
- package/src/telemetry/crash-reports-node.ts +7 -4
- package/src/telemetry/crash-reports.ts +11 -11
- package/src/telemetry/usage-counts.ts +16 -12
- package/src/theme/colors.ts +2 -0
- package/src/theme/font-scale.ts +2 -0
- package/src/theme/ui-tokens.ts +71 -0
- package/src/tickers/batched-lookups.ts +186 -0
- package/src/tickers/company-name-listing.ts +97 -0
- package/src/tickers/company-name.ts +13 -0
- package/src/tickers/instrument-coverage.ts +112 -0
- package/src/tickers/listing-fate.ts +49 -9
- package/src/tickers/listing-venues.ts +24 -5
- package/src/tickers/open-target.ts +13 -5
- package/src/tickers/search/index.ts +112 -35
- package/src/tickers/search/ranking.ts +49 -1
- package/src/tickers/terminal-codes.ts +79 -0
- package/src/tickers/ticker-changes.ts +74 -3
- package/src/tickers/us-listing.ts +2 -1
- package/src/time-series/chart-data.ts +79 -8
- package/src/time-series/fundamentals.ts +55 -7
- package/src/time-series/ice-credit.ts +17 -2
- package/src/time-series/live-bars.ts +21 -8
- package/src/time-series/live-quotes.ts +2 -3
- package/src/time-series/price-comparison.ts +6 -1
- package/src/time-series/resolve.ts +65 -10
- package/src/time-series/spec.ts +2 -1
- package/src/time-series/studies.ts +3 -3
- package/src/time-series/types.ts +22 -0
- package/src/time-series/use-chart-resolution.ts +2 -1
- package/src/types/config.ts +7 -0
- package/src/types/data-provider.ts +20 -0
- package/src/types/headless.ts +142 -1
- package/src/types/instrument.ts +6 -0
- package/src/types/plugin.ts +32 -9
- package/src/types/price-history.ts +5 -0
- package/src/types/ticker-changes.ts +19 -0
- package/src/types/ticker.ts +10 -2
- package/src/ui/context-menu.tsx +2 -0
- package/src/ui/host.tsx +40 -4
- package/src/ui/measured-cells.ts +13 -7
- package/src/ui/qr.ts +7 -5
- package/src/updater/index.ts +5 -5
- package/src/utils/calendar-date.ts +86 -27
- package/src/utils/currency-units.ts +17 -0
- package/src/utils/debug-log.ts +1 -1
- package/src/utils/dividend-cash.ts +48 -0
- package/src/utils/dom-screenshot-rasters.ts +3 -1
- package/src/utils/dom-screenshot.ts +14 -10
- package/src/utils/exchange-rate-snapshot.ts +9 -4
- package/src/utils/external-url.ts +1 -0
- package/src/utils/fiscal-period.ts +47 -0
- package/src/utils/format.ts +5 -2
- package/src/utils/forward-dividend.ts +160 -11
- package/src/utils/futures-generic.ts +34 -8
- package/src/utils/http-proxy-response.ts +2 -1
- package/src/utils/local-utc-offset.ts +2 -0
- package/src/utils/log-failure.ts +31 -0
- package/src/utils/option-expiry.ts +12 -12
- package/src/utils/price-history-integrity.ts +56 -0
- package/src/utils/price-history.ts +13 -1
- package/src/utils/sec-filings.ts +245 -0
- package/src/utils/selection-clipboard.ts +5 -2
- package/src/utils/throttled-fetch.ts +1 -0
- package/src/{plugins/builtin/dividend-yield/trailing-cash.ts → utils/trailing-dividend.ts} +55 -8
- package/src/utils/url.ts +1 -0
- package/src/utils/utc-time.ts +11 -0
- package/src/plugins/builtin/chat/share/targets.ts +0 -27
- package/src/renderers/dom/host/top-surface.ts +0 -119
- package/src/shares/dialog.tsx +0 -221
- package/src/shares/live.ts +0 -19
|
@@ -16,10 +16,19 @@ import { getPortfolioPositionMetrics, resolvePortfolioMarketValue, resolvePortfo
|
|
|
16
16
|
import {
|
|
17
17
|
getPortfolioValuationDisplay,
|
|
18
18
|
getPortfolioValuationTime,
|
|
19
|
-
|
|
19
|
+
valuationBasisLabel,
|
|
20
20
|
valuationTimeIso,
|
|
21
|
+
valuesAtSettlement,
|
|
21
22
|
type PortfolioValuationBasis,
|
|
22
23
|
} from "../../../../portfolio/valuation-basis";
|
|
24
|
+
import {
|
|
25
|
+
futuresQuantityText,
|
|
26
|
+
futuresSettlementDisplay,
|
|
27
|
+
isFuturesTicker,
|
|
28
|
+
withFuturesContractSize,
|
|
29
|
+
type PortfolioFuturesTerms,
|
|
30
|
+
} from "../../../../portfolio/futures-positions";
|
|
31
|
+
import { dailyFuturesHistory, loadPortfolioFuturesTerms, type PortfolioFuturesSources } from "../../../../portfolio/futures-terms";
|
|
23
32
|
import { formatUtcDayTime, parseReportTime } from "../../../../utils/utc-time";
|
|
24
33
|
import { quoteFreshnessFields } from "../../shared/report-freshness";
|
|
25
34
|
import { resolvePortfolioTotalsCurrency } from "../summary/totals";
|
|
@@ -33,7 +42,7 @@ import {
|
|
|
33
42
|
type ExtendedSession,
|
|
34
43
|
type ExtendedSessionDisplay,
|
|
35
44
|
} from "../../../../market-data/market/status";
|
|
36
|
-
import { formatPerpetualRate, headlineQuoteDisplay } from "
|
|
45
|
+
import { formatPerpetualRate, headlineQuoteDisplay } from "../../../../portfolio/column-values";
|
|
37
46
|
import type { AppConfig } from "../../../../types/config";
|
|
38
47
|
import type { DataProvider } from "../../../../types/data-provider";
|
|
39
48
|
import type { CliCommandContext } from "../../../../types/plugin";
|
|
@@ -63,13 +72,18 @@ import {
|
|
|
63
72
|
formatAllocationWeight,
|
|
64
73
|
formatTradeUnits,
|
|
65
74
|
hasTargetWeights,
|
|
75
|
+
lotWeight,
|
|
66
76
|
resolvePortfolioCash,
|
|
77
|
+
weightBasisNote,
|
|
67
78
|
type AllocationHolding,
|
|
68
79
|
type PortfolioAllocation,
|
|
69
|
-
} from "
|
|
80
|
+
} from "../../../../portfolio/allocation";
|
|
70
81
|
import { findCachedPortfolioAccount } from "../cached-account";
|
|
71
82
|
import { importedFromText } from "../imported-from";
|
|
72
83
|
import { findNamedCollection } from "../../../../portfolio/collection-name";
|
|
84
|
+
import { isoDateToday } from "../../../../utils/calendar-date";
|
|
85
|
+
import { lastTradeDate } from "../../../../tickers/listing-fate";
|
|
86
|
+
import { DIVIDEND_READ_DEADLINE_MS, incomeGapNote, loadHoldingDividends, portfolioIncome, projectDrip, type DripYear, type HoldingDividend } from "../income";
|
|
73
87
|
|
|
74
88
|
/** Money prices pad to the currency's minor unit ($337.90, not $337.9), as the ticker view does. */
|
|
75
89
|
function priceFormatOptions(quote: Parameters<typeof quoteFormatOptions>[0] & { currency?: string }, assetCategory?: string) {
|
|
@@ -180,6 +194,12 @@ export interface ShowCollectionOptions {
|
|
|
180
194
|
explain?: boolean;
|
|
181
195
|
/** What a portfolio's value is priced at; the latest print when absent. */
|
|
182
196
|
basis?: PortfolioValuationBasis;
|
|
197
|
+
/** `--drip`: project reinvested dividends this many years, with this percent withheld from each. */
|
|
198
|
+
drip?: { years: number; withholding: number };
|
|
199
|
+
/** How long every holding's dividend read may take together; a test shortens it. */
|
|
200
|
+
dividendDeadlineMs?: number;
|
|
201
|
+
/** Where futures' terms and settlements are read; Gloom Cloud's terms and the data provider's daily bars by default. */
|
|
202
|
+
futuresSources?: PortfolioFuturesSources;
|
|
183
203
|
}
|
|
184
204
|
|
|
185
205
|
export async function showCollection(name: string, ctx: CliCommandContext, options: ShowCollectionOptions = {}) {
|
|
@@ -260,6 +280,8 @@ interface PortfolioPositionValue {
|
|
|
260
280
|
dayMove: { change: number; previousValue: number } | null;
|
|
261
281
|
/** The `portfolio show --json` row. */
|
|
262
282
|
row: Record<string, unknown>;
|
|
283
|
+
/** A future's contract size and settlement from its terms; null for any other holding. */
|
|
284
|
+
futures: PortfolioFuturesTerms | null;
|
|
263
285
|
}
|
|
264
286
|
|
|
265
287
|
/** What a row's value is priced at: exported after every other field, so existing columns keep their places. */
|
|
@@ -269,6 +291,8 @@ interface PortfolioValuationFields {
|
|
|
269
291
|
valuationPrice: number | null;
|
|
270
292
|
/** An ISO instant in UTC, or an ISO date for a close whose hour the venue's calendar does not give. */
|
|
271
293
|
valuationAsOf: string | null;
|
|
294
|
+
/** Only on a future valued at its exchange settlement, which `valuationAsOf` dates by its session. */
|
|
295
|
+
valuationPriceKind?: "settle";
|
|
272
296
|
}
|
|
273
297
|
|
|
274
298
|
export interface PortfolioValuation {
|
|
@@ -281,13 +305,40 @@ export interface PortfolioValuation {
|
|
|
281
305
|
unavailableCost: Set<string>;
|
|
282
306
|
unavailableMarketValue: Set<string>;
|
|
283
307
|
brokerPnlSymbols: Set<string>;
|
|
308
|
+
/** Futures held without a contract size: no value, cost or P&L, said once instead of as each. */
|
|
309
|
+
unknownContractSize: Set<string>;
|
|
310
|
+
/** Futures valued at the close without a confirmed settlement to value them at. */
|
|
311
|
+
unsettled: Set<string>;
|
|
312
|
+
/** Long and short market value of the priced lots, and their net and gross, in the portfolio's currency. */
|
|
313
|
+
exposure: PortfolioExposure;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** A book's exposure: what its longs and its shorts are worth (short negative), net and gross. */
|
|
317
|
+
export interface PortfolioExposure {
|
|
318
|
+
long: number;
|
|
319
|
+
short: number;
|
|
320
|
+
net: number;
|
|
321
|
+
gross: number;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** The exposure of lots at their signed market values; an unpriced lot (null) is left out, as the total leaves it. */
|
|
325
|
+
function portfolioExposure(values: Iterable<number | null | undefined>): PortfolioExposure {
|
|
326
|
+
let long = 0;
|
|
327
|
+
let short = 0;
|
|
328
|
+
for (const value of values) {
|
|
329
|
+
if (value == null || !Number.isFinite(value)) continue;
|
|
330
|
+
if (value >= 0) long += value;
|
|
331
|
+
else short += value;
|
|
332
|
+
}
|
|
333
|
+
return { long, short, net: long + short, gross: long - short };
|
|
284
334
|
}
|
|
285
335
|
|
|
286
336
|
/**
|
|
287
337
|
* Values every position of a portfolio in the portfolio's currency, as
|
|
288
338
|
* `portfolio show` prints it: cost basis, market value and unrealized P&L
|
|
289
339
|
* from the current quote at `basis` (the latest print unless given), or the
|
|
290
|
-
* broker's snapshot where that is the basis.
|
|
340
|
+
* broker's snapshot where that is the basis. A future counts contracts of the
|
|
341
|
+
* size `futures` gives for it, and at the close is valued at its settlement.
|
|
291
342
|
*/
|
|
292
343
|
export async function valuePortfolioPositions({
|
|
293
344
|
tickers,
|
|
@@ -297,6 +348,7 @@ export async function valuePortfolioPositions({
|
|
|
297
348
|
baseCurrency,
|
|
298
349
|
toBase,
|
|
299
350
|
basis = "latest-print",
|
|
351
|
+
futures,
|
|
300
352
|
}: {
|
|
301
353
|
tickers: readonly TickerRecord[];
|
|
302
354
|
quotes: ReadonlyMap<string, CollectionQuote>;
|
|
@@ -305,6 +357,8 @@ export async function valuePortfolioPositions({
|
|
|
305
357
|
baseCurrency: string;
|
|
306
358
|
toBase: (value: number, fromCurrency: string) => Promise<number>;
|
|
307
359
|
basis?: PortfolioValuationBasis;
|
|
360
|
+
/** Each future's contract size and settlement (loadPortfolioFuturesTerms), by symbol. */
|
|
361
|
+
futures?: ReadonlyMap<string, PortfolioFuturesTerms>;
|
|
308
362
|
}): Promise<PortfolioValuation> {
|
|
309
363
|
const id = portfolioId;
|
|
310
364
|
let totalPnl = 0;
|
|
@@ -312,16 +366,23 @@ export async function valuePortfolioPositions({
|
|
|
312
366
|
const unavailableCost = new Set<string>();
|
|
313
367
|
const unavailableMarketValue = new Set<string>();
|
|
314
368
|
const brokerPnlSymbols = new Set<string>();
|
|
369
|
+
const unknownContractSize = new Set<string>();
|
|
370
|
+
const unsettled = new Set<string>();
|
|
315
371
|
const values: PortfolioPositionValue[] = [];
|
|
316
372
|
const holdings: AllocationHolding[] = [];
|
|
317
373
|
const known = (value: number | null | undefined): number | null => value != null && Number.isFinite(value) ? value : null;
|
|
318
374
|
|
|
319
375
|
for (const ticker of tickers) {
|
|
320
376
|
const quote = quotes.get(ticker.metadata.ticker);
|
|
321
|
-
const
|
|
377
|
+
const futuresTerms = isFuturesTicker(ticker) ? futures?.get(ticker.metadata.ticker) ?? { size: null, settlement: null } : null;
|
|
378
|
+
// A lot entered without its contract size takes the one the terms state; the entries keep the saved record.
|
|
379
|
+
const sized = withFuturesContractSize(ticker, futuresTerms?.size);
|
|
380
|
+
const positions = sized.metadata.positions.filter((position) => position.portfolio === id && position.shares !== 0);
|
|
322
381
|
const displayedQuote = getActiveQuoteDisplay(quote);
|
|
323
382
|
const activeQuote = displayedQuote && Number.isFinite(displayedQuote.price) ? displayedQuote : null;
|
|
324
|
-
|
|
383
|
+
// At the close a future's Last is the settlement it is valued at, and its move the one from the settlement before.
|
|
384
|
+
const settledHeadline = basis === "regular-close" && futuresTerms?.settlement ? futuresSettlementDisplay(futuresTerms.settlement) : null;
|
|
385
|
+
const { headline, extended } = settledHeadline ? { headline: settledHeadline, extended: null } : collectionQuoteHeadline(quote);
|
|
325
386
|
|
|
326
387
|
if (positions.length === 0) {
|
|
327
388
|
const quoteCurrency = quote?.currency || ticker.metadata.currency || baseCurrency;
|
|
@@ -329,8 +390,10 @@ export async function valuePortfolioPositions({
|
|
|
329
390
|
const priced = basis === "regular-close" ? headline : activeQuote;
|
|
330
391
|
const unitPrice = priced ? known(await toBase(priced.price, quoteCurrency)) : null;
|
|
331
392
|
holdings.push({ symbol: ticker.metadata.ticker, held: false, marketValue: 0, units: 0, unitPrice });
|
|
332
|
-
values.push({ ticker, quote, activeQuote, headline, extended, position: null, metrics: null, pnl: null, dayMove: null,
|
|
333
|
-
valuation: { valuationBasis: basis, valuationPrice: known(priced?.price),
|
|
393
|
+
values.push({ ticker, quote, activeQuote, headline, extended, position: null, metrics: null, pnl: null, dayMove: null, futures: futuresTerms,
|
|
394
|
+
valuation: { valuationBasis: basis, valuationPrice: known(priced?.price),
|
|
395
|
+
valuationAsOf: settledHeadline ? futuresTerms!.settlement!.date : valuationTimeIso(getPortfolioValuationTime(quote, basis)),
|
|
396
|
+
...(settledHeadline ? { valuationPriceKind: "settle" as const } : {}) },
|
|
334
397
|
row: { symbol: ticker.metadata.ticker, ...listingColumns(ticker, quote), exchange: ticker.metadata.exchange, shares: null, avgCost: null,
|
|
335
398
|
positionCurrency: null, quotePrice: known(headline?.price), ...quoteMoveColumns(headline), ...extendedQuoteFields(extended), quoteCurrency: quote?.currency ?? null,
|
|
336
399
|
volume: known(quote?.volume), costBasis: null, marketValue: null, unrealizedPnl: null, baseCurrency: currency } });
|
|
@@ -342,7 +405,9 @@ export async function valuePortfolioPositions({
|
|
|
342
405
|
for (const position of positions) {
|
|
343
406
|
const quoteCurrency = quote?.currency || ticker.metadata.currency || baseCurrency;
|
|
344
407
|
const metrics = getPortfolioPositionMetrics({ ...ticker, metadata: { ...ticker.metadata, positions: [position] } }, id, quoteCurrency, undefined, quote);
|
|
345
|
-
const
|
|
408
|
+
const settlement = futuresTerms?.settlement ?? null;
|
|
409
|
+
const valuationQuote = getPortfolioValuationDisplay(metrics, quote, basis, settlement);
|
|
410
|
+
const atSettlement = valuesAtSettlement(metrics, basis);
|
|
346
411
|
const positionCurrency = metrics.positionCurrency;
|
|
347
412
|
const costBasisBase = positionCurrency ? await toBase(metrics.totalCost, positionCurrency) : Number.NaN;
|
|
348
413
|
|
|
@@ -354,6 +419,8 @@ export async function valuePortfolioPositions({
|
|
|
354
419
|
const selectedPnl = resolvePortfolioPositionPnl(baseMetrics,
|
|
355
420
|
valuationQuote ? await toBase(valuationQuote.price, quoteCurrency) : null);
|
|
356
421
|
const pnl = selectedPnl.value;
|
|
422
|
+
if (metrics.unknownContractSize) unknownContractSize.add(ticker.metadata.ticker);
|
|
423
|
+
else if (atSettlement && !settlement) unsettled.add(ticker.metadata.ticker);
|
|
357
424
|
if (!metrics.hasCostBasis) unavailableCost.add(ticker.metadata.ticker);
|
|
358
425
|
if (known(currentValueBase) === null) unavailableMarketValue.add(ticker.metadata.ticker);
|
|
359
426
|
if (selectedPnl.basis === "broker-snapshot" || selectedPnl.basis === "mixed") brokerPnlSymbols.add(ticker.metadata.ticker);
|
|
@@ -369,10 +436,11 @@ export async function valuePortfolioPositions({
|
|
|
369
436
|
? { change: await toBase(metrics.totalPriceUnits * change, quoteCurrency),
|
|
370
437
|
previousValue: await toBase(metrics.grossPriceUnits * (valuationQuote.price - change), quoteCurrency) }
|
|
371
438
|
: null;
|
|
372
|
-
values.push({ ticker, quote, activeQuote, headline, extended, position, metrics, pnl,
|
|
439
|
+
values.push({ ticker, quote, activeQuote, headline, extended, position, metrics, pnl, futures: futuresTerms,
|
|
373
440
|
dayMove: dayMove && Number.isFinite(dayMove.change) && Number.isFinite(dayMove.previousValue) ? dayMove : null,
|
|
374
441
|
valuation: { valuationBasis: basis, valuationPrice: known(valuationQuote?.price),
|
|
375
|
-
valuationAsOf: valuationTimeIso(getPortfolioValuationTime(quote, basis, metrics))
|
|
442
|
+
valuationAsOf: valuationTimeIso(getPortfolioValuationTime(quote, basis, metrics, settlement)),
|
|
443
|
+
...(atSettlement && settlement ? { valuationPriceKind: "settle" as const } : {}) },
|
|
376
444
|
row: { symbol: ticker.metadata.ticker, ...listingColumns(ticker, quote), exchange: ticker.metadata.exchange,
|
|
377
445
|
shares: metrics.totalShares, avgCost: known(position.avgCost), positionCurrency,
|
|
378
446
|
priceBasis: position.priceBasis ?? null, quantityUnit: metrics.priceBasis === "percent-of-par" ? "face" : null, quotePriceBasis: quote?.priceBasis ?? null,
|
|
@@ -385,7 +453,9 @@ export async function valuePortfolioPositions({
|
|
|
385
453
|
}
|
|
386
454
|
}
|
|
387
455
|
|
|
388
|
-
|
|
456
|
+
const exposure = portfolioExposure(values.map((entry) => entry.position ? entry.row.marketValue as number | null : null));
|
|
457
|
+
return { basis, positions: values, holdings, totalPnl, unavailablePnl, unavailableCost, unavailableMarketValue, brokerPnlSymbols,
|
|
458
|
+
unknownContractSize, unsettled, exposure };
|
|
389
459
|
}
|
|
390
460
|
|
|
391
461
|
/**
|
|
@@ -416,6 +486,25 @@ export function dayMoveLine(
|
|
|
416
486
|
return `Day's move: ${color(`${formatAllocationMoney(move.change, currency, { signed: true })}${percent}`, move.change)}`;
|
|
417
487
|
}
|
|
418
488
|
|
|
489
|
+
/**
|
|
490
|
+
* `Exposure: long $600,000, short -$419,800,000, net -$419,200,000, gross $420,400,000`:
|
|
491
|
+
* what the priced holdings are worth on each side, in whole units, a short negative as every value is.
|
|
492
|
+
*/
|
|
493
|
+
export function exposureLine(exposure: PortfolioExposure, currency: string): string {
|
|
494
|
+
const money = (value: number) => formatAllocationMoney(value, currency, { whole: true });
|
|
495
|
+
return `Exposure: long ${money(exposure.long)}, short ${money(exposure.short)}, net ${money(exposure.net)}, gross ${money(exposure.gross)}`;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/** What a hedged book's weights divide, in whole units as the exposure line states it; null for a book weighed on its total. */
|
|
499
|
+
export function weightBasisLine(allocation: Pick<PortfolioAllocation, "weightBasis" | "weightBase">, currency: string): string | null {
|
|
500
|
+
return weightBasisNote(allocation, (value) => formatAllocationMoney(value, currency, { whole: true }));
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** What the TOTAL row weighs: all of a total; nothing on a gross base, where the weights net to the book's net over its gross. */
|
|
504
|
+
function totalWeight(allocation: Pick<PortfolioAllocation, "weightBasis" | "total">): number | null {
|
|
505
|
+
return allocation.weightBasis === "total" && allocation.total != null && allocation.total > 0 ? 100 : null;
|
|
506
|
+
}
|
|
507
|
+
|
|
419
508
|
/** A portfolio's cash line, as `portfolio show` and `fn PF` report it. */
|
|
420
509
|
export interface PortfolioCashReport {
|
|
421
510
|
/** In the cash's own currency. */
|
|
@@ -512,30 +601,49 @@ function portfolioValuationFreshness(
|
|
|
512
601
|
source,
|
|
513
602
|
observedKey: "valuationAsOf",
|
|
514
603
|
citeOldest: true,
|
|
515
|
-
...(note ? { note: `priced at ${
|
|
604
|
+
...(note ? { note: `priced at ${valuationBasisLabel(valuation.basis, { futures: holdsFutures(valuation) })}` } : {}),
|
|
516
605
|
}, undefined, now);
|
|
517
606
|
}
|
|
518
607
|
|
|
608
|
+
/** Whether a valued book holds a future, whose price at the close is its settlement. */
|
|
609
|
+
export function holdsFutures(valuation: Pick<PortfolioValuation, "positions">): boolean {
|
|
610
|
+
return valuation.positions.some((entry) => entry.position && entry.futures);
|
|
611
|
+
}
|
|
612
|
+
|
|
519
613
|
/** `Priced at the latest print, extended hours included`: the line under a portfolio's title. */
|
|
520
|
-
function valuationBasisLine(basis: PortfolioValuationBasis): string {
|
|
521
|
-
return `Priced at ${
|
|
614
|
+
function valuationBasisLine(basis: PortfolioValuationBasis, futures: boolean): string {
|
|
615
|
+
return `Priced at ${valuationBasisLabel(basis, { futures })}`;
|
|
522
616
|
}
|
|
523
617
|
|
|
524
618
|
/**
|
|
525
619
|
* An As of cell: when the price a row's value uses is from (`valuationAsOf`),
|
|
526
|
-
* in UTC as its header says; a close dated only by its day says so
|
|
620
|
+
* in UTC as its header says; a close dated only by its day says so, and a
|
|
621
|
+
* future's settlement names its session.
|
|
527
622
|
*/
|
|
528
|
-
export function valuationTimeText(value: unknown, now = Date.now()): string {
|
|
623
|
+
export function valuationTimeText(value: unknown, now = Date.now(), kind?: "settle"): string {
|
|
529
624
|
const time = parseReportTime(value);
|
|
530
625
|
if (!time) return "—";
|
|
531
|
-
return time.dateOnly ? `${formatUtcDayTime(time, now)} close` : formatUtcDayTime(time, now);
|
|
626
|
+
return time.dateOnly ? `${formatUtcDayTime(time, now)} ${kind ?? "close"}` : formatUtcDayTime(time, now);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/** A yield in percent with two decimals, as DVD shows one: 2.31%. */
|
|
630
|
+
function formatIncomeYield(value: number | null | undefined): string {
|
|
631
|
+
return value != null && Number.isFinite(value) ? `${value.toFixed(2)}%` : "—";
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/** One holding's projected years, for the DRIP table and its export. */
|
|
635
|
+
interface DripProjection {
|
|
636
|
+
ticker: TickerRecord;
|
|
637
|
+
dividend: HoldingDividend & { perShare: number; currency: string; price: number };
|
|
638
|
+
shares: number;
|
|
639
|
+
years: DripYear[];
|
|
532
640
|
}
|
|
533
641
|
|
|
534
642
|
async function showCollectionWithMarketData(
|
|
535
643
|
name: string,
|
|
536
644
|
ctx: CliCommandContext,
|
|
537
645
|
{ config, store, dataProvider, persistence }: MarketContext,
|
|
538
|
-
{ explain = false, basis = "latest-print" }: ShowCollectionOptions,
|
|
646
|
+
{ explain = false, basis = "latest-print", drip, dividendDeadlineMs = DIVIDEND_READ_DEADLINE_MS, futuresSources }: ShowCollectionOptions,
|
|
539
647
|
) {
|
|
540
648
|
const tickers = (await store.loadAllTickers()).sort((left, right) =>
|
|
541
649
|
left.metadata.ticker.localeCompare(right.metadata.ticker)
|
|
@@ -553,6 +661,9 @@ async function showCollectionWithMarketData(
|
|
|
553
661
|
if (!matchedPortfolio && basis === "regular-close") {
|
|
554
662
|
ctx.fail(`--close values a portfolio; "${collection.name}" is a watchlist.`, "Usage: gloomberb portfolio show <portfolio> --close");
|
|
555
663
|
}
|
|
664
|
+
if (!matchedPortfolio && drip) {
|
|
665
|
+
ctx.fail(`--drip projects a portfolio's dividends; "${collection.name}" is a watchlist.`, "Usage: gloomberb portfolio show <portfolio> --drip");
|
|
666
|
+
}
|
|
556
667
|
const isPortfolio = !!matchedPortfolio;
|
|
557
668
|
const id = collection.id;
|
|
558
669
|
const displayName = collection.name;
|
|
@@ -575,24 +686,34 @@ async function showCollectionWithMarketData(
|
|
|
575
686
|
// The terms the report shows, which --explain follows it with.
|
|
576
687
|
let explained: GlossaryEntry[] = [];
|
|
577
688
|
|
|
578
|
-
const
|
|
689
|
+
const futuresHeld = filtered.filter((ticker) => isFuturesTicker(ticker)
|
|
690
|
+
&& ticker.metadata.positions.some((position) => position.portfolio === id && position.shares !== 0));
|
|
691
|
+
const [quotes, futures] = await Promise.all([
|
|
692
|
+
loadCollectionQuotes(filtered, collection, dataProvider),
|
|
693
|
+
isPortfolio ? loadPortfolioFuturesTerms(futuresHeld, {
|
|
694
|
+
settlement: basis === "regular-close",
|
|
695
|
+
sources: { history: dailyFuturesHistory(dataProvider), ...futuresSources },
|
|
696
|
+
}) : undefined,
|
|
697
|
+
]);
|
|
579
698
|
// Worst of across the holdings' quotes, as the PORT and watchlist reports state it.
|
|
580
699
|
const broker = !!(matchedPortfolio?.brokerId || matchedPortfolio?.brokerInstanceId);
|
|
581
700
|
const source = !isPortfolio ? "Local watchlist and Gloom Cloud"
|
|
582
701
|
: broker ? "Your broker account and Gloom Cloud" : "Local portfolio and Gloom Cloud";
|
|
583
702
|
let freshness = isPortfolio ? undefined : quotesFreshness(quotes.values(), { source });
|
|
703
|
+
// A watchlist's stale rows, named under the closing line that counts them.
|
|
704
|
+
let staleNote: string | null = null;
|
|
584
705
|
|
|
585
706
|
if (!structured) {
|
|
586
707
|
console.log(cliStyles.bold(displayName + (isPortfolio ? ` (${currency})` : "")));
|
|
587
708
|
console.log(cliStyles.muted(`${filtered.length} ticker${filtered.length === 1 ? "" : "s"}`));
|
|
588
709
|
const imported = matchedPortfolio ? importedFromText(matchedPortfolio) : null;
|
|
589
710
|
if (imported) console.log(cliStyles.muted(imported));
|
|
590
|
-
if (isPortfolio) console.log(cliStyles.muted(valuationBasisLine(basis)));
|
|
711
|
+
if (isPortfolio) console.log(cliStyles.muted(valuationBasisLine(basis, futuresHeld.length > 0)));
|
|
591
712
|
console.log("");
|
|
592
713
|
}
|
|
593
714
|
|
|
594
715
|
if (matchedPortfolio) {
|
|
595
|
-
const valuation = await valuePortfolioPositions({ tickers: filtered, quotes, portfolioId: id, currency, baseCurrency: config.baseCurrency, toBase, basis });
|
|
716
|
+
const valuation = await valuePortfolioPositions({ tickers: filtered, quotes, portfolioId: id, currency, baseCurrency: config.baseCurrency, toBase, basis, futures });
|
|
596
717
|
// Exports carry no header line, so their source line names the basis.
|
|
597
718
|
freshness = portfolioValuationFreshness(valuation, source, { note: structured });
|
|
598
719
|
const {
|
|
@@ -602,22 +723,45 @@ async function showCollectionWithMarketData(
|
|
|
602
723
|
unavailableCost,
|
|
603
724
|
unavailableMarketValue,
|
|
604
725
|
brokerPnlSymbols,
|
|
726
|
+
unknownContractSize,
|
|
727
|
+
unsettled,
|
|
728
|
+
exposure,
|
|
605
729
|
} = valuation;
|
|
606
730
|
const { allocation, cash } = await valuePortfolioAllocation({ valuation, portfolio: matchedPortfolio, account, toBase });
|
|
607
731
|
const allocationBySymbol = new Map(allocation.rows.map((row) => [row.symbol, row]));
|
|
732
|
+
// What each holding pays a year: trailing dividends per share times its shares, in the portfolio's currency.
|
|
733
|
+
const held = filtered.filter((ticker) => valued.some((entry) => entry.ticker === ticker && entry.position));
|
|
734
|
+
const { dividends } = await loadHoldingDividends(held, quotes, dataProvider, { deadlineMs: dividendDeadlineMs });
|
|
735
|
+
// Lots without a position or shares are no lot of income; a row of their own keeps its place with none.
|
|
736
|
+
const income = await portfolioIncome(valued.map((entry) => ({
|
|
737
|
+
symbol: entry.position && entry.metrics ? entry.ticker.metadata.ticker : "",
|
|
738
|
+
shares: entry.metrics?.totalShares ?? 0,
|
|
739
|
+
value: typeof entry.row.marketValue === "number" ? entry.row.marketValue : null,
|
|
740
|
+
})), dividends, toBase);
|
|
741
|
+
const lotIncome = income.lots;
|
|
742
|
+
const totalIncome = income.totalIncome;
|
|
743
|
+
const portfolioYield = totalIncome != null && allocation.total != null && allocation.total > 0 ? (totalIncome / allocation.total) * 100 : null;
|
|
744
|
+
const incomeUnavailable = [...income.gaps].map(([symbol, gap]) => ({ symbol, ...gap }));
|
|
608
745
|
const showTargets = allocation.targetSum != null;
|
|
609
746
|
// Never dropped on a narrow terminal, so --explain reads the terms off them.
|
|
610
747
|
const allocationHeaders = showTargets ? ["Weight", "Target", "Drift", "Trade", "Trade Value"] : ["Weight"];
|
|
611
748
|
explained = reportGlossary("PF", allocationHeaders.join(" "));
|
|
612
|
-
// A ticker held in several lots carries its allocation on its first row.
|
|
749
|
+
// A ticker held in several lots carries its allocation, the whole position's, on its first row.
|
|
613
750
|
const firstRows = new Set<string>();
|
|
614
|
-
const
|
|
751
|
+
const lotCounts = new Map<string, number>();
|
|
752
|
+
for (const entry of valued) {
|
|
753
|
+
if (entry.position) lotCounts.set(entry.ticker.metadata.ticker, (lotCounts.get(entry.ticker.metadata.ticker) ?? 0) + 1);
|
|
754
|
+
}
|
|
755
|
+
const positionsExport = valued.map((entry, index): Record<string, unknown> & {
|
|
615
756
|
weight: number | null; targetWeight: number | null; drift: number | null; tradeShares: number | null; tradeValue: number | null;
|
|
757
|
+
annualIncome: number | null; incomeYield: number | null;
|
|
616
758
|
} => {
|
|
617
759
|
const symbol = entry.ticker.metadata.ticker;
|
|
618
760
|
const first = !firstRows.has(symbol);
|
|
619
761
|
firstRows.add(symbol);
|
|
620
762
|
const figures = first ? allocationBySymbol.get(symbol) : undefined;
|
|
763
|
+
const dividend = entry.position ? dividends.get(symbol) : undefined;
|
|
764
|
+
const lots = lotCounts.get(symbol) ?? 0;
|
|
621
765
|
return {
|
|
622
766
|
...entry.row,
|
|
623
767
|
weight: figures?.weight ?? null,
|
|
@@ -626,6 +770,18 @@ async function showCollectionWithMarketData(
|
|
|
626
770
|
tradeShares: figures?.tradeUnits ?? null,
|
|
627
771
|
tradeValue: figures?.tradeValue ?? null,
|
|
628
772
|
...entry.valuation,
|
|
773
|
+
// After the valuation fields, so every column an export had keeps its place.
|
|
774
|
+
dividendPerShare: dividend?.perShare ?? null,
|
|
775
|
+
dividendCurrency: dividend?.currency ?? null,
|
|
776
|
+
annualIncome: lotIncome[index]?.income ?? null,
|
|
777
|
+
incomeYield: lotIncome[index]?.yield ?? null,
|
|
778
|
+
// Only the rows of a ticker held in several lots: `lots` says the allocation on its first row is the whole position's, `lotWeight` is this lot's own share.
|
|
779
|
+
...(lots > 1 ? { lots, lotWeight: lotWeight(typeof entry.row.marketValue === "number" ? entry.row.marketValue : null, allocation.weightBase) } : {}),
|
|
780
|
+
// Only on a future: `shares` counts contracts, each `contractMultiplier` units of the price, as the exchange sizes it.
|
|
781
|
+
...(entry.futures && entry.position ? {
|
|
782
|
+
contractMultiplier: entry.position.multiplier ?? null,
|
|
783
|
+
contractSize: entry.futures.size?.size ?? null,
|
|
784
|
+
} : {}),
|
|
629
785
|
};
|
|
630
786
|
});
|
|
631
787
|
const totalPnlValue = unavailablePnl.size > 0 ? null : totalPnl;
|
|
@@ -643,28 +799,91 @@ async function showCollectionWithMarketData(
|
|
|
643
799
|
drift: allocation.cash.drift, tradeValue: allocation.cash.tradeValue,
|
|
644
800
|
} : null;
|
|
645
801
|
|
|
802
|
+
// `--drip`: each holding with a known income and a price, its lots netted: a long reinvests, a short owes.
|
|
803
|
+
const dripNotProjected = new Set<string>();
|
|
804
|
+
const dripProjections: DripProjection[] = [];
|
|
805
|
+
if (drip) {
|
|
806
|
+
for (const ticker of held) {
|
|
807
|
+
const symbol = ticker.metadata.ticker;
|
|
808
|
+
const dividend = dividends.get(symbol);
|
|
809
|
+
if (!dividend) continue;
|
|
810
|
+
const shares = valued.filter((entry) => entry.ticker === ticker && entry.position && entry.metrics)
|
|
811
|
+
.reduce((sum, entry) => sum + entry.metrics!.totalShares, 0);
|
|
812
|
+
if (shares === 0) continue;
|
|
813
|
+
// Income the table could not show is not projected either; the note under it says why.
|
|
814
|
+
if (income.gaps.has(symbol) || dividend.perShare == null || !dividend.currency) continue;
|
|
815
|
+
if (dividend.perShare === 0) continue;
|
|
816
|
+
if (dividend.price == null || !(dividend.price > 0)) {
|
|
817
|
+
dripNotProjected.add(symbol);
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
const rate = await toBase(1, dividend.currency);
|
|
821
|
+
if (!Number.isFinite(rate) || rate <= 0) {
|
|
822
|
+
dripNotProjected.add(symbol);
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
dripProjections.push({
|
|
826
|
+
ticker, shares,
|
|
827
|
+
dividend: { ...dividend, perShare: dividend.perShare, currency: dividend.currency, price: dividend.price },
|
|
828
|
+
years: projectDrip({ symbol, shares, perShare: dividend.perShare, price: dividend.price, rate }, drip),
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
const dripAsOf = isoDateToday();
|
|
833
|
+
const dripMethod = drip
|
|
834
|
+
? `Trailing twelve-month dividends per share, share prices and exchange rates as of ${dripAsOf} held flat; ${drip.withholding}% withheld from each dividend; what is left buys shares once a year, at the year's end. A short owes each dividend in full, every year, and reinvests nothing.`
|
|
835
|
+
: null;
|
|
836
|
+
|
|
646
837
|
const accountingBasis = "Unrealized P&L on current positions; excludes realized trades, distributions and cash flows. This is not account investment return.";
|
|
647
838
|
const manualAccounting = !matchedPortfolio.brokerId && !matchedPortfolio.brokerInstanceId
|
|
648
839
|
? "Manual holdings are snapshots. Reconcile shares and per-share cost after corporate actions using portfolio position set; cash distributions are not credited."
|
|
649
840
|
: null;
|
|
841
|
+
if (structured && drip) {
|
|
842
|
+
const rows = dripProjections.flatMap(({ ticker, dividend, years }) => years.map((year) => ({
|
|
843
|
+
symbol: ticker.metadata.ticker, listing: listingLabel(ticker), year: year.year,
|
|
844
|
+
shares: year.shares, addedShares: year.addedShares,
|
|
845
|
+
dividendPerShare: dividend.perShare, dividendCurrency: dividend.currency, price: dividend.price,
|
|
846
|
+
grossIncome: year.grossIncome, withheld: year.withheld, netIncome: year.netIncome,
|
|
847
|
+
netIncomeBase: year.netIncomeBase, valueBase: year.valueBase, baseCurrency: currency,
|
|
848
|
+
})));
|
|
849
|
+
ctx.printResult({ data: rows, metadata: {
|
|
850
|
+
portfolioId: id, portfolioName: displayName, baseCurrency: currency,
|
|
851
|
+
years: drip.years, withholdingPercent: drip.withholding, asOf: dripAsOf, method: dripMethod,
|
|
852
|
+
annualIncome: totalIncome, knownAnnualIncome: income.knownIncome, portfolioYield,
|
|
853
|
+
incomeComplete: income.gaps.size === 0, incomeUnavailable,
|
|
854
|
+
notProjectedSymbols: [...dripNotProjected, ...income.gaps.keys()],
|
|
855
|
+
complete: income.gaps.size === 0 && dripNotProjected.size === 0,
|
|
856
|
+
}, freshness });
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
650
859
|
if (structured) {
|
|
651
860
|
// CSV and NDJSON read the cash and the total as rows; JSON keeps them in the metadata.
|
|
652
861
|
const extraRows: Record<string, unknown>[] = [
|
|
653
862
|
...(cashExport ? [{ symbol: CASH_SYMBOL, marketValue: cashExport.value, baseCurrency: currency, weight: cashExport.weight,
|
|
654
863
|
targetWeight: cashExport.targetWeight, drift: cashExport.drift, tradeShares: null, tradeValue: cashExport.tradeValue }] : []),
|
|
655
864
|
{ symbol: "TOTAL", marketValue: allocation.total, unrealizedPnl: totalPnlValue, baseCurrency: currency,
|
|
656
|
-
weight: allocation
|
|
865
|
+
weight: totalWeight(allocation), targetWeight: allocation.targetSum, valuationBasis: basis,
|
|
866
|
+
annualIncome: totalIncome, incomeYield: portfolioYield },
|
|
657
867
|
];
|
|
658
868
|
ctx.printResult({ data: positionsExport, metadata: {
|
|
659
869
|
portfolioId: id, portfolioName: displayName, baseCurrency: currency, currency,
|
|
660
870
|
valuationBasis: basis,
|
|
661
871
|
total: allocation.total, cash: cashExport, targetSum: allocation.targetSum,
|
|
872
|
+
// Only on a book that holds a short: its weights are shares of `weightBase`, its gross exposure, not of the total.
|
|
873
|
+
...(allocation.weightBasis === "gross" ? { weightBasis: allocation.weightBasis, weightBase: allocation.weightBase } : {}),
|
|
662
874
|
...(basis === "regular-close" ? { dayChange: dayMove?.change ?? null, dayChangePercent: dayMove?.changePercent ?? null } : {}),
|
|
663
875
|
unpricedCount: allocation.unpriced.length, unpricedSymbols: allocation.unpriced,
|
|
664
876
|
totalUnrealizedPnl: totalPnlValue,
|
|
665
|
-
|
|
877
|
+
annualIncome: totalIncome, knownAnnualIncome: income.knownIncome, portfolioYield,
|
|
878
|
+
incomeComplete: income.gaps.size === 0, incomeUnavailable,
|
|
879
|
+
incomeBasis: "Trailing twelve-month dividends per share times shares held, in the portfolio's currency; a short owes the dividend, so its income is negative. Portfolio yield is the total over the total value; annualIncome is null when any holding's income is unknown, and knownAnnualIncome sums the holdings that have one.",
|
|
880
|
+
complete: unavailablePnl.size === 0 && unavailableCost.size === 0 && unavailableMarketValue.size === 0 && income.gaps.size === 0,
|
|
666
881
|
unavailableSymbols: [...new Set([...unavailablePnl, ...unavailableCost, ...unavailableMarketValue])],
|
|
667
882
|
unavailableCostSymbols: [...unavailableCost], brokerPnlSymbols: [...brokerPnlSymbols],
|
|
883
|
+
// The priced holdings' market value: longs, shorts (negative), net and gross. Cash and unpriced holdings are not in it.
|
|
884
|
+
exposure,
|
|
885
|
+
...(unknownContractSize.size > 0 ? { unknownContractSizeSymbols: [...unknownContractSize] } : {}),
|
|
886
|
+
...(unsettled.size > 0 ? { unsettledSymbols: [...unsettled] } : {}),
|
|
668
887
|
accountingBasis, ...(manualAccounting ? { manualAccounting } : {}),
|
|
669
888
|
...(matchedPortfolio.importedFrom ? { importedFrom: matchedPortfolio.importedFrom } : {}),
|
|
670
889
|
...(explain ? { glossary: glossaryExport(explained) } : {}),
|
|
@@ -692,54 +911,85 @@ async function showCollectionWithMarketData(
|
|
|
692
911
|
return [weight, target, drift, trade, tradeValue];
|
|
693
912
|
};
|
|
694
913
|
|
|
914
|
+
// Income and its yield, once any share or fund has one; with none known, the note under the table says why.
|
|
915
|
+
const showIncome = income.knownHoldings > 0;
|
|
916
|
+
const incomeCells = (income: number | null | undefined, yieldPercent: number | null | undefined): string[] =>
|
|
917
|
+
showIncome ? [money(income), formatIncomeYield(yieldPercent)] : [];
|
|
695
918
|
const extendedSessions = extendedSessionsOf(valued.map(({ extended }) => extended?.session));
|
|
696
919
|
const now = Date.now();
|
|
697
|
-
|
|
698
|
-
|
|
920
|
+
// Where each ticker's lots end, to put its position line there.
|
|
921
|
+
const lastLot = new Map<string, number>();
|
|
922
|
+
valued.forEach((entry, index) => { if (entry.position) lastLot.set(entry.ticker.metadata.ticker, index); });
|
|
923
|
+
const rows: string[][] = valued.flatMap(({ ticker, quote, headline, extended, position, metrics, pnl, row, valuation: priced, futures: contract }, index): string[][] => {
|
|
924
|
+
const asOf = valuationTimeText(priced.valuationAsOf, now, priced.valuationPriceKind);
|
|
699
925
|
const priceText = quote && headline
|
|
700
926
|
? colorBySign(formatListPrice(headline.price, quote, ticker.metadata.assetCategory), headline.change)
|
|
701
927
|
: "—";
|
|
702
928
|
const changeText = headline ? colorBySign(formatPercentRaw(headline.changePercent), headline.change) : "—";
|
|
703
929
|
const quoteCells = [priceText, changeText, ...extendedCells(extendedSessions, extended, quote, ticker.metadata.assetCategory)];
|
|
704
930
|
const exported = positionsExport[index]!;
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
const
|
|
931
|
+
const symbol = ticker.metadata.ticker;
|
|
932
|
+
const lots = lotCounts.get(symbol) ?? 0;
|
|
933
|
+
// A lot of several weighs by its own value; its target, drift and trade belong to the position, on the line under its lots.
|
|
934
|
+
const figures = lots > 1
|
|
935
|
+
? { weight: exported.lotWeight as number | null, targetWeight: null, drift: null, tradeValue: null, tradeUnits: null }
|
|
936
|
+
: exported.weight != null || exported.targetWeight != null || exported.tradeValue != null
|
|
937
|
+
? { weight: exported.weight, targetWeight: exported.targetWeight, drift: exported.drift, tradeValue: exported.tradeValue, tradeUnits: exported.tradeShares }
|
|
938
|
+
: undefined;
|
|
939
|
+
const symbolUnits = allocationBySymbol.get(symbol)?.units ?? 0;
|
|
709
940
|
if (!position || !metrics) {
|
|
710
|
-
return [listingLabel(ticker), ...quoteCells, "—", "—", "—",
|
|
711
|
-
...allocationCells(figures, { unpriced: false, units: symbolUnits, assetCategory: ticker.metadata.assetCategory }),
|
|
941
|
+
return [[listingLabel(ticker), ...quoteCells, "—", "—", "—",
|
|
942
|
+
...allocationCells(figures, { unpriced: false, units: symbolUnits, assetCategory: ticker.metadata.assetCategory }),
|
|
943
|
+
...incomeCells(null, null), "—", asOf]];
|
|
712
944
|
}
|
|
713
945
|
const positionCurrency = metrics.positionCurrency;
|
|
714
946
|
const marketValue = row.marketValue as number | null;
|
|
715
947
|
const unpriced = marketValue == null;
|
|
716
|
-
|
|
948
|
+
const quantityOptions = { assetCategory: ticker.metadata.assetCategory, multiplier: position.multiplier, priceBasis: metrics.priceBasis, quantityCurrency: positionCurrency };
|
|
949
|
+
// A future counts contracts, each the size its terms give: 1,000 × 100 oz.
|
|
950
|
+
const quantity = (units: number) => contract
|
|
951
|
+
? futuresQuantityText(formatMarketQuantity(units, quantityOptions), contract.size
|
|
952
|
+
?? (position.multiplier ? { size: formatMarketQuantity(position.multiplier) } : null))
|
|
953
|
+
: formatMarketQuantity(units, quantityOptions);
|
|
954
|
+
const lotRow = [
|
|
717
955
|
listingLabel(ticker),
|
|
718
956
|
...quoteCells,
|
|
719
|
-
|
|
957
|
+
quantity(metrics.totalShares),
|
|
720
958
|
formatMarketCostWithCurrency(position.avgCost, positionCurrency, { assetCategory: ticker.metadata.assetCategory, multiplier: position.multiplier, priceBasis: metrics.priceBasis }),
|
|
721
959
|
unpriced ? NOT_AVAILABLE : wholeMoney(marketValue),
|
|
722
960
|
...allocationCells(figures, { unpriced, units: symbolUnits, assetCategory: ticker.metadata.assetCategory }),
|
|
961
|
+
...incomeCells(exported.annualIncome, exported.incomeYield),
|
|
723
962
|
pnl == null || !Number.isFinite(pnl) ? "—" : colorBySign(money(pnl, { signed: true }), pnl),
|
|
724
963
|
asOf,
|
|
725
964
|
];
|
|
965
|
+
if (lots < 2 || lastLot.get(symbol) !== index) return [lotRow];
|
|
966
|
+
// The whole position under its lots: its shares and value, and the weight, target, drift and trade they add up to.
|
|
967
|
+
const whole = allocationBySymbol.get(symbol);
|
|
968
|
+
const wholeValue = whole?.marketValue ?? null;
|
|
969
|
+
return [lotRow, [`${listingLabel(ticker)} (${lots} lots)`, "", "", ...extendedSessions.map(() => ""),
|
|
970
|
+
quantity(symbolUnits), "", wholeValue == null ? NOT_AVAILABLE : wholeMoney(wholeValue),
|
|
971
|
+
...allocationCells(whole, { unpriced: wholeValue == null, units: symbolUnits, assetCategory: ticker.metadata.assetCategory }),
|
|
972
|
+
...(showIncome ? ["", ""] : []), "", ""]];
|
|
726
973
|
});
|
|
727
974
|
if (cashExport) {
|
|
728
975
|
const label = cashExport.currency && cashExport.currency !== currency ? `${CASH_SYMBOL} ${cashExport.currency}` : CASH_SYMBOL;
|
|
729
976
|
rows.push([label, "", "", ...extendedSessions.map(() => ""), "", "", cashExport.value == null ? NOT_AVAILABLE : wholeMoney(cashExport.value),
|
|
730
|
-
...allocationCells(cashExport, { unpriced: cashExport.value == null }), "", ""]);
|
|
977
|
+
...allocationCells(cashExport, { unpriced: cashExport.value == null }), ...(showIncome ? ["", ""] : []), "", ""]);
|
|
731
978
|
}
|
|
732
979
|
const total = allocation.total;
|
|
733
980
|
rows.push([
|
|
734
981
|
cliStyles.bold("TOTAL"), "", "", ...extendedSessions.map(() => ""), "", "",
|
|
735
982
|
cliStyles.bold(wholeMoney(total)),
|
|
736
|
-
|
|
983
|
+
formatAllocationWeight(totalWeight(allocation)),
|
|
737
984
|
...(showTargets ? [formatAllocationWeight(allocation.targetSum), "", "", ""] : []),
|
|
985
|
+
...(showIncome ? [cliStyles.bold(money(totalIncome)), formatIncomeYield(portfolioYield)] : []),
|
|
738
986
|
totalPnlValue == null ? "—" : colorBySign(money(totalPnlValue, { signed: true }), totalPnlValue),
|
|
739
987
|
"",
|
|
740
988
|
]);
|
|
741
989
|
|
|
742
|
-
|
|
990
|
+
// Face amounts and contracts are not shares.
|
|
991
|
+
const quantityHeader = filtered.some(ticker => ticker.metadata.assetCategory?.toUpperCase() === "BOND") || positionsExport.some(row => row.quantityUnit === "face")
|
|
992
|
+
|| holdsFutures(valuation) ? "Qty" : "Shares";
|
|
743
993
|
// A narrow terminal drops the cost, then the quote, then P&L, before any of the allocation.
|
|
744
994
|
console.log(renderTable(
|
|
745
995
|
[
|
|
@@ -752,6 +1002,11 @@ async function showCollectionWithMarketData(
|
|
|
752
1002
|
{ header: "Avg Cost", align: "right", optional: true, dropPriority: 3 },
|
|
753
1003
|
{ header: "Value", align: "right" },
|
|
754
1004
|
...allocationHeaders.map((header) => ({ header, align: "right" as const })),
|
|
1005
|
+
// A year of trailing dividends on the shares held, dropped after the quote and before P&L.
|
|
1006
|
+
...(showIncome ? [
|
|
1007
|
+
{ header: "Income", align: "right" as const, optional: true, dropPriority: 0.5 },
|
|
1008
|
+
{ header: "Yield", align: "right" as const, optional: true, dropPriority: 0.5 },
|
|
1009
|
+
] : []),
|
|
755
1010
|
{ header: "P&L", align: "right", optional: true },
|
|
756
1011
|
// When each row's price is from, so a Friday close never hides behind a newer row.
|
|
757
1012
|
{ header: "As of (UTC)" },
|
|
@@ -760,10 +1015,15 @@ async function showCollectionWithMarketData(
|
|
|
760
1015
|
));
|
|
761
1016
|
console.log("");
|
|
762
1017
|
if (basis === "regular-close") console.log(dayMoveLine(dayMove, currency, colorBySign));
|
|
1018
|
+
if (valued.some((entry) => entry.position)) console.log(exposureLine(exposure, currency));
|
|
763
1019
|
const notes: string[] = [];
|
|
1020
|
+
const weightNote = weightBasisLine(allocation, currency);
|
|
1021
|
+
if (weightNote) notes.push(cliStyles.muted(weightNote));
|
|
764
1022
|
// The notes name the rows as the table does.
|
|
765
1023
|
const labels = new Map(filtered.map((ticker) => [ticker.metadata.ticker, listingLabel(ticker)]));
|
|
766
1024
|
const named = (symbols: Iterable<string>) => [...symbols].map((symbol) => labels.get(symbol) ?? symbol).join(", ");
|
|
1025
|
+
// A future without its contract size or settlement is said once, not as a missing cost, value and P&L.
|
|
1026
|
+
const unsized = (symbols: ReadonlySet<string>) => [...symbols].filter((symbol) => !unknownContractSize.has(symbol) && !unsettled.has(symbol));
|
|
767
1027
|
if (allocation.unpriced.length > 0) {
|
|
768
1028
|
const count = allocation.unpriced.length;
|
|
769
1029
|
notes.push(cliStyles.warning(`Total and weights exclude ${count} unpriced holding${count === 1 ? "" : "s"}: ${named(allocation.unpriced)}.`));
|
|
@@ -775,12 +1035,58 @@ async function showCollectionWithMarketData(
|
|
|
775
1035
|
? `No ${cashExport.currency}/${currency} rate for the cash, so the total and weights are unavailable.`
|
|
776
1036
|
: "The cash's currency is unknown, so the total and weights are unavailable."));
|
|
777
1037
|
}
|
|
778
|
-
const targetNote = describeTargetSum(allocation.targetSum);
|
|
1038
|
+
const targetNote = describeTargetSum(allocation.targetSum, allocation.weightBasis);
|
|
779
1039
|
if (targetNote) notes.push(cliStyles.muted(targetNote));
|
|
780
1040
|
if (brokerPnlSymbols.size > 0) notes.push(cliStyles.muted(`P&L for ${named(brokerPnlSymbols)} is the broker's last snapshot.`));
|
|
781
|
-
if (
|
|
782
|
-
|
|
1041
|
+
if (unknownContractSize.size > 0) {
|
|
1042
|
+
notes.push(cliStyles.warning(`No contract size for ${named(unknownContractSize)}, so ${unknownContractSize.size === 1 ? "its" : "their"} value, cost and P&L are not shown.`));
|
|
1043
|
+
}
|
|
1044
|
+
if (unsettled.size > 0) {
|
|
1045
|
+
notes.push(cliStyles.warning(`No confirmed settlement for ${named(unsettled)}, so ${unsettled.size === 1 ? "it is" : "they are"} not valued at the close.`));
|
|
1046
|
+
}
|
|
1047
|
+
if (unsized(unavailableCost).length > 0) notes.push(cliStyles.muted(`Cost unavailable for ${named(unsized(unavailableCost))}.`));
|
|
1048
|
+
if (unsized(unavailablePnl).length > 0) notes.push(cliStyles.muted(`P&L unavailable for ${named(unsized(unavailablePnl))}.`));
|
|
1049
|
+
const incomeNote = incomeGapNote(income, { name: named, money: (value) => money(value), deadlineMs: dividendDeadlineMs });
|
|
1050
|
+
if (incomeNote) notes.push(cliStyles.muted(incomeNote));
|
|
783
1051
|
for (const note of notes) console.log(note);
|
|
1052
|
+
if (drip) {
|
|
1053
|
+
console.log("");
|
|
1054
|
+
console.log(renderSection(`DRIP projection, ${drip.years} year${drip.years === 1 ? "" : "s"}`));
|
|
1055
|
+
if (dripProjections.length === 0) {
|
|
1056
|
+
console.log(cliStyles.muted(dividends.size === 0 ? "No shares or funds to project."
|
|
1057
|
+
: income.gaps.size > 0 ? "Nothing to project: the income above is unavailable." : "No holding paid a dividend in the past year."));
|
|
1058
|
+
} else {
|
|
1059
|
+
const yearNumbers = Array.from({ length: drip.years }, (_, index) => index + 1);
|
|
1060
|
+
const finalYear = `Shares Y${drip.years}`;
|
|
1061
|
+
const quantity = (value: number, ticker: TickerRecord) => formatMarketQuantity(value, { assetCategory: ticker.metadata.assetCategory });
|
|
1062
|
+
const dripRows = dripProjections.map(({ ticker, shares, years }) => [
|
|
1063
|
+
listingLabel(ticker),
|
|
1064
|
+
quantity(shares, ticker),
|
|
1065
|
+
quantity(years.at(-1)!.shares, ticker),
|
|
1066
|
+
...years.map((year) => money(year.netIncomeBase)),
|
|
1067
|
+
]);
|
|
1068
|
+
dripRows.push([
|
|
1069
|
+
cliStyles.bold("TOTAL"), "", "",
|
|
1070
|
+
...yearNumbers.map((year) => cliStyles.bold(money(dripProjections.reduce((sum, entry) => sum + entry.years[year - 1]!.netIncomeBase, 0)))),
|
|
1071
|
+
]);
|
|
1072
|
+
console.log(renderTable(
|
|
1073
|
+
[
|
|
1074
|
+
{ header: "Ticker" },
|
|
1075
|
+
{ header: "Shares", align: "right" },
|
|
1076
|
+
{ header: finalYear, align: "right" },
|
|
1077
|
+
...yearNumbers.map((year) => ({ header: `Y${year} income`, align: "right" as const })),
|
|
1078
|
+
],
|
|
1079
|
+
dripRows,
|
|
1080
|
+
));
|
|
1081
|
+
const shorts = dripProjections.some((entry) => entry.shares < 0) ? "; shorts owe each dividend in full" : "";
|
|
1082
|
+
console.log(cliStyles.muted(`As of ${dripAsOf}: trailing dividends, prices and FX held flat; ${drip.withholding}% withheld; reinvested once a year${shorts}.`));
|
|
1083
|
+
}
|
|
1084
|
+
const unprojected = [
|
|
1085
|
+
...(income.gaps.size > 0 && dripProjections.length > 0 ? [`${named([...income.gaps.keys()])} (income unavailable above)`] : []),
|
|
1086
|
+
...(dripNotProjected.size > 0 ? [`${named(dripNotProjected)} (no price to reinvest at)`] : []),
|
|
1087
|
+
];
|
|
1088
|
+
if (unprojected.length > 0) console.log(cliStyles.muted(`Not projected: ${unprojected.join("; ")}.`));
|
|
1089
|
+
}
|
|
784
1090
|
} else {
|
|
785
1091
|
const watched = await valueWatchlistRows({ tickers: filtered, quotes, dataProvider, currency, toBase });
|
|
786
1092
|
if (structured) {
|
|
@@ -794,7 +1100,9 @@ async function showCollectionWithMarketData(
|
|
|
794
1100
|
const extendedSessions = extendedSessionsOf(watched.map(({ extended }) => extended?.session));
|
|
795
1101
|
// Funding is the one column a list earns by holding perpetual markets; any other list's table is unchanged.
|
|
796
1102
|
const showFunding = watched.some(({ funding }) => funding !== undefined);
|
|
797
|
-
|
|
1103
|
+
// So is Last Trade, by holding a stale quote: the rows the closing line counts stale, dated as `quote` dates them.
|
|
1104
|
+
const stale = watched.filter((entry) => entry.stale);
|
|
1105
|
+
const rows = watched.map(({ ticker, quote, headline, extended, venue, marketCap, funding, stale: rowStale, lastTrade }) => {
|
|
798
1106
|
const priceText = quote && headline
|
|
799
1107
|
? colorBySign(formatListPrice(headline.price, quote, ticker.metadata.assetCategory), headline.change)
|
|
800
1108
|
: "—";
|
|
@@ -804,6 +1112,7 @@ async function showCollectionWithMarketData(
|
|
|
804
1112
|
venue ?? "—",
|
|
805
1113
|
priceText,
|
|
806
1114
|
changeText,
|
|
1115
|
+
...stale.length > 0 ? [rowStale ? cliStyles.warning(lastTrade ? `STALE ${lastTrade}` : "STALE") : ""] : [],
|
|
807
1116
|
...extendedCells(extendedSessions, extended, quote, ticker.metadata.assetCategory),
|
|
808
1117
|
...showFunding ? [funding == null ? "—" : colorBySign(formatPerpetualRate(funding, 4), funding)] : [],
|
|
809
1118
|
marketCap == null ? "—" : `${formatCompact(marketCap)} ${currency}`,
|
|
@@ -816,6 +1125,7 @@ async function showCollectionWithMarketData(
|
|
|
816
1125
|
{ header: "Exchange" },
|
|
817
1126
|
{ header: "Last", align: "right" },
|
|
818
1127
|
{ header: "Chg", align: "right" },
|
|
1128
|
+
...stale.length > 0 ? [{ header: "Last Trade", shrink: false }] : [],
|
|
819
1129
|
...extendedSessions.map((session) => ({ header: EXTENDED_SESSION_LABELS[session], align: "right" as const, optional: true, dropPriority: 1 })),
|
|
820
1130
|
// As the TUI's FUND 8H: per eight hours whatever interval the venue pays on.
|
|
821
1131
|
...showFunding ? [{ header: "Fund 8H", align: "right" as const, optional: true, dropPriority: 1 }] : [],
|
|
@@ -826,8 +1136,12 @@ async function showCollectionWithMarketData(
|
|
|
826
1136
|
));
|
|
827
1137
|
const notes = marketCapNotes(watched);
|
|
828
1138
|
if (notes.length > 0) console.log(["", ...notes.map((note) => cliStyles.muted(note))].join("\n"));
|
|
1139
|
+
// The closing line counts the stale rows; the line under it names them.
|
|
1140
|
+
if (stale.length > 0) {
|
|
1141
|
+
staleNote = `Stale: ${namedList(stale.map(({ ticker, lastTrade }) => `${listingLabel(ticker)}${lastTrade ? ` (last trade ${lastTrade})` : ""}`))}.`;
|
|
1142
|
+
}
|
|
829
1143
|
}
|
|
830
|
-
if (freshness) console.log(`\n${cliFreshnessFooter(freshness)}`);
|
|
1144
|
+
if (freshness) console.log(`\n${cliFreshnessFooter(freshness)}${staleNote ? `\n${cliStyles.muted(staleNote)}` : ""}`);
|
|
831
1145
|
if (explain) console.log(`\n${renderReportGlossary(EXPLAINED_REPORT, explained)}`);
|
|
832
1146
|
}
|
|
833
1147
|
|
|
@@ -843,6 +1157,10 @@ interface WatchlistRowValue {
|
|
|
843
1157
|
marketCap: number | null;
|
|
844
1158
|
/** A perpetual market's funding per eight hours, as a fraction; null when its market cannot be read, undefined for any other row. */
|
|
845
1159
|
funding: number | null | undefined;
|
|
1160
|
+
/** Whether the quote is stale, as the closing line counts it. */
|
|
1161
|
+
stale: boolean;
|
|
1162
|
+
/** The exchange-local date of the quote's last trade. */
|
|
1163
|
+
lastTrade: string | null;
|
|
846
1164
|
row: Record<string, unknown>;
|
|
847
1165
|
}
|
|
848
1166
|
|
|
@@ -933,8 +1251,10 @@ async function valueWatchlistRows({
|
|
|
933
1251
|
const kind = resolveTickerInstrumentKind(ticker, { quote: quote ?? snapshot?.quote });
|
|
934
1252
|
const venue = exchangeShortName(quote?.tradingExchangeName || quote?.exchangeName, quote?.fullExchangeName) || ticker.metadata.exchange || null;
|
|
935
1253
|
const funding = perpetualFunding.get(symbol);
|
|
1254
|
+
const stale = quoteFreshnessFields(quote).stale === true;
|
|
1255
|
+
const lastTrade = quote ? lastTradeDate(quote) : null;
|
|
936
1256
|
values.push({
|
|
937
|
-
ticker, quote, headline, extended, kind, venue, marketCap, funding,
|
|
1257
|
+
ticker, quote, headline, extended, kind, venue, marketCap, funding, stale, lastTrade,
|
|
938
1258
|
row: {
|
|
939
1259
|
symbol,
|
|
940
1260
|
...listingColumns(ticker, quote),
|
|
@@ -951,6 +1271,9 @@ async function valueWatchlistRows({
|
|
|
951
1271
|
marketCapCurrency: currency,
|
|
952
1272
|
// When the price shown was observed, as `quote` dates it.
|
|
953
1273
|
quoteAsOf: quote ? known(quoteHeadlineTime(quote)) : null,
|
|
1274
|
+
// A stale quote (as the source line counts it) and the exchange-local date it last traded.
|
|
1275
|
+
stale,
|
|
1276
|
+
lastTradeDate: lastTrade,
|
|
954
1277
|
},
|
|
955
1278
|
});
|
|
956
1279
|
}
|