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
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { Box, Text, useUiCapabilities } from "../../../ui";
|
|
3
|
-
import { usePaneSettingValue, usePaneTicker, useUpdatePaneSettings } from "../../../state/app/context";
|
|
3
|
+
import { usePaneAppConfig, usePaneSettingValue, usePaneTicker, useUpdatePaneSettings } from "../../../state/app/context";
|
|
4
4
|
import { colors, priceColor } from "../../../theme/colors";
|
|
5
5
|
import { isPlainKey } from "../../../utils/keyboard";
|
|
6
6
|
import { formatCompact, formatPercentRaw } from "../../../utils/format";
|
|
7
7
|
import { formatMarketPrice, formatSignedMarketPrice, liveQuoteFormatOptions } from "../../../market-data/market/format";
|
|
8
|
-
import {
|
|
8
|
+
import { getRegularSessionDisplay } from "../../../market-data/market/status";
|
|
9
|
+
import { chainOptionContract, formatExpDate, optionContractName, resolveOptionsTarget } from "../../../utils/options";
|
|
9
10
|
import {
|
|
10
11
|
expiryIsoDate, findListedExpiry, formatDaysToExpiry, daysToExpiry, missingExpiryMessage, missingExpiryText, readOptionExpiration,
|
|
11
12
|
} from "../../../utils/option-expiry";
|
|
@@ -34,7 +35,9 @@ import { useLiveQuoteEntries, useQuoteUpdates } from "../../../state/hooks/quote
|
|
|
34
35
|
import { buildChartKey } from "../../../market-data/selectors";
|
|
35
36
|
import type { ChartRequest } from "../../../market-data/request-types";
|
|
36
37
|
import { quoteSubscriptionTargetFromTicker } from "../../../market-data/request-types";
|
|
37
|
-
import { usePluginAppActions } from "../../runtime";
|
|
38
|
+
import { usePluginAppActions, usePluginBrokerActions, usePluginTickerActions } from "../../runtime";
|
|
39
|
+
import { brokerCanTrade, openTradeTicket } from "../broker-trading/command";
|
|
40
|
+
import type { OptionContract } from "../../../types/financials";
|
|
38
41
|
import { OPTIONS_CALCULATOR_TEMPLATE_ID } from "../options-calculator/model";
|
|
39
42
|
import { buildChainCalcParams, resolveCalcSide } from "./calc-seed";
|
|
40
43
|
import { useOptionsCatalogue } from "./expiry-catalogue";
|
|
@@ -66,7 +69,7 @@ import { signedPositionDirection } from "../../../portfolio/position-metrics";
|
|
|
66
69
|
import { optionMarketReference } from "./market-reference";
|
|
67
70
|
import { useOptionsEnrichment } from "./enrichment";
|
|
68
71
|
import type { OptionsEnrichmentSnapshot } from "./enrichment-model";
|
|
69
|
-
import { optionMid, type OptionSide } from "../shared/volatility";
|
|
72
|
+
import { daysToExpiryFrom, optionMid, type OptionSide } from "../shared/volatility";
|
|
70
73
|
import type { TickerRecord } from "../../../types/ticker";
|
|
71
74
|
import type { TickerFinancials } from "../../../types/financials";
|
|
72
75
|
import type { IvStats } from "../iv-history/client";
|
|
@@ -74,7 +77,10 @@ import { useIvRank } from "../iv-history/rank";
|
|
|
74
77
|
import { useOptionsSessionOpen, useThrottledValue } from "../shared/volatility/live-session";
|
|
75
78
|
import { useRemoteUiNode } from "../../../remote/semantic-tree";
|
|
76
79
|
import { optionCarry, optionCostOfSpot } from "./carry";
|
|
77
|
-
import {
|
|
80
|
+
import {
|
|
81
|
+
formatModelSpotPrice, modelSpotExtendedPrint, modelSpotLabel, modelSpotStamp, modelSpotStampVariants, optionModelSpot,
|
|
82
|
+
type OptionModelSpot,
|
|
83
|
+
} from "./model-spot";
|
|
78
84
|
import { chainOpenInterestAsOf, loadListedOpenInterest, openInterestAsOfLabel, statedOpenInterestAsOf } from "./open-interest-as-of";
|
|
79
85
|
import { useAsyncResource } from "../../../react/async-resource";
|
|
80
86
|
import {
|
|
@@ -111,13 +117,35 @@ function transientTicker(symbol: string, exchange: string, currency: string): Ti
|
|
|
111
117
|
}
|
|
112
118
|
|
|
113
119
|
/**
|
|
114
|
-
* The underlying's price
|
|
115
|
-
*
|
|
120
|
+
* The underlying's price: the level every strike is read against. A chain
|
|
121
|
+
* priced at a session's close or an extended-session print shows that spot
|
|
122
|
+
* with its stamp (`modelSpotStamp`), and any extended print after it named by
|
|
123
|
+
* its session; the regular session's live price shows its day change. A stale
|
|
124
|
+
* quote stays on screen, muted, while the footer says why.
|
|
116
125
|
*/
|
|
117
|
-
function spotItem(
|
|
126
|
+
function spotItem(
|
|
127
|
+
underlying: TickerFinancials | null | undefined,
|
|
128
|
+
assetCategory: string | undefined,
|
|
129
|
+
modelSpot: OptionModelSpot | null,
|
|
130
|
+
): StatItem {
|
|
118
131
|
const quote = underlying?.quote;
|
|
119
132
|
if (!quote || !Number.isFinite(quote.price)) return { id: "spot", label: "Spot", value: "--" };
|
|
120
133
|
const options = liveQuoteFormatOptions(quote, quote.currency, assetCategory, underlying?.quoteMetadata?.instrumentType);
|
|
134
|
+
const stamp = modelSpot ? modelSpotStamp(modelSpot) : null;
|
|
135
|
+
if (modelSpot && stamp) {
|
|
136
|
+
const move = modelSpot.basis === "close" ? getRegularSessionDisplay(quote)?.change : quote.change;
|
|
137
|
+
const extended = modelSpotExtendedPrint(quote, modelSpot, (price) => formatMarketPrice(price, options));
|
|
138
|
+
// The band keeps its columns: the extended print, then the stamp's day, give way first.
|
|
139
|
+
const shorter = modelSpotStampVariants(modelSpot);
|
|
140
|
+
return {
|
|
141
|
+
id: "spot",
|
|
142
|
+
label: "Spot",
|
|
143
|
+
value: formatMarketPrice(modelSpot.price, options),
|
|
144
|
+
detail: extended ? `${stamp} · ${extended}` : stamp,
|
|
145
|
+
detailVariants: extended ? [stamp, ...shorter] : shorter,
|
|
146
|
+
color: quote.stale ? colors.textDim : priceColor(Number.isFinite(move) ? move! : 0),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
121
149
|
const change = Number.isFinite(quote.change) ? quote.change : 0;
|
|
122
150
|
return {
|
|
123
151
|
id: "spot",
|
|
@@ -176,6 +204,9 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
|
|
|
176
204
|
const ticker = useMemo(() => savedTicker ?? (boundSymbol ? transientTicker(boundSymbol, fallbackExchange, fallbackCurrency) : null),
|
|
177
205
|
[savedTicker, boundSymbol, fallbackExchange, fallbackCurrency]);
|
|
178
206
|
const { createPaneFromTemplate } = usePluginAppActions();
|
|
207
|
+
const { pinTicker } = usePluginTickerActions();
|
|
208
|
+
const { getBrokerAdapter } = usePluginBrokerActions();
|
|
209
|
+
const appConfig = usePaneAppConfig();
|
|
179
210
|
const liveStreaming = useLiveStreamingSetting();
|
|
180
211
|
// A number from the app, or the date or seconds `fn OMON --expiration` took as text.
|
|
181
212
|
const [seededSetting] = usePaneSettingValue<unknown>("expiration", undefined);
|
|
@@ -238,13 +269,20 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
|
|
|
238
269
|
const strikeWindowText = strikeWindowValue(strikeWindow);
|
|
239
270
|
const initialChainEntry = useOptionsQuery(baseRequest);
|
|
240
271
|
const initialChain = useResolvedEntryValue(initialChainEntry);
|
|
241
|
-
|
|
272
|
+
// A listing can still carry a date whose 16:00 ET close has passed. The chain
|
|
273
|
+
// never opens there, nor on a saved date that has since passed: it takes the nearest live one.
|
|
274
|
+
const now = Date.now();
|
|
275
|
+
const liveExpiration = (expiration: number) => daysToExpiryFrom(expiration, now) > 0;
|
|
276
|
+
const listedExpirations = initialChain?.expirationDates ?? [];
|
|
277
|
+
const openingExpirations = listedExpirations.some(liveExpiration) ? listedExpirations.filter(liveExpiration) : listedExpirations;
|
|
278
|
+
const initialExpiration = openingExpirations.length === 0 ? undefined : openingExpirations.reduce((best, expiration) => (
|
|
242
279
|
parsed && Math.abs(expiration - parsed.expTs) < Math.abs(best - parsed.expTs) ? expiration : best
|
|
243
|
-
),
|
|
280
|
+
), openingExpirations[0]!);
|
|
244
281
|
// A typed date names a day; the catalogue's stamp for that day is the expiry.
|
|
245
282
|
const seededDate = readOptionExpiration(seededSetting);
|
|
246
|
-
const
|
|
247
|
-
|
|
283
|
+
const seededListed = seededDate == null ? undefined : findListedExpiry(seededDate, listedExpirations) ?? seededDate;
|
|
284
|
+
const seededExpiration = seededListed != null && (liveExpiration(seededListed) || !openingExpirations.some(liveExpiration))
|
|
285
|
+
? seededListed : undefined;
|
|
248
286
|
const selectedExpiration = expirationTargetKey == null || expirationTargetKey === selectionTargetKey
|
|
249
287
|
? seededExpiration ?? initialExpiration : initialExpiration;
|
|
250
288
|
const viewportKey = `${effectiveTicker}:${selectedExpiration ?? "initial"}`;
|
|
@@ -281,8 +319,11 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
|
|
|
281
319
|
?? (matchesSelectedExpiration(initialChain) ? initialChain : null);
|
|
282
320
|
const strikesLoading = strikeChain === null && !expirationUnavailable;
|
|
283
321
|
const expirationDates = useMemo(() => {
|
|
284
|
-
|
|
322
|
+
const listed = selectedExpirationMissing
|
|
285
323
|
? [...availableExpirations, selectedExpiration!].sort((left, right) => left - right) : availableExpirations;
|
|
324
|
+
// A date that has ended has no contracts left to show; the strip leaves it out, unless every date has.
|
|
325
|
+
const current = (expiration: number) => daysToExpiry(expiration, Date.now()) >= 0;
|
|
326
|
+
return listed.some(current) ? listed.filter((expiration) => expiration === selectedExpiration || current(expiration)) : listed;
|
|
286
327
|
}, [availableExpirations, selectedExpiration, selectedExpirationMissing]);
|
|
287
328
|
// Keep the date strip stable while a mouse press focuses this pane. A new
|
|
288
329
|
// tab list asks the web host to reveal the active tab and can move the date
|
|
@@ -468,13 +509,17 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
|
|
|
468
509
|
: { key: viewportKey, range }
|
|
469
510
|
));
|
|
470
511
|
}, [viewportKey]);
|
|
512
|
+
// A chain read from a broker profile has its contracts quoted by that profile too.
|
|
513
|
+
const quoteBrokerId = instrument?.brokerId, quoteBrokerInstanceId = instrument?.brokerInstanceId;
|
|
514
|
+
const optionQuoteBroker = useMemo(() => quoteBrokerId || quoteBrokerInstanceId
|
|
515
|
+
? { brokerId: quoteBrokerId, brokerInstanceId: quoteBrokerInstanceId } : undefined, [quoteBrokerId, quoteBrokerInstanceId]);
|
|
471
516
|
const optionQuoteTargets = useMemo(
|
|
472
517
|
() => buildOptionQuoteTargets(quoteRows, {
|
|
473
518
|
fallbackHeight: height,
|
|
474
519
|
selectedIndex: selectedStrikeIdx,
|
|
475
520
|
visibleRange: visibleStrikeRange,
|
|
476
|
-
}),
|
|
477
|
-
[height, quoteRows, selectedStrikeIdx, visibleStrikeRange],
|
|
521
|
+
}, optionQuoteBroker),
|
|
522
|
+
[height, quoteRows, selectedStrikeIdx, visibleStrikeRange, optionQuoteBroker],
|
|
478
523
|
);
|
|
479
524
|
const {
|
|
480
525
|
entries: optionQuoteEntries,
|
|
@@ -497,8 +542,8 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
|
|
|
497
542
|
// The snapshot object itself comes back while nothing streamed, so the
|
|
498
543
|
// memos below only recompute when a quote actually landed.
|
|
499
544
|
const liveChain = useMemo(
|
|
500
|
-
() => strikeChain ? overlayOptionChainQuotes(strikeChain, optionQuoteEntries, optionQuoteFreshness).chain : null,
|
|
501
|
-
[optionQuoteEntries, optionQuoteFreshness, strikeChain],
|
|
545
|
+
() => strikeChain ? overlayOptionChainQuotes(strikeChain, optionQuoteEntries, optionQuoteFreshness, optionQuoteBroker).chain : null,
|
|
546
|
+
[optionQuoteEntries, optionQuoteFreshness, strikeChain, optionQuoteBroker],
|
|
502
547
|
);
|
|
503
548
|
const callsByStrike = useMemo(
|
|
504
549
|
() => new Map(liveChain?.calls.map((c) => [c.strike, c]) ?? []),
|
|
@@ -533,10 +578,10 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
|
|
|
533
578
|
putGreeks: volatilities ? calculateOptionGreeks(put, "put", greeksSpot, dividendYield, volatilities) : undefined,
|
|
534
579
|
callExtrinsicPerYear: call && volatilities ? optionCarry(call, "call", greeksSpot, volatilities.valuationTime)?.extrinsicPerYear ?? null : null,
|
|
535
580
|
putExtrinsicPerYear: put && volatilities ? optionCarry(put, "put", greeksSpot, volatilities.valuationTime)?.extrinsicPerYear ?? null : null,
|
|
536
|
-
putCostOfSpot: put ? optionCostOfSpot(put,
|
|
581
|
+
putCostOfSpot: put ? optionCostOfSpot(put, greeksSpot) : null,
|
|
537
582
|
isPositionStrike: !!parsed && strike === parsed.strike,
|
|
538
583
|
};
|
|
539
|
-
}), [callsByStrike, dividendYield, greeksSpot, parsed, putsByStrike,
|
|
584
|
+
}), [callsByStrike, dividendYield, greeksSpot, parsed, putsByStrike, strikes, volatilities]);
|
|
540
585
|
// The open interest's session: the chain's own, or for a chain that does not say, OPX's listing of the
|
|
541
586
|
// same expiry, stated only when the counts are OPX's.
|
|
542
587
|
const statedOpenInterest = useMemo(() => strikeChain ? statedOpenInterestAsOf(strikeChain) : undefined, [strikeChain]);
|
|
@@ -565,10 +610,12 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
|
|
|
565
610
|
} : null);
|
|
566
611
|
// What IV and the Greeks are valued at, and the open interest's date. In a
|
|
567
612
|
// crowded footer it shortens, then gives way to the contract under the cursor.
|
|
568
|
-
|
|
613
|
+
// A stamped spot (a close, an extended print) is already the Spot figure's.
|
|
614
|
+
const footerSpot = modelSpot && !modelSpotStamp(modelSpot) ? modelSpot : null;
|
|
615
|
+
const basisText = [footerSpot ? `spot ${formatModelSpotPrice(footerSpot.price)} ${modelSpotLabel(footerSpot)}` : null,
|
|
569
616
|
strikeChain && !listedOpenInterest.loading ? openInterestAsOfLabel(openInterestAsOf) : null].filter(Boolean).join(" · ");
|
|
570
|
-
const shortBasisText = [
|
|
571
|
-
+ `${new Date(
|
|
617
|
+
const shortBasisText = [footerSpot ? `${formatModelSpotPrice(footerSpot.price)} last `
|
|
618
|
+
+ `${new Date(footerSpot.observedAt).toISOString().slice(11, 16)} UTC` : null,
|
|
572
619
|
strikeChain && !listedOpenInterest.loading ? openInterestAsOf?.date ? `OI ${openInterestAsOf.date.slice(5)}` : "OI undated" : null]
|
|
573
620
|
.filter(Boolean).join(" · ");
|
|
574
621
|
usePaneFooter("options-basis", () => ({ info: basisText ? [{ id: "greeks-basis",
|
|
@@ -578,9 +625,9 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
|
|
|
578
625
|
// busy stream reads as a steady strip rather than flicker.
|
|
579
626
|
// A new expiry, a refreshed snapshot or the underlying price arriving or
|
|
580
627
|
// going stale shows at once; only the stream itself is paced.
|
|
581
|
-
const summaryResetKey = useMemo(() => ({}), [strikeSelectionKey, strikeChain,
|
|
628
|
+
const summaryResetKey = useMemo(() => ({}), [strikeSelectionKey, strikeChain, greeksSpot == null]);
|
|
582
629
|
const summaryInput = useThrottledValue(
|
|
583
|
-
useMemo(() => ({ chain: liveChain, spot, volatilities }), [liveChain,
|
|
630
|
+
useMemo(() => ({ chain: liveChain, spot: greeksSpot, volatilities }), [liveChain, greeksSpot, volatilities]),
|
|
584
631
|
OPTIONS_SUMMARY_THROTTLE_MS,
|
|
585
632
|
summaryResetKey,
|
|
586
633
|
);
|
|
@@ -594,7 +641,7 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
|
|
|
594
641
|
instrument: baseRequest?.instrument ?? null, expiration: selectedExpiration,
|
|
595
642
|
selectedEntry: strikeChain === expirationChain ? expirationChainEntry
|
|
596
643
|
: strikeChain === initialChain ? initialChainEntry : null,
|
|
597
|
-
catalogue: availableExpirations, spot, spotAsOf:
|
|
644
|
+
catalogue: availableExpirations, spot: greeksSpot, spotAsOf: modelSpot?.observedAt,
|
|
598
645
|
liveChain: liveChain !== strikeChain ? liveChain : null,
|
|
599
646
|
});
|
|
600
647
|
const enrichment = expirationUnavailable ? null : enrichmentState.snapshot;
|
|
@@ -672,6 +719,28 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
|
|
|
672
719
|
}, [scenarioContract, scenarioAvailable, scenarioMid, scenarioVolatility, createPaneFromTemplate, dividendYield, effectiveTicker,
|
|
673
720
|
effectiveExchange, selectedSide, spot, underlying?.quote?.lastUpdated]);
|
|
674
721
|
|
|
722
|
+
// A contract opens as its own listing: quote, chart and Trade. It stays on
|
|
723
|
+
// the chain's broker while that profile is on, else it opens by its symbol.
|
|
724
|
+
const contractListing = useCallback((contract: OptionContract) => {
|
|
725
|
+
const symbol = contract.contractSymbol.trim().toUpperCase();
|
|
726
|
+
const currency = contract.currency || underlying?.quote?.currency || "USD";
|
|
727
|
+
const brokerOn = !!instrument && appConfig.brokerInstances.some((profile) => profile.id === instrument.brokerInstanceId && profile.enabled !== false);
|
|
728
|
+
return { symbol, selection: { instrument: brokerOn ? chainOptionContract(symbol, instrument, currency) : null,
|
|
729
|
+
listing: { name: optionContractName(symbol) ?? symbol, exchange: "", currency, type: "OPT" } } };
|
|
730
|
+
}, [appConfig.brokerInstances, instrument, underlying?.quote?.currency]);
|
|
731
|
+
const openContract = useCallback((contract: OptionContract | null | undefined) => {
|
|
732
|
+
if (!contract) return;
|
|
733
|
+
const { symbol, selection } = contractListing(contract);
|
|
734
|
+
pinTicker(symbol, selection);
|
|
735
|
+
}, [contractListing, pinTicker]);
|
|
736
|
+
const tradable = !!scenarioContract && brokerCanTrade(appConfig.brokerInstances, getBrokerAdapter, scenarioContract.contractSymbol.trim().toUpperCase(), "OPT");
|
|
737
|
+
const tradeContract = useCallback(() => {
|
|
738
|
+
if (!scenarioContract || !tradable) return;
|
|
739
|
+
const { symbol, selection } = contractListing(scenarioContract);
|
|
740
|
+
// A new contract's ticket starts on Buy; b and s choose the side in it.
|
|
741
|
+
openTradeTicket({ pinTicker }, symbol, { action: "BUY" }, selection);
|
|
742
|
+
}, [contractListing, pinTicker, scenarioContract, tradable]);
|
|
743
|
+
|
|
675
744
|
const openSurface = useCallback(() => {
|
|
676
745
|
if (!ticker || selectedExpiration == null) return;
|
|
677
746
|
createPaneFromTemplate("vol-surface-pane", { symbol: ticker.metadata.ticker, ticker, instrument,
|
|
@@ -703,12 +772,14 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
|
|
|
703
772
|
setCalcSide(sideTarget);
|
|
704
773
|
}, [selectContract, selectedRow, selectedStrikeIdx, sideTarget]);
|
|
705
774
|
|
|
775
|
+
// With a broker that trades the contract, [t]rade opens its ticket on Buy, and Sell is chosen there.
|
|
706
776
|
const footerHints = useMemo(() => [
|
|
707
777
|
...(sideTarget ? [{ id: "side", key: SIDE_KEY, label: ` ${sideTarget}`, title: sideTarget === "put" ? "Select Put" : "Select Call", onPress: switchSide }] : []),
|
|
778
|
+
...(tradable ? [{ id: "trade-contract", key: "t", label: "rade", title: "Trade Contract", onPress: tradeContract }] : []),
|
|
708
779
|
...(calcParams ? [{ id: "calc", key: "c", label: "alc", onPress: openCalculator }] : []),
|
|
709
780
|
...(scenarioAvailable ? [{ id: "scenario", key: "a", label: "dd to OSA", onPress: openScenario }] : []),
|
|
710
781
|
...(ticker && selectedExpiration != null ? [{ id: "surface", key: "s", label: "urface", onPress: openSurface }] : []),
|
|
711
|
-
], [sideTarget, switchSide, calcParams, openCalculator, scenarioAvailable, openScenario, ticker, selectedExpiration, openSurface]);
|
|
782
|
+
], [sideTarget, switchSide, tradable, tradeContract, calcParams, openCalculator, scenarioAvailable, openScenario, ticker, selectedExpiration, openSurface]);
|
|
712
783
|
|
|
713
784
|
// Back to where the chain opens: the held contract's strike, else the money.
|
|
714
785
|
const defaultStrikeIndex = strikes.length === 0 ? -1 : (() => {
|
|
@@ -732,8 +803,10 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
|
|
|
732
803
|
{ id: "default-strike", label: parsed ? "Go to Held Strike" : "Go to ATM Strike",
|
|
733
804
|
enabled: defaultStrikeIndex >= 0, onSelect: goToDefaultStrike },
|
|
734
805
|
...(enrichment?.asOf && dialog ? [{ id: "analytics-as-of", label: "Analytics As Of…", onSelect: showAnalyticsAsOf }] : []),
|
|
806
|
+
{ id: "open-contract", label: "Open Contract", accelerator: "Enter", enabled: !!scenarioContract, onSelect: () => openContract(scenarioContract) },
|
|
807
|
+
...(tradable ? [{ id: "trade-contract", label: "Trade Contract", accelerator: "t", onSelect: tradeContract }] : []),
|
|
735
808
|
], [defaultStrikeIndex, dialog, enrichment?.asOf, expirationDates.length, goToDefaultStrike, parsed,
|
|
736
|
-
selectAdjacentExpiration, selectedExpirationIndex, showAnalyticsAsOf]);
|
|
809
|
+
selectAdjacentExpiration, selectedExpirationIndex, showAnalyticsAsOf, scenarioContract, openContract, tradable, tradeContract]);
|
|
737
810
|
|
|
738
811
|
const renderCell = useCallback((
|
|
739
812
|
row: OptionTableRow,
|
|
@@ -756,6 +829,7 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
|
|
|
756
829
|
}, [selectContract, selectedSide]);
|
|
757
830
|
|
|
758
831
|
useOptionsAccessFooter({
|
|
832
|
+
broker: !!optionQuoteBroker,
|
|
759
833
|
chain,
|
|
760
834
|
error: [error, underlyingStale ? "Underlying quote stale: Greeks and calculator unavailable" : null,
|
|
761
835
|
selectedContract && strikeChain && !selectedContractAvailable
|
|
@@ -789,7 +863,21 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
|
|
|
789
863
|
// the arrows for it; in a research tab those stay with the tab strip.
|
|
790
864
|
useShortcut((event) => {
|
|
791
865
|
if (event.defaultPrevented || event.propagationStopped || event.targetEditable) return;
|
|
792
|
-
if (event.ctrl || event.meta || event.alt
|
|
866
|
+
if (event.ctrl || event.meta || event.alt) return;
|
|
867
|
+
const surface = isPlainKey(event, "s");
|
|
868
|
+
if (surface && ticker && selectedExpiration != null) {
|
|
869
|
+
event.preventDefault();
|
|
870
|
+
event.stopPropagation();
|
|
871
|
+
openSurface();
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
if (event.shift) return;
|
|
875
|
+
if (tradable && isPlainKey(event, "t") && !event.repeat) {
|
|
876
|
+
event.preventDefault();
|
|
877
|
+
event.stopPropagation();
|
|
878
|
+
tradeContract();
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
793
881
|
|
|
794
882
|
const expiryStep = isPlainKey(event, EXPIRY_PREVIOUS_KEY) || (!nestedInTabs && isPlainKey(event, "h", "left")) ? -1
|
|
795
883
|
: isPlainKey(event, EXPIRY_NEXT_KEY) || (!nestedInTabs && isPlainKey(event, "l", "right")) ? 1 : 0;
|
|
@@ -799,12 +887,6 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
|
|
|
799
887
|
selectAdjacentExpiration(expiryStep);
|
|
800
888
|
return;
|
|
801
889
|
}
|
|
802
|
-
if (isPlainKey(event, "s") && ticker && selectedExpiration != null) {
|
|
803
|
-
event.preventDefault();
|
|
804
|
-
event.stopPropagation();
|
|
805
|
-
openSurface();
|
|
806
|
-
return;
|
|
807
|
-
}
|
|
808
890
|
if (isPlainKey(event, "c") && calcParams) {
|
|
809
891
|
event.preventDefault();
|
|
810
892
|
event.stopPropagation();
|
|
@@ -847,7 +929,7 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
|
|
|
847
929
|
// The root insets the terminal body, so the band gets the width inside it.
|
|
848
930
|
const statWidth = Math.max(1, width - (nativePaneChrome ? 0 : 2));
|
|
849
931
|
const figures = optionsSummaryItems({
|
|
850
|
-
spot: spotItem(underlying, isOpt ? undefined : ticker.metadata.assetCategory),
|
|
932
|
+
spot: spotItem(underlying, isOpt ? undefined : ticker.metadata.assetCategory, modelSpot),
|
|
851
933
|
summary, enrichment, currency: underlying?.quote?.currency || ticker.metadata.currency || "",
|
|
852
934
|
ivRank: showIvRank ? { stats: ivRank } : null,
|
|
853
935
|
});
|
|
@@ -925,6 +1007,7 @@ export function OptionsView({ width, height, focused, nestedInTabs = false, ivRa
|
|
|
925
1007
|
onVisibleRangeChange={handleVisibleStrikeRangeChange}
|
|
926
1008
|
getItemKey={(row) => String(row.strike)}
|
|
927
1009
|
renderCell={renderCell}
|
|
1010
|
+
onActivate={(row) => openContract(selectedSide === "put" ? row.put : selectedSide === "call" ? row.call : null)}
|
|
928
1011
|
selectedTextOverridesCellColor
|
|
929
1012
|
emptyStateTitle={unavailableExpiry ?? (error && !strikeChain ? "Selected expiration unavailable." : strikesLoading ? "Loading strikes..."
|
|
930
1013
|
: strikeWindowResult.total > 0 ? "No strikes in this window." : "No strikes available.")}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { unavailableText } from "../../../components/ui/status";
|
|
1
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { FieldGrid, KeyValueRow, QueryBar, usePaneFooter, usePaneNoticeFooter, type GridField } from "../../../components";
|
|
3
|
+
import { delayedFooterSegment, FieldGrid, KeyValueRow, QueryBar, usePaneFooter, usePaneNoticeFooter, type GridField } from "../../../components";
|
|
3
4
|
import { CompositeChart } from "../../../components/chart/composite/composite-chart";
|
|
4
5
|
import { usePaneRefreshKey } from "../../../components/data-table/table-pane";
|
|
5
6
|
import { useAsyncResource, useInputCapture } from "../../../public/react";
|
|
@@ -194,7 +195,7 @@ export function OptionsCalculatorPane({ focused, width, height }: PaneProps) {
|
|
|
194
195
|
const calculation = useMemo(() => {
|
|
195
196
|
const unavailable = seedError ?? (american ? dividendInput.error : null)
|
|
196
197
|
?? (surfaceSource && !draft.symbol ? "Choose an underlying ticker for surface IV." : null)
|
|
197
|
-
?? (surfaceSource && !surfaceResource.loading && surface?.volatility == null ? surface?.error ?? surfaceResource.error ?? "Surface IV
|
|
198
|
+
?? (surfaceSource && !surfaceResource.loading && surface?.volatility == null ? surface?.error ?? surfaceResource.error ?? unavailableText("Surface IV") : null);
|
|
198
199
|
if (unavailable || surfaceSource && surface?.volatility == null) {
|
|
199
200
|
return { valuation: null, implied: { volatility: null, note: null }, problem: unavailable };
|
|
200
201
|
}
|
|
@@ -279,7 +280,9 @@ export function OptionsCalculatorPane({ focused, width, height }: PaneProps) {
|
|
|
279
280
|
...(surfaceSource && surfaceResource.loading ? [{ id: "loading", parts: [{ text: "loading surface", tone: "muted" as const }] }] : []),
|
|
280
281
|
...(surfaceSource && surface?.asOf ? [{ id: "surface-asof", parts: [{ text: `surface · ${surface.asOf}`, tone: "muted" as const }] }] : []),
|
|
281
282
|
...(effectiveSteps && effectiveSteps !== (draft.steps ?? 400) ? [{ id: "refined", parts: [{ text: `tree refined to ${effectiveSteps} steps`, tone: "muted" as const }] }] : []),
|
|
282
|
-
...(liveInputs ? [
|
|
283
|
+
...(liveInputs ? [liveInputs.delayed
|
|
284
|
+
? delayedFooterSegment({ id: "market", detail: "Delayed market inputs" })
|
|
285
|
+
: { id: "market", parts: [{ text: "real-time market", tone: "muted" as const }] }] : []),
|
|
283
286
|
],
|
|
284
287
|
hints: activeFieldId ? [] : [
|
|
285
288
|
// Names the cell Enter or `e` edits, which the grid does not mark.
|
|
@@ -398,7 +401,7 @@ function ContractPremiumChart({
|
|
|
398
401
|
width: number;
|
|
399
402
|
focused: boolean;
|
|
400
403
|
}) {
|
|
401
|
-
const empty = unavailable ? "Premium history
|
|
404
|
+
const empty = unavailable ? unavailableText("Premium history") : series.length < 1 ? "No premium history." : undefined;
|
|
402
405
|
return (
|
|
403
406
|
<Box flexDirection="column" paddingX={1} height={PREMIUM_CHART_ROWS} flexShrink={0}>
|
|
404
407
|
{empty && !loading ? (
|
|
@@ -2,6 +2,8 @@ import type { HeadlessPaneDefinition, HeadlessPaneEntry } from "../../../types/p
|
|
|
2
2
|
import { expirationOptionSeconds, expiryIsoDate, OPTION_EXPIRATION_FORMAT, OPTION_EXPIRATION_PLACEHOLDER } from "../../../utils/option-expiry";
|
|
3
3
|
import { formatUtcTime } from "../../../utils/utc-time";
|
|
4
4
|
import { loadGamma, loadOpenInterest, positioningSymbol, type GammaPayload, type OpenInterestPayload } from "./client";
|
|
5
|
+
import { listingFateReport, loadListingFate } from "../../../tickers/listing-fate";
|
|
6
|
+
import { resolveHeadlessInstrument } from "../shared/headless-market-data";
|
|
5
7
|
import {
|
|
6
8
|
ALL_EXPIRIES,
|
|
7
9
|
expiryLabel,
|
|
@@ -10,6 +12,7 @@ import {
|
|
|
10
12
|
formatDistance,
|
|
11
13
|
formatGamma,
|
|
12
14
|
formatLevel,
|
|
15
|
+
formatPositioningSpot,
|
|
13
16
|
formatPayout,
|
|
14
17
|
formatRatio,
|
|
15
18
|
formatStrike,
|
|
@@ -49,7 +52,7 @@ function summary(openInterest: OpenInterestPayload, dealer: GammaPayload | null)
|
|
|
49
52
|
{ key: "oiDate", label: "Open interest as of", value: openInterest.oiDate },
|
|
50
53
|
];
|
|
51
54
|
const textEntries: HeadlessPaneEntry[] = [
|
|
52
|
-
{ label: "Spot", value: spot, formatted:
|
|
55
|
+
{ label: "Spot", value: spot, formatted: formatPositioningSpot(spot, spotAsOf) },
|
|
53
56
|
...(dealer ? [{ label: "Quotes as of", value: dealer.quotesAsOf, formatted: time(dealer.quotesAsOf) }] : []),
|
|
54
57
|
{ label: "Open interest as of", value: openInterest.oiDate, formatted: openInterest.oiDate ?? "--" },
|
|
55
58
|
];
|
|
@@ -118,7 +121,7 @@ export const optionsPositioningHeadless = (initialTab: PositioningTab): Headless
|
|
|
118
121
|
// VIX has no dealer gamma, so its expiries have no column for it.
|
|
119
122
|
const expiryGamma = !(allExpiries instanceof Error) && allExpiries.total ? netGammaByExpiry(allExpiries) : null;
|
|
120
123
|
const gammaColumn = allExpiries instanceof Error || !!allExpiries.total;
|
|
121
|
-
const
|
|
124
|
+
const loadErrors = [
|
|
122
125
|
...openInterest.warnings,
|
|
123
126
|
...(dealer instanceof Error ? [`Dealer gamma: ${dealer.message}`] : [
|
|
124
127
|
...dealer.warnings,
|
|
@@ -126,6 +129,11 @@ export const optionsPositioningHeadless = (initialTab: PositioningTab): Headless
|
|
|
126
129
|
]),
|
|
127
130
|
...(everyExpiry instanceof Error ? [`Net GEX by expiry: ${everyExpiry.message}`] : []),
|
|
128
131
|
];
|
|
132
|
+
// A symbol with no listed open interest says what became of it when something did (TWTR stopped
|
|
133
|
+
// trading, FB is a fund now) in place of the load's errors.
|
|
134
|
+
const fate = openInterest.expiries.length || expiry ? null
|
|
135
|
+
: listingFateReport(await loadListingFate(await resolveHeadlessInstrument(ctx, symbol), ctx.marketData), { served: false }, loadErrors);
|
|
136
|
+
const errors = fate?.errors ?? loadErrors;
|
|
129
137
|
const changes = openInterest.previousOiDate != null;
|
|
130
138
|
const gammaShown = dealer instanceof Error || !dealer.total ? null : dealer;
|
|
131
139
|
const scope = dealer instanceof Error ? null : dealer.expiry ? expiryLabel(dealer.expiry) : "all expiries";
|
|
@@ -173,7 +181,7 @@ export const optionsPositioningHeadless = (initialTab: PositioningTab): Headless
|
|
|
173
181
|
],
|
|
174
182
|
complete: !!openInterest.expiries.length && !(dealer instanceof Error) && errors.length === 0,
|
|
175
183
|
unavailableSymbols: openInterest.expiries.length ? [] : [symbol],
|
|
176
|
-
errors,
|
|
184
|
+
errors, ...(fate?.notes.length ? { notes: fate.notes } : {}),
|
|
177
185
|
metadata: {
|
|
178
186
|
underlying: openInterest.underlying,
|
|
179
187
|
oiDate: openInterest.oiDate,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { formatCompact, formatCompactAmount } from "../../../utils/format";
|
|
2
2
|
import { formatExpDate } from "../../../utils/options";
|
|
3
|
+
import { modelSpotLabel, modelSpotStamp, modelSpotStampVariants, observedModelSpot } from "../options/model-spot";
|
|
3
4
|
|
|
4
5
|
export type PositioningTab = "strikes" | "expiries" | "gex";
|
|
5
6
|
export const POSITIONING_TABS: { value: PositioningTab; label: string }[] = [
|
|
@@ -66,6 +67,29 @@ export function formatRatio(value: number | null | undefined): string {
|
|
|
66
67
|
export function formatLevel(value: number | null | undefined): string {
|
|
67
68
|
return value == null || !Number.isFinite(value) ? "--" : value.toFixed(2);
|
|
68
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* The spot Gloom Cloud read the distances and dealer gamma at, stamped as
|
|
72
|
+
* every options pane stamps its spot (`modelSpotStamp`), as a stat cell
|
|
73
|
+
* carries it: `pre-market Wed 21 Oct 09:35 UTC`, its shorter forms, then
|
|
74
|
+
* none, so the band keeps the columns it has in regular hours. Nothing for
|
|
75
|
+
* the regular session's price.
|
|
76
|
+
*/
|
|
77
|
+
export function positioningSpotDetail(spot: number | null | undefined, spotAsOf: string | null | undefined):
|
|
78
|
+
{ detail?: string; detailVariants?: string[] } {
|
|
79
|
+
const at = spotAsOf ? Date.parse(spotAsOf) : Number.NaN;
|
|
80
|
+
if (spot == null || !Number.isFinite(at)) return {};
|
|
81
|
+
const observed = observedModelSpot(spot, at);
|
|
82
|
+
const stamp = modelSpotStamp(observed);
|
|
83
|
+
return stamp ? { detail: stamp, detailVariants: [...modelSpotStampVariants(observed), ""] } : {};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The spot as a report says it: `770.88 pre-market Wed 21 Oct 09:35 UTC`, `770.10 last Wed 21 Oct 14:05 UTC`. */
|
|
87
|
+
export function formatPositioningSpot(spot: number | null | undefined, spotAsOf: string | null | undefined): string {
|
|
88
|
+
const at = spotAsOf ? Date.parse(spotAsOf) : Number.NaN;
|
|
89
|
+
if (spot == null || !Number.isFinite(spot)) return "--";
|
|
90
|
+
return Number.isFinite(at) ? `${formatLevel(spot)} ${modelSpotLabel(observedModelSpot(spot, at))}` : formatLevel(spot);
|
|
91
|
+
}
|
|
92
|
+
|
|
69
93
|
/** Where a level sits against spot: +0.6%. */
|
|
70
94
|
export function formatDistance(level: number | null | undefined, spot: number | null | undefined): string {
|
|
71
95
|
if (level == null || spot == null || !(spot > 0)) return "--";
|
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
formatDistance,
|
|
58
58
|
formatGamma,
|
|
59
59
|
formatLevel,
|
|
60
|
+
positioningSpotDetail,
|
|
60
61
|
formatPayout,
|
|
61
62
|
formatRatio,
|
|
62
63
|
formatStrike,
|
|
@@ -204,6 +205,7 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
204
205
|
const openInterest = useAsyncResource<OpenInterestPayload>(openInterestLoader, { keepPreviousData: true });
|
|
205
206
|
const data = openInterest.data;
|
|
206
207
|
const spot = data?.spot ?? null;
|
|
208
|
+
const spotStamp = useMemo(() => positioningSpotDetail(spot, data?.spotAsOf), [spot, data?.spotAsOf]);
|
|
207
209
|
const expiries = data?.expiries ?? [];
|
|
208
210
|
const shownExpiry = data?.expiry ?? requestedExpiry;
|
|
209
211
|
const changes = data?.previousOiDate != null;
|
|
@@ -264,9 +266,7 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
264
266
|
enabled: expiryIndex < expiryDates.length - 1, onSelect: () => stepExpiry(1) },
|
|
265
267
|
], [expiryDates.length, expiryIndex, stepExpiry, tab]);
|
|
266
268
|
|
|
267
|
-
const { strip: tabStrip, rows: tabRows } = usePaneTabs(
|
|
268
|
-
? { tabs, activeValue: tab, onSelect: (value: string) => setTab(value), focused, dense: true }
|
|
269
|
-
: null);
|
|
269
|
+
const { strip: tabStrip, rows: tabRows } = usePaneTabs({ tabs, activeValue: tab, onSelect: (value: string) => setTab(value), focused, dense: true });
|
|
270
270
|
const bodyHeight = Math.max(3, height - tabRows);
|
|
271
271
|
|
|
272
272
|
// Footer: what the data is (delayed or real time, the OI session), and its gaps.
|
|
@@ -335,13 +335,13 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
335
335
|
const strikeFigures = useMemo<StatItem[]>(() => !callTotal ? [] : [
|
|
336
336
|
{ id: "max-pain", label: "Max pain", value: maxPain == null ? "--" : formatStrike(maxPain), color: colors.warning,
|
|
337
337
|
detail: maxPain == null ? undefined : `${formatDistance(maxPain, spot)} vs spot` },
|
|
338
|
-
{ id: "spot", label: "Spot", value: formatLevel(spot) },
|
|
338
|
+
{ id: "spot", label: "Spot", value: formatLevel(spot), ...spotStamp },
|
|
339
339
|
{ id: "pc", label: "P/C OI", value: formatRatio(callTotal.putCallRatio) },
|
|
340
340
|
{ id: "calls", label: "Call OI", value: formatCompact(callTotal.callOI), color: colors.positive,
|
|
341
341
|
detail: callTotal.callChange == null ? undefined : formatCountChange(callTotal.callChange) },
|
|
342
342
|
{ id: "puts", label: "Put OI", value: formatCompact(callTotal.putOI), color: colors.negative,
|
|
343
343
|
detail: callTotal.putChange == null ? undefined : formatCountChange(callTotal.putChange) },
|
|
344
|
-
], [callTotal, colors.negative, colors.positive, colors.warning, maxPain, spot]);
|
|
344
|
+
], [callTotal, colors.negative, colors.positive, colors.warning, maxPain, spot, spotStamp]);
|
|
345
345
|
const selectedStrikeRow = strikes.find((row) => strikeKey(row) === strikeSelectedId) ?? null;
|
|
346
346
|
const strikeStrip = useMemo<ChartStripSpec | null>(() => strikeWindow.length < 3 ? null : {
|
|
347
347
|
label: "Open interest by strike",
|
|
@@ -404,9 +404,9 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
404
404
|
split: [{ id: "calls", value: calls, color: colors.positive }, { id: "puts", value: puts, color: colors.negative }] },
|
|
405
405
|
{ id: "pc", label: "P/C OI", value: formatRatio(calls > 0 ? puts / calls : null) },
|
|
406
406
|
{ id: "largest", label: "Largest expiry", value: expiryLabel(largest.date), detail: formatCompact(expirySize(largest)) },
|
|
407
|
-
{ id: "spot", label: "Spot", value: formatLevel(spot) },
|
|
407
|
+
{ id: "spot", label: "Spot", value: formatLevel(spot), ...spotStamp },
|
|
408
408
|
];
|
|
409
|
-
}, [changes, colors.negative, colors.positive, expiries, spot]);
|
|
409
|
+
}, [changes, colors.negative, colors.positive, expiries, spot, spotStamp]);
|
|
410
410
|
const selectedExpiryRow = expiries.find((row) => row.date === expirySelectedId) ?? null;
|
|
411
411
|
const expiryStrip = useMemo<ChartStripSpec | null>(() => expiries.length < 3 ? null : {
|
|
412
412
|
label: "Open interest by expiry",
|
|
@@ -479,7 +479,7 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
479
479
|
})),
|
|
480
480
|
...(band ? [{ id: "band", label: "Dealer range", value: `${formatGamma(band.low)} to ${formatGamma(band.high)}`,
|
|
481
481
|
detail: `${shares} with dealers${gammaData.boxes?.length ? ", boxes out" : ""}` }] : []),
|
|
482
|
-
{ id: "spot", label: "Spot", value: formatLevel(gammaSpot) },
|
|
482
|
+
{ id: "spot", label: "Spot", value: formatLevel(gammaSpot), ...positioningSpotDetail(gammaSpot, gammaData.spotAsOf) },
|
|
483
483
|
], [band, colors.negative, colors.positive, gammaData, gammaSpot, shares, walls]);
|
|
484
484
|
// The assumption rides in the legend, where the numbers it changes are read; the
|
|
485
485
|
// dealer range figure names its own share. It gives way before the series name does.
|
|
@@ -610,13 +610,11 @@ function OptionsPositioningView({ width, height, focused, symbol }: PaneProps &
|
|
|
610
610
|
}
|
|
611
611
|
|
|
612
612
|
return <Box width={width} height={height} flexDirection="column">
|
|
613
|
+
{tabStrip}
|
|
613
614
|
<PaneStatusBody loading={openInterest.loading && !data} error={!data ? openInterest.error : null}
|
|
614
615
|
empty={!!data && !data.expiries.length} subject="open interest"
|
|
615
616
|
emptyTitle={`No listed options for ${parsePublicTickerKey(symbol).symbol.replace(/^\^/, "")}.`}>
|
|
616
|
-
{data?.expiries.length ?
|
|
617
|
-
{tabStrip}
|
|
618
|
-
{content}
|
|
619
|
-
</> : null}
|
|
617
|
+
{data?.expiries.length ? content : null}
|
|
620
618
|
</PaneStatusBody>
|
|
621
619
|
</Box>;
|
|
622
620
|
}
|
|
@@ -13,7 +13,7 @@ import type { YieldPoint } from "../yield-curve/treasury-data";
|
|
|
13
13
|
import { parseLegs, scenarioCurrency, validatePosition, type ScenarioControls, type ScenarioLeg, type ScenarioPosition } from "./model";
|
|
14
14
|
import { loadModelDividendYield, type ModelDividendYield } from "../../../market-data/model-dividend-yield";
|
|
15
15
|
import { abortable, abortError } from "../../../utils/async-deadline";
|
|
16
|
-
import { extendedSession } from "../options/model-spot";
|
|
16
|
+
import { extendedSession, modelSpotStamp, modelSpotStampVariants, type OptionModelSpot } from "../options/model-spot";
|
|
17
17
|
import { errorMessage } from "../../../utils/errors";
|
|
18
18
|
|
|
19
19
|
export interface ScenarioMarketSnapshot {
|
|
@@ -191,6 +191,24 @@ export function scenarioSpotBasis(
|
|
|
191
191
|
return (market?.underlyingQuote ? extendedSession(market.underlyingQuote) : null) ?? "last price";
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
const EXTENDED_BASES: readonly string[] = ["pre-market", "after hours", "overnight"];
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* A spot basis as the pane and report name it: an extended-session print with
|
|
198
|
+
* its time, stamped as every options pane stamps its spot (`modelSpotStamp`),
|
|
199
|
+
* `pre-market Wed 21 Oct 09:42 UTC`; any other basis as it is.
|
|
200
|
+
*/
|
|
201
|
+
export function scenarioSpotStamp(basis: string, observedAt: number): string {
|
|
202
|
+
if (!EXTENDED_BASES.includes(basis)) return basis;
|
|
203
|
+
return modelSpotStamp({ basis: "last", session: basis as OptionModelSpot["session"], observedAt }) ?? basis;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** That stamp's shorter forms, longest first (`modelSpotStampVariants`): `pre-market 09:42 UTC`, `pre-market`. */
|
|
207
|
+
export function scenarioSpotStampVariants(basis: string, observedAt: number): string[] {
|
|
208
|
+
if (!EXTENDED_BASES.includes(basis)) return [];
|
|
209
|
+
return modelSpotStampVariants({ basis: "last", session: basis as OptionModelSpot["session"], observedAt });
|
|
210
|
+
}
|
|
211
|
+
|
|
194
212
|
function supplied(value: unknown): boolean { return value != null && value !== ""; }
|
|
195
213
|
|
|
196
214
|
/** A position typed whole: its legs and the spot, rate and dividend yield they are valued at, so no market data is read. */
|