@gloom-sh/terminal 0.18.5 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +163 -8
- package/src/api-client/app-attention.ts +3 -0
- package/src/api-client/beneficial-owners.ts +3 -0
- package/src/api-client/company-kpis.ts +3 -0
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +19 -7
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/hiring.ts +3 -0
- package/src/api-client/index.ts +15 -0
- package/src/api-client/ipo.ts +24 -1
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +1 -1
- package/src/api-client/mna.ts +3 -0
- package/src/api-client/paths.ts +9 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/research-activity.ts +3 -2
- package/src/api-client/roadmap.ts +85 -0
- package/src/api-client/types.ts +82 -2
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +0 -2
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/core-builtin-allowlist.json +2 -2
- package/src/architecture/pane-tabs-allowlist.json +3 -0
- package/src/architecture/ttl-caches-allowlist.json +19 -0
- package/src/brokers/sync-broker-instance.ts +2 -8
- package/src/capabilities/chart-series.ts +1 -1
- package/src/cli/commands/crypto-hints.ts +40 -24
- package/src/cli/commands/market.ts +107 -38
- package/src/cli/commands/news-search.ts +22 -0
- package/src/cli/commands/options-chain.ts +48 -7
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +147 -32
- package/src/cli/commands/search.ts +21 -1
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +87 -36
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +25 -2
- package/src/cli/glossary.ts +31 -2
- package/src/cli/history-rows.ts +57 -9
- package/src/cli/index-roots.ts +0 -7
- package/src/cli/index.ts +2 -0
- package/src/cli/listing-arg.ts +235 -60
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +4 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/discovery.ts +21 -1
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +93 -4
- package/src/cli/pane-functions/function-help.ts +20 -10
- package/src/cli/pane-functions/headless.ts +17 -7
- package/src/cli/pane-functions/index.ts +54 -16
- package/src/cli/pane-functions/market-session.ts +66 -4
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/resolver.ts +19 -8
- package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
- package/src/cli/pane-functions/screenshot-types.ts +5 -0
- package/src/cli/quote-answer.ts +22 -11
- package/src/cli/saved-copy-persistence.ts +88 -0
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +4 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-surface.tsx +52 -3
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +39 -4
- package/src/components/chart/composite/series-cues.ts +14 -0
- package/src/components/chart/composite/text-renderer.ts +9 -2
- package/src/components/chart/composite/tool-catalog.ts +36 -0
- package/src/components/chart/composite/tools.ts +203 -44
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
- package/src/components/command-bar/assist/inventory.ts +2 -1
- package/src/components/command-bar/commands/direct/index.ts +28 -13
- package/src/components/command-bar/list/model.ts +5 -0
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/routes/root/runtime.ts +6 -7
- package/src/components/command-bar/selection-runtime.ts +52 -3
- package/src/components/command-bar/surface/index.tsx +2 -0
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/pane/capture-stamp.ts +2 -2
- package/src/components/layout/pane/footer/index.tsx +22 -6
- package/src/components/layout/pane/footer/model.ts +83 -15
- package/src/components/layout/pane/status-footer.ts +35 -2
- package/src/components/layout/status-widget-space.ts +9 -0
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +12 -7
- package/src/components/onboarding/wizard-broker-sync.ts +0 -1
- package/src/components/onboarding/wizard-positions.ts +3 -3
- package/src/components/ticker/list-table-view.tsx +8 -21
- package/src/components/ui/choice-dialog.tsx +23 -18
- package/src/components/ui/choice-groups.ts +18 -0
- package/src/components/ui/data-table/opentui/index.tsx +9 -7
- package/src/components/ui/data-table/opentui/model.ts +26 -1
- package/src/components/ui/data-table/types.ts +2 -0
- package/src/components/ui/display.tsx +18 -3
- package/src/components/ui/icon.tsx +4 -1
- package/src/components/ui/list-view.tsx +8 -4
- package/src/components/ui/query-bar-keys.tsx +2 -0
- package/src/components/ui/query-bar.tsx +1 -0
- package/src/components/ui/select-button.tsx +5 -1
- package/src/components/ui/select-field.tsx +1 -0
- package/src/components/ui/table-layout.ts +8 -5
- package/src/core/app-runtime.ts +2 -0
- package/src/core/app-services.ts +2 -2
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/plugin-cache.ts +5 -3
- package/src/data/saved-copies.ts +8 -0
- package/src/data/ticker-repository.ts +16 -9
- package/src/data/ticker-writes.ts +81 -0
- package/src/data/ttl-cache.ts +165 -0
- package/src/docs/snapshot.json +103 -142
- package/src/i18n/es.ts +20 -6
- package/src/i18n/ja.ts +20 -6
- package/src/i18n/ko.ts +20 -6
- package/src/i18n/zh-cn.ts +20 -6
- package/src/i18n/zh-tw.ts +20 -6
- package/src/market-data/depositary-pair.ts +27 -3
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +16 -3
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/options-alternatives.ts +81 -4
- package/src/market-data/quotes/metadata.ts +7 -0
- package/src/news/aggregator.ts +119 -9
- package/src/news/cloud-session.ts +21 -0
- package/src/news/hooks.ts +1 -0
- package/src/news/news-model.ts +1 -0
- package/src/news/types.ts +17 -0
- package/src/plugins/builtin/alerts/alert-engine.ts +20 -1
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/calendar-events.ts +10 -4
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +362 -80
- package/src/plugins/builtin/alerts/command.ts +25 -1
- package/src/plugins/builtin/alerts/event-rows.ts +24 -3
- package/src/plugins/builtin/alerts/events-pane.tsx +34 -10
- package/src/plugins/builtin/alerts/fired-log.ts +73 -9
- package/src/plugins/builtin/alerts/format.ts +75 -5
- package/src/plugins/builtin/alerts/headless.ts +34 -12
- package/src/plugins/builtin/alerts/history-rows.ts +3 -2
- package/src/plugins/builtin/alerts/history.ts +1 -1
- package/src/plugins/builtin/alerts/index.tsx +79 -16
- package/src/plugins/builtin/alerts/metric-command.ts +77 -4
- package/src/plugins/builtin/alerts/metric-data.ts +54 -13
- package/src/plugins/builtin/alerts/metric-model.ts +163 -14
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +1 -1
- package/src/plugins/builtin/alerts/pane.tsx +61 -23
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +12 -8
- package/src/plugins/builtin/alerts/types.ts +4 -0
- package/src/plugins/builtin/analytics/index.tsx +2 -1
- package/src/plugins/builtin/analytics/risk-client.ts +16 -5
- package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
- package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
- package/src/plugins/builtin/analytics/risk-model.ts +55 -9
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/attention/index.tsx +2 -1
- package/src/plugins/builtin/attention/listing.ts +14 -0
- package/src/plugins/builtin/broker-manager/index.tsx +7 -3
- package/src/plugins/builtin/buildout/cells.tsx +12 -4
- package/src/plugins/builtin/buildout/format.ts +38 -0
- package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
- package/src/plugins/builtin/buildout/table-model.ts +23 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +6 -2
- package/src/plugins/builtin/cds/client.ts +142 -30
- package/src/plugins/builtin/cds/headless.ts +35 -8
- package/src/plugins/builtin/cds/model.ts +145 -18
- package/src/plugins/builtin/cds/pane.tsx +16 -5
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +3 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +4 -0
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +7 -0
- package/src/plugins/builtin/chart-composer/editor/controller.ts +2 -2
- package/src/plugins/builtin/chart-composer/follow-binding.ts +14 -2
- package/src/plugins/builtin/chart-composer/headless-schema.ts +7 -1
- package/src/plugins/builtin/chart-composer/headless.ts +6 -1
- package/src/plugins/builtin/chart-composer/index.tsx +5 -6
- package/src/plugins/builtin/chart-composer/pane.tsx +37 -8
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chat/content/command-suggestions.ts +1 -0
- package/src/plugins/builtin/chat/controller/index.ts +5 -1
- package/src/plugins/builtin/chat/controller/notifications.ts +33 -3
- package/src/plugins/builtin/chat/controller/state.ts +25 -2
- package/src/plugins/builtin/chat/controller/unread-people.ts +89 -23
- package/src/plugins/builtin/chat/controller/utils.ts +7 -2
- package/src/plugins/builtin/chat/pane-links/commands.ts +7 -1
- package/src/plugins/builtin/chat/status-preview.ts +79 -0
- package/src/plugins/builtin/chat/status-widget.tsx +147 -10
- package/src/plugins/builtin/chat/unread-badge.ts +51 -10
- package/src/plugins/builtin/clinical-trials/client.ts +49 -4
- package/src/plugins/builtin/clinical-trials/model.ts +86 -31
- package/src/plugins/builtin/clinical-trials/pane.tsx +61 -49
- package/src/plugins/builtin/clinical-trials/sponsor-listing.ts +90 -0
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +95 -15
- package/src/plugins/builtin/clinical-trials/trial-pages.ts +50 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- package/src/plugins/builtin/company-attention/building.tsx +5 -1
- package/src/plugins/builtin/company-attention/client.ts +8 -9
- package/src/plugins/builtin/company-attention/evidence.ts +4 -1
- package/src/plugins/builtin/company-attention/headless.ts +2 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +12 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/company-kpis/headless.ts +8 -3
- package/src/plugins/builtin/company-kpis/model.ts +73 -5
- package/src/plugins/builtin/company-kpis/pane.tsx +15 -7
- package/src/plugins/builtin/composite-plugins.ts +2 -1
- package/src/plugins/builtin/correlation/headless.ts +13 -5
- package/src/plugins/builtin/correlation/history.ts +22 -0
- package/src/plugins/builtin/cot/client.ts +22 -18
- package/src/plugins/builtin/cot/headless.ts +15 -10
- package/src/plugins/builtin/cot/model.ts +14 -1
- package/src/plugins/builtin/cot/pane.tsx +22 -14
- package/src/plugins/builtin/credit-boards/client.ts +6 -10
- package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
- package/src/plugins/builtin/credit-conditions/model.ts +4 -0
- package/src/plugins/builtin/credit-conditions/series.ts +3 -1
- package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
- package/src/plugins/builtin/credit-documents/export.ts +173 -0
- package/src/plugins/builtin/credit-documents/headless.ts +10 -10
- package/src/plugins/builtin/credit-documents/model.ts +104 -14
- package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
- package/src/plugins/builtin/custom-view/sources.ts +2 -0
- package/src/plugins/builtin/dividend-yield/client.ts +131 -23
- package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
- package/src/plugins/builtin/dividend-yield/model.ts +8 -5
- package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
- package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
- package/src/plugins/builtin/dividend-yield/types.ts +20 -2
- package/src/plugins/builtin/dividend-yield/view.ts +30 -2
- package/src/plugins/builtin/docs/targets.ts +5 -1
- package/src/plugins/builtin/doe/headless.ts +3 -2
- package/src/plugins/builtin/doe/pane.tsx +13 -3
- package/src/plugins/builtin/earnings/board-model.ts +16 -15
- package/src/plugins/builtin/earnings/board.tsx +62 -71
- package/src/plugins/builtin/earnings/headless.ts +40 -4
- package/src/plugins/builtin/earnings/scope.ts +31 -43
- package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
- package/src/plugins/builtin/earnings-calls/data.ts +18 -6
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +20 -5
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +12 -4
- package/src/plugins/builtin/econ/calendar-model.ts +48 -1
- package/src/plugins/builtin/econ/calendar-source.ts +4 -2
- package/src/plugins/builtin/econ/detail-view.tsx +2 -2
- package/src/plugins/builtin/econ/figures.ts +48 -0
- package/src/plugins/builtin/econ/index.tsx +14 -5
- package/src/plugins/builtin/econ/print-watch.ts +3 -1
- package/src/plugins/builtin/econ/types.ts +2 -0
- package/src/plugins/builtin/econ-statistics/client.ts +11 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +16 -6
- package/src/plugins/builtin/econ-statistics/pane.tsx +8 -4
- package/src/plugins/builtin/econ-statistics/stats.ts +39 -9
- package/src/plugins/builtin/equity-screener/categories.ts +179 -0
- package/src/plugins/builtin/equity-screener/client.ts +4 -2
- package/src/plugins/builtin/equity-screener/headless.ts +144 -13
- package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
- package/src/plugins/builtin/equity-screener/model.ts +99 -10
- package/src/plugins/builtin/equity-screener/pane.tsx +68 -13
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
- package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
- package/src/plugins/builtin/fx-matrix/client.ts +6 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +1 -1
- package/src/plugins/builtin/fx-matrix/index.tsx +4 -2
- package/src/plugins/builtin/fx-matrix/live-legs.ts +42 -3
- package/src/plugins/builtin/gpu/headless.ts +8 -2
- package/src/plugins/builtin/help/function-index.ts +4 -1
- package/src/plugins/builtin/help/index.tsx +62 -59
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/help/shortcut-table.tsx +5 -6
- package/src/plugins/builtin/help/table-cells.tsx +13 -2
- package/src/plugins/builtin/holders/beneficial-client.ts +5 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +3 -0
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +22 -3
- package/src/plugins/builtin/holders/pane.tsx +3 -0
- package/src/plugins/builtin/holders/report-rows.ts +4 -1
- package/src/plugins/builtin/insider/headless.ts +2 -2
- package/src/plugins/builtin/ipo-calendar/client.ts +18 -2
- package/src/plugins/builtin/ipo-calendar/headless.ts +75 -11
- package/src/plugins/builtin/ipo-calendar/listing-ipo.ts +160 -0
- package/src/plugins/builtin/ipo-calendar/model.ts +98 -2
- package/src/plugins/builtin/iv-history/client.ts +52 -11
- package/src/plugins/builtin/iv-history/format.ts +36 -23
- package/src/plugins/builtin/iv-history/headless.ts +33 -18
- package/src/plugins/builtin/iv-history/index.tsx +2 -2
- package/src/plugins/builtin/iv-history/model.ts +163 -27
- package/src/plugins/builtin/iv-history/pane.tsx +10 -6
- package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
- package/src/plugins/builtin/iv-history/universe.ts +5 -1
- package/src/plugins/builtin/jobs/client.ts +25 -29
- package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
- package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
- package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
- package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
- package/src/plugins/builtin/macro-day/headless.ts +12 -5
- package/src/plugins/builtin/macro-day/index.tsx +1 -1
- package/src/plugins/builtin/macro-day/model.ts +39 -5
- package/src/plugins/builtin/macro-day/pane.tsx +27 -19
- package/src/plugins/builtin/macro-day/release-table.ts +2 -2
- package/src/plugins/builtin/market-heatmap/data.ts +8 -33
- package/src/plugins/builtin/market-movers/client.ts +27 -7
- package/src/plugins/builtin/market-movers/headless.ts +34 -12
- package/src/plugins/builtin/market-movers/index.tsx +26 -14
- package/src/plugins/builtin/market-movers/min-float.ts +74 -0
- package/src/plugins/builtin/market-movers/model.ts +4 -1
- package/src/plugins/builtin/market-movers/screener.ts +22 -4
- package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
- package/src/plugins/builtin/market-movers/session-table.ts +4 -3
- package/src/plugins/builtin/market-movers/table.ts +17 -5
- package/src/plugins/builtin/members/breadth-pane.tsx +2 -1
- package/src/plugins/builtin/mna/headless.ts +2 -0
- package/src/plugins/builtin/mna/pane.tsx +3 -1
- package/src/plugins/builtin/news/headless.ts +3 -1
- package/src/plugins/builtin/news/index.tsx +4 -0
- package/src/plugins/builtin/news/wire/categories.ts +19 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
- package/src/plugins/builtin/openfda/client.ts +14 -2
- package/src/plugins/builtin/openfda/index.tsx +3 -2
- package/src/plugins/builtin/openfda/model.ts +9 -0
- package/src/plugins/builtin/openfda/pane.tsx +42 -6
- package/src/plugins/builtin/options/analytics.ts +29 -5
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/table.ts +5 -2
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +17 -4
- package/src/plugins/builtin/options-positioning/client.ts +11 -0
- package/src/plugins/builtin/options-positioning/headless.ts +21 -8
- package/src/plugins/builtin/options-positioning/index.tsx +1 -1
- package/src/plugins/builtin/options-positioning/model.ts +78 -9
- package/src/plugins/builtin/options-positioning/pane.tsx +21 -6
- package/src/plugins/builtin/options-scenario/client.ts +116 -47
- package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
- package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
- package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
- package/src/plugins/builtin/options-scenario/csp.ts +8 -2
- package/src/plugins/builtin/options-scenario/index.tsx +9 -7
- package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
- package/src/plugins/builtin/pe-band/model.ts +12 -49
- package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
- package/src/plugins/builtin/perps/cascade.ts +236 -0
- package/src/plugins/builtin/perps/client.ts +56 -2
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +19 -22
- package/src/plugins/builtin/perps/status-line.ts +25 -0
- package/src/plugins/builtin/plugin-module.ts +5 -1
- package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
- package/src/plugins/builtin/portfolio-list/cli/render.ts +7 -5
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +6 -7
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +0 -14
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +5 -2
- package/src/plugins/builtin/portfolio-list/table.tsx +15 -9
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/realized-vol/client.ts +13 -5
- package/src/plugins/builtin/realized-vol/evidence.ts +29 -4
- package/src/plugins/builtin/realized-vol/headless.ts +8 -4
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/model.ts +37 -6
- package/src/plugins/builtin/realized-vol/pane.tsx +11 -9
- package/src/plugins/builtin/relative-rotation/client.ts +18 -4
- package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
- package/src/plugins/builtin/relative-rotation/model.ts +16 -40
- package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
- package/src/plugins/builtin/research/event-model.ts +40 -12
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/index.tsx +7 -3
- package/src/plugins/builtin/research/relative-valuation-headless.ts +13 -26
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +59 -20
- package/src/plugins/builtin/research/relative-valuation-peers.ts +217 -20
- package/src/plugins/builtin/research/relative-valuation-settings.ts +44 -0
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
- package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
- package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
- package/src/plugins/builtin/risk-factors/data.ts +4 -2
- package/src/plugins/builtin/risk-factors/headless.ts +5 -2
- package/src/plugins/builtin/roadmap/index.tsx +28 -0
- package/src/plugins/builtin/roadmap/model.ts +67 -0
- package/src/plugins/builtin/roadmap/pane.tsx +244 -0
- package/src/plugins/builtin/scanner/flow-model.ts +50 -4
- package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
- package/src/plugins/builtin/scanner/flow-session.ts +146 -0
- package/src/plugins/builtin/scanner/headless.ts +204 -35
- package/src/plugins/builtin/scanner/index.ts +16 -5
- package/src/plugins/builtin/scanner/live-headless.ts +48 -5
- package/src/plugins/builtin/scanner/live-model.ts +11 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +3 -3
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/headless.ts +10 -1
- package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
- package/src/plugins/builtin/sec/fund-facts.ts +116 -34
- package/src/plugins/builtin/sec/headless.ts +4 -2
- package/src/plugins/builtin/sec/index.tsx +3 -0
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +16 -7
- package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
- package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
- package/src/plugins/builtin/shared/earnings-report.ts +2 -2
- package/src/plugins/builtin/shared/headless-market-data.ts +2 -2
- package/src/plugins/builtin/shared/job-posting.ts +1 -0
- package/src/plugins/builtin/shared/members-menu.tsx +3 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
- package/src/plugins/builtin/shared/report-freshness.ts +11 -2
- package/src/plugins/builtin/shared/research-cells.tsx +1 -1
- package/src/plugins/builtin/shared/series-cache.ts +4 -4
- package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
- package/src/plugins/builtin/shared/ticker-company.ts +77 -0
- package/src/plugins/builtin/shared/use-quote-board.ts +2 -1
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +19 -0
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/model.ts +16 -7
- package/src/plugins/builtin/themes/pane.tsx +5 -5
- package/src/plugins/builtin/thirteenf/api.ts +12 -1
- package/src/plugins/builtin/thirteenf/data.ts +7 -7
- package/src/plugins/builtin/thirteenf/headless.ts +31 -4
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
- package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
- package/src/plugins/builtin/ticker-detail/overview/model.ts +11 -3
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +14 -8
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
- package/src/plugins/builtin/time-sales/pane.tsx +31 -19
- package/src/plugins/builtin/treasury-auctions/headless.ts +21 -12
- package/src/plugins/builtin/treasury-auctions/pane.tsx +9 -3
- package/src/plugins/builtin/treasury-daily/client.ts +41 -5
- package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
- package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
- package/src/plugins/builtin/treasury-daily/model.ts +50 -3
- package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
- package/src/plugins/builtin/vol-surface/client.ts +3 -13
- package/src/plugins/builtin/vol-surface/headless.ts +3 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -1
- package/src/plugins/builtin/vol-surface/pane.tsx +7 -5
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +7 -4
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/builtin/yield-curve/client.ts +61 -1
- package/src/plugins/builtin/yield-curve/headless.ts +9 -6
- package/src/plugins/builtin/yield-curve/index.tsx +34 -10
- package/src/plugins/builtin/yield-curve/swaps.tsx +9 -5
- package/src/plugins/catalog-browser.ts +2 -1
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/host-link.ts +1 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +11 -7
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/remote/resources.ts +2 -1
- package/src/renderers/dom/data-table/row.tsx +26 -0
- package/src/renderers/dom/desktop/icons.tsx +7 -0
- package/src/renderers/dom/desktop/list-view.tsx +13 -10
- package/src/renderers/dom/desktop/query-bar.tsx +4 -2
- package/src/renderers/dom/desktop/select-field.tsx +4 -2
- package/src/renderers/dom/host/chart-surface.tsx +21 -0
- package/src/renderers/electrobun/bun/desktop/app-name.ts +3 -3
- package/src/renderers/electrobun/bun/desktop/backend.ts +21 -10
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +15 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/installer-folder.ts +731 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +102 -10
- package/src/renderers/electrobun/bun/desktop/update.ts +115 -14
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/bun/desktop/windows-script.ts +92 -0
- package/src/renderers/electrobun/bun/index.ts +26 -2
- package/src/renderers/electrobun/bun/window/create.ts +43 -4
- package/src/renderers/electrobun/bun/window/events.ts +20 -0
- package/src/renderers/electrobun/bun/window/windows-custom-chrome.ts +9 -2
- package/src/renderers/electrobun/bun/window/windows-native.ts +78 -0
- package/src/renderers/electrobun/bun/window/windows-quit.ts +57 -0
- package/src/renderers/electrobun/shared/app-name.ts +13 -0
- package/src/renderers/electrobun/shared/protocol.ts +12 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +5 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/ui-host.tsx +8 -0
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- package/src/series/registry.ts +1 -0
- package/src/series/types.ts +6 -0
- package/src/series/validate.ts +1 -0
- package/src/sources/gloomberb-cloud/fred-series.ts +3 -1
- package/src/sources/gloomberb-cloud/index.ts +43 -8
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/index.ts +8 -5
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/controller.ts +4 -3
- package/src/sync/core-contributors.ts +90 -31
- package/src/telemetry/stall-reports-dom.ts +38 -0
- package/src/telemetry/stall-reports-node.ts +75 -0
- package/src/telemetry/stall-reports.ts +214 -0
- package/src/test-support/preload.ts +14 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name-listing.ts +53 -13
- package/src/tickers/company-name.ts +50 -4
- package/src/tickers/index-roots.ts +7 -0
- package/src/tickers/listing-fate.ts +2 -2
- package/src/tickers/listing-venues.ts +15 -3
- package/src/tickers/search/index.ts +6 -2
- package/src/tickers/search/ranking.ts +5 -1
- package/src/tickers/search/upsert.ts +13 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/tickers/ticker-changes.ts +8 -4
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +1 -1
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/fundamentals.ts +44 -1
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/resolve-context.ts +3 -1
- package/src/time-series/resolve.ts +56 -4
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +3 -2
- package/src/time-series/studies.ts +8 -2
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +2 -3
- package/src/time-series/types.ts +22 -1
- package/src/types/company-from.ts +40 -0
- package/src/types/data-provider.ts +7 -0
- package/src/types/financials.ts +59 -1
- package/src/types/headless.ts +27 -3
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +9 -0
- package/src/ui/host.tsx +3 -0
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +165 -7
- package/src/utils/declared-dividends.ts +1 -1
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +60 -8
- package/src/utils/fx-market-hours.ts +26 -3
- package/src/utils/main-thread-stall.ts +35 -2
- package/src/utils/market-capitalization.ts +24 -1
- package/src/utils/price-history.ts +7 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/version.ts +2 -2
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
|
@@ -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
|
|
@@ -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
|
</>
|
|
@@ -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: {
|
|
@@ -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
|
+
}
|
|
@@ -5,7 +5,7 @@ import { resolveEntryData } from "../../../market-data/selectors";
|
|
|
5
5
|
import type { DataProvider } from "../../../types/data-provider";
|
|
6
6
|
import type { Quote } from "../../../types/financials";
|
|
7
7
|
import { quoteChangeBasis, type QuoteChangeBasis } from "../../../market-data/market/change-basis";
|
|
8
|
-
import { fxLegMoveReference, fxLegs, fxLegSavedAt, liveFxLegEntry, type FxLeg } from "./live-legs";
|
|
8
|
+
import { fxBoardMoveDay, fxLegMoveReference, fxLegs, fxLegSavedAt, liveFxLegEntry, type FxLeg } from "./live-legs";
|
|
9
9
|
|
|
10
10
|
/** One USD leg as the board reads it: the rate it draws and where that rate came from. */
|
|
11
11
|
export interface FxLegReading {
|
|
@@ -17,7 +17,8 @@ export interface FxLegReading {
|
|
|
17
17
|
/**
|
|
18
18
|
* USD per unit at the pair quote's previous daily close, for the rate drawn
|
|
19
19
|
* whether it is the quote's or a loaded rate of the same FX trading day
|
|
20
|
-
* (fxLegMoveReference); null when the quote is stale or
|
|
20
|
+
* (fxLegMoveReference); null when the quote is stale, has none, or is not
|
|
21
|
+
* of the board's trading day (fxBoardMoveDay).
|
|
21
22
|
*/
|
|
22
23
|
reference: number | null;
|
|
23
24
|
/** The close `reference` is, as `quote` names it ("vs Wed 21 Oct close"). */
|
|
@@ -51,12 +52,14 @@ export async function loadFxBoard(
|
|
|
51
52
|
coordinator.loadQuotesBatch(legs.map((leg) => ({ symbol: leg.symbol, exchange: "" })), { forceRefresh })
|
|
52
53
|
.catch(logFailure("fx-matrix.client", "Load FX pair quotes failed", [] as QueryEntry<Quote>[])),
|
|
53
54
|
]);
|
|
55
|
+
// Every leg moves from the board's one close, read off the clock, as the pane's tint does.
|
|
56
|
+
const boardDay = fxBoardMoveDay(quotes);
|
|
54
57
|
return new Map(legs.map((leg, index) => {
|
|
55
58
|
const rate = rates[index]!;
|
|
56
59
|
const quote = quotes[index];
|
|
57
60
|
const live = liveFxLegEntry(leg, quote, rate);
|
|
58
61
|
const entry = live ?? (resolveEntryData(rate) != null ? rate : null);
|
|
59
|
-
const close = fxLegMoveReference(leg, quote, entry);
|
|
62
|
+
const close = fxLegMoveReference(leg, quote, entry, boardDay);
|
|
60
63
|
// A pair quote or a loaded rate whose refresh failed is the copy saved before, not a current answer.
|
|
61
64
|
const savedAt = fxLegSavedAt(live ? resolveEntryData(quote) : null, rate);
|
|
62
65
|
return [leg.currency, {
|
|
@@ -94,7 +94,7 @@ export function projectFxCrosses(
|
|
|
94
94
|
}, saved.length > 0 ? Math.min(...saved) : Number.NaN);
|
|
95
95
|
}));
|
|
96
96
|
|
|
97
|
-
// The
|
|
97
|
+
// The close the moves are read from: the board's one trading day (fxBoardMoveDay), never mixed closes.
|
|
98
98
|
const moveBasis = combinedChangeBasisLabel(currencies.map((currency) => readings.get(currency)?.changeBasis));
|
|
99
99
|
const unavailable = currencies.filter((currency) => !rates.has(currency));
|
|
100
100
|
const errors = unavailable.flatMap((currency) => {
|
|
@@ -26,7 +26,7 @@ import { fxRateObservations } from "../shared/fx-rate-freshness";
|
|
|
26
26
|
import { useLiveQuoteEntries } from "../../../state/hooks/quote-streaming";
|
|
27
27
|
import { useAutoRefresh, useUpdatedAgo } from "../../../react/auto-refresh";
|
|
28
28
|
import { useLiveStreamingSetting } from "../../../state/hooks/live-streaming";
|
|
29
|
-
import { fxLegMoveReference, fxLegQuoteKey, fxLegReferenceRate, fxLegSavedAt, fxLegTargets, fxLegs, fxLegsBehind, liveFxLegEntry } from "./live-legs";
|
|
29
|
+
import { fxBoardMoveDay, fxLegMoveReference, fxLegQuoteKey, fxLegReferenceRate, fxLegSavedAt, fxLegTargets, fxLegs, fxLegsBehind, liveFxLegEntry } from "./live-legs";
|
|
30
30
|
import { CURRENCY_FLAG_REGIONS, FX_CURRENCIES, formatRate, resolveCurrencies, type FxCurrency } from "./pairs";
|
|
31
31
|
import { crossMovePercent, directionTint, nextTintLevel } from "./direction";
|
|
32
32
|
import { fxMatrixSettings } from "./settings";
|
|
@@ -101,9 +101,11 @@ function FxMatrixPane({ focused, width, height }: PaneProps) {
|
|
|
101
101
|
const directionReferences = useMemo(() => {
|
|
102
102
|
const coordinator = getSharedMarketDataCoordinator();
|
|
103
103
|
const references = new Map<string, number>([["USD", 1]]);
|
|
104
|
+
// One close for the board, read off the clock (fxBoardMoveDay): a leg on another day has no move.
|
|
105
|
+
const boardDay = fxBoardMoveDay(legs.map((leg) => legEntries.get(fxLegQuoteKey(leg))), Date.now());
|
|
104
106
|
for (const leg of legs) {
|
|
105
107
|
const drawn = liveEntries.get(leg.currency) ?? coordinator?.getFxEntry(leg.currency);
|
|
106
|
-
const reference = fxLegMoveReference(leg, legEntries.get(fxLegQuoteKey(leg)), drawn);
|
|
108
|
+
const reference = fxLegMoveReference(leg, legEntries.get(fxLegQuoteKey(leg)), drawn, boardDay);
|
|
107
109
|
if (reference) references.set(leg.currency, reference.rate);
|
|
108
110
|
}
|
|
109
111
|
return references;
|
|
@@ -3,7 +3,8 @@ import { buildQuoteKey, resolveEntryData } from "../../../market-data/selectors"
|
|
|
3
3
|
import type { QuoteSubscriptionTarget } from "../../../types/data-provider";
|
|
4
4
|
import type { Quote } from "../../../types/financials";
|
|
5
5
|
import { isStreamCarryingQuote } from "../shared/use-quote-board";
|
|
6
|
-
import { fxTradingDay } from "../../../utils/fx-market-hours";
|
|
6
|
+
import { fxPrintTradingDay, fxTradingDay, fxTradingDayAt } from "../../../utils/fx-market-hours";
|
|
7
|
+
import { delayedFeedLagMs } from "../../../market-data/market/freshness";
|
|
7
8
|
import { savedCopyAt } from "../../../data/saved-copies";
|
|
8
9
|
|
|
9
10
|
/** Currencies the market quotes as USD per unit; the rest quote as units per USD. */
|
|
@@ -106,22 +107,60 @@ export function fxLegReferenceRate(leg: FxLeg, entry: QueryEntry<Quote> | undefi
|
|
|
106
107
|
return leg.inverted ? 1 / close : close;
|
|
107
108
|
}
|
|
108
109
|
|
|
110
|
+
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The FX trading day a pair quote's move is of: the day its source dated
|
|
114
|
+
* (`changeSessionDate`), else the day of its print, which turns at 17:00 New
|
|
115
|
+
* York. Null for a stale quote, or an undated print made while the market was
|
|
116
|
+
* shut, which is of no trading day.
|
|
117
|
+
*/
|
|
118
|
+
function fxLegMoveDay(quote: Quote | null | undefined): string | null {
|
|
119
|
+
if (!quote || quote.stale === true || !Number.isFinite(quote.lastUpdated)) return null;
|
|
120
|
+
if (quote.changeSessionDate && ISO_DATE.test(quote.changeSessionDate)) return quote.changeSessionDate;
|
|
121
|
+
return fxPrintTradingDay(quote.lastUpdated);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** How far behind the present a leg's quote is held: a delayed feed's lag, none for a live one. */
|
|
125
|
+
function legDelayMs(quote: Quote | null | undefined): number {
|
|
126
|
+
return quote?.dataSource === "delayed" ? delayedFeedLagMs(quote.listingExchangeName || quote.exchangeName || "CCY") : 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* The one FX trading day a board's moves are read on, from the clock: the
|
|
131
|
+
* day the market is on as the legs' feed shows it, `now` less the longest
|
|
132
|
+
* delay among their quotes, or the Friday that closed the week while FX is
|
|
133
|
+
* shut (fxTradingDayAt). A leg's quote is never trusted to set it, so a leg
|
|
134
|
+
* dated ahead of the clock, or on a weekend, cannot move the board's close:
|
|
135
|
+
* only its own move goes blank (fxLegMoveReference).
|
|
136
|
+
*/
|
|
137
|
+
export function fxBoardMoveDay(quotes: Iterable<QueryEntry<Quote> | undefined>, now = Date.now()): string | null {
|
|
138
|
+
let delay = 0;
|
|
139
|
+
for (const entry of quotes) delay = Math.max(delay, legDelayMs(resolveEntryData(entry)));
|
|
140
|
+
return fxTradingDayAt(now - delay);
|
|
141
|
+
}
|
|
142
|
+
|
|
109
143
|
/**
|
|
110
144
|
* What a leg's move is read against: its pair quote's previous daily close
|
|
111
145
|
* (USD per unit), the close `quote` measures the pair from, whichever rate
|
|
112
146
|
* the board draws. A loaded rate a minute newer than the quote moves from the
|
|
113
147
|
* same close, so it reads against it too, as long as it is of the quote's FX
|
|
114
|
-
* trading day. Null when the quote is stale or carries no previous close,
|
|
115
|
-
* when the drawn rate is of a later trading day (or undated) than the quote
|
|
148
|
+
* trading day. Null when the quote is stale or carries no previous close,
|
|
149
|
+
* when the drawn rate is of a later trading day (or undated) than the quote,
|
|
150
|
+
* or when the quote's move is not of the board's day (`boardDay`,
|
|
151
|
+
* fxBoardMoveDay): a quote dated ahead of it or on a weekend, or a thin pair
|
|
152
|
+
* that has not printed since the day turned, whose move is the day before's.
|
|
116
153
|
*/
|
|
117
154
|
export function fxLegMoveReference(
|
|
118
155
|
leg: FxLeg,
|
|
119
156
|
entry: QueryEntry<Quote> | undefined,
|
|
120
157
|
drawn: Pick<QueryEntry<number>, "asOf"> | null | undefined,
|
|
158
|
+
boardDay: string | null,
|
|
121
159
|
): { rate: number; quote: Quote } | null {
|
|
122
160
|
const quote = resolveEntryData(entry);
|
|
123
161
|
const rate = fxLegReferenceRate(leg, entry);
|
|
124
162
|
if (!quote || quote.stale === true || rate == null || !Number.isFinite(quote.lastUpdated)) return null;
|
|
163
|
+
if (boardDay == null || fxLegMoveDay(quote) !== boardDay) return null;
|
|
125
164
|
const drawnAt = drawn?.asOf;
|
|
126
165
|
if (drawnAt == null || !Number.isFinite(drawnAt) || fxTradingDay(drawnAt) !== fxTradingDay(quote.lastUpdated)) return null;
|
|
127
166
|
return { rate, quote };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { GpuObservation } from "../../../api-client/gpu";
|
|
2
2
|
import type { HeadlessPaneDefinition, HeadlessPaneRow } from "../../../types/plugin";
|
|
3
|
-
import { formatUtcTime } from "../../../utils/utc-time";
|
|
3
|
+
import { formatUtcTime, parseReportTime } from "../../../utils/utc-time";
|
|
4
4
|
import { fetchGpuBoard, fetchGpuEvents, fetchGpuHistory, fetchGpuModelPreviews, gpuPreviewModels, loadGpuEquityHistory } from "./client";
|
|
5
5
|
import {
|
|
6
6
|
GPU_TABS, gpuBasisLabel, gpuChange, gpuChangesLocked, gpuEquityGap, gpuEquityModels, gpuEquityRows, gpuEventDate, gpuFamilies, gpuFamily,
|
|
@@ -16,7 +16,13 @@ const observationRow = (row: GpuObservation) => ({ gpu: gpuLabel(row), source: g
|
|
|
16
16
|
const percent = (value: unknown) => gpuChange(typeof value === "number" ? value : null);
|
|
17
17
|
/** Prices to the cent and times to the minute, as the pane draws them; exports keep the full value. */
|
|
18
18
|
const price = (value: unknown) => typeof value === "number" ? gpuPrice(value) : "-";
|
|
19
|
-
const
|
|
19
|
+
const DAY_MS = 86_400_000;
|
|
20
|
+
/** A reference index's daily figure is stamped at 00:00 UTC: that is a day, not a time to the minute. */
|
|
21
|
+
const minute = (value: unknown) => {
|
|
22
|
+
if (typeof value !== "string") return "-";
|
|
23
|
+
const time = parseReportTime(value);
|
|
24
|
+
return time && !time.dateOnly && time.time % DAY_MS === 0 ? formatUtcTime({ ...time, dateOnly: true }) : formatUtcTime(value);
|
|
25
|
+
};
|
|
20
26
|
/** A move the free preview leaves to Pro says so, instead of reading as missing. */
|
|
21
27
|
const change = (value: unknown, row: HeadlessPaneRow) => row.changesLocked === true ? "Pro" : percent(value);
|
|
22
28
|
const columns = [
|
|
@@ -24,6 +24,8 @@ export interface HelpFunction {
|
|
|
24
24
|
argPlaceholder?: string;
|
|
25
25
|
argOptional?: boolean;
|
|
26
26
|
help: FunctionHelp | null;
|
|
27
|
+
/** Left out of `HELP <words>` matches, as of every list (`listed: false`); `HELP` with its code still answers. */
|
|
28
|
+
unlisted?: true;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export interface HelpFunctionSources {
|
|
@@ -97,6 +99,7 @@ export function buildHelpFunctionIndex(sources: HelpFunctionSources): HelpFuncti
|
|
|
97
99
|
argPlaceholder: template.shortcut?.argPlaceholder
|
|
98
100
|
?? (template.shortcut?.argKind === "ticker-list" ? "tickers" : template.shortcut?.argKind),
|
|
99
101
|
argOptional: template.shortcut?.argOptional,
|
|
102
|
+
...(template.listed === false ? { unlisted: true as const } : {}),
|
|
100
103
|
}));
|
|
101
104
|
}
|
|
102
105
|
|
|
@@ -165,7 +168,7 @@ export function parseHelpArgument(arg: string, index: HelpFunctionIndex): HelpRe
|
|
|
165
168
|
|
|
166
169
|
const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
167
170
|
const matches = listHelpFunctions(index)
|
|
168
|
-
.filter((fn) => fn.code !== HELP_CODE)
|
|
171
|
+
.filter((fn) => fn.code !== HELP_CODE && !fn.unlisted)
|
|
169
172
|
.map((fn) => ({ fn, score: searchScore(fn, terms) }))
|
|
170
173
|
.filter(({ score }) => score > 0)
|
|
171
174
|
.sort((left, right) => right.score - left.score || left.fn.code.localeCompare(right.fn.code))
|