gloomberb 0.18.2 → 0.18.4
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 +8 -8
- package/README.zh-CN.md +11 -11
- package/bin/{gloomberb → gloom} +3 -1
- package/package.json +6 -2
- package/patches/electrobun@1.18.1.patch +106 -5
- package/src/account/checkout-handoff.tsx +225 -0
- package/src/account/device-signin-dialog.tsx +2 -2
- package/src/account/device-signin.ts +2 -2
- package/src/account/upgrade-dialog.tsx +1 -1
- package/src/api-client/askg.ts +2 -2
- package/src/api-client/auth.ts +2 -2
- package/src/api-client/central-bank-rates.ts +6 -0
- package/src/api-client/collections.ts +5 -1
- package/src/api-client/cot.ts +2 -0
- package/src/api-client/crypto-etf-flows.ts +37 -0
- package/src/api-client/crypto-markets.ts +60 -0
- package/src/api-client/data.ts +99 -5
- package/src/api-client/doe.ts +15 -0
- package/src/api-client/equity-screener.ts +28 -0
- package/src/api-client/exchange-announcements.ts +38 -0
- package/src/api-client/fund-facts.ts +39 -0
- package/src/api-client/futures-curve.ts +9 -1
- package/src/api-client/hiring.ts +301 -0
- package/src/api-client/index.ts +24 -5
- package/src/api-client/market-discovery.ts +14 -0
- package/src/api-client/members.ts +6 -0
- package/src/api-client/mna.ts +6 -0
- package/src/api-client/paths.ts +18 -1
- package/src/api-client/request.ts +26 -7
- package/src/api-client/short-side-publications.ts +37 -0
- package/src/api-client/socket.ts +64 -4
- package/src/api-client/swap-curve.ts +35 -0
- package/src/api-client/telemetry.ts +17 -0
- package/src/api-client/themes.ts +7 -1
- package/src/api-client/types.ts +90 -2
- package/src/app/global-shortcuts.ts +1 -16
- package/src/app/pane-runtime/plugin-bindings.ts +9 -2
- package/src/app/runtime/desktop-deeplink.ts +5 -5
- package/src/app/runtime/plugin-bindings.ts +2 -2
- package/src/app/runtime/startup.ts +13 -0
- package/src/app/runtime/update.ts +28 -8
- package/src/app/star-prompt/status-line.tsx +2 -2
- package/src/architecture/core-builtin-allowlist.json +5 -2
- package/src/architecture/pane-tabs-allowlist.json +65 -0
- package/src/architecture/ui-literals-allowlist.json +3 -3
- package/src/architecture/ui-tokens-allowlist.json +1 -1
- package/src/brokers/broker-portfolio-sync.ts +37 -0
- package/src/brokers/directory.ts +11 -0
- package/src/brokers/logos.ts +3 -1
- package/src/brokers/signed-in/adapter.ts +304 -26
- package/src/brokers/signed-in/client.ts +14 -2
- package/src/brokers/signed-in/connect.ts +10 -2
- package/src/brokers/sync-broker-instance.ts +9 -1
- package/src/capabilities/chart-series.ts +22 -6
- package/src/cli/bank-section.ts +1 -1
- package/src/cli/base-converter.ts +23 -14
- package/src/cli/central-bank-events.ts +1 -1
- package/src/cli/command-options.ts +59 -4
- package/src/cli/commands/api.ts +9 -6
- package/src/cli/commands/broker.ts +2 -2
- package/src/cli/commands/command-utils.ts +2 -2
- package/src/cli/commands/crypto-hints.ts +7 -20
- package/src/cli/commands/fred-series.ts +4 -2
- package/src/cli/commands/keybindings.ts +2 -2
- package/src/cli/commands/market.ts +1189 -296
- package/src/cli/commands/news-hints.ts +2 -2
- package/src/cli/commands/news-search.ts +59 -6
- package/src/cli/commands/options-leaps.ts +41 -26
- package/src/cli/commands/overview.ts +121 -48
- package/src/cli/commands/plugins.ts +9 -9
- package/src/cli/commands/remote.ts +13 -10
- package/src/cli/commands/search.ts +64 -44
- package/src/cli/commands/system.ts +27 -19
- package/src/cli/commands/themes.ts +1 -1
- package/src/cli/commands/ticker.ts +272 -70
- package/src/cli/context.ts +3 -2
- package/src/cli/desktop-pane-shot-view.ts +2 -2
- package/src/cli/desktop-pane-shot.ts +1 -1
- package/src/cli/earnings-calendar.ts +4 -4
- package/src/cli/entry.ts +18 -1
- package/src/cli/errors.ts +20 -2
- package/src/cli/exit-codes.ts +1 -1
- package/src/cli/export-source.ts +10 -4
- package/src/cli/freshness.ts +2 -8
- package/src/cli/fx-forward.ts +130 -0
- package/src/cli/fx-pair.ts +10 -9
- package/src/cli/glossary.ts +64 -20
- package/src/cli/help.ts +20 -14
- package/src/cli/history-export.ts +126 -0
- package/src/cli/history-rows.ts +196 -43
- package/src/cli/index-roots.ts +5 -45
- package/src/cli/index.ts +32 -18
- package/src/cli/issuer-membership.ts +103 -0
- package/src/cli/known-gaps.ts +118 -25
- package/src/cli/listing-arg.ts +315 -43
- package/src/cli/name-entries.ts +73 -22
- package/src/cli/native-smoke.ts +2 -0
- package/src/cli/not-a-ticker.ts +19 -9
- package/src/cli/opentui-libc.ts +5 -0
- package/src/cli/options.ts +12 -0
- package/src/cli/pane-functions/capabilities.ts +77 -20
- package/src/cli/pane-functions/catalog.ts +40 -23
- package/src/cli/pane-functions/discovery.ts +1 -1
- package/src/cli/pane-functions/dom.ts +12 -8
- package/src/cli/pane-functions/financial-ratio-shot.ts +2 -2
- package/src/cli/pane-functions/freshness.ts +174 -23
- package/src/cli/pane-functions/function-help.ts +66 -27
- package/src/cli/pane-functions/headless.ts +48 -16
- package/src/cli/pane-functions/index.ts +82 -21
- package/src/cli/pane-functions/options.ts +22 -2
- package/src/cli/pane-functions/report-notices.ts +12 -4
- package/src/cli/pane-functions/report.ts +5 -5
- package/src/cli/pane-functions/resolver.ts +65 -14
- package/src/cli/pane-functions/screenshot-assessment.ts +322 -0
- package/src/cli/pane-functions/screenshot-bridge.ts +152 -0
- package/src/cli/pane-functions/screenshot-chart-evidence.ts +396 -0
- package/src/cli/pane-functions/screenshot-data-evidence.ts +383 -0
- package/src/cli/pane-functions/screenshot-evidence.ts +1 -1
- package/src/cli/pane-functions/screenshot-payload.ts +453 -0
- package/src/cli/pane-functions/screenshot-render.ts +316 -0
- package/src/cli/pane-functions/screenshot-types.ts +162 -0
- package/src/cli/pane-functions/shot-options.ts +8 -3
- package/src/cli/peso-dollar-rates.ts +79 -0
- package/src/cli/quote-answer.ts +38 -3
- package/src/cli/report-tables.ts +190 -27
- package/src/cli/restore-plugins.ts +29 -14
- package/src/cli/result.ts +122 -25
- package/src/cli/series-search.ts +5 -5
- package/src/cli/statement-filings.ts +19 -0
- package/src/cli/suffixed-listing-rows.ts +49 -0
- package/src/cli/symbol-args.ts +50 -15
- package/src/cli/symbol-cells.ts +120 -0
- package/src/cli/symbol-list.ts +226 -60
- package/src/cli/symbol-rows.ts +131 -25
- package/src/cli/ticker-resolution.ts +108 -11
- package/src/cli/treasury-yields.ts +5 -5
- package/src/cli/types.ts +2 -0
- package/src/cli/uncovered-instrument.ts +3 -3
- package/src/cli/unknown-command.ts +6 -6
- package/src/components/chart/composite/chart-layout.ts +131 -0
- package/src/components/chart/composite/composite-chart.tsx +387 -21
- package/src/components/chart/composite/drawing-store.ts +26 -6
- package/src/components/chart/composite/interactions.ts +9 -3
- package/src/components/chart/composite/legend.tsx +75 -3
- package/src/components/chart/composite/levels.ts +5 -0
- package/src/components/chart/composite/panel-gestures.ts +41 -0
- package/src/components/chart/composite/panel-labels.ts +2 -1
- package/src/components/chart/composite/panel-surface.tsx +316 -11
- package/src/components/chart/composite/price-axis-labels.tsx +27 -3
- package/src/components/chart/composite/rasterizer.ts +44 -19
- package/src/components/chart/composite/scene.ts +16 -4
- package/src/components/chart/composite/text-renderer.ts +48 -21
- package/src/components/chart/composite/toolbar.tsx +52 -5
- package/src/components/chart/composite/types.ts +19 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +120 -13
- package/src/components/chart/composite/use-composite-value-scales.ts +44 -0
- package/src/components/chart/composite/use-composite-viewport.ts +12 -3
- package/src/components/chart/composite/value-scale.ts +149 -0
- package/src/components/chart/composite/volume-profile-paint.ts +6 -3
- package/src/components/chart/core/pointer.ts +2 -0
- package/src/components/chart-table/index.ts +2 -0
- package/src/components/chart-table/read.tsx +74 -0
- package/src/components/command-bar/commands/collection-list.ts +41 -19
- package/src/components/command-bar/commands/collection.ts +9 -3
- package/src/components/command-bar/commands/direct/index.ts +1 -1
- package/src/components/command-bar/commands/direct/runtime.ts +3 -2
- package/src/components/command-bar/pane-templates/items.ts +59 -6
- package/src/components/command-bar/pane-templates/workflow.ts +10 -1
- package/src/components/command-bar/routes/root/command-items.ts +32 -6
- package/src/components/command-bar/routes/root/plugin-install.ts +1 -1
- package/src/components/command-bar/routes/root/provider-search.ts +42 -11
- package/src/components/command-bar/routes/root/results.ts +18 -4
- package/src/components/command-bar/routes/root/runtime.ts +15 -3
- package/src/components/command-bar/routes/root/search-providers.ts +25 -2
- package/src/components/command-bar/routes/root/selection.ts +6 -1
- package/src/components/command-bar/routes/root/shortcut-feedback.ts +4 -1
- package/src/components/command-bar/routes/root/shortcuts.ts +30 -3
- package/src/components/command-bar/routes/ticker-search/results.ts +73 -4
- package/src/components/command-bar/selection-runtime.ts +50 -10
- package/src/components/command-bar/surface/index.tsx +24 -2
- package/src/components/command-bar/view-model.ts +7 -0
- package/src/components/command-bar/workflow/broker.ts +2 -2
- package/src/components/command-bar/workflow/collection-actions.ts +46 -7
- package/src/components/command-bar/workflow/collection-ticker.ts +32 -4
- package/src/components/command-bar/workflow/fields.ts +8 -2
- package/src/components/command-bar/workflow/ops.ts +18 -4
- package/src/components/command-bar/workflow/portfolio-position.ts +53 -4
- package/src/components/command-bar/workflow/submit.ts +3 -2
- package/src/components/command-bar/workflow/tickers.ts +5 -1
- package/src/components/company-logo.tsx +39 -0
- package/src/components/data-table/sections.ts +4 -2
- package/src/components/data-table/view.tsx +23 -3
- package/src/components/form-modal/confirm.tsx +2 -1
- package/src/components/form-modal/request.ts +5 -0
- package/src/components/form-modal/routes.ts +4 -1
- package/src/components/index.ts +13 -6
- package/src/components/layout/detached-pane-shell.tsx +1 -0
- package/src/components/layout/floating-pane.tsx +1 -0
- package/src/components/layout/header.tsx +234 -60
- package/src/components/layout/market-summary.ts +9 -6
- package/src/components/layout/pane/capture-status.ts +1 -1
- package/src/components/layout/pane/footer/model.ts +11 -5
- package/src/components/layout/pane/header.tsx +10 -3
- package/src/components/layout/pane/index.tsx +1 -0
- package/src/components/layout/pane/pane-tabs.tsx +9 -1
- package/src/components/layout/shell/chrome.ts +19 -6
- package/src/components/layout/shell/index.tsx +2 -1
- package/src/components/layout/shell/pane/layers.tsx +19 -1
- package/src/components/layout/status-bar.tsx +2 -2
- package/src/components/market-board/index.tsx +11 -3
- package/src/components/onboarding/onboarding-wizard.tsx +1 -0
- package/src/components/onboarding/portfolio-step/broker-setup-panel.tsx +2 -2
- package/src/components/onboarding/wizard-broker-sync.ts +4 -1
- package/src/components/pane-settings-dialog/desktop.tsx +20 -7
- package/src/components/pane-settings-dialog/tui.tsx +17 -9
- package/src/components/pane-settings-dialog/value.ts +8 -1
- package/src/components/pane-settings-dialog.tsx +44 -13
- package/src/components/sign-in-code-panel.tsx +27 -19
- package/src/components/ui/column-grid.tsx +48 -4
- package/src/components/ui/country-flag-art.tsx +180 -0
- package/src/components/ui/country-flag.tsx +118 -12
- package/src/components/ui/data-table/opentui/index.tsx +61 -2
- package/src/components/ui/data-table/opentui/model.ts +109 -2
- package/src/components/ui/data-table/types.ts +1 -1
- package/src/components/ui/display.tsx +5 -1
- package/src/components/ui/index.ts +3 -1
- package/src/components/ui/logo-mark.tsx +11 -2
- package/src/components/ui/query-bar.tsx +1 -1
- package/src/components/ui/row-notes.ts +79 -0
- package/src/components/ui/stat-grid.tsx +28 -3
- package/src/components/ui/status.tsx +3 -1
- package/src/core/app-services.ts +4 -0
- package/src/core/state/app/layout.ts +4 -4
- package/src/data/cached-query.ts +8 -1
- package/src/data/config/chart-settings.ts +3 -3
- package/src/data/config/home.ts +10 -2
- package/src/data/config/layout.ts +4 -1
- package/src/data/config/store/node.ts +4 -0
- package/src/data/config/store/normalize.ts +13 -3
- package/src/data/config/store/portfolio-as-of-column.ts +2 -2
- package/src/data/json-storage.ts +2 -0
- package/src/data/saved-copies.ts +84 -0
- package/src/docs/snapshot.json +65 -32
- package/src/i18n/es.ts +51 -12
- package/src/i18n/ja.ts +51 -12
- package/src/i18n/ko.ts +51 -12
- package/src/i18n/zh-cn.ts +51 -12
- package/src/i18n/zh-tw.ts +51 -12
- package/src/layout/desks.ts +1 -1
- package/src/layout/launch-layout.ts +1 -1
- package/src/layout/pane-manager/floating-actions.ts +5 -3
- package/src/layout/pane-manager/floating.ts +43 -1
- package/src/layout/pane-manager/index.ts +2 -0
- package/src/layout/selected-ticker.ts +74 -0
- package/src/market-data/depositary-pair.ts +251 -0
- package/src/market-data/futures-board.ts +1 -1
- package/src/market-data/futures-contract.ts +124 -40
- package/src/market-data/fx-closes.ts +17 -2
- package/src/market-data/hooks.tsx +19 -11
- package/src/market-data/market/change-basis.ts +49 -7
- package/src/market-data/market/freshness.ts +34 -5
- package/src/market-data/market/period-return.ts +163 -0
- package/src/market-data/market/trading-sessions.ts +13 -0
- package/src/market-data/market/year-to-date.ts +8 -13
- package/src/market-data/performance.ts +20 -14
- package/src/market-data/perpetual-premium.ts +32 -0
- package/src/market-data/published-apac-sessions.ts +2 -1
- package/src/market-data/published-futures-sessions.ts +69 -0
- package/src/market-data/quotes/contributions.ts +5 -1
- package/src/market-data/quotes/freshness.ts +42 -0
- package/src/market-data/quotes/intraday-bar.ts +37 -0
- package/src/market-data/quotes/regular-session-valuation.ts +13 -8
- package/src/market-data/quotes/resolution.ts +4 -2
- package/src/market-data/tax-equivalent.ts +1 -1
- package/src/market-data/ticker-price-returns.ts +3 -1
- package/src/news/aggregator.ts +5 -1
- package/src/news/news-model.ts +4 -0
- package/src/news/types.ts +11 -0
- package/src/notifications/app-notifier.ts +1 -1
- package/src/plugins/auto-update.ts +2 -2
- package/src/plugins/builtin/account-management/index.tsx +1 -0
- package/src/plugins/builtin/account-management/pane.tsx +9 -8
- package/src/plugins/builtin/alerts/alert-engine.ts +30 -18
- package/src/plugins/builtin/alerts/calendar-events.ts +378 -0
- package/src/plugins/builtin/alerts/cli.ts +485 -76
- package/src/plugins/builtin/alerts/command.ts +5 -10
- package/src/plugins/builtin/alerts/event-rows.ts +9 -5
- package/src/plugins/builtin/alerts/events-pane.tsx +71 -56
- package/src/plugins/builtin/alerts/events.ts +108 -5
- package/src/plugins/builtin/alerts/fired-log.ts +78 -0
- package/src/plugins/builtin/alerts/format.ts +2 -1
- package/src/plugins/builtin/alerts/headless.ts +100 -39
- package/src/plugins/builtin/alerts/history-pane.tsx +105 -62
- package/src/plugins/builtin/alerts/history-rows.ts +89 -0
- package/src/plugins/builtin/alerts/index.tsx +45 -18
- package/src/plugins/builtin/alerts/iv-level.ts +365 -0
- package/src/plugins/builtin/alerts/levels.ts +3 -3
- package/src/plugins/builtin/alerts/pane.tsx +97 -29
- package/src/plugins/builtin/alerts/price.ts +35 -0
- package/src/plugins/builtin/alerts/quotes.ts +24 -0
- package/src/plugins/builtin/alerts/research-builder.ts +84 -11
- package/src/plugins/builtin/alerts/research-rules.ts +77 -3
- package/src/plugins/builtin/alerts/scheduled-check.ts +206 -32
- package/src/plugins/builtin/alerts/scheduled-sources.ts +26 -3
- package/src/plugins/builtin/alerts/scheduled.ts +188 -29
- package/src/plugins/builtin/alerts/storage.ts +13 -10
- package/src/plugins/builtin/alerts/targets.ts +12 -3
- package/src/plugins/builtin/analytics/index.tsx +8 -2
- package/src/plugins/builtin/analytics/metrics.ts +1 -1
- package/src/plugins/builtin/analytics/risk-book.ts +215 -0
- package/src/plugins/builtin/analytics/risk-client.ts +13 -6
- package/src/plugins/builtin/analytics/risk-headless.ts +55 -21
- package/src/plugins/builtin/analytics/risk-math.ts +6 -3
- package/src/plugins/builtin/analytics/risk-model.ts +297 -104
- package/src/plugins/builtin/analytics/risk-options.ts +17 -2
- package/src/plugins/builtin/analytics/risk-pane.tsx +107 -43
- package/src/plugins/builtin/attention/pane.tsx +22 -16
- package/src/plugins/builtin/awards/headless.ts +7 -3
- package/src/plugins/builtin/awards/model.ts +18 -0
- package/src/plugins/builtin/awards/pane.tsx +12 -9
- package/src/plugins/builtin/backtest/export.ts +1 -1
- package/src/plugins/builtin/backtest/headless.ts +3 -3
- package/src/plugins/builtin/backtest/rules.ts +41 -5
- package/src/plugins/builtin/broker-manager/add-flow.tsx +12 -7
- package/src/plugins/builtin/broker-manager/detail.tsx +47 -19
- package/src/plugins/builtin/broker-manager/footer.ts +27 -11
- package/src/plugins/builtin/broker-manager/index.tsx +21 -12
- package/src/plugins/builtin/broker-manager/keyboard.ts +1 -1
- package/src/plugins/builtin/broker-manager/model.ts +12 -2
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +183 -57
- package/src/plugins/builtin/broker-manager/table.ts +28 -12
- package/src/plugins/builtin/broker-trading/account-picker.tsx +2 -1
- package/src/plugins/builtin/broker-trading/chart-levels.ts +82 -13
- package/src/plugins/builtin/broker-trading/controller.ts +45 -13
- package/src/plugins/builtin/broker-trading/dock.tsx +24 -4
- package/src/plugins/builtin/broker-trading/headless.ts +2 -1
- package/src/plugins/builtin/broker-trading/index.tsx +7 -1
- package/src/plugins/builtin/broker-trading/linked-orders.ts +148 -0
- package/src/plugins/builtin/broker-trading/order-feed.ts +75 -7
- package/src/plugins/builtin/broker-trading/orders.tsx +109 -31
- package/src/plugins/builtin/broker-trading/overview-orders.tsx +9 -3
- package/src/plugins/builtin/broker-trading/pane-model.ts +30 -2
- package/src/plugins/builtin/broker-trading/pane.tsx +115 -26
- package/src/plugins/builtin/broker-trading/ticket-parts.tsx +36 -7
- package/src/plugins/builtin/broker-trading/ticket.tsx +122 -36
- package/src/plugins/builtin/buildout/cells.tsx +4 -0
- package/src/plugins/builtin/buildout/detail/index.tsx +5 -1
- package/src/plugins/builtin/buildout/detail/ui.tsx +7 -4
- package/src/plugins/builtin/buildout/pane/index.tsx +34 -18
- package/src/plugins/builtin/buildout/pane/status.tsx +0 -4
- package/src/plugins/builtin/builtin-plugin-meta.ts +1 -1
- package/src/plugins/builtin/catalysts/client.ts +2 -2
- package/src/plugins/builtin/catalysts/detail.tsx +3 -2
- package/src/plugins/builtin/catalysts/index.tsx +1 -1
- package/src/plugins/builtin/catalysts/model.ts +49 -4
- package/src/plugins/builtin/catalysts/pane.tsx +9 -11
- package/src/plugins/builtin/central-bank-rates/client.ts +5 -0
- package/src/plugins/builtin/central-bank-rates/headless.ts +36 -10
- package/src/plugins/builtin/central-bank-rates/model.ts +33 -8
- package/src/plugins/builtin/changelog/index.tsx +1 -0
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +1 -0
- package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +2 -1
- package/src/plugins/builtin/chart-composer/chart-spec.ts +7 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +2 -5
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
- package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +4 -1
- package/src/plugins/builtin/chart-composer/headless-schema.ts +28 -26
- package/src/plugins/builtin/chart-composer/headless.ts +253 -35
- package/src/plugins/builtin/chart-composer/index.tsx +12 -2
- package/src/plugins/builtin/chart-composer/leverage-overlay.ts +142 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +128 -28
- package/src/plugins/builtin/chart-composer/series-catalog.ts +1 -0
- package/src/plugins/builtin/chart-composer/series-expression.ts +65 -6
- package/src/plugins/builtin/chart-composer/session-history.ts +15 -21
- package/src/plugins/builtin/chart-composer/session-range.ts +4 -2
- package/src/plugins/builtin/chart-composer/settings.ts +31 -19
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +5 -6
- package/src/plugins/builtin/chart-composer/viewport-labels.ts +18 -8
- package/src/plugins/builtin/chat/attachments/model.ts +3 -1
- package/src/plugins/builtin/chat/content/index.tsx +1 -1
- package/src/plugins/builtin/chat/pane-links/open.ts +1 -1
- package/src/plugins/builtin/chat/people.ts +1 -1
- package/src/plugins/builtin/clinical-trials/client.ts +5 -2
- package/src/plugins/builtin/clinical-trials/model.ts +13 -4
- package/src/plugins/builtin/clinical-trials/pane.tsx +32 -12
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +17 -0
- package/src/plugins/builtin/cloud/askg/controller.ts +1 -1
- package/src/plugins/builtin/cloud/askg/lock-note.ts +1 -1
- package/src/plugins/builtin/cloud/askg/tool-display.ts +1 -1
- package/src/plugins/builtin/cloud/auth-commands.ts +3 -3
- package/src/plugins/builtin/cloud/team/pane-sections.tsx +105 -47
- package/src/plugins/builtin/cloud/team/pane-ui.tsx +42 -4
- package/src/plugins/builtin/cloud/team/pane.tsx +204 -64
- package/src/plugins/builtin/cloud/thesis/store.ts +45 -22
- package/src/plugins/builtin/cloud-tweets/registration.ts +1 -0
- package/src/plugins/builtin/commodities-energy/index.tsx +29 -2
- package/src/plugins/builtin/commodities-energy/view-state.ts +2 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +14 -8
- package/src/plugins/builtin/company-attention/app-rank.tsx +11 -10
- package/src/plugins/builtin/company-attention/building-model.ts +222 -0
- package/src/plugins/builtin/company-attention/building.tsx +172 -0
- package/src/plugins/builtin/company-attention/client.ts +117 -1
- package/src/plugins/builtin/company-attention/evidence.ts +16 -3
- package/src/plugins/builtin/company-attention/headless.ts +101 -5
- package/src/plugins/builtin/company-attention/index.tsx +6 -3
- package/src/plugins/builtin/company-attention/model.ts +33 -7
- package/src/plugins/builtin/company-attention/moves-model.ts +253 -0
- package/src/plugins/builtin/company-attention/moves.tsx +159 -0
- package/src/plugins/builtin/company-attention/pane.tsx +89 -31
- package/src/plugins/builtin/company-attention/signals-model.ts +229 -0
- package/src/plugins/builtin/company-attention/signals.tsx +160 -0
- package/src/plugins/builtin/company-kpis/headless.ts +3 -2
- package/src/plugins/builtin/company-kpis/model.ts +2 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +5 -4
- package/src/plugins/builtin/composite-plugins.ts +2 -1
- package/src/plugins/builtin/congress-trades/headless.ts +1 -1
- package/src/plugins/builtin/connections/index.ts +1 -0
- package/src/plugins/builtin/correlation/headless-schema.ts +3 -2
- package/src/plugins/builtin/correlation/headless.ts +7 -4
- package/src/plugins/builtin/correlation/relationship/model.ts +1 -0
- package/src/plugins/builtin/correlation/settings.ts +1 -0
- package/src/plugins/builtin/cot/headless.ts +28 -6
- package/src/plugins/builtin/cot/model.ts +12 -0
- package/src/plugins/builtin/cot/pane.tsx +7 -5
- package/src/plugins/builtin/cpi/headless.ts +5 -1
- package/src/plugins/builtin/cpi/pane.tsx +11 -9
- package/src/plugins/builtin/credit-boards/model.ts +2 -0
- package/src/plugins/builtin/credit-documents/headless.ts +1 -1
- package/src/plugins/builtin/credit-documents/pane.tsx +8 -7
- package/src/plugins/builtin/crypto-board/client.ts +86 -0
- package/src/plugins/builtin/crypto-board/defi-view.tsx +218 -0
- package/src/plugins/builtin/crypto-board/defi.ts +239 -0
- package/src/plugins/builtin/crypto-board/headless.ts +117 -6
- package/src/plugins/builtin/crypto-board/index.tsx +8 -3
- package/src/plugins/builtin/crypto-board/model.ts +40 -8
- package/src/plugins/builtin/crypto-board/pane.tsx +55 -21
- package/src/plugins/builtin/crypto-board/series.ts +19 -1
- package/src/plugins/builtin/crypto-etf-flows/client.ts +60 -0
- package/src/plugins/builtin/crypto-etf-flows/headless.ts +132 -0
- package/src/plugins/builtin/crypto-etf-flows/index.tsx +44 -0
- package/src/plugins/builtin/crypto-etf-flows/model.ts +121 -0
- package/src/plugins/builtin/crypto-etf-flows/pane.tsx +193 -0
- package/src/plugins/builtin/debt-maturities/pane.tsx +4 -3
- package/src/plugins/builtin/debug/index.tsx +1 -0
- package/src/plugins/builtin/dividend-yield/index.tsx +1 -1
- package/src/plugins/builtin/docs/index.tsx +1 -1
- package/src/plugins/builtin/docs/pane.tsx +2 -2
- package/src/plugins/builtin/docs/targets.ts +1 -1
- package/src/plugins/builtin/doe/client.ts +5 -1
- package/src/plugins/builtin/doe/headless.ts +12 -6
- package/src/plugins/builtin/doe/model.ts +43 -6
- package/src/plugins/builtin/doe/pane.tsx +19 -12
- package/src/plugins/builtin/earnings/board-model.ts +13 -6
- package/src/plugins/builtin/earnings/board-table.ts +4 -1
- package/src/plugins/builtin/earnings/board.tsx +253 -50
- package/src/plugins/builtin/earnings/calendar-model.ts +188 -0
- package/src/plugins/builtin/earnings/calendar.tsx +338 -0
- package/src/plugins/builtin/earnings/client.ts +4 -3
- package/src/plugins/builtin/earnings/headless.ts +53 -15
- package/src/plugins/builtin/earnings/history-model.ts +53 -36
- package/src/plugins/builtin/earnings/index.tsx +15 -22
- package/src/plugins/builtin/earnings/model.ts +17 -12
- package/src/plugins/builtin/earnings/scope.ts +89 -0
- package/src/plugins/builtin/earnings-calls/calendar.tsx +310 -0
- package/src/plugins/builtin/earnings-calls/data.ts +146 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +151 -9
- package/src/plugins/builtin/earnings-calls/index.tsx +3 -3
- package/src/plugins/builtin/earnings-calls/lock-note.ts +1 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +149 -31
- package/src/plugins/builtin/earnings-calls/upcoming.ts +195 -0
- package/src/plugins/builtin/earnings-ripple/pane.tsx +10 -5
- package/src/plugins/builtin/econ/calendar-model.ts +132 -20
- package/src/plugins/builtin/econ/detail-view.tsx +48 -10
- package/src/plugins/builtin/econ/fred-catalog.ts +23 -6
- package/src/plugins/builtin/econ/fred-series-map.ts +4 -0
- package/src/plugins/builtin/econ/index.tsx +112 -25
- package/src/plugins/builtin/econ/print-watch.ts +2 -1
- package/src/plugins/builtin/econ/types.ts +32 -1
- package/src/plugins/builtin/econ-statistics/client.ts +16 -3
- package/src/plugins/builtin/econ-statistics/defs.ts +4 -0
- package/src/plugins/builtin/econ-statistics/detail.tsx +2 -2
- package/src/plugins/builtin/econ-statistics/headless.ts +13 -2
- package/src/plugins/builtin/econ-statistics/index.tsx +3 -3
- package/src/plugins/builtin/econ-statistics/pane.tsx +2 -1
- package/src/plugins/builtin/econ-statistics/stats.ts +103 -0
- package/src/plugins/builtin/econ-statistics/view.ts +16 -1
- package/src/plugins/builtin/equity-screener/client.ts +21 -3
- package/src/plugins/builtin/equity-screener/headless.ts +77 -5
- package/src/plugins/builtin/equity-screener/live.ts +1 -1
- package/src/plugins/builtin/equity-screener/model.ts +34 -0
- package/src/plugins/builtin/equity-screener/pane.tsx +148 -106
- package/src/plugins/builtin/equity-screener/screen-check.ts +90 -0
- package/src/plugins/builtin/equity-screener/ticker-check.tsx +148 -0
- package/src/plugins/builtin/estimate-revisions/headless.ts +3 -1
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +3 -3
- package/src/plugins/builtin/estimate-revisions/model.ts +8 -10
- package/src/plugins/builtin/exposure/headless.ts +51 -6
- package/src/plugins/builtin/exposure/index.tsx +28 -2
- package/src/plugins/builtin/exposure/model.ts +7 -0
- package/src/plugins/builtin/exposure/pane.tsx +16 -10
- package/src/plugins/builtin/fear-greed/index.tsx +1 -1
- package/src/plugins/builtin/filing-events/index.tsx +1 -1
- package/src/plugins/builtin/futures/model.ts +10 -9
- package/src/plugins/builtin/futures/returns.ts +8 -36
- package/src/plugins/builtin/futures/table.tsx +1 -1
- package/src/plugins/builtin/futures-curve/client.ts +40 -9
- package/src/plugins/builtin/futures-curve/headless.ts +39 -17
- package/src/plugins/builtin/futures-curve/model.ts +258 -69
- package/src/plugins/builtin/futures-curve/pane.tsx +46 -17
- package/src/plugins/builtin/fx-matrix/client.ts +15 -8
- package/src/plugins/builtin/fx-matrix/headless.ts +32 -14
- package/src/plugins/builtin/fx-matrix/index.tsx +13 -11
- package/src/plugins/builtin/fx-matrix/live-legs.ts +22 -0
- package/src/plugins/builtin/fx-matrix/settings.ts +2 -1
- package/src/plugins/builtin/gpu/board.tsx +4 -1
- package/src/plugins/builtin/gpu/changes.tsx +10 -10
- package/src/plugins/builtin/gpu/equities.tsx +4 -1
- package/src/plugins/builtin/gpu/headless.ts +9 -4
- package/src/plugins/builtin/gpu/history.tsx +4 -2
- package/src/plugins/builtin/gpu/pane.tsx +5 -3
- package/src/plugins/builtin/help/index.tsx +13 -3
- package/src/plugins/builtin/holders/beneficial-model.ts +8 -11
- package/src/plugins/builtin/holders/beneficial-report.ts +6 -5
- package/src/plugins/builtin/holders/beneficial-view.tsx +7 -3
- package/src/plugins/builtin/holders/client.ts +15 -2
- package/src/plugins/builtin/holders/format.ts +28 -17
- package/src/plugins/builtin/holders/headless.ts +48 -8
- package/src/plugins/builtin/holders/index-holders.ts +264 -0
- package/src/plugins/builtin/holders/pane.tsx +79 -15
- package/src/plugins/builtin/holders/report-rows.ts +35 -7
- package/src/plugins/builtin/holders/table-model.ts +2 -1
- package/src/plugins/builtin/holders/thirteenf-match.ts +3 -1
- package/src/plugins/builtin/holders/treemap.tsx +2 -2
- package/src/plugins/builtin/insider/base-values.ts +11 -7
- package/src/plugins/builtin/insider/client.ts +3 -0
- package/src/plugins/builtin/insider/headless.ts +20 -7
- package/src/plugins/builtin/insider/index.tsx +17 -8
- package/src/plugins/builtin/insider/issuer.ts +34 -4
- package/src/plugins/builtin/insider/signals-client.ts +3 -3
- package/src/plugins/builtin/insider/signals.tsx +1 -1
- package/src/plugins/builtin/insider/table-model.ts +35 -13
- package/src/plugins/builtin/insider/table.tsx +5 -2
- package/src/plugins/builtin/ipo-calendar/pane.tsx +7 -4
- package/src/plugins/builtin/iv-history/client.ts +20 -0
- package/src/plugins/builtin/iv-history/events.ts +145 -0
- package/src/plugins/builtin/iv-history/format.ts +3 -0
- package/src/plugins/builtin/iv-history/headless.ts +91 -7
- package/src/plugins/builtin/iv-history/index.tsx +1 -1
- package/src/plugins/builtin/iv-history/model.ts +13 -4
- package/src/plugins/builtin/iv-history/series.ts +3 -1
- package/src/plugins/builtin/jobs/lock-note.ts +1 -1
- package/src/plugins/builtin/jobs/model.ts +4 -15
- package/src/plugins/builtin/jobs/pane.tsx +7 -5
- package/src/plugins/builtin/jobs/share-bars.tsx +11 -5
- package/src/plugins/builtin/jodi/pane.tsx +14 -8
- package/src/plugins/builtin/layout-manager/index.tsx +1 -0
- package/src/plugins/builtin/macro-day/headless.ts +20 -7
- package/src/plugins/builtin/macro-day/index.tsx +3 -2
- package/src/plugins/builtin/macro-day/model.ts +29 -8
- package/src/plugins/builtin/macro-day/pane.tsx +46 -10
- package/src/plugins/builtin/macro-day/releases.ts +55 -13
- package/src/plugins/builtin/market-heatmap/data.ts +9 -2
- package/src/plugins/builtin/market-heatmap/evidence.ts +1 -1
- package/src/plugins/builtin/market-heatmap/headless.ts +9 -6
- package/src/plugins/builtin/market-heatmap/index.tsx +3 -3
- package/src/plugins/builtin/market-heatmap/model.ts +10 -7
- package/src/plugins/builtin/market-movers/client.ts +36 -21
- package/src/plugins/builtin/market-movers/headless.ts +8 -2
- package/src/plugins/builtin/market-movers/index.tsx +16 -10
- package/src/plugins/builtin/market-movers/screener.ts +33 -11
- package/src/plugins/builtin/market-movers/session-body.tsx +4 -2
- package/src/plugins/builtin/market-movers/session-table.ts +4 -2
- package/src/plugins/builtin/market-movers/table.ts +4 -1
- package/src/plugins/builtin/market-valuation/headless.ts +2 -0
- package/src/plugins/builtin/market-valuation/view.ts +5 -0
- package/src/plugins/builtin/members/headless.ts +6 -5
- package/src/plugins/builtin/members/pane.tsx +11 -4
- package/src/plugins/builtin/mna/client.ts +1 -0
- package/src/plugins/builtin/mna/detail.tsx +20 -32
- package/src/plugins/builtin/mna/distress/detail.tsx +16 -4
- package/src/plugins/builtin/mna/distress/model.ts +3 -2
- package/src/plugins/builtin/mna/distress/pane.tsx +7 -4
- package/src/plugins/builtin/mna/headless.ts +6 -13
- package/src/plugins/builtin/mna/model.ts +93 -15
- package/src/plugins/builtin/mna/pane.tsx +5 -4
- package/src/plugins/builtin/news/headless.ts +12 -6
- package/src/plugins/builtin/news/index.tsx +10 -1
- package/src/plugins/builtin/news/wire/index.tsx +3 -2
- package/src/plugins/builtin/news/wire/news/command-bar-search.ts +26 -10
- package/src/plugins/builtin/news/wire/news/query-presets.ts +6 -2
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -3
- package/src/plugins/builtin/notes/cli.ts +50 -15
- package/src/plugins/builtin/notes/export.ts +17 -6
- package/src/plugins/builtin/notes/files.ts +16 -5
- package/src/plugins/builtin/openfda/pane.tsx +1 -1
- package/src/plugins/builtin/options/carry.ts +2 -2
- package/src/plugins/builtin/options/enrichment-model.ts +10 -9
- package/src/plugins/builtin/options/index.tsx +1 -1
- package/src/plugins/builtin/options/model-spot.ts +70 -4
- package/src/plugins/builtin/options/view.tsx +51 -23
- package/src/plugins/builtin/options-calculator/headless.ts +46 -7
- package/src/plugins/builtin/options-calculator/pane.tsx +3 -2
- package/src/plugins/builtin/options-positioning/headless.ts +11 -3
- package/src/plugins/builtin/options-positioning/model.ts +24 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +10 -12
- package/src/plugins/builtin/options-scenario/client.ts +142 -29
- package/src/plugins/builtin/options-scenario/collar.ts +8 -6
- package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +127 -0
- package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +165 -0
- package/src/plugins/builtin/options-scenario/csp-screen.ts +258 -0
- package/src/plugins/builtin/options-scenario/csp.ts +101 -21
- package/src/plugins/builtin/options-scenario/headless.ts +49 -21
- package/src/plugins/builtin/options-scenario/hedge.ts +20 -14
- package/src/plugins/builtin/options-scenario/index.tsx +28 -3
- package/src/plugins/builtin/options-scenario/model.ts +13 -0
- package/src/plugins/builtin/options-scenario/pane.tsx +22 -10
- package/src/plugins/builtin/perps/equity-row.tsx +11 -3
- package/src/plugins/builtin/perps/freshness.ts +8 -5
- package/src/plugins/builtin/perps/headless.ts +3 -1
- package/src/plugins/builtin/perps/list-perpetuals.ts +25 -4
- package/src/plugins/builtin/perps/live-rows.ts +161 -0
- package/src/plugins/builtin/perps/markets-section.tsx +154 -0
- package/src/plugins/builtin/perps/pane.tsx +36 -17
- package/src/plugins/builtin/perps/venue-markets.ts +96 -0
- package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -1
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +145 -60
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +78 -27
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +158 -141
- package/src/plugins/builtin/portfolio-list/cli/render.ts +49 -20
- package/src/plugins/builtin/portfolio-list/cli/shared.ts +3 -3
- package/src/plugins/builtin/portfolio-list/cli/watchlist-command.ts +29 -24
- package/src/plugins/builtin/portfolio-list/headless.ts +6 -3
- package/src/plugins/builtin/portfolio-list/income.ts +13 -2
- package/src/plugins/builtin/portfolio-list/index.tsx +9 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +21 -8
- package/src/plugins/builtin/portfolio-list/pane/purchase-date-fx.ts +46 -0
- package/src/plugins/builtin/portfolio-list/summary/live-accounts.ts +16 -9
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +2 -15
- package/src/plugins/builtin/power/client.ts +6 -3
- package/src/plugins/builtin/power/evidence.ts +22 -7
- package/src/plugins/builtin/power/gas.tsx +5 -1
- package/src/plugins/builtin/power/pane.tsx +44 -32
- package/src/plugins/builtin/power/prices.tsx +10 -3
- package/src/plugins/builtin/research/analyst-headless.ts +25 -8
- package/src/plugins/builtin/research/analyst-model.ts +68 -3
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +63 -14
- package/src/plugins/builtin/research/event-model.ts +3 -1
- package/src/plugins/builtin/research/event-sources.ts +12 -4
- package/src/plugins/builtin/research/index.tsx +2 -1
- package/src/plugins/builtin/research/relative-valuation-basis.ts +58 -0
- package/src/plugins/builtin/research/relative-valuation-columns.ts +31 -2
- package/src/plugins/builtin/research/relative-valuation-currency.ts +12 -5
- package/src/plugins/builtin/research/relative-valuation-headless.ts +84 -26
- package/src/plugins/builtin/research/relative-valuation-model.ts +88 -19
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +76 -9
- package/src/plugins/builtin/research/relative-valuation-peers.ts +1 -1
- package/src/plugins/builtin/reverse-dcf/client.ts +2 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +10 -5
- package/src/plugins/builtin/risk-factors/index.tsx +2 -2
- package/src/plugins/builtin/scanner/lock-note.ts +1 -1
- package/src/plugins/builtin/seasonality/client.ts +2 -1
- package/src/plugins/builtin/sec/forms.ts +13 -0
- package/src/plugins/builtin/sec/fund-abroad.ts +32 -0
- package/src/plugins/builtin/sec/fund-facts.ts +68 -8
- package/src/plugins/builtin/sec/headless.ts +85 -8
- package/src/plugins/builtin/sec/index.tsx +58 -12
- package/src/plugins/builtin/sectors/client.ts +6 -4
- package/src/plugins/builtin/sectors/sector-model.ts +11 -15
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/broker-reconnect.ts +107 -22
- package/src/plugins/builtin/shared/cloud-resource.ts +15 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +34 -6
- package/src/plugins/builtin/shared/earnings-calendar.ts +375 -0
- package/src/plugins/builtin/{earnings/estimate-basis.ts → shared/earnings-estimate-basis.ts} +1 -1
- package/src/plugins/builtin/shared/earnings-report.ts +3 -3
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +23 -1
- package/src/plugins/builtin/shared/job-posting.ts +129 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +3 -3
- package/src/plugins/builtin/shared/report-freshness.ts +3 -0
- package/src/plugins/builtin/shared/research-trading.tsx +23 -0
- package/src/plugins/builtin/shared/series-cache.ts +2 -0
- package/src/plugins/builtin/shared/series-list-detail.tsx +3 -1
- package/src/plugins/builtin/shared/ticker-surface.ts +2 -1
- package/src/plugins/builtin/shared/volatility/options.ts +2 -1
- package/src/plugins/builtin/short-interest/client.ts +2 -0
- package/src/plugins/builtin/short-interest/headless.ts +41 -12
- package/src/plugins/builtin/short-interest/index.tsx +23 -1
- package/src/plugins/builtin/short-interest/model.ts +70 -19
- package/src/plugins/builtin/short-interest/pane.tsx +19 -2
- package/src/plugins/builtin/short-interest/publications-client.ts +82 -0
- package/src/plugins/builtin/short-interest/publications-headless.ts +29 -0
- package/src/plugins/builtin/short-interest/publications-model.ts +50 -0
- package/src/plugins/builtin/short-interest/publications-pane.tsx +66 -0
- package/src/plugins/builtin/short-interest/schedule.ts +35 -2
- package/src/plugins/builtin/short-interest/surface.tsx +11 -4
- package/src/plugins/builtin/short-interest/types.ts +9 -0
- package/src/plugins/builtin/short-interest/watch-client.ts +14 -8
- package/src/plugins/builtin/short-interest/watch-model.ts +12 -25
- package/src/plugins/builtin/short-interest/watch-pane.tsx +5 -1
- package/src/plugins/builtin/social-mentions/client.ts +2 -1
- package/src/plugins/builtin/supply-chain/flow.tsx +33 -9
- package/src/plugins/builtin/supply-chain/model.ts +51 -3
- package/src/plugins/builtin/supply-chain/pane.tsx +78 -39
- package/src/plugins/builtin/supply-chain/row-evidence.tsx +1 -1
- package/src/plugins/builtin/themes/headless.ts +8 -5
- package/src/plugins/builtin/themes/model.ts +14 -4
- package/src/plugins/builtin/themes/pane.tsx +24 -5
- package/src/plugins/builtin/thirteenf/headless.ts +5 -5
- package/src/plugins/builtin/thirteenf/signals.ts +3 -1
- package/src/plugins/builtin/ticker-detail/adr-pair/headless.ts +123 -0
- package/src/plugins/builtin/ticker-detail/adr-pair/model.ts +132 -0
- package/src/plugins/builtin/ticker-detail/adr-pair/tab.tsx +87 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +40 -26
- package/src/plugins/builtin/ticker-detail/data-panes/history-bars.ts +8 -4
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +67 -3
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +112 -5
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +17 -2
- package/src/plugins/builtin/ticker-detail/financials/aggregation.ts +21 -7
- package/src/plugins/builtin/ticker-detail/financials/model.ts +36 -1
- package/src/plugins/builtin/ticker-detail/financials/offerings.ts +56 -0
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +62 -13
- package/src/plugins/builtin/ticker-detail/financials/tab.tsx +30 -4
- package/src/plugins/builtin/ticker-detail/headless-schema.ts +14 -2
- package/src/plugins/builtin/ticker-detail/headless.ts +148 -35
- package/src/plugins/builtin/ticker-detail/index.tsx +37 -5
- package/src/plugins/builtin/ticker-detail/overview/model.ts +65 -10
- package/src/plugins/builtin/ticker-detail/overview/pane.tsx +9 -1
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +68 -11
- package/src/plugins/builtin/ticker-detail/pane.tsx +11 -0
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +8 -3
- package/src/plugins/builtin/ticker-detail/research-tabs.ts +11 -0
- package/src/plugins/builtin/vol-surface/client.ts +28 -7
- package/src/plugins/builtin/vol-surface/headless.ts +43 -15
- package/src/plugins/builtin/vol-surface/model.ts +34 -12
- package/src/plugins/builtin/vol-surface/pane.tsx +33 -13
- package/src/plugins/builtin/vol-surface/stored.ts +34 -3
- package/src/plugins/builtin/vol-surface/tables.ts +14 -0
- package/src/plugins/builtin/volatility/model.ts +5 -10
- package/src/plugins/builtin/world-venue-map/basemap-layer.tsx +4 -3
- package/src/plugins/builtin/world-venue-map/basemap.ts +3 -1
- package/src/plugins/builtin/world-venue-map/chokepoint-chart.ts +40 -0
- package/src/plugins/builtin/world-venue-map/evidence.ts +1 -1
- package/src/plugins/builtin/world-venue-map/geo-series.ts +90 -11
- package/src/plugins/builtin/world-venue-map/headless.ts +1 -1
- package/src/plugins/builtin/world-venue-map/index.ts +3 -26
- package/src/plugins/builtin/world-venue-map/layers.ts +1 -1
- package/src/plugins/builtin/world-venue-map/map.tsx +2 -1
- package/src/plugins/builtin/world-venue-map/pane.tsx +4 -2
- package/src/plugins/builtin/world-venue-map/venue-header.tsx +15 -4
- package/src/plugins/builtin/yield-curve/client.ts +25 -0
- package/src/plugins/builtin/yield-curve/curves.ts +7 -4
- package/src/plugins/builtin/yield-curve/headless.ts +38 -4
- package/src/plugins/builtin/yield-curve/history.ts +4 -3
- package/src/plugins/builtin/yield-curve/index.tsx +12 -6
- package/src/plugins/builtin/yield-curve/swaps-model.ts +93 -0
- package/src/plugins/builtin/yield-curve/swaps.tsx +99 -0
- package/src/plugins/bundle.ts +1 -1
- package/src/plugins/catalog-browser.ts +2 -1
- package/src/plugins/compat.ts +3 -3
- package/src/plugins/dependencies.ts +75 -14
- package/src/plugins/extracted-plugins.ts +132 -34
- package/src/plugins/host-link.ts +149 -34
- package/src/plugins/installer.ts +35 -10
- package/src/plugins/loader.ts +206 -38
- package/src/plugins/manager-node.ts +2 -2
- package/src/plugins/marketplace/deactivation.ts +22 -0
- package/src/plugins/marketplace/model.ts +2 -2
- package/src/plugins/marketplace/packs.ts +1 -1
- package/src/plugins/ownership.ts +1 -1
- package/src/plugins/peer-retry.ts +155 -0
- package/src/plugins/registry/contributions.ts +3 -1
- package/src/plugins/registry/index.ts +4 -3
- package/src/plugins/registry/plugin-state.ts +3 -0
- package/src/plugins/seed-notice.ts +20 -0
- package/src/plugins/shortcut-argument.ts +39 -0
- package/src/portfolio/allocation.ts +1 -1
- package/src/portfolio/collection-name.ts +31 -0
- package/src/portfolio/column-values.ts +10 -8
- package/src/portfolio/cost-fx.ts +296 -19
- package/src/portfolio/manual-lots.ts +75 -0
- package/src/portfolio/mutations.ts +60 -22
- package/src/portfolio/suffixed-listings.ts +209 -0
- package/src/portfolio/totals-currency.ts +24 -0
- package/src/public/__snapshots__/public-api.test.ts.snap +14 -0
- package/src/public/time-series.ts +11 -8
- package/src/remote/schema.ts +1 -1
- package/src/renderers/browser/config-host.ts +3 -0
- package/src/renderers/browser/main.tsx +23 -3
- package/src/renderers/dom/boot-frame-page.ts +187 -0
- package/src/renderers/dom/boot-frame.ts +237 -0
- package/src/renderers/dom/build-assets.ts +46 -8
- package/src/renderers/dom/desktop/controls.tsx +1 -1
- package/src/renderers/dom/desktop/query-bar.tsx +2 -0
- package/src/renderers/dom/dom-ui-host.tsx +4 -29
- package/src/renderers/dom/host/ascii-text.ts +16 -2
- package/src/renderers/dom/host/box.tsx +43 -2
- package/src/renderers/dom/host/chart-surface.tsx +2 -0
- package/src/renderers/dom/host/hls.chunk.ts +3 -0
- package/src/renderers/dom/host/image-surface.tsx +49 -0
- package/src/renderers/dom/host/media-surface.tsx +24 -18
- package/src/renderers/dom/host/mouse.ts +30 -0
- package/src/renderers/dom/host/size-observer.ts +1 -1
- package/src/renderers/dom/host/style.ts +1 -1
- package/src/renderers/dom/host/tabs.tsx +16 -5
- package/src/renderers/dom/host/top-surface.ts +127 -0
- package/src/renderers/dom/styles.css +115 -0
- package/src/renderers/dom/test-utils.tsx +2 -1
- package/src/renderers/electrobun/bun/application-menu/index.ts +3 -3
- package/src/renderers/electrobun/bun/core-capabilities.ts +9 -1
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +3 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +56 -3
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +1 -1
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +9 -3
- package/src/renderers/electrobun/bun/desktop/initialization.ts +81 -18
- package/src/renderers/electrobun/bun/desktop/update.ts +210 -30
- package/src/renderers/electrobun/bun/external-plugins.ts +29 -7
- package/src/renderers/electrobun/bun/index.ts +11 -1
- package/src/renderers/electrobun/bun/window/boot-frame.ts +67 -0
- package/src/renderers/electrobun/bun/window/create.ts +4 -1
- package/src/renderers/electrobun/shared/boot-frame.ts +7 -0
- package/src/renderers/electrobun/shared/protocol.ts +6 -1
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +1 -1
- package/src/renderers/electrobun/view/config-host.ts +3 -0
- package/src/renderers/electrobun/view/fatal-screen.tsx +1 -1
- package/src/renderers/electrobun/view/main.tsx +30 -4
- package/src/renderers/electrobun/view/news-source.ts +9 -7
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +9 -2
- package/src/renderers/electrobun/view/ui-host.tsx +1 -1
- package/src/renderers/opentui/browser-open.ts +88 -0
- package/src/renderers/opentui/host.tsx +5 -14
- package/src/renderers/opentui/image/loader.ts +3 -2
- package/src/renderers/opentui/native-smoke.ts +10 -1
- package/src/renderers/opentui/pane-image/index.ts +2 -1
- package/src/renderers/opentui/render-lib.ts +253 -0
- package/src/renderers/opentui/render-timing-smoke.ts +1 -1
- package/src/renderers/opentui/start.tsx +8 -1
- package/src/renderers/share/layout-view.tsx +2 -2
- package/src/renderers/share/main.tsx +4 -4
- package/src/renderers/share/view.tsx +1 -1
- package/src/series/resolve.ts +22 -6
- package/src/series/standalone.ts +30 -2
- package/src/series/types.ts +8 -1
- package/src/shares/api.ts +4 -1
- package/src/shares/pane.ts +1 -1
- package/src/sources/gloomberb-cloud/fred-series.ts +46 -1
- package/src/sources/gloomberb-cloud/index.ts +39 -16
- package/src/sources/gloomberb-cloud/news.ts +39 -0
- package/src/sources/gloomberb-cloud/normalizers.ts +14 -1
- package/src/sources/gloomberb-cloud/perpetuals.ts +49 -1
- package/src/sources/listing-symbols.ts +37 -0
- package/src/sources/provider-errors.ts +16 -2
- package/src/sources/provider-router/financial-routes.ts +21 -3
- package/src/sources/provider-router/financials.ts +15 -2
- package/src/sources/provider-router/history.ts +30 -8
- package/src/sources/provider-router/index.ts +16 -0
- package/src/sources/provider-router/primary.ts +1 -1
- package/src/state/app/bootstrap.ts +23 -0
- package/src/state/hooks/inline-tickers.ts +30 -5
- package/src/sync/controller.ts +1 -1
- package/src/sync/core-contributors.ts +46 -14
- package/src/team/collections-sync.ts +2 -1
- package/src/team/collections.ts +178 -17
- package/src/telemetry/crash-reports.ts +21 -4
- package/src/telemetry/update-failures-node.ts +59 -0
- package/src/telemetry/update-failures.ts +307 -0
- package/src/test-support/builtin-panes.ts +1 -1
- package/src/theme/colors.ts +3 -30
- package/src/theme/ui-tokens.ts +37 -1
- package/src/tickers/company-name-listing.ts +215 -36
- package/src/tickers/company-name.ts +43 -0
- package/src/tickers/index-roots.ts +47 -0
- package/src/tickers/instrument-label.ts +5 -1
- package/src/tickers/listing-fate.ts +30 -0
- package/src/tickers/metadata.ts +10 -0
- package/src/tickers/perpetual-listing.ts +5 -2
- package/src/tickers/search/home-listing.ts +75 -0
- package/src/tickers/search/index.ts +76 -27
- package/src/tickers/search/ranking.ts +54 -4
- package/src/tickers/search/upsert.ts +29 -0
- package/src/tickers/terminal-codes.ts +416 -40
- package/src/time-series/bar-aggregation.ts +243 -0
- package/src/time-series/base-series.ts +307 -0
- package/src/time-series/chart-bounds.ts +349 -0
- package/src/time-series/chart-seed.ts +142 -0
- package/src/time-series/economic.ts +5 -0
- package/src/time-series/fundamentals.ts +18 -3
- package/src/time-series/history-acquisition.ts +499 -0
- package/src/time-series/leverage.ts +100 -0
- package/src/time-series/market-resolution.ts +4 -2
- package/src/time-series/market.ts +2 -1
- package/src/time-series/price-comparison.ts +175 -30
- package/src/time-series/range.ts +9 -1
- package/src/time-series/resolution.ts +174 -16
- package/src/time-series/resolve-context.ts +157 -0
- package/src/time-series/resolve.ts +126 -1577
- package/src/time-series/series-name.ts +38 -0
- package/src/time-series/series-presentation.ts +208 -0
- package/src/time-series/spec.ts +11 -6
- package/src/time-series/studies.ts +109 -12
- package/src/time-series/tail-reconcile.ts +121 -0
- package/src/time-series/types.ts +29 -2
- package/src/time-series/use-chart-resolution.ts +4 -5
- package/src/types/broker.ts +10 -0
- package/src/types/config.ts +14 -0
- package/src/types/data-provider.ts +20 -0
- package/src/types/exchange-rate.ts +6 -0
- package/src/types/financials.ts +104 -7
- package/src/types/headless.ts +83 -6
- package/src/types/instrument.ts +3 -0
- package/src/types/issuer-listings.ts +28 -0
- package/src/types/plugin.ts +65 -8
- package/src/types/price-history.ts +2 -0
- package/src/types/ticker.ts +13 -0
- package/src/types/trading.ts +59 -1
- package/src/ui/ascii-font.ts +13 -1
- package/src/ui/host.tsx +6 -1
- package/src/updater/command-names.ts +175 -0
- package/src/updater/failure.ts +163 -0
- package/src/updater/index.ts +97 -28
- package/src/utils/calendar-date.ts +32 -15
- package/src/utils/cash-runway.ts +241 -0
- package/src/utils/central-banks.ts +42 -0
- package/src/utils/cli-output.ts +259 -80
- package/src/utils/contract-month.ts +16 -0
- package/src/utils/crypto-pair.ts +61 -3
- package/src/utils/currency-pair.ts +26 -0
- package/src/utils/currency-units.ts +29 -0
- package/src/utils/datetime-format.ts +10 -0
- package/src/utils/debug-log.ts +1 -1
- package/src/utils/decimal-comma.ts +39 -6
- package/src/utils/exchange-announcements.ts +71 -0
- package/src/utils/exchange-rate-snapshot.ts +10 -0
- package/src/utils/financial-statements.ts +83 -4
- package/src/utils/format.ts +34 -16
- package/src/utils/fundamentals.ts +150 -34
- package/src/utils/fx-market-hours.ts +10 -1
- package/src/utils/latest-financial-period.ts +8 -0
- package/src/utils/linux-libc.ts +54 -0
- package/src/utils/market-capitalization.ts +111 -15
- package/src/utils/max-drawdown.ts +64 -0
- package/src/utils/network-failure.ts +83 -0
- package/src/utils/option-expiry.ts +139 -7
- package/src/utils/price-earnings.ts +11 -5
- package/src/utils/price-history.ts +18 -8
- package/src/utils/price-window.ts +131 -0
- package/src/utils/release-date.ts +47 -0
- package/src/utils/sec.ts +71 -0
- package/src/utils/throttled-fetch.ts +11 -4
- package/src/utils/trailing-eps.ts +67 -0
- package/src/utils/typed-text.ts +21 -0
- package/src/utils/utc-time.ts +24 -1
- package/src/version.ts +6 -0
- package/src/cli/pane-functions/screenshot.ts +0 -2122
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import type { PerpBoardRow } from "../../../api-client/perps";
|
|
3
|
+
import { getServerClockOffsetMs } from "../../../market-data/quotes/clock";
|
|
4
|
+
import { buildQuoteKey, instrumentFromQuoteTarget } from "../../../market-data/selectors";
|
|
5
|
+
import { useLiveQuoteEntries } from "../../../state/hooks/quote-streaming";
|
|
6
|
+
import { perpetualListingOfMarket } from "../../../tickers/perpetual-listing";
|
|
7
|
+
import type { QuoteSubscriptionTarget } from "../../../types/data-provider";
|
|
8
|
+
import type { Quote } from "../../../types/financials";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Perpetual markets move with the app's quote stream like any listing: a
|
|
12
|
+
* market is quoted as the listing it is saved under (`BTC:HYPERLIQUID`), and
|
|
13
|
+
* its quote carries the market's whole row, live while Gloom Cloud follows the
|
|
14
|
+
* venue's own socket. A view that fetched rows (PERP's board, DES, a list's
|
|
15
|
+
* perpetual columns) lays each quote's newer row over its own, so the one
|
|
16
|
+
* subscription a watchlist row already holds feeds every view of the market.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
type Surface = NonNullable<QuoteSubscriptionTarget["surface"]>;
|
|
20
|
+
|
|
21
|
+
/** The quote target of a market, as its saved listing is quoted; null for a venue the app has no listing for. */
|
|
22
|
+
function perpQuoteTarget(marketId: string, surface: Surface = "detail", visible = true): QuoteSubscriptionTarget | null {
|
|
23
|
+
const listing = perpetualListingOfMarket(marketId);
|
|
24
|
+
return listing ? { symbol: listing.key, exchange: listing.exchange, route: "auto", surface, ...(visible ? { visible: true } : {}) } : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A fetched row with the newer reading a quote of its market brought; the row itself when the quote has none newer. */
|
|
28
|
+
export function withLiveReading(row: PerpBoardRow, quote: Quote | null | undefined): PerpBoardRow {
|
|
29
|
+
const live = quote?.perpetual;
|
|
30
|
+
if (!live || live.marketId.toLowerCase() !== row.marketId.toLowerCase()) return row;
|
|
31
|
+
return Date.parse(live.observedAt) > Date.parse(row.observedAt) ? { ...row, ...live, marketId: row.marketId } : row;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** As Gloom Cloud rules it: a market with no mark from its venue for a minute is no longer live. */
|
|
35
|
+
const LIVE_FOR_MS = 60_000;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Whether a quote of a market is fed by its venue's socket now, not the minute's read: streamed live, not stale, and
|
|
39
|
+
* marked within the last minute by the server's clock. The server says so too, but a socket that went quiet says
|
|
40
|
+
* nothing, so the app keeps its own count.
|
|
41
|
+
*/
|
|
42
|
+
export function isLivePerpQuote(quote: Quote | null | undefined, now = Date.now() + (getServerClockOffsetMs() ?? 0)): boolean {
|
|
43
|
+
return quote?.dataSource === "live" && quote.stale !== true && Number.isFinite(quote.lastUpdated)
|
|
44
|
+
&& now - quote.lastUpdated <= LIVE_FOR_MS;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* How long until a view looks again at whether its live rows still are: when the first one's minute runs out, a beat
|
|
49
|
+
* after, never more than a minute away. A reading stamped ahead of the clock would otherwise ask for a timer past what
|
|
50
|
+
* `setTimeout` holds, which fires at once, again and again.
|
|
51
|
+
*/
|
|
52
|
+
export function liveRecheckMs(expiresInMs: number): number {
|
|
53
|
+
return Math.min(LIVE_FOR_MS, Math.max(0, expiresInMs) + 250);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The most rows one view follows on the quote stream at once: as many as the app streams (the socket's budget). */
|
|
57
|
+
const LIVE_ROW_LIMIT = 60;
|
|
58
|
+
|
|
59
|
+
/** The markets of the rows a view follows on the quote stream: the first LIVE_ROW_LIMIT. */
|
|
60
|
+
export const followedPerpMarkets = (rows: readonly Pick<PerpBoardRow, "marketId">[]) =>
|
|
61
|
+
rows.slice(0, LIVE_ROW_LIMIT).map((row) => row.marketId);
|
|
62
|
+
|
|
63
|
+
export interface LivePerpRows {
|
|
64
|
+
rows: readonly PerpBoardRow[];
|
|
65
|
+
/** Every row followed is fed by its venue's socket now: the view may call itself live. */
|
|
66
|
+
live: boolean;
|
|
67
|
+
/** The markets fed by their venue's socket; the others are the minute's read. */
|
|
68
|
+
liveMarkets: ReadonlySet<string>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const NONE: ReadonlySet<string> = new Set();
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Fetched rows with the quote stream's newer readings laid over them. Only the
|
|
75
|
+
* first LIVE_ROW_LIMIT rows are subscribed: a list passes the rows in view.
|
|
76
|
+
*/
|
|
77
|
+
export function useLivePerpRows(
|
|
78
|
+
rows: readonly PerpBoardRow[],
|
|
79
|
+
{ surface = "detail", visible = true, enabled = true }: { surface?: Surface; visible?: boolean; enabled?: boolean } = {},
|
|
80
|
+
): LivePerpRows {
|
|
81
|
+
const marketKey = enabled ? followedPerpMarkets(rows).join("\u001f") : "";
|
|
82
|
+
const targets = useMemo(() => marketKey ? marketKey.split("\u001f").flatMap((marketId) => {
|
|
83
|
+
const target = perpQuoteTarget(marketId, surface, visible);
|
|
84
|
+
return target ? [target] : [];
|
|
85
|
+
}) : [], [marketKey, surface, visible]);
|
|
86
|
+
const { entries } = useLiveQuoteEntries(targets);
|
|
87
|
+
// Ticks when a live row's minute runs out, so a feed that went quiet stops calling itself live.
|
|
88
|
+
const [expired, setExpired] = useState(0);
|
|
89
|
+
const result = useMemo(() => {
|
|
90
|
+
if (!targets.length) return { rows, live: false, liveMarkets: NONE, expiresAt: null };
|
|
91
|
+
const now = Date.now() + (getServerClockOffsetMs() ?? 0);
|
|
92
|
+
const followed = new Set(targets.map((target) => buildQuoteKey(instrumentFromQuoteTarget(target))));
|
|
93
|
+
const followedMarkets = new Set(marketKey.split("\u001f").filter((marketId) => perpQuoteTarget(marketId, surface, visible)));
|
|
94
|
+
const quoteOf = (marketId: string) => {
|
|
95
|
+
const target = perpQuoteTarget(marketId, surface, visible);
|
|
96
|
+
if (!target) return null;
|
|
97
|
+
const key = buildQuoteKey(instrumentFromQuoteTarget(target));
|
|
98
|
+
const entry = followed.has(key) ? entries.get(key) : undefined;
|
|
99
|
+
return entry?.data ?? entry?.lastGoodData ?? null;
|
|
100
|
+
};
|
|
101
|
+
const liveMarkets = new Set<string>();
|
|
102
|
+
let expiresAt: number | null = null;
|
|
103
|
+
const next = rows.map((row) => {
|
|
104
|
+
const quote = quoteOf(row.marketId);
|
|
105
|
+
if (quote && isLivePerpQuote(quote, now)) {
|
|
106
|
+
liveMarkets.add(row.marketId);
|
|
107
|
+
expiresAt = Math.min(expiresAt ?? Number.POSITIVE_INFINITY, quote.lastUpdated + LIVE_FOR_MS - now);
|
|
108
|
+
}
|
|
109
|
+
return withLiveReading(row, quote);
|
|
110
|
+
});
|
|
111
|
+
const live = [...followedMarkets].every((marketId) => liveMarkets.has(marketId));
|
|
112
|
+
return { rows: next, live, liveMarkets, expiresAt };
|
|
113
|
+
// `expired` is the clock that ends a quiet row's minute.
|
|
114
|
+
}, [entries, expired, marketKey, rows, surface, targets, visible]);
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (result.expiresAt == null) return;
|
|
117
|
+
const timer = setTimeout(() => setExpired((count) => count + 1), liveRecheckMs(result.expiresAt));
|
|
118
|
+
return () => clearTimeout(timer);
|
|
119
|
+
}, [result]);
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface Range { start: number; end: number }
|
|
124
|
+
/** Rows a table starts with before it measures its viewport: a pane's height of them. */
|
|
125
|
+
const FIRST_VIEW: Range = { start: 0, end: 40 };
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* A table's rows with the quote stream laid over the ones in view: pass
|
|
129
|
+
* `onVisibleRangeChange` to the table, and its rows in view (and the row
|
|
130
|
+
* `pinned`, the one other tabs follow) are subscribed as it scrolls.
|
|
131
|
+
*/
|
|
132
|
+
export function useLivePerpTableRows<T>(
|
|
133
|
+
items: readonly T[],
|
|
134
|
+
rowOf: (item: T) => PerpBoardRow | null,
|
|
135
|
+
withRow: (item: T, row: PerpBoardRow) => T,
|
|
136
|
+
{ surface = "screener", pinned = null }: { surface?: Surface; pinned?: PerpBoardRow | null } = {},
|
|
137
|
+
): { items: T[]; live: boolean; onVisibleRangeChange: (range: Range) => void } {
|
|
138
|
+
const [range, setRange] = useState<Range>(FIRST_VIEW);
|
|
139
|
+
const onVisibleRangeChange = useCallback((next: Range) => {
|
|
140
|
+
setRange((current) => current.start === next.start && current.end === next.end ? current : next);
|
|
141
|
+
}, []);
|
|
142
|
+
const inView = useMemo(() => {
|
|
143
|
+
const rows = new Map<string, PerpBoardRow>();
|
|
144
|
+
if (pinned) rows.set(pinned.marketId, pinned);
|
|
145
|
+
for (const item of items.slice(range.start, range.end)) {
|
|
146
|
+
const row = rowOf(item);
|
|
147
|
+
if (row) rows.set(row.marketId, row);
|
|
148
|
+
}
|
|
149
|
+
return [...rows.values()];
|
|
150
|
+
}, [items, pinned, range, rowOf]);
|
|
151
|
+
const live = useLivePerpRows(inView, { surface });
|
|
152
|
+
const next = useMemo(() => {
|
|
153
|
+
const byMarket = new Map(live.rows.map((row) => [row.marketId, row]));
|
|
154
|
+
return items.map((item) => {
|
|
155
|
+
const row = rowOf(item);
|
|
156
|
+
const fresh = row ? byMarket.get(row.marketId) : undefined;
|
|
157
|
+
return row && fresh && fresh !== row ? withRow(item, fresh) : item;
|
|
158
|
+
});
|
|
159
|
+
}, [items, live.rows, rowOf, withRow]);
|
|
160
|
+
return { items: next, live: live.live, onVisibleRangeChange };
|
|
161
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from "react";
|
|
2
|
+
import { isAccessDenied } from "../../../api-client/errors";
|
|
3
|
+
import { usePlanAccess } from "../../../api-client/plan-access";
|
|
4
|
+
import type { PerpBoardRow } from "../../../api-client/perps";
|
|
5
|
+
import { ActionRow, ColumnGrid, SectionHeading, type ColumnGridCell, type ColumnGridColumn } from "../../../components";
|
|
6
|
+
import { t, tf } from "../../../i18n";
|
|
7
|
+
import { useAsyncResource, useAutoRefresh } from "../../../public/react";
|
|
8
|
+
import { useThemeColors } from "../../../theme/theme-context";
|
|
9
|
+
import { perpetualListing } from "../../../tickers/perpetual-listing";
|
|
10
|
+
import { premiumToUnderlying } from "../../../market-data/perpetual-premium";
|
|
11
|
+
import type { Quote } from "../../../types/financials";
|
|
12
|
+
import { Box, Text } from "../../../ui";
|
|
13
|
+
import { isRoundTheClockCoin } from "../../../utils/crypto-pair";
|
|
14
|
+
import { displayWidth } from "../../../utils/format";
|
|
15
|
+
import { parsePublicTickerKey } from "../../../utils/exchanges";
|
|
16
|
+
import { useResearchCloudSession } from "../shared/research-cloud-session";
|
|
17
|
+
import { loadPerpsCompare, loadPerpsEquity } from "./client";
|
|
18
|
+
import { perpEquityIdentity } from "./equity-identity";
|
|
19
|
+
import { useLivePerpRows } from "./live-rows";
|
|
20
|
+
import { compact, perpCell, percent, price, readingTime, venueName } from "./model";
|
|
21
|
+
import { useOpenPerpMarket } from "./open-market";
|
|
22
|
+
import { contractText, foldSmallMarkets, mergeVenueMarkets, type PerpVenueMarket } from "./venue-markets";
|
|
23
|
+
|
|
24
|
+
const STOCK_TYPES = new Set(["STK", "EQUITY", "STOCK", "COMMON STOCK"]);
|
|
25
|
+
const COIN_PAIR = /^([A-Z0-9]{1,15})[-/](?:USD|USDT|USDC)$/;
|
|
26
|
+
|
|
27
|
+
/** What an asset's contracts are looked up by: a stock's listing, or a coin's base asset. Null for a contract itself. */
|
|
28
|
+
function perpAsset(symbol: string, exchange: string | undefined, instrumentType: string | undefined):
|
|
29
|
+
{ kind: "stock"; symbol: string; exchange: string } | { kind: "coin"; base: string } | null {
|
|
30
|
+
if (perpetualListing(symbol, exchange)) return null;
|
|
31
|
+
if (isRoundTheClockCoin(symbol, exchange)) {
|
|
32
|
+
const bare = parsePublicTickerKey(symbol).symbol.toUpperCase();
|
|
33
|
+
const base = COIN_PAIR.exec(bare)?.[1] ?? (/^[A-Z0-9]{1,15}$/.test(bare) ? bare : null);
|
|
34
|
+
return base ? { kind: "coin", base } : null;
|
|
35
|
+
}
|
|
36
|
+
const identity = perpEquityIdentity(symbol, exchange);
|
|
37
|
+
return identity && STOCK_TYPES.has(instrumentType?.toUpperCase() ?? "") ? { kind: "stock", ...identity } : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const VENUE_MIN_WIDTH = 11;
|
|
41
|
+
/** Coinbase International, in full. */
|
|
42
|
+
const VENUE_MAX_WIDTH = 22;
|
|
43
|
+
|
|
44
|
+
interface Column { id: "venue" | "contract" | "mark" | "premium" | "funding" | "oi" | "volume"; label: string; width: number; align?: "right" }
|
|
45
|
+
// Reading order.
|
|
46
|
+
const COLUMNS: readonly Column[] = [
|
|
47
|
+
{ id: "venue", label: "Venue", width: VENUE_MIN_WIDTH },
|
|
48
|
+
{ id: "contract", label: "Contract", width: 11 },
|
|
49
|
+
{ id: "mark", label: "Mark", width: 11, align: "right" },
|
|
50
|
+
{ id: "premium", label: "Premium", width: 8, align: "right" },
|
|
51
|
+
{ id: "funding", label: "Fund 8h", width: 9, align: "right" },
|
|
52
|
+
{ id: "oi", label: "OI USD", width: 7, align: "right" },
|
|
53
|
+
{ id: "volume", label: "Vol 24h", width: 7, align: "right" },
|
|
54
|
+
];
|
|
55
|
+
/** What a narrow overview gives up first: the volume, then the contract, then the premium. */
|
|
56
|
+
const DROP_ORDER: readonly Column["id"][] = ["volume", "contract", "premium"];
|
|
57
|
+
const GAP = 2;
|
|
58
|
+
const span = (columns: readonly Column[]) => columns.reduce((sum, column) => sum + column.width, 0) + GAP * (columns.length - 1);
|
|
59
|
+
|
|
60
|
+
function fitColumns(width: number, venueWidth: number): Column[] {
|
|
61
|
+
let columns = COLUMNS.map((column) => column.id === "venue" ? { ...column, width: venueWidth } : column);
|
|
62
|
+
for (const id of DROP_ORDER) {
|
|
63
|
+
if (span(columns) <= width) break;
|
|
64
|
+
columns = columns.filter((column) => column.id !== id);
|
|
65
|
+
}
|
|
66
|
+
return columns;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* DES's perpetual markets on a stock or a coin: one row per venue (its contracts merged, `venue-markets.ts`), the
|
|
71
|
+
* deepest first, markets with a small share of the open interest folded behind a row that shows them. A row opens
|
|
72
|
+
* PERP on its venue's deepest contract. The figures move with the quote stream while the venue streams; otherwise
|
|
73
|
+
* the section says when the venues were read.
|
|
74
|
+
*/
|
|
75
|
+
export function PerpMarketsSection({ symbol, exchange, instrumentType, quote, width }: {
|
|
76
|
+
symbol: string;
|
|
77
|
+
exchange?: string;
|
|
78
|
+
instrumentType?: string;
|
|
79
|
+
/** The security's own quote: a stock's contracts are priced against it while it streams. */
|
|
80
|
+
quote?: Quote | null;
|
|
81
|
+
width: number;
|
|
82
|
+
}) {
|
|
83
|
+
const colors = useThemeColors();
|
|
84
|
+
const session = useResearchCloudSession();
|
|
85
|
+
const plan = usePlanAccess();
|
|
86
|
+
const openMarket = useOpenPerpMarket();
|
|
87
|
+
const [expanded, setExpanded] = useState(false);
|
|
88
|
+
const asset = perpAsset(symbol, exchange, instrumentType);
|
|
89
|
+
const assetKey = asset ? asset.kind === "coin" ? `coin:${asset.base}` : `stock:${asset.symbol}:${asset.exchange}` : null;
|
|
90
|
+
const accessKey = `${session.requestKey}:${plan.hasProAccess ? "pro" : "preview"}`;
|
|
91
|
+
const loader = useCallback((force: boolean) => {
|
|
92
|
+
const [kind, first, second] = assetKey!.split(":");
|
|
93
|
+
return kind === "coin" ? loadPerpsCompare(first!, accessKey, force) : loadPerpsEquity({ symbol: first!, exchange: second! }, accessKey, force);
|
|
94
|
+
}, [assetKey, accessKey]);
|
|
95
|
+
const resource = useAsyncResource(assetKey ? loader : null, { clearOnError: isAccessDenied });
|
|
96
|
+
useAutoRefresh(resource.updatedAt, resource.load, { intervalMs: 60_000 });
|
|
97
|
+
const markets = useMemo(() => mergeVenueMarkets(resource.data?.payload.rows ?? []), [resource.data]);
|
|
98
|
+
const { shown, folded } = useMemo(() => foldSmallMarkets(markets), [markets]);
|
|
99
|
+
const visible = expanded ? markets : shown;
|
|
100
|
+
// The deepest contract of each row on screen follows the quote stream.
|
|
101
|
+
const primaries = useMemo(() => visible.map((market) => market.primary), [visible]);
|
|
102
|
+
const live = useLivePerpRows(primaries, { surface: "detail" });
|
|
103
|
+
if (!assetKey || !markets.length) return null;
|
|
104
|
+
const rows = visible.map((market, index) => ({ market, row: live.rows[index] ?? market.primary }));
|
|
105
|
+
const premiumLabel = asset?.kind === "stock" ? "vs last" : "Premium";
|
|
106
|
+
// The venue column fits the longest venue name shown.
|
|
107
|
+
const venueWidth = Math.min(VENUE_MAX_WIDTH, Math.max(VENUE_MIN_WIDTH, ...visible.map((market) => displayWidth(venueName(market.venue)))));
|
|
108
|
+
const columns = fitColumns(width, venueWidth);
|
|
109
|
+
const fixed = span(columns);
|
|
110
|
+
// The last text column takes what the figures leave, on the desktop too, so a venue keeps its contract beside it.
|
|
111
|
+
const growing = columns.some((column) => column.id === "contract") ? "contract" : "venue";
|
|
112
|
+
const gridColumns: ColumnGridColumn[] = columns.map((column, index) => ({
|
|
113
|
+
width: column.id === growing ? Math.max(column.width, width - fixed + column.width) : column.width,
|
|
114
|
+
align: column.align,
|
|
115
|
+
gapBefore: index > 0 ? GAP : 0,
|
|
116
|
+
...(column.id === growing ? { growFrom: column.width } : {}),
|
|
117
|
+
}));
|
|
118
|
+
const header: ColumnGridCell[] = columns.map((column) => ({ text: t(column.id === "premium" ? premiumLabel : column.label), color: colors.textDim }));
|
|
119
|
+
const cell = (column: Column, market: PerpVenueMarket, row: PerpBoardRow): ColumnGridCell => {
|
|
120
|
+
if (column.id === "venue") {
|
|
121
|
+
return { text: venueName(market.venue), color: row.stale ? colors.textDim : colors.textBright,
|
|
122
|
+
link: { label: tf("Open {label}", { label: `PERP ${row.baseAsset} · ${venueName(market.venue)}` }), onOpen: () => openMarket(row.marketId) } };
|
|
123
|
+
}
|
|
124
|
+
if (column.id === "contract") return { text: contractText(market), color: colors.textMuted };
|
|
125
|
+
if (column.id === "mark") return { text: price(row.markPrice ?? row.lastPrice), color: colors.text };
|
|
126
|
+
if (column.id === "premium") {
|
|
127
|
+
const value = asset?.kind === "stock" ? premiumToUnderlying(row, quote) : row.premium;
|
|
128
|
+
return { text: percent(value, 2), color: value == null || value === 0 ? colors.textMuted : value > 0 ? colors.positive : colors.negative };
|
|
129
|
+
}
|
|
130
|
+
if (column.id === "funding") {
|
|
131
|
+
const { text, color } = perpCell(row, "fundingRate8h", colors);
|
|
132
|
+
return { text, color };
|
|
133
|
+
}
|
|
134
|
+
// A venue's open interest and volume are its contracts' together.
|
|
135
|
+
return { text: compact(column.id === "oi" ? market.openInterestUsd : market.volume24hUsd), color: colors.text };
|
|
136
|
+
};
|
|
137
|
+
const body = rows.map(({ market, row }) => columns.map((column) => cell(column, market, row)));
|
|
138
|
+
// Streaming figures date themselves; the rows from the minute's read say when it was.
|
|
139
|
+
const read = rows.filter(({ row }) => !live.liveMarkets.has(row.marketId));
|
|
140
|
+
const readAt = read.length ? readingTime(read.reduce<string | null>((latest, { row }) => !latest || row.observedAt > latest ? row.observedAt : latest, null)) : null;
|
|
141
|
+
const stale = read.length > 0 && (!!resource.data?.stale || read.some(({ row }) => row.stale));
|
|
142
|
+
return (
|
|
143
|
+
<Box flexDirection="column" data-gloom-role="perp-markets">
|
|
144
|
+
<SectionHeading title={t("Perpetuals")} />
|
|
145
|
+
<ColumnGrid columns={gridColumns} rows={[header, ...body]} width={width} role="perp-markets-grid" />
|
|
146
|
+
{folded.length > 0 && (
|
|
147
|
+
<ActionRow expanded={expanded} width={width}
|
|
148
|
+
label={expanded ? t("Fewer markets") : folded.length === 1 ? t("1 smaller market") : tf("{count} smaller markets", { count: String(folded.length) })}
|
|
149
|
+
onPress={() => setExpanded((value) => !value)} />
|
|
150
|
+
)}
|
|
151
|
+
{readAt && <Text fg={stale ? colors.warning : colors.textDim}>{`${read.length < rows.length ? `${[...new Set(read.map(({ market }) => venueName(market.venue)))].join(", ")} ` : ""}${t("as of")} ${readAt}${stale ? ` · ${t("stale")}` : ""}`}</Text>}
|
|
152
|
+
</Box>
|
|
153
|
+
);
|
|
154
|
+
}
|
|
@@ -25,21 +25,27 @@ import { isCloudSessionRequired, useResearchCloudSession } from "../shared/resea
|
|
|
25
25
|
import { boardSearchText, cachedPerpSelection, cachedPerpsBoard, cachedPerpsCompare, cachedPerpsRankings, loadPerpSelection, loadPerpsBoard, loadPerpsCompare,
|
|
26
26
|
loadPerpsHistoryForRange, loadPerpsRankings } from "./client";
|
|
27
27
|
import { perpFreshness, perpObservation } from "./freshness";
|
|
28
|
+
import { useLivePerpRows, useLivePerpTableRows } from "./live-rows";
|
|
28
29
|
import { aprFormula, ASSET_FILTERS, BOARD_SORTS, boardColumns, collectedVenueCount, compact, compareColumns, dayChange, fundingInterval, fundingSpread, historyCaption, historyCell, historyChange, historyColumns,
|
|
29
30
|
HISTORY_METRICS, historyRows, historySeries, historyValue, inPoints, isMarketTab, label, longShortReason, marketLabel, metricLabel, openingTab, perpCell, perpHistoryProText, PERP_TABS, percent,
|
|
30
31
|
positioningFields, price, rankingColumns, rankingRows, RANKINGS, ratioText, share, sortLabel, time, underlyingTicker, venueLabel, venueName, venuesOf, type HistoryMetric,
|
|
31
32
|
type PerpColumnId, type PerpTab, type RankingKey } from "./model";
|
|
32
33
|
|
|
33
34
|
const NO_ROWS: readonly PerpBoardRow[] = [];
|
|
35
|
+
const rowItself = (row: PerpBoardRow) => row;
|
|
36
|
+
const replaced = (_: PerpBoardRow, row: PerpBoardRow) => row;
|
|
37
|
+
const rankedRow = (entry: SectionedRow<Ranked>) => entry.kind === "item" ? entry.item.row : null;
|
|
38
|
+
const withRankedRow = (entry: SectionedRow<Ranked>, row: PerpBoardRow): SectionedRow<Ranked> =>
|
|
39
|
+
entry.kind === "item" ? { ...entry, item: { ...entry.item, row } } : entry;
|
|
34
40
|
|
|
35
41
|
/**
|
|
36
42
|
* The footer line for the rows on screen: each venue's own observation time, how old the oldest is, the delay state
|
|
37
43
|
* and what the funding figures are, the line `fn PERP` and `shot PERP` end with. It keeps ageing while the pane is
|
|
38
44
|
* seen (`since` is when the rows loaded), and is published for a capture to date itself the same way.
|
|
39
45
|
*/
|
|
40
|
-
function usePerpStatusLine(rows: readonly PerpBoardRow[], funding: readonly PerpBoardRow[], since: number | null): PaneFooterSegment[] {
|
|
46
|
+
function usePerpStatusLine(rows: readonly PerpBoardRow[], funding: readonly PerpBoardRow[], since: number | null, live = false): PaneFooterSegment[] {
|
|
41
47
|
const now = useAgeClock(since);
|
|
42
|
-
const declared = useMemo(() => rows.length ? perpFreshness(rows, { funding, now }) : null, [rows, funding, now]);
|
|
48
|
+
const declared = useMemo(() => rows.length ? perpFreshness(rows, { funding, now, live }) : null, [rows, funding, now, live]);
|
|
43
49
|
useRemoteUiNode(declared ? { role: "report-freshness", label: "Venue freshness", getMetadata: () => ({ ...declared, observations: rows.map(perpObservation) }) } : null);
|
|
44
50
|
return useMemo(() => {
|
|
45
51
|
if (!declared) return [];
|
|
@@ -104,8 +110,8 @@ function MarketEvidence({ row, data, width, height }: { row: PerpBoardRow; data:
|
|
|
104
110
|
</ScrollBox>;
|
|
105
111
|
}
|
|
106
112
|
|
|
107
|
-
function History({ market, venues, accessKey, width, height, focused, range, metric, refreshMarket }: {
|
|
108
|
-
market: PerpBoardRow; venues: number | null; accessKey: string; width: number; height: number; focused: boolean; range: string; metric: HistoryMetric; refreshMarket: () => void;
|
|
113
|
+
function History({ market, live = false, venues, accessKey, width, height, focused, range, metric, refreshMarket }: {
|
|
114
|
+
market: PerpBoardRow; live?: boolean; venues: number | null; accessKey: string; width: number; height: number; focused: boolean; range: string; metric: HistoryMetric; refreshMarket: () => void;
|
|
109
115
|
}) {
|
|
110
116
|
const colors = useThemeColors();
|
|
111
117
|
const loader = useCallback((force: boolean) => loadPerpsHistoryForRange(market.marketId, Number(range), accessKey, force), [market.marketId, range, accessKey]);
|
|
@@ -125,7 +131,7 @@ function History({ market, venues, accessKey, width, height, focused, range, met
|
|
|
125
131
|
const lastTime = rows.at(-1)?.time;
|
|
126
132
|
// The market row dates the figures beside the series. Its funding is the current estimate, unless the series is paid funding.
|
|
127
133
|
const marketRows = useMemo(() => [market], [market]);
|
|
128
|
-
const line = usePerpStatusLine(marketRows, metric === "funding" && !data?.funding.length ? marketRows : NO_ROWS, resource.updatedAt);
|
|
134
|
+
const line = usePerpStatusLine(marketRows, metric === "funding" && !data?.funding.length ? marketRows : NO_ROWS, resource.updatedAt, live);
|
|
129
135
|
usePaneStatusFooter({ registrationId: "perps:history", loading: resource.loading, error: resource.error, stale: resource.data?.stale,
|
|
130
136
|
info: [...line, ...(lastTime ? [{ id: "history-asof", parts: [{ text: `${historyCaption(data, metric)} · ${time(lastTime)}`, tone: "muted" as const }] }] : [])] });
|
|
131
137
|
usePaneNoticeFooter({ registrationId: "perps:history-notice", focused, notices: [resource.data?.refreshError,
|
|
@@ -221,14 +227,18 @@ function Board({ accessKey, width, height, focused, onPick, onOpen }: ListTabPro
|
|
|
221
227
|
const resource = useAsyncResource(loader, { initialData: () => cachedPerpsBoard(query, accessKey), clearOnError: isAccessDenied, keepPreviousData: true });
|
|
222
228
|
const data = resource.data?.payload;
|
|
223
229
|
const venues = useMemo(() => venuesOf(data?.rows ?? []), [data]);
|
|
224
|
-
const
|
|
230
|
+
const fetchedRows = useMemo(() => venue === "all" ? data?.rows ?? [] : (data?.rows ?? []).filter((row) => row.venue === venue), [data, venue]);
|
|
231
|
+
// The rows in view move with the quote stream.
|
|
232
|
+
const pinned = fetchedRows.find((row) => row.marketId === selectedId) ?? null;
|
|
233
|
+
const stream = useLivePerpTableRows(fetchedRows, rowItself, replaced, { pinned });
|
|
234
|
+
const rows = stream.items;
|
|
225
235
|
const selected = rows.find((row) => row.marketId === selectedId) ?? rows[0] ?? null;
|
|
226
236
|
useEffect(() => { onPick(selected); }, [selected, onPick]);
|
|
227
237
|
const columns = useMemo(() => boardColumns(width), [width]);
|
|
228
238
|
const locked = typeof data?.locked === "number" ? data.locked : 0;
|
|
229
239
|
useAutoRefresh(resource.updatedAt, resource.load, { intervalMs: 60_000 });
|
|
230
240
|
usePaneRefreshKey(() => { void resource.reload(); }, { focused: focused && !querySearch.active });
|
|
231
|
-
const line = usePerpStatusLine(rows, rows, resource.updatedAt);
|
|
241
|
+
const line = usePerpStatusLine(rows, rows, resource.updatedAt, stream.live);
|
|
232
242
|
usePaneStatusFooter({ registrationId: "perps:board", loading: resource.loading, error: data ? resource.error : null, stale: resource.data?.stale, info: line });
|
|
233
243
|
usePaneNoticeFooter({ registrationId: "perps:board-notice", focused, notices: resource.data?.refreshError ? [resource.data.refreshError] : [] });
|
|
234
244
|
if (!data && isCloudSessionRequired(resource.error)) return <SignInWall placement="perps-signin" action="view perpetual markets" needsVerification={session.needsVerification} />;
|
|
@@ -244,7 +254,7 @@ function Board({ accessKey, width, height, focused, onPick, onOpen }: ListTabPro
|
|
|
244
254
|
]} />;
|
|
245
255
|
return <PaneStatusBody loading={!data && resource.loading} error={!data ? resource.error : null} subject="perpetual markets">
|
|
246
256
|
<DataTableView<PerpBoardRow> columns={columns} items={rows} focused={focused && !querySearch.active} rootWidth={width} rootHeight={height} rootBefore={queryBar}
|
|
247
|
-
getItemKey={(row) => row.marketId} sortColumnId={null} sortDirection="desc" resetScrollKey={`${JSON.stringify(query)}:${venue}`}
|
|
257
|
+
getItemKey={(row) => row.marketId} sortColumnId={null} sortDirection="desc" resetScrollKey={`${JSON.stringify(query)}:${venue}`} onVisibleRangeChange={stream.onVisibleRangeChange}
|
|
248
258
|
selection={{ kind: "id", selectedId: selected?.marketId ?? null, getId: (row) => row.marketId, onChange: setSelected }} onActivate={(row) => onOpen(row)}
|
|
249
259
|
renderCell={(row, column) => perpCell(row, column.id as PerpColumnId, colors)} selectedTextOverridesCellColor
|
|
250
260
|
emptyStateTitle="No markets match these filters." emptyContent={lock && <Box paddingX={1} paddingY={1}><EmptyState title="No preview markets match." actions={<UpgradeLabel text={moreText(locked, "markets")} onPress={upgrade} role="perps-board-upgrade" />} /></Box>}
|
|
@@ -264,8 +274,11 @@ function Rankings({ accessKey, width, height, focused, onPick, onOpen }: ListTab
|
|
|
264
274
|
const loader = useCallback((force: boolean) => loadPerpsRankings(accessKey, force), [accessKey]);
|
|
265
275
|
const resource = useAsyncResource(loader, { initialData: () => cachedPerpsRankings(accessKey), clearOnError: isAccessDenied });
|
|
266
276
|
const data = resource.data?.payload;
|
|
267
|
-
const
|
|
277
|
+
const fetchedItems = useMemo(() => buildSectionedRows(RANKINGS.map((ranking) => ({ label: ranking.label, emptyLabel: "None",
|
|
268
278
|
items: rankingRows(data, ranking.key).map((row): Ranked => ({ list: ranking.key, row })) })), rankedKey), [data]);
|
|
279
|
+
// The ranked rows in view move with the quote stream; the lists keep the order they were ranked in.
|
|
280
|
+
const stream = useLivePerpTableRows(fetchedItems, rankedRow, withRankedRow);
|
|
281
|
+
const items = stream.items;
|
|
269
282
|
const ranked = items.flatMap((item) => item.kind === "item" ? [item.item] : []);
|
|
270
283
|
const selected = ranked.find((item) => rankedKey(item) === selectedId) ?? ranked[0] ?? null;
|
|
271
284
|
useEffect(() => { onPick(selected?.row ?? null); }, [selected, onPick]);
|
|
@@ -275,7 +288,7 @@ function Rankings({ accessKey, width, height, focused, onPick, onOpen }: ListTab
|
|
|
275
288
|
// Every ranked row is dated; only the funding lists show funding.
|
|
276
289
|
const rankedRows = useMemo(() => RANKINGS.flatMap((ranking) => rankingRows(data, ranking.key)), [data]);
|
|
277
290
|
const fundingRows = useMemo(() => RANKINGS.filter((ranking) => ranking.field === "fundingRate8h").flatMap((ranking) => rankingRows(data, ranking.key)), [data]);
|
|
278
|
-
const line = usePerpStatusLine(rankedRows, fundingRows, resource.updatedAt);
|
|
291
|
+
const line = usePerpStatusLine(rankedRows, fundingRows, resource.updatedAt, stream.live);
|
|
279
292
|
usePaneStatusFooter({ registrationId: "perps:rankings", loading: resource.loading, error: data ? resource.error : null, stale: resource.data?.stale, info: line });
|
|
280
293
|
usePaneNoticeFooter({ registrationId: "perps:rankings-notice", focused, notices: resource.data?.refreshError ? [resource.data.refreshError] : [] });
|
|
281
294
|
if (!data && isCloudSessionRequired(resource.error)) return <SignInWall placement="perps-signin" action="view perpetual rankings" needsVerification={session.needsVerification} />;
|
|
@@ -290,7 +303,7 @@ function Rankings({ accessKey, width, height, focused, onPick, onOpen }: ListTab
|
|
|
290
303
|
};
|
|
291
304
|
return <PaneStatusBody loading={!data && resource.loading} error={!data ? resource.error : null} subject="perpetual rankings">
|
|
292
305
|
<DataTableView<SectionedRow<Ranked>> columns={columns} items={items} focused={focused} rootWidth={width} rootHeight={height}
|
|
293
|
-
getItemKey={(entry) => entry.key} sortColumnId={null} sortDirection="desc" isNavigable={(entry) => entry.kind === "item"}
|
|
306
|
+
getItemKey={(entry) => entry.key} sortColumnId={null} sortDirection="desc" isNavigable={(entry) => entry.kind === "item"} onVisibleRangeChange={stream.onVisibleRangeChange}
|
|
294
307
|
renderSectionHeader={(entry) => entry.kind === "section" ? { text: entry.label } : null}
|
|
295
308
|
selection={{ kind: "id", selectedId: selected ? rankedKey(selected) : null, getId: (entry) => entry.key, onChange: (id) => { if (ranked.some((item) => rankedKey(item) === id)) setSelected(id); } }}
|
|
296
309
|
onActivate={(entry) => { if (entry.kind === "item") onOpen(entry.item.row); }}
|
|
@@ -310,11 +323,13 @@ function Compare({ asset, onAssetChange, accessKey, width, height, focused, onPi
|
|
|
310
323
|
const loader = useCallback((force: boolean) => loadPerpsCompare(base, accessKey, force), [base, accessKey]);
|
|
311
324
|
const resource = useAsyncResource(loader, { initialData: () => cachedPerpsCompare(base, accessKey), clearOnError: isAccessDenied, keepPreviousData: true });
|
|
312
325
|
const data = resource.data?.payload;
|
|
313
|
-
const
|
|
326
|
+
const fetchedRows = useMemo(() => data?.rows ?? [], [data]);
|
|
327
|
+
const stream = useLivePerpTableRows(fetchedRows, rowItself, replaced, { pinned: fetchedRows.find((row) => row.marketId === selectedId) ?? null });
|
|
328
|
+
const rows = stream.items;
|
|
314
329
|
const selected = rows.find((row) => row.marketId === selectedId) ?? rows[0] ?? null;
|
|
315
330
|
useEffect(() => { onPick(selected); }, [selected, onPick]);
|
|
316
331
|
const columns = useMemo(() => compareColumns(width), [width]);
|
|
317
|
-
const line = usePerpStatusLine(rows, rows, resource.updatedAt);
|
|
332
|
+
const line = usePerpStatusLine(rows, rows, resource.updatedAt, stream.live);
|
|
318
333
|
const spread = useMemo(() => fundingSpread(rows), [rows]);
|
|
319
334
|
const locked = typeof data?.locked === "number" ? data.locked : 0;
|
|
320
335
|
useAutoRefresh(resource.updatedAt, resource.load, { intervalMs: 60_000 });
|
|
@@ -330,7 +345,7 @@ function Compare({ asset, onAssetChange, accessKey, width, height, focused, onPi
|
|
|
330
345
|
</>;
|
|
331
346
|
return <PaneStatusBody loading={!data && resource.loading} error={!data ? resource.error : null} subject="perpetual contracts">
|
|
332
347
|
<DataTableView<PerpBoardRow> columns={columns} items={rows} focused={focused && !querySearch.active} rootWidth={width} rootHeight={height} rootBefore={before}
|
|
333
|
-
getItemKey={(row) => row.marketId} sortColumnId={null} sortDirection="desc" resetScrollKey={base}
|
|
348
|
+
getItemKey={(row) => row.marketId} sortColumnId={null} sortDirection="desc" resetScrollKey={base} onVisibleRangeChange={stream.onVisibleRangeChange}
|
|
334
349
|
selection={{ kind: "id", selectedId: selected?.marketId ?? null, getId: (row) => row.marketId, onChange: setSelected }} onActivate={(row) => onOpen(row)}
|
|
335
350
|
renderCell={(row, column) => perpCell(row, column.id as PerpColumnId, colors)} selectedTextOverridesCellColor
|
|
336
351
|
emptyStateTitle={`No perpetual contracts for ${base}.`} emptyContent={lock && <Box paddingX={1} paddingY={1}><EmptyState title={`No preview contracts for ${base}.`} actions={<UpgradeLabel text={moreText(locked, locked === 1 ? "contract" : "contracts")} onPress={upgrade} role="perps-compare-upgrade" />} /></Box>}
|
|
@@ -380,7 +395,11 @@ export function PerpsPane({ width, height, focused, marketAction }: Pick<PanePro
|
|
|
380
395
|
const loader = useCallback((force: boolean) => loadPerpSelection(search, accessKey, force), [search, accessKey]);
|
|
381
396
|
const resource = useAsyncResource(marketTab ? loader : null, { initialData: () => cachedPerpSelection(search, accessKey), clearOnError: isAccessDenied });
|
|
382
397
|
const data = marketTab ? resource.data?.payload : undefined;
|
|
383
|
-
const
|
|
398
|
+
const fetchedActive = data?.rows[0] ?? null;
|
|
399
|
+
// The market on screen moves with the quote stream, as its listing's quote does everywhere else.
|
|
400
|
+
const activeRows = useMemo(() => fetchedActive ? [fetchedActive] : NO_ROWS, [fetchedActive]);
|
|
401
|
+
const activeStream = useLivePerpRows(activeRows, { surface: "detail", enabled: marketTab });
|
|
402
|
+
const active = activeStream.rows[0] ?? null;
|
|
384
403
|
usePaneTitle(active ? `PERP ${marketLabel(active)} · ${venueLabel(active)}` : "PERP");
|
|
385
404
|
const querySearch = useQueryBarSearch();
|
|
386
405
|
// The market on screen goes with the user: History and Evidence open the row picked on a list, Compare its base asset.
|
|
@@ -416,7 +435,7 @@ export function PerpsPane({ width, height, focused, marketAction }: Pick<PanePro
|
|
|
416
435
|
];
|
|
417
436
|
// History dates its own market beside its series; Evidence is dated here.
|
|
418
437
|
const evidenceRows = useMemo(() => tab === "evidence" && active ? [active] : NO_ROWS, [tab, active]);
|
|
419
|
-
const evidenceLine = usePerpStatusLine(evidenceRows, evidenceRows, resource.updatedAt);
|
|
438
|
+
const evidenceLine = usePerpStatusLine(evidenceRows, evidenceRows, resource.updatedAt, activeStream.live);
|
|
420
439
|
usePaneStatusFooter({ registrationId: "perps", loading: marketTab && resource.loading, error: data ? resource.error : null, stale: marketTab && resource.data?.stale,
|
|
421
440
|
info: evidenceLine, hints });
|
|
422
441
|
usePaneNoticeFooter({ registrationId: "perps:notices", focused, notices: marketTab ? [resource.data?.refreshError,
|
|
@@ -434,7 +453,7 @@ export function PerpsPane({ width, height, focused, marketAction }: Pick<PanePro
|
|
|
434
453
|
{ id: "range", label: "Range", value: range, options: ["1", "7", "30", "90", "365"].map((value) => ({ value, label: `${value}D` })), onChange: setRange },
|
|
435
454
|
] : []} />
|
|
436
455
|
<PaneStatusBody loading={!data && resource.loading} error={!data ? resource.error : null} subject="perpetual market">
|
|
437
|
-
{active ? tab === "history" ? <History key={active.marketId} market={active} venues={collectedVenueCount(data?.sources)} accessKey={accessKey} width={width} height={listHeight - 1} focused={focused && !querySearch.active} range={range} metric={metric} refreshMarket={refreshMarket} />
|
|
456
|
+
{active ? tab === "history" ? <History key={active.marketId} market={active} live={activeStream.live} venues={collectedVenueCount(data?.sources)} accessKey={accessKey} width={width} height={listHeight - 1} focused={focused && !querySearch.active} range={range} metric={metric} refreshMarket={refreshMarket} />
|
|
438
457
|
: <MarketEvidence row={active} data={data!} width={width} height={listHeight - 1} />
|
|
439
458
|
: <EmptyState title="No market matches." message={data?.suggestions?.length ? `Try ${data.suggestions.join(", ")}.` : undefined} />}
|
|
440
459
|
</PaneStatusBody>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { PerpBoardRow } from "../../../api-client/perps";
|
|
2
|
+
import { venueName } from "./model";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* An asset's perpetual markets as DES lists them: one row per venue.
|
|
6
|
+
*
|
|
7
|
+
* A venue often lists one asset several times: Bybit's BTC settles in USDT,
|
|
8
|
+
* in USDC and inversely in BTC, and on Hyperliquid each builder dex (xyz, flx,
|
|
9
|
+
* km...) deploys its own NVDA. Listed contract by contract they read as the
|
|
10
|
+
* same line over and over, so each venue's contracts are one market here: the
|
|
11
|
+
* deepest contract gives the row its mark, funding and premium, and the open
|
|
12
|
+
* interest and volume are the venue's whole. A row reached twice (the same
|
|
13
|
+
* market id in two cases, or answered by two reads) is one contract.
|
|
14
|
+
*/
|
|
15
|
+
export interface PerpVenueMarket {
|
|
16
|
+
/** `hyperliquid:BTC`: the venue and the asset, whatever the contract. */
|
|
17
|
+
key: string;
|
|
18
|
+
venue: string;
|
|
19
|
+
/** The deepest contract, which a click opens. */
|
|
20
|
+
primary: PerpBoardRow;
|
|
21
|
+
/** Every contract on the venue, deepest first; the primary is the first. */
|
|
22
|
+
contracts: PerpBoardRow[];
|
|
23
|
+
/** Open interest across the contracts, in USD; null when no contract states it. */
|
|
24
|
+
openInterestUsd: number | null;
|
|
25
|
+
volume24hUsd: number | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const sumOrNull = (values: Array<number | null | undefined>) => {
|
|
29
|
+
const known = values.filter((value): value is number => typeof value === "number" && Number.isFinite(value));
|
|
30
|
+
return known.length ? known.reduce((sum, value) => sum + value, 0) : null;
|
|
31
|
+
};
|
|
32
|
+
const depth = (value: number | null | undefined) => typeof value === "number" && Number.isFinite(value) ? value : -1;
|
|
33
|
+
const deeper = (a: { openInterestUsd: number | null; volume24hUsd: number | null }, b: typeof a) =>
|
|
34
|
+
depth(b.openInterestUsd) - depth(a.openInterestUsd) || depth(b.volume24hUsd) - depth(a.volume24hUsd);
|
|
35
|
+
|
|
36
|
+
/** Rows merged into one row per venue and asset, the deepest first (open interest, then volume). Delisted contracts are left out. */
|
|
37
|
+
export function mergeVenueMarkets(rows: readonly PerpBoardRow[]): PerpVenueMarket[] {
|
|
38
|
+
const contracts = new Map<string, PerpBoardRow>();
|
|
39
|
+
for (const row of rows) {
|
|
40
|
+
if (row.delisted) continue;
|
|
41
|
+
const id = row.marketId.toLowerCase();
|
|
42
|
+
const seen = contracts.get(id);
|
|
43
|
+
if (!seen || Date.parse(row.observedAt) > Date.parse(seen.observedAt)) contracts.set(id, row);
|
|
44
|
+
}
|
|
45
|
+
const venues = new Map<string, PerpBoardRow[]>();
|
|
46
|
+
for (const row of contracts.values()) {
|
|
47
|
+
const key = `${row.venue}:${row.baseAsset.toUpperCase()}`;
|
|
48
|
+
venues.set(key, [...venues.get(key) ?? [], row]);
|
|
49
|
+
}
|
|
50
|
+
return [...venues].map(([key, group]): PerpVenueMarket => {
|
|
51
|
+
const sorted = [...group].sort((a, b) => deeper(a, b) || a.marketId.localeCompare(b.marketId));
|
|
52
|
+
return {
|
|
53
|
+
key,
|
|
54
|
+
venue: sorted[0]!.venue,
|
|
55
|
+
primary: sorted[0]!,
|
|
56
|
+
contracts: sorted,
|
|
57
|
+
openInterestUsd: sumOrNull(sorted.map((row) => row.openInterestUsd)),
|
|
58
|
+
volume24hUsd: sumOrNull(sorted.map((row) => row.volume24hUsd)),
|
|
59
|
+
};
|
|
60
|
+
}).sort((a, b) => deeper(a, b) || venueName(a.venue).localeCompare(venueName(b.venue)) || a.key.localeCompare(b.key));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** A market under this share of the asset's open interest is folded behind "more". */
|
|
64
|
+
const SMALL_MARKET_SHARE = 0.02;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The markets shown and those folded behind "more": a market holding less than
|
|
68
|
+
* `minShare` of the asset's open interest across venues, or past `maxShown`,
|
|
69
|
+
* is folded. The deepest market always shows, and one with no open interest
|
|
70
|
+
* stated shows only while no market states any.
|
|
71
|
+
*/
|
|
72
|
+
export function foldSmallMarkets(markets: readonly PerpVenueMarket[], { minShare = SMALL_MARKET_SHARE, maxShown = 4 }: { minShare?: number; maxShown?: number } = {}): {
|
|
73
|
+
shown: PerpVenueMarket[]; folded: PerpVenueMarket[];
|
|
74
|
+
} {
|
|
75
|
+
const total = sumOrNull(markets.map((market) => market.openInterestUsd));
|
|
76
|
+
const shown: PerpVenueMarket[] = [];
|
|
77
|
+
const folded: PerpVenueMarket[] = [];
|
|
78
|
+
for (const market of markets) {
|
|
79
|
+
const share = total && market.openInterestUsd != null ? market.openInterestUsd / total : total ? 0 : 1;
|
|
80
|
+
if (shown.length === 0 || (shown.length < maxShown && share >= minShare)) shown.push(market);
|
|
81
|
+
else folded.push(market);
|
|
82
|
+
}
|
|
83
|
+
return { shown, folded };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The contract a venue's row reads from, as the venue settles it (`USDT`, `USD inverse`, a Hyperliquid builder dex
|
|
88
|
+
* such as `xyz`), and how many more it stands for: `USDT +2`.
|
|
89
|
+
*/
|
|
90
|
+
export function contractText(market: PerpVenueMarket): string {
|
|
91
|
+
const row = market.primary;
|
|
92
|
+
const own = row.venue === "hyperliquid" && row.dex && row.dex !== "default" ? row.dex
|
|
93
|
+
: row.contractType === "inverse" ? `${row.quoteCurrency} inverse` : row.quoteCurrency;
|
|
94
|
+
const others = market.contracts.length - 1;
|
|
95
|
+
return others > 0 ? `${own} +${others}` : own;
|
|
96
|
+
}
|
|
@@ -10,6 +10,7 @@ export const pluginMarketplaceModule: PluginModule = {
|
|
|
10
10
|
name: "Plugins",
|
|
11
11
|
icon: "P",
|
|
12
12
|
component: PluginMarketplacePane,
|
|
13
|
+
reportFreshness: { status: "not-a-feed", basis: "plugin listings" },
|
|
13
14
|
defaultPosition: "right",
|
|
14
15
|
defaultMode: "floating",
|
|
15
16
|
defaultFloatingSize: { width: 118, height: 34 },
|
|
@@ -34,7 +35,7 @@ export const pluginMarketplaceModule: PluginModule = {
|
|
|
34
35
|
id: "plugin-marketplace-pane",
|
|
35
36
|
paneId: PLUGIN_MARKETPLACE_PANE_ID,
|
|
36
37
|
label: "Plugins",
|
|
37
|
-
description: "Browse and install
|
|
38
|
+
description: "Browse and install Gloom plugins, and enable or disable the ones you have.",
|
|
38
39
|
keywords: ["plugin", "plugins", "marketplace", "install", "extend", "addon", "extension"],
|
|
39
40
|
shortcut: { prefix: "PL" },
|
|
40
41
|
createInstance: () => ({ placement: "floating" }),
|