gloomberb 0.18.1 → 0.18.3
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/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +106 -5
- package/src/api-client/commodity-balances.ts +6 -0
- package/src/api-client/company-kpis.ts +9 -0
- package/src/api-client/crypto-markets.ts +9 -0
- package/src/api-client/data.ts +21 -5
- package/src/api-client/equity-screener.ts +2 -0
- package/src/api-client/index.ts +2 -0
- package/src/api-client/request.ts +5 -3
- package/src/api-client/telemetry.ts +17 -0
- package/src/api-client/types.ts +5 -0
- package/src/app/global-shortcuts.ts +1 -16
- package/src/app/pane-runtime/plugin-bindings.ts +3 -1
- package/src/app/pane-runtime/ticker-open-runtime.ts +17 -5
- package/src/app/runtime/plugin-bindings.ts +6 -2
- package/src/app/runtime/update.ts +28 -8
- package/src/architecture/pane-tabs-allowlist.json +65 -0
- package/src/brokers/logos.ts +66 -0
- package/src/brokers/profile-form.ts +3 -0
- package/src/brokers/remote-broker-adapter.ts +13 -3
- package/src/capabilities/index.ts +1 -1
- package/src/capabilities/service-capabilities.ts +7 -1
- package/src/cli/base-converter.ts +60 -18
- package/src/cli/commands/market.ts +354 -187
- package/src/cli/commands/news-search.ts +35 -8
- package/src/cli/commands/system.ts +35 -1
- package/src/cli/commands/ticker.ts +167 -60
- package/src/cli/currency-hints.ts +27 -0
- package/src/cli/fx-pair.ts +41 -4
- package/src/cli/glossary.ts +57 -4
- package/src/cli/help.ts +1 -1
- package/src/cli/helpers.ts +36 -3
- package/src/cli/index.ts +12 -1
- package/src/cli/issuer-membership.ts +103 -0
- package/src/cli/known-gaps.ts +37 -1
- package/src/cli/listing-arg.ts +51 -3
- package/src/cli/name-entries.ts +51 -12
- package/src/cli/pane-functions/capabilities.ts +2 -0
- package/src/cli/pane-functions/catalog.ts +19 -1
- package/src/cli/pane-functions/discovery.ts +15 -1
- package/src/cli/pane-functions/dom.ts +11 -5
- package/src/cli/pane-functions/freshness.ts +4 -4
- package/src/cli/pane-functions/function-help.ts +27 -3
- package/src/cli/pane-functions/headless.ts +31 -11
- package/src/cli/pane-functions/index.ts +67 -7
- package/src/cli/pane-functions/lock-note.ts +7 -4
- package/src/cli/pane-functions/resolver.ts +21 -6
- package/src/cli/quote-answer.ts +179 -0
- package/src/cli/report-tables.ts +136 -18
- package/src/cli/result.ts +12 -4
- package/src/cli/series-search.ts +2 -1
- package/src/cli/symbol-list.ts +47 -11
- package/src/cli/types.ts +4 -0
- package/src/components/chart/composite/composite-chart.tsx +25 -7
- package/src/components/chart/composite/drawing-store.ts +26 -6
- package/src/components/chart/composite/format.ts +13 -1
- package/src/components/chart/composite/interactions.ts +2 -1
- package/src/components/chart/composite/level-labels.ts +30 -0
- package/src/components/chart/composite/levels.ts +17 -6
- package/src/components/chart/composite/panel-surface.tsx +69 -8
- package/src/components/chart/composite/types.ts +6 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +1 -1
- package/src/components/command-bar/routes/root/command-items.ts +32 -6
- package/src/components/command-bar/routes/ticker-search/actions.ts +6 -0
- package/src/components/data-table/view.tsx +23 -3
- package/src/components/index.ts +3 -0
- package/src/components/layout/detached-pane-shell.tsx +1 -0
- package/src/components/layout/floating-pane.tsx +1 -0
- package/src/components/layout/header.tsx +228 -55
- package/src/components/layout/pane/content.tsx +16 -14
- package/src/components/layout/pane/footer/index.tsx +22 -3
- package/src/components/layout/pane/footer/model.ts +99 -4
- package/src/components/layout/pane/header-tabs.tsx +15 -1
- package/src/components/layout/pane/header.tsx +10 -3
- package/src/components/layout/pane/index.tsx +1 -0
- package/src/components/layout/pane/pane-tabs.tsx +9 -1
- package/src/components/onboarding/onboarding-frame.tsx +9 -11
- package/src/components/onboarding/use-onboarding-broker-form.ts +5 -2
- package/src/components/onboarding/wizard-model.ts +10 -3
- package/src/components/ui/button.tsx +11 -4
- package/src/components/ui/choice-dialog.tsx +8 -5
- package/src/components/ui/display.tsx +11 -6
- package/src/components/ui/fields.tsx +100 -20
- package/src/components/ui/icon.tsx +6 -1
- package/src/components/ui/list-row-lines.tsx +87 -0
- package/src/components/ui/list-view.tsx +14 -0
- package/src/components/ui/logo-mark.tsx +64 -0
- package/src/components/ui/number-step.ts +54 -0
- package/src/components/ui/select-button.tsx +5 -4
- package/src/components/ui/select-field.tsx +3 -3
- package/src/components/ui/stat-grid.tsx +28 -3
- package/src/components/ui/status.tsx +20 -4
- package/src/components/ui/tabs.tsx +9 -1
- package/src/components/ui/toggle.tsx +17 -3
- package/src/core/app-services.ts +4 -0
- package/src/core/state/app/instrument.ts +8 -3
- package/src/data/config/store/normalize.ts +4 -1
- package/src/data/plugin-cache.ts +32 -6
- package/src/docs/snapshot.json +5 -5
- package/src/i18n/es.ts +31 -3
- package/src/i18n/ja.ts +31 -3
- package/src/i18n/ko.ts +31 -3
- package/src/i18n/zh-cn.ts +32 -4
- package/src/i18n/zh-tw.ts +32 -4
- package/src/market-data/commodity-names.ts +80 -0
- package/src/market-data/coordinator/auxiliary.ts +3 -1
- package/src/market-data/coordinator/chart.ts +3 -1
- package/src/market-data/futures-board.ts +38 -1
- package/src/market-data/futures-contract.ts +91 -0
- package/src/market-data/fx-closes.ts +78 -1
- package/src/market-data/market/change-basis.ts +219 -0
- package/src/market-data/market/format.ts +28 -12
- package/src/market-data/market/freshness.ts +38 -0
- package/src/market-data/market/status.ts +55 -9
- package/src/market-data/market/year-to-date.ts +46 -0
- package/src/market-data/quotes/contributions.ts +12 -0
- package/src/market-data/quotes/delisting.ts +34 -0
- package/src/market-data/quotes/live-valuation.ts +23 -0
- package/src/market-data/quotes/resolution.ts +8 -2
- package/src/market-data/tax-equivalent.ts +10 -2
- package/src/notifications/app-notifier.ts +2 -2
- package/src/notifications/notification-log.ts +8 -6
- package/src/plugins/builtin/alerts/alert-engine.ts +3 -1
- package/src/plugins/builtin/alerts/cli.ts +26 -18
- package/src/plugins/builtin/alerts/events.ts +1 -0
- package/src/plugins/builtin/alerts/headless.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +13 -4
- package/src/plugins/builtin/alerts/research-rules.ts +1 -0
- package/src/plugins/builtin/analytics/risk-pane.tsx +10 -7
- package/src/plugins/builtin/attention/pane.tsx +16 -14
- package/src/plugins/builtin/attention/trending.tsx +8 -6
- package/src/plugins/builtin/awards/pane.tsx +6 -6
- package/src/plugins/builtin/backtest/client.ts +5 -2
- package/src/plugins/builtin/backtest/export.ts +1 -1
- package/src/plugins/builtin/backtest/window.ts +16 -3
- package/src/plugins/builtin/broker-manager/add-flow.tsx +40 -8
- package/src/plugins/builtin/broker-manager/detail.tsx +26 -1
- package/src/plugins/builtin/broker-manager/index.tsx +20 -9
- package/src/plugins/builtin/broker-manager/model.ts +5 -0
- package/src/plugins/builtin/broker-manager/pane-actions.tsx +8 -1
- package/src/plugins/builtin/broker-manager/table.ts +5 -1
- package/src/plugins/builtin/broker-trading/account-picker.tsx +91 -0
- package/src/plugins/builtin/broker-trading/chart-levels.ts +54 -0
- package/src/plugins/builtin/broker-trading/command.ts +103 -0
- package/src/plugins/builtin/broker-trading/controller.ts +688 -0
- package/src/plugins/builtin/broker-trading/dock.tsx +184 -0
- package/src/plugins/builtin/broker-trading/headless.ts +76 -0
- package/src/plugins/builtin/broker-trading/index.tsx +77 -0
- package/src/plugins/builtin/broker-trading/order-feed.ts +383 -0
- package/src/plugins/builtin/broker-trading/orders-data.ts +133 -0
- package/src/plugins/builtin/broker-trading/orders.tsx +437 -0
- package/src/plugins/builtin/broker-trading/overview-orders.tsx +121 -0
- package/src/plugins/builtin/broker-trading/pane-model.ts +207 -0
- package/src/plugins/builtin/broker-trading/pane.tsx +507 -0
- package/src/plugins/builtin/broker-trading/strategy.ts +153 -0
- package/src/plugins/builtin/broker-trading/ticket-parts.tsx +117 -0
- package/src/plugins/builtin/broker-trading/ticket.tsx +578 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +1 -1
- package/src/plugins/builtin/buildout/format.ts +1 -0
- package/src/plugins/builtin/buildout/model/index.ts +16 -7
- package/src/plugins/builtin/buildout/pane/index.tsx +11 -7
- package/src/plugins/builtin/buildout/pane/status.tsx +3 -3
- package/src/plugins/builtin/catalysts/client.ts +2 -2
- package/src/plugins/builtin/catalysts/pane.tsx +3 -3
- package/src/plugins/builtin/chart-composer/cli-options.ts +19 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +2 -1
- package/src/plugins/builtin/chart-composer/headless-schema.ts +3 -3
- package/src/plugins/builtin/chart-composer/headless.ts +2 -1
- package/src/plugins/builtin/chart-composer/option-range.ts +39 -0
- package/src/plugins/builtin/chart-composer/pane.tsx +32 -11
- package/src/plugins/builtin/chart-composer/presets.ts +2 -2
- package/src/plugins/builtin/chart-composer/series-catalog.ts +4 -2
- package/src/plugins/builtin/chart-composer/shortcuts.ts +3 -3
- package/src/plugins/builtin/chart-composer/viewport-labels.ts +5 -2
- package/src/plugins/builtin/chat/index.tsx +1 -1
- package/src/plugins/builtin/chat/status-widget.tsx +1 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +6 -1
- package/src/plugins/builtin/chat/unread-inbox.ts +1 -1
- package/src/plugins/builtin/commodity-balances/client.ts +6 -4
- package/src/plugins/builtin/commodity-balances/headless.ts +41 -7
- package/src/plugins/builtin/commodity-balances/index.tsx +19 -4
- package/src/plugins/builtin/commodity-balances/model.ts +22 -0
- package/src/plugins/builtin/commodity-balances/pane.tsx +39 -9
- package/src/plugins/builtin/company-attention/app-rank.tsx +4 -5
- package/src/plugins/builtin/company-attention/pane.tsx +13 -11
- package/src/plugins/builtin/company-kpis/client.ts +3 -2
- package/src/plugins/builtin/company-kpis/headless.ts +5 -7
- package/src/plugins/builtin/company-kpis/model.ts +85 -2
- package/src/plugins/builtin/company-kpis/pane.tsx +30 -15
- package/src/plugins/builtin/composite-plugins.ts +3 -2
- package/src/plugins/builtin/correlation/headless.ts +2 -1
- package/src/plugins/builtin/correlation/relationship/model.ts +1 -0
- package/src/plugins/builtin/correlation/settings.ts +1 -0
- package/src/plugins/builtin/cot/headless.ts +1 -1
- package/src/plugins/builtin/cot/index.tsx +1 -1
- package/src/plugins/builtin/cot/model.ts +35 -12
- package/src/plugins/builtin/cot/pane.tsx +4 -3
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +2 -1
- package/src/plugins/builtin/credit-conditions/index.tsx +2 -1
- package/src/plugins/builtin/credit-documents/pane.tsx +8 -7
- package/src/plugins/builtin/crypto-board/client.ts +33 -2
- package/src/plugins/builtin/crypto-board/headless.ts +5 -2
- package/src/plugins/builtin/crypto-board/model.ts +28 -8
- package/src/plugins/builtin/debt-maturities/pane.tsx +4 -3
- package/src/plugins/builtin/dividend-yield/headless.ts +30 -8
- package/src/plugins/builtin/dividend-yield/provider-client.ts +3 -1
- package/src/plugins/builtin/doe/pane.tsx +1 -1
- package/src/plugins/builtin/earnings-calls/headless.ts +4 -4
- package/src/plugins/builtin/earnings-calls/lock-note.ts +12 -3
- package/src/plugins/builtin/econ/calendar-model.ts +35 -0
- package/src/plugins/builtin/econ/index.tsx +31 -4
- package/src/plugins/builtin/econ/print-watch.ts +279 -0
- package/src/plugins/builtin/equity-screener/headless.ts +30 -5
- package/src/plugins/builtin/equity-screener/model.ts +68 -2
- package/src/plugins/builtin/equity-screener/pane.tsx +129 -119
- package/src/plugins/builtin/estimate-revisions/headless.ts +3 -2
- package/src/plugins/builtin/estimate-revisions/lock-note.ts +29 -7
- package/src/plugins/builtin/exposure/client.ts +4 -1
- package/src/plugins/builtin/exposure/editor.tsx +3 -3
- package/src/plugins/builtin/exposure/evidence.ts +4 -1
- package/src/plugins/builtin/exposure/holdings.ts +3 -1
- package/src/plugins/builtin/exposure/pane.tsx +43 -21
- package/src/plugins/builtin/filing-events/index.tsx +9 -3
- package/src/plugins/builtin/futures/table.tsx +6 -3
- package/src/plugins/builtin/futures-curve/pane.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +4 -1
- package/src/plugins/builtin/gpu/changes.tsx +10 -10
- package/src/plugins/builtin/gpu/equities.tsx +4 -1
- package/src/plugins/builtin/gpu/history.tsx +4 -2
- package/src/plugins/builtin/gpu/pane.tsx +5 -3
- package/src/plugins/builtin/holders/client.ts +2 -1
- package/src/plugins/builtin/holders/headless.ts +2 -1
- package/src/plugins/builtin/holders/thirteenf-match.ts +3 -1
- package/src/plugins/builtin/insider/base-values.ts +76 -0
- package/src/plugins/builtin/insider/client.ts +3 -0
- package/src/plugins/builtin/insider/headless.ts +48 -10
- package/src/plugins/builtin/insider/index.tsx +17 -8
- package/src/plugins/builtin/insider/issuer.ts +34 -4
- package/src/plugins/builtin/insider/signals-client.ts +2 -2
- package/src/plugins/builtin/insider/table-model.ts +35 -13
- package/src/plugins/builtin/insider/table.tsx +5 -2
- package/src/plugins/builtin/iv-history/events.ts +145 -0
- package/src/plugins/builtin/iv-history/format.ts +3 -0
- package/src/plugins/builtin/iv-history/headless.ts +91 -7
- package/src/plugins/builtin/jobs/headless.ts +2 -2
- package/src/plugins/builtin/jobs/lock-note.ts +14 -3
- package/src/plugins/builtin/jodi/pane.tsx +4 -4
- package/src/plugins/builtin/market-halts/model.ts +31 -6
- package/src/plugins/builtin/market-halts/pane.tsx +3 -0
- package/src/plugins/builtin/market-movers/client.ts +4 -3
- package/src/plugins/builtin/market-movers/screener.ts +4 -3
- package/src/plugins/builtin/market-valuation/pane.tsx +2 -1
- package/src/plugins/builtin/members/pane.tsx +8 -3
- package/src/plugins/builtin/news/wire/feed-config.ts +29 -12
- package/src/plugins/builtin/news/wire/index.tsx +7 -2
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +11 -3
- package/src/plugins/builtin/news/wire/rss/cli.ts +3 -2
- package/src/plugins/builtin/news/wire/rss/source.ts +13 -4
- package/src/plugins/builtin/notes/cli.ts +1 -1
- package/src/plugins/builtin/notes/export.ts +17 -6
- package/src/plugins/builtin/notes/files.ts +16 -5
- package/src/plugins/builtin/options/enrichment-model.ts +10 -9
- package/src/plugins/builtin/options/footer.ts +25 -20
- package/src/plugins/builtin/options/live-quotes.ts +33 -18
- package/src/plugins/builtin/options/model-spot.ts +70 -4
- package/src/plugins/builtin/options/view.tsx +118 -35
- package/src/plugins/builtin/options-calculator/pane.tsx +7 -4
- package/src/plugins/builtin/options-positioning/headless.ts +11 -3
- package/src/plugins/builtin/options-positioning/model.ts +24 -0
- package/src/plugins/builtin/options-positioning/pane.tsx +10 -12
- package/src/plugins/builtin/options-scenario/client.ts +19 -1
- package/src/plugins/builtin/options-scenario/headless.ts +13 -6
- package/src/plugins/builtin/options-scenario/pane.tsx +54 -11
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +5 -4
- package/src/plugins/builtin/portfolio-list/account-metrics.ts +3 -0
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +8 -5
- package/src/plugins/builtin/portfolio-list/cli/render.ts +34 -10
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/data.ts +23 -1
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +33 -14
- package/src/plugins/builtin/portfolio-list/settings.ts +20 -2
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +12 -0
- package/src/plugins/builtin/portfolio-list/summary/totals.ts +24 -3
- package/src/plugins/builtin/power/client.ts +2 -2
- package/src/plugins/builtin/power/pane.tsx +36 -31
- package/src/plugins/builtin/primary-dealers/client.ts +137 -62
- package/src/plugins/builtin/primary-dealers/headless.ts +30 -5
- package/src/plugins/builtin/primary-dealers/index.tsx +3 -2
- package/src/plugins/builtin/primary-dealers/model.ts +179 -129
- package/src/plugins/builtin/primary-dealers/pane.tsx +135 -15
- package/src/plugins/builtin/research/analyst-model.ts +5 -1
- package/src/plugins/builtin/reverse-dcf/client.ts +2 -1
- package/src/plugins/builtin/reverse-dcf/model.ts +4 -3
- package/src/plugins/builtin/risk-factors/index.tsx +12 -3
- package/src/plugins/builtin/scanner/feed.ts +6 -1
- package/src/plugins/builtin/scanner/headless.ts +1 -1
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/client.ts +2 -1
- package/src/plugins/builtin/sectors/client.ts +4 -3
- package/src/plugins/builtin/series-inventory.ts +1 -0
- package/src/plugins/builtin/shared/cloud-denied.ts +7 -6
- package/src/plugins/builtin/shared/cloud-resource.ts +15 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +10 -11
- package/src/plugins/builtin/shared/free-routes.ts +24 -0
- package/src/plugins/builtin/shared/headless-options.ts +11 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +89 -7
- package/src/plugins/builtin/shared/research-trading.tsx +64 -0
- package/src/plugins/builtin/shared/series-list-detail.tsx +3 -1
- package/src/plugins/builtin/shared/volatility/options.ts +2 -1
- package/src/plugins/builtin/social-mentions/client.ts +2 -1
- package/src/plugins/builtin/social-mentions/headless.ts +1 -1
- package/src/plugins/builtin/social-mentions/lock-note.ts +1 -1
- package/src/plugins/builtin/themes/headless.ts +8 -2
- package/src/plugins/builtin/themes/model.ts +43 -10
- package/src/plugins/builtin/themes/pane.tsx +8 -6
- package/src/plugins/builtin/ticker-detail/overview/model.ts +44 -10
- package/src/plugins/builtin/ticker-detail/overview/option-contract.ts +64 -0
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +82 -21
- package/src/plugins/builtin/ticker-detail/pane.tsx +21 -14
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +5 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/card.tsx +37 -8
- package/src/plugins/builtin/time-sales/pane.tsx +2 -2
- package/src/plugins/builtin/trace-bonds/client.ts +14 -3
- package/src/plugins/builtin/trace-bonds/headless.ts +1 -1
- package/src/plugins/builtin/vol-surface/client.ts +23 -3
- package/src/plugins/builtin/vol-surface/headless.ts +33 -14
- package/src/plugins/builtin/vol-surface/model.ts +30 -10
- package/src/plugins/builtin/vol-surface/pane.tsx +35 -15
- package/src/plugins/builtin/vol-surface/stored.ts +34 -3
- package/src/plugins/builtin/vol-surface/tables.ts +14 -0
- package/src/plugins/builtin-editorial.ts +1 -1
- package/src/plugins/compat.ts +3 -0
- package/src/plugins/external-runtime.ts +5 -0
- package/src/plugins/loader.ts +6 -0
- package/src/plugins/plugin-icon.ts +61 -0
- package/src/plugins/registry/host-actions.ts +2 -1
- package/src/plugins/registry/index.ts +9 -1
- package/src/plugins/runtime/context.tsx +5 -0
- package/src/plugins/runtime/index.tsx +3 -0
- package/src/portfolio/column-values.ts +13 -4
- package/src/portfolio/cost-fx.ts +5 -28
- package/src/public/__snapshots__/public-api.test.ts.snap +5 -0
- package/src/public/broker.ts +8 -0
- package/src/public/market-data.ts +5 -0
- package/src/renderers/dom/desktop/control-styles.ts +7 -1
- package/src/renderers/dom/desktop/controls.tsx +40 -8
- package/src/renderers/dom/desktop/icons.tsx +14 -0
- package/src/renderers/dom/desktop/list-view.tsx +51 -19
- package/src/renderers/dom/desktop/menu.tsx +10 -1
- package/src/renderers/dom/desktop/query-bar.tsx +2 -0
- package/src/renderers/dom/desktop/select-field.tsx +8 -6
- package/src/renderers/dom/host/style.ts +6 -1
- package/src/renderers/dom/host/tabs.tsx +22 -5
- package/src/renderers/dom/host/top-surface.ts +127 -0
- package/src/renderers/dom/styles.css +51 -5
- package/src/renderers/electrobun/bun/core-capabilities.ts +21 -1
- package/src/renderers/electrobun/bun/desktop/backend.ts +10 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +210 -30
- package/src/renderers/electrobun/bun/external-plugins.ts +3 -0
- package/src/renderers/electrobun/shared/protocol.ts +2 -0
- package/src/renderers/electrobun/view/broker-remote-client.ts +16 -0
- package/src/renderers/electrobun/view/external-plugins.ts +1 -0
- package/src/renderers/electrobun/view/news-source.ts +4 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +8 -1
- package/src/renderers/opentui/start.tsx +7 -0
- package/src/series/validate.ts +2 -2
- package/src/sources/gloomberb-cloud/index.ts +57 -17
- package/src/sources/gloomberb-cloud/normalizers.ts +4 -0
- package/src/sources/history-result.ts +3 -0
- package/src/sources/provider-errors.ts +3 -1
- package/src/sources/provider-router/history.ts +49 -6
- package/src/sources/provider-router/index.ts +18 -1
- package/src/state/app/activity.ts +13 -0
- package/src/sync/controller.ts +4 -0
- package/src/sync/core-contributors.ts +62 -11
- package/src/sync/local-only.ts +121 -0
- package/src/sync/react.ts +6 -0
- package/src/sync/types.ts +3 -0
- package/src/telemetry/crash-reports.ts +4 -0
- package/src/telemetry/update-failures-node.ts +59 -0
- package/src/telemetry/update-failures.ts +307 -0
- package/src/tickers/company-name-listing.ts +9 -7
- package/src/tickers/company-name.ts +43 -0
- package/src/tickers/listing-fate.ts +57 -8
- package/src/tickers/search/index.ts +24 -3
- package/src/time-series/price-comparison.ts +35 -0
- package/src/time-series/resolve.ts +52 -11
- package/src/time-series/spec.ts +1 -1
- package/src/time-series/types.ts +5 -0
- package/src/types/broker.ts +56 -1
- package/src/types/config.ts +6 -0
- package/src/types/data-provider.ts +11 -1
- package/src/types/financials.ts +23 -0
- package/src/types/headless.ts +19 -2
- package/src/types/instrument.ts +4 -0
- package/src/types/issuer-listings.ts +28 -0
- package/src/types/plugin.ts +24 -6
- package/src/types/price-history.ts +6 -1
- package/src/types/trading.ts +67 -0
- package/src/ui/host.tsx +4 -0
- package/src/updater/failure.ts +163 -0
- package/src/updater/index.ts +87 -23
- package/src/utils/base-currency.ts +108 -0
- package/src/utils/currency-names.ts +128 -0
- package/src/utils/currency-pair.ts +28 -1
- package/src/utils/currency-units.ts +32 -1
- package/src/utils/dom-screenshot.ts +22 -9
- package/src/utils/exchange-notation.generated.ts +360 -0
- package/src/utils/exchanges.ts +15 -322
- package/src/utils/format.ts +31 -1
- package/src/utils/instrument-identity.ts +38 -1
- package/src/utils/options.ts +46 -1
- package/src/utils/price-history.ts +4 -0
- package/src/utils/reported-money.ts +8 -4
- package/src/utils/sec.ts +68 -0
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from "../../../../market-data/market/status";
|
|
21
21
|
import { t } from "../../../../i18n";
|
|
22
22
|
import { isQuoteStaleForCurrentSession } from "../../../../market-data/quotes/freshness";
|
|
23
|
+
import { delistingLabel, delistingStateLabel } from "../../../../market-data/quotes/delisting";
|
|
23
24
|
import { formatQuoteNavAsOf } from "../../../../market-data/quotes/time";
|
|
24
25
|
import { useQuoteFlashDirection } from "../../../../components/quote-flash";
|
|
25
26
|
import { followLiveSparklinePrice } from "../../../../components/price-sparkline/model";
|
|
@@ -89,6 +90,8 @@ interface QuoteStatus {
|
|
|
89
90
|
failed: boolean;
|
|
90
91
|
text: string;
|
|
91
92
|
stale?: boolean;
|
|
93
|
+
/** DELISTED or INACTIVE: the listing no longer trades, and `text` says since when. */
|
|
94
|
+
ended?: string;
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
function resolveQuoteStatus(entry: QueryEntry<Quote> | null, symbol: string, quote: Quote | null | undefined): QuoteStatus {
|
|
@@ -98,6 +101,7 @@ function resolveQuoteStatus(entry: QueryEntry<Quote> | null, symbol: string, quo
|
|
|
98
101
|
? { failed: true, text: `${symbol} not recognized` }
|
|
99
102
|
: { failed: true, text: error.message || "Quote unavailable" };
|
|
100
103
|
}
|
|
104
|
+
if (quote?.delisting) return { failed: true, text: delistingLabel(quote.delisting), stale: true, ended: delistingStateLabel(quote.delisting) };
|
|
101
105
|
if (isQuoteStaleForCurrentSession(quote)) return { failed: true, text: "Stale quote", stale: true };
|
|
102
106
|
if (entry?.phase === "ready") return { failed: false, text: "No quote data" };
|
|
103
107
|
return { failed: false, text: "Loading quote..." };
|
|
@@ -175,8 +179,9 @@ export function QuoteMonitorCard({
|
|
|
175
179
|
// One decimal count per instrument, so streamed ticks never narrow or widen the price column.
|
|
176
180
|
const priceOptions = liveQuoteFormatOptions(quote, currency, assetCategory, cachedFinancials?.quoteMetadata?.instrumentType);
|
|
177
181
|
const priceText = display ? formatMarketPriceWithCurrency(display.price, currency, priceOptions) : "";
|
|
178
|
-
|
|
179
|
-
const
|
|
182
|
+
// A listing that no longer trades shows its last close alone: no move, not even a dash for one.
|
|
183
|
+
const changePercentText = display && !quote?.delisting ? formatPercentRaw(display.changePercent) : "";
|
|
184
|
+
const changeValueText = display && !quote?.delisting ? formatSignedMarketPrice(display.change, priceOptions) : "";
|
|
180
185
|
const priceColumnWidth = Math.max(priceText.length, changePercentText.length + changeValueText.length + 1);
|
|
181
186
|
const nameMaxWidth = Math.max(10, width - priceColumnWidth - (nativePaneChrome ? 5 : 3));
|
|
182
187
|
// The terminal sets the line beside the change, under the symbol (or after it in a
|
|
@@ -187,6 +192,8 @@ export function QuoteMonitorCard({
|
|
|
187
192
|
const extendedParts = display && extended
|
|
188
193
|
? extendedLineParts(extended, formatMarketPriceWithCurrency(extended.price, currency, priceOptions), (text) => displayWidth(text) <= extendedRoom)
|
|
189
194
|
: null;
|
|
195
|
+
// A desktop card says a listing no longer trades on a row of its own.
|
|
196
|
+
const endedRow = nativePaneChrome && quoteFailed && !!quoteStatus.ended && !extendedParts;
|
|
190
197
|
const extendedColor = quoteFailed ? colors.textDim : priceColor(extended?.change ?? 0);
|
|
191
198
|
const extendedDim = !!flashDirection && headlineHolds;
|
|
192
199
|
const sparklineRange = resolvePriceSparklineRange(priceHistory, chartPeriod);
|
|
@@ -254,7 +261,7 @@ export function QuoteMonitorCard({
|
|
|
254
261
|
<Text attributes={TextAttributes.BOLD} fg={symbolFg} bg={symbolBg} style={desktopSymbolStyle}>
|
|
255
262
|
{symbol}
|
|
256
263
|
</Text>
|
|
257
|
-
<Text fg={quoteStatus.failed ? colors.negative : colors.textDim}>{quoteStatus.text}</Text>
|
|
264
|
+
<Text fg={quoteStatus.ended ? colors.warning : quoteStatus.failed ? colors.negative : colors.textDim}>{quoteStatus.text}</Text>
|
|
258
265
|
</Box>
|
|
259
266
|
) : nativePaneChrome ? (
|
|
260
267
|
<Box
|
|
@@ -276,7 +283,7 @@ export function QuoteMonitorCard({
|
|
|
276
283
|
minWidth={0}
|
|
277
284
|
style={{
|
|
278
285
|
gridColumn: "1",
|
|
279
|
-
gridRow: extendedParts ? "1" : "1 / span 2",
|
|
286
|
+
gridRow: extendedParts || endedRow ? "1" : "1 / span 2",
|
|
280
287
|
minWidth: 0,
|
|
281
288
|
overflow: "hidden",
|
|
282
289
|
}}
|
|
@@ -284,8 +291,8 @@ export function QuoteMonitorCard({
|
|
|
284
291
|
<Text attributes={TextAttributes.BOLD} fg={symbolFg} bg={symbolBg} style={desktopSymbolStyle}>
|
|
285
292
|
{symbol}
|
|
286
293
|
</Text>
|
|
287
|
-
{quoteFailed && (
|
|
288
|
-
<Text fg={colors.negative} style={{ fontSize: "var(--gloom-type-ui)", lineHeight: "14px" }}>
|
|
294
|
+
{quoteFailed && !endedRow && (
|
|
295
|
+
<Text fg={quoteStatus.ended ? colors.warning : colors.negative} style={{ fontSize: "var(--gloom-type-ui)", lineHeight: "14px" }}>
|
|
289
296
|
{quoteStatus.text}
|
|
290
297
|
</Text>
|
|
291
298
|
)}
|
|
@@ -332,6 +339,28 @@ export function QuoteMonitorCard({
|
|
|
332
339
|
</Box>
|
|
333
340
|
</Box>
|
|
334
341
|
|
|
342
|
+
{endedRow && (
|
|
343
|
+
// Under the price, across the card, so the date is never cut on a narrow card;
|
|
344
|
+
// backed like the price so the sparkline does not run through it.
|
|
345
|
+
<Text
|
|
346
|
+
fg={colors.warning}
|
|
347
|
+
style={{
|
|
348
|
+
gridColumn: "1 / -1",
|
|
349
|
+
gridRow: "2",
|
|
350
|
+
justifySelf: "start",
|
|
351
|
+
alignSelf: "start",
|
|
352
|
+
fontSize: "var(--gloom-type-ui)",
|
|
353
|
+
lineHeight: "14px",
|
|
354
|
+
whiteSpace: "nowrap",
|
|
355
|
+
backgroundColor: colors.bg,
|
|
356
|
+
borderRadius: "var(--gloom-radius-sm)",
|
|
357
|
+
boxShadow: `0 0 0 2px ${colors.bg}`,
|
|
358
|
+
}}
|
|
359
|
+
>
|
|
360
|
+
{quoteStatus.text}
|
|
361
|
+
</Text>
|
|
362
|
+
)}
|
|
363
|
+
|
|
335
364
|
{extendedParts && (
|
|
336
365
|
<ExtendedSessionLine
|
|
337
366
|
parts={extendedParts}
|
|
@@ -385,7 +414,7 @@ export function QuoteMonitorCard({
|
|
|
385
414
|
</Box>
|
|
386
415
|
<Box flexDirection="column">
|
|
387
416
|
<FigureText fg={changeColor} dim={flashing} style={figureHalo}>
|
|
388
|
-
{compactQuoteFailure ? `${priceText} · ${quoteStatus.stale ? "STALE" : "ERROR"}` : priceText}
|
|
417
|
+
{compactQuoteFailure ? `${priceText} · ${quoteStatus.ended ?? (quoteStatus.stale ? "STALE" : "ERROR")}` : priceText}
|
|
389
418
|
</FigureText>
|
|
390
419
|
<Box flexDirection="row" gap={1}>
|
|
391
420
|
<FigureText part="sub" fg={changeColor} dim={flashing} style={figureHalo}>{changePercentText}</FigureText>
|
|
@@ -432,7 +461,7 @@ export function QuoteMonitorCard({
|
|
|
432
461
|
)}
|
|
433
462
|
|
|
434
463
|
{quoteFailed && !compactQuoteFailure && (
|
|
435
|
-
<Box height={1} overflow="hidden"><Text fg={colors.negative}>{quoteStatus.text}</Text></Box>
|
|
464
|
+
<Box height={1} overflow="hidden"><Text fg={quoteStatus.ended ? colors.warning : colors.negative}>{quoteStatus.text}</Text></Box>
|
|
436
465
|
)}
|
|
437
466
|
{navAsOf && !compactNavAsOf && (
|
|
438
467
|
<Box height={1} overflow="hidden"><Text fg={colors.textDim}>{navAsOf}</Text></Box>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { Box } from "../../../ui";
|
|
3
3
|
import { usePaneSettingValue, useShortcut } from "../../../public/react";
|
|
4
|
-
import { DataTableStackView, KeyValueRow, PaneStatusBody, StatGrid, usePaneFooter, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, usePaneTicker, type DataTableColumn } from "../../../components";
|
|
4
|
+
import { splitDelayedStatus, DataTableStackView, KeyValueRow, PaneStatusBody, StatGrid, usePaneFooter, usePaneNoticeFooter, usePaneStatusFooter, usePaneTabs, usePaneTicker, type DataTableColumn } from "../../../components";
|
|
5
5
|
import { useThemeColors } from "../../../theme/theme-context";
|
|
6
6
|
import type { PaneProps } from "../../../types/plugin";
|
|
7
7
|
import type { TapeQuote, TapeSnapshot, TapeTrade } from "../../../api-client/tape";
|
|
@@ -109,7 +109,7 @@ function TimeSalesView({ width, height, focused, symbol, exchange }: PaneProps &
|
|
|
109
109
|
usePaneNoticeFooter({ registrationId: "time-sales:notices", focused, notices: [...(data?.gaps ?? []), ...(resource.transport ? [resource.transport] : []),
|
|
110
110
|
...(pinnedBeforeTape ? [`The pinned moment is before this tape's first ${tab === "quotes" ? "quote" : "print"}, ${tapeStampNewYork(rowTime(rows.at(-1)!))}.`] : [])] });
|
|
111
111
|
usePaneStatusFooter({ registrationId: "time-sales:status", loading: resource.loading, error: resource.error,
|
|
112
|
-
info: data ? [{ id: "feed", quoteStatus: true, parts: [{ text: `${data.feed === "sip" ? "real-time" : "15m delayed"} · ${tapeStampNewYork(data.asOf)}`, tone: "muted" }] },
|
|
112
|
+
info: data ? [...splitDelayedStatus({ id: "feed", quoteStatus: true, parts: [{ text: `${data.feed === "sip" ? "real-time" : "15m delayed"} · ${tapeStampNewYork(data.asOf)}`, tone: "muted" }] }),
|
|
113
113
|
...(frozen ? [{ id: "paused", parts: [{ text: "paused", tone: "warning" as const }] }] : []),
|
|
114
114
|
...(!data.connected || resource.snapshotOnly ? [{ id: "snapshot", parts: [{ text: "snapshot", tone: "warning" as const }] }] : [])] : [] });
|
|
115
115
|
const selectTab = (value: string) => { setTab(value); setDetail(null); setSelected(null); };
|
|
@@ -4,6 +4,7 @@ import { signalWithTimeout } from "../../../utils/async-deadline";
|
|
|
4
4
|
import { parsePublicTickerKey } from "../../../utils/exchanges";
|
|
5
5
|
import { httpFetch } from "../../../utils/http-transport";
|
|
6
6
|
import { isoDateOrNull, numericOrNull, recordOrNull, textOrNull } from "../../../utils/guards";
|
|
7
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
7
8
|
import { zonedDateKey } from "../../../utils/zoned-date-time";
|
|
8
9
|
|
|
9
10
|
const ENDPOINT = "https://services-dynarep.ddwa.finra.org/public/reporting/v2/data/group/FixedIncomeMarket/name/corporateAndAgencySecurities";
|
|
@@ -226,6 +227,7 @@ function dataRows(data: unknown): unknown[] | null {
|
|
|
226
227
|
const parsed = JSON.parse(data) as unknown;
|
|
227
228
|
return Array.isArray(parsed) ? parsed : null;
|
|
228
229
|
} catch {
|
|
230
|
+
// Intentional ignore: malformed nested rows make postBondPage reject the response shape.
|
|
229
231
|
return null;
|
|
230
232
|
}
|
|
231
233
|
}
|
|
@@ -340,7 +342,9 @@ async function readPayload(response: Response): Promise<unknown> {
|
|
|
340
342
|
async function openSession(signal: AbortSignal): Promise<string> {
|
|
341
343
|
const session = await httpFetch(ENDPOINT, { headers: REQUEST_HEADERS, signal });
|
|
342
344
|
const token = xsrfToken(session);
|
|
343
|
-
await session.body?.cancel().catch(() => {
|
|
345
|
+
await session.body?.cancel().catch(() => {
|
|
346
|
+
// Intentional ignore: only session headers are needed; discarding its unused body is best effort.
|
|
347
|
+
});
|
|
344
348
|
if (!token) throw new Error(failureText(session.status, "session"));
|
|
345
349
|
return token;
|
|
346
350
|
}
|
|
@@ -477,7 +481,14 @@ export async function fetchBondTape(input: string, options: BondTapeOptions = {}
|
|
|
477
481
|
let answer = first;
|
|
478
482
|
let issuer: BondIssuer | null = null;
|
|
479
483
|
if (first.bonds.length === 0 && query?.kind === "issuer" && query.ticker && options.companyName) {
|
|
480
|
-
const company = await options.companyName(query.ticker).catch(() =>
|
|
484
|
+
const company = await options.companyName(query.ticker).catch((error: unknown) => {
|
|
485
|
+
options.signal?.throwIfAborted();
|
|
486
|
+
if (error instanceof Error && error.name === "AbortError") throw error;
|
|
487
|
+
logFailure("trace-bonds.client", "Resolve bond issuer company failed")(error);
|
|
488
|
+
answer = { ...first, failures: [...first.failures, "Issuer-name lookup failed."] };
|
|
489
|
+
return null;
|
|
490
|
+
});
|
|
491
|
+
options.signal?.throwIfAborted();
|
|
481
492
|
const name = company ? tapeIssuerName(company) : null;
|
|
482
493
|
if (company && name && name !== query.name) {
|
|
483
494
|
issuer = { company, name };
|
|
@@ -492,7 +503,7 @@ export async function fetchBondTape(input: string, options: BondTapeOptions = {}
|
|
|
492
503
|
// No prints for the query: date the answer by the market's newest sale.
|
|
493
504
|
const market = await postBondPage(token, [TRADED], 1, deadline)
|
|
494
505
|
.then((payload) => latestTradeDate(mergeBondAnswers([{ payload, accepts: anyBond, limit: 1 }]).bonds))
|
|
495
|
-
.catch((
|
|
506
|
+
.catch(logFailure("trace-bonds.client", "Load bond market date failed", null));
|
|
496
507
|
return { ...result, tradeDate: market };
|
|
497
508
|
}
|
|
498
509
|
|
|
@@ -89,7 +89,7 @@ export const traceBondsHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
89
89
|
rows: bonds.map(reportRow),
|
|
90
90
|
...(query ? { empty: noPrintsText(query, tradeDate, issuer) } : {}),
|
|
91
91
|
}],
|
|
92
|
-
complete:
|
|
92
|
+
complete: failures.length === 0,
|
|
93
93
|
...(failures.length > 0 ? { errors: failures } : {}),
|
|
94
94
|
...(truncated ? { notes: [TRUNCATED_TEXT] } : {}),
|
|
95
95
|
...(shown ? { warnings: [issuerFallbackText(query!, issuer)] } : {}),
|
|
@@ -3,7 +3,7 @@ import { getSharedMarketDataCoordinator, MarketDataCoordinator, resolveEntryValu
|
|
|
3
3
|
import type { InstrumentRef, OptionsRequest } from "../../../market-data/request-types";
|
|
4
4
|
import type { QueryEntry } from "../../../market-data/result-types";
|
|
5
5
|
import type { DataProvider } from "../../../types/data-provider";
|
|
6
|
-
import type { OptionsChain } from "../../../types/financials";
|
|
6
|
+
import type { OptionsChain, Quote } from "../../../types/financials";
|
|
7
7
|
import { daysToExpiryFrom } from "../shared/volatility";
|
|
8
8
|
import type { YieldPoint } from "../yield-curve/treasury-data";
|
|
9
9
|
import {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { abortable, abortError } from "../../../utils/async-deadline";
|
|
14
14
|
import { errorMessage } from "../../../utils/errors";
|
|
15
15
|
import { findListedExpiry, unlistedExpiryText } from "../../../utils/option-expiry";
|
|
16
|
+
import { optionModelSpot } from "../options/model-spot";
|
|
16
17
|
|
|
17
18
|
const DEFAULT_SURFACE_EXPIRY_LIMIT = 18;
|
|
18
19
|
const SURFACE_LOAD_CONCURRENCY = 4;
|
|
@@ -91,6 +92,14 @@ export interface SurfaceLoadRequest {
|
|
|
91
92
|
instrument: InstrumentRef;
|
|
92
93
|
spot: number;
|
|
93
94
|
spotAsOf?: string | number | null;
|
|
95
|
+
/**
|
|
96
|
+
* The underlying's quote, to read the spot from as OMON reads it
|
|
97
|
+
* (`optionModelSpot`): a chain from a closed session is priced at that
|
|
98
|
+
* session's official close, which the quote carries, so the same chain
|
|
99
|
+
* gives the same surface whatever trades before the next open. Without
|
|
100
|
+
* it, or without a chain to date, `spot` is used as given.
|
|
101
|
+
*/
|
|
102
|
+
quote?: Quote | null;
|
|
94
103
|
settings?: Partial<SurfaceSettings>;
|
|
95
104
|
limit?: number;
|
|
96
105
|
/** Listed expiries required by a selection, added to the representative sample. */
|
|
@@ -134,6 +143,9 @@ export async function loadVolatilitySurface(
|
|
|
134
143
|
let catalogueExpiration: number | null = null;
|
|
135
144
|
let treasuryError: string | null = null;
|
|
136
145
|
let finished = false;
|
|
146
|
+
let spot = request.spot;
|
|
147
|
+
let spotAsOf = request.spotAsOf ?? null;
|
|
148
|
+
let spotBasis: SurfaceSnapshot["spotBasis"] = null;
|
|
137
149
|
const checkAbort = () => { if (request.signal?.aborted) throw abortError(CANCELLED); };
|
|
138
150
|
const snapshot = (): SurfaceSnapshot => {
|
|
139
151
|
const failures: SurfaceFailure[] = [];
|
|
@@ -152,7 +164,7 @@ export async function loadVolatilitySurface(
|
|
|
152
164
|
}
|
|
153
165
|
const cached = models.get(expiration);
|
|
154
166
|
if (cached?.entry === entry && cached.curve === curve) return cached.value;
|
|
155
|
-
const value = buildSurfaceExpiry({ chain, expiration, spot
|
|
167
|
+
const value = buildSurfaceExpiry({ chain, expiration, spot, curve, settings, now,
|
|
156
168
|
source: entry.source, stale: !!entry.error || (entry.staleAt != null && entry.staleAt <= now), error });
|
|
157
169
|
models.set(expiration, { entry, curve, value });
|
|
158
170
|
return value;
|
|
@@ -166,7 +178,7 @@ export async function loadVolatilitySurface(
|
|
|
166
178
|
: failures.length > 0 ? (ready ? "partial" : "error") : "ready";
|
|
167
179
|
const dates = [...new Set(expiries.flatMap((expiry) => expiry.rateAsOf))];
|
|
168
180
|
return {
|
|
169
|
-
symbol, spot
|
|
181
|
+
symbol, spot, spotAsOf, ...spotBasis ? { spotBasis } : {}, phase, settings, catalogue: [...catalogue],
|
|
170
182
|
requested: selected.length, loaded, failed, expiries, failures,
|
|
171
183
|
warnings: [...new Set([...expiries.flatMap((expiry) => expiry.warnings), ...surfaceCalendarWarnings(expiries)])],
|
|
172
184
|
rateAsOf: dates.length === 1 ? dates[0]! : null, fetchedAt: now,
|
|
@@ -190,6 +202,14 @@ export async function loadVolatilitySurface(
|
|
|
190
202
|
{ forceRefresh: request.expiries ? false : request.forceRefresh }), request.signal, CANCELLED);
|
|
191
203
|
checkAbort();
|
|
192
204
|
const chain = resolveEntryValue(initial);
|
|
205
|
+
// Every expiry is valued at one spot, read against the catalogue chain's time before any expiry is fitted.
|
|
206
|
+
const modelSpot = request.quote ? optionModelSpot(request.quote, chain, now, request.instrument.exchange) : null;
|
|
207
|
+
if (modelSpot) {
|
|
208
|
+
spot = modelSpot.price;
|
|
209
|
+
spotAsOf = modelSpot.observedAt;
|
|
210
|
+
spotBasis = { basis: modelSpot.basis, session: modelSpot.session,
|
|
211
|
+
sessionDate: modelSpot.basis === "close" ? request.quote!.regularCloseSessionDate ?? null : null };
|
|
212
|
+
}
|
|
193
213
|
const initialContracts = chain ? [...chain.calls, ...chain.puts] : [];
|
|
194
214
|
const representedExpiry = initialContracts[0]?.expiration;
|
|
195
215
|
if (representedExpiry != null && initialContracts.every((contract) => contract.expiration === representedExpiry)
|
|
@@ -4,14 +4,15 @@ import {
|
|
|
4
4
|
expirationOptionSeconds, findListedExpiry, OPTION_EXPIRATION_FORMAT, OPTION_EXPIRATION_PLACEHOLDER, readOptionExpiration, unlistedExpiryText,
|
|
5
5
|
} from "../../../utils/option-expiry";
|
|
6
6
|
import { DATE_OPTION_EXAMPLES } from "../../../utils/calendar-date";
|
|
7
|
-
import { skewHeader } from "../iv-history/format";
|
|
7
|
+
import { IMPLIED_VOL_WORDS, skewHeader } from "../iv-history/format";
|
|
8
8
|
import { formatStrikeLabel } from "../options/table";
|
|
9
9
|
import { createSurfaceDependencies, loadVolatilitySurface } from "./client";
|
|
10
10
|
import { buildSurfaceGrid, type SurfaceGridOptions, type SurfaceSettings, type SurfaceSnapshot } from "./model";
|
|
11
|
-
import { formatCaptureTime, loadDatedSurface, storedSurfaceDate, type DatedSurfaceSnapshot } from "./stored";
|
|
11
|
+
import { atCapturePrecision, formatCaptureTime, loadDatedSurface, storedSurfaceDate, type DatedSurfaceSnapshot } from "./stored";
|
|
12
|
+
import { listingFateEmpty, loadListingFate } from "../../../tickers/listing-fate";
|
|
12
13
|
import {
|
|
13
14
|
expiryFeedFields, expiryLabel, formatIv, formatPrice, formatVolPoints, HIDDEN_SLOPE_TEXT, HIDDEN_YIELD_TEXT, selectSurfaceExpiry, surfaceForwardRow, surfaceSkewRow,
|
|
14
|
-
surfaceSmileRows, surfaceTermRows, TERM_SLOPE_MIN_DAYS, type SurfaceForwardRow, type SurfaceSkewRow,
|
|
15
|
+
surfaceSmileRows, surfaceSpotBasisLabel, surfaceTermRows, TERM_SLOPE_MIN_DAYS, type SurfaceForwardRow, type SurfaceSkewRow,
|
|
15
16
|
} from "./tables";
|
|
16
17
|
|
|
17
18
|
type SurfaceAxis = NonNullable<SurfaceGridOptions["axis"]>;
|
|
@@ -26,8 +27,8 @@ export function surfaceCoordinateLabel(axis: SurfaceAxis, coordinate: number, in
|
|
|
26
27
|
|
|
27
28
|
/** The Skew view's sign conventions and units, one line each. */
|
|
28
29
|
const SKEW_NOTES = [
|
|
29
|
-
"RR is 25D call IV minus 25D put IV, BF the average 25D wing IV minus
|
|
30
|
-
`Slope is the
|
|
30
|
+
"RR is 25D call IV minus 25D put IV, BF the average 25D wing IV minus the IV at the forward, 90/110 the IV at 90% of the forward minus the IV at 110%; all in vol points.",
|
|
31
|
+
`Slope is the change per year in the IV at the forward to the next listed expiry, in vol points; left out under ${TERM_SLOPE_MIN_DAYS} days to expiry, where it annualises a few hours of noise.`,
|
|
31
32
|
];
|
|
32
33
|
|
|
33
34
|
/**
|
|
@@ -132,6 +133,7 @@ function forwardsSection(snapshot: SurfaceSnapshot): HeadlessBundleSection {
|
|
|
132
133
|
export const volSurfaceHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
133
134
|
shape: "bundle", argument: { kind: "ticker", description: "Underlying ticker" },
|
|
134
135
|
usListingsOnly: "Options data",
|
|
136
|
+
discovery: { suggestFor: IMPLIED_VOL_WORDS },
|
|
135
137
|
describe: (args) => `OVDV ${args.symbols[0] ?? ""}`,
|
|
136
138
|
options: [
|
|
137
139
|
{ key: "tab", type: "enum", values: SURFACE_TABS.map((value) => ({ value })),
|
|
@@ -154,6 +156,7 @@ export const volSurfaceHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
154
156
|
const date = typeof args.options.date === "string" && args.options.date ? storedSurfaceDate(args.options.date) : null;
|
|
155
157
|
let snapshot: DatedSurfaceSnapshot;
|
|
156
158
|
let quote: Quote | null = null;
|
|
159
|
+
let instrument: { symbol: string; exchange?: string } = { symbol };
|
|
157
160
|
if (date) {
|
|
158
161
|
// A stored close needs no current price: it carries the spot it was fitted at.
|
|
159
162
|
snapshot = await loadDatedSurface(symbol, date, { signal: ctx.signal, api: ctx.apiClient });
|
|
@@ -162,21 +165,35 @@ export const volSurfaceHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
162
165
|
snapshot = { ...snapshot, failures: [...snapshot.failures, { expiration: requested, message: unlistedExpiryText(requested, snapshot.catalogue) }] };
|
|
163
166
|
}
|
|
164
167
|
} else {
|
|
165
|
-
const
|
|
166
|
-
|
|
168
|
+
const resolved = await ctx.resolveInstrument?.(symbol) ?? { symbol };
|
|
169
|
+
instrument = resolved;
|
|
170
|
+
quote = await ctx.marketData.getQuote(symbol, resolved.exchange);
|
|
167
171
|
if (!(quote.price > 0) || !Number.isFinite(quote.price) || quote.stale) {
|
|
168
|
-
|
|
172
|
+
// A listing that stopped trading has no current price: what became of it is why (TWTR).
|
|
173
|
+
const fate = await loadListingFate(resolved, ctx.marketData, { quote });
|
|
174
|
+
return { sections: [], complete: false, unavailableSymbols: [symbol],
|
|
175
|
+
...listingFateEmpty(fate, ["A current underlying price is required"]) };
|
|
169
176
|
}
|
|
170
|
-
|
|
177
|
+
// At a stored close's precision, so the live report and a --date one export the same digits. A chain
|
|
178
|
+
// from a closed session is priced at that session's close, as OMON prices it.
|
|
179
|
+
snapshot = atCapturePrecision(await loadVolatilitySurface({ instrument, spot: quote.price, spotAsOf: quote.lastUpdated, quote,
|
|
171
180
|
settings: { ivSource: args.options.ivSource as SurfaceSettings["ivSource"], priceSide: args.options.priceSide as SurfaceSettings["priceSide"] },
|
|
172
181
|
limit: Number(args.options.limit), signal: ctx.signal,
|
|
173
182
|
requiredExpiries: requested == null ? [] : [requested],
|
|
174
|
-
}, createSurfaceDependencies(ctx.marketData, ctx.apiClient));
|
|
183
|
+
}, createSurfaceDependencies(ctx.marketData, ctx.apiClient)));
|
|
175
184
|
}
|
|
185
|
+
const available = snapshot.expiries.some((expiry) => expiry.fit);
|
|
186
|
+
const loadErrors = snapshot.failures.map((failure) => failure.expiration == null ? failure.message
|
|
187
|
+
: `${expiryLabel(failure.expiration)}: ${failure.message}`);
|
|
188
|
+
// A symbol with no option chain at all says what became of it when something did (FB is a fund now,
|
|
189
|
+
// Meta trades as META) in place of the chain's own error; a chain without clean quotes is the symbol's own.
|
|
190
|
+
const fate = !snapshot.stored && !available && snapshot.catalogue.length === 0
|
|
191
|
+
? listingFateEmpty(await loadListingFate(instrument, ctx.marketData, { quote }), loadErrors) : null;
|
|
192
|
+
// The spot a chain from a closed session was priced at, and its session.
|
|
193
|
+
const spotBasis = available ? surfaceSpotBasisLabel(snapshot) : null;
|
|
176
194
|
const stored = snapshot.stored ?? null;
|
|
177
195
|
const axis = args.options.axis as SurfaceAxis;
|
|
178
196
|
const tab = (SURFACE_TABS as readonly string[]).includes(String(args.options.tab)) ? args.options.tab as SurfaceTab : "surface";
|
|
179
|
-
const available = snapshot.expiries.some((expiry) => expiry.fit);
|
|
180
197
|
const expiryWarnings = snapshot.expiries.flatMap((expiry) => expiry.warnings.map((warning) =>
|
|
181
198
|
({ expiry: expiryLabel(expiry.expiration), warning })));
|
|
182
199
|
const perExpiry = new Set(expiryWarnings.map((row) => row.warning));
|
|
@@ -197,9 +214,11 @@ export const volSurfaceHeadless: HeadlessPaneDefinition<"bundle"> = {
|
|
|
197
214
|
],
|
|
198
215
|
complete: available && snapshot.failures.length === 0 && snapshot.expiries.every((expiry) => expiry.state === "ready"),
|
|
199
216
|
unavailableSymbols: available ? [] : [symbol],
|
|
200
|
-
errors: [...
|
|
201
|
-
|
|
202
|
-
...(stored || tab === "skew" ? { notes: [
|
|
217
|
+
errors: [...fate?.errors ?? loadErrors, ...smile?.errors ?? []],
|
|
218
|
+
...(fate?.empty ? { empty: fate.empty } : {}),
|
|
219
|
+
...(stored || spotBasis || tab === "skew" ? { notes: [
|
|
220
|
+
...stored ? storedNotes(snapshot, args.options) : spotBasis ? [`Spot ${formatPrice(snapshot.spot)}, ${spotBasis}.`] : [],
|
|
221
|
+
...tab === "skew" ? SKEW_NOTES : []] } : {}),
|
|
203
222
|
// A stored close is dated by its session, whichever view prints it.
|
|
204
223
|
...(stored ? { freshness: { status: "not-a-feed" as const, basis: "stored close capture", asOf: stored.sessionDate } }
|
|
205
224
|
: smile?.freshness ? { freshness: smile.freshness } : {}),
|
|
@@ -99,10 +99,23 @@ export interface SurfaceFailure {
|
|
|
99
99
|
reasonCode?: string;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
/**
|
|
103
|
+
* What a surface's spot is (`optionModelSpot`): the official close of the
|
|
104
|
+
* session the chain was quoted in (`close`, that session's New York date),
|
|
105
|
+
* or the latest price (`last`, with the extended session it printed in).
|
|
106
|
+
*/
|
|
107
|
+
interface SurfaceSpotBasis {
|
|
108
|
+
basis: "close" | "last";
|
|
109
|
+
session: "pre-market" | "after hours" | "overnight" | null;
|
|
110
|
+
sessionDate: string | null;
|
|
111
|
+
}
|
|
112
|
+
|
|
102
113
|
export interface SurfaceSnapshot {
|
|
103
114
|
symbol: string;
|
|
104
115
|
spot: number;
|
|
105
116
|
spotAsOf?: string | number | null;
|
|
117
|
+
/** Absent when the caller gave a bare spot, as a stored close does. */
|
|
118
|
+
spotBasis?: SurfaceSpotBasis | null;
|
|
106
119
|
phase: "loading" | "partial" | "ready" | "error";
|
|
107
120
|
settings: SurfaceSettings;
|
|
108
121
|
catalogue: number[];
|
|
@@ -401,11 +414,16 @@ export function buildSurfaceExpiry(input: BuildSurfaceExpiryInput): SurfaceExpir
|
|
|
401
414
|
}
|
|
402
415
|
|
|
403
416
|
/**
|
|
404
|
-
* A fitted expiry's skew in decimal IV
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
*
|
|
417
|
+
* A fitted expiry's skew in decimal IV, measured from its forward: `atm` is
|
|
418
|
+
* the IV at the forward (ATMF), RR the 25-delta call minus put, BF their
|
|
419
|
+
* average minus ATMF, 90/110 the IV at 90% of the forward minus 110%. The
|
|
420
|
+
* spot only enters through the 25-delta strikes' spot delta, whose carry is
|
|
421
|
+
* the parity forward's, so a pre-market print against the close's chain does
|
|
422
|
+
* not move a fly, and a long-dated fly is not dragged below zero by the skew
|
|
423
|
+
* between spot and a forward several percent above it. The 25-delta strikes
|
|
424
|
+
* are found on the fit inside the quoted strikes, as the Table's delta
|
|
425
|
+
* columns find them, so a fit that cannot price a far wing it was never
|
|
426
|
+
* quoted at does not blank a quoted expiry's skew.
|
|
409
427
|
*/
|
|
410
428
|
export function surfaceExpirySkew(expiry: SurfaceExpiry, spot: number): SkewMetrics {
|
|
411
429
|
const smile = (strike: number) => evaluateSurfaceSmile(expiry, strike);
|
|
@@ -413,8 +431,9 @@ export function surfaceExpirySkew(expiry: SurfaceExpiry, spot: number): SkewMetr
|
|
|
413
431
|
const strike = deltaStrike(expiry, spot, delta);
|
|
414
432
|
return strike == null ? null : smile(strike);
|
|
415
433
|
};
|
|
416
|
-
const
|
|
417
|
-
const
|
|
434
|
+
const forward = expiry.forward;
|
|
435
|
+
const put25 = at(-0.25), call25 = at(0.25), atm = positive(forward) ? smile(forward) : null;
|
|
436
|
+
const low = positive(forward) ? smile(forward * 0.9) : null, high = positive(forward) ? smile(forward * 1.1) : null;
|
|
418
437
|
const both = put25 != null && call25 != null;
|
|
419
438
|
return { atm, put25, call25, putCallSkew: both ? put25 - call25 : null, riskReversal: both ? call25 - put25 : null,
|
|
420
439
|
butterfly: both && atm != null ? (put25 + call25) / 2 - atm : null,
|
|
@@ -433,12 +452,13 @@ export function surfaceCalendarWarnings(expiries: readonly SurfaceExpiry[]): str
|
|
|
433
452
|
return count > 0 ? [`${count} calendar arbitrage ${count === 1 ? "warning" : "warnings"}`] : [];
|
|
434
453
|
}
|
|
435
454
|
|
|
455
|
+
/** The ATM-forward IV change per year to the next expiry that has one: spot plays no part in it. */
|
|
436
456
|
export function withSurfaceTermSlopes(expiries: readonly SurfaceExpiry[]): SurfaceExpiry[] {
|
|
437
457
|
const sorted = [...expiries].sort((a, b) => a.years - b.years);
|
|
438
458
|
return sorted.map((expiry, index) => {
|
|
439
|
-
const next = sorted.slice(index + 1).find((item) => item.
|
|
440
|
-
return { ...expiry, termSlope: expiry.
|
|
441
|
-
? volatilityTermSlope({ years: expiry.years, volatility: expiry.
|
|
459
|
+
const next = sorted.slice(index + 1).find((item) => item.skew.atm != null);
|
|
460
|
+
return { ...expiry, termSlope: expiry.skew.atm != null && next?.skew.atm != null
|
|
461
|
+
? volatilityTermSlope({ years: expiry.years, volatility: expiry.skew.atm }, { years: next.years, volatility: next.skew.atm }) : null };
|
|
442
462
|
});
|
|
443
463
|
}
|
|
444
464
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { DataTableView, EmptyState, PaneStatusBody, QueryBar, StatGrid, statGridRows, usePaneFooter, usePaneMenuItems,
|
|
2
|
+
import { splitDelayedStatus, DataTableView, EmptyState, PaneStatusBody, QueryBar, StatGrid, statGridRows, usePaneFooter, usePaneMenuItems,
|
|
3
3
|
usePaneNoticeFooter, usePaneTabs, usePaneTicker, type DataTableColumn, type DataTableKeyEvent, type StatItem } from "../../../components";
|
|
4
4
|
import { useTableLoadMore } from "../../../components/table-view-shared";
|
|
5
5
|
import { useStaticChartBitmapSize } from "../../../components/chart/composite/bitmap";
|
|
@@ -39,7 +39,7 @@ import { VolatilitySurface } from "./surface";
|
|
|
39
39
|
import { SmileChart, TermChart } from "./charts";
|
|
40
40
|
import { surfaceCoordinateLabel } from "./headless";
|
|
41
41
|
import { expiryLabel, formatIv, formatPrice, formatVolPoints, HIDDEN_SLOPE_TEXT, HIDDEN_YIELD_TEXT, selectSurfaceExpiry, surfaceForwardRow,
|
|
42
|
-
surfaceSkewRow } from "./tables";
|
|
42
|
+
surfaceSkewRow, surfaceSpotBasisLabel } from "./tables";
|
|
43
43
|
import { findListedExpiry, unlistedExpiryText } from "../../../utils/option-expiry";
|
|
44
44
|
|
|
45
45
|
const TABS = [
|
|
@@ -95,17 +95,19 @@ export function VolSurfacePane({ focused, width, height }: PaneProps) {
|
|
|
95
95
|
const spotAvailable = !!quote && quote.price > 0 && Number.isFinite(quote.price) && !quote.stale;
|
|
96
96
|
const spotAsOfRef = useRef(quote?.lastUpdated ?? null);
|
|
97
97
|
spotAsOfRef.current = quote?.lastUpdated ?? null;
|
|
98
|
-
const
|
|
99
|
-
|
|
98
|
+
const quoteRef = useRef(quote ?? null);
|
|
99
|
+
quoteRef.current = quote ?? null;
|
|
100
100
|
const requestKey = JSON.stringify([target?.cacheKey, symbol, axis, tenors, ivSource, priceSide, spread, age, limit, spotAvailable, requestedExpiration]);
|
|
101
101
|
// Every load reads the spot at the moment it starts; a reload in session
|
|
102
102
|
// therefore uses the current stream price, not the one from the first load.
|
|
103
|
+
// A chain from a closed session is priced at that session's close, which
|
|
104
|
+
// the quote carries, so a pre-market print does not move it.
|
|
103
105
|
const surfaceRequest = useCallback((force: boolean, signal: AbortSignal): SurfaceLoadRequest => {
|
|
104
106
|
const instrument = target?.instrument;
|
|
105
107
|
return {
|
|
106
108
|
instrument: { symbol: target?.effectiveTicker ?? symbol!, exchange: target?.effectiveExchange ?? "",
|
|
107
109
|
brokerId: instrument?.brokerId, brokerInstanceId: instrument?.brokerInstanceId, instrument },
|
|
108
|
-
spot:
|
|
110
|
+
spot: quoteRef.current?.price ?? 0, spotAsOf: spotAsOfRef.current, quote: quoteRef.current, limit, forceRefresh: force, signal,
|
|
109
111
|
requiredExpiries: expirationRef.current == null ? [] : [expirationRef.current],
|
|
110
112
|
settings: { ...DEFAULT_SURFACE_SETTINGS, ivSource, priceSide, maxRelativeSpread: Number(spread), maxStaleSessions: Number(age) },
|
|
111
113
|
};
|
|
@@ -153,6 +155,14 @@ export function VolSurfacePane({ focused, width, height }: PaneProps) {
|
|
|
153
155
|
const underlying = (target?.effectiveTicker ?? symbol ?? "").toUpperCase();
|
|
154
156
|
const datesLoader = useCallback(async () => (await loadSurfaceDates(underlying)).dates, [underlying]);
|
|
155
157
|
const dates = useAsyncResource(underlying ? datesLoader : null);
|
|
158
|
+
// A symbol with no current price, or no option chain at all, says what became of it when something
|
|
159
|
+
// did, as `quote` says it: TWTR stopped trading, FB is a fund now and Meta trades as META.
|
|
160
|
+
const noChain = !historyDate && !!quote && (!spotAvailable
|
|
161
|
+
|| (!!resource.data && !resource.loading && resource.data.catalogue.length === 0));
|
|
162
|
+
const fateExchange = target?.effectiveExchange ?? "";
|
|
163
|
+
const fateLoader = useCallback(async () => (await getSharedMarketDataCoordinator()
|
|
164
|
+
?.loadListingFate({ symbol: underlying, exchange: fateExchange }))?.note ?? null, [underlying, fateExchange]);
|
|
165
|
+
const fateNote = useAsyncResource(underlying && noChain ? fateLoader : null).data ?? null;
|
|
156
166
|
const ivRank = useIvRank(underlying || null);
|
|
157
167
|
useEffect(() => () => controller.current?.abort(), [request]);
|
|
158
168
|
const incremental = partial?.key === requestKey ? partial.snapshot : null;
|
|
@@ -229,6 +239,11 @@ export function VolSurfacePane({ focused, width, height }: PaneProps) {
|
|
|
229
239
|
const nearestRow = (years: number | undefined) => years == null || !denseGrid?.tenors.length ? -1
|
|
230
240
|
: denseGrid.tenors.reduce((best, value, index) => Math.abs(value - years) < Math.abs(denseGrid.tenors[best]! - years) ? index : best, 0);
|
|
231
241
|
const selectedExpiry = snapshot ? selectSurfaceExpiry(snapshot, expiration) : null;
|
|
242
|
+
// Another date loads under the query bar, which keeps the last surface's expiries until it answers.
|
|
243
|
+
const lastShown = useRef<{ underlying: string; snapshot: DatedSurfaceSnapshot } | null>(null);
|
|
244
|
+
if (snapshot) lastShown.current = { underlying, snapshot };
|
|
245
|
+
const barSnapshot = snapshot ?? (lastShown.current?.underlying === underlying ? lastShown.current.snapshot : null);
|
|
246
|
+
const barExpiry = barSnapshot ? selectSurfaceExpiry(barSnapshot, expiration) : null;
|
|
232
247
|
const openChain = useCallback(() => {
|
|
233
248
|
if (!snapshot) return;
|
|
234
249
|
const selected = pinned ?? selectedExpiry?.expiration;
|
|
@@ -334,7 +349,7 @@ export function VolSurfacePane({ focused, width, height }: PaneProps) {
|
|
|
334
349
|
for (const entry of snapshot?.expiries ?? []) {
|
|
335
350
|
for (const warning of entry.warnings) expiryWarnings.set(warning, [...expiryWarnings.get(warning) ?? [], entry.expiration]);
|
|
336
351
|
}
|
|
337
|
-
const notices = [...(snapshot?.warnings ?? []).filter((warning) => !expiryWarnings.has(warning)), ...failures,
|
|
352
|
+
const notices = fateNote ? [] : [...(snapshot?.warnings ?? []).filter((warning) => !expiryWarnings.has(warning)), ...failures,
|
|
338
353
|
...(activeTab === "surface" && sheet?.omitted.length ? [`3D sheet omits ${sheet.omitted.map((entry) => expiryLabel(entry.expiration)).join(", ")}: smile fit fell back to interpolation (shown in Table)`] : []),
|
|
339
354
|
...[...expiryWarnings].map(([warning, expirations]) => `${expirations.length > 1 && expirations.length === snapshot?.expiries.length
|
|
340
355
|
? "All expiries" : expirations.map(expiryLabel).join(", ")}: ${warning}`),
|
|
@@ -345,6 +360,7 @@ export function VolSurfacePane({ focused, width, height }: PaneProps) {
|
|
|
345
360
|
&& !snapshot.failures.some((failure) => failure.expiration === pinned)
|
|
346
361
|
? [`${expiryLabel(pinned)}: ${unlistedExpiryText(pinned, snapshot.catalogue)}`] : []),
|
|
347
362
|
...(active.error ? [active.error] : [])];
|
|
363
|
+
const spotBasis = snapshot && !snapshot.stored ? surfaceSpotBasisLabel(snapshot) : null;
|
|
348
364
|
// What the quotes are and when they were observed; a reload moves the time.
|
|
349
365
|
const freshness = !snapshot ? null : snapshot.stored ? `stored close ${snapshot.stored.sessionDate}`
|
|
350
366
|
: surfaceFreshnessLabel(snapshot, CLOUD_QUOTE_DELAY_MINUTES);
|
|
@@ -360,7 +376,7 @@ export function VolSurfacePane({ focused, width, height }: PaneProps) {
|
|
|
360
376
|
info: [
|
|
361
377
|
...(active.loading ? [{ id: "loading", parts: [{ text: snapshot && snapshot.loaded < snapshot.requested
|
|
362
378
|
? `loading ${snapshot.loaded}/${snapshot.requested} expiries` : "loading", tone: "muted" as const }] }] : []),
|
|
363
|
-
...(freshness ?
|
|
379
|
+
...(freshness ? splitDelayedStatus({ id: "source", parts: [{ text: freshness, tone: "muted" }] }) : []),
|
|
364
380
|
...(snapshot?.expiries.some((entry) => entry.stale) ? [{ id: "stale", parts: [{ text: "stale", tone: "warning" as const }] }] : []),
|
|
365
381
|
...(selectedExpiry?.fit && activeTab === "smile" ? [{ id: "fit", parts: [{ text: `${selectedExpiry.fit.method} · RMSE ${(selectedExpiry.fit.residual * 100).toFixed(3)} vol pts`, tone: "muted" as const }] }] : []),
|
|
366
382
|
...(selectionStatus ? [{ id: "selection", parts: [{ text: selectionStatus, tone: "muted" as const }] }] : []),
|
|
@@ -447,24 +463,28 @@ export function VolSurfacePane({ focused, width, height }: PaneProps) {
|
|
|
447
463
|
useEffect(() => setTableOnScreen(tableShown), [tableShown]);
|
|
448
464
|
return <Box flexDirection="column" width={width} overflow="hidden" {...(nativePaneChrome ? FILL_REST : { height })}>
|
|
449
465
|
{tabStrip}
|
|
450
|
-
{!symbol ? <EmptyState title="Choose an underlying ticker." /> :
|
|
451
|
-
|
|
452
|
-
<QueryBar width={width}
|
|
466
|
+
{!symbol ? <EmptyState title="Choose an underlying ticker." /> : <>
|
|
467
|
+
{barSnapshot ? <QueryBar width={width}
|
|
453
468
|
filters={[
|
|
454
|
-
{ id: "expiry", label: "Expiry", value: String(pinned ??
|
|
455
|
-
options:
|
|
469
|
+
{ id: "expiry", label: "Expiry", value: String(pinned ?? barExpiry?.expiration ?? ""),
|
|
470
|
+
options: barSnapshot.catalogue.map((value) => ({ value: String(value), label: expiryLabel(value) })),
|
|
456
471
|
onChange: (value: string) => setExpiration(Number(value)) },
|
|
457
472
|
...(storedDates.length ? [{ id: "date", label: "Date", value: shownDate ?? "", defaultValue: "",
|
|
458
473
|
options: [{ value: "", label: "Live" }, ...storedDates.map((date) => ({ value: date, label: date }))],
|
|
459
474
|
onChange: (value: string) => setHistoryDate(value || null) }] : []),
|
|
460
475
|
]}
|
|
461
|
-
meta={snapshot
|
|
476
|
+
meta={!snapshot ? undefined : snapshot.stored
|
|
462
477
|
? `Stored close · spot ${formatPrice(snapshot.spot)} · captured ${formatCaptureTime(snapshot.stored.capturedAt)} New York`
|
|
463
|
-
: [`Spot ${formatPrice(snapshot
|
|
478
|
+
: [`Spot ${formatPrice(snapshot.spot)}${quote?.currency ? ` ${quote.currency}` : ""}`,
|
|
479
|
+
// The close a closed session's chain is priced at dates the chain too.
|
|
480
|
+
spotBasis, snapshot.spotBasis?.basis === "close" ? null : selectedExpiry?.asOf?.slice(0, 10)].filter(Boolean).join(" · ")} /> : null}
|
|
481
|
+
<PaneStatusBody loading={active.loading && !snapshot && !fateNote}
|
|
482
|
+
error={!snapshot && !fateNote ? active.error : null} empty={!!fateNote || !snapshot && !active.loading}
|
|
483
|
+
emptyTitle={fateNote ?? undefined} subject="volatility surface">
|
|
464
484
|
<StatGrid items={ivStats} width={width} />
|
|
465
485
|
{/* The terminal sizes the view in the rows under the bar and figures; the desktop fills what is left. */}
|
|
466
486
|
{nativePaneChrome ? <Box flexDirection="column" overflow="hidden" {...FILL_REST}>{content}</Box> : content}
|
|
467
|
-
</PaneStatusBody
|
|
487
|
+
</PaneStatusBody></>}
|
|
468
488
|
</Box>;
|
|
469
489
|
}
|
|
470
490
|
|