gloomberb 0.16.1 → 0.16.2
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 +2 -1
- package/package.json +2 -1
- package/src/api-client/beneficial-owners.ts +92 -0
- package/src/api-client/data.ts +26 -8
- package/src/api-client/errors.ts +12 -0
- package/src/api-client/geo.ts +363 -0
- package/src/api-client/index.ts +2 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/paths.ts +30 -1
- package/src/api-client/perps.ts +37 -0
- package/src/api-client/request.ts +5 -2
- package/src/api-client/types.ts +2 -0
- package/src/brokers/account-cache.ts +24 -0
- package/src/cli/commands/command-utils.ts +8 -0
- package/src/cli/commands/crypto-hints.ts +75 -0
- package/src/cli/commands/market.ts +414 -106
- package/src/cli/commands/overview.ts +77 -32
- package/src/cli/commands/ticker.ts +101 -43
- package/src/cli/desktop-pane-shot.ts +34 -4
- package/src/cli/freshness.ts +61 -0
- package/src/cli/help.ts +1 -0
- package/src/cli/helpers.ts +4 -8
- package/src/cli/index.ts +12 -2
- package/src/cli/listing-arg.ts +321 -0
- package/src/cli/pane-functions/access-gate.ts +42 -0
- package/src/cli/pane-functions/catalog.ts +28 -1
- package/src/cli/pane-functions/data.ts +3 -1
- package/src/cli/pane-functions/dom.ts +20 -2
- package/src/cli/pane-functions/freshness.ts +400 -0
- package/src/cli/pane-functions/function-help.ts +27 -20
- package/src/cli/pane-functions/headless.ts +39 -11
- package/src/cli/pane-functions/index.ts +15 -4
- package/src/cli/pane-functions/options.ts +3 -0
- package/src/cli/pane-functions/report.ts +4 -0
- package/src/cli/pane-functions/resolver.ts +35 -7
- package/src/cli/pane-functions/screenshot-evidence.ts +2 -0
- package/src/cli/pane-functions/screenshot.ts +7 -3
- package/src/cli/result.ts +41 -27
- package/src/cli/ticker-resolution.ts +7 -5
- package/src/components/chart/composite/composite-chart.tsx +88 -2536
- package/src/components/chart/composite/drawing-store.ts +59 -0
- package/src/components/chart/composite/legend.tsx +319 -0
- package/src/components/chart/composite/panel-bitmap.ts +139 -0
- package/src/components/chart/composite/panel-gestures.ts +59 -0
- package/src/components/chart/composite/panel-labels.ts +107 -0
- package/src/components/chart/composite/panel-surface.tsx +876 -0
- package/src/components/chart/composite/tool-catalog.ts +98 -0
- package/src/components/chart/composite/toolbar.tsx +186 -0
- package/src/components/chart/composite/tools.ts +8 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +703 -0
- package/src/components/chart/composite/use-composite-viewport.ts +143 -0
- package/src/components/command-bar/list/model.ts +5 -0
- package/src/components/command-bar/list/view.tsx +16 -3
- package/src/components/command-bar/pane-templates/workflow.ts +1 -0
- package/src/components/command-bar/routes/ticker-search/actions.ts +5 -0
- package/src/components/command-bar/workflow/ops.ts +1 -1
- package/src/components/command-bar/workflow/tickers.ts +6 -0
- package/src/components/data-table/view.tsx +29 -10
- package/src/components/feedback-dialog.tsx +2 -1
- package/src/components/form-modal/field-row.tsx +2 -2
- package/src/components/layout/detached-pane-shell.tsx +6 -3
- package/src/components/layout/pane/content.tsx +23 -10
- package/src/components/layout/pane/error-boundary.tsx +103 -0
- package/src/components/layout/pane/footer/registration.tsx +68 -23
- package/src/components/layout/pane/header.tsx +11 -11
- package/src/components/layout/shell/menu.ts +2 -1
- package/src/components/layout/shell/pane/actions.ts +2 -1
- package/src/components/layout/shell/pane/layers.tsx +14 -3
- package/src/components/metric-treemap/heat-surface.tsx +43 -16
- package/src/components/metric-treemap/heat-tiles.ts +18 -0
- package/src/components/metric-treemap/layout.ts +2 -0
- package/src/components/onboarding/onboarding-keyboard.ts +311 -0
- package/src/components/onboarding/onboarding-wizard.tsx +101 -788
- package/src/components/onboarding/use-onboarding-broker-form.ts +263 -0
- package/src/components/onboarding/use-onboarding-progress.ts +298 -0
- package/src/components/onboarding/use-onboarding-upgrade.ts +83 -0
- package/src/components/ticker/list-table-view.tsx +13 -1
- package/src/components/ui/fields.tsx +8 -0
- package/src/components/ui/range-track.tsx +10 -3
- package/src/components/ui/status.tsx +8 -2
- package/src/components/ui/tabs.tsx +7 -4
- package/src/components/ui/text-prompt-dialog.tsx +4 -2
- package/src/data/config/store/normalize.ts +17 -1
- package/src/i18n/es.ts +19 -1
- package/src/i18n/ja.ts +19 -1
- package/src/i18n/ko.ts +19 -1
- package/src/i18n/zh-cn.ts +19 -1
- package/src/i18n/zh-tw.ts +19 -1
- package/src/market-data/coordinator/chart.ts +1 -1
- package/src/market-data/coordinator/quotes.ts +23 -4
- package/src/market-data/fx-closes.ts +72 -0
- package/src/market-data/market/freshness.ts +78 -6
- package/src/market-data/market/session-close-note.ts +57 -0
- package/src/market-data/market/status.ts +151 -0
- package/src/market-data/published-apac-sessions.ts +66 -0
- package/src/market-data/quotes/contributions.ts +2 -0
- package/src/market-data/quotes/resolution.ts +2 -0
- package/src/market-data/quotes/screener-live-quotes.ts +49 -4
- package/src/plugins/builtin/account-management/pane.tsx +4 -0
- package/src/plugins/builtin/alerts/cli.ts +10 -5
- package/src/plugins/builtin/alerts/index.tsx +1 -0
- package/src/plugins/builtin/analytics/metrics.ts +8 -3
- package/src/plugins/builtin/analytics/risk-client.ts +191 -66
- package/src/plugins/builtin/analytics/risk-fx.ts +102 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +19 -13
- package/src/plugins/builtin/analytics/risk-model.ts +45 -17
- package/src/plugins/builtin/analytics/risk-pane.tsx +1 -0
- package/src/plugins/builtin/analytics/risk-test-data.ts +25 -1
- package/src/plugins/builtin/attention/headless.ts +6 -4
- package/src/plugins/builtin/attention/index.tsx +1 -1
- package/src/plugins/builtin/attention/pane.tsx +1 -1
- package/src/plugins/builtin/awards/headless.ts +1 -0
- package/src/plugins/builtin/awards/pane.tsx +1 -1
- package/src/plugins/builtin/backtest/headless.ts +1 -0
- package/src/plugins/builtin/bond-calculator/headless.ts +1 -0
- package/src/plugins/builtin/buildout/index.tsx +1 -0
- package/src/plugins/builtin/catalysts/headless.ts +3 -1
- package/src/plugins/builtin/cds/headless.ts +2 -0
- package/src/plugins/builtin/central-bank-rates/headless.ts +1 -0
- package/src/plugins/builtin/chart-composer/headless.ts +38 -3
- package/src/plugins/builtin/chart-composer/index.tsx +3 -1
- package/src/plugins/builtin/chart-composer/series-expression.ts +18 -2
- package/src/plugins/builtin/chat/channels.ts +19 -3
- package/src/plugins/builtin/chat/content/composer-runtime.ts +28 -13
- package/src/plugins/builtin/chat/content/footer.tsx +15 -6
- package/src/plugins/builtin/chat/content/index.tsx +120 -21
- package/src/plugins/builtin/chat/content/new-dm-dialog.tsx +261 -113
- package/src/plugins/builtin/chat/content/transcript.tsx +20 -2
- package/src/plugins/builtin/chat/direct-messages.ts +200 -0
- package/src/plugins/builtin/chat/index.tsx +1 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +3 -0
- package/src/plugins/builtin/chat/message/header.tsx +16 -6
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +72 -32
- package/src/plugins/builtin/chat/message/profile-popover.tsx +96 -12
- package/src/plugins/builtin/chat/message/terminal.tsx +3 -0
- package/src/plugins/builtin/chat/message/types.ts +14 -2
- package/src/plugins/builtin/chat/profile-popover.ts +18 -13
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -0
- package/src/plugins/builtin/cloud/askg/executor.ts +2 -1
- package/src/plugins/builtin/cloud/plugin.tsx +1 -0
- package/src/plugins/builtin/cloud/team/module.tsx +1 -0
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -0
- package/src/plugins/builtin/cloud-tweets/registration.ts +1 -0
- package/src/plugins/builtin/comment-letters/index.tsx +1 -0
- package/src/plugins/builtin/company-attention/headless.ts +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +1 -1
- package/src/plugins/builtin/company-kpis/headless.ts +1 -0
- package/src/plugins/builtin/congress-trades/headless.ts +1 -0
- package/src/plugins/builtin/correlation/compute.ts +59 -16
- package/src/plugins/builtin/correlation/geo.ts +34 -0
- package/src/plugins/builtin/correlation/headless-schema.ts +4 -1
- package/src/plugins/builtin/correlation/headless.ts +42 -8
- package/src/plugins/builtin/correlation/history.ts +2 -0
- package/src/plugins/builtin/correlation/index.tsx +68 -14
- package/src/plugins/builtin/correlation/matrix/model.ts +17 -2
- package/src/plugins/builtin/correlation/matrix/selection.ts +5 -3
- package/src/plugins/builtin/correlation/relationship/model.ts +1 -1
- package/src/plugins/builtin/correlation/relationship/pane.tsx +4 -2
- package/src/plugins/builtin/correlation/relationship/view-model.ts +16 -1
- package/src/plugins/builtin/correlation/settings.ts +1 -1
- package/src/plugins/builtin/cot/headless.ts +6 -3
- package/src/plugins/builtin/cot/index.tsx +3 -3
- package/src/plugins/builtin/cot/model.ts +36 -3
- package/src/plugins/builtin/cpi/headless.ts +4 -0
- package/src/plugins/builtin/credit-boards/headless.ts +2 -0
- package/src/plugins/builtin/credit-conditions/headless.ts +1 -0
- package/src/plugins/builtin/credit-documents/gauge.tsx +5 -8
- package/src/plugins/builtin/credit-documents/headless.ts +2 -0
- package/src/plugins/builtin/crypto-board/headless.ts +3 -0
- package/src/plugins/builtin/custom-view/loader.ts +1 -1
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +1 -0
- package/src/plugins/builtin/doe/headless.ts +7 -0
- package/src/plugins/builtin/doe/range-bar.tsx +7 -37
- package/src/plugins/builtin/earnings/headless.ts +5 -0
- package/src/plugins/builtin/earnings-calls/headless.ts +1 -0
- package/src/plugins/builtin/earnings-ripple/client.ts +91 -18
- package/src/plugins/builtin/earnings-ripple/headless.ts +28 -8
- package/src/plugins/builtin/earnings-ripple/index.tsx +2 -2
- package/src/plugins/builtin/earnings-ripple/model.ts +97 -7
- package/src/plugins/builtin/earnings-ripple/pane.tsx +148 -23
- package/src/plugins/builtin/econ/index.tsx +1 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +2 -0
- package/src/plugins/builtin/equity-screener/headless.ts +2 -0
- package/src/plugins/builtin/estimate-revisions/headless.ts +1 -0
- package/src/plugins/builtin/executives/index.tsx +1 -0
- package/src/plugins/builtin/exposure/headless.ts +3 -1
- package/src/plugins/builtin/fear-greed/headless.ts +2 -0
- package/src/plugins/builtin/filing-events/index.tsx +1 -0
- package/src/plugins/builtin/futures-curve/client.ts +12 -3
- package/src/plugins/builtin/futures-curve/headless.ts +39 -10
- package/src/plugins/builtin/futures-curve/index.tsx +3 -3
- package/src/plugins/builtin/futures-curve/model.ts +188 -4
- package/src/plugins/builtin/futures-curve/pane.tsx +51 -9
- package/src/plugins/builtin/gpu/headless.ts +7 -3
- package/src/plugins/builtin/gpu/ladder.tsx +26 -30
- package/src/plugins/builtin/gpu/model.ts +27 -1
- package/src/plugins/builtin/holders/beneficial-client.ts +209 -0
- package/src/plugins/builtin/holders/beneficial-model.ts +279 -0
- package/src/plugins/builtin/holders/beneficial-report.ts +126 -0
- package/src/plugins/builtin/holders/beneficial-view.tsx +241 -0
- package/src/plugins/builtin/holders/headless.ts +139 -5
- package/src/plugins/builtin/holders/index.tsx +10 -3
- package/src/plugins/builtin/holders/pane.tsx +20 -3
- package/src/plugins/builtin/holders/report-header.ts +95 -0
- package/src/plugins/builtin/holders/table-model.ts +20 -5
- package/src/plugins/builtin/holders/thirteenf-match.ts +29 -1
- package/src/plugins/builtin/holders/types.ts +2 -1
- package/src/plugins/builtin/insider/headless.ts +2 -0
- package/src/plugins/builtin/ipo-calendar/headless.ts +1 -0
- package/src/plugins/builtin/iv-history/headless.ts +4 -1
- package/src/plugins/builtin/jobs/headless.ts +1 -0
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -0
- package/src/plugins/builtin/macro-day/headless.ts +2 -0
- package/src/plugins/builtin/market-halts/index.tsx +1 -0
- package/src/plugins/builtin/market-heatmap/data.ts +2 -0
- package/src/plugins/builtin/market-heatmap/headless.ts +135 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +39 -46
- package/src/plugins/builtin/market-heatmap/model.ts +146 -0
- package/src/plugins/builtin/market-heatmap/portfolio.ts +23 -4
- package/src/plugins/builtin/market-movers/headless.ts +2 -0
- package/src/plugins/builtin/market-valuation/headless.ts +2 -0
- package/src/plugins/builtin/members/client.ts +20 -3
- package/src/plugins/builtin/members/headless.ts +5 -3
- package/src/plugins/builtin/members/model.ts +10 -2
- package/src/plugins/builtin/members/pane.tsx +13 -6
- package/src/plugins/builtin/mna/distress/headless.ts +1 -0
- package/src/plugins/builtin/mna/headless.ts +1 -0
- package/src/plugins/builtin/money-markets/headless.ts +1 -0
- package/src/plugins/builtin/news/headless.ts +2 -0
- package/src/plugins/builtin/news/wire/index.tsx +8 -1
- package/src/plugins/builtin/notes/cli.ts +14 -6
- package/src/plugins/builtin/notes/index.tsx +1 -0
- package/src/plugins/builtin/openfda/index.tsx +1 -0
- package/src/plugins/builtin/options-calculator/headless.ts +2 -0
- package/src/plugins/builtin/options-positioning/headless.ts +3 -0
- package/src/plugins/builtin/options-scenario/headless.ts +5 -0
- package/src/plugins/builtin/pe-band/client.ts +48 -3
- package/src/plugins/builtin/pe-band/headless.ts +25 -7
- package/src/plugins/builtin/pe-band/model.ts +92 -16
- package/src/plugins/builtin/pe-band/pane.tsx +33 -16
- package/src/plugins/builtin/perps/client.ts +50 -3
- package/src/plugins/builtin/perps/headless.ts +144 -16
- package/src/plugins/builtin/perps/index.tsx +6 -6
- package/src/plugins/builtin/perps/model.ts +189 -12
- package/src/plugins/builtin/perps/pane.tsx +248 -47
- package/src/plugins/builtin/portfolio-list/allocation.ts +245 -0
- package/src/plugins/builtin/portfolio-list/cached-account.ts +33 -0
- package/src/plugins/builtin/portfolio-list/cell-version.ts +19 -1
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +328 -29
- package/src/plugins/builtin/portfolio-list/cli/render.ts +230 -39
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +67 -2
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +35 -31
- package/src/plugins/builtin/portfolio-list/column-values.ts +124 -47
- package/src/plugins/builtin/portfolio-list/headless.ts +98 -16
- package/src/plugins/builtin/portfolio-list/index.tsx +14 -3
- package/src/plugins/builtin/portfolio-list/live-valuation.ts +7 -5
- package/src/plugins/builtin/portfolio-list/mutations.ts +55 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +6 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +47 -13
- package/src/plugins/builtin/portfolio-list/settings.ts +33 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +24 -1
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +26 -7
- package/src/plugins/builtin/power/headless.ts +3 -1
- package/src/plugins/builtin/rate-path/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/relative-rotation/headless.ts +2 -0
- package/src/plugins/builtin/research/analyst-headless.ts +3 -0
- package/src/plugins/builtin/research/events-headless.ts +2 -0
- package/src/plugins/builtin/research/headless.ts +2 -0
- package/src/plugins/builtin/research/index.tsx +1 -0
- package/src/plugins/builtin/research/relative-valuation-headless.ts +10 -5
- package/src/plugins/builtin/research/relative-valuation-model.ts +11 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +3 -3
- package/src/plugins/builtin/research-search/index.tsx +1 -0
- package/src/plugins/builtin/revenue-breakdown/headless.ts +2 -0
- package/src/plugins/builtin/reverse-dcf/headless.ts +2 -0
- package/src/plugins/builtin/risk-factors/headless.ts +2 -0
- package/src/plugins/builtin/scanner/flow-history.ts +2 -13
- package/src/plugins/builtin/scanner/headless.ts +14 -0
- package/src/plugins/builtin/seasonality/headless.ts +2 -0
- package/src/plugins/builtin/sec/command-bar-search.ts +14 -4
- package/src/plugins/builtin/sec/filing-content.ts +42 -17
- package/src/plugins/builtin/sec/forms.ts +12 -0
- package/src/plugins/builtin/sec/headless.ts +2 -0
- package/src/plugins/builtin/sectors/client.ts +1 -0
- package/src/plugins/builtin/sectors/headless.ts +9 -1
- package/src/plugins/builtin/sectors/sector-model.ts +2 -0
- package/src/plugins/builtin/shared/crypto-pair.ts +14 -0
- package/src/plugins/builtin/shared/locked-rows.tsx +1 -1
- package/src/plugins/builtin/shared/report-freshness.ts +57 -0
- package/src/plugins/builtin/shared/research-cells.tsx +4 -9
- package/src/plugins/builtin/short-interest/headless.ts +2 -0
- package/src/plugins/builtin/short-interest/watch-headless.ts +1 -0
- package/src/plugins/builtin/short-volume/headless.ts +1 -0
- package/src/plugins/builtin/social-mentions/headless.ts +1 -0
- package/src/plugins/builtin/supply-chain/headless.ts +7 -1
- package/src/plugins/builtin/supply-chain/index.tsx +10 -1
- package/src/plugins/builtin/supply-chain/model.ts +27 -2
- package/src/plugins/builtin/supply-chain/pane.tsx +71 -32
- package/src/plugins/builtin/themes/headless.ts +3 -0
- package/src/plugins/builtin/thirteenf/headless.ts +110 -25
- package/src/plugins/builtin/thirteenf/report-notes.ts +149 -0
- package/src/plugins/builtin/ticker-detail/headless.ts +7 -1
- package/src/plugins/builtin/ticker-detail/index.tsx +1 -0
- package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -2
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +26 -26
- package/src/plugins/builtin/time-sales/headless.ts +5 -1
- package/src/plugins/builtin/treasury-auctions/headless.ts +1 -0
- package/src/plugins/builtin/volatility/headless.ts +6 -1
- package/src/plugins/builtin/world-indices/headless.ts +2 -0
- package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +157 -0
- package/src/plugins/builtin/world-venue-map/basemap.ts +214 -0
- package/src/plugins/builtin/world-venue-map/client.ts +45 -0
- package/src/plugins/builtin/world-venue-map/entity-detail.tsx +147 -0
- package/src/plugins/builtin/world-venue-map/entity-table.ts +59 -0
- package/src/plugins/builtin/world-venue-map/evidence.ts +77 -0
- package/src/plugins/builtin/world-venue-map/feed.ts +293 -0
- package/src/plugins/builtin/world-venue-map/geo-draw.tsx +248 -0
- package/src/plugins/builtin/world-venue-map/geo-series.ts +142 -0
- package/src/plugins/builtin/world-venue-map/geo-svg.tsx +447 -0
- package/src/plugins/builtin/world-venue-map/headless.ts +146 -0
- package/src/plugins/builtin/world-venue-map/ids.ts +1 -0
- package/src/plugins/builtin/world-venue-map/index.ts +67 -4
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +552 -0
- package/src/plugins/builtin/world-venue-map/layers.ts +348 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +220 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +381 -45
- package/src/plugins/builtin/world-venue-map/model.ts +7 -4
- package/src/plugins/builtin/world-venue-map/natural-earth/index.chunk.ts +199 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e000s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e015s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e030s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e045s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e060s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e075s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e090s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e105s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e120s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e135s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e150s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/e165s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w015s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w030s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w045s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w060s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w075s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w090s60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w105s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w120s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135n75.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w135s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w150s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w165s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n00.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180n60.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s15.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s30.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/tiles/w180s45.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/natural-earth/world-50m.chunk.ts +4 -0
- package/src/plugins/builtin/world-venue-map/pane.tsx +28 -43
- package/src/plugins/builtin/world-venue-map/use-geo.ts +55 -0
- package/src/plugins/builtin/world-venue-map/venue-header.tsx +32 -0
- package/src/plugins/builtin/yield-curve/headless.ts +16 -0
- package/src/plugins/loader.ts +13 -1
- package/src/plugins/registry/pane-settings.ts +1 -0
- package/src/renderers/dom/build-assets.ts +81 -3
- package/src/renderers/dom/data-table/row.tsx +2 -0
- package/src/renderers/dom/desktop/controls.tsx +7 -3
- package/src/renderers/dom/desktop/list-view.tsx +35 -10
- package/src/renderers/dom/desktop/popover.tsx +73 -14
- package/src/renderers/dom/host/input.tsx +14 -3
- package/src/renderers/dom/styles.css +6 -7
- package/src/renderers/electrobun/view/backend-rpc.ts +10 -4
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -0
- package/src/renderers/electrobun/view/rpc-timeout.ts +14 -3
- package/src/renderers/opentui/host.tsx +5 -0
- package/src/sources/gloomberb-cloud/index.ts +25 -4
- package/src/sources/gloomberb-cloud/normalizers.ts +27 -3
- package/src/sources/provider-router/cached-routes.ts +4 -1
- package/src/sources/provider-router/financials.ts +8 -7
- package/src/sources/provider-router/history.ts +15 -10
- package/src/sources/sec-registrant.ts +75 -0
- package/src/state/app/context/index.tsx +24 -8
- package/src/state/hooks/quote-streaming.ts +41 -26
- package/src/sync/core-contributors.ts +4 -0
- package/src/telemetry/crash-reports.ts +4 -1
- package/src/test-support/cli-context.ts +10 -2
- package/src/theme/heat-colors.ts +266 -84
- package/src/time-series/resolve.ts +1 -1
- package/src/time-series/use-chart-resolution.ts +1 -1
- package/src/types/context-menu.ts +6 -0
- package/src/types/data-provider.ts +2 -0
- package/src/types/financials.ts +3 -0
- package/src/types/headless.ts +79 -2
- package/src/types/plugin.ts +20 -1
- package/src/types/ticker.ts +15 -0
- package/src/ui/host.tsx +14 -0
- package/src/updater/index.ts +9 -11
- package/src/utils/async-deadline.ts +21 -0
- package/src/utils/cli-output.ts +5 -1
- package/src/utils/crypto-pair.ts +41 -0
- package/src/utils/currency-units.ts +14 -0
- package/src/utils/depositary-receipt.ts +26 -0
- package/src/utils/exchanges.ts +72 -0
- package/src/utils/fire-and-forget.ts +24 -0
- package/src/utils/format.ts +13 -0
- package/src/utils/price-earnings.ts +70 -3
- package/src/utils/price-history.ts +58 -13
- package/src/utils/rpc-timeout-error.ts +27 -0
- package/src/utils/sec.ts +13 -1
- package/src/utils/utc-time.ts +79 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { apiClient } from "../../../api-client";
|
|
2
2
|
import { getSharedMarketDataCoordinator, MarketDataCoordinator, resolveEntryValue } from "../../../market-data/coordinator";
|
|
3
|
+
import { fxLegForCurrency } from "../../../market-data/coordinator/fx-legs";
|
|
4
|
+
import { dailyFxCloses, FxHistoryError, type FxCloses } from "../../../market-data/fx-closes";
|
|
3
5
|
import type { InstrumentRef } from "../../../market-data/request-types";
|
|
4
6
|
import type { ManualChartResolution } from "../../../time-series/resolution";
|
|
5
7
|
import type { DataProvider } from "../../../types/data-provider";
|
|
@@ -8,6 +10,7 @@ import { abortable, abortError, settleWithin } from "../../../utils/async-deadli
|
|
|
8
10
|
import { errorMessage } from "../../../utils/errors";
|
|
9
11
|
import { isUsListingExchange } from "../../../utils/exchanges";
|
|
10
12
|
import { fetchEarningsHistory } from "../earnings/client";
|
|
13
|
+
import { epsFxCurrencies } from "./model";
|
|
11
14
|
import { reportDatesFrom, type ReportDate } from "./report-dates";
|
|
12
15
|
|
|
13
16
|
export interface PeBandInputs {
|
|
@@ -20,6 +23,10 @@ export interface PeBandInputs {
|
|
|
20
23
|
historyError: string | null;
|
|
21
24
|
/** Report dates for US listings; empty for the rest and when the earnings history is unavailable. */
|
|
22
25
|
reports: ReportDate[];
|
|
26
|
+
/** Daily FX closes for each currency a dollar-priced listing reports its EPS in. */
|
|
27
|
+
fx: Map<string, FxCloses>;
|
|
28
|
+
/** Why a currency's FX closes are missing; its EPS then stays unconverted. */
|
|
29
|
+
fxError: string | null;
|
|
23
30
|
fetchedAt: number;
|
|
24
31
|
}
|
|
25
32
|
|
|
@@ -31,7 +38,40 @@ const REPORT_LIMIT = 40;
|
|
|
31
38
|
/** Report dates only refine the figures' dates, so the pane does not wait longer than this for them. */
|
|
32
39
|
const REPORT_DEADLINE_MS = 8_000;
|
|
33
40
|
|
|
34
|
-
|
|
41
|
+
/** Daily FX closes back this far before the oldest statement, so its weeks find a close. */
|
|
42
|
+
const FX_LEAD_DAYS = 14;
|
|
43
|
+
/** About thirty years of daily closes; a longer reply is not a daily FX history. */
|
|
44
|
+
const FX_MAX_ROWS = 8_000;
|
|
45
|
+
|
|
46
|
+
type ReportClient = Pick<typeof apiClient, "getCloudEarningsHistory" | "getCloudHistory">;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Completed daily closes of each currency's dollar pair, from just before the
|
|
50
|
+
* oldest statement in that currency. Never rejects but on cancel: a missing
|
|
51
|
+
* or stale history is said in `error`, and that currency's EPS stays unconverted.
|
|
52
|
+
*/
|
|
53
|
+
async function loadFxCloses(financials: TickerFinancials, client: ReportClient, signal: AbortSignal | undefined, now: Date):
|
|
54
|
+
Promise<{ fx: Map<string, FxCloses>; error: string | null }> {
|
|
55
|
+
const fx = new Map<string, FxCloses>();
|
|
56
|
+
const errors: string[] = [];
|
|
57
|
+
const rows = [...financials.annualStatements, ...financials.quarterlyStatements];
|
|
58
|
+
await Promise.all(epsFxCurrencies(financials).map(async (currency) => {
|
|
59
|
+
const leg = fxLegForCurrency(currency)!;
|
|
60
|
+
const oldest = rows.filter((row) => row.currency?.trim().toUpperCase() === currency && Number.isFinite(Date.parse(row.date)))
|
|
61
|
+
.reduce((min, row) => Math.min(min, Date.parse(row.date)), now.getTime());
|
|
62
|
+
try {
|
|
63
|
+
const response = await client.getCloudHistory(leg.instrument.symbol, leg.instrument.exchange ?? "", {
|
|
64
|
+
interval: "1day", startDate: new Date(oldest - FX_LEAD_DAYS * 86_400_000).toISOString().slice(0, 10),
|
|
65
|
+
endDate: now.toISOString().slice(0, 10), outputsize: FX_MAX_ROWS,
|
|
66
|
+
}, signal ? { signal } : undefined);
|
|
67
|
+
fx.set(currency, dailyFxCloses(response, leg, now, FX_MAX_ROWS));
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (signal?.aborted) throw abortError(CANCELLED);
|
|
70
|
+
errors.push(`daily ${currency} FX closes are ${error instanceof FxHistoryError && error.problem === "stale" ? "stale" : "unavailable"}`);
|
|
71
|
+
}
|
|
72
|
+
}));
|
|
73
|
+
return { fx, error: errors.length ? errors.join("; ") : null };
|
|
74
|
+
}
|
|
35
75
|
|
|
36
76
|
/** Never rejects: a listing without reports, an outage or a refused session leaves the figures as they are. */
|
|
37
77
|
function loadReportDates(symbol: string, client: ReportClient): Promise<ReportDate[]> {
|
|
@@ -73,12 +113,17 @@ export async function loadPeBandInputs(
|
|
|
73
113
|
const entry = chart.status === "fulfilled" ? chart.value : null;
|
|
74
114
|
const history = entry ? resolveEntryValue(entry) ?? [] : [];
|
|
75
115
|
const quote = value?.quote;
|
|
76
|
-
const reports = await abortable(
|
|
77
|
-
|
|
116
|
+
const [reports, fx] = await abortable(Promise.all([
|
|
117
|
+
early ?? (isUsListingExchange(quote?.listingExchangeName ?? quote?.exchangeName ?? quote?.fullExchangeName)
|
|
118
|
+
? loadReportDates(instrument.symbol, cloud) : Promise.resolve([])),
|
|
119
|
+
value ? loadFxCloses(value, cloud, request.signal, new Date(now)) : Promise.resolve({ fx: new Map<string, FxCloses>(), error: null }),
|
|
120
|
+
]), request.signal, CANCELLED);
|
|
78
121
|
return {
|
|
79
122
|
financials: value,
|
|
80
123
|
history,
|
|
81
124
|
reports,
|
|
125
|
+
fx: fx.fx,
|
|
126
|
+
fxError: fx.error,
|
|
82
127
|
stale: !!value?.fundamentals?.stale || !!entry?.error || (entry?.staleAt != null && entry.staleAt <= now),
|
|
83
128
|
error: financials.status === "rejected" ? errorMessage(financials.reason) : null,
|
|
84
129
|
historyError: chart.status === "rejected" ? errorMessage(chart.reason)
|
|
@@ -2,7 +2,8 @@ import { MarketDataCoordinator } from "../../../market-data/coordinator";
|
|
|
2
2
|
import type { HeadlessPaneDefinition } from "../../../types/plugin";
|
|
3
3
|
import { resolveHeadlessInstrument } from "../shared/headless-market-data";
|
|
4
4
|
import { loadPeBandInputs } from "./client";
|
|
5
|
-
import { formatPerShare, projectPeBand } from "./model";
|
|
5
|
+
import { formatPerShare, fxPairQuote, projectPeBand } from "./model";
|
|
6
|
+
import { DAILY_CLOSES } from "../shared/report-freshness";
|
|
6
7
|
|
|
7
8
|
const multiple = (value: unknown) => typeof value === "number" ? `${value.toFixed(1)}x` : "--";
|
|
8
9
|
const amount = (value: unknown) => typeof value === "number" ? formatPerShare(value) : "--";
|
|
@@ -10,8 +11,11 @@ const percent = (value: unknown) => typeof value === "number" ? `${(value * 100)
|
|
|
10
11
|
|
|
11
12
|
export const peBandHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
12
13
|
shape: "bundle", argument: { kind: "ticker", description: "Ticker" },
|
|
14
|
+
freshness: { ...DAILY_CLOSES, basis: "daily closes and reported EPS" },
|
|
13
15
|
describe: (args) => `PEB ${args.symbols[0] ?? ""}`,
|
|
14
|
-
discovery: { screenshotReadiness: "live-dom", limitations: [
|
|
16
|
+
discovery: { screenshotReadiness: "live-dom", limitations: [
|
|
17
|
+
"Weekly closes. EPS reported in another currency than a dollar price is converted at daily FX closes; other currency pairs are not computed.",
|
|
18
|
+
] },
|
|
15
19
|
options: [
|
|
16
20
|
{ key: "lookbackYears", type: "integer", minimum: 0, maximum: 30, defaultValue: 10, description: "Years of history; 0 for all on record" },
|
|
17
21
|
],
|
|
@@ -20,8 +24,16 @@ export const peBandHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
20
24
|
const inputs = await loadPeBandInputs({ instrument, signal: ctx.signal }, ctx.marketData, new MarketDataCoordinator(ctx.marketData), ctx.apiClient);
|
|
21
25
|
ctx.signal.throwIfAborted();
|
|
22
26
|
const lookbackYears = Number(args.options.lookbackYears ?? 10);
|
|
23
|
-
const model = projectPeBand(inputs.financials, inputs.history, { symbol: instrument.symbol, lookbackYears, reports: inputs.reports
|
|
27
|
+
const model = projectPeBand(inputs.financials, inputs.history, { symbol: instrument.symbol, lookbackYears, reports: inputs.reports,
|
|
28
|
+
fx: inputs.fx, fxError: inputs.fxError });
|
|
24
29
|
const current = model.current;
|
|
30
|
+
const conversion = model.conversion;
|
|
31
|
+
const pairQuote = (rate: number) => {
|
|
32
|
+
const quote = fxPairQuote(conversion!.currency, rate);
|
|
33
|
+
return `${quote.pair} ${quote.value.toPrecision(5)}`;
|
|
34
|
+
};
|
|
35
|
+
const reported = current?.step?.eps != null && conversion && current.step.currency === conversion.currency
|
|
36
|
+
? `, from ${conversion.currency} ${amount(current.step.eps)} at ${pairQuote(conversion.latest.rate)} (${conversion.latest.date} close)` : "";
|
|
25
37
|
return {
|
|
26
38
|
sections: [
|
|
27
39
|
{ title: "P/E band", columns: [{ key: "label", header: "" }, { key: "value", header: "Value" }], rows: [
|
|
@@ -30,20 +42,26 @@ export const peBandHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
30
42
|
{ label: "Ranked over", value: model.sample ? `${model.sample.weeks} weeks since ${model.sample.start.toISOString().slice(0, 10)}` : "--" },
|
|
31
43
|
{ label: "P/E low, median, high", value: model.range ? `${multiple(model.range.min)}, ${multiple(model.range.median)}, ${multiple(model.range.max)}` : "--" },
|
|
32
44
|
{ label: "Price", value: `${amount(current?.price)} ${model.currency ?? ""}`.trim() },
|
|
33
|
-
{ label: "Trailing EPS", value: current?.step
|
|
45
|
+
{ label: "Trailing EPS", value: current?.step
|
|
46
|
+
? `${amount(current.eps)} (${current.step.basis === "annual" ? "FY" : "TTM"} ${current.step.periodEnd})${reported}` : "--" },
|
|
34
47
|
] },
|
|
35
48
|
{ title: "Price at each multiple", columns: [{ key: "multiple", header: "Multiple", format: multiple }, { key: "price", header: "Price", format: amount }],
|
|
36
49
|
rows: model.multiples.map((value) => ({ multiple: value, price: current?.eps != null && current.eps > 0 ? value * current.eps : null })) },
|
|
37
50
|
{ title: "Trailing EPS", columns: [{ key: "periodEnd", header: "Period end" }, { key: "basis", header: "Basis" },
|
|
38
|
-
{ key: "knownAt", header: "Known" }, { key: "eps", header: "EPS
|
|
39
|
-
{ key: "
|
|
51
|
+
{ key: "knownAt", header: "Known" }, { key: "eps", header: conversion ? "EPS reported" : "EPS", format: amount },
|
|
52
|
+
...(conversion ? [{ key: "currency", header: "Currency" }, { key: "fx", header: "FX close" }] : []),
|
|
53
|
+
{ key: "yoy", header: "YoY", format: percent }, { key: "price", header: "Price", format: amount }, { key: "pe", header: "P/E", format: multiple }],
|
|
40
54
|
rows: model.rows.map((row) => ({ periodEnd: row.periodEnd, basis: row.basis === "annual" ? "FY" : "TTM",
|
|
41
|
-
knownAt: row.dated ? row.knownAt.toISOString().slice(0, 10) : "period end", eps: row.eps,
|
|
55
|
+
knownAt: row.dated ? row.knownAt.toISOString().slice(0, 10) : "period end", eps: row.eps,
|
|
56
|
+
...(conversion ? { currency: row.currency, fx: row.fx ? `${pairQuote(row.fx.rate)} (${row.fx.date})` : row.currency === model.currency ? "" : "--" } : {}),
|
|
57
|
+
yoy: row.yoy, price: row.price, pe: row.pe })) },
|
|
42
58
|
],
|
|
43
59
|
complete: !inputs.stale && !inputs.error && !inputs.historyError && !model.error,
|
|
44
60
|
unavailableSymbols: current?.pe != null ? [] : [instrument.symbol],
|
|
45
61
|
errors: [inputs.error, inputs.historyError, model.error, model.notice].filter((value): value is string => !!value),
|
|
46
62
|
metadata: { currency: model.currency, lookbackYears,
|
|
63
|
+
epsConversion: conversion ? { from: conversion.currency, to: model.currency, usdPerUnit: conversion.latest.rate, latestClose: conversion.latest.date,
|
|
64
|
+
basis: "daily FX close on or before each date: a week's bar date for the band, the publication date for each table row, the latest completed close for the current P/E" } : null,
|
|
47
65
|
sampleStart: model.sample?.start.toISOString().slice(0, 10) ?? null, sampleWeeks: model.sample?.weeks ?? 0, multiples: model.multiples, undatedEpsPeriods: model.undated, unavailableTtmSums: model.unavailable,
|
|
48
66
|
stale: inputs.stale, fetchedAt: inputs.fetchedAt, methodology: "docs/research-data.md#pe-band" },
|
|
49
67
|
};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { fxLegForCurrency } from "../../../market-data/coordinator/fx-legs";
|
|
2
|
+
import { fxCloseOnOrBefore, type FxCloses } from "../../../market-data/fx-closes";
|
|
1
3
|
import { extractFundamentalSeries } from "../../../time-series/fundamentals";
|
|
2
4
|
import type { SecuritySeriesSource, TimeSeriesPoint } from "../../../time-series/types";
|
|
3
5
|
import { createValuationCurrencyContext } from "../../../time-series/valuation-currency";
|
|
4
6
|
import type { FinancialStatement, PricePoint, TickerFinancials } from "../../../types/financials";
|
|
7
|
+
import { resolveCurrencyUnit } from "../../../utils/currency-units";
|
|
5
8
|
import { areNearbyFinancialPeriodEnds } from "../../../utils/financial-statements";
|
|
6
9
|
import { datedByReport, type ReportDate } from "./report-dates";
|
|
7
10
|
|
|
@@ -43,8 +46,24 @@ export interface PeBandRow extends EpsStep {
|
|
|
43
46
|
/** The close of the week the figure became known, in price units. */
|
|
44
47
|
price: number | null;
|
|
45
48
|
pe: number | null;
|
|
46
|
-
/** Change from the figure a year earlier, when both are positive. */
|
|
49
|
+
/** Change from the figure a year earlier in the same currency, when both are positive. */
|
|
47
50
|
yoy: number | null;
|
|
51
|
+
/** The daily FX close the figure was converted at, when it is in another currency than the price. */
|
|
52
|
+
fx: FxClose | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** One completed daily FX close: USD per unit of the statement currency. */
|
|
56
|
+
interface FxClose {
|
|
57
|
+
date: string;
|
|
58
|
+
rate: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** EPS reported in another currency than a dollar price, converted at daily FX closes. */
|
|
62
|
+
interface EpsConversion {
|
|
63
|
+
/** The statement currency of today's EPS. */
|
|
64
|
+
currency: string;
|
|
65
|
+
/** The latest completed close, which converts today's EPS. */
|
|
66
|
+
latest: FxClose;
|
|
48
67
|
}
|
|
49
68
|
|
|
50
69
|
export interface PeBandModel {
|
|
@@ -69,6 +88,8 @@ export interface PeBandModel {
|
|
|
69
88
|
undated: number;
|
|
70
89
|
/** Trailing sums in the window that are unavailable (a quarter's EPS is withheld or not reported). */
|
|
71
90
|
unavailable: number;
|
|
91
|
+
/** Set when today's EPS is in another currency than the dollar price. */
|
|
92
|
+
conversion: EpsConversion | null;
|
|
72
93
|
/** Blocks the pane: nothing can be priced. */
|
|
73
94
|
error: string | null;
|
|
74
95
|
/** The pane still draws, but without bands. */
|
|
@@ -131,6 +152,37 @@ export function perShareDigits(value: number): number {
|
|
|
131
152
|
|
|
132
153
|
export const formatPerShare = (value: number) => value.toFixed(perShareDigits(value));
|
|
133
154
|
|
|
155
|
+
/**
|
|
156
|
+
* A rate in the pair's market convention: "USD/TWD 31.99" (Taiwan dollars per
|
|
157
|
+
* dollar) or "EUR/USD 1.17" (dollars per euro), from USD per unit.
|
|
158
|
+
*/
|
|
159
|
+
export function fxPairQuote(currency: string, usdPerUnit: number): { pair: string; value: number } {
|
|
160
|
+
const perDollar = fxLegForCurrency(currency)?.invert ?? true;
|
|
161
|
+
return perDollar ? { pair: `USD/${currency}`, value: 1 / usdPerUnit } : { pair: `${currency}/USD`, value: usdPerUnit };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** The statement unit of an EPS currency when it is a whole currency other than USD with a daily pair, else null. */
|
|
165
|
+
function fxCurrency(code: string | null | undefined): string | null {
|
|
166
|
+
const unit = resolveCurrencyUnit(code);
|
|
167
|
+
return unit.divisor === 1 && unit.currency !== "USD" && fxLegForCurrency(unit.currency) ? unit.currency : null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const isDollarPrice = (currency: string | null | undefined) => {
|
|
171
|
+
const unit = resolveCurrencyUnit(currency);
|
|
172
|
+
return unit.currency === "USD" && unit.divisor === 1;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The currencies whose daily FX closes a dollar-priced listing needs: those
|
|
177
|
+
* its EPS is reported in, other than USD. Rows without a declared currency
|
|
178
|
+
* are never converted.
|
|
179
|
+
*/
|
|
180
|
+
export function epsFxCurrencies(financials: TickerFinancials): string[] {
|
|
181
|
+
if (!isDollarPrice(financials.quote?.currency)) return [];
|
|
182
|
+
const rows = [...financials.annualStatements, ...financials.quarterlyStatements].filter((row) => typeof row.eps === "number");
|
|
183
|
+
return [...new Set(rows.flatMap((row) => fxCurrency(row.currency?.trim()) ?? []))];
|
|
184
|
+
}
|
|
185
|
+
|
|
134
186
|
function quantile(sorted: readonly number[], q: number): number {
|
|
135
187
|
const position = (sorted.length - 1) * q;
|
|
136
188
|
const low = Math.floor(position), high = Math.ceil(position);
|
|
@@ -197,30 +249,47 @@ export function stepAt(steps: readonly EpsStep[], time: number): EpsStep | null
|
|
|
197
249
|
export function projectPeBand(
|
|
198
250
|
financials: TickerFinancials | null,
|
|
199
251
|
history: readonly PricePoint[],
|
|
200
|
-
options: {
|
|
252
|
+
options: {
|
|
253
|
+
symbol: string; lookbackYears: number; now?: number; reports?: readonly ReportDate[];
|
|
254
|
+
/** Daily FX closes by statement currency, for a dollar price against EPS in another currency. */
|
|
255
|
+
fx?: ReadonlyMap<string, FxCloses>;
|
|
256
|
+
/** Why a needed FX history is missing, said when it leaves today's EPS unconverted. */
|
|
257
|
+
fxError?: string | null;
|
|
258
|
+
},
|
|
201
259
|
): PeBandModel {
|
|
202
260
|
const currency = financials?.quote?.currency ?? null;
|
|
203
261
|
const empty: PeBandModel = { symbol: options.symbol, currency, weeks: [], rows: [], multiples: [], current: null, range: null, sample: null, bandCeiling: null,
|
|
204
|
-
undated: 0, unavailable: 0, error: null, notice: null };
|
|
262
|
+
undated: 0, unavailable: 0, conversion: null, error: null, notice: null };
|
|
205
263
|
if (!financials) return { ...empty, error: "Fundamentals unavailable." };
|
|
206
264
|
const allSteps = trailingEpsSteps(financials, options.reports);
|
|
207
265
|
const latest = allSteps.findLast((step) => step.eps != null);
|
|
208
266
|
if (!latest) return { ...empty, error: "No trailing EPS on record." };
|
|
209
267
|
const units = createValuationCurrencyContext(financials);
|
|
210
268
|
// Price units per statement unit: 1 for the same currency, 100 for a pence price against pound statements.
|
|
211
|
-
const
|
|
269
|
+
const unitScale = (step: EpsStep) => {
|
|
212
270
|
const factor = units.priceInStatementUnits({ date: step.periodEnd, currency: step.currency ?? undefined }, 1);
|
|
213
271
|
return factor ? 1 / factor : null;
|
|
214
272
|
};
|
|
215
|
-
// ADRs
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
273
|
+
// ADRs report in one currency and trade in dollars: their EPS converts at a daily FX close.
|
|
274
|
+
const fxFor = (step: EpsStep) => {
|
|
275
|
+
const code = isDollarPrice(currency) ? fxCurrency(step.currency) : null;
|
|
276
|
+
return code ? options.fx?.get(code) ?? null : null;
|
|
277
|
+
};
|
|
278
|
+
if (unitScale(latest) == null && !fxFor(latest)) {
|
|
279
|
+
const fxCode = isDollarPrice(currency) ? fxCurrency(latest.currency) : null;
|
|
280
|
+
return { ...empty, error: !currency || !latest.currency ? "The price or EPS currency is unknown."
|
|
281
|
+
: `EPS is reported in ${latest.currency} and the price is in ${currency}${fxCode && options.fxError ? `; ${options.fxError}` : ""}.` };
|
|
219
282
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
283
|
+
/** The EPS in price units on `date` (YYYY-MM-DD), with the FX close used when it was converted. */
|
|
284
|
+
const priced = (step: EpsStep | null, date: string): { eps: number; fx: FxClose | null } | null => {
|
|
285
|
+
if (step?.eps == null) return null;
|
|
286
|
+
const factor = unitScale(step);
|
|
287
|
+
if (factor != null) return { eps: step.eps * factor, fx: null };
|
|
288
|
+
const history = fxFor(step);
|
|
289
|
+
const close = history ? fxCloseOnOrBefore(history, date) : null;
|
|
290
|
+
return close ? { eps: step.eps * close.rate, fx: close } : null;
|
|
223
291
|
};
|
|
292
|
+
const day = (time: Date | number) => new Date(time).toISOString().slice(0, 10);
|
|
224
293
|
|
|
225
294
|
const now = options.now ?? Date.now();
|
|
226
295
|
const firstKnown = allSteps.find((step) => step.eps != null)!.knownAt.getTime();
|
|
@@ -230,7 +299,9 @@ export function projectPeBand(
|
|
|
230
299
|
.filter((point) => Number.isFinite(point.date.getTime()) && Number.isFinite(point.close) && point.close > 0)
|
|
231
300
|
.sort((a, b) => a.date.getTime() - b.date.getTime());
|
|
232
301
|
const weeks = sorted.filter((point) => point.date.getTime() >= start).map((point): PeBandWeek => {
|
|
233
|
-
|
|
302
|
+
// Converted at the FX close of the bar's date, the same date that picks the EPS in force, so each week's multiple
|
|
303
|
+
// moves with the price and the currency together rather than with the rate of an old publication date.
|
|
304
|
+
const eps = priced(stepAt(allSteps, point.date.getTime()), day(point.date))?.eps ?? null;
|
|
234
305
|
const positive = eps != null && eps > 0 ? eps : null;
|
|
235
306
|
return { date: point.date, close: point.close, eps: positive, pe: positive ? point.close / positive : null };
|
|
236
307
|
});
|
|
@@ -239,10 +310,12 @@ export function projectPeBand(
|
|
|
239
310
|
const rows = steps.filter((step) => step.eps != null).map((step): PeBandRow => {
|
|
240
311
|
const time = step.knownAt.getTime();
|
|
241
312
|
const price = sorted.findLast((point) => point.date.getTime() <= time)?.close ?? null;
|
|
242
|
-
|
|
243
|
-
const
|
|
313
|
+
// Converted at the FX close of the day it became known.
|
|
314
|
+
const converted = priced(step, day(step.knownAt));
|
|
315
|
+
const eps = converted?.eps ?? null;
|
|
316
|
+
const prior = allSteps.find((other) => other.eps != null && other.currency === step.currency
|
|
244
317
|
&& Math.abs(Date.parse(step.periodEnd) - Date.parse(other.periodEnd) - 365 * DAY_MS) <= 20 * DAY_MS);
|
|
245
|
-
return { ...step, price, pe: price != null && eps != null && eps > 0 ? price / eps : null,
|
|
318
|
+
return { ...step, price, pe: price != null && eps != null && eps > 0 ? price / eps : null, fx: converted?.fx ?? null,
|
|
246
319
|
yoy: step.eps != null && prior?.eps != null && step.eps > 0 && prior.eps > 0 ? step.eps / prior.eps - 1 : null };
|
|
247
320
|
}).reverse();
|
|
248
321
|
|
|
@@ -252,13 +325,16 @@ export function projectPeBand(
|
|
|
252
325
|
const quotePrice = financials.quote?.price;
|
|
253
326
|
const price = quotePrice != null && Number.isFinite(quotePrice) && quotePrice > 0 ? quotePrice : sorted.at(-1)?.close ?? null;
|
|
254
327
|
const currentStep = stepAt(allSteps, now);
|
|
255
|
-
|
|
328
|
+
// Today's EPS at the latest completed FX close.
|
|
329
|
+
const currentEps = priced(currentStep, day(now))?.eps ?? null;
|
|
330
|
+
const latestFx = unitScale(latest) == null ? fxFor(latest) : null;
|
|
256
331
|
const currentPe = price != null && currentEps != null && currentEps > 0 ? price / currentEps : null;
|
|
257
332
|
const multiples = chooseMultiples(pes, currentPe);
|
|
258
333
|
const above = currentPe == null ? undefined : multiples.find((multiple) => multiple >= currentPe);
|
|
259
334
|
const highestClose = weeks.reduce((max, week) => Math.max(max, week.close), 0);
|
|
260
335
|
return {
|
|
261
336
|
...empty, weeks, rows, multiples,
|
|
337
|
+
conversion: latestFx ? { currency: latestFx.currency, latest: fxCloseOnOrBefore(latestFx, day(now))! } : null,
|
|
262
338
|
sample: ranked.length ? { start: ranked[0]!.date, weeks: ranked.length } : null,
|
|
263
339
|
bandCeiling: highestClose > 0 ? Math.max(highestClose * 1.5, above != null && currentEps != null ? above * currentEps : 0) : null,
|
|
264
340
|
current: price == null ? null : { price, eps: currentEps, pe: currentPe, step: currentStep,
|
|
@@ -14,7 +14,7 @@ import { Box } from "../../../ui";
|
|
|
14
14
|
import { formatCurrency, formatPercentileRank } from "../../../utils/format";
|
|
15
15
|
import { useAssetData } from "../../runtime";
|
|
16
16
|
import { loadPeBandInputs } from "./client";
|
|
17
|
-
import { formatPerShare, perShareDigits, projectPeBand, type PeBandModel, type PeBandRow } from "./model";
|
|
17
|
+
import { formatPerShare, fxPairQuote, perShareDigits, projectPeBand, type PeBandModel, type PeBandRow } from "./model";
|
|
18
18
|
|
|
19
19
|
export const LOOKBACK_OPTIONS = [{ value: "5", label: "5Y" }, { value: "10", label: "10Y" }, { value: "0", label: "Max" }];
|
|
20
20
|
|
|
@@ -28,14 +28,18 @@ const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "
|
|
|
28
28
|
const periodLabel = (row: Pick<PeBandRow, "basis" | "periodEnd">) =>
|
|
29
29
|
`${row.basis === "annual" ? "FY" : "TTM"} ${MONTHS[Number(row.periodEnd.slice(5, 7)) - 1]} ${row.periodEnd.slice(0, 4)}`;
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
/** With EPS converted from another currency, the table keeps the reported figure and adds the FX close it was converted at. */
|
|
32
|
+
function tableColumns(conversion: PeBandModel["conversion"]): DataTableColumn[] {
|
|
33
|
+
return [
|
|
34
|
+
{ id: "period", label: "Period", width: 13, align: "left" },
|
|
35
|
+
{ id: "known", label: "Known", width: 11, align: "left" },
|
|
36
|
+
{ id: "eps", label: conversion ? `EPS ${conversion.currency}` : "EPS", width: 9, align: "right" },
|
|
37
|
+
...(conversion ? [{ id: "fx", label: fxPairQuote(conversion.currency, conversion.latest.rate).pair, width: 9, align: "right" as const }] : []),
|
|
38
|
+
{ id: "yoy", label: "YoY", width: 8, align: "right" },
|
|
39
|
+
{ id: "price", label: "Price", width: 10, align: "right" },
|
|
40
|
+
{ id: "pe", label: "P/E", width: 7, align: "right" },
|
|
41
|
+
];
|
|
42
|
+
}
|
|
39
43
|
|
|
40
44
|
const rowKey = (row: PeBandRow) => `${row.basis}:${row.periodEnd}`;
|
|
41
45
|
|
|
@@ -49,6 +53,10 @@ function renderCell(row: PeBandRow, column: DataTableColumn, colors: ReturnType<
|
|
|
49
53
|
color: row.eps != null && row.eps <= 0 ? colors.negative : undefined };
|
|
50
54
|
case "yoy": return { text: pct(row.yoy), value: row.yoy == null ? null : row.yoy * 100,
|
|
51
55
|
color: row.yoy == null || Math.abs(row.yoy) < 0.0005 ? undefined : row.yoy > 0 ? colors.positive : colors.negative };
|
|
56
|
+
case "fx": {
|
|
57
|
+
const quote = row.fx && row.currency ? fxPairQuote(row.currency, row.fx.rate).value : null;
|
|
58
|
+
return { text: quote == null ? "--" : quote.toPrecision(5), value: quote };
|
|
59
|
+
}
|
|
52
60
|
case "price": return { text: row.price == null ? "--" : formatPerShare(row.price), value: row.price };
|
|
53
61
|
default: return { text: multiple(row.pe), value: row.pe };
|
|
54
62
|
}
|
|
@@ -95,9 +103,11 @@ export function PeBandPane({ width, height, focused }: PaneProps) {
|
|
|
95
103
|
const lookbackYears = Number(lookback);
|
|
96
104
|
const model = useMemo(() => inputs.data
|
|
97
105
|
? projectPeBand(inputs.data.financials, inputs.data.history, { symbol: symbol ?? "", lookbackYears: Number.isFinite(lookbackYears) ? lookbackYears : 10,
|
|
98
|
-
reports: inputs.data.reports })
|
|
106
|
+
reports: inputs.data.reports, fx: inputs.data.fx, fxError: inputs.data.fxError })
|
|
99
107
|
: null, [inputs.data, lookbackYears, symbol]);
|
|
100
108
|
const rows = model?.rows ?? [];
|
|
109
|
+
const conversion = model?.conversion ?? null;
|
|
110
|
+
const columns = useMemo(() => tableColumns(conversion), [conversion?.currency, conversion?.latest.rate]);
|
|
101
111
|
const series = useMemo(() => model && !model.error ? chartSeries(model, colors) : [], [model, colors]);
|
|
102
112
|
const currency = model?.currency ?? undefined;
|
|
103
113
|
const firstWeek = model?.weeks[0]?.date;
|
|
@@ -117,7 +127,8 @@ export function PeBandPane({ width, height, focused }: PaneProps) {
|
|
|
117
127
|
const unavailableNotice = model?.unavailable
|
|
118
128
|
? `${model.unavailable} trailing EPS sum${model.unavailable === 1 ? " is" : "s are"} unavailable because a quarter's EPS is not reported; the previous figure stays in force.` : null;
|
|
119
129
|
usePaneNoticeFooter({ registrationId: "pe-band-notices", focused, notices: [...new Set([identityError, inputs.error, inputs.data?.historyError,
|
|
120
|
-
model?.notice,
|
|
130
|
+
model?.notice, model && !model.error && inputs.data?.fxError ? `EPS in another currency is not converted: ${inputs.data.fxError}.` : null,
|
|
131
|
+
unavailableNotice, undatedNotice].filter((value): value is string => !!value))] });
|
|
121
132
|
usePaneFooter("pe-band", () => ({ info: [
|
|
122
133
|
...(inputs.loading ? [{ id: "loading", parts: [{ text: "loading statements", tone: "muted" as const }] }] : []),
|
|
123
134
|
...(inputs.data?.stale ? [{ id: "stale", parts: [{ text: "stale", tone: "warning" as const }] }] : []),
|
|
@@ -128,15 +139,19 @@ export function PeBandPane({ width, height, focused }: PaneProps) {
|
|
|
128
139
|
if (!model?.current || model.error) return [];
|
|
129
140
|
const { current, range } = model;
|
|
130
141
|
const step = current.step;
|
|
142
|
+
// Converted EPS says what it was converted from, and at which close.
|
|
143
|
+
const pair = conversion ? fxPairQuote(conversion.currency, conversion.latest.rate) : null;
|
|
144
|
+
const converted = step?.eps != null && conversion && pair && step.currency === conversion.currency
|
|
145
|
+
? `, from ${conversion.currency} ${formatPerShare(step.eps)} at ${pair.pair} ${pair.value.toPrecision(5)}` : "";
|
|
131
146
|
return [
|
|
132
147
|
{ id: "pe", label: "P/E", value: multiple(current.pe),
|
|
133
148
|
detail: current.pe != null ? formatPercentileRank(current.percentile, windowLabel) : current.eps != null ? "EPS not positive" : "EPS unavailable" },
|
|
134
149
|
{ id: "median", label: "Median P/E", value: multiple(range?.median), detail: range ? `${multiple(range.min)} to ${multiple(range.max)}` : undefined },
|
|
135
150
|
{ id: "eps", label: "EPS", value: current.eps == null ? "--" : formatCurrency(current.eps, currency, perShareDigits(current.eps)),
|
|
136
|
-
detail: step ? periodLabel(step) : undefined },
|
|
151
|
+
detail: step ? `${periodLabel(step)}${converted}` : undefined },
|
|
137
152
|
{ id: "price", label: "Price", value: formatCurrency(current.price, currency, perShareDigits(current.price)) },
|
|
138
153
|
];
|
|
139
|
-
}, [model, windowLabel, currency]);
|
|
154
|
+
}, [model, windowLabel, currency, conversion]);
|
|
140
155
|
|
|
141
156
|
const formatPrice = useCallback((value: number) => formatCurrency(value, currency, perShareDigits(value)), [currency]);
|
|
142
157
|
const formatAxis = useMemo(() => spanAxisFormatter((value, digits) => formatCurrency(value, currency, Math.max(2, digits))), [currency]);
|
|
@@ -145,14 +160,16 @@ export function PeBandPane({ width, height, focused }: PaneProps) {
|
|
|
145
160
|
if (!symbol) return <EmptyState title="Choose a ticker." />;
|
|
146
161
|
return <Box width={width} height={height} flexDirection="column" overflow="hidden">
|
|
147
162
|
<PaneStatusBody subject="P/E band" loading={inputs.loading && !model} error={!model ? inputs.error ?? identityError ?? null : model.error} empty={false}>
|
|
148
|
-
{model && !model.error ? <DataTableView<PeBandRow> focused={focused} columns={
|
|
163
|
+
{model && !model.error ? <DataTableView<PeBandRow> focused={focused} columns={columns} items={rows} rootWidth={width} rootHeight={height}
|
|
149
164
|
getItemKey={rowKey} emptyStateTitle="No trailing EPS in this window." sortColumnId={null} sortDirection="desc" resetScrollKey={symbol}
|
|
150
165
|
selection={{ kind: "id", selectedId: selected, getId: rowKey, onChange: (id) => setSelectedId(id) }}
|
|
151
166
|
selectedTextOverridesCellColor
|
|
152
167
|
getExportMetadata={() => [["symbol", model.symbol], ["currency", model.currency ?? ""],
|
|
153
|
-
["units",
|
|
168
|
+
["units", conversion
|
|
169
|
+
? `EPS as reported in ${conversion.currency}; FX close of the day it became known; price in ${model.currency ?? ""}; P/E at that close and that week's close`
|
|
170
|
+
: "EPS and price in the listing currency; P/E at the close of the week the EPS became known"]]}
|
|
154
171
|
renderCell={(row, column) => renderCell(row, column, colors)}
|
|
155
|
-
rootBefore={<ChartTableHeader width={width} height={height} tableRows={rows.length} tableColumns={
|
|
172
|
+
rootBefore={<ChartTableHeader width={width} height={height} tableRows={rows.length} tableColumns={columns} query={query} figures={figures}
|
|
156
173
|
chart={model.weeks.length >= 2 ? { series, formatValue: formatPrice, formatAxisValue: formatAxis, remoteKind: "pe-band", ...link } : null} />} />
|
|
157
174
|
: null}
|
|
158
175
|
</PaneStatusBody>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { apiClient } from "../../../api-client";
|
|
2
|
-
import type { PerpBoardPayload, PerpBoardRow, PerpEquityListing, PerpHistoryPayload, PerpHistoryQuery,
|
|
2
|
+
import type { PerpBoardPayload, PerpBoardQuery, PerpBoardRow, PerpEquityListing, PerpHistoryPayload, PerpHistoryQuery, PerpLongShortPoint, PerpLongShortRatio, PerpMarketPayload,
|
|
3
|
+
PerpRankingsPayload } from "../../../api-client/perps";
|
|
3
4
|
import { createPluginCache } from "../../../data/plugin-cache";
|
|
4
5
|
import { cachedCloudResource, loadCloudResource } from "../shared/cloud-resource";
|
|
5
6
|
|
|
@@ -9,6 +10,12 @@ export const perpsHistoryCache = createPluginCache<PerpHistoryPayload>({ kind: "
|
|
|
9
10
|
policy: { staleMs: 60_000, expireMs: 86_400_000 } });
|
|
10
11
|
const finiteOrNull = (n: unknown) => n === null || typeof n === "number" && Number.isFinite(n);
|
|
11
12
|
const date = (s: unknown) => typeof s === "string" && Number.isFinite(Date.parse(s));
|
|
13
|
+
const share = (n: unknown) => typeof n === "number" && n >= 0 && n <= 1;
|
|
14
|
+
/** A long/short reading: shares within 0..1, a finite ratio or none. Older servers send no reading at all. */
|
|
15
|
+
const validShares = (value: Pick<PerpLongShortPoint, "longShare" | "shortShare" | "ratio" | "derived" | "definition" | "observedAt">) =>
|
|
16
|
+
share(value.longShare) && share(value.shortShare) && finiteOrNull(value.ratio) && typeof value.derived === "boolean" && typeof value.definition === "string" && date(value.observedAt);
|
|
17
|
+
const validLongShort = (value: PerpLongShortRatio | null | undefined) => value == null
|
|
18
|
+
|| typeof value === "object" && validShares(value) && typeof value.venue === "string" && typeof value.definitionText === "string" && date(value.bucketAt) && typeof value.stale === "boolean";
|
|
12
19
|
function validRow(row: PerpBoardRow) {
|
|
13
20
|
return !!row && typeof row.marketId === "string" && typeof row.symbol === "string" && typeof row.displayName === "string"
|
|
14
21
|
&& typeof row.baseAsset === "string" && typeof row.quoteCurrency === "string" && typeof row.marginCurrency === "string"
|
|
@@ -16,7 +23,8 @@ function validRow(row: PerpBoardRow) {
|
|
|
16
23
|
&& [row.markPrice, row.oraclePrice, row.fundingRate, row.fundingIntervalHours, row.fundingRate8h, row.fundingApr,
|
|
17
24
|
row.predictedFundingRate, row.predictedFundingIntervalHours, row.openInterestBase, row.openInterestUsd, row.volume24hUsd,
|
|
18
25
|
row.premium, row.oiChange24h, row.closedMarketPremium].every(finiteOrNull)
|
|
19
|
-
&& (row.fundingIntervalHours === null || row.fundingIntervalHours > 0)
|
|
26
|
+
&& (row.fundingIntervalHours === null || row.fundingIntervalHours > 0)
|
|
27
|
+
&& validLongShort(row.longShortRatio) && (row.longShortRatioReason == null || typeof row.longShortRatioReason === "string");
|
|
20
28
|
}
|
|
21
29
|
function envelope(data: { access: string; locked: number | boolean; asOf: string | null }) {
|
|
22
30
|
return !!data && ["pro", "preview"].includes(data.access) && (typeof data.locked === "boolean" || Number.isInteger(data.locked) && data.locked >= 0) && (data.asOf === null || date(data.asOf));
|
|
@@ -30,10 +38,49 @@ export function validatePerpsHistory(data: PerpHistoryPayload, expectedMarketId?
|
|
|
30
38
|
if (!envelope(data) || typeof data.marketId !== "string" || expectedMarketId !== undefined && data.marketId !== expectedMarketId || !Array.isArray(data.rows) || !Array.isArray(data.funding) || !Array.isArray(data.candles)
|
|
31
39
|
|| !data.rows.every((row) => date(row.time) && [row.markPrice, row.oraclePrice, row.premium, row.fundingRate, row.fundingIntervalHours, row.openInterestBase, row.openInterestUsd].every(finiteOrNull) && (row.fundingIntervalHours === null || row.fundingIntervalHours > 0))
|
|
32
40
|
|| !data.funding.every((row) => date(row.time) && typeof row.rate === "number" && Number.isFinite(row.rate) && Number.isFinite(row.intervalHours) && row.intervalHours > 0 && row.marketId === data.marketId && date(row.observedAt) && typeof row.sourceUrl === "string")
|
|
33
|
-
|| !data.candles.every((row) => date(row.time) && [row.open, row.high, row.low, row.close].every((n) => typeof n === "number" && Number.isFinite(n)) && row.marketId === data.marketId && date(row.observedAt) && typeof row.sourceUrl === "string")
|
|
41
|
+
|| !data.candles.every((row) => date(row.time) && [row.open, row.high, row.low, row.close].every((n) => typeof n === "number" && Number.isFinite(n)) && row.marketId === data.marketId && date(row.observedAt) && typeof row.sourceUrl === "string")
|
|
42
|
+
|| data.longShortRatio != null && !(Array.isArray(data.longShortRatio) && data.longShortRatio.every((point) => !!point && date(point.time) && validShares(point)))) throw new Error("The server returned unreadable perpetual history");
|
|
34
43
|
return data;
|
|
35
44
|
}
|
|
36
45
|
export const fetchPerpsHistory = async (query: PerpHistoryQuery, client: Pick<typeof apiClient, "getCloudPerpsHistory"> = apiClient) => validatePerpsHistory(await client.getCloudPerpsHistory(query), query.marketId);
|
|
46
|
+
|
|
47
|
+
const RANKING_KEYS = ["fundingPositive", "fundingNegative", "oiSurges", "premiumDislocations", "closedMarketDislocations"] as const;
|
|
48
|
+
export function validatePerpsRankings(data: PerpRankingsPayload): PerpRankingsPayload {
|
|
49
|
+
// A market can rank in several lists, but only once in each.
|
|
50
|
+
if (!envelope(data) || !RANKING_KEYS.every((key) => Array.isArray(data[key]) && data[key].every(validRow)
|
|
51
|
+
&& new Set(data[key].map((row) => row.marketId)).size === data[key].length)) throw new Error("The server returned unreadable perpetual rankings");
|
|
52
|
+
return data;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The server ranks by its own figures and has no long-share order. A full board (Pro) and the fixed preview
|
|
56
|
+
* both arrive whole, so ordering them here is the order the server would give: highest first, none last.
|
|
57
|
+
*/
|
|
58
|
+
const LONG_SHORT_SORT = "long-short";
|
|
59
|
+
const byLongShare = (rows: readonly PerpBoardRow[]) => [...rows].sort((a, b) =>
|
|
60
|
+
(b.longShortRatio?.longShare ?? -Infinity) - (a.longShortRatio?.longShare ?? -Infinity) || a.marketId.localeCompare(b.marketId));
|
|
61
|
+
export async function fetchPerpsBoard(query: PerpBoardQuery, client: Pick<typeof apiClient, "getCloudPerpsBoard"> = apiClient) {
|
|
62
|
+
if (query.sort !== LONG_SHORT_SORT) return validatePerpsBoard(await client.getCloudPerpsBoard(query));
|
|
63
|
+
const data = validatePerpsBoard(await client.getCloudPerpsBoard({ ...query, sort: "oi" }));
|
|
64
|
+
return { ...data, rows: byLongShare(data.rows) };
|
|
65
|
+
}
|
|
66
|
+
export const fetchPerpsRankings = async (client: Pick<typeof apiClient, "getCloudPerpsRankings"> = apiClient) => validatePerpsRankings(await client.getCloudPerpsRankings());
|
|
67
|
+
function validatePerpsCompare(data: PerpBoardPayload, baseAsset: string): PerpBoardPayload {
|
|
68
|
+
validatePerpsBoard(data);
|
|
69
|
+
if (!data.rows.every((row) => row.baseAsset.toUpperCase() === baseAsset.toUpperCase())) throw new Error("The server returned contracts for another asset");
|
|
70
|
+
return data;
|
|
71
|
+
}
|
|
72
|
+
export const fetchPerpsCompare = async (baseAsset: string, client: Pick<typeof apiClient, "getCloudPerpsCompare"> = apiClient) => validatePerpsCompare(await client.getCloudPerpsCompare(baseAsset), baseAsset);
|
|
73
|
+
export const perpsRankingsCache = createPluginCache<PerpRankingsPayload>({ kind: "perps-rankings", source: "gloom-cloud", schemaVersion: 1,
|
|
74
|
+
policy: { staleMs: 60_000, expireMs: 86_400_000 } });
|
|
75
|
+
// Board and comparison payloads share the board cache, keyed by the query and the account's plan.
|
|
76
|
+
const boardKey = (query: PerpBoardQuery, access: string) => `board:v1:${query.assetClass ?? "all"}:${query.sort ?? "oi"}:${query.search?.trim().toUpperCase() ?? ""}:${access}`;
|
|
77
|
+
export const cachedPerpsBoard = (query: PerpBoardQuery, access: string) => cachedCloudResource(perpsCache, boardKey(query, access), validatePerpsBoard);
|
|
78
|
+
export const loadPerpsBoard = (query: PerpBoardQuery, access: string, force = false) => loadCloudResource(perpsCache, boardKey(query, access), () => fetchPerpsBoard(query), { force, validate: validatePerpsBoard });
|
|
79
|
+
export const cachedPerpsRankings = (access: string) => cachedCloudResource(perpsRankingsCache, `rankings:v1:${access}`, validatePerpsRankings);
|
|
80
|
+
export const loadPerpsRankings = (access: string, force = false) => loadCloudResource(perpsRankingsCache, `rankings:v1:${access}`, () => fetchPerpsRankings(), { force, validate: validatePerpsRankings });
|
|
81
|
+
const compareKey = (baseAsset: string, access: string) => `compare:v1:${baseAsset.toUpperCase()}:${access}`;
|
|
82
|
+
export const cachedPerpsCompare = (baseAsset: string, access: string) => cachedCloudResource(perpsCache, compareKey(baseAsset, access), (data) => validatePerpsCompare(data, baseAsset));
|
|
83
|
+
export const loadPerpsCompare = (baseAsset: string, access: string, force = false) => loadCloudResource(perpsCache, compareKey(baseAsset, access), () => fetchPerpsCompare(baseAsset), { force, validate: (data) => validatePerpsCompare(data, baseAsset) });
|
|
37
84
|
function validatePerpsEquity(data: PerpBoardPayload, listing: PerpEquityListing): PerpBoardPayload {
|
|
38
85
|
validatePerpsBoard(data);
|
|
39
86
|
const same = (value: PerpEquityListing | null | undefined) => value?.symbol === listing.symbol && value.exchange === listing.exchange;
|