@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
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
DataTableView, PaneStatusBody, QueryBar, usePaneFooter, usePaneNoticeFooter, usePaneTabs, type DataTableCell, type DataTableColumn, type DataTableKeyEvent,
|
|
4
|
+
type QueryBarFilter,
|
|
5
|
+
} from "../../../components";
|
|
6
|
+
import { dropColumnsToFit } from "../../../components/ui/table-layout";
|
|
3
7
|
import { useAsyncResource } from "../../../react/async-resource";
|
|
4
8
|
import { useAutoRefresh } from "../../../react/auto-refresh";
|
|
5
9
|
import { usePaneSettingValue, usePluginAppActions } from "../../../public/react";
|
|
@@ -10,11 +14,19 @@ import { parsePublicTickerKey } from "../../../utils/exchanges";
|
|
|
10
14
|
import { compareSortValues, nextHeaderSort, type SortDirection } from "../../../utils/sort-values";
|
|
11
15
|
import { useAssetData } from "../../runtime";
|
|
12
16
|
import { createScenarioDependencies } from "./client";
|
|
13
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
cspScreenNotes, cspScreenReportRow, rewardRiskText, STRATEGY_MONEY_UNIT, strategyScreenReportRow, type CspScreenReportRow,
|
|
19
|
+
type StrategyScreenReportRow,
|
|
20
|
+
} from "./csp-screen-headless";
|
|
14
21
|
import { CSP_SCREEN_DEFAULTS, CSP_SCREEN_LIMIT, cspScreenCriteriaText, cspTargetDate, loadCspScreen, readCspScreenCriteria, type CspScreenRow } from "./csp-screen";
|
|
15
22
|
import { formatHedgePrice } from "./hedge";
|
|
23
|
+
import {
|
|
24
|
+
loadStrategyScreen, readStrategyFamily, readStrategyScreenCriteria, STRATEGY_TABS, strategyFamilyDef, strategyRankedBy,
|
|
25
|
+
strategyScreenCriteriaText, tabFamilies, type ScreenFamily, type StrategyFamily, type StrategyScreenRow, type StrategyTab,
|
|
26
|
+
} from "./strategy-screen";
|
|
16
27
|
|
|
17
28
|
type ScreenRow = CspScreenReportRow & { source: CspScreenRow };
|
|
29
|
+
type TradeRow = StrategyScreenReportRow & { source: StrategyScreenRow };
|
|
18
30
|
|
|
19
31
|
const COLUMNS: DataTableColumn[] = [
|
|
20
32
|
{ id: "rank", label: "#", width: 3, align: "right" },
|
|
@@ -33,7 +45,50 @@ const COLUMNS: DataTableColumn[] = [
|
|
|
33
45
|
{ id: "earnings", label: "Earnings", width: 17, align: "left" },
|
|
34
46
|
];
|
|
35
47
|
|
|
48
|
+
/** At a narrow width the put screen keeps the put, its yield and its risks; its expiry and the mid's yield give way first. */
|
|
49
|
+
const PUT_DROP_ORDER = ["midYield", "expiry", "otm", "ask", "bid", "rank", "delta"];
|
|
50
|
+
|
|
51
|
+
/** The Trade column fits the longest trade listed, so a condor's bought wings show beside its sold legs. */
|
|
52
|
+
const TRADE_WIDTH = { min: 14, max: 40 };
|
|
53
|
+
const tradeWidth = (rows: readonly { trade: string | null }[]) =>
|
|
54
|
+
Math.min(TRADE_WIDTH.max, Math.max(TRADE_WIDTH.min, ...rows.map((row) => row.trade?.length ?? 0)));
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* A family's columns. DTE dates the trade (a calendar's bought expiry is in
|
|
58
|
+
* its words). Money is per contract, said once in the bar. An income trade
|
|
59
|
+
* reads its annualised return, a debit trade its reward to risk.
|
|
60
|
+
*/
|
|
61
|
+
const tradeColumns = (strategy: ScreenFamily, tradeColumnWidth: number): DataTableColumn[] => {
|
|
62
|
+
const credit = strategyFamilyDef(strategy).net === "credit";
|
|
63
|
+
return [
|
|
64
|
+
{ id: "rank", label: "#", width: 3, align: "right" },
|
|
65
|
+
{ id: "symbol", label: "Symbol", width: 7, align: "left" },
|
|
66
|
+
{ id: "days", label: "DTE", width: 4, align: "right" },
|
|
67
|
+
{ id: "trade", label: "Trade", width: tradeColumnWidth, align: "left", flexGrow: 1 },
|
|
68
|
+
{ id: "net", label: credit ? "Credit" : "Debit", width: 8, align: "right", title: STRATEGY_MONEY_UNIT },
|
|
69
|
+
{ id: "maxGain", label: "Max gain", width: 9, align: "right", title: STRATEGY_MONEY_UNIT },
|
|
70
|
+
{ id: "maxLoss", label: "Max loss", width: 9, align: "right", title: STRATEGY_MONEY_UNIT },
|
|
71
|
+
{ id: "breakEven", label: "Break-even", width: 15, align: "right" },
|
|
72
|
+
{ id: "pop", label: "PoP", width: 4, align: "right" },
|
|
73
|
+
credit ? { id: "returnPerYear", label: "Return/yr", width: 9, align: "right" }
|
|
74
|
+
: { id: "rewardRisk", label: "Reward/risk", width: 9, align: "right", title: "Max gain over max loss" },
|
|
75
|
+
{ id: "ivRank", label: "IVR", width: 4, align: "right" },
|
|
76
|
+
{ id: "spread", label: "Spread", width: 11, align: "right" },
|
|
77
|
+
{ id: "earnings", label: "Earnings", width: 13, align: "left" },
|
|
78
|
+
];
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* What a narrow pane gives up first: the max gain or loss that repeats the
|
|
82
|
+
* credit or debit, then the break-evens, the rank, the other max, the
|
|
83
|
+
* earnings flag and the spread. Symbol, DTE, trade, credit or debit, PoP,
|
|
84
|
+
* the return and IV rank stay at every width.
|
|
85
|
+
*/
|
|
86
|
+
const tradeDropOrder = (strategy: ScreenFamily) => strategyFamilyDef(strategy).net === "credit"
|
|
87
|
+
? ["maxGain", "breakEven", "rank", "maxLoss", "earnings", "spread"]
|
|
88
|
+
: ["maxLoss", "breakEven", "rank", "maxGain", "earnings", "spread"];
|
|
89
|
+
|
|
36
90
|
const DELTA_OPTIONS = ["0.10", "0.15", "0.20", "0.25", "0.30", "0.35", "0.40", "0.50"];
|
|
91
|
+
const WING_OPTIONS = ["0.05", "0.10", "0.15", "0.20", "0.25", "0.30", "0.35", "0.40"];
|
|
37
92
|
const DAYS_OPTIONS = ["7", "14", "21", "30", "45", "60", "90"];
|
|
38
93
|
const SYMBOLS_FIELD_WIDTH = 24;
|
|
39
94
|
/** Symbols apply once typing pauses, so a half-typed ticker does not start a screen. */
|
|
@@ -42,79 +97,128 @@ const SYMBOLS_DEBOUNCE_MS = 700;
|
|
|
42
97
|
const symbolList = (text: string) => [...new Set(text.split(/[\s,;]+/).map((symbol) => symbol.trim().toUpperCase()).filter(Boolean))];
|
|
43
98
|
const percentText = (value: unknown, digits: number) => typeof value === "number" ? `${value.toFixed(digits)}%` : "--";
|
|
44
99
|
const priceText = (value: unknown) => typeof value === "number" ? formatHedgePrice(value) : "--";
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
100
|
+
const moneyText = (value: number) => value.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
101
|
+
const riskText = (value: number | null, unlimited: boolean) => value != null ? moneyText(value) : unlimited ? "unlimited" : "--";
|
|
102
|
+
const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
103
|
+
/** `before Oct 28`: the flag and the date without its year, which the next report never needs. */
|
|
104
|
+
const earningsCell = (row: { earningsDate: string | null; earningsBeforeExpiry: boolean | null; earnings: string }) => {
|
|
105
|
+
const date = row.earningsDate;
|
|
106
|
+
if (!date || row.earningsBeforeExpiry == null) return row.earnings;
|
|
107
|
+
return `${row.earningsBeforeExpiry ? "before" : "after"} ${MONTHS[Number(date.slice(5, 7)) - 1]} ${Number(date.slice(8, 10))}`;
|
|
108
|
+
};
|
|
109
|
+
/** A column's value to sort on: earnings by date, a flag as a number, an unlimited gain or loss above every limit. */
|
|
110
|
+
function sortValue(row: Record<string, unknown>, columnId: string): string | number | null {
|
|
111
|
+
if (columnId === "earnings") return typeof row.earningsDate === "string" ? row.earningsDate : null;
|
|
112
|
+
if ((columnId === "maxGain" || columnId === "rewardRisk") && row.unlimited === "gain") return Number.POSITIVE_INFINITY;
|
|
113
|
+
if (columnId === "maxLoss" && row.unlimited === "loss") return Number.POSITIVE_INFINITY;
|
|
114
|
+
const value = row[columnId];
|
|
115
|
+
return typeof value === "boolean" ? Number(value) : typeof value === "number" || typeof value === "string" ? value : null;
|
|
50
116
|
}
|
|
51
117
|
/** A select's options with the current value kept when it is not a preset (a delta typed on the command line). */
|
|
52
118
|
const withCurrent = (presets: readonly string[], current: string, label: (value: string) => string) =>
|
|
53
119
|
[...new Set([...presets, current])].sort((a, b) => Number(a) - Number(b)).map((value) => ({ value, label: label(value) }));
|
|
54
120
|
|
|
121
|
+
/** OSA's typed legs for a trade (`call,105,2026-11-20,-1,1.64,28.1`), each at its entry price and IV. */
|
|
122
|
+
const osaLegs = (row: StrategyScreenRow) => row.trade!.position.legs.map((leg) => [leg.side, leg.strike, new Date(leg.expiration * 1000).toISOString().slice(0, 10),
|
|
123
|
+
leg.quantity, Number(leg.price.toFixed(6)), Number((leg.volatility * 100).toFixed(4)), leg.multiplier].join(",")).join(";");
|
|
124
|
+
|
|
55
125
|
export function CspScreenPane({ width, height, focused }: PaneProps) {
|
|
56
126
|
const colors = useThemeColors();
|
|
57
127
|
const [symbolsText, setSymbolsText] = usePaneSettingValue<string>("symbols", "");
|
|
128
|
+
const [strategySetting, setStrategySetting] = usePaneSettingValue<string>("strategy", "");
|
|
58
129
|
const [delta, setDelta] = usePaneSettingValue<string | number>("delta", "");
|
|
130
|
+
const [wingDelta, setWingDelta] = usePaneSettingValue<string | number>("wingDelta", "");
|
|
59
131
|
const [days, setDays] = usePaneSettingValue<string | number>("days", "");
|
|
60
132
|
const [expiration, setExpiration] = usePaneSettingValue<string | number>("expiration", "");
|
|
61
133
|
const [maxSpread] = usePaneSettingValue<string | number>("maxSpread", "");
|
|
62
134
|
const [symbolsActive, setSymbolsActive] = useState(false);
|
|
63
135
|
const { createPaneFromTemplate } = usePluginAppActions();
|
|
64
136
|
const assetData = useAssetData();
|
|
137
|
+
const strategy = useMemo((): StrategyFamily => {
|
|
138
|
+
try { return readStrategyFamily(strategySetting); } catch { return "csp"; }
|
|
139
|
+
}, [strategySetting]);
|
|
140
|
+
const family = strategyFamilyDef(strategy);
|
|
141
|
+
// Each strategy opens at its own deltas; the names and the expiry carry over.
|
|
142
|
+
const chooseStrategy = (next: StrategyFamily) => {
|
|
143
|
+
if (next === strategy) return;
|
|
144
|
+
setStrategySetting(next === "csp" ? "" : next);
|
|
145
|
+
setDelta("");
|
|
146
|
+
setWingDelta("");
|
|
147
|
+
};
|
|
148
|
+
const { strip: tabStrip, rows: tabRows } = usePaneTabs({ tabs: STRATEGY_TABS.map((tab) => ({ ...tab })), activeValue: family.tab,
|
|
149
|
+
onSelect: (tab) => chooseStrategy(tabFamilies(tab as StrategyTab)[0]!.id), focused, dense: true });
|
|
65
150
|
const typed = useMemo(() => symbolList(String(symbolsText ?? "")), [symbolsText]);
|
|
66
151
|
// The screen reads the first names it can rank; the footer names the rest.
|
|
67
152
|
const symbols = useMemo(() => typed.slice(0, CSP_SCREEN_LIMIT), [typed]);
|
|
68
153
|
const leftOut = typed.slice(CSP_SCREEN_LIMIT);
|
|
69
154
|
const criteria = useMemo(() => {
|
|
70
|
-
try {
|
|
71
|
-
|
|
72
|
-
|
|
155
|
+
try {
|
|
156
|
+
const options = { delta, wingDelta, days, expiration, maxSpread };
|
|
157
|
+
if (strategy === "csp") return { value: readCspScreenCriteria(options), trade: null, error: null };
|
|
158
|
+
return { value: null, trade: readStrategyScreenCriteria(strategy, options), error: null };
|
|
159
|
+
} catch (error) { return { value: null, trade: null, error: error instanceof Error ? error.message : String(error) }; }
|
|
160
|
+
}, [strategy, delta, wingDelta, days, expiration, maxSpread]);
|
|
161
|
+
const ready = criteria.value ?? criteria.trade;
|
|
73
162
|
const controller = useRef<AbortController | null>(null);
|
|
74
|
-
const key = JSON.stringify([symbols,
|
|
163
|
+
const key = JSON.stringify([symbols, strategy, ready]);
|
|
75
164
|
const loader = useCallback(async () => {
|
|
76
165
|
controller.current?.abort();
|
|
77
166
|
const current = new AbortController();
|
|
78
167
|
controller.current = current;
|
|
79
|
-
|
|
80
|
-
|
|
168
|
+
const instruments = symbols.map((symbol) => parsePublicTickerKey(symbol));
|
|
169
|
+
const dependencies = { scenario: createScenarioDependencies(), earnings: assetData };
|
|
170
|
+
if (criteria.trade) return { kind: "trade" as const, screen: await loadStrategyScreen(instruments, criteria.trade, dependencies, current.signal) };
|
|
171
|
+
return { kind: "csp" as const, screen: await loadCspScreen(instruments, criteria.value!, dependencies, current.signal) };
|
|
81
172
|
}, [key, assetData]);
|
|
82
|
-
const resource = useAsyncResource(symbols.length &&
|
|
173
|
+
const resource = useAsyncResource(symbols.length && ready ? loader : null);
|
|
83
174
|
useEffect(() => () => controller.current?.abort(), [loader]);
|
|
84
175
|
useAutoRefresh(resource.updatedAt, resource.load);
|
|
85
176
|
const [sort, setSort] = useState<{ columnId: string; direction: SortDirection }>({ columnId: "rank", direction: "asc" });
|
|
86
|
-
|
|
87
|
-
|
|
177
|
+
// A strategy's answer shows only under its own tab: the last one stays until the next has loaded only for the same strategy.
|
|
178
|
+
const data = resource.data && (resource.data.kind === "csp") === (strategy === "csp")
|
|
179
|
+
&& (resource.data.kind === "csp" || resource.data.screen.criteria.strategy === strategy) ? resource.data : null;
|
|
180
|
+
const order = useCallback(<Row extends { rank: number | null } & Record<string, unknown>>(flat: Row[]) => flat.sort((left, right) => sort.columnId === "rank"
|
|
88
181
|
// Unranked names stay under the ranked ones whichever way rank sorts.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
182
|
+
? compareSortValues(left.rank ?? Number.POSITIVE_INFINITY, right.rank ?? Number.POSITIVE_INFINITY, sort.direction)
|
|
183
|
+
: compareSortValues(sortValue(left, sort.columnId), sortValue(right, sort.columnId), sort.direction)), [sort]);
|
|
184
|
+
const putRows = useMemo<ScreenRow[]>(() => data?.kind !== "csp" ? []
|
|
185
|
+
: order(data.screen.rows.map((row) => ({ ...cspScreenReportRow(row), source: row }))), [data, order]);
|
|
186
|
+
const tradeRows = useMemo<TradeRow[]>(() => data?.kind !== "trade" ? []
|
|
187
|
+
: order(data.screen.rows.map((row) => ({ ...strategyScreenReportRow(row), source: row }))), [data, order]);
|
|
93
188
|
const [selected, setSelected] = useState<string | null>(null);
|
|
94
|
-
const screen =
|
|
95
|
-
const
|
|
189
|
+
const screen = data?.screen;
|
|
190
|
+
const rankedBy = strategy === "csp" ? undefined : strategyRankedBy(strategy);
|
|
191
|
+
const notices = [criteria.error, data ? null : resource.error,
|
|
96
192
|
leftOut.length ? `Ranks the first ${CSP_SCREEN_LIMIT} symbols; ${leftOut.length} left out: ${leftOut.join(", ")}` : null,
|
|
97
193
|
...(screen?.ivRankError ? [`IV rank unavailable: ${screen.ivRankError}`] : []),
|
|
98
194
|
...(screen?.rows ?? []).flatMap((row) => row.note ? [`${row.symbol}: ${row.note}`] : []),
|
|
99
|
-
...(screen ? cspScreenNotes(screen).slice(1) : [])].filter((value): value is string => !!value);
|
|
195
|
+
...(screen ? cspScreenNotes(screen, rankedBy).slice(1) : [])].filter((value): value is string => !!value);
|
|
100
196
|
usePaneNoticeFooter({ registrationId: "csp-screen-notices", notices, focused });
|
|
101
197
|
// The bar names the delta and the expiry; the footer says only what changes.
|
|
102
198
|
usePaneFooter("csp-screen", () => ({ info: [
|
|
103
199
|
...(resource.loading ? [{ id: "loading", parts: [{ text: "loading", tone: "muted" as const }] }] : []),
|
|
104
200
|
] }), [resource.loading]);
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
|
|
201
|
+
const now = Date.now();
|
|
202
|
+
const criteriaText = criteria.value ? cspScreenCriteriaText(criteria.value, now) : criteria.trade ? strategyScreenCriteriaText(criteria.trade, now) : "";
|
|
203
|
+
// Enter opens the name's trade in OSA: the put at the strike and expiry the screen read, any other trade as its legs.
|
|
204
|
+
const openScenario = (row: ScreenRow | TradeRow) => {
|
|
205
|
+
if ("csp" in row.source) {
|
|
206
|
+
const csp = row.source.csp;
|
|
207
|
+
if (!csp) return;
|
|
208
|
+
createPaneFromTemplate("options-scenario-pane", { symbol: row.symbol, values: { strategy: "csp", strike: String(csp.strike), expiration: csp.expiry } });
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const trade = row.source.trade;
|
|
212
|
+
if (!trade) return;
|
|
213
|
+
createPaneFromTemplate("options-scenario-pane", { symbol: row.symbol, values: { legs: osaLegs(row.source),
|
|
214
|
+
...(trade.position.underlying ? { shares: String(trade.position.underlying.quantity) } : {}) } });
|
|
111
215
|
};
|
|
112
216
|
const handleKey = (event: DataTableKeyEvent): boolean => {
|
|
113
217
|
if (event.ctrl || event.alt || event.meta || event.name !== "r") return false;
|
|
114
218
|
void resource.reload();
|
|
115
219
|
event.preventDefault?.(); event.stopPropagation?.(); return true;
|
|
116
220
|
};
|
|
117
|
-
const
|
|
221
|
+
const putCell = (row: ScreenRow, id: string): { text: string; color?: string } => {
|
|
118
222
|
switch (id) {
|
|
119
223
|
case "rank": return { text: row.rank == null ? "" : String(row.rank), color: colors.textDim };
|
|
120
224
|
case "symbol": return { text: row.symbol, color: colors.textBright };
|
|
@@ -133,33 +237,80 @@ export function CspScreenPane({ width, height, focused }: PaneProps) {
|
|
|
133
237
|
default: return { text: "" };
|
|
134
238
|
}
|
|
135
239
|
};
|
|
136
|
-
const
|
|
137
|
-
|
|
240
|
+
const tradeCell = (row: TradeRow, id: string): DataTableCell => {
|
|
241
|
+
switch (id) {
|
|
242
|
+
case "rank": return { text: row.rank == null ? "" : String(row.rank), color: colors.textDim };
|
|
243
|
+
case "symbol": return { text: row.symbol, color: colors.textBright };
|
|
244
|
+
case "days": return { text: row.days == null ? "--" : String(row.days) };
|
|
245
|
+
case "trade": return { text: row.trade ?? "--" };
|
|
246
|
+
case "net": return { text: row.net == null ? "--" : moneyText(row.net) };
|
|
247
|
+
case "maxGain": return { text: riskText(row.maxGain, row.unlimited === "gain") };
|
|
248
|
+
case "maxLoss": return { text: riskText(row.maxLoss, row.unlimited === "loss") };
|
|
249
|
+
case "breakEven": return { text: row.breakEvens };
|
|
250
|
+
case "pop": return { text: percentText(row.pop, 0) };
|
|
251
|
+
// A return above zero reads as a gain; none, or nothing to rank, stays neutral.
|
|
252
|
+
case "returnPerYear": return { text: percentText(row.returnPerYear, 1), color: (row.returnPerYear ?? 0) > 0 ? colors.positive : undefined };
|
|
253
|
+
case "rewardRisk": return { text: rewardRiskText(row.rewardRisk, row.unlimited), value: row.rewardRisk };
|
|
254
|
+
case "ivRank": return { text: typeof row.ivRank === "number" ? row.ivRank.toFixed(0) : "--" };
|
|
255
|
+
case "spread": return { text: `${percentText(row.spread, 1)}${row.wide ? " wide" : ""}`, color: row.wide ? colors.warning : undefined };
|
|
256
|
+
case "earnings": return { text: earningsCell(row), value: row.earningsDate,
|
|
257
|
+
color: row.earningsBeforeExpiry === true ? colors.warning : colors.textDim };
|
|
258
|
+
default: return { text: "" };
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
const familyDelta = family.delta ?? CSP_SCREEN_DEFAULTS.delta;
|
|
262
|
+
const deltaValue = (criteria.value ?? criteria.trade)?.delta?.toFixed(2) ?? familyDelta.toFixed(2);
|
|
263
|
+
const wingValue = criteria.trade?.wingDelta?.toFixed(2) ?? (family.wing != null ? family.wing.toFixed(2) : "");
|
|
264
|
+
const defaultWing = family.wing == null ? "" : (family.wing < Number(deltaValue) ? family.wing : Number(deltaValue) / 2).toFixed(2);
|
|
265
|
+
const daysValue = String(ready?.days ?? CSP_SCREEN_DEFAULTS.days);
|
|
138
266
|
// A date typed on the command line is the expiry's choice until a number of days is picked.
|
|
139
|
-
const typedDate =
|
|
267
|
+
const typedDate = ready?.expiration != null ? cspTargetDate(ready, now) : null;
|
|
268
|
+
const choices = tabFamilies(family.tab);
|
|
269
|
+
const filters: QueryBarFilter[] = [
|
|
270
|
+
{ id: "symbols", kind: "text" as const, label: "Symbols", value: String(symbolsText ?? ""), placeholder: "JPM, GS, KO",
|
|
271
|
+
width: SYMBOLS_FIELD_WIDTH, debounceMs: SYMBOLS_DEBOUNCE_MS, focused, active: symbolsActive, onActiveChange: setSymbolsActive,
|
|
272
|
+
onChange: setSymbolsText },
|
|
273
|
+
...(choices.length > 1 ? [{ id: "variant", label: family.tab === "vertical" ? "Spread" : "Side", value: strategy,
|
|
274
|
+
options: choices.map((choice) => ({ value: choice.id, label: choice.choice ?? choice.title })),
|
|
275
|
+
onChange: (value: string) => chooseStrategy(value as ScreenFamily) }] : []),
|
|
276
|
+
...(family.delta != null ? [{ id: "delta", label: "Delta", value: deltaValue, defaultValue: familyDelta.toFixed(2),
|
|
277
|
+
options: withCurrent(DELTA_OPTIONS, deltaValue, (value) => value), onChange: (value: string) => setDelta(value) }] : []),
|
|
278
|
+
...(family.wing != null ? [{ id: "wing", label: "Wing", value: wingValue, defaultValue: defaultWing,
|
|
279
|
+
options: withCurrent(WING_OPTIONS.filter((value) => Number(value) < Number(deltaValue)), wingValue, (value) => value),
|
|
280
|
+
onChange: (value: string) => setWingDelta(value) }] : []),
|
|
281
|
+
{ id: "expiry", label: "Expiry", value: typedDate ?? daysValue, defaultValue: String(CSP_SCREEN_DEFAULTS.days),
|
|
282
|
+
options: [...withCurrent(DAYS_OPTIONS, daysValue, (value) => `${value} days`), ...(typedDate ? [{ value: typedDate, label: typedDate }] : [])],
|
|
283
|
+
onChange: (value: string) => { if (value === typedDate) return; setExpiration(""); setDays(value); } },
|
|
284
|
+
];
|
|
285
|
+
const subject = strategy === "csp" ? "cash-secured puts" : family.title.toLowerCase();
|
|
286
|
+
const table = {
|
|
287
|
+
focused: focused && !symbolsActive, rootWidth: width, rootHeight: Math.max(2, height - 1 - tabRows),
|
|
288
|
+
getItemKey: (row: { symbol: string }) => row.symbol, sortColumnId: sort.columnId, sortDirection: sort.direction,
|
|
289
|
+
emptyStateTitle: symbols.length ? `No ${strategy === "csp" ? "puts" : subject} to rank.` : `Type symbols to rank their ${subject}.`,
|
|
290
|
+
onHeaderClick: (id: string) => setSort((current) => nextHeaderSort(current, id, { firstDirection: id === "rank" || id === "symbol" ? "asc" : "desc" })),
|
|
291
|
+
onActivate: openScenario, onRootKeyDown: handleKey,
|
|
292
|
+
getExportMetadata: (): Array<[string, string | undefined]> => [[strategy === "csp" ? "puts" : subject, criteriaText || undefined],
|
|
293
|
+
["ranked by", strategy === "csp" ? "annualised yield on cash at the bid" : rankedBy],
|
|
294
|
+
["wide", ready ? `spread over ${ready.maxSpreadPercent}% of the mid` : undefined],
|
|
295
|
+
...(strategy === "csp" ? [] : [["money", STRATEGY_MONEY_UNIT] as [string, string]])],
|
|
296
|
+
};
|
|
297
|
+
// Whole columns give way, least needed first, so nothing scrolls sideways; a vertical scrollbar takes a cell.
|
|
298
|
+
const putColumns = useMemo(() => dropColumnsToFit(COLUMNS, width - 1, PUT_DROP_ORDER), [width]);
|
|
299
|
+
const tradeColumnWidth = tradeWidth(tradeRows);
|
|
300
|
+
const shownTradeColumns = useMemo(() => strategy === "csp" ? [] : dropColumnsToFit(tradeColumns(strategy, tradeColumnWidth), width - 1,
|
|
301
|
+
tradeDropOrder(strategy)), [strategy, tradeColumnWidth, width]);
|
|
302
|
+
const firstSymbol = (strategy === "csp" ? putRows : tradeRows)[0]?.symbol ?? "";
|
|
303
|
+
const selection = { kind: "id" as const, selectedId: selected ?? firstSymbol, getId: (row: { symbol: string }) => row.symbol, onChange: (id: string) => setSelected(id) };
|
|
140
304
|
return <Box width={width} height={height} flexDirection="column" overflow="hidden">
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
{
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
]} />
|
|
151
|
-
<PaneStatusBody subject="cash-secured puts" loading={resource.loading && !resource.data}
|
|
152
|
-
error={criteria.error && !criteria.value ? criteria.error : !resource.data ? resource.error : null}>
|
|
153
|
-
<DataTableView<ScreenRow> focused={focused && !symbolsActive} columns={COLUMNS} items={rows} rootWidth={width}
|
|
154
|
-
rootHeight={Math.max(2, height - 1)}
|
|
155
|
-
getItemKey={(row) => row.symbol} sortColumnId={sort.columnId} sortDirection={sort.direction}
|
|
156
|
-
emptyStateTitle={symbols.length ? "No puts to rank." : "Type symbols to rank their cash-secured puts."}
|
|
157
|
-
onHeaderClick={(id) => setSort((current) => nextHeaderSort(current, id, { firstDirection: id === "rank" || id === "symbol" ? "asc" : "desc" }))}
|
|
158
|
-
selection={{ kind: "id", selectedId: selected ?? rows[0]?.symbol ?? "", getId: (row) => row.symbol, onChange: (id) => setSelected(id) }}
|
|
159
|
-
onActivate={openScenario} onRootKeyDown={handleKey}
|
|
160
|
-
getExportMetadata={() => [["puts", criteriaText || undefined], ["ranked by", "annualised yield on cash at the bid"],
|
|
161
|
-
["wide", criteria.value ? `spread over ${criteria.value.maxSpreadPercent}% of the mid` : undefined]]}
|
|
162
|
-
renderCell={(row, column) => cell(row, column.id)} />
|
|
305
|
+
{tabStrip}
|
|
306
|
+
<QueryBar width={width} filters={filters} meta={strategy === "csp" ? undefined : STRATEGY_MONEY_UNIT} />
|
|
307
|
+
<PaneStatusBody subject={subject} loading={resource.loading && !data}
|
|
308
|
+
error={criteria.error && !ready ? criteria.error : !data ? resource.error : null}>
|
|
309
|
+
{strategy === "csp"
|
|
310
|
+
? <DataTableView<ScreenRow> {...table} columns={putColumns} items={putRows} selection={selection}
|
|
311
|
+
renderCell={(row, column) => putCell(row, column.id)} />
|
|
312
|
+
: <DataTableView<TradeRow> {...table} columns={shownTradeColumns} items={tradeRows} selection={selection}
|
|
313
|
+
renderCell={(row, column) => tradeCell(row, column.id)} />}
|
|
163
314
|
</PaneStatusBody>
|
|
164
315
|
</Box>;
|
|
165
316
|
}
|
|
@@ -31,7 +31,7 @@ export interface CspScreenCriteria {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export const CSP_SCREEN_DEFAULTS: Readonly<Omit<CspScreenCriteria, "expiration">> = { delta: 0.3, days: 30, maxSpreadPercent: 10 };
|
|
34
|
-
/** The most names one screen reads: each is two chain requests. */
|
|
34
|
+
/** The most names one screen reads: each is up to two chain requests (the first lists the expiries, the next is the one traded), a calendar one more. */
|
|
35
35
|
export const CSP_SCREEN_LIMIT = 20;
|
|
36
36
|
const DAY_MS = 86_400_000;
|
|
37
37
|
const CONCURRENCY = 4;
|
|
@@ -61,9 +61,9 @@ export interface CspScreen {
|
|
|
61
61
|
freshness: HeadlessPaneFreshness;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
const supplied = (value: unknown) => value != null && value !== "";
|
|
64
|
+
export const supplied = (value: unknown) => value != null && value !== "";
|
|
65
65
|
|
|
66
|
-
function readNumber(value: unknown, flag: string, example: string): number {
|
|
66
|
+
export function readNumber(value: unknown, flag: string, example: string): number {
|
|
67
67
|
const text = typeof value === "number" ? String(value) : typeof value === "string" ? value.trim().replace(/%$/, "") : "";
|
|
68
68
|
const number = Number(text);
|
|
69
69
|
if (!text || !/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/.test(text) || !Number.isFinite(number)) {
|
|
@@ -143,11 +143,16 @@ export interface CspScreenDependencies {
|
|
|
143
143
|
|
|
144
144
|
interface ScreenedPut { csp: CashSecuredPut; delta: number | null; market: ScenarioMarketSnapshot }
|
|
145
145
|
|
|
146
|
-
/**
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
/**
|
|
147
|
+
* One name's chain on its listed expiry nearest `target` (YYYY-MM-DD), the
|
|
148
|
+
* catalogue's own chain when it is already on that expiry. A typed
|
|
149
|
+
* `expiration` whose last session has closed is refused with the live ones
|
|
150
|
+
* nearest it.
|
|
151
|
+
*/
|
|
152
|
+
export async function loadScreenMarket(
|
|
153
|
+
instrument: InstrumentRef, criteria: Pick<CspScreenCriteria, "expiration">, dependencies: ScenarioLoaderDependencies, target: string,
|
|
154
|
+
signal: AbortSignal | undefined,
|
|
155
|
+
): Promise<ScenarioMarketSnapshot> {
|
|
151
156
|
const first = await loadScenarioMarket({ instrument, signal }, dependencies);
|
|
152
157
|
// A typed date whose last session has closed is not stood in for by the next one: it says so, and names it.
|
|
153
158
|
const now = dependencies.now?.() ?? Date.now();
|
|
@@ -160,8 +165,16 @@ async function screenPut(
|
|
|
160
165
|
throw new Error(first.warnings.find((warning) => warning.startsWith("Options chain")) ?? "No listed option expiries");
|
|
161
166
|
}
|
|
162
167
|
const loaded = first.chain ? [...first.chain.puts, ...first.chain.calls][0]?.expiration : undefined;
|
|
163
|
-
|
|
168
|
+
return loaded != null && expiryIsoDate(loaded) === expiryIsoDate(expiration)
|
|
164
169
|
? first : await loadScenarioMarket({ instrument, expiration, signal }, dependencies);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** One name's put: OSA's csp seed at `--delta` on the listed expiry nearest the target, read as OSA reads it. */
|
|
173
|
+
async function screenPut(
|
|
174
|
+
instrument: InstrumentRef, criteria: CspScreenCriteria, dependencies: ScenarioLoaderDependencies, target: string,
|
|
175
|
+
earningsDate: string | null, signal: AbortSignal | undefined,
|
|
176
|
+
): Promise<ScreenedPut> {
|
|
177
|
+
const market = await loadScreenMarket(instrument, criteria, dependencies, target, signal);
|
|
165
178
|
const settings = { symbol: instrument.symbol, exchange: instrument.exchange, strategy: "csp", delta: criteria.delta };
|
|
166
179
|
const position = scenarioPositionFromSettings(settings, market);
|
|
167
180
|
const leg = position?.legs[0];
|
|
@@ -201,13 +214,26 @@ function screenFreshness(markets: readonly ScenarioMarketSnapshot[], now: number
|
|
|
201
214
|
};
|
|
202
215
|
}
|
|
203
216
|
|
|
217
|
+
/** One name of a screen: what it found, or why it found nothing, with its IV rank either way. */
|
|
218
|
+
interface ScreenedName<T> { symbol: string; ivRank: number | null; value: T | null; note: string | null }
|
|
219
|
+
|
|
220
|
+
export interface ScreenedNames<T> {
|
|
221
|
+
names: ScreenedName<T>[];
|
|
222
|
+
missingRanks: string[];
|
|
223
|
+
ivRankError: string | null;
|
|
224
|
+
freshness: HeadlessPaneFreshness;
|
|
225
|
+
}
|
|
226
|
+
|
|
204
227
|
/**
|
|
205
|
-
* Each name
|
|
206
|
-
* calendar request and one Treasury curve for all of them.
|
|
228
|
+
* Each name screened by `screen`, four at a time: one IV rank request, one
|
|
229
|
+
* earnings calendar request and one Treasury curve for all of them. A name
|
|
230
|
+
* `screen` throws on is kept with the error as its note (`fallback` when the
|
|
231
|
+
* error has no message). `screen` returns the market that dates its row.
|
|
207
232
|
*/
|
|
208
|
-
export async function
|
|
209
|
-
instruments: readonly InstrumentRef[],
|
|
210
|
-
|
|
233
|
+
export async function screenNames<T>(
|
|
234
|
+
instruments: readonly InstrumentRef[], dependencies: CspScreenDependencies, signal: AbortSignal | undefined, fallback: string,
|
|
235
|
+
screen: (instrument: InstrumentRef, earningsDate: string | null, scenario: ScenarioLoaderDependencies) => Promise<{ value: T; market: ScenarioMarketSnapshot }>,
|
|
236
|
+
): Promise<ScreenedNames<T>> {
|
|
211
237
|
const now = dependencies.scenario.now?.() ?? Date.now();
|
|
212
238
|
// Every name reads the same curve, up to twice (the catalogue, then its expiry): asked for once, when the first name needs it.
|
|
213
239
|
let curve: ReturnType<ScenarioLoaderDependencies["loadYieldCurve"]> | null = null;
|
|
@@ -218,29 +244,43 @@ export async function loadCspScreen(
|
|
|
218
244
|
loadNextEarningsDates(dependencies.earnings, symbols, zonedDateKey(now, "America/New_York")),
|
|
219
245
|
]);
|
|
220
246
|
signal?.throwIfAborted();
|
|
221
|
-
const target = cspTargetDate(criteria, now);
|
|
222
247
|
const markets: ScenarioMarketSnapshot[] = [];
|
|
223
248
|
const missingRanks: string[] = [];
|
|
224
|
-
const
|
|
249
|
+
const names = await mapWithConcurrency(instruments, CONCURRENCY, async (instrument, index): Promise<ScreenedName<T>> => {
|
|
225
250
|
const symbol = symbols[index]!;
|
|
226
251
|
const ivRow = ivRanks.rows.get(symbol);
|
|
227
252
|
const rank = ivRow?.iv30?.rank;
|
|
228
253
|
const ivRank = rank != null && Number.isFinite(rank) ? rank : null;
|
|
229
254
|
try {
|
|
230
|
-
const screened = await
|
|
255
|
+
const screened = await screen(instrument, earnings.get(symbol) ?? null, scenario);
|
|
231
256
|
markets.push(screened.market);
|
|
232
257
|
if (ivRank == null && !ivRanks.error) {
|
|
233
258
|
const reason = ivRankReason(ivRow);
|
|
234
259
|
missingRanks.push(reason ? `${symbol} (${reason})` : symbol);
|
|
235
260
|
}
|
|
236
|
-
return { symbol,
|
|
261
|
+
return { symbol, ivRank, value: screened.value, note: null };
|
|
237
262
|
} catch (error) {
|
|
238
263
|
if (signal?.aborted) throw error;
|
|
239
|
-
return { symbol,
|
|
264
|
+
return { symbol, ivRank, value: null, note: errorMessage(error) ?? fallback };
|
|
240
265
|
}
|
|
241
266
|
});
|
|
242
|
-
return {
|
|
243
|
-
|
|
267
|
+
return { names, missingRanks: missingRanks.sort(), ivRankError: ivRanks.error, freshness: screenFreshness(markets, now) };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Each name's cash-secured put, ranked. */
|
|
271
|
+
export async function loadCspScreen(
|
|
272
|
+
instruments: readonly InstrumentRef[], criteria: CspScreenCriteria, dependencies: CspScreenDependencies, signal?: AbortSignal,
|
|
273
|
+
): Promise<CspScreen> {
|
|
274
|
+
const target = cspTargetDate(criteria, dependencies.scenario.now?.() ?? Date.now());
|
|
275
|
+
const screened = await screenNames(instruments, dependencies, signal, "No put to sell", async (instrument, earningsDate, scenario) => {
|
|
276
|
+
const put = await screenPut(instrument, criteria, scenario, target, earningsDate, signal);
|
|
277
|
+
return { value: put, market: put.market };
|
|
278
|
+
});
|
|
279
|
+
const rows = screened.names.map(({ symbol, ivRank, value, note }): Omit<CspScreenRow, "rank"> => ({
|
|
280
|
+
symbol, csp: value?.csp ?? null, delta: value?.delta ?? null, ivRank,
|
|
281
|
+
wideSpread: value ? cspWideSpread(value.csp, criteria.maxSpreadPercent) : false, note }));
|
|
282
|
+
return { rows: rankCspScreen(rows), criteria, missingRanks: screened.missingRanks, ivRankError: screened.ivRankError,
|
|
283
|
+
freshness: screened.freshness };
|
|
244
284
|
}
|
|
245
285
|
|
|
246
286
|
/** `nearest 0.30 delta · expiry nearest 2026-11-21 (30 days)`: what each name's put is. */
|
|
@@ -249,8 +289,8 @@ export function cspScreenCriteriaText(criteria: CspScreenCriteria, now: number):
|
|
|
249
289
|
+ (criteria.expiration == null ? ` (${criteria.days} days)` : "");
|
|
250
290
|
}
|
|
251
291
|
|
|
252
|
-
/** `before 2026-11-04`, `after 2027-01-19`, `unknown`: whether the next report falls before the put expires, and its date. */
|
|
253
|
-
export function cspEarningsText(csp: CashSecuredPut | null): string {
|
|
292
|
+
/** `before 2026-11-04`, `after 2027-01-19`, `unknown`: whether the next report falls before the put (or a trade's first leg) expires, and its date. */
|
|
293
|
+
export function cspEarningsText(csp: Pick<CashSecuredPut, "earnings"> | null): string {
|
|
254
294
|
if (!csp) return "--";
|
|
255
295
|
const { beforeExpiry, date } = csp.earnings;
|
|
256
296
|
return beforeExpiry == null ? "unknown" : `${beforeExpiry ? "before" : "after"} ${date}`;
|
|
@@ -87,6 +87,13 @@ function newYorkCalendarDate(time: number): string {
|
|
|
87
87
|
return newYorkDate.format(time);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/** The valuation date in New York, and the calendar days from it to `expiry` (YYYY-MM-DD), 0 once it is reached. */
|
|
91
|
+
export function calendarDaysToExpiry(asOf: number, expiry: string): { valuationDate: string; daysToExpiry: number } {
|
|
92
|
+
const valuationDate = newYorkCalendarDate(asOf);
|
|
93
|
+
return { valuationDate,
|
|
94
|
+
daysToExpiry: Math.max(0, Math.round((Date.parse(`${expiry}T00:00:00Z`) - Date.parse(`${valuationDate}T00:00:00Z`)) / DAY_MS)) };
|
|
95
|
+
}
|
|
96
|
+
|
|
90
97
|
/** Whether the next report falls before the put expires; unknown without an upcoming date. */
|
|
91
98
|
export function earningsBeforeExpiry(earningsDate: string | null | undefined, valuationDate: string, expiry: string): CspEarnings {
|
|
92
99
|
if (!earningsDate || !/^\d{4}-\d{2}-\d{2}$/.test(earningsDate) || earningsDate < valuationDate) return { date: null, beforeExpiry: null };
|
|
@@ -114,8 +121,7 @@ export function cashSecuredPut(
|
|
|
114
121
|
if (position.legs.length !== 1 || !leg || leg.side !== "put" || leg.quantity >= 0 || position.underlying) return null;
|
|
115
122
|
const contracts = -leg.quantity;
|
|
116
123
|
const expiry = new Date(leg.expiration * 1000).toISOString().slice(0, 10);
|
|
117
|
-
const valuationDate =
|
|
118
|
-
const daysToExpiry = Math.max(0, Math.round((Date.parse(`${expiry}T00:00:00Z`) - Date.parse(`${valuationDate}T00:00:00Z`)) / DAY_MS));
|
|
124
|
+
const { valuationDate, daysToExpiry } = calendarDaysToExpiry(position.asOf, expiry);
|
|
119
125
|
const cashSecured = leg.strike * leg.multiplier * contracts;
|
|
120
126
|
const sharesSold = leg.multiplier * contracts;
|
|
121
127
|
const atEntry = putYield(leg.price, cashSecured, sharesSold, daysToExpiry);
|
|
@@ -26,24 +26,26 @@ export const optionsScenarioModule: PluginModule = {
|
|
|
26
26
|
],
|
|
27
27
|
},
|
|
28
28
|
}, {
|
|
29
|
-
id: "csp-screen", name: "
|
|
29
|
+
id: "csp-screen", name: "Options Strategy Screen", icon: "V", component: CspScreenPane,
|
|
30
30
|
defaultPosition: "right", defaultMode: "floating", defaultFloatingSize: { width: 144, height: 24 },
|
|
31
31
|
tableExport: true, headless: cspScreenHeadless,
|
|
32
32
|
settings: {
|
|
33
|
-
title: "
|
|
33
|
+
title: "Options Strategy Screen",
|
|
34
34
|
fields: [
|
|
35
35
|
{ key: "symbols", label: "Symbols", type: "text", placeholder: "JPM, GS, KO" },
|
|
36
36
|
{ key: "maxSpread", label: "Wide spread over (%)", type: "text", placeholder: "10",
|
|
37
|
-
description: "A
|
|
37
|
+
description: "A trade whose bid/ask spread is over this percent of its mid is marked wide. It is still ranked." },
|
|
38
38
|
],
|
|
39
39
|
},
|
|
40
40
|
}],
|
|
41
41
|
paneTemplates: [{
|
|
42
|
-
id: "csp-screen-pane", paneId: "csp-screen", label: "
|
|
43
|
-
description: "Cash-secured puts
|
|
44
|
-
|
|
42
|
+
id: "csp-screen-pane", paneId: "csp-screen", label: "Options Strategy Screen",
|
|
43
|
+
description: "Cash-secured puts, covered calls, verticals, iron condors, straddles, strangles and calendars across a list, ranked, "
|
|
44
|
+
+ "with max gain and loss, break-evens, probability of profit, IV rank, spread and an earnings flag.",
|
|
45
|
+
// The put-selling phrases lead here first, then to OSA for one name; so do the strategies it screens.
|
|
45
46
|
keywords: ["csp", "cash secured put", "cash-secured puts", "put selling", "selling puts", "sell put", "sell puts", "short put", "wheel",
|
|
46
|
-
"put screen", "premium yield"
|
|
47
|
+
"put screen", "premium yield", "strategy screen", "options screener", "covered call", "covered calls", "iron condor", "strangle",
|
|
48
|
+
"straddle screen", "calendar spread", "bull put spread", "bear call spread", "vertical spread"],
|
|
47
49
|
shortcut: { prefix: "CSP", argKind: "ticker-list" as const }, headless: cspScreenHeadless,
|
|
48
50
|
createInstance: (_context, options) => ({
|
|
49
51
|
title: "CSP", placement: "floating" as const,
|