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
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import type { Fundamentals, FinancialStatement, TickerFinancials } from "../types/financials";
|
|
2
|
+
import { resolveCurrencyUnit } from "../utils/currency-units";
|
|
3
|
+
import { areNearbyFinancialPeriodEnds, statementFieldAvailability } from "../utils/financial-statements";
|
|
4
|
+
import { extractFundamentalSeries, knownWhenFiled } from "./fundamentals";
|
|
5
|
+
import type { SecuritySeriesSource, TimeSeriesPoint } from "./types";
|
|
6
|
+
|
|
7
|
+
const DAY_MS = 86_400_000;
|
|
8
|
+
/**
|
|
9
|
+
* A figure stops pricing this long after its period end. Sixteen months
|
|
10
|
+
* covers a fiscal year plus its filing, so a year of unavailable quarterly
|
|
11
|
+
* sums falls back on the last annual figure but never on one long overdue.
|
|
12
|
+
*/
|
|
13
|
+
export const STALE_TRAILING_EPS_MS = 487 * DAY_MS;
|
|
14
|
+
/**
|
|
15
|
+
* The latest period's trailing figure reads statements this far back at most:
|
|
16
|
+
* its four quarters (some 270 days), and for a fiscal fourth quarter read from
|
|
17
|
+
* its year, the three quarters before it. Older rows change nothing.
|
|
18
|
+
*/
|
|
19
|
+
const LATEST_WINDOW_MS = 760 * DAY_MS;
|
|
20
|
+
|
|
21
|
+
/** One trailing-twelve-month EPS figure and when it became known. */
|
|
22
|
+
export interface EpsStep {
|
|
23
|
+
/** ISO period end. */
|
|
24
|
+
periodEnd: string;
|
|
25
|
+
/** Four reported quarters summed, or a reported fiscal year. */
|
|
26
|
+
basis: "ttm" | "annual";
|
|
27
|
+
knownAt: Date;
|
|
28
|
+
/** False when no publication date or report is on record and `knownAt` is the period end. */
|
|
29
|
+
dated: boolean;
|
|
30
|
+
/** Statement currency units; null when unavailable (a declared gap or an incomplete run of quarters). */
|
|
31
|
+
eps: number | null;
|
|
32
|
+
currency: string | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function epsSeries(financials: TickerFinancials, period: "ttm" | "annual"): TimeSeriesPoint[] {
|
|
36
|
+
const source: SecuritySeriesSource = { kind: "security", instrument: { symbol: "" }, fieldId: "fundamental.eps", period, timestampMode: "available-at" };
|
|
37
|
+
return extractFundamentalSeries(financials, source);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Trailing EPS through time, oldest first. Four reported quarters are summed
|
|
42
|
+
* by the shared statement series, which keeps declared gaps and broken runs
|
|
43
|
+
* of quarters unavailable and reads a fiscal fourth quarter with no EPS of its
|
|
44
|
+
* own from its year. A reported fiscal year is itself a trailing twelve-month
|
|
45
|
+
* figure; at a fiscal year end it replaces the quarterly sum. `known` dates
|
|
46
|
+
* each row's figures (PEB dates them by the company's reports as well).
|
|
47
|
+
*/
|
|
48
|
+
export function trailingEpsSteps(
|
|
49
|
+
financials: TickerFinancials,
|
|
50
|
+
known: (row: FinancialStatement) => FinancialStatement = knownWhenFiled,
|
|
51
|
+
): EpsStep[] {
|
|
52
|
+
const adjusted = { ...financials, quarterlyStatements: financials.quarterlyStatements.map(known),
|
|
53
|
+
annualStatements: financials.annualStatements.map(known) };
|
|
54
|
+
const toStep = (basis: EpsStep["basis"]) => (point: TimeSeriesPoint): EpsStep => ({
|
|
55
|
+
periodEnd: point.observedAt.toISOString().slice(0, 10), basis, knownAt: point.date, dated: point.availableAt !== undefined,
|
|
56
|
+
eps: point.value != null && Number.isFinite(point.value) ? point.value : null, currency: point.provenance?.currency ?? null,
|
|
57
|
+
});
|
|
58
|
+
const steps = epsSeries(adjusted, "annual").map(toStep("annual"));
|
|
59
|
+
for (const step of epsSeries(adjusted, "ttm").map(toStep("ttm"))) {
|
|
60
|
+
const index = steps.findIndex((other) => areNearbyFinancialPeriodEnds(other.periodEnd, step.periodEnd));
|
|
61
|
+
if (index < 0) steps.push(step);
|
|
62
|
+
else if (steps[index]!.eps == null && step.eps != null) steps[index] = step;
|
|
63
|
+
}
|
|
64
|
+
return steps.sort((a, b) => a.knownAt.getTime() - b.knownAt.getTime() || a.periodEnd.localeCompare(b.periodEnd));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The trailing EPS of the last period filed, with that period's end and the statements' currency. */
|
|
68
|
+
interface FiledTrailingEps {
|
|
69
|
+
eps: number;
|
|
70
|
+
periodEnd: string;
|
|
71
|
+
currency: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* How far the filed figure may sit from Gloom Cloud's, either way, before it
|
|
76
|
+
* is taken for another share basis (a receipt or a class the flags miss, as
|
|
77
|
+
* BRK.B's statements are per Class A share) rather than newer or restated
|
|
78
|
+
* quarters: PLTR's 1.17 against 1.13 stands, 4.00 against 0.40 does not.
|
|
79
|
+
*/
|
|
80
|
+
const SERVED_EPS_RATIO_LIMIT = 1.5;
|
|
81
|
+
|
|
82
|
+
type TrailingEpsInputs = Pick<TickerFinancials, "quote" | "fundamentals" | "annualStatements" | "quarterlyStatements" | "financialCurrency">;
|
|
83
|
+
|
|
84
|
+
const isReceipt = (financials: TrailingEpsInputs) => !!(
|
|
85
|
+
financials.quote?.isDepositaryReceipt || financials.fundamentals?.isDepositaryReceipt
|
|
86
|
+
|| financials.fundamentals?.shareBasis === "depositary_receipt" || financials.quote?.adrRatio || financials.fundamentals?.adrRatio
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
/** `filed` is the day the latest EPS on record was filed (a restated one's restating filing), the period end when none is dated. */
|
|
90
|
+
type LatestStep = { periodEnd: string; eps: number; currency: string; filed: string } | null;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The last period's step by what it is read from: the router answers the same
|
|
94
|
+
* rows many times, in new arrays. Bounded; the oldest entry leaves first.
|
|
95
|
+
*/
|
|
96
|
+
const latestSteps = new Map<string, LatestStep>();
|
|
97
|
+
const LATEST_STEPS_KEPT = 500;
|
|
98
|
+
|
|
99
|
+
/** What the trailing EPS series reads of a row. */
|
|
100
|
+
const rowKey = (row: FinancialStatement) => [
|
|
101
|
+
row.date, row.currency ?? "", row.eps ?? "", statementFieldAvailability(row, "eps") ?? "", row.availableAt ?? "", row.dateSource ?? "",
|
|
102
|
+
Object.keys(row.fieldAvailability ?? {}).length,
|
|
103
|
+
row.unavailableFields?.join("+") ?? "", row.unavailableEarnings?.join("+") ?? "", row.epsBasis?.status ?? "", row.shareBasis ?? "",
|
|
104
|
+
].join("|");
|
|
105
|
+
|
|
106
|
+
/** The trailing EPS step for the last period on record, null when that period gives none. */
|
|
107
|
+
function latestStep(financials: TrailingEpsInputs): LatestStep {
|
|
108
|
+
const rows = [...financials.quarterlyStatements, ...financials.annualStatements];
|
|
109
|
+
const latest = rows.map((row) => row.date).filter((date) => Number.isFinite(Date.parse(date))).sort().at(-1);
|
|
110
|
+
if (!latest) return null;
|
|
111
|
+
const recent = (row: FinancialStatement) => Date.parse(latest) - Date.parse(row.date) <= LATEST_WINDOW_MS;
|
|
112
|
+
const quarterlyStatements = financials.quarterlyStatements.filter(recent);
|
|
113
|
+
const annualStatements = financials.annualStatements.filter(recent);
|
|
114
|
+
const key = [financials.financialCurrency ?? "", quarterlyStatements.map(rowKey).join(";"), annualStatements.map(rowKey).join(";")].join("#");
|
|
115
|
+
if (latestSteps.has(key)) return latestSteps.get(key)!;
|
|
116
|
+
const found = trailingEpsSteps({ financialCurrency: financials.financialCurrency, priceHistory: [], quarterlyStatements, annualStatements })
|
|
117
|
+
.find((candidate) => areNearbyFinancialPeriodEnds(candidate.periodEnd, latest));
|
|
118
|
+
const filed = [...quarterlyStatements, ...annualStatements]
|
|
119
|
+
.flatMap((row) => typeof row.eps === "number" ? [statementFieldAvailability(row, "eps")?.slice(0, 10)] : [])
|
|
120
|
+
.filter((date): date is string => !!date).sort().at(-1);
|
|
121
|
+
const step = found?.eps != null && found.currency
|
|
122
|
+
? { periodEnd: found.periodEnd, eps: found.eps, currency: found.currency, filed: filed && filed > found.periodEnd ? filed : found.periodEnd }
|
|
123
|
+
: null;
|
|
124
|
+
if (latestSteps.size >= LATEST_STEPS_KEPT) latestSteps.delete(latestSteps.keys().next().value!);
|
|
125
|
+
latestSteps.set(key, step);
|
|
126
|
+
return step;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The headline trailing EPS, as PEB and GE read the statements: the diluted
|
|
131
|
+
* EPS of the last four filed quarters, or of the fiscal year when that is the
|
|
132
|
+
* last period filed. Null when that period gives none (a quarter missing or
|
|
133
|
+
* withheld, a split basis unresolved, two share bases), when it is overdue,
|
|
134
|
+
* when its currency is not the price's, for a depositary receipt, whose
|
|
135
|
+
* statements are per ordinary share, when a split went ex after the last of
|
|
136
|
+
* its figures was filed (they are on the old share count until a filing
|
|
137
|
+
* restates them), and when it is more than SERVED_EPS_RATIO_LIMIT times
|
|
138
|
+
* Gloom Cloud's figure either way.
|
|
139
|
+
*/
|
|
140
|
+
function filedTrailingEps(financials: TrailingEpsInputs, now = Date.now()): FiledTrailingEps | null {
|
|
141
|
+
const price = resolveCurrencyUnit(financials.quote?.currency);
|
|
142
|
+
if (!financials.quote || price.divisor !== 1 || !/^[A-Z]{3}$/.test(price.currency) || isReceipt(financials)) return null;
|
|
143
|
+
const step = latestStep(financials);
|
|
144
|
+
if (!step || now - Date.parse(step.periodEnd) > STALE_TRAILING_EPS_MS) return null;
|
|
145
|
+
const currency = resolveCurrencyUnit(step.currency);
|
|
146
|
+
if (currency.currency !== price.currency || currency.divisor !== 1) return null;
|
|
147
|
+
// The trailing block's amounts are in another currency: an EPS beside them in this one would be read as theirs.
|
|
148
|
+
const block = financials.fundamentals?.financialCurrency?.trim();
|
|
149
|
+
if (block && resolveCurrencyUnit(block).currency !== price.currency) return null;
|
|
150
|
+
const split = financials.fundamentals?.splitShareBasis?.exDate;
|
|
151
|
+
if (split && split > step.filed) return null;
|
|
152
|
+
const served = financials.fundamentals?.eps;
|
|
153
|
+
if (typeof served === "number" && Number.isFinite(served) && !withinServedRatio(step.eps, served)) return null;
|
|
154
|
+
return { eps: step.eps, periodEnd: step.periodEnd, currency: price.currency };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Same sign and within SERVED_EPS_RATIO_LIMIT of each other; a figure against a zero is not. */
|
|
158
|
+
function withinServedRatio(filed: number, served: number): boolean {
|
|
159
|
+
const ratio = filed / served;
|
|
160
|
+
return Number.isFinite(ratio) && ratio > 0 && ratio <= SERVED_EPS_RATIO_LIMIT && ratio >= 1 / SERVED_EPS_RATIO_LIMIT;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const finite = (value: number | null | undefined): value is number => typeof value === "number" && Number.isFinite(value);
|
|
164
|
+
const positive = (value: number | null | undefined): value is number => finite(value) && value > 0;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The financials with their headline trailing EPS and P/E on the filed
|
|
168
|
+
* quarters (filedTrailingEps): `fundamentals.eps` is that figure and
|
|
169
|
+
* `trailingPE` the quote's price over it, so every view that moves the P/E to
|
|
170
|
+
* the regular session price (atRegularSessionPrice, liveTrailingPE) reads the
|
|
171
|
+
* multiple PEB and GE read. Without one, Gloom Cloud's trailing EPS and P/E
|
|
172
|
+
* stand, marked `served`, as does a P/E it serves with no EPS (a fund's). A
|
|
173
|
+
* block with neither is left as it is.
|
|
174
|
+
*/
|
|
175
|
+
export function withFiledTrailingEps<T extends TrailingEpsInputs>(financials: T, now = Date.now()): T {
|
|
176
|
+
const fundamentals = financials.fundamentals;
|
|
177
|
+
if (!fundamentals) return financials;
|
|
178
|
+
const filed = filedTrailingEps(financials, now);
|
|
179
|
+
if (!filed) {
|
|
180
|
+
const served = finite(fundamentals.eps) || finite(fundamentals.trailingPE);
|
|
181
|
+
if (!served || (fundamentals.trailingEpsSource === "served" && fundamentals.trailingEpsPeriodEnd === undefined)) return financials;
|
|
182
|
+
const { trailingEpsPeriodEnd: _period, ...rest } = fundamentals;
|
|
183
|
+
return { ...financials, fundamentals: { ...rest, trailingEpsSource: "served" } };
|
|
184
|
+
}
|
|
185
|
+
const { trailingPEConversion: _conversion, epsCurrency: _currency, trailingPE: _served, ...rest } = fundamentals;
|
|
186
|
+
// filedTrailingEps reads a quote's currency, so there is a quote; its price is the one atRegularSessionPrice moves from.
|
|
187
|
+
const price = financials.quote!.price;
|
|
188
|
+
const next: Fundamentals = {
|
|
189
|
+
...rest,
|
|
190
|
+
eps: filed.eps,
|
|
191
|
+
// A loss leaves no multiple: priceEarningsOnEarnings reads the EPS as not meaningful.
|
|
192
|
+
...(positive(price) && filed.eps > 0 ? { trailingPE: price / filed.eps } : {}),
|
|
193
|
+
trailingEpsSource: "filed-quarters",
|
|
194
|
+
trailingEpsPeriodEnd: filed.periodEnd,
|
|
195
|
+
};
|
|
196
|
+
return { ...financials, fundamentals: next };
|
|
197
|
+
}
|
|
@@ -9,6 +9,7 @@ import type { ValuationCurrencyContext } from "./valuation-currency";
|
|
|
9
9
|
import { currentValuationPrice, valuationPriceAtOrBefore } from "./valuation-price";
|
|
10
10
|
import type { TimeSeriesPoint } from "./types";
|
|
11
11
|
import { isFiniteNumber } from "../utils/guards";
|
|
12
|
+
import { dailyValuationHistory, type DailyValuationBasis } from "./daily-valuation";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Forward multiples over time. No source serves the consensus as it stood on
|
|
@@ -236,6 +237,37 @@ export function forwardPeHistory(
|
|
|
236
237
|
return points;
|
|
237
238
|
}
|
|
238
239
|
|
|
240
|
+
/** Daily closes over consensus that was actually observed by that date. */
|
|
241
|
+
export function dailyForwardPeHistory(
|
|
242
|
+
financials: TickerFinancials,
|
|
243
|
+
currencies: ValuationCurrencyContext,
|
|
244
|
+
exchange?: string,
|
|
245
|
+
): TimeSeriesPoint[] {
|
|
246
|
+
const history = financials.epsEstimates;
|
|
247
|
+
if (!history) return [];
|
|
248
|
+
// Pre-report estimates from future quarters are final-vintage figures,
|
|
249
|
+
// not the forecasts investors had on an earlier day. Only dated snapshots
|
|
250
|
+
// establish a historical daily forward denominator.
|
|
251
|
+
const bases: DailyValuationBasis[] = [...snapshotsByDay(history.snapshots)].map(([day, periods]) => {
|
|
252
|
+
const current = periods.get("current year");
|
|
253
|
+
const next = periods.get("next year");
|
|
254
|
+
const eps = blendedNextTwelveMonthsEps(day,
|
|
255
|
+
current ? { periodEnd: current.periodEnd, eps: current.epsAverage } : undefined,
|
|
256
|
+
next ? { eps: next.epsAverage } : undefined);
|
|
257
|
+
const date = new Date(day);
|
|
258
|
+
return {
|
|
259
|
+
availableAt: day,
|
|
260
|
+
observedAt: date,
|
|
261
|
+
periodLabel: `Consensus observed ${day}`,
|
|
262
|
+
atPrice: (price: number) => {
|
|
263
|
+
const comparable = currencies.priceInStatementUnits({ date: day, currency: history.currency ?? financials.financialCurrency }, price);
|
|
264
|
+
return comparable !== null && eps !== null ? comparable / eps : null;
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
});
|
|
268
|
+
return dailyValuationHistory(financials.priceHistory, bases, financials.quote?.listingExchangeName || financials.quote?.exchangeName || exchange);
|
|
269
|
+
}
|
|
270
|
+
|
|
239
271
|
/** Price at each report date over the EPS reported in the following four quarters. */
|
|
240
272
|
export function realizedNtmPeHistory(
|
|
241
273
|
financials: TickerFinancials,
|
|
@@ -8,7 +8,9 @@ import { fiscalPeriodLabel, fiscalYearEndMonth, ttmPeriodLabel } from "../utils/
|
|
|
8
8
|
import { hasStatementGap, mergeStatementGaps } from "../utils/statement-gaps";
|
|
9
9
|
import { trailingEpsAcrossFiscalYearEnd } from "../utils/trailing-eps";
|
|
10
10
|
import { canonicalTimeSeriesFieldId, getTimeSeriesField } from "./field-catalog";
|
|
11
|
-
import { forwardPeHistory, realizedNtmPeHistory } from "./forward-valuation";
|
|
11
|
+
import { dailyForwardPeHistory, forwardPeHistory, realizedNtmPeHistory } from "./forward-valuation";
|
|
12
|
+
import { dailyValuationHistory, type DailyValuationBasis } from "./daily-valuation";
|
|
13
|
+
import { statementAtRelease } from "./valuation-releases";
|
|
12
14
|
import { reportingCurrencySeries } from "./reporting-currency";
|
|
13
15
|
import { createValuationCurrencyContext, type ValuationCurrencyContext } from "./valuation-currency";
|
|
14
16
|
import { currentValuationPrice, valuationPriceAtOrBefore, valuationQuoteIssue, type ValuationPriceIssue } from "./valuation-price";
|
|
@@ -474,7 +476,7 @@ function buildTtmStatements(
|
|
|
474
476
|
const availableAt = statementFieldAvailability(latest, field);
|
|
475
477
|
if (availableAt) ttm.fieldAvailability![field] = availableAt;
|
|
476
478
|
}
|
|
477
|
-
ttm.availableAt = completeStatementAvailability(ttm);
|
|
479
|
+
ttm.availableAt = completeStatementAvailability(ttm) ?? latest.availableAt;
|
|
478
480
|
result.push(ttm);
|
|
479
481
|
}
|
|
480
482
|
return result;
|
|
@@ -490,7 +492,7 @@ function buildTtmStatements(
|
|
|
490
492
|
export function knownWhenFiled(row: FinancialStatement): FinancialStatement {
|
|
491
493
|
const filed = row.epsBasis?.status === "split-adjusted" ? row.epsBasis.originalFiled : undefined;
|
|
492
494
|
if (!filed || !Number.isFinite(Date.parse(filed))) return row;
|
|
493
|
-
const current = row
|
|
495
|
+
const current = statementFieldAvailability(row, "eps");
|
|
494
496
|
if (current && Date.parse(current) <= Date.parse(filed)) return row;
|
|
495
497
|
return { ...row, fieldAvailability: { ...row.fieldAvailability, eps: filed } };
|
|
496
498
|
}
|
|
@@ -500,6 +502,56 @@ function valuationStatements(financials: TickerFinancials): { quarterly: Financi
|
|
|
500
502
|
return { quarterly: financials.quarterlyStatements.map(knownWhenFiled), annual: financials.annualStatements.map(knownWhenFiled) };
|
|
501
503
|
}
|
|
502
504
|
|
|
505
|
+
/**
|
|
506
|
+
* A fiscal fourth quarter's EPS stated without a filing date of its own (a
|
|
507
|
+
* 10-K states the year, the results release the quarter) was public once its
|
|
508
|
+
* year's filing was, so it is dated by that filing. The daily P/E of the
|
|
509
|
+
* quarters after it then sums the same four quarters PEB and the headline EPS
|
|
510
|
+
* (TTM) sum (time-series/filed-trailing-eps), rather than reading the quarter
|
|
511
|
+
* from the year and landing a cent of rounding away from them. The window that
|
|
512
|
+
* ends with the year takes the year's own EPS (fiscalYearEpsAtYearEnd).
|
|
513
|
+
*/
|
|
514
|
+
function fourthQuarterEpsAtYearFiling(row: FinancialStatement, annual: readonly FinancialStatement[]): FinancialStatement {
|
|
515
|
+
if (!isFiniteNumber(row.eps) || hasStatementGap(row, "eps") || statementFieldAvailability(row, "eps")) return row;
|
|
516
|
+
const year = annual.find((candidate) => candidate.currency === row.currency && areNearbyFinancialPeriodEnds(candidate.date, row.date));
|
|
517
|
+
const filed = year && isFiniteNumber(year.eps) ? statementFieldAvailability(year, "eps") : undefined;
|
|
518
|
+
return filed ? { ...row, fieldAvailability: { ...row.fieldAvailability, eps: filed } } : row;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* At a fiscal year end the filed year's diluted EPS is the daily trailing
|
|
523
|
+
* figure, as PEB and the headline EPS (TTM) read it (filed-trailing-eps): four
|
|
524
|
+
* quarters need not add up to their year (3.90 for the year beside four
|
|
525
|
+
* quarters of 1.00), so their sum stands only where the year gives no dated EPS.
|
|
526
|
+
*/
|
|
527
|
+
function fiscalYearEpsAtYearEnd(statements: readonly InternalStatement[], annual: readonly FinancialStatement[]): InternalStatement[] {
|
|
528
|
+
return statements.map((statement) => {
|
|
529
|
+
const year = annual.find((row) => row.currency === statement.currency && areNearbyFinancialPeriodEnds(row.date, statement.date));
|
|
530
|
+
const eps = year && !hasStatementGap(year, "eps") ? statementNumber(year, "eps") : null;
|
|
531
|
+
if (!year || eps === null) return statement;
|
|
532
|
+
const { epsBasis: _sum, ...rest } = statement;
|
|
533
|
+
const fieldAvailability = { ...statement.fieldAvailability };
|
|
534
|
+
const filed = statementFieldAvailability(year, "eps");
|
|
535
|
+
if (filed) fieldAvailability.eps = filed;
|
|
536
|
+
else delete fieldAvailability.eps;
|
|
537
|
+
const next: InternalStatement = {
|
|
538
|
+
...rest, eps, fieldAvailability, ...(year.epsBasis ? { epsBasis: year.epsBasis } : {}),
|
|
539
|
+
__timeSeriesDerivedFields: [...new Set([...(statement.__timeSeriesDerivedFields ?? []), "eps" as const])],
|
|
540
|
+
};
|
|
541
|
+
next.availableAt = completeStatementAvailability(next) ?? statement.availableAt;
|
|
542
|
+
return next;
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/** An undated vendor value cannot block a derivation from dated filings. */
|
|
547
|
+
function datedValuationStatement(statement: FinancialStatement): FinancialStatement {
|
|
548
|
+
const dated = { ...statement };
|
|
549
|
+
for (const field of NUMERIC_STATEMENT_FIELDS) {
|
|
550
|
+
if (!statementFieldAvailability(statement, field)) delete dated[field];
|
|
551
|
+
}
|
|
552
|
+
return dated;
|
|
553
|
+
}
|
|
554
|
+
|
|
503
555
|
function normalizeFundamentalPeriod(period: SeriesPeriod | undefined): "annual" | "quarterly" | "ttm" | "auto" {
|
|
504
556
|
return period === "annual" || period === "quarterly" || period === "ttm" ? period : "auto";
|
|
505
557
|
}
|
|
@@ -508,12 +560,67 @@ function sourceStatements(
|
|
|
508
560
|
financials: TickerFinancials,
|
|
509
561
|
period: SeriesPeriod | undefined,
|
|
510
562
|
valuationMetric?: string,
|
|
563
|
+
exchange?: string,
|
|
511
564
|
): { statements: InternalStatement[]; period: "annual" | "quarterly" | "ttm" } {
|
|
512
565
|
const normalized = normalizeFundamentalPeriod(period);
|
|
513
|
-
|
|
566
|
+
let known = valuationMetric
|
|
514
567
|
? valuationStatements(financials)
|
|
515
568
|
: { quarterly: financials.quarterlyStatements, annual: financials.annualStatements };
|
|
569
|
+
if (period === "daily" && valuationMetric) {
|
|
570
|
+
const releases = financials.earningsReleases ?? [];
|
|
571
|
+
const venue = financials.quote?.listingExchangeName || financials.quote?.exchangeName || exchange;
|
|
572
|
+
known = { quarterly: known.quarterly.map(row => statementAtRelease(row, releases, venue)),
|
|
573
|
+
annual: known.annual.map(row => statementAtRelease(row, releases, venue)) };
|
|
574
|
+
const annualRows = known.annual;
|
|
575
|
+
known = { ...known, quarterly: known.quarterly.map((row) => fourthQuarterEpsAtYearFiling(row, annualRows)) };
|
|
576
|
+
known = { quarterly: known.quarterly.map(datedValuationStatement), annual: known.annual.map(datedValuationStatement) };
|
|
577
|
+
// A later comparative quarter may repeat a year-end balance sheet that
|
|
578
|
+
// the annual filing already published. Only identical, same-currency
|
|
579
|
+
// snapshots can inherit that earlier evidence; changed values cannot.
|
|
580
|
+
known.quarterly = known.quarterly.map((statement) => {
|
|
581
|
+
const dated = { ...statement, fieldAvailability: Object.fromEntries(NUMERIC_STATEMENT_FIELDS.flatMap((field) => {
|
|
582
|
+
const availableAt = statementFieldAvailability(statement, field);
|
|
583
|
+
return availableAt ? [[field, availableAt]] : [];
|
|
584
|
+
})) };
|
|
585
|
+
for (const annual of known.annual) {
|
|
586
|
+
if (annual.currency !== dated.currency || !areNearbyFinancialPeriodEnds(annual.date, dated.date)) continue;
|
|
587
|
+
for (const field of QUARTERLY_SNAPSHOT_FIELDS) {
|
|
588
|
+
const value = statementNumber(dated, field);
|
|
589
|
+
const earlier = statementFieldAvailability(annual, field);
|
|
590
|
+
const current = statementFieldAvailability(dated, field);
|
|
591
|
+
if (value !== null && value === statementNumber(annual, field) && earlier
|
|
592
|
+
&& (!current || Date.parse(earlier) < Date.parse(current))) {
|
|
593
|
+
dated.fieldAvailability[field] = earlier;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return dated;
|
|
598
|
+
});
|
|
599
|
+
}
|
|
516
600
|
const quarterly = deriveQuarterlyStatements(known.quarterly, known.annual);
|
|
601
|
+
if (period === "daily" && valuationMetric) {
|
|
602
|
+
// Sparse or loss-making quarters are gaps, not permission to substitute
|
|
603
|
+
// an older annual denominator. Annual-only issuers retain their years.
|
|
604
|
+
if (!known.quarterly.length) return { statements: mergeStatementsByPeriod(known.annual), period: "annual" };
|
|
605
|
+
const statements = fiscalYearEpsAtYearEnd(buildTtmStatements(quarterly, known.annual), known.annual);
|
|
606
|
+
// Capitalization uses the latest reported share count, not an average
|
|
607
|
+
// of four quarters. Keep TTM average shares for the income/EPS fallback.
|
|
608
|
+
if (valuationMetric !== "trailingPE") for (const statement of statements) {
|
|
609
|
+
const latest = quarterly.find((row) => row.date === statement.date);
|
|
610
|
+
const year = known.annual.find((row) => areNearbyFinancialPeriodEnds(row.date, statement.date));
|
|
611
|
+
const shareRow = latest && selectedShares(latest) ? latest : year;
|
|
612
|
+
for (const field of ["dilutedShares", "basicShares", "ordinarySharesNumber", "shareIssued"] as const) {
|
|
613
|
+
delete statement[field];
|
|
614
|
+
delete statement.fieldAvailability?.[field];
|
|
615
|
+
if (shareRow?.[field] !== undefined) {
|
|
616
|
+
statement[field] = shareRow[field];
|
|
617
|
+
const availableAt = statementFieldAvailability(shareRow, field);
|
|
618
|
+
if (availableAt) (statement.fieldAvailability ??= {})[field] = availableAt;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return { statements, period: "ttm" };
|
|
623
|
+
}
|
|
517
624
|
if (normalized === "annual") {
|
|
518
625
|
return { statements: mergeStatementsByPeriod(known.annual), period: "annual" };
|
|
519
626
|
}
|
|
@@ -727,7 +834,7 @@ function historicalValuation(
|
|
|
727
834
|
currencies: ValuationCurrencyContext,
|
|
728
835
|
) {
|
|
729
836
|
// A price failure cannot make absent financial inputs appear available.
|
|
730
|
-
if (currencies.priceInStatementUnits(statement, 1) === null || valuationAtPrice(statement, metric, 1) === null) return null;
|
|
837
|
+
if (currencies.priceInStatementUnits(statement, 1) === null || valuationAtPrice(statement, metric, 1, true) === null) return null;
|
|
731
838
|
const priceDate = metricAvailability(statement, metric) ?? statement.date;
|
|
732
839
|
const price = valuationPriceAtOrBefore(financials.priceHistory, priceDate);
|
|
733
840
|
if (!price) return null;
|
|
@@ -754,6 +861,7 @@ function valuationAtPrice(
|
|
|
754
861
|
statement: FinancialStatement,
|
|
755
862
|
metric: string,
|
|
756
863
|
price: number,
|
|
864
|
+
checkingInputs = false,
|
|
757
865
|
): number | null {
|
|
758
866
|
const shares = selectedShares(statement);
|
|
759
867
|
const marketCap = shares ? price * shares.value : null;
|
|
@@ -769,12 +877,83 @@ function valuationAtPrice(
|
|
|
769
877
|
return eps && eps > 0 ? price / eps : null;
|
|
770
878
|
}
|
|
771
879
|
if (metric === "priceSales") return ratio(marketCap, statement.totalRevenue);
|
|
880
|
+
// The probe at price=1 checks input coverage, not the historical EV sign.
|
|
881
|
+
if (!checkingInputs && enterpriseValue !== null && enterpriseValue < 0 && (metric === "evSales" || metric === "evEbitda")) return null;
|
|
772
882
|
if (metric === "evSales") return ratio(enterpriseValue, statement.totalRevenue);
|
|
773
883
|
if (metric === "evEbitda") return ratio(enterpriseValue, statement.ebitda);
|
|
774
884
|
if (metric === "priceFcf") return ratio(marketCap, freeCashFlow(statement).value);
|
|
775
885
|
return null;
|
|
776
886
|
}
|
|
777
887
|
|
|
888
|
+
function dailyStatementValuation(financials: TickerFinancials, metric: string, exchange?: string): TimeSeriesPoint[] {
|
|
889
|
+
const earningsMetric = metric === "pegRatio" ? "trailingPE" : metric;
|
|
890
|
+
const selected = sourceStatements(financials, "daily", earningsMetric, exchange);
|
|
891
|
+
const currencies = createValuationCurrencyContext(financials);
|
|
892
|
+
const yearEndMonth = fiscalYearEndMonth(financials.annualStatements);
|
|
893
|
+
const bases: DailyValuationBasis[] = selected.statements.flatMap((statement) => {
|
|
894
|
+
const usable = valuationAtPrice(statement, earningsMetric, 1, true) !== null;
|
|
895
|
+
// Missing dates cannot put information at the period end: until its
|
|
896
|
+
// publication is known there is no historical denominator to use.
|
|
897
|
+
let publication = metricAvailability(statement, earningsMetric)
|
|
898
|
+
?? (!usable ? statement.availableAt : undefined);
|
|
899
|
+
let growth: number | null = null;
|
|
900
|
+
if (metric === "pegRatio") {
|
|
901
|
+
const prior = selected.statements.find((candidate) => {
|
|
902
|
+
const days = (statementTime(statement) - statementTime(candidate)) / DAY_MS;
|
|
903
|
+
return days >= 330 && days <= 400;
|
|
904
|
+
});
|
|
905
|
+
const currentEps = selectedEps(statement)?.value;
|
|
906
|
+
const previousEps = prior ? selectedEps(prior)?.value : null;
|
|
907
|
+
if (prior && currentEps != null && currentEps > 0 && previousEps != null && previousEps > 0) {
|
|
908
|
+
publication = completeAvailability([publication, metricAvailability(prior, "trailingPE")]);
|
|
909
|
+
const change = (currentEps / previousEps - 1) * 100;
|
|
910
|
+
if (change > 0) growth = change;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
const observedAt = validDate(statement.date);
|
|
914
|
+
if (!observedAt) return [];
|
|
915
|
+
const availableAt = validDate(publication);
|
|
916
|
+
if (availableAt && availableAt < observedAt) publication = undefined;
|
|
917
|
+
const point = pointForStatement(statement, earningsMetric, null, selected.period, "available-at", true, yearEndMonth);
|
|
918
|
+
return [{
|
|
919
|
+
availableAt: publication,
|
|
920
|
+
observedAt,
|
|
921
|
+
periodLabel: point?.periodLabel,
|
|
922
|
+
atPrice: (price: number) => {
|
|
923
|
+
// An undated newer period must stop the previous TTM from continuing.
|
|
924
|
+
// Its period end only bounds the gap; it never publishes a value.
|
|
925
|
+
if (!publication || !availableAt) return null;
|
|
926
|
+
const comparable = currencies.priceInStatementUnits(statement, price);
|
|
927
|
+
if (comparable === null) return null;
|
|
928
|
+
const value = valuationAtPrice(statement, earningsMetric, comparable);
|
|
929
|
+
// Negative cash flow, revenue or EBITDA cannot produce a meaningful
|
|
930
|
+
// multiple. Explicit period views retain their historical behavior.
|
|
931
|
+
const denominator = earningsMetric === "priceFcf" ? freeCashFlow(statement).value
|
|
932
|
+
: earningsMetric === "evEbitda" ? statement.ebitda
|
|
933
|
+
: earningsMetric === "priceSales" || earningsMetric === "evSales" ? statement.totalRevenue : 1;
|
|
934
|
+
if (denominator == null || denominator <= 0 || value === null) return null;
|
|
935
|
+
return metric === "pegRatio" ? growth === null ? null : value / growth : value;
|
|
936
|
+
},
|
|
937
|
+
}];
|
|
938
|
+
});
|
|
939
|
+
const points = dailyValuationHistory(financials.priceHistory, bases, financials.quote?.listingExchangeName || financials.quote?.exchangeName || exchange);
|
|
940
|
+
return points.some((point) => point.value !== null) ? points : [];
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/** A daily history cannot be dated at all, but the issuer still has reported figures. */
|
|
944
|
+
export function dailyValuationFallbackPeriod(financials: TickerFinancials, source: SecuritySeriesSource): "annual" | "quarterly" | "ttm" | null {
|
|
945
|
+
if (source.period !== "daily") return null;
|
|
946
|
+
const [namespace, metric = ""] = canonicalTimeSeriesFieldId(source.fieldId).split(".");
|
|
947
|
+
const inputMetric = metric === "pegRatio" ? "trailingPE" : metric;
|
|
948
|
+
if (namespace !== "valuation" || !QUOTE_DERIVED_VALUATION_IDS.has(inputMetric)) return null;
|
|
949
|
+
const daily = sourceStatements(financials, "daily", inputMetric, source.instrument.exchange);
|
|
950
|
+
if (daily.statements.some(row => metricAvailability(row, inputMetric))) return null;
|
|
951
|
+
const annual = sourceStatements(financials, "annual", inputMetric);
|
|
952
|
+
if (annual.statements.some(row => valuationAtPrice(row, inputMetric, 1, true) !== null)) return "annual";
|
|
953
|
+
const reported = sourceStatements(financials, "auto", inputMetric);
|
|
954
|
+
return reported.statements.some(row => valuationAtPrice(row, inputMetric, 1, true) !== null) ? reported.period : null;
|
|
955
|
+
}
|
|
956
|
+
|
|
778
957
|
function providerCurrentValuationPoint(
|
|
779
958
|
financials: TickerFinancials,
|
|
780
959
|
metric: string,
|
|
@@ -887,9 +1066,9 @@ export function valuationSeriesUsesLiveQuote(fieldId: string): boolean {
|
|
|
887
1066
|
}
|
|
888
1067
|
|
|
889
1068
|
/** Whether a valuation field needs the full price history to date its observations. */
|
|
890
|
-
export function valuationSeriesUsesPriceHistory(fieldId: string): boolean {
|
|
1069
|
+
export function valuationSeriesUsesPriceHistory(fieldId: string, period?: SeriesPeriod): boolean {
|
|
891
1070
|
const [namespace, metric = ""] = canonicalTimeSeriesFieldId(fieldId).split(".");
|
|
892
|
-
return namespace === "valuation" && PRICE_HISTORY_VALUATION_IDS.has(metric);
|
|
1071
|
+
return namespace === "valuation" && (PRICE_HISTORY_VALUATION_IDS.has(metric) || (metric === "pegRatio" && period === "daily"));
|
|
893
1072
|
}
|
|
894
1073
|
|
|
895
1074
|
function preferredPeriodPoint(
|
|
@@ -983,6 +1162,15 @@ export function extractFundamentalSeries(
|
|
|
983
1162
|
}
|
|
984
1163
|
|
|
985
1164
|
if (namespace !== "valuation" || !VALUATION_IDS.has(metric)) return [];
|
|
1165
|
+
const fallback = dailyValuationFallbackPeriod(financials, source);
|
|
1166
|
+
if (fallback) return extractFundamentalSeries(financials, { ...source, period: fallback });
|
|
1167
|
+
if (source.period === "daily" && metric !== "realizedNtmPE") {
|
|
1168
|
+
if (metric !== "forwardPE") return dailyStatementValuation(financials, metric, source.instrument.exchange);
|
|
1169
|
+
const points = dailyForwardPeHistory(financials, createValuationCurrencyContext(financials), source.instrument.exchange);
|
|
1170
|
+
if (points.some((point) => point.value !== null)) return points;
|
|
1171
|
+
const current = providerCurrentValuationPoint(financials, metric);
|
|
1172
|
+
return current ? [current] : [];
|
|
1173
|
+
}
|
|
986
1174
|
if (metric === "forwardPE") {
|
|
987
1175
|
const points = forwardPeHistory(financials, createValuationCurrencyContext(financials));
|
|
988
1176
|
if (points.length > 0) return points;
|
|
@@ -1066,6 +1254,9 @@ export function valuationCurrencyWarning(financials: TickerFinancials, source: S
|
|
|
1066
1254
|
|
|
1067
1255
|
/** Structured failures preserve source values independently of the chart's ratios. */
|
|
1068
1256
|
export function valuationPriceIssues(financials: TickerFinancials, source: SecuritySeriesSource): ValuationPriceIssue[] {
|
|
1257
|
+
if (source.period === "daily" && canonicalTimeSeriesFieldId(source.fieldId).startsWith("valuation.")) {
|
|
1258
|
+
return extractFundamentalSeries(financials, source).flatMap((point) => point.provenance?.valuationPriceIssues ?? []);
|
|
1259
|
+
}
|
|
1069
1260
|
if (!valuationSeriesUsesLiveQuote(source.fieldId)) return [];
|
|
1070
1261
|
const metric = canonicalTimeSeriesFieldId(source.fieldId).split(".")[1]!;
|
|
1071
1262
|
const currencies = createValuationCurrencyContext(financials);
|
package/src/time-series/hooks.ts
CHANGED
|
@@ -79,6 +79,7 @@ export function useResolvedChartSpec(
|
|
|
79
79
|
const sources = useMemo(() => ({
|
|
80
80
|
dataProvider,
|
|
81
81
|
loadFredSeries: loadFred,
|
|
82
|
+
loadEarningsHistory: (symbol: string) => apiClient.getCloudEarningsHistory(symbol, 100),
|
|
82
83
|
resolveCapabilitySeries: createChartSeriesResolver(capabilityInvoker),
|
|
83
84
|
capabilityRefreshMs: registeredSeriesRefreshMs,
|
|
84
85
|
loadFuturesPriceUnit: (symbol: string) => loadFuturesPriceUnit(symbol),
|
|
@@ -17,6 +17,9 @@ export interface IceCreditSeries {
|
|
|
17
17
|
tags: readonly string[];
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/** What every surface that shows an ICE series says about how far back FRED carries it. */
|
|
21
|
+
export const ICE_HISTORY_LIMIT_NOTICE = "History limited to 3 years by the publisher.";
|
|
22
|
+
|
|
20
23
|
const HIGH_YIELD = ["high yield", "junk"];
|
|
21
24
|
|
|
22
25
|
export const ICE_CREDIT_SERIES: readonly IceCreditSeries[] = [
|
|
@@ -10,6 +10,19 @@ import {
|
|
|
10
10
|
} from "../market-data/market/period-return";
|
|
11
11
|
|
|
12
12
|
const PRICE_COMPARISON_BASIS = "Price returns in each listing's currency; cash distributions and FX conversion excluded.";
|
|
13
|
+
const TOTAL_COMPARISON_BASIS = "Total returns in each listing's currency; cash distributions reinvested at the ex-date close, FX conversion excluded.";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* What a comparison's returns are: price returns, or total returns when the
|
|
17
|
+
* chart asked for them, naming any leg whose distributions could not be read
|
|
18
|
+
* (it stays a price return, and the others do not wait for it).
|
|
19
|
+
*/
|
|
20
|
+
function comparisonBasis(spec: ChartSpec, legs: ReadonlyArray<{ label: string; entry: ResolvedSeries | undefined }>): string {
|
|
21
|
+
if (spec.viewport.totalReturn !== true) return PRICE_COMPARISON_BASIS;
|
|
22
|
+
const priceOnly = legs.filter(({ entry }) => entry?.returnBasis === "price").map(({ label }) => label);
|
|
23
|
+
if (priceOnly.length === legs.length && legs.length > 0) return PRICE_COMPARISON_BASIS;
|
|
24
|
+
return priceOnly.length ? `${TOTAL_COMPARISON_BASIS} Price return for ${priceOnly.join(", ")}: distribution history unavailable.` : TOTAL_COMPARISON_BASIS;
|
|
25
|
+
}
|
|
13
26
|
|
|
14
27
|
export interface PriceComparison {
|
|
15
28
|
seriesIds: string[];
|
|
@@ -111,6 +124,20 @@ export function priceComparisonSeriesIds(spec: ChartSpec): string[] | null {
|
|
|
111
124
|
return visible.map((entry) => entry.id);
|
|
112
125
|
}
|
|
113
126
|
|
|
127
|
+
/**
|
|
128
|
+
* What a total-return chart's price levels are when it is not a comparison (the
|
|
129
|
+
* raw price axis): prices with cash distributions reinvested, not what the
|
|
130
|
+
* listing quotes. Null for a price chart, and when no leg is a total return.
|
|
131
|
+
*/
|
|
132
|
+
export function totalReturnLevelsNotice(spec: ChartSpec, series: readonly ResolvedSeries[]): string | null {
|
|
133
|
+
if (spec.viewport.totalReturn !== true) return null;
|
|
134
|
+
const legs = series.filter((entry) => entry.returnBasis !== undefined);
|
|
135
|
+
if (!legs.some((entry) => entry.returnBasis === "total")) return null;
|
|
136
|
+
const priceOnly = legs.filter((entry) => entry.returnBasis === "price").map((entry) => entry.label);
|
|
137
|
+
return `Total return levels in each listing's currency: prices include cash distributions reinvested at the ex-date close, so they are not quoted prices; FX conversion excluded.${
|
|
138
|
+
priceOnly.length ? ` Price only for ${priceOnly.join(", ")}: distribution history unavailable.` : ""}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
114
141
|
interface ComparedLeg {
|
|
115
142
|
id: string;
|
|
116
143
|
label: string;
|
|
@@ -152,7 +179,8 @@ export function resolvePriceComparison(
|
|
|
152
179
|
return source?.kind === "security" ? source.instrument.symbol : id;
|
|
153
180
|
};
|
|
154
181
|
// A leg not loaded yet (a seed from cached history) has not failed: nothing compares until it is.
|
|
155
|
-
const
|
|
182
|
+
const basis = comparisonBasis(spec, seriesIds.map((id) => ({ label: symbolOf(id), entry: byId.get(id) })));
|
|
183
|
+
const unavailable = `${basis} Comparison unavailable: need two shared dates and a nonzero baseline.`;
|
|
156
184
|
if (seriesIds.some((id) => !byId.has(id))) return { seriesIds, start: null, end: null, notice: unavailable };
|
|
157
185
|
const dropped: NonNullable<PriceComparison["dropped"]> = [];
|
|
158
186
|
const reported = new Set<string>();
|
|
@@ -259,7 +287,7 @@ export function resolvePriceComparison(
|
|
|
259
287
|
sourceBounds,
|
|
260
288
|
legs: legDays,
|
|
261
289
|
...(dropped.length ? { dropped } : {}),
|
|
262
|
-
notice: `${
|
|
290
|
+
notice: `${basis} ${span}${droppedText}`,
|
|
263
291
|
};
|
|
264
292
|
}
|
|
265
293
|
|