gloomberb 0.16.0 → 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 +44 -11
- package/src/api-client/errors.ts +12 -0
- package/src/api-client/geo.ts +363 -0
- package/src/api-client/index.ts +5 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/members.ts +26 -0
- package/src/api-client/paths.ts +30 -1
- package/src/api-client/perps.ts +41 -0
- package/src/api-client/request.ts +5 -2
- package/src/api-client/types.ts +9 -1
- package/src/app/global-shortcuts.ts +14 -6
- package/src/app/keybindings/actions.ts +1 -1
- package/src/app/runtime/update.ts +38 -2
- package/src/app.tsx +10 -6
- 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 +36 -22
- 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/commands/collection.ts +5 -1
- 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/root/command-items.ts +9 -2
- package/src/components/command-bar/routes/root/plugin-install.ts +2 -2
- package/src/components/command-bar/routes/root/provider-search.ts +19 -5
- package/src/components/command-bar/routes/root/runtime.ts +14 -5
- package/src/components/command-bar/routes/ticker-search/actions.ts +5 -0
- package/src/components/command-bar/routes/ticker-search/results.ts +31 -5
- package/src/components/command-bar/routes/ticker-search/route.ts +2 -2
- package/src/components/command-bar/routing/actions.ts +2 -0
- package/src/components/command-bar/selection-runtime.ts +38 -0
- package/src/components/command-bar/surface/index.tsx +7 -2
- 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/index.ts +2 -2
- package/src/components/layout/detached-pane-shell.tsx +6 -3
- package/src/components/layout/floating-pane.tsx +6 -0
- package/src/components/layout/header.tsx +24 -7
- package/src/components/layout/missing-pane.tsx +2 -2
- 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 +61 -28
- package/src/components/layout/pane/index.tsx +6 -0
- package/src/components/layout/pane/title.ts +31 -7
- package/src/components/layout/shell/drag/index.ts +6 -5
- package/src/components/layout/shell/drag/runtime.ts +3 -0
- package/src/components/layout/shell/index.tsx +6 -0
- 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 +29 -10
- package/src/components/layout/shell/terminal-pointer-runtime.ts +24 -3
- package/src/components/layout/shell/ticker-link-menu.ts +23 -9
- package/src/components/metric-treemap/heat-surface.tsx +745 -0
- package/src/components/metric-treemap/heat-tiles.ts +228 -0
- package/src/components/metric-treemap/index.tsx +2 -0
- package/src/components/metric-treemap/layout.ts +8 -0
- package/src/components/metric-treemap/scene.ts +432 -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/pane-settings-dialog/field-dialogs.tsx +1 -0
- package/src/components/ticker/list-table-view.tsx +13 -1
- package/src/components/toggle-list.tsx +38 -12
- package/src/components/ui/country-flag-art.tsx +449 -0
- package/src/components/ui/country-flag.tsx +41 -0
- package/src/components/ui/data-table/types.ts +13 -0
- package/src/components/ui/fields.tsx +8 -0
- package/src/components/ui/icon.tsx +2 -0
- package/src/components/ui/index.ts +4 -0
- package/src/components/ui/multi-select/dialog.tsx +108 -22
- package/src/components/ui/range-track.tsx +53 -0
- package/src/components/ui/status.tsx +8 -2
- package/src/components/ui/tabs.tsx +11 -4
- package/src/components/ui/text-prompt-dialog.tsx +4 -2
- package/src/core/state/app/types.ts +10 -1
- package/src/data/config/store/normalize.ts +17 -1
- package/src/i18n/es.ts +24 -4
- package/src/i18n/ja.ts +24 -4
- package/src/i18n/ko.ts +24 -4
- package/src/i18n/zh-cn.ts +24 -4
- package/src/i18n/zh-tw.ts +24 -4
- package/src/layout/ticker-navigation.ts +28 -2
- 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 +8 -0
- package/src/market-data/quotes/freshness.ts +36 -0
- package/src/market-data/quotes/resolution.ts +24 -8
- package/src/market-data/quotes/screener-live-quotes.ts +49 -4
- package/src/market-data/quotes/time.ts +16 -0
- 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 +330 -79
- package/src/plugins/builtin/analytics/risk-fx.ts +102 -0
- package/src/plugins/builtin/analytics/risk-headless.ts +61 -49
- package/src/plugins/builtin/analytics/risk-math.ts +33 -13
- package/src/plugins/builtin/analytics/risk-model.ts +282 -64
- package/src/plugins/builtin/analytics/risk-pane.tsx +62 -30
- package/src/plugins/builtin/analytics/risk-test-data.ts +206 -0
- 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/cells.tsx +15 -12
- package/src/plugins/builtin/buildout/detail/company.tsx +3 -3
- package/src/plugins/builtin/buildout/detail/index.tsx +0 -1
- package/src/plugins/builtin/buildout/detail/intel.tsx +6 -6
- package/src/plugins/builtin/buildout/detail/site.tsx +4 -7
- package/src/plugins/builtin/buildout/detail/ui.tsx +0 -19
- package/src/plugins/builtin/buildout/index.tsx +1 -0
- package/src/plugins/builtin/catalysts/headless.ts +3 -1
- package/src/plugins/builtin/catalysts/index.tsx +2 -2
- package/src/plugins/builtin/catalysts/pane.tsx +3 -1
- package/src/plugins/builtin/cds/headless.ts +2 -0
- package/src/plugins/builtin/cds/index.tsx +1 -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/debt-maturities/wall-bar.tsx +14 -41
- package/src/plugins/builtin/dividend-yield/client.ts +4 -2
- 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/client.ts +2 -2
- package/src/plugins/builtin/earnings/headless.ts +5 -0
- package/src/plugins/builtin/earnings-calls/headless.ts +1 -0
- package/src/plugins/builtin/earnings-calls/index.tsx +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/fx-matrix/direction.ts +81 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +71 -33
- package/src/plugins/builtin/fx-matrix/live-legs.ts +17 -0
- package/src/plugins/builtin/fx-matrix/pairs.ts +68 -14
- package/src/plugins/builtin/fx-matrix/settings.ts +62 -0
- 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/jobs/index.tsx +1 -0
- package/src/plugins/builtin/jobs/share-bars.tsx +5 -28
- 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 +30 -2
- package/src/plugins/builtin/market-heatmap/headless.ts +135 -0
- package/src/plugins/builtin/market-heatmap/index.tsx +245 -81
- package/src/plugins/builtin/market-heatmap/live.ts +88 -0
- package/src/plugins/builtin/market-heatmap/model.ts +146 -0
- package/src/plugins/builtin/market-heatmap/portfolio.ts +50 -11
- package/src/plugins/builtin/market-movers/client.ts +3 -2
- package/src/plugins/builtin/market-movers/headless.ts +10 -1
- package/src/plugins/builtin/market-movers/screener.ts +18 -5
- package/src/plugins/builtin/market-valuation/headless.ts +2 -0
- package/src/plugins/builtin/members/client.ts +53 -0
- package/src/plugins/builtin/members/headless.ts +35 -0
- package/src/plugins/builtin/members/index.tsx +20 -0
- package/src/plugins/builtin/members/model.ts +68 -0
- package/src/plugins/builtin/members/pane.tsx +175 -0
- package/src/plugins/builtin/mna/distress/headless.ts +1 -0
- package/src/plugins/builtin/mna/headless.ts +1 -0
- package/src/plugins/builtin/mna/index.tsx +2 -1
- package/src/plugins/builtin/mna/pane.tsx +5 -2
- 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/index.tsx +2 -1
- 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 +74 -2
- package/src/plugins/builtin/pe-band/headless.ts +26 -8
- package/src/plugins/builtin/pe-band/model.ts +102 -22
- package/src/plugins/builtin/pe-band/pane.tsx +34 -16
- package/src/plugins/builtin/pe-band/report-dates.ts +78 -0
- package/src/plugins/builtin/perps/client.ts +66 -5
- package/src/plugins/builtin/perps/equity-identity.ts +16 -0
- package/src/plugins/builtin/perps/equity-row.tsx +6 -4
- 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/plugin-marketplace/activation.ts +19 -0
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -2
- package/src/plugins/builtin/plugin-marketplace/store.ts +2 -0
- 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/quick-add/index.tsx +43 -2
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +48 -5
- 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/scanner/hilo-bars.tsx +12 -70
- package/src/plugins/builtin/scanner/hilo-model.ts +0 -18
- 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/move-bar.tsx +10 -32
- 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/social-mentions/index.tsx +1 -1
- 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/index.tsx +2 -1
- 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/components.tsx +5 -60
- package/src/plugins/builtin/ticker-detail/overview/model.ts +9 -2
- package/src/plugins/builtin/ticker-detail/overview/pane.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +26 -26
- package/src/plugins/builtin/ticker-detail/pane.tsx +1 -1
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +7 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +20 -4
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -2
- package/src/plugins/builtin/time-sales/headless.ts +5 -1
- package/src/plugins/builtin/treasury-auctions/headless.ts +1 -0
- package/src/plugins/builtin/vol-surface/index.tsx +1 -1
- 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/catalog-browser.ts +2 -0
- package/src/plugins/catalog-manifest.ts +1 -0
- package/src/plugins/catalog-ui.ts +3 -1
- package/src/plugins/catalog.ts +1 -1
- package/src/plugins/host-link.ts +33 -1
- package/src/plugins/installer.ts +48 -12
- package/src/plugins/loader.ts +30 -3
- package/src/plugins/manager-node.ts +15 -5
- package/src/plugins/registry/index.ts +1 -1
- package/src/plugins/registry/pane-settings.ts +26 -7
- package/src/public/__snapshots__/public-api.test.ts.snap +2 -0
- package/src/renderers/dom/build-assets.ts +81 -3
- package/src/renderers/dom/data-table/row.tsx +52 -16
- package/src/renderers/dom/desktop/controls.tsx +7 -3
- package/src/renderers/dom/desktop/icons.tsx +1 -0
- package/src/renderers/dom/desktop/list-view.tsx +35 -10
- package/src/renderers/dom/desktop/popover.tsx +73 -14
- package/src/renderers/dom/host/box.tsx +69 -26
- package/src/renderers/dom/host/input.tsx +14 -3
- package/src/renderers/dom/host/tabs.tsx +37 -1
- package/src/renderers/dom/styles.css +41 -7
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +11 -6
- package/src/renderers/electrobun/bun/desktop/backend.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +181 -87
- package/src/renderers/electrobun/bun/index.ts +73 -6
- package/src/renderers/electrobun/bun/window/remembered-frame.ts +122 -0
- package/src/renderers/electrobun/shared/protocol.ts +3 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +10 -4
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +3 -1
- package/src/renderers/electrobun/view/main.tsx +7 -1
- package/src/renderers/electrobun/view/renderer-window-error.ts +18 -0
- package/src/renderers/electrobun/view/rpc-timeout.ts +14 -3
- package/src/renderers/electrobun/view/update-host.ts +45 -21
- package/src/renderers/opentui/host.tsx +5 -0
- package/src/sources/gloomberb-cloud/index.ts +25 -4
- package/src/sources/gloomberb-cloud/normalizers.ts +36 -5
- package/src/sources/provider-router/cached-routes.ts +4 -1
- package/src/sources/provider-router/financials.ts +11 -8
- 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/benign-window-error.ts +7 -0
- package/src/telemetry/crash-reports-dom.ts +2 -0
- package/src/telemetry/crash-reports.ts +4 -1
- package/src/test-support/cli-context.ts +10 -2
- package/src/test-support/daily-nav.ts +25 -0
- package/src/theme/heat-colors.ts +357 -2
- package/src/tickers/instrument-kind.ts +34 -7
- package/src/tickers/search/asset-classes.ts +24 -9
- package/src/tickers/search/index.ts +44 -11
- package/src/tickers/search/ranking.ts +23 -3
- 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 +5 -0
- package/src/types/headless.ts +79 -2
- package/src/types/plugin.ts +38 -6
- package/src/types/ticker.ts +15 -0
- package/src/ui/context-menu.tsx +1 -0
- package/src/ui/host.tsx +16 -0
- package/src/updater/index.ts +38 -13
- 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
|
@@ -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,18 +11,29 @@ 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
|
],
|
|
18
22
|
async load(args, ctx) {
|
|
19
23
|
const instrument = await resolveHeadlessInstrument(ctx, args.symbols[0]!);
|
|
20
|
-
const inputs = await loadPeBandInputs({ instrument, signal: ctx.signal }, ctx.marketData, new MarketDataCoordinator(ctx.marketData));
|
|
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
|
|
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,8 +1,12 @@
|
|
|
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";
|
|
9
|
+
import { datedByReport, type ReportDate } from "./report-dates";
|
|
6
10
|
|
|
7
11
|
const DAY_MS = 86_400_000;
|
|
8
12
|
const YEAR_MS = 365.25 * DAY_MS;
|
|
@@ -23,7 +27,7 @@ export interface EpsStep {
|
|
|
23
27
|
/** Four reported quarters summed, or a reported fiscal year. */
|
|
24
28
|
basis: "ttm" | "annual";
|
|
25
29
|
knownAt: Date;
|
|
26
|
-
/** False when no publication date is on record and `knownAt` is the period end. */
|
|
30
|
+
/** False when no publication date or report is on record and `knownAt` is the period end. */
|
|
27
31
|
dated: boolean;
|
|
28
32
|
/** Statement currency units; null when unavailable (a declared gap or an incomplete run of quarters). */
|
|
29
33
|
eps: number | null;
|
|
@@ -42,8 +46,24 @@ export interface PeBandRow extends EpsStep {
|
|
|
42
46
|
/** The close of the week the figure became known, in price units. */
|
|
43
47
|
price: number | null;
|
|
44
48
|
pe: number | null;
|
|
45
|
-
/** 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. */
|
|
46
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;
|
|
47
67
|
}
|
|
48
68
|
|
|
49
69
|
export interface PeBandModel {
|
|
@@ -64,10 +84,12 @@ export interface PeBandModel {
|
|
|
64
84
|
* the line just above today's P/E when that is higher.
|
|
65
85
|
*/
|
|
66
86
|
bandCeiling: number | null;
|
|
67
|
-
/** Figures in the window dated by period end because
|
|
87
|
+
/** Figures in the window dated by period end because neither a publication date nor a report is on record. */
|
|
68
88
|
undated: number;
|
|
69
89
|
/** Trailing sums in the window that are unavailable (a quarter's EPS is withheld or not reported). */
|
|
70
90
|
unavailable: number;
|
|
91
|
+
/** Set when today's EPS is in another currency than the dollar price. */
|
|
92
|
+
conversion: EpsConversion | null;
|
|
71
93
|
/** Blocks the pane: nothing can be priced. */
|
|
72
94
|
error: string | null;
|
|
73
95
|
/** The pane still draws, but without bands. */
|
|
@@ -98,10 +120,13 @@ function epsSeries(financials: TickerFinancials, period: "ttm" | "annual"): Time
|
|
|
98
120
|
* by the shared statement series, which keeps declared gaps and broken runs
|
|
99
121
|
* of quarters unavailable. A reported fiscal year is itself a trailing
|
|
100
122
|
* twelve-month figure; at a fiscal year end it replaces the quarterly sum.
|
|
123
|
+
* A figure with no publication date of its own is dated by the company's
|
|
124
|
+
* report of that quarter, when `reports` has it.
|
|
101
125
|
*/
|
|
102
|
-
export function trailingEpsSteps(financials: TickerFinancials): EpsStep[] {
|
|
103
|
-
const
|
|
104
|
-
|
|
126
|
+
export function trailingEpsSteps(financials: TickerFinancials, reports: readonly ReportDate[] = []): EpsStep[] {
|
|
127
|
+
const known = (row: FinancialStatement) => datedByReport(knownWhenFiled(row), reports);
|
|
128
|
+
const adjusted = { ...financials, quarterlyStatements: financials.quarterlyStatements.map(known),
|
|
129
|
+
annualStatements: financials.annualStatements.map(known) };
|
|
105
130
|
const toStep = (basis: EpsStep["basis"]) => (point: TimeSeriesPoint): EpsStep => ({
|
|
106
131
|
periodEnd: point.observedAt.toISOString().slice(0, 10), basis, knownAt: point.date, dated: point.availableAt !== undefined,
|
|
107
132
|
eps: point.value != null && Number.isFinite(point.value) ? point.value : null, currency: point.provenance?.currency ?? null,
|
|
@@ -127,6 +152,37 @@ export function perShareDigits(value: number): number {
|
|
|
127
152
|
|
|
128
153
|
export const formatPerShare = (value: number) => value.toFixed(perShareDigits(value));
|
|
129
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
|
+
|
|
130
186
|
function quantile(sorted: readonly number[], q: number): number {
|
|
131
187
|
const position = (sorted.length - 1) * q;
|
|
132
188
|
const low = Math.floor(position), high = Math.ceil(position);
|
|
@@ -193,30 +249,47 @@ export function stepAt(steps: readonly EpsStep[], time: number): EpsStep | null
|
|
|
193
249
|
export function projectPeBand(
|
|
194
250
|
financials: TickerFinancials | null,
|
|
195
251
|
history: readonly PricePoint[],
|
|
196
|
-
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
|
+
},
|
|
197
259
|
): PeBandModel {
|
|
198
260
|
const currency = financials?.quote?.currency ?? null;
|
|
199
261
|
const empty: PeBandModel = { symbol: options.symbol, currency, weeks: [], rows: [], multiples: [], current: null, range: null, sample: null, bandCeiling: null,
|
|
200
|
-
undated: 0, unavailable: 0, error: null, notice: null };
|
|
262
|
+
undated: 0, unavailable: 0, conversion: null, error: null, notice: null };
|
|
201
263
|
if (!financials) return { ...empty, error: "Fundamentals unavailable." };
|
|
202
|
-
const allSteps = trailingEpsSteps(financials);
|
|
264
|
+
const allSteps = trailingEpsSteps(financials, options.reports);
|
|
203
265
|
const latest = allSteps.findLast((step) => step.eps != null);
|
|
204
266
|
if (!latest) return { ...empty, error: "No trailing EPS on record." };
|
|
205
267
|
const units = createValuationCurrencyContext(financials);
|
|
206
268
|
// Price units per statement unit: 1 for the same currency, 100 for a pence price against pound statements.
|
|
207
|
-
const
|
|
269
|
+
const unitScale = (step: EpsStep) => {
|
|
208
270
|
const factor = units.priceInStatementUnits({ date: step.periodEnd, currency: step.currency ?? undefined }, 1);
|
|
209
271
|
return factor ? 1 / factor : null;
|
|
210
272
|
};
|
|
211
|
-
// ADRs
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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}` : ""}.` };
|
|
215
282
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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;
|
|
219
291
|
};
|
|
292
|
+
const day = (time: Date | number) => new Date(time).toISOString().slice(0, 10);
|
|
220
293
|
|
|
221
294
|
const now = options.now ?? Date.now();
|
|
222
295
|
const firstKnown = allSteps.find((step) => step.eps != null)!.knownAt.getTime();
|
|
@@ -226,7 +299,9 @@ export function projectPeBand(
|
|
|
226
299
|
.filter((point) => Number.isFinite(point.date.getTime()) && Number.isFinite(point.close) && point.close > 0)
|
|
227
300
|
.sort((a, b) => a.date.getTime() - b.date.getTime());
|
|
228
301
|
const weeks = sorted.filter((point) => point.date.getTime() >= start).map((point): PeBandWeek => {
|
|
229
|
-
|
|
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;
|
|
230
305
|
const positive = eps != null && eps > 0 ? eps : null;
|
|
231
306
|
return { date: point.date, close: point.close, eps: positive, pe: positive ? point.close / positive : null };
|
|
232
307
|
});
|
|
@@ -235,10 +310,12 @@ export function projectPeBand(
|
|
|
235
310
|
const rows = steps.filter((step) => step.eps != null).map((step): PeBandRow => {
|
|
236
311
|
const time = step.knownAt.getTime();
|
|
237
312
|
const price = sorted.findLast((point) => point.date.getTime() <= time)?.close ?? null;
|
|
238
|
-
|
|
239
|
-
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
|
|
240
317
|
&& Math.abs(Date.parse(step.periodEnd) - Date.parse(other.periodEnd) - 365 * DAY_MS) <= 20 * DAY_MS);
|
|
241
|
-
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,
|
|
242
319
|
yoy: step.eps != null && prior?.eps != null && step.eps > 0 && prior.eps > 0 ? step.eps / prior.eps - 1 : null };
|
|
243
320
|
}).reverse();
|
|
244
321
|
|
|
@@ -248,13 +325,16 @@ export function projectPeBand(
|
|
|
248
325
|
const quotePrice = financials.quote?.price;
|
|
249
326
|
const price = quotePrice != null && Number.isFinite(quotePrice) && quotePrice > 0 ? quotePrice : sorted.at(-1)?.close ?? null;
|
|
250
327
|
const currentStep = stepAt(allSteps, now);
|
|
251
|
-
|
|
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;
|
|
252
331
|
const currentPe = price != null && currentEps != null && currentEps > 0 ? price / currentEps : null;
|
|
253
332
|
const multiples = chooseMultiples(pes, currentPe);
|
|
254
333
|
const above = currentPe == null ? undefined : multiples.find((multiple) => multiple >= currentPe);
|
|
255
334
|
const highestClose = weeks.reduce((max, week) => Math.max(max, week.close), 0);
|
|
256
335
|
return {
|
|
257
336
|
...empty, weeks, rows, multiples,
|
|
337
|
+
conversion: latestFx ? { currency: latestFx.currency, latest: fxCloseOnOrBefore(latestFx, day(now))! } : null,
|
|
258
338
|
sample: ranked.length ? { start: ranked[0]!.date, weeks: ranked.length } : null,
|
|
259
339
|
bandCeiling: highestClose > 0 ? Math.max(highestClose * 1.5, above != null && currentEps != null ? above * currentEps : 0) : null,
|
|
260
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
|
}
|
|
@@ -94,9 +102,12 @@ export function PeBandPane({ width, height, focused }: PaneProps) {
|
|
|
94
102
|
|
|
95
103
|
const lookbackYears = Number(lookback);
|
|
96
104
|
const model = useMemo(() => inputs.data
|
|
97
|
-
? projectPeBand(inputs.data.financials, inputs.data.history, { symbol: symbol ?? "", lookbackYears: Number.isFinite(lookbackYears) ? lookbackYears : 10
|
|
105
|
+
? projectPeBand(inputs.data.financials, inputs.data.history, { symbol: symbol ?? "", lookbackYears: Number.isFinite(lookbackYears) ? lookbackYears : 10,
|
|
106
|
+
reports: inputs.data.reports, fx: inputs.data.fx, fxError: inputs.data.fxError })
|
|
98
107
|
: null, [inputs.data, lookbackYears, symbol]);
|
|
99
108
|
const rows = model?.rows ?? [];
|
|
109
|
+
const conversion = model?.conversion ?? null;
|
|
110
|
+
const columns = useMemo(() => tableColumns(conversion), [conversion?.currency, conversion?.latest.rate]);
|
|
100
111
|
const series = useMemo(() => model && !model.error ? chartSeries(model, colors) : [], [model, colors]);
|
|
101
112
|
const currency = model?.currency ?? undefined;
|
|
102
113
|
const firstWeek = model?.weeks[0]?.date;
|
|
@@ -116,7 +127,8 @@ export function PeBandPane({ width, height, focused }: PaneProps) {
|
|
|
116
127
|
const unavailableNotice = model?.unavailable
|
|
117
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;
|
|
118
129
|
usePaneNoticeFooter({ registrationId: "pe-band-notices", focused, notices: [...new Set([identityError, inputs.error, inputs.data?.historyError,
|
|
119
|
-
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))] });
|
|
120
132
|
usePaneFooter("pe-band", () => ({ info: [
|
|
121
133
|
...(inputs.loading ? [{ id: "loading", parts: [{ text: "loading statements", tone: "muted" as const }] }] : []),
|
|
122
134
|
...(inputs.data?.stale ? [{ id: "stale", parts: [{ text: "stale", tone: "warning" as const }] }] : []),
|
|
@@ -127,15 +139,19 @@ export function PeBandPane({ width, height, focused }: PaneProps) {
|
|
|
127
139
|
if (!model?.current || model.error) return [];
|
|
128
140
|
const { current, range } = model;
|
|
129
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)}` : "";
|
|
130
146
|
return [
|
|
131
147
|
{ id: "pe", label: "P/E", value: multiple(current.pe),
|
|
132
148
|
detail: current.pe != null ? formatPercentileRank(current.percentile, windowLabel) : current.eps != null ? "EPS not positive" : "EPS unavailable" },
|
|
133
149
|
{ id: "median", label: "Median P/E", value: multiple(range?.median), detail: range ? `${multiple(range.min)} to ${multiple(range.max)}` : undefined },
|
|
134
150
|
{ id: "eps", label: "EPS", value: current.eps == null ? "--" : formatCurrency(current.eps, currency, perShareDigits(current.eps)),
|
|
135
|
-
detail: step ? periodLabel(step) : undefined },
|
|
151
|
+
detail: step ? `${periodLabel(step)}${converted}` : undefined },
|
|
136
152
|
{ id: "price", label: "Price", value: formatCurrency(current.price, currency, perShareDigits(current.price)) },
|
|
137
153
|
];
|
|
138
|
-
}, [model, windowLabel, currency]);
|
|
154
|
+
}, [model, windowLabel, currency, conversion]);
|
|
139
155
|
|
|
140
156
|
const formatPrice = useCallback((value: number) => formatCurrency(value, currency, perShareDigits(value)), [currency]);
|
|
141
157
|
const formatAxis = useMemo(() => spanAxisFormatter((value, digits) => formatCurrency(value, currency, Math.max(2, digits))), [currency]);
|
|
@@ -144,14 +160,16 @@ export function PeBandPane({ width, height, focused }: PaneProps) {
|
|
|
144
160
|
if (!symbol) return <EmptyState title="Choose a ticker." />;
|
|
145
161
|
return <Box width={width} height={height} flexDirection="column" overflow="hidden">
|
|
146
162
|
<PaneStatusBody subject="P/E band" loading={inputs.loading && !model} error={!model ? inputs.error ?? identityError ?? null : model.error} empty={false}>
|
|
147
|
-
{model && !model.error ? <DataTableView<PeBandRow> focused={focused} columns={
|
|
163
|
+
{model && !model.error ? <DataTableView<PeBandRow> focused={focused} columns={columns} items={rows} rootWidth={width} rootHeight={height}
|
|
148
164
|
getItemKey={rowKey} emptyStateTitle="No trailing EPS in this window." sortColumnId={null} sortDirection="desc" resetScrollKey={symbol}
|
|
149
165
|
selection={{ kind: "id", selectedId: selected, getId: rowKey, onChange: (id) => setSelectedId(id) }}
|
|
150
166
|
selectedTextOverridesCellColor
|
|
151
167
|
getExportMetadata={() => [["symbol", model.symbol], ["currency", model.currency ?? ""],
|
|
152
|
-
["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"]]}
|
|
153
171
|
renderCell={(row, column) => renderCell(row, column, colors)}
|
|
154
|
-
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}
|
|
155
173
|
chart={model.weeks.length >= 2 ? { series, formatValue: formatPrice, formatAxisValue: formatAxis, remoteKind: "pe-band", ...link } : null} />} />
|
|
156
174
|
: null}
|
|
157
175
|
</PaneStatusBody>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { EarningsHistoryPayload } from "../../../api-client/earnings";
|
|
2
|
+
import type { FinancialStatement } from "../../../types/financials";
|
|
3
|
+
import { statementFieldAvailability } from "../../../utils/financial-statements";
|
|
4
|
+
import { newYorkToday } from "../earnings/board-model";
|
|
5
|
+
|
|
6
|
+
const DAY_MS = 86_400_000;
|
|
7
|
+
/** A 52/53-week fiscal quarter ends within days of its month end, never a month away. */
|
|
8
|
+
const MONTH_END_TOLERANCE_MS = 10 * DAY_MS;
|
|
9
|
+
/** A company reports after its period ends; later than this the report is another quarter's. */
|
|
10
|
+
const MAX_REPORT_LAG_MS = 180 * DAY_MS;
|
|
11
|
+
|
|
12
|
+
/** One reported quarter, reduced to what dates its EPS. */
|
|
13
|
+
export interface ReportDate {
|
|
14
|
+
/** New York report date, YYYY-MM-DD. */
|
|
15
|
+
date: string;
|
|
16
|
+
/** Fiscal quarter end month, YYYY-MM. */
|
|
17
|
+
fiscalPeriod: string;
|
|
18
|
+
/** Filing time, when on record. */
|
|
19
|
+
reportedAt: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The reports that happened, with the quarter they cover. An upcoming report,
|
|
24
|
+
* one whose date is only expected, and one with no fiscal quarter date nothing.
|
|
25
|
+
*/
|
|
26
|
+
export function reportDatesFrom(payload: EarningsHistoryPayload): ReportDate[] {
|
|
27
|
+
const asOf = Date.parse(payload.asOf);
|
|
28
|
+
if (!Number.isFinite(asOf)) return [];
|
|
29
|
+
const today = newYorkToday(new Date(asOf));
|
|
30
|
+
return payload.reports.flatMap((report) => (
|
|
31
|
+
report.fiscalPeriod && /^\d{4}-(0[1-9]|1[0-2])$/.test(report.fiscalPeriod) && report.date <= today
|
|
32
|
+
&& (report.epsActual != null || report.reportedAt != null)
|
|
33
|
+
? [{ date: report.date, fiscalPeriod: report.fiscalPeriod, reportedAt: report.reportedAt }] : []
|
|
34
|
+
));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function coversPeriod(periodEnd: number, fiscalPeriod: string): boolean {
|
|
38
|
+
if (new Date(periodEnd).toISOString().slice(0, 7) === fiscalPeriod) return true;
|
|
39
|
+
const monthEnd = Date.UTC(Number(fiscalPeriod.slice(0, 4)), Number(fiscalPeriod.slice(5, 7)), 0);
|
|
40
|
+
return Math.abs(periodEnd - monthEnd) <= MONTH_END_TOLERANCE_MS;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The filing time when it falls on the report date in both New York and UTC, else the date. */
|
|
44
|
+
function publishedAt(report: ReportDate): string {
|
|
45
|
+
const filed = report.reportedAt ? new Date(report.reportedAt) : null;
|
|
46
|
+
return filed && Number.isFinite(filed.getTime()) && filed.toISOString().slice(0, 10) === report.date
|
|
47
|
+
&& newYorkToday(filed) === report.date ? report.reportedAt! : report.date;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* When the quarter ended on `periodEnd` was reported. It is the one report
|
|
52
|
+
* whose fiscal quarter month is the period's own month or sits within days of
|
|
53
|
+
* it, and which comes after the period. No report, or two on different dates,
|
|
54
|
+
* leaves the period undated rather than guessed.
|
|
55
|
+
*/
|
|
56
|
+
function reportedWhen(periodEnd: string, reports: readonly ReportDate[]): string | null {
|
|
57
|
+
const end = Date.parse(`${periodEnd}T00:00:00Z`);
|
|
58
|
+
if (!Number.isFinite(end)) return null;
|
|
59
|
+
const matches = reports.filter((report) => {
|
|
60
|
+
const lag = Date.parse(`${report.date}T00:00:00Z`) - end;
|
|
61
|
+
return lag > 0 && lag <= MAX_REPORT_LAG_MS && coversPeriod(end, report.fiscalPeriod);
|
|
62
|
+
});
|
|
63
|
+
if (new Set(matches.map((report) => report.date)).size !== 1) return null;
|
|
64
|
+
return publishedAt(matches.find((report) => report.reportedAt) ?? matches[0]!);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The statement with its EPS dated by the company's report, only when it has no
|
|
69
|
+
* publication date of its own: a date on record always wins. A fiscal year is
|
|
70
|
+
* dated by the report of its last quarter, which shares its period end. A
|
|
71
|
+
* trailing sum takes the latest of its four quarters from the shared series,
|
|
72
|
+
* so it cannot be known before all four are.
|
|
73
|
+
*/
|
|
74
|
+
export function datedByReport(row: FinancialStatement, reports: readonly ReportDate[]): FinancialStatement {
|
|
75
|
+
if (!reports.length || typeof row.eps !== "number" || !Number.isFinite(row.eps) || statementFieldAvailability(row, "eps") !== undefined) return row;
|
|
76
|
+
const known = reportedWhen(row.date, reports);
|
|
77
|
+
return known ? { ...row, fieldAvailability: { ...row.fieldAvailability, eps: known } } : row;
|
|
78
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { apiClient } from "../../../api-client";
|
|
2
|
-
import type { PerpBoardPayload, PerpBoardRow, 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,12 +38,65 @@ 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);
|
|
37
|
-
|
|
38
|
-
|
|
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) });
|
|
84
|
+
function validatePerpsEquity(data: PerpBoardPayload, listing: PerpEquityListing): PerpBoardPayload {
|
|
85
|
+
validatePerpsBoard(data);
|
|
86
|
+
const same = (value: PerpEquityListing | null | undefined) => value?.symbol === listing.symbol && value.exchange === listing.exchange;
|
|
87
|
+
if (!same(data.listing) || !data.rows.every((row) => {
|
|
88
|
+
const match = row.equityMatch;
|
|
89
|
+
return same(match?.listing) && row.assetClass === "stocks" && !!match?.underlying.exchange
|
|
90
|
+
&& match.underlying.symbol === row.underlyingSymbol
|
|
91
|
+
&& (match.basis === "issuer" || match.basis === "listing" && same(match.underlying));
|
|
92
|
+
})) throw new Error("The server returned perpetuals for another listing");
|
|
93
|
+
return data;
|
|
94
|
+
}
|
|
95
|
+
export const loadPerpsEquity = (listing: PerpEquityListing, access: string, force = false) => {
|
|
96
|
+
const validate = (data: PerpBoardPayload) => validatePerpsEquity(data, listing);
|
|
97
|
+
return loadCloudResource(perpsCache, `equity:v2:${listing.symbol}:${listing.exchange}:${access}`,
|
|
98
|
+
async () => validate(await apiClient.getCloudPerpsEquity(listing.symbol, listing.exchange)), { force, validate });
|
|
99
|
+
};
|
|
39
100
|
|
|
40
101
|
function validatePerpsMarket(data: PerpMarketPayload): PerpMarketPayload {
|
|
41
102
|
validatePerpsBoard(data);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PerpEquityListing } from "../../../api-client/perps";
|
|
2
|
+
import { listingSuffixExchange } from "../../../sources/listing-symbols";
|
|
3
|
+
import { canonicalExchange } from "../../../utils/exchanges";
|
|
4
|
+
import { listingIdentity } from "../shared/ticker-request";
|
|
5
|
+
|
|
6
|
+
/** A qualifier owns the listing. Conflicting metadata cannot authorize a comparison. */
|
|
7
|
+
export function perpEquityIdentity(key: string, savedExchange = ""): PerpEquityListing | null {
|
|
8
|
+
const identity = listingIdentity(key, savedExchange);
|
|
9
|
+
if (!identity) return null;
|
|
10
|
+
const suffixExchange = listingSuffixExchange(identity.symbol);
|
|
11
|
+
const exchange = suffixExchange ?? canonicalExchange(identity.exchange);
|
|
12
|
+
if (!exchange || (identity.exchange && canonicalExchange(identity.exchange) !== exchange)
|
|
13
|
+
|| (savedExchange && canonicalExchange(savedExchange) !== exchange)) return null;
|
|
14
|
+
const symbol = suffixExchange ? identity.symbol.slice(0, identity.symbol.lastIndexOf(".")) : identity.symbol;
|
|
15
|
+
return /^[A-Z0-9.-]{1,32}$/.test(symbol) ? { symbol, exchange } : null;
|
|
16
|
+
}
|