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
|
@@ -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
|
-
}
|
|
@@ -9,13 +9,18 @@ import type {
|
|
|
9
9
|
import type { TickerRecord } from "../../../types/ticker";
|
|
10
10
|
import { formatNumber, formatPercentRaw } from "../../../utils/format";
|
|
11
11
|
import {
|
|
12
|
+
collectionQuoteHeadline,
|
|
13
|
+
extendedQuoteFields,
|
|
14
|
+
extendedSessionsOf,
|
|
12
15
|
findCollection,
|
|
13
16
|
loadCollectionQuotes,
|
|
14
17
|
valuePortfolioAllocation,
|
|
15
18
|
valuePortfolioPositions,
|
|
16
19
|
type CollectionMatch,
|
|
17
20
|
} from "./cli/render";
|
|
21
|
+
import { EXTENDED_SESSION_LABELS, type ExtendedSession } from "../../../market-data/market/status";
|
|
18
22
|
import {
|
|
23
|
+
BROKER_ACCOUNT_MISSING_NOTE,
|
|
19
24
|
CASH_SYMBOL,
|
|
20
25
|
describeTargetSum,
|
|
21
26
|
formatAllocationDrift,
|
|
@@ -60,6 +65,7 @@ function textOf(value: unknown): string | undefined {
|
|
|
60
65
|
const POSITION_COLUMNS: HeadlessPaneColumn[] = [
|
|
61
66
|
{ key: "symbol", header: "Ticker" },
|
|
62
67
|
{ key: "name", header: "Name" },
|
|
68
|
+
{ key: "exchange", header: "Exchange" },
|
|
63
69
|
{ key: "shares", header: "Qty", align: "right", format: quantity },
|
|
64
70
|
{ key: "avgCost", header: "Avg Cost", align: "right", format: unitMoney("positionCurrency"), description: "Per unit, in the position's currency." },
|
|
65
71
|
{ key: "price", header: "Last", align: "right", format: unitMoney("priceCurrency"), description: "In the quote's currency." },
|
|
@@ -78,6 +84,26 @@ const TARGET_COLUMNS: HeadlessPaneColumn[] = [
|
|
|
78
84
|
{ key: "tradeValue", header: "Trade Value", align: "right", format: signedAmount, description: "The trade in the portfolio's currency." },
|
|
79
85
|
];
|
|
80
86
|
|
|
87
|
+
/** The pre-market or after-hours print and its move from the regular close, for the rows that have one. */
|
|
88
|
+
function extendedColumn(session: ExtendedSession): HeadlessPaneColumn {
|
|
89
|
+
return {
|
|
90
|
+
key: "extendedPrice",
|
|
91
|
+
header: EXTENDED_SESSION_LABELS[session],
|
|
92
|
+
align: "right",
|
|
93
|
+
format: (value, row) => row.extendedSession === session && typeof value === "number"
|
|
94
|
+
? `${unitMoney("priceCurrency")(value, row)} ${percent(row.extendedChangePercent)}`
|
|
95
|
+
: formatNumber(undefined),
|
|
96
|
+
description: "Measured from the regular close in Last.",
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** `columns` with an extended column after Chg for each session a row has a print for. */
|
|
101
|
+
function withExtendedColumns(columns: HeadlessPaneColumn[], sessions: readonly ExtendedSession[]): HeadlessPaneColumn[] {
|
|
102
|
+
if (sessions.length === 0) return columns;
|
|
103
|
+
const at = columns.findIndex((column) => column.key === "changePercent") + 1;
|
|
104
|
+
return [...columns.slice(0, at), ...sessions.map(extendedColumn), ...columns.slice(at)];
|
|
105
|
+
}
|
|
106
|
+
|
|
81
107
|
const WATCHLIST_COLUMNS: HeadlessPaneColumn[] = [
|
|
82
108
|
{ key: "symbol", header: "Ticker" },
|
|
83
109
|
{ key: "name", header: "Name" },
|
|
@@ -153,8 +179,9 @@ async function portfolioHoldings(
|
|
|
153
179
|
const gross = held.reduce((sum, entry) => sum + Math.abs(finite(entry.row.marketValue) ?? 0), 0);
|
|
154
180
|
const sum = (key: string) => held.reduce((total, entry) => total + (finite(entry.row[key]) ?? 0), 0);
|
|
155
181
|
const seen = new Set<string>();
|
|
182
|
+
const extendedSessions = extendedSessionsOf(held.map(({ extended }) => extended?.session));
|
|
156
183
|
const rows = held
|
|
157
|
-
.map(({ ticker,
|
|
184
|
+
.map(({ ticker, headline, extended, row }) => {
|
|
158
185
|
const marketValue = finite(row.marketValue);
|
|
159
186
|
// A ticker held in several lots carries its allocation on its first row.
|
|
160
187
|
const figures = seen.has(ticker.metadata.ticker) ? undefined : allocationBySymbol.get(ticker.metadata.ticker);
|
|
@@ -169,9 +196,11 @@ async function portfolioHoldings(
|
|
|
169
196
|
shares: finite(row.shares),
|
|
170
197
|
avgCost: finite(row.avgCost),
|
|
171
198
|
positionCurrency: row.positionCurrency ?? null,
|
|
199
|
+
// Last and Chg are the regular session; market value and P&L are at the live price.
|
|
172
200
|
price: finite(row.quotePrice),
|
|
173
201
|
priceCurrency: row.quoteCurrency ?? null,
|
|
174
|
-
changePercent: finite(
|
|
202
|
+
changePercent: finite(headline?.changePercent),
|
|
203
|
+
...extendedQuoteFields(extended),
|
|
175
204
|
marketValue,
|
|
176
205
|
unrealizedPnl: finite(row.unrealizedPnl),
|
|
177
206
|
weight: figures?.weight ?? null,
|
|
@@ -210,11 +239,12 @@ async function portfolioHoldings(
|
|
|
210
239
|
? [`${rows.length - limit} more position${rows.length - limit === 1 ? "" : "s"} not shown; totals include every position.`]
|
|
211
240
|
: []),
|
|
212
241
|
...(targetNote ? [targetNote] : []),
|
|
242
|
+
...(target.portfolio.brokerInstanceId && !resolved.account ? [BROKER_ACCOUNT_MISSING_NOTE] : []),
|
|
213
243
|
];
|
|
214
244
|
|
|
215
245
|
return {
|
|
216
246
|
freshness: { source: broker ? "Your broker account and Gloom Cloud" : "Local portfolio and Gloom Cloud" },
|
|
217
|
-
columns: showTargets ? [...POSITION_COLUMNS, ...TARGET_COLUMNS] : POSITION_COLUMNS,
|
|
247
|
+
columns: withExtendedColumns(showTargets ? [...POSITION_COLUMNS, ...TARGET_COLUMNS] : POSITION_COLUMNS, extendedSessions),
|
|
218
248
|
rows: shown,
|
|
219
249
|
complete: unavailable.length === 0,
|
|
220
250
|
...(unavailable.length
|
|
@@ -252,21 +282,24 @@ async function watchlistHoldings(
|
|
|
252
282
|
ctx.signal.throwIfAborted();
|
|
253
283
|
const rows = tickers.map((ticker) => {
|
|
254
284
|
const quote = quotes.get(ticker.metadata.ticker);
|
|
285
|
+
// As the watchlist pane shows it: the regular session, then any extended print from its close.
|
|
286
|
+
const { headline, extended } = collectionQuoteHeadline(quote);
|
|
255
287
|
return {
|
|
256
288
|
...quoteFreshnessFields(quote),
|
|
257
289
|
updatedAt: quote?.lastUpdated ?? null,
|
|
258
290
|
symbol: ticker.metadata.ticker,
|
|
259
291
|
name: ticker.metadata.name ?? null,
|
|
260
292
|
exchange: ticker.metadata.exchange || null,
|
|
261
|
-
price: finite(quote?.price),
|
|
293
|
+
price: finite(headline?.price ?? quote?.price),
|
|
262
294
|
priceCurrency: quote?.currency ?? null,
|
|
263
|
-
changePercent: finite(quote?.changePercent),
|
|
295
|
+
changePercent: finite(headline ? headline.changePercent : quote?.changePercent),
|
|
296
|
+
...extendedQuoteFields(extended),
|
|
264
297
|
};
|
|
265
298
|
});
|
|
266
299
|
const shown = rows.slice(0, limit);
|
|
267
300
|
return {
|
|
268
301
|
freshness: { source: "Local watchlist and Gloom Cloud" },
|
|
269
|
-
columns: WATCHLIST_COLUMNS,
|
|
302
|
+
columns: withExtendedColumns(WATCHLIST_COLUMNS, extendedSessionsOf(shown.map((row) => row.extendedSession))),
|
|
270
303
|
rows: shown,
|
|
271
304
|
metadata: {
|
|
272
305
|
collection: { kind: "watchlist", id: target.id, name: target.name },
|
|
@@ -286,7 +319,7 @@ async function watchlistHoldings(
|
|
|
286
319
|
export const collectionHoldingsHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
287
320
|
shape: "rows",
|
|
288
321
|
description:
|
|
289
|
-
"Positions held in a portfolio, broker or manual: symbol,
|
|
322
|
+
"Positions held in a portfolio, broker or manual: symbol, exchange, shares, average cost, last price, market value, unrealized P&L and weight of the total with cash, largest first, then the cash line and totals. With target weights set, each row adds its target, drift and the trade to reach it. For a watchlist, its tickers with quotes. Takes a portfolio or watchlist ID; the first portfolio when omitted.",
|
|
290
323
|
discovery: {
|
|
291
324
|
dataRequirements: ["Local portfolios, watchlists and synced broker positions; current quotes"],
|
|
292
325
|
limitations: ["Unrealized P&L on current positions; excludes realized trades, distributions and cash flows"],
|
|
@@ -172,7 +172,8 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
|
|
|
172
172
|
const flashSymbols = useQuoteFlashMap(financialsMap, valueFlashingEnabled);
|
|
173
173
|
|
|
174
174
|
const accountStateInput = useMemo(() => ({ brokerAccounts, config }), [brokerAccounts, config]);
|
|
175
|
-
const { accountState, accountsError } = usePortfolioAccountState(currentPortfolio, accountStateInput);
|
|
175
|
+
const { accountState, accountsError, brokerError } = usePortfolioAccountState(currentPortfolio, accountStateInput);
|
|
176
|
+
const brokerPortfolio = isPortfolioTab && !!currentPortfolio?.brokerInstanceId;
|
|
176
177
|
const portfolioTargets = currentPortfolio?.targetWeights;
|
|
177
178
|
const columns = useMemo(
|
|
178
179
|
() => resolveVisibleColumns(paneSettings.columnIds, isPortfolioTab, { targetWeights: hasTargetWeights(currentPortfolio) }),
|
|
@@ -338,7 +339,8 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
|
|
|
338
339
|
currency: totalsCurrency,
|
|
339
340
|
baseCurrency: config.baseCurrency,
|
|
340
341
|
manualCash,
|
|
341
|
-
|
|
342
|
+
brokerPortfolio,
|
|
343
|
+
}), [brokerPortfolio, config.baseCurrency, convertAccountValue, isPortfolioTab, manualCash, portfolioSummaryTotals, summaryAccountState, totalsCurrency]);
|
|
342
344
|
// The header row sits in the pane's one-cell side padding, like the table.
|
|
343
345
|
const summaryWidth = Math.max(0, width - 2);
|
|
344
346
|
const summaryLayout = useMemo(() => layoutPortfolioSummaryHeader(summarySegments, summaryWidth, {
|
|
@@ -476,7 +478,7 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
|
|
|
476
478
|
accountState: summaryAccountState,
|
|
477
479
|
accountStatusText: accountsError
|
|
478
480
|
? `Accounts unavailable: ${accountsError}`
|
|
479
|
-
:
|
|
481
|
+
: brokerPortfolio ? brokerError ?? (accountState ? undefined : "Acct missing") : undefined,
|
|
480
482
|
financialsMap,
|
|
481
483
|
isPortfolioTab,
|
|
482
484
|
refreshingSize,
|
|
@@ -485,7 +487,8 @@ export function PortfolioListPane({ focused, width, height }: PaneProps) {
|
|
|
485
487
|
}), [financialsMap, sortedTickers], [
|
|
486
488
|
accountState,
|
|
487
489
|
accountsError,
|
|
488
|
-
|
|
490
|
+
brokerError,
|
|
491
|
+
brokerPortfolio,
|
|
489
492
|
isPortfolioTab,
|
|
490
493
|
portfolioSummaryTotals,
|
|
491
494
|
refreshingSize,
|
|
@@ -12,6 +12,7 @@ import { displayWidth, formatPercentRaw } from "../../../../utils/format";
|
|
|
12
12
|
import { getBrokerInstance } from "../../../../utils/broker-instances";
|
|
13
13
|
import {
|
|
14
14
|
resolvePortfolioAccountMetrics,
|
|
15
|
+
resolvePortfolioLeverage,
|
|
15
16
|
resolvePortfolioMarketValue,
|
|
16
17
|
resolvePortfolioNetLiquidation,
|
|
17
18
|
type BrokerSnapshotBasis,
|
|
@@ -223,10 +224,13 @@ export function buildPortfolioSummarySegments({
|
|
|
223
224
|
currency = "USD",
|
|
224
225
|
baseCurrency = "USD",
|
|
225
226
|
manualCash = null,
|
|
227
|
+
brokerPortfolio = false,
|
|
226
228
|
}: {
|
|
227
229
|
totals: PortfolioSummaryTotals;
|
|
228
230
|
accountState: PortfolioSummaryAccountState | null;
|
|
229
231
|
isPortfolioTab?: boolean;
|
|
232
|
+
/** Synced from a broker, so it is worth its account's equity even while the account is missing. */
|
|
233
|
+
brokerPortfolio?: boolean;
|
|
230
234
|
/** Into the totals currency. */
|
|
231
235
|
convertAccountValue?: (value: number) => number;
|
|
232
236
|
/** The totals currency. */
|
|
@@ -249,6 +253,9 @@ export function buildPortfolioSummarySegments({
|
|
|
249
253
|
|
|
250
254
|
const candidates: PortfolioSummarySegment[] = [];
|
|
251
255
|
const account = accountState?.account;
|
|
256
|
+
// A broker account is worth its equity. Its positions alone are its gross,
|
|
257
|
+
// which margin or shorts put above it, so that sum never reads as its value.
|
|
258
|
+
const broker = brokerPortfolio || !!account;
|
|
252
259
|
const basis = accountState?.snapshotBasis;
|
|
253
260
|
const accountMetrics = resolvePortfolioAccountMetrics(totals, account, convertAccountValue, basis);
|
|
254
261
|
const totalMarketValue = resolvePortfolioMarketValue(totals, account, convertAccountValue, basis);
|
|
@@ -269,15 +276,17 @@ export function buildPortfolioSummarySegments({
|
|
|
269
276
|
]));
|
|
270
277
|
}
|
|
271
278
|
|
|
272
|
-
|
|
279
|
+
// Unknown without the account, so it reads as missing rather than disappearing,
|
|
280
|
+
// unless cash entered by hand gives the Total above in its place.
|
|
281
|
+
if (broker && (netLiquidation != null || !manualCash)) {
|
|
273
282
|
candidates.push(createSummarySegment("netliq", [
|
|
274
283
|
{ text: "Net Liq", tone: "label" },
|
|
275
|
-
{ text: money(netLiquidation), tone: "value", bold: true },
|
|
284
|
+
{ text: money(netLiquidation ?? Number.NaN), tone: "value", bold: true },
|
|
276
285
|
]));
|
|
277
286
|
}
|
|
278
287
|
|
|
279
288
|
candidates.push(createSummarySegment("val", [
|
|
280
|
-
{ text: totals.hasShorts ? "Gross" : "Val", tone: "label" },
|
|
289
|
+
{ text: broker || totals.hasShorts ? "Gross" : "Val", tone: "label" },
|
|
281
290
|
{ text: money(totalMarketValue), tone: "value", bold: true },
|
|
282
291
|
]));
|
|
283
292
|
|
|
@@ -289,7 +298,7 @@ export function buildPortfolioSummarySegments({
|
|
|
289
298
|
}
|
|
290
299
|
|
|
291
300
|
// A broker account always states its cash, so a missing balance reads as unknown rather than zero.
|
|
292
|
-
if (
|
|
301
|
+
if (broker && !manualCash) candidates.push(accountValue("cash", "Cash", account?.totalCashValue ?? Number.NaN)!);
|
|
293
302
|
if (manualCash) {
|
|
294
303
|
const drift = formatAllocationDrift(manualCash.drift);
|
|
295
304
|
candidates.push(createSummarySegment("cash", [
|
|
@@ -318,6 +327,14 @@ export function buildPortfolioSummarySegments({
|
|
|
318
327
|
|
|
319
328
|
if (!account) return candidates;
|
|
320
329
|
|
|
330
|
+
const leverage = resolvePortfolioLeverage(totalMarketValue, netLiquidation);
|
|
331
|
+
// Only while the account borrows or is short enough to read above 1.0x, as the analytics pane rounds it.
|
|
332
|
+
const lev = leverage != null && leverage >= 1.05
|
|
333
|
+
? createSummarySegment("lev", [
|
|
334
|
+
{ text: "Lev", tone: "label" },
|
|
335
|
+
{ text: `${leverage.toFixed(1)}x`, tone: "value", bold: true },
|
|
336
|
+
])
|
|
337
|
+
: null;
|
|
321
338
|
const realized = accountMetrics.realizedPnl != null
|
|
322
339
|
? createSummarySegment("realized", [
|
|
323
340
|
{ text: "Realized", tone: "label" },
|
|
@@ -327,6 +344,7 @@ export function buildPortfolioSummarySegments({
|
|
|
327
344
|
return [
|
|
328
345
|
...candidates,
|
|
329
346
|
...[
|
|
347
|
+
lev,
|
|
330
348
|
realized,
|
|
331
349
|
accountValue("settled", "Settled", account.settledCash),
|
|
332
350
|
accountValue("avail", "Avail", account.availableFunds),
|
|
@@ -84,6 +84,12 @@ export interface PortfolioAccountStateResult {
|
|
|
84
84
|
accountState: ResolvedPortfolioAccountState | null;
|
|
85
85
|
/** Set when the broker refused to list accounts, so "no cash" is not mistaken for a clean empty. */
|
|
86
86
|
accountsError: string | null;
|
|
87
|
+
/**
|
|
88
|
+
* The broker's last call failed, such as the sync at startup after its
|
|
89
|
+
* sign-in lapsed. Accounts are listed only while it is connected, so this
|
|
90
|
+
* is the only word of why the account is missing or stale.
|
|
91
|
+
*/
|
|
92
|
+
brokerError: string | null;
|
|
87
93
|
}
|
|
88
94
|
|
|
89
95
|
/**
|
|
@@ -111,5 +117,9 @@ export function usePortfolioAccountState(
|
|
|
111
117
|
() => resolvePortfolioAccountState(portfolio, state, snapshot),
|
|
112
118
|
[portfolio, snapshot, state.brokerAccounts, state.config],
|
|
113
119
|
);
|
|
114
|
-
|
|
120
|
+
const brokerError = live.status?.state === "error" ? live.status.message?.trim() || "Broker unavailable" : null;
|
|
121
|
+
return useMemo(
|
|
122
|
+
() => ({ accountState, accountsError: live.error, brokerError }),
|
|
123
|
+
[accountState, brokerError, live.error],
|
|
124
|
+
);
|
|
115
125
|
}
|
|
@@ -46,7 +46,10 @@ export function realizedVolHeadless(initialView: "graph" | "cone"): HeadlessPane
|
|
|
46
46
|
estimator: ESTIMATOR_OPTIONS.find(({ value }) => value === args.options.estimator)?.value,
|
|
47
47
|
windows, lookbackYears: Number(args.options.lookbackYears) === 2 ? 2 : 1 });
|
|
48
48
|
const errors = [history.error, iv?.error].filter((value): value is string => !!value);
|
|
49
|
+
// The cone rows are as of the last daily bar, not of when the report ran.
|
|
50
|
+
const lastBar = model.asOf && Number.isFinite(model.asOf.getTime()) ? model.asOf.toISOString().slice(0, 10) : null;
|
|
49
51
|
return {
|
|
52
|
+
freshness: { asOf: lastBar },
|
|
50
53
|
sections: [
|
|
51
54
|
{ title: "Volatility cone", columns: [
|
|
52
55
|
{ key: "window", header: "Sessions" },
|
|
@@ -164,7 +164,7 @@ export function RealizedVolPane({ width, height, focused }: PaneProps) {
|
|
|
164
164
|
{ id: "estimator", label: "Estimator", value: estimator, options: ESTIMATOR_OPTIONS, onChange: (value: string) => setEstimator(value as RealizedVolatilityEstimator) },
|
|
165
165
|
{ id: "lookback", label: "Lookback", value: String(lookback), options: [{ value: "1", label: "1Y" }, { value: "2", label: "2Y" }], onChange: setLookback },
|
|
166
166
|
]} meta={reference ? `ATM IV observed ${reference.date.toISOString().slice(0, 16).replace("T", " ")} UTC` : "Annualized %"} />
|
|
167
|
-
{!symbol ? <EmptyState title="
|
|
167
|
+
{!symbol ? <EmptyState title="Select a ticker." /> : <PaneStatusBody subject="realized volatility" loading={history.loading && !model}
|
|
168
168
|
error={!model ? history.error ?? identityError ?? null : null} empty={!!model && !model.history.length}>
|
|
169
169
|
{model && view === "cone" ? <>
|
|
170
170
|
<StatGrid items={coneStats} width={width} />
|
|
@@ -25,7 +25,7 @@ import { formatPercent } from "../../../utils/format";
|
|
|
25
25
|
import { useResolvedEntryValue, useSecFilingDocuments, useSecFilingsQuery } from "../../../market-data/hooks";
|
|
26
26
|
import { instrumentFromTicker } from "../../../market-data/request-types";
|
|
27
27
|
import { usePaneTicker } from "../../../state/app/context";
|
|
28
|
-
import {
|
|
28
|
+
import { mayBeUsEquityTicker } from "../../../utils/sec";
|
|
29
29
|
import { useAssetData, usePluginPaneState } from "../../runtime";
|
|
30
30
|
import { handleRefreshKey } from "../../../components/data-table/table-pane";
|
|
31
31
|
import { SignInWall } from "../cloud/auth-actions";
|
|
@@ -496,7 +496,7 @@ export function CorporateActionsView({
|
|
|
496
496
|
: null;
|
|
497
497
|
const instrument = useMemo(() => instrumentFromTicker(ticker, symbol), [symbol, ticker]);
|
|
498
498
|
const secFilingsEntry = useSecFilingsQuery(
|
|
499
|
-
openRow?.status === "Earnings" && instrument &&
|
|
499
|
+
openRow?.status === "Earnings" && instrument && mayBeUsEquityTicker(ticker)
|
|
500
500
|
? { instrument, count: SEC_EVENT_FILING_LIMIT }
|
|
501
501
|
: null,
|
|
502
502
|
);
|
|
@@ -558,7 +558,7 @@ export function EquityDiagnosticView({ focused, width, height }: {
|
|
|
558
558
|
// Walls and empty states are drawn straight into the pane, like every other
|
|
559
559
|
// pane's; only a report sits in the padded scroll area.
|
|
560
560
|
if (!symbol) {
|
|
561
|
-
return <PaneStatusBody empty emptyTitle="
|
|
561
|
+
return <PaneStatusBody empty emptyTitle="Select a ticker." emptyMessage="Move the cursor in a list pane to populate this view." />;
|
|
562
562
|
}
|
|
563
563
|
if (signInRequired || verificationRequired) {
|
|
564
564
|
return <SignInWall placement="diag-signin" width={width} height={height} symbol={symbol} exchange={exchange} action="run the Equity Diagnostic" needsVerification={verificationRequired} />;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { comparablePriceEarnings, notMeaningfulMultiples } from "../../../utils/price-earnings";
|
|
2
2
|
import { selectMarketCapitalization } from "../../../utils/market-capitalization";
|
|
3
|
+
import { reportedEnterpriseValue } from "../../../utils/fundamentals";
|
|
3
4
|
import type { Quote, TickerFinancials } from "../../../types/financials";
|
|
4
5
|
|
|
5
6
|
export const RELATIVE_VALUATION_STALE_QUOTE_NOTICE = "Quote stale: quote-based values unavailable";
|
|
@@ -10,6 +11,9 @@ export function relativeValuationValues(financials: TickerFinancials | null) {
|
|
|
10
11
|
const fundamentals = financials?.fundamentals;
|
|
11
12
|
const quoteStale = quote?.stale === true;
|
|
12
13
|
const capitalization = selectMarketCapitalization(quoteStale ? undefined : quote, fundamentals);
|
|
14
|
+
const enterpriseValue = reportedEnterpriseValue(fundamentals);
|
|
15
|
+
// A zero or non-finite enterprise value is the source's gap, and the ratio it serves beside it comes from the same observation.
|
|
16
|
+
const enterpriseValueGap = fundamentals?.enterpriseValue != null && enterpriseValue == null;
|
|
13
17
|
const compatibleCurrency = !!fundamentals?.financialCurrency && !!quote?.currency
|
|
14
18
|
&& fundamentals.financialCurrency === quote.currency;
|
|
15
19
|
const reportedMultiples = {
|
|
@@ -47,11 +51,11 @@ export function relativeValuationValues(financials: TickerFinancials | null) {
|
|
|
47
51
|
/** Why a multiple is N/M: its earnings base is zero or a loss. */
|
|
48
52
|
notMeaningful,
|
|
49
53
|
// Vendor ADR ratios can mix unverified valuation and reporting units too.
|
|
50
|
-
evSales: !compatibleCurrency ? null
|
|
54
|
+
evSales: !compatibleCurrency || enterpriseValueGap ? null
|
|
51
55
|
: fundamentals?.enterpriseToRevenue != null && Number.isFinite(fundamentals.enterpriseToRevenue)
|
|
52
56
|
? fundamentals.enterpriseToRevenue
|
|
53
|
-
:
|
|
54
|
-
?
|
|
57
|
+
: enterpriseValue != null && fundamentals?.revenue != null && fundamentals.revenue > 0
|
|
58
|
+
? enterpriseValue / fundamentals.revenue : null,
|
|
55
59
|
fcfYield: capitalization && fundamentals?.financialCurrency === capitalization.currency
|
|
56
60
|
&& fundamentals.freeCashFlow != null && Number.isFinite(fundamentals.freeCashFlow) && capitalization.value > 0
|
|
57
61
|
? fundamentals.freeCashFlow / capitalization.value : null,
|
|
@@ -4,7 +4,9 @@ import type {
|
|
|
4
4
|
RevenueBreakdownPayload,
|
|
5
5
|
RevenueBreakdownView,
|
|
6
6
|
} from "../../../api-client/revenue-breakdown";
|
|
7
|
+
import type { IssuerListingParams } from "../../../api-client/paths";
|
|
7
8
|
import { createPluginCache } from "../../../data/plugin-cache";
|
|
9
|
+
import { secListingKey } from "../../../utils/sec";
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Revenue by product, segment or region comes from Gloom Cloud, which reads
|
|
@@ -59,10 +61,12 @@ export async function fetchRevenueBreakdown(
|
|
|
59
61
|
symbol: string,
|
|
60
62
|
view: RevenueBreakdownView,
|
|
61
63
|
client: Pick<typeof apiClient, "getCloudRevenueBreakdown"> = apiClient,
|
|
64
|
+
listing?: IssuerListingParams,
|
|
62
65
|
): Promise<RevenueBreakdownPayload> {
|
|
63
66
|
try {
|
|
64
|
-
return validateRevenueBreakdown(await client.getCloudRevenueBreakdown(symbol, view));
|
|
67
|
+
return validateRevenueBreakdown(await client.getCloudRevenueBreakdown(symbol, view, listing));
|
|
65
68
|
} catch (error) {
|
|
69
|
+
// 404 is also a listing abroad whose company the SEC does not know.
|
|
66
70
|
if (error instanceof ApiRequestError && error.status === 404) throw new Error(NO_BREAKDOWN);
|
|
67
71
|
throw error;
|
|
68
72
|
}
|
|
@@ -74,8 +78,9 @@ export interface RevenueResource {
|
|
|
74
78
|
refreshError: string | null;
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
// SAN in Paris is not SAN in New York: a listing abroad is cached under its venue.
|
|
82
|
+
const cacheKey = (symbol: string, view: RevenueBreakdownView, pro: boolean, listing?: IssuerListingParams) =>
|
|
83
|
+
`${secListingKey(symbol, listing?.exchange)}:${view}:${pro ? "full" : "preview"}`;
|
|
79
84
|
|
|
80
85
|
// Seeds the first paint while the mount load runs; cache age is not staleness,
|
|
81
86
|
// and a failed refresh reports its own.
|
|
@@ -83,8 +88,9 @@ export function cachedRevenueBreakdown(
|
|
|
83
88
|
symbol: string,
|
|
84
89
|
view: RevenueBreakdownView,
|
|
85
90
|
pro: boolean,
|
|
91
|
+
listing?: IssuerListingParams,
|
|
86
92
|
): RevenueResource | null {
|
|
87
|
-
const cached = revenueBreakdownCache.get(cacheKey(symbol, view, pro), { allowExpired: true });
|
|
93
|
+
const cached = revenueBreakdownCache.get(cacheKey(symbol, view, pro, listing), { allowExpired: true });
|
|
88
94
|
if (!cached) return null;
|
|
89
95
|
try {
|
|
90
96
|
return { payload: validateRevenueBreakdown(cached.data), stale: false, refreshError: null };
|
|
@@ -98,10 +104,11 @@ export async function loadRevenueBreakdown(
|
|
|
98
104
|
view: RevenueBreakdownView,
|
|
99
105
|
pro: boolean,
|
|
100
106
|
force = false,
|
|
107
|
+
listing?: IssuerListingParams,
|
|
101
108
|
): Promise<RevenueResource> {
|
|
102
109
|
const result = await revenueBreakdownCache.load(
|
|
103
|
-
cacheKey(symbol, view, pro),
|
|
104
|
-
() => fetchRevenueBreakdown(symbol, view),
|
|
110
|
+
cacheKey(symbol, view, pro, listing),
|
|
111
|
+
() => fetchRevenueBreakdown(symbol, view, apiClient, listing),
|
|
105
112
|
{ force },
|
|
106
113
|
);
|
|
107
114
|
// A company that stopped reporting a breakdown must not keep showing an old one.
|
|
@@ -3,6 +3,7 @@ import type { HeadlessPaneDefinition } from "../../../types/plugin";
|
|
|
3
3
|
import { fetchRevenueBreakdown } from "./client";
|
|
4
4
|
import { quarterLabel, reportedSpan } from "./model";
|
|
5
5
|
import { SEC_FILINGS } from "../shared/report-freshness";
|
|
6
|
+
import { resolveHeadlessIssuerListing } from "../shared/headless-market-data";
|
|
6
7
|
|
|
7
8
|
export const revenueBreakdownHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
8
9
|
shape: "bundle",
|
|
@@ -34,10 +35,12 @@ export const revenueBreakdownHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
34
35
|
],
|
|
35
36
|
describe: (args) => `Revenue breakdown | ${args.symbols[0]}`,
|
|
36
37
|
async load(args, ctx) {
|
|
38
|
+
const symbol = args.symbols[0]!;
|
|
37
39
|
const data = reportedSpan(await fetchRevenueBreakdown(
|
|
38
|
-
|
|
40
|
+
symbol,
|
|
39
41
|
(args.options.view as RevenueBreakdownView | undefined) ?? "product",
|
|
40
42
|
ctx.apiClient,
|
|
43
|
+
await resolveHeadlessIssuerListing(ctx, symbol),
|
|
41
44
|
));
|
|
42
45
|
return {
|
|
43
46
|
complete: data.access === "full",
|
|
@@ -30,7 +30,7 @@ import { Blurred, LockedOverlay, UpgradeLabel } from "../shared/locked-rows";
|
|
|
30
30
|
import { usePaneStatusFooter } from "../../../components/layout/pane/status-footer";
|
|
31
31
|
import { usePlanAccess } from "../../../api-client/plan-access";
|
|
32
32
|
import { useResearchCloudSession } from "../shared/research-cloud-session";
|
|
33
|
-
import { listingIdentity } from "../shared/ticker-request";
|
|
33
|
+
import { listingIdentity, useIssuerListing } from "../shared/ticker-request";
|
|
34
34
|
import { QuarterBars, type BarHover } from "./bars";
|
|
35
35
|
import {
|
|
36
36
|
cachedRevenueBreakdown,
|
|
@@ -82,17 +82,20 @@ function RevenueBreakdownView({ width, height, focused }: { width: number; heigh
|
|
|
82
82
|
const openUpgrade = useCloudUpgradeAction("seg");
|
|
83
83
|
const identity = listingIdentity(boundSymbol, ticker?.metadata.exchange ?? "");
|
|
84
84
|
const symbol = identity?.symbol ?? null;
|
|
85
|
+
const { name } = useIssuerListing(ticker);
|
|
86
|
+
const exchange = identity?.exchange ?? "";
|
|
87
|
+
const listing = useMemo(() => ({ exchange, name }), [exchange, name]);
|
|
85
88
|
const [requestedView, setView] = usePaneSettingValue<RevenueBreakdownView>("view", "product");
|
|
86
89
|
const [mode, setMode] = usePluginPaneState<RevenueMode>("revenue:mode", "trend");
|
|
87
90
|
const [sort, setSort] = usePluginPaneState<RevenueSort>("revenue:sort", { column: "revenue", direction: "desc" });
|
|
88
91
|
const view = REVENUE_VIEWS.includes(requestedView) ? requestedView : "product";
|
|
89
92
|
|
|
90
93
|
const loader = useCallback(
|
|
91
|
-
(force: boolean) => loadRevenueBreakdown(symbol!, view, pro, force),
|
|
92
|
-
[symbol, view, pro, session.requestKey],
|
|
94
|
+
(force: boolean) => loadRevenueBreakdown(symbol!, view, pro, force, listing),
|
|
95
|
+
[symbol, listing, view, pro, session.requestKey],
|
|
93
96
|
);
|
|
94
97
|
const resource = useAsyncResource(symbol ? loader : null, {
|
|
95
|
-
initialData: () => (symbol ? cachedRevenueBreakdown(symbol, view, pro) : null),
|
|
98
|
+
initialData: () => (symbol ? cachedRevenueBreakdown(symbol, view, pro, listing) : null),
|
|
96
99
|
clearOnError: (error) => error instanceof Error && error.message === NO_BREAKDOWN,
|
|
97
100
|
});
|
|
98
101
|
useAutoRefresh(resource.updatedAt, resource.load);
|
|
@@ -36,7 +36,11 @@ export const reverseDcfHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
36
36
|
...Object.fromEntries(row.implied.map((value, index) => [`t${index}`, implied(value)])) })) },
|
|
37
37
|
],
|
|
38
38
|
complete: !inputs.stale && !inputs.error && !model.error,
|
|
39
|
-
|
|
39
|
+
// Dated by the price the enterprise value is marked at, not by when the report ran.
|
|
40
|
+
freshness: {
|
|
41
|
+
asOf: inputs.financials?.quote?.lastUpdated ?? null,
|
|
42
|
+
...(inputs.stale ? { status: "stale" as const } : {}),
|
|
43
|
+
},
|
|
40
44
|
unavailableSymbols: model.implied ? [] : [instrument.symbol],
|
|
41
45
|
errors: [inputs.error, model.error].filter((value): value is string => !!value),
|
|
42
46
|
metadata: { currency: model.currency, discountRate, terminalGrowth: TERMINAL_GROWTH, discountRates: DISCOUNT_RATES,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TickerFinancials } from "../../../types/financials";
|
|
2
|
+
import { reportedEnterpriseValue } from "../../../utils/fundamentals";
|
|
2
3
|
|
|
3
4
|
/** Years of explicit growth before the terminal value. */
|
|
4
5
|
export const FORECAST_YEARS = 10;
|
|
@@ -65,7 +66,7 @@ function pastFcfGrowth(financials: TickerFinancials): ReverseDcfModel["pastGrowt
|
|
|
65
66
|
export function projectReverseDcf(financials: TickerFinancials | null, options: { symbol: string; discountRate: number }): ReverseDcfModel {
|
|
66
67
|
const fundamentals = financials?.fundamentals;
|
|
67
68
|
const currency = fundamentals?.financialCurrency ?? financials?.financialCurrency ?? null;
|
|
68
|
-
const enterpriseValue = fundamentals
|
|
69
|
+
const enterpriseValue = reportedEnterpriseValue(fundamentals) ?? null;
|
|
69
70
|
const freeCashFlow = fundamentals?.freeCashFlow ?? null;
|
|
70
71
|
const empty: ReverseDcfModel = { symbol: options.symbol, currency, enterpriseValue, freeCashFlow, fcfYield: null, implied: null,
|
|
71
72
|
pastGrowth: financials ? pastFcfGrowth(financials) : null, sensitivity: [], error: null };
|
|
@@ -91,7 +91,7 @@ export function ReverseDcfPane({ width, height, focused }: PaneProps) {
|
|
|
91
91
|
|
|
92
92
|
return <Box width={width} height={height} flexDirection="column" overflow="hidden">
|
|
93
93
|
<QueryBar width={width} filters={[{ id: "discount", label: "Discount", value: String(discount), options: DISCOUNT_OPTIONS, onChange: setDiscount }]} />
|
|
94
|
-
{!symbol ? <EmptyState title="
|
|
94
|
+
{!symbol ? <EmptyState title="Select a ticker." /> : <PaneStatusBody subject="reverse DCF" loading={inputs.loading && !model}
|
|
95
95
|
error={!model ? inputs.error ?? identityError ?? null : model.error} empty={false}>
|
|
96
96
|
{model && !model.error ? <>
|
|
97
97
|
<StatGrid items={stats} width={width} />
|
|
@@ -4,7 +4,9 @@ import {
|
|
|
4
4
|
type CloudRiskReportPayload,
|
|
5
5
|
} from "../../../api-client";
|
|
6
6
|
import { ApiRequestError, isPermanentClientError } from "../../../api-client/errors";
|
|
7
|
+
import { listingAbroad, type IssuerListingParams } from "../../../api-client/paths";
|
|
7
8
|
import type { HeadlessPaneApiClient, PluginPersistence } from "../../../types/plugin";
|
|
9
|
+
import { secListingKey } from "../../../utils/sec";
|
|
8
10
|
|
|
9
11
|
/** Annual reports are immutable; discovery of newly produced reports is not. */
|
|
10
12
|
const LIST_KIND = "reports";
|
|
@@ -88,12 +90,20 @@ function loadCached<T extends object>(
|
|
|
88
90
|
return request.promise;
|
|
89
91
|
}
|
|
90
92
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
interface RiskLoadOptions {
|
|
94
|
+
force?: boolean;
|
|
95
|
+
/** The listing's venue and company, for a listing outside the US. */
|
|
96
|
+
listing?: IssuerListingParams;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function loadRiskReportsWithClient(client: RiskApiClient, ticker: string, options?: RiskLoadOptions): Promise<RiskReportsResult> {
|
|
100
|
+
const symbol = ticker.trim().toUpperCase();
|
|
101
|
+
// SAN in Paris is not SAN in New York: a listing abroad is cached under its venue.
|
|
102
|
+
const key = secListingKey(symbol, options?.listing?.exchange);
|
|
93
103
|
return loadCached(client, LIST_KIND, key, activeListFetches, LIST_CACHE_POLICY, options?.force ?? false, async () => {
|
|
94
104
|
let payload: CloudRiskReportListPayload;
|
|
95
105
|
try {
|
|
96
|
-
payload = await client.getRiskReports(
|
|
106
|
+
payload = await client.getRiskReports(symbol, options?.listing);
|
|
97
107
|
} catch (error) {
|
|
98
108
|
// The list answers 404 when no 10-K risk report is on file (20-F filers, funds).
|
|
99
109
|
if (error instanceof ApiRequestError && error.status === 404) return { company: null, reports: [] };
|
|
@@ -104,12 +114,16 @@ export function loadRiskReportsWithClient(client: RiskApiClient, ticker: string,
|
|
|
104
114
|
});
|
|
105
115
|
}
|
|
106
116
|
|
|
107
|
-
export function loadRiskReportWithClient(client: RiskApiClient, ticker: string, year: number, options?:
|
|
117
|
+
export function loadRiskReportWithClient(client: RiskApiClient, ticker: string, year: number, options?: RiskLoadOptions): Promise<RiskReportResult> {
|
|
108
118
|
const symbol = ticker.trim().toUpperCase();
|
|
109
|
-
|
|
110
|
-
|
|
119
|
+
const abroad = listingAbroad(symbol, options?.listing?.exchange) !== null;
|
|
120
|
+
const key = `${secListingKey(symbol, options?.listing?.exchange)}:${year}`;
|
|
121
|
+
return loadCached(client, REPORT_KIND, key, activeReportFetches, REPORT_CACHE_POLICY, options?.force ?? false, async () => {
|
|
122
|
+
const payload = await client.getRiskReport(symbol, year, options?.listing);
|
|
111
123
|
// SEC rows spell share classes BRK-B; a BRK.B request is the same company.
|
|
112
|
-
|
|
124
|
+
// A listing abroad reads its registrant's rows, filed under that
|
|
125
|
+
// registrant's US ticker (Sanofi's SNY for SAN in Paris).
|
|
126
|
+
const sameTicker = (value: string | undefined) => abroad || value?.toUpperCase().replace(/\./g, "-") === symbol.replace(/\./g, "-");
|
|
113
127
|
if (!payload || !sameTicker(payload.ticker) || payload.reportYear !== year) {
|
|
114
128
|
throw new Error(`Risk report response does not match ${symbol} ${year}.`);
|
|
115
129
|
}
|
|
@@ -117,5 +131,5 @@ export function loadRiskReportWithClient(client: RiskApiClient, ticker: string,
|
|
|
117
131
|
});
|
|
118
132
|
}
|
|
119
133
|
|
|
120
|
-
export const loadRiskReports = (ticker: string, options?:
|
|
121
|
-
export const loadRiskReport = (ticker: string, year: number, options?:
|
|
134
|
+
export const loadRiskReports = (ticker: string, options?: RiskLoadOptions) => loadRiskReportsWithClient(apiClient, ticker, options);
|
|
135
|
+
export const loadRiskReport = (ticker: string, year: number, options?: RiskLoadOptions) => loadRiskReportWithClient(apiClient, ticker, year, options);
|