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
|
@@ -6,15 +6,19 @@ import type { DataProvider } from "../../../types/data-provider";
|
|
|
6
6
|
import type { CorporateActionsData, OptionsChain, Quote, TickerFinancials } from "../../../types/financials";
|
|
7
7
|
import { canonicalExchange, parsePublicTickerKey } from "../../../utils/exchanges";
|
|
8
8
|
import { numberErrorMessage } from "../../../utils/decimal-comma";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
expiredExpiryText, expiryChoiceText, expiryDayLabel, expiryIsoDate, findListedExpiry, isLiveOptionExpiry, liveDefaultExpiry, liveOptionExpiries,
|
|
11
|
+
missingExpiryText, nextLiveExpiry,
|
|
12
|
+
} from "../../../utils/option-expiry";
|
|
10
13
|
import { surfaceTreasuryRate } from "../vol-surface/model";
|
|
11
|
-
import { daysToExpiryFrom, extractImpliedForward, optionMid, solveImpliedVolatility } from "../shared/volatility";
|
|
14
|
+
import { daysToExpiryFrom, extractImpliedForward, optionDelta, optionMid, solveImpliedVolatility, valueOption } from "../shared/volatility";
|
|
12
15
|
import type { YieldPoint } from "../yield-curve/treasury-data";
|
|
13
16
|
import { parseLegs, scenarioCurrency, validatePosition, type ScenarioControls, type ScenarioLeg, type ScenarioPosition } from "./model";
|
|
14
17
|
import { loadModelDividendYield, type ModelDividendYield } from "../../../market-data/model-dividend-yield";
|
|
15
18
|
import { abortable, abortError } from "../../../utils/async-deadline";
|
|
16
19
|
import { extendedSession, modelSpotStamp, modelSpotStampVariants, type OptionModelSpot } from "../options/model-spot";
|
|
17
20
|
import { errorMessage } from "../../../utils/errors";
|
|
21
|
+
import { dateOptionError, dateOptionFormatsOr, readCalendarDate } from "../../../utils/calendar-date";
|
|
18
22
|
|
|
19
23
|
export interface ScenarioMarketSnapshot {
|
|
20
24
|
symbol: string;
|
|
@@ -86,10 +90,13 @@ export async function loadScenarioMarket(
|
|
|
86
90
|
const symbol = request.instrument.symbol.trim().toUpperCase();
|
|
87
91
|
const options = { forceRefresh: request.forceRefresh };
|
|
88
92
|
const warnings: string[] = [];
|
|
93
|
+
const exchange = request.instrument.exchange;
|
|
94
|
+
// An expiry whose last session has closed has no chain: the default one lists the live expiries to name instead.
|
|
95
|
+
const expired = request.expiration != null && !isLiveOptionExpiry(request.expiration, now, exchange);
|
|
89
96
|
const settled = await abortable(Promise.allSettled([
|
|
90
97
|
dependencies.loadQuote(request.instrument, options),
|
|
91
98
|
dependencies.loadSnapshot(request.instrument, options),
|
|
92
|
-
dependencies.loadOptions({ instrument: request.instrument, expirationDate: request.expiration }, options),
|
|
99
|
+
dependencies.loadOptions({ instrument: request.instrument, expirationDate: expired ? undefined : request.expiration }, options),
|
|
93
100
|
dependencies.loadYieldCurve(),
|
|
94
101
|
]), request.signal, CANCELLED);
|
|
95
102
|
if (request.signal?.aborted) throw abortError(CANCELLED);
|
|
@@ -125,31 +132,41 @@ export async function loadScenarioMarket(
|
|
|
125
132
|
if (chain && !symbolMatches(chain.underlyingSymbol, symbol)) {
|
|
126
133
|
warnings.push("Options chain does not match the requested ticker"); chain = null;
|
|
127
134
|
}
|
|
128
|
-
const expirationDates =
|
|
129
|
-
positive(expiration) && daysToExpiryFrom(expiration, now) > 0))].sort((a, b) => a - b);
|
|
135
|
+
const expirationDates = liveOptionExpiries(chain?.expirationDates ?? [], now, exchange);
|
|
130
136
|
// A typed date is that day's UTC midnight; the chain's own stamp for the
|
|
131
137
|
// expiry is matched by calendar date, as OMON matches it.
|
|
132
|
-
const listedExpiration = request.expiration == null ? undefined : findListedExpiry(request.expiration, expirationDates);
|
|
138
|
+
const listedExpiration = request.expiration == null || expired ? undefined : findListedExpiry(request.expiration, expirationDates);
|
|
133
139
|
const onExpiry = (value: OptionsChain, expiration: number) => [...value.calls, ...value.puts]
|
|
134
140
|
.every((contract) => expiryIsoDate(contract.expiration) === expiryIsoDate(expiration));
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
141
|
+
// A default chain on the expiry that lapsed at the last close gives way to the nearest live one.
|
|
142
|
+
const liveDefault = request.expiration == null && chain ? liveDefaultExpiry(chain, now, exchange) : undefined;
|
|
143
|
+
const reload = liveDefault ?? (chain && listedExpiration != null && listedExpiration !== request.expiration
|
|
144
|
+
&& !chain.calls.length && !chain.puts.length ? listedExpiration : undefined);
|
|
145
|
+
if (reload != null) {
|
|
146
|
+
// Or a source that keys chains by its exact stamp answered the midnight request empty.
|
|
147
|
+
chainEntry = await abortable(dependencies.loadOptions({ instrument: request.instrument, expirationDate: reload }, options)
|
|
138
148
|
.catch((error: unknown) => { warnings.push(`Options chain: ${errorMessage(error)}`); return null; }), request.signal, CANCELLED);
|
|
139
149
|
if (request.signal?.aborted) throw abortError(CANCELLED);
|
|
140
150
|
if (chainEntry?.error) warnings.push(`Options chain: ${chainEntry.error.message}`);
|
|
141
151
|
chain = chainEntry ? resolveEntryValue(chainEntry) : null;
|
|
142
152
|
}
|
|
143
153
|
let missingExpiry: string | undefined;
|
|
144
|
-
if (
|
|
154
|
+
if (expired) {
|
|
155
|
+
missingExpiry = sentence(`expiry ${expiryIsoDate(request.expiration!)} for ${symbol} `
|
|
156
|
+
+ expiredExpiryText(request.expiration!, expirationDates, now, { exchange, flag: "expiration" }));
|
|
157
|
+
warnings.push(missingExpiry); chain = null;
|
|
158
|
+
} else if (chain && request.expiration != null && listedExpiration == null) {
|
|
145
159
|
missingExpiry = sentence(missingExpiryText(request.expiration, expirationDates, symbol));
|
|
146
160
|
warnings.push(missingExpiry); chain = null;
|
|
147
|
-
} else if (
|
|
161
|
+
} else if (liveDefault === null) {
|
|
162
|
+
missingExpiry = `No live expiry listed for ${symbol}`;
|
|
163
|
+
warnings.push(missingExpiry); chain = null;
|
|
164
|
+
} else if (chain && (listedExpiration ?? liveDefault) != null && !onExpiry(chain, (listedExpiration ?? liveDefault)!)) {
|
|
148
165
|
warnings.push("Selected expiration unavailable in the returned options chain"); chain = null;
|
|
149
166
|
}
|
|
150
167
|
if (!chain) warnings.push("Options chain unavailable");
|
|
151
168
|
else if (chainEntry?.error || (chainEntry?.staleAt != null && chainEntry.staleAt <= now)) warnings.push("Options chain is stale");
|
|
152
|
-
const rateExpiration = request.rateExpiration ?? request.expiration ?? expirationDates[0];
|
|
169
|
+
const rateExpiration = request.rateExpiration ?? (expired ? undefined : request.expiration) ?? expirationDates[0];
|
|
153
170
|
const curve = curveResult.status === "fulfilled" ? curveResult.value : [];
|
|
154
171
|
if (curveResult.status === "rejected") warnings.push(`Treasury: ${errorMessage(curveResult.reason)}`);
|
|
155
172
|
const rate = surfaceTreasuryRate(curve, rateExpiration == null ? NaN : daysToExpiryFrom(rateExpiration, now) / 365);
|
|
@@ -225,13 +242,17 @@ function numericSetting(settings: Record<string, unknown>, key: string): number
|
|
|
225
242
|
return Number(value);
|
|
226
243
|
}
|
|
227
244
|
|
|
228
|
-
/** Dates are explicit UTC ISO timestamps or UTC calendar dates. Milliseconds are accepted in persisted state. */
|
|
245
|
+
/** Dates are explicit UTC ISO timestamps or UTC calendar dates, written as a date option takes them. Milliseconds are accepted in persisted state. */
|
|
229
246
|
function dateSetting(settings: Record<string, unknown>, key: string): number | undefined {
|
|
230
247
|
const value = settings[key];
|
|
231
248
|
if (!supplied(value)) return undefined;
|
|
232
249
|
if (typeof value === "number" && positive(value) && Number.isFinite(new Date(value).getTime())) return value;
|
|
250
|
+
const day = typeof value === "string" ? readCalendarDate(value) : null;
|
|
251
|
+
if (day) return Date.parse(`${day}T00:00:00Z`);
|
|
233
252
|
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?Z)?$/.test(value)) {
|
|
234
|
-
throw new Error(
|
|
253
|
+
throw new Error(dateOptionError(String(value), key === "asOf" ? "--as-of" : `--${key}`, {
|
|
254
|
+
subject: key === "asOf" ? "a valuation date" : "a scenario date", formats: dateOptionFormatsOr("an ISO timestamp ending in Z"),
|
|
255
|
+
}));
|
|
235
256
|
}
|
|
236
257
|
const date = Date.parse(value);
|
|
237
258
|
if (!positive(date) || new Date(date).toISOString().slice(0, 10) !== value.slice(0, 10)) throw new Error(`${key} is not a valid date`);
|
|
@@ -266,6 +287,8 @@ export function scenarioPositionFromSettings(
|
|
|
266
287
|
const strike = numericSetting(settings, "strike");
|
|
267
288
|
if (strike != null && !(strike > 0)) throw new Error("strike must be positive");
|
|
268
289
|
if (strike != null && !supplied(settings.strategy)) throw new Error("strike picks the strike of --strategy; add --strategy put, csp, vertical or straddle");
|
|
290
|
+
const delta = putDeltaSetting(settings);
|
|
291
|
+
if (delta != null && strike != null) throw new Error("delta and strike each pick the put; give one of them");
|
|
269
292
|
const collarStrikes = { put: numericSetting(settings, "putStrike"), call: numericSetting(settings, "callStrike") };
|
|
270
293
|
if ((collarStrikes.put != null || collarStrikes.call != null) && settings.strategy !== "collar") {
|
|
271
294
|
throw new Error("put-strike and call-strike pick a collar's strikes; add --strategy collar");
|
|
@@ -288,8 +311,10 @@ export function scenarioPositionFromSettings(
|
|
|
288
311
|
}
|
|
289
312
|
if (spot == null) throw new Error("A current underlying price or explicit --spot is required");
|
|
290
313
|
// Leg IVs are solved at the market's own spot and time, never at a what-if override.
|
|
291
|
-
const pricing = market.spot != null && rate != null ? { spot: market.spot, asOf: market.asOf, rate } : null;
|
|
292
|
-
|
|
314
|
+
const pricing = market.spot != null && rate != null ? { spot: market.spot, asOf: market.asOf, rate, dividendYield: dividendYield ?? 0 } : null;
|
|
315
|
+
if (delta != null && !pricing) throw new Error("Choosing the put by delta needs the Treasury rate; supply --rate, or pick it with --strike");
|
|
316
|
+
const seeded = scenarioStrategyLegs(market.chain, spot, settings.strategy, pricing, strike, collarStrikes, delta,
|
|
317
|
+
unusableChainText(market, exchange));
|
|
293
318
|
legs = seeded.legs;
|
|
294
319
|
shareEntry = market.spot;
|
|
295
320
|
// Mids priced off the parity forward are worth their cost only at the spot
|
|
@@ -326,6 +351,19 @@ export function scenarioPositionFromSettings(
|
|
|
326
351
|
return position;
|
|
327
352
|
}
|
|
328
353
|
|
|
354
|
+
/**
|
|
355
|
+
* `--delta`: the put a csp or put seed sells or buys, by the absolute delta it
|
|
356
|
+
* is nearest (0.30, or -0.30 as a put's own sign reads it); undefined when not set.
|
|
357
|
+
*/
|
|
358
|
+
function putDeltaSetting(settings: Record<string, unknown>): number | undefined {
|
|
359
|
+
const value = numericSetting(settings, "delta");
|
|
360
|
+
if (value == null) return undefined;
|
|
361
|
+
if (settings.strategy !== "csp" && settings.strategy !== "put") throw new Error("delta picks the put of --strategy csp or put");
|
|
362
|
+
const delta = Math.abs(value);
|
|
363
|
+
if (!(delta > 0 && delta < 1)) throw new Error(`delta must be a put's delta between 0 and 1, such as 0.30; got ${String(settings.delta)}`);
|
|
364
|
+
return delta;
|
|
365
|
+
}
|
|
366
|
+
|
|
329
367
|
/** `--shares`: a signed whole number of shares of the underlying, or undefined when not set. */
|
|
330
368
|
function sharesSetting(settings: Record<string, unknown>): number | undefined {
|
|
331
369
|
const value = settings.shares;
|
|
@@ -346,7 +384,7 @@ export const SCENARIO_STRATEGIES = ["vertical", "straddle", "put", "csp", "colla
|
|
|
346
384
|
type ScenarioStrategy = typeof SCENARIO_STRATEGIES[number];
|
|
347
385
|
const isScenarioStrategy = (value: unknown): value is ScenarioStrategy => SCENARIO_STRATEGIES.includes(value as ScenarioStrategy);
|
|
348
386
|
|
|
349
|
-
interface StrategyPricing { spot: number; asOf: number; rate: number }
|
|
387
|
+
interface StrategyPricing { spot: number; asOf: number; rate: number; dividendYield: number }
|
|
350
388
|
|
|
351
389
|
const usableVolatility = (value: unknown): value is number => positive(value) && value <= 5;
|
|
352
390
|
|
|
@@ -357,8 +395,8 @@ interface StrategyParity { forward: number; daysToExpiry: number }
|
|
|
357
395
|
* at its quote midpoint, so each leg takes the IV that midpoint implies against
|
|
358
396
|
* the expiry's put-call parity forward, with the time left at the market
|
|
359
397
|
* timestamp OSA values at. This holds even when the chain carries a provider
|
|
360
|
-
* IV measured at another time (a same-day IV solved
|
|
361
|
-
* hours
|
|
398
|
+
* IV measured at another time (a same-day IV solved at the close's quotes,
|
|
399
|
+
* hours before the after-hours print OSA values at). The forward comes from the
|
|
362
400
|
* same quotes, so a spot that moved after they were taken (an after-hours or
|
|
363
401
|
* pre-market print against the close's chain) does not skew the call against
|
|
364
402
|
* the put. A leg whose midpoint cannot be solved (a spot too far from every
|
|
@@ -373,10 +411,12 @@ interface StrategyParity { forward: number; daysToExpiry: number }
|
|
|
373
411
|
* to the next listed strike up, or a straddle at the anchor. A collar buys
|
|
374
412
|
* the put at `collar.put` and sells the call at `collar.call` on the put's
|
|
375
413
|
* expiry, each the quoted strike nearest 10% out of the money when omitted.
|
|
414
|
+
* A put or csp seed given a `delta` takes the put whose spot delta, valued at
|
|
415
|
+
* the IV its own midpoint implies, is nearest that delta's negative.
|
|
376
416
|
*/
|
|
377
417
|
function scenarioStrategyLegs(
|
|
378
418
|
chain: OptionsChain, spot: number, strategy: ScenarioStrategy, pricing: StrategyPricing | null, strike?: number,
|
|
379
|
-
collar: { put?: number; call?: number } = {},
|
|
419
|
+
collar: { put?: number; call?: number } = {}, delta?: number, unusable: UnusableChainText = defaultUnusableChainText,
|
|
380
420
|
): { legs: ScenarioLeg[]; parity: StrategyParity | null } {
|
|
381
421
|
const parity = new Map<number, StrategyParity | null>();
|
|
382
422
|
const parityFor = (expiration: number) => {
|
|
@@ -406,15 +446,16 @@ function scenarioStrategyLegs(
|
|
|
406
446
|
const calls = quoted(chain.calls, "call").sort(nearest);
|
|
407
447
|
const puts = quoted(chain.puts, "put").sort(nearest);
|
|
408
448
|
const missing = (candidates: { contract: { strike: number } }[], at = strike, what: string = strategy === "csp" ? "put to sell" : strategy) => {
|
|
409
|
-
if (at == null) return new Error("
|
|
449
|
+
if (at == null) return new Error(unusable("strategy"));
|
|
410
450
|
const listed = [...new Set(candidates.map(({ contract }) => contract.strike))]
|
|
411
451
|
.sort((a, b) => Math.abs(a - at) - Math.abs(b - at)).slice(0, 4).sort((a, b) => a - b);
|
|
412
|
-
|
|
413
|
-
|
|
452
|
+
// Another strike on this expiry is the nearer fix; with none quoted, the next expiry is.
|
|
453
|
+
return new Error(listed.length ? `${unusable(`${what} at strike ${at}`, false)}; nearest quoted strikes: ${listed.join(", ")}`
|
|
454
|
+
: unusable(`${what} at strike ${at}`));
|
|
414
455
|
};
|
|
415
|
-
if (strategy === "collar") return collarLegs(calls, puts, spot, collar, missing, parityFor);
|
|
456
|
+
if (strategy === "collar") return collarLegs(calls, puts, spot, collar, missing, parityFor, unusable);
|
|
416
457
|
if (strategy === "put" || strategy === "csp") {
|
|
417
|
-
const put = puts.find(anchored);
|
|
458
|
+
const put = delta == null ? puts.find(anchored) : nearestDelta(puts, -delta, spot, pricing, parityFor);
|
|
418
459
|
if (!put) throw missing(puts);
|
|
419
460
|
return { legs: [{ id: put.contract.contractSymbol, side: "put", quantity: strategy === "csp" ? -1 : 1, strike: put.contract.strike,
|
|
420
461
|
expiration: put.contract.expiration, price: put.mid, priceSource: "mid", volatility: put.volatility, multiplier: 100,
|
|
@@ -441,6 +482,60 @@ function scenarioStrategyLegs(
|
|
|
441
482
|
|
|
442
483
|
interface QuotedContract { contract: OptionsChain["calls"][number]; mid: number; volatility: number; solved: boolean }
|
|
443
484
|
|
|
485
|
+
/** Why a chain seeds nothing: `what` it has no usable quote for, then the next live expiry to try unless `next` is false. */
|
|
486
|
+
type UnusableChainText = (what: string, next?: boolean) => string;
|
|
487
|
+
const defaultUnusableChainText: UnusableChainText = (what) => `The selected chain has no complete quoted ${what} with usable IV`;
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* The seed failure naming the expiry the chain is on and the next live one to
|
|
491
|
+
* ask for: `The 30 Oct chain has no complete quoted strategy with usable IV;
|
|
492
|
+
* try the next expiry, 6 Nov (--expiration 2026-11-06)`.
|
|
493
|
+
*/
|
|
494
|
+
function unusableChainText(market: ScenarioMarketSnapshot, exchange: string | undefined): UnusableChainText {
|
|
495
|
+
const contracts = market.chain ? [...market.chain.calls, ...market.chain.puts] : [];
|
|
496
|
+
const tried = contracts[0]?.expiration;
|
|
497
|
+
if (tried == null) return defaultUnusableChainText;
|
|
498
|
+
const now = market.asOf;
|
|
499
|
+
const next = nextLiveExpiry(tried, market.expirationDates, now, exchange ?? market.exchange);
|
|
500
|
+
return (what, withNext = true) => `The ${expiryDayLabel(tried, now)} chain has no complete quoted ${what} with usable IV`
|
|
501
|
+
+ (withNext && next != null ? `; try the next expiry, ${expiryChoiceText(next, now, "expiration")}` : "");
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* A put's spot delta at the IV it trades at: against the parity forward its
|
|
506
|
+
* IV was solved on when it was (forward delta, carried to spot), else at the
|
|
507
|
+
* seed's spot. Null when it cannot be valued.
|
|
508
|
+
*/
|
|
509
|
+
function quotedPutDelta(quoted: QuotedContract, spot: number, pricing: StrategyPricing | null, parity: StrategyParity | null): number | null {
|
|
510
|
+
const { contract, volatility, solved } = quoted;
|
|
511
|
+
if (!pricing) return null;
|
|
512
|
+
if (solved && parity) {
|
|
513
|
+
// On the forward, with the dividend yield set to the rate so it carries no drift, as the IV was solved.
|
|
514
|
+
const forwardDelta = valueOption({ side: "put", spot: parity.forward, strike: contract.strike, daysToExpiry: parity.daysToExpiry,
|
|
515
|
+
rate: pricing.rate, dividendYield: pricing.rate, volatility }).delta;
|
|
516
|
+
return Number.isFinite(forwardDelta) ? forwardDelta * Math.exp((pricing.rate - pricing.dividendYield) * parity.daysToExpiry / 365) : null;
|
|
517
|
+
}
|
|
518
|
+
const years = daysToExpiryFrom(contract.expiration, pricing.asOf) / 365;
|
|
519
|
+
return optionDelta({ spot, years, volatility, rate: pricing.rate, dividendYield: pricing.dividendYield }, contract.strike, "put");
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/** The put whose delta is nearest `target` (negative, as a put's delta reads), the lower strike on a tie; null when none can be valued. */
|
|
523
|
+
function nearestDelta(
|
|
524
|
+
puts: readonly QuotedContract[], target: number, spot: number, pricing: StrategyPricing | null,
|
|
525
|
+
parityFor: (expiration: number) => StrategyParity | null,
|
|
526
|
+
): QuotedContract | null {
|
|
527
|
+
let best: { put: QuotedContract; distance: number } | null = null;
|
|
528
|
+
for (const put of puts) {
|
|
529
|
+
const delta = quotedPutDelta(put, spot, pricing, put.solved ? parityFor(put.contract.expiration) : null);
|
|
530
|
+
if (delta == null) continue;
|
|
531
|
+
const distance = Math.abs(delta - target);
|
|
532
|
+
if (!best || distance < best.distance - 1e-12 || (Math.abs(distance - best.distance) <= 1e-12 && put.contract.strike < best.put.contract.strike)) {
|
|
533
|
+
best = { put, distance };
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return best?.put ?? null;
|
|
537
|
+
}
|
|
538
|
+
|
|
444
539
|
/** How far out of the money a collar's put and call sit when their strikes are not given. */
|
|
445
540
|
const COLLAR_DEFAULT_DISTANCE = 0.1;
|
|
446
541
|
|
|
@@ -448,18 +543,18 @@ const COLLAR_DEFAULT_DISTANCE = 0.1;
|
|
|
448
543
|
function collarLegs(
|
|
449
544
|
calls: QuotedContract[], puts: QuotedContract[], spot: number, strikes: { put?: number; call?: number },
|
|
450
545
|
missing: (candidates: QuotedContract[], at: number | undefined, what: string) => Error,
|
|
451
|
-
parityFor: (expiration: number) => StrategyParity | null,
|
|
546
|
+
parityFor: (expiration: number) => StrategyParity | null, unusable: UnusableChainText,
|
|
452
547
|
): { legs: ScenarioLeg[]; parity: StrategyParity | null } {
|
|
453
548
|
const closest = (candidates: QuotedContract[], target: number) => candidates.toSorted((a, b) =>
|
|
454
549
|
Math.abs(a.contract.strike - target) - Math.abs(b.contract.strike - target) || a.contract.strike - b.contract.strike);
|
|
455
550
|
const at = (candidates: QuotedContract[], value: number) => candidates.filter(({ contract }) => Math.abs(contract.strike - value) < 1e-9);
|
|
456
551
|
const put = strikes.put != null ? at(puts, strikes.put)[0]
|
|
457
552
|
: closest(puts.filter(({ contract }) => strikes.call == null || contract.strike < strikes.call), spot * (1 - COLLAR_DEFAULT_DISTANCE))[0];
|
|
458
|
-
if (!put) throw strikes.put != null ? missing(puts, strikes.put, "put to buy") : new Error("
|
|
553
|
+
if (!put) throw strikes.put != null ? missing(puts, strikes.put, "put to buy") : new Error(unusable("put to buy"));
|
|
459
554
|
const sameExpiry = calls.filter(({ contract }) => contract.expiration === put.contract.expiration);
|
|
460
555
|
const call = strikes.call != null ? at(sameExpiry, strikes.call)[0]
|
|
461
556
|
: closest(sameExpiry.filter(({ contract }) => contract.strike > put.contract.strike), spot * (1 + COLLAR_DEFAULT_DISTANCE))[0];
|
|
462
|
-
if (!call) throw strikes.call != null ? missing(sameExpiry, strikes.call, "call to sell") : new Error("
|
|
557
|
+
if (!call) throw strikes.call != null ? missing(sameExpiry, strikes.call, "call to sell") : new Error(unusable("call to sell above the put"));
|
|
463
558
|
if (call.contract.strike <= put.contract.strike) throw new Error("A collar's put strike must be below its call strike");
|
|
464
559
|
if (put.contract.currency !== call.contract.currency) throw new Error("Strategy quote currencies differ");
|
|
465
560
|
const legs = [[put, "put", 1], [call, "call", -1]] as const;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { StatItem } from "../../../components";
|
|
2
2
|
import type { HeadlessPaneEntry } from "../../../types/headless";
|
|
3
3
|
import { formatHedgePrice } from "./hedge";
|
|
4
|
-
import { UNKNOWN_CURRENCY, type ScenarioPosition } from "./model";
|
|
4
|
+
import { amountUnit, UNKNOWN_CURRENCY, type ScenarioPosition } from "./model";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* A collar (docs/research-data.md#collar): one long put and one short call on
|
|
@@ -104,17 +104,19 @@ function boundText(collar: Collar, value: number | null, side: "put" | "call"):
|
|
|
104
104
|
export function collarReportEntries(collar: Collar): HeadlessPaneEntry[] {
|
|
105
105
|
const unit = unitOf(collar);
|
|
106
106
|
const premium = Math.abs(collar.netPremium);
|
|
107
|
+
// Every figure is per share, so an export writes the number with that unit.
|
|
108
|
+
const perShare = { unit: `${amountUnit(collar.currency)} per share` };
|
|
107
109
|
return [
|
|
108
|
-
{ key: "putBought", label: "Put bought", value: collar.put.price,
|
|
110
|
+
{ key: "putBought", label: "Put bought", value: collar.put.price, ...perShare,
|
|
109
111
|
formatted: `${strikeText(collar.put.strike)} put at ${formatHedgePrice(collar.put.price)} per share (${priceWord(collar.put)})` },
|
|
110
|
-
{ key: "callSold", label: "Call sold", value: collar.call.price,
|
|
112
|
+
{ key: "callSold", label: "Call sold", value: collar.call.price, ...perShare,
|
|
111
113
|
formatted: `${strikeText(collar.call.strike)} call at ${formatHedgePrice(collar.call.price)} per share (${priceWord(collar.call)})` },
|
|
112
|
-
{ key: "netPremium", label: "Net premium", value: collar.netPremium,
|
|
114
|
+
{ key: "netPremium", label: "Net premium", value: collar.netPremium, ...perShare,
|
|
113
115
|
formatted: collar.netPremium === 0 ? `0.00 per share (zero cost)`
|
|
114
116
|
: `${formatHedgePrice(premium)} per share ${premiumWord(collar)} (${money(Math.abs(collar.netPremiumTotal))}${unit} `
|
|
115
117
|
+ `for ${collar.contracts.toLocaleString("en-US")} ${collar.contracts === 1 ? "contract" : "contracts"} each)` },
|
|
116
|
-
{ key: "floor", label: "Floor", value: collar.floor, formatted: boundText(collar, collar.floor, "put") },
|
|
117
|
-
{ key: "cap", label: "Cap", value: collar.cap, formatted: boundText(collar, collar.cap, "call") },
|
|
118
|
+
{ key: "floor", label: "Floor", value: collar.floor, ...perShare, formatted: boundText(collar, collar.floor, "put") },
|
|
119
|
+
{ key: "cap", label: "Cap", value: collar.cap, ...perShare, formatted: boundText(collar, collar.cap, "call") },
|
|
118
120
|
];
|
|
119
121
|
}
|
|
120
122
|
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { HeadlessPaneColumn, HeadlessPaneDefinition } from "../../../types/plugin";
|
|
2
|
+
import { expirationOptionSeconds, OPTION_EXPIRATION_FORMAT, OPTION_EXPIRATION_PLACEHOLDER } from "../../../utils/option-expiry";
|
|
3
|
+
import { resolveHeadlessInstrument } from "../shared/headless-market-data";
|
|
4
|
+
import { createScenarioDependencies } from "./client";
|
|
5
|
+
import { formatHedgePrice } from "./hedge";
|
|
6
|
+
import { exportPercent } from "./model";
|
|
7
|
+
import {
|
|
8
|
+
CSP_SCREEN_DEFAULTS, CSP_SCREEN_LIMIT, cspEarningsText, cspScreenCriteriaText, loadCspScreen, readCspScreenCriteria,
|
|
9
|
+
type CspScreen, type CspScreenRow,
|
|
10
|
+
} from "./csp-screen";
|
|
11
|
+
|
|
12
|
+
const percentCell = (digits: number) => (value: unknown) => typeof value === "number" ? `${value.toFixed(digits)}%` : "--";
|
|
13
|
+
const priceCell = (value: unknown) => typeof value === "number" ? formatHedgePrice(value) : "--";
|
|
14
|
+
const strikeCell = (value: unknown) => typeof value === "number" ? String(value) : "--";
|
|
15
|
+
|
|
16
|
+
/** One name's row, flat, every percent in percent. */
|
|
17
|
+
export type CspScreenReportRow = {
|
|
18
|
+
rank: number | null; symbol: string; expiry: string | null; days: number | null; strike: number | null;
|
|
19
|
+
bid: number | null; ask: number | null; spread: number | null; wide: boolean | null; delta: number | null; otm: number | null;
|
|
20
|
+
midYield: number | null; bidYield: number | null; ivRank: number | null;
|
|
21
|
+
earnings: string; earningsBeforeExpiry: boolean | null; earningsDate: string | null; spot: number | null; note: string | null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function cspScreenReportRow(row: CspScreenRow): CspScreenReportRow {
|
|
25
|
+
const { csp } = row;
|
|
26
|
+
return {
|
|
27
|
+
rank: row.rank, symbol: row.symbol,
|
|
28
|
+
expiry: csp?.expiry ?? null, days: csp?.daysToExpiry ?? null, strike: csp?.strike ?? null,
|
|
29
|
+
bid: csp?.quote?.bid ?? null, ask: csp?.quote?.ask ?? null, spread: exportPercent(csp?.quote?.spreadOfMid), wide: csp?.quote ? row.wideSpread : null,
|
|
30
|
+
delta: row.delta, otm: exportPercent(csp?.outOfTheMoney), midYield: exportPercent(csp?.annualizedYield),
|
|
31
|
+
bidYield: exportPercent(csp?.quote?.bidAnnualizedYield), ivRank: row.ivRank,
|
|
32
|
+
earnings: cspEarningsText(csp), earningsBeforeExpiry: csp ? csp.earnings.beforeExpiry : null, earningsDate: csp?.earnings.date ?? null,
|
|
33
|
+
spot: csp?.spot ?? null, note: row.note,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const COLUMNS: HeadlessPaneColumn[] = [
|
|
38
|
+
{ key: "rank", header: "#", align: "right", format: (value) => typeof value === "number" ? String(value) : "" },
|
|
39
|
+
{ key: "symbol", header: "Symbol" },
|
|
40
|
+
{ key: "expiry", header: "Expiry" },
|
|
41
|
+
{ key: "days", header: "DTE", align: "right" },
|
|
42
|
+
{ key: "strike", header: "Strike", align: "right", format: strikeCell },
|
|
43
|
+
{ key: "bid", header: "Bid", align: "right", format: priceCell },
|
|
44
|
+
{ key: "ask", header: "Ask", align: "right", format: priceCell },
|
|
45
|
+
// The text view marks a wide spread in its cell; exports keep the number and say so in Wide.
|
|
46
|
+
{ key: "spread", header: "Spread", align: "right", format: percentCell(1),
|
|
47
|
+
textFormat: (value, row) => `${percentCell(1)(value)}${row.wide ? " wide" : ""}` },
|
|
48
|
+
{ key: "delta", header: "Delta", align: "right", format: (value) => typeof value === "number" ? value.toFixed(2) : "--" },
|
|
49
|
+
{ key: "otm", header: "OTM", align: "right", format: percentCell(1) },
|
|
50
|
+
{ key: "midYield", header: "Mid yield/yr", align: "right", format: percentCell(2) },
|
|
51
|
+
{ key: "bidYield", header: "Bid yield/yr", align: "right", format: percentCell(2) },
|
|
52
|
+
{ key: "ivRank", header: "IVR", align: "right", format: (value) => typeof value === "number" ? value.toFixed(0) : "--" },
|
|
53
|
+
{ key: "earnings", header: "Earnings" },
|
|
54
|
+
];
|
|
55
|
+
const NOTE: HeadlessPaneColumn = { key: "note", header: "Note", underRow: true };
|
|
56
|
+
const EXPORT_ONLY: HeadlessPaneColumn[] = [
|
|
57
|
+
{ key: "wide", header: "Wide spread", format: (value) => value === true ? "yes" : value === false ? "no" : "" },
|
|
58
|
+
{ key: "earningsBeforeExpiry", header: "Earnings before expiry", format: (value) => value === true ? "yes" : value === false ? "no" : "unknown" },
|
|
59
|
+
{ key: "earningsDate", header: "Earnings date" },
|
|
60
|
+
{ key: "spot", header: "Spot", format: priceCell },
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
/** `A`, `A and B`, `A, B and C`. */
|
|
64
|
+
const listText = (items: readonly string[]) => items.length <= 1 ? items.join("") : `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The ranking basis and the wide flag's limit, then the names IV history has no
|
|
68
|
+
* rank for: those with a reason say it (`No IV rank: BBB (fewer than 120
|
|
69
|
+
* sessions).`), and a bare symbol, which has none to give, is just not ranked
|
|
70
|
+
* yet (`No IV rank for DAL yet.`).
|
|
71
|
+
*/
|
|
72
|
+
export function cspScreenNotes(screen: Pick<CspScreen, "criteria" | "missingRanks">): string[] {
|
|
73
|
+
const reasoned = screen.missingRanks.filter((entry) => entry.includes(" "));
|
|
74
|
+
const bare = screen.missingRanks.filter((entry) => !entry.includes(" "));
|
|
75
|
+
return [
|
|
76
|
+
`Ranked by annualised yield at the bid. Wide is a spread over ${screen.criteria.maxSpreadPercent}% of the mid (--max-spread).`,
|
|
77
|
+
...(reasoned.length ? [`No IV rank: ${reasoned.join(", ")}.`] : []),
|
|
78
|
+
...(bare.length ? [`No IV rank for ${listText(bare)} yet.`] : []),
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const cspScreenHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
83
|
+
shape: "rows",
|
|
84
|
+
argument: { kind: "tickers", maximum: CSP_SCREEN_LIMIT, description: "US option underlyings to rank" },
|
|
85
|
+
usListingsOnly: "Options data",
|
|
86
|
+
failsWithoutData: true,
|
|
87
|
+
description: "Cash-secured puts across a list, ranked by annualised yield on cash at the bid: for each name, the put nearest a delta "
|
|
88
|
+
+ "(0.30) on the listed expiry nearest a date or a number of days out (30), with its strike, bid, ask, spread (flagged wide over "
|
|
89
|
+
+ "--max-spread, never left out), delta, distance out of the money, yield at the mid and at the bid, IV rank and whether earnings fall before expiry.",
|
|
90
|
+
discovery: { screenshotReadiness: "live-dom", dataRequirements: ["Current option chains", "Cloud stored implied volatility", "Earnings calendar"],
|
|
91
|
+
limitations: ["Assignment, margin and the interest the cash earns are not modelled."] },
|
|
92
|
+
describe: (_args, result) => {
|
|
93
|
+
const criteria = (result?.metadata as { criteria?: CspScreen["criteria"] } | undefined)?.criteria;
|
|
94
|
+
return ["Cash-secured puts", ...(criteria ? [cspScreenCriteriaText(criteria, Date.now())] : [])].join(" · ");
|
|
95
|
+
},
|
|
96
|
+
options: [
|
|
97
|
+
{ key: "delta", type: "string", placeholder: "0.30", example: "--delta 0.20",
|
|
98
|
+
description: `Each name's put is the one whose delta is nearest this, as a put's delta reads without its sign. Default ${CSP_SCREEN_DEFAULTS.delta.toFixed(2)}` },
|
|
99
|
+
{ key: "days", type: "string", placeholder: "days", example: "--days 45",
|
|
100
|
+
description: `Each name's expiry is its listed one nearest this many calendar days out. Default ${CSP_SCREEN_DEFAULTS.days}` },
|
|
101
|
+
{ key: "expiration", type: "string", placeholder: OPTION_EXPIRATION_PLACEHOLDER, example: "--expiration 2026-11-20",
|
|
102
|
+
description: `Each name's expiry is its listed one nearest this date, in place of --days, as ${OPTION_EXPIRATION_FORMAT}`,
|
|
103
|
+
normalize: (value) => expirationOptionSeconds(value) },
|
|
104
|
+
{ key: "maxSpread", aliases: ["max-spread"], type: "string", placeholder: "percent", example: "--max-spread 15",
|
|
105
|
+
description: `A put whose bid/ask spread is over this percent of its mid is marked wide; it is still ranked. Default ${CSP_SCREEN_DEFAULTS.maxSpreadPercent}` },
|
|
106
|
+
],
|
|
107
|
+
async load(args, ctx) {
|
|
108
|
+
const criteria = readCspScreenCriteria(args.options);
|
|
109
|
+
const instruments = await Promise.all(args.symbols.map((symbol) => resolveHeadlessInstrument(ctx, symbol)));
|
|
110
|
+
const screen = await loadCspScreen(instruments, criteria, {
|
|
111
|
+
scenario: createScenarioDependencies(ctx.marketData, ctx.apiClient), earnings: ctx.marketData, ivApi: ctx.apiClient,
|
|
112
|
+
}, ctx.signal);
|
|
113
|
+
const unavailable = screen.rows.filter((row) => !row.csp);
|
|
114
|
+
return {
|
|
115
|
+
freshness: screen.freshness,
|
|
116
|
+
columns: [...COLUMNS, ...EXPORT_ONLY, NOTE],
|
|
117
|
+
textColumns: [...COLUMNS, NOTE],
|
|
118
|
+
rows: screen.rows.map(cspScreenReportRow),
|
|
119
|
+
complete: unavailable.length === 0 && !screen.ivRankError,
|
|
120
|
+
unavailableSymbols: unavailable.map((row) => row.symbol),
|
|
121
|
+
errors: screen.ivRankError ? [`IV rank unavailable: ${screen.ivRankError}`] : [],
|
|
122
|
+
notes: cspScreenNotes(screen),
|
|
123
|
+
metadata: { criteria: screen.criteria, rankedBy: "annualized yield on cash at the bid", unit: "percent; prices per share",
|
|
124
|
+
methodology: "docs/research-data.md#cash-secured-put-screen" },
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
};
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { DataTableView, PaneStatusBody, QueryBar, usePaneFooter, usePaneNoticeFooter, type DataTableColumn, type DataTableKeyEvent } from "../../../components";
|
|
3
|
+
import { useAsyncResource } from "../../../react/async-resource";
|
|
4
|
+
import { useAutoRefresh } from "../../../react/auto-refresh";
|
|
5
|
+
import { usePaneSettingValue, usePluginAppActions } from "../../../public/react";
|
|
6
|
+
import { useThemeColors } from "../../../theme/theme-context";
|
|
7
|
+
import type { PaneProps } from "../../../types/plugin";
|
|
8
|
+
import { Box } from "../../../ui";
|
|
9
|
+
import { parsePublicTickerKey } from "../../../utils/exchanges";
|
|
10
|
+
import { compareSortValues, nextHeaderSort, type SortDirection } from "../../../utils/sort-values";
|
|
11
|
+
import { useAssetData } from "../../runtime";
|
|
12
|
+
import { createScenarioDependencies } from "./client";
|
|
13
|
+
import { cspScreenNotes, cspScreenReportRow, type CspScreenReportRow } from "./csp-screen-headless";
|
|
14
|
+
import { CSP_SCREEN_DEFAULTS, CSP_SCREEN_LIMIT, cspScreenCriteriaText, cspTargetDate, loadCspScreen, readCspScreenCriteria, type CspScreenRow } from "./csp-screen";
|
|
15
|
+
import { formatHedgePrice } from "./hedge";
|
|
16
|
+
|
|
17
|
+
type ScreenRow = CspScreenReportRow & { source: CspScreenRow };
|
|
18
|
+
|
|
19
|
+
const COLUMNS: DataTableColumn[] = [
|
|
20
|
+
{ id: "rank", label: "#", width: 3, align: "right" },
|
|
21
|
+
{ id: "symbol", label: "Symbol", width: 7, align: "left" },
|
|
22
|
+
{ id: "expiry", label: "Expiry", width: 10, align: "right" },
|
|
23
|
+
{ id: "days", label: "DTE", width: 4, align: "right" },
|
|
24
|
+
{ id: "strike", label: "Strike", width: 8, align: "right" },
|
|
25
|
+
{ id: "bid", label: "Bid", width: 7, align: "right" },
|
|
26
|
+
{ id: "ask", label: "Ask", width: 7, align: "right" },
|
|
27
|
+
{ id: "spread", label: "Spread", width: 11, align: "right" },
|
|
28
|
+
{ id: "delta", label: "Delta", width: 6, align: "right" },
|
|
29
|
+
{ id: "otm", label: "OTM", width: 6, align: "right" },
|
|
30
|
+
{ id: "midYield", label: "Mid yield/yr", width: 12, align: "right" },
|
|
31
|
+
{ id: "bidYield", label: "Bid yield/yr", width: 12, align: "right" },
|
|
32
|
+
{ id: "ivRank", label: "IVR", width: 4, align: "right" },
|
|
33
|
+
{ id: "earnings", label: "Earnings", width: 17, align: "left" },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const DELTA_OPTIONS = ["0.10", "0.15", "0.20", "0.25", "0.30", "0.35", "0.40", "0.50"];
|
|
37
|
+
const DAYS_OPTIONS = ["7", "14", "21", "30", "45", "60", "90"];
|
|
38
|
+
const SYMBOLS_FIELD_WIDTH = 24;
|
|
39
|
+
/** Symbols apply once typing pauses, so a half-typed ticker does not start a screen. */
|
|
40
|
+
const SYMBOLS_DEBOUNCE_MS = 700;
|
|
41
|
+
|
|
42
|
+
const symbolList = (text: string) => [...new Set(text.split(/[\s,;]+/).map((symbol) => symbol.trim().toUpperCase()).filter(Boolean))];
|
|
43
|
+
const percentText = (value: unknown, digits: number) => typeof value === "number" ? `${value.toFixed(digits)}%` : "--";
|
|
44
|
+
const priceText = (value: unknown) => typeof value === "number" ? formatHedgePrice(value) : "--";
|
|
45
|
+
/** A column's value to sort on: earnings by date, a flag as a number. */
|
|
46
|
+
function sortValue(row: CspScreenReportRow, columnId: string): string | number | null {
|
|
47
|
+
if (columnId === "earnings") return row.earningsDate;
|
|
48
|
+
const value = row[columnId as keyof CspScreenReportRow];
|
|
49
|
+
return typeof value === "boolean" ? Number(value) : value;
|
|
50
|
+
}
|
|
51
|
+
/** A select's options with the current value kept when it is not a preset (a delta typed on the command line). */
|
|
52
|
+
const withCurrent = (presets: readonly string[], current: string, label: (value: string) => string) =>
|
|
53
|
+
[...new Set([...presets, current])].sort((a, b) => Number(a) - Number(b)).map((value) => ({ value, label: label(value) }));
|
|
54
|
+
|
|
55
|
+
export function CspScreenPane({ width, height, focused }: PaneProps) {
|
|
56
|
+
const colors = useThemeColors();
|
|
57
|
+
const [symbolsText, setSymbolsText] = usePaneSettingValue<string>("symbols", "");
|
|
58
|
+
const [delta, setDelta] = usePaneSettingValue<string | number>("delta", "");
|
|
59
|
+
const [days, setDays] = usePaneSettingValue<string | number>("days", "");
|
|
60
|
+
const [expiration, setExpiration] = usePaneSettingValue<string | number>("expiration", "");
|
|
61
|
+
const [maxSpread] = usePaneSettingValue<string | number>("maxSpread", "");
|
|
62
|
+
const [symbolsActive, setSymbolsActive] = useState(false);
|
|
63
|
+
const { createPaneFromTemplate } = usePluginAppActions();
|
|
64
|
+
const assetData = useAssetData();
|
|
65
|
+
const typed = useMemo(() => symbolList(String(symbolsText ?? "")), [symbolsText]);
|
|
66
|
+
// The screen reads the first names it can rank; the footer names the rest.
|
|
67
|
+
const symbols = useMemo(() => typed.slice(0, CSP_SCREEN_LIMIT), [typed]);
|
|
68
|
+
const leftOut = typed.slice(CSP_SCREEN_LIMIT);
|
|
69
|
+
const criteria = useMemo(() => {
|
|
70
|
+
try { return { value: readCspScreenCriteria({ delta, days, expiration, maxSpread }), error: null }; }
|
|
71
|
+
catch (error) { return { value: null, error: error instanceof Error ? error.message : String(error) }; }
|
|
72
|
+
}, [delta, days, expiration, maxSpread]);
|
|
73
|
+
const controller = useRef<AbortController | null>(null);
|
|
74
|
+
const key = JSON.stringify([symbols, criteria.value]);
|
|
75
|
+
const loader = useCallback(async () => {
|
|
76
|
+
controller.current?.abort();
|
|
77
|
+
const current = new AbortController();
|
|
78
|
+
controller.current = current;
|
|
79
|
+
return loadCspScreen(symbols.map((symbol) => parsePublicTickerKey(symbol)), criteria.value!,
|
|
80
|
+
{ scenario: createScenarioDependencies(), earnings: assetData }, current.signal);
|
|
81
|
+
}, [key, assetData]);
|
|
82
|
+
const resource = useAsyncResource(symbols.length && criteria.value ? loader : null);
|
|
83
|
+
useEffect(() => () => controller.current?.abort(), [loader]);
|
|
84
|
+
useAutoRefresh(resource.updatedAt, resource.load);
|
|
85
|
+
const [sort, setSort] = useState<{ columnId: string; direction: SortDirection }>({ columnId: "rank", direction: "asc" });
|
|
86
|
+
const rows = useMemo<ScreenRow[]>(() => {
|
|
87
|
+
const flat = (resource.data?.rows ?? []).map((row) => ({ ...cspScreenReportRow(row), source: row }));
|
|
88
|
+
// Unranked names stay under the ranked ones whichever way rank sorts.
|
|
89
|
+
return flat.sort((left, right) => sort.columnId === "rank"
|
|
90
|
+
? compareSortValues(left.rank ?? Number.POSITIVE_INFINITY, right.rank ?? Number.POSITIVE_INFINITY, sort.direction)
|
|
91
|
+
: compareSortValues(sortValue(left, sort.columnId), sortValue(right, sort.columnId), sort.direction));
|
|
92
|
+
}, [resource.data, sort]);
|
|
93
|
+
const [selected, setSelected] = useState<string | null>(null);
|
|
94
|
+
const screen = resource.data;
|
|
95
|
+
const notices = [criteria.error, resource.data ? null : resource.error,
|
|
96
|
+
leftOut.length ? `Ranks the first ${CSP_SCREEN_LIMIT} symbols; ${leftOut.length} left out: ${leftOut.join(", ")}` : null,
|
|
97
|
+
...(screen?.ivRankError ? [`IV rank unavailable: ${screen.ivRankError}`] : []),
|
|
98
|
+
...(screen?.rows ?? []).flatMap((row) => row.note ? [`${row.symbol}: ${row.note}`] : []),
|
|
99
|
+
...(screen ? cspScreenNotes(screen).slice(1) : [])].filter((value): value is string => !!value);
|
|
100
|
+
usePaneNoticeFooter({ registrationId: "csp-screen-notices", notices, focused });
|
|
101
|
+
// The bar names the delta and the expiry; the footer says only what changes.
|
|
102
|
+
usePaneFooter("csp-screen", () => ({ info: [
|
|
103
|
+
...(resource.loading ? [{ id: "loading", parts: [{ text: "loading", tone: "muted" as const }] }] : []),
|
|
104
|
+
] }), [resource.loading]);
|
|
105
|
+
const criteriaText = criteria.value ? cspScreenCriteriaText(criteria.value, Date.now()) : "";
|
|
106
|
+
// Enter opens the name's put in OSA, at the strike and expiry the screen read.
|
|
107
|
+
const openScenario = (row: ScreenRow) => {
|
|
108
|
+
const csp = row.source.csp;
|
|
109
|
+
if (!csp) return;
|
|
110
|
+
createPaneFromTemplate("options-scenario-pane", { symbol: row.symbol, values: { strategy: "csp", strike: String(csp.strike), expiration: csp.expiry } });
|
|
111
|
+
};
|
|
112
|
+
const handleKey = (event: DataTableKeyEvent): boolean => {
|
|
113
|
+
if (event.ctrl || event.alt || event.meta || event.name !== "r") return false;
|
|
114
|
+
void resource.reload();
|
|
115
|
+
event.preventDefault?.(); event.stopPropagation?.(); return true;
|
|
116
|
+
};
|
|
117
|
+
const cell = (row: ScreenRow, id: string): { text: string; color?: string } => {
|
|
118
|
+
switch (id) {
|
|
119
|
+
case "rank": return { text: row.rank == null ? "" : String(row.rank), color: colors.textDim };
|
|
120
|
+
case "symbol": return { text: row.symbol, color: colors.textBright };
|
|
121
|
+
case "expiry": return { text: String(row.expiry ?? "--"), color: colors.textDim };
|
|
122
|
+
case "days": return { text: row.days == null ? "--" : String(row.days) };
|
|
123
|
+
case "strike": return { text: row.strike == null ? "--" : String(row.strike) };
|
|
124
|
+
case "bid": return { text: priceText(row.bid) };
|
|
125
|
+
case "ask": return { text: priceText(row.ask) };
|
|
126
|
+
case "spread": return { text: `${percentText(row.spread, 1)}${row.wide ? " wide" : ""}`, color: row.wide ? colors.warning : undefined };
|
|
127
|
+
case "delta": return { text: typeof row.delta === "number" ? row.delta.toFixed(2) : "--" };
|
|
128
|
+
case "otm": return { text: percentText(row.otm, 1) };
|
|
129
|
+
case "midYield": return { text: percentText(row.midYield, 2), color: colors.textDim };
|
|
130
|
+
case "bidYield": return { text: percentText(row.bidYield, 2), color: colors.positive };
|
|
131
|
+
case "ivRank": return { text: typeof row.ivRank === "number" ? row.ivRank.toFixed(0) : "--" };
|
|
132
|
+
case "earnings": return { text: String(row.earnings), color: row.earningsBeforeExpiry === true ? colors.warning : colors.textDim };
|
|
133
|
+
default: return { text: "" };
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const deltaValue = criteria.value ? criteria.value.delta.toFixed(2) : CSP_SCREEN_DEFAULTS.delta.toFixed(2);
|
|
137
|
+
const daysValue = String(criteria.value?.days ?? CSP_SCREEN_DEFAULTS.days);
|
|
138
|
+
// A date typed on the command line is the expiry's choice until a number of days is picked.
|
|
139
|
+
const typedDate = criteria.value?.expiration != null ? cspTargetDate(criteria.value, Date.now()) : null;
|
|
140
|
+
return <Box width={width} height={height} flexDirection="column" overflow="hidden">
|
|
141
|
+
<QueryBar width={width} filters={[
|
|
142
|
+
{ id: "symbols", kind: "text" as const, label: "Symbols", value: String(symbolsText ?? ""), placeholder: "JPM, GS, KO",
|
|
143
|
+
width: SYMBOLS_FIELD_WIDTH, debounceMs: SYMBOLS_DEBOUNCE_MS, focused, active: symbolsActive, onActiveChange: setSymbolsActive,
|
|
144
|
+
onChange: setSymbolsText },
|
|
145
|
+
{ id: "delta", label: "Delta", value: deltaValue, defaultValue: CSP_SCREEN_DEFAULTS.delta.toFixed(2),
|
|
146
|
+
options: withCurrent(DELTA_OPTIONS, deltaValue, (value) => value), onChange: (value: string) => setDelta(value) },
|
|
147
|
+
{ id: "expiry", label: "Expiry", value: typedDate ?? daysValue, defaultValue: String(CSP_SCREEN_DEFAULTS.days),
|
|
148
|
+
options: [...withCurrent(DAYS_OPTIONS, daysValue, (value) => `${value} days`), ...(typedDate ? [{ value: typedDate, label: typedDate }] : [])],
|
|
149
|
+
onChange: (value: string) => { if (value === typedDate) return; setExpiration(""); setDays(value); } },
|
|
150
|
+
]} />
|
|
151
|
+
<PaneStatusBody subject="cash-secured puts" loading={resource.loading && !resource.data}
|
|
152
|
+
error={criteria.error && !criteria.value ? criteria.error : !resource.data ? resource.error : null}>
|
|
153
|
+
<DataTableView<ScreenRow> focused={focused && !symbolsActive} columns={COLUMNS} items={rows} rootWidth={width}
|
|
154
|
+
rootHeight={Math.max(2, height - 1)}
|
|
155
|
+
getItemKey={(row) => row.symbol} sortColumnId={sort.columnId} sortDirection={sort.direction}
|
|
156
|
+
emptyStateTitle={symbols.length ? "No puts to rank." : "Type symbols to rank their cash-secured puts."}
|
|
157
|
+
onHeaderClick={(id) => setSort((current) => nextHeaderSort(current, id, { firstDirection: id === "rank" || id === "symbol" ? "asc" : "desc" }))}
|
|
158
|
+
selection={{ kind: "id", selectedId: selected ?? rows[0]?.symbol ?? "", getId: (row) => row.symbol, onChange: (id) => setSelected(id) }}
|
|
159
|
+
onActivate={openScenario} onRootKeyDown={handleKey}
|
|
160
|
+
getExportMetadata={() => [["puts", criteriaText || undefined], ["ranked by", "annualised yield on cash at the bid"],
|
|
161
|
+
["wide", criteria.value ? `spread over ${criteria.value.maxSpreadPercent}% of the mid` : undefined]]}
|
|
162
|
+
renderCell={(row, column) => cell(row, column.id)} />
|
|
163
|
+
</PaneStatusBody>
|
|
164
|
+
</Box>;
|
|
165
|
+
}
|