@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
|
@@ -2,8 +2,8 @@ import type { HeadlessPaneDefinition } from "../../../types/plugin";
|
|
|
2
2
|
import { resolveHeadlessInstrument } from "../shared/headless-market-data";
|
|
3
3
|
import { createRealizedVolatilityDependencies, loadRealizedVolatilityHistory } from "../realized-vol/client";
|
|
4
4
|
import { createHvDependencies, loadIvHistory, loadIvScreen, loadScreenVolatilities } from "./client";
|
|
5
|
-
import { formatPoints, formatRank, formatSpot, formatStat, formatVol, IMPLIED_VOL_WORDS, readingLabel, sharedDates, skewHeader,
|
|
6
|
-
import { HV_WINDOWS, type HvWindow, type IvLookback, type IvStatRow, projectIvHistory, projectRichCheap, type RichCheapRow, richCheapDates, screenCoverage, VCA_LIMIT, VCA_PRESETS } from "./model";
|
|
5
|
+
import { formatPoints, formatRank, formatSpot, formatStat, formatVol, IMPLIED_VOL_WORDS, readingLabel, sharedDates, skewHeader, statSource, vcaDefinitions, verdictLabel } from "./format";
|
|
6
|
+
import { HV_WINDOWS, type HvWindow, type IvLookback, type IvStatRow, projectIvHistory, projectRichCheap, type RichCheapRow, richCheapDates, screenCoverage, screenStaleNote, VCA_LIMIT, VCA_PRESETS } from "./model";
|
|
7
7
|
import { vcaUniverse } from "./universe";
|
|
8
8
|
import { offsetText, releaseOffsetRows } from "./events";
|
|
9
9
|
import { loadMacroReleases } from "../macro-day/client";
|
|
@@ -16,6 +16,7 @@ import type { HeadlessPaneContext } from "../../../types/headless";
|
|
|
16
16
|
import { zonedDateKey } from "../../../utils/zoned-date-time";
|
|
17
17
|
import { listingFateNotes, listingFateReport, loadListingFate, MovedSymbolError } from "../../../tickers/listing-fate";
|
|
18
18
|
import { startTickerChangeLookup } from "../../../tickers/ticker-changes";
|
|
19
|
+
import { fxVolatilityRefusal } from "../../../market-data/options-alternatives";
|
|
19
20
|
|
|
20
21
|
const METHODOLOGY = "docs/research-data.md#implied-volatility-history";
|
|
21
22
|
const percent = (value: unknown) => typeof value === "number" ? formatVol(value) : "--";
|
|
@@ -24,6 +25,7 @@ const points = (value: unknown) => typeof value === "number" ? formatPoints(valu
|
|
|
24
25
|
export const ivHistoryHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
25
26
|
shape: "bundle", argument: { kind: "ticker", description: "US option underlying" },
|
|
26
27
|
usListingsOnly: "Implied volatility history",
|
|
28
|
+
refuseSymbol: fxVolatilityRefusal("HIVG"),
|
|
27
29
|
freshness: { status: "not-a-feed", basis: "daily implied volatility", cadence: "daily", observedKey: "date", oldest: null },
|
|
28
30
|
describe: (args) => `HIVG ${args.symbols[0] ?? ""}`,
|
|
29
31
|
discovery: { aliases: ["HIVG"], suggestFor: IMPLIED_VOL_WORDS, screenshotReadiness: "live-dom", dataRequirements: ["Cloud stored implied volatility", "Daily price history"],
|
|
@@ -79,7 +81,7 @@ export const ivHistoryHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
79
81
|
// With nothing served, the fate is why: no "not a ticker" or missing-history errors beside it.
|
|
80
82
|
const { notes: fateNotes, errors } = listingFateReport(fate, { served },
|
|
81
83
|
[prices.error, ...model.warnings].filter((value): value is string => !!value));
|
|
82
|
-
const notes = [...fateNotes, ...[model.oneYearNote, model.skewNote].filter((note): note is string => !!note)];
|
|
84
|
+
const notes = [...fateNotes, ...[model.oneYearNote, model.skewNote, model.staleNote].filter((note): note is string => !!note)];
|
|
83
85
|
if (event && releases) {
|
|
84
86
|
const lookback = String(args.options.lookback) as IvLookback;
|
|
85
87
|
const label = MACRO_EVENT_LABELS[event];
|
|
@@ -117,18 +119,27 @@ export const ivHistoryHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
117
119
|
releaseSource: MACRO_RELEASE_SOURCES[event], unit: "decimal annualized volatility", methodology: METHODOLOGY },
|
|
118
120
|
};
|
|
119
121
|
}
|
|
122
|
+
// The text report dates a stale measure "stale"; exports keep the bare date and say it in `stale`.
|
|
123
|
+
const statColumns = [
|
|
124
|
+
{ key: "label", header: "Measure" },
|
|
125
|
+
{ key: "value", header: "Current", format: (value: unknown, row?: unknown) => formatStat(value as number | null, (row as IvStatRow).unit) },
|
|
126
|
+
{ key: "date", header: "As of" }, { key: "method", header: "Method" },
|
|
127
|
+
{ key: "low", header: "52w low", format: (value: unknown, row?: unknown) => formatStat(value as number | null, (row as IvStatRow).unit) },
|
|
128
|
+
{ key: "high", header: "52w high", format: (value: unknown, row?: unknown) => formatStat(value as number | null, (row as IvStatRow).unit) },
|
|
129
|
+
{ key: "rank", header: "Rank", format: (value: unknown) => formatRank(value as number | null) },
|
|
130
|
+
{ key: "percentile", header: "Pctl", format: (value: unknown) => formatRank(value as number | null) },
|
|
131
|
+
{ key: "samples", header: "Sessions" },
|
|
132
|
+
];
|
|
133
|
+
const statTextColumns = statColumns.map((column) => column.key === "date"
|
|
134
|
+
? { ...column, textFormat: (value: unknown, row: unknown) => typeof value !== "string" ? "-" : (row as IvStatRow).stale ? `${value} stale` : value }
|
|
135
|
+
: column.key === "method"
|
|
136
|
+
// The wording the pane's source column uses; exports keep the raw method and carry the basis.
|
|
137
|
+
? { ...column, textFormat: (value: unknown, row: unknown) => typeof value !== "string" ? "-" : statSource(row as IvStatRow) }
|
|
138
|
+
: column);
|
|
120
139
|
return {
|
|
121
140
|
sections: [
|
|
122
|
-
{ title: "Statistics", columns: [
|
|
123
|
-
|
|
124
|
-
{ key: "value", header: "Current", format: (value: unknown, row?: unknown) => formatStat(value as number | null, (row as IvStatRow).unit) },
|
|
125
|
-
{ key: "date", header: "As of" }, { key: "method", header: "Method" },
|
|
126
|
-
{ key: "low", header: "52w low", format: (value: unknown, row?: unknown) => formatStat(value as number | null, (row as IvStatRow).unit) },
|
|
127
|
-
{ key: "high", header: "52w high", format: (value: unknown, row?: unknown) => formatStat(value as number | null, (row as IvStatRow).unit) },
|
|
128
|
-
{ key: "rank", header: "Rank", format: (value: unknown) => formatRank(value as number | null) },
|
|
129
|
-
{ key: "percentile", header: "Pctl", format: (value: unknown) => formatRank(value as number | null) },
|
|
130
|
-
{ key: "samples", header: "Sessions" },
|
|
131
|
-
], rows: model.stats.map((row) => ({ ...row })) },
|
|
141
|
+
{ title: "Statistics", columns: [...statColumns, { key: "basis", header: "Basis" }, { key: "stale", header: "Stale" }], textColumns: statTextColumns,
|
|
142
|
+
rows: model.stats.map((row) => ({ ...row })) },
|
|
132
143
|
// Exports always carry the skew columns, so a script's header never moves; the text table drops them when none has a value.
|
|
133
144
|
{ title: "Daily history", columns: [...historyColumns, ...skewColumns], rows,
|
|
134
145
|
...(rows.some((row) => row.skew25d != null) ? {} : { textColumns: historyColumns }) },
|
|
@@ -151,6 +162,7 @@ export const ivScreenHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
151
162
|
freshness: { status: "not-a-feed", basis: "daily implied volatility", cadence: "daily", observedKey: "date" },
|
|
152
163
|
argument: { kind: "symbol-list", optional: true, maximum: VCA_LIMIT, description: "US option underlyings; defaults to index and sector ETFs." },
|
|
153
164
|
usListingsOnly: "Implied volatility",
|
|
165
|
+
refuseSymbol: fxVolatilityRefusal("VCA"),
|
|
154
166
|
options: [{ key: "preset", type: "enum", values: [{ value: "etfs" }, { value: "megacaps" }], defaultValue: "etfs", description: "Preset when no symbols are given" }],
|
|
155
167
|
describe: "Volatility rich/cheap",
|
|
156
168
|
// The pane names its universe `scope`; symbols typed open their own (custom), which a preset does not replace.
|
|
@@ -180,14 +192,15 @@ export const ivScreenHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
180
192
|
// A listing that stopped trading has its row's note, not a line saying it has no options history.
|
|
181
193
|
const coverage = screenCoverage(rows, { explained: new Set([...noted].filter(([, entry]) => entry.fate.endedOn).map(([symbol]) => symbol)) });
|
|
182
194
|
const dates = richCheapDates(rows);
|
|
183
|
-
const hasSkew = rows.some((row) => row.
|
|
195
|
+
const hasSkew = rows.some((row) => row.riskReversal != null);
|
|
184
196
|
const hasIv1y = rows.some((row) => row.iv1y != null), hasIv1yRank = rows.some((row) => row.iv1yPercentile != null);
|
|
185
197
|
const dated = (value: unknown) => typeof value === "string" ? value : "--";
|
|
186
198
|
return {
|
|
187
199
|
sections: [{ title: ["Rich/cheap", universe.label, ...sharedDates(dates)].join(" · "), columns: [
|
|
188
200
|
{ key: "symbol", header: "Symbol" }, { key: "iv30", header: "IV30", format: percent },
|
|
189
201
|
...(dates.reading ? [] : [{ key: "date", header: "As of", format: (value: unknown, row?: unknown) =>
|
|
190
|
-
typeof value === "string" ? readingLabel(value, (row as RichCheapRow).method
|
|
202
|
+
typeof value === "string" ? readingLabel(value, (row as RichCheapRow).method, false,
|
|
203
|
+
{ basis: (row as RichCheapRow).basis, stale: (row as RichCheapRow).stale }) : "--" }]),
|
|
191
204
|
// The underlying price each reading was taken at, on the date the title or the As of column gives.
|
|
192
205
|
{ key: "spot", header: "Spot", format: (value: unknown) => typeof value === "number" ? formatSpot(value) : "--" },
|
|
193
206
|
...(dates.rankApart ? [{ key: "rankDate", header: "Rank as of", format: (value: unknown) => typeof value === "string" ? `${value} close` : "--" }] : []),
|
|
@@ -197,16 +210,18 @@ export const ivScreenHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
197
210
|
...(hasIv1y ? [{ key: "iv1y", header: "IV1Y", format: percent }] : []),
|
|
198
211
|
...(hasIv1yRank ? [{ key: "iv1yPercentile", header: "IVP1Y", format: (value: unknown) => formatRank(value as number | null) }] : []),
|
|
199
212
|
{ key: "termSlope", header: "30-90", format: (value: unknown) => formatPoints(value as number | null) },
|
|
200
|
-
...(hasSkew ? [{ key: "
|
|
201
|
-
...(hasSkew && dates.skewApart ? [{ key: "skewDate", header: "
|
|
213
|
+
...(hasSkew ? [{ key: "riskReversal", header: skewHeader("25D RR", "call-put"), format: (value: unknown) => formatPoints(value as number | null) }] : []),
|
|
214
|
+
...(hasSkew && dates.skewApart ? [{ key: "skewDate", header: "RR as of", format: dated }] : []),
|
|
202
215
|
{ key: "hv", header: "HV20", format: percent },
|
|
216
|
+
// Said only where an HV is not the one of its IV's date: a stale reading's, or one a few sessions off.
|
|
217
|
+
...(dates.hvApart ? [{ key: "hvDate", header: "HV as of", format: dated }] : []),
|
|
203
218
|
{ key: "ivHv", header: "IV/HV", format: (value: unknown) => typeof value === "number" ? value.toFixed(2) : "--" },
|
|
204
219
|
...(noted.size > 0 ? [{ key: "note", header: "Note", underRow: true }] : []),
|
|
205
220
|
], rows: rows.map((row) => ({ ...row, ...noted.has(row.symbol) ? { note: noted.get(row.symbol)!.note } : {} })) }],
|
|
206
221
|
// A symbol with no options history is final; only history still to come leaves the screen incomplete.
|
|
207
222
|
complete: !coverage.pending.length && !universe.error, unavailableSymbols: coverage.missing,
|
|
208
223
|
errors: [universe.error].filter((value): value is string => !!value),
|
|
209
|
-
notes: [...coverage.notes, ...
|
|
224
|
+
notes: [...coverage.notes, ...[screenStaleNote(rows)].filter((note): note is string => !!note), ...vcaDefinitions({ skew: hasSkew })],
|
|
210
225
|
metadata: { presets: VCA_PRESETS, methodology: METHODOLOGY },
|
|
211
226
|
};
|
|
212
227
|
},
|
|
@@ -32,13 +32,13 @@ export const ivHistoryModule: PluginModule = {
|
|
|
32
32
|
{ ...createTickerSurfacePaneTemplate({
|
|
33
33
|
id: "iv-history-pane", paneId: "iv-history", label: "Implied Volatility History",
|
|
34
34
|
description: "30-day, 90-day and one-year ATM implied volatility since 2024 against realized, with IV rank and percentile.",
|
|
35
|
-
keywords: ["hivg", "implied", "volatility", "history", "iv rank", "ivr", "percentile", "vrp", "iv1y", "leaps"], shortcut: "HIVG", publicShare: true,
|
|
35
|
+
keywords: ["hivg", "implied", "volatility", "vol", "history", "iv rank", "ivr", "percentile", "vrp", "iv1y", "leaps"], shortcut: "HIVG", publicShare: true,
|
|
36
36
|
}), headless: ivHistoryHeadless },
|
|
37
37
|
{
|
|
38
38
|
id: "iv-screen-pane", paneId: "iv-screen", label: "Volatility Rich/Cheap",
|
|
39
39
|
description: "IV rank, percentile, one-year IV, term slope, skew and IV/HV across a list, ranked rich to cheap.",
|
|
40
40
|
// Where a put seller finds rich premium; CSP and OSA own the exact put-selling phrases and rank first on them.
|
|
41
|
-
keywords: ["vca", "rich", "cheap", "iv rank", "implied volatility", "screen", "cash-secured puts", "selling puts", "short-put candidates",
|
|
41
|
+
keywords: ["vca", "vol", "rich", "cheap", "iv rank", "implied volatility", "screen", "cash-secured puts", "selling puts", "short-put candidates",
|
|
42
42
|
"csp candidates", "wheel candidates"],
|
|
43
43
|
shortcut: { prefix: "VCA", argKind: "ticker-list" as const, argOptional: true }, headless: ivScreenHeadless,
|
|
44
44
|
createInstance: (_context, options) => ({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { getPublishedUsEquityCalendarDay } from "../../../market-data/published-us-sessions";
|
|
1
2
|
import type { PricePoint } from "../../../types/financials";
|
|
2
3
|
import { rollingRealizedVolatility } from "../shared/volatility";
|
|
3
|
-
import type { IvCoverageStatus, IvHistoryPayload, IvMethod, IvPoint, IvScreenRow, IvStats } from "./client";
|
|
4
|
+
import type { IvCloseBasis, IvCoverageStatus, IvHistoryPayload, IvMethod, IvPoint, IvScreenRow, IvStats, ScreenHv } from "./client";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* A history's warnings in words for the reader. A symbol demand coverage could
|
|
@@ -18,6 +19,75 @@ export type IvLookback = "1Y" | "2Y" | "ALL";
|
|
|
18
19
|
const RICH_PERCENTILE = 80;
|
|
19
20
|
const CHEAP_PERCENTILE = 20;
|
|
20
21
|
|
|
22
|
+
/** A reading trailing the symbol's newest session by more sessions than this is stale. */
|
|
23
|
+
const STALE_AFTER_SESSIONS = 5;
|
|
24
|
+
const DAY_MS = 86_400_000;
|
|
25
|
+
|
|
26
|
+
/** Whether `date` ("2026-10-23") is a US equity session: the published NYSE calendar where it covers, else a weekday. */
|
|
27
|
+
function isSession(date: string): boolean {
|
|
28
|
+
const published = getPublishedUsEquityCalendarDay("NYSE", date);
|
|
29
|
+
if (published) return published === "session";
|
|
30
|
+
const weekday = new Date(`${date}T00:00:00Z`).getUTCDay();
|
|
31
|
+
return weekday !== 0 && weekday !== 6;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** US sessions after `date` up to and including `through`: how far behind that session a reading is. */
|
|
35
|
+
export function sessionsBehind(date: string, through: string): number {
|
|
36
|
+
const end = Date.parse(`${through}T00:00:00Z`);
|
|
37
|
+
let behind = 0;
|
|
38
|
+
for (let time = Date.parse(`${date}T00:00:00Z`) + DAY_MS; time <= end; time += DAY_MS) {
|
|
39
|
+
if (isSession(new Date(time).toISOString().slice(0, 10))) behind += 1;
|
|
40
|
+
}
|
|
41
|
+
return behind;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The newest of the dates given: the newest session the symbol's stored readings, backfill and prices reach. */
|
|
45
|
+
function newestSession(...dates: ReadonlyArray<string | null | undefined>): string | null {
|
|
46
|
+
return dates.filter((date): date is string => !!date).map((date) => date.slice(0, 10)).sort().at(-1) ?? null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** How many sessions a reading is behind the newest, and whether that makes it stale. */
|
|
50
|
+
function staleness(date: string | null | undefined, newest: string | null): { sessionsBehind: number | null; stale: boolean } {
|
|
51
|
+
const behind = date && newest ? sessionsBehind(date.slice(0, 10), newest) : null;
|
|
52
|
+
return { sessionsBehind: behind, stale: behind != null && behind > STALE_AFTER_SESSIONS };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** An HV further than this many sessions from the IV's date says nothing about it: IV/HV is left empty. */
|
|
56
|
+
const HV_MAX_SESSIONS_APART = 3;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* HV after the close of the last session on or before `date`, when that is within
|
|
60
|
+
* `HV_MAX_SESSIONS_APART` sessions of it; null before the series starts or when the
|
|
61
|
+
* nearest HV is older than that.
|
|
62
|
+
*/
|
|
63
|
+
export function hvOn(hv: ScreenHv, date: string): { date: string; value: number } | null {
|
|
64
|
+
for (let index = hv.series.length - 1; index >= 0; index -= 1) {
|
|
65
|
+
const [sessionDate, value] = hv.series[index]!;
|
|
66
|
+
if (sessionDate <= date) return sessionsBehind(sessionDate, date) <= HV_MAX_SESSIONS_APART ? { date: sessionDate, value } : null;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* How a reading's method is worded wherever it is dated: "quote", "close", or "close mid" for a
|
|
73
|
+
* close solved from quote midpoints (mid or mixed). Two readings that word alike share one label.
|
|
74
|
+
*/
|
|
75
|
+
export function readingSource(method: IvMethod | null | undefined, basis?: IvCloseBasis | null): string {
|
|
76
|
+
return method === "quote-mid" ? "quote" : basis === "mid" || basis === "mixed" ? "close mid" : "close";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const STALE_NOTE_LIMIT = 5;
|
|
80
|
+
/**
|
|
81
|
+
* The stale readings, dated, and what that leaves out, in one wording for HIVG and VCA: "Stale, left
|
|
82
|
+
* out of rank: IV 1Y ATM 2025-01-17 (over 5 sessions behind)." The list stops at five, then "N more".
|
|
83
|
+
*/
|
|
84
|
+
export function staleReadingsNote(leftOut: string, items: ReadonlyArray<{ label: string; date: string }>): string | null {
|
|
85
|
+
if (!items.length) return null;
|
|
86
|
+
const listed = items.slice(0, STALE_NOTE_LIMIT).map((item) => `${item.label} ${item.date}`);
|
|
87
|
+
const more = items.length - listed.length;
|
|
88
|
+
return `Stale, left out of ${leftOut}: ${listed.join(", ")}${more > 0 ? `, ${more} more` : ""} (over ${STALE_AFTER_SESSIONS} sessions behind).`;
|
|
89
|
+
}
|
|
90
|
+
|
|
21
91
|
export interface DatedValue { date: Date; value: number | null }
|
|
22
92
|
export type IvStatUnit = "vol" | "points" | "ratio";
|
|
23
93
|
/** One measure's latest value ranked against its own prior 52 weeks. */
|
|
@@ -28,11 +98,21 @@ export interface IvStatRow {
|
|
|
28
98
|
value: number | null;
|
|
29
99
|
date: string | null;
|
|
30
100
|
method: IvMethod | "prices" | null;
|
|
101
|
+
/** Prices a trade close was solved from, when the server says (see `IvCloseBasis`). */
|
|
102
|
+
basis: IvCloseBasis | null;
|
|
31
103
|
low: number | null;
|
|
32
104
|
high: number | null;
|
|
33
105
|
rank: number | null;
|
|
34
106
|
percentile: number | null;
|
|
35
107
|
samples: number;
|
|
108
|
+
/**
|
|
109
|
+
* The reading trails the symbol's newest session by more than `STALE_AFTER_SESSIONS`
|
|
110
|
+
* sessions: its rank and percentile are left out, since they would rank an old
|
|
111
|
+
* figure as though it were current.
|
|
112
|
+
*/
|
|
113
|
+
stale: boolean;
|
|
114
|
+
/** Sessions between the reading and the symbol's newest session. */
|
|
115
|
+
sessionsBehind: number | null;
|
|
36
116
|
}
|
|
37
117
|
/** Rank and percentile need this many prior sessions, matching the server. */
|
|
38
118
|
const MIN_RANK_SAMPLES = 120;
|
|
@@ -96,6 +176,8 @@ export interface IvHistoryModel {
|
|
|
96
176
|
/** "skew from 2026-09-23", or why there is no skew at all. */
|
|
97
177
|
skewNote: string | null;
|
|
98
178
|
stats: IvStatRow[];
|
|
179
|
+
/** Which measures are stale and what that leaves out, when any is. */
|
|
180
|
+
staleNote: string | null;
|
|
99
181
|
since: string | null;
|
|
100
182
|
/** Where the 1Y series starts and how deep it is, when that differs from the rest or leaves it unranked. */
|
|
101
183
|
oneYearNote: string | null;
|
|
@@ -127,15 +209,16 @@ function lookbackStart(asOf: string, lookback: IvLookback): string | null {
|
|
|
127
209
|
/** The latest value against the 52 weeks before it, with the same rules as the server's IV rank. */
|
|
128
210
|
function seriesStat(id: string, label: string, unit: IvStatUnit, points: readonly DatedValue[], method: IvStatRow["method"]): IvStatRow {
|
|
129
211
|
const latest = [...points].reverse().find((point) => point.value != null);
|
|
130
|
-
if (!latest) return { id, label, unit, value: null, date: null, method, low: null, high: null, rank: null, percentile: null, samples: 0
|
|
212
|
+
if (!latest) return { id, label, unit, value: null, date: null, method, basis: null, low: null, high: null, rank: null, percentile: null, samples: 0,
|
|
213
|
+
stale: false, sessionsBehind: null };
|
|
131
214
|
const start = lookbackStart(day(latest.date), "1Y")!;
|
|
132
215
|
const window = points.filter((point) => point.value != null && day(point.date) >= start && point.date < latest.date).map((point) => point.value!);
|
|
133
216
|
const value = latest.value!;
|
|
134
217
|
const low = window.length ? Math.min(value, ...window) : null, high = window.length ? Math.max(value, ...window) : null;
|
|
135
218
|
const enough = window.length >= MIN_RANK_SAMPLES;
|
|
136
|
-
return { id, label, unit, value, date: day(latest.date), method, low, high,
|
|
219
|
+
return { id, label, unit, value, date: day(latest.date), method, basis: null, low, high,
|
|
137
220
|
rank: enough && high! > low! ? (value - low!) / (high! - low!) * 100 : null,
|
|
138
|
-
percentile: midrankPercentile(window, value), samples: window.length };
|
|
221
|
+
percentile: midrankPercentile(window, value), samples: window.length, stale: false, sessionsBehind: null };
|
|
139
222
|
}
|
|
140
223
|
|
|
141
224
|
function byMethod(series: readonly IvPoint[], method: IvMethod, field: "iv30" | "iv90" | "iv365") {
|
|
@@ -192,25 +275,37 @@ export function projectIvHistory(
|
|
|
192
275
|
return { date: point.date, value: point.value == null || iv90 == null || !(iv90 > 0) ? null : point.value / iv90 };
|
|
193
276
|
});
|
|
194
277
|
const fromServer = (id: string, label: string, stats: IvStats | null): IvStatRow => ({ id, label, unit: "vol",
|
|
195
|
-
value: stats?.value ?? null, date: stats?.date ?? null, method: stats?.method ?? null,
|
|
196
|
-
|
|
278
|
+
value: stats?.value ?? null, date: stats?.date ?? null, method: stats?.method ?? null, basis: stats?.basis ?? null,
|
|
279
|
+
low: stats?.low ?? null, high: stats?.high ?? null,
|
|
280
|
+
rank: stats?.rank ?? null, percentile: stats?.percentile ?? null, samples: stats?.samples ?? 0, stale: false, sessionsBehind: null });
|
|
197
281
|
const stats: IvStatRow[] = [fromServer("iv30", "IV 30d ATM", payload.stats.iv30)];
|
|
198
282
|
// A newer quote capture is shown on its own row: its rank would mix methods.
|
|
199
283
|
if (latest?.method === "quote-mid" && latest.iv30 != null && (!payload.stats.iv30 || latest.date > payload.stats.iv30.date)) {
|
|
200
|
-
stats.push({ id: "iv30-quote", label: "IV 30d quote", unit: "vol", value: latest.iv30, date: latest.date, method: "quote-mid",
|
|
201
|
-
low: null, high: null, rank: null, percentile: null, samples: 0 });
|
|
284
|
+
stats.push({ id: "iv30-quote", label: "IV 30d quote", unit: "vol", value: latest.iv30, date: latest.date, method: "quote-mid", basis: null,
|
|
285
|
+
low: null, high: null, rank: null, percentile: null, samples: 0, stale: false, sessionsBehind: null });
|
|
202
286
|
}
|
|
203
287
|
// IV1Y ranks trade closes only; until there is one, a stored quote gives its level, unranked.
|
|
204
288
|
const oneYear = payload.stats.iv365 ?? null;
|
|
205
289
|
stats.push(fromServer("iv90", "IV 90d ATM", payload.stats.iv90),
|
|
206
290
|
!oneYear && latest?.iv365 != null
|
|
207
|
-
? { id: "iv365", label: "IV 1Y ATM", unit: "vol", value: latest.iv365, date: latest.date, method: latest.method,
|
|
208
|
-
low: null, high: null, rank: null, percentile: null, samples: 0 }
|
|
291
|
+
? { id: "iv365", label: "IV 1Y ATM", unit: "vol", value: latest.iv365, date: latest.date, method: latest.method, basis: latest.basis ?? null,
|
|
292
|
+
low: null, high: null, rank: null, percentile: null, samples: 0, stale: false, sessionsBehind: null }
|
|
209
293
|
: fromServer("iv365", "IV 1Y ATM", oneYear),
|
|
210
294
|
seriesStat("hv", `HV ${window}`, "vol", rolling, "prices"),
|
|
211
295
|
seriesStat("spread", `IV 30d - HV ${window}`, "points", spreadAll, "trade-close"),
|
|
212
296
|
seriesStat("term", "IV 30d / 90d", "ratio", termAll, "trade-close"));
|
|
213
297
|
|
|
298
|
+
// The newest session the symbol reaches, by its prices, its backfill and every reading it has: a reading more
|
|
299
|
+
// than STALE_AFTER_SESSIONS behind it is shown as it is, marked stale, and ranks nothing.
|
|
300
|
+
const newest = newestSession(payload.coverage?.backfilledThrough, latest?.date, latestDate,
|
|
301
|
+
...payload.series.map((point) => point.sessionDate), ...prices.map((price) => day(price.date)));
|
|
302
|
+
const measured = stats.map((row): IvStatRow => {
|
|
303
|
+
const behind = staleness(row.date, newest);
|
|
304
|
+
return { ...row, ...behind, ...behind.stale ? { rank: null, percentile: null } : {} };
|
|
305
|
+
});
|
|
306
|
+
const staleRows = measured.filter((row) => row.stale);
|
|
307
|
+
const staleText = staleReadingsNote("rank", staleRows.map((row) => ({ label: row.label, date: row.date! })));
|
|
308
|
+
|
|
214
309
|
// Worded as VCA words each symbol (screenCoverage).
|
|
215
310
|
if (payload.status === "queued") warnings.push(`${payload.symbol} ${queuedText(payload.coverage?.addedAt, options.now ?? Date.now())}.`);
|
|
216
311
|
else if (payload.status === "backfilling") {
|
|
@@ -232,7 +327,7 @@ export function projectIvHistory(
|
|
|
232
327
|
iv30: visible(tradeIv30), iv90: visible(byMethod(payload.series, "trade-close", "iv90")), iv365: visible(tradeIv365),
|
|
233
328
|
quoteIv30: visible(byMethod(payload.series, "quote-mid", "iv30")),
|
|
234
329
|
hv, spread: visible(spreadAll), skew: visible(skew.points),
|
|
235
|
-
skewSince: skew.points[0] ? day(skew.points[0].date) : null, skewNote: skew.note, stats,
|
|
330
|
+
skewSince: skew.points[0] ? day(skew.points[0].date) : null, skewNote: skew.note, stats: measured, staleNote: staleText,
|
|
236
331
|
since, oneYearNote, asOf: latestDate, warnings,
|
|
237
332
|
};
|
|
238
333
|
}
|
|
@@ -251,6 +346,16 @@ export interface RichCheapRow {
|
|
|
251
346
|
iv30: number | null;
|
|
252
347
|
date: string | null;
|
|
253
348
|
method: IvMethod | null;
|
|
349
|
+
/** Prices a trade close was solved from, when the server says (see `IvCloseBasis`). */
|
|
350
|
+
basis: IvCloseBasis | null;
|
|
351
|
+
/**
|
|
352
|
+
* The reading trails the symbol's newest session by more than `STALE_AFTER_SESSIONS`
|
|
353
|
+
* sessions. It is shown, marked stale, and left out of IV/HV, rank, percentile and
|
|
354
|
+
* Rich/Cheap, and so are its skew and IV1Y rank when they are as old.
|
|
355
|
+
*/
|
|
356
|
+
stale: boolean;
|
|
357
|
+
/** Sessions between the reading and the symbol's newest session. */
|
|
358
|
+
sessionsBehind: number | null;
|
|
254
359
|
/** The underlying price the reading was taken at: the session's close, or the spot at the quote capture. */
|
|
255
360
|
spot: number | null;
|
|
256
361
|
rank: number | null;
|
|
@@ -265,10 +370,18 @@ export interface RichCheapRow {
|
|
|
265
370
|
* series has no reading that session, or there is no IV1Y beside it to describe.
|
|
266
371
|
*/
|
|
267
372
|
iv1yPercentile: number | null;
|
|
268
|
-
|
|
269
|
-
|
|
373
|
+
/** The 25-delta risk reversal at 30 days: call IV minus put IV, so positive means calls are richer, as OVDV signs it. */
|
|
374
|
+
riskReversal: number | null;
|
|
375
|
+
/** Session of the quote capture the risk reversal comes from. */
|
|
270
376
|
skewDate: string | null;
|
|
377
|
+
/**
|
|
378
|
+
* Realized volatility on the reading's own date, so the row reads as IV30 over HV;
|
|
379
|
+
* the newest HV for a stale reading or one with no HV on its date.
|
|
380
|
+
*/
|
|
271
381
|
hv: number | null;
|
|
382
|
+
/** Session of the HV shown. */
|
|
383
|
+
hvDate: string | null;
|
|
384
|
+
/** IV30 over the HV of its own date; empty for a stale reading. */
|
|
272
385
|
ivHv: number | null;
|
|
273
386
|
verdict: RichCheap;
|
|
274
387
|
}
|
|
@@ -280,24 +393,29 @@ export interface RichCheapRow {
|
|
|
280
393
|
* is measured on. A group the rows disagree on gets its own column.
|
|
281
394
|
*/
|
|
282
395
|
export interface RichCheapDates {
|
|
283
|
-
reading: { date: string; method: IvMethod } | null;
|
|
396
|
+
reading: { date: string; method: IvMethod; basis: IvCloseBasis | null } | null;
|
|
284
397
|
/** The trade close every ranked row is ranked on. */
|
|
285
398
|
rank: string | null;
|
|
286
399
|
/** Ranked rows are ranked on different closes. */
|
|
287
400
|
rankApart: boolean;
|
|
288
|
-
/** Some
|
|
401
|
+
/** Some risk reversal comes from a different session than its row's reading. */
|
|
289
402
|
skewApart: boolean;
|
|
403
|
+
/** Some HV is dated on a different session than the IV it sits beside. */
|
|
404
|
+
hvApart: boolean;
|
|
290
405
|
}
|
|
291
406
|
export function richCheapDates(rows: readonly RichCheapRow[]): RichCheapDates {
|
|
292
407
|
const read = rows.filter((row) => row.date && row.method);
|
|
293
408
|
const ranked = rows.filter((row) => row.rankDate);
|
|
294
409
|
const first = read[0], firstRanked = ranked[0];
|
|
295
410
|
return {
|
|
296
|
-
|
|
297
|
-
|
|
411
|
+
// A stale row's own date is said in its row, never once for all.
|
|
412
|
+
// The label the rows would be dated with, not the raw basis: a mid and a mixed close are both "close mid".
|
|
413
|
+
reading: first && read.every((row) => row.date === first.date && readingSource(row.method, row.basis) === readingSource(first.method, first.basis) && !row.stale)
|
|
414
|
+
? { date: first.date!, method: first.method!, basis: first.basis } : null,
|
|
298
415
|
rank: firstRanked && ranked.every((row) => row.rankDate === firstRanked.rankDate) ? firstRanked.rankDate : null,
|
|
299
416
|
rankApart: !!firstRanked && ranked.some((row) => row.rankDate !== firstRanked.rankDate),
|
|
300
|
-
skewApart: rows.some((row) => row.
|
|
417
|
+
skewApart: rows.some((row) => row.riskReversal != null && row.skewDate !== row.date),
|
|
418
|
+
hvApart: rows.some((row) => row.hv != null && row.hvDate != null && row.hvDate !== row.date),
|
|
301
419
|
};
|
|
302
420
|
}
|
|
303
421
|
|
|
@@ -307,22 +425,34 @@ export function verdictFor(percentile: number | null): RichCheap {
|
|
|
307
425
|
return percentile >= RICH_PERCENTILE ? "rich" : percentile <= CHEAP_PERCENTILE ? "cheap" : "fair";
|
|
308
426
|
}
|
|
309
427
|
|
|
310
|
-
export function projectRichCheap(rows: readonly IvScreenRow[], hv: ReadonlyMap<string,
|
|
428
|
+
export function projectRichCheap(rows: readonly IvScreenRow[], hv: ReadonlyMap<string, ScreenHv | null> = new Map()): RichCheapRow[] {
|
|
311
429
|
return rows.map((row) => {
|
|
312
430
|
const latest = row.latest;
|
|
313
431
|
const iv30 = latest?.iv30 ?? null;
|
|
314
|
-
const
|
|
315
|
-
|
|
432
|
+
const series = hv.get(row.symbol) ?? null;
|
|
433
|
+
// The newest session the symbol reaches: its prices, its backfill and every reading it has.
|
|
434
|
+
const newest = newestSession(row.coverage?.backfilledThrough, latest?.date, row.iv30?.date, row.iv90?.date, row.iv365?.date,
|
|
435
|
+
row.skew?.date, series?.date);
|
|
436
|
+
const { stale, sessionsBehind: behind } = staleness(latest?.date, newest);
|
|
437
|
+
// A rank on a close that old ranks an old figure; so does a skew.
|
|
438
|
+
const rankStale = stale || staleness(row.iv30?.date, newest).stale;
|
|
439
|
+
const skewStale = row.skew ? staleness(row.skew.date, newest).stale : false;
|
|
440
|
+
const percentile = rankStale ? null : row.iv30?.percentile ?? null;
|
|
441
|
+
// HV on the reading's own date: today's HV beside an old IV says nothing about it.
|
|
442
|
+
const atReading = series && !stale && latest?.date ? hvOn(series, latest.date) : null;
|
|
443
|
+
const shown = atReading ?? (series?.value != null && series.date ? { date: series.date, value: series.value } : null);
|
|
316
444
|
return {
|
|
317
445
|
symbol: row.symbol, status: row.coverageStatus ?? row.status, queuedAt: row.coverage?.addedAt ?? null,
|
|
318
446
|
backfilledThrough: row.coverage?.backfilledThrough ?? null, warning: row.warnings?.[0] ?? null,
|
|
319
|
-
iv30, date: latest?.date ?? null, method: latest?.method ?? null,
|
|
320
|
-
|
|
447
|
+
iv30, date: latest?.date ?? null, method: latest?.method ?? null, basis: latest?.basis ?? null,
|
|
448
|
+
stale, sessionsBehind: behind, spot: latest?.spot ?? null,
|
|
449
|
+
rank: rankStale ? null : row.iv30?.rank ?? null, percentile, rankDate: rankStale ? null : row.iv30?.date ?? null,
|
|
321
450
|
termSlope: iv30 != null && latest?.iv90 != null ? iv30 - latest.iv90 : null,
|
|
322
451
|
iv1y: latest?.iv365 ?? null,
|
|
323
|
-
iv1yPercentile: latest?.iv365 != null && row.iv365 && row.iv365.date === row.iv30?.date ? row.iv365.percentile : null,
|
|
324
|
-
skew: row.skew
|
|
325
|
-
|
|
452
|
+
iv1yPercentile: !rankStale && latest?.iv365 != null && row.iv365 && row.iv365.date === row.iv30?.date ? row.iv365.percentile : null,
|
|
453
|
+
riskReversal: skewStale || !row.skew ? null : row.skew.call25 - row.skew.put25, skewDate: skewStale ? null : row.skew?.date ?? null,
|
|
454
|
+
hv: shown?.value ?? null, hvDate: shown?.date ?? null,
|
|
455
|
+
ivHv: iv30 != null && atReading != null && atReading.value > 0 ? iv30 / atReading.value : null,
|
|
326
456
|
verdict: verdictFor(percentile),
|
|
327
457
|
};
|
|
328
458
|
});
|
|
@@ -375,6 +505,11 @@ export function screenCoverage(
|
|
|
375
505
|
missing: pending.filter((row) => row.iv30 == null && row.date == null).map((row) => row.symbol) };
|
|
376
506
|
}
|
|
377
507
|
|
|
508
|
+
/** The symbols whose reading is stale, dated, and what that leaves out of the screen. */
|
|
509
|
+
export function screenStaleNote(rows: readonly RichCheapRow[]): string | null {
|
|
510
|
+
return staleReadingsNote("IV/HV, rank and Rich/Cheap", rows.flatMap((row) => row.stale && row.date ? [{ label: row.symbol, date: row.date }] : []));
|
|
511
|
+
}
|
|
512
|
+
|
|
378
513
|
/**
|
|
379
514
|
* The VCA pane's notices: the report's wording without a time to come back
|
|
380
515
|
* at, since the pane checks again by itself, and a count of ready symbols with
|
|
@@ -383,7 +518,8 @@ export function screenCoverage(
|
|
|
383
518
|
export function screenPaneNotices(rows: readonly RichCheapRow[], now = Date.now()): string[] {
|
|
384
519
|
const empty = rows.filter((row) => row.status === "ready" && row.iv30 == null).length;
|
|
385
520
|
return [...screenCoverage(rows, { now, retry: false }).notes,
|
|
386
|
-
...(empty ? [`${empty} symbol${empty === 1 ? " has" : "s have"} no stored implied volatility yet.`] : [])
|
|
521
|
+
...(empty ? [`${empty} symbol${empty === 1 ? " has" : "s have"} no stored implied volatility yet.`] : []),
|
|
522
|
+
...[screenStaleNote(rows)].filter((note): note is string => !!note)];
|
|
387
523
|
}
|
|
388
524
|
|
|
389
525
|
export const VCA_PRESETS = {
|
|
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
2
2
|
import { listingIdentity } from "../shared/ticker-request";
|
|
3
3
|
import { DataTableView, EmptyState, PaneStatusBody, QueryBar, usePaneFooter, usePaneNoticeFooter, usePaneTicker, type DataTableColumn } from "../../../components";
|
|
4
4
|
import { instrumentFromTicker } from "../../../market-data/request-types";
|
|
5
|
+
import { fxVolatilityNote } from "../../../market-data/options-alternatives";
|
|
5
6
|
import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
6
7
|
import { useAsyncResource } from "../../../react/async-resource";
|
|
7
8
|
import { useShortcut } from "../../../react/input";
|
|
@@ -24,7 +25,7 @@ const HV_OPTIONS = HV_WINDOWS.map((window) => ({ value: String(window), label: `
|
|
|
24
25
|
const STAT_COLUMNS: DataTableColumn[] = [
|
|
25
26
|
{ id: "label", label: "Measure", width: 16, align: "left" },
|
|
26
27
|
{ id: "value", label: "Current", width: 9, align: "right" },
|
|
27
|
-
{ id: "date", label: "As of", width:
|
|
28
|
+
{ id: "date", label: "As of", width: 20, align: "left" },
|
|
28
29
|
{ id: "low", label: "52w low", width: 9, align: "right" },
|
|
29
30
|
{ id: "high", label: "52w high", width: 9, align: "right" },
|
|
30
31
|
{ id: "rank", label: "Rank", width: 6, align: "right" },
|
|
@@ -54,7 +55,9 @@ export function IvHistoryPane({ width, height, focused }: PaneProps) {
|
|
|
54
55
|
]);
|
|
55
56
|
return { payload, prices };
|
|
56
57
|
}, [instrumentKey]);
|
|
57
|
-
|
|
58
|
+
// An FX pair has no implied volatility history: the pane says what stands in, and asks for nothing.
|
|
59
|
+
const fxNote = symbol ? fxVolatilityNote(symbol, ticker?.metadata.exchange) : null;
|
|
60
|
+
const resource = useAsyncResource(instrument && !fxNote ? loader : null);
|
|
58
61
|
useEffect(() => () => controller.current?.abort(), [loader]);
|
|
59
62
|
useAutoRefresh(resource.updatedAt, resource.load);
|
|
60
63
|
const model = useMemo(() => resource.data
|
|
@@ -66,7 +69,7 @@ export function IvHistoryPane({ width, height, focused }: PaneProps) {
|
|
|
66
69
|
const timer = setTimeout(() => { void resource.reload(); }, 60_000);
|
|
67
70
|
return () => clearTimeout(timer);
|
|
68
71
|
}, [model?.status, resource.updatedAt]);
|
|
69
|
-
const notices = [identityError, resource.error, resource.data?.prices.error, ...(model?.warnings ?? [])]
|
|
72
|
+
const notices = [identityError, resource.error, resource.data?.prices.error, ...(model?.warnings ?? []), model?.staleNote]
|
|
70
73
|
.filter((value): value is string => !!value);
|
|
71
74
|
usePaneNoticeFooter({ registrationId: "iv-history-notices", notices: [...new Set(notices)], focused });
|
|
72
75
|
const cycleLookback = () => setLookback(lookback === "1Y" ? "2Y" : lookback === "2Y" ? "ALL" : "1Y");
|
|
@@ -104,8 +107,9 @@ export function IvHistoryPane({ width, height, focused }: PaneProps) {
|
|
|
104
107
|
const tone = row.percentile != null && (row.percentile >= 80 || row.percentile <= 20) ? colors.warning : colors.text;
|
|
105
108
|
switch (id) {
|
|
106
109
|
case "label": return { text: row.label, color: colors.textBright };
|
|
107
|
-
case "value": return { text: formatStat(row.value, row.unit), color: row.id.startsWith("iv30") ? colors.warning : colors.textBright };
|
|
108
|
-
|
|
110
|
+
case "value": return { text: formatStat(row.value, row.unit), color: row.stale ? colors.textDim : row.id.startsWith("iv30") ? colors.warning : colors.textBright };
|
|
111
|
+
// A stale reading says so in place of how it was taken: it is no current figure either way.
|
|
112
|
+
case "date": return { text: row.date ? `${row.date} ${row.stale ? "stale" : statSource(row)}` : "--", color: row.stale ? colors.warning : colors.textDim };
|
|
109
113
|
case "low": return { text: formatStat(row.low, row.unit), color: colors.textDim };
|
|
110
114
|
case "high": return { text: formatStat(row.high, row.unit), color: colors.textDim };
|
|
111
115
|
case "rank": return { text: formatRank(row.rank), color: tone };
|
|
@@ -119,7 +123,7 @@ export function IvHistoryPane({ width, height, focused }: PaneProps) {
|
|
|
119
123
|
{ id: "lookback", label: "Lookback", value: lookback, options: LOOKBACKS, onChange: setLookback },
|
|
120
124
|
{ id: "realized", label: "Realized", value: String(hvWindow), options: HV_OPTIONS, onChange: setHvWindow },
|
|
121
125
|
]} />
|
|
122
|
-
{!symbol ? <EmptyState title="Select a ticker." /> : <PaneStatusBody subject="implied volatility history" loading={resource.loading && !model}
|
|
126
|
+
{!symbol ? <EmptyState title="Select a ticker." /> : fxNote ? <PaneStatusBody empty emptyTitle={fxNote} /> : <PaneStatusBody subject="implied volatility history" loading={resource.loading && !model}
|
|
123
127
|
error={!model ? resource.error ?? identityError ?? null : null} empty={!!model && !model.iv30.length && !model.quoteIv30.length}
|
|
124
128
|
emptyTitle={model?.status === "queued" || model?.status === "backfilling" ? `Backfilling ${symbol} implied volatility history...` : undefined}>
|
|
125
129
|
{model ? <>
|