gloomberb 0.16.2 → 0.16.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -1
- package/src/api-client/askg.ts +43 -0
- package/src/api-client/auth.ts +15 -0
- package/src/api-client/beneficial-owners.ts +4 -0
- package/src/api-client/chat.ts +19 -0
- package/src/api-client/company-kpis.ts +14 -2
- package/src/api-client/data.ts +34 -16
- package/src/api-client/index.ts +12 -0
- package/src/api-client/normalizers.ts +27 -0
- package/src/api-client/paths.ts +88 -23
- package/src/api-client/request.ts +2 -1
- package/src/api-client/research-activity.ts +31 -0
- package/src/api-client/socket.ts +39 -8
- package/src/api-client/types.ts +57 -1
- package/src/api-client/upgrade-experiment.ts +90 -0
- package/src/app/global-shortcuts.ts +11 -1
- package/src/app/keybindings/actions.ts +10 -0
- package/src/app/pane-runtime/index.tsx +8 -0
- package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
- package/src/app/pane-runtime/plugin-bindings.ts +65 -25
- package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
- package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
- package/src/app/runtime/desktop-deeplink.ts +5 -0
- package/src/app/star-prompt/model.ts +98 -0
- package/src/app/star-prompt/runtime.ts +41 -0
- package/src/app/star-prompt/status-line.tsx +190 -0
- package/src/app.tsx +13 -7
- package/src/architecture/core-builtin-allowlist.json +70 -0
- package/src/brokers/account-cache.ts +45 -0
- package/src/brokers/signed-in/adapter.ts +33 -2
- package/src/brokers/signed-in/client.ts +10 -0
- package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
- package/src/brokers/signed-in/sign-in.ts +62 -5
- package/src/capabilities/registry.ts +8 -1
- package/src/capabilities/types.ts +2 -0
- package/src/cli/command-options.ts +74 -0
- package/src/cli/commands/command-utils.ts +29 -0
- package/src/cli/commands/crypto-hints.ts +12 -4
- package/src/cli/commands/market.ts +524 -148
- package/src/cli/commands/options-chain.ts +172 -0
- package/src/cli/commands/options-leaps.ts +422 -0
- package/src/cli/commands/overview.ts +223 -42
- package/src/cli/commands/search.ts +29 -8
- package/src/cli/commands/system.ts +99 -19
- package/src/cli/commands/themes.ts +64 -0
- package/src/cli/commands/ticker.ts +200 -73
- package/src/cli/desktop-pane-shot.ts +100 -16
- package/src/cli/errors.ts +25 -2
- package/src/cli/freshness.ts +7 -4
- package/src/cli/fx-pair.ts +57 -0
- package/src/cli/help.ts +40 -7
- package/src/cli/history-rows.ts +187 -15
- package/src/cli/index-roots.ts +51 -0
- package/src/cli/index.ts +21 -13
- package/src/cli/listing-arg.ts +211 -3
- package/src/cli/not-a-ticker.ts +37 -0
- package/src/cli/options.ts +55 -7
- package/src/cli/pane-functions/capabilities.ts +13 -2
- package/src/cli/pane-functions/catalog.ts +13 -2
- package/src/cli/pane-functions/data.ts +36 -5
- package/src/cli/pane-functions/dom.ts +89 -28
- package/src/cli/pane-functions/freshness.ts +205 -26
- package/src/cli/pane-functions/function-help.ts +71 -24
- package/src/cli/pane-functions/headless.ts +168 -35
- package/src/cli/pane-functions/index.ts +12 -2
- package/src/cli/pane-functions/market-session.ts +133 -0
- package/src/cli/pane-functions/options.ts +15 -4
- package/src/cli/pane-functions/report-notices.ts +63 -0
- package/src/cli/pane-functions/report.ts +12 -1
- package/src/cli/pane-functions/resolver.ts +32 -8
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +109 -23
- package/src/cli/registry.ts +8 -4
- package/src/cli/report-tables.ts +460 -0
- package/src/cli/result.ts +63 -16
- package/src/cli/row-window.ts +76 -0
- package/src/cli/scroll-truncation.ts +34 -0
- package/src/components/chart/composite/composite-chart.tsx +43 -4
- package/src/components/chart/composite/legend.tsx +39 -6
- package/src/components/chart/composite/rasterizer.ts +64 -4
- package/src/components/chart/composite/series-cues.ts +71 -0
- package/src/components/chart/composite/text-renderer.ts +16 -8
- package/src/components/chart/composite/types.ts +3 -0
- package/src/components/command-bar/commands/direct/index.ts +9 -1
- package/src/components/command-bar/commands/plugin/items.ts +3 -1
- package/src/components/command-bar/commands/registry.ts +7 -0
- package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/routes/root/command-items.ts +2 -0
- package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
- package/src/components/command-bar/routes/root/results.ts +40 -10
- package/src/components/command-bar/routes/root/runtime.ts +8 -0
- package/src/components/command-bar/routes/root/search-providers.ts +97 -14
- package/src/components/command-bar/surface/index.tsx +18 -4
- package/src/components/command-bar/theme-picker.tsx +48 -13
- package/src/components/command-bar/workflow/ops.ts +5 -3
- package/src/components/data-table/export.ts +6 -51
- package/src/components/data-table/view.tsx +31 -10
- package/src/components/layout/floating-pane.tsx +6 -2
- package/src/components/layout/market-summary.ts +23 -9
- package/src/components/layout/pane/content.tsx +46 -29
- package/src/components/layout/pane/cycle-order.ts +2 -11
- package/src/components/layout/pane/header.tsx +2 -1
- package/src/components/layout/pane/title.ts +1 -1
- package/src/components/layout/presentation.ts +22 -0
- package/src/components/layout/shell/active-drag.ts +67 -35
- package/src/components/layout/shell/cursor-occlusion.ts +6 -5
- package/src/components/layout/shell/drag/index.ts +16 -7
- package/src/components/layout/shell/drag/live.ts +193 -0
- package/src/components/layout/shell/drag/overlays.tsx +179 -35
- package/src/components/layout/shell/drag/runtime.ts +62 -50
- package/src/components/layout/shell/drag/slide.ts +10 -0
- package/src/components/layout/shell/index.tsx +84 -57
- package/src/components/layout/shell/layout-state.ts +5 -11
- package/src/components/layout/shell/menu.ts +28 -1
- package/src/components/layout/shell/native/pointer-runtime.ts +13 -13
- package/src/components/layout/shell/native/surfaces.ts +30 -14
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +461 -180
- package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
- package/src/components/layout/shell/pane/new-layout.ts +116 -0
- package/src/components/layout/shell/shortcuts.ts +5 -2
- package/src/components/layout/shell/terminal-pointer-runtime.ts +18 -18
- package/src/components/layout/shell/visible-layout.ts +44 -1
- package/src/components/layout/shell/window-mode/overlays.tsx +12 -4
- package/src/components/layout/status-bar.tsx +4 -1
- package/src/components/markdown-text.tsx +9 -5
- package/src/components/metric-treemap/heat-surface.tsx +27 -8
- package/src/components/metric-treemap/heat-tiles.ts +48 -17
- package/src/components/onboarding/onboarding-frame.tsx +33 -10
- package/src/components/onboarding/onboarding-wizard.tsx +49 -40
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
- package/src/components/onboarding/pro-step-layout.ts +86 -0
- package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
- package/src/components/ui/button.tsx +10 -0
- package/src/components/ui/data-table/index.tsx +38 -0
- package/src/components/ui/data-table/types.ts +8 -1
- package/src/components/ui/data-table/visible-range.ts +2 -0
- package/src/components/ui/icon.tsx +11 -2
- package/src/components/ui/index.ts +1 -1
- package/src/core/connection-health.ts +1 -1
- package/src/core/state/app/layout-reducer.ts +89 -3
- package/src/core/state/app/state.ts +8 -5
- package/src/core/state/app/types.ts +13 -2
- package/src/data/config/layout.ts +55 -21
- package/src/data/config/store/fold-panes.ts +149 -0
- package/src/data/config/store/migrations.ts +7 -0
- package/src/data/config/store/node.ts +27 -6
- package/src/data/config/store/normalize.ts +37 -3
- package/src/data/json-persistence.ts +17 -3
- package/src/data/json-storage.ts +2 -0
- package/src/data/memory-resource-store.ts +46 -0
- package/src/i18n/es.ts +14 -1
- package/src/i18n/ja.ts +14 -1
- package/src/i18n/ko.ts +14 -1
- package/src/i18n/zh-cn.ts +14 -1
- package/src/i18n/zh-tw.ts +14 -1
- package/src/layout/desks.ts +1 -2
- package/src/layout/pane-layout-move.ts +264 -0
- package/src/layout/pane-manager/hidden-panes.ts +160 -0
- package/src/layout/pane-manager/index.ts +1 -0
- package/src/market-data/coordinator/entries.ts +7 -1
- package/src/market-data/coordinator/quotes.ts +2 -2
- package/src/market-data/market/freshness.ts +156 -11
- package/src/market-data/market/status.ts +40 -0
- package/src/market-data/options-alternatives.ts +69 -0
- package/src/market-data/quotes/contributions.ts +19 -2
- package/src/market-data/quotes/freshness.ts +5 -0
- package/src/market-data/quotes/metadata.ts +16 -0
- package/src/market-data/quotes/screener-live-quotes.ts +56 -5
- package/src/plugins/builtin/account-management/keyboard.ts +3 -0
- package/src/plugins/builtin/account-management/model.ts +1 -0
- package/src/plugins/builtin/account-management/navigation.ts +1 -1
- package/src/plugins/builtin/account-management/pane.tsx +31 -4
- package/src/plugins/builtin/alerts/quotes.ts +3 -0
- package/src/plugins/builtin/analytics/pane-model.ts +10 -6
- package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
- package/src/plugins/builtin/analytics/risk-model.ts +37 -8
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
- package/src/plugins/builtin/attention/pane.tsx +1 -1
- package/src/plugins/builtin/backtest/pane.tsx +1 -1
- package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/broker-manager/model.ts +8 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
- package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
- package/src/plugins/builtin/broker-manager/table.ts +5 -0
- package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
- package/src/plugins/builtin/catalysts/detail.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
- package/src/plugins/builtin/chat/channels.ts +34 -36
- package/src/plugins/builtin/chat/command-bar.ts +138 -0
- package/src/plugins/builtin/chat/composer-commands.ts +21 -1
- package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
- package/src/plugins/builtin/chat/content/footer.tsx +5 -3
- package/src/plugins/builtin/chat/content/index.tsx +21 -1
- package/src/plugins/builtin/chat/content/mentions.ts +2 -1
- package/src/plugins/builtin/chat/content/user-map.ts +3 -1
- package/src/plugins/builtin/chat/controller/index.ts +48 -0
- package/src/plugins/builtin/chat/conversations.ts +256 -0
- package/src/plugins/builtin/chat/discord-command.ts +91 -0
- package/src/plugins/builtin/chat/edit-window.ts +12 -6
- package/src/plugins/builtin/chat/ghost-user.ts +14 -0
- package/src/plugins/builtin/chat/index.tsx +40 -2
- package/src/plugins/builtin/chat/layout.ts +2 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
- package/src/plugins/builtin/chat/message/header.tsx +24 -15
- package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
- package/src/plugins/builtin/chat/message/render-state.ts +3 -0
- package/src/plugins/builtin/chat/pane-requests.ts +60 -0
- package/src/plugins/builtin/chat/pane-state.ts +32 -2
- package/src/plugins/builtin/chat/pane.tsx +8 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
- package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
- package/src/plugins/builtin/cloud/askg/model.ts +129 -70
- package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
- package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
- package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
- package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
- package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
- package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
- package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
- package/src/plugins/builtin/cloud/plugin.tsx +36 -1
- package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
- package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
- package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
- package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
- package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
- package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
- package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
- package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
- package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
- package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
- package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
- package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
- package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
- package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
- package/src/plugins/builtin/comment-letters/client.ts +1 -3
- package/src/plugins/builtin/company-attention/pane.tsx +1 -1
- package/src/plugins/builtin/company-kpis/client.ts +23 -8
- package/src/plugins/builtin/company-kpis/headless.ts +3 -1
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
- package/src/plugins/builtin/composite-plugins.ts +107 -64
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
- package/src/plugins/builtin/credit-boards/index.tsx +21 -21
- package/src/plugins/builtin/credit-boards/model.ts +0 -1
- package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
- package/src/plugins/builtin/debt-maturities/client.ts +37 -9
- package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/data.ts +28 -7
- package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
- package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
- package/src/plugins/builtin/econ/calendar-model.ts +42 -17
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/index.tsx +68 -22
- package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +17 -0
- package/src/plugins/builtin/executives/data.ts +17 -6
- package/src/plugins/builtin/executives/pane.tsx +16 -7
- package/src/plugins/builtin/filing-events/pane.tsx +22 -5
- package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
- package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
- package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
- package/src/plugins/builtin/gpu/headless.ts +9 -3
- package/src/plugins/builtin/gpu/model.ts +14 -5
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
- package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
- package/src/plugins/builtin/holders/client.ts +3 -6
- package/src/plugins/builtin/holders/format.ts +31 -1
- package/src/plugins/builtin/holders/headless.ts +32 -6
- package/src/plugins/builtin/holders/pane.tsx +25 -13
- package/src/plugins/builtin/holders/report-header.ts +46 -5
- package/src/plugins/builtin/insider/index.tsx +4 -4
- package/src/plugins/builtin/iv-history/charts.tsx +21 -7
- package/src/plugins/builtin/iv-history/client.ts +12 -2
- package/src/plugins/builtin/iv-history/format.ts +33 -2
- package/src/plugins/builtin/iv-history/headless.ts +22 -10
- package/src/plugins/builtin/iv-history/index.tsx +3 -3
- package/src/plugins/builtin/iv-history/model.ts +61 -13
- package/src/plugins/builtin/iv-history/pane.tsx +11 -3
- package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
- package/src/plugins/builtin/jobs/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
- package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +121 -45
- package/src/plugins/builtin/kelly-sizer/portfolio.ts +25 -0
- package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
- package/src/plugins/builtin/layout-manager/index.tsx +9 -1
- package/src/plugins/builtin/macro-day/client.ts +24 -0
- package/src/plugins/builtin/macro-day/headless.ts +11 -8
- package/src/plugins/builtin/macro-day/index.tsx +3 -0
- package/src/plugins/builtin/macro-day/model.ts +11 -6
- package/src/plugins/builtin/macro-day/pane.tsx +12 -8
- package/src/plugins/builtin/macro-day/release-table.ts +40 -0
- package/src/plugins/builtin/macro-day/releases.ts +62 -44
- package/src/plugins/builtin/market-heatmap/data.ts +25 -5
- package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
- package/src/plugins/builtin/market-heatmap/model.ts +94 -3
- package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
- package/src/plugins/builtin/market-movers/headless.ts +31 -4
- package/src/plugins/builtin/market-movers/index.tsx +8 -1
- package/src/plugins/builtin/market-movers/model.ts +71 -17
- package/src/plugins/builtin/market-movers/table.ts +50 -23
- package/src/plugins/builtin/members/pane.tsx +10 -0
- package/src/plugins/builtin/news/headless.ts +9 -2
- package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
- package/src/plugins/builtin/openfda/client.ts +1 -1
- package/src/plugins/builtin/options/carry.ts +95 -0
- package/src/plugins/builtin/options/index.tsx +58 -5
- package/src/plugins/builtin/options/strike-window.ts +183 -0
- package/src/plugins/builtin/options/table.ts +55 -3
- package/src/plugins/builtin/options/types.ts +8 -1
- package/src/plugins/builtin/options/view.tsx +161 -12
- package/src/plugins/builtin/options-positioning/headless.ts +4 -2
- package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
- package/src/plugins/builtin/options-scenario/client.ts +73 -17
- package/src/plugins/builtin/options-scenario/headless.ts +71 -24
- package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
- package/src/plugins/builtin/options-scenario/index.tsx +9 -2
- package/src/plugins/builtin/options-scenario/model.ts +33 -0
- package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
- package/src/plugins/builtin/pe-band/model.ts +2 -2
- package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
- package/src/plugins/builtin/perps/equity-row.tsx +8 -5
- package/src/plugins/builtin/perps/model.ts +16 -5
- package/src/plugins/builtin/perps/pane.tsx +1 -1
- package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
- package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
- package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
- package/src/plugins/builtin/plugin-module.ts +87 -4
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +16 -1
- package/src/plugins/builtin/portfolio-list/allocation.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cached-account.ts +18 -9
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
- package/src/plugins/builtin/portfolio-list/cli/render.ts +89 -18
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
- package/src/plugins/builtin/portfolio-list/headless.ts +40 -7
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +7 -4
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +22 -4
- package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +11 -1
- package/src/plugins/builtin/realized-vol/headless.ts +3 -0
- package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +7 -3
- package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
- package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
- package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +2 -1
- package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
- package/src/plugins/builtin/risk-factors/data.ts +23 -9
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
- package/src/plugins/builtin/seasonality/pane.tsx +1 -1
- package/src/plugins/builtin/sec/index.tsx +5 -5
- package/src/plugins/builtin/sectors/client.ts +10 -2
- package/src/plugins/builtin/sectors/headless.ts +35 -6
- package/src/plugins/builtin/sectors/index.tsx +18 -1
- package/src/plugins/builtin/sectors/sector-model.ts +150 -41
- package/src/plugins/builtin/shared/command-tab.ts +51 -0
- package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
- package/src/plugins/builtin/shared/members-menu.tsx +260 -0
- package/src/plugins/builtin/shared/report-freshness.ts +50 -2
- package/src/plugins/builtin/shared/ticker-request.ts +25 -1
- package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
- package/src/plugins/builtin/shared/volatility/options.ts +1 -46
- package/src/plugins/builtin/short-interest/index.tsx +19 -4
- package/src/plugins/builtin/short-interest/surface.tsx +4 -4
- package/src/plugins/builtin/short-volume/index.tsx +1 -10
- package/src/plugins/builtin/short-volume/pane.tsx +1 -1
- package/src/plugins/builtin/social-mentions/pane.tsx +1 -1
- package/src/plugins/builtin/supply-chain/model.ts +1 -1
- package/src/plugins/builtin/themes/pane.tsx +3 -0
- package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +4 -0
- package/src/plugins/builtin/ticker-detail/financials/model.ts +25 -2
- package/src/plugins/builtin/ticker-detail/headless.ts +63 -25
- package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -4
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
- package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
- package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
- package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
- package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
- package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
- package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
- package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
- package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
- package/src/plugins/builtin/vol-surface/client.ts +6 -2
- package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
- package/src/plugins/builtin/vol-surface/headless.ts +124 -20
- package/src/plugins/builtin/vol-surface/model.ts +23 -11
- package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
- package/src/plugins/builtin/vol-surface/tables.ts +124 -0
- package/src/plugins/builtin/world-indices/client.ts +22 -4
- package/src/plugins/builtin/world-indices/headless.ts +54 -27
- package/src/plugins/builtin/world-indices/model.ts +4 -2
- package/src/plugins/builtin/world-indices/table.tsx +13 -2
- package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +2 -0
- package/src/plugins/builtin/world-venue-map/basemap.ts +18 -0
- package/src/plugins/builtin/world-venue-map/country-labels.ts +78 -0
- package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
- package/src/plugins/builtin/world-venue-map/geo-svg.tsx +2 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -6
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +27 -14
- package/src/plugins/builtin/world-venue-map/layers.ts +68 -10
- package/src/plugins/builtin/world-venue-map/map.tsx +198 -62
- package/src/plugins/builtin/world-venue-map/model.ts +16 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +3 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/labels.chunk.ts +244 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +16 -10
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/builtin-editorial.ts +126 -0
- package/src/plugins/catalog-backend.ts +16 -9
- package/src/plugins/catalog-browser.ts +127 -63
- package/src/plugins/catalog-manifest.ts +73 -40
- package/src/plugins/catalog-ui.ts +26 -6
- package/src/plugins/extracted-plugins.ts +7 -1
- package/src/plugins/ownership.ts +150 -7
- package/src/plugins/plugin-functions.ts +54 -0
- package/src/plugins/plugin-off.ts +36 -0
- package/src/plugins/registry/host-actions.ts +16 -0
- package/src/plugins/registry/index.ts +84 -24
- package/src/plugins/registry/plugin-state.ts +25 -1
- package/src/plugins/runtime/context.tsx +29 -0
- package/src/react/input.ts +48 -1
- package/src/remote/app-host.tsx +5 -0
- package/src/remote/controller.ts +3 -53
- package/src/remote/matching.ts +69 -0
- package/src/remote/schema.ts +1 -1
- package/src/remote/semantic-tree.tsx +14 -0
- package/src/remote/types.ts +2 -0
- package/src/renderers/browser/app-services.ts +3 -1
- package/src/renderers/dom/data-table/dom.ts +31 -0
- package/src/renderers/dom/data-table/index.tsx +5 -0
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/query-bar.tsx +59 -32
- package/src/renderers/dom/host/box.tsx +113 -17
- package/src/renderers/dom/host/drag-shield.ts +35 -0
- package/src/renderers/dom/host/live-frame.ts +62 -0
- package/src/renderers/dom/host/mouse.ts +8 -1
- package/src/renderers/dom/host/scroll-box.tsx +6 -5
- package/src/renderers/dom/host/tabs.tsx +3 -2
- package/src/renderers/dom/input-host.tsx +6 -1
- package/src/renderers/dom/styles.css +26 -5
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +15 -1
- package/src/renderers/electrobun/shared/protocol.ts +12 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +4 -1
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
- package/src/renderers/electrobun/view/host/native.ts +0 -10
- package/src/renderers/electrobun/view/host/window-move.ts +28 -0
- package/src/renderers/electrobun/view/main.tsx +4 -1
- package/src/renderers/electrobun/view/remote/persistence.ts +15 -2
- package/src/renderers/electrobun/view/ui-host.tsx +2 -5
- package/src/renderers/opentui/input-host.tsx +6 -1
- package/src/renderers/opentui/start.tsx +9 -0
- package/src/renderers/opentui/ui-host.tsx +7 -1
- package/src/shares/portable-layout.ts +16 -6
- package/src/sources/gloomberb-cloud/index.ts +19 -10
- package/src/sources/gloomberb-cloud/normalizers.ts +20 -21
- package/src/sources/listing-symbols.ts +11 -5
- package/src/sources/provider-errors.ts +88 -3
- package/src/sources/provider-router/batches.ts +3 -3
- package/src/sources/provider-router/cached-routes.ts +8 -4
- package/src/sources/provider-router/financial-routes.ts +13 -7
- package/src/sources/provider-router/financials.ts +106 -16
- package/src/sources/provider-router/history.ts +27 -11
- package/src/sources/provider-router/index.ts +1 -1
- package/src/sources/provider-router/primary.ts +24 -6
- package/src/sources/sec-edgar.ts +68 -8
- package/src/state/pane-scroll-registry.tsx +67 -35
- package/src/sync/core-contributors.ts +4 -3
- package/src/test-support/cli-context.ts +2 -1
- package/src/test-support/data-provider.ts +34 -0
- package/src/test-support/margin-account.ts +108 -0
- package/src/test-support/shell-pane-keys.tsx +1 -0
- package/src/theme/series-colors.ts +46 -0
- package/src/theme/themes.ts +149 -0
- package/src/tickers/search/index.ts +22 -2
- package/src/tickers/search/ranking.ts +45 -1
- package/src/tickers/search/types.ts +3 -1
- package/src/time-series/resolution.ts +15 -0
- package/src/time-series/resolve.ts +1 -14
- package/src/types/config.ts +61 -1
- package/src/types/financials.ts +32 -3
- package/src/types/headless.ts +11 -0
- package/src/types/instrument.ts +8 -0
- package/src/types/plugin.ts +49 -3
- package/src/ui/host.tsx +16 -0
- package/src/ui/index.tsx +10 -3
- package/src/ui/scroll-box-axis.ts +26 -0
- package/src/utils/cli-output.ts +14 -1
- package/src/utils/currency-units.ts +8 -3
- package/src/utils/depositary-receipt.ts +54 -10
- package/src/utils/display-number.ts +63 -0
- package/src/utils/exchanges.ts +13 -0
- package/src/utils/format.ts +2 -1
- package/src/utils/fundamentals.ts +17 -1
- package/src/utils/fx-market-hours.ts +34 -0
- package/src/utils/option-expiry.ts +126 -0
- package/src/utils/price-history-integrity.ts +19 -6
- package/src/utils/price-history.ts +39 -6
- package/src/utils/rpc-timeout-error.ts +6 -2
- package/src/utils/sec-eps-basis.ts +159 -30
- package/src/utils/sec.ts +38 -34
- package/src/utils/throttled-fetch.ts +51 -22
|
@@ -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
|
}
|
|
@@ -139,6 +139,13 @@ export function resolvePortfolioNetLiquidation(
|
|
|
139
139
|
return convertAccountValue(account.netLiquidation) + brokerSnapshotDelta(totals, account, basis, convertAccountValue).net;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
/** Gross exposure per unit of equity; above 1 the account borrows or is short. */
|
|
143
|
+
export function resolvePortfolioLeverage(grossValue: number, netLiquidation: number | null | undefined): number | null {
|
|
144
|
+
return isFiniteNumber(netLiquidation) && netLiquidation > 0 && Number.isFinite(grossValue) && grossValue >= 0
|
|
145
|
+
? grossValue / netLiquidation
|
|
146
|
+
: null;
|
|
147
|
+
}
|
|
148
|
+
|
|
142
149
|
export function resolvePortfolioAccountMetrics(
|
|
143
150
|
totals: PortfolioSummaryTotals,
|
|
144
151
|
account?: BrokerAccount | null,
|
|
@@ -167,9 +174,17 @@ export function resolvePortfolioAccountMetrics(
|
|
|
167
174
|
: isFiniteNumber(account?.netLiquidation)
|
|
168
175
|
? convertAccountValue(account.netLiquidation) - brokerDailyPnl
|
|
169
176
|
: null;
|
|
177
|
+
// Without the broker's day, the quotes' day is still a move of the equity,
|
|
178
|
+
// which margin puts below the positions the totals divide by.
|
|
179
|
+
const quotedPreviousEquity = brokerDailyPnl == null && account && isFiniteNumber(account.netLiquidation)
|
|
180
|
+
&& Number.isFinite(dailyPnl)
|
|
181
|
+
? convertAccountValue(account.netLiquidation) + delta.net - dailyPnl
|
|
182
|
+
: null;
|
|
170
183
|
const dailyPnlPct = previousNetLiquidation != null
|
|
171
184
|
? percentChange(dailyPnl, previousNetLiquidation)
|
|
172
|
-
:
|
|
185
|
+
: quotedPreviousEquity != null && quotedPreviousEquity > 0
|
|
186
|
+
? percentChange(dailyPnl, quotedPreviousEquity)
|
|
187
|
+
: totals.dailyPnlPct;
|
|
173
188
|
|
|
174
189
|
const liveUnrealizedPnl = liveTotal(totals, totals.unrealizedPnl);
|
|
175
190
|
const brokerUnrealizedPnl = liveUnrealizedPnl == null && isFiniteNumber(account?.unrealizedPnl)
|
|
@@ -17,6 +17,9 @@ import { formatNumber } from "../../../utils/format";
|
|
|
17
17
|
/** The cash line's symbol in targets and reports. */
|
|
18
18
|
export const CASH_SYMBOL = "CASH";
|
|
19
19
|
|
|
20
|
+
/** A broker portfolio without its account: margin can put its positions far above what it is worth. */
|
|
21
|
+
export const BROKER_ACCOUNT_MISSING_NOTE = "The broker account has not synced on this device, so its cash and margin are unknown: the total is the positions alone, not the account's value.";
|
|
22
|
+
|
|
20
23
|
/** One ticker of a portfolio, valued in the portfolio's currency. */
|
|
21
24
|
export interface AllocationHolding {
|
|
22
25
|
symbol: string;
|
|
@@ -6,17 +6,16 @@ import type { BrokerAccount } from "../../../types/trading";
|
|
|
6
6
|
import { resolvePortfolioAccountState } from "./summary";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* the same
|
|
9
|
+
* Every profile's broker accounts as last synced on this device, by profile,
|
|
10
|
+
* for reports and screenshots that run without a broker connection. The pane
|
|
11
|
+
* reads the same accounts from app state.
|
|
12
12
|
*/
|
|
13
|
-
export function
|
|
14
|
-
config: AppConfig,
|
|
15
|
-
portfolio: Portfolio,
|
|
13
|
+
export function peekCachedBrokerAccounts(
|
|
14
|
+
config: Pick<AppConfig, "brokerInstances">,
|
|
16
15
|
resources: Pick<AppResourceStorePort, "list"> | null | undefined,
|
|
17
|
-
): BrokerAccount
|
|
18
|
-
if (!resources || !portfolio.brokerInstanceId) return null;
|
|
16
|
+
): Record<string, BrokerAccount[]> {
|
|
19
17
|
const brokerAccounts: Record<string, BrokerAccount[]> = {};
|
|
18
|
+
if (!resources) return brokerAccounts;
|
|
20
19
|
for (const instance of config.brokerInstances) {
|
|
21
20
|
try {
|
|
22
21
|
const accounts = peekPersistedBrokerAccounts(resources, instance);
|
|
@@ -25,9 +24,19 @@ export function findCachedPortfolioAccount(
|
|
|
25
24
|
// An unreadable snapshot leaves the portfolio without account figures.
|
|
26
25
|
}
|
|
27
26
|
}
|
|
27
|
+
return brokerAccounts;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The broker account a portfolio was last synced with, as saved on this device. */
|
|
31
|
+
export function findCachedPortfolioAccount(
|
|
32
|
+
config: AppConfig,
|
|
33
|
+
portfolio: Portfolio,
|
|
34
|
+
resources: Pick<AppResourceStorePort, "list"> | null | undefined,
|
|
35
|
+
): BrokerAccount | null {
|
|
36
|
+
if (!resources || !portfolio.brokerInstanceId) return null;
|
|
28
37
|
return resolvePortfolioAccountState(
|
|
29
38
|
portfolio,
|
|
30
|
-
{ config, brokerAccounts },
|
|
39
|
+
{ config, brokerAccounts: peekCachedBrokerAccounts(config, resources) },
|
|
31
40
|
{ status: null, accounts: [] },
|
|
32
41
|
)?.account ?? null;
|
|
33
42
|
}
|
|
@@ -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";
|
|
@@ -24,6 +33,7 @@ import { cliFreshnessFooter } from "../../../../cli/result";
|
|
|
24
33
|
import { quotesFreshness } from "../../../../cli/freshness";
|
|
25
34
|
import type { BrokerAccount } from "../../../../types/trading";
|
|
26
35
|
import {
|
|
36
|
+
BROKER_ACCOUNT_MISSING_NOTE,
|
|
27
37
|
CASH_SYMBOL,
|
|
28
38
|
buildPortfolioAllocation,
|
|
29
39
|
describeTargetSum,
|
|
@@ -46,6 +56,38 @@ function priceFormatOptions(quote: Parameters<typeof quoteFormatOptions>[0] & {
|
|
|
46
56
|
: options;
|
|
47
57
|
}
|
|
48
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
|
+
|
|
49
91
|
export function renderCollectionOverview(config: AppConfig, tickers: TickerRecord[]): string {
|
|
50
92
|
const blocks: string[] = [];
|
|
51
93
|
|
|
@@ -142,7 +184,12 @@ export async function loadCollectionQuotes(
|
|
|
142
184
|
interface PortfolioPositionValue {
|
|
143
185
|
ticker: TickerRecord;
|
|
144
186
|
quote: CollectionQuote | undefined;
|
|
187
|
+
/** The live print, which values the position. */
|
|
145
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;
|
|
146
193
|
/** Null for a ticker kept in the portfolio without an open position. */
|
|
147
194
|
position: TickerPosition | null;
|
|
148
195
|
metrics: ReturnType<typeof getPortfolioPositionMetrics> | null;
|
|
@@ -198,14 +245,15 @@ export async function valuePortfolioPositions({
|
|
|
198
245
|
const positions = ticker.metadata.positions.filter((position) => position.portfolio === id && position.shares !== 0);
|
|
199
246
|
const displayedQuote = getActiveQuoteDisplay(quote);
|
|
200
247
|
const activeQuote = displayedQuote && Number.isFinite(displayedQuote.price) ? displayedQuote : null;
|
|
248
|
+
const { headline, extended } = collectionQuoteHeadline(quote);
|
|
201
249
|
|
|
202
250
|
if (positions.length === 0) {
|
|
203
251
|
const quoteCurrency = quote?.currency || ticker.metadata.currency || baseCurrency;
|
|
204
252
|
const unitPrice = activeQuote ? known(await toBase(activeQuote.price, quoteCurrency)) : null;
|
|
205
253
|
holdings.push({ symbol: ticker.metadata.ticker, held: false, marketValue: 0, units: 0, unitPrice });
|
|
206
|
-
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,
|
|
207
255
|
row: { symbol: ticker.metadata.ticker, exchange: ticker.metadata.exchange, shares: null, avgCost: null,
|
|
208
|
-
positionCurrency: null, quotePrice: known(
|
|
256
|
+
positionCurrency: null, quotePrice: known(headline?.price), ...extendedQuoteFields(extended), quoteCurrency: quote?.currency ?? null,
|
|
209
257
|
costBasis: null, marketValue: null, unrealizedPnl: null, baseCurrency: currency } });
|
|
210
258
|
continue;
|
|
211
259
|
}
|
|
@@ -237,11 +285,12 @@ export async function valuePortfolioPositions({
|
|
|
237
285
|
holding.marketValue = holding.marketValue == null || marketValue == null ? null : holding.marketValue + marketValue;
|
|
238
286
|
holding.units += metrics.totalShares;
|
|
239
287
|
holding.unitPrice ??= valuationQuote ? known(await toBase(valuationQuote.price, quoteCurrency)) : null;
|
|
240
|
-
values.push({ ticker, quote, activeQuote, position, metrics, pnl,
|
|
288
|
+
values.push({ ticker, quote, activeQuote, headline, extended, position, metrics, pnl,
|
|
241
289
|
row: { symbol: ticker.metadata.ticker, exchange: ticker.metadata.exchange,
|
|
242
290
|
shares: metrics.totalShares, avgCost: known(position.avgCost), positionCurrency,
|
|
243
291
|
priceBasis: position.priceBasis ?? null, quantityUnit: metrics.priceBasis === "percent-of-par" ? "face" : null, quotePriceBasis: quote?.priceBasis ?? null,
|
|
244
|
-
|
|
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,
|
|
245
294
|
costBasis: known(direction * costBasisBase), marketValue,
|
|
246
295
|
unrealizedPnl: known(pnl), baseCurrency: currency, dateAcquired: position.dateAcquired ?? null,
|
|
247
296
|
pnlBasis: selectedPnl.basis, brokerUnrealizedPnl: known(position.unrealizedPnl), brokerPnlCurrency: positionCurrency, brokerPnlAsOf: null } });
|
|
@@ -295,6 +344,18 @@ function signed(text: string, value: number | null | undefined): string {
|
|
|
295
344
|
|
|
296
345
|
const NOT_AVAILABLE = "n/a";
|
|
297
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
|
+
|
|
298
359
|
async function showCollectionWithMarketData(
|
|
299
360
|
name: string,
|
|
300
361
|
ctx: CliCommandContext,
|
|
@@ -435,18 +496,20 @@ async function showCollectionWithMarketData(
|
|
|
435
496
|
return [weight, target, drift, trade, tradeValue];
|
|
436
497
|
};
|
|
437
498
|
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
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)
|
|
441
503
|
: "—";
|
|
442
|
-
const changeText =
|
|
504
|
+
const changeText = headline ? colorBySign(formatPercentRaw(headline.changePercent), headline.change) : "—";
|
|
505
|
+
const quoteCells = [priceText, changeText, ...extendedCells(extendedSessions, extended, quote, ticker.metadata.assetCategory)];
|
|
443
506
|
const exported = positionsExport[index]!;
|
|
444
507
|
const figures = exported.weight != null || exported.targetWeight != null || exported.tradeValue != null
|
|
445
508
|
? { weight: exported.weight, targetWeight: exported.targetWeight, drift: exported.drift, tradeValue: exported.tradeValue, tradeUnits: exported.tradeShares }
|
|
446
509
|
: undefined;
|
|
447
510
|
const symbolUnits = allocationBySymbol.get(ticker.metadata.ticker)?.units ?? 0;
|
|
448
511
|
if (!position || !metrics) {
|
|
449
|
-
return [ticker.metadata.ticker,
|
|
512
|
+
return [ticker.metadata.ticker, ...quoteCells, "—", "—", "—",
|
|
450
513
|
...allocationCells(figures, { unpriced: false, units: symbolUnits, assetCategory: ticker.metadata.assetCategory }), "—"];
|
|
451
514
|
}
|
|
452
515
|
const positionCurrency = metrics.positionCurrency;
|
|
@@ -454,8 +517,7 @@ async function showCollectionWithMarketData(
|
|
|
454
517
|
const unpriced = marketValue == null;
|
|
455
518
|
return [
|
|
456
519
|
ticker.metadata.ticker,
|
|
457
|
-
|
|
458
|
-
changeText,
|
|
520
|
+
...quoteCells,
|
|
459
521
|
formatMarketQuantity(metrics.totalShares, { assetCategory: ticker.metadata.assetCategory, multiplier: position.multiplier, priceBasis: metrics.priceBasis, quantityCurrency: positionCurrency }),
|
|
460
522
|
formatMarketCostWithCurrency(position.avgCost, positionCurrency, { assetCategory: ticker.metadata.assetCategory, multiplier: position.multiplier, priceBasis: metrics.priceBasis }),
|
|
461
523
|
unpriced ? NOT_AVAILABLE : wholeMoney(marketValue),
|
|
@@ -465,12 +527,12 @@ async function showCollectionWithMarketData(
|
|
|
465
527
|
});
|
|
466
528
|
if (cashExport) {
|
|
467
529
|
const label = cashExport.currency && cashExport.currency !== currency ? `${CASH_SYMBOL} ${cashExport.currency}` : CASH_SYMBOL;
|
|
468
|
-
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),
|
|
469
531
|
...allocationCells(cashExport, { unpriced: cashExport.value == null }), ""]);
|
|
470
532
|
}
|
|
471
533
|
const total = allocation.total;
|
|
472
534
|
rows.push([
|
|
473
|
-
cliStyles.bold("TOTAL"), "", "", "", "",
|
|
535
|
+
cliStyles.bold("TOTAL"), "", "", ...extendedSessions.map(() => ""), "", "",
|
|
474
536
|
cliStyles.bold(wholeMoney(total)),
|
|
475
537
|
total != null && total > 0 ? formatAllocationWeight(100) : "—",
|
|
476
538
|
...(showTargets ? [formatAllocationWeight(allocation.targetSum), "", "", ""] : []),
|
|
@@ -484,6 +546,8 @@ async function showCollectionWithMarketData(
|
|
|
484
546
|
{ header: "Ticker" },
|
|
485
547
|
{ header: "Last", align: "right", optional: true, dropPriority: 1 },
|
|
486
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 })),
|
|
487
551
|
{ header: quantityHeader, align: "right" },
|
|
488
552
|
{ header: "Avg Cost", align: "right", optional: true, dropPriority: 3 },
|
|
489
553
|
{ header: "Value", align: "right" },
|
|
@@ -507,6 +571,7 @@ async function showCollectionWithMarketData(
|
|
|
507
571
|
notes.push(cliStyles.warning(`Total and weights exclude ${count} unpriced holding${count === 1 ? "" : "s"}: ${allocation.unpriced.join(", ")}.`));
|
|
508
572
|
}
|
|
509
573
|
if (cashExport?.source === "broker") notes.push(cliStyles.muted("Cash is the broker account's balance as of its last sync."));
|
|
574
|
+
if (matchedPortfolio.brokerInstanceId && !account) notes.push(cliStyles.warning(BROKER_ACCOUNT_MISSING_NOTE));
|
|
510
575
|
if (cashExport && cashExport.value == null) {
|
|
511
576
|
notes.push(cliStyles.warning(cashExport.currency
|
|
512
577
|
? `No ${cashExport.currency}/${currency} rate for the cash, so the total and weights are unavailable.`
|
|
@@ -520,12 +585,15 @@ async function showCollectionWithMarketData(
|
|
|
520
585
|
for (const note of notes) console.log(note);
|
|
521
586
|
} else {
|
|
522
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));
|
|
523
590
|
for (const ticker of filtered) {
|
|
524
591
|
const quote = quotes.get(ticker.metadata.ticker);
|
|
525
|
-
const
|
|
526
|
-
|
|
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)
|
|
527
595
|
: "—";
|
|
528
|
-
const changeText =
|
|
596
|
+
const changeText = headline ? colorBySign(formatPercentRaw(headline.changePercent), headline.change) : "—";
|
|
529
597
|
const marketCapText = quote?.marketCap != null
|
|
530
598
|
? `${formatCompact(await toBase(quote.marketCap, quote.currency || ticker.metadata.currency || currency))} ${currency}`
|
|
531
599
|
: "—";
|
|
@@ -535,6 +603,7 @@ async function showCollectionWithMarketData(
|
|
|
535
603
|
exchangeShortName(quote?.exchangeName, quote?.fullExchangeName) || ticker.metadata.exchange || "—",
|
|
536
604
|
priceText,
|
|
537
605
|
changeText,
|
|
606
|
+
...extendedCells(extendedSessions, extended, quote, ticker.metadata.assetCategory),
|
|
538
607
|
marketCapText,
|
|
539
608
|
]);
|
|
540
609
|
}
|
|
@@ -545,7 +614,9 @@ async function showCollectionWithMarketData(
|
|
|
545
614
|
{ header: "Exchange" },
|
|
546
615
|
{ header: "Last", align: "right" },
|
|
547
616
|
{ header: "Chg", align: "right" },
|
|
548
|
-
{ 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 },
|
|
549
620
|
],
|
|
550
621
|
rows,
|
|
551
622
|
));
|