@gloom-sh/terminal 0.18.5 → 0.19.1
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 +163 -8
- package/src/api-client/app-attention.ts +3 -0
- package/src/api-client/beneficial-owners.ts +3 -0
- package/src/api-client/company-kpis.ts +3 -0
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +19 -7
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/hiring.ts +3 -0
- package/src/api-client/index.ts +15 -0
- package/src/api-client/ipo.ts +24 -1
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +1 -1
- package/src/api-client/mna.ts +3 -0
- package/src/api-client/paths.ts +9 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/research-activity.ts +3 -2
- package/src/api-client/roadmap.ts +85 -0
- package/src/api-client/types.ts +82 -2
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +0 -2
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/core-builtin-allowlist.json +2 -2
- 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/market.ts +107 -38
- package/src/cli/commands/news-search.ts +22 -0
- package/src/cli/commands/options-chain.ts +48 -7
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +147 -32
- package/src/cli/commands/search.ts +21 -1
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +87 -36
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +25 -2
- package/src/cli/glossary.ts +31 -2
- package/src/cli/history-rows.ts +57 -9
- package/src/cli/index-roots.ts +0 -7
- package/src/cli/index.ts +2 -0
- package/src/cli/listing-arg.ts +235 -60
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +4 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/discovery.ts +21 -1
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +93 -4
- package/src/cli/pane-functions/function-help.ts +20 -10
- package/src/cli/pane-functions/headless.ts +17 -7
- package/src/cli/pane-functions/index.ts +54 -16
- package/src/cli/pane-functions/market-session.ts +66 -4
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/resolver.ts +19 -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 +22 -11
- package/src/cli/saved-copy-persistence.ts +88 -0
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +4 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-surface.tsx +52 -3
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +39 -4
- 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/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/list/model.ts +5 -0
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/routes/root/runtime.ts +6 -7
- package/src/components/command-bar/selection-runtime.ts +52 -3
- package/src/components/command-bar/surface/index.tsx +2 -0
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/pane/capture-stamp.ts +2 -2
- package/src/components/layout/pane/footer/index.tsx +22 -6
- package/src/components/layout/pane/footer/model.ts +83 -15
- package/src/components/layout/pane/status-footer.ts +35 -2
- package/src/components/layout/status-widget-space.ts +9 -0
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
- 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 +8 -21
- 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 +18 -3
- package/src/components/ui/icon.tsx +4 -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/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/plugin-cache.ts +5 -3
- package/src/data/saved-copies.ts +8 -0
- 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 +103 -142
- package/src/i18n/es.ts +20 -6
- package/src/i18n/ja.ts +20 -6
- package/src/i18n/ko.ts +20 -6
- package/src/i18n/zh-cn.ts +20 -6
- package/src/i18n/zh-tw.ts +20 -6
- package/src/market-data/depositary-pair.ts +27 -3
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +16 -3
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/options-alternatives.ts +81 -4
- package/src/market-data/quotes/metadata.ts +7 -0
- package/src/news/aggregator.ts +119 -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 +17 -0
- package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +362 -80
- package/src/plugins/builtin/alerts/command.ts +25 -1
- package/src/plugins/builtin/alerts/event-rows.ts +24 -3
- package/src/plugins/builtin/alerts/events-pane.tsx +34 -10
- package/src/plugins/builtin/alerts/fired-log.ts +73 -9
- package/src/plugins/builtin/alerts/format.ts +75 -5
- package/src/plugins/builtin/alerts/headless.ts +34 -12
- package/src/plugins/builtin/alerts/history-rows.ts +3 -2
- package/src/plugins/builtin/alerts/history.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +79 -16
- package/src/plugins/builtin/alerts/metric-command.ts +77 -4
- package/src/plugins/builtin/alerts/metric-data.ts +54 -13
- package/src/plugins/builtin/alerts/metric-model.ts +163 -14
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
- package/src/plugins/builtin/alerts/pane.tsx +61 -23
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +12 -8
- package/src/plugins/builtin/alerts/types.ts +4 -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/broker-manager/index.tsx +7 -3
- 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 +6 -2
- package/src/plugins/builtin/cds/client.ts +142 -30
- package/src/plugins/builtin/cds/headless.ts +35 -8
- package/src/plugins/builtin/cds/model.ts +145 -18
- package/src/plugins/builtin/cds/pane.tsx +16 -5
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
- package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
- package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
- package/src/plugins/builtin/chart-composer/headless.ts +6 -1
- package/src/plugins/builtin/chart-composer/index.tsx +5 -6
- package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
- package/src/plugins/builtin/chat/controller/index.ts +5 -1
- package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
- package/src/plugins/builtin/chat/controller/state.ts +25 -2
- package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
- package/src/plugins/builtin/chat/controller/utils.ts +7 -2
- package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
- package/src/plugins/builtin/chat/status-preview.ts +79 -0
- package/src/plugins/builtin/chat/status-widget.tsx +147 -10
- package/src/plugins/builtin/chat/unread-badge.ts +51 -10
- package/src/plugins/builtin/clinical-trials/client.ts +49 -4
- package/src/plugins/builtin/clinical-trials/model.ts +86 -31
- package/src/plugins/builtin/clinical-trials/pane.tsx +61 -49
- package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +95 -15
- package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- 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/headless.ts +2 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +12 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/company-kpis/headless.ts +8 -3
- package/src/plugins/builtin/company-kpis/model.ts +73 -5
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/composite-plugins.ts +2 -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 +10 -10
- 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/doe/headless.ts +3 -2
- package/src/plugins/builtin/doe/pane.tsx +13 -3
- 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/data.ts +18 -6
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +20 -5
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +12 -4
- package/src/plugins/builtin/econ/calendar-model.ts +48 -1
- package/src/plugins/builtin/econ/calendar-source.ts +4 -2
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/figures.ts +48 -0
- package/src/plugins/builtin/econ/index.tsx +14 -5
- package/src/plugins/builtin/econ/print-watch.ts +3 -1
- package/src/plugins/builtin/econ/types.ts +2 -0
- package/src/plugins/builtin/econ-statistics/client.ts +11 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +16 -6
- package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +39 -9
- 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 +68 -13
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- 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 +6 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
- package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
- package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -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 +62 -59
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
- package/src/plugins/builtin/help/table-cells.tsx +13 -2
- package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +22 -3
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/holders/report-rows.ts +4 -1
- package/src/plugins/builtin/insider/headless.ts +2 -2
- package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
- package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
- package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
- package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
- 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 +33 -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/headless.ts +12 -5
- package/src/plugins/builtin/macro-day/index.tsx +1 -1
- package/src/plugins/builtin/macro-day/model.ts +39 -5
- package/src/plugins/builtin/macro-day/pane.tsx +27 -19
- 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 +27 -7
- package/src/plugins/builtin/market-movers/headless.ts +34 -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 +22 -4
- 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 +2 -1
- package/src/plugins/builtin/mna/headless.ts +2 -0
- package/src/plugins/builtin/mna/pane.tsx +3 -1
- package/src/plugins/builtin/news/headless.ts +3 -1
- package/src/plugins/builtin/news/index.tsx +4 -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 +29 -5
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/table.ts +5 -2
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +17 -4
- package/src/plugins/builtin/options-positioning/client.ts +11 -0
- package/src/plugins/builtin/options-positioning/headless.ts +21 -8
- package/src/plugins/builtin/options-positioning/index.tsx +1 -1
- package/src/plugins/builtin/options-positioning/model.ts +78 -9
- package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
- 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/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +19 -22
- 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 +7 -5
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -7
- 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/index.tsx +0 -14
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +5 -2
- package/src/plugins/builtin/portfolio-list/table.tsx +15 -9
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/realized-vol/client.ts +13 -5
- package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
- package/src/plugins/builtin/realized-vol/headless.ts +8 -4
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/model.ts +37 -6
- package/src/plugins/builtin/realized-vol/pane.tsx +11 -9
- 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 +40 -12
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/index.tsx +7 -3
- package/src/plugins/builtin/research/relative-valuation-headless.ts +13 -26
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +59 -20
- package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
- package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
- 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/risk-factors/data.ts +4 -2
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/roadmap/index.tsx +28 -0
- package/src/plugins/builtin/roadmap/model.ts +67 -0
- package/src/plugins/builtin/roadmap/pane.tsx +244 -0
- 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 +16 -5
- package/src/plugins/builtin/scanner/live-headless.ts +48 -5
- package/src/plugins/builtin/scanner/live-model.ts +11 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
- 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 +4 -2
- package/src/plugins/builtin/sec/index.tsx +3 -0
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +16 -7
- 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/headless-market-data.ts +2 -2
- package/src/plugins/builtin/shared/job-posting.ts +1 -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 +11 -2
- package/src/plugins/builtin/shared/research-cells.tsx +1 -1
- package/src/plugins/builtin/shared/series-cache.ts +4 -4
- 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 +2 -1
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/model.ts +16 -7
- package/src/plugins/builtin/themes/pane.tsx +5 -5
- package/src/plugins/builtin/thirteenf/api.ts +12 -1
- package/src/plugins/builtin/thirteenf/data.ts +7 -7
- package/src/plugins/builtin/thirteenf/headless.ts +31 -4
- 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 +11 -3
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +14 -8
- 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-auctions/headless.ts +21 -12
- package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
- 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/client.ts +3 -13
- package/src/plugins/builtin/vol-surface/headless.ts +3 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +7 -4
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/builtin/yield-curve/client.ts +61 -1
- package/src/plugins/builtin/yield-curve/headless.ts +9 -6
- package/src/plugins/builtin/yield-curve/index.tsx +34 -10
- package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
- package/src/plugins/catalog-browser.ts +2 -1
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/host-link.ts +1 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +11 -7
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/remote/resources.ts +2 -1
- package/src/renderers/dom/data-table/row.tsx +26 -0
- package/src/renderers/dom/desktop/icons.tsx +7 -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 +3 -3
- package/src/renderers/electrobun/bun/desktop/backend.ts +21 -10
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +15 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
- package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
- package/src/renderers/electrobun/bun/index.ts +26 -2
- package/src/renderers/electrobun/bun/window/create.ts +43 -4
- package/src/renderers/electrobun/bun/window/events.ts +20 -0
- package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
- package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
- package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
- package/src/renderers/electrobun/shared/app-name.ts +13 -0
- package/src/renderers/electrobun/shared/protocol.ts +12 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +5 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/ui-host.tsx +8 -0
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- 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/fred-series.ts +3 -1
- package/src/sources/gloomberb-cloud/index.ts +43 -8
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/index.ts +8 -5
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/controller.ts +4 -3
- package/src/sync/core-contributors.ts +90 -31
- 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/test-support/preload.ts +14 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name-listing.ts +53 -13
- package/src/tickers/company-name.ts +50 -4
- package/src/tickers/index-roots.ts +7 -0
- package/src/tickers/listing-fate.ts +2 -2
- package/src/tickers/listing-venues.ts +15 -3
- package/src/tickers/search/index.ts +6 -2
- package/src/tickers/search/ranking.ts +5 -1
- package/src/tickers/search/upsert.ts +13 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/tickers/ticker-changes.ts +8 -4
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +1 -1
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/fundamentals.ts +44 -1
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/resolve-context.ts +3 -1
- package/src/time-series/resolve.ts +56 -4
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +3 -2
- package/src/time-series/studies.ts +8 -2
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +2 -3
- package/src/time-series/types.ts +22 -1
- package/src/types/company-from.ts +40 -0
- package/src/types/data-provider.ts +7 -0
- package/src/types/financials.ts +59 -1
- package/src/types/headless.ts +27 -3
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +9 -0
- package/src/ui/host.tsx +3 -0
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +165 -7
- package/src/utils/declared-dividends.ts +1 -1
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +60 -8
- 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/price-history.ts +7 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/version.ts +2 -2
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
} from "./client";
|
|
52
52
|
import {
|
|
53
53
|
ALL_EXPIRIES,
|
|
54
|
+
axisRatioOfDate,
|
|
54
55
|
chartWindow,
|
|
55
56
|
expiryAxis,
|
|
56
57
|
expiryLabel,
|
|
@@ -63,6 +64,7 @@ import {
|
|
|
63
64
|
formatDistance,
|
|
64
65
|
formatGamma,
|
|
65
66
|
formatLevel,
|
|
67
|
+
gammaTimesFigure,
|
|
66
68
|
positioningSpotDetail,
|
|
67
69
|
formatPayout,
|
|
68
70
|
formatRatio,
|
|
@@ -160,13 +162,18 @@ interface PositioningChartProps {
|
|
|
160
162
|
|
|
161
163
|
/** Columns across a numeric axis (strikes, or expiries one slot apart), with spot and the other levels as guide lines. */
|
|
162
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]);
|
|
163
170
|
const xAxis = useMemo(() => ({
|
|
164
171
|
ticks: props.ticks(Math.max(1, props.width - VALUE_GUTTER)),
|
|
165
172
|
markers: props.markers.flatMap((marker) => marker.ratio != null && marker.ratio >= 0 && marker.ratio <= 1
|
|
166
173
|
? [{ id: marker.id, xRatio: marker.ratio, label: marker.label, color: marker.color }] : []),
|
|
167
174
|
formatCursor: props.formatCursor,
|
|
168
175
|
}), [props.formatCursor, props.markers, props.ticks, props.width]);
|
|
169
|
-
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}
|
|
170
177
|
focused={false} navigable={false} showLegend showTimeAxis xAxis={xAxis}
|
|
171
178
|
formatValue={(value) => props.formatValue(value)} formatAxisValue={props.formatAxisValue}
|
|
172
179
|
cursorDate={props.selection.cursorDate} onCursorDateChange={props.selection.onCursorDateChange}
|
|
@@ -217,7 +224,8 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
217
224
|
const data = openInterest.data;
|
|
218
225
|
const spot = data?.spot ?? null;
|
|
219
226
|
const venue = data?.venue;
|
|
220
|
-
const spotStamp = useMemo(() => positioningSpotDetail(spot, data?.spotAsOf, venue
|
|
227
|
+
const spotStamp = useMemo(() => positioningSpotDetail(spot, data?.spotAsOf, venue, data?.spotBasis),
|
|
228
|
+
[spot, data?.spotAsOf, data?.spotBasis, venue]);
|
|
221
229
|
// A coin's options (Deribit's) count open interest in coins, to tenths.
|
|
222
230
|
const coin = isCoinContract(data?.contract);
|
|
223
231
|
const unit = coin ? ` ${data!.contract!.unit}` : "";
|
|
@@ -496,6 +504,8 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
496
504
|
const band = gammaData?.band;
|
|
497
505
|
const shares = band ? `${Math.round(band.shareLow * 100)}-${Math.round(band.shareHigh * 100)}%` : "";
|
|
498
506
|
const walls = useMemo(() => gammaWalls(gammaStrikes), [gammaStrikes]);
|
|
507
|
+
const gammaTimes = useMemo(() => gammaTimesFigure(gammaData?.gammaAsOf, gammaData?.quotesAsOf),
|
|
508
|
+
[gammaData?.gammaAsOf, gammaData?.quotesAsOf]);
|
|
499
509
|
const gammaFigures = useMemo<StatItem[]>(() => !gammaData?.total ? [] : [
|
|
500
510
|
{ id: "net", label: "Net GEX", value: formatGamma(gammaData.total.net), detail: "$ per 1% move",
|
|
501
511
|
color: gammaData.total.net >= 0 ? colors.positive : colors.negative },
|
|
@@ -507,8 +517,11 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
507
517
|
})),
|
|
508
518
|
...(band ? [{ id: "band", label: "Dealer range", value: `${formatGamma(band.low)} to ${formatGamma(band.high)}`,
|
|
509
519
|
detail: `${shares} with dealers${gammaData.boxes?.length ? ", boxes out" : ""}` }] : []),
|
|
510
|
-
{ id: "spot", label: "Spot", value: formatLevel(gammaSpot),
|
|
511
|
-
|
|
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]);
|
|
512
525
|
// The assumption rides in the legend, where the numbers it changes are read; the
|
|
513
526
|
// dealer range figure names its own share. It gives way before the series name does.
|
|
514
527
|
const assumption = width >= 60 ? "dealers long calls, short puts" : width >= 48 ? "long calls, short puts" : null;
|
|
@@ -580,7 +593,8 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
580
593
|
onHeaderClick={(id) => setExpirySort((current) => nextHeaderSort(current, id))}
|
|
581
594
|
emptyStateTitle="No listed expiries."
|
|
582
595
|
getExportMetadata={() => [["open interest as of", data?.oiDate ?? ""], ["change since", data?.previousOiDate ?? ""], ["spot", spot ?? ""],
|
|
583
|
-
...(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 ?? "" : ""]] : [])]}
|
|
584
598
|
rootBefore={<ChartTableHeader width={width} height={bodyHeight} figures={ladderFigures}
|
|
585
599
|
tableRows={expiryRows.length} tableChromeRows={chartTableChromeRows(expiryColumnList, width)}
|
|
586
600
|
chart={expiryChart ? {
|
|
@@ -619,7 +633,8 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
619
633
|
["range with boxes high", gammaData.bandWithBoxes.high],
|
|
620
634
|
["box legs left out", (gammaData.boxes ?? []).map((leg) => `${leg.expiry} ${leg.strike} x${leg.contracts}`).join("; ")]] : []),
|
|
621
635
|
["flip", gammaData.flip ?? ""], ["call wall", walls.callWall ?? ""], ["put wall", walls.putWall ?? ""],
|
|
622
|
-
["open interest as of", gammaData.oiDate ?? ""], ["
|
|
636
|
+
["open interest as of", gammaData.oiDate ?? ""], ["gamma at", gammaData.gammaAsOf ?? ""],
|
|
637
|
+
["vols from", gammaData.quotesAsOf ?? ""]]}
|
|
623
638
|
rootBefore={<ChartTableHeader width={width} height={bodyHeight} query={query} figures={gammaFigures}
|
|
624
639
|
tableRows={gammaRows.length} tableChromeRows={chartTableChromeRows(GAMMA_COLUMNS, width)}
|
|
625
640
|
chart={gammaChart ? {
|
|
@@ -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. */
|
|
@@ -8,6 +8,10 @@ import {
|
|
|
8
8
|
CSP_SCREEN_DEFAULTS, CSP_SCREEN_LIMIT, cspEarningsText, cspScreenCriteriaText, loadCspScreen, readCspScreenCriteria,
|
|
9
9
|
type CspScreen, type CspScreenRow,
|
|
10
10
|
} from "./csp-screen";
|
|
11
|
+
import {
|
|
12
|
+
loadStrategyScreen, readStrategyFamily, readStrategyScreenCriteria, STRATEGY_FAMILY_DEFS, strategyFamilyDef,
|
|
13
|
+
strategyRankedBy, strategyScreenCriteriaText, type ScreenFamily, type StrategyScreen, type StrategyScreenCriteria, type StrategyScreenRow,
|
|
14
|
+
} from "./strategy-screen";
|
|
11
15
|
|
|
12
16
|
const percentCell = (digits: number) => (value: unknown) => typeof value === "number" ? `${value.toFixed(digits)}%` : "--";
|
|
13
17
|
const priceCell = (value: unknown) => typeof value === "number" ? formatHedgePrice(value) : "--";
|
|
@@ -69,42 +73,146 @@ const listText = (items: readonly string[]) => items.length <= 1 ? items.join(""
|
|
|
69
73
|
* sessions).`), and a bare symbol, which has none to give, is just not ranked
|
|
70
74
|
* yet (`No IV rank for DAL yet.`).
|
|
71
75
|
*/
|
|
72
|
-
export function cspScreenNotes(
|
|
76
|
+
export function cspScreenNotes(
|
|
77
|
+
screen: { criteria: Pick<CspScreen["criteria"], "maxSpreadPercent">; missingRanks: string[] }, rankedBy = "annualised yield at the bid",
|
|
78
|
+
): string[] {
|
|
73
79
|
const reasoned = screen.missingRanks.filter((entry) => entry.includes(" "));
|
|
74
80
|
const bare = screen.missingRanks.filter((entry) => !entry.includes(" "));
|
|
75
81
|
return [
|
|
76
|
-
`Ranked by
|
|
82
|
+
`Ranked by ${rankedBy}. Wide is a spread over ${screen.criteria.maxSpreadPercent}% of the mid (--max-spread).`,
|
|
77
83
|
...(reasoned.length ? [`No IV rank: ${reasoned.join(", ")}.`] : []),
|
|
78
84
|
...(bare.length ? [`No IV rank for ${listText(bare)} yet.`] : []),
|
|
79
85
|
];
|
|
80
86
|
}
|
|
81
87
|
|
|
88
|
+
const money = (value: number) => value.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* One name's trade, flat: money (credit or debit, max gain and loss) for one
|
|
92
|
+
* contract a leg as OSA counts it, break-evens per share, every percent in
|
|
93
|
+
* percent, reward to risk as a multiple.
|
|
94
|
+
*/
|
|
95
|
+
export type StrategyScreenReportRow = {
|
|
96
|
+
rank: number | null; symbol: string; expiry: string | null; farExpiry: string | null; days: number | null; trade: string | null;
|
|
97
|
+
net: number | null; maxGain: number | null; maxLoss: number | null; unlimited: "gain" | "loss" | null;
|
|
98
|
+
breakEven: number | null; secondBreakEven: number | null; breakEvens: string; pop: number | null; returnToExpiry: number | null;
|
|
99
|
+
returnPerYear: number | null; rewardRisk: number | null; ivRank: number | null; spread: number | null; wide: boolean | null;
|
|
100
|
+
earnings: string; earningsBeforeExpiry: boolean | null; earningsDate: string | null; spot: number | null; note: string | null;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export function strategyScreenReportRow(row: StrategyScreenRow): StrategyScreenReportRow {
|
|
104
|
+
const { trade } = row;
|
|
105
|
+
return {
|
|
106
|
+
rank: row.rank, symbol: row.symbol, expiry: trade?.expiry ?? null, farExpiry: trade?.farExpiry ?? null, days: trade?.daysToExpiry ?? null,
|
|
107
|
+
// The screen keeps only trades whose mids open the way the family does, so the amount is the column's own credit or debit.
|
|
108
|
+
trade: trade?.text ?? null, net: trade ? Math.abs(trade.netAmount) : null, maxGain: trade?.maxGain ?? null, maxLoss: trade?.maxLoss ?? null,
|
|
109
|
+
unlimited: trade?.unlimited ?? null, breakEven: trade?.breakEvens[0] ?? null, secondBreakEven: trade?.breakEvens[1] ?? null,
|
|
110
|
+
breakEvens: trade?.breakEvens.length ? trade.breakEvens.map((value) => value.toFixed(2)).join(" / ") : "--",
|
|
111
|
+
pop: exportPercent(trade?.probabilityOfProfit), returnToExpiry: exportPercent(trade?.returnToExpiry),
|
|
112
|
+
returnPerYear: exportPercent(trade?.annualizedReturn), rewardRisk: trade?.rewardToRisk ?? null,
|
|
113
|
+
ivRank: row.ivRank, spread: exportPercent(trade?.quote?.spreadOfMid), wide: trade?.quote ? row.wideSpread : null,
|
|
114
|
+
earnings: cspEarningsText(trade), earningsBeforeExpiry: trade ? trade.earnings.beforeExpiry : null, earningsDate: trade?.earnings.date ?? null,
|
|
115
|
+
spot: trade?.spot ?? null, note: row.note,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** A gain or loss for one contract a leg, `unlimited` on the side that has no limit. */
|
|
120
|
+
const riskCell = (side: "gain" | "loss") => (value: unknown, row: Record<string, unknown>) =>
|
|
121
|
+
typeof value === "number" ? money(value) : row.unlimited === side ? "unlimited" : "--";
|
|
122
|
+
/** `2.68x`, or `unlimited` where the gain has no limit. */
|
|
123
|
+
export const rewardRiskText = (value: unknown, unlimited: unknown) =>
|
|
124
|
+
typeof value === "number" ? `${value.toFixed(2)}x` : unlimited === "gain" ? "unlimited" : "--";
|
|
125
|
+
|
|
126
|
+
/** The unit every money column of a trade is in, said once: OSA's own for position money. */
|
|
127
|
+
export const STRATEGY_MONEY_UNIT = "USD per contract";
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* A family's columns: its money column is `Credit` or `Debit`; an income trade
|
|
131
|
+
* reads its annualised return and a debit trade its reward to risk. A
|
|
132
|
+
* calendar's trade names the expiry it buys, which exports also give alone.
|
|
133
|
+
*/
|
|
134
|
+
function strategyScreenColumns(strategy: ScreenFamily): { text: HeadlessPaneColumn[]; exported: HeadlessPaneColumn[] } {
|
|
135
|
+
const def = strategyFamilyDef(strategy);
|
|
136
|
+
const lead: HeadlessPaneColumn[] = [COLUMNS[0]!, COLUMNS[1]!, COLUMNS[2]!];
|
|
137
|
+
const farExpiry: HeadlessPaneColumn[] = strategy === "calendar" ? [{ key: "farExpiry", header: "Bought expiry" }] : [];
|
|
138
|
+
const body: HeadlessPaneColumn[] = [
|
|
139
|
+
COLUMNS[3]!,
|
|
140
|
+
{ key: "trade", header: "Trade", format: (value) => typeof value === "string" ? value : "--" },
|
|
141
|
+
{ key: "net", header: def.net === "credit" ? "Credit" : "Debit", align: "right", format: (value) => typeof value === "number" ? money(value) : "--" },
|
|
142
|
+
{ key: "maxGain", header: "Max gain", align: "right", format: riskCell("gain") },
|
|
143
|
+
{ key: "maxLoss", header: "Max loss", align: "right", format: riskCell("loss") },
|
|
144
|
+
];
|
|
145
|
+
const measure: HeadlessPaneColumn = def.net === "credit"
|
|
146
|
+
? { key: "returnPerYear", header: "Return/yr", align: "right", format: percentCell(1) }
|
|
147
|
+
: { key: "rewardRisk", header: "Reward/risk", align: "right", format: (value, row) => rewardRiskText(value, row.unlimited) };
|
|
148
|
+
const odds: HeadlessPaneColumn[] = [{ key: "pop", header: "PoP", align: "right", format: percentCell(0) }, measure, COLUMNS[12]!, COLUMNS[7]!, COLUMNS[13]!];
|
|
149
|
+
const breakEven: HeadlessPaneColumn = { key: "breakEven", header: "Break-even", align: "right", format: priceCell,
|
|
150
|
+
textFormat: (_value, row) => String(row.breakEvens) };
|
|
151
|
+
const toExpiry: HeadlessPaneColumn[] = def.net === "credit"
|
|
152
|
+
? [{ key: "returnToExpiry", header: "Return to expiry", align: "right", format: percentCell(2) }] : [];
|
|
153
|
+
return {
|
|
154
|
+
text: [...lead, ...body, breakEven, ...odds, NOTE],
|
|
155
|
+
exported: [...lead, ...farExpiry, ...body, breakEven, { key: "secondBreakEven", header: "Second break-even", format: priceCell }, ...odds,
|
|
156
|
+
...toExpiry, ...EXPORT_ONLY, NOTE],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function strategyMetadata(screen: StrategyScreen) {
|
|
161
|
+
return { strategy: screen.criteria.strategy, criteria: screen.criteria, rankedBy: strategyRankedBy(screen.criteria.strategy),
|
|
162
|
+
unit: `credit, debit, max gain and max loss in ${STRATEGY_MONEY_UNIT} (one contract a leg, 100 shares); break-evens per share; percent; reward/risk as a multiple`,
|
|
163
|
+
methodology: "docs/research-data.md#options-strategy-screen" };
|
|
164
|
+
}
|
|
165
|
+
|
|
82
166
|
export const cspScreenHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
83
167
|
shape: "rows",
|
|
84
168
|
argument: { kind: "tickers", maximum: CSP_SCREEN_LIMIT, description: "US option underlyings to rank" },
|
|
85
169
|
usListingsOnly: "Options data",
|
|
86
170
|
failsWithoutData: true,
|
|
87
|
-
description: "
|
|
88
|
-
+ "(0.30) on the listed expiry nearest a date or a number of days out (30), with its strike, bid, ask,
|
|
89
|
-
+ "--max-spread, never left out), delta, distance out of the money, yield at the mid and at the bid, IV rank and whether
|
|
171
|
+
description: "Option strategies across a list, one family at a time (--strategy): cash-secured puts by default, ranked by annualised yield on "
|
|
172
|
+
+ "cash at the bid, the put nearest a delta (0.30) on the listed expiry nearest a date or a number of days out (30), with its strike, bid, ask, "
|
|
173
|
+
+ "spread (flagged wide over --max-spread, never left out), delta, distance out of the money, yield at the mid and at the bid, IV rank and whether "
|
|
174
|
+
+ "earnings fall before expiry. Covered calls, verticals, iron condors, straddles, strangles and calendars pick their legs by delta on the same "
|
|
175
|
+
+ "expiry and show the trade, credit or debit, max gain and loss per contract, break-evens, probability of profit, and an income trade's "
|
|
176
|
+
+ "annualised return or a debit trade's reward to risk.",
|
|
90
177
|
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."
|
|
178
|
+
limitations: ["Assignment, margin and the interest the cash earns are not modelled.",
|
|
179
|
+
"Probability of profit is the Black-Scholes chance at each break-even's nearest leg IV, not a forecast."] },
|
|
92
180
|
describe: (_args, result) => {
|
|
93
|
-
const
|
|
94
|
-
|
|
181
|
+
const metadata = result?.metadata as { criteria?: CspScreen["criteria"] | StrategyScreenCriteria; strategy?: ScreenFamily } | undefined;
|
|
182
|
+
const criteria = metadata?.criteria;
|
|
183
|
+
if (metadata?.strategy && criteria) {
|
|
184
|
+
return [strategyFamilyDef(metadata.strategy).title, strategyScreenCriteriaText(criteria as StrategyScreenCriteria, Date.now()), STRATEGY_MONEY_UNIT]
|
|
185
|
+
.join(" · ");
|
|
186
|
+
}
|
|
187
|
+
return ["Cash-secured puts", ...(criteria ? [cspScreenCriteriaText(criteria as CspScreen["criteria"], Date.now())] : [])].join(" · ");
|
|
95
188
|
},
|
|
96
189
|
options: [
|
|
190
|
+
{ key: "strategy", type: "enum", example: "--strategy iron-condor",
|
|
191
|
+
values: STRATEGY_FAMILY_DEFS.map((def) => ({ value: def.id, ...(def.aliases.length ? { aliases: def.aliases } : {}) })),
|
|
192
|
+
description: "The strategy each name trades, a cash-secured put (csp) when not given; also cc, put-credit, call-credit, call-debit, "
|
|
193
|
+
+ "put-debit, ic and call-calendar" },
|
|
97
194
|
{ 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)}`
|
|
195
|
+
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)}. `
|
|
196
|
+
+ "Other strategies pick their legs the same way: a covered call's call, a vertical's leg nearer the money, a condor's or a strangle's put and call, "
|
|
197
|
+
+ "a calendar's call, each strategy at its own default (0.30; 0.50 for debit verticals and calendars; 0.20 for condors and short strangles)" },
|
|
198
|
+
{ key: "wingDelta", aliases: ["wing-delta"], type: "string", placeholder: "0.15", example: "--strategy iron-condor --wing-delta 0.05",
|
|
199
|
+
description: "A vertical's or an iron condor's legs further out of the money are the ones whose delta is nearest this: the legs a credit spread "
|
|
200
|
+
+ "buys and a debit spread sells. Default 0.15 (0.30 for debit verticals, 0.10 for condors), or half --delta when that is not below it" },
|
|
99
201
|
{ key: "days", type: "string", placeholder: "days", example: "--days 45",
|
|
100
202
|
description: `Each name's expiry is its listed one nearest this many calendar days out. Default ${CSP_SCREEN_DEFAULTS.days}` },
|
|
101
203
|
{ key: "expiration", type: "string", placeholder: OPTION_EXPIRATION_PLACEHOLDER, example: "--expiration 2026-11-20",
|
|
102
204
|
description: `Each name's expiry is its listed one nearest this date, in place of --days, as ${OPTION_EXPIRATION_FORMAT}`,
|
|
103
205
|
normalize: (value) => expirationOptionSeconds(value) },
|
|
104
206
|
{ key: "maxSpread", aliases: ["max-spread"], type: "string", placeholder: "percent", example: "--max-spread 15",
|
|
105
|
-
description:
|
|
207
|
+
description: "A put whose bid/ask spread is over this percent of its mid is marked wide; it is still ranked. A strategy of several legs adds "
|
|
208
|
+
+ `their spreads up against its net mid. Default ${CSP_SCREEN_DEFAULTS.maxSpreadPercent}` },
|
|
106
209
|
],
|
|
107
210
|
async load(args, ctx) {
|
|
211
|
+
const strategy = readStrategyFamily(args.options.strategy);
|
|
212
|
+
if (strategy !== "csp") return loadStrategyReport(strategy, args, ctx);
|
|
213
|
+
if (args.options.wingDelta != null && args.options.wingDelta !== "") {
|
|
214
|
+
throw new Error("--wing-delta sets the far legs of a vertical or an iron condor; a cash-secured put has none");
|
|
215
|
+
}
|
|
108
216
|
const criteria = readCspScreenCriteria(args.options);
|
|
109
217
|
const instruments = await Promise.all(args.symbols.map((symbol) => resolveHeadlessInstrument(ctx, symbol)));
|
|
110
218
|
const screen = await loadCspScreen(instruments, criteria, {
|
|
@@ -125,3 +233,27 @@ export const cspScreenHeadless: HeadlessPaneDefinition<"rows"> = {
|
|
|
125
233
|
};
|
|
126
234
|
},
|
|
127
235
|
};
|
|
236
|
+
|
|
237
|
+
/** `fn CSP --strategy <family>`: each name's trade of the family, ranked, with its own columns. */
|
|
238
|
+
async function loadStrategyReport(
|
|
239
|
+
strategy: ScreenFamily, args: Parameters<HeadlessPaneDefinition["load"]>[0], ctx: Parameters<HeadlessPaneDefinition["load"]>[1],
|
|
240
|
+
) {
|
|
241
|
+
const criteria = readStrategyScreenCriteria(strategy, args.options);
|
|
242
|
+
const instruments = await Promise.all(args.symbols.map((symbol) => resolveHeadlessInstrument(ctx, symbol)));
|
|
243
|
+
const screen = await loadStrategyScreen(instruments, criteria, {
|
|
244
|
+
scenario: createScenarioDependencies(ctx.marketData, ctx.apiClient), earnings: ctx.marketData, ivApi: ctx.apiClient,
|
|
245
|
+
}, ctx.signal);
|
|
246
|
+
const unavailable = screen.rows.filter((row) => !row.trade);
|
|
247
|
+
const columns = strategyScreenColumns(strategy);
|
|
248
|
+
return {
|
|
249
|
+
freshness: screen.freshness,
|
|
250
|
+
columns: columns.exported,
|
|
251
|
+
textColumns: columns.text,
|
|
252
|
+
rows: screen.rows.map(strategyScreenReportRow),
|
|
253
|
+
complete: unavailable.length === 0 && !screen.ivRankError,
|
|
254
|
+
unavailableSymbols: unavailable.map((row) => row.symbol),
|
|
255
|
+
errors: screen.ivRankError ? [`IV rank unavailable: ${screen.ivRankError}`] : [],
|
|
256
|
+
notes: cspScreenNotes(screen, strategyRankedBy(strategy)),
|
|
257
|
+
metadata: strategyMetadata(screen),
|
|
258
|
+
};
|
|
259
|
+
}
|