gloomberb 0.19.2 → 0.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +108 -1
- package/src/account/upgrade-dialog.tsx +4 -23
- package/src/api-client/coalesce.ts +118 -0
- package/src/api-client/credit-boards.ts +13 -0
- package/src/api-client/crypto-etf-flows.ts +6 -0
- package/src/api-client/crypto-markets.ts +40 -0
- package/src/api-client/data.ts +201 -14
- package/src/api-client/equity-screener.ts +10 -0
- package/src/api-client/errors.ts +20 -0
- package/src/api-client/gpu.ts +29 -0
- package/src/api-client/index.ts +5 -2
- package/src/api-client/plan-access.ts +10 -1
- package/src/api-client/plan-rules.ts +35 -0
- package/src/api-client/research-activity.ts +1 -32
- package/src/api-client/types.ts +21 -1
- package/src/app/attention-notice.tsx +94 -0
- package/src/app.tsx +23 -1
- package/src/architecture/core-builtin-allowlist.json +2 -1
- package/src/capabilities/factories.ts +1 -0
- package/src/cli/ascii-output.ts +42 -0
- package/src/cli/base-converter.ts +10 -2
- package/src/cli/command-options.ts +16 -1
- package/src/cli/commands/crypto-hints.ts +23 -4
- package/src/cli/commands/market.ts +92 -23
- package/src/cli/commands/news-search.ts +24 -5
- package/src/cli/commands/overview.ts +9 -4
- package/src/cli/commands/search.ts +71 -12
- package/src/cli/commands/system.ts +22 -30
- package/src/cli/commands/ticker.ts +49 -9
- package/src/cli/commands/version.ts +33 -0
- package/src/cli/context.ts +5 -9
- package/src/cli/data-dir.ts +7 -0
- package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
- package/src/cli/desktop-pane-shot-browser.ts +320 -0
- package/src/cli/desktop-pane-shot-routes.ts +26 -1
- package/src/cli/desktop-pane-shot.ts +196 -256
- package/src/cli/early-command.ts +42 -0
- package/src/cli/earnings-calendar.ts +9 -0
- package/src/cli/entry.ts +19 -13
- package/src/cli/errors.ts +12 -2
- package/src/cli/help.ts +1 -1
- package/src/cli/history-export.ts +22 -2
- package/src/cli/history-rows.ts +154 -4
- package/src/cli/index.ts +18 -1
- package/src/cli/known-gaps.ts +40 -5
- package/src/cli/listing-arg.ts +46 -9
- package/src/cli/options.ts +1 -1
- package/src/cli/output-file.ts +60 -13
- package/src/cli/pane-functions/catalog.ts +14 -3
- package/src/cli/pane-functions/data.ts +1 -1
- package/src/cli/pane-functions/dom.ts +45 -0
- package/src/cli/pane-functions/freshness.ts +46 -9
- package/src/cli/pane-functions/function-help.ts +17 -9
- package/src/cli/pane-functions/headless.ts +22 -14
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +6 -2
- package/src/cli/pane-functions/resolver.ts +27 -5
- package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
- package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
- package/src/cli/pane-functions/screenshot-http.ts +70 -0
- package/src/cli/pane-functions/screenshot-render.ts +19 -5
- package/src/cli/quote-answer.ts +20 -0
- package/src/cli/registry.ts +41 -13
- package/src/cli/result.ts +12 -6
- package/src/cli/ticker-resolution.ts +2 -1
- package/src/cli/types.ts +2 -0
- package/src/components/chart/composite/format.ts +3 -0
- package/src/components/chart-table/header.tsx +1 -1
- package/src/components/chart-table/layout.ts +28 -11
- package/src/components/command-bar/action-runtime.ts +4 -0
- package/src/components/command-bar/commands/direct/index.ts +18 -33
- package/src/components/command-bar/commands/direct/runtime.ts +5 -0
- package/src/components/command-bar/commands/registry.ts +10 -1
- package/src/components/command-bar/routes/root/command-items.ts +9 -4
- package/src/components/command-bar/routes/root/runtime.ts +24 -1
- package/src/components/command-bar/surface/index.tsx +9 -3
- package/src/components/company-logo.tsx +2 -1
- package/src/components/layout/header.tsx +8 -3
- package/src/components/layout/market-summary.ts +25 -7
- package/src/components/layout/pane/footer/index.tsx +7 -2
- package/src/components/layout/pane/footer/model.ts +40 -5
- package/src/components/market-board/index.tsx +3 -2
- package/src/components/onboarding/desks-step.tsx +12 -4
- package/src/components/onboarding/first-run-workspace.ts +81 -4
- package/src/components/onboarding/onboarding-frame.tsx +2 -1
- package/src/components/onboarding/onboarding-keyboard.ts +8 -5
- package/src/components/onboarding/onboarding-wizard.tsx +54 -23
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
- package/src/components/onboarding/run-setup-again.ts +24 -0
- package/src/components/onboarding/use-onboarding-progress.ts +33 -15
- package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
- package/src/components/onboarding/wizard-model.ts +57 -3
- package/src/components/onboarding/wizard-positions.ts +1 -1
- package/src/components/ui/delayed-mark.tsx +56 -0
- package/src/components/ui/external-link.tsx +3 -6
- package/src/components/ui/icon.tsx +8 -1
- package/src/components/ui/logo-mark.tsx +8 -5
- package/src/components/ui/stat-grid.tsx +263 -19
- package/src/data/config/store/node.ts +11 -5
- package/src/data/config/store/normalize.ts +16 -3
- package/src/data/profile-permissions.ts +47 -0
- package/src/data/resource-store.ts +34 -31
- package/src/data/sqlite/access.ts +19 -8
- package/src/data/sqlite/checkpointer.ts +95 -8
- package/src/data/sqlite/database.ts +40 -7
- package/src/docs/snapshot.json +191 -78
- package/src/i18n/es.ts +18 -2
- package/src/i18n/ja.ts +18 -2
- package/src/i18n/ko.ts +18 -2
- package/src/i18n/zh-cn.ts +18 -2
- package/src/i18n/zh-tw.ts +18 -2
- package/src/layout/desk-keys.ts +4 -1
- package/src/layout/desks.ts +38 -5
- package/src/layout-marketplace/gallery-desktop.tsx +45 -16
- package/src/market-data/batched-reads.ts +16 -0
- package/src/market-data/coordinator/auxiliary.ts +4 -2
- package/src/market-data/coordinator/index.ts +11 -2
- package/src/market-data/futures-board.ts +1 -1
- package/src/market-data/futures-contract.ts +3 -1
- package/src/market-data/market/index-level.ts +6 -4
- package/src/market-data/published-apac-sessions.ts +6 -0
- package/src/news/aggregator.ts +7 -4
- package/src/news/news-model.ts +11 -0
- package/src/news/story-id.ts +22 -0
- package/src/news/ticker-symbols.ts +17 -1
- package/src/notifications/app-notifier.ts +35 -14
- package/src/plugins/builtin/account-management/form-components.tsx +1 -1
- package/src/plugins/builtin/account-management/index.tsx +2 -3
- package/src/plugins/builtin/account-management/model.ts +1 -1
- package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
- package/src/plugins/builtin/alerts/events.ts +10 -2
- package/src/plugins/builtin/alerts/index.tsx +5 -5
- package/src/plugins/builtin/alerts/research-builder.ts +5 -1
- package/src/plugins/builtin/alerts/research-rules.ts +18 -0
- package/src/plugins/builtin/alerts/targets.ts +4 -1
- package/src/plugins/builtin/attention/headless.ts +1 -1
- package/src/plugins/builtin/attention/index.tsx +2 -4
- package/src/plugins/builtin/attention/listing.ts +1 -1
- package/src/plugins/builtin/attention/series.ts +1 -1
- package/src/plugins/builtin/awards/index.tsx +1 -2
- package/src/plugins/builtin/backtest/client.ts +1 -0
- package/src/plugins/builtin/backtest/index.tsx +1 -2
- package/src/plugins/builtin/backtest/rules.ts +3 -1
- package/src/plugins/builtin/backtest/window.ts +8 -7
- package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
- package/src/plugins/builtin/broker-manager/index.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +5 -0
- package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
- package/src/plugins/builtin/buildout/company/id.ts +2 -0
- package/src/plugins/builtin/buildout/company/model.ts +247 -0
- package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
- package/src/plugins/builtin/buildout/facilities.ts +309 -0
- package/src/plugins/builtin/buildout/format.ts +9 -0
- package/src/plugins/builtin/buildout/index.tsx +51 -5
- package/src/plugins/builtin/buildout/model/index.ts +62 -10
- package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
- package/src/plugins/builtin/buildout/model/types.ts +11 -2
- package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
- package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
- package/src/plugins/builtin/buildout/table-model.ts +26 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
- package/src/plugins/builtin/cds/headless.ts +10 -2
- package/src/plugins/builtin/cds/model.ts +7 -3
- package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
- package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
- package/src/plugins/builtin/changelog/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -2
- package/src/plugins/builtin/chart-composer/headless.ts +88 -21
- package/src/plugins/builtin/chart-composer/index.tsx +2 -3
- package/src/plugins/builtin/chart-composer/pane.tsx +26 -2
- package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/controller/index.ts +1 -1
- package/src/plugins/builtin/chat/controller/messages.ts +15 -4
- package/src/plugins/builtin/chat/ghost-user.ts +8 -0
- package/src/plugins/builtin/chat/index.tsx +5 -5
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/index.tsx +1 -2
- package/src/plugins/builtin/cloud/plugin.tsx +6 -4
- package/src/plugins/builtin/cloud/team/module.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
- package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
- package/src/plugins/builtin/comment-letters/index.tsx +1 -2
- package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
- package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
- package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
- package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
- package/src/plugins/builtin/congress-trades/headless.ts +2 -0
- package/src/plugins/builtin/congress-trades/model.ts +92 -64
- package/src/plugins/builtin/congress-trades/table.tsx +14 -2
- package/src/plugins/builtin/connections/index.ts +2 -3
- package/src/plugins/builtin/cot/index.tsx +1 -2
- package/src/plugins/builtin/cpi/index.tsx +1 -2
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/client.ts +7 -2
- package/src/plugins/builtin/credit-boards/headless.ts +25 -12
- package/src/plugins/builtin/credit-boards/model.ts +29 -0
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
- package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
- package/src/plugins/builtin/crypto-board/headless.ts +4 -3
- package/src/plugins/builtin/crypto-board/index.tsx +4 -2
- package/src/plugins/builtin/crypto-board/series.ts +2 -0
- package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
- package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
- package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
- package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
- package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
- package/src/plugins/builtin/debt-maturities/model.ts +17 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
- package/src/plugins/builtin/debug/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/client.ts +32 -2
- package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
- package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
- package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
- package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
- package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
- package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
- package/src/plugins/builtin/dividend-yield/view.ts +21 -0
- package/src/plugins/builtin/docs/index.tsx +2 -3
- package/src/plugins/builtin/doe/headless.ts +16 -3
- package/src/plugins/builtin/doe/index.tsx +5 -4
- package/src/plugins/builtin/earnings/board.tsx +2 -1
- package/src/plugins/builtin/earnings/history.tsx +2 -1
- package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
- package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
- package/src/plugins/builtin/econ/calendar-model.ts +29 -6
- package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/headless.ts +1 -0
- package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
- package/src/plugins/builtin/equity-screener/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/model.ts +28 -4
- package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
- package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
- package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
- package/src/plugins/builtin/exposure/holdings.ts +35 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/cli.ts +44 -0
- package/src/plugins/builtin/fear-greed/index.tsx +5 -53
- package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
- package/src/plugins/builtin/fixed-income/index.tsx +1 -2
- package/src/plugins/builtin/futures/board-reads.ts +25 -0
- package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
- package/src/plugins/builtin/futures/index.tsx +15 -0
- package/src/plugins/builtin/futures/model.ts +14 -0
- package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
- package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
- package/src/plugins/builtin/futures-curve/headless.ts +6 -1
- package/src/plugins/builtin/futures-curve/index.tsx +1 -2
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +51 -12
- package/src/plugins/builtin/gpu/client.ts +15 -2
- package/src/plugins/builtin/gpu/equities.tsx +5 -2
- package/src/plugins/builtin/gpu/headless.ts +14 -2
- package/src/plugins/builtin/gpu/index.tsx +1 -2
- package/src/plugins/builtin/gpu/model.ts +20 -2
- package/src/plugins/builtin/gpu/pane.tsx +1 -1
- package/src/plugins/builtin/help/index.tsx +1 -1
- package/src/plugins/builtin/holders/index-holders.ts +24 -12
- package/src/plugins/builtin/holders/index.tsx +2 -3
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
- package/src/plugins/builtin/iv-history/index.tsx +1 -2
- package/src/plugins/builtin/jodi/index.tsx +1 -2
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
- package/src/plugins/builtin/layout-manager/index.tsx +9 -8
- package/src/plugins/builtin/macro-day/pane.tsx +4 -2
- package/src/plugins/builtin/market-halts/index.tsx +1 -2
- package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
- package/src/plugins/builtin/market-movers/index.tsx +7 -1
- package/src/plugins/builtin/market-movers/session-body.tsx +4 -1
- package/src/plugins/builtin/market-valuation/index.tsx +1 -2
- package/src/plugins/builtin/members/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/pane.tsx +2 -1
- package/src/plugins/builtin/news/headless.ts +7 -0
- package/src/plugins/builtin/news/wire/index.tsx +1 -1
- package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
- package/src/plugins/builtin/notes/index.tsx +3 -6
- package/src/plugins/builtin/openfda/index.tsx +1 -2
- package/src/plugins/builtin/options/view.tsx +6 -5
- package/src/plugins/builtin/options-calculator/index.tsx +1 -2
- package/src/plugins/builtin/options-positioning/index.tsx +1 -2
- package/src/plugins/builtin/options-scenario/index.tsx +2 -4
- package/src/plugins/builtin/perps/index.tsx +1 -2
- package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
- package/src/plugins/builtin/plugin-module.ts +16 -2
- package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
- package/src/plugins/builtin/power/index.tsx +1 -2
- package/src/plugins/builtin/rate-path/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/model.ts +16 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
- package/src/plugins/builtin/research/analyst-headless.ts +33 -6
- package/src/plugins/builtin/research/analyst-model.ts +37 -2
- package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
- package/src/plugins/builtin/research/index.tsx +5 -8
- package/src/plugins/builtin/research-search/index.tsx +1 -2
- package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
- package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
- package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
- package/src/plugins/builtin/roadmap/index.tsx +2 -3
- package/src/plugins/builtin/roadmap/pane.tsx +49 -2
- package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
- package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
- package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
- package/src/plugins/builtin/sec/filing-content.ts +38 -6
- package/src/plugins/builtin/sec/headless.ts +5 -1
- package/src/plugins/builtin/sec/index.tsx +4 -2
- package/src/plugins/builtin/sec/model.ts +2 -0
- package/src/plugins/builtin/sectors/index.tsx +11 -5
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +60 -0
- package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
- package/src/plugins/builtin/shared/forming-bars.ts +29 -0
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
- package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
- package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
- package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/pane.tsx +2 -1
- package/src/plugins/builtin/social-mentions/index.tsx +1 -2
- package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
- package/src/plugins/builtin/supply-chain/client.ts +22 -1
- package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
- package/src/plugins/builtin/supply-chain/index.tsx +1 -2
- package/src/plugins/builtin/thirteenf/api.ts +116 -13
- package/src/plugins/builtin/thirteenf/data.ts +132 -30
- package/src/plugins/builtin/thirteenf/index.tsx +2 -4
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/pane.tsx +20 -11
- package/src/plugins/builtin/thirteenf/types.ts +12 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +17 -4
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
- package/src/plugins/builtin/ticker-detail/headless.ts +11 -3
- package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
- package/src/plugins/builtin/time-sales/index.tsx +1 -2
- package/src/plugins/builtin/time-sales/pane.tsx +3 -1
- package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
- package/src/plugins/builtin/trace-bonds/model.ts +44 -10
- package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
- package/src/plugins/builtin/treasury-auctions/pane.tsx +2 -2
- package/src/plugins/builtin/vol-surface/client.ts +18 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -2
- package/src/plugins/builtin/volatility/index.tsx +1 -2
- package/src/plugins/builtin/world-indices/footer.ts +6 -1
- package/src/plugins/builtin/world-venue-map/client.ts +10 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -2
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
- package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
- package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
- package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
- package/src/plugins/builtin/yield-curve/curves.ts +16 -2
- package/src/plugins/builtin/yield-curve/headless.ts +3 -3
- package/src/plugins/bundle.ts +52 -2
- package/src/plugins/catalog-browser.ts +2 -2
- package/src/plugins/catalog-cli.ts +20 -0
- package/src/plugins/host-link.ts +13 -1
- package/src/plugins/installer.ts +15 -4
- package/src/plugins/loader.ts +43 -11
- package/src/plugins/peer-imports.ts +201 -0
- package/src/plugins/peer-retry.ts +26 -29
- package/src/plugins/plugin-process.ts +128 -0
- package/src/plugins/redirection-guard.ts +45 -0
- package/src/remote/endpoint.ts +16 -3
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/http-proxy.ts +7 -123
- package/src/renderers/dom/desktop/icons.tsx +2 -0
- package/src/renderers/dom/host/image-surface.tsx +46 -21
- package/src/renderers/dom/styles.css +7 -0
- package/src/renderers/dom/toast-host.tsx +2 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +3 -2
- package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
- package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
- package/src/renderers/electrobun/bun/window/create.ts +4 -2
- package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
- package/src/renderers/electrobun/view/main.tsx +7 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/renderers/opentui/browser-open.ts +10 -4
- package/src/renderers/opentui/image/surface.tsx +1 -1
- package/src/renderers/opentui/start.tsx +12 -6
- package/src/renderers/opentui/toast-host.tsx +2 -1
- package/src/series/registry.ts +18 -0
- package/src/series/standalone.ts +10 -1
- package/src/series/types.ts +7 -0
- package/src/series/validate.ts +11 -2
- package/src/sources/gloomberb-cloud/index.ts +28 -5
- package/src/sources/gloomberb-cloud/news.ts +24 -3
- package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
- package/src/sources/provider-errors.ts +26 -3
- package/src/sources/provider-router/history.ts +5 -0
- package/src/sources/provider-router/index.ts +13 -0
- package/src/sync/core-contributors.ts +32 -0
- package/src/telemetry/attention-counts.ts +64 -6
- package/src/telemetry/attention-setting.ts +33 -0
- package/src/telemetry/crash-reports-node.ts +3 -1
- package/src/telemetry/stall-clocks.ts +93 -0
- package/src/telemetry/stall-reports-node.ts +12 -7
- package/src/telemetry/stall-reports.ts +34 -5
- package/src/test-support/preload.ts +9 -0
- package/src/tickers/company-name.ts +19 -0
- package/src/tickers/listing-fate.ts +14 -3
- package/src/tickers/saved-instrument.ts +17 -0
- package/src/tickers/search/home-listing.ts +13 -2
- package/src/tickers/search/index.ts +4 -1
- package/src/tickers/search/types.ts +6 -1
- package/src/time-series/bar-aggregation.ts +7 -1
- package/src/time-series/base-series.ts +3 -3
- package/src/time-series/chart-bounds.ts +1 -1
- package/src/time-series/chart-data.ts +5 -2
- package/src/time-series/chart-seed.ts +1 -1
- package/src/time-series/coin-session-close.ts +211 -0
- package/src/time-series/live-bars.ts +13 -2
- package/src/time-series/live-quotes.ts +1 -1
- package/src/time-series/official-close.ts +159 -0
- package/src/time-series/price-comparison.ts +55 -6
- package/src/time-series/resolve.ts +12 -3
- package/src/time-series/series-presentation.ts +1 -1
- package/src/time-series/studies/formula.ts +205 -0
- package/src/time-series/studies/indicators.ts +299 -0
- package/src/time-series/studies/leverage.ts +75 -0
- package/src/time-series/studies/pair.ts +138 -0
- package/src/time-series/studies/resolve.ts +333 -0
- package/src/time-series/studies/shared.ts +141 -0
- package/src/time-series/studies/units.ts +243 -0
- package/src/time-series/studies/vwap.ts +274 -0
- package/src/types/config.ts +18 -2
- package/src/types/data-provider.ts +23 -0
- package/src/types/financials.ts +22 -0
- package/src/types/headless.ts +42 -3
- package/src/types/plugin.ts +32 -3
- package/src/ui/host.tsx +11 -0
- package/src/ui/toast.tsx +26 -1
- package/src/utils/ascii-text.ts +140 -0
- package/src/utils/cli-output.ts +91 -18
- package/src/utils/concurrency.ts +24 -0
- package/src/utils/exchanges.ts +6 -0
- package/src/utils/external-url.ts +20 -7
- package/src/utils/futures-generic.ts +4 -4
- package/src/utils/http-proxy-upstream.ts +124 -0
- package/src/utils/main-thread-stall.ts +75 -3
- package/src/utils/perf-marks.ts +36 -2
- package/src/utils/private-files.ts +141 -0
- package/src/utils/public-address.ts +99 -0
- package/src/account/upgrade-personalization.ts +0 -57
- package/src/account/upgrade-tickers.ts +0 -124
- package/src/api-client/upgrade-experiment.ts +0 -91
- package/src/plugins/builtin/cloud/browser.tsx +0 -3
- package/src/renderers/electrobun/launcher/preload.ts +0 -13
- package/src/renderers/electrobun/launcher/windows-emulation.ts +0 -50
- package/src/time-series/studies.ts +0 -1628
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type DataTableCell,
|
|
13
13
|
type StatItem,
|
|
14
14
|
} from "../../../components";
|
|
15
|
+
import { ageFooterSegment } from "../../../components/layout/pane/footer";
|
|
15
16
|
import { formatCompactAxis } from "../../../components/chart-table";
|
|
16
17
|
import { useChartViewReset } from "../../../components/chart/core/view-reset";
|
|
17
18
|
import { scalarPoint, staticSeries } from "../../../components/chart/static/series";
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
flowColor,
|
|
37
38
|
formatFlow,
|
|
38
39
|
formatFlowAmount,
|
|
40
|
+
formatPriceChange,
|
|
39
41
|
fundTickerOfColumn,
|
|
40
42
|
pendingText,
|
|
41
43
|
type CryptoEtfFlowColumn,
|
|
@@ -76,13 +78,15 @@ export function CryptoEtfFlowsPane({ focused, width, height }: PaneProps) {
|
|
|
76
78
|
}, [asset, setSelectedByAsset]);
|
|
77
79
|
|
|
78
80
|
const label = cryptoEtfFlowAssetLabel(asset);
|
|
79
|
-
// The running total since the first fund launched, drawn over the range the table lists.
|
|
81
|
+
// The running total since the first fund launched, drawn over the range the table lists. Its legend is the one
|
|
82
|
+
// place the total and the day it starts from are said; the figures above carry only the table's windows.
|
|
80
83
|
const series = useMemo(() => {
|
|
81
84
|
if (!data) return [];
|
|
82
85
|
const first = days.at(-1)?.date ?? "";
|
|
83
86
|
const points = cumulativeFlows(data).filter((point) => point.date >= first)
|
|
84
87
|
.map((point) => scalarPoint(new Date(`${point.date}T00:00:00Z`), point.value * 1_000_000));
|
|
85
|
-
|
|
88
|
+
const launch = data.days.at(-1)?.date;
|
|
89
|
+
return [staticSeries(points, { id: `etf-flows:${asset}`, label: `${label} ETF net flows since ${launch ?? "launch"}`, color: colors.positive, calendarSpaced: true })];
|
|
86
90
|
}, [asset, data, days, label]);
|
|
87
91
|
const link = useChartTableSelection({ rows: days, getId: rowId, getDate: rowDate, selectedId, onSelect: selectDay, focused });
|
|
88
92
|
|
|
@@ -93,14 +97,12 @@ export function CryptoEtfFlowsPane({ focused, width, height }: PaneProps) {
|
|
|
93
97
|
detail: [latest?.date, pending].filter(Boolean).join(" · ") || undefined },
|
|
94
98
|
{ id: "week", label: "5 days", value: formatFlowAmount(summary.week), color: flowColor(summary.week, colors) },
|
|
95
99
|
{ id: "month", label: "20 days", value: formatFlowAmount(summary.month), color: flowColor(summary.month, colors) },
|
|
96
|
-
{ id: "launch", label: "Since launch", value: formatFlowAmount(summary.sinceLaunch), color: flowColor(summary.sinceLaunch, colors),
|
|
97
|
-
detail: summary.firstDate ? `since ${summary.firstDate}` : undefined },
|
|
98
100
|
] : [];
|
|
99
101
|
|
|
100
102
|
const updatedAgo = useUpdatedAgo(resource.updatedAt);
|
|
101
103
|
const footerInfo = useMemo(() => data ? [
|
|
102
104
|
{ id: "as-of", parts: [{ text: `as of ${data.asOf}`, tone: "muted" as const }] },
|
|
103
|
-
...(updatedAgo ? [
|
|
105
|
+
...(updatedAgo ? [ageFooterSegment(updatedAgo)] : []),
|
|
104
106
|
] : [], [data, updatedAgo]);
|
|
105
107
|
useAutoRefresh(resource.updatedAt, resource.load);
|
|
106
108
|
const refresh = useCallback(() => void resource.reload(), [resource.reload]);
|
|
@@ -135,6 +137,7 @@ export function CryptoEtfFlowsPane({ focused, width, height }: PaneProps) {
|
|
|
135
137
|
const renderCell = useCallback((day: CryptoEtfFlowDay, column: CryptoEtfFlowColumn): DataTableCell => {
|
|
136
138
|
if (column.id === "date") return { text: day.date, value: day.date, color: colors.text };
|
|
137
139
|
if (column.id === "total") return { text: formatFlow(day.total), value: day.total, color: flowColor(day.total, colors) };
|
|
140
|
+
if (column.id === "price") return { text: formatPriceChange(day.priceChangePct), value: day.priceChangePct ?? null, color: flowColor(day.priceChangePct, colors) };
|
|
138
141
|
const ticker = fundTickerOfColumn(column.id) ?? "";
|
|
139
142
|
const value = day.flows[ticker] ?? null;
|
|
140
143
|
// A fund that reported the day before and not yet today: its figure is to come, not none.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { HeadlessPaneDefinition } from "../../../types/plugin";
|
|
2
2
|
import { fetchDebtMaturities } from "./client";
|
|
3
|
-
import { bucketShare, debtAmount, debtNewestAsOf, debtNotices, debtPercent, newerQuarter } from "./model";
|
|
3
|
+
import { bucketShare, debtAmount, debtNewestAsOf, debtNewestForm, debtNotices, debtPercent, newerQuarter } from "./model";
|
|
4
4
|
import { SEC_FILINGS } from "../shared/report-freshness";
|
|
5
5
|
import { resolveHeadlessIssuerListing } from "../shared/headless-market-data";
|
|
6
6
|
|
|
@@ -118,8 +118,9 @@ export const debtMaturitiesHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
118
118
|
},
|
|
119
119
|
],
|
|
120
120
|
metadata: { ...data },
|
|
121
|
-
// The latest filing dates the report (a newer 10-Q's balance date)
|
|
122
|
-
|
|
121
|
+
// The latest filing dates the report (a newer 10-Q's balance date), named by its form so the date is
|
|
122
|
+
// not read as the schedule's; the history rows are past years, not stale data.
|
|
123
|
+
freshness: { asOf: debtNewestAsOf(data), asOfLabel: debtNewestForm(data) ?? undefined },
|
|
123
124
|
};
|
|
124
125
|
},
|
|
125
126
|
};
|
|
@@ -219,9 +219,25 @@ export function newerQuarter(data: DebtMaturitiesPayload): DebtQuarter | null {
|
|
|
219
219
|
const quarter = data.latestQuarter ?? null;
|
|
220
220
|
return quarter && data.latest && quarter.asOf > data.latest.asOf ? quarter : null;
|
|
221
221
|
}
|
|
222
|
+
/** The newest filing the payload reports: the 10-Q when it is newer, else the annual schedule's. */
|
|
223
|
+
function newestDebtFiling(data: DebtMaturitiesPayload): Pick<DebtQuarter, "form" | "asOf" | "filed"> | null {
|
|
224
|
+
return newerQuarter(data) ?? data.latest ?? null;
|
|
225
|
+
}
|
|
222
226
|
/** The newest balance date the payload reports: the 10-Q's when it is newer, else the schedule's. */
|
|
223
227
|
export function debtNewestAsOf(data: DebtMaturitiesPayload): string | null {
|
|
224
|
-
return
|
|
228
|
+
return newestDebtFiling(data)?.asOf ?? null;
|
|
229
|
+
}
|
|
230
|
+
/** The form of the filing `debtNewestAsOf` dates, which names that date: `10-Q`, `10-K`. */
|
|
231
|
+
export function debtNewestForm(data: DebtMaturitiesPayload): string | null {
|
|
232
|
+
return newestDebtFiling(data)?.form ?? null;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Which filing the pane's date is, as its footer says it, longest first:
|
|
236
|
+
* `10-Q 2026-09-30, filed 2026-10-27`, then `10-Q 2026-09-30`.
|
|
237
|
+
*/
|
|
238
|
+
export function debtNewestFilingText(data: DebtMaturitiesPayload): string[] {
|
|
239
|
+
const filing = newestDebtFiling(data);
|
|
240
|
+
return filing ? [`${filing.form} ${filing.asOf}, filed ${filing.filed}`, `${filing.form} ${filing.asOf}`] : [];
|
|
225
241
|
}
|
|
226
242
|
/** The 10-Q's debt in one line, `Current 212.00M · Non-current 6.84B USD`. */
|
|
227
243
|
export function quarterDebtText(quarter: DebtQuarter): string {
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type DataTableCell,
|
|
25
25
|
type StatItem,
|
|
26
26
|
} from "../../../components";
|
|
27
|
+
import { ageFooterSegment } from "../../../components/layout/pane/footer";
|
|
27
28
|
import { colors } from "../../../theme/colors";
|
|
28
29
|
import type {
|
|
29
30
|
DebtFact,
|
|
@@ -53,6 +54,7 @@ import {
|
|
|
53
54
|
debtFilingUrl,
|
|
54
55
|
debtMetricCaption,
|
|
55
56
|
debtMetricValue,
|
|
57
|
+
debtNewestFilingText,
|
|
56
58
|
debtNotices,
|
|
57
59
|
debtPercent,
|
|
58
60
|
historyAxis,
|
|
@@ -431,10 +433,13 @@ export function DebtMaturitiesPane({ width, height, focused }: PaneProps) {
|
|
|
431
433
|
metric("next12", "Due next 12 months", latest.next12MonthsShare),
|
|
432
434
|
metric("next3", "Due next 3 years", latest.next3YearsShare),
|
|
433
435
|
{ id: "as-of", label: "As of", value: latest.asOf },
|
|
434
|
-
// A 10-Q filed since: its own row,
|
|
436
|
+
// A 10-Q filed since: its own row, named by its period, so it never reads
|
|
437
|
+
// as the schedule's. The filed date goes beside the figures where it fits
|
|
438
|
+
// and gives way to them where it does not: the footer says it
|
|
439
|
+
// ("10-Q 2026-09-30, filed 2026-10-27").
|
|
435
440
|
...(quarter ? [{
|
|
436
|
-
id: "latest-10q", label:
|
|
437
|
-
detail:
|
|
441
|
+
id: "latest-10q", label: `10-Q ${quarter.asOf}`, value: quarterDebtText(quarter),
|
|
442
|
+
detail: `filed ${quarter.filed}`, detailVariants: [""], wide: true,
|
|
438
443
|
}] : []),
|
|
439
444
|
];
|
|
440
445
|
}, [latest, quarter]);
|
|
@@ -445,6 +450,15 @@ export function DebtMaturitiesPane({ width, height, focused }: PaneProps) {
|
|
|
445
450
|
label: "Principal total", values, value: debtAmount(last), color: colors.warning,
|
|
446
451
|
};
|
|
447
452
|
}, [bars]);
|
|
453
|
+
// Which filing the pane's date is, then how long ago it loaded: the age goes first in a narrow footer.
|
|
454
|
+
const filingFooter = useMemo(() => {
|
|
455
|
+
const [filing, ...shorter] = data ? debtNewestFilingText(data) : [];
|
|
456
|
+
return [
|
|
457
|
+
...(filing ? [{ id: "filing", asOf: true, parts: [{ text: filing, tone: "muted" as const }],
|
|
458
|
+
variants: shorter.map((text) => [{ text, tone: "muted" as const }]) }] : []),
|
|
459
|
+
...(data && updatedAgo ? [ageFooterSegment(updatedAgo)] : []),
|
|
460
|
+
];
|
|
461
|
+
}, [data, updatedAgo]);
|
|
448
462
|
useAutoRefresh(resource.updatedAt, resource.load);
|
|
449
463
|
usePaneRefreshKey(() => void resource.reload(), { focused });
|
|
450
464
|
usePaneNoticeFooter({
|
|
@@ -465,7 +479,7 @@ export function DebtMaturitiesPane({ width, height, focused }: PaneProps) {
|
|
|
465
479
|
? debtFilingUrl(data.cik, selectedHistoryRow.accession)
|
|
466
480
|
: (latest?.filingUrl ?? data?.source.url ?? null),
|
|
467
481
|
showOpenHint: true,
|
|
468
|
-
info:
|
|
482
|
+
info: filingFooter,
|
|
469
483
|
stale: !!data && resource.data?.stale,
|
|
470
484
|
});
|
|
471
485
|
const renderBucket = (
|
|
@@ -288,7 +288,7 @@ export const debugPlugin: GloomPlugin = {
|
|
|
288
288
|
name: "Debug Log",
|
|
289
289
|
icon: "D",
|
|
290
290
|
component: DebugPane,
|
|
291
|
-
reportFreshness: { status: "not-a-feed", basis: "debug log" },
|
|
291
|
+
reportFreshness: { status: "not-a-feed", basis: "debug log", local: true },
|
|
292
292
|
defaultPosition: "right",
|
|
293
293
|
defaultMode: "floating",
|
|
294
294
|
defaultFloatingSize: { width: 120, height: 30 },
|
|
@@ -75,6 +75,19 @@ function statedRateSummary(stated: StatedForwardRate): QuoteSummaryDividendField
|
|
|
75
75
|
interface DividendReadOptions {
|
|
76
76
|
/** The forward rate fundamentals state (statedForwardRate), asked only when the summary is missing. */
|
|
77
77
|
loadStatedForwardRate?: () => Promise<StatedForwardRate | null> | StatedForwardRate | null;
|
|
78
|
+
/** The history already asked for (requestDividendHistory), so it need not wait for the quote the price comes from. */
|
|
79
|
+
history?: Promise<DividendHistoryResponse>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
type DividendHistoryResponse = Awaited<ReturnType<typeof apiClient.getMarketDividends>>;
|
|
83
|
+
|
|
84
|
+
/** Asks Gloom Cloud for a listing's dividend history, for fetchDividendData to read once the price is known. */
|
|
85
|
+
export function requestDividendHistory(symbol: string, exchange = ""): Promise<DividendHistoryResponse> {
|
|
86
|
+
const pending = trackRequest("dividends", () => apiClient.getMarketDividends(symbol, exchange));
|
|
87
|
+
pending.catch(() => {
|
|
88
|
+
// Intentional ignore: fetchDividendData awaits this same promise and reports its failure.
|
|
89
|
+
});
|
|
90
|
+
return pending;
|
|
78
91
|
}
|
|
79
92
|
|
|
80
93
|
let connectionHealth: ConnectionHealthRegistry | null = null;
|
|
@@ -125,8 +138,22 @@ function declaredCashByExDate(declared: DeclaredCash | undefined, cashCurrency:
|
|
|
125
138
|
? [[row.exDate, { amount: row.amount / unit.divisor, currency: unit.currency }] as const] : []));
|
|
126
139
|
}
|
|
127
140
|
|
|
141
|
+
/**
|
|
142
|
+
* The currency the company declares in when it pays the listing another
|
|
143
|
+
* (South32's dollars, which its rand line converts): the listing's amounts are
|
|
144
|
+
* conversions of it. Undefined for a listing paid in what was declared.
|
|
145
|
+
*/
|
|
146
|
+
function declaredCurrencyOf(declared: DeclaredCash | undefined, cashCurrency: string): string | undefined {
|
|
147
|
+
const currency = resolveCurrencyUnit(declared?.currency).currency;
|
|
148
|
+
return currency && currency !== cashCurrency ? currency : undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
128
151
|
export interface DividendData {
|
|
129
152
|
payments: DividendPayment[];
|
|
153
|
+
/** The currency the company declares in, when its amounts here are converted from it (declaredCurrencyOf). */
|
|
154
|
+
convertedFrom?: string;
|
|
155
|
+
/** Beside convertedFrom, the sub-unit the source states the cash in (GBp, ZAc): converted amounts are written in it. */
|
|
156
|
+
convertedSubUnit?: string;
|
|
130
157
|
/** Declared and unpaid, in `currency`; never in `payments` or any figure. Absent from older servers. */
|
|
131
158
|
declared?: DeclaredDividendAction[];
|
|
132
159
|
metrics: DividendMetrics;
|
|
@@ -155,7 +182,7 @@ export async function fetchDividendData(
|
|
|
155
182
|
currentPriceCurrency?: string,
|
|
156
183
|
options: DividendReadOptions = {},
|
|
157
184
|
): Promise<DividendData> {
|
|
158
|
-
const response = await
|
|
185
|
+
const response = await (options.history ?? requestDividendHistory(symbol, exchange));
|
|
159
186
|
if (!response.data) throw new Error(`No dividend data found for ${symbol}`);
|
|
160
187
|
const { actions: sourceActions, quote: rawQuote, summary, triedAt } = response.data;
|
|
161
188
|
// In the major unit as the history arrives, as every other reader of it gets it.
|
|
@@ -180,6 +207,9 @@ export async function fetchDividendData(
|
|
|
180
207
|
historyError = MISSING_DIVIDEND_CURRENCY;
|
|
181
208
|
}
|
|
182
209
|
const declaredCash = cashCurrency ? declaredCashByExDate(actions?.declaredCash, cashCurrency) : new Map();
|
|
210
|
+
const convertedFrom = cashCurrency ? declaredCurrencyOf(actions?.declaredCash, cashCurrency) : undefined;
|
|
211
|
+
const sourceUnit = sourceActions?.currency;
|
|
212
|
+
const convertedSubUnit = convertedFrom && sourceUnit && resolveCurrencyUnit(sourceUnit).divisor !== 1 ? sourceUnit : undefined;
|
|
183
213
|
const payments = actions && cashCurrency ? actions.dividends.flatMap(action => {
|
|
184
214
|
const payment = toDividendPayment(action.exDate, action.amount, cashCurrency);
|
|
185
215
|
const declared = declaredCash.get(action.exDate);
|
|
@@ -203,7 +233,7 @@ export async function fetchDividendData(
|
|
|
203
233
|
throw new Error(`No dividend data found for ${symbol}`);
|
|
204
234
|
}
|
|
205
235
|
return {
|
|
206
|
-
payments, ...(declared.length > 0 ? { declared } : {}), metrics, price, currency: currency || undefined, historyAvailable,
|
|
236
|
+
payments, ...(declared.length > 0 ? { declared } : {}), ...(convertedFrom ? { convertedFrom } : {}), ...(convertedSubUnit ? { convertedSubUnit } : {}), metrics, price, currency: currency || undefined, historyAvailable,
|
|
207
237
|
...(suppliedPrice == null && price != null && quote ? dividendQuotePriceMetadata(quote) : {}),
|
|
208
238
|
...(historyError ? { historyError } : {}), ...(summaryError ? { summaryError } : {}),
|
|
209
239
|
...(statedFields && metrics.forwardRate != null ? { forwardFromFundamentals: true } : {}),
|
|
@@ -4,19 +4,21 @@ import type {
|
|
|
4
4
|
HeadlessPaneDefinition,
|
|
5
5
|
HeadlessPaneLoadArgs,
|
|
6
6
|
} from "../../../types/plugin";
|
|
7
|
-
import { fetchDividendData, statedForwardRate, type DividendData } from "./client";
|
|
7
|
+
import { fetchDividendData, requestDividendHistory, statedForwardRate, type DividendData } from "./client";
|
|
8
8
|
import { dividendReferencePrice } from "../../../utils/trailing-dividend";
|
|
9
9
|
import type { DividendPayment, GrowthPayments } from "./types";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
convertedFromText, formatConvertedAmount, formatDividendYield, growthPaymentsText, PAYOUT_NOT_MEANINGFUL, staleHistoryText, toDividendRows, trailingPaymentsText,
|
|
12
|
+
} from "./view";
|
|
11
13
|
import { formatDistributionAmount, formatPercent } from "../../../utils/format";
|
|
12
14
|
import { forwardDividendBasis, noCurrentDividend, TRAILING_DIVIDEND_BASIS, UNEVEN_REGULAR_PAYMENTS } from "../../../utils/forward-dividend";
|
|
13
15
|
import { currencyPriceMinDigits, formatMarketPriceWithCurrency } from "../../../market-data/market/format";
|
|
14
16
|
import type { Quote } from "../../../types/financials";
|
|
15
17
|
import { regularSessionValuationPrice } from "../../../market-data/quotes/regular-session-valuation";
|
|
16
18
|
import { dividendPriceStatus, dividendQuotePriceMetadata } from "./reference-price";
|
|
17
|
-
import { mapWithConcurrency } from "../../../utils/concurrency";
|
|
18
19
|
import { logFailure } from "../../../utils/log-failure";
|
|
19
|
-
import { projectDividendRanking, roundedRatio
|
|
20
|
+
import { projectDividendRanking, roundedRatio } from "./ranking";
|
|
21
|
+
import { MAX_RANKED_SYMBOLS, readDividendRanking, type DividendRankingReads } from "./ranking-reads";
|
|
20
22
|
import { REPORT_FILTER, REPORT_ROWS } from "../shared/headless-options";
|
|
21
23
|
import type { FundBondFacts } from "../../../api-client/fund-facts";
|
|
22
24
|
import { resolveTickerInstrumentKind } from "../../../tickers/instrument-kind";
|
|
@@ -27,31 +29,35 @@ import {
|
|
|
27
29
|
type TaxEquivalentAnalysis, type TreasuryTenor,
|
|
28
30
|
} from "../../../market-data/tax-equivalent";
|
|
29
31
|
|
|
30
|
-
/** Symbols one `fn DVD` ranks at most, and how many it reads at once. */
|
|
31
|
-
const MAX_RANKED_SYMBOLS = 25;
|
|
32
|
-
const RANKING_CONCURRENCY = 4;
|
|
33
32
|
/** The history options' defaults: a ranking lists no history, so only these leave it unchanged. */
|
|
34
33
|
const DEFAULT_TYPE = "all";
|
|
35
34
|
const DEFAULT_LIMIT = 40;
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
/** The history's columns; amounts converted from the currency the company declares in go to two decimals of their source's unit. */
|
|
37
|
+
function paymentColumns(converted: boolean, subUnit?: string) {
|
|
38
|
+
const amount = converted ? (value: number, currency: string) => formatConvertedAmount(value, currency, subUnit) : formatDistributionAmount;
|
|
39
|
+
return [
|
|
40
|
+
{ key: "exDate", header: "Ex-date" },
|
|
41
|
+
{ key: "amount", header: "Amount", align: "right" as const,
|
|
42
|
+
format: (value: unknown, row: Record<string, unknown>) => typeof value === "number" && typeof row.currency === "string" && row.currency
|
|
43
|
+
? amount(value, row.currency) : value == null ? "-" : String(value) },
|
|
44
|
+
{ key: "currency", header: "CCY" },
|
|
45
|
+
{ key: "type", header: "Type" },
|
|
46
|
+
];
|
|
47
|
+
}
|
|
45
48
|
|
|
46
49
|
/** After the amount, for a company declaring in another currency than the listing pays: what it declared. */
|
|
47
50
|
const DECLARED_COLUMN = { key: "declared", header: "Declared", align: "right" as const,
|
|
48
51
|
format: (value: unknown, row: Record<string, unknown>) => typeof value === "number" && typeof row.declaredCurrency === "string"
|
|
49
52
|
? formatDistributionAmount(value, row.declaredCurrency) : "-" };
|
|
50
53
|
|
|
54
|
+
/** What a ticker's read needs: the market data, Gloom Cloud, and the listing a typed key stands for. */
|
|
55
|
+
export type DividendReadContext = Pick<HeadlessPaneContext, "marketData" | "apiClient" | "resolveInstrument">;
|
|
56
|
+
|
|
51
57
|
export interface DividendYieldHeadlessDependencies {
|
|
52
|
-
loadData(symbol: string, context:
|
|
58
|
+
loadData(symbol: string, context: DividendReadContext): Promise<DividendData>;
|
|
53
59
|
/** A US-listed fund's bond facts (SEC yield, yield to worst, duration); null for any other listing. */
|
|
54
|
-
loadBond?(symbol: string, context:
|
|
60
|
+
loadBond?(symbol: string, context: DividendReadContext): Promise<FundBondFacts | null>;
|
|
55
61
|
/** The Treasury par curve GC shows, for the muni/Treasury ratio; null when it cannot be read. */
|
|
56
62
|
loadTreasury?(context: HeadlessPaneContext): Promise<TreasuryTenor[] | null>;
|
|
57
63
|
}
|
|
@@ -68,6 +74,8 @@ const defaultDependencies: DividendYieldHeadlessDependencies = {
|
|
|
68
74
|
const instrument = await context.resolveInstrument?.(symbol);
|
|
69
75
|
const listingSymbol = instrument?.symbol ?? symbol;
|
|
70
76
|
const exchange = instrument?.exchange ?? "";
|
|
77
|
+
// The history is asked for beside the quote, not after it: only its yields need the price.
|
|
78
|
+
const history = requestDividendHistory(listingSymbol, exchange);
|
|
71
79
|
try {
|
|
72
80
|
const quote = await context.marketData.getQuote(listingSymbol, exchange);
|
|
73
81
|
// The regular session's price: after the close, the close rather than an after-hours print.
|
|
@@ -79,6 +87,7 @@ const defaultDependencies: DividendYieldHeadlessDependencies = {
|
|
|
79
87
|
currentPrice = null;
|
|
80
88
|
}
|
|
81
89
|
const data = await fetchDividendData(listingSymbol, currentPrice, exchange, currentPriceCurrency, {
|
|
90
|
+
history,
|
|
82
91
|
// Read only when the summary is missing: the forward rate fundamentals state, as `fundamentals` shows it.
|
|
83
92
|
loadStatedForwardRate: async () => statedForwardRate(await context.marketData.getTickerFinancials(listingSymbol, exchange)),
|
|
84
93
|
});
|
|
@@ -102,6 +111,25 @@ const defaultDependencies: DividendYieldHeadlessDependencies = {
|
|
|
102
111
|
loadTreasury: (context) => loadTreasuryTenors(context.apiClient),
|
|
103
112
|
};
|
|
104
113
|
|
|
114
|
+
/**
|
|
115
|
+
* How a ranking reads each ticker: `fn DVD`'s, and the DVD pane's with the
|
|
116
|
+
* app's market data, so both read the same listing at the same price. A
|
|
117
|
+
* fund's bond facts never fail its row: without them it ranks as before.
|
|
118
|
+
*/
|
|
119
|
+
export function dividendRankingReads(
|
|
120
|
+
context: DividendReadContext,
|
|
121
|
+
dependencies: Pick<DividendYieldHeadlessDependencies, "loadData" | "loadBond"> = defaultDependencies,
|
|
122
|
+
): DividendRankingReads {
|
|
123
|
+
const { loadBond } = dependencies;
|
|
124
|
+
return {
|
|
125
|
+
loadData: (symbol) => dependencies.loadData(symbol, context),
|
|
126
|
+
...loadBond ? {
|
|
127
|
+
loadBond: (symbol: string) => loadBond(symbol, context).catch((error) => error instanceof BondClassificationError ? null
|
|
128
|
+
: logFailure("dividend-yield.headless", "Read bond fund facts for ranking failed", null)(error)),
|
|
129
|
+
} : {},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
105
133
|
/** A growth figure, or what its short year holds: "n/a: 1 of 2 payments". */
|
|
106
134
|
function growthText(growth: number | null, payments: GrowthPayments | null | undefined): string {
|
|
107
135
|
return growth == null && payments ? growthPaymentsText(payments) : formatPercent(growth ?? undefined);
|
|
@@ -147,10 +175,16 @@ export function projectDividendYieldHeadless(
|
|
|
147
175
|
...(row.declared ? { declared: row.declared.amount, declaredCurrency: row.declared.currency } : {}),
|
|
148
176
|
};
|
|
149
177
|
});
|
|
178
|
+
// A listing paid in another currency than its company declares (South32's rand) has its amounts converted from that one.
|
|
179
|
+
const convertedFrom = data.convertedFrom;
|
|
180
|
+
const subUnit = data.convertedSubUnit;
|
|
181
|
+
const paymentColumnList = paymentColumns(!!convertedFrom, subUnit);
|
|
150
182
|
const declaredColumns = rows.some((row) => row.declared)
|
|
151
|
-
? [...
|
|
183
|
+
? [...paymentColumnList.slice(0, 2), DECLARED_COLUMN, ...paymentColumnList.slice(2)] : paymentColumnList;
|
|
152
184
|
const metrics = data.metrics;
|
|
153
185
|
const currency = data.currency ?? data.payments[0]?.currency ?? "";
|
|
186
|
+
const cashRate = (value: number | null) => !currency ? "—" : !convertedFrom ? formatDistributionAmount(value ?? undefined, currency)
|
|
187
|
+
: value == null ? formatConvertedAmount(value, currency) : `${formatConvertedAmount(value, currency, subUnit)} (${convertedFromText(convertedFrom)})`;
|
|
154
188
|
const priceStatus = dividendPriceStatus(data.price, data.priceAsOf, data.priceStale);
|
|
155
189
|
// A regular payer's year with fewer payments than its cadence pays: the yield says how many it counts.
|
|
156
190
|
const shortfall = trailingPaymentsText(metrics.trailingPayments, { cadence: true });
|
|
@@ -180,8 +214,8 @@ export function projectDividendYieldHeadless(
|
|
|
180
214
|
value: figure.value,
|
|
181
215
|
formatted: `${figure.text}${figure.date ? ` (as of ${figure.date})` : ""}`,
|
|
182
216
|
})) : []),
|
|
183
|
-
{ label: "Trailing rate", value: metrics.trailingRate, formatted:
|
|
184
|
-
{ label: "Forward rate", value: metrics.forwardRate, formatted:
|
|
217
|
+
{ label: "Trailing rate", value: metrics.trailingRate, formatted: cashRate(metrics.trailingRate) },
|
|
218
|
+
{ label: "Forward rate", value: metrics.forwardRate, formatted: cashRate(metrics.forwardRate) },
|
|
185
219
|
{ label: "Earnings Payout", value: roundedRatio(metrics.payoutRatio), formatted: metrics.payoutNotMeaningful ? PAYOUT_NOT_MEANINGFUL : formatDividendYield(metrics.payoutRatio) },
|
|
186
220
|
{ label: "1Y Cash Growth", value: roundedRatio(metrics.growth1Y), formatted: growthText(metrics.growth1Y, metrics.growth1YPayments) },
|
|
187
221
|
{ label: "3Y Cash CAGR", value: roundedRatio(metrics.growth3Y), formatted: growthText(metrics.growth3Y, metrics.growth3YPayments) },
|
|
@@ -198,7 +232,7 @@ export function projectDividendYieldHeadless(
|
|
|
198
232
|
entries: taxEquivalentLines(tax).map((line) => ({ label: line.label, value: line.value, formatted: line.text })),
|
|
199
233
|
}] : []),
|
|
200
234
|
{
|
|
201
|
-
title: "Dividend history",
|
|
235
|
+
title: convertedFrom ? `Dividend history (amounts ${convertedFromText(convertedFrom)})` : "Dividend history",
|
|
202
236
|
columns: declaredColumns,
|
|
203
237
|
rows,
|
|
204
238
|
// Each amount is in its own row's currency.
|
|
@@ -211,6 +245,8 @@ export function projectDividendYieldHeadless(
|
|
|
211
245
|
truncated: rows.length < matching.length,
|
|
212
246
|
type,
|
|
213
247
|
currency: data.currency ?? data.payments[0]?.currency ?? null,
|
|
248
|
+
// The currency the company declares in, when the amounts are converted from it; null for one paid as declared.
|
|
249
|
+
convertedFrom: convertedFrom ?? null,
|
|
214
250
|
historyAvailable: data.historyAvailable ?? true,
|
|
215
251
|
historyError: data.historyError ?? null,
|
|
216
252
|
summaryError: data.summaryError ?? null,
|
|
@@ -321,18 +357,7 @@ export function createDividendYieldHeadless(
|
|
|
321
357
|
if (historyOptions.length > 0) {
|
|
322
358
|
throw new Error(`${historyOptions.join(" and ")} ${historyOptions.length === 1 ? "filters" : "filter"} one ticker's payment history, and a ranking of several lists none. Drop ${historyOptions.length === 1 ? "it" : "them"}, or run DVD on one ticker.`);
|
|
323
359
|
}
|
|
324
|
-
|
|
325
|
-
// A fund's bond facts never fail its row: without them it ranks as before.
|
|
326
|
-
const bond = dependencies.loadBond?.(symbol, context)
|
|
327
|
-
.catch((error) => error instanceof BondClassificationError ? null
|
|
328
|
-
: logFailure("dividend-yield.headless", "Read bond fund facts for ranking failed", null)(error)) ?? null;
|
|
329
|
-
try {
|
|
330
|
-
return { symbol, data: await dependencies.loadData(symbol, context), bond: await bond };
|
|
331
|
-
} catch (error) {
|
|
332
|
-
return { symbol, error: error instanceof Error ? error.message : String(error) };
|
|
333
|
-
}
|
|
334
|
-
});
|
|
335
|
-
return projectDividendRanking(entries);
|
|
360
|
+
return projectDividendRanking(await readDividendRanking(args.symbols, dividendRankingReads(context, dependencies)));
|
|
336
361
|
},
|
|
337
362
|
};
|
|
338
363
|
}
|
|
@@ -8,6 +8,9 @@ import {
|
|
|
8
8
|
} from "./client";
|
|
9
9
|
import { DividendYieldPane } from "./pane";
|
|
10
10
|
import { dividendYieldHeadless } from "./headless";
|
|
11
|
+
import { RANK_SYMBOLS_SETTING } from "./ranking-view";
|
|
12
|
+
import { MAX_RANKED_SYMBOLS } from "./ranking-reads";
|
|
13
|
+
import type { PaneTemplateCreateOptions, PaneTemplateDef } from "../../../types/plugin";
|
|
11
14
|
|
|
12
15
|
function createDividendYieldModule({
|
|
13
16
|
component = DividendYieldPane,
|
|
@@ -65,7 +68,7 @@ function createDividendYieldModule({
|
|
|
65
68
|
|
|
66
69
|
paneTemplates: [
|
|
67
70
|
{
|
|
68
|
-
...createTickerSurfacePaneTemplate({
|
|
71
|
+
...withRanking(createTickerSurfacePaneTemplate({
|
|
69
72
|
id: "dividend-yield-pane",
|
|
70
73
|
paneId: "dividend-yield",
|
|
71
74
|
label: "Dividend Yield",
|
|
@@ -75,11 +78,43 @@ function createDividendYieldModule({
|
|
|
75
78
|
"dividend growth", "payout ratio", "drip", "reinvest", "reinvestment", "dividend reinvestment", "rank dividend payers",
|
|
76
79
|
"sec yield", "distribution yield", "muni", "munis", "municipal", "municipal bonds", "tax-exempt", "tax-equivalent yield", "muni treasury ratio"],
|
|
77
80
|
shortcut: "DVD",
|
|
78
|
-
}),
|
|
81
|
+
})),
|
|
79
82
|
headless,
|
|
80
83
|
},
|
|
81
84
|
],
|
|
82
85
|
};
|
|
83
86
|
}
|
|
84
87
|
|
|
88
|
+
/**
|
|
89
|
+
* The tickers a DVD is opened with when there are several: the list a command
|
|
90
|
+
* passes (`shot DVD KO JNJ`), or a comma list typed as its argument.
|
|
91
|
+
*/
|
|
92
|
+
function rankedSymbols(options: PaneTemplateCreateOptions | undefined): string[] {
|
|
93
|
+
const list = options?.symbols?.length ? options.symbols : (options?.arg ?? options?.symbol ?? "").split(",");
|
|
94
|
+
// The first 25, as `fn DVD` ranks at most.
|
|
95
|
+
const symbols = [...new Set(list.map((symbol) => symbol.trim().toUpperCase()).filter(Boolean))].slice(0, MAX_RANKED_SYMBOLS);
|
|
96
|
+
return symbols.length > 1 ? symbols : [];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** DVD with several tickers opens one pane that ranks them by yield, as `fn DVD A B C` does; with one, its own pane. */
|
|
100
|
+
function withRanking(template: PaneTemplateDef): PaneTemplateDef {
|
|
101
|
+
return {
|
|
102
|
+
...template,
|
|
103
|
+
// `fn` and `shot` pass a typed list as `symbols`, so `shot DVD KO JNJ` draws the ranking `fn DVD KO JNJ` prints.
|
|
104
|
+
opensTickerList: true,
|
|
105
|
+
canCreate: (context, options) => rankedSymbols(options).length > 1 || (template.canCreate?.(context, options) ?? true),
|
|
106
|
+
createInstance: (context, options) => {
|
|
107
|
+
const symbols = rankedSymbols(options);
|
|
108
|
+
if (symbols.length < 2) return template.createInstance?.(context, options) ?? null;
|
|
109
|
+
return {
|
|
110
|
+
instanceId: `dividend-yield:rank:${symbols.map((symbol) => encodeURIComponent(symbol)).join(",")}`,
|
|
111
|
+
title: `DVD ${symbols.join(", ")}`,
|
|
112
|
+
binding: { kind: "none" },
|
|
113
|
+
placement: "floating",
|
|
114
|
+
settings: { [RANK_SYMBOLS_SETTING]: symbols },
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
85
120
|
export const dividendYieldModule = createDividendYieldModule();
|
|
@@ -31,7 +31,7 @@ import { isCloudSessionRequired, useResearchCloudSession } from "../shared/resea
|
|
|
31
31
|
import { fetchDividendData, repriceDividendMetrics, statedForwardRate, type DividendData } from "./client";
|
|
32
32
|
import { dividendReferencePrice } from "../../../utils/trailing-dividend";
|
|
33
33
|
import { useTickerQuoteStream } from "../../../state/hooks/live-ticker-financials";
|
|
34
|
-
import { buildTrailingCashChartPoints, formatDividendYield, growthPaymentsDetail, PAYOUT_NOT_MEANINGFUL, staleHistoryText, toDividendRows, trailingPaymentsText, type DividendRow } from "./view";
|
|
34
|
+
import { buildTrailingCashChartPoints, convertedFromText, formatConvertedAmount, formatDividendYield, growthPaymentsDetail, PAYOUT_NOT_MEANINGFUL, staleHistoryText, toDividendRows, trailingPaymentsText, type DividendRow } from "./view";
|
|
35
35
|
import {
|
|
36
36
|
DEFAULT_SORT_PREFERENCE,
|
|
37
37
|
buildDividendColumns,
|
|
@@ -46,11 +46,14 @@ import type { FundBondFacts } from "../../../api-client/fund-facts";
|
|
|
46
46
|
import { resolveTickerInstrumentKind } from "../../../tickers/instrument-kind";
|
|
47
47
|
import { mayBeUsEquityOrFundTicker } from "../../../utils/sec";
|
|
48
48
|
import { useFundFacts } from "../sec/fund-facts-view";
|
|
49
|
+
import { usePaneInstance } from "../../../state/app/context";
|
|
50
|
+
import { DividendRankingView, rankSymbolsOf } from "./ranking-view";
|
|
49
51
|
import { bondFigures } from "../../../market-data/bond-funds";
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
/** A per-share rate; one converted from the currency the company declares in goes to two decimals of its source's unit. */
|
|
54
|
+
function formatRate(value: number | null, currency: string, converted = false, subUnit?: string): string {
|
|
52
55
|
if (value == null) return "—";
|
|
53
|
-
return formatDistributionAmount(value, currency);
|
|
56
|
+
return converted ? formatConvertedAmount(value, currency, subUnit) : formatDistributionAmount(value, currency);
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
function formatGrowth(value: number | null): string {
|
|
@@ -90,7 +93,9 @@ function formatFrequency(freq: DividendMetrics["paymentFrequency"]): string {
|
|
|
90
93
|
* say they are not its SEC yield, and the SEC yield follows them with its date.
|
|
91
94
|
*/
|
|
92
95
|
function buildMetricItems(metrics: DividendMetrics, currency: string, hasHistory: boolean, short: boolean,
|
|
93
|
-
declared: DividendData["declared"], bond: FundBondFacts | null = null): StatItem[] {
|
|
96
|
+
declared: DividendData["declared"], bond: FundBondFacts | null = null, convertedFrom?: string, convertedSubUnit?: string): StatItem[] {
|
|
97
|
+
// The rates of a listing paid in a conversion of what its company declares say what they were converted from.
|
|
98
|
+
const converted = convertedFrom ? { detail: convertedFromText(convertedFrom) } : {};
|
|
94
99
|
const exDate: StatItem[] = metrics.nextExDividendDate
|
|
95
100
|
? [{ id: "next-ex", label: "Next ex", value: formatDate(metrics.nextExDividendDate) }]
|
|
96
101
|
: hasHistory ? [] : [{ id: "last-ex", label: "Last ex", value: formatDate(metrics.lastExDividendDate) }];
|
|
@@ -116,8 +121,10 @@ function buildMetricItems(metrics: DividendMetrics, currency: string, hasHistory
|
|
|
116
121
|
...(forwardNote ? { detail: forwardNote } : {}) },
|
|
117
122
|
// The 30-day SEC yield as the fund's sponsor publishes it, dated.
|
|
118
123
|
...(sec ? [{ id: "sec-yield", label: short ? "SEC yld" : "SEC yield", value: sec.text, ...(sec.date ? { detail: `as of ${sec.date}` } : {}) }] : []),
|
|
119
|
-
{ id: "ttm-rate", label: short ? "TTM/sh" : "TTM/share", value: formatRate(metrics.trailingRate, currency
|
|
120
|
-
|
|
124
|
+
{ id: "ttm-rate", label: short ? "TTM/sh" : "TTM/share", value: formatRate(metrics.trailingRate, currency, !!convertedFrom, convertedSubUnit),
|
|
125
|
+
...metrics.trailingRate != null ? converted : {} },
|
|
126
|
+
{ id: "forward-rate", label: short ? "Fwd/sh" : "Fwd/share", value: formatRate(metrics.forwardRate, currency, !!convertedFrom, convertedSubUnit),
|
|
127
|
+
...metrics.forwardRate != null ? converted : {} },
|
|
121
128
|
growthItem("growth-1y", short ? "1Y chg" : "1Y growth", metrics.growth1Y, metrics.growth1YPayments),
|
|
122
129
|
growthItem("cagr-3y", "3Y CAGR", metrics.growth3Y, metrics.growth3YPayments),
|
|
123
130
|
{ id: "payout", label: "Payout", value: metrics.payoutNotMeaningful ? PAYOUT_NOT_MEANINGFUL : metrics.payoutRatio != null ? `${(metrics.payoutRatio * 100).toFixed(1)}%` : "—" },
|
|
@@ -144,13 +151,15 @@ const rowKey = (row: DividendRow) => row.key;
|
|
|
144
151
|
function renderCell(
|
|
145
152
|
row: DividendRow,
|
|
146
153
|
column: DividendColumn,
|
|
154
|
+
converted = false,
|
|
155
|
+
subUnit?: string,
|
|
147
156
|
): DataTableCell {
|
|
148
157
|
switch (column.id) {
|
|
149
158
|
case "exDate":
|
|
150
159
|
return { text: row.exDate, color: colors.textDim };
|
|
151
160
|
case "amount":
|
|
152
161
|
return {
|
|
153
|
-
text: formatDistributionAmount(row.amount, row.currency),
|
|
162
|
+
text: converted ? formatConvertedAmount(row.amount, row.currency, subUnit) : formatDistributionAmount(row.amount, row.currency),
|
|
154
163
|
color: colors.textBright,
|
|
155
164
|
attributes: TextAttributes.BOLD,
|
|
156
165
|
};
|
|
@@ -161,9 +170,15 @@ function renderCell(
|
|
|
161
170
|
}
|
|
162
171
|
}
|
|
163
172
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
173
|
+
type DividendPaneProps = { focused: boolean; width: number; height: number; loadData?: typeof fetchDividendData };
|
|
174
|
+
|
|
175
|
+
/** One ticker's dividends, or several ranked by yield when the pane was opened with a list (`shot DVD KO JNJ`). */
|
|
176
|
+
export function DividendYieldPane(props: DividendPaneProps) {
|
|
177
|
+
const ranked = rankSymbolsOf(usePaneInstance()?.settings);
|
|
178
|
+
return ranked.length > 1 ? <DividendRankingView symbols={ranked} {...props} /> : <DividendTickerPane {...props} />;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function DividendTickerPane({ focused, width, height, loadData = fetchDividendData }: DividendPaneProps) {
|
|
167
182
|
const { symbol, ticker, financials } = usePaneTicker();
|
|
168
183
|
const { nativePaneChrome } = useUiCapabilities();
|
|
169
184
|
// Yields are repriced from the quote on every render, so streaming it is all
|
|
@@ -275,13 +290,17 @@ export function DividendYieldPane({ focused, width, height, loadData = fetchDivi
|
|
|
275
290
|
});
|
|
276
291
|
// The header row, plus the scrollbar row once the columns overflow the pane.
|
|
277
292
|
const tableChromeRows = chartTableChromeRows(columns, width);
|
|
278
|
-
const
|
|
293
|
+
const convertedFrom = historyData?.convertedFrom;
|
|
294
|
+
const convertedSubUnit = historyData?.convertedSubUnit;
|
|
295
|
+
const renderRowCell = useCallback((row: DividendRow, column: DividendColumn) => renderCell(row, column, !!convertedFrom, convertedSubUnit),
|
|
296
|
+
[convertedFrom, convertedSubUnit]);
|
|
297
|
+
const fullFigures = metrics ? buildMetricItems(metrics, currency, sortedRows.length > 0, false, data?.declared, bond, convertedFrom, convertedSubUnit) : [];
|
|
279
298
|
const keptFigures = chartTableLayout({
|
|
280
299
|
width, height, figures: fullFigures, tableRows: sortedRows.length, tableChromeRows, chart: chartPoints.length >= 2 ? {} : null,
|
|
281
300
|
}).figures;
|
|
282
301
|
// The desktop grid never clips a label; the terminal shortens them rather than clip.
|
|
283
302
|
const figures = metrics && !nativePaneChrome && labelsClip(keptFigures, width)
|
|
284
|
-
? buildMetricItems(metrics, currency, sortedRows.length > 0, true, data?.declared, bond) : fullFigures;
|
|
303
|
+
? buildMetricItems(metrics, currency, sortedRows.length > 0, true, data?.declared, bond, convertedFrom, convertedSubUnit) : fullFigures;
|
|
285
304
|
|
|
286
305
|
const handleHeaderClick = useCallback((columnId: string) => {
|
|
287
306
|
setSortPreference((current) => nextHeaderSort(current, columnId as DividendColumnId, {
|
|
@@ -324,7 +343,7 @@ export function DividendYieldPane({ focused, width, height, loadData = fetchDivi
|
|
|
324
343
|
sortDirection={sortPreference.direction}
|
|
325
344
|
onHeaderClick={handleHeaderClick}
|
|
326
345
|
getItemKey={rowKey}
|
|
327
|
-
renderCell={
|
|
346
|
+
renderCell={renderRowCell}
|
|
328
347
|
selectedTextOverridesCellColor
|
|
329
348
|
emptyStateTitle={emptyTitle}
|
|
330
349
|
/>
|