gloomberb 0.18.4 → 0.19.0
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +169 -5
- package/src/account/auth-model.ts +7 -3
- package/src/api-client/auth.ts +9 -4
- package/src/api-client/catalysts.ts +2 -1
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +31 -6
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/index.ts +171 -11
- package/src/api-client/live-scan.ts +91 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +13 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/request.ts +222 -41
- package/src/api-client/research-activity.ts +6 -12
- package/src/api-client/types.ts +74 -0
- package/src/app/keybindings/index.ts +3 -0
- package/src/app/keybindings/labels.ts +18 -1
- package/src/app/keybindings/resolve.ts +14 -0
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +42 -27
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/core-builtin-allowlist.json +1 -1
- package/src/architecture/pane-tabs-allowlist.json +3 -0
- package/src/architecture/ttl-caches-allowlist.json +19 -0
- package/src/brokers/sync-broker-instance.ts +2 -8
- package/src/capabilities/chart-series.ts +1 -1
- package/src/cli/commands/crypto-hints.ts +40 -24
- package/src/cli/commands/fred-series.ts +5 -0
- package/src/cli/commands/keybindings.ts +23 -0
- package/src/cli/commands/market.ts +138 -46
- package/src/cli/commands/options-chain.ts +107 -10
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +176 -39
- package/src/cli/commands/search.ts +39 -3
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +57 -35
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +10 -2
- package/src/cli/export-source.ts +29 -1
- package/src/cli/glossary.ts +73 -1
- package/src/cli/history-rows.ts +87 -16
- package/src/cli/index.ts +2 -0
- package/src/cli/known-gaps.ts +5 -2
- package/src/cli/listing-arg.ts +195 -56
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +3 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +111 -16
- package/src/cli/pane-functions/function-help.ts +23 -13
- package/src/cli/pane-functions/headless.ts +56 -12
- package/src/cli/pane-functions/index.ts +58 -16
- package/src/cli/pane-functions/market-session.ts +81 -9
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/report.ts +5 -1
- package/src/cli/pane-functions/resolver.ts +35 -8
- package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
- package/src/cli/pane-functions/screenshot-types.ts +5 -0
- package/src/cli/quote-answer.ts +30 -13
- package/src/cli/report-tables.ts +18 -7
- package/src/cli/restore-plugins.ts +13 -1
- package/src/cli/result.ts +57 -5
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +26 -4
- package/src/components/chart/composite/format.ts +27 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/levels.ts +2 -0
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +61 -5
- package/src/components/chart/composite/range-statistics.ts +62 -0
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +46 -7
- package/src/components/chart/composite/series-axis-tags.tsx +16 -9
- package/src/components/chart/composite/series-cues.ts +14 -0
- package/src/components/chart/composite/text-renderer.ts +9 -2
- package/src/components/chart/composite/tool-catalog.ts +36 -0
- package/src/components/chart/composite/tools.ts +203 -44
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
- package/src/components/command-bar/assist/inventory.ts +2 -1
- package/src/components/command-bar/commands/direct/index.ts +28 -13
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/panel/prompt-binding.ts +0 -8
- package/src/components/command-bar/workflow/alert.ts +1 -1
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/header.tsx +26 -40
- package/src/components/layout/pane/footer/index.tsx +1 -0
- package/src/components/layout/pane/footer/model.ts +27 -0
- package/src/components/onboarding/onboarding-frame.tsx +1 -1
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
- package/src/components/onboarding/wizard-broker-sync.ts +0 -1
- package/src/components/onboarding/wizard-positions.ts +3 -3
- package/src/components/ticker/list-table-view.tsx +4 -1
- package/src/components/ui/choice-dialog.tsx +23 -18
- package/src/components/ui/choice-groups.ts +18 -0
- package/src/components/ui/data-table/opentui/index.tsx +9 -7
- package/src/components/ui/data-table/opentui/model.ts +26 -1
- package/src/components/ui/data-table/types.ts +2 -0
- package/src/components/ui/display.tsx +5 -1
- package/src/components/ui/list-view.tsx +8 -4
- package/src/components/ui/query-bar-keys.tsx +2 -0
- package/src/components/ui/query-bar.tsx +1 -0
- package/src/components/ui/select-button.tsx +5 -1
- package/src/components/ui/select-field.tsx +1 -0
- package/src/components/ui/table-layout.ts +8 -5
- package/src/core/app-runtime.ts +2 -0
- package/src/core/app-services.ts +2 -2
- package/src/core/state/app/layout.ts +2 -0
- package/src/core/state/app/state.ts +9 -0
- package/src/core/state/app/types.ts +3 -0
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/saved-copies.ts +55 -16
- package/src/data/ticker-repository.ts +16 -9
- package/src/data/ticker-writes.ts +81 -0
- package/src/data/ttl-cache.ts +165 -0
- package/src/docs/snapshot.json +427 -216
- package/src/i18n/es.ts +19 -3
- package/src/i18n/ja.ts +19 -3
- package/src/i18n/ko.ts +19 -3
- package/src/i18n/zh-cn.ts +19 -3
- package/src/i18n/zh-tw.ts +19 -3
- package/src/market-data/coordinator/financials.ts +10 -2
- package/src/market-data/coordinator/index.ts +3 -1
- package/src/market-data/coordinator/quotes.ts +4 -2
- package/src/market-data/depositary-pair.ts +2 -2
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +48 -7
- package/src/market-data/market/dated-previous-close.ts +203 -0
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/market/futures-week.ts +68 -0
- package/src/market-data/market/status.ts +16 -4
- package/src/market-data/options-alternatives.ts +141 -4
- package/src/market-data/quotes/contributions.ts +20 -2
- package/src/market-data/quotes/resolution.ts +3 -0
- package/src/market-data/selectors.ts +3 -0
- package/src/news/aggregator.ts +113 -9
- package/src/news/cloud-session.ts +21 -0
- package/src/news/hooks.ts +1 -0
- package/src/news/news-model.ts +1 -0
- package/src/news/types.ts +11 -0
- package/src/notifications/notification-log.ts +6 -4
- package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +380 -82
- package/src/plugins/builtin/alerts/command.ts +31 -0
- package/src/plugins/builtin/alerts/constants.ts +1 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
- package/src/plugins/builtin/alerts/fired-log.ts +113 -8
- package/src/plugins/builtin/alerts/format.ts +80 -4
- package/src/plugins/builtin/alerts/headless.ts +30 -7
- package/src/plugins/builtin/alerts/history-rows.ts +7 -3
- package/src/plugins/builtin/alerts/history.ts +2 -0
- package/src/plugins/builtin/alerts/index.tsx +135 -22
- package/src/plugins/builtin/alerts/live.ts +3 -3
- package/src/plugins/builtin/alerts/metric-command.ts +134 -0
- package/src/plugins/builtin/alerts/metric-data.ts +111 -0
- package/src/plugins/builtin/alerts/metric-model.ts +324 -0
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
- package/src/plugins/builtin/alerts/pane.tsx +90 -39
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +37 -10
- package/src/plugins/builtin/alerts/types.ts +7 -0
- package/src/plugins/builtin/analytics/index.tsx +2 -1
- package/src/plugins/builtin/analytics/risk-client.ts +16 -5
- package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
- package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
- package/src/plugins/builtin/analytics/risk-model.ts +55 -9
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/attention/index.tsx +2 -1
- package/src/plugins/builtin/attention/listing.ts +14 -0
- package/src/plugins/builtin/backtest/headless.ts +10 -5
- package/src/plugins/builtin/backtest/pane.tsx +9 -4
- package/src/plugins/builtin/backtest/presets.ts +29 -3
- package/src/plugins/builtin/broker-manager/index.tsx +10 -5
- package/src/plugins/builtin/broker-manager/model.ts +6 -1
- package/src/plugins/builtin/broker-trading/controller.ts +22 -12
- package/src/plugins/builtin/broker-trading/index.tsx +13 -1
- package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
- package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +12 -4
- package/src/plugins/builtin/buildout/format.ts +38 -0
- package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
- package/src/plugins/builtin/buildout/table-model.ts +23 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +7 -3
- package/src/plugins/builtin/catalysts/model.ts +2 -1
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
- package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
- package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
- package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
- package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
- package/src/plugins/builtin/chart-composer/formula.ts +71 -18
- package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
- package/src/plugins/builtin/chart-composer/headless.ts +19 -3
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
- package/src/plugins/builtin/chart-composer/presets.ts +50 -15
- package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
- package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
- package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
- package/src/plugins/builtin/chat/content/composer.tsx +13 -6
- package/src/plugins/builtin/chat/content/footer.tsx +6 -3
- package/src/plugins/builtin/chat/content/index.tsx +50 -21
- package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
- package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
- package/src/plugins/builtin/chat/index.tsx +23 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
- package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
- package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
- package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
- package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
- package/src/plugins/builtin/clinical-trials/model.ts +0 -9
- package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- package/src/plugins/builtin/cloud/plugin.tsx +1 -1
- package/src/plugins/builtin/company-attention/building.tsx +5 -1
- package/src/plugins/builtin/company-attention/client.ts +8 -9
- package/src/plugins/builtin/company-attention/evidence.ts +4 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +9 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/correlation/headless.ts +13 -5
- package/src/plugins/builtin/correlation/history.ts +22 -0
- package/src/plugins/builtin/cot/client.ts +22 -18
- package/src/plugins/builtin/cot/headless.ts +15 -10
- package/src/plugins/builtin/cot/model.ts +14 -1
- package/src/plugins/builtin/cot/pane.tsx +22 -14
- package/src/plugins/builtin/credit-boards/client.ts +6 -10
- package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
- package/src/plugins/builtin/credit-conditions/model.ts +4 -0
- package/src/plugins/builtin/credit-conditions/series.ts +3 -1
- package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
- package/src/plugins/builtin/credit-documents/export.ts +173 -0
- package/src/plugins/builtin/credit-documents/headless.ts +7 -7
- package/src/plugins/builtin/credit-documents/model.ts +104 -14
- package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
- package/src/plugins/builtin/custom-view/sources.ts +2 -0
- package/src/plugins/builtin/dividend-yield/client.ts +131 -23
- package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
- package/src/plugins/builtin/dividend-yield/model.ts +8 -5
- package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
- package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
- package/src/plugins/builtin/dividend-yield/types.ts +20 -2
- package/src/plugins/builtin/dividend-yield/view.ts +30 -2
- package/src/plugins/builtin/docs/targets.ts +5 -1
- package/src/plugins/builtin/earnings/board-model.ts +16 -15
- package/src/plugins/builtin/earnings/board.tsx +62 -71
- package/src/plugins/builtin/earnings/headless.ts +40 -4
- package/src/plugins/builtin/earnings/scope.ts +31 -43
- package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
- package/src/plugins/builtin/econ/calendar-model.ts +14 -9
- package/src/plugins/builtin/econ/figures.ts +72 -0
- package/src/plugins/builtin/econ/index.tsx +33 -3
- package/src/plugins/builtin/econ/types.ts +4 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
- package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
- package/src/plugins/builtin/econ-statistics/view.ts +3 -0
- package/src/plugins/builtin/equity-screener/categories.ts +179 -0
- package/src/plugins/builtin/equity-screener/client.ts +4 -2
- package/src/plugins/builtin/equity-screener/headless.ts +144 -13
- package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
- package/src/plugins/builtin/equity-screener/model.ts +99 -10
- package/src/plugins/builtin/equity-screener/pane.tsx +70 -15
- package/src/plugins/builtin/exposure/charts.tsx +2 -2
- package/src/plugins/builtin/exposure/model.ts +13 -1
- package/src/plugins/builtin/exposure/pane.tsx +4 -4
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/index.tsx +1 -0
- package/src/plugins/builtin/futures/model.ts +2 -1
- package/src/plugins/builtin/futures/table.tsx +2 -2
- package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
- package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
- package/src/plugins/builtin/fx-matrix/client.ts +11 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
- package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
- package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -3
- package/src/plugins/builtin/gpu/headless.ts +8 -2
- package/src/plugins/builtin/help/function-index.ts +4 -1
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +18 -1
- package/src/plugins/builtin/iv-history/client.ts +52 -11
- package/src/plugins/builtin/iv-history/format.ts +36 -23
- package/src/plugins/builtin/iv-history/headless.ts +36 -18
- package/src/plugins/builtin/iv-history/index.tsx +2 -2
- package/src/plugins/builtin/iv-history/model.ts +163 -27
- package/src/plugins/builtin/iv-history/pane.tsx +10 -6
- package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
- package/src/plugins/builtin/iv-history/universe.ts +5 -1
- package/src/plugins/builtin/jobs/client.ts +25 -29
- package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
- package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
- package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
- package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
- package/src/plugins/builtin/macro-day/pane.tsx +7 -5
- package/src/plugins/builtin/macro-day/release-table.ts +2 -2
- package/src/plugins/builtin/market-heatmap/data.ts +8 -33
- package/src/plugins/builtin/market-movers/client.ts +25 -7
- package/src/plugins/builtin/market-movers/footer.ts +2 -2
- package/src/plugins/builtin/market-movers/headless.ts +32 -12
- package/src/plugins/builtin/market-movers/index.tsx +26 -14
- package/src/plugins/builtin/market-movers/min-float.ts +74 -0
- package/src/plugins/builtin/market-movers/model.ts +4 -1
- package/src/plugins/builtin/market-movers/screener.ts +14 -0
- package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
- package/src/plugins/builtin/market-movers/session-table.ts +4 -3
- package/src/plugins/builtin/market-movers/table.ts +17 -5
- package/src/plugins/builtin/members/breadth-pane.tsx +82 -0
- package/src/plugins/builtin/members/client.ts +19 -4
- package/src/plugins/builtin/members/headless.ts +20 -7
- package/src/plugins/builtin/members/index.tsx +9 -9
- package/src/plugins/builtin/members/model.ts +23 -4
- package/src/plugins/builtin/members/pane.tsx +25 -15
- package/src/plugins/builtin/news/index.tsx +1 -0
- package/src/plugins/builtin/news/wire/categories.ts +19 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
- package/src/plugins/builtin/openfda/client.ts +14 -2
- package/src/plugins/builtin/openfda/index.tsx +3 -2
- package/src/plugins/builtin/openfda/model.ts +9 -0
- package/src/plugins/builtin/openfda/pane.tsx +42 -6
- package/src/plugins/builtin/options/analytics.ts +54 -6
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/model-spot.ts +5 -2
- package/src/plugins/builtin/options/strike-window.ts +2 -1
- package/src/plugins/builtin/options/table.ts +6 -9
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +42 -12
- package/src/plugins/builtin/options-positioning/client.ts +30 -0
- package/src/plugins/builtin/options-positioning/headless.ts +74 -21
- package/src/plugins/builtin/options-positioning/index.tsx +4 -4
- package/src/plugins/builtin/options-positioning/model.ts +122 -14
- package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
- package/src/plugins/builtin/options-scenario/client.ts +116 -47
- package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
- package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
- package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
- package/src/plugins/builtin/options-scenario/csp.ts +8 -2
- package/src/plugins/builtin/options-scenario/index.tsx +9 -7
- package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
- package/src/plugins/builtin/pe-band/model.ts +12 -49
- package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
- package/src/plugins/builtin/perps/cascade.ts +236 -0
- package/src/plugins/builtin/perps/client.ts +56 -2
- package/src/plugins/builtin/perps/freshness.ts +12 -10
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/live-rows.ts +10 -3
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +32 -31
- package/src/plugins/builtin/perps/status-line.ts +25 -0
- package/src/plugins/builtin/plugin-module.ts +5 -1
- package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
- package/src/plugins/builtin/portfolio-list/cli/render.ts +12 -7
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/rate-path/pane.tsx +2 -2
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/client.ts +18 -4
- package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
- package/src/plugins/builtin/relative-rotation/model.ts +16 -40
- package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
- package/src/plugins/builtin/research/event-model.ts +10 -8
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +11 -9
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
- package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
- package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
- package/src/plugins/builtin/scanner/feed.ts +2 -2
- package/src/plugins/builtin/scanner/flow-model.ts +50 -4
- package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
- package/src/plugins/builtin/scanner/flow-session.ts +146 -0
- package/src/plugins/builtin/scanner/headless.ts +204 -35
- package/src/plugins/builtin/scanner/index.ts +40 -6
- package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
- package/src/plugins/builtin/scanner/live-feed.ts +168 -0
- package/src/plugins/builtin/scanner/live-headless.ts +138 -0
- package/src/plugins/builtin/scanner/live-model.ts +362 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
- package/src/plugins/builtin/scanner/live-track.ts +186 -0
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/headless.ts +10 -1
- package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
- package/src/plugins/builtin/sec/fund-facts.ts +116 -34
- package/src/plugins/builtin/sec/headless.ts +2 -1
- package/src/plugins/builtin/series-inventory.ts +3 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
- package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
- package/src/plugins/builtin/shared/earnings-report.ts +2 -2
- package/src/plugins/builtin/shared/job-posting.ts +1 -0
- package/src/plugins/builtin/shared/macro-releases.ts +65 -0
- package/src/plugins/builtin/shared/members-menu.tsx +3 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
- package/src/plugins/builtin/shared/report-freshness.ts +3 -2
- package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
- package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
- package/src/plugins/builtin/shared/ticker-company.ts +77 -0
- package/src/plugins/builtin/shared/use-quote-board.ts +10 -3
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/headless.ts +6 -4
- package/src/plugins/builtin/themes/model.ts +17 -8
- package/src/plugins/builtin/themes/pane.tsx +7 -7
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
- package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +6 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
- package/src/plugins/builtin/time-sales/pane.tsx +31 -19
- package/src/plugins/builtin/treasury-daily/client.ts +41 -5
- package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
- package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
- package/src/plugins/builtin/treasury-daily/model.ts +50 -3
- package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
- package/src/plugins/builtin/vol-surface/charts.tsx +9 -4
- package/src/plugins/builtin/vol-surface/client.ts +9 -17
- package/src/plugins/builtin/vol-surface/headless.ts +63 -14
- package/src/plugins/builtin/vol-surface/index.tsx +2 -2
- package/src/plugins/builtin/vol-surface/model.ts +22 -7
- package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
- package/src/plugins/builtin/vol-surface/tables.ts +31 -3
- package/src/plugins/builtin/volatility/dvol.ts +46 -0
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +4 -4
- package/src/plugins/builtin/volatility/series.ts +26 -0
- package/src/plugins/builtin/world-indices/footer.ts +1 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/catalog-browser.ts +13 -8
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +5 -5
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
- package/src/remote/resources.ts +2 -1
- package/src/renderers/browser/app-services.ts +1 -7
- package/src/renderers/browser/config-host.ts +2 -55
- package/src/renderers/browser/main.tsx +7 -57
- package/src/renderers/dom/data-table/row.tsx +26 -0
- package/src/renderers/dom/desktop/list-view.tsx +13 -10
- package/src/renderers/dom/desktop/query-bar.tsx +4 -2
- package/src/renderers/dom/desktop/select-field.tsx +4 -2
- package/src/renderers/dom/host/chart-surface.tsx +21 -0
- package/src/renderers/electrobun/bun/desktop/app-name.ts +222 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
- package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
- package/src/renderers/electrobun/bun/index.ts +25 -1
- package/src/renderers/electrobun/shared/app-name.ts +30 -0
- package/src/renderers/electrobun/shared/protocol.ts +10 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +3 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- package/src/renderers/opentui/toast-host.tsx +71 -43
- package/src/series/registry.ts +1 -0
- package/src/series/types.ts +6 -0
- package/src/series/validate.ts +1 -0
- package/src/sources/gloomberb-cloud/index.ts +1 -0
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
- package/src/sources/provider-router/batches.ts +10 -2
- package/src/sources/provider-router/financial-routes.ts +3 -3
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/history.ts +28 -7
- package/src/sources/provider-router/index.ts +149 -10
- package/src/sources/provider-router/search.ts +13 -2
- package/src/state/app/context/index.tsx +3 -0
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/auth-transition.ts +19 -9
- package/src/sync/baseline.ts +10 -4
- package/src/sync/controller.ts +70 -19
- package/src/sync/core-contributors.ts +90 -31
- package/src/sync/react.ts +4 -1
- package/src/sync/types.ts +3 -0
- package/src/telemetry/stall-reports-dom.ts +38 -0
- package/src/telemetry/stall-reports-node.ts +75 -0
- package/src/telemetry/stall-reports.ts +214 -0
- package/src/telemetry/usage-counts.ts +0 -18
- package/src/test-support/preload.ts +14 -0
- package/src/theme/colors.ts +8 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name.ts +103 -3
- package/src/tickers/listing-venues.ts +38 -3
- package/src/tickers/search/bare-symbol.ts +36 -0
- package/src/tickers/search/index.ts +36 -7
- package/src/tickers/search/ranking.ts +10 -1
- package/src/tickers/search/types.ts +5 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/base-series.ts +13 -5
- package/src/time-series/daily-valuation.ts +62 -0
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +2 -4
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/forward-valuation.ts +32 -0
- package/src/time-series/fundamentals.ts +198 -7
- package/src/time-series/hooks.ts +1 -0
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/price-units.ts +72 -2
- package/src/time-series/resolve-context.ts +7 -1
- package/src/time-series/resolve.ts +116 -15
- package/src/time-series/series-presentation.ts +40 -6
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +5 -3
- package/src/time-series/studies.ts +278 -74
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +3 -3
- package/src/time-series/types.ts +27 -1
- package/src/time-series/valuation-releases.ts +39 -0
- package/src/types/data-provider.ts +9 -1
- package/src/types/desktop-window.ts +2 -0
- package/src/types/financials.ts +73 -2
- package/src/types/headless.ts +61 -5
- package/src/types/instrument.ts +3 -0
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +10 -1
- package/src/ui/host.tsx +3 -0
- package/src/updater/command-names.ts +12 -7
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +44 -0
- package/src/utils/financial-statements.ts +6 -1
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +24 -3
- package/src/utils/fx-market-hours.ts +26 -3
- package/src/utils/main-thread-stall.ts +35 -2
- package/src/utils/market-capitalization.ts +24 -1
- package/src/utils/network-failure.ts +9 -6
- package/src/utils/option-expiry.ts +48 -1
- package/src/utils/options.ts +10 -0
- package/src/utils/plugin-proxy-hosts.json +1 -0
- package/src/utils/price-history.ts +12 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
- package/src/renderers/browser/first-screen.ts +0 -193
|
@@ -34,10 +34,12 @@ import type { ResolvedSeries } from "../../../time-series/types";
|
|
|
34
34
|
import type { PaneProps } from "../../../types/plugin";
|
|
35
35
|
import { Box, Text } from "../../../ui";
|
|
36
36
|
import { formatCompact } from "../../../utils/format";
|
|
37
|
+
import { formatStrikeLabel } from "../../../utils/options";
|
|
37
38
|
import { parsePublicTickerKey } from "../../../utils/exchanges";
|
|
38
39
|
import { isPlainKey } from "../../../utils/keyboard";
|
|
39
40
|
import { nextHeaderSort, type SortDirection } from "../../../utils/sort-values";
|
|
40
41
|
import { useCloudAccessFooter } from "../shared/cloud-upgrade";
|
|
42
|
+
import { COIN_OPTIONS_HINT, isCoinWithoutOptions, optionsUnavailableTitle } from "../../../market-data/options-alternatives";
|
|
41
43
|
import {
|
|
42
44
|
loadGamma,
|
|
43
45
|
loadOpenInterest,
|
|
@@ -49,18 +51,23 @@ import {
|
|
|
49
51
|
} from "./client";
|
|
50
52
|
import {
|
|
51
53
|
ALL_EXPIRIES,
|
|
54
|
+
axisRatioOfDate,
|
|
52
55
|
chartWindow,
|
|
53
56
|
expiryAxis,
|
|
54
57
|
expiryLabel,
|
|
55
|
-
|
|
58
|
+
expiryWithTimeLabel,
|
|
59
|
+
formatContracts,
|
|
60
|
+
isCoinContract,
|
|
61
|
+
settlementTime,
|
|
62
|
+
wallsByExpiry,
|
|
56
63
|
formatCountChange,
|
|
57
64
|
formatDistance,
|
|
58
65
|
formatGamma,
|
|
59
66
|
formatLevel,
|
|
67
|
+
gammaTimesFigure,
|
|
60
68
|
positioningSpotDetail,
|
|
61
69
|
formatPayout,
|
|
62
70
|
formatRatio,
|
|
63
|
-
formatStrike,
|
|
64
71
|
gammaWalls,
|
|
65
72
|
netGammaByExpiry,
|
|
66
73
|
openInterestTotal,
|
|
@@ -92,26 +99,30 @@ const expirySize = (row: { callOI: number; putOI: number }) => row.callOI + row.
|
|
|
92
99
|
const reportedOpenInterest = (row: { callOI: number | null; putOI: number | null }) =>
|
|
93
100
|
(row.callOI == null ? 0 : row.callOI) + (row.putOI == null ? 0 : row.putOI);
|
|
94
101
|
|
|
95
|
-
function strikeColumns(changes: boolean): DataTableColumn[] {
|
|
102
|
+
function strikeColumns(changes: boolean, unit = ""): DataTableColumn[] {
|
|
96
103
|
return [
|
|
97
104
|
{ id: "strike", label: "Strike", width: 9, align: "left" },
|
|
98
|
-
{ id: "callOI", label:
|
|
105
|
+
{ id: "callOI", label: `Call OI${unit}`, width: 10 + unit.length, align: "right" },
|
|
99
106
|
...(changes ? [{ id: "callChange", label: "Call chg", width: 9, align: "right" as const }] : []),
|
|
100
|
-
{ id: "putOI", label:
|
|
107
|
+
{ id: "putOI", label: `Put OI${unit}`, width: 10 + unit.length, align: "right" },
|
|
101
108
|
...(changes ? [{ id: "putChange", label: "Put chg", width: 9, align: "right" as const }] : []),
|
|
102
109
|
{ id: "payout", label: "Payout $", width: 10, align: "right" },
|
|
103
110
|
];
|
|
104
111
|
}
|
|
105
|
-
function expiryColumns(changes: boolean, gamma: boolean): DataTableColumn[] {
|
|
112
|
+
function expiryColumns(changes: boolean, gamma: boolean, options: { settles?: boolean; walls?: boolean; unit?: string } = {}): DataTableColumn[] {
|
|
113
|
+
const unit = options.unit ?? "";
|
|
106
114
|
return [
|
|
107
|
-
|
|
115
|
+
// An expiry that settles at a stated time (Deribit's 08:00 UTC) names it.
|
|
116
|
+
{ id: "date", label: "Expiry", width: options.settles ? 21 : 11, align: "left" },
|
|
108
117
|
{ id: "days", label: "Days", width: 5, align: "right" },
|
|
109
|
-
{ id: "callOI", label:
|
|
110
|
-
{ id: "putOI", label:
|
|
118
|
+
{ id: "callOI", label: `Call OI${unit}`, width: 11 + unit.length, align: "right" },
|
|
119
|
+
{ id: "putOI", label: `Put OI${unit}`, width: 11 + unit.length, align: "right" },
|
|
111
120
|
{ id: "putCallRatio", label: "P/C", width: 6, align: "right" },
|
|
112
121
|
...(changes ? [{ id: "change", label: "OI chg", width: 10, align: "right" as const }] : []),
|
|
113
122
|
{ id: "maxPain", label: "Max pain", width: 9, align: "right" },
|
|
114
123
|
{ id: "distance", label: "Vs spot", width: 8, align: "right" },
|
|
124
|
+
...(options.walls ? [{ id: "callWall", label: "Call wall", width: 10, align: "right" as const },
|
|
125
|
+
{ id: "putWall", label: "Put wall", width: 9, align: "right" as const }] : []),
|
|
115
126
|
...(gamma ? [{ id: "netGamma", label: "Net GEX $", width: 11, align: "right" as const }] : []),
|
|
116
127
|
];
|
|
117
128
|
}
|
|
@@ -151,13 +162,18 @@ interface PositioningChartProps {
|
|
|
151
162
|
|
|
152
163
|
/** Columns across a numeric axis (strikes, or expiries one slot apart), with spot and the other levels as guide lines. */
|
|
153
164
|
function PositioningChart(props: PositioningChartProps) {
|
|
165
|
+
// The legend's values are one column's, under the cursor (the last column's
|
|
166
|
+
// without one), not the totals the figures above show: it names the column.
|
|
167
|
+
const column = props.formatCursor(props.selection.cursorDate ? axisRatioOfDate(props.selection.cursorDate) : 1);
|
|
168
|
+
const legendSeries = useMemo(() => column
|
|
169
|
+
? props.series.map((entry) => ({ ...entry, label: `${entry.label} at ${column}` })) : undefined, [column, props.series]);
|
|
154
170
|
const xAxis = useMemo(() => ({
|
|
155
171
|
ticks: props.ticks(Math.max(1, props.width - VALUE_GUTTER)),
|
|
156
172
|
markers: props.markers.flatMap((marker) => marker.ratio != null && marker.ratio >= 0 && marker.ratio <= 1
|
|
157
173
|
? [{ id: marker.id, xRatio: marker.ratio, label: marker.label, color: marker.color }] : []),
|
|
158
174
|
formatCursor: props.formatCursor,
|
|
159
175
|
}), [props.formatCursor, props.markers, props.ticks, props.width]);
|
|
160
|
-
return <CompositeChart series={props.series} panels={PANELS} width={props.width} height={props.height}
|
|
176
|
+
return <CompositeChart series={props.series} legendSeries={legendSeries} panels={PANELS} width={props.width} height={props.height}
|
|
161
177
|
focused={false} navigable={false} showLegend showTimeAxis xAxis={xAxis}
|
|
162
178
|
formatValue={(value) => props.formatValue(value)} formatAxisValue={props.formatAxisValue}
|
|
163
179
|
cursorDate={props.selection.cursorDate} onCursorDateChange={props.selection.onCursorDateChange}
|
|
@@ -170,6 +186,8 @@ const UNDERLYING_STATE_KEY = /^opx:(expiry|gammaExpiry|strike|expiryRow|gammaStr
|
|
|
170
186
|
|
|
171
187
|
export function OptionsPositioningPane(props: PaneProps) {
|
|
172
188
|
const { symbol } = usePaneTickerIdentity();
|
|
189
|
+
// A coin nothing lists options on (SOL-USD) says which coins have them, before any request.
|
|
190
|
+
if (symbol && isCoinWithoutOptions(symbol)) return <EmptyState title={optionsUnavailableTitle(parsePublicTickerKey(symbol).symbol)} hint={COIN_OPTIONS_HINT} />;
|
|
173
191
|
// A new underlying starts over rather than relabelling the last one's data.
|
|
174
192
|
return symbol ? <OptionsPositioningView key={symbol} {...props} symbol={symbol} />
|
|
175
193
|
: <EmptyState title="Choose an option underlying." />;
|
|
@@ -205,7 +223,13 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
205
223
|
const openInterest = useAsyncResource<OpenInterestPayload>(openInterestLoader, { keepPreviousData: true });
|
|
206
224
|
const data = openInterest.data;
|
|
207
225
|
const spot = data?.spot ?? null;
|
|
208
|
-
const
|
|
226
|
+
const venue = data?.venue;
|
|
227
|
+
const spotStamp = useMemo(() => positioningSpotDetail(spot, data?.spotAsOf, venue, data?.spotBasis),
|
|
228
|
+
[spot, data?.spotAsOf, data?.spotBasis, venue]);
|
|
229
|
+
// A coin's options (Deribit's) count open interest in coins, to tenths.
|
|
230
|
+
const coin = isCoinContract(data?.contract);
|
|
231
|
+
const unit = coin ? ` ${data!.contract!.unit}` : "";
|
|
232
|
+
const count = useCallback((value: number | null | undefined) => formatContracts(value, coin), [coin]);
|
|
209
233
|
const expiries = data?.expiries ?? [];
|
|
210
234
|
const shownExpiry = data?.expiry ?? requestedExpiry;
|
|
211
235
|
const changes = data?.previousOiDate != null;
|
|
@@ -276,9 +300,12 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
276
300
|
});
|
|
277
301
|
const info = useMemo<PaneFooterSegment[]>(() => [
|
|
278
302
|
...(accessSegment ? [accessSegment] : []),
|
|
279
|
-
...(
|
|
303
|
+
...(venue ? [{ id: "venue", parts: [{ text: venue, tone: "muted" as const }] }] : []),
|
|
304
|
+
// A venue that publishes open interest live (Deribit) says when it was read.
|
|
305
|
+
...(venue && data?.oiAsOf ? [{ id: "oi-date", parts: [{ text: `OI live ${data.oiAsOf.slice(11, 16)} UTC`, tone: "muted" as const }] }]
|
|
306
|
+
: data?.oiDate ? [{ id: "oi-date", parts: [{ text: `OI as of ${shortDate(data.oiDate)}`, tone: "muted" as const }] }] : []),
|
|
280
307
|
...(changes && tab !== "gex" ? [{ id: "oi-change", parts: [{ text: `chg since ${shortDate(data!.previousOiDate!)}`, tone: "muted" as const }] }] : []),
|
|
281
|
-
], [accessSegment, changes, data?.oiDate, data?.previousOiDate, tab]);
|
|
308
|
+
], [accessSegment, changes, data?.oiAsOf, data?.oiDate, data?.previousOiDate, tab, venue]);
|
|
282
309
|
const activeResource = tab === "gex" ? gamma : openInterest;
|
|
283
310
|
// Expiries' Net GEX column loads beside the open interest it sits with.
|
|
284
311
|
const columnGamma = tab === "expiries" && gammaReady ? gamma : null;
|
|
@@ -305,7 +332,7 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
305
332
|
: index === options.findIndex((entry) => entry.value === value) + 1 ? EXPIRY_NEXT_KEY : undefined,
|
|
306
333
|
})),
|
|
307
334
|
});
|
|
308
|
-
const expiryOptions = useMemo(() => expiries.map((row) => ({ value: row.date, label:
|
|
335
|
+
const expiryOptions = useMemo(() => expiries.map((row) => ({ value: row.date, label: expiryWithTimeLabel(row) })), [expiries]);
|
|
309
336
|
|
|
310
337
|
// ---- Strikes ----
|
|
311
338
|
const strikes = data?.strikes ?? [];
|
|
@@ -333,15 +360,17 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
333
360
|
] : [], [colors.negative, colors.positive, strikeChart, strikeWindow]);
|
|
334
361
|
const callTotal = expiries.find((row) => row.date === shownExpiry);
|
|
335
362
|
const strikeFigures = useMemo<StatItem[]>(() => !callTotal ? [] : [
|
|
336
|
-
{ id: "max-pain", label: "Max pain", value: maxPain == null ? "--" :
|
|
363
|
+
{ id: "max-pain", label: "Max pain", value: maxPain == null ? "--" : formatStrikeLabel(maxPain), color: colors.warning,
|
|
337
364
|
detail: maxPain == null ? undefined : `${formatDistance(maxPain, spot)} vs spot` },
|
|
338
365
|
{ id: "spot", label: "Spot", value: formatLevel(spot), ...spotStamp },
|
|
339
366
|
{ id: "pc", label: "P/C OI", value: formatRatio(callTotal.putCallRatio) },
|
|
340
|
-
{ id: "calls", label:
|
|
367
|
+
{ id: "calls", label: `Call OI${unit}`, value: formatCompact(callTotal.callOI), color: colors.positive,
|
|
341
368
|
detail: callTotal.callChange == null ? undefined : formatCountChange(callTotal.callChange) },
|
|
342
|
-
{ id: "puts", label:
|
|
369
|
+
{ id: "puts", label: `Put OI${unit}`, value: formatCompact(callTotal.putOI), color: colors.negative,
|
|
343
370
|
detail: callTotal.putChange == null ? undefined : formatCountChange(callTotal.putChange) },
|
|
344
|
-
|
|
371
|
+
...(settlementTime(callTotal.expiresAt) ? [{ id: "settles", label: "Settles", value: settlementTime(callTotal.expiresAt)!,
|
|
372
|
+
detail: expiryLabel(callTotal.date) }] : []),
|
|
373
|
+
], [callTotal, colors.negative, colors.positive, colors.warning, maxPain, spot, spotStamp, unit]);
|
|
345
374
|
const selectedStrikeRow = strikes.find((row) => strikeKey(row) === strikeSelectedId) ?? null;
|
|
346
375
|
const strikeStrip = useMemo<ChartStripSpec | null>(() => strikeWindow.length < 3 ? null : {
|
|
347
376
|
label: "Open interest by strike",
|
|
@@ -349,17 +378,17 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
349
378
|
const value = openInterestTotal(row);
|
|
350
379
|
return value == null ? [] : [value];
|
|
351
380
|
}),
|
|
352
|
-
value: selectedStrikeRow ? `${
|
|
381
|
+
value: selectedStrikeRow ? `${formatStrikeLabel(selectedStrikeRow.strike)} ${formatCompact(openInterestTotal(selectedStrikeRow) ?? undefined)}` : "",
|
|
353
382
|
color: colors.textMuted,
|
|
354
383
|
}, [colors.textMuted, selectedStrikeRow, strikeWindow]);
|
|
355
384
|
const renderStrike = useCallback((row: StrikeOpenInterest, column: DataTableColumn): DataTableCell => {
|
|
356
385
|
switch (column.id) {
|
|
357
386
|
case "strike": return {
|
|
358
|
-
text:
|
|
387
|
+
text: formatStrikeLabel(row.strike), value: row.strike,
|
|
359
388
|
color: row.strike === maxPain ? colors.warning : colors.textBright,
|
|
360
389
|
};
|
|
361
|
-
case "callOI": return { text:
|
|
362
|
-
case "putOI": return { text:
|
|
390
|
+
case "callOI": return { text: count(row.callOI), value: row.callOI, color: colors.positive };
|
|
391
|
+
case "putOI": return { text: count(row.putOI), value: row.putOI, color: colors.negative };
|
|
363
392
|
case "callChange":
|
|
364
393
|
case "putChange": {
|
|
365
394
|
const change = row[column.id];
|
|
@@ -369,16 +398,18 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
369
398
|
case "payout": return { text: formatPayout(row.payout), value: row.payout, color: colors.textMuted };
|
|
370
399
|
default: return { text: "" };
|
|
371
400
|
}
|
|
372
|
-
}, [colors, maxPain]);
|
|
401
|
+
}, [colors, count, maxPain]);
|
|
373
402
|
|
|
374
403
|
// ---- Expiries ----
|
|
375
404
|
// Older servers send no per-expiry gamma; the column then reads --.
|
|
376
405
|
const netGammas = useMemo(() => netGammaByExpiry(gamma.data), [gamma.data]);
|
|
406
|
+
const expiryWalls = useMemo(() => wallsByExpiry(gamma.data), [gamma.data]);
|
|
377
407
|
const expiryRows = useMemo(() => sortRows(expiries, expirySort, (row, id) =>
|
|
378
408
|
id === "date" ? row.date : id === "distance" ? (row.maxPain == null || spot == null ? null : row.maxPain / spot)
|
|
379
409
|
: id === "change" ? (row.callChange == null ? null : row.callChange + (row.putChange ?? 0))
|
|
380
410
|
: id === "netGamma" ? netGammas?.get(row.date) ?? null
|
|
381
|
-
:
|
|
411
|
+
: id === "callWall" || id === "putWall" ? expiryWalls?.get(row.date)?.[id] ?? null
|
|
412
|
+
: (row[id as keyof ExpiryOpenInterest] as number | null)), [expiries, expiryWalls, expirySort, netGammas, spot]);
|
|
382
413
|
const expiryChart = useMemo(() => expiries.length >= 3 ? expiryAxis(expiryDates) : null, [expiries.length, expiryDates]);
|
|
383
414
|
const expirySelectedId = expiryRows.some((row) => row.date === selectedExpiry) ? selectedExpiry!
|
|
384
415
|
: shownExpiry && expiryRows.some((row) => row.date === shownExpiry) ? shownExpiry : expiryRows[0]?.date ?? null;
|
|
@@ -416,17 +447,22 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
416
447
|
}, [colors.textMuted, expiries, selectedExpiryRow]);
|
|
417
448
|
const renderExpiry = useCallback((row: ExpiryOpenInterest, column: DataTableColumn): DataTableCell => {
|
|
418
449
|
switch (column.id) {
|
|
419
|
-
case "date": return { text:
|
|
450
|
+
case "date": return { text: expiryWithTimeLabel(row), value: row.date, color: colors.textBright };
|
|
420
451
|
case "days": return { text: String(row.days), value: row.days, color: colors.textMuted };
|
|
421
|
-
case "callOI": return { text:
|
|
422
|
-
case "putOI": return { text:
|
|
452
|
+
case "callOI": return { text: count(row.callOI), value: row.callOI, color: colors.positive };
|
|
453
|
+
case "putOI": return { text: count(row.putOI), value: row.putOI, color: colors.negative };
|
|
454
|
+
case "callWall":
|
|
455
|
+
case "putWall": {
|
|
456
|
+
const strike = expiryWalls?.get(row.date)?.[column.id] ?? null;
|
|
457
|
+
return { text: strike == null ? "--" : formatStrikeLabel(strike), value: strike, color: colors.textMuted };
|
|
458
|
+
}
|
|
423
459
|
case "putCallRatio": return { text: formatRatio(row.putCallRatio), value: row.putCallRatio };
|
|
424
460
|
case "change": {
|
|
425
461
|
const change = row.callChange == null ? null : row.callChange + (row.putChange ?? 0);
|
|
426
462
|
return { text: formatCountChange(change), value: change,
|
|
427
463
|
color: change == null || change === 0 ? colors.textMuted : change > 0 ? colors.positive : colors.negative };
|
|
428
464
|
}
|
|
429
|
-
case "maxPain": return { text: row.maxPain == null ? "--" :
|
|
465
|
+
case "maxPain": return { text: row.maxPain == null ? "--" : formatStrikeLabel(row.maxPain), value: row.maxPain, color: colors.warning };
|
|
430
466
|
case "distance": return {
|
|
431
467
|
text: formatDistance(row.maxPain, spot),
|
|
432
468
|
value: row.maxPain == null || spot == null ? null : Number(((row.maxPain / spot - 1) * 100).toFixed(4)),
|
|
@@ -439,7 +475,7 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
439
475
|
}
|
|
440
476
|
default: return { text: "" };
|
|
441
477
|
}
|
|
442
|
-
}, [colors, netGammas, spot]);
|
|
478
|
+
}, [colors, count, expiryWalls, netGammas, spot]);
|
|
443
479
|
|
|
444
480
|
// ---- GEX ----
|
|
445
481
|
const gammaData = gamma.data;
|
|
@@ -468,19 +504,24 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
468
504
|
const band = gammaData?.band;
|
|
469
505
|
const shares = band ? `${Math.round(band.shareLow * 100)}-${Math.round(band.shareHigh * 100)}%` : "";
|
|
470
506
|
const walls = useMemo(() => gammaWalls(gammaStrikes), [gammaStrikes]);
|
|
507
|
+
const gammaTimes = useMemo(() => gammaTimesFigure(gammaData?.gammaAsOf, gammaData?.quotesAsOf),
|
|
508
|
+
[gammaData?.gammaAsOf, gammaData?.quotesAsOf]);
|
|
471
509
|
const gammaFigures = useMemo<StatItem[]>(() => !gammaData?.total ? [] : [
|
|
472
510
|
{ id: "net", label: "Net GEX", value: formatGamma(gammaData.total.net), detail: "$ per 1% move",
|
|
473
511
|
color: gammaData.total.net >= 0 ? colors.positive : colors.negative },
|
|
474
512
|
{ id: "flip", label: "Flip", value: formatLevel(gammaData.flip),
|
|
475
513
|
detail: gammaData.flip == null ? "none within 15%" : `${formatDistance(gammaData.flip, gammaSpot)} vs spot` },
|
|
476
514
|
...([["call-wall", "Call wall", walls.callWall], ["put-wall", "Put wall", walls.putWall]] as const).map(([id, label, strike]) => ({
|
|
477
|
-
id, label, value: strike == null ? "--" :
|
|
515
|
+
id, label, value: strike == null ? "--" : formatStrikeLabel(strike),
|
|
478
516
|
detail: strike == null ? undefined : `${formatDistance(strike, gammaSpot)} vs spot`,
|
|
479
517
|
})),
|
|
480
518
|
...(band ? [{ id: "band", label: "Dealer range", value: `${formatGamma(band.low)} to ${formatGamma(band.high)}`,
|
|
481
519
|
detail: `${shares} with dealers${gammaData.boxes?.length ? ", boxes out" : ""}` }] : []),
|
|
482
|
-
{ id: "spot", label: "Spot", value: formatLevel(gammaSpot),
|
|
483
|
-
|
|
520
|
+
{ id: "spot", label: "Spot", value: formatLevel(gammaSpot),
|
|
521
|
+
...positioningSpotDetail(gammaSpot, gammaData.spotAsOf, gammaData.venue, gammaData.spotBasis) },
|
|
522
|
+
// Gamma is evaluated at the spot's time; before the open its volatilities are the last session's.
|
|
523
|
+
...(gammaTimes ? [{ id: "gamma-at", ...gammaTimes }] : []),
|
|
524
|
+
], [band, colors.negative, colors.positive, gammaData, gammaSpot, gammaTimes, shares, walls]);
|
|
484
525
|
// The assumption rides in the legend, where the numbers it changes are read; the
|
|
485
526
|
// dealer range figure names its own share. It gives way before the series name does.
|
|
486
527
|
const assumption = width >= 60 ? "dealers long calls, short puts" : width >= 48 ? "long calls, short puts" : null;
|
|
@@ -488,12 +529,12 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
488
529
|
const gammaStrip = useMemo<ChartStripSpec | null>(() => gammaWindow.length < 3 ? null : {
|
|
489
530
|
label: "Net GEX by strike",
|
|
490
531
|
values: gammaWindow.map((row) => row.net),
|
|
491
|
-
value: selectedGammaRow ? `${
|
|
532
|
+
value: selectedGammaRow ? `${formatStrikeLabel(selectedGammaRow.strike)} ${formatGamma(selectedGammaRow.net)}` : "",
|
|
492
533
|
color: colors.textMuted,
|
|
493
534
|
}, [colors.textMuted, gammaWindow, selectedGammaRow]);
|
|
494
535
|
const renderGamma = useCallback((row: GammaStrike, column: DataTableColumn): DataTableCell => {
|
|
495
536
|
switch (column.id) {
|
|
496
|
-
case "strike": return { text:
|
|
537
|
+
case "strike": return { text: formatStrikeLabel(row.strike), value: row.strike, color: colors.textBright };
|
|
497
538
|
case "calls": return { text: formatGamma(row.calls), value: row.calls, color: colors.positive };
|
|
498
539
|
case "puts": return { text: formatGamma(-row.puts), value: -row.puts, color: colors.negative };
|
|
499
540
|
case "net": return { text: formatGamma(row.net), value: row.net, color: row.net >= 0 ? colors.positive : colors.negative };
|
|
@@ -506,8 +547,10 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
506
547
|
}
|
|
507
548
|
}, [colors, gammaSpot]);
|
|
508
549
|
|
|
509
|
-
const strikeColumnList = useMemo(() => strikeColumns(changes), [changes]);
|
|
510
|
-
const
|
|
550
|
+
const strikeColumnList = useMemo(() => strikeColumns(changes, unit), [changes, unit]);
|
|
551
|
+
const settles = expiries.some((row) => settlementTime(row.expiresAt));
|
|
552
|
+
const expiryColumnList = useMemo(() => expiryColumns(changes, hasGamma, { settles, walls: !!expiryWalls, unit }),
|
|
553
|
+
[changes, expiryWalls, hasGamma, settles, unit]);
|
|
511
554
|
const markerColor = colors.textBright;
|
|
512
555
|
|
|
513
556
|
let content: ReactNode = null;
|
|
@@ -532,8 +575,8 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
532
575
|
{ id: "spot", ratio: spot == null ? null : strikeChart.ratio(spot), label: "spot", color: markerColor },
|
|
533
576
|
{ id: "max-pain", ratio: maxPain == null ? null : strikeChart.ratio(maxPain), label: "max pain", color: colors.warning },
|
|
534
577
|
]}
|
|
535
|
-
formatCursor={(ratio) =>
|
|
536
|
-
formatValue={(value) =>
|
|
578
|
+
formatCursor={(ratio) => formatStrikeLabel(nearestStrike(strikeWindow, strikeChart.at(ratio)))}
|
|
579
|
+
formatValue={(value) => count(Math.abs(value))}
|
|
537
580
|
formatAxisValue={(value, domain) => formatCompactAxis(Math.abs(value), domain)}
|
|
538
581
|
selection={strikeSelection} remoteKind="opx-strikes" />,
|
|
539
582
|
minRows: CHART_MIN_ROWS,
|
|
@@ -550,7 +593,8 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
550
593
|
onHeaderClick={(id) => setExpirySort((current) => nextHeaderSort(current, id))}
|
|
551
594
|
emptyStateTitle="No listed expiries."
|
|
552
595
|
getExportMetadata={() => [["open interest as of", data?.oiDate ?? ""], ["change since", data?.previousOiDate ?? ""], ["spot", spot ?? ""],
|
|
553
|
-
...(hasGamma ? [["net GEX unit", "dollars of dealer delta per 1% move"], ["
|
|
596
|
+
...(hasGamma ? [["net GEX unit", "dollars of dealer delta per 1% move"], ["gamma at", netGammas ? gamma.data?.gammaAsOf ?? "" : ""],
|
|
597
|
+
["vols from", netGammas ? gamma.data?.quotesAsOf ?? "" : ""]] : [])]}
|
|
554
598
|
rootBefore={<ChartTableHeader width={width} height={bodyHeight} figures={ladderFigures}
|
|
555
599
|
tableRows={expiryRows.length} tableChromeRows={chartTableChromeRows(expiryColumnList, width)}
|
|
556
600
|
chart={expiryChart ? {
|
|
@@ -560,7 +604,7 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
560
604
|
const date = expiryChart.at(ratio);
|
|
561
605
|
return date ? expiryLabel(date) : "";
|
|
562
606
|
}}
|
|
563
|
-
formatValue={(value) =>
|
|
607
|
+
formatValue={(value) => count(Math.abs(value))}
|
|
564
608
|
formatAxisValue={(value, domain) => formatCompactAxis(Math.abs(value), domain)}
|
|
565
609
|
selection={expirySelection} remoteKind="opx-expiries" />,
|
|
566
610
|
minRows: CHART_MIN_ROWS - 1,
|
|
@@ -589,7 +633,8 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
589
633
|
["range with boxes high", gammaData.bandWithBoxes.high],
|
|
590
634
|
["box legs left out", (gammaData.boxes ?? []).map((leg) => `${leg.expiry} ${leg.strike} x${leg.contracts}`).join("; ")]] : []),
|
|
591
635
|
["flip", gammaData.flip ?? ""], ["call wall", walls.callWall ?? ""], ["put wall", walls.putWall ?? ""],
|
|
592
|
-
["open interest as of", gammaData.oiDate ?? ""], ["
|
|
636
|
+
["open interest as of", gammaData.oiDate ?? ""], ["gamma at", gammaData.gammaAsOf ?? ""],
|
|
637
|
+
["vols from", gammaData.quotesAsOf ?? ""]]}
|
|
593
638
|
rootBefore={<ChartTableHeader width={width} height={bodyHeight} query={query} figures={gammaFigures}
|
|
594
639
|
tableRows={gammaRows.length} tableChromeRows={chartTableChromeRows(GAMMA_COLUMNS, width)}
|
|
595
640
|
chart={gammaChart ? {
|
|
@@ -599,7 +644,7 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
599
644
|
{ id: "spot", ratio: gammaSpot == null ? null : gammaChart.ratio(gammaSpot), label: "spot", color: markerColor },
|
|
600
645
|
{ id: "flip", ratio: gammaData.flip == null ? null : gammaChart.ratio(gammaData.flip), label: "flip", color: colors.warning },
|
|
601
646
|
]}
|
|
602
|
-
formatCursor={(ratio) =>
|
|
647
|
+
formatCursor={(ratio) => formatStrikeLabel(nearestStrike(gammaWindow, gammaChart.at(ratio)))}
|
|
603
648
|
formatValue={formatGamma} formatAxisValue={formatCompactAxis}
|
|
604
649
|
legendAccessory={assumption ? <Text fg={colors.textMuted}>{assumption}</Text> : undefined}
|
|
605
650
|
legendAccessoryWidth={assumption?.length}
|
|
@@ -384,11 +384,52 @@ export const SCENARIO_STRATEGIES = ["vertical", "straddle", "put", "csp", "colla
|
|
|
384
384
|
type ScenarioStrategy = typeof SCENARIO_STRATEGIES[number];
|
|
385
385
|
const isScenarioStrategy = (value: unknown): value is ScenarioStrategy => SCENARIO_STRATEGIES.includes(value as ScenarioStrategy);
|
|
386
386
|
|
|
387
|
-
interface StrategyPricing { spot: number; asOf: number; rate: number; dividendYield: number }
|
|
387
|
+
export interface StrategyPricing { spot: number; asOf: number; rate: number; dividendYield: number }
|
|
388
388
|
|
|
389
389
|
const usableVolatility = (value: unknown): value is number => positive(value) && value <= 5;
|
|
390
390
|
|
|
391
|
-
interface StrategyParity { forward: number; daysToExpiry: number }
|
|
391
|
+
export interface StrategyParity { forward: number; daysToExpiry: number }
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* The contracts a strategy seed picks from: every two-sided quote with the IV
|
|
395
|
+
* its midpoint implies against its expiry's parity forward (or a usable
|
|
396
|
+
* provider IV), each side nearest `spot` first. `parityFor` is the forward an
|
|
397
|
+
* expiry's IVs were solved on, null without a rate to solve them by.
|
|
398
|
+
*/
|
|
399
|
+
export interface QuotedChain {
|
|
400
|
+
calls: QuotedContract[];
|
|
401
|
+
puts: QuotedContract[];
|
|
402
|
+
parityFor(expiration: number): StrategyParity | null;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** The quoted chain every seed reads, as `scenarioStrategyLegs` describes it. */
|
|
406
|
+
function quoteStrategyChain(chain: OptionsChain, spot: number, pricing: StrategyPricing | null): QuotedChain {
|
|
407
|
+
const parity = new Map<number, StrategyParity | null>();
|
|
408
|
+
const parityFor = (expiration: number) => {
|
|
409
|
+
if (!pricing) return null;
|
|
410
|
+
if (!parity.has(expiration)) {
|
|
411
|
+
const daysToExpiry = daysToExpiryFrom(expiration, pricing.asOf);
|
|
412
|
+
const { forward } = extractImpliedForward(chain.calls.filter((call) => call.expiration === expiration),
|
|
413
|
+
chain.puts.filter((put) => put.expiration === expiration), pricing.spot, daysToExpiry / 365, pricing.rate,
|
|
414
|
+
chain.underlyingSymbol);
|
|
415
|
+
parity.set(expiration, forward == null ? null : { forward, daysToExpiry });
|
|
416
|
+
}
|
|
417
|
+
return parity.get(expiration)!;
|
|
418
|
+
};
|
|
419
|
+
const quoted = (contracts: OptionsChain["calls"], side: ScenarioLeg["side"]) => contracts.flatMap((contract): QuotedContract[] => {
|
|
420
|
+
const mid = optionMid(contract);
|
|
421
|
+
if (!positive(contract.strike) || mid == null) return [];
|
|
422
|
+
const expiry = parityFor(contract.expiration);
|
|
423
|
+
// q=r turns the spot pricer into discounted forward pricing.
|
|
424
|
+
const volatility = expiry ? solveImpliedVolatility({ side, spot: expiry.forward, strike: contract.strike,
|
|
425
|
+
daysToExpiry: expiry.daysToExpiry, rate: pricing!.rate, dividendYield: pricing!.rate }, mid).volatility : null;
|
|
426
|
+
if (usableVolatility(volatility)) return [{ contract, side, mid, volatility, solved: true }];
|
|
427
|
+
return usableVolatility(contract.impliedVolatility) ? [{ contract, side, mid, volatility: contract.impliedVolatility, solved: false }] : [];
|
|
428
|
+
});
|
|
429
|
+
const nearest = (a: { contract: { strike: number } }, b: { contract: { strike: number } }) =>
|
|
430
|
+
Math.abs(a.contract.strike - spot) - Math.abs(b.contract.strike - spot);
|
|
431
|
+
return { calls: quoted(chain.calls, "call").sort(nearest), puts: quoted(chain.puts, "put").sort(nearest), parityFor };
|
|
432
|
+
}
|
|
392
433
|
|
|
393
434
|
/**
|
|
394
435
|
* An explicit strategy request uses only two-sided quotes and enters each leg
|
|
@@ -418,33 +459,8 @@ function scenarioStrategyLegs(
|
|
|
418
459
|
chain: OptionsChain, spot: number, strategy: ScenarioStrategy, pricing: StrategyPricing | null, strike?: number,
|
|
419
460
|
collar: { put?: number; call?: number } = {}, delta?: number, unusable: UnusableChainText = defaultUnusableChainText,
|
|
420
461
|
): { legs: ScenarioLeg[]; parity: StrategyParity | null } {
|
|
421
|
-
const
|
|
422
|
-
const parityFor = (expiration: number) => {
|
|
423
|
-
if (!pricing) return null;
|
|
424
|
-
if (!parity.has(expiration)) {
|
|
425
|
-
const daysToExpiry = daysToExpiryFrom(expiration, pricing.asOf);
|
|
426
|
-
const { forward } = extractImpliedForward(chain.calls.filter((call) => call.expiration === expiration),
|
|
427
|
-
chain.puts.filter((put) => put.expiration === expiration), pricing.spot, daysToExpiry / 365, pricing.rate,
|
|
428
|
-
chain.underlyingSymbol);
|
|
429
|
-
parity.set(expiration, forward == null ? null : { forward, daysToExpiry });
|
|
430
|
-
}
|
|
431
|
-
return parity.get(expiration)!;
|
|
432
|
-
};
|
|
433
|
-
const quoted = (contracts: OptionsChain["calls"], side: ScenarioLeg["side"]) => contracts.flatMap((contract) => {
|
|
434
|
-
const mid = optionMid(contract);
|
|
435
|
-
if (!positive(contract.strike) || mid == null) return [];
|
|
436
|
-
const expiry = parityFor(contract.expiration);
|
|
437
|
-
// q=r turns the spot pricer into discounted forward pricing.
|
|
438
|
-
const volatility = expiry ? solveImpliedVolatility({ side, spot: expiry.forward, strike: contract.strike,
|
|
439
|
-
daysToExpiry: expiry.daysToExpiry, rate: pricing!.rate, dividendYield: pricing!.rate }, mid).volatility : null;
|
|
440
|
-
if (usableVolatility(volatility)) return [{ contract, mid, volatility, solved: true }];
|
|
441
|
-
return usableVolatility(contract.impliedVolatility) ? [{ contract, mid, volatility: contract.impliedVolatility, solved: false }] : [];
|
|
442
|
-
});
|
|
443
|
-
const nearest = (a: { contract: { strike: number } }, b: { contract: { strike: number } }) =>
|
|
444
|
-
Math.abs(a.contract.strike - spot) - Math.abs(b.contract.strike - spot);
|
|
462
|
+
const { calls, puts, parityFor } = quoteStrategyChain(chain, spot, pricing);
|
|
445
463
|
const anchored = ({ contract }: { contract: { strike: number } }) => strike == null || Math.abs(contract.strike - strike) < 1e-9;
|
|
446
|
-
const calls = quoted(chain.calls, "call").sort(nearest);
|
|
447
|
-
const puts = quoted(chain.puts, "put").sort(nearest);
|
|
448
464
|
const missing = (candidates: { contract: { strike: number } }[], at = strike, what: string = strategy === "csp" ? "put to sell" : strategy) => {
|
|
449
465
|
if (at == null) return new Error(unusable("strategy"));
|
|
450
466
|
const listed = [...new Set(candidates.map(({ contract }) => contract.strike))]
|
|
@@ -455,7 +471,7 @@ function scenarioStrategyLegs(
|
|
|
455
471
|
};
|
|
456
472
|
if (strategy === "collar") return collarLegs(calls, puts, spot, collar, missing, parityFor, unusable);
|
|
457
473
|
if (strategy === "put" || strategy === "csp") {
|
|
458
|
-
const put = delta == null ? puts.find(anchored) :
|
|
474
|
+
const put = delta == null ? puts.find(anchored) : nearestDeltaContract(puts, -delta, spot, pricing, parityFor);
|
|
459
475
|
if (!put) throw missing(puts);
|
|
460
476
|
return { legs: [{ id: put.contract.contractSymbol, side: "put", quantity: strategy === "csp" ? -1 : 1, strike: put.contract.strike,
|
|
461
477
|
expiration: put.contract.expiration, price: put.mid, priceSource: "mid", volatility: put.volatility, multiplier: 100,
|
|
@@ -480,10 +496,10 @@ function scenarioStrategyLegs(
|
|
|
480
496
|
return { legs, parity: first.solved || second.solved ? parityFor(first.contract.expiration) : null };
|
|
481
497
|
}
|
|
482
498
|
|
|
483
|
-
interface QuotedContract { contract: OptionsChain["calls"][number]; mid: number; volatility: number; solved: boolean }
|
|
499
|
+
export interface QuotedContract { contract: OptionsChain["calls"][number]; side: ScenarioLeg["side"]; mid: number; volatility: number; solved: boolean }
|
|
484
500
|
|
|
485
501
|
/** 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;
|
|
502
|
+
export type UnusableChainText = (what: string, next?: boolean) => string;
|
|
487
503
|
const defaultUnusableChainText: UnusableChainText = (what) => `The selected chain has no complete quoted ${what} with usable IV`;
|
|
488
504
|
|
|
489
505
|
/**
|
|
@@ -502,38 +518,91 @@ function unusableChainText(market: ScenarioMarketSnapshot, exchange: string | un
|
|
|
502
518
|
}
|
|
503
519
|
|
|
504
520
|
/**
|
|
505
|
-
*
|
|
506
|
-
* IV was solved on when it was (forward delta, carried to spot), else at
|
|
507
|
-
* seed's spot. Null when it cannot be valued.
|
|
521
|
+
* An option's spot delta at the IV it trades at: against the parity forward
|
|
522
|
+
* its IV was solved on when it was (forward delta, carried to spot), else at
|
|
523
|
+
* the seed's spot. Null when it cannot be valued.
|
|
508
524
|
*/
|
|
509
|
-
function
|
|
510
|
-
const { contract, volatility, solved } = quoted;
|
|
525
|
+
function quotedDelta(quoted: QuotedContract, spot: number, pricing: StrategyPricing | null, parity: StrategyParity | null): number | null {
|
|
526
|
+
const { contract, side, volatility, solved } = quoted;
|
|
511
527
|
if (!pricing) return null;
|
|
512
528
|
if (solved && parity) {
|
|
513
529
|
// 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
|
|
530
|
+
const forwardDelta = valueOption({ side, spot: parity.forward, strike: contract.strike, daysToExpiry: parity.daysToExpiry,
|
|
515
531
|
rate: pricing.rate, dividendYield: pricing.rate, volatility }).delta;
|
|
516
532
|
return Number.isFinite(forwardDelta) ? forwardDelta * Math.exp((pricing.rate - pricing.dividendYield) * parity.daysToExpiry / 365) : null;
|
|
517
533
|
}
|
|
518
534
|
const years = daysToExpiryFrom(contract.expiration, pricing.asOf) / 365;
|
|
519
|
-
return optionDelta({ spot, years, volatility, rate: pricing.rate, dividendYield: pricing.dividendYield }, contract.strike,
|
|
535
|
+
return optionDelta({ spot, years, volatility, rate: pricing.rate, dividendYield: pricing.dividendYield }, contract.strike, side);
|
|
520
536
|
}
|
|
521
537
|
|
|
522
|
-
/**
|
|
523
|
-
|
|
524
|
-
|
|
538
|
+
/**
|
|
539
|
+
* The contract whose delta is nearest `target` (negative for a put, as its
|
|
540
|
+
* delta reads), the one further out of the money on a tie (the lower strike
|
|
541
|
+
* of two puts, the higher of two calls); null when none can be valued.
|
|
542
|
+
*/
|
|
543
|
+
export function nearestDeltaContract(
|
|
544
|
+
candidates: readonly QuotedContract[], target: number, spot: number, pricing: StrategyPricing | null,
|
|
525
545
|
parityFor: (expiration: number) => StrategyParity | null,
|
|
526
546
|
): QuotedContract | null {
|
|
527
|
-
let best: {
|
|
528
|
-
for (const
|
|
529
|
-
const delta =
|
|
547
|
+
let best: { quoted: QuotedContract; distance: number } | null = null;
|
|
548
|
+
for (const quoted of candidates) {
|
|
549
|
+
const delta = quotedDelta(quoted, spot, pricing, quoted.solved ? parityFor(quoted.contract.expiration) : null);
|
|
530
550
|
if (delta == null) continue;
|
|
531
551
|
const distance = Math.abs(delta - target);
|
|
532
|
-
|
|
533
|
-
|
|
552
|
+
const further = quoted.side === "put" ? quoted.contract.strike < (best?.quoted.contract.strike ?? Infinity)
|
|
553
|
+
: quoted.contract.strike > (best?.quoted.contract.strike ?? -Infinity);
|
|
554
|
+
if (!best || distance < best.distance - 1e-12 || (Math.abs(distance - best.distance) <= 1e-12 && further)) {
|
|
555
|
+
best = { quoted, distance };
|
|
534
556
|
}
|
|
535
557
|
}
|
|
536
|
-
return best?.
|
|
558
|
+
return best?.quoted ?? null;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/** A seeded leg: `quantity` contracts of a quoted option, entered at its midpoint with the IV that midpoint implies, as every seed enters it. */
|
|
562
|
+
export function seededLeg({ contract, side, mid, volatility, solved }: QuotedContract, quantity: number): ScenarioLeg {
|
|
563
|
+
return { id: contract.contractSymbol, side, quantity, strike: contract.strike, expiration: contract.expiration, price: mid,
|
|
564
|
+
priceSource: "mid", volatility, multiplier: 100, ...(solved ? { volatilitySource: "mid" as const } : {}) };
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/** What a strategy screen picks from a quoted chain: its legs, and the shares it holds with them (a covered call's). */
|
|
568
|
+
export interface ScreenSeed { legs: ScenarioLeg[]; shares?: number }
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* A position a strategy screen seeds as OSA seeds `--strategy`: legs picked
|
|
572
|
+
* from the market's chain (or `merged`, a calendar's two expiries together) at
|
|
573
|
+
* their quote midpoints, valued at the market's rate and dividend yield from
|
|
574
|
+
* the spot the first expiry's parity forward implies when a leg's IV was solved
|
|
575
|
+
* against it. Shares are bought at that spot. `pick` reads the quoted chain
|
|
576
|
+
* with its pricing, so a leg can be chosen by delta.
|
|
577
|
+
*/
|
|
578
|
+
export function seedScreenPosition(
|
|
579
|
+
market: ScenarioMarketSnapshot,
|
|
580
|
+
pick: (quoted: QuotedChain, pricing: StrategyPricing, unusable: UnusableChainText) => ScreenSeed,
|
|
581
|
+
merged?: OptionsChain,
|
|
582
|
+
): ScenarioPosition {
|
|
583
|
+
if (!market.chain || market.warnings.includes("Options chain is stale")) {
|
|
584
|
+
throw new Error(market.missingExpiry ?? "A current options chain is required to seed a strategy");
|
|
585
|
+
}
|
|
586
|
+
const chain = merged ?? market.chain;
|
|
587
|
+
if (market.spot == null) throw new Error("A current underlying price is required");
|
|
588
|
+
if (market.rate == null) throw new Error("Choosing legs by delta needs the Treasury rate");
|
|
589
|
+
if (market.dividendYield == null) throw new Error("Dividend yield unavailable");
|
|
590
|
+
const pricing = { spot: market.spot, asOf: market.asOf, rate: market.rate, dividendYield: market.dividendYield };
|
|
591
|
+
const quoted = quoteStrategyChain(chain, market.spot, pricing);
|
|
592
|
+
const { legs, shares } = pick(quoted, pricing, unusableChainText(market, market.exchange));
|
|
593
|
+
const first = Math.min(...legs.map((leg) => leg.expiration));
|
|
594
|
+
const parity = legs.some((leg) => leg.expiration === first && leg.volatilitySource === "mid") ? quoted.parityFor(first) : null;
|
|
595
|
+
const spot = parity ? parity.forward * Math.exp(-(pricing.rate - pricing.dividendYield) * parity.daysToExpiry / 365) : market.spot;
|
|
596
|
+
const position: ScenarioPosition = {
|
|
597
|
+
symbol: market.symbol, exchange: market.exchange, currency: scenarioCurrency(undefined, market.currency, market.exchange),
|
|
598
|
+
spot, rate: pricing.rate, dividendYield: pricing.dividendYield, asOf: market.asOf, legs,
|
|
599
|
+
...(shares ? { underlying: { quantity: shares, price: spot } } : {}),
|
|
600
|
+
};
|
|
601
|
+
const error = validatePosition(position);
|
|
602
|
+
if (error) throw new Error(error);
|
|
603
|
+
const contracts = [...chain.calls, ...chain.puts].filter((contract) => legs.some((leg) => leg.id === contract.contractSymbol));
|
|
604
|
+
if (contracts.some((contract) => contract.currency && contract.currency !== position.currency)) throw new Error("Strategy currency differs from the underlying quote currency");
|
|
605
|
+
return position;
|
|
537
606
|
}
|
|
538
607
|
|
|
539
608
|
/** How far out of the money a collar's put and call sit when their strikes are not given. */
|