gloomberb 0.18.4 → 0.19.0
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +169 -5
- package/src/account/auth-model.ts +7 -3
- package/src/api-client/auth.ts +9 -4
- package/src/api-client/catalysts.ts +2 -1
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +31 -6
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/index.ts +171 -11
- package/src/api-client/live-scan.ts +91 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +13 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/request.ts +222 -41
- package/src/api-client/research-activity.ts +6 -12
- package/src/api-client/types.ts +74 -0
- package/src/app/keybindings/index.ts +3 -0
- package/src/app/keybindings/labels.ts +18 -1
- package/src/app/keybindings/resolve.ts +14 -0
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +42 -27
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/core-builtin-allowlist.json +1 -1
- package/src/architecture/pane-tabs-allowlist.json +3 -0
- package/src/architecture/ttl-caches-allowlist.json +19 -0
- package/src/brokers/sync-broker-instance.ts +2 -8
- package/src/capabilities/chart-series.ts +1 -1
- package/src/cli/commands/crypto-hints.ts +40 -24
- package/src/cli/commands/fred-series.ts +5 -0
- package/src/cli/commands/keybindings.ts +23 -0
- package/src/cli/commands/market.ts +138 -46
- package/src/cli/commands/options-chain.ts +107 -10
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +176 -39
- package/src/cli/commands/search.ts +39 -3
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +57 -35
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +10 -2
- package/src/cli/export-source.ts +29 -1
- package/src/cli/glossary.ts +73 -1
- package/src/cli/history-rows.ts +87 -16
- package/src/cli/index.ts +2 -0
- package/src/cli/known-gaps.ts +5 -2
- package/src/cli/listing-arg.ts +195 -56
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +3 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +111 -16
- package/src/cli/pane-functions/function-help.ts +23 -13
- package/src/cli/pane-functions/headless.ts +56 -12
- package/src/cli/pane-functions/index.ts +58 -16
- package/src/cli/pane-functions/market-session.ts +81 -9
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/report.ts +5 -1
- package/src/cli/pane-functions/resolver.ts +35 -8
- package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
- package/src/cli/pane-functions/screenshot-types.ts +5 -0
- package/src/cli/quote-answer.ts +30 -13
- package/src/cli/report-tables.ts +18 -7
- package/src/cli/restore-plugins.ts +13 -1
- package/src/cli/result.ts +57 -5
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +26 -4
- package/src/components/chart/composite/format.ts +27 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/levels.ts +2 -0
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +61 -5
- package/src/components/chart/composite/range-statistics.ts +62 -0
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +46 -7
- package/src/components/chart/composite/series-axis-tags.tsx +16 -9
- package/src/components/chart/composite/series-cues.ts +14 -0
- package/src/components/chart/composite/text-renderer.ts +9 -2
- package/src/components/chart/composite/tool-catalog.ts +36 -0
- package/src/components/chart/composite/tools.ts +203 -44
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
- package/src/components/command-bar/assist/inventory.ts +2 -1
- package/src/components/command-bar/commands/direct/index.ts +28 -13
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/panel/prompt-binding.ts +0 -8
- package/src/components/command-bar/workflow/alert.ts +1 -1
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/header.tsx +26 -40
- package/src/components/layout/pane/footer/index.tsx +1 -0
- package/src/components/layout/pane/footer/model.ts +27 -0
- package/src/components/onboarding/onboarding-frame.tsx +1 -1
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
- package/src/components/onboarding/wizard-broker-sync.ts +0 -1
- package/src/components/onboarding/wizard-positions.ts +3 -3
- package/src/components/ticker/list-table-view.tsx +4 -1
- package/src/components/ui/choice-dialog.tsx +23 -18
- package/src/components/ui/choice-groups.ts +18 -0
- package/src/components/ui/data-table/opentui/index.tsx +9 -7
- package/src/components/ui/data-table/opentui/model.ts +26 -1
- package/src/components/ui/data-table/types.ts +2 -0
- package/src/components/ui/display.tsx +5 -1
- package/src/components/ui/list-view.tsx +8 -4
- package/src/components/ui/query-bar-keys.tsx +2 -0
- package/src/components/ui/query-bar.tsx +1 -0
- package/src/components/ui/select-button.tsx +5 -1
- package/src/components/ui/select-field.tsx +1 -0
- package/src/components/ui/table-layout.ts +8 -5
- package/src/core/app-runtime.ts +2 -0
- package/src/core/app-services.ts +2 -2
- package/src/core/state/app/layout.ts +2 -0
- package/src/core/state/app/state.ts +9 -0
- package/src/core/state/app/types.ts +3 -0
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/saved-copies.ts +55 -16
- package/src/data/ticker-repository.ts +16 -9
- package/src/data/ticker-writes.ts +81 -0
- package/src/data/ttl-cache.ts +165 -0
- package/src/docs/snapshot.json +427 -216
- package/src/i18n/es.ts +19 -3
- package/src/i18n/ja.ts +19 -3
- package/src/i18n/ko.ts +19 -3
- package/src/i18n/zh-cn.ts +19 -3
- package/src/i18n/zh-tw.ts +19 -3
- package/src/market-data/coordinator/financials.ts +10 -2
- package/src/market-data/coordinator/index.ts +3 -1
- package/src/market-data/coordinator/quotes.ts +4 -2
- package/src/market-data/depositary-pair.ts +2 -2
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +48 -7
- package/src/market-data/market/dated-previous-close.ts +203 -0
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/market/futures-week.ts +68 -0
- package/src/market-data/market/status.ts +16 -4
- package/src/market-data/options-alternatives.ts +141 -4
- package/src/market-data/quotes/contributions.ts +20 -2
- package/src/market-data/quotes/resolution.ts +3 -0
- package/src/market-data/selectors.ts +3 -0
- package/src/news/aggregator.ts +113 -9
- package/src/news/cloud-session.ts +21 -0
- package/src/news/hooks.ts +1 -0
- package/src/news/news-model.ts +1 -0
- package/src/news/types.ts +11 -0
- package/src/notifications/notification-log.ts +6 -4
- package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +380 -82
- package/src/plugins/builtin/alerts/command.ts +31 -0
- package/src/plugins/builtin/alerts/constants.ts +1 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
- package/src/plugins/builtin/alerts/fired-log.ts +113 -8
- package/src/plugins/builtin/alerts/format.ts +80 -4
- package/src/plugins/builtin/alerts/headless.ts +30 -7
- package/src/plugins/builtin/alerts/history-rows.ts +7 -3
- package/src/plugins/builtin/alerts/history.ts +2 -0
- package/src/plugins/builtin/alerts/index.tsx +135 -22
- package/src/plugins/builtin/alerts/live.ts +3 -3
- package/src/plugins/builtin/alerts/metric-command.ts +134 -0
- package/src/plugins/builtin/alerts/metric-data.ts +111 -0
- package/src/plugins/builtin/alerts/metric-model.ts +324 -0
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
- package/src/plugins/builtin/alerts/pane.tsx +90 -39
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +37 -10
- package/src/plugins/builtin/alerts/types.ts +7 -0
- package/src/plugins/builtin/analytics/index.tsx +2 -1
- package/src/plugins/builtin/analytics/risk-client.ts +16 -5
- package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
- package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
- package/src/plugins/builtin/analytics/risk-model.ts +55 -9
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/attention/index.tsx +2 -1
- package/src/plugins/builtin/attention/listing.ts +14 -0
- package/src/plugins/builtin/backtest/headless.ts +10 -5
- package/src/plugins/builtin/backtest/pane.tsx +9 -4
- package/src/plugins/builtin/backtest/presets.ts +29 -3
- package/src/plugins/builtin/broker-manager/index.tsx +10 -5
- package/src/plugins/builtin/broker-manager/model.ts +6 -1
- package/src/plugins/builtin/broker-trading/controller.ts +22 -12
- package/src/plugins/builtin/broker-trading/index.tsx +13 -1
- package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
- package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +12 -4
- package/src/plugins/builtin/buildout/format.ts +38 -0
- package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
- package/src/plugins/builtin/buildout/table-model.ts +23 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +7 -3
- package/src/plugins/builtin/catalysts/model.ts +2 -1
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
- package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
- package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
- package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
- package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
- package/src/plugins/builtin/chart-composer/formula.ts +71 -18
- package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
- package/src/plugins/builtin/chart-composer/headless.ts +19 -3
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
- package/src/plugins/builtin/chart-composer/presets.ts +50 -15
- package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
- package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
- package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
- package/src/plugins/builtin/chat/content/composer.tsx +13 -6
- package/src/plugins/builtin/chat/content/footer.tsx +6 -3
- package/src/plugins/builtin/chat/content/index.tsx +50 -21
- package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
- package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
- package/src/plugins/builtin/chat/index.tsx +23 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
- package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
- package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
- package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
- package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
- package/src/plugins/builtin/clinical-trials/model.ts +0 -9
- package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- package/src/plugins/builtin/cloud/plugin.tsx +1 -1
- package/src/plugins/builtin/company-attention/building.tsx +5 -1
- package/src/plugins/builtin/company-attention/client.ts +8 -9
- package/src/plugins/builtin/company-attention/evidence.ts +4 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +9 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/correlation/headless.ts +13 -5
- package/src/plugins/builtin/correlation/history.ts +22 -0
- package/src/plugins/builtin/cot/client.ts +22 -18
- package/src/plugins/builtin/cot/headless.ts +15 -10
- package/src/plugins/builtin/cot/model.ts +14 -1
- package/src/plugins/builtin/cot/pane.tsx +22 -14
- package/src/plugins/builtin/credit-boards/client.ts +6 -10
- package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
- package/src/plugins/builtin/credit-conditions/model.ts +4 -0
- package/src/plugins/builtin/credit-conditions/series.ts +3 -1
- package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
- package/src/plugins/builtin/credit-documents/export.ts +173 -0
- package/src/plugins/builtin/credit-documents/headless.ts +7 -7
- package/src/plugins/builtin/credit-documents/model.ts +104 -14
- package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
- package/src/plugins/builtin/custom-view/sources.ts +2 -0
- package/src/plugins/builtin/dividend-yield/client.ts +131 -23
- package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
- package/src/plugins/builtin/dividend-yield/model.ts +8 -5
- package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
- package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
- package/src/plugins/builtin/dividend-yield/types.ts +20 -2
- package/src/plugins/builtin/dividend-yield/view.ts +30 -2
- package/src/plugins/builtin/docs/targets.ts +5 -1
- package/src/plugins/builtin/earnings/board-model.ts +16 -15
- package/src/plugins/builtin/earnings/board.tsx +62 -71
- package/src/plugins/builtin/earnings/headless.ts +40 -4
- package/src/plugins/builtin/earnings/scope.ts +31 -43
- package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
- package/src/plugins/builtin/econ/calendar-model.ts +14 -9
- package/src/plugins/builtin/econ/figures.ts +72 -0
- package/src/plugins/builtin/econ/index.tsx +33 -3
- package/src/plugins/builtin/econ/types.ts +4 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
- package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
- package/src/plugins/builtin/econ-statistics/view.ts +3 -0
- package/src/plugins/builtin/equity-screener/categories.ts +179 -0
- package/src/plugins/builtin/equity-screener/client.ts +4 -2
- package/src/plugins/builtin/equity-screener/headless.ts +144 -13
- package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
- package/src/plugins/builtin/equity-screener/model.ts +99 -10
- package/src/plugins/builtin/equity-screener/pane.tsx +70 -15
- package/src/plugins/builtin/exposure/charts.tsx +2 -2
- package/src/plugins/builtin/exposure/model.ts +13 -1
- package/src/plugins/builtin/exposure/pane.tsx +4 -4
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/index.tsx +1 -0
- package/src/plugins/builtin/futures/model.ts +2 -1
- package/src/plugins/builtin/futures/table.tsx +2 -2
- package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
- package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
- package/src/plugins/builtin/fx-matrix/client.ts +11 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
- package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
- package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -3
- package/src/plugins/builtin/gpu/headless.ts +8 -2
- package/src/plugins/builtin/help/function-index.ts +4 -1
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +18 -1
- package/src/plugins/builtin/iv-history/client.ts +52 -11
- package/src/plugins/builtin/iv-history/format.ts +36 -23
- package/src/plugins/builtin/iv-history/headless.ts +36 -18
- package/src/plugins/builtin/iv-history/index.tsx +2 -2
- package/src/plugins/builtin/iv-history/model.ts +163 -27
- package/src/plugins/builtin/iv-history/pane.tsx +10 -6
- package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
- package/src/plugins/builtin/iv-history/universe.ts +5 -1
- package/src/plugins/builtin/jobs/client.ts +25 -29
- package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
- package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
- package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
- package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
- package/src/plugins/builtin/macro-day/pane.tsx +7 -5
- package/src/plugins/builtin/macro-day/release-table.ts +2 -2
- package/src/plugins/builtin/market-heatmap/data.ts +8 -33
- package/src/plugins/builtin/market-movers/client.ts +25 -7
- package/src/plugins/builtin/market-movers/footer.ts +2 -2
- package/src/plugins/builtin/market-movers/headless.ts +32 -12
- package/src/plugins/builtin/market-movers/index.tsx +26 -14
- package/src/plugins/builtin/market-movers/min-float.ts +74 -0
- package/src/plugins/builtin/market-movers/model.ts +4 -1
- package/src/plugins/builtin/market-movers/screener.ts +14 -0
- package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
- package/src/plugins/builtin/market-movers/session-table.ts +4 -3
- package/src/plugins/builtin/market-movers/table.ts +17 -5
- package/src/plugins/builtin/members/breadth-pane.tsx +82 -0
- package/src/plugins/builtin/members/client.ts +19 -4
- package/src/plugins/builtin/members/headless.ts +20 -7
- package/src/plugins/builtin/members/index.tsx +9 -9
- package/src/plugins/builtin/members/model.ts +23 -4
- package/src/plugins/builtin/members/pane.tsx +25 -15
- package/src/plugins/builtin/news/index.tsx +1 -0
- package/src/plugins/builtin/news/wire/categories.ts +19 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
- package/src/plugins/builtin/openfda/client.ts +14 -2
- package/src/plugins/builtin/openfda/index.tsx +3 -2
- package/src/plugins/builtin/openfda/model.ts +9 -0
- package/src/plugins/builtin/openfda/pane.tsx +42 -6
- package/src/plugins/builtin/options/analytics.ts +54 -6
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/model-spot.ts +5 -2
- package/src/plugins/builtin/options/strike-window.ts +2 -1
- package/src/plugins/builtin/options/table.ts +6 -9
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +42 -12
- package/src/plugins/builtin/options-positioning/client.ts +30 -0
- package/src/plugins/builtin/options-positioning/headless.ts +74 -21
- package/src/plugins/builtin/options-positioning/index.tsx +4 -4
- package/src/plugins/builtin/options-positioning/model.ts +122 -14
- package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
- package/src/plugins/builtin/options-scenario/client.ts +116 -47
- package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
- package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
- package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
- package/src/plugins/builtin/options-scenario/csp.ts +8 -2
- package/src/plugins/builtin/options-scenario/index.tsx +9 -7
- package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
- package/src/plugins/builtin/pe-band/model.ts +12 -49
- package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
- package/src/plugins/builtin/perps/cascade.ts +236 -0
- package/src/plugins/builtin/perps/client.ts +56 -2
- package/src/plugins/builtin/perps/freshness.ts +12 -10
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/live-rows.ts +10 -3
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +32 -31
- package/src/plugins/builtin/perps/status-line.ts +25 -0
- package/src/plugins/builtin/plugin-module.ts +5 -1
- package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
- package/src/plugins/builtin/portfolio-list/cli/render.ts +12 -7
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/rate-path/pane.tsx +2 -2
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/client.ts +18 -4
- package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
- package/src/plugins/builtin/relative-rotation/model.ts +16 -40
- package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
- package/src/plugins/builtin/research/event-model.ts +10 -8
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +11 -9
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
- package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
- package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
- package/src/plugins/builtin/scanner/feed.ts +2 -2
- package/src/plugins/builtin/scanner/flow-model.ts +50 -4
- package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
- package/src/plugins/builtin/scanner/flow-session.ts +146 -0
- package/src/plugins/builtin/scanner/headless.ts +204 -35
- package/src/plugins/builtin/scanner/index.ts +40 -6
- package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
- package/src/plugins/builtin/scanner/live-feed.ts +168 -0
- package/src/plugins/builtin/scanner/live-headless.ts +138 -0
- package/src/plugins/builtin/scanner/live-model.ts +362 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
- package/src/plugins/builtin/scanner/live-track.ts +186 -0
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/headless.ts +10 -1
- package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
- package/src/plugins/builtin/sec/fund-facts.ts +116 -34
- package/src/plugins/builtin/sec/headless.ts +2 -1
- package/src/plugins/builtin/series-inventory.ts +3 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
- package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
- package/src/plugins/builtin/shared/earnings-report.ts +2 -2
- package/src/plugins/builtin/shared/job-posting.ts +1 -0
- package/src/plugins/builtin/shared/macro-releases.ts +65 -0
- package/src/plugins/builtin/shared/members-menu.tsx +3 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
- package/src/plugins/builtin/shared/report-freshness.ts +3 -2
- package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
- package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
- package/src/plugins/builtin/shared/ticker-company.ts +77 -0
- package/src/plugins/builtin/shared/use-quote-board.ts +10 -3
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/headless.ts +6 -4
- package/src/plugins/builtin/themes/model.ts +17 -8
- package/src/plugins/builtin/themes/pane.tsx +7 -7
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
- package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +6 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
- package/src/plugins/builtin/time-sales/pane.tsx +31 -19
- package/src/plugins/builtin/treasury-daily/client.ts +41 -5
- package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
- package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
- package/src/plugins/builtin/treasury-daily/model.ts +50 -3
- package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
- package/src/plugins/builtin/vol-surface/charts.tsx +9 -4
- package/src/plugins/builtin/vol-surface/client.ts +9 -17
- package/src/plugins/builtin/vol-surface/headless.ts +63 -14
- package/src/plugins/builtin/vol-surface/index.tsx +2 -2
- package/src/plugins/builtin/vol-surface/model.ts +22 -7
- package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
- package/src/plugins/builtin/vol-surface/tables.ts +31 -3
- package/src/plugins/builtin/volatility/dvol.ts +46 -0
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +4 -4
- package/src/plugins/builtin/volatility/series.ts +26 -0
- package/src/plugins/builtin/world-indices/footer.ts +1 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/catalog-browser.ts +13 -8
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +5 -5
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
- package/src/remote/resources.ts +2 -1
- package/src/renderers/browser/app-services.ts +1 -7
- package/src/renderers/browser/config-host.ts +2 -55
- package/src/renderers/browser/main.tsx +7 -57
- package/src/renderers/dom/data-table/row.tsx +26 -0
- package/src/renderers/dom/desktop/list-view.tsx +13 -10
- package/src/renderers/dom/desktop/query-bar.tsx +4 -2
- package/src/renderers/dom/desktop/select-field.tsx +4 -2
- package/src/renderers/dom/host/chart-surface.tsx +21 -0
- package/src/renderers/electrobun/bun/desktop/app-name.ts +222 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
- package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
- package/src/renderers/electrobun/bun/index.ts +25 -1
- package/src/renderers/electrobun/shared/app-name.ts +30 -0
- package/src/renderers/electrobun/shared/protocol.ts +10 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +3 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- package/src/renderers/opentui/toast-host.tsx +71 -43
- package/src/series/registry.ts +1 -0
- package/src/series/types.ts +6 -0
- package/src/series/validate.ts +1 -0
- package/src/sources/gloomberb-cloud/index.ts +1 -0
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
- package/src/sources/provider-router/batches.ts +10 -2
- package/src/sources/provider-router/financial-routes.ts +3 -3
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/history.ts +28 -7
- package/src/sources/provider-router/index.ts +149 -10
- package/src/sources/provider-router/search.ts +13 -2
- package/src/state/app/context/index.tsx +3 -0
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/auth-transition.ts +19 -9
- package/src/sync/baseline.ts +10 -4
- package/src/sync/controller.ts +70 -19
- package/src/sync/core-contributors.ts +90 -31
- package/src/sync/react.ts +4 -1
- package/src/sync/types.ts +3 -0
- package/src/telemetry/stall-reports-dom.ts +38 -0
- package/src/telemetry/stall-reports-node.ts +75 -0
- package/src/telemetry/stall-reports.ts +214 -0
- package/src/telemetry/usage-counts.ts +0 -18
- package/src/test-support/preload.ts +14 -0
- package/src/theme/colors.ts +8 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name.ts +103 -3
- package/src/tickers/listing-venues.ts +38 -3
- package/src/tickers/search/bare-symbol.ts +36 -0
- package/src/tickers/search/index.ts +36 -7
- package/src/tickers/search/ranking.ts +10 -1
- package/src/tickers/search/types.ts +5 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/base-series.ts +13 -5
- package/src/time-series/daily-valuation.ts +62 -0
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +2 -4
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/forward-valuation.ts +32 -0
- package/src/time-series/fundamentals.ts +198 -7
- package/src/time-series/hooks.ts +1 -0
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/price-units.ts +72 -2
- package/src/time-series/resolve-context.ts +7 -1
- package/src/time-series/resolve.ts +116 -15
- package/src/time-series/series-presentation.ts +40 -6
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +5 -3
- package/src/time-series/studies.ts +278 -74
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +3 -3
- package/src/time-series/types.ts +27 -1
- package/src/time-series/valuation-releases.ts +39 -0
- package/src/types/data-provider.ts +9 -1
- package/src/types/desktop-window.ts +2 -0
- package/src/types/financials.ts +73 -2
- package/src/types/headless.ts +61 -5
- package/src/types/instrument.ts +3 -0
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +10 -1
- package/src/ui/host.tsx +3 -0
- package/src/updater/command-names.ts +12 -7
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +44 -0
- package/src/utils/financial-statements.ts +6 -1
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +24 -3
- package/src/utils/fx-market-hours.ts +26 -3
- package/src/utils/main-thread-stall.ts +35 -2
- package/src/utils/market-capitalization.ts +24 -1
- package/src/utils/network-failure.ts +9 -6
- package/src/utils/option-expiry.ts +48 -1
- package/src/utils/options.ts +10 -0
- package/src/utils/plugin-proxy-hosts.json +1 -0
- package/src/utils/price-history.ts +12 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
- package/src/renderers/browser/first-screen.ts +0 -193
|
@@ -1,24 +1,82 @@
|
|
|
1
1
|
import { apiClient } from "../../../api-client";
|
|
2
2
|
import type { DividendSummary } from "../../../api-client/market-discovery";
|
|
3
3
|
import type { ConnectionHealthRegistry } from "../../../core/connection-health";
|
|
4
|
-
import type { DividendMetrics, DividendPayment } from "./types";
|
|
4
|
+
import type { DividendMetrics, DividendPayment, GrowthPayments } from "./types";
|
|
5
5
|
import { resolveCurrencyUnit } from "../../../utils/currency-units";
|
|
6
6
|
import { calendarMonthsBefore } from "../../../utils/calendar-date";
|
|
7
|
-
import { cashDistribution, dividendReferencePrice, dividendYieldAt, inferCadence, trailingCashAt, trailingDividendRate } from "../../../utils/trailing-dividend";
|
|
7
|
+
import { cashDistribution, dividendReferencePrice, dividendYieldAt, inferCadence, trailingCashAt, trailingDividendRate, trailingPaymentShortfall, unevenRegularPayments } from "../../../utils/trailing-dividend";
|
|
8
8
|
import { dividendQuotePriceMetadata } from "./reference-price";
|
|
9
9
|
import { mapQuote } from "../../../sources/gloomberb-cloud/normalizers";
|
|
10
10
|
import { regularSessionValuationPrice } from "../../../market-data/quotes/regular-session-valuation";
|
|
11
11
|
import { declaredDividends } from "../../../utils/declared-dividends";
|
|
12
12
|
import { cashInMajorUnits } from "../../../utils/dividend-cash";
|
|
13
13
|
import { lapsedDividend } from "../../../utils/forward-dividend";
|
|
14
|
-
import type { DeclaredDividendAction } from "../../../types/financials";
|
|
14
|
+
import type { DeclaredCash, DeclaredDividendAction, TickerFinancials } from "../../../types/financials";
|
|
15
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
15
16
|
|
|
16
17
|
export const DIVIDENDS_CONNECTION_ID = "gloom-dividends";
|
|
17
|
-
|
|
18
|
+
|
|
19
|
+
/** ` (last tried 2026-10-27 15:38 UTC)`, or nothing when the time is not known. */
|
|
20
|
+
function lastTried(at: string | undefined): string {
|
|
21
|
+
const time = at ? Date.parse(at) : Number.NaN;
|
|
22
|
+
if (!Number.isFinite(time)) return "";
|
|
23
|
+
const iso = new Date(time).toISOString();
|
|
24
|
+
return ` (last tried ${iso.slice(0, 10)} ${iso.slice(11, 16)} UTC)`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A read with some cash rows, but not all of them usable. */
|
|
28
|
+
function incompleteDividendHistory(triedAt?: string): string {
|
|
29
|
+
return `Incomplete cash history${lastTried(triedAt)}; totals unavailable.`;
|
|
30
|
+
}
|
|
31
|
+
/** A read with no cash rows at all: nothing is incomplete, there is nothing. */
|
|
32
|
+
function unavailableDividendHistory(triedAt?: string): string {
|
|
33
|
+
return `Cash history unavailable${lastTried(triedAt)}; no payments, totals or growth.`;
|
|
34
|
+
}
|
|
35
|
+
/** A read without its summary; `forward` when another source still gave the forward rate. */
|
|
36
|
+
function unavailableDividendSummary(triedAt?: string, { forward = false } = {}): string {
|
|
37
|
+
return `Dividend summary unavailable${lastTried(triedAt)}; no ${forward ? "payout ratio or pay date" : "forward rate, payout ratio or pay date"}.`;
|
|
38
|
+
}
|
|
39
|
+
export const INCOMPLETE_DIVIDEND_HISTORY = incompleteDividendHistory();
|
|
18
40
|
export const MISSING_DIVIDEND_CURRENCY = "Dividend currency is unavailable; cash amounts cannot be compared safely.";
|
|
19
|
-
export const UNAVAILABLE_DIVIDEND_SUMMARY =
|
|
41
|
+
export const UNAVAILABLE_DIVIDEND_SUMMARY = unavailableDividendSummary();
|
|
20
42
|
export const INVALID_DIVIDEND_SUMMARY_DATE = "Invalid dividend summary date.";
|
|
21
43
|
|
|
44
|
+
/** The forward rate a listing's fundamentals state: the annual rate, in the major unit of `currency`, and the ex-date it annualizes. */
|
|
45
|
+
interface StatedForwardRate {
|
|
46
|
+
rate: number;
|
|
47
|
+
exDate: string;
|
|
48
|
+
currency: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The forward rate fundamentals state for a listing (DUK's 4.34, declared,
|
|
53
|
+
* ex 2026-11-13), in its quote's currency; null when its yield is not a
|
|
54
|
+
* forward one or the rate, its ex-date or the currency is missing.
|
|
55
|
+
*/
|
|
56
|
+
export function statedForwardRate(financials: Pick<TickerFinancials, "fundamentals" | "quote"> | null | undefined): StatedForwardRate | null {
|
|
57
|
+
const fundamentals = financials?.fundamentals;
|
|
58
|
+
const currency = resolveCurrencyUnit(financials?.quote?.currency).currency;
|
|
59
|
+
const rate = fundamentals?.dividendRate;
|
|
60
|
+
const exDate = fundamentals?.exDividendDate;
|
|
61
|
+
if (fundamentals?.dividendYieldBasis !== "forward" || !currency || typeof rate !== "number" || !Number.isFinite(rate) || rate <= 0
|
|
62
|
+
|| !exDate || !/^\d{4}-\d{2}-\d{2}$/.test(exDate)) return null;
|
|
63
|
+
return { rate, exDate, currency };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** A summary holding only a stated forward rate, dated by its ex-date, for a read whose own summary is missing. */
|
|
67
|
+
function statedRateSummary(stated: StatedForwardRate): QuoteSummaryDividendFields {
|
|
68
|
+
return {
|
|
69
|
+
trailingAnnualDividendRate: null, trailingAnnualDividendYield: null, payoutRatio: null, dividendDate: null,
|
|
70
|
+
forwardAnnualDividendRate: stated.rate, forwardRateAsOf: stated.exDate,
|
|
71
|
+
exDividendDate: Date.parse(`${stated.exDate}T00:00:00Z`) / 1000, currency: stated.currency,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface DividendReadOptions {
|
|
76
|
+
/** The forward rate fundamentals state (statedForwardRate), asked only when the summary is missing. */
|
|
77
|
+
loadStatedForwardRate?: () => Promise<StatedForwardRate | null> | StatedForwardRate | null;
|
|
78
|
+
}
|
|
79
|
+
|
|
22
80
|
let connectionHealth: ConnectionHealthRegistry | null = null;
|
|
23
81
|
|
|
24
82
|
export function attachDividendYieldHealth(health?: ConnectionHealthRegistry): void {
|
|
@@ -55,6 +113,18 @@ export function toDividendPayment(
|
|
|
55
113
|
};
|
|
56
114
|
}
|
|
57
115
|
|
|
116
|
+
/**
|
|
117
|
+
* The declared amount of each paid row by its ex-date: a company declaring in
|
|
118
|
+
* another currency than the listing pays. Rows in the listing's own currency,
|
|
119
|
+
* or with no usable amount, are not a declaration in another currency.
|
|
120
|
+
*/
|
|
121
|
+
function declaredCashByExDate(declared: DeclaredCash | undefined, cashCurrency: string): Map<string, { amount: number; currency: string }> {
|
|
122
|
+
const unit = resolveCurrencyUnit(declared?.currency);
|
|
123
|
+
if (!declared || !unit.currency || unit.currency === cashCurrency || !Array.isArray(declared.rows)) return new Map();
|
|
124
|
+
return new Map(declared.rows.flatMap((row) => typeof row?.exDate === "string" && Number.isFinite(row.amount) && row.amount > 0
|
|
125
|
+
? [[row.exDate, { amount: row.amount / unit.divisor, currency: unit.currency }] as const] : []));
|
|
126
|
+
}
|
|
127
|
+
|
|
58
128
|
export interface DividendData {
|
|
59
129
|
payments: DividendPayment[];
|
|
60
130
|
/** Declared and unpaid, in `currency`; never in `payments` or any figure. Absent from older servers. */
|
|
@@ -67,6 +137,8 @@ export interface DividendData {
|
|
|
67
137
|
historyError?: string;
|
|
68
138
|
/** Independent summary failures do not invalidate usable cash history. */
|
|
69
139
|
summaryError?: string;
|
|
140
|
+
/** Set when the summary was missing and the forward rate is the one fundamentals state (statedForwardRate). */
|
|
141
|
+
forwardFromFundamentals?: boolean;
|
|
70
142
|
notes?: string[];
|
|
71
143
|
providerId?: string;
|
|
72
144
|
/** Source fetch time, not the last ex-date or this pane's cache-read time. */
|
|
@@ -81,31 +153,41 @@ export async function fetchDividendData(
|
|
|
81
153
|
currentPrice: number | null,
|
|
82
154
|
exchange = "",
|
|
83
155
|
currentPriceCurrency?: string,
|
|
156
|
+
options: DividendReadOptions = {},
|
|
84
157
|
): Promise<DividendData> {
|
|
85
158
|
const response = await trackRequest("dividends", () => apiClient.getMarketDividends(symbol, exchange));
|
|
86
159
|
if (!response.data) throw new Error(`No dividend data found for ${symbol}`);
|
|
87
|
-
const { actions: sourceActions, quote: rawQuote, summary
|
|
160
|
+
const { actions: sourceActions, quote: rawQuote, summary, triedAt } = response.data;
|
|
88
161
|
// In the major unit as the history arrives, as every other reader of it gets it.
|
|
89
162
|
const actions = sourceActions ? cashInMajorUnits(sourceActions) : null;
|
|
90
163
|
const quote = rawQuote ? mapQuote(rawQuote) : null;
|
|
91
|
-
const
|
|
92
|
-
|
|
164
|
+
const cashCurrency = resolveCurrencyUnit(actions?.currency).currency;
|
|
165
|
+
// Without a summary, the forward rate fundamentals state stands in, dated by the ex-date it annualizes (DUK's declared one).
|
|
166
|
+
const stated = !summary && options.loadStatedForwardRate
|
|
167
|
+
? await Promise.resolve().then(options.loadStatedForwardRate).catch(logFailure("dividend-yield.client", "Read the stated forward rate failed", null))
|
|
168
|
+
: null;
|
|
169
|
+
const statedFields = !summary && stated && (!cashCurrency || stated.currency === cashCurrency) ? statedRateSummary(stated) : null;
|
|
170
|
+
const quoteFields = summary ?? statedFields;
|
|
171
|
+
const summaryError = !summary ? unavailableDividendSummary(triedAt, { forward: quoteFields != null })
|
|
172
|
+
: response.data.summaryError ?? ([summary.exDividendDate, summary.dividendDate]
|
|
93
173
|
.some((timestamp) => timestamp != null && reportedDividendDate(timestamp) === null)
|
|
94
174
|
? INVALID_DIVIDEND_SUMMARY_DATE : undefined);
|
|
95
|
-
const cashCurrency = resolveCurrencyUnit(actions?.currency).currency;
|
|
96
175
|
const summaryUnit = resolveCurrencyUnit(quoteFields?.currency);
|
|
97
176
|
const currency = cashCurrency || summaryUnit.currency;
|
|
98
|
-
let historyError = response.data.historyError;
|
|
177
|
+
let historyError = !actions && response.data.historyError ? unavailableDividendHistory(triedAt) : response.data.historyError;
|
|
99
178
|
if (actions && !cashCurrency) historyError = MISSING_DIVIDEND_CURRENCY;
|
|
100
179
|
if (!currency && (quoteFields?.trailingAnnualDividendRate != null || quoteFields?.forwardAnnualDividendRate != null)) {
|
|
101
180
|
historyError = MISSING_DIVIDEND_CURRENCY;
|
|
102
181
|
}
|
|
182
|
+
const declaredCash = cashCurrency ? declaredCashByExDate(actions?.declaredCash, cashCurrency) : new Map();
|
|
103
183
|
const payments = actions && cashCurrency ? actions.dividends.flatMap(action => {
|
|
104
184
|
const payment = toDividendPayment(action.exDate, action.amount, cashCurrency);
|
|
105
|
-
|
|
185
|
+
const declared = declaredCash.get(action.exDate);
|
|
186
|
+
return payment ? [declared ? { ...payment, declared } : payment] : [];
|
|
106
187
|
}).sort((a, b) => b.exDate.getTime() - a.exDate.getTime()) : [];
|
|
107
188
|
if (!historyError && actions && (actions.coverage?.dividends === "unavailable" || payments.length !== actions.dividends.length)) {
|
|
108
|
-
|
|
189
|
+
// A read with no rows is not an incomplete one.
|
|
190
|
+
historyError = actions.dividends.length === 0 ? unavailableDividendHistory(triedAt) : incompleteDividendHistory(triedAt);
|
|
109
191
|
}
|
|
110
192
|
const suppliedPrice = dividendReferencePrice(currentPrice, currentPriceCurrency, currency);
|
|
111
193
|
// The regular session's price, as the quote headlines it: after the close, the close rather than an after-hours print.
|
|
@@ -124,7 +206,9 @@ export async function fetchDividendData(
|
|
|
124
206
|
payments, ...(declared.length > 0 ? { declared } : {}), metrics, price, currency: currency || undefined, historyAvailable,
|
|
125
207
|
...(suppliedPrice == null && price != null && quote ? dividendQuotePriceMetadata(quote) : {}),
|
|
126
208
|
...(historyError ? { historyError } : {}), ...(summaryError ? { summaryError } : {}),
|
|
127
|
-
|
|
209
|
+
...(statedFields && metrics.forwardRate != null ? { forwardFromFundamentals: true } : {}),
|
|
210
|
+
// A history standing in for a refresh that could not read it is dated by its own read.
|
|
211
|
+
providerId: actions?.providerId ?? "gloom", fetchedAt: actions?.dividendsAsOf ?? actions?.fetchedAt,
|
|
128
212
|
stale: response.stale === true || response.data.stale === true || actions?.stale === true,
|
|
129
213
|
notes: ["Cash yield excludes taxes and reinvestment. SEC yield, tax components and future payments are not modeled."],
|
|
130
214
|
};
|
|
@@ -155,9 +239,6 @@ export function buildDividendMetrics(
|
|
|
155
239
|
const statedRate = options.summaryRatesComparable !== false ? quoteFields?.forwardAnnualDividendRate ?? null : null;
|
|
156
240
|
const statedAsOf = statedRate != null && /^\d{4}-\d{2}-\d{2}$/.test(quoteFields?.forwardRateAsOf ?? "")
|
|
157
241
|
? quoteFields!.forwardRateAsOf! : null;
|
|
158
|
-
const growth1Y = options.historyAvailable !== false ? computeGrowth(eligible, 1, now) : null;
|
|
159
|
-
const growth3Y = options.historyAvailable !== false ? computeGrowth(eligible, 3, now) : null;
|
|
160
|
-
|
|
161
242
|
// A reported ex-date can be the latest one or an announced one; keep them apart.
|
|
162
243
|
const reportedExDate = reportedDividendDate(quoteFields?.exDividendDate ?? null);
|
|
163
244
|
const pastExDates = payments.filter((payment) => payment.exDate <= now).map((payment) => payment.exDate.getTime());
|
|
@@ -179,8 +260,17 @@ export function buildDividendMetrics(
|
|
|
179
260
|
exDates: [...payments.map((payment) => payment.exDate), ...lastExDividendDate ? [lastExDividendDate] : []], declared }, now)
|
|
180
261
|
: lapsedDividend([statedAsOf, lastExDividendDate?.toISOString().slice(0, 10)]
|
|
181
262
|
.filter((day): day is string => !!day).sort().at(-1), { exDates: null, declared }, now);
|
|
182
|
-
|
|
183
|
-
const
|
|
263
|
+
// A year whose regular payments differ widely (HSBC's final beside its interims) has no one payment to annualize: its total stands.
|
|
264
|
+
const uneven = statedRate != null && !lapse && options.historyAvailable !== false ? unevenRegularPayments(eligible, now) : null;
|
|
265
|
+
const forwardRate = lapse ? null : uneven ? uneven.total : statedRate;
|
|
266
|
+
const forwardRateAsOf = lapse ? null : uneven ? uneven.latest.toISOString().slice(0, 10) : statedAsOf;
|
|
267
|
+
const growth1Y = options.historyAvailable !== false ? computeGrowth(eligible, 1, now) : null;
|
|
268
|
+
const growth3Y = options.historyAvailable !== false ? computeGrowth(eligible, 3, now) : null;
|
|
269
|
+
|
|
270
|
+
// A payout over zero or negative earnings (ECC's 2000% beside an EPS of -1.30) is not a payout, whatever the source states.
|
|
271
|
+
const payoutNotMeaningful = typeof quoteFields?.trailingEps === "number" && Number.isFinite(quoteFields.trailingEps) && quoteFields.trailingEps <= 0;
|
|
272
|
+
// A trailing yield over a hole in a regular payer's history says how many payments it counts; a stopped dividend has said so.
|
|
273
|
+
const trailingPayments = options.historyAvailable !== false && !lapse ? trailingPaymentShortfall(eligible, now) : null;
|
|
184
274
|
|
|
185
275
|
return repriceDividendMetrics({
|
|
186
276
|
trailingYield: null,
|
|
@@ -188,10 +278,15 @@ export function buildDividendMetrics(
|
|
|
188
278
|
trailingRate,
|
|
189
279
|
forwardRate,
|
|
190
280
|
forwardRateAsOf,
|
|
281
|
+
...(uneven ? { forwardBasis: "uneven" as const } : {}),
|
|
191
282
|
lapse,
|
|
192
|
-
payoutRatio: quoteFields?.payoutRatio ?? null,
|
|
193
|
-
|
|
194
|
-
|
|
283
|
+
payoutRatio: payoutNotMeaningful ? null : quoteFields?.payoutRatio ?? null,
|
|
284
|
+
payoutNotMeaningful,
|
|
285
|
+
trailingPayments,
|
|
286
|
+
growth1Y: growth1Y?.growth ?? null,
|
|
287
|
+
growth3Y: growth3Y?.growth ?? null,
|
|
288
|
+
...(growth1Y?.shortfall ? { growth1YPayments: growth1Y.shortfall } : {}),
|
|
289
|
+
...(growth3Y?.shortfall ? { growth3YPayments: growth3Y.shortfall } : {}),
|
|
195
290
|
paymentFrequency: options.historyAvailable !== false ? inferFrequency(eligible, now) : null,
|
|
196
291
|
lastExDividendDate,
|
|
197
292
|
nextExDividendDate,
|
|
@@ -207,14 +302,27 @@ export function repriceDividendMetrics(metrics: DividendMetrics, price: number |
|
|
|
207
302
|
};
|
|
208
303
|
}
|
|
209
304
|
|
|
210
|
-
|
|
305
|
+
/**
|
|
306
|
+
* Cash growth between the trailing year and the one `years` before it, only
|
|
307
|
+
* when each holds every payment the cadence pays: a year with a hole in its
|
|
308
|
+
* history (AZN's missing August interim) measures the hole, not a cut, so it
|
|
309
|
+
* has no growth and says how many payments it holds. A year with none is a
|
|
310
|
+
* suspension, which still reads as a full decline.
|
|
311
|
+
*/
|
|
312
|
+
function computeGrowth(payments: DividendPayment[], years: number, now: Date): { growth: number | null; shortfall?: GrowthPayments } | null {
|
|
211
313
|
const priorEnd = calendarMonthsBefore(now, 12 * years);
|
|
212
314
|
const priorStart = calendarMonthsBefore(priorEnd, 12);
|
|
213
315
|
// A new fund's partial first year is not a full-year growth baseline.
|
|
214
316
|
if (!payments.some((payment) => payment.exDate <= priorStart)) return null;
|
|
317
|
+
for (const end of [now, priorEnd]) {
|
|
318
|
+
const short = trailingPaymentShortfall(payments, end);
|
|
319
|
+
if (short && short.counted > 0) {
|
|
320
|
+
return { growth: null, shortfall: { ...short, ...end === now ? {} : { yearTo: end.toISOString().slice(0, 10) } } };
|
|
321
|
+
}
|
|
322
|
+
}
|
|
215
323
|
const recent = trailingCashAt(payments, now);
|
|
216
324
|
const prior = trailingCashAt(payments, priorEnd);
|
|
217
|
-
return prior > 0 ? Math.pow(recent / prior, 1 / years) - 1 : null;
|
|
325
|
+
return { growth: prior > 0 ? Math.pow(recent / prior, 1 / years) - 1 : null };
|
|
218
326
|
}
|
|
219
327
|
|
|
220
328
|
function inferFrequency(payments: DividendPayment[], now: Date): DividendMetrics["paymentFrequency"] {
|
|
@@ -4,12 +4,12 @@ import type {
|
|
|
4
4
|
HeadlessPaneDefinition,
|
|
5
5
|
HeadlessPaneLoadArgs,
|
|
6
6
|
} from "../../../types/plugin";
|
|
7
|
-
import { fetchDividendData, type DividendData } from "./client";
|
|
7
|
+
import { fetchDividendData, statedForwardRate, type DividendData } from "./client";
|
|
8
8
|
import { dividendReferencePrice } from "../../../utils/trailing-dividend";
|
|
9
|
-
import type { DividendPayment } from "./types";
|
|
10
|
-
import { formatDividendYield, toDividendRows } from "./view";
|
|
9
|
+
import type { DividendPayment, GrowthPayments } from "./types";
|
|
10
|
+
import { formatDividendYield, growthPaymentsText, PAYOUT_NOT_MEANINGFUL, staleHistoryText, toDividendRows, trailingPaymentsText } from "./view";
|
|
11
11
|
import { formatDistributionAmount, formatPercent } from "../../../utils/format";
|
|
12
|
-
import { forwardDividendBasis, noCurrentDividend, TRAILING_DIVIDEND_BASIS } from "../../../utils/forward-dividend";
|
|
12
|
+
import { forwardDividendBasis, noCurrentDividend, TRAILING_DIVIDEND_BASIS, UNEVEN_REGULAR_PAYMENTS } from "../../../utils/forward-dividend";
|
|
13
13
|
import { currencyPriceMinDigits, formatMarketPriceWithCurrency } from "../../../market-data/market/format";
|
|
14
14
|
import type { Quote } from "../../../types/financials";
|
|
15
15
|
import { regularSessionValuationPrice } from "../../../market-data/quotes/regular-session-valuation";
|
|
@@ -43,6 +43,11 @@ const PAYMENT_COLUMNS = [
|
|
|
43
43
|
{ key: "type", header: "Type" },
|
|
44
44
|
];
|
|
45
45
|
|
|
46
|
+
/** After the amount, for a company declaring in another currency than the listing pays: what it declared. */
|
|
47
|
+
const DECLARED_COLUMN = { key: "declared", header: "Declared", align: "right" as const,
|
|
48
|
+
format: (value: unknown, row: Record<string, unknown>) => typeof value === "number" && typeof row.declaredCurrency === "string"
|
|
49
|
+
? formatDistributionAmount(value, row.declaredCurrency) : "-" };
|
|
50
|
+
|
|
46
51
|
export interface DividendYieldHeadlessDependencies {
|
|
47
52
|
loadData(symbol: string, context: HeadlessPaneContext): Promise<DividendData>;
|
|
48
53
|
/** A US-listed fund's bond facts (SEC yield, yield to worst, duration); null for any other listing. */
|
|
@@ -59,10 +64,12 @@ const defaultDependencies: DividendYieldHeadlessDependencies = {
|
|
|
59
64
|
let currentPrice: number | null = null;
|
|
60
65
|
let currentPriceCurrency: string | undefined;
|
|
61
66
|
let referenceQuote: Quote | undefined;
|
|
67
|
+
// `ULVR:LSE` is ULVR on LSE, as quote and events read it: the history is asked for that listing, not for a symbol `ULVR:LSE`.
|
|
62
68
|
const instrument = await context.resolveInstrument?.(symbol);
|
|
69
|
+
const listingSymbol = instrument?.symbol ?? symbol;
|
|
63
70
|
const exchange = instrument?.exchange ?? "";
|
|
64
71
|
try {
|
|
65
|
-
const quote = await context.marketData.getQuote(
|
|
72
|
+
const quote = await context.marketData.getQuote(listingSymbol, exchange);
|
|
66
73
|
// The regular session's price: after the close, the close rather than an after-hours print.
|
|
67
74
|
currentPrice = regularSessionValuationPrice(quote);
|
|
68
75
|
currentPriceCurrency = quote.currency;
|
|
@@ -71,7 +78,10 @@ const defaultDependencies: DividendYieldHeadlessDependencies = {
|
|
|
71
78
|
logFailure("dividend-yield.headless", "Read dividend reference quote failed")(error);
|
|
72
79
|
currentPrice = null;
|
|
73
80
|
}
|
|
74
|
-
const data = await fetchDividendData(
|
|
81
|
+
const data = await fetchDividendData(listingSymbol, currentPrice, exchange, currentPriceCurrency, {
|
|
82
|
+
// Read only when the summary is missing: the forward rate fundamentals state, as `fundamentals` shows it.
|
|
83
|
+
loadStatedForwardRate: async () => statedForwardRate(await context.marketData.getTickerFinancials(listingSymbol, exchange)),
|
|
84
|
+
});
|
|
75
85
|
if (referenceQuote && dividendReferencePrice(currentPrice, currentPriceCurrency, data.currency ?? "") != null) {
|
|
76
86
|
Object.assign(data, dividendQuotePriceMetadata(referenceQuote));
|
|
77
87
|
}
|
|
@@ -80,7 +90,7 @@ const defaultDependencies: DividendYieldHeadlessDependencies = {
|
|
|
80
90
|
async loadBond(symbol, context) {
|
|
81
91
|
const instrument = await context.resolveInstrument?.(symbol);
|
|
82
92
|
const exchange = instrument?.exchange ?? "";
|
|
83
|
-
const quote = await context.marketData.getQuote(symbol, exchange)
|
|
93
|
+
const quote = await context.marketData.getQuote(instrument?.symbol ?? symbol, exchange)
|
|
84
94
|
.catch((error) => {
|
|
85
95
|
logFailure("dividend-yield.headless", "Read bond fund classification quote failed")(error);
|
|
86
96
|
throw new BondClassificationError("Bond fund quote check failed");
|
|
@@ -92,6 +102,11 @@ const defaultDependencies: DividendYieldHeadlessDependencies = {
|
|
|
92
102
|
loadTreasury: (context) => loadTreasuryTenors(context.apiClient),
|
|
93
103
|
};
|
|
94
104
|
|
|
105
|
+
/** A growth figure, or what its short year holds: "n/a: 1 of 2 payments". */
|
|
106
|
+
function growthText(growth: number | null, payments: GrowthPayments | null | undefined): string {
|
|
107
|
+
return growth == null && payments ? growthPaymentsText(payments) : formatPercent(growth ?? undefined);
|
|
108
|
+
}
|
|
109
|
+
|
|
95
110
|
function dateEntry(label: string, date: Date | null) {
|
|
96
111
|
return { label, value: date, formatted: date ? date.toISOString().slice(0, 10) : "—" };
|
|
97
112
|
}
|
|
@@ -129,11 +144,16 @@ export function projectDividendYieldHeadless(
|
|
|
129
144
|
paymentDate: payment?.paymentDate?.toISOString() ?? null,
|
|
130
145
|
declarationDate: payment?.declarationDate?.toISOString() ?? null,
|
|
131
146
|
type: payment?.type ?? null,
|
|
147
|
+
...(row.declared ? { declared: row.declared.amount, declaredCurrency: row.declared.currency } : {}),
|
|
132
148
|
};
|
|
133
149
|
});
|
|
150
|
+
const declaredColumns = rows.some((row) => row.declared)
|
|
151
|
+
? [...PAYMENT_COLUMNS.slice(0, 2), DECLARED_COLUMN, ...PAYMENT_COLUMNS.slice(2)] : PAYMENT_COLUMNS;
|
|
134
152
|
const metrics = data.metrics;
|
|
135
153
|
const currency = data.currency ?? data.payments[0]?.currency ?? "";
|
|
136
154
|
const priceStatus = dividendPriceStatus(data.price, data.priceAsOf, data.priceStale);
|
|
155
|
+
// A regular payer's year with fewer payments than its cadence pays: the yield says how many it counts.
|
|
156
|
+
const shortfall = trailingPaymentsText(metrics.trailingPayments, { cadence: true });
|
|
137
157
|
const errors = [data.historyError, data.summaryError, tax?.checkError].filter((error): error is string => !!error);
|
|
138
158
|
|
|
139
159
|
return {
|
|
@@ -143,18 +163,18 @@ export function projectDividendYieldHeadless(
|
|
|
143
163
|
title: "Dividend metrics",
|
|
144
164
|
entries: [
|
|
145
165
|
{ label: "Price", value: data.price, ...(data.price != null && currency ? { formatted: formatMarketPriceWithCurrency(data.price, currency, { minimumFractionDigits: currencyPriceMinDigits(currency) }) } : {}) },
|
|
146
|
-
...(data.stale ? [{ label: "History status", value:
|
|
166
|
+
...(data.stale ? [{ label: "History status", value: staleHistoryText(data.fetchedAt) }] : []),
|
|
147
167
|
...(priceStatus ? [{ label: "Price status", value: priceStatus === "stale"
|
|
148
168
|
? "Stale reference price; cash yield may be out of date."
|
|
149
169
|
: "Reference price time unavailable; cash yield may be out of date." }] : []),
|
|
150
170
|
{ label: bond ? `Trailing distribution yield (${NOT_SEC_YIELD})` : "Trailing yield", value: roundedRatio(metrics.trailingYield),
|
|
151
171
|
formatted: metrics.trailingYield == null ? formatDividendYield(null)
|
|
152
|
-
: `${formatDividendYield(metrics.trailingYield)} (${TRAILING_DIVIDEND_BASIS})` },
|
|
172
|
+
: `${formatDividendYield(metrics.trailingYield)} (${TRAILING_DIVIDEND_BASIS}${shortfall ? `; counts ${shortfall}` : ""})` },
|
|
153
173
|
{ label: bond ? `Forward distribution yield (${NOT_SEC_YIELD})` : "Forward yield", value: roundedRatio(metrics.forwardYield),
|
|
154
174
|
formatted: metrics.lapse ? noCurrentDividend(metrics.lapse)
|
|
155
175
|
: metrics.forwardYield == null ? formatDividendYield(null)
|
|
156
|
-
: `${formatDividendYield(metrics.forwardYield)} (${
|
|
157
|
-
{ payDate: forwardPayDate(metrics), declared: data.declared, currency })})` },
|
|
176
|
+
: `${formatDividendYield(metrics.forwardYield)} (${metrics.forwardBasis === "uneven" ? UNEVEN_REGULAR_PAYMENTS
|
|
177
|
+
: forwardDividendBasis(metrics.forwardRateAsOf, { payDate: forwardPayDate(metrics), declared: data.declared, currency })})` },
|
|
158
178
|
...(bond ? bondFigures(bond).map((figure) => ({
|
|
159
179
|
label: figure.key === "secYield" ? "30-day SEC yield" : figure.label,
|
|
160
180
|
value: figure.value,
|
|
@@ -162,9 +182,9 @@ export function projectDividendYieldHeadless(
|
|
|
162
182
|
})) : []),
|
|
163
183
|
{ label: "Trailing rate", value: metrics.trailingRate, formatted: currency ? formatDistributionAmount(metrics.trailingRate ?? undefined, currency) : "—" },
|
|
164
184
|
{ label: "Forward rate", value: metrics.forwardRate, formatted: currency ? formatDistributionAmount(metrics.forwardRate ?? undefined, currency) : "—" },
|
|
165
|
-
{ label: "Earnings Payout", value: roundedRatio(metrics.payoutRatio), formatted: formatDividendYield(metrics.payoutRatio) },
|
|
166
|
-
{ label: "1Y Cash Growth", value: roundedRatio(metrics.growth1Y), formatted:
|
|
167
|
-
{ label: "3Y Cash CAGR", value: roundedRatio(metrics.growth3Y), formatted:
|
|
185
|
+
{ label: "Earnings Payout", value: roundedRatio(metrics.payoutRatio), formatted: metrics.payoutNotMeaningful ? PAYOUT_NOT_MEANINGFUL : formatDividendYield(metrics.payoutRatio) },
|
|
186
|
+
{ label: "1Y Cash Growth", value: roundedRatio(metrics.growth1Y), formatted: growthText(metrics.growth1Y, metrics.growth1YPayments) },
|
|
187
|
+
{ label: "3Y Cash CAGR", value: roundedRatio(metrics.growth3Y), formatted: growthText(metrics.growth3Y, metrics.growth3YPayments) },
|
|
168
188
|
{ label: "Frequency", value: metrics.paymentFrequency },
|
|
169
189
|
dateEntry("Last ex-dividend", metrics.lastExDividendDate),
|
|
170
190
|
dateEntry("Next ex-dividend", metrics.nextExDividendDate),
|
|
@@ -179,7 +199,7 @@ export function projectDividendYieldHeadless(
|
|
|
179
199
|
}] : []),
|
|
180
200
|
{
|
|
181
201
|
title: "Dividend history",
|
|
182
|
-
columns:
|
|
202
|
+
columns: declaredColumns,
|
|
183
203
|
rows,
|
|
184
204
|
// Each amount is in its own row's currency.
|
|
185
205
|
rowUnitKey: "currency",
|
|
@@ -201,10 +221,18 @@ export function projectDividendYieldHeadless(
|
|
|
201
221
|
priceStale: data.priceStale ?? null,
|
|
202
222
|
limitations: data.notes ?? [],
|
|
203
223
|
yieldMethod: "Cash distributions with ex-dates in the preceding 12 months, one payment per period for a regular cadence, divided by the reference share price; excludes reinvestment and is not SEC yield or total return.",
|
|
224
|
+
// Set when a regular payer's year holds fewer payments than its cadence pays (a gap in its history): the yield counts only these.
|
|
225
|
+
trailingPayments: metrics.trailingPayments ?? null,
|
|
226
|
+
// Set when a year a growth figure compares holds fewer payments than the cadence pays: that figure is null.
|
|
227
|
+
growth1YPayments: metrics.growth1YPayments ?? null,
|
|
228
|
+
growth3YPayments: metrics.growth3YPayments ?? null,
|
|
204
229
|
forwardRateAsOf: metrics.forwardRateAsOf,
|
|
230
|
+
// "uneven": the year's regular payments differ too widely for one to be annualized, so the forward rate is their total.
|
|
231
|
+
forwardBasis: metrics.forwardBasis ?? null,
|
|
205
232
|
// Declared and unpaid, apart from the paid history the table and every figure read.
|
|
206
233
|
declaredDividends: data.declared ?? [],
|
|
207
|
-
forwardRateMethod: "Indicated annual cash rate, the one fundamentals use: the latest regular distribution annualized, or once the next one is declared that one, dated by its ex-date (forwardRateAsOf; a date still ahead is the declared distribution's). Only when its currency units are comparable with the share price.",
|
|
234
|
+
forwardRateMethod: "Indicated annual cash rate, the one fundamentals use: the latest regular distribution annualized, or once the next one is declared that one, dated by its ex-date (forwardRateAsOf; a date still ahead is the declared distribution's). When the trailing year's regular payments differ widely (the largest over 1.5 times the smallest; forwardBasis: uneven), their total instead, dated by the latest. Only when its currency units are comparable with the share price.",
|
|
235
|
+
payoutMethod: "Trailing twelve-month dividends over trailing twelve-month diluted EPS, as the source states it; N/M (not meaningful) when that EPS is zero or negative.",
|
|
208
236
|
paymentFrequencyMethod: "Cadence inferred from ex-dates in the last two years, unavailable when no cash was reported in the last year; not an announced payment schedule.",
|
|
209
237
|
// A bond fund's sponsor figures, each dated; its distribution yields above are not its SEC yield.
|
|
210
238
|
...(bond ? { bondFund: { ...bond, class: bondClassText(bond) } } : {}),
|
|
@@ -2,7 +2,7 @@ import type { DataTableColumn } from "../../../components";
|
|
|
2
2
|
import { compareSortValues, type SortDirection } from "../../../utils/sort-values";
|
|
3
3
|
import type { DividendRow } from "./view";
|
|
4
4
|
|
|
5
|
-
export type DividendColumnId = "exDate" | "amount" | "currency";
|
|
5
|
+
export type DividendColumnId = "exDate" | "amount" | "declared" | "currency";
|
|
6
6
|
export type DividendColumn = DataTableColumn & { id: DividendColumnId };
|
|
7
7
|
|
|
8
8
|
export interface DividendSortPreference {
|
|
@@ -16,15 +16,17 @@ export const DEFAULT_SORT_PREFERENCE: DividendSortPreference = {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* The amount sits beside its ex-date rather than across the pane. The
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* The amount sits beside its ex-date rather than across the pane. The declared
|
|
20
|
+
* amount follows it only for a company declaring in another currency than the
|
|
21
|
+
* listing pays (BP's dollars beside its pence). The currency column shows only
|
|
22
|
+
* when the payments mix currencies; otherwise the amount's own symbol says it.
|
|
22
23
|
*/
|
|
23
|
-
export function buildDividendColumns(rows: readonly Pick<DividendRow, "currency">[] = []): DividendColumn[] {
|
|
24
|
+
export function buildDividendColumns(rows: readonly Pick<DividendRow, "currency" | "declared">[] = []): DividendColumn[] {
|
|
24
25
|
const mixed = new Set(rows.map((row) => row.currency)).size > 1;
|
|
25
26
|
return [
|
|
26
27
|
{ id: "exDate", label: "EX-DATE", width: 10, align: "left" },
|
|
27
28
|
{ id: "amount", label: "AMOUNT", width: 12, align: "right" },
|
|
29
|
+
...(rows.some((row) => row.declared) ? [{ id: "declared" as const, label: "DECLARED", width: 10, align: "right" as const }] : []),
|
|
28
30
|
...(mixed ? [{ id: "currency" as const, label: "CCY", width: 6, align: "left" as const }] : []),
|
|
29
31
|
];
|
|
30
32
|
}
|
|
@@ -33,6 +35,7 @@ function getSortValue(columnId: DividendColumnId, row: DividendRow): string | nu
|
|
|
33
35
|
switch (columnId) {
|
|
34
36
|
case "exDate": return row.exDate;
|
|
35
37
|
case "amount": return row.amount;
|
|
38
|
+
case "declared": return row.declared?.amount ?? null;
|
|
36
39
|
case "currency": return row.currency;
|
|
37
40
|
}
|
|
38
41
|
}
|
|
@@ -22,15 +22,16 @@ import { colors, priceColor } from "../../../theme/colors";
|
|
|
22
22
|
import { TextAttributes, useUiCapabilities } from "../../../ui";
|
|
23
23
|
import { displayWidth, formatCurrency, formatDistributionAmount, formatPercentRaw } from "../../../utils/format";
|
|
24
24
|
import { resolveCurrencyUnit } from "../../../utils/currency-units";
|
|
25
|
-
import {
|
|
25
|
+
import { parsePublicTickerKey } from "../../../utils/exchanges";
|
|
26
|
+
import { forwardDividendNote, lastPaidNote, NO_CURRENT_DIVIDEND, UNEVEN_REGULAR_PAYMENTS } from "../../../utils/forward-dividend";
|
|
26
27
|
import { nextHeaderSort } from "../../../utils/sort-values";
|
|
27
28
|
import { handleRefreshKey, loadingErrorFooterInfo } from "../../../components/data-table/table-pane";
|
|
28
29
|
import { SignInWall } from "../cloud/auth-actions";
|
|
29
30
|
import { isCloudSessionRequired, useResearchCloudSession } from "../shared/research-cloud-session";
|
|
30
|
-
import { fetchDividendData, repriceDividendMetrics, type DividendData } from "./client";
|
|
31
|
+
import { fetchDividendData, repriceDividendMetrics, statedForwardRate, type DividendData } from "./client";
|
|
31
32
|
import { dividendReferencePrice } from "../../../utils/trailing-dividend";
|
|
32
33
|
import { useTickerQuoteStream } from "../../../state/hooks/live-ticker-financials";
|
|
33
|
-
import { buildTrailingCashChartPoints, formatDividendYield, toDividendRows, type DividendRow } from "./view";
|
|
34
|
+
import { buildTrailingCashChartPoints, formatDividendYield, growthPaymentsDetail, PAYOUT_NOT_MEANINGFUL, staleHistoryText, toDividendRows, trailingPaymentsText, type DividendRow } from "./view";
|
|
34
35
|
import {
|
|
35
36
|
DEFAULT_SORT_PREFERENCE,
|
|
36
37
|
buildDividendColumns,
|
|
@@ -39,7 +40,7 @@ import {
|
|
|
39
40
|
type DividendColumnId,
|
|
40
41
|
type DividendSortPreference,
|
|
41
42
|
} from "./model";
|
|
42
|
-
import type { DividendMetrics } from "./types";
|
|
43
|
+
import type { DividendMetrics, GrowthPayments } from "./types";
|
|
43
44
|
import { dividendPriceStatus, dividendPriceTime, dividendQuotePriceMetadata } from "./reference-price";
|
|
44
45
|
import type { FundBondFacts } from "../../../api-client/fund-facts";
|
|
45
46
|
import { resolveTickerInstrumentKind } from "../../../tickers/instrument-kind";
|
|
@@ -57,6 +58,14 @@ function formatGrowth(value: number | null): string {
|
|
|
57
58
|
return formatPercentRaw(value * 100);
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
/** A growth figure, or "n/a" with how many payments its short year holds. */
|
|
62
|
+
function growthItem(id: string, label: string, growth: number | null, payments: GrowthPayments | null | undefined): StatItem {
|
|
63
|
+
if (growth == null && payments) {
|
|
64
|
+
return { id, label, value: "n/a", detail: growthPaymentsDetail(payments) };
|
|
65
|
+
}
|
|
66
|
+
return { id, label, value: formatGrowth(growth), color: priceColor(growth ?? 0) };
|
|
67
|
+
}
|
|
68
|
+
|
|
60
69
|
function formatDate(date: Date | null): string {
|
|
61
70
|
if (!date) return "—";
|
|
62
71
|
return date.toISOString().slice(0, 10);
|
|
@@ -86,11 +95,13 @@ function buildMetricItems(metrics: DividendMetrics, currency: string, hasHistory
|
|
|
86
95
|
? [{ id: "next-ex", label: "Next ex", value: formatDate(metrics.nextExDividendDate) }]
|
|
87
96
|
: hasHistory ? [] : [{ id: "last-ex", label: "Last ex", value: formatDate(metrics.lastExDividendDate) }];
|
|
88
97
|
const dated = metrics.lapse ? lastPaidNote(metrics.lapse)
|
|
89
|
-
: metrics.forwardYield
|
|
98
|
+
: metrics.forwardYield == null ? null
|
|
99
|
+
: metrics.forwardBasis === "uneven" ? UNEVEN_REGULAR_PAYMENTS : forwardDividendNote(metrics.forwardRateAsOf, { declared, currency });
|
|
90
100
|
// "not SEC" in the grid: the SEC yield is the next cell, and the long form costs the header a column.
|
|
91
101
|
const notSec = (note: string | null) => bond ? [note, "not SEC"].filter(Boolean).join(", ") : note;
|
|
92
102
|
const forwardNote = notSec(dated);
|
|
93
|
-
|
|
103
|
+
// A regular payer's year with fewer payments than its cadence pays says how many the yield counts.
|
|
104
|
+
const trailingNote = notSec(trailingPaymentsText(metrics.trailingPayments));
|
|
94
105
|
const ttmLabel = bond ? (short ? "TTM dist" : "TTM dist yld") : short ? "TTM yld" : "TTM yield";
|
|
95
106
|
const fwdLabel = bond ? (short ? "Fwd dist" : "Fwd dist yld") : short ? "Fwd yld" : "Fwd yield";
|
|
96
107
|
const sec = bond ? bondFigures(bond).find((figure) => figure.key === "secYield") : undefined;
|
|
@@ -107,9 +118,9 @@ function buildMetricItems(metrics: DividendMetrics, currency: string, hasHistory
|
|
|
107
118
|
...(sec ? [{ id: "sec-yield", label: short ? "SEC yld" : "SEC yield", value: sec.text, ...(sec.date ? { detail: `as of ${sec.date}` } : {}) }] : []),
|
|
108
119
|
{ id: "ttm-rate", label: short ? "TTM/sh" : "TTM/share", value: formatRate(metrics.trailingRate, currency) },
|
|
109
120
|
{ id: "forward-rate", label: short ? "Fwd/sh" : "Fwd/share", value: formatRate(metrics.forwardRate, currency) },
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
{ id: "payout", label: "Payout", value: metrics.payoutRatio != null ? `${(metrics.payoutRatio * 100).toFixed(1)}%` : "—" },
|
|
121
|
+
growthItem("growth-1y", short ? "1Y chg" : "1Y growth", metrics.growth1Y, metrics.growth1YPayments),
|
|
122
|
+
growthItem("cagr-3y", "3Y CAGR", metrics.growth3Y, metrics.growth3YPayments),
|
|
123
|
+
{ id: "payout", label: "Payout", value: metrics.payoutNotMeaningful ? PAYOUT_NOT_MEANINGFUL : metrics.payoutRatio != null ? `${(metrics.payoutRatio * 100).toFixed(1)}%` : "—" },
|
|
113
124
|
{ id: "cadence", label: "Cadence", value: formatFrequency(metrics.paymentFrequency) },
|
|
114
125
|
...exDate,
|
|
115
126
|
{ id: "next-pay", label: "Next pay", value: formatDate(metrics.nextPayDate) },
|
|
@@ -143,6 +154,8 @@ function renderCell(
|
|
|
143
154
|
color: colors.textBright,
|
|
144
155
|
attributes: TextAttributes.BOLD,
|
|
145
156
|
};
|
|
157
|
+
case "declared":
|
|
158
|
+
return { text: row.declared ? formatDistributionAmount(row.declared.amount, row.declared.currency) : "", color: colors.textDim };
|
|
146
159
|
case "currency":
|
|
147
160
|
return { text: row.currency, color: colors.textDim };
|
|
148
161
|
}
|
|
@@ -162,7 +175,11 @@ export function DividendYieldPane({ focused, width, height, loadData = fetchDivi
|
|
|
162
175
|
const fundSymbol = ticker && resolveTickerInstrumentKind(ticker, financials) === "fund" && mayBeUsEquityOrFundTicker(ticker) ? ticker.metadata.ticker : null;
|
|
163
176
|
const bond = useFundFacts(fundSymbol).data?.payload?.bond ?? null;
|
|
164
177
|
const quoteCurrency = financials?.quote?.currency;
|
|
165
|
-
|
|
178
|
+
// The listing's own symbol beside its venue: a `ULVR:LSE` key is ULVR on LSE, never a symbol of that name.
|
|
179
|
+
const listingKey = ticker?.metadata.ticker ?? symbol;
|
|
180
|
+
const parsedListing = listingKey ? parsePublicTickerKey(listingKey) : null;
|
|
181
|
+
const listingSymbol = parsedListing?.symbol ?? null;
|
|
182
|
+
const exchange = parsedListing?.exchange ?? ticker?.metadata.exchange ?? "";
|
|
166
183
|
// The regular session's price: after the close, the close rather than an after-hours print.
|
|
167
184
|
const quotePrice = regularSessionValuationPrice(financials?.quote);
|
|
168
185
|
|
|
@@ -173,9 +190,17 @@ export function DividendYieldPane({ focused, width, height, loadData = fetchDivi
|
|
|
173
190
|
// quote is read through a ref instead of being an effect dependency.
|
|
174
191
|
const quoteRef = useRef({ price: quotePrice, currency: quoteCurrency });
|
|
175
192
|
quoteRef.current = { price: quotePrice, currency: quoteCurrency };
|
|
193
|
+
// The forward rate the ticker's fundamentals state, read only when the summary is missing. Fundamentals can
|
|
194
|
+
// arrive after the read, so a new stated rate reads again; a price tick does not.
|
|
195
|
+
const stated = statedForwardRate(financials);
|
|
196
|
+
const statedKey = stated ? `${stated.rate}|${stated.exDate}|${stated.currency}` : "";
|
|
197
|
+
const statedRef = useRef(stated);
|
|
198
|
+
statedRef.current = stated;
|
|
176
199
|
|
|
177
|
-
const request = useCallback(() => loadData(
|
|
178
|
-
|
|
200
|
+
const request = useCallback(() => loadData(listingSymbol!, quoteRef.current.price, exchange, quoteRef.current.currency, {
|
|
201
|
+
loadStatedForwardRate: () => statedRef.current,
|
|
202
|
+
}), [exchange, loadData, listingSymbol, cloudSession.requestKey, statedKey]);
|
|
203
|
+
const { data, loading, error, updatedAt, reload: refresh } = useAsyncResource(listingSymbol ? request : null);
|
|
179
204
|
// An integrity response may contain no usable rows. Retain only historical
|
|
180
205
|
// rows from this resource; its new unavailable metrics remain authoritative.
|
|
181
206
|
const lastHistory = useRef<{ request: typeof request; data: DividendData } | null>(null);
|
|
@@ -211,7 +236,7 @@ export function DividendYieldPane({ focused, width, height, loadData = fetchDivi
|
|
|
211
236
|
}, [authWall, currentPrice, historyData?.fetchedAt, data?.historyError, data?.summaryError, error, loading, priceAsOf, priceStatus, width]);
|
|
212
237
|
|
|
213
238
|
const sourceWarnings = [
|
|
214
|
-
...(data?.stale ? [
|
|
239
|
+
...(data?.stale ? [staleHistoryText(data.fetchedAt)] : []),
|
|
215
240
|
];
|
|
216
241
|
usePaneNoticeFooter({
|
|
217
242
|
registrationId: "dividend-yield-notices",
|