gloomberb 0.18.3 → 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/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 +84 -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 +22 -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 +21 -4
- 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/themes.ts +7 -1
- package/src/api-client/types.ts +90 -2
- 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/star-prompt/status-line.tsx +2 -2
- package/src/architecture/core-builtin-allowlist.json +5 -2
- 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 +1117 -264
- 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 +192 -47
- 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 +19 -13
- 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 +24 -18
- package/src/cli/known-gaps.ts +118 -25
- package/src/cli/listing-arg.ts +300 -41
- package/src/cli/name-entries.ts +30 -18
- 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 +75 -20
- package/src/cli/pane-functions/catalog.ts +24 -23
- package/src/cli/pane-functions/discovery.ts +1 -1
- package/src/cli/pane-functions/dom.ts +10 -7
- 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 +24 -11
- package/src/cli/pane-functions/index.ts +18 -16
- 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 +47 -10
- 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 +61 -16
- package/src/cli/restore-plugins.ts +29 -14
- package/src/cli/result.ts +111 -22
- 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 +198 -68
- 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 +373 -16
- package/src/components/chart/composite/interactions.ts +7 -2
- 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 +305 -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 +13 -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/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/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 +12 -6
- package/src/components/layout/header.tsx +7 -6
- 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/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/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 +29 -12
- package/src/i18n/ja.ts +29 -12
- package/src/i18n/ko.ts +29 -12
- package/src/i18n/zh-cn.ts +29 -12
- package/src/i18n/zh-tw.ts +29 -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 +29 -19
- 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 +107 -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 +76 -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 +97 -36
- package/src/plugins/builtin/attention/pane.tsx +6 -2
- 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 +6 -3
- 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 +22 -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 +110 -23
- package/src/plugins/builtin/broker-trading/ticket-parts.tsx +36 -7
- package/src/plugins/builtin/broker-trading/ticket.tsx +112 -31
- 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 +25 -12
- 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/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 +6 -8
- 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 +127 -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 +11 -4
- package/src/plugins/builtin/company-attention/app-rank.tsx +7 -5
- 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 +79 -23
- 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 +1 -1
- 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 +5 -3
- 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 +3 -2
- 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/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/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 +18 -11
- 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 +68 -6
- package/src/plugins/builtin/equity-screener/live.ts +1 -1
- package/src/plugins/builtin/equity-screener/model.ts +6 -0
- package/src/plugins/builtin/equity-screener/pane.tsx +45 -10
- 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 +6 -1
- 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/headless.ts +9 -4
- 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 +13 -1
- package/src/plugins/builtin/holders/format.ts +28 -17
- package/src/plugins/builtin/holders/headless.ts +46 -7
- 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/treemap.tsx +2 -2
- package/src/plugins/builtin/insider/headless.ts +1 -1
- package/src/plugins/builtin/insider/signals-client.ts +1 -1
- package/src/plugins/builtin/insider/signals.tsx +1 -1
- 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/headless.ts +4 -4
- 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 +10 -4
- 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 +33 -19
- 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 +29 -8
- 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 +49 -14
- package/src/plugins/builtin/openfda/pane.tsx +1 -1
- package/src/plugins/builtin/options/carry.ts +2 -2
- package/src/plugins/builtin/options/index.tsx +1 -1
- package/src/plugins/builtin/options/view.tsx +10 -10
- package/src/plugins/builtin/options-calculator/headless.ts +46 -7
- package/src/plugins/builtin/options-scenario/client.ts +123 -28
- 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 +36 -15
- 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 +9 -5
- 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 +143 -59
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +72 -24
- 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 +4 -1
- 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 +8 -1
- 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/model.ts +6 -2
- package/src/plugins/builtin/risk-factors/index.tsx +2 -2
- package/src/plugins/builtin/scanner/lock-note.ts +1 -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 +2 -1
- 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-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 +21 -0
- package/src/plugins/builtin/shared/series-cache.ts +2 -0
- package/src/plugins/builtin/shared/ticker-surface.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/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 +5 -4
- package/src/plugins/builtin/vol-surface/headless.ts +12 -3
- package/src/plugins/builtin/vol-surface/model.ts +4 -2
- 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/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/styles.css +76 -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/desktop/backend-requests.ts +3 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +46 -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/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 +5 -5
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -1
- 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 +1 -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 +32 -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/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 +17 -4
- 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 +210 -33
- package/src/tickers/index-roots.ts +47 -0
- package/src/tickers/instrument-label.ts +5 -1
- 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 +14 -0
- package/src/types/exchange-rate.ts +6 -0
- package/src/types/financials.ts +104 -7
- package/src/types/headless.ts +76 -6
- package/src/types/instrument.ts +3 -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/index.ts +10 -5
- 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-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 +3 -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
|
@@ -3,7 +3,7 @@ import type { OptionsChain } from "../../../types/financials";
|
|
|
3
3
|
import type { HeadlessPaneEntry } from "../../../types/headless";
|
|
4
4
|
import { numberErrorMessage } from "../../../utils/decimal-comma";
|
|
5
5
|
import { formatCompact } from "../../../utils/format";
|
|
6
|
-
import { UNKNOWN_CURRENCY, type ScenarioPosition } from "./model";
|
|
6
|
+
import { amountUnit, exportPercent, UNKNOWN_CURRENCY, type ScenarioPosition } from "./model";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Hedge budget sizing (docs/research-data.md#options-scenario-analysis): how
|
|
@@ -118,9 +118,10 @@ export interface HedgeQuote { bid: number; ask: number }
|
|
|
118
118
|
|
|
119
119
|
/**
|
|
120
120
|
* The bid and ask of each leg a strategy seeded from the chain, by leg id: the
|
|
121
|
-
* chain's, or the leg's streamed quote when the pane follows the market.
|
|
121
|
+
* chain's, or the leg's streamed quote when the pane follows the market. A
|
|
122
|
+
* typed leg has none.
|
|
122
123
|
*/
|
|
123
|
-
function
|
|
124
|
+
export function seededLegQuotes(
|
|
124
125
|
position: ScenarioPosition, chain: OptionsChain | null | undefined, liveQuotes?: ReadonlyMap<string, HedgeQuote>,
|
|
125
126
|
): Map<string, HedgeQuote> {
|
|
126
127
|
const contracts = new Map([...(chain?.calls ?? []), ...(chain?.puts ?? [])].map((contract) => [contract.contractSymbol, contract]));
|
|
@@ -206,7 +207,7 @@ export function scenarioHedgeBudget(
|
|
|
206
207
|
const inputs = hedgeInputsFromSettings(settings);
|
|
207
208
|
if (!inputs) return null;
|
|
208
209
|
if (!position?.legs.length) throw new Error("A hedge budget needs a position; add --legs or select --strategy.");
|
|
209
|
-
return sizeHedgeBudget(position, inputs, { quotes:
|
|
210
|
+
return sizeHedgeBudget(position, inputs, { quotes: seededLegQuotes(position, market?.chain, market?.liveQuotes),
|
|
210
211
|
spot: hedgeSpot(position, market?.spot, supplied(settings.spot)) });
|
|
211
212
|
}
|
|
212
213
|
|
|
@@ -246,21 +247,26 @@ function costText(cost: number, sizingPrice: number | null, multiplier: number,
|
|
|
246
247
|
/** The report's Hedge budget section; `value` keeps full precision, `formatted` is what text output prints. */
|
|
247
248
|
export function hedgeReportEntries(hedge: HedgeBudget): HeadlessPaneEntry[] {
|
|
248
249
|
const unit = hedge.currency && hedge.currency !== UNKNOWN_CURRENCY ? ` ${hedge.currency}` : "";
|
|
250
|
+
// Each figure's `value` is in its `unit`, a share of NAV in percent, so an export writes the number and its unit.
|
|
251
|
+
const amount = { unit: amountUnit(hedge.currency) };
|
|
249
252
|
return [
|
|
250
|
-
{ key: "nav", label: "NAV", value: hedge.nav, formatted: `${money(hedge.nav)}${unit}` },
|
|
251
|
-
{ key: "budget", label: "Budget", value: hedge.budget, formatted: `${money(hedge.budget)}${unit} (${formatBps(hedge.budgetBps)} of NAV)` },
|
|
252
|
-
{ key: "sizingPrice", label: "Sizing price", value: hedge.sizingPrice,
|
|
253
|
+
{ key: "nav", label: "NAV", value: hedge.nav, ...amount, formatted: `${money(hedge.nav)}${unit}` },
|
|
254
|
+
{ key: "budget", label: "Budget", value: hedge.budget, ...amount, formatted: `${money(hedge.budget)}${unit} (${formatBps(hedge.budgetBps)} of NAV)` },
|
|
255
|
+
{ key: "sizingPrice", label: "Sizing price", value: hedge.sizingPrice, unit: `${amount.unit} per share`,
|
|
253
256
|
formatted: hedge.sizingPrice == null ? "--" : describePrice(hedge.basis, hedge.singleLeg, hedge.sizingPrice) },
|
|
254
|
-
{ key: "costPerContract", label: "Cost per contract", value: hedge.costPerContract,
|
|
255
|
-
|
|
256
|
-
{ key: "
|
|
257
|
-
{ key: "
|
|
258
|
-
{ key: "
|
|
257
|
+
{ key: "costPerContract", label: "Cost per contract", value: hedge.costPerContract, ...amount,
|
|
258
|
+
formatted: costText(hedge.costPerContract, hedge.sizingPrice, hedge.multiplier, unit) },
|
|
259
|
+
{ key: "contracts", label: "Contracts", value: hedge.contracts, unit: "contracts", formatted: hedge.contracts.toLocaleString("en-US") },
|
|
260
|
+
{ key: "premium", label: "Premium spent", value: hedge.premium, ...amount, formatted: `${money(hedge.premium)}${unit}` },
|
|
261
|
+
{ key: "premiumBps", label: "Premium / NAV", value: hedge.premiumBps, unit: "bp",
|
|
262
|
+
formatted: `${formatBps(hedge.premiumBps)} (${(hedge.premiumBps / 100).toFixed(2)}% of NAV)` },
|
|
263
|
+
{ key: "notional", label: "Notional protected", value: hedge.notional, ...amount,
|
|
259
264
|
formatted: `${money(hedge.notional)}${unit} (${hedge.contracts.toLocaleString("en-US")} x ${hedge.multiplier} x ${formatHedgePrice(hedge.spot)})` },
|
|
260
|
-
{ key: "notionalOfNav", label: "Notional / NAV", value: hedge.notionalOfNav, formatted: percent(hedge.notionalOfNav) },
|
|
265
|
+
{ key: "notionalOfNav", label: "Notional / NAV", value: exportPercent(hedge.notionalOfNav), unit: "%", formatted: percent(hedge.notionalOfNav) },
|
|
261
266
|
hedge.coverage == null || hedge.sleeve == null
|
|
262
267
|
? { key: "coverage", label: "Coverage", value: null, formatted: "No equity sleeve given; add --sleeve <amount> to read notional against it" }
|
|
263
|
-
: { key: "coverage", label: "Coverage", value:
|
|
268
|
+
: { key: "coverage", label: "Coverage", value: exportPercent(hedge.coverage), unit: "%",
|
|
269
|
+
formatted: `${percent(hedge.coverage)} of a ${money(hedge.sleeve)}${unit} sleeve` },
|
|
264
270
|
];
|
|
265
271
|
}
|
|
266
272
|
|
|
@@ -2,8 +2,10 @@ import type { PluginModule } from "../plugin-module";
|
|
|
2
2
|
import { createTickerSurfacePaneTemplate } from "../shared/ticker-surface";
|
|
3
3
|
import { OptionsScenarioPane } from "./pane";
|
|
4
4
|
import { optionsScenarioHeadless } from "./headless";
|
|
5
|
+
import { CspScreenPane } from "./csp-screen-pane";
|
|
6
|
+
import { cspScreenHeadless } from "./csp-screen-headless";
|
|
5
7
|
|
|
6
|
-
const scenarioSettingKeys = ["legs", "strategy", "strike", "putStrike", "callStrike", "shares", "expiration", "spot", "rate", "dividendYield", "currency", "asOf", "date", "volShift",
|
|
8
|
+
const scenarioSettingKeys = ["legs", "strategy", "strike", "delta", "putStrike", "callStrike", "shares", "expiration", "spot", "rate", "dividendYield", "currency", "asOf", "date", "volShift",
|
|
7
9
|
"spotRange", "seedLeg", "nav", "budgetBps", "sleeve"];
|
|
8
10
|
|
|
9
11
|
export const optionsScenarioModule: PluginModule = {
|
|
@@ -23,11 +25,34 @@ export const optionsScenarioModule: PluginModule = {
|
|
|
23
25
|
description: "The equity the hedge protects, for coverage: notional protected over the sleeve." },
|
|
24
26
|
],
|
|
25
27
|
},
|
|
28
|
+
}, {
|
|
29
|
+
id: "csp-screen", name: "Cash-Secured Put Screen", icon: "V", component: CspScreenPane,
|
|
30
|
+
defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 144, height: 24 },
|
|
31
|
+
tableExport: true, headless: cspScreenHeadless,
|
|
32
|
+
settings: {
|
|
33
|
+
title: "Cash-Secured Put Screen",
|
|
34
|
+
fields: [
|
|
35
|
+
{ key: "symbols", label: "Symbols", type: "text", placeholder: "JPM, GS, KO" },
|
|
36
|
+
{ key: "maxSpread", label: "Wide spread over (%)", type: "text", placeholder: "10",
|
|
37
|
+
description: "A put whose bid/ask spread is over this percent of its mid is marked wide. It is still ranked." },
|
|
38
|
+
],
|
|
39
|
+
},
|
|
26
40
|
}],
|
|
27
|
-
paneTemplates: [
|
|
41
|
+
paneTemplates: [{
|
|
42
|
+
id: "csp-screen-pane", paneId: "csp-screen", label: "Cash-Secured Put Screen",
|
|
43
|
+
description: "Cash-secured puts across a list, ranked by annualised yield at the bid, with % OTM, delta, IV rank, spread and an earnings flag.",
|
|
44
|
+
// The put-selling phrases lead here first, then to OSA for one name.
|
|
45
|
+
keywords: ["csp", "cash secured put", "cash-secured puts", "put selling", "selling puts", "sell put", "sell puts", "short put", "wheel",
|
|
46
|
+
"put screen", "premium yield"],
|
|
47
|
+
shortcut: { prefix: "CSP", argKind: "ticker-list" as const }, headless: cspScreenHeadless,
|
|
48
|
+
createInstance: (_context, options) => ({
|
|
49
|
+
title: "CSP", placement: "floating" as const,
|
|
50
|
+
settings: options?.symbols?.length ? { symbols: options.symbols.join(",") } : {},
|
|
51
|
+
}),
|
|
52
|
+
}, createTickerSurfacePaneTemplate({
|
|
28
53
|
id: "options-scenario-pane", paneId: "options-scenario", label: "Options Scenario",
|
|
29
54
|
description: "Build multi-leg positions and compare scenario P&L, payoff and aggregate Greeks.",
|
|
30
|
-
// OSA
|
|
55
|
+
// OSA carries the put-selling phrases after CSP, which ranks names on them; OMON and VCA carry the same words, so a search finds all four.
|
|
31
56
|
keywords: ["osa", "options", "scenario", "payoff", "strategy", "greeks", "hedge", "hedging", "collar", "protective put", "put spread",
|
|
32
57
|
"cash secured put", "csp", "put selling", "sell put", "short put", "wheel"],
|
|
33
58
|
shortcut: "OSA", publicShare: true,
|
|
@@ -160,6 +160,19 @@ export function validateScenarioInputs(position: ScenarioPosition): string | nul
|
|
|
160
160
|
/** The stored stand-in for a currency nothing names; shown as `currency unknown`. */
|
|
161
161
|
export const UNKNOWN_CURRENCY = "UNKNOWN";
|
|
162
162
|
export const currencyLabel = (currency: string) => currency === UNKNOWN_CURRENCY ? "currency unknown" : currency;
|
|
163
|
+
/** The unit an export names beside an amount: the currency code, or `currency unknown`. */
|
|
164
|
+
export const amountUnit = (currency: string) => currency && currency !== UNKNOWN_CURRENCY ? currency : "currency unknown";
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* A fraction as the percent an export carries: a rate of 0.041 is 4.1, not
|
|
168
|
+
* the 4.1000000000000005 the product leaves. Twelve significant digits keep
|
|
169
|
+
* every digit a figure has; null stays null.
|
|
170
|
+
*/
|
|
171
|
+
export function exportPercent(fraction: number): number;
|
|
172
|
+
export function exportPercent(fraction: number | null | undefined): number | null;
|
|
173
|
+
export function exportPercent(fraction: number | null | undefined): number | null {
|
|
174
|
+
return fraction == null ? null : Number((fraction * 100).toPrecision(12));
|
|
175
|
+
}
|
|
163
176
|
|
|
164
177
|
/**
|
|
165
178
|
* The position's currency: the one typed, else the underlying's quote
|
|
@@ -38,8 +38,8 @@ import { buildScenario, currencyLabel, scenarioCurrency, scenarioValueUnit, UNKN
|
|
|
38
38
|
type ScenarioControls, validatePosition } from "./model";
|
|
39
39
|
import { EMPTY_SAVED_STRATEGIES, restoreSavedStrategies, type SavedScenarioStrategy } from "./state";
|
|
40
40
|
import { useScenarioEvidence } from "./evidence";
|
|
41
|
-
import { hedgeStatItems, scenarioHedgeBudget, type HedgeQuote } from "./hedge";
|
|
42
|
-
import { readOptionExpiration } from "../../../utils/option-expiry";
|
|
41
|
+
import { hedgeStatItems, scenarioHedgeBudget, seededLegQuotes, type HedgeQuote } from "./hedge";
|
|
42
|
+
import { isLiveOptionExpiry, readOptionExpiration } from "../../../utils/option-expiry";
|
|
43
43
|
import { parsePublicTickerKey } from "../../../utils/exchanges";
|
|
44
44
|
|
|
45
45
|
const EMPTY_ERRORS: string[] = [];
|
|
@@ -65,7 +65,9 @@ export function OptionsScenarioPane({ width, height, focused }: PaneProps) {
|
|
|
65
65
|
const underlying = listed.symbol;
|
|
66
66
|
const exchange = listed.exchange || target?.effectiveExchange || undefined;
|
|
67
67
|
// A strategy seeded for an expiry (`OSA SPY --strategy put --expiration 2027-01-15`) opens that expiry's chain.
|
|
68
|
-
const [
|
|
68
|
+
const [pickedExpiration, setExpiration] = usePluginPaneState<number | null>("chainExpiration", readOptionExpiration(settings.expiration));
|
|
69
|
+
// A picked expiry that has since lapsed gives way to the nearest live one, as OMON's does.
|
|
70
|
+
const expiration = pickedExpiration != null && isLiveOptionExpiry(pickedExpiration, Date.now(), exchange) ? pickedExpiration : null;
|
|
69
71
|
const [tab, setTab] = usePluginPaneState("activeTabId", "payoff");
|
|
70
72
|
const [detail, setDetail] = useState<"chain" | "leg" | "save" | "inputs" | null>(null);
|
|
71
73
|
const [editingLeg, setEditingLeg] = useState<ScenarioLeg | null>(null);
|
|
@@ -166,9 +168,11 @@ export function OptionsScenarioPane({ width, height, focused }: PaneProps) {
|
|
|
166
168
|
const earningsDate = frozenEarnings ? settings.scenarioEarningsDate as string | null : earnings.data;
|
|
167
169
|
// A position following the market is at the live print, which the footer names with its feed.
|
|
168
170
|
const spotBasis = scenario && !live ? scenarioSpotBasis(settings, scenario.position, market) : null;
|
|
171
|
+
// The put's bid and ask: its streamed quote while the pane follows the market, else the chain's.
|
|
169
172
|
const csp = useMemo(() => cspStrategy && scenario && !earnings.loading
|
|
170
|
-
? cashSecuredPut(scenario.position, live?.basis ?? spotBasis ?? "last price", earningsDate
|
|
171
|
-
|
|
173
|
+
? cashSecuredPut(scenario.position, live?.basis ?? spotBasis ?? "last price", earningsDate,
|
|
174
|
+
seededLegQuotes(scenario.position, market?.chain, live?.quotes).get(scenario.position.legs[0]?.id ?? "")) : null,
|
|
175
|
+
[cspStrategy, scenario, earnings.loading, earningsDate, live?.basis, live?.quotes, spotBasis, market?.chain]);
|
|
172
176
|
// A collar seeded, or one typed with the shares it hedges, as the report reads it.
|
|
173
177
|
const collarStrategy = settings.strategy === "collar";
|
|
174
178
|
const collar = useMemo(() => scenario && (collarStrategy || scenario.position.underlying)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback } from "react";
|
|
1
|
+
import { useCallback, useMemo } from "react";
|
|
2
2
|
import { isAccessDenied } from "../../../api-client/errors";
|
|
3
3
|
import { usePlanAccess } from "../../../api-client/plan-access";
|
|
4
4
|
import type { PerpBoardRow } from "../../../api-client/perps";
|
|
@@ -10,7 +10,9 @@ import { perpEquityIdentity } from "./equity-identity";
|
|
|
10
10
|
import { perpetualListing } from "../../../tickers/perpetual-listing";
|
|
11
11
|
import { loadPerpSelection, loadPerpsEquity } from "./client";
|
|
12
12
|
import { compact, percent, perpDescription, price, readingTime } from "./model";
|
|
13
|
+
import { useLivePerpRows } from "./live-rows";
|
|
13
14
|
import { openPerpLabel, useOpenPerpMarket } from "./open-market";
|
|
15
|
+
import { mergeVenueMarkets } from "./venue-markets";
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* A stock's perpetuals, or for a perpetual market itself its own contract: funding, open interest and when the venue
|
|
@@ -34,9 +36,15 @@ export function PerpEquityRow({ symbol, exchange, instrumentType, maxRows = 3, w
|
|
|
34
36
|
: loadPerpsEquity({ symbol: listingSymbol!, exchange: listingExchange! }, accessKey, force), [marketId, listingSymbol, listingExchange, accessKey]);
|
|
35
37
|
const resource = useAsyncResource(enabled ? loader : null, { clearOnError: isAccessDenied });
|
|
36
38
|
useAutoRefresh(resource.updatedAt, resource.load, { intervalMs: 60_000 });
|
|
37
|
-
|
|
39
|
+
// A stock's contracts one per venue, deepest first, as DES lists them; the ones shown follow the quote stream.
|
|
40
|
+
const fetched = useMemo(() => {
|
|
41
|
+
const rows = resource.data?.payload.rows ?? [];
|
|
42
|
+
return marketId ? rows.filter((row) => !row.delisted && row.marketId === marketId) : mergeVenueMarkets(rows).map((market) => market.primary);
|
|
43
|
+
}, [marketId, resource.data]);
|
|
44
|
+
const shown = useMemo(() => fetched.slice(0, maxRows), [fetched, maxRows]);
|
|
45
|
+
const { rows } = useLivePerpRows(shown, { surface: marketId ? "detail" : "monitor", enabled });
|
|
38
46
|
if (!enabled || !rows.length) return null;
|
|
39
|
-
return <>{rows.
|
|
47
|
+
return <>{rows.map((row) => <PerpRow key={row.marketId} row={row} stale={!!resource.data?.stale} width={width} own={!!marketId} />)}</>;
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
/** The parts that fit `width`, dropped from the end; all of them when the width is not known. */
|
|
@@ -48,18 +48,21 @@ export function fundingNote(rows: readonly Funded[], now = Date.now()): string |
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
* How current a view of perpetual markets is: each venue's own newest observation,
|
|
52
|
-
*
|
|
53
|
-
* and the pane footer all read this, so they
|
|
51
|
+
* How current a view of perpetual markets is: each venue's own newest observation, read once a minute (live while
|
|
52
|
+
* every row followed has a price from its venue's socket within the minute) and stale after an hour without a read, and
|
|
53
|
+
* what the funding figures it shows (`funding`) are. The report, the capture and the pane footer all read this, so they
|
|
54
|
+
* end with the same line.
|
|
54
55
|
*/
|
|
55
|
-
export function perpFreshness(rows: readonly Dated[], { funding = [], venue = "all", now = Date.now() }: {
|
|
56
|
+
export function perpFreshness(rows: readonly Dated[], { funding = [], venue = "all", now = Date.now(), live = false }: {
|
|
56
57
|
funding?: readonly Funded[]; venue?: string; now?: number;
|
|
58
|
+
/** Every row followed moves with its venue's socket: the view is a live feed, not the minute's read. */
|
|
59
|
+
live?: boolean;
|
|
57
60
|
} = {}): HeadlessPaneFreshness {
|
|
58
61
|
const sources = venuesOf(rows).map((id) => ({ source: venueName(id), asOf: new Date(Math.max(...rows.filter((row) => row.venue === id)
|
|
59
62
|
.map((row) => Date.parse(row.observedAt)))).toISOString() }));
|
|
60
63
|
const source = sources.length ? sources.map((entry) => entry.source).join(", ") : venue !== "all" ? venueName(venue) : null;
|
|
61
64
|
const note = fundingNote(funding, now);
|
|
62
|
-
return { ...(source ? { source } : {}), sources, status: "delayed", maxAgeMinutes: 60, ...(note ? { note } : {}) };
|
|
65
|
+
return { ...(source ? { source } : {}), sources, status: live ? "live" : "delayed", maxAgeMinutes: 60, ...(note ? { note } : {}) };
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
/** What a capture reads of a row: when it was observed and whether the venue has stopped answering for it. */
|
|
@@ -89,8 +89,10 @@ const aprNote = (rows: readonly PerpBoardRow[]) => {
|
|
|
89
89
|
const requestedOf = (args: HeadlessPaneLoadArgs) => String(Array.isArray(args.argument) ? args.argument.join(" ") : args.argument ?? "").trim();
|
|
90
90
|
// No tab named: a market opens its history, nothing opens the board, as the pane does.
|
|
91
91
|
const tabOf = (args: HeadlessPaneLoadArgs) => PERP_TABS.find((tab) => tab === args.options.tab) ?? openingTab(requestedOf(args));
|
|
92
|
+
// A free account's market opens its latest values under the title, so the title names the function, not the Pro history: a table the free view
|
|
93
|
+
// locks is left out and named on the report's one Pro line (`lockedTable`), never marked on the title over the free ones.
|
|
92
94
|
const TITLES: Record<typeof PERP_TABS[number], string> = { board: "Perpetual markets", rankings: "Perpetual rankings", compare: "Perpetual contracts across venues",
|
|
93
|
-
history: "
|
|
95
|
+
history: "Perpetuals", evidence: "Perpetual evidence" };
|
|
94
96
|
const copy = (rows: readonly PerpBoardRow[]) => rows.map((row) => ({ ...row }));
|
|
95
97
|
const more = (count: number, noun: string) => `${count.toLocaleString("en-US")} more ${noun} need Gloom Pro`;
|
|
96
98
|
// A table the free view leaves empty is locked: the report leaves it out and its one Pro line says what Pro adds.
|
|
@@ -8,6 +8,7 @@ import { useResearchCloudSession } from "../shared/research-cloud-session";
|
|
|
8
8
|
import { perpetualListing } from "../../../tickers/perpetual-listing";
|
|
9
9
|
import { loadPerpSelection, loadPerpsEquities } from "./client";
|
|
10
10
|
import { perpEquityIdentity } from "./equity-identity";
|
|
11
|
+
import { useLivePerpRows } from "./live-rows";
|
|
11
12
|
|
|
12
13
|
const STOCK_TYPES = new Set(["STK", "EQUITY", "STOCK", "COMMON STOCK"]);
|
|
13
14
|
const listingKey = (listing: PerpEquityListing) => `${listing.symbol}:${listing.exchange}`;
|
|
@@ -26,6 +27,7 @@ function rowListing(ticker: TickerRecord): PerpEquityListing | null {
|
|
|
26
27
|
const rowMarket = (ticker: TickerRecord) => perpetualListing(ticker.metadata.ticker, ticker.metadata.exchange)?.marketId ?? null;
|
|
27
28
|
|
|
28
29
|
const NO_PERPETUALS: ListPerpetuals = { markets: new Map(), version: 0, stale: false };
|
|
30
|
+
const NO_SYMBOLS: ReadonlySet<string> = new Set();
|
|
29
31
|
|
|
30
32
|
export interface ListPerpetuals {
|
|
31
33
|
/** Each row's contract by ticker, null for none; a row is missing while its first answer loads. */
|
|
@@ -55,7 +57,7 @@ async function loadOwnMarket(marketId: string, access: string, force: boolean):
|
|
|
55
57
|
* A row's figures never wait on anything else: rows without a stock listing read as no perpetual at once, and a
|
|
56
58
|
* failed or refused lookup leaves every row without one, as DES leaves out its perpetual row.
|
|
57
59
|
*/
|
|
58
|
-
export function useListPerpetuals(tickers: readonly TickerRecord[], enabled: boolean): ListPerpetuals {
|
|
60
|
+
export function useListPerpetuals(tickers: readonly TickerRecord[], enabled: boolean, liveSymbols: ReadonlySet<string> = NO_SYMBOLS): ListPerpetuals {
|
|
59
61
|
const session = useResearchCloudSession();
|
|
60
62
|
const plan = usePlanAccess();
|
|
61
63
|
const accessKey = `${session.requestKey}:${plan.hasProAccess ? "pro" : "preview"}`;
|
|
@@ -98,7 +100,7 @@ export function useListPerpetuals(tickers: readonly TickerRecord[], enabled: boo
|
|
|
98
100
|
useAutoRefresh(resource.updatedAt, resource.load, { intervalMs: 60_000 });
|
|
99
101
|
const versionRef = useRef(0);
|
|
100
102
|
const { data, loading } = resource;
|
|
101
|
-
|
|
103
|
+
const fetched = useMemo(() => {
|
|
102
104
|
if (!enabled) return NO_PERPETUALS;
|
|
103
105
|
const byListing = new Map<string, PerpBoardRow | null>();
|
|
104
106
|
data?.listings.forEach((listing, index) => byListing.set(listingKey(listing), data.results[index]?.market ?? null));
|
|
@@ -112,7 +114,26 @@ export function useListPerpetuals(tickers: readonly TickerRecord[], enabled: boo
|
|
|
112
114
|
// Still loading: the row's cell waits. Otherwise there is nothing to show.
|
|
113
115
|
else if ((!key && !market) || !loading) markets.set(ticker.metadata.ticker, null);
|
|
114
116
|
}
|
|
115
|
-
|
|
116
|
-
return { markets, version: versionRef.current, stale: !!data?.stale };
|
|
117
|
+
return { markets, version: 0, stale: !!data?.stale };
|
|
117
118
|
}, [data, enabled, loading, tickers]);
|
|
119
|
+
// The contracts of the rows in view follow the quote stream, as many as useLivePerpRows follows; each is subscribed
|
|
120
|
+
// once however many rows share it.
|
|
121
|
+
const liveRows = useMemo(() => {
|
|
122
|
+
const rows = new Map<string, PerpBoardRow>();
|
|
123
|
+
for (const symbol of liveSymbols) {
|
|
124
|
+
const row = fetched.markets.get(symbol);
|
|
125
|
+
if (row) rows.set(row.marketId, row);
|
|
126
|
+
}
|
|
127
|
+
return [...rows.values()];
|
|
128
|
+
}, [fetched, liveSymbols]);
|
|
129
|
+
const live = useLivePerpRows(liveRows, { surface: "watchlist", enabled: enabled && liveRows.length > 0 });
|
|
130
|
+
return useMemo(() => {
|
|
131
|
+
if (fetched === NO_PERPETUALS) return NO_PERPETUALS;
|
|
132
|
+
const byMarket = new Map(live.rows.map((row) => [row.marketId, row]));
|
|
133
|
+
const markets = byMarket.size
|
|
134
|
+
? new Map([...fetched.markets].map(([symbol, row]) => [symbol, row ? byMarket.get(row.marketId) ?? row : row]))
|
|
135
|
+
: fetched.markets;
|
|
136
|
+
versionRef.current += 1;
|
|
137
|
+
return { markets, version: versionRef.current, stale: fetched.stale };
|
|
138
|
+
}, [fetched, live.rows]);
|
|
118
139
|
}
|
|
@@ -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
|
+
}
|