gloomberb 0.18.4 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +169 -5
- package/src/account/auth-model.ts +7 -3
- package/src/api-client/auth.ts +9 -4
- package/src/api-client/catalysts.ts +2 -1
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +31 -6
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/index.ts +171 -11
- package/src/api-client/live-scan.ts +91 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +13 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/request.ts +222 -41
- package/src/api-client/research-activity.ts +6 -12
- package/src/api-client/types.ts +74 -0
- package/src/app/keybindings/index.ts +3 -0
- package/src/app/keybindings/labels.ts +18 -1
- package/src/app/keybindings/resolve.ts +14 -0
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +42 -27
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/core-builtin-allowlist.json +1 -1
- package/src/architecture/pane-tabs-allowlist.json +3 -0
- package/src/architecture/ttl-caches-allowlist.json +19 -0
- package/src/brokers/sync-broker-instance.ts +2 -8
- package/src/capabilities/chart-series.ts +1 -1
- package/src/cli/commands/crypto-hints.ts +40 -24
- package/src/cli/commands/fred-series.ts +5 -0
- package/src/cli/commands/keybindings.ts +23 -0
- package/src/cli/commands/market.ts +138 -46
- package/src/cli/commands/options-chain.ts +107 -10
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +176 -39
- package/src/cli/commands/search.ts +39 -3
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +57 -35
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +10 -2
- package/src/cli/export-source.ts +29 -1
- package/src/cli/glossary.ts +73 -1
- package/src/cli/history-rows.ts +87 -16
- package/src/cli/index.ts +2 -0
- package/src/cli/known-gaps.ts +5 -2
- package/src/cli/listing-arg.ts +195 -56
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +3 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +111 -16
- package/src/cli/pane-functions/function-help.ts +23 -13
- package/src/cli/pane-functions/headless.ts +56 -12
- package/src/cli/pane-functions/index.ts +58 -16
- package/src/cli/pane-functions/market-session.ts +81 -9
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/report.ts +5 -1
- package/src/cli/pane-functions/resolver.ts +35 -8
- package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
- package/src/cli/pane-functions/screenshot-types.ts +5 -0
- package/src/cli/quote-answer.ts +30 -13
- package/src/cli/report-tables.ts +18 -7
- package/src/cli/restore-plugins.ts +13 -1
- package/src/cli/result.ts +57 -5
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +26 -4
- package/src/components/chart/composite/format.ts +27 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/levels.ts +2 -0
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +61 -5
- package/src/components/chart/composite/range-statistics.ts +62 -0
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +46 -7
- package/src/components/chart/composite/series-axis-tags.tsx +16 -9
- package/src/components/chart/composite/series-cues.ts +14 -0
- package/src/components/chart/composite/text-renderer.ts +9 -2
- package/src/components/chart/composite/tool-catalog.ts +36 -0
- package/src/components/chart/composite/tools.ts +203 -44
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
- package/src/components/command-bar/assist/inventory.ts +2 -1
- package/src/components/command-bar/commands/direct/index.ts +28 -13
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/panel/prompt-binding.ts +0 -8
- package/src/components/command-bar/workflow/alert.ts +1 -1
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/header.tsx +26 -40
- package/src/components/layout/pane/footer/index.tsx +1 -0
- package/src/components/layout/pane/footer/model.ts +27 -0
- package/src/components/onboarding/onboarding-frame.tsx +1 -1
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
- package/src/components/onboarding/wizard-broker-sync.ts +0 -1
- package/src/components/onboarding/wizard-positions.ts +3 -3
- package/src/components/ticker/list-table-view.tsx +4 -1
- package/src/components/ui/choice-dialog.tsx +23 -18
- package/src/components/ui/choice-groups.ts +18 -0
- package/src/components/ui/data-table/opentui/index.tsx +9 -7
- package/src/components/ui/data-table/opentui/model.ts +26 -1
- package/src/components/ui/data-table/types.ts +2 -0
- package/src/components/ui/display.tsx +5 -1
- package/src/components/ui/list-view.tsx +8 -4
- package/src/components/ui/query-bar-keys.tsx +2 -0
- package/src/components/ui/query-bar.tsx +1 -0
- package/src/components/ui/select-button.tsx +5 -1
- package/src/components/ui/select-field.tsx +1 -0
- package/src/components/ui/table-layout.ts +8 -5
- package/src/core/app-runtime.ts +2 -0
- package/src/core/app-services.ts +2 -2
- package/src/core/state/app/layout.ts +2 -0
- package/src/core/state/app/state.ts +9 -0
- package/src/core/state/app/types.ts +3 -0
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/saved-copies.ts +55 -16
- package/src/data/ticker-repository.ts +16 -9
- package/src/data/ticker-writes.ts +81 -0
- package/src/data/ttl-cache.ts +165 -0
- package/src/docs/snapshot.json +427 -216
- package/src/i18n/es.ts +19 -3
- package/src/i18n/ja.ts +19 -3
- package/src/i18n/ko.ts +19 -3
- package/src/i18n/zh-cn.ts +19 -3
- package/src/i18n/zh-tw.ts +19 -3
- package/src/market-data/coordinator/financials.ts +10 -2
- package/src/market-data/coordinator/index.ts +3 -1
- package/src/market-data/coordinator/quotes.ts +4 -2
- package/src/market-data/depositary-pair.ts +2 -2
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +48 -7
- package/src/market-data/market/dated-previous-close.ts +203 -0
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/market/futures-week.ts +68 -0
- package/src/market-data/market/status.ts +16 -4
- package/src/market-data/options-alternatives.ts +141 -4
- package/src/market-data/quotes/contributions.ts +20 -2
- package/src/market-data/quotes/resolution.ts +3 -0
- package/src/market-data/selectors.ts +3 -0
- package/src/news/aggregator.ts +113 -9
- package/src/news/cloud-session.ts +21 -0
- package/src/news/hooks.ts +1 -0
- package/src/news/news-model.ts +1 -0
- package/src/news/types.ts +11 -0
- package/src/notifications/notification-log.ts +6 -4
- package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +380 -82
- package/src/plugins/builtin/alerts/command.ts +31 -0
- package/src/plugins/builtin/alerts/constants.ts +1 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
- package/src/plugins/builtin/alerts/fired-log.ts +113 -8
- package/src/plugins/builtin/alerts/format.ts +80 -4
- package/src/plugins/builtin/alerts/headless.ts +30 -7
- package/src/plugins/builtin/alerts/history-rows.ts +7 -3
- package/src/plugins/builtin/alerts/history.ts +2 -0
- package/src/plugins/builtin/alerts/index.tsx +135 -22
- package/src/plugins/builtin/alerts/live.ts +3 -3
- package/src/plugins/builtin/alerts/metric-command.ts +134 -0
- package/src/plugins/builtin/alerts/metric-data.ts +111 -0
- package/src/plugins/builtin/alerts/metric-model.ts +324 -0
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
- package/src/plugins/builtin/alerts/pane.tsx +90 -39
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +37 -10
- package/src/plugins/builtin/alerts/types.ts +7 -0
- package/src/plugins/builtin/analytics/index.tsx +2 -1
- package/src/plugins/builtin/analytics/risk-client.ts +16 -5
- package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
- package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
- package/src/plugins/builtin/analytics/risk-model.ts +55 -9
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/attention/index.tsx +2 -1
- package/src/plugins/builtin/attention/listing.ts +14 -0
- package/src/plugins/builtin/backtest/headless.ts +10 -5
- package/src/plugins/builtin/backtest/pane.tsx +9 -4
- package/src/plugins/builtin/backtest/presets.ts +29 -3
- package/src/plugins/builtin/broker-manager/index.tsx +10 -5
- package/src/plugins/builtin/broker-manager/model.ts +6 -1
- package/src/plugins/builtin/broker-trading/controller.ts +22 -12
- package/src/plugins/builtin/broker-trading/index.tsx +13 -1
- package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
- package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +12 -4
- package/src/plugins/builtin/buildout/format.ts +38 -0
- package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
- package/src/plugins/builtin/buildout/table-model.ts +23 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +7 -3
- package/src/plugins/builtin/catalysts/model.ts +2 -1
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
- package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
- package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
- package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
- package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
- package/src/plugins/builtin/chart-composer/formula.ts +71 -18
- package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
- package/src/plugins/builtin/chart-composer/headless.ts +19 -3
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
- package/src/plugins/builtin/chart-composer/presets.ts +50 -15
- package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
- package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
- package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
- package/src/plugins/builtin/chat/content/composer.tsx +13 -6
- package/src/plugins/builtin/chat/content/footer.tsx +6 -3
- package/src/plugins/builtin/chat/content/index.tsx +50 -21
- package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
- package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
- package/src/plugins/builtin/chat/index.tsx +23 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
- package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
- package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
- package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
- package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
- package/src/plugins/builtin/clinical-trials/model.ts +0 -9
- package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- package/src/plugins/builtin/cloud/plugin.tsx +1 -1
- package/src/plugins/builtin/company-attention/building.tsx +5 -1
- package/src/plugins/builtin/company-attention/client.ts +8 -9
- package/src/plugins/builtin/company-attention/evidence.ts +4 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +9 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/correlation/headless.ts +13 -5
- package/src/plugins/builtin/correlation/history.ts +22 -0
- package/src/plugins/builtin/cot/client.ts +22 -18
- package/src/plugins/builtin/cot/headless.ts +15 -10
- package/src/plugins/builtin/cot/model.ts +14 -1
- package/src/plugins/builtin/cot/pane.tsx +22 -14
- package/src/plugins/builtin/credit-boards/client.ts +6 -10
- package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
- package/src/plugins/builtin/credit-conditions/model.ts +4 -0
- package/src/plugins/builtin/credit-conditions/series.ts +3 -1
- package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
- package/src/plugins/builtin/credit-documents/export.ts +173 -0
- package/src/plugins/builtin/credit-documents/headless.ts +7 -7
- package/src/plugins/builtin/credit-documents/model.ts +104 -14
- package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
- package/src/plugins/builtin/custom-view/sources.ts +2 -0
- package/src/plugins/builtin/dividend-yield/client.ts +131 -23
- package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
- package/src/plugins/builtin/dividend-yield/model.ts +8 -5
- package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
- package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
- package/src/plugins/builtin/dividend-yield/types.ts +20 -2
- package/src/plugins/builtin/dividend-yield/view.ts +30 -2
- package/src/plugins/builtin/docs/targets.ts +5 -1
- package/src/plugins/builtin/earnings/board-model.ts +16 -15
- package/src/plugins/builtin/earnings/board.tsx +62 -71
- package/src/plugins/builtin/earnings/headless.ts +40 -4
- package/src/plugins/builtin/earnings/scope.ts +31 -43
- package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
- package/src/plugins/builtin/econ/calendar-model.ts +14 -9
- package/src/plugins/builtin/econ/figures.ts +72 -0
- package/src/plugins/builtin/econ/index.tsx +33 -3
- package/src/plugins/builtin/econ/types.ts +4 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
- package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
- package/src/plugins/builtin/econ-statistics/view.ts +3 -0
- package/src/plugins/builtin/equity-screener/categories.ts +179 -0
- package/src/plugins/builtin/equity-screener/client.ts +4 -2
- package/src/plugins/builtin/equity-screener/headless.ts +144 -13
- package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
- package/src/plugins/builtin/equity-screener/model.ts +99 -10
- package/src/plugins/builtin/equity-screener/pane.tsx +70 -15
- package/src/plugins/builtin/exposure/charts.tsx +2 -2
- package/src/plugins/builtin/exposure/model.ts +13 -1
- package/src/plugins/builtin/exposure/pane.tsx +4 -4
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/index.tsx +1 -0
- package/src/plugins/builtin/futures/model.ts +2 -1
- package/src/plugins/builtin/futures/table.tsx +2 -2
- package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
- package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
- package/src/plugins/builtin/fx-matrix/client.ts +11 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
- package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
- package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -3
- package/src/plugins/builtin/gpu/headless.ts +8 -2
- package/src/plugins/builtin/help/function-index.ts +4 -1
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +18 -1
- package/src/plugins/builtin/iv-history/client.ts +52 -11
- package/src/plugins/builtin/iv-history/format.ts +36 -23
- package/src/plugins/builtin/iv-history/headless.ts +36 -18
- package/src/plugins/builtin/iv-history/index.tsx +2 -2
- package/src/plugins/builtin/iv-history/model.ts +163 -27
- package/src/plugins/builtin/iv-history/pane.tsx +10 -6
- package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
- package/src/plugins/builtin/iv-history/universe.ts +5 -1
- package/src/plugins/builtin/jobs/client.ts +25 -29
- package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
- package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
- package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
- package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
- package/src/plugins/builtin/macro-day/pane.tsx +7 -5
- package/src/plugins/builtin/macro-day/release-table.ts +2 -2
- package/src/plugins/builtin/market-heatmap/data.ts +8 -33
- package/src/plugins/builtin/market-movers/client.ts +25 -7
- package/src/plugins/builtin/market-movers/footer.ts +2 -2
- package/src/plugins/builtin/market-movers/headless.ts +32 -12
- package/src/plugins/builtin/market-movers/index.tsx +26 -14
- package/src/plugins/builtin/market-movers/min-float.ts +74 -0
- package/src/plugins/builtin/market-movers/model.ts +4 -1
- package/src/plugins/builtin/market-movers/screener.ts +14 -0
- package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
- package/src/plugins/builtin/market-movers/session-table.ts +4 -3
- package/src/plugins/builtin/market-movers/table.ts +17 -5
- package/src/plugins/builtin/members/breadth-pane.tsx +82 -0
- package/src/plugins/builtin/members/client.ts +19 -4
- package/src/plugins/builtin/members/headless.ts +20 -7
- package/src/plugins/builtin/members/index.tsx +9 -9
- package/src/plugins/builtin/members/model.ts +23 -4
- package/src/plugins/builtin/members/pane.tsx +25 -15
- package/src/plugins/builtin/news/index.tsx +1 -0
- package/src/plugins/builtin/news/wire/categories.ts +19 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
- package/src/plugins/builtin/openfda/client.ts +14 -2
- package/src/plugins/builtin/openfda/index.tsx +3 -2
- package/src/plugins/builtin/openfda/model.ts +9 -0
- package/src/plugins/builtin/openfda/pane.tsx +42 -6
- package/src/plugins/builtin/options/analytics.ts +54 -6
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/model-spot.ts +5 -2
- package/src/plugins/builtin/options/strike-window.ts +2 -1
- package/src/plugins/builtin/options/table.ts +6 -9
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +42 -12
- package/src/plugins/builtin/options-positioning/client.ts +30 -0
- package/src/plugins/builtin/options-positioning/headless.ts +74 -21
- package/src/plugins/builtin/options-positioning/index.tsx +4 -4
- package/src/plugins/builtin/options-positioning/model.ts +122 -14
- package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
- package/src/plugins/builtin/options-scenario/client.ts +116 -47
- package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
- package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
- package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
- package/src/plugins/builtin/options-scenario/csp.ts +8 -2
- package/src/plugins/builtin/options-scenario/index.tsx +9 -7
- package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
- package/src/plugins/builtin/pe-band/model.ts +12 -49
- package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
- package/src/plugins/builtin/perps/cascade.ts +236 -0
- package/src/plugins/builtin/perps/client.ts +56 -2
- package/src/plugins/builtin/perps/freshness.ts +12 -10
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/live-rows.ts +10 -3
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +32 -31
- package/src/plugins/builtin/perps/status-line.ts +25 -0
- package/src/plugins/builtin/plugin-module.ts +5 -1
- package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
- package/src/plugins/builtin/portfolio-list/cli/render.ts +12 -7
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/rate-path/pane.tsx +2 -2
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/client.ts +18 -4
- package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
- package/src/plugins/builtin/relative-rotation/model.ts +16 -40
- package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
- package/src/plugins/builtin/research/event-model.ts +10 -8
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +11 -9
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
- package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
- package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
- package/src/plugins/builtin/scanner/feed.ts +2 -2
- package/src/plugins/builtin/scanner/flow-model.ts +50 -4
- package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
- package/src/plugins/builtin/scanner/flow-session.ts +146 -0
- package/src/plugins/builtin/scanner/headless.ts +204 -35
- package/src/plugins/builtin/scanner/index.ts +40 -6
- package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
- package/src/plugins/builtin/scanner/live-feed.ts +168 -0
- package/src/plugins/builtin/scanner/live-headless.ts +138 -0
- package/src/plugins/builtin/scanner/live-model.ts +362 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
- package/src/plugins/builtin/scanner/live-track.ts +186 -0
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/headless.ts +10 -1
- package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
- package/src/plugins/builtin/sec/fund-facts.ts +116 -34
- package/src/plugins/builtin/sec/headless.ts +2 -1
- package/src/plugins/builtin/series-inventory.ts +3 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
- package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
- package/src/plugins/builtin/shared/earnings-report.ts +2 -2
- package/src/plugins/builtin/shared/job-posting.ts +1 -0
- package/src/plugins/builtin/shared/macro-releases.ts +65 -0
- package/src/plugins/builtin/shared/members-menu.tsx +3 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
- package/src/plugins/builtin/shared/report-freshness.ts +3 -2
- package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
- package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
- package/src/plugins/builtin/shared/ticker-company.ts +77 -0
- package/src/plugins/builtin/shared/use-quote-board.ts +10 -3
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/headless.ts +6 -4
- package/src/plugins/builtin/themes/model.ts +17 -8
- package/src/plugins/builtin/themes/pane.tsx +7 -7
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
- package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +6 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
- package/src/plugins/builtin/time-sales/pane.tsx +31 -19
- package/src/plugins/builtin/treasury-daily/client.ts +41 -5
- package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
- package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
- package/src/plugins/builtin/treasury-daily/model.ts +50 -3
- package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
- package/src/plugins/builtin/vol-surface/charts.tsx +9 -4
- package/src/plugins/builtin/vol-surface/client.ts +9 -17
- package/src/plugins/builtin/vol-surface/headless.ts +63 -14
- package/src/plugins/builtin/vol-surface/index.tsx +2 -2
- package/src/plugins/builtin/vol-surface/model.ts +22 -7
- package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
- package/src/plugins/builtin/vol-surface/tables.ts +31 -3
- package/src/plugins/builtin/volatility/dvol.ts +46 -0
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +4 -4
- package/src/plugins/builtin/volatility/series.ts +26 -0
- package/src/plugins/builtin/world-indices/footer.ts +1 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/catalog-browser.ts +13 -8
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +5 -5
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
- package/src/remote/resources.ts +2 -1
- package/src/renderers/browser/app-services.ts +1 -7
- package/src/renderers/browser/config-host.ts +2 -55
- package/src/renderers/browser/main.tsx +7 -57
- package/src/renderers/dom/data-table/row.tsx +26 -0
- package/src/renderers/dom/desktop/list-view.tsx +13 -10
- package/src/renderers/dom/desktop/query-bar.tsx +4 -2
- package/src/renderers/dom/desktop/select-field.tsx +4 -2
- package/src/renderers/dom/host/chart-surface.tsx +21 -0
- package/src/renderers/electrobun/bun/desktop/app-name.ts +222 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
- package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
- package/src/renderers/electrobun/bun/index.ts +25 -1
- package/src/renderers/electrobun/shared/app-name.ts +30 -0
- package/src/renderers/electrobun/shared/protocol.ts +10 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +3 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- package/src/renderers/opentui/toast-host.tsx +71 -43
- package/src/series/registry.ts +1 -0
- package/src/series/types.ts +6 -0
- package/src/series/validate.ts +1 -0
- package/src/sources/gloomberb-cloud/index.ts +1 -0
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
- package/src/sources/provider-router/batches.ts +10 -2
- package/src/sources/provider-router/financial-routes.ts +3 -3
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/history.ts +28 -7
- package/src/sources/provider-router/index.ts +149 -10
- package/src/sources/provider-router/search.ts +13 -2
- package/src/state/app/context/index.tsx +3 -0
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/auth-transition.ts +19 -9
- package/src/sync/baseline.ts +10 -4
- package/src/sync/controller.ts +70 -19
- package/src/sync/core-contributors.ts +90 -31
- package/src/sync/react.ts +4 -1
- package/src/sync/types.ts +3 -0
- package/src/telemetry/stall-reports-dom.ts +38 -0
- package/src/telemetry/stall-reports-node.ts +75 -0
- package/src/telemetry/stall-reports.ts +214 -0
- package/src/telemetry/usage-counts.ts +0 -18
- package/src/test-support/preload.ts +14 -0
- package/src/theme/colors.ts +8 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name.ts +103 -3
- package/src/tickers/listing-venues.ts +38 -3
- package/src/tickers/search/bare-symbol.ts +36 -0
- package/src/tickers/search/index.ts +36 -7
- package/src/tickers/search/ranking.ts +10 -1
- package/src/tickers/search/types.ts +5 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/base-series.ts +13 -5
- package/src/time-series/daily-valuation.ts +62 -0
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +2 -4
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/forward-valuation.ts +32 -0
- package/src/time-series/fundamentals.ts +198 -7
- package/src/time-series/hooks.ts +1 -0
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/price-units.ts +72 -2
- package/src/time-series/resolve-context.ts +7 -1
- package/src/time-series/resolve.ts +116 -15
- package/src/time-series/series-presentation.ts +40 -6
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +5 -3
- package/src/time-series/studies.ts +278 -74
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +3 -3
- package/src/time-series/types.ts +27 -1
- package/src/time-series/valuation-releases.ts +39 -0
- package/src/types/data-provider.ts +9 -1
- package/src/types/desktop-window.ts +2 -0
- package/src/types/financials.ts +73 -2
- package/src/types/headless.ts +61 -5
- package/src/types/instrument.ts +3 -0
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +10 -1
- package/src/ui/host.tsx +3 -0
- package/src/updater/command-names.ts +12 -7
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +44 -0
- package/src/utils/financial-statements.ts +6 -1
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +24 -3
- package/src/utils/fx-market-hours.ts +26 -3
- package/src/utils/main-thread-stall.ts +35 -2
- package/src/utils/market-capitalization.ts +24 -1
- package/src/utils/network-failure.ts +9 -6
- package/src/utils/option-expiry.ts +48 -1
- package/src/utils/options.ts +10 -0
- package/src/utils/plugin-proxy-hosts.json +1 -0
- package/src/utils/price-history.ts +12 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
- package/src/renderers/browser/first-screen.ts +0 -193
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { MarketDataRequestContext } from "../../types/data-provider";
|
|
2
|
+
import type { PricePoint, Quote } from "../../types/financials";
|
|
3
|
+
import { abortable, settleWithin, withDeadline } from "../../utils/async-deadline";
|
|
4
|
+
import { canonicalExchange } from "../../utils/exchanges";
|
|
5
|
+
import { logFailure } from "../../utils/log-failure";
|
|
6
|
+
import { dailyBarDate } from "../../utils/price-history";
|
|
7
|
+
import { classifyInstrumentType } from "../../tickers/instrument-kind";
|
|
8
|
+
import { changeBasisSessionDate, isChangeBasisDatedWithoutCloses, quoteChangeBasis } from "./change-basis";
|
|
9
|
+
|
|
10
|
+
/** History closes come as float32 (30.93000030517578) where the quote says 30.93: one close. */
|
|
11
|
+
const SAME_CLOSE_RELATIVE = 1e-5;
|
|
12
|
+
/**
|
|
13
|
+
* How long a reading that named no day stands before the closes are read
|
|
14
|
+
* again: a history a session behind catches up, a failed read is retried.
|
|
15
|
+
*/
|
|
16
|
+
const UNDATED_RETRY_MS = 10 * 60_000;
|
|
17
|
+
/** Readings kept at once; the oldest goes first. */
|
|
18
|
+
const MAX_READINGS = 2_000;
|
|
19
|
+
/** Daily histories read at once. */
|
|
20
|
+
const MAX_READS_IN_FLIGHT = 6;
|
|
21
|
+
/** How long one read may take before its slot goes to the next: a hung read must not stall the rest. */
|
|
22
|
+
const READ_DEADLINE_MS = 10_000;
|
|
23
|
+
|
|
24
|
+
/** The listing a quote was asked for, as its daily closes are read: its context names a broker's contract. */
|
|
25
|
+
export interface DatedCloseListing {
|
|
26
|
+
symbol: string;
|
|
27
|
+
exchange: string;
|
|
28
|
+
context?: MarketDataRequestContext;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A listing's recent daily closes (a month is enough). */
|
|
32
|
+
export type ReadDailyCloses = (listing: DatedCloseListing) => Promise<readonly Pick<PricePoint, "date" | "close">[]>;
|
|
33
|
+
|
|
34
|
+
export interface PreviousCloseDatesOptions {
|
|
35
|
+
/** Called with the latest copy of a quote, dated, when a read lands that names its day. */
|
|
36
|
+
onDated?: (listing: DatedCloseListing, quote: Quote) => void;
|
|
37
|
+
now?: () => number;
|
|
38
|
+
maxReadsInFlight?: number;
|
|
39
|
+
readDeadlineMs?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface Reading {
|
|
43
|
+
date: string | null;
|
|
44
|
+
at: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** One listing whatever spelling of its exchange a request used. */
|
|
48
|
+
export function datedCloseListingKey(listing: DatedCloseListing): string {
|
|
49
|
+
return `${listing.symbol.trim().toUpperCase()}|${canonicalExchange(listing.exchange)}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function sameClose(left: number, right: number): boolean {
|
|
53
|
+
return Math.abs(left - right) <= Math.max(Math.abs(left), Math.abs(right)) * SAME_CLOSE_RELATIVE;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* What dates a quote's previous close by its listing's daily closes: the
|
|
58
|
+
* listing, the session the move is dated to and the close itself. Null for a
|
|
59
|
+
* quote something else dates (its venue's calendar, a coin's UTC day, a
|
|
60
|
+
* future's terms, a close the source dated), one with no move from a previous
|
|
61
|
+
* close, and an option, whose chain would read a history per contract. The
|
|
62
|
+
* venue's calendar is checked first, since this runs on every tick.
|
|
63
|
+
*/
|
|
64
|
+
function readingKey(quote: Quote | null | undefined, listing: DatedCloseListing, now: number): string | null {
|
|
65
|
+
if (!quote || isChangeBasisDatedWithoutCloses(quote, now) || classifyInstrumentType(quote.instrumentType) === "option") return null;
|
|
66
|
+
if (quoteChangeBasis(quote)?.kind !== "previous close") return null;
|
|
67
|
+
const previousClose = quote.previousClose;
|
|
68
|
+
const sessionDate = changeBasisSessionDate(quote);
|
|
69
|
+
if (previousClose == null || !Number.isFinite(previousClose) || !sessionDate) return null;
|
|
70
|
+
return `${datedCloseListingKey(listing)}|${sessionDate}|${previousClose}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The day of the latest daily close before the move's session, when that
|
|
75
|
+
* close is the quote's previous close; null otherwise, so a gap in the
|
|
76
|
+
* history never names the wrong day.
|
|
77
|
+
*/
|
|
78
|
+
function latestCloseBefore(points: readonly Pick<PricePoint, "date" | "close">[], quote: Quote): string | null {
|
|
79
|
+
const sessionDate = changeBasisSessionDate(quote)!;
|
|
80
|
+
const exchange = quote.listingExchangeName ?? quote.exchangeName ?? "";
|
|
81
|
+
const before = points
|
|
82
|
+
.map((point) => ({ time: new Date(point.date).getTime(), close: point.close }))
|
|
83
|
+
.filter((point) => Number.isFinite(point.time) && Number.isFinite(point.close))
|
|
84
|
+
.map((point) => ({ date: dailyBarDate(point.time, exchange), close: point.close }))
|
|
85
|
+
.filter((point) => point.date < sessionDate)
|
|
86
|
+
.sort((left, right) => left.date.localeCompare(right.date))
|
|
87
|
+
.at(-1);
|
|
88
|
+
return before && sameClose(before.close, quote.previousClose!) ? before.date : null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function withDate<T extends Quote | null | undefined>(quote: T, date: string | null | undefined): T {
|
|
92
|
+
return date && quote ? { ...quote, previousCloseDate: date } : quote;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Dates the previous close a quote's move is from where the venue's calendar
|
|
97
|
+
* cannot (LSE and Helsinki publish none the app carries), by the listing's
|
|
98
|
+
* own daily closes, so every surface that reads quotes through the app's
|
|
99
|
+
* quote router names the day (`quoteChangeBasis`). The closes are read once
|
|
100
|
+
* per listing, session and close, and the answer is kept: a refresh, a tick
|
|
101
|
+
* or a cached read of the same quote is dated without another read. A few
|
|
102
|
+
* reads run at once, each for at most ten seconds.
|
|
103
|
+
*/
|
|
104
|
+
export class PreviousCloseDates {
|
|
105
|
+
private readonly readings = new Map<string, Reading>();
|
|
106
|
+
private readonly pending = new Map<string, Promise<string | null>>();
|
|
107
|
+
/** The latest copy of each quote a read is pending for, which the read dates when it lands. */
|
|
108
|
+
private readonly latestCopies = new Map<string, { quote: Quote; listing: DatedCloseListing }>();
|
|
109
|
+
private readonly waiting: Array<() => void> = [];
|
|
110
|
+
private active = 0;
|
|
111
|
+
private readonly now: () => number;
|
|
112
|
+
private readonly maxReadsInFlight: number;
|
|
113
|
+
private readonly readDeadlineMs: number;
|
|
114
|
+
|
|
115
|
+
constructor(private readonly readCloses: ReadDailyCloses, private readonly options: PreviousCloseDatesOptions = {}) {
|
|
116
|
+
this.now = options.now ?? (() => Date.now());
|
|
117
|
+
this.maxReadsInFlight = options.maxReadsInFlight ?? MAX_READS_IN_FLIGHT;
|
|
118
|
+
this.readDeadlineMs = options.readDeadlineMs ?? READ_DEADLINE_MS;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** The quote dated by what was already read for it; as it was when nothing was. Never reads. */
|
|
122
|
+
known<T extends Quote | null | undefined>(quote: T, listing: DatedCloseListing): T {
|
|
123
|
+
const key = readingKey(quote, listing, this.now());
|
|
124
|
+
return key ? withDate(quote, this.readings.get(key)?.date) : quote;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The quote dated by what was already read for it, starting the read when
|
|
129
|
+
* no reading for it stands. Never waits: the read dates the copies after
|
|
130
|
+
* it, and `onDated` the latest one.
|
|
131
|
+
*/
|
|
132
|
+
observe<T extends Quote | null | undefined>(quote: T, listing: DatedCloseListing): T {
|
|
133
|
+
const key = readingKey(quote, listing, this.now());
|
|
134
|
+
if (!key) return quote;
|
|
135
|
+
const reading = this.readings.get(key);
|
|
136
|
+
if (!this.stands(reading)) void this.start(key, quote!, listing);
|
|
137
|
+
return withDate(quote, reading?.date);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Starts the reads these quotes need and waits for them, at most `waitMs`
|
|
142
|
+
* in all and no longer than `signal` lets it; `known` then dates them.
|
|
143
|
+
*/
|
|
144
|
+
async settle(
|
|
145
|
+
entries: ReadonlyArray<{ quote: Quote; listing: DatedCloseListing }>,
|
|
146
|
+
{ waitMs, signal }: { waitMs: number; signal?: AbortSignal },
|
|
147
|
+
): Promise<void> {
|
|
148
|
+
if (signal?.aborted) return;
|
|
149
|
+
const reads: Promise<string | null>[] = [];
|
|
150
|
+
for (const { quote, listing } of entries) {
|
|
151
|
+
const key = readingKey(quote, listing, this.now());
|
|
152
|
+
if (key && !this.stands(this.readings.get(key))) reads.push(this.start(key, quote, listing));
|
|
153
|
+
}
|
|
154
|
+
if (reads.length) await settleWithin(abortable(Promise.all(reads), signal, "Stopped waiting for daily closes"), waitMs);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Whether a reading answers without another read: a day it named, or no day named lately. */
|
|
158
|
+
private stands(reading: Reading | undefined): boolean {
|
|
159
|
+
return !!reading && (!!reading.date || this.now() - reading.at < UNDATED_RETRY_MS);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private start(key: string, quote: Quote, listing: DatedCloseListing): Promise<string | null> {
|
|
163
|
+
this.latestCopies.set(key, { quote, listing });
|
|
164
|
+
return this.pending.get(key) ?? this.read(key, quote, listing);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private read(key: string, quote: Quote, listing: DatedCloseListing): Promise<string | null> {
|
|
168
|
+
const read = this.inSlot(() => withDeadline(Promise.resolve().then(() => this.readCloses(listing)), this.readDeadlineMs, "Reading daily closes timed out"))
|
|
169
|
+
.then((points) => latestCloseBefore(points, quote))
|
|
170
|
+
.catch(logFailure("market-data.dated-previous-close", "Read daily closes failed", null))
|
|
171
|
+
.then((date) => {
|
|
172
|
+
this.pending.delete(key);
|
|
173
|
+
this.readings.delete(key);
|
|
174
|
+
if (this.readings.size >= MAX_READINGS) this.readings.delete(this.readings.keys().next().value!);
|
|
175
|
+
this.readings.set(key, { date, at: this.now() });
|
|
176
|
+
const latest = this.latestCopies.get(key);
|
|
177
|
+
this.latestCopies.delete(key);
|
|
178
|
+
if (date && latest && this.options.onDated) {
|
|
179
|
+
try {
|
|
180
|
+
this.options.onDated(latest.listing, withDate(latest.quote, date));
|
|
181
|
+
} catch (error) {
|
|
182
|
+
logFailure("market-data.dated-previous-close", "Passing on a dated quote failed")(error);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return date;
|
|
186
|
+
});
|
|
187
|
+
this.pending.set(key, read);
|
|
188
|
+
return read;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private async inSlot<T>(work: () => Promise<T>): Promise<T> {
|
|
192
|
+
if (this.active < this.maxReadsInFlight) this.active += 1;
|
|
193
|
+
else await new Promise<void>((resolve) => this.waiting.push(resolve));
|
|
194
|
+
try {
|
|
195
|
+
return await work();
|
|
196
|
+
} finally {
|
|
197
|
+
// A finished read hands its slot to the next one waiting.
|
|
198
|
+
const next = this.waiting.shift();
|
|
199
|
+
if (next) next();
|
|
200
|
+
else this.active -= 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -628,6 +628,31 @@ function currencyMarks(currency: string, normalizedCurrency: string, code = fals
|
|
|
628
628
|
return code ? { prefix: "", suffix: ` ${normalizedCurrency}` } : { prefix: getCurrencySymbol(normalizedCurrency), suffix: "" };
|
|
629
629
|
}
|
|
630
630
|
|
|
631
|
+
/**
|
|
632
|
+
* A London line shown in pounds and served in the pence it trades in. The
|
|
633
|
+
* code is compared in its own case: `GBp` (and `GBX`) is a price already in
|
|
634
|
+
* pence, which says so itself (630.8p) and needs no pence figure beside it.
|
|
635
|
+
*/
|
|
636
|
+
export function isPenceQuotedLine(quote: Pick<Quote, "currency" | "providerPriceDivisor"> | null | undefined): boolean {
|
|
637
|
+
return quote?.currency?.trim() === "GBP" && quote.providerPriceDivisor === 100;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* A London line's price in the pence it trades in, as the exchange and its
|
|
642
|
+
* announcements write it: `630.8p` for £6.308, with the decimals of a penny
|
|
643
|
+
* the pounds are shown to (`poundDecimals`) beyond two. Null for a price not
|
|
644
|
+
* quoted in pence.
|
|
645
|
+
*/
|
|
646
|
+
export function penceFigure(
|
|
647
|
+
value: number | null | undefined,
|
|
648
|
+
quote: Pick<Quote, "currency" | "providerPriceDivisor"> | null | undefined,
|
|
649
|
+
poundDecimals: number,
|
|
650
|
+
): string | null {
|
|
651
|
+
if (value == null || !Number.isFinite(value) || !isPenceQuotedLine(quote)) return null;
|
|
652
|
+
const digits = Math.max(0, Math.min(4, poundDecimals - 2));
|
|
653
|
+
return `${getNumberFormatter(digits, digits, true).format(value * 100)}${minorUnitSign("GBp")}`;
|
|
654
|
+
}
|
|
655
|
+
|
|
631
656
|
export function formatMarketPriceWithCurrency(
|
|
632
657
|
value: number | undefined,
|
|
633
658
|
currency = "USD",
|
|
@@ -32,7 +32,8 @@ const REGULAR_OPEN_MINUTES: Record<string, number> = {
|
|
|
32
32
|
TSXV: 9 * 60 + 30,
|
|
33
33
|
CSE: 9 * 60 + 30,
|
|
34
34
|
FWB2: 8 * 60,
|
|
35
|
-
|
|
35
|
+
// Continuous trading opens at 09:00 Berlin time, after the opening auction; before it is pre-trading.
|
|
36
|
+
XETRA: 9 * 60,
|
|
36
37
|
LSE: 8 * 60,
|
|
37
38
|
EPA: 9 * 60,
|
|
38
39
|
AMS: 9 * 60,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { canonicalExchange } from "../../utils/exchanges";
|
|
2
|
+
import { zonedDateTimeParts, zonedWallClockToUtcMs } from "../../utils/zoned-date-time";
|
|
3
|
+
|
|
4
|
+
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
5
|
+
const FRIDAY = 5;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* CME Group's Globex week runs from Sunday 17:00 Central to Friday 16:00
|
|
9
|
+
* Central, 17:00 in New York, which ICE Futures U.S. keeps too
|
|
10
|
+
* (cmegroup.com/trading-hours, ice.com). Nothing trades between, whatever
|
|
11
|
+
* state a quote stamped before the Friday close still carries. The venues
|
|
12
|
+
* have no other calendar here: their daily breaks and holiday sessions vary
|
|
13
|
+
* by contract, so the quote's own state says those.
|
|
14
|
+
*/
|
|
15
|
+
const WEEK_ZONE = "America/Chicago";
|
|
16
|
+
const WEEK_CLOSE_MINUTES = 16 * 60;
|
|
17
|
+
const WEEK_OPEN_MINUTES = 17 * 60;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The futures venues the weekly gap covers, and whether the week's first open
|
|
21
|
+
* is theirs to name: every Globex contract opens by Sunday 17:00 Central, but
|
|
22
|
+
* an ICE U.S. one opens when its own hours say, from Sunday evening (cotton)
|
|
23
|
+
* to Monday morning (coffee), so the venue has no one reopen to give.
|
|
24
|
+
*/
|
|
25
|
+
const WEEK_VENUES: Readonly<Record<string, { reopens: boolean }>> = {
|
|
26
|
+
CME: { reopens: true },
|
|
27
|
+
CBT: { reopens: true },
|
|
28
|
+
NYM: { reopens: true },
|
|
29
|
+
CMX: { reopens: true },
|
|
30
|
+
NYB: { reopens: false },
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export interface FuturesWeekendClosure {
|
|
34
|
+
/** The Friday close that began it. */
|
|
35
|
+
closedAt: number;
|
|
36
|
+
/** The Sunday open that ends it, when the venue's contracts all open then or later. */
|
|
37
|
+
reopensAt?: number;
|
|
38
|
+
/** That open's local date, `YYYY-MM-DD` (always a Sunday). */
|
|
39
|
+
reopensOn?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The weekend gap of a futures venue's week that `time` falls in, from the
|
|
44
|
+
* Friday close to the Sunday open; null on any other instant, and for a venue
|
|
45
|
+
* that is not a futures exchange here. Only ever the closed side: a moment
|
|
46
|
+
* outside it is not known to be open.
|
|
47
|
+
*/
|
|
48
|
+
export function futuresWeekendClosure(exchange: string | undefined, time: number): FuturesWeekendClosure | null {
|
|
49
|
+
const venue = WEEK_VENUES[canonicalExchange(exchange)];
|
|
50
|
+
if (!venue || !Number.isFinite(time)) return null;
|
|
51
|
+
const { year, month, day } = zonedDateTimeParts(time, WEEK_ZONE);
|
|
52
|
+
const today = Date.UTC(year, month - 1, day) / MS_PER_DAY;
|
|
53
|
+
const weekday = new Date(today * MS_PER_DAY).getUTCDay();
|
|
54
|
+
// The latest Friday on or before the local date: a Monday to Thursday reads last week's, whose gap has ended.
|
|
55
|
+
const friday = today - ((weekday - FRIDAY + 7) % 7);
|
|
56
|
+
const at = (days: number, minutes: number) => {
|
|
57
|
+
const date = new Date(days * MS_PER_DAY);
|
|
58
|
+
return zonedWallClockToUtcMs(WEEK_ZONE, date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate(),
|
|
59
|
+
Math.floor(minutes / 60), minutes % 60, 0);
|
|
60
|
+
};
|
|
61
|
+
const closedAt = at(friday, WEEK_CLOSE_MINUTES);
|
|
62
|
+
const reopensAt = at(friday + 2, WEEK_OPEN_MINUTES);
|
|
63
|
+
if (time < closedAt || time >= reopensAt) return null;
|
|
64
|
+
return {
|
|
65
|
+
closedAt,
|
|
66
|
+
...(venue.reopens ? { reopensAt, reopensOn: new Date((friday + 2) * MS_PER_DAY).toISOString().slice(0, 10) } : {}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -6,6 +6,7 @@ import { quoteTradingDay } from "../quotes/day-range";
|
|
|
6
6
|
import { isQuoteBeforeLatestSession } from "../quotes/freshness";
|
|
7
7
|
import { delistingLabel, delistingStateLabel } from "../quotes/delisting";
|
|
8
8
|
import { preMarketOpenOn, regularSessionCloseOn } from "./freshness";
|
|
9
|
+
import { futuresWeekendClosure } from "./futures-week";
|
|
9
10
|
|
|
10
11
|
const CLOSED_CHANGE_MUTING_RATIO = 0.55;
|
|
11
12
|
const US_SESSION_TIME = new Intl.DateTimeFormat("en-US", {
|
|
@@ -97,6 +98,17 @@ function closeLabel(date: string): string {
|
|
|
97
98
|
return `${WEEKDAYS[new Date(`${date}T00:00:00Z`).getUTCDay()]} close`;
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
/**
|
|
102
|
+
* The market state a quote's venue is in now. A futures venue's weekend gap
|
|
103
|
+
* closes every contract, so it outranks the state a quote stamped before the
|
|
104
|
+
* Friday close (a stale print reading REGULAR on a Sunday); every other quote
|
|
105
|
+
* keeps the state its source reported.
|
|
106
|
+
*/
|
|
107
|
+
function quoteMarketState(quote: Quote | null | undefined, now = Date.now()): MarketState | undefined {
|
|
108
|
+
if (!quote?.marketState) return undefined;
|
|
109
|
+
return futuresWeekendClosure(quote.listingExchangeName || quote.exchangeName, now) ? "CLOSED" : quote.marketState;
|
|
110
|
+
}
|
|
111
|
+
|
|
100
112
|
/**
|
|
101
113
|
* A quote's market state as a status cell names it (marketStateLabel), STALE
|
|
102
114
|
* for a print older than its venue's latest session, and the close it stands
|
|
@@ -109,7 +121,7 @@ export function quoteMarketStateLabel(quote: Quote | null | undefined, now = Dat
|
|
|
109
121
|
if (!quote?.marketState) return null;
|
|
110
122
|
if (isQuoteBeforeLatestSession(quote, now)) return STALE_SESSION_LABEL;
|
|
111
123
|
const awaiting = preMarketCloseAwaitingPrint(quote);
|
|
112
|
-
return awaiting ? closeLabel(awaiting.date) : marketStateLabel(quote
|
|
124
|
+
return awaiting ? closeLabel(awaiting.date) : marketStateLabel(quoteMarketState(quote, now)!);
|
|
113
125
|
}
|
|
114
126
|
|
|
115
127
|
/** An extended session's column, named as marketStateLabel names its state. */
|
|
@@ -216,7 +228,7 @@ function sessionLabel(quote: Quote | null | undefined, now: number, standingClos
|
|
|
216
228
|
if (isQuoteBeforeLatestSession(quote, now)) return STALE_SESSION_LABEL;
|
|
217
229
|
if (overnightSessionDisplay(quote)) return "OVERNIGHT";
|
|
218
230
|
const awaiting = standingClose ? preMarketCloseAwaitingPrint(quote) : null;
|
|
219
|
-
return awaiting ? closeLabel(awaiting.date) : marketStateLabel(quote
|
|
231
|
+
return awaiting ? closeLabel(awaiting.date) : marketStateLabel(quoteMarketState(quote, now)!);
|
|
220
232
|
}
|
|
221
233
|
|
|
222
234
|
/**
|
|
@@ -230,7 +242,7 @@ export function quoteSessionColor(quote: Quote | null | undefined, palette: Them
|
|
|
230
242
|
if (!quote?.marketState) return palette.textDim;
|
|
231
243
|
if (isQuoteBeforeLatestSession(quote, now)) return palette.warning;
|
|
232
244
|
if (overnightSessionDisplay(quote)) return marketStateColor("POST", palette);
|
|
233
|
-
return marketStateColor(preMarketCloseAwaitingPrint(quote) ? "CLOSED" : quote
|
|
245
|
+
return marketStateColor(preMarketCloseAwaitingPrint(quote) ? "CLOSED" : quoteMarketState(quote, now)!, palette);
|
|
234
246
|
}
|
|
235
247
|
|
|
236
248
|
/**
|
|
@@ -239,7 +251,7 @@ export function quoteSessionColor(quote: Quote | null | undefined, palette: Them
|
|
|
239
251
|
* with no known session rather than an open market.
|
|
240
252
|
*/
|
|
241
253
|
export function quoteDotMarketState(quote: Quote | null | undefined, now = Date.now()): MarketState | undefined {
|
|
242
|
-
return quote && !quote.delisting && !isQuoteBeforeLatestSession(quote, now) ? quote
|
|
254
|
+
return quote && !quote.delisting && !isQuoteBeforeLatestSession(quote, now) ? quoteMarketState(quote, now) : undefined;
|
|
243
255
|
}
|
|
244
256
|
|
|
245
257
|
/** Closed prices are final snapshots, so they should not look live or directional. */
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { isRoundTheClockCoin } from "../utils/crypto-pair";
|
|
2
|
+
import { currencyPairCode, isCurrencyCode } from "../utils/currency-pair";
|
|
3
|
+
import { parsePublicTickerKey } from "../utils/exchanges";
|
|
1
4
|
import { parseFuturesGeneric } from "../utils/futures-generic";
|
|
2
5
|
|
|
3
6
|
/** A US-listed fund whose options track an instrument that has none of its own. */
|
|
@@ -32,23 +35,24 @@ const FUTURES_ALTERNATIVES: Readonly<Record<string, ListedOptionsAlternative>> =
|
|
|
32
35
|
ZN: { symbol: "IEF", name: "iShares 7-10 Year Treasury Bond ETF" },
|
|
33
36
|
"6E": { symbol: "FXE", name: "Invesco CurrencyShares Euro Trust" },
|
|
34
37
|
"6J": { symbol: "FXY", name: "Invesco CurrencyShares Japanese Yen Trust" },
|
|
38
|
+
"6B": { symbol: "FXB", name: "Invesco CurrencyShares British Pound Sterling Trust" },
|
|
35
39
|
BTC: { symbol: "IBIT", name: "iShares Bitcoin Trust" },
|
|
36
40
|
ETH: { symbol: "ETHA", name: "iShares Ethereum Trust" },
|
|
37
41
|
};
|
|
38
42
|
|
|
39
43
|
const MONTH_CODE_CONTRACT = /^([A-Z0-9]{1,3}?)[FGHJKMNQUVXZ]\d{1,4}$/;
|
|
40
44
|
|
|
41
|
-
/** The futures root a symbol names: GC=F, GCZ26 and the generic GC1 all give GC. */
|
|
42
|
-
function futuresRoot(symbol: string): string | null {
|
|
45
|
+
/** The futures root a symbol names: GC=F, GCZ26 and the generic GC1 all give GC. `known` says which roots a bare contract may carry. */
|
|
46
|
+
function futuresRoot(symbol: string, known: (root: string) => boolean = (root) => !!FUTURES_ALTERNATIVES[root]): string | null {
|
|
43
47
|
const ticker = symbol.trim().toUpperCase().replace(/[.:][A-Z]+$/, "");
|
|
44
48
|
if (ticker.endsWith("=F")) {
|
|
45
49
|
const body = ticker.slice(0, -2);
|
|
46
|
-
return
|
|
50
|
+
return known(body) ? body : MONTH_CODE_CONTRACT.exec(body)?.[1] ?? body;
|
|
47
51
|
}
|
|
48
52
|
const generic = parseFuturesGeneric(ticker);
|
|
49
53
|
if (generic) return generic.root;
|
|
50
54
|
const contract = MONTH_CODE_CONTRACT.exec(ticker)?.[1];
|
|
51
|
-
return contract &&
|
|
55
|
+
return contract && known(contract) ? contract : null;
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
/** The listed fund whose options stand in for an instrument with no chain, if there is one. */
|
|
@@ -67,3 +71,136 @@ export function isOptionsUnavailableError(error: unknown): boolean {
|
|
|
67
71
|
export function optionsUnavailableTitle(symbol: string): string {
|
|
68
72
|
return `Options are not available for ${symbol.trim().toUpperCase()}.`;
|
|
69
73
|
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The coins whose options Gloom Cloud serves, from Deribit, as BTC-USD and
|
|
77
|
+
* ETH-USD: the standard options path (options, OMON, OPX, OVDV).
|
|
78
|
+
*/
|
|
79
|
+
const COIN_OPTION_UNDERLYINGS: Readonly<Record<string, string>> = { BTC: "BTC-USD", ETH: "ETH-USD" };
|
|
80
|
+
|
|
81
|
+
/** BTC-USD or ETH-USD for a coin pair whose options are listed (on the coin board or bare); null otherwise. */
|
|
82
|
+
export function listedCoinOptions(symbol: string): string | null {
|
|
83
|
+
const parsed = parsePublicTickerKey(symbol.trim().toUpperCase());
|
|
84
|
+
if (!isRoundTheClockCoin(parsed.symbol, parsed.exchange)) return null;
|
|
85
|
+
const pair = /^([A-Z0-9]+)-USD$/.exec(parsed.symbol)?.[1];
|
|
86
|
+
return pair ? COIN_OPTION_UNDERLYINGS[pair] ?? null : null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** A coin (SOL-USD, DOGE-USD) nothing lists options on: only BTC and ETH have them. */
|
|
90
|
+
export function isCoinWithoutOptions(symbol: string): boolean {
|
|
91
|
+
const parsed = parsePublicTickerKey(symbol.trim().toUpperCase());
|
|
92
|
+
return isRoundTheClockCoin(parsed.symbol, parsed.exchange) && listedCoinOptions(symbol) == null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* What exists instead for a coin without options, worded for the CLI:
|
|
97
|
+
* BTC and ETH options, and the bitcoin-linked US listings with their own,
|
|
98
|
+
* each as the command that reads it (`options`, or `fn OPX`).
|
|
99
|
+
*/
|
|
100
|
+
export function coinOptionsAlternativesText(command = "options"): string {
|
|
101
|
+
const run = (symbol: string) => `gloomberb ${command} ${symbol}`;
|
|
102
|
+
return `Listed crypto options cover BTC and ETH: ${run("BTC-USD")}, ${run("ETH-USD")}.`
|
|
103
|
+
+ ` Bitcoin-linked US listings have their own: ${run("IBIT")}.`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The same for a pane, which has no commands to give. */
|
|
107
|
+
export const COIN_OPTIONS_HINT = "Listed crypto options cover BTC-USD and ETH-USD; bitcoin-linked US funds such as IBIT list their own.";
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* An options function's `refuseSymbol`: a coin nothing lists options on
|
|
111
|
+
* (SOL-USD) is answered before any request, with what exists instead.
|
|
112
|
+
*/
|
|
113
|
+
export function coinWithoutOptionsRefusal(code: string) {
|
|
114
|
+
return (key: string): { message: string; details: string } | null => isCoinWithoutOptions(key)
|
|
115
|
+
? { message: optionsUnavailableTitle(parsePublicTickerKey(key.trim().toUpperCase()).symbol), details: coinOptionsAlternativesText(`fn ${code}`) }
|
|
116
|
+
: null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* A CLI options function's `symbolAlias` (`gloomberb fn OPX BTC`, `shot`): a
|
|
121
|
+
* bare BTC or ETH is read as the coin, with a note naming the Grayscale fund's
|
|
122
|
+
* listing (BTC:ARCA) for its own options (`code` is the function, OPX). The
|
|
123
|
+
* command bar and panes keep a bare BTC as the fund, as everywhere else.
|
|
124
|
+
*/
|
|
125
|
+
export function coinOptionsAlias(code: string) {
|
|
126
|
+
return (typed: string): { symbol: string; note: string } | null => {
|
|
127
|
+
const key = typed.trim().toUpperCase();
|
|
128
|
+
const pair = COIN_OPTION_UNDERLYINGS[key];
|
|
129
|
+
return pair ? { symbol: pair, note: `${key} read as ${pair}, the coin's options. For the US fund that trades as ${key}: gloomberb fn ${code} ${key}:ARCA` } : null;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The CME currency futures, each as the pair it settles on, in the order the
|
|
135
|
+
* pair is quoted: 6E is EUR/USD, 6J USD/JPY. No chain of options on any of
|
|
136
|
+
* them is served here, as none is on the spot pair.
|
|
137
|
+
*/
|
|
138
|
+
const FX_FUTURES_PAIRS: Readonly<Record<string, string>> = {
|
|
139
|
+
"6E": "EURUSD", "6J": "USDJPY", "6B": "GBPUSD", "6A": "AUDUSD", "6C": "USDCAD", "6S": "USDCHF", "6N": "NZDUSD", "6M": "USDMXN",
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/** The US-listed currency funds whose options stand in for an FX pair's volatility, by the currency they hold. */
|
|
143
|
+
const FX_VOL_FUNDS: Readonly<Record<string, string>> = { EUR: "FXE", JPY: "FXY", GBP: "FXB" };
|
|
144
|
+
/** The fund that holds the dollar against a basket, for a pair none of the single-currency funds holds. */
|
|
145
|
+
const DOLLAR_FUND = "UUP";
|
|
146
|
+
const FX_VOL_FUND_LIST = [...Object.values(FX_VOL_FUNDS), DOLLAR_FUND];
|
|
147
|
+
|
|
148
|
+
/** The pair a symbol is: a spot pair in any spelling (EURUSD=X, EUR/USD), or a currency future (6E=F, 6EZ26, 6E1) as the pair it settles on; null for anything else. */
|
|
149
|
+
export function fxVolatilityPair(symbol: string, exchange?: string): string | null {
|
|
150
|
+
const spot = currencyPairCode(symbol, exchange);
|
|
151
|
+
// Metals quoted against a currency (XAUUSD=X) are no currency pair.
|
|
152
|
+
if (spot) return isCurrencyCode(spot.slice(0, 3)) && isCurrencyCode(spot.slice(3)) ? spot : null;
|
|
153
|
+
const root = futuresRoot(symbol, (candidate) => !!FX_FUTURES_PAIRS[candidate]);
|
|
154
|
+
return root ? FX_FUTURES_PAIRS[root] ?? null : null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The listed fund that stands in for an FX pair's volatility: the one that
|
|
159
|
+
* holds the pair's euro, yen or pound, else the dollar index fund.
|
|
160
|
+
*/
|
|
161
|
+
function fxVolatilityFunds(pair: string): string[] {
|
|
162
|
+
const held = [pair.slice(0, 3), pair.slice(3)].map((code) => FX_VOL_FUNDS[code]).filter((fund): fund is string => !!fund);
|
|
163
|
+
return held.length > 0 ? held : FX_VOL_FUND_LIST;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* What exists instead for the volatility of an FX pair or currency future,
|
|
168
|
+
* worded as one line for the CLI: OTC FX volatility is not served, the
|
|
169
|
+
* currency funds' options are the nearest listed thing (the one that holds
|
|
170
|
+
* the pair's euro, yen or pound, else any of them), and HVT reads the pair's
|
|
171
|
+
* realized volatility. Null for anything that is no FX pair. `code` is the
|
|
172
|
+
* function asked (OVDV), `symbol` as typed.
|
|
173
|
+
*/
|
|
174
|
+
function fxVolatilityAnswer(code: string, symbol: string): string | null {
|
|
175
|
+
const pair = fxVolatilityPair(symbol);
|
|
176
|
+
if (!pair) return null;
|
|
177
|
+
const typed = symbol.trim().toUpperCase();
|
|
178
|
+
const funds = fxVolatilityFunds(pair);
|
|
179
|
+
// VCA screens every fund; the others read the pair's own, or the dollar's when it has none.
|
|
180
|
+
const read = code === "VCA" ? funds.join(",") : funds === FX_VOL_FUND_LIST ? DOLLAR_FUND : funds[0]!;
|
|
181
|
+
return `OTC FX volatility is not covered for ${typed}. Currency-ETF options are the listed proxy: `
|
|
182
|
+
+ `${funds.join(", ")} (gloom fn ${code} ${read}). Realized volatility: gloom fn HVT ${typed}.`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The same for a pane, which has no commands to give: what is not covered,
|
|
187
|
+
* the funds whose options stand in, and HVT for realized volatility.
|
|
188
|
+
*/
|
|
189
|
+
export function fxVolatilityNote(symbol: string, exchange?: string): string | null {
|
|
190
|
+
const pair = fxVolatilityPair(symbol, exchange);
|
|
191
|
+
if (!pair) return null;
|
|
192
|
+
return `OTC FX volatility is not covered for ${symbol.trim().toUpperCase()}. Currency-ETF options are the listed proxy: `
|
|
193
|
+
+ `${fxVolatilityFunds(pair).join(", ")}. HVT shows the pair's realized volatility.`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* A volatility function's `refuseSymbol` for FX (OVDV, VCA, HIVG): an FX pair
|
|
198
|
+
* or currency future is answered before any request, with one line saying
|
|
199
|
+
* OTC FX volatility is not covered and what to read instead.
|
|
200
|
+
*/
|
|
201
|
+
export function fxVolatilityRefusal(code: string) {
|
|
202
|
+
return (key: string): { message: string; details?: string } | null => {
|
|
203
|
+
const message = fxVolatilityAnswer(code, key);
|
|
204
|
+
return message ? { message } : null;
|
|
205
|
+
};
|
|
206
|
+
}
|
|
@@ -20,7 +20,7 @@ const PRICE_DENOMINATED_DESCRIPTIVE_FIELDS: ReadonlySet<string> = new Set(["high
|
|
|
20
20
|
* reconciled separately (reconcileQuoteDayRange).
|
|
21
21
|
*/
|
|
22
22
|
const TRADING_DAY_FIELDS = [
|
|
23
|
-
"changeSessionDate", "previousClose", "open", "volume", "mark", "bid", "ask", "bidSize", "askSize",
|
|
23
|
+
"changeSessionDate", "previousClose", "previousCloseDate", "open", "volume", "mark", "bid", "ask", "bidSize", "askSize",
|
|
24
24
|
"lastTradePrice", "lastTradeTime",
|
|
25
25
|
] as const;
|
|
26
26
|
|
|
@@ -36,6 +36,12 @@ function isOtherTradingDay(current: Quote, next: Quote): boolean {
|
|
|
36
36
|
return currentDay !== null && nextDay !== null && nextDay !== currentDay;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/** Whether two observations are known to describe the same trading day. */
|
|
40
|
+
function isSameTradingDay(current: Quote, next: Quote): boolean {
|
|
41
|
+
const currentDay = quoteTradingDay(current);
|
|
42
|
+
return currentDay !== null && currentDay === quoteTradingDay(next);
|
|
43
|
+
}
|
|
44
|
+
|
|
39
45
|
function inferQuoteProviderId(quote: Quote | QuoteContribution): string {
|
|
40
46
|
if (quote.providerId?.trim()) return quote.providerId;
|
|
41
47
|
return "quote";
|
|
@@ -195,7 +201,7 @@ export function mergeQuoteContribution(
|
|
|
195
201
|
// Close provenance belongs to this observation; never borrow an anchor
|
|
196
202
|
// from another session, listing or currency when a new quote omits it.
|
|
197
203
|
...(!samePriceBasis ? {
|
|
198
|
-
previousClose: next.previousClose, bid: next.bid, ask: next.ask,
|
|
204
|
+
previousClose: next.previousClose, previousCloseDate: next.previousCloseDate, bid: next.bid, ask: next.ask,
|
|
199
205
|
open: next.open, mark: next.mark, high52w: next.high52w, low52w: next.low52w,
|
|
200
206
|
lastTradePrice: next.lastTradePrice, lastTradeTime: next.lastTradeTime,
|
|
201
207
|
preMarketPrice: next.preMarketPrice, preMarketChange: next.preMarketChange,
|
|
@@ -232,6 +238,18 @@ export function mergeQuoteContribution(
|
|
|
232
238
|
delete merged.changeBasis;
|
|
233
239
|
} else if (next.previousClose === undefined) {
|
|
234
240
|
delete merged.previousClose;
|
|
241
|
+
delete merged.previousCloseDate;
|
|
242
|
+
}
|
|
243
|
+
// The day a previous close is from is said of that close and that session: a tick that names
|
|
244
|
+
// none keeps it while both are the same, and drops it otherwise. A close can repeat across
|
|
245
|
+
// sessions (a flat day), so the trading day decides, not the close alone.
|
|
246
|
+
if (next.previousCloseDate === undefined) {
|
|
247
|
+
const sameClose = next.previousClose === undefined || next.previousClose === current.previousClose;
|
|
248
|
+
if (samePriceBasis && sameClose && merged.previousClose === current.previousClose && current.previousCloseDate && isSameTradingDay(current, next)) {
|
|
249
|
+
merged.previousCloseDate = current.previousCloseDate;
|
|
250
|
+
} else {
|
|
251
|
+
delete merged.previousCloseDate;
|
|
252
|
+
}
|
|
235
253
|
}
|
|
236
254
|
|
|
237
255
|
if (isOtherTradingDay(current, next)) {
|
|
@@ -221,6 +221,8 @@ function assignDailyChangeFields(
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
assignField(target, provenance, "previousClose", referenceProvider);
|
|
224
|
+
// The day that close is from comes with it, from the same source.
|
|
225
|
+
if (referenceProvider.previousCloseDate) target.previousCloseDate = referenceProvider.previousCloseDate;
|
|
224
226
|
const price = target.price;
|
|
225
227
|
const previousClose = target.previousClose;
|
|
226
228
|
if (!finitePositiveNumber(price) || !finitePositiveNumber(previousClose)) {
|
|
@@ -500,6 +502,7 @@ export function resolveCanonicalQuote(
|
|
|
500
502
|
// What that move is from when it is not previousClose; absent when it is.
|
|
501
503
|
...(resolved.changeBasis ? { changeBasis: resolved.changeBasis } : {}),
|
|
502
504
|
previousClose: resolved.previousClose as Quote["previousClose"],
|
|
505
|
+
...(resolved.previousCloseDate ? { previousCloseDate: resolved.previousCloseDate } : {}),
|
|
503
506
|
regularClose: priceProvider?.regularClose,
|
|
504
507
|
regularCloseSessionDate: priceProvider?.regularClose != null ? priceProvider.regularCloseSessionDate : undefined,
|
|
505
508
|
regularChange: priceProvider?.regularClose != null ? priceProvider.regularChange : undefined,
|
|
@@ -6,6 +6,7 @@ import { hasLikelyQuoteUnitMismatch } from "../utils/currency-units";
|
|
|
6
6
|
import { normalizePriceHistory } from "../utils/price-history";
|
|
7
7
|
import { resolveTickerFinancialsQuoteState } from "./quotes/resolution";
|
|
8
8
|
import { instrumentIdentityKey } from "../utils/instrument-identity";
|
|
9
|
+
import { withSavedCopyMarkOf } from "../data/saved-copies";
|
|
9
10
|
|
|
10
11
|
export function toMarketDataContext(instrument: InstrumentRef): MarketDataRequestContext {
|
|
11
12
|
return {
|
|
@@ -123,6 +124,8 @@ export function buildTickerFinancialsSnapshot(
|
|
|
123
124
|
|
|
124
125
|
return {
|
|
125
126
|
...resolved,
|
|
127
|
+
// The quote shown is a saved copy when the newest quote answered was one.
|
|
128
|
+
...(resolved.quote ? { quote: withSavedCopyMarkOf(resolved.quote, quote ?? snapshot?.quote) } : {}),
|
|
126
129
|
priceHistory: normalizePriceHistory(priceHistory ?? snapshot?.priceHistory ?? []),
|
|
127
130
|
};
|
|
128
131
|
}
|