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
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
} from "../../../public/react";
|
|
37
37
|
import { useDialog } from "../../../ui/dialog";
|
|
38
38
|
import { useThemeColors } from "../../../theme/theme-context";
|
|
39
|
-
import { priceColor } from "../../../theme/colors";
|
|
39
|
+
import { priceColor, shownPriceColor } from "../../../theme/colors";
|
|
40
40
|
import { apiClient } from "../../../api-client";
|
|
41
41
|
import {
|
|
42
42
|
NUMERIC_FIELDS,
|
|
@@ -54,6 +54,7 @@ import { useSampledValue } from "../../../state/hooks/live-ticker-financials";
|
|
|
54
54
|
import { usePlanAccess } from "../../../api-client/plan-access";
|
|
55
55
|
import { SignInWall } from "../cloud/auth-actions";
|
|
56
56
|
import { CriterionEditor } from "./criterion-editor";
|
|
57
|
+
import { SCREEN_IDEAS, screenIdea } from "./ideas";
|
|
57
58
|
import { ScreenTickerCheck } from "./ticker-check";
|
|
58
59
|
import {
|
|
59
60
|
fetchSavedScreens,
|
|
@@ -70,7 +71,9 @@ import {
|
|
|
70
71
|
formatScreenValue,
|
|
71
72
|
metricDate,
|
|
72
73
|
parseScreenDefinition,
|
|
74
|
+
pickerFields,
|
|
73
75
|
resultFields,
|
|
76
|
+
screenMetricSetting,
|
|
74
77
|
SIGN_COLORED,
|
|
75
78
|
screenColumnLabel,
|
|
76
79
|
screenCurrencyEmpty,
|
|
@@ -80,6 +83,7 @@ import {
|
|
|
80
83
|
screenYtdBase,
|
|
81
84
|
} from "./model";
|
|
82
85
|
import { useScreenResults } from "./results";
|
|
86
|
+
import { unmatchedCategoryText, unmatchedCategoryValues } from "./categories";
|
|
83
87
|
import { overlayLiveScreenRows, screenExtendedMove, screenExtendedSessions, sortLiveScreenRows, type LiveScreenRow } from "./live";
|
|
84
88
|
import { useLiveQuoteEntries } from "../../../state/hooks/quote-streaming";
|
|
85
89
|
import { buildScreenerQuoteTargets } from "../../../market-data/quotes/screener-live-quotes";
|
|
@@ -92,6 +96,7 @@ const TABS = [
|
|
|
92
96
|
{ value: "results", label: "Results" },
|
|
93
97
|
{ value: "criteria", label: "Criteria" },
|
|
94
98
|
{ value: "saved", label: "Saved" },
|
|
99
|
+
{ value: "ideas", label: "Ideas" },
|
|
95
100
|
];
|
|
96
101
|
const errorText = (error: unknown) =>
|
|
97
102
|
error instanceof Error ? error.message : "Request failed.";
|
|
@@ -115,8 +120,10 @@ export function resultColumns(
|
|
|
115
120
|
asOf: boolean,
|
|
116
121
|
extendedSessions: readonly ExtendedSession[] = [],
|
|
117
122
|
ytdBase: string | null = null,
|
|
123
|
+
/** Other metrics `--metric` asked for, shown first after the focus metric. */
|
|
124
|
+
otherMetrics: readonly NumericField[] = [],
|
|
118
125
|
): DataTableColumn[] {
|
|
119
|
-
const [focus, ...others] = resultFields(definition, metric);
|
|
126
|
+
const [focus, ...others] = resultFields(definition, metric, otherMetrics);
|
|
120
127
|
const metricColumn = (field: NumericField, form: "pane" | "compact"): DataTableColumn => {
|
|
121
128
|
const label = screenColumnLabel(field, ytdBase, form);
|
|
122
129
|
return { id: `m:${field}`, label, width: columnWidth(field, label), align: "right" };
|
|
@@ -222,9 +229,12 @@ export function EquityScreenerPane(props: PaneProps) {
|
|
|
222
229
|
"definition",
|
|
223
230
|
undefined,
|
|
224
231
|
);
|
|
232
|
+
// A ready screen from Ideas (`--preset`), which opens on its own metric.
|
|
233
|
+
const [presetId] = usePaneSettingValue<string | undefined>("preset", undefined);
|
|
234
|
+
const preset = screenIdea(presetId);
|
|
225
235
|
let initial: ScreenDefinition;
|
|
226
236
|
try {
|
|
227
|
-
initial = seed ? parseScreenDefinition(JSON.parse(seed)) : DEFAULT_SCREEN;
|
|
237
|
+
initial = preset?.definition ?? (seed ? parseScreenDefinition(JSON.parse(seed)) : DEFAULT_SCREEN);
|
|
228
238
|
} catch (error) {
|
|
229
239
|
return (
|
|
230
240
|
<PaneStatusBody subject="screen definition" error={errorText(error)} />
|
|
@@ -232,9 +242,10 @@ export function EquityScreenerPane(props: PaneProps) {
|
|
|
232
242
|
}
|
|
233
243
|
return (
|
|
234
244
|
<EquityScreenView
|
|
235
|
-
key={`${seed ?? "default"}:${session.requestKey}`}
|
|
245
|
+
key={`${preset?.id ?? seed ?? "default"}:${session.requestKey}`}
|
|
236
246
|
{...props}
|
|
237
247
|
initial={initial}
|
|
248
|
+
initialMetric={preset?.metric ?? "marketCap"}
|
|
238
249
|
/>
|
|
239
250
|
);
|
|
240
251
|
}
|
|
@@ -243,7 +254,8 @@ function EquityScreenView({
|
|
|
243
254
|
height,
|
|
244
255
|
focused,
|
|
245
256
|
initial,
|
|
246
|
-
|
|
257
|
+
initialMetric,
|
|
258
|
+
}: PaneProps & { initial: ScreenDefinition; initialMetric: NumericField }) {
|
|
247
259
|
const colors = useThemeColors(),
|
|
248
260
|
dialog = useDialog(),
|
|
249
261
|
host = useRendererHost();
|
|
@@ -253,10 +265,12 @@ function EquityScreenView({
|
|
|
253
265
|
const session = useResearchCloudSession(),
|
|
254
266
|
access = usePlanAccess();
|
|
255
267
|
const [definition, setDefinition] = usePluginPaneState("definition", initial);
|
|
256
|
-
const [
|
|
268
|
+
const [metricSetting, setMetric] = usePaneSettingValue<string>(
|
|
257
269
|
"metric",
|
|
258
|
-
|
|
270
|
+
initialMetric,
|
|
259
271
|
);
|
|
272
|
+
// `--metric trailingPE,dividendYieldPercent` leads with the first and adds the others as columns.
|
|
273
|
+
const [metric, ...otherMetrics] = screenMetricSetting(metricSetting, initialMetric);
|
|
260
274
|
const [mode, setMode] = usePluginPaneState("mode", "results");
|
|
261
275
|
const [selectedId, setSelectedId] = usePluginPaneState<string | null>(
|
|
262
276
|
"selected",
|
|
@@ -271,6 +285,10 @@ function EquityScreenView({
|
|
|
271
285
|
const [savedSelectedId, setSavedSelectedId] = usePluginPaneState<
|
|
272
286
|
string | null
|
|
273
287
|
>("savedSelected", null);
|
|
288
|
+
const [ideaSelectedId, setIdeaSelectedId] = usePluginPaneState(
|
|
289
|
+
"ideaSelected",
|
|
290
|
+
SCREEN_IDEAS[0]!.id,
|
|
291
|
+
);
|
|
274
292
|
const [currentSavedId, setCurrentSavedId] = usePluginPaneState<string | null>(
|
|
275
293
|
"currentSaved",
|
|
276
294
|
null,
|
|
@@ -356,8 +374,10 @@ function EquityScreenView({
|
|
|
356
374
|
!opened && !results.loading && !results.loadingMore && !!data?.nextCursor,
|
|
357
375
|
results.loadMore,
|
|
358
376
|
);
|
|
359
|
-
const metricFields =
|
|
360
|
-
fields.data?.fields.filter((field) => field.kind === "number") ?? []
|
|
377
|
+
const metricFields = pickerFields(
|
|
378
|
+
fields.data?.fields.filter((field) => field.kind === "number") ?? [],
|
|
379
|
+
[metric],
|
|
380
|
+
);
|
|
361
381
|
// AS OF earns its column only when some row's focus metric is stale; the
|
|
362
382
|
// snapshot decides, so a live quote freshening a row does not reflow the table.
|
|
363
383
|
const staleFocus = snapshotRows.some((row) => row.metrics[metric]?.state === "stale");
|
|
@@ -367,7 +387,11 @@ function EquityScreenView({
|
|
|
367
387
|
const ytdBase = screenYtdBase(snapshotRows, data?.snapshot?.assembledAt);
|
|
368
388
|
// A screen its currency emptied says so, not that no company matches.
|
|
369
389
|
const currencyEmpty = data && data.rows.length === 0 ? screenCurrencyEmpty(data.definition, data.universe) : null;
|
|
370
|
-
|
|
390
|
+
// A criterion's name that matches nothing (`Utility`) says so with the names it was likely meant as.
|
|
391
|
+
const unmatchedNames = data && data.rows.length === 0
|
|
392
|
+
? unmatchedCategoryValues(data.definition, data).map(unmatchedCategoryText).join(" ") || null
|
|
393
|
+
: null;
|
|
394
|
+
const columns = resultColumns(width, definition, metric, staleFocus, extendedSessions, ytdBase, otherMetrics);
|
|
371
395
|
const apply = (next: ScreenDefinition) => {
|
|
372
396
|
try {
|
|
373
397
|
setDefinition(parseScreenDefinition(next));
|
|
@@ -494,7 +518,7 @@ function EquityScreenView({
|
|
|
494
518
|
useAutoRefresh(results.updatedAt, results.load);
|
|
495
519
|
const openQueryControl = () => queryControl.current?.open();
|
|
496
520
|
const hints =
|
|
497
|
-
saveForm || editing !== null || check
|
|
521
|
+
saveForm || editing !== null || check || mode === "ideas"
|
|
498
522
|
? []
|
|
499
523
|
: mode === "criteria"
|
|
500
524
|
? [
|
|
@@ -783,7 +807,7 @@ function EquityScreenView({
|
|
|
783
807
|
editing !== null ? (
|
|
784
808
|
<CriterionEditor
|
|
785
809
|
key={editing}
|
|
786
|
-
fields={fields.data.fields}
|
|
810
|
+
fields={pickerFields(fields.data.fields, editingCriterion ? [editingCriterion.field] : [])}
|
|
787
811
|
value={editingCriterion ?? null}
|
|
788
812
|
focused={focused}
|
|
789
813
|
width={width}
|
|
@@ -863,6 +887,36 @@ function EquityScreenView({
|
|
|
863
887
|
/>
|
|
864
888
|
) : null}
|
|
865
889
|
</PaneStatusBody>
|
|
890
|
+
) : mode === "ideas" ? (
|
|
891
|
+
<DataTableView
|
|
892
|
+
columns={[
|
|
893
|
+
{ id: "name", label: "SCREEN", width: 28, align: "left" },
|
|
894
|
+
{ id: "criteria", label: "CRITERIA", width: 20, flexGrow: 1, align: "left" },
|
|
895
|
+
]}
|
|
896
|
+
items={SCREEN_IDEAS}
|
|
897
|
+
focused={focused && !saveForm}
|
|
898
|
+
rootWidth={width}
|
|
899
|
+
rootHeight={bodyHeight}
|
|
900
|
+
getItemKey={(row) => row.id}
|
|
901
|
+
selection={{
|
|
902
|
+
kind: "id",
|
|
903
|
+
selectedId: ideaSelectedId,
|
|
904
|
+
getId: (row) => row.id,
|
|
905
|
+
onChange: setIdeaSelectedId,
|
|
906
|
+
}}
|
|
907
|
+
onActivate={(row) => {
|
|
908
|
+
setMetric(row.metric);
|
|
909
|
+
apply(row.definition);
|
|
910
|
+
setCurrentSavedId(null);
|
|
911
|
+
setMode("results");
|
|
912
|
+
}}
|
|
913
|
+
renderCell={(row, column) => ({
|
|
914
|
+
text: column.id === "name" ? row.name : row.definition.criteria.map(criterionText).join("; "),
|
|
915
|
+
})}
|
|
916
|
+
sortColumnId={null}
|
|
917
|
+
sortDirection="asc"
|
|
918
|
+
emptyStateTitle="No ideas."
|
|
919
|
+
/>
|
|
866
920
|
) : mode === "saved" ? (
|
|
867
921
|
!access.emailVerified ? (
|
|
868
922
|
<SignInWall
|
|
@@ -987,7 +1041,7 @@ function EquityScreenView({
|
|
|
987
1041
|
const move = screenExtendedMove(row, column.id.slice(EXTENDED_COLUMN_PREFIX.length) as ExtendedSession);
|
|
988
1042
|
return move == null
|
|
989
1043
|
? { text: "--", color: colors.textDim }
|
|
990
|
-
: { text: formatPercentRaw(move), color:
|
|
1044
|
+
: { text: formatPercentRaw(move), color: shownPriceColor(move, 2, colors) };
|
|
991
1045
|
}
|
|
992
1046
|
const field = column.id.startsWith("m:")
|
|
993
1047
|
? (column.id.slice(2) as NumericField)
|
|
@@ -1042,8 +1096,9 @@ function EquityScreenView({
|
|
|
1042
1096
|
},
|
|
1043
1097
|
});
|
|
1044
1098
|
}}
|
|
1045
|
-
emptyStateTitle={currencyEmpty?.title ?? "No equities match this screen."}
|
|
1046
|
-
emptyStateHint={
|
|
1099
|
+
emptyStateTitle={(unmatchedNames ? null : currencyEmpty?.title) ?? "No equities match this screen."}
|
|
1100
|
+
emptyStateHint={unmatchedNames
|
|
1101
|
+
?? (currencyEmpty ? `${currencyEmpty.detail} Choose ${currencyEmpty.base.currency} to screen in the base currency.` : undefined)}
|
|
1047
1102
|
/>
|
|
1048
1103
|
</PaneStatusBody>
|
|
1049
1104
|
</>
|
|
@@ -4,7 +4,7 @@ import { Box, ChartSurface, Text, useUiCapabilities } from "../../../ui";
|
|
|
4
4
|
import { useThemeColors } from "../../../theme/theme-context";
|
|
5
5
|
import type { ChartVectorShape } from "../../../ui/host";
|
|
6
6
|
import type { ExposureComponent } from "../../../api-client/exposure";
|
|
7
|
-
import { basisLabel, rangeText, type ExposureRow } from "./model";
|
|
7
|
+
import { basisLabel, hopsText, rangeText, type ExposureRow } from "./model";
|
|
8
8
|
import { truncateToDisplayWidth } from "../../../utils/format";
|
|
9
9
|
|
|
10
10
|
/** A disclosure-bound interval, not a probability band. Same numerical domain on both renderers. */
|
|
@@ -53,7 +53,7 @@ export function ExposurePathDiagram({ component, width, height }: { component: E
|
|
|
53
53
|
const nodeWidth = Math.max(8, Math.floor((width - 2) / nodes.length));
|
|
54
54
|
const vectors: ChartVectorShape[] = hops.map((hop, i) => ({ id: `${i}`, points: [{ x: (i + .5) / nodes.length, y: .5 }, { x: (i + 1.5) / nodes.length, y: .5 }], color: hop.pct === null ? colors.textDim : colors.warning, strokeWidth: 2, handles: true }));
|
|
55
55
|
return <Box flexDirection="column" height={height} overflow="hidden">
|
|
56
|
-
<Box paddingX={1} height={1} flexShrink={0}><Text fg={colors.textDim}>{hops.length}
|
|
56
|
+
<Box paddingX={1} height={1} flexShrink={0}><Text fg={colors.textDim}>{hopsText(hops.length)} · {component.classification}{component.exposurePct ? ` · ${rangeText(component.exposurePct)}` : ""}</Text></Box>
|
|
57
57
|
<Box flexDirection="row" height={1} flexShrink={0} paddingX={1}>{nodes.map((node, i) => <Box key={`${node.id}:${i}`} width={nodeWidth} alignItems="center"><ClippedText fg={colors.textBright} text={node.symbol ?? node.name} width={nodeWidth - 2} /></Box>)}</Box>
|
|
58
58
|
<Box height={1} flexShrink={0} paddingX={1}>
|
|
59
59
|
{desktop ? <ChartSurface width={width - 2} height={1} vectors={vectors} /> : <Text fg={colors.textDim}>{nodes.map((_, i) => i < hops.length ? "─".repeat(Math.max(1, nodeWidth - 2)) + "→ " : "").join("")}</Text>}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExposureBasis, ExposureComponent, ExposureExtensionObservation, ExposureHolding, ExposurePayload, ExposureRange, ExposureScenario } from "../../../api-client/exposure";
|
|
2
|
+
import { quarterLabel } from "../shared/earnings-report";
|
|
2
3
|
import { humanLabel } from "../shared/research-cells";
|
|
3
4
|
|
|
4
5
|
export const TABS = [{ value: "table", label: "Table" }, { value: "drivers", label: "Drivers" }, { value: "paths", label: "Paths" }, { value: "portfolio", label: "Portfolio" }];
|
|
@@ -14,6 +15,17 @@ export const basisLabel = (basis: string | null) => basis?.replaceAll("_", " ")
|
|
|
14
15
|
const pct = (value: number) => `${value.toFixed(1)}%`;
|
|
15
16
|
export const rangeText = (range: ExposureRange | null) => !range ? "Unknown" : range.low === range.high ? pct(range.low) : `${range.low.toFixed(1)} to ${range.high.toFixed(1)}%`;
|
|
16
17
|
export const fraction = (value: number) => pct(value * 100);
|
|
18
|
+
/** "1 hop", "2 hops". */
|
|
19
|
+
export const hopsText = (count: number) => `${count} hop${count === 1 ? "" : "s"}`;
|
|
20
|
+
/**
|
|
21
|
+
* A reporting period as the tables word it. The server keys a quarter by its end date (`quarter:2026-07-31`), which
|
|
22
|
+
* reads as the month it ends in, Jul 2026, as the earnings events label a quarter; a date or a source's own words
|
|
23
|
+
* stay as they are.
|
|
24
|
+
*/
|
|
25
|
+
export function periodText(period: string): string {
|
|
26
|
+
const quarter = /^quarter:(\d{4}-\d{2}(?:-\d{2})?)$/.exec(period);
|
|
27
|
+
return quarter ? quarterLabel(quarter[1]) : period;
|
|
28
|
+
}
|
|
17
29
|
|
|
18
30
|
/** A weighted list is entirely explicit. Never silently fill missing weights or normalize shorts. */
|
|
19
31
|
export function parseHoldings(text: string): ExposureHolding[] {
|
|
@@ -78,7 +90,7 @@ export function exposureCellText(row: ExposureRow, column: "label" | "basis" | "
|
|
|
78
90
|
// What the number rests on, in words: disclosed by the company, estimated by a chain, or not visible.
|
|
79
91
|
case "classification": return `${humanLabel(row.classification)}${row.incomplete && row.classification !== "unknown" ? " · partial" : ""}`;
|
|
80
92
|
case "impact": return row.impact ? rangeText(row.impact) : "--";
|
|
81
|
-
case "period": return row.period
|
|
93
|
+
case "period": return row.period ? periodText(row.period) : "--";
|
|
82
94
|
case "weight": return fraction(row.weight ?? 0);
|
|
83
95
|
}
|
|
84
96
|
}
|
|
@@ -20,7 +20,7 @@ import { ExposurePathDiagram, ExposureRanges } from "./charts";
|
|
|
20
20
|
import { fetchExposure, fetchScenarios, validateExposure } from "./client";
|
|
21
21
|
import { ExposureEditor, type ExposureInputs } from "./editor";
|
|
22
22
|
import { portfolioHoldings, watchlistHoldings } from "./holdings";
|
|
23
|
-
import { basisLabel, DEFAULT_SCENARIO, exposureCellText, fraction, parseCustomScenario, parseHoldings, pathRows, portfolioRows, rangeText, tableRows, TABS, type ExposureRow } from "./model";
|
|
23
|
+
import { basisLabel, DEFAULT_SCENARIO, exposureCellText, fraction, hopsText, parseCustomScenario, parseHoldings, pathRows, periodText, portfolioRows, rangeText, tableRows, TABS, type ExposureRow } from "./model";
|
|
24
24
|
import { useExposureEvidence } from "./evidence";
|
|
25
25
|
import { driverRows, driverScope, driverUnits, driverValue } from "./drivers";
|
|
26
26
|
|
|
@@ -29,7 +29,7 @@ function EvidenceItem({ item }: { item: ExposureEvidence }) {
|
|
|
29
29
|
const host = useRendererHost();
|
|
30
30
|
return <Box flexDirection="column" paddingY={1}>
|
|
31
31
|
<KeyValueRow labelWidth={20} label="Evidence" value={`${item.tier} · ${item.asOf ?? "undated"}`} detail={item.confidence === null ? undefined : `${Math.round(item.confidence * 100)}% confidence`} />
|
|
32
|
-
<KeyValueRow labelWidth={20} label="Period / units" value={`${item.period
|
|
32
|
+
<KeyValueRow labelWidth={20} label="Period / units" value={`${item.period ? periodText(item.period) : "unknown"} · ${item.units}${item.currency ? ` ${item.currency}` : ""}`} />
|
|
33
33
|
{item.revision ? <KeyValueRow labelWidth={20} label="Revision" value={item.revision} /> : null}
|
|
34
34
|
{item.quote ? <Text fg={colors.textBright}>{item.quote}</Text> : <Text fg={colors.textDim}>{item.tier === "user" ? "User-supplied assumption" : "Structured observation; no verbatim quote"}</Text>}
|
|
35
35
|
{item.url ? <ActionRow label="Open primary evidence" onPress={() => void host.openExternal(item.url!)} /> : null}
|
|
@@ -49,7 +49,7 @@ function ExposureDetail({ row, data, width }: { row: ExposureRow | null; data: E
|
|
|
49
49
|
{Object.entries(row.driver.dimensions ?? {}).map(([key, value]) => <KeyValueRow key={key} labelWidth={20} label={humanLabel(key)} value={value} />)}
|
|
50
50
|
{row.driver.evidence.map(item => <EvidenceItem key={item.id} item={item} />)}
|
|
51
51
|
</> : row ? <>
|
|
52
|
-
<KeyValueRow labelWidth={20} label="Basis / period" value={`${basisLabel(row.basis)} · ${row.period
|
|
52
|
+
<KeyValueRow labelWidth={20} label="Basis / period" value={`${basisLabel(row.basis)} · ${row.period ? periodText(row.period) : "unknown"}`} />
|
|
53
53
|
<KeyValueRow labelWidth={20} label={row.concentration ? "Gross exposure" : "Exposure"} value={rangeText(row.exposure)} detail={`${row.classification}${row.incomplete ? " · incomplete bound" : ""}`} />
|
|
54
54
|
<KeyValueRow labelWidth={20} label={row.concentration ? "Signed exposure" : "Operating stress"} value={rangeText(row.impact)} detail={row.concentration ? "NAV-weighted operating exposure; not a scenario change" : "Estimated change in the stated denominator"} />
|
|
55
55
|
{row.components.map(component => <Box key={component.id} flexDirection="column" paddingY={1}>
|
|
@@ -249,7 +249,7 @@ export function ExposurePane({ width, height, focused }: PaneProps) {
|
|
|
249
249
|
const query = <QueryBar width={width} filters={[
|
|
250
250
|
{ id: "scenario", label: "Scenario", value: scenarioId, options: [...(library.data ?? [DEFAULT_SCENARIO]).map(s => ({ value: s.id ?? (snapshot ? "captured" : "custom"), label: s.label })), { value: "custom", label: "Custom" }].filter((s, i, all) => all.findIndex(v => v.value === s.value) === i), onChange: (value: string) => { if (value === "custom") setEditing(true); else setScenarioId(value); } },
|
|
251
251
|
...(tab === "portfolio" ? [{ id: "group", label: "View", value: view, options: [{ value: "stress", label: "Stress" }, { value: "country", label: "Country" }, { value: "supplier", label: "Supplier" }, { value: "customer", label: "Customer" }], onChange: setView }] : []),
|
|
252
|
-
]} meta={tab === "drivers" ? "Company disclosures" : tab === "portfolio" ? "NAV-weighted operating exposure" :
|
|
252
|
+
]} meta={tab === "drivers" ? "Company disclosures" : tab === "portfolio" ? "NAV-weighted operating exposure" : hopsText(data?.depth ?? inputs.depth)} />;
|
|
253
253
|
if (!data && isCloudSessionRequired(resource.error) && !editing) return <SignInWall placement="exposure-signin" action="analyze exposures" needsVerification={session.needsVerification} />;
|
|
254
254
|
return <Box width={width} height={height} flexDirection="column">
|
|
255
255
|
{strip}
|
|
@@ -51,7 +51,8 @@ const SECTION_VIEWS: Record<FixedIncomeSection, { defaultView: string; views: Vi
|
|
|
51
51
|
defaultView: "documents",
|
|
52
52
|
views: [
|
|
53
53
|
{ value: "documents", label: "Documents", paneType: "credit-documents", component: ResearchCreditDocumentsPane },
|
|
54
|
-
|
|
54
|
+
// CRDOC's own Maturities tab is the contractual calendar-year wall; this is the filed fiscal-bucket schedule.
|
|
55
|
+
{ value: "maturities", label: "Debt Schedule", paneType: "debt-maturities", component: ResearchDebtMaturitiesPane },
|
|
55
56
|
],
|
|
56
57
|
},
|
|
57
58
|
bonds: {
|
|
@@ -5,6 +5,7 @@ import type { BoardQuoteMap } from "../shared/use-quote-board";
|
|
|
5
5
|
import { FUTURES_SECTOR_ORDER, type FuturesContract, type FuturesSector } from "../../../market-data/futures-board";
|
|
6
6
|
import { futuresPriceUnit } from "../../../market-data/futures-contract";
|
|
7
7
|
import { contractMonthLabel } from "../../../utils/contract-month";
|
|
8
|
+
import { quoteDotMarketState } from "../../../market-data/market/status";
|
|
8
9
|
import type { FuturesReturnHorizon } from "./returns";
|
|
9
10
|
import type { FuturesTermsMap } from "./use-board-terms";
|
|
10
11
|
import type { FrontReturnsMap } from "./use-front-returns";
|
|
@@ -169,7 +170,7 @@ function getSortValue(
|
|
|
169
170
|
case "unit":
|
|
170
171
|
return futuresUnitLabel(contract, terms?.get(contract.symbol)?.terms);
|
|
171
172
|
case "status":
|
|
172
|
-
return quote
|
|
173
|
+
return quoteDotMarketState(quote) === "REGULAR" ? 0 : 1;
|
|
173
174
|
case "code":
|
|
174
175
|
return contract.code;
|
|
175
176
|
case "name":
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DataTableCell, DataTableColumn } from "../../../components";
|
|
2
|
-
import { colors,
|
|
2
|
+
import { colors, shownPriceColor } from "../../../theme/colors";
|
|
3
3
|
import type { Quote } from "../../../types/financials";
|
|
4
4
|
import { TextAttributes } from "../../../ui";
|
|
5
5
|
import { formatCompact, formatNumber, formatPercentRaw } from "../../../utils/format";
|
|
@@ -253,7 +253,7 @@ export function renderFuturesCell(
|
|
|
253
253
|
if (isBoardRowLoading(state) || returns?.loading) return { text: "…", color: colors.textDim };
|
|
254
254
|
const value = returns?.values[FUTURES_RETURN_COLUMNS[column.id]!];
|
|
255
255
|
if (value == null || !Number.isFinite(value)) return { text: "—", color: colors.textDim };
|
|
256
|
-
return { text: formatPercentRaw(value), color:
|
|
256
|
+
return { text: formatPercentRaw(value), color: shownPriceColor(value, 2) };
|
|
257
257
|
}
|
|
258
258
|
case "prevClose":
|
|
259
259
|
if (isBoardRowLoading(state)) return { text: "…", color: colors.textDim };
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { ApiRequestError } from "../../../api-client/errors";
|
|
2
3
|
import type { FuturesContractTermsPayload } from "../../../api-client/futures-curve";
|
|
3
4
|
import type { FuturesContract } from "../../../market-data/futures-board";
|
|
5
|
+
import { createTtlCache } from "../../../data/ttl-cache";
|
|
4
6
|
import { loadFuturesContractTerms } from "../../../market-data/futures-contract";
|
|
7
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
5
8
|
|
|
6
9
|
interface FuturesTermsState {
|
|
7
10
|
/** The row's terms are still being asked for. */
|
|
8
11
|
loading: boolean;
|
|
9
|
-
/** Null when the server has
|
|
12
|
+
/** Null while they are being asked for, when the server has none for the symbol, or when the read failed. */
|
|
10
13
|
terms: FuturesContractTermsPayload | null;
|
|
11
14
|
}
|
|
12
15
|
|
|
@@ -14,32 +17,35 @@ export type FuturesTermsMap = ReadonlyMap<string, FuturesTermsState>;
|
|
|
14
17
|
|
|
15
18
|
/** The terms change with a roll, and a roll is decided once a day. */
|
|
16
19
|
const TERMS_TTL_MS = 30 * 60_000;
|
|
20
|
+
/**
|
|
21
|
+
* A read that failed is asked again after this: one blip leaves a row without
|
|
22
|
+
* its terms for a minute, not for the TTL, and a service that is down is asked
|
|
23
|
+
* once a minute, not once a tick.
|
|
24
|
+
*/
|
|
25
|
+
const TERMS_RETRY_MS = 60_000;
|
|
17
26
|
/** Forty-odd rows open at once; a few requests at a time keep the board's quotes first. */
|
|
18
27
|
const TERMS_CONCURRENCY = 4;
|
|
19
28
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const termsCache = new Map<string, CachedTerms>();
|
|
27
|
-
const inFlight = new Map<string, Promise<void>>();
|
|
29
|
+
/**
|
|
30
|
+
* Null when the server has no terms for the symbol, which is an answer and is
|
|
31
|
+
* kept like any other. A read that fails is not kept as one: it rejects, and
|
|
32
|
+
* the cache holds the rejection for `TERMS_RETRY_MS`.
|
|
33
|
+
*/
|
|
34
|
+
const termsCache = createTtlCache<FuturesContractTermsPayload | null>({ ttlMs: TERMS_TTL_MS, rejectionMs: TERMS_RETRY_MS });
|
|
28
35
|
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
async function readTerms(symbol: string): Promise<FuturesContractTermsPayload | null> {
|
|
37
|
+
try {
|
|
38
|
+
return await loadFuturesContractTerms(symbol);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if (error instanceof ApiRequestError && error.status === 404) return null;
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
32
43
|
}
|
|
33
44
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
.then((terms) => { termsCache.set(symbol, { at: Date.now(), terms }); })
|
|
39
|
-
.catch(() => { termsCache.set(symbol, { at: Date.now(), terms: null }); })
|
|
40
|
-
.finally(() => { inFlight.delete(symbol); });
|
|
41
|
-
inFlight.set(symbol, request);
|
|
42
|
-
return request;
|
|
45
|
+
/** Whether the row has an answer; false when the read failed. */
|
|
46
|
+
function loadTerms(symbol: string): Promise<boolean> {
|
|
47
|
+
return termsCache.load(symbol, () => readTerms(symbol))
|
|
48
|
+
.then(() => true, logFailure("futures", "Read a futures row's contract terms failed", false));
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
/**
|
|
@@ -51,32 +57,40 @@ function loadTerms(symbol: string): Promise<void> {
|
|
|
51
57
|
export function useFuturesBoardTerms(contracts: readonly FuturesContract[], enabled: boolean): FuturesTermsMap {
|
|
52
58
|
const symbolKey = contracts.map((contract) => contract.symbol).join(",");
|
|
53
59
|
const [version, setVersion] = useState(0);
|
|
60
|
+
// Bumped once the retry delay has passed after a failed read, to ask again.
|
|
61
|
+
const [retry, setRetry] = useState(0);
|
|
54
62
|
|
|
55
63
|
useEffect(() => {
|
|
56
64
|
if (!enabled || !symbolKey) return;
|
|
57
|
-
const
|
|
58
|
-
const queue = symbolKey.split(",").filter((symbol) => !fresh(symbol, now));
|
|
65
|
+
const queue = symbolKey.split(",").filter((symbol) => termsCache.peek(symbol) === undefined);
|
|
59
66
|
if (queue.length === 0) return;
|
|
60
67
|
let cancelled = false;
|
|
68
|
+
let failed = false;
|
|
69
|
+
let retryTimer: ReturnType<typeof setTimeout> | undefined;
|
|
61
70
|
const worker = async () => {
|
|
62
71
|
for (let symbol = queue.shift(); symbol; symbol = queue.shift()) {
|
|
63
|
-
await loadTerms(symbol);
|
|
72
|
+
if (!await loadTerms(symbol)) failed = true;
|
|
64
73
|
if (!cancelled) setVersion((current) => current + 1);
|
|
65
74
|
}
|
|
66
75
|
};
|
|
67
|
-
void Promise.all(Array.from({ length: Math.min(TERMS_CONCURRENCY, queue.length) }, worker))
|
|
76
|
+
void Promise.all(Array.from({ length: Math.min(TERMS_CONCURRENCY, queue.length) }, worker)).then(() => {
|
|
77
|
+
if (failed && !cancelled) retryTimer = setTimeout(() => setRetry((current) => current + 1), TERMS_RETRY_MS);
|
|
78
|
+
});
|
|
68
79
|
return () => {
|
|
69
80
|
cancelled = true;
|
|
70
81
|
queue.length = 0;
|
|
82
|
+
clearTimeout(retryTimer);
|
|
71
83
|
};
|
|
72
|
-
}, [enabled, symbolKey]);
|
|
84
|
+
}, [enabled, symbolKey, retry]);
|
|
73
85
|
|
|
74
86
|
return useMemo(() => {
|
|
75
87
|
const result = new Map<string, FuturesTermsState>();
|
|
76
88
|
if (!enabled) return result;
|
|
77
89
|
for (const symbol of symbolKey ? symbolKey.split(",") : []) {
|
|
78
|
-
|
|
79
|
-
|
|
90
|
+
// A row keeps the terms it had past their TTL until the next load lands, and
|
|
91
|
+
// one whose read failed draws without them rather than waiting on a retry.
|
|
92
|
+
const terms = termsCache.peek(symbol, { stale: true });
|
|
93
|
+
result.set(symbol, { loading: terms === undefined && !termsCache.failed(symbol), terms: terms ?? null });
|
|
80
94
|
}
|
|
81
95
|
return result;
|
|
82
96
|
// `version` re-reads the cache as loads land.
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { createTtlCache } from "../../../data/ttl-cache";
|
|
2
3
|
import type { DataProvider } from "../../../types/data-provider";
|
|
4
|
+
import { isNoSourceAnswer } from "../../../sources/provider-errors";
|
|
3
5
|
import type { PricePoint } from "../../../types/financials";
|
|
6
|
+
import { logFailure } from "../../../utils/log-failure";
|
|
4
7
|
import type { BoardQuoteMap } from "../shared/use-quote-board";
|
|
5
8
|
import type { FuturesContract } from "../../../market-data/futures-board";
|
|
6
9
|
import {
|
|
@@ -24,32 +27,33 @@ export type FrontReturnsMap = ReadonlyMap<string, FrontReturnsState>;
|
|
|
24
27
|
* for half an hour; the live quote moves the returns in between.
|
|
25
28
|
*/
|
|
26
29
|
const HISTORY_TTL_MS = 30 * 60_000;
|
|
30
|
+
/**
|
|
31
|
+
* A read that failed is asked again after this: one blip leaves a row without
|
|
32
|
+
* returns for a minute, not for the TTL, and a service that is down is asked
|
|
33
|
+
* once a minute, not once a tick.
|
|
34
|
+
*/
|
|
35
|
+
const HISTORY_RETRY_MS = 60_000;
|
|
27
36
|
/** Forty-odd contracts open at once; a few requests at a time keep the board's quotes first. */
|
|
28
37
|
const HISTORY_CONCURRENCY = 4;
|
|
29
38
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const historyCache = new Map<string, CachedHistory>();
|
|
37
|
-
const inFlight = new Map<string, Promise<void>>();
|
|
39
|
+
/**
|
|
40
|
+
* Null when the source has no history for the contract (delisted, unknown),
|
|
41
|
+
* which is an answer and is kept like any other. A read that fails is not kept
|
|
42
|
+
* as one: it rejects, and the cache holds the rejection for `HISTORY_RETRY_MS`.
|
|
43
|
+
*/
|
|
44
|
+
const historyCache = createTtlCache<readonly PricePoint[] | null>({ ttlMs: HISTORY_TTL_MS, rejectionMs: HISTORY_RETRY_MS });
|
|
38
45
|
|
|
39
|
-
function
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
function readHistory(provider: DataProvider, symbol: string): Promise<readonly PricePoint[] | null> {
|
|
47
|
+
return provider.getPriceHistory(symbol, "", "1Y").then((points) => points.length > 0 ? points : null, (error: unknown) => {
|
|
48
|
+
if (isNoSourceAnswer(error)) return null;
|
|
49
|
+
throw error;
|
|
50
|
+
});
|
|
42
51
|
}
|
|
43
52
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
.then((points) => { historyCache.set(symbol, { at: Date.now(), points: points.length > 0 ? points : null }); })
|
|
49
|
-
.catch(() => { historyCache.set(symbol, { at: Date.now(), points: null }); })
|
|
50
|
-
.finally(() => { inFlight.delete(symbol); });
|
|
51
|
-
inFlight.set(symbol, request);
|
|
52
|
-
return request;
|
|
53
|
+
/** Whether the contract has an answer; false when the read failed. */
|
|
54
|
+
function loadHistory(provider: DataProvider, symbol: string): Promise<boolean> {
|
|
55
|
+
return historyCache.load(symbol, () => readHistory(provider, symbol))
|
|
56
|
+
.then(() => true, logFailure("futures", "Read a futures contract's history failed", false));
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
/**
|
|
@@ -73,27 +77,33 @@ export function useFrontContractReturns(
|
|
|
73
77
|
return [symbol, list ? list.split(",") : []] as const;
|
|
74
78
|
})), [candidateKey]);
|
|
75
79
|
const [version, setVersion] = useState(0);
|
|
80
|
+
// Bumped once the retry delay has passed after a failed read, to ask again.
|
|
81
|
+
const [retry, setRetry] = useState(0);
|
|
76
82
|
// A board left open overnight reloads once the day turns.
|
|
77
83
|
const today = new Date().toISOString().slice(0, 10);
|
|
78
84
|
|
|
79
85
|
useEffect(() => {
|
|
80
86
|
if (!enabled || !provider) return;
|
|
81
|
-
const
|
|
82
|
-
const queue = [...new Set([...candidates.values()].flat())].filter((symbol) => !freshHistory(symbol, now));
|
|
87
|
+
const queue = [...new Set([...candidates.values()].flat())].filter((symbol) => historyCache.peek(symbol) === undefined);
|
|
83
88
|
if (queue.length === 0) return;
|
|
84
89
|
let cancelled = false;
|
|
90
|
+
let failed = false;
|
|
91
|
+
let retryTimer: ReturnType<typeof setTimeout> | undefined;
|
|
85
92
|
const worker = async () => {
|
|
86
93
|
for (let symbol = queue.shift(); symbol; symbol = queue.shift()) {
|
|
87
|
-
await loadHistory(provider, symbol);
|
|
94
|
+
if (!await loadHistory(provider, symbol)) failed = true;
|
|
88
95
|
if (!cancelled) setVersion((current) => current + 1);
|
|
89
96
|
}
|
|
90
97
|
};
|
|
91
|
-
void Promise.all(Array.from({ length: Math.min(HISTORY_CONCURRENCY, queue.length) }, worker))
|
|
98
|
+
void Promise.all(Array.from({ length: Math.min(HISTORY_CONCURRENCY, queue.length) }, worker)).then(() => {
|
|
99
|
+
if (failed && !cancelled) retryTimer = setTimeout(() => setRetry((current) => current + 1), HISTORY_RETRY_MS);
|
|
100
|
+
});
|
|
92
101
|
return () => {
|
|
93
102
|
cancelled = true;
|
|
94
103
|
queue.length = 0;
|
|
104
|
+
clearTimeout(retryTimer);
|
|
95
105
|
};
|
|
96
|
-
}, [candidates, enabled, provider, today]);
|
|
106
|
+
}, [candidates, enabled, provider, today, retry]);
|
|
97
107
|
|
|
98
108
|
return useMemo(() => {
|
|
99
109
|
const result = new Map<string, FrontReturnsState>();
|
|
@@ -101,12 +111,14 @@ export function useFrontContractReturns(
|
|
|
101
111
|
for (const contract of contracts) {
|
|
102
112
|
const quote = quotes.get(contract.symbol)?.quote;
|
|
103
113
|
const symbols = candidates.get(contract.symbol) ?? [];
|
|
104
|
-
|
|
105
|
-
|
|
114
|
+
// A contract keeps the history it had past its TTL until the next load lands,
|
|
115
|
+
// and one whose read failed counts as having none rather than waiting on a retry.
|
|
116
|
+
const loaded = symbols.map((symbol) => ({ symbol, history: historyCache.peek(symbol, { stale: true }), failed: historyCache.failed(symbol) }));
|
|
117
|
+
if (loaded.some((entry) => entry.history === undefined && !entry.failed)) {
|
|
106
118
|
result.set(contract.symbol, { loading: true, values: NO_FUTURES_RETURNS });
|
|
107
119
|
continue;
|
|
108
120
|
}
|
|
109
|
-
const available = loaded.flatMap((entry) => entry.
|
|
121
|
+
const available = loaded.flatMap((entry) => entry.history ? [{ symbol: entry.symbol, history: entry.history }] : []);
|
|
110
122
|
const front = pickFrontContract(available, quote?.price);
|
|
111
123
|
const history = available.find((entry) => entry.symbol === front)?.history;
|
|
112
124
|
result.set(contract.symbol, {
|
|
@@ -118,3 +130,8 @@ export function useFrontContractReturns(
|
|
|
118
130
|
// `version` re-reads the history cache as loads land.
|
|
119
131
|
}, [candidates, contracts, enabled, quotes, version]);
|
|
120
132
|
}
|
|
133
|
+
|
|
134
|
+
/** Forgets every cached contract's history (tests). */
|
|
135
|
+
export function resetFrontContractReturns(): void {
|
|
136
|
+
historyCache.clear();
|
|
137
|
+
}
|