gloomberb 0.16.3 → 0.16.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -1
- package/src/api-client/askg.ts +43 -0
- package/src/api-client/auth.ts +15 -0
- package/src/api-client/beneficial-owners.ts +4 -0
- package/src/api-client/chat.ts +19 -0
- package/src/api-client/company-kpis.ts +14 -2
- package/src/api-client/data.ts +34 -16
- package/src/api-client/index.ts +12 -0
- package/src/api-client/normalizers.ts +27 -0
- package/src/api-client/paths.ts +88 -23
- package/src/api-client/request.ts +2 -1
- package/src/api-client/research-activity.ts +31 -0
- package/src/api-client/socket.ts +39 -8
- package/src/api-client/types.ts +57 -1
- package/src/api-client/upgrade-experiment.ts +90 -0
- package/src/app/global-shortcuts.ts +11 -1
- package/src/app/keybindings/actions.ts +10 -0
- package/src/app/pane-runtime/index.tsx +8 -0
- package/src/app/pane-runtime/pane-template-runtime.ts +5 -0
- package/src/app/pane-runtime/plugin-bindings.ts +65 -25
- package/src/app/pane-runtime/ticker-inspector-runtime.ts +12 -0
- package/src/app/pane-runtime/ticker-open-runtime.ts +18 -1
- package/src/app/runtime/desktop-deeplink.ts +5 -0
- package/src/app/star-prompt/model.ts +98 -0
- package/src/app/star-prompt/runtime.ts +41 -0
- package/src/app/star-prompt/status-line.tsx +190 -0
- package/src/app.tsx +13 -7
- package/src/architecture/core-builtin-allowlist.json +70 -0
- package/src/brokers/signed-in/adapter.ts +33 -2
- package/src/brokers/signed-in/client.ts +10 -0
- package/src/brokers/signed-in/sign-in-dialog.tsx +23 -9
- package/src/brokers/signed-in/sign-in.ts +62 -5
- package/src/capabilities/registry.ts +8 -1
- package/src/capabilities/types.ts +2 -0
- package/src/cli/commands/crypto-hints.ts +12 -4
- package/src/cli/commands/market.ts +359 -99
- package/src/cli/commands/options-chain.ts +172 -0
- package/src/cli/commands/options-leaps.ts +422 -0
- package/src/cli/commands/overview.ts +164 -26
- package/src/cli/commands/search.ts +29 -8
- package/src/cli/commands/system.ts +99 -19
- package/src/cli/commands/themes.ts +64 -0
- package/src/cli/commands/ticker.ts +62 -30
- package/src/cli/desktop-pane-shot.ts +97 -16
- package/src/cli/errors.ts +25 -2
- package/src/cli/freshness.ts +7 -4
- package/src/cli/fx-pair.ts +57 -0
- package/src/cli/help.ts +14 -1
- package/src/cli/index-roots.ts +51 -0
- package/src/cli/index.ts +9 -6
- package/src/cli/listing-arg.ts +211 -3
- package/src/cli/not-a-ticker.ts +37 -0
- package/src/cli/options.ts +50 -6
- package/src/cli/pane-functions/capabilities.ts +8 -2
- package/src/cli/pane-functions/catalog.ts +13 -2
- package/src/cli/pane-functions/data.ts +36 -5
- package/src/cli/pane-functions/dom.ts +21 -13
- package/src/cli/pane-functions/freshness.ts +205 -26
- package/src/cli/pane-functions/function-help.ts +67 -22
- package/src/cli/pane-functions/headless.ts +5 -4
- package/src/cli/pane-functions/index.ts +3 -0
- package/src/cli/pane-functions/market-session.ts +133 -0
- package/src/cli/pane-functions/options.ts +15 -4
- package/src/cli/pane-functions/report-notices.ts +63 -0
- package/src/cli/pane-functions/report.ts +4 -0
- package/src/cli/pane-functions/resolver.ts +4 -6
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +105 -23
- package/src/cli/registry.ts +2 -1
- package/src/cli/result.ts +50 -13
- package/src/cli/row-window.ts +76 -0
- package/src/cli/scroll-truncation.ts +34 -0
- package/src/components/chart/composite/composite-chart.tsx +43 -4
- package/src/components/chart/composite/legend.tsx +39 -6
- package/src/components/chart/composite/rasterizer.ts +64 -4
- package/src/components/chart/composite/series-cues.ts +71 -0
- package/src/components/chart/composite/text-renderer.ts +16 -8
- package/src/components/chart/composite/types.ts +3 -0
- package/src/components/command-bar/commands/direct/index.ts +9 -1
- package/src/components/command-bar/commands/plugin/items.ts +3 -1
- package/src/components/command-bar/commands/registry.ts +7 -0
- package/src/components/command-bar/layout-items/focused-pane.ts +38 -0
- package/src/components/command-bar/pane-templates/workflow.ts +5 -0
- package/src/components/command-bar/routes/root/command-items.ts +2 -0
- package/src/components/command-bar/routes/root/plugin-turn-on.ts +92 -0
- package/src/components/command-bar/routes/root/results.ts +40 -10
- package/src/components/command-bar/routes/root/runtime.ts +8 -0
- package/src/components/command-bar/routes/root/search-providers.ts +97 -14
- package/src/components/command-bar/surface/index.tsx +18 -4
- package/src/components/command-bar/theme-picker.tsx +48 -13
- package/src/components/command-bar/workflow/ops.ts +5 -3
- package/src/components/data-table/view.tsx +28 -9
- package/src/components/layout/floating-pane.tsx +6 -2
- package/src/components/layout/market-summary.ts +23 -9
- package/src/components/layout/pane/content.tsx +46 -29
- package/src/components/layout/pane/cycle-order.ts +2 -11
- package/src/components/layout/pane/header.tsx +2 -1
- package/src/components/layout/pane/title.ts +1 -1
- package/src/components/layout/presentation.ts +22 -0
- package/src/components/layout/shell/active-drag.ts +1 -1
- package/src/components/layout/shell/drag/index.ts +4 -0
- package/src/components/layout/shell/drag/live.ts +76 -5
- package/src/components/layout/shell/drag/runtime.ts +8 -8
- package/src/components/layout/shell/index.tsx +47 -6
- package/src/components/layout/shell/layout-state.ts +5 -11
- package/src/components/layout/shell/menu.ts +28 -1
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +51 -10
- package/src/components/layout/shell/pane/management-shortcuts.ts +5 -0
- package/src/components/layout/shell/pane/new-layout.ts +116 -0
- package/src/components/layout/shell/shortcuts.ts +5 -2
- package/src/components/layout/shell/visible-layout.ts +44 -1
- package/src/components/layout/status-bar.tsx +4 -1
- package/src/components/markdown-text.tsx +9 -5
- package/src/components/metric-treemap/heat-surface.tsx +27 -8
- package/src/components/metric-treemap/heat-tiles.ts +48 -17
- package/src/components/onboarding/onboarding-frame.tsx +33 -10
- package/src/components/onboarding/onboarding-wizard.tsx +49 -40
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +5 -2
- package/src/components/onboarding/pro-step-layout.ts +86 -0
- package/src/components/onboarding/use-onboarding-upgrade.ts +6 -2
- package/src/components/ui/button.tsx +10 -0
- package/src/components/ui/data-table/index.tsx +38 -0
- package/src/components/ui/data-table/types.ts +8 -1
- package/src/components/ui/data-table/visible-range.ts +2 -0
- package/src/components/ui/icon.tsx +11 -2
- package/src/components/ui/index.ts +1 -1
- package/src/core/connection-health.ts +1 -1
- package/src/core/state/app/layout-reducer.ts +89 -3
- package/src/core/state/app/state.ts +8 -5
- package/src/core/state/app/types.ts +13 -2
- package/src/data/config/layout.ts +55 -21
- package/src/data/config/store/fold-panes.ts +149 -0
- package/src/data/config/store/migrations.ts +7 -0
- package/src/data/config/store/node.ts +27 -6
- package/src/data/config/store/normalize.ts +37 -3
- package/src/i18n/es.ts +14 -1
- package/src/i18n/ja.ts +14 -1
- package/src/i18n/ko.ts +14 -1
- package/src/i18n/zh-cn.ts +14 -1
- package/src/i18n/zh-tw.ts +14 -1
- package/src/layout/desks.ts +1 -2
- package/src/layout/pane-layout-move.ts +264 -0
- package/src/layout/pane-manager/hidden-panes.ts +160 -0
- package/src/layout/pane-manager/index.ts +1 -0
- package/src/market-data/coordinator/entries.ts +7 -1
- package/src/market-data/coordinator/quotes.ts +2 -2
- package/src/market-data/market/freshness.ts +118 -18
- package/src/market-data/market/status.ts +40 -0
- package/src/market-data/options-alternatives.ts +69 -0
- package/src/market-data/quotes/contributions.ts +19 -2
- package/src/market-data/quotes/metadata.ts +16 -0
- package/src/market-data/quotes/screener-live-quotes.ts +56 -5
- package/src/plugins/builtin/account-management/keyboard.ts +3 -0
- package/src/plugins/builtin/account-management/model.ts +1 -0
- package/src/plugins/builtin/account-management/navigation.ts +1 -1
- package/src/plugins/builtin/account-management/pane.tsx +31 -4
- package/src/plugins/builtin/alerts/quotes.ts +3 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +3 -1
- package/src/plugins/builtin/analytics/risk-model.ts +37 -8
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -4
- package/src/plugins/builtin/backtest/pane.tsx +1 -1
- package/src/plugins/builtin/broker-manager/detail.tsx +4 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/broker-manager/model.ts +8 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -2
- package/src/plugins/builtin/broker-manager/sign-in-end.ts +82 -0
- package/src/plugins/builtin/broker-manager/table.ts +5 -0
- package/src/plugins/builtin/builtin-plugin-meta.ts +121 -11
- package/src/plugins/builtin/chart-composer/pane.tsx +1 -1
- package/src/plugins/builtin/chat/channels.ts +34 -36
- package/src/plugins/builtin/chat/command-bar.ts +138 -0
- package/src/plugins/builtin/chat/composer-commands.ts +21 -1
- package/src/plugins/builtin/chat/content/composer-runtime.ts +11 -1
- package/src/plugins/builtin/chat/content/footer.tsx +5 -3
- package/src/plugins/builtin/chat/content/index.tsx +21 -1
- package/src/plugins/builtin/chat/content/mentions.ts +2 -1
- package/src/plugins/builtin/chat/content/user-map.ts +3 -1
- package/src/plugins/builtin/chat/controller/index.ts +48 -0
- package/src/plugins/builtin/chat/conversations.ts +256 -0
- package/src/plugins/builtin/chat/discord-command.ts +91 -0
- package/src/plugins/builtin/chat/edit-window.ts +12 -6
- package/src/plugins/builtin/chat/ghost-user.ts +14 -0
- package/src/plugins/builtin/chat/index.tsx +40 -2
- package/src/plugins/builtin/chat/layout.ts +2 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +2 -1
- package/src/plugins/builtin/chat/message/header.tsx +24 -15
- package/src/plugins/builtin/chat/message/profile-popover.tsx +2 -1
- package/src/plugins/builtin/chat/message/render-state.ts +3 -0
- package/src/plugins/builtin/chat/pane-requests.ts +60 -0
- package/src/plugins/builtin/chat/pane-state.ts +32 -2
- package/src/plugins/builtin/chat/pane.tsx +8 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +112 -4
- package/src/plugins/builtin/cloud/askg/conversation-store.ts +21 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +1 -0
- package/src/plugins/builtin/cloud/askg/model.ts +129 -70
- package/src/plugins/builtin/cloud/askg/pane.tsx +772 -377
- package/src/plugins/builtin/cloud/askg/protocol.ts +40 -2
- package/src/plugins/builtin/cloud/askg/tool-display.ts +437 -0
- package/src/plugins/builtin/cloud/askg/tool-timeline.tsx +403 -0
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -9
- package/src/plugins/builtin/cloud/mcp-connect/command.ts +24 -0
- package/src/plugins/builtin/cloud/mcp-connect/dialog.tsx +360 -0
- package/src/plugins/builtin/cloud/mcp-connect/model.ts +135 -0
- package/src/plugins/builtin/cloud/mcp-connect/sections.ts +62 -0
- package/src/plugins/builtin/cloud/plugin.tsx +36 -1
- package/src/plugins/builtin/cloud/starter-symbols.ts +8 -0
- package/src/plugins/builtin/cloud/terminal-relay/acm-tab.tsx +89 -0
- package/src/plugins/builtin/cloud/terminal-relay/client.ts +97 -0
- package/src/plugins/builtin/cloud/terminal-relay/connect-section.tsx +57 -0
- package/src/plugins/builtin/cloud/terminal-relay/engine.ts +666 -0
- package/src/plugins/builtin/cloud/terminal-relay/grants.ts +137 -0
- package/src/plugins/builtin/cloud/terminal-relay/host.tsx +81 -0
- package/src/plugins/builtin/cloud/terminal-relay/indicator.tsx +82 -0
- package/src/plugins/builtin/cloud/terminal-relay/manifest.ts +234 -0
- package/src/plugins/builtin/cloud/terminal-relay/policy.ts +153 -0
- package/src/plugins/builtin/cloud/terminal-relay/prompts.tsx +168 -0
- package/src/plugins/builtin/cloud/terminal-relay/redact.ts +73 -0
- package/src/plugins/builtin/cloud/terminal-relay/summary.ts +62 -0
- package/src/plugins/builtin/cloud/upgrade-dialog.tsx +26 -2
- package/src/plugins/builtin/cloud/upgrade-personalization.ts +57 -0
- package/src/plugins/builtin/cloud/upgrade-tickers.ts +124 -0
- package/src/plugins/builtin/comment-letters/client.ts +1 -3
- package/src/plugins/builtin/company-kpis/client.ts +23 -8
- package/src/plugins/builtin/company-kpis/headless.ts +3 -1
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/company-kpis/screenshot-evidence.ts +10 -3
- package/src/plugins/builtin/composite-plugins.ts +107 -64
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +4 -4
- package/src/plugins/builtin/credit-boards/index.tsx +21 -21
- package/src/plugins/builtin/credit-boards/model.ts +0 -1
- package/src/plugins/builtin/credit-boards/pane.tsx +30 -0
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +4 -4
- package/src/plugins/builtin/debt-maturities/client.ts +37 -9
- package/src/plugins/builtin/debt-maturities/headless.ts +3 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +5 -4
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/data.ts +28 -7
- package/src/plugins/builtin/earnings-calls/headless.ts +3 -2
- package/src/plugins/builtin/earnings-calls/pane.tsx +10 -5
- package/src/plugins/builtin/econ/calendar-model.ts +42 -17
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/index.tsx +68 -22
- package/src/plugins/builtin/econ-statistics/headless.ts +12 -2
- package/src/plugins/builtin/econ-statistics/pane.tsx +1 -1
- package/src/plugins/builtin/econ-statistics/view.ts +17 -0
- package/src/plugins/builtin/executives/data.ts +17 -6
- package/src/plugins/builtin/executives/pane.tsx +16 -7
- package/src/plugins/builtin/filing-events/pane.tsx +22 -5
- package/src/plugins/builtin/fx-matrix/freshness.ts +65 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +22 -1
- package/src/plugins/builtin/fx-matrix/pairs.ts +1 -1
- package/src/plugins/builtin/fx-matrix/settings.ts +42 -2
- package/src/plugins/builtin/gpu/headless.ts +9 -3
- package/src/plugins/builtin/gpu/model.ts +14 -5
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/holders/beneficial-client.ts +29 -7
- package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
- package/src/plugins/builtin/holders/headless.ts +6 -1
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/insider/index.tsx +4 -4
- package/src/plugins/builtin/iv-history/charts.tsx +21 -7
- package/src/plugins/builtin/iv-history/client.ts +12 -2
- package/src/plugins/builtin/iv-history/format.ts +33 -2
- package/src/plugins/builtin/iv-history/headless.ts +22 -10
- package/src/plugins/builtin/iv-history/index.tsx +3 -3
- package/src/plugins/builtin/iv-history/model.ts +61 -13
- package/src/plugins/builtin/iv-history/pane.tsx +11 -3
- package/src/plugins/builtin/iv-history/screen-pane.tsx +42 -18
- package/src/plugins/builtin/jobs/pane.tsx +1 -1
- package/src/plugins/builtin/kelly-sizer/headless.ts +149 -0
- package/src/plugins/builtin/kelly-sizer/history.ts +80 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +2 -0
- package/src/plugins/builtin/kelly-sizer/pane.tsx +109 -39
- package/src/plugins/builtin/kelly-sizer/sections.tsx +14 -0
- package/src/plugins/builtin/kelly-sizer/types.ts +9 -1
- package/src/plugins/builtin/layout-manager/index.tsx +9 -1
- package/src/plugins/builtin/macro-day/client.ts +24 -0
- package/src/plugins/builtin/macro-day/headless.ts +11 -8
- package/src/plugins/builtin/macro-day/index.tsx +3 -0
- package/src/plugins/builtin/macro-day/model.ts +11 -6
- package/src/plugins/builtin/macro-day/pane.tsx +12 -8
- package/src/plugins/builtin/macro-day/release-table.ts +40 -0
- package/src/plugins/builtin/macro-day/releases.ts +62 -44
- package/src/plugins/builtin/market-heatmap/data.ts +25 -5
- package/src/plugins/builtin/market-heatmap/evidence.ts +90 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +144 -63
- package/src/plugins/builtin/market-heatmap/model.ts +94 -3
- package/src/plugins/builtin/market-heatmap/portfolio.ts +12 -2
- package/src/plugins/builtin/market-movers/headless.ts +31 -4
- package/src/plugins/builtin/market-movers/index.tsx +8 -1
- package/src/plugins/builtin/market-movers/model.ts +71 -17
- package/src/plugins/builtin/market-movers/table.ts +50 -23
- package/src/plugins/builtin/news/headless.ts +9 -2
- package/src/plugins/builtin/news/wire/news/footer.ts +10 -0
- package/src/plugins/builtin/openfda/client.ts +1 -1
- package/src/plugins/builtin/options/carry.ts +95 -0
- package/src/plugins/builtin/options/index.tsx +58 -5
- package/src/plugins/builtin/options/strike-window.ts +183 -0
- package/src/plugins/builtin/options/table.ts +55 -3
- package/src/plugins/builtin/options/types.ts +8 -1
- package/src/plugins/builtin/options/view.tsx +161 -12
- package/src/plugins/builtin/options-positioning/headless.ts +4 -2
- package/src/plugins/builtin/options-scenario/charts.tsx +2 -2
- package/src/plugins/builtin/options-scenario/client.ts +73 -17
- package/src/plugins/builtin/options-scenario/headless.ts +71 -24
- package/src/plugins/builtin/options-scenario/hedge.ts +284 -0
- package/src/plugins/builtin/options-scenario/index.tsx +9 -2
- package/src/plugins/builtin/options-scenario/model.ts +33 -0
- package/src/plugins/builtin/options-scenario/pane.tsx +40 -16
- package/src/plugins/builtin/pe-band/model.ts +2 -2
- package/src/plugins/builtin/pe-band/report-dates.ts +11 -7
- package/src/plugins/builtin/perps/equity-row.tsx +8 -5
- package/src/plugins/builtin/perps/model.ts +16 -5
- package/src/plugins/builtin/plugin-marketplace/model.ts +64 -21
- package/src/plugins/builtin/plugin-marketplace/packs.ts +118 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +344 -32
- package/src/plugins/builtin/plugin-marketplace/store.ts +7 -1
- package/src/plugins/builtin/plugin-module.ts +87 -4
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +71 -36
- package/src/plugins/builtin/portfolio-list/cli/render.ts +87 -18
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +2 -16
- package/src/plugins/builtin/portfolio-list/headless.ts +38 -7
- package/src/plugins/builtin/realized-vol/headless.ts +3 -0
- package/src/plugins/builtin/realized-vol/pane.tsx +1 -1
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +2 -2
- package/src/plugins/builtin/research/equity-diagnostic-pane.tsx +1 -1
- package/src/plugins/builtin/revenue-breakdown/client.ts +13 -6
- package/src/plugins/builtin/revenue-breakdown/headless.ts +4 -1
- package/src/plugins/builtin/revenue-breakdown/pane.tsx +7 -4
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/pane.tsx +1 -1
- package/src/plugins/builtin/risk-factors/data.ts +23 -9
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/risk-factors/pane.tsx +6 -5
- package/src/plugins/builtin/seasonality/pane.tsx +1 -1
- package/src/plugins/builtin/sec/index.tsx +5 -5
- package/src/plugins/builtin/sectors/client.ts +10 -2
- package/src/plugins/builtin/sectors/headless.ts +35 -6
- package/src/plugins/builtin/sectors/index.tsx +18 -1
- package/src/plugins/builtin/sectors/sector-model.ts +150 -41
- package/src/plugins/builtin/shared/command-tab.ts +51 -0
- package/src/plugins/builtin/shared/headless-market-data.ts +33 -1
- package/src/plugins/builtin/shared/members-menu.tsx +4 -4
- package/src/plugins/builtin/shared/report-freshness.ts +50 -2
- package/src/plugins/builtin/shared/ticker-request.ts +25 -1
- package/src/plugins/builtin/shared/use-quote-board.ts +25 -5
- package/src/plugins/builtin/shared/volatility/options.ts +1 -46
- package/src/plugins/builtin/short-interest/index.tsx +19 -4
- package/src/plugins/builtin/short-interest/surface.tsx +4 -4
- package/src/plugins/builtin/short-volume/index.tsx +1 -10
- package/src/plugins/builtin/supply-chain/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/headless.ts +43 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +4 -5
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +94 -6
- package/src/plugins/builtin/treasury-auctions/cache.ts +5 -2
- package/src/plugins/builtin/treasury-auctions/client.ts +22 -8
- package/src/plugins/builtin/treasury-auctions/headless.ts +40 -4
- package/src/plugins/builtin/treasury-auctions/index.tsx +11 -9
- package/src/plugins/builtin/treasury-auctions/model.ts +197 -30
- package/src/plugins/builtin/treasury-auctions/pane.tsx +35 -11
- package/src/plugins/builtin/treasury-auctions/types.ts +11 -1
- package/src/plugins/builtin/vol-surface/charts.tsx +1 -3
- package/src/plugins/builtin/vol-surface/client.ts +6 -2
- package/src/plugins/builtin/vol-surface/evidence.ts +11 -6
- package/src/plugins/builtin/vol-surface/headless.ts +124 -20
- package/src/plugins/builtin/vol-surface/model.ts +23 -11
- package/src/plugins/builtin/vol-surface/pane.tsx +38 -27
- package/src/plugins/builtin/vol-surface/tables.ts +124 -0
- package/src/plugins/builtin/world-indices/client.ts +22 -4
- package/src/plugins/builtin/world-indices/headless.ts +54 -27
- package/src/plugins/builtin/world-indices/model.ts +4 -2
- package/src/plugins/builtin/world-indices/table.tsx +13 -2
- package/src/plugins/builtin/world-venue-map/geo-series.ts +1 -1
- package/src/plugins/builtin/world-venue-map/index.ts +1 -1
- package/src/plugins/builtin/yield-curve/index.tsx +1 -1
- package/src/plugins/builtin-editorial.ts +126 -0
- package/src/plugins/catalog-backend.ts +16 -9
- package/src/plugins/catalog-browser.ts +127 -63
- package/src/plugins/catalog-manifest.ts +73 -40
- package/src/plugins/catalog-ui.ts +26 -6
- package/src/plugins/extracted-plugins.ts +7 -1
- package/src/plugins/ownership.ts +150 -7
- package/src/plugins/plugin-functions.ts +54 -0
- package/src/plugins/plugin-off.ts +36 -0
- package/src/plugins/registry/host-actions.ts +16 -0
- package/src/plugins/registry/index.ts +84 -24
- package/src/plugins/registry/plugin-state.ts +25 -1
- package/src/plugins/runtime/context.tsx +29 -0
- package/src/react/input.ts +48 -1
- package/src/remote/app-host.tsx +5 -0
- package/src/remote/controller.ts +3 -53
- package/src/remote/matching.ts +69 -0
- package/src/remote/schema.ts +1 -1
- package/src/remote/semantic-tree.tsx +14 -0
- package/src/remote/types.ts +2 -0
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/query-bar.tsx +59 -32
- package/src/renderers/dom/host/box.tsx +1 -1
- package/src/renderers/dom/host/drag-shield.ts +17 -6
- package/src/renderers/dom/host/live-frame.ts +10 -2
- package/src/renderers/dom/input-host.tsx +6 -1
- package/src/renderers/dom/styles.css +13 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +5 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +12 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -1
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +101 -5
- package/src/renderers/electrobun/view/main.tsx +4 -1
- package/src/renderers/opentui/input-host.tsx +6 -1
- package/src/renderers/opentui/start.tsx +9 -0
- package/src/shares/portable-layout.ts +16 -6
- package/src/sources/gloomberb-cloud/index.ts +19 -10
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -1
- package/src/sources/listing-symbols.ts +11 -5
- package/src/sources/provider-errors.ts +88 -3
- package/src/sources/provider-router/cached-routes.ts +8 -4
- package/src/sources/provider-router/financial-routes.ts +11 -5
- package/src/sources/provider-router/financials.ts +42 -15
- package/src/sources/provider-router/history.ts +27 -11
- package/src/sources/provider-router/index.ts +1 -1
- package/src/sources/provider-router/primary.ts +23 -5
- package/src/sources/sec-edgar.ts +68 -8
- package/src/sync/core-contributors.ts +4 -3
- package/src/test-support/cli-context.ts +2 -1
- package/src/test-support/data-provider.ts +34 -0
- package/src/test-support/shell-pane-keys.tsx +1 -0
- package/src/theme/series-colors.ts +46 -0
- package/src/theme/themes.ts +149 -0
- package/src/tickers/search/index.ts +22 -2
- package/src/tickers/search/ranking.ts +45 -1
- package/src/tickers/search/types.ts +3 -1
- package/src/time-series/resolve.ts +1 -14
- package/src/types/config.ts +61 -1
- package/src/types/headless.ts +11 -0
- package/src/types/instrument.ts +8 -0
- package/src/types/plugin.ts +49 -3
- package/src/ui/host.tsx +8 -4
- package/src/ui/index.tsx +2 -0
- package/src/utils/cli-output.ts +14 -1
- package/src/utils/exchanges.ts +13 -0
- package/src/utils/format.ts +2 -1
- package/src/utils/fx-market-hours.ts +34 -0
- package/src/utils/option-expiry.ts +126 -0
- package/src/utils/price-history.ts +39 -6
- package/src/utils/rpc-timeout-error.ts +6 -2
- package/src/utils/sec-eps-basis.ts +159 -30
- package/src/utils/sec.ts +38 -34
- package/src/utils/throttled-fetch.ts +51 -22
|
@@ -6,6 +6,7 @@ import type { QueryEntry } from "../result-types";
|
|
|
6
6
|
import { resolveCurrencyUnit } from "../../utils/currency-units";
|
|
7
7
|
import { isFiniteNumber } from "../../utils/guards";
|
|
8
8
|
import { getCompletedRegularSessionDisplay, getSessionMoveDisplay, type ExtendedSession } from "../market/status";
|
|
9
|
+
import { quoteTradingDay } from "./day-range";
|
|
9
10
|
|
|
10
11
|
const STREAM_FRESHNESS_MS = 2 * 60_000;
|
|
11
12
|
const STREAM_CONNECTING_GRACE_MS = 15_000;
|
|
@@ -39,6 +40,21 @@ export interface ScreenerQuoteOverlayOptions {
|
|
|
39
40
|
* two different days.
|
|
40
41
|
*/
|
|
41
42
|
extendedSessions?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Only the regular session, never an extended print, whatever
|
|
45
|
+
* `extendedSessions` says: the live quote while the session trades, then
|
|
46
|
+
* the completed session's close and its move, given as
|
|
47
|
+
* `regularChangePercent`. A quote that cannot say which close that is
|
|
48
|
+
* leaves its row as it was.
|
|
49
|
+
*/
|
|
50
|
+
regularSession?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* With `regularSession`, the completed regular session the rows already
|
|
53
|
+
* are (a New York date) when they were taken outside the session. A quote
|
|
54
|
+
* replaces such a row only with that session's close or a later one, and a
|
|
55
|
+
* live quote only once a later session trades.
|
|
56
|
+
*/
|
|
57
|
+
completedSessionDate?: string | null;
|
|
42
58
|
}
|
|
43
59
|
|
|
44
60
|
export interface ScreenerQuoteFreshness {
|
|
@@ -83,14 +99,18 @@ function overlayCurrency(row: ScreenerQuoteRow, quote: Quote): string {
|
|
|
83
99
|
* entries map, but most rows' quotes did not move; returning the same row
|
|
84
100
|
* object for those lets memoized table rows skip the render.
|
|
85
101
|
*/
|
|
86
|
-
const overlayCache = new WeakMap<object, { quote: Quote;
|
|
102
|
+
const overlayCache = new WeakMap<object, { quote: Quote; basis: string; row: ScreenerQuoteRow }>();
|
|
87
103
|
|
|
88
104
|
export function overlayScreenerQuoteEntries<T extends ScreenerQuoteRow>(
|
|
89
105
|
rows: readonly T[],
|
|
90
106
|
entries: ReadonlyMap<string, QueryEntry<Quote>>,
|
|
91
107
|
options: ScreenerQuoteOverlayOptions = {},
|
|
92
108
|
): T[] {
|
|
93
|
-
const
|
|
109
|
+
const regular = options.regularSession === true;
|
|
110
|
+
const completedSessionDate = regular ? options.completedSessionDate ?? null : null;
|
|
111
|
+
const extended = !regular && options.extendedSessions === true;
|
|
112
|
+
// The cached row is reused only for the same quote under the same options.
|
|
113
|
+
const basis = regular ? `regular:${completedSessionDate ?? ""}` : extended ? "extended" : "plain";
|
|
94
114
|
return rows.map((row) => {
|
|
95
115
|
const quote = resolveEntryData(entries.get(quoteKey(row)));
|
|
96
116
|
if (!quote || !isFiniteNumber(quote.price)) return row;
|
|
@@ -102,9 +122,11 @@ export function overlayScreenerQuoteEntries<T extends ScreenerQuoteRow>(
|
|
|
102
122
|
return row;
|
|
103
123
|
}
|
|
104
124
|
const cached = overlayCache.get(row);
|
|
105
|
-
if (cached?.quote === quote && cached.
|
|
106
|
-
const overlaid =
|
|
107
|
-
|
|
125
|
+
if (cached?.quote === quote && cached.basis === basis) return cached.row as T;
|
|
126
|
+
const overlaid = regular
|
|
127
|
+
? overlayRegularSessionQuote(row, quote, completedSessionDate)
|
|
128
|
+
: extended ? overlaySessionQuote(row, quote) : overlayQuote(row, quote);
|
|
129
|
+
overlayCache.set(row, { quote, basis, row: overlaid });
|
|
108
130
|
return overlaid;
|
|
109
131
|
});
|
|
110
132
|
}
|
|
@@ -149,6 +171,35 @@ function overlaySessionQuote<T extends ScreenerQuoteRow>(row: T, quote: Quote):
|
|
|
149
171
|
};
|
|
150
172
|
}
|
|
151
173
|
|
|
174
|
+
function overlayRegularSessionQuote<T extends ScreenerQuoteRow>(row: T, quote: Quote, completedSessionDate: string | null): T {
|
|
175
|
+
const state = quote.marketState;
|
|
176
|
+
if (state == null || state === "REGULAR") {
|
|
177
|
+
// On a board of a completed session, only a session that trades after it is news.
|
|
178
|
+
if (completedSessionDate) {
|
|
179
|
+
const day = state === "REGULAR" ? quoteTradingDay(quote) : null;
|
|
180
|
+
if (day == null || day <= completedSessionDate) return row;
|
|
181
|
+
}
|
|
182
|
+
return overlayQuote(row, quote);
|
|
183
|
+
}
|
|
184
|
+
const display = getCompletedRegularSessionDisplay(quote);
|
|
185
|
+
const closeDate = quote.regularCloseSessionDate;
|
|
186
|
+
if (
|
|
187
|
+
!display
|
|
188
|
+
|| !(isFiniteNumber(display.price) && display.price > 0)
|
|
189
|
+
|| !isFiniteNumber(display.changePercent)
|
|
190
|
+
|| (completedSessionDate != null && closeDate != null && closeDate < completedSessionDate)
|
|
191
|
+
) {
|
|
192
|
+
return row;
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
...overlayQuote(row, quote),
|
|
196
|
+
price: display.price,
|
|
197
|
+
change: isFiniteNumber(display.change) ? display.change : null,
|
|
198
|
+
changePercent: display.changePercent,
|
|
199
|
+
regularChangePercent: display.changePercent,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
152
203
|
function freshQuote(
|
|
153
204
|
entry: QueryEntry<Quote> | undefined,
|
|
154
205
|
freshness: ScreenerQuoteFreshness,
|
|
@@ -22,6 +22,7 @@ export function useAccountManagementKeyboard({
|
|
|
22
22
|
draftRef,
|
|
23
23
|
fieldOrder,
|
|
24
24
|
focused,
|
|
25
|
+
openAssistants,
|
|
25
26
|
openPasswordDialog,
|
|
26
27
|
openPortfolioDialog,
|
|
27
28
|
openUpgrade,
|
|
@@ -38,6 +39,7 @@ export function useAccountManagementKeyboard({
|
|
|
38
39
|
/** The active tab's ring, in reading order. */
|
|
39
40
|
fieldOrder: readonly AccountFieldKey[];
|
|
40
41
|
focused: boolean;
|
|
42
|
+
openAssistants: () => void;
|
|
41
43
|
openPasswordDialog: () => void;
|
|
42
44
|
openPortfolioDialog: () => Promise<void>;
|
|
43
45
|
openUpgrade: () => void;
|
|
@@ -52,6 +54,7 @@ export function useAccountManagementKeyboard({
|
|
|
52
54
|
sharedPortfolioId: () => { void openPortfolioDialog(); },
|
|
53
55
|
passwordAction: openPasswordDialog,
|
|
54
56
|
upgradeAction: openUpgrade,
|
|
57
|
+
assistantsAction: openAssistants,
|
|
55
58
|
deleteAccountAction: () => { void deleteAccount(); },
|
|
56
59
|
emailAlertsOffAction: () => { void turnOffEmailAlerts(); },
|
|
57
60
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createPaneRequestChannel } from "../shared/pane-request";
|
|
2
2
|
|
|
3
|
-
export type AccountManagementTab = "profile" | "emails" | "calendar" | "pro" | "teams" | "advanced";
|
|
3
|
+
export type AccountManagementTab = "profile" | "emails" | "calendar" | "pro" | "teams" | "agents" | "advanced";
|
|
4
4
|
|
|
5
5
|
const tabRequests = createPaneRequestChannel<AccountManagementTab>();
|
|
6
6
|
|
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
type StatItem,
|
|
13
13
|
} from "../../../components";
|
|
14
14
|
import { useAppSelector, usePaneAppConfig } from "../../../state/app/context";
|
|
15
|
+
import { useViewport } from "../../../react/input";
|
|
16
|
+
import { displayWidth } from "../../../utils/format";
|
|
15
17
|
import { useChartQueries, useFxRatesMap } from "../../../market-data/hooks";
|
|
16
18
|
import { buildPortfolioFinancialsMap } from "../../../market-data/portfolio-financials";
|
|
17
19
|
import { useLiveTickerFinancialsMap, useSampledValue } from "../../../state/hooks/live-ticker-financials";
|
|
@@ -33,7 +35,9 @@ import { apiClient, type AccountProfile, type CloudPricing } from "../../../api-
|
|
|
33
35
|
import { chatController } from "../chat/controller";
|
|
34
36
|
import { SignInWall } from "../cloud/auth-actions";
|
|
35
37
|
import { loadUpgradeOffer } from "../cloud/upgrade-dialog";
|
|
38
|
+
import { openMcpConnectDialog } from "../cloud/mcp-connect/dialog";
|
|
36
39
|
import { TeamsAccountTab } from "../cloud/team/acm-tab";
|
|
40
|
+
import { AgentsAccountTab } from "../cloud/terminal-relay/acm-tab";
|
|
37
41
|
import {
|
|
38
42
|
CheckboxRow,
|
|
39
43
|
FieldRow,
|
|
@@ -87,6 +91,7 @@ const ACCOUNT_TAB_DEFS: Array<{ label: string; value: AccountManagementTab }> =
|
|
|
87
91
|
{ label: "Calendar", value: "calendar" },
|
|
88
92
|
{ label: "Pro", value: "pro" },
|
|
89
93
|
{ label: "Teams", value: "teams" },
|
|
94
|
+
{ label: "Agents", value: "agents" },
|
|
90
95
|
{ label: "Advanced", value: "advanced" },
|
|
91
96
|
];
|
|
92
97
|
|
|
@@ -114,7 +119,9 @@ const ACCOUNT_TAB_FIELD_ORDER: Record<AccountManagementTab, AccountFieldKey[]> =
|
|
|
114
119
|
pro: ["upgradeAction"],
|
|
115
120
|
// The Teams tab owns its own keyboard handling (a list, not form fields).
|
|
116
121
|
teams: [],
|
|
117
|
-
|
|
122
|
+
// Remote assistants allowed to drive this terminal; a list with its own footer.
|
|
123
|
+
agents: [],
|
|
124
|
+
advanced: ["assistantsAction", "passwordAction", "deleteAccountAction"],
|
|
118
125
|
};
|
|
119
126
|
|
|
120
127
|
const PROFILE_PREVIEW_SAMPLE_MS = 10_000;
|
|
@@ -562,6 +569,12 @@ export function AccountManagementPane({ focused, width, height }: PaneProps) {
|
|
|
562
569
|
}).catch(() => {});
|
|
563
570
|
}, [busy, dialog]);
|
|
564
571
|
|
|
572
|
+
const viewport = useViewport();
|
|
573
|
+
const openAssistants = useCallback(() => {
|
|
574
|
+
setActiveField("assistantsAction");
|
|
575
|
+
void openMcpConnectDialog(dialog, { viewportWidth: viewport.width });
|
|
576
|
+
}, [dialog, viewport.width]);
|
|
577
|
+
|
|
565
578
|
const openPortfolioPicker = useCallback(async () => {
|
|
566
579
|
setActiveField("sharedPortfolioId");
|
|
567
580
|
portfolioSelectRef.current?.open();
|
|
@@ -705,8 +718,8 @@ export function AccountManagementPane({ focused, width, height }: PaneProps) {
|
|
|
705
718
|
|
|
706
719
|
useAccountManagementFooter({
|
|
707
720
|
busy,
|
|
708
|
-
// The Calendar
|
|
709
|
-
enabled: activeTab !== "calendar",
|
|
721
|
+
// The Calendar and Agents tabs save nothing and show their own status.
|
|
722
|
+
enabled: activeTab !== "calendar" && activeTab !== "agents",
|
|
710
723
|
hasSession,
|
|
711
724
|
message,
|
|
712
725
|
saveProfile,
|
|
@@ -719,7 +732,8 @@ export function AccountManagementPane({ focused, width, height }: PaneProps) {
|
|
|
719
732
|
draftRef,
|
|
720
733
|
fieldOrder,
|
|
721
734
|
// Behind the sign-in wall the fields are not there, and Enter is the wall's.
|
|
722
|
-
focused: focused && activeTab !== "teams" && activeTab !== "calendar" && (hasSession || apiClient.isSignedIn()),
|
|
735
|
+
focused: focused && activeTab !== "teams" && activeTab !== "calendar" && activeTab !== "agents" && (hasSession || apiClient.isSignedIn()),
|
|
736
|
+
openAssistants,
|
|
723
737
|
openPasswordDialog,
|
|
724
738
|
openPortfolioDialog: openPortfolioPicker,
|
|
725
739
|
openUpgrade,
|
|
@@ -927,8 +941,21 @@ export function AccountManagementPane({ focused, width, height }: PaneProps) {
|
|
|
927
941
|
{activeTab === "teams" ? (
|
|
928
942
|
<TeamsAccountTab focused={focused} width={contentWidth} />
|
|
929
943
|
) : null}
|
|
944
|
+
{activeTab === "agents" ? (
|
|
945
|
+
<AgentsAccountTab focused={focused} width={contentWidth} />
|
|
946
|
+
) : null}
|
|
930
947
|
{activeTab === "advanced" ? (
|
|
931
948
|
<>
|
|
949
|
+
<Box ref={fieldNodeRef("assistantsAction")} flexDirection="row" gap={1} alignItems="center">
|
|
950
|
+
{/* The only label on the tab, so it takes the room it needs, with the terminal's "> " marker. */}
|
|
951
|
+
<FieldLabel label={t("Assistants (MCP)")} active={activeField === "assistantsAction"} width={displayWidth(t("Assistants (MCP)")) + 3} />
|
|
952
|
+
<Button
|
|
953
|
+
label={t("Connect an AI Assistant")}
|
|
954
|
+
active={activeField === "assistantsAction"}
|
|
955
|
+
onPress={openAssistants}
|
|
956
|
+
disabled={!!busy}
|
|
957
|
+
/>
|
|
958
|
+
</Box>
|
|
932
959
|
<Box flexDirection="row" gap={1}>
|
|
933
960
|
<Button
|
|
934
961
|
label={t("Change Password")}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { DataProvider } from "../../../types/data-provider";
|
|
2
2
|
import type { Quote } from "../../../types/financials";
|
|
3
3
|
import { getActiveQuoteDisplay } from "../../../market-data/market/status";
|
|
4
|
+
import { isQuoteStaleForCurrentSession } from "../../../market-data/quotes/freshness";
|
|
4
5
|
import type { AlertRule } from "./types";
|
|
5
6
|
|
|
6
7
|
export function normalizeAlertSymbol(value: string | null | undefined): string {
|
|
@@ -21,6 +22,8 @@ export async function resolveAlertQuote(
|
|
|
21
22
|
exchange = "",
|
|
22
23
|
): Promise<Quote> {
|
|
23
24
|
const quote = await marketData.getQuote(symbol, exchange);
|
|
25
|
+
// A last known price is not the price now: it must neither trigger an alert nor set its baseline.
|
|
26
|
+
if (isQuoteStaleForCurrentSession(quote)) throw new Error(`The quote for "${symbol}" is stale.`);
|
|
24
27
|
const display = getActiveQuoteDisplay(quote);
|
|
25
28
|
if (!display || !Number.isFinite(display.price)) {
|
|
26
29
|
throw new Error(`No quote found for "${symbol}".`);
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
riskCoverageShortfall,
|
|
12
12
|
riskCoverageText,
|
|
13
13
|
RISK_VIEWS,
|
|
14
|
+
riskEvidence,
|
|
14
15
|
riskPercentile,
|
|
15
16
|
riskValue,
|
|
16
17
|
type PortfolioRiskModel,
|
|
@@ -39,7 +40,8 @@ function displayRow(row: RiskDisplayRow) {
|
|
|
39
40
|
value: riskValue(row),
|
|
40
41
|
percentile: riskPercentile(row),
|
|
41
42
|
asOf: row.asOf?.slice(0, 10) ?? null,
|
|
42
|
-
detail: row
|
|
43
|
+
detail: riskEvidence(row),
|
|
44
|
+
...(row.amount != null ? { amount: row.amount, currency: row.amountCurrency ?? null } : {}),
|
|
43
45
|
};
|
|
44
46
|
}
|
|
45
47
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { portfolioOptionGreeks } from "./risk-options";
|
|
2
2
|
import type { Portfolio, TickerRecord } from "../../../types/ticker";
|
|
3
|
-
import { formatNumber } from "../../../utils/format";
|
|
3
|
+
import { formatCurrency, formatNumber } from "../../../utils/format";
|
|
4
4
|
import { resolveCurrencyUnit } from "../../../utils/currency-units";
|
|
5
5
|
import {
|
|
6
6
|
getPortfolioPositionMetrics,
|
|
@@ -97,7 +97,13 @@ export interface RiskDisplayRow {
|
|
|
97
97
|
interaction?: number;
|
|
98
98
|
/** Holdings rows only: why the holding is outside the covered basket. */
|
|
99
99
|
leftOut?: string;
|
|
100
|
+
/** VaR and expected shortfall only: the loss `value` is on the covered basket's current value (`amountBase`), in `amountCurrency`. */
|
|
101
|
+
amount?: number;
|
|
102
|
+
amountCurrency?: string;
|
|
103
|
+
amountBase?: number;
|
|
100
104
|
}
|
|
105
|
+
/** The tail-loss rows, which also read in money on the covered basket. */
|
|
106
|
+
const TAIL_ROW_IDS: ReadonlySet<string> = new Set(["var", "es"]);
|
|
101
107
|
/**
|
|
102
108
|
* Below half of market value the qualifying holdings are not most of the
|
|
103
109
|
* account, so a basket estimate would mostly describe something other than it;
|
|
@@ -565,12 +571,19 @@ export function buildPortfolioRisk(
|
|
|
565
571
|
: null;
|
|
566
572
|
warnings.push(...(greeks?.warnings ?? []));
|
|
567
573
|
const rows: Record<RiskView, RiskDisplayRow[]> = {
|
|
568
|
-
risk: metrics.map((row) =>
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
+
risk: metrics.map((row) => {
|
|
575
|
+
// VaR and expected shortfall are a one-day loss on the covered basket's current value, in the portfolio currency.
|
|
576
|
+
const tail = TAIL_ROW_IDS.has(row.id) && row.value != null && coverage.coveredValue > 0;
|
|
577
|
+
return {
|
|
578
|
+
...row,
|
|
579
|
+
percentile: row.rank.percentile,
|
|
580
|
+
// The method (price returns at fixed current weights) is in docs; the footer keeps room for coverage.
|
|
581
|
+
detail: `${row.samples} sessions`,
|
|
582
|
+
...(tail
|
|
583
|
+
? { amount: (row.value! / 100) * coverage.coveredValue, amountCurrency: coverage.currency, amountBase: coverage.coveredValue }
|
|
584
|
+
: {}),
|
|
585
|
+
};
|
|
586
|
+
}),
|
|
574
587
|
factors: factors.map((row) => ({
|
|
575
588
|
...row,
|
|
576
589
|
percentile: row.rank.percentile,
|
|
@@ -770,6 +783,22 @@ export const riskValue = (row: RiskDisplayRow) =>
|
|
|
770
783
|
? "left out"
|
|
771
784
|
: row.value == null
|
|
772
785
|
? "--"
|
|
773
|
-
: `${row.value.toFixed(2)}${row.unit === "%" ? "%" : ` ${row.unit}`}`;
|
|
786
|
+
: `${row.value.toFixed(2)}${row.unit === "%" ? "%" : ` ${row.unit}`}${riskAmount(row)}`;
|
|
787
|
+
/** "220,476 USD basket": the value a tail loss in money is taken on, or null for a row without one. */
|
|
788
|
+
export function riskAmountBase(row: Pick<RiskDisplayRow, "amountBase" | "amountCurrency">): string | null {
|
|
789
|
+
return row.amountBase != null && Number.isFinite(row.amountBase) && row.amountCurrency
|
|
790
|
+
? `${formatNumber(row.amountBase, 0)} ${row.amountCurrency} basket`
|
|
791
|
+
: null;
|
|
792
|
+
}
|
|
793
|
+
/** A row's evidence with the basket a tail loss in money is taken on. */
|
|
794
|
+
export function riskEvidence(row: RiskDisplayRow): string {
|
|
795
|
+
const base = riskAmountBase(row);
|
|
796
|
+
return base ? `${row.detail}; of ${base}` : row.detail;
|
|
797
|
+
}
|
|
798
|
+
/** " ($5,735)": a tail loss in money beside its percent. */
|
|
799
|
+
function riskAmount(row: RiskDisplayRow): string {
|
|
800
|
+
if (row.amount == null || !Number.isFinite(row.amount) || !row.amountCurrency) return "";
|
|
801
|
+
return ` (${formatCurrency(row.amount, row.amountCurrency, 0)})`;
|
|
802
|
+
}
|
|
774
803
|
export const riskPercentile = (row: RiskDisplayRow) =>
|
|
775
804
|
row.percentile == null ? "--" : String(Math.round(row.percentile));
|
|
@@ -56,6 +56,8 @@ import {
|
|
|
56
56
|
riskCoverageShortfall,
|
|
57
57
|
riskCoverageText,
|
|
58
58
|
riskPercentile,
|
|
59
|
+
riskAmountBase,
|
|
60
|
+
riskEvidence,
|
|
59
61
|
riskValue,
|
|
60
62
|
RISK_VIEWS,
|
|
61
63
|
type PortfolioRiskModel,
|
|
@@ -178,7 +180,7 @@ function RiskDetail({
|
|
|
178
180
|
detail: row.asOf?.slice(0, 10) ?? "Source date unavailable",
|
|
179
181
|
},
|
|
180
182
|
...(row.percentile != null ? [{ id: "percentile", label: "Pctl 1Y", value: riskPercentile(row) }] : []),
|
|
181
|
-
{ id: "evidence", label: "Evidence", value: row
|
|
183
|
+
{ id: "evidence", label: "Evidence", value: riskEvidence(row), wide: true },
|
|
182
184
|
];
|
|
183
185
|
const statRows = statGridRows(items, width);
|
|
184
186
|
const points = (row.history ?? []).map((point, index) => ({
|
|
@@ -458,7 +460,8 @@ export function PortfolioRiskPane({ focused, width, height }: PaneProps) {
|
|
|
458
460
|
: [
|
|
459
461
|
...(basketBody ? [] : [
|
|
460
462
|
...(asOfDate && sharedEvidence ? [sharedEvidence] : []),
|
|
461
|
-
|
|
463
|
+
// The value the tail losses in money are taken on names the currency too.
|
|
464
|
+
rows.map(riskAmountBase).find(Boolean) ?? model.portfolio.currency,
|
|
462
465
|
...(asOfDate ? [asOfDate] : []),
|
|
463
466
|
...(EVIDENCE_VIEWS.has(view) && model.evidence ? [model.evidence.source] : []),
|
|
464
467
|
]),
|
|
@@ -576,7 +579,8 @@ export function PortfolioRiskPane({ focused, width, height }: PaneProps) {
|
|
|
576
579
|
{
|
|
577
580
|
id: "value",
|
|
578
581
|
label: valueHeader,
|
|
579
|
-
|
|
582
|
+
// A tail loss reads in money beside its percent: 2.60% ($5,735).
|
|
583
|
+
width: sharedUnit || attributionParts ? Math.max(9, valueHeader.length) : rows.some((row) => row.amount != null) ? 18 : 16,
|
|
580
584
|
align: "right",
|
|
581
585
|
},
|
|
582
586
|
...(attributionParts
|
|
@@ -601,7 +605,7 @@ export function PortfolioRiskPane({ focused, width, height }: PaneProps) {
|
|
|
601
605
|
]
|
|
602
606
|
: []),
|
|
603
607
|
],
|
|
604
|
-
[width, view, valueHeader, sharedUnit, attributionParts, sharedEvidence, showPercentile, sharedDate],
|
|
608
|
+
[width, view, valueHeader, sharedUnit, attributionParts, sharedEvidence, showPercentile, sharedDate, rows],
|
|
605
609
|
);
|
|
606
610
|
const selectView = (value: string) => {
|
|
607
611
|
setView(value);
|
|
@@ -146,7 +146,7 @@ export function BacktestPane({ width, height, focused }: PaneProps) {
|
|
|
146
146
|
}), [history.loading, result, view, paneId, symbol]);
|
|
147
147
|
|
|
148
148
|
const { strip: tabStrip, rows: tabRows } = usePaneTabs(symbol ? { tabs: TABS, activeValue: view, onSelect: setView, focused, dense: true } : null);
|
|
149
|
-
if (!symbol) return <EmptyState title="
|
|
149
|
+
if (!symbol) return <EmptyState title="Select a ticker." hint="Open BT with a symbol, for example BT AAPL." />;
|
|
150
150
|
const bodyHeight = Math.max(4, height - 1 - tabRows);
|
|
151
151
|
const wide = width - SUMMARY_WIDTH - 1 >= MIN_WIDE_CHART_COLS;
|
|
152
152
|
const summaryTable = (tableWidth: number, tableHeight: number, before?: ReactNode) => (
|
|
@@ -14,6 +14,7 @@ import { formatCurrency, truncateToDisplayWidth } from "../../../utils/format";
|
|
|
14
14
|
import { t, tf } from "../../../i18n";
|
|
15
15
|
import type { BrokerProfileRow } from "./model";
|
|
16
16
|
import type { BrokerManagerMessage } from "./pane-actions";
|
|
17
|
+
import { signInEndNote } from "./sign-in-end";
|
|
17
18
|
import { stateColor } from "./table";
|
|
18
19
|
|
|
19
20
|
export type BrokerEditKey = "label" | "enabled" | string;
|
|
@@ -277,6 +278,9 @@ export function BrokerDetailContent({
|
|
|
277
278
|
>
|
|
278
279
|
{detailStatusMessage}
|
|
279
280
|
</Text>
|
|
281
|
+
{row.signInEndsAt !== null && (
|
|
282
|
+
<Text fg={colors.warning} width={width} wrapText>{signInEndNote(row.signInEndsAt)}</Text>
|
|
283
|
+
)}
|
|
280
284
|
<Text fg={colors.textMuted}>{tf("Last sync {time}", { time: formatRelativeAge(row.lastSyncedAt) })}</Text>
|
|
281
285
|
<Text fg={colors.textMuted}>{tf("Status updated {time}", { time: formatRelativeAge(row.updatedAt) })}</Text>
|
|
282
286
|
<Box height={1} />
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
type BrokerProfileRow,
|
|
38
38
|
} from "./model";
|
|
39
39
|
import { useBrokerManagerActions } from "./pane-actions";
|
|
40
|
+
import { useSignInEnds } from "./sign-in-end";
|
|
40
41
|
import {
|
|
41
42
|
buildBrokerColumns,
|
|
42
43
|
renderBrokerCell,
|
|
@@ -78,9 +79,10 @@ export function BrokersPane({ focused, width, height }: PaneProps) {
|
|
|
78
79
|
|
|
79
80
|
// Signed-in profiles take their broker's name from the connector list.
|
|
80
81
|
const signedInBrokers = useSyncExternalStore(subscribeSignedInBrokers, getSignedInBrokers, getSignedInBrokers);
|
|
82
|
+
const { ends: signInEnds, reload: reloadSignInEnds } = useSignInEnds(config.brokerInstances);
|
|
81
83
|
const rows = useMemo(
|
|
82
|
-
() => buildBrokerProfileRows(config, adapters, brokerAccounts),
|
|
83
|
-
[adapters, brokerAccounts, config, language, signedInBrokers, statusVersion],
|
|
84
|
+
() => buildBrokerProfileRows(config, adapters, brokerAccounts, signInEnds),
|
|
85
|
+
[adapters, brokerAccounts, config, language, signInEnds, signedInBrokers, statusVersion],
|
|
84
86
|
);
|
|
85
87
|
const selectedIndex = Math.max(0, rows.findIndex((row) => row.id === selectedId));
|
|
86
88
|
const selectedRow = rows[selectedIndex] ?? null;
|
|
@@ -107,9 +109,11 @@ export function BrokersPane({ focused, width, height }: PaneProps) {
|
|
|
107
109
|
if (!editKeys.includes(activeEditKey)) setActiveEditKey(editKeys[0] ?? "label");
|
|
108
110
|
}, [activeEditKey, editDraft, editKeys]);
|
|
109
111
|
|
|
112
|
+
// After a connect or sync, which can renew a sign-in or find it ended.
|
|
110
113
|
const refreshStatuses = useCallback(() => {
|
|
111
114
|
setStatusVersion((version) => version + 1);
|
|
112
|
-
|
|
115
|
+
reloadSignInEnds();
|
|
116
|
+
}, [reloadSignInEnds]);
|
|
113
117
|
|
|
114
118
|
const {
|
|
115
119
|
busy,
|
|
@@ -3,6 +3,7 @@ import type { AppConfig, BrokerInstanceConfig } from "../../../types/config";
|
|
|
3
3
|
import type { BrokerAccount } from "../../../types/trading";
|
|
4
4
|
import { formatCurrency } from "../../../utils/format";
|
|
5
5
|
import { t } from "../../../i18n";
|
|
6
|
+
import type { SignInEnds } from "./sign-in-end";
|
|
6
7
|
|
|
7
8
|
export type BrokerDisplayState =
|
|
8
9
|
| "disabled"
|
|
@@ -24,6 +25,8 @@ export interface BrokerProfileRow {
|
|
|
24
25
|
accountCount: number;
|
|
25
26
|
accountSummary: string;
|
|
26
27
|
accountIds: string[];
|
|
28
|
+
/** When the broker ends this profile's sign-in, once Gloom says it is time to connect again. */
|
|
29
|
+
signInEndsAt: number | null;
|
|
27
30
|
instance: BrokerInstanceConfig;
|
|
28
31
|
adapter: BrokerAdapter | null;
|
|
29
32
|
}
|
|
@@ -107,6 +110,7 @@ export function buildBrokerProfileRows(
|
|
|
107
110
|
config: AppConfig,
|
|
108
111
|
adapters: ReadonlyMap<string, BrokerAdapter | null>,
|
|
109
112
|
brokerAccounts: Record<string, BrokerAccount[]>,
|
|
113
|
+
signInEnds: SignInEnds = new Map(),
|
|
110
114
|
): BrokerProfileRow[] {
|
|
111
115
|
return config.brokerInstances.map((instance) => {
|
|
112
116
|
const adapter = adapters.get(instance.brokerType) ?? null;
|
|
@@ -139,6 +143,10 @@ export function buildBrokerProfileRows(
|
|
|
139
143
|
accountCount: accounts.length,
|
|
140
144
|
accountSummary: summarizeBrokerAccounts(accounts),
|
|
141
145
|
accountIds: accounts.map((account) => account.accountId),
|
|
146
|
+
// Say nothing next to a failure or a profile that does not connect.
|
|
147
|
+
signInEndsAt: state.state === "error" || state.state === "disabled" || state.state === "unavailable"
|
|
148
|
+
? null
|
|
149
|
+
: signInEnds.get(instance.id) ?? null,
|
|
142
150
|
instance,
|
|
143
151
|
adapter,
|
|
144
152
|
};
|
|
@@ -114,8 +114,14 @@ export function useBrokerManagerActions({
|
|
|
114
114
|
const broker = signedInBrokerForProfile(selectedRow.instance, selectedRow.brokerName);
|
|
115
115
|
try {
|
|
116
116
|
setBusy(t("Connecting…"));
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
// A connected broker is connected again only by a new sign-in: this renews it before the broker ends it.
|
|
118
|
+
// A row that says when the sign-in ends is renewable before this session's first sync too.
|
|
119
|
+
const renew = selectedRow.state === "connected" || selectedRow.signInEndsAt !== null;
|
|
120
|
+
if (!await requestBrokerSignIn(broker, { renew })) {
|
|
121
|
+
// Backing out of a renewal leaves the broker connected, for now.
|
|
122
|
+
setMessage(infoMessage(renew
|
|
123
|
+
? tf("{broker} sign-in was not renewed.", { broker: broker.name })
|
|
124
|
+
: tf("{broker} was not connected.", { broker: broker.name })));
|
|
119
125
|
return;
|
|
120
126
|
}
|
|
121
127
|
await syncBrokerInstance(selectedRow.id);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useCallback, useMemo, useSyncExternalStore } from "react";
|
|
2
|
+
import { apiClient } from "../../../api-client";
|
|
3
|
+
import { fetchSignedInBrokerConnection, type SignedInBrokerConnection } from "../../../brokers/signed-in/client";
|
|
4
|
+
import { isSignedInBrokerProfile, signedInBrokerId } from "../../../brokers/signed-in/profile";
|
|
5
|
+
import { tf } from "../../../i18n";
|
|
6
|
+
import { useAsyncResource } from "../../../react/async-resource";
|
|
7
|
+
import type { BrokerInstanceConfig } from "../../../types/config";
|
|
8
|
+
import { formatDetailDate, formatShortDate } from "../../../utils/datetime-format";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Some brokers end a sign-in on their own clock, however often it was
|
|
12
|
+
* refreshed, and Gloom flags it a day before. Only that case shows anything: a
|
|
13
|
+
* server that predates the flag, or a sign-in with no end in sight, adds nothing.
|
|
14
|
+
*/
|
|
15
|
+
export type SignInEnds = ReadonlyMap<string, number>;
|
|
16
|
+
|
|
17
|
+
/** When the broker ends the sign-in, if Gloom says it is time to connect again. */
|
|
18
|
+
export function signInEndOf(connection: Pick<SignedInBrokerConnection, "expiresAt" | "expiresSoon">): number | null {
|
|
19
|
+
if (connection.expiresSoon !== true || !connection.expiresAt) return null;
|
|
20
|
+
const endsAt = Date.parse(connection.expiresAt);
|
|
21
|
+
return Number.isFinite(endsAt) ? endsAt : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The profile row's status, in the place of "Connected": "Ends Oct 10". */
|
|
25
|
+
export function signInEndStatus(endsAt: number, now = Date.now()): string {
|
|
26
|
+
const date = formatShortDate(endsAt, { year: false });
|
|
27
|
+
return endsAt > now ? tf("Ends {date}", { date }) : tf("Ended {date}", { date });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The profile detail's note: "Sign-in ends Oct 10, 2026 at 3:00 PM". */
|
|
31
|
+
export function signInEndNote(endsAt: number, now = Date.now()): string {
|
|
32
|
+
const when = formatDetailDate(endsAt);
|
|
33
|
+
return endsAt > now ? tf("Sign-in ends {when}", { when }) : tf("Sign-in ended {when}", { when });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* When each signed-in profile's sign-in ends, by instance id, for the profiles
|
|
38
|
+
* whose broker is about to end it. Read from Gloom when the pane opens and
|
|
39
|
+
* after a connect or sync; a failed read is the same as nothing to say.
|
|
40
|
+
*/
|
|
41
|
+
export function useSignInEnds(
|
|
42
|
+
instances: readonly BrokerInstanceConfig[],
|
|
43
|
+
): { ends: SignInEnds; reload: () => void } {
|
|
44
|
+
const profiles = useMemo(
|
|
45
|
+
() => instances.flatMap((instance) => isSignedInBrokerProfile(instance) && instance.enabled !== false
|
|
46
|
+
? [{ id: instance.id, broker: signedInBrokerId(instance) }]
|
|
47
|
+
: []),
|
|
48
|
+
[instances],
|
|
49
|
+
);
|
|
50
|
+
// One read per broker, however many profiles name it.
|
|
51
|
+
const brokers = useMemo(() => [...new Set(profiles.map((profile) => profile.broker).filter(Boolean))].sort(), [profiles]);
|
|
52
|
+
const brokerKey = brokers.join(",");
|
|
53
|
+
const signedIn = useSyncExternalStore(
|
|
54
|
+
(listener) => apiClient.subscribeCurrentUser(listener),
|
|
55
|
+
() => apiClient.isSignedIn(),
|
|
56
|
+
() => apiClient.isSignedIn(),
|
|
57
|
+
);
|
|
58
|
+
const loader = useMemo(() => brokerKey && signedIn
|
|
59
|
+
? async () => {
|
|
60
|
+
const reads = await Promise.all(brokerKey.split(",").map(async (broker) => {
|
|
61
|
+
try {
|
|
62
|
+
return [broker, signInEndOf(await fetchSignedInBrokerConnection(broker))] as const;
|
|
63
|
+
} catch {
|
|
64
|
+
return [broker, null] as const;
|
|
65
|
+
}
|
|
66
|
+
}));
|
|
67
|
+
return new Map(reads);
|
|
68
|
+
}
|
|
69
|
+
: null, [brokerKey, signedIn]);
|
|
70
|
+
const { data, reload } = useAsyncResource(loader, { keepPreviousData: true });
|
|
71
|
+
|
|
72
|
+
const ends = useMemo<SignInEnds>(() => {
|
|
73
|
+
const byInstance = new Map<string, number>();
|
|
74
|
+
for (const profile of profiles) {
|
|
75
|
+
const endsAt = data?.get(profile.broker);
|
|
76
|
+
if (endsAt != null) byInstance.set(profile.id, endsAt);
|
|
77
|
+
}
|
|
78
|
+
return byInstance;
|
|
79
|
+
}, [data, profiles]);
|
|
80
|
+
const refresh = useCallback(() => { void reload(); }, [reload]);
|
|
81
|
+
return { ends, reload: refresh };
|
|
82
|
+
}
|
|
@@ -4,6 +4,7 @@ import { colors } from "../../../theme/colors";
|
|
|
4
4
|
import { formatRelativeAge } from "../../../utils/datetime-format";
|
|
5
5
|
import type { BrokerDisplayState, BrokerProfileRow } from "./model";
|
|
6
6
|
import { t } from "../../../i18n";
|
|
7
|
+
import { signInEndStatus } from "./sign-in-end";
|
|
7
8
|
|
|
8
9
|
type BrokerColumnId = "profile" | "status" | "broker" | "mode" | "accounts" | "updated";
|
|
9
10
|
export type BrokerColumn = DataTableColumn & { id: BrokerColumnId };
|
|
@@ -45,6 +46,10 @@ export function renderBrokerCell(row: BrokerProfileRow, column: BrokerColumn): D
|
|
|
45
46
|
attributes: TextAttributes.BOLD,
|
|
46
47
|
};
|
|
47
48
|
case "status":
|
|
49
|
+
// A sign-in the broker is about to end replaces "Connected" until it is renewed.
|
|
50
|
+
if (row.signInEndsAt !== null) {
|
|
51
|
+
return { text: signInEndStatus(row.signInEndsAt), color: colors.warning };
|
|
52
|
+
}
|
|
48
53
|
return {
|
|
49
54
|
text: row.stateLabel,
|
|
50
55
|
color: stateColor(row.state),
|