gloomberb 0.16.3 → 0.16.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -1
- package/src/api-client/askg.ts +43 -0
- package/src/api-client/auth.ts +15 -0
- package/src/api-client/beneficial-owners.ts +4 -0
- package/src/api-client/chat.ts +19 -0
- package/src/api-client/company-kpis.ts +14 -2
- package/src/api-client/data.ts +34 -16
- package/src/api-client/index.ts +12 -0
- package/src/api-client/normalizers.ts +27 -0
- package/src/api-client/paths.ts +88 -23
- package/src/api-client/request.ts +2 -1
- package/src/api-client/research-activity.ts +31 -0
- package/src/api-client/socket.ts +39 -8
- package/src/api-client/types.ts +57 -1
- package/src/api-client/upgrade-experiment.ts +90 -0
- package/src/app/global-shortcuts.ts +11 -1
- package/src/app/keybindings/actions.ts +10 -0
- package/src/app/pane-runtime/index.tsx +8 -0
- package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
- package/src/app/pane-runtime/plugin-bindings.ts +65 -25
- package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
- package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
- package/src/app/runtime/desktop-deeplink.ts +5 -0
- package/src/app/star-prompt/model.ts +98 -0
- package/src/app/star-prompt/runtime.ts +41 -0
- package/src/app/star-prompt/status-line.tsx +190 -0
- package/src/app.tsx +13 -7
- package/src/architecture/core-builtin-allowlist.json +70 -0
- package/src/brokers/signed-in/adapter.ts +33 -2
- package/src/brokers/signed-in/client.ts +10 -0
- package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
- package/src/brokers/signed-in/sign-in.ts +62 -5
- package/src/capabilities/registry.ts +8 -1
- package/src/capabilities/types.ts +2 -0
- package/src/cli/commands/crypto-hints.ts +12 -4
- package/src/cli/commands/market.ts +359 -99
- package/src/cli/commands/options-chain.ts +172 -0
- package/src/cli/commands/options-leaps.ts +422 -0
- package/src/cli/commands/overview.ts +164 -26
- package/src/cli/commands/search.ts +29 -8
- package/src/cli/commands/system.ts +99 -19
- package/src/cli/commands/themes.ts +64 -0
- package/src/cli/commands/ticker.ts +62 -30
- package/src/cli/desktop-pane-shot.ts +97 -16
- package/src/cli/errors.ts +25 -2
- package/src/cli/freshness.ts +7 -4
- package/src/cli/fx-pair.ts +57 -0
- package/src/cli/help.ts +14 -1
- package/src/cli/index-roots.ts +51 -0
- package/src/cli/index.ts +9 -6
- package/src/cli/listing-arg.ts +211 -3
- package/src/cli/not-a-ticker.ts +37 -0
- package/src/cli/options.ts +50 -6
- package/src/cli/pane-functions/capabilities.ts +8 -2
- package/src/cli/pane-functions/catalog.ts +13 -2
- package/src/cli/pane-functions/data.ts +36 -5
- package/src/cli/pane-functions/dom.ts +21 -13
- package/src/cli/pane-functions/freshness.ts +205 -26
- package/src/cli/pane-functions/function-help.ts +67 -22
- package/src/cli/pane-functions/headless.ts +5 -4
- package/src/cli/pane-functions/index.ts +3 -0
- package/src/cli/pane-functions/market-session.ts +133 -0
- package/src/cli/pane-functions/options.ts +15 -4
- package/src/cli/pane-functions/report-notices.ts +63 -0
- package/src/cli/pane-functions/report.ts +4 -0
- package/src/cli/pane-functions/resolver.ts +4 -6
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +105 -23
- package/src/cli/registry.ts +2 -1
- package/src/cli/result.ts +50 -13
- package/src/cli/row-window.ts +76 -0
- package/src/cli/scroll-truncation.ts +34 -0
- package/src/components/chart/composite/composite-chart.tsx +43 -4
- package/src/components/chart/composite/legend.tsx +39 -6
- package/src/components/chart/composite/rasterizer.ts +64 -4
- package/src/components/chart/composite/series-cues.ts +71 -0
- package/src/components/chart/composite/text-renderer.ts +16 -8
- package/src/components/chart/composite/types.ts +3 -0
- package/src/components/command-bar/commands/direct/index.ts +9 -1
- package/src/components/command-bar/commands/plugin/items.ts +3 -1
- package/src/components/command-bar/commands/registry.ts +7 -0
- package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/routes/root/command-items.ts +2 -0
- package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
- package/src/components/command-bar/routes/root/results.ts +40 -10
- package/src/components/command-bar/routes/root/runtime.ts +8 -0
- package/src/components/command-bar/routes/root/search-providers.ts +97 -14
- package/src/components/command-bar/surface/index.tsx +18 -4
- package/src/components/command-bar/theme-picker.tsx +48 -13
- package/src/components/command-bar/workflow/ops.ts +5 -3
- package/src/components/data-table/view.tsx +28 -9
- package/src/components/layout/floating-pane.tsx +6 -2
- package/src/components/layout/market-summary.ts +23 -9
- package/src/components/layout/pane/content.tsx +46 -29
- package/src/components/layout/pane/cycle-order.ts +2 -11
- package/src/components/layout/pane/header.tsx +2 -1
- package/src/components/layout/pane/title.ts +1 -1
- package/src/components/layout/presentation.ts +22 -0
- package/src/components/layout/shell/active-drag.ts +1 -1
- package/src/components/layout/shell/drag/index.ts +4 -0
- package/src/components/layout/shell/drag/live.ts +76 -5
- package/src/components/layout/shell/drag/runtime.ts +8 -8
- package/src/components/layout/shell/index.tsx +47 -6
- package/src/components/layout/shell/layout-state.ts +5 -11
- package/src/components/layout/shell/menu.ts +28 -1
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +51 -10
- package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
- package/src/components/layout/shell/pane/new-layout.ts +116 -0
- package/src/components/layout/shell/shortcuts.ts +5 -2
- package/src/components/layout/shell/visible-layout.ts +44 -1
- package/src/components/layout/status-bar.tsx +4 -1
- package/src/components/markdown-text.tsx +9 -5
- package/src/components/metric-treemap/heat-surface.tsx +27 -8
- package/src/components/metric-treemap/heat-tiles.ts +48 -17
- package/src/components/onboarding/onboarding-frame.tsx +33 -10
- package/src/components/onboarding/onboarding-wizard.tsx +49 -40
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
- package/src/components/onboarding/pro-step-layout.ts +86 -0
- package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
- package/src/components/ui/button.tsx +10 -0
- package/src/components/ui/data-table/index.tsx +38 -0
- package/src/components/ui/data-table/types.ts +8 -1
- package/src/components/ui/data-table/visible-range.ts +2 -0
- package/src/components/ui/icon.tsx +11 -2
- package/src/components/ui/index.ts +1 -1
- package/src/core/connection-health.ts +1 -1
- package/src/core/state/app/layout-reducer.ts +89 -3
- package/src/core/state/app/state.ts +8 -5
- package/src/core/state/app/types.ts +13 -2
- package/src/data/config/layout.ts +55 -21
- package/src/data/config/store/fold-panes.ts +149 -0
- package/src/data/config/store/migrations.ts +7 -0
- package/src/data/config/store/node.ts +27 -6
- package/src/data/config/store/normalize.ts +37 -3
- package/src/i18n/es.ts +14 -1
- package/src/i18n/ja.ts +14 -1
- package/src/i18n/ko.ts +14 -1
- package/src/i18n/zh-cn.ts +14 -1
- package/src/i18n/zh-tw.ts +14 -1
- package/src/layout/desks.ts +1 -2
- package/src/layout/pane-layout-move.ts +264 -0
- package/src/layout/pane-manager/hidden-panes.ts +160 -0
- package/src/layout/pane-manager/index.ts +1 -0
- package/src/market-data/coordinator/entries.ts +7 -1
- package/src/market-data/coordinator/quotes.ts +2 -2
- package/src/market-data/market/freshness.ts +118 -18
- package/src/market-data/market/status.ts +40 -0
- package/src/market-data/options-alternatives.ts +69 -0
- package/src/market-data/quotes/contributions.ts +19 -2
- package/src/market-data/quotes/metadata.ts +16 -0
- package/src/market-data/quotes/screener-live-quotes.ts +56 -5
- package/src/plugins/builtin/account-management/keyboard.ts +3 -0
- package/src/plugins/builtin/account-management/model.ts +1 -0
- package/src/plugins/builtin/account-management/navigation.ts +1 -1
- package/src/plugins/builtin/account-management/pane.tsx +31 -4
- package/src/plugins/builtin/alerts/quotes.ts +3 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
- package/src/plugins/builtin/analytics/risk-model.ts +37 -8
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
- package/src/plugins/builtin/backtest/pane.tsx +1 -1
- package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/broker-manager/model.ts +8 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
- package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
- package/src/plugins/builtin/broker-manager/table.ts +5 -0
- package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
- package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
- package/src/plugins/builtin/chat/channels.ts +34 -36
- package/src/plugins/builtin/chat/command-bar.ts +138 -0
- package/src/plugins/builtin/chat/composer-commands.ts +21 -1
- package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
- package/src/plugins/builtin/chat/content/footer.tsx +5 -3
- package/src/plugins/builtin/chat/content/index.tsx +21 -1
- package/src/plugins/builtin/chat/content/mentions.ts +2 -1
- package/src/plugins/builtin/chat/content/user-map.ts +3 -1
- package/src/plugins/builtin/chat/controller/index.ts +48 -0
- package/src/plugins/builtin/chat/conversations.ts +256 -0
- package/src/plugins/builtin/chat/discord-command.ts +91 -0
- package/src/plugins/builtin/chat/edit-window.ts +12 -6
- package/src/plugins/builtin/chat/ghost-user.ts +14 -0
- package/src/plugins/builtin/chat/index.tsx +40 -2
- package/src/plugins/builtin/chat/layout.ts +2 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
- package/src/plugins/builtin/chat/message/header.tsx +24 -15
- package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
- package/src/plugins/builtin/chat/message/render-state.ts +3 -0
- package/src/plugins/builtin/chat/pane-requests.ts +60 -0
- package/src/plugins/builtin/chat/pane-state.ts +32 -2
- package/src/plugins/builtin/chat/pane.tsx +8 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
- package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
- package/src/plugins/builtin/cloud/askg/model.ts +129 -70
- package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
- package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
- package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
- package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
- package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
- package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
- package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
- package/src/plugins/builtin/cloud/plugin.tsx +36 -1
- package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
- package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
- package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
- package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
- package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
- package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
- package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
- package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
- package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
- package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
- package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
- package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
- package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
- package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
- package/src/plugins/builtin/comment-letters/client.ts +1 -3
- package/src/plugins/builtin/company-kpis/client.ts +23 -8
- package/src/plugins/builtin/company-kpis/headless.ts +3 -1
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
- package/src/plugins/builtin/composite-plugins.ts +107 -64
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
- package/src/plugins/builtin/credit-boards/index.tsx +21 -21
- package/src/plugins/builtin/credit-boards/model.ts +0 -1
- package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
- package/src/plugins/builtin/debt-maturities/client.ts +37 -9
- package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/data.ts +28 -7
- package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
- package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
- package/src/plugins/builtin/econ/calendar-model.ts +42 -17
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/index.tsx +68 -22
- package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +17 -0
- package/src/plugins/builtin/executives/data.ts +17 -6
- package/src/plugins/builtin/executives/pane.tsx +16 -7
- package/src/plugins/builtin/filing-events/pane.tsx +22 -5
- package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
- package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
- package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
- package/src/plugins/builtin/gpu/headless.ts +9 -3
- package/src/plugins/builtin/gpu/model.ts +14 -5
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
- package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
- package/src/plugins/builtin/holders/headless.ts +6 -1
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/insider/index.tsx +4 -4
- package/src/plugins/builtin/iv-history/charts.tsx +21 -7
- package/src/plugins/builtin/iv-history/client.ts +12 -2
- package/src/plugins/builtin/iv-history/format.ts +33 -2
- package/src/plugins/builtin/iv-history/headless.ts +22 -10
- package/src/plugins/builtin/iv-history/index.tsx +3 -3
- package/src/plugins/builtin/iv-history/model.ts +61 -13
- package/src/plugins/builtin/iv-history/pane.tsx +11 -3
- package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
- package/src/plugins/builtin/jobs/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
- package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +109 -39
- package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
- package/src/plugins/builtin/layout-manager/index.tsx +9 -1
- package/src/plugins/builtin/macro-day/client.ts +24 -0
- package/src/plugins/builtin/macro-day/headless.ts +11 -8
- package/src/plugins/builtin/macro-day/index.tsx +3 -0
- package/src/plugins/builtin/macro-day/model.ts +11 -6
- package/src/plugins/builtin/macro-day/pane.tsx +12 -8
- package/src/plugins/builtin/macro-day/release-table.ts +40 -0
- package/src/plugins/builtin/macro-day/releases.ts +62 -44
- package/src/plugins/builtin/market-heatmap/data.ts +25 -5
- package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
- package/src/plugins/builtin/market-heatmap/model.ts +94 -3
- package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
- package/src/plugins/builtin/market-movers/headless.ts +31 -4
- package/src/plugins/builtin/market-movers/index.tsx +8 -1
- package/src/plugins/builtin/market-movers/model.ts +71 -17
- package/src/plugins/builtin/market-movers/table.ts +50 -23
- package/src/plugins/builtin/news/headless.ts +9 -2
- package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
- package/src/plugins/builtin/openfda/client.ts +1 -1
- package/src/plugins/builtin/options/carry.ts +95 -0
- package/src/plugins/builtin/options/index.tsx +58 -5
- package/src/plugins/builtin/options/strike-window.ts +183 -0
- package/src/plugins/builtin/options/table.ts +55 -3
- package/src/plugins/builtin/options/types.ts +8 -1
- package/src/plugins/builtin/options/view.tsx +161 -12
- package/src/plugins/builtin/options-positioning/headless.ts +4 -2
- package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
- package/src/plugins/builtin/options-scenario/client.ts +73 -17
- package/src/plugins/builtin/options-scenario/headless.ts +71 -24
- package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
- package/src/plugins/builtin/options-scenario/index.tsx +9 -2
- package/src/plugins/builtin/options-scenario/model.ts +33 -0
- package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
- package/src/plugins/builtin/pe-band/model.ts +2 -2
- package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
- package/src/plugins/builtin/perps/equity-row.tsx +8 -5
- package/src/plugins/builtin/perps/model.ts +16 -5
- package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
- package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
- package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
- package/src/plugins/builtin/plugin-module.ts +87 -4
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
- package/src/plugins/builtin/portfolio-list/cli/render.ts +87 -18
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
- package/src/plugins/builtin/portfolio-list/headless.ts +38 -7
- package/src/plugins/builtin/realized-vol/headless.ts +3 -0
- package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
- package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
- package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
- package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
- package/src/plugins/builtin/risk-factors/data.ts +23 -9
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
- package/src/plugins/builtin/seasonality/pane.tsx +1 -1
- package/src/plugins/builtin/sec/index.tsx +5 -5
- package/src/plugins/builtin/sectors/client.ts +10 -2
- package/src/plugins/builtin/sectors/headless.ts +35 -6
- package/src/plugins/builtin/sectors/index.tsx +18 -1
- package/src/plugins/builtin/sectors/sector-model.ts +150 -41
- package/src/plugins/builtin/shared/command-tab.ts +51 -0
- package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
- package/src/plugins/builtin/shared/members-menu.tsx +4 -4
- package/src/plugins/builtin/shared/report-freshness.ts +50 -2
- package/src/plugins/builtin/shared/ticker-request.ts +25 -1
- package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
- package/src/plugins/builtin/shared/volatility/options.ts +1 -46
- package/src/plugins/builtin/short-interest/index.tsx +19 -4
- package/src/plugins/builtin/short-interest/surface.tsx +4 -4
- package/src/plugins/builtin/short-volume/index.tsx +1 -10
- package/src/plugins/builtin/supply-chain/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/headless.ts +43 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
- package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
- package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
- package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
- package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
- package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
- package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
- package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
- package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
- package/src/plugins/builtin/vol-surface/client.ts +6 -2
- package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
- package/src/plugins/builtin/vol-surface/headless.ts +124 -20
- package/src/plugins/builtin/vol-surface/model.ts +23 -11
- package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
- package/src/plugins/builtin/vol-surface/tables.ts +124 -0
- package/src/plugins/builtin/world-indices/client.ts +22 -4
- package/src/plugins/builtin/world-indices/headless.ts +54 -27
- package/src/plugins/builtin/world-indices/model.ts +4 -2
- package/src/plugins/builtin/world-indices/table.tsx +13 -2
- package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
- package/src/plugins/builtin/world-venue-map/index.ts +1 -1
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/builtin-editorial.ts +126 -0
- package/src/plugins/catalog-backend.ts +16 -9
- package/src/plugins/catalog-browser.ts +127 -63
- package/src/plugins/catalog-manifest.ts +73 -40
- package/src/plugins/catalog-ui.ts +26 -6
- package/src/plugins/extracted-plugins.ts +7 -1
- package/src/plugins/ownership.ts +150 -7
- package/src/plugins/plugin-functions.ts +54 -0
- package/src/plugins/plugin-off.ts +36 -0
- package/src/plugins/registry/host-actions.ts +16 -0
- package/src/plugins/registry/index.ts +84 -24
- package/src/plugins/registry/plugin-state.ts +25 -1
- package/src/plugins/runtime/context.tsx +29 -0
- package/src/react/input.ts +48 -1
- package/src/remote/app-host.tsx +5 -0
- package/src/remote/controller.ts +3 -53
- package/src/remote/matching.ts +69 -0
- package/src/remote/schema.ts +1 -1
- package/src/remote/semantic-tree.tsx +14 -0
- package/src/remote/types.ts +2 -0
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/query-bar.tsx +59 -32
- package/src/renderers/dom/host/box.tsx +1 -1
- package/src/renderers/dom/host/drag-shield.ts +17 -6
- package/src/renderers/dom/host/live-frame.ts +10 -2
- package/src/renderers/dom/input-host.tsx +6 -1
- package/src/renderers/dom/styles.css +13 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +5 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
- package/src/renderers/electrobun/view/main.tsx +4 -1
- package/src/renderers/opentui/input-host.tsx +6 -1
- package/src/renderers/opentui/start.tsx +9 -0
- package/src/shares/portable-layout.ts +16 -6
- package/src/sources/gloomberb-cloud/index.ts +19 -10
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -1
- package/src/sources/listing-symbols.ts +11 -5
- package/src/sources/provider-errors.ts +88 -3
- package/src/sources/provider-router/cached-routes.ts +8 -4
- package/src/sources/provider-router/financial-routes.ts +11 -5
- package/src/sources/provider-router/financials.ts +42 -15
- package/src/sources/provider-router/history.ts +27 -11
- package/src/sources/provider-router/index.ts +1 -1
- package/src/sources/provider-router/primary.ts +23 -5
- package/src/sources/sec-edgar.ts +68 -8
- package/src/sync/core-contributors.ts +4 -3
- package/src/test-support/cli-context.ts +2 -1
- package/src/test-support/data-provider.ts +34 -0
- package/src/test-support/shell-pane-keys.tsx +1 -0
- package/src/theme/series-colors.ts +46 -0
- package/src/theme/themes.ts +149 -0
- package/src/tickers/search/index.ts +22 -2
- package/src/tickers/search/ranking.ts +45 -1
- package/src/tickers/search/types.ts +3 -1
- package/src/time-series/resolve.ts +1 -14
- package/src/types/config.ts +61 -1
- package/src/types/headless.ts +11 -0
- package/src/types/instrument.ts +8 -0
- package/src/types/plugin.ts +49 -3
- package/src/ui/host.tsx +8 -4
- package/src/ui/index.tsx +2 -0
- package/src/utils/cli-output.ts +14 -1
- package/src/utils/exchanges.ts +13 -0
- package/src/utils/format.ts +2 -1
- package/src/utils/fx-market-hours.ts +34 -0
- package/src/utils/option-expiry.ts +126 -0
- package/src/utils/price-history.ts +39 -6
- package/src/utils/rpc-timeout-error.ts +6 -2
- package/src/utils/sec-eps-basis.ts +159 -30
- package/src/utils/sec.ts +38 -34
- package/src/utils/throttled-fetch.ts +51 -22
|
@@ -3,6 +3,8 @@ import type {
|
|
|
3
3
|
GloomPlugin,
|
|
4
4
|
GloomPluginContext,
|
|
5
5
|
} from "../../types/plugin";
|
|
6
|
+
import { pluginContextInNamespace } from "../registry/plugin-state";
|
|
7
|
+
import { withPluginStateNamespace } from "../runtime/context";
|
|
6
8
|
|
|
7
9
|
type PluginMetadataKey =
|
|
8
10
|
| "id"
|
|
@@ -21,6 +23,22 @@ type PluginMetadata = Pick<GloomPlugin, PluginMetadataKey>;
|
|
|
21
23
|
// Brokers and declared hosts belong to external plugins; no built-in module contributes them.
|
|
22
24
|
export type PluginModule = Omit<GloomPlugin, PluginMetadataKey | "broker" | "hosts">;
|
|
23
25
|
|
|
26
|
+
/** A module with a say in how it is composed. A bare module takes the defaults. */
|
|
27
|
+
interface PluginModuleEntry {
|
|
28
|
+
module: PluginModule;
|
|
29
|
+
/**
|
|
30
|
+
* The state namespace the module keeps when it sits in a plugin whose own
|
|
31
|
+
* namespace is different: its panes, tabs and slots read pane, plugin and
|
|
32
|
+
* config state there, and its setup gets persistence and caches there. Set
|
|
33
|
+
* it when a module moves to another plugin so its users keep their state.
|
|
34
|
+
*/
|
|
35
|
+
stateId?: string;
|
|
36
|
+
/** Left out of the plugin the desktop backend process runs. */
|
|
37
|
+
rendererOnly?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type PluginModuleInput = PluginModule | PluginModuleEntry;
|
|
41
|
+
|
|
24
42
|
const HANDLED_MODULE_KEYS = [
|
|
25
43
|
"cliCommands",
|
|
26
44
|
"setup",
|
|
@@ -36,7 +54,43 @@ const ALL_MODULE_KEYS_HANDLED: MissingModuleKey extends never ? true : never = t
|
|
|
36
54
|
void ALL_MODULE_KEYS_HANDLED;
|
|
37
55
|
|
|
38
56
|
interface CompositePluginOptions extends PluginMetadata {
|
|
39
|
-
modules: readonly
|
|
57
|
+
modules: readonly PluginModuleInput[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function moduleEntry(input: PluginModuleInput): PluginModuleEntry {
|
|
61
|
+
return "module" in input ? input : { module: input };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The module as it runs under a namespace other than its plugin's: every
|
|
66
|
+
* surface it renders and the context its setup receives are moved there,
|
|
67
|
+
* while what it contributes still belongs to the plugin.
|
|
68
|
+
*/
|
|
69
|
+
function moduleInNamespace(module: PluginModule, stateId: string): PluginModule {
|
|
70
|
+
const slots = module.slots && Object.fromEntries(Object.entries(module.slots).map(([slotName, renderer]) => [
|
|
71
|
+
slotName,
|
|
72
|
+
renderer && withPluginStateNamespace(stateId, renderer as (props: unknown) => ReactNode),
|
|
73
|
+
])) as PluginModule["slots"];
|
|
74
|
+
return {
|
|
75
|
+
...module,
|
|
76
|
+
...(module.panes ? {
|
|
77
|
+
panes: module.panes.map((pane) => ({ ...pane, component: withPluginStateNamespace(stateId, pane.component) })),
|
|
78
|
+
} : {}),
|
|
79
|
+
...(slots ? { slots } : {}),
|
|
80
|
+
...(module.setup ? {
|
|
81
|
+
setup: (ctx: GloomPluginContext) => {
|
|
82
|
+
const scoped = pluginContextInNamespace(ctx, stateId);
|
|
83
|
+
return module.setup!({
|
|
84
|
+
...scoped,
|
|
85
|
+
registerPane: (pane) => ctx.registerPane({ ...pane, component: withPluginStateNamespace(stateId, pane.component) }),
|
|
86
|
+
registerTickerResearchTab: (tab) => ctx.registerTickerResearchTab({
|
|
87
|
+
...tab,
|
|
88
|
+
component: withPluginStateNamespace(stateId, tab.component),
|
|
89
|
+
}),
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
} : {}),
|
|
93
|
+
};
|
|
40
94
|
}
|
|
41
95
|
|
|
42
96
|
function composeSlots(modules: readonly PluginModule[]): GloomPlugin["slots"] {
|
|
@@ -65,14 +119,23 @@ function composeSlots(modules: readonly PluginModule[]): GloomPlugin["slots"] {
|
|
|
65
119
|
return slots as GloomPlugin["slots"];
|
|
66
120
|
}
|
|
67
121
|
|
|
122
|
+
const compositions = new WeakMap<GloomPlugin, CompositePluginOptions>();
|
|
123
|
+
const backendVariants = new WeakMap<GloomPlugin, GloomPlugin>();
|
|
124
|
+
|
|
68
125
|
/**
|
|
69
126
|
* Combines implementation modules under one real plugin identity.
|
|
70
127
|
*
|
|
71
128
|
* Modules deliberately have no identity or toggle metadata. Every contribution
|
|
72
|
-
* is owned,
|
|
129
|
+
* is owned, enabled, and disposed through the returned parent plugin, and
|
|
130
|
+
* persisted under its namespace unless the module's entry keeps another.
|
|
73
131
|
*/
|
|
74
132
|
export function composeBuiltinPlugin(options: CompositePluginOptions): GloomPlugin {
|
|
75
|
-
const { modules, ...metadata } = options;
|
|
133
|
+
const { modules: inputs, ...metadata } = options;
|
|
134
|
+
const pluginStateId = metadata.stateId ?? metadata.id;
|
|
135
|
+
const modules = inputs.map((input) => {
|
|
136
|
+
const { module, stateId } = moduleEntry(input);
|
|
137
|
+
return stateId && stateId !== pluginStateId ? moduleInNamespace(module, stateId) : module;
|
|
138
|
+
});
|
|
76
139
|
const cliCommands = modules.flatMap((module) => module.cliCommands ?? []);
|
|
77
140
|
const panes = modules.flatMap((module) => module.panes ?? []);
|
|
78
141
|
const paneTemplates = modules.flatMap((module) => module.paneTemplates ?? []);
|
|
@@ -80,7 +143,7 @@ export function composeBuiltinPlugin(options: CompositePluginOptions): GloomPlug
|
|
|
80
143
|
const slots = composeSlots(modules);
|
|
81
144
|
let startedModules: PluginModule[] = [];
|
|
82
145
|
|
|
83
|
-
|
|
146
|
+
const plugin: GloomPlugin = {
|
|
84
147
|
...metadata,
|
|
85
148
|
...(cliCommands.length > 0 ? { cliCommands } : {}),
|
|
86
149
|
...(panes.length > 0 ? { panes } : {}),
|
|
@@ -114,4 +177,24 @@ export function composeBuiltinPlugin(options: CompositePluginOptions): GloomPlug
|
|
|
114
177
|
if (firstError) throw firstError;
|
|
115
178
|
},
|
|
116
179
|
};
|
|
180
|
+
compositions.set(plugin, options);
|
|
181
|
+
return plugin;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The plugin as the desktop backend process runs it: the same identity
|
|
186
|
+
* without its renderer-only modules. Anything else comes back unchanged.
|
|
187
|
+
*/
|
|
188
|
+
export function withoutRendererOnlyModules(plugin: GloomPlugin): GloomPlugin {
|
|
189
|
+
const options = compositions.get(plugin);
|
|
190
|
+
if (!options?.modules.some((input) => moduleEntry(input).rendererOnly)) return plugin;
|
|
191
|
+
let variant = backendVariants.get(plugin);
|
|
192
|
+
if (!variant) {
|
|
193
|
+
variant = composeBuiltinPlugin({
|
|
194
|
+
...options,
|
|
195
|
+
modules: options.modules.filter((input) => !moduleEntry(input).rendererOnly),
|
|
196
|
+
});
|
|
197
|
+
backendVariants.set(plugin, variant);
|
|
198
|
+
}
|
|
199
|
+
return variant;
|
|
117
200
|
}
|
|
@@ -11,7 +11,7 @@ import type { AppConfig } from "../../../../types/config";
|
|
|
11
11
|
import type { CliCommandContext, CliCommandDef } from "../../../../types/plugin";
|
|
12
12
|
import type { Portfolio, TickerRecord } from "../../../../types/ticker";
|
|
13
13
|
import type { TickerRepository } from "../../../../data/ticker-repository";
|
|
14
|
-
import {
|
|
14
|
+
import { canonicalTickerKey, parsePublicTickerKey } from "../../../../utils/exchanges";
|
|
15
15
|
import {
|
|
16
16
|
addTickerToPortfolio,
|
|
17
17
|
adoptFirstPositionCurrency,
|
|
@@ -38,7 +38,6 @@ import {
|
|
|
38
38
|
} from "../allocation";
|
|
39
39
|
import { findCachedPortfolioAccount } from "../cached-account";
|
|
40
40
|
import {
|
|
41
|
-
describeTicker,
|
|
42
41
|
failPortfolioCommand,
|
|
43
42
|
isCashArgument,
|
|
44
43
|
listingFields,
|
|
@@ -204,12 +203,19 @@ async function setPositionCommand(
|
|
|
204
203
|
await store.saveTicker(result.ticker);
|
|
205
204
|
const adopted = firstPosition ? adoptFirstPositionCurrency(config, portfolio.id, currency) : null;
|
|
206
205
|
if (adopted) await saveConfig(adopted);
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
[
|
|
212
|
-
|
|
206
|
+
const saved = savedListingName(result.ticker);
|
|
207
|
+
ctx.printResult({
|
|
208
|
+
data: { changed: true, ...listingFields(saved), portfolio: portfolio.name, shares, avgCost, currency },
|
|
209
|
+
}, {
|
|
210
|
+
text: () => [
|
|
211
|
+
cliStyles.success(`Set position for ${saved.label} in "${portfolio.name}".`),
|
|
212
|
+
renderStats([
|
|
213
|
+
["Shares", formatMarketQuantity(shares, { assetCategory: result.ticker.metadata.assetCategory })],
|
|
214
|
+
["Average Cost", formatMarketCostWithCurrency(avgCost, currency, { assetCategory: result.ticker.metadata.assetCategory })],
|
|
215
|
+
["Currency", currency],
|
|
216
|
+
]),
|
|
217
|
+
].join("\n"),
|
|
218
|
+
});
|
|
213
219
|
} catch (error) {
|
|
214
220
|
failPortfolioCommand(ctx, error, `Failed to set position for ${symbol} in "${portfolioName}".`);
|
|
215
221
|
}
|
|
@@ -275,7 +281,8 @@ async function clearCashCommand(name: string, ctx: CliCommandContext) {
|
|
|
275
281
|
});
|
|
276
282
|
}
|
|
277
283
|
|
|
278
|
-
|
|
284
|
+
/** The note under a target change when the targets do not add up to the whole portfolio, else null. */
|
|
285
|
+
function targetSumNote(config: AppConfig, portfolioId: string, tickers: readonly TickerRecord[]): string | null {
|
|
279
286
|
const portfolio = config.portfolios.find((entry) => entry.id === portfolioId);
|
|
280
287
|
const listed = new Set([CASH_SYMBOL, ...tickers.filter((ticker) => ticker.metadata.portfolios.includes(portfolioId)).map((ticker) => ticker.metadata.ticker)]);
|
|
281
288
|
let sum: number | null = null;
|
|
@@ -283,24 +290,32 @@ function printTargetSumNote(config: AppConfig, portfolioId: string, tickers: rea
|
|
|
283
290
|
if (listed.has(symbol)) sum = (sum ?? 0) + weight;
|
|
284
291
|
}
|
|
285
292
|
const note = describeTargetSum(sum);
|
|
286
|
-
|
|
293
|
+
return note ? cliStyles.muted(note) : null;
|
|
287
294
|
}
|
|
288
295
|
|
|
289
|
-
/**
|
|
296
|
+
/**
|
|
297
|
+
* The portfolio's own row for a listing, without a search when it is already
|
|
298
|
+
* listed. A named exchange (SAN:EPA, or SAN --exchange EPA) matches only a row
|
|
299
|
+
* on that venue, whatever key the row is saved under: a bare SAN row is one
|
|
300
|
+
* listing, Paris or New York, never both. A bare SAN takes the row saved under
|
|
301
|
+
* that key, else any row of the symbol.
|
|
302
|
+
*/
|
|
290
303
|
async function findListedTicker(store: TickerRepository, portfolio: Portfolio, symbol: string, exchange?: string): Promise<TickerRecord | null> {
|
|
291
304
|
const listing = parseListingArg(symbol, exchange);
|
|
292
|
-
const typed = symbol.trim().toUpperCase();
|
|
293
305
|
const listed = (await store.loadAllTickers()).filter((ticker) => ticker.metadata.portfolios.includes(portfolio.id));
|
|
306
|
+
const savedAs = (ticker: TickerRecord, key: string) => ticker.metadata.ticker.toUpperCase() === key;
|
|
307
|
+
if (!listing.exchange) {
|
|
308
|
+
const sameSymbol = listed.filter((ticker) => parsePublicTickerKey(ticker.metadata.ticker).symbol === listing.symbol);
|
|
309
|
+
return sameSymbol.find((ticker) => savedAs(ticker, listing.symbol)) ?? sameSymbol[0] ?? null;
|
|
310
|
+
}
|
|
311
|
+
const onVenue = listed.filter((ticker) => canonicalTickerKey(ticker.metadata.ticker, ticker.metadata.exchange) === listing.key);
|
|
294
312
|
// The key as saved (VTI:XMEX), else the same listing under another spelling of its venue (VTI:BMV).
|
|
295
|
-
return
|
|
296
|
-
?? listed.find((ticker) => {
|
|
297
|
-
const saved = parsePublicTickerKey(ticker.metadata.ticker);
|
|
298
|
-
return saved.symbol === listing.symbol
|
|
299
|
-
&& (!listing.exchange || canonicalExchange(saved.exchange ?? ticker.metadata.exchange) === listing.exchange);
|
|
300
|
-
})
|
|
301
|
-
?? null;
|
|
313
|
+
return onVenue.find((ticker) => savedAs(ticker, listing.key)) ?? onVenue[0] ?? null;
|
|
302
314
|
}
|
|
303
315
|
|
|
316
|
+
/** What a target command reports for the cash line, in the fields a listing has. */
|
|
317
|
+
const CASH_LISTING = { key: CASH_SYMBOL, symbol: CASH_SYMBOL, exchange: "", name: null };
|
|
318
|
+
|
|
304
319
|
async function setTargetCommand(rest: string[], exchange: string | undefined, ctx: CliCommandContext) {
|
|
305
320
|
const rawWeight = rest.at(-1);
|
|
306
321
|
const symbol = rest.at(-2);
|
|
@@ -313,8 +328,12 @@ async function setTargetCommand(rest: string[], exchange: string | undefined, ct
|
|
|
313
328
|
if (isCashArgument(symbol, exchange)) {
|
|
314
329
|
const next = setPortfolioTargetWeight(config, portfolio.id, CASH_SYMBOL, weight);
|
|
315
330
|
await saveConfig(next);
|
|
316
|
-
|
|
317
|
-
|
|
331
|
+
const note = targetSumNote(next, portfolio.id, await store.loadAllTickers());
|
|
332
|
+
ctx.printResult({
|
|
333
|
+
data: { changed: true, ...listingFields(CASH_LISTING), portfolio: portfolio.name, targetWeight: weight },
|
|
334
|
+
}, {
|
|
335
|
+
text: () => [cliStyles.success(`Set the cash target in "${portfolio.name}" to ${formatAllocationWeight(weight)}.`), note].filter(Boolean).join("\n"),
|
|
336
|
+
});
|
|
318
337
|
return;
|
|
319
338
|
}
|
|
320
339
|
let ticker = await findListedTicker(store, portfolio, symbol, exchange);
|
|
@@ -324,7 +343,7 @@ async function setTargetCommand(rest: string[], exchange: string | undefined, ct
|
|
|
324
343
|
if (!resolved.metadata.portfolios.includes(portfolio.id)) {
|
|
325
344
|
if (!isManualPortfolio(portfolio)) {
|
|
326
345
|
throw new PortfolioCliError(
|
|
327
|
-
`${
|
|
346
|
+
`${savedListingName(resolved).label} is not in "${portfolio.name}".`,
|
|
328
347
|
"A broker portfolio lists what the broker holds; set targets on its holdings.",
|
|
329
348
|
);
|
|
330
349
|
}
|
|
@@ -338,9 +357,17 @@ async function setTargetCommand(rest: string[], exchange: string | undefined, ct
|
|
|
338
357
|
}
|
|
339
358
|
const next = setPortfolioTargetWeight(config, portfolio.id, ticker.metadata.ticker, weight);
|
|
340
359
|
await saveConfig(next);
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
360
|
+
const saved = savedListingName(ticker);
|
|
361
|
+
const note = targetSumNote(next, portfolio.id, await store.loadAllTickers());
|
|
362
|
+
ctx.printResult({
|
|
363
|
+
data: { changed: true, ...listingFields(saved), portfolio: portfolio.name, targetWeight: weight, added },
|
|
364
|
+
}, {
|
|
365
|
+
text: () => [
|
|
366
|
+
added ? cliStyles.success(`Added ${saved.label} to "${portfolio.name}".`) : null,
|
|
367
|
+
cliStyles.success(`Set the target for ${saved.label} in "${portfolio.name}" to ${formatAllocationWeight(weight)}.`),
|
|
368
|
+
note,
|
|
369
|
+
].filter(Boolean).join("\n"),
|
|
370
|
+
});
|
|
344
371
|
} catch (error) {
|
|
345
372
|
failPortfolioCommand(ctx, error, `Failed to set the target for ${symbol} in "${name}".`);
|
|
346
373
|
}
|
|
@@ -355,21 +382,28 @@ async function clearTargetCommand(rest: string[], exchange: string | undefined,
|
|
|
355
382
|
const whole = config.portfolios.some((entry) => [entry.id, entry.name].some((value) => value.toLowerCase() === rest.join(" ").trim().toLowerCase()));
|
|
356
383
|
const portfolio = requirePortfolio(config, whole ? rest.join(" ") : rest.slice(0, -1).join(" "));
|
|
357
384
|
const rawSymbol = whole ? undefined : rest.at(-1);
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
385
|
+
const cash = rawSymbol != null && isCashArgument(rawSymbol, exchange);
|
|
386
|
+
const listing = rawSymbol == null || cash ? null : parseListingArg(rawSymbol, exchange);
|
|
387
|
+
// The row the listing is: the portfolio's own, else any saved one; a target is kept under that row's key.
|
|
388
|
+
const ticker = listing ? await findListedTicker(store, portfolio, rawSymbol!, exchange) ?? await loadSavedListing(store, listing) : null;
|
|
389
|
+
const saved = ticker ? savedListingName(ticker) : null;
|
|
390
|
+
const symbol = cash ? CASH_SYMBOL : ticker?.metadata.ticker ?? listing?.key;
|
|
361
391
|
const { config: next, removed } = clearPortfolioTargetWeights(config, portfolio.id, symbol);
|
|
392
|
+
const fields = cash ? listingFields(CASH_LISTING) : saved ? listingFields(saved) : listing ? listingFields({ ...listing, name: null }) : {};
|
|
393
|
+
const named = saved?.label ?? listing?.key ?? CASH_SYMBOL;
|
|
394
|
+
const data = { changed: removed.length > 0, ...fields, portfolio: portfolio.name, removedTargets: removed.length };
|
|
362
395
|
if (removed.length === 0) {
|
|
363
|
-
|
|
396
|
+
ctx.printResult({ data }, { text: () => cliStyles.warning(symbol ? `"${portfolio.name}" has no target for ${named}.` : `"${portfolio.name}" has no targets.`) });
|
|
364
397
|
return;
|
|
365
398
|
}
|
|
366
399
|
await saveConfig(next);
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
400
|
+
ctx.printResult({ data }, {
|
|
401
|
+
text: () => cliStyles.success(
|
|
402
|
+
symbol
|
|
403
|
+
? `Cleared the ${cash ? "cash target" : `target for ${named}`} in "${portfolio.name}".`
|
|
404
|
+
: `Cleared ${removed.length} target${removed.length === 1 ? "" : "s"} in "${portfolio.name}".`,
|
|
405
|
+
),
|
|
406
|
+
});
|
|
373
407
|
} catch (error) {
|
|
374
408
|
failPortfolioCommand(ctx, error, `Failed to clear targets in "${rest.join(" ")}".`);
|
|
375
409
|
}
|
|
@@ -407,7 +441,8 @@ async function showTargetsCommand(name: string, ctx: CliCommandContext) {
|
|
|
407
441
|
[{ header: "Ticker" }, { header: "Name" }, { header: "Target", align: "right" }],
|
|
408
442
|
rows.map((row) => [row.symbol, row.listed ? row.name ?? "" : cliStyles.muted("not in this portfolio"), formatAllocationWeight(row.targetWeight)]),
|
|
409
443
|
));
|
|
410
|
-
|
|
444
|
+
const note = targetSumNote(config, portfolio.id, tickers);
|
|
445
|
+
if (note) console.log(note);
|
|
411
446
|
} catch (error) {
|
|
412
447
|
failPortfolioCommand(ctx, error, `Failed to show targets for "${name}".`);
|
|
413
448
|
}
|
|
@@ -12,7 +12,16 @@ import { currencyMinorDigits, formatMarketCostWithCurrency, formatMarketPriceWit
|
|
|
12
12
|
import { resolvePriceBasis } from "../../../../market-data/market/price-basis";
|
|
13
13
|
import { getPortfolioPositionMetrics, getPortfolioQuoteDisplay, resolvePortfolioMarketValue, resolvePortfolioPositionPnl } from "../position-metrics";
|
|
14
14
|
import { resolvePortfolioTotalsCurrency } from "../summary/totals";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
EXTENDED_SESSION_LABELS,
|
|
17
|
+
exchangeShortName,
|
|
18
|
+
getActiveQuoteDisplay,
|
|
19
|
+
getExtendedSessionDisplay,
|
|
20
|
+
type ActiveQuoteDisplay,
|
|
21
|
+
type ExtendedSession,
|
|
22
|
+
type ExtendedSessionDisplay,
|
|
23
|
+
} from "../../../../market-data/market/status";
|
|
24
|
+
import { headlineQuoteDisplay } from "../column-values";
|
|
16
25
|
import type { AppConfig } from "../../../../types/config";
|
|
17
26
|
import type { DataProvider } from "../../../../types/data-provider";
|
|
18
27
|
import type { CliCommandContext } from "../../../../types/plugin";
|
|
@@ -47,6 +56,38 @@ function priceFormatOptions(quote: Parameters<typeof quoteFormatOptions>[0] & {
|
|
|
47
56
|
: options;
|
|
48
57
|
}
|
|
49
58
|
|
|
59
|
+
/**
|
|
60
|
+
* What a collection's Last and Chg show, as the PF and watchlist panes do: the
|
|
61
|
+
* traded price until the regular close, then that close and its move, with a
|
|
62
|
+
* pre-market or after-hours print beside them, measured from that close.
|
|
63
|
+
* Market value and P&L keep the live price.
|
|
64
|
+
*/
|
|
65
|
+
export function collectionQuoteHeadline(quote: CollectionQuote | undefined): {
|
|
66
|
+
headline: ActiveQuoteDisplay | null;
|
|
67
|
+
extended: ExtendedSessionDisplay | null;
|
|
68
|
+
} {
|
|
69
|
+
const traded = getActiveQuoteDisplay(quote);
|
|
70
|
+
const headline = traded && Number.isFinite(traded.price) ? headlineQuoteDisplay(traded, quote) : null;
|
|
71
|
+
const extended = headline ? getExtendedSessionDisplay(quote) : null;
|
|
72
|
+
return { headline, extended: extended && Number.isFinite(extended.price) ? extended : null };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The extended print as export fields: all null without one. */
|
|
76
|
+
export function extendedQuoteFields(extended: ExtendedSessionDisplay | null) {
|
|
77
|
+
return {
|
|
78
|
+
extendedSession: extended?.session ?? null,
|
|
79
|
+
extendedPrice: extended?.price ?? null,
|
|
80
|
+
extendedChange: extended?.change ?? null,
|
|
81
|
+
extendedChangePercent: extended?.changePercent ?? null,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The extended sessions among the rows' prints, in the order their columns go. */
|
|
86
|
+
export function extendedSessionsOf(sessions: Iterable<ExtendedSession | null | undefined>): ExtendedSession[] {
|
|
87
|
+
const present = new Set(sessions);
|
|
88
|
+
return (["PRE", "POST"] as const).filter((session) => present.has(session));
|
|
89
|
+
}
|
|
90
|
+
|
|
50
91
|
export function renderCollectionOverview(config: AppConfig, tickers: TickerRecord[]): string {
|
|
51
92
|
const blocks: string[] = [];
|
|
52
93
|
|
|
@@ -143,7 +184,12 @@ export async function loadCollectionQuotes(
|
|
|
143
184
|
interface PortfolioPositionValue {
|
|
144
185
|
ticker: TickerRecord;
|
|
145
186
|
quote: CollectionQuote | undefined;
|
|
187
|
+
/** The live print, which values the position. */
|
|
146
188
|
activeQuote: ReturnType<typeof getActiveQuoteDisplay> | null;
|
|
189
|
+
/** Last and Chg as shown: the regular session's (collectionQuoteHeadline). */
|
|
190
|
+
headline: ActiveQuoteDisplay | null;
|
|
191
|
+
/** A pre-market or after-hours print, from the close in `headline`. */
|
|
192
|
+
extended: ExtendedSessionDisplay | null;
|
|
147
193
|
/** Null for a ticker kept in the portfolio without an open position. */
|
|
148
194
|
position: TickerPosition | null;
|
|
149
195
|
metrics: ReturnType<typeof getPortfolioPositionMetrics> | null;
|
|
@@ -199,14 +245,15 @@ export async function valuePortfolioPositions({
|
|
|
199
245
|
const positions = ticker.metadata.positions.filter((position) => position.portfolio === id && position.shares !== 0);
|
|
200
246
|
const displayedQuote = getActiveQuoteDisplay(quote);
|
|
201
247
|
const activeQuote = displayedQuote && Number.isFinite(displayedQuote.price) ? displayedQuote : null;
|
|
248
|
+
const { headline, extended } = collectionQuoteHeadline(quote);
|
|
202
249
|
|
|
203
250
|
if (positions.length === 0) {
|
|
204
251
|
const quoteCurrency = quote?.currency || ticker.metadata.currency || baseCurrency;
|
|
205
252
|
const unitPrice = activeQuote ? known(await toBase(activeQuote.price, quoteCurrency)) : null;
|
|
206
253
|
holdings.push({ symbol: ticker.metadata.ticker, held: false, marketValue: 0, units: 0, unitPrice });
|
|
207
|
-
values.push({ ticker, quote, activeQuote, position: null, metrics: null, pnl: null,
|
|
254
|
+
values.push({ ticker, quote, activeQuote, headline, extended, position: null, metrics: null, pnl: null,
|
|
208
255
|
row: { symbol: ticker.metadata.ticker, exchange: ticker.metadata.exchange, shares: null, avgCost: null,
|
|
209
|
-
positionCurrency: null, quotePrice: known(
|
|
256
|
+
positionCurrency: null, quotePrice: known(headline?.price), ...extendedQuoteFields(extended), quoteCurrency: quote?.currency ?? null,
|
|
210
257
|
costBasis: null, marketValue: null, unrealizedPnl: null, baseCurrency: currency } });
|
|
211
258
|
continue;
|
|
212
259
|
}
|
|
@@ -238,11 +285,12 @@ export async function valuePortfolioPositions({
|
|
|
238
285
|
holding.marketValue = holding.marketValue == null || marketValue == null ? null : holding.marketValue + marketValue;
|
|
239
286
|
holding.units += metrics.totalShares;
|
|
240
287
|
holding.unitPrice ??= valuationQuote ? known(await toBase(valuationQuote.price, quoteCurrency)) : null;
|
|
241
|
-
values.push({ ticker, quote, activeQuote, position, metrics, pnl,
|
|
288
|
+
values.push({ ticker, quote, activeQuote, headline, extended, position, metrics, pnl,
|
|
242
289
|
row: { symbol: ticker.metadata.ticker, exchange: ticker.metadata.exchange,
|
|
243
290
|
shares: metrics.totalShares, avgCost: known(position.avgCost), positionCurrency,
|
|
244
291
|
priceBasis: position.priceBasis ?? null, quantityUnit: metrics.priceBasis === "percent-of-par" ? "face" : null, quotePriceBasis: quote?.priceBasis ?? null,
|
|
245
|
-
|
|
292
|
+
// Last as shown, the regular close after it; market value and P&L are at the live price.
|
|
293
|
+
quotePrice: known(headline?.price), ...extendedQuoteFields(extended), quoteCurrency, quoteAsOf: quote?.lastUpdated ?? null,
|
|
246
294
|
costBasis: known(direction * costBasisBase), marketValue,
|
|
247
295
|
unrealizedPnl: known(pnl), baseCurrency: currency, dateAcquired: position.dateAcquired ?? null,
|
|
248
296
|
pnlBasis: selectedPnl.basis, brokerUnrealizedPnl: known(position.unrealizedPnl), brokerPnlCurrency: positionCurrency, brokerPnlAsOf: null } });
|
|
@@ -296,6 +344,18 @@ function signed(text: string, value: number | null | undefined): string {
|
|
|
296
344
|
|
|
297
345
|
const NOT_AVAILABLE = "n/a";
|
|
298
346
|
|
|
347
|
+
/** One cell per extended column: the print and its move from the close, as `quote` prints it, or blank. */
|
|
348
|
+
function extendedCells(
|
|
349
|
+
sessions: readonly ExtendedSession[],
|
|
350
|
+
extended: ExtendedSessionDisplay | null,
|
|
351
|
+
quote: CollectionQuote | undefined,
|
|
352
|
+
assetCategory: string | undefined,
|
|
353
|
+
): string[] {
|
|
354
|
+
return sessions.map((session) => extended?.session === session && quote
|
|
355
|
+
? `${formatMarketPriceWithCurrency(extended.price, quote.currency, priceFormatOptions(quote, assetCategory))} ${colorBySign(formatPercentRaw(extended.changePercent), extended.change)}`
|
|
356
|
+
: "");
|
|
357
|
+
}
|
|
358
|
+
|
|
299
359
|
async function showCollectionWithMarketData(
|
|
300
360
|
name: string,
|
|
301
361
|
ctx: CliCommandContext,
|
|
@@ -436,18 +496,20 @@ async function showCollectionWithMarketData(
|
|
|
436
496
|
return [weight, target, drift, trade, tradeValue];
|
|
437
497
|
};
|
|
438
498
|
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
|
|
499
|
+
const extendedSessions = extendedSessionsOf(valued.map(({ extended }) => extended?.session));
|
|
500
|
+
const rows: string[][] = valued.map(({ ticker, quote, headline, extended, position, metrics, pnl, row }, index) => {
|
|
501
|
+
const priceText = quote && headline
|
|
502
|
+
? colorBySign(formatMarketPriceWithCurrency(headline.price, quote.currency, priceFormatOptions(quote, ticker.metadata.assetCategory)), headline.change)
|
|
442
503
|
: "—";
|
|
443
|
-
const changeText =
|
|
504
|
+
const changeText = headline ? colorBySign(formatPercentRaw(headline.changePercent), headline.change) : "—";
|
|
505
|
+
const quoteCells = [priceText, changeText, ...extendedCells(extendedSessions, extended, quote, ticker.metadata.assetCategory)];
|
|
444
506
|
const exported = positionsExport[index]!;
|
|
445
507
|
const figures = exported.weight != null || exported.targetWeight != null || exported.tradeValue != null
|
|
446
508
|
? { weight: exported.weight, targetWeight: exported.targetWeight, drift: exported.drift, tradeValue: exported.tradeValue, tradeUnits: exported.tradeShares }
|
|
447
509
|
: undefined;
|
|
448
510
|
const symbolUnits = allocationBySymbol.get(ticker.metadata.ticker)?.units ?? 0;
|
|
449
511
|
if (!position || !metrics) {
|
|
450
|
-
return [ticker.metadata.ticker,
|
|
512
|
+
return [ticker.metadata.ticker, ...quoteCells, "—", "—", "—",
|
|
451
513
|
...allocationCells(figures, { unpriced: false, units: symbolUnits, assetCategory: ticker.metadata.assetCategory }), "—"];
|
|
452
514
|
}
|
|
453
515
|
const positionCurrency = metrics.positionCurrency;
|
|
@@ -455,8 +517,7 @@ async function showCollectionWithMarketData(
|
|
|
455
517
|
const unpriced = marketValue == null;
|
|
456
518
|
return [
|
|
457
519
|
ticker.metadata.ticker,
|
|
458
|
-
|
|
459
|
-
changeText,
|
|
520
|
+
...quoteCells,
|
|
460
521
|
formatMarketQuantity(metrics.totalShares, { assetCategory: ticker.metadata.assetCategory, multiplier: position.multiplier, priceBasis: metrics.priceBasis, quantityCurrency: positionCurrency }),
|
|
461
522
|
formatMarketCostWithCurrency(position.avgCost, positionCurrency, { assetCategory: ticker.metadata.assetCategory, multiplier: position.multiplier, priceBasis: metrics.priceBasis }),
|
|
462
523
|
unpriced ? NOT_AVAILABLE : wholeMoney(marketValue),
|
|
@@ -466,12 +527,12 @@ async function showCollectionWithMarketData(
|
|
|
466
527
|
});
|
|
467
528
|
if (cashExport) {
|
|
468
529
|
const label = cashExport.currency && cashExport.currency !== currency ? `${CASH_SYMBOL} ${cashExport.currency}` : CASH_SYMBOL;
|
|
469
|
-
rows.push([label, "", "", "", "", cashExport.value == null ? NOT_AVAILABLE : wholeMoney(cashExport.value),
|
|
530
|
+
rows.push([label, "", "", ...extendedSessions.map(() => ""), "", "", cashExport.value == null ? NOT_AVAILABLE : wholeMoney(cashExport.value),
|
|
470
531
|
...allocationCells(cashExport, { unpriced: cashExport.value == null }), ""]);
|
|
471
532
|
}
|
|
472
533
|
const total = allocation.total;
|
|
473
534
|
rows.push([
|
|
474
|
-
cliStyles.bold("TOTAL"), "", "", "", "",
|
|
535
|
+
cliStyles.bold("TOTAL"), "", "", ...extendedSessions.map(() => ""), "", "",
|
|
475
536
|
cliStyles.bold(wholeMoney(total)),
|
|
476
537
|
total != null && total > 0 ? formatAllocationWeight(100) : "—",
|
|
477
538
|
...(showTargets ? [formatAllocationWeight(allocation.targetSum), "", "", ""] : []),
|
|
@@ -485,6 +546,8 @@ async function showCollectionWithMarketData(
|
|
|
485
546
|
{ header: "Ticker" },
|
|
486
547
|
{ header: "Last", align: "right", optional: true, dropPriority: 1 },
|
|
487
548
|
{ header: "Chg", align: "right", optional: true, dropPriority: 2 },
|
|
549
|
+
// Before Chg, after Avg Cost, which is right of it at the same priority.
|
|
550
|
+
...extendedSessions.map((session) => ({ header: EXTENDED_SESSION_LABELS[session], align: "right" as const, optional: true, dropPriority: 3 })),
|
|
488
551
|
{ header: quantityHeader, align: "right" },
|
|
489
552
|
{ header: "Avg Cost", align: "right", optional: true, dropPriority: 3 },
|
|
490
553
|
{ header: "Value", align: "right" },
|
|
@@ -522,12 +585,15 @@ async function showCollectionWithMarketData(
|
|
|
522
585
|
for (const note of notes) console.log(note);
|
|
523
586
|
} else {
|
|
524
587
|
const rows: string[][] = [];
|
|
588
|
+
const shown = new Map(filtered.map((ticker) => [ticker, collectionQuoteHeadline(quotes.get(ticker.metadata.ticker))]));
|
|
589
|
+
const extendedSessions = extendedSessionsOf([...shown.values()].map(({ extended }) => extended?.session));
|
|
525
590
|
for (const ticker of filtered) {
|
|
526
591
|
const quote = quotes.get(ticker.metadata.ticker);
|
|
527
|
-
const
|
|
528
|
-
|
|
592
|
+
const { headline, extended } = shown.get(ticker)!;
|
|
593
|
+
const priceText = quote && headline
|
|
594
|
+
? colorBySign(formatMarketPriceWithCurrency(headline.price, quote.currency, priceFormatOptions(quote, ticker.metadata.assetCategory)), headline.change)
|
|
529
595
|
: "—";
|
|
530
|
-
const changeText =
|
|
596
|
+
const changeText = headline ? colorBySign(formatPercentRaw(headline.changePercent), headline.change) : "—";
|
|
531
597
|
const marketCapText = quote?.marketCap != null
|
|
532
598
|
? `${formatCompact(await toBase(quote.marketCap, quote.currency || ticker.metadata.currency || currency))} ${currency}`
|
|
533
599
|
: "—";
|
|
@@ -537,6 +603,7 @@ async function showCollectionWithMarketData(
|
|
|
537
603
|
exchangeShortName(quote?.exchangeName, quote?.fullExchangeName) || ticker.metadata.exchange || "—",
|
|
538
604
|
priceText,
|
|
539
605
|
changeText,
|
|
606
|
+
...extendedCells(extendedSessions, extended, quote, ticker.metadata.assetCategory),
|
|
540
607
|
marketCapText,
|
|
541
608
|
]);
|
|
542
609
|
}
|
|
@@ -547,7 +614,9 @@ async function showCollectionWithMarketData(
|
|
|
547
614
|
{ header: "Exchange" },
|
|
548
615
|
{ header: "Last", align: "right" },
|
|
549
616
|
{ header: "Chg", align: "right" },
|
|
550
|
-
{ header:
|
|
617
|
+
...extendedSessions.map((session) => ({ header: EXTENDED_SESSION_LABELS[session], align: "right" as const, optional: true, dropPriority: 1 })),
|
|
618
|
+
// A narrow terminal drops these first.
|
|
619
|
+
{ header: "Mkt Cap", align: "right", optional: true, dropPriority: 2 },
|
|
551
620
|
],
|
|
552
621
|
rows,
|
|
553
622
|
));
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { AppConfig } from "../../../../types/config";
|
|
2
2
|
import type { CliCommandContext } from "../../../../types/plugin";
|
|
3
|
-
import type { TickerRecord } from "../../../../types/ticker";
|
|
4
3
|
import { parseListingArg, type SavedListingName } from "../../../../cli/listing-arg";
|
|
5
|
-
import { isUsListingExchange } from "../../../../utils/exchanges";
|
|
6
4
|
import { findPortfolio, isManualPortfolio } from "../mutations";
|
|
7
5
|
import { CASH_SYMBOL } from "../allocation";
|
|
8
6
|
|
|
@@ -23,8 +21,8 @@ export function failPortfolioCommand(ctx: CliCommandContext, error: unknown, fal
|
|
|
23
21
|
return ctx.fail(fallback);
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
/** What
|
|
27
|
-
export function listingFields(saved: SavedListingName) {
|
|
24
|
+
/** What every collection command reports about the listing it changed, as JSON and as the sentence's subject. */
|
|
25
|
+
export function listingFields(saved: Pick<SavedListingName, "key" | "symbol" | "exchange" | "name">) {
|
|
28
26
|
return { listing: saved.key, symbol: saved.symbol, exchange: saved.exchange || null, name: saved.name };
|
|
29
27
|
}
|
|
30
28
|
|
|
@@ -72,15 +70,3 @@ export function rejectCashSymbol(symbol: string, exchange: string | undefined, p
|
|
|
72
70
|
`Record cash with: gloomberb portfolio cash set "${portfolioName}" <amount> [currency]. For the stock that trades as CASH, name its exchange: CASH:NASDAQ.`,
|
|
73
71
|
);
|
|
74
72
|
}
|
|
75
|
-
|
|
76
|
-
/** `VTI (Vanguard Total Stock Market ETF)`, with the venue and currency for a listing outside the US. */
|
|
77
|
-
export function describeTicker(ticker: TickerRecord): string {
|
|
78
|
-
const { ticker: symbol, name, exchange, currency } = ticker.metadata;
|
|
79
|
-
const foreign = !!exchange && !isUsListingExchange(exchange);
|
|
80
|
-
const details = [
|
|
81
|
-
name?.trim(),
|
|
82
|
-
foreign ? exchange : undefined,
|
|
83
|
-
foreign && currency && currency.toUpperCase() !== "USD" ? currency.toUpperCase() : undefined,
|
|
84
|
-
].filter((part): part is string => !!part);
|
|
85
|
-
return details.length > 0 ? `${symbol} (${details.join(", ")})` : symbol;
|
|
86
|
-
}
|